@lablup/ui-common 0.2.0-alpha.16 → 0.2.0-alpha.17

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.
@@ -5,14 +5,13 @@ import { RefObject } from 'react';
5
5
  */
6
6
  export declare const MODAL_OPEN_ATTRIBUTE = "data-uic-modal-open";
7
7
  /**
8
- * The open `<dialog>` a surface inside it renders into, instead of
9
- * `document.body`. A drawer is top layer whether modal (`showModal()`, which
10
- * also inerts everything outside it) or not (`showPopover()`), so a `Modal`
11
- * portalled to the body from inside one would sit behind the drawer,
12
- * unreachable. `Drawer` (src/forks/Drawer) provides its dialog while it is
13
- * open; `Modal` renders there and enters the top layer itself, as a manual
14
- * popover, so it paints above the drawer and escapes the panel's clipping
15
- * and transform. Internal: not exported from the package.
8
+ * The open modal `<dialog>` a surface inside it renders into, instead of
9
+ * `document.body`. `showModal()` makes everything outside the dialog inert,
10
+ * so a `Modal` portalled to the body from inside a scrimmed drawer would sit
11
+ * behind the drawer, unreachable. `Drawer` (src/forks/Drawer) provides its
12
+ * dialog while it is modal; `Modal` renders there and enters the top layer
13
+ * itself, as a manual popover, so it paints above the drawer and escapes the
14
+ * panel's clipping and transform. Internal: not exported from the package.
16
15
  */
17
16
  export declare const ModalPortalContext: import('react').Context<HTMLElement | null>;
18
17
  /** Nesting depth ceiling. Two surfaces past it share a level. */
@@ -33,6 +32,8 @@ export interface ModalZIndexBand {
33
32
  * `{ base: 1100, step: 10, max: 10999 }`. Pass nothing to restore it.
34
33
  */
35
34
  export declare function configureModalZIndex(next?: Partial<ModalZIndexBand>): void;
35
+ /** The band's first level; a scrimless Drawer (src/forks/Drawer) stays below it. */
36
+ export declare function modalZIndexBase(): number;
36
37
  /** A claim on the stack. Released by reference, never by level. */
37
38
  export interface ModalLevelEntry {
38
39
  level: number;
@@ -13,69 +13,72 @@ function d(e) {
13
13
  ...e
14
14
  };
15
15
  }
16
- var f = "--modal-level", p = "--modal-z", m = [];
17
- function h(e, t) {
16
+ function f() {
17
+ return u.base;
18
+ }
19
+ var p = "--modal-level", m = "--modal-z", h = [];
20
+ function g(e, t) {
18
21
  let n = t;
19
22
  n != null && (n < u.base || n > u.max) && (a("Modal", `zIndex ${n} is outside the modal band (${u.base}..${u.max}); ignoring it. Change the band with configureModalZIndex instead.`), n = void 0);
20
23
  let r = e?.zIndex ?? u.base - u.step;
21
24
  return Math.min(Math.max(r + u.step, n ?? 0), u.max);
22
25
  }
23
- var g = "data-uic-modal-live", _ = /* @__PURE__ */ new Set(), v = /* @__PURE__ */ new Set([
26
+ var _ = "data-uic-modal-live", v = /* @__PURE__ */ new Set(), y = /* @__PURE__ */ new Set([
24
27
  "SCRIPT",
25
28
  "STYLE",
26
29
  "LINK",
27
30
  "TEMPLATE",
28
31
  "NOSCRIPT"
29
32
  ]);
30
- function y(e) {
33
+ function b(e) {
31
34
  let t = new Set(e), n = /* @__PURE__ */ new Set();
32
35
  for (let t of e) for (let e = t.parentElement; e && e !== document.body;) n.add(e), e = e.parentElement;
33
36
  let r = [], i = (e) => {
34
- for (let a of Array.from(e.children)) t.has(a) || v.has(a.tagName) || (n.has(a) ? i(a) : r.push(a));
37
+ for (let a of Array.from(e.children)) t.has(a) || y.has(a.tagName) || (n.has(a) ? i(a) : r.push(a));
35
38
  };
36
39
  return i(document.body), r;
37
40
  }
38
- function b() {
39
- if (typeof document > "u" || !document.body) return;
40
- let e = m.map((e) => e.root).filter((e) => e != null && e.isConnected), t = new Set(m.length === 0 ? [] : y([...e, ...Array.from(document.querySelectorAll(`[${g}]`))]));
41
- for (let e of _) t.has(e) || (e.removeAttribute("inert"), _.delete(e));
42
- for (let e of t) _.has(e) || e.hasAttribute("inert") || (e.setAttribute("inert", ""), _.add(e));
43
- }
44
41
  function x() {
45
- (m.length > 0 || _.size > 0) && b();
42
+ if (typeof document > "u" || !document.body) return;
43
+ let e = h.map((e) => e.root).filter((e) => e != null && e.isConnected), t = new Set(h.length === 0 ? [] : b([...e, ...Array.from(document.querySelectorAll(`[${_}]`))]));
44
+ for (let e of v) t.has(e) || (e.removeAttribute("inert"), v.delete(e));
45
+ for (let e of t) v.has(e) || e.hasAttribute("inert") || (e.setAttribute("inert", ""), v.add(e));
46
46
  }
47
47
  function S() {
48
- b(), m.forEach((e, t) => {
49
- let n = t === m.length - 1;
48
+ (h.length > 0 || v.size > 0) && x();
49
+ }
50
+ function C() {
51
+ x(), h.forEach((e, t) => {
52
+ let n = t === h.length - 1;
50
53
  e.root?.toggleAttribute("inert", !n), e.isTopmost !== n && (e.isTopmost = n, e.setIsTopmost(n));
51
54
  });
52
55
  }
53
- function C(t, n, r) {
56
+ function w(t, n, r) {
54
57
  e();
55
- let i = m.at(-1), a = {
58
+ let i = h.at(-1), a = {
56
59
  level: Math.min((i?.level ?? -1) + 1, 80),
57
- zIndex: h(i, r),
60
+ zIndex: g(i, r),
58
61
  isTopmost: !0,
59
62
  root: t,
60
63
  setIsTopmost: n
61
64
  };
62
- return m.push(a), S(), a;
65
+ return h.push(a), C(), a;
63
66
  }
64
- function w(e) {
65
- let t = m.indexOf(e);
66
- t !== -1 && (e.root?.removeAttribute("inert"), m.splice(t, 1), S());
67
+ function T(e) {
68
+ let t = h.indexOf(e);
69
+ t !== -1 && (e.root?.removeAttribute("inert"), h.splice(t, 1), C());
67
70
  }
68
- function T(e, t, a) {
71
+ function E(e, t, a) {
69
72
  let [o, s] = i(!0), c = n(() => a);
70
73
  return r(() => {
71
74
  if (!t) return;
72
- let n = e.current, r = C(n, s, c());
73
- return n?.style.setProperty(f, String(r.level)), n?.style.setProperty(p, String(r.zIndex)), () => {
74
- w(r), n?.style.removeProperty(f), n?.style.removeProperty(p);
75
+ let n = e.current, r = w(n, s, c());
76
+ return n?.style.setProperty(p, String(r.level)), n?.style.setProperty(m, String(r.zIndex)), () => {
77
+ T(r), n?.style.removeProperty(p), n?.style.removeProperty(m);
75
78
  };
76
79
  }, [t, e]), o;
77
80
  }
78
81
  //#endregion
79
- export { c as MAX_MODAL_LEVEL, g as MODAL_LIVE_ATTRIBUTE, o as MODAL_OPEN_ATTRIBUTE, s as ModalPortalContext, C as claimModalLevel, d as configureModalZIndex, x as refreshModalBackground, w as releaseModalLevel, T as useModalLevel };
82
+ export { c as MAX_MODAL_LEVEL, _ as MODAL_LIVE_ATTRIBUTE, o as MODAL_OPEN_ATTRIBUTE, s as ModalPortalContext, w as claimModalLevel, d as configureModalZIndex, f as modalZIndexBase, S as refreshModalBackground, T as releaseModalLevel, E as useModalLevel };
80
83
 
81
84
  //# sourceMappingURL=modalStack.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"modalStack.js","names":[],"sources":["../../../src/components/Modal/modalStack.ts"],"sourcesContent":["/**\n * The one level stack every portalled modal surface shares.\n *\n * `Modal` renders into a `document.body` portal instead of the browser's top\n * layer (see Modal.tsx for why). Without the top layer, stacking and\n * inertness are this module's job: a surface opened from inside another\n * paints above it, only the topmost one is interactive, and while any is\n * open the page behind them is inert, except what is marked\n * `MODAL_LIVE_ATTRIBUTE`.\n *\n * Other portalled modal surfaces (a scrimmed drawer, say) claim a level with\n * `useModalLevel` so they stack on the same order as `Modal`.\n */\nimport {\n createContext,\n useEffectEvent,\n useLayoutEffect,\n useState,\n type RefObject,\n} from \"react\";\nimport { devWarn } from \"@astryxdesign/core/utils\";\n\nimport { registerInstance } from \"../../instance\";\n\n/**\n * Marks an open modal root. Scope document queries to it through this\n * constant rather than retyping the string.\n */\nexport const MODAL_OPEN_ATTRIBUTE = \"data-uic-modal-open\";\n\n/**\n * The open `<dialog>` a surface inside it renders into, instead of\n * `document.body`. A drawer is top layer whether modal (`showModal()`, which\n * also inerts everything outside it) or not (`showPopover()`), so a `Modal`\n * portalled to the body from inside one would sit behind the drawer,\n * unreachable. `Drawer` (src/forks/Drawer) provides its dialog while it is\n * open; `Modal` renders there and enters the top layer itself, as a manual\n * popover, so it paints above the drawer and escapes the panel's clipping\n * and transform. Internal: not exported from the package.\n */\nexport const ModalPortalContext = createContext<HTMLElement | null>(null);\n\n/** Nesting depth ceiling. Two surfaces past it share a level. */\nexport const MAX_MODAL_LEVEL = 80;\n\n/**\n * The z-index band modal roots paint in. `base` is the first level, each\n * nested level adds `step`, and nothing paints above `max`, so whatever the\n * app layers above the band (a notification stack) stays above every modal.\n */\nexport interface ModalZIndexBand {\n base: number;\n step: number;\n max: number;\n}\n\nconst DEFAULT_BAND: ModalZIndexBand = { base: 1100, step: 10, max: 10999 };\nlet band: ModalZIndexBand = DEFAULT_BAND;\n\n/**\n * Sets the z-index band for every modal opened after the call. Call it once,\n * at startup, to fit the app's own layer ladder. The default is\n * `{ base: 1100, step: 10, max: 10999 }`. Pass nothing to restore it.\n */\nexport function configureModalZIndex(next?: Partial<ModalZIndexBand>): void {\n band = { ...DEFAULT_BAND, ...next };\n}\n\nconst LEVEL_CSS_VAR = \"--modal-level\";\nconst Z_INDEX_CSS_VAR = \"--modal-z\";\n\n/** A claim on the stack. Released by reference, never by level. */\nexport interface ModalLevelEntry {\n level: number;\n zIndex: number;\n isTopmost: boolean;\n root: HTMLElement | null;\n setIsTopmost: (isTopmost: boolean) => void;\n}\n\nconst openModals: ModalLevelEntry[] = [];\n\n/**\n * One step above the topmost surface (or the band's base), raised by an\n * accepted override. Stacking and inertness therefore run on one order: a\n * surface opened later paints above an earlier one, whatever that one asked.\n */\nfunction resolveZIndex(\n topmost: ModalLevelEntry | undefined,\n requested?: number,\n): number {\n let override = requested;\n if (override != null && (override < band.base || override > band.max)) {\n devWarn(\n \"Modal\",\n `zIndex ${override} is outside the modal band (${band.base}..${band.max}); ` +\n \"ignoring it. Change the band with configureModalZIndex instead.\",\n );\n override = undefined;\n }\n const floor = topmost?.zIndex ?? band.base - band.step;\n return Math.min(Math.max(floor + band.step, override ?? 0), band.max);\n}\n\n/**\n * Marks an element that stays reachable while a modal is open: it, and the\n * ancestors it needs, are never made inert. `NotificationStack` sets it on\n * its root, so a notice raised over a modal can still be read and dismissed.\n * The mark is read when a modal opens or closes and on\n * `refreshModalBackground()`; call that after mounting a marked element while\n * a modal is open.\n */\nexport const MODAL_LIVE_ATTRIBUTE = \"data-uic-modal-live\";\n\n/** The background elements this module made inert, and only those. */\nconst inertedBackground = new Set<Element>();\n\nconst NEVER_INERT = new Set([\"SCRIPT\", \"STYLE\", \"LINK\", \"TEMPLATE\", \"NOSCRIPT\"]);\n\n/**\n * The elements to inert while `keep` stays reachable: every child of\n * `document.body` that holds none of them, and, inside a body child that\n * does, every sibling on the way down to them.\n */\nfunction backgroundOf(keep: Element[]): Element[] {\n const kept = new Set(keep);\n const onPath = new Set<Element>();\n for (const el of keep) {\n for (let node = el.parentElement; node && node !== document.body;) {\n onPath.add(node);\n node = node.parentElement;\n }\n }\n const out: Element[] = [];\n const visit = (parent: Element) => {\n for (const child of Array.from(parent.children)) {\n if (kept.has(child) || NEVER_INERT.has(child.tagName)) continue;\n if (onPath.has(child)) visit(child);\n else out.push(child);\n }\n };\n visit(document.body);\n return out;\n}\n\n/**\n * While any modal is open the rest of the page is `inert`, which is what\n * `aria-modal` promises. Modal roots (a drawer's too, when it claims a level\n * through `useModalLevel`) and elements marked `MODAL_LIVE_ATTRIBUTE` are\n * spared. An `inert` the page set itself is left alone, before and after.\n */\nfunction syncBackground(): void {\n if (typeof document === \"undefined\" || !document.body) return;\n const roots = openModals\n .map((entry) => entry.root)\n .filter((root): root is HTMLElement => root != null && root.isConnected);\n const target = new Set(\n openModals.length === 0\n ? []\n : backgroundOf([\n ...roots,\n ...Array.from(document.querySelectorAll(`[${MODAL_LIVE_ATTRIBUTE}]`)),\n ]),\n );\n for (const el of inertedBackground) {\n if (target.has(el)) continue;\n el.removeAttribute(\"inert\");\n inertedBackground.delete(el);\n }\n for (const el of target) {\n if (inertedBackground.has(el) || el.hasAttribute(\"inert\")) continue;\n el.setAttribute(\"inert\", \"\");\n inertedBackground.add(el);\n }\n}\n\n/**\n * Re-reads which elements are marked `MODAL_LIVE_ATTRIBUTE` (and which modal\n * roots are in the document) while a modal is open. A no-op otherwise.\n */\nexport function refreshModalBackground(): void {\n if (openModals.length > 0 || inertedBackground.size > 0) syncBackground();\n}\n\n/**\n * Only the topmost surface stays interactive. A covered one drops its focus\n * trap and goes `inert`: the trap alone lets Tab escape to the parent, and\n * `inert` alone leaves the covered trap swallowing Tab with nothing to focus.\n */\nfunction syncCovered(): void {\n syncBackground();\n openModals.forEach((entry, index) => {\n const isTopmost = index === openModals.length - 1;\n entry.root?.toggleAttribute(\"inert\", !isTopmost);\n if (entry.isTopmost !== isTopmost) {\n entry.isTopmost = isTopmost;\n entry.setIsTopmost(isTopmost);\n }\n });\n}\n\nexport function claimModalLevel(\n root: HTMLElement | null,\n setIsTopmost: (isTopmost: boolean) => void,\n requestedZIndex?: number,\n): ModalLevelEntry {\n registerInstance();\n const topmost = openModals.at(-1);\n const entry: ModalLevelEntry = {\n level: Math.min((topmost?.level ?? -1) + 1, MAX_MODAL_LEVEL),\n zIndex: resolveZIndex(topmost, requestedZIndex),\n isTopmost: true,\n root,\n setIsTopmost,\n };\n openModals.push(entry);\n syncCovered();\n return entry;\n}\n\nexport function releaseModalLevel(entry: ModalLevelEntry): void {\n const index = openModals.indexOf(entry);\n if (index === -1) return;\n entry.root?.removeAttribute(\"inert\");\n openModals.splice(index, 1);\n syncCovered();\n}\n\n/**\n * Claims a level for `rootRef` while `isOpen`, and writes the level and the\n * resolved z-index onto the root as `--modal-level` and `--modal-z`.\n * Returns whether this surface is the topmost one; gate a focus trap on it.\n */\nexport function useModalLevel(\n rootRef: RefObject<HTMLElement | null>,\n isOpen: boolean,\n zIndex?: number,\n): boolean {\n const [isTopmost, setIsTopmost] = useState(true);\n // Read at claim time only: re-claiming would move an open surface to the\n // top of the stack and inert whatever legitimately covered it.\n const readZIndex = useEffectEvent(() => zIndex);\n\n useLayoutEffect(() => {\n if (!isOpen) return;\n const root = rootRef.current;\n const entry = claimModalLevel(root, setIsTopmost, readZIndex());\n root?.style.setProperty(LEVEL_CSS_VAR, String(entry.level));\n root?.style.setProperty(Z_INDEX_CSS_VAR, String(entry.zIndex));\n return () => {\n releaseModalLevel(entry);\n root?.style.removeProperty(LEVEL_CSS_VAR);\n root?.style.removeProperty(Z_INDEX_CSS_VAR);\n };\n // An effect event is not a dependency; this plugin version predates it.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [isOpen, rootRef]);\n\n return isTopmost;\n}\n"],"mappings":";;;;AA4BA,IAAa,IAAuB,uBAYvB,IAAqB,EAAkC,IAAI,GAG3D,IAAkB,IAazB,IAAgC;CAAE,MAAM;CAAM,MAAM;CAAI,KAAK;AAAM,GACrE,IAAwB;AAO5B,SAAgB,EAAqB,GAAuC;CAC1E,IAAO;EAAE,GAAG;EAAc,GAAG;CAAK;AACpC;AAEA,IAAM,IAAgB,iBAChB,IAAkB,aAWlB,IAAgC,CAAC;AAOvC,SAAS,EACP,GACA,GACQ;CACR,IAAI,IAAW;CACf,AAAI,KAAY,SAAS,IAAW,EAAK,QAAQ,IAAW,EAAK,SAC/D,EACE,SACA,UAAU,EAAS,8BAA8B,EAAK,KAAK,IAAI,EAAK,IAAI,mEAE1E,GACA,IAAW,KAAA;CAEb,IAAM,IAAQ,GAAS,UAAU,EAAK,OAAO,EAAK;CAClD,OAAO,KAAK,IAAI,KAAK,IAAI,IAAQ,EAAK,MAAM,KAAY,CAAC,GAAG,EAAK,GAAG;AACtE;AAUA,IAAa,IAAuB,uBAG9B,oBAAoB,IAAI,IAAa,GAErC,oBAAc,IAAI,IAAI;CAAC;CAAU;CAAS;CAAQ;CAAY;AAAU,CAAC;AAO/E,SAAS,EAAa,GAA4B;CAChD,IAAM,IAAO,IAAI,IAAI,CAAI,GACnB,oBAAS,IAAI,IAAa;CAChC,KAAK,IAAM,KAAM,GACf,KAAK,IAAI,IAAO,EAAG,eAAe,KAAQ,MAAS,SAAS,OAE1D,AADA,EAAO,IAAI,CAAI,GACf,IAAO,EAAK;CAGhB,IAAM,IAAiB,CAAC,GAClB,KAAS,MAAoB;EACjC,KAAK,IAAM,KAAS,MAAM,KAAK,EAAO,QAAQ,GACxC,EAAK,IAAI,CAAK,KAAK,EAAY,IAAI,EAAM,OAAO,MAChD,EAAO,IAAI,CAAK,IAAG,EAAM,CAAK,IAC7B,EAAI,KAAK,CAAK;CAEvB;CAEA,OADA,EAAM,SAAS,IAAI,GACZ;AACT;AAQA,SAAS,IAAuB;CAC9B,IAAI,OAAO,WAAa,OAAe,CAAC,SAAS,MAAM;CACvD,IAAM,IAAQ,EACX,KAAK,MAAU,EAAM,IAAI,CAAC,CAC1B,QAAQ,MAA8B,KAAQ,QAAQ,EAAK,WAAW,GACnE,IAAS,IAAI,IACjB,EAAW,WAAW,IAClB,CAAC,IACD,EAAa,CACX,GAAG,GACH,GAAG,MAAM,KAAK,SAAS,iBAAiB,IAAI,EAAqB,EAAE,CAAC,CACtE,CAAC,CACP;CACA,KAAK,IAAM,KAAM,GACX,EAAO,IAAI,CAAE,MACjB,EAAG,gBAAgB,OAAO,GAC1B,EAAkB,OAAO,CAAE;CAE7B,KAAK,IAAM,KAAM,GACX,EAAkB,IAAI,CAAE,KAAK,EAAG,aAAa,OAAO,MACxD,EAAG,aAAa,SAAS,EAAE,GAC3B,EAAkB,IAAI,CAAE;AAE5B;AAMA,SAAgB,IAA+B;CAC7C,CAAI,EAAW,SAAS,KAAK,EAAkB,OAAO,MAAG,EAAe;AAC1E;AAOA,SAAS,IAAoB;CAE3B,AADA,EAAe,GACf,EAAW,SAAS,GAAO,MAAU;EACnC,IAAM,IAAY,MAAU,EAAW,SAAS;EAEhD,AADA,EAAM,MAAM,gBAAgB,SAAS,CAAC,CAAS,GAC3C,EAAM,cAAc,MACtB,EAAM,YAAY,GAClB,EAAM,aAAa,CAAS;CAEhC,CAAC;AACH;AAEA,SAAgB,EACd,GACA,GACA,GACiB;CACjB,EAAiB;CACjB,IAAM,IAAU,EAAW,GAAG,EAAE,GAC1B,IAAyB;EAC7B,OAAO,KAAK,KAAK,GAAS,SAAS,MAAM,GAAA,EAAkB;EAC3D,QAAQ,EAAc,GAAS,CAAe;EAC9C,WAAW;EACX;EACA;CACF;CAGA,OAFA,EAAW,KAAK,CAAK,GACrB,EAAY,GACL;AACT;AAEA,SAAgB,EAAkB,GAA8B;CAC9D,IAAM,IAAQ,EAAW,QAAQ,CAAK;CAClC,MAAU,OACd,EAAM,MAAM,gBAAgB,OAAO,GACnC,EAAW,OAAO,GAAO,CAAC,GAC1B,EAAY;AACd;AAOA,SAAgB,EACd,GACA,GACA,GACS;CACT,IAAM,CAAC,GAAW,KAAgB,EAAS,EAAI,GAGzC,IAAa,QAAqB,CAAM;CAiB9C,OAfA,QAAsB;EACpB,IAAI,CAAC,GAAQ;EACb,IAAM,IAAO,EAAQ,SACf,IAAQ,EAAgB,GAAM,GAAc,EAAW,CAAC;EAG9D,OAFA,GAAM,MAAM,YAAY,GAAe,OAAO,EAAM,KAAK,CAAC,GAC1D,GAAM,MAAM,YAAY,GAAiB,OAAO,EAAM,MAAM,CAAC,SAChD;GAGX,AAFA,EAAkB,CAAK,GACvB,GAAM,MAAM,eAAe,CAAa,GACxC,GAAM,MAAM,eAAe,CAAe;EAC5C;CAGF,GAAG,CAAC,GAAQ,CAAO,CAAC,GAEb;AACT"}
1
+ {"version":3,"file":"modalStack.js","names":[],"sources":["../../../src/components/Modal/modalStack.ts"],"sourcesContent":["/**\n * The one level stack every portalled modal surface shares.\n *\n * `Modal` renders into a `document.body` portal instead of the browser's top\n * layer (see Modal.tsx for why). Without the top layer, stacking and\n * inertness are this module's job: a surface opened from inside another\n * paints above it, only the topmost one is interactive, and while any is\n * open the page behind them is inert, except what is marked\n * `MODAL_LIVE_ATTRIBUTE`.\n *\n * Other portalled modal surfaces (a scrimmed drawer, say) claim a level with\n * `useModalLevel` so they stack on the same order as `Modal`.\n */\nimport {\n createContext,\n useEffectEvent,\n useLayoutEffect,\n useState,\n type RefObject,\n} from \"react\";\nimport { devWarn } from \"@astryxdesign/core/utils\";\n\nimport { registerInstance } from \"../../instance\";\n\n/**\n * Marks an open modal root. Scope document queries to it through this\n * constant rather than retyping the string.\n */\nexport const MODAL_OPEN_ATTRIBUTE = \"data-uic-modal-open\";\n\n/**\n * The open modal `<dialog>` a surface inside it renders into, instead of\n * `document.body`. `showModal()` makes everything outside the dialog inert,\n * so a `Modal` portalled to the body from inside a scrimmed drawer would sit\n * behind the drawer, unreachable. `Drawer` (src/forks/Drawer) provides its\n * dialog while it is modal; `Modal` renders there and enters the top layer\n * itself, as a manual popover, so it paints above the drawer and escapes the\n * panel's clipping and transform. Internal: not exported from the package.\n */\nexport const ModalPortalContext = createContext<HTMLElement | null>(null);\n\n/** Nesting depth ceiling. Two surfaces past it share a level. */\nexport const MAX_MODAL_LEVEL = 80;\n\n/**\n * The z-index band modal roots paint in. `base` is the first level, each\n * nested level adds `step`, and nothing paints above `max`, so whatever the\n * app layers above the band (a notification stack) stays above every modal.\n */\nexport interface ModalZIndexBand {\n base: number;\n step: number;\n max: number;\n}\n\nconst DEFAULT_BAND: ModalZIndexBand = { base: 1100, step: 10, max: 10999 };\nlet band: ModalZIndexBand = DEFAULT_BAND;\n\n/**\n * Sets the z-index band for every modal opened after the call. Call it once,\n * at startup, to fit the app's own layer ladder. The default is\n * `{ base: 1100, step: 10, max: 10999 }`. Pass nothing to restore it.\n */\nexport function configureModalZIndex(next?: Partial<ModalZIndexBand>): void {\n band = { ...DEFAULT_BAND, ...next };\n}\n\n/** The band's first level; a scrimless Drawer (src/forks/Drawer) stays below it. */\nexport function modalZIndexBase(): number {\n return band.base;\n}\n\nconst LEVEL_CSS_VAR = \"--modal-level\";\nconst Z_INDEX_CSS_VAR = \"--modal-z\";\n\n/** A claim on the stack. Released by reference, never by level. */\nexport interface ModalLevelEntry {\n level: number;\n zIndex: number;\n isTopmost: boolean;\n root: HTMLElement | null;\n setIsTopmost: (isTopmost: boolean) => void;\n}\n\nconst openModals: ModalLevelEntry[] = [];\n\n/**\n * One step above the topmost surface (or the band's base), raised by an\n * accepted override. Stacking and inertness therefore run on one order: a\n * surface opened later paints above an earlier one, whatever that one asked.\n */\nfunction resolveZIndex(\n topmost: ModalLevelEntry | undefined,\n requested?: number,\n): number {\n let override = requested;\n if (override != null && (override < band.base || override > band.max)) {\n devWarn(\n \"Modal\",\n `zIndex ${override} is outside the modal band (${band.base}..${band.max}); ` +\n \"ignoring it. Change the band with configureModalZIndex instead.\",\n );\n override = undefined;\n }\n const floor = topmost?.zIndex ?? band.base - band.step;\n return Math.min(Math.max(floor + band.step, override ?? 0), band.max);\n}\n\n/**\n * Marks an element that stays reachable while a modal is open: it, and the\n * ancestors it needs, are never made inert. `NotificationStack` sets it on\n * its root, so a notice raised over a modal can still be read and dismissed.\n * The mark is read when a modal opens or closes and on\n * `refreshModalBackground()`; call that after mounting a marked element while\n * a modal is open.\n */\nexport const MODAL_LIVE_ATTRIBUTE = \"data-uic-modal-live\";\n\n/** The background elements this module made inert, and only those. */\nconst inertedBackground = new Set<Element>();\n\nconst NEVER_INERT = new Set([\"SCRIPT\", \"STYLE\", \"LINK\", \"TEMPLATE\", \"NOSCRIPT\"]);\n\n/**\n * The elements to inert while `keep` stays reachable: every child of\n * `document.body` that holds none of them, and, inside a body child that\n * does, every sibling on the way down to them.\n */\nfunction backgroundOf(keep: Element[]): Element[] {\n const kept = new Set(keep);\n const onPath = new Set<Element>();\n for (const el of keep) {\n for (let node = el.parentElement; node && node !== document.body;) {\n onPath.add(node);\n node = node.parentElement;\n }\n }\n const out: Element[] = [];\n const visit = (parent: Element) => {\n for (const child of Array.from(parent.children)) {\n if (kept.has(child) || NEVER_INERT.has(child.tagName)) continue;\n if (onPath.has(child)) visit(child);\n else out.push(child);\n }\n };\n visit(document.body);\n return out;\n}\n\n/**\n * While any modal is open the rest of the page is `inert`, which is what\n * `aria-modal` promises. Modal roots (a drawer's too, when it claims a level\n * through `useModalLevel`) and elements marked `MODAL_LIVE_ATTRIBUTE` are\n * spared. An `inert` the page set itself is left alone, before and after.\n */\nfunction syncBackground(): void {\n if (typeof document === \"undefined\" || !document.body) return;\n const roots = openModals\n .map((entry) => entry.root)\n .filter((root): root is HTMLElement => root != null && root.isConnected);\n const target = new Set(\n openModals.length === 0\n ? []\n : backgroundOf([\n ...roots,\n ...Array.from(document.querySelectorAll(`[${MODAL_LIVE_ATTRIBUTE}]`)),\n ]),\n );\n for (const el of inertedBackground) {\n if (target.has(el)) continue;\n el.removeAttribute(\"inert\");\n inertedBackground.delete(el);\n }\n for (const el of target) {\n if (inertedBackground.has(el) || el.hasAttribute(\"inert\")) continue;\n el.setAttribute(\"inert\", \"\");\n inertedBackground.add(el);\n }\n}\n\n/**\n * Re-reads which elements are marked `MODAL_LIVE_ATTRIBUTE` (and which modal\n * roots are in the document) while a modal is open. A no-op otherwise.\n */\nexport function refreshModalBackground(): void {\n if (openModals.length > 0 || inertedBackground.size > 0) syncBackground();\n}\n\n/**\n * Only the topmost surface stays interactive. A covered one drops its focus\n * trap and goes `inert`: the trap alone lets Tab escape to the parent, and\n * `inert` alone leaves the covered trap swallowing Tab with nothing to focus.\n */\nfunction syncCovered(): void {\n syncBackground();\n openModals.forEach((entry, index) => {\n const isTopmost = index === openModals.length - 1;\n entry.root?.toggleAttribute(\"inert\", !isTopmost);\n if (entry.isTopmost !== isTopmost) {\n entry.isTopmost = isTopmost;\n entry.setIsTopmost(isTopmost);\n }\n });\n}\n\nexport function claimModalLevel(\n root: HTMLElement | null,\n setIsTopmost: (isTopmost: boolean) => void,\n requestedZIndex?: number,\n): ModalLevelEntry {\n registerInstance();\n const topmost = openModals.at(-1);\n const entry: ModalLevelEntry = {\n level: Math.min((topmost?.level ?? -1) + 1, MAX_MODAL_LEVEL),\n zIndex: resolveZIndex(topmost, requestedZIndex),\n isTopmost: true,\n root,\n setIsTopmost,\n };\n openModals.push(entry);\n syncCovered();\n return entry;\n}\n\nexport function releaseModalLevel(entry: ModalLevelEntry): void {\n const index = openModals.indexOf(entry);\n if (index === -1) return;\n entry.root?.removeAttribute(\"inert\");\n openModals.splice(index, 1);\n syncCovered();\n}\n\n/**\n * Claims a level for `rootRef` while `isOpen`, and writes the level and the\n * resolved z-index onto the root as `--modal-level` and `--modal-z`.\n * Returns whether this surface is the topmost one; gate a focus trap on it.\n */\nexport function useModalLevel(\n rootRef: RefObject<HTMLElement | null>,\n isOpen: boolean,\n zIndex?: number,\n): boolean {\n const [isTopmost, setIsTopmost] = useState(true);\n // Read at claim time only: re-claiming would move an open surface to the\n // top of the stack and inert whatever legitimately covered it.\n const readZIndex = useEffectEvent(() => zIndex);\n\n useLayoutEffect(() => {\n if (!isOpen) return;\n const root = rootRef.current;\n const entry = claimModalLevel(root, setIsTopmost, readZIndex());\n root?.style.setProperty(LEVEL_CSS_VAR, String(entry.level));\n root?.style.setProperty(Z_INDEX_CSS_VAR, String(entry.zIndex));\n return () => {\n releaseModalLevel(entry);\n root?.style.removeProperty(LEVEL_CSS_VAR);\n root?.style.removeProperty(Z_INDEX_CSS_VAR);\n };\n // An effect event is not a dependency; this plugin version predates it.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [isOpen, rootRef]);\n\n return isTopmost;\n}\n"],"mappings":"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"}
@@ -1,70 +1,88 @@
1
1
  "use client";
2
- import { ModalPortalContext as e } from "../../components/Modal/modalStack.js";
3
- import { compiledStyles as t } from "../compiled.js";
4
- import { dynamicStyles as n, styles as r } from "./Drawer.styles.js";
5
- import { useDrawerDialogPresence as i } from "./useDrawerDialogPresence.js";
6
- import { useCallback as a, useEffect as o, useRef as s, useState as c } from "react";
7
- import { overlayPaddingReset as l } from "@astryxdesign/core/Layout";
2
+ import { ModalPortalContext as e, modalZIndexBase as t } from "../../components/Modal/modalStack.js";
3
+ import { compiledStyles as n } from "../compiled.js";
4
+ import { dynamicStyles as r, styles as i } from "./Drawer.styles.js";
5
+ import { useDrawerDialogPresence as a } from "./useDrawerDialogPresence.js";
6
+ import { useCallback as o, useEffect as s, useId as ee, useRef as c, useState as l } from "react";
7
+ import { overlayPaddingReset as te } from "@astryxdesign/core/Layout";
8
8
  import { useFocusTrap as u, useMergedRefs as d, useScrollLock as f } from "@astryxdesign/core/hooks";
9
9
  import { LayerDepthProvider as p, useLayerDismissal as m } from "@astryxdesign/core/Layer";
10
- import { composeEventHandlers as h, mergeProps as g, themeProps as _ } from "@astryxdesign/core/utils";
11
- import { jsx as v } from "react/jsx-runtime";
12
- import * as y from "@stylexjs/stylex";
10
+ import { composeEventHandlers as ne, mergeProps as h, themeProps as g } from "@astryxdesign/core/utils";
11
+ import { jsx as _ } from "react/jsx-runtime";
12
+ import * as v from "@stylexjs/stylex";
13
13
  //#region src/forks/Drawer/Drawer.tsx
14
- var b = 56, x = "100dvw", S = t(r), ee = n, C = "x1iyjqo2 x2lwn1j xh8yej3 x1odjw0f x6ikm8r xish69e xx69xxh x1a148e8 x1a2a7pz";
15
- function w({ isOpen: t, onOpenChange: n, side: r = "end", width: w = 400, isFullWidthOnMobile: T = !1, label: E, hasScrim: D = !0, purpose: O = "info", children: k, xstyle: A, className: j, style: M, onClick: N, onKeyDown: P, ref: F, ...I }) {
16
- let L = s(null), { containerRef: R } = u({ isActive: t && D }), te = d(F, L, R), z = O !== "required", B = O === "info", [V, H] = c(t);
17
- t && !V && H(!0), i({
18
- dialogRef: L,
14
+ var y = 56, re = "100dvw", b = n(i), ie = r, ae = 1e3, x = /* @__PURE__ */ new Map();
15
+ function oe(e) {
16
+ let n = Math.max(999, ...x.values()), r = Math.min(n + 1, t() - 1);
17
+ return x.set(e, r), r;
18
+ }
19
+ function S(e) {
20
+ x.delete(e);
21
+ }
22
+ var C = "x1iyjqo2 x2lwn1j xh8yej3 x1odjw0f x6ikm8r xish69e xx69xxh x1a148e8 x1a2a7pz";
23
+ function w({ isOpen: t, onOpenChange: n, side: r = "end", width: i = 400, isFullWidthOnMobile: x = !1, label: w, hasScrim: T = !0, purpose: E = "info", children: D, xstyle: O, className: k, style: A, onClick: j, onKeyDown: M, ref: N, ...P }) {
24
+ let F = c(null), { containerRef: I } = u({ isActive: t && T }), L = d(N, F, I), R = E !== "required", z = E === "info", [B, V] = l(t);
25
+ t && !B && V(!0), a({
26
+ dialogRef: F,
19
27
  isOpen: t,
20
- isModal: D,
21
- setIsRendered: H
28
+ isModal: T,
29
+ setIsRendered: V
22
30
  });
23
- let [U, W] = c(null);
24
- o(() => {
25
- W(t ? L.current : null);
26
- }, [t]);
27
- let G = a(() => {
31
+ let [H, U] = l(null);
32
+ s(() => {
33
+ U(t && T ? F.current : null);
34
+ }, [t, T]);
35
+ let W = ee(), [G, K] = l(ae);
36
+ s(() => {
37
+ if (!(!B || T)) return K(oe(W)), () => S(W);
38
+ }, [
39
+ B,
40
+ T,
41
+ W
42
+ ]);
43
+ let q = o(() => {
28
44
  n(!1);
29
- }, [n]), { shouldDismissOnCloseRequest: K } = m({
30
- isActive: V,
31
- escapeBehavior: z ? "close" : "block",
32
- onDismiss: G,
33
- getContainer: () => L.current
45
+ }, [n]), { shouldDismissOnCloseRequest: J } = m({
46
+ isActive: B,
47
+ escapeBehavior: R ? "close" : "block",
48
+ onDismiss: q,
49
+ getContainer: () => F.current
34
50
  });
35
- f(t && D);
36
- let q = a((e) => {
37
- e.preventDefault(), K() && z && G();
51
+ f(t && T);
52
+ let se = o((e) => {
53
+ e.preventDefault(), J() && R && q();
38
54
  }, [
39
- z,
40
- G,
41
- K
42
- ]), J = a((e) => {
43
- e.target === e.currentTarget && D && B && n(!1);
55
+ R,
56
+ q,
57
+ J
58
+ ]), ce = o((e) => {
59
+ e.target === e.currentTarget && T && z && n(!1);
44
60
  }, [
45
- B,
46
- D,
61
+ z,
62
+ T,
47
63
  n
48
- ]), Y = typeof w == "number" ? `${w}px` : w, X = T ? x : `min(${Y}, calc(100dvw - ${b}px))`, Z = s(r);
49
- t && (Z.current = r);
50
- let Q = t ? r : Z.current, ne = Q === "start" ? S.start : S.end, re = Q === "start" ? S.startOpen : S.endOpen, { open: ie, ...$ } = I;
51
- return /* @__PURE__ */ v("dialog", {
52
- ref: te,
53
- ...g(_("drawer", { side: Q }), y.props(S.dialog, l.reset, ne, ee.inlineSize(Y, X), V && S.rendered, t && re, D && S.scrim, D && t && S.scrimOpen, A), j, M),
64
+ ]), Y = typeof i == "number" ? `${i}px` : i, le = x ? re : `min(${Y}, calc(100dvw - ${y}px))`, X = c(r);
65
+ t && (X.current = r);
66
+ let Z = t ? r : X.current, ue = Z === "start" ? b.start : b.end, Q = Z === "start" ? b.startOpen : b.endOpen, { open: de, ...$ } = P;
67
+ return /* @__PURE__ */ _("dialog", {
68
+ ref: L,
69
+ ...h(g("drawer", { side: Z }), v.props(b.dialog, te.reset, ue, ie.inlineSize(Y, le), B && b.rendered, t && Q, T && b.scrim, T && t && b.scrimOpen, O), k, T ? A : {
70
+ zIndex: G,
71
+ ...A
72
+ }),
54
73
  ...$,
55
- popover: D ? void 0 : "manual",
56
- "aria-label": E,
57
- "aria-modal": $["aria-modal"] ?? (D ? "true" : void 0),
58
- ...O === "required" && D ? { role: "alertdialog" } : void 0,
59
- onClick: h(N, J),
60
- onKeyDown: P,
61
- onCancel: q,
62
- children: /* @__PURE__ */ v(p, { children: /* @__PURE__ */ v("div", {
74
+ "aria-label": w,
75
+ "aria-modal": $["aria-modal"] ?? (T ? "true" : void 0),
76
+ ...E === "required" && T ? { role: "alertdialog" } : void 0,
77
+ onClick: ne(j, ce),
78
+ onKeyDown: M,
79
+ onCancel: se,
80
+ children: /* @__PURE__ */ _(p, { children: /* @__PURE__ */ _("div", {
63
81
  tabIndex: -1,
64
82
  className: C,
65
- children: /* @__PURE__ */ v(e, {
66
- value: U,
67
- children: k
83
+ children: /* @__PURE__ */ _(e, {
84
+ value: H,
85
+ children: D
68
86
  })
69
87
  }) })
70
88
  });
@@ -1 +1 @@
1
- {"version":3,"file":"Drawer.js","names":[],"sources":["../../../src/forks/Drawer/Drawer.tsx"],"sourcesContent":["// Copyright (c) Meta Platforms, Inc. and affiliates.\n// Modifications copyright (c) Lablup Inc.\n//\n// Forked from @astryxdesign/lab 0.6.5-canary.8701623, src/Drawer/Drawer.tsx\n// (MIT; see NOTICE). Provenance and the drift guard: src/forks/provenance.json.\n\n\"use client\";\n\n/**\n * Drawer, ui-common's copy of Astryx lab's, with two changes (not upstream):\n *\n * - **`aria-modal` passes through.** A scrimless drawer is non-modal, but a\n * consumer that restores modality by hand (its own mask and focus trap) can\n * now say so; the default is unchanged.\n * - **ui-common's `Modal` opens above it.** The drawer is top layer either\n * way: a modal `<dialog>` with a scrim (which also inerts everything\n * outside it), a manual popover without. A `Modal` portalled to the body\n * sat behind it, unreachable. The drawer provides its dialog through\n * `ModalPortalContext` while it is open, and a `Modal` inside renders\n * there and enters the top layer after it (modalStack.ts). Core's\n * `Dialog` needs none of this: it is top layer itself, and ui-common's\n * `Modal` is not.\n *\n * Escape routing is upstream's since lab 0.6.5: the drawer registers with\n * core's layer-dismissal stack, so a popover, selector or modal opened inside\n * it closes first and nested drawers close top-first. Everything else is\n * upstream's too. Its style namespaces are Astryx's compiled output\n * (src/forks/compiled.ts).\n *\n * Delete this fork, and its exports.exclude.json entry, once lab passes\n * `aria-modal` through and `Modal` no longer needs the portal host\n * (CONTRIBUTING, \"Forks of Astryx components\").\n */\n\n/*\n * Upstream's file notes, kept:\n *\n * Overlay panel for inspectors and detail views — the \"click a table row,\n * see its details\" pattern. Slides in from the inline start or end edge and\n * floats above the page content: unlike a docked panel it never reflows the\n * layout underneath, it overlays it (with or without a scrim).\n *\n * Inline axis only (start/end). Block-axis sheets are BottomSheet's job;\n * a drawer is always a full-height side panel.\n *\n * Sizing is viewport-aware: `width` is the desktop budget, and below\n * the mobile breakpoint it preserves a 56px reveal of the page behind, capped\n * by the requested width (or fills the viewport with `isFullWidthOnMobile`).\n *\n * Uses the native `<dialog>` element (same precedent as Dialog/MobileNav):\n * - `showModal()` when `hasScrim` (default) — top-layer rendering, focus\n * trapping, `::backdrop`, no z-index management.\n * - `showPopover()` when `hasScrim={false}` — non-modal top-layer overlay;\n * the page behind stays interactive (e.g. master-detail inspectors).\n *\n * Entry animation uses `@starting-style`; exit slides out before the active\n * modal-dialog or manual-popover host releases the top layer and focus returns\n * to the element that opened the drawer. React owns `display` for both legs\n * rather than a discrete `display` transition, so the panel stops painting in\n * the same commit as the host closes — see the `rendered` style for why.\n *\n * Sibling drawers use the shared layer dismissal stack for topmost-only Escape\n * handling and the browser top layer's chronological paint order.\n *\n * Dismissal follows Dialog: `purpose` decides whether Escape and a scrim click\n * request close, and Drawer renders no close button of its own. Compose\n * `DrawerHeader` with `onOpenChange` for a visible close action, as with\n * `DialogHeader`.\n *\n */\n\nimport { useCallback, useEffect, useRef, useState, type ReactNode } from \"react\";\nimport * as stylex from \"@stylexjs/stylex\";\nimport type { StyleXStyles } from \"@stylexjs/stylex\";\nimport type { BaseProps } from \"@astryxdesign/core\";\nimport type { DialogPurpose } from \"@astryxdesign/core/Dialog\";\nimport { useFocusTrap, useMergedRefs, useScrollLock } from \"@astryxdesign/core/hooks\";\nimport { LayerDepthProvider, useLayerDismissal } from \"@astryxdesign/core/Layer\";\nimport { composeEventHandlers, mergeProps, themeProps } from \"@astryxdesign/core/utils\";\nimport { overlayPaddingReset } from \"@astryxdesign/core/Layout\";\n\nimport { compiledStyles } from \"../compiled\";\nimport * as compiled from \"./Drawer.styles\";\nimport { ModalPortalContext } from \"../../components/Modal/modalStack\";\nimport { useDrawerDialogPresence } from \"./useDrawerDialogPresence\";\n\n// =============================================================================\n// Styles\n// =============================================================================\n\n// Material's established mobile drawer pattern leaves a 56dp reveal. Using\n// the same value in CSS pixels gives the overlay a stable visual relationship\n// to the page behind while the requested width remains an upper bound.\nconst MOBILE_PAGE_REVEAL = 56;\nconst MOBILE_WIDTH_FULL = \"100dvw\";\n\n// @astryxdesign/lab dist/Drawer/Drawer.js `styles` and `dynamicStyles`.\nconst styles = compiledStyles(compiled.styles);\nconst dynamicStyles = compiled.dynamicStyles as {\n inlineSize: (desktopWidth: string, mobileWidth: string) => StyleXStyles;\n};\n\n// Upstream's `content` style, which its compiler folded into this class list\n// (dist/Drawer/Drawer.js).\nconst CONTENT_CLASS_NAME =\n \"x1iyjqo2 x2lwn1j xh8yej3 x1odjw0f x6ikm8r xish69e xx69xxh x1a148e8 x1a2a7pz\";\n\n// =============================================================================\n// Types\n// =============================================================================\n\nexport interface DrawerProps extends BaseProps<HTMLDialogElement> {\n /** Ref forwarded to the root <dialog> element */\n ref?: React.Ref<HTMLDialogElement>;\n\n /**\n * Whether the drawer is open. Fully controlled — pair with `onOpenChange`.\n */\n isOpen: boolean;\n\n /**\n * Called when the drawer requests an open-state change. Escape and a scrim\n * click call it with `false` as `purpose` allows; a `DrawerHeader` close\n * button calls it when you pass the same callback to the header. The caller\n * owns the open state. When sibling drawers are open, Escape only closes the\n * top (last-opened) drawer.\n */\n onOpenChange: (isOpen: boolean) => void;\n\n /**\n * Which edge the drawer slides from.\n * - `'end'` — inline-end edge (right in LTR) — the inspector convention\n * - `'start'` — inline-start edge (left in LTR)\n * @default 'end'\n */\n side?: \"start\" | \"end\";\n\n /**\n * Desktop width budget. A number is pixels; a string is any CSS length\n * (`'50%'`, `'32rem'`). Below the mobile breakpoint (640px), this\n * remains the maximum while the drawer preserves a 56px reveal of the page\n * behind — see `isFullWidthOnMobile`.\n * @default 400\n */\n width?: number | string;\n\n /**\n * Whether the drawer covers the full viewport width on mobile\n * (below 640px) instead of preserving the default 56px reveal of the page\n * behind. The reveal makes the drawer read as an overlay rather than a\n * navigation.\n * @default false\n */\n isFullWidthOnMobile?: boolean;\n\n /**\n * Accessible label for the drawer (required — the drawer has no\n * built-in heading to derive a name from).\n */\n label: string;\n\n /**\n * Whether to render a modal scrim behind the drawer.\n * - `true` (default) — `showModal()`: top layer, focus trap, body scroll\n * lock, click-outside-to-close.\n * - `false` — `showPopover()`: non-modal top-layer overlay; the page behind\n * stays interactive. Escape still closes through the shared layer stack.\n * @default true\n */\n hasScrim?: boolean;\n\n /**\n * Configures how the drawer enables dismissals, matching Dialog.\n * - required: Disables Escape and scrim click (for mandatory flows); a\n * modal required drawer is exposed as an `alertdialog`\n * - form: Prevents scrim click, allows the Escape key\n * - info: Allows Escape and scrim click\n *\n * A non-modal drawer has no scrim, so `form` and `info` behave the same.\n * @default 'info'\n */\n purpose?: DialogPurpose;\n\n /**\n * Drawer content. Rendered inside a full-height scrollable area.\n * Focus the element with `data-autofocus` on open, if present.\n */\n children: ReactNode;\n\n /**\n * Test ID for the root element.\n */\n \"data-testid\"?: string;\n}\n\n// =============================================================================\n// Component\n// =============================================================================\n\n/**\n * An overlay panel for inspectors and detail views.\n *\n * Slides in from the logical start or end edge and floats above the page\n * using the native `<dialog>` element: modal with a scrim by default, or a\n * non-modal overlay with `hasScrim={false}` that leaves the page behind\n * interactive. `width` is the desktop budget; below 640px the panel preserves\n * a 56px page reveal without exceeding that budget (or fills the viewport\n * with `isFullWidthOnMobile`). Escape\n * closes the top-most open drawer; focus returns to the element that\n * opened it.\n *\n * Dismissal matches Dialog: `purpose` decides whether Escape and a scrim click\n * close the drawer, and `DrawerHeader` renders a close button when given\n * `onOpenChange`.\n *\n * @example\n * ```\n * const [selected, setSelected] = useState(null);\n * const handleOpenChange = isOpen => !isOpen && setSelected(null);\n * <Drawer\n * isOpen={selected != null}\n * onOpenChange={handleOpenChange}\n * label={`Details: ${selected?.name}`}>\n * <DrawerHeader title={selected?.name} onOpenChange={handleOpenChange} />\n * <HostDetails host={selected} />\n * </Drawer>\n * ```\n */\nexport function Drawer({\n isOpen,\n onOpenChange,\n side = \"end\",\n width = 400,\n isFullWidthOnMobile = false,\n label,\n hasScrim = true,\n purpose = \"info\",\n children,\n xstyle,\n className,\n style,\n onClick: onClickProp,\n onKeyDown: onKeyDownProp,\n ref,\n ...props\n}: DrawerProps) {\n const dialogRef = useRef<HTMLDialogElement>(null);\n const { containerRef: focusTrapRef } = useFocusTrap<HTMLDialogElement>({\n isActive: isOpen && hasScrim,\n });\n const mergedDialogRef = useMergedRefs(ref, dialogRef, focusTrapRef);\n // Derive dismissal behavior from purpose, as Dialog does.\n const allowEscape = purpose !== \"required\";\n const allowScrimClick = purpose === \"info\";\n // Whether the panel paints: true while open and for the whole slide-out.\n const [isRendered, setIsRendered] = useState(isOpen);\n\n // Adjusted during render, not in an effect: the panel has to be rendered in\n // the same commit that targets the open transform, or @starting-style has\n // nothing to animate from.\n if (isOpen && !isRendered) {\n setIsRendered(true);\n }\n\n useDrawerDialogPresence({\n dialogRef,\n isOpen,\n isModal: hasScrim,\n setIsRendered,\n });\n\n // ui-common: the dialog a Modal inside renders into, set only once\n // showModal() / showPopover() has run (the effect above), so the Modal's\n // popover enters the top layer after the dialog and paints above it. Both\n // hosts are top layer, so a body-portalled Modal would sit behind either.\n const [modalHost, setModalHost] = useState<HTMLDialogElement | null>(null);\n useEffect(() => {\n setModalHost(isOpen ? dialogRef.current : null);\n }, [isOpen]);\n\n const handleDismiss = useCallback(() => {\n onOpenChange(false);\n }, [onOpenChange]);\n\n // The shared stack owns Escape delivery across every overlay family. The\n // provider below gives layers opened from Drawer content a greater logical\n // depth even when they render elsewhere in the DOM or browser top layer.\n const { shouldDismissOnCloseRequest } = useLayerDismissal({\n // The native host stays present through the slide-out. Keep this entry on\n // the stack until the same commit that hides the host, so a second Escape\n // cannot fall through to a lower layer during the exit animation.\n isActive: isRendered,\n // A `required` drawer consumes Escape without closing, so the press cannot\n // fall through and dismiss a layer behind it either.\n escapeBehavior: allowEscape ? \"close\" : \"block\",\n onDismiss: handleDismiss,\n getContainer: () => dialogRef.current,\n });\n\n // Lock body scroll while a modal drawer is open (iOS Safari workaround).\n useScrollLock(isOpen && hasScrim);\n\n // A modal dialog's native cancel event is a platform close request (for\n // example Android Back). Keep controlled state authoritative and apply the\n // same top-most/IME rules as the shared Escape listener.\n const handleCancel = useCallback(\n (event: React.SyntheticEvent<HTMLDialogElement>) => {\n event.preventDefault();\n if (shouldDismissOnCloseRequest() && allowEscape) {\n handleDismiss();\n }\n },\n [allowEscape, handleDismiss, shouldDismissOnCloseRequest],\n );\n\n // Clicks on the ::backdrop target the <dialog> element itself; clicks on\n // drawer content always target a child (the content area fills the panel).\n const handleClick = useCallback(\n (event: React.MouseEvent<HTMLDialogElement>) => {\n if (event.target === event.currentTarget && hasScrim && allowScrimClick) {\n onOpenChange(false);\n }\n },\n [allowScrimClick, hasScrim, onOpenChange],\n );\n\n const widthValue = typeof width === \"number\" ? `${width}px` : width;\n const mobileWidth = isFullWidthOnMobile\n ? MOBILE_WIDTH_FULL\n : `min(${widthValue}, calc(100dvw - ${MOBILE_PAGE_REVEAL}px))`;\n\n // The side the panel is ANCHORED to, which is the side it must slide back\n // out to. Latched at open, because a consumer commonly derives `side` from\n // the same state that drives `isOpen` (`side={selected?.side ?? 'end'}`):\n // that state clears on close, so the live prop flips mid-exit and the panel\n // teleports to the other edge and slides out the wrong way. Children stay\n // mounted for the exit for the same reason; so does the anchor.\n const exitSideRef = useRef(side);\n if (isOpen) {\n exitSideRef.current = side;\n }\n const anchoredSide = isOpen ? side : exitSideRef.current;\n\n const sideStyle = anchoredSide === \"start\" ? styles.start : styles.end;\n const sideOpenStyle = anchoredSide === \"start\" ? styles.startOpen : styles.endOpen;\n\n // Filter out native `open` to prevent InvalidStateError when passed\n const { open: _open, ...safeProps } = props as Record<string, unknown>;\n\n return (\n <dialog\n ref={mergedDialogRef}\n {...mergeProps(\n themeProps(\"drawer\", { side: anchoredSide }),\n stylex.props(\n styles.dialog,\n overlayPaddingReset.reset,\n sideStyle,\n dynamicStyles.inlineSize(widthValue, mobileWidth),\n isRendered && styles.rendered,\n isOpen && sideOpenStyle,\n hasScrim && styles.scrim,\n hasScrim && isOpen && styles.scrimOpen,\n xstyle,\n ),\n className,\n style,\n )}\n {...safeProps}\n popover={hasScrim ? undefined : \"manual\"}\n aria-label={label}\n // ui-common: scrimless by itself is non-modal, but a consumer that\n // restores the modality by hand (a portal supplying its own mask and\n // focus trap) must be able to say so.\n aria-modal={\n (safeProps[\"aria-modal\"] as React.AriaAttributes[\"aria-modal\"]) ??\n (hasScrim ? \"true\" : undefined)\n }\n {...(purpose === \"required\" && hasScrim ? { role: \"alertdialog\" } : undefined)}\n onClick={composeEventHandlers(onClickProp, handleClick)}\n onKeyDown={onKeyDownProp}\n onCancel={handleCancel}\n >\n <LayerDepthProvider>\n {/* Scrollable content area — tabIndex so the dialog's focusing steps\n land on the panel body rather than the first button inside. */}\n <div tabIndex={-1} className={CONTENT_CLASS_NAME}>\n <ModalPortalContext value={modalHost}>{children}</ModalPortalContext>\n </div>\n </LayerDepthProvider>\n </dialog>\n );\n}\n\nDrawer.displayName = \"Drawer\";\n"],"mappings":";;;;;;;;;;;;;AA6FA,IAAM,IAAqB,IACrB,IAAoB,UAGpB,IAAS,EAAe,CAAe,GACvC,KAAgB,GAMhB,IACJ;AA2HF,SAAgB,EAAO,EACrB,WACA,iBACA,UAAO,OACP,WAAQ,KACR,yBAAsB,IACtB,UACA,cAAW,IACX,aAAU,QACV,aACA,WACA,cACA,UACA,SAAS,GACT,WAAW,GACX,QACA,GAAG,KACW;CACd,IAAM,IAAY,EAA0B,IAAI,GAC1C,EAAE,cAAc,MAAiB,EAAgC,EACrE,UAAU,KAAU,EACtB,CAAC,GACK,KAAkB,EAAc,GAAK,GAAW,CAAY,GAE5D,IAAc,MAAY,YAC1B,IAAkB,MAAY,QAE9B,CAAC,GAAY,KAAiB,EAAS,CAAM;CASnD,AAJI,KAAU,CAAC,KACb,EAAc,EAAI,GAGpB,EAAwB;EACtB;EACA;EACA,SAAS;EACT;CACF,CAAC;CAMD,IAAM,CAAC,GAAW,KAAgB,EAAmC,IAAI;CACzE,QAAgB;EACd,EAAa,IAAS,EAAU,UAAU,IAAI;CAChD,GAAG,CAAC,CAAM,CAAC;CAEX,IAAM,IAAgB,QAAkB;EACtC,EAAa,EAAK;CACpB,GAAG,CAAC,CAAY,CAAC,GAKX,EAAE,mCAAgC,EAAkB;EAIxD,UAAU;EAGV,gBAAgB,IAAc,UAAU;EACxC,WAAW;EACX,oBAAoB,EAAU;CAChC,CAAC;CAGD,EAAc,KAAU,CAAQ;CAKhC,IAAM,IAAe,GAClB,MAAmD;EAElD,AADA,EAAM,eAAe,GACjB,EAA4B,KAAK,KACnC,EAAc;CAElB,GACA;EAAC;EAAa;EAAe;CAA2B,CAC1D,GAIM,IAAc,GACjB,MAA+C;EAC9C,AAAI,EAAM,WAAW,EAAM,iBAAiB,KAAY,KACtD,EAAa,EAAK;CAEtB,GACA;EAAC;EAAiB;EAAU;CAAY,CAC1C,GAEM,IAAa,OAAO,KAAU,WAAW,GAAG,EAAM,MAAM,GACxD,IAAc,IAChB,IACA,OAAO,EAAW,kBAAkB,EAAmB,OAQrD,IAAc,EAAO,CAAI;CAC/B,AAAI,MACF,EAAY,UAAU;CAExB,IAAM,IAAe,IAAS,IAAO,EAAY,SAE3C,KAAY,MAAiB,UAAU,EAAO,QAAQ,EAAO,KAC7D,KAAgB,MAAiB,UAAU,EAAO,YAAY,EAAO,SAGrE,EAAE,MAAM,IAAO,GAAG,MAAc;CAEtC,OACE,kBAAC,UAAD;EACE,KAAK;EACL,GAAI,EACF,EAAW,UAAU,EAAE,MAAM,EAAa,CAAC,GAC3C,EAAO,MACL,EAAO,QACP,EAAoB,OACpB,IACA,GAAc,WAAW,GAAY,CAAW,GAChD,KAAc,EAAO,UACrB,KAAU,IACV,KAAY,EAAO,OACnB,KAAY,KAAU,EAAO,WAC7B,CACF,GACA,GACA,CACF;EACA,GAAI;EACJ,SAAS,IAAW,KAAA,IAAY;EAChC,cAAY;EAIZ,cACG,EAAU,kBACV,IAAW,SAAS,KAAA;EAEvB,GAAK,MAAY,cAAc,IAAW,EAAE,MAAM,cAAc,IAAI,KAAA;EACpE,SAAS,EAAqB,GAAa,CAAW;EACtD,WAAW;EACX,UAAU;EAEV,UAAA,kBAAC,GAAD,EAAA,UAGE,kBAAC,OAAD;GAAK,UAAU;GAAI,WAAW;GAC5B,UAAA,kBAAC,GAAD;IAAoB,OAAO;IAAY;GAA6B,CAAA;EACjE,CAAA,EACa,CAAA;CACd,CAAA;AAEZ;AAEA,EAAO,cAAc"}
1
+ {"version":3,"file":"Drawer.js","names":[],"sources":["../../../src/forks/Drawer/Drawer.tsx"],"sourcesContent":["// Copyright (c) Meta Platforms, Inc. and affiliates.\n// Modifications copyright (c) Lablup Inc.\n//\n// Forked from @astryxdesign/lab 0.6.5-canary.8701623, src/Drawer/Drawer.tsx\n// (MIT; see NOTICE). Provenance and the drift guard: src/forks/provenance.json.\n\n\"use client\";\n\n/**\n * Drawer, ui-common's copy of Astryx lab's, with three changes (not upstream):\n *\n * - **A scrimless drawer stays out of the top layer.** lab 0.6.5 opens it as\n * a manual popover; this copy keeps the `show()` + z-index it had before\n * (base 1000, later drawers above), because ui-common's `Modal` and\n * notification stack are z-index surfaces: a top-layer drawer painted over\n * a confirm or a notification opened while it was up, and the modal stack\n * then inerted the drawer it could not cover.\n * - **`aria-modal` passes through.** A scrimless drawer is non-modal, but a\n * consumer that restores modality by hand (its own mask and focus trap) can\n * now say so; the default is unchanged.\n * - **ui-common's `Modal` opens above a scrimmed drawer.** That drawer is a\n * modal `<dialog>`, top layer and inerting everything outside it, so a\n * `Modal` portalled to the body sat behind it, unreachable. The drawer\n * provides its dialog through `ModalPortalContext` while it is open and\n * modal, and a `Modal` inside renders there and enters the top layer\n * after it (modalStack.ts).\n *\n * Escape routing is upstream's since lab 0.6.5: the drawer registers with\n * core's layer-dismissal stack, so a popover, selector or modal opened inside\n * it closes first and nested drawers close top-first. Everything else is\n * upstream's too. Its style namespaces are Astryx's compiled output\n * (src/forks/compiled.ts).\n *\n * Delete this fork, and its exports.exclude.json entry, once ui-common's\n * `Modal` and notification stack are top layer themselves and lab passes\n * `aria-modal` through (CONTRIBUTING, \"Forks of Astryx components\").\n */\n\n/*\n * Upstream's file notes, kept:\n *\n * Overlay panel for inspectors and detail views — the \"click a table row,\n * see its details\" pattern. Slides in from the inline start or end edge and\n * floats above the page content: unlike a docked panel it never reflows the\n * layout underneath, it overlays it (with or without a scrim).\n *\n * Inline axis only (start/end). Block-axis sheets are BottomSheet's job;\n * a drawer is always a full-height side panel.\n *\n * Sizing is viewport-aware: `width` is the desktop budget, and below\n * the mobile breakpoint it preserves a 56px reveal of the page behind, capped\n * by the requested width (or fills the viewport with `isFullWidthOnMobile`).\n *\n * Uses the native `<dialog>` element (same precedent as Dialog/MobileNav):\n * - `showModal()` when `hasScrim` (default) — top-layer rendering, focus\n * trapping, `::backdrop`, no z-index management.\n * - `showPopover()` when `hasScrim={false}` — non-modal top-layer overlay;\n * the page behind stays interactive (e.g. master-detail inspectors).\n * (ui-common: `show()` and a z-index instead; see the header above.)\n *\n * Entry animation uses `@starting-style`; exit slides out before the active\n * modal-dialog or manual-popover host releases the top layer and focus returns\n * to the element that opened the drawer. React owns `display` for both legs\n * rather than a discrete `display` transition, so the panel stops painting in\n * the same commit as the host closes — see the `rendered` style for why.\n *\n * Sibling drawers use the shared layer dismissal stack for topmost-only Escape\n * handling and the browser top layer's chronological paint order.\n *\n * Dismissal follows Dialog: `purpose` decides whether Escape and a scrim click\n * request close, and Drawer renders no close button of its own. Compose\n * `DrawerHeader` with `onOpenChange` for a visible close action, as with\n * `DialogHeader`.\n *\n */\n\nimport { useCallback, useEffect, useId, useRef, useState, type ReactNode } from \"react\";\nimport * as stylex from \"@stylexjs/stylex\";\nimport type { StyleXStyles } from \"@stylexjs/stylex\";\nimport type { BaseProps } from \"@astryxdesign/core\";\nimport type { DialogPurpose } from \"@astryxdesign/core/Dialog\";\nimport { useFocusTrap, useMergedRefs, useScrollLock } from \"@astryxdesign/core/hooks\";\nimport { LayerDepthProvider, useLayerDismissal } from \"@astryxdesign/core/Layer\";\nimport { composeEventHandlers, mergeProps, themeProps } from \"@astryxdesign/core/utils\";\nimport { overlayPaddingReset } from \"@astryxdesign/core/Layout\";\n\nimport { compiledStyles } from \"../compiled\";\nimport * as compiled from \"./Drawer.styles\";\nimport { ModalPortalContext, modalZIndexBase } from \"../../components/Modal/modalStack\";\nimport { useDrawerDialogPresence } from \"./useDrawerDialogPresence\";\n\n// =============================================================================\n// Styles\n// =============================================================================\n\n// Material's established mobile drawer pattern leaves a 56dp reveal. Using\n// the same value in CSS pixels gives the overlay a stable visual relationship\n// to the page behind while the requested width remains an upper bound.\nconst MOBILE_PAGE_REVEAL = 56;\nconst MOBILE_WIDTH_FULL = \"100dvw\";\n\n// @astryxdesign/lab dist/Drawer/Drawer.js `styles` and `dynamicStyles`.\nconst styles = compiledStyles(compiled.styles);\nconst dynamicStyles = compiled.dynamicStyles as {\n inlineSize: (desktopWidth: string, mobileWidth: string) => StyleXStyles;\n};\n\n// ui-common: stacking for scrimless drawers, which open with `show()` and so\n// need a z-index; modal drawers rely on the top layer's chronological order.\n// Module-level; only mutated inside effects (SSR-safe). 1000 is the app-level\n// drawer convention lab used before 0.6.5. A new drawer goes one above the\n// highest OPEN one, so the value is bounded by how many are open at once, and\n// it stays below the modal band so a Modal always paints over a drawer.\nconst NON_MODAL_BASE_Z = 1000;\nconst openNonModalDrawers = new Map<string, number>();\n\nfunction registerNonModalDrawer(id: string): number {\n const highest = Math.max(NON_MODAL_BASE_Z - 1, ...openNonModalDrawers.values());\n const z = Math.min(highest + 1, modalZIndexBase() - 1);\n openNonModalDrawers.set(id, z);\n return z;\n}\n\nfunction unregisterNonModalDrawer(id: string): void {\n openNonModalDrawers.delete(id);\n}\n\n// Upstream's `content` style, which its compiler folded into this class list\n// (dist/Drawer/Drawer.js).\nconst CONTENT_CLASS_NAME =\n \"x1iyjqo2 x2lwn1j xh8yej3 x1odjw0f x6ikm8r xish69e xx69xxh x1a148e8 x1a2a7pz\";\n\n// =============================================================================\n// Types\n// =============================================================================\n\nexport interface DrawerProps extends BaseProps<HTMLDialogElement> {\n /** Ref forwarded to the root <dialog> element */\n ref?: React.Ref<HTMLDialogElement>;\n\n /**\n * Whether the drawer is open. Fully controlled — pair with `onOpenChange`.\n */\n isOpen: boolean;\n\n /**\n * Called when the drawer requests an open-state change. Escape and a scrim\n * click call it with `false` as `purpose` allows; a `DrawerHeader` close\n * button calls it when you pass the same callback to the header. The caller\n * owns the open state. When sibling drawers are open, Escape only closes the\n * top (last-opened) drawer.\n */\n onOpenChange: (isOpen: boolean) => void;\n\n /**\n * Which edge the drawer slides from.\n * - `'end'` — inline-end edge (right in LTR) — the inspector convention\n * - `'start'` — inline-start edge (left in LTR)\n * @default 'end'\n */\n side?: \"start\" | \"end\";\n\n /**\n * Desktop width budget. A number is pixels; a string is any CSS length\n * (`'50%'`, `'32rem'`). Below the mobile breakpoint (640px), this\n * remains the maximum while the drawer preserves a 56px reveal of the page\n * behind — see `isFullWidthOnMobile`.\n * @default 400\n */\n width?: number | string;\n\n /**\n * Whether the drawer covers the full viewport width on mobile\n * (below 640px) instead of preserving the default 56px reveal of the page\n * behind. The reveal makes the drawer read as an overlay rather than a\n * navigation.\n * @default false\n */\n isFullWidthOnMobile?: boolean;\n\n /**\n * Accessible label for the drawer (required — the drawer has no\n * built-in heading to derive a name from).\n */\n label: string;\n\n /**\n * Whether to render a modal scrim behind the drawer.\n * - `true` (default) — `showModal()`: top layer, focus trap, body scroll\n * lock, click-outside-to-close.\n * - `false` — `showPopover()`: non-modal top-layer overlay; the page behind\n * stays interactive. Escape still closes through the shared layer stack.\n * @default true\n */\n hasScrim?: boolean;\n\n /**\n * Configures how the drawer enables dismissals, matching Dialog.\n * - required: Disables Escape and scrim click (for mandatory flows); a\n * modal required drawer is exposed as an `alertdialog`\n * - form: Prevents scrim click, allows the Escape key\n * - info: Allows Escape and scrim click\n *\n * A non-modal drawer has no scrim, so `form` and `info` behave the same.\n * @default 'info'\n */\n purpose?: DialogPurpose;\n\n /**\n * Drawer content. Rendered inside a full-height scrollable area.\n * Focus the element with `data-autofocus` on open, if present.\n */\n children: ReactNode;\n\n /**\n * Test ID for the root element.\n */\n \"data-testid\"?: string;\n}\n\n// =============================================================================\n// Component\n// =============================================================================\n\n/**\n * An overlay panel for inspectors and detail views.\n *\n * Slides in from the logical start or end edge and floats above the page\n * using the native `<dialog>` element: modal with a scrim by default, or a\n * non-modal overlay with `hasScrim={false}` that leaves the page behind\n * interactive. `width` is the desktop budget; below 640px the panel preserves\n * a 56px page reveal without exceeding that budget (or fills the viewport\n * with `isFullWidthOnMobile`). Escape\n * closes the top-most open drawer; focus returns to the element that\n * opened it.\n *\n * Dismissal matches Dialog: `purpose` decides whether Escape and a scrim click\n * close the drawer, and `DrawerHeader` renders a close button when given\n * `onOpenChange`.\n *\n * @example\n * ```\n * const [selected, setSelected] = useState(null);\n * const handleOpenChange = isOpen => !isOpen && setSelected(null);\n * <Drawer\n * isOpen={selected != null}\n * onOpenChange={handleOpenChange}\n * label={`Details: ${selected?.name}`}>\n * <DrawerHeader title={selected?.name} onOpenChange={handleOpenChange} />\n * <HostDetails host={selected} />\n * </Drawer>\n * ```\n */\nexport function Drawer({\n isOpen,\n onOpenChange,\n side = \"end\",\n width = 400,\n isFullWidthOnMobile = false,\n label,\n hasScrim = true,\n purpose = \"info\",\n children,\n xstyle,\n className,\n style,\n onClick: onClickProp,\n onKeyDown: onKeyDownProp,\n ref,\n ...props\n}: DrawerProps) {\n const dialogRef = useRef<HTMLDialogElement>(null);\n const { containerRef: focusTrapRef } = useFocusTrap<HTMLDialogElement>({\n isActive: isOpen && hasScrim,\n });\n const mergedDialogRef = useMergedRefs(ref, dialogRef, focusTrapRef);\n // Derive dismissal behavior from purpose, as Dialog does.\n const allowEscape = purpose !== \"required\";\n const allowScrimClick = purpose === \"info\";\n // Whether the panel paints: true while open and for the whole slide-out.\n const [isRendered, setIsRendered] = useState(isOpen);\n\n // Adjusted during render, not in an effect: the panel has to be rendered in\n // the same commit that targets the open transform, or @starting-style has\n // nothing to animate from.\n if (isOpen && !isRendered) {\n setIsRendered(true);\n }\n\n useDrawerDialogPresence({\n dialogRef,\n isOpen,\n isModal: hasScrim,\n setIsRendered,\n });\n\n // ui-common: the dialog a Modal inside renders into, set only once\n // showModal() has run (the effect above), so the Modal's popover enters the\n // top layer after the dialog and paints above it. A scrimless drawer is not\n // top layer (`show()`), so a body-portalled Modal already stacks above it.\n const [modalHost, setModalHost] = useState<HTMLDialogElement | null>(null);\n useEffect(() => {\n setModalHost(isOpen && hasScrim ? dialogRef.current : null);\n }, [isOpen, hasScrim]);\n\n // ui-common: a scrimless drawer's z-index, last opened on top.\n const drawerId = useId();\n const [stackZ, setStackZ] = useState(NON_MODAL_BASE_Z);\n // Held while the panel paints, slide-out included, so a drawer opened during\n // another's exit goes above it rather than taking the same value.\n useEffect(() => {\n if (!isRendered || hasScrim) return;\n setStackZ(registerNonModalDrawer(drawerId));\n return () => unregisterNonModalDrawer(drawerId);\n }, [isRendered, hasScrim, drawerId]);\n\n const handleDismiss = useCallback(() => {\n onOpenChange(false);\n }, [onOpenChange]);\n\n // The shared stack owns Escape delivery across every overlay family. The\n // provider below gives layers opened from Drawer content a greater logical\n // depth even when they render elsewhere in the DOM or browser top layer.\n const { shouldDismissOnCloseRequest } = useLayerDismissal({\n // The native host stays present through the slide-out. Keep this entry on\n // the stack until the same commit that hides the host, so a second Escape\n // cannot fall through to a lower layer during the exit animation.\n isActive: isRendered,\n // A `required` drawer consumes Escape without closing, so the press cannot\n // fall through and dismiss a layer behind it either.\n escapeBehavior: allowEscape ? \"close\" : \"block\",\n onDismiss: handleDismiss,\n getContainer: () => dialogRef.current,\n });\n\n // Lock body scroll while a modal drawer is open (iOS Safari workaround).\n useScrollLock(isOpen && hasScrim);\n\n // A modal dialog's native cancel event is a platform close request (for\n // example Android Back). Keep controlled state authoritative and apply the\n // same top-most/IME rules as the shared Escape listener.\n const handleCancel = useCallback(\n (event: React.SyntheticEvent<HTMLDialogElement>) => {\n event.preventDefault();\n if (shouldDismissOnCloseRequest() && allowEscape) {\n handleDismiss();\n }\n },\n [allowEscape, handleDismiss, shouldDismissOnCloseRequest],\n );\n\n // Clicks on the ::backdrop target the <dialog> element itself; clicks on\n // drawer content always target a child (the content area fills the panel).\n const handleClick = useCallback(\n (event: React.MouseEvent<HTMLDialogElement>) => {\n if (event.target === event.currentTarget && hasScrim && allowScrimClick) {\n onOpenChange(false);\n }\n },\n [allowScrimClick, hasScrim, onOpenChange],\n );\n\n const widthValue = typeof width === \"number\" ? `${width}px` : width;\n const mobileWidth = isFullWidthOnMobile\n ? MOBILE_WIDTH_FULL\n : `min(${widthValue}, calc(100dvw - ${MOBILE_PAGE_REVEAL}px))`;\n\n // The side the panel is ANCHORED to, which is the side it must slide back\n // out to. Latched at open, because a consumer commonly derives `side` from\n // the same state that drives `isOpen` (`side={selected?.side ?? 'end'}`):\n // that state clears on close, so the live prop flips mid-exit and the panel\n // teleports to the other edge and slides out the wrong way. Children stay\n // mounted for the exit for the same reason; so does the anchor.\n const exitSideRef = useRef(side);\n if (isOpen) {\n exitSideRef.current = side;\n }\n const anchoredSide = isOpen ? side : exitSideRef.current;\n\n const sideStyle = anchoredSide === \"start\" ? styles.start : styles.end;\n const sideOpenStyle = anchoredSide === \"start\" ? styles.startOpen : styles.endOpen;\n\n // Filter out native `open` to prevent InvalidStateError when passed\n const { open: _open, ...safeProps } = props as Record<string, unknown>;\n\n return (\n <dialog\n ref={mergedDialogRef}\n {...mergeProps(\n themeProps(\"drawer\", { side: anchoredSide }),\n stylex.props(\n styles.dialog,\n overlayPaddingReset.reset,\n sideStyle,\n dynamicStyles.inlineSize(widthValue, mobileWidth),\n isRendered && styles.rendered,\n isOpen && sideOpenStyle,\n hasScrim && styles.scrim,\n hasScrim && isOpen && styles.scrimOpen,\n xstyle,\n ),\n className,\n // ui-common: the consumer's style may still override the stack.\n hasScrim ? style : { zIndex: stackZ, ...style },\n )}\n {...safeProps}\n aria-label={label}\n // ui-common: scrimless by itself is non-modal, but a consumer that\n // restores the modality by hand (a portal supplying its own mask and\n // focus trap) must be able to say so.\n aria-modal={\n (safeProps[\"aria-modal\"] as React.AriaAttributes[\"aria-modal\"]) ??\n (hasScrim ? \"true\" : undefined)\n }\n {...(purpose === \"required\" && hasScrim ? { role: \"alertdialog\" } : undefined)}\n onClick={composeEventHandlers(onClickProp, handleClick)}\n onKeyDown={onKeyDownProp}\n onCancel={handleCancel}\n >\n <LayerDepthProvider>\n {/* Scrollable content area — tabIndex so the dialog's focusing steps\n land on the panel body rather than the first button inside. */}\n <div tabIndex={-1} className={CONTENT_CLASS_NAME}>\n <ModalPortalContext value={modalHost}>{children}</ModalPortalContext>\n </div>\n </LayerDepthProvider>\n </dialog>\n );\n}\n\nDrawer.displayName = \"Drawer\";\n"],"mappings":"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"}
@@ -8,13 +8,13 @@ type UseDrawerDialogPresenceOptions = {
8
8
  /**
9
9
  * Coordinates the native dialog and React-rendered presence for Drawer.
10
10
  *
11
- * Opening captures the trigger, enters the modal-dialog or manual-popover host,
11
+ * Opening captures the trigger, opens the dialog (`showModal()` or `show()`),
12
12
  * and honours the component's `data-autofocus` contract. Closing waits for the
13
- * actual transform transition (with a computed-duration backstop), then leaves
14
- * the native host and synchronously hides the panel before the browser can paint
15
- * it outside the top layer, restores focus to the captured trigger, and only
16
- * then dispatches the popover host's synthetic `close`. Unmount cleanup closes
17
- * a host left open by React Activity or a removed subtree.
13
+ * actual transform transition (with a computed-duration backstop), then calls
14
+ * `close()` (which fires the native `close` event) and synchronously hides the
15
+ * panel before the browser can paint it outside the top layer, then restores
16
+ * focus to the captured trigger. Unmount cleanup closes a dialog left open by
17
+ * React Activity or a removed subtree.
18
18
  */
19
19
  export declare function useDrawerDialogPresence({ dialogRef, isOpen, isModal, setIsRendered, }: UseDrawerDialogPresenceOptions): void;
20
20
  export {};
@@ -4,80 +4,68 @@ import { flushSync as n } from "react-dom";
4
4
  //#region src/forks/Drawer/useDrawerDialogPresence.ts
5
5
  var r = 50, i = 250;
6
6
  function a(e) {
7
- try {
8
- return e.matches(":popover-open");
9
- } catch {
10
- return !1;
11
- }
7
+ return e.open;
12
8
  }
13
9
  function o(e, t) {
14
- return t ? e.open : a(e) || e.open;
10
+ t ? e.showModal() : e.show();
15
11
  }
16
- function s(e, t) {
17
- t ? e.showModal() : typeof e.showPopover == "function" ? e.showPopover() : e.show();
12
+ function s(e) {
13
+ e.open && e.close();
18
14
  }
19
- function c(e) {
20
- let t = e.ownerDocument.defaultView?.Event ?? Event;
21
- e.dispatchEvent(new t("close"));
22
- }
23
- function l(e, t) {
24
- return !t && typeof e.hidePopover == "function" ? (e.hidePopover(), !0) : (e.open && e.close(), !1);
25
- }
26
- function u({ dialogRef: r, isOpen: i, isModal: a, setIsRendered: u }) {
27
- let f = t(null);
15
+ function c({ dialogRef: r, isOpen: i, isModal: c, setIsRendered: u }) {
16
+ let d = t(null);
28
17
  e(() => {
29
18
  let e = r.current;
30
19
  if (e) {
31
20
  if (i) {
32
- o(e, a) || (f.current = document.activeElement, s(e, a), e.querySelector("[data-autofocus]")?.focus());
21
+ a(e) || (d.current = document.activeElement, o(e, c), e.querySelector("[data-autofocus]")?.focus());
33
22
  return;
34
23
  }
35
- if (o(e, a)) return d(e, () => {
36
- let t = l(e, a);
37
- n(() => {
24
+ if (a(e)) return l(e, () => {
25
+ s(e), n(() => {
38
26
  u(!1);
39
- }), f.current?.focus(), f.current = null, t && c(e);
27
+ }), d.current?.focus(), d.current = null;
40
28
  });
41
29
  }
42
30
  }, [
43
31
  r,
44
- a,
32
+ c,
45
33
  i,
46
34
  u
47
35
  ]), e(() => {
48
36
  let e = r.current;
49
37
  return () => {
50
- e && o(e, a) && l(e, a) && c(e);
38
+ e && a(e) && s(e);
51
39
  };
52
- }, [r, a]);
40
+ }, [r]);
53
41
  }
54
- function d(e, t) {
42
+ function l(e, t) {
55
43
  let n = !1, a = null, o = () => {
56
44
  n || (n = !0, a != null && clearTimeout(a), e.removeEventListener("transitionend", s), e.removeEventListener("transitioncancel", s), t());
57
45
  }, s = (t) => {
58
46
  t.target === e && t.propertyName === "transform" && o();
59
47
  };
60
48
  e.addEventListener("transitionend", s), e.addEventListener("transitioncancel", s);
61
- let c = f(e);
49
+ let c = u(e);
62
50
  return a = setTimeout(o, (c ?? i) + r), () => {
63
51
  n = !0, a != null && clearTimeout(a), e.removeEventListener("transitionend", s), e.removeEventListener("transitioncancel", s);
64
52
  };
65
53
  }
66
- function f(e) {
67
- let t = window.getComputedStyle(e), n = t.transitionProperty.split(",").map((e) => e.trim()), r = p(t.transitionDuration), i = p(t.transitionDelay);
54
+ function u(e) {
55
+ let t = window.getComputedStyle(e), n = t.transitionProperty.split(",").map((e) => e.trim()), r = d(t.transitionDuration), i = d(t.transitionDelay);
68
56
  return n.length === 0 || r.length === 0 || i.length === 0 || r.includes(null) || i.includes(null) ? null : n.reduce((e, t, n) => {
69
57
  if (t !== "transform" && t !== "all") return e;
70
58
  let a = r[n % r.length], o = i[n % i.length];
71
59
  return Math.max(e, (a ?? 0) + (o ?? 0));
72
60
  }, 0);
73
61
  }
74
- function p(e) {
62
+ function d(e) {
75
63
  return e.split(",").map((e) => {
76
64
  let t = e.trim(), n = Number.parseFloat(t);
77
65
  return Number.isFinite(n) ? t.endsWith("ms") ? n : t.endsWith("s") ? n * 1e3 : null : null;
78
66
  });
79
67
  }
80
68
  //#endregion
81
- export { u as useDrawerDialogPresence };
69
+ export { c as useDrawerDialogPresence };
82
70
 
83
71
  //# sourceMappingURL=useDrawerDialogPresence.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"useDrawerDialogPresence.js","names":[],"sources":["../../../src/forks/Drawer/useDrawerDialogPresence.ts"],"sourcesContent":["// Copyright (c) Meta Platforms, Inc. and affiliates.\n//\n// Forked from @astryxdesign/lab 0.6.5-canary.8701623, src/Drawer/useDrawerDialogPresence.ts\n// (MIT; see NOTICE). Unchanged: forked with Drawer, which uses it.\n// Provenance and the drift guard: src/forks/provenance.json.\n\n\"use client\";\n\n/**\n * @file useDrawerDialogPresence.ts\n * @input Controlled open state, modal mode, dialog ref, and rendered-state setter\n * @output Coordinates native top-layer presence, exit timing, focus restoration, and unmount cleanup\n * @position Drawer-internal hook; consumed only by Drawer.tsx\n *\n * The drawer has two independent notions of presence:\n * - React's rendered state keeps the panel visible for its CSS exit.\n * - Native `showModal()` or `showPopover()` state keeps it in the browser top\n * layer.\n *\n * Their close ordering is a browser-visible invariant: the panel must finish\n * its exit, then leave the active native host and hide in the same task. If the\n * hide lands a frame later, a transformed ancestor becomes the containing block\n * for the now-non-top-layer `position: fixed` panel and it paints back inside\n * the page for one frame.\n *\n * SYNC: When modified, update:\n * - /packages/lab/src/Drawer/Drawer.test.tsx\n * - /.github/scripts/modal-close-visibility.js\n */\n\nimport {\n useEffect,\n useRef,\n type Dispatch,\n type RefObject,\n type SetStateAction,\n} from \"react\";\nimport { flushSync } from \"react-dom\";\n\n/** Slack past the computed transition before the backstop gives up waiting. */\nconst EXIT_BACKSTOP_BUFFER_MS = 50;\n\n/**\n * Hold used when the transition duration cannot be read — an unresolved\n * `var()` outside a real browser. Picking a fixed number would otherwise make\n * an assumption about the consumer's theme.\n */\nconst EXIT_FALLBACK_MS = 250;\n\ntype UseDrawerDialogPresenceOptions = {\n dialogRef: RefObject<HTMLDialogElement | null>;\n isOpen: boolean;\n isModal: boolean;\n setIsRendered: Dispatch<SetStateAction<boolean>>;\n};\n\nfunction isPopoverOpen(dialog: HTMLDialogElement): boolean {\n try {\n return dialog.matches(\":popover-open\");\n } catch {\n // jsdom and pre-Popover browsers do not recognize :popover-open.\n return false;\n }\n}\n\nfunction isDrawerHostOpen(dialog: HTMLDialogElement, isModal: boolean): boolean {\n return isModal ? dialog.open : isPopoverOpen(dialog) || dialog.open;\n}\n\nfunction showDrawerHost(dialog: HTMLDialogElement, isModal: boolean): void {\n if (isModal) {\n dialog.showModal();\n } else if (typeof dialog.showPopover === \"function\") {\n dialog.showPopover();\n } else {\n // Reduced fallback for browsers below the Popover API support floor.\n dialog.show();\n }\n}\n\nfunction dispatchDialogClose(dialog: HTMLDialogElement): void {\n const EventConstructor = dialog.ownerDocument.defaultView?.Event ?? Event;\n dialog.dispatchEvent(new EventConstructor(\"close\"));\n}\n\n/**\n * Leaves the active native host. Returns whether the caller owes a synthetic\n * `close` dispatch: `dialog.close()` used to power the non-modal path, and\n * consumers observe its native `close` event through refs/onClose. Popover\n * dismissal has no equivalent event, so that public DOM contract is preserved\n * explicitly — but by the caller, not here. Native `close` is queued as a\n * task, so a consumer's listener always ran after this hook's own focus\n * restore; the synthetic event must keep that ordering (and fire exactly once\n * per exit, including the unmount path).\n */\nfunction hideDrawerHost(dialog: HTMLDialogElement, isModal: boolean): boolean {\n if (!isModal && typeof dialog.hidePopover === \"function\") {\n dialog.hidePopover();\n return true;\n }\n if (dialog.open) {\n dialog.close();\n }\n return false;\n}\n\n/**\n * Coordinates the native dialog and React-rendered presence for Drawer.\n *\n * Opening captures the trigger, enters the modal-dialog or manual-popover host,\n * and honours the component's `data-autofocus` contract. Closing waits for the\n * actual transform transition (with a computed-duration backstop), then leaves\n * the native host and synchronously hides the panel before the browser can paint\n * it outside the top layer, restores focus to the captured trigger, and only\n * then dispatches the popover host's synthetic `close`. Unmount cleanup closes\n * a host left open by React Activity or a removed subtree.\n */\nexport function useDrawerDialogPresence({\n dialogRef,\n isOpen,\n isModal,\n setIsRendered,\n}: UseDrawerDialogPresenceOptions): void {\n // Element focused when the drawer opened — restored on close.\n const triggerElementRef = useRef<HTMLElement | null>(null);\n\n useEffect(() => {\n const dialog = dialogRef.current;\n if (!dialog) {\n return;\n }\n\n if (isOpen) {\n if (!isDrawerHostOpen(dialog, isModal)) {\n triggerElementRef.current = document.activeElement as HTMLElement | null;\n showDrawerHost(dialog, isModal);\n // React's autoFocus calls .focus() during commit, before the native host\n // is visible, so it silently fails — honour data-autofocus instead (same\n // contract as Dialog).\n dialog.querySelector<HTMLElement>(\"[data-autofocus]\")?.focus();\n }\n return;\n }\n\n if (!isDrawerHostOpen(dialog, isModal)) {\n return;\n }\n\n return waitForDrawerExit(dialog, () => {\n const owesCloseEvent = hideDrawerHost(dialog, isModal);\n // flushSync, not a plain setState: React's default scheduling can land\n // the commit after the next paint, and that one frame is exactly the\n // bug — the panel paints outside the top layer. Both happen in this\n // task, so the browser never gets to paint between them.\n flushSync(() => {\n setIsRendered(false);\n });\n // Return focus after leaving the native host: a modal dialog makes the\n // rest of the document inert, so focusing earlier silently fails.\n triggerElementRef.current?.focus();\n triggerElementRef.current = null;\n // The synthetic popover close fires after the focus restore above,\n // mirroring the task-queued native event: a consumer close listener\n // that retargets focus (master-detail row switching) gets the last word.\n if (owesCloseEvent) {\n dispatchDialogClose(dialog);\n }\n });\n }, [dialogRef, isModal, isOpen, setIsRendered]);\n\n // Leave the active native host on unmount. When the drawer is mounted inside\n // an <Activity> that flips to mode=\"hidden\", React runs effect cleanups (with\n // stale isOpen) instead of re-running the effect with isOpen=false. Leaving\n // the host active would strand the top-layer surface and skip the next open.\n // This is deliberately separate from the open/close effect: putting it in\n // that cleanup would cut off every delayed slide-out.\n useEffect(() => {\n const dialog = dialogRef.current;\n return () => {\n if (dialog && isDrawerHostOpen(dialog, isModal)) {\n if (hideDrawerHost(dialog, isModal)) {\n // No focus restore on this path — the drawer is being torn down, so\n // the owed synthetic close fires immediately (still exactly once).\n dispatchDialogClose(dialog);\n }\n }\n };\n }, [dialogRef, isModal]);\n}\n\n/**\n * Wait for the panel's transform transition, with a computed-duration\n * backstop. Mirrors BottomSheetPanel.waitForTransition: the native event is\n * authoritative, and the timeout only prevents a lost event from stranding an\n * open dialog.\n */\nfunction waitForDrawerExit(\n element: HTMLDialogElement,\n complete: () => void,\n): () => void {\n let done = false;\n let timer: ReturnType<typeof setTimeout> | null = null;\n\n const finish = () => {\n if (done) {\n return;\n }\n done = true;\n if (timer != null) {\n clearTimeout(timer);\n }\n element.removeEventListener(\"transitionend\", handleTransitionEnd);\n element.removeEventListener(\"transitioncancel\", handleTransitionEnd);\n complete();\n };\n\n const handleTransitionEnd = (event: TransitionEvent) => {\n if (event.target === element && event.propertyName === \"transform\") {\n finish();\n }\n };\n\n element.addEventListener(\"transitionend\", handleTransitionEnd);\n element.addEventListener(\"transitioncancel\", handleTransitionEnd);\n\n const transitionMs = readTransformTransitionMs(element);\n timer = setTimeout(\n finish,\n (transitionMs ?? EXIT_FALLBACK_MS) + EXIT_BACKSTOP_BUFFER_MS,\n );\n\n return () => {\n done = true;\n if (timer != null) {\n clearTimeout(timer);\n }\n element.removeEventListener(\"transitionend\", handleTransitionEnd);\n element.removeEventListener(\"transitioncancel\", handleTransitionEnd);\n };\n}\n\n/** Read the transform transition's duration + delay from computed CSS. */\nfunction readTransformTransitionMs(element: HTMLElement): number | null {\n const computed = window.getComputedStyle(element);\n const properties = computed.transitionProperty\n .split(\",\")\n .map((value) => value.trim());\n const durations = parseTimes(computed.transitionDuration);\n const delays = parseTimes(computed.transitionDelay);\n\n if (\n properties.length === 0 ||\n durations.length === 0 ||\n delays.length === 0 ||\n durations.includes(null) ||\n delays.includes(null)\n ) {\n return null;\n }\n\n return properties.reduce((longest, property, index) => {\n if (property !== \"transform\" && property !== \"all\") {\n return longest;\n }\n const duration = durations[index % durations.length];\n const delay = delays[index % delays.length];\n return Math.max(longest, (duration ?? 0) + (delay ?? 0));\n }, 0);\n}\n\nfunction parseTimes(value: string): (number | null)[] {\n return value.split(\",\").map((part) => {\n const trimmed = part.trim();\n const time = Number.parseFloat(trimmed);\n if (!Number.isFinite(time)) {\n return null;\n }\n if (trimmed.endsWith(\"ms\")) {\n return time;\n }\n return trimmed.endsWith(\"s\") ? time * 1000 : null;\n });\n}\n"],"mappings":";;;;AAwCA,IAAM,IAA0B,IAO1B,IAAmB;AASzB,SAAS,EAAc,GAAoC;CACzD,IAAI;EACF,OAAO,EAAO,QAAQ,eAAe;CACvC,QAAQ;EAEN,OAAO;CACT;AACF;AAEA,SAAS,EAAiB,GAA2B,GAA2B;CAC9E,OAAO,IAAU,EAAO,OAAO,EAAc,CAAM,KAAK,EAAO;AACjE;AAEA,SAAS,EAAe,GAA2B,GAAwB;CACzE,AAAI,IACF,EAAO,UAAU,IACR,OAAO,EAAO,eAAgB,aACvC,EAAO,YAAY,IAGnB,EAAO,KAAK;AAEhB;AAEA,SAAS,EAAoB,GAAiC;CAC5D,IAAM,IAAmB,EAAO,cAAc,aAAa,SAAS;CACpE,EAAO,cAAc,IAAI,EAAiB,OAAO,CAAC;AACpD;AAYA,SAAS,EAAe,GAA2B,GAA2B;CAQ5E,OAPI,CAAC,KAAW,OAAO,EAAO,eAAgB,cAC5C,EAAO,YAAY,GACZ,OAEL,EAAO,QACT,EAAO,MAAM,GAER;AACT;AAaA,SAAgB,EAAwB,EACtC,cACA,WACA,YACA,oBACuC;CAEvC,IAAM,IAAoB,EAA2B,IAAI;CAoDzD,AAlDA,QAAgB;EACd,IAAM,IAAS,EAAU;EACpB,OAIL;OAAI,GAAQ;IACV,AAAK,EAAiB,GAAQ,CAAO,MACnC,EAAkB,UAAU,SAAS,eACrC,EAAe,GAAQ,CAAO,GAI9B,EAAO,cAA2B,kBAAkB,CAAC,EAAE,MAAM;IAE/D;GACF;GAEK,MAAiB,GAAQ,CAAO,GAIrC,OAAO,EAAkB,SAAc;IACrC,IAAM,IAAiB,EAAe,GAAQ,CAAO;IAerD,AAVA,QAAgB;KACd,EAAc,EAAK;IACrB,CAAC,GAGD,EAAkB,SAAS,MAAM,GACjC,EAAkB,UAAU,MAIxB,KACF,EAAoB,CAAM;GAE9B,CAAC;EAzBD;CA0BF,GAAG;EAAC;EAAW;EAAS;EAAQ;CAAa,CAAC,GAQ9C,QAAgB;EACd,IAAM,IAAS,EAAU;EACzB,aAAa;GACX,AAAI,KAAU,EAAiB,GAAQ,CAAO,KACxC,EAAe,GAAQ,CAAO,KAGhC,EAAoB,CAAM;EAGhC;CACF,GAAG,CAAC,GAAW,CAAO,CAAC;AACzB;AAQA,SAAS,EACP,GACA,GACY;CACZ,IAAI,IAAO,IACP,IAA8C,MAE5C,UAAe;EACf,MAGJ,IAAO,IACH,KAAS,QACX,aAAa,CAAK,GAEpB,EAAQ,oBAAoB,iBAAiB,CAAmB,GAChE,EAAQ,oBAAoB,oBAAoB,CAAmB,GACnE,EAAS;CACX,GAEM,KAAuB,MAA2B;EACtD,AAAI,EAAM,WAAW,KAAW,EAAM,iBAAiB,eACrD,EAAO;CAEX;CAGA,AADA,EAAQ,iBAAiB,iBAAiB,CAAmB,GAC7D,EAAQ,iBAAiB,oBAAoB,CAAmB;CAEhE,IAAM,IAAe,EAA0B,CAAO;CAMtD,OALA,IAAQ,WACN,IACC,KAAgB,KAAoB,CACvC,SAEa;EAMX,AALA,IAAO,IACH,KAAS,QACX,aAAa,CAAK,GAEpB,EAAQ,oBAAoB,iBAAiB,CAAmB,GAChE,EAAQ,oBAAoB,oBAAoB,CAAmB;CACrE;AACF;AAGA,SAAS,EAA0B,GAAqC;CACtE,IAAM,IAAW,OAAO,iBAAiB,CAAO,GAC1C,IAAa,EAAS,mBACzB,MAAM,GAAG,CAAC,CACV,KAAK,MAAU,EAAM,KAAK,CAAC,GACxB,IAAY,EAAW,EAAS,kBAAkB,GAClD,IAAS,EAAW,EAAS,eAAe;CAYlD,OATE,EAAW,WAAW,KACtB,EAAU,WAAW,KACrB,EAAO,WAAW,KAClB,EAAU,SAAS,IAAI,KACvB,EAAO,SAAS,IAAI,IAEb,OAGF,EAAW,QAAQ,GAAS,GAAU,MAAU;EACrD,IAAI,MAAa,eAAe,MAAa,OAC3C,OAAO;EAET,IAAM,IAAW,EAAU,IAAQ,EAAU,SACvC,IAAQ,EAAO,IAAQ,EAAO;EACpC,OAAO,KAAK,IAAI,IAAU,KAAY,MAAM,KAAS,EAAE;CACzD,GAAG,CAAC;AACN;AAEA,SAAS,EAAW,GAAkC;CACpD,OAAO,EAAM,MAAM,GAAG,CAAC,CAAC,KAAK,MAAS;EACpC,IAAM,IAAU,EAAK,KAAK,GACpB,IAAO,OAAO,WAAW,CAAO;EAOtC,OANK,OAAO,SAAS,CAAI,IAGrB,EAAQ,SAAS,IAAI,IAChB,IAEF,EAAQ,SAAS,GAAG,IAAI,IAAO,MAAO,OALpC;CAMX,CAAC;AACH"}
1
+ {"version":3,"file":"useDrawerDialogPresence.js","names":[],"sources":["../../../src/forks/Drawer/useDrawerDialogPresence.ts"],"sourcesContent":["// Copyright (c) Meta Platforms, Inc. and affiliates.\n//\n// Forked from @astryxdesign/lab 0.6.5-canary.8701623, src/Drawer/useDrawerDialogPresence.ts\n// (MIT; see NOTICE). Changed (marked `ui-common:`): a non-modal drawer opens\n// with `show()`, not `showPopover()`, so it stays out of the top layer (Drawer.tsx).\n// Provenance and the drift guard: src/forks/provenance.json.\n\n\"use client\";\n\n/**\n * @file useDrawerDialogPresence.ts\n * @input Controlled open state, modal mode, dialog ref, and rendered-state setter\n * @output Coordinates native dialog presence, exit timing, focus restoration, and unmount cleanup\n * @position Drawer-internal hook; consumed only by Drawer.tsx\n *\n * The drawer has two independent notions of presence:\n * - React's rendered state keeps the panel visible for its CSS exit.\n * - The native dialog's `open` state: `showModal()` (top layer) with a scrim,\n * `show()` (in the page, by z-index) without (ui-common; lab uses\n * `showPopover()`).\n *\n * Their close ordering is a browser-visible invariant: the panel must finish\n * its exit, then leave the active native host and hide in the same task. If the\n * hide lands a frame later, a transformed ancestor becomes the containing block\n * for the now-non-top-layer `position: fixed` panel and it paints back inside\n * the page for one frame.\n *\n * SYNC: When modified, update:\n * - /packages/lab/src/Drawer/Drawer.test.tsx\n * - /.github/scripts/modal-close-visibility.js\n */\n\nimport {\n useEffect,\n useRef,\n type Dispatch,\n type RefObject,\n type SetStateAction,\n} from \"react\";\nimport { flushSync } from \"react-dom\";\n\n/** Slack past the computed transition before the backstop gives up waiting. */\nconst EXIT_BACKSTOP_BUFFER_MS = 50;\n\n/**\n * Hold used when the transition duration cannot be read — an unresolved\n * `var()` outside a real browser. Picking a fixed number would otherwise make\n * an assumption about the consumer's theme.\n */\nconst EXIT_FALLBACK_MS = 250;\n\ntype UseDrawerDialogPresenceOptions = {\n dialogRef: RefObject<HTMLDialogElement | null>;\n isOpen: boolean;\n isModal: boolean;\n setIsRendered: Dispatch<SetStateAction<boolean>>;\n};\n\n// ui-common: a non-modal drawer opens with `show()` and stacks by z-index, as\n// it did before lab 0.6.5 moved it into the top layer as a manual popover.\n// ui-common's `Modal` and notification stack are z-index surfaces too, so a\n// top-layer drawer would paint over every one of them (Drawer.tsx).\nfunction isDrawerHostOpen(dialog: HTMLDialogElement): boolean {\n return dialog.open;\n}\n\nfunction showDrawerHost(dialog: HTMLDialogElement, isModal: boolean): void {\n if (isModal) {\n dialog.showModal();\n } else {\n dialog.show();\n }\n}\n\n/** Leaves the native host; `close()` fires the native `close` event itself. */\nfunction hideDrawerHost(dialog: HTMLDialogElement): void {\n if (dialog.open) {\n dialog.close();\n }\n}\n\n/**\n * Coordinates the native dialog and React-rendered presence for Drawer.\n *\n * Opening captures the trigger, opens the dialog (`showModal()` or `show()`),\n * and honours the component's `data-autofocus` contract. Closing waits for the\n * actual transform transition (with a computed-duration backstop), then calls\n * `close()` (which fires the native `close` event) and synchronously hides the\n * panel before the browser can paint it outside the top layer, then restores\n * focus to the captured trigger. Unmount cleanup closes a dialog left open by\n * React Activity or a removed subtree.\n */\nexport function useDrawerDialogPresence({\n dialogRef,\n isOpen,\n isModal,\n setIsRendered,\n}: UseDrawerDialogPresenceOptions): void {\n // Element focused when the drawer opened — restored on close.\n const triggerElementRef = useRef<HTMLElement | null>(null);\n\n useEffect(() => {\n const dialog = dialogRef.current;\n if (!dialog) {\n return;\n }\n\n if (isOpen) {\n if (!isDrawerHostOpen(dialog)) {\n triggerElementRef.current = document.activeElement as HTMLElement | null;\n showDrawerHost(dialog, isModal);\n // React's autoFocus calls .focus() during commit, before the native host\n // is visible, so it silently fails — honour data-autofocus instead (same\n // contract as Dialog).\n dialog.querySelector<HTMLElement>(\"[data-autofocus]\")?.focus();\n }\n return;\n }\n\n if (!isDrawerHostOpen(dialog)) {\n return;\n }\n\n return waitForDrawerExit(dialog, () => {\n hideDrawerHost(dialog);\n // flushSync, not a plain setState: React's default scheduling can land\n // the commit after the next paint, and that one frame is exactly the\n // bug — the panel paints outside the top layer. Both happen in this\n // task, so the browser never gets to paint between them.\n flushSync(() => {\n setIsRendered(false);\n });\n // Return focus after leaving the native host: a modal dialog makes the\n // rest of the document inert, so focusing earlier silently fails.\n triggerElementRef.current?.focus();\n triggerElementRef.current = null;\n });\n }, [dialogRef, isModal, isOpen, setIsRendered]);\n\n // Leave the active native host on unmount. When the drawer is mounted inside\n // an <Activity> that flips to mode=\"hidden\", React runs effect cleanups (with\n // stale isOpen) instead of re-running the effect with isOpen=false. Leaving\n // the host active would strand the top-layer surface and skip the next open.\n // This is deliberately separate from the open/close effect: putting it in\n // that cleanup would cut off every delayed slide-out.\n useEffect(() => {\n const dialog = dialogRef.current;\n return () => {\n if (dialog && isDrawerHostOpen(dialog)) {\n hideDrawerHost(dialog);\n }\n };\n }, [dialogRef]);\n}\n\n/**\n * Wait for the panel's transform transition, with a computed-duration\n * backstop. Mirrors BottomSheetPanel.waitForTransition: the native event is\n * authoritative, and the timeout only prevents a lost event from stranding an\n * open dialog.\n */\nfunction waitForDrawerExit(\n element: HTMLDialogElement,\n complete: () => void,\n): () => void {\n let done = false;\n let timer: ReturnType<typeof setTimeout> | null = null;\n\n const finish = () => {\n if (done) {\n return;\n }\n done = true;\n if (timer != null) {\n clearTimeout(timer);\n }\n element.removeEventListener(\"transitionend\", handleTransitionEnd);\n element.removeEventListener(\"transitioncancel\", handleTransitionEnd);\n complete();\n };\n\n const handleTransitionEnd = (event: TransitionEvent) => {\n if (event.target === element && event.propertyName === \"transform\") {\n finish();\n }\n };\n\n element.addEventListener(\"transitionend\", handleTransitionEnd);\n element.addEventListener(\"transitioncancel\", handleTransitionEnd);\n\n const transitionMs = readTransformTransitionMs(element);\n timer = setTimeout(\n finish,\n (transitionMs ?? EXIT_FALLBACK_MS) + EXIT_BACKSTOP_BUFFER_MS,\n );\n\n return () => {\n done = true;\n if (timer != null) {\n clearTimeout(timer);\n }\n element.removeEventListener(\"transitionend\", handleTransitionEnd);\n element.removeEventListener(\"transitioncancel\", handleTransitionEnd);\n };\n}\n\n/** Read the transform transition's duration + delay from computed CSS. */\nfunction readTransformTransitionMs(element: HTMLElement): number | null {\n const computed = window.getComputedStyle(element);\n const properties = computed.transitionProperty\n .split(\",\")\n .map((value) => value.trim());\n const durations = parseTimes(computed.transitionDuration);\n const delays = parseTimes(computed.transitionDelay);\n\n if (\n properties.length === 0 ||\n durations.length === 0 ||\n delays.length === 0 ||\n durations.includes(null) ||\n delays.includes(null)\n ) {\n return null;\n }\n\n return properties.reduce((longest, property, index) => {\n if (property !== \"transform\" && property !== \"all\") {\n return longest;\n }\n const duration = durations[index % durations.length];\n const delay = delays[index % delays.length];\n return Math.max(longest, (duration ?? 0) + (delay ?? 0));\n }, 0);\n}\n\nfunction parseTimes(value: string): (number | null)[] {\n return value.split(\",\").map((part) => {\n const trimmed = part.trim();\n const time = Number.parseFloat(trimmed);\n if (!Number.isFinite(time)) {\n return null;\n }\n if (trimmed.endsWith(\"ms\")) {\n return time;\n }\n return trimmed.endsWith(\"s\") ? time * 1000 : null;\n });\n}\n"],"mappings":";;;;AA0CA,IAAM,IAA0B,IAO1B,IAAmB;AAazB,SAAS,EAAiB,GAAoC;CAC5D,OAAO,EAAO;AAChB;AAEA,SAAS,EAAe,GAA2B,GAAwB;CACzE,AAAI,IACF,EAAO,UAAU,IAEjB,EAAO,KAAK;AAEhB;AAGA,SAAS,EAAe,GAAiC;CACvD,AAAI,EAAO,QACT,EAAO,MAAM;AAEjB;AAaA,SAAgB,EAAwB,EACtC,cACA,WACA,YACA,oBACuC;CAEvC,IAAM,IAAoB,EAA2B,IAAI;CA8CzD,AA5CA,QAAgB;EACd,IAAM,IAAS,EAAU;EACpB,OAIL;OAAI,GAAQ;IACV,AAAK,EAAiB,CAAM,MAC1B,EAAkB,UAAU,SAAS,eACrC,EAAe,GAAQ,CAAO,GAI9B,EAAO,cAA2B,kBAAkB,CAAC,EAAE,MAAM;IAE/D;GACF;GAEK,MAAiB,CAAM,GAI5B,OAAO,EAAkB,SAAc;IAYrC,AAXA,EAAe,CAAM,GAKrB,QAAgB;KACd,EAAc,EAAK;IACrB,CAAC,GAGD,EAAkB,SAAS,MAAM,GACjC,EAAkB,UAAU;GAC9B,CAAC;EAnBD;CAoBF,GAAG;EAAC;EAAW;EAAS;EAAQ;CAAa,CAAC,GAQ9C,QAAgB;EACd,IAAM,IAAS,EAAU;EACzB,aAAa;GACX,AAAI,KAAU,EAAiB,CAAM,KACnC,EAAe,CAAM;EAEzB;CACF,GAAG,CAAC,CAAS,CAAC;AAChB;AAQA,SAAS,EACP,GACA,GACY;CACZ,IAAI,IAAO,IACP,IAA8C,MAE5C,UAAe;EACf,MAGJ,IAAO,IACH,KAAS,QACX,aAAa,CAAK,GAEpB,EAAQ,oBAAoB,iBAAiB,CAAmB,GAChE,EAAQ,oBAAoB,oBAAoB,CAAmB,GACnE,EAAS;CACX,GAEM,KAAuB,MAA2B;EACtD,AAAI,EAAM,WAAW,KAAW,EAAM,iBAAiB,eACrD,EAAO;CAEX;CAGA,AADA,EAAQ,iBAAiB,iBAAiB,CAAmB,GAC7D,EAAQ,iBAAiB,oBAAoB,CAAmB;CAEhE,IAAM,IAAe,EAA0B,CAAO;CAMtD,OALA,IAAQ,WACN,IACC,KAAgB,KAAoB,CACvC,SAEa;EAMX,AALA,IAAO,IACH,KAAS,QACX,aAAa,CAAK,GAEpB,EAAQ,oBAAoB,iBAAiB,CAAmB,GAChE,EAAQ,oBAAoB,oBAAoB,CAAmB;CACrE;AACF;AAGA,SAAS,EAA0B,GAAqC;CACtE,IAAM,IAAW,OAAO,iBAAiB,CAAO,GAC1C,IAAa,EAAS,mBACzB,MAAM,GAAG,CAAC,CACV,KAAK,MAAU,EAAM,KAAK,CAAC,GACxB,IAAY,EAAW,EAAS,kBAAkB,GAClD,IAAS,EAAW,EAAS,eAAe;CAYlD,OATE,EAAW,WAAW,KACtB,EAAU,WAAW,KACrB,EAAO,WAAW,KAClB,EAAU,SAAS,IAAI,KACvB,EAAO,SAAS,IAAI,IAEb,OAGF,EAAW,QAAQ,GAAS,GAAU,MAAU;EACrD,IAAI,MAAa,eAAe,MAAa,OAC3C,OAAO;EAET,IAAM,IAAW,EAAU,IAAQ,EAAU,SACvC,IAAQ,EAAO,IAAQ,EAAO;EACpC,OAAO,KAAK,IAAI,IAAU,KAAY,MAAM,KAAS,EAAE;CACzD,GAAG,CAAC;AACN;AAEA,SAAS,EAAW,GAAkC;CACpD,OAAO,EAAM,MAAM,GAAG,CAAC,CAAC,KAAK,MAAS;EACpC,IAAM,IAAU,EAAK,KAAK,GACpB,IAAO,OAAO,WAAW,CAAO;EAOtC,OANK,OAAO,SAAS,CAAI,IAGrB,EAAQ,SAAS,IAAI,IAChB,IAEF,EAAQ,SAAS,GAAG,IAAI,IAAO,MAAO,OALpC;CAMX,CAAC;AACH"}
package/dist/instance.js CHANGED
@@ -16,7 +16,7 @@ function r(e, r, i = console.warn) {
16
16
  o !== null && (a.warned = !0, i(o));
17
17
  }
18
18
  var i = {
19
- version: "0.2.0-alpha.16",
19
+ version: "0.2.0-alpha.17",
20
20
  astryxVersion: "0.6.5",
21
21
  url: import.meta.url,
22
22
  astryxIdentity: e
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lablup/ui-common",
3
- "version": "0.2.0-alpha.16",
3
+ "version": "0.2.0-alpha.17",
4
4
  "description": "Lablup's UI layer on Astryx: the Astryx surface re-exported 1:1, the Lablup theme, and shared components",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Lablup Inc.",