@stackone/malachite 2.37.0 → 2.38.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.
Files changed (96) hide show
  1. package/COMPONENTS.md +7 -2
  2. package/dist/assets/icons/account.svg.js +2 -2
  3. package/dist/assets/icons/account.svg.js.map +1 -1
  4. package/dist/assets/icons/building.svg.js +6 -0
  5. package/dist/assets/icons/building.svg.js.map +1 -0
  6. package/dist/assets/icons/chevrons_up_down.svg.js +6 -0
  7. package/dist/assets/icons/chevrons_up_down.svg.js.map +1 -0
  8. package/dist/assets/icons/connector.svg.js +6 -0
  9. package/dist/assets/icons/connector.svg.js.map +1 -0
  10. package/dist/assets/icons/external_link.svg.js +6 -0
  11. package/dist/assets/icons/external_link.svg.js.map +1 -0
  12. package/dist/assets/icons/field_coverage.svg.js +6 -0
  13. package/dist/assets/icons/field_coverage.svg.js.map +1 -0
  14. package/dist/assets/icons/layout.svg.js +6 -0
  15. package/dist/assets/icons/layout.svg.js.map +1 -0
  16. package/dist/assets/icons/link.svg.js +6 -0
  17. package/dist/assets/icons/link.svg.js.map +1 -0
  18. package/dist/assets/icons/playground.svg.js +6 -0
  19. package/dist/assets/icons/playground.svg.js.map +1 -0
  20. package/dist/components/ActionMenu.js +139 -170
  21. package/dist/components/ActionMenu.js.map +1 -1
  22. package/dist/components/CardStrip.js +195 -190
  23. package/dist/components/CardStrip.js.map +1 -1
  24. package/dist/components/Chat/ChatModelPicker.js +64 -60
  25. package/dist/components/Chat/ChatModelPicker.js.map +1 -1
  26. package/dist/components/CustomIcons/BuildingIcon.js +16 -0
  27. package/dist/components/CustomIcons/BuildingIcon.js.map +1 -0
  28. package/dist/components/CustomIcons/ChevronsUpDownIcon.js +16 -0
  29. package/dist/components/CustomIcons/ChevronsUpDownIcon.js.map +1 -0
  30. package/dist/components/CustomIcons/ConnectorIcon.js +16 -0
  31. package/dist/components/CustomIcons/ConnectorIcon.js.map +1 -0
  32. package/dist/components/CustomIcons/ExternalLinkIcon.js +16 -0
  33. package/dist/components/CustomIcons/ExternalLinkIcon.js.map +1 -0
  34. package/dist/components/CustomIcons/FieldCoverageIcon.js +16 -0
  35. package/dist/components/CustomIcons/FieldCoverageIcon.js.map +1 -0
  36. package/dist/components/CustomIcons/LayoutIcon.js +16 -0
  37. package/dist/components/CustomIcons/LayoutIcon.js.map +1 -0
  38. package/dist/components/CustomIcons/LinkIcon.js +16 -0
  39. package/dist/components/CustomIcons/LinkIcon.js.map +1 -0
  40. package/dist/components/CustomIcons/PlaygroundIcon.js +16 -0
  41. package/dist/components/CustomIcons/PlaygroundIcon.js.map +1 -0
  42. package/dist/components/CustomIcons/index.js +303 -279
  43. package/dist/components/CustomIcons/index.js.map +1 -1
  44. package/dist/components/NavItem.js +158 -0
  45. package/dist/components/NavItem.js.map +1 -0
  46. package/dist/components/Popover.js +262 -0
  47. package/dist/components/Popover.js.map +1 -0
  48. package/dist/components/PopoverParts.js +46 -0
  49. package/dist/components/PopoverParts.js.map +1 -0
  50. package/dist/components/QueryInput.js +103 -97
  51. package/dist/components/QueryInput.js.map +1 -1
  52. package/dist/components/SearchTrigger.js +37 -0
  53. package/dist/components/SearchTrigger.js.map +1 -0
  54. package/dist/components/Sidebar.js +174 -0
  55. package/dist/components/Sidebar.js.map +1 -0
  56. package/dist/components/Tooltip.js +97 -92
  57. package/dist/components/Tooltip.js.map +1 -1
  58. package/dist/components/UsageCard.js +44 -0
  59. package/dist/components/UsageCard.js.map +1 -0
  60. package/dist/components/actionMenuKeyboard.js +23 -0
  61. package/dist/components/actionMenuKeyboard.js.map +1 -0
  62. package/dist/components/actionMenuStyles.js +83 -0
  63. package/dist/components/actionMenuStyles.js.map +1 -0
  64. package/dist/components/ariaState.js +5 -0
  65. package/dist/components/ariaState.js.map +1 -0
  66. package/dist/components/chromeStyles.js +6 -0
  67. package/dist/components/chromeStyles.js.map +1 -0
  68. package/dist/components/composeRefs.js +27 -0
  69. package/dist/components/composeRefs.js.map +1 -0
  70. package/dist/components/floatingMenuGeometry.js +74 -35
  71. package/dist/components/floatingMenuGeometry.js.map +1 -1
  72. package/dist/components/overlayBehaviour.js +90 -84
  73. package/dist/components/overlayBehaviour.js.map +1 -1
  74. package/dist/components/popoverTree.js +14 -0
  75. package/dist/components/popoverTree.js.map +1 -0
  76. package/dist/components/portalOverlay.js +28 -22
  77. package/dist/components/portalOverlay.js.map +1 -1
  78. package/dist/components/sidebarContext.js +9 -0
  79. package/dist/components/sidebarContext.js.map +1 -0
  80. package/dist/components/topLayer.js +18 -18
  81. package/dist/components/topLayer.js.map +1 -1
  82. package/dist/components/useActionMenuPosition.js +73 -0
  83. package/dist/components/useActionMenuPosition.js.map +1 -0
  84. package/dist/components/useHoverClose.js +18 -0
  85. package/dist/components/useHoverClose.js.map +1 -0
  86. package/dist/components/useLatestRef.js +11 -0
  87. package/dist/components/useLatestRef.js.map +1 -0
  88. package/dist/components/usePopoverPosition.js +62 -0
  89. package/dist/components/usePopoverPosition.js.map +1 -0
  90. package/dist/components/usePopoverTree.js +34 -0
  91. package/dist/components/usePopoverTree.js.map +1 -0
  92. package/dist/index.d.ts +387 -100
  93. package/dist/index.js +365 -335
  94. package/dist/index.js.map +1 -1
  95. package/dist/tailwind.css.js +1 -1
  96. package/package.json +1 -1
@@ -1,127 +1,133 @@
1
- import { useSyncExternalStore as m, useRef as l, useEffect as L, useLayoutEffect as v, useCallback as k } from "react";
2
- import { getDeepActiveElement as E } from "./activeElement.js";
3
- import { pushEscapeLayer as N } from "./escapeLayers.js";
4
- import { FOCUS_RING as z } from "./focusRing.js";
5
- import { OVERLAY_Z_BASE as I, ensureElevatedOverlayStyles as U } from "./portalOverlay.js";
6
- import { lockBodyScroll as q } from "./scrollLock.js";
7
- import { leaveTopLayer as A, raiseToTopLayer as K } from "./topLayer.js";
8
- const X = `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 ${z}`, C = 'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])', o = [], f = /* @__PURE__ */ new Set(), x = /* @__PURE__ */ new Map(), b = /* @__PURE__ */ new Set(), M = () => {
1
+ import { useSyncExternalStore as p, useRef as l, useCallback as L, useEffect as x, useLayoutEffect as b } from "react";
2
+ import { getDeepActiveElement as O } from "./activeElement.js";
3
+ import { pushEscapeLayer as z } from "./escapeLayers.js";
4
+ import { FOCUS_RING as U } from "./focusRing.js";
5
+ import { useOwnedByPopover as q } from "./popoverTree.js";
6
+ import { OVERLAY_Z_BASE as w, ensureElevatedOverlayStyles as K } from "./portalOverlay.js";
7
+ import { lockBodyScroll as M } from "./scrollLock.js";
8
+ import { leaveTopLayer as A, raiseToTopLayer as V } from "./topLayer.js";
9
+ const ee = `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 ${U}`, B = 'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])', s = [], f = /* @__PURE__ */ new Set(), E = /* @__PURE__ */ new Map(), h = /* @__PURE__ */ new Set(), Y = () => {
9
10
  };
10
- let p = [];
11
- const V = () => {
12
- const e = f.size ? o.map((t) => {
13
- var n;
14
- return (n = x.get(t)) == null ? void 0 : n();
11
+ let m = [];
12
+ const j = () => {
13
+ const e = f.size ? s.map((t) => {
14
+ var r;
15
+ return (r = E.get(t)) == null ? void 0 : r();
15
16
  }).filter((t) => !!t) : [];
16
- if (e.length === p.length && e.every((t, n) => t === p[n]))
17
+ if (e.length === m.length && e.every((t, r) => t === m[r]))
17
18
  return;
18
- const r = new Set(e);
19
- for (const t of p)
20
- r.has(t) || A(t);
19
+ const n = new Set(e);
20
+ for (const t of m)
21
+ n.has(t) || A(t);
21
22
  for (const t of e)
22
- K(t);
23
- p = e;
24
- }, h = () => {
25
- for (const e of b)
23
+ V(t);
24
+ m = e;
25
+ }, S = () => {
26
+ for (const e of h)
26
27
  e();
27
- }, _ = (e) => (b.add(e), () => {
28
- b.delete(e);
29
- }), g = (e) => {
28
+ }, C = (e) => (h.add(e), () => {
29
+ h.delete(e);
30
+ }), I = (e) => {
30
31
  if (e.key !== "Tab" || e.defaultPrevented)
31
32
  return;
32
- let r = null;
33
- for (let a = o.length - 1; a >= 0 && !r; a -= 1)
34
- r = o[a]();
35
- if (!r)
33
+ let n = null;
34
+ for (let a = s.length - 1; a >= 0 && !n; a -= 1)
35
+ n = s[a]();
36
+ if (!n)
36
37
  return;
37
- const t = Array.from(r.querySelectorAll(C));
38
+ const t = Array.from(n.querySelectorAll(B));
38
39
  if (t.length === 0) {
39
- e.preventDefault(), r.focus();
40
+ e.preventDefault(), n.focus();
40
41
  return;
41
42
  }
42
- const n = t[0], d = t[t.length - 1], c = E(), u = c !== null && r.contains(c);
43
- e.shiftKey ? (!u || c === n || c === r) && (e.preventDefault(), d.focus()) : (!u || c === d) && (e.preventDefault(), n.focus());
44
- }, Y = (e, r) => (o.length === 0 && document.addEventListener("keydown", g), o.push(e), x.set(e, r), h(), () => {
45
- const t = o.lastIndexOf(e);
46
- t !== -1 && o.splice(t, 1), f.delete(e), x.delete(e);
47
- const n = r();
48
- n && A(n), o.length === 0 && document.removeEventListener("keydown", g), h();
49
- }), P = () => m(
50
- _,
51
- () => o.length > 0,
43
+ const r = t[0], d = t[t.length - 1], o = O(), u = o !== null && n.contains(o);
44
+ e.shiftKey ? (!u || o === r || o === n) && (e.preventDefault(), d.focus()) : (!u || o === d) && (e.preventDefault(), r.focus());
45
+ }, G = (e, n) => (s.length === 0 && document.addEventListener("keydown", I), s.push(e), E.set(e, n), S(), () => {
46
+ const t = s.lastIndexOf(e);
47
+ t !== -1 && s.splice(t, 1), f.delete(e), E.delete(e);
48
+ const r = n();
49
+ r && A(r), s.length === 0 && document.removeEventListener("keydown", I), S();
50
+ }), te = () => p(
51
+ C,
52
+ () => s.length > 0,
52
53
  () => !1
53
- ), w = (e, r) => {
54
- if (r === "content") {
55
- const t = E();
54
+ ), g = (e, n) => {
55
+ if (n === "content") {
56
+ const t = O();
56
57
  if (t && e.contains(t))
57
58
  return;
58
- (e.querySelector(C) ?? e).focus();
59
+ (e.querySelector(B) ?? e).focus();
59
60
  return;
60
61
  }
61
62
  e.focus();
62
- }, ee = ({
63
+ }, re = ({
63
64
  open: e,
64
- onClose: r,
65
+ onClose: n,
65
66
  elevated: t = !1,
66
- hostRef: n,
67
+ hostRef: r,
67
68
  initialFocus: d = "panel"
68
69
  }) => {
69
- const c = l(null), u = l(() => c.current).current, a = l(null), i = l(null), O = l(d), T = l(r);
70
- L(() => {
71
- T.current = r;
72
- }, [r]), L(() => {
70
+ const o = l(null), u = l(() => o.current).current;
71
+ q(
72
+ L(() => (r == null ? void 0 : r.current) ?? o.current, [r]),
73
+ e
74
+ );
75
+ const a = l(null), i = l(null), T = l(d), k = l(n);
76
+ x(() => {
77
+ k.current = n;
78
+ }, [n]), x(() => {
73
79
  if (e)
74
- return a.current = E(), () => {
75
- const s = a.current;
76
- a.current = null, s != null && s.isConnected && s.focus();
80
+ return a.current = O(), () => {
81
+ const c = a.current;
82
+ a.current = null, c != null && c.isConnected && c.focus();
77
83
  };
78
- }, [e]), L(() => {
84
+ }, [e]), x(() => {
79
85
  if (!e)
80
86
  return;
81
87
  i.current = u;
82
- const s = N(() => T.current()), R = q(), D = Y(u, () => (n == null ? void 0 : n.current) ?? null);
83
- return c.current && w(c.current, O.current), () => {
84
- i.current = null, D(), s(), R();
88
+ const c = z(() => k.current()), D = M(), N = G(u, () => (r == null ? void 0 : r.current) ?? null);
89
+ return o.current && g(o.current, T.current), () => {
90
+ i.current = null, N(), c(), D();
85
91
  };
86
- }, [e, n, u]), v(() => {
92
+ }, [e, r, u]), b(() => {
87
93
  if (!(!e || !t))
88
- return f.add(u), h(), () => {
89
- f.delete(u), h();
94
+ return f.add(u), S(), () => {
95
+ f.delete(u), S();
90
96
  };
91
97
  }, [e, t, u]);
92
- const S = k(
93
- (s) => e ? _(s) : M,
98
+ const v = L(
99
+ (c) => e ? C(c) : Y,
94
100
  [e]
95
- ), B = m(
96
- S,
101
+ ), _ = p(
102
+ v,
97
103
  // Gated on `open` so an overlay fading out stops claiming modal. Before it registers
98
104
  // it is the one about to go on top, or aria-modal would drop for a frame on open.
99
- () => e && (i.current === null || o[o.length - 1] === i.current),
105
+ () => e && (i.current === null || s[s.length - 1] === i.current),
100
106
  () => !0
101
- ), y = m(
102
- S,
107
+ ), y = p(
108
+ v,
103
109
  () => e && (t || f.size > 0),
104
110
  () => !1
105
- ), F = m(
106
- S,
111
+ ), F = p(
112
+ v,
107
113
  () => {
108
- const s = i.current;
109
- return I + (e && s ? Math.max(0, o.indexOf(s)) : 0);
114
+ const c = i.current;
115
+ return w + (e && c ? Math.max(0, s.indexOf(c)) : 0);
110
116
  },
111
- () => I
117
+ () => w
112
118
  );
113
- return v(() => {
114
- y && U();
115
- }, [y]), v(() => {
116
- V();
117
- }), { ref: k((s) => {
118
- c.current = s, s && w(s, O.current);
119
- }, []), isTopmost: B, zIndex: y ? void 0 : F, elevated: y };
119
+ return b(() => {
120
+ y && K();
121
+ }, [y]), b(() => {
122
+ j();
123
+ }), { ref: L((c) => {
124
+ o.current = c, c && g(c, T.current);
125
+ }, []), isTopmost: _, zIndex: y ? void 0 : F, elevated: y };
120
126
  };
121
127
  export {
122
- C as FOCUSABLE_SELECTOR,
123
- X as OVERLAY_ICON_BUTTON_CLASSES,
124
- P as useHasOpenOverlay,
125
- ee as useOverlayBehaviour
128
+ B as FOCUSABLE_SELECTOR,
129
+ ee as OVERLAY_ICON_BUTTON_CLASSES,
130
+ te as useHasOpenOverlay,
131
+ re as useOverlayBehaviour
126
132
  };
127
133
  //# sourceMappingURL=overlayBehaviour.js.map
@@ -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 { 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 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","previousFocusRef","layerRef","initialFocusRef","onCloseRef","useEffect","previous","popEscapeLayer","pushEscapeLayer","releaseScrollLock","lockBodyScroll","popFocusTrapLayer","useLayoutEffect","subscribe","useCallback","onStackChange","isTopmost","stackElevated","zIndex","OVERLAY_Z_BASE","ensureElevatedOverlayStyles","node"],"mappings":"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"}
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":"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"}
@@ -0,0 +1,14 @@
1
+ import { useContext as r, useLayoutEffect as n, createContext as s } from "react";
2
+ const f = 150, u = s(null), p = (t, o) => {
3
+ const e = r(u);
4
+ n(() => {
5
+ if (!(!o || !e))
6
+ return e.register(t);
7
+ }, [o, e, t]);
8
+ };
9
+ export {
10
+ f as HOVER_CLOSE_DELAY_MS,
11
+ u as PopoverTreeContext,
12
+ p as useOwnedByPopover
13
+ };
14
+ //# sourceMappingURL=popoverTree.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"popoverTree.js","sources":["../../src/components/popoverTree.ts"],"sourcesContent":["import { createContext, useContext, useLayoutEffect } from 'react';\n\nexport type OverlaySource = () => HTMLElement | null;\n\nexport interface PopoverTree {\n register: (source: OverlaySource) => () => void;\n hold: () => () => void;\n}\n\nexport const HOVER_CLOSE_DELAY_MS = 150;\n\nexport const PopoverTreeContext = createContext<PopoverTree | null>(null);\n\nexport const useOwnedByPopover = (source: OverlaySource, active: boolean): void => {\n const tree = useContext(PopoverTreeContext);\n useLayoutEffect(() => {\n if (!active || !tree) {\n return;\n }\n return tree.register(source);\n }, [active, tree, source]);\n};\n"],"names":["HOVER_CLOSE_DELAY_MS","PopoverTreeContext","createContext","useOwnedByPopover","source","active","tree","useContext","useLayoutEffect"],"mappings":";AASO,MAAMA,IAAuB,KAEvBC,IAAqBC,EAAkC,IAAI,GAE3DC,IAAoB,CAACC,GAAuBC,MAA0B;AAC/E,QAAMC,IAAOC,EAAWN,CAAkB;AAC1C,EAAAO,EAAgB,MAAM;AAClB,QAAI,GAACH,KAAU,CAACC;AAGhB,aAAOA,EAAK,SAASF,CAAM;AAAA,EAC/B,GAAG,CAACC,GAAQC,GAAMF,CAAM,CAAC;AAC7B;"}
@@ -1,38 +1,44 @@
1
- import { injectStyleSheet as o } from "./injectStyleSheet.js";
2
- const l = "data-malachite-overlay", e = {
1
+ import { injectStyleSheet as t } from "./injectStyleSheet.js";
2
+ const s = "data-malachite-overlay", e = {
3
3
  dropdown: "malachite-dropdown-surface",
4
4
  multiselect: "malachite-multiselect-surface",
5
5
  actionmenu: "malachite-actionmenu-surface",
6
6
  tooltip: "malachite-tip-surface",
7
7
  sidepanel: "malachite-drawer-surface",
8
8
  modal: "malachite-modal-surface",
9
- commandpalette: "malachite-cmdpalette-surface"
10
- }, $ = (t) => ({
11
- className: e[t],
12
- [l]: t
13
- }), s = "malachite-overlay-styles", R = 1e3, i = 2147483646, a = "data-malachite-elevated", n = (t) => Object.values(e).map((c) => `.${c}:popover-open${t}`).join(", "), r = "inset: auto; margin: 0; border: 0; padding: 0; width: auto; height: auto; overflow: visible; background: none; color: inherit;", d = [
9
+ commandpalette: "malachite-cmdpalette-surface",
10
+ popover: "malachite-popover-surface"
11
+ }, R = (o) => ({
12
+ className: e[o],
13
+ [s]: o
14
+ }), i = "malachite-overlay-styles", S = 1e3, c = 2147483646, a = "data-malachite-elevated", n = (o) => Object.values(e).map((r) => `.${r}:popover-open${o}`).join(", "), l = "inset: auto; margin: 0; border: 0; padding: 0; width: auto; height: auto; overflow: visible; background: none; color: inherit;", p = [
14
15
  `.${e.dropdown}, .${e.multiselect}, .${e.actionmenu} { z-index: 2147483646; }`,
15
16
  `.${e.tooltip} { z-index: 2147483647; }`
16
- ], E = "malachite-overlay-menu-box", m = [
17
+ ], E = "malachite-overlay-menu-box", d = [
17
18
  `.${e.dropdown}, .${e.dropdown} *, .${e.multiselect}, .${e.multiselect} *, .${e.actionmenu}, .${e.actionmenu} * { box-sizing: border-box; }`
18
- ], S = () => (o(E, m), o(s, d)), u = "malachite-overlay-elevated", _ = [
19
- `.${e.sidepanel}[${a}], .${e.modal}[${a}] { z-index: ${i}; }`
20
- ], y = () => o(u, _), p = "malachite-overlay-top-layer", L = [
21
- `${n("")} { ${r} }`,
19
+ ], m = "malachite-overlay-popover", u = [
20
+ `.${e.popover} { z-index: ${c}; }`,
21
+ `.${e.popover}:popover-open { ${l} }`,
22
+ `.${e.popover}:popover-open::backdrop { display: none; }`
23
+ ], y = () => t(m, u), O = () => (t(E, d), t(i, p)), _ = "malachite-overlay-elevated", v = [
24
+ `.${e.sidepanel}[${a}], .${e.modal}[${a}] { z-index: ${c}; }`
25
+ ], A = () => t(_, v), L = "malachite-overlay-top-layer", $ = [
26
+ `${n("")} { ${l} }`,
22
27
  `${n("::backdrop")} { display: none; }`
23
- ], A = () => {
24
- const t = o(p, L);
25
- return t && t.cssRules.length > 0 ? t : null;
28
+ ], T = () => {
29
+ const o = t(L, $);
30
+ return o && o.cssRules.length > 0 ? o : null;
26
31
  };
27
32
  export {
28
- l as OVERLAY_ATTR,
33
+ s as OVERLAY_ATTR,
29
34
  e as OVERLAY_CLASS,
30
35
  a as OVERLAY_ELEVATED_ATTR,
31
- R as OVERLAY_Z_BASE,
32
- i as OVERLAY_Z_ELEVATED,
33
- y as ensureElevatedOverlayStyles,
34
- S as ensureOverlayStyles,
35
- A as ensureTopLayerStyles,
36
- $ as overlayIdentity
36
+ S as OVERLAY_Z_BASE,
37
+ c as OVERLAY_Z_ELEVATED,
38
+ A as ensureElevatedOverlayStyles,
39
+ O as ensureOverlayStyles,
40
+ y as ensurePopoverStyles,
41
+ T as ensureTopLayerStyles,
42
+ R as overlayIdentity
37
43
  };
38
44
  //# sourceMappingURL=portalOverlay.js.map
@@ -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} 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\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","ensureOverlayStyles","injectStyleSheet","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;AACpB,GAIaC,IAAkB,CAACC,OAAuB;AAAA,EACnD,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,GAEaa,IAAsB,OAC/BC,EAAiBH,GAAmBC,CAAc,GAC3CE,EAAiBX,GAAkBO,CAAa,IAIrDK,IAAoB,8BAEpBC,IAAiB;AAAA,EACnB,IAAIhB,EAAc,SAAS,IAAIM,CAAqB,OAAON,EAAc,KAAK,IAAIM,CAAqB,gBAAgBD,CAAkB;AAC7I,GAEaY,IAA8B,MACvCH,EAAiBC,GAAmBC,CAAc,GAKhDE,IAAqB,+BAErBC,IAAkB;AAAA,EACpB,GAAGZ,EAAiB,EAAE,CAAC,MAAME,CAAa;AAAA,EAC1C,GAAGF,EAAiB,YAAY,CAAC;AACrC,GAEaa,IAAuB,MAA4B;AAI5D,QAAMC,IAAQP,EAAiBI,GAAoBC,CAAe;AAClE,SAAOE,KAASA,EAAM,SAAS,SAAS,IAAIA,IAAQ;AACxD;"}
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,GAIaC,IAAkB,CAACC,OAAuB;AAAA,EACnD,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;"}
@@ -0,0 +1,9 @@
1
+ import { useContext as e, createContext as o } from "react";
2
+ const a = { expanded: 260, collapsed: 60 }, l = 244, t = o({ collapsed: !1 }), c = () => e(t).collapsed;
3
+ export {
4
+ l as SIDEBAR_POPOVER_WIDTH_PX,
5
+ a as SIDEBAR_WIDTH_PX,
6
+ t as SidebarContext,
7
+ c as useSidebarCollapsed
8
+ };
9
+ //# sourceMappingURL=sidebarContext.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sidebarContext.js","sources":["../../src/components/sidebarContext.ts"],"sourcesContent":["import { createContext, useContext } from 'react';\n\nexport const SIDEBAR_WIDTH_PX = { expanded: 260, collapsed: 60 } as const;\n\nexport const SIDEBAR_POPOVER_WIDTH_PX = 244;\n\nexport const SidebarContext = createContext({ collapsed: false });\n\nexport const useSidebarCollapsed = (): boolean => useContext(SidebarContext).collapsed;\n"],"names":["SIDEBAR_WIDTH_PX","SIDEBAR_POPOVER_WIDTH_PX","SidebarContext","createContext","useSidebarCollapsed","useContext"],"mappings":";AAEO,MAAMA,IAAmB,EAAE,UAAU,KAAK,WAAW,GAAA,GAE/CC,IAA2B,KAE3BC,IAAiBC,EAAc,EAAE,WAAW,IAAO,GAEnDC,IAAsB,MAAeC,EAAWH,CAAc,EAAE;"}
@@ -1,29 +1,29 @@
1
- import { useLayoutEffect as t } from "react";
2
- import { ensureTopLayerStyles as p } from "./portalOverlay.js";
3
- const s = (o) => "showPopover" in o ? typeof CSS < "u" && CSS.supports("selector(:popover-open)") : !1, n = (o) => {
4
- if (s(o) && p())
1
+ import { useContext as p, useLayoutEffect as n } from "react";
2
+ import { PopoverTreeContext as s } from "./popoverTree.js";
3
+ import { ensureTopLayerStyles as u } from "./portalOverlay.js";
4
+ const i = (o) => "showPopover" in o ? typeof CSS < "u" && CSS.supports("selector(:popover-open)") : !1, v = (o) => {
5
+ if (i(o) && u())
5
6
  try {
6
7
  o.setAttribute("popover", "manual"), o.matches(":popover-open") && o.hidePopover(), o.showPopover();
7
8
  } catch {
8
9
  o.removeAttribute("popover");
9
10
  }
10
- }, v = (o) => {
11
+ }, m = (o) => {
11
12
  o.removeAttribute("popover");
12
- }, f = (o, r) => {
13
- t(() => {
14
- const e = o.current;
15
- if (e) {
16
- if (r) {
17
- n(e);
18
- return;
19
- }
20
- e.removeAttribute("popover");
13
+ }, y = (o, t) => {
14
+ const e = p(s);
15
+ n(() => {
16
+ const r = o.current;
17
+ if (r) {
18
+ if (t)
19
+ return v(r), e == null ? void 0 : e.register(() => o.current);
20
+ r.removeAttribute("popover");
21
21
  }
22
- }, [o, r]);
22
+ }, [o, t, e]);
23
23
  };
24
24
  export {
25
- v as leaveTopLayer,
26
- n as raiseToTopLayer,
27
- f as useTopLayer
25
+ m as leaveTopLayer,
26
+ v as raiseToTopLayer,
27
+ y as useTopLayer
28
28
  };
29
29
  //# sourceMappingURL=topLayer.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"topLayer.js","sources":["../../src/components/topLayer.ts"],"sourcesContent":["// A host that opens its modal in the browser's top layer (Angular CDK 21+ sets\n// `popover=\"manual\"` by default; native `<dialog>.showModal()` does the same) paints above every\n// normal-flow element, and z-index is not consulted at all. An overlay portaled to document.body\n// therefore renders UNDER that modal however high it bids, so it has to join the top layer.\n\nimport { type RefObject, useLayoutEffect } from 'react';\nimport { ensureTopLayerStyles } from './portalOverlay';\n\nconst supportsPopover = (el: HTMLElement): boolean => {\n // The same probe CDK uses: without popover, CDK builds no top layer either. A host using\n // `<dialog>.showModal()` still outranks us on an engine too old for popover.\n if (!('showPopover' in el)) {\n return false;\n }\n // A JS-only popover polyfill (e.g. `@oddbird/popover-polyfill`) can define `showPopover` on\n // the prototype without the CSS engine ever matching `:popover-open`, which would promote\n // into the raw UA box with no reset able to reach it. `CSS` itself is undefined in jsdom.\n return typeof CSS !== 'undefined' && CSS.supports('selector(:popover-open)');\n};\n\nexport const raiseToTopLayer = (el: HTMLElement): void => {\n if (!supportsPopover(el)) {\n return;\n }\n // Never promote unstyled: the UA box would stretch a panel to the viewport edge, which is\n // worse than losing to the modal. Covers a host CSP blocking the sheet as well.\n if (!ensureTopLayerStyles()) {\n return;\n }\n try {\n // setAttribute, not the `popover` IDL property: the property is an expando wherever the\n // attribute is not reflected, which strands the element without the UA popover behaviour.\n el.setAttribute('popover', 'manual');\n // Entry order decides which top-layer element wins, so re-enter to land on top.\n if (el.matches(':popover-open')) {\n el.hidePopover();\n }\n el.showPopover();\n } catch {\n // `[popover]:not(:popover-open)` is `display: none` in the UA sheet, so a half-applied\n // popover erases the overlay outright. Falling back to the z-index path is the lesser bug.\n el.removeAttribute('popover');\n }\n};\n\n// Dropping the attribute rather than calling hidePopover leaves the top layer AND takes the UA\n// `display: none` with it, so an overlay that stays mounted while hidden keeps its transition.\nexport const leaveTopLayer = (el: HTMLElement): void => {\n el.removeAttribute('popover');\n};\n\n/**\n * Promotes a portaled overlay into the top layer while `active`. Pass the visibility state, not\n * `true`, for an overlay that stays mounted while hidden, so each show re-enters above its peers.\n */\nexport const useTopLayer = (ref: RefObject<HTMLElement | null>, active: boolean): void => {\n useLayoutEffect(() => {\n const el = ref.current;\n if (!el) {\n return;\n }\n if (active) {\n raiseToTopLayer(el);\n return;\n }\n // Dropping the attribute rather than calling hidePopover leaves the top layer AND takes the\n // UA `display: none` with it, so an overlay that stays mounted while hidden keeps its exit\n // transition and costs a style recalc no more than it did before promotion. Left open, each\n // one measurably slows a full recalc — 500 of them tripled it in Chromium and WebKit.\n el.removeAttribute('popover');\n }, [ref, active]);\n};\n"],"names":["supportsPopover","el","raiseToTopLayer","ensureTopLayerStyles","leaveTopLayer","useTopLayer","ref","active","useLayoutEffect"],"mappings":";;AAQA,MAAMA,IAAkB,CAACC,MAGf,iBAAiBA,IAMhB,OAAO,MAAQ,OAAe,IAAI,SAAS,yBAAyB,IALhE,IAQFC,IAAkB,CAACD,MAA0B;AACtD,MAAKD,EAAgBC,CAAE,KAKlBE;AAGL,QAAI;AAGA,MAAAF,EAAG,aAAa,WAAW,QAAQ,GAE/BA,EAAG,QAAQ,eAAe,KAC1BA,EAAG,YAAA,GAEPA,EAAG,YAAA;AAAA,IACP,QAAQ;AAGJ,MAAAA,EAAG,gBAAgB,SAAS;AAAA,IAChC;AACJ,GAIaG,IAAgB,CAACH,MAA0B;AACpD,EAAAA,EAAG,gBAAgB,SAAS;AAChC,GAMaI,IAAc,CAACC,GAAoCC,MAA0B;AACtF,EAAAC,EAAgB,MAAM;AAClB,UAAMP,IAAKK,EAAI;AACf,QAAKL,GAGL;AAAA,UAAIM,GAAQ;AACR,QAAAL,EAAgBD,CAAE;AAClB;AAAA,MACJ;AAKA,MAAAA,EAAG,gBAAgB,SAAS;AAAA;AAAA,EAChC,GAAG,CAACK,GAAKC,CAAM,CAAC;AACpB;"}
1
+ {"version":3,"file":"topLayer.js","sources":["../../src/components/topLayer.ts"],"sourcesContent":["// A host that opens its modal in the browser's top layer (Angular CDK 21+ sets\n// `popover=\"manual\"` by default; native `<dialog>.showModal()` does the same) paints above every\n// normal-flow element, and z-index is not consulted at all. An overlay portaled to document.body\n// therefore renders UNDER that modal however high it bids, so it has to join the top layer.\n\nimport { type RefObject, useContext, useLayoutEffect } from 'react';\nimport { PopoverTreeContext } from './popoverTree';\nimport { ensureTopLayerStyles } from './portalOverlay';\n\nconst supportsPopover = (el: HTMLElement): boolean => {\n // The same probe CDK uses: without popover, CDK builds no top layer either. A host using\n // `<dialog>.showModal()` still outranks us on an engine too old for popover.\n if (!('showPopover' in el)) {\n return false;\n }\n // A JS-only popover polyfill (e.g. `@oddbird/popover-polyfill`) can define `showPopover` on\n // the prototype without the CSS engine ever matching `:popover-open`, which would promote\n // into the raw UA box with no reset able to reach it. `CSS` itself is undefined in jsdom.\n return typeof CSS !== 'undefined' && CSS.supports('selector(:popover-open)');\n};\n\nexport const raiseToTopLayer = (el: HTMLElement): void => {\n if (!supportsPopover(el)) {\n return;\n }\n // Never promote unstyled: the UA box would stretch a panel to the viewport edge, which is\n // worse than losing to the modal. Covers a host CSP blocking the sheet as well.\n if (!ensureTopLayerStyles()) {\n return;\n }\n try {\n // setAttribute, not the `popover` IDL property: the property is an expando wherever the\n // attribute is not reflected, which strands the element without the UA popover behaviour.\n el.setAttribute('popover', 'manual');\n // Entry order decides which top-layer element wins, so re-enter to land on top.\n if (el.matches(':popover-open')) {\n el.hidePopover();\n }\n el.showPopover();\n } catch {\n // `[popover]:not(:popover-open)` is `display: none` in the UA sheet, so a half-applied\n // popover erases the overlay outright. Falling back to the z-index path is the lesser bug.\n el.removeAttribute('popover');\n }\n};\n\n// Dropping the attribute rather than calling hidePopover leaves the top layer AND takes the UA\n// `display: none` with it, so an overlay that stays mounted while hidden keeps its transition.\nexport const leaveTopLayer = (el: HTMLElement): void => {\n el.removeAttribute('popover');\n};\n\n/**\n * Promotes a portaled overlay into the top layer while `active`. Pass the visibility state, not\n * `true`, for an overlay that stays mounted while hidden, so each show re-enters above its peers.\n */\nexport const useTopLayer = (ref: RefObject<HTMLElement | null>, active: boolean): void => {\n const tree = useContext(PopoverTreeContext);\n useLayoutEffect(() => {\n const el = ref.current;\n if (!el) {\n return;\n }\n if (active) {\n raiseToTopLayer(el);\n return tree?.register(() => ref.current);\n }\n // Dropping the attribute rather than calling hidePopover leaves the top layer AND takes the\n // UA `display: none` with it, so an overlay that stays mounted while hidden keeps its exit\n // transition and costs a style recalc no more than it did before promotion. Left open, each\n // one measurably slows a full recalc — 500 of them tripled it in Chromium and WebKit.\n el.removeAttribute('popover');\n }, [ref, active, tree]);\n};\n"],"names":["supportsPopover","el","raiseToTopLayer","ensureTopLayerStyles","leaveTopLayer","useTopLayer","ref","active","tree","useContext","PopoverTreeContext","useLayoutEffect"],"mappings":";;;AASA,MAAMA,IAAkB,CAACC,MAGf,iBAAiBA,IAMhB,OAAO,MAAQ,OAAe,IAAI,SAAS,yBAAyB,IALhE,IAQFC,IAAkB,CAACD,MAA0B;AACtD,MAAKD,EAAgBC,CAAE,KAKlBE;AAGL,QAAI;AAGA,MAAAF,EAAG,aAAa,WAAW,QAAQ,GAE/BA,EAAG,QAAQ,eAAe,KAC1BA,EAAG,YAAA,GAEPA,EAAG,YAAA;AAAA,IACP,QAAQ;AAGJ,MAAAA,EAAG,gBAAgB,SAAS;AAAA,IAChC;AACJ,GAIaG,IAAgB,CAACH,MAA0B;AACpD,EAAAA,EAAG,gBAAgB,SAAS;AAChC,GAMaI,IAAc,CAACC,GAAoCC,MAA0B;AACtF,QAAMC,IAAOC,EAAWC,CAAkB;AAC1C,EAAAC,EAAgB,MAAM;AAClB,UAAMV,IAAKK,EAAI;AACf,QAAKL,GAGL;AAAA,UAAIM;AACA,eAAAL,EAAgBD,CAAE,GACXO,KAAA,gBAAAA,EAAM,SAAS,MAAMF,EAAI;AAMpC,MAAAL,EAAG,gBAAgB,SAAS;AAAA;AAAA,EAChC,GAAG,CAACK,GAAKC,GAAQC,CAAI,CAAC;AAC1B;"}
@@ -0,0 +1,73 @@
1
+ import { useState as L, useRef as y, useInsertionEffect as A, useCallback as F, useLayoutEffect as b } from "react";
2
+ import { ensureActionMenuStyles as O } from "./actionMenuStyles.js";
3
+ import { measureContentWidth as z, measureMenuGeometry as C, computeFloatingGeometry as P, fullPanelHeight as T, MENU_MAX_HEIGHT_PX as H, rectSignature as E, FLOATING_OFFSET_PX as W } from "./floatingMenuGeometry.js";
4
+ import { useTopLayer as _ } from "./topLayer.js";
5
+ const I = (c, i, l, { minWidth: g, placement: m, offset: h }) => {
6
+ const [M, p] = L(null), w = y(void 0), d = y(!0);
7
+ _(l, c), A(() => {
8
+ c && O();
9
+ }, [c]);
10
+ const r = F(() => {
11
+ const o = i.current;
12
+ if (!o)
13
+ return;
14
+ const t = l.current;
15
+ if (t && d.current) {
16
+ const n = t.style.width;
17
+ t.style.width = "", w.current = z(t) ?? w.current, t.style.width = n, d.current = !1;
18
+ }
19
+ const a = o.getBoundingClientRect(), s = C(t, {
20
+ triggerRect: a,
21
+ preset: "options",
22
+ minWidth: g,
23
+ contentWidth: w.current,
24
+ viewportWidth: window.innerWidth,
25
+ viewportHeight: window.innerHeight
26
+ });
27
+ if (m === "bottom-end" && h === void 0 || !t) {
28
+ p(s);
29
+ return;
30
+ }
31
+ const e = P({
32
+ triggerRect: a,
33
+ placement: m,
34
+ panelWidth: s.width,
35
+ panelHeight: Math.min(T(t), H),
36
+ gap: W[h ?? "small"],
37
+ viewportWidth: window.innerWidth,
38
+ viewportHeight: window.innerHeight
39
+ });
40
+ p({
41
+ ...s,
42
+ top: e.top,
43
+ left: e.left,
44
+ maxHeight: Math.min(H, e.maxHeight),
45
+ openUp: e.side === "top"
46
+ });
47
+ }, [i, l, g, m, h]);
48
+ return b(() => {
49
+ if (!c) {
50
+ p(null), w.current = void 0, d.current = !0;
51
+ return;
52
+ }
53
+ d.current = !0, r();
54
+ let o = 0, t = !1, a = i.current ? E(i.current.getBoundingClientRect()) : "";
55
+ const s = () => {
56
+ const f = i.current, v = f ? E(f.getBoundingClientRect()) : "";
57
+ (v !== a || t) && (a = v, t = !1, r()), o = requestAnimationFrame(s);
58
+ };
59
+ o = requestAnimationFrame(s), window.addEventListener("scroll", r, !0), window.addEventListener("resize", r);
60
+ const e = l.current, n = e && typeof ResizeObserver < "u" ? new ResizeObserver(() => {
61
+ t = !0;
62
+ }) : null, u = e && typeof MutationObserver < "u" ? new MutationObserver(() => {
63
+ d.current = !0, t = !0;
64
+ }) : null;
65
+ return e && (n == null || n.observe(e), u == null || u.observe(e, { childList: !0, subtree: !0, characterData: !0 })), () => {
66
+ cancelAnimationFrame(o), n == null || n.disconnect(), u == null || u.disconnect(), window.removeEventListener("scroll", r, !0), window.removeEventListener("resize", r);
67
+ };
68
+ }, [c, r, i, l]), M;
69
+ };
70
+ export {
71
+ I as useActionMenuPosition
72
+ };
73
+ //# sourceMappingURL=useActionMenuPosition.js.map