mirror of
https://github.com/php-flasher/php-flasher.git
synced 2026-03-31 15:07:47 +01:00
add event dispatching system for Livewire integration
Implement consistent event dispatching across Noty, Notyf, Toastr adapters and
themes, following the existing SweetAlert pattern. This enables Livewire
integration for all notification types.
JavaScript Events:
- Noty: flasher:noty:show, flasher:noty:click, flasher:noty:close, flasher:noty:hover
- Notyf: flasher:notyf:click, flasher:notyf:dismiss
- Toastr: flasher:toastr:show, flasher:toastr:click, flasher:toastr:close, flasher:toastr:hidden
- Themes: flasher:theme:click (generic), flasher:theme:{name}:click (specific)
PHP Livewire Listeners:
- LivewireListener for each adapter (Noty, Notyf, Toastr)
- ThemeLivewireListener for theme click events
- Registered in service providers when Livewire is bound
This allows Livewire users to listen for notification events and react
accordingly (e.g., noty:click, theme:flasher:click).
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Flasher\Laravel\EventListener;
|
||||
|
||||
use Flasher\Prime\EventDispatcher\Event\ResponseEvent;
|
||||
use Flasher\Prime\EventDispatcher\EventListener\EventListenerInterface;
|
||||
|
||||
final readonly class ThemeLivewireListener implements EventListenerInterface
|
||||
{
|
||||
public function __invoke(ResponseEvent $event): void
|
||||
{
|
||||
// Only process HTML responses
|
||||
if ('html' !== $event->getPresenter()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$response = $event->getResponse() ?: '';
|
||||
if (!\is_string($response)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Avoid duplicate script injection
|
||||
if (false === strripos($response, '<script type="text/javascript" class="flasher-js"')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (strripos($response, '<script type="text/javascript" class="flasher-theme-livewire-js"')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Inject the Theme-Livewire bridge JavaScript
|
||||
$response .= <<<'JAVASCRIPT'
|
||||
<script type="text/javascript" class="flasher-theme-livewire-js">
|
||||
(function() {
|
||||
window.addEventListener('flasher:theme:click', function(event) {
|
||||
if (typeof Livewire === 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
const { detail } = event;
|
||||
const { envelope } = detail;
|
||||
const context = envelope.context || {};
|
||||
|
||||
if (!context.livewire?.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { livewire: { id: componentId } } = context;
|
||||
const component = Livewire.all().find(c => c.id === componentId);
|
||||
|
||||
if (!component) {
|
||||
return;
|
||||
}
|
||||
|
||||
Livewire.dispatchTo(component.name, 'theme:click', { payload: detail });
|
||||
|
||||
// Also dispatch theme-specific event
|
||||
const plugin = envelope.metadata?.plugin || '';
|
||||
let themeName = plugin;
|
||||
if (plugin.startsWith('theme.')) {
|
||||
themeName = plugin.replace('theme.', '');
|
||||
}
|
||||
if (themeName) {
|
||||
Livewire.dispatchTo(component.name, 'theme:' + themeName + ':click', { payload: detail });
|
||||
}
|
||||
}, false);
|
||||
})();
|
||||
</script>
|
||||
JAVASCRIPT;
|
||||
|
||||
$event->setResponse($response);
|
||||
}
|
||||
|
||||
public function getSubscribedEvents(): string
|
||||
{
|
||||
return ResponseEvent::class;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Flasher\Noty\Laravel;
|
||||
|
||||
use Flasher\Prime\EventDispatcher\Event\ResponseEvent;
|
||||
use Flasher\Prime\EventDispatcher\EventListener\EventListenerInterface;
|
||||
|
||||
final readonly class LivewireListener implements EventListenerInterface
|
||||
{
|
||||
public function __invoke(ResponseEvent $event): void
|
||||
{
|
||||
// Only process HTML responses
|
||||
if ('html' !== $event->getPresenter()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$response = $event->getResponse() ?: '';
|
||||
if (!\is_string($response)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Avoid duplicate script injection
|
||||
if (false === strripos($response, '<script type="text/javascript" class="flasher-js"')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (strripos($response, '<script type="text/javascript" class="flasher-noty-livewire-js"')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Inject the Noty-Livewire bridge JavaScript
|
||||
$response .= <<<'JAVASCRIPT'
|
||||
<script type="text/javascript" class="flasher-noty-livewire-js">
|
||||
(function() {
|
||||
const events = ['flasher:noty:show', 'flasher:noty:click', 'flasher:noty:close', 'flasher:noty:hover'];
|
||||
|
||||
events.forEach(function(eventName) {
|
||||
window.addEventListener(eventName, function(event) {
|
||||
if (typeof Livewire === 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
const { detail } = event;
|
||||
const { envelope } = detail;
|
||||
const context = envelope.context || {};
|
||||
|
||||
if (!context.livewire?.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { livewire: { id: componentId } } = context;
|
||||
const component = Livewire.all().find(c => c.id === componentId);
|
||||
|
||||
if (!component) {
|
||||
return;
|
||||
}
|
||||
|
||||
const livewireEventName = eventName.replace('flasher:', '').replace(':', ':');
|
||||
Livewire.dispatchTo(component.name, livewireEventName, { payload: detail });
|
||||
}, false);
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
JAVASCRIPT;
|
||||
|
||||
$event->setResponse($response);
|
||||
}
|
||||
|
||||
public function getSubscribedEvents(): string
|
||||
{
|
||||
return ResponseEvent::class;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
+29
@@ -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;
|
||||
|
||||
+1
-1
@@ -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}));
|
||||
|
||||
+1
@@ -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;
|
||||
}
|
||||
|
||||
+1
-1
@@ -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}));
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Flasher\Notyf\Laravel;
|
||||
|
||||
use Flasher\Prime\EventDispatcher\Event\ResponseEvent;
|
||||
use Flasher\Prime\EventDispatcher\EventListener\EventListenerInterface;
|
||||
|
||||
final readonly class LivewireListener implements EventListenerInterface
|
||||
{
|
||||
public function __invoke(ResponseEvent $event): void
|
||||
{
|
||||
// Only process HTML responses
|
||||
if ('html' !== $event->getPresenter()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$response = $event->getResponse() ?: '';
|
||||
if (!\is_string($response)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Avoid duplicate script injection
|
||||
if (false === strripos($response, '<script type="text/javascript" class="flasher-js"')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (strripos($response, '<script type="text/javascript" class="flasher-notyf-livewire-js"')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Inject the Notyf-Livewire bridge JavaScript
|
||||
$response .= <<<'JAVASCRIPT'
|
||||
<script type="text/javascript" class="flasher-notyf-livewire-js">
|
||||
(function() {
|
||||
const events = ['flasher:notyf:click', 'flasher:notyf:dismiss'];
|
||||
|
||||
events.forEach(function(eventName) {
|
||||
window.addEventListener(eventName, function(event) {
|
||||
if (typeof Livewire === 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
const { detail } = event;
|
||||
const { envelope } = detail;
|
||||
const context = envelope.context || {};
|
||||
|
||||
if (!context.livewire?.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { livewire: { id: componentId } } = context;
|
||||
const component = Livewire.all().find(c => c.id === componentId);
|
||||
|
||||
if (!component) {
|
||||
return;
|
||||
}
|
||||
|
||||
const livewireEventName = eventName.replace('flasher:', '').replace(':', ':');
|
||||
Livewire.dispatchTo(component.name, livewireEventName, { payload: detail });
|
||||
}, false);
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
JAVASCRIPT;
|
||||
|
||||
$event->setResponse($response);
|
||||
}
|
||||
|
||||
public function getSubscribedEvents(): string
|
||||
{
|
||||
return ResponseEvent::class;
|
||||
}
|
||||
}
|
||||
@@ -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<string, any> = {}): void {
|
||||
window.dispatchEvent(new CustomEvent(eventName, {
|
||||
detail: { envelope, ...extra },
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
+25
-1
@@ -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();
|
||||
|
||||
+25
-1
@@ -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();
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
+2
@@ -7,4 +7,6 @@ export default class NotyfPlugin extends AbstractPlugin {
|
||||
renderOptions(options: Options): void;
|
||||
private initializeNotyf;
|
||||
private addTypeIfNotExists;
|
||||
private attachEventListeners;
|
||||
private dispatchEvent;
|
||||
}
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,4 +14,6 @@ export default class FlasherPlugin extends AbstractPlugin {
|
||||
private removeNotification;
|
||||
private stringToHTML;
|
||||
private escapeHtml;
|
||||
private dispatchClickEvents;
|
||||
private getThemeName;
|
||||
}
|
||||
|
||||
+25
@@ -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 {
|
||||
|
||||
Vendored
+25
@@ -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 {
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
@@ -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;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Flasher\Toastr\Laravel;
|
||||
|
||||
use Flasher\Prime\EventDispatcher\Event\ResponseEvent;
|
||||
use Flasher\Prime\EventDispatcher\EventListener\EventListenerInterface;
|
||||
|
||||
final readonly class LivewireListener implements EventListenerInterface
|
||||
{
|
||||
public function __invoke(ResponseEvent $event): void
|
||||
{
|
||||
// Only process HTML responses
|
||||
if ('html' !== $event->getPresenter()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$response = $event->getResponse() ?: '';
|
||||
if (!\is_string($response)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Avoid duplicate script injection
|
||||
if (false === strripos($response, '<script type="text/javascript" class="flasher-js"')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (strripos($response, '<script type="text/javascript" class="flasher-toastr-livewire-js"')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Inject the Toastr-Livewire bridge JavaScript
|
||||
$response .= <<<'JAVASCRIPT'
|
||||
<script type="text/javascript" class="flasher-toastr-livewire-js">
|
||||
(function() {
|
||||
const events = ['flasher:toastr:show', 'flasher:toastr:click', 'flasher:toastr:close', 'flasher:toastr:hidden'];
|
||||
|
||||
events.forEach(function(eventName) {
|
||||
window.addEventListener(eventName, function(event) {
|
||||
if (typeof Livewire === 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
const { detail } = event;
|
||||
const { envelope } = detail;
|
||||
const context = envelope.context || {};
|
||||
|
||||
if (!context.livewire?.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { livewire: { id: componentId } } = context;
|
||||
const component = Livewire.all().find(c => c.id === componentId);
|
||||
|
||||
if (!component) {
|
||||
return;
|
||||
}
|
||||
|
||||
const livewireEventName = eventName.replace('flasher:', '').replace(':', ':');
|
||||
Livewire.dispatchTo(component.name, livewireEventName, { payload: detail });
|
||||
}, false);
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
JAVASCRIPT;
|
||||
|
||||
$event->setResponse($response);
|
||||
}
|
||||
|
||||
public function getSubscribedEvents(): string
|
||||
{
|
||||
return ResponseEvent::class;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
+23
-1
@@ -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;
|
||||
|
||||
+23
-1
@@ -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;
|
||||
|
||||
+1
-1
@@ -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}));
|
||||
|
||||
+1
@@ -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;
|
||||
}
|
||||
|
||||
+1
-1
@@ -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}));
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Flasher\Tests\Laravel\EventListener;
|
||||
|
||||
use Flasher\Laravel\EventListener\ThemeLivewireListener;
|
||||
use Flasher\Prime\EventDispatcher\Event\ResponseEvent;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
final class ThemeLivewireListenerTest extends TestCase
|
||||
{
|
||||
private ThemeLivewireListener $listener;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->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('<html><body>No flasher</body></html>', 'html');
|
||||
|
||||
($this->listener)($event);
|
||||
|
||||
$this->assertSame('<html><body>No flasher</body></html>', $event->getResponse());
|
||||
}
|
||||
|
||||
public function testInvokeSkipsDuplicateInjection(): void
|
||||
{
|
||||
$response = '<script type="text/javascript" class="flasher-js"></script>';
|
||||
$response .= '<script type="text/javascript" class="flasher-theme-livewire-js"></script>';
|
||||
|
||||
$event = new ResponseEvent($response, 'html');
|
||||
|
||||
($this->listener)($event);
|
||||
|
||||
$this->assertSame($response, $event->getResponse());
|
||||
}
|
||||
|
||||
public function testInvokeInjectsLivewireScript(): void
|
||||
{
|
||||
$response = '<script type="text/javascript" class="flasher-js"></script>';
|
||||
|
||||
$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());
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Flasher\Tests\Noty\Laravel;
|
||||
|
||||
use Flasher\Noty\Laravel\LivewireListener;
|
||||
use Flasher\Prime\EventDispatcher\Event\ResponseEvent;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
final class LivewireListenerTest extends TestCase
|
||||
{
|
||||
private LivewireListener $listener;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->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('<html><body>No flasher</body></html>', 'html');
|
||||
|
||||
($this->listener)($event);
|
||||
|
||||
$this->assertSame('<html><body>No flasher</body></html>', $event->getResponse());
|
||||
}
|
||||
|
||||
public function testInvokeSkipsDuplicateInjection(): void
|
||||
{
|
||||
$response = '<script type="text/javascript" class="flasher-js"></script>';
|
||||
$response .= '<script type="text/javascript" class="flasher-noty-livewire-js"></script>';
|
||||
|
||||
$event = new ResponseEvent($response, 'html');
|
||||
|
||||
($this->listener)($event);
|
||||
|
||||
$this->assertSame($response, $event->getResponse());
|
||||
}
|
||||
|
||||
public function testInvokeInjectsLivewireScript(): void
|
||||
{
|
||||
$response = '<script type="text/javascript" class="flasher-js"></script>';
|
||||
|
||||
$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());
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Flasher\Tests\Notyf\Laravel;
|
||||
|
||||
use Flasher\Notyf\Laravel\LivewireListener;
|
||||
use Flasher\Prime\EventDispatcher\Event\ResponseEvent;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
final class LivewireListenerTest extends TestCase
|
||||
{
|
||||
private LivewireListener $listener;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->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('<html><body>No flasher</body></html>', 'html');
|
||||
|
||||
($this->listener)($event);
|
||||
|
||||
$this->assertSame('<html><body>No flasher</body></html>', $event->getResponse());
|
||||
}
|
||||
|
||||
public function testInvokeSkipsDuplicateInjection(): void
|
||||
{
|
||||
$response = '<script type="text/javascript" class="flasher-js"></script>';
|
||||
$response .= '<script type="text/javascript" class="flasher-notyf-livewire-js"></script>';
|
||||
|
||||
$event = new ResponseEvent($response, 'html');
|
||||
|
||||
($this->listener)($event);
|
||||
|
||||
$this->assertSame($response, $event->getResponse());
|
||||
}
|
||||
|
||||
public function testInvokeInjectsLivewireScript(): void
|
||||
{
|
||||
$response = '<script type="text/javascript" class="flasher-js"></script>';
|
||||
|
||||
$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());
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Flasher\Tests\Toastr\Laravel;
|
||||
|
||||
use Flasher\Prime\EventDispatcher\Event\ResponseEvent;
|
||||
use Flasher\Toastr\Laravel\LivewireListener;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
final class LivewireListenerTest extends TestCase
|
||||
{
|
||||
private LivewireListener $listener;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->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('<html><body>No flasher</body></html>', 'html');
|
||||
|
||||
($this->listener)($event);
|
||||
|
||||
$this->assertSame('<html><body>No flasher</body></html>', $event->getResponse());
|
||||
}
|
||||
|
||||
public function testInvokeSkipsDuplicateInjection(): void
|
||||
{
|
||||
$response = '<script type="text/javascript" class="flasher-js"></script>';
|
||||
$response .= '<script type="text/javascript" class="flasher-toastr-livewire-js"></script>';
|
||||
|
||||
$event = new ResponseEvent($response, 'html');
|
||||
|
||||
($this->listener)($event);
|
||||
|
||||
$this->assertSame($response, $event->getResponse());
|
||||
}
|
||||
|
||||
public function testInvokeInjectsLivewireScript(): void
|
||||
{
|
||||
$response = '<script type="text/javascript" class="flasher-js"></script>';
|
||||
|
||||
$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());
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user