@octanejs/aria 0.0.4 → 0.0.6
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 -5
- package/src/autocomplete/useAutocomplete.ts +656 -0
- package/src/collections/BaseCollection.ts +374 -0
- package/src/collections/CollectionBuilder.ts +343 -0
- package/src/collections/Document.ts +451 -0
- package/src/collections/Hidden.ts +84 -0
- package/src/collections/useCachedChildren.ts +103 -0
- package/src/components/Autocomplete.ts +124 -0
- package/src/components/Breadcrumbs.ts +170 -0
- package/src/components/Button.ts +209 -0
- package/src/components/Checkbox.ts +593 -0
- package/src/components/Collection.ts +301 -0
- package/src/components/ComboBox.ts +477 -0
- package/src/components/Dialog.ts +195 -0
- package/src/components/Disclosure.ts +322 -0
- package/src/components/DragAndDrop.ts +177 -0
- package/src/components/FieldError.ts +79 -0
- package/src/components/Form.ts +65 -0
- package/src/components/GridList.ts +1099 -0
- package/src/components/Group.ts +146 -0
- package/src/components/Header.ts +32 -0
- package/src/components/Heading.ts +45 -0
- package/src/components/Input.ts +139 -0
- package/src/components/Keyboard.ts +23 -0
- package/src/components/Label.ts +30 -0
- package/src/components/Link.ts +137 -0
- package/src/components/ListBox.ts +886 -0
- package/src/components/Menu.ts +685 -0
- package/src/components/Meter.ts +100 -0
- package/src/components/Modal.ts +352 -0
- package/src/components/NumberField.ts +221 -0
- package/src/components/OverlayArrow.ts +117 -0
- package/src/components/Popover.ts +385 -0
- package/src/components/ProgressBar.ts +112 -0
- package/src/components/RadioGroup.ts +542 -0
- package/src/components/SearchField.ts +208 -0
- package/src/components/Select.ts +505 -0
- package/src/components/SelectionIndicator.ts +52 -0
- package/src/components/Separator.ts +97 -0
- package/src/components/SharedElementTransition.ts +209 -0
- package/src/components/Slider.ts +472 -0
- package/src/components/Switch.ts +449 -0
- package/src/components/Table.ts +2618 -0
- package/src/components/Tabs.ts +675 -0
- package/src/components/TagGroup.ts +443 -0
- package/src/components/Text.ts +27 -0
- package/src/components/TextArea.ts +92 -0
- package/src/components/TextField.ts +200 -0
- package/src/components/ToggleButton.ts +147 -0
- package/src/components/ToggleButtonGroup.ts +106 -0
- package/src/components/Toolbar.ts +74 -0
- package/src/components/Tooltip.ts +249 -0
- package/src/components/Tree.ts +1450 -0
- package/src/components/TreeDropTargetDelegate.ts +363 -0
- package/src/components/index.ts +359 -0
- package/src/components/useDragAndDrop.ts +164 -0
- package/src/components/utils.ts +521 -0
- 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/ar-AE.json +3 -0
- package/src/intl/autocomplete/bg-BG.json +3 -0
- package/src/intl/autocomplete/cs-CZ.json +3 -0
- package/src/intl/autocomplete/da-DK.json +3 -0
- package/src/intl/autocomplete/de-DE.json +3 -0
- package/src/intl/autocomplete/el-GR.json +3 -0
- package/src/intl/autocomplete/en-US.json +3 -0
- package/src/intl/autocomplete/es-ES.json +3 -0
- package/src/intl/autocomplete/et-EE.json +3 -0
- package/src/intl/autocomplete/fi-FI.json +3 -0
- package/src/intl/autocomplete/fr-FR.json +3 -0
- package/src/intl/autocomplete/he-IL.json +3 -0
- package/src/intl/autocomplete/hr-HR.json +3 -0
- package/src/intl/autocomplete/hu-HU.json +3 -0
- package/src/intl/autocomplete/index.ts +82 -0
- package/src/intl/autocomplete/it-IT.json +3 -0
- package/src/intl/autocomplete/ja-JP.json +3 -0
- package/src/intl/autocomplete/ko-KR.json +3 -0
- package/src/intl/autocomplete/lt-LT.json +3 -0
- package/src/intl/autocomplete/lv-LV.json +3 -0
- package/src/intl/autocomplete/nb-NO.json +3 -0
- package/src/intl/autocomplete/nl-NL.json +3 -0
- package/src/intl/autocomplete/pl-PL.json +3 -0
- package/src/intl/autocomplete/pt-BR.json +3 -0
- package/src/intl/autocomplete/pt-PT.json +3 -0
- package/src/intl/autocomplete/ro-RO.json +3 -0
- package/src/intl/autocomplete/ru-RU.json +3 -0
- package/src/intl/autocomplete/sk-SK.json +3 -0
- package/src/intl/autocomplete/sl-SI.json +3 -0
- package/src/intl/autocomplete/sr-SP.json +3 -0
- package/src/intl/autocomplete/sv-SE.json +3 -0
- package/src/intl/autocomplete/tr-TR.json +3 -0
- package/src/intl/autocomplete/uk-UA.json +3 -0
- package/src/intl/autocomplete/zh-CN.json +3 -0
- package/src/intl/autocomplete/zh-TW.json +3 -0
- 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/autocomplete/useAutocompleteState.ts +85 -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/animation.ts +149 -0
- package/src/utils/useLoadMoreSentinel.ts +78 -0
- package/src/utils/useViewportSize.ts +123 -0
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
// Ported from react-aria (source: .react-spectrum/packages/react-aria/src/table/useTableRow.ts).
|
|
2
|
+
// octane adaptations:
|
|
3
|
+
// - Handlers receive NATIVE events: the treegrid expansion keydown handler takes the
|
|
4
|
+
// native KeyboardEvent.
|
|
5
|
+
// - `TableState`/`TreeGridState`/`ITableCollection` come from the ported stately hooks;
|
|
6
|
+
// `useGridRow` from the ported src/grid/; React's `HTMLAttributes` and `DOMAttributes`
|
|
7
|
+
// collapse to a structural prop bag.
|
|
8
|
+
// - The Parcel glob intl import becomes the generated src/intl/table index (verbatim
|
|
9
|
+
// dictionaries).
|
|
10
|
+
// - Public-hook slot threading (splitSlot/subSlot).
|
|
11
|
+
import type { AriaButtonProps } from '../button/useButton';
|
|
12
|
+
|
|
13
|
+
import type { Collection, FocusableElement, Node, RefObject } from '@react-types/shared';
|
|
14
|
+
import { getRowLabelledBy } from './utils';
|
|
15
|
+
import { GridRowAria, GridRowProps, useGridRow } from '../grid/useGridRow';
|
|
16
|
+
import intlMessages from '../intl/table';
|
|
17
|
+
import type { ITableCollection } from '../stately/table/TableCollection';
|
|
18
|
+
import { mergeProps } from '../utils/mergeProps';
|
|
19
|
+
import type { TableState } from '../stately/table/useTableState';
|
|
20
|
+
import type { TreeGridState } from '../stately/table/useTreeGridState';
|
|
21
|
+
import { useLabels } from '../utils/useLabels';
|
|
22
|
+
import { useLocale } from '../i18n/I18nProvider';
|
|
23
|
+
import { useLocalizedStringFormatter } from '../i18n/useLocalizedStringFormatter';
|
|
24
|
+
import { useSyntheticLinkProps } from '../utils/openLink';
|
|
25
|
+
|
|
26
|
+
import { S, splitSlot, subSlot } from '../internal';
|
|
27
|
+
|
|
28
|
+
const EXPANSION_KEYS = {
|
|
29
|
+
expand: {
|
|
30
|
+
ltr: 'ArrowRight',
|
|
31
|
+
rtl: 'ArrowLeft',
|
|
32
|
+
},
|
|
33
|
+
collapse: {
|
|
34
|
+
ltr: 'ArrowLeft',
|
|
35
|
+
rtl: 'ArrowRight',
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export interface TableRowAria extends GridRowAria {
|
|
40
|
+
expandButtonProps: AriaButtonProps;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Provides the behavior and accessibility implementation for a row in a table.
|
|
45
|
+
*
|
|
46
|
+
* @param props - Props for the row.
|
|
47
|
+
* @param state - State of the table, as returned by `useTableState`.
|
|
48
|
+
*/
|
|
49
|
+
export function useTableRow<T>(
|
|
50
|
+
props: GridRowProps<T>,
|
|
51
|
+
state: TableState<T> | TreeGridState<T>,
|
|
52
|
+
ref: RefObject<FocusableElement | null>,
|
|
53
|
+
): TableRowAria;
|
|
54
|
+
// Slot-threading form: sibling ported hooks pass their derived sub-slot as the trailing arg.
|
|
55
|
+
export function useTableRow<T>(
|
|
56
|
+
props: GridRowProps<T>,
|
|
57
|
+
state: TableState<T> | TreeGridState<T>,
|
|
58
|
+
ref: RefObject<FocusableElement | null>,
|
|
59
|
+
slot: symbol | undefined,
|
|
60
|
+
): TableRowAria;
|
|
61
|
+
export function useTableRow(...args: any[]): TableRowAria {
|
|
62
|
+
const [user, slotArg] = splitSlot(args);
|
|
63
|
+
const slot = slotArg ?? S('useTableRow');
|
|
64
|
+
const props = user[0] as GridRowProps<any>;
|
|
65
|
+
const state = user[1] as TableState<any> | TreeGridState<any>;
|
|
66
|
+
const ref = user[2] as RefObject<FocusableElement | null>;
|
|
67
|
+
|
|
68
|
+
let { node, isVirtualized } = props;
|
|
69
|
+
let { rowProps, ...states } = useGridRow<any, ITableCollection<any>, TableState<any>>(
|
|
70
|
+
props,
|
|
71
|
+
state as TableState<any>,
|
|
72
|
+
ref,
|
|
73
|
+
subSlot(slot, 'gridRow'),
|
|
74
|
+
);
|
|
75
|
+
let { direction } = useLocale(subSlot(slot, 'locale'));
|
|
76
|
+
|
|
77
|
+
if (isVirtualized && state.treeColumn == null) {
|
|
78
|
+
rowProps['aria-rowindex'] = node.index + 1 + state.collection.headerRows.length; // aria-rowindex is 1 based
|
|
79
|
+
} else {
|
|
80
|
+
delete rowProps['aria-rowindex'];
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
let isExpanded =
|
|
84
|
+
state.treeColumn != null && (state.expandedKeys === 'all' || state.expandedKeys.has(node.key));
|
|
85
|
+
let stringFormatter = useLocalizedStringFormatter(
|
|
86
|
+
intlMessages,
|
|
87
|
+
'@react-aria/table',
|
|
88
|
+
subSlot(slot, 'strings'),
|
|
89
|
+
);
|
|
90
|
+
let labelProps = useLabels(
|
|
91
|
+
{
|
|
92
|
+
'aria-label': isExpanded
|
|
93
|
+
? stringFormatter.format('collapse')
|
|
94
|
+
: stringFormatter.format('expand'),
|
|
95
|
+
'aria-labelledby': getRowLabelledBy(state as TableState<any>, node.key),
|
|
96
|
+
},
|
|
97
|
+
undefined,
|
|
98
|
+
subSlot(slot, 'labels'),
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
let treeGridRowProps: Record<string, any> = {};
|
|
102
|
+
let expandButtonProps: AriaButtonProps = {};
|
|
103
|
+
if (state.treeColumn != null) {
|
|
104
|
+
let treeNode = state.collection.getItem(node.key);
|
|
105
|
+
if (treeNode != null) {
|
|
106
|
+
let lastChild = getLastChild(state.collection, node);
|
|
107
|
+
let hasChildRows =
|
|
108
|
+
treeNode.props?.hasChildRows ||
|
|
109
|
+
treeNode.props?.UNSTABLE_childItems ||
|
|
110
|
+
lastChild?.type !== 'cell';
|
|
111
|
+
let parent = state.collection.getItem(node.parentKey!)!;
|
|
112
|
+
let isParentBody = parent.type === 'tablebody' || parent.type === 'body';
|
|
113
|
+
let lastSibling = getLastChild(state.collection, parent)!;
|
|
114
|
+
while (lastSibling && lastSibling.type !== 'item' && lastSibling.prevKey != null) {
|
|
115
|
+
lastSibling = state.collection.getItem(lastSibling.prevKey)!;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
treeGridRowProps = {
|
|
119
|
+
onKeyDown: (e: KeyboardEvent) => {
|
|
120
|
+
if (
|
|
121
|
+
e.key === EXPANSION_KEYS['expand'][direction] &&
|
|
122
|
+
state.selectionManager.focusedKey === treeNode.key &&
|
|
123
|
+
hasChildRows &&
|
|
124
|
+
state.expandedKeys !== 'all' &&
|
|
125
|
+
!state.expandedKeys.has(treeNode.key)
|
|
126
|
+
) {
|
|
127
|
+
state.toggleKey(treeNode.key);
|
|
128
|
+
e.stopPropagation();
|
|
129
|
+
} else if (
|
|
130
|
+
e.key === EXPANSION_KEYS['collapse'][direction] &&
|
|
131
|
+
state.selectionManager.focusedKey === treeNode.key
|
|
132
|
+
) {
|
|
133
|
+
if (state.expandedKeys !== 'all') {
|
|
134
|
+
if (hasChildRows && state.expandedKeys.has(treeNode.key)) {
|
|
135
|
+
state.toggleKey(treeNode.key);
|
|
136
|
+
e.stopPropagation();
|
|
137
|
+
} else if (
|
|
138
|
+
!state.expandedKeys.has(treeNode.key) &&
|
|
139
|
+
treeNode.parentKey != null &&
|
|
140
|
+
treeNode.level > 0
|
|
141
|
+
) {
|
|
142
|
+
// Item is a leaf or already collapsed, move focus to parent
|
|
143
|
+
state.selectionManager.setFocusedKey(treeNode.parentKey);
|
|
144
|
+
e.stopPropagation();
|
|
145
|
+
}
|
|
146
|
+
} else if (state.expandedKeys === 'all') {
|
|
147
|
+
state.toggleKey(treeNode.key);
|
|
148
|
+
e.stopPropagation();
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
},
|
|
152
|
+
'aria-expanded': hasChildRows
|
|
153
|
+
? state.expandedKeys === 'all' || state.expandedKeys.has(node.key)
|
|
154
|
+
: undefined,
|
|
155
|
+
'aria-level': treeNode.level + 1,
|
|
156
|
+
'aria-posinset': treeNode.index - (isParentBody ? 0 : state.collection.columnCount) + 1,
|
|
157
|
+
'aria-setsize': lastSibling.index - (isParentBody ? 0 : state.collection.columnCount) + 1,
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
expandButtonProps = {
|
|
161
|
+
isDisabled: states.isDisabled,
|
|
162
|
+
onPress: () => {
|
|
163
|
+
if (!states.isDisabled) {
|
|
164
|
+
state.toggleKey(node.key);
|
|
165
|
+
state.selectionManager.setFocused(true);
|
|
166
|
+
state.selectionManager.setFocusedKey(node.key);
|
|
167
|
+
}
|
|
168
|
+
},
|
|
169
|
+
excludeFromTabOrder: true,
|
|
170
|
+
preventFocusOnPress: true,
|
|
171
|
+
// @ts-ignore
|
|
172
|
+
'data-react-aria-prevent-focus': true,
|
|
173
|
+
...labelProps,
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
let syntheticLinkProps = useSyntheticLinkProps(node.props);
|
|
179
|
+
let linkProps = states.hasAction ? syntheticLinkProps : {};
|
|
180
|
+
return {
|
|
181
|
+
rowProps: {
|
|
182
|
+
...mergeProps(rowProps, treeGridRowProps, linkProps),
|
|
183
|
+
'aria-labelledby': getRowLabelledBy(state as TableState<any>, node.key),
|
|
184
|
+
},
|
|
185
|
+
expandButtonProps,
|
|
186
|
+
...states,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function getLastChild(collection: Collection<Node<unknown>>, node: Node<unknown>) {
|
|
191
|
+
if ('lastChildKey' in node) {
|
|
192
|
+
return (node as any).lastChildKey != null
|
|
193
|
+
? collection.getItem((node as any).lastChildKey)
|
|
194
|
+
: null;
|
|
195
|
+
} else {
|
|
196
|
+
return Array.from(node.childNodes).findLast((item) => item.parentKey === node.key);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// Ported from react-aria (source: .react-spectrum/packages/react-aria/src/table/useTableRowGroup.ts).
|
|
2
|
+
// octane adaptations: composes the ported src/grid/useGridRowGroup; the optional trailing
|
|
3
|
+
// slot is forwarded (uniform calling convention).
|
|
4
|
+
import { GridRowGroupAria, useGridRowGroup } from '../grid/useGridRowGroup';
|
|
5
|
+
|
|
6
|
+
import { restSlot } from '../internal';
|
|
7
|
+
|
|
8
|
+
export function useTableRowGroup(): GridRowGroupAria;
|
|
9
|
+
// Slot-threading form: sibling ported hooks pass their derived sub-slot as the trailing arg.
|
|
10
|
+
export function useTableRowGroup(slot: symbol | undefined): GridRowGroupAria;
|
|
11
|
+
export function useTableRowGroup(...args: any[]): GridRowGroupAria {
|
|
12
|
+
return useGridRowGroup(restSlot(args));
|
|
13
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
// Ported from react-aria (source: .react-spectrum/packages/react-aria/src/table/useTableSelectionCheckbox.ts).
|
|
2
|
+
// octane adaptations:
|
|
3
|
+
// - `TableState` comes from the ported stately table hook; `useGridSelectionCheckbox`
|
|
4
|
+
// from the ported src/grid/.
|
|
5
|
+
// - The checkbox `onChange` is the value-level AriaCheckboxProps callback (not DOM
|
|
6
|
+
// wiring), so it ports unchanged — consumers wire it to the native checkbox `change`
|
|
7
|
+
// event.
|
|
8
|
+
// - The Parcel glob intl import becomes the generated src/intl/table index (verbatim
|
|
9
|
+
// dictionaries).
|
|
10
|
+
// - Public-hook slot threading (splitSlot/subSlot).
|
|
11
|
+
import type { AriaCheckboxProps } from '../checkbox/useCheckbox';
|
|
12
|
+
import { getRowLabelledBy } from './utils';
|
|
13
|
+
import intlMessages from '../intl/table';
|
|
14
|
+
import type { Key } from '@react-types/shared';
|
|
15
|
+
import type { TableState } from '../stately/table/useTableState';
|
|
16
|
+
import { useGridSelectionCheckbox } from '../grid/useGridSelectionCheckbox';
|
|
17
|
+
import { useLocalizedStringFormatter } from '../i18n/useLocalizedStringFormatter';
|
|
18
|
+
|
|
19
|
+
import { S, splitSlot, subSlot } from '../internal';
|
|
20
|
+
|
|
21
|
+
export interface AriaTableSelectionCheckboxProps {
|
|
22
|
+
/** A unique key for the checkbox. */
|
|
23
|
+
key: Key;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface TableSelectionCheckboxAria {
|
|
27
|
+
/** Props for the row selection checkbox element. */
|
|
28
|
+
checkboxProps: AriaCheckboxProps;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface TableSelectAllCheckboxAria {
|
|
32
|
+
/** Props for the select all checkbox element. */
|
|
33
|
+
checkboxProps: AriaCheckboxProps;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Provides the behavior and accessibility implementation for a selection checkbox in a table.
|
|
38
|
+
*
|
|
39
|
+
* @param props - Props for the selection checkbox.
|
|
40
|
+
* @param state - State of the table, as returned by `useTableState`.
|
|
41
|
+
*/
|
|
42
|
+
export function useTableSelectionCheckbox<T>(
|
|
43
|
+
props: AriaTableSelectionCheckboxProps,
|
|
44
|
+
state: TableState<T>,
|
|
45
|
+
): TableSelectionCheckboxAria;
|
|
46
|
+
// Slot-threading form: sibling ported hooks pass their derived sub-slot as the trailing arg.
|
|
47
|
+
export function useTableSelectionCheckbox<T>(
|
|
48
|
+
props: AriaTableSelectionCheckboxProps,
|
|
49
|
+
state: TableState<T>,
|
|
50
|
+
slot: symbol | undefined,
|
|
51
|
+
): TableSelectionCheckboxAria;
|
|
52
|
+
export function useTableSelectionCheckbox(...args: any[]): TableSelectionCheckboxAria {
|
|
53
|
+
const [user, slotArg] = splitSlot(args);
|
|
54
|
+
const slot = slotArg ?? S('useTableSelectionCheckbox');
|
|
55
|
+
const props = user[0] as AriaTableSelectionCheckboxProps;
|
|
56
|
+
const state = user[1] as TableState<any>;
|
|
57
|
+
|
|
58
|
+
let { key } = props;
|
|
59
|
+
const { checkboxProps } = useGridSelectionCheckbox(props, state, subSlot(slot, 'gridCheckbox'));
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
checkboxProps: {
|
|
63
|
+
...checkboxProps,
|
|
64
|
+
'aria-labelledby': `${checkboxProps.id} ${getRowLabelledBy(state, key)}`,
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Provides the behavior and accessibility implementation for the select all checkbox in a table.
|
|
71
|
+
*
|
|
72
|
+
* @param props - Props for the select all checkbox.
|
|
73
|
+
* @param state - State of the table, as returned by `useTableState`.
|
|
74
|
+
*/
|
|
75
|
+
export function useTableSelectAllCheckbox<T>(state: TableState<T>): TableSelectAllCheckboxAria;
|
|
76
|
+
// Slot-threading form: sibling ported hooks pass their derived sub-slot as the trailing arg.
|
|
77
|
+
export function useTableSelectAllCheckbox<T>(
|
|
78
|
+
state: TableState<T>,
|
|
79
|
+
slot: symbol | undefined,
|
|
80
|
+
): TableSelectAllCheckboxAria;
|
|
81
|
+
export function useTableSelectAllCheckbox(...args: any[]): TableSelectAllCheckboxAria {
|
|
82
|
+
const [user, slotArg] = splitSlot(args);
|
|
83
|
+
const slot = slotArg ?? S('useTableSelectAllCheckbox');
|
|
84
|
+
const state = user[0] as TableState<any>;
|
|
85
|
+
|
|
86
|
+
let { isEmpty, isSelectAll, selectionMode } = state.selectionManager;
|
|
87
|
+
const stringFormatter = useLocalizedStringFormatter(
|
|
88
|
+
intlMessages,
|
|
89
|
+
'@react-aria/table',
|
|
90
|
+
subSlot(slot, 'strings'),
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
return {
|
|
94
|
+
checkboxProps: {
|
|
95
|
+
'aria-label': stringFormatter.format(selectionMode === 'single' ? 'select' : 'selectAll'),
|
|
96
|
+
isSelected: isSelectAll,
|
|
97
|
+
isDisabled:
|
|
98
|
+
selectionMode !== 'multiple' ||
|
|
99
|
+
state.collection.size === 0 ||
|
|
100
|
+
(state.collection.rows.length === 1 && state.collection.rows[0].type === 'loader'),
|
|
101
|
+
isIndeterminate: !isEmpty && !isSelectAll,
|
|
102
|
+
onChange: () => state.selectionManager.toggleSelectAll(),
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// Ported from react-aria (source: .react-spectrum/packages/react-aria/src/table/utils.ts).
|
|
2
|
+
// octane adaptations: `TableState` type comes from the ported stately table hook.
|
|
3
|
+
import type { Key } from '@react-types/shared';
|
|
4
|
+
import type { TableState } from '../stately/table/useTableState';
|
|
5
|
+
|
|
6
|
+
export const gridIds: WeakMap<TableState<unknown>, string> = new WeakMap<
|
|
7
|
+
TableState<unknown>,
|
|
8
|
+
string
|
|
9
|
+
>();
|
|
10
|
+
|
|
11
|
+
function normalizeKey(key: Key): string {
|
|
12
|
+
if (typeof key === 'string') {
|
|
13
|
+
return key.replace(/\s*/g, '');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return '' + key;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function getColumnHeaderId<T>(state: TableState<T>, columnKey: Key): string {
|
|
20
|
+
let gridId = gridIds.get(state);
|
|
21
|
+
if (!gridId) {
|
|
22
|
+
throw new Error('Unknown grid');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return `${gridId}-${normalizeKey(columnKey)}`;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function getCellId<T>(state: TableState<T>, rowKey: Key, columnKey: Key): string {
|
|
29
|
+
let gridId = gridIds.get(state);
|
|
30
|
+
if (!gridId) {
|
|
31
|
+
throw new Error('Unknown grid');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return `${gridId}-${normalizeKey(rowKey)}-${normalizeKey(columnKey)}`;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function getRowLabelledBy<T>(state: TableState<T>, rowKey: Key): string {
|
|
38
|
+
// A row is labelled by it's row headers.
|
|
39
|
+
return [...state.collection.rowHeaderColumnKeys]
|
|
40
|
+
.map((columnKey) => getCellId(state, rowKey, columnKey))
|
|
41
|
+
.join(' ');
|
|
42
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// Ported from react-aria (source: .react-spectrum/packages/react-aria/src/tree/useTree.ts).
|
|
2
|
+
// octane adaptations:
|
|
3
|
+
// - `TreeState` comes from the ported stately tree hook; `DOMAttributes` is a local
|
|
4
|
+
// structural prop-bag alias (upstream's is typed over React's synthetic handlers).
|
|
5
|
+
// - Public-hook slot threading (splitSlot/subSlot).
|
|
6
|
+
import type { KeyboardDelegate, RefObject } from '@react-types/shared';
|
|
7
|
+
import {
|
|
8
|
+
type AriaGridListOptions,
|
|
9
|
+
type AriaGridListProps,
|
|
10
|
+
type GridListProps,
|
|
11
|
+
useGridList,
|
|
12
|
+
} from '../gridlist/useGridList';
|
|
13
|
+
import type { TreeState } from '../stately/tree/useTreeState';
|
|
14
|
+
|
|
15
|
+
import { S, splitSlot, subSlot } from '../internal';
|
|
16
|
+
|
|
17
|
+
// octane adaptation: minimal structural DOMAttributes (upstream's drags React's synthetic
|
|
18
|
+
// handler types along).
|
|
19
|
+
type DOMAttributes = Record<string, any>;
|
|
20
|
+
|
|
21
|
+
export interface TreeProps<T> extends GridListProps<T> {}
|
|
22
|
+
|
|
23
|
+
export interface AriaTreeProps<T> extends AriaGridListProps<T> {}
|
|
24
|
+
export interface AriaTreeOptions<T> extends Omit<
|
|
25
|
+
AriaGridListOptions<T>,
|
|
26
|
+
'children' | 'shouldFocusWrap'
|
|
27
|
+
> {
|
|
28
|
+
/**
|
|
29
|
+
* An optional keyboard delegate implementation for type to select,
|
|
30
|
+
* to override the default.
|
|
31
|
+
*/
|
|
32
|
+
keyboardDelegate?: KeyboardDelegate;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface TreeAria {
|
|
36
|
+
/** Props for the treegrid element. */
|
|
37
|
+
gridProps: DOMAttributes;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Provides the behavior and accessibility implementation for a single column treegrid component
|
|
42
|
+
* with interactive children. A tree grid provides users with a way to navigate nested hierarchical
|
|
43
|
+
* information.
|
|
44
|
+
*
|
|
45
|
+
* @param props - Props for the treegrid.
|
|
46
|
+
* @param state - State for the treegrid, as returned by `useTreeState`.
|
|
47
|
+
* @param ref - The ref attached to the treegrid element.
|
|
48
|
+
*/
|
|
49
|
+
export function useTree<T>(
|
|
50
|
+
props: AriaTreeOptions<T>,
|
|
51
|
+
state: TreeState<T>,
|
|
52
|
+
ref: RefObject<HTMLElement | null>,
|
|
53
|
+
): TreeAria;
|
|
54
|
+
// Slot-threading form: sibling ported hooks pass their derived sub-slot as the trailing arg.
|
|
55
|
+
export function useTree<T>(
|
|
56
|
+
props: AriaTreeOptions<T>,
|
|
57
|
+
state: TreeState<T>,
|
|
58
|
+
ref: RefObject<HTMLElement | null>,
|
|
59
|
+
slot: symbol | undefined,
|
|
60
|
+
): TreeAria;
|
|
61
|
+
export function useTree(...args: any[]): TreeAria {
|
|
62
|
+
const [user, slotArg] = splitSlot(args);
|
|
63
|
+
const slot = slotArg ?? S('useTree');
|
|
64
|
+
const props = user[0] as AriaTreeOptions<any>;
|
|
65
|
+
const state = user[1] as TreeState<any>;
|
|
66
|
+
const ref = user[2] as RefObject<HTMLElement | null>;
|
|
67
|
+
|
|
68
|
+
// TreeState is a structural superset of ListState (collection/disabledKeys/selectionManager),
|
|
69
|
+
// exactly like upstream's call into useGridList.
|
|
70
|
+
let { gridProps } = useGridList(props as any, state as any, ref, subSlot(slot, 'gridList'));
|
|
71
|
+
gridProps.role = 'treegrid';
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
gridProps,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
// Ported from react-aria (source: .react-spectrum/packages/react-aria/src/tree/useTreeItem.ts).
|
|
2
|
+
// octane adaptations:
|
|
3
|
+
// - The Parcel glob intl import becomes the generated src/intl/tree index (verbatim
|
|
4
|
+
// locale JSONs from the pinned checkout).
|
|
5
|
+
// - `TreeState` comes from the ported stately tree hook; `DOMAttributes` is a local
|
|
6
|
+
// structural prop-bag alias (upstream's is typed over React's synthetic handlers).
|
|
7
|
+
// - Public-hook slot threading (splitSlot/subSlot).
|
|
8
|
+
import type { FocusableElement, Node, RefObject } from '@react-types/shared';
|
|
9
|
+
import type { AriaButtonProps } from '../button/useButton';
|
|
10
|
+
import {
|
|
11
|
+
type AriaGridListItemOptions,
|
|
12
|
+
type GridListItemAria,
|
|
13
|
+
useGridListItem,
|
|
14
|
+
} from '../gridlist/useGridListItem';
|
|
15
|
+
import { useLocalizedStringFormatter } from '../i18n/useLocalizedStringFormatter';
|
|
16
|
+
import intlMessages from '../intl/tree';
|
|
17
|
+
import type { TreeState } from '../stately/tree/useTreeState';
|
|
18
|
+
import { useLabels } from '../utils/useLabels';
|
|
19
|
+
|
|
20
|
+
import { S, splitSlot, subSlot } from '../internal';
|
|
21
|
+
|
|
22
|
+
// octane adaptation: minimal structural DOMAttributes (upstream's drags React's synthetic
|
|
23
|
+
// handler types along).
|
|
24
|
+
type DOMAttributes = Record<string, any>;
|
|
25
|
+
|
|
26
|
+
export interface AriaTreeItemOptions extends Omit<AriaGridListItemOptions, 'isVirtualized'> {
|
|
27
|
+
/**
|
|
28
|
+
* An object representing the treegrid item. Contains all the relevant information that makes up
|
|
29
|
+
* the treegrid row.
|
|
30
|
+
*/
|
|
31
|
+
node: Node<unknown>;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface TreeItemAria extends GridListItemAria {
|
|
35
|
+
/** Props for the tree grid row element. */
|
|
36
|
+
rowProps: DOMAttributes;
|
|
37
|
+
/** Props for the tree grid cell element within the tree grid list row. */
|
|
38
|
+
gridCellProps: DOMAttributes;
|
|
39
|
+
/** Props for the tree grid row description element, if any. */
|
|
40
|
+
descriptionProps: DOMAttributes;
|
|
41
|
+
/** Props for the tree grid row expand button. */
|
|
42
|
+
expandButtonProps: AriaButtonProps;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Provides the behavior and accessibility implementation for a row in a tree grid list.
|
|
47
|
+
*
|
|
48
|
+
* @param props - Props for the row.
|
|
49
|
+
* @param state - State of the parent list, as returned by `useTreeState`.
|
|
50
|
+
* @param ref - The ref attached to the row element.
|
|
51
|
+
*/
|
|
52
|
+
export function useTreeItem<T>(
|
|
53
|
+
props: AriaTreeItemOptions,
|
|
54
|
+
state: TreeState<T>,
|
|
55
|
+
ref: RefObject<FocusableElement | null>,
|
|
56
|
+
): TreeItemAria;
|
|
57
|
+
// Slot-threading form: sibling ported hooks pass their derived sub-slot as the trailing arg.
|
|
58
|
+
export function useTreeItem<T>(
|
|
59
|
+
props: AriaTreeItemOptions,
|
|
60
|
+
state: TreeState<T>,
|
|
61
|
+
ref: RefObject<FocusableElement | null>,
|
|
62
|
+
slot: symbol | undefined,
|
|
63
|
+
): TreeItemAria;
|
|
64
|
+
export function useTreeItem(...args: any[]): TreeItemAria {
|
|
65
|
+
const [user, slotArg] = splitSlot(args);
|
|
66
|
+
const slot = slotArg ?? S('useTreeItem');
|
|
67
|
+
const props = user[0] as AriaTreeItemOptions;
|
|
68
|
+
const state = user[1] as TreeState<any>;
|
|
69
|
+
const ref = user[2] as RefObject<FocusableElement | null>;
|
|
70
|
+
|
|
71
|
+
let { node } = props;
|
|
72
|
+
let gridListAria = useGridListItem(props, state, ref, subSlot(slot, 'gridListItem'));
|
|
73
|
+
let isExpanded = gridListAria.rowProps['aria-expanded'] === true;
|
|
74
|
+
let stringFormatter = useLocalizedStringFormatter(
|
|
75
|
+
intlMessages,
|
|
76
|
+
'@react-aria/tree',
|
|
77
|
+
subSlot(slot, 'strings'),
|
|
78
|
+
);
|
|
79
|
+
let labelProps = useLabels(
|
|
80
|
+
{
|
|
81
|
+
'aria-label': isExpanded
|
|
82
|
+
? stringFormatter.format('collapse')
|
|
83
|
+
: stringFormatter.format('expand'),
|
|
84
|
+
'aria-labelledby': gridListAria.rowProps.id,
|
|
85
|
+
},
|
|
86
|
+
undefined,
|
|
87
|
+
subSlot(slot, 'labels'),
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
let expandButtonProps = {
|
|
91
|
+
onPress: () => {
|
|
92
|
+
if (!gridListAria.isDisabled) {
|
|
93
|
+
state.toggleKey(node.key);
|
|
94
|
+
state.selectionManager.setFocused(true);
|
|
95
|
+
state.selectionManager.setFocusedKey(node.key);
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
excludeFromTabOrder: true,
|
|
99
|
+
preventFocusOnPress: true,
|
|
100
|
+
'data-react-aria-prevent-focus': true,
|
|
101
|
+
...labelProps,
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
// TODO: should it return a state specifically for isExpanded? Or is aria attribute sufficient?
|
|
105
|
+
return {
|
|
106
|
+
...gridListAria,
|
|
107
|
+
expandButtonProps,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
// Ported from react-aria (source: .react-spectrum/packages/react-aria/src/utils/animation.ts).
|
|
2
|
+
// octane adaptations: `flushSync` comes from 'octane' (not react-dom); public hooks get the
|
|
3
|
+
// binding's slot threading (splitSlot/subSlot); explicit dep arrays are preserved verbatim.
|
|
4
|
+
// In environments without `Element#getAnimations` (jsdom), the animation completes
|
|
5
|
+
// immediately, exactly like upstream's JSDOM branch.
|
|
6
|
+
import { flushSync, useCallback, useState } from 'octane';
|
|
7
|
+
|
|
8
|
+
import { S, splitSlot, subSlot } from '../internal';
|
|
9
|
+
import { useLayoutEffect } from './useLayoutEffect';
|
|
10
|
+
|
|
11
|
+
type RefObject<T> = { current: T };
|
|
12
|
+
|
|
13
|
+
export function useEnterAnimation(ref: RefObject<HTMLElement | null>, isReady?: boolean): boolean;
|
|
14
|
+
// Slot-threading form: sibling ported hooks pass their derived sub-slot as the trailing arg.
|
|
15
|
+
export function useEnterAnimation(
|
|
16
|
+
ref: RefObject<HTMLElement | null>,
|
|
17
|
+
isReady: boolean | undefined,
|
|
18
|
+
slot: symbol | undefined,
|
|
19
|
+
): boolean;
|
|
20
|
+
export function useEnterAnimation(...args: any[]): boolean {
|
|
21
|
+
const [user, slotArg] = splitSlot(args);
|
|
22
|
+
const slot = slotArg ?? S('useEnterAnimation');
|
|
23
|
+
const ref = user[0] as RefObject<HTMLElement | null>;
|
|
24
|
+
const isReady = (user[1] as boolean | undefined) ?? true;
|
|
25
|
+
|
|
26
|
+
let [isEntering, setEntering] = useState(true, subSlot(slot, 'entering'));
|
|
27
|
+
let isAnimationReady = isEntering && isReady;
|
|
28
|
+
|
|
29
|
+
// There are two cases for entry animations:
|
|
30
|
+
// 1. CSS @keyframes. The `animation` property is set during the isEntering state, and it is removed after the animation finishes.
|
|
31
|
+
// 2. CSS transitions. The initial styles are applied during the isEntering state, and removed immediately, causing the transition to occur.
|
|
32
|
+
//
|
|
33
|
+
// In the second case, cancel any transitions that were triggered prior to the isEntering = false state (when the transition is supposed to start).
|
|
34
|
+
// This can happen when isReady starts as false (e.g. popovers prior to placement calculation).
|
|
35
|
+
useLayoutEffect(
|
|
36
|
+
() => {
|
|
37
|
+
if (isAnimationReady && ref.current && 'getAnimations' in ref.current) {
|
|
38
|
+
for (let animation of ref.current.getAnimations()) {
|
|
39
|
+
if (animation instanceof CSSTransition) {
|
|
40
|
+
animation.cancel();
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
[ref, isAnimationReady],
|
|
46
|
+
subSlot(slot, 'cancelTransitions'),
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
useAnimation(
|
|
50
|
+
ref,
|
|
51
|
+
isAnimationReady,
|
|
52
|
+
useCallback(() => setEntering(false), [], subSlot(slot, 'onEnd')),
|
|
53
|
+
subSlot(slot, 'animation'),
|
|
54
|
+
);
|
|
55
|
+
return isAnimationReady;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function useExitAnimation(ref: RefObject<HTMLElement | null>, isOpen: boolean): boolean;
|
|
59
|
+
// Slot-threading form: sibling ported hooks pass their derived sub-slot as the trailing arg.
|
|
60
|
+
export function useExitAnimation(
|
|
61
|
+
ref: RefObject<HTMLElement | null>,
|
|
62
|
+
isOpen: boolean,
|
|
63
|
+
slot: symbol | undefined,
|
|
64
|
+
): boolean;
|
|
65
|
+
export function useExitAnimation(...args: any[]): boolean {
|
|
66
|
+
const [user, slotArg] = splitSlot(args);
|
|
67
|
+
const slot = slotArg ?? S('useExitAnimation');
|
|
68
|
+
const ref = user[0] as RefObject<HTMLElement | null>;
|
|
69
|
+
const isOpen = user[1] as boolean;
|
|
70
|
+
|
|
71
|
+
let [exitState, setExitState] = useState<'closed' | 'open' | 'exiting'>(
|
|
72
|
+
isOpen ? 'open' : 'closed',
|
|
73
|
+
subSlot(slot, 'exitState'),
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
switch (exitState) {
|
|
77
|
+
case 'open':
|
|
78
|
+
// If isOpen becomes false, set the state to exiting.
|
|
79
|
+
if (!isOpen) {
|
|
80
|
+
setExitState('exiting');
|
|
81
|
+
}
|
|
82
|
+
break;
|
|
83
|
+
case 'closed':
|
|
84
|
+
case 'exiting':
|
|
85
|
+
// If we are exiting and isOpen becomes true, the animation was interrupted.
|
|
86
|
+
// Reset the state to open.
|
|
87
|
+
if (isOpen) {
|
|
88
|
+
setExitState('open');
|
|
89
|
+
}
|
|
90
|
+
break;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
let isExiting = exitState === 'exiting';
|
|
94
|
+
useAnimation(
|
|
95
|
+
ref,
|
|
96
|
+
isExiting,
|
|
97
|
+
useCallback(
|
|
98
|
+
() => {
|
|
99
|
+
// Set the state to closed, which will cause the element to be unmounted.
|
|
100
|
+
setExitState((state) => (state === 'exiting' ? 'closed' : state));
|
|
101
|
+
},
|
|
102
|
+
[],
|
|
103
|
+
subSlot(slot, 'onEnd'),
|
|
104
|
+
),
|
|
105
|
+
subSlot(slot, 'animation'),
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
return isExiting;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function useAnimation(
|
|
112
|
+
ref: RefObject<HTMLElement | null>,
|
|
113
|
+
isActive: boolean,
|
|
114
|
+
onEnd: () => void,
|
|
115
|
+
slot: symbol | undefined,
|
|
116
|
+
): void {
|
|
117
|
+
useLayoutEffect(
|
|
118
|
+
() => {
|
|
119
|
+
if (isActive && ref.current) {
|
|
120
|
+
if (!('getAnimations' in ref.current)) {
|
|
121
|
+
// JSDOM
|
|
122
|
+
onEnd();
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
let animations = ref.current.getAnimations();
|
|
127
|
+
if (animations.length === 0) {
|
|
128
|
+
onEnd();
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
let canceled = false;
|
|
133
|
+
Promise.allSettled(animations.map((a) => a.finished)).then(() => {
|
|
134
|
+
if (!canceled) {
|
|
135
|
+
flushSync(() => {
|
|
136
|
+
onEnd();
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
return () => {
|
|
142
|
+
canceled = true;
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
},
|
|
146
|
+
[ref, isActive, onEnd],
|
|
147
|
+
subSlot(slot, 'effect'),
|
|
148
|
+
);
|
|
149
|
+
}
|