@zvndev/yable-react 0.3.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.js CHANGED
@@ -1,10 +1,41 @@
1
1
  import { canCellEnterEditMode, functionalUpdate, createTable, getDefaultLocale, getFirstKeyboardCell, getResolvedFocusedCell, detectCellChanges } from '@zvndev/yable-core';
2
2
  export { CommitError, FormulaEngine, FormulaError, PivotEngine, UndoStack, aggregationFns, createColumnHelper, createLocale, createUndoRedoIntegration, en, filterFns, formulaFunctions, functionalUpdate, generatePivotColumnDefs, getDefaultLocale, getInitialPivotState, getPivotRowModel, resetLocale, setDefaultLocale, sortingFns } from '@zvndev/yable-core';
3
- import React3, { createContext, useCallback, useMemo, useState, useRef, useEffect, useContext } from 'react';
3
+ import React3, { createContext, useCallback, useMemo, useContext, useState, useRef, useEffect } from 'react';
4
4
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
5
5
  import { getInitialRowDragState, moveRow } from '@zvndev/yable-core/features/rowDragging';
6
6
 
7
7
  // src/index.ts
8
+ var YableContext = createContext({});
9
+ function useYableDefaults() {
10
+ return useContext(YableContext);
11
+ }
12
+ function YableProvider({
13
+ children,
14
+ defaultColumnDef,
15
+ striped,
16
+ stickyHeader,
17
+ bordered,
18
+ compact,
19
+ theme,
20
+ direction,
21
+ ariaLabel
22
+ }) {
23
+ const tableProps = {};
24
+ if (striped !== void 0) tableProps.striped = striped;
25
+ if (stickyHeader !== void 0) tableProps.stickyHeader = stickyHeader;
26
+ if (bordered !== void 0) tableProps.bordered = bordered;
27
+ if (compact !== void 0) tableProps.compact = compact;
28
+ if (theme !== void 0) tableProps.theme = theme;
29
+ if (direction !== void 0) tableProps.direction = direction;
30
+ if (ariaLabel !== void 0) tableProps.ariaLabel = ariaLabel;
31
+ const value = {
32
+ tableProps: Object.keys(tableProps).length > 0 ? tableProps : void 0,
33
+ defaultColumnDef
34
+ };
35
+ return /* @__PURE__ */ jsx(YableContext.Provider, { value, children });
36
+ }
37
+
38
+ // src/useTable.ts
8
39
  function shallowEqual(a, b) {
9
40
  if (a === b) return true;
10
41
  const keysA = Object.keys(a);
@@ -17,6 +48,17 @@ function shallowEqual(a, b) {
17
48
  return true;
18
49
  }
19
50
  function useTable(options) {
51
+ const providerDefaults = useYableDefaults();
52
+ const optionsWithDefaults = useMemo(() => {
53
+ if (!providerDefaults.defaultColumnDef) return options;
54
+ return {
55
+ ...options,
56
+ defaultColumnDef: {
57
+ ...providerDefaults.defaultColumnDef,
58
+ ...options.defaultColumnDef
59
+ }
60
+ };
61
+ }, [options, providerDefaults.defaultColumnDef]);
20
62
  const [state, setState] = useState(() => ({
21
63
  sorting: [],
22
64
  columnFilters: [],
@@ -56,14 +98,14 @@ function useTable(options) {
56
98
  }));
57
99
  const stateRef = useRef(state);
58
100
  stateRef.current = state;
59
- const prevOptionsRef = useRef(options);
101
+ const prevOptionsRef = useRef(optionsWithDefaults);
60
102
  const stableOptions = useMemo(() => {
61
- if (shallowEqual(prevOptionsRef.current, options)) {
103
+ if (shallowEqual(prevOptionsRef.current, optionsWithDefaults)) {
62
104
  return prevOptionsRef.current;
63
105
  }
64
- prevOptionsRef.current = options;
65
- return options;
66
- }, [options]);
106
+ prevOptionsRef.current = optionsWithDefaults;
107
+ return optionsWithDefaults;
108
+ }, [optionsWithDefaults]);
67
109
  const onStateChangeRef = useRef(options.onStateChange);
68
110
  onStateChangeRef.current = options.onStateChange;
69
111
  const resolvedState = useMemo(
@@ -73,17 +115,14 @@ function useTable(options) {
73
115
  }),
74
116
  [state, stableOptions.state]
75
117
  );
76
- const onStateChange = useCallback(
77
- (updater) => {
78
- const latest = onStateChangeRef.current;
79
- if (latest) {
80
- latest(updater);
81
- } else {
82
- setState((prev) => functionalUpdate(updater, prev));
83
- }
84
- },
85
- []
86
- );
118
+ const onStateChange = useCallback((updater) => {
119
+ const latest = onStateChangeRef.current;
120
+ if (latest) {
121
+ latest(updater);
122
+ } else {
123
+ setState((prev) => functionalUpdate(updater, prev));
124
+ }
125
+ }, []);
87
126
  const resolvedOptions = useMemo(
88
127
  () => ({
89
128
  ...stableOptions,
@@ -96,12 +135,14 @@ function useTable(options) {
96
135
  if (!tableRef.current) {
97
136
  tableRef.current = createTable(resolvedOptions);
98
137
  } else {
99
- tableRef.current.setOptions((prev) => ({
100
- ...prev,
101
- ...resolvedOptions,
102
- state: resolvedState,
103
- onStateChange
104
- }));
138
+ tableRef.current.setOptions(
139
+ (prev) => ({
140
+ ...prev,
141
+ ...resolvedOptions,
142
+ state: resolvedState,
143
+ onStateChange
144
+ })
145
+ );
105
146
  }
106
147
  useEffect(() => {
107
148
  return () => {
@@ -112,6 +153,122 @@ function useTable(options) {
112
153
  }, []);
113
154
  return tableRef.current;
114
155
  }
156
+ var DEFAULT_PERSISTED_KEYS = [
157
+ "columnVisibility",
158
+ "columnOrder",
159
+ "columnSizing",
160
+ "columnPinning"
161
+ ];
162
+ function resolveStorage(custom) {
163
+ if (custom) return custom;
164
+ if (typeof window !== "undefined") {
165
+ try {
166
+ return window.localStorage;
167
+ } catch {
168
+ return void 0;
169
+ }
170
+ }
171
+ return void 0;
172
+ }
173
+ function pick(obj, keys) {
174
+ const result = {};
175
+ for (const k of keys) {
176
+ if (k in obj) {
177
+ result[k] = obj[k];
178
+ }
179
+ }
180
+ return result;
181
+ }
182
+ function readState(storage, key, version, persistedKeys) {
183
+ if (!storage) return {};
184
+ try {
185
+ const raw = storage.getItem(key);
186
+ if (!raw) return {};
187
+ const envelope = JSON.parse(raw);
188
+ if (envelope.version !== version) {
189
+ storage.removeItem(key);
190
+ return {};
191
+ }
192
+ return pick(envelope.state, persistedKeys);
193
+ } catch {
194
+ if (typeof console !== "undefined") {
195
+ console.warn(`[yable] Failed to read persisted state for key "${key}"`);
196
+ }
197
+ return {};
198
+ }
199
+ }
200
+ function writeState(storage, key, version, state) {
201
+ if (!storage) return;
202
+ try {
203
+ const envelope = { version, state };
204
+ storage.setItem(key, JSON.stringify(envelope));
205
+ } catch {
206
+ if (typeof console !== "undefined") {
207
+ console.warn(`[yable] Failed to persist state for key "${key}" (storage may be full)`);
208
+ }
209
+ }
210
+ }
211
+ function useTablePersistence(options) {
212
+ const {
213
+ key,
214
+ persistedKeys = DEFAULT_PERSISTED_KEYS,
215
+ debounce: debounceMs = 100,
216
+ version = 0,
217
+ storage: customStorage
218
+ } = options;
219
+ const storage = useMemo(() => resolveStorage(customStorage), [customStorage]);
220
+ const initialState = useMemo(
221
+ () => readState(storage, key, version, persistedKeys),
222
+ // eslint-disable-next-line react-hooks/exhaustive-deps -- intentionally read only on mount
223
+ []
224
+ );
225
+ const [state, setState] = useState(initialState);
226
+ const timerRef = useRef(null);
227
+ const keyRef = useRef(key);
228
+ keyRef.current = key;
229
+ const versionRef = useRef(version);
230
+ versionRef.current = version;
231
+ const persistedKeysRef = useRef(persistedKeys);
232
+ persistedKeysRef.current = persistedKeys;
233
+ const debounceRef = useRef(debounceMs);
234
+ debounceRef.current = debounceMs;
235
+ const storageRef = useRef(storage);
236
+ storageRef.current = storage;
237
+ const onStateChange = useCallback((updater) => {
238
+ setState((prev) => {
239
+ const next = functionalUpdate(updater, prev);
240
+ if (timerRef.current !== null) {
241
+ clearTimeout(timerRef.current);
242
+ }
243
+ timerRef.current = setTimeout(() => {
244
+ const sliced = pick(
245
+ next,
246
+ persistedKeysRef.current
247
+ );
248
+ writeState(storageRef.current, keyRef.current, versionRef.current, sliced);
249
+ timerRef.current = null;
250
+ }, debounceRef.current);
251
+ return next;
252
+ });
253
+ }, []);
254
+ useEffect(() => {
255
+ return () => {
256
+ if (timerRef.current !== null) {
257
+ clearTimeout(timerRef.current);
258
+ }
259
+ };
260
+ }, []);
261
+ const clearPersistedState = useCallback(() => {
262
+ const s = storageRef.current;
263
+ if (s) {
264
+ try {
265
+ s.removeItem(keyRef.current);
266
+ } catch {
267
+ }
268
+ }
269
+ }, []);
270
+ return { initialState, state, onStateChange, clearPersistedState };
271
+ }
115
272
  var EMPTY_RESULT = {
116
273
  virtualRows: [],
117
274
  totalHeight: 0,
@@ -125,11 +282,14 @@ function useVirtualization({
125
282
  overscan = 5,
126
283
  estimateRowHeight: _estimateRowHeight,
127
284
  pretextHeights,
128
- pretextPrefixSums
285
+ pretextPrefixSums,
286
+ columnSizingHash
129
287
  }) {
130
288
  const hasPretextHeights = !!(pretextHeights && pretextPrefixSums && pretextHeights.length >= totalRows);
131
289
  const isFixedHeight = typeof rowHeight === "number" && !hasPretextHeights;
132
290
  const heightCacheRef = useRef(/* @__PURE__ */ new Map());
291
+ const heightCacheVersionRef = useRef(0);
292
+ const [heightCacheVersion, setHeightCacheVersion] = useState(0);
133
293
  const getRowHeight = useCallback(
134
294
  (index) => {
135
295
  if (hasPretextHeights) return pretextHeights[index];
@@ -140,7 +300,9 @@ function useVirtualization({
140
300
  heightCacheRef.current.set(index, height);
141
301
  return height;
142
302
  },
143
- [rowHeight, isFixedHeight, hasPretextHeights, pretextHeights]
303
+ // heightCacheVersion forces a new callback identity after cache invalidation
304
+ // eslint-disable-next-line react-hooks/exhaustive-deps
305
+ [rowHeight, isFixedHeight, hasPretextHeights, pretextHeights, heightCacheVersion]
144
306
  );
145
307
  const [scrollState, setScrollState] = useState({ scrollTop: 0, containerHeight: 0 });
146
308
  const rafRef = useRef(null);
@@ -193,6 +355,18 @@ function useVirtualization({
193
355
  heightCacheRef.current.clear();
194
356
  }
195
357
  }, [totalRows, isFixedHeight]);
358
+ useEffect(() => {
359
+ if (!isFixedHeight && columnSizingHash !== void 0) {
360
+ heightCacheRef.current.clear();
361
+ heightCacheVersionRef.current += 1;
362
+ setHeightCacheVersion(heightCacheVersionRef.current);
363
+ }
364
+ }, [columnSizingHash]);
365
+ const invalidateRowHeights = useCallback(() => {
366
+ heightCacheRef.current.clear();
367
+ heightCacheVersionRef.current += 1;
368
+ setHeightCacheVersion(heightCacheVersionRef.current);
369
+ }, []);
196
370
  const scrollTo = useCallback(
197
371
  (index) => {
198
372
  const container = containerRef.current;
@@ -229,10 +403,18 @@ function useVirtualization({
229
403
  total += getRowHeight(i);
230
404
  }
231
405
  return total;
232
- }, [totalRows, rowHeight, isFixedHeight, getRowHeight, hasPretextHeights, pretextPrefixSums]);
406
+ }, [
407
+ totalRows,
408
+ rowHeight,
409
+ isFixedHeight,
410
+ getRowHeight,
411
+ hasPretextHeights,
412
+ pretextPrefixSums,
413
+ heightCacheVersion
414
+ ]);
233
415
  const { scrollTop, containerHeight } = scrollState;
234
416
  if (totalRows === 0) {
235
- return { ...EMPTY_RESULT, scrollTo };
417
+ return { ...EMPTY_RESULT, scrollTo, invalidateRowHeights };
236
418
  }
237
419
  if (containerHeight === 0) {
238
420
  return {
@@ -240,7 +422,8 @@ function useVirtualization({
240
422
  totalHeight,
241
423
  startIndex: 0,
242
424
  endIndex: 0,
243
- scrollTo
425
+ scrollTo,
426
+ invalidateRowHeights
244
427
  };
245
428
  }
246
429
  let startIndex = 0;
@@ -336,7 +519,8 @@ function useVirtualization({
336
519
  totalHeight,
337
520
  startIndex: overscanStart,
338
521
  endIndex: overscanEnd,
339
- scrollTo
522
+ scrollTo,
523
+ invalidateRowHeights
340
524
  };
341
525
  }
342
526
  function binarySearchOffsets(offsets, target) {
@@ -1382,11 +1566,6 @@ function HeaderCell({
1382
1566
  const handleDragStart = useCallback(
1383
1567
  (e) => {
1384
1568
  if (!canReorder) return;
1385
- const target = e.target;
1386
- if (target && target.closest(".yable-resize-handle")) {
1387
- e.preventDefault();
1388
- return;
1389
- }
1390
1569
  e.stopPropagation();
1391
1570
  e.dataTransfer.effectAllowed = "move";
1392
1571
  try {
@@ -1394,8 +1573,9 @@ function HeaderCell({
1394
1573
  e.dataTransfer.setData("text/plain", column.id);
1395
1574
  } catch {
1396
1575
  }
1576
+ table.setColumnDragActive(true);
1397
1577
  },
1398
- [canReorder, column.id]
1578
+ [canReorder, column.id, table]
1399
1579
  );
1400
1580
  const handleDragOver = useCallback(
1401
1581
  (e) => {
@@ -1424,7 +1604,8 @@ function HeaderCell({
1424
1604
  }, []);
1425
1605
  const handleDragEnd = useCallback(() => {
1426
1606
  setDragOver(null);
1427
- }, []);
1607
+ table.setColumnDragActive(false);
1608
+ }, [table]);
1428
1609
  const handleDrop = useCallback(
1429
1610
  (e) => {
1430
1611
  if (!canReorder) return;
@@ -1434,6 +1615,7 @@ function HeaderCell({
1434
1615
  const rect = e.currentTarget.getBoundingClientRect();
1435
1616
  const insertAfter = e.clientX >= rect.left + rect.width / 2;
1436
1617
  setDragOver(null);
1618
+ table.setColumnDragActive(false);
1437
1619
  if (!sourceId || sourceId === column.id) return;
1438
1620
  const state = table.getState();
1439
1621
  const allLeafs = table.getAllLeafColumns();
@@ -1484,18 +1666,24 @@ function HeaderCell({
1484
1666
  "aria-sort": sortDirection === "asc" ? "ascending" : sortDirection === "desc" ? "descending" : canSort ? "none" : void 0,
1485
1667
  role: "columnheader",
1486
1668
  colSpan: header.colSpan,
1487
- draggable: canReorder || void 0,
1488
1669
  onClick: handleHeaderClick,
1489
- onDragStart: canReorder ? handleDragStart : void 0,
1490
1670
  onDragOver: canReorder ? handleDragOver : void 0,
1491
1671
  onDragLeave: canReorder ? handleDragLeave : void 0,
1492
- onDragEnd: canReorder ? handleDragEnd : void 0,
1493
1672
  onDrop: canReorder ? handleDrop : void 0,
1494
1673
  children: [
1495
- /* @__PURE__ */ jsxs("div", { className: "yable-th-content", children: [
1496
- /* @__PURE__ */ jsx("span", { children: headerContent }),
1497
- canSort && /* @__PURE__ */ jsx(SortIndicator, { direction: sortDirection, index: sortIndex > 0 ? sortIndex : void 0 })
1498
- ] }),
1674
+ /* @__PURE__ */ jsxs(
1675
+ "div",
1676
+ {
1677
+ className: "yable-th-content",
1678
+ draggable: canReorder || void 0,
1679
+ onDragStart: canReorder ? handleDragStart : void 0,
1680
+ onDragEnd: canReorder ? handleDragEnd : void 0,
1681
+ children: [
1682
+ /* @__PURE__ */ jsx("span", { children: headerContent }),
1683
+ canSort && /* @__PURE__ */ jsx(SortIndicator, { direction: sortDirection, index: sortIndex > 0 ? sortIndex : void 0 })
1684
+ ]
1685
+ }
1686
+ ),
1499
1687
  canResize && /* @__PURE__ */ jsx(
1500
1688
  "div",
1501
1689
  {
@@ -1503,9 +1691,7 @@ function HeaderCell({
1503
1691
  "data-resizing": column.getIsResizing() || void 0,
1504
1692
  onMouseDown: startResize,
1505
1693
  onTouchStart: startResize,
1506
- onClick: handleResizeClick,
1507
- draggable: false,
1508
- onDragStart: (e) => e.preventDefault()
1694
+ onClick: handleResizeClick
1509
1695
  }
1510
1696
  )
1511
1697
  ]
@@ -1704,6 +1890,11 @@ function TableCell({
1704
1890
  if (!table.getState().cellSelection?.isDragging) return;
1705
1891
  table.endCellRangeSelection();
1706
1892
  }, [table]);
1893
+ const cellClassNameDef = column.columnDef.cellClassName;
1894
+ const userClassName = typeof cellClassNameDef === "function" ? cellClassNameDef(cell.getContext()) : cellClassNameDef;
1895
+ const cellStyleDef = column.columnDef.cellStyle;
1896
+ const userStyle = typeof cellStyleDef === "function" ? cellStyleDef(cell.getContext()) : cellStyleDef;
1897
+ const mergedStyle = userStyle ? { ...style, ...userStyle } : style;
1707
1898
  const classNames = [
1708
1899
  "yable-td",
1709
1900
  isFocused && "yable-cell--focused",
@@ -1711,13 +1902,14 @@ function TableCell({
1711
1902
  selectionEdges?.top && "yable-cell--selection-top",
1712
1903
  selectionEdges?.right && "yable-cell--selection-right",
1713
1904
  selectionEdges?.bottom && "yable-cell--selection-bottom",
1714
- selectionEdges?.left && "yable-cell--selection-left"
1905
+ selectionEdges?.left && "yable-cell--selection-left",
1906
+ userClassName
1715
1907
  ].filter(Boolean).join(" ");
1716
1908
  return /* @__PURE__ */ jsxs(
1717
1909
  "td",
1718
1910
  {
1719
1911
  className: classNames,
1720
- style,
1912
+ style: mergedStyle,
1721
1913
  "data-editing": isEditing || void 0,
1722
1914
  "data-focused": isFocused || void 0,
1723
1915
  "data-pinned": pinned || void 0,
@@ -1857,7 +2049,11 @@ var CellErrorBoundary = class extends React3.Component {
1857
2049
  return this.props.children;
1858
2050
  }
1859
2051
  };
1860
- function TableBody({ table, clickableRows }) {
2052
+ function TableBody({
2053
+ table,
2054
+ clickableRows,
2055
+ colgroup
2056
+ }) {
1861
2057
  const rows = table.getRowModel().rows;
1862
2058
  const visibleColumns = table.getVisibleLeafColumns();
1863
2059
  const activeCell = table.getState().editing.activeCell;
@@ -1874,6 +2070,19 @@ function TableBody({ table, clickableRows }) {
1874
2070
  const estimateRowHeight = options.estimateRowHeight;
1875
2071
  const pretextHeights = options.pretextHeights ?? null;
1876
2072
  const pretextPrefixSums = options.pretextPrefixSums ?? null;
2073
+ const columnSizing = table.getState().columnSizing;
2074
+ const columnSizingHash = useMemo(() => {
2075
+ const entries = Object.entries(columnSizing);
2076
+ if (entries.length === 0) return 0;
2077
+ let h = 0;
2078
+ for (const [key, value] of entries) {
2079
+ for (let i = 0; i < key.length; i++) {
2080
+ h = h * 31 + key.charCodeAt(i) | 0;
2081
+ }
2082
+ h = h * 31 + (value | 0) | 0;
2083
+ }
2084
+ return h;
2085
+ }, [columnSizing]);
1877
2086
  const { virtualRows, totalHeight } = useVirtualization({
1878
2087
  containerRef: scrollContainerRef,
1879
2088
  totalRows: rows.length,
@@ -1881,7 +2090,8 @@ function TableBody({ table, clickableRows }) {
1881
2090
  overscan,
1882
2091
  estimateRowHeight,
1883
2092
  pretextHeights,
1884
- pretextPrefixSums
2093
+ pretextPrefixSums,
2094
+ columnSizingHash
1885
2095
  });
1886
2096
  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"}`;
1887
2097
  useEffect(() => {
@@ -1932,7 +2142,7 @@ function TableBody({ table, clickableRows }) {
1932
2142
  {
1933
2143
  className: "yable-virtual-spacer",
1934
2144
  style: { height: totalHeight, position: "relative" },
1935
- children: /* @__PURE__ */ jsx(
2145
+ children: /* @__PURE__ */ jsxs(
1936
2146
  "table",
1937
2147
  {
1938
2148
  style: {
@@ -1943,35 +2153,38 @@ function TableBody({ table, clickableRows }) {
1943
2153
  tableLayout: "fixed",
1944
2154
  borderCollapse: "collapse"
1945
2155
  },
1946
- children: /* @__PURE__ */ jsx("tbody", { children: virtualRows.map((vRow) => {
1947
- const row = rows[vRow.index];
1948
- if (!row) return null;
1949
- return /* @__PURE__ */ jsx(
1950
- MemoizedTableRow,
1951
- {
1952
- row,
1953
- table,
1954
- rowIndex: vRow.index,
1955
- visibleColumns,
1956
- isSelected: row.getIsSelected(),
1957
- isExpanded: row.getIsExpanded(),
1958
- activeColumnId: activeCell?.rowId === row.id ? activeCell.columnId : void 0,
1959
- focusedColumnIndex: focusedCell?.rowIndex === vRow.index ? focusedCell.columnIndex : null,
1960
- hasFocusedCell: focusedCell !== null,
1961
- cellSelectionKey,
1962
- clickable: clickableRows,
1963
- virtualStyle: {
1964
- position: "absolute",
1965
- top: 0,
1966
- left: 0,
1967
- width: "100%",
1968
- height: vRow.size,
1969
- transform: `translateY(${vRow.start}px)`
1970
- }
1971
- },
1972
- row.id
1973
- );
1974
- }) })
2156
+ children: [
2157
+ colgroup,
2158
+ /* @__PURE__ */ jsx("tbody", { children: virtualRows.map((vRow) => {
2159
+ const row = rows[vRow.index];
2160
+ if (!row) return null;
2161
+ return /* @__PURE__ */ jsx(
2162
+ MemoizedTableRow,
2163
+ {
2164
+ row,
2165
+ table,
2166
+ rowIndex: vRow.index,
2167
+ visibleColumns,
2168
+ isSelected: row.getIsSelected(),
2169
+ isExpanded: row.getIsExpanded(),
2170
+ activeColumnId: activeCell?.rowId === row.id ? activeCell.columnId : void 0,
2171
+ focusedColumnIndex: focusedCell?.rowIndex === vRow.index ? focusedCell.columnIndex : null,
2172
+ hasFocusedCell: focusedCell !== null,
2173
+ cellSelectionKey,
2174
+ clickable: clickableRows,
2175
+ virtualStyle: {
2176
+ position: "absolute",
2177
+ top: 0,
2178
+ left: 0,
2179
+ width: "100%",
2180
+ height: vRow.size,
2181
+ transform: `translateY(${vRow.start}px)`
2182
+ }
2183
+ },
2184
+ row.id
2185
+ );
2186
+ }) })
2187
+ ]
1975
2188
  }
1976
2189
  )
1977
2190
  }
@@ -3250,11 +3463,11 @@ function filterHeaderGroups(groups, visibleColumnIds) {
3250
3463
  }
3251
3464
  function Table({
3252
3465
  table,
3253
- stickyHeader,
3254
- striped,
3255
- bordered,
3256
- compact,
3257
- theme,
3466
+ stickyHeader: stickyHeaderProp,
3467
+ striped: stripedProp,
3468
+ bordered: borderedProp,
3469
+ compact: compactProp,
3470
+ theme: themeProp,
3258
3471
  clickableRows,
3259
3472
  footer,
3260
3473
  loading,
@@ -3268,7 +3481,7 @@ function Table({
3268
3481
  renderLoading,
3269
3482
  children,
3270
3483
  className,
3271
- direction,
3484
+ direction: directionProp,
3272
3485
  statusBar,
3273
3486
  statusBarPanels,
3274
3487
  sidebar,
@@ -3277,9 +3490,17 @@ function Table({
3277
3490
  floatingFilters,
3278
3491
  columnVirtualization,
3279
3492
  columnVirtualizationOverscan,
3280
- ariaLabel,
3493
+ ariaLabel: ariaLabelProp,
3281
3494
  ...rest
3282
3495
  }) {
3496
+ const { tableProps: providerTableProps } = useYableDefaults();
3497
+ const stickyHeader = stickyHeaderProp ?? providerTableProps?.stickyHeader;
3498
+ const striped = stripedProp ?? providerTableProps?.striped;
3499
+ const bordered = borderedProp ?? providerTableProps?.bordered;
3500
+ const compact = compactProp ?? providerTableProps?.compact;
3501
+ const theme = themeProp ?? providerTableProps?.theme;
3502
+ const direction = directionProp ?? providerTableProps?.direction;
3503
+ const ariaLabel = ariaLabelProp ?? providerTableProps?.ariaLabel;
3283
3504
  const [sidebarOpen, setSidebarOpen] = useState(false);
3284
3505
  const [sidebarPanel, setSidebarPanel] = useState(
3285
3506
  defaultSidebarPanel ?? "columns"
@@ -3390,20 +3611,42 @@ function Table({
3390
3611
  },
3391
3612
  [contextMenu, table]
3392
3613
  );
3393
- const tableNode = /* @__PURE__ */ jsxs(
3394
- "table",
3395
- {
3396
- className: "yable-table",
3397
- style: columnVirtualState.isVirtualized ? {
3614
+ const enableRowVirtualization = renderTable.options.enableVirtualization ?? false;
3615
+ const visibleLeafColumns = renderTable.getVisibleLeafColumns();
3616
+ const columnSizing = renderTable.getState().columnSizing;
3617
+ const colgroup = useMemo(() => {
3618
+ if (visibleLeafColumns.length === 0) return null;
3619
+ return /* @__PURE__ */ jsx("colgroup", { children: visibleLeafColumns.map((col) => /* @__PURE__ */ jsx("col", { style: { width: col.getSize() } }, col.id)) });
3620
+ }, [visibleLeafColumns, columnSizing]);
3621
+ const outerTableStyle = useMemo(() => {
3622
+ if (columnVirtualState.isVirtualized) {
3623
+ return {
3398
3624
  width: columnVirtualState.visibleWidth,
3399
3625
  minWidth: columnVirtualState.visibleWidth,
3400
3626
  marginLeft: columnVirtualState.startOffset,
3401
3627
  tableLayout: "fixed"
3402
- } : void 0,
3628
+ };
3629
+ }
3630
+ if (enableRowVirtualization) {
3631
+ return { tableLayout: "fixed" };
3632
+ }
3633
+ return void 0;
3634
+ }, [
3635
+ columnVirtualState.isVirtualized,
3636
+ columnVirtualState.visibleWidth,
3637
+ columnVirtualState.startOffset,
3638
+ enableRowVirtualization
3639
+ ]);
3640
+ const tableNode = /* @__PURE__ */ jsxs(
3641
+ "table",
3642
+ {
3643
+ className: "yable-table",
3644
+ style: outerTableStyle,
3403
3645
  "data-column-virtualized": columnVirtualState.isVirtualized || void 0,
3404
3646
  children: [
3647
+ enableRowVirtualization && colgroup,
3405
3648
  /* @__PURE__ */ jsx(TableHeader, { table: renderTable, floatingFilters }),
3406
- /* @__PURE__ */ jsx(TableBody, { table: renderTable, clickableRows }),
3649
+ /* @__PURE__ */ jsx(TableBody, { table: renderTable, clickableRows, colgroup }),
3407
3650
  footer && /* @__PURE__ */ jsx(TableFooter, { table: renderTable })
3408
3651
  ]
3409
3652
  }
@@ -5411,7 +5654,234 @@ function useTheme(options = {}) {
5411
5654
  containerRef
5412
5655
  };
5413
5656
  }
5657
+ function selectColumn(options = {}) {
5658
+ const { id = "_select", size = 40, headerAriaLabel = "Select all rows" } = options;
5659
+ return {
5660
+ id,
5661
+ header: ({ table }) => /* @__PURE__ */ jsx(
5662
+ "input",
5663
+ {
5664
+ type: "checkbox",
5665
+ checked: table.getIsAllPageRowsSelected(),
5666
+ ref: (el) => {
5667
+ if (el)
5668
+ el.indeterminate = table.getIsSomePageRowsSelected() && !table.getIsAllPageRowsSelected();
5669
+ },
5670
+ onChange: () => table.toggleAllPageRowsSelected(),
5671
+ "aria-label": headerAriaLabel
5672
+ }
5673
+ ),
5674
+ cell: ({ row }) => /* @__PURE__ */ jsx(
5675
+ "input",
5676
+ {
5677
+ type: "checkbox",
5678
+ checked: row.getIsSelected(),
5679
+ disabled: !row.getCanSelect(),
5680
+ onChange: row.getToggleSelectedHandler(),
5681
+ "aria-label": `Select row`
5682
+ }
5683
+ ),
5684
+ size,
5685
+ enableSorting: false,
5686
+ enableColumnFilter: false,
5687
+ enableResizing: false,
5688
+ enableReorder: false,
5689
+ enableHiding: false,
5690
+ lockVisible: true
5691
+ };
5692
+ }
5693
+
5694
+ // src/presets/rowNumberColumn.tsx
5695
+ function rowNumberColumn(options = {}) {
5696
+ const { id = "_rowNumber", header = "#", size = 50, startFrom = 1 } = options;
5697
+ return {
5698
+ id,
5699
+ header,
5700
+ cell: ({ row }) => row.index + startFrom,
5701
+ size,
5702
+ enableSorting: false,
5703
+ enableColumnFilter: false,
5704
+ enableResizing: false,
5705
+ enableReorder: false,
5706
+ lockVisible: true
5707
+ };
5708
+ }
5709
+ function actionsColumn(options) {
5710
+ const { id = "_actions", header = "", size = 100, actions } = options;
5711
+ return {
5712
+ id,
5713
+ header,
5714
+ cell: (ctx) => {
5715
+ const items = typeof actions === "function" ? actions(ctx.row) : actions;
5716
+ return /* @__PURE__ */ jsx("div", { className: "yable-cell-actions", children: items.filter((a) => !a.hidden || !a.hidden(ctx.row)).map((action, i) => /* @__PURE__ */ jsx(
5717
+ "button",
5718
+ {
5719
+ type: "button",
5720
+ className: "yable-action-btn",
5721
+ disabled: action.disabled?.(ctx.row),
5722
+ onClick: (e) => {
5723
+ e.stopPropagation();
5724
+ action.onClick(ctx.row);
5725
+ },
5726
+ title: action.label,
5727
+ children: action.icon ?? action.label
5728
+ },
5729
+ i
5730
+ )) });
5731
+ },
5732
+ size,
5733
+ enableSorting: false,
5734
+ enableColumnFilter: false,
5735
+ enableResizing: false,
5736
+ enableReorder: false
5737
+ };
5738
+ }
5739
+ function expandColumn(options = {}) {
5740
+ const { id = "_expand", size = 40 } = options;
5741
+ return {
5742
+ id,
5743
+ header: () => null,
5744
+ cell: ({ row }) => {
5745
+ if (!row.getCanExpand()) return null;
5746
+ return /* @__PURE__ */ jsx(
5747
+ "button",
5748
+ {
5749
+ type: "button",
5750
+ className: "yable-expand-btn",
5751
+ onClick: (e) => {
5752
+ e.stopPropagation();
5753
+ row.toggleExpanded();
5754
+ },
5755
+ "aria-expanded": row.getIsExpanded(),
5756
+ "aria-label": row.getIsExpanded() ? "Collapse row" : "Expand row",
5757
+ children: /* @__PURE__ */ jsx(
5758
+ "span",
5759
+ {
5760
+ className: "yable-expand-icon",
5761
+ style: {
5762
+ display: "inline-block",
5763
+ transform: row.getIsExpanded() ? "rotate(90deg)" : "rotate(0deg)",
5764
+ transition: "transform 150ms ease"
5765
+ },
5766
+ children: "\u25B6"
5767
+ }
5768
+ )
5769
+ }
5770
+ );
5771
+ },
5772
+ size,
5773
+ enableSorting: false,
5774
+ enableColumnFilter: false,
5775
+ enableResizing: false,
5776
+ enableReorder: false,
5777
+ lockVisible: true
5778
+ };
5779
+ }
5780
+ function CellStack({ children, gap = 2 }) {
5781
+ return /* @__PURE__ */ jsx(
5782
+ "div",
5783
+ {
5784
+ className: "yable-cell-stack",
5785
+ style: {
5786
+ display: "flex",
5787
+ flexDirection: "column",
5788
+ gap
5789
+ },
5790
+ children
5791
+ }
5792
+ );
5793
+ }
5794
+ function CellRow({ children, gap = 6, align = "center", justify = "start" }) {
5795
+ const justifyMap = {
5796
+ start: "flex-start",
5797
+ center: "center",
5798
+ end: "flex-end",
5799
+ between: "space-between"
5800
+ };
5801
+ return /* @__PURE__ */ jsx(
5802
+ "div",
5803
+ {
5804
+ className: "yable-cell-row",
5805
+ style: {
5806
+ display: "flex",
5807
+ flexDirection: "row",
5808
+ alignItems: align === "baseline" ? "baseline" : align === "start" ? "flex-start" : align === "end" ? "flex-end" : "center",
5809
+ justifyContent: justifyMap[justify] || "flex-start",
5810
+ gap
5811
+ },
5812
+ children
5813
+ }
5814
+ );
5815
+ }
5816
+ function CellWithIcon({ icon, children, gap = 6, iconSize }) {
5817
+ return /* @__PURE__ */ jsxs(
5818
+ "div",
5819
+ {
5820
+ className: "yable-cell-with-icon",
5821
+ style: {
5822
+ display: "flex",
5823
+ flexDirection: "row",
5824
+ alignItems: "center",
5825
+ gap
5826
+ },
5827
+ children: [
5828
+ /* @__PURE__ */ jsx(
5829
+ "span",
5830
+ {
5831
+ className: "yable-cell-icon",
5832
+ style: {
5833
+ display: "inline-flex",
5834
+ alignItems: "center",
5835
+ justifyContent: "center",
5836
+ flexShrink: 0,
5837
+ ...iconSize ? { width: iconSize, height: iconSize } : {}
5838
+ },
5839
+ children: icon
5840
+ }
5841
+ ),
5842
+ /* @__PURE__ */ jsx("span", { className: "yable-cell-icon-content", style: { minWidth: 0 }, children })
5843
+ ]
5844
+ }
5845
+ );
5846
+ }
5847
+ function CellText({
5848
+ children,
5849
+ variant = "primary",
5850
+ bold,
5851
+ truncate,
5852
+ size = "md"
5853
+ }) {
5854
+ const fontSizeMap = { sm: "0.75rem", md: "0.875rem", lg: "1rem" };
5855
+ return /* @__PURE__ */ jsx(
5856
+ "span",
5857
+ {
5858
+ className: `yable-cell-text yable-cell-text--${variant}`,
5859
+ style: {
5860
+ fontSize: fontSizeMap[size],
5861
+ fontWeight: bold ? 600 : void 0,
5862
+ color: variant === "secondary" ? "var(--yable-text-secondary, #6b7280)" : variant === "muted" ? "var(--yable-text-muted, #9ca3af)" : void 0,
5863
+ ...truncate ? {
5864
+ overflow: "hidden",
5865
+ textOverflow: "ellipsis",
5866
+ whiteSpace: "nowrap"
5867
+ } : {}
5868
+ },
5869
+ children
5870
+ }
5871
+ );
5872
+ }
5873
+
5874
+ // src/utils/mergeEditChanges.ts
5875
+ function mergeEditChanges(data, changes, getRowId = (_, i) => String(i)) {
5876
+ const changeKeys = Object.keys(changes);
5877
+ if (changeKeys.length === 0) return data;
5878
+ return data.map((row, i) => {
5879
+ const id = getRowId(row, i);
5880
+ const patch = changes[id];
5881
+ return patch ? { ...row, ...patch } : row;
5882
+ });
5883
+ }
5414
5884
 
5415
- export { CellBadge, CellBoolean, CellCheckbox, CellCurrency, CellDate, CellDatePicker, CellErrorBoundary, CellInput, CellLink, CellNumeric, CellProgress, CellRating, CellSelect, CellStatus, CellStatusBadge, CellToggle, ColumnsPanel, ContextMenu, ContextMenuItem, DEFAULT_TEXT_RECIPE, DragHandle, ErrorBoundary, ExpandIcon, FillHandle, FiltersPanel, FlashCell, FloatingFilter, GlobalFilter, LoadingOverlay, MasterDetail, NoRowsOverlay, Pagination, PivotConfigPanel, PrintLayout, SetFilter, Sidebar, SortIndicator, StatusBar, StatusBarPanelComponent, Table, TableBody, TableCell, TableFooter, TableHeader, TableProvider, Tooltip, TreeToggle, getMeasureRecipeForCellType, getRegisteredCellTypes, resolveMeasureRecipe, useAutoMeasurements, useCellFlash, useClipboard, useColumnVirtualization, useContextMenu, useFillHandle, useKeyboardNavigation, usePretextMeasurement, usePrintLayout, useRowAnimation, useRowDrag, useTable, useTableContext, useTableRowHeights, useTheme, useTooltip, useVirtualization };
5885
+ export { CellBadge, CellBoolean, CellCheckbox, CellCurrency, CellDate, CellDatePicker, CellErrorBoundary, CellInput, CellLink, CellNumeric, CellProgress, CellRating, CellRow, CellSelect, CellStack, CellStatus, CellStatusBadge, CellText, CellToggle, CellWithIcon, ColumnsPanel, ContextMenu, ContextMenuItem, DEFAULT_TEXT_RECIPE, DragHandle, ErrorBoundary, ExpandIcon, FillHandle, FiltersPanel, FlashCell, FloatingFilter, GlobalFilter, LoadingOverlay, MasterDetail, NoRowsOverlay, Pagination, PivotConfigPanel, PrintLayout, SetFilter, Sidebar, SortIndicator, StatusBar, StatusBarPanelComponent, Table, TableBody, TableCell, TableFooter, TableHeader, TableProvider, Tooltip, TreeToggle, YableProvider, actionsColumn, expandColumn, getMeasureRecipeForCellType, getRegisteredCellTypes, mergeEditChanges, resolveMeasureRecipe, rowNumberColumn, selectColumn, useAutoMeasurements, useCellFlash, useClipboard, useColumnVirtualization, useContextMenu, useFillHandle, useKeyboardNavigation, usePretextMeasurement, usePrintLayout, useRowAnimation, useRowDrag, useTable, useTableContext, useTablePersistence, useTableRowHeights, useTheme, useTooltip, useVirtualization, useYableDefaults };
5416
5886
  //# sourceMappingURL=index.js.map
5417
5887
  //# sourceMappingURL=index.js.map