@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.
package/dist/index.mjs CHANGED
@@ -94,6 +94,74 @@ const columnSpecsEquivalent = (a, b, depth) => {
94
94
  return true;
95
95
  };
96
96
  //#endregion
97
+ //#region src/helpers/columnRuleUtils.ts
98
+ const FILTER_FN_NAMES = [
99
+ "includesString",
100
+ "includesStringSensitive",
101
+ "equalsString",
102
+ "arrIncludes",
103
+ "arrIncludesAll",
104
+ "arrIncludesSome",
105
+ "equals",
106
+ "weakEquals",
107
+ "inNumberRange"
108
+ ];
109
+ const SORTING_FN_NAMES = [
110
+ "alphanumeric",
111
+ "alphanumericCaseSensitive",
112
+ "text",
113
+ "textCaseSensitive",
114
+ "datetime",
115
+ "basic"
116
+ ];
117
+ const warned = /* @__PURE__ */ new Set();
118
+ const resolveName = (value, names, kind, columnId, warn) => {
119
+ if (value === "auto" || names.includes(value)) return value;
120
+ const key = kind + "\0" + columnId + "\0" + value;
121
+ if (!warned.has(key)) {
122
+ warned.add(key);
123
+ warn(`[data-table] column "${columnId}": unknown ${kind} "${value}"; falling back to "auto".`);
124
+ }
125
+ return "auto";
126
+ };
127
+ const resolveFilterFn = (value, columnId, warn = console.warn) => {
128
+ if (value == null) return void 0;
129
+ if (typeof value === "string") return resolveName(value, FILTER_FN_NAMES, "filterFn", columnId, warn);
130
+ if (typeof value !== "function") return void 0;
131
+ const fn = value;
132
+ const adapter = (row, colId, filterValue) => {
133
+ try {
134
+ return fn(row.getValue(colId), filterValue, row.original);
135
+ } catch (err) {
136
+ return true;
137
+ }
138
+ };
139
+ if (fn.autoRemove !== void 0) adapter.autoRemove = fn.autoRemove;
140
+ if (fn.resolveFilterValue !== void 0) adapter.resolveFilterValue = fn.resolveFilterValue;
141
+ return adapter;
142
+ };
143
+ const resolveSortingFn = (value, columnId, warn = console.warn) => {
144
+ if (value == null) return void 0;
145
+ if (typeof value === "string") return resolveName(value, SORTING_FN_NAMES, "sortingFn", columnId, warn);
146
+ if (typeof value !== "function") return void 0;
147
+ const fn = value;
148
+ return (rowA, rowB, colId) => {
149
+ try {
150
+ return fn(rowA.getValue(colId), rowB.getValue(colId), rowA.original, rowB.original);
151
+ } catch (err) {
152
+ return 0;
153
+ }
154
+ };
155
+ };
156
+ const columnRuleKeys = (spec, columnId, warn) => {
157
+ const out = {};
158
+ const f = resolveFilterFn(spec?.filterFn, columnId, warn);
159
+ if (f !== void 0) out.filterFn = f;
160
+ const s = resolveSortingFn(spec?.sortingFn, columnId, warn);
161
+ if (s !== void 0) out.sortingFn = s;
162
+ return out;
163
+ };
164
+ //#endregion
97
165
  //#region src/helpers/indexMath.ts
98
166
  const clamp = (v, lo, hi) => v < lo ? lo : v > hi ? hi : v;
99
167
  const focusables = (cellEl) => {
@@ -102,6 +170,24 @@ const focusables = (cellEl) => {
102
170
  };
103
171
  const applyUpdater = (updater, current) => typeof updater === "function" ? updater(current) : updater;
104
172
  //#endregion
173
+ //#region src/helpers/activeFollow.ts
174
+ function resolveFollow(prevIds, activeRow, nextIds, viewChangeOrWrite) {
175
+ if (!prevIds || !Number.isInteger(activeRow) || activeRow < 0 || activeRow >= prevIds.length) return { kind: "clamp" };
176
+ const id = prevIds[activeRow];
177
+ if (nextIds[activeRow] === id) return { kind: "stay" };
178
+ const index = nextIds.indexOf(id);
179
+ if (index >= 0) return {
180
+ kind: "move",
181
+ index
182
+ };
183
+ return viewChangeOrWrite ? { kind: "park" } : { kind: "clamp" };
184
+ }
185
+ function sameIdSequence(a, b) {
186
+ if (!a || a.length !== b.length) return false;
187
+ for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false;
188
+ return true;
189
+ }
190
+ //#endregion
105
191
  //#region src/helpers/tsvGrid.ts
106
192
  const escapeTsvField = (s) => {
107
193
  if (s.indexOf(" ") >= 0 || s.indexOf("\n") >= 0 || s.indexOf("\r") >= 0 || s.indexOf("\"") >= 0) return "\"" + s.replace(/"/g, "\"\"") + "\"";
@@ -237,9 +323,153 @@ const indexOfRowIn = (rows, rowOriginal, rowId) => {
237
323
  return -1;
238
324
  };
239
325
  //#endregion
326
+ //#region src/helpers/heldView.ts
327
+ const CLEAN_VIEW_STATE = Object.freeze({
328
+ held: false,
329
+ filterMismatches: 0,
330
+ sortMismatches: 0
331
+ });
332
+ const viewStatesEqual = (a, b) => a.held === b.held && a.filterMismatches === b.filterMismatches && a.sortMismatches === b.sortMismatches;
333
+ const changedIndices = (prev, next) => {
334
+ const out = [];
335
+ if (prev.length !== next.length) {
336
+ const n = Math.max(prev.length, next.length);
337
+ for (let i = 0; i < n; i++) out.push(i);
338
+ return out;
339
+ }
340
+ for (let i = 0; i < next.length; i++) if (prev[i] !== next[i]) out.push(i);
341
+ return out;
342
+ };
343
+ const rowIds = (model) => (model?.rows ?? []).map((r) => r.id);
344
+ const createHoldController = () => {
345
+ let active = false;
346
+ let heldMembership = null;
347
+ let heldOrder = null;
348
+ const written = /* @__PURE__ */ new Set();
349
+ let version = 0;
350
+ let liveFilteredIds = /* @__PURE__ */ new Set();
351
+ let liveSortedIds = [];
352
+ const release = () => {
353
+ heldMembership = null;
354
+ heldOrder = null;
355
+ written.clear();
356
+ version++;
357
+ };
358
+ const wrapFiltered = (base) => (table) => {
359
+ const getBase = base(table);
360
+ let lastBase = void 0;
361
+ let lastVersion = -1;
362
+ let lastOut = void 0;
363
+ return () => {
364
+ const baseModel = getBase();
365
+ if (!heldMembership) return baseModel;
366
+ if (baseModel === lastBase && version === lastVersion) return lastOut;
367
+ liveFilteredIds = new Set(rowIds(baseModel));
368
+ const held = heldMembership;
369
+ const rows = table.getCoreRowModel().rows.filter((r) => held.has(r.id));
370
+ const rowsById = {};
371
+ for (const r of rows) rowsById[r.id] = r;
372
+ lastBase = baseModel;
373
+ lastVersion = version;
374
+ lastOut = {
375
+ rows,
376
+ flatRows: rows,
377
+ rowsById
378
+ };
379
+ return lastOut;
380
+ };
381
+ };
382
+ const wrapSorted = (base) => (table) => {
383
+ const getBase = base(table);
384
+ let lastBase = void 0;
385
+ let lastVersion = -1;
386
+ let lastOut = void 0;
387
+ return () => {
388
+ const baseModel = getBase();
389
+ if (!heldOrder) return baseModel;
390
+ if (baseModel === lastBase && version === lastVersion) return lastOut;
391
+ const baseRows = baseModel?.rows ?? [];
392
+ liveSortedIds = baseRows.map((r) => r.id);
393
+ const byId = /* @__PURE__ */ new Map();
394
+ for (const r of baseRows) byId.set(r.id, r);
395
+ const rows = [];
396
+ for (const id of heldOrder) {
397
+ const r = byId.get(id);
398
+ if (r) {
399
+ rows.push(r);
400
+ byId.delete(id);
401
+ }
402
+ }
403
+ for (const r of baseRows) if (byId.has(r.id)) rows.push(r);
404
+ lastBase = baseModel;
405
+ lastVersion = version;
406
+ lastOut = {
407
+ rows,
408
+ flatRows: rows,
409
+ rowsById: baseModel?.rowsById ?? {}
410
+ };
411
+ return lastOut;
412
+ };
413
+ };
414
+ return {
415
+ setActive(next) {
416
+ const was = active;
417
+ active = !!next;
418
+ if (!active && was) release();
419
+ },
420
+ isActive: () => active,
421
+ isHolding: () => heldMembership !== null,
422
+ capture(table) {
423
+ if (!active || heldMembership) return;
424
+ const filtered = table.getFilteredRowModel();
425
+ const sorted = typeof table.getSortedRowModel === "function" ? table.getSortedRowModel() : filtered;
426
+ heldMembership = new Set(rowIds(filtered));
427
+ heldOrder = rowIds(sorted);
428
+ written.clear();
429
+ version++;
430
+ },
431
+ release,
432
+ noteWrite(prev, next) {
433
+ if (!heldMembership) return;
434
+ for (const i of changedIndices(prev, next)) written.add(i);
435
+ },
436
+ wrapFiltered,
437
+ wrapSorted,
438
+ viewState(table) {
439
+ if (!active) return CLEAN_VIEW_STATE;
440
+ if (!heldMembership || !heldOrder) return {
441
+ held: true,
442
+ filterMismatches: 0,
443
+ sortMismatches: 0
444
+ };
445
+ table.getSortedRowModel();
446
+ const held = heldMembership;
447
+ let filterMismatches = 0;
448
+ for (const id of held) if (!liveFilteredIds.has(id)) filterMismatches++;
449
+ const coreRows = table.getCoreRowModel().rows;
450
+ for (const r of coreRows) if (!held.has(r.id) && liveFilteredIds.has(r.id)) filterMismatches++;
451
+ const heldPos = /* @__PURE__ */ new Map();
452
+ heldOrder.forEach((id, i) => heldPos.set(id, i));
453
+ const livePos = /* @__PURE__ */ new Map();
454
+ liveSortedIds.forEach((id, i) => livePos.set(id, i));
455
+ let sortMismatches = 0;
456
+ for (const i of written) {
457
+ const id = coreRows[i]?.id;
458
+ if (id === void 0 || !heldPos.has(id)) continue;
459
+ if (heldPos.get(id) !== livePos.get(id)) sortMismatches++;
460
+ }
461
+ return {
462
+ held: true,
463
+ filterMismatches,
464
+ sortMismatches
465
+ };
466
+ }
467
+ };
468
+ };
469
+ //#endregion
240
470
  //#region src/DataTable.tsx
241
471
  var _tmpl$$11 = /* @__PURE__ */ template(`<div class=rozie-data-table-wrap data-rozie-s-d5dcab4c=""><div class=rdt-column-defs aria-hidden=true data-rozie-s-d5dcab4c=""></div><div class=rdt-toolbar data-rozie-s-d5dcab4c=""><input type=text role=searchbox aria-label="Search table"class=rdt-global-filter data-rozie-s-d5dcab4c="">`), _tmpl$2$4 = /* @__PURE__ */ template(`<div class=rdt-sr-live role=status aria-live=polite aria-atomic=true data-rozie-s-d5dcab4c="">`), _tmpl$3$1 = /* @__PURE__ */ template(`<div class="rdt-sr-live rdt-sr-paste"data-testid=paste-announce role=status aria-live=polite aria-atomic=true data-rozie-s-d5dcab4c="">`), _tmpl$4$1 = /* @__PURE__ */ template(`<div class="rdt-sr-live rdt-sr-range"data-testid=range-announce role=status aria-live=polite aria-atomic=true data-rozie-s-d5dcab4c="">`), _tmpl$5$1 = /* @__PURE__ */ template(`<div class="rdt-sr-live rdt-sr-sortfilter"data-testid=sortfilter-announce role=status aria-live=polite aria-atomic=true data-rozie-s-d5dcab4c="">`), _tmpl$6 = /* @__PURE__ */ template(`<details class=rdt-colvis data-rozie-s-d5dcab4c=""><summary class=rdt-colvis-summary data-rozie-s-d5dcab4c="">Columns</summary><div class=rdt-colvis-menu role=group aria-label="Toggle columns"data-rozie-s-d5dcab4c="">`), _tmpl$7 = /* @__PURE__ */ template(`<label class=rdt-colvis-item data-rozie-s-d5dcab4c=""><input type=checkbox class=rdt-colvis-checkbox data-rozie-s-d5dcab4c=""><span class=rdt-colvis-label data-rozie-s-d5dcab4c="">`), _tmpl$8 = /* @__PURE__ */ template(`<div class=rdt-group-bar-host data-rozie-s-d5dcab4c="">`), _tmpl$9 = /* @__PURE__ */ template(`<span class=rdt-group-token data-group-token=""data-rozie-s-d5dcab4c="">`), _tmpl$0 = /* @__PURE__ */ template(`<div class=rdt-scroll data-rozie-s-d5dcab4c=""><table data-rozie-s-d5dcab4c=""><thead class=rdt-thead role=rowgroup data-rozie-s-d5dcab4c=""></thead><tbody class=rdt-tbody role=rowgroup data-rozie-s-d5dcab4c=""><tr class=rdt-spacer aria-hidden=true data-rozie-s-d5dcab4c=""><td data-rozie-s-d5dcab4c=""></td></tr><tr class=rdt-spacer aria-hidden=true data-rozie-s-d5dcab4c=""><td data-rozie-s-d5dcab4c="">`), _tmpl$1 = /* @__PURE__ */ template(`<table data-rozie-s-d5dcab4c=""><thead class=rdt-thead role=rowgroup data-rozie-s-d5dcab4c=""></thead><tbody class=rdt-tbody role=rowgroup data-rozie-s-d5dcab4c="">`), _tmpl$10 = /* @__PURE__ */ template(`<tr class=rdt-tr role=row data-rozie-s-d5dcab4c="">`), _tmpl$11 = /* @__PURE__ */ template(`<th role=columnheader data-grid-cell=""data-row=__header data-rozie-s-d5dcab4c="">`), _tmpl$12 = /* @__PURE__ */ template(`<span data-rozie-s-d5dcab4c="">`), _tmpl$13 = /* @__PURE__ */ template(`<div class=rdt-col-menu role=menu data-rozie-s-d5dcab4c=""><button type=button role=menuitem class=rdt-col-menu-item data-rozie-s-d5dcab4c="">Pin left</button><button type=button role=menuitem class=rdt-col-menu-item data-rozie-s-d5dcab4c="">Pin right</button><button type=button role=menuitem class=rdt-col-menu-item data-rozie-s-d5dcab4c="">Unpin</button><hr class=rdt-col-menu-sep data-rozie-s-d5dcab4c=""><button type=button role=menuitem class=rdt-col-menu-item data-rozie-s-d5dcab4c="">Hide column`), _tmpl$14 = /* @__PURE__ */ template(`<span data-rozie-s-d5dcab4c=""><button type=button class=rdt-resize-handle data-rozie-s-d5dcab4c=""><span class=rdt-resize-grip aria-hidden=true data-rozie-s-d5dcab4c="">`), _tmpl$15 = /* @__PURE__ */ template(`<button type=button class=rdt-sort-btn data-rozie-s-d5dcab4c=""><span class=rdt-header-label data-rozie-s-d5dcab4c=""></span><span class=rdt-sort-ind aria-hidden=true data-rozie-s-d5dcab4c="">`), _tmpl$16 = /* @__PURE__ */ template(`<span data-rozie-s-d5dcab4c=""><span class=rdt-header-label data-rozie-s-d5dcab4c="">`), _tmpl$17 = /* @__PURE__ */ template(`<button type=button class=rdt-col-menu-trigger data-rozie-s-d5dcab4c="">⋯`), _tmpl$18 = /* @__PURE__ */ template(`<input type=checkbox aria-label="Select all rows"class=rdt-select-all data-rozie-s-d5dcab4c="">`), _tmpl$19 = /* @__PURE__ */ template(`<tr class=rdt-filter-row data-rozie-s-d5dcab4c="">`), _tmpl$20 = /* @__PURE__ */ template(`<th role=presentation data-rozie-s-d5dcab4c="">`), _tmpl$21 = /* @__PURE__ */ template(`<input type=text class=rdt-col-filter data-rozie-s-d5dcab4c="">`), _tmpl$22 = /* @__PURE__ */ template(`<tr role=row data-rozie-s-d5dcab4c="">`), _tmpl$23 = /* @__PURE__ */ template(`<td data-grid-cell=""data-rozie-s-d5dcab4c="">`), _tmpl$24 = /* @__PURE__ */ template(`<span data-rozie-s-d5dcab4c=""><button type=button data-expander=""class="rdt-expander rdt-group-toggle"data-rozie-s-d5dcab4c=""></button><span class=rdt-group-value data-rozie-s-d5dcab4c=""></span><span class=rdt-group-count data-rozie-s-d5dcab4c="">`), _tmpl$25 = /* @__PURE__ */ template(`<span class=rdt-cell-value data-rozie-s-d5dcab4c="">`), _tmpl$26 = /* @__PURE__ */ template(`<input type=number data-editing-cell=""data-builtin-editor=""class=rdt-cell-editor data-rozie-s-d5dcab4c="">`), _tmpl$27 = /* @__PURE__ */ template(`<select data-editing-cell=""data-builtin-editor=""class=rdt-cell-editor data-rozie-s-d5dcab4c="">`), _tmpl$28 = /* @__PURE__ */ template(`<input type=checkbox data-editing-cell=""data-builtin-editor=""class=rdt-cell-editor data-rozie-s-d5dcab4c="">`), _tmpl$29 = /* @__PURE__ */ template(`<input type=text data-editing-cell=""data-builtin-editor=""class=rdt-cell-editor data-rozie-s-d5dcab4c="">`), _tmpl$30 = /* @__PURE__ */ template(`<option data-rozie-s-d5dcab4c="">`), _tmpl$31 = /* @__PURE__ */ template(`<input type=checkbox aria-label="Select row"class=rdt-select-row data-rozie-s-d5dcab4c="">`), _tmpl$32 = /* @__PURE__ */ template(`<button type=button data-expander=""class=rdt-expander data-rozie-s-d5dcab4c="">`), _tmpl$33 = /* @__PURE__ */ template(`<span data-fill-handle=""data-testid=fill-handle aria-hidden=true class=rdt-fill-handle data-rozie-s-d5dcab4c="">`), _tmpl$34 = /* @__PURE__ */ template(`<tr class=rdt-detail-row role=row data-rozie-s-d5dcab4c=""><td class=rdt-detail-cell data-rozie-s-d5dcab4c="">`), _tmpl$35 = /* @__PURE__ */ template(`<th class=rdt-col-spacer aria-hidden=true data-rozie-s-d5dcab4c="">`), _tmpl$36 = /* @__PURE__ */ template(`<td class=rdt-col-spacer aria-hidden=true data-rozie-s-d5dcab4c="">`), _tmpl$37 = /* @__PURE__ */ template(`<span data-rozie-s-d5dcab4c=""><input class=rdt-select-placeholder type=checkbox tabindex=-1 aria-hidden=true data-rozie-s-d5dcab4c="">`), _tmpl$38 = /* @__PURE__ */ template(`<span class=rdt-skeleton aria-hidden=true data-rozie-s-d5dcab4c="">&#8203;`), _tmpl$39 = /* @__PURE__ */ template(`<div class=rdt-pagination role=group aria-label=Pagination data-rozie-s-d5dcab4c=""><button type=button class="rdt-page-btn rdt-page-prev"data-rozie-s-d5dcab4c="">Prev</button><span class=rdt-page-status aria-live=polite data-rozie-s-d5dcab4c=""></span><button type=button class="rdt-page-btn rdt-page-next"data-rozie-s-d5dcab4c="">Next</button><select aria-label="Rows per page"class=rdt-page-size data-rozie-s-d5dcab4c=""><option value=10 data-rozie-s-d5dcab4c="">10</option><option value=25 data-rozie-s-d5dcab4c="">25</option><option value=50 data-rozie-s-d5dcab4c="">50</option><option value=100 data-rozie-s-d5dcab4c="">100`);
242
- /** A cell position as integers over the visible (display-order) model. */ /** The active (roving-focus) cell. A header cell reports `rowIndex: null` and `isHeader: true`. */ /** The rectangular cell-range selection. Both corners are `null` when there is no range. */ /** One data column offered to the `#groupBar` slot. */ /** One changed cell inside a row edit commit. */ /** The `activecell-change` payload. `isHeader` is set when the move landed on or left a header cell. */ /** The `cell-edit-commit` payload: one committed cell. */ /** The `row-edit-commit` payload: the row and every cell the save changed. */ /** The `filter-change` payload: `globalFilter` for the search box, `columnFilters` for a per-column filter. Exactly one key is set. */ /** The `history-change` payload: undo/redo availability. */ /** The `range-change` payload: the new range corners (both `null` when the range cleared). */ /** The `row-activate` payload. `row` is the original data object and `index` its position in the rendered model. */ /** The `visible-range-change` payload: the rendered row window, `end` exclusive. */ __rozieInjectStyle("DataTable-d5dcab4c", `[data-rozie-s-d5dcab4c]:host {
472
+ /** A cell position as integers over the visible (display-order) model. */ /** The active (roving-focus) cell. A header cell reports `rowIndex: null` and `isHeader: true`. */ /** The rectangular cell-range selection. Both corners are `null` when there is no range. */ /** One data column offered to the `#groupBar` slot. */ /** One changed cell inside a row edit commit. */ /** The `activecell-change` payload. `isHeader` is set when the move landed on or left a header cell. */ /** The `cell-edit-commit` payload: one committed cell. */ /** The `row-edit-commit` payload: the row and every cell the save changed. */ /** The `filter-change` payload: `globalFilter` for the search box, `columnFilters` for a per-column filter. Exactly one key is set. */ /** The `history-change` payload: undo/redo availability. */ /** The held-view state (`holdView`): whether the view is held right now, and how many rows the held view disagrees with. */ /** The `range-change` payload: the new range corners (both `null` when the range cleared). */ /** The `row-activate` payload. `row` is the original data object and `index` its position in the rendered model. */ /** The `visible-range-change` payload: the rendered row window, `end` exclusive. */ __rozieInjectStyle("DataTable-d5dcab4c", `[data-rozie-s-d5dcab4c]:host {
243
473
  --rdt-color: var(--rozie-data-table-fg, var(--rdt-ds-fg));
244
474
  --rdt-select-accent: var(--rozie-data-table-select-accent, var(--rozie-data-table-accent, var(--rdt-ds-select-accent)));
245
475
  --rdt-border: 1px solid var(--rozie-data-table-border-color, var(--rdt-ds-border-color));
@@ -655,6 +885,7 @@ function DataTable(_props) {
655
885
  singleClickEdit: false,
656
886
  undoable: false,
657
887
  undoLimit: 100,
888
+ holdView: false,
658
889
  virtual: false,
659
890
  estimateRowHeight: 40,
660
891
  autoMeasure: false,
@@ -686,6 +917,7 @@ function DataTable(_props) {
686
917
  "singleClickEdit",
687
918
  "undoable",
688
919
  "undoLimit",
920
+ "holdView",
689
921
  "virtual",
690
922
  "estimateRowHeight",
691
923
  "autoMeasure",
@@ -708,6 +940,7 @@ function DataTable(_props) {
708
940
  "onSelectionChange",
709
941
  "onSortChange",
710
942
  "onVisibilityChange",
943
+ "onViewStateChange",
711
944
  "onVisibleRangeChange",
712
945
  "groupBarSlot",
713
946
  "selectAllSlot",
@@ -758,7 +991,9 @@ function DataTable(_props) {
758
991
  redo,
759
992
  canUndo,
760
993
  canRedo,
761
- clearHistory
994
+ clearHistory,
995
+ refreshView,
996
+ getViewState
762
997
  });
763
998
  });
764
999
  const __ctx_data_table_columns = rozieContext("data-table:columns");
@@ -847,6 +1082,7 @@ function DataTable(_props) {
847
1082
  columnOrder(),
848
1083
  columnPinning(),
849
1084
  local.selectionMode,
1085
+ local.holdView,
850
1086
  (data() || []).length,
851
1087
  data(),
852
1088
  dataDefault(),
@@ -871,13 +1107,15 @@ function DataTable(_props) {
871
1107
  onMount(() => {
872
1108
  setDataDefault(data() || []);
873
1109
  seedColumnPinning();
1110
+ const cols0 = tableColumns();
1111
+ lastColumnSig = columnSetSignature(cols0);
874
1112
  table = createTable({
875
1113
  data: feedData(),
876
- columns: tableColumns(),
1114
+ columns: cols0,
877
1115
  state: currentState(),
878
1116
  getCoreRowModel: getCoreRowModel(),
879
- getSortedRowModel: getSortedRowModel(),
880
- getFilteredRowModel: getFilteredRowModel(),
1117
+ getSortedRowModel: heldSortedRowModel,
1118
+ getFilteredRowModel: heldFilteredRowModel,
881
1119
  getPaginationRowModel: getPaginationRowModel(),
882
1120
  getExpandedRowModel: getExpandedRowModel(),
883
1121
  getSubRows: local.getSubRows || void 0,
@@ -912,6 +1150,21 @@ function DataTable(_props) {
912
1150
  renderFallbackValue: null,
913
1151
  onStateChange: () => {}
914
1152
  });
1153
+ table.resetPageIndex = () => {
1154
+ if (!table) return;
1155
+ const pg = table.getState().pagination;
1156
+ writePagination({
1157
+ pageIndex: pg.pageIndex,
1158
+ pageSize: pg.pageSize
1159
+ });
1160
+ };
1161
+ const st0 = currentState();
1162
+ lastHoldSorting = st0.sorting;
1163
+ lastHoldColumnFilters = st0.columnFilters;
1164
+ lastHoldGlobalFilter = st0.globalFilter;
1165
+ lastPageGrouping = st0.grouping;
1166
+ syncHoldActive(st0);
1167
+ lastPropsData = data();
915
1168
  refreshRowModel = () => {
916
1169
  if (!table) return;
917
1170
  const nextRows = windowSource().slice();
@@ -926,7 +1179,17 @@ function DataTable(_props) {
926
1179
  }
927
1180
  remeasureColumnWindow();
928
1181
  const nextRowCount = nextRows.length;
929
- clampActiveCell(nextRowCount, nextRows.length ? nextRows[0].getVisibleCells().length : nextGroups.length ? (nextGroups[nextGroups.length - 1].headers || []).length : 0);
1182
+ const nextColCount = nextRows.length ? nextRows[0].getVisibleCells().length : nextGroups.length ? (nextGroups[nextGroups.length - 1].headers || []).length : 0;
1183
+ const nextIds = nextRows.map((r) => r.id);
1184
+ const fedData = table.options.data;
1185
+ const dataChanged = fedData !== lastFedData;
1186
+ lastFedData = fedData;
1187
+ const viewChangeOrWrite = pendingViewChange || pendingWrite && dataChanged;
1188
+ const swapWithoutIds = dataChanged && !pendingWrite && !pendingViewChange && table.options.getRowId == null;
1189
+ pendingViewChange = false;
1190
+ if (dataChanged) pendingWrite = false;
1191
+ followActiveCell(swapWithoutIds ? null : prevRowIds, nextIds, viewChangeOrWrite, nextRowCount, nextColCount, pendingEditFollow != null);
1192
+ prevRowIds = nextIds;
930
1193
  const pgState = table.getState().pagination;
931
1194
  const pc = table.getPageCount();
932
1195
  if (pc > 0 && pgState.pageIndex > pc - 1) writePagination({
@@ -942,6 +1205,11 @@ function DataTable(_props) {
942
1205
  syncIndeterminate();
943
1206
  if (typeof queueMicrotask !== "undefined") queueMicrotask(syncIndeterminate);
944
1207
  else Promise.resolve().then(syncIndeterminate);
1208
+ const vs = holdCtrl.viewState(table);
1209
+ if (!viewStatesEqual(vs, lastViewState)) {
1210
+ lastViewState = vs;
1211
+ _props.onViewStateChange?.(vs);
1212
+ }
945
1213
  };
946
1214
  refreshRowModel();
947
1215
  gridRoot = __rozieRootRef ? __rozieRootRef.querySelector(".rozie-data-table") : null;
@@ -1033,6 +1301,48 @@ function DataTable(_props) {
1033
1301
  let undoStack = [];
1034
1302
  let redoStack = [];
1035
1303
  let restoringHistory = false;
1304
+ const holdCtrl = createHoldController();
1305
+ const heldFilteredRowModel = holdCtrl.wrapFiltered(getFilteredRowModel());
1306
+ const heldSortedRowModel = holdCtrl.wrapSorted(getSortedRowModel());
1307
+ let pendingViewChange = false;
1308
+ let pendingWrite = false;
1309
+ let prevRowIds = null;
1310
+ let lastFedData = null;
1311
+ let lastViewState = CLEAN_VIEW_STATE;
1312
+ let lastHoldSorting = null;
1313
+ let lastHoldColumnFilters = null;
1314
+ let lastHoldGlobalFilter = null;
1315
+ let holdFallbackWarned = false;
1316
+ let lastPageGrouping = null;
1317
+ let lastColumnSig = "";
1318
+ function columnSetSignature(defs) {
1319
+ let sig = "";
1320
+ const stack = (defs || []).slice().reverse();
1321
+ while (stack.length) {
1322
+ const d = stack.pop();
1323
+ if (!d) continue;
1324
+ sig += String(d.id) + (d.enableSorting ? "+" : "") + (d.enableColumnFilter ? "*" : "") + "|";
1325
+ if (Array.isArray(d.columns)) for (let i = d.columns.length - 1; i >= 0; i--) stack.push(d.columns[i]);
1326
+ }
1327
+ return sig;
1328
+ }
1329
+ let lastPropsData = null;
1330
+ function isExternalSwap() {
1331
+ const pd = data();
1332
+ if (pd === lastPropsData) return false;
1333
+ return !(pd != null && pd[DATA_WRITE_TOKEN_KEY] != null);
1334
+ }
1335
+ function syncHoldActive(st) {
1336
+ const wanted = local.holdView === true;
1337
+ const active = wanted && local.manual !== true && local.getSubRows == null && !(st.grouping || []).length;
1338
+ const wasHolding = holdCtrl.isHolding();
1339
+ holdCtrl.setActive(active);
1340
+ if (wasHolding && !holdCtrl.isHolding()) pendingViewChange = true;
1341
+ if (wanted && !active && !holdFallbackWarned) {
1342
+ holdFallbackWarned = true;
1343
+ 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.");
1344
+ }
1345
+ }
1036
1346
  let expandedTouched = false;
1037
1347
  function groupingActiveDefault() {
1038
1348
  return ((grouping() != null ? grouping() : groupingDefault()) || []).length > 0;
@@ -1165,6 +1475,7 @@ function DataTable(_props) {
1165
1475
  expandable: c.expandable === true,
1166
1476
  groupable: c.groupable !== false,
1167
1477
  aggregationFn: wrapAggregationFn(c.aggregationFn),
1478
+ ...columnRuleKeys(c, id),
1168
1479
  pinned: c.pinned != null ? c.pinned : "",
1169
1480
  width: c.width != null ? c.width : "",
1170
1481
  ...parseWidthToSize(c.width) != null ? { size: parseWidthToSize(c.width) } : {},
@@ -1208,6 +1519,7 @@ function DataTable(_props) {
1208
1519
  expandable: spec.expandable === true,
1209
1520
  groupable: spec.groupable !== false,
1210
1521
  aggregationFn: wrapAggregationFn(spec.aggregationFn),
1522
+ ...columnRuleKeys(spec, id),
1211
1523
  pinned: spec.pinned != null ? spec.pinned : "",
1212
1524
  width: spec.width != null ? spec.width : "",
1213
1525
  ...parseWidthToSize(spec.width) != null ? { size: parseWidthToSize(spec.width) } : {},
@@ -1364,6 +1676,9 @@ function DataTable(_props) {
1364
1676
  recordSnapshot(currentData());
1365
1677
  emitHistoryChangeIfEdged(prevU, prevR);
1366
1678
  }
1679
+ if (table) holdCtrl.capture(table);
1680
+ holdCtrl.noteWrite(currentData() || [], Array.isArray(next) ? next : []);
1681
+ pendingWrite = true;
1367
1682
  const fresh = Array.isArray(next) ? next.slice() : next;
1368
1683
  try {
1369
1684
  Object.defineProperty(fresh, DATA_WRITE_TOKEN_KEY, {
@@ -1987,13 +2302,60 @@ function DataTable(_props) {
1987
2302
  if (local.selectionMode === "none") return false;
1988
2303
  return lazyRowsActive() ? (row) => !rowIsLazyPlaceholder(row) : true;
1989
2304
  }
2305
+ function writePaginationSoon(next) {
2306
+ if (!programmatic) {
2307
+ writePagination(next);
2308
+ return;
2309
+ }
2310
+ const run = () => writePagination(next);
2311
+ if (typeof queueMicrotask !== "undefined") queueMicrotask(run);
2312
+ else Promise.resolve().then(run);
2313
+ }
1990
2314
  function reFeed() {
1991
2315
  if (!table) return;
2316
+ const st0 = currentState();
2317
+ syncHoldActive(st0);
2318
+ let st = st0;
2319
+ let pageReset = null;
2320
+ const groupingChanged = st0.grouping !== lastPageGrouping;
2321
+ lastPageGrouping = st0.grouping;
2322
+ const viewChanged = st0.sorting !== lastHoldSorting || st0.columnFilters !== lastHoldColumnFilters || st0.globalFilter !== lastHoldGlobalFilter;
2323
+ if (viewChanged) {
2324
+ lastHoldSorting = st0.sorting;
2325
+ lastHoldColumnFilters = st0.columnFilters;
2326
+ lastHoldGlobalFilter = st0.globalFilter;
2327
+ holdCtrl.release();
2328
+ pendingViewChange = true;
2329
+ }
2330
+ if ((viewChanged || groupingChanged) && local.manual !== true && st0.pagination && st0.pagination.pageIndex !== 0) {
2331
+ pageReset = {
2332
+ pageIndex: 0,
2333
+ pageSize: st0.pagination.pageSize
2334
+ };
2335
+ st = {
2336
+ ...st0,
2337
+ pagination: pageReset
2338
+ };
2339
+ }
2340
+ const cols = tableColumns();
2341
+ const colSig = columnSetSignature(cols);
2342
+ if (colSig !== lastColumnSig) {
2343
+ lastColumnSig = colSig;
2344
+ st = {
2345
+ ...st,
2346
+ sorting: [...st.sorting || []],
2347
+ columnFilters: [...st.columnFilters || []]
2348
+ };
2349
+ }
2350
+ if (holdCtrl.isHolding() && isExternalSwap()) holdCtrl.release();
2351
+ if (pendingWrite && isExternalSwap()) pendingWrite = false;
1992
2352
  table.setOptions((prev) => ({
1993
2353
  ...prev,
1994
2354
  data: feedData(),
1995
- columns: tableColumns(),
1996
- state: currentState(),
2355
+ columns: cols,
2356
+ state: st,
2357
+ getFilteredRowModel: heldFilteredRowModel,
2358
+ getSortedRowModel: heldSortedRowModel,
1997
2359
  enableRowSelection: rowSelectionOption(),
1998
2360
  enableMultiRowSelection: local.selectionMode === "multiple",
1999
2361
  rowCount: local.rowCount ?? void 0,
@@ -2021,15 +2383,19 @@ function DataTable(_props) {
2021
2383
  onColumnSizingInfoChange: onColumnSizingInfoChangeCb
2022
2384
  }));
2023
2385
  if (refreshRowModel) refreshRowModel();
2386
+ if (pageReset) writePaginationSoon(pageReset);
2024
2387
  }
2025
- let lastPropsData = null;
2026
2388
  function maybeClearHistoryOnExternalSwap() {
2027
2389
  const pd = data();
2028
2390
  if (pd === lastPropsData) return;
2029
2391
  lastPropsData = pd;
2030
- if (!local.undoable) return;
2031
2392
  if (pd != null && pd[DATA_WRITE_TOKEN_KEY] != null) return;
2032
- clearHistory();
2393
+ pendingWrite = false;
2394
+ if (holdCtrl.isHolding()) {
2395
+ holdCtrl.release();
2396
+ if (refreshRowModel) refreshRowModel();
2397
+ }
2398
+ if (local.undoable) clearHistory();
2033
2399
  }
2034
2400
  let lastData = null;
2035
2401
  let lastDataLen = -1;
@@ -2406,6 +2772,23 @@ function DataTable(_props) {
2406
2772
  function cut() {
2407
2773
  return cutRange();
2408
2774
  }
2775
+ function refreshView() {
2776
+ holdCtrl.release();
2777
+ pendingViewChange = true;
2778
+ if (!table) return;
2779
+ table.setOptions((p) => ({
2780
+ ...p,
2781
+ state: {
2782
+ ...p.state,
2783
+ columnFilters: [...p.state.columnFilters || []],
2784
+ sorting: [...p.state.sorting || []]
2785
+ }
2786
+ }));
2787
+ if (refreshRowModel) refreshRowModel();
2788
+ }
2789
+ function getViewState() {
2790
+ return holdCtrl.viewState(table);
2791
+ }
2409
2792
  function isGrid() {
2410
2793
  return local.interactionMode === "grid";
2411
2794
  }
@@ -2938,6 +3321,7 @@ function DataTable(_props) {
2938
3321
  if (!tgt || !tgt.closest) return;
2939
3322
  const cellEl = tgt.closest("[data-grid-cell]");
2940
3323
  if (!cellEl) return;
3324
+ activeCellEngaged = true;
2941
3325
  const rowAttr = cellEl.getAttribute("data-row");
2942
3326
  const colAttr = cellEl.getAttribute("data-col-index");
2943
3327
  if (rowAttr == null || colAttr == null) return;
@@ -3111,13 +3495,62 @@ function DataTable(_props) {
3111
3495
  if (typeof requestAnimationFrame === "function") requestAnimationFrame(tryFocus);
3112
3496
  else setTimeout(tryFocus, 0);
3113
3497
  }
3114
- function clampActiveCell(rowCount, colCount) {
3498
+ function followOpenEditors(prevIds, nextIds) {
3499
+ if (!prevIds || sameIdSequence(prevIds, nextIds)) return false;
3500
+ let owns = false;
3501
+ const er = editingRow();
3502
+ if (er >= 0) {
3503
+ const ef = resolveFollow(prevIds, er, nextIds, true);
3504
+ if (ef.kind === "move") {
3505
+ setEditingRow(ef.index);
3506
+ focusEditorWhenReady(ef.index, editingCol(), false);
3507
+ owns = true;
3508
+ } else if (ef.kind !== "stay") {
3509
+ committedThisSession = true;
3510
+ editTransition = true;
3511
+ endEdit();
3512
+ editTransition = false;
3513
+ }
3514
+ }
3515
+ const rr = editingRowIndex();
3516
+ if (rr != null && rr >= 0) {
3517
+ const rf = resolveFollow(prevIds, rr, nextIds, true);
3518
+ if (rf.kind === "move") {
3519
+ setEditingRowIndex(rf.index);
3520
+ owns = true;
3521
+ } else if (rf.kind !== "stay") {
3522
+ editTransition = true;
3523
+ endRowEdit();
3524
+ editTransition = false;
3525
+ }
3526
+ }
3527
+ return owns;
3528
+ }
3529
+ function rowIdInModel(index) {
3530
+ const row = windowSource()[index];
3531
+ return row ? row.id : null;
3532
+ }
3533
+ function modelIndexOfRowId(id) {
3534
+ const rows = windowSource();
3535
+ for (let i = 0; i < rows.length; i++) if (rows[i].id === id) return i;
3536
+ return -1;
3537
+ }
3538
+ function followActiveCell(prevIds, nextIds, viewChangeOrWrite, rowCount, colCount, editFollowPending = false) {
3539
+ const editorOwnsFocus = followOpenEditors(prevIds, nextIds);
3540
+ if (!isGrid()) return;
3541
+ let follow = null;
3542
+ if (activeCellEngaged && rowCount > 0 && !activeIsHeader() && !gridEmptyFallback) follow = resolveFollow(prevIds, activeRow(), nextIds, viewChangeOrWrite);
3543
+ clampActiveCell(rowCount, colCount, follow, editFollowPending || editorOwnsFocus, rowCount <= 0 && viewChangeOrWrite);
3544
+ if (viewChangeOrWrite && !sameIdSequence(prevIds, nextIds)) clearRange();
3545
+ }
3546
+ function clampActiveCell(rowCount, colCount, follow = null, editFollowPending = false, focusEmptyPark = false) {
3115
3547
  if (!isGrid()) return;
3116
3548
  const colN = colCount != null ? colCount : visibleColCount();
3117
3549
  const rowN = rowCount != null ? rowCount : bodyRowCount();
3118
3550
  let recoverFocus = false;
3119
3551
  let doomedRow = -1;
3120
3552
  let doomedCol = 0;
3553
+ let focusOnBody = false;
3121
3554
  if (gridRoot) {
3122
3555
  const rootNode = gridRoot.getRootNode ? gridRoot.getRootNode() : null;
3123
3556
  const focusedEl = rootNode ? rootNode.activeElement : null;
@@ -3126,6 +3559,7 @@ function DataTable(_props) {
3126
3559
  const fRowAttr = focusedCell.getAttribute("data-row");
3127
3560
  const fColAttr = focusedCell.getAttribute("data-col-index");
3128
3561
  if (fRowAttr != null && fRowAttr !== "__header") {
3562
+ focusOnBody = true;
3129
3563
  const fr = parseInt(fRowAttr, 10);
3130
3564
  const fc = parseInt(fColAttr, 10);
3131
3565
  if (Number.isFinite(fr) && fr > rowN - 1) {
@@ -3145,6 +3579,7 @@ function DataTable(_props) {
3145
3579
  setActiveColIndex(0);
3146
3580
  gridEmptyFallback = true;
3147
3581
  clampRange(rowN - 1, colN - 1);
3582
+ if (focusEmptyPark && (focusOnBody || editFollowPending)) recoverGridFocus("__header", 0, headerLeafLevel());
3148
3583
  return;
3149
3584
  }
3150
3585
  if (gridEmptyFallback) {
@@ -3152,6 +3587,31 @@ function DataTable(_props) {
3152
3587
  setActiveIsHeader(false);
3153
3588
  setActiveRow(0);
3154
3589
  }
3590
+ if (follow && follow.kind === "move") {
3591
+ const nextRow = follow.index;
3592
+ setActiveRow(nextRow);
3593
+ clampRange(rowN - 1, colN - 1);
3594
+ _props.onActivecellChange?.({
3595
+ rowIndex: toAbsRow(nextRow),
3596
+ colIndex: col,
3597
+ isHeader: false
3598
+ });
3599
+ if (focusOnBody && !editFollowPending) focusCellWhenReady(nextRow, col);
3600
+ return;
3601
+ }
3602
+ if (follow && follow.kind === "park") {
3603
+ const level = headerLeafLevel();
3604
+ setActiveIsHeader(true);
3605
+ setActiveHeaderLevel(level);
3606
+ clampRange(rowN - 1, colN - 1);
3607
+ _props.onActivecellChange?.({
3608
+ rowIndex: null,
3609
+ colIndex: col,
3610
+ isHeader: true
3611
+ });
3612
+ if (focusOnBody || editFollowPending) recoverGridFocus("__header", col, level);
3613
+ return;
3614
+ }
3155
3615
  if (!activeIsHeader()) {
3156
3616
  const lastRow = rowN - 1;
3157
3617
  const maxRow = lastRow < 0 ? 0 : lastRow;
@@ -3217,6 +3677,7 @@ function DataTable(_props) {
3217
3677
  colVirtualizer._willUpdate();
3218
3678
  }
3219
3679
  let gridEmptyFallback = false;
3680
+ let activeCellEngaged = false;
3220
3681
  let rangeTransition = false;
3221
3682
  let rangeClickPending = null;
3222
3683
  let rangeActive = false;
@@ -3937,6 +4398,7 @@ function DataTable(_props) {
3937
4398
  const colId = columnIdAt(rowIndex, colIndex);
3938
4399
  if (colId == null || !columnEditable(colId)) return;
3939
4400
  committedThisSession = false;
4401
+ outsidePointerDown = false;
3940
4402
  setInvalid("");
3941
4403
  setEditingRowIndex(null);
3942
4404
  setRowDraft({});
@@ -4387,12 +4849,14 @@ function DataTable(_props) {
4387
4849
  const fromRow = editingRow();
4388
4850
  const fromCol = editingCol();
4389
4851
  const target = e.shiftKey ? prevEditableCell(fromRow, fromCol) : nextEditableCell(fromRow, fromCol);
4852
+ const targetRowId = target ? rowIdInModel(target.row) : null;
4390
4853
  const committed = commitEdit(void 0, true);
4391
- if (committed && target) {
4392
- setActiveRow(target.row);
4854
+ const advanceRow = targetRowId != null ? modelIndexOfRowId(targetRowId) : -1;
4855
+ if (committed && target && advanceRow >= 0) {
4856
+ setActiveRow(advanceRow);
4393
4857
  setActiveColIndex(target.col);
4394
- beginEdit(target.row, target.col, null);
4395
- } else if (committed) focusCellWhenReady(fromRow, fromCol);
4858
+ beginEdit(advanceRow, target.col, null);
4859
+ } else if (committed && target) {} else if (committed) focusCellWhenReady(fromRow, fromCol);
4396
4860
  } else if (key === "Escape") {
4397
4861
  e.preventDefault();
4398
4862
  cancelEdit();
@@ -6594,6 +7058,8 @@ function Column(_props) {
6594
7058
  expandable: false,
6595
7059
  groupable: true,
6596
7060
  aggregationFn: null,
7061
+ filterFn: null,
7062
+ sortingFn: null,
6597
7063
  editable: false,
6598
7064
  editor: "text",
6599
7065
  editorOptions: [],
@@ -6609,6 +7075,8 @@ function Column(_props) {
6609
7075
  "expandable",
6610
7076
  "groupable",
6611
7077
  "aggregationFn",
7078
+ "filterFn",
7079
+ "sortingFn",
6612
7080
  "editable",
6613
7081
  "editor",
6614
7082
  "editorOptions",
@@ -6626,11 +7094,15 @@ function Column(_props) {
6626
7094
  local.expandable,
6627
7095
  local.groupable,
6628
7096
  local.aggregationFn,
7097
+ local.filterFn,
7098
+ local.sortingFn,
6629
7099
  local.editable,
6630
7100
  local.editor,
6631
7101
  local.editorOptions,
6632
7102
  local.validate
6633
7103
  ], (v) => untrack(() => (() => {
7104
+ latestFilterFn = local.filterFn;
7105
+ latestSortingFn = local.sortingFn;
6634
7106
  if (reg) reg.registerColumn(colId(), buildSpec());
6635
7107
  })()), { defer: true }));
6636
7108
  onMount(() => {
@@ -6656,10 +7128,21 @@ function Column(_props) {
6656
7128
  let reg = null;
6657
7129
  reg = registry;
6658
7130
  let registered = false;
7131
+ let latestFilterFn = null;
7132
+ let latestSortingFn = null;
7133
+ const filterTrampoline = Object.assign((...args) => typeof latestFilterFn === "function" ? latestFilterFn(...args) : true, {
7134
+ autoRemove: (value, column) => typeof latestFilterFn === "function" && typeof latestFilterFn.autoRemove === "function" ? latestFilterFn.autoRemove(value, column) : false,
7135
+ resolveFilterValue: (value) => typeof latestFilterFn === "function" && typeof latestFilterFn.resolveFilterValue === "function" ? latestFilterFn.resolveFilterValue(value) : value
7136
+ });
7137
+ function sortingTrampoline(...args) {
7138
+ return typeof latestSortingFn === "function" ? latestSortingFn(...args) : 0;
7139
+ }
6659
7140
  function colId() {
6660
7141
  return local.id !== "" ? local.id : local.field;
6661
7142
  }
6662
7143
  function buildSpec() {
7144
+ latestFilterFn = local.filterFn;
7145
+ latestSortingFn = local.sortingFn;
6663
7146
  return {
6664
7147
  id: colId(),
6665
7148
  field: local.field !== "" ? local.field : colId(),
@@ -6671,6 +7154,8 @@ function Column(_props) {
6671
7154
  expandable: local.expandable,
6672
7155
  groupable: local.groupable,
6673
7156
  aggregationFn: local.aggregationFn,
7157
+ filterFn: typeof local.filterFn === "function" ? filterTrampoline : local.filterFn,
7158
+ sortingFn: typeof local.sortingFn === "function" ? sortingTrampoline : local.sortingFn,
6674
7159
  editable: local.editable,
6675
7160
  editor: local.editor,
6676
7161
  editorOptions: local.editorOptions,