@geomak/ui 2.0.0 → 4.0.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/dist/index.cjs CHANGED
@@ -1465,70 +1465,208 @@ function ContextMenuLabel({ icon, value }) {
1465
1465
  function ChevronRight2() {
1466
1466
  return /* @__PURE__ */ jsxRuntime.jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, className: "h-4 w-4 flex-shrink-0", "aria-hidden": "true", children: /* @__PURE__ */ jsxRuntime.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M9 5l7 7-7 7" }) });
1467
1467
  }
1468
- function Wizard({ children, steps, storageKey = "po_wizard" }) {
1469
- const wizardRef = React8.useRef(null);
1470
- const [activeStep, setActiveStep] = React8.useState(0);
1471
- const [targetBbox, setTargetBbox] = React8.useState(null);
1472
- const HIGHLIGHTED = React8.useMemo(
1473
- () => ["border", "border-4", "border-prussian-blue", "pointer-events-none"],
1474
- []
1475
- );
1476
- const closeWizard = React8.useCallback(() => {
1477
- steps[activeStep]?.stepRef.current?.classList.remove(...HIGHLIGHTED);
1478
- if (wizardRef.current) wizardRef.current.style.display = "none";
1479
- }, [HIGHLIGHTED, steps, activeStep]);
1480
- React8.useEffect(() => {
1481
- const visited = JSON.parse(localStorage.getItem(storageKey) ?? "false");
1482
- if (visited) {
1483
- closeWizard();
1468
+ function readDismissed(key) {
1469
+ if (key === null) return false;
1470
+ if (typeof window === "undefined") return false;
1471
+ try {
1472
+ return window.localStorage.getItem(key) === "true";
1473
+ } catch {
1474
+ return false;
1475
+ }
1476
+ }
1477
+ function writeDismissed(key) {
1478
+ if (key === null) return;
1479
+ if (typeof window === "undefined") return;
1480
+ try {
1481
+ window.localStorage.setItem(key, "true");
1482
+ } catch {
1483
+ }
1484
+ }
1485
+ function useTargetBbox(ref) {
1486
+ const [bbox, setBbox] = React8.useState(null);
1487
+ React8.useLayoutEffect(() => {
1488
+ const el = ref?.current;
1489
+ if (!el) {
1490
+ setBbox(null);
1484
1491
  return;
1485
1492
  }
1486
- const el = steps[activeStep]?.stepRef.current;
1487
- if (el) {
1488
- setTargetBbox(el.getBoundingClientRect());
1489
- el.classList.add(...HIGHLIGHTED);
1490
- }
1491
- }, [closeWizard, steps, activeStep, HIGHLIGHTED, storageKey]);
1492
- const onStepChange = () => {
1493
- if (steps[activeStep + 1]) {
1494
- steps[activeStep].stepRef.current?.classList.remove(...HIGHLIGHTED);
1495
- const nextEl = steps[activeStep + 1].stepRef.current;
1496
- if (nextEl) setTargetBbox(nextEl.getBoundingClientRect());
1497
- setActiveStep(activeStep + 1);
1498
- } else {
1499
- localStorage.setItem(storageKey, "true");
1500
- closeWizard();
1501
- }
1502
- };
1503
- const step = steps[activeStep];
1504
- const left = step && targetBbox ? step.positioning === "natural" ? isNaN(targetBbox.width + 20) ? 0 : targetBbox.width + 20 : isNaN(targetBbox.width / 2) ? 0 : targetBbox.width / 2 : "auto";
1505
- const top = step && targetBbox ? step.positioning === "natural" ? isNaN(targetBbox.y + 10) ? 0 : targetBbox.y + 10 : isNaN(targetBbox.height / 2) ? 0 : targetBbox.height / 2 : "auto";
1506
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "h-full p-1 rounded-lg w-full", children: [
1507
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute", ref: wizardRef, children: /* @__PURE__ */ jsxRuntime.jsxs(
1508
- "div",
1509
- {
1510
- style: { left, top },
1511
- className: "absolute bg-white rounded-lg p-2 w-[220px] z-50 text-prussian-blue drop-shadow-md transition-all duration-300",
1512
- children: [
1513
- step?.description,
1514
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex justify-end", children: [
1515
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex h-3 w-3 relative left-14 top-4", children: [
1516
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "animate-ping absolute inline-flex h-3 w-3 rounded-full bg-dark-cornflower-blue opacity-75" }),
1517
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "relative inline-flex rounded-full h-3 w-3 bg-dark-cornflower-blue" })
1518
- ] }),
1519
- /* @__PURE__ */ jsxRuntime.jsx(
1520
- Button,
1521
- {
1522
- onClick: onStepChange,
1523
- content: activeStep === steps.length - 1 ? "Close" : "Next",
1524
- style: { width: 55, padding: "5px 10px", margin: "0" }
1525
- }
1526
- )
1527
- ] })
1528
- ]
1493
+ const update = () => setBbox(el.getBoundingClientRect());
1494
+ update();
1495
+ window.addEventListener("scroll", update, true);
1496
+ window.addEventListener("resize", update);
1497
+ const ro = typeof ResizeObserver !== "undefined" ? new ResizeObserver(update) : null;
1498
+ ro?.observe(el);
1499
+ return () => {
1500
+ window.removeEventListener("scroll", update, true);
1501
+ window.removeEventListener("resize", update);
1502
+ ro?.disconnect();
1503
+ };
1504
+ }, [ref]);
1505
+ return bbox;
1506
+ }
1507
+ var TOOLTIP_WIDTH = 280;
1508
+ var TOOLTIP_GAP = 12;
1509
+ function tooltipStyleFor(bbox, placement) {
1510
+ const pl = placement ?? "right";
1511
+ if (pl === "right") return { left: bbox.right + TOOLTIP_GAP, top: bbox.top + bbox.height / 2, transform: "translateY(-50%)", width: TOOLTIP_WIDTH };
1512
+ if (pl === "left") return { left: bbox.left - TOOLTIP_WIDTH - TOOLTIP_GAP, top: bbox.top + bbox.height / 2, transform: "translateY(-50%)", width: TOOLTIP_WIDTH };
1513
+ if (pl === "bottom") return { left: bbox.left + bbox.width / 2, top: bbox.bottom + TOOLTIP_GAP, transform: "translateX(-50%)", width: TOOLTIP_WIDTH };
1514
+ return { left: bbox.left + bbox.width / 2, top: bbox.top - TOOLTIP_GAP, transform: "translate(-50%, -100%)", width: TOOLTIP_WIDTH };
1515
+ }
1516
+ function useFocusTrap(containerRef, active) {
1517
+ React8.useEffect(() => {
1518
+ if (!active) return;
1519
+ const el = containerRef.current;
1520
+ if (!el) return;
1521
+ const t = setTimeout(() => {
1522
+ const first = el.querySelector('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])');
1523
+ first?.focus();
1524
+ }, 0);
1525
+ const onKey = (e) => {
1526
+ if (e.key !== "Tab") return;
1527
+ const focusables = el.querySelectorAll(
1528
+ 'button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])'
1529
+ );
1530
+ if (focusables.length === 0) return;
1531
+ const first = focusables[0];
1532
+ const last = focusables[focusables.length - 1];
1533
+ if (e.shiftKey && document.activeElement === first) {
1534
+ e.preventDefault();
1535
+ last.focus();
1536
+ } else if (!e.shiftKey && document.activeElement === last) {
1537
+ e.preventDefault();
1538
+ first.focus();
1529
1539
  }
1530
- ) }),
1531
- children
1540
+ };
1541
+ document.addEventListener("keydown", onKey);
1542
+ return () => {
1543
+ clearTimeout(t);
1544
+ document.removeEventListener("keydown", onKey);
1545
+ };
1546
+ }, [containerRef, active]);
1547
+ }
1548
+ function Wizard({
1549
+ children,
1550
+ steps,
1551
+ storageKey = "oxygen.wizard.completed",
1552
+ dismissible = true,
1553
+ onComplete,
1554
+ onSkip
1555
+ }) {
1556
+ const tooltipRef = React8.useRef(null);
1557
+ const tooltipTitleId = React8.useId();
1558
+ const tooltipBodyId = React8.useId();
1559
+ const [open, setOpen] = React8.useState(() => steps.length > 0 && !readDismissed(storageKey));
1560
+ const [activeIndex, setActiveIndex] = React8.useState(0);
1561
+ const step = steps[activeIndex];
1562
+ const bbox = useTargetBbox(step?.stepRef);
1563
+ useFocusTrap(tooltipRef, open);
1564
+ React8.useEffect(() => {
1565
+ if (!open || !dismissible) return;
1566
+ const onKey = (e) => {
1567
+ if (e.key === "Escape") {
1568
+ e.preventDefault();
1569
+ handleSkip();
1570
+ }
1571
+ };
1572
+ document.addEventListener("keydown", onKey);
1573
+ return () => document.removeEventListener("keydown", onKey);
1574
+ }, [open, dismissible]);
1575
+ const handleSkip = React8.useCallback(() => {
1576
+ writeDismissed(storageKey);
1577
+ setOpen(false);
1578
+ onSkip?.();
1579
+ }, [storageKey, onSkip]);
1580
+ const handleComplete = React8.useCallback(() => {
1581
+ writeDismissed(storageKey);
1582
+ setOpen(false);
1583
+ onComplete?.();
1584
+ }, [storageKey, onComplete]);
1585
+ const handleNext = () => {
1586
+ if (activeIndex < steps.length - 1) setActiveIndex((i) => i + 1);
1587
+ else handleComplete();
1588
+ };
1589
+ const handlePrev = () => {
1590
+ if (activeIndex > 0) setActiveIndex((i) => i - 1);
1591
+ };
1592
+ const highlightStyle = bbox ? {
1593
+ left: bbox.left - 4,
1594
+ top: bbox.top - 4,
1595
+ width: bbox.width + 8,
1596
+ height: bbox.height + 8
1597
+ } : { display: "none" };
1598
+ const tooltipStyle = bbox ? tooltipStyleFor(bbox, step?.placement) : { display: "none" };
1599
+ const isLast = activeIndex === steps.length - 1;
1600
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1601
+ children,
1602
+ open && step && /* @__PURE__ */ jsxRuntime.jsxs(Portal, { children: [
1603
+ /* @__PURE__ */ jsxRuntime.jsx(
1604
+ "div",
1605
+ {
1606
+ className: "fixed inset-0 z-[7000000] bg-foreground/40 backdrop-blur-[1px] pointer-events-auto",
1607
+ "aria-hidden": "true"
1608
+ }
1609
+ ),
1610
+ /* @__PURE__ */ jsxRuntime.jsx(
1611
+ "div",
1612
+ {
1613
+ className: "fixed z-[7000001] pointer-events-none rounded-md ring-2 ring-accent ring-offset-2 ring-offset-background transition-all duration-200",
1614
+ style: highlightStyle,
1615
+ "aria-hidden": "true"
1616
+ }
1617
+ ),
1618
+ /* @__PURE__ */ jsxRuntime.jsxs(
1619
+ "div",
1620
+ {
1621
+ ref: tooltipRef,
1622
+ role: "dialog",
1623
+ "aria-modal": "true",
1624
+ "aria-labelledby": step.title ? tooltipTitleId : void 0,
1625
+ "aria-describedby": tooltipBodyId,
1626
+ className: "fixed z-[7000002] rounded-lg bg-surface text-foreground border border-border shadow-xl p-4 pointer-events-auto",
1627
+ style: tooltipStyle,
1628
+ children: [
1629
+ step.title && /* @__PURE__ */ jsxRuntime.jsx("h3", { id: tooltipTitleId, className: "text-sm font-semibold text-foreground mb-1", children: step.title }),
1630
+ /* @__PURE__ */ jsxRuntime.jsx("div", { id: tooltipBodyId, className: "text-sm text-foreground-secondary leading-relaxed", children: step.description }),
1631
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-4 flex items-center justify-between", children: [
1632
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-xs text-foreground-muted tabular-nums", children: [
1633
+ activeIndex + 1,
1634
+ " / ",
1635
+ steps.length
1636
+ ] }),
1637
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
1638
+ dismissible && !isLast && /* @__PURE__ */ jsxRuntime.jsx(
1639
+ Button,
1640
+ {
1641
+ variant: "ghost",
1642
+ size: "sm",
1643
+ content: "Skip",
1644
+ onClick: handleSkip
1645
+ }
1646
+ ),
1647
+ activeIndex > 0 && /* @__PURE__ */ jsxRuntime.jsx(
1648
+ Button,
1649
+ {
1650
+ variant: "secondary",
1651
+ size: "sm",
1652
+ content: "Back",
1653
+ onClick: handlePrev
1654
+ }
1655
+ ),
1656
+ /* @__PURE__ */ jsxRuntime.jsx(
1657
+ Button,
1658
+ {
1659
+ size: "sm",
1660
+ content: isLast ? "Done" : "Next",
1661
+ onClick: handleNext
1662
+ }
1663
+ )
1664
+ ] })
1665
+ ] })
1666
+ ]
1667
+ }
1668
+ )
1669
+ ] })
1532
1670
  ] });
1533
1671
  }
1534
1672
  var SearchInput = React8__default.default.forwardRef(function SearchInput2({
@@ -3038,6 +3176,26 @@ function AutoComplete({
3038
3176
  }
3039
3177
  ) });
3040
3178
  }
3179
+ function flattenVisible(items, expanded, depth = 0, out = []) {
3180
+ for (const node of items) {
3181
+ const isParent2 = !!node.children && node.children.length > 0;
3182
+ out.push({ node, depth, isParent: isParent2 });
3183
+ if (isParent2 && expanded.has(node.key)) {
3184
+ flattenVisible(node.children, expanded, depth + 1, out);
3185
+ }
3186
+ }
3187
+ return out;
3188
+ }
3189
+ function findNodeByKey(items, key) {
3190
+ for (const n of items) {
3191
+ if (n.key === key) return n;
3192
+ if (n.children) {
3193
+ const found = findNodeByKey(n.children, key);
3194
+ if (found) return found;
3195
+ }
3196
+ }
3197
+ return null;
3198
+ }
3041
3199
  function TreeSelect({
3042
3200
  label,
3043
3201
  name,
@@ -3046,106 +3204,231 @@ function TreeSelect({
3046
3204
  disabled,
3047
3205
  layout = "horizontal",
3048
3206
  errorMessage,
3049
- style = {},
3207
+ style,
3050
3208
  htmlFor,
3051
- items = []
3209
+ items = [],
3210
+ placeholder = "Select\u2026",
3211
+ parentsSelectable = true,
3212
+ defaultExpandedKeys = []
3052
3213
  }) {
3214
+ const errorId = React8.useId();
3215
+ const hasError = errorMessage != null;
3053
3216
  const [open, setOpen] = React8.useState(false);
3054
- const [hoveredItem, setHoveredItem] = React8.useState(null);
3055
- const [innerItems, setInnerItems] = React8.useState([]);
3217
+ const [expanded, setExpanded] = React8.useState(() => new Set(defaultExpandedKeys));
3218
+ const [activeIndex, setActiveIndex] = React8.useState(0);
3219
+ const listRef = React8.useRef(null);
3220
+ const visible = React8.useMemo(() => flattenVisible(items, expanded), [items, expanded]);
3056
3221
  React8.useEffect(() => {
3057
- setInnerItems(items);
3058
- }, [items]);
3059
- const selectItem = (key) => {
3222
+ if (!open) return;
3223
+ const selectedIdx = visible.findIndex((v) => v.node.key === value);
3224
+ setActiveIndex(selectedIdx >= 0 ? selectedIdx : 0);
3225
+ }, [open, visible, value]);
3226
+ const selectedNode = React8.useMemo(
3227
+ () => value != null ? findNodeByKey(items, value) : null,
3228
+ [items, value]
3229
+ );
3230
+ const toggleExpand = (key) => {
3231
+ setExpanded((prev) => {
3232
+ const next = new Set(prev);
3233
+ if (next.has(key)) next.delete(key);
3234
+ else next.add(key);
3235
+ return next;
3236
+ });
3237
+ };
3238
+ const selectKey = (key) => {
3060
3239
  onChange?.({ target: { value: key, id: htmlFor, name } });
3061
3240
  setOpen(false);
3062
3241
  };
3063
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-2", children: [
3064
- /* @__PURE__ */ jsxRuntime.jsxs(
3065
- "div",
3066
- {
3067
- className: `flex ${layout === "vertical" ? "flex-col" : "flex-row items-center gap-2"}`,
3068
- children: [
3069
- label && /* @__PURE__ */ jsxRuntime.jsx(
3070
- "label",
3071
- {
3072
- className: "text-md font-bold ml-1 max-content select-none text-prussian-blue dark:text-white",
3073
- htmlFor,
3074
- children: label
3075
- }
3076
- ),
3077
- /* @__PURE__ */ jsxRuntime.jsxs(Popover__namespace.Root, { open: open && !disabled, onOpenChange: (o) => !disabled && setOpen(o), children: [
3078
- /* @__PURE__ */ jsxRuntime.jsx(Popover__namespace.Trigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsxs(
3242
+ const onListKey = (e) => {
3243
+ if (visible.length === 0) return;
3244
+ const cur = visible[activeIndex];
3245
+ if (e.key === "ArrowDown") {
3246
+ e.preventDefault();
3247
+ setActiveIndex((i) => Math.min(i + 1, visible.length - 1));
3248
+ } else if (e.key === "ArrowUp") {
3249
+ e.preventDefault();
3250
+ setActiveIndex((i) => Math.max(i - 1, 0));
3251
+ } else if (e.key === "ArrowRight") {
3252
+ e.preventDefault();
3253
+ if (cur.isParent) {
3254
+ if (!expanded.has(cur.node.key)) toggleExpand(cur.node.key);
3255
+ else setActiveIndex((i) => Math.min(i + 1, visible.length - 1));
3256
+ }
3257
+ } else if (e.key === "ArrowLeft") {
3258
+ e.preventDefault();
3259
+ if (cur.isParent && expanded.has(cur.node.key)) {
3260
+ toggleExpand(cur.node.key);
3261
+ } else if (cur.depth > 0) {
3262
+ for (let i = activeIndex - 1; i >= 0; i--) {
3263
+ if (visible[i].depth < cur.depth) {
3264
+ setActiveIndex(i);
3265
+ break;
3266
+ }
3267
+ }
3268
+ }
3269
+ } else if (e.key === "Enter" || e.key === " ") {
3270
+ e.preventDefault();
3271
+ if (cur.node.disabled) return;
3272
+ if (cur.isParent && !parentsSelectable) {
3273
+ toggleExpand(cur.node.key);
3274
+ } else {
3275
+ selectKey(cur.node.key);
3276
+ }
3277
+ } else if (e.key === "Escape") {
3278
+ e.preventDefault();
3279
+ setOpen(false);
3280
+ }
3281
+ };
3282
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1", children: [
3283
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `flex ${layout === "vertical" ? "flex-col gap-1" : "flex-row items-center gap-2"}`, children: [
3284
+ label && /* @__PURE__ */ jsxRuntime.jsx(
3285
+ "label",
3286
+ {
3287
+ className: "text-sm font-medium ml-1 max-content select-none text-foreground",
3288
+ htmlFor,
3289
+ children: label
3290
+ }
3291
+ ),
3292
+ /* @__PURE__ */ jsxRuntime.jsxs(Popover__namespace.Root, { open: open && !disabled, onOpenChange: (o) => !disabled && setOpen(o), children: [
3293
+ /* @__PURE__ */ jsxRuntime.jsx(Popover__namespace.Trigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsxs(
3294
+ "button",
3295
+ {
3296
+ id: htmlFor,
3297
+ type: "button",
3298
+ style,
3299
+ role: "combobox",
3300
+ "aria-expanded": open,
3301
+ "aria-haspopup": "listbox",
3302
+ "aria-invalid": hasError || void 0,
3303
+ "aria-describedby": hasError ? errorId : void 0,
3304
+ disabled,
3305
+ className: `flex items-center justify-between h-9 rounded-lg border ${hasError ? "border-status-error" : "border-border"} px-3 cursor-pointer select-none focus:outline-none focus-visible:ring-2 focus-visible:ring-accent ${disabled ? "cursor-not-allowed bg-surface-raised text-foreground-muted" : "bg-surface text-foreground"} ${!style?.width ? "min-w-[240px]" : ""}`,
3306
+ children: [
3307
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm truncate text-left", children: selectedNode ? selectedNode.label : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-foreground-muted", children: placeholder }) }),
3308
+ /* @__PURE__ */ jsxRuntime.jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, className: `h-4 w-4 flex-shrink-0 transition-transform duration-200 ${open ? "rotate-180" : ""}`, "aria-hidden": "true", children: /* @__PURE__ */ jsxRuntime.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M19 9l-7 7-7-7" }) })
3309
+ ]
3310
+ }
3311
+ ) }),
3312
+ /* @__PURE__ */ jsxRuntime.jsx(Popover__namespace.Portal, { children: /* @__PURE__ */ jsxRuntime.jsx(
3313
+ Popover__namespace.Content,
3314
+ {
3315
+ align: "start",
3316
+ sideOffset: 4,
3317
+ style: { width: style?.width || 280 },
3318
+ className: "bg-surface text-foreground border border-border rounded-lg shadow-md z-50 p-1 animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95",
3319
+ onOpenAutoFocus: (e) => {
3320
+ e.preventDefault();
3321
+ listRef.current?.focus();
3322
+ },
3323
+ children: /* @__PURE__ */ jsxRuntime.jsx(
3079
3324
  "div",
3080
3325
  {
3081
- id: htmlFor,
3082
- style,
3083
- role: "combobox",
3084
- "aria-expanded": open,
3085
- "aria-haspopup": "listbox",
3086
- className: `flex items-center justify-between relative h-9 rounded-lg p-2 cursor-pointer ${disabled ? "cursor-not-allowed bg-disabled" : "bg-white"}`,
3087
- tabIndex: disabled ? -1 : 0,
3088
- children: [
3089
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: `h-7 ${!style?.width ? "min-w-[240px]" : ""} focus:outline-none text-prussian-blue flex items-center gap-1`, children: Array.isArray(value) ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
3090
- value.slice(0, 1).map((val, id) => /* @__PURE__ */ jsxRuntime.jsx(
3091
- DropdownPill,
3092
- {
3093
- hasSiblings: value.length > 1,
3094
- value: innerItems.find((it) => it.key === val)?.label
3095
- },
3096
- id
3097
- )),
3098
- value.length > 1 && /* @__PURE__ */ jsxRuntime.jsx(DropdownPill, { value: `+${value.length - 1} more` })
3099
- ] }) : value != null ? /* @__PURE__ */ jsxRuntime.jsx(DropdownPill, { value: innerItems.find((it) => it.key === value)?.label }) : null }),
3100
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: `transition-transform duration-300 ml-2 ${open ? "rotate-180" : "rotate-0"}`, children: /* @__PURE__ */ jsxRuntime.jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: chunk255PCZIW_cjs.colors_default.PALETTE["prussian-blue"], strokeWidth: 2, className: "h-4 w-4", children: /* @__PURE__ */ jsxRuntime.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M19 9l-7 7-7-7" }) }) })
3101
- ]
3102
- }
3103
- ) }),
3104
- /* @__PURE__ */ jsxRuntime.jsx(Popover__namespace.Portal, { children: /* @__PURE__ */ jsxRuntime.jsx(
3105
- Popover__namespace.Content,
3106
- {
3107
- align: "start",
3108
- sideOffset: 4,
3109
- style: { width: style?.width || 240 },
3110
- className: "bg-ice rounded-lg shadow-md z-50 p-2 animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95",
3111
- children: /* @__PURE__ */ jsxRuntime.jsx("div", { role: "listbox", className: "max-h-40 overflow-y-auto", children: innerItems.map((item, idx) => /* @__PURE__ */ jsxRuntime.jsxs(
3112
- "div",
3326
+ ref: listRef,
3327
+ role: "tree",
3328
+ tabIndex: -1,
3329
+ "aria-activedescendant": visible[activeIndex] ? `tree-opt-${visible[activeIndex].node.key}` : void 0,
3330
+ onKeyDown: onListKey,
3331
+ className: "max-h-72 overflow-y-auto outline-none",
3332
+ children: visible.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-3 py-4 text-sm text-foreground-secondary text-center", children: "No items" }) : visible.map((v, idx) => /* @__PURE__ */ jsxRuntime.jsx(
3333
+ TreeNodeRow,
3113
3334
  {
3114
- role: "option",
3115
- "aria-selected": value === item.key,
3116
- "aria-rowindex": idx,
3117
- className: `flex items-center justify-between p-2 hover:bg-prussian-blue hover:text-white transition-all duration-150 text-sm text-prussian-blue rounded-lg cursor-pointer`,
3118
- onClick: () => selectItem(item.key),
3119
- onMouseEnter: () => setHoveredItem(item.key),
3120
- onMouseLeave: () => setHoveredItem(null),
3121
- children: [
3122
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 text-xs", children: [
3123
- item.icon && /* @__PURE__ */ jsxRuntime.jsx("div", { children: item.icon }),
3124
- item.label
3125
- ] }),
3126
- value === item.key && /* @__PURE__ */ jsxRuntime.jsx("svg", { width: "16", height: "16", viewBox: "0 0 20 20", fill: "none", children: /* @__PURE__ */ jsxRuntime.jsx(
3127
- "path",
3128
- {
3129
- d: "M4 10l4.5 4.5L16 6",
3130
- stroke: hoveredItem === item.key ? "#fff" : chunk255PCZIW_cjs.colors_default.PALETTE["prussian-blue"],
3131
- strokeWidth: "2",
3132
- strokeLinecap: "round",
3133
- strokeLinejoin: "round"
3134
- }
3135
- ) })
3136
- ]
3335
+ node: v.node,
3336
+ depth: v.depth,
3337
+ isParent: v.isParent,
3338
+ isExpanded: expanded.has(v.node.key),
3339
+ isActive: idx === activeIndex,
3340
+ isSelected: value === v.node.key,
3341
+ parentsSelectable,
3342
+ onActivate: () => {
3343
+ if (v.node.disabled) return;
3344
+ if (v.isParent && !parentsSelectable) toggleExpand(v.node.key);
3345
+ else selectKey(v.node.key);
3346
+ },
3347
+ onToggle: () => toggleExpand(v.node.key),
3348
+ onHover: () => setActiveIndex(idx)
3137
3349
  },
3138
- item.key
3139
- )) })
3350
+ v.node.key
3351
+ ))
3140
3352
  }
3141
- ) })
3142
- ] })
3143
- ]
3144
- }
3145
- ),
3146
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-center text-error dark:text-prussian-blue min-h-0", children: errorMessage })
3353
+ )
3354
+ }
3355
+ ) })
3356
+ ] })
3357
+ ] }),
3358
+ hasError && /* @__PURE__ */ jsxRuntime.jsx("div", { id: errorId, className: "text-xs text-status-error ml-1", children: errorMessage })
3147
3359
  ] });
3148
3360
  }
3361
+ function TreeNodeRow({
3362
+ node,
3363
+ depth,
3364
+ isParent: isParent2,
3365
+ isExpanded,
3366
+ isActive,
3367
+ isSelected,
3368
+ parentsSelectable,
3369
+ onActivate,
3370
+ onToggle,
3371
+ onHover
3372
+ }) {
3373
+ const disabled = node.disabled;
3374
+ return /* @__PURE__ */ jsxRuntime.jsxs(
3375
+ "div",
3376
+ {
3377
+ id: `tree-opt-${node.key}`,
3378
+ role: "treeitem",
3379
+ "aria-selected": isSelected,
3380
+ "aria-expanded": isParent2 ? isExpanded : void 0,
3381
+ "aria-disabled": disabled || void 0,
3382
+ "data-active": isActive ? "" : void 0,
3383
+ onMouseEnter: onHover,
3384
+ className: `flex items-center gap-1 rounded-md px-1 py-1.5 select-none cursor-pointer transition-colors duration-100 ${disabled ? "opacity-40 cursor-not-allowed" : isActive ? "bg-accent text-accent-fg" : isSelected ? "bg-surface-raised" : "hover:bg-surface-raised"}`,
3385
+ style: { paddingLeft: depth * 16 + 4 },
3386
+ children: [
3387
+ isParent2 ? /* @__PURE__ */ jsxRuntime.jsx(
3388
+ "button",
3389
+ {
3390
+ type: "button",
3391
+ onClick: (e) => {
3392
+ e.stopPropagation();
3393
+ onToggle();
3394
+ },
3395
+ "aria-label": isExpanded ? "Collapse" : "Expand",
3396
+ tabIndex: -1,
3397
+ className: "w-5 h-5 inline-flex items-center justify-center rounded hover:bg-black/10 focus:outline-none",
3398
+ children: /* @__PURE__ */ jsxRuntime.jsx(
3399
+ "svg",
3400
+ {
3401
+ viewBox: "0 0 24 24",
3402
+ fill: "none",
3403
+ stroke: "currentColor",
3404
+ strokeWidth: 2.5,
3405
+ className: `h-3 w-3 transition-transform duration-150 ${isExpanded ? "rotate-0" : "-rotate-90"}`,
3406
+ "aria-hidden": "true",
3407
+ children: /* @__PURE__ */ jsxRuntime.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M19 9l-7 7-7-7" })
3408
+ }
3409
+ )
3410
+ }
3411
+ ) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "w-5 h-5 inline-block", "aria-hidden": "true" }),
3412
+ /* @__PURE__ */ jsxRuntime.jsxs(
3413
+ "button",
3414
+ {
3415
+ type: "button",
3416
+ onClick: onActivate,
3417
+ disabled,
3418
+ tabIndex: -1,
3419
+ className: "flex-1 flex items-center gap-2 text-sm text-left focus:outline-none disabled:cursor-not-allowed",
3420
+ children: [
3421
+ node.icon,
3422
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: node.label }),
3423
+ isParent2 && !parentsSelectable && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ml-auto text-xs text-foreground-muted", children: "parent" })
3424
+ ]
3425
+ }
3426
+ ),
3427
+ isSelected && /* @__PURE__ */ jsxRuntime.jsx("svg", { width: "16", height: "16", viewBox: "0 0 20 20", fill: "none", "aria-hidden": "true", className: "ml-1", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M4 10l4.5 4.5L16 6", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }) })
3428
+ ]
3429
+ }
3430
+ );
3431
+ }
3149
3432
  function FileInput({
3150
3433
  allowMultiple = false,
3151
3434
  onChange,