@stackone/malachite 2.44.0 → 2.46.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 +4 -4
- package/dist/components/ActionMenu.js +143 -111
- package/dist/components/ActionMenu.js.map +1 -1
- package/dist/components/ActionMenuTagHost.js +11 -11
- package/dist/components/ActionMenuTagHost.js.map +1 -1
- package/dist/components/Dropdown.js +290 -286
- package/dist/components/Dropdown.js.map +1 -1
- package/dist/components/MultiSelect.js +132 -131
- package/dist/components/MultiSelect.js.map +1 -1
- package/dist/components/NavItem.js +18 -17
- package/dist/components/NavItem.js.map +1 -1
- package/dist/components/PopoverParts.js +40 -26
- package/dist/components/PopoverParts.js.map +1 -1
- package/dist/components/Sidebar.js +132 -87
- package/dist/components/Sidebar.js.map +1 -1
- package/dist/components/TruncatedTooltip.js +24 -23
- package/dist/components/TruncatedTooltip.js.map +1 -1
- package/dist/components/actionMenuKeyboard.js +5 -1
- package/dist/components/actionMenuKeyboard.js.map +1 -1
- package/dist/components/actionMenuStyles.js +13 -11
- package/dist/components/actionMenuStyles.js.map +1 -1
- package/dist/components/floatingMenuGeometry.js +51 -50
- package/dist/components/floatingMenuGeometry.js.map +1 -1
- package/dist/components/hasBadge.js +5 -0
- package/dist/components/hasBadge.js.map +1 -0
- package/dist/components/linkClick.js +5 -0
- package/dist/components/linkClick.js.map +1 -0
- package/dist/components/sidebarContext.js +6 -5
- package/dist/components/sidebarContext.js.map +1 -1
- package/dist/index.d.ts +22 -3
- package/dist/index.js +300 -299
- package/dist/tailwind.css.js +1 -1
- package/package.json +1 -1
|
@@ -20,28 +20,28 @@ const y = {
|
|
|
20
20
|
overflow: "hidden",
|
|
21
21
|
textOverflow: "ellipsis",
|
|
22
22
|
whiteSpace: "nowrap"
|
|
23
|
-
},
|
|
24
|
-
...
|
|
23
|
+
}, g = { display: "inline-flex", flexShrink: 0 }, R = {
|
|
24
|
+
...g,
|
|
25
25
|
color: "var(--malachite-text-dimmed, rgb(161, 161, 161))"
|
|
26
|
-
}, n = "malachite-actionmenu-item-icon", a = "malachite-actionmenu-item",
|
|
26
|
+
}, n = "malachite-actionmenu-item-icon", a = "malachite-actionmenu-item", h = "data-malachite-actionmenu-tag", w = { display: "inline-flex", flexShrink: 0 }, I = { [h]: "" }, x = [
|
|
27
27
|
`.${a} { background: transparent; }`,
|
|
28
28
|
`.${a}:not(:disabled):hover { background: var(--malachite-card-dimmed-background, rgb(247, 247, 247)); }`,
|
|
29
29
|
`.${a}:focus-visible { outline: 2px solid var(--malachite-text-primary, rgb(10, 10, 10)); outline-offset: -2px; }`
|
|
30
|
-
], t = `.${a}[data-danger="hover"]`, c = `${t}:not(:disabled):is(:hover, :focus-visible)`,
|
|
30
|
+
], t = `.${a}[data-danger="hover"]`, c = `${t}:not(:disabled):is(:hover, :focus-visible)`, b = [
|
|
31
31
|
`${t} { color: var(--malachite-text-primary, rgb(10, 10, 10)); }`,
|
|
32
32
|
`${t}[data-tone="muted"] { color: var(--malachite-text-secondary, rgb(123, 123, 123)); }`,
|
|
33
33
|
`${t} > .${n} { color: var(--malachite-text-dimmed, rgb(161, 161, 161)); }`,
|
|
34
34
|
`${t}[aria-current] { color: ${d}; background: ${i}; }`,
|
|
35
35
|
`${c} { color: var(--malachite-text-destructive, rgb(237, 53, 54)); background: var(--malachite-error-background-glass, rgba(197, 63, 63, 0.08)); }`,
|
|
36
36
|
`${c} > .${n} { color: inherit; }`
|
|
37
|
-
],
|
|
37
|
+
], v = () => l("malachite-actionmenu-danger-hover-styles", b), A = () => (s(), v(), l("malachite-actionmenu-item-styles", x)), S = {
|
|
38
38
|
default: "var(--malachite-text-primary, rgb(10, 10, 10))",
|
|
39
39
|
muted: "var(--malachite-text-secondary, rgb(123, 123, 123))"
|
|
40
|
-
},
|
|
40
|
+
}, f = (e, o) => {
|
|
41
41
|
if (e.danger === !0)
|
|
42
42
|
return "var(--malachite-text-destructive, rgb(237, 53, 54))";
|
|
43
43
|
if (e.danger !== "hover")
|
|
44
|
-
return e.current ? d :
|
|
44
|
+
return e.current ? d : S[o];
|
|
45
45
|
}, $ = (e, o = "default") => e.type === "text" ? u : e.type === "divider" ? {
|
|
46
46
|
height: 1,
|
|
47
47
|
background: "var(--malachite-card-border, rgb(232, 232, 232))",
|
|
@@ -54,7 +54,7 @@ const y = {
|
|
|
54
54
|
borderRadius: 8,
|
|
55
55
|
fontSize: "var(--malachite-font-size-small, 13px)",
|
|
56
56
|
fontWeight: 400,
|
|
57
|
-
color:
|
|
57
|
+
color: f(e, o),
|
|
58
58
|
background: e.current && e.danger !== "hover" ? i : void 0,
|
|
59
59
|
cursor: e.disabled ? "not-allowed" : "pointer",
|
|
60
60
|
opacity: e.disabled ? 0.5 : 1,
|
|
@@ -63,6 +63,8 @@ const y = {
|
|
|
63
63
|
overflow: "hidden",
|
|
64
64
|
border: "none",
|
|
65
65
|
textAlign: "left",
|
|
66
|
+
textDecoration: "none",
|
|
67
|
+
boxSizing: "border-box",
|
|
66
68
|
width: "100%"
|
|
67
69
|
}, L = (e) => ({
|
|
68
70
|
position: "fixed",
|
|
@@ -86,16 +88,16 @@ export {
|
|
|
86
88
|
m as EMPTY_ITEM_STYLE,
|
|
87
89
|
a as ITEM_CLASS,
|
|
88
90
|
n as ITEM_ICON_CLASS,
|
|
89
|
-
|
|
91
|
+
g as ITEM_ICON_LAYOUT,
|
|
90
92
|
R as ITEM_ICON_STYLE,
|
|
91
93
|
O as LABEL_STYLE,
|
|
92
|
-
|
|
94
|
+
h as TAG_HOST_ATTR,
|
|
93
95
|
I as TAG_HOST_PROPS,
|
|
94
96
|
w as TAG_HOST_STYLE,
|
|
95
97
|
u as TEXT_ITEM_STYLE,
|
|
96
98
|
y as TRIGGER_SIZES,
|
|
97
99
|
A as ensureActionMenuStyles,
|
|
98
|
-
|
|
100
|
+
v as ensureDangerOnHoverStyles,
|
|
99
101
|
$ as itemStyle,
|
|
100
102
|
L as menuStyle
|
|
101
103
|
};
|
|
@@ -1 +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';\nimport { SELECTED_ROW_BACKGROUND, SELECTED_ROW_FOREGROUND } from './selectedRow';\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_LAYOUT: React.CSSProperties = { display: 'inline-flex', flexShrink: 0 };\n\nexport const ITEM_ICON_STYLE: React.CSSProperties = {\n ...ITEM_ICON_LAYOUT,\n color: 'var(--malachite-text-dimmed, rgb(161, 161, 161))',\n};\n\nexport const ITEM_ICON_CLASS = 'malachite-actionmenu-item-icon';\n\nexport const ITEM_CLASS = 'malachite-actionmenu-item';\n\nexport const TAG_HOST_ATTR = 'data-malachite-actionmenu-tag' as const;\n\nexport const TAG_HOST_STYLE: React.CSSProperties = { display: 'inline-flex', flexShrink: 0 };\n\nexport const TAG_HOST_PROPS = { [TAG_HOST_ATTR]: '' };\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\nconst DANGER_ON_HOVER = `.${ITEM_CLASS}[data-danger=\"hover\"]`;\nconst DANGER_ON_HOVER_ACTIVE = `${DANGER_ON_HOVER}:not(:disabled):is(:hover, :focus-visible)`;\n\nconst DANGER_ON_HOVER_RULES = [\n `${DANGER_ON_HOVER} { color: var(--malachite-text-primary, rgb(10, 10, 10)); }`,\n `${DANGER_ON_HOVER}[data-tone=\"muted\"] { color: var(--malachite-text-secondary, rgb(123, 123, 123)); }`,\n `${DANGER_ON_HOVER} > .${ITEM_ICON_CLASS} { color: var(--malachite-text-dimmed, rgb(161, 161, 161)); }`,\n `${DANGER_ON_HOVER}[aria-current] { color: ${SELECTED_ROW_FOREGROUND}; background: ${SELECTED_ROW_BACKGROUND}; }`,\n `${DANGER_ON_HOVER_ACTIVE} { color: var(--malachite-text-destructive, rgb(237, 53, 54)); background: var(--malachite-error-background-glass, rgba(197, 63, 63, 0.08)); }`,\n `${DANGER_ON_HOVER_ACTIVE} > .${ITEM_ICON_CLASS} { color: inherit; }`,\n];\n\nexport const ensureDangerOnHoverStyles = (): CSSStyleSheet | null =>\n injectStyleSheet('malachite-actionmenu-danger-hover-styles', DANGER_ON_HOVER_RULES);\n\nexport const ensureActionMenuStyles = (): CSSStyleSheet | null => {\n ensureOverlayStyles();\n ensureDangerOnHoverStyles();\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\ntype ActionItem = Exclude<ActionMenuItem, { type: 'text' } | { type: 'divider' }>;\n\nconst actionItemColor = (item: ActionItem, tone: ActionMenuTone): string | undefined => {\n if (item.danger === true) {\n return 'var(--malachite-text-destructive, rgb(237, 53, 54))';\n }\n if (item.danger === 'hover') {\n return undefined;\n }\n if (item.current) {\n return SELECTED_ROW_FOREGROUND;\n }\n return ITEM_TEXT[tone];\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: actionItemColor(item, tone),\n background: item.current && item.danger !== 'hover' ? SELECTED_ROW_BACKGROUND : undefined,\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_LAYOUT","ITEM_ICON_STYLE","ITEM_ICON_CLASS","ITEM_CLASS","TAG_HOST_ATTR","TAG_HOST_STYLE","TAG_HOST_PROPS","ITEM_RULES","DANGER_ON_HOVER","DANGER_ON_HOVER_ACTIVE","DANGER_ON_HOVER_RULES","SELECTED_ROW_FOREGROUND","SELECTED_ROW_BACKGROUND","ensureDangerOnHoverStyles","injectStyleSheet","ensureActionMenuStyles","ensureOverlayStyles","ITEM_TEXT","actionItemColor","item","tone","itemStyle","menuStyle","position","MENU_SPECS"],"mappings":"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"}
|
|
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';\nimport { SELECTED_ROW_BACKGROUND, SELECTED_ROW_FOREGROUND } from './selectedRow';\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_LAYOUT: React.CSSProperties = { display: 'inline-flex', flexShrink: 0 };\n\nexport const ITEM_ICON_STYLE: React.CSSProperties = {\n ...ITEM_ICON_LAYOUT,\n color: 'var(--malachite-text-dimmed, rgb(161, 161, 161))',\n};\n\nexport const ITEM_ICON_CLASS = 'malachite-actionmenu-item-icon';\n\nexport const ITEM_CLASS = 'malachite-actionmenu-item';\n\nexport const TAG_HOST_ATTR = 'data-malachite-actionmenu-tag' as const;\n\nexport const TAG_HOST_STYLE: React.CSSProperties = { display: 'inline-flex', flexShrink: 0 };\n\nexport const TAG_HOST_PROPS = { [TAG_HOST_ATTR]: '' };\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\nconst DANGER_ON_HOVER = `.${ITEM_CLASS}[data-danger=\"hover\"]`;\nconst DANGER_ON_HOVER_ACTIVE = `${DANGER_ON_HOVER}:not(:disabled):is(:hover, :focus-visible)`;\n\nconst DANGER_ON_HOVER_RULES = [\n `${DANGER_ON_HOVER} { color: var(--malachite-text-primary, rgb(10, 10, 10)); }`,\n `${DANGER_ON_HOVER}[data-tone=\"muted\"] { color: var(--malachite-text-secondary, rgb(123, 123, 123)); }`,\n `${DANGER_ON_HOVER} > .${ITEM_ICON_CLASS} { color: var(--malachite-text-dimmed, rgb(161, 161, 161)); }`,\n `${DANGER_ON_HOVER}[aria-current] { color: ${SELECTED_ROW_FOREGROUND}; background: ${SELECTED_ROW_BACKGROUND}; }`,\n `${DANGER_ON_HOVER_ACTIVE} { color: var(--malachite-text-destructive, rgb(237, 53, 54)); background: var(--malachite-error-background-glass, rgba(197, 63, 63, 0.08)); }`,\n `${DANGER_ON_HOVER_ACTIVE} > .${ITEM_ICON_CLASS} { color: inherit; }`,\n];\n\nexport const ensureDangerOnHoverStyles = (): CSSStyleSheet | null =>\n injectStyleSheet('malachite-actionmenu-danger-hover-styles', DANGER_ON_HOVER_RULES);\n\nexport const ensureActionMenuStyles = (): CSSStyleSheet | null => {\n ensureOverlayStyles();\n ensureDangerOnHoverStyles();\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\ntype ActionItem = Exclude<ActionMenuItem, { type: 'text' } | { type: 'divider' }>;\n\nconst actionItemColor = (item: ActionItem, tone: ActionMenuTone): string | undefined => {\n if (item.danger === true) {\n return 'var(--malachite-text-destructive, rgb(237, 53, 54))';\n }\n if (item.danger === 'hover') {\n return undefined;\n }\n if (item.current) {\n return SELECTED_ROW_FOREGROUND;\n }\n return ITEM_TEXT[tone];\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: actionItemColor(item, tone),\n background: item.current && item.danger !== 'hover' ? SELECTED_ROW_BACKGROUND : undefined,\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 textDecoration: 'none',\n boxSizing: 'border-box',\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_LAYOUT","ITEM_ICON_STYLE","ITEM_ICON_CLASS","ITEM_CLASS","TAG_HOST_ATTR","TAG_HOST_STYLE","TAG_HOST_PROPS","ITEM_RULES","DANGER_ON_HOVER","DANGER_ON_HOVER_ACTIVE","DANGER_ON_HOVER_RULES","SELECTED_ROW_FOREGROUND","SELECTED_ROW_BACKGROUND","ensureDangerOnHoverStyles","injectStyleSheet","ensureActionMenuStyles","ensureOverlayStyles","ITEM_TEXT","actionItemColor","item","tone","itemStyle","menuStyle","position","MENU_SPECS"],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const S = 8, y = 160, F = 160, $ = 180,
|
|
1
|
+
const S = 8, y = 160, F = 160, $ = 180, g = {
|
|
2
2
|
standard: {
|
|
3
3
|
width: "trigger",
|
|
4
4
|
minWidth: 160,
|
|
@@ -22,85 +22,86 @@ const S = 8, y = 160, F = 160, $ = 180, L = {
|
|
|
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
|
+
}, L = 400, O = ({
|
|
26
26
|
triggerRect: t,
|
|
27
|
-
preset:
|
|
27
|
+
preset: n,
|
|
28
28
|
minWidth: o,
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
align: e,
|
|
30
|
+
contentWidth: s,
|
|
31
|
+
menuHeight: c,
|
|
32
|
+
viewportWidth: m,
|
|
32
33
|
viewportHeight: h
|
|
33
34
|
}) => {
|
|
34
|
-
const
|
|
35
|
-
let
|
|
36
|
-
E(
|
|
37
|
-
const l = h - t.bottom -
|
|
35
|
+
const i = g[n], a = m - 16, f = Math.max(i.minWidth, o ?? 0), P = Math.min(a, Math.max(i.maxWidth ?? a, f)), T = i.width === "trigger" ? t.width : s ?? P, _ = Math.max(0, Math.min(Math.max(T, f), P)), r = e ?? i.align, d = r === "right" ? t.right - _ : t.left, I = r === "right" ? t.left : t.right - _, N = 8, b = m - _ - 8, E = (D) => D >= N && D <= b;
|
|
36
|
+
let x;
|
|
37
|
+
E(d) ? x = d : E(I) ? x = I : x = Math.max(N, Math.min(d, b));
|
|
38
|
+
const l = h - t.bottom - i.gap - 8, u = t.top - i.gap - 8, M = c > 0 && c > l && u > l, X = Math.max(0, Math.min(L, M ? u : l)), p = c > 0 ? Math.min(c, X) : 0;
|
|
38
39
|
return { top: Math.max(
|
|
39
40
|
8,
|
|
40
|
-
M ? t.top -
|
|
41
|
-
), left:
|
|
41
|
+
M ? t.top - i.gap - p : t.bottom + i.gap
|
|
42
|
+
), left: x, width: _, maxHeight: X, openUp: M };
|
|
42
43
|
}, k = (t) => {
|
|
43
44
|
if (!t || t.style.width !== "")
|
|
44
45
|
return;
|
|
45
|
-
const
|
|
46
|
+
const n = t.querySelectorAll('button, [role="menuitem"]');
|
|
46
47
|
let o = 0;
|
|
47
|
-
if (
|
|
48
|
-
o = Math.max(o,
|
|
48
|
+
if (n.forEach((m) => {
|
|
49
|
+
o = Math.max(o, m.scrollWidth);
|
|
49
50
|
}), o === 0)
|
|
50
51
|
return;
|
|
51
|
-
const
|
|
52
|
-
return Math.ceil(o +
|
|
53
|
-
}, B = (t,
|
|
52
|
+
const e = getComputedStyle(t), s = (m) => Number.parseFloat(m) || 0, c = s(e.paddingLeft) + s(e.paddingRight) + s(e.borderLeftWidth) + s(e.borderRightWidth);
|
|
53
|
+
return Math.ceil(o + c);
|
|
54
|
+
}, B = (t, n) => {
|
|
54
55
|
if (!t)
|
|
55
|
-
return
|
|
56
|
-
const o = `${
|
|
57
|
-
return t.style.width !== o && (t.style.width = o),
|
|
58
|
-
}, C = (t,
|
|
56
|
+
return O({ ...n, menuHeight: 0 });
|
|
57
|
+
const o = `${O({ ...n, menuHeight: 0 }).width}px`;
|
|
58
|
+
return t.style.width !== o && (t.style.width = o), O({ ...n, menuHeight: t.offsetHeight });
|
|
59
|
+
}, C = (t, n, o) => t.bottom < 0 || t.top > o || t.right < 0 || t.left > n, v = (t) => `${t.top},${t.left},${t.width},${t.height}`, V = (t, n, o) => t === "end" ? n.right - o : t === "center" ? (n.left + n.right - o) / 2 : n.left, q = (t) => t.scrollHeight + t.offsetHeight - t.clientHeight, j = { small: 8, medium: 12, large: 14 }, A = (t, n, o) => Math.max(8, Math.min(t, o - n - 8)), z = ({
|
|
59
60
|
triggerRect: t,
|
|
60
|
-
placement:
|
|
61
|
+
placement: n,
|
|
61
62
|
panelWidth: o,
|
|
62
|
-
panelHeight:
|
|
63
|
-
gap:
|
|
64
|
-
viewportWidth:
|
|
65
|
-
viewportHeight:
|
|
63
|
+
panelHeight: e,
|
|
64
|
+
gap: s,
|
|
65
|
+
viewportWidth: c,
|
|
66
|
+
viewportHeight: m
|
|
66
67
|
}) => {
|
|
67
|
-
const [
|
|
68
|
-
if (
|
|
69
|
-
const
|
|
68
|
+
const [h, i] = n.split("-"), a = 8;
|
|
69
|
+
if (h === "top" || h === "bottom") {
|
|
70
|
+
const l = m - t.bottom - s - a, u = t.top - s - a, M = h === "bottom" ? l : u, X = h === "bottom" ? u : l, p = e > M && X > M ? h === "bottom" ? "top" : "bottom" : h, G = Math.max(a, t.bottom + s), D = Math.min(m - a, t.top - s), U = Math.max(
|
|
70
71
|
0,
|
|
71
|
-
|
|
72
|
-
), W = Math.min(
|
|
73
|
-
|
|
72
|
+
p === "bottom" ? m - G - a : D - a
|
|
73
|
+
), W = Math.min(e, U), w = p === "bottom" ? G : Math.max(a, D - W), H = A(
|
|
74
|
+
V(i, t, o),
|
|
74
75
|
o,
|
|
75
|
-
|
|
76
|
+
c
|
|
76
77
|
);
|
|
77
|
-
return { top: w, left: H, maxHeight: U, side:
|
|
78
|
+
return { top: w, left: H, maxHeight: U, side: p };
|
|
78
79
|
}
|
|
79
|
-
const
|
|
80
|
-
if (
|
|
80
|
+
const f = c - t.right - s - a, P = t.left - s - a, T = h === "right" ? f : P, _ = h === "right" ? P : f, r = o > T && _ > T ? h === "right" ? "left" : "right" : h, d = r === "right" ? t.right + s : t.left - s - o, I = Math.max(a, t.top), N = m - I - a;
|
|
81
|
+
if (i === "start" && N >= Math.min(e, 160))
|
|
81
82
|
return {
|
|
82
|
-
top:
|
|
83
|
-
left:
|
|
84
|
-
maxHeight:
|
|
85
|
-
side:
|
|
83
|
+
top: I,
|
|
84
|
+
left: A(d, o, c),
|
|
85
|
+
maxHeight: N,
|
|
86
|
+
side: r
|
|
86
87
|
};
|
|
87
|
-
const
|
|
88
|
-
return { top:
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
), left:
|
|
88
|
+
const b = Math.max(0, m - a * 2), E = Math.min(e, b);
|
|
89
|
+
return { top: A(
|
|
90
|
+
i === "end" ? t.bottom - E : t.top,
|
|
91
|
+
E,
|
|
92
|
+
m
|
|
93
|
+
), left: A(d, o, c), maxHeight: b, side: r };
|
|
93
94
|
};
|
|
94
95
|
export {
|
|
95
96
|
j as FLOATING_OFFSET_PX,
|
|
96
|
-
|
|
97
|
+
L as MENU_MAX_HEIGHT_PX,
|
|
97
98
|
F as MENU_MIN_WIDTH_PX,
|
|
98
99
|
$ as MENU_OPTIONS_MIN_WIDTH_PX,
|
|
99
|
-
|
|
100
|
+
g as MENU_SPECS,
|
|
100
101
|
S as MENU_VIEWPORT_PADDING_PX,
|
|
101
102
|
y as SIDE_PANEL_MIN_HEIGHT_PX,
|
|
102
103
|
z as computeFloatingGeometry,
|
|
103
|
-
|
|
104
|
+
O as computeMenuGeometry,
|
|
104
105
|
q as fullPanelHeight,
|
|
105
106
|
C as isTriggerOffscreen,
|
|
106
107
|
k as measureContentWidth,
|
|
@@ -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 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":"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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 /** Which trigger edge the menu prefers to share; defaults to the preset's. */\n align?: 'left' | 'right';\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 align: alignOverride,\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 align = alignOverride ?? spec.align;\n const preferredLeft = align === 'right' ? triggerRect.right - width : triggerRect.left;\n const oppositeLeft = 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","alignOverride","contentWidth","menuHeight","viewportWidth","viewportHeight","spec","available","floor","ceiling","natural","width","align","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","panelWidth","fullPanelHeight","FLOATING_OFFSET_PX","clampToViewport","size","viewport","computeFloatingGeometry","placement","panelHeight","gap","preferred","pad","wanted","opposite","side","belowTop","aboveBottom","top","roomRight","roomLeft","levelTop"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hasBadge.js","sources":["../../src/components/hasBadge.ts"],"sourcesContent":["import type React from 'react';\n\n// React renders 0 and '' as text, so only the values it skips mean \"no badge\".\nexport const hasBadge = (badge: React.ReactNode): boolean =>\n badge !== undefined && badge !== null && badge !== false;\n"],"names":["hasBadge","badge"],"mappings":"AAGO,MAAMA,IAAW,CAACC,MACEA,KAAU,QAAQA,MAAU;"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"linkClick.js","sources":["../../src/components/linkClick.ts"],"sourcesContent":["import type React from 'react';\n\nexport const isPlainClick = (event: React.MouseEvent): boolean =>\n event.button === 0 && !event.metaKey && !event.ctrlKey && !event.shiftKey && !event.altKey;\n"],"names":["isPlainClick","event"],"mappings":"AAEO,MAAMA,IAAe,CAACC,MACzBA,EAAM,WAAW,KAAK,CAACA,EAAM,WAAW,CAACA,EAAM,WAAW,CAACA,EAAM,YAAY,CAACA,EAAM;"}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { useContext as e, createContext as o } from "react";
|
|
2
|
-
const
|
|
2
|
+
const c = { expanded: 260, collapsed: 60 }, n = 244, _ = 100, a = 0.2, t = o({ collapsed: !1 }), l = () => e(t).collapsed;
|
|
3
3
|
export {
|
|
4
|
-
|
|
5
|
-
a as
|
|
6
|
-
|
|
4
|
+
n as SIDEBAR_POPOVER_WIDTH_PX,
|
|
5
|
+
a as SIDEBAR_TOOLTIP_DELAY_S,
|
|
6
|
+
c as SIDEBAR_WIDTH_PX,
|
|
7
|
+
_ as SIDEBAR_Z_INDEX,
|
|
7
8
|
t as SidebarContext,
|
|
8
|
-
|
|
9
|
+
l as useSidebarCollapsed
|
|
9
10
|
};
|
|
10
11
|
//# sourceMappingURL=sidebarContext.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sidebarContext.js","sources":["../../src/components/sidebarContext.ts"],"sourcesContent":["import { createContext, useContext } from 'react';\n\nexport const SIDEBAR_WIDTH_PX = { expanded: 260, collapsed: 60 } as const;\n\nexport const SIDEBAR_POPOVER_WIDTH_PX = 244;\n\nexport const SIDEBAR_Z_INDEX = 100;\n\nexport const SidebarContext = createContext({ collapsed: false });\n\nexport const useSidebarCollapsed = (): boolean => useContext(SidebarContext).collapsed;\n"],"names":["SIDEBAR_WIDTH_PX","SIDEBAR_POPOVER_WIDTH_PX","SIDEBAR_Z_INDEX","SidebarContext","createContext","useSidebarCollapsed","useContext"],"mappings":";AAEO,MAAMA,IAAmB,EAAE,UAAU,KAAK,WAAW,GAAA,GAE/CC,IAA2B,KAE3BC,IAAkB,KAElBC,IAAiBC,EAAc,EAAE,WAAW,IAAO,GAEnDC,IAAsB,MAAeC,EAAWH,CAAc,EAAE;"}
|
|
1
|
+
{"version":3,"file":"sidebarContext.js","sources":["../../src/components/sidebarContext.ts"],"sourcesContent":["import { createContext, useContext } from 'react';\n\nexport const SIDEBAR_WIDTH_PX = { expanded: 260, collapsed: 60 } as const;\n\nexport const SIDEBAR_POPOVER_WIDTH_PX = 244;\n\nexport const SIDEBAR_Z_INDEX = 100;\n\nexport const SIDEBAR_TOOLTIP_DELAY_S = 0.2;\n\nexport const SidebarContext = createContext({ collapsed: false });\n\nexport const useSidebarCollapsed = (): boolean => useContext(SidebarContext).collapsed;\n"],"names":["SIDEBAR_WIDTH_PX","SIDEBAR_POPOVER_WIDTH_PX","SIDEBAR_Z_INDEX","SIDEBAR_TOOLTIP_DELAY_S","SidebarContext","createContext","useSidebarCollapsed","useContext"],"mappings":";AAEO,MAAMA,IAAmB,EAAE,UAAU,KAAK,WAAW,GAAA,GAE/CC,IAA2B,KAE3BC,IAAkB,KAElBC,IAA0B,KAE1BC,IAAiBC,EAAc,EAAE,WAAW,IAAO,GAEnDC,IAAsB,MAAeC,EAAWH,CAAc,EAAE;"}
|
package/dist/index.d.ts
CHANGED
|
@@ -101,6 +101,8 @@ export declare type ActionMenuItem = {
|
|
|
101
101
|
key: string;
|
|
102
102
|
label: default_2.ReactNode;
|
|
103
103
|
onClick?: () => void;
|
|
104
|
+
/** Renders a link; a plain click runs `onClick` instead of following it, when one is given. */
|
|
105
|
+
href?: string;
|
|
104
106
|
disabled?: boolean;
|
|
105
107
|
/** `'hover'` turns the item destructive only on hover or focus. */
|
|
106
108
|
danger?: boolean | 'hover';
|
|
@@ -1353,7 +1355,7 @@ export declare const DoubleCheckIcon: React.FC<IconProps_39>;
|
|
|
1353
1355
|
|
|
1354
1356
|
export declare const DownloadIcon: React.FC<IconProps_40>;
|
|
1355
1357
|
|
|
1356
|
-
export declare const Dropdown: ({ items, onItemSelected, defaultValue, value, disabled, error, tone, label, hint, rightSideContent, tooltip, name, required, requiredIndicator, loading, menuLoading, formControl, description, helperText, placeholder, search, showSubtitleInTrigger, subtitleLayout, clearable, size, fullWidth, width, minWidth, id, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, }: DropdownProps) => JSX_2.Element;
|
|
1358
|
+
export declare const Dropdown: ({ items, onItemSelected, defaultValue, value, disabled, error, tone, label, hint, rightSideContent, tooltip, name, required, requiredIndicator, loading, menuLoading, formControl, description, helperText, placeholder, search, showSubtitleInTrigger, subtitleLayout, clearable, size, fullWidth, width, minWidth, menuAlign, id, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, }: DropdownProps) => JSX_2.Element;
|
|
1357
1359
|
|
|
1358
1360
|
declare interface DropdownBaseProps {
|
|
1359
1361
|
label?: string;
|
|
@@ -1393,6 +1395,11 @@ declare interface DropdownBaseProps {
|
|
|
1393
1395
|
* picker in a 200px slot — whose options carry a subtitle the trigger never has to show.
|
|
1394
1396
|
*/
|
|
1395
1397
|
minWidth?: number;
|
|
1398
|
+
/**
|
|
1399
|
+
* Which trigger edge a menu wider than its trigger lines up with. `end` grows it leftwards,
|
|
1400
|
+
* for a control at the right of its container. Either flips when the viewport has no room.
|
|
1401
|
+
*/
|
|
1402
|
+
menuAlign?: 'start' | 'end';
|
|
1396
1403
|
/** Names the trigger from the light DOM, where `htmlFor` cannot reach into the shadow root. */
|
|
1397
1404
|
id?: string;
|
|
1398
1405
|
'aria-label'?: string;
|
|
@@ -1411,6 +1418,8 @@ export declare interface DropdownItem {
|
|
|
1411
1418
|
rightSideText?: string;
|
|
1412
1419
|
rightSideTextVariant?: 'muted' | 'primary';
|
|
1413
1420
|
rightSideTextAlignment?: 'left' | 'right';
|
|
1421
|
+
/** Right-aligned badge shown in the open menu only (not in the trigger or the search input). */
|
|
1422
|
+
badge?: default_2.ReactNode;
|
|
1414
1423
|
}
|
|
1415
1424
|
|
|
1416
1425
|
export declare type DropdownProps = DropdownBaseProps & DropdownTriggerModeProps;
|
|
@@ -3606,6 +3615,8 @@ export declare interface PopoverActionProps {
|
|
|
3606
3615
|
icon: default_2.ReactNode;
|
|
3607
3616
|
'aria-label': string;
|
|
3608
3617
|
onClick?: () => void;
|
|
3618
|
+
/** Renders a link; a plain click runs `onClick` instead of following it, when one is given. */
|
|
3619
|
+
href?: string;
|
|
3609
3620
|
disabled?: boolean;
|
|
3610
3621
|
tooltip?: string;
|
|
3611
3622
|
}
|
|
@@ -4145,6 +4156,8 @@ export declare const Sidebar: default_2.FC<SidebarProps> & {
|
|
|
4145
4156
|
|
|
4146
4157
|
export declare const SIDEBAR_POPOVER_WIDTH_PX = 244;
|
|
4147
4158
|
|
|
4159
|
+
export declare const SIDEBAR_TOOLTIP_DELAY_S = 0.2;
|
|
4160
|
+
|
|
4148
4161
|
export declare const SIDEBAR_WIDTH_PX: {
|
|
4149
4162
|
readonly expanded: 260;
|
|
4150
4163
|
readonly collapsed: 60;
|
|
@@ -4154,8 +4167,8 @@ export declare interface SidebarAccountProps extends SidebarButtonAttributes {
|
|
|
4154
4167
|
avatar?: default_2.ReactNode;
|
|
4155
4168
|
initials?: string;
|
|
4156
4169
|
name: string;
|
|
4157
|
-
/** Collapsed-rail tooltip, a node so `name` can be wrapped
|
|
4158
|
-
*
|
|
4170
|
+
/** Collapsed-rail tooltip, and expanded when `name` is cut off; a node so `name` can be wrapped.
|
|
4171
|
+
* Should read as `name`. Defaults to `name`, `false` hides it; none when `name` is empty. */
|
|
4159
4172
|
tooltip?: default_2.ReactNode;
|
|
4160
4173
|
}
|
|
4161
4174
|
|
|
@@ -4174,6 +4187,10 @@ export declare interface SidebarSwitcherProps extends SidebarButtonAttributes {
|
|
|
4174
4187
|
logo: default_2.ReactNode;
|
|
4175
4188
|
title: string;
|
|
4176
4189
|
subtitle?: string;
|
|
4190
|
+
/** Shown when `title` is cut off; a node so it can be wrapped. Defaults to `title`. */
|
|
4191
|
+
titleTooltip?: default_2.ReactNode;
|
|
4192
|
+
/** As `titleTooltip`, for `subtitle`. */
|
|
4193
|
+
subtitleTooltip?: default_2.ReactNode;
|
|
4177
4194
|
}
|
|
4178
4195
|
|
|
4179
4196
|
/**
|
|
@@ -5002,6 +5019,8 @@ export declare interface TruncatedTooltipProps {
|
|
|
5002
5019
|
triggerAs?: 'div' | 'span';
|
|
5003
5020
|
/** False when `text` repeats the children, which are already in the DOM in full. */
|
|
5004
5021
|
describesTrigger?: boolean;
|
|
5022
|
+
/** Keeps the tooltip shut, e.g. while the control it sits in has its own popup open. */
|
|
5023
|
+
suppressed?: boolean;
|
|
5005
5024
|
}
|
|
5006
5025
|
|
|
5007
5026
|
export declare const Typography: {
|