@womp/kookie-ui 0.35.0 → 0.35.2
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/components.css +35 -35
- package/dist/cjs/components/alert-dialog.js +1 -1
- package/dist/cjs/components/alert-dialog.js.map +3 -3
- package/dist/cjs/components/command.d.ts.map +1 -1
- package/dist/cjs/components/command.js +1 -1
- package/dist/cjs/components/command.js.map +3 -3
- package/dist/cjs/components/dialog.js +1 -1
- package/dist/cjs/components/dialog.js.map +3 -3
- package/dist/cjs/components/index.js +2 -2
- package/dist/cjs/components/index.js.map +3 -3
- package/dist/cjs/components/navigator.d.ts +6 -1
- package/dist/cjs/components/navigator.d.ts.map +1 -1
- package/dist/cjs/components/navigator.js +1 -1
- package/dist/cjs/components/navigator.js.map +2 -2
- package/dist/cjs/components/sheet.js +1 -1
- package/dist/cjs/components/sheet.js.map +3 -3
- package/dist/cjs/hooks/index.js +2 -2
- package/dist/cjs/hooks/index.js.map +3 -3
- package/dist/cjs/hooks/use-body-pointer-events-cleanup.d.ts +6 -0
- package/dist/cjs/hooks/use-body-pointer-events-cleanup.d.ts.map +1 -1
- package/dist/cjs/hooks/use-body-pointer-events-cleanup.js +1 -1
- package/dist/cjs/hooks/use-body-pointer-events-cleanup.js.map +3 -3
- package/dist/cjs/index.js +3 -3
- package/dist/cjs/index.js.map +3 -3
- package/dist/esm/chunk-3RJVW6RK.js +3 -0
- package/dist/esm/{chunk-7VJF6EXN.js.map → chunk-3RJVW6RK.js.map} +3 -3
- package/dist/esm/chunk-LR4X7SVV.js +3 -0
- package/dist/esm/chunk-LR4X7SVV.js.map +7 -0
- package/dist/esm/{chunk-J7MIXG6M.js → chunk-QV66TN2A.js} +2 -2
- package/dist/esm/chunk-SR42NATX.js +3 -0
- package/dist/esm/chunk-SR42NATX.js.map +7 -0
- package/dist/esm/chunk-UH6CJXHY.js +3 -0
- package/dist/esm/chunk-UH6CJXHY.js.map +7 -0
- package/dist/esm/chunk-XVN43IZL.js +3 -0
- package/dist/esm/chunk-XVN43IZL.js.map +7 -0
- package/dist/esm/components/alert-dialog.js +1 -1
- package/dist/esm/components/command.d.ts.map +1 -1
- package/dist/esm/components/command.js +1 -1
- package/dist/esm/components/dialog.js +1 -1
- package/dist/esm/components/index.js +1 -1
- package/dist/esm/components/navigator.d.ts +6 -1
- package/dist/esm/components/navigator.d.ts.map +1 -1
- package/dist/esm/components/navigator.js +1 -1
- package/dist/esm/components/sheet.js +1 -1
- package/dist/esm/hooks/index.js +1 -1
- package/dist/esm/hooks/use-body-pointer-events-cleanup.d.ts +6 -0
- package/dist/esm/hooks/use-body-pointer-events-cleanup.d.ts.map +1 -1
- package/dist/esm/hooks/use-body-pointer-events-cleanup.js +1 -1
- package/dist/esm/index.js +1 -1
- package/package.json +1 -1
- package/src/components/_internal/base-card.css +3 -3
- package/src/components/_internal/base-menu.css +1 -1
- package/src/components/_internal/base-sidebar-menu.css +1 -1
- package/src/components/_internal/base-tab-list.css +1 -1
- package/src/components/accordion.css +2 -2
- package/src/components/alert-dialog.tsx +2 -2
- package/src/components/avatar.css +2 -2
- package/src/components/card.css +1 -1
- package/src/components/chatbar.css +2 -2
- package/src/components/combobox.css +1 -1
- package/src/components/command.css +1 -1
- package/src/components/command.tsx +16 -3
- package/src/components/dialog.tsx +2 -2
- package/src/components/kbd.css +2 -2
- package/src/components/message-scroller.css +1 -1
- package/src/components/navigator.css +1 -1
- package/src/components/navigator.tsx +14 -1
- package/src/components/scroll-area.css +1 -1
- package/src/components/select.css +6 -6
- package/src/components/sidebar.css +2 -2
- package/src/components/text-area.css +1 -1
- package/src/components/text-field.css +3 -3
- package/src/components/virtual-menu.css +1 -1
- package/src/hooks/use-body-pointer-events-cleanup.ts +12 -0
- package/src/styles/tokens/constants.css +3 -3
- package/styles.css +37 -37
- package/tokens.css +2 -2
- package/dist/esm/chunk-7VJF6EXN.js +0 -3
- package/dist/esm/chunk-CAM66IXC.js +0 -3
- package/dist/esm/chunk-CAM66IXC.js.map +0 -7
- package/dist/esm/chunk-GZPB6MRQ.js +0 -3
- package/dist/esm/chunk-GZPB6MRQ.js.map +0 -7
- package/dist/esm/chunk-I5TPDYJO.js +0 -3
- package/dist/esm/chunk-I5TPDYJO.js.map +0 -7
- package/dist/esm/chunk-NZJRXZYX.js +0 -3
- package/dist/esm/chunk-NZJRXZYX.js.map +0 -7
- /package/dist/esm/{chunk-J7MIXG6M.js.map → chunk-QV66TN2A.js.map} +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/hooks/index.ts", "../../../src/hooks/use-body-pointer-events-cleanup.ts", "../../../src/hooks/use-tooltip-wrapper.ts", "../../../src/hooks/use-appearance.ts", "../../../src/hooks/appearance-script.ts"],
|
|
4
|
-
"sourcesContent": ["export { useBodyPointerEventsCleanup } from './use-body-pointer-events-cleanup.js';\nexport { useTooltipWrapper } from './use-tooltip-wrapper.js';\nexport { useAppearance, setAppearance } from './use-appearance.js';\nexport { appearanceScript, APPEARANCE_STORAGE_KEY } from './appearance-script.js';\nexport type { AppearanceChoice, ResolvedAppearance } from './use-appearance.js';\n", "import * as React from 'react';\n\n// How many overlays currently use the hook. The listeners are shared: the first mount installs\n// them and the last unmount removes them, so closing one overlay never strips the fallback from\n// another that is still mounted.\nlet mountCount = 0;\nlet teardown: (() => void) | null = null;\n\nconst devLog = (message: string) => {\n if (process.env.NODE_ENV !== 'production') {\n console.log(message);\n }\n};\n\nfunction install(): () => void {\n // Deferred cleanups are tracked so teardown can cancel the ones still queued. A timer that\n // outlives the document reaches for `document.body` after it is gone; under jsdom that surfaces\n // as an unhandled ReferenceError attributed to whichever test happened to be running.\n const pending = new Set<ReturnType<typeof setTimeout>>();\n\n const defer = (fn: () => void, delay: number) => {\n const id = setTimeout(() => {\n pending.delete(id);\n fn();\n }, delay);\n pending.add(id);\n };\n\n const isStuck = () => document.body.style.pointerEvents === 'none';\n\n const hasOpenModal = (): boolean => {\n // Open dialogs and alert dialogs\n const hasDialogs = Boolean(\n document.querySelector('[role=\"dialog\"][aria-modal=\"true\"], [role=\"alertdialog\"][aria-modal=\"true\"]'),\n );\n\n // Any Radix overlay that is still mounted. Only overlays count: an open Accordion or\n // Collapsible also carries data-state=\"open\" and must not keep a stuck page blocked.\n const hasRadixOverlays = Boolean(\n document.querySelector(\n '[data-radix-popper-content-wrapper], [role=\"dialog\"][data-state=\"open\"], [role=\"alertdialog\"][data-state=\"open\"]',\n ),\n );\n\n return hasDialogs || hasRadixOverlays;\n };\n\n // Only clears pointer-events when no modal is open. Scroll-lock attributes belong to\n // react-remove-scroll and are never touched here.\n const safeCleanup = () => {\n if (isStuck() && !hasOpenModal()) {\n devLog('[KookieUI] Cleaning stuck pointer-events: none from body');\n document.body.style.pointerEvents = '';\n }\n };\n\n // Every trigger queues a check only while the body is actually blocked.\n const scheduleCleanup = (delay: number) => {\n if (isStuck()) defer(safeCleanup, delay);\n };\n\n const onDocumentClick = (event: Event) => {\n const target = event.target as Element;\n if (target && !target.closest('[role=\"dialog\"], [role=\"alertdialog\"], [data-radix-popper-content-wrapper]')) {\n scheduleCleanup(100);\n }\n };\n\n const onEscapeKey = (event: KeyboardEvent) => {\n if (event.key === 'Escape') {\n scheduleCleanup(200);\n }\n };\n\n const onInteraction = () => {\n scheduleCleanup(50);\n };\n\n document.addEventListener('click', onDocumentClick, true);\n document.addEventListener('keydown', onEscapeKey, true);\n document.addEventListener('pointerup', onInteraction, true);\n document.addEventListener('transitionend', onInteraction, true);\n document.addEventListener('animationend', onInteraction, true);\n\n // Portals mount and unmount as direct children of body, and the stuck value lives in body's\n // style, so body's own children and style are all that need watching.\n const observer = new MutationObserver(() => {\n scheduleCleanup(0);\n });\n observer.observe(document.body, { childList: true, attributes: true, attributeFilter: ['style'] });\n\n // Fallback periodic cleanup\n const intervalId = setInterval(safeCleanup, 1000);\n\n // Initial cleanup\n scheduleCleanup(100);\n\n // Tear everything down together. The observer is the important one: it fires while React\n // unmounts the tree, so leaving it connected keeps queueing work against a document that is\n // about to go away.\n return () => {\n clearInterval(intervalId);\n\n pending.forEach(clearTimeout);\n pending.clear();\n\n observer.disconnect();\n\n document.removeEventListener('click', onDocumentClick, true);\n document.removeEventListener('keydown', onEscapeKey, true);\n document.removeEventListener('pointerup', onInteraction, true);\n document.removeEventListener('transitionend', onInteraction, true);\n document.removeEventListener('animationend', onInteraction, true);\n };\n}\n\n/**\n * Hook to cleanup stuck pointer-events: none on document.body\n *\n * This addresses an issue where react-remove-scroll (used by Radix UI Dialog)\n * sometimes fails to restore body pointer-events after dialog closes,\n * leaving the page unclickable. It never clears the value while a modal is open.\n */\nexport function useBodyPointerEventsCleanup() {\n React.useEffect(() => {\n if (typeof document === 'undefined') return;\n\n mountCount += 1;\n if (mountCount === 1) {\n teardown = install();\n }\n\n return () => {\n mountCount -= 1;\n if (mountCount === 0) {\n teardown?.();\n teardown = null;\n }\n };\n }, []);\n}\n", "import * as React from 'react';\n\n/**\n * Hook for managing tooltip accessibility props and conditional rendering.\n * Encapsulates tooltip ID generation and aria-describedby binding.\n *\n * @param tooltip - The tooltip content (if any)\n * @returns Object containing tooltipId, hasTooltip flag, and accessibility props\n *\n * @example\n * ```tsx\n * const { tooltipId, hasTooltip, accessibilityProps } = useTooltipWrapper(tooltip);\n *\n * const button = <button {...accessibilityProps}>Click me</button>;\n *\n * if (!hasTooltip) return button;\n *\n * return <Tooltip id={tooltipId} content={tooltip}>{button}</Tooltip>;\n * ```\n */\nexport function useTooltipWrapper(tooltip: React.ReactNode) {\n const tooltipId = React.useId();\n const hasTooltip = Boolean(tooltip);\n\n const accessibilityProps = React.useMemo(() => (hasTooltip ? { 'aria-describedby': tooltipId } : {}), [hasTooltip, tooltipId]);\n\n return { tooltipId, hasTooltip, accessibilityProps };\n}\n", "'use client';\n\nimport * as React from 'react';\nimport { APPEARANCE_STORAGE_KEY } from './appearance-script.js';\n\n/**\n * Light and dark mode, decided before the first paint and kept on `<html>`.\n *\n * The server cannot know the visitor's mode, so `appearanceScript` runs synchronously in\n * `<head>`: the stored choice first, `prefers-color-scheme` otherwise. It writes the `light`\n * or `dark` class (which every token and component stylesheet already keys off) and\n * `data-appearance` on `<html>`. The root `<Theme>` keeps `appearance=\"inherit\"`, so it\n * writes no class of its own and `<html>` is the one source of truth. Nested themes and\n * portals inherit it from there.\n *\n * `useAppearance` is the client half: an external store over `<html>` and the same storage\n * key. It never mirrors the mode into React state that could disagree with the DOM.\n */\n\ntype AppearanceChoice = 'system' | 'light' | 'dark';\ntype ResolvedAppearance = 'light' | 'dark';\n\nconst listeners = new Set<() => void>();\n\nfunction emit() {\n for (const listener of listeners) listener();\n}\n\n// The last choice made on this page, for when storage is blocked and cannot hold it.\nlet pageChoice: AppearanceChoice = 'system';\n\nfunction readChoice(): AppearanceChoice {\n try {\n const value = globalThis.localStorage?.getItem(APPEARANCE_STORAGE_KEY);\n return value === 'light' || value === 'dark' ? value : 'system';\n } catch {\n return pageChoice;\n }\n}\n\nfunction systemAppearance(): ResolvedAppearance {\n return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';\n}\n\nfunction stamp(mode: ResolvedAppearance) {\n const root = document.documentElement;\n root.classList.remove('light', 'dark');\n root.classList.add(mode);\n root.setAttribute('data-appearance', mode);\n root.style.colorScheme = mode;\n}\n\nfunction readResolved(): ResolvedAppearance {\n const root = document.documentElement;\n if (root.classList.contains('dark') || root.classList.contains('dark-theme')) return 'dark';\n if (root.classList.contains('light') || root.classList.contains('light-theme')) return 'light';\n return systemAppearance();\n}\n\nlet snapshot: { choice: AppearanceChoice; resolved: ResolvedAppearance } | null = null;\n\nfunction getSnapshot() {\n const choice = readChoice();\n const resolved = readResolved();\n if (!snapshot || snapshot.choice !== choice || snapshot.resolved !== resolved) {\n snapshot = { choice, resolved };\n }\n return snapshot;\n}\n\nconst serverSnapshot = { choice: 'system' as AppearanceChoice, resolved: 'light' as ResolvedAppearance };\n\n// With no stored choice, follow the system as it changes.\nfunction onSystemChange() {\n if (readChoice() === 'system') {\n stamp(systemAppearance());\n emit();\n }\n}\n\n// Another tab changed the choice.\nfunction onStorage(event: StorageEvent) {\n if (event.key !== APPEARANCE_STORAGE_KEY) return;\n const choice = readChoice();\n stamp(choice === 'system' ? systemAppearance() : choice);\n emit();\n}\n\nlet media: MediaQueryList | null = null;\n\n// One set of window listeners for every subscriber, so one change stamps `<html>` once.\nfunction subscribe(listener: () => void) {\n if (listeners.size === 0) {\n media = window.matchMedia('(prefers-color-scheme: dark)');\n media.addEventListener('change', onSystemChange);\n window.addEventListener('storage', onStorage);\n }\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n if (listeners.size > 0) return;\n media?.removeEventListener('change', onSystemChange);\n media = null;\n window.removeEventListener('storage', onStorage);\n };\n}\n\n/** Applies a choice to `<html>` and stores it. The change applies even if storage is blocked. */\nfunction setAppearance(choice: AppearanceChoice) {\n pageChoice = choice;\n try {\n if (choice === 'system') localStorage.removeItem(APPEARANCE_STORAGE_KEY);\n else localStorage.setItem(APPEARANCE_STORAGE_KEY, choice);\n } catch {\n // Storage is blocked: the mode still changes for this page.\n }\n stamp(choice === 'system' ? systemAppearance() : choice);\n emit();\n}\n\n/**\n * The page's light or dark mode. `choice` is what the person picked (`system` when they\n * haven't); `appearance` is what is showing. On the server and in the first client render it\n * reports `system` / `light`, then corrects without a hydration mismatch.\n */\nfunction useAppearance() {\n const { choice, resolved } = React.useSyncExternalStore(subscribe, getSnapshot, () => serverSnapshot);\n return { choice, appearance: resolved, setAppearance } as const;\n}\n\nexport { useAppearance, setAppearance };\nexport type { AppearanceChoice, ResolvedAppearance };\n", "/**\n * The pre-paint half of light and dark mode (see `use-appearance.ts`). Kept in its own module\n * with no client directive, so a server component such as a Next.js root layout can import the\n * string and inline it in `<head>`.\n */\n\nconst APPEARANCE_STORAGE_KEY = 'kookie-appearance';\n\n/**\n * Inline this in `<head>`, before any stylesheet paints, e.g.\n * `<script dangerouslySetInnerHTML={{ __html: appearanceScript }} />`.\n *\n * Storage is read inside its own try/catch: browsers that block site data throw on access,\n * and that must not skip the stamp. `matchMedia` always answers.\n */\nconst appearanceScript = `(function () {\n var e = document.documentElement;\n var a = null;\n try { a = localStorage.getItem(${JSON.stringify(APPEARANCE_STORAGE_KEY)}); } catch (err) {}\n var dark = a === \"light\" ? false : a === \"dark\" ? true : matchMedia(\"(prefers-color-scheme: dark)\").matches;\n var mode = dark ? \"dark\" : \"light\";\n e.classList.remove(\"light\", \"dark\");\n e.classList.add(mode);\n e.setAttribute(\"data-appearance\", mode);\n e.style.colorScheme = mode;\n})();`;\n\nexport { appearanceScript, APPEARANCE_STORAGE_KEY };\n"],
|
|
5
|
-
"mappings": ";0jBAAA,IAAAA,EAAA,GAAAC,EAAAD,EAAA,4BAAAE,EAAA,qBAAAC,EAAA,kBAAAC,EAAA,kBAAAC,EAAA,gCAAAC,EAAA,sBAAAC,IAAA,eAAAC,EAAAR,GCAA,IAAAS,EAAuB,oBAKnBC,EAAa,EACbC,EAAgC,KAE9BC,EAAUC,GAAoB,CAIpC,EAEA,SAASC,GAAsB,CAI7B,IAAMC,EAAU,IAAI,IAEdC,EAAQ,CAACC,EAAgBC,IAAkB,CAC/C,IAAMC,EAAK,WAAW,IAAM,CAC1BJ,EAAQ,OAAOI,CAAE,EACjBF,EAAG,CACL,EAAGC,CAAK,EACRH,EAAQ,IAAII,CAAE,CAChB,EAEMC,EAAU,IAAM,SAAS,KAAK,MAAM,gBAAkB,OAEtDC,EAAe,IAAe,CAElC,IAAMC,EAAa,EACjB,SAAS,cAAc,6EAA6E,EAKhGC,EAAmB,EACvB,SAAS,cACP,kHACF,EAGF,OAAOD,GAAcC,CACvB,EAIMC,EAAc,IAAM,CACpBJ,EAAQ,GAAK,CAACC,EAAa,IAC7BT,EAAO,0DAA0D,EACjE,SAAS,KAAK,MAAM,cAAgB,GAExC,EAGMa,EAAmBP,GAAkB,CACrCE,EAAQ,GAAGJ,EAAMQ,EAAaN,CAAK,CACzC,EAEMQ,EAAmBC,GAAiB,CACxC,IAAMC,EAASD,EAAM,OACjBC,GAAU,CAACA,EAAO,QAAQ,4EAA4E,GACxGH,EAAgB,GAAG,CAEvB,EAEMI,EAAeF,GAAyB,CACxCA,EAAM,MAAQ,UAChBF,EAAgB,GAAG,CAEvB,EAEMK,EAAgB,IAAM,CAC1BL,EAAgB,EAAE,CACpB,EAEA,SAAS,iBAAiB,QAASC,EAAiB,EAAI,EACxD,SAAS,iBAAiB,UAAWG,EAAa,EAAI,EACtD,SAAS,iBAAiB,YAAaC,EAAe,EAAI,EAC1D,SAAS,iBAAiB,gBAAiBA,EAAe,EAAI,EAC9D,SAAS,iBAAiB,eAAgBA,EAAe,EAAI,EAI7D,IAAMC,EAAW,IAAI,iBAAiB,IAAM,CAC1CN,EAAgB,CAAC,CACnB,CAAC,EACDM,EAAS,QAAQ,SAAS,KAAM,CAAE,UAAW,GAAM,WAAY,GAAM,gBAAiB,CAAC,OAAO,CAAE,CAAC,EAGjG,IAAMC,EAAa,YAAYR,EAAa,GAAI,EAGhD,OAAAC,EAAgB,GAAG,EAKZ,IAAM,CACX,cAAcO,CAAU,EAExBjB,EAAQ,QAAQ,YAAY,EAC5BA,EAAQ,MAAM,EAEdgB,EAAS,WAAW,EAEpB,SAAS,oBAAoB,QAASL,EAAiB,EAAI,EAC3D,SAAS,oBAAoB,UAAWG,EAAa,EAAI,EACzD,SAAS,oBAAoB,YAAaC,EAAe,EAAI,EAC7D,SAAS,oBAAoB,gBAAiBA,EAAe,EAAI,EACjE,SAAS,oBAAoB,eAAgBA,EAAe,EAAI,CAClE,CACF,
|
|
6
|
-
"names": ["hooks_exports", "__export", "APPEARANCE_STORAGE_KEY", "appearanceScript", "setAppearance", "useAppearance", "useBodyPointerEventsCleanup", "useTooltipWrapper", "__toCommonJS", "React", "mountCount", "teardown", "devLog", "message", "install", "pending", "defer", "fn", "delay", "id", "isStuck", "hasOpenModal", "hasDialogs", "hasRadixOverlays", "safeCleanup", "scheduleCleanup", "onDocumentClick", "event", "target", "onEscapeKey", "onInteraction", "observer", "intervalId", "useBodyPointerEventsCleanup", "React", "useTooltipWrapper", "tooltip", "tooltipId", "hasTooltip", "accessibilityProps", "React", "APPEARANCE_STORAGE_KEY", "appearanceScript", "listeners", "emit", "listener", "pageChoice", "readChoice", "value", "APPEARANCE_STORAGE_KEY", "systemAppearance", "stamp", "mode", "root", "readResolved", "snapshot", "getSnapshot", "choice", "resolved", "serverSnapshot", "onSystemChange", "onStorage", "event", "media", "subscribe", "setAppearance", "useAppearance"]
|
|
4
|
+
"sourcesContent": ["export { useBodyPointerEventsCleanup } from './use-body-pointer-events-cleanup.js';\nexport { useTooltipWrapper } from './use-tooltip-wrapper.js';\nexport { useAppearance, setAppearance } from './use-appearance.js';\nexport { appearanceScript, APPEARANCE_STORAGE_KEY } from './appearance-script.js';\nexport type { AppearanceChoice, ResolvedAppearance } from './use-appearance.js';\n", "import * as React from 'react';\n\n// How many overlays currently use the hook. The listeners are shared: the first mount installs\n// them and the last unmount removes them, so closing one overlay never strips the fallback from\n// another that is still mounted.\nlet mountCount = 0;\nlet teardown: (() => void) | null = null;\n\nconst devLog = (message: string) => {\n if (process.env.NODE_ENV !== 'production') {\n console.log(message);\n }\n};\n\nfunction install(): () => void {\n // Deferred cleanups are tracked so teardown can cancel the ones still queued. A timer that\n // outlives the document reaches for `document.body` after it is gone; under jsdom that surfaces\n // as an unhandled ReferenceError attributed to whichever test happened to be running.\n const pending = new Set<ReturnType<typeof setTimeout>>();\n\n const defer = (fn: () => void, delay: number) => {\n const id = setTimeout(() => {\n pending.delete(id);\n fn();\n }, delay);\n pending.add(id);\n };\n\n const isStuck = () => document.body.style.pointerEvents === 'none';\n\n const hasOpenModal = (): boolean => {\n // Open dialogs and alert dialogs\n const hasDialogs = Boolean(\n document.querySelector('[role=\"dialog\"][aria-modal=\"true\"], [role=\"alertdialog\"][aria-modal=\"true\"]'),\n );\n\n // Any Radix overlay that is still mounted. Only overlays count: an open Accordion or\n // Collapsible also carries data-state=\"open\" and must not keep a stuck page blocked.\n const hasRadixOverlays = Boolean(\n document.querySelector(\n '[data-radix-popper-content-wrapper], [role=\"dialog\"][data-state=\"open\"], [role=\"alertdialog\"][data-state=\"open\"]',\n ),\n );\n\n return hasDialogs || hasRadixOverlays;\n };\n\n // Only clears pointer-events when no modal is open. Scroll-lock attributes belong to\n // react-remove-scroll and are never touched here.\n const safeCleanup = () => {\n if (isStuck() && !hasOpenModal()) {\n devLog('[KookieUI] Cleaning stuck pointer-events: none from body');\n document.body.style.pointerEvents = '';\n }\n };\n\n // Every trigger queues a check only while the body is actually blocked.\n const scheduleCleanup = (delay: number) => {\n if (isStuck()) defer(safeCleanup, delay);\n };\n\n const onDocumentClick = (event: Event) => {\n const target = event.target as Element;\n if (target && !target.closest('[role=\"dialog\"], [role=\"alertdialog\"], [data-radix-popper-content-wrapper]')) {\n scheduleCleanup(100);\n }\n };\n\n const onEscapeKey = (event: KeyboardEvent) => {\n if (event.key === 'Escape') {\n scheduleCleanup(200);\n }\n };\n\n const onInteraction = () => {\n scheduleCleanup(50);\n };\n\n document.addEventListener('click', onDocumentClick, true);\n document.addEventListener('keydown', onEscapeKey, true);\n document.addEventListener('pointerup', onInteraction, true);\n document.addEventListener('transitionend', onInteraction, true);\n document.addEventListener('animationend', onInteraction, true);\n\n // Portals mount and unmount as direct children of body, and the stuck value lives in body's\n // style, so body's own children and style are all that need watching.\n const observer = new MutationObserver(() => {\n scheduleCleanup(0);\n });\n observer.observe(document.body, { childList: true, attributes: true, attributeFilter: ['style'] });\n\n // Fallback periodic cleanup\n const intervalId = setInterval(safeCleanup, 1000);\n\n // Initial cleanup\n scheduleCleanup(100);\n\n // Tear everything down together. The observer is the important one: it fires while React\n // unmounts the tree, so leaving it connected keeps queueing work against a document that is\n // about to go away.\n return () => {\n clearInterval(intervalId);\n\n pending.forEach(clearTimeout);\n pending.clear();\n\n observer.disconnect();\n\n document.removeEventListener('click', onDocumentClick, true);\n document.removeEventListener('keydown', onEscapeKey, true);\n document.removeEventListener('pointerup', onInteraction, true);\n document.removeEventListener('transitionend', onInteraction, true);\n document.removeEventListener('animationend', onInteraction, true);\n };\n}\n\n/**\n * Clears a `pointer-events: none` that Radix left on `document.body`, for a dialog that has just\n * closed. Leaves it alone while another modal is still open: that modal needs the page behind it\n * to stay blocked, and it clears the value itself when it closes.\n */\nexport function releaseBodyPointerEvents() {\n if (typeof document === 'undefined') return;\n const stillOpen = document.querySelector('[role=\"dialog\"][data-state=\"open\"], [role=\"alertdialog\"][data-state=\"open\"]');\n if (stillOpen) return;\n document.body.style.pointerEvents = '';\n}\n\n/**\n * Hook to cleanup stuck pointer-events: none on document.body\n *\n * This addresses an issue where react-remove-scroll (used by Radix UI Dialog)\n * sometimes fails to restore body pointer-events after dialog closes,\n * leaving the page unclickable. It never clears the value while a modal is open.\n */\nexport function useBodyPointerEventsCleanup() {\n React.useEffect(() => {\n if (typeof document === 'undefined') return;\n\n mountCount += 1;\n if (mountCount === 1) {\n teardown = install();\n }\n\n return () => {\n mountCount -= 1;\n if (mountCount === 0) {\n teardown?.();\n teardown = null;\n }\n };\n }, []);\n}\n", "import * as React from 'react';\n\n/**\n * Hook for managing tooltip accessibility props and conditional rendering.\n * Encapsulates tooltip ID generation and aria-describedby binding.\n *\n * @param tooltip - The tooltip content (if any)\n * @returns Object containing tooltipId, hasTooltip flag, and accessibility props\n *\n * @example\n * ```tsx\n * const { tooltipId, hasTooltip, accessibilityProps } = useTooltipWrapper(tooltip);\n *\n * const button = <button {...accessibilityProps}>Click me</button>;\n *\n * if (!hasTooltip) return button;\n *\n * return <Tooltip id={tooltipId} content={tooltip}>{button}</Tooltip>;\n * ```\n */\nexport function useTooltipWrapper(tooltip: React.ReactNode) {\n const tooltipId = React.useId();\n const hasTooltip = Boolean(tooltip);\n\n const accessibilityProps = React.useMemo(() => (hasTooltip ? { 'aria-describedby': tooltipId } : {}), [hasTooltip, tooltipId]);\n\n return { tooltipId, hasTooltip, accessibilityProps };\n}\n", "'use client';\n\nimport * as React from 'react';\nimport { APPEARANCE_STORAGE_KEY } from './appearance-script.js';\n\n/**\n * Light and dark mode, decided before the first paint and kept on `<html>`.\n *\n * The server cannot know the visitor's mode, so `appearanceScript` runs synchronously in\n * `<head>`: the stored choice first, `prefers-color-scheme` otherwise. It writes the `light`\n * or `dark` class (which every token and component stylesheet already keys off) and\n * `data-appearance` on `<html>`. The root `<Theme>` keeps `appearance=\"inherit\"`, so it\n * writes no class of its own and `<html>` is the one source of truth. Nested themes and\n * portals inherit it from there.\n *\n * `useAppearance` is the client half: an external store over `<html>` and the same storage\n * key. It never mirrors the mode into React state that could disagree with the DOM.\n */\n\ntype AppearanceChoice = 'system' | 'light' | 'dark';\ntype ResolvedAppearance = 'light' | 'dark';\n\nconst listeners = new Set<() => void>();\n\nfunction emit() {\n for (const listener of listeners) listener();\n}\n\n// The last choice made on this page, for when storage is blocked and cannot hold it.\nlet pageChoice: AppearanceChoice = 'system';\n\nfunction readChoice(): AppearanceChoice {\n try {\n const value = globalThis.localStorage?.getItem(APPEARANCE_STORAGE_KEY);\n return value === 'light' || value === 'dark' ? value : 'system';\n } catch {\n return pageChoice;\n }\n}\n\nfunction systemAppearance(): ResolvedAppearance {\n return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';\n}\n\nfunction stamp(mode: ResolvedAppearance) {\n const root = document.documentElement;\n root.classList.remove('light', 'dark');\n root.classList.add(mode);\n root.setAttribute('data-appearance', mode);\n root.style.colorScheme = mode;\n}\n\nfunction readResolved(): ResolvedAppearance {\n const root = document.documentElement;\n if (root.classList.contains('dark') || root.classList.contains('dark-theme')) return 'dark';\n if (root.classList.contains('light') || root.classList.contains('light-theme')) return 'light';\n return systemAppearance();\n}\n\nlet snapshot: { choice: AppearanceChoice; resolved: ResolvedAppearance } | null = null;\n\nfunction getSnapshot() {\n const choice = readChoice();\n const resolved = readResolved();\n if (!snapshot || snapshot.choice !== choice || snapshot.resolved !== resolved) {\n snapshot = { choice, resolved };\n }\n return snapshot;\n}\n\nconst serverSnapshot = { choice: 'system' as AppearanceChoice, resolved: 'light' as ResolvedAppearance };\n\n// With no stored choice, follow the system as it changes.\nfunction onSystemChange() {\n if (readChoice() === 'system') {\n stamp(systemAppearance());\n emit();\n }\n}\n\n// Another tab changed the choice.\nfunction onStorage(event: StorageEvent) {\n if (event.key !== APPEARANCE_STORAGE_KEY) return;\n const choice = readChoice();\n stamp(choice === 'system' ? systemAppearance() : choice);\n emit();\n}\n\nlet media: MediaQueryList | null = null;\n\n// One set of window listeners for every subscriber, so one change stamps `<html>` once.\nfunction subscribe(listener: () => void) {\n if (listeners.size === 0) {\n media = window.matchMedia('(prefers-color-scheme: dark)');\n media.addEventListener('change', onSystemChange);\n window.addEventListener('storage', onStorage);\n }\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n if (listeners.size > 0) return;\n media?.removeEventListener('change', onSystemChange);\n media = null;\n window.removeEventListener('storage', onStorage);\n };\n}\n\n/** Applies a choice to `<html>` and stores it. The change applies even if storage is blocked. */\nfunction setAppearance(choice: AppearanceChoice) {\n pageChoice = choice;\n try {\n if (choice === 'system') localStorage.removeItem(APPEARANCE_STORAGE_KEY);\n else localStorage.setItem(APPEARANCE_STORAGE_KEY, choice);\n } catch {\n // Storage is blocked: the mode still changes for this page.\n }\n stamp(choice === 'system' ? systemAppearance() : choice);\n emit();\n}\n\n/**\n * The page's light or dark mode. `choice` is what the person picked (`system` when they\n * haven't); `appearance` is what is showing. On the server and in the first client render it\n * reports `system` / `light`, then corrects without a hydration mismatch.\n */\nfunction useAppearance() {\n const { choice, resolved } = React.useSyncExternalStore(subscribe, getSnapshot, () => serverSnapshot);\n return { choice, appearance: resolved, setAppearance } as const;\n}\n\nexport { useAppearance, setAppearance };\nexport type { AppearanceChoice, ResolvedAppearance };\n", "/**\n * The pre-paint half of light and dark mode (see `use-appearance.ts`). Kept in its own module\n * with no client directive, so a server component such as a Next.js root layout can import the\n * string and inline it in `<head>`.\n */\n\nconst APPEARANCE_STORAGE_KEY = 'kookie-appearance';\n\n/**\n * Inline this in `<head>`, before any stylesheet paints, e.g.\n * `<script dangerouslySetInnerHTML={{ __html: appearanceScript }} />`.\n *\n * Storage is read inside its own try/catch: browsers that block site data throw on access,\n * and that must not skip the stamp. `matchMedia` always answers.\n */\nconst appearanceScript = `(function () {\n var e = document.documentElement;\n var a = null;\n try { a = localStorage.getItem(${JSON.stringify(APPEARANCE_STORAGE_KEY)}); } catch (err) {}\n var dark = a === \"light\" ? false : a === \"dark\" ? true : matchMedia(\"(prefers-color-scheme: dark)\").matches;\n var mode = dark ? \"dark\" : \"light\";\n e.classList.remove(\"light\", \"dark\");\n e.classList.add(mode);\n e.setAttribute(\"data-appearance\", mode);\n e.style.colorScheme = mode;\n})();`;\n\nexport { appearanceScript, APPEARANCE_STORAGE_KEY };\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["hooks_exports", "__export", "APPEARANCE_STORAGE_KEY", "appearanceScript", "setAppearance", "useAppearance", "useBodyPointerEventsCleanup", "useTooltipWrapper", "__toCommonJS", "React", "mountCount", "teardown", "devLog", "message", "install", "pending", "defer", "fn", "delay", "id", "isStuck", "hasOpenModal", "hasDialogs", "hasRadixOverlays", "safeCleanup", "scheduleCleanup", "onDocumentClick", "event", "target", "onEscapeKey", "onInteraction", "observer", "intervalId", "useBodyPointerEventsCleanup", "mountCount", "teardown", "install", "React", "useTooltipWrapper", "tooltip", "tooltipId", "hasTooltip", "accessibilityProps", "React", "APPEARANCE_STORAGE_KEY", "appearanceScript", "listeners", "emit", "listener", "pageChoice", "readChoice", "value", "APPEARANCE_STORAGE_KEY", "systemAppearance", "stamp", "mode", "root", "readResolved", "snapshot", "getSnapshot", "choice", "resolved", "serverSnapshot", "onSystemChange", "onStorage", "event", "media", "subscribe", "setAppearance", "useAppearance"]
|
|
7
7
|
}
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Clears a `pointer-events: none` that Radix left on `document.body`, for a dialog that has just
|
|
3
|
+
* closed. Leaves it alone while another modal is still open: that modal needs the page behind it
|
|
4
|
+
* to stay blocked, and it clears the value itself when it closes.
|
|
5
|
+
*/
|
|
6
|
+
export declare function releaseBodyPointerEvents(): void;
|
|
1
7
|
/**
|
|
2
8
|
* Hook to cleanup stuck pointer-events: none on document.body
|
|
3
9
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-body-pointer-events-cleanup.d.ts","sourceRoot":"","sources":["../../../src/hooks/use-body-pointer-events-cleanup.ts"],"names":[],"mappings":"AAoHA;;;;;;GAMG;AACH,wBAAgB,2BAA2B,SAiB1C"}
|
|
1
|
+
{"version":3,"file":"use-body-pointer-events-cleanup.d.ts","sourceRoot":"","sources":["../../../src/hooks/use-body-pointer-events-cleanup.ts"],"names":[],"mappings":"AAoHA;;;;GAIG;AACH,wBAAgB,wBAAwB,SAKvC;AAED;;;;;;GAMG;AACH,wBAAgB,2BAA2B,SAiB1C"}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
"use strict";var b=Object.create;var u=Object.defineProperty;var L=Object.getOwnPropertyDescriptor;var k=Object.getOwnPropertyNames;var w=Object.getPrototypeOf,h=Object.prototype.hasOwnProperty;var C=(e,t)=>{for(var n in t)u(e,n,{get:t[n],enumerable:!0})},
|
|
2
|
+
"use strict";var b=Object.create;var u=Object.defineProperty;var L=Object.getOwnPropertyDescriptor;var k=Object.getOwnPropertyNames;var w=Object.getPrototypeOf,h=Object.prototype.hasOwnProperty;var C=(e,t)=>{for(var n in t)u(e,n,{get:t[n],enumerable:!0})},y=(e,t,n,s)=>{if(t&&typeof t=="object"||typeof t=="function")for(let r of k(t))!h.call(e,r)&&r!==n&&u(e,r,{get:()=>t[r],enumerable:!(s=L(t,r))||s.enumerable});return e};var O=(e,t,n)=>(n=e!=null?b(w(e)):{},y(t||!e||!e.__esModule?u(n,"default",{value:e,enumerable:!0}):n,e)),x=e=>y(u({},"__esModule",{value:!0}),e);var T={};C(T,{releaseBodyPointerEvents:()=>B,useBodyPointerEventsCleanup:()=>R});module.exports=x(T);var E=O(require("react")),l=0,c=null,I=e=>{};function S(){let e=new Set,t=(o,i)=>{let f=setTimeout(()=>{e.delete(f),o()},i);e.add(f)},n=()=>document.body.style.pointerEvents==="none",s=()=>{let o=!!document.querySelector('[role="dialog"][aria-modal="true"], [role="alertdialog"][aria-modal="true"]'),i=!!document.querySelector('[data-radix-popper-content-wrapper], [role="dialog"][data-state="open"], [role="alertdialog"][data-state="open"]');return o||i},r=()=>{n()&&!s()&&(I("[KookieUI] Cleaning stuck pointer-events: none from body"),document.body.style.pointerEvents="")},d=o=>{n()&&t(r,o)},m=o=>{let i=o.target;i&&!i.closest('[role="dialog"], [role="alertdialog"], [data-radix-popper-content-wrapper]')&&d(100)},p=o=>{o.key==="Escape"&&d(200)},a=()=>{d(50)};document.addEventListener("click",m,!0),document.addEventListener("keydown",p,!0),document.addEventListener("pointerup",a,!0),document.addEventListener("transitionend",a,!0),document.addEventListener("animationend",a,!0);let v=new MutationObserver(()=>{d(0)});v.observe(document.body,{childList:!0,attributes:!0,attributeFilter:["style"]});let g=setInterval(r,1e3);return d(100),()=>{clearInterval(g),e.forEach(clearTimeout),e.clear(),v.disconnect(),document.removeEventListener("click",m,!0),document.removeEventListener("keydown",p,!0),document.removeEventListener("pointerup",a,!0),document.removeEventListener("transitionend",a,!0),document.removeEventListener("animationend",a,!0)}}function B(){typeof document>"u"||document.querySelector('[role="dialog"][data-state="open"], [role="alertdialog"][data-state="open"]')||(document.body.style.pointerEvents="")}function R(){E.useEffect(()=>{if(!(typeof document>"u"))return l+=1,l===1&&(c=S()),()=>{l-=1,l===0&&(c?.(),c=null)}},[])}
|
|
3
3
|
//# sourceMappingURL=use-body-pointer-events-cleanup.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/hooks/use-body-pointer-events-cleanup.ts"],
|
|
4
|
-
"sourcesContent": ["import * as React from 'react';\n\n// How many overlays currently use the hook. The listeners are shared: the first mount installs\n// them and the last unmount removes them, so closing one overlay never strips the fallback from\n// another that is still mounted.\nlet mountCount = 0;\nlet teardown: (() => void) | null = null;\n\nconst devLog = (message: string) => {\n if (process.env.NODE_ENV !== 'production') {\n console.log(message);\n }\n};\n\nfunction install(): () => void {\n // Deferred cleanups are tracked so teardown can cancel the ones still queued. A timer that\n // outlives the document reaches for `document.body` after it is gone; under jsdom that surfaces\n // as an unhandled ReferenceError attributed to whichever test happened to be running.\n const pending = new Set<ReturnType<typeof setTimeout>>();\n\n const defer = (fn: () => void, delay: number) => {\n const id = setTimeout(() => {\n pending.delete(id);\n fn();\n }, delay);\n pending.add(id);\n };\n\n const isStuck = () => document.body.style.pointerEvents === 'none';\n\n const hasOpenModal = (): boolean => {\n // Open dialogs and alert dialogs\n const hasDialogs = Boolean(\n document.querySelector('[role=\"dialog\"][aria-modal=\"true\"], [role=\"alertdialog\"][aria-modal=\"true\"]'),\n );\n\n // Any Radix overlay that is still mounted. Only overlays count: an open Accordion or\n // Collapsible also carries data-state=\"open\" and must not keep a stuck page blocked.\n const hasRadixOverlays = Boolean(\n document.querySelector(\n '[data-radix-popper-content-wrapper], [role=\"dialog\"][data-state=\"open\"], [role=\"alertdialog\"][data-state=\"open\"]',\n ),\n );\n\n return hasDialogs || hasRadixOverlays;\n };\n\n // Only clears pointer-events when no modal is open. Scroll-lock attributes belong to\n // react-remove-scroll and are never touched here.\n const safeCleanup = () => {\n if (isStuck() && !hasOpenModal()) {\n devLog('[KookieUI] Cleaning stuck pointer-events: none from body');\n document.body.style.pointerEvents = '';\n }\n };\n\n // Every trigger queues a check only while the body is actually blocked.\n const scheduleCleanup = (delay: number) => {\n if (isStuck()) defer(safeCleanup, delay);\n };\n\n const onDocumentClick = (event: Event) => {\n const target = event.target as Element;\n if (target && !target.closest('[role=\"dialog\"], [role=\"alertdialog\"], [data-radix-popper-content-wrapper]')) {\n scheduleCleanup(100);\n }\n };\n\n const onEscapeKey = (event: KeyboardEvent) => {\n if (event.key === 'Escape') {\n scheduleCleanup(200);\n }\n };\n\n const onInteraction = () => {\n scheduleCleanup(50);\n };\n\n document.addEventListener('click', onDocumentClick, true);\n document.addEventListener('keydown', onEscapeKey, true);\n document.addEventListener('pointerup', onInteraction, true);\n document.addEventListener('transitionend', onInteraction, true);\n document.addEventListener('animationend', onInteraction, true);\n\n // Portals mount and unmount as direct children of body, and the stuck value lives in body's\n // style, so body's own children and style are all that need watching.\n const observer = new MutationObserver(() => {\n scheduleCleanup(0);\n });\n observer.observe(document.body, { childList: true, attributes: true, attributeFilter: ['style'] });\n\n // Fallback periodic cleanup\n const intervalId = setInterval(safeCleanup, 1000);\n\n // Initial cleanup\n scheduleCleanup(100);\n\n // Tear everything down together. The observer is the important one: it fires while React\n // unmounts the tree, so leaving it connected keeps queueing work against a document that is\n // about to go away.\n return () => {\n clearInterval(intervalId);\n\n pending.forEach(clearTimeout);\n pending.clear();\n\n observer.disconnect();\n\n document.removeEventListener('click', onDocumentClick, true);\n document.removeEventListener('keydown', onEscapeKey, true);\n document.removeEventListener('pointerup', onInteraction, true);\n document.removeEventListener('transitionend', onInteraction, true);\n document.removeEventListener('animationend', onInteraction, true);\n };\n}\n\n/**\n * Hook to cleanup stuck pointer-events: none on document.body\n *\n * This addresses an issue where react-remove-scroll (used by Radix UI Dialog)\n * sometimes fails to restore body pointer-events after dialog closes,\n * leaving the page unclickable. It never clears the value while a modal is open.\n */\nexport function useBodyPointerEventsCleanup() {\n React.useEffect(() => {\n if (typeof document === 'undefined') return;\n\n mountCount += 1;\n if (mountCount === 1) {\n teardown = install();\n }\n\n return () => {\n mountCount -= 1;\n if (mountCount === 0) {\n teardown?.();\n teardown = null;\n }\n };\n }, []);\n}\n"],
|
|
5
|
-
"mappings": ";0jBAAA,IAAAA,EAAA,GAAAC,EAAAD,EAAA,
|
|
6
|
-
"names": ["use_body_pointer_events_cleanup_exports", "__export", "useBodyPointerEventsCleanup", "__toCommonJS", "React", "mountCount", "teardown", "devLog", "message", "install", "pending", "defer", "fn", "delay", "id", "isStuck", "hasOpenModal", "hasDialogs", "hasRadixOverlays", "safeCleanup", "scheduleCleanup", "onDocumentClick", "event", "target", "onEscapeKey", "onInteraction", "observer", "intervalId"]
|
|
4
|
+
"sourcesContent": ["import * as React from 'react';\n\n// How many overlays currently use the hook. The listeners are shared: the first mount installs\n// them and the last unmount removes them, so closing one overlay never strips the fallback from\n// another that is still mounted.\nlet mountCount = 0;\nlet teardown: (() => void) | null = null;\n\nconst devLog = (message: string) => {\n if (process.env.NODE_ENV !== 'production') {\n console.log(message);\n }\n};\n\nfunction install(): () => void {\n // Deferred cleanups are tracked so teardown can cancel the ones still queued. A timer that\n // outlives the document reaches for `document.body` after it is gone; under jsdom that surfaces\n // as an unhandled ReferenceError attributed to whichever test happened to be running.\n const pending = new Set<ReturnType<typeof setTimeout>>();\n\n const defer = (fn: () => void, delay: number) => {\n const id = setTimeout(() => {\n pending.delete(id);\n fn();\n }, delay);\n pending.add(id);\n };\n\n const isStuck = () => document.body.style.pointerEvents === 'none';\n\n const hasOpenModal = (): boolean => {\n // Open dialogs and alert dialogs\n const hasDialogs = Boolean(\n document.querySelector('[role=\"dialog\"][aria-modal=\"true\"], [role=\"alertdialog\"][aria-modal=\"true\"]'),\n );\n\n // Any Radix overlay that is still mounted. Only overlays count: an open Accordion or\n // Collapsible also carries data-state=\"open\" and must not keep a stuck page blocked.\n const hasRadixOverlays = Boolean(\n document.querySelector(\n '[data-radix-popper-content-wrapper], [role=\"dialog\"][data-state=\"open\"], [role=\"alertdialog\"][data-state=\"open\"]',\n ),\n );\n\n return hasDialogs || hasRadixOverlays;\n };\n\n // Only clears pointer-events when no modal is open. Scroll-lock attributes belong to\n // react-remove-scroll and are never touched here.\n const safeCleanup = () => {\n if (isStuck() && !hasOpenModal()) {\n devLog('[KookieUI] Cleaning stuck pointer-events: none from body');\n document.body.style.pointerEvents = '';\n }\n };\n\n // Every trigger queues a check only while the body is actually blocked.\n const scheduleCleanup = (delay: number) => {\n if (isStuck()) defer(safeCleanup, delay);\n };\n\n const onDocumentClick = (event: Event) => {\n const target = event.target as Element;\n if (target && !target.closest('[role=\"dialog\"], [role=\"alertdialog\"], [data-radix-popper-content-wrapper]')) {\n scheduleCleanup(100);\n }\n };\n\n const onEscapeKey = (event: KeyboardEvent) => {\n if (event.key === 'Escape') {\n scheduleCleanup(200);\n }\n };\n\n const onInteraction = () => {\n scheduleCleanup(50);\n };\n\n document.addEventListener('click', onDocumentClick, true);\n document.addEventListener('keydown', onEscapeKey, true);\n document.addEventListener('pointerup', onInteraction, true);\n document.addEventListener('transitionend', onInteraction, true);\n document.addEventListener('animationend', onInteraction, true);\n\n // Portals mount and unmount as direct children of body, and the stuck value lives in body's\n // style, so body's own children and style are all that need watching.\n const observer = new MutationObserver(() => {\n scheduleCleanup(0);\n });\n observer.observe(document.body, { childList: true, attributes: true, attributeFilter: ['style'] });\n\n // Fallback periodic cleanup\n const intervalId = setInterval(safeCleanup, 1000);\n\n // Initial cleanup\n scheduleCleanup(100);\n\n // Tear everything down together. The observer is the important one: it fires while React\n // unmounts the tree, so leaving it connected keeps queueing work against a document that is\n // about to go away.\n return () => {\n clearInterval(intervalId);\n\n pending.forEach(clearTimeout);\n pending.clear();\n\n observer.disconnect();\n\n document.removeEventListener('click', onDocumentClick, true);\n document.removeEventListener('keydown', onEscapeKey, true);\n document.removeEventListener('pointerup', onInteraction, true);\n document.removeEventListener('transitionend', onInteraction, true);\n document.removeEventListener('animationend', onInteraction, true);\n };\n}\n\n/**\n * Clears a `pointer-events: none` that Radix left on `document.body`, for a dialog that has just\n * closed. Leaves it alone while another modal is still open: that modal needs the page behind it\n * to stay blocked, and it clears the value itself when it closes.\n */\nexport function releaseBodyPointerEvents() {\n if (typeof document === 'undefined') return;\n const stillOpen = document.querySelector('[role=\"dialog\"][data-state=\"open\"], [role=\"alertdialog\"][data-state=\"open\"]');\n if (stillOpen) return;\n document.body.style.pointerEvents = '';\n}\n\n/**\n * Hook to cleanup stuck pointer-events: none on document.body\n *\n * This addresses an issue where react-remove-scroll (used by Radix UI Dialog)\n * sometimes fails to restore body pointer-events after dialog closes,\n * leaving the page unclickable. It never clears the value while a modal is open.\n */\nexport function useBodyPointerEventsCleanup() {\n React.useEffect(() => {\n if (typeof document === 'undefined') return;\n\n mountCount += 1;\n if (mountCount === 1) {\n teardown = install();\n }\n\n return () => {\n mountCount -= 1;\n if (mountCount === 0) {\n teardown?.();\n teardown = null;\n }\n };\n }, []);\n}\n"],
|
|
5
|
+
"mappings": ";0jBAAA,IAAAA,EAAA,GAAAC,EAAAD,EAAA,8BAAAE,EAAA,gCAAAC,IAAA,eAAAC,EAAAJ,GAAA,IAAAK,EAAuB,oBAKnBC,EAAa,EACbC,EAAgC,KAE9BC,EAAUC,GAAoB,CAIpC,EAEA,SAASC,GAAsB,CAI7B,IAAMC,EAAU,IAAI,IAEdC,EAAQ,CAACC,EAAgBC,IAAkB,CAC/C,IAAMC,EAAK,WAAW,IAAM,CAC1BJ,EAAQ,OAAOI,CAAE,EACjBF,EAAG,CACL,EAAGC,CAAK,EACRH,EAAQ,IAAII,CAAE,CAChB,EAEMC,EAAU,IAAM,SAAS,KAAK,MAAM,gBAAkB,OAEtDC,EAAe,IAAe,CAElC,IAAMC,EAAa,EACjB,SAAS,cAAc,6EAA6E,EAKhGC,EAAmB,EACvB,SAAS,cACP,kHACF,EAGF,OAAOD,GAAcC,CACvB,EAIMC,EAAc,IAAM,CACpBJ,EAAQ,GAAK,CAACC,EAAa,IAC7BT,EAAO,0DAA0D,EACjE,SAAS,KAAK,MAAM,cAAgB,GAExC,EAGMa,EAAmBP,GAAkB,CACrCE,EAAQ,GAAGJ,EAAMQ,EAAaN,CAAK,CACzC,EAEMQ,EAAmBC,GAAiB,CACxC,IAAMC,EAASD,EAAM,OACjBC,GAAU,CAACA,EAAO,QAAQ,4EAA4E,GACxGH,EAAgB,GAAG,CAEvB,EAEMI,EAAeF,GAAyB,CACxCA,EAAM,MAAQ,UAChBF,EAAgB,GAAG,CAEvB,EAEMK,EAAgB,IAAM,CAC1BL,EAAgB,EAAE,CACpB,EAEA,SAAS,iBAAiB,QAASC,EAAiB,EAAI,EACxD,SAAS,iBAAiB,UAAWG,EAAa,EAAI,EACtD,SAAS,iBAAiB,YAAaC,EAAe,EAAI,EAC1D,SAAS,iBAAiB,gBAAiBA,EAAe,EAAI,EAC9D,SAAS,iBAAiB,eAAgBA,EAAe,EAAI,EAI7D,IAAMC,EAAW,IAAI,iBAAiB,IAAM,CAC1CN,EAAgB,CAAC,CACnB,CAAC,EACDM,EAAS,QAAQ,SAAS,KAAM,CAAE,UAAW,GAAM,WAAY,GAAM,gBAAiB,CAAC,OAAO,CAAE,CAAC,EAGjG,IAAMC,EAAa,YAAYR,EAAa,GAAI,EAGhD,OAAAC,EAAgB,GAAG,EAKZ,IAAM,CACX,cAAcO,CAAU,EAExBjB,EAAQ,QAAQ,YAAY,EAC5BA,EAAQ,MAAM,EAEdgB,EAAS,WAAW,EAEpB,SAAS,oBAAoB,QAASL,EAAiB,EAAI,EAC3D,SAAS,oBAAoB,UAAWG,EAAa,EAAI,EACzD,SAAS,oBAAoB,YAAaC,EAAe,EAAI,EAC7D,SAAS,oBAAoB,gBAAiBA,EAAe,EAAI,EACjE,SAAS,oBAAoB,eAAgBA,EAAe,EAAI,CAClE,CACF,CAOO,SAASxB,GAA2B,CACrC,OAAO,SAAa,KACN,SAAS,cAAc,6EAA6E,IAEtH,SAAS,KAAK,MAAM,cAAgB,GACtC,CASO,SAASC,GAA8B,CACtC,YAAU,IAAM,CACpB,GAAI,SAAO,SAAa,KAExB,OAAAG,GAAc,EACVA,IAAe,IACjBC,EAAWG,EAAQ,GAGd,IAAM,CACXJ,GAAc,EACVA,IAAe,IACjBC,IAAW,EACXA,EAAW,KAEf,CACF,EAAG,CAAC,CAAC,CACP",
|
|
6
|
+
"names": ["use_body_pointer_events_cleanup_exports", "__export", "releaseBodyPointerEvents", "useBodyPointerEventsCleanup", "__toCommonJS", "React", "mountCount", "teardown", "devLog", "message", "install", "pending", "defer", "fn", "delay", "id", "isStuck", "hasOpenModal", "hasDialogs", "hasRadixOverlays", "safeCleanup", "scheduleCleanup", "onDocumentClick", "event", "target", "onEscapeKey", "onInteraction", "observer", "intervalId"]
|
|
7
7
|
}
|