From 634717732fae56f42c861b054d0bdc74c8e79bfb Mon Sep 17 00:00:00 2001 From: JonLuca DeCaro Date: Tue, 6 Feb 2024 14:12:06 -0800 Subject: [PATCH 1/2] feat: event handlers on toast container --- src/core/containerObserver.ts | 12 ++++++++-- src/core/toast.cy.tsx | 43 +++++++++++++++++++++++++++++++++++ src/types.ts | 14 ++++++------ 3 files changed, 60 insertions(+), 9 deletions(-) diff --git a/src/core/containerObserver.ts b/src/core/containerObserver.ts index f6e69dbe..f5f387dd 100644 --- a/src/core/containerObserver.ts +++ b/src/core/containerObserver.ts @@ -98,8 +98,11 @@ export function createContainerObserver( notify(); dispatchChanges(toToastItem(toast, isNew ? 'added' : 'updated')); - if (isNew && isFn(onOpen)) - onOpen(isValidElement(children) && children.props); + if (isNew) { + if (isFn(containerProps.onOpen)) + containerProps.onOpen(isValidElement(children) && children.props); + if (isFn(onOpen)) onOpen(isValidElement(children) && children.props); + } }; const buildToast = ( @@ -119,6 +122,9 @@ export function createContainerObserver( const toastProps = { ...props, + // dont propagate the handlers called above for the container to the toast + onOpen: undefined, + onClose: undefined, style: props.toastStyle, key: toastKey++, ...Object.fromEntries( @@ -142,6 +148,8 @@ export function createContainerObserver( deleteToast() { const toastToRemove = toasts.get(toastId)!; const { onClose, children } = toastToRemove.props; + if (isFn(containerProps.onClose)) + containerProps.onClose(isValidElement(children) && children.props); if (isFn(onClose)) onClose(isValidElement(children) && children.props); dispatchChanges(toToastItem(toastToRemove, 'removed')); diff --git a/src/core/toast.cy.tsx b/src/core/toast.cy.tsx index b344d2b1..9caad028 100644 --- a/src/core/toast.cy.tsx +++ b/src/core/toast.cy.tsx @@ -34,6 +34,49 @@ describe('without container', () => { }); }); +describe('with container event handlers', () => { + it('calls container open and close', () => { + const onOpen = cy.stub().as('onContainerOpen'); + const onClose = cy.stub().as('onContainerClose'); + cy.mount( + + ); + toast('msg'); + cy.resolveEntranceAnimation(); + cy.findByText('msg').should('exist').click().should('not.exist'); + cy.get('@onContainerOpen').should('have.been.calledOnce'); + cy.get('@onContainerClose').should('have.been.calledOnce'); + }); + + it('calls container open and close and toast open and close', () => { + const onOpen = cy.stub().as('onContainerOpen'); + const onClose = cy.stub().as('onContainerClose'); + cy.mount( + + ); + toast('msg', { + onOpen: cy.stub().as('onToastOpen'), + onClose: cy.stub().as('onToastClose') + }); + cy.resolveEntranceAnimation(); + cy.findByText('msg').should('exist').click().should('not.exist'); + cy.get('@onContainerOpen').should('have.been.calledOnce'); + cy.get('@onContainerClose').should('have.been.calledOnce'); + cy.get('@onToastOpen').should('have.been.calledOnce'); + cy.get('@onToastClose').should('have.been.calledOnce'); + }); +}); + describe('with container', () => { beforeEach(() => { cy.mount(); diff --git a/src/types.ts b/src/types.ts index c7c4fd78..be78bf57 100644 --- a/src/types.ts +++ b/src/types.ts @@ -183,13 +183,6 @@ interface CommonOptions { * `Default: 'light'` */ theme?: Theme; -} - -export interface ToastOptions extends CommonOptions { - /** - * An optional css class to set. - */ - className?: ToastClassName; /** * Called when toast is mounted. @@ -200,6 +193,13 @@ export interface ToastOptions extends CommonOptions { * Called when toast is unmounted. */ onClose?: (props: T) => void; +} + +export interface ToastOptions extends CommonOptions { + /** + * An optional css class to set. + */ + className?: ToastClassName; /** * An optional inline style to apply. From ff186d1f4736c5a84f08a575e52b5b0b7cb9479c Mon Sep 17 00:00:00 2001 From: JonLuca DeCaro Date: Sat, 18 Jul 2026 23:05:14 -0700 Subject: [PATCH 2/2] fix: use current toast container callbacks --- src/core/containerObserver.ts | 10 +++++---- src/core/toast.cy.tsx | 42 +++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 4 deletions(-) diff --git a/src/core/containerObserver.ts b/src/core/containerObserver.ts index f5f387dd..0e43f46a 100644 --- a/src/core/containerObserver.ts +++ b/src/core/containerObserver.ts @@ -99,8 +99,8 @@ export function createContainerObserver( dispatchChanges(toToastItem(toast, isNew ? 'added' : 'updated')); if (isNew) { - if (isFn(containerProps.onOpen)) - containerProps.onOpen(isValidElement(children) && children.props); + if (isFn(props.onOpen)) + props.onOpen(isValidElement(toast.content) && toast.content.props); if (isFn(onOpen)) onOpen(isValidElement(children) && children.props); } }; @@ -148,8 +148,10 @@ export function createContainerObserver( deleteToast() { const toastToRemove = toasts.get(toastId)!; const { onClose, children } = toastToRemove.props; - if (isFn(containerProps.onClose)) - containerProps.onClose(isValidElement(children) && children.props); + if (isFn(props.onClose)) + props.onClose( + isValidElement(toastToRemove.content) && toastToRemove.content.props + ); if (isFn(onClose)) onClose(isValidElement(children) && children.props); dispatchChanges(toToastItem(toastToRemove, 'removed')); diff --git a/src/core/toast.cy.tsx b/src/core/toast.cy.tsx index 9caad028..7c194dd5 100644 --- a/src/core/toast.cy.tsx +++ b/src/core/toast.cy.tsx @@ -75,6 +75,48 @@ describe('with container event handlers', () => { cy.get('@onToastOpen').should('have.been.calledOnce'); cy.get('@onToastClose').should('have.been.calledOnce'); }); + + it('uses current container callbacks and passes component props', () => { + const initialOpen = cy.stub().as('initialOpen'); + const initialClose = cy.stub().as('initialClose'); + const currentOpen = cy.stub().as('currentOpen'); + const currentClose = cy.stub().as('currentClose'); + + function Content({ message }: { message: string }) { + return {message}; + } + + function RerenderingContainer() { + const [updated, setUpdated] = React.useState(false); + + return ( + <> + + + + ); + } + + cy.mount(); + cy.findByRole('button', { name: 'Update callbacks' }).click(); + cy.then(() => toast()); + cy.resolveEntranceAnimation(); + cy.findByText('current toast').click().should('not.exist'); + + cy.get('@initialOpen').should('not.have.been.called'); + cy.get('@initialClose').should('not.have.been.called'); + cy.get('@currentOpen').should('have.been.calledWithMatch', { + message: 'current toast' + }); + cy.get('@currentClose').should('have.been.calledWithMatch', { + message: 'current toast' + }); + }); }); describe('with container', () => {