@gridkitjs/core 0.4.0 → 0.6.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.d.ts +405 -1
- package/dist/index.js +353 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -40,6 +40,10 @@ interface CellTemplateContext<Row> {
|
|
|
40
40
|
* within the page; an index into the whole dataset would be a second field.
|
|
41
41
|
*/
|
|
42
42
|
rowIndex: number;
|
|
43
|
+
/** This row's id, as `resolveRowId` settled it. */
|
|
44
|
+
rowId: string;
|
|
45
|
+
/** Whether this row is selected, so a template can style itself to match. */
|
|
46
|
+
selected: boolean;
|
|
43
47
|
}
|
|
44
48
|
/**
|
|
45
49
|
* @typeParam Node - What a header or cell renders to. This package is
|
|
@@ -93,6 +97,8 @@ interface ColumnDefinition<Row, Node = string> {
|
|
|
93
97
|
* grid-level default. A column that cannot move can still be moved past.
|
|
94
98
|
*/
|
|
95
99
|
reorderable?: boolean;
|
|
100
|
+
/** Whether this column can be sorted, overriding the grid-level default. */
|
|
101
|
+
sortable?: boolean;
|
|
96
102
|
/**
|
|
97
103
|
* Lets this column's header and/or cell text wrap onto multiple lines
|
|
98
104
|
* instead of the grid's default single line with an ellipsis. Off by
|
|
@@ -215,6 +221,223 @@ interface ColumnOrderEvent {
|
|
|
215
221
|
readonly columnId: string;
|
|
216
222
|
readonly order: ColumnOrderState;
|
|
217
223
|
}
|
|
224
|
+
/** Which way a column sorts. */
|
|
225
|
+
type SortDirection = "asc" | "desc";
|
|
226
|
+
/** One column's place in a stacked sort, and which way it sorts. */
|
|
227
|
+
interface ColumnSortEntry {
|
|
228
|
+
readonly columnId: string;
|
|
229
|
+
readonly direction: SortDirection;
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* The active sort, in priority order: index 0 sorts first, ties broken by
|
|
233
|
+
* index 1, and so on down the stack. Empty for an unsorted grid, mirroring
|
|
234
|
+
* `SelectionState`'s empty array for "nothing selected".
|
|
235
|
+
*/
|
|
236
|
+
type ColumnSortState = readonly ColumnSortEntry[];
|
|
237
|
+
/**
|
|
238
|
+
* Reports a user sort change whole — the stack to persist from. No `phase`:
|
|
239
|
+
* unlike a resize, a sort has no in-progress state, only before and after.
|
|
240
|
+
*/
|
|
241
|
+
interface ColumnSortEvent {
|
|
242
|
+
readonly columnId: string;
|
|
243
|
+
readonly sort: ColumnSortState;
|
|
244
|
+
}
|
|
245
|
+
interface FilterEntryBase<Row> {
|
|
246
|
+
readonly columnId?: FieldPath<Row> | (string & {});
|
|
247
|
+
}
|
|
248
|
+
/** Matches a column's stringified value against an SQL LIKE-style query. */
|
|
249
|
+
interface TextFilterEntry<Row> extends FilterEntryBase<Row> {
|
|
250
|
+
/**
|
|
251
|
+
* A bare query is an exact match; `%text%` is contains; `text%` is
|
|
252
|
+
* starts-with; `%text` is ends-with. Always case-insensitive. Only the
|
|
253
|
+
* first and last characters are read as anchors — a `%` anywhere else is
|
|
254
|
+
* a literal character, not a wildcard.
|
|
255
|
+
*/
|
|
256
|
+
readonly query: string;
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Matches a column's actual (non-stringified) value by strict equality —
|
|
260
|
+
* only when the column's resolved `type` agrees: `number`/`decimal`/
|
|
261
|
+
* `currency`/`percent` for a `number` value, `boolean` for a `boolean`
|
|
262
|
+
* value, `date`/`dateTime`/`time` for a `Date` value (compared via
|
|
263
|
+
* `getTime()`). A `value` against a column of the wrong type never matches,
|
|
264
|
+
* rather than silently stringifying and falling through to a text
|
|
265
|
+
* comparison — the whole point of this variant existing separately from
|
|
266
|
+
* `TextFilterEntry` is that it doesn't do that.
|
|
267
|
+
*/
|
|
268
|
+
interface ValueFilterEntry<Row> extends FilterEntryBase<Row> {
|
|
269
|
+
readonly value: number | boolean | Date;
|
|
270
|
+
}
|
|
271
|
+
type FilterPredicate<Row> = (value: unknown, row: Row) => boolean;
|
|
272
|
+
/**
|
|
273
|
+
* Matches via caller-supplied logic — a numeric range, a cross-field
|
|
274
|
+
* combination, anything `query`/`value` can't express. Called once per row,
|
|
275
|
+
* not per column: when `columnId` is given, `value` is that column's value,
|
|
276
|
+
* resolved for convenience; when it's omitted, `value` is `undefined` and
|
|
277
|
+
* the predicate is expected to read whatever it needs off `row` itself.
|
|
278
|
+
*/
|
|
279
|
+
interface PredicateFilterEntry<Row> extends FilterEntryBase<Row> {
|
|
280
|
+
readonly predicate: FilterPredicate<Row>;
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* A nested group of entries, combined with `combinator` instead of the
|
|
284
|
+
* implicit AND every top-level `FilterState` uses. `entries` is itself a
|
|
285
|
+
* `FilterState<Row>`, so groups nest to any depth — the only entry variant
|
|
286
|
+
* with no `columnId` of its own, since it isn't scoped to one column, only
|
|
287
|
+
* to the entries it composes.
|
|
288
|
+
*/
|
|
289
|
+
interface GroupFilterEntry<Row> {
|
|
290
|
+
readonly combinator: "and" | "or";
|
|
291
|
+
readonly entries: FilterState<Row>;
|
|
292
|
+
}
|
|
293
|
+
type FilterEntry<Row> = TextFilterEntry<Row> | ValueFilterEntry<Row> | PredicateFilterEntry<Row> | GroupFilterEntry<Row>;
|
|
294
|
+
/**
|
|
295
|
+
* The active filter, every entry ANDed together at the top level — nest a
|
|
296
|
+
* `GroupFilterEntry` for anything that needs OR. Empty for an unfiltered
|
|
297
|
+
* grid, mirroring `ColumnSortState`'s empty array for "no sort".
|
|
298
|
+
*/
|
|
299
|
+
type FilterState<Row> = readonly FilterEntry<Row>[];
|
|
300
|
+
/**
|
|
301
|
+
* Ids selected, in the order they were selected — so the most recent is last,
|
|
302
|
+
* and a consumer wanting only that reads `at(-1)` rather than tracking it
|
|
303
|
+
* alongside.
|
|
304
|
+
*
|
|
305
|
+
* Ordered rather than a keyed record for that reason alone; a render layer
|
|
306
|
+
* that needs lookups builds a `Set` from it. Rows and columns share the type:
|
|
307
|
+
* they are both a list of ids, and the state they live in already names which.
|
|
308
|
+
*/
|
|
309
|
+
type SelectionState = readonly string[];
|
|
310
|
+
/**
|
|
311
|
+
* How much of a member may be selected at once. `false` rather than an absent
|
|
312
|
+
* key so that turning selection off for one member reads the same as never
|
|
313
|
+
* having enabled it.
|
|
314
|
+
*/
|
|
315
|
+
type SelectionMode = false | "single" | "multiple";
|
|
316
|
+
/**
|
|
317
|
+
* The modes a cell may take. A cell addresses one value, so it has no
|
|
318
|
+
* `"multiple"` — a range of cells is a different feature, with a rectangle
|
|
319
|
+
* rather than a list behind it.
|
|
320
|
+
*/
|
|
321
|
+
type CellSelectionMode = false | "single";
|
|
322
|
+
/**
|
|
323
|
+
* Which parts of the grid the user may select, and how many of each.
|
|
324
|
+
*
|
|
325
|
+
* Off by default, unlike `HoverableConfig`: selection claims the click, which
|
|
326
|
+
* a grid that only displays data should not do.
|
|
327
|
+
*/
|
|
328
|
+
interface SelectableConfig {
|
|
329
|
+
rows?: SelectionMode | undefined;
|
|
330
|
+
columns?: SelectionMode | undefined;
|
|
331
|
+
cells?: CellSelectionMode | undefined;
|
|
332
|
+
}
|
|
333
|
+
/** The address of one cell: which row, and which column within it. */
|
|
334
|
+
interface SelectedCellRef {
|
|
335
|
+
readonly rowId: string;
|
|
336
|
+
readonly columnId: string;
|
|
337
|
+
}
|
|
338
|
+
/** The one selected cell, or `null` for none. */
|
|
339
|
+
type CellSelectionState = SelectedCellRef | null;
|
|
340
|
+
/**
|
|
341
|
+
* What changed between two selections. Ids rather than resolved members, so
|
|
342
|
+
* that the transform producing it stays free of the data it selects from.
|
|
343
|
+
*/
|
|
344
|
+
interface SelectionDiff {
|
|
345
|
+
readonly added: readonly string[];
|
|
346
|
+
readonly removed: readonly string[];
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* A row paired with the identity and position it renders under — the row
|
|
350
|
+
* counterpart to `ResolvedColumn`, and resolved once for the same reason: so
|
|
351
|
+
* no part of the grid works out a row's id for itself.
|
|
352
|
+
*
|
|
353
|
+
* It is also what the selection callbacks report, so a handler reads the row
|
|
354
|
+
* itself rather than an id it has to look up.
|
|
355
|
+
*/
|
|
356
|
+
interface ResolvedRow<Row> {
|
|
357
|
+
readonly rowId: string;
|
|
358
|
+
readonly row: Row;
|
|
359
|
+
/** Position among the rows as rendered, matching `CellTemplateContext`. */
|
|
360
|
+
readonly rowIndex: number;
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* A selected column. Carries the resolved column rather than the bare
|
|
364
|
+
* definition, so a handler reads the width and label the user actually
|
|
365
|
+
* clicked rather than what the definition asked for.
|
|
366
|
+
*/
|
|
367
|
+
interface SelectedColumn<Row, Node = string> {
|
|
368
|
+
readonly columnId: string;
|
|
369
|
+
readonly column: ResolvedColumn<Row, Node>;
|
|
370
|
+
/** Position among the columns as displayed, so after any reorder. */
|
|
371
|
+
readonly columnIndex: number;
|
|
372
|
+
}
|
|
373
|
+
/** A selected cell, resolved down to the value it shows. */
|
|
374
|
+
interface SelectedCell<Row, Node = string> {
|
|
375
|
+
readonly rowId: string;
|
|
376
|
+
readonly columnId: string;
|
|
377
|
+
readonly row: Row;
|
|
378
|
+
readonly column: ResolvedColumn<Row, Node>;
|
|
379
|
+
readonly rowIndex: number;
|
|
380
|
+
readonly columnIndex: number;
|
|
381
|
+
/** Read off the column's field path — the value a `cellTemplate` receives. */
|
|
382
|
+
readonly value: unknown;
|
|
383
|
+
}
|
|
384
|
+
/** Reports one row selected or deselected. */
|
|
385
|
+
interface RowSelectEvent<Row> {
|
|
386
|
+
readonly row: ResolvedRow<Row>;
|
|
387
|
+
readonly selection: SelectionState;
|
|
388
|
+
}
|
|
389
|
+
/**
|
|
390
|
+
* Reports every row one interaction selected or deselected. Fires once for a
|
|
391
|
+
* range that `RowSelectEvent` reports one row at a time.
|
|
392
|
+
*/
|
|
393
|
+
interface RowsSelectEvent<Row> {
|
|
394
|
+
readonly rows: readonly ResolvedRow<Row>[];
|
|
395
|
+
readonly selection: SelectionState;
|
|
396
|
+
}
|
|
397
|
+
/**
|
|
398
|
+
* Reports a row selection change whole: what it gained, what it lost, and
|
|
399
|
+
* everything it now holds. The one to persist from.
|
|
400
|
+
*/
|
|
401
|
+
interface RowSelectionChangeEvent<Row> {
|
|
402
|
+
readonly added: readonly ResolvedRow<Row>[];
|
|
403
|
+
readonly removed: readonly ResolvedRow<Row>[];
|
|
404
|
+
/** Every selected row, not only what this interaction changed. */
|
|
405
|
+
readonly selected: readonly ResolvedRow<Row>[];
|
|
406
|
+
readonly selection: SelectionState;
|
|
407
|
+
}
|
|
408
|
+
/** Reports one column selected or deselected. */
|
|
409
|
+
interface ColumnSelectEvent<Row, Node = string> {
|
|
410
|
+
readonly column: SelectedColumn<Row, Node>;
|
|
411
|
+
readonly selection: SelectionState;
|
|
412
|
+
}
|
|
413
|
+
/** Reports every column one interaction selected or deselected. */
|
|
414
|
+
interface ColumnsSelectEvent<Row, Node = string> {
|
|
415
|
+
readonly columns: readonly SelectedColumn<Row, Node>[];
|
|
416
|
+
readonly selection: SelectionState;
|
|
417
|
+
}
|
|
418
|
+
/** Reports a column selection change whole. The one to persist from. */
|
|
419
|
+
interface ColumnSelectionChangeEvent<Row, Node = string> {
|
|
420
|
+
readonly added: readonly SelectedColumn<Row, Node>[];
|
|
421
|
+
readonly removed: readonly SelectedColumn<Row, Node>[];
|
|
422
|
+
/** Every selected column, not only what this interaction changed. */
|
|
423
|
+
readonly selected: readonly SelectedColumn<Row, Node>[];
|
|
424
|
+
readonly selection: SelectionState;
|
|
425
|
+
}
|
|
426
|
+
/** Reports the cell selected or deselected. */
|
|
427
|
+
interface CellSelectEvent<Row, Node = string> {
|
|
428
|
+
readonly cell: SelectedCell<Row, Node>;
|
|
429
|
+
readonly selection: CellSelectionState;
|
|
430
|
+
}
|
|
431
|
+
/**
|
|
432
|
+
* Reports a cell selection change whole. Both fields are nullable and one
|
|
433
|
+
* interaction can fill both — moving between cells deselects and selects at
|
|
434
|
+
* once.
|
|
435
|
+
*/
|
|
436
|
+
interface CellSelectionChangeEvent<Row, Node = string> {
|
|
437
|
+
readonly selected: SelectedCell<Row, Node> | null;
|
|
438
|
+
readonly deselected: SelectedCell<Row, Node> | null;
|
|
439
|
+
readonly selection: CellSelectionState;
|
|
440
|
+
}
|
|
218
441
|
|
|
219
442
|
/**
|
|
220
443
|
* How a column of `type` aligns by default. Numeric types align right so that
|
|
@@ -237,6 +460,17 @@ declare function defineColumnsFromRows<Row>(rows: readonly Row[]): readonly Colu
|
|
|
237
460
|
* Every such lookup goes through here so the two can never disagree.
|
|
238
461
|
*/
|
|
239
462
|
declare function getColumnId<Row>(column: ColumnDefinition<Row, unknown>): string;
|
|
463
|
+
/**
|
|
464
|
+
* The key state about a row is stored under, and the key it renders with — the
|
|
465
|
+
* row's counterpart to `getColumnId`, and for the same reason: every such
|
|
466
|
+
* lookup goes through here so no two can disagree.
|
|
467
|
+
*
|
|
468
|
+
* Falls back to the position so a grid given no `getRowId` still works. That
|
|
469
|
+
* fallback ties a selection to where a row sits rather than to the row, so
|
|
470
|
+
* anything that sorts, filters or pages its data has to supply one — otherwise
|
|
471
|
+
* a re-sort leaves the same positions selected under different rows.
|
|
472
|
+
*/
|
|
473
|
+
declare function resolveRowId<Row>(row: Row, index: number, getRowId?: (row: Row, index: number) => string): string;
|
|
240
474
|
/**
|
|
241
475
|
* What a column's header shows: its own `headerTemplate`, called first if it is
|
|
242
476
|
* a function, or else a label read off the field path — `"Application.Id"`
|
|
@@ -286,6 +520,61 @@ declare function movesColumn(orderedIds: ColumnOrderState, movedId: string, befo
|
|
|
286
520
|
*/
|
|
287
521
|
declare function resolveDropBefore(orderedIds: ColumnOrderState, targetId: string, side: DropSide): string | null;
|
|
288
522
|
|
|
523
|
+
/**
|
|
524
|
+
* The selection with `id` added if absent and removed if present — a
|
|
525
|
+
* Ctrl-click, or Space on the focused member.
|
|
526
|
+
*
|
|
527
|
+
* The mode is enforced here rather than by each caller, so `"single"` cannot
|
|
528
|
+
* accumulate no matter which interaction reaches it. Returns `selection`
|
|
529
|
+
* itself when `mode` is `false`, keeping the unchanged-reference contract
|
|
530
|
+
* `moveColumnBefore` set: a caller treats an unchanged reference as "nothing
|
|
531
|
+
* happened" and neither renders nor reports.
|
|
532
|
+
*/
|
|
533
|
+
declare function toggleSelection(selection: SelectionState, id: string, mode: SelectionMode): SelectionState;
|
|
534
|
+
/**
|
|
535
|
+
* The selection reduced to `id` alone — a plain click, which discards what
|
|
536
|
+
* came before rather than adding to it.
|
|
537
|
+
*
|
|
538
|
+
* Never deselects, so clicking the one selected row leaves it selected.
|
|
539
|
+
* Letting go of it is Ctrl-click's job, and `toggleSelection`'s.
|
|
540
|
+
*/
|
|
541
|
+
declare function selectOnly(selection: SelectionState, id: string, mode: SelectionMode): SelectionState;
|
|
542
|
+
/**
|
|
543
|
+
* The selection replaced by every id from `anchorId` to `focusId` inclusive — a
|
|
544
|
+
* Shift-click. The two may be given in either order.
|
|
545
|
+
*
|
|
546
|
+
* Spans `orderedIds`, the order as displayed, rather than the selection's own:
|
|
547
|
+
* the range a user drew across the screen is the range they mean, whatever
|
|
548
|
+
* order they clicked its ends in. Degrades to `selectOnly` under `"single"`,
|
|
549
|
+
* where a range has nowhere to go.
|
|
550
|
+
*/
|
|
551
|
+
declare function selectRange(selection: SelectionState, orderedIds: readonly string[], anchorId: string, focusId: string, mode: SelectionMode): SelectionState;
|
|
552
|
+
/**
|
|
553
|
+
* Every id in `orderedIds`, or `selection` untouched under any mode but
|
|
554
|
+
* `"multiple"` — there is no selecting all of one thing.
|
|
555
|
+
*/
|
|
556
|
+
declare function selectAll(selection: SelectionState, orderedIds: readonly string[], mode: SelectionMode): SelectionState;
|
|
557
|
+
declare function clearSelection(selection: SelectionState): SelectionState;
|
|
558
|
+
/**
|
|
559
|
+
* What one transition added and what it removed.
|
|
560
|
+
*
|
|
561
|
+
* Defined here rather than in an adapter so that every callback reporting a
|
|
562
|
+
* selection change reads one answer, and a select and a deselect fired for the
|
|
563
|
+
* same interaction cannot disagree about which it was.
|
|
564
|
+
*/
|
|
565
|
+
declare function diffSelection(previous: SelectionState, next: SelectionState): SelectionDiff;
|
|
566
|
+
declare function isSameCell(a: CellSelectionState, b: CellSelectionState): boolean;
|
|
567
|
+
/**
|
|
568
|
+
* The cell selection moved to `cell` — a plain click. Idempotent: clicking the
|
|
569
|
+
* selected cell again leaves it selected, matching `selectOnly`.
|
|
570
|
+
*/
|
|
571
|
+
declare function selectCell(selection: CellSelectionState, cell: SelectedCellRef, mode: CellSelectionMode): CellSelectionState;
|
|
572
|
+
/**
|
|
573
|
+
* The cell selection moved to `cell`, or cleared when it is already there — a
|
|
574
|
+
* Ctrl-click, matching `toggleSelection`.
|
|
575
|
+
*/
|
|
576
|
+
declare function toggleCellSelection(selection: CellSelectionState, cell: SelectedCellRef, mode: CellSelectionMode): CellSelectionState;
|
|
577
|
+
|
|
289
578
|
/**
|
|
290
579
|
* Applied to any column that does not size itself. The minimum is a width a
|
|
291
580
|
* header stays legible at rather than a measured value — a column dragged
|
|
@@ -346,4 +635,119 @@ declare function fitColumnsToWidth<Row, Node>(resolved: readonly ResolvedColumn<
|
|
|
346
635
|
*/
|
|
347
636
|
declare function sizeColumnToContent(measuredWidth: number, constraints: ColumnConstraints, padding?: number): number;
|
|
348
637
|
|
|
349
|
-
|
|
638
|
+
/**
|
|
639
|
+
* Compares two cell values the way `type` implies — numerically for the
|
|
640
|
+
* numeric types, chronologically for the date/time types, lexically
|
|
641
|
+
* otherwise. Defaults to string comparison when `type` is absent, mirroring
|
|
642
|
+
* `ColumnDefinition.type`'s own default.
|
|
643
|
+
*
|
|
644
|
+
* `null`/`undefined` sort after every value on both sides. Used directly,
|
|
645
|
+
* that ordering is fixed; `sortRows` applies it directionally without
|
|
646
|
+
* disturbing it — see its own doc comment.
|
|
647
|
+
*/
|
|
648
|
+
declare function compareValues(a: unknown, b: unknown, type?: ColumnType): number;
|
|
649
|
+
/** This column's direction in `sort`, or `null` outside it. */
|
|
650
|
+
declare function sortDirectionFor(sort: ColumnSortState, columnId: string): SortDirection | null;
|
|
651
|
+
/** This column's 1-based place in `sort`, or `null` outside it. */
|
|
652
|
+
declare function sortPriorityFor(sort: ColumnSortState, columnId: string): number | null;
|
|
653
|
+
/**
|
|
654
|
+
* The sort with `columnId` cycled to its next direction — none → asc → desc →
|
|
655
|
+
* none.
|
|
656
|
+
*
|
|
657
|
+
* `stack: false` (a plain click) means "sort by only this column": unless
|
|
658
|
+
* `columnId` is already the sole entry, in which case its own cycle just
|
|
659
|
+
* continues, it collapses `sort` down to `columnId` alone at `"asc"`,
|
|
660
|
+
* regardless of any direction it held as part of a larger stack.
|
|
661
|
+
*
|
|
662
|
+
* `stack: true` (a Shift-click) cycles or appends `columnId` in place without
|
|
663
|
+
* disturbing the rest of the stack; cycling it back to `"none"` removes it
|
|
664
|
+
* and closes the gap, leaving the others in their existing order.
|
|
665
|
+
*/
|
|
666
|
+
declare function toggleColumnSort(sort: ColumnSortState, columnId: string, options: {
|
|
667
|
+
readonly stack: boolean;
|
|
668
|
+
}): ColumnSortState;
|
|
669
|
+
/**
|
|
670
|
+
* `rows` ordered by `sort` — each entry read against `columns` for its
|
|
671
|
+
* `field` and `type` — with `rowIndex` renumbered to match the new
|
|
672
|
+
* positions, the invariant the rest of the grid relies on for keyboard
|
|
673
|
+
* navigation and `aria-rowindex`.
|
|
674
|
+
*
|
|
675
|
+
* Stable (`Array.prototype.sort` has been stable since ES2019), so rows
|
|
676
|
+
* equal under every active key keep their relative order. A `null`/
|
|
677
|
+
* `undefined` value sorts last under either direction — resolved before
|
|
678
|
+
* `compareValues` is asked, and left out of the direction flip below, so
|
|
679
|
+
* that toggling a column reorders the values it holds rather than moving its
|
|
680
|
+
* gaps to the front.
|
|
681
|
+
*
|
|
682
|
+
* Returns `rows` itself, untouched, when `sort` is empty or names only
|
|
683
|
+
* columns absent from `columns` — a stored sort tolerates a column removed
|
|
684
|
+
* from the definition the same way a stored `ColumnOrderState` does.
|
|
685
|
+
*/
|
|
686
|
+
declare function sortRows<Row>(rows: readonly ResolvedRow<Row>[], sort: ColumnSortState, columns: readonly ResolvedColumn<Row, unknown>[]): readonly ResolvedRow<Row>[];
|
|
687
|
+
|
|
688
|
+
/**
|
|
689
|
+
* Whether `value`, stringified, matches `query`'s SQL LIKE-style pattern —
|
|
690
|
+
* case-insensitive always. A bare query is an exact match; `%text%` is
|
|
691
|
+
* contains; `text%` is starts-with; `%text` is ends-with. `null`/`undefined`
|
|
692
|
+
* stringify to `""`.
|
|
693
|
+
*
|
|
694
|
+
* A one-shot convenience: parses `query` fresh on every call. `filterRows`
|
|
695
|
+
* does not call this per cell — it compiles each `TextFilterEntry` once,
|
|
696
|
+
* outside its row loop.
|
|
697
|
+
*/
|
|
698
|
+
declare function matchesQuery(value: unknown, query: string): boolean;
|
|
699
|
+
/**
|
|
700
|
+
* This column's current text query, or the global entry's when `columnId`
|
|
701
|
+
* is omitted; `null` if no `TextFilterEntry` names it — including when the
|
|
702
|
+
* entry that exists for that key is a `ValueFilterEntry`/
|
|
703
|
+
* `PredicateFilterEntry`/`GroupFilterEntry` instead, since none of those
|
|
704
|
+
* hold a query string to report.
|
|
705
|
+
*/
|
|
706
|
+
declare function filterQueryFor<Row>(filter: FilterState<Row>, columnId?: FieldPath<Row> | (string & {})): string | null;
|
|
707
|
+
/**
|
|
708
|
+
* The filter with the `TextFilterEntry` for `columnId` (or the global
|
|
709
|
+
* entry, when `columnId` is omitted) set to `query` — replacing whatever
|
|
710
|
+
* entry already holds that key, of any variant, if present; appending a new
|
|
711
|
+
* `TextFilterEntry` if not. An empty `query` removes the entry instead of
|
|
712
|
+
* storing a filter that can never usefully match.
|
|
713
|
+
*
|
|
714
|
+
* Returns `filter` itself, unchanged, when the result would be a no-op: an
|
|
715
|
+
* empty `query` naming a key with no existing entry, or a `query` equal to
|
|
716
|
+
* an existing `TextFilterEntry`'s.
|
|
717
|
+
*/
|
|
718
|
+
declare function setColumnFilter<Row>(filter: FilterState<Row>, columnId: (FieldPath<Row> | (string & {})) | undefined, query: string): FilterState<Row>;
|
|
719
|
+
/** `filter` reduced to empty. Returns `filter` itself when already empty. */
|
|
720
|
+
declare function clearAllFilters<Row>(filter: FilterState<Row>): FilterState<Row>;
|
|
721
|
+
/**
|
|
722
|
+
* `rows` reduced to those matching every entry in `filter` (AND at the top
|
|
723
|
+
* level — nest a `GroupFilterEntry` for OR). `rowIndex` is renumbered to
|
|
724
|
+
* match the retained positions — the same invariant `sortRows` keeps,
|
|
725
|
+
* needed here independently since `filterRows` runs before `sortRows` in
|
|
726
|
+
* the composed pipeline (see `resolveShownRows`).
|
|
727
|
+
*
|
|
728
|
+
* Order-preserving: surviving rows keep their relative order, which is what
|
|
729
|
+
* makes running this before a sort produce the identical final order a
|
|
730
|
+
* sort-then-filter would — only the cost differs.
|
|
731
|
+
*
|
|
732
|
+
* Every entry is compiled once, before the row loop.
|
|
733
|
+
*
|
|
734
|
+
* Returns `rows` itself, untouched, when `filter` is empty or names only
|
|
735
|
+
* columns absent from `columns` — a stored filter tolerates a column
|
|
736
|
+
* removed from the definition the same way a stored `ColumnSortState` does.
|
|
737
|
+
*/
|
|
738
|
+
declare function filterRows<Row>(rows: readonly ResolvedRow<Row>[], filter: FilterState<Row>, columns: readonly ResolvedColumn<Row, unknown>[]): readonly ResolvedRow<Row>[];
|
|
739
|
+
|
|
740
|
+
/**
|
|
741
|
+
* `rows`, filtered then sorted — the one function `DataGrid.tsx` (and any
|
|
742
|
+
* future framework binding) calls instead of composing `filterRows`/
|
|
743
|
+
* `sortRows` itself.
|
|
744
|
+
*
|
|
745
|
+
* Filter always runs first: an O(n) pass that shrinks what the O(n log n)
|
|
746
|
+
* sort has to touch whenever a filter is active, free when it isn't. This
|
|
747
|
+
* is a performance choice, not a correctness one — filtering is an
|
|
748
|
+
* order-preserving predicate and sort is a stable total order over whatever
|
|
749
|
+
* it's handed, so the final order is identical either way.
|
|
750
|
+
*/
|
|
751
|
+
declare function resolveShownRows<Row>(rows: readonly ResolvedRow<Row>[], filter: FilterState<Row>, sort: ColumnSortState, columns: readonly ResolvedColumn<Row, unknown>[]): readonly ResolvedRow<Row>[];
|
|
752
|
+
|
|
753
|
+
export { type CellSelectEvent, type CellSelectionChangeEvent, type CellSelectionMode, type CellSelectionState, type CellTemplateContext, type ColumnAlignment, type ColumnConstraints, type ColumnDefinition, type ColumnOrderEvent, type ColumnOrderState, type ColumnResizeEvent, type ColumnResizeSession, type ColumnResolveOptions, type ColumnSelectEvent, type ColumnSelectionChangeEvent, type ColumnSizeDefaults, type ColumnSizingState, type ColumnSortEntry, type ColumnSortEvent, type ColumnSortState, type ColumnType, type ColumnWrapConfig, type ColumnsSelectEvent, DEFAULT_COLUMN_SIZES, type DropSide, type FieldPath, type FilterEntry, type FilterPredicate, type FilterState, type GroupFilterEntry, KEYBOARD_STEP, type PredicateFilterEntry, type ResolvedColumn, type ResolvedRow, type RowSelectEvent, type RowSelectionChangeEvent, type RowsSelectEvent, type SelectableConfig, type SelectedCell, type SelectedCellRef, type SelectedColumn, type SelectionDiff, type SelectionMode, type SelectionState, type SortDirection, type TextFilterEntry, type ValueFilterEntry, accessDotted, alignmentForType, applyColumnOrder, applyColumnResize, beginColumnResize, clampColumnWidth, clearAllFilters, clearSelection, compareValues, defineColumnsFromRows, diffSelection, filterQueryFor, filterRows, fitColumnsToWidth, getColumnId, isSameCell, matchesQuery, moveColumnBefore, movesColumn, resolveColumnConstraints, resolveColumnLabel, resolveColumnWidths, resolveDropBefore, resolveRowId, resolveShownRows, selectAll, selectCell, selectOnly, selectRange, setColumnFilter, sizeColumnToContent, sortDirectionFor, sortPriorityFor, sortRows, toggleCellSelection, toggleColumnSort, toggleSelection, totalColumnWidth };
|
package/dist/index.js
CHANGED
|
@@ -60,6 +60,9 @@ function defineColumnsFromRows(rows) {
|
|
|
60
60
|
function getColumnId(column) {
|
|
61
61
|
return column.id ?? column.field;
|
|
62
62
|
}
|
|
63
|
+
function resolveRowId(row, index, getRowId) {
|
|
64
|
+
return getRowId?.(row, index) ?? String(index);
|
|
65
|
+
}
|
|
63
66
|
function resolveColumnLabel(column) {
|
|
64
67
|
const { headerTemplate } = column;
|
|
65
68
|
if (headerTemplate === void 0) {
|
|
@@ -108,6 +111,80 @@ function resolveDropBefore(orderedIds, targetId, side) {
|
|
|
108
111
|
return index === -1 ? null : orderedIds[index + 1] ?? null;
|
|
109
112
|
}
|
|
110
113
|
|
|
114
|
+
// src/util/selection.ts
|
|
115
|
+
var NO_CHANGE = { added: [], removed: [] };
|
|
116
|
+
function sameSelection(a, b) {
|
|
117
|
+
return a.length === b.length && a.every((id, index) => id === b[index]);
|
|
118
|
+
}
|
|
119
|
+
function toggleSelection(selection, id, mode) {
|
|
120
|
+
if (mode === false) {
|
|
121
|
+
return selection;
|
|
122
|
+
}
|
|
123
|
+
if (selection.includes(id)) {
|
|
124
|
+
return selection.filter((selectedId) => selectedId !== id);
|
|
125
|
+
}
|
|
126
|
+
return mode === "single" ? [id] : [...selection, id];
|
|
127
|
+
}
|
|
128
|
+
function selectOnly(selection, id, mode) {
|
|
129
|
+
if (mode === false) {
|
|
130
|
+
return selection;
|
|
131
|
+
}
|
|
132
|
+
return selection.length === 1 && selection[0] === id ? selection : [id];
|
|
133
|
+
}
|
|
134
|
+
function selectRange(selection, orderedIds, anchorId, focusId, mode) {
|
|
135
|
+
if (mode !== "multiple") {
|
|
136
|
+
return selectOnly(selection, focusId, mode);
|
|
137
|
+
}
|
|
138
|
+
const anchor = orderedIds.indexOf(anchorId);
|
|
139
|
+
const focus = orderedIds.indexOf(focusId);
|
|
140
|
+
if (anchor === -1 || focus === -1) {
|
|
141
|
+
return selection;
|
|
142
|
+
}
|
|
143
|
+
const next = orderedIds.slice(
|
|
144
|
+
Math.min(anchor, focus),
|
|
145
|
+
Math.max(anchor, focus) + 1
|
|
146
|
+
);
|
|
147
|
+
return sameSelection(selection, next) ? selection : next;
|
|
148
|
+
}
|
|
149
|
+
function selectAll(selection, orderedIds, mode) {
|
|
150
|
+
if (mode !== "multiple") {
|
|
151
|
+
return selection;
|
|
152
|
+
}
|
|
153
|
+
return sameSelection(selection, orderedIds) ? selection : orderedIds;
|
|
154
|
+
}
|
|
155
|
+
function clearSelection(selection) {
|
|
156
|
+
return selection.length === 0 ? selection : [];
|
|
157
|
+
}
|
|
158
|
+
function diffSelection(previous, next) {
|
|
159
|
+
if (previous === next) {
|
|
160
|
+
return NO_CHANGE;
|
|
161
|
+
}
|
|
162
|
+
const before = new Set(previous);
|
|
163
|
+
const after = new Set(next);
|
|
164
|
+
return {
|
|
165
|
+
added: next.filter((id) => !before.has(id)),
|
|
166
|
+
removed: previous.filter((id) => !after.has(id))
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
function isSameCell(a, b) {
|
|
170
|
+
if (a === null || b === null) {
|
|
171
|
+
return a === b;
|
|
172
|
+
}
|
|
173
|
+
return a.rowId === b.rowId && a.columnId === b.columnId;
|
|
174
|
+
}
|
|
175
|
+
function selectCell(selection, cell, mode) {
|
|
176
|
+
if (mode === false || isSameCell(selection, cell)) {
|
|
177
|
+
return selection;
|
|
178
|
+
}
|
|
179
|
+
return cell;
|
|
180
|
+
}
|
|
181
|
+
function toggleCellSelection(selection, cell, mode) {
|
|
182
|
+
if (mode === false) {
|
|
183
|
+
return selection;
|
|
184
|
+
}
|
|
185
|
+
return isSameCell(selection, cell) ? null : cell;
|
|
186
|
+
}
|
|
187
|
+
|
|
111
188
|
// src/util/sizing.ts
|
|
112
189
|
var DEFAULT_COLUMN_SIZES = {
|
|
113
190
|
width: 150,
|
|
@@ -237,6 +314,261 @@ function fitColumnsToWidth(resolved, availableWidth, defaults) {
|
|
|
237
314
|
function sizeColumnToContent(measuredWidth, constraints, padding = 2) {
|
|
238
315
|
return clampColumnWidth(Math.ceil(measuredWidth + padding), constraints);
|
|
239
316
|
}
|
|
317
|
+
|
|
318
|
+
// src/util/sorting.ts
|
|
319
|
+
function isEmpty(value) {
|
|
320
|
+
return value === null || value === void 0;
|
|
321
|
+
}
|
|
322
|
+
function toTime(value) {
|
|
323
|
+
return value instanceof Date ? value.getTime() : new Date(String(value)).getTime();
|
|
324
|
+
}
|
|
325
|
+
function compareValues(a, b, type) {
|
|
326
|
+
if (isEmpty(a)) {
|
|
327
|
+
return isEmpty(b) ? 0 : 1;
|
|
328
|
+
}
|
|
329
|
+
if (isEmpty(b)) {
|
|
330
|
+
return -1;
|
|
331
|
+
}
|
|
332
|
+
switch (type) {
|
|
333
|
+
case "number":
|
|
334
|
+
case "decimal":
|
|
335
|
+
case "currency":
|
|
336
|
+
case "percent":
|
|
337
|
+
return Number(a) - Number(b);
|
|
338
|
+
case "boolean":
|
|
339
|
+
return Number(Boolean(a)) - Number(Boolean(b));
|
|
340
|
+
case "date":
|
|
341
|
+
case "dateTime":
|
|
342
|
+
case "time":
|
|
343
|
+
return toTime(a) - toTime(b);
|
|
344
|
+
default:
|
|
345
|
+
return String(a).localeCompare(String(b));
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
function sortDirectionFor(sort, columnId) {
|
|
349
|
+
return sort.find((entry) => entry.columnId === columnId)?.direction ?? null;
|
|
350
|
+
}
|
|
351
|
+
function sortPriorityFor(sort, columnId) {
|
|
352
|
+
const index = sort.findIndex((entry) => entry.columnId === columnId);
|
|
353
|
+
return index === -1 ? null : index + 1;
|
|
354
|
+
}
|
|
355
|
+
function toggleColumnSort(sort, columnId, options) {
|
|
356
|
+
const index = sort.findIndex((entry) => entry.columnId === columnId);
|
|
357
|
+
const isSoleEntry = sort.length === 1 && index === 0;
|
|
358
|
+
if (!options.stack && !isSoleEntry) {
|
|
359
|
+
return [{ columnId, direction: "asc" }];
|
|
360
|
+
}
|
|
361
|
+
const current = index === -1 ? void 0 : sort[index]?.direction;
|
|
362
|
+
const next = current === void 0 ? "asc" : current === "asc" ? "desc" : null;
|
|
363
|
+
if (next === null) {
|
|
364
|
+
return sort.filter((entry) => entry.columnId !== columnId);
|
|
365
|
+
}
|
|
366
|
+
if (index === -1) {
|
|
367
|
+
return [...sort, { columnId, direction: next }];
|
|
368
|
+
}
|
|
369
|
+
return sort.map(
|
|
370
|
+
(entry, i) => i === index ? { columnId, direction: next } : entry
|
|
371
|
+
);
|
|
372
|
+
}
|
|
373
|
+
function sortRows(rows, sort, columns) {
|
|
374
|
+
if (sort.length === 0) {
|
|
375
|
+
return rows;
|
|
376
|
+
}
|
|
377
|
+
const byId = new Map(columns.map((entry) => [entry.id, entry.column]));
|
|
378
|
+
const active = sort.filter((entry) => byId.has(entry.columnId));
|
|
379
|
+
if (active.length === 0) {
|
|
380
|
+
return rows;
|
|
381
|
+
}
|
|
382
|
+
const sorted = [...rows].sort((a, b) => {
|
|
383
|
+
for (const { columnId, direction } of active) {
|
|
384
|
+
const column = byId.get(columnId);
|
|
385
|
+
if (column === void 0) {
|
|
386
|
+
continue;
|
|
387
|
+
}
|
|
388
|
+
const left = accessDotted(a.row, column.field);
|
|
389
|
+
const right = accessDotted(b.row, column.field);
|
|
390
|
+
const leftEmpty = isEmpty(left);
|
|
391
|
+
const rightEmpty = isEmpty(right);
|
|
392
|
+
if (leftEmpty || rightEmpty) {
|
|
393
|
+
if (leftEmpty && rightEmpty) {
|
|
394
|
+
continue;
|
|
395
|
+
}
|
|
396
|
+
return leftEmpty ? 1 : -1;
|
|
397
|
+
}
|
|
398
|
+
const result = compareValues(left, right, column.type);
|
|
399
|
+
if (result !== 0) {
|
|
400
|
+
return direction === "asc" ? result : -result;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
return 0;
|
|
404
|
+
});
|
|
405
|
+
return sorted.map(
|
|
406
|
+
(row, index) => row.rowIndex === index ? row : { ...row, rowIndex: index }
|
|
407
|
+
);
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// src/util/filtering.ts
|
|
411
|
+
function parseFilterPattern(query) {
|
|
412
|
+
const start = query.startsWith("%");
|
|
413
|
+
const end = query.endsWith("%");
|
|
414
|
+
let text = query;
|
|
415
|
+
if (start) {
|
|
416
|
+
text = text.slice(1);
|
|
417
|
+
}
|
|
418
|
+
if (end) {
|
|
419
|
+
text = text.slice(0, -1);
|
|
420
|
+
}
|
|
421
|
+
text = text.toLowerCase();
|
|
422
|
+
if (start && end) {
|
|
423
|
+
return { mode: "contains", text };
|
|
424
|
+
}
|
|
425
|
+
if (start) {
|
|
426
|
+
return { mode: "endsWith", text };
|
|
427
|
+
}
|
|
428
|
+
if (end) {
|
|
429
|
+
return { mode: "startsWith", text };
|
|
430
|
+
}
|
|
431
|
+
return { mode: "exact", text };
|
|
432
|
+
}
|
|
433
|
+
function isEmptyValue(value) {
|
|
434
|
+
return value === null || value === void 0;
|
|
435
|
+
}
|
|
436
|
+
function compileFilterQuery(query) {
|
|
437
|
+
const { mode, text } = parseFilterPattern(query);
|
|
438
|
+
return (value) => {
|
|
439
|
+
const candidate = (isEmptyValue(value) ? "" : String(value)).toLowerCase();
|
|
440
|
+
switch (mode) {
|
|
441
|
+
case "exact":
|
|
442
|
+
return candidate === text;
|
|
443
|
+
case "contains":
|
|
444
|
+
return candidate.includes(text);
|
|
445
|
+
case "startsWith":
|
|
446
|
+
return candidate.startsWith(text);
|
|
447
|
+
case "endsWith":
|
|
448
|
+
return candidate.endsWith(text);
|
|
449
|
+
}
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
function matchesTypedValue(value, target, type) {
|
|
453
|
+
if (typeof target === "number") {
|
|
454
|
+
if (type !== "number" && type !== "decimal" && type !== "currency" && type !== "percent") {
|
|
455
|
+
return false;
|
|
456
|
+
}
|
|
457
|
+
return typeof value === "number" && value === target;
|
|
458
|
+
}
|
|
459
|
+
if (typeof target === "boolean") {
|
|
460
|
+
if (type !== "boolean") {
|
|
461
|
+
return false;
|
|
462
|
+
}
|
|
463
|
+
return typeof value === "boolean" && value === target;
|
|
464
|
+
}
|
|
465
|
+
if (type !== "date" && type !== "dateTime" && type !== "time") {
|
|
466
|
+
return false;
|
|
467
|
+
}
|
|
468
|
+
return value instanceof Date && value.getTime() === target.getTime();
|
|
469
|
+
}
|
|
470
|
+
function isEntryUsable(entry, byId) {
|
|
471
|
+
if ("combinator" in entry) {
|
|
472
|
+
return true;
|
|
473
|
+
}
|
|
474
|
+
return entry.columnId === void 0 || byId.has(entry.columnId);
|
|
475
|
+
}
|
|
476
|
+
function compileFilterEntry(entry, byId, columns) {
|
|
477
|
+
if ("combinator" in entry) {
|
|
478
|
+
const tests = entry.entries.filter((nested) => isEntryUsable(nested, byId)).map((nested) => compileFilterEntry(nested, byId, columns));
|
|
479
|
+
return entry.combinator === "and" ? (row) => tests.every((test2) => test2(row)) : (row) => tests.some((test2) => test2(row));
|
|
480
|
+
}
|
|
481
|
+
if ("predicate" in entry) {
|
|
482
|
+
const { columnId: columnId2, predicate } = entry;
|
|
483
|
+
const column = columnId2 === void 0 ? void 0 : byId.get(columnId2);
|
|
484
|
+
return (row) => predicate(
|
|
485
|
+
column === void 0 ? void 0 : accessDotted(row.row, column.field),
|
|
486
|
+
row.row
|
|
487
|
+
);
|
|
488
|
+
}
|
|
489
|
+
if ("value" in entry) {
|
|
490
|
+
const { columnId: columnId2, value } = entry;
|
|
491
|
+
if (columnId2 !== void 0) {
|
|
492
|
+
const column = byId.get(columnId2);
|
|
493
|
+
if (column === void 0) {
|
|
494
|
+
return () => false;
|
|
495
|
+
}
|
|
496
|
+
return (row) => matchesTypedValue(
|
|
497
|
+
accessDotted(row.row, column.field),
|
|
498
|
+
value,
|
|
499
|
+
column.type
|
|
500
|
+
);
|
|
501
|
+
}
|
|
502
|
+
return (row) => columns.some(
|
|
503
|
+
(entry2) => matchesTypedValue(
|
|
504
|
+
accessDotted(row.row, entry2.column.field),
|
|
505
|
+
value,
|
|
506
|
+
entry2.column.type
|
|
507
|
+
)
|
|
508
|
+
);
|
|
509
|
+
}
|
|
510
|
+
const { columnId, query } = entry;
|
|
511
|
+
const test = compileFilterQuery(query);
|
|
512
|
+
if (columnId !== void 0) {
|
|
513
|
+
const column = byId.get(columnId);
|
|
514
|
+
if (column === void 0) {
|
|
515
|
+
return () => false;
|
|
516
|
+
}
|
|
517
|
+
return (row) => test(accessDotted(row.row, column.field));
|
|
518
|
+
}
|
|
519
|
+
return (row) => columns.some((entry2) => test(accessDotted(row.row, entry2.column.field)));
|
|
520
|
+
}
|
|
521
|
+
function matchesQuery(value, query) {
|
|
522
|
+
return compileFilterQuery(query)(value);
|
|
523
|
+
}
|
|
524
|
+
function filterQueryFor(filter, columnId) {
|
|
525
|
+
const entry = filter.find(
|
|
526
|
+
(candidate) => "query" in candidate && candidate.columnId === columnId
|
|
527
|
+
);
|
|
528
|
+
return entry?.query ?? null;
|
|
529
|
+
}
|
|
530
|
+
function setColumnFilter(filter, columnId, query) {
|
|
531
|
+
const index = filter.findIndex(
|
|
532
|
+
(entry) => !("combinator" in entry) && entry.columnId === columnId
|
|
533
|
+
);
|
|
534
|
+
if (query === "") {
|
|
535
|
+
return index === -1 ? filter : filter.filter((_entry, i) => i !== index);
|
|
536
|
+
}
|
|
537
|
+
const next = columnId === void 0 ? { query } : { columnId, query };
|
|
538
|
+
if (index === -1) {
|
|
539
|
+
return [...filter, next];
|
|
540
|
+
}
|
|
541
|
+
const existing = filter[index];
|
|
542
|
+
if (existing !== void 0 && "query" in existing && existing.query === query) {
|
|
543
|
+
return filter;
|
|
544
|
+
}
|
|
545
|
+
return filter.map((entry, i) => i === index ? next : entry);
|
|
546
|
+
}
|
|
547
|
+
function clearAllFilters(filter) {
|
|
548
|
+
return filter.length === 0 ? filter : [];
|
|
549
|
+
}
|
|
550
|
+
function filterRows(rows, filter, columns) {
|
|
551
|
+
if (filter.length === 0) {
|
|
552
|
+
return rows;
|
|
553
|
+
}
|
|
554
|
+
const byId = new Map(columns.map((entry) => [entry.id, entry.column]));
|
|
555
|
+
const active = filter.filter((entry) => isEntryUsable(entry, byId));
|
|
556
|
+
if (active.length === 0) {
|
|
557
|
+
return rows;
|
|
558
|
+
}
|
|
559
|
+
const compiled = active.map(
|
|
560
|
+
(entry) => compileFilterEntry(entry, byId, columns)
|
|
561
|
+
);
|
|
562
|
+
const filtered = rows.filter((row) => compiled.every((test) => test(row)));
|
|
563
|
+
return filtered.map(
|
|
564
|
+
(row, index) => row.rowIndex === index ? row : { ...row, rowIndex: index }
|
|
565
|
+
);
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
// src/util/rows.ts
|
|
569
|
+
function resolveShownRows(rows, filter, sort, columns) {
|
|
570
|
+
return sortRows(filterRows(rows, filter, columns), sort, columns);
|
|
571
|
+
}
|
|
240
572
|
export {
|
|
241
573
|
DEFAULT_COLUMN_SIZES,
|
|
242
574
|
KEYBOARD_STEP,
|
|
@@ -246,16 +578,37 @@ export {
|
|
|
246
578
|
applyColumnResize,
|
|
247
579
|
beginColumnResize,
|
|
248
580
|
clampColumnWidth,
|
|
581
|
+
clearAllFilters,
|
|
582
|
+
clearSelection,
|
|
583
|
+
compareValues,
|
|
249
584
|
defineColumnsFromRows,
|
|
585
|
+
diffSelection,
|
|
586
|
+
filterQueryFor,
|
|
587
|
+
filterRows,
|
|
250
588
|
fitColumnsToWidth,
|
|
251
589
|
getColumnId,
|
|
590
|
+
isSameCell,
|
|
591
|
+
matchesQuery,
|
|
252
592
|
moveColumnBefore,
|
|
253
593
|
movesColumn,
|
|
254
594
|
resolveColumnConstraints,
|
|
255
595
|
resolveColumnLabel,
|
|
256
596
|
resolveColumnWidths,
|
|
257
597
|
resolveDropBefore,
|
|
598
|
+
resolveRowId,
|
|
599
|
+
resolveShownRows,
|
|
600
|
+
selectAll,
|
|
601
|
+
selectCell,
|
|
602
|
+
selectOnly,
|
|
603
|
+
selectRange,
|
|
604
|
+
setColumnFilter,
|
|
258
605
|
sizeColumnToContent,
|
|
606
|
+
sortDirectionFor,
|
|
607
|
+
sortPriorityFor,
|
|
608
|
+
sortRows,
|
|
609
|
+
toggleCellSelection,
|
|
610
|
+
toggleColumnSort,
|
|
611
|
+
toggleSelection,
|
|
259
612
|
totalColumnWidth
|
|
260
613
|
};
|
|
261
614
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/util/grid.ts","../src/util/ordering.ts","../src/util/sizing.ts"],"sourcesContent":["import type { ColumnAlignment, ColumnDefinition, ColumnType } from \"../types\";\n\n/**\n * Narrows to a value worth drilling into. Mirrors the `LeafValue` union in\n * `../types`: arrays and `Date`s are cell values rather than groups of\n * columns, and `null` cannot be introspected. Keep the two in sync.\n */\nfunction isNested(value: unknown): value is Record<string, unknown> {\n return (\n typeof value === \"object\" &&\n value !== null &&\n !Array.isArray(value) &&\n !(value instanceof Date)\n );\n}\n\nfunction getType(value: unknown): ColumnType {\n if (value instanceof Date) return \"dateTime\";\n if (typeof value === \"string\") return \"string\";\n if (typeof value === \"number\") return \"number\";\n if (typeof value === \"boolean\") return \"boolean\";\n if (typeof value === \"bigint\") return \"number\";\n if (typeof value === \"symbol\") return \"string\";\n return \"string\";\n}\n\n/**\n * How a column of `type` aligns by default. Numeric types align right so that\n * their digits line up by place value; everything else reads from the left.\n */\nexport function alignmentForType(type: ColumnType): ColumnAlignment {\n switch (type) {\n case \"number\":\n case \"decimal\":\n case \"currency\":\n case \"percent\":\n return \"right\";\n default:\n return \"left\";\n }\n}\n\nfunction getAlignment(value: unknown): ColumnAlignment {\n return alignmentForType(getType(value));\n}\n\n/**\n * Derives one column per field across `rows`, in the order each field is first\n * seen — so a key that only appears in a later row still gets a column,\n * appended at the end. Nested objects contribute their non-object properties\n * as `\"Parent.Child\"`; anything deeper is skipped, as is the nested object\n * itself.\n *\n * Order follows `Object.entries`, which lists integer-like keys first\n * regardless of the order they were written in.\n */\nexport function defineColumnsFromRows<Row>(\n rows: readonly Row[],\n): readonly ColumnDefinition<Row>[] {\n const columns: ColumnDefinition<Row>[] = [];\n const seen = new Set<string>();\n\n function add(field: string, value: unknown): void {\n if (seen.has(field)) {\n return;\n }\n seen.add(field);\n columns.push({\n field,\n type: getType(value),\n alignment: getAlignment(value),\n });\n }\n\n for (const row of rows) {\n if (!isNested(row)) {\n continue;\n }\n for (const [key, value] of Object.entries(row)) {\n if (!isNested(value)) {\n add(key, value);\n continue;\n }\n for (const [nestedKey, nestedValue] of Object.entries(value)) {\n if (!isNested(nestedValue)) {\n add(`${key}.${nestedKey}`, nestedValue);\n }\n }\n }\n }\n\n return columns;\n}\n\n/**\n * The key state about a column is stored under, and the key it renders with.\n * Every such lookup goes through here so the two can never disagree.\n */\nexport function getColumnId<Row>(\n column: ColumnDefinition<Row, unknown>,\n): string {\n return column.id ?? column.field;\n}\n\n/**\n * What a column's header shows: its own `headerTemplate`, called first if it is\n * a function, or else a label read off the field path — `\"Application.Id\"`\n * reading as `\"Application Id\"`.\n */\nexport function resolveColumnLabel<Row, Node>(\n column: ColumnDefinition<Row, Node>,\n): Node | string {\n const { headerTemplate } = column;\n if (headerTemplate === undefined) {\n return column.field.split(\".\").join(\" \");\n }\n // `Node` is open, so it could itself be a function type and the `typeof`\n // check cannot narrow on its own. A header that is callable is the lazy\n // form by definition — no adapter binds `Node` to a function.\n return typeof headerTemplate === \"function\"\n ? (headerTemplate as () => Node)()\n : headerTemplate;\n}\n\n/**\n * Accesses a value at a dotted path, e.g. `foo.bar.baz`.\n *\n * @param obj The object to access\n * @param path Path to the value, e.g. `\"foo.bar\"`\n * @returns The value at `path`, or `undefined` if it doesn't exist\n */\nexport function accessDotted(obj: unknown, path: string): unknown {\n return path\n .split(\".\")\n .reduce((value, key) => (isNested(value) ? value[key] : undefined), obj);\n}\n","import type { ColumnDefinition, ColumnOrderState, DropSide } from \"../types\";\nimport { getColumnId } from \"./grid\";\n\n/**\n * Puts `columns` in the order `order` gives. Ids it lists come first, in that\n * order; columns it omits keep their position among the definitions and\n * follow. Ids naming no column are ignored.\n *\n * Tolerating both gaps is what lets a stored order outlive the column list it\n * was made against.\n */\nexport function applyColumnOrder<Row, Node>(\n columns: readonly ColumnDefinition<Row, Node>[],\n order: ColumnOrderState,\n): readonly ColumnDefinition<Row, Node>[] {\n if (order.length === 0) {\n return columns;\n }\n\n const byId = new Map(columns.map((column) => [getColumnId(column), column]));\n const listed = order\n .map((id) => byId.get(id))\n .filter((column) => column !== undefined);\n const taken = new Set(listed.map((column) => getColumnId(column)));\n\n return [\n ...listed,\n ...columns.filter((column) => !taken.has(getColumnId(column))),\n ];\n}\n\n/**\n * The order with `movedId` lifted out and put back in front of `beforeId`, or\n * at the end when that is `null`.\n *\n * Returns `orderedIds` itself when the move changes nothing, so callers can\n * treat an unchanged reference as \"no move happened\".\n */\nexport function moveColumnBefore(\n orderedIds: ColumnOrderState,\n movedId: string,\n beforeId: string | null,\n): ColumnOrderState {\n if (movedId === beforeId || !orderedIds.includes(movedId)) {\n return orderedIds;\n }\n\n const without = orderedIds.filter((id) => id !== movedId);\n // Taken after the removal: an index read before it would be one too far\n // right for every move that travels rightwards.\n const target = beforeId === null ? without.length : without.indexOf(beforeId);\n if (target === -1) {\n return orderedIds;\n }\n\n const next = [...without];\n next.splice(target, 0, movedId);\n return next.every((id, index) => id === orderedIds[index])\n ? orderedIds\n : next;\n}\n\n/**\n * Whether moving `movedId` in front of `beforeId` would rearrange anything, so\n * that a drop indicator promises exactly the moves that happen.\n *\n * Defined against `moveColumnBefore` rather than restating its conditions, so\n * the two cannot disagree about what a no-op is.\n */\nexport function movesColumn(\n orderedIds: ColumnOrderState,\n movedId: string,\n beforeId: string | null,\n): boolean {\n return moveColumnBefore(orderedIds, movedId, beforeId) !== orderedIds;\n}\n\n/**\n * The id a drop on `side` of `targetId` lands in front of, which is `null`\n * past the last column.\n *\n * \"After C\" and \"before D\" name the same gap, so collapsing both to one id\n * keeps a drop target a single nullable string.\n */\nexport function resolveDropBefore(\n orderedIds: ColumnOrderState,\n targetId: string,\n side: DropSide,\n): string | null {\n if (side === \"before\") {\n return targetId;\n }\n const index = orderedIds.indexOf(targetId);\n return index === -1 ? null : (orderedIds[index + 1] ?? null);\n}\n","import type {\n ColumnConstraints,\n ColumnDefinition,\n ColumnResizeSession,\n ColumnResolveOptions,\n ColumnSizeDefaults,\n ColumnSizingState,\n ResolvedColumn,\n} from \"../types\";\nimport { alignmentForType, getColumnId, resolveColumnLabel } from \"./grid\";\n\n/**\n * Applied to any column that does not size itself. The minimum is a width a\n * header stays legible at rather than a measured value — a column dragged\n * narrower than this reads as a rendering fault.\n */\nexport const DEFAULT_COLUMN_SIZES: ColumnSizeDefaults = {\n width: 150,\n minWidth: 40,\n maxWidth: Number.POSITIVE_INFINITY,\n};\n\n/** How far one arrow key press moves a column edge. */\nexport const KEYBOARD_STEP = 10;\n\nfunction withDefaults(\n defaults?: Partial<ColumnSizeDefaults>,\n): ColumnSizeDefaults {\n return { ...DEFAULT_COLUMN_SIZES, ...defaults };\n}\n\n/**\n * The bounds `column` may be sized between. Resolved in one place so that a\n * drag, a clamp and an auto-fit cannot disagree about a column's limits.\n */\nexport function resolveColumnConstraints<Row>(\n column: ColumnDefinition<Row, unknown>,\n defaults?: Partial<ColumnSizeDefaults>,\n): ColumnConstraints {\n const resolved = withDefaults(defaults);\n return {\n minWidth: column.minWidth ?? resolved.minWidth,\n maxWidth: column.maxWidth ?? resolved.maxWidth,\n };\n}\n\n/** Holds `width` within `constraints`. */\nexport function clampColumnWidth(\n width: number,\n constraints: ColumnConstraints,\n): number {\n return Math.min(Math.max(width, constraints.minWidth), constraints.maxWidth);\n}\n\n/**\n * Pairs each column with everything needed to render it — the width it sits\n * at, the label its header shows, whether it may be resized and how its cells\n * align. Widths take the first of the sizing state, the column's own `width`,\n * and the default, then clamp.\n *\n * The sizing state winning means a user's drag outlives a re-render; a column\n * absent from it still tracks a `width` edited in the definition.\n */\nexport function resolveColumnWidths<Row, Node>(\n columns: readonly ColumnDefinition<Row, Node>[],\n sizing: ColumnSizingState,\n options?: ColumnResolveOptions,\n): readonly ResolvedColumn<Row, Node>[] {\n const resolved = withDefaults(options?.sizes);\n\n return columns.map((column) => {\n const id = getColumnId(column);\n const stored = sizing[id];\n\n return {\n column,\n id,\n sized: stored !== undefined,\n width: clampColumnWidth(\n stored ?? column.width ?? resolved.width,\n resolveColumnConstraints(column, resolved),\n ),\n label: resolveColumnLabel(column),\n resizable: column.resizable ?? options?.resizable ?? false,\n reorderable: column.reorderable ?? options?.reorderable ?? false,\n alignment: column.alignment ?? alignmentForType(column.type ?? \"string\"),\n };\n });\n}\n\nexport function totalColumnWidth<Row, Node>(\n resolved: readonly ResolvedColumn<Row, Node>[],\n): number {\n return resolved.reduce((total, entry) => total + entry.width, 0);\n}\n\n/**\n * Opens a resize. The session captures its constraints and starting point up\n * front, so applying a pointer position later is arithmetic on numbers alone —\n * which is what keeps the drag testable without a DOM.\n *\n * @param startPosition Where the pointer went down, on the axis being dragged.\n */\nexport function beginColumnResize<Row>(\n column: ColumnDefinition<Row, unknown>,\n startWidth: number,\n startPosition: number,\n defaults?: Partial<ColumnSizeDefaults>,\n): ColumnResizeSession {\n return {\n columnId: getColumnId(column),\n startWidth,\n startPosition,\n constraints: resolveColumnConstraints(column, defaults),\n };\n}\n\n/**\n * The width the dragged column takes with the pointer at `position`. The delta\n * is signed, so a right-to-left adapter negates it and nothing else changes.\n */\nexport function applyColumnResize(\n session: ColumnResizeSession,\n position: number,\n): number {\n return clampColumnWidth(\n session.startWidth + (position - session.startPosition),\n session.constraints,\n );\n}\n\ninterface SizingEntry<Row, Node> {\n entry: ResolvedColumn<Row, Node>;\n width: number;\n bound: number;\n adjustable: boolean;\n}\n\n/**\n * Whether `item` has reached `bound` in the direction it moves toward — above\n * it for a column growing to a `maxWidth`, below it for one shrinking to a\n * `minWidth`.\n */\nfunction reachedBound<Row, Node>(\n width: number,\n item: SizingEntry<Row, Node>,\n): boolean {\n return item.bound > item.width ? width >= item.bound : width <= item.bound;\n}\n\n/**\n * Moves every adjustable entry's `width` toward `bound` by its proportional\n * share of `remaining`'s current surplus or deficit, looping so a column\n * clamped at its bound in one pass frees the remainder for the others to\n * absorb in the next.\n */\nfunction redistribute<Row, Node>(\n entries: readonly SizingEntry<Row, Node>[],\n remaining: (sum: number) => number,\n): void {\n for (;;) {\n const movable = entries.filter(\n (item) => item.adjustable && !reachedBound(item.width, item),\n );\n const left = remaining(entries.reduce((t, i) => t + i.width, 0));\n if (left <= 0 || movable.length === 0) {\n break;\n }\n\n const base = movable.reduce((total, item) => total + item.width, 0);\n let clamped = false;\n\n for (const item of movable) {\n const share =\n base === 0 ? left / movable.length : (left * item.width) / base;\n const next = item.width + (item.bound > item.width ? share : -share);\n\n if (reachedBound(next, item)) {\n item.width = item.bound;\n clamped = true;\n } else {\n item.width = next;\n }\n }\n\n if (!clamped) {\n break;\n }\n }\n}\n\n/**\n * Fits columns to `availableWidth`: grows them to fill leftover space, or\n * shrinks them to relieve an overflow, in both cases proportionally to each\n * column's current width. Columns the user has sized are left alone in\n * either direction, so dragging one column neither rubber-bands it back nor\n * has it absorb a squeeze meant for the rest.\n *\n * Growing stops at each column's `maxWidth`; shrinking stops at its\n * `minWidth`. If shrinking every adjustable column to its floor still does\n * not reach `availableWidth`, the columns are returned untouched and the\n * container scrolls rather than crushing them further.\n */\nexport function fitColumnsToWidth<Row, Node>(\n resolved: readonly ResolvedColumn<Row, Node>[],\n availableWidth: number,\n defaults?: Partial<ColumnSizeDefaults>,\n): readonly ResolvedColumn<Row, Node>[] {\n const target = Math.floor(availableWidth);\n const total = totalColumnWidth(resolved);\n if (resolved.length === 0 || total === target) {\n return resolved;\n }\n\n const growing = total < target;\n const entries: SizingEntry<Row, Node>[] = resolved.map((entry) => {\n const constraints = resolveColumnConstraints(entry.column, defaults);\n return {\n entry,\n width: entry.width,\n bound: growing ? constraints.maxWidth : constraints.minWidth,\n adjustable: !entry.sized,\n };\n });\n\n if (!growing) {\n // Only unsized columns can be shrunk, so if they alone cannot reach the\n // target the grid genuinely cannot fit and should scroll, unchanged.\n const floor = entries.reduce(\n (sum, item) => sum + (item.adjustable ? item.bound : item.width),\n 0,\n );\n if (floor > target) {\n return resolved;\n }\n }\n\n redistribute(entries, (sum) => (growing ? target - sum : sum - target));\n\n // Fractional widths would leave the table a sub-pixel off its container and\n // show a scrollbar that never goes away, so round every column toward the\n // target and hand the remaining pixels back out (growing) or claw them\n // back (shrinking) one at a time.\n const rounded = entries.map((item) =>\n growing ? Math.floor(item.width) : Math.ceil(item.width),\n );\n let remainder = target - rounded.reduce((total, width) => total + width, 0);\n\n return entries.map((item, index) => {\n let width = rounded[index] ?? item.width;\n // Rounding toward the target can only ever leave it short when growing\n // (floor) or over when shrinking (ceil), so only one direction is ever\n // live here — but each still needs room left before its own bound.\n const hasRoom = growing ? width < item.bound : width > item.bound;\n\n if (remainder !== 0 && item.adjustable && hasRoom) {\n width += growing ? 1 : -1;\n remainder += growing ? -1 : 1;\n }\n\n return { ...item.entry, width };\n });\n}\n\n/**\n * The width a column needs to show `measuredWidth` of content.\n *\n * `padding` is an allowance on top of the measurement, since a column sized to\n * exactly its content can still round down into an ellipsis.\n */\nexport function sizeColumnToContent(\n measuredWidth: number,\n constraints: ColumnConstraints,\n padding = 2,\n): number {\n return clampColumnWidth(Math.ceil(measuredWidth + padding), constraints);\n}\n"],"mappings":"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","names":["total"]}
|
|
1
|
+
{"version":3,"sources":["../src/util/grid.ts","../src/util/ordering.ts","../src/util/selection.ts","../src/util/sizing.ts","../src/util/sorting.ts","../src/util/filtering.ts","../src/util/rows.ts"],"sourcesContent":["import type { ColumnAlignment, ColumnDefinition, ColumnType } from \"../types\";\n\n/**\n * Narrows to a value worth drilling into. Mirrors the `LeafValue` union in\n * `../types`: arrays and `Date`s are cell values rather than groups of\n * columns, and `null` cannot be introspected. Keep the two in sync.\n */\nfunction isNested(value: unknown): value is Record<string, unknown> {\n return (\n typeof value === \"object\" &&\n value !== null &&\n !Array.isArray(value) &&\n !(value instanceof Date)\n );\n}\n\nfunction getType(value: unknown): ColumnType {\n if (value instanceof Date) return \"dateTime\";\n if (typeof value === \"string\") return \"string\";\n if (typeof value === \"number\") return \"number\";\n if (typeof value === \"boolean\") return \"boolean\";\n if (typeof value === \"bigint\") return \"number\";\n if (typeof value === \"symbol\") return \"string\";\n return \"string\";\n}\n\n/**\n * How a column of `type` aligns by default. Numeric types align right so that\n * their digits line up by place value; everything else reads from the left.\n */\nexport function alignmentForType(type: ColumnType): ColumnAlignment {\n switch (type) {\n case \"number\":\n case \"decimal\":\n case \"currency\":\n case \"percent\":\n return \"right\";\n default:\n return \"left\";\n }\n}\n\nfunction getAlignment(value: unknown): ColumnAlignment {\n return alignmentForType(getType(value));\n}\n\n/**\n * Derives one column per field across `rows`, in the order each field is first\n * seen — so a key that only appears in a later row still gets a column,\n * appended at the end. Nested objects contribute their non-object properties\n * as `\"Parent.Child\"`; anything deeper is skipped, as is the nested object\n * itself.\n *\n * Order follows `Object.entries`, which lists integer-like keys first\n * regardless of the order they were written in.\n */\nexport function defineColumnsFromRows<Row>(\n rows: readonly Row[],\n): readonly ColumnDefinition<Row>[] {\n const columns: ColumnDefinition<Row>[] = [];\n const seen = new Set<string>();\n\n function add(field: string, value: unknown): void {\n if (seen.has(field)) {\n return;\n }\n seen.add(field);\n columns.push({\n field,\n type: getType(value),\n alignment: getAlignment(value),\n });\n }\n\n for (const row of rows) {\n if (!isNested(row)) {\n continue;\n }\n for (const [key, value] of Object.entries(row)) {\n if (!isNested(value)) {\n add(key, value);\n continue;\n }\n for (const [nestedKey, nestedValue] of Object.entries(value)) {\n if (!isNested(nestedValue)) {\n add(`${key}.${nestedKey}`, nestedValue);\n }\n }\n }\n }\n\n return columns;\n}\n\n/**\n * The key state about a column is stored under, and the key it renders with.\n * Every such lookup goes through here so the two can never disagree.\n */\nexport function getColumnId<Row>(\n column: ColumnDefinition<Row, unknown>,\n): string {\n return column.id ?? column.field;\n}\n\n/**\n * The key state about a row is stored under, and the key it renders with — the\n * row's counterpart to `getColumnId`, and for the same reason: every such\n * lookup goes through here so no two can disagree.\n *\n * Falls back to the position so a grid given no `getRowId` still works. That\n * fallback ties a selection to where a row sits rather than to the row, so\n * anything that sorts, filters or pages its data has to supply one — otherwise\n * a re-sort leaves the same positions selected under different rows.\n */\nexport function resolveRowId<Row>(\n row: Row,\n index: number,\n getRowId?: (row: Row, index: number) => string,\n): string {\n return getRowId?.(row, index) ?? String(index);\n}\n\n/**\n * What a column's header shows: its own `headerTemplate`, called first if it is\n * a function, or else a label read off the field path — `\"Application.Id\"`\n * reading as `\"Application Id\"`.\n */\nexport function resolveColumnLabel<Row, Node>(\n column: ColumnDefinition<Row, Node>,\n): Node | string {\n const { headerTemplate } = column;\n if (headerTemplate === undefined) {\n return column.field.split(\".\").join(\" \");\n }\n // `Node` is open, so it could itself be a function type and the `typeof`\n // check cannot narrow on its own. A header that is callable is the lazy\n // form by definition — no adapter binds `Node` to a function.\n return typeof headerTemplate === \"function\"\n ? (headerTemplate as () => Node)()\n : headerTemplate;\n}\n\n/**\n * Accesses a value at a dotted path, e.g. `foo.bar.baz`.\n *\n * @param obj The object to access\n * @param path Path to the value, e.g. `\"foo.bar\"`\n * @returns The value at `path`, or `undefined` if it doesn't exist\n */\nexport function accessDotted(obj: unknown, path: string): unknown {\n return path\n .split(\".\")\n .reduce((value, key) => (isNested(value) ? value[key] : undefined), obj);\n}\n","import type { ColumnDefinition, ColumnOrderState, DropSide } from \"../types\";\nimport { getColumnId } from \"./grid\";\n\n/**\n * Puts `columns` in the order `order` gives. Ids it lists come first, in that\n * order; columns it omits keep their position among the definitions and\n * follow. Ids naming no column are ignored.\n *\n * Tolerating both gaps is what lets a stored order outlive the column list it\n * was made against.\n */\nexport function applyColumnOrder<Row, Node>(\n columns: readonly ColumnDefinition<Row, Node>[],\n order: ColumnOrderState,\n): readonly ColumnDefinition<Row, Node>[] {\n if (order.length === 0) {\n return columns;\n }\n\n const byId = new Map(columns.map((column) => [getColumnId(column), column]));\n const listed = order\n .map((id) => byId.get(id))\n .filter((column) => column !== undefined);\n const taken = new Set(listed.map((column) => getColumnId(column)));\n\n return [\n ...listed,\n ...columns.filter((column) => !taken.has(getColumnId(column))),\n ];\n}\n\n/**\n * The order with `movedId` lifted out and put back in front of `beforeId`, or\n * at the end when that is `null`.\n *\n * Returns `orderedIds` itself when the move changes nothing, so callers can\n * treat an unchanged reference as \"no move happened\".\n */\nexport function moveColumnBefore(\n orderedIds: ColumnOrderState,\n movedId: string,\n beforeId: string | null,\n): ColumnOrderState {\n if (movedId === beforeId || !orderedIds.includes(movedId)) {\n return orderedIds;\n }\n\n const without = orderedIds.filter((id) => id !== movedId);\n // Taken after the removal: an index read before it would be one too far\n // right for every move that travels rightwards.\n const target = beforeId === null ? without.length : without.indexOf(beforeId);\n if (target === -1) {\n return orderedIds;\n }\n\n const next = [...without];\n next.splice(target, 0, movedId);\n return next.every((id, index) => id === orderedIds[index])\n ? orderedIds\n : next;\n}\n\n/**\n * Whether moving `movedId` in front of `beforeId` would rearrange anything, so\n * that a drop indicator promises exactly the moves that happen.\n *\n * Defined against `moveColumnBefore` rather than restating its conditions, so\n * the two cannot disagree about what a no-op is.\n */\nexport function movesColumn(\n orderedIds: ColumnOrderState,\n movedId: string,\n beforeId: string | null,\n): boolean {\n return moveColumnBefore(orderedIds, movedId, beforeId) !== orderedIds;\n}\n\n/**\n * The id a drop on `side` of `targetId` lands in front of, which is `null`\n * past the last column.\n *\n * \"After C\" and \"before D\" name the same gap, so collapsing both to one id\n * keeps a drop target a single nullable string.\n */\nexport function resolveDropBefore(\n orderedIds: ColumnOrderState,\n targetId: string,\n side: DropSide,\n): string | null {\n if (side === \"before\") {\n return targetId;\n }\n const index = orderedIds.indexOf(targetId);\n return index === -1 ? null : (orderedIds[index + 1] ?? null);\n}\n","import type {\n CellSelectionMode,\n CellSelectionState,\n SelectedCellRef,\n SelectionDiff,\n SelectionMode,\n SelectionState,\n} from \"../types\";\n\n/** Nothing changed, so nothing to report — shared to save an allocation. */\nconst NO_CHANGE: SelectionDiff = { added: [], removed: [] };\n\n/**\n * Whether two selections hold the same ids in the same order.\n *\n * Order counts because the selection's own order is what makes `at(-1)` the\n * most recently selected; a comparison that ignored it would have a transform\n * return the old reference after a reordering that a consumer can observe.\n */\nfunction sameSelection(a: SelectionState, b: SelectionState): boolean {\n return a.length === b.length && a.every((id, index) => id === b[index]);\n}\n\n/**\n * The selection with `id` added if absent and removed if present — a\n * Ctrl-click, or Space on the focused member.\n *\n * The mode is enforced here rather than by each caller, so `\"single\"` cannot\n * accumulate no matter which interaction reaches it. Returns `selection`\n * itself when `mode` is `false`, keeping the unchanged-reference contract\n * `moveColumnBefore` set: a caller treats an unchanged reference as \"nothing\n * happened\" and neither renders nor reports.\n */\nexport function toggleSelection(\n selection: SelectionState,\n id: string,\n mode: SelectionMode,\n): SelectionState {\n if (mode === false) {\n return selection;\n }\n if (selection.includes(id)) {\n return selection.filter((selectedId) => selectedId !== id);\n }\n return mode === \"single\" ? [id] : [...selection, id];\n}\n\n/**\n * The selection reduced to `id` alone — a plain click, which discards what\n * came before rather than adding to it.\n *\n * Never deselects, so clicking the one selected row leaves it selected.\n * Letting go of it is Ctrl-click's job, and `toggleSelection`'s.\n */\nexport function selectOnly(\n selection: SelectionState,\n id: string,\n mode: SelectionMode,\n): SelectionState {\n if (mode === false) {\n return selection;\n }\n return selection.length === 1 && selection[0] === id ? selection : [id];\n}\n\n/**\n * The selection replaced by every id from `anchorId` to `focusId` inclusive — a\n * Shift-click. The two may be given in either order.\n *\n * Spans `orderedIds`, the order as displayed, rather than the selection's own:\n * the range a user drew across the screen is the range they mean, whatever\n * order they clicked its ends in. Degrades to `selectOnly` under `\"single\"`,\n * where a range has nowhere to go.\n */\nexport function selectRange(\n selection: SelectionState,\n orderedIds: readonly string[],\n anchorId: string,\n focusId: string,\n mode: SelectionMode,\n): SelectionState {\n if (mode !== \"multiple\") {\n return selectOnly(selection, focusId, mode);\n }\n\n const anchor = orderedIds.indexOf(anchorId);\n const focus = orderedIds.indexOf(focusId);\n if (anchor === -1 || focus === -1) {\n return selection;\n }\n\n const next = orderedIds.slice(\n Math.min(anchor, focus),\n Math.max(anchor, focus) + 1,\n );\n return sameSelection(selection, next) ? selection : next;\n}\n\n/**\n * Every id in `orderedIds`, or `selection` untouched under any mode but\n * `\"multiple\"` — there is no selecting all of one thing.\n */\nexport function selectAll(\n selection: SelectionState,\n orderedIds: readonly string[],\n mode: SelectionMode,\n): SelectionState {\n if (mode !== \"multiple\") {\n return selection;\n }\n return sameSelection(selection, orderedIds) ? selection : orderedIds;\n}\n\nexport function clearSelection(selection: SelectionState): SelectionState {\n return selection.length === 0 ? selection : [];\n}\n\n/**\n * What one transition added and what it removed.\n *\n * Defined here rather than in an adapter so that every callback reporting a\n * selection change reads one answer, and a select and a deselect fired for the\n * same interaction cannot disagree about which it was.\n */\nexport function diffSelection(\n previous: SelectionState,\n next: SelectionState,\n): SelectionDiff {\n if (previous === next) {\n return NO_CHANGE;\n }\n\n const before = new Set(previous);\n const after = new Set(next);\n return {\n added: next.filter((id) => !before.has(id)),\n removed: previous.filter((id) => !after.has(id)),\n };\n}\n\nexport function isSameCell(\n a: CellSelectionState,\n b: CellSelectionState,\n): boolean {\n if (a === null || b === null) {\n return a === b;\n }\n return a.rowId === b.rowId && a.columnId === b.columnId;\n}\n\n/**\n * The cell selection moved to `cell` — a plain click. Idempotent: clicking the\n * selected cell again leaves it selected, matching `selectOnly`.\n */\nexport function selectCell(\n selection: CellSelectionState,\n cell: SelectedCellRef,\n mode: CellSelectionMode,\n): CellSelectionState {\n if (mode === false || isSameCell(selection, cell)) {\n return selection;\n }\n return cell;\n}\n\n/**\n * The cell selection moved to `cell`, or cleared when it is already there — a\n * Ctrl-click, matching `toggleSelection`.\n */\nexport function toggleCellSelection(\n selection: CellSelectionState,\n cell: SelectedCellRef,\n mode: CellSelectionMode,\n): CellSelectionState {\n if (mode === false) {\n return selection;\n }\n return isSameCell(selection, cell) ? null : cell;\n}\n","import type {\n ColumnConstraints,\n ColumnDefinition,\n ColumnResizeSession,\n ColumnResolveOptions,\n ColumnSizeDefaults,\n ColumnSizingState,\n ResolvedColumn,\n} from \"../types\";\nimport { alignmentForType, getColumnId, resolveColumnLabel } from \"./grid\";\n\n/**\n * Applied to any column that does not size itself. The minimum is a width a\n * header stays legible at rather than a measured value — a column dragged\n * narrower than this reads as a rendering fault.\n */\nexport const DEFAULT_COLUMN_SIZES: ColumnSizeDefaults = {\n width: 150,\n minWidth: 40,\n maxWidth: Number.POSITIVE_INFINITY,\n};\n\n/** How far one arrow key press moves a column edge. */\nexport const KEYBOARD_STEP = 10;\n\nfunction withDefaults(\n defaults?: Partial<ColumnSizeDefaults>,\n): ColumnSizeDefaults {\n return { ...DEFAULT_COLUMN_SIZES, ...defaults };\n}\n\n/**\n * The bounds `column` may be sized between. Resolved in one place so that a\n * drag, a clamp and an auto-fit cannot disagree about a column's limits.\n */\nexport function resolveColumnConstraints<Row>(\n column: ColumnDefinition<Row, unknown>,\n defaults?: Partial<ColumnSizeDefaults>,\n): ColumnConstraints {\n const resolved = withDefaults(defaults);\n return {\n minWidth: column.minWidth ?? resolved.minWidth,\n maxWidth: column.maxWidth ?? resolved.maxWidth,\n };\n}\n\n/** Holds `width` within `constraints`. */\nexport function clampColumnWidth(\n width: number,\n constraints: ColumnConstraints,\n): number {\n return Math.min(Math.max(width, constraints.minWidth), constraints.maxWidth);\n}\n\n/**\n * Pairs each column with everything needed to render it — the width it sits\n * at, the label its header shows, whether it may be resized and how its cells\n * align. Widths take the first of the sizing state, the column's own `width`,\n * and the default, then clamp.\n *\n * The sizing state winning means a user's drag outlives a re-render; a column\n * absent from it still tracks a `width` edited in the definition.\n */\nexport function resolveColumnWidths<Row, Node>(\n columns: readonly ColumnDefinition<Row, Node>[],\n sizing: ColumnSizingState,\n options?: ColumnResolveOptions,\n): readonly ResolvedColumn<Row, Node>[] {\n const resolved = withDefaults(options?.sizes);\n\n return columns.map((column) => {\n const id = getColumnId(column);\n const stored = sizing[id];\n\n return {\n column,\n id,\n sized: stored !== undefined,\n width: clampColumnWidth(\n stored ?? column.width ?? resolved.width,\n resolveColumnConstraints(column, resolved),\n ),\n label: resolveColumnLabel(column),\n resizable: column.resizable ?? options?.resizable ?? false,\n reorderable: column.reorderable ?? options?.reorderable ?? false,\n alignment: column.alignment ?? alignmentForType(column.type ?? \"string\"),\n };\n });\n}\n\nexport function totalColumnWidth<Row, Node>(\n resolved: readonly ResolvedColumn<Row, Node>[],\n): number {\n return resolved.reduce((total, entry) => total + entry.width, 0);\n}\n\n/**\n * Opens a resize. The session captures its constraints and starting point up\n * front, so applying a pointer position later is arithmetic on numbers alone —\n * which is what keeps the drag testable without a DOM.\n *\n * @param startPosition Where the pointer went down, on the axis being dragged.\n */\nexport function beginColumnResize<Row>(\n column: ColumnDefinition<Row, unknown>,\n startWidth: number,\n startPosition: number,\n defaults?: Partial<ColumnSizeDefaults>,\n): ColumnResizeSession {\n return {\n columnId: getColumnId(column),\n startWidth,\n startPosition,\n constraints: resolveColumnConstraints(column, defaults),\n };\n}\n\n/**\n * The width the dragged column takes with the pointer at `position`. The delta\n * is signed, so a right-to-left adapter negates it and nothing else changes.\n */\nexport function applyColumnResize(\n session: ColumnResizeSession,\n position: number,\n): number {\n return clampColumnWidth(\n session.startWidth + (position - session.startPosition),\n session.constraints,\n );\n}\n\ninterface SizingEntry<Row, Node> {\n entry: ResolvedColumn<Row, Node>;\n width: number;\n bound: number;\n adjustable: boolean;\n}\n\n/**\n * Whether `item` has reached `bound` in the direction it moves toward — above\n * it for a column growing to a `maxWidth`, below it for one shrinking to a\n * `minWidth`.\n */\nfunction reachedBound<Row, Node>(\n width: number,\n item: SizingEntry<Row, Node>,\n): boolean {\n return item.bound > item.width ? width >= item.bound : width <= item.bound;\n}\n\n/**\n * Moves every adjustable entry's `width` toward `bound` by its proportional\n * share of `remaining`'s current surplus or deficit, looping so a column\n * clamped at its bound in one pass frees the remainder for the others to\n * absorb in the next.\n */\nfunction redistribute<Row, Node>(\n entries: readonly SizingEntry<Row, Node>[],\n remaining: (sum: number) => number,\n): void {\n for (;;) {\n const movable = entries.filter(\n (item) => item.adjustable && !reachedBound(item.width, item),\n );\n const left = remaining(entries.reduce((t, i) => t + i.width, 0));\n if (left <= 0 || movable.length === 0) {\n break;\n }\n\n const base = movable.reduce((total, item) => total + item.width, 0);\n let clamped = false;\n\n for (const item of movable) {\n const share =\n base === 0 ? left / movable.length : (left * item.width) / base;\n const next = item.width + (item.bound > item.width ? share : -share);\n\n if (reachedBound(next, item)) {\n item.width = item.bound;\n clamped = true;\n } else {\n item.width = next;\n }\n }\n\n if (!clamped) {\n break;\n }\n }\n}\n\n/**\n * Fits columns to `availableWidth`: grows them to fill leftover space, or\n * shrinks them to relieve an overflow, in both cases proportionally to each\n * column's current width. Columns the user has sized are left alone in\n * either direction, so dragging one column neither rubber-bands it back nor\n * has it absorb a squeeze meant for the rest.\n *\n * Growing stops at each column's `maxWidth`; shrinking stops at its\n * `minWidth`. If shrinking every adjustable column to its floor still does\n * not reach `availableWidth`, the columns are returned untouched and the\n * container scrolls rather than crushing them further.\n */\nexport function fitColumnsToWidth<Row, Node>(\n resolved: readonly ResolvedColumn<Row, Node>[],\n availableWidth: number,\n defaults?: Partial<ColumnSizeDefaults>,\n): readonly ResolvedColumn<Row, Node>[] {\n const target = Math.floor(availableWidth);\n const total = totalColumnWidth(resolved);\n if (resolved.length === 0 || total === target) {\n return resolved;\n }\n\n const growing = total < target;\n const entries: SizingEntry<Row, Node>[] = resolved.map((entry) => {\n const constraints = resolveColumnConstraints(entry.column, defaults);\n return {\n entry,\n width: entry.width,\n bound: growing ? constraints.maxWidth : constraints.minWidth,\n adjustable: !entry.sized,\n };\n });\n\n if (!growing) {\n // Only unsized columns can be shrunk, so if they alone cannot reach the\n // target the grid genuinely cannot fit and should scroll, unchanged.\n const floor = entries.reduce(\n (sum, item) => sum + (item.adjustable ? item.bound : item.width),\n 0,\n );\n if (floor > target) {\n return resolved;\n }\n }\n\n redistribute(entries, (sum) => (growing ? target - sum : sum - target));\n\n // Fractional widths would leave the table a sub-pixel off its container and\n // show a scrollbar that never goes away, so round every column toward the\n // target and hand the remaining pixels back out (growing) or claw them\n // back (shrinking) one at a time.\n const rounded = entries.map((item) =>\n growing ? Math.floor(item.width) : Math.ceil(item.width),\n );\n let remainder = target - rounded.reduce((total, width) => total + width, 0);\n\n return entries.map((item, index) => {\n let width = rounded[index] ?? item.width;\n // Rounding toward the target can only ever leave it short when growing\n // (floor) or over when shrinking (ceil), so only one direction is ever\n // live here — but each still needs room left before its own bound.\n const hasRoom = growing ? width < item.bound : width > item.bound;\n\n if (remainder !== 0 && item.adjustable && hasRoom) {\n width += growing ? 1 : -1;\n remainder += growing ? -1 : 1;\n }\n\n return { ...item.entry, width };\n });\n}\n\n/**\n * The width a column needs to show `measuredWidth` of content.\n *\n * `padding` is an allowance on top of the measurement, since a column sized to\n * exactly its content can still round down into an ellipsis.\n */\nexport function sizeColumnToContent(\n measuredWidth: number,\n constraints: ColumnConstraints,\n padding = 2,\n): number {\n return clampColumnWidth(Math.ceil(measuredWidth + padding), constraints);\n}\n","import type {\n ColumnSortState,\n ColumnType,\n ResolvedColumn,\n ResolvedRow,\n SortDirection,\n} from \"../types\";\nimport { accessDotted } from \"./grid\";\n\nfunction isEmpty(value: unknown): boolean {\n return value === null || value === undefined;\n}\n\n/** A date/time value converted to a comparable instant. */\nfunction toTime(value: unknown): number {\n return value instanceof Date\n ? value.getTime()\n : new Date(String(value)).getTime();\n}\n\n/**\n * Compares two cell values the way `type` implies — numerically for the\n * numeric types, chronologically for the date/time types, lexically\n * otherwise. Defaults to string comparison when `type` is absent, mirroring\n * `ColumnDefinition.type`'s own default.\n *\n * `null`/`undefined` sort after every value on both sides. Used directly,\n * that ordering is fixed; `sortRows` applies it directionally without\n * disturbing it — see its own doc comment.\n */\nexport function compareValues(\n a: unknown,\n b: unknown,\n type?: ColumnType,\n): number {\n if (isEmpty(a)) {\n return isEmpty(b) ? 0 : 1;\n }\n if (isEmpty(b)) {\n return -1;\n }\n\n switch (type) {\n case \"number\":\n case \"decimal\":\n case \"currency\":\n case \"percent\":\n return Number(a) - Number(b);\n case \"boolean\":\n return Number(Boolean(a)) - Number(Boolean(b));\n case \"date\":\n case \"dateTime\":\n case \"time\":\n return toTime(a) - toTime(b);\n default:\n return String(a).localeCompare(String(b));\n }\n}\n\n/** This column's direction in `sort`, or `null` outside it. */\nexport function sortDirectionFor(\n sort: ColumnSortState,\n columnId: string,\n): SortDirection | null {\n return sort.find((entry) => entry.columnId === columnId)?.direction ?? null;\n}\n\n/** This column's 1-based place in `sort`, or `null` outside it. */\nexport function sortPriorityFor(\n sort: ColumnSortState,\n columnId: string,\n): number | null {\n const index = sort.findIndex((entry) => entry.columnId === columnId);\n return index === -1 ? null : index + 1;\n}\n\n/**\n * The sort with `columnId` cycled to its next direction — none → asc → desc →\n * none.\n *\n * `stack: false` (a plain click) means \"sort by only this column\": unless\n * `columnId` is already the sole entry, in which case its own cycle just\n * continues, it collapses `sort` down to `columnId` alone at `\"asc\"`,\n * regardless of any direction it held as part of a larger stack.\n *\n * `stack: true` (a Shift-click) cycles or appends `columnId` in place without\n * disturbing the rest of the stack; cycling it back to `\"none\"` removes it\n * and closes the gap, leaving the others in their existing order.\n */\nexport function toggleColumnSort(\n sort: ColumnSortState,\n columnId: string,\n options: { readonly stack: boolean },\n): ColumnSortState {\n const index = sort.findIndex((entry) => entry.columnId === columnId);\n const isSoleEntry = sort.length === 1 && index === 0;\n\n if (!options.stack && !isSoleEntry) {\n return [{ columnId, direction: \"asc\" }];\n }\n\n const current = index === -1 ? undefined : sort[index]?.direction;\n const next: SortDirection | null =\n current === undefined ? \"asc\" : current === \"asc\" ? \"desc\" : null;\n\n if (next === null) {\n return sort.filter((entry) => entry.columnId !== columnId);\n }\n if (index === -1) {\n return [...sort, { columnId, direction: next }];\n }\n return sort.map((entry, i) =>\n i === index ? { columnId, direction: next } : entry,\n );\n}\n\n/**\n * `rows` ordered by `sort` — each entry read against `columns` for its\n * `field` and `type` — with `rowIndex` renumbered to match the new\n * positions, the invariant the rest of the grid relies on for keyboard\n * navigation and `aria-rowindex`.\n *\n * Stable (`Array.prototype.sort` has been stable since ES2019), so rows\n * equal under every active key keep their relative order. A `null`/\n * `undefined` value sorts last under either direction — resolved before\n * `compareValues` is asked, and left out of the direction flip below, so\n * that toggling a column reorders the values it holds rather than moving its\n * gaps to the front.\n *\n * Returns `rows` itself, untouched, when `sort` is empty or names only\n * columns absent from `columns` — a stored sort tolerates a column removed\n * from the definition the same way a stored `ColumnOrderState` does.\n */\nexport function sortRows<Row>(\n rows: readonly ResolvedRow<Row>[],\n sort: ColumnSortState,\n columns: readonly ResolvedColumn<Row, unknown>[],\n): readonly ResolvedRow<Row>[] {\n if (sort.length === 0) {\n return rows;\n }\n\n const byId = new Map(columns.map((entry) => [entry.id, entry.column]));\n const active = sort.filter((entry) => byId.has(entry.columnId));\n if (active.length === 0) {\n return rows;\n }\n\n const sorted = [...rows].sort((a, b) => {\n for (const { columnId, direction } of active) {\n const column = byId.get(columnId);\n if (column === undefined) {\n continue;\n }\n const left = accessDotted(a.row, column.field);\n const right = accessDotted(b.row, column.field);\n const leftEmpty = isEmpty(left);\n const rightEmpty = isEmpty(right);\n if (leftEmpty || rightEmpty) {\n if (leftEmpty && rightEmpty) {\n continue;\n }\n return leftEmpty ? 1 : -1;\n }\n\n const result = compareValues(left, right, column.type);\n if (result !== 0) {\n return direction === \"asc\" ? result : -result;\n }\n }\n return 0;\n });\n\n return sorted.map((row, index) =>\n row.rowIndex === index ? row : { ...row, rowIndex: index },\n );\n}\n","import type {\n ColumnDefinition,\n ColumnType,\n FieldPath,\n FilterEntry,\n FilterState,\n ResolvedColumn,\n ResolvedRow,\n TextFilterEntry,\n} from \"../types\";\nimport { accessDotted } from \"./grid\";\n\ninterface FilterPattern {\n mode: \"exact\" | \"contains\" | \"startsWith\" | \"endsWith\";\n text: string; // anchors stripped, lowercased once\n}\n\n/**\n * Reads a query's leading/trailing `%` into a match mode — see\n * `TextFilterEntry`'s own doc comment for the four shapes. Only the first\n * and last characters are read as anchors — a `%` anywhere else in the\n * query is a literal character, not a wildcard.\n */\nfunction parseFilterPattern(query: string): FilterPattern {\n const start = query.startsWith(\"%\");\n const end = query.endsWith(\"%\");\n let text = query;\n if (start) {\n text = text.slice(1);\n }\n if (end) {\n text = text.slice(0, -1);\n }\n text = text.toLowerCase();\n\n if (start && end) {\n return { mode: \"contains\", text };\n }\n if (start) {\n return { mode: \"endsWith\", text };\n }\n if (end) {\n return { mode: \"startsWith\", text };\n }\n return { mode: \"exact\", text };\n}\n\nfunction isEmptyValue(value: unknown): boolean {\n return value === null || value === undefined;\n}\n\n/**\n * `parseFilterPattern`, compiled to a reusable test — parsed once, reused\n * per row, unlike a per-call `matchesQuery`.\n */\nfunction compileFilterQuery(query: string): (value: unknown) => boolean {\n const { mode, text } = parseFilterPattern(query);\n return (value: unknown) => {\n const candidate = (isEmptyValue(value) ? \"\" : String(value)).toLowerCase();\n switch (mode) {\n case \"exact\":\n return candidate === text;\n case \"contains\":\n return candidate.includes(text);\n case \"startsWith\":\n return candidate.startsWith(text);\n case \"endsWith\":\n return candidate.endsWith(text);\n }\n };\n}\n\n/**\n * Whether `target` (a `ValueFilterEntry`'s value) equals `value` under\n * `type` — `false` whenever `type` doesn't agree with `target`'s own kind,\n * so a number filter never silently matches a string column.\n */\nfunction matchesTypedValue(\n value: unknown,\n target: number | boolean | Date,\n type: ColumnType | undefined,\n): boolean {\n if (typeof target === \"number\") {\n if (\n type !== \"number\" &&\n type !== \"decimal\" &&\n type !== \"currency\" &&\n type !== \"percent\"\n ) {\n return false;\n }\n return typeof value === \"number\" && value === target;\n }\n if (typeof target === \"boolean\") {\n if (type !== \"boolean\") {\n return false;\n }\n return typeof value === \"boolean\" && value === target;\n }\n if (type !== \"date\" && type !== \"dateTime\" && type !== \"time\") {\n return false;\n }\n return value instanceof Date && value.getTime() === target.getTime();\n}\n\n/**\n * Whether a non-group entry names a column present in `byId` (or names\n * none at all) — lets a stored filter tolerate a column removed from the\n * definition the same way a stored `ColumnSortState` does, by dropping only\n * the entries that reference it.\n *\n * A `GroupFilterEntry` is always usable, regardless of its own contents:\n * its vacuous truth value under an empty or fully-unusable `entries` list\n * depends on its own `combinator` (`\"and\"` vacuously matches everything,\n * `\"or\"` vacuously matches nothing) — a group can't be dropped from its\n * parent's list the way an unusable simple entry can, since whether\n * dropping it would be safe depends on the *parent's* combinator too,\n * which this check has no way to know. Compiling it and letting `.every()`/\n * `.some()` resolve the vacuous case naturally is what stays correct in\n * every context; deciding upfront here is not.\n */\nfunction isEntryUsable<Row>(\n entry: FilterEntry<Row>,\n byId: ReadonlyMap<string, ColumnDefinition<Row, unknown>>,\n): boolean {\n if (\"combinator\" in entry) {\n return true;\n }\n return entry.columnId === undefined || byId.has(entry.columnId);\n}\n\n/**\n * One compiled test per usable `FilterEntry`, closing over its column\n * lookup so `filterRows`'s row loop is just `.every(test)`.\n */\nfunction compileFilterEntry<Row>(\n entry: FilterEntry<Row>,\n byId: ReadonlyMap<string, ColumnDefinition<Row, unknown>>,\n columns: readonly ResolvedColumn<Row, unknown>[],\n): (row: ResolvedRow<Row>) => boolean {\n if (\"combinator\" in entry) {\n const tests = entry.entries\n .filter((nested) => isEntryUsable(nested, byId))\n .map((nested) => compileFilterEntry(nested, byId, columns));\n return entry.combinator === \"and\"\n ? (row) => tests.every((test) => test(row))\n : (row) => tests.some((test) => test(row));\n }\n\n if (\"predicate\" in entry) {\n const { columnId, predicate } = entry;\n const column = columnId === undefined ? undefined : byId.get(columnId);\n return (row) =>\n predicate(\n column === undefined ? undefined : accessDotted(row.row, column.field),\n row.row,\n );\n }\n\n if (\"value\" in entry) {\n const { columnId, value } = entry;\n if (columnId !== undefined) {\n const column = byId.get(columnId);\n if (column === undefined) {\n return () => false;\n }\n return (row) =>\n matchesTypedValue(\n accessDotted(row.row, column.field),\n value,\n column.type,\n );\n }\n return (row) =>\n columns.some((entry) =>\n matchesTypedValue(\n accessDotted(row.row, entry.column.field),\n value,\n entry.column.type,\n ),\n );\n }\n\n const { columnId, query } = entry;\n const test = compileFilterQuery(query);\n if (columnId !== undefined) {\n const column = byId.get(columnId);\n if (column === undefined) {\n return () => false;\n }\n return (row) => test(accessDotted(row.row, column.field));\n }\n return (row) =>\n columns.some((entry) => test(accessDotted(row.row, entry.column.field)));\n}\n\n/**\n * Whether `value`, stringified, matches `query`'s SQL LIKE-style pattern —\n * case-insensitive always. A bare query is an exact match; `%text%` is\n * contains; `text%` is starts-with; `%text` is ends-with. `null`/`undefined`\n * stringify to `\"\"`.\n *\n * A one-shot convenience: parses `query` fresh on every call. `filterRows`\n * does not call this per cell — it compiles each `TextFilterEntry` once,\n * outside its row loop.\n */\nexport function matchesQuery(value: unknown, query: string): boolean {\n return compileFilterQuery(query)(value);\n}\n\n/**\n * This column's current text query, or the global entry's when `columnId`\n * is omitted; `null` if no `TextFilterEntry` names it — including when the\n * entry that exists for that key is a `ValueFilterEntry`/\n * `PredicateFilterEntry`/`GroupFilterEntry` instead, since none of those\n * hold a query string to report.\n */\nexport function filterQueryFor<Row>(\n filter: FilterState<Row>,\n columnId?: FieldPath<Row> | (string & {}),\n): string | null {\n const entry = filter.find(\n (candidate): candidate is TextFilterEntry<Row> =>\n \"query\" in candidate && candidate.columnId === columnId,\n );\n return entry?.query ?? null;\n}\n\n/**\n * The filter with the `TextFilterEntry` for `columnId` (or the global\n * entry, when `columnId` is omitted) set to `query` — replacing whatever\n * entry already holds that key, of any variant, if present; appending a new\n * `TextFilterEntry` if not. An empty `query` removes the entry instead of\n * storing a filter that can never usefully match.\n *\n * Returns `filter` itself, unchanged, when the result would be a no-op: an\n * empty `query` naming a key with no existing entry, or a `query` equal to\n * an existing `TextFilterEntry`'s.\n */\nexport function setColumnFilter<Row>(\n filter: FilterState<Row>,\n columnId: (FieldPath<Row> | (string & {})) | undefined,\n query: string,\n): FilterState<Row> {\n const index = filter.findIndex(\n (entry) => !(\"combinator\" in entry) && entry.columnId === columnId,\n );\n\n if (query === \"\") {\n return index === -1 ? filter : filter.filter((_entry, i) => i !== index);\n }\n\n const next: TextFilterEntry<Row> =\n columnId === undefined ? { query } : { columnId, query };\n\n if (index === -1) {\n return [...filter, next];\n }\n const existing = filter[index];\n if (\n existing !== undefined &&\n \"query\" in existing &&\n existing.query === query\n ) {\n return filter;\n }\n return filter.map((entry, i) => (i === index ? next : entry));\n}\n\n/** `filter` reduced to empty. Returns `filter` itself when already empty. */\nexport function clearAllFilters<Row>(\n filter: FilterState<Row>,\n): FilterState<Row> {\n return filter.length === 0 ? filter : [];\n}\n\n/**\n * `rows` reduced to those matching every entry in `filter` (AND at the top\n * level — nest a `GroupFilterEntry` for OR). `rowIndex` is renumbered to\n * match the retained positions — the same invariant `sortRows` keeps,\n * needed here independently since `filterRows` runs before `sortRows` in\n * the composed pipeline (see `resolveShownRows`).\n *\n * Order-preserving: surviving rows keep their relative order, which is what\n * makes running this before a sort produce the identical final order a\n * sort-then-filter would — only the cost differs.\n *\n * Every entry is compiled once, before the row loop.\n *\n * Returns `rows` itself, untouched, when `filter` is empty or names only\n * columns absent from `columns` — a stored filter tolerates a column\n * removed from the definition the same way a stored `ColumnSortState` does.\n */\nexport function filterRows<Row>(\n rows: readonly ResolvedRow<Row>[],\n filter: FilterState<Row>,\n columns: readonly ResolvedColumn<Row, unknown>[],\n): readonly ResolvedRow<Row>[] {\n if (filter.length === 0) {\n return rows;\n }\n\n const byId = new Map(columns.map((entry) => [entry.id, entry.column]));\n const active = filter.filter((entry) => isEntryUsable(entry, byId));\n if (active.length === 0) {\n return rows;\n }\n\n const compiled = active.map((entry) =>\n compileFilterEntry(entry, byId, columns),\n );\n const filtered = rows.filter((row) => compiled.every((test) => test(row)));\n\n return filtered.map((row, index) =>\n row.rowIndex === index ? row : { ...row, rowIndex: index },\n );\n}\n","import type {\n ColumnSortState,\n FilterState,\n ResolvedColumn,\n ResolvedRow,\n} from \"../types\";\nimport { filterRows } from \"./filtering\";\nimport { sortRows } from \"./sorting\";\n\n/**\n * `rows`, filtered then sorted — the one function `DataGrid.tsx` (and any\n * future framework binding) calls instead of composing `filterRows`/\n * `sortRows` itself.\n *\n * Filter always runs first: an O(n) pass that shrinks what the O(n log n)\n * sort has to touch whenever a filter is active, free when it isn't. This\n * is a performance choice, not a correctness one — filtering is an\n * order-preserving predicate and sort is a stable total order over whatever\n * it's handed, so the final order is identical either way.\n */\nexport function resolveShownRows<Row>(\n rows: readonly ResolvedRow<Row>[],\n filter: FilterState<Row>,\n sort: ColumnSortState,\n columns: readonly ResolvedColumn<Row, unknown>[],\n): readonly ResolvedRow<Row>[] {\n return sortRows(filterRows(rows, filter, columns), sort, columns);\n}\n"],"mappings":"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","names":["total","test","columnId","entry"]}
|