@tensor_1/react 2.11.0 → 2.12.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
@@ -140,6 +140,7 @@ __export(src_exports, {
140
140
  useAnchoredPosition: () => useAnchoredPosition,
141
141
  useDialog: () => useDialog,
142
142
  useTable: () => useTable,
143
+ useVirtualRows: () => useVirtualRows,
143
144
  virtualWindow: () => virtualWindow
144
145
  });
145
146
  module.exports = __toCommonJS(src_exports);
@@ -203,22 +204,23 @@ function useDialog(open) {
203
204
  var import_react2 = require("react");
204
205
 
205
206
  // src/table-engine.ts
207
+ var stringCollator = new Intl.Collator(void 0, { numeric: true, sensitivity: "base" });
206
208
  function compare(a, b) {
207
209
  if (a == null && b == null) return 0;
208
210
  if (a == null) return -1;
209
211
  if (b == null) return 1;
210
212
  if (typeof a === "number" && typeof b === "number") return a - b;
211
- return String(a).localeCompare(String(b), void 0, { numeric: true, sensitivity: "base" });
213
+ return stringCollator.compare(String(a), String(b));
212
214
  }
213
215
  function multiSort(rows, keys) {
214
216
  if (!keys.length) return rows.slice();
215
- return rows.map((row, i) => ({ row, i })).sort((x, y) => {
217
+ return rows.slice().sort((a, b) => {
216
218
  for (const k of keys) {
217
- const c = compare(x.row[k.field], y.row[k.field]);
219
+ const c = compare(a[k.field], b[k.field]);
218
220
  if (c !== 0) return k.dir === "ascending" ? c : -c;
219
221
  }
220
- return x.i - y.i;
221
- }).map((w) => w.row);
222
+ return 0;
223
+ });
222
224
  }
223
225
  function nextSortDir(current) {
224
226
  if (current === "ascending") return "descending";
@@ -418,7 +420,8 @@ function toggleId(set, id) {
418
420
  return next;
419
421
  }
420
422
  function selectAllState(selected, pageIds) {
421
- const on = pageIds.filter((id) => selected.has(id)).length;
423
+ let on = 0;
424
+ for (const id of pageIds) if (selected.has(id)) on += 1;
422
425
  if (on === 0) return "none";
423
426
  return on === pageIds.length ? "all" : "some";
424
427
  }
@@ -444,18 +447,22 @@ function useTable(data, options = {}) {
444
447
  const [query, setQuery] = (0, import_react2.useState)(() => options.initialQuery ?? "");
445
448
  const [selected, setSelected] = (0, import_react2.useState)(() => /* @__PURE__ */ new Set());
446
449
  const [pinned, setPinned] = (0, import_react2.useState)(() => new Set(options.initialPinned ?? []));
447
- const rows = (0, import_react2.useMemo)(() => {
448
- const filtered = applyFilters(data, filters);
449
- const searched = searchRows(filtered, query, searchFields);
450
- const sorted = multiSort(searched, sort);
451
- return mergePinned(data, sorted, pinned, idField);
452
- }, [data, filters, query, searchFields, sort, pinned, idField]);
450
+ const visibleRows = (0, import_react2.useMemo)(() => {
451
+ const filtered = filters.length ? applyFilters(data, filters) : data;
452
+ const searched = query.trim() ? searchRows(filtered, query, searchFields) : filtered;
453
+ return sort.length ? multiSort(searched, sort) : searched;
454
+ }, [data, filters, query, searchFields, sort]);
455
+ const rows = (0, import_react2.useMemo)(
456
+ () => pinned.size ? mergePinned(data, visibleRows, pinned, idField) : visibleRows.slice(),
457
+ [data, visibleRows, pinned, idField]
458
+ );
453
459
  const pageIds = (0, import_react2.useMemo)(() => rows.map((r) => String(r[idField])), [rows, idField]);
454
460
  const selectAll = selectAllState(selected, pageIds);
455
461
  const doToggleSort = (0, import_react2.useCallback)((field, additive = false) => {
456
462
  setSort((keys) => toggleSort(keys, field, additive));
457
463
  }, []);
458
464
  const toggleRow = (0, import_react2.useCallback)((id) => setSelected((s) => toggleId(s, id)), []);
465
+ const isSelected = (0, import_react2.useCallback)((id) => selected.has(id), [selected]);
459
466
  const toggleAllRows = (0, import_react2.useCallback)(() => setSelected((s) => togglePage(s, pageIds)), [pageIds]);
460
467
  const clearSelection = (0, import_react2.useCallback)(() => setSelected(/* @__PURE__ */ new Set()), []);
461
468
  const togglePin = (0, import_react2.useCallback)((id) => setPinned((s) => toggleId(s, id)), []);
@@ -479,7 +486,7 @@ function useTable(data, options = {}) {
479
486
  setSort,
480
487
  setFilters,
481
488
  setQuery,
482
- isSelected: (id) => selected.has(id),
489
+ isSelected,
483
490
  toggleRow,
484
491
  toggleAllRows,
485
492
  clearSelection,
@@ -490,8 +497,55 @@ function useTable(data, options = {}) {
490
497
  };
491
498
  }
492
499
 
493
- // src/hooks/useAnchoredPosition.ts
500
+ // src/hooks/useVirtualRows.ts
494
501
  var import_react3 = require("react");
502
+ function useVirtualRows(rows, { rowHeight, overscan = 6, threshold = 100 }) {
503
+ const containerRef = (0, import_react3.useRef)(null);
504
+ const [viewport, setViewport] = (0, import_react3.useState)({ scrollTop: 0, height: 0 });
505
+ (0, import_react3.useLayoutEffect)(() => {
506
+ const element = containerRef.current;
507
+ if (!element) return;
508
+ let frame = 0;
509
+ const commitMeasurement = () => {
510
+ const next = { scrollTop: element.scrollTop, height: element.clientHeight };
511
+ setViewport((current) => current.scrollTop === next.scrollTop && current.height === next.height ? current : next);
512
+ };
513
+ const measure = () => {
514
+ if (frame) return;
515
+ frame = requestAnimationFrame(() => {
516
+ frame = 0;
517
+ commitMeasurement();
518
+ });
519
+ };
520
+ commitMeasurement();
521
+ element.addEventListener("scroll", measure, { passive: true });
522
+ const observer = new ResizeObserver(measure);
523
+ observer.observe(element);
524
+ return () => {
525
+ element.removeEventListener("scroll", measure);
526
+ observer.disconnect();
527
+ if (frame) cancelAnimationFrame(frame);
528
+ };
529
+ }, []);
530
+ const enabled = viewport.height > 0 && shouldVirtualize(rows.length, threshold);
531
+ const window2 = (0, import_react3.useMemo)(
532
+ () => enabled ? virtualWindow(viewport.scrollTop, viewport.height, rowHeight, rows.length, overscan) : { start: 0, end: rows.length, padTop: 0, padBottom: 0, total: rows.length * rowHeight },
533
+ [enabled, overscan, rowHeight, rows.length, viewport.height, viewport.scrollTop]
534
+ );
535
+ const visibleRows = (0, import_react3.useMemo)(() => rows.slice(window2.start, window2.end), [rows, window2.start, window2.end]);
536
+ return {
537
+ containerRef,
538
+ rows: visibleRows,
539
+ startIndex: window2.start,
540
+ padTop: window2.padTop,
541
+ padBottom: window2.padBottom,
542
+ totalHeight: window2.total,
543
+ virtualized: enabled
544
+ };
545
+ }
546
+
547
+ // src/hooks/useAnchoredPosition.ts
548
+ var import_react4 = require("react");
495
549
 
496
550
  // src/overlay-position.ts
497
551
  var OPPOSITE = { top: "bottom", bottom: "top", left: "right", right: "left" };
@@ -555,12 +609,12 @@ function computePosition(anchor, floating, viewport, options = {}) {
555
609
  }
556
610
 
557
611
  // src/hooks/useAnchoredPosition.ts
558
- var useIsomorphicLayoutEffect = typeof document !== "undefined" ? import_react3.useLayoutEffect : import_react3.useEffect;
612
+ var useIsomorphicLayoutEffect = typeof document !== "undefined" ? import_react4.useLayoutEffect : import_react4.useEffect;
559
613
  var resolve = (a) => a && typeof a === "object" && "current" in a ? a.current : a ?? null;
560
614
  function useAnchoredPosition(anchor, floatingRef, { open, placement = "bottom-start", offset = 6, padding = 8 }) {
561
- const opts = (0, import_react3.useRef)({ placement, offset, padding });
615
+ const opts = (0, import_react4.useRef)({ placement, offset, padding });
562
616
  opts.current = { placement, offset, padding };
563
- const reposition = (0, import_react3.useCallback)(() => {
617
+ const reposition = (0, import_react4.useCallback)(() => {
564
618
  const anchorEl = resolve(anchor);
565
619
  const floating = floatingRef.current;
566
620
  if (!anchorEl || !floating) return;
@@ -595,9 +649,9 @@ function useAnchoredPosition(anchor, floatingRef, { open, placement = "bottom-st
595
649
  }
596
650
 
597
651
  // src/components/Button.tsx
598
- var import_react4 = require("react");
652
+ var import_react5 = require("react");
599
653
  var import_jsx_runtime = require("react/jsx-runtime");
600
- var Button = (0, import_react4.forwardRef)(
654
+ var Button = (0, import_react5.forwardRef)(
601
655
  ({ variant = "secondary", size = "md", icon = false, loading = false, className, children, disabled, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
602
656
  "button",
603
657
  {
@@ -622,9 +676,9 @@ function ButtonGroup({ children, className }) {
622
676
  }
623
677
 
624
678
  // src/components/Input.tsx
625
- var import_react5 = require("react");
679
+ var import_react6 = require("react");
626
680
  var import_jsx_runtime2 = require("react/jsx-runtime");
627
- var Input = (0, import_react5.forwardRef)(
681
+ var Input = (0, import_react6.forwardRef)(
628
682
  ({ size = "md", invalid, valid, className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
629
683
  "input",
630
684
  {
@@ -657,9 +711,9 @@ function InputGroup({ children, leadingIcon, trailingIcon, className }) {
657
711
  }
658
712
 
659
713
  // src/components/Textarea.tsx
660
- var import_react6 = require("react");
714
+ var import_react7 = require("react");
661
715
  var import_jsx_runtime3 = require("react/jsx-runtime");
662
- var Textarea = (0, import_react6.forwardRef)(
716
+ var Textarea = (0, import_react7.forwardRef)(
663
717
  ({ invalid, className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
664
718
  "textarea",
665
719
  {
@@ -673,9 +727,9 @@ var Textarea = (0, import_react6.forwardRef)(
673
727
  Textarea.displayName = "Textarea";
674
728
 
675
729
  // src/components/Select.tsx
676
- var import_react7 = require("react");
730
+ var import_react8 = require("react");
677
731
  var import_jsx_runtime4 = require("react/jsx-runtime");
678
- var Select = (0, import_react7.forwardRef)(
732
+ var Select = (0, import_react8.forwardRef)(
679
733
  ({ size = "md", className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
680
734
  "select",
681
735
  {
@@ -688,14 +742,14 @@ var Select = (0, import_react7.forwardRef)(
688
742
  Select.displayName = "Select";
689
743
 
690
744
  // src/components/Checkbox.tsx
691
- var import_react8 = require("react");
745
+ var import_react9 = require("react");
692
746
  var import_jsx_runtime5 = require("react/jsx-runtime");
693
747
  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" }) });
694
748
  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" }) });
695
- var Checkbox = (0, import_react8.forwardRef)(
749
+ var Checkbox = (0, import_react9.forwardRef)(
696
750
  ({ label, indeterminate, className, ...props }, forwardedRef) => {
697
- const innerRef = (0, import_react8.useRef)(null);
698
- (0, import_react8.useEffect)(() => {
751
+ const innerRef = (0, import_react9.useRef)(null);
752
+ (0, import_react9.useEffect)(() => {
699
753
  const el = innerRef.current;
700
754
  if (el) el.indeterminate = !!indeterminate;
701
755
  }, [indeterminate]);
@@ -720,9 +774,9 @@ var Checkbox = (0, import_react8.forwardRef)(
720
774
  Checkbox.displayName = "Checkbox";
721
775
 
722
776
  // src/components/Radio.tsx
723
- var import_react9 = require("react");
777
+ var import_react10 = require("react");
724
778
  var import_jsx_runtime6 = require("react/jsx-runtime");
725
- var Radio = (0, import_react9.forwardRef)(
779
+ var Radio = (0, import_react10.forwardRef)(
726
780
  ({ label, className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("label", { className: cx("uix-radio", className), children: [
727
781
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("input", { type: "radio", ref, ...props }),
728
782
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "uix-radio__dot" }),
@@ -735,9 +789,9 @@ function RadioGroup({ children, className }) {
735
789
  }
736
790
 
737
791
  // src/components/Switch.tsx
738
- var import_react10 = require("react");
792
+ var import_react11 = require("react");
739
793
  var import_jsx_runtime7 = require("react/jsx-runtime");
740
- var Switch = (0, import_react10.forwardRef)(
794
+ var Switch = (0, import_react11.forwardRef)(
741
795
  ({ label, className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("label", { className: cx("uix-switch", className), children: [
742
796
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("input", { type: "checkbox", role: "switch", ref, ...props }),
743
797
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "uix-switch__track" }),
@@ -747,13 +801,13 @@ var Switch = (0, import_react10.forwardRef)(
747
801
  Switch.displayName = "Switch";
748
802
 
749
803
  // src/components/Field.tsx
750
- var import_react11 = require("react");
804
+ var import_react12 = require("react");
751
805
  var import_jsx_runtime8 = require("react/jsx-runtime");
752
806
  function Field({ label, hint, error, success, required, children, className, htmlFor }) {
753
- const uid = (0, import_react11.useId)();
807
+ const uid = (0, import_react12.useId)();
754
808
  const msgId = `${uid}-msg`;
755
809
  const describedBy = error || success || hint ? msgId : void 0;
756
- const control = (0, import_react11.isValidElement)(children) ? (0, import_react11.cloneElement)(children, {
810
+ const control = (0, import_react12.isValidElement)(children) ? (0, import_react12.cloneElement)(children, {
757
811
  "aria-describedby": [children.props["aria-describedby"], describedBy].filter(Boolean).join(" ") || void 0,
758
812
  "aria-invalid": error ? true : children.props["aria-invalid"]
759
813
  }) : children;
@@ -890,11 +944,11 @@ function ListItem({ title, meta, trail, leading, children, className, ...props }
890
944
  }
891
945
 
892
946
  // src/components/DescriptionList.tsx
893
- var import_react12 = require("react");
947
+ var import_react13 = require("react");
894
948
  var import_jsx_runtime16 = require("react/jsx-runtime");
895
949
  function DescriptionList({ items, children, className, ...props }) {
896
950
  return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("dl", { className: cx("uix-dl", className), ...props, children: [
897
- items?.map((item, i) => /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_react12.Fragment, { children: [
951
+ items?.map((item, i) => /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_react13.Fragment, { children: [
898
952
  /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("dt", { children: item.term }),
899
953
  /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("dd", { children: item.description })
900
954
  ] }, item.id ?? i)),
@@ -909,14 +963,15 @@ function DescriptionItem({ term, children }) {
909
963
  }
910
964
 
911
965
  // src/components/Tabs.tsx
912
- var import_react13 = require("react");
966
+ var import_react14 = require("react");
913
967
  var import_jsx_runtime17 = require("react/jsx-runtime");
914
- var TabsCtx = (0, import_react13.createContext)({});
968
+ var TabsCtx = (0, import_react14.createContext)({});
915
969
  function Tabs({ variant = "line", value, onChange, children, className }) {
916
- 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 }) });
970
+ const contextValue = (0, import_react14.useMemo)(() => ({ value, onChange }), [value, onChange]);
971
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(TabsCtx.Provider, { value: contextValue, children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { role: "tablist", className: cx("uix-tabs", `uix-tabs--${variant}`, className), children }) });
917
972
  }
918
973
  function Tab({ value, children, disabled, className, onClick, ...props }) {
919
- const ctx = (0, import_react13.useContext)(TabsCtx);
974
+ const ctx = (0, import_react14.useContext)(TabsCtx);
920
975
  const selected = ctx.value === value;
921
976
  return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
922
977
  "button",
@@ -936,7 +991,7 @@ function Tab({ value, children, disabled, className, onClick, ...props }) {
936
991
  }
937
992
 
938
993
  // src/components/AppShell.tsx
939
- var import_react14 = require("react");
994
+ var import_react15 = require("react");
940
995
  var import_jsx_runtime18 = require("react/jsx-runtime");
941
996
  function AppShell({
942
997
  nav,
@@ -951,7 +1006,7 @@ function AppShell({
951
1006
  ...props
952
1007
  }) {
953
1008
  const navState = nav ?? (collapsed ? "rail" : "full");
954
- (0, import_react14.useEffect)(() => {
1009
+ (0, import_react15.useEffect)(() => {
955
1010
  if (!focus || !onExitFocus) return;
956
1011
  const onKey = (e) => {
957
1012
  if (e.key === "Escape") onExitFocus();
@@ -978,7 +1033,7 @@ function AppShell({
978
1033
  }
979
1034
 
980
1035
  // src/components/Sidebar.tsx
981
- var import_react15 = require("react");
1036
+ var import_react16 = require("react");
982
1037
  var import_jsx_runtime19 = require("react/jsx-runtime");
983
1038
  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" }) });
984
1039
  function Sidebar({ collapsed, onToggle, brand, toggleIcon, children, className, ...props }) {
@@ -1032,7 +1087,7 @@ function NavItem({ icon, badge, active, as: Tag = "a", children, className, ...p
1032
1087
  );
1033
1088
  }
1034
1089
  function NavGroup({ icon, label, children, defaultExpanded = true }) {
1035
- const [expanded, setExpanded] = (0, import_react15.useState)(defaultExpanded);
1090
+ const [expanded, setExpanded] = (0, import_react16.useState)(defaultExpanded);
1036
1091
  return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "uix-navgroup", children: [
1037
1092
  /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
1038
1093
  "button",
@@ -1100,7 +1155,7 @@ function StarButton({
1100
1155
  }
1101
1156
 
1102
1157
  // src/components/NavFavourites.tsx
1103
- var import_react16 = require("react");
1158
+ var import_react17 = require("react");
1104
1159
  var import_jsx_runtime21 = require("react/jsx-runtime");
1105
1160
  var ChevronDownIcon = () => /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
1106
1161
  "svg",
@@ -1114,7 +1169,7 @@ var ChevronDownIcon = () => /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
1114
1169
  strokeLinecap: "round",
1115
1170
  strokeLinejoin: "round",
1116
1171
  "aria-hidden": "true",
1117
- style: { flexShrink: 0 },
1172
+ className: "uix-favourites__chevron",
1118
1173
  children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("path", { d: "m6 9 6 6 6-6" })
1119
1174
  }
1120
1175
  );
@@ -1130,7 +1185,7 @@ var ChevronRightIcon = () => /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
1130
1185
  strokeLinecap: "round",
1131
1186
  strokeLinejoin: "round",
1132
1187
  "aria-hidden": "true",
1133
- style: { flexShrink: 0 },
1188
+ className: "uix-favourites__chevron",
1134
1189
  children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("path", { d: "m9 18 6-6-6-6" })
1135
1190
  }
1136
1191
  );
@@ -1153,7 +1208,6 @@ var MoreIcon = () => /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
1153
1208
  ]
1154
1209
  }
1155
1210
  );
1156
- var MUTED = "color-mix(in srgb, var(--uix-text) 50%, transparent)";
1157
1211
  function NavFavourites({
1158
1212
  items,
1159
1213
  collapsed = false,
@@ -1164,20 +1218,20 @@ function NavFavourites({
1164
1218
  emptyReason,
1165
1219
  className
1166
1220
  }) {
1167
- const regionId = (0, import_react16.useId)();
1168
- const headerRef = (0, import_react16.useRef)(null);
1169
- const regionRef = (0, import_react16.useRef)(null);
1170
- const focusAfterRef = (0, import_react16.useRef)(null);
1171
- const triggerRefs = (0, import_react16.useRef)(/* @__PURE__ */ new Map());
1172
- const order = items.map((i) => i.id);
1173
- (0, import_react16.useEffect)(() => {
1221
+ const regionId = (0, import_react17.useId)();
1222
+ const headerRef = (0, import_react17.useRef)(null);
1223
+ const regionRef = (0, import_react17.useRef)(null);
1224
+ const focusAfterRef = (0, import_react17.useRef)(null);
1225
+ const triggerRefs = (0, import_react17.useRef)(/* @__PURE__ */ new Map());
1226
+ const order = (0, import_react17.useMemo)(() => items.map((item) => item.id), [items]);
1227
+ (0, import_react17.useEffect)(() => {
1174
1228
  const id = focusAfterRef.current;
1175
1229
  if (!id) return;
1176
1230
  focusAfterRef.current = null;
1177
1231
  const el = triggerRefs.current.get(id);
1178
1232
  if (el) el.focus();
1179
1233
  else headerRef.current?.focus();
1180
- }, [order.join("|")]);
1234
+ }, [order]);
1181
1235
  const handleToggle = () => {
1182
1236
  if (!collapsed) {
1183
1237
  const active = document.activeElement;
@@ -1187,7 +1241,7 @@ function NavFavourites({
1187
1241
  }
1188
1242
  onToggleCollapsed?.();
1189
1243
  };
1190
- const move = (id, dir) => {
1244
+ const move = (0, import_react17.useCallback)((id, dir) => {
1191
1245
  const idx = order.indexOf(id);
1192
1246
  const next = idx + dir;
1193
1247
  if (idx < 0 || next < 0 || next >= order.length) return;
@@ -1197,14 +1251,18 @@ function NavFavourites({
1197
1251
  reordered.splice(next, 0, it);
1198
1252
  focusAfterRef.current = id;
1199
1253
  onReorder(reordered);
1200
- };
1201
- const remove = (id) => {
1254
+ }, [onReorder, order]);
1255
+ const remove = (0, import_react17.useCallback)((id) => {
1202
1256
  const idx = order.indexOf(id);
1203
1257
  const neighbour = order[idx + 1] ?? order[idx - 1] ?? null;
1204
1258
  focusAfterRef.current = neighbour;
1205
1259
  onRemove(id);
1206
- };
1207
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: cx("uix-favourites", className), style: { marginBottom: "var(--uix-space-1)" }, children: [
1260
+ }, [onRemove, order]);
1261
+ const registerTrigger = (0, import_react17.useCallback)((id, element) => {
1262
+ if (element) triggerRefs.current.set(id, element);
1263
+ else triggerRefs.current.delete(id);
1264
+ }, []);
1265
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: cx("uix-favourites", className), children: [
1208
1266
  /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
1209
1267
  "button",
1210
1268
  {
@@ -1213,18 +1271,7 @@ function NavFavourites({
1213
1271
  "aria-expanded": !collapsed,
1214
1272
  "aria-controls": regionId,
1215
1273
  onClick: handleToggle,
1216
- className: "uix-text-eyebrow",
1217
- style: {
1218
- display: "flex",
1219
- width: "100%",
1220
- alignItems: "center",
1221
- gap: "6px",
1222
- padding: "8px 4px 4px",
1223
- border: 0,
1224
- background: "transparent",
1225
- cursor: "pointer",
1226
- color: "inherit"
1227
- },
1274
+ className: "uix-favourites__heading uix-text-eyebrow",
1228
1275
  children: [
1229
1276
  collapsed ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ChevronRightIcon, {}) : /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ChevronDownIcon, {}),
1230
1277
  labels.heading
@@ -1237,20 +1284,12 @@ function NavFavourites({
1237
1284
  ref: regionRef,
1238
1285
  id: regionId,
1239
1286
  hidden: collapsed,
1240
- style: {
1241
- display: "flex",
1242
- flexDirection: "column",
1243
- gap: "1px",
1244
- listStyle: "none",
1245
- margin: 0,
1246
- padding: 0
1247
- },
1287
+ className: "uix-favourites__list",
1248
1288
  children: items.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
1249
1289
  "li",
1250
1290
  {
1251
- className: "uix-text-meta",
1291
+ className: "uix-favourites__empty uix-text-meta",
1252
1292
  "data-empty-reason": emptyReason,
1253
- style: { padding: "6px 8px", color: MUTED },
1254
1293
  children: labels.empty
1255
1294
  }
1256
1295
  ) : items.map((item, idx) => /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
@@ -1260,10 +1299,9 @@ function NavFavourites({
1260
1299
  isFirst: idx === 0,
1261
1300
  isLast: idx === items.length - 1,
1262
1301
  labels,
1263
- onMoveUp: () => move(item.id, -1),
1264
- onMoveDown: () => move(item.id, 1),
1265
- onRemove: () => remove(item.id),
1266
- registerTrigger: (el) => triggerRefs.current.set(item.id, el)
1302
+ onMove: move,
1303
+ onRemove: remove,
1304
+ registerTrigger
1267
1305
  },
1268
1306
  item.id
1269
1307
  ))
@@ -1271,28 +1309,26 @@ function NavFavourites({
1271
1309
  )
1272
1310
  ] });
1273
1311
  }
1274
- function FavouriteRow({
1312
+ var FavouriteRow = (0, import_react17.memo)(function FavouriteRow2({
1275
1313
  item,
1276
1314
  isFirst,
1277
1315
  isLast,
1278
1316
  labels,
1279
- onMoveUp,
1280
- onMoveDown,
1317
+ onMove,
1281
1318
  onRemove,
1282
1319
  registerTrigger
1283
1320
  }) {
1284
- const [open, setOpen] = (0, import_react16.useState)(false);
1285
- const [revealed, setRevealed] = (0, import_react16.useState)(false);
1286
- const menuId = (0, import_react16.useId)();
1287
- const triggerRef = (0, import_react16.useRef)(null);
1288
- const itemRefs = (0, import_react16.useRef)([]);
1321
+ const [open, setOpen] = (0, import_react17.useState)(false);
1322
+ const menuId = (0, import_react17.useId)();
1323
+ const triggerRef = (0, import_react17.useRef)(null);
1324
+ const itemRefs = (0, import_react17.useRef)([]);
1289
1325
  const optionsTemplate = labels.options ?? "{label} favourite options";
1290
1326
  const actions = [
1291
- ...isFirst ? [] : [{ label: labels.moveUp, onSelect: onMoveUp }],
1292
- ...isLast ? [] : [{ label: labels.moveDown, onSelect: onMoveDown }],
1293
- { label: labels.remove.replace("{label}", item.label), onSelect: onRemove }
1327
+ ...isFirst ? [] : [{ label: labels.moveUp, onSelect: () => onMove(item.id, -1) }],
1328
+ ...isLast ? [] : [{ label: labels.moveDown, onSelect: () => onMove(item.id, 1) }],
1329
+ { label: labels.remove.replace("{label}", item.label), onSelect: () => onRemove(item.id) }
1294
1330
  ];
1295
- (0, import_react16.useEffect)(() => {
1331
+ (0, import_react17.useEffect)(() => {
1296
1332
  if (open) itemRefs.current[0]?.focus();
1297
1333
  }, [open]);
1298
1334
  const closeAndReturnFocus = () => {
@@ -1319,34 +1355,27 @@ function FavouriteRow({
1319
1355
  if (!e.altKey) return;
1320
1356
  if (e.key === "ArrowUp" && !isFirst) {
1321
1357
  e.preventDefault();
1322
- onMoveUp();
1358
+ onMove(item.id, -1);
1323
1359
  } else if (e.key === "ArrowDown" && !isLast) {
1324
1360
  e.preventDefault();
1325
- onMoveDown();
1361
+ onMove(item.id, 1);
1326
1362
  }
1327
1363
  };
1328
- const triggerVisible = revealed || open;
1329
1364
  return (
1330
1365
  // biome-ignore lint/a11y/noStaticElementInteractions: keyboard accelerators on the row; the focusable controls inside carry their own semantics.
1331
1366
  /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
1332
1367
  "li",
1333
1368
  {
1334
- style: { position: "relative", display: "flex", alignItems: "center" },
1369
+ className: "uix-favourites__row",
1335
1370
  onKeyDown: onRowKeyDown,
1336
- onMouseEnter: () => setRevealed(true),
1337
- onMouseLeave: () => setRevealed(false),
1338
- onFocus: () => setRevealed(true),
1339
- onBlur: (e) => {
1340
- if (!e.currentTarget.contains(e.relatedTarget)) setRevealed(false);
1341
- },
1342
1371
  children: [
1343
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { style: { minWidth: 0, flex: 1 }, children: item.link }),
1372
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { className: "uix-favourites__link", children: item.link }),
1344
1373
  /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
1345
1374
  "button",
1346
1375
  {
1347
1376
  ref: (el) => {
1348
1377
  triggerRef.current = el;
1349
- registerTrigger(el);
1378
+ registerTrigger(item.id, el);
1350
1379
  },
1351
1380
  type: "button",
1352
1381
  "aria-haspopup": "menu",
@@ -1354,22 +1383,7 @@ function FavouriteRow({
1354
1383
  "aria-controls": open ? menuId : void 0,
1355
1384
  "aria-label": optionsTemplate.replace("{label}", item.label),
1356
1385
  onClick: () => setOpen((v) => !v),
1357
- style: {
1358
- marginLeft: "auto",
1359
- display: "inline-flex",
1360
- height: "20px",
1361
- width: "20px",
1362
- flexShrink: 0,
1363
- alignItems: "center",
1364
- justifyContent: "center",
1365
- border: 0,
1366
- borderRadius: "2px",
1367
- background: "transparent",
1368
- cursor: "pointer",
1369
- color: MUTED,
1370
- opacity: triggerVisible ? 1 : 0,
1371
- transition: "opacity var(--uix-dur-fast) var(--uix-ease-out)"
1372
- },
1386
+ className: "uix-favourites__menu-trigger",
1373
1387
  children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(MoreIcon, {})
1374
1388
  }
1375
1389
  ),
@@ -1380,7 +1394,7 @@ function FavouriteRow({
1380
1394
  "aria-hidden": "true",
1381
1395
  role: "presentation",
1382
1396
  onClick: () => setOpen(false),
1383
- style: { position: "fixed", inset: 0, cursor: "default", background: "transparent", zIndex: 40 }
1397
+ className: "uix-favourites__backdrop"
1384
1398
  }
1385
1399
  ),
1386
1400
  /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
@@ -1389,21 +1403,7 @@ function FavouriteRow({
1389
1403
  id: menuId,
1390
1404
  role: "menu",
1391
1405
  onKeyDown: onMenuKeyDown,
1392
- style: {
1393
- position: "absolute",
1394
- right: 0,
1395
- top: "calc(100% + 2px)",
1396
- zIndex: 50,
1397
- minWidth: "160px",
1398
- overflow: "hidden",
1399
- margin: 0,
1400
- padding: "4px 0",
1401
- listStyle: "none",
1402
- borderRadius: "6px",
1403
- border: "1px solid var(--uix-border)",
1404
- background: "var(--uix-surface)",
1405
- boxShadow: "var(--uix-shadow-popover)"
1406
- },
1406
+ className: "uix-favourites__menu",
1407
1407
  children: actions.map((action, i) => /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("li", { role: "presentation", children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
1408
1408
  "button",
1409
1409
  {
@@ -1417,25 +1417,7 @@ function FavouriteRow({
1417
1417
  setOpen(false);
1418
1418
  action.onSelect();
1419
1419
  },
1420
- style: {
1421
- display: "flex",
1422
- width: "100%",
1423
- alignItems: "center",
1424
- padding: "6px 12px",
1425
- border: 0,
1426
- background: "transparent",
1427
- cursor: "pointer",
1428
- textAlign: "left",
1429
- fontSize: "13px",
1430
- color: "color-mix(in srgb, var(--uix-text) 85%, transparent)",
1431
- transition: "background-color var(--uix-dur-fast) var(--uix-ease-out)"
1432
- },
1433
- onMouseEnter: (e) => {
1434
- e.currentTarget.style.background = "var(--uix-bg-hover)";
1435
- },
1436
- onMouseLeave: (e) => {
1437
- e.currentTarget.style.background = "transparent";
1438
- },
1420
+ className: "uix-favourites__menu-item",
1439
1421
  children: action.label
1440
1422
  }
1441
1423
  ) }, action.label))
@@ -1446,7 +1428,7 @@ function FavouriteRow({
1446
1428
  }
1447
1429
  )
1448
1430
  );
1449
- }
1431
+ });
1450
1432
 
1451
1433
  // src/components/Alert.tsx
1452
1434
  var import_jsx_runtime22 = require("react/jsx-runtime");
@@ -1502,10 +1484,11 @@ function Toaster({ children, className, ...props }) {
1502
1484
  }
1503
1485
 
1504
1486
  // src/components/Table.tsx
1487
+ var import_react18 = require("react");
1505
1488
  var import_jsx_runtime25 = require("react/jsx-runtime");
1506
- function TableWrap({ children, className, ...props }) {
1507
- return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("div", { className: cx("uix-table-wrap", className), ...props, children });
1508
- }
1489
+ var TableWrap = (0, import_react18.forwardRef)(function TableWrap2({ children, className, ...props }, ref) {
1490
+ return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("div", { ref, className: cx("uix-table-wrap", className), ...props, children });
1491
+ });
1509
1492
  function Table({
1510
1493
  density = "standard",
1511
1494
  zebra = true,
@@ -1735,13 +1718,13 @@ function Stat({ label, value, icon, meta, trend, className, ...props }) {
1735
1718
  }
1736
1719
 
1737
1720
  // src/components/Tooltip.tsx
1738
- var import_react17 = require("react");
1721
+ var import_react19 = require("react");
1739
1722
  var import_jsx_runtime31 = require("react/jsx-runtime");
1740
1723
  function Tooltip({ label, placement = "top", className, children, ...props }) {
1741
- const triggerRef = (0, import_react17.useRef)(null);
1742
- const bubbleRef = (0, import_react17.useRef)(null);
1743
- const [open, setOpen] = (0, import_react17.useState)(false);
1744
- const id = (0, import_react17.useId)();
1724
+ const triggerRef = (0, import_react19.useRef)(null);
1725
+ const bubbleRef = (0, import_react19.useRef)(null);
1726
+ const [open, setOpen] = (0, import_react19.useState)(false);
1727
+ const id = (0, import_react19.useId)();
1745
1728
  useAnchoredPosition(triggerRef, bubbleRef, { open, placement, offset: 6 });
1746
1729
  const show = () => {
1747
1730
  const b = bubbleRef.current;
@@ -1763,7 +1746,7 @@ function Tooltip({ label, placement = "top", className, children, ...props }) {
1763
1746
  }
1764
1747
  setOpen(false);
1765
1748
  };
1766
- (0, import_react17.useEffect)(() => () => {
1749
+ (0, import_react19.useEffect)(() => () => {
1767
1750
  const b = bubbleRef.current;
1768
1751
  if (b && b.matches(":popover-open")) {
1769
1752
  try {
@@ -1894,11 +1877,12 @@ function ComposerBar({ children, className, ...props }) {
1894
1877
  }
1895
1878
 
1896
1879
  // src/components/Segmented.tsx
1897
- var import_react18 = require("react");
1880
+ var import_react20 = require("react");
1898
1881
  var import_jsx_runtime37 = require("react/jsx-runtime");
1899
- var SegmentedCtx = (0, import_react18.createContext)({});
1882
+ var SegmentedCtx = (0, import_react20.createContext)({});
1900
1883
  function Segmented({ value, onChange, children, className, ...props }) {
1901
- 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 }) });
1884
+ const contextValue = (0, import_react20.useMemo)(() => ({ value, onChange }), [value, onChange]);
1885
+ return /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(SegmentedCtx.Provider, { value: contextValue, children: /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("div", { role: "group", className: cx("uix-segmented", className), ...props, children }) });
1902
1886
  }
1903
1887
  function SegmentedOption({
1904
1888
  value,
@@ -1907,7 +1891,7 @@ function SegmentedOption({
1907
1891
  onClick,
1908
1892
  ...props
1909
1893
  }) {
1910
- const ctx = (0, import_react18.useContext)(SegmentedCtx);
1894
+ const ctx = (0, import_react20.useContext)(SegmentedCtx);
1911
1895
  const selected = ctx.value === value;
1912
1896
  return /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
1913
1897
  "button",
@@ -1956,7 +1940,7 @@ function Note({ tone, icon, children, className, ...props }) {
1956
1940
  }
1957
1941
 
1958
1942
  // src/components/Popover.tsx
1959
- var import_react19 = require("react");
1943
+ var import_react21 = require("react");
1960
1944
  var import_jsx_runtime40 = require("react/jsx-runtime");
1961
1945
  function Popover({
1962
1946
  popover = "auto",
@@ -1967,15 +1951,15 @@ function Popover({
1967
1951
  children,
1968
1952
  ...props
1969
1953
  }) {
1970
- const ref = (0, import_react19.useRef)(null);
1971
- const [open, setOpen] = (0, import_react19.useState)(false);
1954
+ const ref = (0, import_react21.useRef)(null);
1955
+ const [open, setOpen] = (0, import_react21.useState)(false);
1972
1956
  const popoverAttr = { popover };
1973
1957
  const reposition = useAnchoredPosition(anchor ?? null, ref, {
1974
1958
  open: open && !!anchor,
1975
1959
  placement,
1976
1960
  offset
1977
1961
  });
1978
- (0, import_react19.useEffect)(() => {
1962
+ (0, import_react21.useEffect)(() => {
1979
1963
  const el = ref.current;
1980
1964
  if (!el || !anchor) return;
1981
1965
  const sync = () => {
@@ -2073,7 +2057,7 @@ function KanbanCard({ title, meta, children, className, ...props }) {
2073
2057
  }
2074
2058
 
2075
2059
  // src/components/Tree.tsx
2076
- var import_react20 = require("react");
2060
+ var import_react22 = require("react");
2077
2061
 
2078
2062
  // src/tree-model.ts
2079
2063
  function flattenTree(nodes, expanded, level = 1) {
@@ -2185,12 +2169,12 @@ function Tree({
2185
2169
  className,
2186
2170
  ...props
2187
2171
  }) {
2188
- const [internalExpanded, setInternalExpanded] = (0, import_react20.useState)(defaultExpanded ?? /* @__PURE__ */ new Set());
2172
+ const [internalExpanded, setInternalExpanded] = (0, import_react22.useState)(defaultExpanded ?? /* @__PURE__ */ new Set());
2189
2173
  const expanded = controlledExpanded ?? internalExpanded;
2190
- const rootRef = (0, import_react20.useRef)(null);
2191
- const [focusedId, setFocusedId] = (0, import_react20.useState)(void 0);
2174
+ const rootRef = (0, import_react22.useRef)(null);
2175
+ const [focusedId, setFocusedId] = (0, import_react22.useState)(void 0);
2192
2176
  const selectable = selected !== void 0 || onSelect != null;
2193
- const handleToggle = (0, import_react20.useCallback)((id) => {
2177
+ const handleToggle = (0, import_react22.useCallback)((id) => {
2194
2178
  if (!controlledExpanded) {
2195
2179
  setInternalExpanded((prev) => {
2196
2180
  const next = new Set(prev);
@@ -2201,7 +2185,7 @@ function Tree({
2201
2185
  }
2202
2186
  onToggle?.(id);
2203
2187
  }, [controlledExpanded, onToggle]);
2204
- const flat = (0, import_react20.useMemo)(() => flattenTree(nodes, expanded), [nodes, expanded]);
2188
+ const flat = (0, import_react22.useMemo)(() => flattenTree(nodes, expanded), [nodes, expanded]);
2205
2189
  const useVirtual = virtualize ?? shouldVirtualize(flat.length);
2206
2190
  const tabbableId = focusedId ?? selected ?? nodes[0]?.id;
2207
2191
  if (useVirtual) {
@@ -2305,12 +2289,12 @@ function VirtualTreeView({
2305
2289
  className,
2306
2290
  ...props
2307
2291
  }) {
2308
- const scrollRef = (0, import_react20.useRef)(null);
2309
- const [scrollTop, setScrollTop] = (0, import_react20.useState)(0);
2310
- const pendingFocus = (0, import_react20.useRef)(false);
2292
+ const scrollRef = (0, import_react22.useRef)(null);
2293
+ const [scrollTop, setScrollTop] = (0, import_react22.useState)(0);
2294
+ const pendingFocus = (0, import_react22.useRef)(false);
2311
2295
  const win = virtualWindow(scrollTop, maxHeight, rowHeight, flat.length);
2312
2296
  const rows = flat.slice(win.start, win.end);
2313
- (0, import_react20.useEffect)(() => {
2297
+ (0, import_react22.useEffect)(() => {
2314
2298
  if (!pendingFocus.current || focusedId == null) return;
2315
2299
  const el = scrollRef.current?.querySelector(`[role="treeitem"][data-id="${CSS.escape(focusedId)}"]`);
2316
2300
  if (el) {
@@ -2687,5 +2671,6 @@ function StatusRow({ indicator, name, meta, className, ...props }) {
2687
2671
  useAnchoredPosition,
2688
2672
  useDialog,
2689
2673
  useTable,
2674
+ useVirtualRows,
2690
2675
  virtualWindow
2691
2676
  });