@stackone/malachite 2.57.0 → 2.57.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,184 +1,214 @@
1
- import { useSyncExternalStore as y, useRef as f, useCallback as x, useEffect as T, useLayoutEffect as E } from "react";
2
- import { getDeepActiveElement as k } from "./activeElement.js";
3
- import { pushEscapeLayer as R } from "./escapeLayers.js";
4
- import { FOCUS_RING as z } from "./focusRing.js";
5
- import { nextLayerTicket as K, popupLayerHolding as H } from "./overlayLayers.js";
6
- import { useOwnedByPopover as V } from "./popoverTree.js";
7
- import { OVERLAY_Z_BASE as D, ensureElevatedOverlayStyles as W } from "./portalOverlay.js";
8
- import { lockBodyScroll as Y } from "./scrollLock.js";
9
- import { leaveTopLayer as M, raiseToTopLayer as j } from "./topLayer.js";
10
- const de = `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}`, q = 'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), summary, [tabindex]:not([tabindex="-1"])', P = (e) => {
11
- if (e.tabIndex < 0 || e.matches(":disabled") || e.closest("[inert]"))
1
+ import { useSyncExternalStore as b, useRef as f, useCallback as N, useEffect as E, useLayoutEffect as m } from "react";
2
+ import { getDeepActiveElement as k, containsComposed as F } from "./activeElement.js";
3
+ import { pushEscapeLayer as K } from "./escapeLayers.js";
4
+ import { FOCUS_RING as W } from "./focusRing.js";
5
+ import { nextLayerTicket as V, popupLayerHolding as Y } from "./overlayLayers.js";
6
+ import { useOwnedByPopover as j } from "./popoverTree.js";
7
+ import { OVERLAY_Z_BASE as R, ensureElevatedOverlayStyles as Z } from "./portalOverlay.js";
8
+ import { lockBodyScroll as $ } from "./scrollLock.js";
9
+ import { leaveTopLayer as U, raiseToTopLayer as q } from "./topLayer.js";
10
+ const Ot = `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 ${W}`, J = 'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), summary, [tabindex]:not([tabindex="-1"])', Q = (t) => t.assignedSlot ?? t.parentElement ?? (t.parentNode instanceof ShadowRoot ? t.parentNode.host : null), X = (t) => Array.from(t.children).find((o) => o.tagName === "SUMMARY"), tt = () => {
11
+ const t = /* @__PURE__ */ new Map(), o = (n) => {
12
+ const e = t.get(n);
13
+ if (e)
14
+ return e;
15
+ const { display: a, visibility: r, contentVisibility: s } = getComputedStyle(n), c = Q(n), d = c ? o(c) : null, h = c instanceof HTMLDetailsElement && !c.open && X(c) !== n, l = {
16
+ hidden: n.hasAttribute("inert") || a === "none" || h || !!(d && (d.hidden || d.hidesContent)),
17
+ invisible: r === "hidden" || r === "collapse",
18
+ hidesContent: s === "hidden"
19
+ };
20
+ return t.set(n, l), l;
21
+ };
22
+ return o;
23
+ }, nt = (t, o) => {
24
+ if (t.tabIndex < 0 || t.matches(":disabled"))
12
25
  return !1;
13
- const { visibility: t } = getComputedStyle(e);
14
- if (t === "hidden" || t === "collapse")
26
+ const { hidden: n, invisible: e } = o(t);
27
+ return !n && !e;
28
+ }, _ = (t) => t instanceof HTMLInputElement && t.type === "radio" && t.name !== "", et = (t, o) => {
29
+ if (!_(t))
15
30
  return !1;
16
- for (let r = e; r; r = r.parentElement) {
17
- const { display: n, contentVisibility: u } = getComputedStyle(r);
18
- if (n === "none" || r !== e && u === "hidden")
19
- return !1;
20
- }
21
- return !0;
22
- }, _ = (e) => e instanceof HTMLInputElement && e.type === "radio" && e.name !== "", Z = (e, t) => {
23
- if (!_(e))
24
- return !1;
25
- const r = t.filter(
26
- (n) => _(n) && n.name === e.name && n.form === e.form
31
+ const n = o.filter(
32
+ (e) => _(e) && e.name === t.name && e.form === t.form
27
33
  );
28
- return (r.find((n) => n.checked) ?? r[0]) !== e;
29
- }, $ = (e) => {
30
- for (let t = e.parentElement; t; t = t.parentElement)
31
- if (t instanceof HTMLDetailsElement && !t.open) {
32
- const r = Array.from(t.children).find((n) => n.tagName === "SUMMARY");
33
- if (!(r != null && r.contains(e)))
34
- return !0;
35
- }
36
- return !1;
37
- }, J = (e) => e.map((t, r) => ({ element: t, index: r })).sort((t, r) => t.element.tabIndex !== r.element.tabIndex ? t.element.tabIndex === 0 ? 1 : r.element.tabIndex === 0 ? -1 : t.element.tabIndex - r.element.tabIndex : t.index - r.index).map(({ element: t }) => t), I = (e) => {
38
- const t = Array.from(e.querySelectorAll(q)).filter(
39
- (r) => P(r) && !$(r)
34
+ return (n.find((e) => e.checked) ?? n[0]) !== t;
35
+ }, ot = (t, o) => t.map((n, e) => ({ element: n, index: e, tabIndex: o(n) })).sort((n, e) => n.tabIndex !== e.tabIndex ? n.tabIndex === 0 ? 1 : e.tabIndex === 0 ? -1 : n.tabIndex - e.tabIndex : n.index - e.index).map(({ element: n }) => n), x = (t) => t instanceof HTMLSlotElement && t.getRootNode() instanceof ShadowRoot, I = (t, o = []) => {
36
+ for (const n of t)
37
+ o.push(n), !n.shadowRoot && !x(n) && I(n.children, o);
38
+ return o;
39
+ }, L = (t, o) => {
40
+ const n = t.filter(
41
+ (r) => r instanceof HTMLElement && r.matches(J) && nt(r, o)
42
+ ), e = new Set(
43
+ n.filter((r) => !et(r, n))
44
+ ), a = t.filter(
45
+ (r) => e.has(r) || r.shadowRoot || x(r)
40
46
  );
41
- return J(t.filter((r) => !Z(r, t)));
42
- }, Q = (e) => I(e)[0] ?? null, c = [], d = /* @__PURE__ */ new Set(), L = /* @__PURE__ */ new Map(), O = /* @__PURE__ */ new Map(), h = /* @__PURE__ */ new Set(), X = () => {
47
+ return ot(
48
+ a,
49
+ (r) => e.has(r) ? r.tabIndex : 0
50
+ ).flatMap((r) => {
51
+ if (x(r))
52
+ return L(I(r.assignedElements({ flatten: !0 })), o);
53
+ const s = r.shadowRoot;
54
+ if (s && Number.parseInt(r.getAttribute("tabindex") ?? "", 10) < 0)
55
+ return [];
56
+ const c = e.has(r) && !(s != null && s.delegatesFocus) ? [r] : [];
57
+ return s ? [...c, ...L(I(s.children), o)] : c;
58
+ });
59
+ }, C = (t) => L(I(t.children), tt()), rt = (t, o) => {
60
+ const n = (r) => {
61
+ const s = [r];
62
+ for (let c = r.getRootNode(); c instanceof ShadowRoot; c = c.host.getRootNode())
63
+ s.push(c.host);
64
+ return s;
65
+ }, e = n(t), a = n(o);
66
+ for (const r of e) {
67
+ const s = a.find((c) => c.getRootNode() === r.getRootNode());
68
+ if (s)
69
+ return r !== s ? r.compareDocumentPosition(s) : r === t ? Node.DOCUMENT_POSITION_FOLLOWING : Node.DOCUMENT_POSITION_PRECEDING;
70
+ }
71
+ return Node.DOCUMENT_POSITION_DISCONNECTED;
72
+ }, st = (t) => C(t)[0] ?? null, u = [], p = /* @__PURE__ */ new Set(), w = /* @__PURE__ */ new Map(), g = /* @__PURE__ */ new Map(), D = /* @__PURE__ */ new Set(), ct = () => {
43
73
  };
44
- let m = [];
45
- const ee = () => {
46
- const e = d.size ? c.map((r) => {
47
- var n;
48
- return (n = L.get(r)) == null ? void 0 : n();
49
- }).filter((r) => !!r) : [];
50
- if (e.length === m.length && e.every((r, n) => r === m[n]))
74
+ let y = [];
75
+ const at = () => {
76
+ const t = p.size ? u.map((n) => {
77
+ var e;
78
+ return (e = w.get(n)) == null ? void 0 : e();
79
+ }).filter((n) => !!n) : [];
80
+ if (t.length === y.length && t.every((n, e) => n === y[e]))
51
81
  return;
52
- const t = new Set(e);
53
- for (const r of m)
54
- t.has(r) || M(r);
55
- for (const r of e)
56
- j(r);
57
- m = e;
58
- }, b = () => {
59
- for (const e of h)
60
- e();
61
- }, B = (e) => (h.add(e), () => {
62
- h.delete(e);
63
- }), g = (e, t, r, n) => {
64
- const u = n[0], a = n[n.length - 1], o = t !== null && r.contains(t);
65
- if (t && o && t !== r && !n.includes(t)) {
66
- const i = e.shiftKey ? Node.DOCUMENT_POSITION_PRECEDING : Node.DOCUMENT_POSITION_FOLLOWING;
67
- n.some((l) => t.compareDocumentPosition(l) & i) || (e.preventDefault(), (e.shiftKey ? a : u).focus());
82
+ const o = new Set(t);
83
+ for (const n of y)
84
+ o.has(n) || U(n);
85
+ for (const n of t)
86
+ q(n);
87
+ y = t;
88
+ }, O = () => {
89
+ for (const t of D)
90
+ t();
91
+ }, B = (t) => (D.add(t), () => {
92
+ D.delete(t);
93
+ }), M = (t, o, n, e) => {
94
+ const a = e[0], r = e[e.length - 1], s = F(n, o);
95
+ if (o && s && o !== n && !e.includes(o)) {
96
+ const c = t.shiftKey ? Node.DOCUMENT_POSITION_PRECEDING : Node.DOCUMENT_POSITION_FOLLOWING;
97
+ e.some((d) => rt(o, d) & c) || (t.preventDefault(), (t.shiftKey ? r : a).focus());
68
98
  return;
69
99
  }
70
- e.shiftKey ? (!o || t === u || t === r) && (e.preventDefault(), a.focus()) : (!o || t === a || t === r) && (e.preventDefault(), u.focus());
71
- }, w = (e) => {
72
- if (e.key !== "Tab" || e.defaultPrevented)
100
+ t.shiftKey ? (!s || o === a || o === n) && (t.preventDefault(), r.focus()) : (!s || o === r || o === n) && (t.preventDefault(), a.focus());
101
+ }, v = (t) => {
102
+ if (t.key !== "Tab" || t.defaultPrevented)
73
103
  return;
74
- let t = null, r = 0;
75
- for (let o = c.length - 1; o >= 0 && !t; o -= 1)
76
- t = c[o](), t && (r = O.get(c[o]) ?? 0);
77
- if (!t)
104
+ let o = null, n = 0;
105
+ for (let s = u.length - 1; s >= 0 && !o; s -= 1)
106
+ o = u[s](), o && (n = g.get(u[s]) ?? 0);
107
+ if (!o)
78
108
  return;
79
- const n = k(), u = n ? H(n) : null;
80
- if (n && u && u.ticket > r) {
81
- const o = u.popup.shadowRoot ?? u.popup, i = I(o);
82
- if (i.length === 0) {
83
- e.preventDefault();
109
+ const e = k(), a = e ? Y(e) : null;
110
+ if (e && a && a.ticket > n) {
111
+ const s = a.popup.shadowRoot ?? a.popup, c = C(s);
112
+ if (c.length === 0) {
113
+ t.preventDefault();
84
114
  return;
85
115
  }
86
- g(e, n, o, i);
116
+ M(t, e, s, c);
87
117
  return;
88
118
  }
89
- const a = I(t);
90
- if (a.length === 0) {
91
- e.preventDefault(), t.focus();
119
+ const r = C(o);
120
+ if (r.length === 0) {
121
+ t.preventDefault(), o.focus();
92
122
  return;
93
123
  }
94
- g(e, n, t, a);
95
- }, te = (e, t) => (c.length === 0 && document.addEventListener("keydown", w), c.push(e), L.set(e, t), O.set(e, K()), b(), () => {
96
- const r = c.lastIndexOf(e);
97
- r !== -1 && c.splice(r, 1), d.delete(e), L.delete(e), O.delete(e);
98
- const n = t();
99
- n && M(n), c.length === 0 && document.removeEventListener("keydown", w), b();
100
- }), pe = () => y(
124
+ M(t, e, o, r);
125
+ }, it = (t, o) => (u.length === 0 && document.addEventListener("keydown", v), u.push(t), w.set(t, o), g.set(t, V()), O(), () => {
126
+ const n = u.lastIndexOf(t);
127
+ n !== -1 && u.splice(n, 1), p.delete(t), w.delete(t), g.delete(t);
128
+ const e = o();
129
+ e && U(e), u.length === 0 && document.removeEventListener("keydown", v), O();
130
+ }), Tt = () => b(
101
131
  B,
102
- () => c.length > 0,
132
+ () => u.length > 0,
103
133
  () => !1
104
- ), A = (e, t) => {
105
- if (t === "content") {
106
- const r = k();
107
- if (r && e.contains(r))
134
+ ), A = (t, o) => {
135
+ if (o === "content") {
136
+ const n = k();
137
+ if (F(t, n))
108
138
  return;
109
- (Q(e) ?? e).focus();
139
+ (st(t) ?? t).focus();
110
140
  return;
111
141
  }
112
- e.focus();
113
- }, me = ({
114
- open: e,
115
- onClose: t,
116
- elevated: r = !1,
117
- hostRef: n,
118
- initialFocus: u = "panel"
142
+ t.focus();
143
+ }, Nt = ({
144
+ open: t,
145
+ onClose: o,
146
+ elevated: n = !1,
147
+ hostRef: e,
148
+ initialFocus: a = "panel"
119
149
  }) => {
120
- const a = f(null), o = f(() => a.current).current;
121
- V(
122
- x(() => (n == null ? void 0 : n.current) ?? a.current, [n]),
123
- e
150
+ const r = f(null), s = f(() => r.current).current;
151
+ j(
152
+ N(() => (e == null ? void 0 : e.current) ?? r.current, [e]),
153
+ t
124
154
  );
125
- const i = f(null), l = f(null), C = f(u), N = f(t);
126
- T(() => {
127
- N.current = t;
128
- }, [t]), T(() => {
129
- if (e)
130
- return i.current = k(), () => {
131
- const s = i.current;
132
- i.current = null, s != null && s.isConnected && s.focus();
155
+ const c = f(null), d = f(null), h = f(a), l = f(o);
156
+ E(() => {
157
+ l.current = o;
158
+ }, [o]), E(() => {
159
+ if (t)
160
+ return c.current = k(), () => {
161
+ const i = c.current;
162
+ c.current = null, i != null && i.isConnected && i.focus();
133
163
  };
134
- }, [e]), T(() => {
135
- if (!e)
164
+ }, [t]), E(() => {
165
+ if (!t)
136
166
  return;
137
- l.current = o;
138
- const s = R(() => N.current()), U = Y(), G = te(o, () => (n == null ? void 0 : n.current) ?? null);
139
- return a.current && A(a.current, C.current), () => {
140
- l.current = null, G(), s(), U();
167
+ d.current = s;
168
+ const i = K(() => l.current()), H = $(), z = it(s, () => (e == null ? void 0 : e.current) ?? null);
169
+ return r.current && A(r.current, h.current), () => {
170
+ d.current = null, z(), i(), H();
141
171
  };
142
- }, [e, n, o]), E(() => {
143
- if (!(!e || !r))
144
- return d.add(o), b(), () => {
145
- d.delete(o), b();
172
+ }, [t, e, s]), m(() => {
173
+ if (!(!t || !n))
174
+ return p.add(s), O(), () => {
175
+ p.delete(s), O();
146
176
  };
147
- }, [e, r, o]);
148
- const S = x(
149
- (s) => e ? B(s) : X,
150
- [e]
151
- ), F = y(
152
- S,
177
+ }, [t, n, s]);
178
+ const T = N(
179
+ (i) => t ? B(i) : ct,
180
+ [t]
181
+ ), G = b(
182
+ T,
153
183
  // Gated on `open` so an overlay fading out stops claiming modal. Before it registers
154
184
  // it is the one about to go on top, or aria-modal would drop for a frame on open.
155
- () => e && (l.current === null || c[c.length - 1] === l.current),
185
+ () => t && (d.current === null || u[u.length - 1] === d.current),
156
186
  () => !0
157
- ), p = y(
158
- S,
159
- () => e && (r || d.size > 0),
187
+ ), S = b(
188
+ T,
189
+ () => t && (n || p.size > 0),
160
190
  () => !1
161
- ), v = y(
162
- S,
191
+ ), P = b(
192
+ T,
163
193
  () => {
164
- const s = l.current;
165
- return D + (e && s ? Math.max(0, c.indexOf(s)) : 0);
194
+ const i = d.current;
195
+ return R + (t && i ? Math.max(0, u.indexOf(i)) : 0);
166
196
  },
167
- () => D
197
+ () => R
168
198
  );
169
- return E(() => {
170
- p && W();
171
- }, [p]), E(() => {
172
- ee();
173
- }), { ref: x((s) => {
174
- a.current = s, s && A(s, C.current);
175
- }, []), isTopmost: F, zIndex: p ? void 0 : v, elevated: p };
199
+ return m(() => {
200
+ S && Z();
201
+ }, [S]), m(() => {
202
+ at();
203
+ }), { ref: N((i) => {
204
+ r.current = i, i && A(i, h.current);
205
+ }, []), isTopmost: G, zIndex: S ? void 0 : P, elevated: S };
176
206
  };
177
207
  export {
178
- q as FOCUSABLE_SELECTOR,
179
- de as OVERLAY_ICON_BUTTON_CLASSES,
180
- Q as firstTabStopIn,
181
- pe as useHasOpenOverlay,
182
- me as useOverlayBehaviour
208
+ J as FOCUSABLE_SELECTOR,
209
+ Ot as OVERLAY_ICON_BUTTON_CLASSES,
210
+ st as firstTabStopIn,
211
+ Tt as useHasOpenOverlay,
212
+ Nt as useOverlayBehaviour
183
213
  };
184
214
  //# 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 { 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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"}
1
+ {"version":3,"file":"overlayBehaviour.js","sources":["../../src/components/overlayBehaviour.ts"],"sourcesContent":["import { useCallback, useEffect, useLayoutEffect, useRef, useSyncExternalStore } from 'react';\nimport { containsComposed, 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// The flat-tree parent: a slotted element renders under its slot, a shadow root under its host.\nconst flatParent = (element: Element): Element | null =>\n element.assignedSlot ??\n element.parentElement ??\n (element.parentNode instanceof ShadowRoot ? element.parentNode.host : null);\n\n// `children`, not `:scope > summary`: inside a shadow root jsdom's selector engine matches\n// nothing for `:scope`, which silently hid the summary along with the rest.\nconst firstSummaryOf = (details: HTMLDetailsElement): Element | undefined =>\n Array.from(details.children).find((child) => child.tagName === 'SUMMARY');\n\ntype RenderState = { hidden: boolean; invisible: boolean; hidesContent: boolean };\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.\n// One per Tab press, so an ancestor shared by many controls has its style read once.\nconst createRenderStates = (): ((element: Element) => RenderState) => {\n const states = new Map<Element, RenderState>();\n const stateOf = (element: Element): RenderState => {\n const cached = states.get(element);\n if (cached) {\n return cached;\n }\n const { display, visibility, contentVisibility } = getComputedStyle(element);\n const parent = flatParent(element);\n const parentState = parent ? stateOf(parent) : null;\n // A closed `<details>` hides all but its summary in the UA shadow slot, so the hidden\n // descendants keep `tabIndex === 0` and a normal computed display.\n const closedDisclosure =\n parent instanceof HTMLDetailsElement &&\n !parent.open &&\n firstSummaryOf(parent) !== element;\n const state = {\n hidden:\n element.hasAttribute('inert') ||\n display === 'none' ||\n closedDisclosure ||\n Boolean(parentState && (parentState.hidden || parentState.hidesContent)),\n invisible: visibility === 'hidden' || visibility === 'collapse',\n hidesContent: contentVisibility === 'hidden',\n };\n states.set(element, state);\n return state;\n };\n return stateOf;\n};\n\nconst isTabStop = (element: HTMLElement, stateOf: (element: Element) => RenderState): boolean => {\n if (element.tabIndex < 0 || element.matches(':disabled')) {\n return false;\n }\n const { hidden, invisible } = stateOf(element);\n\n return !hidden && !invisible;\n};\n\nconst isGroupedRadio = (element: Element): 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\nconst inTabOrder = (entries: Element[], tabIndexOf: (element: Element) => number): Element[] =>\n entries\n .map((element, index) => ({ element, index, tabIndex: tabIndexOf(element) }))\n .sort((left, right) => {\n if (left.tabIndex !== right.tabIndex) {\n if (left.tabIndex === 0) {\n return 1;\n }\n if (right.tabIndex === 0) {\n return -1;\n }\n\n return left.tabIndex - right.tabIndex;\n }\n\n return left.index - right.index;\n })\n .map(({ element }) => element);\n\n// Only a slot inside a shadow root projects assigned content; elsewhere it renders its children.\nconst isShadowSlot = (element: Element): element is HTMLSlotElement =>\n element instanceof HTMLSlotElement && element.getRootNode() instanceof ShadowRoot;\n\n// One focus navigation scope in tree order. A host's light children render only through its\n// slots, and hosts and slots each own a scope, so the walk descends into neither.\nconst scopeMembers = (elements: Iterable<Element>, into: Element[] = []): Element[] => {\n for (const element of elements) {\n into.push(element);\n if (!element.shadowRoot && !isShadowSlot(element)) {\n scopeMembers(element.children, into);\n }\n }\n return into;\n};\n\n// Ordered per scope, as browsers do: positive tabindex orders within its own scope, and a nested\n// scope takes its owner's place, an owner that is no stop itself ordering as tabindex 0.\nconst stopsInScope = (\n members: Element[],\n stateOf: (element: Element) => RenderState,\n): HTMLElement[] => {\n const reachable = members.filter(\n (element): element is HTMLElement =>\n element instanceof HTMLElement &&\n element.matches(FOCUSABLE_SELECTOR) &&\n isTabStop(element, stateOf),\n );\n const stops = new Set<Element>(\n reachable.filter((element) => !skipsWithinRadioGroup(element, reachable)),\n );\n const entries = members.filter(\n (element) => stops.has(element) || element.shadowRoot || isShadowSlot(element),\n );\n\n return inTabOrder(entries, (element) =>\n stops.has(element) ? (element as HTMLElement).tabIndex : 0,\n ).flatMap((element) => {\n if (isShadowSlot(element)) {\n return stopsInScope(scopeMembers(element.assignedElements({ flatten: true })), stateOf);\n }\n const root = element.shadowRoot;\n // A host given a negative tabindex keeps its whole shadow tree out of the order; an\n // unparsable value counts as absent, as it does for any element.\n if (root && Number.parseInt(element.getAttribute('tabindex') ?? '', 10) < 0) {\n return [];\n }\n const own = stops.has(element) && !root?.delegatesFocus ? [element as HTMLElement] : [];\n\n return root ? [...own, ...stopsInScope(scopeMembers(root.children), stateOf)] : own;\n });\n};\n\nconst tabStopsIn = (root: ParentNode): HTMLElement[] =>\n stopsInScope(scopeMembers(root.children), createRenderStates());\n\n// `compareDocumentPosition` calls nodes in different shadow trees disconnected, so lift both\n// into their deepest shared tree first; a host precedes its own shadow content.\nconst composedPosition = (from: Node, to: Node): number => {\n const ancestry = (node: Node): Node[] => {\n const chain = [node];\n for (\n let root = node.getRootNode();\n root instanceof ShadowRoot;\n root = root.host.getRootNode()\n ) {\n chain.push(root.host);\n }\n return chain;\n };\n const fromChain = ancestry(from);\n const toChain = ancestry(to);\n for (const fromStep of fromChain) {\n const toStep = toChain.find((step) => step.getRootNode() === fromStep.getRootNode());\n if (!toStep) {\n continue;\n }\n if (fromStep !== toStep) {\n return fromStep.compareDocumentPosition(toStep);\n }\n\n return fromStep === from\n ? Node.DOCUMENT_POSITION_FOLLOWING\n : Node.DOCUMENT_POSITION_PRECEDING;\n }\n\n return Node.DOCUMENT_POSITION_DISCONNECTED;\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 = containsComposed(within, 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) => composedPosition(active, 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 (containsComposed(panel, 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","flatParent","element","firstSummaryOf","details","child","createRenderStates","states","stateOf","cached","display","visibility","contentVisibility","parent","parentState","closedDisclosure","state","isTabStop","hidden","invisible","isGroupedRadio","skipsWithinRadioGroup","reachable","group","candidate","radio","inTabOrder","entries","tabIndexOf","index","left","right","isShadowSlot","scopeMembers","elements","into","stopsInScope","members","stops","root","own","tabStopsIn","composedPosition","from","to","ancestry","node","chain","fromChain","toChain","fromStep","toStep","step","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","containsComposed","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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"}
@@ -1,34 +1,28 @@
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);
1
+ import { useEffect as p } from "react";
2
+ import { containsComposed as s } from "./activeElement.js";
3
+ import { OVERLAY_CLASS as u, OVERLAY_ATTR as c } from "./portalOverlay.js";
4
+ let n = 0;
5
+ const i = () => (n += 1, n), o = [], f = (r) => {
6
+ const e = { source: r, ticket: i() };
7
+ return o.push(e), () => {
8
+ const t = o.indexOf(e);
9
+ t !== -1 && o.splice(t, 1);
9
10
  };
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 };
11
+ }, m = (r) => {
12
+ for (let e = o.length - 1; e >= 0; e -= 1) {
13
+ const t = o[e].source();
14
+ if (t && s(t, r))
15
+ return { popup: t, ticket: o[e].ticket };
22
16
  }
23
17
  return null;
24
- }, d = (r, t, e) => (u(() => {
25
- if (t)
26
- return i(() => e.current);
27
- }, [t, e]), { className: s[r], [c]: r });
18
+ }, y = (r, e, t) => (p(() => {
19
+ if (e)
20
+ return f(() => t.current);
21
+ }, [e, t]), { className: u[r], [c]: r });
28
22
  export {
29
- p as nextLayerTicket,
30
- L as popupLayerHolding,
31
- i as pushPopupLayer,
32
- d as usePopupLayer
23
+ i as nextLayerTicket,
24
+ m as popupLayerHolding,
25
+ f as pushPopupLayer,
26
+ y as usePopupLayer
33
27
  };
34
28
  //# sourceMappingURL=overlayLayers.js.map
@@ -1 +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
+ {"version":3,"file":"overlayLayers.js","sources":["../../src/components/overlayLayers.ts"],"sourcesContent":["import { type RefObject, useEffect } from 'react';\nimport { containsComposed } from './activeElement';\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/** 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 && containsComposed(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","popupLayerHolding","node","popup","containsComposed","usePopupLayer","name","open","ref","useEffect","OVERLAY_CLASS","OVERLAY_ATTR"],"mappings":";;;AASA,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,GAGaC,IAAoB,CAACC,MAA8D;AAC5F,WAASF,IAAQJ,EAAY,SAAS,GAAGI,KAAS,GAAGA,KAAS,GAAG;AAC7D,UAAMG,IAAQP,EAAYI,CAAK,EAAE,OAAA;AACjC,QAAIG,KAASC,EAAiBD,GAAOD,CAAI;AACrC,aAAO,EAAE,OAAAC,GAAO,QAAQP,EAAYI,CAAK,EAAE,OAAA;AAAA,EAEnD;AACA,SAAO;AACX,GAIaK,IAAgB,CACzBC,GACAC,GACAC,OAEAC,EAAU,MAAM;AACZ,MAAKF;AAGL,WAAOV,EAAe,MAAMW,EAAI,OAAO;AAC3C,GAAG,CAACD,GAAMC,CAAG,CAAC,GAEP,EAAE,WAAWE,EAAcJ,CAAI,GAAG,CAACK,CAAY,GAAGL,EAAA;"}