@rozie-ui/data-table-solid 0.7.4 → 0.8.1

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.
@@ -93,6 +93,74 @@ const columnSpecsEquivalent = (a, b, depth) => {
93
93
  return true;
94
94
  };
95
95
  //#endregion
96
+ //#region src/helpers/columnRuleUtils.ts
97
+ const FILTER_FN_NAMES = [
98
+ "includesString",
99
+ "includesStringSensitive",
100
+ "equalsString",
101
+ "arrIncludes",
102
+ "arrIncludesAll",
103
+ "arrIncludesSome",
104
+ "equals",
105
+ "weakEquals",
106
+ "inNumberRange"
107
+ ];
108
+ const SORTING_FN_NAMES = [
109
+ "alphanumeric",
110
+ "alphanumericCaseSensitive",
111
+ "text",
112
+ "textCaseSensitive",
113
+ "datetime",
114
+ "basic"
115
+ ];
116
+ const warned = /* @__PURE__ */ new Set();
117
+ const resolveName = (value, names, kind, columnId, warn) => {
118
+ if (value === "auto" || names.includes(value)) return value;
119
+ const key = kind + "\0" + columnId + "\0" + value;
120
+ if (!warned.has(key)) {
121
+ warned.add(key);
122
+ warn(`[data-table] column "${columnId}": unknown ${kind} "${value}"; falling back to "auto".`);
123
+ }
124
+ return "auto";
125
+ };
126
+ const resolveFilterFn = (value, columnId, warn = console.warn) => {
127
+ if (value == null) return void 0;
128
+ if (typeof value === "string") return resolveName(value, FILTER_FN_NAMES, "filterFn", columnId, warn);
129
+ if (typeof value !== "function") return void 0;
130
+ const fn = value;
131
+ const adapter = (row, colId, filterValue) => {
132
+ try {
133
+ return fn(row.getValue(colId), filterValue, row.original);
134
+ } catch (err) {
135
+ return true;
136
+ }
137
+ };
138
+ if (fn.autoRemove !== void 0) adapter.autoRemove = fn.autoRemove;
139
+ if (fn.resolveFilterValue !== void 0) adapter.resolveFilterValue = fn.resolveFilterValue;
140
+ return adapter;
141
+ };
142
+ const resolveSortingFn = (value, columnId, warn = console.warn) => {
143
+ if (value == null) return void 0;
144
+ if (typeof value === "string") return resolveName(value, SORTING_FN_NAMES, "sortingFn", columnId, warn);
145
+ if (typeof value !== "function") return void 0;
146
+ const fn = value;
147
+ return (rowA, rowB, colId) => {
148
+ try {
149
+ return fn(rowA.getValue(colId), rowB.getValue(colId), rowA.original, rowB.original);
150
+ } catch (err) {
151
+ return 0;
152
+ }
153
+ };
154
+ };
155
+ const columnRuleKeys = (spec, columnId, warn) => {
156
+ const out = {};
157
+ const f = resolveFilterFn(spec?.filterFn, columnId, warn);
158
+ if (f !== void 0) out.filterFn = f;
159
+ const s = resolveSortingFn(spec?.sortingFn, columnId, warn);
160
+ if (s !== void 0) out.sortingFn = s;
161
+ return out;
162
+ };
163
+ //#endregion
96
164
  //#region src/helpers/indexMath.ts
97
165
  const clamp = (v, lo, hi) => v < lo ? lo : v > hi ? hi : v;
98
166
  const focusables = (cellEl) => {
@@ -101,6 +169,24 @@ const focusables = (cellEl) => {
101
169
  };
102
170
  const applyUpdater = (updater, current) => typeof updater === "function" ? updater(current) : updater;
103
171
  //#endregion
172
+ //#region src/helpers/activeFollow.ts
173
+ function resolveFollow(prevIds, activeRow, nextIds, viewChangeOrWrite) {
174
+ if (!prevIds || !Number.isInteger(activeRow) || activeRow < 0 || activeRow >= prevIds.length) return { kind: "clamp" };
175
+ const id = prevIds[activeRow];
176
+ if (nextIds[activeRow] === id) return { kind: "stay" };
177
+ const index = nextIds.indexOf(id);
178
+ if (index >= 0) return {
179
+ kind: "move",
180
+ index
181
+ };
182
+ return viewChangeOrWrite ? { kind: "park" } : { kind: "clamp" };
183
+ }
184
+ function sameIdSequence(a, b) {
185
+ if (!a || a.length !== b.length) return false;
186
+ for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false;
187
+ return true;
188
+ }
189
+ //#endregion
104
190
  //#region src/helpers/tsvGrid.ts
105
191
  const escapeTsvField = (s) => {
106
192
  if (s.indexOf(" ") >= 0 || s.indexOf("\n") >= 0 || s.indexOf("\r") >= 0 || s.indexOf("\"") >= 0) return "\"" + s.replace(/"/g, "\"\"") + "\"";
@@ -236,6 +322,150 @@ const indexOfRowIn = (rows, rowOriginal, rowId) => {
236
322
  return -1;
237
323
  };
238
324
  //#endregion
325
+ //#region src/helpers/heldView.ts
326
+ const CLEAN_VIEW_STATE = Object.freeze({
327
+ held: false,
328
+ filterMismatches: 0,
329
+ sortMismatches: 0
330
+ });
331
+ const viewStatesEqual = (a, b) => a.held === b.held && a.filterMismatches === b.filterMismatches && a.sortMismatches === b.sortMismatches;
332
+ const changedIndices = (prev, next) => {
333
+ const out = [];
334
+ if (prev.length !== next.length) {
335
+ const n = Math.max(prev.length, next.length);
336
+ for (let i = 0; i < n; i++) out.push(i);
337
+ return out;
338
+ }
339
+ for (let i = 0; i < next.length; i++) if (prev[i] !== next[i]) out.push(i);
340
+ return out;
341
+ };
342
+ const rowIds = (model) => (model?.rows ?? []).map((r) => r.id);
343
+ const createHoldController = () => {
344
+ let active = false;
345
+ let heldMembership = null;
346
+ let heldOrder = null;
347
+ const written = /* @__PURE__ */ new Set();
348
+ let version = 0;
349
+ let liveFilteredIds = /* @__PURE__ */ new Set();
350
+ let liveSortedIds = [];
351
+ const release = () => {
352
+ heldMembership = null;
353
+ heldOrder = null;
354
+ written.clear();
355
+ version++;
356
+ };
357
+ const wrapFiltered = (base) => (table) => {
358
+ const getBase = base(table);
359
+ let lastBase = void 0;
360
+ let lastVersion = -1;
361
+ let lastOut = void 0;
362
+ return () => {
363
+ const baseModel = getBase();
364
+ if (!heldMembership) return baseModel;
365
+ if (baseModel === lastBase && version === lastVersion) return lastOut;
366
+ liveFilteredIds = new Set(rowIds(baseModel));
367
+ const held = heldMembership;
368
+ const rows = table.getCoreRowModel().rows.filter((r) => held.has(r.id));
369
+ const rowsById = {};
370
+ for (const r of rows) rowsById[r.id] = r;
371
+ lastBase = baseModel;
372
+ lastVersion = version;
373
+ lastOut = {
374
+ rows,
375
+ flatRows: rows,
376
+ rowsById
377
+ };
378
+ return lastOut;
379
+ };
380
+ };
381
+ const wrapSorted = (base) => (table) => {
382
+ const getBase = base(table);
383
+ let lastBase = void 0;
384
+ let lastVersion = -1;
385
+ let lastOut = void 0;
386
+ return () => {
387
+ const baseModel = getBase();
388
+ if (!heldOrder) return baseModel;
389
+ if (baseModel === lastBase && version === lastVersion) return lastOut;
390
+ const baseRows = baseModel?.rows ?? [];
391
+ liveSortedIds = baseRows.map((r) => r.id);
392
+ const byId = /* @__PURE__ */ new Map();
393
+ for (const r of baseRows) byId.set(r.id, r);
394
+ const rows = [];
395
+ for (const id of heldOrder) {
396
+ const r = byId.get(id);
397
+ if (r) {
398
+ rows.push(r);
399
+ byId.delete(id);
400
+ }
401
+ }
402
+ for (const r of baseRows) if (byId.has(r.id)) rows.push(r);
403
+ lastBase = baseModel;
404
+ lastVersion = version;
405
+ lastOut = {
406
+ rows,
407
+ flatRows: rows,
408
+ rowsById: baseModel?.rowsById ?? {}
409
+ };
410
+ return lastOut;
411
+ };
412
+ };
413
+ return {
414
+ setActive(next) {
415
+ const was = active;
416
+ active = !!next;
417
+ if (!active && was) release();
418
+ },
419
+ isActive: () => active,
420
+ isHolding: () => heldMembership !== null,
421
+ capture(table) {
422
+ if (!active || heldMembership) return;
423
+ const filtered = table.getFilteredRowModel();
424
+ const sorted = typeof table.getSortedRowModel === "function" ? table.getSortedRowModel() : filtered;
425
+ heldMembership = new Set(rowIds(filtered));
426
+ heldOrder = rowIds(sorted);
427
+ written.clear();
428
+ version++;
429
+ },
430
+ release,
431
+ noteWrite(prev, next) {
432
+ if (!heldMembership) return;
433
+ for (const i of changedIndices(prev, next)) written.add(i);
434
+ },
435
+ wrapFiltered,
436
+ wrapSorted,
437
+ viewState(table) {
438
+ if (!active) return CLEAN_VIEW_STATE;
439
+ if (!heldMembership || !heldOrder) return {
440
+ held: true,
441
+ filterMismatches: 0,
442
+ sortMismatches: 0
443
+ };
444
+ table.getSortedRowModel();
445
+ const held = heldMembership;
446
+ let filterMismatches = 0;
447
+ for (const id of held) if (!liveFilteredIds.has(id)) filterMismatches++;
448
+ const coreRows = table.getCoreRowModel().rows;
449
+ for (const r of coreRows) if (!held.has(r.id) && liveFilteredIds.has(r.id)) filterMismatches++;
450
+ const heldPos = /* @__PURE__ */ new Map();
451
+ heldOrder.forEach((id, i) => heldPos.set(id, i));
452
+ const livePos = /* @__PURE__ */ new Map();
453
+ liveSortedIds.forEach((id, i) => livePos.set(id, i));
454
+ let sortMismatches = 0;
455
+ for (const i of written) {
456
+ const id = coreRows[i]?.id;
457
+ if (id === void 0 || !heldPos.has(id)) continue;
458
+ if (heldPos.get(id) !== livePos.get(id)) sortMismatches++;
459
+ }
460
+ return {
461
+ held: true,
462
+ filterMismatches,
463
+ sortMismatches
464
+ };
465
+ }
466
+ };
467
+ };
468
+ //#endregion
239
469
  //#region src/DataTable.tsx
240
470
  __rozieInjectStyle("DataTable-d5dcab4c", `[data-rozie-s-d5dcab4c]:host {
241
471
  --rdt-color: var(--rozie-data-table-fg, var(--rdt-ds-fg));
@@ -653,6 +883,7 @@ function DataTable(_props) {
653
883
  singleClickEdit: false,
654
884
  undoable: false,
655
885
  undoLimit: 100,
886
+ holdView: false,
656
887
  virtual: false,
657
888
  estimateRowHeight: 40,
658
889
  autoMeasure: false,
@@ -684,6 +915,7 @@ function DataTable(_props) {
684
915
  "singleClickEdit",
685
916
  "undoable",
686
917
  "undoLimit",
918
+ "holdView",
687
919
  "virtual",
688
920
  "estimateRowHeight",
689
921
  "autoMeasure",
@@ -706,6 +938,7 @@ function DataTable(_props) {
706
938
  "onSelectionChange",
707
939
  "onSortChange",
708
940
  "onVisibilityChange",
941
+ "onViewStateChange",
709
942
  "onVisibleRangeChange",
710
943
  "groupBarSlot",
711
944
  "selectAllSlot",
@@ -756,7 +989,9 @@ function DataTable(_props) {
756
989
  redo,
757
990
  canUndo,
758
991
  canRedo,
759
- clearHistory
992
+ clearHistory,
993
+ refreshView,
994
+ getViewState
760
995
  });
761
996
  });
762
997
  const __ctx_data_table_columns = rozieContext("data-table:columns");
@@ -845,6 +1080,7 @@ function DataTable(_props) {
845
1080
  columnOrder(),
846
1081
  columnPinning(),
847
1082
  local.selectionMode,
1083
+ local.holdView,
848
1084
  (data() || []).length,
849
1085
  data(),
850
1086
  dataDefault(),
@@ -869,13 +1105,15 @@ function DataTable(_props) {
869
1105
  onMount(() => {
870
1106
  setDataDefault(data() || []);
871
1107
  seedColumnPinning();
1108
+ const cols0 = tableColumns();
1109
+ lastColumnSig = columnSetSignature(cols0);
872
1110
  table = createTable({
873
1111
  data: feedData(),
874
- columns: tableColumns(),
1112
+ columns: cols0,
875
1113
  state: currentState(),
876
1114
  getCoreRowModel: getCoreRowModel(),
877
- getSortedRowModel: getSortedRowModel(),
878
- getFilteredRowModel: getFilteredRowModel(),
1115
+ getSortedRowModel: heldSortedRowModel,
1116
+ getFilteredRowModel: heldFilteredRowModel,
879
1117
  getPaginationRowModel: getPaginationRowModel(),
880
1118
  getExpandedRowModel: getExpandedRowModel(),
881
1119
  getSubRows: local.getSubRows || void 0,
@@ -910,6 +1148,21 @@ function DataTable(_props) {
910
1148
  renderFallbackValue: null,
911
1149
  onStateChange: () => {}
912
1150
  });
1151
+ table.resetPageIndex = () => {
1152
+ if (!table) return;
1153
+ const pg = table.getState().pagination;
1154
+ writePagination({
1155
+ pageIndex: pg.pageIndex,
1156
+ pageSize: pg.pageSize
1157
+ });
1158
+ };
1159
+ const st0 = currentState();
1160
+ lastHoldSorting = st0.sorting;
1161
+ lastHoldColumnFilters = st0.columnFilters;
1162
+ lastHoldGlobalFilter = st0.globalFilter;
1163
+ lastPageGrouping = st0.grouping;
1164
+ syncHoldActive(st0);
1165
+ lastPropsData = data();
913
1166
  refreshRowModel = () => {
914
1167
  if (!table) return;
915
1168
  const nextRows = windowSource().slice();
@@ -924,7 +1177,17 @@ function DataTable(_props) {
924
1177
  }
925
1178
  remeasureColumnWindow();
926
1179
  const nextRowCount = nextRows.length;
927
- clampActiveCell(nextRowCount, nextRows.length ? nextRows[0].getVisibleCells().length : nextGroups.length ? (nextGroups[nextGroups.length - 1].headers || []).length : 0);
1180
+ const nextColCount = nextRows.length ? nextRows[0].getVisibleCells().length : nextGroups.length ? (nextGroups[nextGroups.length - 1].headers || []).length : 0;
1181
+ const nextIds = nextRows.map((r) => r.id);
1182
+ const fedData = table.options.data;
1183
+ const dataChanged = fedData !== lastFedData;
1184
+ lastFedData = fedData;
1185
+ const viewChangeOrWrite = pendingViewChange || pendingWrite && dataChanged;
1186
+ const swapWithoutIds = dataChanged && !pendingWrite && !pendingViewChange && table.options.getRowId == null;
1187
+ pendingViewChange = false;
1188
+ if (dataChanged) pendingWrite = false;
1189
+ followActiveCell(swapWithoutIds ? null : prevRowIds, nextIds, viewChangeOrWrite, nextRowCount, nextColCount, pendingEditFollow != null);
1190
+ prevRowIds = nextIds;
928
1191
  const pgState = table.getState().pagination;
929
1192
  const pc = table.getPageCount();
930
1193
  if (pc > 0 && pgState.pageIndex > pc - 1) writePagination({
@@ -940,6 +1203,11 @@ function DataTable(_props) {
940
1203
  syncIndeterminate();
941
1204
  if (typeof queueMicrotask !== "undefined") queueMicrotask(syncIndeterminate);
942
1205
  else Promise.resolve().then(syncIndeterminate);
1206
+ const vs = holdCtrl.viewState(table);
1207
+ if (!viewStatesEqual(vs, lastViewState)) {
1208
+ lastViewState = vs;
1209
+ _props.onViewStateChange?.(vs);
1210
+ }
943
1211
  };
944
1212
  refreshRowModel();
945
1213
  gridRoot = __rozieRootRef ? __rozieRootRef.querySelector(".rozie-data-table") : null;
@@ -1031,6 +1299,48 @@ function DataTable(_props) {
1031
1299
  let undoStack = [];
1032
1300
  let redoStack = [];
1033
1301
  let restoringHistory = false;
1302
+ const holdCtrl = createHoldController();
1303
+ const heldFilteredRowModel = holdCtrl.wrapFiltered(getFilteredRowModel());
1304
+ const heldSortedRowModel = holdCtrl.wrapSorted(getSortedRowModel());
1305
+ let pendingViewChange = false;
1306
+ let pendingWrite = false;
1307
+ let prevRowIds = null;
1308
+ let lastFedData = null;
1309
+ let lastViewState = CLEAN_VIEW_STATE;
1310
+ let lastHoldSorting = null;
1311
+ let lastHoldColumnFilters = null;
1312
+ let lastHoldGlobalFilter = null;
1313
+ let holdFallbackWarned = false;
1314
+ let lastPageGrouping = null;
1315
+ let lastColumnSig = "";
1316
+ function columnSetSignature(defs) {
1317
+ let sig = "";
1318
+ const stack = (defs || []).slice().reverse();
1319
+ while (stack.length) {
1320
+ const d = stack.pop();
1321
+ if (!d) continue;
1322
+ sig += String(d.id) + (d.enableSorting ? "+" : "") + (d.enableColumnFilter ? "*" : "") + "|";
1323
+ if (Array.isArray(d.columns)) for (let i = d.columns.length - 1; i >= 0; i--) stack.push(d.columns[i]);
1324
+ }
1325
+ return sig;
1326
+ }
1327
+ let lastPropsData = null;
1328
+ function isExternalSwap() {
1329
+ const pd = data();
1330
+ if (pd === lastPropsData) return false;
1331
+ return !(pd != null && pd[DATA_WRITE_TOKEN_KEY] != null);
1332
+ }
1333
+ function syncHoldActive(st) {
1334
+ const wanted = local.holdView === true;
1335
+ const active = wanted && local.manual !== true && local.getSubRows == null && !(st.grouping || []).length;
1336
+ const wasHolding = holdCtrl.isHolding();
1337
+ holdCtrl.setActive(active);
1338
+ if (wasHolding && !holdCtrl.isHolding()) pendingViewChange = true;
1339
+ if (wanted && !active && !holdFallbackWarned) {
1340
+ holdFallbackWarned = true;
1341
+ console.warn("[rozie-data-table] holdView falls back to live behaviour while grouping is active, in manual mode, or with tree data (getSubRows); the view is not held.");
1342
+ }
1343
+ }
1034
1344
  let expandedTouched = false;
1035
1345
  function groupingActiveDefault() {
1036
1346
  return ((grouping() != null ? grouping() : groupingDefault()) || []).length > 0;
@@ -1164,6 +1474,7 @@ function DataTable(_props) {
1164
1474
  expandable: c.expandable === true,
1165
1475
  groupable: c.groupable !== false,
1166
1476
  aggregationFn: wrapAggregationFn(c.aggregationFn),
1477
+ ...columnRuleKeys(c, id),
1167
1478
  pinned: c.pinned != null ? c.pinned : "",
1168
1479
  width: c.width != null ? c.width : "",
1169
1480
  ...parseWidthToSize(c.width) != null ? { size: parseWidthToSize(c.width) } : {},
@@ -1207,6 +1518,7 @@ function DataTable(_props) {
1207
1518
  expandable: spec.expandable === true,
1208
1519
  groupable: spec.groupable !== false,
1209
1520
  aggregationFn: wrapAggregationFn(spec.aggregationFn),
1521
+ ...columnRuleKeys(spec, id),
1210
1522
  pinned: spec.pinned != null ? spec.pinned : "",
1211
1523
  width: spec.width != null ? spec.width : "",
1212
1524
  ...parseWidthToSize(spec.width) != null ? { size: parseWidthToSize(spec.width) } : {},
@@ -1363,6 +1675,9 @@ function DataTable(_props) {
1363
1675
  recordSnapshot(currentData());
1364
1676
  emitHistoryChangeIfEdged(prevU, prevR);
1365
1677
  }
1678
+ if (table) holdCtrl.capture(table);
1679
+ holdCtrl.noteWrite(currentData() || [], Array.isArray(next) ? next : []);
1680
+ pendingWrite = true;
1366
1681
  const fresh = Array.isArray(next) ? next.slice() : next;
1367
1682
  try {
1368
1683
  Object.defineProperty(fresh, DATA_WRITE_TOKEN_KEY, {
@@ -1986,13 +2301,60 @@ function DataTable(_props) {
1986
2301
  if (local.selectionMode === "none") return false;
1987
2302
  return lazyRowsActive() ? (row) => !rowIsLazyPlaceholder(row) : true;
1988
2303
  }
2304
+ function writePaginationSoon(next) {
2305
+ if (!programmatic) {
2306
+ writePagination(next);
2307
+ return;
2308
+ }
2309
+ const run = () => writePagination(next);
2310
+ if (typeof queueMicrotask !== "undefined") queueMicrotask(run);
2311
+ else Promise.resolve().then(run);
2312
+ }
1989
2313
  function reFeed() {
1990
2314
  if (!table) return;
2315
+ const st0 = currentState();
2316
+ syncHoldActive(st0);
2317
+ let st = st0;
2318
+ let pageReset = null;
2319
+ const groupingChanged = st0.grouping !== lastPageGrouping;
2320
+ lastPageGrouping = st0.grouping;
2321
+ const viewChanged = st0.sorting !== lastHoldSorting || st0.columnFilters !== lastHoldColumnFilters || st0.globalFilter !== lastHoldGlobalFilter;
2322
+ if (viewChanged) {
2323
+ lastHoldSorting = st0.sorting;
2324
+ lastHoldColumnFilters = st0.columnFilters;
2325
+ lastHoldGlobalFilter = st0.globalFilter;
2326
+ holdCtrl.release();
2327
+ pendingViewChange = true;
2328
+ }
2329
+ if ((viewChanged || groupingChanged) && local.manual !== true && st0.pagination && st0.pagination.pageIndex !== 0) {
2330
+ pageReset = {
2331
+ pageIndex: 0,
2332
+ pageSize: st0.pagination.pageSize
2333
+ };
2334
+ st = {
2335
+ ...st0,
2336
+ pagination: pageReset
2337
+ };
2338
+ }
2339
+ const cols = tableColumns();
2340
+ const colSig = columnSetSignature(cols);
2341
+ if (colSig !== lastColumnSig) {
2342
+ lastColumnSig = colSig;
2343
+ st = {
2344
+ ...st,
2345
+ sorting: [...st.sorting || []],
2346
+ columnFilters: [...st.columnFilters || []]
2347
+ };
2348
+ }
2349
+ if (holdCtrl.isHolding() && isExternalSwap()) holdCtrl.release();
2350
+ if (pendingWrite && isExternalSwap()) pendingWrite = false;
1991
2351
  table.setOptions((prev) => ({
1992
2352
  ...prev,
1993
2353
  data: feedData(),
1994
- columns: tableColumns(),
1995
- state: currentState(),
2354
+ columns: cols,
2355
+ state: st,
2356
+ getFilteredRowModel: heldFilteredRowModel,
2357
+ getSortedRowModel: heldSortedRowModel,
1996
2358
  enableRowSelection: rowSelectionOption(),
1997
2359
  enableMultiRowSelection: local.selectionMode === "multiple",
1998
2360
  rowCount: local.rowCount ?? void 0,
@@ -2020,15 +2382,19 @@ function DataTable(_props) {
2020
2382
  onColumnSizingInfoChange: onColumnSizingInfoChangeCb
2021
2383
  }));
2022
2384
  if (refreshRowModel) refreshRowModel();
2385
+ if (pageReset) writePaginationSoon(pageReset);
2023
2386
  }
2024
- let lastPropsData = null;
2025
2387
  function maybeClearHistoryOnExternalSwap() {
2026
2388
  const pd = data();
2027
2389
  if (pd === lastPropsData) return;
2028
2390
  lastPropsData = pd;
2029
- if (!local.undoable) return;
2030
2391
  if (pd != null && pd[DATA_WRITE_TOKEN_KEY] != null) return;
2031
- clearHistory();
2392
+ pendingWrite = false;
2393
+ if (holdCtrl.isHolding()) {
2394
+ holdCtrl.release();
2395
+ if (refreshRowModel) refreshRowModel();
2396
+ }
2397
+ if (local.undoable) clearHistory();
2032
2398
  }
2033
2399
  let lastData = null;
2034
2400
  let lastDataLen = -1;
@@ -2405,6 +2771,23 @@ function DataTable(_props) {
2405
2771
  function cut() {
2406
2772
  return cutRange();
2407
2773
  }
2774
+ function refreshView() {
2775
+ holdCtrl.release();
2776
+ pendingViewChange = true;
2777
+ if (!table) return;
2778
+ table.setOptions((p) => ({
2779
+ ...p,
2780
+ state: {
2781
+ ...p.state,
2782
+ columnFilters: [...p.state.columnFilters || []],
2783
+ sorting: [...p.state.sorting || []]
2784
+ }
2785
+ }));
2786
+ if (refreshRowModel) refreshRowModel();
2787
+ }
2788
+ function getViewState() {
2789
+ return holdCtrl.viewState(table);
2790
+ }
2408
2791
  function isGrid() {
2409
2792
  return local.interactionMode === "grid";
2410
2793
  }
@@ -2937,6 +3320,7 @@ function DataTable(_props) {
2937
3320
  if (!tgt || !tgt.closest) return;
2938
3321
  const cellEl = tgt.closest("[data-grid-cell]");
2939
3322
  if (!cellEl) return;
3323
+ activeCellEngaged = true;
2940
3324
  const rowAttr = cellEl.getAttribute("data-row");
2941
3325
  const colAttr = cellEl.getAttribute("data-col-index");
2942
3326
  if (rowAttr == null || colAttr == null) return;
@@ -3110,13 +3494,62 @@ function DataTable(_props) {
3110
3494
  if (typeof requestAnimationFrame === "function") requestAnimationFrame(tryFocus);
3111
3495
  else setTimeout(tryFocus, 0);
3112
3496
  }
3113
- function clampActiveCell(rowCount, colCount) {
3497
+ function followOpenEditors(prevIds, nextIds) {
3498
+ if (!prevIds || sameIdSequence(prevIds, nextIds)) return false;
3499
+ let owns = false;
3500
+ const er = editingRow();
3501
+ if (er >= 0) {
3502
+ const ef = resolveFollow(prevIds, er, nextIds, true);
3503
+ if (ef.kind === "move") {
3504
+ setEditingRow(ef.index);
3505
+ focusEditorWhenReady(ef.index, editingCol(), false);
3506
+ owns = true;
3507
+ } else if (ef.kind !== "stay") {
3508
+ committedThisSession = true;
3509
+ editTransition = true;
3510
+ endEdit();
3511
+ editTransition = false;
3512
+ }
3513
+ }
3514
+ const rr = editingRowIndex();
3515
+ if (rr != null && rr >= 0) {
3516
+ const rf = resolveFollow(prevIds, rr, nextIds, true);
3517
+ if (rf.kind === "move") {
3518
+ setEditingRowIndex(rf.index);
3519
+ owns = true;
3520
+ } else if (rf.kind !== "stay") {
3521
+ editTransition = true;
3522
+ endRowEdit();
3523
+ editTransition = false;
3524
+ }
3525
+ }
3526
+ return owns;
3527
+ }
3528
+ function rowIdInModel(index) {
3529
+ const row = windowSource()[index];
3530
+ return row ? row.id : null;
3531
+ }
3532
+ function modelIndexOfRowId(id) {
3533
+ const rows = windowSource();
3534
+ for (let i = 0; i < rows.length; i++) if (rows[i].id === id) return i;
3535
+ return -1;
3536
+ }
3537
+ function followActiveCell(prevIds, nextIds, viewChangeOrWrite, rowCount, colCount, editFollowPending = false) {
3538
+ const editorOwnsFocus = followOpenEditors(prevIds, nextIds);
3539
+ if (!isGrid()) return;
3540
+ let follow = null;
3541
+ if (activeCellEngaged && rowCount > 0 && !activeIsHeader() && !gridEmptyFallback) follow = resolveFollow(prevIds, activeRow(), nextIds, viewChangeOrWrite);
3542
+ clampActiveCell(rowCount, colCount, follow, editFollowPending || editorOwnsFocus, rowCount <= 0 && viewChangeOrWrite);
3543
+ if (viewChangeOrWrite && !sameIdSequence(prevIds, nextIds)) clearRange();
3544
+ }
3545
+ function clampActiveCell(rowCount, colCount, follow = null, editFollowPending = false, focusEmptyPark = false) {
3114
3546
  if (!isGrid()) return;
3115
3547
  const colN = colCount != null ? colCount : visibleColCount();
3116
3548
  const rowN = rowCount != null ? rowCount : bodyRowCount();
3117
3549
  let recoverFocus = false;
3118
3550
  let doomedRow = -1;
3119
3551
  let doomedCol = 0;
3552
+ let focusOnBody = false;
3120
3553
  if (gridRoot) {
3121
3554
  const rootNode = gridRoot.getRootNode ? gridRoot.getRootNode() : null;
3122
3555
  const focusedEl = rootNode ? rootNode.activeElement : null;
@@ -3125,6 +3558,7 @@ function DataTable(_props) {
3125
3558
  const fRowAttr = focusedCell.getAttribute("data-row");
3126
3559
  const fColAttr = focusedCell.getAttribute("data-col-index");
3127
3560
  if (fRowAttr != null && fRowAttr !== "__header") {
3561
+ focusOnBody = true;
3128
3562
  const fr = parseInt(fRowAttr, 10);
3129
3563
  const fc = parseInt(fColAttr, 10);
3130
3564
  if (Number.isFinite(fr) && fr > rowN - 1) {
@@ -3144,6 +3578,7 @@ function DataTable(_props) {
3144
3578
  setActiveColIndex(0);
3145
3579
  gridEmptyFallback = true;
3146
3580
  clampRange(rowN - 1, colN - 1);
3581
+ if (focusEmptyPark && (focusOnBody || editFollowPending)) recoverGridFocus("__header", 0, headerLeafLevel());
3147
3582
  return;
3148
3583
  }
3149
3584
  if (gridEmptyFallback) {
@@ -3151,6 +3586,31 @@ function DataTable(_props) {
3151
3586
  setActiveIsHeader(false);
3152
3587
  setActiveRow(0);
3153
3588
  }
3589
+ if (follow && follow.kind === "move") {
3590
+ const nextRow = follow.index;
3591
+ setActiveRow(nextRow);
3592
+ clampRange(rowN - 1, colN - 1);
3593
+ _props.onActivecellChange?.({
3594
+ rowIndex: toAbsRow(nextRow),
3595
+ colIndex: col,
3596
+ isHeader: false
3597
+ });
3598
+ if (focusOnBody && !editFollowPending) focusCellWhenReady(nextRow, col);
3599
+ return;
3600
+ }
3601
+ if (follow && follow.kind === "park") {
3602
+ const level = headerLeafLevel();
3603
+ setActiveIsHeader(true);
3604
+ setActiveHeaderLevel(level);
3605
+ clampRange(rowN - 1, colN - 1);
3606
+ _props.onActivecellChange?.({
3607
+ rowIndex: null,
3608
+ colIndex: col,
3609
+ isHeader: true
3610
+ });
3611
+ if (focusOnBody || editFollowPending) recoverGridFocus("__header", col, level);
3612
+ return;
3613
+ }
3154
3614
  if (!activeIsHeader()) {
3155
3615
  const lastRow = rowN - 1;
3156
3616
  const maxRow = lastRow < 0 ? 0 : lastRow;
@@ -3216,6 +3676,7 @@ function DataTable(_props) {
3216
3676
  colVirtualizer._willUpdate();
3217
3677
  }
3218
3678
  let gridEmptyFallback = false;
3679
+ let activeCellEngaged = false;
3219
3680
  let rangeTransition = false;
3220
3681
  let rangeClickPending = null;
3221
3682
  let rangeActive = false;
@@ -3936,6 +4397,7 @@ function DataTable(_props) {
3936
4397
  const colId = columnIdAt(rowIndex, colIndex);
3937
4398
  if (colId == null || !columnEditable(colId)) return;
3938
4399
  committedThisSession = false;
4400
+ outsidePointerDown = false;
3939
4401
  setInvalid("");
3940
4402
  setEditingRowIndex(null);
3941
4403
  setRowDraft({});
@@ -4386,12 +4848,14 @@ function DataTable(_props) {
4386
4848
  const fromRow = editingRow();
4387
4849
  const fromCol = editingCol();
4388
4850
  const target = e.shiftKey ? prevEditableCell(fromRow, fromCol) : nextEditableCell(fromRow, fromCol);
4851
+ const targetRowId = target ? rowIdInModel(target.row) : null;
4389
4852
  const committed = commitEdit(void 0, true);
4390
- if (committed && target) {
4391
- setActiveRow(target.row);
4853
+ const advanceRow = targetRowId != null ? modelIndexOfRowId(targetRowId) : -1;
4854
+ if (committed && target && advanceRow >= 0) {
4855
+ setActiveRow(advanceRow);
4392
4856
  setActiveColIndex(target.col);
4393
- beginEdit(target.row, target.col, null);
4394
- } else if (committed) focusCellWhenReady(fromRow, fromCol);
4857
+ beginEdit(advanceRow, target.col, null);
4858
+ } else if (committed && target) {} else if (committed) focusCellWhenReady(fromRow, fromCol);
4395
4859
  } else if (key === "Escape") {
4396
4860
  e.preventDefault();
4397
4861
  cancelEdit();
@@ -5479,6 +5943,8 @@ function Column(_props) {
5479
5943
  expandable: false,
5480
5944
  groupable: true,
5481
5945
  aggregationFn: null,
5946
+ filterFn: null,
5947
+ sortingFn: null,
5482
5948
  editable: false,
5483
5949
  editor: "text",
5484
5950
  editorOptions: [],
@@ -5494,6 +5960,8 @@ function Column(_props) {
5494
5960
  "expandable",
5495
5961
  "groupable",
5496
5962
  "aggregationFn",
5963
+ "filterFn",
5964
+ "sortingFn",
5497
5965
  "editable",
5498
5966
  "editor",
5499
5967
  "editorOptions",
@@ -5511,11 +5979,15 @@ function Column(_props) {
5511
5979
  local.expandable,
5512
5980
  local.groupable,
5513
5981
  local.aggregationFn,
5982
+ local.filterFn,
5983
+ local.sortingFn,
5514
5984
  local.editable,
5515
5985
  local.editor,
5516
5986
  local.editorOptions,
5517
5987
  local.validate
5518
5988
  ], (v) => untrack(() => (() => {
5989
+ latestFilterFn = local.filterFn;
5990
+ latestSortingFn = local.sortingFn;
5519
5991
  if (reg) reg.registerColumn(colId(), buildSpec());
5520
5992
  })()), { defer: true }));
5521
5993
  onMount(() => {
@@ -5541,10 +6013,21 @@ function Column(_props) {
5541
6013
  let reg = null;
5542
6014
  reg = registry;
5543
6015
  let registered = false;
6016
+ let latestFilterFn = null;
6017
+ let latestSortingFn = null;
6018
+ const filterTrampoline = Object.assign((...args) => typeof latestFilterFn === "function" ? latestFilterFn(...args) : true, {
6019
+ autoRemove: (value, column) => typeof latestFilterFn === "function" && typeof latestFilterFn.autoRemove === "function" ? latestFilterFn.autoRemove(value, column) : false,
6020
+ resolveFilterValue: (value) => typeof latestFilterFn === "function" && typeof latestFilterFn.resolveFilterValue === "function" ? latestFilterFn.resolveFilterValue(value) : value
6021
+ });
6022
+ function sortingTrampoline(...args) {
6023
+ return typeof latestSortingFn === "function" ? latestSortingFn(...args) : 0;
6024
+ }
5544
6025
  function colId() {
5545
6026
  return local.id !== "" ? local.id : local.field;
5546
6027
  }
5547
6028
  function buildSpec() {
6029
+ latestFilterFn = local.filterFn;
6030
+ latestSortingFn = local.sortingFn;
5548
6031
  return {
5549
6032
  id: colId(),
5550
6033
  field: local.field !== "" ? local.field : colId(),
@@ -5556,6 +6039,8 @@ function Column(_props) {
5556
6039
  expandable: local.expandable,
5557
6040
  groupable: local.groupable,
5558
6041
  aggregationFn: local.aggregationFn,
6042
+ filterFn: typeof local.filterFn === "function" ? filterTrampoline : local.filterFn,
6043
+ sortingFn: typeof local.sortingFn === "function" ? sortingTrampoline : local.sortingFn,
5559
6044
  editable: local.editable,
5560
6045
  editor: local.editor,
5561
6046
  editorOptions: local.editorOptions,