@stackone/malachite 2.38.0 → 2.39.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/COMPONENTS.md CHANGED
@@ -58,7 +58,7 @@ The catalog of components exported from `@stackone/malachite`. **Reach for one o
58
58
  - **`PreviewTreeRow`** — A single labelled row within a PreviewTree. _Key props:_ `label`.
59
59
  - **`Tabs`** — Tabbed navigation switching between content panels. `trailing` puts content on the same row, settled against its end — a count, a scope picker, the state the tabs are showing — inside the rule this component already draws. Without it a consumer had to redraw that rule to carry it past the tabs, and the tab list, which cannot wrap, would not yield any width to what sat beside it. The tabs hold their width and the trailing content gives ground; the `tablist` role stays on the tabs alone, so the extra content is not announced as one. _Key props:_ `TabItem[]`, `activeKey`, `onChange`, `trailing`, `className`, `style`.
60
60
  - **`Breadcrumbs`** — Breadcrumb trail with caret separators, an optional back button (shown only when there is more than one crumb AND the parent — the second-to-last — carries a `url` to target) and a right-aligned `actions` row. A lone crumb marked `isRoot` switches to title mode: plain emphasised text, no trail chrome, but the `actions` row still renders. Renders as a `nav`/`ol` with `aria-current="page"` on the last crumb. Pass `renderLink` to route crumbs through the app's own router instead of a plain `<a>`. Used by `PageLayout`, but standalone too. _Key props:_ `links[]` (`name`, `url`, `isRoot`), `actions[]`, `renderLink`, `className`, `style`.
61
- - **`ActionMenu`** — Dropdown menu of actions (kebab/overflow style). The trigger is an icon-only `Button` by default, so `size` draws exactly the square a `Button` of that size draws beside it: `xsmall` is Figma's 20px "Button Element - Square (X-Small)", `small` the 28px rung above it. Both squares are fixed rather than read off the `controlHeights` tokens, so re-theming those does not resize the trigger. A supplied `triggerIcon` is held to the tier's glyph size — 11px at `xsmall`, 16px at `small`. At 20px the `xsmall` trigger meets WCAG 2.5.8 through the *Spacing* exception rather than the 24px minimum, so keep at least 4px between adjacent triggers. `showLabel` draws `label` beside the icon instead of leaving it to the accessible name — for a menu that is the point of its corner rather than an overflow for a row that already says what it acts on — and `variant` puts the trigger on any `Button` variant, so a menu handing work to the assistant can wear the assistant ground rather than the overflow-kebab outline. An item's `icon` leads its label: every caller's names the action itself — copy, open, delete — and an icon that is part of an item's identity belongs where the eye starts rather than right-aligned, where it reads as a status column. The menu is Figma's `Dropdown Component - Options List`: a fixed sized to its widest item between a 180px floor and the 280px Figma draws, by default under the trigger's right edge, 14px below it, flipping above or clamping sideways when the viewport has no room, rounding at the library's 12px and padding wider (8px) than the menu a `Dropdown` opens. Items read in the primary text colour, or the secondary one with `tone="muted"` (e.g. a sidebar's menus); destructive ones in red. A ceiling rather than a flat width: taken literally it gave a menu of four short verbs 130px of air and made every kebab in an app the same width whatever it held. `minWidth` only raises it above that ceiling, so it is the one way to go past 280px, and a label too long for the box truncates rather than wrapping. _Key props:_ `ActionMenuItem[]`, `size`, `label`, `triggerIcon`, `minWidth`, `showLabel`, `variant`, `tone` (`default` | `muted`), `trigger` (your own control in place of the kebab, cloned with the menu's ref and aria state), `placement` (`bottom-end` default | `bottom-start` | `right-start` | `top-start` | `top-center`), `offset` (`small` 8px | `medium` 12px | `large` 14px; applies to every placement; without it the default `bottom-end` keeps the options menu's 14px and the others use 8px). An item with `type: 'text'` is context rather than an action (an account's email above its actions): secondary text the arrow keys skip. Opened from the keyboard, focus lands on the first item; Up/Down/Home/End move through enabled items, Tab closes the menu and moves on as usual; Escape or a picked item returns focus to the trigger.
61
+ - **`ActionMenu`** — Dropdown menu of actions (kebab/overflow style). The trigger is an icon-only `Button` by default, so `size` draws exactly the square a `Button` of that size draws beside it: `xsmall` is Figma's 20px "Button Element - Square (X-Small)", `small` the 28px rung above it. Both squares are fixed rather than read off the `controlHeights` tokens, so re-theming those does not resize the trigger. A supplied `triggerIcon` is held to the tier's glyph size — 11px at `xsmall`, 16px at `small`. At 20px the `xsmall` trigger meets WCAG 2.5.8 through the *Spacing* exception rather than the 24px minimum, so keep at least 4px between adjacent triggers. `showLabel` draws `label` beside the icon instead of leaving it to the accessible name — for a menu that is the point of its corner rather than an overflow for a row that already says what it acts on — and `variant` puts the trigger on any `Button` variant, so a menu handing work to the assistant can wear the assistant ground rather than the overflow-kebab outline. An item's `icon` leads its label: every caller's names the action itself — copy, open, delete — and an icon that is part of an item's identity belongs where the eye starts rather than right-aligned, where it reads as a status column. The menu is Figma's `Dropdown Component - Options List`: a fixed sized to its widest item between a 180px floor and the 280px Figma draws, by default under the trigger's right edge, 14px below it, flipping above or clamping sideways when the viewport has no room, rounding at the library's 12px and padding wider (8px) than the menu a `Dropdown` opens. Items read in the primary text colour, or the secondary one with `tone="muted"` (e.g. a sidebar's menus); destructive ones in red. A ceiling rather than a flat width: taken literally it gave a menu of four short verbs 130px of air and made every kebab in an app the same width whatever it held. `minWidth` only raises it above that ceiling, so it is the one way to go past 280px, and a label too long for the box truncates rather than wrapping. _Key props:_ `ActionMenuItem[]`, `size`, `label`, `triggerIcon`, `minWidth`, `showLabel`, `variant`, `tone` (`default` | `muted`), `trigger` (your own control in place of the kebab, cloned with the menu's ref and aria state), `placement` (`bottom-end` default | `bottom-start` | `right-start` | `top-start` | `top-center`), `offset` (`small` 8px | `medium` 12px | `large` 14px; applies to every placement; without it the default `bottom-end` keeps the options menu's 14px and the others use 8px). An item's `tag` (e.g. an "Early access" `Tag`) follows its label in a lazily loaded shadow host of its own, since the menu renders into `document.body`, and joins its accessible name; `current: true` marks the item for the page the user is on (`aria-current="page"` and the selected row paint; a `danger` item keeps its red text). An item with `type: 'text'` is context rather than an action (an account's email above its actions): secondary text the arrow keys skip. Opened from the keyboard, focus lands on the first item; Up/Down/Home/End move through enabled items, Tab closes the menu and moves on as usual; Escape or a picked item returns focus to the trigger.
62
62
  - **`NavItem`** — A row in an app's side navigation. Idle rows read secondary and take the list hover; the `active` row wears the accent tint and sets `aria-current="page"` on a link. `collapsed` shrinks it to a 36px icon tile whose label becomes the accessible name and a right-hand tooltip, with any `badge` pinned to the tile's corner. `variant="subitem"` indents a child row behind a dot instead of an icon; `disclosure` makes a group header button with `aria-expanded` and a rotating chevron, and `hasActiveChild` colours its text while a child is current. Renders an `<a>` with `href`, the element passed with `asChild` (a router `Link`), or a `<button>`, and forwards its ref and extra attributes so it can be a `Popover` or `ActionMenu` trigger. _Key props:_ `label`, `icon`, `active`, `hasActiveChild`, `collapsed`, `variant` (`item` | `subitem`), `danger`, `trailing`, `badge`, `disclosure` (`expanded` | `collapsed`), `tooltip`, `accessibleLabel` (overrides the accessible name; collapsed it defaults to the tooltip text plus `badgeLabel`), `href`, `target`, `rel`, `onClick`, `asChild` + `children`, `className`, `size` (`medium` | `small`, the tighter row for lists inside a popover), `tag` (beside the label, e.g. early access), `badgeLabel` (read in place of the badge and added to the collapsed name). A passed `aria-expanded` (e.g. from a `Popover`) wins over `disclosure`, and a button row forwards a passed `aria-current`.
63
63
  - **`Sidebar`** — An app's left navigation shell, pinned to the viewport's left edge: 260px wide, 60px `collapsed`, on the sunken page background. Composed from `Sidebar.Header` (a 56px band for `Sidebar.Switcher`, the logo + workspace + parent trigger), `Sidebar.Body` (the part that scrolls), `Sidebar.Notices`, `Sidebar.Search`, `Sidebar.Section` (rows of `NavItem`s at the gutter), `Sidebar.Group` + `Sidebar.GroupItems` (a disclosure header and its sub-items), `Sidebar.Divider`, `Sidebar.Usage` (a `UsageCard` placed at the sidebar's gutter) and `Sidebar.Footer` holding `Sidebar.Account`. Popovers opened from it (with `padding="none"`) wrap their body in `Sidebar.Panel`, sized so the popover comes to `SIDEBAR_POPOVER_WIDTH_PX`, and build it from the `Popover` parts. `onToggleCollapse` adds the edge toggle, shown in CSS while the pointer is over the sidebar or a keyboard focus is inside it. Notices, the divider and the usage card hide while collapsed. _Key props:_ `collapsed`, `onToggleCollapse`, `expandLabel`, `collapseLabel`, `aria-label`; `Sidebar.Switcher`: `logo`, `title`, `subtitle` + button attributes; `Sidebar.Account`: `avatar` or `initials` (a small chip when there is no photo), `name` + button attributes; `Sidebar.Usage`: the `UsageCard` props; `Sidebar.Notices`: `aria-label` (required). `useSidebarCollapsed()` reads the state from inside; `SIDEBAR_WIDTH_PX` gives the page its offset beside the sidebar.
64
64
  - **`SearchTrigger`** — A button drawn as a filled search field, for opening a command palette or search dialog from app chrome such as a sidebar. It is not an input — typing happens in what it opens. `shortcut` draws flat `<kbd>` chips at the end (not `Keycap`, whose raised shadow reads as a second button inside a filled field), kept out of the accessible name; `iconOnly` makes a 36px icon tile matching a collapsed `NavItem`, named by `label`. _Key props:_ `label` (default `Search`), `shortcut` (`ReactNode[]`), `iconOnly`, `aria-label`, `className`, plus native button attributes (`onClick`, `aria-haspopup`, `aria-expanded`, `aria-keyshortcuts`).
@@ -1,83 +1,57 @@
1
- import { jsx as l } from "react/jsx-runtime";
2
- import { useRef as w, useCallback as F, useState as C, useEffect as h } from "react";
3
- import { createPortal as R } from "react-dom";
4
- import { ensureGeistFontFace as T } from "./geist-font.js";
5
- import { ensureInterFontFace as g } from "./inter-font.js";
6
- import { ensureStackSansFontFace as E } from "./stack-sans-font.js";
7
- import p from "./tailwind.css.js";
8
- import { ensureThemeInitialized as k, applyThemeVars as v, registerShadowRoot as A, unregisterShadowRoot as I } from "./theme.js";
9
- let s = null, S = !1;
10
- const L = () => {
11
- if (s || S)
12
- return s;
13
- if (S = !0, typeof CSSStyleSheet > "u")
14
- return null;
15
- try {
16
- const e = new CSSStyleSheet();
17
- e.replaceSync(p), s = e;
18
- } catch (e) {
19
- console.error("Failed to construct Tailwind stylesheet:", e);
20
- }
21
- return s;
22
- }, f = "malachite-tailwind-style", _ = (e, r) => {
23
- if (!("adoptedStyleSheets" in e))
24
- return !1;
25
- try {
26
- return e.adoptedStyleSheets.includes(r) || (e.adoptedStyleSheets = [...e.adoptedStyleSheets, r]), !0;
27
- } catch {
28
- return !1;
29
- }
30
- }, b = (e, r) => {
31
- if (e.querySelector(`#${f}`))
32
- return;
33
- const n = document.createElement("style");
34
- n.id = f, n.textContent = r, e.appendChild(n);
35
- }, V = ({ children: e, theme: r, hostStyle: n, hostProps: c, hostRef: i, fillHeight: m = !1 }) => {
36
- const a = w(null), d = F(
1
+ import { jsx as n } from "react/jsx-runtime";
2
+ import { useRef as m, useCallback as p, useState as h, useEffect as c } from "react";
3
+ import { createPortal as w } from "react-dom";
4
+ import { ensureGeistFontFace as S } from "./geist-font.js";
5
+ import { ensureInterFontFace as R } from "./inter-font.js";
6
+ import { ensureStackSansFontFace as F } from "./stack-sans-font.js";
7
+ import { adoptTailwindSheet as g } from "./tailwindSheet.js";
8
+ import { ensureThemeInitialized as y, applyThemeVars as k, registerShadowRoot as v, unregisterShadowRoot as T } from "./theme.js";
9
+ const W = ({ children: d, theme: e, hostStyle: i, hostProps: s, hostRef: a, fillHeight: l = !1 }) => {
10
+ const t = m(null), u = p(
37
11
  (o) => {
38
- a.current = o, i && (i.current = o);
12
+ t.current = o, a && (a.current = o);
39
13
  },
40
- [i]
41
- ), [t, y] = C(null);
42
- return h(() => {
43
- if (a.current && !t) {
44
- const o = a.current.shadowRoot ?? a.current.attachShadow({ mode: "open" }), u = L();
45
- (!u || !_(o, u)) && b(o, p), y(o);
14
+ [a]
15
+ ), [r, f] = h(null);
16
+ return c(() => {
17
+ if (t.current && !r) {
18
+ const o = t.current.shadowRoot ?? t.current.attachShadow({ mode: "open" });
19
+ g(o), f(o);
46
20
  }
47
- }, [t]), h(() => {
48
- if (t) {
49
- if (k(), g(), T(), E(), r) {
50
- v(t, r);
21
+ }, [r]), c(() => {
22
+ if (r) {
23
+ if (y(), R(), S(), F(), e) {
24
+ k(r, e);
51
25
  return;
52
26
  }
53
- return A(t), () => {
27
+ return v(r), () => {
54
28
  try {
55
- I(t);
29
+ T(r);
56
30
  } catch (o) {
57
31
  console.warn("Error during shadow root cleanup:", o);
58
32
  }
59
33
  };
60
34
  }
61
- }, [t, r]), t === null ? /* @__PURE__ */ l(
35
+ }, [r, e]), r === null ? /* @__PURE__ */ n(
62
36
  "div",
63
37
  {
64
- ...c,
65
- ref: d,
38
+ ...s,
39
+ ref: u,
66
40
  "data-testid": "shadow-root-wrapper",
67
- style: n
41
+ style: i
68
42
  }
69
- ) : /* @__PURE__ */ l("div", { ...c, ref: d, style: n, children: t && R(
70
- /* @__PURE__ */ l(
43
+ ) : /* @__PURE__ */ n("div", { ...s, ref: u, style: i, children: r && w(
44
+ /* @__PURE__ */ n(
71
45
  "div",
72
46
  {
73
- style: m ? { isolation: "isolate", height: "100%" } : { isolation: "isolate" },
74
- children: e
47
+ style: l ? { isolation: "isolate", height: "100%" } : { isolation: "isolate" },
48
+ children: d
75
49
  }
76
50
  ),
77
- t
51
+ r
78
52
  ) });
79
53
  };
80
54
  export {
81
- V as ShadowRootWrapper
55
+ W as ShadowRootWrapper
82
56
  };
83
57
  //# sourceMappingURL=ShadowRootWrapper.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ShadowRootWrapper.js","sources":["../src/ShadowRootWrapper.tsx"],"sourcesContent":["import React, { useCallback, useEffect, useRef, useState } from 'react';\nimport { createPortal } from 'react-dom';\nimport { ensureGeistFontFace } from './geist-font';\nimport { ensureInterFontFace } from './inter-font';\nimport { ensureStackSansFontFace } from './stack-sans-font';\nimport tailwindCss from './tailwind.css?inline';\nimport {\n type ThemeMode,\n applyThemeVars,\n ensureThemeInitialized,\n registerShadowRoot,\n unregisterShadowRoot,\n} from './theme';\n\nlet tailwindSheet: CSSStyleSheet | null = null;\nlet tailwindSheetAttempted = false;\n\nconst ensureTailwindSheet = (): CSSStyleSheet | null => {\n if (tailwindSheet || tailwindSheetAttempted) {\n return tailwindSheet;\n }\n tailwindSheetAttempted = true;\n if (typeof CSSStyleSheet === 'undefined') {\n return null;\n }\n try {\n const sheet = new CSSStyleSheet();\n sheet.replaceSync(tailwindCss);\n tailwindSheet = sheet;\n } catch (error) {\n console.error('Failed to construct Tailwind stylesheet:', error);\n }\n return tailwindSheet;\n};\n\nconst FALLBACK_STYLE_ID = 'malachite-tailwind-style';\n\nconst adoptSheet = (root: ShadowRoot, sheet: CSSStyleSheet): boolean => {\n if (!('adoptedStyleSheets' in root)) {\n return false;\n }\n try {\n if (!root.adoptedStyleSheets.includes(sheet)) {\n root.adoptedStyleSheets = [...root.adoptedStyleSheets, sheet];\n }\n return true;\n } catch {\n return false;\n }\n};\n\nconst appendStyleTag = (root: ShadowRoot, css: string): void => {\n if (root.querySelector(`#${FALLBACK_STYLE_ID}`)) {\n return;\n }\n const style = document.createElement('style');\n style.id = FALLBACK_STYLE_ID;\n style.textContent = css;\n root.appendChild(style);\n};\n\n// Vite HMR: when tailwind.css changes, mutate the shared sheet in place so every\n// already-adopted shadow root picks up the new rules without a full reload.\nif (import.meta.hot) {\n import.meta.hot.accept('./tailwind.css?inline', (newModule) => {\n const next = (newModule as unknown as { default?: string } | undefined)?.default;\n if (next && tailwindSheet) {\n tailwindSheet.replaceSync(next);\n }\n });\n}\n\nexport const ShadowRootWrapper: React.FC<{\n children: React.ReactNode;\n theme?: ThemeMode;\n hostStyle?: React.CSSProperties;\n /** Identity for the shadow host, so host CSS matching unclassed `body` children cannot\n * blanket-hide a portaled one. Narrow on purpose: a wider type would let a caller\n * clobber `style`, `ref` or `data-testid` through the spread below. */\n hostProps?: { className?: string } & Record<`data-${string}`, string>;\n /** Reads back the shadow host, which a caller needs to promote it into the top layer. */\n hostRef?: { current: HTMLDivElement | null };\n /** Stretches the portal's inner wrapper div (inside the shadow root) to 100% height, to\n * match a `hostStyle` height a caller supplies on the host itself — without this, a tall\n * host div still collapses to content height one level in, because this wrapper div has\n * no height of its own. Off by default: sizing it for every consumer would change layout\n * for callers that rely on it hugging its content. */\n fillHeight?: boolean;\n}> = ({ children, theme, hostStyle, hostProps, hostRef: externalHostRef, fillHeight = false }) => {\n const hostRef = useRef<HTMLDivElement | null>(null);\n const takeHost = useCallback(\n (node: HTMLDivElement | null) => {\n hostRef.current = node;\n if (externalHostRef) {\n externalHostRef.current = node;\n }\n },\n [externalHostRef],\n );\n const [shadowRoot, setShadowRoot] = useState<ShadowRoot | null>(null);\n\n useEffect(() => {\n if (hostRef.current && !shadowRoot) {\n const shadow =\n hostRef.current.shadowRoot ?? hostRef.current.attachShadow({ mode: 'open' });\n // Adopt the stylesheet before setShadowRoot triggers the render that\n // portals children in: adopting in a later effect leaves a window where\n // the browser can paint the children unstyled (e.g. hover-hidden\n // controls flash fully visible on page load until `opacity: 0` lands).\n const sheet = ensureTailwindSheet();\n if (!sheet || !adoptSheet(shadow, sheet)) {\n appendStyleTag(shadow, tailwindCss);\n }\n setShadowRoot(shadow);\n }\n }, [shadowRoot]);\n\n useEffect(() => {\n if (!shadowRoot) {\n return;\n }\n // Both of these belong to the document, not the shadow root: @font-face does not\n // load fonts from inside a shadow root, and the theme's custom properties are set on\n // documentElement so light-DOM content and portals resolve them too.\n ensureThemeInitialized();\n ensureInterFontFace();\n ensureGeistFontFace();\n ensureStackSansFontFace();\n if (theme) {\n applyThemeVars(shadowRoot, theme);\n return;\n }\n registerShadowRoot(shadowRoot);\n return () => {\n try {\n unregisterShadowRoot(shadowRoot);\n } catch (error) {\n // Silently handle cleanup errors during hot reloading\n console.warn('Error during shadow root cleanup:', error);\n }\n };\n }, [shadowRoot, theme]);\n\n if (shadowRoot === null) {\n // Initial render, attach shadow root first\n return (\n <div\n {...hostProps}\n ref={takeHost}\n data-testid=\"shadow-root-wrapper\"\n style={hostStyle}\n />\n );\n }\n\n // Container div inside the shadow root keeps React context working across the portal.\n return (\n <div {...hostProps} ref={takeHost} style={hostStyle}>\n {shadowRoot &&\n createPortal(\n <div\n style={\n fillHeight\n ? { isolation: 'isolate', height: '100%' }\n : { isolation: 'isolate' }\n }\n >\n {children}\n </div>,\n shadowRoot,\n )}\n </div>\n );\n};\n"],"names":["tailwindSheet","tailwindSheetAttempted","ensureTailwindSheet","sheet","tailwindCss","error","FALLBACK_STYLE_ID","adoptSheet","root","appendStyleTag","css","style","ShadowRootWrapper","children","theme","hostStyle","hostProps","externalHostRef","fillHeight","hostRef","useRef","takeHost","useCallback","node","shadowRoot","setShadowRoot","useState","useEffect","shadow","ensureThemeInitialized","ensureInterFontFace","ensureGeistFontFace","ensureStackSansFontFace","applyThemeVars","registerShadowRoot","unregisterShadowRoot","jsx","createPortal"],"mappings":";;;;;;;;AAcA,IAAIA,IAAsC,MACtCC,IAAyB;AAE7B,MAAMC,IAAsB,MAA4B;AACpD,MAAIF,KAAiBC;AACjB,WAAOD;AAGX,MADAC,IAAyB,IACrB,OAAO,gBAAkB;AACzB,WAAO;AAEX,MAAI;AACA,UAAME,IAAQ,IAAI,cAAA;AAClB,IAAAA,EAAM,YAAYC,CAAW,GAC7BJ,IAAgBG;AAAA,EACpB,SAASE,GAAO;AACZ,YAAQ,MAAM,4CAA4CA,CAAK;AAAA,EACnE;AACA,SAAOL;AACX,GAEMM,IAAoB,4BAEpBC,IAAa,CAACC,GAAkBL,MAAkC;AACpE,MAAI,EAAE,wBAAwBK;AAC1B,WAAO;AAEX,MAAI;AACA,WAAKA,EAAK,mBAAmB,SAASL,CAAK,MACvCK,EAAK,qBAAqB,CAAC,GAAGA,EAAK,oBAAoBL,CAAK,IAEzD;AAAA,EACX,QAAQ;AACJ,WAAO;AAAA,EACX;AACJ,GAEMM,IAAiB,CAACD,GAAkBE,MAAsB;AAC5D,MAAIF,EAAK,cAAc,IAAIF,CAAiB,EAAE;AAC1C;AAEJ,QAAMK,IAAQ,SAAS,cAAc,OAAO;AAC5C,EAAAA,EAAM,KAAKL,GACXK,EAAM,cAAcD,GACpBF,EAAK,YAAYG,CAAK;AAC1B,GAaaC,IAgBR,CAAC,EAAE,UAAAC,GAAU,OAAAC,GAAO,WAAAC,GAAW,WAAAC,GAAW,SAASC,GAAiB,YAAAC,IAAa,SAAY;AAC9F,QAAMC,IAAUC,EAA8B,IAAI,GAC5CC,IAAWC;AAAA,IACb,CAACC,MAAgC;AAC7B,MAAAJ,EAAQ,UAAUI,GACdN,MACAA,EAAgB,UAAUM;AAAA,IAElC;AAAA,IACA,CAACN,CAAe;AAAA,EAAA,GAEd,CAACO,GAAYC,CAAa,IAAIC,EAA4B,IAAI;AA4CpE,SA1CAC,EAAU,MAAM;AACZ,QAAIR,EAAQ,WAAW,CAACK,GAAY;AAChC,YAAMI,IACFT,EAAQ,QAAQ,cAAcA,EAAQ,QAAQ,aAAa,EAAE,MAAM,QAAQ,GAKzEhB,IAAQD,EAAA;AACd,OAAI,CAACC,KAAS,CAACI,EAAWqB,GAAQzB,CAAK,MACnCM,EAAemB,GAAQxB,CAAW,GAEtCqB,EAAcG,CAAM;AAAA,IACxB;AAAA,EACJ,GAAG,CAACJ,CAAU,CAAC,GAEfG,EAAU,MAAM;AACZ,QAAKH,GAUL;AAAA,UAJAK,EAAA,GACAC,EAAA,GACAC,EAAA,GACAC,EAAA,GACIlB,GAAO;AACP,QAAAmB,EAAeT,GAAYV,CAAK;AAChC;AAAA,MACJ;AACA,aAAAoB,EAAmBV,CAAU,GACtB,MAAM;AACT,YAAI;AACA,UAAAW,EAAqBX,CAAU;AAAA,QACnC,SAASnB,GAAO;AAEZ,kBAAQ,KAAK,qCAAqCA,CAAK;AAAA,QAC3D;AAAA,MACJ;AAAA;AAAA,EACJ,GAAG,CAACmB,GAAYV,CAAK,CAAC,GAElBU,MAAe,OAGX,gBAAAY;AAAA,IAAC;AAAA,IAAA;AAAA,MACI,GAAGpB;AAAA,MACJ,KAAKK;AAAA,MACL,eAAY;AAAA,MACZ,OAAON;AAAA,IAAA;AAAA,EAAA,IAOf,gBAAAqB,EAAC,SAAK,GAAGpB,GAAW,KAAKK,GAAU,OAAON,GACrC,UAAAS,KACGa;AAAA,IACI,gBAAAD;AAAA,MAAC;AAAA,MAAA;AAAA,QACG,OACIlB,IACM,EAAE,WAAW,WAAW,QAAQ,OAAA,IAChC,EAAE,WAAW,UAAA;AAAA,QAGtB,UAAAL;AAAA,MAAA;AAAA,IAAA;AAAA,IAELW;AAAA,EAAA,GAEZ;AAER;"}
1
+ {"version":3,"file":"ShadowRootWrapper.js","sources":["../src/ShadowRootWrapper.tsx"],"sourcesContent":["import React, { useCallback, useEffect, useRef, useState } from 'react';\nimport { createPortal } from 'react-dom';\nimport { ensureGeistFontFace } from './geist-font';\nimport { ensureInterFontFace } from './inter-font';\nimport { ensureStackSansFontFace } from './stack-sans-font';\nimport { adoptTailwindSheet } from './tailwindSheet';\nimport {\n type ThemeMode,\n applyThemeVars,\n ensureThemeInitialized,\n registerShadowRoot,\n unregisterShadowRoot,\n} from './theme';\n\nexport const ShadowRootWrapper: React.FC<{\n children: React.ReactNode;\n theme?: ThemeMode;\n hostStyle?: React.CSSProperties;\n /** Identity for the shadow host, so host CSS matching unclassed `body` children cannot\n * blanket-hide a portaled one. Narrow on purpose: a wider type would let a caller\n * clobber `style`, `ref` or `data-testid` through the spread below. */\n hostProps?: { className?: string } & Record<`data-${string}`, string>;\n /** Reads back the shadow host, which a caller needs to promote it into the top layer. */\n hostRef?: { current: HTMLDivElement | null };\n /** Stretches the portal's inner wrapper div (inside the shadow root) to 100% height, to\n * match a `hostStyle` height a caller supplies on the host itself — without this, a tall\n * host div still collapses to content height one level in, because this wrapper div has\n * no height of its own. Off by default: sizing it for every consumer would change layout\n * for callers that rely on it hugging its content. */\n fillHeight?: boolean;\n}> = ({ children, theme, hostStyle, hostProps, hostRef: externalHostRef, fillHeight = false }) => {\n const hostRef = useRef<HTMLDivElement | null>(null);\n const takeHost = useCallback(\n (node: HTMLDivElement | null) => {\n hostRef.current = node;\n if (externalHostRef) {\n externalHostRef.current = node;\n }\n },\n [externalHostRef],\n );\n const [shadowRoot, setShadowRoot] = useState<ShadowRoot | null>(null);\n\n useEffect(() => {\n if (hostRef.current && !shadowRoot) {\n const shadow =\n hostRef.current.shadowRoot ?? hostRef.current.attachShadow({ mode: 'open' });\n // Adopt the stylesheet before setShadowRoot triggers the render that\n // portals children in: adopting in a later effect leaves a window where\n // the browser can paint the children unstyled (e.g. hover-hidden\n // controls flash fully visible on page load until `opacity: 0` lands).\n adoptTailwindSheet(shadow);\n setShadowRoot(shadow);\n }\n }, [shadowRoot]);\n\n useEffect(() => {\n if (!shadowRoot) {\n return;\n }\n // Both of these belong to the document, not the shadow root: @font-face does not\n // load fonts from inside a shadow root, and the theme's custom properties are set on\n // documentElement so light-DOM content and portals resolve them too.\n ensureThemeInitialized();\n ensureInterFontFace();\n ensureGeistFontFace();\n ensureStackSansFontFace();\n if (theme) {\n applyThemeVars(shadowRoot, theme);\n return;\n }\n registerShadowRoot(shadowRoot);\n return () => {\n try {\n unregisterShadowRoot(shadowRoot);\n } catch (error) {\n // Silently handle cleanup errors during hot reloading\n console.warn('Error during shadow root cleanup:', error);\n }\n };\n }, [shadowRoot, theme]);\n\n if (shadowRoot === null) {\n // Initial render, attach shadow root first\n return (\n <div\n {...hostProps}\n ref={takeHost}\n data-testid=\"shadow-root-wrapper\"\n style={hostStyle}\n />\n );\n }\n\n // Container div inside the shadow root keeps React context working across the portal.\n return (\n <div {...hostProps} ref={takeHost} style={hostStyle}>\n {shadowRoot &&\n createPortal(\n <div\n style={\n fillHeight\n ? { isolation: 'isolate', height: '100%' }\n : { isolation: 'isolate' }\n }\n >\n {children}\n </div>,\n shadowRoot,\n )}\n </div>\n );\n};\n"],"names":["ShadowRootWrapper","children","theme","hostStyle","hostProps","externalHostRef","fillHeight","hostRef","useRef","takeHost","useCallback","node","shadowRoot","setShadowRoot","useState","useEffect","shadow","adoptTailwindSheet","ensureThemeInitialized","ensureInterFontFace","ensureGeistFontFace","ensureStackSansFontFace","applyThemeVars","registerShadowRoot","unregisterShadowRoot","error","jsx","createPortal"],"mappings":";;;;;;;;AAcO,MAAMA,IAgBR,CAAC,EAAE,UAAAC,GAAU,OAAAC,GAAO,WAAAC,GAAW,WAAAC,GAAW,SAASC,GAAiB,YAAAC,IAAa,SAAY;AAC9F,QAAMC,IAAUC,EAA8B,IAAI,GAC5CC,IAAWC;AAAA,IACb,CAACC,MAAgC;AAC7B,MAAAJ,EAAQ,UAAUI,GACdN,MACAA,EAAgB,UAAUM;AAAA,IAElC;AAAA,IACA,CAACN,CAAe;AAAA,EAAA,GAEd,CAACO,GAAYC,CAAa,IAAIC,EAA4B,IAAI;AAyCpE,SAvCAC,EAAU,MAAM;AACZ,QAAIR,EAAQ,WAAW,CAACK,GAAY;AAChC,YAAMI,IACFT,EAAQ,QAAQ,cAAcA,EAAQ,QAAQ,aAAa,EAAE,MAAM,QAAQ;AAK/E,MAAAU,EAAmBD,CAAM,GACzBH,EAAcG,CAAM;AAAA,IACxB;AAAA,EACJ,GAAG,CAACJ,CAAU,CAAC,GAEfG,EAAU,MAAM;AACZ,QAAKH,GAUL;AAAA,UAJAM,EAAA,GACAC,EAAA,GACAC,EAAA,GACAC,EAAA,GACInB,GAAO;AACP,QAAAoB,EAAeV,GAAYV,CAAK;AAChC;AAAA,MACJ;AACA,aAAAqB,EAAmBX,CAAU,GACtB,MAAM;AACT,YAAI;AACA,UAAAY,EAAqBZ,CAAU;AAAA,QACnC,SAASa,GAAO;AAEZ,kBAAQ,KAAK,qCAAqCA,CAAK;AAAA,QAC3D;AAAA,MACJ;AAAA;AAAA,EACJ,GAAG,CAACb,GAAYV,CAAK,CAAC,GAElBU,MAAe,OAGX,gBAAAc;AAAA,IAAC;AAAA,IAAA;AAAA,MACI,GAAGtB;AAAA,MACJ,KAAKK;AAAA,MACL,eAAY;AAAA,MACZ,OAAON;AAAA,IAAA;AAAA,EAAA,IAOf,gBAAAuB,EAAC,SAAK,GAAGtB,GAAW,KAAKK,GAAU,OAAON,GACrC,UAAAS,KACGe;AAAA,IACI,gBAAAD;AAAA,MAAC;AAAA,MAAA;AAAA,QACG,OACIpB,IACM,EAAE,WAAW,WAAW,QAAQ,OAAA,IAChC,EAAE,WAAW,UAAA;AAAA,QAGtB,UAAAL;AAAA,MAAA;AAAA,IAAA;AAAA,IAELW;AAAA,EAAA,GAEZ;AAER;"}
@@ -1,151 +1,153 @@
1
- import { jsxs as g, jsx as i, Fragment as Y } from "react/jsx-runtime";
2
- import B, { useState as j, useRef as E, useEffect as z, useMemo as G } from "react";
1
+ import { jsxs as h, jsx as r, Fragment as B } from "react/jsx-runtime";
2
+ import S, { useState as j, useRef as E, useEffect as z, useMemo as G } from "react";
3
3
  import { createPortal as $ } from "react-dom";
4
4
  import { Button as X } from "./Button.js";
5
5
  import Z from "./CustomIcons/MoreHorizontalIcon.js";
6
- import { focusEdge as R, edgeForTriggerKey as q, itemForMenuKey as J, enabledItems as Q } from "./actionMenuKeyboard.js";
7
- import { TRIGGER_SIZES as U, EMPTY_ITEM_STYLE as V, menuStyle as W, itemStyle as _, TEXT_ITEM_STYLE as H, ITEM_CLASS as ee, ITEM_ICON_STYLE as ne, LABEL_STYLE as oe } from "./actionMenuStyles.js";
6
+ import { focusEdge as _, edgeForTriggerKey as q, itemForMenuKey as J, enabledItems as Q } from "./actionMenuKeyboard.js";
7
+ import { TRIGGER_SIZES as U, EMPTY_ITEM_STYLE as V, menuStyle as W, itemStyle as x, TEXT_ITEM_STYLE as H, ITEM_CLASS as ee, ITEM_ICON_STYLE as ne, LABEL_STYLE as oe } from "./actionMenuStyles.js";
8
8
  import { getDeepActiveElement as re } from "./activeElement.js";
9
- import { elementRef as te, composeRefs as se, chainHandler as b } from "./composeRefs.js";
10
- import { pushEscapeLayer as ie } from "./escapeLayers.js";
11
- import { overlayIdentity as le } from "./portalOverlay.js";
9
+ import { elementRef as te, composeRefs as le, chainHandler as b } from "./composeRefs.js";
10
+ import { pushEscapeLayer as se } from "./escapeLayers.js";
11
+ import { overlayIdentity as ae } from "./portalOverlay.js";
12
12
  import { useActionMenuPosition as ce } from "./useActionMenuPosition.js";
13
- const M = (n) => n.stopPropagation(), ae = ({ item: n, tone: d, onSelect: h }) => n.type === "divider" ? /* @__PURE__ */ i("div", { style: _(n) }) : n.type === "text" ? /* @__PURE__ */ i("div", { role: "menuitem", "aria-disabled": "true", style: H, children: n.label }) : /* @__PURE__ */ g(
13
+ const ie = S.lazy(() => import("./ActionMenuTagHost.js")), ue = (n) => n != null && n !== !1, g = (n) => n.stopPropagation(), de = ({ item: n, tone: f, onSelect: M }) => n.type === "divider" ? /* @__PURE__ */ r("div", { style: x(n) }) : n.type === "text" ? /* @__PURE__ */ r("div", { role: "menuitem", "aria-disabled": "true", style: H, children: n.label }) : /* @__PURE__ */ h(
14
14
  "button",
15
15
  {
16
16
  type: "button",
17
17
  role: "menuitem",
18
18
  disabled: n.disabled,
19
19
  title: n.tooltip,
20
+ "aria-current": n.current ? "page" : void 0,
20
21
  className: ee,
21
- onClick: () => h(n),
22
- style: _(n, d),
22
+ onClick: () => M(n),
23
+ style: x(n, f),
23
24
  children: [
24
- n.icon && /* @__PURE__ */ i("span", { style: ne, children: n.icon }),
25
- /* @__PURE__ */ i("span", { style: oe, children: n.label })
25
+ n.icon && /* @__PURE__ */ r("span", { style: ne, children: n.icon }),
26
+ /* @__PURE__ */ r("span", { style: oe, children: n.label }),
27
+ ue(n.tag) ? /* @__PURE__ */ r(S.Suspense, { fallback: null, children: /* @__PURE__ */ r(ie, { children: n.tag }) }) : null
26
28
  ]
27
29
  }
28
- ), ve = ({
30
+ ), ke = ({
29
31
  items: n,
30
- label: d = "More actions",
31
- triggerIcon: h,
32
- minWidth: x,
33
- size: S = "small",
32
+ label: f = "More actions",
33
+ triggerIcon: M,
34
+ minWidth: K,
35
+ size: v = "small",
34
36
  showLabel: y = !1,
35
- variant: K = "outline",
36
- trigger: l,
37
+ variant: C = "outline",
38
+ trigger: a,
37
39
  placement: I = "bottom-end",
38
- offset: C,
39
- tone: L = "default"
40
+ offset: L,
41
+ tone: P = "default"
40
42
  }) => {
41
- const v = U[S], [r, c] = j(!1), u = E(null), a = E(null), p = E(null), f = E(!1), P = ce(r, u, a, {
42
- minWidth: x,
43
+ const T = U[v], [t, c] = j(!1), u = E(null), i = E(null), p = E(null), d = E(!1), O = ce(t, u, i, {
44
+ minWidth: K,
43
45
  placement: I,
44
- offset: C
46
+ offset: L
45
47
  });
46
48
  z(() => {
47
- if (!r)
49
+ if (!t)
48
50
  return;
49
- p.current && (R(a.current, p.current), p.current = null);
51
+ p.current && (_(i.current, p.current), p.current = null);
50
52
  const e = (s) => {
51
53
  const m = s.composedPath();
52
- [u.current, a.current].some(
53
- (w) => w && m.includes(w)
54
- ) || (f.current = !1, c(!1));
54
+ [u.current, i.current].some(
55
+ (R) => R && m.includes(R)
56
+ ) || (d.current = !1, c(!1));
55
57
  };
56
58
  document.addEventListener("mousedown", e, !0);
57
- const o = ie(() => c(!1));
59
+ const o = se(() => c(!1));
58
60
  return () => {
59
61
  var m;
60
62
  document.removeEventListener("mousedown", e, !0), o();
61
63
  const s = re();
62
- f.current && (!s || s === document.body) && ((m = u.current) == null || m.focus({ preventScroll: !0 })), f.current = !1;
64
+ d.current && (!s || s === document.body) && ((m = u.current) == null || m.focus({ preventScroll: !0 })), d.current = !1;
63
65
  };
64
- }, [r]);
65
- const D = (e) => {
66
+ }, [t]);
67
+ const k = (e) => {
66
68
  e.stopPropagation();
67
- const o = !r && e.detail === 0;
69
+ const o = !t && e.detail === 0;
68
70
  p.current = o ? "first" : null, c((s) => !s);
69
- }, k = (e) => {
70
- if (e.key === "Tab" && r) {
71
+ }, D = (e) => {
72
+ if (e.key === "Tab" && t) {
71
73
  c(!1);
72
74
  return;
73
75
  }
74
76
  const o = q(e.key, I);
75
77
  if (o) {
76
- if (e.preventDefault(), r) {
77
- R(a.current, o);
78
+ if (e.preventDefault(), t) {
79
+ _(i.current, o);
78
80
  return;
79
81
  }
80
82
  p.current = o, c(!0);
81
83
  }
82
- }, O = (e) => {
84
+ }, A = (e) => {
83
85
  var s;
84
86
  if (e.key === "Tab") {
85
- f.current = !1, (s = u.current) == null || s.focus({ preventScroll: !0 }), c(!1);
87
+ d.current = !1, (s = u.current) == null || s.focus({ preventScroll: !0 }), c(!1);
86
88
  return;
87
89
  }
88
- const o = J(Q(a.current), e.target, e.key);
90
+ const o = J(Q(i.current), e.target, e.key);
89
91
  o && (e.preventDefault(), o.focus());
90
92
  }, F = (e) => {
91
93
  var o;
92
94
  e.type === "divider" || e.type === "text" || e.disabled || ((o = e.onClick) == null || o.call(e), c(!1));
93
- }, T = l ? te(l) : void 0, A = G(
94
- () => se(T, u),
95
- [T]
96
- ), t = l == null ? void 0 : l.props, N = l ? B.cloneElement(l, {
97
- ref: A,
95
+ }, w = a ? te(a) : void 0, N = G(
96
+ () => le(w, u),
97
+ [w]
98
+ ), l = a == null ? void 0 : a.props, Y = a ? S.cloneElement(a, {
99
+ ref: N,
98
100
  "aria-haspopup": "menu",
99
- "aria-expanded": r,
100
- onClick: b(t == null ? void 0 : t.onClick, D),
101
- onMouseDown: b(t == null ? void 0 : t.onMouseDown, M),
102
- onKeyDown: b(t == null ? void 0 : t.onKeyDown, k)
103
- }) : /* @__PURE__ */ g(
101
+ "aria-expanded": t,
102
+ onClick: b(l == null ? void 0 : l.onClick, k),
103
+ onMouseDown: b(l == null ? void 0 : l.onMouseDown, g),
104
+ onKeyDown: b(l == null ? void 0 : l.onKeyDown, D)
105
+ }) : /* @__PURE__ */ h(
104
106
  X,
105
107
  {
106
108
  ref: u,
107
109
  type: "button",
108
- variant: K,
109
- size: S,
110
+ variant: C,
111
+ size: v,
110
112
  iconOnly: !y,
111
- "aria-label": y ? void 0 : d,
113
+ "aria-label": y ? void 0 : f,
112
114
  "aria-haspopup": "menu",
113
- "aria-expanded": r,
115
+ "aria-expanded": t,
114
116
  className: `shrink-0${y ? " gap-2" : ""}`,
115
- onClick: D,
116
- onMouseDown: M,
117
- onKeyDown: k,
117
+ onClick: k,
118
+ onMouseDown: g,
119
+ onKeyDown: D,
118
120
  children: [
119
- /* @__PURE__ */ i("span", { className: `inline-flex items-center justify-center ${v.iconBox}`, children: h ?? /* @__PURE__ */ i(Z, { size: v.icon }) }),
120
- y && /* @__PURE__ */ i("span", { children: d })
121
+ /* @__PURE__ */ r("span", { className: `inline-flex items-center justify-center ${T.iconBox}`, children: M ?? /* @__PURE__ */ r(Z, { size: T.icon }) }),
122
+ y && /* @__PURE__ */ r("span", { children: f })
121
123
  ]
122
124
  }
123
125
  );
124
- return /* @__PURE__ */ g(Y, { children: [
125
- N,
126
- r && $(
127
- /* @__PURE__ */ g(
126
+ return /* @__PURE__ */ h(B, { children: [
127
+ Y,
128
+ t && $(
129
+ /* @__PURE__ */ h(
128
130
  "div",
129
131
  {
130
- ref: a,
132
+ ref: i,
131
133
  role: "menu",
132
134
  tabIndex: -1,
133
- "aria-label": d,
134
- ...le("actionmenu"),
135
- style: W(P),
136
- onMouseDown: M,
137
- onClick: M,
138
- onKeyDown: O,
135
+ "aria-label": f,
136
+ ...ae("actionmenu"),
137
+ style: W(O),
138
+ onMouseDown: g,
139
+ onClick: g,
140
+ onKeyDown: A,
139
141
  onFocus: () => {
140
- f.current = !0;
142
+ d.current = !0;
141
143
  },
142
144
  onBlur: (e) => {
143
145
  var o;
144
- (o = a.current) != null && o.contains(e.relatedTarget) || (f.current = !1);
146
+ (o = i.current) != null && o.contains(e.relatedTarget) || (d.current = !1);
145
147
  },
146
148
  children: [
147
- n.length === 0 && /* @__PURE__ */ i("div", { role: "menuitem", "aria-disabled": "true", style: V, children: "No actions available" }),
148
- n.map((e) => /* @__PURE__ */ i(ae, { item: e, tone: L, onSelect: F }, e.key))
149
+ n.length === 0 && /* @__PURE__ */ r("div", { role: "menuitem", "aria-disabled": "true", style: V, children: "No actions available" }),
150
+ n.map((e) => /* @__PURE__ */ r(de, { item: e, tone: P, onSelect: F }, e.key))
149
151
  ]
150
152
  }
151
153
  ),
@@ -154,6 +156,6 @@ const M = (n) => n.stopPropagation(), ae = ({ item: n, tone: d, onSelect: h }) =
154
156
  ] });
155
157
  };
156
158
  export {
157
- ve as default
159
+ ke as default
158
160
  };
159
161
  //# sourceMappingURL=ActionMenu.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ActionMenu.js","sources":["../../src/components/ActionMenu.tsx"],"sourcesContent":["import React, { useEffect, useMemo, useRef, useState } from 'react';\nimport { createPortal } from 'react-dom';\nimport Button from './Button';\nimport type { ButtonVariant } from './Button';\nimport MoreHorizontalIcon from './CustomIcons/MoreHorizontalIcon';\nimport {\n type MenuEdge,\n edgeForTriggerKey,\n enabledItems,\n focusEdge,\n itemForMenuKey,\n} from './actionMenuKeyboard';\nimport {\n EMPTY_ITEM_STYLE,\n ITEM_CLASS,\n ITEM_ICON_STYLE,\n LABEL_STYLE,\n TEXT_ITEM_STYLE,\n TRIGGER_SIZES,\n type ActionMenuTone,\n itemStyle,\n menuStyle,\n} from './actionMenuStyles';\nimport type { ActionMenuItem, ActionMenuPlacement } from './actionMenuTypes';\nimport { getDeepActiveElement } from './activeElement';\nimport { chainHandler, composeRefs, elementRef } from './composeRefs';\nimport { pushEscapeLayer } from './escapeLayers';\nimport type { FloatingOffset } from './floatingMenuGeometry';\nimport { overlayIdentity } from './portalOverlay';\nimport { useActionMenuPosition } from './useActionMenuPosition';\n\nexport type { ActionMenuItem, ActionMenuPlacement, ActionMenuTone };\n\nexport interface ActionMenuProps {\n items: ActionMenuItem[];\n label?: string;\n triggerIcon?: React.ReactNode;\n minWidth?: number;\n size?: 'small' | 'xsmall';\n /**\n * Shows `label` beside the icon instead of leaving it to the accessible name. For a menu\n * that is the point of its corner rather than an overflow for a row that already reads —\n * a kebab nobody can name is only safe when the row it belongs to says what it acts on.\n */\n showLabel?: boolean;\n /** Trigger's Button variant. Defaults to `outline`, the overflow-kebab look. */\n variant?: ButtonVariant;\n trigger?: React.ReactElement;\n placement?: ActionMenuPlacement;\n offset?: FloatingOffset;\n /** `muted` reads items in the secondary text colour, as in a sidebar's menus. */\n tone?: ActionMenuTone;\n}\n\ntype TriggerHandlers = {\n onClick?: (event: React.MouseEvent) => void;\n onMouseDown?: (event: React.MouseEvent) => void;\n onKeyDown?: (event: React.KeyboardEvent) => void;\n};\n\nconst stopPropagation = (event: React.SyntheticEvent) => event.stopPropagation();\n\nconst MenuItemRow: React.FC<{\n item: ActionMenuItem;\n tone: ActionMenuTone;\n onSelect: (item: ActionMenuItem) => void;\n}> = ({ item, tone, onSelect }) => {\n if (item.type === 'divider') {\n return <div style={itemStyle(item)} />;\n }\n if (item.type === 'text') {\n return (\n <div role=\"menuitem\" aria-disabled=\"true\" style={TEXT_ITEM_STYLE}>\n {item.label}\n </div>\n );\n }\n return (\n <button\n type=\"button\"\n role=\"menuitem\"\n disabled={item.disabled}\n title={item.tooltip}\n className={ITEM_CLASS}\n onClick={() => onSelect(item)}\n style={itemStyle(item, tone)}\n >\n {item.icon && <span style={ITEM_ICON_STYLE}>{item.icon}</span>}\n <span style={LABEL_STYLE}>{item.label}</span>\n </button>\n );\n};\n\n/**\n * ActionMenu is an overflow action menu. The trigger is a three-dot `Button` by default, or\n * your own control via `trigger`. The menu is portaled to document.body, so it escapes any\n * Shadow DOM or overflow:hidden ancestor, which is also why it is styled inline.\n */\nconst ActionMenu: React.FC<ActionMenuProps> = ({\n items,\n label = 'More actions',\n triggerIcon,\n minWidth,\n size = 'small',\n showLabel = false,\n variant = 'outline',\n trigger: customTrigger,\n placement = 'bottom-end',\n offset,\n tone = 'default',\n}) => {\n const triggerSize = TRIGGER_SIZES[size];\n const [open, setOpen] = useState(false);\n const triggerRef = useRef<HTMLElement | null>(null);\n const menuRef = useRef<HTMLDivElement>(null);\n const focusOnOpen = useRef<MenuEdge | null>(null);\n const focusInMenu = useRef(false);\n\n const position = useActionMenuPosition(open, triggerRef, menuRef, {\n minWidth,\n placement,\n offset,\n });\n\n useEffect(() => {\n if (!open) {\n return;\n }\n if (focusOnOpen.current) {\n focusEdge(menuRef.current, focusOnOpen.current);\n focusOnOpen.current = null;\n }\n const closeOnOutsidePress = (event: MouseEvent) => {\n const path = event.composedPath();\n const inside = [triggerRef.current, menuRef.current].some(\n (node) => node && path.includes(node),\n );\n if (!inside) {\n focusInMenu.current = false;\n setOpen(false);\n }\n };\n document.addEventListener('mousedown', closeOnOutsidePress, true);\n const popEscapeLayer = pushEscapeLayer(() => setOpen(false));\n return () => {\n document.removeEventListener('mousedown', closeOnOutsidePress, true);\n popEscapeLayer();\n const active = getDeepActiveElement();\n if (focusInMenu.current && (!active || active === document.body)) {\n triggerRef.current?.focus({ preventScroll: true });\n }\n focusInMenu.current = false;\n };\n }, [open]);\n\n const toggleFromTrigger = (event: React.MouseEvent) => {\n event.stopPropagation();\n const openedFromKeyboard = !open && event.detail === 0;\n focusOnOpen.current = openedFromKeyboard ? 'first' : null;\n setOpen((v) => !v);\n };\n\n const handleTriggerKeyDown = (event: React.KeyboardEvent) => {\n if (event.key === 'Tab' && open) {\n setOpen(false);\n return;\n }\n const edge = edgeForTriggerKey(event.key, placement);\n if (!edge) {\n return;\n }\n event.preventDefault();\n if (open) {\n focusEdge(menuRef.current, edge);\n return;\n }\n focusOnOpen.current = edge;\n setOpen(true);\n };\n\n const handleMenuKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => {\n if (event.key === 'Tab') {\n focusInMenu.current = false;\n triggerRef.current?.focus({ preventScroll: true });\n setOpen(false);\n return;\n }\n const target = itemForMenuKey(enabledItems(menuRef.current), event.target, event.key);\n if (target) {\n event.preventDefault();\n target.focus();\n }\n };\n\n const selectItem = (item: ActionMenuItem) => {\n if (item.type === 'divider' || item.type === 'text' || item.disabled) {\n return;\n }\n item.onClick?.();\n setOpen(false);\n };\n\n const ownTriggerRef = customTrigger ? elementRef<HTMLElement>(customTrigger) : undefined;\n const composedTriggerRef = useMemo(\n () => composeRefs(ownTriggerRef, triggerRef),\n [ownTriggerRef],\n );\n const customHandlers = customTrigger?.props as TriggerHandlers | undefined;\n\n const triggerElement = customTrigger ? (\n React.cloneElement(customTrigger, {\n ref: composedTriggerRef,\n 'aria-haspopup': 'menu',\n 'aria-expanded': open,\n onClick: chainHandler(customHandlers?.onClick, toggleFromTrigger),\n onMouseDown: chainHandler(customHandlers?.onMouseDown, stopPropagation),\n onKeyDown: chainHandler(customHandlers?.onKeyDown, handleTriggerKeyDown),\n } as Record<string, unknown>)\n ) : (\n <Button\n ref={triggerRef as React.RefObject<HTMLButtonElement>}\n type=\"button\"\n variant={variant}\n size={size}\n iconOnly={!showLabel}\n aria-label={showLabel ? undefined : label}\n aria-haspopup=\"menu\"\n aria-expanded={open}\n className={`shrink-0${showLabel ? ' gap-2' : ''}`}\n onClick={toggleFromTrigger}\n onMouseDown={stopPropagation}\n onKeyDown={handleTriggerKeyDown}\n >\n <span className={`inline-flex items-center justify-center ${triggerSize.iconBox}`}>\n {triggerIcon ?? <MoreHorizontalIcon size={triggerSize.icon} />}\n </span>\n {showLabel && <span>{label}</span>}\n </Button>\n );\n\n return (\n <>\n {triggerElement}\n {open &&\n createPortal(\n <div\n ref={menuRef}\n role=\"menu\"\n tabIndex={-1}\n aria-label={label}\n {...overlayIdentity('actionmenu')}\n style={menuStyle(position)}\n onMouseDown={stopPropagation}\n onClick={stopPropagation}\n onKeyDown={handleMenuKeyDown}\n onFocus={() => {\n focusInMenu.current = true;\n }}\n onBlur={(e) => {\n if (!menuRef.current?.contains(e.relatedTarget as Node | null)) {\n focusInMenu.current = false;\n }\n }}\n >\n {items.length === 0 && (\n <div role=\"menuitem\" aria-disabled=\"true\" style={EMPTY_ITEM_STYLE}>\n No actions available\n </div>\n )}\n {items.map((item) => (\n <MenuItemRow key={item.key} item={item} tone={tone} onSelect={selectItem} />\n ))}\n </div>,\n document.body,\n )}\n </>\n );\n};\n\nexport default ActionMenu;\n"],"names":["stopPropagation","event","MenuItemRow","item","tone","onSelect","jsx","itemStyle","TEXT_ITEM_STYLE","jsxs","ITEM_CLASS","ITEM_ICON_STYLE","LABEL_STYLE","ActionMenu","items","label","triggerIcon","minWidth","size","showLabel","variant","customTrigger","placement","offset","triggerSize","TRIGGER_SIZES","open","setOpen","useState","triggerRef","useRef","menuRef","focusOnOpen","focusInMenu","position","useActionMenuPosition","useEffect","focusEdge","closeOnOutsidePress","path","node","popEscapeLayer","pushEscapeLayer","active","getDeepActiveElement","_a","toggleFromTrigger","openedFromKeyboard","v","handleTriggerKeyDown","edge","edgeForTriggerKey","handleMenuKeyDown","target","itemForMenuKey","enabledItems","selectItem","ownTriggerRef","elementRef","composedTriggerRef","useMemo","composeRefs","customHandlers","triggerElement","React","chainHandler","Button","MoreHorizontalIcon","Fragment","createPortal","overlayIdentity","menuStyle","EMPTY_ITEM_STYLE"],"mappings":";;;;;;;;;;;;AA4DA,MAAMA,IAAkB,CAACC,MAAgCA,EAAM,gBAAA,GAEzDC,KAID,CAAC,EAAE,MAAAC,GAAM,MAAAC,GAAM,UAAAC,QACZF,EAAK,SAAS,YACP,gBAAAG,EAAC,OAAA,EAAI,OAAOC,EAAUJ,CAAI,GAAG,IAEpCA,EAAK,SAAS,SAEV,gBAAAG,EAAC,SAAI,MAAK,YAAW,iBAAc,QAAO,OAAOE,GAC5C,UAAAL,EAAK,MAAA,CACV,IAIJ,gBAAAM;AAAA,EAAC;AAAA,EAAA;AAAA,IACG,MAAK;AAAA,IACL,MAAK;AAAA,IACL,UAAUN,EAAK;AAAA,IACf,OAAOA,EAAK;AAAA,IACZ,WAAWO;AAAA,IACX,SAAS,MAAML,EAASF,CAAI;AAAA,IAC5B,OAAOI,EAAUJ,GAAMC,CAAI;AAAA,IAE1B,UAAA;AAAA,MAAAD,EAAK,QAAQ,gBAAAG,EAAC,QAAA,EAAK,OAAOK,IAAkB,YAAK,MAAK;AAAA,MACvD,gBAAAL,EAAC,QAAA,EAAK,OAAOM,IAAc,YAAK,MAAA,CAAM;AAAA,IAAA;AAAA,EAAA;AAAA,GAU5CC,KAAwC,CAAC;AAAA,EAC3C,OAAAC;AAAA,EACA,OAAAC,IAAQ;AAAA,EACR,aAAAC;AAAA,EACA,UAAAC;AAAA,EACA,MAAAC,IAAO;AAAA,EACP,WAAAC,IAAY;AAAA,EACZ,SAAAC,IAAU;AAAA,EACV,SAASC;AAAA,EACT,WAAAC,IAAY;AAAA,EACZ,QAAAC;AAAA,EACA,MAAAnB,IAAO;AACX,MAAM;AACF,QAAMoB,IAAcC,EAAcP,CAAI,GAChC,CAACQ,GAAMC,CAAO,IAAIC,EAAS,EAAK,GAChCC,IAAaC,EAA2B,IAAI,GAC5CC,IAAUD,EAAuB,IAAI,GACrCE,IAAcF,EAAwB,IAAI,GAC1CG,IAAcH,EAAO,EAAK,GAE1BI,IAAWC,GAAsBT,GAAMG,GAAYE,GAAS;AAAA,IAC9D,UAAAd;AAAA,IACA,WAAAK;AAAA,IACA,QAAAC;AAAA,EAAA,CACH;AAED,EAAAa,EAAU,MAAM;AACZ,QAAI,CAACV;AACD;AAEJ,IAAIM,EAAY,YACZK,EAAUN,EAAQ,SAASC,EAAY,OAAO,GAC9CA,EAAY,UAAU;AAE1B,UAAMM,IAAsB,CAACrC,MAAsB;AAC/C,YAAMsC,IAAOtC,EAAM,aAAA;AAInB,MAHe,CAAC4B,EAAW,SAASE,EAAQ,OAAO,EAAE;AAAA,QACjD,CAACS,MAASA,KAAQD,EAAK,SAASC,CAAI;AAAA,MAAA,MAGpCP,EAAY,UAAU,IACtBN,EAAQ,EAAK;AAAA,IAErB;AACA,aAAS,iBAAiB,aAAaW,GAAqB,EAAI;AAChE,UAAMG,IAAiBC,GAAgB,MAAMf,EAAQ,EAAK,CAAC;AAC3D,WAAO,MAAM;;AACT,eAAS,oBAAoB,aAAaW,GAAqB,EAAI,GACnEG,EAAA;AACA,YAAME,IAASC,GAAA;AACf,MAAIX,EAAY,YAAY,CAACU,KAAUA,MAAW,SAAS,WACvDE,IAAAhB,EAAW,YAAX,QAAAgB,EAAoB,MAAM,EAAE,eAAe,QAE/CZ,EAAY,UAAU;AAAA,IAC1B;AAAA,EACJ,GAAG,CAACP,CAAI,CAAC;AAET,QAAMoB,IAAoB,CAAC7C,MAA4B;AACnD,IAAAA,EAAM,gBAAA;AACN,UAAM8C,IAAqB,CAACrB,KAAQzB,EAAM,WAAW;AACrD,IAAA+B,EAAY,UAAUe,IAAqB,UAAU,MACrDpB,EAAQ,CAACqB,MAAM,CAACA,CAAC;AAAA,EACrB,GAEMC,IAAuB,CAAChD,MAA+B;AACzD,QAAIA,EAAM,QAAQ,SAASyB,GAAM;AAC7B,MAAAC,EAAQ,EAAK;AACb;AAAA,IACJ;AACA,UAAMuB,IAAOC,EAAkBlD,EAAM,KAAKqB,CAAS;AACnD,QAAK4B,GAIL;AAAA,UADAjD,EAAM,eAAA,GACFyB,GAAM;AACN,QAAAW,EAAUN,EAAQ,SAASmB,CAAI;AAC/B;AAAA,MACJ;AACA,MAAAlB,EAAY,UAAUkB,GACtBvB,EAAQ,EAAI;AAAA;AAAA,EAChB,GAEMyB,IAAoB,CAACnD,MAA+C;;AACtE,QAAIA,EAAM,QAAQ,OAAO;AACrB,MAAAgC,EAAY,UAAU,KACtBY,IAAAhB,EAAW,YAAX,QAAAgB,EAAoB,MAAM,EAAE,eAAe,OAC3ClB,EAAQ,EAAK;AACb;AAAA,IACJ;AACA,UAAM0B,IAASC,EAAeC,EAAaxB,EAAQ,OAAO,GAAG9B,EAAM,QAAQA,EAAM,GAAG;AACpF,IAAIoD,MACApD,EAAM,eAAA,GACNoD,EAAO,MAAA;AAAA,EAEf,GAEMG,IAAa,CAACrD,MAAyB;;AACzC,IAAIA,EAAK,SAAS,aAAaA,EAAK,SAAS,UAAUA,EAAK,cAG5D0C,IAAA1C,EAAK,YAAL,QAAA0C,EAAA,KAAA1C,IACAwB,EAAQ,EAAK;AAAA,EACjB,GAEM8B,IAAgBpC,IAAgBqC,GAAwBrC,CAAa,IAAI,QACzEsC,IAAqBC;AAAA,IACvB,MAAMC,GAAYJ,GAAe5B,CAAU;AAAA,IAC3C,CAAC4B,CAAa;AAAA,EAAA,GAEZK,IAAiBzC,KAAA,gBAAAA,EAAe,OAEhC0C,IAAiB1C,IACnB2C,EAAM,aAAa3C,GAAe;AAAA,IAC9B,KAAKsC;AAAA,IACL,iBAAiB;AAAA,IACjB,iBAAiBjC;AAAA,IACjB,SAASuC,EAAaH,KAAA,gBAAAA,EAAgB,SAAShB,CAAiB;AAAA,IAChE,aAAamB,EAAaH,KAAA,gBAAAA,EAAgB,aAAa9D,CAAe;AAAA,IACtE,WAAWiE,EAAaH,KAAA,gBAAAA,EAAgB,WAAWb,CAAoB;AAAA,EAAA,CAC/C,IAE5B,gBAAAxC;AAAA,IAACyD;AAAA,IAAA;AAAA,MACG,KAAKrC;AAAA,MACL,MAAK;AAAA,MACL,SAAAT;AAAA,MACA,MAAAF;AAAA,MACA,UAAU,CAACC;AAAA,MACX,cAAYA,IAAY,SAAYJ;AAAA,MACpC,iBAAc;AAAA,MACd,iBAAeW;AAAA,MACf,WAAW,WAAWP,IAAY,WAAW,EAAE;AAAA,MAC/C,SAAS2B;AAAA,MACT,aAAa9C;AAAA,MACb,WAAWiD;AAAA,MAEX,UAAA;AAAA,QAAA,gBAAA3C,EAAC,QAAA,EAAK,WAAW,2CAA2CkB,EAAY,OAAO,IAC1E,UAAAR,KAAe,gBAAAV,EAAC6D,GAAA,EAAmB,MAAM3C,EAAY,KAAA,CAAM,GAChE;AAAA,QACCL,KAAa,gBAAAb,EAAC,QAAA,EAAM,UAAAS,EAAA,CAAM;AAAA,MAAA;AAAA,IAAA;AAAA,EAAA;AAInC,SACI,gBAAAN,EAAA2D,GAAA,EACK,UAAA;AAAA,IAAAL;AAAA,IACArC,KACG2C;AAAA,MACI,gBAAA5D;AAAA,QAAC;AAAA,QAAA;AAAA,UACG,KAAKsB;AAAA,UACL,MAAK;AAAA,UACL,UAAU;AAAA,UACV,cAAYhB;AAAA,UACX,GAAGuD,GAAgB,YAAY;AAAA,UAChC,OAAOC,EAAUrC,CAAQ;AAAA,UACzB,aAAalC;AAAA,UACb,SAASA;AAAA,UACT,WAAWoD;AAAA,UACX,SAAS,MAAM;AACX,YAAAnB,EAAY,UAAU;AAAA,UAC1B;AAAA,UACA,QAAQ,CAAC,MAAM;;AACX,aAAKY,IAAAd,EAAQ,YAAR,QAAAc,EAAiB,SAAS,EAAE,mBAC7BZ,EAAY,UAAU;AAAA,UAE9B;AAAA,UAEC,UAAA;AAAA,YAAAnB,EAAM,WAAW,KACd,gBAAAR,EAAC,OAAA,EAAI,MAAK,YAAW,iBAAc,QAAO,OAAOkE,GAAkB,UAAA,uBAAA,CAEnE;AAAA,YAEH1D,EAAM,IAAI,CAACX,MACR,gBAAAG,EAACJ,IAAA,EAA2B,MAAAC,GAAY,MAAAC,GAAY,UAAUoD,KAA5CrD,EAAK,GAAmD,CAC7E;AAAA,UAAA;AAAA,QAAA;AAAA,MAAA;AAAA,MAEL,SAAS;AAAA,IAAA;AAAA,EACb,GACR;AAER;"}
1
+ {"version":3,"file":"ActionMenu.js","sources":["../../src/components/ActionMenu.tsx"],"sourcesContent":["import React, { useEffect, useMemo, useRef, useState } from 'react';\nimport { createPortal } from 'react-dom';\nimport Button from './Button';\nimport type { ButtonVariant } from './Button';\nimport MoreHorizontalIcon from './CustomIcons/MoreHorizontalIcon';\nimport {\n type MenuEdge,\n edgeForTriggerKey,\n enabledItems,\n focusEdge,\n itemForMenuKey,\n} from './actionMenuKeyboard';\nimport {\n EMPTY_ITEM_STYLE,\n ITEM_CLASS,\n ITEM_ICON_STYLE,\n LABEL_STYLE,\n TEXT_ITEM_STYLE,\n TRIGGER_SIZES,\n type ActionMenuTone,\n itemStyle,\n menuStyle,\n} from './actionMenuStyles';\nimport type { ActionMenuItem, ActionMenuPlacement } from './actionMenuTypes';\nimport { getDeepActiveElement } from './activeElement';\nimport { chainHandler, composeRefs, elementRef } from './composeRefs';\nimport { pushEscapeLayer } from './escapeLayers';\nimport type { FloatingOffset } from './floatingMenuGeometry';\nimport { overlayIdentity } from './portalOverlay';\nimport { useActionMenuPosition } from './useActionMenuPosition';\n\nexport type { ActionMenuItem, ActionMenuPlacement, ActionMenuTone };\n\nexport interface ActionMenuProps {\n items: ActionMenuItem[];\n label?: string;\n triggerIcon?: React.ReactNode;\n minWidth?: number;\n size?: 'small' | 'xsmall';\n /**\n * Shows `label` beside the icon instead of leaving it to the accessible name. For a menu\n * that is the point of its corner rather than an overflow for a row that already reads —\n * a kebab nobody can name is only safe when the row it belongs to says what it acts on.\n */\n showLabel?: boolean;\n /** Trigger's Button variant. Defaults to `outline`, the overflow-kebab look. */\n variant?: ButtonVariant;\n trigger?: React.ReactElement;\n placement?: ActionMenuPlacement;\n offset?: FloatingOffset;\n /** `muted` reads items in the secondary text colour, as in a sidebar's menus. */\n tone?: ActionMenuTone;\n}\n\ntype TriggerHandlers = {\n onClick?: (event: React.MouseEvent) => void;\n onMouseDown?: (event: React.MouseEvent) => void;\n onKeyDown?: (event: React.KeyboardEvent) => void;\n};\n\n// Loaded only when an item has a tag: its shadow host pulls in the Tailwind sheet, which a menu\n// without tags should not pay for.\nconst ActionMenuTagHost = React.lazy(() => import('./ActionMenuTagHost'));\n\nconst hasTag = (tag: React.ReactNode) => tag !== undefined && tag !== null && tag !== false;\n\nconst stopPropagation = (event: React.SyntheticEvent) => event.stopPropagation();\n\nconst MenuItemRow: React.FC<{\n item: ActionMenuItem;\n tone: ActionMenuTone;\n onSelect: (item: ActionMenuItem) => void;\n}> = ({ item, tone, onSelect }) => {\n if (item.type === 'divider') {\n return <div style={itemStyle(item)} />;\n }\n if (item.type === 'text') {\n return (\n <div role=\"menuitem\" aria-disabled=\"true\" style={TEXT_ITEM_STYLE}>\n {item.label}\n </div>\n );\n }\n return (\n <button\n type=\"button\"\n role=\"menuitem\"\n disabled={item.disabled}\n title={item.tooltip}\n aria-current={item.current ? 'page' : undefined}\n className={ITEM_CLASS}\n onClick={() => onSelect(item)}\n style={itemStyle(item, tone)}\n >\n {item.icon && <span style={ITEM_ICON_STYLE}>{item.icon}</span>}\n <span style={LABEL_STYLE}>{item.label}</span>\n {hasTag(item.tag) ? (\n <React.Suspense fallback={null}>\n <ActionMenuTagHost>{item.tag}</ActionMenuTagHost>\n </React.Suspense>\n ) : null}\n </button>\n );\n};\n\n/**\n * ActionMenu is an overflow action menu. The trigger is a three-dot `Button` by default, or\n * your own control via `trigger`. The menu is portaled to document.body, so it escapes any\n * Shadow DOM or overflow:hidden ancestor, which is also why it is styled inline.\n */\nconst ActionMenu: React.FC<ActionMenuProps> = ({\n items,\n label = 'More actions',\n triggerIcon,\n minWidth,\n size = 'small',\n showLabel = false,\n variant = 'outline',\n trigger: customTrigger,\n placement = 'bottom-end',\n offset,\n tone = 'default',\n}) => {\n const triggerSize = TRIGGER_SIZES[size];\n const [open, setOpen] = useState(false);\n const triggerRef = useRef<HTMLElement | null>(null);\n const menuRef = useRef<HTMLDivElement>(null);\n const focusOnOpen = useRef<MenuEdge | null>(null);\n const focusInMenu = useRef(false);\n\n const position = useActionMenuPosition(open, triggerRef, menuRef, {\n minWidth,\n placement,\n offset,\n });\n\n useEffect(() => {\n if (!open) {\n return;\n }\n if (focusOnOpen.current) {\n focusEdge(menuRef.current, focusOnOpen.current);\n focusOnOpen.current = null;\n }\n const closeOnOutsidePress = (event: MouseEvent) => {\n const path = event.composedPath();\n const inside = [triggerRef.current, menuRef.current].some(\n (node) => node && path.includes(node),\n );\n if (!inside) {\n focusInMenu.current = false;\n setOpen(false);\n }\n };\n document.addEventListener('mousedown', closeOnOutsidePress, true);\n const popEscapeLayer = pushEscapeLayer(() => setOpen(false));\n return () => {\n document.removeEventListener('mousedown', closeOnOutsidePress, true);\n popEscapeLayer();\n const active = getDeepActiveElement();\n if (focusInMenu.current && (!active || active === document.body)) {\n triggerRef.current?.focus({ preventScroll: true });\n }\n focusInMenu.current = false;\n };\n }, [open]);\n\n const toggleFromTrigger = (event: React.MouseEvent) => {\n event.stopPropagation();\n const openedFromKeyboard = !open && event.detail === 0;\n focusOnOpen.current = openedFromKeyboard ? 'first' : null;\n setOpen((v) => !v);\n };\n\n const handleTriggerKeyDown = (event: React.KeyboardEvent) => {\n if (event.key === 'Tab' && open) {\n setOpen(false);\n return;\n }\n const edge = edgeForTriggerKey(event.key, placement);\n if (!edge) {\n return;\n }\n event.preventDefault();\n if (open) {\n focusEdge(menuRef.current, edge);\n return;\n }\n focusOnOpen.current = edge;\n setOpen(true);\n };\n\n const handleMenuKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => {\n if (event.key === 'Tab') {\n focusInMenu.current = false;\n triggerRef.current?.focus({ preventScroll: true });\n setOpen(false);\n return;\n }\n const target = itemForMenuKey(enabledItems(menuRef.current), event.target, event.key);\n if (target) {\n event.preventDefault();\n target.focus();\n }\n };\n\n const selectItem = (item: ActionMenuItem) => {\n if (item.type === 'divider' || item.type === 'text' || item.disabled) {\n return;\n }\n item.onClick?.();\n setOpen(false);\n };\n\n const ownTriggerRef = customTrigger ? elementRef<HTMLElement>(customTrigger) : undefined;\n const composedTriggerRef = useMemo(\n () => composeRefs(ownTriggerRef, triggerRef),\n [ownTriggerRef],\n );\n const customHandlers = customTrigger?.props as TriggerHandlers | undefined;\n\n const triggerElement = customTrigger ? (\n React.cloneElement(customTrigger, {\n ref: composedTriggerRef,\n 'aria-haspopup': 'menu',\n 'aria-expanded': open,\n onClick: chainHandler(customHandlers?.onClick, toggleFromTrigger),\n onMouseDown: chainHandler(customHandlers?.onMouseDown, stopPropagation),\n onKeyDown: chainHandler(customHandlers?.onKeyDown, handleTriggerKeyDown),\n } as Record<string, unknown>)\n ) : (\n <Button\n ref={triggerRef as React.RefObject<HTMLButtonElement>}\n type=\"button\"\n variant={variant}\n size={size}\n iconOnly={!showLabel}\n aria-label={showLabel ? undefined : label}\n aria-haspopup=\"menu\"\n aria-expanded={open}\n className={`shrink-0${showLabel ? ' gap-2' : ''}`}\n onClick={toggleFromTrigger}\n onMouseDown={stopPropagation}\n onKeyDown={handleTriggerKeyDown}\n >\n <span className={`inline-flex items-center justify-center ${triggerSize.iconBox}`}>\n {triggerIcon ?? <MoreHorizontalIcon size={triggerSize.icon} />}\n </span>\n {showLabel && <span>{label}</span>}\n </Button>\n );\n\n return (\n <>\n {triggerElement}\n {open &&\n createPortal(\n <div\n ref={menuRef}\n role=\"menu\"\n tabIndex={-1}\n aria-label={label}\n {...overlayIdentity('actionmenu')}\n style={menuStyle(position)}\n onMouseDown={stopPropagation}\n onClick={stopPropagation}\n onKeyDown={handleMenuKeyDown}\n onFocus={() => {\n focusInMenu.current = true;\n }}\n onBlur={(e) => {\n if (!menuRef.current?.contains(e.relatedTarget as Node | null)) {\n focusInMenu.current = false;\n }\n }}\n >\n {items.length === 0 && (\n <div role=\"menuitem\" aria-disabled=\"true\" style={EMPTY_ITEM_STYLE}>\n No actions available\n </div>\n )}\n {items.map((item) => (\n <MenuItemRow key={item.key} item={item} tone={tone} onSelect={selectItem} />\n ))}\n </div>,\n document.body,\n )}\n </>\n );\n};\n\nexport default ActionMenu;\n"],"names":["ActionMenuTagHost","React","hasTag","tag","stopPropagation","event","MenuItemRow","item","tone","onSelect","jsx","itemStyle","TEXT_ITEM_STYLE","jsxs","ITEM_CLASS","ITEM_ICON_STYLE","LABEL_STYLE","ActionMenu","items","label","triggerIcon","minWidth","size","showLabel","variant","customTrigger","placement","offset","triggerSize","TRIGGER_SIZES","open","setOpen","useState","triggerRef","useRef","menuRef","focusOnOpen","focusInMenu","position","useActionMenuPosition","useEffect","focusEdge","closeOnOutsidePress","path","node","popEscapeLayer","pushEscapeLayer","active","getDeepActiveElement","_a","toggleFromTrigger","openedFromKeyboard","v","handleTriggerKeyDown","edge","edgeForTriggerKey","handleMenuKeyDown","target","itemForMenuKey","enabledItems","selectItem","ownTriggerRef","elementRef","composedTriggerRef","useMemo","composeRefs","customHandlers","triggerElement","chainHandler","Button","MoreHorizontalIcon","Fragment","createPortal","overlayIdentity","menuStyle","EMPTY_ITEM_STYLE"],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ import { jsx as n } from "react/jsx-runtime";
2
+ import { useRef as f, useState as c, useLayoutEffect as m } from "react";
3
+ import { createPortal as u } from "react-dom";
4
+ import { adoptTailwindSheet as d } from "../tailwindSheet.js";
5
+ import { TAG_HOST_PROPS as h, TAG_HOST_STYLE as i } from "./actionMenuStyles.js";
6
+ const R = ({ children: s }) => {
7
+ const t = f(null), [e, a] = c(null);
8
+ return m(() => {
9
+ const o = t.current;
10
+ if (!o)
11
+ return;
12
+ const r = o.shadowRoot ?? o.attachShadow({ mode: "open" });
13
+ d(r), a(r);
14
+ }, []), /* @__PURE__ */ n("span", { ref: t, style: i, ...h, children: e && u(s, e) });
15
+ };
16
+ export {
17
+ R as default
18
+ };
19
+ //# sourceMappingURL=ActionMenuTagHost.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ActionMenuTagHost.js","sources":["../../src/components/ActionMenuTagHost.tsx"],"sourcesContent":["import type React from 'react';\nimport { useLayoutEffect, useRef, useState } from 'react';\nimport { createPortal } from 'react-dom';\nimport { adoptTailwindSheet } from '../tailwindSheet';\nimport { TAG_HOST_PROPS, TAG_HOST_STYLE } from './actionMenuStyles';\n\n// Only the Tailwind sheet: fonts and theme variables already reach the menu from the document,\n// so ShadowRootWrapper's font and theme payload would be dead weight here.\nconst ActionMenuTagHost: React.FC<{ children: React.ReactNode }> = ({ children }) => {\n const hostRef = useRef<HTMLSpanElement>(null);\n const [shadowRoot, setShadowRoot] = useState<ShadowRoot | null>(null);\n\n useLayoutEffect(() => {\n const host = hostRef.current;\n if (!host) {\n return;\n }\n const shadow = host.shadowRoot ?? host.attachShadow({ mode: 'open' });\n adoptTailwindSheet(shadow);\n setShadowRoot(shadow);\n }, []);\n\n return (\n <span ref={hostRef} style={TAG_HOST_STYLE} {...TAG_HOST_PROPS}>\n {shadowRoot && createPortal(children, shadowRoot)}\n </span>\n );\n};\n\nexport default ActionMenuTagHost;\n"],"names":["ActionMenuTagHost","children","hostRef","useRef","shadowRoot","setShadowRoot","useState","useLayoutEffect","host","shadow","adoptTailwindSheet","jsx","TAG_HOST_STYLE","TAG_HOST_PROPS","createPortal"],"mappings":";;;;;AAQA,MAAMA,IAA6D,CAAC,EAAE,UAAAC,QAAe;AACjF,QAAMC,IAAUC,EAAwB,IAAI,GACtC,CAACC,GAAYC,CAAa,IAAIC,EAA4B,IAAI;AAEpE,SAAAC,EAAgB,MAAM;AAClB,UAAMC,IAAON,EAAQ;AACrB,QAAI,CAACM;AACD;AAEJ,UAAMC,IAASD,EAAK,cAAcA,EAAK,aAAa,EAAE,MAAM,QAAQ;AACpE,IAAAE,EAAmBD,CAAM,GACzBJ,EAAcI,CAAM;AAAA,EACxB,GAAG,CAAA,CAAE,GAGD,gBAAAE,EAAC,QAAA,EAAK,KAAKT,GAAS,OAAOU,GAAiB,GAAGC,GAC1C,UAAAT,KAAcU,EAAab,GAAUG,CAAU,EAAA,CACpD;AAER;"}
@@ -1,12 +1,12 @@
1
1
  import { jsxs as u, jsx as a } from "react/jsx-runtime";
2
2
  import { Button as v } from "./Button.js";
3
- import N from "./CustomIcons/ArrowLeftIcon.js";
4
- import b from "./CustomIcons/CaretRightIcon.js";
3
+ import b from "./CustomIcons/ArrowLeftIcon.js";
4
+ import N from "./CustomIcons/CaretRightIcon.js";
5
5
  import B from "./Typography/Text.js";
6
6
  import { hasBackButton as $ } from "./breadcrumbsNav.js";
7
7
  import { FOCUS_RING as x } from "./focusRing.js";
8
8
  import { isSafeHref as y, safeHref as S } from "./safeHref.js";
9
- const A = `no-underline rounded-xs cursor-pointer transition-colors duration-200 ease-in-out hover:text-[var(--malachite-accent-foreground)] ${x}`, C = `flex h-6 w-6 shrink-0 items-center justify-center rounded-[var(--malachite-radius-10,10px)] no-underline bg-[var(--malachite-number-tag-background)] text-[var(--malachite-text-primary)] transition-colors duration-200 ease-in-out hover:bg-[var(--malachite-number-tag-emphasis-background)] ${x}`, g = (e, r) => y(e.href) ? r ? r(e) : /* @__PURE__ */ a("a", { ...e }) : e.children, f = (e, r) => typeof e == "string" ? /* @__PURE__ */ a(B, { size: "medium", fontWeight: "semi-bold", color: r, children: e }) : e, _ = ({
9
+ const A = `no-underline rounded-xs cursor-pointer transition-colors duration-200 ease-in-out hover:text-[var(--malachite-accent-foreground,rgb(2,122,66))] ${x}`, C = `flex h-6 w-6 shrink-0 items-center justify-center rounded-[var(--malachite-radius-10,10px)] no-underline bg-[var(--malachite-number-tag-background,rgba(0,0,0,0.08))] text-[var(--malachite-text-primary,rgb(10,10,10))] transition-colors duration-200 ease-in-out hover:bg-[var(--malachite-number-tag-emphasis-background,rgba(0,0,0,0.15))] ${x}`, g = (e, r) => y(e.href) ? r ? r(e) : /* @__PURE__ */ a("a", { ...e }) : e.children, f = (e, r) => typeof e == "string" ? /* @__PURE__ */ a(B, { size: "medium", fontWeight: "semi-bold", color: r, children: e }) : e, _ = ({
10
10
  label: e,
11
11
  onClick: r,
12
12
  disabled: m,
@@ -56,7 +56,7 @@ const A = `no-underline rounded-xs cursor-pointer transition-colors duration-200
56
56
  href: t.url,
57
57
  "aria-label": typeof l == "string" ? `Back to ${l}` : "Go back",
58
58
  className: C,
59
- children: /* @__PURE__ */ a(N, { size: 12 })
59
+ children: /* @__PURE__ */ a(b, { size: 12 })
60
60
  },
61
61
  m
62
62
  ),
@@ -72,7 +72,7 @@ const A = `no-underline rounded-xs cursor-pointer transition-colors duration-200
72
72
  {
73
73
  href: i.url,
74
74
  "aria-current": o ? "page" : void 0,
75
- className: `${A} ${o ? "text-[var(--malachite-text-primary)]" : "text-[var(--malachite-text-muted)]"}`,
75
+ className: `${A} ${o ? "text-[var(--malachite-text-primary,rgb(10,10,10))]" : "text-[var(--malachite-text-muted,rgb(76,75,75))]"}`,
76
76
  children: f(
77
77
  i.name,
78
78
  "inherit"
@@ -84,8 +84,8 @@ const A = `no-underline rounded-xs cursor-pointer transition-colors duration-200
84
84
  "span",
85
85
  {
86
86
  "aria-hidden": "true",
87
- className: "flex items-center text-[var(--malachite-text-dimmed)]",
88
- children: /* @__PURE__ */ a(b, { size: 13 })
87
+ className: "flex items-center text-[var(--malachite-text-dimmed,rgb(161,161,161))]",
88
+ children: /* @__PURE__ */ a(N, { size: 13 })
89
89
  }
90
90
  )
91
91
  ]