diff --git a/.changeset/kind-sheep-look.md b/.changeset/kind-sheep-look.md new file mode 100644 index 000000000..d352ea50f --- /dev/null +++ b/.changeset/kind-sheep-look.md @@ -0,0 +1,5 @@ +--- +"@skeletonlabs/skeleton": major +--- + +breaking: Refactored global stores to use contexts for `Toast`, `Drawer`, and `Modal` utilities diff --git a/packages/skeleton/src/lib/index.ts b/packages/skeleton/src/lib/index.ts index 4fa8ef8f4..9934d0671 100644 --- a/packages/skeleton/src/lib/index.ts +++ b/packages/skeleton/src/lib/index.ts @@ -13,15 +13,13 @@ export type { PopupSettings } from './utilities/Popup/types.js'; export type { Transition, TransitionParams } from './internal/transitions.js'; export type { CssClasses, SvelteEvent } from './types.js'; -// Stores --- - +// Utilities --- export { storeHighlightJs } from './utilities/CodeBlock/stores.js'; export { storePopup } from './utilities/Popup/popup.js'; -export { drawerStore } from './utilities/Drawer/stores.js'; -export { modalStore } from './utilities/Modal/stores.js'; -export { toastStore } from './utilities/Toast/stores.js'; - -// Utilities --- +export { getDrawerStore } from './utilities/Drawer/stores.js'; +export { getModalStore } from './utilities/Modal/stores.js'; +export { getToastStore } from './utilities/Toast/stores.js'; +export { initializeStores } from './utilities/index.js'; // Data Table export { diff --git a/packages/skeleton/src/lib/utilities/Drawer/Drawer.svelte b/packages/skeleton/src/lib/utilities/Drawer/Drawer.svelte index 384251be8..f4a3732c3 100644 --- a/packages/skeleton/src/lib/utilities/Drawer/Drawer.svelte +++ b/packages/skeleton/src/lib/utilities/Drawer/Drawer.svelte @@ -17,7 +17,7 @@ // Drawer Utils import type { DrawerSettings } from './types.js'; - import { drawerStore } from './stores.js'; + import { getDrawerStore } from './stores.js'; import { fade, fly } from 'svelte/transition'; import { dynamicTransition } from '../../internal/transitions.js'; @@ -85,6 +85,7 @@ let elemBackdrop: HTMLElement; let elemDrawer: HTMLElement; let anim = { x: 0, y: 0 }; + const drawerStore = getDrawerStore(); // Classes const cBackdrop = 'fixed top-0 left-0 right-0 bottom-0 flex'; diff --git a/packages/skeleton/src/lib/utilities/Drawer/stores.ts b/packages/skeleton/src/lib/utilities/Drawer/stores.ts index 5813e5627..b85a0dfb4 100644 --- a/packages/skeleton/src/lib/utilities/Drawer/stores.ts +++ b/packages/skeleton/src/lib/utilities/Drawer/stores.ts @@ -1,8 +1,44 @@ // Drawer Stores import { writable } from 'svelte/store'; +import { getContext, setContext } from 'svelte'; import type { DrawerSettings } from './types.js'; +const DRAWER_STORE_KEY = 'drawerStore'; + +/** + * Retrieves the `drawerStore`. + * + * @example + * ```ts + * import { getDrawerStore } from "@skeletonlabs/skeleton"; + * + * const drawerStore = getDrawerStore(); + * + * drawerStore.open(); + * ``` + */ +export function getDrawerStore(): DrawerStore { + const drawerStore = getContext(DRAWER_STORE_KEY); + + if (!drawerStore) + throw new Error( + 'drawerStore is not initialized. Please ensure that `initializeStores()` is invoked in the root layout file of this app!' + ); + + return drawerStore; +} + +/** + * Initializes the `drawerStore`. + */ +export function initializeDrawerStore(): DrawerStore { + const drawerStore = drawerService(); + + return setContext(DRAWER_STORE_KEY, drawerStore); +} + +type DrawerStore = ReturnType; function drawerService() { const { subscribe, set, update } = writable({}); return { @@ -22,6 +58,3 @@ function drawerService() { }) }; } - -// Exports -export const drawerStore = drawerService(); diff --git a/packages/skeleton/src/lib/utilities/Modal/Modal.svelte b/packages/skeleton/src/lib/utilities/Modal/Modal.svelte index fdb6648c8..ad9bdb91e 100644 --- a/packages/skeleton/src/lib/utilities/Modal/Modal.svelte +++ b/packages/skeleton/src/lib/utilities/Modal/Modal.svelte @@ -21,8 +21,8 @@ // Types import type { CssClasses, SvelteEvent } from '../../index.js'; - import { modalStore } from './stores.js'; import { focusTrap } from '../../actions/FocusTrap/focusTrap.js'; + import { getModalStore } from './stores.js'; import type { ModalComponent, ModalSettings } from './types.js'; // Props @@ -116,6 +116,8 @@ let currentComponent: ModalComponent | undefined; let registeredInteractionWithBackdrop = false; + const modalStore = getModalStore(); + // Modal Store Subscription modalStore.subscribe((modals: ModalSettings[]) => { if (!modals.length) return; diff --git a/packages/skeleton/src/lib/utilities/Modal/Modal.test.ts b/packages/skeleton/src/lib/utilities/Modal/Modal.test.ts index a06d570b2..70a0659a4 100644 --- a/packages/skeleton/src/lib/utilities/Modal/Modal.test.ts +++ b/packages/skeleton/src/lib/utilities/Modal/Modal.test.ts @@ -1,7 +1,7 @@ import { render } from '@testing-library/svelte'; import { describe, it, expect } from 'vitest'; -import { modalStore } from '$lib/utilities/Modal/stores.js'; +import { getModalStore } from '$lib/utilities/Modal/stores.js'; import type { ModalSettings } from '$lib/utilities/Modal/types.js'; import Modal from '$lib/utilities/Modal/Modal.svelte'; @@ -27,6 +27,8 @@ const modalPrompt: ModalSettings = { }; describe('Modal.svelte', () => { + const modalStore = getModalStore(); + it('Renders modal alert', async () => { modalStore.trigger(modalAlert); const { getByTestId } = render(Modal); diff --git a/packages/skeleton/src/lib/utilities/Modal/stores.ts b/packages/skeleton/src/lib/utilities/Modal/stores.ts index aa3d392fe..6cef05caf 100644 --- a/packages/skeleton/src/lib/utilities/Modal/stores.ts +++ b/packages/skeleton/src/lib/utilities/Modal/stores.ts @@ -1,8 +1,44 @@ // Modal Store Queue import { writable } from 'svelte/store'; +import { getContext, setContext } from 'svelte'; import type { ModalSettings } from './types.js'; +const MODAL_STORE_KEY = 'modalStore'; + +/** + * Retrieves the `modalStore`. + * + * @example + * ```ts + * import { getmodalStore } from "@skeletonlabs/skeleton"; + * + * const modalStore = getModalStore(); + * + * modalStore.trigger({ type: "alert", title: "Welcome!" }); + * ``` + */ +export function getModalStore(): ModalStore { + const modalStore = getContext(MODAL_STORE_KEY); + + if (!modalStore) + throw new Error( + 'modalStore is not initialized. Please ensure that `initializeStores()` is invoked in the root layout file of this app!' + ); + + return modalStore; +} + +/** + * Initializes the `modalStore`. + */ +export function initializeModalStore(): ModalStore { + const modalStore = modalService(); + + return setContext(MODAL_STORE_KEY, modalStore); +} + +type ModalStore = ReturnType; function modalService() { const { subscribe, set, update } = writable([]); return { @@ -25,5 +61,3 @@ function modalService() { clear: () => set([]) }; } - -export const modalStore = modalService(); diff --git a/packages/skeleton/src/lib/utilities/Toast/Toast.svelte b/packages/skeleton/src/lib/utilities/Toast/Toast.svelte index 3007ea9bb..13d7a7824 100644 --- a/packages/skeleton/src/lib/utilities/Toast/Toast.svelte +++ b/packages/skeleton/src/lib/utilities/Toast/Toast.svelte @@ -13,7 +13,8 @@ import { flip } from 'svelte/animate'; // Stores - import { toastStore } from './stores.js'; + import { getToastStore } from './stores.js'; + const toastStore = getToastStore(); // Props /** Set the toast position. diff --git a/packages/skeleton/src/lib/utilities/Toast/Toats.test.ts b/packages/skeleton/src/lib/utilities/Toast/Toats.test.ts index 3ad53d22a..7d69f3107 100644 --- a/packages/skeleton/src/lib/utilities/Toast/Toats.test.ts +++ b/packages/skeleton/src/lib/utilities/Toast/Toats.test.ts @@ -1,7 +1,7 @@ import { render } from '@testing-library/svelte'; import { describe, it, expect } from 'vitest'; -import { toastStore } from '$lib/utilities/Toast/stores.js'; +import { getToastStore } from '$lib/utilities/Toast/stores.js'; import type { ToastSettings } from './types.js'; import Toast from '$lib/utilities/Toast/Toast.svelte'; @@ -17,6 +17,8 @@ const toastMessage: ToastSettings = { }; describe('Toast.svelte', () => { + const toastStore = getToastStore(); + it('Renders modal alert', async () => { toastStore.trigger(toastMessage); const { getByTestId } = render(Toast); diff --git a/packages/skeleton/src/lib/utilities/Toast/stores.ts b/packages/skeleton/src/lib/utilities/Toast/stores.ts index d059097d6..262dc7a74 100644 --- a/packages/skeleton/src/lib/utilities/Toast/stores.ts +++ b/packages/skeleton/src/lib/utilities/Toast/stores.ts @@ -1,29 +1,85 @@ // Toast Store Queue import { writable } from 'svelte/store'; +import { getContext, setContext } from 'svelte'; import type { ToastSettings, Toast } from './types.js'; const toastDefaults: ToastSettings = { message: 'Missing Toast Message', autohide: true, timeout: 5000 }; +const TOAST_STORE_KEY = 'toastStore'; + +/** + * Retrieves the `toastStore`. + * + * @example + * ```ts + * import { getToastStore } from "@skeletonlabs/skeleton"; + * + * const toastStore = getToastStore(); + * + * toastStore.open({ message: "Welcome!" }); + * ``` + */ +export function getToastStore(): ToastStore { + const toastStore = getContext(TOAST_STORE_KEY); + + if (!toastStore) + throw new Error( + 'toastStore is not initialized. Please ensure that `initializeStores()` is invoked in the root layout file of this app!' + ); + + return toastStore; +} + +/** + * Initializes the `toastStore`. + */ +export function initializeToastStore(): ToastStore { + const toastStore = toastService(); + + return setContext(TOAST_STORE_KEY, toastStore); +} + // Note for security; differentiates the queued toasts function randomUUID(): string { const random = Math.random(); return Number(random).toString(32); } -// If toast should auto-hide, wait X time, then close by ID -function handleAutoHide(toast: Toast) { - if (toast.autohide === true) { - return setTimeout(() => { - toastStore.close(toast.id); - }, toast.timeout); - } -} - +type ToastStore = ReturnType; function toastService() { const { subscribe, set, update } = writable([]); + + /** Remove toast in queue*/ + const close = (id: string) => + update((tStore) => { + if (tStore.length > 0) { + const index = tStore.findIndex((t) => t.id === id); + const selectedToast = tStore[index]; + if (selectedToast) { + // Trigger Callback + if (selectedToast.callback) selectedToast.callback({ id, status: 'closed' }); + // Clear timeout + if (selectedToast.timeoutId) clearTimeout(selectedToast.timeoutId); + // Remove + tStore.splice(index, 1); + } + } + return tStore; + }); + + // If toast should auto-hide, wait X time, then close by ID + function handleAutoHide(toast: Toast) { + if (toast.autohide === true) { + return setTimeout(() => { + close(toast.id); + }, toast.timeout); + } + } + return { subscribe, + close, /** Add a new toast to the queue. */ trigger: (toast: ToastSettings) => { const id: string = randomUUID(); @@ -43,30 +99,13 @@ function toastService() { }); return id; }, - /** Remove toast in queue*/ - close: (id: string) => - update((tStore) => { - if (tStore.length > 0) { - const index = tStore.findIndex((t) => t.id === id); - const selectedToast = tStore[index]; - if (selectedToast) { - // Trigger Callback - if (selectedToast.callback) selectedToast.callback({ id, status: 'closed' }); - // Clear timeout - if (selectedToast.timeoutId) clearTimeout(selectedToast.timeoutId); - // Remove - tStore.splice(index, 1); - } - } - return tStore; - }), - /** remain visible on hover */ + /** Remain visible on hover */ freeze: (index: number) => update((tStore) => { if (tStore.length > 0) clearTimeout(tStore[index].timeoutId); return tStore; }), - /** cancel remain visible on leave */ + /** Cancel remain visible on leave */ unfreeze: (index: number) => update((tStore) => { if (tStore.length > 0) tStore[index].timeoutId = handleAutoHide(tStore[index]); @@ -76,5 +115,3 @@ function toastService() { clear: () => set([]) }; } - -export const toastStore = toastService(); diff --git a/packages/skeleton/src/lib/utilities/index.ts b/packages/skeleton/src/lib/utilities/index.ts new file mode 100644 index 000000000..588ef050b --- /dev/null +++ b/packages/skeleton/src/lib/utilities/index.ts @@ -0,0 +1,26 @@ +import { initializeModalStore } from './Modal/stores.js'; +import { initializeToastStore } from './Toast/stores.js'; +import { initializeDrawerStore } from './Drawer/stores.js'; + +/** + * Used to initialize the stores for the `Modal`, `Toast`, and `Drawer` utilities. + * + * @example + * ```svelte + * + * + * + * + * + * + * ``` + */ +export function initializeStores() { + initializeModalStore(); + initializeToastStore(); + initializeDrawerStore(); +} diff --git a/sites/skeleton.dev/src/lib/components/DocsAppBar/DocsAppBar.svelte b/sites/skeleton.dev/src/lib/components/DocsAppBar/DocsAppBar.svelte index a2589f860..e55ec1eb8 100644 --- a/sites/skeleton.dev/src/lib/components/DocsAppBar/DocsAppBar.svelte +++ b/sites/skeleton.dev/src/lib/components/DocsAppBar/DocsAppBar.svelte @@ -11,14 +11,16 @@ import DocsIcon from '$lib/components/DocsIcon/DocsIcon.svelte'; // Components & Utilities - import { AppBar, LightSwitch, popup, modalStore } from '@skeletonlabs/skeleton'; + import { AppBar, LightSwitch, popup, getModalStore } from '@skeletonlabs/skeleton'; // Stores - import { drawerStore } from '@skeletonlabs/skeleton'; + import { getDrawerStore } from '@skeletonlabs/skeleton'; import { storeTheme } from '$lib/stores/stores'; + const drawerStore = getDrawerStore(); // Local let isOsMac = false; + const modalStore = getModalStore(); // Set Search Keyboard Shortcut if (browser) { diff --git a/sites/skeleton.dev/src/lib/components/DocsDrawer/DocsDrawer.svelte b/sites/skeleton.dev/src/lib/components/DocsDrawer/DocsDrawer.svelte index 3b54df19d..a8768e8ca 100644 --- a/sites/skeleton.dev/src/lib/components/DocsDrawer/DocsDrawer.svelte +++ b/sites/skeleton.dev/src/lib/components/DocsDrawer/DocsDrawer.svelte @@ -1,7 +1,9 @@ diff --git a/sites/skeleton.dev/src/lib/components/DocsSidebar/DocsSidebar.svelte b/sites/skeleton.dev/src/lib/components/DocsSidebar/DocsSidebar.svelte index 5aefb21e9..80f6fe34c 100644 --- a/sites/skeleton.dev/src/lib/components/DocsSidebar/DocsSidebar.svelte +++ b/sites/skeleton.dev/src/lib/components/DocsSidebar/DocsSidebar.svelte @@ -3,11 +3,12 @@ import DocsIcon from '$lib/components/DocsIcon/DocsIcon.svelte'; import { AppRail, AppRailTile, AppRailAnchor } from '@skeletonlabs/skeleton'; - import { drawerStore } from '@skeletonlabs/skeleton'; + import { getDrawerStore } from '@skeletonlabs/skeleton'; import { menuNavLinks } from '$lib/links'; // Local let currentRailCategory: keyof typeof menuNavLinks | undefined = undefined; + const drawerStore = getDrawerStore(); function onClickAnchor(): void { currentRailCategory = undefined; diff --git a/sites/skeleton.dev/src/lib/modals/DocsSearch/DocsSearch.svelte b/sites/skeleton.dev/src/lib/modals/DocsSearch/DocsSearch.svelte index aa644417a..17e433264 100644 --- a/sites/skeleton.dev/src/lib/modals/DocsSearch/DocsSearch.svelte +++ b/sites/skeleton.dev/src/lib/modals/DocsSearch/DocsSearch.svelte @@ -1,6 +1,6 @@ diff --git a/sites/skeleton.dev/src/lib/modals/examples/ModalExampleList.svelte b/sites/skeleton.dev/src/lib/modals/examples/ModalExampleList.svelte index e85eb7f4c..65d4f75e6 100644 --- a/sites/skeleton.dev/src/lib/modals/examples/ModalExampleList.svelte +++ b/sites/skeleton.dev/src/lib/modals/examples/ModalExampleList.svelte @@ -1,5 +1,5 @@