@ntmats/hc-ui 0.2.0 → 0.3.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/README.md +37 -3
- package/dist/index.d.ts +562 -6
- package/dist/index.js +1326 -39
- package/dist/styles.css +1158 -2
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
// src/index.tsx
|
|
2
|
-
import { forwardRef, useId, useRef } from "react";
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
import { cloneElement, createContext, forwardRef, Fragment, useCallback, useContext, useEffect, useId, useImperativeHandle, useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
3
|
+
import { createPortal } from "react-dom";
|
|
4
|
+
import { Fragment as Fragment2, jsx, jsxs } from "react/jsx-runtime";
|
|
5
|
+
function Surface({ tone = "content", radius, className, ...props }) {
|
|
6
|
+
return /* @__PURE__ */ jsx("div", { ...props, className: ["hc-ui-surface", className].filter(Boolean).join(" "), "data-tone": tone, "data-radius": radius });
|
|
6
7
|
}
|
|
7
8
|
function SegmentedControl({
|
|
8
9
|
label,
|
|
@@ -12,12 +13,24 @@ function SegmentedControl({
|
|
|
12
13
|
density = "comfortable",
|
|
13
14
|
orientation = "horizontal",
|
|
14
15
|
hideLabels = false,
|
|
16
|
+
indicator = "static",
|
|
17
|
+
equalWidth = false,
|
|
18
|
+
optionWidth,
|
|
15
19
|
className,
|
|
20
|
+
style,
|
|
16
21
|
...props
|
|
17
22
|
}) {
|
|
18
23
|
const buttons = useRef([]);
|
|
19
24
|
const selectedIndex = options.findIndex((option) => option.value === value && !option.disabled);
|
|
20
25
|
const tabStop = selectedIndex >= 0 ? selectedIndex : options.findIndex((option) => !option.disabled);
|
|
26
|
+
const slide = indicator === "slide";
|
|
27
|
+
const equal = slide || equalWidth || optionWidth !== void 0;
|
|
28
|
+
const knobIndex = options.findIndex((option) => option.value === value);
|
|
29
|
+
const layout = {
|
|
30
|
+
...equal ? { "--hc-segmented-count": options.length } : {},
|
|
31
|
+
...slide ? { "--hc-segmented-index": Math.max(knobIndex, 0) } : {},
|
|
32
|
+
...optionWidth !== void 0 ? { "--hc-segmented-option-width": `${optionWidth}px` } : {}
|
|
33
|
+
};
|
|
21
34
|
const move = (index, key) => {
|
|
22
35
|
if (!["ArrowRight", "ArrowLeft", "ArrowDown", "ArrowUp", "Home", "End"].includes(key)) return false;
|
|
23
36
|
const direction = key === "ArrowRight" || key === "ArrowDown" ? 1 : -1;
|
|
@@ -36,7 +49,7 @@ function SegmentedControl({
|
|
|
36
49
|
buttons.current[next]?.focus();
|
|
37
50
|
return true;
|
|
38
51
|
};
|
|
39
|
-
return /* @__PURE__ */
|
|
52
|
+
return /* @__PURE__ */ jsxs(
|
|
40
53
|
"div",
|
|
41
54
|
{
|
|
42
55
|
...props,
|
|
@@ -47,35 +60,41 @@ function SegmentedControl({
|
|
|
47
60
|
"data-density": density,
|
|
48
61
|
"data-orientation": orientation,
|
|
49
62
|
"data-labels": hideLabels ? "hidden" : void 0,
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
63
|
+
"data-indicator": slide ? "slide" : void 0,
|
|
64
|
+
"data-equal-width": equal || void 0,
|
|
65
|
+
style: { ...layout, ...style },
|
|
66
|
+
children: [
|
|
67
|
+
slide && knobIndex >= 0 && /* @__PURE__ */ jsx("span", { className: "hc-ui-segmented__indicator", "aria-hidden": "true" }),
|
|
68
|
+
options.map((option, index) => {
|
|
69
|
+
const selected = option.value === value;
|
|
70
|
+
return /* @__PURE__ */ jsxs(
|
|
71
|
+
"button",
|
|
72
|
+
{
|
|
73
|
+
ref: (element) => {
|
|
74
|
+
buttons.current[index] = element;
|
|
75
|
+
},
|
|
76
|
+
type: "button",
|
|
77
|
+
role: "radio",
|
|
78
|
+
"aria-checked": selected,
|
|
79
|
+
"aria-label": option.label,
|
|
80
|
+
title: hideLabels ? option.label : void 0,
|
|
81
|
+
disabled: option.disabled,
|
|
82
|
+
tabIndex: !option.disabled && index === tabStop ? 0 : -1,
|
|
83
|
+
className: "hc-ui-segmented__option",
|
|
84
|
+
"data-selected": selected || void 0,
|
|
85
|
+
onClick: () => !option.disabled && onChange(option.value),
|
|
86
|
+
onKeyDown: (event) => {
|
|
87
|
+
if (move(index, event.key)) event.preventDefault();
|
|
88
|
+
},
|
|
89
|
+
children: [
|
|
90
|
+
option.icon && /* @__PURE__ */ jsx("span", { className: "hc-ui-segmented__icon", "aria-hidden": "true", children: option.icon }),
|
|
91
|
+
!hideLabels && /* @__PURE__ */ jsx("span", { className: "hc-ui-segmented__label", children: option.label })
|
|
92
|
+
]
|
|
70
93
|
},
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
},
|
|
76
|
-
option.value
|
|
77
|
-
);
|
|
78
|
-
})
|
|
94
|
+
option.value
|
|
95
|
+
);
|
|
96
|
+
})
|
|
97
|
+
]
|
|
79
98
|
}
|
|
80
99
|
);
|
|
81
100
|
}
|
|
@@ -106,7 +125,7 @@ var Button = forwardRef(function Button2({ variant = "secondary", size = "md", i
|
|
|
106
125
|
}
|
|
107
126
|
);
|
|
108
127
|
});
|
|
109
|
-
var IconButton = forwardRef(function IconButton2({ label, icon, size = "md", tone = "primary", selected, type = "button", title, className, ...props }, ref) {
|
|
128
|
+
var IconButton = forwardRef(function IconButton2({ label, icon, size = "md", tone = "primary", variant = "muted", shape = "circle", selected, iconKey, type = "button", title, className, ...props }, ref) {
|
|
110
129
|
return /* @__PURE__ */ jsx(
|
|
111
130
|
"button",
|
|
112
131
|
{
|
|
@@ -119,16 +138,63 @@ var IconButton = forwardRef(function IconButton2({ label, icon, size = "md", ton
|
|
|
119
138
|
className: classes("hc-ui-icon-button", className),
|
|
120
139
|
"data-size": size,
|
|
121
140
|
"data-tone": tone,
|
|
122
|
-
|
|
141
|
+
"data-variant": variant === "muted" ? void 0 : variant,
|
|
142
|
+
"data-shape": shape === "circle" ? void 0 : shape,
|
|
143
|
+
children: /* @__PURE__ */ jsx("span", { className: "hc-ui-icon-button__icon", "aria-hidden": "true", "data-swap": iconKey === void 0 ? void 0 : "", children: icon }, iconKey)
|
|
123
144
|
}
|
|
124
145
|
);
|
|
125
146
|
});
|
|
126
|
-
var
|
|
127
|
-
|
|
147
|
+
var glyph = (size, d) => /* @__PURE__ */ jsx("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", strokeWidth: 1.8, "aria-hidden": "true", focusable: "false", children: /* @__PURE__ */ jsx("path", { d, stroke: "currentColor", strokeLinecap: "round", strokeLinejoin: "round" }) });
|
|
148
|
+
var CHEVRON_DOWN = "M6 9L12 15L18 9";
|
|
149
|
+
var XMARK = "M6.75827 17.2426L12.0009 12M17.2435 6.75736L12.0009 12M12.0009 12L6.75827 6.75736M12.0009 12L17.2435 17.2426";
|
|
150
|
+
var visible = (node) => node !== void 0 && node !== null && node !== false && node !== "";
|
|
151
|
+
var Chip = forwardRef(function Chip2({ selected, icon, size = "md", variant = "outline", applied = false, count, badge, chevron, onClear, clearLabel, type = "button", className, children, ...props }, ref) {
|
|
152
|
+
const popup = props["aria-haspopup"];
|
|
153
|
+
const withChevron = chevron ?? (popup !== void 0 && popup !== false && popup !== "false");
|
|
154
|
+
const look = {
|
|
155
|
+
"data-size": size === "md" ? void 0 : size,
|
|
156
|
+
"data-variant": variant === "outline" ? void 0 : variant,
|
|
157
|
+
"data-applied": applied || void 0
|
|
158
|
+
};
|
|
159
|
+
const content = /* @__PURE__ */ jsxs(Fragment2, { children: [
|
|
128
160
|
icon && /* @__PURE__ */ jsx("span", { className: "hc-ui-chip__icon", "aria-hidden": "true", children: icon }),
|
|
129
|
-
children
|
|
161
|
+
children,
|
|
162
|
+
visible(count) && /* @__PURE__ */ jsx("span", { className: "hc-ui-chip__count", children: count }),
|
|
163
|
+
visible(badge) && /* @__PURE__ */ jsx("span", { className: "hc-ui-chip__badge", children: badge }),
|
|
164
|
+
withChevron && /* @__PURE__ */ jsx("span", { className: "hc-ui-chip__chevron", "aria-hidden": "true", children: glyph(size === "xs" ? 12 : 14, CHEVRON_DOWN) })
|
|
165
|
+
] });
|
|
166
|
+
if (!onClear) {
|
|
167
|
+
return /* @__PURE__ */ jsx("button", { ...props, ref, type, "aria-pressed": selected, className: classes("hc-ui-chip", className), ...look, children: content });
|
|
168
|
+
}
|
|
169
|
+
const { style, id, onClick, disabled, ...rest } = props;
|
|
170
|
+
return /* @__PURE__ */ jsxs("span", { className: classes("hc-ui-chip", className), style, ...look, "data-clearable": "", "data-selected": selected || void 0, "data-disabled": disabled || void 0, children: [
|
|
171
|
+
onClick ? /* @__PURE__ */ jsx("button", { ...rest, id, ref, type, disabled, "aria-pressed": selected, className: "hc-ui-chip__main", onClick, children: content }) : /* @__PURE__ */ jsx("span", { id, className: "hc-ui-chip__main", children: content }),
|
|
172
|
+
/* @__PURE__ */ jsx("button", { type: "button", className: "hc-ui-chip__clear", "aria-label": clearLabel, title: clearLabel, disabled, onClick: onClear, children: glyph(12, XMARK) })
|
|
130
173
|
] });
|
|
131
174
|
});
|
|
175
|
+
var FilterBar = forwardRef(function FilterBar2({ label, start, end, footer, layout = "auto", density = "comfortable", sticky = false, className, children, ...props }, ref) {
|
|
176
|
+
return /* @__PURE__ */ jsxs(
|
|
177
|
+
"div",
|
|
178
|
+
{
|
|
179
|
+
...props,
|
|
180
|
+
ref,
|
|
181
|
+
role: "group",
|
|
182
|
+
"aria-label": label,
|
|
183
|
+
className: classes("hc-ui-filter-bar", className),
|
|
184
|
+
"data-layout": layout,
|
|
185
|
+
"data-density": density === "comfortable" ? void 0 : density,
|
|
186
|
+
"data-sticky": sticky || void 0,
|
|
187
|
+
children: [
|
|
188
|
+
/* @__PURE__ */ jsxs("div", { className: "hc-ui-filter-bar__line", children: [
|
|
189
|
+
start && /* @__PURE__ */ jsx("div", { className: "hc-ui-filter-bar__start", children: start }),
|
|
190
|
+
/* @__PURE__ */ jsx("div", { className: "hc-ui-filter-bar__row", children }),
|
|
191
|
+
end && /* @__PURE__ */ jsx("div", { className: "hc-ui-filter-bar__end", children: end })
|
|
192
|
+
] }),
|
|
193
|
+
footer && /* @__PURE__ */ jsx("div", { className: "hc-ui-filter-bar__footer", children: footer })
|
|
194
|
+
]
|
|
195
|
+
}
|
|
196
|
+
);
|
|
197
|
+
});
|
|
132
198
|
var Field = forwardRef(function Field2({ label, hideLabel = false, density = "comfortable", icon, end, hint, error, id, className, disabled, "aria-describedby": describedBy, ...props }, ref) {
|
|
133
199
|
const generated = useId();
|
|
134
200
|
const inputId = id ?? generated;
|
|
@@ -169,12 +235,1233 @@ function Skeleton({ width, height, circle = false, className, style, ...props })
|
|
|
169
235
|
}
|
|
170
236
|
);
|
|
171
237
|
}
|
|
238
|
+
var finite = (number) => Number.isFinite(number) ? number : 0;
|
|
239
|
+
var Meter = forwardRef(function Meter2({ value, max, label, valueText, decorative = false, thickness = 4, track = "muted", cap = "round", overflow = "clamp", tone = "auto", animated = false, className, ...props }, ref) {
|
|
240
|
+
const amount = Math.max(0, finite(value));
|
|
241
|
+
const limit = Math.max(0, finite(max));
|
|
242
|
+
const over = tone === "over" || tone === "auto" && amount > limit;
|
|
243
|
+
const scale = overflow === "mark" ? Math.max(limit, amount, 1) : limit;
|
|
244
|
+
const share = scale > 0 ? Math.min(100, amount / scale * 100) : amount > 0 ? 100 : 0;
|
|
245
|
+
const marker = overflow === "mark" && amount > limit ? limit / scale * 100 : null;
|
|
246
|
+
const semantics = decorative ? { "aria-hidden": true } : {
|
|
247
|
+
role: "meter",
|
|
248
|
+
"aria-label": label,
|
|
249
|
+
"aria-valuemin": 0,
|
|
250
|
+
"aria-valuemax": limit,
|
|
251
|
+
// ARIA keeps aria-valuenow inside the range; the text carries the real amount.
|
|
252
|
+
"aria-valuenow": Math.min(amount, limit),
|
|
253
|
+
"aria-valuetext": valueText ?? `${finite(value)} / ${finite(max)}`
|
|
254
|
+
};
|
|
255
|
+
return /* @__PURE__ */ jsxs(
|
|
256
|
+
"span",
|
|
257
|
+
{
|
|
258
|
+
...props,
|
|
259
|
+
...semantics,
|
|
260
|
+
ref,
|
|
261
|
+
className: classes("hc-ui-meter", className),
|
|
262
|
+
"data-thickness": thickness,
|
|
263
|
+
"data-track": track,
|
|
264
|
+
"data-cap": cap,
|
|
265
|
+
"data-tone": over ? "over" : void 0,
|
|
266
|
+
"data-animated": animated || void 0,
|
|
267
|
+
children: [
|
|
268
|
+
/* @__PURE__ */ jsx("span", { className: "hc-ui-meter__fill", style: { width: `${share}%` } }),
|
|
269
|
+
marker !== null && /* @__PURE__ */ jsx("span", { className: "hc-ui-meter__limit", style: { left: `${marker}%` } })
|
|
270
|
+
]
|
|
271
|
+
}
|
|
272
|
+
);
|
|
273
|
+
});
|
|
274
|
+
var checkboxIcon = (className, d) => /* @__PURE__ */ jsx("svg", { className, viewBox: "0 0 24 24", fill: "none", strokeWidth: 2.4, "aria-hidden": "true", focusable: "false", children: /* @__PURE__ */ jsx("path", { d, stroke: "currentColor", strokeLinecap: "round", strokeLinejoin: "round" }) });
|
|
275
|
+
var Checkbox = forwardRef(function Checkbox2({ size = "md", indeterminate = false, className, ...props }, ref) {
|
|
276
|
+
const inputRef = useRef(null);
|
|
277
|
+
useImperativeHandle(ref, () => inputRef.current);
|
|
278
|
+
useEffect(() => {
|
|
279
|
+
if (inputRef.current) inputRef.current.indeterminate = indeterminate;
|
|
280
|
+
}, [indeterminate]);
|
|
281
|
+
return /* @__PURE__ */ jsxs("span", { className: classes("hc-ui-checkbox", className), "data-size": size, children: [
|
|
282
|
+
/* @__PURE__ */ jsx("input", { ...props, ref: inputRef, type: "checkbox", className: "hc-ui-checkbox__input" }),
|
|
283
|
+
/* @__PURE__ */ jsxs("span", { className: "hc-ui-checkbox__box", "aria-hidden": "true", children: [
|
|
284
|
+
checkboxIcon("hc-ui-checkbox__mark", "M5 13L9 17L19 7"),
|
|
285
|
+
checkboxIcon("hc-ui-checkbox__mixed", "M6 12H18")
|
|
286
|
+
] })
|
|
287
|
+
] });
|
|
288
|
+
});
|
|
289
|
+
var useIsomorphicLayoutEffect = typeof document === "undefined" ? useEffect : useLayoutEffect;
|
|
290
|
+
var scrollLocks = 0;
|
|
291
|
+
var restoreScroll = () => {
|
|
292
|
+
};
|
|
293
|
+
function lockScroll() {
|
|
294
|
+
if (scrollLocks++ === 0) {
|
|
295
|
+
const root = document.documentElement;
|
|
296
|
+
const { overflow, scrollbarGutter } = root.style;
|
|
297
|
+
const hasScrollbar = window.innerWidth > root.clientWidth;
|
|
298
|
+
root.style.overflow = "hidden";
|
|
299
|
+
if (hasScrollbar) root.style.scrollbarGutter = "stable";
|
|
300
|
+
restoreScroll = () => {
|
|
301
|
+
root.style.overflow = overflow;
|
|
302
|
+
root.style.scrollbarGutter = scrollbarGutter;
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
let released = false;
|
|
306
|
+
return () => {
|
|
307
|
+
if (released) return;
|
|
308
|
+
released = true;
|
|
309
|
+
if (--scrollLocks === 0) restoreScroll();
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
function DialogMount({ onMount }) {
|
|
313
|
+
useIsomorphicLayoutEffect(onMount, []);
|
|
314
|
+
return null;
|
|
315
|
+
}
|
|
316
|
+
var closeGlyph = /* @__PURE__ */ jsx("svg", { width: 18, height: 18, viewBox: "0 0 24 24", fill: "none", strokeWidth: 1.8, "aria-hidden": "true", focusable: "false", children: /* @__PURE__ */ jsx("path", { d: "M6.75827 17.2426L12.0009 12M17.2435 6.75736L12.0009 12M12.0009 12L6.75827 6.75736M12.0009 12L17.2435 17.2426", stroke: "currentColor", strokeLinecap: "round", strokeLinejoin: "round" }) });
|
|
317
|
+
var outside = (element, x, y) => {
|
|
318
|
+
const box = element.getBoundingClientRect();
|
|
319
|
+
return x < box.left || x > box.right || y < box.top || y > box.bottom;
|
|
320
|
+
};
|
|
321
|
+
var Dialog = forwardRef(function Dialog2({
|
|
322
|
+
open,
|
|
323
|
+
onClose,
|
|
324
|
+
title,
|
|
325
|
+
description,
|
|
326
|
+
children,
|
|
327
|
+
actions,
|
|
328
|
+
closeLabel,
|
|
329
|
+
closeIcon,
|
|
330
|
+
size = "md",
|
|
331
|
+
dismissible = true,
|
|
332
|
+
className,
|
|
333
|
+
onClick,
|
|
334
|
+
onPointerDown,
|
|
335
|
+
"aria-describedby": describedBy,
|
|
336
|
+
...props
|
|
337
|
+
}, ref) {
|
|
338
|
+
const dialogRef = useRef(null);
|
|
339
|
+
const setRef = useCallback((node) => {
|
|
340
|
+
dialogRef.current = node;
|
|
341
|
+
if (typeof ref === "function") ref(node);
|
|
342
|
+
else if (ref) ref.current = node;
|
|
343
|
+
}, [ref]);
|
|
344
|
+
const id = useId();
|
|
345
|
+
const titleId = `${id}-title`;
|
|
346
|
+
const descriptionId = description ? `${id}-description` : void 0;
|
|
347
|
+
const [mounted, setMounted] = useState(open);
|
|
348
|
+
const [closing, setClosing] = useState(false);
|
|
349
|
+
if (open && (!mounted || closing)) {
|
|
350
|
+
setMounted(true);
|
|
351
|
+
setClosing(false);
|
|
352
|
+
}
|
|
353
|
+
if (!open && mounted && !closing) setClosing(true);
|
|
354
|
+
const latest = useRef({ open, onClose, dismissible, closing });
|
|
355
|
+
useIsomorphicLayoutEffect(() => {
|
|
356
|
+
latest.current = { open, onClose, dismissible, closing };
|
|
357
|
+
});
|
|
358
|
+
const keepFocus = useRef(null);
|
|
359
|
+
const handleMount = useCallback(() => {
|
|
360
|
+
const dialog = document.getElementById(titleId)?.closest("dialog");
|
|
361
|
+
if (!dialog) return () => {
|
|
362
|
+
};
|
|
363
|
+
const trigger = document.activeElement instanceof HTMLElement ? document.activeElement : null;
|
|
364
|
+
if (!dialog.open) dialog.showModal();
|
|
365
|
+
const kept = keepFocus.current;
|
|
366
|
+
keepFocus.current = null;
|
|
367
|
+
if (kept instanceof HTMLElement && dialog.contains(kept)) kept.focus();
|
|
368
|
+
const unlock = lockScroll();
|
|
369
|
+
return () => {
|
|
370
|
+
unlock();
|
|
371
|
+
keepFocus.current = dialog.contains(document.activeElement) ? document.activeElement : null;
|
|
372
|
+
if (dialog.open) dialog.close();
|
|
373
|
+
queueMicrotask(() => {
|
|
374
|
+
const active = document.activeElement;
|
|
375
|
+
const lost = !active || active === document.body || dialog.contains(active);
|
|
376
|
+
if (lost && !dialog.open && trigger?.isConnected) trigger.focus({ preventScroll: true });
|
|
377
|
+
});
|
|
378
|
+
};
|
|
379
|
+
}, [titleId]);
|
|
380
|
+
useEffect(() => {
|
|
381
|
+
const dialog = dialogRef.current;
|
|
382
|
+
if (!closing || !dialog) return;
|
|
383
|
+
let done = false;
|
|
384
|
+
const finish = () => {
|
|
385
|
+
if (done) return;
|
|
386
|
+
done = true;
|
|
387
|
+
setMounted(false);
|
|
388
|
+
setClosing(false);
|
|
389
|
+
};
|
|
390
|
+
const animations = typeof dialog.getAnimations === "function" ? dialog.getAnimations({ subtree: true }).filter((animation) => {
|
|
391
|
+
const effect = animation.effect;
|
|
392
|
+
return effect?.target === dialog && effect.getComputedTiming().endTime !== Infinity;
|
|
393
|
+
}) : [];
|
|
394
|
+
if (animations.length) Promise.all(animations.map((animation) => animation.finished)).then(finish, finish);
|
|
395
|
+
else finish();
|
|
396
|
+
return () => {
|
|
397
|
+
done = true;
|
|
398
|
+
};
|
|
399
|
+
}, [closing]);
|
|
400
|
+
const ownEvent = (event) => event.target === event.currentTarget;
|
|
401
|
+
const handleCancel = (event) => {
|
|
402
|
+
if (!ownEvent(event)) return;
|
|
403
|
+
event.preventDefault();
|
|
404
|
+
if (latest.current.dismissible && !latest.current.closing) latest.current.onClose();
|
|
405
|
+
};
|
|
406
|
+
const handleNativeClose = (event) => {
|
|
407
|
+
const dialog = event.currentTarget;
|
|
408
|
+
if (!ownEvent(event) || dialog.open || !latest.current.open || latest.current.closing) return;
|
|
409
|
+
if (latest.current.dismissible) latest.current.onClose();
|
|
410
|
+
window.setTimeout(() => {
|
|
411
|
+
if (latest.current.open && !latest.current.closing && dialog.isConnected && !dialog.open) dialog.showModal();
|
|
412
|
+
}, 0);
|
|
413
|
+
};
|
|
414
|
+
const pressedBackdrop = useRef(false);
|
|
415
|
+
const handlePointerDown = (event) => {
|
|
416
|
+
onPointerDown?.(event);
|
|
417
|
+
pressedBackdrop.current = ownEvent(event) && outside(event.currentTarget, event.clientX, event.clientY);
|
|
418
|
+
};
|
|
419
|
+
const handleClick = (event) => {
|
|
420
|
+
onClick?.(event);
|
|
421
|
+
const fromBackdrop = pressedBackdrop.current && ownEvent(event) && outside(event.currentTarget, event.clientX, event.clientY);
|
|
422
|
+
pressedBackdrop.current = false;
|
|
423
|
+
if (fromBackdrop && latest.current.dismissible && !latest.current.closing) latest.current.onClose();
|
|
424
|
+
};
|
|
425
|
+
if (!mounted) return null;
|
|
426
|
+
const hasBody = Boolean(description) || children !== void 0 && children !== null && children !== false;
|
|
427
|
+
return /* @__PURE__ */ jsxs(
|
|
428
|
+
"dialog",
|
|
429
|
+
{
|
|
430
|
+
...props,
|
|
431
|
+
ref: setRef,
|
|
432
|
+
className: classes("hc-ui-dialog", className),
|
|
433
|
+
"data-size": size,
|
|
434
|
+
"data-closing": closing || void 0,
|
|
435
|
+
"aria-labelledby": titleId,
|
|
436
|
+
"aria-describedby": classes(descriptionId, describedBy) || void 0,
|
|
437
|
+
onCancel: handleCancel,
|
|
438
|
+
onClose: handleNativeClose,
|
|
439
|
+
onPointerDown: handlePointerDown,
|
|
440
|
+
onClick: handleClick,
|
|
441
|
+
children: [
|
|
442
|
+
/* @__PURE__ */ jsx(DialogMount, { onMount: handleMount }),
|
|
443
|
+
/* @__PURE__ */ jsxs("div", { className: "hc-ui-dialog__header", children: [
|
|
444
|
+
/* @__PURE__ */ jsx("h2", { className: "hc-ui-dialog__title", id: titleId, children: title }),
|
|
445
|
+
closeLabel && /* @__PURE__ */ jsx(IconButton, { className: "hc-ui-dialog__close", label: closeLabel, icon: closeIcon ?? closeGlyph, size: "sm", tone: "secondary", onClick: () => latest.current.onClose() })
|
|
446
|
+
] }),
|
|
447
|
+
hasBody && /* @__PURE__ */ jsxs("div", { className: "hc-ui-dialog__body", children: [
|
|
448
|
+
description && /* @__PURE__ */ jsx("p", { className: "hc-ui-dialog__description", id: descriptionId, children: description }),
|
|
449
|
+
children
|
|
450
|
+
] }),
|
|
451
|
+
actions && /* @__PURE__ */ jsx("div", { className: "hc-ui-dialog__actions", children: actions })
|
|
452
|
+
]
|
|
453
|
+
}
|
|
454
|
+
);
|
|
455
|
+
});
|
|
456
|
+
var SHEET_EASE = "cubic-bezier(0.32, 0.72, 0, 1)";
|
|
457
|
+
var NO_DRAG = 'input, textarea, select, [contenteditable="true"], [data-hc-sheet-nodrag]';
|
|
458
|
+
function useSheetDrag(sheet, latest) {
|
|
459
|
+
useEffect(() => {
|
|
460
|
+
const el = sheet;
|
|
461
|
+
if (!el) return;
|
|
462
|
+
const reduce = window.matchMedia("(prefers-reduced-motion: reduce)");
|
|
463
|
+
let startX = 0;
|
|
464
|
+
let startY = 0;
|
|
465
|
+
let lastY = 0;
|
|
466
|
+
let lastT = 0;
|
|
467
|
+
let velocity = 0;
|
|
468
|
+
let tracking = false;
|
|
469
|
+
let dragging = false;
|
|
470
|
+
let inHandle = false;
|
|
471
|
+
let scroller = null;
|
|
472
|
+
const timers = /* @__PURE__ */ new Set();
|
|
473
|
+
const later = (run, ms) => {
|
|
474
|
+
const id = window.setTimeout(() => {
|
|
475
|
+
timers.delete(id);
|
|
476
|
+
run();
|
|
477
|
+
}, ms);
|
|
478
|
+
timers.add(id);
|
|
479
|
+
};
|
|
480
|
+
const expanded = () => el.hasAttribute("data-expanded");
|
|
481
|
+
const findScroller = (node) => {
|
|
482
|
+
while (node && node !== el.parentElement) {
|
|
483
|
+
const overflowY = getComputedStyle(node).overflowY;
|
|
484
|
+
if ((overflowY === "auto" || overflowY === "scroll") && node.scrollHeight > node.clientHeight + 1) return node;
|
|
485
|
+
if (node === el) break;
|
|
486
|
+
node = node.parentElement;
|
|
487
|
+
}
|
|
488
|
+
return null;
|
|
489
|
+
};
|
|
490
|
+
const settle = () => {
|
|
491
|
+
el.style.transition = reduce.matches ? "none" : `translate 320ms ${SHEET_EASE}`;
|
|
492
|
+
el.style.translate = "";
|
|
493
|
+
later(() => {
|
|
494
|
+
el.style.transition = "";
|
|
495
|
+
}, 340);
|
|
496
|
+
};
|
|
497
|
+
const onStart = (event) => {
|
|
498
|
+
if (event.touches.length !== 1 || getComputedStyle(el).getPropertyValue("--hc-sheet-drag").trim() !== "on") return;
|
|
499
|
+
const touch = event.touches[0];
|
|
500
|
+
const target = event.target;
|
|
501
|
+
if (target === el && outside(el, touch.clientX, touch.clientY)) return;
|
|
502
|
+
if (target.closest?.("dialog") !== el || target.closest(NO_DRAG)) return;
|
|
503
|
+
startX = touch.clientX;
|
|
504
|
+
startY = lastY = touch.clientY;
|
|
505
|
+
lastT = performance.now();
|
|
506
|
+
velocity = 0;
|
|
507
|
+
dragging = false;
|
|
508
|
+
tracking = true;
|
|
509
|
+
inHandle = Boolean(target.closest?.("[data-hc-sheet-handle]")) || touch.clientY - el.getBoundingClientRect().top < 64;
|
|
510
|
+
scroller = inHandle ? null : findScroller(target);
|
|
511
|
+
};
|
|
512
|
+
const onMove = (event) => {
|
|
513
|
+
if (!tracking) return;
|
|
514
|
+
const touch = event.touches[0];
|
|
515
|
+
const dy = touch.clientY - startY;
|
|
516
|
+
const dx = touch.clientX - startX;
|
|
517
|
+
if (!dragging) {
|
|
518
|
+
if (Math.abs(dx) > 8 && Math.abs(dx) > Math.abs(dy)) {
|
|
519
|
+
tracking = false;
|
|
520
|
+
return;
|
|
521
|
+
}
|
|
522
|
+
if (dy < -4 && !(inHandle && !expanded())) {
|
|
523
|
+
tracking = false;
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
526
|
+
if (Math.abs(dy) < 8) return;
|
|
527
|
+
if (scroller && scroller.scrollTop > 0) {
|
|
528
|
+
tracking = false;
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
531
|
+
dragging = true;
|
|
532
|
+
startY = touch.clientY;
|
|
533
|
+
el.style.transition = "none";
|
|
534
|
+
}
|
|
535
|
+
if (event.cancelable) event.preventDefault();
|
|
536
|
+
const now = performance.now();
|
|
537
|
+
velocity = (touch.clientY - lastY) / Math.max(1, now - lastT);
|
|
538
|
+
lastY = touch.clientY;
|
|
539
|
+
lastT = now;
|
|
540
|
+
const offset = touch.clientY - startY;
|
|
541
|
+
el.style.translate = `0 ${offset > 0 ? offset : offset / 6}px`;
|
|
542
|
+
};
|
|
543
|
+
const onEnd = () => {
|
|
544
|
+
if (!tracking) return;
|
|
545
|
+
tracking = false;
|
|
546
|
+
if (!dragging) return;
|
|
547
|
+
dragging = false;
|
|
548
|
+
const dy = lastY - startY;
|
|
549
|
+
const height = el.getBoundingClientRect().height || 1;
|
|
550
|
+
if (dy < -40 || velocity < -0.5) {
|
|
551
|
+
el.setAttribute("data-expanded", "");
|
|
552
|
+
settle();
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
555
|
+
const goingDown = dy > height * 0.2 || velocity > 0.5 && dy > 20;
|
|
556
|
+
if (goingDown && expanded()) {
|
|
557
|
+
el.removeAttribute("data-expanded");
|
|
558
|
+
settle();
|
|
559
|
+
return;
|
|
560
|
+
}
|
|
561
|
+
if (!goingDown || !latest.current.dismissible) {
|
|
562
|
+
settle();
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
565
|
+
el.style.transition = reduce.matches ? "none" : `translate 220ms ${SHEET_EASE}`;
|
|
566
|
+
el.style.translate = "0 105%";
|
|
567
|
+
el.setAttribute("data-dismissing", "");
|
|
568
|
+
later(() => latest.current.onClose(), reduce.matches ? 0 : 200);
|
|
569
|
+
later(() => {
|
|
570
|
+
if (!el.isConnected || el.hasAttribute("data-closing")) return;
|
|
571
|
+
el.removeAttribute("data-dismissing");
|
|
572
|
+
el.style.transition = reduce.matches ? "none" : `translate 320ms ${SHEET_EASE}`;
|
|
573
|
+
el.style.translate = "";
|
|
574
|
+
}, 900);
|
|
575
|
+
};
|
|
576
|
+
el.addEventListener("touchstart", onStart, { passive: true });
|
|
577
|
+
el.addEventListener("touchmove", onMove, { passive: false });
|
|
578
|
+
el.addEventListener("touchend", onEnd);
|
|
579
|
+
el.addEventListener("touchcancel", onEnd);
|
|
580
|
+
return () => {
|
|
581
|
+
el.removeEventListener("touchstart", onStart);
|
|
582
|
+
el.removeEventListener("touchmove", onMove);
|
|
583
|
+
el.removeEventListener("touchend", onEnd);
|
|
584
|
+
el.removeEventListener("touchcancel", onEnd);
|
|
585
|
+
timers.forEach((id) => window.clearTimeout(id));
|
|
586
|
+
};
|
|
587
|
+
}, [sheet, latest]);
|
|
588
|
+
}
|
|
589
|
+
var Sheet = forwardRef(function Sheet2({ presentation = "auto", width = 440, footer, dismissible = true, onClose, className, style, ...props }, ref) {
|
|
590
|
+
const [node, setNode] = useState(null);
|
|
591
|
+
const setRef = useCallback((element) => {
|
|
592
|
+
setNode(element);
|
|
593
|
+
setRefs(element, ref);
|
|
594
|
+
}, [ref]);
|
|
595
|
+
const latest = useRef({ onClose, dismissible });
|
|
596
|
+
useIsomorphicLayoutEffect(() => {
|
|
597
|
+
latest.current = { onClose, dismissible };
|
|
598
|
+
});
|
|
599
|
+
useSheetDrag(node, latest);
|
|
600
|
+
const sized = width === 440 ? style : { ...style, "--hc-sheet-width": `${width}px` };
|
|
601
|
+
return /* @__PURE__ */ jsx(
|
|
602
|
+
Dialog,
|
|
603
|
+
{
|
|
604
|
+
...props,
|
|
605
|
+
ref: setRef,
|
|
606
|
+
size: "md",
|
|
607
|
+
actions: footer,
|
|
608
|
+
dismissible,
|
|
609
|
+
onClose,
|
|
610
|
+
className: classes("hc-ui-sheet", className),
|
|
611
|
+
style: sized,
|
|
612
|
+
"data-presentation": presentation
|
|
613
|
+
}
|
|
614
|
+
);
|
|
615
|
+
});
|
|
616
|
+
function useRovingRadios(options, value) {
|
|
617
|
+
const buttons = useRef([]);
|
|
618
|
+
const [focusIndex, setFocusIndex] = useState(null);
|
|
619
|
+
const chosen = options.findIndex((option) => option.value === value && !option.disabled);
|
|
620
|
+
const fallback = chosen >= 0 ? chosen : options.findIndex((option) => !option.disabled);
|
|
621
|
+
const tabStop = focusIndex !== null && options[focusIndex] && !options[focusIndex].disabled ? focusIndex : fallback;
|
|
622
|
+
const onKeyDown = (index) => (event) => {
|
|
623
|
+
const enabled = options.map((option, at) => option.disabled ? -1 : at).filter((at) => at >= 0);
|
|
624
|
+
if (!enabled.length) return;
|
|
625
|
+
const position = enabled.indexOf(index);
|
|
626
|
+
let next;
|
|
627
|
+
if (event.key === "ArrowDown" || event.key === "ArrowRight") next = enabled[(position + 1) % enabled.length];
|
|
628
|
+
else if (event.key === "ArrowUp" || event.key === "ArrowLeft") next = enabled[(position - 1 + enabled.length) % enabled.length];
|
|
629
|
+
else if (event.key === "Home") next = enabled[0];
|
|
630
|
+
else if (event.key === "End") next = enabled[enabled.length - 1];
|
|
631
|
+
else return;
|
|
632
|
+
event.preventDefault();
|
|
633
|
+
setFocusIndex(next);
|
|
634
|
+
buttons.current[next]?.focus();
|
|
635
|
+
};
|
|
636
|
+
const item = (index) => ({
|
|
637
|
+
ref: (element) => {
|
|
638
|
+
buttons.current[index] = element;
|
|
639
|
+
},
|
|
640
|
+
tabIndex: index === tabStop ? 0 : -1,
|
|
641
|
+
onKeyDown: onKeyDown(index),
|
|
642
|
+
onFocus: () => setFocusIndex(index)
|
|
643
|
+
});
|
|
644
|
+
return item;
|
|
645
|
+
}
|
|
646
|
+
function SheetOptions({ label, options, value, onChange, variant = "card", className, ...props }) {
|
|
647
|
+
const item = useRovingRadios(options, value);
|
|
648
|
+
const list = variant === "list";
|
|
649
|
+
return /* @__PURE__ */ jsx("div", { ...props, className: classes("hc-ui-sheet-options", className), role: "radiogroup", "aria-label": label, "data-variant": list ? "list" : void 0, children: options.map((option, index) => {
|
|
650
|
+
const on = option.value === value;
|
|
651
|
+
return /* @__PURE__ */ jsxs(
|
|
652
|
+
"button",
|
|
653
|
+
{
|
|
654
|
+
...item(index),
|
|
655
|
+
type: "button",
|
|
656
|
+
role: "radio",
|
|
657
|
+
"aria-checked": on,
|
|
658
|
+
disabled: option.disabled,
|
|
659
|
+
className: "hc-ui-sheet-options__row",
|
|
660
|
+
"data-selected": on || void 0,
|
|
661
|
+
onClick: () => onChange(option.value),
|
|
662
|
+
children: [
|
|
663
|
+
option.icon && /* @__PURE__ */ jsx("span", { className: "hc-ui-sheet-options__icon", "aria-hidden": "true", children: option.icon }),
|
|
664
|
+
/* @__PURE__ */ jsxs("span", { className: "hc-ui-sheet-options__text", children: [
|
|
665
|
+
/* @__PURE__ */ jsx("span", { className: "hc-ui-sheet-options__label", children: option.label }),
|
|
666
|
+
option.hint && /* @__PURE__ */ jsx("span", { className: "hc-ui-sheet-options__hint", children: option.hint })
|
|
667
|
+
] }),
|
|
668
|
+
/* @__PURE__ */ jsx("span", { className: "hc-ui-sheet-options__check", "aria-hidden": "true", children: on && /* @__PURE__ */ jsx("svg", { width: list ? 20 : 18, height: list ? 20 : 18, viewBox: "0 0 24 24", fill: "none", strokeWidth: list ? 1.8 : 2.4, "aria-hidden": "true", focusable: "false", children: /* @__PURE__ */ jsx("path", { d: "M5 13L9 17L19 7", stroke: "currentColor", strokeLinecap: "round", strokeLinejoin: "round" }) }) })
|
|
669
|
+
]
|
|
670
|
+
},
|
|
671
|
+
option.value
|
|
672
|
+
);
|
|
673
|
+
}) });
|
|
674
|
+
}
|
|
675
|
+
function SheetChips({ label, options, value, onChange, className, ...props }) {
|
|
676
|
+
const item = useRovingRadios(options, value);
|
|
677
|
+
return /* @__PURE__ */ jsx("div", { ...props, className: classes("hc-ui-sheet-chips", className), role: "radiogroup", "aria-label": label, children: options.map((option, index) => /* @__PURE__ */ jsx(
|
|
678
|
+
"button",
|
|
679
|
+
{
|
|
680
|
+
...item(index),
|
|
681
|
+
type: "button",
|
|
682
|
+
role: "radio",
|
|
683
|
+
"aria-checked": option.value === value,
|
|
684
|
+
disabled: option.disabled,
|
|
685
|
+
className: "hc-ui-chip",
|
|
686
|
+
onClick: () => onChange(option.value),
|
|
687
|
+
children: option.label
|
|
688
|
+
},
|
|
689
|
+
option.value
|
|
690
|
+
)) });
|
|
691
|
+
}
|
|
692
|
+
var GAP = 4;
|
|
693
|
+
function boundaryOf(anchor, clip) {
|
|
694
|
+
const box = { top: 0, left: 0, right: window.innerWidth, bottom: window.innerHeight };
|
|
695
|
+
if (!clip) return box;
|
|
696
|
+
for (let node = anchor.parentElement; node && node !== document.documentElement; node = node.parentElement) {
|
|
697
|
+
const style = getComputedStyle(node);
|
|
698
|
+
if (style.overflowX === "visible" && style.overflowY === "visible") continue;
|
|
699
|
+
const rect = node.getBoundingClientRect();
|
|
700
|
+
box.top = Math.max(box.top, rect.top);
|
|
701
|
+
box.left = Math.max(box.left, rect.left);
|
|
702
|
+
box.right = Math.min(box.right, rect.right);
|
|
703
|
+
box.bottom = Math.min(box.bottom, rect.bottom);
|
|
704
|
+
}
|
|
705
|
+
return box;
|
|
706
|
+
}
|
|
707
|
+
function placePopup(trigger, size, boundary, side, align) {
|
|
708
|
+
const room = { bottom: boundary.bottom - trigger.bottom - GAP, top: trigger.top - boundary.top - GAP };
|
|
709
|
+
const other = side === "bottom" ? "top" : "bottom";
|
|
710
|
+
const finalSide = size.height > room[side] && room[other] > room[side] ? other : side;
|
|
711
|
+
const fits = (edge) => edge === "end" ? trigger.right - size.width >= boundary.left : trigger.left + size.width <= boundary.right;
|
|
712
|
+
const finalAlign = !fits(align) && fits(align === "end" ? "start" : "end") ? align === "end" ? "start" : "end" : align;
|
|
713
|
+
return { side: finalSide, align: finalAlign };
|
|
714
|
+
}
|
|
715
|
+
var layers = [];
|
|
716
|
+
function themeOf(element) {
|
|
717
|
+
const scoped = element?.closest("[data-theme]")?.getAttribute("data-theme");
|
|
718
|
+
if (scoped === "light" || scoped === "dark") return scoped;
|
|
719
|
+
const classed = element?.closest(".dark, .light");
|
|
720
|
+
return classed ? classed.classList.contains("dark") ? "dark" : "light" : void 0;
|
|
721
|
+
}
|
|
722
|
+
var setRefs = (node, ...refs) => {
|
|
723
|
+
for (const ref of refs) {
|
|
724
|
+
if (typeof ref === "function") ref(node);
|
|
725
|
+
else if (ref) ref.current = node;
|
|
726
|
+
}
|
|
727
|
+
};
|
|
728
|
+
function usePopup({ kind, trigger, open: openProp, defaultOpen = false, onOpenChange, side = "bottom", align = "end", portal = false, theme, contentId, onShown }) {
|
|
729
|
+
const [uncontrolled, setUncontrolled] = useState(defaultOpen);
|
|
730
|
+
const open = openProp ?? uncontrolled;
|
|
731
|
+
const generated = useId();
|
|
732
|
+
const id = contentId ?? `${generated}-popup`;
|
|
733
|
+
const triggerRef = useRef(null);
|
|
734
|
+
const anchorRef = useRef(null);
|
|
735
|
+
const contentRef = useRef(null);
|
|
736
|
+
const focusAfter = useRef("none");
|
|
737
|
+
const reason = useRef("first");
|
|
738
|
+
const [placement, setPlacement] = useState({ side, align });
|
|
739
|
+
const [container, setContainer] = useState(null);
|
|
740
|
+
const latest = useRef({ open, onOpenChange, onShown });
|
|
741
|
+
useIsomorphicLayoutEffect(() => {
|
|
742
|
+
latest.current = { open, onOpenChange, onShown };
|
|
743
|
+
});
|
|
744
|
+
const setOpen = useCallback((next, after = "none") => {
|
|
745
|
+
if (next === latest.current.open) return;
|
|
746
|
+
if (!next) focusAfter.current = after;
|
|
747
|
+
if (openProp === void 0) setUncontrolled(next);
|
|
748
|
+
latest.current.onOpenChange?.(next);
|
|
749
|
+
}, [openProp]);
|
|
750
|
+
const close = useCallback((after = "trigger") => setOpen(false, after), [setOpen]);
|
|
751
|
+
const show = (why) => {
|
|
752
|
+
reason.current = why;
|
|
753
|
+
setOpen(true);
|
|
754
|
+
};
|
|
755
|
+
useIsomorphicLayoutEffect(() => {
|
|
756
|
+
if (!open || !portal) {
|
|
757
|
+
setContainer(null);
|
|
758
|
+
return;
|
|
759
|
+
}
|
|
760
|
+
const element = portal instanceof HTMLElement ? portal : triggerRef.current?.closest("dialog[open]") ?? document.body;
|
|
761
|
+
setContainer({ element, theme: theme ?? themeOf(triggerRef.current) });
|
|
762
|
+
}, [open, portal, theme]);
|
|
763
|
+
const place = useCallback(() => {
|
|
764
|
+
const content = contentRef.current;
|
|
765
|
+
const anchor = anchorRef.current;
|
|
766
|
+
if (!content || !anchor) return;
|
|
767
|
+
const triggerBox = (triggerRef.current ?? anchor).getBoundingClientRect();
|
|
768
|
+
const size = { width: content.offsetWidth, height: content.offsetHeight };
|
|
769
|
+
if (!portal) {
|
|
770
|
+
setPlacement(placePopup(anchor.getBoundingClientRect(), size, boundaryOf(anchor, true), side, align));
|
|
771
|
+
return;
|
|
772
|
+
}
|
|
773
|
+
const next = placePopup(triggerBox, size, boundaryOf(anchor, false), side, align);
|
|
774
|
+
const style = content.style;
|
|
775
|
+
style.top = "0px";
|
|
776
|
+
style.left = "0px";
|
|
777
|
+
const origin = content.getBoundingClientRect();
|
|
778
|
+
const top = next.side === "bottom" ? triggerBox.bottom + GAP : triggerBox.top - GAP - size.height;
|
|
779
|
+
const left = next.align === "end" ? triggerBox.right - size.width : triggerBox.left;
|
|
780
|
+
style.top = `${top - origin.top}px`;
|
|
781
|
+
style.left = `${left - origin.left}px`;
|
|
782
|
+
setPlacement({ ...next, top: top - origin.top, left: left - origin.left });
|
|
783
|
+
}, [portal, side, align]);
|
|
784
|
+
const ready = open && (!portal || container !== null);
|
|
785
|
+
useIsomorphicLayoutEffect(() => {
|
|
786
|
+
if (!ready) return;
|
|
787
|
+
place();
|
|
788
|
+
const content = contentRef.current;
|
|
789
|
+
if (content) latest.current.onShown?.(content, reason.current);
|
|
790
|
+
}, [ready]);
|
|
791
|
+
useIsomorphicLayoutEffect(() => {
|
|
792
|
+
if (!ready) setPlacement({ side, align });
|
|
793
|
+
}, [ready, side, align]);
|
|
794
|
+
const handlers = useRef({ close, place });
|
|
795
|
+
useIsomorphicLayoutEffect(() => {
|
|
796
|
+
handlers.current = { close, place };
|
|
797
|
+
});
|
|
798
|
+
useEffect(() => {
|
|
799
|
+
if (!ready) return;
|
|
800
|
+
const layer = { content: () => contentRef.current };
|
|
801
|
+
layers.push(layer);
|
|
802
|
+
const isTop = () => layers[layers.length - 1] === layer;
|
|
803
|
+
const onKey = (event) => {
|
|
804
|
+
if (event.key !== "Escape" || !isTop() || event.defaultPrevented) return;
|
|
805
|
+
event.preventDefault();
|
|
806
|
+
event.stopPropagation();
|
|
807
|
+
handlers.current.close("trigger");
|
|
808
|
+
};
|
|
809
|
+
const onPointer = (event) => {
|
|
810
|
+
const target = event.target;
|
|
811
|
+
if (anchorRef.current?.contains(target) || contentRef.current?.contains(target)) return;
|
|
812
|
+
const index = layers.indexOf(layer);
|
|
813
|
+
if (layers.slice(index + 1).some((other) => other.content()?.contains(target))) return;
|
|
814
|
+
handlers.current.close("none");
|
|
815
|
+
};
|
|
816
|
+
const onMove = () => handlers.current.place();
|
|
817
|
+
document.addEventListener("keydown", onKey, true);
|
|
818
|
+
document.addEventListener("pointerdown", onPointer, true);
|
|
819
|
+
window.addEventListener("resize", onMove);
|
|
820
|
+
if (portal) window.addEventListener("scroll", onMove, true);
|
|
821
|
+
return () => {
|
|
822
|
+
layers.splice(layers.indexOf(layer), 1);
|
|
823
|
+
document.removeEventListener("keydown", onKey, true);
|
|
824
|
+
document.removeEventListener("pointerdown", onPointer, true);
|
|
825
|
+
window.removeEventListener("resize", onMove);
|
|
826
|
+
window.removeEventListener("scroll", onMove, true);
|
|
827
|
+
};
|
|
828
|
+
}, [ready, portal]);
|
|
829
|
+
useIsomorphicLayoutEffect(() => {
|
|
830
|
+
if (open) return;
|
|
831
|
+
const after = focusAfter.current;
|
|
832
|
+
focusAfter.current = "none";
|
|
833
|
+
const active = document.activeElement;
|
|
834
|
+
const lost = !active || active === document.body;
|
|
835
|
+
if (after === "trigger" || after === "if-lost" && lost) triggerRef.current?.focus({ preventScroll: true });
|
|
836
|
+
}, [open]);
|
|
837
|
+
const triggerProps = {
|
|
838
|
+
ref: (node) => {
|
|
839
|
+
triggerRef.current = node;
|
|
840
|
+
},
|
|
841
|
+
"aria-haspopup": kind,
|
|
842
|
+
"aria-expanded": open,
|
|
843
|
+
"aria-controls": open ? id : void 0,
|
|
844
|
+
onClick: (event) => {
|
|
845
|
+
if (event.defaultPrevented) return;
|
|
846
|
+
if (open) close("none");
|
|
847
|
+
else show(event.detail === 0 ? "first" : "pointer");
|
|
848
|
+
},
|
|
849
|
+
onKeyDown: (event) => {
|
|
850
|
+
if (event.defaultPrevented || kind !== "menu") return;
|
|
851
|
+
if (event.key === "ArrowDown" || event.key === "ArrowUp") {
|
|
852
|
+
event.preventDefault();
|
|
853
|
+
show(event.key === "ArrowUp" ? "last" : "first");
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
};
|
|
857
|
+
let triggerNode;
|
|
858
|
+
if (typeof trigger === "function") triggerNode = trigger(triggerProps, { open });
|
|
859
|
+
else {
|
|
860
|
+
const own = trigger.props;
|
|
861
|
+
const elementRef = trigger.ref;
|
|
862
|
+
triggerNode = cloneElement(trigger, {
|
|
863
|
+
...triggerProps,
|
|
864
|
+
ref: (node) => setRefs(node, triggerProps.ref, elementRef),
|
|
865
|
+
onClick: (event) => {
|
|
866
|
+
own.onClick?.(event);
|
|
867
|
+
triggerProps.onClick(event);
|
|
868
|
+
},
|
|
869
|
+
onKeyDown: (event) => {
|
|
870
|
+
own.onKeyDown?.(event);
|
|
871
|
+
triggerProps.onKeyDown(event);
|
|
872
|
+
}
|
|
873
|
+
});
|
|
874
|
+
}
|
|
875
|
+
const contentAttributes = {
|
|
876
|
+
id,
|
|
877
|
+
"data-side": placement.side,
|
|
878
|
+
"data-align": placement.align,
|
|
879
|
+
"data-portal": portal ? "" : void 0,
|
|
880
|
+
style: portal ? { top: placement.top ?? 0, left: placement.left ?? 0, right: "auto", bottom: "auto" } : void 0
|
|
881
|
+
};
|
|
882
|
+
const wrap = (content) => {
|
|
883
|
+
if (!open) return null;
|
|
884
|
+
if (!portal) return content;
|
|
885
|
+
if (!container) return null;
|
|
886
|
+
return createPortal(/* @__PURE__ */ jsx("div", { className: "hc-ui-portal", "data-hc-ds": "", "data-theme": container.theme, children: content }), container.element);
|
|
887
|
+
};
|
|
888
|
+
return { open, close, triggerNode, anchorRef, contentRef, contentAttributes, wrap, triggerRef };
|
|
889
|
+
}
|
|
890
|
+
var mergeStyle = (base, own) => base || own ? { ...own, ...base } : void 0;
|
|
891
|
+
var MenuContext = createContext(null);
|
|
892
|
+
var RadioContext = createContext(null);
|
|
893
|
+
var ITEM_SELECTOR = '[role="menuitem"], [role="menuitemradio"], [role="menuitemcheckbox"]';
|
|
894
|
+
var itemsOf = (menu) => [...menu.querySelectorAll(ITEM_SELECTOR)].filter((item) => !item.disabled && item.closest('[role="menu"]') === menu);
|
|
895
|
+
var itemText = (item) => (item.querySelector(".hc-ui-menu__text")?.firstChild?.textContent ?? item.textContent ?? "").trim().toLocaleLowerCase();
|
|
896
|
+
var Menu = forwardRef(function Menu2({ label, trigger, children, open, defaultOpen, onOpenChange, side, align, portal, theme, anchorClassName, className, style, id, onKeyDown, ...props }, ref) {
|
|
897
|
+
const typed = useRef({ text: "", at: 0 });
|
|
898
|
+
const popup = usePopup({
|
|
899
|
+
kind: "menu",
|
|
900
|
+
trigger,
|
|
901
|
+
open,
|
|
902
|
+
defaultOpen,
|
|
903
|
+
onOpenChange,
|
|
904
|
+
side,
|
|
905
|
+
align,
|
|
906
|
+
portal,
|
|
907
|
+
theme,
|
|
908
|
+
contentId: id,
|
|
909
|
+
onShown: (menu, why) => {
|
|
910
|
+
typed.current = { text: "", at: 0 };
|
|
911
|
+
const items = itemsOf(menu);
|
|
912
|
+
const selected = items.find((item) => item.getAttribute("role") === "menuitemradio" && item.getAttribute("aria-checked") === "true");
|
|
913
|
+
(why === "last" ? items[items.length - 1] : selected ?? items[0])?.focus({ preventScroll: true });
|
|
914
|
+
}
|
|
915
|
+
});
|
|
916
|
+
const { close } = popup;
|
|
917
|
+
const context = useMemo(() => ({ close }), [close]);
|
|
918
|
+
useIsomorphicLayoutEffect(() => {
|
|
919
|
+
const menu = popup.contentRef.current;
|
|
920
|
+
const active = document.activeElement;
|
|
921
|
+
const lost = !active || active === document.body || menu?.contains(active) && active.disabled;
|
|
922
|
+
if (popup.open && menu && lost) itemsOf(menu)[0]?.focus({ preventScroll: true });
|
|
923
|
+
});
|
|
924
|
+
const handleKeyDown = (event) => {
|
|
925
|
+
onKeyDown?.(event);
|
|
926
|
+
if (event.defaultPrevented) return;
|
|
927
|
+
const menu = event.currentTarget;
|
|
928
|
+
const items = itemsOf(menu);
|
|
929
|
+
const current = items.indexOf(document.activeElement);
|
|
930
|
+
const focus = (index) => items[(index + items.length) % items.length]?.focus();
|
|
931
|
+
switch (event.key) {
|
|
932
|
+
case "ArrowDown":
|
|
933
|
+
event.preventDefault();
|
|
934
|
+
focus(current + 1);
|
|
935
|
+
return;
|
|
936
|
+
case "ArrowUp":
|
|
937
|
+
event.preventDefault();
|
|
938
|
+
focus(current < 0 ? -1 : current - 1);
|
|
939
|
+
return;
|
|
940
|
+
case "Home":
|
|
941
|
+
event.preventDefault();
|
|
942
|
+
focus(0);
|
|
943
|
+
return;
|
|
944
|
+
case "End":
|
|
945
|
+
event.preventDefault();
|
|
946
|
+
focus(-1);
|
|
947
|
+
return;
|
|
948
|
+
case "Tab":
|
|
949
|
+
popup.triggerRef.current?.focus({ preventScroll: true });
|
|
950
|
+
close("none");
|
|
951
|
+
return;
|
|
952
|
+
}
|
|
953
|
+
if (event.key.length !== 1 || event.ctrlKey || event.metaKey || event.altKey || !items.length) return;
|
|
954
|
+
const now = Date.now();
|
|
955
|
+
const typing = typed.current.text !== "" && now - typed.current.at <= 500;
|
|
956
|
+
if (event.key === " " && !typing) return;
|
|
957
|
+
event.preventDefault();
|
|
958
|
+
const key = event.key.toLocaleLowerCase();
|
|
959
|
+
typed.current.text = typing ? typed.current.text + key : key;
|
|
960
|
+
typed.current.at = now;
|
|
961
|
+
const query = [...typed.current.text].every((letter) => letter === key) ? key : typed.current.text;
|
|
962
|
+
const start = query.length === 1 ? current + 1 : Math.max(current, 0);
|
|
963
|
+
for (let step = 0; step < items.length; step += 1) {
|
|
964
|
+
const item = items[(start + step) % items.length];
|
|
965
|
+
if (itemText(item).startsWith(query)) {
|
|
966
|
+
item.focus();
|
|
967
|
+
break;
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
};
|
|
971
|
+
return /* @__PURE__ */ jsxs("div", { className: classes("hc-ui-popup-anchor", anchorClassName), ref: popup.anchorRef, children: [
|
|
972
|
+
popup.triggerNode,
|
|
973
|
+
popup.wrap(
|
|
974
|
+
/* @__PURE__ */ jsx(MenuContext.Provider, { value: context, children: /* @__PURE__ */ jsx(
|
|
975
|
+
"div",
|
|
976
|
+
{
|
|
977
|
+
...props,
|
|
978
|
+
...popup.contentAttributes,
|
|
979
|
+
style: mergeStyle(popup.contentAttributes.style, style),
|
|
980
|
+
ref: (node) => setRefs(node, popup.contentRef, ref),
|
|
981
|
+
role: "menu",
|
|
982
|
+
"aria-label": label,
|
|
983
|
+
tabIndex: -1,
|
|
984
|
+
className: classes("hc-ui-menu", className),
|
|
985
|
+
onKeyDown: handleKeyDown,
|
|
986
|
+
children
|
|
987
|
+
}
|
|
988
|
+
) })
|
|
989
|
+
)
|
|
990
|
+
] });
|
|
991
|
+
});
|
|
992
|
+
function useMenu(name) {
|
|
993
|
+
const menu = useContext(MenuContext);
|
|
994
|
+
if (!menu) throw new Error(`${name} must be inside a Menu`);
|
|
995
|
+
return menu;
|
|
996
|
+
}
|
|
997
|
+
var checkGlyph = /* @__PURE__ */ jsx("svg", { width: 14, height: 14, viewBox: "0 0 24 24", fill: "none", strokeWidth: 1.8, "aria-hidden": "true", focusable: "false", children: /* @__PURE__ */ jsx("path", { d: "M5 13L9 17L19 7", stroke: "currentColor", strokeLinecap: "round", strokeLinejoin: "round" }) });
|
|
998
|
+
var MenuRow = forwardRef(function MenuRow2({ icon, hint, danger, end, role, onChoose, className, children, type = "button", onClick, ...props }, ref) {
|
|
999
|
+
return /* @__PURE__ */ jsxs(
|
|
1000
|
+
"button",
|
|
1001
|
+
{
|
|
1002
|
+
...props,
|
|
1003
|
+
ref,
|
|
1004
|
+
type,
|
|
1005
|
+
role,
|
|
1006
|
+
tabIndex: -1,
|
|
1007
|
+
className: classes("hc-ui-menu__item", className),
|
|
1008
|
+
"data-danger": danger || void 0,
|
|
1009
|
+
onClick: (event) => {
|
|
1010
|
+
onClick?.(event);
|
|
1011
|
+
if (!event.defaultPrevented) onChoose(event);
|
|
1012
|
+
},
|
|
1013
|
+
children: [
|
|
1014
|
+
/* @__PURE__ */ jsxs("span", { className: "hc-ui-menu__text", children: [
|
|
1015
|
+
children,
|
|
1016
|
+
hint && /* @__PURE__ */ jsx("small", { className: "hc-ui-menu__hint", children: hint })
|
|
1017
|
+
] }),
|
|
1018
|
+
end,
|
|
1019
|
+
icon && /* @__PURE__ */ jsx("span", { className: "hc-ui-menu__icon", "aria-hidden": "true", children: icon })
|
|
1020
|
+
]
|
|
1021
|
+
}
|
|
1022
|
+
);
|
|
1023
|
+
});
|
|
1024
|
+
var MenuItem = forwardRef(function MenuItem2({ onSelect, ...props }, ref) {
|
|
1025
|
+
const menu = useMenu("MenuItem");
|
|
1026
|
+
return /* @__PURE__ */ jsx(MenuRow, { ...props, ref, role: "menuitem", onChoose: (event) => {
|
|
1027
|
+
onSelect?.(event);
|
|
1028
|
+
if (!event.defaultPrevented) menu.close("if-lost");
|
|
1029
|
+
} });
|
|
1030
|
+
});
|
|
1031
|
+
function MenuRadioGroup({ value, onValueChange, label, className, children, ...props }) {
|
|
1032
|
+
const context = useMemo(() => ({ value, onValueChange }), [value, onValueChange]);
|
|
1033
|
+
return /* @__PURE__ */ jsx(RadioContext.Provider, { value: context, children: /* @__PURE__ */ jsx("div", { ...props, role: "group", "aria-label": label, className: classes("hc-ui-menu__group", className), children }) });
|
|
1034
|
+
}
|
|
1035
|
+
var MenuItemRadio = forwardRef(function MenuItemRadio2({ value, ...props }, ref) {
|
|
1036
|
+
const menu = useMenu("MenuItemRadio");
|
|
1037
|
+
const group = useContext(RadioContext);
|
|
1038
|
+
if (!group) throw new Error("MenuItemRadio must be inside a MenuRadioGroup");
|
|
1039
|
+
const checked = group.value === value;
|
|
1040
|
+
return /* @__PURE__ */ jsx(
|
|
1041
|
+
MenuRow,
|
|
1042
|
+
{
|
|
1043
|
+
...props,
|
|
1044
|
+
ref,
|
|
1045
|
+
role: "menuitemradio",
|
|
1046
|
+
"aria-checked": checked,
|
|
1047
|
+
end: checked ? /* @__PURE__ */ jsx("span", { className: "hc-ui-menu__check", children: checkGlyph }) : void 0,
|
|
1048
|
+
onChoose: () => {
|
|
1049
|
+
group.onValueChange(value);
|
|
1050
|
+
menu.close("trigger");
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
);
|
|
1054
|
+
});
|
|
1055
|
+
var MenuItemCheckbox = forwardRef(function MenuItemCheckbox2({ checked, onCheckedChange, closeOnSelect = false, ...props }, ref) {
|
|
1056
|
+
const menu = useMenu("MenuItemCheckbox");
|
|
1057
|
+
return /* @__PURE__ */ jsx(
|
|
1058
|
+
MenuRow,
|
|
1059
|
+
{
|
|
1060
|
+
...props,
|
|
1061
|
+
ref,
|
|
1062
|
+
role: "menuitemcheckbox",
|
|
1063
|
+
"aria-checked": checked,
|
|
1064
|
+
end: checked ? /* @__PURE__ */ jsx("span", { className: "hc-ui-menu__check", children: checkGlyph }) : void 0,
|
|
1065
|
+
onChoose: () => {
|
|
1066
|
+
onCheckedChange(!checked);
|
|
1067
|
+
if (closeOnSelect) menu.close("trigger");
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
);
|
|
1071
|
+
});
|
|
1072
|
+
function MenuSeparator({ className, ...props }) {
|
|
1073
|
+
return /* @__PURE__ */ jsx("div", { ...props, role: "separator", className: classes("hc-ui-menu__separator", className) });
|
|
1074
|
+
}
|
|
1075
|
+
function MenuLabel({ className, ...props }) {
|
|
1076
|
+
return /* @__PURE__ */ jsx("div", { ...props, className: classes("hc-ui-menu__label", className) });
|
|
1077
|
+
}
|
|
1078
|
+
function SortMenu({ label, value, options, onChange, applied = false, size = "sm", triggerClassName, ...props }) {
|
|
1079
|
+
const current = options.find((option) => option.value === value)?.label;
|
|
1080
|
+
return /* @__PURE__ */ jsx(
|
|
1081
|
+
Menu,
|
|
1082
|
+
{
|
|
1083
|
+
...props,
|
|
1084
|
+
label,
|
|
1085
|
+
trigger: /* @__PURE__ */ jsx(Chip, { size, applied, className: triggerClassName, "aria-label": current === void 0 ? label : `${label}: ${current}`, children: current }),
|
|
1086
|
+
children: /* @__PURE__ */ jsx(MenuRadioGroup, { value, onValueChange: onChange, children: options.map((option) => /* @__PURE__ */ jsx(MenuItemRadio, { value: option.value, hint: option.hint, disabled: option.disabled, children: option.label }, option.value)) })
|
|
1087
|
+
}
|
|
1088
|
+
);
|
|
1089
|
+
}
|
|
1090
|
+
var Popover = forwardRef(function Popover2({ label, trigger, children, autoFocus = false, open, defaultOpen, onOpenChange, side, align, portal, theme, anchorClassName, className, style, id, ...props }, ref) {
|
|
1091
|
+
const popup = usePopup({
|
|
1092
|
+
kind: "dialog",
|
|
1093
|
+
trigger,
|
|
1094
|
+
open,
|
|
1095
|
+
defaultOpen,
|
|
1096
|
+
onOpenChange,
|
|
1097
|
+
side,
|
|
1098
|
+
align,
|
|
1099
|
+
portal,
|
|
1100
|
+
theme,
|
|
1101
|
+
contentId: id,
|
|
1102
|
+
onShown: (content) => {
|
|
1103
|
+
if (!autoFocus) return;
|
|
1104
|
+
content.querySelector('button:not(:disabled), [href], input:not(:disabled), select:not(:disabled), textarea:not(:disabled), [tabindex]:not([tabindex="-1"])')?.focus({ preventScroll: true });
|
|
1105
|
+
}
|
|
1106
|
+
});
|
|
1107
|
+
const close = useCallback(() => popup.close("trigger"), [popup.close]);
|
|
1108
|
+
return /* @__PURE__ */ jsxs("div", { className: classes("hc-ui-popup-anchor", anchorClassName), ref: popup.anchorRef, children: [
|
|
1109
|
+
popup.triggerNode,
|
|
1110
|
+
popup.wrap(
|
|
1111
|
+
/* @__PURE__ */ jsx(
|
|
1112
|
+
"div",
|
|
1113
|
+
{
|
|
1114
|
+
...props,
|
|
1115
|
+
...popup.contentAttributes,
|
|
1116
|
+
style: mergeStyle(popup.contentAttributes.style, style),
|
|
1117
|
+
ref: (node) => setRefs(node, popup.contentRef, ref),
|
|
1118
|
+
role: "dialog",
|
|
1119
|
+
"aria-label": label,
|
|
1120
|
+
tabIndex: -1,
|
|
1121
|
+
className: classes("hc-ui-popover", className),
|
|
1122
|
+
children: typeof children === "function" ? children({ close }) : children
|
|
1123
|
+
}
|
|
1124
|
+
)
|
|
1125
|
+
)
|
|
1126
|
+
] });
|
|
1127
|
+
});
|
|
1128
|
+
var arrowGlyph = (up) => glyph(12, up ? "M12 21L12 3M12 3L20.5 11.5M12 3L3.5 11.5" : "M12 3L12 21M12 21L20.5 12.5M12 21L3.5 12.5");
|
|
1129
|
+
function TableInner({ columns, rows, rowKey, sort, onSort, stickyHeader = false, onRowClick, selectedKey, className, ...props }, ref) {
|
|
1130
|
+
const alignOf = (column) => column.align ?? (column.numeric ? "end" : "start");
|
|
1131
|
+
const cellLook = (column) => ({
|
|
1132
|
+
className: column.className,
|
|
1133
|
+
"data-align": alignOf(column),
|
|
1134
|
+
"data-numeric": column.numeric || void 0,
|
|
1135
|
+
"data-tone": column.tone
|
|
1136
|
+
});
|
|
1137
|
+
const rowHeaderKey = columns.find((column) => column.rowHeader)?.key;
|
|
1138
|
+
return /* @__PURE__ */ jsxs("table", { ...props, ref, className: classes("hc-ui-table", className), "data-sticky-header": stickyHeader || void 0, "data-clickable": onRowClick ? "" : void 0, children: [
|
|
1139
|
+
/* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsx("tr", { children: columns.map((column) => {
|
|
1140
|
+
const direction = column.sortable ? sort && sort.key === column.key ? sort.direction : "none" : void 0;
|
|
1141
|
+
return /* @__PURE__ */ jsx(
|
|
1142
|
+
"th",
|
|
1143
|
+
{
|
|
1144
|
+
scope: "col",
|
|
1145
|
+
...cellLook(column),
|
|
1146
|
+
"aria-sort": direction,
|
|
1147
|
+
style: column.width === void 0 ? void 0 : { width: column.width },
|
|
1148
|
+
children: column.sortable ? /* @__PURE__ */ jsxs("button", { type: "button", className: "hc-ui-table__sort", onClick: () => onSort?.(column.key), children: [
|
|
1149
|
+
column.header,
|
|
1150
|
+
direction !== "none" && arrowGlyph(direction === "ascending")
|
|
1151
|
+
] }) : column.header
|
|
1152
|
+
},
|
|
1153
|
+
column.key
|
|
1154
|
+
);
|
|
1155
|
+
}) }) }),
|
|
1156
|
+
/* @__PURE__ */ jsx("tbody", { children: rows.map((row, index) => {
|
|
1157
|
+
const key = rowKey(row, index);
|
|
1158
|
+
const selected = selectedKey !== void 0 && selectedKey !== null && key === selectedKey;
|
|
1159
|
+
const press = onRowClick && (() => onRowClick(row, index));
|
|
1160
|
+
return /* @__PURE__ */ jsx("tr", { onClick: press, "data-selected": selected || void 0, children: columns.map((column) => {
|
|
1161
|
+
const content = column.cell(row, index);
|
|
1162
|
+
if (!column.rowHeader) return /* @__PURE__ */ jsx("td", { ...cellLook(column), children: content }, column.key);
|
|
1163
|
+
return /* @__PURE__ */ jsx("th", { scope: "row", ...cellLook(column), children: press && column.key === rowHeaderKey ? /* @__PURE__ */ jsx(
|
|
1164
|
+
"button",
|
|
1165
|
+
{
|
|
1166
|
+
type: "button",
|
|
1167
|
+
className: "hc-ui-table__row-button",
|
|
1168
|
+
"aria-current": selected || void 0,
|
|
1169
|
+
onClick: (event) => {
|
|
1170
|
+
event.stopPropagation();
|
|
1171
|
+
press();
|
|
1172
|
+
},
|
|
1173
|
+
children: content
|
|
1174
|
+
}
|
|
1175
|
+
) : content }, column.key);
|
|
1176
|
+
}) }, key);
|
|
1177
|
+
}) })
|
|
1178
|
+
] });
|
|
1179
|
+
}
|
|
1180
|
+
var Table = forwardRef(TableInner);
|
|
1181
|
+
var Heading = ({ level, ...props }) => {
|
|
1182
|
+
const Tag = `h${level}`;
|
|
1183
|
+
return /* @__PURE__ */ jsx(Tag, { ...props });
|
|
1184
|
+
};
|
|
1185
|
+
var PageHeader = forwardRef(function PageHeader2({ title, level = 1, count, actions, visuallyHidden = false, className, ...props }, ref) {
|
|
1186
|
+
return /* @__PURE__ */ jsxs("header", { ...props, ref, className: classes("hc-ui-page-header", className), "data-visually-hidden": visuallyHidden || void 0, children: [
|
|
1187
|
+
/* @__PURE__ */ jsxs(Heading, { level, className: "hc-ui-page-header__title", children: [
|
|
1188
|
+
title,
|
|
1189
|
+
visible(count) && count !== 0 && /* @__PURE__ */ jsxs(Fragment2, { children: [
|
|
1190
|
+
" ",
|
|
1191
|
+
/* @__PURE__ */ jsx("small", { className: "hc-ui-page-header__count", children: count })
|
|
1192
|
+
] })
|
|
1193
|
+
] }),
|
|
1194
|
+
actions && /* @__PURE__ */ jsx("div", { className: "hc-ui-page-header__actions", children: actions })
|
|
1195
|
+
] });
|
|
1196
|
+
});
|
|
1197
|
+
var navArrow = glyph(14, "M9 6L15 12L9 18");
|
|
1198
|
+
var PanelHeader = forwardRef(function PanelHeader2({ title, level = 2, variant = "bar", eyebrow, subtitle, subtitleInline = false, icon, trail, trailLabel, actions, onClose, closeLabel, closeTitle, closeIcon, className, ...props }, ref) {
|
|
1199
|
+
const steps = trail && trail.length > 0 ? trail : null;
|
|
1200
|
+
return /* @__PURE__ */ jsxs(
|
|
1201
|
+
"header",
|
|
1202
|
+
{
|
|
1203
|
+
...props,
|
|
1204
|
+
ref,
|
|
1205
|
+
className: classes("hc-ui-panel-header", className),
|
|
1206
|
+
"data-variant": variant,
|
|
1207
|
+
"data-subtitle": subtitle && subtitleInline ? "inline" : void 0,
|
|
1208
|
+
children: [
|
|
1209
|
+
icon && /* @__PURE__ */ jsx("span", { className: "hc-ui-panel-header__icon", "aria-hidden": "true", children: icon }),
|
|
1210
|
+
steps ? /* @__PURE__ */ jsx("nav", { className: "hc-ui-panel-header__trail", "aria-label": trailLabel, children: steps.map((step, index) => {
|
|
1211
|
+
const last = index === steps.length - 1;
|
|
1212
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1213
|
+
index > 0 && navArrow,
|
|
1214
|
+
last ? /* @__PURE__ */ jsx(Heading, { level, className: "hc-ui-panel-header__title", "aria-current": "location", children: step.label }) : step.onSelect ? /* @__PURE__ */ jsx("button", { type: "button", className: "hc-ui-panel-header__step", onClick: step.onSelect, children: step.label }) : /* @__PURE__ */ jsx("strong", { className: "hc-ui-panel-header__title", children: step.label })
|
|
1215
|
+
] }, `${index}:${step.label}`);
|
|
1216
|
+
}) }) : /* @__PURE__ */ jsxs("div", { className: "hc-ui-panel-header__text", children: [
|
|
1217
|
+
eyebrow && /* @__PURE__ */ jsx("p", { className: "hc-ui-panel-header__eyebrow", children: eyebrow }),
|
|
1218
|
+
/* @__PURE__ */ jsx(Heading, { level, className: "hc-ui-panel-header__title", children: title }),
|
|
1219
|
+
subtitle && /* @__PURE__ */ jsx("p", { className: "hc-ui-panel-header__subtitle", children: subtitle })
|
|
1220
|
+
] }),
|
|
1221
|
+
actions && /* @__PURE__ */ jsx("div", { className: "hc-ui-panel-header__actions", children: actions }),
|
|
1222
|
+
onClose && /* @__PURE__ */ jsx("button", { type: "button", className: "hc-ui-panel-header__close", onClick: onClose, "aria-label": closeLabel, title: closeTitle ?? closeLabel, children: closeIcon ?? glyph(18, XMARK) })
|
|
1223
|
+
]
|
|
1224
|
+
}
|
|
1225
|
+
);
|
|
1226
|
+
});
|
|
1227
|
+
var ListRow = forwardRef(function ListRow2({ type = "button", className, ...props }, ref) {
|
|
1228
|
+
return /* @__PURE__ */ jsx("button", { ...props, ref, type, className: classes("hc-ui-list-row", className) });
|
|
1229
|
+
});
|
|
1230
|
+
var searchGlyph = /* @__PURE__ */ jsxs("svg", { width: 20, height: 20, viewBox: "0 0 24 24", fill: "none", strokeWidth: 1.8, "aria-hidden": "true", focusable: "false", children: [
|
|
1231
|
+
/* @__PURE__ */ jsx("path", { d: "M17 17L21 21", stroke: "currentColor", strokeLinecap: "round", strokeLinejoin: "round" }),
|
|
1232
|
+
/* @__PURE__ */ jsx("path", { d: "M3 11C3 15.4183 6.58172 19 11 19C13.213 19 15.2161 18.1015 16.6644 16.6493C18.1077 15.2022 19 13.2053 19 11C19 6.58172 15.4183 3 11 3C6.58172 3 3 6.58172 3 11Z", stroke: "currentColor", strokeLinecap: "round", strokeLinejoin: "round" })
|
|
1233
|
+
] });
|
|
1234
|
+
var CommandPalette = forwardRef(function CommandPalette2({
|
|
1235
|
+
open,
|
|
1236
|
+
onClose,
|
|
1237
|
+
label,
|
|
1238
|
+
inputLabel,
|
|
1239
|
+
placeholder,
|
|
1240
|
+
query,
|
|
1241
|
+
onQueryChange,
|
|
1242
|
+
groups,
|
|
1243
|
+
searchIcon,
|
|
1244
|
+
loading,
|
|
1245
|
+
empty,
|
|
1246
|
+
selectHint,
|
|
1247
|
+
altHint,
|
|
1248
|
+
hints,
|
|
1249
|
+
listClassName,
|
|
1250
|
+
className,
|
|
1251
|
+
onClick,
|
|
1252
|
+
onPointerDown,
|
|
1253
|
+
...props
|
|
1254
|
+
}, ref) {
|
|
1255
|
+
const dialogRef = useRef(null);
|
|
1256
|
+
const inputRef = useRef(null);
|
|
1257
|
+
const listRef = useRef(null);
|
|
1258
|
+
const setRef = useCallback((node) => setRefs(node, dialogRef, ref), [ref]);
|
|
1259
|
+
const id = useId();
|
|
1260
|
+
const listId = `${id}-list`;
|
|
1261
|
+
const optionId = (index2) => `${id}-option-${index2}`;
|
|
1262
|
+
const items = useMemo(() => groups.flatMap((group) => group.items), [groups]);
|
|
1263
|
+
const [active, setActive] = useState(0);
|
|
1264
|
+
const [seen, setSeen] = useState({ query, open });
|
|
1265
|
+
if (seen.query !== query || seen.open !== open) {
|
|
1266
|
+
setSeen({ query, open });
|
|
1267
|
+
if (active !== 0) setActive(0);
|
|
1268
|
+
}
|
|
1269
|
+
const current = items.length ? Math.min(active, items.length - 1) : -1;
|
|
1270
|
+
const latest = useRef({ open, onClose });
|
|
1271
|
+
useIsomorphicLayoutEffect(() => {
|
|
1272
|
+
latest.current = { open, onClose };
|
|
1273
|
+
});
|
|
1274
|
+
useIsomorphicLayoutEffect(() => {
|
|
1275
|
+
const dialog = dialogRef.current;
|
|
1276
|
+
if (!open || !dialog) return;
|
|
1277
|
+
const trigger = document.activeElement instanceof HTMLElement && !dialog.contains(document.activeElement) ? document.activeElement : null;
|
|
1278
|
+
if (!dialog.open) dialog.showModal();
|
|
1279
|
+
inputRef.current?.focus();
|
|
1280
|
+
const unlock = lockScroll();
|
|
1281
|
+
return () => {
|
|
1282
|
+
unlock();
|
|
1283
|
+
if (dialog.open) dialog.close();
|
|
1284
|
+
queueMicrotask(() => {
|
|
1285
|
+
const focused = document.activeElement;
|
|
1286
|
+
const lost = !focused || focused === document.body || dialog.contains(focused);
|
|
1287
|
+
if (lost && !dialog.open && trigger?.isConnected) trigger.focus({ preventScroll: true });
|
|
1288
|
+
});
|
|
1289
|
+
};
|
|
1290
|
+
}, [open]);
|
|
1291
|
+
useEffect(() => {
|
|
1292
|
+
if (open && current >= 0) listRef.current?.querySelector('[aria-selected="true"]')?.scrollIntoView({ block: "nearest" });
|
|
1293
|
+
}, [open, current]);
|
|
1294
|
+
const ownEvent = (event) => event.target === event.currentTarget;
|
|
1295
|
+
const handleCancel = (event) => {
|
|
1296
|
+
if (!ownEvent(event)) return;
|
|
1297
|
+
event.preventDefault();
|
|
1298
|
+
latest.current.onClose();
|
|
1299
|
+
};
|
|
1300
|
+
const handleNativeClose = (event) => {
|
|
1301
|
+
const dialog = event.currentTarget;
|
|
1302
|
+
if (!ownEvent(event) || dialog.open || !latest.current.open) return;
|
|
1303
|
+
latest.current.onClose();
|
|
1304
|
+
window.setTimeout(() => {
|
|
1305
|
+
if (latest.current.open && dialog.isConnected && !dialog.open) {
|
|
1306
|
+
dialog.showModal();
|
|
1307
|
+
inputRef.current?.focus();
|
|
1308
|
+
}
|
|
1309
|
+
}, 0);
|
|
1310
|
+
};
|
|
1311
|
+
const pressedBackdrop = useRef(false);
|
|
1312
|
+
const handlePointerDown = (event) => {
|
|
1313
|
+
onPointerDown?.(event);
|
|
1314
|
+
pressedBackdrop.current = ownEvent(event) && outside(event.currentTarget, event.clientX, event.clientY);
|
|
1315
|
+
};
|
|
1316
|
+
const handleClick = (event) => {
|
|
1317
|
+
onClick?.(event);
|
|
1318
|
+
const fromBackdrop = pressedBackdrop.current && ownEvent(event) && outside(event.currentTarget, event.clientX, event.clientY);
|
|
1319
|
+
pressedBackdrop.current = false;
|
|
1320
|
+
if (fromBackdrop) latest.current.onClose();
|
|
1321
|
+
};
|
|
1322
|
+
const handleKeyDown = (event) => {
|
|
1323
|
+
const count = items.length;
|
|
1324
|
+
if (!count || event.nativeEvent.isComposing || event.altKey) return;
|
|
1325
|
+
let next = null;
|
|
1326
|
+
if (event.key === "ArrowDown") next = (current + 1) % count;
|
|
1327
|
+
else if (event.key === "ArrowUp") next = (current - 1 + count) % count;
|
|
1328
|
+
else if (event.key === "Home" && !event.shiftKey) next = 0;
|
|
1329
|
+
else if (event.key === "End" && !event.shiftKey) next = count - 1;
|
|
1330
|
+
else if (event.key === "Enter") {
|
|
1331
|
+
const item = items[current];
|
|
1332
|
+
event.preventDefault();
|
|
1333
|
+
if ((event.metaKey || event.ctrlKey) && item.onAltSelect) item.onAltSelect();
|
|
1334
|
+
else item.onSelect();
|
|
1335
|
+
return;
|
|
1336
|
+
}
|
|
1337
|
+
if (next === null) return;
|
|
1338
|
+
event.preventDefault();
|
|
1339
|
+
setActive(next);
|
|
1340
|
+
};
|
|
1341
|
+
if (!open) return null;
|
|
1342
|
+
let index = 0;
|
|
1343
|
+
return /* @__PURE__ */ jsx(
|
|
1344
|
+
"dialog",
|
|
1345
|
+
{
|
|
1346
|
+
...props,
|
|
1347
|
+
ref: setRef,
|
|
1348
|
+
className: classes("hc-ui-command-palette", className),
|
|
1349
|
+
"aria-label": label,
|
|
1350
|
+
onCancel: handleCancel,
|
|
1351
|
+
onClose: handleNativeClose,
|
|
1352
|
+
onPointerDown: handlePointerDown,
|
|
1353
|
+
onClick: handleClick,
|
|
1354
|
+
children: /* @__PURE__ */ jsxs("div", { className: "hc-ui-command-palette__box", onKeyDown: handleKeyDown, children: [
|
|
1355
|
+
/* @__PURE__ */ jsxs("label", { className: "hc-ui-command-palette__field", children: [
|
|
1356
|
+
searchIcon ?? searchGlyph,
|
|
1357
|
+
/* @__PURE__ */ jsx(
|
|
1358
|
+
"input",
|
|
1359
|
+
{
|
|
1360
|
+
ref: inputRef,
|
|
1361
|
+
className: "hc-ui-command-palette__input",
|
|
1362
|
+
value: query,
|
|
1363
|
+
placeholder,
|
|
1364
|
+
onChange: (event) => onQueryChange(event.target.value),
|
|
1365
|
+
autoComplete: "off",
|
|
1366
|
+
"aria-label": inputLabel ?? label,
|
|
1367
|
+
role: "combobox",
|
|
1368
|
+
"aria-expanded": "true",
|
|
1369
|
+
"aria-controls": listId,
|
|
1370
|
+
"aria-autocomplete": "list",
|
|
1371
|
+
"aria-activedescendant": current >= 0 ? optionId(current) : void 0
|
|
1372
|
+
}
|
|
1373
|
+
),
|
|
1374
|
+
visible(loading) && /* @__PURE__ */ jsx("span", { className: "hc-ui-command-palette__status", children: loading })
|
|
1375
|
+
] }),
|
|
1376
|
+
/* @__PURE__ */ jsxs(
|
|
1377
|
+
"div",
|
|
1378
|
+
{
|
|
1379
|
+
ref: listRef,
|
|
1380
|
+
className: classes("hc-ui-command-palette__list", listClassName),
|
|
1381
|
+
id: listId,
|
|
1382
|
+
role: "listbox",
|
|
1383
|
+
"aria-label": label,
|
|
1384
|
+
"aria-busy": visible(loading) || void 0,
|
|
1385
|
+
children: [
|
|
1386
|
+
visible(empty) && /* @__PURE__ */ jsx("p", { className: "hc-ui-command-palette__empty", children: empty }),
|
|
1387
|
+
groups.map((group, groupIndex) => {
|
|
1388
|
+
if (!group.items.length) return null;
|
|
1389
|
+
const headingId = visible(group.heading) ? `${id}-group-${groupIndex}` : void 0;
|
|
1390
|
+
return /* @__PURE__ */ jsxs("div", { className: "hc-ui-command-palette__group", role: headingId ? "group" : "presentation", "aria-labelledby": headingId, children: [
|
|
1391
|
+
headingId && /* @__PURE__ */ jsx("p", { className: "hc-ui-command-palette__heading", id: headingId, "aria-hidden": "true", children: group.heading }),
|
|
1392
|
+
group.items.map((item) => {
|
|
1393
|
+
const itemIndex = index++;
|
|
1394
|
+
const isActive = itemIndex === current;
|
|
1395
|
+
const itemAltHint = item.onAltSelect ? item.altHint ?? altHint : void 0;
|
|
1396
|
+
return /* @__PURE__ */ jsxs(
|
|
1397
|
+
"button",
|
|
1398
|
+
{
|
|
1399
|
+
type: "button",
|
|
1400
|
+
tabIndex: -1,
|
|
1401
|
+
id: optionId(itemIndex),
|
|
1402
|
+
role: "option",
|
|
1403
|
+
"aria-selected": isActive,
|
|
1404
|
+
className: "hc-ui-command-palette__item",
|
|
1405
|
+
onMouseDown: (event) => event.preventDefault(),
|
|
1406
|
+
onMouseMove: () => {
|
|
1407
|
+
if (!isActive) setActive(itemIndex);
|
|
1408
|
+
},
|
|
1409
|
+
onClick: () => item.onSelect(),
|
|
1410
|
+
children: [
|
|
1411
|
+
item.icon !== void 0 && /* @__PURE__ */ jsx("span", { className: "hc-ui-command-palette__icon", "aria-hidden": "true", children: item.icon }),
|
|
1412
|
+
/* @__PURE__ */ jsxs("span", { className: "hc-ui-command-palette__text", children: [
|
|
1413
|
+
/* @__PURE__ */ jsx("span", { className: "hc-ui-command-palette__label", children: item.label }),
|
|
1414
|
+
visible(item.detail) && /* @__PURE__ */ jsx("small", { className: "hc-ui-command-palette__detail", children: item.detail })
|
|
1415
|
+
] }),
|
|
1416
|
+
isActive && visible(itemAltHint) && /* @__PURE__ */ jsx("kbd", { className: "hc-ui-command-palette__kbd", "aria-hidden": "true", children: itemAltHint }),
|
|
1417
|
+
isActive && visible(selectHint) && /* @__PURE__ */ jsx("kbd", { className: "hc-ui-command-palette__kbd", "aria-hidden": "true", children: selectHint })
|
|
1418
|
+
]
|
|
1419
|
+
},
|
|
1420
|
+
item.key
|
|
1421
|
+
);
|
|
1422
|
+
})
|
|
1423
|
+
] }, group.key);
|
|
1424
|
+
})
|
|
1425
|
+
]
|
|
1426
|
+
}
|
|
1427
|
+
),
|
|
1428
|
+
hints && hints.length > 0 && /* @__PURE__ */ jsx("footer", { className: "hc-ui-command-palette__footer", children: hints.map((hint, hintIndex) => /* @__PURE__ */ jsxs("span", { children: [
|
|
1429
|
+
hint.keys.map((key, keyIndex) => /* @__PURE__ */ jsx("kbd", { className: "hc-ui-command-palette__kbd", children: key }, keyIndex)),
|
|
1430
|
+
" ",
|
|
1431
|
+
hint.label
|
|
1432
|
+
] }, hintIndex)) })
|
|
1433
|
+
] })
|
|
1434
|
+
}
|
|
1435
|
+
);
|
|
1436
|
+
});
|
|
172
1437
|
export {
|
|
173
1438
|
Button,
|
|
1439
|
+
Checkbox,
|
|
174
1440
|
Chip,
|
|
1441
|
+
CommandPalette,
|
|
1442
|
+
Dialog,
|
|
175
1443
|
Field,
|
|
1444
|
+
FilterBar,
|
|
176
1445
|
IconButton,
|
|
1446
|
+
ListRow,
|
|
1447
|
+
Menu,
|
|
1448
|
+
MenuItem,
|
|
1449
|
+
MenuItemCheckbox,
|
|
1450
|
+
MenuItemRadio,
|
|
1451
|
+
MenuLabel,
|
|
1452
|
+
MenuRadioGroup,
|
|
1453
|
+
MenuSeparator,
|
|
1454
|
+
Meter,
|
|
1455
|
+
PageHeader,
|
|
1456
|
+
PanelHeader,
|
|
1457
|
+
Popover,
|
|
177
1458
|
SegmentedControl,
|
|
1459
|
+
Sheet,
|
|
1460
|
+
SheetChips,
|
|
1461
|
+
SheetOptions,
|
|
178
1462
|
Skeleton,
|
|
179
|
-
|
|
1463
|
+
SortMenu,
|
|
1464
|
+
Surface,
|
|
1465
|
+
Table,
|
|
1466
|
+
placePopup
|
|
180
1467
|
};
|