@gridkitjs/core 0.10.0 → 0.11.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
@@ -63,13 +63,23 @@ function getColumnId(column) {
63
63
  function resolveRowId(row, index, getRowId) {
64
64
  return getRowId?.(row, index) ?? String(index);
65
65
  }
66
+ function labelFromField(field) {
67
+ return field.split(".").join(" ");
68
+ }
66
69
  function resolveColumnLabel(column) {
67
70
  const { headerTemplate } = column;
68
71
  if (headerTemplate === void 0) {
69
- return column.field.split(".").join(" ");
72
+ return column.header ?? labelFromField(column.field);
70
73
  }
71
74
  return typeof headerTemplate === "function" ? headerTemplate() : headerTemplate;
72
75
  }
76
+ function resolveColumnHeaderText(column) {
77
+ if (column.header !== void 0) {
78
+ return column.header;
79
+ }
80
+ const label = resolveColumnLabel(column);
81
+ return typeof label === "string" ? label : labelFromField(column.field);
82
+ }
73
83
  function buildKeyShortcuts(capabilities) {
74
84
  return [
75
85
  capabilities.reorderable ? "Control+ArrowLeft Control+ArrowRight" : "",
@@ -87,341 +97,675 @@ function accessDotted(obj, path) {
87
97
  return path.split(".").reduce((value, key) => isNested(value) ? value[key] : void 0, obj);
88
98
  }
89
99
 
90
- // src/util/navigation.ts
91
- var HEADER_ROW = -1;
92
- function clampFocus(focus, rowCount, columnCount) {
93
- return {
94
- rowIndex: Math.min(Math.max(focus.rowIndex, HEADER_ROW), rowCount - 1),
95
- columnIndex: Math.min(
96
- Math.max(focus.columnIndex, 0),
97
- Math.max(columnCount - 1, 0)
98
- )
99
- };
100
+ // src/util/sorting.ts
101
+ function isEmpty(value) {
102
+ return value === null || value === void 0;
100
103
  }
101
- function noRowsSkippable() {
102
- return false;
104
+ function toTime(value) {
105
+ return value instanceof Date ? value.getTime() : new Date(String(value)).getTime();
103
106
  }
104
- function skipSkippableRows(rowIndex, direction, rowCount, isSkippableRow, fallback) {
105
- let next = rowIndex;
106
- while (next >= 0 && next < rowCount && isSkippableRow(next)) {
107
- const stepped = next + direction;
108
- if (stepped < HEADER_ROW || stepped >= rowCount) {
109
- return fallback;
110
- }
111
- next = stepped;
107
+ function compareValues(a, b, type) {
108
+ if (isEmpty(a)) {
109
+ return isEmpty(b) ? 0 : 1;
112
110
  }
113
- return next;
114
- }
115
- function nextFocusForKey(key, modifiers, focus, rowCount, columnCount, pageSize, isSkippableRow = noRowsSkippable) {
116
- if (modifiers.altKey) {
117
- return null;
111
+ if (isEmpty(b)) {
112
+ return -1;
118
113
  }
119
- const { rowIndex, columnIndex } = focus;
120
- switch (key) {
121
- case "ArrowLeft":
122
- if (modifiers.ctrlKey) return null;
123
- return { rowIndex, columnIndex: columnIndex - 1 };
124
- case "ArrowRight":
125
- if (modifiers.ctrlKey) return null;
126
- return { rowIndex, columnIndex: columnIndex + 1 };
127
- case "ArrowUp":
128
- return {
129
- rowIndex: skipSkippableRows(
130
- rowIndex - 1,
131
- -1,
132
- rowCount,
133
- isSkippableRow,
134
- rowIndex
135
- ),
136
- columnIndex
137
- };
138
- case "ArrowDown":
139
- return {
140
- rowIndex: skipSkippableRows(
141
- rowIndex + 1,
142
- 1,
143
- rowCount,
144
- isSkippableRow,
145
- rowIndex
146
- ),
147
- columnIndex
148
- };
149
- // Ctrl takes Home and End to the grid's ends rather than the row's, which
150
- // is the one place Ctrl is navigation rather than reorder.
151
- case "Home":
152
- return modifiers.ctrlKey ? { rowIndex: HEADER_ROW, columnIndex: 0 } : { rowIndex, columnIndex: 0 };
153
- case "End":
154
- return modifiers.ctrlKey ? {
155
- rowIndex: skipSkippableRows(
156
- rowCount - 1,
157
- -1,
158
- rowCount,
159
- isSkippableRow,
160
- rowIndex
161
- ),
162
- columnIndex: columnCount - 1
163
- } : { rowIndex, columnIndex: columnCount - 1 };
164
- case "PageUp":
165
- return {
166
- rowIndex: skipSkippableRows(
167
- rowIndex - pageSize,
168
- -1,
169
- rowCount,
170
- isSkippableRow,
171
- rowIndex
172
- ),
173
- columnIndex
174
- };
175
- case "PageDown":
176
- return {
177
- rowIndex: skipSkippableRows(
178
- rowIndex + pageSize,
179
- 1,
180
- rowCount,
181
- isSkippableRow,
182
- rowIndex
183
- ),
184
- columnIndex
185
- };
114
+ switch (type) {
115
+ case "number":
116
+ case "decimal":
117
+ case "currency":
118
+ case "percent":
119
+ return Number(a) - Number(b);
120
+ case "boolean":
121
+ return Number(Boolean(a)) - Number(Boolean(b));
122
+ case "date":
123
+ case "dateTime":
124
+ case "time":
125
+ return toTime(a) - toTime(b);
186
126
  default:
187
- return null;
127
+ return String(a).localeCompare(String(b));
188
128
  }
189
129
  }
190
-
191
- // src/util/ordering.ts
192
- function applyColumnOrder(columns, order) {
193
- if (order.length === 0) {
194
- return columns;
195
- }
196
- const byId = new Map(columns.map((column) => [getColumnId(column), column]));
197
- const listed = order.map((id) => byId.get(id)).filter((column) => column !== void 0);
198
- const taken = new Set(listed.map((column) => getColumnId(column)));
199
- return [
200
- ...listed,
201
- ...columns.filter((column) => !taken.has(getColumnId(column)))
202
- ];
130
+ function sortDirectionFor(sort, columnId) {
131
+ return sort.find((entry) => entry.columnId === columnId)?.direction ?? null;
203
132
  }
204
- function moveColumnBefore(orderedIds, movedId, beforeId) {
205
- if (movedId === beforeId || !orderedIds.includes(movedId)) {
206
- return orderedIds;
133
+ function sortPriorityFor(sort, columnId) {
134
+ const index = sort.findIndex((entry) => entry.columnId === columnId);
135
+ return index === -1 ? null : index + 1;
136
+ }
137
+ function toggleColumnSort(sort, columnId, options) {
138
+ const index = sort.findIndex((entry) => entry.columnId === columnId);
139
+ const isSoleEntry = sort.length === 1 && index === 0;
140
+ if (!options.stack && !isSoleEntry) {
141
+ return [{ columnId, direction: "asc" }];
207
142
  }
208
- const without = orderedIds.filter((id) => id !== movedId);
209
- const target = beforeId === null ? without.length : without.indexOf(beforeId);
210
- if (target === -1) {
211
- return orderedIds;
143
+ const current = index === -1 ? void 0 : sort[index]?.direction;
144
+ const next = current === void 0 ? "asc" : current === "asc" ? "desc" : null;
145
+ if (next === null) {
146
+ return sort.filter((entry) => entry.columnId !== columnId);
212
147
  }
213
- const next = [...without];
214
- next.splice(target, 0, movedId);
215
- return next.every((id, index) => id === orderedIds[index]) ? orderedIds : next;
216
- }
217
- function movesColumn(orderedIds, movedId, beforeId) {
218
- return moveColumnBefore(orderedIds, movedId, beforeId) !== orderedIds;
219
- }
220
- function resolveDropBefore(orderedIds, targetId, side) {
221
- if (side === "before") {
222
- return targetId;
148
+ if (index === -1) {
149
+ return [...sort, { columnId, direction: next }];
223
150
  }
224
- const index = orderedIds.indexOf(targetId);
225
- return index === -1 ? null : orderedIds[index + 1] ?? null;
151
+ return sort.map(
152
+ (entry, i) => i === index ? { columnId, direction: next } : entry
153
+ );
226
154
  }
227
- function resolveKeyboardDropTarget(orderedIds, columnId, direction) {
228
- const index = orderedIds.indexOf(columnId);
229
- if (index === -1) {
230
- return void 0;
155
+ function sortRows(rows, sort, columns) {
156
+ if (sort.length === 0) {
157
+ return rows;
231
158
  }
232
- const targetIndex = direction === -1 ? index - 1 : index + 2;
233
- if (targetIndex < 0) {
234
- return void 0;
159
+ const byId = new Map(columns.map((entry) => [entry.id, entry.column]));
160
+ const active = sort.filter((entry) => byId.has(entry.columnId));
161
+ if (active.length === 0) {
162
+ return rows;
235
163
  }
236
- return orderedIds[targetIndex] ?? null;
164
+ const sorted = [...rows].sort((a, b) => {
165
+ for (const { columnId, direction } of active) {
166
+ const column = byId.get(columnId);
167
+ if (column === void 0) {
168
+ continue;
169
+ }
170
+ const left = accessDotted(a.row, column.field);
171
+ const right = accessDotted(b.row, column.field);
172
+ const leftEmpty = isEmpty(left);
173
+ const rightEmpty = isEmpty(right);
174
+ if (leftEmpty || rightEmpty) {
175
+ if (leftEmpty && rightEmpty) {
176
+ continue;
177
+ }
178
+ return leftEmpty ? 1 : -1;
179
+ }
180
+ const result = compareValues(left, right, column.type);
181
+ if (result !== 0) {
182
+ return direction === "asc" ? result : -result;
183
+ }
184
+ }
185
+ return 0;
186
+ });
187
+ return sorted.map(
188
+ (row, index) => row.rowIndex === index ? row : { ...row, rowIndex: index }
189
+ );
237
190
  }
238
191
 
239
- // src/util/selection.ts
240
- var NO_CHANGE = { added: [], removed: [] };
241
- function sameSelection(a, b) {
242
- return a.length === b.length && a.every((id, index) => id === b[index]);
192
+ // src/util/grouping.ts
193
+ function groupRowId(path) {
194
+ return JSON.stringify(path);
243
195
  }
244
- function toggleSelection(selection, id, mode) {
245
- if (mode === false) {
246
- return selection;
196
+ function bucketKey(value) {
197
+ if (value === null || value === void 0) {
198
+ return "\0empty";
247
199
  }
248
- if (selection.includes(id)) {
249
- return selection.filter((selectedId) => selectedId !== id);
200
+ if (value instanceof Date) {
201
+ return `\0date:${String(value.getTime())}`;
250
202
  }
251
- return mode === "single" ? [id] : [...selection, id];
252
- }
253
- function selectOnly(selection, id, mode) {
254
- if (mode === false) {
255
- return selection;
203
+ switch (typeof value) {
204
+ case "string":
205
+ case "number":
206
+ case "boolean":
207
+ case "bigint":
208
+ case "symbol":
209
+ return `\0${typeof value}:${String(value)}`;
210
+ case "function":
211
+ return "\0function";
212
+ default:
213
+ return `\0object:${JSON.stringify(value)}`;
256
214
  }
257
- return selection.length === 1 && selection[0] === id ? selection : [id];
258
215
  }
259
- function selectRange(selection, orderedIds, anchorId, focusId, mode) {
260
- if (mode !== "multiple") {
261
- return selectOnly(selection, focusId, mode);
262
- }
263
- const anchor = orderedIds.indexOf(anchorId);
264
- const focus = orderedIds.indexOf(focusId);
265
- if (anchor === -1 || focus === -1) {
266
- return selection;
267
- }
268
- const next = orderedIds.slice(
269
- Math.min(anchor, focus),
270
- Math.max(anchor, focus) + 1
271
- );
272
- return sameSelection(selection, next) ? selection : next;
273
- }
274
- function selectAll(selection, orderedIds, mode) {
275
- if (mode !== "multiple") {
276
- return selection;
277
- }
278
- return sameSelection(selection, orderedIds) ? selection : orderedIds;
279
- }
280
- function clearSelection(selection) {
281
- return selection.length === 0 ? selection : [];
282
- }
283
- function diffSelection(previous, next) {
284
- if (previous === next) {
285
- return NO_CHANGE;
216
+ function partitionByValue(rows, column, direction) {
217
+ const buckets = /* @__PURE__ */ new Map();
218
+ for (const row of rows) {
219
+ const value = accessDotted(row.row, column.field);
220
+ const key = bucketKey(value);
221
+ const bucket = buckets.get(key);
222
+ if (bucket === void 0) {
223
+ buckets.set(key, { value, rows: [row] });
224
+ } else {
225
+ bucket.rows.push(row);
226
+ }
286
227
  }
287
- const before = new Set(previous);
288
- const after = new Set(next);
289
- return {
290
- added: next.filter((id) => !before.has(id)),
291
- removed: previous.filter((id) => !after.has(id))
292
- };
228
+ const groups = [...buckets.values()];
229
+ groups.sort((a, b) => {
230
+ const result = compareValues(a.value, b.value, column.type);
231
+ return direction === "asc" ? result : -result;
232
+ });
233
+ return groups;
293
234
  }
294
- function isSameCell(a, b) {
295
- if (a === null || b === null) {
296
- return a === b;
235
+ function buildLevel(rows, levels, parentPath, expansion, byId, out) {
236
+ const level = levels[0];
237
+ if (level === void 0) {
238
+ out.push(...rows);
239
+ return;
297
240
  }
298
- return a.rowId === b.rowId && a.columnId === b.columnId;
299
- }
300
- function selectCell(selection, cell, mode) {
301
- if (mode === false || isSameCell(selection, cell)) {
302
- return selection;
241
+ const column = byId.get(level.columnId);
242
+ if (column === void 0) {
243
+ out.push(...rows);
244
+ return;
303
245
  }
304
- return cell;
305
- }
306
- function toggleCellSelection(selection, cell, mode) {
307
- if (mode === false) {
308
- return selection;
246
+ const rest = levels.slice(1);
247
+ const groups = partitionByValue(rows, column, level.direction ?? "asc");
248
+ for (const group of groups) {
249
+ const path = [...parentPath, group.value];
250
+ const groupId = groupRowId(path);
251
+ const expanded = !expansion.includes(groupId);
252
+ const header = {
253
+ kind: "group",
254
+ groupId,
255
+ level: parentPath.length,
256
+ columnId: level.columnId,
257
+ value: group.value,
258
+ path,
259
+ expanded,
260
+ count: group.rows.length,
261
+ // Renumbered globally once `groupRows` has the whole flattened array —
262
+ // a header has no row of its own to carry a dataset position, so
263
+ // `datasetIndex` takes the same renumbered value `rowIndex` does.
264
+ rowIndex: -1,
265
+ datasetIndex: -1,
266
+ // Set by `withGroupAggregates`, downstream of grouping — empty here
267
+ // since `groupRows` itself never knows about `AggregateState`.
268
+ aggregates: /* @__PURE__ */ new Map()
269
+ };
270
+ out.push(header);
271
+ if (expanded) {
272
+ buildLevel(group.rows, rest, path, expansion, byId, out);
273
+ }
309
274
  }
310
- return isSameCell(selection, cell) ? null : cell;
311
275
  }
312
- function intentOf(event) {
313
- if (event.shiftKey) {
314
- return "range";
276
+ function groupRows(rows, groupBy, expansion, columns) {
277
+ if (groupBy.length === 0) {
278
+ return rows;
315
279
  }
316
- return event.ctrlKey || event.metaKey ? "toggle" : "replace";
317
- }
318
- function applySelectionIntent(selection, orderedIds, anchorId, id, intent, mode) {
319
- switch (intent) {
320
- case "toggle":
321
- return toggleSelection(selection, id, mode);
322
- // A range with no anchor yet spans from the id to itself, which is the
323
- // plain click a first Shift-click may as well be.
324
- case "range":
325
- return selectRange(selection, orderedIds, anchorId ?? id, id, mode);
326
- default:
327
- return selectOnly(selection, id, mode);
280
+ const byId = new Map(columns.map((entry) => [entry.id, entry.column]));
281
+ const active = groupBy.filter((entry) => byId.has(entry.columnId));
282
+ if (active.length === 0) {
283
+ return rows;
328
284
  }
285
+ const out = [];
286
+ buildLevel(rows, active, [], expansion, byId, out);
287
+ return out.map((entry, index) => {
288
+ if ("kind" in entry) {
289
+ return entry.rowIndex === index && entry.datasetIndex === index ? entry : { ...entry, rowIndex: index, datasetIndex: index };
290
+ }
291
+ return entry.rowIndex === index ? entry : { ...entry, rowIndex: index };
292
+ });
329
293
  }
330
-
331
- // src/util/sizing.ts
332
- var DEFAULT_COLUMN_SIZES = {
333
- width: 150,
334
- minWidth: 40,
335
- maxWidth: Number.POSITIVE_INFINITY
336
- };
337
- var KEYBOARD_STEP = 10;
338
- function withDefaults(defaults) {
339
- return { ...DEFAULT_COLUMN_SIZES, ...defaults };
294
+ function toggleGroupExpansion(expansion, groupId) {
295
+ return expansion.includes(groupId) ? expansion.filter((id) => id !== groupId) : [...expansion, groupId];
340
296
  }
341
- function resolveColumnConstraints(column, defaults) {
342
- const resolved = withDefaults(defaults);
343
- return {
344
- minWidth: column.minWidth ?? resolved.minWidth,
345
- maxWidth: column.maxWidth ?? resolved.maxWidth
346
- };
297
+ function expandAllGroups(expansion) {
298
+ return expansion.length === 0 ? expansion : [];
347
299
  }
348
- function clampColumnWidth(width, constraints) {
349
- return Math.min(Math.max(width, constraints.minWidth), constraints.maxWidth);
300
+ function collapseAllGroups(rows) {
301
+ return rows.filter(
302
+ (entry) => "kind" in entry && entry.kind === "group"
303
+ ).map((entry) => entry.groupId);
350
304
  }
351
- function resolveColumnWidths(columns, sizing, options) {
352
- const resolved = withDefaults(options?.sizes);
353
- return columns.map((column) => {
354
- const id = getColumnId(column);
355
- const stored = sizing[id];
356
- return {
357
- column,
358
- id,
359
- sized: stored !== void 0,
360
- width: clampColumnWidth(
361
- stored ?? column.width ?? resolved.width,
362
- resolveColumnConstraints(column, resolved)
363
- ),
364
- label: resolveColumnLabel(column),
365
- resizable: column.resizable ?? options?.resizable ?? false,
366
- reorderable: column.reorderable ?? options?.reorderable ?? false,
367
- groupByDraggable: column.groupByDraggable ?? options?.groupByDraggable ?? false,
368
- alignment: column.alignment ?? alignmentForType(column.type ?? "string")
369
- };
370
- });
305
+ function moveGroupByBefore(groupBy, columnId, beforeColumnId) {
306
+ if (columnId === beforeColumnId) {
307
+ return groupBy;
308
+ }
309
+ const without = groupBy.filter((entry) => entry.columnId !== columnId);
310
+ const moved = groupBy.find((entry) => entry.columnId === columnId) ?? {
311
+ columnId
312
+ };
313
+ const target = beforeColumnId === null ? without.length : without.findIndex((entry) => entry.columnId === beforeColumnId);
314
+ if (target === -1) {
315
+ return groupBy;
316
+ }
317
+ const next = [...without];
318
+ next.splice(target, 0, moved);
319
+ return next.length === groupBy.length && next.every((entry, index) => entry === groupBy[index]) ? groupBy : next;
371
320
  }
372
- function totalColumnWidth(resolved) {
373
- return resolved.reduce((total, entry) => total + entry.width, 0);
321
+ function movesGroupBy(groupBy, columnId, beforeColumnId) {
322
+ return moveGroupByBefore(groupBy, columnId, beforeColumnId) !== groupBy;
374
323
  }
375
- function beginColumnResize(column, startWidth, startPosition, defaults) {
376
- return {
377
- columnId: getColumnId(column),
378
- startWidth,
379
- startPosition,
380
- constraints: resolveColumnConstraints(column, defaults)
324
+
325
+ // src/util/memoize.ts
326
+ function memoizeLast(compute) {
327
+ let last;
328
+ return (...args) => {
329
+ const previous = last;
330
+ if (previous !== void 0 && args.every((arg, index) => Object.is(arg, previous.args[index]))) {
331
+ return previous.result;
332
+ }
333
+ const result = compute(...args);
334
+ last = { args, result };
335
+ return result;
381
336
  };
382
337
  }
383
- function applyColumnResize(session, position) {
384
- return clampColumnWidth(
385
- session.startWidth + (position - session.startPosition),
386
- session.constraints
387
- );
338
+
339
+ // src/util/ordering.ts
340
+ function applyColumnOrder(columns, order) {
341
+ if (order.length === 0) {
342
+ return columns;
343
+ }
344
+ const byId = new Map(columns.map((column) => [getColumnId(column), column]));
345
+ const listed = order.map((id) => byId.get(id)).filter((column) => column !== void 0);
346
+ const taken = new Set(listed.map((column) => getColumnId(column)));
347
+ return [
348
+ ...listed,
349
+ ...columns.filter((column) => !taken.has(getColumnId(column)))
350
+ ];
388
351
  }
389
- function reachedBound(width, item) {
390
- return item.bound > item.width ? width >= item.bound : width <= item.bound;
352
+ function resolveDataColumns(columns, order) {
353
+ return applyColumnOrder(columns, order).map((column) => ({
354
+ id: getColumnId(column),
355
+ column
356
+ }));
391
357
  }
392
- function redistribute(entries, remaining) {
393
- for (; ; ) {
394
- const movable = entries.filter(
395
- (item) => item.adjustable && !reachedBound(item.width, item)
396
- );
397
- const left = remaining(entries.reduce((t, i) => t + i.width, 0));
398
- if (left <= 0 || movable.length === 0) {
399
- break;
400
- }
401
- const base = movable.reduce((total, item) => total + item.width, 0);
402
- let clamped = false;
403
- for (const item of movable) {
404
- const share = base === 0 ? left / movable.length : left * item.width / base;
405
- const next = item.width + (item.bound > item.width ? share : -share);
406
- if (reachedBound(next, item)) {
407
- item.width = item.bound;
408
- clamped = true;
409
- } else {
410
- item.width = next;
411
- }
412
- }
413
- if (!clamped) {
414
- break;
415
- }
358
+ function moveColumnBefore(orderedIds, movedId, beforeId) {
359
+ if (movedId === beforeId || !orderedIds.includes(movedId)) {
360
+ return orderedIds;
361
+ }
362
+ const without = orderedIds.filter((id) => id !== movedId);
363
+ const target = beforeId === null ? without.length : without.indexOf(beforeId);
364
+ if (target === -1) {
365
+ return orderedIds;
416
366
  }
367
+ const next = [...without];
368
+ next.splice(target, 0, movedId);
369
+ return next.every((id, index) => id === orderedIds[index]) ? orderedIds : next;
417
370
  }
418
- function fitColumnsToWidth(resolved, availableWidth, defaults) {
419
- const target = Math.floor(availableWidth);
420
- const total = totalColumnWidth(resolved);
421
- if (resolved.length === 0 || total === target) {
422
- return resolved;
371
+ function movesColumn(orderedIds, movedId, beforeId) {
372
+ return moveColumnBefore(orderedIds, movedId, beforeId) !== orderedIds;
373
+ }
374
+ function resolveDropBefore(orderedIds, targetId, side) {
375
+ if (side === "before") {
376
+ return targetId;
423
377
  }
424
- const growing = total < target;
378
+ const index = orderedIds.indexOf(targetId);
379
+ return index === -1 ? null : orderedIds[index + 1] ?? null;
380
+ }
381
+ function resolveKeyboardDropTarget(orderedIds, columnId, direction) {
382
+ const index = orderedIds.indexOf(columnId);
383
+ if (index === -1) {
384
+ return void 0;
385
+ }
386
+ const targetIndex = direction === -1 ? index - 1 : index + 2;
387
+ if (targetIndex < 0) {
388
+ return void 0;
389
+ }
390
+ return orderedIds[targetIndex] ?? null;
391
+ }
392
+
393
+ // src/util/paging.ts
394
+ function unitsOf(rows) {
395
+ const units = [];
396
+ let start = 0;
397
+ let openedByGroup = false;
398
+ for (let index = 0; index < rows.length; index++) {
399
+ const entry = rows[index];
400
+ if (entry === void 0) {
401
+ continue;
402
+ }
403
+ const isTopLevelHeader = "kind" in entry && entry.kind === "group" && entry.level === 0;
404
+ const isBoundary = index !== start && (isTopLevelHeader || !openedByGroup);
405
+ if (isBoundary) {
406
+ units.push({ start, end: index });
407
+ start = index;
408
+ openedByGroup = isTopLevelHeader;
409
+ } else if (index === start) {
410
+ openedByGroup = isTopLevelHeader;
411
+ }
412
+ }
413
+ if (start < rows.length) {
414
+ units.push({ start, end: rows.length });
415
+ }
416
+ return units;
417
+ }
418
+ function paginateRows(rows, pagination) {
419
+ const pageSize = Math.max(1, pagination.pageSize);
420
+ const units = unitsOf(rows);
421
+ const pageCount = Math.ceil(units.length / pageSize);
422
+ if (pageCount === 0) {
423
+ return { rows: [], pageCount: 0, pageIndex: 0 };
424
+ }
425
+ const pageIndex = Math.min(Math.max(pagination.pageIndex, 0), pageCount - 1);
426
+ const firstUnit = units[pageIndex * pageSize];
427
+ const lastUnit = units[Math.min(pageIndex * pageSize + pageSize, units.length) - 1];
428
+ if (firstUnit === void 0 || lastUnit === void 0) {
429
+ return { rows: [], pageCount, pageIndex };
430
+ }
431
+ const page = rows.slice(firstUnit.start, lastUnit.end);
432
+ const renumbered = page.map(
433
+ (entry, index) => entry.rowIndex === index ? entry : { ...entry, rowIndex: index }
434
+ );
435
+ return { rows: renumbered, pageCount, pageIndex };
436
+ }
437
+
438
+ // src/util/report.ts
439
+ function reportError(message) {
440
+ console.error(`GridKit: ${message}`);
441
+ }
442
+
443
+ // src/util/filterOperators.ts
444
+ var matchNothing = () => false;
445
+ function kindOf(type) {
446
+ switch (type) {
447
+ case "number":
448
+ case "decimal":
449
+ case "currency":
450
+ case "percent":
451
+ return "number";
452
+ case "boolean":
453
+ return "boolean";
454
+ case "date":
455
+ case "dateTime":
456
+ case "time":
457
+ return "date";
458
+ default:
459
+ return "text";
460
+ }
461
+ }
462
+ function isDateType(type) {
463
+ return kindOf(type) === "date";
464
+ }
465
+ function isEmptyCell(value) {
466
+ return value === null || value === void 0 || value === "";
467
+ }
468
+ function toDate(value) {
469
+ const date = value instanceof Date ? value : typeof value === "string" ? new Date(value) : void 0;
470
+ return date === void 0 || Number.isNaN(date.getTime()) ? void 0 : date;
471
+ }
472
+ function comparable(value, kind) {
473
+ switch (kind) {
474
+ case "number":
475
+ return typeof value === "number" && !Number.isNaN(value) ? value : void 0;
476
+ case "boolean":
477
+ return typeof value === "boolean" ? value : void 0;
478
+ case "date":
479
+ return toDate(value);
480
+ case "text":
481
+ return String(value).toLowerCase();
482
+ }
483
+ }
484
+ function isOperandOfKind(operand, kind) {
485
+ if (kind !== "text") {
486
+ return comparable(operand, kind) !== void 0;
487
+ }
488
+ return typeof operand === "string" || typeof operand === "number" || typeof operand === "boolean";
489
+ }
490
+ function compileComparison(operand, type) {
491
+ const kind = kindOf(type);
492
+ if (!isOperandOfKind(operand, kind)) {
493
+ return void 0;
494
+ }
495
+ const target = comparable(operand, kind);
496
+ return (value) => {
497
+ if (isEmptyCell(value)) {
498
+ return void 0;
499
+ }
500
+ const cell = comparable(value, kind);
501
+ return cell === void 0 ? void 0 : compareValues(cell, target, type);
502
+ };
503
+ }
504
+ function isScalarList(operand) {
505
+ return Array.isArray(operand);
506
+ }
507
+ function compileOrdering(operator, operand, type) {
508
+ const compare = compileComparison(operand, type);
509
+ if (compare === void 0) {
510
+ return matchNothing;
511
+ }
512
+ const where = (holds) => (value) => {
513
+ const result = compare(value);
514
+ return result !== void 0 && holds(result);
515
+ };
516
+ switch (operator) {
517
+ case "eq":
518
+ return where((result) => result === 0);
519
+ case "ne": {
520
+ const equals = where((result) => result === 0);
521
+ return (value) => !equals(value);
522
+ }
523
+ case "gt":
524
+ return where((result) => result > 0);
525
+ case "gte":
526
+ return where((result) => result >= 0);
527
+ case "lt":
528
+ return where((result) => result < 0);
529
+ case "lte":
530
+ return where((result) => result <= 0);
531
+ }
532
+ }
533
+ function compileBetween(operand, type) {
534
+ if (!isScalarList(operand) || operand.length !== 2) {
535
+ return matchNothing;
536
+ }
537
+ const [low, high] = operand;
538
+ const fromLow = compileComparison(low, type);
539
+ const fromHigh = compileComparison(high, type);
540
+ if (fromLow === void 0 || fromHigh === void 0) {
541
+ return matchNothing;
542
+ }
543
+ return (value) => {
544
+ const aboveLow = fromLow(value);
545
+ const belowHigh = fromHigh(value);
546
+ return aboveLow !== void 0 && belowHigh !== void 0 && aboveLow >= 0 && belowHigh <= 0;
547
+ };
548
+ }
549
+ function filterValueKey(value, type) {
550
+ return bucketKey(isDateType(type) ? toDate(value) ?? value : value);
551
+ }
552
+ function compileIn(operand, type) {
553
+ if (!isScalarList(operand)) {
554
+ return matchNothing;
555
+ }
556
+ const keys = new Set(
557
+ operand.filter((scalar) => !isEmptyCell(scalar)).map((scalar) => filterValueKey(scalar, type))
558
+ );
559
+ return (value) => !isEmptyCell(value) && keys.has(filterValueKey(value, type));
560
+ }
561
+ function compileText(operator, operand) {
562
+ if (!isOperandOfKind(operand, "text")) {
563
+ return matchNothing;
564
+ }
565
+ const text = String(operand).toLowerCase();
566
+ const read = (value) => String(value).toLowerCase();
567
+ switch (operator) {
568
+ case "contains":
569
+ return (value) => !isEmptyCell(value) && read(value).includes(text);
570
+ case "notContains":
571
+ return (value) => isEmptyCell(value) || !read(value).includes(text);
572
+ case "startsWith":
573
+ return (value) => !isEmptyCell(value) && read(value).startsWith(text);
574
+ case "endsWith":
575
+ return (value) => !isEmptyCell(value) && read(value).endsWith(text);
576
+ }
577
+ }
578
+ function compileOperatorEntry(entry, type) {
579
+ const { operator, value } = entry;
580
+ switch (operator) {
581
+ case "isEmpty":
582
+ return isEmptyCell;
583
+ case "isNotEmpty":
584
+ return (cell) => !isEmptyCell(cell);
585
+ case "contains":
586
+ case "notContains":
587
+ case "startsWith":
588
+ case "endsWith":
589
+ return compileText(operator, value);
590
+ case "between":
591
+ return compileBetween(value, type);
592
+ case "in":
593
+ return compileIn(value, type);
594
+ case "eq":
595
+ case "ne":
596
+ case "gt":
597
+ case "gte":
598
+ case "lt":
599
+ case "lte":
600
+ return compileOrdering(operator, value, type);
601
+ }
602
+ }
603
+
604
+ // src/util/serialize.ts
605
+ function serializeScalar(scalar) {
606
+ return scalar instanceof Date ? scalar.toISOString() : scalar;
607
+ }
608
+ function serializeOperand(operand) {
609
+ return isScalarList(operand) ? operand.map(serializeScalar) : serializeScalar(operand);
610
+ }
611
+ function serializeEntry(entry) {
612
+ if ("combinator" in entry) {
613
+ const entries = serializeFilter(entry.entries);
614
+ return entries.length === 0 && entry.entries.length > 0 ? void 0 : { combinator: entry.combinator, entries };
615
+ }
616
+ if ("predicate" in entry) {
617
+ reportError(
618
+ entry.columnId === void 0 ? "a predicate filter entry across every column cannot be serialised, so it is left out of the saved state." : `a predicate filter entry on \`${entry.columnId}\` cannot be serialised, so it is left out of the saved state.`
619
+ );
620
+ return void 0;
621
+ }
622
+ if ("operator" in entry) {
623
+ const { value, ...rest } = entry;
624
+ return value === void 0 ? rest : { ...rest, value: serializeOperand(value) };
625
+ }
626
+ if ("value" in entry) {
627
+ const { value, ...rest } = entry;
628
+ return {
629
+ ...rest,
630
+ value: value instanceof Date ? value.toISOString() : value
631
+ };
632
+ }
633
+ return entry;
634
+ }
635
+ function serializeFilter(filter) {
636
+ return filter.map((entry) => serializeEntry(entry)).filter((entry) => entry !== void 0);
637
+ }
638
+ function serializeGridState(state) {
639
+ return { ...state, filter: serializeFilter(state.filter) };
640
+ }
641
+ function reviveScalar(scalar, type) {
642
+ return isDateType(type) && typeof scalar === "string" ? toDate(scalar) ?? scalar : scalar;
643
+ }
644
+ function reviveOperand(operand, type) {
645
+ return isScalarList(operand) ? operand.map((scalar) => reviveScalar(scalar, type)) : reviveScalar(operand, type);
646
+ }
647
+ function reviveEntry(entry, typeOf) {
648
+ if ("combinator" in entry) {
649
+ return {
650
+ combinator: entry.combinator,
651
+ entries: reviveEntries(entry.entries, typeOf)
652
+ };
653
+ }
654
+ if ("operator" in entry) {
655
+ const { value, ...rest } = entry;
656
+ return value === void 0 ? rest : { ...rest, value: reviveOperand(value, typeOf(entry.columnId)) };
657
+ }
658
+ if ("value" in entry) {
659
+ const { value, ...rest } = entry;
660
+ return {
661
+ ...rest,
662
+ value: typeof value === "string" ? new Date(value) : value
663
+ };
664
+ }
665
+ return entry;
666
+ }
667
+ function reviveEntries(filter, typeOf) {
668
+ return filter.map((entry) => reviveEntry(entry, typeOf));
669
+ }
670
+ function reviveFilter(filter, columns) {
671
+ const types = new Map(columns.map((entry) => [entry.id, entry.column.type]));
672
+ return reviveEntries(filter, (columnId) => types.get(columnId));
673
+ }
674
+
675
+ // src/util/sizing.ts
676
+ var DEFAULT_COLUMN_SIZES = {
677
+ width: 150,
678
+ minWidth: 40,
679
+ maxWidth: Number.POSITIVE_INFINITY
680
+ };
681
+ var KEYBOARD_STEP = 10;
682
+ function withDefaults(defaults) {
683
+ return { ...DEFAULT_COLUMN_SIZES, ...defaults };
684
+ }
685
+ function resolveColumnConstraints(column, defaults) {
686
+ const resolved = withDefaults(defaults);
687
+ return {
688
+ minWidth: column.minWidth ?? resolved.minWidth,
689
+ maxWidth: column.maxWidth ?? resolved.maxWidth
690
+ };
691
+ }
692
+ function clampColumnWidth(width, constraints) {
693
+ return Math.min(Math.max(width, constraints.minWidth), constraints.maxWidth);
694
+ }
695
+ function resolveColumnWidths(columns, sizing, options) {
696
+ const resolved = withDefaults(options?.sizes);
697
+ return columns.map((column) => {
698
+ const id = getColumnId(column);
699
+ const stored = sizing[id];
700
+ return {
701
+ column,
702
+ id,
703
+ sized: stored !== void 0,
704
+ width: clampColumnWidth(
705
+ stored ?? column.width ?? resolved.width,
706
+ resolveColumnConstraints(column, resolved)
707
+ ),
708
+ label: resolveColumnLabel(column),
709
+ resizable: column.resizable ?? options?.resizable ?? false,
710
+ reorderable: column.reorderable ?? options?.reorderable ?? false,
711
+ groupByDraggable: column.groupByDraggable ?? options?.groupByDraggable ?? false,
712
+ alignment: column.alignment ?? alignmentForType(column.type ?? "string")
713
+ };
714
+ });
715
+ }
716
+ function totalColumnWidth(resolved) {
717
+ return resolved.reduce((total, entry) => total + entry.width, 0);
718
+ }
719
+ function beginColumnResize(column, startWidth, startPosition, defaults) {
720
+ return {
721
+ columnId: getColumnId(column),
722
+ startWidth,
723
+ startPosition,
724
+ constraints: resolveColumnConstraints(column, defaults)
725
+ };
726
+ }
727
+ function applyColumnResize(session, position) {
728
+ return clampColumnWidth(
729
+ session.startWidth + (position - session.startPosition),
730
+ session.constraints
731
+ );
732
+ }
733
+ function reachedBound(width, item) {
734
+ return item.bound > item.width ? width >= item.bound : width <= item.bound;
735
+ }
736
+ function redistribute(entries, remaining) {
737
+ for (; ; ) {
738
+ const movable = entries.filter(
739
+ (item) => item.adjustable && !reachedBound(item.width, item)
740
+ );
741
+ const left = remaining(entries.reduce((t, i) => t + i.width, 0));
742
+ if (left <= 0 || movable.length === 0) {
743
+ break;
744
+ }
745
+ const base = movable.reduce((total, item) => total + item.width, 0);
746
+ let clamped = false;
747
+ for (const item of movable) {
748
+ const share = base === 0 ? left / movable.length : left * item.width / base;
749
+ const next = item.width + (item.bound > item.width ? share : -share);
750
+ if (reachedBound(next, item)) {
751
+ item.width = item.bound;
752
+ clamped = true;
753
+ } else {
754
+ item.width = next;
755
+ }
756
+ }
757
+ if (!clamped) {
758
+ break;
759
+ }
760
+ }
761
+ }
762
+ function fitColumnsToWidth(resolved, availableWidth, defaults) {
763
+ const target = Math.floor(availableWidth);
764
+ const total = totalColumnWidth(resolved);
765
+ if (resolved.length === 0 || total === target) {
766
+ return resolved;
767
+ }
768
+ const growing = total < target;
425
769
  const entries = resolved.map((entry) => {
426
770
  const constraints = resolveColumnConstraints(entry.column, defaults);
427
771
  return {
@@ -468,96 +812,180 @@ function revertColumnSize(current, base, columnId) {
468
812
  return next;
469
813
  }
470
814
 
471
- // src/util/sorting.ts
472
- function isEmpty(value) {
815
+ // src/util/aggregation.ts
816
+ function isEmpty2(value) {
473
817
  return value === null || value === void 0;
474
818
  }
475
- function toTime(value) {
476
- return value instanceof Date ? value.getTime() : new Date(String(value)).getTime();
819
+ function toNumber(value) {
820
+ return Number(value);
477
821
  }
478
- function compareValues(a, b, type) {
479
- if (isEmpty(a)) {
480
- return isEmpty(b) ? 0 : 1;
822
+ function distinctKey(value) {
823
+ if (isEmpty2(value)) {
824
+ return "\0empty";
481
825
  }
482
- if (isEmpty(b)) {
483
- return -1;
826
+ if (value instanceof Date) {
827
+ return `\0date:${String(value.getTime())}`;
484
828
  }
485
- switch (type) {
829
+ switch (typeof value) {
830
+ case "string":
486
831
  case "number":
487
- case "decimal":
488
- case "currency":
489
- case "percent":
490
- return Number(a) - Number(b);
491
832
  case "boolean":
492
- return Number(Boolean(a)) - Number(Boolean(b));
493
- case "date":
494
- case "dateTime":
495
- case "time":
496
- return toTime(a) - toTime(b);
833
+ case "bigint":
834
+ case "symbol":
835
+ return `\0${typeof value}:${String(value)}`;
836
+ case "function":
837
+ return "\0function";
497
838
  default:
498
- return String(a).localeCompare(String(b));
839
+ return `\0object:${JSON.stringify(value)}`;
499
840
  }
500
841
  }
501
- function sortDirectionFor(sort, columnId) {
502
- return sort.find((entry) => entry.columnId === columnId)?.direction ?? null;
842
+ function reduceBuiltIn(rows, fn, column) {
843
+ if (fn === "count") {
844
+ return rows.length;
845
+ }
846
+ const field = column?.column.field;
847
+ const values = field === void 0 ? [] : rows.map((row) => accessDotted(row, field));
848
+ switch (fn) {
849
+ case "sum": {
850
+ let total = 0;
851
+ for (const value of values) {
852
+ if (isEmpty2(value)) continue;
853
+ total += toNumber(value);
854
+ }
855
+ return total;
856
+ }
857
+ case "avg": {
858
+ const present = values.filter((value) => !isEmpty2(value));
859
+ if (present.length === 0) return void 0;
860
+ const total = present.reduce(
861
+ (acc, value) => acc + toNumber(value),
862
+ 0
863
+ );
864
+ return total / present.length;
865
+ }
866
+ case "min":
867
+ case "max": {
868
+ const type = column?.column.type;
869
+ let result;
870
+ for (const value of values) {
871
+ if (isEmpty2(value)) continue;
872
+ if (result === void 0) {
873
+ result = value;
874
+ continue;
875
+ }
876
+ const comparison = compareValues(value, result, type);
877
+ if (fn === "min" ? comparison < 0 : comparison > 0) {
878
+ result = value;
879
+ }
880
+ }
881
+ return result;
882
+ }
883
+ case "countDistinct": {
884
+ const seen = /* @__PURE__ */ new Set();
885
+ for (const value of values) {
886
+ if (isEmpty2(value)) continue;
887
+ seen.add(distinctKey(value));
888
+ }
889
+ return seen.size;
890
+ }
891
+ }
503
892
  }
504
- function sortPriorityFor(sort, columnId) {
505
- const index = sort.findIndex((entry) => entry.columnId === columnId);
506
- return index === -1 ? null : index + 1;
893
+ function keyOf(spec) {
894
+ return spec.id ?? spec.columnId;
507
895
  }
508
- function toggleColumnSort(sort, columnId, options) {
509
- const index = sort.findIndex((entry) => entry.columnId === columnId);
510
- const isSoleEntry = sort.length === 1 && index === 0;
511
- if (!options.stack && !isSoleEntry) {
512
- return [{ columnId, direction: "asc" }];
896
+ function computeAggregates(rows, specs, columns) {
897
+ const results = /* @__PURE__ */ new Map();
898
+ if (specs.length === 0) {
899
+ return results;
513
900
  }
514
- const current = index === -1 ? void 0 : sort[index]?.direction;
515
- const next = current === void 0 ? "asc" : current === "asc" ? "desc" : null;
516
- if (next === null) {
517
- return sort.filter((entry) => entry.columnId !== columnId);
901
+ const byId = new Map(columns.map((entry) => [entry.id, entry]));
902
+ for (const spec of specs) {
903
+ const value = typeof spec.fn === "function" ? spec.fn(rows) : reduceBuiltIn(rows, spec.fn, byId.get(spec.columnId));
904
+ results.set(keyOf(spec), value);
518
905
  }
519
- if (index === -1) {
520
- return [...sort, { columnId, direction: next }];
906
+ return results;
907
+ }
908
+ function leafDescendantsOf(flatRows, path, groupBy, byId) {
909
+ const keys = path.map((value) => bucketKey(value));
910
+ return flatRows.filter(
911
+ (row) => keys.every((key, level) => {
912
+ const columnId = groupBy[level]?.columnId;
913
+ const column = columnId === void 0 ? void 0 : byId.get(columnId);
914
+ if (column === void 0) return false;
915
+ return bucketKey(accessDotted(row.row, column.field)) === key;
916
+ })
917
+ ).map((row) => row.row);
918
+ }
919
+ function appendAggregatedSpan(displayRows, start, levelFloor, flatRows, groupBy, byId, specs, columns, display, out) {
920
+ let index = start;
921
+ while (index < displayRows.length) {
922
+ const entry = displayRows[index];
923
+ if (entry === void 0) {
924
+ index++;
925
+ continue;
926
+ }
927
+ if (levelFloor !== null && "kind" in entry && entry.kind === "group" && entry.level <= levelFloor) {
928
+ break;
929
+ }
930
+ index++;
931
+ if (!("kind" in entry) || entry.kind !== "group") {
932
+ out.push(entry);
933
+ continue;
934
+ }
935
+ const leaves = leafDescendantsOf(flatRows, entry.path, groupBy, byId);
936
+ const aggregates = computeAggregates(leaves, specs, columns);
937
+ const withAggregates = { ...entry, aggregates };
938
+ out.push(withAggregates);
939
+ index = appendAggregatedSpan(
940
+ displayRows,
941
+ index,
942
+ entry.level,
943
+ flatRows,
944
+ groupBy,
945
+ byId,
946
+ specs,
947
+ columns,
948
+ display,
949
+ out
950
+ );
951
+ if (display === "row") {
952
+ const summary = {
953
+ kind: "group-summary",
954
+ groupId: entry.groupId,
955
+ level: entry.level,
956
+ rowIndex: -1,
957
+ datasetIndex: -1,
958
+ aggregates
959
+ };
960
+ out.push(summary);
961
+ }
521
962
  }
522
- return sort.map(
523
- (entry, i) => i === index ? { columnId, direction: next } : entry
524
- );
963
+ return index;
525
964
  }
526
- function sortRows(rows, sort, columns) {
527
- if (sort.length === 0) {
528
- return rows;
965
+ function withGroupAggregates(displayRows, flatRows, groupBy, specs, columns, display = "inline") {
966
+ if (specs.length === 0) {
967
+ return displayRows;
529
968
  }
530
969
  const byId = new Map(columns.map((entry) => [entry.id, entry.column]));
531
- const active = sort.filter((entry) => byId.has(entry.columnId));
532
- if (active.length === 0) {
533
- return rows;
534
- }
535
- const sorted = [...rows].sort((a, b) => {
536
- for (const { columnId, direction } of active) {
537
- const column = byId.get(columnId);
538
- if (column === void 0) {
539
- continue;
540
- }
541
- const left = accessDotted(a.row, column.field);
542
- const right = accessDotted(b.row, column.field);
543
- const leftEmpty = isEmpty(left);
544
- const rightEmpty = isEmpty(right);
545
- if (leftEmpty || rightEmpty) {
546
- if (leftEmpty && rightEmpty) {
547
- continue;
548
- }
549
- return leftEmpty ? 1 : -1;
550
- }
551
- const result = compareValues(left, right, column.type);
552
- if (result !== 0) {
553
- return direction === "asc" ? result : -result;
554
- }
970
+ const out = [];
971
+ appendAggregatedSpan(
972
+ displayRows,
973
+ 0,
974
+ null,
975
+ flatRows,
976
+ groupBy,
977
+ byId,
978
+ specs,
979
+ columns,
980
+ display,
981
+ out
982
+ );
983
+ return out.map((entry, position) => {
984
+ if ("kind" in entry) {
985
+ return entry.rowIndex === position && entry.datasetIndex === position ? entry : { ...entry, rowIndex: position, datasetIndex: position };
555
986
  }
556
- return 0;
987
+ return entry.rowIndex === position ? entry : { ...entry, rowIndex: position };
557
988
  });
558
- return sorted.map(
559
- (row, index) => row.rowIndex === index ? row : { ...row, rowIndex: index }
560
- );
561
989
  }
562
990
 
563
991
  // src/util/filtering.ts
@@ -635,10 +1063,18 @@ function compileFilterEntry(entry, byId, columns) {
635
1063
  const { columnId: columnId2, predicate } = entry;
636
1064
  const column = columnId2 === void 0 ? void 0 : byId.get(columnId2);
637
1065
  return (row) => predicate(
638
- column === void 0 ? void 0 : accessDotted(row.row, column.field),
639
- row.row
1066
+ column === void 0 ? void 0 : accessDotted(row, column.field),
1067
+ row
640
1068
  );
641
1069
  }
1070
+ if ("operator" in entry) {
1071
+ const column = byId.get(entry.columnId);
1072
+ if (column === void 0) {
1073
+ return () => false;
1074
+ }
1075
+ const test2 = compileOperatorEntry(entry, column.type);
1076
+ return (row) => test2(accessDotted(row, column.field));
1077
+ }
642
1078
  if ("value" in entry) {
643
1079
  const { columnId: columnId2, value } = entry;
644
1080
  if (columnId2 !== void 0) {
@@ -646,15 +1082,11 @@ function compileFilterEntry(entry, byId, columns) {
646
1082
  if (column === void 0) {
647
1083
  return () => false;
648
1084
  }
649
- return (row) => matchesTypedValue(
650
- accessDotted(row.row, column.field),
651
- value,
652
- column.type
653
- );
1085
+ return (row) => matchesTypedValue(accessDotted(row, column.field), value, column.type);
654
1086
  }
655
1087
  return (row) => columns.some(
656
1088
  (entry2) => matchesTypedValue(
657
- accessDotted(row.row, entry2.column.field),
1089
+ accessDotted(row, entry2.column.field),
658
1090
  value,
659
1091
  entry2.column.type
660
1092
  )
@@ -667,9 +1099,14 @@ function compileFilterEntry(entry, byId, columns) {
667
1099
  if (column === void 0) {
668
1100
  return () => false;
669
1101
  }
670
- return (row) => test(accessDotted(row.row, column.field));
1102
+ return (row) => test(accessDotted(row, column.field));
671
1103
  }
672
- return (row) => columns.some((entry2) => test(accessDotted(row.row, entry2.column.field)));
1104
+ return (row) => columns.some((entry2) => test(accessDotted(row, entry2.column.field)));
1105
+ }
1106
+ function compileFilter(filter, columns) {
1107
+ const byId = new Map(columns.map((entry) => [entry.id, entry.column]));
1108
+ const compiled = filter.filter((entry) => isEntryUsable(entry, byId)).map((entry) => compileFilterEntry(entry, byId, columns));
1109
+ return compiled.length === 0 ? void 0 : (row) => compiled.every((test) => test(row));
673
1110
  }
674
1111
  function matchesQuery(value, query) {
675
1112
  return compileFilterQuery(query)(value);
@@ -704,231 +1141,1106 @@ function filterRows(rows, filter, columns) {
704
1141
  if (filter.length === 0) {
705
1142
  return rows;
706
1143
  }
707
- const byId = new Map(columns.map((entry) => [entry.id, entry.column]));
708
- const active = filter.filter((entry) => isEntryUsable(entry, byId));
709
- if (active.length === 0) {
1144
+ const test = compileFilter(filter, columns);
1145
+ if (test === void 0) {
710
1146
  return rows;
711
1147
  }
712
- const compiled = active.map(
713
- (entry) => compileFilterEntry(entry, byId, columns)
714
- );
715
- const filtered = rows.filter((row) => compiled.every((test) => test(row)));
1148
+ const filtered = rows.filter((row) => test(row.row));
716
1149
  return filtered.map(
717
1150
  (row, index) => row.rowIndex === index ? row : { ...row, rowIndex: index }
718
1151
  );
719
1152
  }
720
1153
 
721
- // src/util/grouping.ts
722
- function groupRowId(path) {
723
- return JSON.stringify(path);
1154
+ // src/util/rows.ts
1155
+ function resolveShownRows(rows, filter, sort, columns) {
1156
+ return sortRows(filterRows(rows, filter, columns), sort, columns);
724
1157
  }
725
- function bucketKey(value) {
726
- if (value === null || value === void 0) {
727
- return "\0empty";
728
- }
729
- if (value instanceof Date) {
730
- return `\0date:${String(value.getTime())}`;
731
- }
732
- switch (typeof value) {
733
- case "string":
734
- case "number":
735
- case "boolean":
736
- case "bigint":
737
- case "symbol":
738
- return `\0${typeof value}:${String(value)}`;
739
- case "function":
740
- return "\0function";
741
- default:
742
- return `\0object:${JSON.stringify(value)}`;
743
- }
1158
+ function resolveRows(rowsById, ids) {
1159
+ return ids.map((id) => rowsById.get(id)).filter((row) => row !== void 0);
744
1160
  }
745
- function partitionByValue(rows, column, direction) {
746
- const buckets = /* @__PURE__ */ new Map();
747
- for (const row of rows) {
748
- const value = accessDotted(row.row, column.field);
749
- const key = bucketKey(value);
750
- const bucket = buckets.get(key);
751
- if (bucket === void 0) {
752
- buckets.set(key, { value, rows: [row] });
753
- } else {
754
- bucket.rows.push(row);
755
- }
1161
+ function resolveColumns(columns, ids) {
1162
+ return ids.map((id) => {
1163
+ const columnIndex = columns.findIndex((entry) => entry.id === id);
1164
+ const column = columns[columnIndex];
1165
+ return column === void 0 ? void 0 : { columnId: id, column, columnIndex };
1166
+ }).filter((column) => column !== void 0);
1167
+ }
1168
+ function resolveCell(rowsById, columns, cell) {
1169
+ if (cell === null) {
1170
+ return null;
756
1171
  }
757
- const groups = [...buckets.values()];
758
- groups.sort((a, b) => {
759
- const result = compareValues(a.value, b.value, column.type);
760
- return direction === "asc" ? result : -result;
761
- });
762
- return groups;
1172
+ const entry = rowsById.get(cell.rowId);
1173
+ const columnIndex = columns.findIndex(
1174
+ (candidate) => candidate.id === cell.columnId
1175
+ );
1176
+ const column = columns[columnIndex];
1177
+ if (entry === void 0 || column === void 0) {
1178
+ return null;
1179
+ }
1180
+ return {
1181
+ rowId: cell.rowId,
1182
+ columnId: cell.columnId,
1183
+ row: entry.row,
1184
+ column,
1185
+ rowIndex: entry.rowIndex,
1186
+ columnIndex,
1187
+ value: accessDotted(entry.row, column.column.field)
1188
+ };
763
1189
  }
764
- function buildLevel(rows, levels, parentPath, expansion, byId, out) {
765
- const level = levels[0];
766
- if (level === void 0) {
767
- out.push(...rows);
768
- return;
1190
+
1191
+ // src/util/visibility.ts
1192
+ function isColumnVisible(column, visibility) {
1193
+ return visibility[getColumnId(column)] ?? column.hidden !== true;
1194
+ }
1195
+ function applyColumnVisibility(columns, visibility) {
1196
+ const visible = columns.filter(
1197
+ (entry) => isColumnVisible(entry.column, visibility)
1198
+ );
1199
+ return visible.length === columns.length ? columns : visible;
1200
+ }
1201
+ function setColumnVisibility(columns, visibility, columnIds, visible) {
1202
+ const requested = new Set(columnIds);
1203
+ const changed = columns.filter(
1204
+ (entry) => requested.has(entry.id) && isColumnVisible(entry.column, visibility) !== visible
1205
+ );
1206
+ if (changed.length === 0) {
1207
+ return visibility;
769
1208
  }
770
- const column = byId.get(level.columnId);
771
- if (column === void 0) {
772
- out.push(...rows);
773
- return;
1209
+ return {
1210
+ ...visibility,
1211
+ ...Object.fromEntries(changed.map((entry) => [entry.id, visible]))
1212
+ };
1213
+ }
1214
+
1215
+ // src/util/view.ts
1216
+ var NO_AGGREGATES = [];
1217
+ function identifyRows(rows, getRowId) {
1218
+ return rows.map((row, rowIndex) => ({
1219
+ rowId: resolveRowId(row, rowIndex, getRowId),
1220
+ row,
1221
+ rowIndex,
1222
+ datasetIndex: rowIndex
1223
+ }));
1224
+ }
1225
+ function sizeColumns(visibleColumns, sizing, sizes, resizable, reorderable, groupByDraggable, resizeMode, viewportWidth) {
1226
+ const widths = resolveColumnWidths(
1227
+ visibleColumns.map((entry) => entry.column),
1228
+ sizing,
1229
+ { sizes, resizable, reorderable, groupByDraggable }
1230
+ );
1231
+ return resizeMode === "fit" && viewportWidth !== null ? fitColumnsToWidth(widths, viewportWidth, sizes) : widths;
1232
+ }
1233
+ function totalAggregates(rows, aggregates, dataColumns) {
1234
+ return computeAggregates(
1235
+ rows.map((entry) => entry.row),
1236
+ aggregates,
1237
+ dataColumns
1238
+ );
1239
+ }
1240
+ function paginate(displayRows, paginated, pagination) {
1241
+ return paginated ? paginateRows(displayRows, pagination) : { rows: displayRows, pageCount: 1, pageIndex: 0 };
1242
+ }
1243
+ function narrowToDataRows(pageRows, rows, reshaped) {
1244
+ return reshaped ? pageRows.filter((entry) => !("kind" in entry)) : rows;
1245
+ }
1246
+ function createGridViewResolver() {
1247
+ const identify = memoizeLast(identifyRows);
1248
+ const orderColumns = memoizeLast(resolveDataColumns);
1249
+ const showColumns = memoizeLast(applyColumnVisibility);
1250
+ const size = memoizeLast(sizeColumns);
1251
+ const filterAndSort = memoizeLast(resolveShownRows);
1252
+ const group = memoizeLast(groupRows);
1253
+ const aggregate = memoizeLast(withGroupAggregates);
1254
+ const total = memoizeLast(totalAggregates);
1255
+ const page = memoizeLast(paginate);
1256
+ const narrow = memoizeLast(narrowToDataRows);
1257
+ const assemble = memoizeLast(
1258
+ (dataColumns, columns, rows, displayRows, pageRows, displayDataRows, pageCount, pageIndex, grandTotal) => ({
1259
+ dataColumns,
1260
+ columns,
1261
+ rows,
1262
+ displayRows,
1263
+ pageRows,
1264
+ displayDataRows,
1265
+ pageCount,
1266
+ pageIndex,
1267
+ grandTotal
1268
+ })
1269
+ );
1270
+ return ({ rows: data, getRowId, columns: definitions, state, options }) => {
1271
+ const paginated = options.paginated ?? false;
1272
+ const aggregates = options.aggregates ?? NO_AGGREGATES;
1273
+ const dataColumns = orderColumns(definitions, state.columnOrder);
1274
+ const columns = size(
1275
+ showColumns(dataColumns, state.columnVisibility),
1276
+ state.columnSizing,
1277
+ options.columnSizeDefaults,
1278
+ options.resizableColumns,
1279
+ options.reorderableColumns,
1280
+ options.groupByDraggableColumns,
1281
+ options.resizeMode ?? "fit",
1282
+ options.viewportWidth ?? null
1283
+ );
1284
+ const rows = filterAndSort(
1285
+ identify(data, getRowId),
1286
+ state.filter,
1287
+ state.columnSort,
1288
+ dataColumns
1289
+ );
1290
+ const displayRows = aggregate(
1291
+ group(rows, state.groupBy, state.groupExpansion, dataColumns),
1292
+ rows,
1293
+ state.groupBy,
1294
+ aggregates,
1295
+ dataColumns,
1296
+ options.groupAggregateDisplay ?? "inline"
1297
+ );
1298
+ const {
1299
+ rows: pageRows,
1300
+ pageCount,
1301
+ pageIndex
1302
+ } = page(displayRows, paginated, state.pagination);
1303
+ return assemble(
1304
+ dataColumns,
1305
+ columns,
1306
+ rows,
1307
+ displayRows,
1308
+ pageRows,
1309
+ narrow(pageRows, rows, state.groupBy.length > 0 || paginated),
1310
+ pageCount,
1311
+ pageIndex,
1312
+ total(rows, aggregates, dataColumns)
1313
+ );
1314
+ };
1315
+ }
1316
+ function resolveGridView(input) {
1317
+ return createGridViewResolver()(input);
1318
+ }
1319
+
1320
+ // src/controller/changes.ts
1321
+ function changedEntryColumnId(previous, next) {
1322
+ const length = Math.max(previous.length, next.length);
1323
+ for (let index = 0; index < length; index++) {
1324
+ const before = previous[index];
1325
+ const after = next[index];
1326
+ if (before?.columnId !== after?.columnId || before?.direction !== after?.direction) {
1327
+ return (after ?? before)?.columnId;
1328
+ }
774
1329
  }
775
- const rest = levels.slice(1);
776
- const groups = partitionByValue(rows, column, level.direction ?? "asc");
777
- for (const group of groups) {
778
- const path = [...parentPath, group.value];
779
- const groupId = groupRowId(path);
780
- const expanded = !expansion.includes(groupId);
781
- const header = {
782
- kind: "group",
783
- groupId,
784
- level: parentPath.length,
785
- columnId: level.columnId,
786
- value: group.value,
787
- path,
788
- expanded,
789
- count: group.rows.length,
790
- // Renumbered globally once `groupRows` has the whole flattened array —
791
- // a header has no row of its own to carry a dataset position, so
792
- // `datasetIndex` takes the same renumbered value `rowIndex` does.
793
- rowIndex: -1,
794
- datasetIndex: -1,
795
- // Set by `withGroupAggregates`, downstream of grouping — empty here
796
- // since `groupRows` itself never knows about `AggregateState`.
797
- aggregates: /* @__PURE__ */ new Map()
798
- };
799
- out.push(header);
800
- if (expanded) {
801
- buildLevel(group.rows, rest, path, expansion, byId, out);
1330
+ return void 0;
1331
+ }
1332
+ function changedId(previous, next) {
1333
+ const length = Math.max(previous.length, next.length);
1334
+ for (let index = 0; index < length; index++) {
1335
+ if (previous[index] !== next[index]) {
1336
+ return next[index] ?? previous[index];
802
1337
  }
803
1338
  }
1339
+ return void 0;
804
1340
  }
805
- function groupRows(rows, groupBy, expansion, columns) {
806
- if (groupBy.length === 0) {
807
- return rows;
1341
+ function changedKeys(previous, next) {
1342
+ const keys = /* @__PURE__ */ new Set([...Object.keys(next), ...Object.keys(previous)]);
1343
+ return [...keys].filter((key) => previous[key] !== next[key]);
1344
+ }
1345
+ function sameItems(previous, next) {
1346
+ return previous.length === next.length && previous.every((item, index) => item === next[index]);
1347
+ }
1348
+ function samePagination(previous, next) {
1349
+ return previous.pageIndex === next.pageIndex && previous.pageSize === next.pageSize;
1350
+ }
1351
+ function sameValue(previous, next) {
1352
+ return previous instanceof Date && next instanceof Date ? previous.getTime() === next.getTime() : previous === next;
1353
+ }
1354
+ function sameOperand(previous, next) {
1355
+ if (isScalarList(previous) || isScalarList(next)) {
1356
+ return isScalarList(previous) && isScalarList(next) && previous.length === next.length && previous.every((scalar, index) => sameValue(scalar, next[index]));
808
1357
  }
809
- const byId = new Map(columns.map((entry) => [entry.id, entry.column]));
810
- const active = groupBy.filter((entry) => byId.has(entry.columnId));
811
- if (active.length === 0) {
812
- return rows;
1358
+ return sameValue(previous, next);
1359
+ }
1360
+ function sameEntry(previous, next) {
1361
+ if ("combinator" in previous || "combinator" in next) {
1362
+ return "combinator" in previous && "combinator" in next && previous.combinator === next.combinator && sameFilter(previous.entries, next.entries);
813
1363
  }
814
- const out = [];
815
- buildLevel(rows, active, [], expansion, byId, out);
816
- return out.map((entry, index) => {
817
- if ("kind" in entry) {
818
- return entry.rowIndex === index && entry.datasetIndex === index ? entry : { ...entry, rowIndex: index, datasetIndex: index };
819
- }
820
- return entry.rowIndex === index ? entry : { ...entry, rowIndex: index };
1364
+ if (previous.columnId !== next.columnId) {
1365
+ return false;
1366
+ }
1367
+ if ("predicate" in previous || "predicate" in next) {
1368
+ return "predicate" in previous && "predicate" in next && previous.predicate === next.predicate;
1369
+ }
1370
+ if ("operator" in previous || "operator" in next) {
1371
+ return "operator" in previous && "operator" in next && previous.operator === next.operator && sameOperand(previous.value, next.value);
1372
+ }
1373
+ if ("value" in previous || "value" in next) {
1374
+ return "value" in previous && "value" in next && sameValue(previous.value, next.value);
1375
+ }
1376
+ return previous.query === next.query;
1377
+ }
1378
+ function sameFilter(previous, next) {
1379
+ return previous.length === next.length && previous.every((entry, index) => {
1380
+ const other = next[index];
1381
+ return other !== void 0 && sameEntry(entry, other);
821
1382
  });
822
1383
  }
823
- function toggleGroupExpansion(expansion, groupId) {
824
- return expansion.includes(groupId) ? expansion.filter((id) => id !== groupId) : [...expansion, groupId];
1384
+
1385
+ // src/util/selection.ts
1386
+ var NO_CHANGE = { added: [], removed: [] };
1387
+ function sameSelection(a, b) {
1388
+ return a.length === b.length && a.every((id, index) => id === b[index]);
825
1389
  }
826
- function expandAllGroups(expansion) {
827
- return expansion.length === 0 ? expansion : [];
1390
+ function toggleSelection(selection, id, mode) {
1391
+ if (mode === false) {
1392
+ return selection;
1393
+ }
1394
+ if (selection.includes(id)) {
1395
+ return selection.filter((selectedId) => selectedId !== id);
1396
+ }
1397
+ return mode === "single" ? [id] : [...selection, id];
828
1398
  }
829
- function collapseAllGroups(rows) {
830
- return rows.filter((entry) => "kind" in entry).map((entry) => entry.groupId);
1399
+ function selectOnly(selection, id, mode) {
1400
+ if (mode === false) {
1401
+ return selection;
1402
+ }
1403
+ return selection.length === 1 && selection[0] === id ? selection : [id];
831
1404
  }
832
- function moveGroupByBefore(groupBy, columnId, beforeColumnId) {
833
- if (columnId === beforeColumnId) {
834
- return groupBy;
1405
+ function selectRange(selection, orderedIds, anchorId, focusId, mode) {
1406
+ if (mode !== "multiple") {
1407
+ return selectOnly(selection, focusId, mode);
835
1408
  }
836
- const without = groupBy.filter((entry) => entry.columnId !== columnId);
837
- const moved = groupBy.find((entry) => entry.columnId === columnId) ?? {
838
- columnId
1409
+ const anchor = orderedIds.indexOf(anchorId);
1410
+ const focus = orderedIds.indexOf(focusId);
1411
+ if (anchor === -1 || focus === -1) {
1412
+ return selection;
1413
+ }
1414
+ const next = orderedIds.slice(
1415
+ Math.min(anchor, focus),
1416
+ Math.max(anchor, focus) + 1
1417
+ );
1418
+ return sameSelection(selection, next) ? selection : next;
1419
+ }
1420
+ function selectAll(selection, orderedIds, mode) {
1421
+ if (mode !== "multiple") {
1422
+ return selection;
1423
+ }
1424
+ return sameSelection(selection, orderedIds) ? selection : orderedIds;
1425
+ }
1426
+ function clearSelection(selection) {
1427
+ return selection.length === 0 ? selection : [];
1428
+ }
1429
+ function diffSelection(previous, next) {
1430
+ if (previous === next) {
1431
+ return NO_CHANGE;
1432
+ }
1433
+ const before = new Set(previous);
1434
+ const after = new Set(next);
1435
+ return {
1436
+ added: next.filter((id) => !before.has(id)),
1437
+ removed: previous.filter((id) => !after.has(id))
839
1438
  };
840
- const target = beforeColumnId === null ? without.length : without.findIndex((entry) => entry.columnId === beforeColumnId);
841
- if (target === -1) {
842
- return groupBy;
1439
+ }
1440
+ function isSameCell(a, b) {
1441
+ if (a === null || b === null) {
1442
+ return a === b;
843
1443
  }
844
- const next = [...without];
845
- next.splice(target, 0, moved);
846
- return next.length === groupBy.length && next.every((entry, index) => entry === groupBy[index]) ? groupBy : next;
1444
+ return a.rowId === b.rowId && a.columnId === b.columnId;
847
1445
  }
848
- function movesGroupBy(groupBy, columnId, beforeColumnId) {
849
- return moveGroupByBefore(groupBy, columnId, beforeColumnId) !== groupBy;
1446
+ function selectCell(selection, cell, mode) {
1447
+ if (mode === false || isSameCell(selection, cell)) {
1448
+ return selection;
1449
+ }
1450
+ return cell;
1451
+ }
1452
+ function toggleCellSelection(selection, cell, mode) {
1453
+ if (mode === false) {
1454
+ return selection;
1455
+ }
1456
+ return isSameCell(selection, cell) ? null : cell;
1457
+ }
1458
+ function intentOf(event, options) {
1459
+ if (event.shiftKey) {
1460
+ return "range";
1461
+ }
1462
+ if (event.ctrlKey || event.metaKey) {
1463
+ return "toggle";
1464
+ }
1465
+ return options?.toggleOnClick === true ? "toggle" : "replace";
1466
+ }
1467
+ function applySelectionIntent(selection, orderedIds, anchorId, id, intent, mode) {
1468
+ switch (intent) {
1469
+ case "toggle":
1470
+ return toggleSelection(selection, id, mode);
1471
+ // A range with no anchor yet spans from the id to itself, which is the
1472
+ // plain click a first Shift-click may as well be.
1473
+ case "range":
1474
+ return selectRange(selection, orderedIds, anchorId ?? id, id, mode);
1475
+ default:
1476
+ return selectOnly(selection, id, mode);
1477
+ }
850
1478
  }
851
1479
 
852
- // src/util/rows.ts
853
- function resolveShownRows(rows, filter, sort, columns) {
854
- return sortRows(filterRows(rows, filter, columns), sort, columns);
1480
+ // src/controller/selectionEvents.ts
1481
+ function reportRowSelection(previous, next, rowsById, callbacks) {
1482
+ const diff = diffSelection(previous, next);
1483
+ const added = resolveRows(rowsById, diff.added);
1484
+ const removed = resolveRows(rowsById, diff.removed);
1485
+ for (const row of added) {
1486
+ callbacks.onRowSelect?.({ row, selection: next });
1487
+ }
1488
+ if (added.length > 0) {
1489
+ callbacks.onRowsSelect?.({ rows: added, selection: next });
1490
+ }
1491
+ for (const row of removed) {
1492
+ callbacks.onRowDeselect?.({ row, selection: next });
1493
+ }
1494
+ if (removed.length > 0) {
1495
+ callbacks.onRowsDeselect?.({ rows: removed, selection: next });
1496
+ }
1497
+ callbacks.onRowSelectionChange?.({
1498
+ added,
1499
+ removed,
1500
+ selected: resolveRows(rowsById, next),
1501
+ selection: next
1502
+ });
1503
+ return diff;
855
1504
  }
856
- function resolveRows(rowsById, ids) {
857
- return ids.map((id) => rowsById.get(id)).filter((row) => row !== void 0);
1505
+ function reportColumnSelection(previous, next, columns, callbacks) {
1506
+ const diff = diffSelection(previous, next);
1507
+ const added = resolveColumns(columns, diff.added);
1508
+ const removed = resolveColumns(columns, diff.removed);
1509
+ for (const column of added) {
1510
+ callbacks.onColumnSelect?.({ column, selection: next });
1511
+ }
1512
+ if (added.length > 0) {
1513
+ callbacks.onColumnsSelect?.({ columns: added, selection: next });
1514
+ }
1515
+ for (const column of removed) {
1516
+ callbacks.onColumnDeselect?.({ column, selection: next });
1517
+ }
1518
+ if (removed.length > 0) {
1519
+ callbacks.onColumnsDeselect?.({ columns: removed, selection: next });
1520
+ }
1521
+ callbacks.onColumnSelectionChange?.({
1522
+ added,
1523
+ removed,
1524
+ selected: resolveColumns(columns, next),
1525
+ selection: next
1526
+ });
1527
+ return diff;
858
1528
  }
859
- function resolveColumns(columns, ids) {
860
- return ids.map((id) => {
861
- const columnIndex = columns.findIndex((entry) => entry.id === id);
862
- const column = columns[columnIndex];
863
- return column === void 0 ? void 0 : { columnId: id, column, columnIndex };
864
- }).filter((column) => column !== void 0);
1529
+ function reportCellSelection(previous, next, rowsById, columns, callbacks) {
1530
+ const selected = resolveCell(rowsById, columns, next);
1531
+ const deselected = resolveCell(rowsById, columns, previous);
1532
+ if (deselected !== null) {
1533
+ callbacks.onCellDeselect?.({ cell: deselected, selection: next });
1534
+ }
1535
+ if (selected !== null) {
1536
+ callbacks.onCellSelect?.({ cell: selected, selection: next });
1537
+ }
1538
+ callbacks.onCellSelectionChange?.({
1539
+ selected,
1540
+ deselected,
1541
+ selection: next
1542
+ });
865
1543
  }
866
- function resolveCell(rowsById, columns, cell) {
867
- if (cell === null) {
868
- return null;
1544
+
1545
+ // src/controller/selectionActions.ts
1546
+ function indexRows(rows) {
1547
+ return new Map(rows.map((entry) => [entry.rowId, entry]));
1548
+ }
1549
+ function distinct(ids) {
1550
+ return [...new Set(ids)];
1551
+ }
1552
+ function createSelectionActions(grid) {
1553
+ let rowAnchor = null;
1554
+ let columnAnchor = null;
1555
+ const rowsById = memoizeLast(indexRows);
1556
+ function modes() {
1557
+ const selectable = grid.getOptions().selectable;
1558
+ return {
1559
+ rows: selectable?.rows ?? false,
1560
+ columns: selectable?.columns ?? false,
1561
+ cells: selectable?.cells ?? false
1562
+ };
869
1563
  }
870
- const entry = rowsById.get(cell.rowId);
871
- const columnIndex = columns.findIndex(
872
- (candidate) => candidate.id === cell.columnId
873
- );
874
- const column = columns[columnIndex];
875
- if (entry === void 0 || column === void 0) {
876
- return null;
1564
+ function rowIds() {
1565
+ return grid.getView().displayDataRows.map((entry) => entry.rowId);
1566
+ }
1567
+ function columnIds() {
1568
+ return grid.getView().columns.map((entry) => entry.id);
1569
+ }
1570
+ function commitRows(next) {
1571
+ const previous = grid.getState().rowSelection;
1572
+ grid.commit("rowSelection", next, () => {
1573
+ const options = grid.getOptions();
1574
+ const diff = reportRowSelection(
1575
+ previous,
1576
+ next,
1577
+ rowsById(grid.getView().displayDataRows),
1578
+ options
1579
+ );
1580
+ options.onSelectionDiff?.({ target: "rows", ...diff, selection: next });
1581
+ });
1582
+ }
1583
+ function commitColumns(next) {
1584
+ const previous = grid.getState().columnSelection;
1585
+ grid.commit("columnSelection", next, () => {
1586
+ const options = grid.getOptions();
1587
+ const diff = reportColumnSelection(
1588
+ previous,
1589
+ next,
1590
+ grid.getView().columns,
1591
+ options
1592
+ );
1593
+ options.onSelectionDiff?.({
1594
+ target: "columns",
1595
+ ...diff,
1596
+ selection: next
1597
+ });
1598
+ });
1599
+ }
1600
+ function commitCell(next) {
1601
+ const previous = grid.getState().cellSelection;
1602
+ grid.commit("cellSelection", next, () => {
1603
+ const view = grid.getView();
1604
+ reportCellSelection(
1605
+ previous,
1606
+ next,
1607
+ rowsById(view.displayDataRows),
1608
+ view.columns,
1609
+ grid.getOptions()
1610
+ );
1611
+ });
877
1612
  }
878
1613
  return {
879
- rowId: cell.rowId,
880
- columnId: cell.columnId,
881
- row: entry.row,
882
- column,
883
- rowIndex: entry.rowIndex,
884
- columnIndex,
885
- value: accessDotted(entry.row, column.column.field)
1614
+ setRowSelection(selection) {
1615
+ if (!sameItems(grid.getState().rowSelection, selection)) {
1616
+ commitRows(selection);
1617
+ }
1618
+ },
1619
+ selectRows(ids, intent = "replace") {
1620
+ const mode = modes().rows;
1621
+ const current = grid.getState().rowSelection;
1622
+ if (mode === false || ids.length === 0) {
1623
+ return;
1624
+ }
1625
+ const next = intent === "toggle" ? ids.reduce(
1626
+ (selection, id) => toggleSelection(selection, id, mode),
1627
+ current
1628
+ ) : mode === "single" ? ids.slice(-1) : distinct(ids);
1629
+ rowAnchor = ids.at(-1) ?? null;
1630
+ if (!sameItems(current, next)) {
1631
+ commitRows(next);
1632
+ }
1633
+ },
1634
+ selectRow(rowId, intent) {
1635
+ const next = applySelectionIntent(
1636
+ grid.getState().rowSelection,
1637
+ rowIds(),
1638
+ rowAnchor,
1639
+ rowId,
1640
+ intent,
1641
+ modes().rows
1642
+ );
1643
+ if (intent !== "range") {
1644
+ rowAnchor = rowId;
1645
+ }
1646
+ commitRows(next);
1647
+ },
1648
+ selectAllRows() {
1649
+ commitRows(
1650
+ selectAll(grid.getState().rowSelection, rowIds(), modes().rows)
1651
+ );
1652
+ },
1653
+ clearSelection() {
1654
+ commitRows(clearSelection(grid.getState().rowSelection));
1655
+ commitColumns(clearSelection(grid.getState().columnSelection));
1656
+ commitCell(null);
1657
+ },
1658
+ setColumnSelection(selection) {
1659
+ if (!sameItems(grid.getState().columnSelection, selection)) {
1660
+ commitColumns(selection);
1661
+ }
1662
+ },
1663
+ selectColumn(columnId, intent) {
1664
+ const next = applySelectionIntent(
1665
+ grid.getState().columnSelection,
1666
+ columnIds(),
1667
+ columnAnchor,
1668
+ columnId,
1669
+ intent,
1670
+ modes().columns
1671
+ );
1672
+ if (intent !== "range") {
1673
+ columnAnchor = columnId;
1674
+ }
1675
+ commitColumns(next);
1676
+ },
1677
+ setCellSelection(selection) {
1678
+ if (!isSameCell(grid.getState().cellSelection, selection)) {
1679
+ commitCell(selection);
1680
+ }
1681
+ },
1682
+ selectCell(cell, intent) {
1683
+ const current = grid.getState().cellSelection;
1684
+ const mode = modes().cells;
1685
+ commitCell(
1686
+ intent === "toggle" ? toggleCellSelection(current, cell, mode) : selectCell(current, cell, mode)
1687
+ );
1688
+ }
886
1689
  };
887
1690
  }
888
1691
 
889
- // src/util/paging.ts
890
- function unitsOf(rows) {
891
- const units = [];
892
- let start = 0;
893
- let openedByGroup = false;
894
- for (let index = 0; index < rows.length; index++) {
895
- const entry = rows[index];
896
- if (entry === void 0) {
897
- continue;
1692
+ // src/controller/createGridController.ts
1693
+ var DEFAULT_PAGE_SIZE = 25;
1694
+ var NO_ROWS = [];
1695
+ var NO_COLUMNS = [];
1696
+ var SLICE_KEYS = [
1697
+ "columnSizing",
1698
+ "columnOrder",
1699
+ "columnVisibility",
1700
+ "columnSort",
1701
+ "filter",
1702
+ "groupBy",
1703
+ "groupExpansion",
1704
+ "pagination",
1705
+ "rowSelection",
1706
+ "columnSelection",
1707
+ "cellSelection"
1708
+ ];
1709
+ var DEFAULT_OPTION = {
1710
+ columnSizing: "defaultColumnSizing",
1711
+ columnOrder: "defaultColumnOrder",
1712
+ columnVisibility: "defaultColumnVisibility",
1713
+ columnSort: "defaultColumnSort",
1714
+ filter: "defaultFilter",
1715
+ groupBy: "defaultGroupBy",
1716
+ groupExpansion: "defaultGroupExpansion",
1717
+ pagination: "defaultPagination",
1718
+ rowSelection: "defaultRowSelection",
1719
+ columnSelection: "defaultColumnSelection",
1720
+ cellSelection: "defaultCellSelection"
1721
+ };
1722
+ function startingState(options) {
1723
+ return {
1724
+ columnSizing: options.defaultColumnSizing ?? {},
1725
+ columnOrder: options.defaultColumnOrder ?? [],
1726
+ columnVisibility: options.defaultColumnVisibility ?? {},
1727
+ columnSort: options.defaultColumnSort ?? [],
1728
+ filter: options.defaultFilter ?? [],
1729
+ groupBy: options.defaultGroupBy ?? [],
1730
+ groupExpansion: options.defaultGroupExpansion ?? [],
1731
+ pagination: options.defaultPagination ?? {
1732
+ pageIndex: 0,
1733
+ pageSize: DEFAULT_PAGE_SIZE
1734
+ },
1735
+ rowSelection: options.defaultRowSelection ?? [],
1736
+ columnSelection: options.defaultColumnSelection ?? [],
1737
+ cellSelection: options.defaultCellSelection ?? null
1738
+ };
1739
+ }
1740
+ function definedColumns(columns, rows) {
1741
+ if (columns !== void 0 && columns.length > 0) {
1742
+ return columns;
1743
+ }
1744
+ if (rows !== void 0 && rows.length > 0) {
1745
+ return defineColumnsFromRows(rows);
1746
+ }
1747
+ return NO_COLUMNS;
1748
+ }
1749
+ function pagingInputsOf(state) {
1750
+ return {
1751
+ filter: state.filter,
1752
+ columnSort: state.columnSort,
1753
+ groupBy: state.groupBy
1754
+ };
1755
+ }
1756
+ function createGridController(initial) {
1757
+ let options = initial;
1758
+ const own = startingState(initial);
1759
+ const resolveView = createGridViewResolver();
1760
+ const resolveColumns2 = memoizeLast(definedColumns);
1761
+ const listeners = /* @__PURE__ */ new Set();
1762
+ const reportedConflicts = /* @__PURE__ */ new Set();
1763
+ let heldReports;
1764
+ let state = resolveSlices();
1765
+ let pagingInputs = pagingInputsOf(state);
1766
+ reportConflicts();
1767
+ function controlledValue(key) {
1768
+ const controlled = options;
1769
+ return controlled[key];
1770
+ }
1771
+ function isControlled(key) {
1772
+ return controlledValue(key) !== void 0;
1773
+ }
1774
+ function resolveSlice(key) {
1775
+ const controlled = controlledValue(key);
1776
+ return controlled === void 0 ? own[key] : controlled;
1777
+ }
1778
+ function resolveSlices() {
1779
+ return {
1780
+ columnSizing: resolveSlice("columnSizing"),
1781
+ columnOrder: resolveSlice("columnOrder"),
1782
+ columnVisibility: resolveSlice("columnVisibility"),
1783
+ columnSort: resolveSlice("columnSort"),
1784
+ filter: resolveSlice("filter"),
1785
+ groupBy: resolveSlice("groupBy"),
1786
+ groupExpansion: resolveSlice("groupExpansion"),
1787
+ pagination: resolveSlice("pagination"),
1788
+ rowSelection: resolveSlice("rowSelection"),
1789
+ columnSelection: resolveSlice("columnSelection"),
1790
+ cellSelection: resolveSlice("cellSelection")
1791
+ };
1792
+ }
1793
+ function reportConflicts() {
1794
+ for (const key of SLICE_KEYS) {
1795
+ const defaultKey = DEFAULT_OPTION[key];
1796
+ if (reportedConflicts.has(key) || !isControlled(key) || options[defaultKey] === void 0) {
1797
+ continue;
1798
+ }
1799
+ reportedConflicts.add(key);
1800
+ reportError(
1801
+ `\`${key}\` and \`${defaultKey}\` are both set \u2014 \`${key}\` is controlled, so \`${defaultKey}\` is ignored.`
1802
+ );
898
1803
  }
899
- const isTopLevelHeader = "kind" in entry && entry.kind === "group" && entry.level === 0;
900
- const isBoundary = index !== start && (isTopLevelHeader || !openedByGroup);
901
- if (isBoundary) {
902
- units.push({ start, end: index });
903
- start = index;
904
- openedByGroup = isTopLevelHeader;
905
- } else if (index === start) {
906
- openedByGroup = isTopLevelHeader;
1804
+ }
1805
+ function refresh() {
1806
+ let next = resolveSlices();
1807
+ if (next.filter !== pagingInputs.filter || next.columnSort !== pagingInputs.columnSort || next.groupBy !== pagingInputs.groupBy) {
1808
+ pagingInputs = pagingInputsOf(next);
1809
+ if (!isControlled("pagination") && own.pagination.pageIndex !== 0) {
1810
+ own.pagination = { ...own.pagination, pageIndex: 0 };
1811
+ next = { ...next, pagination: own.pagination };
1812
+ }
907
1813
  }
1814
+ state = SLICE_KEYS.every((key) => next[key] === state[key]) ? state : next;
908
1815
  }
909
- if (start < rows.length) {
910
- units.push({ start, end: rows.length });
1816
+ function notify() {
1817
+ for (const listener of [...listeners]) {
1818
+ listener();
1819
+ }
911
1820
  }
912
- return units;
1821
+ function commit(key, next, report) {
1822
+ if (next === state[key]) {
1823
+ return;
1824
+ }
1825
+ if (!isControlled(key)) {
1826
+ own[key] = next;
1827
+ }
1828
+ if (heldReports !== void 0) {
1829
+ heldReports.push(report);
1830
+ return;
1831
+ }
1832
+ refresh();
1833
+ report();
1834
+ notify();
1835
+ }
1836
+ function getView() {
1837
+ return resolveView({
1838
+ rows: options.dataSource ?? NO_ROWS,
1839
+ getRowId: options.getRowId,
1840
+ columns: resolveColumns2(options.columns, options.dataSource),
1841
+ state,
1842
+ options: {
1843
+ paginated: options.paginated,
1844
+ aggregates: options.aggregates,
1845
+ groupAggregateDisplay: options.groupAggregateDisplay,
1846
+ columnSizeDefaults: options.columnSizeDefaults,
1847
+ resizableColumns: options.resizableColumns,
1848
+ reorderableColumns: options.reorderableColumns,
1849
+ groupByDraggableColumns: options.groupByDraggableColumns,
1850
+ resizeMode: options.resizeMode,
1851
+ viewportWidth: options.viewportWidth
1852
+ }
1853
+ });
1854
+ }
1855
+ function commitColumnSizing(next, columnId, width, phase) {
1856
+ commit("columnSizing", next, () => {
1857
+ options.onColumnResize?.({ columnId, width, sizing: next, phase });
1858
+ });
1859
+ }
1860
+ function definedWidth(columnId) {
1861
+ const entry = getView().dataColumns.find(
1862
+ (candidate) => candidate.id === columnId
1863
+ );
1864
+ const [resolved] = entry === void 0 ? [] : resolveColumnWidths(
1865
+ [entry.column],
1866
+ {},
1867
+ {
1868
+ sizes: options.columnSizeDefaults
1869
+ }
1870
+ );
1871
+ return resolved?.width ?? 0;
1872
+ }
1873
+ function commitColumnVisibility(next) {
1874
+ const previous = state.columnVisibility;
1875
+ commit("columnVisibility", next, () => {
1876
+ const changed = new Set(changedKeys(previous, next));
1877
+ const shown = getView().dataColumns.map((entry) => entry.id).filter((id) => changed.delete(id));
1878
+ options.onColumnVisibilityChange?.({
1879
+ columnIds: [...shown, ...changed],
1880
+ visibility: next
1881
+ });
1882
+ });
1883
+ }
1884
+ function changeColumnVisibility(columnIds, visible) {
1885
+ commitColumnVisibility(
1886
+ setColumnVisibility(
1887
+ getView().dataColumns,
1888
+ state.columnVisibility,
1889
+ columnIds,
1890
+ visible
1891
+ )
1892
+ );
1893
+ }
1894
+ function commitGroupBy(next, columnId) {
1895
+ commit("groupBy", next, () => {
1896
+ options.onGroupByChange?.({ columnId, groupBy: next });
1897
+ });
1898
+ }
1899
+ function commitGroupExpansion(next, groupId) {
1900
+ commit("groupExpansion", next, () => {
1901
+ options.onGroupExpansionChange?.({ groupId, expansion: next });
1902
+ });
1903
+ }
1904
+ function commitPagination(next) {
1905
+ commit("pagination", next, () => {
1906
+ options.onPaginationChange?.({
1907
+ pagination: next,
1908
+ pageCount: paginateRows(getView().displayRows, next).pageCount
1909
+ });
1910
+ });
1911
+ }
1912
+ function goToPage(pageIndex) {
1913
+ const current = state.pagination;
1914
+ const clamped = paginateRows(getView().displayRows, {
1915
+ ...current,
1916
+ pageIndex
1917
+ }).pageIndex;
1918
+ commitPagination({ ...current, pageIndex: clamped });
1919
+ }
1920
+ function shownPageIndex() {
1921
+ return paginateRows(getView().displayRows, state.pagination).pageIndex;
1922
+ }
1923
+ const selection = createSelectionActions({
1924
+ getState: () => state,
1925
+ getOptions: () => options,
1926
+ getView,
1927
+ commit
1928
+ });
1929
+ const controller = {
1930
+ ...selection,
1931
+ getOptions: () => options,
1932
+ setOptions(next) {
1933
+ options = next;
1934
+ reportConflicts();
1935
+ refresh();
1936
+ },
1937
+ getState: () => state,
1938
+ getView,
1939
+ restoreState(saved) {
1940
+ const reports = [];
1941
+ heldReports = reports;
1942
+ try {
1943
+ restoreSlices(saved);
1944
+ } finally {
1945
+ heldReports = void 0;
1946
+ }
1947
+ if (saved.pagination !== void 0) {
1948
+ pagingInputs = pagingInputsOf(resolveSlices());
1949
+ }
1950
+ refresh();
1951
+ for (const report of reports) {
1952
+ report();
1953
+ }
1954
+ if (reports.length > 0) {
1955
+ notify();
1956
+ }
1957
+ },
1958
+ subscribe(listener) {
1959
+ listeners.add(listener);
1960
+ return () => {
1961
+ listeners.delete(listener);
1962
+ };
1963
+ },
1964
+ setColumnSizing(sizing) {
1965
+ const [columnId] = changedKeys(state.columnSizing, sizing);
1966
+ if (columnId !== void 0) {
1967
+ commitColumnSizing(
1968
+ sizing,
1969
+ columnId,
1970
+ sizing[columnId] ?? definedWidth(columnId),
1971
+ "end"
1972
+ );
1973
+ }
1974
+ },
1975
+ resizeColumn(columnId, width, phase) {
1976
+ commitColumnSizing(
1977
+ { ...state.columnSizing, [columnId]: width },
1978
+ columnId,
1979
+ width,
1980
+ phase
1981
+ );
1982
+ },
1983
+ cancelColumnResize(columnId, startSizing, startWidth) {
1984
+ commitColumnSizing(
1985
+ revertColumnSize(state.columnSizing, startSizing, columnId),
1986
+ columnId,
1987
+ startWidth,
1988
+ "end"
1989
+ );
1990
+ },
1991
+ setColumnOrder(order) {
1992
+ const columnId = changedId(state.columnOrder, order);
1993
+ if (columnId === void 0) {
1994
+ return;
1995
+ }
1996
+ commit("columnOrder", order, () => {
1997
+ options.onColumnOrderChange?.({ columnId, order });
1998
+ });
1999
+ },
2000
+ /**
2001
+ * Applied to every id in display order rather than to the stored order,
2002
+ * which starts empty and may name only some columns, so would have
2003
+ * nothing to rearrange.
2004
+ */
2005
+ moveColumn(columnId, beforeId) {
2006
+ const orderedIds = getView().dataColumns.map((entry) => entry.id);
2007
+ const next = moveColumnBefore(orderedIds, columnId, beforeId);
2008
+ if (next === orderedIds) {
2009
+ return;
2010
+ }
2011
+ commit("columnOrder", next, () => {
2012
+ options.onColumnOrderChange?.({ columnId, order: next });
2013
+ });
2014
+ },
2015
+ setColumnVisibility(visibility) {
2016
+ if (changedKeys(state.columnVisibility, visibility).length > 0) {
2017
+ commitColumnVisibility(visibility);
2018
+ }
2019
+ },
2020
+ showColumns(columnIds) {
2021
+ changeColumnVisibility(columnIds, true);
2022
+ },
2023
+ hideColumns(columnIds) {
2024
+ changeColumnVisibility(columnIds, false);
2025
+ },
2026
+ setFilter(filter) {
2027
+ if (sameFilter(state.filter, filter)) {
2028
+ return;
2029
+ }
2030
+ commit("filter", filter, () => {
2031
+ options.onFilterChange?.({ filter });
2032
+ });
2033
+ },
2034
+ setColumnSort(sort) {
2035
+ const columnId = changedEntryColumnId(state.columnSort, sort);
2036
+ if (columnId === void 0) {
2037
+ return;
2038
+ }
2039
+ commit("columnSort", sort, () => {
2040
+ options.onColumnSortChange?.({ columnId, sort });
2041
+ });
2042
+ },
2043
+ toggleColumnSort(columnId, { stack = false } = {}) {
2044
+ const next = toggleColumnSort(state.columnSort, columnId, { stack });
2045
+ commit("columnSort", next, () => {
2046
+ options.onColumnSortChange?.({ columnId, sort: next });
2047
+ });
2048
+ },
2049
+ setGroupBy(groupBy) {
2050
+ const columnId = changedEntryColumnId(state.groupBy, groupBy);
2051
+ if (columnId !== void 0) {
2052
+ commitGroupBy(groupBy, columnId);
2053
+ }
2054
+ },
2055
+ toggleGroupBy(columnId) {
2056
+ const grouped = state.groupBy.some(
2057
+ (entry) => entry.columnId === columnId
2058
+ );
2059
+ commitGroupBy(
2060
+ grouped ? state.groupBy.filter((entry) => entry.columnId !== columnId) : [...state.groupBy, { columnId }],
2061
+ columnId
2062
+ );
2063
+ },
2064
+ moveGroupBy(columnId, beforeColumnId) {
2065
+ commitGroupBy(
2066
+ moveGroupByBefore(state.groupBy, columnId, beforeColumnId),
2067
+ columnId
2068
+ );
2069
+ },
2070
+ setGroupExpansion(expansion) {
2071
+ if (!sameItems(state.groupExpansion, expansion)) {
2072
+ commitGroupExpansion(expansion, null);
2073
+ }
2074
+ },
2075
+ toggleGroupExpansion(groupId) {
2076
+ commitGroupExpansion(
2077
+ toggleGroupExpansion(state.groupExpansion, groupId),
2078
+ groupId
2079
+ );
2080
+ },
2081
+ expandAllGroups() {
2082
+ commitGroupExpansion(expandAllGroups(state.groupExpansion), null);
2083
+ },
2084
+ collapseAllGroups() {
2085
+ commitGroupExpansion(collapseAllGroups(getView().displayRows), null);
2086
+ },
2087
+ setPagination(pagination) {
2088
+ if (!samePagination(state.pagination, pagination)) {
2089
+ commitPagination(pagination);
2090
+ }
2091
+ },
2092
+ goToPage,
2093
+ nextPage() {
2094
+ goToPage(shownPageIndex() + 1);
2095
+ },
2096
+ previousPage() {
2097
+ goToPage(shownPageIndex() - 1);
2098
+ },
2099
+ setPageSize(pageSize) {
2100
+ const first = { pageIndex: 0, pageSize };
2101
+ commitPagination({
2102
+ ...first,
2103
+ pageIndex: paginateRows(getView().displayRows, first).pageIndex
2104
+ });
2105
+ }
2106
+ };
2107
+ function restoreSlices(saved) {
2108
+ if (saved.columnSizing !== void 0) {
2109
+ controller.setColumnSizing(saved.columnSizing);
2110
+ }
2111
+ if (saved.columnOrder !== void 0) {
2112
+ controller.setColumnOrder(saved.columnOrder);
2113
+ }
2114
+ if (saved.columnVisibility !== void 0) {
2115
+ controller.setColumnVisibility(saved.columnVisibility);
2116
+ }
2117
+ if (saved.columnSort !== void 0) {
2118
+ controller.setColumnSort(saved.columnSort);
2119
+ }
2120
+ if (saved.filter !== void 0) {
2121
+ controller.setFilter(reviveFilter(saved.filter, getView().dataColumns));
2122
+ }
2123
+ if (saved.groupBy !== void 0) {
2124
+ controller.setGroupBy(saved.groupBy);
2125
+ }
2126
+ if (saved.groupExpansion !== void 0) {
2127
+ controller.setGroupExpansion(saved.groupExpansion);
2128
+ }
2129
+ if (saved.pagination !== void 0) {
2130
+ controller.setPagination(saved.pagination);
2131
+ }
2132
+ if (saved.rowSelection !== void 0) {
2133
+ controller.setRowSelection(saved.rowSelection);
2134
+ }
2135
+ if (saved.columnSelection !== void 0) {
2136
+ controller.setColumnSelection(saved.columnSelection);
2137
+ }
2138
+ if (saved.cellSelection !== void 0) {
2139
+ controller.setCellSelection(saved.cellSelection);
2140
+ }
2141
+ }
2142
+ return controller;
913
2143
  }
914
- function paginateRows(rows, pagination) {
915
- const pageSize = Math.max(1, pagination.pageSize);
916
- const units = unitsOf(rows);
917
- const pageCount = Math.ceil(units.length / pageSize);
918
- if (pageCount === 0) {
919
- return { rows: [], pageCount: 0, pageIndex: 0 };
2144
+
2145
+ // src/util/navigation.ts
2146
+ var HEADER_ROW = -1;
2147
+ function clampFocus(focus, rowCount, columnCount) {
2148
+ return {
2149
+ rowIndex: Math.min(Math.max(focus.rowIndex, HEADER_ROW), rowCount - 1),
2150
+ columnIndex: Math.min(
2151
+ Math.max(focus.columnIndex, 0),
2152
+ Math.max(columnCount - 1, 0)
2153
+ )
2154
+ };
2155
+ }
2156
+ function noRowsSkippable() {
2157
+ return false;
2158
+ }
2159
+ function skipSkippableRows(rowIndex, direction, rowCount, isSkippableRow, fallback) {
2160
+ let next = rowIndex;
2161
+ while (next >= 0 && next < rowCount && isSkippableRow(next)) {
2162
+ const stepped = next + direction;
2163
+ if (stepped < HEADER_ROW || stepped >= rowCount) {
2164
+ return fallback;
2165
+ }
2166
+ next = stepped;
920
2167
  }
921
- const pageIndex = Math.min(Math.max(pagination.pageIndex, 0), pageCount - 1);
922
- const firstUnit = units[pageIndex * pageSize];
923
- const lastUnit = units[Math.min(pageIndex * pageSize + pageSize, units.length) - 1];
924
- if (firstUnit === void 0 || lastUnit === void 0) {
925
- return { rows: [], pageCount, pageIndex };
2168
+ return next;
2169
+ }
2170
+ function nextFocusForKey(key, modifiers, focus, rowCount, columnCount, pageSize, isSkippableRow = noRowsSkippable) {
2171
+ if (modifiers.altKey) {
2172
+ return null;
2173
+ }
2174
+ const { rowIndex, columnIndex } = focus;
2175
+ switch (key) {
2176
+ case "ArrowLeft":
2177
+ if (modifiers.ctrlKey) return null;
2178
+ return { rowIndex, columnIndex: columnIndex - 1 };
2179
+ case "ArrowRight":
2180
+ if (modifiers.ctrlKey) return null;
2181
+ return { rowIndex, columnIndex: columnIndex + 1 };
2182
+ case "ArrowUp":
2183
+ return {
2184
+ rowIndex: skipSkippableRows(
2185
+ rowIndex - 1,
2186
+ -1,
2187
+ rowCount,
2188
+ isSkippableRow,
2189
+ rowIndex
2190
+ ),
2191
+ columnIndex
2192
+ };
2193
+ case "ArrowDown":
2194
+ return {
2195
+ rowIndex: skipSkippableRows(
2196
+ rowIndex + 1,
2197
+ 1,
2198
+ rowCount,
2199
+ isSkippableRow,
2200
+ rowIndex
2201
+ ),
2202
+ columnIndex
2203
+ };
2204
+ // Ctrl takes Home and End to the grid's ends rather than the row's, which
2205
+ // is the one place Ctrl is navigation rather than reorder.
2206
+ case "Home":
2207
+ return modifiers.ctrlKey ? { rowIndex: HEADER_ROW, columnIndex: 0 } : { rowIndex, columnIndex: 0 };
2208
+ case "End":
2209
+ return modifiers.ctrlKey ? {
2210
+ rowIndex: skipSkippableRows(
2211
+ rowCount - 1,
2212
+ -1,
2213
+ rowCount,
2214
+ isSkippableRow,
2215
+ rowIndex
2216
+ ),
2217
+ columnIndex: columnCount - 1
2218
+ } : { rowIndex, columnIndex: columnCount - 1 };
2219
+ case "PageUp":
2220
+ return {
2221
+ rowIndex: skipSkippableRows(
2222
+ rowIndex - pageSize,
2223
+ -1,
2224
+ rowCount,
2225
+ isSkippableRow,
2226
+ rowIndex
2227
+ ),
2228
+ columnIndex
2229
+ };
2230
+ case "PageDown":
2231
+ return {
2232
+ rowIndex: skipSkippableRows(
2233
+ rowIndex + pageSize,
2234
+ 1,
2235
+ rowCount,
2236
+ isSkippableRow,
2237
+ rowIndex
2238
+ ),
2239
+ columnIndex
2240
+ };
2241
+ default:
2242
+ return null;
926
2243
  }
927
- const page = rows.slice(firstUnit.start, lastUnit.end);
928
- const renumbered = page.map(
929
- (entry, index) => entry.rowIndex === index ? entry : { ...entry, rowIndex: index }
930
- );
931
- return { rows: renumbered, pageCount, pageIndex };
932
2244
  }
933
2245
 
934
2246
  // src/util/paginationWindow.ts
@@ -966,180 +2278,426 @@ function paginationWindow(currentPage, pageCount, options) {
966
2278
  return entries;
967
2279
  }
968
2280
 
969
- // src/util/aggregation.ts
970
- function isEmpty2(value) {
971
- return value === null || value === void 0;
2281
+ // src/util/format.ts
2282
+ var DATE_FIELDS = {
2283
+ year: "numeric",
2284
+ month: "numeric",
2285
+ day: "numeric"
2286
+ };
2287
+ var TIME_FIELDS = {
2288
+ hour: "numeric",
2289
+ minute: "numeric",
2290
+ second: "numeric"
2291
+ };
2292
+ var DATE_TIME_FIELDS = {
2293
+ ...DATE_FIELDS,
2294
+ ...TIME_FIELDS
2295
+ };
2296
+ var CURRENCY_DIGITS = {
2297
+ minimumFractionDigits: 2,
2298
+ maximumFractionDigits: 2
2299
+ };
2300
+ var NO_FORMAT = {};
2301
+ var formatters = /* @__PURE__ */ new WeakMap();
2302
+ function cached(format, key, create) {
2303
+ const owner = format ?? NO_FORMAT;
2304
+ let byKey = formatters.get(owner);
2305
+ if (byKey === void 0) {
2306
+ byKey = /* @__PURE__ */ new Map();
2307
+ formatters.set(owner, byKey);
2308
+ }
2309
+ const existing = byKey.get(key);
2310
+ if (existing !== void 0) {
2311
+ return existing;
2312
+ }
2313
+ const formatter = create();
2314
+ byKey.set(key, formatter);
2315
+ return formatter;
2316
+ }
2317
+ function localeKey(locale) {
2318
+ return typeof locale === "string" ? locale : locale?.join(",") ?? "";
2319
+ }
2320
+ function isNumericType(type) {
2321
+ return type === "number" || type === "decimal" || type === "currency" || type === "percent";
2322
+ }
2323
+ function numberOptions(type, format) {
2324
+ switch (type) {
2325
+ case "currency":
2326
+ return format?.currency === void 0 ? { ...CURRENCY_DIGITS, ...format } : { style: "currency", ...format };
2327
+ case "percent":
2328
+ return { style: "percent", ...format };
2329
+ default:
2330
+ return format ?? {};
2331
+ }
972
2332
  }
973
- function toNumber(value) {
974
- return Number(value);
2333
+ function dateOptions(type, format) {
2334
+ if (format !== void 0) {
2335
+ return format;
2336
+ }
2337
+ if (type === "date") return DATE_FIELDS;
2338
+ if (type === "time") return TIME_FIELDS;
2339
+ return DATE_TIME_FIELDS;
975
2340
  }
976
- function distinctKey(value) {
977
- if (isEmpty2(value)) {
978
- return "\0empty";
2341
+ function formatNumber(value, type, format, locale) {
2342
+ const formatter = cached(
2343
+ format,
2344
+ `number|${type ?? ""}|${localeKey(locale)}`,
2345
+ () => new Intl.NumberFormat(
2346
+ locale,
2347
+ numberOptions(type, format)
2348
+ )
2349
+ );
2350
+ return formatter.format(value);
2351
+ }
2352
+ function formatDate(value, type, format, locale) {
2353
+ if (Number.isNaN(value.getTime())) {
2354
+ return String(value);
2355
+ }
2356
+ const formatter = cached(
2357
+ format,
2358
+ `date|${type ?? ""}|${localeKey(locale)}`,
2359
+ () => new Intl.DateTimeFormat(
2360
+ locale,
2361
+ dateOptions(type, format)
2362
+ )
2363
+ );
2364
+ return formatter.format(value);
2365
+ }
2366
+ function formatOther(value) {
2367
+ if (typeof value === "function") {
2368
+ return "(function)";
2369
+ }
2370
+ if (typeof value === "object" && value !== null) {
2371
+ try {
2372
+ return JSON.stringify(value);
2373
+ } catch {
2374
+ return Object.prototype.toString.call(value);
2375
+ }
979
2376
  }
2377
+ return String(value);
2378
+ }
2379
+ function formatCellValue(value, column, locale, row, messages) {
2380
+ if (column.valueFormatter !== void 0) {
2381
+ return column.valueFormatter(value, row);
2382
+ }
2383
+ if (value === null || value === void 0) {
2384
+ return "";
2385
+ }
2386
+ if (typeof value === "boolean" && messages !== void 0) {
2387
+ return value ? messages.booleanTrue : messages.booleanFalse;
2388
+ }
2389
+ const { type, format } = column;
2390
+ const formatted = format !== void 0 || locale !== void 0;
980
2391
  if (value instanceof Date) {
981
- return `\0date:${String(value.getTime())}`;
2392
+ return formatted ? formatDate(value, type, format, locale) : value.toLocaleString();
982
2393
  }
983
- switch (typeof value) {
984
- case "string":
985
- case "number":
986
- case "boolean":
987
- case "bigint":
988
- case "symbol":
989
- return `\0${typeof value}:${String(value)}`;
990
- case "function":
991
- return "\0function";
992
- default:
993
- return `\0object:${JSON.stringify(value)}`;
2394
+ if (formatted && (typeof value === "number" || typeof value === "bigint") && isNumericType(type)) {
2395
+ return formatNumber(value, type, format, locale);
2396
+ }
2397
+ if (typeof value === "string") {
2398
+ return value;
994
2399
  }
2400
+ return formatOther(value);
995
2401
  }
996
- function reduceBuiltIn(rows, fn, column) {
997
- if (fn === "count") {
998
- return rows.length;
2402
+ function aggregateFormatColumn(value, spec, column) {
2403
+ const plain = {
2404
+ field: spec.columnId,
2405
+ type: "number"
2406
+ };
2407
+ if (column === void 0 || spec.fn === "count" || spec.fn === "countDistinct") {
2408
+ return plain;
999
2409
  }
1000
- const field = column?.column.field;
1001
- const values = field === void 0 ? [] : rows.map((row) => accessDotted(row, field));
1002
- switch (fn) {
1003
- case "sum": {
1004
- let total = 0;
1005
- for (const value of values) {
1006
- if (isEmpty2(value)) continue;
1007
- total += toNumber(value);
1008
- }
1009
- return total;
1010
- }
1011
- case "avg": {
1012
- const present = values.filter((value) => !isEmpty2(value));
1013
- if (present.length === 0) return void 0;
1014
- const total = present.reduce(
1015
- (acc, value) => acc + toNumber(value),
1016
- 0
1017
- );
1018
- return total / present.length;
1019
- }
1020
- case "min":
1021
- case "max": {
1022
- const type = column?.column.type;
1023
- let result;
1024
- for (const value of values) {
1025
- if (isEmpty2(value)) continue;
1026
- if (result === void 0) {
1027
- result = value;
1028
- continue;
1029
- }
1030
- const comparison = compareValues(value, result, type);
1031
- if (fn === "min" ? comparison < 0 : comparison > 0) {
1032
- result = value;
1033
- }
1034
- }
1035
- return result;
1036
- }
1037
- case "countDistinct": {
1038
- const seen = /* @__PURE__ */ new Set();
1039
- for (const value of values) {
1040
- if (isEmpty2(value)) continue;
1041
- seen.add(distinctKey(value));
1042
- }
1043
- return seen.size;
1044
- }
2410
+ const numeric = typeof value === "number" || typeof value === "bigint";
2411
+ return column.valueFormatter === void 0 && numeric && !isNumericType(column.type) ? plain : column;
2412
+ }
2413
+ function formatAggregateValue(value, spec, column, locale) {
2414
+ const target = aggregateFormatColumn(value, spec, column);
2415
+ if (typeof value === "number" && target.valueFormatter === void 0 && target.format === void 0 && locale === void 0) {
2416
+ return value.toLocaleString();
1045
2417
  }
2418
+ return formatCellValue(value, target, locale);
1046
2419
  }
1047
- function keyOf(spec) {
1048
- return spec.id ?? spec.columnId;
2420
+
2421
+ // src/util/filterEditor.ts
2422
+ function touchesColumn(entry, columnId) {
2423
+ return "combinator" in entry ? entry.entries.some((nested) => touchesColumn(nested, columnId)) : entry.columnId === columnId;
1049
2424
  }
1050
- function computeAggregates(rows, specs, columns) {
1051
- const results = /* @__PURE__ */ new Map();
1052
- if (specs.length === 0) {
1053
- return results;
2425
+ function isOperatorOn(entry, columnId) {
2426
+ return "operator" in entry && entry.columnId === columnId;
2427
+ }
2428
+ function isConditionOn(entry, columnId) {
2429
+ return isOperatorOn(entry, columnId) && entry.operator !== "in";
2430
+ }
2431
+ function valuesShape(entry, columnId) {
2432
+ const values = (candidate) => candidate !== void 0 && isOperatorOn(candidate, columnId) && candidate.operator === "in" && isScalarList(candidate.value) ? candidate.value : void 0;
2433
+ const blanks = (candidate) => candidate !== void 0 && isOperatorOn(candidate, columnId) && candidate.operator === "isEmpty";
2434
+ const alone = values(entry);
2435
+ if (alone !== void 0) {
2436
+ return { kind: "values", values: alone, includeBlanks: false };
1054
2437
  }
1055
- const byId = new Map(columns.map((entry) => [entry.id, entry]));
1056
- for (const spec of specs) {
1057
- const value = typeof spec.fn === "function" ? spec.fn(rows) : reduceBuiltIn(rows, spec.fn, byId.get(spec.columnId));
1058
- results.set(keyOf(spec), value);
2438
+ if (!("combinator" in entry) || entry.combinator !== "or" || entry.entries.length !== 2) {
2439
+ return void 0;
1059
2440
  }
1060
- return results;
2441
+ const [first, second] = entry.entries;
2442
+ const listed = blanks(first) ? values(second) : values(first);
2443
+ return listed !== void 0 && (blanks(first) || blanks(second)) ? { kind: "values", values: listed, includeBlanks: true } : void 0;
1061
2444
  }
1062
- function leafDescendantsOf(flatRows, path, groupBy, byId) {
1063
- const keys = path.map((value) => bucketKey(value));
1064
- return flatRows.filter(
1065
- (row) => keys.every((key, level) => {
1066
- const columnId = groupBy[level]?.columnId;
1067
- const column = columnId === void 0 ? void 0 : byId.get(columnId);
1068
- if (column === void 0) return false;
1069
- return bucketKey(accessDotted(row.row, column.field)) === key;
1070
- })
1071
- ).map((row) => row.row);
2445
+ function conditionsShape(touching, columnId) {
2446
+ const [only] = touching;
2447
+ const grouped = touching.length === 1 && only !== void 0 && "combinator" in only;
2448
+ const entries = grouped ? only.entries : touching;
2449
+ const combinator = grouped ? only.combinator : "and";
2450
+ if (entries.length === 0 || entries.length > 2) {
2451
+ return void 0;
2452
+ }
2453
+ const conditions = entries.filter((entry) => isConditionOn(entry, columnId));
2454
+ return conditions.length === entries.length ? { kind: "conditions", conditions, combinator } : void 0;
1072
2455
  }
1073
- function appendAggregatedSpan(displayRows, start, levelFloor, flatRows, groupBy, byId, specs, columns, display, out) {
1074
- let index = start;
1075
- while (index < displayRows.length) {
1076
- const entry = displayRows[index];
1077
- if (entry === void 0) {
1078
- index++;
2456
+ function describeColumnFilter(filter, columnId) {
2457
+ const touching = filter.filter((entry) => touchesColumn(entry, columnId));
2458
+ const [only] = touching;
2459
+ if (only === void 0) {
2460
+ return { kind: "none" };
2461
+ }
2462
+ return (touching.length === 1 ? valuesShape(only, columnId) : void 0) ?? conditionsShape(touching, columnId) ?? { kind: "custom" };
2463
+ }
2464
+ function isOfferable(value) {
2465
+ return typeof value === "string" || typeof value === "number" || typeof value === "boolean" || value instanceof Date;
2466
+ }
2467
+ function matchesSearch(label, search) {
2468
+ return label.toLowerCase().includes(search.toLowerCase());
2469
+ }
2470
+ function columnValueOptions(rows, dataColumns, filter, columnId, query = {}) {
2471
+ const column = dataColumns.find((entry) => entry.id === columnId)?.column;
2472
+ if (column === void 0) {
2473
+ return { options: [], total: 0, truncated: false, blanks: 0 };
2474
+ }
2475
+ const keep = compileFilter(
2476
+ filter.filter((entry) => !touchesColumn(entry, columnId)),
2477
+ dataColumns
2478
+ );
2479
+ const buckets = /* @__PURE__ */ new Map();
2480
+ let blanks = 0;
2481
+ for (const row of rows) {
2482
+ if (keep !== void 0 && !keep(row)) {
1079
2483
  continue;
1080
2484
  }
1081
- if (levelFloor !== null && "kind" in entry && entry.kind === "group" && entry.level <= levelFloor) {
1082
- break;
1083
- }
1084
- index++;
1085
- if (!("kind" in entry) || entry.kind !== "group") {
1086
- out.push(entry);
1087
- continue;
2485
+ const value = accessDotted(row, column.field);
2486
+ if (isEmptyCell(value)) {
2487
+ blanks++;
2488
+ } else if (isOfferable(value)) {
2489
+ const key = bucketKey(value);
2490
+ const bucket = buckets.get(key);
2491
+ if (bucket === void 0) {
2492
+ buckets.set(key, { value, row, count: 1 });
2493
+ } else {
2494
+ bucket.count++;
2495
+ }
1088
2496
  }
1089
- const leaves = leafDescendantsOf(flatRows, entry.path, groupBy, byId);
1090
- const aggregates = computeAggregates(leaves, specs, columns);
1091
- const withAggregates = { ...entry, aggregates };
1092
- out.push(withAggregates);
1093
- index = appendAggregatedSpan(
1094
- displayRows,
1095
- index,
1096
- entry.level,
1097
- flatRows,
1098
- groupBy,
1099
- byId,
1100
- specs,
1101
- columns,
1102
- display,
1103
- out
1104
- );
1105
- if (display === "row") {
1106
- const summary = {
1107
- kind: "group-summary",
1108
- groupId: entry.groupId,
1109
- level: entry.level,
1110
- rowIndex: -1,
1111
- datasetIndex: -1,
1112
- aggregates
2497
+ }
2498
+ const { search = "", limit, locale } = query;
2499
+ const options = [...buckets.values()].sort((a, b) => compareValues(a.value, b.value, column.type)).map(({ value, row, count }) => ({
2500
+ value,
2501
+ label: formatCellValue(value, column, locale, row),
2502
+ count
2503
+ })).filter((option) => matchesSearch(option.label, search));
2504
+ const shown = limit === void 0 ? options : options.slice(0, limit);
2505
+ return {
2506
+ options: shown,
2507
+ total: options.length,
2508
+ truncated: shown.length < options.length,
2509
+ blanks
2510
+ };
2511
+ }
2512
+ function everyOption(options) {
2513
+ return new Map(
2514
+ options.map((option) => [bucketKey(option.value), option.value])
2515
+ );
2516
+ }
2517
+ function initialFilterEditorDraft(shape, values) {
2518
+ const draft = {
2519
+ mode: "values",
2520
+ options: values.options,
2521
+ blanks: values.blanks,
2522
+ search: "",
2523
+ checked: everyOption(values.options),
2524
+ includeBlanks: values.blanks > 0,
2525
+ conditions: [],
2526
+ combinator: "and"
2527
+ };
2528
+ switch (shape.kind) {
2529
+ case "values":
2530
+ return {
2531
+ ...draft,
2532
+ checked: new Map(
2533
+ shape.values.filter((value) => !isEmptyCell(value)).map((value) => [bucketKey(value), value])
2534
+ ),
2535
+ includeBlanks: shape.includeBlanks
1113
2536
  };
1114
- out.push(summary);
2537
+ case "conditions":
2538
+ return {
2539
+ ...draft,
2540
+ mode: "conditions",
2541
+ conditions: shape.conditions,
2542
+ combinator: shape.combinator
2543
+ };
2544
+ case "none":
2545
+ case "custom":
2546
+ return draft;
2547
+ }
2548
+ }
2549
+ function visibleFilterOptions(draft) {
2550
+ return draft.search === "" ? draft.options : draft.options.filter(
2551
+ (option) => matchesSearch(option.label, draft.search)
2552
+ );
2553
+ }
2554
+ function selectAllCoversBlanks(draft) {
2555
+ return draft.search === "" && draft.blanks > 0;
2556
+ }
2557
+ function selectAllState(draft) {
2558
+ const states = visibleFilterOptions(draft).map(
2559
+ (option) => draft.checked.has(bucketKey(option.value))
2560
+ );
2561
+ if (selectAllCoversBlanks(draft)) {
2562
+ states.push(draft.includeBlanks);
2563
+ }
2564
+ if (states.length > 0 && states.every(Boolean)) {
2565
+ return "checked";
2566
+ }
2567
+ return states.some(Boolean) ? "mixed" : "unchecked";
2568
+ }
2569
+ function toggleAll(draft) {
2570
+ const check = selectAllState(draft) !== "checked";
2571
+ const checked = new Map(draft.checked);
2572
+ for (const option of visibleFilterOptions(draft)) {
2573
+ const key = bucketKey(option.value);
2574
+ if (check) {
2575
+ checked.set(key, option.value);
2576
+ } else {
2577
+ checked.delete(key);
1115
2578
  }
1116
2579
  }
1117
- return index;
2580
+ return {
2581
+ ...draft,
2582
+ mode: "values",
2583
+ checked,
2584
+ includeBlanks: selectAllCoversBlanks(draft) ? check : draft.includeBlanks
2585
+ };
1118
2586
  }
1119
- function withGroupAggregates(displayRows, flatRows, groupBy, specs, columns, display = "inline") {
1120
- if (specs.length === 0) {
1121
- return displayRows;
2587
+ function filterEditorReducer(draft, action) {
2588
+ switch (action.type) {
2589
+ case "search":
2590
+ return { ...draft, search: action.search };
2591
+ case "toggleValue": {
2592
+ const key = bucketKey(action.value);
2593
+ const checked = new Map(draft.checked);
2594
+ if (checked.has(key)) {
2595
+ checked.delete(key);
2596
+ } else {
2597
+ checked.set(key, action.value);
2598
+ }
2599
+ return { ...draft, mode: "values", checked };
2600
+ }
2601
+ case "toggleAll":
2602
+ return toggleAll(draft);
2603
+ case "setIncludeBlanks":
2604
+ return { ...draft, mode: "values", includeBlanks: action.includeBlanks };
2605
+ case "setConditions":
2606
+ return {
2607
+ ...draft,
2608
+ mode: "conditions",
2609
+ conditions: action.conditions,
2610
+ combinator: action.combinator ?? draft.combinator
2611
+ };
2612
+ case "clear":
2613
+ return {
2614
+ ...draft,
2615
+ search: "",
2616
+ checked: everyOption(draft.options),
2617
+ includeBlanks: draft.blanks > 0,
2618
+ conditions: []
2619
+ };
1122
2620
  }
1123
- const byId = new Map(columns.map((entry) => [entry.id, entry.column]));
1124
- const out = [];
1125
- appendAggregatedSpan(
1126
- displayRows,
1127
- 0,
1128
- null,
1129
- flatRows,
1130
- groupBy,
1131
- byId,
1132
- specs,
1133
- columns,
1134
- display,
1135
- out
1136
- );
1137
- return out.map((entry, position) => {
1138
- if ("kind" in entry) {
1139
- return entry.rowIndex === position && entry.datasetIndex === position ? entry : { ...entry, rowIndex: position, datasetIndex: position };
2621
+ }
2622
+ function checksEverything(draft) {
2623
+ return draft.options.every(
2624
+ (option) => draft.checked.has(bucketKey(option.value))
2625
+ ) && (draft.includeBlanks || draft.blanks === 0);
2626
+ }
2627
+ function draftEntries(draft, columnId) {
2628
+ if (draft.mode === "conditions") {
2629
+ const conditions = draft.conditions.map((condition) => ({
2630
+ ...condition,
2631
+ columnId
2632
+ }));
2633
+ return conditions.length > 1 ? [{ combinator: draft.combinator, entries: conditions }] : conditions;
2634
+ }
2635
+ if (checksEverything(draft)) {
2636
+ return [];
2637
+ }
2638
+ const picked = {
2639
+ columnId,
2640
+ operator: "in",
2641
+ value: [...draft.checked.values()]
2642
+ };
2643
+ return draft.includeBlanks ? [
2644
+ {
2645
+ combinator: "or",
2646
+ entries: [picked, { columnId, operator: "isEmpty" }]
1140
2647
  }
1141
- return entry.rowIndex === position ? entry : { ...entry, rowIndex: position };
1142
- });
2648
+ ] : [picked];
2649
+ }
2650
+ function draftToFilter(draft, columnId, current) {
2651
+ const replacement = draftEntries(draft, columnId);
2652
+ const first = current.findIndex((entry) => touchesColumn(entry, columnId));
2653
+ if (first === -1) {
2654
+ return replacement.length === 0 ? current : [...current, ...replacement];
2655
+ }
2656
+ const kept = current.filter((entry) => !touchesColumn(entry, columnId));
2657
+ return [...kept.slice(0, first), ...replacement, ...kept.slice(first)];
2658
+ }
2659
+
2660
+ // src/messages.ts
2661
+ function selectedCount(count, target) {
2662
+ const noun = target === "rows" ? "row" : "column";
2663
+ if (count === 0) {
2664
+ return `No ${noun}s selected`;
2665
+ }
2666
+ return count === 1 ? `1 ${noun} selected` : `${String(count)} ${noun}s selected`;
2667
+ }
2668
+ var defaultMessages = {
2669
+ pagerLabel: "Pagination",
2670
+ previousPage: "Previous",
2671
+ nextPage: "Next",
2672
+ pageSizeLabel: "Rows per page",
2673
+ pageButtonLabel: (page) => `Page ${String(page)}`,
2674
+ pageStatus: (page, pageCount) => `Page ${String(page)} of ${String(pageCount)}`,
2675
+ loadingMore: "Loading more\u2026",
2676
+ booleanTrue: "true",
2677
+ booleanFalse: "false",
2678
+ blankGroupValue: "(blank)",
2679
+ groupByBarLabel: "Grouped by",
2680
+ groupByBarPlaceholder: "Drag a column here to group by it",
2681
+ groupByChipLabel: (columnName, level, levelCount) => levelCount > 1 ? `${columnName}, level ${String(level)} of ${String(levelCount)}` : columnName,
2682
+ removeGroupBy: (columnName) => `Stop grouping by ${columnName}`,
2683
+ columnResized: (columnName, width) => `${columnName}, ${String(width)} pixels wide`,
2684
+ columnMoved: (columnName, position, columnCount) => `${columnName}, column ${String(position)} of ${String(columnCount)}`,
2685
+ columnSorted: (columnName, direction, key, keyCount) => {
2686
+ const sorted = `${columnName} sorted ${direction === "asc" ? "ascending" : "descending"}`;
2687
+ return keyCount > 1 ? `${sorted}, key ${String(key)} of ${String(keyCount)}` : sorted;
2688
+ },
2689
+ sortCleared: (columnName) => `${columnName}, sort cleared`,
2690
+ groupedBy: (columnName, level, levelCount) => levelCount > 1 ? `Grouped by ${columnName}, level ${String(level)} of ${String(levelCount)}` : `Grouped by ${columnName}`,
2691
+ groupingRemoved: (columnName) => `${columnName}, grouping removed`,
2692
+ groupExpanded: "Group expanded",
2693
+ groupCollapsed: "Group collapsed",
2694
+ allGroupsExpanded: "All groups expanded",
2695
+ allGroupsCollapsed: "All groups collapsed",
2696
+ selectedCount,
2697
+ cellSelected: (columnName, rowNumber) => `${columnName}, row ${String(rowNumber)}, selected`
2698
+ };
2699
+ function resolveMessages(messages) {
2700
+ return messages === void 0 ? defaultMessages : { ...defaultMessages, ...messages };
1143
2701
  }
1144
2702
 
1145
2703
  // src/util/virtualization.ts
@@ -1190,6 +2748,7 @@ export {
1190
2748
  alignmentForType,
1191
2749
  applyColumnOrder,
1192
2750
  applyColumnResize,
2751
+ applyColumnVisibility,
1193
2752
  applySelectionIntent,
1194
2753
  beginColumnResize,
1195
2754
  buildKeyShortcuts,
@@ -1198,18 +2757,29 @@ export {
1198
2757
  clearAllFilters,
1199
2758
  clearSelection,
1200
2759
  collapseAllGroups,
2760
+ columnValueOptions,
1201
2761
  compareValues,
1202
2762
  computeAggregates,
2763
+ createGridController,
2764
+ createGridViewResolver,
2765
+ defaultMessages,
1203
2766
  defineColumnsFromRows,
2767
+ describeColumnFilter,
1204
2768
  diffSelection,
2769
+ draftToFilter,
1205
2770
  expandAllGroups,
2771
+ filterEditorReducer,
1206
2772
  filterQueryFor,
1207
2773
  filterRows,
1208
2774
  fitColumnsToWidth,
2775
+ formatAggregateValue,
2776
+ formatCellValue,
1209
2777
  getColumnId,
1210
2778
  groupRowId,
1211
2779
  groupRows,
2780
+ initialFilterEditorDraft,
1212
2781
  intentOf,
2782
+ isColumnVisible,
1213
2783
  isSameCell,
1214
2784
  matchesQuery,
1215
2785
  moveColumnBefore,
@@ -1221,20 +2791,27 @@ export {
1221
2791
  paginationWindow,
1222
2792
  resolveCell,
1223
2793
  resolveColumnConstraints,
2794
+ resolveColumnHeaderText,
1224
2795
  resolveColumnLabel,
1225
2796
  resolveColumnWidths,
1226
2797
  resolveColumns,
2798
+ resolveDataColumns,
1227
2799
  resolveDropBefore,
2800
+ resolveGridView,
1228
2801
  resolveKeyboardDropTarget,
2802
+ resolveMessages,
1229
2803
  resolveRowId,
1230
2804
  resolveRows,
1231
2805
  resolveShownRows,
1232
2806
  revertColumnSize,
1233
2807
  selectAll,
2808
+ selectAllState,
1234
2809
  selectCell,
1235
2810
  selectOnly,
1236
2811
  selectRange,
2812
+ serializeGridState,
1237
2813
  setColumnFilter,
2814
+ setColumnVisibility,
1238
2815
  sizeColumnToContent,
1239
2816
  sortDirectionFor,
1240
2817
  sortPriorityFor,
@@ -1244,6 +2821,7 @@ export {
1244
2821
  toggleGroupExpansion,
1245
2822
  toggleSelection,
1246
2823
  totalColumnWidth,
2824
+ visibleFilterOptions,
1247
2825
  visibleRange,
1248
2826
  withGroupAggregates
1249
2827
  };