@tensor_1/react 2.6.0 → 2.7.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
@@ -106,7 +106,9 @@ __export(src_exports, {
106
106
  UserChip: () => UserChip,
107
107
  applyFilters: () => applyFilters,
108
108
  clampWidth: () => clampWidth,
109
+ computePosition: () => computePosition,
109
110
  cx: () => cx,
111
+ flattenTree: () => flattenTree,
110
112
  highlightSegments: () => highlightSegments,
111
113
  matchFilter: () => matchFilter,
112
114
  mergePinned: () => mergePinned,
@@ -121,6 +123,8 @@ __export(src_exports, {
121
123
  toggleId: () => toggleId,
122
124
  togglePage: () => togglePage,
123
125
  toggleSort: () => toggleSort,
126
+ treeNav: () => treeNav,
127
+ useAnchoredPosition: () => useAnchoredPosition,
124
128
  useDialog: () => useDialog,
125
129
  useTable: () => useTable,
126
130
  virtualWindow: () => virtualWindow
@@ -134,17 +138,48 @@ function cx(...args) {
134
138
 
135
139
  // src/hooks/useDialog.ts
136
140
  var import_react = require("react");
141
+ var lockCount = 0;
142
+ var saved = null;
143
+ function lockBodyScroll() {
144
+ if (typeof document === "undefined") return;
145
+ if (++lockCount > 1) return;
146
+ const el = document.scrollingElement ?? document.documentElement;
147
+ const scrollbarW = window.innerWidth - document.documentElement.clientWidth;
148
+ saved = {
149
+ el,
150
+ overflow: el.style.overflow,
151
+ paddingRight: el.style.paddingRight,
152
+ overscroll: el.style.overscrollBehavior
153
+ };
154
+ el.style.overflow = "hidden";
155
+ if (scrollbarW > 0) {
156
+ const current = parseFloat(getComputedStyle(el).paddingRight) || 0;
157
+ el.style.paddingRight = `${current + scrollbarW}px`;
158
+ }
159
+ el.style.overscrollBehavior = "contain";
160
+ }
161
+ function unlockBodyScroll() {
162
+ if (typeof document === "undefined" || lockCount === 0) return;
163
+ if (--lockCount > 0) return;
164
+ if (saved) {
165
+ saved.el.style.overflow = saved.overflow;
166
+ saved.el.style.paddingRight = saved.paddingRight;
167
+ saved.el.style.overscrollBehavior = saved.overscroll;
168
+ saved = null;
169
+ }
170
+ }
137
171
  function useDialog(open) {
138
172
  const ref = (0, import_react.useRef)(null);
139
173
  (0, import_react.useEffect)(() => {
140
174
  const el = ref.current;
141
- if (!el) return;
142
- if (open) {
143
- if (!el.open) el.showModal();
144
- } else {
145
- if (el.open) el.close();
175
+ if (!el || !open) {
176
+ if (el && el.open) el.close();
177
+ return;
146
178
  }
179
+ if (!el.open) el.showModal();
180
+ lockBodyScroll();
147
181
  return () => {
182
+ unlockBodyScroll();
148
183
  if (el.open) el.close();
149
184
  };
150
185
  }, [open]);
@@ -187,8 +222,36 @@ function toggleSort(keys, field, additive = false) {
187
222
  function asNum(v) {
188
223
  return typeof v === "number" ? v : Number(v);
189
224
  }
225
+ function asTime(v) {
226
+ if (v == null) return NaN;
227
+ if (v instanceof Date) return v.getTime();
228
+ if (typeof v === "number") return v;
229
+ const s = String(v).trim();
230
+ if (s === "") return NaN;
231
+ const parsed = Date.parse(s);
232
+ return Number.isNaN(parsed) ? Number(s) : parsed;
233
+ }
234
+ function matchDate(cell, f) {
235
+ const c = asTime(cell);
236
+ if (Number.isNaN(c)) return false;
237
+ switch (f.op) {
238
+ case "eq":
239
+ return c === asTime(f.value);
240
+ case "lt":
241
+ return c < asTime(f.value);
242
+ case "gt":
243
+ return c > asTime(f.value);
244
+ case "between": {
245
+ const [lo, hi] = f.value.map(asTime);
246
+ return c >= lo && c <= hi;
247
+ }
248
+ default:
249
+ return true;
250
+ }
251
+ }
190
252
  function matchFilter(row, f) {
191
253
  const cell = row[f.field];
254
+ if (f.kind === "date") return matchDate(cell, f);
192
255
  switch (f.op) {
193
256
  case "isAnyOf":
194
257
  return Array.isArray(f.value) && f.value.includes(cell);
@@ -247,13 +310,53 @@ function highlightSegments(text, query) {
247
310
  }
248
311
  return out;
249
312
  }
313
+ var ARRAY_OPS = /* @__PURE__ */ new Set(["isAnyOf", "isNoneOf", "between"]);
314
+ var escapePart = (s) => encodeURIComponent(s).replace(/~/g, "%7E");
315
+ var unescapePart = (s) => decodeURIComponent(s);
316
+ var scalarToStr = (v) => v == null ? "" : String(v);
317
+ function parseScalar(s, kind) {
318
+ switch (kind) {
319
+ case "number":
320
+ return Number(s);
321
+ case "boolean":
322
+ return s === "true";
323
+ case "date":
324
+ return /^-?\d+$/.test(s) ? Number(s) : s;
325
+ // epoch-ms number, else the ISO/date string
326
+ default:
327
+ return s;
328
+ }
329
+ }
330
+ function kindFromOp(op) {
331
+ if (op === "isAnyOf" || op === "isNoneOf") return "enum";
332
+ if (op === "is") return "boolean";
333
+ if (op === "contains" || op === "equals" || op === "startsWith") return "text";
334
+ return "number";
335
+ }
336
+ function serializeFilter(f) {
337
+ const value = Array.isArray(f.value) ? f.value.map((x) => escapePart(scalarToStr(x))).join("|") : escapePart(scalarToStr(f.value));
338
+ return `${escapePart(f.field)}~${f.kind}~${f.op}~${value}`;
339
+ }
340
+ function parseFilter(tok) {
341
+ const parts = tok.split("~");
342
+ let field, kind, op, raw;
343
+ if (parts.length >= 4) {
344
+ [field, kind, op, raw] = [unescapePart(parts[0]), parts[1], parts[2], parts[3] ?? ""];
345
+ } else {
346
+ [field, op, raw] = [unescapePart(parts[0]), parts[1], parts[2] ?? ""];
347
+ kind = kindFromOp(op);
348
+ }
349
+ if (!field || !op) return null;
350
+ const value = ARRAY_OPS.has(op) ? raw.split("|").filter((s) => s !== "").map((s) => parseScalar(unescapePart(s), kind)) : parseScalar(unescapePart(raw), kind);
351
+ return { field, kind, op, value };
352
+ }
250
353
  function serializeView(v) {
251
354
  const p = new URLSearchParams();
252
355
  if (v.sort?.length) p.set("sort", v.sort.map((s) => `${s.field}:${s.dir === "descending" ? "d" : "a"}`).join(","));
253
356
  if (v.columns?.length) p.set("cols", v.columns.join(","));
254
357
  if (v.density && v.density !== "standard") p.set("density", v.density);
255
358
  if (v.q) p.set("q", v.q);
256
- if (v.filters?.length) p.set("filter", v.filters.map((f) => `${f.field}~${f.op}~${Array.isArray(f.value) ? f.value.join("|") : f.value ?? ""}`).join(","));
359
+ if (v.filters?.length) p.set("filter", v.filters.map(serializeFilter).join(","));
257
360
  return p.toString();
258
361
  }
259
362
  function parseView(qs) {
@@ -271,11 +374,9 @@ function parseView(qs) {
271
374
  const q = p.get("q");
272
375
  if (q) v.q = q;
273
376
  const filter = p.get("filter");
274
- if (filter) v.filters = filter.split(",").filter(Boolean).map((tok) => {
275
- const [field, op, raw] = tok.split("~");
276
- const value = raw?.includes("|") ? raw.split("|") : raw;
277
- return { field, kind: "text", op, value };
278
- });
377
+ if (filter) {
378
+ v.filters = filter.split(",").filter(Boolean).map(parseFilter).filter((f) => f !== null);
379
+ }
279
380
  return v;
280
381
  }
281
382
  function virtualWindow(scrollTop, viewportH, rowH, count, overscan = 6) {
@@ -376,10 +477,114 @@ function useTable(data, options = {}) {
376
477
  };
377
478
  }
378
479
 
379
- // src/components/Button.tsx
480
+ // src/hooks/useAnchoredPosition.ts
380
481
  var import_react3 = require("react");
482
+
483
+ // src/overlay-position.ts
484
+ var OPPOSITE = { top: "bottom", bottom: "top", left: "right", right: "left" };
485
+ function parsePlacement(p) {
486
+ const [side, align] = p.split("-");
487
+ return { side, align: align ?? "center" };
488
+ }
489
+ var clamp = (v, min, max) => Math.max(min, Math.min(max, v));
490
+ function spaceOn(side, a, vp, offset) {
491
+ switch (side) {
492
+ case "top":
493
+ return a.y - offset;
494
+ case "bottom":
495
+ return vp.height - (a.y + a.height) - offset;
496
+ case "left":
497
+ return a.x - offset;
498
+ case "right":
499
+ return vp.width - (a.x + a.width) - offset;
500
+ }
501
+ }
502
+ function fitsOn(side, a, f, vp, offset, padding) {
503
+ const need = side === "top" || side === "bottom" ? f.height : f.width;
504
+ return spaceOn(side, a, vp, offset) >= need + padding;
505
+ }
506
+ function computePosition(anchor, floating, viewport, options = {}) {
507
+ const { placement = "bottom-start", offset = 6, padding = 8, flip = true, shift = true } = options;
508
+ const { side: preferred, align } = parsePlacement(placement);
509
+ let side = preferred;
510
+ if (flip && !fitsOn(side, anchor, floating, viewport, offset, padding)) {
511
+ const opp = OPPOSITE[side];
512
+ if (fitsOn(opp, anchor, floating, viewport, offset, padding) || spaceOn(opp, anchor, viewport, offset) > spaceOn(side, anchor, viewport, offset)) {
513
+ side = opp;
514
+ }
515
+ }
516
+ let x, y;
517
+ const horizontal = side === "top" || side === "bottom";
518
+ if (side === "top") y = anchor.y - floating.height - offset;
519
+ else if (side === "bottom") y = anchor.y + anchor.height + offset;
520
+ else if (side === "left") x = anchor.x - floating.width - offset;
521
+ else x = anchor.x + anchor.width + offset;
522
+ if (horizontal) {
523
+ const start = anchor.x;
524
+ const end = anchor.x + anchor.width - floating.width;
525
+ const center = anchor.x + (anchor.width - floating.width) / 2;
526
+ x = align === "start" ? start : align === "end" ? end : center;
527
+ if (shift) x = clamp(x, padding, Math.max(padding, viewport.width - floating.width - padding));
528
+ } else {
529
+ const start = anchor.y;
530
+ const end = anchor.y + anchor.height - floating.height;
531
+ const center = anchor.y + (anchor.height - floating.height) / 2;
532
+ y = align === "start" ? start : align === "end" ? end : center;
533
+ if (shift) y = clamp(y, padding, Math.max(padding, viewport.height - floating.height - padding));
534
+ }
535
+ return {
536
+ x,
537
+ y,
538
+ placement: align === "center" ? side : `${side}-${align}`,
539
+ side,
540
+ align
541
+ };
542
+ }
543
+
544
+ // src/hooks/useAnchoredPosition.ts
545
+ var useIsomorphicLayoutEffect = typeof document !== "undefined" ? import_react3.useLayoutEffect : import_react3.useEffect;
546
+ var resolve = (a) => a && typeof a === "object" && "current" in a ? a.current : a ?? null;
547
+ function useAnchoredPosition(anchor, floatingRef, { open, placement = "bottom-start", offset = 6, padding = 8 }) {
548
+ const opts = (0, import_react3.useRef)({ placement, offset, padding });
549
+ opts.current = { placement, offset, padding };
550
+ const reposition = (0, import_react3.useCallback)(() => {
551
+ const anchorEl = resolve(anchor);
552
+ const floating = floatingRef.current;
553
+ if (!anchorEl || !floating) return;
554
+ const s = floating.style;
555
+ s.position = "fixed";
556
+ s.inset = "auto";
557
+ s.margin = "0";
558
+ const a = anchorEl.getBoundingClientRect();
559
+ const { placement: p, offset: o, padding: pad } = opts.current;
560
+ const pos = computePosition(
561
+ { x: a.x, y: a.y, width: a.width, height: a.height },
562
+ { width: floating.offsetWidth, height: floating.offsetHeight },
563
+ { width: window.innerWidth, height: window.innerHeight },
564
+ { placement: p, offset: o, padding: pad }
565
+ );
566
+ s.left = `${Math.round(pos.x)}px`;
567
+ s.top = `${Math.round(pos.y)}px`;
568
+ floating.dataset.placement = pos.placement;
569
+ }, [anchor, floatingRef]);
570
+ useIsomorphicLayoutEffect(() => {
571
+ if (!open) return;
572
+ reposition();
573
+ const onScroll = () => reposition();
574
+ window.addEventListener("scroll", onScroll, { passive: true, capture: true });
575
+ window.addEventListener("resize", onScroll);
576
+ return () => {
577
+ window.removeEventListener("scroll", onScroll, true);
578
+ window.removeEventListener("resize", onScroll);
579
+ };
580
+ }, [open, reposition]);
581
+ return reposition;
582
+ }
583
+
584
+ // src/components/Button.tsx
585
+ var import_react4 = require("react");
381
586
  var import_jsx_runtime = require("react/jsx-runtime");
382
- var Button = (0, import_react3.forwardRef)(
587
+ var Button = (0, import_react4.forwardRef)(
383
588
  ({ variant = "secondary", size = "md", icon = false, loading = false, className, children, disabled, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
384
589
  "button",
385
590
  {
@@ -404,9 +609,9 @@ function ButtonGroup({ children, className }) {
404
609
  }
405
610
 
406
611
  // src/components/Input.tsx
407
- var import_react4 = require("react");
612
+ var import_react5 = require("react");
408
613
  var import_jsx_runtime2 = require("react/jsx-runtime");
409
- var Input = (0, import_react4.forwardRef)(
614
+ var Input = (0, import_react5.forwardRef)(
410
615
  ({ size = "md", invalid, valid, className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
411
616
  "input",
412
617
  {
@@ -439,9 +644,9 @@ function InputGroup({ children, leadingIcon, trailingIcon, className }) {
439
644
  }
440
645
 
441
646
  // src/components/Textarea.tsx
442
- var import_react5 = require("react");
647
+ var import_react6 = require("react");
443
648
  var import_jsx_runtime3 = require("react/jsx-runtime");
444
- var Textarea = (0, import_react5.forwardRef)(
649
+ var Textarea = (0, import_react6.forwardRef)(
445
650
  ({ invalid, className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
446
651
  "textarea",
447
652
  {
@@ -455,9 +660,9 @@ var Textarea = (0, import_react5.forwardRef)(
455
660
  Textarea.displayName = "Textarea";
456
661
 
457
662
  // src/components/Select.tsx
458
- var import_react6 = require("react");
663
+ var import_react7 = require("react");
459
664
  var import_jsx_runtime4 = require("react/jsx-runtime");
460
- var Select = (0, import_react6.forwardRef)(
665
+ var Select = (0, import_react7.forwardRef)(
461
666
  ({ size = "md", className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
462
667
  "select",
463
668
  {
@@ -470,14 +675,14 @@ var Select = (0, import_react6.forwardRef)(
470
675
  Select.displayName = "Select";
471
676
 
472
677
  // src/components/Checkbox.tsx
473
- var import_react7 = require("react");
678
+ var import_react8 = require("react");
474
679
  var import_jsx_runtime5 = require("react/jsx-runtime");
475
680
  var CheckIcon = () => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("svg", { viewBox: "0 0 12 12", fill: "none", stroke: "currentColor", strokeWidth: 2.5, strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("polyline", { points: "1.5,6 4.5,9 10.5,3" }) });
476
681
  var IndeterminateIcon = () => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("svg", { viewBox: "0 0 12 12", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("rect", { x: "2", y: "5", width: "8", height: "2", rx: "1", fill: "currentColor" }) });
477
- var Checkbox = (0, import_react7.forwardRef)(
682
+ var Checkbox = (0, import_react8.forwardRef)(
478
683
  ({ label, indeterminate, className, ...props }, forwardedRef) => {
479
- const innerRef = (0, import_react7.useRef)(null);
480
- (0, import_react7.useEffect)(() => {
684
+ const innerRef = (0, import_react8.useRef)(null);
685
+ (0, import_react8.useEffect)(() => {
481
686
  const el = innerRef.current;
482
687
  if (el) el.indeterminate = !!indeterminate;
483
688
  }, [indeterminate]);
@@ -502,9 +707,9 @@ var Checkbox = (0, import_react7.forwardRef)(
502
707
  Checkbox.displayName = "Checkbox";
503
708
 
504
709
  // src/components/Radio.tsx
505
- var import_react8 = require("react");
710
+ var import_react9 = require("react");
506
711
  var import_jsx_runtime6 = require("react/jsx-runtime");
507
- var Radio = (0, import_react8.forwardRef)(
712
+ var Radio = (0, import_react9.forwardRef)(
508
713
  ({ label, className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("label", { className: cx("uix-radio", className), children: [
509
714
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("input", { type: "radio", ref, ...props }),
510
715
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "uix-radio__dot" }),
@@ -517,9 +722,9 @@ function RadioGroup({ children, className }) {
517
722
  }
518
723
 
519
724
  // src/components/Switch.tsx
520
- var import_react9 = require("react");
725
+ var import_react10 = require("react");
521
726
  var import_jsx_runtime7 = require("react/jsx-runtime");
522
- var Switch = (0, import_react9.forwardRef)(
727
+ var Switch = (0, import_react10.forwardRef)(
523
728
  ({ label, className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("label", { className: cx("uix-switch", className), children: [
524
729
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("input", { type: "checkbox", role: "switch", ref, ...props }),
525
730
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "uix-switch__track" }),
@@ -529,14 +734,20 @@ var Switch = (0, import_react9.forwardRef)(
529
734
  Switch.displayName = "Switch";
530
735
 
531
736
  // src/components/Field.tsx
737
+ var import_react11 = require("react");
532
738
  var import_jsx_runtime8 = require("react/jsx-runtime");
533
739
  function Field({ label, hint, error, success, required, children, className, htmlFor }) {
740
+ const uid = (0, import_react11.useId)();
741
+ const msgId = `${uid}-msg`;
742
+ const describedBy = error || success || hint ? msgId : void 0;
743
+ const control = (0, import_react11.isValidElement)(children) ? (0, import_react11.cloneElement)(children, {
744
+ "aria-describedby": [children.props["aria-describedby"], describedBy].filter(Boolean).join(" ") || void 0,
745
+ "aria-invalid": error ? true : children.props["aria-invalid"]
746
+ }) : children;
534
747
  return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: cx("uix-field", className), children: [
535
748
  label && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("label", { className: "uix-field__label", htmlFor, "data-required": required || void 0, children: label }),
536
- children,
537
- error && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "uix-field__error", children: error }),
538
- success && !error && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "uix-field__success", children: success }),
539
- hint && !error && !success && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "uix-field__hint", children: hint })
749
+ control,
750
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "uix-field__msg", id: msgId, children: error ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "uix-field__error", role: "alert", children: error }) : success ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "uix-field__success", children: success }) : hint ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "uix-field__hint", children: hint }) : null })
540
751
  ] });
541
752
  }
542
753
 
@@ -666,11 +877,11 @@ function ListItem({ title, meta, trail, leading, children, className, ...props }
666
877
  }
667
878
 
668
879
  // src/components/DescriptionList.tsx
669
- var import_react10 = require("react");
880
+ var import_react12 = require("react");
670
881
  var import_jsx_runtime16 = require("react/jsx-runtime");
671
882
  function DescriptionList({ items, children, className, ...props }) {
672
883
  return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("dl", { className: cx("uix-dl", className), ...props, children: [
673
- items?.map((item, i) => /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_react10.Fragment, { children: [
884
+ items?.map((item, i) => /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_react12.Fragment, { children: [
674
885
  /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("dt", { children: item.term }),
675
886
  /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("dd", { children: item.description })
676
887
  ] }, item.id ?? i)),
@@ -685,14 +896,14 @@ function DescriptionItem({ term, children }) {
685
896
  }
686
897
 
687
898
  // src/components/Tabs.tsx
688
- var import_react11 = require("react");
899
+ var import_react13 = require("react");
689
900
  var import_jsx_runtime17 = require("react/jsx-runtime");
690
- var TabsCtx = (0, import_react11.createContext)({});
901
+ var TabsCtx = (0, import_react13.createContext)({});
691
902
  function Tabs({ variant = "line", value, onChange, children, className }) {
692
903
  return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(TabsCtx.Provider, { value: { value, onChange }, children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { role: "tablist", className: cx("uix-tabs", `uix-tabs--${variant}`, className), children }) });
693
904
  }
694
905
  function Tab({ value, children, disabled, className, onClick, ...props }) {
695
- const ctx = (0, import_react11.useContext)(TabsCtx);
906
+ const ctx = (0, import_react13.useContext)(TabsCtx);
696
907
  const selected = ctx.value === value;
697
908
  return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
698
909
  "button",
@@ -712,7 +923,7 @@ function Tab({ value, children, disabled, className, onClick, ...props }) {
712
923
  }
713
924
 
714
925
  // src/components/AppShell.tsx
715
- var import_react12 = require("react");
926
+ var import_react14 = require("react");
716
927
  var import_jsx_runtime18 = require("react/jsx-runtime");
717
928
  function AppShell({
718
929
  nav,
@@ -727,7 +938,7 @@ function AppShell({
727
938
  ...props
728
939
  }) {
729
940
  const navState = nav ?? (collapsed ? "rail" : "full");
730
- (0, import_react12.useEffect)(() => {
941
+ (0, import_react14.useEffect)(() => {
731
942
  if (!focus || !onExitFocus) return;
732
943
  const onKey = (e) => {
733
944
  if (e.key === "Escape") onExitFocus();
@@ -754,7 +965,7 @@ function AppShell({
754
965
  }
755
966
 
756
967
  // src/components/Sidebar.tsx
757
- var import_react13 = require("react");
968
+ var import_react15 = require("react");
758
969
  var import_jsx_runtime19 = require("react/jsx-runtime");
759
970
  var ChevronIcon = () => /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("svg", { className: "uix-navgroup__chevron", viewBox: "0 0 12 12", fill: "none", stroke: "currentColor", strokeWidth: 1.5, strokeLinecap: "round", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("path", { d: "M2 4l4 4 4-4" }) });
760
971
  function Sidebar({ collapsed, onToggle, brand, toggleIcon, children, className, ...props }) {
@@ -808,7 +1019,7 @@ function NavItem({ icon, badge, active, as: Tag = "a", children, className, ...p
808
1019
  );
809
1020
  }
810
1021
  function NavGroup({ icon, label, children, defaultExpanded = true }) {
811
- const [expanded, setExpanded] = (0, import_react13.useState)(defaultExpanded);
1022
+ const [expanded, setExpanded] = (0, import_react15.useState)(defaultExpanded);
812
1023
  return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "uix-navgroup", children: [
813
1024
  /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
814
1025
  "button",
@@ -876,7 +1087,7 @@ function StarButton({
876
1087
  }
877
1088
 
878
1089
  // src/components/NavFavourites.tsx
879
- var import_react14 = require("react");
1090
+ var import_react16 = require("react");
880
1091
  var import_jsx_runtime21 = require("react/jsx-runtime");
881
1092
  var ChevronDownIcon = () => /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
882
1093
  "svg",
@@ -940,13 +1151,13 @@ function NavFavourites({
940
1151
  emptyReason,
941
1152
  className
942
1153
  }) {
943
- const regionId = (0, import_react14.useId)();
944
- const headerRef = (0, import_react14.useRef)(null);
945
- const regionRef = (0, import_react14.useRef)(null);
946
- const focusAfterRef = (0, import_react14.useRef)(null);
947
- const triggerRefs = (0, import_react14.useRef)(/* @__PURE__ */ new Map());
1154
+ const regionId = (0, import_react16.useId)();
1155
+ const headerRef = (0, import_react16.useRef)(null);
1156
+ const regionRef = (0, import_react16.useRef)(null);
1157
+ const focusAfterRef = (0, import_react16.useRef)(null);
1158
+ const triggerRefs = (0, import_react16.useRef)(/* @__PURE__ */ new Map());
948
1159
  const order = items.map((i) => i.id);
949
- (0, import_react14.useEffect)(() => {
1160
+ (0, import_react16.useEffect)(() => {
950
1161
  const id = focusAfterRef.current;
951
1162
  if (!id) return;
952
1163
  focusAfterRef.current = null;
@@ -1057,18 +1268,18 @@ function FavouriteRow({
1057
1268
  onRemove,
1058
1269
  registerTrigger
1059
1270
  }) {
1060
- const [open, setOpen] = (0, import_react14.useState)(false);
1061
- const [revealed, setRevealed] = (0, import_react14.useState)(false);
1062
- const menuId = (0, import_react14.useId)();
1063
- const triggerRef = (0, import_react14.useRef)(null);
1064
- const itemRefs = (0, import_react14.useRef)([]);
1271
+ const [open, setOpen] = (0, import_react16.useState)(false);
1272
+ const [revealed, setRevealed] = (0, import_react16.useState)(false);
1273
+ const menuId = (0, import_react16.useId)();
1274
+ const triggerRef = (0, import_react16.useRef)(null);
1275
+ const itemRefs = (0, import_react16.useRef)([]);
1065
1276
  const optionsTemplate = labels.options ?? "{label} favourite options";
1066
1277
  const actions = [
1067
1278
  ...isFirst ? [] : [{ label: labels.moveUp, onSelect: onMoveUp }],
1068
1279
  ...isLast ? [] : [{ label: labels.moveDown, onSelect: onMoveDown }],
1069
1280
  { label: labels.remove.replace("{label}", item.label), onSelect: onRemove }
1070
1281
  ];
1071
- (0, import_react14.useEffect)(() => {
1282
+ (0, import_react16.useEffect)(() => {
1072
1283
  if (open) itemRefs.current[0]?.focus();
1073
1284
  }, [open]);
1074
1285
  const closeAndReturnFocus = () => {
@@ -1253,16 +1464,17 @@ function Spinner({ size = "md", accent, className, ...props }) {
1253
1464
  // src/components/Toast.tsx
1254
1465
  var import_jsx_runtime24 = require("react/jsx-runtime");
1255
1466
  function Toast({ title, message, tone, icon, onClose, leaving, className, ...props }) {
1467
+ const assertive = tone === "danger";
1256
1468
  return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
1257
1469
  "div",
1258
1470
  {
1259
1471
  className: cx("uix-toast", tone && `uix-toast--${tone}`, className),
1260
1472
  "data-leaving": leaving || void 0,
1261
- role: "status",
1262
- "aria-live": "polite",
1473
+ role: assertive ? "alert" : "status",
1474
+ "aria-live": assertive ? "assertive" : "polite",
1263
1475
  ...props,
1264
1476
  children: [
1265
- icon && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { className: "uix-toast__icon", children: icon }),
1477
+ icon && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { className: "uix-toast__icon", "aria-hidden": "true", children: icon }),
1266
1478
  /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { className: "uix-toast__body", children: [
1267
1479
  title && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { className: "uix-toast__title", children: title }),
1268
1480
  message && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { className: "uix-toast__msg", children: message })
@@ -1273,7 +1485,7 @@ function Toast({ title, message, tone, icon, onClose, leaving, className, ...pro
1273
1485
  );
1274
1486
  }
1275
1487
  function Toaster({ children, className, ...props }) {
1276
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { className: cx("uix-toaster", className), "aria-live": "polite", "aria-atomic": "false", ...props, children });
1488
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { className: cx("uix-toaster", className), ...props, children });
1277
1489
  }
1278
1490
 
1279
1491
  // src/components/Table.tsx
@@ -1510,9 +1722,64 @@ function Stat({ label, value, icon, meta, trend, className, ...props }) {
1510
1722
  }
1511
1723
 
1512
1724
  // src/components/Tooltip.tsx
1725
+ var import_react17 = require("react");
1513
1726
  var import_jsx_runtime31 = require("react/jsx-runtime");
1514
- function Tooltip({ label, children, ...props }) {
1515
- return /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("span", { "data-uix-tip": label, ...props, children });
1727
+ function Tooltip({ label, placement = "top", className, children, ...props }) {
1728
+ const triggerRef = (0, import_react17.useRef)(null);
1729
+ const bubbleRef = (0, import_react17.useRef)(null);
1730
+ const [open, setOpen] = (0, import_react17.useState)(false);
1731
+ const id = (0, import_react17.useId)();
1732
+ useAnchoredPosition(triggerRef, bubbleRef, { open, placement, offset: 6 });
1733
+ const show = () => {
1734
+ const b = bubbleRef.current;
1735
+ if (b && !b.matches(":popover-open")) {
1736
+ try {
1737
+ b.showPopover();
1738
+ } catch {
1739
+ }
1740
+ }
1741
+ setOpen(true);
1742
+ };
1743
+ const hide = () => {
1744
+ const b = bubbleRef.current;
1745
+ if (b && b.matches(":popover-open")) {
1746
+ try {
1747
+ b.hidePopover();
1748
+ } catch {
1749
+ }
1750
+ }
1751
+ setOpen(false);
1752
+ };
1753
+ (0, import_react17.useEffect)(() => () => {
1754
+ const b = bubbleRef.current;
1755
+ if (b && b.matches(":popover-open")) {
1756
+ try {
1757
+ b.hidePopover();
1758
+ } catch {
1759
+ }
1760
+ }
1761
+ }, []);
1762
+ const bubbleAttr = { popover: "manual" };
1763
+ return /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(
1764
+ "span",
1765
+ {
1766
+ ref: triggerRef,
1767
+ className: cx("uix-tooltip", className),
1768
+ "aria-describedby": id,
1769
+ onMouseEnter: show,
1770
+ onMouseLeave: hide,
1771
+ onFocus: show,
1772
+ onBlur: hide,
1773
+ onKeyDown: (e) => {
1774
+ if (e.key === "Escape") hide();
1775
+ },
1776
+ ...props,
1777
+ children: [
1778
+ children,
1779
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("span", { ref: bubbleRef, id, role: "tooltip", className: "uix-tooltip__bubble", ...bubbleAttr, children: label })
1780
+ ]
1781
+ }
1782
+ );
1516
1783
  }
1517
1784
 
1518
1785
  // src/components/States.tsx
@@ -1614,9 +1881,9 @@ function ComposerBar({ children, className, ...props }) {
1614
1881
  }
1615
1882
 
1616
1883
  // src/components/Segmented.tsx
1617
- var import_react15 = require("react");
1884
+ var import_react18 = require("react");
1618
1885
  var import_jsx_runtime37 = require("react/jsx-runtime");
1619
- var SegmentedCtx = (0, import_react15.createContext)({});
1886
+ var SegmentedCtx = (0, import_react18.createContext)({});
1620
1887
  function Segmented({ value, onChange, children, className, ...props }) {
1621
1888
  return /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(SegmentedCtx.Provider, { value: { value, onChange }, children: /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("div", { role: "group", className: cx("uix-segmented", className), ...props, children }) });
1622
1889
  }
@@ -1627,7 +1894,7 @@ function SegmentedOption({
1627
1894
  onClick,
1628
1895
  ...props
1629
1896
  }) {
1630
- const ctx = (0, import_react15.useContext)(SegmentedCtx);
1897
+ const ctx = (0, import_react18.useContext)(SegmentedCtx);
1631
1898
  const selected = ctx.value === value;
1632
1899
  return /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
1633
1900
  "button",
@@ -1676,10 +1943,37 @@ function Note({ tone, icon, children, className, ...props }) {
1676
1943
  }
1677
1944
 
1678
1945
  // src/components/Popover.tsx
1946
+ var import_react19 = require("react");
1679
1947
  var import_jsx_runtime40 = require("react/jsx-runtime");
1680
- function Popover({ popover = "auto", children, className, ...props }) {
1948
+ function Popover({
1949
+ popover = "auto",
1950
+ anchor,
1951
+ placement = "bottom-start",
1952
+ offset = 6,
1953
+ className,
1954
+ children,
1955
+ ...props
1956
+ }) {
1957
+ const ref = (0, import_react19.useRef)(null);
1958
+ const [open, setOpen] = (0, import_react19.useState)(false);
1681
1959
  const popoverAttr = { popover };
1682
- return /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("div", { className: cx("uix-popover", className), ...popoverAttr, ...props, children });
1960
+ const reposition = useAnchoredPosition(anchor ?? null, ref, {
1961
+ open: open && !!anchor,
1962
+ placement,
1963
+ offset
1964
+ });
1965
+ (0, import_react19.useEffect)(() => {
1966
+ const el = ref.current;
1967
+ if (!el || !anchor) return;
1968
+ const sync = () => {
1969
+ const isOpen = el.matches(":popover-open");
1970
+ if (isOpen) reposition();
1971
+ setOpen(isOpen);
1972
+ };
1973
+ el.addEventListener("toggle", sync);
1974
+ return () => el.removeEventListener("toggle", sync);
1975
+ }, [anchor, reposition]);
1976
+ return /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("div", { ref, className: cx("uix-popover", className), ...popoverAttr, ...props, children });
1683
1977
  }
1684
1978
 
1685
1979
  // src/components/CommandPalette.tsx
@@ -1766,48 +2060,124 @@ function KanbanCard({ title, meta, children, className, ...props }) {
1766
2060
  }
1767
2061
 
1768
2062
  // src/components/Tree.tsx
1769
- var import_react16 = require("react");
2063
+ var import_react20 = require("react");
2064
+
2065
+ // src/tree-model.ts
2066
+ function flattenTree(nodes, expanded, level = 1) {
2067
+ const out = [];
2068
+ const setsize = nodes.length;
2069
+ nodes.forEach((node, i) => {
2070
+ const hasChildren = Array.isArray(node.children) && node.children.length > 0;
2071
+ const isExpanded = hasChildren && expanded.has(node.id);
2072
+ out.push({ node, level, setsize, posinset: i + 1, hasChildren, isExpanded });
2073
+ if (isExpanded) out.push(...flattenTree(node.children, expanded, level + 1));
2074
+ });
2075
+ return out;
2076
+ }
2077
+ function treeNav(flat, currentId, key) {
2078
+ const idx = flat.findIndex((f) => f.node.id === currentId);
2079
+ if (idx < 0) return {};
2080
+ const cur = flat[idx];
2081
+ const focusAt = (i) => {
2082
+ const clamped = Math.max(0, Math.min(flat.length - 1, i));
2083
+ return { focusId: flat[clamped]?.node.id };
2084
+ };
2085
+ switch (key) {
2086
+ case "ArrowDown":
2087
+ return focusAt(idx + 1);
2088
+ case "ArrowUp":
2089
+ return focusAt(idx - 1);
2090
+ case "Home":
2091
+ return focusAt(0);
2092
+ case "End":
2093
+ return focusAt(flat.length - 1);
2094
+ case "ArrowRight":
2095
+ if (cur.hasChildren && !cur.isExpanded) return { toggleId: cur.node.id };
2096
+ if (cur.hasChildren && cur.isExpanded) return focusAt(idx + 1);
2097
+ return {};
2098
+ case "ArrowLeft":
2099
+ if (cur.hasChildren && cur.isExpanded) return { toggleId: cur.node.id };
2100
+ for (let i = idx - 1; i >= 0; i--) {
2101
+ if (flat[i].level === cur.level - 1) return { focusId: flat[i].node.id };
2102
+ }
2103
+ return {};
2104
+ case "Enter":
2105
+ case " ":
2106
+ return cur.hasChildren ? { toggleId: cur.node.id, selectId: cur.node.id } : { selectId: cur.node.id };
2107
+ default:
2108
+ return {};
2109
+ }
2110
+ }
2111
+
2112
+ // src/components/Tree.tsx
1770
2113
  var import_jsx_runtime44 = require("react/jsx-runtime");
1771
2114
  var ChevronIcon2 = () => /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("svg", { viewBox: "0 0 12 12", fill: "none", stroke: "currentColor", strokeWidth: 1.5, strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("path", { d: "M2 4l4 4 4-4" }) });
1772
- function TreeNode({ node, expanded, selected, onToggle, onSelect, level = 0 }) {
2115
+ function TreeNode({ node, expanded, selected, selectable, tabbableId, onToggle, onSelect, onFocusNode, level = 1 }) {
1773
2116
  const hasChildren = Array.isArray(node.children) && node.children.length > 0;
1774
2117
  const isExpanded = expanded.has(node.id);
1775
- return /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)("li", { children: [
1776
- /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)(
1777
- "button",
1778
- {
1779
- className: "uix-tree__row",
1780
- "aria-expanded": hasChildren ? isExpanded : void 0,
1781
- "aria-selected": selected === node.id,
1782
- onClick: () => {
1783
- if (hasChildren) onToggle(node.id);
1784
- onSelect?.(node.id);
1785
- },
1786
- children: [
2118
+ return /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)(
2119
+ "li",
2120
+ {
2121
+ role: "treeitem",
2122
+ "aria-level": level,
2123
+ "aria-expanded": hasChildren ? isExpanded : void 0,
2124
+ "aria-selected": selectable ? selected === node.id : void 0,
2125
+ "data-id": node.id,
2126
+ tabIndex: tabbableId === node.id ? 0 : -1,
2127
+ className: "uix-tree__item",
2128
+ onFocus: (e) => {
2129
+ if (e.target === e.currentTarget) onFocusNode(node.id);
2130
+ },
2131
+ onClick: (e) => {
2132
+ e.stopPropagation();
2133
+ if (hasChildren) onToggle(node.id);
2134
+ onSelect?.(node.id);
2135
+ onFocusNode(node.id);
2136
+ },
2137
+ children: [
2138
+ /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)("span", { className: "uix-tree__row", children: [
1787
2139
  /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("span", { className: "uix-tree__toggle", children: hasChildren && /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(ChevronIcon2, {}) }),
1788
2140
  node.icon && /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("span", { "aria-hidden": "true", children: node.icon }),
1789
- node.label
1790
- ]
1791
- }
1792
- ),
1793
- hasChildren && isExpanded && /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("ul", { children: node.children.map((child) => /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
1794
- TreeNode,
1795
- {
1796
- node: child,
1797
- expanded,
1798
- selected,
1799
- onToggle,
1800
- onSelect,
1801
- level: level + 1
1802
- },
1803
- child.id
1804
- )) })
1805
- ] });
2141
+ /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("span", { className: "uix-tree__label", children: node.label })
2142
+ ] }),
2143
+ hasChildren && isExpanded && /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("ul", { role: "group", className: "uix-tree__group", children: node.children.map((child) => /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
2144
+ TreeNode,
2145
+ {
2146
+ node: child,
2147
+ expanded,
2148
+ selected,
2149
+ selectable,
2150
+ tabbableId,
2151
+ onToggle,
2152
+ onSelect,
2153
+ onFocusNode,
2154
+ level: level + 1
2155
+ },
2156
+ child.id
2157
+ )) })
2158
+ ]
2159
+ }
2160
+ );
1806
2161
  }
1807
- function Tree({ nodes, expanded: controlledExpanded, defaultExpanded, selected, onToggle, onSelect, className, ...props }) {
1808
- const [internalExpanded, setInternalExpanded] = (0, import_react16.useState)(defaultExpanded ?? /* @__PURE__ */ new Set());
2162
+ function Tree({
2163
+ nodes,
2164
+ expanded: controlledExpanded,
2165
+ defaultExpanded,
2166
+ selected,
2167
+ onToggle,
2168
+ onSelect,
2169
+ virtualize,
2170
+ rowHeight = 32,
2171
+ maxHeight = 384,
2172
+ className,
2173
+ ...props
2174
+ }) {
2175
+ const [internalExpanded, setInternalExpanded] = (0, import_react20.useState)(defaultExpanded ?? /* @__PURE__ */ new Set());
1809
2176
  const expanded = controlledExpanded ?? internalExpanded;
1810
- function handleToggle(id) {
2177
+ const rootRef = (0, import_react20.useRef)(null);
2178
+ const [focusedId, setFocusedId] = (0, import_react20.useState)(void 0);
2179
+ const selectable = selected !== void 0 || onSelect != null;
2180
+ const handleToggle = (0, import_react20.useCallback)((id) => {
1811
2181
  if (!controlledExpanded) {
1812
2182
  setInternalExpanded((prev) => {
1813
2183
  const next = new Set(prev);
@@ -1817,19 +2187,202 @@ function Tree({ nodes, expanded: controlledExpanded, defaultExpanded, selected,
1817
2187
  });
1818
2188
  }
1819
2189
  onToggle?.(id);
2190
+ }, [controlledExpanded, onToggle]);
2191
+ const flat = (0, import_react20.useMemo)(() => flattenTree(nodes, expanded), [nodes, expanded]);
2192
+ const useVirtual = virtualize ?? shouldVirtualize(flat.length);
2193
+ const tabbableId = focusedId ?? selected ?? nodes[0]?.id;
2194
+ if (useVirtual) {
2195
+ return /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
2196
+ VirtualTreeView,
2197
+ {
2198
+ flat,
2199
+ rowHeight,
2200
+ maxHeight,
2201
+ selectable,
2202
+ selected,
2203
+ tabbableId,
2204
+ onToggle: handleToggle,
2205
+ onSelect,
2206
+ focusedId,
2207
+ setFocusedId,
2208
+ className,
2209
+ ...props
2210
+ }
2211
+ );
1820
2212
  }
1821
- return /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("ul", { className: cx("uix-tree", className), role: "tree", ...props, children: nodes.map((node) => /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
2213
+ const treeItems = () => Array.from(rootRef.current?.querySelectorAll('[role="treeitem"]') ?? []);
2214
+ const focusItem = (el) => {
2215
+ if (el) {
2216
+ el.focus();
2217
+ setFocusedId(el.dataset.id);
2218
+ }
2219
+ };
2220
+ const onKeyDown = (e) => {
2221
+ const cur = e.target.closest('[role="treeitem"]');
2222
+ if (!cur || !rootRef.current?.contains(cur)) return;
2223
+ const list = treeItems();
2224
+ const idx = list.indexOf(cur);
2225
+ const id = cur.dataset.id;
2226
+ const exp = cur.getAttribute("aria-expanded");
2227
+ switch (e.key) {
2228
+ case "ArrowDown":
2229
+ e.preventDefault();
2230
+ focusItem(list[Math.min(idx + 1, list.length - 1)]);
2231
+ break;
2232
+ case "ArrowUp":
2233
+ e.preventDefault();
2234
+ focusItem(list[Math.max(idx - 1, 0)]);
2235
+ break;
2236
+ case "Home":
2237
+ e.preventDefault();
2238
+ focusItem(list[0]);
2239
+ break;
2240
+ case "End":
2241
+ e.preventDefault();
2242
+ focusItem(list[list.length - 1]);
2243
+ break;
2244
+ case "ArrowRight":
2245
+ e.preventDefault();
2246
+ if (exp === "false") handleToggle(id);
2247
+ else if (exp === "true") focusItem(cur.querySelector(':scope > [role="group"] > [role="treeitem"]'));
2248
+ break;
2249
+ case "ArrowLeft":
2250
+ e.preventDefault();
2251
+ if (exp === "true") handleToggle(id);
2252
+ else focusItem(cur.parentElement?.closest('[role="treeitem"]'));
2253
+ break;
2254
+ case "Enter":
2255
+ case " ":
2256
+ e.preventDefault();
2257
+ if (exp !== null) handleToggle(id);
2258
+ onSelect?.(id);
2259
+ setFocusedId(id);
2260
+ break;
2261
+ default:
2262
+ break;
2263
+ }
2264
+ };
2265
+ return /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("ul", { ref: rootRef, className: cx("uix-tree", className), role: "tree", onKeyDown, ...props, children: nodes.map((node) => /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
1822
2266
  TreeNode,
1823
2267
  {
1824
2268
  node,
1825
2269
  expanded,
1826
2270
  selected,
2271
+ selectable,
2272
+ tabbableId,
1827
2273
  onToggle: handleToggle,
1828
- onSelect
2274
+ onSelect,
2275
+ onFocusNode: setFocusedId,
2276
+ level: 1
1829
2277
  },
1830
2278
  node.id
1831
2279
  )) });
1832
2280
  }
2281
+ function VirtualTreeView({
2282
+ flat,
2283
+ rowHeight,
2284
+ maxHeight,
2285
+ selectable,
2286
+ selected,
2287
+ tabbableId,
2288
+ onToggle,
2289
+ onSelect,
2290
+ focusedId,
2291
+ setFocusedId,
2292
+ className,
2293
+ ...props
2294
+ }) {
2295
+ const scrollRef = (0, import_react20.useRef)(null);
2296
+ const [scrollTop, setScrollTop] = (0, import_react20.useState)(0);
2297
+ const pendingFocus = (0, import_react20.useRef)(false);
2298
+ const win = virtualWindow(scrollTop, maxHeight, rowHeight, flat.length);
2299
+ const rows = flat.slice(win.start, win.end);
2300
+ (0, import_react20.useEffect)(() => {
2301
+ if (!pendingFocus.current || focusedId == null) return;
2302
+ const el = scrollRef.current?.querySelector(`[role="treeitem"][data-id="${CSS.escape(focusedId)}"]`);
2303
+ if (el) {
2304
+ el.focus();
2305
+ pendingFocus.current = false;
2306
+ }
2307
+ }, [focusedId, win.start, win.end]);
2308
+ const moveTo = (id) => {
2309
+ const i = flat.findIndex((f) => f.node.id === id);
2310
+ if (i < 0) return;
2311
+ const top = i * rowHeight;
2312
+ let next = scrollTop;
2313
+ if (top < scrollTop) next = top;
2314
+ else if (top + rowHeight > scrollTop + maxHeight) next = top + rowHeight - maxHeight;
2315
+ if (next !== scrollTop) {
2316
+ if (scrollRef.current) scrollRef.current.scrollTop = next;
2317
+ setScrollTop(next);
2318
+ }
2319
+ pendingFocus.current = true;
2320
+ setFocusedId(id);
2321
+ };
2322
+ const onKeyDown = (e) => {
2323
+ const curId = e.target.closest('[role="treeitem"]')?.dataset.id ?? focusedId;
2324
+ const act = treeNav(flat, curId, e.key);
2325
+ if (act.focusId === void 0 && act.toggleId === void 0 && act.selectId === void 0) return;
2326
+ e.preventDefault();
2327
+ if (act.toggleId !== void 0) onToggle(act.toggleId);
2328
+ if (act.selectId !== void 0) {
2329
+ onSelect?.(act.selectId);
2330
+ if (act.focusId === void 0) {
2331
+ pendingFocus.current = true;
2332
+ setFocusedId(act.selectId);
2333
+ }
2334
+ }
2335
+ if (act.focusId !== void 0) moveTo(act.focusId);
2336
+ };
2337
+ return /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)(
2338
+ "ul",
2339
+ {
2340
+ ref: scrollRef,
2341
+ className: cx("uix-tree", "uix-tree--virtual", className),
2342
+ role: "tree",
2343
+ style: { maxHeight, overflow: "auto" },
2344
+ onScroll: (e) => setScrollTop(e.currentTarget.scrollTop),
2345
+ onKeyDown,
2346
+ ...props,
2347
+ children: [
2348
+ win.padTop > 0 && /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("li", { "aria-hidden": "true", style: { height: win.padTop } }),
2349
+ rows.map((f) => {
2350
+ const rowStyle = { height: rowHeight, paddingLeft: `calc(${f.level - 1} * var(--uix-space-5))` };
2351
+ return /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
2352
+ "li",
2353
+ {
2354
+ role: "treeitem",
2355
+ "aria-level": f.level,
2356
+ "aria-setsize": f.setsize,
2357
+ "aria-posinset": f.posinset,
2358
+ "aria-expanded": f.hasChildren ? f.isExpanded : void 0,
2359
+ "aria-selected": selectable ? selected === f.node.id : void 0,
2360
+ "data-id": f.node.id,
2361
+ tabIndex: tabbableId === f.node.id ? 0 : -1,
2362
+ className: "uix-tree__item",
2363
+ onFocus: (e) => {
2364
+ if (e.target === e.currentTarget) setFocusedId(f.node.id);
2365
+ },
2366
+ onClick: (e) => {
2367
+ e.stopPropagation();
2368
+ if (f.hasChildren) onToggle(f.node.id);
2369
+ onSelect?.(f.node.id);
2370
+ setFocusedId(f.node.id);
2371
+ },
2372
+ children: /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)("span", { className: "uix-tree__row", style: rowStyle, children: [
2373
+ /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("span", { className: "uix-tree__toggle", children: f.hasChildren && /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(ChevronIcon2, {}) }),
2374
+ f.node.icon && /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("span", { "aria-hidden": "true", children: f.node.icon }),
2375
+ /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("span", { className: "uix-tree__label", children: f.node.label })
2376
+ ] })
2377
+ },
2378
+ f.node.id
2379
+ );
2380
+ }),
2381
+ win.padBottom > 0 && /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("li", { "aria-hidden": "true", style: { height: win.padBottom } })
2382
+ ]
2383
+ }
2384
+ );
2385
+ }
1833
2386
  // Annotate the CommonJS export names for ESM import in node:
1834
2387
  0 && (module.exports = {
1835
2388
  Alert,
@@ -1918,7 +2471,9 @@ function Tree({ nodes, expanded: controlledExpanded, defaultExpanded, selected,
1918
2471
  UserChip,
1919
2472
  applyFilters,
1920
2473
  clampWidth,
2474
+ computePosition,
1921
2475
  cx,
2476
+ flattenTree,
1922
2477
  highlightSegments,
1923
2478
  matchFilter,
1924
2479
  mergePinned,
@@ -1933,6 +2488,8 @@ function Tree({ nodes, expanded: controlledExpanded, defaultExpanded, selected,
1933
2488
  toggleId,
1934
2489
  togglePage,
1935
2490
  toggleSort,
2491
+ treeNav,
2492
+ useAnchoredPosition,
1936
2493
  useDialog,
1937
2494
  useTable,
1938
2495
  virtualWindow