@stackone/malachite 2.42.2 → 2.43.0
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.md +2 -2
- package/README.md +7 -0
- package/dist/components/ActionMenu.js +70 -70
- package/dist/components/ActionMenu.js.map +1 -1
- package/dist/components/CardStrip.js +179 -172
- package/dist/components/CardStrip.js.map +1 -1
- package/dist/components/Chat/ChatModelPicker.js +74 -73
- package/dist/components/Chat/ChatModelPicker.js.map +1 -1
- package/dist/components/Dropdown.js +432 -419
- package/dist/components/Dropdown.js.map +1 -1
- package/dist/components/MultiSelect.js +218 -213
- package/dist/components/MultiSelect.js.map +1 -1
- package/dist/components/Popover.js +102 -102
- package/dist/components/Popover.js.map +1 -1
- package/dist/components/QueryInput.js +65 -65
- package/dist/components/QueryInput.js.map +1 -1
- package/dist/components/SelectableCard.js +194 -168
- package/dist/components/SelectableCard.js.map +1 -1
- package/dist/components/focusRing.js +7 -6
- package/dist/components/focusRing.js.map +1 -1
- package/dist/components/overlayBehaviour.js +145 -94
- package/dist/components/overlayBehaviour.js.map +1 -1
- package/dist/components/overlayLayers.js +34 -0
- package/dist/components/overlayLayers.js.map +1 -0
- package/dist/components/portalOverlay.js.map +1 -1
- package/dist/index.d.ts +8 -0
- package/dist/tailwind.css.js +1 -1
- package/package.json +4 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"overlayBehaviour.js","sources":["../../src/components/overlayBehaviour.ts"],"sourcesContent":["import { useCallback, useEffect, useLayoutEffect, useRef, useSyncExternalStore } from 'react';\nimport { getDeepActiveElement } from './activeElement';\nimport { pushEscapeLayer } from './escapeLayers';\nimport { FOCUS_RING } from './focusRing';\nimport { useOwnedByPopover } from './popoverTree';\nimport { OVERLAY_Z_BASE, ensureElevatedOverlayStyles } from './portalOverlay';\nimport { lockBodyScroll } from './scrollLock';\nimport { leaveTopLayer, raiseToTopLayer } from './topLayer';\n\n/** Back/close button chrome, shared so the two overlays cannot drift apart. */\nexport const OVERLAY_ICON_BUTTON_CLASSES = `flex shrink-0 cursor-pointer items-center justify-center rounded-md border-0 bg-transparent p-[4px] text-[var(--malachite-text-secondary)] hover:text-[var(--malachite-text-primary)] transition-colors ${FOCUS_RING}`;\n\nexport const FOCUSABLE_SELECTOR =\n 'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex=\"-1\"])';\n\ntype PanelSource = () => HTMLElement | null;\n\n// LIFO like escapeLayers: with a listener per overlay the first-registered one wins, which\n// sent Tab from a dialog into the panel behind it.\nconst trapLayers: PanelSource[] = [];\n// Elevation belongs to the stack: a Modal left in the base band opens behind the panel that\n// elevated it.\nconst elevatedLayers = new Set<PanelSource>();\n// The shadow host per layer, so promotion can replay in stack order.\nconst hostSources = new Map<PanelSource, () => HTMLElement | null>();\nconst stackListeners = new Set<() => void>();\nconst NO_SUBSCRIPTION = () => {\n // Nothing to tear down: a closed overlay never subscribed.\n};\n\n// Top-layer paint order is entry order, so replay the whole stack bottom-up rather than letting\n// each overlay promote itself: those effects run in React tree order, which need not match.\nlet promoted: HTMLElement[] = [];\n\nconst syncTopLayer = (): void => {\n const wanted = elevatedLayers.size\n ? trapLayers\n .map((layer) => hostSources.get(layer)?.())\n .filter((host): host is HTMLElement => Boolean(host))\n : [];\n // Compared, not reapplied: this runs on every commit, because a host can appear a commit\n // after the layer that owns it, and re-entering the top layer unprompted costs a recalc.\n if (\n wanted.length === promoted.length &&\n wanted.every((host, index) => host === promoted[index])\n ) {\n return;\n }\n const stillWanted = new Set(wanted);\n for (const host of promoted) {\n if (!stillWanted.has(host)) {\n leaveTopLayer(host);\n }\n }\n for (const host of wanted) {\n raiseToTopLayer(host);\n }\n promoted = wanted;\n};\n\nconst notifyStackChanged = (): void => {\n for (const listener of stackListeners) {\n listener();\n }\n};\n\nconst subscribeToStack = (listener: () => void): (() => void) => {\n stackListeners.add(listener);\n return () => {\n stackListeners.delete(listener);\n };\n};\n\nconst handleTabKeyDown = (event: KeyboardEvent): void => {\n if (event.key !== 'Tab' || event.defaultPrevented) {\n return;\n }\n // Walk down rather than bail: React detaches refs before flushing the cleanup that pops\n // the layer, so the topmost can be registered-but-null and Tab would escape entirely.\n let panel: HTMLElement | null = null;\n for (let index = trapLayers.length - 1; index >= 0 && !panel; index -= 1) {\n panel = trapLayers[index]();\n }\n if (!panel) {\n return;\n }\n const focusables = Array.from(panel.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTOR));\n if (focusables.length === 0) {\n event.preventDefault();\n panel.focus();\n return;\n }\n const first = focusables[0];\n const last = focusables[focusables.length - 1];\n const active = getDeepActiveElement();\n const activeInPanel = active !== null && panel.contains(active);\n if (event.shiftKey) {\n if (!activeInPanel || active === first || active === panel) {\n event.preventDefault();\n last.focus();\n }\n } else if (!activeInPanel || active === last) {\n event.preventDefault();\n first.focus();\n }\n};\n\n// On `document` so it also catches focus that escaped behind the overlay. The trap reaches\n// focusables in the overlay's own shadow root only, so a popup portaled elsewhere (Dropdown,\n// ActionMenu, Tooltip) is outside it and Tab will pull focus back out of the popup.\nconst pushFocusTrapLayer = (getPanel: PanelSource, getHost: () => HTMLElement | null) => {\n if (trapLayers.length === 0) {\n document.addEventListener('keydown', handleTabKeyDown);\n }\n trapLayers.push(getPanel);\n hostSources.set(getPanel, getHost);\n notifyStackChanged();\n return () => {\n const index = trapLayers.lastIndexOf(getPanel);\n if (index !== -1) {\n trapLayers.splice(index, 1);\n }\n elevatedLayers.delete(getPanel);\n hostSources.delete(getPanel);\n const host = getHost();\n if (host) {\n leaveTopLayer(host);\n }\n if (trapLayers.length === 0) {\n document.removeEventListener('keydown', handleTabKeyDown);\n }\n notifyStackChanged();\n };\n};\n\n/**\n * Whether a SidePanel, Modal or CommandPalette is open anywhere, for host chrome that has to move out of the way.\n * Overlays portal, so the only other signal is this module's markers, which a host cannot read\n * without coupling to an internal. Dropdowns and tooltips do not count: they trap no focus.\n */\nexport const useHasOpenOverlay = (): boolean =>\n useSyncExternalStore(\n subscribeToStack,\n () => trapLayers.length > 0,\n () => false,\n );\n\nconst takeInitialFocus = (panel: HTMLElement, initialFocus: 'panel' | 'content'): void => {\n if (initialFocus === 'content') {\n const active = getDeepActiveElement();\n if (active && panel.contains(active)) {\n return;\n }\n (panel.querySelector<HTMLElement>(FOCUSABLE_SELECTOR) ?? panel).focus();\n return;\n }\n panel.focus();\n};\n\n/**\n * The modal-overlay behaviour shared by SidePanel, Modal and CommandPalette: ESC and the Tab focus trap\n * (topmost overlay only, so these may be stacked), background scroll lock, and focus moved\n * into the overlay on open then restored to the trigger on close.\n *\n * Returns the ref callback the dialog element must take, whether this overlay is the topmost\n * one — only that one may claim `aria-modal`, or a screen reader can still browse the overlay\n * underneath — the z-index its host must carry, which keeps paint order with the focus stack,\n * and whether it is elevated. Presentation otherwise stays with the calling component.\n */\nexport const useOverlayBehaviour = ({\n open,\n onClose,\n elevated = false,\n hostRef,\n initialFocus = 'panel',\n}: {\n open: boolean;\n onClose: () => void;\n elevated?: boolean;\n /** The shadow host, which the stack promotes into the top layer while elevated. */\n hostRef?: { current: HTMLElement | null };\n /** Where focus lands on open: the panel itself, or the content's own first control. */\n initialFocus?: 'panel' | 'content';\n}): {\n ref: (node: HTMLElement | null) => void;\n isTopmost: boolean;\n zIndex: number | undefined;\n elevated: boolean;\n} => {\n const panelRef = useRef<HTMLElement | null>(null);\n // One identity for the life of the instance: elevation registers it in the layout phase,\n // before the effect below pushes it onto the stack.\n const source = useRef<PanelSource>(() => panelRef.current).current;\n useOwnedByPopover(\n useCallback(() => hostRef?.current ?? panelRef.current, [hostRef]),\n open,\n );\n const previousFocusRef = useRef<HTMLElement | null>(null);\n const layerRef = useRef<PanelSource | null>(null);\n const initialFocusRef = useRef(initialFocus);\n\n // Ref so the ESC layer registers once per open, not on every inline-onClose change.\n const onCloseRef = useRef(onClose);\n useEffect(() => {\n onCloseRef.current = onClose;\n }, [onClose]);\n\n // Before the trap effect, so the trigger is recorded before focus moves in — and relies on\n // ShadowRootWrapper portaling a commit late, or this would capture the panel itself.\n useEffect(() => {\n if (!open) {\n return;\n }\n previousFocusRef.current = getDeepActiveElement();\n return () => {\n const previous = previousFocusRef.current;\n previousFocusRef.current = null;\n if (previous?.isConnected) {\n previous.focus();\n }\n };\n }, [open]);\n\n useEffect(() => {\n if (!open) {\n return;\n }\n layerRef.current = source;\n const popEscapeLayer = pushEscapeLayer(() => onCloseRef.current());\n const releaseScrollLock = lockBodyScroll();\n const popFocusTrapLayer = pushFocusTrapLayer(source, () => hostRef?.current ?? null);\n // Reopening inside the exit transition reuses the same node, so the ref callback\n // never fires again and nothing would take focus.\n if (panelRef.current) {\n takeInitialFocus(panelRef.current, initialFocusRef.current);\n }\n return () => {\n layerRef.current = null;\n popFocusTrapLayer();\n popEscapeLayer();\n releaseScrollLock();\n };\n }, [open, hostRef, source]);\n\n // Its own effect: re-pushing the trap layer to change elevation would move this overlay to\n // the top of the stack, handing it the Escape and Tab that belong to a dialog above it.\n // Layout, and declared above the sync below, so that sync sees it in the same commit.\n useLayoutEffect(() => {\n if (!open || !elevated) {\n return;\n }\n elevatedLayers.add(source);\n notifyStackChanged();\n return () => {\n elevatedLayers.delete(source);\n notifyStackChanged();\n };\n }, [open, elevated, source]);\n\n // Only an open overlay subscribes: a mounted-but-idle one must carry no at-rest cost,\n // and its snapshot is a constant `false` regardless of what the stack does.\n const subscribe = useCallback(\n (onStackChange: () => void) => (open ? subscribeToStack(onStackChange) : NO_SUBSCRIPTION),\n [open],\n );\n\n const isTopmost = useSyncExternalStore(\n subscribe,\n // Gated on `open` so an overlay fading out stops claiming modal. Before it registers\n // it is the one about to go on top, or aria-modal would drop for a frame on open.\n () =>\n open &&\n (layerRef.current === null || trapLayers[trapLayers.length - 1] === layerRef.current),\n () => true,\n );\n\n // Its own request counts before the stack has it: the layer is not pushed until effects run.\n const stackElevated = useSyncExternalStore(\n subscribe,\n () => open && (elevated || elevatedLayers.size > 0),\n () => false,\n );\n\n // Overlay hosts share one z-index band, so paint order would otherwise fall out of body\n // order: an overlay reopened over a newer one takes the keyboard while painting under it.\n const zIndex = useSyncExternalStore(\n subscribe,\n () => {\n const layer = layerRef.current;\n return OVERLAY_Z_BASE + (open && layer ? Math.max(0, trapLayers.indexOf(layer)) : 0);\n },\n () => OVERLAY_Z_BASE,\n );\n\n // Layout, not passive: the rule has to exist before the first paint the attribute applies to.\n useLayoutEffect(() => {\n if (stackElevated) {\n ensureElevatedOverlayStyles();\n }\n }, [stackElevated]);\n\n // No deps: the host mounts a commit after the layer that owns it, so there is no stack\n // change left to key this on. `syncTopLayer` compares before it touches anything.\n useLayoutEffect(() => {\n syncTopLayer();\n });\n\n const ref = useCallback((node: HTMLElement | null) => {\n panelRef.current = node;\n // The panel mounts fresh on every open, so first attach = time to take focus.\n if (node) {\n takeInitialFocus(node, initialFocusRef.current);\n }\n }, []);\n\n return { ref, isTopmost, zIndex: stackElevated ? undefined : zIndex, elevated: stackElevated };\n};\n\nexport default useOverlayBehaviour;\n"],"names":["OVERLAY_ICON_BUTTON_CLASSES","FOCUS_RING","FOCUSABLE_SELECTOR","trapLayers","elevatedLayers","hostSources","stackListeners","NO_SUBSCRIPTION","promoted","syncTopLayer","wanted","layer","_a","host","index","stillWanted","leaveTopLayer","raiseToTopLayer","notifyStackChanged","listener","subscribeToStack","handleTabKeyDown","event","panel","focusables","first","last","active","getDeepActiveElement","activeInPanel","pushFocusTrapLayer","getPanel","getHost","useHasOpenOverlay","useSyncExternalStore","takeInitialFocus","initialFocus","useOverlayBehaviour","open","onClose","elevated","hostRef","panelRef","useRef","source","useOwnedByPopover","useCallback","previousFocusRef","layerRef","initialFocusRef","onCloseRef","useEffect","previous","popEscapeLayer","pushEscapeLayer","releaseScrollLock","lockBodyScroll","popFocusTrapLayer","useLayoutEffect","subscribe","onStackChange","isTopmost","stackElevated","zIndex","OVERLAY_Z_BASE","ensureElevatedOverlayStyles","node"],"mappings":";;;;;;;;AAUO,MAAMA,KAA8B,2MAA2MC,CAAU,IAEnPC,IACT,6IAMEC,IAA4B,CAAA,GAG5BC,wBAAqB,IAAA,GAErBC,wBAAkB,IAAA,GAClBC,wBAAqB,IAAA,GACrBC,IAAkB,MAAM;AAE9B;AAIA,IAAIC,IAA0B,CAAA;AAE9B,MAAMC,IAAe,MAAY;AAC7B,QAAMC,IAASN,EAAe,OACxBD,EACK,IAAI,CAACQ,MAAA;;AAAU,YAAAC,IAAAP,EAAY,IAAIM,CAAK,MAArB,gBAAAC;AAAA,GAA0B,EACzC,OAAO,CAACC,MAA8B,EAAQA,CAAK,IACxD,CAAA;AAGN,MACIH,EAAO,WAAWF,EAAS,UAC3BE,EAAO,MAAM,CAACG,GAAMC,MAAUD,MAASL,EAASM,CAAK,CAAC;AAEtD;AAEJ,QAAMC,IAAc,IAAI,IAAIL,CAAM;AAClC,aAAWG,KAAQL;AACf,IAAKO,EAAY,IAAIF,CAAI,KACrBG,EAAcH,CAAI;AAG1B,aAAWA,KAAQH;AACf,IAAAO,EAAgBJ,CAAI;AAExB,EAAAL,IAAWE;AACf,GAEMQ,IAAqB,MAAY;AACnC,aAAWC,KAAYb;AACnB,IAAAa,EAAA;AAER,GAEMC,IAAmB,CAACD,OACtBb,EAAe,IAAIa,CAAQ,GACpB,MAAM;AACT,EAAAb,EAAe,OAAOa,CAAQ;AAClC,IAGEE,IAAmB,CAACC,MAA+B;AACrD,MAAIA,EAAM,QAAQ,SAASA,EAAM;AAC7B;AAIJ,MAAIC,IAA4B;AAChC,WAAST,IAAQX,EAAW,SAAS,GAAGW,KAAS,KAAK,CAACS,GAAOT,KAAS;AACnE,IAAAS,IAAQpB,EAAWW,CAAK,EAAA;AAE5B,MAAI,CAACS;AACD;AAEJ,QAAMC,IAAa,MAAM,KAAKD,EAAM,iBAA8BrB,CAAkB,CAAC;AACrF,MAAIsB,EAAW,WAAW,GAAG;AACzB,IAAAF,EAAM,eAAA,GACNC,EAAM,MAAA;AACN;AAAA,EACJ;AACA,QAAME,IAAQD,EAAW,CAAC,GACpBE,IAAOF,EAAWA,EAAW,SAAS,CAAC,GACvCG,IAASC,EAAA,GACTC,IAAgBF,MAAW,QAAQJ,EAAM,SAASI,CAAM;AAC9D,EAAIL,EAAM,YACF,CAACO,KAAiBF,MAAWF,KAASE,MAAWJ,OACjDD,EAAM,eAAA,GACNI,EAAK,MAAA,MAEF,CAACG,KAAiBF,MAAWD,OACpCJ,EAAM,eAAA,GACNG,EAAM,MAAA;AAEd,GAKMK,IAAqB,CAACC,GAAuBC,OAC3C7B,EAAW,WAAW,KACtB,SAAS,iBAAiB,WAAWkB,CAAgB,GAEzDlB,EAAW,KAAK4B,CAAQ,GACxB1B,EAAY,IAAI0B,GAAUC,CAAO,GACjCd,EAAA,GACO,MAAM;AACT,QAAMJ,IAAQX,EAAW,YAAY4B,CAAQ;AAC7C,EAAIjB,MAAU,MACVX,EAAW,OAAOW,GAAO,CAAC,GAE9BV,EAAe,OAAO2B,CAAQ,GAC9B1B,EAAY,OAAO0B,CAAQ;AAC3B,QAAMlB,IAAOmB,EAAA;AACb,EAAInB,KACAG,EAAcH,CAAI,GAElBV,EAAW,WAAW,KACtB,SAAS,oBAAoB,WAAWkB,CAAgB,GAE5DH,EAAA;AACJ,IAQSe,KAAoB,MAC7BC;AAAA,EACId;AAAA,EACA,MAAMjB,EAAW,SAAS;AAAA,EAC1B,MAAM;AACV,GAEEgC,IAAmB,CAACZ,GAAoBa,MAA4C;AACtF,MAAIA,MAAiB,WAAW;AAC5B,UAAMT,IAASC,EAAA;AACf,QAAID,KAAUJ,EAAM,SAASI,CAAM;AAC/B;AAEJ,KAACJ,EAAM,cAA2BrB,CAAkB,KAAKqB,GAAO,MAAA;AAChE;AAAA,EACJ;AACA,EAAAA,EAAM,MAAA;AACV,GAYac,KAAsB,CAAC;AAAA,EAChC,MAAAC;AAAA,EACA,SAAAC;AAAA,EACA,UAAAC,IAAW;AAAA,EACX,SAAAC;AAAA,EACA,cAAAL,IAAe;AACnB,MAaK;AACD,QAAMM,IAAWC,EAA2B,IAAI,GAG1CC,IAASD,EAAoB,MAAMD,EAAS,OAAO,EAAE;AAC3D,EAAAG;AAAA,IACIC,EAAY,OAAML,KAAA,gBAAAA,EAAS,YAAWC,EAAS,SAAS,CAACD,CAAO,CAAC;AAAA,IACjEH;AAAA,EAAA;AAEJ,QAAMS,IAAmBJ,EAA2B,IAAI,GAClDK,IAAWL,EAA2B,IAAI,GAC1CM,IAAkBN,EAAOP,CAAY,GAGrCc,IAAaP,EAAOJ,CAAO;AACjC,EAAAY,EAAU,MAAM;AACZ,IAAAD,EAAW,UAAUX;AAAA,EACzB,GAAG,CAACA,CAAO,CAAC,GAIZY,EAAU,MAAM;AACZ,QAAKb;AAGL,aAAAS,EAAiB,UAAUnB,EAAA,GACpB,MAAM;AACT,cAAMwB,IAAWL,EAAiB;AAClC,QAAAA,EAAiB,UAAU,MACvBK,KAAA,QAAAA,EAAU,eACVA,EAAS,MAAA;AAAA,MAEjB;AAAA,EACJ,GAAG,CAACd,CAAI,CAAC,GAETa,EAAU,MAAM;AACZ,QAAI,CAACb;AACD;AAEJ,IAAAU,EAAS,UAAUJ;AACnB,UAAMS,IAAiBC,EAAgB,MAAMJ,EAAW,SAAS,GAC3DK,IAAoBC,EAAA,GACpBC,IAAoB3B,EAAmBc,GAAQ,OAAMH,KAAA,gBAAAA,EAAS,YAAW,IAAI;AAGnF,WAAIC,EAAS,WACTP,EAAiBO,EAAS,SAASO,EAAgB,OAAO,GAEvD,MAAM;AACT,MAAAD,EAAS,UAAU,MACnBS,EAAA,GACAJ,EAAA,GACAE,EAAA;AAAA,IACJ;AAAA,EACJ,GAAG,CAACjB,GAAMG,GAASG,CAAM,CAAC,GAK1Bc,EAAgB,MAAM;AAClB,QAAI,GAACpB,KAAQ,CAACE;AAGd,aAAApC,EAAe,IAAIwC,CAAM,GACzB1B,EAAA,GACO,MAAM;AACT,QAAAd,EAAe,OAAOwC,CAAM,GAC5B1B,EAAA;AAAA,MACJ;AAAA,EACJ,GAAG,CAACoB,GAAME,GAAUI,CAAM,CAAC;AAI3B,QAAMe,IAAYb;AAAA,IACd,CAACc,MAA+BtB,IAAOlB,EAAiBwC,CAAa,IAAIrD;AAAA,IACzE,CAAC+B,CAAI;AAAA,EAAA,GAGHuB,IAAY3B;AAAA,IACdyB;AAAA;AAAA;AAAA,IAGA,MACIrB,MACCU,EAAS,YAAY,QAAQ7C,EAAWA,EAAW,SAAS,CAAC,MAAM6C,EAAS;AAAA,IACjF,MAAM;AAAA,EAAA,GAIJc,IAAgB5B;AAAA,IAClByB;AAAA,IACA,MAAMrB,MAASE,KAAYpC,EAAe,OAAO;AAAA,IACjD,MAAM;AAAA,EAAA,GAKJ2D,IAAS7B;AAAA,IACXyB;AAAA,IACA,MAAM;AACF,YAAMhD,IAAQqC,EAAS;AACvB,aAAOgB,KAAkB1B,KAAQ3B,IAAQ,KAAK,IAAI,GAAGR,EAAW,QAAQQ,CAAK,CAAC,IAAI;AAAA,IACtF;AAAA,IACA,MAAMqD;AAAA,EAAA;AAIV,SAAAN,EAAgB,MAAM;AAClB,IAAII,KACAG,EAAA;AAAA,EAER,GAAG,CAACH,CAAa,CAAC,GAIlBJ,EAAgB,MAAM;AAClB,IAAAjD,EAAA;AAAA,EACJ,CAAC,GAUM,EAAE,KARGqC,EAAY,CAACoB,MAA6B;AAClD,IAAAxB,EAAS,UAAUwB,GAEfA,KACA/B,EAAiB+B,GAAMjB,EAAgB,OAAO;AAAA,EAEtD,GAAG,CAAA,CAAE,GAES,WAAAY,GAAW,QAAQC,IAAgB,SAAYC,GAAQ,UAAUD,EAAA;AACnF;"}
|
|
1
|
+
{"version":3,"file":"overlayBehaviour.js","sources":["../../src/components/overlayBehaviour.ts"],"sourcesContent":["import { useCallback, useEffect, useLayoutEffect, useRef, useSyncExternalStore } from 'react';\nimport { getDeepActiveElement } from './activeElement';\nimport { pushEscapeLayer } from './escapeLayers';\nimport { FOCUS_RING } from './focusRing';\nimport { nextLayerTicket, popupLayerHolding } from './overlayLayers';\nimport { useOwnedByPopover } from './popoverTree';\nimport { OVERLAY_Z_BASE, ensureElevatedOverlayStyles } from './portalOverlay';\nimport { lockBodyScroll } from './scrollLock';\nimport { leaveTopLayer, raiseToTopLayer } from './topLayer';\n\n/** Back/close button chrome, shared so the two overlays cannot drift apart. */\nexport const OVERLAY_ICON_BUTTON_CLASSES = `flex shrink-0 cursor-pointer items-center justify-center rounded-md border-0 bg-transparent p-[4px] text-[var(--malachite-text-secondary)] hover:text-[var(--malachite-text-primary)] transition-colors ${FOCUS_RING}`;\n\n// `summary`: a disclosure's first one is a native tab stop and its only keyboard control. A loose\n// one reports `tabIndex === -1`, which `isTabStop` drops.\nexport const FOCUSABLE_SELECTOR =\n 'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), summary, [tabindex]:not([tabindex=\"-1\"])';\n\n// Reads computed style: call at interaction time only, never from a mount path. Under jsdom,\n// `offsetParent`, `getClientRects()` and `checkVisibility()` report every element invisible.\nconst isTabStop = (element: HTMLElement): boolean => {\n if (element.tabIndex < 0 || element.matches(':disabled') || element.closest('[inert]')) {\n return false;\n }\n const { visibility } = getComputedStyle(element);\n if (visibility === 'hidden' || visibility === 'collapse') {\n return false;\n }\n // `visibility` inherits but `display` does not, hence the walk to the ancestors.\n // `content-visibility: hidden` skips an element's contents, not the element itself.\n for (let node: HTMLElement | null = element; node; node = node.parentElement) {\n const { display, contentVisibility } = getComputedStyle(node);\n if (display === 'none' || (node !== element && contentVisibility === 'hidden')) {\n return false;\n }\n }\n\n return true;\n};\n\nconst isGroupedRadio = (element: HTMLElement): element is HTMLInputElement =>\n element instanceof HTMLInputElement && element.type === 'radio' && element.name !== '';\n\n// Only a group's checked radio, or its first with none checked, is a tab stop, yet every member\n// reports `tabIndex === 0`.\nconst skipsWithinRadioGroup = (element: HTMLElement, reachable: HTMLElement[]): boolean => {\n if (!isGroupedRadio(element)) {\n return false;\n }\n const group = reachable.filter(\n (candidate): candidate is HTMLInputElement =>\n isGroupedRadio(candidate) &&\n candidate.name === element.name &&\n candidate.form === element.form,\n );\n\n return (group.find((radio) => radio.checked) ?? group[0]) !== element;\n};\n\n// A closed `<details>` hides its content in the UA shadow slot, so the hidden descendants keep\n// `tabIndex === 0` and a normal computed display.\nconst isInClosedDisclosure = (element: HTMLElement): boolean => {\n for (let node = element.parentElement; node; node = node.parentElement) {\n if (node instanceof HTMLDetailsElement && !node.open) {\n // `children`, not `:scope > summary`: inside a shadow root jsdom's selector engine\n // matches nothing for `:scope`, which silently hid the summary along with the rest.\n const summary = Array.from(node.children).find((child) => child.tagName === 'SUMMARY');\n if (!summary?.contains(element)) {\n return true;\n }\n }\n }\n\n return false;\n};\n\nconst inTabOrder = (stops: HTMLElement[]): HTMLElement[] =>\n stops\n .map((element, index) => ({ element, index }))\n .sort((left, right) => {\n if (left.element.tabIndex !== right.element.tabIndex) {\n if (left.element.tabIndex === 0) {\n return 1;\n }\n if (right.element.tabIndex === 0) {\n return -1;\n }\n\n return left.element.tabIndex - right.element.tabIndex;\n }\n\n return left.index - right.index;\n })\n .map(({ element }) => element);\n\nconst tabStopsIn = (root: ParentNode): HTMLElement[] => {\n const reachable = Array.from(root.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTOR)).filter(\n (element) => isTabStop(element) && !isInClosedDisclosure(element),\n );\n\n return inTabOrder(reachable.filter((element) => !skipsWithinRadioGroup(element, reachable)));\n};\n\n/** Where Tab would land first, for initial focus: the raw selector also matches hidden controls. */\nexport const firstTabStopIn = (root: ParentNode): HTMLElement | null => tabStopsIn(root)[0] ?? null;\n\ntype PanelSource = () => HTMLElement | null;\n\n// LIFO like escapeLayers: with a listener per overlay the first-registered one wins, which\n// sent Tab from a dialog into the panel behind it.\nconst trapLayers: PanelSource[] = [];\n// Elevation belongs to the stack: a Modal left in the base band opens behind the panel that\n// elevated it.\nconst elevatedLayers = new Set<PanelSource>();\n// The shadow host per layer, so promotion can replay in stack order.\nconst hostSources = new Map<PanelSource, () => HTMLElement | null>();\nconst trapTickets = new Map<PanelSource, number>();\nconst stackListeners = new Set<() => void>();\nconst NO_SUBSCRIPTION = () => {\n // Nothing to tear down: a closed overlay never subscribed.\n};\n\n// Top-layer paint order is entry order, so replay the whole stack bottom-up rather than letting\n// each overlay promote itself: those effects run in React tree order, which need not match.\nlet promoted: HTMLElement[] = [];\n\nconst syncTopLayer = (): void => {\n const wanted = elevatedLayers.size\n ? trapLayers\n .map((layer) => hostSources.get(layer)?.())\n .filter((host): host is HTMLElement => Boolean(host))\n : [];\n // Compared, not reapplied: this runs on every commit, because a host can appear a commit\n // after the layer that owns it, and re-entering the top layer unprompted costs a recalc.\n if (\n wanted.length === promoted.length &&\n wanted.every((host, index) => host === promoted[index])\n ) {\n return;\n }\n const stillWanted = new Set(wanted);\n for (const host of promoted) {\n if (!stillWanted.has(host)) {\n leaveTopLayer(host);\n }\n }\n for (const host of wanted) {\n raiseToTopLayer(host);\n }\n promoted = wanted;\n};\n\nconst notifyStackChanged = (): void => {\n for (const listener of stackListeners) {\n listener();\n }\n};\n\nconst subscribeToStack = (listener: () => void): (() => void) => {\n stackListeners.add(listener);\n return () => {\n stackListeners.delete(listener);\n };\n};\n\nconst wrapTab = (\n event: KeyboardEvent,\n active: HTMLElement | null,\n within: Node,\n stops: HTMLElement[],\n): void => {\n const first = stops[0];\n const last = stops[stops.length - 1];\n const inside = active !== null && within.contains(active);\n // A mouse-only control, such as a search Dropdown's caret, is no edge: Tab resumes from its\n // place in the DOM and would leave the overlay where no real stop follows it.\n if (active && inside && active !== within && !stops.includes(active)) {\n const wanted = event.shiftKey\n ? Node.DOCUMENT_POSITION_PRECEDING\n : Node.DOCUMENT_POSITION_FOLLOWING;\n if (!stops.some((stop) => active.compareDocumentPosition(stop) & wanted)) {\n event.preventDefault();\n (event.shiftKey ? last : first).focus();\n }\n\n return;\n }\n if (event.shiftKey) {\n if (!inside || active === first || active === within) {\n event.preventDefault();\n last.focus();\n }\n } else if (!inside || active === last || active === within) {\n event.preventDefault();\n first.focus();\n }\n};\n\nconst handleTabKeyDown = (event: KeyboardEvent): void => {\n if (event.key !== 'Tab' || event.defaultPrevented) {\n return;\n }\n // Walk down rather than bail: React detaches refs before flushing the cleanup that pops\n // the layer, so the topmost can be registered-but-null and Tab would escape entirely.\n let panel: HTMLElement | null = null;\n let trapTicket = 0;\n for (let index = trapLayers.length - 1; index >= 0 && !panel; index -= 1) {\n panel = trapLayers[index]();\n if (panel) {\n trapTicket = trapTickets.get(trapLayers[index]) ?? 0;\n }\n }\n if (!panel) {\n return;\n }\n const active = getDeepActiveElement();\n const layer = active ? popupLayerHolding(active) : null;\n if (active && layer && layer.ticket > trapTicket) {\n const root = layer.popup.shadowRoot ?? layer.popup;\n const popupStops = tabStopsIn(root);\n if (popupStops.length === 0) {\n event.preventDefault();\n return;\n }\n wrapTab(event, active, root, popupStops);\n return;\n }\n const focusables = tabStopsIn(panel);\n if (focusables.length === 0) {\n event.preventDefault();\n panel.focus();\n return;\n }\n wrapTab(event, active, panel, focusables);\n};\n\n// On `document` so it also catches focus that escaped behind the overlay.\nconst pushFocusTrapLayer = (getPanel: PanelSource, getHost: () => HTMLElement | null) => {\n if (trapLayers.length === 0) {\n document.addEventListener('keydown', handleTabKeyDown);\n }\n trapLayers.push(getPanel);\n hostSources.set(getPanel, getHost);\n trapTickets.set(getPanel, nextLayerTicket());\n notifyStackChanged();\n return () => {\n const index = trapLayers.lastIndexOf(getPanel);\n if (index !== -1) {\n trapLayers.splice(index, 1);\n }\n elevatedLayers.delete(getPanel);\n hostSources.delete(getPanel);\n trapTickets.delete(getPanel);\n const host = getHost();\n if (host) {\n leaveTopLayer(host);\n }\n if (trapLayers.length === 0) {\n document.removeEventListener('keydown', handleTabKeyDown);\n }\n notifyStackChanged();\n };\n};\n\n/**\n * Whether a SidePanel, Modal or CommandPalette is open anywhere, for host chrome that has to move out of the way.\n * Overlays portal, so the only other signal is this module's markers, which a host cannot read\n * without coupling to an internal. Dropdowns and tooltips do not count: they trap no focus.\n */\nexport const useHasOpenOverlay = (): boolean =>\n useSyncExternalStore(\n subscribeToStack,\n () => trapLayers.length > 0,\n () => false,\n );\n\nconst takeInitialFocus = (panel: HTMLElement, initialFocus: 'panel' | 'content'): void => {\n if (initialFocus === 'content') {\n const active = getDeepActiveElement();\n if (active && panel.contains(active)) {\n return;\n }\n (firstTabStopIn(panel) ?? panel).focus();\n return;\n }\n panel.focus();\n};\n\n/**\n * The modal-overlay behaviour shared by SidePanel, Modal and CommandPalette: ESC and the Tab focus trap\n * (topmost overlay only, so these may be stacked), background scroll lock, and focus moved\n * into the overlay on open then restored to the trigger on close.\n *\n * Returns the ref callback the dialog element must take, whether this overlay is the topmost\n * one — only that one may claim `aria-modal`, or a screen reader can still browse the overlay\n * underneath — the z-index its host must carry, which keeps paint order with the focus stack,\n * and whether it is elevated. Presentation otherwise stays with the calling component.\n */\nexport const useOverlayBehaviour = ({\n open,\n onClose,\n elevated = false,\n hostRef,\n initialFocus = 'panel',\n}: {\n open: boolean;\n onClose: () => void;\n elevated?: boolean;\n /** The shadow host, which the stack promotes into the top layer while elevated. */\n hostRef?: { current: HTMLElement | null };\n /** Where focus lands on open: the panel itself, or the content's own first control. */\n initialFocus?: 'panel' | 'content';\n}): {\n ref: (node: HTMLElement | null) => void;\n isTopmost: boolean;\n zIndex: number | undefined;\n elevated: boolean;\n} => {\n const panelRef = useRef<HTMLElement | null>(null);\n // One identity for the life of the instance: elevation registers it in the layout phase,\n // before the effect below pushes it onto the stack.\n const source = useRef<PanelSource>(() => panelRef.current).current;\n useOwnedByPopover(\n useCallback(() => hostRef?.current ?? panelRef.current, [hostRef]),\n open,\n );\n const previousFocusRef = useRef<HTMLElement | null>(null);\n const layerRef = useRef<PanelSource | null>(null);\n const initialFocusRef = useRef(initialFocus);\n\n // Ref so the ESC layer registers once per open, not on every inline-onClose change.\n const onCloseRef = useRef(onClose);\n useEffect(() => {\n onCloseRef.current = onClose;\n }, [onClose]);\n\n // Before the trap effect, so the trigger is recorded before focus moves in — and relies on\n // ShadowRootWrapper portaling a commit late, or this would capture the panel itself.\n useEffect(() => {\n if (!open) {\n return;\n }\n previousFocusRef.current = getDeepActiveElement();\n return () => {\n const previous = previousFocusRef.current;\n previousFocusRef.current = null;\n if (previous?.isConnected) {\n previous.focus();\n }\n };\n }, [open]);\n\n useEffect(() => {\n if (!open) {\n return;\n }\n layerRef.current = source;\n const popEscapeLayer = pushEscapeLayer(() => onCloseRef.current());\n const releaseScrollLock = lockBodyScroll();\n const popFocusTrapLayer = pushFocusTrapLayer(source, () => hostRef?.current ?? null);\n // Reopening inside the exit transition reuses the same node, so the ref callback\n // never fires again and nothing would take focus.\n if (panelRef.current) {\n takeInitialFocus(panelRef.current, initialFocusRef.current);\n }\n return () => {\n layerRef.current = null;\n popFocusTrapLayer();\n popEscapeLayer();\n releaseScrollLock();\n };\n }, [open, hostRef, source]);\n\n // Its own effect: re-pushing the trap layer to change elevation would move this overlay to\n // the top of the stack, handing it the Escape and Tab that belong to a dialog above it.\n // Layout, and declared above the sync below, so that sync sees it in the same commit.\n useLayoutEffect(() => {\n if (!open || !elevated) {\n return;\n }\n elevatedLayers.add(source);\n notifyStackChanged();\n return () => {\n elevatedLayers.delete(source);\n notifyStackChanged();\n };\n }, [open, elevated, source]);\n\n // Only an open overlay subscribes: a mounted-but-idle one must carry no at-rest cost,\n // and its snapshot is a constant `false` regardless of what the stack does.\n const subscribe = useCallback(\n (onStackChange: () => void) => (open ? subscribeToStack(onStackChange) : NO_SUBSCRIPTION),\n [open],\n );\n\n const isTopmost = useSyncExternalStore(\n subscribe,\n // Gated on `open` so an overlay fading out stops claiming modal. Before it registers\n // it is the one about to go on top, or aria-modal would drop for a frame on open.\n () =>\n open &&\n (layerRef.current === null || trapLayers[trapLayers.length - 1] === layerRef.current),\n () => true,\n );\n\n // Its own request counts before the stack has it: the layer is not pushed until effects run.\n const stackElevated = useSyncExternalStore(\n subscribe,\n () => open && (elevated || elevatedLayers.size > 0),\n () => false,\n );\n\n // Overlay hosts share one z-index band, so paint order would otherwise fall out of body\n // order: an overlay reopened over a newer one takes the keyboard while painting under it.\n const zIndex = useSyncExternalStore(\n subscribe,\n () => {\n const layer = layerRef.current;\n return OVERLAY_Z_BASE + (open && layer ? Math.max(0, trapLayers.indexOf(layer)) : 0);\n },\n () => OVERLAY_Z_BASE,\n );\n\n // Layout, not passive: the rule has to exist before the first paint the attribute applies to.\n useLayoutEffect(() => {\n if (stackElevated) {\n ensureElevatedOverlayStyles();\n }\n }, [stackElevated]);\n\n // No deps: the host mounts a commit after the layer that owns it, so there is no stack\n // change left to key this on. `syncTopLayer` compares before it touches anything.\n useLayoutEffect(() => {\n syncTopLayer();\n });\n\n const ref = useCallback((node: HTMLElement | null) => {\n panelRef.current = node;\n // The panel mounts fresh on every open, so first attach = time to take focus.\n if (node) {\n takeInitialFocus(node, initialFocusRef.current);\n }\n }, []);\n\n return { ref, isTopmost, zIndex: stackElevated ? undefined : zIndex, elevated: stackElevated };\n};\n\nexport default useOverlayBehaviour;\n"],"names":["OVERLAY_ICON_BUTTON_CLASSES","FOCUS_RING","FOCUSABLE_SELECTOR","isTabStop","element","visibility","node","display","contentVisibility","isGroupedRadio","skipsWithinRadioGroup","reachable","group","candidate","radio","isInClosedDisclosure","summary","child","inTabOrder","stops","index","left","right","tabStopsIn","root","firstTabStopIn","trapLayers","elevatedLayers","hostSources","trapTickets","stackListeners","NO_SUBSCRIPTION","promoted","syncTopLayer","wanted","layer","_a","host","stillWanted","leaveTopLayer","raiseToTopLayer","notifyStackChanged","listener","subscribeToStack","wrapTab","event","active","within","first","last","inside","stop","handleTabKeyDown","panel","trapTicket","getDeepActiveElement","popupLayerHolding","popupStops","focusables","pushFocusTrapLayer","getPanel","getHost","nextLayerTicket","useHasOpenOverlay","useSyncExternalStore","takeInitialFocus","initialFocus","useOverlayBehaviour","open","onClose","elevated","hostRef","panelRef","useRef","source","useOwnedByPopover","useCallback","previousFocusRef","layerRef","initialFocusRef","onCloseRef","useEffect","previous","popEscapeLayer","pushEscapeLayer","releaseScrollLock","lockBodyScroll","popFocusTrapLayer","useLayoutEffect","subscribe","onStackChange","isTopmost","stackElevated","zIndex","OVERLAY_Z_BASE","ensureElevatedOverlayStyles"],"mappings":"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"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { useEffect as u } from "react";
|
|
2
|
+
import { OVERLAY_CLASS as s, OVERLAY_ATTR as c } from "./portalOverlay.js";
|
|
3
|
+
let o = 0;
|
|
4
|
+
const p = () => (o += 1, o), n = [], i = (r) => {
|
|
5
|
+
const t = { source: r, ticket: p() };
|
|
6
|
+
return n.push(t), () => {
|
|
7
|
+
const e = n.indexOf(t);
|
|
8
|
+
e !== -1 && n.splice(e, 1);
|
|
9
|
+
};
|
|
10
|
+
}, f = (r, t) => {
|
|
11
|
+
for (let e = t; e; ) {
|
|
12
|
+
if (e === r)
|
|
13
|
+
return !0;
|
|
14
|
+
e = e instanceof ShadowRoot ? e.host : e.parentNode;
|
|
15
|
+
}
|
|
16
|
+
return !1;
|
|
17
|
+
}, L = (r) => {
|
|
18
|
+
for (let t = n.length - 1; t >= 0; t -= 1) {
|
|
19
|
+
const e = n[t].source();
|
|
20
|
+
if (e && f(e, r))
|
|
21
|
+
return { popup: e, ticket: n[t].ticket };
|
|
22
|
+
}
|
|
23
|
+
return null;
|
|
24
|
+
}, d = (r, t, e) => (u(() => {
|
|
25
|
+
if (t)
|
|
26
|
+
return i(() => e.current);
|
|
27
|
+
}, [t, e]), { className: s[r], [c]: r });
|
|
28
|
+
export {
|
|
29
|
+
p as nextLayerTicket,
|
|
30
|
+
L as popupLayerHolding,
|
|
31
|
+
i as pushPopupLayer,
|
|
32
|
+
d as usePopupLayer
|
|
33
|
+
};
|
|
34
|
+
//# sourceMappingURL=overlayLayers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"overlayLayers.js","sources":["../../src/components/overlayLayers.ts"],"sourcesContent":["import { type RefObject, useEffect } from 'react';\nimport { OVERLAY_ATTR, OVERLAY_CLASS, type PopupOverlayName } from './portalOverlay';\n\n// Document order cannot tell a popup opened above a dialog from one left open behind it: a\n// dialog's shadow host is appended to `document.body` when it mounts, not when it opens.\n\ntype LayerSource = () => HTMLElement | null;\n\nlet sequence = 0;\n\nexport const nextLayerTicket = (): number => {\n sequence += 1;\n return sequence;\n};\n\nconst popupLayers: { source: LayerSource; ticket: number }[] = [];\n\nexport const pushPopupLayer = (source: LayerSource): (() => void) => {\n const layer = { source, ticket: nextLayerTicket() };\n popupLayers.push(layer);\n return () => {\n const index = popupLayers.indexOf(layer);\n if (index !== -1) {\n popupLayers.splice(index, 1);\n }\n };\n};\n\n// `contains` stops at a shadow root, and a Popover holds its content in its host's.\nconst holds = (container: HTMLElement, node: Node): boolean => {\n for (let current: Node | null = node; current; ) {\n if (current === container) {\n return true;\n }\n current = current instanceof ShadowRoot ? current.host : current.parentNode;\n }\n return false;\n};\n\n/** The topmost registered popup holding `node`, with the ticket it was opened under. */\nexport const popupLayerHolding = (node: Node): { popup: HTMLElement; ticket: number } | null => {\n for (let index = popupLayers.length - 1; index >= 0; index -= 1) {\n const popup = popupLayers[index].source();\n if (popup && holds(popup, node)) {\n return { popup, ticket: popupLayers[index].ticket };\n }\n }\n return null;\n};\n\n// A popup's identity, registered with the dialog trap while `open`: the only route to one. Keyed\n// on `open` alone: a re-push takes a fresh ticket, outranking a newer dialog.\nexport const usePopupLayer = (\n name: PopupOverlayName,\n open: boolean,\n ref: RefObject<HTMLElement | null>,\n) => {\n useEffect(() => {\n if (!open) {\n return;\n }\n return pushPopupLayer(() => ref.current);\n }, [open, ref]);\n\n return { className: OVERLAY_CLASS[name], [OVERLAY_ATTR]: name };\n};\n"],"names":["sequence","nextLayerTicket","popupLayers","pushPopupLayer","source","layer","index","holds","container","node","current","popupLayerHolding","popup","usePopupLayer","name","open","ref","useEffect","OVERLAY_CLASS","OVERLAY_ATTR"],"mappings":";;AAQA,IAAIA,IAAW;AAER,MAAMC,IAAkB,OAC3BD,KAAY,GACLA,IAGLE,IAAyD,CAAA,GAElDC,IAAiB,CAACC,MAAsC;AACjE,QAAMC,IAAQ,EAAE,QAAAD,GAAQ,QAAQH,IAAgB;AAChD,SAAAC,EAAY,KAAKG,CAAK,GACf,MAAM;AACT,UAAMC,IAAQJ,EAAY,QAAQG,CAAK;AACvC,IAAIC,MAAU,MACVJ,EAAY,OAAOI,GAAO,CAAC;AAAA,EAEnC;AACJ,GAGMC,IAAQ,CAACC,GAAwBC,MAAwB;AAC3D,WAASC,IAAuBD,GAAMC,KAAW;AAC7C,QAAIA,MAAYF;AACZ,aAAO;AAEX,IAAAE,IAAUA,aAAmB,aAAaA,EAAQ,OAAOA,EAAQ;AAAA,EACrE;AACA,SAAO;AACX,GAGaC,IAAoB,CAACF,MAA8D;AAC5F,WAASH,IAAQJ,EAAY,SAAS,GAAGI,KAAS,GAAGA,KAAS,GAAG;AAC7D,UAAMM,IAAQV,EAAYI,CAAK,EAAE,OAAA;AACjC,QAAIM,KAASL,EAAMK,GAAOH,CAAI;AAC1B,aAAO,EAAE,OAAAG,GAAO,QAAQV,EAAYI,CAAK,EAAE,OAAA;AAAA,EAEnD;AACA,SAAO;AACX,GAIaO,IAAgB,CACzBC,GACAC,GACAC,OAEAC,EAAU,MAAM;AACZ,MAAKF;AAGL,WAAOZ,EAAe,MAAMa,EAAI,OAAO;AAC3C,GAAG,CAACD,GAAMC,CAAG,CAAC,GAEP,EAAE,WAAWE,EAAcJ,CAAI,GAAG,CAACK,CAAY,GAAGL,EAAA;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"portalOverlay.js","sources":["../../src/components/portalOverlay.ts"],"sourcesContent":["// Host \"kill the ad overlay\" CSS erases anything portaled into document.body: `body >\n// div:not([class])` matches an unclassed body child, `[style*=\"2147483\"]` a max-int z-index.\n// The second matches the style ATTRIBUTE, so declaring z-index here keeps max-int stacking.\n\nimport { injectStyleSheet } from './injectStyleSheet';\n\n/** One selector for every malachite overlay, so a host allow-lists them in one rule. */\nexport const OVERLAY_ATTR = 'data-malachite-overlay';\n\n// Values avoid `panel`/`popup`/`overlay`/`tooltip`: a class defeats `:not([class])`, but one\n// matching `[class*=\"panel\"]` trades that hole for another these elements did not have.\nexport const OVERLAY_CLASS = {\n dropdown: 'malachite-dropdown-surface',\n multiselect: 'malachite-multiselect-surface',\n actionmenu: 'malachite-actionmenu-surface',\n tooltip: 'malachite-tip-surface',\n sidepanel: 'malachite-drawer-surface',\n modal: 'malachite-modal-surface',\n commandpalette: 'malachite-cmdpalette-surface',\n popover: 'malachite-popover-surface',\n} as const;\n\nexport type OverlayName = keyof typeof OVERLAY_CLASS;\n\nexport const overlayIdentity = (name: OverlayName) => ({\n className: OVERLAY_CLASS[name],\n [OVERLAY_ATTR]: name,\n});\n\nconst OVERLAY_STYLE_ID = 'malachite-overlay-styles';\n\n/**\n * Where SidePanel, Modal and CommandPalette start. `useOverlayBehaviour` steps one per stacked overlay, so\n * paint order follows the focus stack rather than body order — a handful above this at most.\n */\nexport const OVERLAY_Z_BASE = 1000;\n\n/**\n * The dropdown band, which clears app chrome pinned just under max-int. One integer wide: a host\n * pins its chrome at max-int-1 and the tooltip band sits at max-int, so the stack cannot step\n * per layer the way the base band does — the top layer orders it, and where popover is\n * unsupported, document order does.\n */\nexport const OVERLAY_Z_ELEVATED = 2147483646;\n\n/** Marks a host in that band — an attribute, not an inline z-index; see above. */\nexport const OVERLAY_ELEVATED_ATTR = 'data-malachite-elevated';\n\n// Every overlay class, not the four promoted by default: one the reset misses wears the UA box.\nconst promotedSelector = (suffix: string) =>\n Object.values(OVERLAY_CLASS)\n .map((name) => `.${name}:popover-open${suffix}`)\n .join(', ');\n\n// Promotion drags in the UA `[popover]` box: `inset: 0` (which survives as right/bottom and\n// stretches a panel to the viewport edge), auto margins, a border, padding and fit-content\n// sizing. Reset to what these computed before promotion; their own inline styles still win.\nconst POPOVER_RESET =\n 'inset: auto; margin: 0; border: 0; padding: 0; width: auto; height: auto; ' +\n 'overflow: visible; background: none; color: inherit;';\n\nconst OVERLAY_RULES = [\n `.${OVERLAY_CLASS.dropdown}, .${OVERLAY_CLASS.multiselect}, .${OVERLAY_CLASS.actionmenu} { z-index: 2147483646; }`,\n `.${OVERLAY_CLASS.tooltip} { z-index: 2147483647; }`,\n];\n\n// Its own id, never folded into OVERLAY_STYLE_ID: injectStyleSheet keeps one sheet per id across\n// malachite copies sharing a realm, so a copy predating this rule would win that id and leave a\n// portaled menu on the host page's box model — a 280px Options menu painting 298. No copy has\n// ever registered this one.\nconst MENU_BOX_STYLE_ID = 'malachite-overlay-menu-box';\n\n// The scoped stylesheet makes everything border-box; a portal escapes it onto a host page that\n// may have no reset at all, while the geometry is computed as a CSS width.\nconst MENU_BOX_RULES = [\n `.${OVERLAY_CLASS.dropdown}, .${OVERLAY_CLASS.dropdown} *,` +\n ` .${OVERLAY_CLASS.multiselect}, .${OVERLAY_CLASS.multiselect} *,` +\n ` .${OVERLAY_CLASS.actionmenu}, .${OVERLAY_CLASS.actionmenu} *` +\n ' { box-sizing: border-box; }',\n];\n\nconst POPOVER_STYLE_ID = 'malachite-overlay-popover';\n\nconst POPOVER_RULES = [\n `.${OVERLAY_CLASS.popover} { z-index: ${OVERLAY_Z_ELEVATED}; }`,\n `.${OVERLAY_CLASS.popover}:popover-open { ${POPOVER_RESET} }`,\n `.${OVERLAY_CLASS.popover}:popover-open::backdrop { display: none; }`,\n];\n\nexport const ensurePopoverStyles = (): CSSStyleSheet | null =>\n injectStyleSheet(POPOVER_STYLE_ID, POPOVER_RULES);\n\nexport const ensureOverlayStyles = (): CSSStyleSheet | null => {\n injectStyleSheet(MENU_BOX_STYLE_ID, MENU_BOX_RULES);\n return injectStyleSheet(OVERLAY_STYLE_ID, OVERLAY_RULES);\n};\n\n// Its own id like TOP_LAYER_STYLE_ID: a copy predating elevation would own OVERLAY_STYLE_ID.\nconst ELEVATED_STYLE_ID = 'malachite-overlay-elevated';\n\nconst ELEVATED_RULES = [\n `.${OVERLAY_CLASS.sidepanel}[${OVERLAY_ELEVATED_ATTR}], .${OVERLAY_CLASS.modal}[${OVERLAY_ELEVATED_ATTR}] { z-index: ${OVERLAY_Z_ELEVATED}; }`,\n];\n\nexport const ensureElevatedOverlayStyles = (): CSSStyleSheet | null =>\n injectStyleSheet(ELEVATED_STYLE_ID, ELEVATED_RULES);\n\n// Its own id, never folded into OVERLAY_STYLE_ID: injectStyleSheet keeps one sheet per id across\n// malachite copies sharing a realm, so a copy predating the reset would win that id and leave a\n// promoted overlay wearing the raw UA box. No copy has ever registered this one.\nconst TOP_LAYER_STYLE_ID = 'malachite-overlay-top-layer';\n\nconst TOP_LAYER_RULES = [\n `${promotedSelector('')} { ${POPOVER_RESET} }`,\n `${promotedSelector('::backdrop')} { display: none; }`,\n];\n\nexport const ensureTopLayerStyles = (): CSSStyleSheet | null => {\n // A sheet that installed does not mean its rules parsed: replaceSync (and the <style> path)\n // drop an unparseable rule silently rather than throwing, so an engine that chokes on\n // `:popover-open` or `::backdrop` hands back a valid, empty sheet.\n const sheet = injectStyleSheet(TOP_LAYER_STYLE_ID, TOP_LAYER_RULES);\n return sheet && sheet.cssRules.length > 0 ? sheet : null;\n};\n"],"names":["OVERLAY_ATTR","OVERLAY_CLASS","overlayIdentity","name","OVERLAY_STYLE_ID","OVERLAY_Z_BASE","OVERLAY_Z_ELEVATED","OVERLAY_ELEVATED_ATTR","promotedSelector","suffix","POPOVER_RESET","OVERLAY_RULES","MENU_BOX_STYLE_ID","MENU_BOX_RULES","POPOVER_STYLE_ID","POPOVER_RULES","ensurePopoverStyles","injectStyleSheet","ensureOverlayStyles","ELEVATED_STYLE_ID","ELEVATED_RULES","ensureElevatedOverlayStyles","TOP_LAYER_STYLE_ID","TOP_LAYER_RULES","ensureTopLayerStyles","sheet"],"mappings":";AAOO,MAAMA,IAAe,0BAIfC,IAAgB;AAAA,EACzB,UAAU;AAAA,EACV,aAAa;AAAA,EACb,YAAY;AAAA,EACZ,SAAS;AAAA,EACT,WAAW;AAAA,EACX,OAAO;AAAA,EACP,gBAAgB;AAAA,EAChB,SAAS;AACb,
|
|
1
|
+
{"version":3,"file":"portalOverlay.js","sources":["../../src/components/portalOverlay.ts"],"sourcesContent":["// Host \"kill the ad overlay\" CSS erases anything portaled into document.body: `body >\n// div:not([class])` matches an unclassed body child, `[style*=\"2147483\"]` a max-int z-index.\n// The second matches the style ATTRIBUTE, so declaring z-index here keeps max-int stacking.\n\nimport { injectStyleSheet } from './injectStyleSheet';\n\n/** One selector for every malachite overlay, so a host allow-lists them in one rule. */\nexport const OVERLAY_ATTR = 'data-malachite-overlay';\n\n// Values avoid `panel`/`popup`/`overlay`/`tooltip`: a class defeats `:not([class])`, but one\n// matching `[class*=\"panel\"]` trades that hole for another these elements did not have.\nexport const OVERLAY_CLASS = {\n dropdown: 'malachite-dropdown-surface',\n multiselect: 'malachite-multiselect-surface',\n actionmenu: 'malachite-actionmenu-surface',\n tooltip: 'malachite-tip-surface',\n sidepanel: 'malachite-drawer-surface',\n modal: 'malachite-modal-surface',\n commandpalette: 'malachite-cmdpalette-surface',\n popover: 'malachite-popover-surface',\n} as const;\n\nexport type OverlayName = keyof typeof OVERLAY_CLASS;\n\n// Exhaustive, so a new overlay fails to typecheck until classified, and a popup is then refused\n// by `overlayIdentity`: its only identity comes from `usePopupLayer`, which registers it.\nconst OVERLAY_ROLE = {\n dropdown: 'popup',\n multiselect: 'popup',\n actionmenu: 'popup',\n popover: 'popup',\n sidepanel: 'trap',\n modal: 'trap',\n commandpalette: 'trap',\n tooltip: 'unfocusable',\n} as const satisfies Record<OverlayName, 'popup' | 'trap' | 'unfocusable'>;\n\nexport type PopupOverlayName = {\n [Name in OverlayName]: (typeof OVERLAY_ROLE)[Name] extends 'popup' ? Name : never;\n}[OverlayName];\n\nexport const overlayIdentity = (name: Exclude<OverlayName, PopupOverlayName>) => ({\n className: OVERLAY_CLASS[name],\n [OVERLAY_ATTR]: name,\n});\n\nconst OVERLAY_STYLE_ID = 'malachite-overlay-styles';\n\n/**\n * Where SidePanel, Modal and CommandPalette start. `useOverlayBehaviour` steps one per stacked overlay, so\n * paint order follows the focus stack rather than body order — a handful above this at most.\n */\nexport const OVERLAY_Z_BASE = 1000;\n\n/**\n * The dropdown band, which clears app chrome pinned just under max-int. One integer wide: a host\n * pins its chrome at max-int-1 and the tooltip band sits at max-int, so the stack cannot step\n * per layer the way the base band does — the top layer orders it, and where popover is\n * unsupported, document order does.\n */\nexport const OVERLAY_Z_ELEVATED = 2147483646;\n\n/** Marks a host in that band — an attribute, not an inline z-index; see above. */\nexport const OVERLAY_ELEVATED_ATTR = 'data-malachite-elevated';\n\n// Every overlay class, not the four promoted by default: one the reset misses wears the UA box.\nconst promotedSelector = (suffix: string) =>\n Object.values(OVERLAY_CLASS)\n .map((name) => `.${name}:popover-open${suffix}`)\n .join(', ');\n\n// Promotion drags in the UA `[popover]` box: `inset: 0` (which survives as right/bottom and\n// stretches a panel to the viewport edge), auto margins, a border, padding and fit-content\n// sizing. Reset to what these computed before promotion; their own inline styles still win.\nconst POPOVER_RESET =\n 'inset: auto; margin: 0; border: 0; padding: 0; width: auto; height: auto; ' +\n 'overflow: visible; background: none; color: inherit;';\n\nconst OVERLAY_RULES = [\n `.${OVERLAY_CLASS.dropdown}, .${OVERLAY_CLASS.multiselect}, .${OVERLAY_CLASS.actionmenu} { z-index: 2147483646; }`,\n `.${OVERLAY_CLASS.tooltip} { z-index: 2147483647; }`,\n];\n\n// Its own id, never folded into OVERLAY_STYLE_ID: injectStyleSheet keeps one sheet per id across\n// malachite copies sharing a realm, so a copy predating this rule would win that id and leave a\n// portaled menu on the host page's box model — a 280px Options menu painting 298. No copy has\n// ever registered this one.\nconst MENU_BOX_STYLE_ID = 'malachite-overlay-menu-box';\n\n// The scoped stylesheet makes everything border-box; a portal escapes it onto a host page that\n// may have no reset at all, while the geometry is computed as a CSS width.\nconst MENU_BOX_RULES = [\n `.${OVERLAY_CLASS.dropdown}, .${OVERLAY_CLASS.dropdown} *,` +\n ` .${OVERLAY_CLASS.multiselect}, .${OVERLAY_CLASS.multiselect} *,` +\n ` .${OVERLAY_CLASS.actionmenu}, .${OVERLAY_CLASS.actionmenu} *` +\n ' { box-sizing: border-box; }',\n];\n\nconst POPOVER_STYLE_ID = 'malachite-overlay-popover';\n\nconst POPOVER_RULES = [\n `.${OVERLAY_CLASS.popover} { z-index: ${OVERLAY_Z_ELEVATED}; }`,\n `.${OVERLAY_CLASS.popover}:popover-open { ${POPOVER_RESET} }`,\n `.${OVERLAY_CLASS.popover}:popover-open::backdrop { display: none; }`,\n];\n\nexport const ensurePopoverStyles = (): CSSStyleSheet | null =>\n injectStyleSheet(POPOVER_STYLE_ID, POPOVER_RULES);\n\nexport const ensureOverlayStyles = (): CSSStyleSheet | null => {\n injectStyleSheet(MENU_BOX_STYLE_ID, MENU_BOX_RULES);\n return injectStyleSheet(OVERLAY_STYLE_ID, OVERLAY_RULES);\n};\n\n// Its own id like TOP_LAYER_STYLE_ID: a copy predating elevation would own OVERLAY_STYLE_ID.\nconst ELEVATED_STYLE_ID = 'malachite-overlay-elevated';\n\nconst ELEVATED_RULES = [\n `.${OVERLAY_CLASS.sidepanel}[${OVERLAY_ELEVATED_ATTR}], .${OVERLAY_CLASS.modal}[${OVERLAY_ELEVATED_ATTR}] { z-index: ${OVERLAY_Z_ELEVATED}; }`,\n];\n\nexport const ensureElevatedOverlayStyles = (): CSSStyleSheet | null =>\n injectStyleSheet(ELEVATED_STYLE_ID, ELEVATED_RULES);\n\n// Its own id, never folded into OVERLAY_STYLE_ID: injectStyleSheet keeps one sheet per id across\n// malachite copies sharing a realm, so a copy predating the reset would win that id and leave a\n// promoted overlay wearing the raw UA box. No copy has ever registered this one.\nconst TOP_LAYER_STYLE_ID = 'malachite-overlay-top-layer';\n\nconst TOP_LAYER_RULES = [\n `${promotedSelector('')} { ${POPOVER_RESET} }`,\n `${promotedSelector('::backdrop')} { display: none; }`,\n];\n\nexport const ensureTopLayerStyles = (): CSSStyleSheet | null => {\n // A sheet that installed does not mean its rules parsed: replaceSync (and the <style> path)\n // drop an unparseable rule silently rather than throwing, so an engine that chokes on\n // `:popover-open` or `::backdrop` hands back a valid, empty sheet.\n const sheet = injectStyleSheet(TOP_LAYER_STYLE_ID, TOP_LAYER_RULES);\n return sheet && sheet.cssRules.length > 0 ? sheet : null;\n};\n"],"names":["OVERLAY_ATTR","OVERLAY_CLASS","overlayIdentity","name","OVERLAY_STYLE_ID","OVERLAY_Z_BASE","OVERLAY_Z_ELEVATED","OVERLAY_ELEVATED_ATTR","promotedSelector","suffix","POPOVER_RESET","OVERLAY_RULES","MENU_BOX_STYLE_ID","MENU_BOX_RULES","POPOVER_STYLE_ID","POPOVER_RULES","ensurePopoverStyles","injectStyleSheet","ensureOverlayStyles","ELEVATED_STYLE_ID","ELEVATED_RULES","ensureElevatedOverlayStyles","TOP_LAYER_STYLE_ID","TOP_LAYER_RULES","ensureTopLayerStyles","sheet"],"mappings":";AAOO,MAAMA,IAAe,0BAIfC,IAAgB;AAAA,EACzB,UAAU;AAAA,EACV,aAAa;AAAA,EACb,YAAY;AAAA,EACZ,SAAS;AAAA,EACT,WAAW;AAAA,EACX,OAAO;AAAA,EACP,gBAAgB;AAAA,EAChB,SAAS;AACb,GAqBaC,IAAkB,CAACC,OAAkD;AAAA,EAC9E,WAAWF,EAAcE,CAAI;AAAA,EAC7B,CAACH,CAAY,GAAGG;AACpB,IAEMC,IAAmB,4BAMZC,IAAiB,KAQjBC,IAAqB,YAGrBC,IAAwB,2BAG/BC,IAAmB,CAACC,MACtB,OAAO,OAAOR,CAAa,EACtB,IAAI,CAACE,MAAS,IAAIA,CAAI,gBAAgBM,CAAM,EAAE,EAC9C,KAAK,IAAI,GAKZC,IACF,kIAGEC,IAAgB;AAAA,EAClB,IAAIV,EAAc,QAAQ,MAAMA,EAAc,WAAW,MAAMA,EAAc,UAAU;AAAA,EACvF,IAAIA,EAAc,OAAO;AAC7B,GAMMW,IAAoB,8BAIpBC,IAAiB;AAAA,EACnB,IAAIZ,EAAc,QAAQ,MAAMA,EAAc,QAAQ,QAC7CA,EAAc,WAAW,MAAMA,EAAc,WAAW,QACxDA,EAAc,UAAU,MAAMA,EAAc,UAAU;AAEnE,GAEMa,IAAmB,6BAEnBC,IAAgB;AAAA,EAClB,IAAId,EAAc,OAAO,eAAeK,CAAkB;AAAA,EAC1D,IAAIL,EAAc,OAAO,mBAAmBS,CAAa;AAAA,EACzD,IAAIT,EAAc,OAAO;AAC7B,GAEae,IAAsB,MAC/BC,EAAiBH,GAAkBC,CAAa,GAEvCG,IAAsB,OAC/BD,EAAiBL,GAAmBC,CAAc,GAC3CI,EAAiBb,GAAkBO,CAAa,IAIrDQ,IAAoB,8BAEpBC,IAAiB;AAAA,EACnB,IAAInB,EAAc,SAAS,IAAIM,CAAqB,OAAON,EAAc,KAAK,IAAIM,CAAqB,gBAAgBD,CAAkB;AAC7I,GAEae,IAA8B,MACvCJ,EAAiBE,GAAmBC,CAAc,GAKhDE,IAAqB,+BAErBC,IAAkB;AAAA,EACpB,GAAGf,EAAiB,EAAE,CAAC,MAAME,CAAa;AAAA,EAC1C,GAAGF,EAAiB,YAAY,CAAC;AACrC,GAEagB,IAAuB,MAA4B;AAI5D,QAAMC,IAAQR,EAAiBK,GAAoBC,CAAe;AAClE,SAAOE,KAASA,EAAM,SAAS,SAAS,IAAIA,IAAQ;AACxD;"}
|
package/dist/index.d.ts
CHANGED
|
@@ -3956,6 +3956,11 @@ export declare type SegmentedControlSize = 'small' | 'medium' | 'large';
|
|
|
3956
3956
|
/** A fixed-size card that behaves as one option in a radio group: a card-shaped `Surface.Row`. */
|
|
3957
3957
|
export declare const SelectableCard: default_2.FC<SelectableCardProps>;
|
|
3958
3958
|
|
|
3959
|
+
export declare interface SelectableCardLink {
|
|
3960
|
+
label: string;
|
|
3961
|
+
href: string;
|
|
3962
|
+
}
|
|
3963
|
+
|
|
3959
3964
|
export declare interface SelectableCardProps {
|
|
3960
3965
|
/** Identifies the card inside its group; handed back to `onSelect`. */
|
|
3961
3966
|
value: string;
|
|
@@ -3974,6 +3979,9 @@ export declare interface SelectableCardProps {
|
|
|
3974
3979
|
description?: string;
|
|
3975
3980
|
/** Bottom-left summary ("16 Actions", "10 Selected"). */
|
|
3976
3981
|
countLabel?: string;
|
|
3982
|
+
/** A link along the bottom edge, opened in a new tab. Rendered beside the radio rather than
|
|
3983
|
+
* inside it, so it stays its own tab stop. */
|
|
3984
|
+
link?: SelectableCardLink;
|
|
3977
3985
|
/** Top-right cluster (markers, an ActionMenu…). The eyebrow row gives way to one
|
|
3978
3986
|
* xsmall control; a wider cluster needs a shorter eyebrow and status to clear it. */
|
|
3979
3987
|
actions?: default_2.ReactNode;
|