@octanejs/aria 0.0.5 → 0.0.7
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/package.json +7 -7
- package/src/components/Table.ts +2618 -0
- package/src/components/Tree.ts +1450 -0
- package/src/components/TreeDropTargetDelegate.ts +363 -0
- package/src/components/index.ts +57 -0
- package/src/components/utils.ts +3 -6
- package/src/grid/GridKeyboardDelegate.ts +514 -0
- package/src/grid/useGrid.ts +263 -0
- package/src/grid/useGridCell.ts +346 -0
- package/src/grid/useGridRow.ts +108 -0
- package/src/grid/useGridRowGroup.ts +29 -0
- package/src/grid/utils.ts +24 -0
- package/src/index.ts +38 -0
- package/src/intl/autocomplete/index.ts +9 -2
- package/src/intl/breadcrumbs/index.ts +9 -2
- package/src/intl/combobox/index.ts +9 -2
- package/src/intl/compileMessages.ts +356 -0
- package/src/intl/grid/index.ts +9 -2
- package/src/intl/gridlist/index.ts +9 -2
- package/src/intl/menu/index.ts +9 -2
- package/src/intl/numberfield/index.ts +9 -2
- package/src/intl/overlays/index.ts +9 -2
- package/src/intl/searchfield/index.ts +9 -2
- package/src/intl/spinbutton/index.ts +9 -2
- package/src/intl/table/ar-AE.json +13 -0
- package/src/intl/table/bg-BG.json +13 -0
- package/src/intl/table/cs-CZ.json +13 -0
- package/src/intl/table/da-DK.json +13 -0
- package/src/intl/table/de-DE.json +13 -0
- package/src/intl/table/el-GR.json +13 -0
- package/src/intl/table/en-US.json +13 -0
- package/src/intl/table/es-ES.json +13 -0
- package/src/intl/table/et-EE.json +13 -0
- package/src/intl/table/fi-FI.json +13 -0
- package/src/intl/table/fr-FR.json +13 -0
- package/src/intl/table/he-IL.json +13 -0
- package/src/intl/table/hr-HR.json +13 -0
- package/src/intl/table/hu-HU.json +13 -0
- package/src/intl/table/index.ts +82 -0
- package/src/intl/table/it-IT.json +13 -0
- package/src/intl/table/ja-JP.json +13 -0
- package/src/intl/table/ko-KR.json +13 -0
- package/src/intl/table/lt-LT.json +13 -0
- package/src/intl/table/lv-LV.json +13 -0
- package/src/intl/table/nb-NO.json +13 -0
- package/src/intl/table/nl-NL.json +13 -0
- package/src/intl/table/pl-PL.json +13 -0
- package/src/intl/table/pt-BR.json +13 -0
- package/src/intl/table/pt-PT.json +13 -0
- package/src/intl/table/ro-RO.json +13 -0
- package/src/intl/table/ru-RU.json +13 -0
- package/src/intl/table/sk-SK.json +13 -0
- package/src/intl/table/sl-SI.json +13 -0
- package/src/intl/table/sr-SP.json +13 -0
- package/src/intl/table/sv-SE.json +13 -0
- package/src/intl/table/tr-TR.json +13 -0
- package/src/intl/table/uk-UA.json +13 -0
- package/src/intl/table/zh-CN.json +13 -0
- package/src/intl/table/zh-TW.json +13 -0
- package/src/intl/tag/index.ts +9 -2
- package/src/intl/tree/ar-AE.json +4 -0
- package/src/intl/tree/bg-BG.json +4 -0
- package/src/intl/tree/cs-CZ.json +4 -0
- package/src/intl/tree/da-DK.json +4 -0
- package/src/intl/tree/de-DE.json +4 -0
- package/src/intl/tree/el-GR.json +4 -0
- package/src/intl/tree/en-US.json +4 -0
- package/src/intl/tree/es-ES.json +4 -0
- package/src/intl/tree/et-EE.json +4 -0
- package/src/intl/tree/fi-FI.json +4 -0
- package/src/intl/tree/fr-FR.json +4 -0
- package/src/intl/tree/he-IL.json +4 -0
- package/src/intl/tree/hr-HR.json +4 -0
- package/src/intl/tree/hu-HU.json +4 -0
- package/src/intl/tree/index.ts +82 -0
- package/src/intl/tree/it-IT.json +4 -0
- package/src/intl/tree/ja-JP.json +4 -0
- package/src/intl/tree/ko-KR.json +4 -0
- package/src/intl/tree/lt-LT.json +4 -0
- package/src/intl/tree/lv-LV.json +4 -0
- package/src/intl/tree/nb-NO.json +4 -0
- package/src/intl/tree/nl-NL.json +4 -0
- package/src/intl/tree/pl-PL.json +4 -0
- package/src/intl/tree/pt-BR.json +4 -0
- package/src/intl/tree/pt-PT.json +4 -0
- package/src/intl/tree/ro-RO.json +4 -0
- package/src/intl/tree/ru-RU.json +4 -0
- package/src/intl/tree/sk-SK.json +4 -0
- package/src/intl/tree/sl-SI.json +4 -0
- package/src/intl/tree/sr-SP.json +4 -0
- package/src/intl/tree/sv-SE.json +4 -0
- package/src/intl/tree/tr-TR.json +4 -0
- package/src/intl/tree/uk-UA.json +4 -0
- package/src/intl/tree/zh-CN.json +4 -0
- package/src/intl/tree/zh-TW.json +4 -0
- package/src/stately/grid/GridCollection.ts +200 -0
- package/src/stately/grid/useGridState.ts +160 -0
- package/src/stately/index.ts +43 -0
- package/src/stately/table/Cell.ts +49 -0
- package/src/stately/table/Column.ts +121 -0
- package/src/stately/table/Row.ts +145 -0
- package/src/stately/table/TableBody.ts +66 -0
- package/src/stately/table/TableCollection.ts +390 -0
- package/src/stately/table/TableColumnLayout.ts +148 -0
- package/src/stately/table/TableHeader.ts +65 -0
- package/src/stately/table/TableUtils.ts +269 -0
- package/src/stately/table/useTableColumnResizeState.ts +202 -0
- package/src/stately/table/useTableState.ts +235 -0
- package/src/stately/table/useTreeGridState.ts +248 -0
- package/src/table/TableKeyboardDelegate.ts +205 -0
- package/src/table/useTable.ts +175 -0
- package/src/table/useTableCell.ts +81 -0
- package/src/table/useTableColumnHeader.ts +155 -0
- package/src/table/useTableColumnResize.ts +374 -0
- package/src/table/useTableHeaderRow.ts +57 -0
- package/src/table/useTableRow.ts +198 -0
- package/src/table/useTableRowGroup.ts +13 -0
- package/src/table/useTableSelectionCheckbox.ts +105 -0
- package/src/table/utils.ts +42 -0
- package/src/tree/useTree.ts +76 -0
- package/src/tree/useTreeItem.ts +109 -0
- package/src/utils/mergeRefs.ts +1 -4
- package/src/utils/useFormReset.ts +1 -2
- package/src/utils/useObjectRef.ts +2 -8
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
// Ported from react-stately (source: .react-spectrum/packages/react-stately/src/grid/useGridState.ts).
|
|
2
|
+
// octane adaptations: public-hook slot threading (splitSlot/subSlot) per the binding
|
|
3
|
+
// convention; upstream's conditional `useMultipleSelectionState` call (behind
|
|
4
|
+
// `UNSAFE_selectionState`) is valid octane — hooks are slot-keyed, not order-keyed;
|
|
5
|
+
// explicit dependency arrays are kept verbatim (they retain React's exact behavior
|
|
6
|
+
// in octane).
|
|
7
|
+
import type { Key } from '@react-types/shared';
|
|
8
|
+
import { useEffect, useMemo, useRef } from 'octane';
|
|
9
|
+
|
|
10
|
+
import { S, splitSlot, subSlot } from '../../internal';
|
|
11
|
+
import { getChildNodes, getFirstItem, getLastItem } from '../collections/getChildNodes';
|
|
12
|
+
import type { GridNode, IGridCollection } from './GridCollection';
|
|
13
|
+
import type { MultipleSelectionState } from '../selection/types';
|
|
14
|
+
import {
|
|
15
|
+
type MultipleSelectionStateProps,
|
|
16
|
+
useMultipleSelectionState,
|
|
17
|
+
} from '../selection/useMultipleSelectionState';
|
|
18
|
+
import { SelectionManager } from '../selection/SelectionManager';
|
|
19
|
+
|
|
20
|
+
export interface GridState<T, C extends IGridCollection<T>> {
|
|
21
|
+
collection: C;
|
|
22
|
+
/** A set of keys for rows that are disabled. */
|
|
23
|
+
disabledKeys: Set<Key>;
|
|
24
|
+
/** A selection manager to read and update row selection state. */
|
|
25
|
+
selectionManager: SelectionManager;
|
|
26
|
+
/**
|
|
27
|
+
* Whether keyboard navigation is disabled, such as when the arrow keys should be handled by a
|
|
28
|
+
* component within a cell.
|
|
29
|
+
*/
|
|
30
|
+
isKeyboardNavigationDisabled: boolean;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface GridStateOptions<
|
|
34
|
+
T,
|
|
35
|
+
C extends IGridCollection<T>,
|
|
36
|
+
> extends MultipleSelectionStateProps {
|
|
37
|
+
collection: C;
|
|
38
|
+
disabledKeys?: Iterable<Key>;
|
|
39
|
+
focusMode?: 'row' | 'cell';
|
|
40
|
+
/** @private - Do not use unless you know what you're doing. */
|
|
41
|
+
UNSAFE_selectionState?: MultipleSelectionState;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Provides state management for a grid component. Handles row selection and focusing a grid cell's
|
|
46
|
+
* focusable child if applicable.
|
|
47
|
+
*/
|
|
48
|
+
export function useGridState<T extends object, C extends IGridCollection<T>>(
|
|
49
|
+
props: GridStateOptions<T, C>,
|
|
50
|
+
): GridState<T, C>;
|
|
51
|
+
// Slot-threading form: sibling ported hooks pass their derived sub-slot as the trailing arg.
|
|
52
|
+
export function useGridState<T extends object, C extends IGridCollection<T>>(
|
|
53
|
+
props: GridStateOptions<T, C>,
|
|
54
|
+
slot: symbol | undefined,
|
|
55
|
+
): GridState<T, C>;
|
|
56
|
+
export function useGridState(...args: any[]): GridState<any, IGridCollection<any>> {
|
|
57
|
+
const [user, slotArg] = splitSlot(args);
|
|
58
|
+
const slot = slotArg ?? S('useGridState');
|
|
59
|
+
const props = user[0] as GridStateOptions<any, IGridCollection<any>>;
|
|
60
|
+
|
|
61
|
+
let { collection, focusMode } = props;
|
|
62
|
+
let selectionState =
|
|
63
|
+
props.UNSAFE_selectionState || useMultipleSelectionState(props, subSlot(slot, 'selection'));
|
|
64
|
+
let disabledKeys = useMemo(
|
|
65
|
+
() => (props.disabledKeys ? new Set(props.disabledKeys) : new Set<Key>()),
|
|
66
|
+
[props.disabledKeys],
|
|
67
|
+
subSlot(slot, 'disabled'),
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
let setFocusedKey = selectionState.setFocusedKey;
|
|
71
|
+
selectionState.setFocusedKey = (key, child) => {
|
|
72
|
+
// If focusMode is cell and an item is focused, focus a child cell instead.
|
|
73
|
+
if (focusMode === 'cell' && key != null) {
|
|
74
|
+
let item = collection.getItem(key);
|
|
75
|
+
if (item?.type === 'item') {
|
|
76
|
+
let children = getChildNodes(item, collection);
|
|
77
|
+
if (child === 'last') {
|
|
78
|
+
key = getLastItem(children)?.key ?? null;
|
|
79
|
+
} else {
|
|
80
|
+
key = getFirstItem(children)?.key ?? null;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
setFocusedKey(key, child);
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
let selectionManager = useMemo(
|
|
89
|
+
() => new SelectionManager(collection, selectionState),
|
|
90
|
+
[collection, selectionState],
|
|
91
|
+
subSlot(slot, 'manager'),
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
// Reset focused key if that item is deleted from the collection.
|
|
95
|
+
const cachedCollection = useRef<IGridCollection<any> | null>(null, subSlot(slot, 'cached'));
|
|
96
|
+
useEffect(
|
|
97
|
+
() => {
|
|
98
|
+
if (
|
|
99
|
+
selectionState.focusedKey != null &&
|
|
100
|
+
cachedCollection.current &&
|
|
101
|
+
!collection.getItem(selectionState.focusedKey)
|
|
102
|
+
) {
|
|
103
|
+
const node = cachedCollection.current.getItem(selectionState.focusedKey);
|
|
104
|
+
const parentNode =
|
|
105
|
+
node?.parentKey != null &&
|
|
106
|
+
(node.type === 'cell' || node.type === 'rowheader' || node.type === 'column')
|
|
107
|
+
? cachedCollection.current.getItem(node.parentKey)
|
|
108
|
+
: node;
|
|
109
|
+
if (!parentNode) {
|
|
110
|
+
selectionState.setFocusedKey(null);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
const cachedRows = cachedCollection.current.rows;
|
|
114
|
+
const rows = collection.rows;
|
|
115
|
+
const diff = cachedRows.length - rows.length;
|
|
116
|
+
let index = Math.min(
|
|
117
|
+
diff > 1 ? Math.max(parentNode.index - diff + 1, 0) : parentNode.index,
|
|
118
|
+
rows.length - 1,
|
|
119
|
+
);
|
|
120
|
+
let newRow: GridNode<any> | null = null;
|
|
121
|
+
while (index >= 0) {
|
|
122
|
+
if (!selectionManager.isDisabled(rows[index].key) && rows[index].type !== 'headerrow') {
|
|
123
|
+
newRow = rows[index];
|
|
124
|
+
break;
|
|
125
|
+
}
|
|
126
|
+
// Find next, not disabled row.
|
|
127
|
+
if (index < rows.length - 1) {
|
|
128
|
+
index++;
|
|
129
|
+
// Otherwise, find previous, not disabled row.
|
|
130
|
+
} else {
|
|
131
|
+
if (index > parentNode.index) {
|
|
132
|
+
index = parentNode.index;
|
|
133
|
+
}
|
|
134
|
+
index--;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
if (newRow) {
|
|
138
|
+
const childNodes = newRow.hasChildNodes ? [...getChildNodes(newRow, collection)] : [];
|
|
139
|
+
const keyToFocus =
|
|
140
|
+
newRow.hasChildNodes && parentNode !== node && node && node.index < childNodes.length
|
|
141
|
+
? childNodes[node.index].key
|
|
142
|
+
: newRow.key;
|
|
143
|
+
selectionState.setFocusedKey(keyToFocus);
|
|
144
|
+
} else {
|
|
145
|
+
selectionState.setFocusedKey(null);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
cachedCollection.current = collection;
|
|
149
|
+
},
|
|
150
|
+
[collection, selectionManager, selectionState, selectionState.focusedKey],
|
|
151
|
+
subSlot(slot, 'focusReset'),
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
return {
|
|
155
|
+
collection,
|
|
156
|
+
disabledKeys,
|
|
157
|
+
isKeyboardNavigationDisabled: false,
|
|
158
|
+
selectionManager,
|
|
159
|
+
};
|
|
160
|
+
}
|
package/src/stately/index.ts
CHANGED
|
@@ -116,3 +116,46 @@ export type { NumberFieldState, NumberFieldStateOptions } from './numberfield/us
|
|
|
116
116
|
// slider
|
|
117
117
|
export { useSliderState } from './slider/useSliderState';
|
|
118
118
|
export type { SliderState, SliderStateOptions } from './slider/useSliderState';
|
|
119
|
+
|
|
120
|
+
// grid — mirrors the @react-stately/grid shim surface.
|
|
121
|
+
export { useGridState } from './grid/useGridState';
|
|
122
|
+
export { GridCollection } from './grid/GridCollection';
|
|
123
|
+
export type { GridStateOptions, GridState } from './grid/useGridState';
|
|
124
|
+
export type { IGridCollection, GridNode, GridRow } from './grid/GridCollection';
|
|
125
|
+
|
|
126
|
+
// table — mirrors the @react-stately/table shim surface (Section is already
|
|
127
|
+
// exported from the collections section above).
|
|
128
|
+
export { useTableState, UNSTABLE_useFilteredTableState } from './table/useTableState';
|
|
129
|
+
export { useTableColumnResizeState } from './table/useTableColumnResizeState';
|
|
130
|
+
export { UNSTABLE_useTreeGridState } from './table/useTreeGridState';
|
|
131
|
+
export { TableHeader } from './table/TableHeader';
|
|
132
|
+
export { TableBody } from './table/TableBody';
|
|
133
|
+
export { Column } from './table/Column';
|
|
134
|
+
export { Row } from './table/Row';
|
|
135
|
+
export { Cell } from './table/Cell';
|
|
136
|
+
export { TableCollection, buildHeaderRows } from './table/TableCollection';
|
|
137
|
+
export { TableColumnLayout } from './table/TableColumnLayout';
|
|
138
|
+
export type {
|
|
139
|
+
TableProps,
|
|
140
|
+
TableState,
|
|
141
|
+
TableStateProps,
|
|
142
|
+
CollectionBuilderContext,
|
|
143
|
+
} from './table/useTableState';
|
|
144
|
+
export type {
|
|
145
|
+
TableColumnResizeState,
|
|
146
|
+
TableColumnResizeStateProps,
|
|
147
|
+
} from './table/useTableColumnResizeState';
|
|
148
|
+
export type { TreeGridState, TreeGridStateProps } from './table/useTreeGridState';
|
|
149
|
+
export type {
|
|
150
|
+
ColumnProps,
|
|
151
|
+
ColumnSize,
|
|
152
|
+
ColumnDynamicSize,
|
|
153
|
+
ColumnStaticSize,
|
|
154
|
+
ColumnElement,
|
|
155
|
+
ColumnRenderer,
|
|
156
|
+
} from './table/Column';
|
|
157
|
+
export type { TableHeaderProps } from './table/TableHeader';
|
|
158
|
+
export type { TableBodyProps } from './table/TableBody';
|
|
159
|
+
export type { RowProps, RowElement } from './table/Row';
|
|
160
|
+
export type { CellProps, CellElement, CellRenderer } from './table/Cell';
|
|
161
|
+
export type { ITableCollection } from './table/TableCollection';
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// Ported from react-stately (source: .react-spectrum/packages/react-stately/src/table/Cell.ts).
|
|
2
|
+
// octane adaptations: React element types → `any` (octane element descriptors). `<Cell>` is
|
|
3
|
+
// a collection DESCRIPTOR component — it renders nothing; CollectionBuilder walks it via
|
|
4
|
+
// the static `getCollectionNode` generator.
|
|
5
|
+
import type { Key } from '@react-types/shared';
|
|
6
|
+
import type { PartialNode } from '../collections/types';
|
|
7
|
+
|
|
8
|
+
export interface CellProps {
|
|
9
|
+
/** The contents of the cell. */
|
|
10
|
+
children: any;
|
|
11
|
+
/** A string representation of the cell's contents, used for features like typeahead. */
|
|
12
|
+
textValue?: string;
|
|
13
|
+
/** Indicates how many columns the data cell spans. */
|
|
14
|
+
colSpan?: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export type CellElement = any;
|
|
18
|
+
export type CellRenderer = (columnKey: Key) => CellElement;
|
|
19
|
+
|
|
20
|
+
function Cell(props: CellProps): any {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
Cell.getCollectionNode = function* getCollectionNode<T>(
|
|
25
|
+
props: CellProps,
|
|
26
|
+
): Generator<PartialNode<T>> {
|
|
27
|
+
let { children } = props;
|
|
28
|
+
|
|
29
|
+
let textValue =
|
|
30
|
+
props.textValue ||
|
|
31
|
+
(typeof children === 'string' ? children : '') ||
|
|
32
|
+
(props as any)['aria-label'] ||
|
|
33
|
+
'';
|
|
34
|
+
yield {
|
|
35
|
+
type: 'cell',
|
|
36
|
+
props: props,
|
|
37
|
+
rendered: children,
|
|
38
|
+
textValue,
|
|
39
|
+
'aria-label': (props as any)['aria-label'],
|
|
40
|
+
hasChildNodes: false,
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* A Cell represents the value of a single Column within a Table Row.
|
|
46
|
+
*/
|
|
47
|
+
// We don't want getCollectionNode to show up in the type definition
|
|
48
|
+
let _Cell = Cell as unknown as (props: CellProps) => any;
|
|
49
|
+
export { _Cell as Cell };
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
// Ported from react-stately (source: .react-spectrum/packages/react-stately/src/table/Column.ts).
|
|
2
|
+
// octane adaptations: React.Children → octane Children (descriptor arrays); React element
|
|
3
|
+
// types → `any` descriptors. Renders nothing; walked via static `getCollectionNode`.
|
|
4
|
+
import { Children } from 'octane';
|
|
5
|
+
|
|
6
|
+
import type { CollectionBuilderContext } from './useTableState';
|
|
7
|
+
import type { GridNode } from '../grid/GridCollection';
|
|
8
|
+
import type { PartialNode } from '../collections/types';
|
|
9
|
+
|
|
10
|
+
/** Widths that result in a constant pixel value for the same Table width. */
|
|
11
|
+
export type ColumnStaticSize = number | `${number}` | `${number}%`; // match regex: /^(\d+)(?=%$)/
|
|
12
|
+
/**
|
|
13
|
+
* Widths that change size in relation to the remaining space and in ratio to other dynamic columns.
|
|
14
|
+
* All numbers must be integers and greater than 0.
|
|
15
|
+
* FR units take up remaining, if any, space in the table.
|
|
16
|
+
*/
|
|
17
|
+
export type ColumnDynamicSize = `${number}fr`; // match regex: /^(\d+)(?=fr$)/
|
|
18
|
+
/** All possible sizes a column can be assigned. */
|
|
19
|
+
export type ColumnSize = ColumnStaticSize | ColumnDynamicSize;
|
|
20
|
+
|
|
21
|
+
export type ColumnElement<T> = any;
|
|
22
|
+
export type ColumnRenderer<T> = (item: T) => ColumnElement<T>;
|
|
23
|
+
export interface ColumnProps<T> {
|
|
24
|
+
/** Rendered contents of the column if `children` contains child columns. */
|
|
25
|
+
title?: any;
|
|
26
|
+
/** Static child columns or content to render as the column header. */
|
|
27
|
+
children: any;
|
|
28
|
+
/** A list of child columns used when dynamically rendering nested child columns. */
|
|
29
|
+
childColumns?: T[];
|
|
30
|
+
/** The width of the column. */
|
|
31
|
+
width?: ColumnSize | null;
|
|
32
|
+
/** The minimum width of the column. */
|
|
33
|
+
minWidth?: ColumnStaticSize | null;
|
|
34
|
+
/** The maximum width of the column. */
|
|
35
|
+
maxWidth?: ColumnStaticSize | null;
|
|
36
|
+
/** The default width of the column. */
|
|
37
|
+
defaultWidth?: ColumnSize | null;
|
|
38
|
+
/** Whether the column allows resizing. */
|
|
39
|
+
allowsResizing?: boolean;
|
|
40
|
+
/** Whether the column allows sorting. */
|
|
41
|
+
allowsSorting?: boolean;
|
|
42
|
+
/**
|
|
43
|
+
* Whether a column is a [row header](https://www.w3.org/TR/wai-aria-1.1/#rowheader) and should be
|
|
44
|
+
* announced by assistive technology during row navigation.
|
|
45
|
+
*/
|
|
46
|
+
isRowHeader?: boolean;
|
|
47
|
+
/** A string representation of the column's contents, used for accessibility announcements. */
|
|
48
|
+
textValue?: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function Column<T>(props: ColumnProps<T>): any {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
Column.getCollectionNode = function* getCollectionNode<T>(
|
|
56
|
+
props: ColumnProps<T>,
|
|
57
|
+
context: CollectionBuilderContext<T>,
|
|
58
|
+
): Generator<PartialNode<T>, void, GridNode<T>[]> {
|
|
59
|
+
let { title, children, childColumns } = props;
|
|
60
|
+
|
|
61
|
+
let rendered = title || children;
|
|
62
|
+
let textValue =
|
|
63
|
+
props.textValue ||
|
|
64
|
+
(typeof rendered === 'string' ? rendered : '') ||
|
|
65
|
+
(props as any)['aria-label'];
|
|
66
|
+
|
|
67
|
+
let fullNodes = yield {
|
|
68
|
+
type: 'column',
|
|
69
|
+
hasChildNodes: !!childColumns || (!!title && Children.count(children) > 0),
|
|
70
|
+
rendered,
|
|
71
|
+
textValue,
|
|
72
|
+
props,
|
|
73
|
+
*childNodes() {
|
|
74
|
+
if (childColumns) {
|
|
75
|
+
for (let child of childColumns) {
|
|
76
|
+
yield {
|
|
77
|
+
type: 'column',
|
|
78
|
+
value: child,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
} else if (title) {
|
|
82
|
+
let childColumns: PartialNode<T>[] = [];
|
|
83
|
+
Children.forEach(children, (child: any) => {
|
|
84
|
+
childColumns.push({
|
|
85
|
+
type: 'column',
|
|
86
|
+
element: child,
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
yield* childColumns;
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
shouldInvalidate(newContext: CollectionBuilderContext<T>) {
|
|
94
|
+
// This is a bit of a hack, but it works.
|
|
95
|
+
// If this method is called, then there's a cached version of this node available.
|
|
96
|
+
// But, we need to keep the list of columns in the new context up to date.
|
|
97
|
+
updateContext(newContext);
|
|
98
|
+
return false;
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
let updateContext = (context: CollectionBuilderContext<T>) => {
|
|
103
|
+
// register leaf columns on the context so that <Row> can access them
|
|
104
|
+
for (let node of fullNodes) {
|
|
105
|
+
if (!node.hasChildNodes) {
|
|
106
|
+
context.columns.push(node);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
updateContext(context);
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* A Column represents a field of each item within a Table. Columns may also contain nested
|
|
116
|
+
* Column elements to represent column groups. Nested columns can be statically defined as
|
|
117
|
+
* children, or dynamically generated using a function based on the `childColumns` prop.
|
|
118
|
+
*/
|
|
119
|
+
// We don't want getCollectionNode to show up in the type definition
|
|
120
|
+
let _Column = Column as unknown as <T>(props: ColumnProps<T>) => any;
|
|
121
|
+
export { _Column as Column };
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
// Ported from react-stately (source: .react-spectrum/packages/react-stately/src/table/Row.ts).
|
|
2
|
+
// octane adaptations: React.Children → octane Children (descriptor arrays); React element
|
|
3
|
+
// types → `any` descriptors. Renders nothing; walked via static `getCollectionNode`.
|
|
4
|
+
// The `node.type === Row` check compares against the INTERNAL function identity, which is
|
|
5
|
+
// what descriptor `.type` carries for `<Row>` used at a value position.
|
|
6
|
+
import type { LinkDOMProps } from '@react-types/shared';
|
|
7
|
+
import { Children } from 'octane';
|
|
8
|
+
|
|
9
|
+
import type { CellElement, CellRenderer } from './Cell';
|
|
10
|
+
import type { CollectionBuilderContext } from './useTableState';
|
|
11
|
+
import type { PartialNode } from '../collections/types';
|
|
12
|
+
|
|
13
|
+
export type RowElement<T> = any;
|
|
14
|
+
export interface RowProps<T> extends LinkDOMProps {
|
|
15
|
+
/**
|
|
16
|
+
* A list of child item objects used when dynamically rendering row children. Requires the feature
|
|
17
|
+
* flag to be enabled along with UNSTABLE_allowsExpandableRows, see
|
|
18
|
+
* https://react-spectrum.adobe.com/react-spectrum/TableView.html#expandable-rows.
|
|
19
|
+
*
|
|
20
|
+
* @private
|
|
21
|
+
* @version alpha
|
|
22
|
+
*/
|
|
23
|
+
UNSTABLE_childItems?: Iterable<T>;
|
|
24
|
+
// TODO: update when async loading is supported for expandable rows
|
|
25
|
+
// /** Whether this row has children, even if not loaded yet. */
|
|
26
|
+
// hasChildItems?: boolean,
|
|
27
|
+
/** Rendered contents of the row or row child items. */
|
|
28
|
+
children: CellElement | CellElement[] | CellRenderer;
|
|
29
|
+
/** A string representation of the row's contents, used for features like typeahead. */
|
|
30
|
+
textValue?: string; // ???
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function Row<T>(props: RowProps<T>): any {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
Row.getCollectionNode = function* getCollectionNode<T>(
|
|
38
|
+
props: RowProps<T>,
|
|
39
|
+
context: CollectionBuilderContext<T>,
|
|
40
|
+
): Generator<PartialNode<T>> {
|
|
41
|
+
let { children, textValue, UNSTABLE_childItems } = props;
|
|
42
|
+
|
|
43
|
+
yield {
|
|
44
|
+
type: 'item',
|
|
45
|
+
props: props,
|
|
46
|
+
textValue,
|
|
47
|
+
'aria-label': (props as any)['aria-label'],
|
|
48
|
+
hasChildNodes: true,
|
|
49
|
+
*childNodes() {
|
|
50
|
+
// Process cells first
|
|
51
|
+
if (context.showDragButtons) {
|
|
52
|
+
yield {
|
|
53
|
+
type: 'cell',
|
|
54
|
+
key: 'header-drag', // this is combined with the row key by CollectionBuilder
|
|
55
|
+
props: {
|
|
56
|
+
isDragButtonCell: true,
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (context.showSelectionCheckboxes && context.selectionMode !== 'none') {
|
|
62
|
+
yield {
|
|
63
|
+
type: 'cell',
|
|
64
|
+
key: 'header', // this is combined with the row key by CollectionBuilder
|
|
65
|
+
props: {
|
|
66
|
+
isSelectionCell: true,
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (typeof children === 'function') {
|
|
72
|
+
for (let column of context.columns) {
|
|
73
|
+
yield {
|
|
74
|
+
type: 'cell',
|
|
75
|
+
element: children(column.key),
|
|
76
|
+
key: column.key, // this is combined with the row key by CollectionBuilder
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (UNSTABLE_childItems) {
|
|
81
|
+
for (let child of UNSTABLE_childItems) {
|
|
82
|
+
// Note: in order to reuse the render function of TableBody for our child rows, we just need to yield a type and a value here. CollectionBuilder will then look up
|
|
83
|
+
// the parent renderer and use that to build the full node of this child row, using the value provided here to generate the cells
|
|
84
|
+
yield {
|
|
85
|
+
type: 'item',
|
|
86
|
+
value: child,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
} else {
|
|
91
|
+
let cells: PartialNode<T>[] = [];
|
|
92
|
+
let childRows: PartialNode<T>[] = [];
|
|
93
|
+
let columnCount = 0;
|
|
94
|
+
Children.forEach(children, (node: any) => {
|
|
95
|
+
if (node.type === Row) {
|
|
96
|
+
if (cells.length < context.columns.length) {
|
|
97
|
+
throw new Error(
|
|
98
|
+
"All of a Row's child Cells must be positioned before any child Rows.",
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
childRows.push({
|
|
103
|
+
type: 'item',
|
|
104
|
+
element: node,
|
|
105
|
+
});
|
|
106
|
+
} else {
|
|
107
|
+
cells.push({
|
|
108
|
+
type: 'cell',
|
|
109
|
+
element: node,
|
|
110
|
+
});
|
|
111
|
+
columnCount += node.props.colSpan ?? 1;
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
if (columnCount !== context.columns.length) {
|
|
116
|
+
throw new Error(
|
|
117
|
+
`Cell count must match column count. Found ${columnCount} cells and ${context.columns.length} columns.`,
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
yield* cells;
|
|
122
|
+
yield* childRows;
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
shouldInvalidate(newContext: CollectionBuilderContext<T>) {
|
|
126
|
+
// Invalidate all rows if the columns changed.
|
|
127
|
+
return (
|
|
128
|
+
newContext.columns.length !== context.columns.length ||
|
|
129
|
+
newContext.columns.some((c, i) => c.key !== context.columns[i].key) ||
|
|
130
|
+
newContext.showSelectionCheckboxes !== context.showSelectionCheckboxes ||
|
|
131
|
+
newContext.showDragButtons !== context.showDragButtons ||
|
|
132
|
+
newContext.selectionMode !== context.selectionMode
|
|
133
|
+
);
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* A Row represents a single item in a Table and contains Cell elements for each column.
|
|
140
|
+
* Cells can be statically defined as children, or generated dynamically using a function
|
|
141
|
+
* based on the columns defined in the TableHeader.
|
|
142
|
+
*/
|
|
143
|
+
// We don't want getCollectionNode to show up in the type definition
|
|
144
|
+
let _Row = Row as unknown as <T>(props: RowProps<T>) => any;
|
|
145
|
+
export { _Row as Row };
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// Ported from react-stately (source: .react-spectrum/packages/react-stately/src/table/TableBody.ts).
|
|
2
|
+
// octane adaptations: React.Children → octane Children (descriptor arrays); React element
|
|
3
|
+
// types → `any` descriptors. Renders nothing; walked via static `getCollectionNode`.
|
|
4
|
+
import type { AsyncLoadable, LoadingState } from '@react-types/shared';
|
|
5
|
+
import { Children } from 'octane';
|
|
6
|
+
|
|
7
|
+
import type { PartialNode } from '../collections/types';
|
|
8
|
+
import type { RowElement } from './Row';
|
|
9
|
+
|
|
10
|
+
export interface TableBodyProps<T> extends Omit<AsyncLoadable, 'isLoading'> {
|
|
11
|
+
/** The contents of the table body. Supports static items or a function for dynamic rendering. */
|
|
12
|
+
children: RowElement<T> | RowElement<T>[] | ((item: T) => RowElement<T>);
|
|
13
|
+
/** A list of row objects in the table body used when dynamically rendering rows. */
|
|
14
|
+
items?: Iterable<T>;
|
|
15
|
+
/** The current loading state of the table. */
|
|
16
|
+
loadingState?: LoadingState;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function TableBody<T>(props: TableBodyProps<T>): any {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
TableBody.getCollectionNode = function* getCollectionNode<T>(
|
|
24
|
+
props: TableBodyProps<T>,
|
|
25
|
+
): Generator<PartialNode<T>> {
|
|
26
|
+
let { children, items } = props;
|
|
27
|
+
yield {
|
|
28
|
+
type: 'body',
|
|
29
|
+
hasChildNodes: true,
|
|
30
|
+
props,
|
|
31
|
+
*childNodes() {
|
|
32
|
+
if (typeof children === 'function') {
|
|
33
|
+
if (!items) {
|
|
34
|
+
throw new Error('props.children was a function but props.items is missing');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
for (let item of items) {
|
|
38
|
+
yield {
|
|
39
|
+
type: 'item',
|
|
40
|
+
value: item,
|
|
41
|
+
renderer: children,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
} else {
|
|
45
|
+
let items: PartialNode<T>[] = [];
|
|
46
|
+
Children.forEach(children, (item: any) => {
|
|
47
|
+
items.push({
|
|
48
|
+
type: 'item',
|
|
49
|
+
element: item,
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
yield* items;
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* A TableBody is a container for the Row elements of a Table. Rows can be statically defined as
|
|
61
|
+
* children, or generated dynamically using a function based on the data passed to the `items`
|
|
62
|
+
* prop.
|
|
63
|
+
*/
|
|
64
|
+
// We don't want getCollectionNode to show up in the type definition
|
|
65
|
+
let _TableBody = TableBody as unknown as <T>(props: TableBodyProps<T>) => any;
|
|
66
|
+
export { _TableBody as TableBody };
|