react-modal-registry 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +119 -95
- package/dist/index.cjs +1 -320
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +50 -92
- package/dist/index.d.ts +50 -92
- package/dist/index.js +1 -301
- package/dist/index.js.map +1 -1
- package/package.json +29 -16
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.tsx"],"sourcesContent":["'use client'\n\nimport {\n createContext,\n memo,\n Suspense,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useState,\n type ComponentType,\n type ReactNode\n} from 'react'\n\n/**\n * Registry for modal payload types via module augmentation.\n *\n * @example\n * ```ts\n * declare module 'react-modal-registry' {\n * interface ModalDataMap {\n * confirmAction: { itemId: string }\n * }\n * }\n * ```\n */\nexport interface ModalDataMap {}\n\n/**\n * Registry for modal return types via module augmentation.\n *\n * @example\n * ```ts\n * declare module 'react-modal-registry' {\n * interface ModalResultMap {\n * confirmAction: boolean\n * }\n * }\n * ```\n */\nexport interface ModalResultMap {}\n\n/**\n * Registry for paired modal payload and return types via module augmentation.\n *\n * @example\n * ```ts\n * declare module 'react-modal-registry' {\n * interface ModalRegistry {\n * confirmAction: { data: { itemId: string }; result: boolean }\n * }\n * }\n * ```\n */\nexport interface ModalRegistry {}\n\nexport type RegisteredModalId = keyof ModalDataMap | keyof ModalResultMap | keyof ModalRegistry\n\n/**\n * Modal identifier supporting autocompletion of registered IDs with string fallback.\n */\nexport type ModalId = RegisteredModalId | (string & {})\n\nexport type ModalData<TId extends ModalId> = TId extends keyof ModalDataMap\n ? ModalDataMap[TId]\n : TId extends keyof ModalRegistry\n ? ModalRegistry[TId] extends { data: infer TData }\n ? TData\n : unknown\n : unknown\n\nexport type ModalResult<TId extends ModalId> = TId extends keyof ModalResultMap\n ? ModalResultMap[TId]\n : TId extends keyof ModalRegistry\n ? ModalRegistry[TId] extends { result: infer TResult }\n ? TResult\n : unknown\n : unknown\n\n/**\n * optional vs required payload arguments based on `TData`.\n */\nexport type ModalDataArg<TData> = [unknown] extends [TData]\n ? [data?: TData]\n : [TData] extends [void] | [undefined]\n ? [data?: TData]\n : undefined extends TData\n ? [data?: TData]\n : [data: TData]\n\n/**\n * Standard props passed to registered modal components.\n */\nexport interface ModalProps<TData = unknown, TResult = unknown> {\n isOpen: boolean\n data: TData\n close: (result?: TResult) => void\n}\n\nexport type ModalComponent<TData = unknown, TResult = unknown> =\n | ComponentType<ModalProps<TData, TResult>>\n | ComponentType<ModalProps<TData, unknown>>\n\nexport interface ModalRegistration<TId extends ModalId = ModalId> {\n id: TId\n component: ModalComponent<ModalData<TId>, ModalResult<TId>>\n}\n\n/**\n * Imperative modal controller returned by `useModal(id)`.\n */\nexport interface UseModalReturn<TData = unknown, TResult = unknown> {\n /**\n * Opens the modal with the specified payload and resolves when closed.\n */\n open(...args: ModalDataArg<TData>): Promise<TResult>\n\n /**\n * Closes the modal imperatively, optionally passing a resolution value.\n */\n close(result?: TResult): void\n\n /**\n * Imperatively reads whether the modal is currently open.\n */\n isOpen(): boolean\n}\n\ninterface ModalSlot {\n id: ModalId\n component: ComponentType<ModalProps<unknown, unknown>>\n isOpen: boolean\n isMounted: boolean\n data: unknown\n}\n\ninterface ModalPending {\n promise: Promise<unknown>\n resolve: (value: unknown) => void\n}\n\nexport interface ModalActionsValue {\n register<TId extends ModalId>(\n id: TId,\n component: ModalComponent<ModalData<TId>, ModalResult<TId>>\n ): void\n register<const TIds extends readonly ModalId[]>(modals: {\n [K in keyof TIds]: ModalRegistration<TIds[K]>\n }): void\n unregister(idOrIds: ModalId | readonly ModalId[]): void\n open<TId extends ModalId, TResult = ModalResult<TId>>(\n id: TId,\n ...args: ModalDataArg<ModalData<TId>>\n ): Promise<TResult>\n close<TId extends ModalId>(id: TId, result?: ModalResult<TId>): void\n closeAll(): void\n isOpen(id: ModalId): boolean\n}\n\nconst ModalActionsContext = createContext<ModalActionsValue | null>(null)\nconst ModalSlotsContext = createContext<ReadonlyMap<ModalId, ModalSlot> | null>(null)\n\n// Allows exit animation transitions to complete before unmounting slot\nconst UNMOUNT_DELAY_MS = 200\n\n/**\n * Root provider hosting the modal registry, lifecycle actions, and slot renderer.\n */\nexport function ModalProvider({ children }: { children: ReactNode }) {\n const slotsRef = useRef<Map<ModalId, ModalSlot>>(new Map())\n const pendingRef = useRef<Map<ModalId, ModalPending>>(new Map())\n const timersRef = useRef<Map<ModalId, ReturnType<typeof setTimeout>>>(new Map())\n const [slots, setSlots] = useState<ReadonlyMap<ModalId, ModalSlot>>(new Map())\n\n useEffect(() => {\n const timers = timersRef.current\n const pendings = pendingRef.current\n return () => {\n for (const timer of timers.values()) {\n clearTimeout(timer)\n }\n timers.clear()\n for (const pending of pendings.values()) {\n pending.resolve(undefined)\n }\n pendings.clear()\n }\n }, [])\n\n const sync = useCallback(() => {\n setSlots(new Map(slotsRef.current))\n }, [])\n\n const register: ModalActionsValue['register'] = useCallback(\n (\n idOrModals: ModalId | readonly ModalRegistration[],\n component?: ModalComponent<unknown, unknown>\n ) => {\n const items: readonly ModalRegistration[] = Array.isArray(idOrModals)\n ? idOrModals\n : component\n ? [\n {\n id: idOrModals,\n component: component as ModalComponent<unknown, unknown>\n }\n ]\n : []\n\n let changed = false\n for (const { id, component: comp } of items) {\n const existing = slotsRef.current.get(id)\n const typedComp = comp as ComponentType<ModalProps<unknown, unknown>>\n\n if (existing) {\n if (existing.component !== typedComp) {\n slotsRef.current.set(id, {\n ...existing,\n component: typedComp\n })\n changed = true\n }\n } else {\n slotsRef.current.set(id, {\n id,\n component: typedComp,\n isOpen: false,\n isMounted: false,\n data: undefined\n })\n changed = true\n }\n }\n\n if (changed) {\n sync()\n }\n },\n [sync]\n )\n\n const unregister = useCallback(\n (idOrIds: ModalId | readonly ModalId[]) => {\n const ids = Array.isArray(idOrIds) ? idOrIds : [idOrIds]\n let changed = false\n for (const id of ids) {\n const timer = timersRef.current.get(id)\n if (timer) {\n clearTimeout(timer)\n timersRef.current.delete(id)\n }\n\n const pending = pendingRef.current.get(id)\n if (pending) {\n pendingRef.current.delete(id)\n pending.resolve(undefined)\n }\n if (slotsRef.current.has(id)) {\n slotsRef.current.delete(id)\n changed = true\n }\n }\n if (changed) {\n sync()\n }\n },\n [sync]\n )\n\n const open = useCallback(\n <TId extends ModalId, TResult = ModalResult<TId>>(\n id: TId,\n ...args: ModalDataArg<ModalData<TId>>\n ): Promise<TResult> => {\n const data = args[0] as ModalData<TId>\n const slot = slotsRef.current.get(id)\n\n if (!slot) {\n return Promise.reject(\n new Error(`[react-modal-registry] Modal \"${String(id)}\" is not registered.`)\n )\n }\n\n // Re-use active pending promise if open call is repeated with identical payload\n const existing = pendingRef.current.get(id)\n if (slot.isOpen && slot.data === data && existing) {\n return existing.promise as Promise<TResult>\n }\n\n // Supersede any pending promise before creating a new one\n if (existing) {\n pendingRef.current.delete(id)\n existing.resolve(undefined)\n }\n\n const timer = timersRef.current.get(id)\n if (timer) {\n clearTimeout(timer)\n timersRef.current.delete(id)\n }\n\n let resolver!: (value: unknown) => void\n const promise = new Promise<TResult>(resolve => {\n resolver = resolve as (value: unknown) => void\n })\n\n pendingRef.current.set(id, {\n promise: promise as Promise<unknown>,\n resolve: resolver\n })\n\n slotsRef.current.set(id, { ...slot, isOpen: true, isMounted: true, data })\n sync()\n\n return promise\n },\n [sync]\n )\n\n const scheduleUnmount = useCallback(\n (id: ModalId) => {\n const timer = timersRef.current.get(id)\n if (timer) clearTimeout(timer)\n\n timersRef.current.set(\n id,\n setTimeout(() => {\n timersRef.current.delete(id)\n const current = slotsRef.current.get(id)\n if (current && !current.isOpen) {\n slotsRef.current.set(id, { ...current, isMounted: false })\n sync()\n }\n }, UNMOUNT_DELAY_MS)\n )\n },\n [sync]\n )\n\n const close = useCallback(\n <TId extends ModalId>(id: TId, result?: ModalResult<TId>) => {\n const slot = slotsRef.current.get(id)\n if (!slot || !slot.isOpen) return\n\n const pending = pendingRef.current.get(id)\n if (pending) {\n pendingRef.current.delete(id)\n pending.resolve(result)\n }\n\n slotsRef.current.set(id, { ...slot, isOpen: false, isMounted: true })\n sync()\n scheduleUnmount(id)\n },\n [sync, scheduleUnmount]\n )\n\n const closeAll = useCallback(() => {\n for (const [, pending] of pendingRef.current) {\n pending.resolve(undefined)\n }\n pendingRef.current.clear()\n\n let changed = false\n for (const [id, slot] of slotsRef.current) {\n if (slot.isOpen) {\n slotsRef.current.set(id, { ...slot, isOpen: false, isMounted: true })\n scheduleUnmount(id)\n changed = true\n }\n }\n\n if (changed) {\n sync()\n }\n }, [sync, scheduleUnmount])\n\n const isOpen = useCallback((id: ModalId) => slotsRef.current.get(id)?.isOpen ?? false, [])\n\n const actions = useMemo<ModalActionsValue>(\n () => ({\n register,\n unregister,\n open,\n close,\n closeAll,\n isOpen\n }),\n [register, unregister, open, close, closeAll, isOpen]\n )\n\n return (\n <ModalActionsContext.Provider value={actions}>\n <ModalSlotsContext.Provider value={slots}>\n {children}\n <ModalRenderer />\n </ModalSlotsContext.Provider>\n </ModalActionsContext.Provider>\n )\n}\n\n/**\n * Registers one or more modals for the lifetime of the calling component.\n *\n */\nexport function useRegisterModal<TId extends ModalId>(\n id: TId,\n component: ModalComponent<ModalData<TId>, ModalResult<TId>>\n): void\nexport function useRegisterModal<const TIds extends readonly ModalId[]>(modals: {\n [K in keyof TIds]: ModalRegistration<TIds[K]>\n}): void\nexport function useRegisterModal<TId extends ModalId>(\n idOrModals:\n | TId\n | readonly {\n id: ModalId\n component: ComponentType<ModalProps<unknown, unknown>>\n }[],\n component?: ModalComponent<ModalData<TId>, ModalResult<TId>>\n): void {\n const { register, unregister } = useModalContext()\n\n useEffect(() => {\n if (Array.isArray(idOrModals)) {\n register(idOrModals as Parameters<typeof register>[0])\n return () => {\n unregister(idOrModals.map(m => m.id))\n }\n }\n\n if (component) {\n register(idOrModals as TId, component)\n return () => {\n unregister(idOrModals as TId)\n }\n }\n\n return undefined\n }, [idOrModals, component, register, unregister])\n}\n\n/**\n * Declarative component for mounting static modal batches at application or layout roots.\n */\nexport function ModalRegistry<const TIds extends readonly ModalId[]>({\n modals\n}: {\n modals: {\n [K in keyof TIds]: ModalRegistration<TIds[K]>\n }\n}) {\n useRegisterModal(modals)\n return null\n}\n\n/**\n * Returns typed controls (`open`, `close`, `isOpen`) for a modal ID.\n *\n * `isOpen()` reads state imperatively without subscribing the caller to render updates.\n * Use `useIsModalOpen(id)` for reactive subscriptions.\n *\n * @example\n * ```tsx\n * function DeleteButton({ id }: { id: string }) {\n * const confirmModal = useModal('confirmation')\n *\n * const handleDelete = async () => {\n * const confirmed = await confirmModal.open({ itemId: id })\n * if (confirmed) {\n * deleteItem(id)\n * }\n * }\n *\n * return <button onClick={handleDelete}>Delete</button>\n * }\n * ```\n */\nexport function useModal<TId extends ModalId, TResult = ModalResult<TId>>(\n id: TId\n): UseModalReturn<ModalData<TId>, TResult> {\n const { open, close, isOpen } = useModalContext()\n\n return useMemo<UseModalReturn<ModalData<TId>, TResult>>(\n () => ({\n open: (...args: ModalDataArg<ModalData<TId>>) => open<TId, TResult>(id, ...args),\n close: (result?: TResult) => close(id, result as ModalResult<TId>),\n isOpen: () => isOpen(id)\n }),\n [open, close, isOpen, id]\n )\n}\n\n/**\n * Reactively subscribes to whether a modal is currently open.\n */\nexport function useIsModalOpen(id: ModalId): boolean {\n const slots = useContext(ModalSlotsContext)\n\n if (!slots) {\n throw new Error('useIsModalOpen must be used within <ModalProvider>')\n }\n\n return slots.get(id)?.isOpen ?? false\n}\n\nexport function useCloseAll(): () => void {\n return useModalContext().closeAll\n}\n\nconst ModalSlotItem = memo(function ModalSlotItem({\n id,\n component: Component,\n isOpen,\n data,\n close\n}: {\n id: ModalId\n component: ComponentType<ModalProps<unknown, unknown>>\n isOpen: boolean\n data: unknown\n close: (id: ModalId, result?: unknown) => void\n}) {\n const handleClose = useCallback(\n (result?: unknown) => {\n close(id, result)\n },\n [close, id]\n )\n\n return (\n <Suspense fallback={null}>\n <Component isOpen={isOpen} data={data} close={handleClose} />\n </Suspense>\n )\n})\n\nfunction ModalRenderer() {\n const slots = useContext(ModalSlotsContext)\n const { close } = useModalContext()\n\n if (!slots) {\n return null\n }\n\n return (\n <>\n {Array.from(slots.values()).map(slot =>\n slot.isMounted ? (\n <ModalSlotItem\n key={slot.id}\n id={slot.id}\n component={slot.component}\n isOpen={slot.isOpen}\n data={slot.data}\n close={close}\n />\n ) : null\n )}\n </>\n )\n}\n\nfunction useModalContext(): ModalActionsValue {\n const context = useContext(ModalActionsContext)\n\n if (!context) {\n throw new Error('useModalContext must be used within <ModalProvider>')\n }\n\n return context\n}\n"],"mappings":"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","names":["ModalSlotItem"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.tsx"],"sourcesContent":["'use client'\n\nimport {\n createContext,\n memo,\n Suspense,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useState,\n type ComponentType,\n type ReactNode\n} from 'react'\n\n/**\n * Canonical registry for modal payloads and return types via module augmentation (e.g., `env.d.ts` or `modals.d.ts`).\n *\n * @example\n * ```ts\n * declare module 'react-modal-registry' {\n * interface ModalRegistry {\n * confirmAction: { data: { itemId: string }; result: boolean }\n * simpleAlert: void\n * }\n * }\n * ```\n */\nexport interface ModalRegistry { }\n\nexport type RegisteredModalId = keyof ModalRegistry\nexport type ModalId = [RegisteredModalId] extends [never] ? string : RegisteredModalId\n\nexport type ModalData<TId extends ModalId> = TId extends keyof ModalRegistry\n ? 'data' extends keyof ModalRegistry[TId]\n ? ModalRegistry[TId]['data']\n : void\n : unknown\n\nexport type ModalResult<TId extends ModalId> = TId extends keyof ModalRegistry\n ? 'result' extends keyof ModalRegistry[TId]\n ? ModalRegistry[TId]['result']\n : void\n : unknown\n\n/**\n * Derives optional vs. required payload arguments based on `TData`.\n */\nexport type ModalDataArg<TData> = [TData] extends [never]\n ? []\n : [unknown] extends [TData]\n ? [data?: TData]\n : [TData] extends [void] | [undefined]\n ? [data?: TData]\n : undefined extends TData\n ? [data?: TData]\n : [data: TData]\n\nexport type ModalClose<TResult = void> = [TResult] extends [void]\n ? () => void\n : (result?: TResult) => void\n\n/**\n * Standard props passed to registered modal components.\n */\nexport interface ModalProps<TData = unknown, TResult = void> {\n isOpen: boolean\n data: TData\n close: ModalClose<TResult>\n}\n\nexport type ModalComponent<TData = unknown, TResult = void> = ComponentType<\n ModalProps<TData, TResult>\n>\n\nexport type UntypedModalComponent = ComponentType<ModalProps<never, unknown>>\n\nexport type RegisteredComponent<TId extends ModalId> =\n [RegisteredModalId] extends [never]\n ? ComponentType<ModalProps<never, unknown>>\n : ModalComponent<ModalData<TId>, ModalResult<TId>>\n\nexport interface ModalRegistration<TId extends ModalId = ModalId> {\n id: TId\n component: RegisteredComponent<TId>\n}\n\n/**\n * Imperative modal controller returned by `useModal(id)`.\n */\nexport interface UseModalReturn<TData = unknown, TResult = unknown> {\n open(...args: ModalDataArg<TData>): Promise<TResult | undefined>\n close(result?: TResult): void\n isOpen(): boolean\n}\n\nexport type CanonicalResult<TId extends ModalId, TResult = ModalResult<TId>> =\n [RegisteredModalId] extends [never] ? TResult : ModalResult<TId>\n\nexport interface ModalActionsValue {\n register<TId extends ModalId>(\n id: TId,\n component: RegisteredComponent<TId>\n ): void\n register(modals: readonly ModalRegistration[]): void\n unregister(idOrIds: ModalId | readonly ModalId[], targetComponent?: UntypedModalComponent): void\n open<TId extends ModalId, TResult = ModalResult<TId>>(\n id: TId,\n ...args: ModalDataArg<ModalData<TId>>\n ): Promise<CanonicalResult<TId, TResult> | undefined>\n close<TId extends ModalId>(id: TId, result?: ModalResult<TId>): void\n closeAll(): void\n isOpen(id: ModalId): boolean\n}\n\nexport type ModalDefaultData =\n | Record<string, unknown>\n | ((id: ModalId) => unknown)\n\nexport type ModalRoutingStrategy = 'hash' | 'query'\nexport type ModalHistoryMode = 'push' | 'replace'\n\nexport interface ModalRoutingConfig {\n enabled?: boolean\n strategy?: ModalRoutingStrategy\n param?: string\n historyMode?: ModalHistoryMode\n defaultData?: ModalDefaultData\n}\n\nexport interface ModalProviderProps {\n children: ReactNode\n unmountDelay?: number\n fallback?: ReactNode\n routing?: ModalRoutingConfig\n}\n\ninterface ActiveModal {\n id: ModalId\n component: UntypedModalComponent\n isOpen: boolean\n data: unknown\n invocationId: number\n}\n\nconst DEFAULT_ROUTING = {\n strategy: 'hash',\n param: 'modal',\n historyMode: 'push'\n} as const\n\nexport const isBrowser = (): boolean => typeof window !== 'undefined'\n\nconst DEFAULT_UNMOUNT_DELAY = 200\n\nconst ModalActionsContext = createContext<ModalActionsValue | null>(null)\nconst ModalActiveContext = createContext<ReadonlyMap<ModalId, ActiveModal>>(new Map())\n\nfunction isShallowEqual(a: unknown, b: unknown): boolean {\n if (Object.is(a, b)) return true\n if (typeof a !== 'object' || a === null || typeof b !== 'object' || b === null) return false\n if (a instanceof Date && b instanceof Date) return a.getTime() === b.getTime()\n if (a instanceof RegExp && b instanceof RegExp) return a.source === b.source && a.flags === b.flags\n if (a instanceof Map || a instanceof Set || b instanceof Map || b instanceof Set) return false\n const keysA = Object.keys(a)\n const keysB = Object.keys(b)\n if (keysA.length !== keysB.length) return false\n return keysA.every(\n k =>\n Object.prototype.hasOwnProperty.call(b, k) &&\n (a as Record<string, unknown>)[k] === (b as Record<string, unknown>)[k]\n )\n}\n\nfunction useStableRegistrations<T extends ModalId | readonly ModalRegistration[]>(\n idOrModals: T\n): T {\n const ref = useRef(idOrModals)\n if (\n Array.isArray(idOrModals) &&\n Array.isArray(ref.current) &&\n ref.current.length === idOrModals.length &&\n ref.current.every((item, i) => item.id === idOrModals[i].id && item.component === idOrModals[i].component)\n ) {\n return ref.current as T\n }\n ref.current = idOrModals\n return idOrModals\n}\n\nfunction getHashParts(rawHash: string): { anchor: string; params: URLSearchParams } {\n const hash = rawHash.replace(/^#/, '')\n const [anchor, query] = hash.includes('?')\n ? hash.split('?', 2)\n : hash.includes('=')\n ? ['', hash]\n : [hash, '']\n return { anchor, params: new URLSearchParams(query) }\n}\n\nfunction formatHash(anchor: string, params: URLSearchParams): string {\n const qs = params.toString()\n return qs ? `#${anchor ? `${anchor}?` : ''}${qs}` : (anchor ? `#${anchor}` : '')\n}\n\nfunction getUrlModalId(config?: ModalRoutingConfig | null): string | null {\n if (!isBrowser() || !config || config.enabled === false) return null\n const param = config.param ?? DEFAULT_ROUTING.param\n return (config.strategy ?? DEFAULT_ROUTING.strategy) === 'query'\n ? new URLSearchParams(window.location.search).get(param)\n : getHashParts(window.location.hash).params.get(param)\n}\n\nfunction setUrlModalId(\n id: string | null,\n config?: ModalRoutingConfig | null,\n forceMode?: ModalHistoryMode\n) {\n if (!isBrowser() || !config || config.enabled === false) return\n if (getUrlModalId(config) === id) return\n\n const mode = forceMode ?? config.historyMode ?? DEFAULT_ROUTING.historyMode\n const param = config.param ?? DEFAULT_ROUTING.param\n const url = new URL(window.location.href)\n const updateParams = (sp: URLSearchParams) => (id ? sp.set(param, id) : sp.delete(param))\n\n if ((config.strategy ?? DEFAULT_ROUTING.strategy) === 'query') {\n updateParams(url.searchParams)\n } else {\n const { anchor, params } = getHashParts(window.location.hash)\n updateParams(params)\n url.hash = formatHash(anchor, params)\n }\n\n window.history[mode === 'push' ? 'pushState' : 'replaceState'](\n window.history.state,\n '',\n url.pathname + url.search + url.hash\n )\n}\n\nfunction getDefaultData(defaultData: ModalDefaultData | undefined, id: ModalId): unknown {\n if (!defaultData) return undefined\n return typeof defaultData === 'function'\n ? defaultData(id)\n : Object.prototype.hasOwnProperty.call(defaultData, id)\n ? (defaultData as Record<string, unknown>)[id]\n : undefined\n}\n\n/**\n * Root provider hosting the modal registry, URL routing, lifecycle actions, and slot renderer.\n */\nexport function ModalProvider({\n children,\n unmountDelay = DEFAULT_UNMOUNT_DELAY,\n fallback = null,\n routing\n}: ModalProviderProps) {\n // Registry stack allows multiple components to register the same modal ID safely\n const registryRef = useRef<Map<ModalId, UntypedModalComponent[]>>(new Map())\n\n // Active modals currently open or animating out\n const [activeModals, setActiveModals] = useState<ReadonlyMap<ModalId, ActiveModal>>(new Map())\n const activeModalsRef = useRef(activeModals)\n\n const updateActiveModals = useCallback(\n (updater: (prev: ReadonlyMap<ModalId, ActiveModal>) => ReadonlyMap<ModalId, ActiveModal>) => {\n const next = updater(activeModalsRef.current)\n activeModalsRef.current = next\n setActiveModals(next)\n return next\n },\n []\n )\n\n const pendingRef = useRef<\n Map<ModalId, { promise: Promise<unknown>; resolve: (value: unknown) => void; invocationId: number }>\n >(new Map())\n const timersRef = useRef<Map<ModalId, ReturnType<typeof setTimeout>>>(new Map())\n const invocationCounterRef = useRef(0)\n\n const resolvedRouting = useMemo(\n () =>\n routing\n ? {\n ...DEFAULT_ROUTING,\n ...routing,\n enabled: routing.enabled !== false\n }\n : null,\n [\n routing?.enabled,\n routing?.strategy,\n routing?.param,\n routing?.historyMode,\n routing?.defaultData\n ]\n )\n const routingRef = useRef(resolvedRouting)\n routingRef.current = resolvedRouting\n\n useEffect(() => {\n return () => {\n timersRef.current.forEach(clearTimeout)\n timersRef.current.clear()\n pendingRef.current.forEach(p => p.resolve(undefined))\n pendingRef.current.clear()\n }\n }, [])\n\n const getComponent = useCallback((id: ModalId) => {\n const stack = registryRef.current.get(id)\n return stack?.[stack.length - 1]\n }, [])\n\n const scheduleUnmount = useCallback(\n (id: ModalId, invocationId: number) => {\n const existing = timersRef.current.get(id)\n if (existing) clearTimeout(existing)\n\n let timer: ReturnType<typeof setTimeout>\n timer = setTimeout(() => {\n if (timersRef.current.get(id) === timer) {\n timersRef.current.delete(id)\n }\n updateActiveModals(prev => {\n const item = prev.get(id)\n if (!item || item.isOpen || item.invocationId !== invocationId) return prev\n const next = new Map(prev)\n next.delete(id)\n return next\n })\n }, unmountDelay)\n timersRef.current.set(id, timer)\n },\n [unmountDelay, updateActiveModals]\n )\n\n const closeModal = useCallback(\n (id: ModalId, invocationId?: number, result?: unknown, syncUrl = true) => {\n const active = activeModalsRef.current.get(id)\n if (!active || !active.isOpen) return\n if (invocationId !== undefined && active.invocationId !== invocationId) return\n\n const pending = pendingRef.current.get(id)\n if (pending && (invocationId === undefined || pending.invocationId === invocationId)) {\n pendingRef.current.delete(id)\n pending.resolve(result)\n }\n\n if (syncUrl && routingRef.current?.enabled) {\n if (getUrlModalId(routingRef.current) === String(id)) {\n setUrlModalId(null, routingRef.current, 'replace')\n }\n }\n\n let didClose = false\n updateActiveModals(prev => {\n const current = prev.get(id)\n if (!current || !current.isOpen) return prev\n if (invocationId !== undefined && current.invocationId !== invocationId) return prev\n didClose = true\n const next = new Map(prev)\n if (unmountDelay <= 0) {\n next.delete(id)\n } else {\n next.set(id, { ...current, isOpen: false })\n }\n return next\n })\n\n if (didClose && unmountDelay > 0) {\n scheduleUnmount(id, active.invocationId)\n }\n },\n [scheduleUnmount, unmountDelay, updateActiveModals]\n )\n\n const openModal = useCallback(\n (id: ModalId, data?: unknown, syncUrl = true): Promise<unknown> => {\n const comp = getComponent(id)\n if (!comp) {\n return Promise.reject(\n new Error(`[react-modal-registry] Modal \"${String(id)}\" is not registered.`)\n )\n }\n\n const current = activeModalsRef.current.get(id)\n const existing = pendingRef.current.get(id)\n\n if (\n current?.isOpen &&\n current.component === comp &&\n isShallowEqual(current.data, data) &&\n existing\n ) {\n return existing.promise\n }\n\n if (syncUrl && routingRef.current?.enabled) {\n setUrlModalId(String(id), routingRef.current)\n }\n\n if (existing) {\n pendingRef.current.delete(id)\n existing.resolve(undefined)\n }\n\n const timer = timersRef.current.get(id)\n if (timer) {\n clearTimeout(timer)\n timersRef.current.delete(id)\n }\n\n const invocationId = ++invocationCounterRef.current\n let resolver!: (val: unknown) => void\n const promise = new Promise<unknown>(resolve => {\n resolver = resolve\n })\n\n pendingRef.current.set(id, { promise, resolve: resolver, invocationId })\n\n updateActiveModals(prev => {\n const next = new Map(prev)\n next.set(id, { id, component: comp, isOpen: true, data, invocationId })\n return next\n })\n\n return promise\n },\n [getComponent, updateActiveModals]\n )\n\n const open = useCallback(\n <TId extends ModalId, TResult = ModalResult<TId>>(\n id: TId,\n ...args: ModalDataArg<ModalData<TId>>\n ): Promise<CanonicalResult<TId, TResult> | undefined> => {\n return openModal(id, args[0], true) as Promise<CanonicalResult<TId, TResult> | undefined>\n },\n [openModal]\n )\n\n const close = useCallback(\n <TId extends ModalId>(id: TId, result?: ModalResult<TId>) => {\n closeModal(id, undefined, result, true)\n },\n [closeModal]\n )\n\n const closeAll = useCallback(() => {\n const pendings = Array.from(pendingRef.current.values())\n pendingRef.current.clear()\n\n if (routingRef.current?.enabled) {\n setUrlModalId(null, routingRef.current, 'replace')\n }\n\n if (unmountDelay <= 0) {\n for (const timer of timersRef.current.values()) clearTimeout(timer)\n timersRef.current.clear()\n updateActiveModals(() => new Map())\n } else {\n for (const [id, active] of activeModalsRef.current) {\n if (active.isOpen) {\n scheduleUnmount(id, active.invocationId)\n }\n }\n\n updateActiveModals(prev => {\n let changed = false\n const next = new Map(prev)\n for (const [id, active] of prev) {\n if (active.isOpen) {\n next.set(id, { ...active, isOpen: false })\n changed = true\n }\n }\n return changed ? next : prev\n })\n }\n\n pendings.forEach(p => p.resolve(undefined))\n }, [scheduleUnmount, unmountDelay, updateActiveModals])\n\n const isOpen = useCallback(\n (id: ModalId) => activeModalsRef.current.get(id)?.isOpen ?? false,\n []\n )\n\n const register: ModalActionsValue['register'] = useCallback(\n ((\n idOrModals: ModalId | readonly ModalRegistration[],\n component?: UntypedModalComponent\n ) => {\n const items: readonly { id: ModalId; component: UntypedModalComponent }[] =\n Array.isArray(idOrModals)\n ? (idOrModals as unknown as readonly { id: ModalId; component: UntypedModalComponent }[])\n : component\n ? [{ id: idOrModals as ModalId, component }]\n : []\n\n const urlId = routingRef.current?.enabled ? getUrlModalId(routingRef.current) : null\n\n for (const item of items) {\n let stack = registryRef.current.get(item.id)\n if (!stack) {\n stack = []\n registryRef.current.set(item.id, stack)\n }\n stack.push(item.component)\n\n // If URL routing is active and specifies this modal, open it immediately\n if (urlId && urlId === String(item.id) && !activeModalsRef.current.get(item.id)?.isOpen) {\n const data = getDefaultData(routingRef.current?.defaultData, item.id)\n openModal(item.id, data, false)\n }\n }\n }) as ModalActionsValue['register'],\n [openModal]\n )\n\n const unregister = useCallback(\n (idOrIds: ModalId | readonly ModalId[], targetComponent?: UntypedModalComponent) => {\n const ids = Array.isArray(idOrIds) ? idOrIds : [idOrIds]\n\n for (const id of ids) {\n const stack = registryRef.current.get(id)\n if (!stack) continue\n\n if (targetComponent) {\n const idx = stack.lastIndexOf(targetComponent)\n if (idx !== -1) {\n stack.splice(idx, 1)\n }\n if (stack.length > 0) {\n continue\n }\n }\n\n registryRef.current.delete(id)\n\n const timer = timersRef.current.get(id)\n if (timer) {\n clearTimeout(timer)\n timersRef.current.delete(id)\n }\n\n const pending = pendingRef.current.get(id)\n if (pending) {\n pendingRef.current.delete(id)\n pending.resolve(undefined)\n }\n\n if (routingRef.current?.enabled && getUrlModalId(routingRef.current) === String(id)) {\n setUrlModalId(null, routingRef.current, 'replace')\n }\n\n updateActiveModals(prev => {\n if (!prev.has(id)) return prev\n const next = new Map(prev)\n next.delete(id)\n return next\n })\n }\n },\n [updateActiveModals]\n )\n\n // Two-way URL synchronization listener\n useEffect(() => {\n if (!resolvedRouting || !resolvedRouting.enabled || !isBrowser()) return\n\n const syncFromUrl = () => {\n const urlId = getUrlModalId(routingRef.current)\n const currentActive = activeModalsRef.current\n\n // Dismiss active modal that does not match urlId\n for (const [id, active] of currentActive) {\n if (String(id) !== urlId && active.isOpen) {\n closeModal(id, undefined, undefined, false)\n }\n }\n\n if (urlId) {\n const modalId = urlId as ModalId\n if (!currentActive.get(modalId)?.isOpen && getComponent(modalId)) {\n openModal(modalId, getDefaultData(routingRef.current?.defaultData, modalId), false)\n }\n }\n }\n\n syncFromUrl()\n\n window.addEventListener('popstate', syncFromUrl)\n window.addEventListener('hashchange', syncFromUrl)\n\n return () => {\n window.removeEventListener('popstate', syncFromUrl)\n window.removeEventListener('hashchange', syncFromUrl)\n }\n }, [\n resolvedRouting,\n getComponent,\n openModal,\n closeModal\n ])\n\n const actions = useMemo<ModalActionsValue>(\n () => ({\n register,\n unregister,\n open,\n close,\n closeAll,\n isOpen\n }),\n [register, unregister, open, close, closeAll, isOpen]\n )\n\n return (\n <ModalActionsContext.Provider value={actions}>\n <ModalActiveContext.Provider value={activeModals}>\n {children}\n <ModalRenderer\n activeModals={activeModals}\n close={closeModal}\n fallback={fallback}\n />\n </ModalActiveContext.Provider>\n </ModalActionsContext.Provider>\n )\n}\n\nfunction ModalRenderer({\n activeModals,\n close,\n fallback\n}: {\n activeModals: ReadonlyMap<ModalId, ActiveModal>\n close: (id: ModalId, invocationId: number, result?: unknown, syncUrl?: boolean) => void\n fallback?: ReactNode\n}) {\n if (activeModals.size === 0) return null\n\n return (\n <>\n {Array.from(activeModals.values(), active => (\n <ModalSlotItem\n key={`${String(active.id)}:${active.invocationId}`}\n active={active}\n onClose={close}\n fallback={fallback}\n />\n ))}\n </>\n )\n}\n\nconst ModalSlotItem = memo(function ModalSlotItem({\n active,\n onClose,\n fallback\n}: {\n active: ActiveModal\n onClose: (id: ModalId, invocationId: number, result?: unknown, syncUrl?: boolean) => void\n fallback?: ReactNode\n}) {\n const handleClose = useCallback(\n (result?: unknown) => onClose(active.id, active.invocationId, result, true),\n [onClose, active.id, active.invocationId]\n )\n\n const Component = active.component\n\n return (\n <Suspense fallback={fallback ?? null}>\n <Component\n isOpen={active.isOpen}\n data={active.data as never}\n close={handleClose}\n />\n </Suspense>\n )\n})\n\n/**\n * Registers one or more modals for the lifetime of the calling component.\n */\nexport function useRegisterModal<TId extends ModalId>(\n id: TId,\n component: RegisteredComponent<TId>\n): void\nexport function useRegisterModal<const TIds extends readonly ModalId[]>(modals: {\n [K in keyof TIds]: ModalRegistration<TIds[K]>\n} | readonly ModalRegistration[]): void\nexport function useRegisterModal<TId extends ModalId>(\n idOrModals: TId | readonly ModalRegistration[],\n component?: RegisteredComponent<TId>\n): void {\n const { register, unregister } = useModalContext()\n const stableIdOrModals = useStableRegistrations(idOrModals)\n\n useEffect(() => {\n if (Array.isArray(stableIdOrModals)) {\n register(stableIdOrModals)\n return () => {\n for (const item of stableIdOrModals) {\n unregister(item.id, item.component as unknown as UntypedModalComponent)\n }\n }\n }\n\n if (component) {\n register(stableIdOrModals as TId, component)\n return () => {\n unregister(stableIdOrModals as TId, component as unknown as UntypedModalComponent)\n }\n }\n\n return undefined\n }, [stableIdOrModals, component, register, unregister])\n}\n\n/**\n * Declarative component for mounting static modal batches at application roots.\n */\nexport function ModalRegistry<const TIds extends readonly ModalId[]>({\n modals\n}: {\n modals: {\n [K in keyof TIds]: ModalRegistration<TIds[K]>\n } | readonly ModalRegistration[]\n}) {\n useRegisterModal(modals)\n return null\n}\n\n/**\n * Returns typed controls (`open`, `close`, `isOpen`) for a modal ID.\n */\nexport function useModal<\n TId extends ModalId,\n TResult = ModalResult<TId>\n>(\n id: TId\n): UseModalReturn<ModalData<TId>, CanonicalResult<TId, TResult>> {\n const { open, close, isOpen } = useModalContext()\n\n return useMemo<UseModalReturn<ModalData<TId>, CanonicalResult<TId, TResult>>>(\n () => ({\n open: (...args: ModalDataArg<ModalData<TId>>) =>\n open<TId, TResult>(id, ...args),\n close: (result?: CanonicalResult<TId, TResult>) =>\n close(id, result as ModalResult<TId>),\n isOpen: () => isOpen(id)\n }),\n [open, close, isOpen, id]\n )\n}\n\n/**\n * Reactively subscribes to whether a modal is currently open.\n */\nexport function useIsModalOpen(id: ModalId): boolean {\n const active = useContext(ModalActiveContext)\n return active.get(id)?.isOpen ?? false\n}\n\nexport function useCloseAll(): () => void {\n return useModalContext().closeAll\n}\n\nfunction useModalContext(): ModalActionsValue {\n const context = useContext(ModalActionsContext)\n if (!context) {\n throw new Error('useModalContext must be used within <ModalProvider>')\n }\n return context\n}\n"],"mappings":"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","names":["index_exports","__export","ModalProvider","ModalRegistry","isBrowser","useCloseAll","useIsModalOpen","useModal","useRegisterModal","__toCommonJS","import_react","import_jsx_runtime","DEFAULT_ROUTING","DEFAULT_UNMOUNT_DELAY","ModalActionsContext","ModalActiveContext","isShallowEqual","a","b","keysA","keysB","k","useStableRegistrations","idOrModals","ref","item","i","getHashParts","rawHash","hash","anchor","query","formatHash","params","qs","getUrlModalId","config","param","setUrlModalId","id","forceMode","mode","url","updateParams","sp","getDefaultData","defaultData","children","unmountDelay","fallback","routing","registryRef","activeModals","setActiveModals","activeModalsRef","updateActiveModals","updater","next","pendingRef","timersRef","invocationCounterRef","resolvedRouting","routingRef","p","getComponent","stack","scheduleUnmount","invocationId","existing","timer","prev","closeModal","result","syncUrl","active","pending","didClose","current","openModal","data","comp","resolver","promise","resolve","open","args","close","closeAll","pendings","changed","isOpen","register","component","items","urlId","unregister","idOrIds","targetComponent","ids","idx","syncFromUrl","currentActive","modalId","actions","ModalRenderer","ModalSlotItem","onClose","handleClose","Component","useModalContext","stableIdOrModals","modals","context"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,112 +1,89 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
2
|
import { ComponentType, ReactNode } from 'react';
|
|
3
3
|
|
|
4
|
+
type RegisteredModalId = keyof ModalRegistry;
|
|
5
|
+
type ModalId = [RegisteredModalId] extends [never] ? string : RegisteredModalId;
|
|
6
|
+
type ModalData<TId extends ModalId> = TId extends keyof ModalRegistry ? 'data' extends keyof ModalRegistry[TId] ? ModalRegistry[TId]['data'] : void : unknown;
|
|
7
|
+
type ModalResult<TId extends ModalId> = TId extends keyof ModalRegistry ? 'result' extends keyof ModalRegistry[TId] ? ModalRegistry[TId]['result'] : void : unknown;
|
|
4
8
|
/**
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* @example
|
|
8
|
-
* ```ts
|
|
9
|
-
* declare module 'react-modal-registry' {
|
|
10
|
-
* interface ModalDataMap {
|
|
11
|
-
* confirmAction: { itemId: string }
|
|
12
|
-
* }
|
|
13
|
-
* }
|
|
14
|
-
* ```
|
|
15
|
-
*/
|
|
16
|
-
interface ModalDataMap {
|
|
17
|
-
}
|
|
18
|
-
/**
|
|
19
|
-
* Registry for modal return types via module augmentation.
|
|
20
|
-
*
|
|
21
|
-
* @example
|
|
22
|
-
* ```ts
|
|
23
|
-
* declare module 'react-modal-registry' {
|
|
24
|
-
* interface ModalResultMap {
|
|
25
|
-
* confirmAction: boolean
|
|
26
|
-
* }
|
|
27
|
-
* }
|
|
28
|
-
* ```
|
|
29
|
-
*/
|
|
30
|
-
interface ModalResultMap {
|
|
31
|
-
}
|
|
32
|
-
type RegisteredModalId = keyof ModalDataMap | keyof ModalResultMap | keyof ModalRegistry;
|
|
33
|
-
/**
|
|
34
|
-
* Modal identifier supporting autocompletion of registered IDs with string fallback.
|
|
9
|
+
* Derives optional vs. required payload arguments based on `TData`.
|
|
35
10
|
*/
|
|
36
|
-
type
|
|
37
|
-
type
|
|
38
|
-
data: infer TData;
|
|
39
|
-
} ? TData : unknown : unknown;
|
|
40
|
-
type ModalResult<TId extends ModalId> = TId extends keyof ModalResultMap ? ModalResultMap[TId] : TId extends keyof ModalRegistry ? ModalRegistry[TId] extends {
|
|
41
|
-
result: infer TResult;
|
|
42
|
-
} ? TResult : unknown : unknown;
|
|
43
|
-
/**
|
|
44
|
-
* optional vs required payload arguments based on `TData`.
|
|
45
|
-
*/
|
|
46
|
-
type ModalDataArg<TData> = [unknown] extends [TData] ? [data?: TData] : [TData] extends [void] | [undefined] ? [data?: TData] : undefined extends TData ? [data?: TData] : [data: TData];
|
|
11
|
+
type ModalDataArg<TData> = [TData] extends [never] ? [] : [unknown] extends [TData] ? [data?: TData] : [TData] extends [void] | [undefined] ? [data?: TData] : undefined extends TData ? [data?: TData] : [data: TData];
|
|
12
|
+
type ModalClose<TResult = void> = [TResult] extends [void] ? () => void : (result?: TResult) => void;
|
|
47
13
|
/**
|
|
48
14
|
* Standard props passed to registered modal components.
|
|
49
15
|
*/
|
|
50
|
-
interface ModalProps<TData = unknown, TResult =
|
|
16
|
+
interface ModalProps<TData = unknown, TResult = void> {
|
|
51
17
|
isOpen: boolean;
|
|
52
18
|
data: TData;
|
|
53
|
-
close:
|
|
19
|
+
close: ModalClose<TResult>;
|
|
54
20
|
}
|
|
55
|
-
type ModalComponent<TData = unknown, TResult =
|
|
21
|
+
type ModalComponent<TData = unknown, TResult = void> = ComponentType<ModalProps<TData, TResult>>;
|
|
22
|
+
type UntypedModalComponent = ComponentType<ModalProps<never, unknown>>;
|
|
23
|
+
type RegisteredComponent<TId extends ModalId> = [
|
|
24
|
+
RegisteredModalId
|
|
25
|
+
] extends [never] ? ComponentType<ModalProps<never, unknown>> : ModalComponent<ModalData<TId>, ModalResult<TId>>;
|
|
56
26
|
interface ModalRegistration<TId extends ModalId = ModalId> {
|
|
57
27
|
id: TId;
|
|
58
|
-
component:
|
|
28
|
+
component: RegisteredComponent<TId>;
|
|
59
29
|
}
|
|
60
30
|
/**
|
|
61
31
|
* Imperative modal controller returned by `useModal(id)`.
|
|
62
32
|
*/
|
|
63
33
|
interface UseModalReturn<TData = unknown, TResult = unknown> {
|
|
64
|
-
|
|
65
|
-
* Opens the modal with the specified payload and resolves when closed.
|
|
66
|
-
*/
|
|
67
|
-
open(...args: ModalDataArg<TData>): Promise<TResult>;
|
|
68
|
-
/**
|
|
69
|
-
* Closes the modal imperatively, optionally passing a resolution value.
|
|
70
|
-
*/
|
|
34
|
+
open(...args: ModalDataArg<TData>): Promise<TResult | undefined>;
|
|
71
35
|
close(result?: TResult): void;
|
|
72
|
-
/**
|
|
73
|
-
* Imperatively reads whether the modal is currently open.
|
|
74
|
-
*/
|
|
75
36
|
isOpen(): boolean;
|
|
76
37
|
}
|
|
38
|
+
type CanonicalResult<TId extends ModalId, TResult = ModalResult<TId>> = [
|
|
39
|
+
RegisteredModalId
|
|
40
|
+
] extends [never] ? TResult : ModalResult<TId>;
|
|
77
41
|
interface ModalActionsValue {
|
|
78
|
-
register<TId extends ModalId>(id: TId, component:
|
|
79
|
-
register
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
unregister(idOrIds: ModalId | readonly ModalId[]): void;
|
|
83
|
-
open<TId extends ModalId, TResult = ModalResult<TId>>(id: TId, ...args: ModalDataArg<ModalData<TId>>): Promise<TResult>;
|
|
42
|
+
register<TId extends ModalId>(id: TId, component: RegisteredComponent<TId>): void;
|
|
43
|
+
register(modals: readonly ModalRegistration[]): void;
|
|
44
|
+
unregister(idOrIds: ModalId | readonly ModalId[], targetComponent?: UntypedModalComponent): void;
|
|
45
|
+
open<TId extends ModalId, TResult = ModalResult<TId>>(id: TId, ...args: ModalDataArg<ModalData<TId>>): Promise<CanonicalResult<TId, TResult> | undefined>;
|
|
84
46
|
close<TId extends ModalId>(id: TId, result?: ModalResult<TId>): void;
|
|
85
47
|
closeAll(): void;
|
|
86
48
|
isOpen(id: ModalId): boolean;
|
|
87
49
|
}
|
|
50
|
+
type ModalDefaultData = Record<string, unknown> | ((id: ModalId) => unknown);
|
|
51
|
+
type ModalRoutingStrategy = 'hash' | 'query';
|
|
52
|
+
type ModalHistoryMode = 'push' | 'replace';
|
|
53
|
+
interface ModalRoutingConfig {
|
|
54
|
+
enabled?: boolean;
|
|
55
|
+
strategy?: ModalRoutingStrategy;
|
|
56
|
+
param?: string;
|
|
57
|
+
historyMode?: ModalHistoryMode;
|
|
58
|
+
defaultData?: ModalDefaultData;
|
|
59
|
+
}
|
|
60
|
+
interface ModalProviderProps {
|
|
61
|
+
children: ReactNode;
|
|
62
|
+
unmountDelay?: number;
|
|
63
|
+
fallback?: ReactNode;
|
|
64
|
+
routing?: ModalRoutingConfig;
|
|
65
|
+
}
|
|
66
|
+
declare const isBrowser: () => boolean;
|
|
88
67
|
/**
|
|
89
|
-
* Root provider hosting the modal registry, lifecycle actions, and slot renderer.
|
|
68
|
+
* Root provider hosting the modal registry, URL routing, lifecycle actions, and slot renderer.
|
|
90
69
|
*/
|
|
91
|
-
declare function ModalProvider({ children }:
|
|
92
|
-
children: ReactNode;
|
|
93
|
-
}): react.JSX.Element;
|
|
70
|
+
declare function ModalProvider({ children, unmountDelay, fallback, routing }: ModalProviderProps): react.JSX.Element;
|
|
94
71
|
/**
|
|
95
72
|
* Registers one or more modals for the lifetime of the calling component.
|
|
96
|
-
*
|
|
97
73
|
*/
|
|
98
|
-
declare function useRegisterModal<TId extends ModalId>(id: TId, component:
|
|
74
|
+
declare function useRegisterModal<TId extends ModalId>(id: TId, component: RegisteredComponent<TId>): void;
|
|
99
75
|
declare function useRegisterModal<const TIds extends readonly ModalId[]>(modals: {
|
|
100
76
|
[K in keyof TIds]: ModalRegistration<TIds[K]>;
|
|
101
|
-
}): void;
|
|
77
|
+
} | readonly ModalRegistration[]): void;
|
|
102
78
|
/**
|
|
103
|
-
*
|
|
79
|
+
* Canonical registry for modal payloads and return types via module augmentation (e.g., `env.d.ts` or `modals.d.ts`).
|
|
104
80
|
*
|
|
105
81
|
* @example
|
|
106
82
|
* ```ts
|
|
107
83
|
* declare module 'react-modal-registry' {
|
|
108
84
|
* interface ModalRegistry {
|
|
109
85
|
* confirmAction: { data: { itemId: string }; result: boolean }
|
|
86
|
+
* simpleAlert: void
|
|
110
87
|
* }
|
|
111
88
|
* }
|
|
112
89
|
* ```
|
|
@@ -114,40 +91,21 @@ declare function useRegisterModal<const TIds extends readonly ModalId[]>(modals:
|
|
|
114
91
|
interface ModalRegistry {
|
|
115
92
|
}
|
|
116
93
|
/**
|
|
117
|
-
* Declarative component for mounting static modal batches at application
|
|
94
|
+
* Declarative component for mounting static modal batches at application roots.
|
|
118
95
|
*/
|
|
119
96
|
declare function ModalRegistry<const TIds extends readonly ModalId[]>({ modals }: {
|
|
120
97
|
modals: {
|
|
121
98
|
[K in keyof TIds]: ModalRegistration<TIds[K]>;
|
|
122
|
-
};
|
|
99
|
+
} | readonly ModalRegistration[];
|
|
123
100
|
}): null;
|
|
124
101
|
/**
|
|
125
102
|
* Returns typed controls (`open`, `close`, `isOpen`) for a modal ID.
|
|
126
|
-
*
|
|
127
|
-
* `isOpen()` reads state imperatively without subscribing the caller to render updates.
|
|
128
|
-
* Use `useIsModalOpen(id)` for reactive subscriptions.
|
|
129
|
-
*
|
|
130
|
-
* @example
|
|
131
|
-
* ```tsx
|
|
132
|
-
* function DeleteButton({ id }: { id: string }) {
|
|
133
|
-
* const confirmModal = useModal('confirmation')
|
|
134
|
-
*
|
|
135
|
-
* const handleDelete = async () => {
|
|
136
|
-
* const confirmed = await confirmModal.open({ itemId: id })
|
|
137
|
-
* if (confirmed) {
|
|
138
|
-
* deleteItem(id)
|
|
139
|
-
* }
|
|
140
|
-
* }
|
|
141
|
-
*
|
|
142
|
-
* return <button onClick={handleDelete}>Delete</button>
|
|
143
|
-
* }
|
|
144
|
-
* ```
|
|
145
103
|
*/
|
|
146
|
-
declare function useModal<TId extends ModalId, TResult = ModalResult<TId>>(id: TId): UseModalReturn<ModalData<TId>, TResult
|
|
104
|
+
declare function useModal<TId extends ModalId, TResult = ModalResult<TId>>(id: TId): UseModalReturn<ModalData<TId>, CanonicalResult<TId, TResult>>;
|
|
147
105
|
/**
|
|
148
106
|
* Reactively subscribes to whether a modal is currently open.
|
|
149
107
|
*/
|
|
150
108
|
declare function useIsModalOpen(id: ModalId): boolean;
|
|
151
109
|
declare function useCloseAll(): () => void;
|
|
152
110
|
|
|
153
|
-
export { type ModalActionsValue, type ModalComponent, type ModalData, type ModalDataArg, type
|
|
111
|
+
export { type CanonicalResult, type ModalActionsValue, type ModalClose, type ModalComponent, type ModalData, type ModalDataArg, type ModalDefaultData, type ModalHistoryMode, type ModalId, type ModalProps, ModalProvider, type ModalProviderProps, type ModalRegistration, ModalRegistry, type ModalResult, type ModalRoutingConfig, type ModalRoutingStrategy, type RegisteredComponent, type RegisteredModalId, type UntypedModalComponent, type UseModalReturn, isBrowser, useCloseAll, useIsModalOpen, useModal, useRegisterModal };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,112 +1,89 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
2
|
import { ComponentType, ReactNode } from 'react';
|
|
3
3
|
|
|
4
|
+
type RegisteredModalId = keyof ModalRegistry;
|
|
5
|
+
type ModalId = [RegisteredModalId] extends [never] ? string : RegisteredModalId;
|
|
6
|
+
type ModalData<TId extends ModalId> = TId extends keyof ModalRegistry ? 'data' extends keyof ModalRegistry[TId] ? ModalRegistry[TId]['data'] : void : unknown;
|
|
7
|
+
type ModalResult<TId extends ModalId> = TId extends keyof ModalRegistry ? 'result' extends keyof ModalRegistry[TId] ? ModalRegistry[TId]['result'] : void : unknown;
|
|
4
8
|
/**
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* @example
|
|
8
|
-
* ```ts
|
|
9
|
-
* declare module 'react-modal-registry' {
|
|
10
|
-
* interface ModalDataMap {
|
|
11
|
-
* confirmAction: { itemId: string }
|
|
12
|
-
* }
|
|
13
|
-
* }
|
|
14
|
-
* ```
|
|
15
|
-
*/
|
|
16
|
-
interface ModalDataMap {
|
|
17
|
-
}
|
|
18
|
-
/**
|
|
19
|
-
* Registry for modal return types via module augmentation.
|
|
20
|
-
*
|
|
21
|
-
* @example
|
|
22
|
-
* ```ts
|
|
23
|
-
* declare module 'react-modal-registry' {
|
|
24
|
-
* interface ModalResultMap {
|
|
25
|
-
* confirmAction: boolean
|
|
26
|
-
* }
|
|
27
|
-
* }
|
|
28
|
-
* ```
|
|
29
|
-
*/
|
|
30
|
-
interface ModalResultMap {
|
|
31
|
-
}
|
|
32
|
-
type RegisteredModalId = keyof ModalDataMap | keyof ModalResultMap | keyof ModalRegistry;
|
|
33
|
-
/**
|
|
34
|
-
* Modal identifier supporting autocompletion of registered IDs with string fallback.
|
|
9
|
+
* Derives optional vs. required payload arguments based on `TData`.
|
|
35
10
|
*/
|
|
36
|
-
type
|
|
37
|
-
type
|
|
38
|
-
data: infer TData;
|
|
39
|
-
} ? TData : unknown : unknown;
|
|
40
|
-
type ModalResult<TId extends ModalId> = TId extends keyof ModalResultMap ? ModalResultMap[TId] : TId extends keyof ModalRegistry ? ModalRegistry[TId] extends {
|
|
41
|
-
result: infer TResult;
|
|
42
|
-
} ? TResult : unknown : unknown;
|
|
43
|
-
/**
|
|
44
|
-
* optional vs required payload arguments based on `TData`.
|
|
45
|
-
*/
|
|
46
|
-
type ModalDataArg<TData> = [unknown] extends [TData] ? [data?: TData] : [TData] extends [void] | [undefined] ? [data?: TData] : undefined extends TData ? [data?: TData] : [data: TData];
|
|
11
|
+
type ModalDataArg<TData> = [TData] extends [never] ? [] : [unknown] extends [TData] ? [data?: TData] : [TData] extends [void] | [undefined] ? [data?: TData] : undefined extends TData ? [data?: TData] : [data: TData];
|
|
12
|
+
type ModalClose<TResult = void> = [TResult] extends [void] ? () => void : (result?: TResult) => void;
|
|
47
13
|
/**
|
|
48
14
|
* Standard props passed to registered modal components.
|
|
49
15
|
*/
|
|
50
|
-
interface ModalProps<TData = unknown, TResult =
|
|
16
|
+
interface ModalProps<TData = unknown, TResult = void> {
|
|
51
17
|
isOpen: boolean;
|
|
52
18
|
data: TData;
|
|
53
|
-
close:
|
|
19
|
+
close: ModalClose<TResult>;
|
|
54
20
|
}
|
|
55
|
-
type ModalComponent<TData = unknown, TResult =
|
|
21
|
+
type ModalComponent<TData = unknown, TResult = void> = ComponentType<ModalProps<TData, TResult>>;
|
|
22
|
+
type UntypedModalComponent = ComponentType<ModalProps<never, unknown>>;
|
|
23
|
+
type RegisteredComponent<TId extends ModalId> = [
|
|
24
|
+
RegisteredModalId
|
|
25
|
+
] extends [never] ? ComponentType<ModalProps<never, unknown>> : ModalComponent<ModalData<TId>, ModalResult<TId>>;
|
|
56
26
|
interface ModalRegistration<TId extends ModalId = ModalId> {
|
|
57
27
|
id: TId;
|
|
58
|
-
component:
|
|
28
|
+
component: RegisteredComponent<TId>;
|
|
59
29
|
}
|
|
60
30
|
/**
|
|
61
31
|
* Imperative modal controller returned by `useModal(id)`.
|
|
62
32
|
*/
|
|
63
33
|
interface UseModalReturn<TData = unknown, TResult = unknown> {
|
|
64
|
-
|
|
65
|
-
* Opens the modal with the specified payload and resolves when closed.
|
|
66
|
-
*/
|
|
67
|
-
open(...args: ModalDataArg<TData>): Promise<TResult>;
|
|
68
|
-
/**
|
|
69
|
-
* Closes the modal imperatively, optionally passing a resolution value.
|
|
70
|
-
*/
|
|
34
|
+
open(...args: ModalDataArg<TData>): Promise<TResult | undefined>;
|
|
71
35
|
close(result?: TResult): void;
|
|
72
|
-
/**
|
|
73
|
-
* Imperatively reads whether the modal is currently open.
|
|
74
|
-
*/
|
|
75
36
|
isOpen(): boolean;
|
|
76
37
|
}
|
|
38
|
+
type CanonicalResult<TId extends ModalId, TResult = ModalResult<TId>> = [
|
|
39
|
+
RegisteredModalId
|
|
40
|
+
] extends [never] ? TResult : ModalResult<TId>;
|
|
77
41
|
interface ModalActionsValue {
|
|
78
|
-
register<TId extends ModalId>(id: TId, component:
|
|
79
|
-
register
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
unregister(idOrIds: ModalId | readonly ModalId[]): void;
|
|
83
|
-
open<TId extends ModalId, TResult = ModalResult<TId>>(id: TId, ...args: ModalDataArg<ModalData<TId>>): Promise<TResult>;
|
|
42
|
+
register<TId extends ModalId>(id: TId, component: RegisteredComponent<TId>): void;
|
|
43
|
+
register(modals: readonly ModalRegistration[]): void;
|
|
44
|
+
unregister(idOrIds: ModalId | readonly ModalId[], targetComponent?: UntypedModalComponent): void;
|
|
45
|
+
open<TId extends ModalId, TResult = ModalResult<TId>>(id: TId, ...args: ModalDataArg<ModalData<TId>>): Promise<CanonicalResult<TId, TResult> | undefined>;
|
|
84
46
|
close<TId extends ModalId>(id: TId, result?: ModalResult<TId>): void;
|
|
85
47
|
closeAll(): void;
|
|
86
48
|
isOpen(id: ModalId): boolean;
|
|
87
49
|
}
|
|
50
|
+
type ModalDefaultData = Record<string, unknown> | ((id: ModalId) => unknown);
|
|
51
|
+
type ModalRoutingStrategy = 'hash' | 'query';
|
|
52
|
+
type ModalHistoryMode = 'push' | 'replace';
|
|
53
|
+
interface ModalRoutingConfig {
|
|
54
|
+
enabled?: boolean;
|
|
55
|
+
strategy?: ModalRoutingStrategy;
|
|
56
|
+
param?: string;
|
|
57
|
+
historyMode?: ModalHistoryMode;
|
|
58
|
+
defaultData?: ModalDefaultData;
|
|
59
|
+
}
|
|
60
|
+
interface ModalProviderProps {
|
|
61
|
+
children: ReactNode;
|
|
62
|
+
unmountDelay?: number;
|
|
63
|
+
fallback?: ReactNode;
|
|
64
|
+
routing?: ModalRoutingConfig;
|
|
65
|
+
}
|
|
66
|
+
declare const isBrowser: () => boolean;
|
|
88
67
|
/**
|
|
89
|
-
* Root provider hosting the modal registry, lifecycle actions, and slot renderer.
|
|
68
|
+
* Root provider hosting the modal registry, URL routing, lifecycle actions, and slot renderer.
|
|
90
69
|
*/
|
|
91
|
-
declare function ModalProvider({ children }:
|
|
92
|
-
children: ReactNode;
|
|
93
|
-
}): react.JSX.Element;
|
|
70
|
+
declare function ModalProvider({ children, unmountDelay, fallback, routing }: ModalProviderProps): react.JSX.Element;
|
|
94
71
|
/**
|
|
95
72
|
* Registers one or more modals for the lifetime of the calling component.
|
|
96
|
-
*
|
|
97
73
|
*/
|
|
98
|
-
declare function useRegisterModal<TId extends ModalId>(id: TId, component:
|
|
74
|
+
declare function useRegisterModal<TId extends ModalId>(id: TId, component: RegisteredComponent<TId>): void;
|
|
99
75
|
declare function useRegisterModal<const TIds extends readonly ModalId[]>(modals: {
|
|
100
76
|
[K in keyof TIds]: ModalRegistration<TIds[K]>;
|
|
101
|
-
}): void;
|
|
77
|
+
} | readonly ModalRegistration[]): void;
|
|
102
78
|
/**
|
|
103
|
-
*
|
|
79
|
+
* Canonical registry for modal payloads and return types via module augmentation (e.g., `env.d.ts` or `modals.d.ts`).
|
|
104
80
|
*
|
|
105
81
|
* @example
|
|
106
82
|
* ```ts
|
|
107
83
|
* declare module 'react-modal-registry' {
|
|
108
84
|
* interface ModalRegistry {
|
|
109
85
|
* confirmAction: { data: { itemId: string }; result: boolean }
|
|
86
|
+
* simpleAlert: void
|
|
110
87
|
* }
|
|
111
88
|
* }
|
|
112
89
|
* ```
|
|
@@ -114,40 +91,21 @@ declare function useRegisterModal<const TIds extends readonly ModalId[]>(modals:
|
|
|
114
91
|
interface ModalRegistry {
|
|
115
92
|
}
|
|
116
93
|
/**
|
|
117
|
-
* Declarative component for mounting static modal batches at application
|
|
94
|
+
* Declarative component for mounting static modal batches at application roots.
|
|
118
95
|
*/
|
|
119
96
|
declare function ModalRegistry<const TIds extends readonly ModalId[]>({ modals }: {
|
|
120
97
|
modals: {
|
|
121
98
|
[K in keyof TIds]: ModalRegistration<TIds[K]>;
|
|
122
|
-
};
|
|
99
|
+
} | readonly ModalRegistration[];
|
|
123
100
|
}): null;
|
|
124
101
|
/**
|
|
125
102
|
* Returns typed controls (`open`, `close`, `isOpen`) for a modal ID.
|
|
126
|
-
*
|
|
127
|
-
* `isOpen()` reads state imperatively without subscribing the caller to render updates.
|
|
128
|
-
* Use `useIsModalOpen(id)` for reactive subscriptions.
|
|
129
|
-
*
|
|
130
|
-
* @example
|
|
131
|
-
* ```tsx
|
|
132
|
-
* function DeleteButton({ id }: { id: string }) {
|
|
133
|
-
* const confirmModal = useModal('confirmation')
|
|
134
|
-
*
|
|
135
|
-
* const handleDelete = async () => {
|
|
136
|
-
* const confirmed = await confirmModal.open({ itemId: id })
|
|
137
|
-
* if (confirmed) {
|
|
138
|
-
* deleteItem(id)
|
|
139
|
-
* }
|
|
140
|
-
* }
|
|
141
|
-
*
|
|
142
|
-
* return <button onClick={handleDelete}>Delete</button>
|
|
143
|
-
* }
|
|
144
|
-
* ```
|
|
145
103
|
*/
|
|
146
|
-
declare function useModal<TId extends ModalId, TResult = ModalResult<TId>>(id: TId): UseModalReturn<ModalData<TId>, TResult
|
|
104
|
+
declare function useModal<TId extends ModalId, TResult = ModalResult<TId>>(id: TId): UseModalReturn<ModalData<TId>, CanonicalResult<TId, TResult>>;
|
|
147
105
|
/**
|
|
148
106
|
* Reactively subscribes to whether a modal is currently open.
|
|
149
107
|
*/
|
|
150
108
|
declare function useIsModalOpen(id: ModalId): boolean;
|
|
151
109
|
declare function useCloseAll(): () => void;
|
|
152
110
|
|
|
153
|
-
export { type ModalActionsValue, type ModalComponent, type ModalData, type ModalDataArg, type
|
|
111
|
+
export { type CanonicalResult, type ModalActionsValue, type ModalClose, type ModalComponent, type ModalData, type ModalDataArg, type ModalDefaultData, type ModalHistoryMode, type ModalId, type ModalProps, ModalProvider, type ModalProviderProps, type ModalRegistration, ModalRegistry, type ModalResult, type ModalRoutingConfig, type ModalRoutingStrategy, type RegisteredComponent, type RegisteredModalId, type UntypedModalComponent, type UseModalReturn, isBrowser, useCloseAll, useIsModalOpen, useModal, useRegisterModal };
|