@proteos/ui 0.46.0 → 0.47.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
@@ -1265,7 +1265,14 @@ function Checkbox({ children, className, ...props }) {
1265
1265
  {
1266
1266
  ...props,
1267
1267
  className: (renderProps) => chunkMWAXK66H_cjs.cn(
1268
- "group inline-flex items-center gap-2.5 cursor-pointer",
1268
+ // `relative` so RAC's visually-hidden <input> — which is absolutely
1269
+ // positioned and is the element that actually receives focus — is
1270
+ // laid out against THIS checkbox. Without a positioned ancestor here
1271
+ // it resolves against a far-away container, ends up outside any
1272
+ // scroll clipping, and focusing it makes the browser scroll a
1273
+ // container the user cannot scroll back (the roles permission
1274
+ // matrix, where toggling a row threw the page off-screen).
1275
+ "group relative inline-flex items-center gap-2.5 cursor-pointer",
1269
1276
  "font-sans text-[14px] text-ink-2 select-none",
1270
1277
  "data-[disabled]:opacity-45 data-[disabled]:cursor-not-allowed",
1271
1278
  typeof className === "function" ? className(renderProps) : className
@@ -2614,6 +2621,7 @@ function useInfiniteScrollSentinel({
2614
2621
  return ref;
2615
2622
  }
2616
2623
  var DEFAULT_ROW_HEIGHT = 32;
2624
+ var SELECTION_COLUMN_ID = "__select__";
2617
2625
  function DataTable({
2618
2626
  query,
2619
2627
  columns,
@@ -2638,6 +2646,9 @@ function DataTable({
2638
2646
  extraRows,
2639
2647
  bodyBefore,
2640
2648
  bodyAfter,
2649
+ selectionMode = "none",
2650
+ selectedIds,
2651
+ onSelectionChange,
2641
2652
  columnBorders = false
2642
2653
  }) {
2643
2654
  const rows = react.useMemo(() => {
@@ -2648,19 +2659,112 @@ function DataTable({
2648
2659
  if (startExtras.length === 0 && endExtras.length === 0) return dataRows;
2649
2660
  return [...startExtras, ...dataRows, ...endExtras];
2650
2661
  }, [query.data, extraRows]);
2662
+ const resolveRowId = react.useMemo(
2663
+ () => getRowId ?? ((row, index) => {
2664
+ const r = row;
2665
+ if (typeof r.id === "string" || typeof r.id === "number") return String(r.id);
2666
+ if (typeof r.slug === "string") return r.slug;
2667
+ return String(index);
2668
+ }),
2669
+ [getRowId]
2670
+ );
2671
+ const selectionEnabled = selectionMode === "multiple";
2672
+ const selected = react.useMemo(() => selectedIds ?? /* @__PURE__ */ new Set(), [selectedIds]);
2673
+ const loadedIds = react.useMemo(
2674
+ () => selectionEnabled ? rows.map((row, index) => resolveRowId(row, index)) : [],
2675
+ [selectionEnabled, rows, resolveRowId]
2676
+ );
2677
+ const lastToggledIndexRef = react.useRef(null);
2678
+ const toggleRow = (id, index, isRange) => {
2679
+ if (!onSelectionChange) return;
2680
+ const next = new Set(selected);
2681
+ const anchor = lastToggledIndexRef.current;
2682
+ if (isRange && anchor !== null) {
2683
+ const [from, to] = anchor <= index ? [anchor, index] : [index, anchor];
2684
+ for (let i = from; i <= to; i += 1) {
2685
+ const rangeId = loadedIds[i];
2686
+ if (rangeId !== void 0) next.add(rangeId);
2687
+ }
2688
+ } else if (next.has(id)) {
2689
+ next.delete(id);
2690
+ } else {
2691
+ next.add(id);
2692
+ }
2693
+ lastToggledIndexRef.current = index;
2694
+ onSelectionChange(next);
2695
+ };
2696
+ const loadedSelectedCount = loadedIds.reduce((count, id) => count + (selected.has(id) ? 1 : 0), 0);
2697
+ const allLoadedSelected = loadedIds.length > 0 && loadedSelectedCount === loadedIds.length;
2698
+ const toggleAllLoaded = () => {
2699
+ if (!onSelectionChange) return;
2700
+ const next = new Set(selected);
2701
+ if (allLoadedSelected) {
2702
+ for (const id of loadedIds) next.delete(id);
2703
+ } else {
2704
+ for (const id of loadedIds) next.add(id);
2705
+ }
2706
+ lastToggledIndexRef.current = null;
2707
+ onSelectionChange(next);
2708
+ };
2709
+ const rangeModifierRef = react.useRef(false);
2710
+ const selectionColumn = react.useMemo(
2711
+ () => ({
2712
+ id: SELECTION_COLUMN_ID,
2713
+ size: 36,
2714
+ enableSorting: false,
2715
+ meta: { align: "center", sticky: "left", cellClassName: "px-0 justify-center" },
2716
+ header: () => /* @__PURE__ */ jsxRuntime.jsx("span", { className: "inline-flex items-center justify-center w-full", children: /* @__PURE__ */ jsxRuntime.jsx(
2717
+ Checkbox,
2718
+ {
2719
+ "aria-label": allLoadedSelected ? "Deselect all rows" : "Select all rows",
2720
+ isSelected: allLoadedSelected,
2721
+ isIndeterminate: loadedSelectedCount > 0 && !allLoadedSelected,
2722
+ isDisabled: loadedIds.length === 0,
2723
+ onChange: toggleAllLoaded
2724
+ }
2725
+ ) }),
2726
+ cell: ({ row }) => (
2727
+ // Stop the click here: selecting a row must never also open it.
2728
+ /* @__PURE__ */ jsxRuntime.jsx(
2729
+ "span",
2730
+ {
2731
+ className: "inline-flex items-center justify-center w-full",
2732
+ onClick: (event) => event.stopPropagation(),
2733
+ onKeyDown: (event) => {
2734
+ event.stopPropagation();
2735
+ rangeModifierRef.current = event.shiftKey;
2736
+ },
2737
+ onPointerDownCapture: (event) => {
2738
+ rangeModifierRef.current = event.shiftKey;
2739
+ },
2740
+ role: "presentation",
2741
+ children: /* @__PURE__ */ jsxRuntime.jsx(
2742
+ Checkbox,
2743
+ {
2744
+ "aria-label": `Select row ${row.index + 1}`,
2745
+ isSelected: selected.has(row.id),
2746
+ onChange: () => toggleRow(row.id, row.index, rangeModifierRef.current)
2747
+ }
2748
+ )
2749
+ }
2750
+ )
2751
+ )
2752
+ }),
2753
+ // biome-ignore lint/correctness/useExhaustiveDependencies: handlers read the latest selection via closure recreated on these inputs
2754
+ [selected, loadedIds, allLoadedSelected, loadedSelectedCount]
2755
+ );
2756
+ const tableColumns = react.useMemo(
2757
+ () => selectionEnabled ? [selectionColumn, ...columns] : columns,
2758
+ [selectionEnabled, selectionColumn, columns]
2759
+ );
2651
2760
  const table = reactTable.useReactTable({
2652
2761
  data: rows,
2653
- columns,
2762
+ columns: tableColumns,
2654
2763
  state: { sorting: sorting ?? [] },
2655
2764
  ...onSortingChange ? { onSortingChange } : {},
2656
2765
  manualSorting: true,
2657
2766
  getCoreRowModel: reactTable.getCoreRowModel(),
2658
- getRowId: getRowId ? (row, index) => getRowId(row, index) : (row, index) => {
2659
- const r = row;
2660
- if (typeof r.id === "string" || typeof r.id === "number") return String(r.id);
2661
- if (typeof r.slug === "string") return r.slug;
2662
- return String(index);
2663
- }
2767
+ getRowId: (row, index) => resolveRowId(row, index)
2664
2768
  });
2665
2769
  const tableRows = table.getRowModel().rows;
2666
2770
  const tableRowCount = tableRows.length;
@@ -2692,8 +2796,8 @@ function DataTable({
2692
2796
  enabled: !!query.hasNextPage
2693
2797
  });
2694
2798
  const totalColumnWidth = react.useMemo(
2695
- () => columns.reduce((sum, c) => sum + (c.size ?? 140), 0),
2696
- [columns]
2799
+ () => tableColumns.reduce((sum, c) => sum + (c.size ?? 140), 0),
2800
+ [tableColumns]
2697
2801
  );
2698
2802
  const columnsRef = react.useRef(columns);
2699
2803
  columnsRef.current = columns;
@@ -7714,6 +7818,7 @@ exports.RecordFilterChips = RecordFilterChips;
7714
7818
  exports.RecordFilterEditor = RecordFilterEditor;
7715
7819
  exports.RenderChipItemProvider = RenderChipItemProvider;
7716
7820
  exports.RenderValueInputProvider = RenderValueInputProvider;
7821
+ exports.SELECTION_COLUMN_ID = SELECTION_COLUMN_ID;
7717
7822
  exports.SegmentedControl = SegmentedControl;
7718
7823
  exports.Select = Select;
7719
7824
  exports.Separator = Separator;