@hearthscale/app-ui 0.2.4 → 0.4.0-rc.1

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/dist/index.js CHANGED
@@ -10,7 +10,7 @@ import {
10
10
  PackageGlyph,
11
11
  SearchGlyph,
12
12
  TrashGlyph
13
- } from "./chunks/chunk-UDXTR7UW.js";
13
+ } from "./chunks/chunk-K3W7YUMY.js";
14
14
 
15
15
  // ../ui/dist/Button.js
16
16
  import { Fragment as _Fragment, jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
@@ -206,7 +206,8 @@ function anchorFrom(key, e) {
206
206
  element: e.currentTarget
207
207
  };
208
208
  }
209
- function MenuSurface({ anchor, align = "auto", dir = "down", minWidth = 180, maxWidth, maxHeight, fit = false, keyboard = false, onDismiss, children }) {
209
+ var spaces = (n) => `calc(var(--space) * ${n})`;
210
+ function MenuSurface({ anchor, align = "auto", dir = "down", minWidth = spaces(45), maxWidth, maxHeight, fit = false, keyboard = false, onDismiss, children }) {
210
211
  const menu = useRef2(null);
211
212
  const [root] = useState(() => popupRoot(anchor.element));
212
213
  const grace = useSafeTriangle(anchor.element, menu, root, onDismiss, !keyboard);
@@ -276,10 +277,14 @@ function MenuSurface({ anchor, align = "auto", dir = "down", minWidth = 180, max
276
277
  padding: 8,
277
278
  apply({ availableWidth, availableHeight, elements, rects }) {
278
279
  const surface = elements.floating.firstElementChild;
279
- const wanted = fit ? rects.reference.width : Math.max(minWidth, rects.reference.width);
280
- surface.style.minWidth = `${Math.max(0, Math.min(wanted, availableWidth))}px`;
281
- surface.style.maxWidth = `${Math.max(0, Math.min(fit ? wanted : Math.max(wanted, maxWidth ?? Infinity), availableWidth))}px`;
282
- surface.style.maxHeight = `${Math.max(0, Math.min(maxHeight ?? Infinity, availableHeight))}px`;
280
+ const room = `${Math.max(0, availableWidth)}px`;
281
+ const tall = `${Math.max(0, availableHeight)}px`;
282
+ const own = `${rects.reference.width}px`;
283
+ const wanted = fit ? own : `max(${minWidth}, ${own})`;
284
+ const most = fit ? wanted : maxWidth === void 0 ? null : `max(${wanted}, ${maxWidth})`;
285
+ surface.style.minWidth = `min(${wanted}, ${room})`;
286
+ surface.style.maxWidth = most ? `min(${most}, ${room})` : room;
287
+ surface.style.maxHeight = maxHeight === void 0 ? tall : `min(${maxHeight}, ${tall})`;
283
288
  if (fit)
284
289
  surface.style.borderRadius = getComputedStyle(anchor.element).borderRadius;
285
290
  }
@@ -299,9 +304,13 @@ function MenuSurface({ anchor, align = "auto", dir = "down", minWidth = 180, max
299
304
  useEffect2(() => {
300
305
  const floating = menu.current;
301
306
  const wheel = (e) => {
302
- const surface = floating.firstElementChild;
303
- if (surface.scrollHeight <= surface.clientHeight)
304
- e.preventDefault();
307
+ for (let at = e.target; at; at = at.parentElement) {
308
+ if (at.scrollHeight > at.clientHeight && getComputedStyle(at).overflowY !== "hidden")
309
+ return;
310
+ if (at === floating)
311
+ break;
312
+ }
313
+ e.preventDefault();
305
314
  };
306
315
  floating.addEventListener("wheel", wheel, { passive: false });
307
316
  return () => floating.removeEventListener("wheel", wheel);
@@ -330,7 +339,10 @@ function MenuSurface({ anchor, align = "auto", dir = "down", minWidth = 180, max
330
339
  event.preventDefault();
331
340
  choices[next]?.focus();
332
341
  }
333
- }, style: pos, children: _jsx5("div", { className: "hs-menu hs-menu-surface", role: keyboard ? "menu" : void 0, style: { minWidth: `min(${minWidth}px, calc(100vw - var(--space) * 4))`, maxWidth }, children: _jsx5(PopupNest.Provider, { value: grace.nest, children: _jsx5(OpenSubmenu.Provider, { value: submenu, children }) }) }) }), root);
342
+ }, style: pos, children: _jsx5("div", { className: "hs-menu hs-menu-surface", role: keyboard ? "menu" : void 0, style: {
343
+ minWidth: `min(${minWidth}, calc(100vw - var(--space) * 4))`,
344
+ ...maxWidth !== void 0 && { maxWidth }
345
+ }, children: _jsx5(PopupNest.Provider, { value: grace.nest, children: _jsx5(OpenSubmenu.Provider, { value: submenu, children }) }) }) }), root);
334
346
  }
335
347
  var OpenSubmenu = createContext2(null);
336
348
  function MItem({ icon, label, sub, chip, checked, selected, danger, disabled, onClick, trailing }) {
@@ -341,7 +353,7 @@ function MItem({ icon, label, sub, chip, checked, selected, danger, disabled, on
341
353
  }
342
354
  }, children: [icon, _jsxs3("span", { className: "hs-menu-label", children: [_jsx5("span", { className: "hs-menu-title", children: label }), sub && _jsx5("span", { className: "hs-menu-sub", children: sub })] }), chip && _jsx5("span", { className: "hs-menu-chip", children: chip }), checked && _jsx5(CheckGlyph, { size: 13 }), trailing] });
343
355
  }
344
- function MSub({ icon, label, minWidth = 220, children }) {
356
+ function MSub({ icon, label, minWidth = spaces(55), children }) {
345
357
  const id = useId();
346
358
  const open = useContext(OpenSubmenu);
347
359
  const [anchor, setAnchor] = useState(null);
@@ -399,6 +411,8 @@ function useFloatingTips(placement) {
399
411
  const [shown, setShown] = useState(null);
400
412
  const [hide] = useState(() => () => setShown(null));
401
413
  return (key, text, more) => ({
414
+ show: (anchor) => setShown({ key, anchor }),
415
+ hide,
402
416
  hover: {
403
417
  onMouseEnter: (e) => setShown({ key, anchor: e.currentTarget }),
404
418
  onFocus: (e) => {
@@ -511,7 +525,7 @@ function SegmentedControl({ value, options, onChange, label, disabled = false })
511
525
 
512
526
  // ../ui/dist/settings.js
513
527
  import { jsx as _jsx13, jsxs as _jsxs7, Fragment as _Fragment4 } from "react/jsx-runtime";
514
- import { createContext as createContext3, useContext as useContext2, useLayoutEffect as useLayoutEffect2, useState as useState2 } from "react";
528
+ import { createContext as createContext3, useContext as useContext2, useLayoutEffect as useLayoutEffect2, useEffect as useEffect4, useState as useState2 } from "react";
515
529
  function SettingsCard({ children }) {
516
530
  return _jsx13("div", { className: "hs-settings-card", children });
517
531
  }
@@ -523,27 +537,30 @@ function HelpTip({ text, onOpen }) {
523
537
  const inner = _jsxs7(_Fragment4, { children: [_jsx13(HelpGlyph, { size: 13 }), tip] });
524
538
  return onOpen ? _jsx13("button", { type: "button", className: "hs-help-button", onClick: onOpen, ...hover, children: inner }) : _jsx13("span", { className: "hs-help-mark", ...hover, children: inner });
525
539
  }
526
- function SettingsSearch({ value, placeholder, onChange }) {
527
- return _jsxs7("label", { className: "hs-url hs-settings-search", children: [_jsx13(SearchGlyph, { size: 12 }), _jsx13("input", { className: "hs-in hs-urlin", "aria-label": placeholder, placeholder, spellCheck: false, value, onChange: (e) => onChange(e.target.value) }), _jsx13("span", { "aria-hidden": "true", className: "hs-urlph hs-ph-icon", children: placeholder })] });
540
+ function SettingsSearch({ value, placeholder, onChange, autoFocus }) {
541
+ return _jsxs7("label", { className: "hs-url hs-settings-search", children: [_jsx13(SearchGlyph, { size: 12 }), _jsx13("input", { className: "hs-in hs-urlin", "aria-label": placeholder, placeholder, spellCheck: false, value, ...autoFocus && { autoFocus: true }, onChange: (e) => onChange(e.target.value) }), _jsx13("span", { "aria-hidden": "true", className: "hs-urlph hs-ph-icon", children: placeholder })] });
528
542
  }
529
- function SettingsSelect({ value, options, onPick, minWidth = 180, menuHeight, menuWidth, placeholder, disabled, label }) {
543
+ function SettingsSelect({ value, options, onPick, minWidth = spaces(45), menuHeight, menuWidth, placeholder, disabled, label, note }) {
530
544
  const [anchor, setAnchor] = useState2(null);
531
545
  const current = options.find((o) => o.value === value);
532
546
  return _jsxs7("span", { className: "hs-select-wrap", children: [_jsxs7("button", { type: "button", className: "hs-glassbtn hs-hovink hs-inktext hs-select", "aria-label": label, "aria-haspopup": "menu", "aria-expanded": anchor !== null, disabled, "data-open": anchor ? "true" : "false", onClick: (e) => {
533
547
  const next = anchorFrom("select", e);
534
548
  setAnchor((current2) => current2 ? null : next);
535
- }, children: [current?.label ?? (value || placeholder), _jsx13(ChevronDownGlyph, { size: 11 })] }), anchor && _jsx13(MenuSurface, { keyboard: true, anchor, onDismiss: () => setAnchor(null), minWidth, ...menuHeight !== void 0 && { maxHeight: menuHeight }, ...menuWidth !== void 0 && { maxWidth: menuWidth }, children: options.map((option) => _jsx13(MItem, { label: option.label, ...option.sub && { sub: option.sub }, ...option.trailing && { trailing: option.trailing }, ...option.disabled && { disabled: true }, checked: option.value === value, onClick: () => {
549
+ }, children: [_jsx13("span", { className: "hs-select-label", children: current?.label ?? (value || placeholder) }), note && _jsx13("span", { className: "hs-select-note", children: note }), _jsx13(ChevronDownGlyph, { size: 11 })] }), anchor && _jsx13(MenuSurface, { keyboard: true, anchor, onDismiss: () => setAnchor(null), minWidth, ...menuHeight !== void 0 && { maxHeight: menuHeight }, ...menuWidth !== void 0 && { maxWidth: menuWidth }, children: options.map((option) => _jsx13(MItem, { label: option.label, ...option.sub && { sub: option.sub }, ...option.trailing && { trailing: option.trailing }, ...option.disabled && { disabled: true }, checked: option.value === value, onClick: () => {
536
550
  anchor.element.focus();
537
551
  setAnchor(null);
538
552
  onPick(option.value);
539
553
  } }, option.value)) })] });
540
554
  }
541
- function ToggleRow({ id, title, sub, help, on, onFlip }) {
555
+ function ToggleRow({ id, mark, title, sub, help, actions, on, onFlip }) {
542
556
  const docs = useContext2(SettingsDocs);
543
557
  const open = id ? docs(id) : void 0;
544
- return _jsxs7("div", { className: "hs-setrow hs-toggle-row", "data-row": id, children: [_jsxs7("span", { className: "hs-setting-label", children: [_jsxs7("span", { className: "hs-setting-label-line", children: [_jsx13("span", { className: "hs-setting-title", children: title }), help && _jsx13(HelpTip, { text: help, ...open && { onOpen: open } })] }), sub && _jsx13("span", { className: "hs-setting-sub", children: sub })] }), _jsx13(Toggle, { checked: on, "aria-label": title, disabled: !onFlip, ...onFlip && { onChange: onFlip } })] });
558
+ return _jsxs7("div", { className: "hs-setrow hs-toggle-row", "data-row": id, children: [mark && _jsx13("span", { className: "hs-row-mark", children: mark }), _jsxs7("span", { className: "hs-setting-label", children: [_jsxs7("span", { className: "hs-setting-label-line", children: [_jsx13("span", { className: "hs-setting-title", children: title }), help && _jsx13(HelpTip, { text: help, ...open && { onOpen: open } })] }), sub && _jsx13("span", { className: "hs-setting-sub", children: sub })] }), actions && _jsx13(SettingControls, { children: actions }), _jsx13(Toggle, { checked: on, "aria-label": title, disabled: !onFlip, ...onFlip && { onChange: onFlip } })] });
545
559
  }
546
560
  var SettingsDocs = createContext3(() => void 0);
561
+ function SettingControls({ children }) {
562
+ return _jsx13("span", { className: "hs-setting-controls", children });
563
+ }
547
564
  function InfoRow({ id, title, sub, help, helpOpen, value }) {
548
565
  const docs = useContext2(SettingsDocs);
549
566
  const open = helpOpen ?? (id ? docs(id) : void 0);
@@ -573,53 +590,63 @@ function SettingsTabs({ label, value, tabs, onPick, panelRef }) {
573
590
  }, children: tab.label }, tab.value)) });
574
591
  }
575
592
 
576
- // ../ui/dist/ShellConfirm.js
593
+ // ../ui/dist/Modal.js
577
594
  import { jsx as _jsx14, jsxs as _jsxs8 } from "react/jsx-runtime";
578
- import { useEffect as useEffect5 } from "react";
595
+ import { useEffect as useEffect6, useRef as useRef6 } from "react";
579
596
 
580
- // ../ui/dist/asking.js
581
- import { useEffect as useEffect4, useSyncExternalStore } from "react";
582
- var asking = 0;
583
- var hearers = /* @__PURE__ */ new Set();
584
- function tell() {
585
- for (const hear of hearers)
586
- hear();
587
- }
588
- function useAsksPerson(on) {
589
- useEffect4(() => {
590
- if (!on)
591
- return;
592
- asking += 1;
593
- tell();
594
- return () => {
595
- asking -= 1;
596
- tell();
597
- };
598
- }, [on]);
599
- }
600
-
601
- // ../ui/dist/ShellConfirm.js
602
- function ShellConfirm({ title, body, action, tone = "danger", icon = tone === "danger" ? _jsx14(TrashGlyph, { size: 13 }) : void 0, onConfirm, onCancel }) {
603
- useAsksPerson(true);
597
+ // ../ui/dist/escape.js
598
+ import { useEffect as useEffect5, useRef as useRef5 } from "react";
599
+ function useEscape(onEscape) {
600
+ const live = useRef5(onEscape);
601
+ live.current = onEscape;
604
602
  useEffect5(() => {
605
603
  const onKey = (e) => {
606
- if (e.key !== "Escape")
607
- return;
608
- e.preventDefault();
609
- onCancel();
604
+ if (e.key === "Escape" && !e.defaultPrevented)
605
+ live.current();
610
606
  };
611
- window.addEventListener("keydown", onKey, true);
612
- return () => window.removeEventListener("keydown", onKey, true);
613
- }, [onCancel]);
614
- return _jsx14("div", { className: "hs-confirm", onClick: (e) => {
615
- e.stopPropagation();
616
- onCancel();
617
- }, children: _jsxs8("div", { className: "hs-confirm-box", role: "dialog", onClick: (e) => e.stopPropagation(), children: [_jsx14("span", { className: "hs-confirm-title", children: title }), _jsx14("span", { className: "hs-confirm-body", children: body }), _jsxs8("div", { className: "hs-confirm-footer", children: [_jsx14(Button, { className: "hs-confirm-cancel", onClick: onCancel, children: "Cancel" }), _jsx14(Button, { className: "hs-confirm-action", variant: tone, icon, onClick: onConfirm, children: action })] })] }) });
607
+ window.addEventListener("keydown", onKey);
608
+ return () => window.removeEventListener("keydown", onKey);
609
+ }, []);
610
+ }
611
+
612
+ // ../ui/dist/Modal.js
613
+ function Modal({ title, children, onClose, width = 460, scrim = true }) {
614
+ const panel = useRef6(null);
615
+ useEffect6(() => {
616
+ const previous = document.activeElement;
617
+ panel.current?.focus();
618
+ return () => previous?.focus();
619
+ }, []);
620
+ useEscape(onClose);
621
+ return _jsx14("div", { onClick: onClose, className: scrim ? "hs-modal-backdrop hs-scrim" : "hs-modal-backdrop", children: _jsxs8("div", { ref: panel, tabIndex: -1, role: "dialog", "aria-modal": "true", "aria-label": title, onKeyDown: (event) => {
622
+ if (event.key === "Escape") {
623
+ event.stopPropagation();
624
+ onClose();
625
+ }
626
+ if (event.key !== "Tab")
627
+ return;
628
+ const controls = [
629
+ ...event.currentTarget.querySelectorAll('button:not(:disabled), input:not(:disabled), textarea:not(:disabled), a[href], [tabindex="0"]')
630
+ ];
631
+ const first = controls[0];
632
+ const last = controls.at(-1);
633
+ if (event.shiftKey && (document.activeElement === first || document.activeElement === panel.current)) {
634
+ event.preventDefault();
635
+ last?.focus();
636
+ } else if (!event.shiftKey && document.activeElement === last) {
637
+ event.preventDefault();
638
+ first?.focus();
639
+ }
640
+ }, onClick: (event) => event.stopPropagation(), className: "hs-modal", style: { width }, children: [_jsxs8("div", { className: "hs-modal-head", children: [_jsx14("p", { className: "hs-modal-title", children: title }), _jsx14(IconButton, { "aria-label": `Close ${title.toLowerCase()}`, onClick: onClose, className: "hs-push-end", children: _jsx14(CloseGlyph, { size: 14 }) })] }), children] }) });
618
641
  }
619
642
 
643
+ // ../ui/dist/MultiSelect.js
644
+ import { jsx as _jsx16, jsxs as _jsxs10 } from "react/jsx-runtime";
645
+ import { useState as useState3 } from "react";
646
+
620
647
  // ../ui/dist/OverlayScroll.js
621
648
  import { jsx as _jsx15, jsxs as _jsxs9 } from "react/jsx-runtime";
622
- import { useLayoutEffect as useLayoutEffect3, useRef as useRef5 } from "react";
649
+ import { useLayoutEffect as useLayoutEffect3, useRef as useRef7 } from "react";
623
650
  function overlayThumb(scroller, thumb) {
624
651
  let inset = 0;
625
652
  let least = 0;
@@ -691,57 +718,125 @@ function overlayThumb(scroller, thumb) {
691
718
  };
692
719
  }
693
720
  function OverlayScroll({ className, children }) {
694
- const scroller = useRef5(null);
695
- const thumb = useRef5(null);
721
+ const scroller = useRef7(null);
722
+ const thumb = useRef7(null);
696
723
  useLayoutEffect3(() => overlayThumb(scroller.current, thumb.current), []);
697
724
  return _jsxs9("div", { className: "hs-overlay-scroll", children: [_jsx15("div", { ref: scroller, className, children }), _jsx15("div", { ref: thumb, className: "hs-overlay-thumb", "aria-hidden": "true" })] });
698
725
  }
699
726
 
727
+ // ../ui/dist/MultiSelect.js
728
+ var NAMED_MOST = 2;
729
+ var MENU_WIDTH = spaces(62.5);
730
+ function MultiSelect({ label, options, labelOf, picked, onPick, searchWords, countWord, onOpen, align, all }) {
731
+ const [anchor, setAnchor] = useState3(null);
732
+ const [search, setSearch] = useState3("");
733
+ const needle = search.trim().toLowerCase();
734
+ const shown = [...picked, ...options.filter((value) => !picked.includes(value))].filter((value) => !needle || value.includes(needle) || labelOf(value).toLowerCase().includes(needle));
735
+ const toggle = (value) => {
736
+ if (!picked.includes(value))
737
+ onPick([...picked, value]);
738
+ else if (all !== void 0 || picked.length > 1)
739
+ onPick(picked.filter((one) => one !== value));
740
+ };
741
+ const words = picked.length === 0 ? all : picked.length > NAMED_MOST ? `${picked.length} ${countWord}` : picked.map(labelOf).join(", ");
742
+ return _jsxs10("span", { className: "hs-select-wrap", children: [_jsxs10("button", { type: "button", className: "hs-glassbtn hs-hovink hs-inktext hs-select", "aria-label": label, "aria-haspopup": "menu", "aria-expanded": anchor !== null, "data-open": anchor ? "true" : "false", onClick: (e) => {
743
+ const next = anchorFrom(label, e);
744
+ if (!anchor)
745
+ onOpen?.();
746
+ setAnchor(anchor ? null : next);
747
+ setSearch("");
748
+ }, children: [_jsx16("span", { className: "hs-select-label", children: words }), _jsx16(ChevronDownGlyph, { size: 11 })] }), anchor && _jsx16(MenuSurface, { keyboard: true, anchor, ...align && { align }, minWidth: MENU_WIDTH, maxWidth: MENU_WIDTH, onDismiss: () => setAnchor(null), children: _jsxs10("div", { className: "hs-multi-select-menu", children: [_jsx16("div", { className: "hs-menu-search", children: _jsx16(SettingsSearch, { value: search, placeholder: searchWords, onChange: setSearch }) }), _jsxs10(OverlayScroll, { className: "hs-scroll hs-multi-select-list", children: [all !== void 0 && !needle && _jsx16(MItem, { label: all, checked: picked.length === 0, onClick: () => onPick([]) }), shown.map((value) => _jsx16(MItem, { label: labelOf(value), checked: picked.includes(value), onClick: () => toggle(value) }, value))] })] }) })] });
749
+ }
750
+
751
+ // ../ui/dist/ShellConfirm.js
752
+ import { jsx as _jsx17, jsxs as _jsxs11 } from "react/jsx-runtime";
753
+ import { useEffect as useEffect8 } from "react";
754
+
755
+ // ../ui/dist/asking.js
756
+ import { useEffect as useEffect7, useSyncExternalStore } from "react";
757
+ var asking = 0;
758
+ var hearers = /* @__PURE__ */ new Set();
759
+ function tell() {
760
+ for (const hear of hearers)
761
+ hear();
762
+ }
763
+ function useAsksPerson(on) {
764
+ useEffect7(() => {
765
+ if (!on)
766
+ return;
767
+ asking += 1;
768
+ tell();
769
+ return () => {
770
+ asking -= 1;
771
+ tell();
772
+ };
773
+ }, [on]);
774
+ }
775
+
776
+ // ../ui/dist/ShellConfirm.js
777
+ function ShellConfirm({ title, body, action, tone = "danger", icon = tone === "danger" ? _jsx17(TrashGlyph, { size: 13 }) : void 0, onConfirm, onCancel }) {
778
+ useAsksPerson(true);
779
+ useEffect8(() => {
780
+ const onKey = (e) => {
781
+ if (e.key !== "Escape")
782
+ return;
783
+ e.preventDefault();
784
+ onCancel();
785
+ };
786
+ window.addEventListener("keydown", onKey, true);
787
+ return () => window.removeEventListener("keydown", onKey, true);
788
+ }, [onCancel]);
789
+ return _jsx17("div", { className: "hs-confirm", onClick: (e) => {
790
+ e.stopPropagation();
791
+ onCancel();
792
+ }, children: _jsxs11("div", { className: "hs-confirm-box", role: "dialog", onClick: (e) => e.stopPropagation(), children: [_jsx17("span", { className: "hs-confirm-title", children: title }), _jsx17("span", { className: "hs-confirm-body", children: body }), _jsxs11("div", { className: "hs-confirm-footer", children: [_jsx17(Button, { className: "hs-confirm-cancel", onClick: onCancel, children: "Cancel" }), _jsx17(Button, { className: "hs-confirm-action", variant: tone, icon, onClick: onConfirm, children: action })] })] }) });
793
+ }
794
+
700
795
  // ../ui/dist/Kbd.js
701
- import { jsx as _jsx16 } from "react/jsx-runtime";
796
+ import { jsx as _jsx18 } from "react/jsx-runtime";
702
797
  function Kbd({ children }) {
703
- return _jsx16("kbd", { className: "hs-kbd", children });
798
+ return _jsx18("kbd", { className: "hs-kbd", children });
704
799
  }
705
800
 
706
801
  // ../ui/dist/Meter.js
707
- import { jsx as _jsx17, jsxs as _jsxs10 } from "react/jsx-runtime";
802
+ import { jsx as _jsx19, jsxs as _jsxs12 } from "react/jsx-runtime";
708
803
  var RADIUS = 7.5;
709
804
  var CIRCUMFERENCE = 2 * Math.PI * RADIUS;
710
805
  function Meter({ value, label, variant = "bar", tone }) {
711
806
  const share = Math.max(0, Math.min(1, value));
712
- return _jsx17("span", { className: "hs-meter", "data-variant": variant, "data-tone": tone, role: "progressbar", "aria-label": label, "aria-valuemin": 0, "aria-valuemax": 100, "aria-valuenow": Math.round(share * 100), children: variant === "ring" ? _jsx17("svg", { width: 20, height: 20, viewBox: "0 0 20 20", "aria-hidden": "true", children: _jsxs10("g", { transform: "rotate(-90 10 10)", children: [_jsx17("circle", { className: "hs-meter-track", cx: 10, cy: 10, r: RADIUS }), _jsx17("circle", { className: "hs-meter-fill", cx: 10, cy: 10, r: RADIUS, strokeDasharray: CIRCUMFERENCE, strokeDashoffset: CIRCUMFERENCE * (1 - share) })] }) }) : _jsx17("span", { className: "hs-meter-fill", style: { width: `${share * 100}%` } }) });
807
+ return _jsx19("span", { className: "hs-meter", "data-variant": variant, "data-tone": tone, role: "progressbar", "aria-label": label, "aria-valuemin": 0, "aria-valuemax": 100, "aria-valuenow": Math.round(share * 100), children: variant === "ring" ? _jsx19("svg", { width: 20, height: 20, viewBox: "0 0 20 20", "aria-hidden": "true", children: _jsxs12("g", { transform: "rotate(-90 10 10)", children: [_jsx19("circle", { className: "hs-meter-track", cx: 10, cy: 10, r: RADIUS }), _jsx19("circle", { className: "hs-meter-fill", cx: 10, cy: 10, r: RADIUS, strokeDasharray: CIRCUMFERENCE, strokeDashoffset: CIRCUMFERENCE * (1 - share) })] }) }) : _jsx19("span", { className: "hs-meter-fill", style: { width: `${share * 100}%` } }) });
713
808
  }
714
809
 
715
810
  // ../ui/dist/Lightbox.js
716
- import { jsx as _jsx18, jsxs as _jsxs11, Fragment as _Fragment5 } from "react/jsx-runtime";
717
- import { useEffect as useEffect6, useLayoutEffect as useLayoutEffect4, useRef as useRef6, useState as useState3 } from "react";
811
+ import { jsx as _jsx20, jsxs as _jsxs13, Fragment as _Fragment5 } from "react/jsx-runtime";
812
+ import { useEffect as useEffect9, useLayoutEffect as useLayoutEffect4, useRef as useRef8, useState as useState4 } from "react";
718
813
  import { createPortal as createPortal2 } from "react-dom";
719
814
  var MAX_ZOOM = 8;
720
815
  var CLICK_ZOOM = 2;
721
816
  var DRAG_SLOP = 4;
722
817
  var FIT = { scale: 1, x: 0, y: 0 };
723
818
  function Lightbox({ items, start, onClose }) {
724
- const [index, setIndex] = useState3(start);
725
- const [view, setView] = useState3(FIT);
726
- const [broken, setBroken] = useState3(false);
727
- const [host, setHost] = useState3(null);
728
- const anchor = useRef6(null);
729
- const stage = useRef6(null);
730
- const closer = useRef6(null);
731
- const drag = useRef6(null);
819
+ const [index, setIndex] = useState4(start);
820
+ const [view, setView] = useState4(FIT);
821
+ const [broken, setBroken] = useState4(false);
822
+ const [host, setHost] = useState4(null);
823
+ const anchor = useRef8(null);
824
+ const stage = useRef8(null);
825
+ const closer = useRef8(null);
826
+ const drag = useRef8(null);
732
827
  const item = items[index];
733
828
  const many = items.length > 1;
734
829
  useLayoutEffect4(() => {
735
830
  setHost(anchor.current?.closest(".hs-shell") ?? document.body);
736
831
  }, []);
737
- useEffect6(() => {
832
+ useEffect9(() => {
738
833
  if (!host)
739
834
  return;
740
835
  const opener = document.activeElement;
741
836
  closer.current?.focus();
742
837
  return () => opener?.focus();
743
838
  }, [host]);
744
- useEffect6(() => {
839
+ useEffect9(() => {
745
840
  setView(FIT);
746
841
  setBroken(false);
747
842
  }, [index]);
@@ -757,7 +852,7 @@ function Lightbox({ items, start, onClose }) {
757
852
  return { px: clientX - box.left - box.width / 2, py: clientY - box.top - box.height / 2 };
758
853
  };
759
854
  const step = (delta) => setIndex((i) => (i + delta + items.length) % items.length);
760
- useEffect6(() => {
855
+ useEffect9(() => {
761
856
  const onKey = (event) => {
762
857
  if (event.key === "Escape")
763
858
  onClose();
@@ -779,7 +874,7 @@ function Lightbox({ items, start, onClose }) {
779
874
  window.addEventListener("keydown", onKey, true);
780
875
  return () => window.removeEventListener("keydown", onKey, true);
781
876
  });
782
- useEffect6(() => {
877
+ useEffect9(() => {
783
878
  const box = stage.current;
784
879
  if (!box)
785
880
  return;
@@ -822,34 +917,34 @@ function Lightbox({ items, start, onClose }) {
822
917
  };
823
918
  if (!item)
824
919
  return null;
825
- const dialog = _jsxs11("div", { className: "hs-lightbox", role: "dialog", "aria-modal": "true", "aria-label": item.name, onClick: onClose, children: [_jsxs11("div", { className: "hs-lightbox-bar", onClick: (e) => e.stopPropagation(), children: [item.onSave && _jsx18("button", { type: "button", className: "hs-lightbox-button", "aria-label": "Save", onClick: item.onSave, children: _jsx18(DownloadGlyph, { size: 16 }) }), item.onOpen && _jsx18("button", { type: "button", className: "hs-lightbox-button", "aria-label": "Open", onClick: (e) => item.onOpen?.(e.currentTarget), children: _jsx18(ExternalGlyph, { size: 16 }) }), _jsx18("button", { ref: closer, type: "button", className: "hs-lightbox-button", "aria-label": "Close", onClick: onClose, children: _jsx18(CloseGlyph, { size: 16 }) })] }), many && _jsx18("button", { type: "button", className: "hs-lightbox-step", "data-side": "previous", "aria-label": "Previous picture", onClick: (e) => {
920
+ const dialog = _jsxs13("div", { className: "hs-lightbox", role: "dialog", "aria-modal": "true", "aria-label": item.name, onClick: onClose, children: [_jsxs13("div", { className: "hs-lightbox-bar", onClick: (e) => e.stopPropagation(), children: [item.onSave && _jsx20("button", { type: "button", className: "hs-lightbox-button", "aria-label": "Save", onClick: item.onSave, children: _jsx20(DownloadGlyph, { size: 16 }) }), item.onOpen && _jsx20("button", { type: "button", className: "hs-lightbox-button", "aria-label": "Open", onClick: (e) => item.onOpen?.(e.currentTarget), children: _jsx20(ExternalGlyph, { size: 16 }) }), _jsx20("button", { ref: closer, type: "button", className: "hs-lightbox-button", "aria-label": "Close", onClick: onClose, children: _jsx20(CloseGlyph, { size: 16 }) })] }), many && _jsx20("button", { type: "button", className: "hs-lightbox-step", "data-side": "previous", "aria-label": "Previous picture", onClick: (e) => {
826
921
  e.stopPropagation();
827
922
  step(-1);
828
- }, children: _jsx18(ChevronLeftGlyph, { size: 20 }) }), _jsxs11("div", { className: "hs-lightbox-body", onClick: (e) => e.stopPropagation(), children: [_jsx18("div", { ref: stage, className: "hs-lightbox-stage", "data-zoomed": view.scale > 1 ? "true" : "false", onPointerDown, onPointerMove, onPointerUp, children: broken ? _jsx18("span", { className: "hs-lightbox-broken", children: "This picture could not be loaded." }) : _jsx18("img", { src: item.src, alt: item.name, draggable: false, onError: () => setBroken(true), style: { transform: `translate(${view.x}px, ${view.y}px) scale(${view.scale})` } }) }), _jsxs11("span", { className: "hs-lightbox-caption", "aria-live": "polite", children: [item.name, many && ` (${index + 1} of ${items.length})`] }), item.note && _jsx18("div", { className: "hs-lightbox-note", children: item.note })] }), many && _jsx18("button", { type: "button", className: "hs-lightbox-step", "data-side": "next", "aria-label": "Next picture", onClick: (e) => {
923
+ }, children: _jsx20(ChevronLeftGlyph, { size: 20 }) }), _jsxs13("div", { className: "hs-lightbox-body", onClick: (e) => e.stopPropagation(), children: [_jsx20("div", { ref: stage, className: "hs-lightbox-stage", "data-zoomed": view.scale > 1 ? "true" : "false", onPointerDown, onPointerMove, onPointerUp, children: broken ? _jsx20("span", { className: "hs-lightbox-broken", children: "This picture could not be loaded." }) : _jsx20("img", { src: item.src, alt: item.name, draggable: false, onError: () => setBroken(true), style: { transform: `translate(${view.x}px, ${view.y}px) scale(${view.scale})` } }) }), _jsxs13("span", { className: "hs-lightbox-caption", "aria-live": "polite", children: [item.name, many && ` (${index + 1} of ${items.length})`] }), item.note && _jsx20("div", { className: "hs-lightbox-note", children: item.note })] }), many && _jsx20("button", { type: "button", className: "hs-lightbox-step", "data-side": "next", "aria-label": "Next picture", onClick: (e) => {
829
924
  e.stopPropagation();
830
925
  step(1);
831
- }, children: _jsx18(ChevronRightGlyph, { size: 20 }) })] });
832
- return _jsxs11(_Fragment5, { children: [_jsx18("span", { ref: anchor, hidden: true }), host && createPortal2(dialog, host)] });
926
+ }, children: _jsx20(ChevronRightGlyph, { size: 20 }) })] });
927
+ return _jsxs13(_Fragment5, { children: [_jsx20("span", { ref: anchor, hidden: true }), host && createPortal2(dialog, host)] });
833
928
  }
834
929
 
835
930
  // ../ui/dist/Slider.js
836
- import { jsx as _jsx19, jsxs as _jsxs12 } from "react/jsx-runtime";
931
+ import { jsx as _jsx21, jsxs as _jsxs14 } from "react/jsx-runtime";
837
932
  function Slider({ value, onChange, min = 0, max = 1, step = 0.01, label, shown, disabled }) {
838
- return _jsxs12("span", { className: "hs-slider", children: [_jsx19("input", { type: "range", className: "hs-range", "aria-label": label, "aria-valuetext": shown, min, max, step, value, disabled, onChange: (event) => onChange(Number(event.target.value)) }), shown !== void 0 && _jsx19("span", { className: "hs-slider-value", children: shown })] });
933
+ return _jsxs14("span", { className: "hs-slider", children: [_jsx21("input", { type: "range", className: "hs-range", "aria-label": label, "aria-valuetext": shown, min, max, step, value, disabled, onChange: (event) => onChange(Number(event.target.value)) }), shown !== void 0 && _jsx21("span", { className: "hs-slider-value", children: shown })] });
839
934
  }
840
935
 
841
936
  // ../ui/dist/Popover.js
842
- import { jsx as _jsx20 } from "react/jsx-runtime";
843
- import { useEffect as useEffect7, useLayoutEffect as useLayoutEffect5, useRef as useRef7, useState as useState4 } from "react";
937
+ import { jsx as _jsx22 } from "react/jsx-runtime";
938
+ import { useEffect as useEffect10, useLayoutEffect as useLayoutEffect5, useRef as useRef9, useState as useState5 } from "react";
844
939
  import { createPortal as createPortal3 } from "react-dom";
845
940
  import { autoUpdate as autoUpdate2, computePosition as computePosition2, flip as flip2, offset as offset2, shift as shift2, size as size2 } from "@floating-ui/dom";
846
941
  var tabbable = (panel) => [...panel.querySelectorAll(FOCUSABLE)].filter((control) => control.tabIndex >= 0);
847
942
  var FOCUSABLE = 'button:not(:disabled), [href], input:not(:disabled), select:not(:disabled), textarea:not(:disabled), [tabindex]:not([tabindex="-1"]), [contenteditable="true"]';
848
943
  function Popover({ anchor, placement = "bottom-start", label, onDismiss, children }) {
849
- const panel = useRef7(null);
850
- const [root] = useState4(() => popupRoot(anchor));
851
- const [pos, setPos] = useState4({ visibility: "hidden" });
852
- const dismiss = useRef7(onDismiss);
944
+ const panel = useRef9(null);
945
+ const [root] = useState5(() => popupRoot(anchor));
946
+ const [pos, setPos] = useState5({ visibility: "hidden" });
947
+ const dismiss = useRef9(onDismiss);
853
948
  dismiss.current = onDismiss;
854
949
  useLayoutEffect5(() => {
855
950
  const floating = panel.current;
@@ -891,8 +986,8 @@ function Popover({ anchor, placement = "bottom-start", label, onDismiss, childre
891
986
  anchor.focus();
892
987
  };
893
988
  }, [shown, anchor]);
894
- const pressed = useRef7(null);
895
- useEffect7(() => {
989
+ const pressed = useRef9(null);
990
+ useEffect10(() => {
896
991
  const outside = (event) => {
897
992
  if (pressed.current !== event && !anchor.contains(event.target))
898
993
  dismiss.current();
@@ -905,7 +1000,7 @@ function Popover({ anchor, placement = "bottom-start", label, onDismiss, childre
905
1000
  window.removeEventListener("blur", blur);
906
1001
  };
907
1002
  }, [anchor]);
908
- return createPortal3(_jsx20("div", { ref: panel, className: "hs-popover", role: "dialog", "aria-label": label, tabIndex: -1, style: pos, onPointerDown: (event) => {
1003
+ return createPortal3(_jsx22("div", { ref: panel, className: "hs-popover", role: "dialog", "aria-label": label, tabIndex: -1, style: pos, onPointerDown: (event) => {
909
1004
  pressed.current = event.nativeEvent;
910
1005
  }, onKeyDown: (event) => {
911
1006
  if (event.key === "Escape") {
@@ -935,15 +1030,15 @@ function Popover({ anchor, placement = "bottom-start", label, onDismiss, childre
935
1030
  }
936
1031
 
937
1032
  // ../ui/dist/Toast.js
938
- import { jsx as _jsx21, jsxs as _jsxs13 } from "react/jsx-runtime";
939
- import { useEffect as useEffect8, useLayoutEffect as useLayoutEffect6, useRef as useRef8, useState as useState5 } from "react";
1033
+ import { jsx as _jsx23, jsxs as _jsxs15 } from "react/jsx-runtime";
1034
+ import { useEffect as useEffect11, useLayoutEffect as useLayoutEffect6, useRef as useRef10, useState as useState6 } from "react";
940
1035
  import { createPortal as createPortal4 } from "react-dom";
941
1036
  function Toast({ children, action, duration = 6e3, above, onClose }) {
942
- const [root, setRoot] = useState5(null);
943
- const place = useRef8(null);
944
- const [held, setHeld] = useState5({ pointer: false, focus: false });
945
- const [anchor, setAnchor] = useState5(null);
946
- const close = useRef8(onClose);
1037
+ const [root, setRoot] = useState6(null);
1038
+ const place = useRef10(null);
1039
+ const [held, setHeld] = useState6({ pointer: false, focus: false });
1040
+ const [anchor, setAnchor] = useState6(null);
1041
+ const close = useRef10(onClose);
947
1042
  close.current = onClose;
948
1043
  useLayoutEffect6(() => {
949
1044
  setRoot(place.current?.closest(".hs-shell") ?? document.body);
@@ -965,31 +1060,31 @@ function Toast({ children, action, duration = 6e3, above, onClose }) {
965
1060
  return () => cancelAnimationFrame(frame);
966
1061
  }, [above]);
967
1062
  const waiting = !held.pointer && !held.focus;
968
- useEffect8(() => {
1063
+ useEffect11(() => {
969
1064
  if (!waiting)
970
1065
  return;
971
1066
  const timer = setTimeout(() => close.current(), duration);
972
1067
  return () => clearTimeout(timer);
973
1068
  }, [waiting, duration]);
974
- return _jsx21("span", { ref: place, hidden: true, children: root && createPortal4(_jsx21("div", { className: "hs-toast-place", ...anchor && {
1069
+ return _jsx23("span", { ref: place, hidden: true, children: root && createPortal4(_jsx23("div", { className: "hs-toast-place", ...anchor && {
975
1070
  "data-above": "true",
976
1071
  style: {
977
1072
  bottom: `calc(${anchor.bottom}px + var(--space) * 4)`,
978
1073
  left: `${anchor.left}px`,
979
1074
  width: `${anchor.width}px`
980
1075
  }
981
- }, children: _jsxs13("div", { className: "hs-toast", role: "status", "aria-live": "polite", onPointerEnter: () => setHeld((h) => ({ ...h, pointer: true })), onPointerLeave: () => setHeld((h) => ({ ...h, pointer: false })), onFocus: () => setHeld((h) => ({ ...h, focus: true })), onBlur: (event) => {
1076
+ }, children: _jsxs15("div", { className: "hs-toast", role: "status", "aria-live": "polite", onPointerEnter: () => setHeld((h) => ({ ...h, pointer: true })), onPointerLeave: () => setHeld((h) => ({ ...h, pointer: false })), onFocus: () => setHeld((h) => ({ ...h, focus: true })), onBlur: (event) => {
982
1077
  if (!event.currentTarget.contains(event.relatedTarget))
983
1078
  setHeld((h) => ({ ...h, focus: false }));
984
- }, children: [_jsx21("span", { className: "hs-toast-text", children }), action && _jsx21(Button, { variant: "ghost", size: "sm", onClick: () => {
1079
+ }, children: [_jsx23("span", { className: "hs-toast-text", children }), action && _jsx23(Button, { variant: "ghost", size: "sm", onClick: () => {
985
1080
  action.onPress();
986
1081
  close.current();
987
- }, children: action.label }), _jsx21(IconButton, { size: "sm", "aria-label": "Close", onClick: () => close.current(), children: _jsx21(CloseGlyph, {}) })] }) }), root) });
1082
+ }, children: action.label }), _jsx23(IconButton, { size: "sm", "aria-label": "Close", onClick: () => close.current(), children: _jsx23(CloseGlyph, {}) })] }) }), root) });
988
1083
  }
989
1084
 
990
1085
  // ../ui/dist/DatePicker.js
991
- import { jsx as _jsx22, jsxs as _jsxs14, Fragment as _Fragment6 } from "react/jsx-runtime";
992
- import { useEffect as useEffect9, useMemo, useRef as useRef9, useState as useState6 } from "react";
1086
+ import { jsx as _jsx24, jsxs as _jsxs16, Fragment as _Fragment6 } from "react/jsx-runtime";
1087
+ import { useEffect as useEffect12, useMemo, useRef as useRef11, useState as useState7 } from "react";
993
1088
  var pad = (n, width = 2) => String(n).padStart(width, "0");
994
1089
  function instant(date) {
995
1090
  const [year, month, day] = date.split("-").map(Number);
@@ -1023,15 +1118,15 @@ function firstDayOfWeek(locale) {
1023
1118
  var STEPS = { ArrowLeft: -1, ArrowRight: 1, ArrowUp: -7, ArrowDown: 7 };
1024
1119
  function DateGrid({ value, onChange, locale, min, max }) {
1025
1120
  const now = today();
1026
- const [focus, setFocus] = useState6(value ?? now);
1121
+ const [focus, setFocus] = useState7(value ?? now);
1027
1122
  const month = monthOf(focus);
1028
- const keyed = useRef9(false);
1029
- const days = useRef9(/* @__PURE__ */ new Map());
1030
- useEffect9(() => {
1123
+ const keyed = useRef11(false);
1124
+ const days = useRef11(/* @__PURE__ */ new Map());
1125
+ useEffect12(() => {
1031
1126
  if (value)
1032
1127
  setFocus(value);
1033
1128
  }, [value]);
1034
- useEffect9(() => {
1129
+ useEffect12(() => {
1035
1130
  if (!keyed.current)
1036
1131
  return;
1037
1132
  keyed.current = false;
@@ -1057,7 +1152,7 @@ function DateGrid({ value, onChange, locale, min, max }) {
1057
1152
  keyed.current = true;
1058
1153
  setFocus(min && next < min ? min : max && next > max ? max : next);
1059
1154
  };
1060
- return _jsxs14("div", { className: "hs-date-grid", children: [_jsxs14("div", { className: "hs-date-grid-head", children: [_jsx22("span", { className: "hs-date-grid-month", "aria-live": "polite", children: words.month.format(instant(month)) }), _jsx22(IconButton, { size: "sm", "aria-label": "Previous month", onClick: () => setFocus(addMonths(focus, -1)), children: _jsx22(ChevronLeftGlyph, {}) }), _jsx22(IconButton, { size: "sm", "aria-label": "Next month", onClick: () => setFocus(addMonths(focus, 1)), children: _jsx22(ChevronRightGlyph, {}) })] }), _jsxs14("div", { className: "hs-date-grid-days", role: "grid", "aria-label": words.month.format(instant(month)), children: [_jsx22("div", { role: "row", className: "hs-date-grid-row", children: weeks[0].map((day) => _jsx22("span", { className: "hs-date-grid-weekday", role: "columnheader", "aria-label": words.long.format(instant(day)), children: words.narrow.format(instant(day)) }, day)) }), weeks.map((week) => _jsx22("div", { role: "row", className: "hs-date-grid-row", children: week.map((day) => _jsx22("button", { ref: (button) => {
1155
+ return _jsxs16("div", { className: "hs-date-grid", children: [_jsxs16("div", { className: "hs-date-grid-head", children: [_jsx24("span", { className: "hs-date-grid-month", "aria-live": "polite", children: words.month.format(instant(month)) }), _jsx24(IconButton, { size: "sm", "aria-label": "Previous month", onClick: () => setFocus(addMonths(focus, -1)), children: _jsx24(ChevronLeftGlyph, {}) }), _jsx24(IconButton, { size: "sm", "aria-label": "Next month", onClick: () => setFocus(addMonths(focus, 1)), children: _jsx24(ChevronRightGlyph, {}) })] }), _jsxs16("div", { className: "hs-date-grid-days", role: "grid", "aria-label": words.month.format(instant(month)), children: [_jsx24("div", { role: "row", className: "hs-date-grid-row", children: weeks[0].map((day) => _jsx24("span", { className: "hs-date-grid-weekday", role: "columnheader", "aria-label": words.long.format(instant(day)), children: words.narrow.format(instant(day)) }, day)) }), weeks.map((week) => _jsx24("div", { role: "row", className: "hs-date-grid-row", children: week.map((day) => _jsx24("button", { ref: (button) => {
1061
1156
  if (button)
1062
1157
  days.current.set(day, button);
1063
1158
  else
@@ -1068,20 +1163,20 @@ function DateGrid({ value, onChange, locale, min, max }) {
1068
1163
  }, onKeyDown: (event) => key(event, day), children: words.day.format(instant(day)) }, day)) }, week[0]))] })] });
1069
1164
  }
1070
1165
  function DatePicker({ value, onChange, locale, min, max, label, placeholder, disabled }) {
1071
- const [anchor, setAnchor] = useState6(null);
1166
+ const [anchor, setAnchor] = useState7(null);
1072
1167
  const shown = useMemo(() => new Intl.DateTimeFormat(locale, { dateStyle: "medium", timeZone: "UTC" }), [locale]);
1073
- return _jsxs14(_Fragment6, { children: [_jsx22("button", { type: "button", className: "hs-input hs-picker-field", "aria-label": label, "aria-haspopup": "dialog", "aria-expanded": anchor !== null, "data-empty": value === null, disabled, onClick: (event) => setAnchor(anchor ? null : event.currentTarget), children: value ? shown.format(instant(value)) : placeholder }), anchor && _jsx22(Popover, { anchor, label, onDismiss: () => setAnchor(null), children: _jsx22(DateGrid, { value, locale, ...min && { min }, ...max && { max }, onChange: (date) => {
1168
+ return _jsxs16(_Fragment6, { children: [_jsx24("button", { type: "button", className: "hs-input hs-picker-field", "aria-label": label, "aria-haspopup": "dialog", "aria-expanded": anchor !== null, "data-empty": value === null, disabled, onClick: (event) => setAnchor(anchor ? null : event.currentTarget), children: value ? shown.format(instant(value)) : placeholder }), anchor && _jsx24(Popover, { anchor, label, onDismiss: () => setAnchor(null), children: _jsx24(DateGrid, { value, locale, ...min && { min }, ...max && { max }, onChange: (date) => {
1074
1169
  onChange(date);
1075
1170
  setAnchor(null);
1076
1171
  } }) })] });
1077
1172
  }
1078
1173
 
1079
1174
  // ../ui/dist/TimePicker.js
1080
- import { jsx as _jsx23, Fragment as _Fragment7, jsxs as _jsxs15 } from "react/jsx-runtime";
1081
- import { useMemo as useMemo2, useState as useState7 } from "react";
1175
+ import { jsx as _jsx25, Fragment as _Fragment7, jsxs as _jsxs17 } from "react/jsx-runtime";
1176
+ import { useMemo as useMemo2, useState as useState8 } from "react";
1082
1177
  var pad2 = (n) => String(n).padStart(2, "0");
1083
1178
  function TimePicker({ value, onChange, locale, label, step = 15, placeholder, disabled }) {
1084
- const [anchor, setAnchor] = useState7(null);
1179
+ const [anchor, setAnchor] = useState8(null);
1085
1180
  const shown = useMemo2(() => new Intl.DateTimeFormat(locale, { hour: "numeric", minute: "2-digit", timeZone: "UTC" }), [locale]);
1086
1181
  const words = (time) => {
1087
1182
  const [hours, minutes] = time.split(":").map(Number);
@@ -1094,7 +1189,7 @@ function TimePicker({ value, onChange, locale, label, step = 15, placeholder, di
1094
1189
  });
1095
1190
  return value && !steps.includes(value) ? [...steps, value].sort() : steps;
1096
1191
  }, [step, value]);
1097
- return _jsxs15(_Fragment7, { children: [_jsx23("button", { type: "button", className: "hs-input hs-picker-field", "aria-label": label, "aria-haspopup": "menu", "aria-expanded": anchor !== null, "data-empty": value === null, disabled, onClick: (event) => setAnchor(anchor ? null : { key: "time", element: event.currentTarget }), children: value ? words(value) : placeholder }), anchor && _jsx23(MenuSurface, { anchor, fit: true, keyboard: true, maxHeight: 280, onDismiss: () => setAnchor(null), children: times.map((time) => _jsx23(MItem, { label: words(time), checked: time === value, onClick: () => {
1192
+ return _jsxs17(_Fragment7, { children: [_jsx25("button", { type: "button", className: "hs-input hs-picker-field", "aria-label": label, "aria-haspopup": "menu", "aria-expanded": anchor !== null, "data-empty": value === null, disabled, onClick: (event) => setAnchor(anchor ? null : { key: "time", element: event.currentTarget }), children: value ? words(value) : placeholder }), anchor && _jsx25(MenuSurface, { anchor, fit: true, keyboard: true, maxHeight: spaces(70), onDismiss: () => setAnchor(null), children: times.map((time) => _jsx25(MItem, { label: words(time), checked: time === value, onClick: () => {
1098
1193
  onChange(time);
1099
1194
  setAnchor(null);
1100
1195
  anchor.element.focus();
@@ -1102,8 +1197,8 @@ function TimePicker({ value, onChange, locale, label, step = 15, placeholder, di
1102
1197
  }
1103
1198
 
1104
1199
  // ../ui/dist/SwatchPicker.js
1105
- import { jsx as _jsx24 } from "react/jsx-runtime";
1106
- import { useRef as useRef10 } from "react";
1200
+ import { jsx as _jsx26 } from "react/jsx-runtime";
1201
+ import { useRef as useRef12 } from "react";
1107
1202
  var SWATCHES = [
1108
1203
  "rose",
1109
1204
  "ember",
@@ -1136,7 +1231,7 @@ var NAMES = {
1136
1231
  };
1137
1232
  var STEPS2 = { ArrowRight: 1, ArrowDown: 1, ArrowLeft: -1, ArrowUp: -1 };
1138
1233
  function SwatchPicker({ value, onChange, label }) {
1139
- const dots = useRef10([]);
1234
+ const dots = useRef12([]);
1140
1235
  const tips = useFloatingTips();
1141
1236
  const step = (event, index) => {
1142
1237
  const by = STEPS2[event.key];
@@ -1147,17 +1242,17 @@ function SwatchPicker({ value, onChange, label }) {
1147
1242
  onChange(SWATCHES[next]);
1148
1243
  dots.current[next]?.focus();
1149
1244
  };
1150
- return _jsx24("div", { className: "hs-swatch-picker", role: "radiogroup", "aria-label": label, children: SWATCHES.map((swatch, index) => {
1245
+ return _jsx26("div", { className: "hs-swatch-picker", role: "radiogroup", "aria-label": label, children: SWATCHES.map((swatch, index) => {
1151
1246
  const tip = tips(swatch, NAMES[swatch]);
1152
- return _jsx24("button", { ref: (dot) => {
1247
+ return _jsx26("button", { ref: (dot) => {
1153
1248
  dots.current[index] = dot;
1154
1249
  }, type: "button", role: "radio", "aria-checked": swatch === value, "aria-label": NAMES[swatch], tabIndex: swatch === value ? 0 : -1, className: "hs-swatch-choice", "data-swatch": swatch, onClick: () => onChange(swatch), onKeyDown: (event) => step(event, index), ...tip.hover, children: tip.tip }, swatch);
1155
1250
  }) });
1156
1251
  }
1157
1252
 
1158
1253
  // ../ui/dist/PersonMark.js
1159
- import { jsx as _jsx25 } from "react/jsx-runtime";
1160
- import { useEffect as useEffect10, useState as useState8 } from "react";
1254
+ import { jsx as _jsx27 } from "react/jsx-runtime";
1255
+ import { useEffect as useEffect13, useState as useState9 } from "react";
1161
1256
  function initials(name) {
1162
1257
  const trimmed = name.trim();
1163
1258
  const words = (/\s/.test(trimmed) ? trimmed : trimmed.split("@")[0]).split(/[\s._-]+/).filter(Boolean);
@@ -1173,17 +1268,17 @@ function hueOf(name) {
1173
1268
  return SWATCHES[(hash >>> 0) % 12];
1174
1269
  }
1175
1270
  function PersonMark({ name, picture, size: size3 = "md", swatch }) {
1176
- const [broken, setBroken] = useState8(false);
1177
- useEffect10(() => setBroken(false), [picture]);
1178
- return _jsx25("span", { className: "hs-person-mark", "data-size": size3, "data-swatch": swatch ?? hueOf(name), role: "img", "aria-label": name, children: picture && !broken ? _jsx25("img", { src: picture, alt: "", onError: () => setBroken(true) }) : initials(name) });
1271
+ const [broken, setBroken] = useState9(false);
1272
+ useEffect13(() => setBroken(false), [picture]);
1273
+ return _jsx27("span", { className: "hs-person-mark", "data-size": size3, "data-swatch": swatch ?? hueOf(name), role: "img", "aria-label": name, children: picture && !broken ? _jsx27("img", { src: picture, alt: "", onError: () => setBroken(true) }) : initials(name) });
1179
1274
  }
1180
1275
 
1181
1276
  // ../ui/dist/ModelAvatar.js
1182
- import { jsx as _jsx27, jsxs as _jsxs16 } from "react/jsx-runtime";
1183
- import { createContext as createContext4, useContext as useContext3, useState as useState9 } from "react";
1277
+ import { jsx as _jsx29, jsxs as _jsxs18 } from "react/jsx-runtime";
1278
+ import { createContext as createContext4, useContext as useContext3, useState as useState10 } from "react";
1184
1279
 
1185
1280
  // ../ui/dist/brand-marks.js
1186
- import { jsx as _jsx26 } from "react/jsx-runtime";
1281
+ import { jsx as _jsx28 } from "react/jsx-runtime";
1187
1282
 
1188
1283
  // ../ui/dist/brand-svg.js
1189
1284
  var BRAND_FILES = ["linux"];
@@ -1247,7 +1342,8 @@ var BRAND_SVG = {
1247
1342
  vercel: '<svg viewBox="0 0 24 24" fill="currentColor" style="display:block;width:100%;height:100%"><path d="m12 1.608 12 20.784H0Z"/></svg>',
1248
1343
  sentry: '<svg viewBox="0 0 24 24" fill="currentColor" style="display:block;width:100%;height:100%"><path d="M13.91 2.505c-.873-1.448-2.972-1.448-3.844 0L6.904 7.92a15.478 15.478 0 0 1 8.53 12.811h-2.221A13.301 13.301 0 0 0 5.784 9.814l-2.926 5.06a7.65 7.65 0 0 1 4.435 5.848H2.194a.365.365 0 0 1-.298-.534l1.413-2.402a5.16 5.16 0 0 0-1.614-.913L.296 19.275a2.182 2.182 0 0 0 .812 2.999 2.24 2.24 0 0 0 1.086.288h6.983a9.322 9.322 0 0 0-3.845-8.318l1.11-1.922a11.47 11.47 0 0 1 4.95 10.24h5.915a17.242 17.242 0 0 0-7.885-15.28l2.244-3.845a.37.37 0 0 1 .504-.13c.255.14 9.75 16.708 9.928 16.9a.365.365 0 0 1-.327.543h-2.287c.029.612.029 1.223 0 1.831h2.297a2.206 2.206 0 0 0 1.922-3.31z"/></svg>',
1249
1344
  apple: '<svg viewBox="0 0 24 24" fill="currentColor" style="display:block;width:100%;height:100%"><path d="M12.152 6.896c-.948 0-2.415-1.078-3.96-1.04-2.04.027-3.91 1.183-4.961 3.014-2.117 3.675-.546 9.103 1.519 12.09 1.013 1.454 2.208 3.09 3.792 3.039 1.52-.065 2.09-.987 3.935-.987 1.831 0 2.35.987 3.96.948 1.637-.026 2.676-1.48 3.676-2.948 1.156-1.688 1.636-3.325 1.662-3.415-.039-.013-3.182-1.221-3.22-4.857-.026-3.04 2.48-4.494 2.597-4.559-1.429-2.09-3.623-2.324-4.39-2.376-2-.156-3.675 1.09-4.61 1.09zM15.53 3.83c.843-1.012 1.4-2.427 1.245-3.83-1.207.052-2.662.805-3.532 1.818-.78.896-1.454 2.338-1.273 3.714 1.338.104 2.715-.688 3.559-1.701"/></svg>',
1250
- windows: `<svg viewBox="0 0 22 22" xmlns:xlink="http://www.w3.org/1999/xlink" style="display:block;width:100%;height:100%"><!-- The Microsoft squares on a white square. The white edge around them is one unit, the same as the gap between them, so the tile reads as the mark with its own margin. Drawn once, so every place shows the same tile. The colours are Microsoft's, as @lobehub/icons-static-svg carries them. --><rect width="22" height="22" fill="#fff"/><rect x="1" y="1" width="9.5" height="9.5" fill="#F25022"/><rect x="11.5" y="1" width="9.5" height="9.5" fill="#7FBA00"/><rect x="1" y="11.5" width="9.5" height="9.5" fill="#00A4EF"/><rect x="11.5" y="11.5" width="9.5" height="9.5" fill="#FFB900"/></svg>`
1345
+ windows: `<svg viewBox="0 0 22 22" xmlns:xlink="http://www.w3.org/1999/xlink" style="display:block;width:100%;height:100%"><!-- The Microsoft squares on a white square. The white edge around them is one unit, the same as the gap between them, so the tile reads as the mark with its own margin. Drawn once, so every place shows the same tile. The colours are Microsoft's, as @lobehub/icons-static-svg carries them. --><rect width="22" height="22" fill="#fff"/><rect x="1" y="1" width="9.5" height="9.5" fill="#F25022"/><rect x="11.5" y="1" width="9.5" height="9.5" fill="#7FBA00"/><rect x="1" y="11.5" width="9.5" height="9.5" fill="#00A4EF"/><rect x="11.5" y="11.5" width="9.5" height="9.5" fill="#FFB900"/></svg>`,
1346
+ docker: '<svg viewBox="0 0 24 24" fill="#2496ED" style="display:block;width:100%;height:100%"><path d="M13.983 11.078h2.119a.186.186 0 00.186-.185V9.006a.186.186 0 00-.186-.186h-2.119a.185.185 0 00-.185.185v1.888c0 .102.083.185.185.185m-2.954-5.43h2.118a.186.186 0 00.186-.186V3.574a.186.186 0 00-.186-.185h-2.118a.185.185 0 00-.185.185v1.888c0 .102.082.185.185.185m0 2.716h2.118a.187.187 0 00.186-.186V6.29a.186.186 0 00-.186-.185h-2.118a.185.185 0 00-.185.185v1.887c0 .102.082.185.185.186m-2.93 0h2.12a.186.186 0 00.184-.186V6.29a.185.185 0 00-.185-.185H8.1a.185.185 0 00-.185.185v1.887c0 .102.083.185.185.186m-2.964 0h2.119a.186.186 0 00.185-.186V6.29a.185.185 0 00-.185-.185H5.136a.186.186 0 00-.186.185v1.887c0 .102.084.185.186.186m5.893 2.715h2.118a.186.186 0 00.186-.185V9.006a.186.186 0 00-.186-.186h-2.118a.185.185 0 00-.185.185v1.888c0 .102.082.185.185.185m-2.93 0h2.12a.185.185 0 00.184-.185V9.006a.185.185 0 00-.184-.186h-2.12a.185.185 0 00-.184.185v1.888c0 .102.083.185.185.185m-2.964 0h2.119a.185.185 0 00.185-.185V9.006a.185.185 0 00-.184-.186h-2.12a.186.186 0 00-.186.186v1.887c0 .102.084.185.186.185m-2.92 0h2.12a.185.185 0 00.184-.185V9.006a.185.185 0 00-.184-.186h-2.12a.185.185 0 00-.184.185v1.888c0 .102.082.185.185.185M23.763 9.89c-.065-.051-.672-.51-1.954-.51-.338.001-.676.03-1.01.087-.248-1.7-1.653-2.53-1.716-2.566l-.344-.199-.226.327c-.284.438-.49.922-.612 1.43-.23.97-.09 1.882.403 2.661-.595.332-1.55.413-1.744.42H.751a.751.751 0 00-.75.748 11.376 11.376 0 00.692 4.062c.545 1.428 1.355 2.48 2.41 3.124 1.18.723 3.1 1.137 5.275 1.137.983.003 1.963-.086 2.93-.266a12.248 12.248 0 003.823-1.389c.98-.567 1.86-1.288 2.61-2.136 1.252-1.418 1.998-2.997 2.553-4.4h.221c1.372 0 2.215-.549 2.68-1.009.309-.293.55-.65.707-1.046l.098-.288Z"/></svg>'
1251
1347
  };
1252
1348
 
1253
1349
  // ../ui/dist/brand-marks.js
@@ -1312,7 +1408,8 @@ var BRAND_NAMES = {
1312
1408
  sentry: "Sentry",
1313
1409
  apple: "Apple",
1314
1410
  linux: "Linux",
1315
- windows: "Windows"
1411
+ windows: "Windows",
1412
+ docker: "Docker"
1316
1413
  };
1317
1414
  function BrandMark({ brand, size: size3 = 16, style }) {
1318
1415
  const props = {
@@ -1321,7 +1418,7 @@ function BrandMark({ brand, size: size3 = 16, style }) {
1321
1418
  "aria-label": BRAND_NAMES[brand],
1322
1419
  style: { width: size3, height: size3, ...style }
1323
1420
  };
1324
- return isFileBrand(brand) ? _jsx26("span", { ...props, "data-brand": brand }) : _jsx26("span", { ...props, dangerouslySetInnerHTML: { __html: BRAND_SVG[brand] } });
1421
+ return isFileBrand(brand) ? _jsx28("span", { ...props, "data-brand": brand }) : _jsx28("span", { ...props, dangerouslySetInnerHTML: { __html: BRAND_SVG[brand] } });
1325
1422
  }
1326
1423
  function isFileBrand(brand) {
1327
1424
  return BRAND_FILES.includes(brand);
@@ -1368,25 +1465,25 @@ function brandOfPublisher(owner) {
1368
1465
  // ../ui/dist/ModelAvatar.js
1369
1466
  var PublisherPicturesContext = createContext4(() => void 0);
1370
1467
  function ModelAvatar({ identity, server, size: size3 = 24 }) {
1371
- return _jsx27(PublisherAvatar, { identity, server, size: size3 }, `${server}:${identity.repository}:${identity.publisher}`);
1468
+ return _jsx29(PublisherAvatar, { identity, server, size: size3 }, `${server}:${identity.repository}:${identity.publisher}`);
1372
1469
  }
1373
1470
  function PublisherAvatar({ identity, server, size: size3 }) {
1374
1471
  const pictures = useContext3(PublisherPicturesContext);
1375
1472
  const owner = identity.repository?.split("/")[0];
1376
1473
  const picture = owner ? pictures(server, owner) : void 0;
1377
- const [failed, setFailed] = useState9(false);
1474
+ const [failed, setFailed] = useState10(false);
1378
1475
  const brand = brandOfPublisher(identity.publisher);
1379
1476
  const tips = useFloatingTips();
1380
1477
  const { hover, tip } = tips("avatar", identity.publisher);
1381
- return _jsxs16("span", { className: "hs-model-avatar", style: { width: size3, height: size3 }, role: "img", "aria-label": identity.publisher, ...hover, children: [tip, picture && !failed ? _jsx27("img", { src: picture, alt: "", width: size3, height: size3, loading: "lazy", decoding: "async", onError: () => setFailed(true) }) : brand ? _jsx27(BrandMark, { brand, size: size3 }) : identity.publisher ? _jsx27("span", { className: "hs-model-avatar-initial", children: identity.publisher.slice(0, 1) }) : _jsx27(PackageGlyph, { size: size3 })] });
1478
+ return _jsxs18("span", { className: "hs-model-avatar", style: { width: size3, height: size3 }, role: "img", "aria-label": identity.publisher, ...hover, children: [tip, picture && !failed ? _jsx29("img", { src: picture, alt: "", width: size3, height: size3, loading: "lazy", decoding: "async", onError: () => setFailed(true) }) : brand ? _jsx29(BrandMark, { brand, size: size3 }) : identity.publisher ? _jsx29("span", { className: "hs-model-avatar-initial", children: identity.publisher.slice(0, 1) }) : _jsx29(PackageGlyph, { size: size3 })] });
1382
1479
  }
1383
1480
 
1384
1481
  // ../ui/dist/VirtualList.js
1385
- import { jsx as _jsx28 } from "react/jsx-runtime";
1386
- import { useImperativeHandle, useRef as useRef11 } from "react";
1482
+ import { jsx as _jsx30 } from "react/jsx-runtime";
1483
+ import { useImperativeHandle, useRef as useRef13 } from "react";
1387
1484
  import { useVirtualizer } from "@tanstack/react-virtual";
1388
1485
  function VirtualList({ count, estimate, row, rowKey, className, ref }) {
1389
- const scroller = useRef11(null);
1486
+ const scroller = useRef13(null);
1390
1487
  const list = useVirtualizer({
1391
1488
  count,
1392
1489
  getScrollElement: () => scroller.current,
@@ -1395,21 +1492,21 @@ function VirtualList({ count, estimate, row, rowKey, className, ref }) {
1395
1492
  ...rowKey && { getItemKey: rowKey }
1396
1493
  });
1397
1494
  useImperativeHandle(ref, () => ({ scrollToIndex: (index, align = "auto") => list.scrollToIndex(index, { align }) }), [list]);
1398
- return _jsx28("div", { ref: scroller, className: className ? `hs-virtual-list ${className}` : "hs-virtual-list", children: _jsx28("div", { className: "hs-virtual-list-rows", style: { height: list.getTotalSize() }, children: list.getVirtualItems().map((item) => _jsx28("div", { ref: list.measureElement, "data-index": item.index, className: "hs-virtual-list-row", style: { transform: `translateY(${item.start}px)` }, children: row(item.index) }, item.key)) }) });
1495
+ return _jsx30("div", { ref: scroller, className: className ? `hs-virtual-list ${className}` : "hs-virtual-list", children: _jsx30("div", { className: "hs-virtual-list-rows", style: { height: list.getTotalSize() }, children: list.getVirtualItems().map((item) => _jsx30("div", { ref: list.measureElement, "data-index": item.index, className: "hs-virtual-list-row", style: { transform: `translateY(${item.start}px)` }, children: row(item.index) }, item.key)) }) });
1399
1496
  }
1400
1497
 
1401
1498
  // ../ui/dist/PictureCompare.js
1402
- import { jsx as _jsx29, jsxs as _jsxs17 } from "react/jsx-runtime";
1403
- import { useRef as useRef12, useState as useState10 } from "react";
1499
+ import { jsx as _jsx31, jsxs as _jsxs19 } from "react/jsx-runtime";
1500
+ import { useRef as useRef14, useState as useState11 } from "react";
1404
1501
  var KEY_STEP = 5;
1405
1502
  function PictureCompare({ before, after }) {
1406
- const [split, setSplit] = useState10(50);
1407
- const box = useRef12(null);
1503
+ const [split, setSplit] = useState11(50);
1504
+ const box = useRef14(null);
1408
1505
  const follow = (event) => {
1409
1506
  const rect = box.current.getBoundingClientRect();
1410
1507
  setSplit(Math.max(0, Math.min(100, (event.clientX - rect.left) / rect.width * 100)));
1411
1508
  };
1412
- return _jsxs17("div", { ref: box, className: "hs-picture-compare", onPointerDown: (event) => {
1509
+ return _jsxs19("div", { ref: box, className: "hs-picture-compare", onPointerDown: (event) => {
1413
1510
  if (event.button !== 0)
1414
1511
  return;
1415
1512
  event.currentTarget.setPointerCapture(event.pointerId);
@@ -1417,13 +1514,13 @@ function PictureCompare({ before, after }) {
1417
1514
  }, onPointerMove: (event) => {
1418
1515
  if (event.currentTarget.hasPointerCapture(event.pointerId))
1419
1516
  follow(event);
1420
- }, children: [_jsx29("img", { src: before.src, alt: before.name, draggable: false }), _jsx29("div", { className: "hs-picture-compare-after", style: { clipPath: `inset(0 0 0 ${split}%)` }, children: _jsx29("img", { src: after.src, alt: after.name, draggable: false }) }), _jsx29("span", { className: "hs-picture-compare-name", "data-side": "before", children: before.name }), _jsx29("span", { className: "hs-picture-compare-name", "data-side": "after", children: after.name }), _jsx29("div", { className: "hs-picture-compare-handle", role: "slider", tabIndex: 0, "aria-label": `${before.name} and ${after.name}`, "aria-valuemin": 0, "aria-valuemax": 100, "aria-valuenow": Math.round(split), style: { left: `${split}%` }, onKeyDown: (event) => {
1517
+ }, children: [_jsx31("img", { src: before.src, alt: before.name, draggable: false }), _jsx31("div", { className: "hs-picture-compare-after", style: { clipPath: `inset(0 0 0 ${split}%)` }, children: _jsx31("img", { src: after.src, alt: after.name, draggable: false }) }), _jsx31("span", { className: "hs-picture-compare-name", "data-side": "before", children: before.name }), _jsx31("span", { className: "hs-picture-compare-name", "data-side": "after", children: after.name }), _jsx31("div", { className: "hs-picture-compare-handle", role: "slider", tabIndex: 0, "aria-label": `${before.name} and ${after.name}`, "aria-valuemin": 0, "aria-valuemax": 100, "aria-valuenow": Math.round(split), style: { left: `${split}%` }, onKeyDown: (event) => {
1421
1518
  const next = event.key === "ArrowLeft" || event.key === "ArrowDown" ? split - KEY_STEP : event.key === "ArrowRight" || event.key === "ArrowUp" ? split + KEY_STEP : event.key === "Home" ? 0 : event.key === "End" ? 100 : null;
1422
1519
  if (next === null)
1423
1520
  return;
1424
1521
  event.preventDefault();
1425
1522
  setSplit(Math.max(0, Math.min(100, next)));
1426
- }, children: _jsx29("span", { className: "hs-picture-compare-knob" }) })] });
1523
+ }, children: _jsx31("span", { className: "hs-picture-compare-knob" }) })] });
1427
1524
  }
1428
1525
 
1429
1526
  // src/Icon.tsx
@@ -1462,8 +1559,10 @@ export {
1462
1559
  MSub,
1463
1560
  MenuSurface,
1464
1561
  Meter,
1562
+ Modal,
1465
1563
  ModelAvatar,
1466
1564
  Mono,
1565
+ MultiSelect,
1467
1566
  Notice,
1468
1567
  OverlayScroll,
1469
1568
  PageTitle,
@@ -1490,5 +1589,6 @@ export {
1490
1589
  ToggleRow,
1491
1590
  VirtualList,
1492
1591
  anchorFrom,
1592
+ spaces,
1493
1593
  useFloatingTips
1494
1594
  };