@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.
- package/dist/esm/common/tree/tree.mjs +1 -1
- package/dist/esm/common/tree/tree.mjs.map +1 -1
- package/dist/esm/common/virtualList/useColumnResize.mjs +2 -0
- package/dist/esm/common/virtualList/useColumnResize.mjs.map +1 -0
- package/dist/esm/common/virtualList/useColumns.mjs +1 -1
- package/dist/esm/common/virtualList/useColumns.mjs.map +1 -1
- package/dist/esm/common/virtualList/useHeaderNavigation.mjs +2 -0
- package/dist/esm/common/virtualList/useHeaderNavigation.mjs.map +1 -0
- package/dist/esm/common/virtualList/useRowNavigation.mjs +2 -0
- package/dist/esm/common/virtualList/useRowNavigation.mjs.map +1 -0
- package/dist/esm/common/virtualList/useSorting.mjs +2 -0
- package/dist/esm/common/virtualList/useSorting.mjs.map +1 -0
- package/dist/esm/common/virtualList/useVirtualWindow.mjs +1 -1
- package/dist/esm/common/virtualList/useVirtualWindow.mjs.map +1 -1
- package/dist/esm/common/virtualList/virtualList.mjs +1 -1
- package/dist/esm/common/virtualList/virtualList.mjs.map +1 -1
- package/dist/esm/common/virtualList/virtualList.module.mjs +1 -1
- package/dist/esm/common/virtualList/virtualList.module.mjs.map +1 -1
- package/dist/esm/common/virtualList/virtualList.utils.mjs +1 -1
- package/dist/esm/common/virtualList/virtualList.utils.mjs.map +1 -1
- package/dist/esm/common/virtualList/virtualListHeader.mjs +2 -0
- package/dist/esm/common/virtualList/virtualListHeader.mjs.map +1 -0
- package/dist/esm/common/virtualList/virtualRow.mjs +1 -1
- package/dist/esm/common/virtualList/virtualRow.mjs.map +1 -1
- package/dist/index.cjs +3 -3
- package/dist/index.cjs.map +1 -1
- package/dist/style.css +1 -1
- package/dist/types/common/virtualList/useColumnResize.d.ts +59 -0
- package/dist/types/common/virtualList/useColumns.d.ts +12 -2
- package/dist/types/common/virtualList/useHeaderNavigation.d.ts +22 -0
- package/dist/types/common/virtualList/useRowNavigation.d.ts +50 -0
- package/dist/types/common/virtualList/useSorting.d.ts +26 -0
- package/dist/types/common/virtualList/useVirtualWindow.d.ts +1 -8
- package/dist/types/common/virtualList/virtualList.d.ts +1 -1
- package/dist/types/common/virtualList/virtualList.types.d.ts +82 -1
- package/dist/types/common/virtualList/virtualList.utils.d.ts +83 -1
- package/dist/types/common/virtualList/virtualListHeader.d.ts +35 -0
- package/dist/types/common/virtualList/virtualRow.d.ts +14 -1
- package/docs/VirtualList.md +125 -7
- 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;
|