@gridkitjs/core 0.5.0 → 0.7.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 +296 -1
- package/dist/index.js +399 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -97,6 +97,8 @@ interface ColumnDefinition<Row, Node = string> {
|
|
|
97
97
|
* grid-level default. A column that cannot move can still be moved past.
|
|
98
98
|
*/
|
|
99
99
|
reorderable?: boolean;
|
|
100
|
+
/** Whether this column can be sorted, overriding the grid-level default. */
|
|
101
|
+
sortable?: boolean;
|
|
100
102
|
/**
|
|
101
103
|
* Lets this column's header and/or cell text wrap onto multiple lines
|
|
102
104
|
* instead of the grid's default single line with an ellipsis. Off by
|
|
@@ -219,6 +221,82 @@ interface ColumnOrderEvent {
|
|
|
219
221
|
readonly columnId: string;
|
|
220
222
|
readonly order: ColumnOrderState;
|
|
221
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>[];
|
|
222
300
|
/**
|
|
223
301
|
* Ids selected, in the order they were selected — so the most recent is last,
|
|
224
302
|
* and a consumer wanting only that reads `at(-1)` rather than tracking it
|
|
@@ -252,6 +330,11 @@ interface SelectableConfig {
|
|
|
252
330
|
columns?: SelectionMode | undefined;
|
|
253
331
|
cells?: CellSelectionMode | undefined;
|
|
254
332
|
}
|
|
333
|
+
/**
|
|
334
|
+
* What an interaction means for the selection, read off its modifiers once at
|
|
335
|
+
* the boundary so that nothing downstream handles a raw event.
|
|
336
|
+
*/
|
|
337
|
+
type SelectIntent = "replace" | "toggle" | "range";
|
|
255
338
|
/** The address of one cell: which row, and which column within it. */
|
|
256
339
|
interface SelectedCellRef {
|
|
257
340
|
readonly rowId: string;
|
|
@@ -399,6 +482,18 @@ declare function resolveRowId<Row>(row: Row, index: number, getRowId?: (row: Row
|
|
|
399
482
|
* reading as `"Application Id"`.
|
|
400
483
|
*/
|
|
401
484
|
declare function resolveColumnLabel<Row, Node>(column: ColumnDefinition<Row, Node>): Node | string;
|
|
485
|
+
/** Which of a column header's keyboard interactions to announce. */
|
|
486
|
+
interface KeyShortcutCapabilities {
|
|
487
|
+
reorderable: boolean;
|
|
488
|
+
resizable: boolean;
|
|
489
|
+
sortable: boolean;
|
|
490
|
+
}
|
|
491
|
+
/**
|
|
492
|
+
* The `aria-keyshortcuts` string for a column header, naming only the keys
|
|
493
|
+
* that column actually responds to — so the keys are discoverable, since
|
|
494
|
+
* none of them is a convention a user would try unprompted.
|
|
495
|
+
*/
|
|
496
|
+
declare function buildKeyShortcuts(capabilities: KeyShortcutCapabilities): string;
|
|
402
497
|
/**
|
|
403
498
|
* Accesses a value at a dotted path, e.g. `foo.bar.baz`.
|
|
404
499
|
*
|
|
@@ -408,6 +503,40 @@ declare function resolveColumnLabel<Row, Node>(column: ColumnDefinition<Row, Nod
|
|
|
408
503
|
*/
|
|
409
504
|
declare function accessDotted(obj: unknown, path: string): unknown;
|
|
410
505
|
|
|
506
|
+
/**
|
|
507
|
+
* The header's row index. One coordinate space covers the header and the body
|
|
508
|
+
* so that arrowing up out of the first row reaches the header without a case
|
|
509
|
+
* of its own, and every other move is plain arithmetic.
|
|
510
|
+
*/
|
|
511
|
+
declare const HEADER_ROW = -1;
|
|
512
|
+
/** The cell the grid's single tab stop sits on. */
|
|
513
|
+
interface GridFocus {
|
|
514
|
+
readonly rowIndex: number;
|
|
515
|
+
readonly columnIndex: number;
|
|
516
|
+
}
|
|
517
|
+
/** The modifier keys held with a navigation key press. */
|
|
518
|
+
interface NavigationModifiers {
|
|
519
|
+
readonly ctrlKey: boolean;
|
|
520
|
+
readonly metaKey: boolean;
|
|
521
|
+
readonly altKey: boolean;
|
|
522
|
+
readonly shiftKey: boolean;
|
|
523
|
+
}
|
|
524
|
+
/**
|
|
525
|
+
* Holds a coordinate inside the grid, so that a focus surviving a column
|
|
526
|
+
* removal or a shorter page never leaves the grid with no tab stop at all —
|
|
527
|
+
* which is a grid a keyboard cannot reach.
|
|
528
|
+
*/
|
|
529
|
+
declare function clampFocus(focus: GridFocus, rowCount: number, columnCount: number): GridFocus;
|
|
530
|
+
/**
|
|
531
|
+
* The focus a navigation key press moves to, or `null` when the key isn't
|
|
532
|
+
* this grid's to handle — left for the browser or another handler.
|
|
533
|
+
*
|
|
534
|
+
* `pageSize` is a pre-measured row count rather than a callback: this
|
|
535
|
+
* function takes plain data only, so a caller measures the viewport once and
|
|
536
|
+
* hands over the number.
|
|
537
|
+
*/
|
|
538
|
+
declare function nextFocusForKey(key: string, modifiers: NavigationModifiers, focus: GridFocus, rowCount: number, columnCount: number, pageSize: number): GridFocus | null;
|
|
539
|
+
|
|
411
540
|
/**
|
|
412
541
|
* Puts `columns` in the order `order` gives. Ids it lists come first, in that
|
|
413
542
|
* order; columns it omits keep their position among the definitions and
|
|
@@ -441,6 +570,16 @@ declare function movesColumn(orderedIds: ColumnOrderState, movedId: string, befo
|
|
|
441
570
|
* keeps a drop target a single nullable string.
|
|
442
571
|
*/
|
|
443
572
|
declare function resolveDropBefore(orderedIds: ColumnOrderState, targetId: string, side: DropSide): string | null;
|
|
573
|
+
/**
|
|
574
|
+
* The `beforeId` a keyboard nudge on `columnId` produces, for `onDrop` to
|
|
575
|
+
* apply the same way a pointer drop does — or `undefined` when the nudge is a
|
|
576
|
+
* no-op: `columnId` is not in `orderedIds`, or the nudge would move it left
|
|
577
|
+
* of the start.
|
|
578
|
+
*
|
|
579
|
+
* `null` is a real answer here, not "no-op": it means append to the end, the
|
|
580
|
+
* same convention `moveColumnBefore` uses.
|
|
581
|
+
*/
|
|
582
|
+
declare function resolveKeyboardDropTarget(orderedIds: ColumnOrderState, columnId: string, direction: -1 | 1): string | null | undefined;
|
|
444
583
|
|
|
445
584
|
/**
|
|
446
585
|
* The selection with `id` added if absent and removed if present — a
|
|
@@ -496,6 +635,18 @@ declare function selectCell(selection: CellSelectionState, cell: SelectedCellRef
|
|
|
496
635
|
* Ctrl-click, matching `toggleSelection`.
|
|
497
636
|
*/
|
|
498
637
|
declare function toggleCellSelection(selection: CellSelectionState, cell: SelectedCellRef, mode: CellSelectionMode): CellSelectionState;
|
|
638
|
+
/** The intent a click or key press carries, by the modifiers held with it. */
|
|
639
|
+
declare function intentOf(event: {
|
|
640
|
+
ctrlKey: boolean;
|
|
641
|
+
metaKey: boolean;
|
|
642
|
+
shiftKey: boolean;
|
|
643
|
+
}): SelectIntent;
|
|
644
|
+
/**
|
|
645
|
+
* The transform an intent asks for. A function rather than a branch inside
|
|
646
|
+
* each caller, so a row and a column cannot come to mean different things by
|
|
647
|
+
* the same click.
|
|
648
|
+
*/
|
|
649
|
+
declare function applySelectionIntent(selection: SelectionState, orderedIds: readonly string[], anchorId: string | null, id: string, intent: SelectIntent, mode: SelectionMode): SelectionState;
|
|
499
650
|
|
|
500
651
|
/**
|
|
501
652
|
* Applied to any column that does not size itself. The minimum is a width a
|
|
@@ -556,5 +707,149 @@ declare function fitColumnsToWidth<Row, Node>(resolved: readonly ResolvedColumn<
|
|
|
556
707
|
* exactly its content can still round down into an ellipsis.
|
|
557
708
|
*/
|
|
558
709
|
declare function sizeColumnToContent(measuredWidth: number, constraints: ColumnConstraints, padding?: number): number;
|
|
710
|
+
/**
|
|
711
|
+
* `current` with `columnId` reverted to its width in `base` — or omitted
|
|
712
|
+
* entirely if `base` had none — for Escape to restore a resize in progress.
|
|
713
|
+
*
|
|
714
|
+
* A column with no stored width in `base` is correctly omitted by deleting
|
|
715
|
+
* the key rather than merging its `base` width back in, which would
|
|
716
|
+
* otherwise leave it pinned and hidden from auto-fit.
|
|
717
|
+
*/
|
|
718
|
+
declare function revertColumnSize(current: ColumnSizingState, base: ColumnSizingState, columnId: string): ColumnSizingState;
|
|
719
|
+
|
|
720
|
+
/**
|
|
721
|
+
* Compares two cell values the way `type` implies — numerically for the
|
|
722
|
+
* numeric types, chronologically for the date/time types, lexically
|
|
723
|
+
* otherwise. Defaults to string comparison when `type` is absent, mirroring
|
|
724
|
+
* `ColumnDefinition.type`'s own default.
|
|
725
|
+
*
|
|
726
|
+
* `null`/`undefined` sort after every value on both sides. Used directly,
|
|
727
|
+
* that ordering is fixed; `sortRows` applies it directionally without
|
|
728
|
+
* disturbing it — see its own doc comment.
|
|
729
|
+
*/
|
|
730
|
+
declare function compareValues(a: unknown, b: unknown, type?: ColumnType): number;
|
|
731
|
+
/** This column's direction in `sort`, or `null` outside it. */
|
|
732
|
+
declare function sortDirectionFor(sort: ColumnSortState, columnId: string): SortDirection | null;
|
|
733
|
+
/** This column's 1-based place in `sort`, or `null` outside it. */
|
|
734
|
+
declare function sortPriorityFor(sort: ColumnSortState, columnId: string): number | null;
|
|
735
|
+
/**
|
|
736
|
+
* The sort with `columnId` cycled to its next direction — none → asc → desc →
|
|
737
|
+
* none.
|
|
738
|
+
*
|
|
739
|
+
* `stack: false` (a plain click) means "sort by only this column": unless
|
|
740
|
+
* `columnId` is already the sole entry, in which case its own cycle just
|
|
741
|
+
* continues, it collapses `sort` down to `columnId` alone at `"asc"`,
|
|
742
|
+
* regardless of any direction it held as part of a larger stack.
|
|
743
|
+
*
|
|
744
|
+
* `stack: true` (a Shift-click) cycles or appends `columnId` in place without
|
|
745
|
+
* disturbing the rest of the stack; cycling it back to `"none"` removes it
|
|
746
|
+
* and closes the gap, leaving the others in their existing order.
|
|
747
|
+
*/
|
|
748
|
+
declare function toggleColumnSort(sort: ColumnSortState, columnId: string, options: {
|
|
749
|
+
readonly stack: boolean;
|
|
750
|
+
}): ColumnSortState;
|
|
751
|
+
/**
|
|
752
|
+
* `rows` ordered by `sort` — each entry read against `columns` for its
|
|
753
|
+
* `field` and `type` — with `rowIndex` renumbered to match the new
|
|
754
|
+
* positions, the invariant the rest of the grid relies on for keyboard
|
|
755
|
+
* navigation and `aria-rowindex`.
|
|
756
|
+
*
|
|
757
|
+
* Stable (`Array.prototype.sort` has been stable since ES2019), so rows
|
|
758
|
+
* equal under every active key keep their relative order. A `null`/
|
|
759
|
+
* `undefined` value sorts last under either direction — resolved before
|
|
760
|
+
* `compareValues` is asked, and left out of the direction flip below, so
|
|
761
|
+
* that toggling a column reorders the values it holds rather than moving its
|
|
762
|
+
* gaps to the front.
|
|
763
|
+
*
|
|
764
|
+
* Returns `rows` itself, untouched, when `sort` is empty or names only
|
|
765
|
+
* columns absent from `columns` — a stored sort tolerates a column removed
|
|
766
|
+
* from the definition the same way a stored `ColumnOrderState` does.
|
|
767
|
+
*/
|
|
768
|
+
declare function sortRows<Row>(rows: readonly ResolvedRow<Row>[], sort: ColumnSortState, columns: readonly ResolvedColumn<Row, unknown>[]): readonly ResolvedRow<Row>[];
|
|
769
|
+
|
|
770
|
+
/**
|
|
771
|
+
* Whether `value`, stringified, matches `query`'s SQL LIKE-style pattern —
|
|
772
|
+
* case-insensitive always. A bare query is an exact match; `%text%` is
|
|
773
|
+
* contains; `text%` is starts-with; `%text` is ends-with. `null`/`undefined`
|
|
774
|
+
* stringify to `""`.
|
|
775
|
+
*
|
|
776
|
+
* A one-shot convenience: parses `query` fresh on every call. `filterRows`
|
|
777
|
+
* does not call this per cell — it compiles each `TextFilterEntry` once,
|
|
778
|
+
* outside its row loop.
|
|
779
|
+
*/
|
|
780
|
+
declare function matchesQuery(value: unknown, query: string): boolean;
|
|
781
|
+
/**
|
|
782
|
+
* This column's current text query, or the global entry's when `columnId`
|
|
783
|
+
* is omitted; `null` if no `TextFilterEntry` names it — including when the
|
|
784
|
+
* entry that exists for that key is a `ValueFilterEntry`/
|
|
785
|
+
* `PredicateFilterEntry`/`GroupFilterEntry` instead, since none of those
|
|
786
|
+
* hold a query string to report.
|
|
787
|
+
*/
|
|
788
|
+
declare function filterQueryFor<Row>(filter: FilterState<Row>, columnId?: FieldPath<Row> | (string & {})): string | null;
|
|
789
|
+
/**
|
|
790
|
+
* The filter with the `TextFilterEntry` for `columnId` (or the global
|
|
791
|
+
* entry, when `columnId` is omitted) set to `query` — replacing whatever
|
|
792
|
+
* entry already holds that key, of any variant, if present; appending a new
|
|
793
|
+
* `TextFilterEntry` if not. An empty `query` removes the entry instead of
|
|
794
|
+
* storing a filter that can never usefully match.
|
|
795
|
+
*
|
|
796
|
+
* Returns `filter` itself, unchanged, when the result would be a no-op: an
|
|
797
|
+
* empty `query` naming a key with no existing entry, or a `query` equal to
|
|
798
|
+
* an existing `TextFilterEntry`'s.
|
|
799
|
+
*/
|
|
800
|
+
declare function setColumnFilter<Row>(filter: FilterState<Row>, columnId: (FieldPath<Row> | (string & {})) | undefined, query: string): FilterState<Row>;
|
|
801
|
+
/** `filter` reduced to empty. Returns `filter` itself when already empty. */
|
|
802
|
+
declare function clearAllFilters<Row>(filter: FilterState<Row>): FilterState<Row>;
|
|
803
|
+
/**
|
|
804
|
+
* `rows` reduced to those matching every entry in `filter` (AND at the top
|
|
805
|
+
* level — nest a `GroupFilterEntry` for OR). `rowIndex` is renumbered to
|
|
806
|
+
* match the retained positions — the same invariant `sortRows` keeps,
|
|
807
|
+
* needed here independently since `filterRows` runs before `sortRows` in
|
|
808
|
+
* the composed pipeline (see `resolveShownRows`).
|
|
809
|
+
*
|
|
810
|
+
* Order-preserving: surviving rows keep their relative order, which is what
|
|
811
|
+
* makes running this before a sort produce the identical final order a
|
|
812
|
+
* sort-then-filter would — only the cost differs.
|
|
813
|
+
*
|
|
814
|
+
* Every entry is compiled once, before the row loop.
|
|
815
|
+
*
|
|
816
|
+
* Returns `rows` itself, untouched, when `filter` is empty or names only
|
|
817
|
+
* columns absent from `columns` — a stored filter tolerates a column
|
|
818
|
+
* removed from the definition the same way a stored `ColumnSortState` does.
|
|
819
|
+
*/
|
|
820
|
+
declare function filterRows<Row>(rows: readonly ResolvedRow<Row>[], filter: FilterState<Row>, columns: readonly ResolvedColumn<Row, unknown>[]): readonly ResolvedRow<Row>[];
|
|
821
|
+
|
|
822
|
+
/**
|
|
823
|
+
* `rows`, filtered then sorted — the one function `DataGrid.tsx` (and any
|
|
824
|
+
* future framework binding) calls instead of composing `filterRows`/
|
|
825
|
+
* `sortRows` itself.
|
|
826
|
+
*
|
|
827
|
+
* Filter always runs first: an O(n) pass that shrinks what the O(n log n)
|
|
828
|
+
* sort has to touch whenever a filter is active, free when it isn't. This
|
|
829
|
+
* is a performance choice, not a correctness one — filtering is an
|
|
830
|
+
* order-preserving predicate and sort is a stable total order over whatever
|
|
831
|
+
* it's handed, so the final order is identical either way.
|
|
832
|
+
*/
|
|
833
|
+
declare function resolveShownRows<Row>(rows: readonly ResolvedRow<Row>[], filter: FilterState<Row>, sort: ColumnSortState, columns: readonly ResolvedColumn<Row, unknown>[]): readonly ResolvedRow<Row>[];
|
|
834
|
+
/**
|
|
835
|
+
* `ids` resolved back to the rows behind them, keyed by `rowsById`.
|
|
836
|
+
*
|
|
837
|
+
* An id with no row behind it is dropped rather than left `undefined`: a
|
|
838
|
+
* selection outlives a change to the data, so that filtering a row out and
|
|
839
|
+
* back leaves it selected, and only what still exists can be handed to a
|
|
840
|
+
* callback.
|
|
841
|
+
*/
|
|
842
|
+
declare function resolveRows<Row>(rowsById: ReadonlyMap<string, ResolvedRow<Row>>, ids: readonly string[]): readonly ResolvedRow<Row>[];
|
|
843
|
+
/**
|
|
844
|
+
* `ids` resolved back to the columns behind them, paired with the position
|
|
845
|
+
* each holds in `columns` — a stale id is dropped, matching `resolveRows`.
|
|
846
|
+
*/
|
|
847
|
+
declare function resolveColumns<Row, Node>(columns: readonly ResolvedColumn<Row, Node>[], ids: readonly string[]): readonly SelectedColumn<Row, Node>[];
|
|
848
|
+
/**
|
|
849
|
+
* `cell` resolved down to the row, column and value it addresses — `null`
|
|
850
|
+
* when there is no cell, or when either half of it names a row or column
|
|
851
|
+
* that no longer exists.
|
|
852
|
+
*/
|
|
853
|
+
declare function resolveCell<Row, Node>(rowsById: ReadonlyMap<string, ResolvedRow<Row>>, columns: readonly ResolvedColumn<Row, Node>[], cell: SelectedCellRef | null): SelectedCell<Row, Node> | null;
|
|
559
854
|
|
|
560
|
-
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 ColumnType, type ColumnWrapConfig, type ColumnsSelectEvent, DEFAULT_COLUMN_SIZES, type DropSide, type FieldPath, KEYBOARD_STEP, type ResolvedColumn, type ResolvedRow, type RowSelectEvent, type RowSelectionChangeEvent, type RowsSelectEvent, type SelectableConfig, type SelectedCell, type SelectedCellRef, type SelectedColumn, type SelectionDiff, type SelectionMode, type SelectionState, accessDotted, alignmentForType, applyColumnOrder, applyColumnResize, beginColumnResize, clampColumnWidth, clearSelection, defineColumnsFromRows, diffSelection, fitColumnsToWidth, getColumnId, isSameCell, moveColumnBefore, movesColumn, resolveColumnConstraints, resolveColumnLabel, resolveColumnWidths, resolveDropBefore, resolveRowId, selectAll, selectCell, selectOnly, selectRange, sizeColumnToContent, toggleCellSelection, toggleSelection, totalColumnWidth };
|
|
855
|
+
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 GridFocus, type GroupFilterEntry, HEADER_ROW, KEYBOARD_STEP, type KeyShortcutCapabilities, type NavigationModifiers, type PredicateFilterEntry, type ResolvedColumn, type ResolvedRow, type RowSelectEvent, type RowSelectionChangeEvent, type RowsSelectEvent, type SelectIntent, type SelectableConfig, type SelectedCell, type SelectedCellRef, type SelectedColumn, type SelectionDiff, type SelectionMode, type SelectionState, type SortDirection, type TextFilterEntry, type ValueFilterEntry, accessDotted, alignmentForType, applyColumnOrder, applyColumnResize, applySelectionIntent, beginColumnResize, buildKeyShortcuts, clampColumnWidth, clampFocus, clearAllFilters, clearSelection, compareValues, defineColumnsFromRows, diffSelection, filterQueryFor, filterRows, fitColumnsToWidth, getColumnId, intentOf, isSameCell, matchesQuery, moveColumnBefore, movesColumn, nextFocusForKey, resolveCell, resolveColumnConstraints, resolveColumnLabel, resolveColumnWidths, resolveColumns, resolveDropBefore, resolveKeyboardDropTarget, resolveRowId, resolveRows, resolveShownRows, revertColumnSize, selectAll, selectCell, selectOnly, selectRange, setColumnFilter, sizeColumnToContent, sortDirectionFor, sortPriorityFor, sortRows, toggleCellSelection, toggleColumnSort, toggleSelection, totalColumnWidth };
|
package/dist/index.js
CHANGED
|
@@ -70,10 +70,62 @@ function resolveColumnLabel(column) {
|
|
|
70
70
|
}
|
|
71
71
|
return typeof headerTemplate === "function" ? headerTemplate() : headerTemplate;
|
|
72
72
|
}
|
|
73
|
+
function buildKeyShortcuts(capabilities) {
|
|
74
|
+
return [
|
|
75
|
+
capabilities.reorderable ? "Control+ArrowLeft Control+ArrowRight" : "",
|
|
76
|
+
// Alt+Enter is the keyboard equivalent of double-clicking the resize
|
|
77
|
+
// handle to size the column to its content — same gate as the nudge
|
|
78
|
+
// shortcuts, since both are resize actions.
|
|
79
|
+
capabilities.resizable ? "Alt+ArrowLeft Alt+ArrowRight Alt+Enter" : "",
|
|
80
|
+
capabilities.sortable ? "Alt+ArrowUp" : ""
|
|
81
|
+
].filter(Boolean).join(" ");
|
|
82
|
+
}
|
|
73
83
|
function accessDotted(obj, path) {
|
|
74
84
|
return path.split(".").reduce((value, key) => isNested(value) ? value[key] : void 0, obj);
|
|
75
85
|
}
|
|
76
86
|
|
|
87
|
+
// src/util/navigation.ts
|
|
88
|
+
var HEADER_ROW = -1;
|
|
89
|
+
function clampFocus(focus, rowCount, columnCount) {
|
|
90
|
+
return {
|
|
91
|
+
rowIndex: Math.min(Math.max(focus.rowIndex, HEADER_ROW), rowCount - 1),
|
|
92
|
+
columnIndex: Math.min(
|
|
93
|
+
Math.max(focus.columnIndex, 0),
|
|
94
|
+
Math.max(columnCount - 1, 0)
|
|
95
|
+
)
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
function nextFocusForKey(key, modifiers, focus, rowCount, columnCount, pageSize) {
|
|
99
|
+
if (modifiers.altKey) {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
const { rowIndex, columnIndex } = focus;
|
|
103
|
+
switch (key) {
|
|
104
|
+
case "ArrowLeft":
|
|
105
|
+
if (modifiers.ctrlKey) return null;
|
|
106
|
+
return { rowIndex, columnIndex: columnIndex - 1 };
|
|
107
|
+
case "ArrowRight":
|
|
108
|
+
if (modifiers.ctrlKey) return null;
|
|
109
|
+
return { rowIndex, columnIndex: columnIndex + 1 };
|
|
110
|
+
case "ArrowUp":
|
|
111
|
+
return { rowIndex: rowIndex - 1, columnIndex };
|
|
112
|
+
case "ArrowDown":
|
|
113
|
+
return { rowIndex: rowIndex + 1, columnIndex };
|
|
114
|
+
// Ctrl takes Home and End to the grid's ends rather than the row's, which
|
|
115
|
+
// is the one place Ctrl is navigation rather than reorder.
|
|
116
|
+
case "Home":
|
|
117
|
+
return modifiers.ctrlKey ? { rowIndex: HEADER_ROW, columnIndex: 0 } : { rowIndex, columnIndex: 0 };
|
|
118
|
+
case "End":
|
|
119
|
+
return modifiers.ctrlKey ? { rowIndex: rowCount - 1, columnIndex: columnCount - 1 } : { rowIndex, columnIndex: columnCount - 1 };
|
|
120
|
+
case "PageUp":
|
|
121
|
+
return { rowIndex: rowIndex - pageSize, columnIndex };
|
|
122
|
+
case "PageDown":
|
|
123
|
+
return { rowIndex: rowIndex + pageSize, columnIndex };
|
|
124
|
+
default:
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
77
129
|
// src/util/ordering.ts
|
|
78
130
|
function applyColumnOrder(columns, order) {
|
|
79
131
|
if (order.length === 0) {
|
|
@@ -110,6 +162,17 @@ function resolveDropBefore(orderedIds, targetId, side) {
|
|
|
110
162
|
const index = orderedIds.indexOf(targetId);
|
|
111
163
|
return index === -1 ? null : orderedIds[index + 1] ?? null;
|
|
112
164
|
}
|
|
165
|
+
function resolveKeyboardDropTarget(orderedIds, columnId, direction) {
|
|
166
|
+
const index = orderedIds.indexOf(columnId);
|
|
167
|
+
if (index === -1) {
|
|
168
|
+
return void 0;
|
|
169
|
+
}
|
|
170
|
+
const targetIndex = direction === -1 ? index - 1 : index + 2;
|
|
171
|
+
if (targetIndex < 0) {
|
|
172
|
+
return void 0;
|
|
173
|
+
}
|
|
174
|
+
return orderedIds[targetIndex] ?? null;
|
|
175
|
+
}
|
|
113
176
|
|
|
114
177
|
// src/util/selection.ts
|
|
115
178
|
var NO_CHANGE = { added: [], removed: [] };
|
|
@@ -184,6 +247,24 @@ function toggleCellSelection(selection, cell, mode) {
|
|
|
184
247
|
}
|
|
185
248
|
return isSameCell(selection, cell) ? null : cell;
|
|
186
249
|
}
|
|
250
|
+
function intentOf(event) {
|
|
251
|
+
if (event.shiftKey) {
|
|
252
|
+
return "range";
|
|
253
|
+
}
|
|
254
|
+
return event.ctrlKey || event.metaKey ? "toggle" : "replace";
|
|
255
|
+
}
|
|
256
|
+
function applySelectionIntent(selection, orderedIds, anchorId, id, intent, mode) {
|
|
257
|
+
switch (intent) {
|
|
258
|
+
case "toggle":
|
|
259
|
+
return toggleSelection(selection, id, mode);
|
|
260
|
+
// A range with no anchor yet spans from the id to itself, which is the
|
|
261
|
+
// plain click a first Shift-click may as well be.
|
|
262
|
+
case "range":
|
|
263
|
+
return selectRange(selection, orderedIds, anchorId ?? id, id, mode);
|
|
264
|
+
default:
|
|
265
|
+
return selectOnly(selection, id, mode);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
187
268
|
|
|
188
269
|
// src/util/sizing.ts
|
|
189
270
|
var DEFAULT_COLUMN_SIZES = {
|
|
@@ -314,34 +395,352 @@ function fitColumnsToWidth(resolved, availableWidth, defaults) {
|
|
|
314
395
|
function sizeColumnToContent(measuredWidth, constraints, padding = 2) {
|
|
315
396
|
return clampColumnWidth(Math.ceil(measuredWidth + padding), constraints);
|
|
316
397
|
}
|
|
398
|
+
function revertColumnSize(current, base, columnId) {
|
|
399
|
+
const next = Object.fromEntries(
|
|
400
|
+
Object.entries(current).filter(([id]) => id !== columnId)
|
|
401
|
+
);
|
|
402
|
+
if (columnId in base) {
|
|
403
|
+
next[columnId] = base[columnId];
|
|
404
|
+
}
|
|
405
|
+
return next;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// src/util/sorting.ts
|
|
409
|
+
function isEmpty(value) {
|
|
410
|
+
return value === null || value === void 0;
|
|
411
|
+
}
|
|
412
|
+
function toTime(value) {
|
|
413
|
+
return value instanceof Date ? value.getTime() : new Date(String(value)).getTime();
|
|
414
|
+
}
|
|
415
|
+
function compareValues(a, b, type) {
|
|
416
|
+
if (isEmpty(a)) {
|
|
417
|
+
return isEmpty(b) ? 0 : 1;
|
|
418
|
+
}
|
|
419
|
+
if (isEmpty(b)) {
|
|
420
|
+
return -1;
|
|
421
|
+
}
|
|
422
|
+
switch (type) {
|
|
423
|
+
case "number":
|
|
424
|
+
case "decimal":
|
|
425
|
+
case "currency":
|
|
426
|
+
case "percent":
|
|
427
|
+
return Number(a) - Number(b);
|
|
428
|
+
case "boolean":
|
|
429
|
+
return Number(Boolean(a)) - Number(Boolean(b));
|
|
430
|
+
case "date":
|
|
431
|
+
case "dateTime":
|
|
432
|
+
case "time":
|
|
433
|
+
return toTime(a) - toTime(b);
|
|
434
|
+
default:
|
|
435
|
+
return String(a).localeCompare(String(b));
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
function sortDirectionFor(sort, columnId) {
|
|
439
|
+
return sort.find((entry) => entry.columnId === columnId)?.direction ?? null;
|
|
440
|
+
}
|
|
441
|
+
function sortPriorityFor(sort, columnId) {
|
|
442
|
+
const index = sort.findIndex((entry) => entry.columnId === columnId);
|
|
443
|
+
return index === -1 ? null : index + 1;
|
|
444
|
+
}
|
|
445
|
+
function toggleColumnSort(sort, columnId, options) {
|
|
446
|
+
const index = sort.findIndex((entry) => entry.columnId === columnId);
|
|
447
|
+
const isSoleEntry = sort.length === 1 && index === 0;
|
|
448
|
+
if (!options.stack && !isSoleEntry) {
|
|
449
|
+
return [{ columnId, direction: "asc" }];
|
|
450
|
+
}
|
|
451
|
+
const current = index === -1 ? void 0 : sort[index]?.direction;
|
|
452
|
+
const next = current === void 0 ? "asc" : current === "asc" ? "desc" : null;
|
|
453
|
+
if (next === null) {
|
|
454
|
+
return sort.filter((entry) => entry.columnId !== columnId);
|
|
455
|
+
}
|
|
456
|
+
if (index === -1) {
|
|
457
|
+
return [...sort, { columnId, direction: next }];
|
|
458
|
+
}
|
|
459
|
+
return sort.map(
|
|
460
|
+
(entry, i) => i === index ? { columnId, direction: next } : entry
|
|
461
|
+
);
|
|
462
|
+
}
|
|
463
|
+
function sortRows(rows, sort, columns) {
|
|
464
|
+
if (sort.length === 0) {
|
|
465
|
+
return rows;
|
|
466
|
+
}
|
|
467
|
+
const byId = new Map(columns.map((entry) => [entry.id, entry.column]));
|
|
468
|
+
const active = sort.filter((entry) => byId.has(entry.columnId));
|
|
469
|
+
if (active.length === 0) {
|
|
470
|
+
return rows;
|
|
471
|
+
}
|
|
472
|
+
const sorted = [...rows].sort((a, b) => {
|
|
473
|
+
for (const { columnId, direction } of active) {
|
|
474
|
+
const column = byId.get(columnId);
|
|
475
|
+
if (column === void 0) {
|
|
476
|
+
continue;
|
|
477
|
+
}
|
|
478
|
+
const left = accessDotted(a.row, column.field);
|
|
479
|
+
const right = accessDotted(b.row, column.field);
|
|
480
|
+
const leftEmpty = isEmpty(left);
|
|
481
|
+
const rightEmpty = isEmpty(right);
|
|
482
|
+
if (leftEmpty || rightEmpty) {
|
|
483
|
+
if (leftEmpty && rightEmpty) {
|
|
484
|
+
continue;
|
|
485
|
+
}
|
|
486
|
+
return leftEmpty ? 1 : -1;
|
|
487
|
+
}
|
|
488
|
+
const result = compareValues(left, right, column.type);
|
|
489
|
+
if (result !== 0) {
|
|
490
|
+
return direction === "asc" ? result : -result;
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
return 0;
|
|
494
|
+
});
|
|
495
|
+
return sorted.map(
|
|
496
|
+
(row, index) => row.rowIndex === index ? row : { ...row, rowIndex: index }
|
|
497
|
+
);
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
// src/util/filtering.ts
|
|
501
|
+
function parseFilterPattern(query) {
|
|
502
|
+
const start = query.startsWith("%");
|
|
503
|
+
const end = query.endsWith("%");
|
|
504
|
+
let text = query;
|
|
505
|
+
if (start) {
|
|
506
|
+
text = text.slice(1);
|
|
507
|
+
}
|
|
508
|
+
if (end) {
|
|
509
|
+
text = text.slice(0, -1);
|
|
510
|
+
}
|
|
511
|
+
text = text.toLowerCase();
|
|
512
|
+
if (start && end) {
|
|
513
|
+
return { mode: "contains", text };
|
|
514
|
+
}
|
|
515
|
+
if (start) {
|
|
516
|
+
return { mode: "endsWith", text };
|
|
517
|
+
}
|
|
518
|
+
if (end) {
|
|
519
|
+
return { mode: "startsWith", text };
|
|
520
|
+
}
|
|
521
|
+
return { mode: "exact", text };
|
|
522
|
+
}
|
|
523
|
+
function isEmptyValue(value) {
|
|
524
|
+
return value === null || value === void 0;
|
|
525
|
+
}
|
|
526
|
+
function compileFilterQuery(query) {
|
|
527
|
+
const { mode, text } = parseFilterPattern(query);
|
|
528
|
+
return (value) => {
|
|
529
|
+
const candidate = (isEmptyValue(value) ? "" : String(value)).toLowerCase();
|
|
530
|
+
switch (mode) {
|
|
531
|
+
case "exact":
|
|
532
|
+
return candidate === text;
|
|
533
|
+
case "contains":
|
|
534
|
+
return candidate.includes(text);
|
|
535
|
+
case "startsWith":
|
|
536
|
+
return candidate.startsWith(text);
|
|
537
|
+
case "endsWith":
|
|
538
|
+
return candidate.endsWith(text);
|
|
539
|
+
}
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
function matchesTypedValue(value, target, type) {
|
|
543
|
+
if (typeof target === "number") {
|
|
544
|
+
if (type !== "number" && type !== "decimal" && type !== "currency" && type !== "percent") {
|
|
545
|
+
return false;
|
|
546
|
+
}
|
|
547
|
+
return typeof value === "number" && value === target;
|
|
548
|
+
}
|
|
549
|
+
if (typeof target === "boolean") {
|
|
550
|
+
if (type !== "boolean") {
|
|
551
|
+
return false;
|
|
552
|
+
}
|
|
553
|
+
return typeof value === "boolean" && value === target;
|
|
554
|
+
}
|
|
555
|
+
if (type !== "date" && type !== "dateTime" && type !== "time") {
|
|
556
|
+
return false;
|
|
557
|
+
}
|
|
558
|
+
return value instanceof Date && value.getTime() === target.getTime();
|
|
559
|
+
}
|
|
560
|
+
function isEntryUsable(entry, byId) {
|
|
561
|
+
if ("combinator" in entry) {
|
|
562
|
+
return true;
|
|
563
|
+
}
|
|
564
|
+
return entry.columnId === void 0 || byId.has(entry.columnId);
|
|
565
|
+
}
|
|
566
|
+
function compileFilterEntry(entry, byId, columns) {
|
|
567
|
+
if ("combinator" in entry) {
|
|
568
|
+
const tests = entry.entries.filter((nested) => isEntryUsable(nested, byId)).map((nested) => compileFilterEntry(nested, byId, columns));
|
|
569
|
+
return entry.combinator === "and" ? (row) => tests.every((test2) => test2(row)) : (row) => tests.some((test2) => test2(row));
|
|
570
|
+
}
|
|
571
|
+
if ("predicate" in entry) {
|
|
572
|
+
const { columnId: columnId2, predicate } = entry;
|
|
573
|
+
const column = columnId2 === void 0 ? void 0 : byId.get(columnId2);
|
|
574
|
+
return (row) => predicate(
|
|
575
|
+
column === void 0 ? void 0 : accessDotted(row.row, column.field),
|
|
576
|
+
row.row
|
|
577
|
+
);
|
|
578
|
+
}
|
|
579
|
+
if ("value" in entry) {
|
|
580
|
+
const { columnId: columnId2, value } = entry;
|
|
581
|
+
if (columnId2 !== void 0) {
|
|
582
|
+
const column = byId.get(columnId2);
|
|
583
|
+
if (column === void 0) {
|
|
584
|
+
return () => false;
|
|
585
|
+
}
|
|
586
|
+
return (row) => matchesTypedValue(
|
|
587
|
+
accessDotted(row.row, column.field),
|
|
588
|
+
value,
|
|
589
|
+
column.type
|
|
590
|
+
);
|
|
591
|
+
}
|
|
592
|
+
return (row) => columns.some(
|
|
593
|
+
(entry2) => matchesTypedValue(
|
|
594
|
+
accessDotted(row.row, entry2.column.field),
|
|
595
|
+
value,
|
|
596
|
+
entry2.column.type
|
|
597
|
+
)
|
|
598
|
+
);
|
|
599
|
+
}
|
|
600
|
+
const { columnId, query } = entry;
|
|
601
|
+
const test = compileFilterQuery(query);
|
|
602
|
+
if (columnId !== void 0) {
|
|
603
|
+
const column = byId.get(columnId);
|
|
604
|
+
if (column === void 0) {
|
|
605
|
+
return () => false;
|
|
606
|
+
}
|
|
607
|
+
return (row) => test(accessDotted(row.row, column.field));
|
|
608
|
+
}
|
|
609
|
+
return (row) => columns.some((entry2) => test(accessDotted(row.row, entry2.column.field)));
|
|
610
|
+
}
|
|
611
|
+
function matchesQuery(value, query) {
|
|
612
|
+
return compileFilterQuery(query)(value);
|
|
613
|
+
}
|
|
614
|
+
function filterQueryFor(filter, columnId) {
|
|
615
|
+
const entry = filter.find(
|
|
616
|
+
(candidate) => "query" in candidate && candidate.columnId === columnId
|
|
617
|
+
);
|
|
618
|
+
return entry?.query ?? null;
|
|
619
|
+
}
|
|
620
|
+
function setColumnFilter(filter, columnId, query) {
|
|
621
|
+
const index = filter.findIndex(
|
|
622
|
+
(entry) => !("combinator" in entry) && entry.columnId === columnId
|
|
623
|
+
);
|
|
624
|
+
if (query === "") {
|
|
625
|
+
return index === -1 ? filter : filter.filter((_entry, i) => i !== index);
|
|
626
|
+
}
|
|
627
|
+
const next = columnId === void 0 ? { query } : { columnId, query };
|
|
628
|
+
if (index === -1) {
|
|
629
|
+
return [...filter, next];
|
|
630
|
+
}
|
|
631
|
+
const existing = filter[index];
|
|
632
|
+
if (existing !== void 0 && "query" in existing && existing.query === query) {
|
|
633
|
+
return filter;
|
|
634
|
+
}
|
|
635
|
+
return filter.map((entry, i) => i === index ? next : entry);
|
|
636
|
+
}
|
|
637
|
+
function clearAllFilters(filter) {
|
|
638
|
+
return filter.length === 0 ? filter : [];
|
|
639
|
+
}
|
|
640
|
+
function filterRows(rows, filter, columns) {
|
|
641
|
+
if (filter.length === 0) {
|
|
642
|
+
return rows;
|
|
643
|
+
}
|
|
644
|
+
const byId = new Map(columns.map((entry) => [entry.id, entry.column]));
|
|
645
|
+
const active = filter.filter((entry) => isEntryUsable(entry, byId));
|
|
646
|
+
if (active.length === 0) {
|
|
647
|
+
return rows;
|
|
648
|
+
}
|
|
649
|
+
const compiled = active.map(
|
|
650
|
+
(entry) => compileFilterEntry(entry, byId, columns)
|
|
651
|
+
);
|
|
652
|
+
const filtered = rows.filter((row) => compiled.every((test) => test(row)));
|
|
653
|
+
return filtered.map(
|
|
654
|
+
(row, index) => row.rowIndex === index ? row : { ...row, rowIndex: index }
|
|
655
|
+
);
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
// src/util/rows.ts
|
|
659
|
+
function resolveShownRows(rows, filter, sort, columns) {
|
|
660
|
+
return sortRows(filterRows(rows, filter, columns), sort, columns);
|
|
661
|
+
}
|
|
662
|
+
function resolveRows(rowsById, ids) {
|
|
663
|
+
return ids.map((id) => rowsById.get(id)).filter((row) => row !== void 0);
|
|
664
|
+
}
|
|
665
|
+
function resolveColumns(columns, ids) {
|
|
666
|
+
return ids.map((id) => {
|
|
667
|
+
const columnIndex = columns.findIndex((entry) => entry.id === id);
|
|
668
|
+
const column = columns[columnIndex];
|
|
669
|
+
return column === void 0 ? void 0 : { columnId: id, column, columnIndex };
|
|
670
|
+
}).filter((column) => column !== void 0);
|
|
671
|
+
}
|
|
672
|
+
function resolveCell(rowsById, columns, cell) {
|
|
673
|
+
if (cell === null) {
|
|
674
|
+
return null;
|
|
675
|
+
}
|
|
676
|
+
const entry = rowsById.get(cell.rowId);
|
|
677
|
+
const columnIndex = columns.findIndex(
|
|
678
|
+
(candidate) => candidate.id === cell.columnId
|
|
679
|
+
);
|
|
680
|
+
const column = columns[columnIndex];
|
|
681
|
+
if (entry === void 0 || column === void 0) {
|
|
682
|
+
return null;
|
|
683
|
+
}
|
|
684
|
+
return {
|
|
685
|
+
rowId: cell.rowId,
|
|
686
|
+
columnId: cell.columnId,
|
|
687
|
+
row: entry.row,
|
|
688
|
+
column,
|
|
689
|
+
rowIndex: entry.rowIndex,
|
|
690
|
+
columnIndex,
|
|
691
|
+
value: accessDotted(entry.row, column.column.field)
|
|
692
|
+
};
|
|
693
|
+
}
|
|
317
694
|
export {
|
|
318
695
|
DEFAULT_COLUMN_SIZES,
|
|
696
|
+
HEADER_ROW,
|
|
319
697
|
KEYBOARD_STEP,
|
|
320
698
|
accessDotted,
|
|
321
699
|
alignmentForType,
|
|
322
700
|
applyColumnOrder,
|
|
323
701
|
applyColumnResize,
|
|
702
|
+
applySelectionIntent,
|
|
324
703
|
beginColumnResize,
|
|
704
|
+
buildKeyShortcuts,
|
|
325
705
|
clampColumnWidth,
|
|
706
|
+
clampFocus,
|
|
707
|
+
clearAllFilters,
|
|
326
708
|
clearSelection,
|
|
709
|
+
compareValues,
|
|
327
710
|
defineColumnsFromRows,
|
|
328
711
|
diffSelection,
|
|
712
|
+
filterQueryFor,
|
|
713
|
+
filterRows,
|
|
329
714
|
fitColumnsToWidth,
|
|
330
715
|
getColumnId,
|
|
716
|
+
intentOf,
|
|
331
717
|
isSameCell,
|
|
718
|
+
matchesQuery,
|
|
332
719
|
moveColumnBefore,
|
|
333
720
|
movesColumn,
|
|
721
|
+
nextFocusForKey,
|
|
722
|
+
resolveCell,
|
|
334
723
|
resolveColumnConstraints,
|
|
335
724
|
resolveColumnLabel,
|
|
336
725
|
resolveColumnWidths,
|
|
726
|
+
resolveColumns,
|
|
337
727
|
resolveDropBefore,
|
|
728
|
+
resolveKeyboardDropTarget,
|
|
338
729
|
resolveRowId,
|
|
730
|
+
resolveRows,
|
|
731
|
+
resolveShownRows,
|
|
732
|
+
revertColumnSize,
|
|
339
733
|
selectAll,
|
|
340
734
|
selectCell,
|
|
341
735
|
selectOnly,
|
|
342
736
|
selectRange,
|
|
737
|
+
setColumnFilter,
|
|
343
738
|
sizeColumnToContent,
|
|
739
|
+
sortDirectionFor,
|
|
740
|
+
sortPriorityFor,
|
|
741
|
+
sortRows,
|
|
344
742
|
toggleCellSelection,
|
|
743
|
+
toggleColumnSort,
|
|
345
744
|
toggleSelection,
|
|
346
745
|
totalColumnWidth
|
|
347
746
|
};
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/util/grid.ts","../src/util/ordering.ts","../src/util/selection.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 * 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"],"mappings":"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","names":["total"]}
|
|
1
|
+
{"version":3,"sources":["../src/util/grid.ts","../src/util/navigation.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/** Which of a column header's keyboard interactions to announce. */\nexport interface KeyShortcutCapabilities {\n reorderable: boolean;\n resizable: boolean;\n sortable: boolean;\n}\n\n/**\n * The `aria-keyshortcuts` string for a column header, naming only the keys\n * that column actually responds to — so the keys are discoverable, since\n * none of them is a convention a user would try unprompted.\n */\nexport function buildKeyShortcuts(\n capabilities: KeyShortcutCapabilities,\n): string {\n return [\n capabilities.reorderable ? \"Control+ArrowLeft Control+ArrowRight\" : \"\",\n // Alt+Enter is the keyboard equivalent of double-clicking the resize\n // handle to size the column to its content — same gate as the nudge\n // shortcuts, since both are resize actions.\n capabilities.resizable ? \"Alt+ArrowLeft Alt+ArrowRight Alt+Enter\" : \"\",\n capabilities.sortable ? \"Alt+ArrowUp\" : \"\",\n ]\n .filter(Boolean)\n .join(\" \");\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","/**\n * The header's row index. One coordinate space covers the header and the body\n * so that arrowing up out of the first row reaches the header without a case\n * of its own, and every other move is plain arithmetic.\n */\nexport const HEADER_ROW = -1;\n\n/** The cell the grid's single tab stop sits on. */\nexport interface GridFocus {\n readonly rowIndex: number;\n readonly columnIndex: number;\n}\n\n/** The modifier keys held with a navigation key press. */\nexport interface NavigationModifiers {\n readonly ctrlKey: boolean;\n readonly metaKey: boolean;\n readonly altKey: boolean;\n readonly shiftKey: boolean;\n}\n\n/**\n * Holds a coordinate inside the grid, so that a focus surviving a column\n * removal or a shorter page never leaves the grid with no tab stop at all —\n * which is a grid a keyboard cannot reach.\n */\nexport function clampFocus(\n focus: GridFocus,\n rowCount: number,\n columnCount: number,\n): GridFocus {\n return {\n rowIndex: Math.min(Math.max(focus.rowIndex, HEADER_ROW), rowCount - 1),\n columnIndex: Math.min(\n Math.max(focus.columnIndex, 0),\n Math.max(columnCount - 1, 0),\n ),\n };\n}\n\n/**\n * The focus a navigation key press moves to, or `null` when the key isn't\n * this grid's to handle — left for the browser or another handler.\n *\n * `pageSize` is a pre-measured row count rather than a callback: this\n * function takes plain data only, so a caller measures the viewport once and\n * hands over the number.\n */\nexport function nextFocusForKey(\n key: string,\n modifiers: NavigationModifiers,\n focus: GridFocus,\n rowCount: number,\n columnCount: number,\n pageSize: number,\n): GridFocus | null {\n // Alt on an arrow resizes and Ctrl on one reorders; the header claims both\n // before this runs, and elsewhere they belong to the browser.\n if (modifiers.altKey) {\n return null;\n }\n\n const { rowIndex, columnIndex } = focus;\n\n switch (key) {\n case \"ArrowLeft\":\n if (modifiers.ctrlKey) return null;\n return { rowIndex, columnIndex: columnIndex - 1 };\n case \"ArrowRight\":\n if (modifiers.ctrlKey) return null;\n return { rowIndex, columnIndex: columnIndex + 1 };\n case \"ArrowUp\":\n return { rowIndex: rowIndex - 1, columnIndex };\n case \"ArrowDown\":\n return { rowIndex: rowIndex + 1, columnIndex };\n // Ctrl takes Home and End to the grid's ends rather than the row's, which\n // is the one place Ctrl is navigation rather than reorder.\n case \"Home\":\n return modifiers.ctrlKey\n ? { rowIndex: HEADER_ROW, columnIndex: 0 }\n : { rowIndex, columnIndex: 0 };\n case \"End\":\n return modifiers.ctrlKey\n ? { rowIndex: rowCount - 1, columnIndex: columnCount - 1 }\n : { rowIndex, columnIndex: columnCount - 1 };\n case \"PageUp\":\n return { rowIndex: rowIndex - pageSize, columnIndex };\n case \"PageDown\":\n return { rowIndex: rowIndex + pageSize, columnIndex };\n default:\n return null;\n }\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\n/**\n * The `beforeId` a keyboard nudge on `columnId` produces, for `onDrop` to\n * apply the same way a pointer drop does — or `undefined` when the nudge is a\n * no-op: `columnId` is not in `orderedIds`, or the nudge would move it left\n * of the start.\n *\n * `null` is a real answer here, not \"no-op\": it means append to the end, the\n * same convention `moveColumnBefore` uses.\n */\nexport function resolveKeyboardDropTarget(\n orderedIds: ColumnOrderState,\n columnId: string,\n direction: -1 | 1,\n): string | null | undefined {\n const index = orderedIds.indexOf(columnId);\n if (index === -1) {\n return undefined;\n }\n\n /**\n * Moving right lands in front of the id two along: the gap immediately\n * after a column is the one it already sits in.\n */\n const targetIndex = direction === -1 ? index - 1 : index + 2;\n if (targetIndex < 0) {\n return undefined;\n }\n\n return orderedIds[targetIndex] ?? null;\n}\n","import type {\n CellSelectionMode,\n CellSelectionState,\n SelectedCellRef,\n SelectIntent,\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\n/** The intent a click or key press carries, by the modifiers held with it. */\nexport function intentOf(event: {\n ctrlKey: boolean;\n metaKey: boolean;\n shiftKey: boolean;\n}): SelectIntent {\n if (event.shiftKey) {\n return \"range\";\n }\n // Cmd on a Mac is Ctrl everywhere else, and both mean the same thing here.\n return event.ctrlKey || event.metaKey ? \"toggle\" : \"replace\";\n}\n\n/**\n * The transform an intent asks for. A function rather than a branch inside\n * each caller, so a row and a column cannot come to mean different things by\n * the same click.\n */\nexport function applySelectionIntent(\n selection: SelectionState,\n orderedIds: readonly string[],\n anchorId: string | null,\n id: string,\n intent: SelectIntent,\n mode: SelectionMode,\n): SelectionState {\n switch (intent) {\n case \"toggle\":\n return toggleSelection(selection, id, mode);\n // A range with no anchor yet spans from the id to itself, which is the\n // plain click a first Shift-click may as well be.\n case \"range\":\n return selectRange(selection, orderedIds, anchorId ?? id, id, mode);\n default:\n return selectOnly(selection, id, mode);\n }\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\n/**\n * `current` with `columnId` reverted to its width in `base` — or omitted\n * entirely if `base` had none — for Escape to restore a resize in progress.\n *\n * A column with no stored width in `base` is correctly omitted by deleting\n * the key rather than merging its `base` width back in, which would\n * otherwise leave it pinned and hidden from auto-fit.\n */\nexport function revertColumnSize(\n current: ColumnSizingState,\n base: ColumnSizingState,\n columnId: string,\n): ColumnSizingState {\n const next = Object.fromEntries(\n Object.entries(current).filter(([id]) => id !== columnId),\n );\n if (columnId in base) {\n next[columnId] = base[columnId] as number;\n }\n return next;\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 SelectedCell,\n SelectedCellRef,\n SelectedColumn,\n} from \"../types\";\nimport { accessDotted } from \"./grid\";\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\n/**\n * `ids` resolved back to the rows behind them, keyed by `rowsById`.\n *\n * An id with no row behind it is dropped rather than left `undefined`: a\n * selection outlives a change to the data, so that filtering a row out and\n * back leaves it selected, and only what still exists can be handed to a\n * callback.\n */\nexport function resolveRows<Row>(\n rowsById: ReadonlyMap<string, ResolvedRow<Row>>,\n ids: readonly string[],\n): readonly ResolvedRow<Row>[] {\n return ids.map((id) => rowsById.get(id)).filter((row) => row !== undefined);\n}\n\n/**\n * `ids` resolved back to the columns behind them, paired with the position\n * each holds in `columns` — a stale id is dropped, matching `resolveRows`.\n */\nexport function resolveColumns<Row, Node>(\n columns: readonly ResolvedColumn<Row, Node>[],\n ids: readonly string[],\n): readonly SelectedColumn<Row, Node>[] {\n return ids\n .map((id) => {\n const columnIndex = columns.findIndex((entry) => entry.id === id);\n const column = columns[columnIndex];\n return column === undefined\n ? undefined\n : { columnId: id, column, columnIndex };\n })\n .filter((column) => column !== undefined);\n}\n\n/**\n * `cell` resolved down to the row, column and value it addresses — `null`\n * when there is no cell, or when either half of it names a row or column\n * that no longer exists.\n */\nexport function resolveCell<Row, Node>(\n rowsById: ReadonlyMap<string, ResolvedRow<Row>>,\n columns: readonly ResolvedColumn<Row, Node>[],\n cell: SelectedCellRef | null,\n): SelectedCell<Row, Node> | null {\n if (cell === null) {\n return null;\n }\n const entry = rowsById.get(cell.rowId);\n const columnIndex = columns.findIndex(\n (candidate) => candidate.id === cell.columnId,\n );\n const column = columns[columnIndex];\n if (entry === undefined || column === undefined) {\n return null;\n }\n return {\n rowId: cell.rowId,\n columnId: cell.columnId,\n row: entry.row,\n column,\n rowIndex: entry.rowIndex,\n columnIndex,\n value: accessDotted(entry.row, column.column.field),\n };\n}\n"],"mappings":";AAOA,SAAS,SAAS,OAAkD;AAClE,SACE,OAAO,UAAU,YACjB,UAAU,QACV,CAAC,MAAM,QAAQ,KAAK,KACpB,EAAE,iBAAiB;AAEvB;AAEA,SAAS,QAAQ,OAA4B;AAC3C,MAAI,iBAAiB,KAAM,QAAO;AAClC,MAAI,OAAO,UAAU,SAAU,QAAO;AACtC,MAAI,OAAO,UAAU,SAAU,QAAO;AACtC,MAAI,OAAO,UAAU,UAAW,QAAO;AACvC,MAAI,OAAO,UAAU,SAAU,QAAO;AACtC,MAAI,OAAO,UAAU,SAAU,QAAO;AACtC,SAAO;AACT;AAMO,SAAS,iBAAiB,MAAmC;AAClE,UAAQ,MAAM;AAAA,IACZ,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AACH,aAAO;AAAA,IACT;AACE,aAAO;AAAA,EACX;AACF;AAEA,SAAS,aAAa,OAAiC;AACrD,SAAO,iBAAiB,QAAQ,KAAK,CAAC;AACxC;AAYO,SAAS,sBACd,MACkC;AAClC,QAAM,UAAmC,CAAC;AAC1C,QAAM,OAAO,oBAAI,IAAY;AAE7B,WAAS,IAAI,OAAe,OAAsB;AAChD,QAAI,KAAK,IAAI,KAAK,GAAG;AACnB;AAAA,IACF;AACA,SAAK,IAAI,KAAK;AACd,YAAQ,KAAK;AAAA,MACX;AAAA,MACA,MAAM,QAAQ,KAAK;AAAA,MACnB,WAAW,aAAa,KAAK;AAAA,IAC/B,CAAC;AAAA,EACH;AAEA,aAAW,OAAO,MAAM;AACtB,QAAI,CAAC,SAAS,GAAG,GAAG;AAClB;AAAA,IACF;AACA,eAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,GAAG,GAAG;AAC9C,UAAI,CAAC,SAAS,KAAK,GAAG;AACpB,YAAI,KAAK,KAAK;AACd;AAAA,MACF;AACA,iBAAW,CAAC,WAAW,WAAW,KAAK,OAAO,QAAQ,KAAK,GAAG;AAC5D,YAAI,CAAC,SAAS,WAAW,GAAG;AAC1B,cAAI,GAAG,GAAG,IAAI,SAAS,IAAI,WAAW;AAAA,QACxC;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;AAMO,SAAS,YACd,QACQ;AACR,SAAO,OAAO,MAAM,OAAO;AAC7B;AAYO,SAAS,aACd,KACA,OACA,UACQ;AACR,SAAO,WAAW,KAAK,KAAK,KAAK,OAAO,KAAK;AAC/C;AAOO,SAAS,mBACd,QACe;AACf,QAAM,EAAE,eAAe,IAAI;AAC3B,MAAI,mBAAmB,QAAW;AAChC,WAAO,OAAO,MAAM,MAAM,GAAG,EAAE,KAAK,GAAG;AAAA,EACzC;AAIA,SAAO,OAAO,mBAAmB,aAC5B,eAA8B,IAC/B;AACN;AAcO,SAAS,kBACd,cACQ;AACR,SAAO;AAAA,IACL,aAAa,cAAc,yCAAyC;AAAA;AAAA;AAAA;AAAA,IAIpE,aAAa,YAAY,2CAA2C;AAAA,IACpE,aAAa,WAAW,gBAAgB;AAAA,EAC1C,EACG,OAAO,OAAO,EACd,KAAK,GAAG;AACb;AASO,SAAS,aAAa,KAAc,MAAuB;AAChE,SAAO,KACJ,MAAM,GAAG,EACT,OAAO,CAAC,OAAO,QAAS,SAAS,KAAK,IAAI,MAAM,GAAG,IAAI,QAAY,GAAG;AAC3E;;;AC/KO,IAAM,aAAa;AAqBnB,SAAS,WACd,OACA,UACA,aACW;AACX,SAAO;AAAA,IACL,UAAU,KAAK,IAAI,KAAK,IAAI,MAAM,UAAU,UAAU,GAAG,WAAW,CAAC;AAAA,IACrE,aAAa,KAAK;AAAA,MAChB,KAAK,IAAI,MAAM,aAAa,CAAC;AAAA,MAC7B,KAAK,IAAI,cAAc,GAAG,CAAC;AAAA,IAC7B;AAAA,EACF;AACF;AAUO,SAAS,gBACd,KACA,WACA,OACA,UACA,aACA,UACkB;AAGlB,MAAI,UAAU,QAAQ;AACpB,WAAO;AAAA,EACT;AAEA,QAAM,EAAE,UAAU,YAAY,IAAI;AAElC,UAAQ,KAAK;AAAA,IACX,KAAK;AACH,UAAI,UAAU,QAAS,QAAO;AAC9B,aAAO,EAAE,UAAU,aAAa,cAAc,EAAE;AAAA,IAClD,KAAK;AACH,UAAI,UAAU,QAAS,QAAO;AAC9B,aAAO,EAAE,UAAU,aAAa,cAAc,EAAE;AAAA,IAClD,KAAK;AACH,aAAO,EAAE,UAAU,WAAW,GAAG,YAAY;AAAA,IAC/C,KAAK;AACH,aAAO,EAAE,UAAU,WAAW,GAAG,YAAY;AAAA;AAAA;AAAA,IAG/C,KAAK;AACH,aAAO,UAAU,UACb,EAAE,UAAU,YAAY,aAAa,EAAE,IACvC,EAAE,UAAU,aAAa,EAAE;AAAA,IACjC,KAAK;AACH,aAAO,UAAU,UACb,EAAE,UAAU,WAAW,GAAG,aAAa,cAAc,EAAE,IACvD,EAAE,UAAU,aAAa,cAAc,EAAE;AAAA,IAC/C,KAAK;AACH,aAAO,EAAE,UAAU,WAAW,UAAU,YAAY;AAAA,IACtD,KAAK;AACH,aAAO,EAAE,UAAU,WAAW,UAAU,YAAY;AAAA,IACtD;AACE,aAAO;AAAA,EACX;AACF;;;ACjFO,SAAS,iBACd,SACA,OACwC;AACxC,MAAI,MAAM,WAAW,GAAG;AACtB,WAAO;AAAA,EACT;AAEA,QAAM,OAAO,IAAI,IAAI,QAAQ,IAAI,CAAC,WAAW,CAAC,YAAY,MAAM,GAAG,MAAM,CAAC,CAAC;AAC3E,QAAM,SAAS,MACZ,IAAI,CAAC,OAAO,KAAK,IAAI,EAAE,CAAC,EACxB,OAAO,CAAC,WAAW,WAAW,MAAS;AAC1C,QAAM,QAAQ,IAAI,IAAI,OAAO,IAAI,CAAC,WAAW,YAAY,MAAM,CAAC,CAAC;AAEjE,SAAO;AAAA,IACL,GAAG;AAAA,IACH,GAAG,QAAQ,OAAO,CAAC,WAAW,CAAC,MAAM,IAAI,YAAY,MAAM,CAAC,CAAC;AAAA,EAC/D;AACF;AASO,SAAS,iBACd,YACA,SACA,UACkB;AAClB,MAAI,YAAY,YAAY,CAAC,WAAW,SAAS,OAAO,GAAG;AACzD,WAAO;AAAA,EACT;AAEA,QAAM,UAAU,WAAW,OAAO,CAAC,OAAO,OAAO,OAAO;AAGxD,QAAM,SAAS,aAAa,OAAO,QAAQ,SAAS,QAAQ,QAAQ,QAAQ;AAC5E,MAAI,WAAW,IAAI;AACjB,WAAO;AAAA,EACT;AAEA,QAAM,OAAO,CAAC,GAAG,OAAO;AACxB,OAAK,OAAO,QAAQ,GAAG,OAAO;AAC9B,SAAO,KAAK,MAAM,CAAC,IAAI,UAAU,OAAO,WAAW,KAAK,CAAC,IACrD,aACA;AACN;AASO,SAAS,YACd,YACA,SACA,UACS;AACT,SAAO,iBAAiB,YAAY,SAAS,QAAQ,MAAM;AAC7D;AASO,SAAS,kBACd,YACA,UACA,MACe;AACf,MAAI,SAAS,UAAU;AACrB,WAAO;AAAA,EACT;AACA,QAAM,QAAQ,WAAW,QAAQ,QAAQ;AACzC,SAAO,UAAU,KAAK,OAAQ,WAAW,QAAQ,CAAC,KAAK;AACzD;AAWO,SAAS,0BACd,YACA,UACA,WAC2B;AAC3B,QAAM,QAAQ,WAAW,QAAQ,QAAQ;AACzC,MAAI,UAAU,IAAI;AAChB,WAAO;AAAA,EACT;AAMA,QAAM,cAAc,cAAc,KAAK,QAAQ,IAAI,QAAQ;AAC3D,MAAI,cAAc,GAAG;AACnB,WAAO;AAAA,EACT;AAEA,SAAO,WAAW,WAAW,KAAK;AACpC;;;AClHA,IAAM,YAA2B,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC,EAAE;AAS1D,SAAS,cAAc,GAAmB,GAA4B;AACpE,SAAO,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,CAAC,IAAI,UAAU,OAAO,EAAE,KAAK,CAAC;AACxE;AAYO,SAAS,gBACd,WACA,IACA,MACgB;AAChB,MAAI,SAAS,OAAO;AAClB,WAAO;AAAA,EACT;AACA,MAAI,UAAU,SAAS,EAAE,GAAG;AAC1B,WAAO,UAAU,OAAO,CAAC,eAAe,eAAe,EAAE;AAAA,EAC3D;AACA,SAAO,SAAS,WAAW,CAAC,EAAE,IAAI,CAAC,GAAG,WAAW,EAAE;AACrD;AASO,SAAS,WACd,WACA,IACA,MACgB;AAChB,MAAI,SAAS,OAAO;AAClB,WAAO;AAAA,EACT;AACA,SAAO,UAAU,WAAW,KAAK,UAAU,CAAC,MAAM,KAAK,YAAY,CAAC,EAAE;AACxE;AAWO,SAAS,YACd,WACA,YACA,UACA,SACA,MACgB;AAChB,MAAI,SAAS,YAAY;AACvB,WAAO,WAAW,WAAW,SAAS,IAAI;AAAA,EAC5C;AAEA,QAAM,SAAS,WAAW,QAAQ,QAAQ;AAC1C,QAAM,QAAQ,WAAW,QAAQ,OAAO;AACxC,MAAI,WAAW,MAAM,UAAU,IAAI;AACjC,WAAO;AAAA,EACT;AAEA,QAAM,OAAO,WAAW;AAAA,IACtB,KAAK,IAAI,QAAQ,KAAK;AAAA,IACtB,KAAK,IAAI,QAAQ,KAAK,IAAI;AAAA,EAC5B;AACA,SAAO,cAAc,WAAW,IAAI,IAAI,YAAY;AACtD;AAMO,SAAS,UACd,WACA,YACA,MACgB;AAChB,MAAI,SAAS,YAAY;AACvB,WAAO;AAAA,EACT;AACA,SAAO,cAAc,WAAW,UAAU,IAAI,YAAY;AAC5D;AAEO,SAAS,eAAe,WAA2C;AACxE,SAAO,UAAU,WAAW,IAAI,YAAY,CAAC;AAC/C;AASO,SAAS,cACd,UACA,MACe;AACf,MAAI,aAAa,MAAM;AACrB,WAAO;AAAA,EACT;AAEA,QAAM,SAAS,IAAI,IAAI,QAAQ;AAC/B,QAAM,QAAQ,IAAI,IAAI,IAAI;AAC1B,SAAO;AAAA,IACL,OAAO,KAAK,OAAO,CAAC,OAAO,CAAC,OAAO,IAAI,EAAE,CAAC;AAAA,IAC1C,SAAS,SAAS,OAAO,CAAC,OAAO,CAAC,MAAM,IAAI,EAAE,CAAC;AAAA,EACjD;AACF;AAEO,SAAS,WACd,GACA,GACS;AACT,MAAI,MAAM,QAAQ,MAAM,MAAM;AAC5B,WAAO,MAAM;AAAA,EACf;AACA,SAAO,EAAE,UAAU,EAAE,SAAS,EAAE,aAAa,EAAE;AACjD;AAMO,SAAS,WACd,WACA,MACA,MACoB;AACpB,MAAI,SAAS,SAAS,WAAW,WAAW,IAAI,GAAG;AACjD,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAMO,SAAS,oBACd,WACA,MACA,MACoB;AACpB,MAAI,SAAS,OAAO;AAClB,WAAO;AAAA,EACT;AACA,SAAO,WAAW,WAAW,IAAI,IAAI,OAAO;AAC9C;AAGO,SAAS,SAAS,OAIR;AACf,MAAI,MAAM,UAAU;AAClB,WAAO;AAAA,EACT;AAEA,SAAO,MAAM,WAAW,MAAM,UAAU,WAAW;AACrD;AAOO,SAAS,qBACd,WACA,YACA,UACA,IACA,QACA,MACgB;AAChB,UAAQ,QAAQ;AAAA,IACd,KAAK;AACH,aAAO,gBAAgB,WAAW,IAAI,IAAI;AAAA;AAAA;AAAA,IAG5C,KAAK;AACH,aAAO,YAAY,WAAW,YAAY,YAAY,IAAI,IAAI,IAAI;AAAA,IACpE;AACE,aAAO,WAAW,WAAW,IAAI,IAAI;AAAA,EACzC;AACF;;;ACzMO,IAAM,uBAA2C;AAAA,EACtD,OAAO;AAAA,EACP,UAAU;AAAA,EACV,UAAU,OAAO;AACnB;AAGO,IAAM,gBAAgB;AAE7B,SAAS,aACP,UACoB;AACpB,SAAO,EAAE,GAAG,sBAAsB,GAAG,SAAS;AAChD;AAMO,SAAS,yBACd,QACA,UACmB;AACnB,QAAM,WAAW,aAAa,QAAQ;AACtC,SAAO;AAAA,IACL,UAAU,OAAO,YAAY,SAAS;AAAA,IACtC,UAAU,OAAO,YAAY,SAAS;AAAA,EACxC;AACF;AAGO,SAAS,iBACd,OACA,aACQ;AACR,SAAO,KAAK,IAAI,KAAK,IAAI,OAAO,YAAY,QAAQ,GAAG,YAAY,QAAQ;AAC7E;AAWO,SAAS,oBACd,SACA,QACA,SACsC;AACtC,QAAM,WAAW,aAAa,SAAS,KAAK;AAE5C,SAAO,QAAQ,IAAI,CAAC,WAAW;AAC7B,UAAM,KAAK,YAAY,MAAM;AAC7B,UAAM,SAAS,OAAO,EAAE;AAExB,WAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA,OAAO,WAAW;AAAA,MAClB,OAAO;AAAA,QACL,UAAU,OAAO,SAAS,SAAS;AAAA,QACnC,yBAAyB,QAAQ,QAAQ;AAAA,MAC3C;AAAA,MACA,OAAO,mBAAmB,MAAM;AAAA,MAChC,WAAW,OAAO,aAAa,SAAS,aAAa;AAAA,MACrD,aAAa,OAAO,eAAe,SAAS,eAAe;AAAA,MAC3D,WAAW,OAAO,aAAa,iBAAiB,OAAO,QAAQ,QAAQ;AAAA,IACzE;AAAA,EACF,CAAC;AACH;AAEO,SAAS,iBACd,UACQ;AACR,SAAO,SAAS,OAAO,CAAC,OAAO,UAAU,QAAQ,MAAM,OAAO,CAAC;AACjE;AASO,SAAS,kBACd,QACA,YACA,eACA,UACqB;AACrB,SAAO;AAAA,IACL,UAAU,YAAY,MAAM;AAAA,IAC5B;AAAA,IACA;AAAA,IACA,aAAa,yBAAyB,QAAQ,QAAQ;AAAA,EACxD;AACF;AAMO,SAAS,kBACd,SACA,UACQ;AACR,SAAO;AAAA,IACL,QAAQ,cAAc,WAAW,QAAQ;AAAA,IACzC,QAAQ;AAAA,EACV;AACF;AAcA,SAAS,aACP,OACA,MACS;AACT,SAAO,KAAK,QAAQ,KAAK,QAAQ,SAAS,KAAK,QAAQ,SAAS,KAAK;AACvE;AAQA,SAAS,aACP,SACA,WACM;AACN,aAAS;AACP,UAAM,UAAU,QAAQ;AAAA,MACtB,CAAC,SAAS,KAAK,cAAc,CAAC,aAAa,KAAK,OAAO,IAAI;AAAA,IAC7D;AACA,UAAM,OAAO,UAAU,QAAQ,OAAO,CAAC,GAAG,MAAM,IAAI,EAAE,OAAO,CAAC,CAAC;AAC/D,QAAI,QAAQ,KAAK,QAAQ,WAAW,GAAG;AACrC;AAAA,IACF;AAEA,UAAM,OAAO,QAAQ,OAAO,CAAC,OAAO,SAAS,QAAQ,KAAK,OAAO,CAAC;AAClE,QAAI,UAAU;AAEd,eAAW,QAAQ,SAAS;AAC1B,YAAM,QACJ,SAAS,IAAI,OAAO,QAAQ,SAAU,OAAO,KAAK,QAAS;AAC7D,YAAM,OAAO,KAAK,SAAS,KAAK,QAAQ,KAAK,QAAQ,QAAQ,CAAC;AAE9D,UAAI,aAAa,MAAM,IAAI,GAAG;AAC5B,aAAK,QAAQ,KAAK;AAClB,kBAAU;AAAA,MACZ,OAAO;AACL,aAAK,QAAQ;AAAA,MACf;AAAA,IACF;AAEA,QAAI,CAAC,SAAS;AACZ;AAAA,IACF;AAAA,EACF;AACF;AAcO,SAAS,kBACd,UACA,gBACA,UACsC;AACtC,QAAM,SAAS,KAAK,MAAM,cAAc;AACxC,QAAM,QAAQ,iBAAiB,QAAQ;AACvC,MAAI,SAAS,WAAW,KAAK,UAAU,QAAQ;AAC7C,WAAO;AAAA,EACT;AAEA,QAAM,UAAU,QAAQ;AACxB,QAAM,UAAoC,SAAS,IAAI,CAAC,UAAU;AAChE,UAAM,cAAc,yBAAyB,MAAM,QAAQ,QAAQ;AACnE,WAAO;AAAA,MACL;AAAA,MACA,OAAO,MAAM;AAAA,MACb,OAAO,UAAU,YAAY,WAAW,YAAY;AAAA,MACpD,YAAY,CAAC,MAAM;AAAA,IACrB;AAAA,EACF,CAAC;AAED,MAAI,CAAC,SAAS;AAGZ,UAAM,QAAQ,QAAQ;AAAA,MACpB,CAAC,KAAK,SAAS,OAAO,KAAK,aAAa,KAAK,QAAQ,KAAK;AAAA,MAC1D;AAAA,IACF;AACA,QAAI,QAAQ,QAAQ;AAClB,aAAO;AAAA,IACT;AAAA,EACF;AAEA,eAAa,SAAS,CAAC,QAAS,UAAU,SAAS,MAAM,MAAM,MAAO;AAMtE,QAAM,UAAU,QAAQ;AAAA,IAAI,CAAC,SAC3B,UAAU,KAAK,MAAM,KAAK,KAAK,IAAI,KAAK,KAAK,KAAK,KAAK;AAAA,EACzD;AACA,MAAI,YAAY,SAAS,QAAQ,OAAO,CAACA,QAAO,UAAUA,SAAQ,OAAO,CAAC;AAE1E,SAAO,QAAQ,IAAI,CAAC,MAAM,UAAU;AAClC,QAAI,QAAQ,QAAQ,KAAK,KAAK,KAAK;AAInC,UAAM,UAAU,UAAU,QAAQ,KAAK,QAAQ,QAAQ,KAAK;AAE5D,QAAI,cAAc,KAAK,KAAK,cAAc,SAAS;AACjD,eAAS,UAAU,IAAI;AACvB,mBAAa,UAAU,KAAK;AAAA,IAC9B;AAEA,WAAO,EAAE,GAAG,KAAK,OAAO,MAAM;AAAA,EAChC,CAAC;AACH;AAQO,SAAS,oBACd,eACA,aACA,UAAU,GACF;AACR,SAAO,iBAAiB,KAAK,KAAK,gBAAgB,OAAO,GAAG,WAAW;AACzE;AAUO,SAAS,iBACd,SACA,MACA,UACmB;AACnB,QAAM,OAAO,OAAO;AAAA,IAClB,OAAO,QAAQ,OAAO,EAAE,OAAO,CAAC,CAAC,EAAE,MAAM,OAAO,QAAQ;AAAA,EAC1D;AACA,MAAI,YAAY,MAAM;AACpB,SAAK,QAAQ,IAAI,KAAK,QAAQ;AAAA,EAChC;AACA,SAAO;AACT;;;ACjSA,SAAS,QAAQ,OAAyB;AACxC,SAAO,UAAU,QAAQ,UAAU;AACrC;AAGA,SAAS,OAAO,OAAwB;AACtC,SAAO,iBAAiB,OACpB,MAAM,QAAQ,IACd,IAAI,KAAK,OAAO,KAAK,CAAC,EAAE,QAAQ;AACtC;AAYO,SAAS,cACd,GACA,GACA,MACQ;AACR,MAAI,QAAQ,CAAC,GAAG;AACd,WAAO,QAAQ,CAAC,IAAI,IAAI;AAAA,EAC1B;AACA,MAAI,QAAQ,CAAC,GAAG;AACd,WAAO;AAAA,EACT;AAEA,UAAQ,MAAM;AAAA,IACZ,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AACH,aAAO,OAAO,CAAC,IAAI,OAAO,CAAC;AAAA,IAC7B,KAAK;AACH,aAAO,OAAO,QAAQ,CAAC,CAAC,IAAI,OAAO,QAAQ,CAAC,CAAC;AAAA,IAC/C,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AACH,aAAO,OAAO,CAAC,IAAI,OAAO,CAAC;AAAA,IAC7B;AACE,aAAO,OAAO,CAAC,EAAE,cAAc,OAAO,CAAC,CAAC;AAAA,EAC5C;AACF;AAGO,SAAS,iBACd,MACA,UACsB;AACtB,SAAO,KAAK,KAAK,CAAC,UAAU,MAAM,aAAa,QAAQ,GAAG,aAAa;AACzE;AAGO,SAAS,gBACd,MACA,UACe;AACf,QAAM,QAAQ,KAAK,UAAU,CAAC,UAAU,MAAM,aAAa,QAAQ;AACnE,SAAO,UAAU,KAAK,OAAO,QAAQ;AACvC;AAeO,SAAS,iBACd,MACA,UACA,SACiB;AACjB,QAAM,QAAQ,KAAK,UAAU,CAAC,UAAU,MAAM,aAAa,QAAQ;AACnE,QAAM,cAAc,KAAK,WAAW,KAAK,UAAU;AAEnD,MAAI,CAAC,QAAQ,SAAS,CAAC,aAAa;AAClC,WAAO,CAAC,EAAE,UAAU,WAAW,MAAM,CAAC;AAAA,EACxC;AAEA,QAAM,UAAU,UAAU,KAAK,SAAY,KAAK,KAAK,GAAG;AACxD,QAAM,OACJ,YAAY,SAAY,QAAQ,YAAY,QAAQ,SAAS;AAE/D,MAAI,SAAS,MAAM;AACjB,WAAO,KAAK,OAAO,CAAC,UAAU,MAAM,aAAa,QAAQ;AAAA,EAC3D;AACA,MAAI,UAAU,IAAI;AAChB,WAAO,CAAC,GAAG,MAAM,EAAE,UAAU,WAAW,KAAK,CAAC;AAAA,EAChD;AACA,SAAO,KAAK;AAAA,IAAI,CAAC,OAAO,MACtB,MAAM,QAAQ,EAAE,UAAU,WAAW,KAAK,IAAI;AAAA,EAChD;AACF;AAmBO,SAAS,SACd,MACA,MACA,SAC6B;AAC7B,MAAI,KAAK,WAAW,GAAG;AACrB,WAAO;AAAA,EACT;AAEA,QAAM,OAAO,IAAI,IAAI,QAAQ,IAAI,CAAC,UAAU,CAAC,MAAM,IAAI,MAAM,MAAM,CAAC,CAAC;AACrE,QAAM,SAAS,KAAK,OAAO,CAAC,UAAU,KAAK,IAAI,MAAM,QAAQ,CAAC;AAC9D,MAAI,OAAO,WAAW,GAAG;AACvB,WAAO;AAAA,EACT;AAEA,QAAM,SAAS,CAAC,GAAG,IAAI,EAAE,KAAK,CAAC,GAAG,MAAM;AACtC,eAAW,EAAE,UAAU,UAAU,KAAK,QAAQ;AAC5C,YAAM,SAAS,KAAK,IAAI,QAAQ;AAChC,UAAI,WAAW,QAAW;AACxB;AAAA,MACF;AACA,YAAM,OAAO,aAAa,EAAE,KAAK,OAAO,KAAK;AAC7C,YAAM,QAAQ,aAAa,EAAE,KAAK,OAAO,KAAK;AAC9C,YAAM,YAAY,QAAQ,IAAI;AAC9B,YAAM,aAAa,QAAQ,KAAK;AAChC,UAAI,aAAa,YAAY;AAC3B,YAAI,aAAa,YAAY;AAC3B;AAAA,QACF;AACA,eAAO,YAAY,IAAI;AAAA,MACzB;AAEA,YAAM,SAAS,cAAc,MAAM,OAAO,OAAO,IAAI;AACrD,UAAI,WAAW,GAAG;AAChB,eAAO,cAAc,QAAQ,SAAS,CAAC;AAAA,MACzC;AAAA,IACF;AACA,WAAO;AAAA,EACT,CAAC;AAED,SAAO,OAAO;AAAA,IAAI,CAAC,KAAK,UACtB,IAAI,aAAa,QAAQ,MAAM,EAAE,GAAG,KAAK,UAAU,MAAM;AAAA,EAC3D;AACF;;;ACzJA,SAAS,mBAAmB,OAA8B;AACxD,QAAM,QAAQ,MAAM,WAAW,GAAG;AAClC,QAAM,MAAM,MAAM,SAAS,GAAG;AAC9B,MAAI,OAAO;AACX,MAAI,OAAO;AACT,WAAO,KAAK,MAAM,CAAC;AAAA,EACrB;AACA,MAAI,KAAK;AACP,WAAO,KAAK,MAAM,GAAG,EAAE;AAAA,EACzB;AACA,SAAO,KAAK,YAAY;AAExB,MAAI,SAAS,KAAK;AAChB,WAAO,EAAE,MAAM,YAAY,KAAK;AAAA,EAClC;AACA,MAAI,OAAO;AACT,WAAO,EAAE,MAAM,YAAY,KAAK;AAAA,EAClC;AACA,MAAI,KAAK;AACP,WAAO,EAAE,MAAM,cAAc,KAAK;AAAA,EACpC;AACA,SAAO,EAAE,MAAM,SAAS,KAAK;AAC/B;AAEA,SAAS,aAAa,OAAyB;AAC7C,SAAO,UAAU,QAAQ,UAAU;AACrC;AAMA,SAAS,mBAAmB,OAA4C;AACtE,QAAM,EAAE,MAAM,KAAK,IAAI,mBAAmB,KAAK;AAC/C,SAAO,CAAC,UAAmB;AACzB,UAAM,aAAa,aAAa,KAAK,IAAI,KAAK,OAAO,KAAK,GAAG,YAAY;AACzE,YAAQ,MAAM;AAAA,MACZ,KAAK;AACH,eAAO,cAAc;AAAA,MACvB,KAAK;AACH,eAAO,UAAU,SAAS,IAAI;AAAA,MAChC,KAAK;AACH,eAAO,UAAU,WAAW,IAAI;AAAA,MAClC,KAAK;AACH,eAAO,UAAU,SAAS,IAAI;AAAA,IAClC;AAAA,EACF;AACF;AAOA,SAAS,kBACP,OACA,QACA,MACS;AACT,MAAI,OAAO,WAAW,UAAU;AAC9B,QACE,SAAS,YACT,SAAS,aACT,SAAS,cACT,SAAS,WACT;AACA,aAAO;AAAA,IACT;AACA,WAAO,OAAO,UAAU,YAAY,UAAU;AAAA,EAChD;AACA,MAAI,OAAO,WAAW,WAAW;AAC/B,QAAI,SAAS,WAAW;AACtB,aAAO;AAAA,IACT;AACA,WAAO,OAAO,UAAU,aAAa,UAAU;AAAA,EACjD;AACA,MAAI,SAAS,UAAU,SAAS,cAAc,SAAS,QAAQ;AAC7D,WAAO;AAAA,EACT;AACA,SAAO,iBAAiB,QAAQ,MAAM,QAAQ,MAAM,OAAO,QAAQ;AACrE;AAkBA,SAAS,cACP,OACA,MACS;AACT,MAAI,gBAAgB,OAAO;AACzB,WAAO;AAAA,EACT;AACA,SAAO,MAAM,aAAa,UAAa,KAAK,IAAI,MAAM,QAAQ;AAChE;AAMA,SAAS,mBACP,OACA,MACA,SACoC;AACpC,MAAI,gBAAgB,OAAO;AACzB,UAAM,QAAQ,MAAM,QACjB,OAAO,CAAC,WAAW,cAAc,QAAQ,IAAI,CAAC,EAC9C,IAAI,CAAC,WAAW,mBAAmB,QAAQ,MAAM,OAAO,CAAC;AAC5D,WAAO,MAAM,eAAe,QACxB,CAAC,QAAQ,MAAM,MAAM,CAACC,UAASA,MAAK,GAAG,CAAC,IACxC,CAAC,QAAQ,MAAM,KAAK,CAACA,UAASA,MAAK,GAAG,CAAC;AAAA,EAC7C;AAEA,MAAI,eAAe,OAAO;AACxB,UAAM,EAAE,UAAAC,WAAU,UAAU,IAAI;AAChC,UAAM,SAASA,cAAa,SAAY,SAAY,KAAK,IAAIA,SAAQ;AACrE,WAAO,CAAC,QACN;AAAA,MACE,WAAW,SAAY,SAAY,aAAa,IAAI,KAAK,OAAO,KAAK;AAAA,MACrE,IAAI;AAAA,IACN;AAAA,EACJ;AAEA,MAAI,WAAW,OAAO;AACpB,UAAM,EAAE,UAAAA,WAAU,MAAM,IAAI;AAC5B,QAAIA,cAAa,QAAW;AAC1B,YAAM,SAAS,KAAK,IAAIA,SAAQ;AAChC,UAAI,WAAW,QAAW;AACxB,eAAO,MAAM;AAAA,MACf;AACA,aAAO,CAAC,QACN;AAAA,QACE,aAAa,IAAI,KAAK,OAAO,KAAK;AAAA,QAClC;AAAA,QACA,OAAO;AAAA,MACT;AAAA,IACJ;AACA,WAAO,CAAC,QACN,QAAQ;AAAA,MAAK,CAACC,WACZ;AAAA,QACE,aAAa,IAAI,KAAKA,OAAM,OAAO,KAAK;AAAA,QACxC;AAAA,QACAA,OAAM,OAAO;AAAA,MACf;AAAA,IACF;AAAA,EACJ;AAEA,QAAM,EAAE,UAAU,MAAM,IAAI;AAC5B,QAAM,OAAO,mBAAmB,KAAK;AACrC,MAAI,aAAa,QAAW;AAC1B,UAAM,SAAS,KAAK,IAAI,QAAQ;AAChC,QAAI,WAAW,QAAW;AACxB,aAAO,MAAM;AAAA,IACf;AACA,WAAO,CAAC,QAAQ,KAAK,aAAa,IAAI,KAAK,OAAO,KAAK,CAAC;AAAA,EAC1D;AACA,SAAO,CAAC,QACN,QAAQ,KAAK,CAACA,WAAU,KAAK,aAAa,IAAI,KAAKA,OAAM,OAAO,KAAK,CAAC,CAAC;AAC3E;AAYO,SAAS,aAAa,OAAgB,OAAwB;AACnE,SAAO,mBAAmB,KAAK,EAAE,KAAK;AACxC;AASO,SAAS,eACd,QACA,UACe;AACf,QAAM,QAAQ,OAAO;AAAA,IACnB,CAAC,cACC,WAAW,aAAa,UAAU,aAAa;AAAA,EACnD;AACA,SAAO,OAAO,SAAS;AACzB;AAaO,SAAS,gBACd,QACA,UACA,OACkB;AAClB,QAAM,QAAQ,OAAO;AAAA,IACnB,CAAC,UAAU,EAAE,gBAAgB,UAAU,MAAM,aAAa;AAAA,EAC5D;AAEA,MAAI,UAAU,IAAI;AAChB,WAAO,UAAU,KAAK,SAAS,OAAO,OAAO,CAAC,QAAQ,MAAM,MAAM,KAAK;AAAA,EACzE;AAEA,QAAM,OACJ,aAAa,SAAY,EAAE,MAAM,IAAI,EAAE,UAAU,MAAM;AAEzD,MAAI,UAAU,IAAI;AAChB,WAAO,CAAC,GAAG,QAAQ,IAAI;AAAA,EACzB;AACA,QAAM,WAAW,OAAO,KAAK;AAC7B,MACE,aAAa,UACb,WAAW,YACX,SAAS,UAAU,OACnB;AACA,WAAO;AAAA,EACT;AACA,SAAO,OAAO,IAAI,CAAC,OAAO,MAAO,MAAM,QAAQ,OAAO,KAAM;AAC9D;AAGO,SAAS,gBACd,QACkB;AAClB,SAAO,OAAO,WAAW,IAAI,SAAS,CAAC;AACzC;AAmBO,SAAS,WACd,MACA,QACA,SAC6B;AAC7B,MAAI,OAAO,WAAW,GAAG;AACvB,WAAO;AAAA,EACT;AAEA,QAAM,OAAO,IAAI,IAAI,QAAQ,IAAI,CAAC,UAAU,CAAC,MAAM,IAAI,MAAM,MAAM,CAAC,CAAC;AACrE,QAAM,SAAS,OAAO,OAAO,CAAC,UAAU,cAAc,OAAO,IAAI,CAAC;AAClE,MAAI,OAAO,WAAW,GAAG;AACvB,WAAO;AAAA,EACT;AAEA,QAAM,WAAW,OAAO;AAAA,IAAI,CAAC,UAC3B,mBAAmB,OAAO,MAAM,OAAO;AAAA,EACzC;AACA,QAAM,WAAW,KAAK,OAAO,CAAC,QAAQ,SAAS,MAAM,CAAC,SAAS,KAAK,GAAG,CAAC,CAAC;AAEzE,SAAO,SAAS;AAAA,IAAI,CAAC,KAAK,UACxB,IAAI,aAAa,QAAQ,MAAM,EAAE,GAAG,KAAK,UAAU,MAAM;AAAA,EAC3D;AACF;;;ACpSO,SAAS,iBACd,MACA,QACA,MACA,SAC6B;AAC7B,SAAO,SAAS,WAAW,MAAM,QAAQ,OAAO,GAAG,MAAM,OAAO;AAClE;AAUO,SAAS,YACd,UACA,KAC6B;AAC7B,SAAO,IAAI,IAAI,CAAC,OAAO,SAAS,IAAI,EAAE,CAAC,EAAE,OAAO,CAAC,QAAQ,QAAQ,MAAS;AAC5E;AAMO,SAAS,eACd,SACA,KACsC;AACtC,SAAO,IACJ,IAAI,CAAC,OAAO;AACX,UAAM,cAAc,QAAQ,UAAU,CAAC,UAAU,MAAM,OAAO,EAAE;AAChE,UAAM,SAAS,QAAQ,WAAW;AAClC,WAAO,WAAW,SACd,SACA,EAAE,UAAU,IAAI,QAAQ,YAAY;AAAA,EAC1C,CAAC,EACA,OAAO,CAAC,WAAW,WAAW,MAAS;AAC5C;AAOO,SAAS,YACd,UACA,SACA,MACgC;AAChC,MAAI,SAAS,MAAM;AACjB,WAAO;AAAA,EACT;AACA,QAAM,QAAQ,SAAS,IAAI,KAAK,KAAK;AACrC,QAAM,cAAc,QAAQ;AAAA,IAC1B,CAAC,cAAc,UAAU,OAAO,KAAK;AAAA,EACvC;AACA,QAAM,SAAS,QAAQ,WAAW;AAClC,MAAI,UAAU,UAAa,WAAW,QAAW;AAC/C,WAAO;AAAA,EACT;AACA,SAAO;AAAA,IACL,OAAO,KAAK;AAAA,IACZ,UAAU,KAAK;AAAA,IACf,KAAK,MAAM;AAAA,IACX;AAAA,IACA,UAAU,MAAM;AAAA,IAChB;AAAA,IACA,OAAO,aAAa,MAAM,KAAK,OAAO,OAAO,KAAK;AAAA,EACpD;AACF;","names":["total","test","columnId","entry"]}
|