diff --git a/src/Laravel/EventListener/ThemeLivewireListener.php b/src/Laravel/EventListener/ThemeLivewireListener.php new file mode 100644 index 00000000..e97eb4c9 --- /dev/null +++ b/src/Laravel/EventListener/ThemeLivewireListener.php @@ -0,0 +1,80 @@ +getPresenter()) { + return; + } + + $response = $event->getResponse() ?: ''; + if (!\is_string($response)) { + return; + } + + // Avoid duplicate script injection + if (false === strripos($response, ' +JAVASCRIPT; + + $event->setResponse($response); + } + + public function getSubscribedEvents(): string + { + return ResponseEvent::class; + } +} diff --git a/src/Laravel/FlasherServiceProvider.php b/src/Laravel/FlasherServiceProvider.php index aa2d6cb8..411398c2 100644 --- a/src/Laravel/FlasherServiceProvider.php +++ b/src/Laravel/FlasherServiceProvider.php @@ -8,6 +8,7 @@ use Flasher\Laravel\Command\InstallCommand; use Flasher\Laravel\Component\FlasherComponent; use Flasher\Laravel\EventListener\LivewireListener; use Flasher\Laravel\EventListener\OctaneListener; +use Flasher\Laravel\EventListener\ThemeLivewireListener; use Flasher\Laravel\Middleware\FlasherMiddleware; use Flasher\Laravel\Middleware\SessionMiddleware; use Flasher\Laravel\Storage\SessionBag; @@ -17,6 +18,7 @@ use Flasher\Laravel\Translation\Translator; use Flasher\Prime\Asset\AssetManager; use Flasher\Prime\Container\FlasherContainer; use Flasher\Prime\EventDispatcher\EventDispatcher; +use Flasher\Prime\EventDispatcher\EventDispatcherInterface; use Flasher\Prime\EventDispatcher\EventListener\ApplyPresetListener; use Flasher\Prime\EventDispatcher\EventListener\NotificationLoggerListener; use Flasher\Prime\EventDispatcher\EventListener\TranslationListener; @@ -317,5 +319,16 @@ final class FlasherServiceProvider extends PluginServiceProvider $livewire->listen('dehydrate', new LivewireListener($livewire, $flasher, $cspHandler, $request)); }); + + $this->registerThemeLivewireListener(); + } + + private function registerThemeLivewireListener(): void + { + $this->app->extend('flasher.event_dispatcher', static function (EventDispatcherInterface $dispatcher) { + $dispatcher->addListener(new ThemeLivewireListener()); + + return $dispatcher; + }); } } diff --git a/src/Noty/Laravel/FlasherNotyServiceProvider.php b/src/Noty/Laravel/FlasherNotyServiceProvider.php index 4322dcae..4722cdab 100644 --- a/src/Noty/Laravel/FlasherNotyServiceProvider.php +++ b/src/Noty/Laravel/FlasherNotyServiceProvider.php @@ -6,6 +6,7 @@ namespace Flasher\Noty\Laravel; use Flasher\Laravel\Support\PluginServiceProvider; use Flasher\Noty\Prime\NotyPlugin; +use Flasher\Prime\EventDispatcher\EventDispatcherInterface; final class FlasherNotyServiceProvider extends PluginServiceProvider { @@ -13,4 +14,22 @@ final class FlasherNotyServiceProvider extends PluginServiceProvider { return new NotyPlugin(); } + + protected function afterBoot(): void + { + $this->registerLivewireListener(); + } + + private function registerLivewireListener(): void + { + if (!$this->app->bound('livewire')) { + return; + } + + $this->app->extend('flasher.event_dispatcher', static function (EventDispatcherInterface $dispatcher) { + $dispatcher->addListener(new LivewireListener()); + + return $dispatcher; + }); + } } diff --git a/src/Noty/Laravel/LivewireListener.php b/src/Noty/Laravel/LivewireListener.php new file mode 100644 index 00000000..9e97d584 --- /dev/null +++ b/src/Noty/Laravel/LivewireListener.php @@ -0,0 +1,75 @@ +getPresenter()) { + return; + } + + $response = $event->getResponse() ?: ''; + if (!\is_string($response)) { + return; + } + + // Avoid duplicate script injection + if (false === strripos($response, ' +JAVASCRIPT; + + $event->setResponse($response); + } + + public function getSubscribedEvents(): string + { + return ResponseEvent::class; + } +} diff --git a/src/Noty/Prime/Resources/assets/noty.ts b/src/Noty/Prime/Resources/assets/noty.ts index 73ece312..8fc06a17 100644 --- a/src/Noty/Prime/Resources/assets/noty.ts +++ b/src/Noty/Prime/Resources/assets/noty.ts @@ -29,6 +29,34 @@ export default class NotyPlugin extends AbstractPlugin { Object.assign(options, envelope.options) } + // Wrap callbacks to dispatch events + const originalCallbacks = { + onShow: options.callbacks?.onShow, + onClick: options.callbacks?.onClick, + onClose: options.callbacks?.onClose, + onHover: options.callbacks?.onHover, + } + + options.callbacks = { + ...options.callbacks, + onShow: () => { + this.dispatchEvent('flasher:noty:show', envelope) + originalCallbacks.onShow?.() + }, + onClick: () => { + this.dispatchEvent('flasher:noty:click', envelope) + originalCallbacks.onClick?.() + }, + onClose: () => { + this.dispatchEvent('flasher:noty:close', envelope) + originalCallbacks.onClose?.() + }, + onHover: () => { + this.dispatchEvent('flasher:noty:hover', envelope) + originalCallbacks.onHover?.() + }, + } + const noty = new Noty(options) noty.show() @@ -42,6 +70,12 @@ export default class NotyPlugin extends AbstractPlugin { }) } + private dispatchEvent(eventName: string, envelope: Envelope): void { + window.dispatchEvent(new CustomEvent(eventName, { + detail: { envelope }, + })) + } + public renderOptions(options: Options): void { if (!options) { return diff --git a/src/Noty/Prime/Resources/dist/flasher-noty.esm.js b/src/Noty/Prime/Resources/dist/flasher-noty.esm.js index 23c3e346..0fdd39b5 100644 --- a/src/Noty/Prime/Resources/dist/flasher-noty.esm.js +++ b/src/Noty/Prime/Resources/dist/flasher-noty.esm.js @@ -96,11 +96,35 @@ class NotyPlugin extends AbstractPlugin { return; } envelopes.forEach((envelope) => { + var _a, _b, _c, _d; try { const options = Object.assign({ text: envelope.message, type: envelope.type }, this.defaultOptions); if (envelope.options) { Object.assign(options, envelope.options); } + const originalCallbacks = { + onShow: (_a = options.callbacks) === null || _a === void 0 ? void 0 : _a.onShow, + onClick: (_b = options.callbacks) === null || _b === void 0 ? void 0 : _b.onClick, + onClose: (_c = options.callbacks) === null || _c === void 0 ? void 0 : _c.onClose, + onHover: (_d = options.callbacks) === null || _d === void 0 ? void 0 : _d.onHover, + }; + options.callbacks = Object.assign(Object.assign({}, options.callbacks), { onShow: () => { + var _a; + this.dispatchEvent('flasher:noty:show', envelope); + (_a = originalCallbacks.onShow) === null || _a === void 0 ? void 0 : _a.call(originalCallbacks); + }, onClick: () => { + var _a; + this.dispatchEvent('flasher:noty:click', envelope); + (_a = originalCallbacks.onClick) === null || _a === void 0 ? void 0 : _a.call(originalCallbacks); + }, onClose: () => { + var _a; + this.dispatchEvent('flasher:noty:close', envelope); + (_a = originalCallbacks.onClose) === null || _a === void 0 ? void 0 : _a.call(originalCallbacks); + }, onHover: () => { + var _a; + this.dispatchEvent('flasher:noty:hover', envelope); + (_a = originalCallbacks.onHover) === null || _a === void 0 ? void 0 : _a.call(originalCallbacks); + } }); const noty = new Noty(options); noty.show(); const layoutDom = noty.layoutDom; @@ -113,6 +137,11 @@ class NotyPlugin extends AbstractPlugin { } }); } + dispatchEvent(eventName, envelope) { + window.dispatchEvent(new CustomEvent(eventName, { + detail: { envelope }, + })); + } renderOptions(options) { if (!options) { return; diff --git a/src/Noty/Prime/Resources/dist/flasher-noty.js b/src/Noty/Prime/Resources/dist/flasher-noty.js index fa81c382..7e774494 100644 --- a/src/Noty/Prime/Resources/dist/flasher-noty.js +++ b/src/Noty/Prime/Resources/dist/flasher-noty.js @@ -99,11 +99,35 @@ return; } envelopes.forEach((envelope) => { + var _a, _b, _c, _d; try { const options = Object.assign({ text: envelope.message, type: envelope.type }, this.defaultOptions); if (envelope.options) { Object.assign(options, envelope.options); } + const originalCallbacks = { + onShow: (_a = options.callbacks) === null || _a === void 0 ? void 0 : _a.onShow, + onClick: (_b = options.callbacks) === null || _b === void 0 ? void 0 : _b.onClick, + onClose: (_c = options.callbacks) === null || _c === void 0 ? void 0 : _c.onClose, + onHover: (_d = options.callbacks) === null || _d === void 0 ? void 0 : _d.onHover, + }; + options.callbacks = Object.assign(Object.assign({}, options.callbacks), { onShow: () => { + var _a; + this.dispatchEvent('flasher:noty:show', envelope); + (_a = originalCallbacks.onShow) === null || _a === void 0 ? void 0 : _a.call(originalCallbacks); + }, onClick: () => { + var _a; + this.dispatchEvent('flasher:noty:click', envelope); + (_a = originalCallbacks.onClick) === null || _a === void 0 ? void 0 : _a.call(originalCallbacks); + }, onClose: () => { + var _a; + this.dispatchEvent('flasher:noty:close', envelope); + (_a = originalCallbacks.onClose) === null || _a === void 0 ? void 0 : _a.call(originalCallbacks); + }, onHover: () => { + var _a; + this.dispatchEvent('flasher:noty:hover', envelope); + (_a = originalCallbacks.onHover) === null || _a === void 0 ? void 0 : _a.call(originalCallbacks); + } }); const noty = new Noty(options); noty.show(); const layoutDom = noty.layoutDom; @@ -116,6 +140,11 @@ } }); } + dispatchEvent(eventName, envelope) { + window.dispatchEvent(new CustomEvent(eventName, { + detail: { envelope }, + })); + } renderOptions(options) { if (!options) { return; diff --git a/src/Noty/Prime/Resources/dist/flasher-noty.min.js b/src/Noty/Prime/Resources/dist/flasher-noty.min.js index 496f65df..be4c28ee 100644 --- a/src/Noty/Prime/Resources/dist/flasher-noty.min.js +++ b/src/Noty/Prime/Resources/dist/flasher-noty.min.js @@ -1 +1 @@ -!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t(require("@flasher/flasher"),require("noty")):"function"==typeof define&&define.amd?define(["@flasher/flasher","noty"],t):(e="undefined"!=typeof globalThis?globalThis:e||self).Noty=t(e.flasher,e.Noty)}(this,(function(e,t){"use strict";class s{success(e,t,s){this.flash("success",e,t,s)}error(e,t,s){this.flash("error",e,t,s)}info(e,t,s){this.flash("info",e,t,s)}warning(e,t,s){this.flash("warning",e,t,s)}flash(e,t,s,o){let i,n,r,l={};if("object"==typeof e?(l=Object.assign({},e),i=l.type,n=l.message,r=l.title,delete l.type,delete l.message,delete l.title):"object"==typeof t?(l=Object.assign({},t),i=e,n=l.message,r=l.title,delete l.message,delete l.title):(i=e,n=t,null==s?(r=void 0,l=o||{}):"string"==typeof s?(r=s,l=o||{}):"object"==typeof s&&(l=Object.assign({},s),"title"in l?(r=l.title,delete l.title):r=void 0,o&&"object"==typeof o&&(l=Object.assign(Object.assign({},l),o)))),!i)throw new Error("Type is required for notifications");if(null==n)throw new Error("Message is required for notifications");null==r&&(r=i.charAt(0).toUpperCase()+i.slice(1));const a={type:i,message:n,title:r,options:l,metadata:{plugin:""}};this.renderOptions({}),this.renderEnvelopes([a])}}const o=new class extends s{constructor(){super(...arguments),this.defaultOptions={timeout:1e4}}renderEnvelopes(e){(null==e?void 0:e.length)&&e.forEach((e=>{try{const s=Object.assign({text:e.message,type:e.type},this.defaultOptions);e.options&&Object.assign(s,e.options);const o=new t(s);o.show();const i=o.layoutDom;i&&"object"==typeof i.dataset&&(i.dataset.turboTemporary="")}catch(t){console.error("PHPFlasher Noty: Error rendering notification",t,e)}}))}renderOptions(e){e&&(Object.assign(this.defaultOptions,e),t.overrideDefaults(this.defaultOptions))}};return e.addPlugin("noty",o),o})); +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t(require("@flasher/flasher"),require("noty")):"function"==typeof define&&define.amd?define(["@flasher/flasher","noty"],t):(e="undefined"!=typeof globalThis?globalThis:e||self).Noty=t(e.flasher,e.Noty)}(this,(function(e,t){"use strict";class o{success(e,t,o){this.flash("success",e,t,o)}error(e,t,o){this.flash("error",e,t,o)}info(e,t,o){this.flash("info",e,t,o)}warning(e,t,o){this.flash("warning",e,t,o)}flash(e,t,o,s){let n,i,l,a={};if("object"==typeof e?(a=Object.assign({},e),n=a.type,i=a.message,l=a.title,delete a.type,delete a.message,delete a.title):"object"==typeof t?(a=Object.assign({},t),n=e,i=a.message,l=a.title,delete a.message,delete a.title):(n=e,i=t,null==o?(l=void 0,a=s||{}):"string"==typeof o?(l=o,a=s||{}):"object"==typeof o&&(a=Object.assign({},o),"title"in a?(l=a.title,delete a.title):l=void 0,s&&"object"==typeof s&&(a=Object.assign(Object.assign({},a),s)))),!n)throw new Error("Type is required for notifications");if(null==i)throw new Error("Message is required for notifications");null==l&&(l=n.charAt(0).toUpperCase()+n.slice(1));const r={type:n,message:i,title:l,options:a,metadata:{plugin:""}};this.renderOptions({}),this.renderEnvelopes([r])}}const s=new class extends o{constructor(){super(...arguments),this.defaultOptions={timeout:1e4}}renderEnvelopes(e){(null==e?void 0:e.length)&&e.forEach((e=>{var o,s,n,i;try{const l=Object.assign({text:e.message,type:e.type},this.defaultOptions);e.options&&Object.assign(l,e.options);const a={onShow:null===(o=l.callbacks)||void 0===o?void 0:o.onShow,onClick:null===(s=l.callbacks)||void 0===s?void 0:s.onClick,onClose:null===(n=l.callbacks)||void 0===n?void 0:n.onClose,onHover:null===(i=l.callbacks)||void 0===i?void 0:i.onHover};l.callbacks=Object.assign(Object.assign({},l.callbacks),{onShow:()=>{var t;this.dispatchEvent("flasher:noty:show",e),null===(t=a.onShow)||void 0===t||t.call(a)},onClick:()=>{var t;this.dispatchEvent("flasher:noty:click",e),null===(t=a.onClick)||void 0===t||t.call(a)},onClose:()=>{var t;this.dispatchEvent("flasher:noty:close",e),null===(t=a.onClose)||void 0===t||t.call(a)},onHover:()=>{var t;this.dispatchEvent("flasher:noty:hover",e),null===(t=a.onHover)||void 0===t||t.call(a)}});const r=new t(l);r.show();const c=r.layoutDom;c&&"object"==typeof c.dataset&&(c.dataset.turboTemporary="")}catch(t){console.error("PHPFlasher Noty: Error rendering notification",t,e)}}))}dispatchEvent(e,t){window.dispatchEvent(new CustomEvent(e,{detail:{envelope:t}}))}renderOptions(e){e&&(Object.assign(this.defaultOptions,e),t.overrideDefaults(this.defaultOptions))}};return e.addPlugin("noty",s),s})); diff --git a/src/Noty/Prime/Resources/dist/noty.d.ts b/src/Noty/Prime/Resources/dist/noty.d.ts index 2c6b19e4..7ca6adf4 100644 --- a/src/Noty/Prime/Resources/dist/noty.d.ts +++ b/src/Noty/Prime/Resources/dist/noty.d.ts @@ -3,5 +3,6 @@ import type { Envelope, Options } from '@flasher/flasher/dist/types'; export default class NotyPlugin extends AbstractPlugin { private defaultOptions; renderEnvelopes(envelopes: Envelope[]): void; + private dispatchEvent; renderOptions(options: Options): void; } diff --git a/src/Noty/Prime/Resources/public/flasher-noty.min.js b/src/Noty/Prime/Resources/public/flasher-noty.min.js index 496f65df..be4c28ee 100644 --- a/src/Noty/Prime/Resources/public/flasher-noty.min.js +++ b/src/Noty/Prime/Resources/public/flasher-noty.min.js @@ -1 +1 @@ -!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t(require("@flasher/flasher"),require("noty")):"function"==typeof define&&define.amd?define(["@flasher/flasher","noty"],t):(e="undefined"!=typeof globalThis?globalThis:e||self).Noty=t(e.flasher,e.Noty)}(this,(function(e,t){"use strict";class s{success(e,t,s){this.flash("success",e,t,s)}error(e,t,s){this.flash("error",e,t,s)}info(e,t,s){this.flash("info",e,t,s)}warning(e,t,s){this.flash("warning",e,t,s)}flash(e,t,s,o){let i,n,r,l={};if("object"==typeof e?(l=Object.assign({},e),i=l.type,n=l.message,r=l.title,delete l.type,delete l.message,delete l.title):"object"==typeof t?(l=Object.assign({},t),i=e,n=l.message,r=l.title,delete l.message,delete l.title):(i=e,n=t,null==s?(r=void 0,l=o||{}):"string"==typeof s?(r=s,l=o||{}):"object"==typeof s&&(l=Object.assign({},s),"title"in l?(r=l.title,delete l.title):r=void 0,o&&"object"==typeof o&&(l=Object.assign(Object.assign({},l),o)))),!i)throw new Error("Type is required for notifications");if(null==n)throw new Error("Message is required for notifications");null==r&&(r=i.charAt(0).toUpperCase()+i.slice(1));const a={type:i,message:n,title:r,options:l,metadata:{plugin:""}};this.renderOptions({}),this.renderEnvelopes([a])}}const o=new class extends s{constructor(){super(...arguments),this.defaultOptions={timeout:1e4}}renderEnvelopes(e){(null==e?void 0:e.length)&&e.forEach((e=>{try{const s=Object.assign({text:e.message,type:e.type},this.defaultOptions);e.options&&Object.assign(s,e.options);const o=new t(s);o.show();const i=o.layoutDom;i&&"object"==typeof i.dataset&&(i.dataset.turboTemporary="")}catch(t){console.error("PHPFlasher Noty: Error rendering notification",t,e)}}))}renderOptions(e){e&&(Object.assign(this.defaultOptions,e),t.overrideDefaults(this.defaultOptions))}};return e.addPlugin("noty",o),o})); +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t(require("@flasher/flasher"),require("noty")):"function"==typeof define&&define.amd?define(["@flasher/flasher","noty"],t):(e="undefined"!=typeof globalThis?globalThis:e||self).Noty=t(e.flasher,e.Noty)}(this,(function(e,t){"use strict";class o{success(e,t,o){this.flash("success",e,t,o)}error(e,t,o){this.flash("error",e,t,o)}info(e,t,o){this.flash("info",e,t,o)}warning(e,t,o){this.flash("warning",e,t,o)}flash(e,t,o,s){let n,i,l,a={};if("object"==typeof e?(a=Object.assign({},e),n=a.type,i=a.message,l=a.title,delete a.type,delete a.message,delete a.title):"object"==typeof t?(a=Object.assign({},t),n=e,i=a.message,l=a.title,delete a.message,delete a.title):(n=e,i=t,null==o?(l=void 0,a=s||{}):"string"==typeof o?(l=o,a=s||{}):"object"==typeof o&&(a=Object.assign({},o),"title"in a?(l=a.title,delete a.title):l=void 0,s&&"object"==typeof s&&(a=Object.assign(Object.assign({},a),s)))),!n)throw new Error("Type is required for notifications");if(null==i)throw new Error("Message is required for notifications");null==l&&(l=n.charAt(0).toUpperCase()+n.slice(1));const r={type:n,message:i,title:l,options:a,metadata:{plugin:""}};this.renderOptions({}),this.renderEnvelopes([r])}}const s=new class extends o{constructor(){super(...arguments),this.defaultOptions={timeout:1e4}}renderEnvelopes(e){(null==e?void 0:e.length)&&e.forEach((e=>{var o,s,n,i;try{const l=Object.assign({text:e.message,type:e.type},this.defaultOptions);e.options&&Object.assign(l,e.options);const a={onShow:null===(o=l.callbacks)||void 0===o?void 0:o.onShow,onClick:null===(s=l.callbacks)||void 0===s?void 0:s.onClick,onClose:null===(n=l.callbacks)||void 0===n?void 0:n.onClose,onHover:null===(i=l.callbacks)||void 0===i?void 0:i.onHover};l.callbacks=Object.assign(Object.assign({},l.callbacks),{onShow:()=>{var t;this.dispatchEvent("flasher:noty:show",e),null===(t=a.onShow)||void 0===t||t.call(a)},onClick:()=>{var t;this.dispatchEvent("flasher:noty:click",e),null===(t=a.onClick)||void 0===t||t.call(a)},onClose:()=>{var t;this.dispatchEvent("flasher:noty:close",e),null===(t=a.onClose)||void 0===t||t.call(a)},onHover:()=>{var t;this.dispatchEvent("flasher:noty:hover",e),null===(t=a.onHover)||void 0===t||t.call(a)}});const r=new t(l);r.show();const c=r.layoutDom;c&&"object"==typeof c.dataset&&(c.dataset.turboTemporary="")}catch(t){console.error("PHPFlasher Noty: Error rendering notification",t,e)}}))}dispatchEvent(e,t){window.dispatchEvent(new CustomEvent(e,{detail:{envelope:t}}))}renderOptions(e){e&&(Object.assign(this.defaultOptions,e),t.overrideDefaults(this.defaultOptions))}};return e.addPlugin("noty",s),s})); diff --git a/src/Notyf/Laravel/FlasherNotyfServiceProvider.php b/src/Notyf/Laravel/FlasherNotyfServiceProvider.php index d43ca3a4..0f93b817 100644 --- a/src/Notyf/Laravel/FlasherNotyfServiceProvider.php +++ b/src/Notyf/Laravel/FlasherNotyfServiceProvider.php @@ -6,6 +6,7 @@ namespace Flasher\Notyf\Laravel; use Flasher\Laravel\Support\PluginServiceProvider; use Flasher\Notyf\Prime\NotyfPlugin; +use Flasher\Prime\EventDispatcher\EventDispatcherInterface; final class FlasherNotyfServiceProvider extends PluginServiceProvider { @@ -13,4 +14,22 @@ final class FlasherNotyfServiceProvider extends PluginServiceProvider { return new NotyfPlugin(); } + + protected function afterBoot(): void + { + $this->registerLivewireListener(); + } + + private function registerLivewireListener(): void + { + if (!$this->app->bound('livewire')) { + return; + } + + $this->app->extend('flasher.event_dispatcher', static function (EventDispatcherInterface $dispatcher) { + $dispatcher->addListener(new LivewireListener()); + + return $dispatcher; + }); + } } diff --git a/src/Notyf/Laravel/LivewireListener.php b/src/Notyf/Laravel/LivewireListener.php new file mode 100644 index 00000000..4710930d --- /dev/null +++ b/src/Notyf/Laravel/LivewireListener.php @@ -0,0 +1,75 @@ +getPresenter()) { + return; + } + + $response = $event->getResponse() ?: ''; + if (!\is_string($response)) { + return; + } + + // Avoid duplicate script injection + if (false === strripos($response, ' +JAVASCRIPT; + + $event->setResponse($response); + } + + public function getSubscribedEvents(): string + { + return ResponseEvent::class; + } +} diff --git a/src/Notyf/Prime/Resources/assets/notyf.ts b/src/Notyf/Prime/Resources/assets/notyf.ts index 373d2482..cf998cae 100644 --- a/src/Notyf/Prime/Resources/assets/notyf.ts +++ b/src/Notyf/Prime/Resources/assets/notyf.ts @@ -16,7 +16,11 @@ export default class NotyfPlugin extends AbstractPlugin { envelopes.forEach((envelope) => { try { const options = { ...envelope, ...envelope.options } - this.notyf?.open(options) + const notification = this.notyf?.open(options) + + if (notification) { + this.attachEventListeners(notification, envelope) + } } catch (error) { console.error('PHPFlasher Notyf: Error rendering notification', error, envelope) } @@ -92,4 +96,35 @@ export default class NotyfPlugin extends AbstractPlugin { types.push(newType) } } + + private attachEventListeners(notification: unknown, envelope: Envelope): void { + if (!this.notyf) { + return + } + + // Notyf supports events at runtime but types don't include them + const notyf = this.notyf as unknown as { + on: (event: string, callback: (params: { target: unknown, event: Event }) => void) => void + } + + // Listen for click events + notyf.on('click', ({ target, event }) => { + if (target === notification) { + this.dispatchEvent('flasher:notyf:click', envelope, { event }) + } + }) + + // Listen for dismiss events + notyf.on('dismiss', ({ target, event }) => { + if (target === notification) { + this.dispatchEvent('flasher:notyf:dismiss', envelope, { event }) + } + }) + } + + private dispatchEvent(eventName: string, envelope: Envelope, extra: Record = {}): void { + window.dispatchEvent(new CustomEvent(eventName, { + detail: { envelope, ...extra }, + })) + } } diff --git a/src/Notyf/Prime/Resources/dist/flasher-notyf.esm.js b/src/Notyf/Prime/Resources/dist/flasher-notyf.esm.js index bf7e61d1..d53d00c8 100644 --- a/src/Notyf/Prime/Resources/dist/flasher-notyf.esm.js +++ b/src/Notyf/Prime/Resources/dist/flasher-notyf.esm.js @@ -515,7 +515,10 @@ class NotyfPlugin extends AbstractPlugin { var _a; try { const options = Object.assign(Object.assign({}, envelope), envelope.options); - (_a = this.notyf) === null || _a === void 0 ? void 0 : _a.open(options); + const notification = (_a = this.notyf) === null || _a === void 0 ? void 0 : _a.open(options); + if (notification) { + this.attachEventListeners(notification, envelope); + } } catch (error) { console.error('PHPFlasher Notyf: Error rendering notification', error, envelope); @@ -579,6 +582,27 @@ class NotyfPlugin extends AbstractPlugin { types.push(newType); } } + attachEventListeners(notification, envelope) { + if (!this.notyf) { + return; + } + const notyf = this.notyf; + notyf.on('click', ({ target, event }) => { + if (target === notification) { + this.dispatchEvent('flasher:notyf:click', envelope, { event }); + } + }); + notyf.on('dismiss', ({ target, event }) => { + if (target === notification) { + this.dispatchEvent('flasher:notyf:dismiss', envelope, { event }); + } + }); + } + dispatchEvent(eventName, envelope, extra = {}) { + window.dispatchEvent(new CustomEvent(eventName, { + detail: Object.assign({ envelope }, extra), + })); + } } const notyf = new NotyfPlugin(); diff --git a/src/Notyf/Prime/Resources/dist/flasher-notyf.js b/src/Notyf/Prime/Resources/dist/flasher-notyf.js index 9d5f3109..3cd0bf50 100644 --- a/src/Notyf/Prime/Resources/dist/flasher-notyf.js +++ b/src/Notyf/Prime/Resources/dist/flasher-notyf.js @@ -519,7 +519,10 @@ var _a; try { const options = Object.assign(Object.assign({}, envelope), envelope.options); - (_a = this.notyf) === null || _a === void 0 ? void 0 : _a.open(options); + const notification = (_a = this.notyf) === null || _a === void 0 ? void 0 : _a.open(options); + if (notification) { + this.attachEventListeners(notification, envelope); + } } catch (error) { console.error('PHPFlasher Notyf: Error rendering notification', error, envelope); @@ -583,6 +586,27 @@ types.push(newType); } } + attachEventListeners(notification, envelope) { + if (!this.notyf) { + return; + } + const notyf = this.notyf; + notyf.on('click', ({ target, event }) => { + if (target === notification) { + this.dispatchEvent('flasher:notyf:click', envelope, { event }); + } + }); + notyf.on('dismiss', ({ target, event }) => { + if (target === notification) { + this.dispatchEvent('flasher:notyf:dismiss', envelope, { event }); + } + }); + } + dispatchEvent(eventName, envelope, extra = {}) { + window.dispatchEvent(new CustomEvent(eventName, { + detail: Object.assign({ envelope }, extra), + })); + } } const notyf = new NotyfPlugin(); diff --git a/src/Notyf/Prime/Resources/dist/flasher-notyf.min.js b/src/Notyf/Prime/Resources/dist/flasher-notyf.min.js index 8528c86d..33feb4cb 100644 --- a/src/Notyf/Prime/Resources/dist/flasher-notyf.min.js +++ b/src/Notyf/Prime/Resources/dist/flasher-notyf.min.js @@ -1 +1 @@ -!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e(require("@flasher/flasher")):"function"==typeof define&&define.amd?define(["@flasher/flasher"],e):(t="undefined"!=typeof globalThis?globalThis:t||self).notyf=e(t.flasher)}(this,(function(t){"use strict";class e{success(t,e,i){this.flash("success",t,e,i)}error(t,e,i){this.flash("error",t,e,i)}info(t,e,i){this.flash("info",t,e,i)}warning(t,e,i){this.flash("warning",t,e,i)}flash(t,e,i,n){let o,s,a,r={};if("object"==typeof t?(r=Object.assign({},t),o=r.type,s=r.message,a=r.title,delete r.type,delete r.message,delete r.title):"object"==typeof e?(r=Object.assign({},e),o=t,s=r.message,a=r.title,delete r.message,delete r.title):(o=t,s=e,null==i?(a=void 0,r=n||{}):"string"==typeof i?(a=i,r=n||{}):"object"==typeof i&&(r=Object.assign({},i),"title"in r?(a=r.title,delete r.title):a=void 0,n&&"object"==typeof n&&(r=Object.assign(Object.assign({},r),n)))),!o)throw new Error("Type is required for notifications");if(null==s)throw new Error("Message is required for notifications");null==a&&(a=o.charAt(0).toUpperCase()+o.slice(1));const c={type:o,message:s,title:a,options:r,metadata:{plugin:""}};this.renderOptions({}),this.renderEnvelopes([c])}}var i,n=function(){return n=Object.assign||function(t){for(var e,i=1,n=arguments.length;i{var e;try{const i=Object.assign(Object.assign({},t),t.options);null===(e=this.notyf)||void 0===e||e.open(i)}catch(e){console.error("PHPFlasher Notyf: Error rendering notification",e,t)}}));try{if(this.notyf){const t=this.notyf.view,e=t.container,i=t.a11yContainer;e&&e.dataset&&(e.dataset.turboTemporary=""),i&&i.dataset&&(i.dataset.turboTemporary="")}}catch(t){console.error("PHPFlasher Notyf: Error setting Turbo compatibility",t)}}renderOptions(t){if(!t)return;const e=Object.assign({duration:t.duration||1e4},t);e.types=e.types||[],this.addTypeIfNotExists(e.types,{type:"info",className:"notyf__toast--info",background:"#5784E5",icon:{className:"notyf__icon--info",tagName:"i"}}),this.addTypeIfNotExists(e.types,{type:"warning",className:"notyf__toast--warning",background:"#E3A008",icon:{className:"notyf__icon--warning",tagName:"i"}}),this.notyf=this.notyf||new p(e)}initializeNotyf(){this.notyf||this.renderOptions({duration:1e4,position:{x:"right",y:"top"},dismissible:!0})}addTypeIfNotExists(t,e){t.some((t=>t.type===e.type))||t.push(e)}};return t.addPlugin("notyf",l),l})); +!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e(require("@flasher/flasher")):"function"==typeof define&&define.amd?define(["@flasher/flasher"],e):(t="undefined"!=typeof globalThis?globalThis:t||self).notyf=e(t.flasher)}(this,(function(t){"use strict";class e{success(t,e,i){this.flash("success",t,e,i)}error(t,e,i){this.flash("error",t,e,i)}info(t,e,i){this.flash("info",t,e,i)}warning(t,e,i){this.flash("warning",t,e,i)}flash(t,e,i,n){let o,s,a,r={};if("object"==typeof t?(r=Object.assign({},t),o=r.type,s=r.message,a=r.title,delete r.type,delete r.message,delete r.title):"object"==typeof e?(r=Object.assign({},e),o=t,s=r.message,a=r.title,delete r.message,delete r.title):(o=t,s=e,null==i?(a=void 0,r=n||{}):"string"==typeof i?(a=i,r=n||{}):"object"==typeof i&&(r=Object.assign({},i),"title"in r?(a=r.title,delete r.title):a=void 0,n&&"object"==typeof n&&(r=Object.assign(Object.assign({},r),n)))),!o)throw new Error("Type is required for notifications");if(null==s)throw new Error("Message is required for notifications");null==a&&(a=o.charAt(0).toUpperCase()+o.slice(1));const c={type:o,message:s,title:a,options:r,metadata:{plugin:""}};this.renderOptions({}),this.renderEnvelopes([c])}}var i,n=function(){return n=Object.assign||function(t){for(var e,i=1,n=arguments.length;i{var e;try{const i=Object.assign(Object.assign({},t),t.options),n=null===(e=this.notyf)||void 0===e?void 0:e.open(i);n&&this.attachEventListeners(n,t)}catch(e){console.error("PHPFlasher Notyf: Error rendering notification",e,t)}}));try{if(this.notyf){const t=this.notyf.view,e=t.container,i=t.a11yContainer;e&&e.dataset&&(e.dataset.turboTemporary=""),i&&i.dataset&&(i.dataset.turboTemporary="")}}catch(t){console.error("PHPFlasher Notyf: Error setting Turbo compatibility",t)}}renderOptions(t){if(!t)return;const e=Object.assign({duration:t.duration||1e4},t);e.types=e.types||[],this.addTypeIfNotExists(e.types,{type:"info",className:"notyf__toast--info",background:"#5784E5",icon:{className:"notyf__icon--info",tagName:"i"}}),this.addTypeIfNotExists(e.types,{type:"warning",className:"notyf__toast--warning",background:"#E3A008",icon:{className:"notyf__icon--warning",tagName:"i"}}),this.notyf=this.notyf||new p(e)}initializeNotyf(){this.notyf||this.renderOptions({duration:1e4,position:{x:"right",y:"top"},dismissible:!0})}addTypeIfNotExists(t,e){t.some((t=>t.type===e.type))||t.push(e)}attachEventListeners(t,e){if(!this.notyf)return;const i=this.notyf;i.on("click",(({target:i,event:n})=>{i===t&&this.dispatchEvent("flasher:notyf:click",e,{event:n})})),i.on("dismiss",(({target:i,event:n})=>{i===t&&this.dispatchEvent("flasher:notyf:dismiss",e,{event:n})}))}dispatchEvent(t,e,i={}){window.dispatchEvent(new CustomEvent(t,{detail:Object.assign({envelope:e},i)}))}};return t.addPlugin("notyf",f),f})); diff --git a/src/Notyf/Prime/Resources/dist/notyf.d.ts b/src/Notyf/Prime/Resources/dist/notyf.d.ts index 72d07d07..728cbad5 100644 --- a/src/Notyf/Prime/Resources/dist/notyf.d.ts +++ b/src/Notyf/Prime/Resources/dist/notyf.d.ts @@ -7,4 +7,6 @@ export default class NotyfPlugin extends AbstractPlugin { renderOptions(options: Options): void; private initializeNotyf; private addTypeIfNotExists; + private attachEventListeners; + private dispatchEvent; } diff --git a/src/Notyf/Prime/Resources/public/flasher-notyf.min.js b/src/Notyf/Prime/Resources/public/flasher-notyf.min.js index 8528c86d..33feb4cb 100644 --- a/src/Notyf/Prime/Resources/public/flasher-notyf.min.js +++ b/src/Notyf/Prime/Resources/public/flasher-notyf.min.js @@ -1 +1 @@ -!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e(require("@flasher/flasher")):"function"==typeof define&&define.amd?define(["@flasher/flasher"],e):(t="undefined"!=typeof globalThis?globalThis:t||self).notyf=e(t.flasher)}(this,(function(t){"use strict";class e{success(t,e,i){this.flash("success",t,e,i)}error(t,e,i){this.flash("error",t,e,i)}info(t,e,i){this.flash("info",t,e,i)}warning(t,e,i){this.flash("warning",t,e,i)}flash(t,e,i,n){let o,s,a,r={};if("object"==typeof t?(r=Object.assign({},t),o=r.type,s=r.message,a=r.title,delete r.type,delete r.message,delete r.title):"object"==typeof e?(r=Object.assign({},e),o=t,s=r.message,a=r.title,delete r.message,delete r.title):(o=t,s=e,null==i?(a=void 0,r=n||{}):"string"==typeof i?(a=i,r=n||{}):"object"==typeof i&&(r=Object.assign({},i),"title"in r?(a=r.title,delete r.title):a=void 0,n&&"object"==typeof n&&(r=Object.assign(Object.assign({},r),n)))),!o)throw new Error("Type is required for notifications");if(null==s)throw new Error("Message is required for notifications");null==a&&(a=o.charAt(0).toUpperCase()+o.slice(1));const c={type:o,message:s,title:a,options:r,metadata:{plugin:""}};this.renderOptions({}),this.renderEnvelopes([c])}}var i,n=function(){return n=Object.assign||function(t){for(var e,i=1,n=arguments.length;i{var e;try{const i=Object.assign(Object.assign({},t),t.options);null===(e=this.notyf)||void 0===e||e.open(i)}catch(e){console.error("PHPFlasher Notyf: Error rendering notification",e,t)}}));try{if(this.notyf){const t=this.notyf.view,e=t.container,i=t.a11yContainer;e&&e.dataset&&(e.dataset.turboTemporary=""),i&&i.dataset&&(i.dataset.turboTemporary="")}}catch(t){console.error("PHPFlasher Notyf: Error setting Turbo compatibility",t)}}renderOptions(t){if(!t)return;const e=Object.assign({duration:t.duration||1e4},t);e.types=e.types||[],this.addTypeIfNotExists(e.types,{type:"info",className:"notyf__toast--info",background:"#5784E5",icon:{className:"notyf__icon--info",tagName:"i"}}),this.addTypeIfNotExists(e.types,{type:"warning",className:"notyf__toast--warning",background:"#E3A008",icon:{className:"notyf__icon--warning",tagName:"i"}}),this.notyf=this.notyf||new p(e)}initializeNotyf(){this.notyf||this.renderOptions({duration:1e4,position:{x:"right",y:"top"},dismissible:!0})}addTypeIfNotExists(t,e){t.some((t=>t.type===e.type))||t.push(e)}};return t.addPlugin("notyf",l),l})); +!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e(require("@flasher/flasher")):"function"==typeof define&&define.amd?define(["@flasher/flasher"],e):(t="undefined"!=typeof globalThis?globalThis:t||self).notyf=e(t.flasher)}(this,(function(t){"use strict";class e{success(t,e,i){this.flash("success",t,e,i)}error(t,e,i){this.flash("error",t,e,i)}info(t,e,i){this.flash("info",t,e,i)}warning(t,e,i){this.flash("warning",t,e,i)}flash(t,e,i,n){let o,s,a,r={};if("object"==typeof t?(r=Object.assign({},t),o=r.type,s=r.message,a=r.title,delete r.type,delete r.message,delete r.title):"object"==typeof e?(r=Object.assign({},e),o=t,s=r.message,a=r.title,delete r.message,delete r.title):(o=t,s=e,null==i?(a=void 0,r=n||{}):"string"==typeof i?(a=i,r=n||{}):"object"==typeof i&&(r=Object.assign({},i),"title"in r?(a=r.title,delete r.title):a=void 0,n&&"object"==typeof n&&(r=Object.assign(Object.assign({},r),n)))),!o)throw new Error("Type is required for notifications");if(null==s)throw new Error("Message is required for notifications");null==a&&(a=o.charAt(0).toUpperCase()+o.slice(1));const c={type:o,message:s,title:a,options:r,metadata:{plugin:""}};this.renderOptions({}),this.renderEnvelopes([c])}}var i,n=function(){return n=Object.assign||function(t){for(var e,i=1,n=arguments.length;i{var e;try{const i=Object.assign(Object.assign({},t),t.options),n=null===(e=this.notyf)||void 0===e?void 0:e.open(i);n&&this.attachEventListeners(n,t)}catch(e){console.error("PHPFlasher Notyf: Error rendering notification",e,t)}}));try{if(this.notyf){const t=this.notyf.view,e=t.container,i=t.a11yContainer;e&&e.dataset&&(e.dataset.turboTemporary=""),i&&i.dataset&&(i.dataset.turboTemporary="")}}catch(t){console.error("PHPFlasher Notyf: Error setting Turbo compatibility",t)}}renderOptions(t){if(!t)return;const e=Object.assign({duration:t.duration||1e4},t);e.types=e.types||[],this.addTypeIfNotExists(e.types,{type:"info",className:"notyf__toast--info",background:"#5784E5",icon:{className:"notyf__icon--info",tagName:"i"}}),this.addTypeIfNotExists(e.types,{type:"warning",className:"notyf__toast--warning",background:"#E3A008",icon:{className:"notyf__icon--warning",tagName:"i"}}),this.notyf=this.notyf||new p(e)}initializeNotyf(){this.notyf||this.renderOptions({duration:1e4,position:{x:"right",y:"top"},dismissible:!0})}addTypeIfNotExists(t,e){t.some((t=>t.type===e.type))||t.push(e)}attachEventListeners(t,e){if(!this.notyf)return;const i=this.notyf;i.on("click",(({target:i,event:n})=>{i===t&&this.dispatchEvent("flasher:notyf:click",e,{event:n})})),i.on("dismiss",(({target:i,event:n})=>{i===t&&this.dispatchEvent("flasher:notyf:dismiss",e,{event:n})}))}dispatchEvent(t,e,i={}){window.dispatchEvent(new CustomEvent(t,{detail:Object.assign({envelope:e},i)}))}};return t.addPlugin("notyf",f),f})); diff --git a/src/Prime/Resources/assets/flasher-plugin.ts b/src/Prime/Resources/assets/flasher-plugin.ts index 52dce6a5..87879c62 100644 --- a/src/Prime/Resources/assets/flasher-plugin.ts +++ b/src/Prime/Resources/assets/flasher-plugin.ts @@ -168,6 +168,15 @@ export default class FlasherPlugin extends AbstractPlugin { }) } + // Add click event listener to dispatch theme events + notification.addEventListener('click', (event) => { + // Don't trigger if clicking the close button + if ((event.target as HTMLElement).closest('.fl-close')) { + return + } + this.dispatchClickEvents(envelope) + }) + // Add timer if timeout is greater than 0 (not sticky) if (options.timeout > 0) { this.addTimer(notification, options) @@ -312,4 +321,33 @@ export default class FlasherPlugin extends AbstractPlugin { return str.replace(/[&<>"'`=/]/g, (char) => htmlEscapes[char] || char) } + + private dispatchClickEvents(envelope: Envelope): void { + const detail = { envelope } + + // Dispatch generic theme click event + window.dispatchEvent(new CustomEvent('flasher:theme:click', { detail })) + + // Dispatch theme-specific click event (e.g., flasher:theme:flasher:click) + const themeName = this.getThemeName(envelope) + if (themeName) { + window.dispatchEvent(new CustomEvent(`flasher:theme:${themeName}:click`, { detail })) + } + } + + private getThemeName(envelope: Envelope): string { + const plugin = envelope.metadata?.plugin || '' + + // Extract theme name from plugin (e.g., 'theme.flasher' -> 'flasher') + if (plugin.startsWith('theme.')) { + return plugin.replace('theme.', '') + } + + // If it's the default 'flasher' plugin, return 'flasher' + if (plugin === 'flasher') { + return 'flasher' + } + + return plugin + } } diff --git a/src/Prime/Resources/dist/flasher-plugin.d.ts b/src/Prime/Resources/dist/flasher-plugin.d.ts index 55895f22..b5867bd5 100644 --- a/src/Prime/Resources/dist/flasher-plugin.d.ts +++ b/src/Prime/Resources/dist/flasher-plugin.d.ts @@ -14,4 +14,6 @@ export default class FlasherPlugin extends AbstractPlugin { private removeNotification; private stringToHTML; private escapeHtml; + private dispatchClickEvents; + private getThemeName; } diff --git a/src/Prime/Resources/dist/flasher.esm.js b/src/Prime/Resources/dist/flasher.esm.js index 381813ec..8ccd2bb5 100644 --- a/src/Prime/Resources/dist/flasher.esm.js +++ b/src/Prime/Resources/dist/flasher.esm.js @@ -227,6 +227,12 @@ class FlasherPlugin extends AbstractPlugin { this.removeNotification(notification); }); } + notification.addEventListener('click', (event) => { + if (event.target.closest('.fl-close')) { + return; + } + this.dispatchClickEvents(envelope); + }); if (options.timeout > 0) { this.addTimer(notification, options); } @@ -331,6 +337,25 @@ class FlasherPlugin extends AbstractPlugin { }; return str.replace(/[&<>"'`=/]/g, (char) => htmlEscapes[char] || char); } + dispatchClickEvents(envelope) { + const detail = { envelope }; + window.dispatchEvent(new CustomEvent('flasher:theme:click', { detail })); + const themeName = this.getThemeName(envelope); + if (themeName) { + window.dispatchEvent(new CustomEvent(`flasher:theme:${themeName}:click`, { detail })); + } + } + getThemeName(envelope) { + var _a; + const plugin = ((_a = envelope.metadata) === null || _a === void 0 ? void 0 : _a.plugin) || ''; + if (plugin.startsWith('theme.')) { + return plugin.replace('theme.', ''); + } + if (plugin === 'flasher') { + return 'flasher'; + } + return plugin; + } } class Flasher extends AbstractPlugin { diff --git a/src/Prime/Resources/dist/flasher.js b/src/Prime/Resources/dist/flasher.js index 7ade0d06..99796483 100644 --- a/src/Prime/Resources/dist/flasher.js +++ b/src/Prime/Resources/dist/flasher.js @@ -233,6 +233,12 @@ this.removeNotification(notification); }); } + notification.addEventListener('click', (event) => { + if (event.target.closest('.fl-close')) { + return; + } + this.dispatchClickEvents(envelope); + }); if (options.timeout > 0) { this.addTimer(notification, options); } @@ -337,6 +343,25 @@ }; return str.replace(/[&<>"'`=/]/g, (char) => htmlEscapes[char] || char); } + dispatchClickEvents(envelope) { + const detail = { envelope }; + window.dispatchEvent(new CustomEvent('flasher:theme:click', { detail })); + const themeName = this.getThemeName(envelope); + if (themeName) { + window.dispatchEvent(new CustomEvent(`flasher:theme:${themeName}:click`, { detail })); + } + } + getThemeName(envelope) { + var _a; + const plugin = ((_a = envelope.metadata) === null || _a === void 0 ? void 0 : _a.plugin) || ''; + if (plugin.startsWith('theme.')) { + return plugin.replace('theme.', ''); + } + if (plugin === 'flasher') { + return 'flasher'; + } + return plugin; + } } class Flasher extends AbstractPlugin { diff --git a/src/Prime/Resources/dist/flasher.min.js b/src/Prime/Resources/dist/flasher.min.js index 52134cf8..c9c3adc6 100644 --- a/src/Prime/Resources/dist/flasher.min.js +++ b/src/Prime/Resources/dist/flasher.min.js @@ -1 +1 @@ -!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).flasher=t()}(this,(function(){"use strict";function e(e,t,s,n){return new(s||(s=Promise))((function(r,o){function i(e){try{a(n.next(e))}catch(e){o(e)}}function l(e){try{a(n.throw(e))}catch(e){o(e)}}function a(e){var t;e.done?r(e.value):(t=e.value,t instanceof s?t:new s((function(e){e(t)}))).then(i,l)}a((n=n.apply(e,t||[])).next())}))}"function"==typeof SuppressedError&&SuppressedError;class t{success(e,t,s){this.flash("success",e,t,s)}error(e,t,s){this.flash("error",e,t,s)}info(e,t,s){this.flash("info",e,t,s)}warning(e,t,s){this.flash("warning",e,t,s)}flash(e,t,s,n){let r,o,i,l={};if("object"==typeof e?(l=Object.assign({},e),r=l.type,o=l.message,i=l.title,delete l.type,delete l.message,delete l.title):"object"==typeof t?(l=Object.assign({},t),r=e,o=l.message,i=l.title,delete l.message,delete l.title):(r=e,o=t,null==s?(i=void 0,l=n||{}):"string"==typeof s?(i=s,l=n||{}):"object"==typeof s&&(l=Object.assign({},s),"title"in l?(i=l.title,delete l.title):i=void 0,n&&"object"==typeof n&&(l=Object.assign(Object.assign({},l),n)))),!r)throw new Error("Type is required for notifications");if(null==o)throw new Error("Message is required for notifications");null==i&&(i=r.charAt(0).toUpperCase()+r.slice(1));const a={type:r,message:o,title:i,options:l,metadata:{plugin:""}};this.renderOptions({}),this.renderEnvelopes([a])}}class s extends t{constructor(e){if(super(),this.options={timeout:null,timeouts:{success:1e4,info:1e4,error:1e4,warning:1e4},fps:30,position:"top-right",direction:"top",rtl:!1,style:{},escapeHtml:!1},!e)throw new Error("Theme is required");if("function"!=typeof e.render)throw new TypeError("Theme must have a render function");this.theme=e}renderEnvelopes(e){if(!(null==e?void 0:e.length))return;const t=()=>{e.forEach((e=>{var t,s,n,r;try{const o=null!==(s=null!==(t=this.options.timeout)&&void 0!==t?t:this.options.timeouts[e.type])&&void 0!==s?s:1e4,i=Object.assign(Object.assign(Object.assign({},this.options),e.options),{timeout:this.normalizeTimeout(null!==(n=e.options.timeout)&&void 0!==n?n:o),escapeHtml:null!==(r=e.options.escapeHtml)&&void 0!==r?r:this.options.escapeHtml}),l=this.createContainer(i),a={direction:i.direction,timeout:Number(i.timeout||0),fps:i.fps,rtl:i.rtl,escapeHtml:i.escapeHtml};this.addToContainer(l,e,a)}catch(t){console.error("PHPFlasher: Error rendering envelope",t,e)}}))};if("loading"===document.readyState){const e=()=>{document.removeEventListener("DOMContentLoaded",e),t()};document.addEventListener("DOMContentLoaded",e)}else t()}renderOptions(e){e&&(this.options=Object.assign(Object.assign({},this.options),e))}createContainer(e){let t=document.querySelector(`.fl-wrapper[data-position="${e.position}"]`);return t||(t=document.createElement("div"),t.className="fl-wrapper",t.dataset.position=e.position,Object.entries(e.style).forEach((([e,s])=>{if(null!=s){const n=e.replace(/([A-Z])/g,"-$1").toLowerCase();t.style.setProperty(n,String(s))}})),document.body.appendChild(t)),t.dataset.turboTemporary="",t}addToContainer(e,t,s){s.escapeHtml&&(t.title=this.escapeHtml(t.title),t.message=this.escapeHtml(t.message));const n=this.stringToHTML(this.theme.render(t));n.classList.add("fl-container"),s.rtl&&n.classList.add("fl-rtl"),"bottom"===s.direction?e.append(n):e.prepend(n),requestAnimationFrame((()=>n.classList.add("fl-show")));const r=n.querySelector(".fl-close");if(r&&r.addEventListener("click",(e=>{e.stopPropagation(),this.removeNotification(n)})),s.timeout>0)this.addTimer(n,s);else{n.classList.add("fl-sticky");const e=n.querySelector(".fl-progress-bar");if(e){const t=document.createElement("span");t.classList.add("fl-progress","fl-sticky-progress"),t.style.width="100%",e.append(t)}}}normalizeTimeout(e){return!1===e||"number"==typeof e&&e<0||null==e?0:Number(e)||0}addTimer(e,{timeout:t,fps:s}){if(t<=0)return;const n=1e3/s;let r,o=0;const i=()=>{o+=n;const s=e.querySelector(".fl-progress-bar");if(s){let e=s.querySelector(".fl-progress");e||(e=document.createElement("span"),e.classList.add("fl-progress"),s.append(e));const n=100*(1-o/t);e.style.width=`${Math.max(0,n)}%`}o>=t&&(clearInterval(r),this.removeNotification(e))};r=window.setInterval(i,n);const l=()=>{clearInterval(r),r=window.setInterval(i,n)},a=()=>clearInterval(r);e.addEventListener("mouseout",l),e.addEventListener("mouseover",a),e._flasherCleanup=()=>{clearInterval(r),e.removeEventListener("mouseout",l),e.removeEventListener("mouseover",a)}}removeNotification(e){e&&(e._flasherCleanup&&(e._flasherCleanup(),delete e._flasherCleanup),e.classList.remove("fl-show"),e.ontransitionend=()=>{const t=e.parentElement;e.remove(),t&&!t.hasChildNodes()&&t.remove()})}stringToHTML(e){const t=document.createElement("template");t.innerHTML=e.trim();const s=t.content.firstElementChild;if(!s)throw new Error("PHPFlasher: Invalid HTML template - no element found");return s}escapeHtml(e){if(null==e)return"";const t={"&":"&","<":"<",">":">",'"':""","'":"'","`":"`","=":"=","/":"/"};return e.replace(/[&<>"'`=/]/g,(e=>t[e]||e))}}const n=new class extends t{constructor(){super(...arguments),this.defaultPlugin="flasher",this.plugins=new Map,this.themes=new Map,this.loadedAssets=new Set}render(t){return e(this,void 0,void 0,(function*(){const e=this.resolveResponse(t);try{yield this.addAssets([{urls:e.styles,nonce:e.context.csp_style_nonce,type:"style"},{urls:e.scripts,nonce:e.context.csp_script_nonce,type:"script"}]),this.renderOptions(e.options),this.renderEnvelopes(e.envelopes)}catch(e){console.error("PHPFlasher: Error rendering notifications",e)}}))}renderEnvelopes(e){if(!(null==e?void 0:e.length))return;const t={};e.forEach((e=>{const s=this.resolvePluginAlias(e.metadata.plugin);t[s]=t[s]||[],t[s].push(e)})),Object.entries(t).forEach((([e,t])=>{try{this.use(e).renderEnvelopes(t)}catch(t){console.error(`PHPFlasher: Error rendering envelopes for plugin "${e}"`,t)}}))}renderOptions(e){e&&Object.entries(e).forEach((([e,t])=>{try{this.use(e).renderOptions(t)}catch(t){console.error(`PHPFlasher: Error applying options for plugin "${e}"`,t)}}))}addPlugin(e,t){if(!e||!t)throw new Error("Both plugin name and instance are required");this.plugins.set(e,t)}addTheme(e,t){if(!e||!t)throw new Error("Both theme name and definition are required");this.themes.set(e,t)}use(e){const t=this.resolvePluginAlias(e);this.resolvePlugin(t);const s=this.plugins.get(t);if(!s)throw new Error(`Unable to resolve "${t}" plugin, did you forget to register it?`);return s}create(e){return this.use(e)}resolveResponse(e){const t=Object.assign({envelopes:[],options:{},scripts:[],styles:[],context:{}},e);return Object.entries(t.options).forEach((([e,s])=>{t.options[e]=this.resolveOptions(s)})),t.context.csp_style_nonce=t.context.csp_style_nonce||"",t.context.csp_script_nonce=t.context.csp_script_nonce||"",t.envelopes.forEach((e=>{e.metadata=e.metadata||{},e.metadata.plugin=this.resolvePluginAlias(e.metadata.plugin),this.addThemeStyles(t,e.metadata.plugin),e.options=this.resolveOptions(e.options),e.context=t.context})),t}resolveOptions(e){if(!e)return{};const t=Object.assign({},e);return Object.entries(t).forEach((([e,s])=>{t[e]=this.resolveFunction(s)})),t}resolveFunction(e){var t,s,n,r;if("string"!=typeof e)return e;const o=e.match(/^function\s*(\w*)\s*\(([^)]*)\)\s*\{([\s\S]*)\}$/),i=e.match(/^\s*(\(([^)]*)\)|[^=]+)\s*=>\s*([\s\S]+)$/);if(!o&&!i)return e;let l,a;o?(l=null!==(s=null===(t=o[2])||void 0===t?void 0:t.split(",").map((e=>e.trim())).filter(Boolean))&&void 0!==s?s:[],a=o[3].trim()):(l=null!==(r=null===(n=i[2])||void 0===n?void 0:n.split(",").map((e=>e.trim())).filter(Boolean))&&void 0!==r?r:[],a=i[3].trim(),a=a.startsWith("{")?a.slice(1,-1).trim():`return ${a};`);try{return new Function(...l,a)}catch(t){return console.error("PHPFlasher: Error converting string to function:",t),e}}resolvePlugin(e){if(this.plugins.get(e)||!e.includes("theme."))return;const t=e.replace("theme.",""),n=this.themes.get(t);n&&this.addPlugin(e,new s(n))}resolvePluginAlias(e){return"flasher"===(e=e||this.defaultPlugin)?"theme.flasher":e}addAssets(t){return e(this,void 0,void 0,(function*(){try{const e=t.filter((e=>"style"===e.type)),s=[];for(const{urls:t,nonce:n,type:r}of e)if(null==t?void 0:t.length)for(const e of t)e&&!this.loadedAssets.has(e)&&(s.push(this.loadAsset(e,n,r)),this.loadedAssets.add(e));yield Promise.all(s);const n=t.filter((e=>"script"===e.type));for(const{urls:e,nonce:t,type:s}of n)if(null==e?void 0:e.length)for(const n of e)n&&!this.loadedAssets.has(n)&&(yield this.loadAsset(n,t,s),this.loadedAssets.add(n))}catch(e){console.error("PHPFlasher: Error loading assets",e)}}))}loadAsset(e,t,s){const n="style"===s?`link[href="${e}"]`:`script[src="${e}"]`;return document.querySelector(n)?Promise.resolve():new Promise(((n,r)=>{const o=document.createElement("style"===s?"link":"script");"style"===s?(o.rel="stylesheet",o.href=e):(o.type="text/javascript",o.src=e),t&&o.setAttribute("nonce",t),o.onload=()=>n(),o.onerror=()=>r(new Error(`Failed to load ${e}`)),document.head.appendChild(o)}))}addThemeStyles(e,t){if("flasher"!==t&&!t.includes("theme."))return;const s=t.replace("theme.",""),n=this.themes.get(s);if(!(null==n?void 0:n.styles))return;const r=Array.isArray(n.styles)?n.styles:[n.styles];e.styles=Array.from(new Set([...e.styles,...r]))}};return n.addTheme("flasher",{render:e=>{const{type:t,title:s,message:n}=e,r="error"===t||"warning"===t,o=r?"alert":"status",i=r?"assertive":"polite",l=s||t.charAt(0).toUpperCase()+t.slice(1);return`\n
\n
\n
\n
\n ${l}\n ${n}\n
\n \n
\n \n \n \n
`}}),"undefined"!=typeof window&&(window.flasher=n),n})); +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).flasher=t()}(this,(function(){"use strict";function e(e,t,s,n){return new(s||(s=Promise))((function(r,o){function i(e){try{a(n.next(e))}catch(e){o(e)}}function l(e){try{a(n.throw(e))}catch(e){o(e)}}function a(e){var t;e.done?r(e.value):(t=e.value,t instanceof s?t:new s((function(e){e(t)}))).then(i,l)}a((n=n.apply(e,t||[])).next())}))}"function"==typeof SuppressedError&&SuppressedError;class t{success(e,t,s){this.flash("success",e,t,s)}error(e,t,s){this.flash("error",e,t,s)}info(e,t,s){this.flash("info",e,t,s)}warning(e,t,s){this.flash("warning",e,t,s)}flash(e,t,s,n){let r,o,i,l={};if("object"==typeof e?(l=Object.assign({},e),r=l.type,o=l.message,i=l.title,delete l.type,delete l.message,delete l.title):"object"==typeof t?(l=Object.assign({},t),r=e,o=l.message,i=l.title,delete l.message,delete l.title):(r=e,o=t,null==s?(i=void 0,l=n||{}):"string"==typeof s?(i=s,l=n||{}):"object"==typeof s&&(l=Object.assign({},s),"title"in l?(i=l.title,delete l.title):i=void 0,n&&"object"==typeof n&&(l=Object.assign(Object.assign({},l),n)))),!r)throw new Error("Type is required for notifications");if(null==o)throw new Error("Message is required for notifications");null==i&&(i=r.charAt(0).toUpperCase()+r.slice(1));const a={type:r,message:o,title:i,options:l,metadata:{plugin:""}};this.renderOptions({}),this.renderEnvelopes([a])}}class s extends t{constructor(e){if(super(),this.options={timeout:null,timeouts:{success:1e4,info:1e4,error:1e4,warning:1e4},fps:30,position:"top-right",direction:"top",rtl:!1,style:{},escapeHtml:!1},!e)throw new Error("Theme is required");if("function"!=typeof e.render)throw new TypeError("Theme must have a render function");this.theme=e}renderEnvelopes(e){if(!(null==e?void 0:e.length))return;const t=()=>{e.forEach((e=>{var t,s,n,r;try{const o=null!==(s=null!==(t=this.options.timeout)&&void 0!==t?t:this.options.timeouts[e.type])&&void 0!==s?s:1e4,i=Object.assign(Object.assign(Object.assign({},this.options),e.options),{timeout:this.normalizeTimeout(null!==(n=e.options.timeout)&&void 0!==n?n:o),escapeHtml:null!==(r=e.options.escapeHtml)&&void 0!==r?r:this.options.escapeHtml}),l=this.createContainer(i),a={direction:i.direction,timeout:Number(i.timeout||0),fps:i.fps,rtl:i.rtl,escapeHtml:i.escapeHtml};this.addToContainer(l,e,a)}catch(t){console.error("PHPFlasher: Error rendering envelope",t,e)}}))};if("loading"===document.readyState){const e=()=>{document.removeEventListener("DOMContentLoaded",e),t()};document.addEventListener("DOMContentLoaded",e)}else t()}renderOptions(e){e&&(this.options=Object.assign(Object.assign({},this.options),e))}createContainer(e){let t=document.querySelector(`.fl-wrapper[data-position="${e.position}"]`);return t||(t=document.createElement("div"),t.className="fl-wrapper",t.dataset.position=e.position,Object.entries(e.style).forEach((([e,s])=>{if(null!=s){const n=e.replace(/([A-Z])/g,"-$1").toLowerCase();t.style.setProperty(n,String(s))}})),document.body.appendChild(t)),t.dataset.turboTemporary="",t}addToContainer(e,t,s){s.escapeHtml&&(t.title=this.escapeHtml(t.title),t.message=this.escapeHtml(t.message));const n=this.stringToHTML(this.theme.render(t));n.classList.add("fl-container"),s.rtl&&n.classList.add("fl-rtl"),"bottom"===s.direction?e.append(n):e.prepend(n),requestAnimationFrame((()=>n.classList.add("fl-show")));const r=n.querySelector(".fl-close");if(r&&r.addEventListener("click",(e=>{e.stopPropagation(),this.removeNotification(n)})),n.addEventListener("click",(e=>{e.target.closest(".fl-close")||this.dispatchClickEvents(t)})),s.timeout>0)this.addTimer(n,s);else{n.classList.add("fl-sticky");const e=n.querySelector(".fl-progress-bar");if(e){const t=document.createElement("span");t.classList.add("fl-progress","fl-sticky-progress"),t.style.width="100%",e.append(t)}}}normalizeTimeout(e){return!1===e||"number"==typeof e&&e<0||null==e?0:Number(e)||0}addTimer(e,{timeout:t,fps:s}){if(t<=0)return;const n=1e3/s;let r,o=0;const i=()=>{o+=n;const s=e.querySelector(".fl-progress-bar");if(s){let e=s.querySelector(".fl-progress");e||(e=document.createElement("span"),e.classList.add("fl-progress"),s.append(e));const n=100*(1-o/t);e.style.width=`${Math.max(0,n)}%`}o>=t&&(clearInterval(r),this.removeNotification(e))};r=window.setInterval(i,n);const l=()=>{clearInterval(r),r=window.setInterval(i,n)},a=()=>clearInterval(r);e.addEventListener("mouseout",l),e.addEventListener("mouseover",a),e._flasherCleanup=()=>{clearInterval(r),e.removeEventListener("mouseout",l),e.removeEventListener("mouseover",a)}}removeNotification(e){e&&(e._flasherCleanup&&(e._flasherCleanup(),delete e._flasherCleanup),e.classList.remove("fl-show"),e.ontransitionend=()=>{const t=e.parentElement;e.remove(),t&&!t.hasChildNodes()&&t.remove()})}stringToHTML(e){const t=document.createElement("template");t.innerHTML=e.trim();const s=t.content.firstElementChild;if(!s)throw new Error("PHPFlasher: Invalid HTML template - no element found");return s}escapeHtml(e){if(null==e)return"";const t={"&":"&","<":"<",">":">",'"':""","'":"'","`":"`","=":"=","/":"/"};return e.replace(/[&<>"'`=/]/g,(e=>t[e]||e))}dispatchClickEvents(e){const t={envelope:e};window.dispatchEvent(new CustomEvent("flasher:theme:click",{detail:t}));const s=this.getThemeName(e);s&&window.dispatchEvent(new CustomEvent(`flasher:theme:${s}:click`,{detail:t}))}getThemeName(e){var t;const s=(null===(t=e.metadata)||void 0===t?void 0:t.plugin)||"";return s.startsWith("theme.")?s.replace("theme.",""):"flasher"===s?"flasher":s}}const n=new class extends t{constructor(){super(...arguments),this.defaultPlugin="flasher",this.plugins=new Map,this.themes=new Map,this.loadedAssets=new Set}render(t){return e(this,void 0,void 0,(function*(){const e=this.resolveResponse(t);try{yield this.addAssets([{urls:e.styles,nonce:e.context.csp_style_nonce,type:"style"},{urls:e.scripts,nonce:e.context.csp_script_nonce,type:"script"}]),this.renderOptions(e.options),this.renderEnvelopes(e.envelopes)}catch(e){console.error("PHPFlasher: Error rendering notifications",e)}}))}renderEnvelopes(e){if(!(null==e?void 0:e.length))return;const t={};e.forEach((e=>{const s=this.resolvePluginAlias(e.metadata.plugin);t[s]=t[s]||[],t[s].push(e)})),Object.entries(t).forEach((([e,t])=>{try{this.use(e).renderEnvelopes(t)}catch(t){console.error(`PHPFlasher: Error rendering envelopes for plugin "${e}"`,t)}}))}renderOptions(e){e&&Object.entries(e).forEach((([e,t])=>{try{this.use(e).renderOptions(t)}catch(t){console.error(`PHPFlasher: Error applying options for plugin "${e}"`,t)}}))}addPlugin(e,t){if(!e||!t)throw new Error("Both plugin name and instance are required");this.plugins.set(e,t)}addTheme(e,t){if(!e||!t)throw new Error("Both theme name and definition are required");this.themes.set(e,t)}use(e){const t=this.resolvePluginAlias(e);this.resolvePlugin(t);const s=this.plugins.get(t);if(!s)throw new Error(`Unable to resolve "${t}" plugin, did you forget to register it?`);return s}create(e){return this.use(e)}resolveResponse(e){const t=Object.assign({envelopes:[],options:{},scripts:[],styles:[],context:{}},e);return Object.entries(t.options).forEach((([e,s])=>{t.options[e]=this.resolveOptions(s)})),t.context.csp_style_nonce=t.context.csp_style_nonce||"",t.context.csp_script_nonce=t.context.csp_script_nonce||"",t.envelopes.forEach((e=>{e.metadata=e.metadata||{},e.metadata.plugin=this.resolvePluginAlias(e.metadata.plugin),this.addThemeStyles(t,e.metadata.plugin),e.options=this.resolveOptions(e.options),e.context=t.context})),t}resolveOptions(e){if(!e)return{};const t=Object.assign({},e);return Object.entries(t).forEach((([e,s])=>{t[e]=this.resolveFunction(s)})),t}resolveFunction(e){var t,s,n,r;if("string"!=typeof e)return e;const o=e.match(/^function\s*(\w*)\s*\(([^)]*)\)\s*\{([\s\S]*)\}$/),i=e.match(/^\s*(\(([^)]*)\)|[^=]+)\s*=>\s*([\s\S]+)$/);if(!o&&!i)return e;let l,a;o?(l=null!==(s=null===(t=o[2])||void 0===t?void 0:t.split(",").map((e=>e.trim())).filter(Boolean))&&void 0!==s?s:[],a=o[3].trim()):(l=null!==(r=null===(n=i[2])||void 0===n?void 0:n.split(",").map((e=>e.trim())).filter(Boolean))&&void 0!==r?r:[],a=i[3].trim(),a=a.startsWith("{")?a.slice(1,-1).trim():`return ${a};`);try{return new Function(...l,a)}catch(t){return console.error("PHPFlasher: Error converting string to function:",t),e}}resolvePlugin(e){if(this.plugins.get(e)||!e.includes("theme."))return;const t=e.replace("theme.",""),n=this.themes.get(t);n&&this.addPlugin(e,new s(n))}resolvePluginAlias(e){return"flasher"===(e=e||this.defaultPlugin)?"theme.flasher":e}addAssets(t){return e(this,void 0,void 0,(function*(){try{const e=t.filter((e=>"style"===e.type)),s=[];for(const{urls:t,nonce:n,type:r}of e)if(null==t?void 0:t.length)for(const e of t)e&&!this.loadedAssets.has(e)&&(s.push(this.loadAsset(e,n,r)),this.loadedAssets.add(e));yield Promise.all(s);const n=t.filter((e=>"script"===e.type));for(const{urls:e,nonce:t,type:s}of n)if(null==e?void 0:e.length)for(const n of e)n&&!this.loadedAssets.has(n)&&(yield this.loadAsset(n,t,s),this.loadedAssets.add(n))}catch(e){console.error("PHPFlasher: Error loading assets",e)}}))}loadAsset(e,t,s){const n="style"===s?`link[href="${e}"]`:`script[src="${e}"]`;return document.querySelector(n)?Promise.resolve():new Promise(((n,r)=>{const o=document.createElement("style"===s?"link":"script");"style"===s?(o.rel="stylesheet",o.href=e):(o.type="text/javascript",o.src=e),t&&o.setAttribute("nonce",t),o.onload=()=>n(),o.onerror=()=>r(new Error(`Failed to load ${e}`)),document.head.appendChild(o)}))}addThemeStyles(e,t){if("flasher"!==t&&!t.includes("theme."))return;const s=t.replace("theme.",""),n=this.themes.get(s);if(!(null==n?void 0:n.styles))return;const r=Array.isArray(n.styles)?n.styles:[n.styles];e.styles=Array.from(new Set([...e.styles,...r]))}};return n.addTheme("flasher",{render:e=>{const{type:t,title:s,message:n}=e,r="error"===t||"warning"===t,o=r?"alert":"status",i=r?"assertive":"polite",l=s||t.charAt(0).toUpperCase()+t.slice(1);return`\n
\n
\n
\n
\n ${l}\n ${n}\n
\n \n
\n \n \n \n
`}}),"undefined"!=typeof window&&(window.flasher=n),n})); diff --git a/src/Prime/Resources/public/flasher.min.js b/src/Prime/Resources/public/flasher.min.js index 52134cf8..c9c3adc6 100644 --- a/src/Prime/Resources/public/flasher.min.js +++ b/src/Prime/Resources/public/flasher.min.js @@ -1 +1 @@ -!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).flasher=t()}(this,(function(){"use strict";function e(e,t,s,n){return new(s||(s=Promise))((function(r,o){function i(e){try{a(n.next(e))}catch(e){o(e)}}function l(e){try{a(n.throw(e))}catch(e){o(e)}}function a(e){var t;e.done?r(e.value):(t=e.value,t instanceof s?t:new s((function(e){e(t)}))).then(i,l)}a((n=n.apply(e,t||[])).next())}))}"function"==typeof SuppressedError&&SuppressedError;class t{success(e,t,s){this.flash("success",e,t,s)}error(e,t,s){this.flash("error",e,t,s)}info(e,t,s){this.flash("info",e,t,s)}warning(e,t,s){this.flash("warning",e,t,s)}flash(e,t,s,n){let r,o,i,l={};if("object"==typeof e?(l=Object.assign({},e),r=l.type,o=l.message,i=l.title,delete l.type,delete l.message,delete l.title):"object"==typeof t?(l=Object.assign({},t),r=e,o=l.message,i=l.title,delete l.message,delete l.title):(r=e,o=t,null==s?(i=void 0,l=n||{}):"string"==typeof s?(i=s,l=n||{}):"object"==typeof s&&(l=Object.assign({},s),"title"in l?(i=l.title,delete l.title):i=void 0,n&&"object"==typeof n&&(l=Object.assign(Object.assign({},l),n)))),!r)throw new Error("Type is required for notifications");if(null==o)throw new Error("Message is required for notifications");null==i&&(i=r.charAt(0).toUpperCase()+r.slice(1));const a={type:r,message:o,title:i,options:l,metadata:{plugin:""}};this.renderOptions({}),this.renderEnvelopes([a])}}class s extends t{constructor(e){if(super(),this.options={timeout:null,timeouts:{success:1e4,info:1e4,error:1e4,warning:1e4},fps:30,position:"top-right",direction:"top",rtl:!1,style:{},escapeHtml:!1},!e)throw new Error("Theme is required");if("function"!=typeof e.render)throw new TypeError("Theme must have a render function");this.theme=e}renderEnvelopes(e){if(!(null==e?void 0:e.length))return;const t=()=>{e.forEach((e=>{var t,s,n,r;try{const o=null!==(s=null!==(t=this.options.timeout)&&void 0!==t?t:this.options.timeouts[e.type])&&void 0!==s?s:1e4,i=Object.assign(Object.assign(Object.assign({},this.options),e.options),{timeout:this.normalizeTimeout(null!==(n=e.options.timeout)&&void 0!==n?n:o),escapeHtml:null!==(r=e.options.escapeHtml)&&void 0!==r?r:this.options.escapeHtml}),l=this.createContainer(i),a={direction:i.direction,timeout:Number(i.timeout||0),fps:i.fps,rtl:i.rtl,escapeHtml:i.escapeHtml};this.addToContainer(l,e,a)}catch(t){console.error("PHPFlasher: Error rendering envelope",t,e)}}))};if("loading"===document.readyState){const e=()=>{document.removeEventListener("DOMContentLoaded",e),t()};document.addEventListener("DOMContentLoaded",e)}else t()}renderOptions(e){e&&(this.options=Object.assign(Object.assign({},this.options),e))}createContainer(e){let t=document.querySelector(`.fl-wrapper[data-position="${e.position}"]`);return t||(t=document.createElement("div"),t.className="fl-wrapper",t.dataset.position=e.position,Object.entries(e.style).forEach((([e,s])=>{if(null!=s){const n=e.replace(/([A-Z])/g,"-$1").toLowerCase();t.style.setProperty(n,String(s))}})),document.body.appendChild(t)),t.dataset.turboTemporary="",t}addToContainer(e,t,s){s.escapeHtml&&(t.title=this.escapeHtml(t.title),t.message=this.escapeHtml(t.message));const n=this.stringToHTML(this.theme.render(t));n.classList.add("fl-container"),s.rtl&&n.classList.add("fl-rtl"),"bottom"===s.direction?e.append(n):e.prepend(n),requestAnimationFrame((()=>n.classList.add("fl-show")));const r=n.querySelector(".fl-close");if(r&&r.addEventListener("click",(e=>{e.stopPropagation(),this.removeNotification(n)})),s.timeout>0)this.addTimer(n,s);else{n.classList.add("fl-sticky");const e=n.querySelector(".fl-progress-bar");if(e){const t=document.createElement("span");t.classList.add("fl-progress","fl-sticky-progress"),t.style.width="100%",e.append(t)}}}normalizeTimeout(e){return!1===e||"number"==typeof e&&e<0||null==e?0:Number(e)||0}addTimer(e,{timeout:t,fps:s}){if(t<=0)return;const n=1e3/s;let r,o=0;const i=()=>{o+=n;const s=e.querySelector(".fl-progress-bar");if(s){let e=s.querySelector(".fl-progress");e||(e=document.createElement("span"),e.classList.add("fl-progress"),s.append(e));const n=100*(1-o/t);e.style.width=`${Math.max(0,n)}%`}o>=t&&(clearInterval(r),this.removeNotification(e))};r=window.setInterval(i,n);const l=()=>{clearInterval(r),r=window.setInterval(i,n)},a=()=>clearInterval(r);e.addEventListener("mouseout",l),e.addEventListener("mouseover",a),e._flasherCleanup=()=>{clearInterval(r),e.removeEventListener("mouseout",l),e.removeEventListener("mouseover",a)}}removeNotification(e){e&&(e._flasherCleanup&&(e._flasherCleanup(),delete e._flasherCleanup),e.classList.remove("fl-show"),e.ontransitionend=()=>{const t=e.parentElement;e.remove(),t&&!t.hasChildNodes()&&t.remove()})}stringToHTML(e){const t=document.createElement("template");t.innerHTML=e.trim();const s=t.content.firstElementChild;if(!s)throw new Error("PHPFlasher: Invalid HTML template - no element found");return s}escapeHtml(e){if(null==e)return"";const t={"&":"&","<":"<",">":">",'"':""","'":"'","`":"`","=":"=","/":"/"};return e.replace(/[&<>"'`=/]/g,(e=>t[e]||e))}}const n=new class extends t{constructor(){super(...arguments),this.defaultPlugin="flasher",this.plugins=new Map,this.themes=new Map,this.loadedAssets=new Set}render(t){return e(this,void 0,void 0,(function*(){const e=this.resolveResponse(t);try{yield this.addAssets([{urls:e.styles,nonce:e.context.csp_style_nonce,type:"style"},{urls:e.scripts,nonce:e.context.csp_script_nonce,type:"script"}]),this.renderOptions(e.options),this.renderEnvelopes(e.envelopes)}catch(e){console.error("PHPFlasher: Error rendering notifications",e)}}))}renderEnvelopes(e){if(!(null==e?void 0:e.length))return;const t={};e.forEach((e=>{const s=this.resolvePluginAlias(e.metadata.plugin);t[s]=t[s]||[],t[s].push(e)})),Object.entries(t).forEach((([e,t])=>{try{this.use(e).renderEnvelopes(t)}catch(t){console.error(`PHPFlasher: Error rendering envelopes for plugin "${e}"`,t)}}))}renderOptions(e){e&&Object.entries(e).forEach((([e,t])=>{try{this.use(e).renderOptions(t)}catch(t){console.error(`PHPFlasher: Error applying options for plugin "${e}"`,t)}}))}addPlugin(e,t){if(!e||!t)throw new Error("Both plugin name and instance are required");this.plugins.set(e,t)}addTheme(e,t){if(!e||!t)throw new Error("Both theme name and definition are required");this.themes.set(e,t)}use(e){const t=this.resolvePluginAlias(e);this.resolvePlugin(t);const s=this.plugins.get(t);if(!s)throw new Error(`Unable to resolve "${t}" plugin, did you forget to register it?`);return s}create(e){return this.use(e)}resolveResponse(e){const t=Object.assign({envelopes:[],options:{},scripts:[],styles:[],context:{}},e);return Object.entries(t.options).forEach((([e,s])=>{t.options[e]=this.resolveOptions(s)})),t.context.csp_style_nonce=t.context.csp_style_nonce||"",t.context.csp_script_nonce=t.context.csp_script_nonce||"",t.envelopes.forEach((e=>{e.metadata=e.metadata||{},e.metadata.plugin=this.resolvePluginAlias(e.metadata.plugin),this.addThemeStyles(t,e.metadata.plugin),e.options=this.resolveOptions(e.options),e.context=t.context})),t}resolveOptions(e){if(!e)return{};const t=Object.assign({},e);return Object.entries(t).forEach((([e,s])=>{t[e]=this.resolveFunction(s)})),t}resolveFunction(e){var t,s,n,r;if("string"!=typeof e)return e;const o=e.match(/^function\s*(\w*)\s*\(([^)]*)\)\s*\{([\s\S]*)\}$/),i=e.match(/^\s*(\(([^)]*)\)|[^=]+)\s*=>\s*([\s\S]+)$/);if(!o&&!i)return e;let l,a;o?(l=null!==(s=null===(t=o[2])||void 0===t?void 0:t.split(",").map((e=>e.trim())).filter(Boolean))&&void 0!==s?s:[],a=o[3].trim()):(l=null!==(r=null===(n=i[2])||void 0===n?void 0:n.split(",").map((e=>e.trim())).filter(Boolean))&&void 0!==r?r:[],a=i[3].trim(),a=a.startsWith("{")?a.slice(1,-1).trim():`return ${a};`);try{return new Function(...l,a)}catch(t){return console.error("PHPFlasher: Error converting string to function:",t),e}}resolvePlugin(e){if(this.plugins.get(e)||!e.includes("theme."))return;const t=e.replace("theme.",""),n=this.themes.get(t);n&&this.addPlugin(e,new s(n))}resolvePluginAlias(e){return"flasher"===(e=e||this.defaultPlugin)?"theme.flasher":e}addAssets(t){return e(this,void 0,void 0,(function*(){try{const e=t.filter((e=>"style"===e.type)),s=[];for(const{urls:t,nonce:n,type:r}of e)if(null==t?void 0:t.length)for(const e of t)e&&!this.loadedAssets.has(e)&&(s.push(this.loadAsset(e,n,r)),this.loadedAssets.add(e));yield Promise.all(s);const n=t.filter((e=>"script"===e.type));for(const{urls:e,nonce:t,type:s}of n)if(null==e?void 0:e.length)for(const n of e)n&&!this.loadedAssets.has(n)&&(yield this.loadAsset(n,t,s),this.loadedAssets.add(n))}catch(e){console.error("PHPFlasher: Error loading assets",e)}}))}loadAsset(e,t,s){const n="style"===s?`link[href="${e}"]`:`script[src="${e}"]`;return document.querySelector(n)?Promise.resolve():new Promise(((n,r)=>{const o=document.createElement("style"===s?"link":"script");"style"===s?(o.rel="stylesheet",o.href=e):(o.type="text/javascript",o.src=e),t&&o.setAttribute("nonce",t),o.onload=()=>n(),o.onerror=()=>r(new Error(`Failed to load ${e}`)),document.head.appendChild(o)}))}addThemeStyles(e,t){if("flasher"!==t&&!t.includes("theme."))return;const s=t.replace("theme.",""),n=this.themes.get(s);if(!(null==n?void 0:n.styles))return;const r=Array.isArray(n.styles)?n.styles:[n.styles];e.styles=Array.from(new Set([...e.styles,...r]))}};return n.addTheme("flasher",{render:e=>{const{type:t,title:s,message:n}=e,r="error"===t||"warning"===t,o=r?"alert":"status",i=r?"assertive":"polite",l=s||t.charAt(0).toUpperCase()+t.slice(1);return`\n
\n
\n
\n
\n ${l}\n ${n}\n
\n \n
\n \n \n \n
`}}),"undefined"!=typeof window&&(window.flasher=n),n})); +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).flasher=t()}(this,(function(){"use strict";function e(e,t,s,n){return new(s||(s=Promise))((function(r,o){function i(e){try{a(n.next(e))}catch(e){o(e)}}function l(e){try{a(n.throw(e))}catch(e){o(e)}}function a(e){var t;e.done?r(e.value):(t=e.value,t instanceof s?t:new s((function(e){e(t)}))).then(i,l)}a((n=n.apply(e,t||[])).next())}))}"function"==typeof SuppressedError&&SuppressedError;class t{success(e,t,s){this.flash("success",e,t,s)}error(e,t,s){this.flash("error",e,t,s)}info(e,t,s){this.flash("info",e,t,s)}warning(e,t,s){this.flash("warning",e,t,s)}flash(e,t,s,n){let r,o,i,l={};if("object"==typeof e?(l=Object.assign({},e),r=l.type,o=l.message,i=l.title,delete l.type,delete l.message,delete l.title):"object"==typeof t?(l=Object.assign({},t),r=e,o=l.message,i=l.title,delete l.message,delete l.title):(r=e,o=t,null==s?(i=void 0,l=n||{}):"string"==typeof s?(i=s,l=n||{}):"object"==typeof s&&(l=Object.assign({},s),"title"in l?(i=l.title,delete l.title):i=void 0,n&&"object"==typeof n&&(l=Object.assign(Object.assign({},l),n)))),!r)throw new Error("Type is required for notifications");if(null==o)throw new Error("Message is required for notifications");null==i&&(i=r.charAt(0).toUpperCase()+r.slice(1));const a={type:r,message:o,title:i,options:l,metadata:{plugin:""}};this.renderOptions({}),this.renderEnvelopes([a])}}class s extends t{constructor(e){if(super(),this.options={timeout:null,timeouts:{success:1e4,info:1e4,error:1e4,warning:1e4},fps:30,position:"top-right",direction:"top",rtl:!1,style:{},escapeHtml:!1},!e)throw new Error("Theme is required");if("function"!=typeof e.render)throw new TypeError("Theme must have a render function");this.theme=e}renderEnvelopes(e){if(!(null==e?void 0:e.length))return;const t=()=>{e.forEach((e=>{var t,s,n,r;try{const o=null!==(s=null!==(t=this.options.timeout)&&void 0!==t?t:this.options.timeouts[e.type])&&void 0!==s?s:1e4,i=Object.assign(Object.assign(Object.assign({},this.options),e.options),{timeout:this.normalizeTimeout(null!==(n=e.options.timeout)&&void 0!==n?n:o),escapeHtml:null!==(r=e.options.escapeHtml)&&void 0!==r?r:this.options.escapeHtml}),l=this.createContainer(i),a={direction:i.direction,timeout:Number(i.timeout||0),fps:i.fps,rtl:i.rtl,escapeHtml:i.escapeHtml};this.addToContainer(l,e,a)}catch(t){console.error("PHPFlasher: Error rendering envelope",t,e)}}))};if("loading"===document.readyState){const e=()=>{document.removeEventListener("DOMContentLoaded",e),t()};document.addEventListener("DOMContentLoaded",e)}else t()}renderOptions(e){e&&(this.options=Object.assign(Object.assign({},this.options),e))}createContainer(e){let t=document.querySelector(`.fl-wrapper[data-position="${e.position}"]`);return t||(t=document.createElement("div"),t.className="fl-wrapper",t.dataset.position=e.position,Object.entries(e.style).forEach((([e,s])=>{if(null!=s){const n=e.replace(/([A-Z])/g,"-$1").toLowerCase();t.style.setProperty(n,String(s))}})),document.body.appendChild(t)),t.dataset.turboTemporary="",t}addToContainer(e,t,s){s.escapeHtml&&(t.title=this.escapeHtml(t.title),t.message=this.escapeHtml(t.message));const n=this.stringToHTML(this.theme.render(t));n.classList.add("fl-container"),s.rtl&&n.classList.add("fl-rtl"),"bottom"===s.direction?e.append(n):e.prepend(n),requestAnimationFrame((()=>n.classList.add("fl-show")));const r=n.querySelector(".fl-close");if(r&&r.addEventListener("click",(e=>{e.stopPropagation(),this.removeNotification(n)})),n.addEventListener("click",(e=>{e.target.closest(".fl-close")||this.dispatchClickEvents(t)})),s.timeout>0)this.addTimer(n,s);else{n.classList.add("fl-sticky");const e=n.querySelector(".fl-progress-bar");if(e){const t=document.createElement("span");t.classList.add("fl-progress","fl-sticky-progress"),t.style.width="100%",e.append(t)}}}normalizeTimeout(e){return!1===e||"number"==typeof e&&e<0||null==e?0:Number(e)||0}addTimer(e,{timeout:t,fps:s}){if(t<=0)return;const n=1e3/s;let r,o=0;const i=()=>{o+=n;const s=e.querySelector(".fl-progress-bar");if(s){let e=s.querySelector(".fl-progress");e||(e=document.createElement("span"),e.classList.add("fl-progress"),s.append(e));const n=100*(1-o/t);e.style.width=`${Math.max(0,n)}%`}o>=t&&(clearInterval(r),this.removeNotification(e))};r=window.setInterval(i,n);const l=()=>{clearInterval(r),r=window.setInterval(i,n)},a=()=>clearInterval(r);e.addEventListener("mouseout",l),e.addEventListener("mouseover",a),e._flasherCleanup=()=>{clearInterval(r),e.removeEventListener("mouseout",l),e.removeEventListener("mouseover",a)}}removeNotification(e){e&&(e._flasherCleanup&&(e._flasherCleanup(),delete e._flasherCleanup),e.classList.remove("fl-show"),e.ontransitionend=()=>{const t=e.parentElement;e.remove(),t&&!t.hasChildNodes()&&t.remove()})}stringToHTML(e){const t=document.createElement("template");t.innerHTML=e.trim();const s=t.content.firstElementChild;if(!s)throw new Error("PHPFlasher: Invalid HTML template - no element found");return s}escapeHtml(e){if(null==e)return"";const t={"&":"&","<":"<",">":">",'"':""","'":"'","`":"`","=":"=","/":"/"};return e.replace(/[&<>"'`=/]/g,(e=>t[e]||e))}dispatchClickEvents(e){const t={envelope:e};window.dispatchEvent(new CustomEvent("flasher:theme:click",{detail:t}));const s=this.getThemeName(e);s&&window.dispatchEvent(new CustomEvent(`flasher:theme:${s}:click`,{detail:t}))}getThemeName(e){var t;const s=(null===(t=e.metadata)||void 0===t?void 0:t.plugin)||"";return s.startsWith("theme.")?s.replace("theme.",""):"flasher"===s?"flasher":s}}const n=new class extends t{constructor(){super(...arguments),this.defaultPlugin="flasher",this.plugins=new Map,this.themes=new Map,this.loadedAssets=new Set}render(t){return e(this,void 0,void 0,(function*(){const e=this.resolveResponse(t);try{yield this.addAssets([{urls:e.styles,nonce:e.context.csp_style_nonce,type:"style"},{urls:e.scripts,nonce:e.context.csp_script_nonce,type:"script"}]),this.renderOptions(e.options),this.renderEnvelopes(e.envelopes)}catch(e){console.error("PHPFlasher: Error rendering notifications",e)}}))}renderEnvelopes(e){if(!(null==e?void 0:e.length))return;const t={};e.forEach((e=>{const s=this.resolvePluginAlias(e.metadata.plugin);t[s]=t[s]||[],t[s].push(e)})),Object.entries(t).forEach((([e,t])=>{try{this.use(e).renderEnvelopes(t)}catch(t){console.error(`PHPFlasher: Error rendering envelopes for plugin "${e}"`,t)}}))}renderOptions(e){e&&Object.entries(e).forEach((([e,t])=>{try{this.use(e).renderOptions(t)}catch(t){console.error(`PHPFlasher: Error applying options for plugin "${e}"`,t)}}))}addPlugin(e,t){if(!e||!t)throw new Error("Both plugin name and instance are required");this.plugins.set(e,t)}addTheme(e,t){if(!e||!t)throw new Error("Both theme name and definition are required");this.themes.set(e,t)}use(e){const t=this.resolvePluginAlias(e);this.resolvePlugin(t);const s=this.plugins.get(t);if(!s)throw new Error(`Unable to resolve "${t}" plugin, did you forget to register it?`);return s}create(e){return this.use(e)}resolveResponse(e){const t=Object.assign({envelopes:[],options:{},scripts:[],styles:[],context:{}},e);return Object.entries(t.options).forEach((([e,s])=>{t.options[e]=this.resolveOptions(s)})),t.context.csp_style_nonce=t.context.csp_style_nonce||"",t.context.csp_script_nonce=t.context.csp_script_nonce||"",t.envelopes.forEach((e=>{e.metadata=e.metadata||{},e.metadata.plugin=this.resolvePluginAlias(e.metadata.plugin),this.addThemeStyles(t,e.metadata.plugin),e.options=this.resolveOptions(e.options),e.context=t.context})),t}resolveOptions(e){if(!e)return{};const t=Object.assign({},e);return Object.entries(t).forEach((([e,s])=>{t[e]=this.resolveFunction(s)})),t}resolveFunction(e){var t,s,n,r;if("string"!=typeof e)return e;const o=e.match(/^function\s*(\w*)\s*\(([^)]*)\)\s*\{([\s\S]*)\}$/),i=e.match(/^\s*(\(([^)]*)\)|[^=]+)\s*=>\s*([\s\S]+)$/);if(!o&&!i)return e;let l,a;o?(l=null!==(s=null===(t=o[2])||void 0===t?void 0:t.split(",").map((e=>e.trim())).filter(Boolean))&&void 0!==s?s:[],a=o[3].trim()):(l=null!==(r=null===(n=i[2])||void 0===n?void 0:n.split(",").map((e=>e.trim())).filter(Boolean))&&void 0!==r?r:[],a=i[3].trim(),a=a.startsWith("{")?a.slice(1,-1).trim():`return ${a};`);try{return new Function(...l,a)}catch(t){return console.error("PHPFlasher: Error converting string to function:",t),e}}resolvePlugin(e){if(this.plugins.get(e)||!e.includes("theme."))return;const t=e.replace("theme.",""),n=this.themes.get(t);n&&this.addPlugin(e,new s(n))}resolvePluginAlias(e){return"flasher"===(e=e||this.defaultPlugin)?"theme.flasher":e}addAssets(t){return e(this,void 0,void 0,(function*(){try{const e=t.filter((e=>"style"===e.type)),s=[];for(const{urls:t,nonce:n,type:r}of e)if(null==t?void 0:t.length)for(const e of t)e&&!this.loadedAssets.has(e)&&(s.push(this.loadAsset(e,n,r)),this.loadedAssets.add(e));yield Promise.all(s);const n=t.filter((e=>"script"===e.type));for(const{urls:e,nonce:t,type:s}of n)if(null==e?void 0:e.length)for(const n of e)n&&!this.loadedAssets.has(n)&&(yield this.loadAsset(n,t,s),this.loadedAssets.add(n))}catch(e){console.error("PHPFlasher: Error loading assets",e)}}))}loadAsset(e,t,s){const n="style"===s?`link[href="${e}"]`:`script[src="${e}"]`;return document.querySelector(n)?Promise.resolve():new Promise(((n,r)=>{const o=document.createElement("style"===s?"link":"script");"style"===s?(o.rel="stylesheet",o.href=e):(o.type="text/javascript",o.src=e),t&&o.setAttribute("nonce",t),o.onload=()=>n(),o.onerror=()=>r(new Error(`Failed to load ${e}`)),document.head.appendChild(o)}))}addThemeStyles(e,t){if("flasher"!==t&&!t.includes("theme."))return;const s=t.replace("theme.",""),n=this.themes.get(s);if(!(null==n?void 0:n.styles))return;const r=Array.isArray(n.styles)?n.styles:[n.styles];e.styles=Array.from(new Set([...e.styles,...r]))}};return n.addTheme("flasher",{render:e=>{const{type:t,title:s,message:n}=e,r="error"===t||"warning"===t,o=r?"alert":"status",i=r?"assertive":"polite",l=s||t.charAt(0).toUpperCase()+t.slice(1);return`\n
\n
\n
\n
\n ${l}\n ${n}\n
\n \n
\n \n \n \n
`}}),"undefined"!=typeof window&&(window.flasher=n),n})); diff --git a/src/Toastr/Laravel/FlasherToastrServiceProvider.php b/src/Toastr/Laravel/FlasherToastrServiceProvider.php index f926db82..f646cd96 100644 --- a/src/Toastr/Laravel/FlasherToastrServiceProvider.php +++ b/src/Toastr/Laravel/FlasherToastrServiceProvider.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace Flasher\Toastr\Laravel; use Flasher\Laravel\Support\PluginServiceProvider; +use Flasher\Prime\EventDispatcher\EventDispatcherInterface; use Flasher\Toastr\Prime\ToastrPlugin; final class FlasherToastrServiceProvider extends PluginServiceProvider @@ -13,4 +14,22 @@ final class FlasherToastrServiceProvider extends PluginServiceProvider { return new ToastrPlugin(); } + + protected function afterBoot(): void + { + $this->registerLivewireListener(); + } + + private function registerLivewireListener(): void + { + if (!$this->app->bound('livewire')) { + return; + } + + $this->app->extend('flasher.event_dispatcher', static function (EventDispatcherInterface $dispatcher) { + $dispatcher->addListener(new LivewireListener()); + + return $dispatcher; + }); + } } diff --git a/src/Toastr/Laravel/LivewireListener.php b/src/Toastr/Laravel/LivewireListener.php new file mode 100644 index 00000000..44b5838b --- /dev/null +++ b/src/Toastr/Laravel/LivewireListener.php @@ -0,0 +1,75 @@ +getPresenter()) { + return; + } + + $response = $event->getResponse() ?: ''; + if (!\is_string($response)) { + return; + } + + // Avoid duplicate script injection + if (false === strripos($response, ' +JAVASCRIPT; + + $event->setResponse($response); + } + + public function getSubscribedEvents(): string + { + return ResponseEvent::class; + } +} diff --git a/src/Toastr/Prime/Resources/assets/toastr.ts b/src/Toastr/Prime/Resources/assets/toastr.ts index 25225126..1e58ae85 100644 --- a/src/Toastr/Prime/Resources/assets/toastr.ts +++ b/src/Toastr/Prime/Resources/assets/toastr.ts @@ -17,7 +17,28 @@ export default class ToastrPlugin extends AbstractPlugin { try { const { message, title, type, options } = envelope - const instance = toastr[type as ToastrType](message, title, options as ToastrOptions) + // Wrap callbacks to dispatch events + const mergedOptions = { + ...options, + onShown: () => { + this.dispatchEvent('flasher:toastr:show', envelope); + (options as any)?.onShown?.() + }, + onclick: () => { + this.dispatchEvent('flasher:toastr:click', envelope); + (options as any)?.onclick?.() + }, + onCloseClick: () => { + this.dispatchEvent('flasher:toastr:close', envelope); + (options as any)?.onCloseClick?.() + }, + onHidden: () => { + this.dispatchEvent('flasher:toastr:hidden', envelope); + (options as any)?.onHidden?.() + }, + } as ToastrOptions + + const instance = toastr[type as ToastrType](message, title, mergedOptions) if (instance && instance.parent) { try { @@ -35,6 +56,12 @@ export default class ToastrPlugin extends AbstractPlugin { }) } + private dispatchEvent(eventName: string, envelope: Envelope): void { + window.dispatchEvent(new CustomEvent(eventName, { + detail: { envelope }, + })) + } + public renderOptions(options: Options): void { if (!this.isDependencyAvailable()) { return diff --git a/src/Toastr/Prime/Resources/dist/flasher-toastr.esm.js b/src/Toastr/Prime/Resources/dist/flasher-toastr.esm.js index 3b7fb42f..dfdd33ea 100644 --- a/src/Toastr/Prime/Resources/dist/flasher-toastr.esm.js +++ b/src/Toastr/Prime/Resources/dist/flasher-toastr.esm.js @@ -95,7 +95,24 @@ class ToastrPlugin extends AbstractPlugin { envelopes.forEach((envelope) => { try { const { message, title, type, options } = envelope; - const instance = toastr[type](message, title, options); + const mergedOptions = Object.assign(Object.assign({}, options), { onShown: () => { + var _a; + this.dispatchEvent('flasher:toastr:show', envelope); + (_a = options === null || options === void 0 ? void 0 : options.onShown) === null || _a === void 0 ? void 0 : _a.call(options); + }, onclick: () => { + var _a; + this.dispatchEvent('flasher:toastr:click', envelope); + (_a = options === null || options === void 0 ? void 0 : options.onclick) === null || _a === void 0 ? void 0 : _a.call(options); + }, onCloseClick: () => { + var _a; + this.dispatchEvent('flasher:toastr:close', envelope); + (_a = options === null || options === void 0 ? void 0 : options.onCloseClick) === null || _a === void 0 ? void 0 : _a.call(options); + }, onHidden: () => { + var _a; + this.dispatchEvent('flasher:toastr:hidden', envelope); + (_a = options === null || options === void 0 ? void 0 : options.onHidden) === null || _a === void 0 ? void 0 : _a.call(options); + } }); + const instance = toastr[type](message, title, mergedOptions); if (instance && instance.parent) { try { const parent = instance.parent(); @@ -113,6 +130,11 @@ class ToastrPlugin extends AbstractPlugin { } }); } + dispatchEvent(eventName, envelope) { + window.dispatchEvent(new CustomEvent(eventName, { + detail: { envelope }, + })); + } renderOptions(options) { if (!this.isDependencyAvailable()) { return; diff --git a/src/Toastr/Prime/Resources/dist/flasher-toastr.js b/src/Toastr/Prime/Resources/dist/flasher-toastr.js index a9c3bfd3..624f8621 100644 --- a/src/Toastr/Prime/Resources/dist/flasher-toastr.js +++ b/src/Toastr/Prime/Resources/dist/flasher-toastr.js @@ -98,7 +98,24 @@ envelopes.forEach((envelope) => { try { const { message, title, type, options } = envelope; - const instance = toastr[type](message, title, options); + const mergedOptions = Object.assign(Object.assign({}, options), { onShown: () => { + var _a; + this.dispatchEvent('flasher:toastr:show', envelope); + (_a = options === null || options === void 0 ? void 0 : options.onShown) === null || _a === void 0 ? void 0 : _a.call(options); + }, onclick: () => { + var _a; + this.dispatchEvent('flasher:toastr:click', envelope); + (_a = options === null || options === void 0 ? void 0 : options.onclick) === null || _a === void 0 ? void 0 : _a.call(options); + }, onCloseClick: () => { + var _a; + this.dispatchEvent('flasher:toastr:close', envelope); + (_a = options === null || options === void 0 ? void 0 : options.onCloseClick) === null || _a === void 0 ? void 0 : _a.call(options); + }, onHidden: () => { + var _a; + this.dispatchEvent('flasher:toastr:hidden', envelope); + (_a = options === null || options === void 0 ? void 0 : options.onHidden) === null || _a === void 0 ? void 0 : _a.call(options); + } }); + const instance = toastr[type](message, title, mergedOptions); if (instance && instance.parent) { try { const parent = instance.parent(); @@ -116,6 +133,11 @@ } }); } + dispatchEvent(eventName, envelope) { + window.dispatchEvent(new CustomEvent(eventName, { + detail: { envelope }, + })); + } renderOptions(options) { if (!this.isDependencyAvailable()) { return; diff --git a/src/Toastr/Prime/Resources/dist/flasher-toastr.min.js b/src/Toastr/Prime/Resources/dist/flasher-toastr.min.js index 7b046b9a..2f15a268 100644 --- a/src/Toastr/Prime/Resources/dist/flasher-toastr.min.js +++ b/src/Toastr/Prime/Resources/dist/flasher-toastr.min.js @@ -1 +1 @@ -!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t(require("@flasher/flasher"),require("toastr")):"function"==typeof define&&define.amd?define(["@flasher/flasher","toastr"],t):(e="undefined"!=typeof globalThis?globalThis:e||self).toastr=t(e.flasher,e.toastr)}(this,(function(e,t){"use strict";class s{success(e,t,s){this.flash("success",e,t,s)}error(e,t,s){this.flash("error",e,t,s)}info(e,t,s){this.flash("info",e,t,s)}warning(e,t,s){this.flash("warning",e,t,s)}flash(e,t,s,r){let o,i,n,a={};if("object"==typeof e?(a=Object.assign({},e),o=a.type,i=a.message,n=a.title,delete a.type,delete a.message,delete a.title):"object"==typeof t?(a=Object.assign({},t),o=e,i=a.message,n=a.title,delete a.message,delete a.title):(o=e,i=t,null==s?(n=void 0,a=r||{}):"string"==typeof s?(n=s,a=r||{}):"object"==typeof s&&(a=Object.assign({},s),"title"in a?(n=a.title,delete a.title):n=void 0,r&&"object"==typeof r&&(a=Object.assign(Object.assign({},a),r)))),!o)throw new Error("Type is required for notifications");if(null==i)throw new Error("Message is required for notifications");null==n&&(n=o.charAt(0).toUpperCase()+o.slice(1));const l={type:o,message:i,title:n,options:a,metadata:{plugin:""}};this.renderOptions({}),this.renderEnvelopes([l])}}const r=new class extends s{renderEnvelopes(e){(null==e?void 0:e.length)&&this.isDependencyAvailable()&&e.forEach((e=>{try{const{message:s,title:r,type:o,options:i}=e,n=t[o](s,r,i);if(n&&n.parent)try{const e=n.parent();e&&"function"==typeof e.attr&&e.attr("data-turbo-temporary","")}catch(e){console.error("PHPFlasher Toastr: Error setting Turbo compatibility",e)}}catch(t){console.error("PHPFlasher Toastr: Error rendering notification",t,e)}}))}renderOptions(e){if(this.isDependencyAvailable())try{t.options=Object.assign({timeOut:e.timeOut||1e4,progressBar:e.progressBar||!0},e)}catch(e){console.error("PHPFlasher Toastr: Error applying options",e)}}isDependencyAvailable(){return!(!window.jQuery&&!window.$)||(console.error("PHPFlasher Toastr: jQuery is required but not loaded. Make sure jQuery is loaded before using Toastr."),!1)}};return e.addPlugin("toastr",r),r})); +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t(require("@flasher/flasher"),require("toastr")):"function"==typeof define&&define.amd?define(["@flasher/flasher","toastr"],t):(e="undefined"!=typeof globalThis?globalThis:e||self).toastr=t(e.flasher,e.toastr)}(this,(function(e,t){"use strict";class s{success(e,t,s){this.flash("success",e,t,s)}error(e,t,s){this.flash("error",e,t,s)}info(e,t,s){this.flash("info",e,t,s)}warning(e,t,s){this.flash("warning",e,t,s)}flash(e,t,s,o){let i,r,n,l={};if("object"==typeof e?(l=Object.assign({},e),i=l.type,r=l.message,n=l.title,delete l.type,delete l.message,delete l.title):"object"==typeof t?(l=Object.assign({},t),i=e,r=l.message,n=l.title,delete l.message,delete l.title):(i=e,r=t,null==s?(n=void 0,l=o||{}):"string"==typeof s?(n=s,l=o||{}):"object"==typeof s&&(l=Object.assign({},s),"title"in l?(n=l.title,delete l.title):n=void 0,o&&"object"==typeof o&&(l=Object.assign(Object.assign({},l),o)))),!i)throw new Error("Type is required for notifications");if(null==r)throw new Error("Message is required for notifications");null==n&&(n=i.charAt(0).toUpperCase()+i.slice(1));const a={type:i,message:r,title:n,options:l,metadata:{plugin:""}};this.renderOptions({}),this.renderEnvelopes([a])}}const o=new class extends s{renderEnvelopes(e){(null==e?void 0:e.length)&&this.isDependencyAvailable()&&e.forEach((e=>{try{const{message:s,title:o,type:i,options:r}=e,n=Object.assign(Object.assign({},r),{onShown:()=>{var t;this.dispatchEvent("flasher:toastr:show",e),null===(t=null==r?void 0:r.onShown)||void 0===t||t.call(r)},onclick:()=>{var t;this.dispatchEvent("flasher:toastr:click",e),null===(t=null==r?void 0:r.onclick)||void 0===t||t.call(r)},onCloseClick:()=>{var t;this.dispatchEvent("flasher:toastr:close",e),null===(t=null==r?void 0:r.onCloseClick)||void 0===t||t.call(r)},onHidden:()=>{var t;this.dispatchEvent("flasher:toastr:hidden",e),null===(t=null==r?void 0:r.onHidden)||void 0===t||t.call(r)}}),l=t[i](s,o,n);if(l&&l.parent)try{const e=l.parent();e&&"function"==typeof e.attr&&e.attr("data-turbo-temporary","")}catch(e){console.error("PHPFlasher Toastr: Error setting Turbo compatibility",e)}}catch(t){console.error("PHPFlasher Toastr: Error rendering notification",t,e)}}))}dispatchEvent(e,t){window.dispatchEvent(new CustomEvent(e,{detail:{envelope:t}}))}renderOptions(e){if(this.isDependencyAvailable())try{t.options=Object.assign({timeOut:e.timeOut||1e4,progressBar:e.progressBar||!0},e)}catch(e){console.error("PHPFlasher Toastr: Error applying options",e)}}isDependencyAvailable(){return!(!window.jQuery&&!window.$)||(console.error("PHPFlasher Toastr: jQuery is required but not loaded. Make sure jQuery is loaded before using Toastr."),!1)}};return e.addPlugin("toastr",o),o})); diff --git a/src/Toastr/Prime/Resources/dist/toastr.d.ts b/src/Toastr/Prime/Resources/dist/toastr.d.ts index 6ca27722..5106ea5a 100644 --- a/src/Toastr/Prime/Resources/dist/toastr.d.ts +++ b/src/Toastr/Prime/Resources/dist/toastr.d.ts @@ -2,6 +2,7 @@ import { AbstractPlugin } from '@flasher/flasher/dist/plugin'; import type { Envelope, Options } from '@flasher/flasher/dist/types'; export default class ToastrPlugin extends AbstractPlugin { renderEnvelopes(envelopes: Envelope[]): void; + private dispatchEvent; renderOptions(options: Options): void; private isDependencyAvailable; } diff --git a/src/Toastr/Prime/Resources/public/flasher-toastr.min.js b/src/Toastr/Prime/Resources/public/flasher-toastr.min.js index 7b046b9a..2f15a268 100644 --- a/src/Toastr/Prime/Resources/public/flasher-toastr.min.js +++ b/src/Toastr/Prime/Resources/public/flasher-toastr.min.js @@ -1 +1 @@ -!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t(require("@flasher/flasher"),require("toastr")):"function"==typeof define&&define.amd?define(["@flasher/flasher","toastr"],t):(e="undefined"!=typeof globalThis?globalThis:e||self).toastr=t(e.flasher,e.toastr)}(this,(function(e,t){"use strict";class s{success(e,t,s){this.flash("success",e,t,s)}error(e,t,s){this.flash("error",e,t,s)}info(e,t,s){this.flash("info",e,t,s)}warning(e,t,s){this.flash("warning",e,t,s)}flash(e,t,s,r){let o,i,n,a={};if("object"==typeof e?(a=Object.assign({},e),o=a.type,i=a.message,n=a.title,delete a.type,delete a.message,delete a.title):"object"==typeof t?(a=Object.assign({},t),o=e,i=a.message,n=a.title,delete a.message,delete a.title):(o=e,i=t,null==s?(n=void 0,a=r||{}):"string"==typeof s?(n=s,a=r||{}):"object"==typeof s&&(a=Object.assign({},s),"title"in a?(n=a.title,delete a.title):n=void 0,r&&"object"==typeof r&&(a=Object.assign(Object.assign({},a),r)))),!o)throw new Error("Type is required for notifications");if(null==i)throw new Error("Message is required for notifications");null==n&&(n=o.charAt(0).toUpperCase()+o.slice(1));const l={type:o,message:i,title:n,options:a,metadata:{plugin:""}};this.renderOptions({}),this.renderEnvelopes([l])}}const r=new class extends s{renderEnvelopes(e){(null==e?void 0:e.length)&&this.isDependencyAvailable()&&e.forEach((e=>{try{const{message:s,title:r,type:o,options:i}=e,n=t[o](s,r,i);if(n&&n.parent)try{const e=n.parent();e&&"function"==typeof e.attr&&e.attr("data-turbo-temporary","")}catch(e){console.error("PHPFlasher Toastr: Error setting Turbo compatibility",e)}}catch(t){console.error("PHPFlasher Toastr: Error rendering notification",t,e)}}))}renderOptions(e){if(this.isDependencyAvailable())try{t.options=Object.assign({timeOut:e.timeOut||1e4,progressBar:e.progressBar||!0},e)}catch(e){console.error("PHPFlasher Toastr: Error applying options",e)}}isDependencyAvailable(){return!(!window.jQuery&&!window.$)||(console.error("PHPFlasher Toastr: jQuery is required but not loaded. Make sure jQuery is loaded before using Toastr."),!1)}};return e.addPlugin("toastr",r),r})); +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t(require("@flasher/flasher"),require("toastr")):"function"==typeof define&&define.amd?define(["@flasher/flasher","toastr"],t):(e="undefined"!=typeof globalThis?globalThis:e||self).toastr=t(e.flasher,e.toastr)}(this,(function(e,t){"use strict";class s{success(e,t,s){this.flash("success",e,t,s)}error(e,t,s){this.flash("error",e,t,s)}info(e,t,s){this.flash("info",e,t,s)}warning(e,t,s){this.flash("warning",e,t,s)}flash(e,t,s,o){let i,r,n,l={};if("object"==typeof e?(l=Object.assign({},e),i=l.type,r=l.message,n=l.title,delete l.type,delete l.message,delete l.title):"object"==typeof t?(l=Object.assign({},t),i=e,r=l.message,n=l.title,delete l.message,delete l.title):(i=e,r=t,null==s?(n=void 0,l=o||{}):"string"==typeof s?(n=s,l=o||{}):"object"==typeof s&&(l=Object.assign({},s),"title"in l?(n=l.title,delete l.title):n=void 0,o&&"object"==typeof o&&(l=Object.assign(Object.assign({},l),o)))),!i)throw new Error("Type is required for notifications");if(null==r)throw new Error("Message is required for notifications");null==n&&(n=i.charAt(0).toUpperCase()+i.slice(1));const a={type:i,message:r,title:n,options:l,metadata:{plugin:""}};this.renderOptions({}),this.renderEnvelopes([a])}}const o=new class extends s{renderEnvelopes(e){(null==e?void 0:e.length)&&this.isDependencyAvailable()&&e.forEach((e=>{try{const{message:s,title:o,type:i,options:r}=e,n=Object.assign(Object.assign({},r),{onShown:()=>{var t;this.dispatchEvent("flasher:toastr:show",e),null===(t=null==r?void 0:r.onShown)||void 0===t||t.call(r)},onclick:()=>{var t;this.dispatchEvent("flasher:toastr:click",e),null===(t=null==r?void 0:r.onclick)||void 0===t||t.call(r)},onCloseClick:()=>{var t;this.dispatchEvent("flasher:toastr:close",e),null===(t=null==r?void 0:r.onCloseClick)||void 0===t||t.call(r)},onHidden:()=>{var t;this.dispatchEvent("flasher:toastr:hidden",e),null===(t=null==r?void 0:r.onHidden)||void 0===t||t.call(r)}}),l=t[i](s,o,n);if(l&&l.parent)try{const e=l.parent();e&&"function"==typeof e.attr&&e.attr("data-turbo-temporary","")}catch(e){console.error("PHPFlasher Toastr: Error setting Turbo compatibility",e)}}catch(t){console.error("PHPFlasher Toastr: Error rendering notification",t,e)}}))}dispatchEvent(e,t){window.dispatchEvent(new CustomEvent(e,{detail:{envelope:t}}))}renderOptions(e){if(this.isDependencyAvailable())try{t.options=Object.assign({timeOut:e.timeOut||1e4,progressBar:e.progressBar||!0},e)}catch(e){console.error("PHPFlasher Toastr: Error applying options",e)}}isDependencyAvailable(){return!(!window.jQuery&&!window.$)||(console.error("PHPFlasher Toastr: jQuery is required but not loaded. Make sure jQuery is loaded before using Toastr."),!1)}};return e.addPlugin("toastr",o),o})); diff --git a/tests/Laravel/EventListener/ThemeLivewireListenerTest.php b/tests/Laravel/EventListener/ThemeLivewireListenerTest.php new file mode 100644 index 00000000..e6ee739a --- /dev/null +++ b/tests/Laravel/EventListener/ThemeLivewireListenerTest.php @@ -0,0 +1,70 @@ +listener = new ThemeLivewireListener(); + } + + public function testGetSubscribedEvents(): void + { + $this->assertSame(ResponseEvent::class, $this->listener->getSubscribedEvents()); + } + + public function testInvokeSkipsNonHtmlPresenter(): void + { + $event = new ResponseEvent('', 'json'); + + ($this->listener)($event); + + $this->assertSame('', $event->getResponse()); + } + + public function testInvokeSkipsResponseWithoutFlasherScript(): void + { + $event = new ResponseEvent('No flasher', 'html'); + + ($this->listener)($event); + + $this->assertSame('No flasher', $event->getResponse()); + } + + public function testInvokeSkipsDuplicateInjection(): void + { + $response = ''; + $response .= ''; + + $event = new ResponseEvent($response, 'html'); + + ($this->listener)($event); + + $this->assertSame($response, $event->getResponse()); + } + + public function testInvokeInjectsLivewireScript(): void + { + $response = ''; + + $event = new ResponseEvent($response, 'html'); + + ($this->listener)($event); + + $this->assertStringContainsString('flasher-theme-livewire-js', $event->getResponse()); + $this->assertStringContainsString('flasher:theme:click', $event->getResponse()); + $this->assertStringContainsString('theme:click', $event->getResponse()); + $this->assertStringContainsString("theme:' + themeName + ':click", $event->getResponse()); + } +} diff --git a/tests/Noty/Laravel/FlasherNotyServiceProviderTest.php b/tests/Noty/Laravel/FlasherNotyServiceProviderTest.php index cc2475b5..3677a428 100644 --- a/tests/Noty/Laravel/FlasherNotyServiceProviderTest.php +++ b/tests/Noty/Laravel/FlasherNotyServiceProviderTest.php @@ -53,6 +53,23 @@ final class FlasherNotyServiceProviderTest extends TestCase $this->app->expects('singleton'); $this->app->expects('alias'); $this->app->expects('extend'); + $this->app->expects('bound')->with('livewire')->andReturn(false); + + $this->serviceProvider->register(); + $this->serviceProvider->boot(); + $this->addToAssertionCount(1); + } + + public function testBootWithLivewire(): void + { + $this->app->expects()->make('config')->andReturns($configMock = \Mockery::mock(Repository::class)); + $configMock->expects('get')->andReturns([]); + $configMock->expects('set'); + + $this->app->expects('singleton'); + $this->app->expects('alias'); + $this->app->expects('extend')->twice(); + $this->app->expects('bound')->with('livewire')->andReturn(true); $this->serviceProvider->register(); $this->serviceProvider->boot(); diff --git a/tests/Noty/Laravel/LivewireListenerTest.php b/tests/Noty/Laravel/LivewireListenerTest.php new file mode 100644 index 00000000..832ad30b --- /dev/null +++ b/tests/Noty/Laravel/LivewireListenerTest.php @@ -0,0 +1,71 @@ +listener = new LivewireListener(); + } + + public function testGetSubscribedEvents(): void + { + $this->assertSame(ResponseEvent::class, $this->listener->getSubscribedEvents()); + } + + public function testInvokeSkipsNonHtmlPresenter(): void + { + $event = new ResponseEvent('', 'json'); + + ($this->listener)($event); + + $this->assertSame('', $event->getResponse()); + } + + public function testInvokeSkipsResponseWithoutFlasherScript(): void + { + $event = new ResponseEvent('No flasher', 'html'); + + ($this->listener)($event); + + $this->assertSame('No flasher', $event->getResponse()); + } + + public function testInvokeSkipsDuplicateInjection(): void + { + $response = ''; + $response .= ''; + + $event = new ResponseEvent($response, 'html'); + + ($this->listener)($event); + + $this->assertSame($response, $event->getResponse()); + } + + public function testInvokeInjectsLivewireScript(): void + { + $response = ''; + + $event = new ResponseEvent($response, 'html'); + + ($this->listener)($event); + + $this->assertStringContainsString('flasher-noty-livewire-js', $event->getResponse()); + $this->assertStringContainsString('flasher:noty:show', $event->getResponse()); + $this->assertStringContainsString('flasher:noty:click', $event->getResponse()); + $this->assertStringContainsString('flasher:noty:close', $event->getResponse()); + $this->assertStringContainsString('flasher:noty:hover', $event->getResponse()); + } +} diff --git a/tests/Notyf/Laravel/FlasherNotyfServiceProviderTest.php b/tests/Notyf/Laravel/FlasherNotyfServiceProviderTest.php index 1179f85f..2c529c9f 100644 --- a/tests/Notyf/Laravel/FlasherNotyfServiceProviderTest.php +++ b/tests/Notyf/Laravel/FlasherNotyfServiceProviderTest.php @@ -53,6 +53,23 @@ final class FlasherNotyfServiceProviderTest extends TestCase $this->app->expects('singleton'); $this->app->expects('alias'); $this->app->expects('extend'); + $this->app->expects('bound')->with('livewire')->andReturn(false); + + $this->serviceProvider->register(); + $this->serviceProvider->boot(); + $this->addToAssertionCount(1); + } + + public function testBootWithLivewire(): void + { + $this->app->expects()->make('config')->andReturns($configMock = \Mockery::mock(Repository::class)); + $configMock->expects('get')->andReturns([]); + $configMock->expects('set'); + + $this->app->expects('singleton'); + $this->app->expects('alias'); + $this->app->expects('extend')->twice(); + $this->app->expects('bound')->with('livewire')->andReturn(true); $this->serviceProvider->register(); $this->serviceProvider->boot(); diff --git a/tests/Notyf/Laravel/LivewireListenerTest.php b/tests/Notyf/Laravel/LivewireListenerTest.php new file mode 100644 index 00000000..102395c1 --- /dev/null +++ b/tests/Notyf/Laravel/LivewireListenerTest.php @@ -0,0 +1,69 @@ +listener = new LivewireListener(); + } + + public function testGetSubscribedEvents(): void + { + $this->assertSame(ResponseEvent::class, $this->listener->getSubscribedEvents()); + } + + public function testInvokeSkipsNonHtmlPresenter(): void + { + $event = new ResponseEvent('', 'json'); + + ($this->listener)($event); + + $this->assertSame('', $event->getResponse()); + } + + public function testInvokeSkipsResponseWithoutFlasherScript(): void + { + $event = new ResponseEvent('No flasher', 'html'); + + ($this->listener)($event); + + $this->assertSame('No flasher', $event->getResponse()); + } + + public function testInvokeSkipsDuplicateInjection(): void + { + $response = ''; + $response .= ''; + + $event = new ResponseEvent($response, 'html'); + + ($this->listener)($event); + + $this->assertSame($response, $event->getResponse()); + } + + public function testInvokeInjectsLivewireScript(): void + { + $response = ''; + + $event = new ResponseEvent($response, 'html'); + + ($this->listener)($event); + + $this->assertStringContainsString('flasher-notyf-livewire-js', $event->getResponse()); + $this->assertStringContainsString('flasher:notyf:click', $event->getResponse()); + $this->assertStringContainsString('flasher:notyf:dismiss', $event->getResponse()); + } +} diff --git a/tests/Toastr/Laravel/FlasherToastrServiceProviderTest.php b/tests/Toastr/Laravel/FlasherToastrServiceProviderTest.php index 1d9289ef..8936edce 100644 --- a/tests/Toastr/Laravel/FlasherToastrServiceProviderTest.php +++ b/tests/Toastr/Laravel/FlasherToastrServiceProviderTest.php @@ -53,6 +53,23 @@ final class FlasherToastrServiceProviderTest extends TestCase $this->app->expects('singleton'); $this->app->expects('alias'); $this->app->expects('extend'); + $this->app->expects('bound')->with('livewire')->andReturn(false); + + $this->serviceProvider->register(); + $this->serviceProvider->boot(); + $this->addToAssertionCount(1); + } + + public function testBootWithLivewire(): void + { + $this->app->expects()->make('config')->andReturns($configMock = \Mockery::mock(Repository::class)); + $configMock->expects('get')->andReturns([]); + $configMock->expects('set'); + + $this->app->expects('singleton'); + $this->app->expects('alias'); + $this->app->expects('extend')->twice(); + $this->app->expects('bound')->with('livewire')->andReturn(true); $this->serviceProvider->register(); $this->serviceProvider->boot(); diff --git a/tests/Toastr/Laravel/LivewireListenerTest.php b/tests/Toastr/Laravel/LivewireListenerTest.php new file mode 100644 index 00000000..bbe3ab73 --- /dev/null +++ b/tests/Toastr/Laravel/LivewireListenerTest.php @@ -0,0 +1,71 @@ +listener = new LivewireListener(); + } + + public function testGetSubscribedEvents(): void + { + $this->assertSame(ResponseEvent::class, $this->listener->getSubscribedEvents()); + } + + public function testInvokeSkipsNonHtmlPresenter(): void + { + $event = new ResponseEvent('', 'json'); + + ($this->listener)($event); + + $this->assertSame('', $event->getResponse()); + } + + public function testInvokeSkipsResponseWithoutFlasherScript(): void + { + $event = new ResponseEvent('No flasher', 'html'); + + ($this->listener)($event); + + $this->assertSame('No flasher', $event->getResponse()); + } + + public function testInvokeSkipsDuplicateInjection(): void + { + $response = ''; + $response .= ''; + + $event = new ResponseEvent($response, 'html'); + + ($this->listener)($event); + + $this->assertSame($response, $event->getResponse()); + } + + public function testInvokeInjectsLivewireScript(): void + { + $response = ''; + + $event = new ResponseEvent($response, 'html'); + + ($this->listener)($event); + + $this->assertStringContainsString('flasher-toastr-livewire-js', $event->getResponse()); + $this->assertStringContainsString('flasher:toastr:show', $event->getResponse()); + $this->assertStringContainsString('flasher:toastr:click', $event->getResponse()); + $this->assertStringContainsString('flasher:toastr:close', $event->getResponse()); + $this->assertStringContainsString('flasher:toastr:hidden', $event->getResponse()); + } +} diff --git a/tests/adapters/noty.test.ts b/tests/adapters/noty.test.ts index 8b48c749..7a151864 100644 --- a/tests/adapters/noty.test.ts +++ b/tests/adapters/noty.test.ts @@ -224,4 +224,99 @@ describe('NotyPlugin', () => { }) }) }) + + describe('event dispatching', () => { + it('should set up event callbacks that dispatch custom events', () => { + plugin.renderEnvelopes([createEnvelope()]) + + // Verify callbacks are set up in options + const calledOptions = MockNoty.mock.calls[0][0] + expect(calledOptions.callbacks).toBeDefined() + expect(calledOptions.callbacks.onShow).toBeInstanceOf(Function) + expect(calledOptions.callbacks.onClick).toBeInstanceOf(Function) + expect(calledOptions.callbacks.onClose).toBeInstanceOf(Function) + expect(calledOptions.callbacks.onHover).toBeInstanceOf(Function) + }) + + it('should dispatch flasher:noty:show event when onShow callback is called', () => { + const eventHandler = vi.fn() + window.addEventListener('flasher:noty:show', eventHandler) + + plugin.renderEnvelopes([createEnvelope({ message: 'Test' })]) + + const calledOptions = MockNoty.mock.calls[0][0] + calledOptions.callbacks.onShow() + + expect(eventHandler).toHaveBeenCalledWith(expect.objectContaining({ + detail: expect.objectContaining({ + envelope: expect.objectContaining({ message: 'Test' }), + }), + })) + + window.removeEventListener('flasher:noty:show', eventHandler) + }) + + it('should dispatch flasher:noty:click event when onClick callback is called', () => { + const eventHandler = vi.fn() + window.addEventListener('flasher:noty:click', eventHandler) + + plugin.renderEnvelopes([createEnvelope({ message: 'Click test' })]) + + const calledOptions = MockNoty.mock.calls[0][0] + calledOptions.callbacks.onClick() + + expect(eventHandler).toHaveBeenCalledWith(expect.objectContaining({ + detail: expect.objectContaining({ + envelope: expect.objectContaining({ message: 'Click test' }), + }), + })) + + window.removeEventListener('flasher:noty:click', eventHandler) + }) + + it('should dispatch flasher:noty:close event when onClose callback is called', () => { + const eventHandler = vi.fn() + window.addEventListener('flasher:noty:close', eventHandler) + + plugin.renderEnvelopes([createEnvelope()]) + + const calledOptions = MockNoty.mock.calls[0][0] + calledOptions.callbacks.onClose() + + expect(eventHandler).toHaveBeenCalled() + + window.removeEventListener('flasher:noty:close', eventHandler) + }) + + it('should dispatch flasher:noty:hover event when onHover callback is called', () => { + const eventHandler = vi.fn() + window.addEventListener('flasher:noty:hover', eventHandler) + + plugin.renderEnvelopes([createEnvelope()]) + + const calledOptions = MockNoty.mock.calls[0][0] + calledOptions.callbacks.onHover() + + expect(eventHandler).toHaveBeenCalled() + + window.removeEventListener('flasher:noty:hover', eventHandler) + }) + + it('should call original callbacks if provided', () => { + const originalOnClick = vi.fn() + + plugin.renderEnvelopes([createEnvelope({ + options: { + callbacks: { + onClick: originalOnClick, + }, + }, + })]) + + const calledOptions = MockNoty.mock.calls[0][0] + calledOptions.callbacks.onClick() + + expect(originalOnClick).toHaveBeenCalled() + }) + }) }) diff --git a/tests/adapters/toastr.test.ts b/tests/adapters/toastr.test.ts index 7bf34828..c26b4021 100644 --- a/tests/adapters/toastr.test.ts +++ b/tests/adapters/toastr.test.ts @@ -81,7 +81,7 @@ describe('ToastrPlugin', () => { expect(mockToastr.success).toHaveBeenCalledWith( 'Hello World', 'Greeting', - { timeOut: 5000 }, + expect.objectContaining({ timeOut: 5000 }), ) }) @@ -218,4 +218,94 @@ describe('ToastrPlugin', () => { expect(mockToastr.warning).toHaveBeenCalled() }) }) + + describe('event dispatching', () => { + it('should set up event callbacks that dispatch custom events', () => { + plugin.renderEnvelopes([createEnvelope()]) + + // Verify options passed to toastr include event callbacks + const calledOptions = mockToastr.success.mock.calls[0][2] + expect(calledOptions.onShown).toBeInstanceOf(Function) + expect(calledOptions.onclick).toBeInstanceOf(Function) + expect(calledOptions.onCloseClick).toBeInstanceOf(Function) + expect(calledOptions.onHidden).toBeInstanceOf(Function) + }) + + it('should dispatch flasher:toastr:show event when onShown callback is called', () => { + const eventHandler = vi.fn() + window.addEventListener('flasher:toastr:show', eventHandler) + + plugin.renderEnvelopes([createEnvelope({ message: 'Show test' })]) + + const calledOptions = mockToastr.success.mock.calls[0][2] + calledOptions.onShown() + + expect(eventHandler).toHaveBeenCalledWith(expect.objectContaining({ + detail: expect.objectContaining({ + envelope: expect.objectContaining({ message: 'Show test' }), + }), + })) + + window.removeEventListener('flasher:toastr:show', eventHandler) + }) + + it('should dispatch flasher:toastr:click event when onclick callback is called', () => { + const eventHandler = vi.fn() + window.addEventListener('flasher:toastr:click', eventHandler) + + plugin.renderEnvelopes([createEnvelope({ message: 'Click test' })]) + + const calledOptions = mockToastr.success.mock.calls[0][2] + calledOptions.onclick() + + expect(eventHandler).toHaveBeenCalledWith(expect.objectContaining({ + detail: expect.objectContaining({ + envelope: expect.objectContaining({ message: 'Click test' }), + }), + })) + + window.removeEventListener('flasher:toastr:click', eventHandler) + }) + + it('should dispatch flasher:toastr:close event when onCloseClick callback is called', () => { + const eventHandler = vi.fn() + window.addEventListener('flasher:toastr:close', eventHandler) + + plugin.renderEnvelopes([createEnvelope()]) + + const calledOptions = mockToastr.success.mock.calls[0][2] + calledOptions.onCloseClick() + + expect(eventHandler).toHaveBeenCalled() + + window.removeEventListener('flasher:toastr:close', eventHandler) + }) + + it('should dispatch flasher:toastr:hidden event when onHidden callback is called', () => { + const eventHandler = vi.fn() + window.addEventListener('flasher:toastr:hidden', eventHandler) + + plugin.renderEnvelopes([createEnvelope()]) + + const calledOptions = mockToastr.success.mock.calls[0][2] + calledOptions.onHidden() + + expect(eventHandler).toHaveBeenCalled() + + window.removeEventListener('flasher:toastr:hidden', eventHandler) + }) + + it('should call original callbacks if provided', () => { + const originalOnClick = vi.fn() + + plugin.renderEnvelopes([createEnvelope({ + options: { onclick: originalOnClick }, + })]) + + const calledOptions = mockToastr.success.mock.calls[0][2] + calledOptions.onclick() + + expect(originalOnClick).toHaveBeenCalled() + }) + }) }) diff --git a/tests/flasher-plugin.test.ts b/tests/flasher-plugin.test.ts index 3d228992..d435bb07 100644 --- a/tests/flasher-plugin.test.ts +++ b/tests/flasher-plugin.test.ts @@ -675,4 +675,99 @@ describe('FlasherPlugin', () => { }) }) }) + + describe('click event dispatching', () => { + it('should dispatch flasher:theme:click event when notification is clicked', () => { + const eventHandler = vi.fn() + window.addEventListener('flasher:theme:click', eventHandler) + + plugin.renderEnvelopes([createEnvelope({ message: 'Click me' })]) + + const notification = document.querySelector('.fl-notification') as HTMLElement + expect(notification).toBeTruthy() + + notification.click() + + expect(eventHandler).toHaveBeenCalledWith(expect.objectContaining({ + detail: expect.objectContaining({ + envelope: expect.objectContaining({ message: 'Click me' }), + }), + })) + + window.removeEventListener('flasher:theme:click', eventHandler) + }) + + it('should dispatch theme-specific click event (flasher:theme:{name}:click)', () => { + const eventHandler = vi.fn() + window.addEventListener('flasher:theme:test:click', eventHandler) + + plugin.renderEnvelopes([createEnvelope({ + message: 'Theme specific', + metadata: { plugin: 'theme.test' }, + })]) + + const notification = document.querySelector('.fl-notification') as HTMLElement + notification.click() + + expect(eventHandler).toHaveBeenCalledWith(expect.objectContaining({ + detail: expect.objectContaining({ + envelope: expect.objectContaining({ message: 'Theme specific' }), + }), + })) + + window.removeEventListener('flasher:theme:test:click', eventHandler) + }) + + it('should dispatch both generic and specific events on click', () => { + const genericHandler = vi.fn() + const specificHandler = vi.fn() + window.addEventListener('flasher:theme:click', genericHandler) + window.addEventListener('flasher:theme:test:click', specificHandler) + + plugin.renderEnvelopes([createEnvelope({ + metadata: { plugin: 'theme.test' }, + })]) + + const notification = document.querySelector('.fl-notification') as HTMLElement + notification.click() + + expect(genericHandler).toHaveBeenCalled() + expect(specificHandler).toHaveBeenCalled() + + window.removeEventListener('flasher:theme:click', genericHandler) + window.removeEventListener('flasher:theme:test:click', specificHandler) + }) + + it('should not dispatch click event when close button is clicked', () => { + const eventHandler = vi.fn() + window.addEventListener('flasher:theme:click', eventHandler) + + plugin.renderEnvelopes([createEnvelope()]) + + const closeButton = document.querySelector('.fl-close') as HTMLElement + expect(closeButton).toBeTruthy() + + closeButton.click() + + expect(eventHandler).not.toHaveBeenCalled() + + window.removeEventListener('flasher:theme:click', eventHandler) + }) + + it('should handle flasher plugin alias correctly', () => { + const eventHandler = vi.fn() + window.addEventListener('flasher:theme:flasher:click', eventHandler) + + plugin.renderEnvelopes([createEnvelope({ + metadata: { plugin: 'flasher' }, + })]) + + const notification = document.querySelector('.fl-notification') as HTMLElement + notification.click() + + expect(eventHandler).toHaveBeenCalled() + + window.removeEventListener('flasher:theme:flasher:click', eventHandler) + }) + }) })