@zvndev/yable-react 0.2.0 → 0.4.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
@@ -10,6 +10,37 @@ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
10
10
  var React3__default = /*#__PURE__*/_interopDefault(React3);
11
11
 
12
12
  // src/index.ts
13
+ var YableContext = React3.createContext({});
14
+ function useYableDefaults() {
15
+ return React3.useContext(YableContext);
16
+ }
17
+ function YableProvider({
18
+ children,
19
+ defaultColumnDef,
20
+ striped,
21
+ stickyHeader,
22
+ bordered,
23
+ compact,
24
+ theme,
25
+ direction,
26
+ ariaLabel
27
+ }) {
28
+ const tableProps = {};
29
+ if (striped !== void 0) tableProps.striped = striped;
30
+ if (stickyHeader !== void 0) tableProps.stickyHeader = stickyHeader;
31
+ if (bordered !== void 0) tableProps.bordered = bordered;
32
+ if (compact !== void 0) tableProps.compact = compact;
33
+ if (theme !== void 0) tableProps.theme = theme;
34
+ if (direction !== void 0) tableProps.direction = direction;
35
+ if (ariaLabel !== void 0) tableProps.ariaLabel = ariaLabel;
36
+ const value = {
37
+ tableProps: Object.keys(tableProps).length > 0 ? tableProps : void 0,
38
+ defaultColumnDef
39
+ };
40
+ return /* @__PURE__ */ jsxRuntime.jsx(YableContext.Provider, { value, children });
41
+ }
42
+
43
+ // src/useTable.ts
13
44
  function shallowEqual(a, b) {
14
45
  if (a === b) return true;
15
46
  const keysA = Object.keys(a);
@@ -22,6 +53,17 @@ function shallowEqual(a, b) {
22
53
  return true;
23
54
  }
24
55
  function useTable(options) {
56
+ const providerDefaults = useYableDefaults();
57
+ const optionsWithDefaults = React3.useMemo(() => {
58
+ if (!providerDefaults.defaultColumnDef) return options;
59
+ return {
60
+ ...options,
61
+ defaultColumnDef: {
62
+ ...providerDefaults.defaultColumnDef,
63
+ ...options.defaultColumnDef
64
+ }
65
+ };
66
+ }, [options, providerDefaults.defaultColumnDef]);
25
67
  const [state, setState] = React3.useState(() => ({
26
68
  sorting: [],
27
69
  columnFilters: [],
@@ -61,14 +103,14 @@ function useTable(options) {
61
103
  }));
62
104
  const stateRef = React3.useRef(state);
63
105
  stateRef.current = state;
64
- const prevOptionsRef = React3.useRef(options);
106
+ const prevOptionsRef = React3.useRef(optionsWithDefaults);
65
107
  const stableOptions = React3.useMemo(() => {
66
- if (shallowEqual(prevOptionsRef.current, options)) {
108
+ if (shallowEqual(prevOptionsRef.current, optionsWithDefaults)) {
67
109
  return prevOptionsRef.current;
68
110
  }
69
- prevOptionsRef.current = options;
70
- return options;
71
- }, [options]);
111
+ prevOptionsRef.current = optionsWithDefaults;
112
+ return optionsWithDefaults;
113
+ }, [optionsWithDefaults]);
72
114
  const onStateChangeRef = React3.useRef(options.onStateChange);
73
115
  onStateChangeRef.current = options.onStateChange;
74
116
  const resolvedState = React3.useMemo(
@@ -78,17 +120,14 @@ function useTable(options) {
78
120
  }),
79
121
  [state, stableOptions.state]
80
122
  );
81
- const onStateChange = React3.useCallback(
82
- (updater) => {
83
- const latest = onStateChangeRef.current;
84
- if (latest) {
85
- latest(updater);
86
- } else {
87
- setState((prev) => yableCore.functionalUpdate(updater, prev));
88
- }
89
- },
90
- []
91
- );
123
+ const onStateChange = React3.useCallback((updater) => {
124
+ const latest = onStateChangeRef.current;
125
+ if (latest) {
126
+ latest(updater);
127
+ } else {
128
+ setState((prev) => yableCore.functionalUpdate(updater, prev));
129
+ }
130
+ }, []);
92
131
  const resolvedOptions = React3.useMemo(
93
132
  () => ({
94
133
  ...stableOptions,
@@ -101,12 +140,14 @@ function useTable(options) {
101
140
  if (!tableRef.current) {
102
141
  tableRef.current = yableCore.createTable(resolvedOptions);
103
142
  } else {
104
- tableRef.current.setOptions((prev) => ({
105
- ...prev,
106
- ...resolvedOptions,
107
- state: resolvedState,
108
- onStateChange
109
- }));
143
+ tableRef.current.setOptions(
144
+ (prev) => ({
145
+ ...prev,
146
+ ...resolvedOptions,
147
+ state: resolvedState,
148
+ onStateChange
149
+ })
150
+ );
110
151
  }
111
152
  React3.useEffect(() => {
112
153
  return () => {
@@ -117,6 +158,122 @@ function useTable(options) {
117
158
  }, []);
118
159
  return tableRef.current;
119
160
  }
161
+ var DEFAULT_PERSISTED_KEYS = [
162
+ "columnVisibility",
163
+ "columnOrder",
164
+ "columnSizing",
165
+ "columnPinning"
166
+ ];
167
+ function resolveStorage(custom) {
168
+ if (custom) return custom;
169
+ if (typeof window !== "undefined") {
170
+ try {
171
+ return window.localStorage;
172
+ } catch {
173
+ return void 0;
174
+ }
175
+ }
176
+ return void 0;
177
+ }
178
+ function pick(obj, keys) {
179
+ const result = {};
180
+ for (const k of keys) {
181
+ if (k in obj) {
182
+ result[k] = obj[k];
183
+ }
184
+ }
185
+ return result;
186
+ }
187
+ function readState(storage, key, version, persistedKeys) {
188
+ if (!storage) return {};
189
+ try {
190
+ const raw = storage.getItem(key);
191
+ if (!raw) return {};
192
+ const envelope = JSON.parse(raw);
193
+ if (envelope.version !== version) {
194
+ storage.removeItem(key);
195
+ return {};
196
+ }
197
+ return pick(envelope.state, persistedKeys);
198
+ } catch {
199
+ if (typeof console !== "undefined") {
200
+ console.warn(`[yable] Failed to read persisted state for key "${key}"`);
201
+ }
202
+ return {};
203
+ }
204
+ }
205
+ function writeState(storage, key, version, state) {
206
+ if (!storage) return;
207
+ try {
208
+ const envelope = { version, state };
209
+ storage.setItem(key, JSON.stringify(envelope));
210
+ } catch {
211
+ if (typeof console !== "undefined") {
212
+ console.warn(`[yable] Failed to persist state for key "${key}" (storage may be full)`);
213
+ }
214
+ }
215
+ }
216
+ function useTablePersistence(options) {
217
+ const {
218
+ key,
219
+ persistedKeys = DEFAULT_PERSISTED_KEYS,
220
+ debounce: debounceMs = 100,
221
+ version = 0,
222
+ storage: customStorage
223
+ } = options;
224
+ const storage = React3.useMemo(() => resolveStorage(customStorage), [customStorage]);
225
+ const initialState = React3.useMemo(
226
+ () => readState(storage, key, version, persistedKeys),
227
+ // eslint-disable-next-line react-hooks/exhaustive-deps -- intentionally read only on mount
228
+ []
229
+ );
230
+ const [state, setState] = React3.useState(initialState);
231
+ const timerRef = React3.useRef(null);
232
+ const keyRef = React3.useRef(key);
233
+ keyRef.current = key;
234
+ const versionRef = React3.useRef(version);
235
+ versionRef.current = version;
236
+ const persistedKeysRef = React3.useRef(persistedKeys);
237
+ persistedKeysRef.current = persistedKeys;
238
+ const debounceRef = React3.useRef(debounceMs);
239
+ debounceRef.current = debounceMs;
240
+ const storageRef = React3.useRef(storage);
241
+ storageRef.current = storage;
242
+ const onStateChange = React3.useCallback((updater) => {
243
+ setState((prev) => {
244
+ const next = yableCore.functionalUpdate(updater, prev);
245
+ if (timerRef.current !== null) {
246
+ clearTimeout(timerRef.current);
247
+ }
248
+ timerRef.current = setTimeout(() => {
249
+ const sliced = pick(
250
+ next,
251
+ persistedKeysRef.current
252
+ );
253
+ writeState(storageRef.current, keyRef.current, versionRef.current, sliced);
254
+ timerRef.current = null;
255
+ }, debounceRef.current);
256
+ return next;
257
+ });
258
+ }, []);
259
+ React3.useEffect(() => {
260
+ return () => {
261
+ if (timerRef.current !== null) {
262
+ clearTimeout(timerRef.current);
263
+ }
264
+ };
265
+ }, []);
266
+ const clearPersistedState = React3.useCallback(() => {
267
+ const s = storageRef.current;
268
+ if (s) {
269
+ try {
270
+ s.removeItem(keyRef.current);
271
+ } catch {
272
+ }
273
+ }
274
+ }, []);
275
+ return { initialState, state, onStateChange, clearPersistedState };
276
+ }
120
277
  var EMPTY_RESULT = {
121
278
  virtualRows: [],
122
279
  totalHeight: 0,
@@ -130,11 +287,14 @@ function useVirtualization({
130
287
  overscan = 5,
131
288
  estimateRowHeight: _estimateRowHeight,
132
289
  pretextHeights,
133
- pretextPrefixSums
290
+ pretextPrefixSums,
291
+ columnSizingHash
134
292
  }) {
135
293
  const hasPretextHeights = !!(pretextHeights && pretextPrefixSums && pretextHeights.length >= totalRows);
136
294
  const isFixedHeight = typeof rowHeight === "number" && !hasPretextHeights;
137
295
  const heightCacheRef = React3.useRef(/* @__PURE__ */ new Map());
296
+ const heightCacheVersionRef = React3.useRef(0);
297
+ const [heightCacheVersion, setHeightCacheVersion] = React3.useState(0);
138
298
  const getRowHeight = React3.useCallback(
139
299
  (index) => {
140
300
  if (hasPretextHeights) return pretextHeights[index];
@@ -145,7 +305,9 @@ function useVirtualization({
145
305
  heightCacheRef.current.set(index, height);
146
306
  return height;
147
307
  },
148
- [rowHeight, isFixedHeight, hasPretextHeights, pretextHeights]
308
+ // heightCacheVersion forces a new callback identity after cache invalidation
309
+ // eslint-disable-next-line react-hooks/exhaustive-deps
310
+ [rowHeight, isFixedHeight, hasPretextHeights, pretextHeights, heightCacheVersion]
149
311
  );
150
312
  const [scrollState, setScrollState] = React3.useState({ scrollTop: 0, containerHeight: 0 });
151
313
  const rafRef = React3.useRef(null);
@@ -198,6 +360,18 @@ function useVirtualization({
198
360
  heightCacheRef.current.clear();
199
361
  }
200
362
  }, [totalRows, isFixedHeight]);
363
+ React3.useEffect(() => {
364
+ if (!isFixedHeight && columnSizingHash !== void 0) {
365
+ heightCacheRef.current.clear();
366
+ heightCacheVersionRef.current += 1;
367
+ setHeightCacheVersion(heightCacheVersionRef.current);
368
+ }
369
+ }, [columnSizingHash]);
370
+ const invalidateRowHeights = React3.useCallback(() => {
371
+ heightCacheRef.current.clear();
372
+ heightCacheVersionRef.current += 1;
373
+ setHeightCacheVersion(heightCacheVersionRef.current);
374
+ }, []);
201
375
  const scrollTo = React3.useCallback(
202
376
  (index) => {
203
377
  const container = containerRef.current;
@@ -215,7 +389,15 @@ function useVirtualization({
215
389
  container.scrollTop = offset;
216
390
  }
217
391
  },
218
- [containerRef, totalRows, rowHeight, isFixedHeight, getRowHeight, hasPretextHeights, pretextPrefixSums]
392
+ [
393
+ containerRef,
394
+ totalRows,
395
+ rowHeight,
396
+ isFixedHeight,
397
+ getRowHeight,
398
+ hasPretextHeights,
399
+ pretextPrefixSums
400
+ ]
219
401
  );
220
402
  const totalHeight = React3.useMemo(() => {
221
403
  if (totalRows === 0) return 0;
@@ -226,10 +408,18 @@ function useVirtualization({
226
408
  total += getRowHeight(i);
227
409
  }
228
410
  return total;
229
- }, [totalRows, rowHeight, isFixedHeight, getRowHeight, hasPretextHeights, pretextPrefixSums]);
411
+ }, [
412
+ totalRows,
413
+ rowHeight,
414
+ isFixedHeight,
415
+ getRowHeight,
416
+ hasPretextHeights,
417
+ pretextPrefixSums,
418
+ heightCacheVersion
419
+ ]);
230
420
  const { scrollTop, containerHeight } = scrollState;
231
421
  if (totalRows === 0) {
232
- return { ...EMPTY_RESULT, scrollTo };
422
+ return { ...EMPTY_RESULT, scrollTo, invalidateRowHeights };
233
423
  }
234
424
  if (containerHeight === 0) {
235
425
  return {
@@ -237,7 +427,8 @@ function useVirtualization({
237
427
  totalHeight,
238
428
  startIndex: 0,
239
429
  endIndex: 0,
240
- scrollTo
430
+ scrollTo,
431
+ invalidateRowHeights
241
432
  };
242
433
  }
243
434
  let startIndex = 0;
@@ -269,10 +460,7 @@ function useVirtualization({
269
460
  } else if (isFixedHeight) {
270
461
  const fixedH = rowHeight;
271
462
  startIndex = Math.floor(scrollTop / fixedH);
272
- endIndex = Math.min(
273
- totalRows - 1,
274
- Math.ceil((scrollTop + containerHeight) / fixedH) - 1
275
- );
463
+ endIndex = Math.min(totalRows - 1, Math.ceil((scrollTop + containerHeight) / fixedH) - 1);
276
464
  } else {
277
465
  let accum = 0;
278
466
  let foundStart = false;
@@ -336,7 +524,164 @@ function useVirtualization({
336
524
  totalHeight,
337
525
  startIndex: overscanStart,
338
526
  endIndex: overscanEnd,
339
- scrollTo
527
+ scrollTo,
528
+ invalidateRowHeights
529
+ };
530
+ }
531
+ function binarySearchOffsets(offsets, target) {
532
+ let low = 0;
533
+ let high = offsets.length - 1;
534
+ while (low < high) {
535
+ const mid = low + high >>> 1;
536
+ if (offsets[mid + 1] <= target) {
537
+ low = mid + 1;
538
+ } else {
539
+ high = mid;
540
+ }
541
+ }
542
+ return low;
543
+ }
544
+ function useColumnVirtualization({
545
+ containerRef,
546
+ columns,
547
+ overscan = 2,
548
+ enabled = true
549
+ }) {
550
+ const [scrollState, setScrollState] = React3.useState({
551
+ scrollLeft: 0,
552
+ containerWidth: 0
553
+ });
554
+ const rafRef = React3.useRef(null);
555
+ const sizes = React3.useMemo(() => columns.map((column) => column.getSize()), [columns]);
556
+ const offsets = React3.useMemo(() => {
557
+ const next = new Array(columns.length + 1);
558
+ next[0] = 0;
559
+ for (let i = 0; i < columns.length; i++) {
560
+ next[i + 1] = next[i] + (sizes[i] ?? 0);
561
+ }
562
+ return next;
563
+ }, [columns.length, sizes]);
564
+ const totalWidth = offsets[offsets.length - 1] ?? 0;
565
+ React3.useEffect(() => {
566
+ const container = containerRef.current;
567
+ if (!container) return;
568
+ setScrollState({
569
+ scrollLeft: container.scrollLeft,
570
+ containerWidth: container.clientWidth
571
+ });
572
+ const handleScroll = () => {
573
+ if (rafRef.current !== null) return;
574
+ rafRef.current = requestAnimationFrame(() => {
575
+ rafRef.current = null;
576
+ const el = containerRef.current;
577
+ if (!el) return;
578
+ setScrollState({
579
+ scrollLeft: el.scrollLeft,
580
+ containerWidth: el.clientWidth
581
+ });
582
+ });
583
+ };
584
+ container.addEventListener("scroll", handleScroll, { passive: true });
585
+ let resizeObserver;
586
+ if (typeof ResizeObserver !== "undefined") {
587
+ resizeObserver = new ResizeObserver(() => {
588
+ const el = containerRef.current;
589
+ if (!el) return;
590
+ setScrollState((prev) => {
591
+ const nextWidth = el.clientWidth;
592
+ if (prev.containerWidth === nextWidth && prev.scrollLeft === el.scrollLeft) {
593
+ return prev;
594
+ }
595
+ return {
596
+ scrollLeft: el.scrollLeft,
597
+ containerWidth: nextWidth
598
+ };
599
+ });
600
+ });
601
+ resizeObserver.observe(container);
602
+ }
603
+ return () => {
604
+ container.removeEventListener("scroll", handleScroll);
605
+ if (rafRef.current !== null) {
606
+ cancelAnimationFrame(rafRef.current);
607
+ rafRef.current = null;
608
+ }
609
+ resizeObserver?.disconnect();
610
+ };
611
+ }, [containerRef]);
612
+ const scrollToIndex = React3.useCallback(
613
+ (index) => {
614
+ const container = containerRef.current;
615
+ if (!container || columns.length === 0) return;
616
+ const clampedIndex = Math.max(0, Math.min(index, columns.length - 1));
617
+ container.scrollLeft = offsets[clampedIndex] ?? 0;
618
+ },
619
+ [columns.length, containerRef, offsets]
620
+ );
621
+ if (!enabled || columns.length === 0) {
622
+ return {
623
+ virtualColumns: columns.map((column, index) => ({
624
+ column,
625
+ index,
626
+ start: offsets[index] ?? 0,
627
+ size: sizes[index] ?? 0
628
+ })),
629
+ startOffset: 0,
630
+ endOffset: 0,
631
+ totalWidth,
632
+ visibleWidth: totalWidth,
633
+ startIndex: 0,
634
+ endIndex: Math.max(columns.length - 1, 0),
635
+ isVirtualized: false,
636
+ scrollToIndex
637
+ };
638
+ }
639
+ const { scrollLeft, containerWidth } = scrollState;
640
+ if (containerWidth <= 0 || totalWidth <= containerWidth) {
641
+ return {
642
+ virtualColumns: columns.map((column, index) => ({
643
+ column,
644
+ index,
645
+ start: offsets[index] ?? 0,
646
+ size: sizes[index] ?? 0
647
+ })),
648
+ startOffset: 0,
649
+ endOffset: 0,
650
+ totalWidth,
651
+ visibleWidth: totalWidth,
652
+ startIndex: 0,
653
+ endIndex: Math.max(columns.length - 1, 0),
654
+ isVirtualized: false,
655
+ scrollToIndex
656
+ };
657
+ }
658
+ const startIndex = binarySearchOffsets(offsets, scrollLeft);
659
+ const endBoundary = scrollLeft + containerWidth;
660
+ const endIndex = binarySearchOffsets(offsets, Math.max(scrollLeft, endBoundary - 1));
661
+ const overscanStart = Math.max(0, startIndex - overscan);
662
+ const overscanEnd = Math.min(columns.length - 1, endIndex + overscan);
663
+ const virtualColumns = [];
664
+ for (let i = overscanStart; i <= overscanEnd; i++) {
665
+ virtualColumns.push({
666
+ column: columns[i],
667
+ index: i,
668
+ start: offsets[i] ?? 0,
669
+ size: sizes[i] ?? 0
670
+ });
671
+ }
672
+ const startOffset = offsets[overscanStart] ?? 0;
673
+ const visibleWidth = (offsets[overscanEnd + 1] ?? totalWidth) - startOffset;
674
+ const endOffset = totalWidth - (offsets[overscanEnd + 1] ?? totalWidth);
675
+ return {
676
+ virtualColumns,
677
+ startOffset,
678
+ endOffset,
679
+ totalWidth,
680
+ visibleWidth,
681
+ startIndex: overscanStart,
682
+ endIndex: overscanEnd,
683
+ isVirtualized: true,
684
+ scrollToIndex
340
685
  };
341
686
  }
342
687
  var pretextPromise = null;
@@ -389,7 +734,12 @@ function usePretextMeasurement({
389
734
  }
390
735
  prepareTimeRef.current = performance.now() - start;
391
736
  return result;
392
- }, [pretext, enabled, data, columns.map((c) => `${c.columnId}:${c.font}:${c.fixedHeight ? "F" : "T"}`).join("|")]);
737
+ }, [
738
+ pretext,
739
+ enabled,
740
+ data,
741
+ columns.map((c) => `${c.columnId}:${c.font}:${c.fixedHeight ? "F" : "T"}`).join("|")
742
+ ]);
393
743
  const measurement = React3.useMemo(() => {
394
744
  if (!pretext || !preparedCells) {
395
745
  return { rowHeights: null, prefixSums: null, totalHeight: 0 };
@@ -427,6 +777,7 @@ function usePretextMeasurement({
427
777
  return {
428
778
  rowHeights,
429
779
  prefixSums,
780
+ // Safe: prefixSums has length data.length + 1, so [data.length] always exists
430
781
  totalHeight: prefixSums[data.length]
431
782
  };
432
783
  }, [pretext, preparedCells, columnWidthsKey, minRowHeight, data.length]);
@@ -691,18 +1042,10 @@ function CellDate({
691
1042
  if (raw == null) return null;
692
1043
  const date = raw instanceof Date ? raw : new Date(String(raw));
693
1044
  if (isNaN(date.getTime())) return /* @__PURE__ */ jsxRuntime.jsx("span", { className: "yable-cell-date", children: String(raw) });
694
- const formatter = React3.useMemo(() => {
695
- if (typeof format === "string" && format !== "relative") {
696
- return new Intl.DateTimeFormat(locale, {
697
- ...PRESETS[format],
698
- timeZone: "UTC"
699
- });
700
- }
701
- if (typeof format === "object") {
702
- return new Intl.DateTimeFormat(locale, { ...format, timeZone: "UTC" });
703
- }
704
- return null;
705
- }, [format, locale]);
1045
+ const formatter = typeof format === "string" && format !== "relative" ? new Intl.DateTimeFormat(locale, {
1046
+ ...PRESETS[format],
1047
+ timeZone: "UTC"
1048
+ }) : typeof format === "object" ? new Intl.DateTimeFormat(locale, { ...format, timeZone: "UTC" }) : null;
706
1049
  const formatted = format === "relative" ? formatRelative(date) : formatter ? formatter.format(date) : date.toLocaleDateString(locale);
707
1050
  return /* @__PURE__ */ jsxRuntime.jsx("span", { className: `yable-cell-date ${className ?? ""}`, title: date.toISOString(), children: formatted });
708
1051
  }
@@ -713,8 +1056,14 @@ var measureRecipe9 = {
713
1056
  padding: 20
714
1057
  };
715
1058
  function isSafeUrl(url) {
716
- const normalized = String(url).toLowerCase().trim();
717
- return !normalized.startsWith("javascript:") && !normalized.startsWith("data:text/html") && !normalized.startsWith("vbscript:");
1059
+ const normalized = String(url).trim();
1060
+ if (/^[/#?]/.test(normalized)) return true;
1061
+ try {
1062
+ const parsed = new URL(normalized, "https://placeholder.invalid");
1063
+ return ["http:", "https:", "mailto:", "tel:"].includes(parsed.protocol);
1064
+ } catch {
1065
+ return false;
1066
+ }
718
1067
  }
719
1068
  function CellLink({
720
1069
  context,
@@ -891,7 +1240,7 @@ function useTableContext() {
891
1240
  const ctx = React3.useContext(TableContext);
892
1241
  if (!ctx) {
893
1242
  throw new Error(
894
- "[yable] useTableContext must be used within a <Table> component. Did you forget to pass the `table` prop?"
1243
+ "[yable E001] useTableContext must be used within a <Table> component. Did you forget to pass the `table` prop?"
895
1244
  );
896
1245
  }
897
1246
  return ctx;
@@ -941,12 +1290,234 @@ function SortIndicator({ direction, index }) {
941
1290
  }
942
1291
  );
943
1292
  }
1293
+ function formatValue(value) {
1294
+ if (value == null || value === "") return "(empty)";
1295
+ if (typeof value === "string") return value;
1296
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
1297
+ if (value instanceof Date) return value.toISOString();
1298
+ return JSON.stringify(value);
1299
+ }
1300
+ function SetFilter({ column, className }) {
1301
+ const [open, setOpen] = React3.useState(false);
1302
+ const filterValue = column.getFilterValue();
1303
+ const selectedValues = Array.isArray(filterValue) ? filterValue : filterValue == null || filterValue === "" ? [] : [filterValue];
1304
+ const facetedUniqueValues = column.getFacetedUniqueValues();
1305
+ const options = Array.from(facetedUniqueValues.entries()).map(([value, count]) => ({
1306
+ value,
1307
+ count,
1308
+ label: formatValue(value)
1309
+ })).sort((a, b) => a.label.localeCompare(b.label));
1310
+ const selectedSet = new Set(selectedValues);
1311
+ const toggleValue = (value) => {
1312
+ const next = new Set(selectedSet);
1313
+ if (next.has(value)) {
1314
+ next.delete(value);
1315
+ } else {
1316
+ next.add(value);
1317
+ }
1318
+ const nextValues = Array.from(next);
1319
+ column.setFilterValue(nextValues.length > 0 ? nextValues : void 0);
1320
+ };
1321
+ const headerLabel = typeof column.columnDef.header === "string" ? column.columnDef.header : column.id;
1322
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1323
+ "div",
1324
+ {
1325
+ className: ["yable-set-filter", className].filter(Boolean).join(" "),
1326
+ style: { position: "relative" },
1327
+ children: [
1328
+ /* @__PURE__ */ jsxRuntime.jsx(
1329
+ "button",
1330
+ {
1331
+ type: "button",
1332
+ className: "yable-set-filter-trigger",
1333
+ "aria-haspopup": "dialog",
1334
+ "aria-expanded": open,
1335
+ onClick: () => setOpen((prev) => !prev),
1336
+ style: {
1337
+ width: "100%",
1338
+ minHeight: 28,
1339
+ padding: "4px 8px",
1340
+ border: "1px solid rgba(127, 127, 127, 0.35)",
1341
+ borderRadius: 6,
1342
+ background: "transparent",
1343
+ font: "inherit",
1344
+ textAlign: "left",
1345
+ cursor: "pointer"
1346
+ },
1347
+ children: selectedValues.length > 0 ? `${selectedValues.length} selected` : `Filter ${headerLabel}`
1348
+ }
1349
+ ),
1350
+ open && /* @__PURE__ */ jsxRuntime.jsxs(
1351
+ "div",
1352
+ {
1353
+ role: "dialog",
1354
+ "aria-label": `${headerLabel} set filter`,
1355
+ className: "yable-set-filter-popover",
1356
+ style: {
1357
+ position: "absolute",
1358
+ insetInlineStart: 0,
1359
+ top: "calc(100% + 4px)",
1360
+ zIndex: 20,
1361
+ width: 220,
1362
+ maxHeight: 240,
1363
+ overflow: "auto",
1364
+ padding: 8,
1365
+ border: "1px solid rgba(127, 127, 127, 0.35)",
1366
+ borderRadius: 8,
1367
+ background: "var(--yable-color-bg, #fff)",
1368
+ boxShadow: "0 10px 30px rgba(0, 0, 0, 0.12)"
1369
+ },
1370
+ children: [
1371
+ /* @__PURE__ */ jsxRuntime.jsxs(
1372
+ "div",
1373
+ {
1374
+ style: {
1375
+ display: "flex",
1376
+ alignItems: "center",
1377
+ justifyContent: "space-between",
1378
+ gap: 8,
1379
+ marginBottom: 8
1380
+ },
1381
+ children: [
1382
+ /* @__PURE__ */ jsxRuntime.jsx("strong", { style: { fontSize: 12 }, children: headerLabel }),
1383
+ selectedValues.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(
1384
+ "button",
1385
+ {
1386
+ type: "button",
1387
+ onClick: () => column.setFilterValue(void 0),
1388
+ style: {
1389
+ border: "none",
1390
+ background: "transparent",
1391
+ padding: 0,
1392
+ cursor: "pointer",
1393
+ fontSize: 12
1394
+ },
1395
+ children: "Clear"
1396
+ }
1397
+ )
1398
+ ]
1399
+ }
1400
+ ),
1401
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { role: "group", "aria-label": `${headerLabel} options`, children: [
1402
+ options.length === 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 12, opacity: 0.75 }, children: "No values" }),
1403
+ options.map((option) => {
1404
+ const checked = selectedSet.has(option.value);
1405
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1406
+ "label",
1407
+ {
1408
+ style: {
1409
+ display: "flex",
1410
+ alignItems: "center",
1411
+ gap: 8,
1412
+ padding: "4px 0",
1413
+ fontSize: 12
1414
+ },
1415
+ children: [
1416
+ /* @__PURE__ */ jsxRuntime.jsx(
1417
+ "input",
1418
+ {
1419
+ type: "checkbox",
1420
+ checked,
1421
+ onChange: () => toggleValue(option.value)
1422
+ }
1423
+ ),
1424
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { flex: 1 }, children: option.label }),
1425
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { opacity: 0.6 }, children: option.count })
1426
+ ]
1427
+ },
1428
+ `${option.label}-${option.count}`
1429
+ );
1430
+ })
1431
+ ] })
1432
+ ]
1433
+ }
1434
+ )
1435
+ ]
1436
+ }
1437
+ );
1438
+ }
1439
+ function isSetCompatibleValue(value) {
1440
+ return value == null || typeof value === "string" || typeof value === "number" || typeof value === "boolean";
1441
+ }
1442
+ function inferFilterVariant(column) {
1443
+ const meta = column.columnDef.meta ?? {};
1444
+ if (meta.filterVariant) return meta.filterVariant;
1445
+ const uniqueValues = column.getFacetedUniqueValues();
1446
+ if (uniqueValues.size > 0 && uniqueValues.size <= 12) {
1447
+ const allValuesSupported = Array.from(uniqueValues.keys()).every(isSetCompatibleValue);
1448
+ if (allValuesSupported) return "set";
1449
+ }
1450
+ return "text";
1451
+ }
1452
+ function FloatingFilter({ column }) {
1453
+ const variant = inferFilterVariant(column);
1454
+ if (!column.getCanFilter()) {
1455
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { style: { minHeight: 28 }, "aria-hidden": "true" });
1456
+ }
1457
+ if (variant === "set") {
1458
+ return /* @__PURE__ */ jsxRuntime.jsx(SetFilter, { column });
1459
+ }
1460
+ const filterValue = column.getFilterValue();
1461
+ const normalizedValue = typeof filterValue === "string" || typeof filterValue === "number" ? String(filterValue) : "";
1462
+ const label = typeof column.columnDef.header === "string" ? column.columnDef.header : column.id;
1463
+ return /* @__PURE__ */ jsxRuntime.jsx("label", { style: { display: "block" }, children: /* @__PURE__ */ jsxRuntime.jsx(
1464
+ "input",
1465
+ {
1466
+ "aria-label": `Filter ${label}`,
1467
+ className: "yable-floating-filter-input",
1468
+ value: normalizedValue,
1469
+ onChange: (event) => {
1470
+ const nextValue = event.target.value;
1471
+ column.setFilterValue(nextValue === "" ? void 0 : nextValue);
1472
+ },
1473
+ placeholder: `Filter ${label}`,
1474
+ style: {
1475
+ width: "100%",
1476
+ minHeight: 28,
1477
+ padding: "4px 8px",
1478
+ border: "1px solid rgba(127, 127, 127, 0.35)",
1479
+ borderRadius: 6,
1480
+ background: "transparent",
1481
+ font: "inherit"
1482
+ }
1483
+ }
1484
+ ) });
1485
+ }
944
1486
  var DRAG_MIME = "application/yable-column";
945
1487
  function TableHeader({
946
- table
1488
+ table,
1489
+ floatingFilters = false
947
1490
  }) {
948
1491
  const headerGroups = table.getHeaderGroups();
949
- return /* @__PURE__ */ jsxRuntime.jsx("thead", { className: "yable-thead", children: headerGroups.map((headerGroup) => /* @__PURE__ */ jsxRuntime.jsx("tr", { className: "yable-header-row", children: headerGroup.headers.map((header) => /* @__PURE__ */ jsxRuntime.jsx(HeaderCell, { header, table }, header.id)) }, headerGroup.id)) });
1492
+ const visibleColumns = table.getVisibleLeafColumns();
1493
+ return /* @__PURE__ */ jsxRuntime.jsxs("thead", { className: "yable-thead", children: [
1494
+ headerGroups.map((headerGroup) => /* @__PURE__ */ jsxRuntime.jsx("tr", { className: "yable-header-row", children: headerGroup.headers.map((header) => /* @__PURE__ */ jsxRuntime.jsx(HeaderCell, { header, table }, header.id)) }, headerGroup.id)),
1495
+ floatingFilters && visibleColumns.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("tr", { className: "yable-header-row yable-header-row--filters", children: visibleColumns.map((column) => /* @__PURE__ */ jsxRuntime.jsx(FloatingFilterCell, { column }, `${column.id}-filter`)) })
1496
+ ] });
1497
+ }
1498
+ function FloatingFilterCell({
1499
+ column
1500
+ }) {
1501
+ const style = React3.useMemo(() => {
1502
+ const next = {
1503
+ width: column.getSize(),
1504
+ minWidth: column.columnDef.minSize,
1505
+ maxWidth: column.columnDef.maxSize,
1506
+ padding: 6,
1507
+ verticalAlign: "top"
1508
+ };
1509
+ const pinned = column.getIsPinned();
1510
+ if (pinned) {
1511
+ next.position = "sticky";
1512
+ if (pinned === "left") {
1513
+ next.left = column.getStart("left");
1514
+ } else {
1515
+ next.right = column.getStart("right");
1516
+ }
1517
+ }
1518
+ return next;
1519
+ }, [column]);
1520
+ return /* @__PURE__ */ jsxRuntime.jsx("th", { className: "yable-th yable-th--filter", role: "columnheader", style, children: /* @__PURE__ */ jsxRuntime.jsx(FloatingFilter, { column }) });
950
1521
  }
951
1522
  function HeaderCell({
952
1523
  header,
@@ -1000,11 +1571,6 @@ function HeaderCell({
1000
1571
  const handleDragStart = React3.useCallback(
1001
1572
  (e) => {
1002
1573
  if (!canReorder) return;
1003
- const target = e.target;
1004
- if (target && target.closest(".yable-resize-handle")) {
1005
- e.preventDefault();
1006
- return;
1007
- }
1008
1574
  e.stopPropagation();
1009
1575
  e.dataTransfer.effectAllowed = "move";
1010
1576
  try {
@@ -1012,8 +1578,9 @@ function HeaderCell({
1012
1578
  e.dataTransfer.setData("text/plain", column.id);
1013
1579
  } catch {
1014
1580
  }
1581
+ table.setColumnDragActive(true);
1015
1582
  },
1016
- [canReorder, column.id]
1583
+ [canReorder, column.id, table]
1017
1584
  );
1018
1585
  const handleDragOver = React3.useCallback(
1019
1586
  (e) => {
@@ -1035,17 +1602,15 @@ function HeaderCell({
1035
1602
  },
1036
1603
  [canReorder]
1037
1604
  );
1038
- const handleDragLeave = React3.useCallback(
1039
- (e) => {
1040
- const next = e.relatedTarget;
1041
- if (next && e.currentTarget.contains(next)) return;
1042
- setDragOver(null);
1043
- },
1044
- []
1045
- );
1046
- const handleDragEnd = React3.useCallback(() => {
1605
+ const handleDragLeave = React3.useCallback((e) => {
1606
+ const next = e.relatedTarget;
1607
+ if (next && e.currentTarget.contains(next)) return;
1047
1608
  setDragOver(null);
1048
1609
  }, []);
1610
+ const handleDragEnd = React3.useCallback(() => {
1611
+ setDragOver(null);
1612
+ table.setColumnDragActive(false);
1613
+ }, [table]);
1049
1614
  const handleDrop = React3.useCallback(
1050
1615
  (e) => {
1051
1616
  if (!canReorder) return;
@@ -1055,6 +1620,7 @@ function HeaderCell({
1055
1620
  const rect = e.currentTarget.getBoundingClientRect();
1056
1621
  const insertAfter = e.clientX >= rect.left + rect.width / 2;
1057
1622
  setDragOver(null);
1623
+ table.setColumnDragActive(false);
1058
1624
  if (!sourceId || sourceId === column.id) return;
1059
1625
  const state = table.getState();
1060
1626
  const allLeafs = table.getAllLeafColumns();
@@ -1105,24 +1671,24 @@ function HeaderCell({
1105
1671
  "aria-sort": sortDirection === "asc" ? "ascending" : sortDirection === "desc" ? "descending" : canSort ? "none" : void 0,
1106
1672
  role: "columnheader",
1107
1673
  colSpan: header.colSpan,
1108
- draggable: canReorder || void 0,
1109
1674
  onClick: handleHeaderClick,
1110
- onDragStart: canReorder ? handleDragStart : void 0,
1111
1675
  onDragOver: canReorder ? handleDragOver : void 0,
1112
1676
  onDragLeave: canReorder ? handleDragLeave : void 0,
1113
- onDragEnd: canReorder ? handleDragEnd : void 0,
1114
1677
  onDrop: canReorder ? handleDrop : void 0,
1115
1678
  children: [
1116
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "yable-th-content", children: [
1117
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: headerContent }),
1118
- canSort && /* @__PURE__ */ jsxRuntime.jsx(
1119
- SortIndicator,
1120
- {
1121
- direction: sortDirection,
1122
- index: sortIndex > 0 ? sortIndex : void 0
1123
- }
1124
- )
1125
- ] }),
1679
+ /* @__PURE__ */ jsxRuntime.jsxs(
1680
+ "div",
1681
+ {
1682
+ className: "yable-th-content",
1683
+ draggable: canReorder || void 0,
1684
+ onDragStart: canReorder ? handleDragStart : void 0,
1685
+ onDragEnd: canReorder ? handleDragEnd : void 0,
1686
+ children: [
1687
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: headerContent }),
1688
+ canSort && /* @__PURE__ */ jsxRuntime.jsx(SortIndicator, { direction: sortDirection, index: sortIndex > 0 ? sortIndex : void 0 })
1689
+ ]
1690
+ }
1691
+ ),
1126
1692
  canResize && /* @__PURE__ */ jsxRuntime.jsx(
1127
1693
  "div",
1128
1694
  {
@@ -1130,9 +1696,7 @@ function HeaderCell({
1130
1696
  "data-resizing": column.getIsResizing() || void 0,
1131
1697
  onMouseDown: startResize,
1132
1698
  onTouchStart: startResize,
1133
- onClick: handleResizeClick,
1134
- draggable: false,
1135
- onDragStart: (e) => e.preventDefault()
1699
+ onClick: handleResizeClick
1136
1700
  }
1137
1701
  )
1138
1702
  ]
@@ -1246,6 +1810,10 @@ function TableCell({
1246
1810
  const cellStatus = table.getCellStatus(cell.row.id, column.id);
1247
1811
  const cellErrorMessage = table.getCellErrorMessage(cell.row.id, column.id);
1248
1812
  const cellConflictWith = table.getCellConflictWith(cell.row.id, column.id);
1813
+ const selectionRange = table.getCellSelectionRange();
1814
+ const selectionEdges = table.getCellSelectionEdges(rowIndex, columnIndex);
1815
+ const isCellSelected = selectionEdges !== null;
1816
+ const isMultiCellSelection = selectionRange !== null && (selectionRange.start.rowIndex !== selectionRange.end.rowIndex || selectionRange.start.columnIndex !== selectionRange.end.columnIndex);
1249
1817
  const overrideValue = cellStatus !== "idle" ? table.getCellRenderValue(cell.row.id, column.id) : void 0;
1250
1818
  let content;
1251
1819
  const cellDef = column.columnDef.cell;
@@ -1269,23 +1837,17 @@ function TableCell({
1269
1837
  }
1270
1838
  const handleClick = React3.useCallback(
1271
1839
  (e) => {
1272
- table.setFocusedCell({ rowIndex, columnIndex });
1273
- const clickTarget = e.target;
1274
- if (!isInteractiveClickTarget(clickTarget)) {
1275
- const currentTarget = e.currentTarget;
1276
- currentTarget.focus({ preventScroll: true });
1277
- }
1278
1840
  table.events.emit("cell:click", {
1279
1841
  cell,
1280
1842
  row: cell.row,
1281
1843
  column: cell.column,
1282
1844
  event: e.nativeEvent
1283
1845
  });
1284
- if (yableCore.canCellEnterEditMode(table, cell.row, column) && !isAlwaysEditable && !isEditing) {
1846
+ if (yableCore.canCellEnterEditMode(table, cell.row, column) && !isAlwaysEditable && !isEditing && !isMultiCellSelection && !e.shiftKey && !e.metaKey && !e.ctrlKey) {
1285
1847
  table.startEditing(cell.row.id, column.id);
1286
1848
  }
1287
1849
  },
1288
- [cell, column, columnIndex, isAlwaysEditable, isEditing, rowIndex, table]
1850
+ [cell, column, isAlwaysEditable, isEditing, isMultiCellSelection, table]
1289
1851
  );
1290
1852
  const handleDoubleClick = React3.useCallback(
1291
1853
  (e) => {
@@ -1313,15 +1875,46 @@ function TableCell({
1313
1875
  if (!keyboardNavigationEnabled) return;
1314
1876
  table.setFocusedCell({ rowIndex, columnIndex });
1315
1877
  }, [columnIndex, keyboardNavigationEnabled, rowIndex, table]);
1878
+ const handleMouseDown = React3.useCallback(
1879
+ (e) => {
1880
+ if (e.button !== 0) return;
1881
+ const clickTarget = e.target;
1882
+ if (isInteractiveClickTarget(clickTarget)) return;
1883
+ e.preventDefault();
1884
+ table.setFocusedCell({ rowIndex, columnIndex });
1885
+ table.startCellRangeSelection({ rowIndex, columnIndex }, { extend: e.shiftKey });
1886
+ e.currentTarget.focus({ preventScroll: true });
1887
+ },
1888
+ [columnIndex, rowIndex, table]
1889
+ );
1890
+ const handleMouseEnter = React3.useCallback(() => {
1891
+ if (!table.getState().cellSelection?.isDragging) return;
1892
+ table.updateCellRangeSelection({ rowIndex, columnIndex });
1893
+ }, [columnIndex, rowIndex, table]);
1894
+ const handleMouseUp = React3.useCallback(() => {
1895
+ if (!table.getState().cellSelection?.isDragging) return;
1896
+ table.endCellRangeSelection();
1897
+ }, [table]);
1898
+ const cellClassNameDef = column.columnDef.cellClassName;
1899
+ const userClassName = typeof cellClassNameDef === "function" ? cellClassNameDef(cell.getContext()) : cellClassNameDef;
1900
+ const cellStyleDef = column.columnDef.cellStyle;
1901
+ const userStyle = typeof cellStyleDef === "function" ? cellStyleDef(cell.getContext()) : cellStyleDef;
1902
+ const mergedStyle = userStyle ? { ...style, ...userStyle } : style;
1316
1903
  const classNames = [
1317
1904
  "yable-td",
1318
- isFocused && "yable-cell--focused"
1905
+ isFocused && "yable-cell--focused",
1906
+ isCellSelected && "yable-cell--selected",
1907
+ selectionEdges?.top && "yable-cell--selection-top",
1908
+ selectionEdges?.right && "yable-cell--selection-right",
1909
+ selectionEdges?.bottom && "yable-cell--selection-bottom",
1910
+ selectionEdges?.left && "yable-cell--selection-left",
1911
+ userClassName
1319
1912
  ].filter(Boolean).join(" ");
1320
1913
  return /* @__PURE__ */ jsxRuntime.jsxs(
1321
1914
  "td",
1322
1915
  {
1323
1916
  className: classNames,
1324
- style,
1917
+ style: mergedStyle,
1325
1918
  "data-editing": isEditing || void 0,
1326
1919
  "data-focused": isFocused || void 0,
1327
1920
  "data-pinned": pinned || void 0,
@@ -1329,10 +1922,14 @@ function TableCell({
1329
1922
  "data-column-id": column.id,
1330
1923
  "data-row-index": rowIndex,
1331
1924
  "data-column-index": columnIndex,
1925
+ "data-cell-selected": isCellSelected || void 0,
1332
1926
  "aria-rowindex": rowIndex + 1,
1333
1927
  "aria-colindex": columnIndex + 1,
1334
1928
  role: "gridcell",
1335
1929
  tabIndex: keyboardNavigationEnabled ? isTabStop ? 0 : -1 : void 0,
1930
+ onMouseDown: handleMouseDown,
1931
+ onMouseEnter: handleMouseEnter,
1932
+ onMouseUp: handleMouseUp,
1336
1933
  onClick: handleClick,
1337
1934
  onDoubleClick: handleDoubleClick,
1338
1935
  onContextMenu: handleContextMenu,
@@ -1459,12 +2056,17 @@ var CellErrorBoundary = class extends React3__default.default.Component {
1459
2056
  };
1460
2057
  function TableBody({
1461
2058
  table,
1462
- clickableRows
2059
+ clickableRows,
2060
+ colgroup
1463
2061
  }) {
1464
2062
  const rows = table.getRowModel().rows;
1465
2063
  const visibleColumns = table.getVisibleLeafColumns();
1466
2064
  const activeCell = table.getState().editing.activeCell;
1467
2065
  const focusedCell = table.getFocusedCell();
2066
+ const cellSelection = table.getState().cellSelection ?? {
2067
+ range: null,
2068
+ isDragging: false
2069
+ };
1468
2070
  const options = table.options;
1469
2071
  const enableVirtualization = options.enableVirtualization ?? false;
1470
2072
  const scrollContainerRef = React3.useRef(null);
@@ -1473,6 +2075,19 @@ function TableBody({
1473
2075
  const estimateRowHeight = options.estimateRowHeight;
1474
2076
  const pretextHeights = options.pretextHeights ?? null;
1475
2077
  const pretextPrefixSums = options.pretextPrefixSums ?? null;
2078
+ const columnSizing = table.getState().columnSizing;
2079
+ const columnSizingHash = React3.useMemo(() => {
2080
+ const entries = Object.entries(columnSizing);
2081
+ if (entries.length === 0) return 0;
2082
+ let h = 0;
2083
+ for (const [key, value] of entries) {
2084
+ for (let i = 0; i < key.length; i++) {
2085
+ h = h * 31 + key.charCodeAt(i) | 0;
2086
+ }
2087
+ h = h * 31 + (value | 0) | 0;
2088
+ }
2089
+ return h;
2090
+ }, [columnSizing]);
1476
2091
  const { virtualRows, totalHeight } = useVirtualization({
1477
2092
  containerRef: scrollContainerRef,
1478
2093
  totalRows: rows.length,
@@ -1480,8 +2095,21 @@ function TableBody({
1480
2095
  overscan,
1481
2096
  estimateRowHeight,
1482
2097
  pretextHeights,
1483
- pretextPrefixSums
2098
+ pretextPrefixSums,
2099
+ columnSizingHash
1484
2100
  });
2101
+ const cellSelectionKey = cellSelection.range ? `${cellSelection.range.start.rowIndex}:${cellSelection.range.start.columnIndex}:${cellSelection.range.end.rowIndex}:${cellSelection.range.end.columnIndex}:${cellSelection.isDragging ? "dragging" : "idle"}` : `none:${cellSelection.isDragging ? "dragging" : "idle"}`;
2102
+ React3.useEffect(() => {
2103
+ const handleWindowMouseUp = () => {
2104
+ if (table.getState().cellSelection?.isDragging) {
2105
+ table.endCellRangeSelection();
2106
+ }
2107
+ };
2108
+ window.addEventListener("mouseup", handleWindowMouseUp);
2109
+ return () => {
2110
+ window.removeEventListener("mouseup", handleWindowMouseUp);
2111
+ };
2112
+ }, [table]);
1485
2113
  if (!enableVirtualization) {
1486
2114
  return /* @__PURE__ */ jsxRuntime.jsx("tbody", { className: "yable-tbody", children: rows.map((row, rowIndex) => /* @__PURE__ */ jsxRuntime.jsx(
1487
2115
  MemoizedTableRow,
@@ -1495,6 +2123,7 @@ function TableBody({
1495
2123
  activeColumnId: activeCell?.rowId === row.id ? activeCell.columnId : void 0,
1496
2124
  focusedColumnIndex: focusedCell?.rowIndex === rowIndex ? focusedCell.columnIndex : null,
1497
2125
  hasFocusedCell: focusedCell !== null,
2126
+ cellSelectionKey,
1498
2127
  clickable: clickableRows
1499
2128
  },
1500
2129
  row.id
@@ -1503,73 +2132,70 @@ function TableBody({
1503
2132
  const hasPretextData = !!(pretextHeights && pretextPrefixSums);
1504
2133
  const fixedRowHeight = typeof rowHeight === "number" && !hasPretextData ? rowHeight : void 0;
1505
2134
  const containerHeight = hasPretextData ? Math.min(totalHeight, 800) : fixedRowHeight ? Math.min(totalHeight, fixedRowHeight * 20) : 600;
1506
- return /* @__PURE__ */ jsxRuntime.jsx("tbody", { className: "yable-tbody", children: /* @__PURE__ */ jsxRuntime.jsx("tr", { style: { height: 0, padding: 0, border: "none" }, children: /* @__PURE__ */ jsxRuntime.jsx(
1507
- "td",
2135
+ return /* @__PURE__ */ jsxRuntime.jsx("tbody", { className: "yable-tbody", children: /* @__PURE__ */ jsxRuntime.jsx("tr", { style: { height: 0, padding: 0, border: "none" }, children: /* @__PURE__ */ jsxRuntime.jsx("td", { style: { height: 0, padding: 0, border: "none" }, colSpan: visibleColumns.length, children: /* @__PURE__ */ jsxRuntime.jsx(
2136
+ "div",
1508
2137
  {
1509
- style: { height: 0, padding: 0, border: "none" },
1510
- colSpan: visibleColumns.length,
2138
+ ref: scrollContainerRef,
2139
+ className: "yable-virtual-scroll-container",
2140
+ style: {
2141
+ overflowY: "auto",
2142
+ height: containerHeight,
2143
+ position: "relative"
2144
+ },
1511
2145
  children: /* @__PURE__ */ jsxRuntime.jsx(
1512
2146
  "div",
1513
2147
  {
1514
- ref: scrollContainerRef,
1515
- className: "yable-virtual-scroll-container",
1516
- style: {
1517
- overflowY: "auto",
1518
- height: containerHeight,
1519
- position: "relative"
1520
- },
1521
- children: /* @__PURE__ */ jsxRuntime.jsx(
1522
- "div",
2148
+ className: "yable-virtual-spacer",
2149
+ style: { height: totalHeight, position: "relative" },
2150
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
2151
+ "table",
1523
2152
  {
1524
- className: "yable-virtual-spacer",
1525
- style: { height: totalHeight, position: "relative" },
1526
- children: /* @__PURE__ */ jsxRuntime.jsx(
1527
- "table",
1528
- {
1529
- style: {
1530
- position: "absolute",
1531
- top: 0,
1532
- left: 0,
1533
- width: "100%",
1534
- tableLayout: "fixed",
1535
- borderCollapse: "collapse"
1536
- },
1537
- children: /* @__PURE__ */ jsxRuntime.jsx("tbody", { children: virtualRows.map((vRow) => {
1538
- const row = rows[vRow.index];
1539
- if (!row) return null;
1540
- return /* @__PURE__ */ jsxRuntime.jsx(
1541
- MemoizedTableRow,
1542
- {
1543
- row,
1544
- table,
1545
- rowIndex: vRow.index,
1546
- visibleColumns,
1547
- isSelected: row.getIsSelected(),
1548
- isExpanded: row.getIsExpanded(),
1549
- activeColumnId: activeCell?.rowId === row.id ? activeCell.columnId : void 0,
1550
- focusedColumnIndex: focusedCell?.rowIndex === vRow.index ? focusedCell.columnIndex : null,
1551
- hasFocusedCell: focusedCell !== null,
1552
- clickable: clickableRows,
1553
- virtualStyle: {
1554
- position: "absolute",
1555
- top: 0,
1556
- left: 0,
1557
- width: "100%",
1558
- height: vRow.size,
1559
- transform: `translateY(${vRow.start}px)`
1560
- }
1561
- },
1562
- row.id
1563
- );
1564
- }) })
1565
- }
1566
- )
2153
+ style: {
2154
+ position: "absolute",
2155
+ top: 0,
2156
+ left: 0,
2157
+ width: "100%",
2158
+ tableLayout: "fixed",
2159
+ borderCollapse: "collapse"
2160
+ },
2161
+ children: [
2162
+ colgroup,
2163
+ /* @__PURE__ */ jsxRuntime.jsx("tbody", { children: virtualRows.map((vRow) => {
2164
+ const row = rows[vRow.index];
2165
+ if (!row) return null;
2166
+ return /* @__PURE__ */ jsxRuntime.jsx(
2167
+ MemoizedTableRow,
2168
+ {
2169
+ row,
2170
+ table,
2171
+ rowIndex: vRow.index,
2172
+ visibleColumns,
2173
+ isSelected: row.getIsSelected(),
2174
+ isExpanded: row.getIsExpanded(),
2175
+ activeColumnId: activeCell?.rowId === row.id ? activeCell.columnId : void 0,
2176
+ focusedColumnIndex: focusedCell?.rowIndex === vRow.index ? focusedCell.columnIndex : null,
2177
+ hasFocusedCell: focusedCell !== null,
2178
+ cellSelectionKey,
2179
+ clickable: clickableRows,
2180
+ virtualStyle: {
2181
+ position: "absolute",
2182
+ top: 0,
2183
+ left: 0,
2184
+ width: "100%",
2185
+ height: vRow.size,
2186
+ transform: `translateY(${vRow.start}px)`
2187
+ }
2188
+ },
2189
+ row.id
2190
+ );
2191
+ }) })
2192
+ ]
1567
2193
  }
1568
2194
  )
1569
2195
  }
1570
2196
  )
1571
2197
  }
1572
- ) }) });
2198
+ ) }) }) });
1573
2199
  }
1574
2200
  function TableRowInner({
1575
2201
  row,
@@ -1581,10 +2207,12 @@ function TableRowInner({
1581
2207
  activeColumnId,
1582
2208
  focusedColumnIndex,
1583
2209
  hasFocusedCell,
2210
+ cellSelectionKey: _cellSelectionKey,
1584
2211
  clickable,
1585
2212
  virtualStyle
1586
2213
  }) {
1587
- const visibleCells = row.getVisibleCells();
2214
+ const allCells = row.getAllCells();
2215
+ const visibleCells = visibleColumns.map((column) => allCells.find((cell) => cell.column.id === column.id)).filter((cell) => cell != null);
1588
2216
  const handleClick = React3.useCallback(
1589
2217
  (e) => {
1590
2218
  if (clickable) {
@@ -1614,6 +2242,8 @@ function TableRowInner({
1614
2242
  },
1615
2243
  [table.events, row]
1616
2244
  );
2245
+ const selectionEnabled = Boolean(table.options.enableRowSelection);
2246
+ const expansionEnabled = Boolean(table.options.enableExpanding);
1617
2247
  return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1618
2248
  /* @__PURE__ */ jsxRuntime.jsx(
1619
2249
  "tr",
@@ -1625,6 +2255,8 @@ function TableRowInner({
1625
2255
  "data-clickable": clickable || void 0,
1626
2256
  "data-row-id": row.id,
1627
2257
  "data-row-index": rowIndex,
2258
+ "aria-selected": selectionEnabled ? isSelected : void 0,
2259
+ "aria-expanded": expansionEnabled ? isExpanded : void 0,
1628
2260
  onClick: handleClick,
1629
2261
  onDoubleClick: handleDoubleClick,
1630
2262
  onContextMenu: handleContextMenu,
@@ -1666,6 +2298,7 @@ function areRowPropsEqual(prev, next) {
1666
2298
  if (prev.activeColumnId !== next.activeColumnId) return false;
1667
2299
  if (prev.focusedColumnIndex !== next.focusedColumnIndex) return false;
1668
2300
  if (prev.hasFocusedCell !== next.hasFocusedCell) return false;
2301
+ if (prev.cellSelectionKey !== next.cellSelectionKey) return false;
1669
2302
  if (prev.virtualStyle !== next.virtualStyle) {
1670
2303
  if (!prev.virtualStyle || !next.virtualStyle) return false;
1671
2304
  if (prev.virtualStyle.transform !== next.virtualStyle.transform) return false;
@@ -1675,9 +2308,7 @@ function areRowPropsEqual(prev, next) {
1675
2308
  return true;
1676
2309
  }
1677
2310
  var MemoizedTableRow = React3__default.default.memo(TableRowInner, areRowPropsEqual);
1678
- function TableFooter({
1679
- table
1680
- }) {
2311
+ function TableFooter({ table }) {
1681
2312
  const footerGroups = table.getFooterGroups();
1682
2313
  if (!footerGroups.length) return null;
1683
2314
  return /* @__PURE__ */ jsxRuntime.jsx("tfoot", { className: "yable-tfoot", children: footerGroups.map((footerGroup) => /* @__PURE__ */ jsxRuntime.jsx("tr", { className: "yable-tr", children: footerGroup.headers.map((header) => {
@@ -2440,11 +3071,11 @@ function ContextMenu({
2440
3071
  }
2441
3072
  if (e.key === "ArrowDown" || e.key === "ArrowUp") {
2442
3073
  e.preventDefault();
2443
- const items2 = menuRef.current?.querySelectorAll('[role="menuitem"]:not([aria-disabled="true"])');
2444
- if (!items2 || items2.length === 0) return;
2445
- const currentIndex = Array.from(items2).findIndex(
2446
- (el) => el === document.activeElement
3074
+ const items2 = menuRef.current?.querySelectorAll(
3075
+ '[role="menuitem"]:not([aria-disabled="true"])'
2447
3076
  );
3077
+ if (!items2 || items2.length === 0) return;
3078
+ const currentIndex = Array.from(items2).findIndex((el) => el === document.activeElement);
2448
3079
  let nextIndex;
2449
3080
  if (e.key === "ArrowDown") {
2450
3081
  nextIndex = currentIndex < items2.length - 1 ? currentIndex + 1 : 0;
@@ -2489,11 +3120,7 @@ function ContextMenu({
2489
3120
  navigator.clipboard?.readText().then((text) => {
2490
3121
  const editing = table.getState().editing;
2491
3122
  if (editing?.activeCell) {
2492
- table.pasteFromClipboard(
2493
- text,
2494
- editing.activeCell.rowId,
2495
- editing.activeCell.columnId
2496
- );
3123
+ table.pasteFromClipboard(text, editing.activeCell.rowId, editing.activeCell.columnId);
2497
3124
  }
2498
3125
  }).catch(() => {
2499
3126
  });
@@ -2833,13 +3460,19 @@ function isEditableTarget(element) {
2833
3460
  }
2834
3461
  return element.isContentEditable;
2835
3462
  }
3463
+ function filterHeaderGroups(groups, visibleColumnIds) {
3464
+ return groups.map((group) => ({
3465
+ ...group,
3466
+ headers: group.headers.filter((header) => visibleColumnIds.has(header.column.id))
3467
+ })).filter((group) => group.headers.length > 0);
3468
+ }
2836
3469
  function Table({
2837
3470
  table,
2838
- stickyHeader,
2839
- striped,
2840
- bordered,
2841
- compact,
2842
- theme,
3471
+ stickyHeader: stickyHeaderProp,
3472
+ striped: stripedProp,
3473
+ bordered: borderedProp,
3474
+ compact: compactProp,
3475
+ theme: themeProp,
2843
3476
  clickableRows,
2844
3477
  footer,
2845
3478
  loading,
@@ -2853,19 +3486,32 @@ function Table({
2853
3486
  renderLoading,
2854
3487
  children,
2855
3488
  className,
2856
- direction,
3489
+ direction: directionProp,
2857
3490
  statusBar,
2858
3491
  statusBarPanels,
2859
3492
  sidebar,
2860
3493
  sidebarPanels = ["columns", "filters"],
2861
3494
  defaultSidebarPanel,
3495
+ floatingFilters,
3496
+ columnVirtualization,
3497
+ columnVirtualizationOverscan,
3498
+ ariaLabel: ariaLabelProp,
2862
3499
  ...rest
2863
3500
  }) {
3501
+ const { tableProps: providerTableProps } = useYableDefaults();
3502
+ const stickyHeader = stickyHeaderProp ?? providerTableProps?.stickyHeader;
3503
+ const striped = stripedProp ?? providerTableProps?.striped;
3504
+ const bordered = borderedProp ?? providerTableProps?.bordered;
3505
+ const compact = compactProp ?? providerTableProps?.compact;
3506
+ const theme = themeProp ?? providerTableProps?.theme;
3507
+ const direction = directionProp ?? providerTableProps?.direction;
3508
+ const ariaLabel = ariaLabelProp ?? providerTableProps?.ariaLabel;
2864
3509
  const [sidebarOpen, setSidebarOpen] = React3.useState(false);
2865
3510
  const [sidebarPanel, setSidebarPanel] = React3.useState(
2866
3511
  defaultSidebarPanel ?? "columns"
2867
3512
  );
2868
3513
  const containerRef = React3.useRef(null);
3514
+ const horizontalScrollRef = React3.useRef(null);
2869
3515
  const isRtl = direction === "rtl";
2870
3516
  const classNames = [
2871
3517
  "yable",
@@ -2883,8 +3529,86 @@ function Table({
2883
3529
  const hasGlobalFilter = Boolean(table.getState().globalFilter);
2884
3530
  const hasColumnFilters = table.getState().columnFilters.length > 0;
2885
3531
  const isFiltered = hasGlobalFilter || hasColumnFilters;
3532
+ const allVisibleColumns = table.getVisibleLeafColumns();
3533
+ const hasPinnedColumns = table.getLeftVisibleLeafColumns().length > 0 || table.getRightVisibleLeafColumns().length > 0;
3534
+ const hasGroupedHeaders = table.getHeaderGroups().length > 1;
3535
+ const canVirtualizeColumns = Boolean(columnVirtualization) && !hasPinnedColumns && !hasGroupedHeaders && allVisibleColumns.length > 0;
3536
+ const columnVirtualState = useColumnVirtualization({
3537
+ containerRef: horizontalScrollRef,
3538
+ columns: allVisibleColumns,
3539
+ overscan: columnVirtualizationOverscan ?? 2,
3540
+ enabled: canVirtualizeColumns
3541
+ });
3542
+ const renderTable = React3.useMemo(() => {
3543
+ if (!canVirtualizeColumns || !columnVirtualState.isVirtualized) {
3544
+ return table;
3545
+ }
3546
+ const virtualColumns = columnVirtualState.virtualColumns.map((entry) => entry.column);
3547
+ const visibleColumnIds = new Set(virtualColumns.map((column) => column.id));
3548
+ const next = Object.create(table);
3549
+ next.getVisibleFlatColumns = () => virtualColumns;
3550
+ next.getVisibleLeafColumns = () => virtualColumns;
3551
+ next.getCenterVisibleLeafColumns = () => virtualColumns;
3552
+ next.getLeftVisibleLeafColumns = () => [];
3553
+ next.getRightVisibleLeafColumns = () => [];
3554
+ next.getHeaderGroups = () => filterHeaderGroups(table.getHeaderGroups(), visibleColumnIds);
3555
+ next.getCenterHeaderGroups = () => filterHeaderGroups(table.getCenterHeaderGroups(), visibleColumnIds);
3556
+ next.getFooterGroups = () => filterHeaderGroups(table.getFooterGroups(), visibleColumnIds);
3557
+ next.getCenterFooterGroups = () => filterHeaderGroups(table.getCenterFooterGroups(), visibleColumnIds);
3558
+ return next;
3559
+ }, [
3560
+ canVirtualizeColumns,
3561
+ columnVirtualState.isVirtualized,
3562
+ columnVirtualState.virtualColumns,
3563
+ table
3564
+ ]);
3565
+ const showColumnVirtualizationShell = canVirtualizeColumns;
2886
3566
  const contextMenu = useContextMenu();
2887
3567
  useKeyboardNavigation(table, { containerRef });
3568
+ const [announcement, setAnnouncement] = React3.useState("");
3569
+ const prevSortingRef = React3.useRef(table.getState().sorting);
3570
+ const prevFilterCountRef = React3.useRef(rows.length);
3571
+ const prevHasFiltersRef = React3.useRef(isFiltered);
3572
+ const prevPaginationRef = React3.useRef(table.getState().pagination);
3573
+ const isFirstRenderRef = React3.useRef(true);
3574
+ React3.useEffect(() => {
3575
+ if (isFirstRenderRef.current) {
3576
+ isFirstRenderRef.current = false;
3577
+ return;
3578
+ }
3579
+ const currentSorting = table.getState().sorting;
3580
+ const currentPagination = table.getState().pagination;
3581
+ const currentRowCount = rows.length;
3582
+ const currentIsFiltered = isFiltered;
3583
+ if (JSON.stringify(currentSorting) !== JSON.stringify(prevSortingRef.current)) {
3584
+ prevSortingRef.current = currentSorting;
3585
+ const firstSort = currentSorting[0];
3586
+ if (firstSort) {
3587
+ const column = table.getColumn(firstSort.id);
3588
+ const headerDef = column?.columnDef.header;
3589
+ const columnName = typeof headerDef === "string" ? headerDef : firstSort.id;
3590
+ const direction2 = firstSort.desc ? "descending" : "ascending";
3591
+ setAnnouncement(`Sorted by ${columnName} ${direction2}`);
3592
+ return;
3593
+ }
3594
+ }
3595
+ if (currentRowCount !== prevFilterCountRef.current || currentIsFiltered !== prevHasFiltersRef.current) {
3596
+ prevFilterCountRef.current = currentRowCount;
3597
+ prevHasFiltersRef.current = currentIsFiltered;
3598
+ if (currentIsFiltered) {
3599
+ setAnnouncement(`${currentRowCount} rows after filtering`);
3600
+ return;
3601
+ }
3602
+ }
3603
+ if (JSON.stringify(currentPagination) !== JSON.stringify(prevPaginationRef.current)) {
3604
+ prevPaginationRef.current = currentPagination;
3605
+ const pageCount = table.getPageCount();
3606
+ if (pageCount > 0) {
3607
+ setAnnouncement(`Page ${currentPagination.pageIndex + 1} of ${pageCount}`);
3608
+ return;
3609
+ }
3610
+ }
3611
+ });
2888
3612
  const handleContextMenu = React3.useCallback(
2889
3613
  (e) => {
2890
3614
  e.preventDefault();
@@ -2892,6 +3616,46 @@ function Table({
2892
3616
  },
2893
3617
  [contextMenu, table]
2894
3618
  );
3619
+ const enableRowVirtualization = renderTable.options.enableVirtualization ?? false;
3620
+ const visibleLeafColumns = renderTable.getVisibleLeafColumns();
3621
+ const columnSizing = renderTable.getState().columnSizing;
3622
+ const colgroup = React3.useMemo(() => {
3623
+ if (visibleLeafColumns.length === 0) return null;
3624
+ return /* @__PURE__ */ jsxRuntime.jsx("colgroup", { children: visibleLeafColumns.map((col) => /* @__PURE__ */ jsxRuntime.jsx("col", { style: { width: col.getSize() } }, col.id)) });
3625
+ }, [visibleLeafColumns, columnSizing]);
3626
+ const outerTableStyle = React3.useMemo(() => {
3627
+ if (columnVirtualState.isVirtualized) {
3628
+ return {
3629
+ width: columnVirtualState.visibleWidth,
3630
+ minWidth: columnVirtualState.visibleWidth,
3631
+ marginLeft: columnVirtualState.startOffset,
3632
+ tableLayout: "fixed"
3633
+ };
3634
+ }
3635
+ if (enableRowVirtualization) {
3636
+ return { tableLayout: "fixed" };
3637
+ }
3638
+ return void 0;
3639
+ }, [
3640
+ columnVirtualState.isVirtualized,
3641
+ columnVirtualState.visibleWidth,
3642
+ columnVirtualState.startOffset,
3643
+ enableRowVirtualization
3644
+ ]);
3645
+ const tableNode = /* @__PURE__ */ jsxRuntime.jsxs(
3646
+ "table",
3647
+ {
3648
+ className: "yable-table",
3649
+ style: outerTableStyle,
3650
+ "data-column-virtualized": columnVirtualState.isVirtualized || void 0,
3651
+ children: [
3652
+ enableRowVirtualization && colgroup,
3653
+ /* @__PURE__ */ jsxRuntime.jsx(TableHeader, { table: renderTable, floatingFilters }),
3654
+ /* @__PURE__ */ jsxRuntime.jsx(TableBody, { table: renderTable, clickableRows, colgroup }),
3655
+ footer && /* @__PURE__ */ jsxRuntime.jsx(TableFooter, { table: renderTable })
3656
+ ]
3657
+ }
3658
+ );
2895
3659
  return /* @__PURE__ */ jsxRuntime.jsx(TableProvider, { value: table, children: /* @__PURE__ */ jsxRuntime.jsxs(
2896
3660
  "div",
2897
3661
  {
@@ -2900,23 +3664,40 @@ function Table({
2900
3664
  "data-theme": theme,
2901
3665
  dir: direction,
2902
3666
  role: "grid",
3667
+ "aria-label": ariaLabel ?? "Data table",
2903
3668
  "aria-rowcount": table.getRowModel().rows.length,
2904
3669
  "aria-colcount": table.getVisibleLeafColumns().length,
2905
3670
  onContextMenu: handleContextMenu,
2906
3671
  ...rest,
2907
3672
  children: [
2908
3673
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "yable-main", children: [
2909
- /* @__PURE__ */ jsxRuntime.jsxs("table", { className: "yable-table", children: [
2910
- /* @__PURE__ */ jsxRuntime.jsx(TableHeader, { table }),
2911
- /* @__PURE__ */ jsxRuntime.jsx(
2912
- TableBody,
2913
- {
2914
- table,
2915
- clickableRows
2916
- }
2917
- ),
2918
- footer && /* @__PURE__ */ jsxRuntime.jsx(TableFooter, { table })
2919
- ] }),
3674
+ showColumnVirtualizationShell ? /* @__PURE__ */ jsxRuntime.jsx(
3675
+ "div",
3676
+ {
3677
+ ref: horizontalScrollRef,
3678
+ className: "yable-horizontal-scroll-container",
3679
+ style: {
3680
+ overflowX: "auto",
3681
+ overflowY: "visible",
3682
+ maxWidth: "100%",
3683
+ position: "relative"
3684
+ },
3685
+ children: /* @__PURE__ */ jsxRuntime.jsx(
3686
+ "div",
3687
+ {
3688
+ className: "yable-horizontal-scroll-inner",
3689
+ style: {
3690
+ width: Math.max(columnVirtualState.totalWidth, columnVirtualState.visibleWidth),
3691
+ minWidth: Math.max(
3692
+ columnVirtualState.totalWidth,
3693
+ columnVirtualState.visibleWidth
3694
+ )
3695
+ },
3696
+ children: tableNode
3697
+ }
3698
+ )
3699
+ }
3700
+ ) : tableNode,
2920
3701
  /* @__PURE__ */ jsxRuntime.jsx(
2921
3702
  LoadingOverlay,
2922
3703
  {
@@ -2957,6 +3738,24 @@ function Table({
2957
3738
  onClose: contextMenu.close,
2958
3739
  table
2959
3740
  }
3741
+ ),
3742
+ /* @__PURE__ */ jsxRuntime.jsx(
3743
+ "div",
3744
+ {
3745
+ role: "status",
3746
+ "aria-live": "polite",
3747
+ "aria-atomic": "true",
3748
+ style: {
3749
+ position: "absolute",
3750
+ width: "1px",
3751
+ height: "1px",
3752
+ overflow: "hidden",
3753
+ clip: "rect(0,0,0,0)",
3754
+ whiteSpace: "nowrap",
3755
+ border: 0
3756
+ },
3757
+ children: announcement
3758
+ }
2960
3759
  )
2961
3760
  ]
2962
3761
  }
@@ -3422,14 +4221,7 @@ function formatDateValue(value, type) {
3422
4221
  return String(value);
3423
4222
  }
3424
4223
  function useClipboard(table, options = {}) {
3425
- const {
3426
- enabled = true,
3427
- containerRef,
3428
- onCopy,
3429
- onCut,
3430
- onPaste,
3431
- ...clipboardOptions
3432
- } = options;
4224
+ const { enabled = true, containerRef, onCopy, onCut, onPaste, ...clipboardOptions } = options;
3433
4225
  const handleCopy = React3.useCallback(
3434
4226
  (e) => {
3435
4227
  if (isEditableTarget2(e.target)) return;
@@ -3467,20 +4259,29 @@ function useClipboard(table, options = {}) {
3467
4259
  targetRowId = state.editing.activeCell.rowId;
3468
4260
  targetColumnId = state.editing.activeCell.columnId;
3469
4261
  } else {
3470
- const selectedRowIds = Object.keys(state.rowSelection || {}).filter(
3471
- (id) => state.rowSelection[id]
3472
- );
3473
- if (selectedRowIds.length > 0) {
3474
- targetRowId = selectedRowIds[0];
4262
+ const selectedRange = table.getCellSelectionRange();
4263
+ const rows = table.getRowModel().rows;
4264
+ const columns = table.getVisibleLeafColumns();
4265
+ if (selectedRange) {
4266
+ const startRowIndex = Math.min(selectedRange.start.rowIndex, selectedRange.end.rowIndex);
4267
+ const startColumnIndex = Math.min(
4268
+ selectedRange.start.columnIndex,
4269
+ selectedRange.end.columnIndex
4270
+ );
4271
+ targetRowId = rows[startRowIndex]?.id;
4272
+ targetColumnId = columns[startColumnIndex]?.id;
3475
4273
  } else {
3476
- const rows = table.getRowModel().rows;
3477
- if (rows.length > 0) {
4274
+ const selectedRowIds = Object.keys(state.rowSelection || {}).filter(
4275
+ (id) => state.rowSelection[id]
4276
+ );
4277
+ if (selectedRowIds.length > 0) {
4278
+ targetRowId = selectedRowIds[0];
4279
+ } else if (rows.length > 0) {
3478
4280
  targetRowId = rows[0].id;
3479
4281
  }
3480
- }
3481
- const columns = table.getVisibleLeafColumns();
3482
- if (columns.length > 0) {
3483
- targetColumnId = columns[0].id;
4282
+ if (columns.length > 0) {
4283
+ targetColumnId = columns[0].id;
4284
+ }
3484
4285
  }
3485
4286
  }
3486
4287
  if (targetRowId && targetColumnId) {
@@ -4770,7 +5571,7 @@ function sanitizeCSS(css) {
4770
5571
  sanitized = sanitized.replace(/javascript\s*:/gi, "/* javascript: removed */");
4771
5572
  return sanitized;
4772
5573
  }
4773
- function usePrintLayout(table, options = {}) {
5574
+ function usePrintLayout(_table, options = {}) {
4774
5575
  const { title, additionalCSS } = options;
4775
5576
  const isPrintingRef = React3.useRef(false);
4776
5577
  const preparePrint = React3.useCallback(() => {
@@ -4807,7 +5608,7 @@ function usePrintLayout(table, options = {}) {
4807
5608
  requestAnimationFrame(() => {
4808
5609
  window.print();
4809
5610
  });
4810
- }, [table, title, additionalCSS]);
5611
+ }, [title, additionalCSS]);
4811
5612
  return {
4812
5613
  preparePrint,
4813
5614
  isPrinting: isPrintingRef.current
@@ -4858,6 +5659,233 @@ function useTheme(options = {}) {
4858
5659
  containerRef
4859
5660
  };
4860
5661
  }
5662
+ function selectColumn(options = {}) {
5663
+ const { id = "_select", size = 40, headerAriaLabel = "Select all rows" } = options;
5664
+ return {
5665
+ id,
5666
+ header: ({ table }) => /* @__PURE__ */ jsxRuntime.jsx(
5667
+ "input",
5668
+ {
5669
+ type: "checkbox",
5670
+ checked: table.getIsAllPageRowsSelected(),
5671
+ ref: (el) => {
5672
+ if (el)
5673
+ el.indeterminate = table.getIsSomePageRowsSelected() && !table.getIsAllPageRowsSelected();
5674
+ },
5675
+ onChange: () => table.toggleAllPageRowsSelected(),
5676
+ "aria-label": headerAriaLabel
5677
+ }
5678
+ ),
5679
+ cell: ({ row }) => /* @__PURE__ */ jsxRuntime.jsx(
5680
+ "input",
5681
+ {
5682
+ type: "checkbox",
5683
+ checked: row.getIsSelected(),
5684
+ disabled: !row.getCanSelect(),
5685
+ onChange: row.getToggleSelectedHandler(),
5686
+ "aria-label": `Select row`
5687
+ }
5688
+ ),
5689
+ size,
5690
+ enableSorting: false,
5691
+ enableColumnFilter: false,
5692
+ enableResizing: false,
5693
+ enableReorder: false,
5694
+ enableHiding: false,
5695
+ lockVisible: true
5696
+ };
5697
+ }
5698
+
5699
+ // src/presets/rowNumberColumn.tsx
5700
+ function rowNumberColumn(options = {}) {
5701
+ const { id = "_rowNumber", header = "#", size = 50, startFrom = 1 } = options;
5702
+ return {
5703
+ id,
5704
+ header,
5705
+ cell: ({ row }) => row.index + startFrom,
5706
+ size,
5707
+ enableSorting: false,
5708
+ enableColumnFilter: false,
5709
+ enableResizing: false,
5710
+ enableReorder: false,
5711
+ lockVisible: true
5712
+ };
5713
+ }
5714
+ function actionsColumn(options) {
5715
+ const { id = "_actions", header = "", size = 100, actions } = options;
5716
+ return {
5717
+ id,
5718
+ header,
5719
+ cell: (ctx) => {
5720
+ const items = typeof actions === "function" ? actions(ctx.row) : actions;
5721
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "yable-cell-actions", children: items.filter((a) => !a.hidden || !a.hidden(ctx.row)).map((action, i) => /* @__PURE__ */ jsxRuntime.jsx(
5722
+ "button",
5723
+ {
5724
+ type: "button",
5725
+ className: "yable-action-btn",
5726
+ disabled: action.disabled?.(ctx.row),
5727
+ onClick: (e) => {
5728
+ e.stopPropagation();
5729
+ action.onClick(ctx.row);
5730
+ },
5731
+ title: action.label,
5732
+ children: action.icon ?? action.label
5733
+ },
5734
+ i
5735
+ )) });
5736
+ },
5737
+ size,
5738
+ enableSorting: false,
5739
+ enableColumnFilter: false,
5740
+ enableResizing: false,
5741
+ enableReorder: false
5742
+ };
5743
+ }
5744
+ function expandColumn(options = {}) {
5745
+ const { id = "_expand", size = 40 } = options;
5746
+ return {
5747
+ id,
5748
+ header: () => null,
5749
+ cell: ({ row }) => {
5750
+ if (!row.getCanExpand()) return null;
5751
+ return /* @__PURE__ */ jsxRuntime.jsx(
5752
+ "button",
5753
+ {
5754
+ type: "button",
5755
+ className: "yable-expand-btn",
5756
+ onClick: (e) => {
5757
+ e.stopPropagation();
5758
+ row.toggleExpanded();
5759
+ },
5760
+ "aria-expanded": row.getIsExpanded(),
5761
+ "aria-label": row.getIsExpanded() ? "Collapse row" : "Expand row",
5762
+ children: /* @__PURE__ */ jsxRuntime.jsx(
5763
+ "span",
5764
+ {
5765
+ className: "yable-expand-icon",
5766
+ style: {
5767
+ display: "inline-block",
5768
+ transform: row.getIsExpanded() ? "rotate(90deg)" : "rotate(0deg)",
5769
+ transition: "transform 150ms ease"
5770
+ },
5771
+ children: "\u25B6"
5772
+ }
5773
+ )
5774
+ }
5775
+ );
5776
+ },
5777
+ size,
5778
+ enableSorting: false,
5779
+ enableColumnFilter: false,
5780
+ enableResizing: false,
5781
+ enableReorder: false,
5782
+ lockVisible: true
5783
+ };
5784
+ }
5785
+ function CellStack({ children, gap = 2 }) {
5786
+ return /* @__PURE__ */ jsxRuntime.jsx(
5787
+ "div",
5788
+ {
5789
+ className: "yable-cell-stack",
5790
+ style: {
5791
+ display: "flex",
5792
+ flexDirection: "column",
5793
+ gap
5794
+ },
5795
+ children
5796
+ }
5797
+ );
5798
+ }
5799
+ function CellRow({ children, gap = 6, align = "center", justify = "start" }) {
5800
+ const justifyMap = {
5801
+ start: "flex-start",
5802
+ center: "center",
5803
+ end: "flex-end",
5804
+ between: "space-between"
5805
+ };
5806
+ return /* @__PURE__ */ jsxRuntime.jsx(
5807
+ "div",
5808
+ {
5809
+ className: "yable-cell-row",
5810
+ style: {
5811
+ display: "flex",
5812
+ flexDirection: "row",
5813
+ alignItems: align === "baseline" ? "baseline" : align === "start" ? "flex-start" : align === "end" ? "flex-end" : "center",
5814
+ justifyContent: justifyMap[justify] || "flex-start",
5815
+ gap
5816
+ },
5817
+ children
5818
+ }
5819
+ );
5820
+ }
5821
+ function CellWithIcon({ icon, children, gap = 6, iconSize }) {
5822
+ return /* @__PURE__ */ jsxRuntime.jsxs(
5823
+ "div",
5824
+ {
5825
+ className: "yable-cell-with-icon",
5826
+ style: {
5827
+ display: "flex",
5828
+ flexDirection: "row",
5829
+ alignItems: "center",
5830
+ gap
5831
+ },
5832
+ children: [
5833
+ /* @__PURE__ */ jsxRuntime.jsx(
5834
+ "span",
5835
+ {
5836
+ className: "yable-cell-icon",
5837
+ style: {
5838
+ display: "inline-flex",
5839
+ alignItems: "center",
5840
+ justifyContent: "center",
5841
+ flexShrink: 0,
5842
+ ...iconSize ? { width: iconSize, height: iconSize } : {}
5843
+ },
5844
+ children: icon
5845
+ }
5846
+ ),
5847
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "yable-cell-icon-content", style: { minWidth: 0 }, children })
5848
+ ]
5849
+ }
5850
+ );
5851
+ }
5852
+ function CellText({
5853
+ children,
5854
+ variant = "primary",
5855
+ bold,
5856
+ truncate,
5857
+ size = "md"
5858
+ }) {
5859
+ const fontSizeMap = { sm: "0.75rem", md: "0.875rem", lg: "1rem" };
5860
+ return /* @__PURE__ */ jsxRuntime.jsx(
5861
+ "span",
5862
+ {
5863
+ className: `yable-cell-text yable-cell-text--${variant}`,
5864
+ style: {
5865
+ fontSize: fontSizeMap[size],
5866
+ fontWeight: bold ? 600 : void 0,
5867
+ color: variant === "secondary" ? "var(--yable-text-secondary, #6b7280)" : variant === "muted" ? "var(--yable-text-muted, #9ca3af)" : void 0,
5868
+ ...truncate ? {
5869
+ overflow: "hidden",
5870
+ textOverflow: "ellipsis",
5871
+ whiteSpace: "nowrap"
5872
+ } : {}
5873
+ },
5874
+ children
5875
+ }
5876
+ );
5877
+ }
5878
+
5879
+ // src/utils/mergeEditChanges.ts
5880
+ function mergeEditChanges(data, changes, getRowId = (_, i) => String(i)) {
5881
+ const changeKeys = Object.keys(changes);
5882
+ if (changeKeys.length === 0) return data;
5883
+ return data.map((row, i) => {
5884
+ const id = getRowId(row, i);
5885
+ const patch = changes[id];
5886
+ return patch ? { ...row, ...patch } : row;
5887
+ });
5888
+ }
4861
5889
 
4862
5890
  Object.defineProperty(exports, "CommitError", {
4863
5891
  enumerable: true,
@@ -4951,10 +5979,14 @@ exports.CellLink = CellLink;
4951
5979
  exports.CellNumeric = CellNumeric;
4952
5980
  exports.CellProgress = CellProgress;
4953
5981
  exports.CellRating = CellRating;
5982
+ exports.CellRow = CellRow;
4954
5983
  exports.CellSelect = CellSelect;
5984
+ exports.CellStack = CellStack;
4955
5985
  exports.CellStatus = CellStatus;
4956
5986
  exports.CellStatusBadge = CellStatusBadge;
5987
+ exports.CellText = CellText;
4957
5988
  exports.CellToggle = CellToggle;
5989
+ exports.CellWithIcon = CellWithIcon;
4958
5990
  exports.ColumnsPanel = ColumnsPanel;
4959
5991
  exports.ContextMenu = ContextMenu;
4960
5992
  exports.ContextMenuItem = ContextMenuItem;
@@ -4965,6 +5997,7 @@ exports.ExpandIcon = ExpandIcon;
4965
5997
  exports.FillHandle = FillHandle;
4966
5998
  exports.FiltersPanel = FiltersPanel;
4967
5999
  exports.FlashCell = FlashCell;
6000
+ exports.FloatingFilter = FloatingFilter;
4968
6001
  exports.GlobalFilter = GlobalFilter;
4969
6002
  exports.LoadingOverlay = LoadingOverlay;
4970
6003
  exports.MasterDetail = MasterDetail;
@@ -4972,6 +6005,7 @@ exports.NoRowsOverlay = NoRowsOverlay;
4972
6005
  exports.Pagination = Pagination;
4973
6006
  exports.PivotConfigPanel = PivotConfigPanel;
4974
6007
  exports.PrintLayout = PrintLayout;
6008
+ exports.SetFilter = SetFilter;
4975
6009
  exports.Sidebar = Sidebar;
4976
6010
  exports.SortIndicator = SortIndicator;
4977
6011
  exports.StatusBar = StatusBar;
@@ -4984,12 +6018,19 @@ exports.TableHeader = TableHeader;
4984
6018
  exports.TableProvider = TableProvider;
4985
6019
  exports.Tooltip = Tooltip;
4986
6020
  exports.TreeToggle = TreeToggle;
6021
+ exports.YableProvider = YableProvider;
6022
+ exports.actionsColumn = actionsColumn;
6023
+ exports.expandColumn = expandColumn;
4987
6024
  exports.getMeasureRecipeForCellType = getMeasureRecipeForCellType;
4988
6025
  exports.getRegisteredCellTypes = getRegisteredCellTypes;
6026
+ exports.mergeEditChanges = mergeEditChanges;
4989
6027
  exports.resolveMeasureRecipe = resolveMeasureRecipe;
6028
+ exports.rowNumberColumn = rowNumberColumn;
6029
+ exports.selectColumn = selectColumn;
4990
6030
  exports.useAutoMeasurements = useAutoMeasurements;
4991
6031
  exports.useCellFlash = useCellFlash;
4992
6032
  exports.useClipboard = useClipboard;
6033
+ exports.useColumnVirtualization = useColumnVirtualization;
4993
6034
  exports.useContextMenu = useContextMenu;
4994
6035
  exports.useFillHandle = useFillHandle;
4995
6036
  exports.useKeyboardNavigation = useKeyboardNavigation;
@@ -4999,9 +6040,11 @@ exports.useRowAnimation = useRowAnimation;
4999
6040
  exports.useRowDrag = useRowDrag;
5000
6041
  exports.useTable = useTable;
5001
6042
  exports.useTableContext = useTableContext;
6043
+ exports.useTablePersistence = useTablePersistence;
5002
6044
  exports.useTableRowHeights = useTableRowHeights;
5003
6045
  exports.useTheme = useTheme;
5004
6046
  exports.useTooltip = useTooltip;
5005
6047
  exports.useVirtualization = useVirtualization;
6048
+ exports.useYableDefaults = useYableDefaults;
5006
6049
  //# sourceMappingURL=index.cjs.map
5007
6050
  //# sourceMappingURL=index.cjs.map