@ahrowe/ui 0.30.0 → 0.31.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.
Files changed (40) hide show
  1. package/dist/esm/common/tree/tree.mjs +1 -1
  2. package/dist/esm/common/tree/tree.mjs.map +1 -1
  3. package/dist/esm/common/virtualList/useColumnResize.mjs +2 -0
  4. package/dist/esm/common/virtualList/useColumnResize.mjs.map +1 -0
  5. package/dist/esm/common/virtualList/useColumns.mjs +1 -1
  6. package/dist/esm/common/virtualList/useColumns.mjs.map +1 -1
  7. package/dist/esm/common/virtualList/useHeaderNavigation.mjs +2 -0
  8. package/dist/esm/common/virtualList/useHeaderNavigation.mjs.map +1 -0
  9. package/dist/esm/common/virtualList/useRowNavigation.mjs +2 -0
  10. package/dist/esm/common/virtualList/useRowNavigation.mjs.map +1 -0
  11. package/dist/esm/common/virtualList/useSorting.mjs +2 -0
  12. package/dist/esm/common/virtualList/useSorting.mjs.map +1 -0
  13. package/dist/esm/common/virtualList/useVirtualWindow.mjs +1 -1
  14. package/dist/esm/common/virtualList/useVirtualWindow.mjs.map +1 -1
  15. package/dist/esm/common/virtualList/virtualList.mjs +1 -1
  16. package/dist/esm/common/virtualList/virtualList.mjs.map +1 -1
  17. package/dist/esm/common/virtualList/virtualList.module.mjs +1 -1
  18. package/dist/esm/common/virtualList/virtualList.module.mjs.map +1 -1
  19. package/dist/esm/common/virtualList/virtualList.utils.mjs +1 -1
  20. package/dist/esm/common/virtualList/virtualList.utils.mjs.map +1 -1
  21. package/dist/esm/common/virtualList/virtualListHeader.mjs +2 -0
  22. package/dist/esm/common/virtualList/virtualListHeader.mjs.map +1 -0
  23. package/dist/esm/common/virtualList/virtualRow.mjs +1 -1
  24. package/dist/esm/common/virtualList/virtualRow.mjs.map +1 -1
  25. package/dist/index.cjs +3 -3
  26. package/dist/index.cjs.map +1 -1
  27. package/dist/style.css +1 -1
  28. package/dist/types/common/virtualList/useColumnResize.d.ts +59 -0
  29. package/dist/types/common/virtualList/useColumns.d.ts +12 -2
  30. package/dist/types/common/virtualList/useHeaderNavigation.d.ts +22 -0
  31. package/dist/types/common/virtualList/useRowNavigation.d.ts +50 -0
  32. package/dist/types/common/virtualList/useSorting.d.ts +26 -0
  33. package/dist/types/common/virtualList/useVirtualWindow.d.ts +1 -8
  34. package/dist/types/common/virtualList/virtualList.d.ts +1 -1
  35. package/dist/types/common/virtualList/virtualList.types.d.ts +82 -1
  36. package/dist/types/common/virtualList/virtualList.utils.d.ts +83 -1
  37. package/dist/types/common/virtualList/virtualListHeader.d.ts +35 -0
  38. package/dist/types/common/virtualList/virtualRow.d.ts +14 -1
  39. package/docs/VirtualList.md +125 -7
  40. package/package.json +1 -1
@@ -0,0 +1,59 @@
1
+ import { default as React } from 'react';
2
+ import { VirtualListColumn } from './virtualList.types';
3
+ interface UseColumnResizeArgs<T> {
4
+ columns?: VirtualListColumn<T>[];
5
+ /**
6
+ * The columns currently rendered, in order. A ref rather than a value because
7
+ * the visible set is derived one hook later, and only the freeze reads it, at
8
+ * event time.
9
+ */
10
+ visibleColumnsRef: React.RefObject<VirtualListColumn<T>[]>;
11
+ resizableColumns: boolean;
12
+ /** Controlled widths. When provided, internal state is ignored. */
13
+ columnWidths?: Record<string, number>;
14
+ onColumnWidthsChange?: (widths: Record<string, number>) => void;
15
+ /** Restores widths saved under this key. Uncontrolled mode only. */
16
+ persistColumnsKey?: string;
17
+ rootRef: React.RefObject<HTMLDivElement | null>;
18
+ }
19
+ export interface UseColumnResizeResult<T> {
20
+ widths: Record<string, number>;
21
+ /**
22
+ * True once every visible column has an exact px width. Nothing moves at that
23
+ * moment, but from then on the layout is pixels rather than `fr`/`fit`.
24
+ */
25
+ isFrozen: boolean;
26
+ isResizing: boolean;
27
+ /** Drops every user width and hands the layout back to the column definitions. */
28
+ reset: () => void;
29
+ /** Whether this column takes a resize at all, by handle or by keyboard. */
30
+ isResizable: (col: VirtualListColumn<T>) => boolean;
31
+ /** Widens (or, negative, narrows) one column. The keyboard entry point. */
32
+ resizeBy: (key: string, delta: number) => void;
33
+ /** Takes one column down to its minimum. */
34
+ resizeToMin: (key: string) => void;
35
+ /** Sizes one column to its widest rendered cell. */
36
+ autoFit: (key: string) => void;
37
+ /** Pointer props for one column's handle, or null when that column can't be resized. */
38
+ getHandleProps: (col: VirtualListColumn<T>) => React.HTMLAttributes<HTMLDivElement> | null;
39
+ }
40
+ /**
41
+ * User column resizing: the width state, and a drag that doesn't go through
42
+ * React.
43
+ *
44
+ * A pointer move writes the dragged column's custom property straight onto the
45
+ * list element. The header and every row read that same property, so one DOM
46
+ * write resizes all of them in a single layout pass. Going through state
47
+ * instead would re-render the header plus every visible row on every frame, and
48
+ * the handle would trail the cursor by one frame, which is the whole difference
49
+ * between this feeling attached to the pointer and feeling cheap. React is told
50
+ * once, on release.
51
+ *
52
+ * The first resize **freezes** every visible column to its measured width. A
53
+ * `fr` track can't express "this one column is now 240px" without the others
54
+ * silently absorbing the difference, and once they hit their minimum the dragged
55
+ * edge stops following the cursor, which is the worst moment for it to happen.
56
+ * Frozen, the table is exact pixels and simply scrolls horizontally instead.
57
+ */
58
+ export declare function useColumnResize<T>({ columns, visibleColumnsRef, resizableColumns, columnWidths, onColumnWidthsChange, persistColumnsKey, rootRef, }: UseColumnResizeArgs<T>): UseColumnResizeResult<T>;
59
+ export {};
@@ -8,6 +8,10 @@ interface UseColumnsArgs<T> {
8
8
  onVisibleColumnsChange?: (keys: string[]) => void;
9
9
  persistColumnsKey?: string;
10
10
  showColumnToggle?: boolean;
11
+ /** User-set column widths, from `useColumnResize`. */
12
+ userWidths: Record<string, number>;
13
+ /** Whether those widths have taken over the layout. */
14
+ isFrozen: boolean;
11
15
  rootRef: React.RefObject<HTMLDivElement | null>;
12
16
  /** Visible-window bounds — re-measure `fit` columns as new cells scroll in. */
13
17
  startIndex: number;
@@ -16,10 +20,16 @@ interface UseColumnsArgs<T> {
16
20
  /** Re-measure `fit` columns when the container width settles (e.g. after a route transition). */
17
21
  containerWidth: number;
18
22
  }
19
- interface UseColumnsResult<T> {
23
+ export interface UseColumnsResult<T> {
20
24
  hasColumns: boolean;
21
25
  visibleColumns: VirtualListColumn<T>[];
22
26
  columnTemplate: string | undefined;
27
+ /**
28
+ * Custom properties for the list element: the total width of a frozen layout,
29
+ * as a `calc()` over the per-column properties so that rewriting one width
30
+ * during a drag updates the total without any bookkeeping.
31
+ */
32
+ widthVars: React.CSSProperties;
23
33
  shouldShowToggle: boolean;
24
34
  effectiveVisible: Set<string>;
25
35
  /** How many default-visible columns the user has hidden (excludes defaultHidden). */
@@ -38,5 +48,5 @@ interface UseColumnsResult<T> {
38
48
  * `fit`-column width measurement that pins one shared px track across the header
39
49
  * and every row grid so they stay aligned.
40
50
  */
41
- export declare function useColumns<T>({ columns, items, multiSelect, visibleColumnKeys, onVisibleColumnsChange, persistColumnsKey, showColumnToggle, rootRef, startIndex, endIndex, containerHeight, containerWidth, }: UseColumnsArgs<T>): UseColumnsResult<T>;
51
+ export declare function useColumns<T>({ columns, items, multiSelect, visibleColumnKeys, onVisibleColumnsChange, persistColumnsKey, showColumnToggle, userWidths, isFrozen, rootRef, startIndex, endIndex, containerHeight, containerWidth, }: UseColumnsArgs<T>): UseColumnsResult<T>;
42
52
  export {};
@@ -0,0 +1,22 @@
1
+ import { default as React } from 'react';
2
+ interface StationProps {
3
+ tabIndex: number;
4
+ ref: (el: HTMLElement | null) => void;
5
+ onFocus: () => void;
6
+ }
7
+ interface UseHeaderNavigationResult {
8
+ getStationProps: (index: number) => StationProps;
9
+ /** True when the key moved the focus, so the caller stops handling it. */
10
+ handleNavigationKey: (event: React.KeyboardEvent, index: number) => boolean;
11
+ }
12
+ /**
13
+ * Roving tab stop across the header: the select-all cell, every column header,
14
+ * and the column menu are stations, and arrow keys move between them.
15
+ *
16
+ * Without it each of those is its own tab stop, so a table with eight sortable,
17
+ * resizable columns takes seventeen presses of Tab before the data. The stops
18
+ * also belong together: they all act on the same header, which is exactly the
19
+ * case the roving pattern exists for.
20
+ */
21
+ export declare function useHeaderNavigation(stationCount: number): UseHeaderNavigationResult;
22
+ export {};
@@ -0,0 +1,50 @@
1
+ import { default as React } from 'react';
2
+ import { VirtualListScrollOptions } from './virtualList.types';
3
+ interface UseRowNavigationArgs {
4
+ enabled: boolean;
5
+ rowCount: number;
6
+ /** Row tops, from the windowing engine, used for paging and visibility. */
7
+ offsets: number[];
8
+ rootRef: React.RefObject<HTMLDivElement | null>;
9
+ headerRef: React.RefObject<HTMLDivElement | null>;
10
+ scrollToIndex: (index: number, options?: VirtualListScrollOptions) => void;
11
+ /** Row index the active row starts on, normally the selected one. */
12
+ initialIndex: number;
13
+ multiSelect: boolean;
14
+ /** Undefined when the consumer gave the rows no activation of their own. */
15
+ onActivate?: (index: number) => void;
16
+ onToggleSelect: (index: number) => void;
17
+ }
18
+ interface UseRowNavigationResult {
19
+ /** Index of the row the arrow keys are on, or -1 when navigation is off. */
20
+ activeIndex: number;
21
+ /** Keeps the arrow keys where the pointer last was. */
22
+ setActiveIndex: (index: number) => void;
23
+ /**
24
+ * The row currently being used, or -1. Its controls are in the tab order and
25
+ * it stays mounted even when it scrolls out of the window.
26
+ */
27
+ enteredIndex: number;
28
+ containerProps: {
29
+ tabIndex?: number;
30
+ onKeyDown?: (event: React.KeyboardEvent) => void;
31
+ onFocus?: (event: React.FocusEvent) => void;
32
+ onBlur?: (event: React.FocusEvent) => void;
33
+ };
34
+ }
35
+ /**
36
+ * Arrow-key navigation over the rows, with the tab stop on the list itself.
37
+ *
38
+ * The stop cannot sit on the active row, the way a roving tabindex normally
39
+ * would: a virtualized row unmounts when it scrolls out of the window, and the
40
+ * list would be left with no tab stop at all (the same reason `Tree` puts its
41
+ * stop on its container). Instead the list keeps the focus and points at the
42
+ * active row with `aria-activedescendant`.
43
+ *
44
+ * Keys are only handled when the list element itself has the focus. A cell may
45
+ * hold an input, a dropdown or a button, and every one of them wants the arrows,
46
+ * Enter and Space for itself. `F2` steps into such a row and `Escape` steps back
47
+ * out, which is the one pair of keys that does work from inside a cell.
48
+ */
49
+ export declare function useRowNavigation({ enabled, rowCount, offsets, rootRef, headerRef, scrollToIndex, initialIndex, multiSelect, onActivate, onToggleSelect, }: UseRowNavigationArgs): UseRowNavigationResult;
50
+ export {};
@@ -0,0 +1,26 @@
1
+ import { VirtualListColumn, VirtualListSort } from './virtualList.types';
2
+ interface UseSortingArgs<T> {
3
+ items: T[];
4
+ columns?: VirtualListColumn<T>[];
5
+ /** Controlled sort. When provided, internal state is ignored. */
6
+ sort?: VirtualListSort | null;
7
+ defaultSort?: VirtualListSort | null;
8
+ onSortChange?: (sort: VirtualListSort | null) => void;
9
+ }
10
+ interface UseSortingResult<T> {
11
+ /** `items`, reordered when the sorted column carries a comparator. */
12
+ rows: T[];
13
+ effectiveSort: VirtualListSort | null;
14
+ /** True only when this hook reordered the rows, not when it merely reported. */
15
+ isSorted: boolean;
16
+ handleHeaderSort: (key: string) => void;
17
+ }
18
+ /**
19
+ * Header sorting, controlled or uncontrolled. Whether the list reorders the data
20
+ * or only reports the click is the column's decision, not a prop: a column with
21
+ * `sortValue`/`compare` is sorted here, a bare `sortable` one is left alone so a
22
+ * server-side sort can own it. Sorting a paginated list here would silently
23
+ * order only the rows loaded so far.
24
+ */
25
+ export declare function useSorting<T>({ items, columns, sort, defaultSort, onSortChange, }: UseSortingArgs<T>): UseSortingResult<T>;
26
+ export {};
@@ -30,14 +30,7 @@ interface UseVirtualWindowResult {
30
30
  showLoadMoreSpinner: boolean;
31
31
  /** The scroll-related imperative methods, for the component to compose into
32
32
  * the forwarded handle alongside column controls. */
33
- scrollApi: Omit<VirtualListHandle, 'recalculateColumns'>;
33
+ scrollApi: Omit<VirtualListHandle, 'recalculateColumns' | 'resetColumnWidths'>;
34
34
  }
35
- /**
36
- * The generic virtualization engine: viewport measurement, per-row height
37
- * caching, offset/visible-range computation, scroll coalescing, scroll
38
- * anchoring on height changes, infinite-load triggering, and the imperative
39
- * scroll handle. Mode-agnostic — knows nothing about columns, selection or
40
- * dragging.
41
- */
42
35
  export declare function useVirtualWindow<T>({ items, keys, getItemKey, estimatedRowHeight, rowGap, overscan, onLoadMore, loadMoreThreshold, isLoading, }: UseVirtualWindowArgs<T>): UseVirtualWindowResult;
43
36
  export {};
@@ -1,6 +1,6 @@
1
1
  import { default as React } from 'react';
2
2
  import { VirtualListProps, VirtualListHandle } from './virtualList.types';
3
- export { buildOffsets, findStartIndex, getVisibleRange, resolveReorderTarget, resolveTreeDropTarget, lastDescendantIndex, buildColumnTemplate, } from './virtualList.utils';
3
+ export { buildOffsets, findStartIndex, getVisibleRange, resolveReorderTarget, resolveTreeDropTarget, lastDescendantIndex, buildColumnTemplate, compareSortValues, isColumnSortable, isEmptySortValue, nextSort, sortItems, } from './virtualList.utils';
4
4
  declare const VirtualList: <T>(props: VirtualListProps<T> & {
5
5
  ref?: React.Ref<VirtualListHandle>;
6
6
  }) => React.ReactElement | null;
@@ -12,6 +12,15 @@ export type ColumnWidthFit = {
12
12
  type: 'fit';
13
13
  };
14
14
  export type ColumnWidthSpec = ColumnWidthFixed | ColumnWidthFlex | ColumnWidthFit;
15
+ /** A value the built-in comparator can order. */
16
+ export type SortValue = string | number | boolean | Date | null | undefined;
17
+ export type SortDirection = 'asc' | 'desc';
18
+ /** The column being sorted and the direction. `null` anywhere this appears means unsorted. */
19
+ export interface VirtualListSort {
20
+ /** `key` of the sorted column. */
21
+ key: string;
22
+ direction: SortDirection;
23
+ }
15
24
  export interface VirtualListColumn<T> {
16
25
  key: string;
17
26
  label: ReactNode;
@@ -39,6 +48,31 @@ export interface VirtualListColumn<T> {
39
48
  * no column remains toggleable.
40
49
  */
41
50
  hideFromToggle?: boolean;
51
+ /**
52
+ * Value this column is ordered by. Its presence makes the column sortable and
53
+ * lets the list sort `items` itself. `null`/`undefined`/`NaN` sort last, in
54
+ * both directions.
55
+ */
56
+ sortValue?: (item: T) => SortValue;
57
+ /**
58
+ * Ascending comparator, for an order `sortValue` can't express. Wins over
59
+ * `sortValue`, and also makes the column sortable. Descending negates its
60
+ * result, so it has no equivalent of the empty-values-last rule.
61
+ */
62
+ compare?: (a: T, b: T) => number;
63
+ /**
64
+ * Floor this column can be dragged to, in px. Default 48.
65
+ */
66
+ minWidth?: number;
67
+ /** Opt this column out of resizing while the list has `resizableColumns`. */
68
+ resizable?: boolean;
69
+ /**
70
+ * Makes the header clickable without the list reordering anything: the sort is
71
+ * only reported through `onSortChange`. For server-side or paginated sorting,
72
+ * where sorting the loaded rows would silently sort part of the data. Ignored
73
+ * when `sortValue` or `compare` is given.
74
+ */
75
+ sortable?: boolean;
42
76
  }
43
77
  export interface VirtualListScrollOptions {
44
78
  /** Where the target row lands in the viewport. Default: 'start'. */
@@ -62,8 +96,13 @@ export interface VirtualListHandle {
62
96
  * set (those reset automatically) — e.g. a cell's content grew in place.
63
97
  */
64
98
  recalculateColumns: () => void;
99
+ /**
100
+ * Drops every user-set column width and returns the table to the widths its
101
+ * columns declare. Also clears them from `persistColumnsKey` storage.
102
+ */
103
+ resetColumnWidths: () => void;
65
104
  }
66
- export type VirtualListSlots = 'root' | 'header' | 'headerCell' | 'headerToggle' | 'togglePopover' | 'toggleItem' | 'body' | 'row' | 'cell' | 'selectCell' | 'loadingIndicator' | 'dropIndicator' | 'columnChip';
105
+ export type VirtualListSlots = 'root' | 'header' | 'headerCell' | 'headerToggle' | 'headerSort' | 'sortIcon' | 'resizeHandle' | 'activeRow' | 'togglePopover' | 'toggleItem' | 'toggleReset' | 'body' | 'row' | 'cell' | 'selectCell' | 'loadingIndicator' | 'dropIndicator' | 'columnChip';
67
106
  /** Where a tree-reorder drop lands relative to the row it is over. */
68
107
  export type TreeDropPosition = 'before' | 'inside' | 'after';
69
108
  /** The resolved drop target reported to `onTreeDrop`. */
@@ -125,6 +164,16 @@ export interface VirtualListProps<T> extends HtmlProps {
125
164
  getItemKey?: (item: T, index: number) => string | number;
126
165
  /** Called when a row is clicked. */
127
166
  onRowClick?: (item: T, index: number) => void;
167
+ /**
168
+ * Arrow-key navigation over the rows, on by default. The list takes one tab
169
+ * stop, the arrows move an active row, Enter fires `onRowClick` and Space
170
+ * toggles the selection in `multiSelect`. Keys are ignored while the focus is
171
+ * inside a cell, so a control in a row keeps its own.
172
+ *
173
+ * Turn it off for a list whose rows already handle their own keys, the way
174
+ * `Tree` does.
175
+ */
176
+ keyboardNavigation?: boolean;
128
177
  /** Controlled visible column keys. When provided, internal state is ignored. */
129
178
  visibleColumnKeys?: string[];
130
179
  /** Called when the user toggles a column in the popover. */
@@ -137,6 +186,38 @@ export interface VirtualListProps<T> extends HtmlProps {
137
186
  * to their `defaultHidden` value rather than the saved set.
138
187
  */
139
188
  persistColumnsKey?: string;
189
+ /**
190
+ * Controlled sort state. `null` is unsorted (source order). When provided,
191
+ * internal state is ignored.
192
+ */
193
+ sort?: VirtualListSort | null;
194
+ /** Initial sort in uncontrolled mode. Default: `null`. */
195
+ defaultSort?: VirtualListSort | null;
196
+ /**
197
+ * Called on each header click with the next step of the asc → desc → unsorted
198
+ * cycle. The list reorders `items` itself for columns that define `sortValue`
199
+ * or `compare`; for a `sortable` column it only reports, and sorting the data
200
+ * is yours to do.
201
+ */
202
+ onSortChange?: (sort: VirtualListSort | null) => void;
203
+ /**
204
+ * Lets the user drag a column's right edge to resize it, double-click it to fit
205
+ * the column to its content, or resize it from the keyboard once the handle has
206
+ * focus. The first resize pins every visible column to its current pixel width
207
+ * (nothing moves at that moment), after which the table is an exact pixel
208
+ * layout that scrolls horizontally rather than redistributing space.
209
+ */
210
+ resizableColumns?: boolean;
211
+ /** Controlled column widths in px, keyed by column key. */
212
+ columnWidths?: Record<string, number>;
213
+ /**
214
+ * Text of the gear-menu entry that undoes the user's column widths (default
215
+ * `'Reset column widths'`). The only text this component renders itself, so
216
+ * it is also the only one a localised app has to pass.
217
+ */
218
+ resetColumnWidthsLabel?: ReactNode;
219
+ /** Called with the full width map whenever the user resizes or auto-fits a column. */
220
+ onColumnWidthsChange?: (widths: Record<string, number>) => void;
140
221
  /** Show the gear column-toggle button in the header. Default: true when columns present. */
141
222
  showColumnToggle?: boolean;
142
223
  /**
@@ -1,4 +1,4 @@
1
- import { ColumnWidthSpec, VirtualListColumn } from './virtualList.types';
1
+ import { ColumnWidthSpec, SortValue, VirtualListColumn, VirtualListSort } from './virtualList.types';
2
2
  export declare function buildOffsets(keys: (string | number)[], heightsMap: Map<string | number, number>, estimatedRowHeight: number, rowGap?: number): {
3
3
  offsets: number[];
4
4
  totalHeight: number;
@@ -34,11 +34,93 @@ export declare function buildColumnTemplate(columns: {
34
34
  key: string;
35
35
  width: ColumnWidthSpec;
36
36
  }[], fitWidths?: Record<string, number>): string;
37
+ /**
38
+ * What counts as a control inside a row, and so what gets held out of the tab
39
+ * order while its row is not the one being used.
40
+ *
41
+ * `[tabindex="-1"]` is deliberately excluded. The row's own select checkbox sits
42
+ * there and is reached with Space, and an element the consumer has already taken
43
+ * out of the tab order stays out.
44
+ */
45
+ export declare const ROW_FOCUSABLE_SELECTOR = "a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex=\"-1\"])";
46
+ /** Marks an element parked at `tabindex="-1"` by the list rather than by the consumer. */
47
+ export declare const ROW_PARKED_ATTRIBUTE = "data-vl-parked";
48
+ /**
49
+ * What `F2` steps into: the controls of a row, including the ones parked out of
50
+ * the tab order. Without the second half it would find nothing at all in a row
51
+ * whose controls are custom elements carrying a `tabindex`, because parking them
52
+ * is exactly what takes them out of the selector above.
53
+ */
54
+ export declare const ROW_ENTER_SELECTOR = "a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex=\"-1\"]), [data-vl-parked]";
55
+ /**
56
+ * The content width of one cell, in px. `scrollWidth` reports the true content
57
+ * width even when the cell is pinned narrower than its content, which is what
58
+ * lets a column recover from having been measured while it was still empty.
59
+ *
60
+ * Two corrections on top of it, both only resolved when they can apply, so the
61
+ * hot scroll path stays cheap for a plain in-flow cell:
62
+ *
63
+ * - `scrollWidth` includes the left padding but drops the right one once the
64
+ * content overflows, so add it back.
65
+ * - A cell whose only children are FontAwesome icons reports just its padding:
66
+ * their boxes overflow via `overflow: visible`, carry no intrinsic width, and
67
+ * so never count towards `scrollWidth` (right-aligned, they even overflow
68
+ * into negative offsets). Only then walk the descendants for their true span.
69
+ * Once a real width pins, the cell is no longer collapsed and this stops.
70
+ */
71
+ export declare function measureCellWidth(el: HTMLElement): number;
72
+ /** Attribute selector for one column's cells. Column keys are arbitrary strings. */
73
+ export declare function columnCellSelector(key: string): string;
74
+ /** Floor for a user-resized column, unless the column raises it with `minWidth`. */
75
+ export declare const MIN_COLUMN_WIDTH = 48;
76
+ /**
77
+ * Width given to a column that gains one while the layout is already frozen,
78
+ * i.e. one revealed from the gear menu after the first resize. Every visible
79
+ * column has to be an exact px track for the total width (and so the horizontal
80
+ * scroll range) to be exact, and there is nothing measured to go on yet.
81
+ */
82
+ export declare const DEFAULT_FROZEN_WIDTH = 160;
83
+ /**
84
+ * Custom property carrying one column's width. Column keys are arbitrary
85
+ * strings, so anything a custom property can't contain is folded to `_`; two
86
+ * keys that differ only in those characters would share a track.
87
+ */
88
+ export declare function columnWidthVar(key: string): string;
89
+ /** Px width for a column the user hasn't sized, at the moment the layout freezes. */
90
+ export declare function resolveFrozenWidth<T>(col: VirtualListColumn<T>): number;
91
+ /**
92
+ * Grid template for a frozen layout: every column reads its own custom property,
93
+ * so a drag can rewrite one width with a single DOM write and have the header
94
+ * and every row follow in the same layout pass, without React rendering.
95
+ */
96
+ export declare function buildFrozenTemplate<T>(columns: VirtualListColumn<T>[]): string;
37
97
  export declare const COLUMN_STORAGE_PREFIX = "ahroweui:virtuallist:columns:";
38
98
  export interface PersistedColumns {
39
99
  visible: string[];
40
100
  known: string[];
101
+ /** User-resized column widths in px. Absent in entries saved before resizing existed. */
102
+ widths?: Record<string, number>;
41
103
  }
42
104
  export declare function loadPersistedColumns(persistKey: string | undefined): PersistedColumns | null;
43
105
  /** Whether a column should start visible, honouring saved settings then `defaultHidden`. */
44
106
  export declare function isColumnInitiallyVisible<T>(col: VirtualListColumn<T>, persisted: PersistedColumns | null): boolean;
107
+ /** Values with no order of their own, parked at the end of the list either way. */
108
+ export declare function isEmptySortValue(value: SortValue): boolean;
109
+ /**
110
+ * Ascending comparison of two non-empty `sortValue` results. Dates and booleans
111
+ * are normalised to numbers; anything with a string on either side goes through
112
+ * the collator, which is also what makes a mixed-type column produce a stable
113
+ * (if arbitrary) order rather than a random one.
114
+ */
115
+ export declare function compareSortValues(a: SortValue, b: SortValue): number;
116
+ /** Whether a header click on this column should do anything at all. */
117
+ export declare function isColumnSortable<T>(col: VirtualListColumn<T>): boolean;
118
+ /** The asc → desc → unsorted cycle a header click steps through. */
119
+ export declare function nextSort(current: VirtualListSort | null, key: string): VirtualListSort | null;
120
+ /**
121
+ * Returns `items` reordered by `sort`, or the same array reference when nothing
122
+ * applies: no sort, no such column, or a `sortable` column that only reports
123
+ * (its data is sorted by the consumer). Callers rely on that identity to tell
124
+ * "the list sorted this" from "the list left it alone".
125
+ */
126
+ export declare function sortItems<T>(items: T[], sort: VirtualListSort | null | undefined, columns: VirtualListColumn<T>[] | undefined): T[];
@@ -0,0 +1,35 @@
1
+ import { default as React } from 'react';
2
+ import { UseColumnsResult } from './useColumns';
3
+ import { UseColumnResizeResult } from './useColumnResize';
4
+ import { SlotClassNames, SlotStyles } from '../types/slots.types';
5
+ import { VirtualListColumn, VirtualListSlots, VirtualListSort } from './virtualList.types';
6
+ interface VirtualListHeaderProps<T> {
7
+ headerRef: React.RefObject<HTMLDivElement | null>;
8
+ /** Every column, including hidden ones: the toggle menu lists them all. */
9
+ columns: VirtualListColumn<T>[];
10
+ cols: UseColumnsResult<T>;
11
+ resize: UseColumnResizeResult<T>;
12
+ sort: VirtualListSort | null;
13
+ onSort: (key: string) => void;
14
+ multiSelect: boolean;
15
+ allSelected: boolean;
16
+ someSelected: boolean;
17
+ onSelectAll: () => void;
18
+ resetColumnWidthsLabel: React.ReactNode;
19
+ classNames?: SlotClassNames<VirtualListSlots>;
20
+ styles?: SlotStyles<VirtualListSlots>;
21
+ }
22
+ /**
23
+ * The table-mode header row: one cell per visible column, each optionally a sort
24
+ * button and a resize handle, plus the column-visibility menu pinned to the
25
+ * right. It is a grid with the same template as every row, which is what keeps
26
+ * the columns aligned across the separate grids.
27
+ *
28
+ * The whole header is a single tab stop (see `useHeaderNavigation`). Focus lands
29
+ * on a header cell rather than on the button inside it, so every key a column
30
+ * understands is handled in one place: Enter or Space sorts, Alt plus the arrow
31
+ * keys resizes, Alt+Home and Alt+End take the column to its minimum or to its
32
+ * content width.
33
+ */
34
+ declare function VirtualListHeader<T>({ headerRef, columns, cols, resize, sort, onSort, multiSelect, allSelected, someSelected, onSelectAll, resetColumnWidthsLabel, classNames, styles: slotStyles, }: VirtualListHeaderProps<T>): React.JSX.Element;
35
+ export default VirtualListHeader;
@@ -2,11 +2,24 @@ import { default as React } from 'react';
2
2
  import { TreeDropPosition } from './virtualList.types';
3
3
  export interface VirtualRowProps {
4
4
  index: number;
5
+ /** Referenced by the list's `aria-activedescendant` while this row is active. */
6
+ id?: string;
5
7
  top: number;
6
8
  /** Padding applied to the row (already normalised to a CSS string). */
7
9
  padding?: string;
8
10
  columnTemplate: string | undefined;
9
11
  isSelected: boolean;
12
+ /** The row the arrow keys are on. Distinct from selection. */
13
+ isActive?: boolean;
14
+ /**
15
+ * Hold this row's own controls out of the tab order unless it is the row being
16
+ * used. Off when the list leaves the keyboard to its rows.
17
+ */
18
+ manageTabStops?: boolean;
19
+ /** This is the row being used, so its controls are reachable with Tab. */
20
+ isEntered?: boolean;
21
+ activeClassName?: string;
22
+ activeStyle?: React.CSSProperties;
10
23
  showDivider: boolean;
11
24
  hover: boolean;
12
25
  onClick: () => void;
@@ -45,5 +58,5 @@ export interface VirtualRowProps {
45
58
  * grid (table mode), registering itself with the shared ResizeObserver and
46
59
  * carrying all the drag / drop-indicator wiring the parent assigns it.
47
60
  */
48
- declare function VirtualRow({ index, top, padding, columnTemplate, isSelected, showDivider, hover, onClick, observer, ariaRowIndex, ariaPosInSet, ariaSetSize, rowRole, reorderable, mouseDragEnabled, draggable, isDragging, reserveToggleGutter, onMouseDown, onMouseOver, onMouseLeave, onDragStart, onDragOver, onDragEnd, onDrop, onTouchStart, dropPosition, dropIndentPx, dropIndicatorClassName, dropIndicatorStyle, rowClassName, rowStyle, children, }: VirtualRowProps): React.JSX.Element;
61
+ declare function VirtualRow({ index, id, top, padding, columnTemplate, isSelected, isActive, manageTabStops, isEntered, activeClassName, activeStyle, showDivider, hover, onClick, observer, ariaRowIndex, ariaPosInSet, ariaSetSize, rowRole, reorderable, mouseDragEnabled, draggable, isDragging, reserveToggleGutter, onMouseDown, onMouseOver, onMouseLeave, onDragStart, onDragOver, onDragEnd, onDrop, onTouchStart, dropPosition, dropIndentPx, dropIndicatorClassName, dropIndicatorStyle, rowClassName, rowStyle, children, }: VirtualRowProps): React.JSX.Element;
49
62
  export default VirtualRow;