@charcuterie/ui 1.0.1 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (134) hide show
  1. package/dist/ColorSchemeSwitcher/ColorSchemeSwitcher.d.ts +35 -0
  2. package/dist/ColorSchemeSwitcher/ColorSchemeSwitcher.d.ts.map +1 -0
  3. package/dist/ColorSchemeSwitcher/ColorSchemeSwitcher.js +33 -0
  4. package/dist/ColorSchemeSwitcher/ColorSchemeSwitcher.js.map +1 -0
  5. package/dist/ColorSchemeToggle/ColorSchemeToggle.d.ts +54 -0
  6. package/dist/ColorSchemeToggle/ColorSchemeToggle.d.ts.map +1 -0
  7. package/dist/ColorSchemeToggle/ColorSchemeToggle.js +30 -0
  8. package/dist/ColorSchemeToggle/ColorSchemeToggle.js.map +1 -0
  9. package/dist/Combobox/Combobox.d.ts +54 -0
  10. package/dist/Combobox/Combobox.d.ts.map +1 -0
  11. package/dist/Combobox/Combobox.js +216 -0
  12. package/dist/Combobox/Combobox.js.map +1 -0
  13. package/dist/Combobox/ComboboxOption.d.ts +36 -0
  14. package/dist/Combobox/ComboboxOption.d.ts.map +1 -0
  15. package/dist/Combobox/ComboboxOption.js +32 -0
  16. package/dist/Combobox/ComboboxOption.js.map +1 -0
  17. package/dist/Dialog/Dialog.d.ts +47 -0
  18. package/dist/Dialog/Dialog.d.ts.map +1 -0
  19. package/dist/Dialog/Dialog.js +52 -0
  20. package/dist/Dialog/Dialog.js.map +1 -0
  21. package/dist/Lightbox/Lightbox.d.ts +57 -0
  22. package/dist/Lightbox/Lightbox.d.ts.map +1 -0
  23. package/dist/Lightbox/Lightbox.js +50 -0
  24. package/dist/Lightbox/Lightbox.js.map +1 -0
  25. package/dist/Listbox/Listbox.d.ts +56 -0
  26. package/dist/Listbox/Listbox.d.ts.map +1 -0
  27. package/dist/Listbox/Listbox.js +151 -0
  28. package/dist/Listbox/Listbox.js.map +1 -0
  29. package/dist/Listbox/ListboxOption.d.ts +36 -0
  30. package/dist/Listbox/ListboxOption.d.ts.map +1 -0
  31. package/dist/Listbox/ListboxOption.js +42 -0
  32. package/dist/Listbox/ListboxOption.js.map +1 -0
  33. package/dist/Menu/Menu.d.ts.map +1 -1
  34. package/dist/Menu/Menu.js +47 -97
  35. package/dist/Menu/Menu.js.map +1 -1
  36. package/dist/Modal/Modal.d.ts +11 -59
  37. package/dist/Modal/Modal.d.ts.map +1 -1
  38. package/dist/Modal/Modal.js +28 -101
  39. package/dist/Modal/Modal.js.map +1 -1
  40. package/dist/Overlay/OverlayPanel.d.ts +50 -0
  41. package/dist/Overlay/OverlayPanel.d.ts.map +1 -0
  42. package/dist/Overlay/OverlayPanel.js +72 -0
  43. package/dist/Overlay/OverlayPanel.js.map +1 -0
  44. package/dist/Overlay/OverlayStack.d.ts +47 -0
  45. package/dist/Overlay/OverlayStack.d.ts.map +1 -0
  46. package/dist/Overlay/OverlayStack.js +46 -0
  47. package/dist/Overlay/OverlayStack.js.map +1 -0
  48. package/dist/Overlay/SharedBackdrop.d.ts +24 -0
  49. package/dist/Overlay/SharedBackdrop.d.ts.map +1 -0
  50. package/dist/Overlay/SharedBackdrop.js +28 -0
  51. package/dist/Overlay/SharedBackdrop.js.map +1 -0
  52. package/dist/Overlay/lockScrollBehind.d.ts.map +1 -0
  53. package/dist/Overlay/lockScrollBehind.js.map +1 -0
  54. package/dist/Overlay/overlayPanelClass.d.ts +17 -0
  55. package/dist/Overlay/overlayPanelClass.d.ts.map +1 -0
  56. package/dist/Overlay/overlayPanelClass.js +17 -0
  57. package/dist/Overlay/overlayPanelClass.js.map +1 -0
  58. package/dist/Overlay/useAnchoredOverlay.d.ts +97 -0
  59. package/dist/Overlay/useAnchoredOverlay.d.ts.map +1 -0
  60. package/dist/Overlay/useAnchoredOverlay.js +81 -0
  61. package/dist/Overlay/useAnchoredOverlay.js.map +1 -0
  62. package/dist/Popover/Popover.d.ts +16 -24
  63. package/dist/Popover/Popover.d.ts.map +1 -1
  64. package/dist/Popover/Popover.js +32 -68
  65. package/dist/Popover/Popover.js.map +1 -1
  66. package/dist/Tooltip/Tooltip.d.ts.map +1 -1
  67. package/dist/Tooltip/Tooltip.js +25 -25
  68. package/dist/Tooltip/Tooltip.js.map +1 -1
  69. package/dist/index.d.ts +13 -1
  70. package/dist/index.d.ts.map +1 -1
  71. package/dist/index.js +6 -0
  72. package/dist/index.js.map +1 -1
  73. package/dist/mountStory.testHelpers.d.ts +1 -0
  74. package/dist/mountStory.testHelpers.d.ts.map +1 -1
  75. package/dist/mountStory.testHelpers.js +21 -1
  76. package/dist/mountStory.testHelpers.js.map +1 -1
  77. package/package.json +5 -4
  78. package/src/ColorSchemeSwitcher/ColorSchemeSwitcher.mdx +70 -0
  79. package/src/ColorSchemeSwitcher/ColorSchemeSwitcher.stories.tsx +118 -0
  80. package/src/ColorSchemeSwitcher/ColorSchemeSwitcher.test.tsx +53 -0
  81. package/src/ColorSchemeSwitcher/ColorSchemeSwitcher.tsx +90 -0
  82. package/src/ColorSchemeToggle/ColorSchemeToggle.mdx +48 -0
  83. package/src/ColorSchemeToggle/ColorSchemeToggle.stories.tsx +161 -0
  84. package/src/ColorSchemeToggle/ColorSchemeToggle.test.tsx +80 -0
  85. package/src/ColorSchemeToggle/ColorSchemeToggle.tsx +107 -0
  86. package/src/Combobox/Combobox.mdx +71 -0
  87. package/src/Combobox/Combobox.stories.tsx +245 -0
  88. package/src/Combobox/Combobox.test.tsx +289 -0
  89. package/src/Combobox/Combobox.tsx +513 -0
  90. package/src/Combobox/ComboboxOption.tsx +88 -0
  91. package/src/Dialog/Dialog.mdx +87 -0
  92. package/src/Dialog/Dialog.stories.tsx +347 -0
  93. package/src/Dialog/Dialog.test.tsx +312 -0
  94. package/src/Dialog/Dialog.tsx +127 -0
  95. package/src/Field/Field.test.tsx +6 -4
  96. package/src/Lightbox/Lightbox.mdx +63 -0
  97. package/src/Lightbox/Lightbox.stories.tsx +133 -0
  98. package/src/Lightbox/Lightbox.test.tsx +124 -0
  99. package/src/Lightbox/Lightbox.tsx +143 -0
  100. package/src/Listbox/Listbox.mdx +68 -0
  101. package/src/Listbox/Listbox.stories.tsx +205 -0
  102. package/src/Listbox/Listbox.test.tsx +198 -0
  103. package/src/Listbox/Listbox.tsx +295 -0
  104. package/src/Listbox/ListboxOption.tsx +104 -0
  105. package/src/Menu/Menu.mdx +11 -13
  106. package/src/Menu/Menu.test.tsx +47 -38
  107. package/src/Menu/Menu.tsx +81 -142
  108. package/src/Modal/Modal.mdx +29 -82
  109. package/src/Modal/Modal.stories.tsx +55 -188
  110. package/src/Modal/Modal.test.tsx +44 -177
  111. package/src/Modal/Modal.tsx +54 -204
  112. package/src/Overlay/OverlayPanel.tsx +195 -0
  113. package/src/Overlay/OverlayStack.test.tsx +235 -0
  114. package/src/Overlay/OverlayStack.tsx +132 -0
  115. package/src/Overlay/SharedBackdrop.tsx +40 -0
  116. package/src/Overlay/overlayPanelClass.ts +17 -0
  117. package/src/Overlay/useAnchoredOverlay.ts +197 -0
  118. package/src/Popover/Popover.mdx +17 -20
  119. package/src/Popover/Popover.stories.tsx +33 -0
  120. package/src/Popover/Popover.test.tsx +68 -32
  121. package/src/Popover/Popover.tsx +56 -95
  122. package/src/Tooltip/Tooltip.mdx +12 -9
  123. package/src/Tooltip/Tooltip.test.tsx +17 -19
  124. package/src/Tooltip/Tooltip.tsx +35 -32
  125. package/src/icons.storyHelpers.tsx +22 -0
  126. package/src/index.ts +22 -4
  127. package/src/mountStory.testHelpers.ts +25 -1
  128. package/src/sourceRules.test.ts +26 -1
  129. package/src/storyControls.test.ts +25 -12
  130. package/dist/Modal/lockScrollBehind.d.ts.map +0 -1
  131. package/dist/Modal/lockScrollBehind.js.map +0 -1
  132. /package/dist/{Modal → Overlay}/lockScrollBehind.d.ts +0 -0
  133. /package/dist/{Modal → Overlay}/lockScrollBehind.js +0 -0
  134. /package/src/{Modal → Overlay}/lockScrollBehind.ts +0 -0
@@ -0,0 +1,46 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useLatestRef } from "@charcuterie/logic";
3
+ import { createContext, useCallback, useContext, useMemo, useState, } from "react";
4
+ import { SharedBackdrop } from "./SharedBackdrop.js";
5
+ const DEFAULT_STACK = {
6
+ depth: 0,
7
+ isProvided: false,
8
+ register: () => { },
9
+ requestCloseTop: () => { },
10
+ topId: null,
11
+ unregister: () => { },
12
+ };
13
+ const OverlayStackContext = createContext(DEFAULT_STACK);
14
+ export const useOverlayStack = () => useContext(OverlayStackContext);
15
+ export const OverlayStackProvider = ({ children, }) => {
16
+ const [entries, setEntries] = useState([]);
17
+ // Read through a ref so `requestCloseTop` stays stable while still
18
+ // seeing the live top.
19
+ const entriesRef = useLatestRef(entries);
20
+ const register = useCallback((entry) => {
21
+ setEntries((previous) =>
22
+ // In place when the id is already present — an `onClose`
23
+ // update must not reorder the stack, or a re-render would
24
+ // shuffle which modal is "top". New ids append, which is what
25
+ // makes the last-registered one the top.
26
+ previous.some((one) => one.id === entry.id)
27
+ ? previous.map((one) => one.id === entry.id ? entry : one)
28
+ : [...previous, entry]);
29
+ }, []);
30
+ const unregister = useCallback((id) => {
31
+ setEntries((previous) => previous.filter((one) => one.id !== id));
32
+ }, []);
33
+ const requestCloseTop = useCallback(() => {
34
+ entriesRef.current.at(-1)?.onClose();
35
+ }, [entriesRef]);
36
+ const value = useMemo(() => ({
37
+ depth: entries.length,
38
+ isProvided: true,
39
+ register,
40
+ requestCloseTop,
41
+ topId: entries.at(-1)?.id ?? null,
42
+ unregister,
43
+ }), [entries, register, requestCloseTop, unregister]);
44
+ return (_jsxs(OverlayStackContext.Provider, { value: value, children: [_jsx(SharedBackdrop, { isVisible: entries.length > 0 }), children] }));
45
+ };
46
+ //# sourceMappingURL=OverlayStack.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"OverlayStack.js","sourceRoot":"","sources":["../../src/Overlay/OverlayStack.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAEjD,OAAO,EACL,aAAa,EACb,WAAW,EACX,UAAU,EACV,OAAO,EACP,QAAQ,GACT,MAAM,OAAO,CAAA;AAEd,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAsB,CAAA;AA6CrD,MAAM,aAAa,GAAsB;IACvC,KAAK,EAAE,CAAC;IACR,UAAU,EAAE,KAAK;IACjB,QAAQ,EAAE,GAAG,EAAE,GAAE,CAAC;IAClB,eAAe,EAAE,GAAG,EAAE,GAAE,CAAC;IACzB,KAAK,EAAE,IAAI;IACX,UAAU,EAAE,GAAG,EAAE,GAAE,CAAC;CACrB,CAAA;AAED,MAAM,mBAAmB,GACvB,aAAa,CAAoB,aAAa,CAAC,CAAA;AAEjD,MAAM,CAAC,MAAM,eAAe,GAAG,GAAsB,EAAE,CACrD,UAAU,CAAC,mBAAmB,CAAC,CAAA;AAEjC,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,EACnC,QAAQ,GAGT,EAAa,EAAE;IACd,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,QAAQ,CAEpC,EAAE,CAAC,CAAA;IAEL,mEAAmE;IACnE,uBAAuB;IACvB,MAAM,UAAU,GAAG,YAAY,CAAC,OAAO,CAAC,CAAA;IAExC,MAAM,QAAQ,GAAG,WAAW,CAC1B,CAAC,KAAwB,EAAE,EAAE;QAC3B,UAAU,CAAC,CAAC,QAAQ,EAAE,EAAE;QACtB,yDAAyD;QACzD,0DAA0D;QAC1D,8DAA8D;QAC9D,yCAAyC;QACzC,QAAQ,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,EAAE,KAAK,KAAK,CAAC,EAAE,CAAC;YACzC,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CACnB,GAAG,CAAC,EAAE,KAAK,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAClC;YACH,CAAC,CAAC,CAAC,GAAG,QAAQ,EAAE,KAAK,CAAC,CACzB,CAAA;IACH,CAAC,EACD,EAAE,CACH,CAAA;IAED,MAAM,UAAU,GAAG,WAAW,CAAC,CAAC,EAAU,EAAE,EAAE;QAC5C,UAAU,CAAC,CAAC,QAAQ,EAAE,EAAE,CACtB,QAAQ,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,EAAE,KAAK,EAAE,CAAC,CACxC,CAAA;IACH,CAAC,EAAE,EAAE,CAAC,CAAA;IAEN,MAAM,eAAe,GAAG,WAAW,CAAC,GAAG,EAAE;QACvC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAA;IACtC,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC,CAAA;IAEhB,MAAM,KAAK,GAAG,OAAO,CACnB,GAAG,EAAE,CAAC,CAAC;QACL,KAAK,EAAE,OAAO,CAAC,MAAM;QACrB,UAAU,EAAE,IAAI;QAChB,QAAQ;QACR,eAAe;QACf,KAAK,EAAE,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,IAAI,IAAI;QACjC,UAAU;KACX,CAAC,EACF,CAAC,OAAO,EAAE,QAAQ,EAAE,eAAe,EAAE,UAAU,CAAC,CACjD,CAAA;IAED,OAAO,CACL,MAAC,mBAAmB,CAAC,QAAQ,IAAC,KAAK,EAAE,KAAK,aAGxC,KAAC,cAAc,IAAC,SAAS,EAAE,OAAO,CAAC,MAAM,GAAG,CAAC,GAAI,EAEhD,QAAQ,IACoB,CAChC,CAAA;AACH,CAAC,CAAA"}
@@ -0,0 +1,24 @@
1
+ import type { ReactNode } from "react";
2
+ /**
3
+ * The scrim, and there is exactly one of it however many backdrop
4
+ * modals are stacked.
5
+ *
6
+ * Purely visual: `aria-hidden`, no handlers. Dismissal — a press
7
+ * anywhere off the top panel — is the top panel's own
8
+ * `useDismiss({ outsidePress })`, not a click on this node, which is
9
+ * what lets the whole package drop the `useKeyWithClickEvents`
10
+ * suppression the `<dialog>` backdrop needed. A press lands on the
11
+ * top panel's full-viewport centring layer (which sits above this),
12
+ * so this element never has to be the click target.
13
+ *
14
+ * Portalled to `document.body` on `--layer-overlay`, one step **below**
15
+ * the panels' `--layer-modal`, so it always paints behind every open
16
+ * modal and never intercepts a click meant for the dialog on top of it
17
+ * — a real bug a same-z-plus-DOM-order scheme has, because a
18
+ * later-mounted scrim then covers the panel. Stacking *between* panels
19
+ * still falls out of append order at the one `--layer-modal`.
20
+ */
21
+ export declare const SharedBackdrop: ({ isVisible, }: {
22
+ isVisible: boolean;
23
+ }) => ReactNode;
24
+ //# sourceMappingURL=SharedBackdrop.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SharedBackdrop.d.ts","sourceRoot":"","sources":["../../src/Overlay/SharedBackdrop.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAEtC;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,cAAc,GAAI,gBAE5B;IACD,SAAS,EAAE,OAAO,CAAA;CACnB,KAAG,SAaH,CAAA"}
@@ -0,0 +1,28 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { FloatingPortal } from "@floating-ui/react";
3
+ /**
4
+ * The scrim, and there is exactly one of it however many backdrop
5
+ * modals are stacked.
6
+ *
7
+ * Purely visual: `aria-hidden`, no handlers. Dismissal — a press
8
+ * anywhere off the top panel — is the top panel's own
9
+ * `useDismiss({ outsidePress })`, not a click on this node, which is
10
+ * what lets the whole package drop the `useKeyWithClickEvents`
11
+ * suppression the `<dialog>` backdrop needed. A press lands on the
12
+ * top panel's full-viewport centring layer (which sits above this),
13
+ * so this element never has to be the click target.
14
+ *
15
+ * Portalled to `document.body` on `--layer-overlay`, one step **below**
16
+ * the panels' `--layer-modal`, so it always paints behind every open
17
+ * modal and never intercepts a click meant for the dialog on top of it
18
+ * — a real bug a same-z-plus-DOM-order scheme has, because a
19
+ * later-mounted scrim then covers the panel. Stacking *between* panels
20
+ * still falls out of append order at the one `--layer-modal`.
21
+ */
22
+ export const SharedBackdrop = ({ isVisible, }) => {
23
+ if (!isVisible) {
24
+ return null;
25
+ }
26
+ return (_jsx(FloatingPortal, { children: _jsx("div", { "aria-hidden": "true", className: "fixed inset-0 z-[var(--layer-overlay)] bg-scrim" }) }));
27
+ };
28
+ //# sourceMappingURL=SharedBackdrop.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SharedBackdrop.js","sourceRoot":"","sources":["../../src/Overlay/SharedBackdrop.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAA;AAGnD;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,EAC7B,SAAS,GAGV,EAAa,EAAE;IACd,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,OAAO,IAAI,CAAA;IACb,CAAC;IAED,OAAO,CACL,KAAC,cAAc,cACb,6BACc,MAAM,EAClB,SAAS,EAAC,iDAAiD,GAC3D,GACa,CAClB,CAAA;AACH,CAAC,CAAA"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lockScrollBehind.d.ts","sourceRoot":"","sources":["../../src/Overlay/lockScrollBehind.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAMH,eAAO,MAAM,gBAAgB,QAAO,CAAC,MAAM,IAAI,CAiC9C,CAAA"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lockScrollBehind.js","sourceRoot":"","sources":["../../src/Overlay/lockScrollBehind.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,IAAI,SAAS,GAAG,CAAC,CAAA;AAEjB,IAAI,gBAAgB,GAAG,EAAE,CAAA;AAEzB,MAAM,CAAC,MAAM,gBAAgB,GAAG,GAAiB,EAAE;IACjD,IAAI,OAAO,QAAQ,KAAK,WAAW,EAAE,CAAC;QACpC,OAAO,GAAG,EAAE,GAAE,CAAC,CAAA;IACjB,CAAC;IAED,MAAM,EAAE,eAAe,EAAE,GAAG,QAAQ,CAAA;IAEpC,IAAI,SAAS,KAAK,CAAC,EAAE,CAAC;QACpB,gBAAgB,GAAG,eAAe,CAAC,KAAK,CAAC,QAAQ,CAAA;QAEjD,eAAe,CAAC,KAAK,CAAC,QAAQ,GAAG,QAAQ,CAAA;IAC3C,CAAC;IAED,SAAS,IAAI,CAAC,CAAA;IAEd,IAAI,UAAU,GAAG,KAAK,CAAA;IAEtB,OAAO,GAAG,EAAE;QACV,6DAA6D;QAC7D,6DAA6D;QAC7D,qCAAqC;QACrC,IAAI,UAAU,EAAE,CAAC;YACf,OAAM;QACR,CAAC;QAED,UAAU,GAAG,IAAI,CAAA;QAEjB,SAAS,IAAI,CAAC,CAAA;QAEd,IAAI,SAAS,KAAK,CAAC,EAAE,CAAC;YACpB,eAAe,CAAC,KAAK,CAAC,QAAQ,GAAG,gBAAgB,CAAA;QACnD,CAAC;IACH,CAAC,CAAA;AACH,CAAC,CAAA"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * The surface an anchored overlay's panel is drawn on — the four
3
+ * tokens `Popover` and `Menu` wrote out byte-identically, and that
4
+ * `Listbox` and `Combobox` are about to write a third and fourth
5
+ * time.
6
+ *
7
+ * Only the *surface* lives here: the raised background, the hairline
8
+ * border, the corner radius, and the elevation. Everything a panel
9
+ * does with that surface — a popover's `max-w-xs p-3`, a menu's
10
+ * `min-w-48 flex flex-col`, a listbox's scroll clamp — is the panel's
11
+ * own and stays at the call site, composed after this with
12
+ * `toClassName`. Pulling the shared half out is what keeps a change
13
+ * to the overlay surface (a new elevation token, say) from having to
14
+ * be made in four places and kept in agreement by hand.
15
+ */
16
+ export declare const PANEL_SURFACE_CLASS = "rounded-md border border-border-default bg-surface-overlay shadow-medium";
17
+ //# sourceMappingURL=overlayPanelClass.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"overlayPanelClass.d.ts","sourceRoot":"","sources":["../../src/Overlay/overlayPanelClass.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,mBAAmB,6EAC4C,CAAA"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * The surface an anchored overlay's panel is drawn on — the four
3
+ * tokens `Popover` and `Menu` wrote out byte-identically, and that
4
+ * `Listbox` and `Combobox` are about to write a third and fourth
5
+ * time.
6
+ *
7
+ * Only the *surface* lives here: the raised background, the hairline
8
+ * border, the corner radius, and the elevation. Everything a panel
9
+ * does with that surface — a popover's `max-w-xs p-3`, a menu's
10
+ * `min-w-48 flex flex-col`, a listbox's scroll clamp — is the panel's
11
+ * own and stays at the call site, composed after this with
12
+ * `toClassName`. Pulling the shared half out is what keeps a change
13
+ * to the overlay surface (a new elevation token, say) from having to
14
+ * be made in four places and kept in agreement by hand.
15
+ */
16
+ export const PANEL_SURFACE_CLASS = "rounded-md border border-border-default bg-surface-overlay shadow-medium";
17
+ //# sourceMappingURL=overlayPanelClass.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"overlayPanelClass.js","sourceRoot":"","sources":["../../src/Overlay/overlayPanelClass.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAC9B,0EAA0E,CAAA"}
@@ -0,0 +1,97 @@
1
+ import type { Placement } from "@floating-ui/react";
2
+ import type { ReactElement } from "react";
3
+ /**
4
+ * The floating-ui block every anchored overlay — `Popover`, `Menu`,
5
+ * `Listbox`, `Combobox` — wrote out for itself, consolidated once.
6
+ *
7
+ * `Popover` and `Menu` had the same twelve-line `useFloating` +
8
+ * `useInteractions` + `useClonedChild` preamble with two constants
9
+ * different (the offset and the role), and the fleet's four pickers
10
+ * hand-rolled a fifth, sixth, seventh and eighth `computePosition`
11
+ * that reimplemented `flip`/`shift` against `window.innerHeight`.
12
+ * This is the one implementation they all take.
13
+ *
14
+ * ### The owner's "configurable, but those are the basics"
15
+ *
16
+ * Escape and outside-press both close, **on by default**, for every
17
+ * anchored overlay — that is what an overlay is. A component switches
18
+ * one off through `dismiss` (a `Combobox`'s Escape clears the query
19
+ * before it closes; a menu opened from a context click wants no
20
+ * outside-press race with the thing that spawned it), but it opts out
21
+ * rather than opting in.
22
+ *
23
+ * ### `size`, for the pickers
24
+ *
25
+ * `matchTriggerWidth` and `maxHeightPx` are the two things
26
+ * `PortalDropdown` did with a hand-measured `TriggerRect` and a
27
+ * `window.innerHeight` clamp. The `size` middleware does both from the
28
+ * platform: the panel takes the trigger's width, and it never grows
29
+ * past the space actually left in the viewport.
30
+ *
31
+ * The computed width/height are written **straight onto the floating
32
+ * element's style** rather than lifted into React state, which is
33
+ * floating-ui's own documented shape and is deliberate twice over: a
34
+ * `setState` inside `apply` re-runs the position, which re-runs
35
+ * `apply`, which is a render loop; and because these properties never
36
+ * enter the `floatingStyles` object React manages, a component
37
+ * re-render (a keystroke in a combobox) does not wipe them — only an
38
+ * `autoUpdate` tick recomputes them.
39
+ *
40
+ * ### Portal-agnostic
41
+ *
42
+ * This returns the pieces — `clonedTrigger`, `getFloatingProps`,
43
+ * `setFloating`, `floatingStyles`, `context` — and renders nothing.
44
+ * Whether the panel goes in a `FloatingPortal`, the top layer, or in
45
+ * place is the component's call, which is what lets the same hook back
46
+ * a portalled `Listbox` and (for now) a top-layer `Popover`.
47
+ */
48
+ export type AnchoredOverlayRole = "dialog" | "listbox" | "menu";
49
+ export type UseAnchoredOverlayOptions = {
50
+ /**
51
+ * Both default **on** — Escape and outside-press are what makes an
52
+ * overlay an overlay. Switch one off for the honest exception (a
53
+ * combobox that swallows the first Escape to clear its query), never
54
+ * to make a plain overlay sticky. Named for the flattened
55
+ * floating-ui `useDismiss` options they carry.
56
+ */
57
+ isEscapeDismissable?: boolean;
58
+ isOutsidePressDismissable?: boolean;
59
+ /** Ports `PortalDropdown`'s anchor-width match, via `size`. */
60
+ isTriggerWidthMatched?: boolean;
61
+ isVisible: boolean;
62
+ /** Clamps the panel to `min(this, available viewport space)`. */
63
+ maxHeightPx?: number;
64
+ /** Popover 8, Menu/Listbox/Combobox 4. */
65
+ offsetValue?: number;
66
+ onDismiss: () => void;
67
+ placement?: Placement;
68
+ role: AnchoredOverlayRole;
69
+ /** The control the panel hangs off. **Cloned, not wrapped.** */
70
+ trigger: ReactElement;
71
+ };
72
+ export declare const useAnchoredOverlay: ({ isEscapeDismissable, isOutsidePressDismissable, isTriggerWidthMatched, isVisible, maxHeightPx, offsetValue, onDismiss, placement, role, trigger, }: UseAnchoredOverlayOptions) => {
73
+ clonedTrigger: ReactElement<Record<string, unknown>, string | import("react").JSXElementConstructor<any>>;
74
+ context: {
75
+ placement: Placement;
76
+ strategy: import("@floating-ui/utils").Strategy;
77
+ x: number;
78
+ y: number;
79
+ middlewareData: import("@floating-ui/core").MiddlewareData;
80
+ isPositioned: boolean;
81
+ update: () => void;
82
+ floatingStyles: React.CSSProperties;
83
+ open: boolean;
84
+ onOpenChange: (open: boolean, event?: Event, reason?: import("@floating-ui/react").OpenChangeReason) => void;
85
+ events: import("@floating-ui/react").FloatingEvents;
86
+ dataRef: React.MutableRefObject<import("@floating-ui/react").ContextData>;
87
+ nodeId: string | undefined;
88
+ floatingId: string | undefined;
89
+ refs: import("@floating-ui/react").ExtendedRefs<import("@floating-ui/react").ReferenceType>;
90
+ elements: import("@floating-ui/react").ExtendedElements<import("@floating-ui/react").ReferenceType>;
91
+ };
92
+ floatingStyles: import("react").CSSProperties;
93
+ getFloatingProps: (userProps?: React.HTMLProps<HTMLElement>) => Record<string, unknown>;
94
+ setFloating: ((node: HTMLElement | null) => void) & ((node: HTMLElement | null) => void);
95
+ triggerId: string;
96
+ };
97
+ //# sourceMappingURL=useAnchoredOverlay.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useAnchoredOverlay.d.ts","sourceRoot":"","sources":["../../src/Overlay/useAnchoredOverlay.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AAYnD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,OAAO,CAAA;AAEzC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AACH,MAAM,MAAM,mBAAmB,GAC3B,QAAQ,GACR,SAAS,GACT,MAAM,CAAA;AAEV,MAAM,MAAM,yBAAyB,GAAG;IACtC;;;;;;OAMG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAA;IAC7B,yBAAyB,CAAC,EAAE,OAAO,CAAA;IACnC,+DAA+D;IAC/D,qBAAqB,CAAC,EAAE,OAAO,CAAA;IAC/B,SAAS,EAAE,OAAO,CAAA;IAClB,iEAAiE;IACjE,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,0CAA0C;IAC1C,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,SAAS,EAAE,MAAM,IAAI,CAAA;IACrB,SAAS,CAAC,EAAE,SAAS,CAAA;IACrB,IAAI,EAAE,mBAAmB,CAAA;IACzB,gEAAgE;IAChE,OAAO,EAAE,YAAY,CAAA;CACtB,CAAA;AAED,eAAO,MAAM,kBAAkB,GAAI,sJAWhC,yBAAyB;;;;;;;;;;;;;;;;;;;;;;;;CA6F3B,CAAA"}
@@ -0,0 +1,81 @@
1
+ import { useClonedChild, useUniqueId, } from "@charcuterie/logic";
2
+ import { autoUpdate, flip, offset, shift, size, useDismiss, useFloating, useInteractions, useRole, } from "@floating-ui/react";
3
+ export const useAnchoredOverlay = ({ isEscapeDismissable = true, isOutsidePressDismissable = true, isTriggerWidthMatched = false, isVisible, maxHeightPx, offsetValue = 8, onDismiss, placement = "bottom-start", role, trigger, }) => {
4
+ const middleware = [
5
+ offset(offsetValue),
6
+ flip(),
7
+ shift({ padding: 8 }),
8
+ ];
9
+ if (isTriggerWidthMatched || maxHeightPx !== undefined) {
10
+ middleware.push(size({
11
+ padding: 8,
12
+ apply({ availableHeight, elements, rects }) {
13
+ const cap = maxHeightPx === undefined
14
+ ? availableHeight
15
+ : Math.min(maxHeightPx, availableHeight);
16
+ elements.floating.style.maxHeight = `${cap}px`;
17
+ if (isTriggerWidthMatched) {
18
+ elements.floating.style.width = `${rects.reference.width}px`;
19
+ }
20
+ },
21
+ }));
22
+ }
23
+ const { context, floatingStyles, refs } = useFloating({
24
+ // Read-only. Charcuterie remains the sole owner; floating-ui is
25
+ // told what is true and never decides it.
26
+ open: isVisible,
27
+ onOpenChange: (isNextVisible) => {
28
+ if (!isNextVisible) {
29
+ onDismiss();
30
+ }
31
+ },
32
+ placement,
33
+ middleware,
34
+ strategy: "fixed",
35
+ whileElementsMounted: autoUpdate,
36
+ });
37
+ // `useRole` writes `aria-expanded`/`aria-haspopup`/`aria-controls`
38
+ // on the trigger and `id` + `role` on the panel from one source —
39
+ // which is what keeps `aria-controls` pointing at the panel's real
40
+ // id across the portal boundary, the objection the top-layer
41
+ // decision raised and this answers.
42
+ const { getFloatingProps, getReferenceProps } = useInteractions([
43
+ useDismiss(context, {
44
+ escapeKey: isEscapeDismissable,
45
+ outsidePress: isOutsidePressDismissable,
46
+ }),
47
+ useRole(context, { role }),
48
+ ]);
49
+ // `useRole` for a `listbox` overwrites the trigger's own role with
50
+ // `combobox` — the select-only-combobox pattern, which then demands
51
+ // its own `aria-label`. The fleet's pattern is a plain button that
52
+ // *opens* a listbox, so the injected `role` is dropped and the
53
+ // trigger keeps its native semantics while still gaining
54
+ // `aria-haspopup="listbox"`/`aria-expanded`/`aria-controls`. `useRole`
55
+ // injects a reference `role` only for listbox/combobox, so this is a
56
+ // no-op for `Popover` (dialog) and `Menu` (menu).
57
+ const { role: _discardedTriggerRole, ...referenceProps } = getReferenceProps();
58
+ // A stable id on the trigger, so a panel that needs a name can point
59
+ // `aria-labelledby` at it across the portal. `useRole` already gives
60
+ // a `menu`'s reference an id (and labels the panel with it); this
61
+ // reuses that when present and supplies one otherwise — which is what
62
+ // a `listbox` panel needs, since a bare `role="listbox"` is an ARIA
63
+ // input field and must be named.
64
+ const generatedTriggerId = useUniqueId();
65
+ const triggerId = referenceProps.id ??
66
+ generatedTriggerId;
67
+ const clonedTrigger = useClonedChild(trigger, {
68
+ ...referenceProps,
69
+ id: triggerId,
70
+ ref: refs.setReference,
71
+ });
72
+ return {
73
+ clonedTrigger,
74
+ context,
75
+ floatingStyles,
76
+ getFloatingProps,
77
+ setFloating: refs.setFloating,
78
+ triggerId,
79
+ };
80
+ };
81
+ //# sourceMappingURL=useAnchoredOverlay.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useAnchoredOverlay.js","sourceRoot":"","sources":["../../src/Overlay/useAnchoredOverlay.ts"],"names":[],"mappings":"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"}
@@ -35,32 +35,24 @@ export type PopoverProps = {
35
35
  *
36
36
  * What floating-ui is used for is what only it can do: collision
37
37
  * handling (`flip`, `shift`), a real dismiss layer, and focus
38
- * management. Positioning strategy, the top layer, and the ARIA
39
- * wiring come from the platform and from `useRole`.
38
+ * management — all of it consolidated in `useAnchoredOverlay`.
39
+ * Positioning strategy and the ARIA wiring come from the platform and
40
+ * from `useRole`.
40
41
  *
41
- * ### The top layer, from the platform — not a portal
42
+ * ### Portalled, not the top layer
42
43
  *
43
- * `popover="manual"` puts the panel in the top layer, which is
44
- * what a portal is normally reached for: no `z-index` contest, and
45
- * no `overflow: hidden` ancestor clipping it. Unlike a portal it
46
- * **stays where it is in the DOM**, so `canvas.getByRole("dialog")`
47
- * still finds it and a story keeps its scoping. Same argument as
48
- * `Modal`, one layer down.
49
- *
50
- * `manual`, not `auto`, and that is the state-ownership decision
51
- * again: `auto` brings the UA's own light-dismiss, which closes the
52
- * element by itself and leaves `isVisible` true. `useDismiss` does
53
- * the same job through our callback, so Charcuterie stays the only
54
- * thing that decides.
55
- *
56
- * ### Two UA defaults that have to be undone
57
- *
58
- * `[popover]` ships `position: fixed; inset: 0; margin: auto`,
59
- * which centres the panel in the viewport and fights every
60
- * coordinate floating-ui computes — hence `inset-auto m-0`, and
61
- * `strategy: "fixed"` so the numbers are viewport-relative like the
62
- * top layer itself. Getting either wrong reads as a positioning
63
- * bug rather than as a UA default.
44
+ * The panel goes in a `FloatingPortal` to `document.body`. The top
45
+ * layer (`popover="manual"`) was reached for first — it needs no
46
+ * portal and no `z-index` — but it does not escape an
47
+ * `overflow: hidden` ancestor's *clipping* in every case the fleet
48
+ * hit, and a portal does. The objection a portal used to raise, that
49
+ * it moves the panel out of a scoped `canvas` query, is answered two
50
+ * ways: `useRole` writes `aria-controls` on the trigger pointing at
51
+ * the panel `id`, so trigger→panel stays followable across the
52
+ * boundary; and the story scopes panel queries to
53
+ * `within(document.body)` while the structural `expectAgentDrivable`
54
+ * needs no change. `strategy: "fixed"` keeps the coordinates
55
+ * viewport-relative.
64
56
  */
65
57
  export declare const Popover: ({ children, className, heading, isVisible, onDismiss, placement, trigger, }: PopoverProps) => ReactNode;
66
58
  //# sourceMappingURL=Popover.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Popover.d.ts","sourceRoot":"","sources":["../../src/Popover/Popover.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AAYnD,OAAO,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAIpD,MAAM,MAAM,YAAY,GAAG;IACzB,QAAQ,EAAE,SAAS,CAAA;IACnB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;;;;;OAMG;IACH,OAAO,EAAE,MAAM,CAAA;IACf,SAAS,EAAE,OAAO,CAAA;IAClB;;;OAGG;IACH,SAAS,EAAE,MAAM,IAAI,CAAA;IACrB,SAAS,CAAC,EAAE,SAAS,CAAA;IACrB;;;;OAIG;IACH,OAAO,EAAE,YAAY,CAAA;CACtB,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,eAAO,MAAM,OAAO,GAAI,6EAQrB,YAAY,KAAG,SA2EjB,CAAA"}
1
+ {"version":3,"file":"Popover.d.ts","sourceRoot":"","sources":["../../src/Popover/Popover.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AAKnD,OAAO,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAMpD,MAAM,MAAM,YAAY,GAAG;IACzB,QAAQ,EAAE,SAAS,CAAA;IACnB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;;;;;OAMG;IACH,OAAO,EAAE,MAAM,CAAA;IACf,SAAS,EAAE,OAAO,CAAA;IAClB;;;OAGG;IACH,SAAS,EAAE,MAAM,IAAI,CAAA;IACrB,SAAS,CAAC,EAAE,SAAS,CAAA;IACrB;;;;OAIG;IACH,OAAO,EAAE,YAAY,CAAA;CACtB,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,eAAO,MAAM,OAAO,GAAI,6EAQrB,YAAY,KAAG,SAkDjB,CAAA"}
@@ -1,6 +1,7 @@
1
1
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useClonedChild } from "@charcuterie/logic";
3
- import { autoUpdate, FloatingFocusManager, flip, offset, shift, useDismiss, useFloating, useInteractions, useRole, } from "@floating-ui/react";
2
+ import { FloatingFocusManager, FloatingPortal, } from "@floating-ui/react";
3
+ import { PANEL_SURFACE_CLASS } from "../Overlay/overlayPanelClass.js";
4
+ import { useAnchoredOverlay } from "../Overlay/useAnchoredOverlay.js";
4
5
  import { toClassName } from "../toClassName.js";
5
6
  /**
6
7
  * Replaces mux-magic's `@radix-ui/react-popover` re-export, and it
@@ -12,77 +13,40 @@ import { toClassName } from "../toClassName.js";
12
13
  *
13
14
  * What floating-ui is used for is what only it can do: collision
14
15
  * handling (`flip`, `shift`), a real dismiss layer, and focus
15
- * management. Positioning strategy, the top layer, and the ARIA
16
- * wiring come from the platform and from `useRole`.
16
+ * management — all of it consolidated in `useAnchoredOverlay`.
17
+ * Positioning strategy and the ARIA wiring come from the platform and
18
+ * from `useRole`.
17
19
  *
18
- * ### The top layer, from the platform — not a portal
20
+ * ### Portalled, not the top layer
19
21
  *
20
- * `popover="manual"` puts the panel in the top layer, which is
21
- * what a portal is normally reached for: no `z-index` contest, and
22
- * no `overflow: hidden` ancestor clipping it. Unlike a portal it
23
- * **stays where it is in the DOM**, so `canvas.getByRole("dialog")`
24
- * still finds it and a story keeps its scoping. Same argument as
25
- * `Modal`, one layer down.
26
- *
27
- * `manual`, not `auto`, and that is the state-ownership decision
28
- * again: `auto` brings the UA's own light-dismiss, which closes the
29
- * element by itself and leaves `isVisible` true. `useDismiss` does
30
- * the same job through our callback, so Charcuterie stays the only
31
- * thing that decides.
32
- *
33
- * ### Two UA defaults that have to be undone
34
- *
35
- * `[popover]` ships `position: fixed; inset: 0; margin: auto`,
36
- * which centres the panel in the viewport and fights every
37
- * coordinate floating-ui computes — hence `inset-auto m-0`, and
38
- * `strategy: "fixed"` so the numbers are viewport-relative like the
39
- * top layer itself. Getting either wrong reads as a positioning
40
- * bug rather than as a UA default.
22
+ * The panel goes in a `FloatingPortal` to `document.body`. The top
23
+ * layer (`popover="manual"`) was reached for first — it needs no
24
+ * portal and no `z-index` — but it does not escape an
25
+ * `overflow: hidden` ancestor's *clipping* in every case the fleet
26
+ * hit, and a portal does. The objection a portal used to raise, that
27
+ * it moves the panel out of a scoped `canvas` query, is answered two
28
+ * ways: `useRole` writes `aria-controls` on the trigger pointing at
29
+ * the panel `id`, so trigger→panel stays followable across the
30
+ * boundary; and the story scopes panel queries to
31
+ * `within(document.body)` while the structural `expectAgentDrivable`
32
+ * needs no change. `strategy: "fixed"` keeps the coordinates
33
+ * viewport-relative.
41
34
  */
42
35
  export const Popover = ({ children, className, heading, isVisible, onDismiss, placement = "bottom-start", trigger, }) => {
43
- const { context, floatingStyles, refs } = useFloating({
44
- // Read-only. Charcuterie remains the sole owner; floating-ui
45
- // is told what is true and never decides it.
46
- open: isVisible,
47
- onOpenChange: (isNextVisible) => {
48
- if (!isNextVisible) {
49
- onDismiss();
50
- }
51
- },
36
+ const { clonedTrigger, context, floatingStyles, getFloatingProps, setFloating, } = useAnchoredOverlay({
37
+ isVisible,
38
+ offsetValue: 8,
39
+ onDismiss,
52
40
  placement,
53
- middleware: [offset(8), flip(), shift({ padding: 8 })],
54
- strategy: "fixed",
55
- whileElementsMounted: autoUpdate,
56
- });
57
- // `useRole` writes `aria-expanded`, `aria-haspopup`, and
58
- // `aria-controls` on the trigger and `id` + `role="dialog"` on
59
- // the panel, from one source. Hand-writing them alongside is how
60
- // an `aria-controls` ends up pointing at an id that moved.
61
- const { getFloatingProps, getReferenceProps } = useInteractions([
62
- useDismiss(context),
63
- useRole(context, { role: "dialog" }),
64
- ]);
65
- const clonedTrigger = useClonedChild(trigger, {
66
- ...getReferenceProps(),
67
- ref: refs.setReference,
41
+ role: "dialog",
42
+ trigger,
68
43
  });
69
- return (_jsxs(_Fragment, { children: [clonedTrigger, isVisible ? (_jsx(FloatingFocusManager, { context: context, modal: false, children: _jsx("div", { ...getFloatingProps(), "aria-label": heading, className: toClassName("inset-auto m-0 max-w-xs rounded-md border border-border-default bg-surface-overlay p-3 text-content-primary text-sm shadow-medium", className), popover: "manual",
70
- // Duplicated from `getFloatingProps()` on purpose, and
71
- // the two cannot differ — `useRole(context, { role:
72
- // "dialog" })` above is where it comes from. Stated
73
- // here because a linter cannot see a role through a
74
- // spread, and neither can the next reader.
75
- role: "dialog", ref: (node) => {
76
- refs.setFloating(node);
77
- // In a ref callback rather than an effect, because a
78
- // `[popover]` is `display: none` until it is shown —
79
- // and `FloatingFocusManager` is a *child*, so its
80
- // effect runs first and would be focusing into a
81
- // hidden element. Refs are set during commit, before
82
- // any of that.
83
- if (node && !node.matches(":popover-open")) {
84
- node.showPopover();
85
- }
86
- }, style: floatingStyles, children: children }) })) : null] }));
44
+ return (_jsxs(_Fragment, { children: [clonedTrigger, isVisible ? (_jsx(FloatingPortal, { children: _jsx(FloatingFocusManager, { context: context, modal: false, children: _jsx("div", { ...getFloatingProps(), "aria-label": heading, className: toClassName(PANEL_SURFACE_CLASS, "z-[var(--layer-modal)] max-w-xs p-3 text-content-primary text-sm", className), ref: setFloating,
45
+ // Duplicated from `getFloatingProps()` on purpose, and
46
+ // the two cannot differ — `useRole(context, { role:
47
+ // "dialog" })` inside the hook is where it comes from.
48
+ // Stated here because a linter cannot see a role through
49
+ // a spread, and neither can the next reader.
50
+ role: "dialog", style: floatingStyles, children: children }) }) })) : null] }));
87
51
  };
88
52
  //# sourceMappingURL=Popover.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"Popover.js","sourceRoot":"","sources":["../../src/Popover/Popover.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAA;AAEnD,OAAO,EACL,UAAU,EACV,oBAAoB,EACpB,IAAI,EACJ,MAAM,EACN,KAAK,EACL,UAAU,EACV,WAAW,EACX,eAAe,EACf,OAAO,GACR,MAAM,oBAAoB,CAAA;AAG3B,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AA4B/C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,MAAM,CAAC,MAAM,OAAO,GAAG,CAAC,EACtB,QAAQ,EACR,SAAS,EACT,OAAO,EACP,SAAS,EACT,SAAS,EACT,SAAS,GAAG,cAAc,EAC1B,OAAO,GACM,EAAa,EAAE;IAC5B,MAAM,EAAE,OAAO,EAAE,cAAc,EAAE,IAAI,EAAE,GAAG,WAAW,CAAC;QACpD,6DAA6D;QAC7D,6CAA6C;QAC7C,IAAI,EAAE,SAAS;QACf,YAAY,EAAE,CAAC,aAAa,EAAE,EAAE;YAC9B,IAAI,CAAC,aAAa,EAAE,CAAC;gBACnB,SAAS,EAAE,CAAA;YACb,CAAC;QACH,CAAC;QACD,SAAS;QACT,UAAU,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,KAAK,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC,CAAC;QACtD,QAAQ,EAAE,OAAO;QACjB,oBAAoB,EAAE,UAAU;KACjC,CAAC,CAAA;IAEF,yDAAyD;IACzD,+DAA+D;IAC/D,iEAAiE;IACjE,2DAA2D;IAC3D,MAAM,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,GAC3C,eAAe,CAAC;QACd,UAAU,CAAC,OAAO,CAAC;QACnB,OAAO,CAAC,OAAO,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;KACrC,CAAC,CAAA;IAEJ,MAAM,aAAa,GAAG,cAAc,CAAC,OAAO,EAAE;QAC5C,GAAG,iBAAiB,EAAE;QACtB,GAAG,EAAE,IAAI,CAAC,YAAY;KACvB,CAAC,CAAA;IAEF,OAAO,CACL,8BACG,aAAa,EAEb,SAAS,CAAC,CAAC,CAAC,CACX,KAAC,oBAAoB,IACnB,OAAO,EAAE,OAAO,EAChB,KAAK,EAAE,KAAK,YAEZ,iBACM,gBAAgB,EAAE,gBACV,OAAO,EACnB,SAAS,EAAE,WAAW,CACpB,mIAAmI,EACnI,SAAS,CACV,EACD,OAAO,EAAC,QAAQ;oBAChB,uDAAuD;oBACvD,oDAAoD;oBACpD,oDAAoD;oBACpD,oDAAoD;oBACpD,2CAA2C;oBAC3C,IAAI,EAAC,QAAQ,EACb,GAAG,EAAE,CAAC,IAAI,EAAE,EAAE;wBACZ,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,CAAA;wBAEtB,qDAAqD;wBACrD,qDAAqD;wBACrD,kDAAkD;wBAClD,iDAAiD;wBACjD,qDAAqD;wBACrD,eAAe;wBACf,IAAI,IAAI,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,eAAe,CAAC,EAAE,CAAC;4BAC3C,IAAI,CAAC,WAAW,EAAE,CAAA;wBACpB,CAAC;oBACH,CAAC,EACD,KAAK,EAAE,cAAc,YAEpB,QAAQ,GACL,GACe,CACxB,CAAC,CAAC,CAAC,IAAI,IACP,CACJ,CAAA;AACH,CAAC,CAAA"}
1
+ {"version":3,"file":"Popover.js","sourceRoot":"","sources":["../../src/Popover/Popover.tsx"],"names":[],"mappings":";AACA,OAAO,EACL,oBAAoB,EACpB,cAAc,GACf,MAAM,oBAAoB,CAAA;AAG3B,OAAO,EAAE,mBAAmB,EAAE,MAAM,iCAAiC,CAAA;AACrE,OAAO,EAAE,kBAAkB,EAAE,MAAM,kCAAkC,CAAA;AACrE,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AA4B/C;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,MAAM,CAAC,MAAM,OAAO,GAAG,CAAC,EACtB,QAAQ,EACR,SAAS,EACT,OAAO,EACP,SAAS,EACT,SAAS,EACT,SAAS,GAAG,cAAc,EAC1B,OAAO,GACM,EAAa,EAAE;IAC5B,MAAM,EACJ,aAAa,EACb,OAAO,EACP,cAAc,EACd,gBAAgB,EAChB,WAAW,GACZ,GAAG,kBAAkB,CAAC;QACrB,SAAS;QACT,WAAW,EAAE,CAAC;QACd,SAAS;QACT,SAAS;QACT,IAAI,EAAE,QAAQ;QACd,OAAO;KACR,CAAC,CAAA;IAEF,OAAO,CACL,8BACG,aAAa,EAEb,SAAS,CAAC,CAAC,CAAC,CACX,KAAC,cAAc,cACb,KAAC,oBAAoB,IACnB,OAAO,EAAE,OAAO,EAChB,KAAK,EAAE,KAAK,YAEZ,iBACM,gBAAgB,EAAE,gBACV,OAAO,EACnB,SAAS,EAAE,WAAW,CACpB,mBAAmB,EACnB,kEAAkE,EAClE,SAAS,CACV,EACD,GAAG,EAAE,WAAW;wBAChB,uDAAuD;wBACvD,oDAAoD;wBACpD,uDAAuD;wBACvD,yDAAyD;wBACzD,6CAA6C;wBAC7C,IAAI,EAAC,QAAQ,EACb,KAAK,EAAE,cAAc,YAEpB,QAAQ,GACL,GACe,GACR,CAClB,CAAC,CAAC,CAAC,IAAI,IACP,CACJ,CAAA;AACH,CAAC,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"Tooltip.d.ts","sourceRoot":"","sources":["../../src/Tooltip/Tooltip.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AAanD,OAAO,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAEpD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAIhD,MAAM,MAAM,YAAY,GAAG,SAAS,GAAG;IACrC;;;;;;OAMG;IACH,QAAQ,EAAE,YAAY,CAAA;IACtB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,6CAA6C;IAC7C,KAAK,CAAC,EAAE,MAAM,CAAA;IACd;;;;;OAKG;IACH,KAAK,EAAE,MAAM,CAAA;IACb,SAAS,CAAC,EAAE,SAAS,CAAA;CACtB,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuDG;AACH,eAAO,MAAM,OAAO,GAAI,wEAOrB,YAAY,KAAG,SAoFjB,CAAA"}
1
+ {"version":3,"file":"Tooltip.d.ts","sourceRoot":"","sources":["../../src/Tooltip/Tooltip.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AAcnD,OAAO,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAEpD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAIhD,MAAM,MAAM,YAAY,GAAG,SAAS,GAAG;IACrC;;;;;;OAMG;IACH,QAAQ,EAAE,YAAY,CAAA;IACtB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,6CAA6C;IAC7C,KAAK,CAAC,EAAE,MAAM,CAAA;IACd;;;;;OAKG;IACH,KAAK,EAAE,MAAM,CAAA;IACb,SAAS,CAAC,EAAE,SAAS,CAAA;CACtB,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuDG;AACH,eAAO,MAAM,OAAO,GAAI,wEAOrB,YAAY,KAAG,SAsFjB,CAAA"}
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useClonedChild, useVisibility, } from "@charcuterie/logic";
3
- import { autoUpdate, flip, offset, shift, useDismiss, useFloating, useFocus, useHover, useInteractions, useRole, } from "@floating-ui/react";
3
+ import { autoUpdate, FloatingPortal, flip, offset, shift, useDismiss, useFloating, useFocus, useHover, useInteractions, useRole, } from "@floating-ui/react";
4
4
  import { mergeSlotProps } from "../slotProps.js";
5
5
  import { toClassName } from "../toClassName.js";
6
6
  /**
@@ -96,29 +96,29 @@ export const Tooltip = ({ children, className, delay = 200, label, placement = "
96
96
  ...getReferenceProps(),
97
97
  ref: refs.setReference,
98
98
  }));
99
- return (_jsxs(_Fragment, { children: [clonedTrigger, isVisible ? (_jsx("div", { ...getFloatingProps(), className: toClassName(
100
- // `intent-neutral-solid` + its own `on-solid`, rather
101
- // than `surface-inverse` + `content-on-accent`. The
102
- // second pair looks like the obvious one and is not a
103
- // *pair* at all — nothing in the token set promises those
104
- // two contrast, and in `daylight` dark they land at
105
- // 2.9:1. The `solid`/`on-solid` roles exist precisely to
106
- // be a guaranteed couple, and the contrast gate checks
107
- // them as one.
108
- "inset-auto m-0 max-w-xs rounded-md bg-intent-neutral-solid px-2 py-1 text-intent-neutral-on-solid text-xs shadow-medium", className),
109
- // No `popover="manual"` and no `FloatingFocusManager`,
110
- // unlike `Popover`. Both exist to manage focus, and focus
111
- // never enters a tooltip — moving it here is what makes
112
- // one unescapable. The trade is that a tooltip is not in
113
- // the top layer, so it can be clipped by an
114
- // `overflow: hidden` ancestor; `strategy: "fixed"` keeps
115
- // that to ancestors that actually clip fixed descendants.
116
- ref: refs.setFloating,
117
- // Duplicated from `getFloatingProps()` deliberately, and
118
- // the two cannot differ — `useRole(context, { role:
119
- // "tooltip" })` above is where it comes from. Stated here
120
- // because a linter cannot see a role through a spread, and
121
- // neither can the next reader.
122
- role: "tooltip", style: floatingStyles, children: label })) : null] }));
99
+ return (_jsxs(_Fragment, { children: [clonedTrigger, isVisible ? (_jsx(FloatingPortal, { children: _jsx("div", { ...getFloatingProps(), className: toClassName(
100
+ // `intent-neutral-solid` + its own `on-solid`, rather
101
+ // than `surface-inverse` + `content-on-accent`. The
102
+ // second pair looks like the obvious one and is not a
103
+ // *pair* at all — nothing in the token set promises those
104
+ // two contrast, and in `daylight` dark they land at
105
+ // 2.9:1. The `solid`/`on-solid` roles exist precisely to
106
+ // be a guaranteed couple, and the contrast gate checks
107
+ // them as one.
108
+ "z-[var(--layer-tooltip)] max-w-xs rounded-md bg-intent-neutral-solid px-2 py-1 text-intent-neutral-on-solid text-xs shadow-medium", className),
109
+ // Portalled to `document.body`, the same reversal as the
110
+ // other overlays: `strategy: "fixed"` alone still let an
111
+ // `overflow: hidden` ancestor clip the tip, and a portal
112
+ // does not. No `FloatingFocusManager`, unlike `Popover` —
113
+ // focus never enters a tooltip, and moving it here is what
114
+ // would make one unescapable. `--layer-tooltip` keeps it
115
+ // above a modal.
116
+ ref: refs.setFloating,
117
+ // Duplicated from `getFloatingProps()` deliberately, and
118
+ // the two cannot differ — `useRole(context, { role:
119
+ // "tooltip" })` above is where it comes from. Stated here
120
+ // because a linter cannot see a role through a spread, and
121
+ // neither can the next reader.
122
+ role: "tooltip", style: floatingStyles, children: label }) })) : null] }));
123
123
  };
124
124
  //# sourceMappingURL=Tooltip.js.map