@stackone/malachite 2.37.1 → 2.38.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/COMPONENTS.md +7 -2
- package/dist/assets/icons/account.svg.js +2 -2
- package/dist/assets/icons/account.svg.js.map +1 -1
- package/dist/assets/icons/building.svg.js +6 -0
- package/dist/assets/icons/building.svg.js.map +1 -0
- package/dist/assets/icons/chevrons_up_down.svg.js +6 -0
- package/dist/assets/icons/chevrons_up_down.svg.js.map +1 -0
- package/dist/assets/icons/connector.svg.js +6 -0
- package/dist/assets/icons/connector.svg.js.map +1 -0
- package/dist/assets/icons/external_link.svg.js +6 -0
- package/dist/assets/icons/external_link.svg.js.map +1 -0
- package/dist/assets/icons/field_coverage.svg.js +6 -0
- package/dist/assets/icons/field_coverage.svg.js.map +1 -0
- package/dist/assets/icons/layout.svg.js +6 -0
- package/dist/assets/icons/layout.svg.js.map +1 -0
- package/dist/assets/icons/link.svg.js +6 -0
- package/dist/assets/icons/link.svg.js.map +1 -0
- package/dist/assets/icons/playground.svg.js +6 -0
- package/dist/assets/icons/playground.svg.js.map +1 -0
- package/dist/components/ActionMenu.js +139 -170
- package/dist/components/ActionMenu.js.map +1 -1
- package/dist/components/Chat/ChatModelPicker.js +64 -60
- package/dist/components/Chat/ChatModelPicker.js.map +1 -1
- package/dist/components/CustomIcons/BuildingIcon.js +16 -0
- package/dist/components/CustomIcons/BuildingIcon.js.map +1 -0
- package/dist/components/CustomIcons/ChevronsUpDownIcon.js +16 -0
- package/dist/components/CustomIcons/ChevronsUpDownIcon.js.map +1 -0
- package/dist/components/CustomIcons/ConnectorIcon.js +16 -0
- package/dist/components/CustomIcons/ConnectorIcon.js.map +1 -0
- package/dist/components/CustomIcons/ExternalLinkIcon.js +16 -0
- package/dist/components/CustomIcons/ExternalLinkIcon.js.map +1 -0
- package/dist/components/CustomIcons/FieldCoverageIcon.js +16 -0
- package/dist/components/CustomIcons/FieldCoverageIcon.js.map +1 -0
- package/dist/components/CustomIcons/LayoutIcon.js +16 -0
- package/dist/components/CustomIcons/LayoutIcon.js.map +1 -0
- package/dist/components/CustomIcons/LinkIcon.js +16 -0
- package/dist/components/CustomIcons/LinkIcon.js.map +1 -0
- package/dist/components/CustomIcons/PlaygroundIcon.js +16 -0
- package/dist/components/CustomIcons/PlaygroundIcon.js.map +1 -0
- package/dist/components/CustomIcons/index.js +303 -279
- package/dist/components/CustomIcons/index.js.map +1 -1
- package/dist/components/NavItem.js +158 -0
- package/dist/components/NavItem.js.map +1 -0
- package/dist/components/Popover.js +262 -0
- package/dist/components/Popover.js.map +1 -0
- package/dist/components/PopoverParts.js +46 -0
- package/dist/components/PopoverParts.js.map +1 -0
- package/dist/components/QueryInput.js +103 -97
- package/dist/components/QueryInput.js.map +1 -1
- package/dist/components/SearchTrigger.js +37 -0
- package/dist/components/SearchTrigger.js.map +1 -0
- package/dist/components/Sidebar.js +174 -0
- package/dist/components/Sidebar.js.map +1 -0
- package/dist/components/Tooltip.js +97 -92
- package/dist/components/Tooltip.js.map +1 -1
- package/dist/components/UsageCard.js +44 -0
- package/dist/components/UsageCard.js.map +1 -0
- package/dist/components/actionMenuKeyboard.js +23 -0
- package/dist/components/actionMenuKeyboard.js.map +1 -0
- package/dist/components/actionMenuStyles.js +83 -0
- package/dist/components/actionMenuStyles.js.map +1 -0
- package/dist/components/ariaState.js +5 -0
- package/dist/components/ariaState.js.map +1 -0
- package/dist/components/chromeStyles.js +6 -0
- package/dist/components/chromeStyles.js.map +1 -0
- package/dist/components/composeRefs.js +27 -0
- package/dist/components/composeRefs.js.map +1 -0
- package/dist/components/floatingMenuGeometry.js +74 -35
- package/dist/components/floatingMenuGeometry.js.map +1 -1
- package/dist/components/overlayBehaviour.js +90 -84
- package/dist/components/overlayBehaviour.js.map +1 -1
- package/dist/components/popoverTree.js +14 -0
- package/dist/components/popoverTree.js.map +1 -0
- package/dist/components/portalOverlay.js +28 -22
- package/dist/components/portalOverlay.js.map +1 -1
- package/dist/components/sidebarContext.js +9 -0
- package/dist/components/sidebarContext.js.map +1 -0
- package/dist/components/topLayer.js +18 -18
- package/dist/components/topLayer.js.map +1 -1
- package/dist/components/useActionMenuPosition.js +73 -0
- package/dist/components/useActionMenuPosition.js.map +1 -0
- package/dist/components/useHoverClose.js +18 -0
- package/dist/components/useHoverClose.js.map +1 -0
- package/dist/components/useLatestRef.js +11 -0
- package/dist/components/useLatestRef.js.map +1 -0
- package/dist/components/usePopoverPosition.js +62 -0
- package/dist/components/usePopoverPosition.js.map +1 -0
- package/dist/components/usePopoverTree.js +34 -0
- package/dist/components/usePopoverTree.js.map +1 -0
- package/dist/index.d.ts +387 -100
- package/dist/index.js +365 -335
- package/dist/index.js.map +1 -1
- package/dist/tailwind.css.js +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { jsxs as r, jsx as a, Fragment as x } from "react/jsx-runtime";
|
|
2
|
+
import m from "./CustomIcons/ArrowUpRightIcon.js";
|
|
3
|
+
import { paintOnChild as c } from "./asChild.js";
|
|
4
|
+
import { BORDER as d } from "./chromeStyles.js";
|
|
5
|
+
import { FOCUS_RING as h } from "./focusRing.js";
|
|
6
|
+
const w = ({
|
|
7
|
+
title: l,
|
|
8
|
+
titleLink: e,
|
|
9
|
+
detail: t,
|
|
10
|
+
children: o,
|
|
11
|
+
"aria-label": s,
|
|
12
|
+
busy: n = !1
|
|
13
|
+
}) => {
|
|
14
|
+
const i = /* @__PURE__ */ a("span", { className: "min-w-0 truncate font-medium text-[var(--malachite-text-primary,rgb(10,10,10))]", children: l }), p = e ? c(
|
|
15
|
+
e,
|
|
16
|
+
`group/usage flex min-w-0 items-center gap-1 no-underline ${h}`,
|
|
17
|
+
void 0,
|
|
18
|
+
/* @__PURE__ */ r(x, { children: [
|
|
19
|
+
i,
|
|
20
|
+
/* @__PURE__ */ a("span", { className: "flex shrink-0 text-[var(--malachite-text-secondary,rgb(123,123,123))] transition-colors group-hover/usage:text-[var(--malachite-text-primary,rgb(10,10,10))]", children: /* @__PURE__ */ a(m, { size: 12 }) })
|
|
21
|
+
] })
|
|
22
|
+
) : i;
|
|
23
|
+
return /* @__PURE__ */ r(
|
|
24
|
+
"div",
|
|
25
|
+
{
|
|
26
|
+
role: "group",
|
|
27
|
+
"aria-label": s,
|
|
28
|
+
"aria-busy": n || void 0,
|
|
29
|
+
className: `flex flex-col gap-4 rounded-[var(--malachite-radius-xxl,14px)] border border-solid ${d} bg-[var(--malachite-card-background,rgb(255,255,255))] px-3 pt-[14px] pb-3 [box-shadow:0_4px_10px_var(--malachite-card-shadow,rgba(0,0,0,0.1))]`,
|
|
30
|
+
children: [
|
|
31
|
+
/* @__PURE__ */ r("div", { className: "flex min-h-[15px] items-end justify-between gap-2 px-[3px] text-[12px] leading-[15px]", children: [
|
|
32
|
+
p,
|
|
33
|
+
t != null && /* @__PURE__ */ a("span", { className: "flex shrink-0 gap-1 text-[11px] whitespace-nowrap text-[var(--malachite-text-primary,rgb(10,10,10))]", children: t })
|
|
34
|
+
] }),
|
|
35
|
+
o
|
|
36
|
+
]
|
|
37
|
+
}
|
|
38
|
+
);
|
|
39
|
+
};
|
|
40
|
+
export {
|
|
41
|
+
w as UsageCard,
|
|
42
|
+
w as default
|
|
43
|
+
};
|
|
44
|
+
//# sourceMappingURL=UsageCard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"UsageCard.js","sources":["../../src/components/UsageCard.tsx"],"sourcesContent":["import type React from 'react';\nimport ArrowUpRightIcon from './CustomIcons/ArrowUpRightIcon';\nimport { paintOnChild } from './asChild';\nimport { BORDER } from './chromeStyles';\nimport { FOCUS_RING } from './focusRing';\n\nexport interface UsageCardProps {\n title: React.ReactNode;\n titleLink?: React.ReactElement;\n detail?: React.ReactNode;\n children: React.ReactNode;\n 'aria-label': string;\n busy?: boolean;\n}\n\nconst UsageCard: React.FC<UsageCardProps> = ({\n title,\n titleLink,\n detail,\n children,\n 'aria-label': ariaLabel,\n busy = false,\n}) => {\n const titleText = (\n <span className=\"min-w-0 truncate font-medium text-[var(--malachite-text-primary,rgb(10,10,10))]\">\n {title}\n </span>\n );\n const heading = titleLink\n ? paintOnChild(\n titleLink,\n `group/usage flex min-w-0 items-center gap-1 no-underline ${FOCUS_RING}`,\n undefined,\n <>\n {titleText}\n <span className=\"flex shrink-0 text-[var(--malachite-text-secondary,rgb(123,123,123))] transition-colors group-hover/usage:text-[var(--malachite-text-primary,rgb(10,10,10))]\">\n <ArrowUpRightIcon size={12} />\n </span>\n </>,\n )\n : titleText;\n return (\n <div\n role=\"group\"\n aria-label={ariaLabel}\n aria-busy={busy || undefined}\n className={`flex flex-col gap-4 rounded-[var(--malachite-radius-xxl,14px)] border border-solid ${BORDER} bg-[var(--malachite-card-background,rgb(255,255,255))] px-3 pt-[14px] pb-3 [box-shadow:0_4px_10px_var(--malachite-card-shadow,rgba(0,0,0,0.1))]`}\n >\n <div className=\"flex min-h-[15px] items-end justify-between gap-2 px-[3px] text-[12px] leading-[15px]\">\n {heading}\n {detail != null && (\n <span className=\"flex shrink-0 gap-1 text-[11px] whitespace-nowrap text-[var(--malachite-text-primary,rgb(10,10,10))]\">\n {detail}\n </span>\n )}\n </div>\n {children}\n </div>\n );\n};\n\nexport { UsageCard };\nexport default UsageCard;\n"],"names":["UsageCard","title","titleLink","detail","children","ariaLabel","busy","titleText","jsx","heading","paintOnChild","FOCUS_RING","jsxs","Fragment","ArrowUpRightIcon","BORDER"],"mappings":";;;;;AAeA,MAAMA,IAAsC,CAAC;AAAA,EACzC,OAAAC;AAAA,EACA,WAAAC;AAAA,EACA,QAAAC;AAAA,EACA,UAAAC;AAAA,EACA,cAAcC;AAAA,EACd,MAAAC,IAAO;AACX,MAAM;AACF,QAAMC,IACF,gBAAAC,EAAC,QAAA,EAAK,WAAU,mFACX,UAAAP,GACL,GAEEQ,IAAUP,IACVQ;AAAA,IACIR;AAAA,IACA,4DAA4DS,CAAU;AAAA,IACtE;AAAA,IACA,gBAAAC,EAAAC,GAAA,EACK,UAAA;AAAA,MAAAN;AAAA,MACD,gBAAAC,EAAC,UAAK,WAAU,gKACZ,4BAACM,GAAA,EAAiB,MAAM,IAAI,EAAA,CAChC;AAAA,IAAA,EAAA,CACJ;AAAA,EAAA,IAEJP;AACN,SACI,gBAAAK;AAAA,IAAC;AAAA,IAAA;AAAA,MACG,MAAK;AAAA,MACL,cAAYP;AAAA,MACZ,aAAWC,KAAQ;AAAA,MACnB,WAAW,sFAAsFS,CAAM;AAAA,MAEvG,UAAA;AAAA,QAAA,gBAAAH,EAAC,OAAA,EAAI,WAAU,yFACV,UAAA;AAAA,UAAAH;AAAA,UACAN,KAAU,QACP,gBAAAK,EAAC,QAAA,EAAK,WAAU,wGACX,UAAAL,EAAA,CACL;AAAA,QAAA,GAER;AAAA,QACCC;AAAA,MAAA;AAAA,IAAA;AAAA,EAAA;AAGb;"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
const l = (o) => o ? [...o.querySelectorAll('button[role="menuitem"]:not(:disabled)')] : [], s = (o, t) => {
|
|
2
|
+
const n = l(o), r = (t === "last" ? n[n.length - 1] : n[0]) ?? o;
|
|
3
|
+
r == null || r.focus({ preventScroll: !0 });
|
|
4
|
+
}, c = (o, t) => ({
|
|
5
|
+
ArrowDown: "first",
|
|
6
|
+
ArrowUp: t.startsWith("top") ? "last" : void 0,
|
|
7
|
+
ArrowRight: t.startsWith("right") ? "first" : void 0
|
|
8
|
+
})[o], g = (o, t, n) => {
|
|
9
|
+
const r = o.indexOf(t), e = r === -1 ? o.length : r;
|
|
10
|
+
return {
|
|
11
|
+
ArrowDown: o[(r + 1) % o.length],
|
|
12
|
+
ArrowUp: o[(e - 1 + o.length) % o.length],
|
|
13
|
+
Home: o[0],
|
|
14
|
+
End: o[o.length - 1]
|
|
15
|
+
}[n];
|
|
16
|
+
};
|
|
17
|
+
export {
|
|
18
|
+
c as edgeForTriggerKey,
|
|
19
|
+
l as enabledItems,
|
|
20
|
+
s as focusEdge,
|
|
21
|
+
g as itemForMenuKey
|
|
22
|
+
};
|
|
23
|
+
//# sourceMappingURL=actionMenuKeyboard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"actionMenuKeyboard.js","sources":["../../src/components/actionMenuKeyboard.ts"],"sourcesContent":["import type { ActionMenuPlacement } from './actionMenuTypes';\n\nexport type MenuEdge = 'first' | 'last';\n\nexport const enabledItems = (menu: HTMLElement | null): HTMLButtonElement[] =>\n menu\n ? [...menu.querySelectorAll<HTMLButtonElement>('button[role=\"menuitem\"]:not(:disabled)')]\n : [];\n\nexport const focusEdge = (menu: HTMLElement | null, edge: MenuEdge): void => {\n const items = enabledItems(menu);\n const target = (edge === 'last' ? items[items.length - 1] : items[0]) ?? menu;\n target?.focus({ preventScroll: true });\n};\n\nexport const edgeForTriggerKey = (\n key: string,\n placement: ActionMenuPlacement,\n): MenuEdge | undefined =>\n ({\n ArrowDown: 'first',\n ArrowUp: placement.startsWith('top') ? 'last' : undefined,\n ArrowRight: placement.startsWith('right') ? 'first' : undefined,\n })[key] as MenuEdge | undefined;\n\nexport const itemForMenuKey = (\n items: HTMLButtonElement[],\n current: EventTarget | null,\n key: string,\n): HTMLButtonElement | undefined => {\n const index = items.indexOf(current as HTMLButtonElement);\n const from = index === -1 ? items.length : index;\n return {\n ArrowDown: items[(index + 1) % items.length],\n ArrowUp: items[(from - 1 + items.length) % items.length],\n Home: items[0],\n End: items[items.length - 1],\n }[key];\n};\n"],"names":["enabledItems","menu","focusEdge","edge","items","target","edgeForTriggerKey","key","placement","itemForMenuKey","current","index","from"],"mappings":"AAIO,MAAMA,IAAe,CAACC,MACzBA,IACM,CAAC,GAAGA,EAAK,iBAAoC,wCAAwC,CAAC,IACtF,CAAA,GAEGC,IAAY,CAACD,GAA0BE,MAAyB;AACzE,QAAMC,IAAQJ,EAAaC,CAAI,GACzBI,KAAUF,MAAS,SAASC,EAAMA,EAAM,SAAS,CAAC,IAAIA,EAAM,CAAC,MAAMH;AACzE,EAAAI,KAAA,QAAAA,EAAQ,MAAM,EAAE,eAAe,GAAA;AACnC,GAEaC,IAAoB,CAC7BC,GACAC,OAEC;AAAA,EACG,WAAW;AAAA,EACX,SAASA,EAAU,WAAW,KAAK,IAAI,SAAS;AAAA,EAChD,YAAYA,EAAU,WAAW,OAAO,IAAI,UAAU;AAC1D,GAAGD,CAAG,GAEGE,IAAiB,CAC1BL,GACAM,GACAH,MACgC;AAChC,QAAMI,IAAQP,EAAM,QAAQM,CAA4B,GAClDE,IAAOD,MAAU,KAAKP,EAAM,SAASO;AAC3C,SAAO;AAAA,IACH,WAAWP,GAAOO,IAAQ,KAAKP,EAAM,MAAM;AAAA,IAC3C,SAASA,GAAOQ,IAAO,IAAIR,EAAM,UAAUA,EAAM,MAAM;AAAA,IACvD,MAAMA,EAAM,CAAC;AAAA,IACb,KAAKA,EAAMA,EAAM,SAAS,CAAC;AAAA,EAAA,EAC7BG,CAAG;AACT;"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { MENU_SPECS as t } from "./floatingMenuGeometry.js";
|
|
2
|
+
import { injectStyleSheet as l } from "./injectStyleSheet.js";
|
|
3
|
+
import { ensureOverlayStyles as d } from "./portalOverlay.js";
|
|
4
|
+
const h = {
|
|
5
|
+
small: { icon: 16, iconBox: "text-[16px] [&_svg]:size-[16px]!" },
|
|
6
|
+
xsmall: { icon: 11, iconBox: "text-[11px] [&_svg]:size-[11px]!" }
|
|
7
|
+
}, g = {
|
|
8
|
+
minWidth: 0,
|
|
9
|
+
overflow: "hidden",
|
|
10
|
+
textOverflow: "ellipsis"
|
|
11
|
+
}, n = {
|
|
12
|
+
padding: "6px 10px",
|
|
13
|
+
fontSize: "var(--malachite-font-size-small, 13px)",
|
|
14
|
+
color: "var(--malachite-text-dimmed, rgb(161, 161, 161))",
|
|
15
|
+
userSelect: "none"
|
|
16
|
+
}, o = {
|
|
17
|
+
...n,
|
|
18
|
+
color: "var(--malachite-text-secondary, rgb(123, 123, 123))",
|
|
19
|
+
overflow: "hidden",
|
|
20
|
+
textOverflow: "ellipsis",
|
|
21
|
+
whiteSpace: "nowrap"
|
|
22
|
+
}, u = {
|
|
23
|
+
display: "inline-flex",
|
|
24
|
+
flexShrink: 0,
|
|
25
|
+
color: "var(--malachite-text-dimmed, rgb(161, 161, 161))"
|
|
26
|
+
}, r = "malachite-actionmenu-item", i = [
|
|
27
|
+
`.${r} { background: transparent; }`,
|
|
28
|
+
`.${r}:not(:disabled):hover { background: var(--malachite-card-dimmed-background, rgb(247, 247, 247)); }`,
|
|
29
|
+
`.${r}:focus-visible { outline: 2px solid var(--malachite-text-primary, rgb(10, 10, 10)); outline-offset: -2px; }`
|
|
30
|
+
], f = () => (d(), l("malachite-actionmenu-item-styles", i)), c = {
|
|
31
|
+
default: "var(--malachite-text-primary, rgb(10, 10, 10))",
|
|
32
|
+
muted: "var(--malachite-text-secondary, rgb(123, 123, 123))"
|
|
33
|
+
}, b = (e, a = "default") => e.type === "text" ? o : e.type === "divider" ? {
|
|
34
|
+
height: 1,
|
|
35
|
+
background: "var(--malachite-card-border, rgb(232, 232, 232))",
|
|
36
|
+
margin: "4px 0"
|
|
37
|
+
} : {
|
|
38
|
+
display: "flex",
|
|
39
|
+
alignItems: "center",
|
|
40
|
+
gap: 8,
|
|
41
|
+
padding: "6px 10px",
|
|
42
|
+
borderRadius: 8,
|
|
43
|
+
fontSize: "var(--malachite-font-size-small, 13px)",
|
|
44
|
+
fontWeight: 400,
|
|
45
|
+
color: e.danger ? "var(--malachite-text-destructive, rgb(237, 53, 54))" : c[a],
|
|
46
|
+
cursor: e.disabled ? "not-allowed" : "pointer",
|
|
47
|
+
opacity: e.disabled ? 0.5 : 1,
|
|
48
|
+
userSelect: "none",
|
|
49
|
+
whiteSpace: "nowrap",
|
|
50
|
+
overflow: "hidden",
|
|
51
|
+
border: "none",
|
|
52
|
+
textAlign: "left",
|
|
53
|
+
width: "100%"
|
|
54
|
+
}, p = (e) => ({
|
|
55
|
+
position: "fixed",
|
|
56
|
+
top: (e == null ? void 0 : e.top) ?? 0,
|
|
57
|
+
left: (e == null ? void 0 : e.left) ?? 0,
|
|
58
|
+
width: e == null ? void 0 : e.width,
|
|
59
|
+
maxHeight: e == null ? void 0 : e.maxHeight,
|
|
60
|
+
overflowY: "auto",
|
|
61
|
+
background: t.options.background,
|
|
62
|
+
border: t.options.border,
|
|
63
|
+
borderRadius: t.options.radius,
|
|
64
|
+
boxShadow: t.options.shadow,
|
|
65
|
+
padding: t.options.padding,
|
|
66
|
+
display: "flex",
|
|
67
|
+
flexDirection: "column",
|
|
68
|
+
gap: 2,
|
|
69
|
+
visibility: e ? "visible" : "hidden",
|
|
70
|
+
opacity: e ? 1 : 0
|
|
71
|
+
});
|
|
72
|
+
export {
|
|
73
|
+
n as EMPTY_ITEM_STYLE,
|
|
74
|
+
r as ITEM_CLASS,
|
|
75
|
+
u as ITEM_ICON_STYLE,
|
|
76
|
+
g as LABEL_STYLE,
|
|
77
|
+
o as TEXT_ITEM_STYLE,
|
|
78
|
+
h as TRIGGER_SIZES,
|
|
79
|
+
f as ensureActionMenuStyles,
|
|
80
|
+
b as itemStyle,
|
|
81
|
+
p as menuStyle
|
|
82
|
+
};
|
|
83
|
+
//# sourceMappingURL=actionMenuStyles.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"actionMenuStyles.js","sources":["../../src/components/actionMenuStyles.ts"],"sourcesContent":["import type React from 'react';\nimport type { ActionMenuItem } from './actionMenuTypes';\nimport { MENU_SPECS, type MenuGeometry } from './floatingMenuGeometry';\nimport { injectStyleSheet } from './injectStyleSheet';\nimport { ensureOverlayStyles } from './portalOverlay';\n\nexport const TRIGGER_SIZES: Record<'small' | 'xsmall', { icon: number; iconBox: string }> = {\n small: { icon: 16, iconBox: 'text-[16px] [&_svg]:size-[16px]!' },\n xsmall: { icon: 11, iconBox: 'text-[11px] [&_svg]:size-[11px]!' },\n};\n\nexport const LABEL_STYLE: React.CSSProperties = {\n minWidth: 0,\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n};\n\nexport const EMPTY_ITEM_STYLE: React.CSSProperties = {\n padding: '6px 10px',\n fontSize: 'var(--malachite-font-size-small, 13px)',\n color: 'var(--malachite-text-dimmed, rgb(161, 161, 161))',\n userSelect: 'none',\n};\n\nexport const TEXT_ITEM_STYLE: React.CSSProperties = {\n ...EMPTY_ITEM_STYLE,\n color: 'var(--malachite-text-secondary, rgb(123, 123, 123))',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n whiteSpace: 'nowrap',\n};\n\nexport const ITEM_ICON_STYLE: React.CSSProperties = {\n display: 'inline-flex',\n flexShrink: 0,\n color: 'var(--malachite-text-dimmed, rgb(161, 161, 161))',\n};\n\nexport const ITEM_CLASS = 'malachite-actionmenu-item';\n\nconst ITEM_RULES = [\n `.${ITEM_CLASS} { background: transparent; }`,\n `.${ITEM_CLASS}:not(:disabled):hover { background: var(--malachite-card-dimmed-background, rgb(247, 247, 247)); }`,\n `.${ITEM_CLASS}:focus-visible { outline: 2px solid var(--malachite-text-primary, rgb(10, 10, 10)); outline-offset: -2px; }`,\n];\n\nexport const ensureActionMenuStyles = (): CSSStyleSheet | null => {\n ensureOverlayStyles();\n return injectStyleSheet('malachite-actionmenu-item-styles', ITEM_RULES);\n};\n\nexport type ActionMenuTone = 'default' | 'muted';\n\nconst ITEM_TEXT: Record<ActionMenuTone, string> = {\n default: 'var(--malachite-text-primary, rgb(10, 10, 10))',\n muted: 'var(--malachite-text-secondary, rgb(123, 123, 123))',\n};\n\nexport const itemStyle = (\n item: ActionMenuItem,\n tone: ActionMenuTone = 'default',\n): React.CSSProperties => {\n if (item.type === 'text') {\n return TEXT_ITEM_STYLE;\n }\n if (item.type === 'divider') {\n return {\n height: 1,\n background: 'var(--malachite-card-border, rgb(232, 232, 232))',\n margin: '4px 0',\n };\n }\n return {\n display: 'flex',\n alignItems: 'center',\n gap: 8,\n padding: '6px 10px',\n borderRadius: 8,\n fontSize: 'var(--malachite-font-size-small, 13px)',\n fontWeight: 400,\n color: item.danger ? 'var(--malachite-text-destructive, rgb(237, 53, 54))' : ITEM_TEXT[tone],\n cursor: item.disabled ? 'not-allowed' : 'pointer',\n opacity: item.disabled ? 0.5 : 1,\n userSelect: 'none',\n whiteSpace: 'nowrap',\n overflow: 'hidden',\n border: 'none',\n textAlign: 'left',\n width: '100%',\n };\n};\n\nexport const menuStyle = (position: MenuGeometry | null): React.CSSProperties => ({\n position: 'fixed',\n top: position?.top ?? 0,\n left: position?.left ?? 0,\n width: position?.width,\n maxHeight: position?.maxHeight,\n overflowY: 'auto',\n background: MENU_SPECS.options.background,\n border: MENU_SPECS.options.border,\n borderRadius: MENU_SPECS.options.radius,\n boxShadow: MENU_SPECS.options.shadow,\n padding: MENU_SPECS.options.padding,\n display: 'flex',\n flexDirection: 'column',\n gap: 2,\n visibility: position ? 'visible' : 'hidden',\n opacity: position ? 1 : 0,\n});\n"],"names":["TRIGGER_SIZES","LABEL_STYLE","EMPTY_ITEM_STYLE","TEXT_ITEM_STYLE","ITEM_ICON_STYLE","ITEM_CLASS","ITEM_RULES","ensureActionMenuStyles","ensureOverlayStyles","injectStyleSheet","ITEM_TEXT","itemStyle","item","tone","menuStyle","position","MENU_SPECS"],"mappings":";;;AAMO,MAAMA,IAA+E;AAAA,EACxF,OAAO,EAAE,MAAM,IAAI,SAAS,mCAAA;AAAA,EAC5B,QAAQ,EAAE,MAAM,IAAI,SAAS,mCAAA;AACjC,GAEaC,IAAmC;AAAA,EAC5C,UAAU;AAAA,EACV,UAAU;AAAA,EACV,cAAc;AAClB,GAEaC,IAAwC;AAAA,EACjD,SAAS;AAAA,EACT,UAAU;AAAA,EACV,OAAO;AAAA,EACP,YAAY;AAChB,GAEaC,IAAuC;AAAA,EAChD,GAAGD;AAAA,EACH,OAAO;AAAA,EACP,UAAU;AAAA,EACV,cAAc;AAAA,EACd,YAAY;AAChB,GAEaE,IAAuC;AAAA,EAChD,SAAS;AAAA,EACT,YAAY;AAAA,EACZ,OAAO;AACX,GAEaC,IAAa,6BAEpBC,IAAa;AAAA,EACf,IAAID,CAAU;AAAA,EACd,IAAIA,CAAU;AAAA,EACd,IAAIA,CAAU;AAClB,GAEaE,IAAyB,OAClCC,EAAA,GACOC,EAAiB,oCAAoCH,CAAU,IAKpEI,IAA4C;AAAA,EAC9C,SAAS;AAAA,EACT,OAAO;AACX,GAEaC,IAAY,CACrBC,GACAC,IAAuB,cAEnBD,EAAK,SAAS,SACPT,IAEPS,EAAK,SAAS,YACP;AAAA,EACH,QAAQ;AAAA,EACR,YAAY;AAAA,EACZ,QAAQ;AAAA,IAGT;AAAA,EACH,SAAS;AAAA,EACT,YAAY;AAAA,EACZ,KAAK;AAAA,EACL,SAAS;AAAA,EACT,cAAc;AAAA,EACd,UAAU;AAAA,EACV,YAAY;AAAA,EACZ,OAAOA,EAAK,SAAS,wDAAwDF,EAAUG,CAAI;AAAA,EAC3F,QAAQD,EAAK,WAAW,gBAAgB;AAAA,EACxC,SAASA,EAAK,WAAW,MAAM;AAAA,EAC/B,YAAY;AAAA,EACZ,YAAY;AAAA,EACZ,UAAU;AAAA,EACV,QAAQ;AAAA,EACR,WAAW;AAAA,EACX,OAAO;AAAA,GAIFE,IAAY,CAACC,OAAwD;AAAA,EAC9E,UAAU;AAAA,EACV,MAAKA,KAAA,gBAAAA,EAAU,QAAO;AAAA,EACtB,OAAMA,KAAA,gBAAAA,EAAU,SAAQ;AAAA,EACxB,OAAOA,KAAA,gBAAAA,EAAU;AAAA,EACjB,WAAWA,KAAA,gBAAAA,EAAU;AAAA,EACrB,WAAW;AAAA,EACX,YAAYC,EAAW,QAAQ;AAAA,EAC/B,QAAQA,EAAW,QAAQ;AAAA,EAC3B,cAAcA,EAAW,QAAQ;AAAA,EACjC,WAAWA,EAAW,QAAQ;AAAA,EAC9B,SAASA,EAAW,QAAQ;AAAA,EAC5B,SAAS;AAAA,EACT,eAAe;AAAA,EACf,KAAK;AAAA,EACL,YAAYD,IAAW,YAAY;AAAA,EACnC,SAASA,IAAW,IAAI;AAC5B;"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ariaState.js","sources":["../../src/components/ariaState.ts"],"sourcesContent":["export const isExpanded = (value: unknown): boolean => String(value) === 'true';\n"],"names":["isExpanded","value"],"mappings":"AAAO,MAAMA,IAAa,CAACC,MAA4B,OAAOA,CAAK,MAAM;"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chromeStyles.js","sources":["../../src/components/chromeStyles.ts"],"sourcesContent":["export const BORDER = 'border-[var(--malachite-card-border,rgb(232,232,232))]';\n\nexport const NO_SCROLLBAR = '[scrollbar-width:none] [&::-webkit-scrollbar]:hidden';\n"],"names":["BORDER","NO_SCROLLBAR"],"mappings":"AAAO,MAAMA,IAAS,0DAETC,IAAe;"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { version as u } from "react";
|
|
2
|
+
const e = (n, o) => {
|
|
3
|
+
if (typeof n == "function")
|
|
4
|
+
return n(o);
|
|
5
|
+
n && (n.current = o);
|
|
6
|
+
}, i = (...n) => (o) => {
|
|
7
|
+
const t = n.map((c) => e(c, o));
|
|
8
|
+
if (t.some((c) => typeof c == "function"))
|
|
9
|
+
return () => {
|
|
10
|
+
n.forEach((c, r) => {
|
|
11
|
+
const f = t[r];
|
|
12
|
+
if (typeof f == "function") {
|
|
13
|
+
f();
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
e(c, null);
|
|
17
|
+
});
|
|
18
|
+
};
|
|
19
|
+
}, p = Number(u.split(".")[0]) >= 19, a = (n) => p ? n.props.ref : n.ref, l = (n, o) => (t) => {
|
|
20
|
+
n == null || n(t), t.defaultPrevented || o(t);
|
|
21
|
+
};
|
|
22
|
+
export {
|
|
23
|
+
l as chainHandler,
|
|
24
|
+
i as composeRefs,
|
|
25
|
+
a as elementRef
|
|
26
|
+
};
|
|
27
|
+
//# sourceMappingURL=composeRefs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"composeRefs.js","sources":["../../src/components/composeRefs.ts"],"sourcesContent":["import type React from 'react';\nimport { version } from 'react';\n\ntype AnyRef<T> = React.Ref<T> | undefined;\n\nconst setRef = <T>(ref: AnyRef<T>, node: T | null): void | (() => void) => {\n if (typeof ref === 'function') {\n return ref(node);\n }\n if (ref) {\n (ref as React.MutableRefObject<T | null>).current = node;\n }\n};\n\nexport const composeRefs =\n <T>(...refs: AnyRef<T>[]) =>\n (node: T | null): void | (() => void) => {\n const cleanups = refs.map((ref) => setRef(ref, node));\n if (!cleanups.some((cleanup) => typeof cleanup === 'function')) {\n return;\n }\n return () => {\n refs.forEach((ref, index) => {\n const cleanup = cleanups[index];\n if (typeof cleanup === 'function') {\n cleanup();\n return;\n }\n setRef(ref, null);\n });\n };\n };\n\nconst REF_IN_PROPS = Number(version.split('.')[0]) >= 19;\n\nexport const elementRef = <T>(element: React.ReactElement): AnyRef<T> =>\n (REF_IN_PROPS\n ? (element.props as { ref?: React.Ref<T> }).ref\n : (element as unknown as { ref?: React.Ref<T> }).ref) as AnyRef<T>;\n\nexport const chainHandler =\n <E extends React.SyntheticEvent>(\n theirs: ((event: E) => void) | undefined,\n ours: (event: E) => void,\n ) =>\n (event: E): void => {\n theirs?.(event);\n if (!event.defaultPrevented) {\n ours(event);\n }\n };\n"],"names":["setRef","ref","node","composeRefs","refs","cleanups","cleanup","index","REF_IN_PROPS","version","elementRef","element","chainHandler","theirs","ours","event"],"mappings":";AAKA,MAAMA,IAAS,CAAIC,GAAgBC,MAAwC;AACvE,MAAI,OAAOD,KAAQ;AACf,WAAOA,EAAIC,CAAI;AAEnB,EAAID,MACCA,EAAyC,UAAUC;AAE5D,GAEaC,IACT,IAAOC,MACP,CAACF,MAAwC;AACrC,QAAMG,IAAWD,EAAK,IAAI,CAACH,MAAQD,EAAOC,GAAKC,CAAI,CAAC;AACpD,MAAKG,EAAS,KAAK,CAACC,MAAY,OAAOA,KAAY,UAAU;AAG7D,WAAO,MAAM;AACT,MAAAF,EAAK,QAAQ,CAACH,GAAKM,MAAU;AACzB,cAAMD,IAAUD,EAASE,CAAK;AAC9B,YAAI,OAAOD,KAAY,YAAY;AAC/B,UAAAA,EAAA;AACA;AAAA,QACJ;AACA,QAAAN,EAAOC,GAAK,IAAI;AAAA,MACpB,CAAC;AAAA,IACL;AACJ,GAEEO,IAAe,OAAOC,EAAQ,MAAM,GAAG,EAAE,CAAC,CAAC,KAAK,IAEzCC,IAAa,CAAIC,MACzBH,IACMG,EAAQ,MAAiC,MACzCA,EAA8C,KAE5CC,IACT,CACIC,GACAC,MAEJ,CAACC,MAAmB;AAChB,EAAAF,KAAA,QAAAA,EAASE,IACJA,EAAM,oBACPD,EAAKC,CAAK;AAElB;"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const
|
|
1
|
+
const S = 8, y = 160, F = 160, $ = 180, L = {
|
|
2
2
|
standard: {
|
|
3
3
|
width: "trigger",
|
|
4
4
|
minWidth: 160,
|
|
@@ -16,56 +16,95 @@ const U = 8, X = 160, O = 180, D = {
|
|
|
16
16
|
maxWidth: 280,
|
|
17
17
|
align: "right",
|
|
18
18
|
gap: 14,
|
|
19
|
-
radius:
|
|
19
|
+
radius: 12,
|
|
20
20
|
padding: 8,
|
|
21
21
|
shadow: "0px 4px 10px 0px rgba(0, 0, 0, 0.1)",
|
|
22
22
|
background: "var(--malachite-card-background, rgb(255, 255, 255))",
|
|
23
23
|
border: "1px solid var(--malachite-card-border, rgb(232, 232, 232))"
|
|
24
24
|
}
|
|
25
|
-
},
|
|
25
|
+
}, V = 400, X = ({
|
|
26
26
|
triggerRect: t,
|
|
27
|
-
preset:
|
|
27
|
+
preset: s,
|
|
28
28
|
minWidth: o,
|
|
29
|
-
contentWidth:
|
|
30
|
-
menuHeight:
|
|
31
|
-
viewportWidth:
|
|
32
|
-
viewportHeight:
|
|
29
|
+
contentWidth: i,
|
|
30
|
+
menuHeight: a,
|
|
31
|
+
viewportWidth: e,
|
|
32
|
+
viewportHeight: h
|
|
33
33
|
}) => {
|
|
34
|
-
const n =
|
|
35
|
-
let
|
|
36
|
-
I
|
|
37
|
-
const
|
|
34
|
+
const n = L[s], r = e - 16, m = Math.max(n.minWidth, o ?? 0), P = Math.min(r, Math.max(n.maxWidth ?? r, m)), u = n.width === "trigger" ? t.width : i ?? P, c = Math.max(0, Math.min(Math.max(u, m), P)), I = n.align === "right" ? t.right - c : t.left, d = n.align === "right" ? t.left : t.right - c, N = 8, b = e - c - 8, E = (f) => f >= N && f <= b;
|
|
35
|
+
let _;
|
|
36
|
+
E(I) ? _ = I : E(d) ? _ = d : _ = Math.max(N, Math.min(I, b));
|
|
37
|
+
const l = h - t.bottom - n.gap - 8, T = t.top - n.gap - 8, M = a > 0 && a > l && T > l, x = Math.max(0, Math.min(V, M ? T : l)), p = a > 0 ? Math.min(a, x) : 0;
|
|
38
38
|
return { top: Math.max(
|
|
39
39
|
8,
|
|
40
|
-
|
|
41
|
-
), left:
|
|
42
|
-
},
|
|
40
|
+
M ? t.top - n.gap - p : t.bottom + n.gap
|
|
41
|
+
), left: _, width: c, maxHeight: x, openUp: M };
|
|
42
|
+
}, k = (t) => {
|
|
43
43
|
if (!t || t.style.width !== "")
|
|
44
44
|
return;
|
|
45
|
-
const
|
|
45
|
+
const s = t.querySelectorAll('button, [role="menuitem"]');
|
|
46
46
|
let o = 0;
|
|
47
|
-
if (
|
|
48
|
-
o = Math.max(o,
|
|
47
|
+
if (s.forEach((h) => {
|
|
48
|
+
o = Math.max(o, h.scrollWidth);
|
|
49
49
|
}), o === 0)
|
|
50
50
|
return;
|
|
51
|
-
const
|
|
52
|
-
return Math.ceil(o +
|
|
53
|
-
},
|
|
51
|
+
const i = getComputedStyle(t), a = (h) => Number.parseFloat(h) || 0, e = a(i.paddingLeft) + a(i.paddingRight) + a(i.borderLeftWidth) + a(i.borderRightWidth);
|
|
52
|
+
return Math.ceil(o + e);
|
|
53
|
+
}, B = (t, s) => {
|
|
54
54
|
if (!t)
|
|
55
|
-
return
|
|
56
|
-
const o = `${
|
|
57
|
-
return t.style.width !== o && (t.style.width = o),
|
|
58
|
-
},
|
|
55
|
+
return X({ ...s, menuHeight: 0 });
|
|
56
|
+
const o = `${X({ ...s, menuHeight: 0 }).width}px`;
|
|
57
|
+
return t.style.width !== o && (t.style.width = o), X({ ...s, menuHeight: t.offsetHeight });
|
|
58
|
+
}, C = (t, s, o) => t.bottom < 0 || t.top > o || t.right < 0 || t.left > s, v = (t) => `${t.top},${t.left},${t.width},${t.height}`, g = (t, s, o) => t === "end" ? s.right - o : t === "center" ? (s.left + s.right - o) / 2 : s.left, q = (t) => t.scrollHeight + t.offsetHeight - t.clientHeight, j = { small: 8, medium: 12, large: 14 }, D = (t, s, o) => Math.max(8, Math.min(t, o - s - 8)), z = ({
|
|
59
|
+
triggerRect: t,
|
|
60
|
+
placement: s,
|
|
61
|
+
panelWidth: o,
|
|
62
|
+
panelHeight: i,
|
|
63
|
+
gap: a,
|
|
64
|
+
viewportWidth: e,
|
|
65
|
+
viewportHeight: h
|
|
66
|
+
}) => {
|
|
67
|
+
const [n, r] = s.split("-"), m = 8;
|
|
68
|
+
if (n === "top" || n === "bottom") {
|
|
69
|
+
const M = h - t.bottom - a - m, x = t.top - a - m, p = n === "bottom" ? M : x, A = n === "bottom" ? x : M, f = i > p && A > p ? n === "bottom" ? "top" : "bottom" : n, G = Math.max(m, t.bottom + a), O = Math.min(h - m, t.top - a), U = Math.max(
|
|
70
|
+
0,
|
|
71
|
+
f === "bottom" ? h - G - m : O - m
|
|
72
|
+
), W = Math.min(i, U), w = f === "bottom" ? G : Math.max(m, O - W), H = D(
|
|
73
|
+
g(r, t, o),
|
|
74
|
+
o,
|
|
75
|
+
e
|
|
76
|
+
);
|
|
77
|
+
return { top: w, left: H, maxHeight: U, side: f };
|
|
78
|
+
}
|
|
79
|
+
const P = e - t.right - a - m, u = t.left - a - m, c = n === "right" ? P : u, I = n === "right" ? u : P, d = o > c && I > c ? n === "right" ? "left" : "right" : n, N = d === "right" ? t.right + a : t.left - a - o, b = Math.max(m, t.top), E = h - b - m;
|
|
80
|
+
if (r === "start" && E >= Math.min(i, 160))
|
|
81
|
+
return {
|
|
82
|
+
top: b,
|
|
83
|
+
left: D(N, o, e),
|
|
84
|
+
maxHeight: E,
|
|
85
|
+
side: d
|
|
86
|
+
};
|
|
87
|
+
const _ = Math.max(0, h - m * 2), l = Math.min(i, _);
|
|
88
|
+
return { top: D(
|
|
89
|
+
r === "end" ? t.bottom - l : t.top,
|
|
90
|
+
l,
|
|
91
|
+
h
|
|
92
|
+
), left: D(N, o, e), maxHeight: _, side: d };
|
|
93
|
+
};
|
|
59
94
|
export {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
y as
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
95
|
+
j as FLOATING_OFFSET_PX,
|
|
96
|
+
V as MENU_MAX_HEIGHT_PX,
|
|
97
|
+
F as MENU_MIN_WIDTH_PX,
|
|
98
|
+
$ as MENU_OPTIONS_MIN_WIDTH_PX,
|
|
99
|
+
L as MENU_SPECS,
|
|
100
|
+
S as MENU_VIEWPORT_PADDING_PX,
|
|
101
|
+
y as SIDE_PANEL_MIN_HEIGHT_PX,
|
|
102
|
+
z as computeFloatingGeometry,
|
|
103
|
+
X as computeMenuGeometry,
|
|
104
|
+
q as fullPanelHeight,
|
|
105
|
+
C as isTriggerOffscreen,
|
|
106
|
+
k as measureContentWidth,
|
|
107
|
+
B as measureMenuGeometry,
|
|
108
|
+
v as rectSignature
|
|
70
109
|
};
|
|
71
110
|
//# sourceMappingURL=floatingMenuGeometry.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"floatingMenuGeometry.js","sources":["../../src/components/floatingMenuGeometry.ts"],"sourcesContent":["// Figma: `Dropdown Component - Item List` fills its trigger, 10px below — 444 (68:2789),\n// 200 (2343:2201), 414 (1437:2986). `Dropdown Component - Options List` (213:4320) is a\n// fixed 280px, 14px below. Figma declares no trigger width and no floor; 160 and 400 are ours.\n\nexport const MENU_VIEWPORT_PADDING_PX = 8;\n\nexport const MENU_MIN_WIDTH_PX = 160;\n\n// Figma draws the Options menu at a flat 280. Taken as a ceiling rather than a width: a kebab\n// of four short verbs then carries 130px of air, and every kebab in a consuming app is the same\n// width whatever it holds. 180 is the floor those menus sized themselves to before this preset.\nexport const MENU_OPTIONS_MIN_WIDTH_PX = 180;\n\nexport type MenuPreset = 'standard' | 'options';\n\nexport interface MenuSpec {\n /** `'trigger'` fills the control that opened it; `'content'` sizes to the widest item. */\n width: 'trigger' | 'content';\n /** Where this preset stops shrinking. */\n minWidth: number;\n /** Where a content-sized one stops growing. A caller's `minWidth` still raises past it. */\n maxWidth?: number;\n align: 'left' | 'right';\n gap: number;\n radius: number;\n padding: number;\n shadow: string;\n background: string;\n border: string;\n}\n\nexport const MENU_SPECS: Record<MenuPreset, MenuSpec> = {\n standard: {\n width: 'trigger',\n minWidth: MENU_MIN_WIDTH_PX,\n align: 'left',\n gap: 10,\n radius: 12,\n padding: 4,\n shadow: '0px 2px 5px 0px rgba(0, 0, 0, 0.1)',\n background: 'var(--malachite-card-background, rgb(255, 255, 255))',\n border: '1px solid var(--malachite-card-border, rgb(232, 232, 232))',\n },\n options: {\n width: 'content',\n minWidth: MENU_OPTIONS_MIN_WIDTH_PX,\n maxWidth: 280,\n align: 'right',\n gap: 14,\n radius: 10,\n padding: 8,\n shadow: '0px 4px 10px 0px rgba(0, 0, 0, 0.1)',\n background: 'var(--malachite-card-background, rgb(255, 255, 255))',\n border: '1px solid var(--malachite-card-border, rgb(232, 232, 232))',\n },\n};\n\nexport const MENU_MAX_HEIGHT_PX = 400;\n\nexport interface MenuGeometry {\n top: number;\n left: number;\n width: number;\n maxHeight: number;\n openUp: boolean;\n}\n\nexport interface MenuGeometryInput {\n triggerRect: Pick<DOMRect, 'top' | 'left' | 'right' | 'bottom' | 'width'>;\n preset: MenuPreset;\n minWidth?: number;\n /** The widest item, for a content-sized preset. Absent until the menu has been laid out. */\n contentWidth?: number;\n menuHeight: number;\n viewportWidth: number;\n viewportHeight: number;\n}\n\nexport const computeMenuGeometry = ({\n triggerRect,\n preset,\n minWidth,\n contentWidth,\n menuHeight,\n viewportWidth,\n viewportHeight,\n}: MenuGeometryInput): MenuGeometry => {\n const spec = MENU_SPECS[preset];\n const available = viewportWidth - MENU_VIEWPORT_PADDING_PX * 2;\n\n const floor = Math.max(spec.minWidth, minWidth ?? 0);\n const ceiling = Math.min(available, Math.max(spec.maxWidth ?? available, floor));\n const natural = spec.width === 'trigger' ? triggerRect.width : (contentWidth ?? ceiling);\n const width = Math.max(0, Math.min(Math.max(natural, floor), ceiling));\n\n const preferredLeft = spec.align === 'right' ? triggerRect.right - width : triggerRect.left;\n const oppositeLeft = spec.align === 'right' ? triggerRect.left : triggerRect.right - width;\n const minLeft = MENU_VIEWPORT_PADDING_PX;\n const maxLeft = viewportWidth - width - MENU_VIEWPORT_PADDING_PX;\n const fits = (candidate: number) => candidate >= minLeft && candidate <= maxLeft;\n\n let left: number;\n if (fits(preferredLeft)) {\n left = preferredLeft;\n } else if (fits(oppositeLeft)) {\n left = oppositeLeft;\n } else {\n left = Math.max(minLeft, Math.min(preferredLeft, maxLeft));\n }\n\n // Which side, then how much of it. \"More room above\" is not \"enough room above\", so the\n // height is clamped to whichever side won. Clamping `top` instead keeps the menu on screen\n // by painting it over its own trigger, which also swallows the click that closes it.\n const roomBelow = viewportHeight - triggerRect.bottom - spec.gap - MENU_VIEWPORT_PADDING_PX;\n const roomAbove = triggerRect.top - spec.gap - MENU_VIEWPORT_PADDING_PX;\n const openUp = menuHeight > 0 && menuHeight > roomBelow && roomAbove > roomBelow;\n\n const maxHeight = Math.max(0, Math.min(MENU_MAX_HEIGHT_PX, openUp ? roomAbove : roomBelow));\n const height = menuHeight > 0 ? Math.min(menuHeight, maxHeight) : 0;\n\n const top = Math.max(\n MENU_VIEWPORT_PADDING_PX,\n openUp ? triggerRect.top - spec.gap - height : triggerRect.bottom + spec.gap,\n );\n\n return { top, left, width, maxHeight, openUp };\n};\n\n// The height decides whether the menu opens upwards, and it is only meaningful at the width the\n// menu will actually have — so on the pass that first sizes the panel the width is written\n// before the height is read. The guard is what keeps that out of the steady state: every later\n// call finds the width already set and writes nothing, so a `ResizeObserver` callback does not\n// resize what it is observing (Chrome's \"undelivered notifications\" error) and a per-frame\n// reposition does not force a reflow. After the first pass this is a pure read.\n// A content-sized preset needs the width the panel chose for itself, which is only readable\n// while no width has been written to it — i.e. on the pass that opens the menu. The caller keeps\n// it from there, because after the first write `offsetWidth` reports what we wrote, not what the\n// items want.\nexport const measureContentWidth = (panel: HTMLElement | null): number | undefined => {\n if (!panel || panel.style.width !== '') {\n return undefined;\n }\n\n // The rows, not the panel. Each label carries `min-width: 0` and `overflow: hidden` so it\n // can ellipsise, which also means it contributes nothing to the panel's intrinsic width —\n // measuring the panel itself returns a box the size of its own padding. `scrollWidth` on a\n // clipped row is the width the row wanted.\n const rows = panel.querySelectorAll<HTMLElement>('button, [role=\"menuitem\"]');\n let widest = 0;\n rows.forEach((row) => {\n widest = Math.max(widest, row.scrollWidth);\n });\n\n if (widest === 0) {\n return undefined;\n }\n\n // The panel is border-box, so the width we hand back has to carry its own padding and rule.\n // Each read is defaulted: a computed style can come back empty — jsdom never expands the\n // `border` shorthand — and one NaN here would make the whole width NaN, which CSS drops,\n // leaving the panel unsized with no sign of why.\n const styles = getComputedStyle(panel);\n const edge = (value: string) => Number.parseFloat(value) || 0;\n const chrome =\n edge(styles.paddingLeft) +\n edge(styles.paddingRight) +\n edge(styles.borderLeftWidth) +\n edge(styles.borderRightWidth);\n\n return Math.ceil(widest + chrome);\n};\n\nexport const measureMenuGeometry = (\n panel: HTMLElement | null,\n input: Omit<MenuGeometryInput, 'menuHeight'>,\n): MenuGeometry => {\n if (!panel) {\n return computeMenuGeometry({ ...input, menuHeight: 0 });\n }\n\n const cssWidth = `${computeMenuGeometry({ ...input, menuHeight: 0 }).width}px`;\n if (panel.style.width !== cssWidth) {\n panel.style.width = cssWidth;\n }\n\n return computeMenuGeometry({ ...input, menuHeight: panel.offsetHeight });\n};\n\nexport const isTriggerOffscreen = (\n rect: Pick<DOMRect, 'top' | 'left' | 'right' | 'bottom'>,\n viewportWidth: number,\n viewportHeight: number,\n): boolean =>\n rect.bottom < 0 || rect.top > viewportHeight || rect.right < 0 || rect.left > viewportWidth;\n\nexport const rectSignature = (rect: Pick<DOMRect, 'top' | 'left' | 'width' | 'height'>): string =>\n `${rect.top},${rect.left},${rect.width},${rect.height}`;\n"],"names":["MENU_VIEWPORT_PADDING_PX","MENU_MIN_WIDTH_PX","MENU_OPTIONS_MIN_WIDTH_PX","MENU_SPECS","MENU_MAX_HEIGHT_PX","computeMenuGeometry","triggerRect","preset","minWidth","contentWidth","menuHeight","viewportWidth","viewportHeight","spec","available","floor","ceiling","natural","width","preferredLeft","oppositeLeft","minLeft","maxLeft","fits","candidate","left","roomBelow","roomAbove","openUp","maxHeight","height","measureContentWidth","panel","rows","widest","row","styles","edge","value","chrome","measureMenuGeometry","input","cssWidth","isTriggerOffscreen","rect","rectSignature"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"floatingMenuGeometry.js","sources":["../../src/components/floatingMenuGeometry.ts"],"sourcesContent":["// Figma: `Dropdown Component - Item List` fills its trigger, 10px below — 444 (68:2789),\n// 200 (2343:2201), 414 (1437:2986). `Dropdown Component - Options List` (213:4320) is a\n// fixed 280px, 14px below. Figma declares no trigger width and no floor; 160 and 400 are ours.\n\nexport const MENU_VIEWPORT_PADDING_PX = 8;\n\nexport const SIDE_PANEL_MIN_HEIGHT_PX = 160;\n\nexport const MENU_MIN_WIDTH_PX = 160;\n\n// Figma draws the Options menu at a flat 280. Taken as a ceiling rather than a width: a kebab\n// of four short verbs then carries 130px of air, and every kebab in a consuming app is the same\n// width whatever it holds. 180 is the floor those menus sized themselves to before this preset.\nexport const MENU_OPTIONS_MIN_WIDTH_PX = 180;\n\nexport type MenuPreset = 'standard' | 'options';\n\nexport interface MenuSpec {\n /** `'trigger'` fills the control that opened it; `'content'` sizes to the widest item. */\n width: 'trigger' | 'content';\n /** Where this preset stops shrinking. */\n minWidth: number;\n /** Where a content-sized one stops growing. A caller's `minWidth` still raises past it. */\n maxWidth?: number;\n align: 'left' | 'right';\n gap: number;\n radius: number;\n padding: number;\n shadow: string;\n background: string;\n border: string;\n}\n\nexport const MENU_SPECS: Record<MenuPreset, MenuSpec> = {\n standard: {\n width: 'trigger',\n minWidth: MENU_MIN_WIDTH_PX,\n align: 'left',\n gap: 10,\n radius: 12,\n padding: 4,\n shadow: '0px 2px 5px 0px rgba(0, 0, 0, 0.1)',\n background: 'var(--malachite-card-background, rgb(255, 255, 255))',\n border: '1px solid var(--malachite-card-border, rgb(232, 232, 232))',\n },\n options: {\n width: 'content',\n minWidth: MENU_OPTIONS_MIN_WIDTH_PX,\n maxWidth: 280,\n align: 'right',\n gap: 14,\n radius: 12,\n padding: 8,\n shadow: '0px 4px 10px 0px rgba(0, 0, 0, 0.1)',\n background: 'var(--malachite-card-background, rgb(255, 255, 255))',\n border: '1px solid var(--malachite-card-border, rgb(232, 232, 232))',\n },\n};\n\nexport const MENU_MAX_HEIGHT_PX = 400;\n\nexport interface MenuGeometry {\n top: number;\n left: number;\n width: number;\n maxHeight: number;\n openUp: boolean;\n}\n\nexport interface MenuGeometryInput {\n triggerRect: Pick<DOMRect, 'top' | 'left' | 'right' | 'bottom' | 'width'>;\n preset: MenuPreset;\n minWidth?: number;\n /** The widest item, for a content-sized preset. Absent until the menu has been laid out. */\n contentWidth?: number;\n menuHeight: number;\n viewportWidth: number;\n viewportHeight: number;\n}\n\nexport const computeMenuGeometry = ({\n triggerRect,\n preset,\n minWidth,\n contentWidth,\n menuHeight,\n viewportWidth,\n viewportHeight,\n}: MenuGeometryInput): MenuGeometry => {\n const spec = MENU_SPECS[preset];\n const available = viewportWidth - MENU_VIEWPORT_PADDING_PX * 2;\n\n const floor = Math.max(spec.minWidth, minWidth ?? 0);\n const ceiling = Math.min(available, Math.max(spec.maxWidth ?? available, floor));\n const natural = spec.width === 'trigger' ? triggerRect.width : (contentWidth ?? ceiling);\n const width = Math.max(0, Math.min(Math.max(natural, floor), ceiling));\n\n const preferredLeft = spec.align === 'right' ? triggerRect.right - width : triggerRect.left;\n const oppositeLeft = spec.align === 'right' ? triggerRect.left : triggerRect.right - width;\n const minLeft = MENU_VIEWPORT_PADDING_PX;\n const maxLeft = viewportWidth - width - MENU_VIEWPORT_PADDING_PX;\n const fits = (candidate: number) => candidate >= minLeft && candidate <= maxLeft;\n\n let left: number;\n if (fits(preferredLeft)) {\n left = preferredLeft;\n } else if (fits(oppositeLeft)) {\n left = oppositeLeft;\n } else {\n left = Math.max(minLeft, Math.min(preferredLeft, maxLeft));\n }\n\n // Which side, then how much of it. \"More room above\" is not \"enough room above\", so the\n // height is clamped to whichever side won. Clamping `top` instead keeps the menu on screen\n // by painting it over its own trigger, which also swallows the click that closes it.\n const roomBelow = viewportHeight - triggerRect.bottom - spec.gap - MENU_VIEWPORT_PADDING_PX;\n const roomAbove = triggerRect.top - spec.gap - MENU_VIEWPORT_PADDING_PX;\n const openUp = menuHeight > 0 && menuHeight > roomBelow && roomAbove > roomBelow;\n\n const maxHeight = Math.max(0, Math.min(MENU_MAX_HEIGHT_PX, openUp ? roomAbove : roomBelow));\n const height = menuHeight > 0 ? Math.min(menuHeight, maxHeight) : 0;\n\n const top = Math.max(\n MENU_VIEWPORT_PADDING_PX,\n openUp ? triggerRect.top - spec.gap - height : triggerRect.bottom + spec.gap,\n );\n\n return { top, left, width, maxHeight, openUp };\n};\n\n// The height decides whether the menu opens upwards, and it is only meaningful at the width the\n// menu will actually have — so on the pass that first sizes the panel the width is written\n// before the height is read. The guard is what keeps that out of the steady state: every later\n// call finds the width already set and writes nothing, so a `ResizeObserver` callback does not\n// resize what it is observing (Chrome's \"undelivered notifications\" error) and a per-frame\n// reposition does not force a reflow. After the first pass this is a pure read.\n// A content-sized preset needs the width the panel chose for itself, which is only readable\n// while no width has been written to it — i.e. on the pass that opens the menu. The caller keeps\n// it from there, because after the first write `offsetWidth` reports what we wrote, not what the\n// items want.\nexport const measureContentWidth = (panel: HTMLElement | null): number | undefined => {\n if (!panel || panel.style.width !== '') {\n return undefined;\n }\n\n // The rows, not the panel. Each label carries `min-width: 0` and `overflow: hidden` so it\n // can ellipsise, which also means it contributes nothing to the panel's intrinsic width —\n // measuring the panel itself returns a box the size of its own padding. `scrollWidth` on a\n // clipped row is the width the row wanted.\n const rows = panel.querySelectorAll<HTMLElement>('button, [role=\"menuitem\"]');\n let widest = 0;\n rows.forEach((row) => {\n widest = Math.max(widest, row.scrollWidth);\n });\n\n if (widest === 0) {\n return undefined;\n }\n\n // The panel is border-box, so the width we hand back has to carry its own padding and rule.\n // Each read is defaulted: a computed style can come back empty — jsdom never expands the\n // `border` shorthand — and one NaN here would make the whole width NaN, which CSS drops,\n // leaving the panel unsized with no sign of why.\n const styles = getComputedStyle(panel);\n const edge = (value: string) => Number.parseFloat(value) || 0;\n const chrome =\n edge(styles.paddingLeft) +\n edge(styles.paddingRight) +\n edge(styles.borderLeftWidth) +\n edge(styles.borderRightWidth);\n\n return Math.ceil(widest + chrome);\n};\n\nexport const measureMenuGeometry = (\n panel: HTMLElement | null,\n input: Omit<MenuGeometryInput, 'menuHeight'>,\n): MenuGeometry => {\n if (!panel) {\n return computeMenuGeometry({ ...input, menuHeight: 0 });\n }\n\n const cssWidth = `${computeMenuGeometry({ ...input, menuHeight: 0 }).width}px`;\n if (panel.style.width !== cssWidth) {\n panel.style.width = cssWidth;\n }\n\n return computeMenuGeometry({ ...input, menuHeight: panel.offsetHeight });\n};\n\nexport const isTriggerOffscreen = (\n rect: Pick<DOMRect, 'top' | 'left' | 'right' | 'bottom'>,\n viewportWidth: number,\n viewportHeight: number,\n): boolean =>\n rect.bottom < 0 || rect.top > viewportHeight || rect.right < 0 || rect.left > viewportWidth;\n\nexport const rectSignature = (rect: Pick<DOMRect, 'top' | 'left' | 'width' | 'height'>): string =>\n `${rect.top},${rect.left},${rect.width},${rect.height}`;\n\nexport type FloatingPlacement =\n | 'bottom-start'\n | 'bottom-end'\n | 'top-start'\n | 'top-center'\n | 'right-start'\n | 'right-end'\n | 'left-start';\n\nconst alignedLeft = (\n align: 'start' | 'end' | 'center',\n triggerRect: Pick<DOMRect, 'left' | 'right'>,\n panelWidth: number,\n): number => {\n if (align === 'end') {\n return triggerRect.right - panelWidth;\n }\n if (align === 'center') {\n return (triggerRect.left + triggerRect.right - panelWidth) / 2;\n }\n return triggerRect.left;\n};\n\nexport const fullPanelHeight = (panel: HTMLElement): number =>\n panel.scrollHeight + panel.offsetHeight - panel.clientHeight;\n\nexport const FLOATING_OFFSET_PX = { small: 8, medium: 12, large: 14 } as const;\n\nexport type FloatingOffset = keyof typeof FLOATING_OFFSET_PX;\n\nexport interface FloatingGeometryInput {\n triggerRect: Pick<DOMRect, 'top' | 'left' | 'right' | 'bottom'>;\n placement: FloatingPlacement;\n panelWidth: number;\n panelHeight: number;\n gap: number;\n viewportWidth: number;\n viewportHeight: number;\n}\n\nexport interface FloatingGeometry {\n top: number;\n left: number;\n maxHeight: number;\n side: 'top' | 'bottom' | 'left' | 'right';\n}\n\nconst clampToViewport = (value: number, size: number, viewport: number): number =>\n Math.max(MENU_VIEWPORT_PADDING_PX, Math.min(value, viewport - size - MENU_VIEWPORT_PADDING_PX));\n\nexport const computeFloatingGeometry = ({\n triggerRect,\n placement,\n panelWidth,\n panelHeight,\n gap,\n viewportWidth,\n viewportHeight,\n}: FloatingGeometryInput): FloatingGeometry => {\n const [preferred, align] = placement.split('-') as [\n FloatingGeometry['side'],\n 'start' | 'end' | 'center',\n ];\n const pad = MENU_VIEWPORT_PADDING_PX;\n\n if (preferred === 'top' || preferred === 'bottom') {\n const roomBelow = viewportHeight - triggerRect.bottom - gap - pad;\n const roomAbove = triggerRect.top - gap - pad;\n const wanted = preferred === 'bottom' ? roomBelow : roomAbove;\n const opposite = preferred === 'bottom' ? roomAbove : roomBelow;\n const side: FloatingGeometry['side'] =\n panelHeight > wanted && opposite > wanted\n ? preferred === 'bottom'\n ? 'top'\n : 'bottom'\n : preferred;\n const belowTop = Math.max(pad, triggerRect.bottom + gap);\n const aboveBottom = Math.min(viewportHeight - pad, triggerRect.top - gap);\n const maxHeight = Math.max(\n 0,\n side === 'bottom' ? viewportHeight - belowTop - pad : aboveBottom - pad,\n );\n const height = Math.min(panelHeight, maxHeight);\n const top = side === 'bottom' ? belowTop : Math.max(pad, aboveBottom - height);\n const left = clampToViewport(\n alignedLeft(align, triggerRect, panelWidth),\n panelWidth,\n viewportWidth,\n );\n return { top, left, maxHeight, side };\n }\n\n const roomRight = viewportWidth - triggerRect.right - gap - pad;\n const roomLeft = triggerRect.left - gap - pad;\n const wanted = preferred === 'right' ? roomRight : roomLeft;\n const opposite = preferred === 'right' ? roomLeft : roomRight;\n const side: FloatingGeometry['side'] =\n panelWidth > wanted && opposite > wanted\n ? preferred === 'right'\n ? 'left'\n : 'right'\n : preferred;\n const left = side === 'right' ? triggerRect.right + gap : triggerRect.left - gap - panelWidth;\n const levelTop = Math.max(pad, triggerRect.top);\n const roomBelow = viewportHeight - levelTop - pad;\n if (align === 'start' && roomBelow >= Math.min(panelHeight, SIDE_PANEL_MIN_HEIGHT_PX)) {\n return {\n top: levelTop,\n left: clampToViewport(left, panelWidth, viewportWidth),\n maxHeight: roomBelow,\n side,\n };\n }\n const maxHeight = Math.max(0, viewportHeight - pad * 2);\n const height = Math.min(panelHeight, maxHeight);\n const top = clampToViewport(\n align === 'end' ? triggerRect.bottom - height : triggerRect.top,\n height,\n viewportHeight,\n );\n return { top, left: clampToViewport(left, panelWidth, viewportWidth), maxHeight, side };\n};\n"],"names":["MENU_VIEWPORT_PADDING_PX","SIDE_PANEL_MIN_HEIGHT_PX","MENU_MIN_WIDTH_PX","MENU_OPTIONS_MIN_WIDTH_PX","MENU_SPECS","MENU_MAX_HEIGHT_PX","computeMenuGeometry","triggerRect","preset","minWidth","contentWidth","menuHeight","viewportWidth","viewportHeight","spec","available","floor","ceiling","natural","width","preferredLeft","oppositeLeft","minLeft","maxLeft","fits","candidate","left","roomBelow","roomAbove","openUp","maxHeight","height","measureContentWidth","panel","rows","widest","row","styles","edge","value","chrome","measureMenuGeometry","input","cssWidth","isTriggerOffscreen","rect","rectSignature","alignedLeft","align","panelWidth","fullPanelHeight","FLOATING_OFFSET_PX","clampToViewport","size","viewport","computeFloatingGeometry","placement","panelHeight","gap","preferred","pad","wanted","opposite","side","belowTop","aboveBottom","top","roomRight","roomLeft","levelTop"],"mappings":"AAIO,MAAMA,IAA2B,GAE3BC,IAA2B,KAE3BC,IAAoB,KAKpBC,IAA4B,KAoB5BC,IAA2C;AAAA,EACpD,UAAU;AAAA,IACN,OAAO;AAAA,IACP,UAAU;AAAA,IACV,OAAO;AAAA,IACP,KAAK;AAAA,IACL,QAAQ;AAAA,IACR,SAAS;AAAA,IACT,QAAQ;AAAA,IACR,YAAY;AAAA,IACZ,QAAQ;AAAA,EAAA;AAAA,EAEZ,SAAS;AAAA,IACL,OAAO;AAAA,IACP,UAAU;AAAA,IACV,UAAU;AAAA,IACV,OAAO;AAAA,IACP,KAAK;AAAA,IACL,QAAQ;AAAA,IACR,SAAS;AAAA,IACT,QAAQ;AAAA,IACR,YAAY;AAAA,IACZ,QAAQ;AAAA,EAAA;AAEhB,GAEaC,IAAqB,KAqBrBC,IAAsB,CAAC;AAAA,EAChC,aAAAC;AAAA,EACA,QAAAC;AAAA,EACA,UAAAC;AAAA,EACA,cAAAC;AAAA,EACA,YAAAC;AAAA,EACA,eAAAC;AAAA,EACA,gBAAAC;AACJ,MAAuC;AACnC,QAAMC,IAAOV,EAAWI,CAAM,GACxBO,IAAYH,IAAgB,IAE5BI,IAAQ,KAAK,IAAIF,EAAK,UAAUL,KAAY,CAAC,GAC7CQ,IAAU,KAAK,IAAIF,GAAW,KAAK,IAAID,EAAK,YAAYC,GAAWC,CAAK,CAAC,GACzEE,IAAUJ,EAAK,UAAU,YAAYP,EAAY,QAASG,KAAgBO,GAC1EE,IAAQ,KAAK,IAAI,GAAG,KAAK,IAAI,KAAK,IAAID,GAASF,CAAK,GAAGC,CAAO,CAAC,GAE/DG,IAAgBN,EAAK,UAAU,UAAUP,EAAY,QAAQY,IAAQZ,EAAY,MACjFc,IAAeP,EAAK,UAAU,UAAUP,EAAY,OAAOA,EAAY,QAAQY,GAC/EG,IAAU,GACVC,IAAUX,IAAgBO,IAAQ,GAClCK,IAAO,CAACC,MAAsBA,KAAaH,KAAWG,KAAaF;AAEzE,MAAIG;AACJ,EAAIF,EAAKJ,CAAa,IAClBM,IAAON,IACAI,EAAKH,CAAY,IACxBK,IAAOL,IAEPK,IAAO,KAAK,IAAIJ,GAAS,KAAK,IAAIF,GAAeG,CAAO,CAAC;AAM7D,QAAMI,IAAYd,IAAiBN,EAAY,SAASO,EAAK,MAAM,GAC7Dc,IAAYrB,EAAY,MAAMO,EAAK,MAAM,GACzCe,IAASlB,IAAa,KAAKA,IAAagB,KAAaC,IAAYD,GAEjEG,IAAY,KAAK,IAAI,GAAG,KAAK,IAAIzB,GAAoBwB,IAASD,IAAYD,CAAS,CAAC,GACpFI,IAASpB,IAAa,IAAI,KAAK,IAAIA,GAAYmB,CAAS,IAAI;AAOlE,SAAO,EAAE,KALG,KAAK;AAAA,IACb;AAAA,IACAD,IAAStB,EAAY,MAAMO,EAAK,MAAMiB,IAASxB,EAAY,SAASO,EAAK;AAAA,EAAA,GAG/D,MAAAY,GAAM,OAAAP,GAAO,WAAAW,GAAW,QAAAD,EAAA;AAC1C,GAYaG,IAAsB,CAACC,MAAkD;AAClF,MAAI,CAACA,KAASA,EAAM,MAAM,UAAU;AAChC;AAOJ,QAAMC,IAAOD,EAAM,iBAA8B,2BAA2B;AAC5E,MAAIE,IAAS;AAKb,MAJAD,EAAK,QAAQ,CAACE,MAAQ;AAClB,IAAAD,IAAS,KAAK,IAAIA,GAAQC,EAAI,WAAW;AAAA,EAC7C,CAAC,GAEGD,MAAW;AACX;AAOJ,QAAME,IAAS,iBAAiBJ,CAAK,GAC/BK,IAAO,CAACC,MAAkB,OAAO,WAAWA,CAAK,KAAK,GACtDC,IACFF,EAAKD,EAAO,WAAW,IACvBC,EAAKD,EAAO,YAAY,IACxBC,EAAKD,EAAO,eAAe,IAC3BC,EAAKD,EAAO,gBAAgB;AAEhC,SAAO,KAAK,KAAKF,IAASK,CAAM;AACpC,GAEaC,IAAsB,CAC/BR,GACAS,MACe;AACf,MAAI,CAACT;AACD,WAAO3B,EAAoB,EAAE,GAAGoC,GAAO,YAAY,GAAG;AAG1D,QAAMC,IAAW,GAAGrC,EAAoB,EAAE,GAAGoC,GAAO,YAAY,EAAA,CAAG,EAAE,KAAK;AAC1E,SAAIT,EAAM,MAAM,UAAUU,MACtBV,EAAM,MAAM,QAAQU,IAGjBrC,EAAoB,EAAE,GAAGoC,GAAO,YAAYT,EAAM,cAAc;AAC3E,GAEaW,IAAqB,CAC9BC,GACAjC,GACAC,MAEAgC,EAAK,SAAS,KAAKA,EAAK,MAAMhC,KAAkBgC,EAAK,QAAQ,KAAKA,EAAK,OAAOjC,GAErEkC,IAAgB,CAACD,MAC1B,GAAGA,EAAK,GAAG,IAAIA,EAAK,IAAI,IAAIA,EAAK,KAAK,IAAIA,EAAK,MAAM,IAWnDE,IAAc,CAChBC,GACAzC,GACA0C,MAEID,MAAU,QACHzC,EAAY,QAAQ0C,IAE3BD,MAAU,YACFzC,EAAY,OAAOA,EAAY,QAAQ0C,KAAc,IAE1D1C,EAAY,MAGV2C,IAAkB,CAACjB,MAC5BA,EAAM,eAAeA,EAAM,eAAeA,EAAM,cAEvCkB,IAAqB,EAAE,OAAO,GAAG,QAAQ,IAAI,OAAO,GAAA,GAqB3DC,IAAkB,CAACb,GAAec,GAAcC,MAClD,KAAK,IAAI,GAA0B,KAAK,IAAIf,GAAOe,IAAWD,IAAO,CAAwB,CAAC,GAErFE,IAA0B,CAAC;AAAA,EACpC,aAAAhD;AAAA,EACA,WAAAiD;AAAA,EACA,YAAAP;AAAA,EACA,aAAAQ;AAAA,EACA,KAAAC;AAAA,EACA,eAAA9C;AAAA,EACA,gBAAAC;AACJ,MAA+C;AAC3C,QAAM,CAAC8C,GAAWX,CAAK,IAAIQ,EAAU,MAAM,GAAG,GAIxCI,IAAM;AAEZ,MAAID,MAAc,SAASA,MAAc,UAAU;AAC/C,UAAMhC,IAAYd,IAAiBN,EAAY,SAASmD,IAAME,GACxDhC,IAAYrB,EAAY,MAAMmD,IAAME,GACpCC,IAASF,MAAc,WAAWhC,IAAYC,GAC9CkC,IAAWH,MAAc,WAAW/B,IAAYD,GAChDoC,IACFN,IAAcI,KAAUC,IAAWD,IAC7BF,MAAc,WACV,QACA,WACJA,GACJK,IAAW,KAAK,IAAIJ,GAAKrD,EAAY,SAASmD,CAAG,GACjDO,IAAc,KAAK,IAAIpD,IAAiB+C,GAAKrD,EAAY,MAAMmD,CAAG,GAClE5B,IAAY,KAAK;AAAA,MACnB;AAAA,MACAiC,MAAS,WAAWlD,IAAiBmD,IAAWJ,IAAMK,IAAcL;AAAA,IAAA,GAElE7B,IAAS,KAAK,IAAI0B,GAAa3B,CAAS,GACxCoC,IAAMH,MAAS,WAAWC,IAAW,KAAK,IAAIJ,GAAKK,IAAclC,CAAM,GACvEL,IAAO0B;AAAA,MACTL,EAAYC,GAAOzC,GAAa0C,CAAU;AAAA,MAC1CA;AAAA,MACArC;AAAA,IAAA;AAEJ,WAAO,EAAE,KAAAsD,GAAK,MAAAxC,GAAM,WAAAI,GAAW,MAAAiC,EAAAA;AAAAA,EACnC;AAEA,QAAMI,IAAYvD,IAAgBL,EAAY,QAAQmD,IAAME,GACtDQ,IAAW7D,EAAY,OAAOmD,IAAME,GACpCC,IAASF,MAAc,UAAUQ,IAAYC,GAC7CN,IAAWH,MAAc,UAAUS,IAAWD,GAC9CJ,IACFd,IAAaY,KAAUC,IAAWD,IAC5BF,MAAc,UACV,SACA,UACJA,GACJjC,IAAOqC,MAAS,UAAUxD,EAAY,QAAQmD,IAAMnD,EAAY,OAAOmD,IAAMT,GAC7EoB,IAAW,KAAK,IAAIT,GAAKrD,EAAY,GAAG,GACxCoB,IAAYd,IAAiBwD,IAAWT;AAC9C,MAAIZ,MAAU,WAAWrB,KAAa,KAAK,IAAI8B,GAAa,GAAwB;AAChF,WAAO;AAAA,MACH,KAAKY;AAAA,MACL,MAAMjB,EAAgB1B,GAAMuB,GAAYrC,CAAa;AAAA,MACrD,WAAWe;AAAA,MACX,MAAAoC;AAAA,IAAA;AAGR,QAAMjC,IAAY,KAAK,IAAI,GAAGjB,IAAiB+C,IAAM,CAAC,GAChD7B,IAAS,KAAK,IAAI0B,GAAa3B,CAAS;AAM9C,SAAO,EAAE,KALGsB;AAAA,IACRJ,MAAU,QAAQzC,EAAY,SAASwB,IAASxB,EAAY;AAAA,IAC5DwB;AAAA,IACAlB;AAAA,EAAA,GAEU,MAAMuC,EAAgB1B,GAAMuB,GAAYrC,CAAa,GAAG,WAAAkB,GAAW,MAAAiC,EAAA;AACrF;"}
|