@pretable/react 0.0.1 → 0.0.2

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
@@ -2,6 +2,7 @@
2
2
 
3
3
  var react = require('react');
4
4
  var core = require('@pretable/core');
5
+ var ui = require('@pretable/ui');
5
6
  var jsxRuntime = require('react/jsx-runtime');
6
7
 
7
8
  // src/pretable-surface.tsx
@@ -485,23 +486,12 @@ function getEstimatedRowHeightSignature(row, columns) {
485
486
  }).join("|");
486
487
  }
487
488
  function readCellValue(row, column) {
488
- return column.getValue ? column.getValue(row) : row[column.id];
489
+ return column.value ? column.value(row) : row[column.id];
489
490
  }
490
491
  function getColumnWidth(column) {
491
492
  return column.widthPx ?? (column.wrap ? WRAPPED_COLUMN_WIDTH : FIXED_COLUMN_WIDTH);
492
493
  }
493
494
  function usePretable({
494
- autosize,
495
- columns,
496
- rows,
497
- getRowId
498
- }) {
499
- return react.useMemo(
500
- () => core.createGrid({ columns, rows, getRowId, autosize }),
501
- [autosize, columns, getRowId, rows]
502
- );
503
- }
504
- function usePretableModel({
505
495
  autosize,
506
496
  columns,
507
497
  rows,
@@ -509,25 +499,79 @@ function usePretableModel({
509
499
  viewportHeight,
510
500
  viewportWidth,
511
501
  overscan = 6,
512
- interactionOverrides,
513
- measuredHeights
502
+ state,
503
+ measuredHeights,
504
+ onSelectionChange,
505
+ onFocusChange
514
506
  }) {
515
- const grid = usePretable({ autosize, columns, rows, getRowId });
516
- if (interactionOverrides) {
517
- grid.setSort(
518
- interactionOverrides.sort?.columnId ?? null,
519
- interactionOverrides.sort?.direction ?? null
520
- );
521
- grid.replaceFilters(interactionOverrides.filters ?? {});
522
- if (interactionOverrides.focusedRowId !== void 0) {
523
- const firstColumnId = columns[0]?.id ?? null;
524
- grid.setFocus(
525
- interactionOverrides.focusedRowId,
526
- interactionOverrides.focusedRowId ? firstColumnId : null
527
- );
507
+ const grid = react.useMemo(
508
+ () => core.createGrid({ columns, rows, getRowId, autosize }),
509
+ [autosize, columns, getRowId, rows]
510
+ );
511
+ const lastColumnIdsRef = react.useRef(null);
512
+ react.useLayoutEffect(() => {
513
+ const currentIds = columns.map((c) => c.id);
514
+ const prevIds = lastColumnIdsRef.current;
515
+ if (prevIds === null || prevIds.length !== currentIds.length || prevIds.some((id, i) => id !== currentIds[i])) {
516
+ if (prevIds !== null) {
517
+ grid.mergeColumnsFromProps(columns);
518
+ }
519
+ lastColumnIdsRef.current = currentIds;
528
520
  }
529
- if (interactionOverrides.selectedRowId !== void 0) {
530
- grid.selectRow(interactionOverrides.selectedRowId);
521
+ }, [columns, grid]);
522
+ if (state) {
523
+ if (state.sort !== void 0) {
524
+ grid.setSort(state.sort?.columnId ?? null, state.sort?.direction ?? null);
525
+ }
526
+ if (state.filters !== void 0) {
527
+ grid.replaceFilters(state.filters);
528
+ }
529
+ if (state.columnWidths !== void 0) {
530
+ const widths = state.columnWidths;
531
+ for (const column of grid.options.columns) {
532
+ const next = widths[column.id];
533
+ if (next !== void 0 && next !== column.widthPx) {
534
+ grid.setColumnWidth(column.id, next);
535
+ }
536
+ }
537
+ }
538
+ if (state.columnOrder !== void 0) {
539
+ const targetOrder = state.columnOrder;
540
+ const currentIds = grid.options.columns.map((c) => c.id);
541
+ const targetIds = [
542
+ ...targetOrder.filter((id) => currentIds.includes(id)),
543
+ ...currentIds.filter((id) => !targetOrder.includes(id))
544
+ ];
545
+ for (let i = 0; i < targetIds.length; i += 1) {
546
+ const id = targetIds[i];
547
+ const currentIdx = grid.options.columns.findIndex((c) => c.id === id);
548
+ if (currentIdx !== i && id !== "__pretable_row_select__") {
549
+ grid.moveColumn(id, i);
550
+ }
551
+ }
552
+ }
553
+ if (state.columnPinned !== void 0) {
554
+ const pinned = state.columnPinned;
555
+ for (const [id, value] of Object.entries(pinned)) {
556
+ const column = grid.options.columns.find((c) => c.id === id);
557
+ if (!column) continue;
558
+ const targetPinned = value === "left" ? "left" : null;
559
+ const currentPinned = column.pinned ?? null;
560
+ if (currentPinned !== targetPinned) {
561
+ grid.setColumnPinned(id, targetPinned);
562
+ }
563
+ }
564
+ }
565
+ if (state.selection !== void 0) {
566
+ grid.setSelection(state.selection);
567
+ }
568
+ if (state.focus !== void 0) {
569
+ const focus = state.focus;
570
+ if (focus.rowId !== null && focus.columnId !== null) {
571
+ grid.setFocus({ rowId: focus.rowId, columnId: focus.columnId });
572
+ } else {
573
+ grid.setFocus(null);
574
+ }
531
575
  }
532
576
  }
533
577
  const snapshot = react.useSyncExternalStore(
@@ -586,7 +630,7 @@ function usePretableModel({
586
630
  focusedRowId: snapshot.focus.rowId,
587
631
  rowModelRowCount: snapshot.visibleRows.length,
588
632
  renderedRowCount: renderSnapshot.rows.length,
589
- selectedRowId: snapshot.selection.rowIds[0] ?? null,
633
+ selectedRowId: snapshot.selection.ranges[0]?.startRowId ?? null,
590
634
  totalRowCount: snapshot.totalRowCount,
591
635
  totalHeight: renderSnapshot.totalHeight,
592
636
  visibleRowCount: viewportRows.length,
@@ -603,7 +647,7 @@ function usePretableModel({
603
647
  renderSnapshot.totalHeight,
604
648
  snapshot.focus.rowId,
605
649
  snapshot.visibleRows.length,
606
- snapshot.selection.rowIds,
650
+ snapshot.selection.ranges,
607
651
  snapshot.totalRowCount,
608
652
  snapshot.viewport.height,
609
653
  snapshot.viewport.scrollTop,
@@ -616,6 +660,45 @@ function usePretableModel({
616
660
  telemetry
617
661
  };
618
662
  }
663
+ function subscribe(callback) {
664
+ if (typeof document === "undefined") return () => {
665
+ };
666
+ const observer = new MutationObserver(callback);
667
+ observer.observe(document.documentElement, {
668
+ attributes: true,
669
+ attributeFilter: ["data-density", "data-theme", "class", "style"]
670
+ });
671
+ return () => observer.disconnect();
672
+ }
673
+ function useResolvedHeights(rowHeightProp, headerHeightProp) {
674
+ const cachedClient = react.useRef(null);
675
+ const cachedServer = react.useRef(null);
676
+ const getSnapshot = react.useCallback(() => {
677
+ const css = ui.getDensityHeights();
678
+ const rowHeight = rowHeightProp ?? css.rowHeight;
679
+ const headerHeight = headerHeightProp ?? css.headerHeight;
680
+ const prev = cachedClient.current;
681
+ if (prev !== null && prev.rowHeight === rowHeight && prev.headerHeight === headerHeight) {
682
+ return prev;
683
+ }
684
+ const next = { rowHeight, headerHeight };
685
+ cachedClient.current = next;
686
+ return next;
687
+ }, [rowHeightProp, headerHeightProp]);
688
+ const getServerSnapshot = react.useCallback(() => {
689
+ const css = ui.getDensityHeights();
690
+ const rowHeight = rowHeightProp ?? css.rowHeight;
691
+ const headerHeight = headerHeightProp ?? css.headerHeight;
692
+ const prev = cachedServer.current;
693
+ if (prev !== null && prev.rowHeight === rowHeight && prev.headerHeight === headerHeight) {
694
+ return prev;
695
+ }
696
+ const next = { rowHeight, headerHeight };
697
+ cachedServer.current = next;
698
+ return next;
699
+ }, [rowHeightProp, headerHeightProp]);
700
+ return react.useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
701
+ }
619
702
 
620
703
  // src/rendering.ts
621
704
  var DEFAULT_ROW_HEIGHT2 = 44;
@@ -647,7 +730,7 @@ function getNextSortDirection(current) {
647
730
  return null;
648
731
  }
649
732
  function resolveCellValue(row, column) {
650
- return column.getValue ? column.getValue(row) : row[column.id];
733
+ return column.value ? column.value(row) : row[column.id];
651
734
  }
652
735
  function formatCellValue(value) {
653
736
  if (Array.isArray(value)) {
@@ -656,69 +739,6 @@ function formatCellValue(value) {
656
739
  return String(value ?? "");
657
740
  }
658
741
 
659
- // src/density.ts
660
- var FALLBACK_ROW_HEIGHT = 32;
661
- var FALLBACK_HEADER_HEIGHT = HEADER_HEIGHT;
662
- function parsePx(value) {
663
- const match = value.trim().match(/^([\d.]+)px$/);
664
- return match ? parseFloat(match[1]) : null;
665
- }
666
- function getDensityHeights() {
667
- if (typeof document === "undefined") {
668
- return {
669
- rowHeight: FALLBACK_ROW_HEIGHT,
670
- headerHeight: FALLBACK_HEADER_HEIGHT
671
- };
672
- }
673
- const styles = getComputedStyle(document.documentElement);
674
- const read = (name) => {
675
- if (typeof styles?.getPropertyValue !== "function") return "";
676
- return styles.getPropertyValue(name);
677
- };
678
- return {
679
- rowHeight: parsePx(read("--pretable-row-height")) ?? FALLBACK_ROW_HEIGHT,
680
- headerHeight: parsePx(read("--pretable-header-height")) ?? FALLBACK_HEADER_HEIGHT
681
- };
682
- }
683
- function subscribe(callback) {
684
- if (typeof document === "undefined") return () => {
685
- };
686
- const observer = new MutationObserver(callback);
687
- observer.observe(document.documentElement, {
688
- attributes: true,
689
- attributeFilter: ["data-density", "data-theme", "class", "style"]
690
- });
691
- return () => observer.disconnect();
692
- }
693
- function useResolvedHeights(rowHeightProp, headerHeightProp) {
694
- const cachedClient = react.useRef(null);
695
- const cachedServer = react.useRef(null);
696
- const getSnapshot = react.useCallback(() => {
697
- const css = getDensityHeights();
698
- const rowHeight = rowHeightProp ?? css.rowHeight;
699
- const headerHeight = headerHeightProp ?? css.headerHeight;
700
- const prev = cachedClient.current;
701
- if (prev !== null && prev.rowHeight === rowHeight && prev.headerHeight === headerHeight) {
702
- return prev;
703
- }
704
- const next = { rowHeight, headerHeight };
705
- cachedClient.current = next;
706
- return next;
707
- }, [rowHeightProp, headerHeightProp]);
708
- const getServerSnapshot = react.useCallback(() => {
709
- const rowHeight = rowHeightProp ?? FALLBACK_ROW_HEIGHT;
710
- const headerHeight = headerHeightProp ?? FALLBACK_HEADER_HEIGHT;
711
- const prev = cachedServer.current;
712
- if (prev !== null && prev.rowHeight === rowHeight && prev.headerHeight === headerHeight) {
713
- return prev;
714
- }
715
- const next = { rowHeight, headerHeight };
716
- cachedServer.current = next;
717
- return next;
718
- }, [rowHeightProp, headerHeightProp]);
719
- return react.useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
720
- }
721
-
722
742
  // src/styles.ts
723
743
  function getViewportStyle(height) {
724
744
  return {
@@ -788,6 +808,162 @@ function getPinnedCellStyle(left) {
788
808
  zIndex: 1
789
809
  };
790
810
  }
811
+
812
+ // src/constants.ts
813
+ var ROW_SELECT_COLUMN_ID = "__pretable_row_select__";
814
+
815
+ // src/copy.ts
816
+ function defaultCoerceForCopy(value) {
817
+ if (value === null || value === void 0) return "";
818
+ if (value instanceof Date) return value.toISOString();
819
+ const t = typeof value;
820
+ if (t === "string" || t === "number" || t === "boolean" || t === "bigint") {
821
+ return String(value);
822
+ }
823
+ if (t === "object") {
824
+ try {
825
+ return JSON.stringify(value);
826
+ } catch {
827
+ return String(value);
828
+ }
829
+ }
830
+ return String(value);
831
+ }
832
+ function serializeRangesAsTsv(args) {
833
+ const dataColumns = args.columns.filter((c) => c.id !== ROW_SELECT_COLUMN_ID);
834
+ if (dataColumns.length === 0) return null;
835
+ const colIndex = /* @__PURE__ */ new Map();
836
+ dataColumns.forEach((c, i) => colIndex.set(c.id, i));
837
+ const rowIndex = /* @__PURE__ */ new Map();
838
+ args.visibleRows.forEach((r, i) => rowIndex.set(r.id, i));
839
+ const blocks = [];
840
+ for (const range of args.ranges) {
841
+ const startRow = rowIndex.get(range.startRowId);
842
+ const endRow = rowIndex.get(range.endRowId);
843
+ const startIsSynth = range.startColumnId === ROW_SELECT_COLUMN_ID;
844
+ const endIsSynth = range.endColumnId === ROW_SELECT_COLUMN_ID;
845
+ const startCol = colIndex.get(range.startColumnId);
846
+ const endCol = colIndex.get(range.endColumnId);
847
+ const haveRows = startRow !== void 0 && endRow !== void 0;
848
+ const rowLo = haveRows ? Math.min(startRow, endRow) : -1;
849
+ const rowHi = haveRows ? Math.max(startRow, endRow) : -1;
850
+ let colLo;
851
+ let colHi;
852
+ if (startIsSynth && endIsSynth) {
853
+ continue;
854
+ } else if (startIsSynth && endCol !== void 0) {
855
+ colLo = 0;
856
+ colHi = endCol;
857
+ } else if (endIsSynth && startCol !== void 0) {
858
+ colLo = startCol;
859
+ colHi = 0;
860
+ } else if (startCol !== void 0 && endCol !== void 0) {
861
+ colLo = Math.min(startCol, endCol);
862
+ colHi = Math.max(startCol, endCol);
863
+ } else if (startCol !== void 0) {
864
+ colLo = colHi = startCol;
865
+ } else if (endCol !== void 0) {
866
+ colLo = colHi = endCol;
867
+ } else {
868
+ continue;
869
+ }
870
+ if (colLo > colHi) {
871
+ [colLo, colHi] = [colHi, colLo];
872
+ }
873
+ colLo = Math.max(colLo, 0);
874
+ colHi = Math.min(colHi, dataColumns.length - 1);
875
+ if (colLo > colHi) continue;
876
+ if (!haveRows || rowLo > rowHi) continue;
877
+ const lines = [];
878
+ if (args.copyWithHeaders) {
879
+ const headerCells = [];
880
+ for (let c = colLo; c <= colHi; c += 1) {
881
+ const col = dataColumns[c];
882
+ headerCells.push(col.header ?? col.id);
883
+ }
884
+ lines.push(headerCells.join(" "));
885
+ lines.push("");
886
+ }
887
+ for (let r = rowLo; r <= rowHi; r += 1) {
888
+ const row = args.visibleRows[r];
889
+ const cells = [];
890
+ for (let c = colLo; c <= colHi; c += 1) {
891
+ const col = dataColumns[c];
892
+ const raw = col.value ? col.value(row.row) : row.row[col.id];
893
+ const text = col.format ? col.format({ value: raw, row: row.row, column: col }) : defaultCoerceForCopy(raw);
894
+ cells.push(text);
895
+ }
896
+ lines.push(cells.join(" "));
897
+ }
898
+ blocks.push(lines.join("\n"));
899
+ }
900
+ if (blocks.length === 0) return null;
901
+ return { text: blocks.join("\n\n") };
902
+ }
903
+ async function defaultCopyToClipboard(payload) {
904
+ if (typeof navigator === "undefined" || !navigator.clipboard) return;
905
+ if (payload.html && typeof globalThis.ClipboardItem !== "undefined" && typeof navigator.clipboard.write === "function") {
906
+ await navigator.clipboard.write([
907
+ new globalThis.ClipboardItem({
908
+ "text/plain": new Blob([payload.text], { type: "text/plain" }),
909
+ "text/html": new Blob([payload.html], { type: "text/html" })
910
+ })
911
+ ]);
912
+ } else {
913
+ await navigator.clipboard.writeText(payload.text);
914
+ }
915
+ }
916
+ var defaultMessages = {
917
+ selectAllAnnouncement: ({ rowCount, columnCount, isAll }) => isAll ? "All rows selected" : `${rowCount} rows \xD7 ${columnCount} columns selected`,
918
+ copyAnnouncement: ({ rowCount, columnCount }) => `${rowCount} rows \xD7 ${columnCount} columns copied`,
919
+ copyFailedAnnouncement: () => "Copy failed"
920
+ };
921
+ var ANNOUNCE_DEBOUNCE_MS = 500;
922
+ var REORDER_THRESHOLD_PX = 5;
923
+ function CellContentImpl({
924
+ formattedValue,
925
+ renderRef,
926
+ fallbackRenderRef,
927
+ cellRenderInput
928
+ }) {
929
+ if (renderRef) {
930
+ return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: renderRef(cellRenderInput) });
931
+ }
932
+ if (fallbackRenderRef) {
933
+ return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: fallbackRenderRef(cellRenderInput) });
934
+ }
935
+ return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: formattedValue });
936
+ }
937
+ function cellContentPropsEqual(prev, next) {
938
+ return prev.rowId === next.rowId && prev.columnId === next.columnId && prev.value === next.value && prev.formattedValue === next.formattedValue && prev.isFocused === next.isFocused && prev.isSelected === next.isSelected && prev.renderRef === next.renderRef && prev.fallbackRenderRef === next.fallbackRenderRef;
939
+ }
940
+ var MemoizedCellContent = react.memo(CellContentImpl, cellContentPropsEqual);
941
+ function HeaderContentImpl({
942
+ label,
943
+ sortDirection,
944
+ renderHeaderRef,
945
+ fallbackRenderHeaderRef,
946
+ headerRenderInput
947
+ }) {
948
+ if (renderHeaderRef) {
949
+ return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: renderHeaderRef(headerRenderInput) });
950
+ }
951
+ if (fallbackRenderHeaderRef) {
952
+ return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: fallbackRenderHeaderRef({
953
+ column: headerRenderInput.column,
954
+ label,
955
+ sortDirection
956
+ }) });
957
+ }
958
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
959
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: label }),
960
+ /* @__PURE__ */ jsxRuntime.jsx("strong", { children: sortDirection === "desc" ? "Newest" : sortDirection === "asc" ? "Oldest" : "Sort" })
961
+ ] });
962
+ }
963
+ function headerContentPropsEqual(prev, next) {
964
+ return prev.columnId === next.columnId && prev.label === next.label && prev.sortDirection === next.sortDirection && prev.isSorted === next.isSorted && prev.width === next.width && prev.isSortable === next.isSortable && prev.renderHeaderRef === next.renderHeaderRef && prev.fallbackRenderHeaderRef === next.fallbackRenderHeaderRef;
965
+ }
966
+ var MemoizedHeaderContent = react.memo(HeaderContentImpl, headerContentPropsEqual);
791
967
  function PretableSurface({
792
968
  ariaLabel,
793
969
  autosize,
@@ -799,45 +975,292 @@ function PretableSurface({
799
975
  getRowClassName,
800
976
  getRowId,
801
977
  getRowProps,
802
- interactionState,
978
+ state,
803
979
  overscan = 6,
804
980
  onGridReady,
805
981
  onSelectedRowIdChange,
982
+ onSelectionChange,
983
+ onFocusChange,
806
984
  onSortChange,
985
+ onColumnWidthsChange,
986
+ onColumnOrderChange,
987
+ onColumnPinnedChange,
807
988
  onTelemetryChange,
808
989
  renderBodyCell,
809
990
  renderHeaderCell,
810
991
  rows,
992
+ rowSelectionColumn,
811
993
  selectFocusedRowOnArrowKey = false,
994
+ tabBehavior = "wrap-rows",
812
995
  viewportStyle,
813
- viewportHeight
996
+ viewportHeight,
997
+ copyWithHeaders,
998
+ onCopy,
999
+ copyToClipboard,
1000
+ messages
814
1001
  }) {
815
1002
  const [measuredHeights, setMeasuredHeights] = react.useState({});
1003
+ const [dragLiveWidth, setDragLiveWidth] = react.useState(null);
1004
+ const resizeStateRef = react.useRef(null);
1005
+ const wasResizingRef = react.useRef(false);
1006
+ const wasReorderingRef = react.useRef(false);
1007
+ const reorderStateRef = react.useRef(null);
1008
+ const [reorderDrag, setReorderDrag] = react.useState(null);
816
1009
  const [viewportWidth, setViewportWidth] = react.useState(0);
1010
+ const [liveMessage, setLiveMessage] = react.useState("");
1011
+ const announceTimerRef = react.useRef(null);
1012
+ const pendingAnnouncementRef = react.useRef(null);
1013
+ const scheduleAnnouncement = react.useCallback((message) => {
1014
+ pendingAnnouncementRef.current = message;
1015
+ if (announceTimerRef.current !== null) {
1016
+ clearTimeout(announceTimerRef.current);
1017
+ }
1018
+ announceTimerRef.current = setTimeout(() => {
1019
+ if (pendingAnnouncementRef.current !== null) {
1020
+ setLiveMessage(pendingAnnouncementRef.current);
1021
+ pendingAnnouncementRef.current = null;
1022
+ }
1023
+ announceTimerRef.current = null;
1024
+ }, ANNOUNCE_DEBOUNCE_MS);
1025
+ }, []);
1026
+ react.useEffect(() => {
1027
+ return () => {
1028
+ if (announceTimerRef.current !== null) {
1029
+ clearTimeout(announceTimerRef.current);
1030
+ }
1031
+ };
1032
+ }, []);
1033
+ const effectiveMessages = react.useMemo(
1034
+ () => ({
1035
+ selectAllAnnouncement: messages?.selectAllAnnouncement ?? defaultMessages.selectAllAnnouncement,
1036
+ copyAnnouncement: messages?.copyAnnouncement ?? defaultMessages.copyAnnouncement,
1037
+ copyFailedAnnouncement: messages?.copyFailedAnnouncement ?? defaultMessages.copyFailedAnnouncement
1038
+ }),
1039
+ [messages]
1040
+ );
817
1041
  const measuredHeightsRef = react.useRef({});
818
1042
  const measuredRowKeysRef = react.useRef({});
819
1043
  const rowNodesRef = react.useRef(/* @__PURE__ */ new Map());
1044
+ const cellNodesRef = react.useRef(/* @__PURE__ */ new Map());
820
1045
  const viewportRef = react.useRef(null);
1046
+ const dragAnchorRef = react.useRef(null);
1047
+ const dragStartSelectionRef = react.useRef(null);
1048
+ const lastCheckedRowAnchorRef = react.useRef(null);
821
1049
  const { headerHeight } = useResolvedHeights();
822
1050
  const bodyViewportHeight = Math.max(viewportHeight - headerHeight, 0);
823
- const { grid, snapshot, renderSnapshot, telemetry } = usePretableModel({
1051
+ const effectiveColumns = react.useMemo(() => {
1052
+ if (!rowSelectionColumn?.enabled) return columns;
1053
+ const synth = {
1054
+ id: ROW_SELECT_COLUMN_ID,
1055
+ header: "",
1056
+ widthPx: rowSelectionColumn.width ?? 36,
1057
+ sortable: false,
1058
+ filterable: false,
1059
+ ...rowSelectionColumn.pinned ?? true ? { pinned: "left" } : {}
1060
+ };
1061
+ return [synth, ...columns];
1062
+ }, [columns, rowSelectionColumn]);
1063
+ const { grid, snapshot, renderSnapshot, telemetry } = usePretable({
824
1064
  autosize,
825
- columns,
1065
+ columns: effectiveColumns,
826
1066
  getRowId,
827
- interactionOverrides: interactionState ?? void 0,
1067
+ state: state ?? void 0,
828
1068
  measuredHeights,
829
1069
  overscan,
830
1070
  rows,
831
1071
  viewportHeight: bodyViewportHeight,
832
- viewportWidth: viewportWidth || void 0
1072
+ viewportWidth: viewportWidth || void 0,
1073
+ onSelectionChange,
1074
+ onFocusChange
833
1075
  });
834
- const pinnedOffsets = react.useMemo(() => getPinnedLeftOffsets(columns), [columns]);
1076
+ const focusedRowId = snapshot.focus.rowId;
1077
+ const focusedColumnId = snapshot.focus.columnId;
1078
+ const pinnedOffsets = react.useMemo(
1079
+ () => getPinnedLeftOffsets(effectiveColumns),
1080
+ [effectiveColumns]
1081
+ );
1082
+ const columnsById = react.useMemo(() => {
1083
+ const map = /* @__PURE__ */ new Map();
1084
+ for (const col of effectiveColumns) {
1085
+ map.set(col.id, col);
1086
+ }
1087
+ return map;
1088
+ }, [effectiveColumns]);
1089
+ const { columnLefts, columnWidths } = react.useMemo(() => {
1090
+ const engineColumns = grid.options.columns;
1091
+ const lefts = new Array(engineColumns.length).fill(0);
1092
+ const widths = new Array(engineColumns.length).fill(0);
1093
+ for (const planned of renderSnapshot.columns) {
1094
+ lefts[planned.index] = planned.left;
1095
+ widths[planned.index] = planned.width;
1096
+ }
1097
+ let acc = 0;
1098
+ for (let i = 0; i < engineColumns.length; i += 1) {
1099
+ const col = engineColumns[i];
1100
+ if (widths[i] === 0) {
1101
+ widths[i] = col.widthPx ?? 0;
1102
+ lefts[i] = acc;
1103
+ }
1104
+ acc = lefts[i] + widths[i];
1105
+ }
1106
+ return { columnLefts: lefts, columnWidths: widths };
1107
+ }, [renderSnapshot.columns, grid.options.columns]);
1108
+ const visibleRowIndexById = react.useMemo(() => {
1109
+ const map = /* @__PURE__ */ new Map();
1110
+ for (let i = 0; i < snapshot.visibleRows.length; i += 1) {
1111
+ const row = snapshot.visibleRows[i];
1112
+ if (row) {
1113
+ map.set(row.id, i);
1114
+ }
1115
+ }
1116
+ return map;
1117
+ }, [snapshot.visibleRows]);
1118
+ const dataColumnIndex = react.useMemo(() => {
1119
+ const dataColumns = effectiveColumns.filter(
1120
+ (c) => c.id !== ROW_SELECT_COLUMN_ID
1121
+ );
1122
+ const idxById = /* @__PURE__ */ new Map();
1123
+ for (let i = 0; i < dataColumns.length; i += 1) {
1124
+ idxById.set(dataColumns[i].id, i);
1125
+ }
1126
+ return { dataColumns, idxById };
1127
+ }, [effectiveColumns]);
1128
+ const { fullySelectedRowIds, indeterminateRowIds } = react.useMemo(() => {
1129
+ const fullyRows = /* @__PURE__ */ new Set();
1130
+ const indeterminateRows = /* @__PURE__ */ new Set();
1131
+ const ranges = snapshot.selection.ranges;
1132
+ const { dataColumns, idxById: dataColIdxByColId } = dataColumnIndex;
1133
+ if (ranges.length === 0 || dataColumns.length === 0) {
1134
+ return {
1135
+ fullySelectedRowIds: fullyRows,
1136
+ indeterminateRowIds: indeterminateRows
1137
+ };
1138
+ }
1139
+ const visibleRows = snapshot.visibleRows;
1140
+ const colCount = dataColumns.length;
1141
+ if (colCount <= 30) {
1142
+ const rowMask = /* @__PURE__ */ new Map();
1143
+ for (const range of ranges) {
1144
+ const r1 = visibleRowIndexById.get(range.startRowId);
1145
+ const r2 = visibleRowIndexById.get(range.endRowId);
1146
+ if (r1 === void 0 || r2 === void 0) continue;
1147
+ const rowLo = Math.min(r1, r2);
1148
+ const rowHi = Math.max(r1, r2);
1149
+ const startSynth = range.startColumnId === ROW_SELECT_COLUMN_ID;
1150
+ const endSynth = range.endColumnId === ROW_SELECT_COLUMN_ID;
1151
+ let dataColLo;
1152
+ let dataColHi;
1153
+ if (startSynth && endSynth) {
1154
+ continue;
1155
+ }
1156
+ if (startSynth || endSynth) {
1157
+ dataColLo = 0;
1158
+ dataColHi = colCount - 1;
1159
+ } else {
1160
+ const a = dataColIdxByColId.get(range.startColumnId);
1161
+ const b = dataColIdxByColId.get(range.endColumnId);
1162
+ if (a === void 0 || b === void 0) continue;
1163
+ dataColLo = Math.min(a, b);
1164
+ dataColHi = Math.max(a, b);
1165
+ }
1166
+ const spanWidth = dataColHi - dataColLo + 1;
1167
+ const spanMask = (spanWidth >= 30 ? 1073741823 : (1 << spanWidth) - 1) << dataColLo >>> 0;
1168
+ for (let rowIdx = rowLo; rowIdx <= rowHi; rowIdx += 1) {
1169
+ rowMask.set(rowIdx, (rowMask.get(rowIdx) ?? 0) | spanMask);
1170
+ }
1171
+ }
1172
+ const fullMask = colCount >= 30 ? 1073741823 : (1 << colCount) - 1 >>> 0;
1173
+ for (const [rowIdx, mask] of rowMask) {
1174
+ if (mask === 0) continue;
1175
+ const row = visibleRows[rowIdx];
1176
+ if (!row) continue;
1177
+ if (mask === fullMask) fullyRows.add(row.id);
1178
+ else indeterminateRows.add(row.id);
1179
+ }
1180
+ } else {
1181
+ const rowCoverage = /* @__PURE__ */ new Map();
1182
+ for (const range of ranges) {
1183
+ const r1 = visibleRowIndexById.get(range.startRowId);
1184
+ const r2 = visibleRowIndexById.get(range.endRowId);
1185
+ if (r1 === void 0 || r2 === void 0) continue;
1186
+ const rowLo = Math.min(r1, r2);
1187
+ const rowHi = Math.max(r1, r2);
1188
+ const startSynth = range.startColumnId === ROW_SELECT_COLUMN_ID;
1189
+ const endSynth = range.endColumnId === ROW_SELECT_COLUMN_ID;
1190
+ let dataColLo;
1191
+ let dataColHi;
1192
+ if (startSynth && endSynth) continue;
1193
+ if (startSynth || endSynth) {
1194
+ dataColLo = 0;
1195
+ dataColHi = colCount - 1;
1196
+ } else {
1197
+ const a = dataColIdxByColId.get(range.startColumnId);
1198
+ const b = dataColIdxByColId.get(range.endColumnId);
1199
+ if (a === void 0 || b === void 0) continue;
1200
+ dataColLo = Math.min(a, b);
1201
+ dataColHi = Math.max(a, b);
1202
+ }
1203
+ for (let rowIdx = rowLo; rowIdx <= rowHi; rowIdx += 1) {
1204
+ let cov = rowCoverage.get(rowIdx);
1205
+ if (!cov) {
1206
+ cov = /* @__PURE__ */ new Set();
1207
+ rowCoverage.set(rowIdx, cov);
1208
+ }
1209
+ for (let colIdx = dataColLo; colIdx <= dataColHi; colIdx += 1) {
1210
+ cov.add(colIdx);
1211
+ }
1212
+ }
1213
+ }
1214
+ for (const [rowIdx, cov] of rowCoverage) {
1215
+ const row = visibleRows[rowIdx];
1216
+ if (!row) continue;
1217
+ if (cov.size === 0) continue;
1218
+ if (cov.size === colCount) fullyRows.add(row.id);
1219
+ else indeterminateRows.add(row.id);
1220
+ }
1221
+ }
1222
+ return {
1223
+ fullySelectedRowIds: fullyRows,
1224
+ indeterminateRowIds: indeterminateRows
1225
+ };
1226
+ }, [
1227
+ snapshot.selection.ranges,
1228
+ snapshot.visibleRows,
1229
+ dataColumnIndex,
1230
+ visibleRowIndexById
1231
+ ]);
1232
+ const isCellSelected = react.useCallback(
1233
+ (rowId, columnId) => {
1234
+ const ranges = snapshot.selection.ranges;
1235
+ if (ranges.length === 0) return false;
1236
+ const rIdx = visibleRowIndexById.get(rowId);
1237
+ if (rIdx === void 0) return false;
1238
+ const cIdx = dataColumnIndex.idxById.get(columnId);
1239
+ if (cIdx === void 0) return false;
1240
+ for (const range of ranges) {
1241
+ const r1 = visibleRowIndexById.get(range.startRowId);
1242
+ const r2 = visibleRowIndexById.get(range.endRowId);
1243
+ if (r1 === void 0 || r2 === void 0) continue;
1244
+ if (rIdx < Math.min(r1, r2) || rIdx > Math.max(r1, r2)) continue;
1245
+ const startSynth = range.startColumnId === ROW_SELECT_COLUMN_ID;
1246
+ const endSynth = range.endColumnId === ROW_SELECT_COLUMN_ID;
1247
+ if (startSynth && endSynth) continue;
1248
+ if (startSynth || endSynth) return true;
1249
+ const a = dataColumnIndex.idxById.get(range.startColumnId);
1250
+ const b = dataColumnIndex.idxById.get(range.endColumnId);
1251
+ if (a === void 0 || b === void 0) continue;
1252
+ if (cIdx >= Math.min(a, b) && cIdx <= Math.max(a, b)) return true;
1253
+ }
1254
+ return false;
1255
+ },
1256
+ [snapshot.selection.ranges, visibleRowIndexById, dataColumnIndex]
1257
+ );
835
1258
  react.useLayoutEffect(() => {
836
1259
  const el = viewportRef.current;
837
1260
  if (el && viewportWidth === 0) {
838
1261
  setViewportWidth(el.clientWidth);
839
1262
  }
840
- });
1263
+ }, [viewportWidth]);
841
1264
  react.useLayoutEffect(() => {
842
1265
  onTelemetryChange?.(telemetry);
843
1266
  }, [onTelemetryChange, telemetry]);
@@ -845,14 +1268,26 @@ function PretableSurface({
845
1268
  onGridReady?.(grid);
846
1269
  }, [grid, onGridReady]);
847
1270
  react.useLayoutEffect(() => {
848
- if (!interactionState?.selectedRowId) {
1271
+ if (!focusedRowId || !focusedColumnId) {
849
1272
  return;
850
1273
  }
851
- const currentSelectedRowId = snapshot.selection.rowIds[0] ?? null;
852
- if (currentSelectedRowId !== interactionState.selectedRowId) {
853
- onSelectedRowIdChange?.(interactionState.selectedRowId);
1274
+ const cellNode = cellNodesRef.current.get(
1275
+ `${focusedRowId}::${focusedColumnId}`
1276
+ );
1277
+ if (cellNode && document.activeElement !== cellNode) {
1278
+ cellNode.focus({ preventScroll: true });
1279
+ }
1280
+ }, [focusedRowId, focusedColumnId]);
1281
+ react.useLayoutEffect(() => {
1282
+ const injectedSelectedRowId = state?.selection?.ranges[0]?.startRowId ?? null;
1283
+ if (!injectedSelectedRowId) {
1284
+ return;
1285
+ }
1286
+ const currentSelectedRowId = snapshot.selection.ranges[0]?.startRowId ?? null;
1287
+ if (currentSelectedRowId !== injectedSelectedRowId) {
1288
+ onSelectedRowIdChange?.(injectedSelectedRowId);
854
1289
  }
855
- }, [interactionState, onSelectedRowIdChange, snapshot.selection.rowIds]);
1290
+ }, [state, onSelectedRowIdChange, snapshot.selection.ranges]);
856
1291
  react.useLayoutEffect(() => {
857
1292
  let nextHeights = measuredHeightsRef.current;
858
1293
  let nextKeys = measuredRowKeysRef.current;
@@ -893,35 +1328,106 @@ function PretableSurface({
893
1328
  if (changed) {
894
1329
  setMeasuredHeights(nextHeights);
895
1330
  }
896
- }, [snapshot.visibleRows, columns, viewportWidth]);
1331
+ }, [snapshot.visibleRows, effectiveColumns, viewportWidth]);
897
1332
  return /* @__PURE__ */ jsxRuntime.jsxs(
898
1333
  "div",
899
1334
  {
1335
+ "aria-colcount": effectiveColumns.length,
900
1336
  "aria-label": ariaLabel,
1337
+ "aria-multiselectable": "true",
1338
+ "aria-rowcount": snapshot.totalRowCount + 1,
901
1339
  "data-pretable-scroll-viewport": "",
902
1340
  ref: viewportRef,
903
1341
  role: "grid",
904
- tabIndex: 0,
1342
+ tabIndex: -1,
905
1343
  onKeyDown: (event) => {
906
- if (event.key === "ArrowDown" || event.key === "ArrowUp") {
907
- grid.moveFocus(event.key === "ArrowDown" ? 1 : -1);
908
- const nextFocus = grid.getSnapshot().focus;
909
- if (nextFocus.rowId && nextFocus.columnId === null && columns[0]) {
910
- grid.setFocus(nextFocus.rowId, columns[0].id);
911
- }
912
- if (selectFocusedRowOnArrowKey && nextFocus.rowId) {
913
- grid.selectRow(nextFocus.rowId);
914
- onSelectedRowIdChange?.(nextFocus.rowId);
1344
+ if ((event.key === "Escape" || event.key === "Esc") && reorderStateRef.current?.dragging) {
1345
+ reorderStateRef.current = null;
1346
+ setReorderDrag(null);
1347
+ event.preventDefault();
1348
+ return;
1349
+ }
1350
+ if ((event.key === "Escape" || event.key === "Esc") && dragAnchorRef.current !== null && dragStartSelectionRef.current !== null) {
1351
+ const before2 = grid.getSnapshot();
1352
+ grid.setSelection(dragStartSelectionRef.current);
1353
+ dragAnchorRef.current = null;
1354
+ dragStartSelectionRef.current = null;
1355
+ const after = grid.getSnapshot();
1356
+ if (JSON.stringify(before2.selection) !== JSON.stringify(after.selection)) {
1357
+ onSelectionChange?.(after.selection);
915
1358
  }
916
1359
  event.preventDefault();
917
1360
  return;
918
1361
  }
919
- if (event.key === "Enter" || event.key === " " || event.key === "Space") {
920
- const focusedRowId = grid.getSnapshot().focus.rowId;
921
- if (focusedRowId) {
922
- grid.selectRow(focusedRowId);
923
- onSelectedRowIdChange?.(focusedRowId);
924
- event.preventDefault();
1362
+ if ((event.key === "c" || event.key === "C") && (event.metaKey || event.ctrlKey) && !event.shiftKey && !event.altKey && !(event.target instanceof HTMLInputElement) && !(event.target instanceof HTMLTextAreaElement)) {
1363
+ event.preventDefault();
1364
+ const snap = grid.getSnapshot();
1365
+ if (snap.selection.ranges.length === 0) {
1366
+ return;
1367
+ }
1368
+ const args = {
1369
+ ranges: snap.selection.ranges,
1370
+ visibleRows: snap.visibleRows,
1371
+ columns: effectiveColumns,
1372
+ copyWithHeaders: copyWithHeaders ?? false
1373
+ };
1374
+ const payload = onCopy ? onCopy(args) : serializeRangesAsTsv(args);
1375
+ if (payload) {
1376
+ const extent = computeSelectionExtent(
1377
+ snap.selection.ranges,
1378
+ snap,
1379
+ effectiveColumns
1380
+ );
1381
+ Promise.resolve(
1382
+ (copyToClipboard ?? defaultCopyToClipboard)(payload)
1383
+ ).then(() => {
1384
+ scheduleAnnouncement(
1385
+ effectiveMessages.copyAnnouncement({
1386
+ rowCount: extent.rowCount,
1387
+ columnCount: extent.columnCount
1388
+ })
1389
+ );
1390
+ }).catch((err) => {
1391
+ console.warn("[pretable] clipboard copy failed", err);
1392
+ scheduleAnnouncement(
1393
+ effectiveMessages.copyFailedAnnouncement()
1394
+ );
1395
+ });
1396
+ }
1397
+ return;
1398
+ }
1399
+ const isSelectAll = (event.metaKey || event.ctrlKey) && (event.key === "a" || event.key === "A") && !event.shiftKey && !event.altKey;
1400
+ const before = grid.getSnapshot();
1401
+ const handled = handleSurfaceKeyDown(event, {
1402
+ bodyViewportHeight,
1403
+ columns: effectiveColumns,
1404
+ grid,
1405
+ onSelectedRowIdChange,
1406
+ selectFocusedRowOnArrowKey,
1407
+ tabBehavior
1408
+ });
1409
+ if (handled) {
1410
+ event.preventDefault();
1411
+ const after = grid.getSnapshot();
1412
+ if (isSelectAll) {
1413
+ const extent = computeSelectionExtent(
1414
+ after.selection.ranges,
1415
+ after,
1416
+ effectiveColumns
1417
+ );
1418
+ scheduleAnnouncement(
1419
+ effectiveMessages.selectAllAnnouncement({
1420
+ rowCount: extent.rowCount,
1421
+ columnCount: extent.columnCount,
1422
+ isAll: extent.isAll
1423
+ })
1424
+ );
1425
+ }
1426
+ if (before.focus.rowId !== after.focus.rowId || before.focus.columnId !== after.focus.columnId) {
1427
+ onFocusChange?.(after.focus);
1428
+ }
1429
+ if (JSON.stringify(before.selection) !== JSON.stringify(after.selection)) {
1430
+ onSelectionChange?.(after.selection);
925
1431
  }
926
1432
  }
927
1433
  },
@@ -945,12 +1451,93 @@ function PretableSurface({
945
1451
  /* @__PURE__ */ jsxRuntime.jsx(
946
1452
  "div",
947
1453
  {
1454
+ "aria-atomic": "true",
1455
+ "aria-live": "polite",
1456
+ className: "pt-sr-only",
1457
+ "data-pretable-live-region": "",
1458
+ role: "status",
1459
+ children: liveMessage
1460
+ }
1461
+ ),
1462
+ /* @__PURE__ */ jsxRuntime.jsx(
1463
+ "div",
1464
+ {
1465
+ "aria-rowindex": 1,
948
1466
  "data-pretable-header-row": "",
1467
+ role: "row",
949
1468
  style: getHeaderRowStyle(renderSnapshot.totalWidth, headerHeight),
950
- children: renderSnapshot.columns.map((plannedCol) => {
951
- const column = columns[plannedCol.index];
1469
+ children: renderSnapshot.columns.flatMap((plannedCol) => {
1470
+ const column = columnsById.get(plannedCol.id);
952
1471
  if (!column) {
953
- return null;
1472
+ return [];
1473
+ }
1474
+ const effWidth = dragLiveWidth?.columnId === column.id ? dragLiveWidth.width : plannedCol.width;
1475
+ if (column.id === ROW_SELECT_COLUMN_ID) {
1476
+ const pinnedOffset2 = pinnedOffsets[column.id];
1477
+ const positionStyle2 = plannedCol.pinned === "left" && pinnedOffset2 !== void 0 ? {
1478
+ ...getHeaderCellStyle(plannedCol.left, plannedCol.width),
1479
+ ...getPinnedCellStyle(pinnedOffset2)
1480
+ } : getHeaderCellStyle(plannedCol.left, plannedCol.width);
1481
+ const visibleRows = snapshot.visibleRows;
1482
+ const allFullySelected = visibleRows.length > 0 && visibleRows.every((r) => fullySelectedRowIds.has(r.id));
1483
+ const anySelected = visibleRows.some(
1484
+ (r) => fullySelectedRowIds.has(r.id) || indeterminateRowIds.has(r.id)
1485
+ );
1486
+ const headerCheckState = allFullySelected ? "true" : anySelected ? "mixed" : "false";
1487
+ const showHeaderCheckbox = rowSelectionColumn?.headerCheckbox !== false;
1488
+ return /* @__PURE__ */ jsxRuntime.jsx(
1489
+ "div",
1490
+ {
1491
+ "aria-colindex": plannedCol.index + 1,
1492
+ "data-pretable-header-cell": "",
1493
+ "data-pretable-row-select-header": "",
1494
+ "data-pinned": plannedCol.pinned === "left" ? "left" : void 0,
1495
+ role: "columnheader",
1496
+ style: {
1497
+ alignItems: "center",
1498
+ display: "flex",
1499
+ justifyContent: "center",
1500
+ padding: 0,
1501
+ ...positionStyle2
1502
+ },
1503
+ children: showHeaderCheckbox ? /* @__PURE__ */ jsxRuntime.jsx(
1504
+ "button",
1505
+ {
1506
+ "aria-checked": headerCheckState,
1507
+ "aria-label": "Select all rows",
1508
+ "data-pretable-row-select-all": "true",
1509
+ onClick: (event) => {
1510
+ event.stopPropagation();
1511
+ const before = grid.getSnapshot();
1512
+ const setting = !allFullySelected;
1513
+ grid.setSelectAllVisible(setting);
1514
+ const after = grid.getSnapshot();
1515
+ if (JSON.stringify(before.selection) !== JSON.stringify(after.selection)) {
1516
+ onSelectionChange?.(after.selection);
1517
+ }
1518
+ if (setting) {
1519
+ const extent = computeSelectionExtent(
1520
+ after.selection.ranges,
1521
+ after,
1522
+ effectiveColumns
1523
+ );
1524
+ scheduleAnnouncement(
1525
+ effectiveMessages.selectAllAnnouncement({
1526
+ rowCount: extent.rowCount,
1527
+ columnCount: extent.columnCount,
1528
+ isAll: extent.isAll
1529
+ })
1530
+ );
1531
+ }
1532
+ },
1533
+ role: "checkbox",
1534
+ type: "button",
1535
+ children: headerCheckState === "true" ? "\u2713" : headerCheckState === "mixed" ? "\u2013" : ""
1536
+ }
1537
+ ) : null
1538
+ },
1539
+ column.id
1540
+ );
954
1541
  }
955
1542
  const label = column.header ?? column.id;
956
1543
  const sortDirection = snapshot.sort.columnId === column.id ? snapshot.sort.direction : null;
@@ -960,54 +1547,261 @@ function PretableSurface({
960
1547
  }) ?? {};
961
1548
  const pinnedOffset = pinnedOffsets[column.id];
962
1549
  const positionStyle = plannedCol.pinned === "left" && pinnedOffset !== void 0 ? {
963
- ...getHeaderCellStyle(plannedCol.left, plannedCol.width),
1550
+ ...getHeaderCellStyle(plannedCol.left, effWidth),
964
1551
  ...getPinnedCellStyle(pinnedOffset)
965
- } : getHeaderCellStyle(plannedCol.left, plannedCol.width);
966
- return /* @__PURE__ */ react.createElement(
967
- "button",
968
- {
969
- ...headerProps,
970
- "aria-label": `Sort ${label}`,
971
- className: getHeaderCellClassName?.({
972
- column,
973
- sortDirection
974
- }),
975
- "data-pretable-header-cell": "",
976
- "data-pinned": plannedCol.pinned === "left" ? "left" : void 0,
977
- key: column.id,
978
- onClick: () => {
979
- const nextDirection = getNextSortDirection(sortDirection);
980
- grid.setSort(column.id, nextDirection);
981
- if (nextDirection) {
982
- onSortChange?.({
1552
+ } : getHeaderCellStyle(plannedCol.left, effWidth);
1553
+ const ariaSort = sortDirection === "asc" ? "ascending" : sortDirection === "desc" ? "descending" : "none";
1554
+ const showResizeHandle = column.resizable !== false;
1555
+ const isDragging = dragLiveWidth?.columnId === column.id;
1556
+ const handleLeft = plannedCol.left + effWidth - 4;
1557
+ const handlePinnedStyle = plannedCol.pinned === "left" && pinnedOffset !== void 0 ? {
1558
+ position: "sticky",
1559
+ zIndex: 3,
1560
+ left: pinnedOffset + effWidth - 4
1561
+ } : null;
1562
+ return [
1563
+ /* @__PURE__ */ react.createElement(
1564
+ "button",
1565
+ {
1566
+ ...headerProps,
1567
+ "aria-colindex": plannedCol.index + 1,
1568
+ "aria-label": `Sort ${label}`,
1569
+ "aria-sort": ariaSort,
1570
+ className: getHeaderCellClassName?.({
1571
+ column,
1572
+ sortDirection
1573
+ }),
1574
+ "data-pretable-header-cell": "",
1575
+ "data-pinned": plannedCol.pinned === "left" ? "left" : void 0,
1576
+ key: column.id,
1577
+ role: "columnheader",
1578
+ onClick: (event) => {
1579
+ if (wasReorderingRef.current) {
1580
+ event.preventDefault();
1581
+ wasReorderingRef.current = false;
1582
+ return;
1583
+ }
1584
+ const nextDirection = getNextSortDirection(sortDirection);
1585
+ grid.setSort(column.id, nextDirection);
1586
+ if (nextDirection) {
1587
+ onSortChange?.({
1588
+ columnId: column.id,
1589
+ direction: nextDirection
1590
+ });
1591
+ } else {
1592
+ onSortChange?.(null);
1593
+ }
1594
+ },
1595
+ ...column.id !== ROW_SELECT_COLUMN_ID && column.reorderable !== false ? {
1596
+ onPointerDown: (event) => {
1597
+ if (event.button !== 0) return;
1598
+ if (event.shiftKey || event.metaKey || event.ctrlKey)
1599
+ return;
1600
+ reorderStateRef.current = {
1601
+ columnId: column.id,
1602
+ pointerId: event.pointerId,
1603
+ startX: event.clientX,
1604
+ startY: event.clientY,
1605
+ dragging: false
1606
+ };
1607
+ },
1608
+ onPointerMove: (event) => {
1609
+ const drag = reorderStateRef.current;
1610
+ if (!drag || drag.columnId !== column.id) return;
1611
+ if (event.pointerId !== drag.pointerId) return;
1612
+ const dx = event.clientX - drag.startX;
1613
+ const dy = event.clientY - drag.startY;
1614
+ const dist = Math.hypot(dx, dy);
1615
+ const surfaceRect = viewportRef.current?.getBoundingClientRect();
1616
+ const surfaceLeft = surfaceRect?.left ?? 0;
1617
+ if (!drag.dragging) {
1618
+ if (dist < REORDER_THRESHOLD_PX) return;
1619
+ drag.dragging = true;
1620
+ try {
1621
+ event.currentTarget.setPointerCapture(
1622
+ event.pointerId
1623
+ );
1624
+ } catch {
1625
+ }
1626
+ const headerEl = event.currentTarget;
1627
+ const rect = headerEl.getBoundingClientRect();
1628
+ setReorderDrag({
1629
+ columnId: column.id,
1630
+ cursorX: event.clientX,
1631
+ cursorY: event.clientY,
1632
+ dropIndex: computeDropIndex(
1633
+ event.clientX,
1634
+ effectiveColumns.length,
1635
+ columnLefts,
1636
+ columnWidths,
1637
+ surfaceLeft
1638
+ ),
1639
+ ghostWidth: rect.width || effWidth,
1640
+ ghostHeight: rect.height || headerHeight,
1641
+ ghostHeader: String(column.header ?? column.id)
1642
+ });
1643
+ return;
1644
+ }
1645
+ setReorderDrag(
1646
+ (prev) => prev ? {
1647
+ ...prev,
1648
+ cursorX: event.clientX,
1649
+ cursorY: event.clientY,
1650
+ dropIndex: computeDropIndex(
1651
+ event.clientX,
1652
+ effectiveColumns.length,
1653
+ columnLefts,
1654
+ columnWidths,
1655
+ surfaceLeft
1656
+ )
1657
+ } : null
1658
+ );
1659
+ },
1660
+ onPointerUp: (event) => {
1661
+ const drag = reorderStateRef.current;
1662
+ if (!drag || drag.columnId !== column.id) return;
1663
+ if (event.pointerId !== drag.pointerId) return;
1664
+ const current = reorderDrag;
1665
+ if (drag.dragging && current) {
1666
+ wasReorderingRef.current = true;
1667
+ const beforePinned = buildPinnedMap(grid);
1668
+ grid.moveColumn(column.id, current.dropIndex);
1669
+ const afterOrder = grid.options.columns.map((c) => c.id).filter((id) => id !== ROW_SELECT_COLUMN_ID);
1670
+ onColumnOrderChange?.(afterOrder);
1671
+ const afterPinned = buildPinnedMap(grid);
1672
+ if (!pinnedMapsEqual(beforePinned, afterPinned)) {
1673
+ onColumnPinnedChange?.(afterPinned);
1674
+ }
1675
+ }
1676
+ try {
1677
+ event.currentTarget.releasePointerCapture(
1678
+ event.pointerId
1679
+ );
1680
+ } catch {
1681
+ }
1682
+ reorderStateRef.current = null;
1683
+ setReorderDrag(null);
1684
+ },
1685
+ onPointerCancel: () => {
1686
+ reorderStateRef.current = null;
1687
+ setReorderDrag(null);
1688
+ }
1689
+ } : {},
1690
+ style: {
1691
+ alignItems: "start",
1692
+ border: 0,
1693
+ borderRight: "1px solid rgba(255, 255, 255, 0.06)",
1694
+ color: "inherit",
1695
+ display: "grid",
1696
+ gap: 4,
1697
+ textAlign: "left",
1698
+ ...positionStyle
1699
+ },
1700
+ type: "button"
1701
+ },
1702
+ /* @__PURE__ */ jsxRuntime.jsx(
1703
+ MemoizedHeaderContent,
1704
+ {
1705
+ columnId: column.id,
1706
+ label,
1707
+ sortDirection,
1708
+ isSorted: sortDirection !== null,
1709
+ width: effWidth,
1710
+ isSortable: column.sortable !== false,
1711
+ renderHeaderRef: column.renderHeader ?? null,
1712
+ fallbackRenderHeaderRef: renderHeaderCell ?? null,
1713
+ headerRenderInput: {
1714
+ column,
1715
+ label,
1716
+ sortDirection,
1717
+ isSorted: sortDirection !== null
1718
+ }
1719
+ }
1720
+ )
1721
+ ),
1722
+ showResizeHandle ? /* @__PURE__ */ jsxRuntime.jsx(
1723
+ "div",
1724
+ {
1725
+ "data-pretable-resize-handle": "",
1726
+ "data-column-id": column.id,
1727
+ "data-dragging": isDragging ? "true" : "false",
1728
+ style: {
1729
+ position: "absolute",
1730
+ top: 0,
1731
+ height: "100%",
1732
+ width: 4,
1733
+ left: handleLeft,
1734
+ cursor: "col-resize",
1735
+ zIndex: 4,
1736
+ touchAction: "none",
1737
+ userSelect: "none",
1738
+ ...handlePinnedStyle ?? {}
1739
+ },
1740
+ onPointerDown: (event) => {
1741
+ if (event.button !== 0) return;
1742
+ event.stopPropagation();
1743
+ const startWidth = column.widthPx ?? plannedCol.width ?? Math.max(column.minWidthPx ?? 40, 80);
1744
+ resizeStateRef.current = {
983
1745
  columnId: column.id,
984
- direction: nextDirection
985
- });
986
- } else {
987
- onSortChange?.(null);
1746
+ startX: event.clientX,
1747
+ startWidth,
1748
+ pointerId: event.pointerId
1749
+ };
1750
+ wasResizingRef.current = false;
1751
+ try {
1752
+ event.currentTarget.setPointerCapture(event.pointerId);
1753
+ } catch {
1754
+ }
1755
+ setDragLiveWidth({ columnId: column.id, width: startWidth });
1756
+ },
1757
+ onPointerMove: (event) => {
1758
+ const drag = resizeStateRef.current;
1759
+ if (!drag || drag.columnId !== column.id) return;
1760
+ const min = column.minWidthPx ?? 40;
1761
+ const max = column.maxWidthPx ?? Infinity;
1762
+ const next = Math.max(
1763
+ min,
1764
+ Math.min(
1765
+ max,
1766
+ drag.startWidth + (event.clientX - drag.startX)
1767
+ )
1768
+ );
1769
+ if (Math.abs(next - drag.startWidth) > 0) {
1770
+ wasResizingRef.current = true;
1771
+ }
1772
+ setDragLiveWidth({ columnId: column.id, width: next });
1773
+ },
1774
+ onPointerUp: (event) => {
1775
+ const drag = resizeStateRef.current;
1776
+ if (!drag || drag.columnId !== column.id) return;
1777
+ const finalWidth = dragLiveWidth?.width ?? drag.startWidth;
1778
+ try {
1779
+ event.currentTarget.releasePointerCapture(drag.pointerId);
1780
+ } catch {
1781
+ }
1782
+ grid.setColumnWidth(column.id, finalWidth);
1783
+ onColumnWidthsChange?.(buildWidthsMap(grid));
1784
+ resizeStateRef.current = null;
1785
+ setDragLiveWidth(null);
1786
+ },
1787
+ onPointerCancel: () => {
1788
+ resizeStateRef.current = null;
1789
+ setDragLiveWidth(null);
1790
+ wasResizingRef.current = false;
1791
+ },
1792
+ onDoubleClick: (event) => {
1793
+ if (wasResizingRef.current) {
1794
+ event.preventDefault();
1795
+ wasResizingRef.current = false;
1796
+ return;
1797
+ }
1798
+ grid.autosizeColumn(column.id);
1799
+ onColumnWidthsChange?.(buildWidthsMap(grid));
988
1800
  }
989
1801
  },
990
- style: {
991
- alignItems: "start",
992
- border: 0,
993
- borderRight: "1px solid rgba(255, 255, 255, 0.06)",
994
- color: "inherit",
995
- display: "grid",
996
- gap: 4,
997
- textAlign: "left",
998
- ...positionStyle
999
- },
1000
- type: "button"
1001
- },
1002
- renderHeaderCell ? renderHeaderCell({
1003
- column,
1004
- label,
1005
- sortDirection
1006
- }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1007
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: label }),
1008
- /* @__PURE__ */ jsxRuntime.jsx("strong", { children: sortDirection === "desc" ? "Newest" : sortDirection === "asc" ? "Oldest" : "Sort" })
1009
- ] })
1010
- );
1802
+ `${column.id}::resize-handle`
1803
+ ) : null
1804
+ ];
1011
1805
  })
1012
1806
  }
1013
1807
  ),
@@ -1021,7 +1815,7 @@ function PretableSurface({
1021
1815
  ),
1022
1816
  children: renderSnapshot.rows.map(({ height, id, row, rowIndex, top }) => {
1023
1817
  const isFocused = snapshot.focus.rowId === id;
1024
- const isSelected = snapshot.selection.rowIds.includes(id);
1818
+ const isSelected = fullySelectedRowIds.has(id);
1025
1819
  const rowProps = getRowProps?.({
1026
1820
  isFocused,
1027
1821
  isSelected,
@@ -1033,8 +1827,8 @@ function PretableSurface({
1033
1827
  "div",
1034
1828
  {
1035
1829
  ...rowProps,
1036
- "aria-rowindex": rowIndex + 1,
1037
- "aria-selected": isSelected,
1830
+ "aria-rowindex": rowIndex + 2,
1831
+ "aria-selected": isSelected ? "true" : void 0,
1038
1832
  className: getRowClassName?.({
1039
1833
  isFocused,
1040
1834
  isSelected,
@@ -1050,11 +1844,7 @@ function PretableSurface({
1050
1844
  "data-selected": isSelected ? "true" : "false",
1051
1845
  "data-testid": "pretable-row",
1052
1846
  key: id,
1053
- onClick: () => {
1054
- grid.setFocus(id, columns[0]?.id ?? null);
1055
- grid.selectRow(id);
1056
- onSelectedRowIdChange?.(id);
1057
- },
1847
+ role: "row",
1058
1848
  ref: (node) => {
1059
1849
  if (node) {
1060
1850
  rowNodesRef.current.set(id, node);
@@ -1065,15 +1855,20 @@ function PretableSurface({
1065
1855
  style: getRowStyle(top, height)
1066
1856
  },
1067
1857
  renderSnapshot.columns.map((plannedCol) => {
1068
- const column = columns[plannedCol.index];
1858
+ const column = columnsById.get(plannedCol.id);
1069
1859
  if (!column) {
1070
1860
  return null;
1071
1861
  }
1072
1862
  const value = resolveCellValue(row, column);
1863
+ const cellKey = `${id}::${column.id}`;
1864
+ const cellIsFocused = isFocused && snapshot.focus.columnId === column.id;
1865
+ const cellIsSelected = isCellSelected(id, column.id);
1866
+ const formattedValue = column.format ? column.format({ value, row, column }) : formatCellValue(value);
1073
1867
  const bodyInput = {
1074
1868
  column,
1075
- isFocused,
1076
- isSelected,
1869
+ formattedValue,
1870
+ isFocused: cellIsFocused,
1871
+ isSelected: cellIsSelected,
1077
1872
  row,
1078
1873
  rowId: id,
1079
1874
  rowIndex,
@@ -1081,39 +1876,310 @@ function PretableSurface({
1081
1876
  };
1082
1877
  const bodyProps = getBodyCellProps?.(bodyInput) ?? {};
1083
1878
  const pinnedOffset = pinnedOffsets[column.id];
1879
+ const cellEffWidth = dragLiveWidth?.columnId === column.id ? dragLiveWidth.width : plannedCol.width;
1084
1880
  const positionStyle = plannedCol.pinned === "left" && pinnedOffset !== void 0 ? {
1085
- ...getCellStyle(plannedCol.left, plannedCol.width),
1881
+ ...getCellStyle(plannedCol.left, cellEffWidth),
1086
1882
  ...getPinnedCellStyle(pinnedOffset)
1087
- } : getCellStyle(plannedCol.left, plannedCol.width);
1883
+ } : getCellStyle(plannedCol.left, cellEffWidth);
1884
+ const isRowSelectCell = column.id === ROW_SELECT_COLUMN_ID;
1885
+ const rowCheckState = fullySelectedRowIds.has(id) ? "true" : indeterminateRowIds.has(id) ? "mixed" : "false";
1088
1886
  return /* @__PURE__ */ react.createElement(
1089
1887
  "div",
1090
1888
  {
1091
1889
  ...bodyProps,
1890
+ "aria-colindex": plannedCol.index + 1,
1891
+ "aria-selected": cellIsSelected ? "true" : void 0,
1092
1892
  className: getBodyCellClassName?.(bodyInput),
1093
1893
  "data-column-id": column.id,
1094
- "data-focused": isFocused ? "true" : "false",
1894
+ "data-focused": cellIsFocused ? "true" : "false",
1095
1895
  "data-pinned": column.pinned === "left" ? "left" : void 0,
1096
1896
  "data-pretable-cell": "",
1097
1897
  "data-pretable-wrap": column.wrap ? "true" : void 0,
1098
- "data-selected": isSelected ? "true" : "false",
1898
+ "data-row-select-cell": isRowSelectCell ? "true" : void 0,
1899
+ "data-selected": cellIsSelected ? "true" : "false",
1099
1900
  key: `${id}:${column.id}`,
1901
+ onClick: (event) => {
1902
+ if (column.id === ROW_SELECT_COLUMN_ID) return;
1903
+ handleCellClick({
1904
+ cmd: event.metaKey || event.ctrlKey,
1905
+ columnId: column.id,
1906
+ columns: effectiveColumns,
1907
+ grid,
1908
+ onFocusChange,
1909
+ onSelectedRowIdChange,
1910
+ onSelectionChange,
1911
+ rowId: id,
1912
+ shift: event.shiftKey
1913
+ });
1914
+ },
1915
+ onPointerDown: (event) => {
1916
+ if (event.button !== 0) return;
1917
+ if (column.id === ROW_SELECT_COLUMN_ID) return;
1918
+ const cmd = event.metaKey || event.ctrlKey;
1919
+ if (event.shiftKey || cmd) return;
1920
+ dragStartSelectionRef.current = grid.getSnapshot().selection;
1921
+ dragAnchorRef.current = {
1922
+ rowId: id,
1923
+ columnId: column.id
1924
+ };
1925
+ handleCellClick({
1926
+ cmd: false,
1927
+ columnId: column.id,
1928
+ columns: effectiveColumns,
1929
+ grid,
1930
+ onFocusChange,
1931
+ onSelectedRowIdChange,
1932
+ onSelectionChange,
1933
+ rowId: id,
1934
+ shift: false
1935
+ });
1936
+ try {
1937
+ event.currentTarget.setPointerCapture(event.pointerId);
1938
+ } catch {
1939
+ }
1940
+ },
1941
+ onPointerEnter: () => {
1942
+ if (!dragAnchorRef.current) return;
1943
+ if (column.id === ROW_SELECT_COLUMN_ID) return;
1944
+ const before = grid.getSnapshot();
1945
+ const addr = {
1946
+ rowId: id,
1947
+ columnId: column.id
1948
+ };
1949
+ grid.extendRangeFromAnchor(addr);
1950
+ grid.setFocus(addr);
1951
+ const after = grid.getSnapshot();
1952
+ if (before.focus.rowId !== after.focus.rowId || before.focus.columnId !== after.focus.columnId) {
1953
+ onFocusChange?.(after.focus);
1954
+ }
1955
+ if (JSON.stringify(before.selection) !== JSON.stringify(after.selection)) {
1956
+ onSelectionChange?.(after.selection);
1957
+ const beforeFullRow = singleFullRowSelection(
1958
+ before.selection,
1959
+ effectiveColumns.filter(
1960
+ (c) => c.id !== ROW_SELECT_COLUMN_ID
1961
+ )
1962
+ );
1963
+ const afterFullRow = singleFullRowSelection(
1964
+ after.selection,
1965
+ effectiveColumns.filter(
1966
+ (c) => c.id !== ROW_SELECT_COLUMN_ID
1967
+ )
1968
+ );
1969
+ if (beforeFullRow !== afterFullRow) {
1970
+ onSelectedRowIdChange?.(afterFullRow);
1971
+ }
1972
+ }
1973
+ },
1974
+ onPointerUp: () => {
1975
+ dragAnchorRef.current = null;
1976
+ },
1977
+ onPointerCancel: () => {
1978
+ dragAnchorRef.current = null;
1979
+ },
1980
+ ref: (node) => {
1981
+ if (node) {
1982
+ cellNodesRef.current.set(cellKey, node);
1983
+ } else {
1984
+ cellNodesRef.current.delete(cellKey);
1985
+ }
1986
+ },
1987
+ role: "gridcell",
1100
1988
  style: {
1989
+ outline: "none",
1101
1990
  overflowWrap: column.wrap ? "anywhere" : "normal",
1102
1991
  whiteSpace: column.wrap ? "pre-wrap" : "nowrap",
1103
1992
  ...positionStyle
1104
- }
1993
+ },
1994
+ tabIndex: cellIsFocused ? 0 : -1
1105
1995
  },
1106
- renderBodyCell ? renderBodyCell(bodyInput) : formatCellValue(value)
1996
+ isRowSelectCell ? /* @__PURE__ */ jsxRuntime.jsx(
1997
+ "button",
1998
+ {
1999
+ "aria-checked": rowCheckState,
2000
+ "aria-label": "Select row",
2001
+ "data-pretable-row-select": "true",
2002
+ onClick: (event) => {
2003
+ event.stopPropagation();
2004
+ event.preventDefault();
2005
+ const before = grid.getSnapshot();
2006
+ const visible = before.visibleRows;
2007
+ if (event.shiftKey && lastCheckedRowAnchorRef.current) {
2008
+ const anchorId = lastCheckedRowAnchorRef.current;
2009
+ const anchorIdx = visible.findIndex(
2010
+ (r) => r.id === anchorId
2011
+ );
2012
+ const clickedIdx = visible.findIndex(
2013
+ (r) => r.id === id
2014
+ );
2015
+ if (anchorIdx >= 0 && clickedIdx >= 0) {
2016
+ const [lo, hi] = anchorIdx <= clickedIdx ? [anchorIdx, clickedIdx] : [clickedIdx, anchorIdx];
2017
+ for (let i = lo; i <= hi; i += 1) {
2018
+ const r = visible[i];
2019
+ if (r && !fullySelectedRowIds.has(r.id)) {
2020
+ grid.toggleRowSelection(r.id);
2021
+ }
2022
+ }
2023
+ }
2024
+ } else {
2025
+ grid.toggleRowSelection(id);
2026
+ }
2027
+ lastCheckedRowAnchorRef.current = id;
2028
+ const after = grid.getSnapshot();
2029
+ if (JSON.stringify(before.selection) !== JSON.stringify(after.selection)) {
2030
+ onSelectionChange?.(after.selection);
2031
+ }
2032
+ },
2033
+ role: "checkbox",
2034
+ type: "button",
2035
+ children: rowCheckState === "true" ? "\u2713" : rowCheckState === "mixed" ? "\u2013" : ""
2036
+ }
2037
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
2038
+ MemoizedCellContent,
2039
+ {
2040
+ rowId: id,
2041
+ columnId: column.id,
2042
+ value,
2043
+ formattedValue,
2044
+ isFocused: cellIsFocused,
2045
+ isSelected: cellIsSelected,
2046
+ renderRef: column.render ?? null,
2047
+ fallbackRenderRef: renderBodyCell ?? null,
2048
+ cellRenderInput: bodyInput
2049
+ }
2050
+ )
1107
2051
  );
1108
2052
  })
1109
2053
  );
1110
2054
  })
1111
2055
  }
1112
- )
2056
+ ),
2057
+ reorderDrag ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2058
+ /* @__PURE__ */ jsxRuntime.jsx(
2059
+ "div",
2060
+ {
2061
+ "data-pretable-reorder-ghost": "",
2062
+ style: {
2063
+ left: reorderDrag.cursorX + 8,
2064
+ top: reorderDrag.cursorY + 8,
2065
+ width: reorderDrag.ghostWidth,
2066
+ height: reorderDrag.ghostHeight,
2067
+ display: "flex",
2068
+ alignItems: "center",
2069
+ paddingLeft: 12
2070
+ },
2071
+ children: reorderDrag.ghostHeader
2072
+ }
2073
+ ),
2074
+ /* @__PURE__ */ jsxRuntime.jsx(
2075
+ "div",
2076
+ {
2077
+ "data-pretable-reorder-drop-indicator": "",
2078
+ style: {
2079
+ left: computeDropIndicatorLeft(
2080
+ reorderDrag.dropIndex,
2081
+ columnLefts,
2082
+ columnWidths
2083
+ ),
2084
+ height: reorderDrag.ghostHeight + bodyViewportHeight
2085
+ }
2086
+ }
2087
+ )
2088
+ ] }) : null
1113
2089
  ]
1114
2090
  }
1115
2091
  );
1116
2092
  }
2093
+ function replaceSelectionWithFullRow(grid, rowId, columns) {
2094
+ const firstColumn = columns[0];
2095
+ const lastColumn = columns[columns.length - 1];
2096
+ if (!firstColumn || !lastColumn) {
2097
+ grid.setSelection({ ranges: [], anchor: null });
2098
+ return;
2099
+ }
2100
+ grid.setSelection({
2101
+ ranges: [
2102
+ {
2103
+ startRowId: rowId,
2104
+ endRowId: rowId,
2105
+ startColumnId: firstColumn.id,
2106
+ endColumnId: lastColumn.id
2107
+ }
2108
+ ],
2109
+ anchor: { rowId, columnId: firstColumn.id }
2110
+ });
2111
+ }
2112
+ function handleCellClick(args) {
2113
+ const {
2114
+ cmd,
2115
+ columnId,
2116
+ columns,
2117
+ grid,
2118
+ onFocusChange,
2119
+ onSelectedRowIdChange,
2120
+ onSelectionChange,
2121
+ rowId,
2122
+ shift
2123
+ } = args;
2124
+ const before = grid.getSnapshot();
2125
+ const addr = { rowId, columnId };
2126
+ if (shift && !cmd && before.selection.anchor) {
2127
+ grid.extendRangeFromAnchor(addr);
2128
+ grid.setFocus(addr);
2129
+ } else if (cmd) {
2130
+ grid.addRange({
2131
+ startRowId: rowId,
2132
+ endRowId: rowId,
2133
+ startColumnId: columnId,
2134
+ endColumnId: columnId
2135
+ });
2136
+ grid.setFocus(addr);
2137
+ } else {
2138
+ grid.setFocus(addr);
2139
+ grid.setSelection({
2140
+ ranges: [
2141
+ {
2142
+ startRowId: rowId,
2143
+ endRowId: rowId,
2144
+ startColumnId: columnId,
2145
+ endColumnId: columnId
2146
+ }
2147
+ ],
2148
+ anchor: addr
2149
+ });
2150
+ }
2151
+ const after = grid.getSnapshot();
2152
+ if (before.focus.rowId !== after.focus.rowId || before.focus.columnId !== after.focus.columnId) {
2153
+ onFocusChange?.(after.focus);
2154
+ }
2155
+ const selectionChanged = JSON.stringify(before.selection) !== JSON.stringify(after.selection);
2156
+ if (selectionChanged) {
2157
+ onSelectionChange?.(after.selection);
2158
+ const dataColumns = columns.filter((c) => c.id !== ROW_SELECT_COLUMN_ID);
2159
+ const beforeFullRow = singleFullRowSelection(before.selection, dataColumns);
2160
+ const afterFullRow = singleFullRowSelection(after.selection, dataColumns);
2161
+ if (beforeFullRow !== afterFullRow) {
2162
+ onSelectedRowIdChange?.(afterFullRow);
2163
+ }
2164
+ }
2165
+ }
2166
+ function singleFullRowSelection(selection, columns) {
2167
+ if (selection.ranges.length !== 1 || columns.length === 0) {
2168
+ return null;
2169
+ }
2170
+ const range = selection.ranges[0];
2171
+ if (!range) return null;
2172
+ if (range.startRowId !== range.endRowId) return null;
2173
+ const firstColumn = columns[0];
2174
+ const lastColumn = columns[columns.length - 1];
2175
+ if (!firstColumn || !lastColumn) return null;
2176
+ const startMatchesFirst = range.startColumnId === firstColumn.id;
2177
+ const endMatchesLast = range.endColumnId === lastColumn.id;
2178
+ const startMatchesLast = range.startColumnId === lastColumn.id;
2179
+ const endMatchesFirst = range.endColumnId === firstColumn.id;
2180
+ const coversAllColumns = startMatchesFirst && endMatchesLast || startMatchesLast && endMatchesFirst;
2181
+ return coversAllColumns ? range.startRowId : null;
2182
+ }
1117
2183
  function getRowMeasurementKey(rowNode) {
1118
2184
  const rowParts = [
1119
2185
  rowNode.getAttribute("class") ?? "",
@@ -1140,6 +2206,281 @@ function getRowMeasurementKey(rowNode) {
1140
2206
  function normalizeStyleSignature(styleValue) {
1141
2207
  return styleValue.split(";").map((declaration) => declaration.trim()).filter(Boolean).filter((declaration) => !/^top\s*:/i.test(declaration)).join(";");
1142
2208
  }
2209
+ function computeSelectionExtent(ranges, snapshot, effectiveColumns) {
2210
+ const visibleRows = snapshot.visibleRows;
2211
+ const dataColumns = effectiveColumns.filter(
2212
+ (c) => c.id !== ROW_SELECT_COLUMN_ID
2213
+ );
2214
+ if (ranges.length === 0 || visibleRows.length === 0 || dataColumns.length === 0) {
2215
+ return { rowCount: 0, columnCount: 0, isAll: false };
2216
+ }
2217
+ const rowOrder = /* @__PURE__ */ new Map();
2218
+ for (let i = 0; i < visibleRows.length; i += 1) {
2219
+ const r = visibleRows[i];
2220
+ if (r) rowOrder.set(r.id, i);
2221
+ }
2222
+ const columnOrder = /* @__PURE__ */ new Map();
2223
+ for (let i = 0; i < effectiveColumns.length; i += 1) {
2224
+ const c = effectiveColumns[i];
2225
+ if (c) columnOrder.set(c.id, i);
2226
+ }
2227
+ const coveredRows = /* @__PURE__ */ new Set();
2228
+ const coveredCols = /* @__PURE__ */ new Set();
2229
+ for (const range of ranges) {
2230
+ const r1 = rowOrder.get(range.startRowId);
2231
+ const r2 = rowOrder.get(range.endRowId);
2232
+ if (r1 === void 0 || r2 === void 0) continue;
2233
+ const rowLo = Math.min(r1, r2);
2234
+ const rowHi = Math.max(r1, r2);
2235
+ const startSynth = range.startColumnId === ROW_SELECT_COLUMN_ID;
2236
+ const endSynth = range.endColumnId === ROW_SELECT_COLUMN_ID;
2237
+ let colsForRange;
2238
+ if (startSynth && endSynth) {
2239
+ continue;
2240
+ }
2241
+ if (startSynth || endSynth) {
2242
+ colsForRange = dataColumns.slice();
2243
+ } else {
2244
+ const c1 = columnOrder.get(range.startColumnId);
2245
+ const c2 = columnOrder.get(range.endColumnId);
2246
+ if (c1 === void 0 || c2 === void 0) continue;
2247
+ const colLo = Math.min(c1, c2);
2248
+ const colHi = Math.max(c1, c2);
2249
+ colsForRange = [];
2250
+ for (let i = colLo; i <= colHi; i += 1) {
2251
+ const col = effectiveColumns[i];
2252
+ if (col && col.id !== ROW_SELECT_COLUMN_ID) {
2253
+ colsForRange.push(col);
2254
+ }
2255
+ }
2256
+ }
2257
+ if (colsForRange.length === 0) continue;
2258
+ for (let i = rowLo; i <= rowHi; i += 1) {
2259
+ const row = visibleRows[i];
2260
+ if (row) coveredRows.add(row.id);
2261
+ }
2262
+ for (const col of colsForRange) {
2263
+ coveredCols.add(col.id);
2264
+ }
2265
+ }
2266
+ const rowCount = coveredRows.size;
2267
+ const columnCount = coveredCols.size;
2268
+ const isAll = rowCount === visibleRows.length && columnCount === dataColumns.length;
2269
+ return { rowCount, columnCount, isAll };
2270
+ }
2271
+ var ARROW_DIRECTIONS = {
2272
+ ArrowUp: "up",
2273
+ ArrowDown: "down",
2274
+ ArrowLeft: "left",
2275
+ ArrowRight: "right"
2276
+ };
2277
+ function handleSurfaceKeyDown(event, ctx) {
2278
+ const {
2279
+ bodyViewportHeight,
2280
+ columns: allColumns,
2281
+ grid,
2282
+ onSelectedRowIdChange,
2283
+ selectFocusedRowOnArrowKey,
2284
+ tabBehavior
2285
+ } = ctx;
2286
+ const columns = allColumns.filter((c) => c.id !== ROW_SELECT_COLUMN_ID);
2287
+ const { key } = event;
2288
+ const cmd = event.metaKey || event.ctrlKey;
2289
+ const shift = event.shiftKey;
2290
+ const snapshot = grid.getSnapshot();
2291
+ const focus = snapshot.focus;
2292
+ const visibleRows = snapshot.visibleRows;
2293
+ const firstColumn = columns[0];
2294
+ const lastColumn = columns[columns.length - 1];
2295
+ const direction = ARROW_DIRECTIONS[key];
2296
+ if (direction) {
2297
+ grid.moveFocus(direction, {
2298
+ extend: shift,
2299
+ jumpToEdge: cmd
2300
+ });
2301
+ const after = grid.getSnapshot();
2302
+ if (after.focus.columnId === ROW_SELECT_COLUMN_ID && firstColumn) {
2303
+ const rowId = after.focus.rowId;
2304
+ if (rowId) {
2305
+ grid.setFocus({ rowId, columnId: firstColumn.id });
2306
+ }
2307
+ }
2308
+ if (selectFocusedRowOnArrowKey) {
2309
+ const nextFocus = grid.getSnapshot().focus;
2310
+ if (nextFocus.rowId) {
2311
+ replaceSelectionWithFullRow(grid, nextFocus.rowId, columns);
2312
+ onSelectedRowIdChange?.(nextFocus.rowId);
2313
+ }
2314
+ }
2315
+ return true;
2316
+ }
2317
+ if (key === "Home") {
2318
+ if (!firstColumn) return false;
2319
+ if (cmd) {
2320
+ const firstRow = visibleRows[0];
2321
+ if (!firstRow) return false;
2322
+ grid.setFocus({ rowId: firstRow.id, columnId: firstColumn.id });
2323
+ } else if (focus.rowId) {
2324
+ grid.setFocus({ rowId: focus.rowId, columnId: firstColumn.id });
2325
+ } else {
2326
+ const firstRow = visibleRows[0];
2327
+ if (!firstRow) return false;
2328
+ grid.setFocus({ rowId: firstRow.id, columnId: firstColumn.id });
2329
+ }
2330
+ return true;
2331
+ }
2332
+ if (key === "End") {
2333
+ if (!lastColumn) return false;
2334
+ if (cmd) {
2335
+ const lastRow = visibleRows[visibleRows.length - 1];
2336
+ if (!lastRow) return false;
2337
+ grid.setFocus({ rowId: lastRow.id, columnId: lastColumn.id });
2338
+ } else if (focus.rowId) {
2339
+ grid.setFocus({ rowId: focus.rowId, columnId: lastColumn.id });
2340
+ } else {
2341
+ const firstRow = visibleRows[0];
2342
+ if (!firstRow) return false;
2343
+ grid.setFocus({ rowId: firstRow.id, columnId: lastColumn.id });
2344
+ }
2345
+ return true;
2346
+ }
2347
+ if (key === "PageUp" || key === "PageDown") {
2348
+ if (visibleRows.length === 0 || !firstColumn) return false;
2349
+ const pageRowCount = Math.max(1, Math.floor(bodyViewportHeight / 32));
2350
+ const currentRowIdx = focus.rowId ? visibleRows.findIndex((r) => r.id === focus.rowId) : -1;
2351
+ const baseRowIdx = currentRowIdx === -1 ? 0 : currentRowIdx;
2352
+ const nextRowIdx = key === "PageUp" ? Math.max(0, baseRowIdx - pageRowCount) : Math.min(visibleRows.length - 1, baseRowIdx + pageRowCount);
2353
+ const nextRow = visibleRows[nextRowIdx];
2354
+ if (!nextRow) return false;
2355
+ const columnId = focus.columnId ?? firstColumn.id;
2356
+ const addr = { rowId: nextRow.id, columnId };
2357
+ if (shift) {
2358
+ if (!snapshot.selection.anchor && focus.rowId && focus.columnId) {
2359
+ grid.setSelection({
2360
+ ranges: [
2361
+ {
2362
+ startRowId: focus.rowId,
2363
+ endRowId: focus.rowId,
2364
+ startColumnId: focus.columnId,
2365
+ endColumnId: focus.columnId
2366
+ }
2367
+ ],
2368
+ anchor: { rowId: focus.rowId, columnId: focus.columnId }
2369
+ });
2370
+ }
2371
+ grid.setFocus(addr);
2372
+ grid.extendRangeFromAnchor(addr);
2373
+ } else {
2374
+ grid.setFocus(addr);
2375
+ }
2376
+ return true;
2377
+ }
2378
+ if (key === "Tab") {
2379
+ if (tabBehavior === "exit") {
2380
+ return false;
2381
+ }
2382
+ if (visibleRows.length === 0 || columns.length === 0) return false;
2383
+ const currentRowIdx = focus.rowId ? visibleRows.findIndex((r) => r.id === focus.rowId) : -1;
2384
+ const currentColIdx = focus.columnId ? columns.findIndex((c) => c.id === focus.columnId) : -1;
2385
+ const baseRowIdx = currentRowIdx === -1 ? 0 : currentRowIdx;
2386
+ const baseColIdx = currentColIdx === -1 ? 0 : currentColIdx;
2387
+ let nextRowIdx = baseRowIdx;
2388
+ let nextColIdx = baseColIdx;
2389
+ if (shift) {
2390
+ if (baseColIdx === 0) {
2391
+ nextColIdx = columns.length - 1;
2392
+ nextRowIdx = Math.max(0, baseRowIdx - 1);
2393
+ if (baseRowIdx === 0) {
2394
+ nextColIdx = 0;
2395
+ nextRowIdx = 0;
2396
+ }
2397
+ } else {
2398
+ nextColIdx = baseColIdx - 1;
2399
+ }
2400
+ } else {
2401
+ if (baseColIdx === columns.length - 1) {
2402
+ nextColIdx = 0;
2403
+ nextRowIdx = Math.min(visibleRows.length - 1, baseRowIdx + 1);
2404
+ if (baseRowIdx === visibleRows.length - 1) {
2405
+ nextColIdx = columns.length - 1;
2406
+ nextRowIdx = visibleRows.length - 1;
2407
+ }
2408
+ } else {
2409
+ nextColIdx = baseColIdx + 1;
2410
+ }
2411
+ }
2412
+ const nextRow = visibleRows[nextRowIdx];
2413
+ const nextCol = columns[nextColIdx];
2414
+ if (!nextRow || !nextCol) return false;
2415
+ grid.setFocus({ rowId: nextRow.id, columnId: nextCol.id });
2416
+ return true;
2417
+ }
2418
+ if (cmd && (key === "a" || key === "A")) {
2419
+ grid.selectAll();
2420
+ return true;
2421
+ }
2422
+ if (key === "Escape" || key === "Esc") {
2423
+ grid.clearSelection();
2424
+ return true;
2425
+ }
2426
+ if (key === "Enter" || key === " " || key === "Space") {
2427
+ const focusedRowId = focus.rowId;
2428
+ if (focusedRowId) {
2429
+ replaceSelectionWithFullRow(grid, focusedRowId, columns);
2430
+ onSelectedRowIdChange?.(focusedRowId);
2431
+ return true;
2432
+ }
2433
+ return false;
2434
+ }
2435
+ return false;
2436
+ }
2437
+ function buildWidthsMap(grid) {
2438
+ const result = {};
2439
+ for (const col of grid.options.columns) {
2440
+ if (col.id === ROW_SELECT_COLUMN_ID) continue;
2441
+ if (typeof col.widthPx === "number") {
2442
+ result[col.id] = col.widthPx;
2443
+ }
2444
+ }
2445
+ return result;
2446
+ }
2447
+ function buildPinnedMap(grid) {
2448
+ const result = {};
2449
+ for (const col of grid.options.columns) {
2450
+ if (col.id === ROW_SELECT_COLUMN_ID) continue;
2451
+ result[col.id] = col.pinned === "left" ? "left" : null;
2452
+ }
2453
+ return result;
2454
+ }
2455
+ function pinnedMapsEqual(a, b) {
2456
+ const aKeys = Object.keys(a);
2457
+ const bKeys = Object.keys(b);
2458
+ if (aKeys.length !== bKeys.length) return false;
2459
+ for (const k of aKeys) {
2460
+ if (a[k] !== b[k]) return false;
2461
+ }
2462
+ return true;
2463
+ }
2464
+ function computeDropIndex(cursorX, columnCount, columnLefts, columnWidths, surfaceLeft) {
2465
+ const x = cursorX - surfaceLeft;
2466
+ for (let i = 0; i < columnCount; i += 1) {
2467
+ const left = columnLefts[i] ?? 0;
2468
+ const width = columnWidths[i] ?? 0;
2469
+ const mid = left + width / 2;
2470
+ if (x < mid) {
2471
+ return i;
2472
+ }
2473
+ }
2474
+ return Math.max(0, columnCount - 1);
2475
+ }
2476
+ function computeDropIndicatorLeft(dropIndex, columnLefts, columnWidths) {
2477
+ if (dropIndex >= columnLefts.length) {
2478
+ const lastIdx = columnLefts.length - 1;
2479
+ if (lastIdx < 0) return 0;
2480
+ return (columnLefts[lastIdx] ?? 0) + (columnWidths[lastIdx] ?? 0);
2481
+ }
2482
+ return columnLefts[dropIndex] ?? 0;
2483
+ }
1143
2484
  var VIEWPORT_HEIGHT = 320;
1144
2485
  var BENCHMARK_VIEWPORT_STYLE = {
1145
2486
  contain: "none",
@@ -1151,7 +2492,16 @@ var BENCHMARK_VIEWPORT_STYLE = {
1151
2492
  function Pretable({
1152
2493
  columns,
1153
2494
  getRowId,
1154
- rows
2495
+ rows,
2496
+ rowSelectionColumn,
2497
+ tabBehavior,
2498
+ copyWithHeaders,
2499
+ onCopy,
2500
+ copyToClipboard,
2501
+ messages,
2502
+ onColumnWidthsChange,
2503
+ onColumnOrderChange,
2504
+ onColumnPinnedChange
1155
2505
  }) {
1156
2506
  const resolvedGetRowId = getRowId ?? ((row, index) => {
1157
2507
  const candidate = row.id;
@@ -1235,6 +2585,15 @@ function Pretable({
1235
2585
  )
1236
2586
  ] }),
1237
2587
  rows,
2588
+ rowSelectionColumn,
2589
+ tabBehavior,
2590
+ copyWithHeaders,
2591
+ onCopy,
2592
+ copyToClipboard,
2593
+ messages,
2594
+ onColumnWidthsChange,
2595
+ onColumnOrderChange,
2596
+ onColumnPinnedChange,
1238
2597
  viewportStyle: BENCHMARK_VIEWPORT_STYLE,
1239
2598
  viewportHeight: VIEWPORT_HEIGHT
1240
2599
  }
@@ -1254,7 +2613,7 @@ var inspectionColumns = [
1254
2613
  id: "tags",
1255
2614
  header: "Tags",
1256
2615
  widthPx: 200,
1257
- getValue: (row) => row.tags.join(", ")
2616
+ value: (row) => row.tags.join(", ")
1258
2617
  },
1259
2618
  { id: "message", header: "Message", wrap: true, widthPx: 480 }
1260
2619
  ];
@@ -1267,22 +2626,33 @@ function LabeledGridSurface({
1267
2626
  getHeaderCellProps,
1268
2627
  getRowId,
1269
2628
  headerCellClassName,
1270
- interactionState,
2629
+ state,
1271
2630
  labelClassName,
1272
2631
  overscan,
1273
2632
  onSelectedRowIdChange,
2633
+ onSelectionChange,
2634
+ onFocusChange,
1274
2635
  onSortChange,
2636
+ onColumnWidthsChange,
2637
+ onColumnOrderChange,
2638
+ onColumnPinnedChange,
1275
2639
  onTelemetryChange,
1276
2640
  pinnedClassName,
1277
2641
  rowClassName,
1278
2642
  rows,
2643
+ rowSelectionColumn,
1279
2644
  selectFocusedRowOnArrowKey,
2645
+ tabBehavior,
2646
+ copyWithHeaders,
2647
+ onCopy,
2648
+ copyToClipboard,
2649
+ messages,
1280
2650
  valueClassName,
1281
2651
  viewportHeight
1282
2652
  }) {
1283
2653
  const getPinnedClassName = (column) => column.pinned === "left" && pinnedClassName ? pinnedClassName : void 0;
1284
2654
  const activeFilterColumns = new Set(
1285
- Object.entries(interactionState?.filters ?? {}).filter(([, value]) => value.trim() !== "").map(([columnId]) => columnId)
2655
+ Object.entries(state?.filters ?? {}).filter(([, value]) => value.trim() !== "").map(([columnId]) => columnId)
1286
2656
  );
1287
2657
  const getFormattedValue = ({
1288
2658
  column,
@@ -1314,10 +2684,15 @@ function LabeledGridSurface({
1314
2684
  ),
1315
2685
  getRowClassName: () => rowClassName,
1316
2686
  getRowId,
1317
- interactionState,
2687
+ state,
1318
2688
  overscan,
1319
2689
  onSelectedRowIdChange,
2690
+ onSelectionChange,
2691
+ onFocusChange,
1320
2692
  onSortChange,
2693
+ onColumnWidthsChange,
2694
+ onColumnOrderChange,
2695
+ onColumnPinnedChange,
1321
2696
  onTelemetryChange,
1322
2697
  renderBodyCell: ({ column, row, value }) => /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1323
2698
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: labelClassName, children: column.header ?? column.id }),
@@ -1332,7 +2707,13 @@ function LabeledGridSurface({
1332
2707
  sortDirection ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "sort-indicator", children: sortDirection === "desc" ? "\u25BC" : "\u25B2" }) : null
1333
2708
  ] }),
1334
2709
  rows,
2710
+ rowSelectionColumn,
1335
2711
  selectFocusedRowOnArrowKey,
2712
+ tabBehavior,
2713
+ copyWithHeaders,
2714
+ onCopy,
2715
+ copyToClipboard,
2716
+ messages,
1336
2717
  viewportHeight
1337
2718
  }
1338
2719
  );
@@ -1369,12 +2750,23 @@ var filterableHeaderProps = {
1369
2750
  function InspectionGrid({
1370
2751
  ariaLabel,
1371
2752
  filterableColumnIds,
1372
- interactionState,
2753
+ state,
1373
2754
  onSelectedRowIdChange,
2755
+ onSelectionChange,
2756
+ onFocusChange,
1374
2757
  onSortChange,
2758
+ onColumnWidthsChange,
2759
+ onColumnOrderChange,
2760
+ onColumnPinnedChange,
1375
2761
  onTelemetryChange,
1376
2762
  overscan,
1377
2763
  rows,
2764
+ rowSelectionColumn,
2765
+ tabBehavior,
2766
+ copyWithHeaders,
2767
+ onCopy,
2768
+ copyToClipboard,
2769
+ messages,
1378
2770
  viewportHeight
1379
2771
  }) {
1380
2772
  const filterableColumns = new Set(filterableColumnIds);
@@ -1389,16 +2781,27 @@ function InspectionGrid({
1389
2781
  getHeaderCellProps: ({ column }) => filterableColumns.has(column.id) ? filterableHeaderProps : void 0,
1390
2782
  getRowId: getInspectionRowId,
1391
2783
  headerCellClassName: "inspection-header-cell",
1392
- interactionState,
2784
+ state,
1393
2785
  labelClassName: "inspection-cell-label",
1394
2786
  overscan,
1395
2787
  onSelectedRowIdChange,
2788
+ onSelectionChange,
2789
+ onFocusChange,
1396
2790
  onSortChange,
2791
+ onColumnWidthsChange,
2792
+ onColumnOrderChange,
2793
+ onColumnPinnedChange,
1397
2794
  onTelemetryChange,
1398
2795
  pinnedClassName: "is-pinned",
1399
2796
  rowClassName: "inspection-row",
1400
2797
  rows,
2798
+ rowSelectionColumn,
1401
2799
  selectFocusedRowOnArrowKey: true,
2800
+ tabBehavior,
2801
+ copyWithHeaders,
2802
+ onCopy,
2803
+ copyToClipboard,
2804
+ messages,
1402
2805
  valueClassName: "inspection-cell-value",
1403
2806
  viewportHeight
1404
2807
  }
@@ -1415,7 +2818,9 @@ exports.InspectionGrid = InspectionGrid;
1415
2818
  exports.LabeledGridSurface = LabeledGridSurface;
1416
2819
  exports.Pretable = Pretable;
1417
2820
  exports.PretableSurface = PretableSurface;
1418
- exports.measureRenderedRowHeight = measureRenderedRowHeight;
2821
+ exports.defaultCoerceForCopy = defaultCoerceForCopy;
2822
+ exports.serializeRangesAsTsv = serializeRangesAsTsv;
1419
2823
  exports.usePretable = usePretable;
1420
- exports.usePretableModel = usePretableModel;
1421
- exports.useResolvedHeights = useResolvedHeights;
2824
+ exports.ɵROW_SELECT_COLUMN_ID = ROW_SELECT_COLUMN_ID;
2825
+ exports.ɵmeasureRenderedRowHeight = measureRenderedRowHeight;
2826
+ exports.ɵuseResolvedHeights = useResolvedHeights;