@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,82 @@
|
|
|
1
|
+
// Generated locale-dictionary index for the tree area — the octane equivalent of
|
|
2
|
+
// upstream's Parcel glob import (`import intlMessages from '../../intl/tree/*.json'`).
|
|
3
|
+
// The JSON files are copied VERBATIM from .react-spectrum/packages/react-aria/intl/tree/;
|
|
4
|
+
// refresh them from the pinned checkout on version bumps, never hand-edit.
|
|
5
|
+
//
|
|
6
|
+
// Upstream's Parcel build additionally compiles each message through
|
|
7
|
+
// @internationalized/string-compiler, so `{variable}` placeholders and plural/select
|
|
8
|
+
// ICU arguments become message FUNCTIONS (LocalizedStringFormatter interpolates only
|
|
9
|
+
// function messages — plain strings are returned verbatim). `compileDictionaries`
|
|
10
|
+
// applies the equivalent compile step here at module init.
|
|
11
|
+
import { compileDictionaries } from '../compileMessages';
|
|
12
|
+
import ar_AE from './ar-AE.json';
|
|
13
|
+
import bg_BG from './bg-BG.json';
|
|
14
|
+
import cs_CZ from './cs-CZ.json';
|
|
15
|
+
import da_DK from './da-DK.json';
|
|
16
|
+
import de_DE from './de-DE.json';
|
|
17
|
+
import el_GR from './el-GR.json';
|
|
18
|
+
import en_US from './en-US.json';
|
|
19
|
+
import es_ES from './es-ES.json';
|
|
20
|
+
import et_EE from './et-EE.json';
|
|
21
|
+
import fi_FI from './fi-FI.json';
|
|
22
|
+
import fr_FR from './fr-FR.json';
|
|
23
|
+
import he_IL from './he-IL.json';
|
|
24
|
+
import hr_HR from './hr-HR.json';
|
|
25
|
+
import hu_HU from './hu-HU.json';
|
|
26
|
+
import it_IT from './it-IT.json';
|
|
27
|
+
import ja_JP from './ja-JP.json';
|
|
28
|
+
import ko_KR from './ko-KR.json';
|
|
29
|
+
import lt_LT from './lt-LT.json';
|
|
30
|
+
import lv_LV from './lv-LV.json';
|
|
31
|
+
import nb_NO from './nb-NO.json';
|
|
32
|
+
import nl_NL from './nl-NL.json';
|
|
33
|
+
import pl_PL from './pl-PL.json';
|
|
34
|
+
import pt_BR from './pt-BR.json';
|
|
35
|
+
import pt_PT from './pt-PT.json';
|
|
36
|
+
import ro_RO from './ro-RO.json';
|
|
37
|
+
import ru_RU from './ru-RU.json';
|
|
38
|
+
import sk_SK from './sk-SK.json';
|
|
39
|
+
import sl_SI from './sl-SI.json';
|
|
40
|
+
import sr_SP from './sr-SP.json';
|
|
41
|
+
import sv_SE from './sv-SE.json';
|
|
42
|
+
import tr_TR from './tr-TR.json';
|
|
43
|
+
import uk_UA from './uk-UA.json';
|
|
44
|
+
import zh_CN from './zh-CN.json';
|
|
45
|
+
import zh_TW from './zh-TW.json';
|
|
46
|
+
|
|
47
|
+
export default compileDictionaries({
|
|
48
|
+
'ar-AE': ar_AE,
|
|
49
|
+
'bg-BG': bg_BG,
|
|
50
|
+
'cs-CZ': cs_CZ,
|
|
51
|
+
'da-DK': da_DK,
|
|
52
|
+
'de-DE': de_DE,
|
|
53
|
+
'el-GR': el_GR,
|
|
54
|
+
'en-US': en_US,
|
|
55
|
+
'es-ES': es_ES,
|
|
56
|
+
'et-EE': et_EE,
|
|
57
|
+
'fi-FI': fi_FI,
|
|
58
|
+
'fr-FR': fr_FR,
|
|
59
|
+
'he-IL': he_IL,
|
|
60
|
+
'hr-HR': hr_HR,
|
|
61
|
+
'hu-HU': hu_HU,
|
|
62
|
+
'it-IT': it_IT,
|
|
63
|
+
'ja-JP': ja_JP,
|
|
64
|
+
'ko-KR': ko_KR,
|
|
65
|
+
'lt-LT': lt_LT,
|
|
66
|
+
'lv-LV': lv_LV,
|
|
67
|
+
'nb-NO': nb_NO,
|
|
68
|
+
'nl-NL': nl_NL,
|
|
69
|
+
'pl-PL': pl_PL,
|
|
70
|
+
'pt-BR': pt_BR,
|
|
71
|
+
'pt-PT': pt_PT,
|
|
72
|
+
'ro-RO': ro_RO,
|
|
73
|
+
'ru-RU': ru_RU,
|
|
74
|
+
'sk-SK': sk_SK,
|
|
75
|
+
'sl-SI': sl_SI,
|
|
76
|
+
'sr-SP': sr_SP,
|
|
77
|
+
'sv-SE': sv_SE,
|
|
78
|
+
'tr-TR': tr_TR,
|
|
79
|
+
'uk-UA': uk_UA,
|
|
80
|
+
'zh-CN': zh_CN,
|
|
81
|
+
'zh-TW': zh_TW,
|
|
82
|
+
});
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// Ported from react-stately (source: .react-spectrum/packages/react-stately/src/autocomplete/useAutocompleteState.ts).
|
|
2
|
+
// octane adaptations: public-hook slot threading (splitSlot/subSlot) per the binding
|
|
3
|
+
// convention; `ReactNode` → a structural `any` alias (octane renderables); the public
|
|
4
|
+
// value-level `onInputChange(value)` callback is unchanged (the onInput rule applies
|
|
5
|
+
// only to DOM wiring, which lives in useTextField/useAutocomplete).
|
|
6
|
+
import { useState } from 'octane';
|
|
7
|
+
|
|
8
|
+
import { S, splitSlot, subSlot } from '../../internal';
|
|
9
|
+
import { useControlledState } from '../utils/useControlledState';
|
|
10
|
+
|
|
11
|
+
// octane adaptation: structural alias for React's ReactNode.
|
|
12
|
+
type ReactNode = any;
|
|
13
|
+
|
|
14
|
+
export interface AutocompleteState {
|
|
15
|
+
/** The current value of the autocomplete input. */
|
|
16
|
+
inputValue: string;
|
|
17
|
+
/** Sets the value of the autocomplete input. */
|
|
18
|
+
setInputValue(value: string): void;
|
|
19
|
+
/** The id of the current aria-activedescendant of the autocomplete input. */
|
|
20
|
+
focusedNodeId: string | null;
|
|
21
|
+
/** Sets the id of the current aria-activedescendant of the autocomplete input. */
|
|
22
|
+
setFocusedNodeId(value: string | null): void;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface AutocompleteProps {
|
|
26
|
+
/** The value of the autocomplete input (controlled). */
|
|
27
|
+
inputValue?: string;
|
|
28
|
+
/** The default value of the autocomplete input (uncontrolled). */
|
|
29
|
+
defaultInputValue?: string;
|
|
30
|
+
/** Handler that is called when the autocomplete input value changes. */
|
|
31
|
+
onInputChange?: (value: string) => void;
|
|
32
|
+
/**
|
|
33
|
+
* The children wrapped by the autocomplete. Consists of at least an input element and a
|
|
34
|
+
* collection element to filter.
|
|
35
|
+
*/
|
|
36
|
+
children: ReactNode;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Emulate our other stately hooks which accept all "base" props even if not used
|
|
40
|
+
export interface AutocompleteStateOptions extends Omit<AutocompleteProps, 'children'> {}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Provides state management for an autocomplete component.
|
|
44
|
+
*/
|
|
45
|
+
export function useAutocompleteState(props: AutocompleteStateOptions): AutocompleteState;
|
|
46
|
+
// Slot-threading form: sibling ported hooks pass their derived sub-slot as the trailing arg.
|
|
47
|
+
export function useAutocompleteState(
|
|
48
|
+
props: AutocompleteStateOptions,
|
|
49
|
+
slot: symbol | undefined,
|
|
50
|
+
): AutocompleteState;
|
|
51
|
+
export function useAutocompleteState(...args: any[]): AutocompleteState {
|
|
52
|
+
const [user, slotArg] = splitSlot(args);
|
|
53
|
+
const slot = slotArg ?? S('useAutocompleteState');
|
|
54
|
+
const props = user[0] as AutocompleteStateOptions;
|
|
55
|
+
|
|
56
|
+
let {
|
|
57
|
+
onInputChange: propsOnInputChange,
|
|
58
|
+
inputValue: propsInputValue,
|
|
59
|
+
defaultInputValue: propsDefaultInputValue = '',
|
|
60
|
+
} = props;
|
|
61
|
+
|
|
62
|
+
let onInputChange = (value: string) => {
|
|
63
|
+
if (propsOnInputChange) {
|
|
64
|
+
propsOnInputChange(value);
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
let [focusedNodeId, setFocusedNodeId] = useState<string | null>(
|
|
69
|
+
null,
|
|
70
|
+
subSlot(slot, 'focusedNodeId'),
|
|
71
|
+
);
|
|
72
|
+
let [inputValue, setInputValue] = useControlledState(
|
|
73
|
+
propsInputValue,
|
|
74
|
+
propsDefaultInputValue!,
|
|
75
|
+
onInputChange,
|
|
76
|
+
subSlot(slot, 'inputValue'),
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
inputValue,
|
|
81
|
+
setInputValue,
|
|
82
|
+
focusedNodeId,
|
|
83
|
+
setFocusedNodeId,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
// Ported from react-stately (source: .react-spectrum/packages/react-stately/src/grid/GridCollection.ts).
|
|
2
|
+
// Verbatim (no React surface).
|
|
3
|
+
import type { Collection, Key, Node } from '@react-types/shared';
|
|
4
|
+
|
|
5
|
+
export interface IGridCollection<T> extends Collection<GridNode<T>> {
|
|
6
|
+
/** The number of columns in the grid. */
|
|
7
|
+
columnCount: number;
|
|
8
|
+
/** A list of rows in the grid. */
|
|
9
|
+
rows: GridNode<T>[];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface GridRow<T> extends Partial<GridNode<T>> {
|
|
13
|
+
key?: Key;
|
|
14
|
+
type: string;
|
|
15
|
+
childNodes: Iterable<Node<T>>;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface GridNode<T> extends Node<T> {
|
|
19
|
+
column?: GridNode<T>;
|
|
20
|
+
/**
|
|
21
|
+
* The number of columns spanned by this cell. Use `colSpan` instead.
|
|
22
|
+
*
|
|
23
|
+
* @deprecated
|
|
24
|
+
*/
|
|
25
|
+
colspan?: number;
|
|
26
|
+
/** The number of columns spanned by this cell. */
|
|
27
|
+
colSpan?: number | null;
|
|
28
|
+
/** The column index of this cell, accounting for any colSpans. */
|
|
29
|
+
colIndex?: number | null;
|
|
30
|
+
/** The index of this node within its parent, ignoring sibling nodes that aren't of the same type. */
|
|
31
|
+
indexOfType?: number;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
interface GridCollectionOptions<T> {
|
|
35
|
+
columnCount: number;
|
|
36
|
+
items: GridRow<T>[];
|
|
37
|
+
visitNode?: (cell: GridNode<T>) => GridNode<T>;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export class GridCollection<T> implements IGridCollection<T> {
|
|
41
|
+
keyMap: Map<Key, GridNode<T>> = new Map();
|
|
42
|
+
columnCount: number;
|
|
43
|
+
rows: GridNode<T>[];
|
|
44
|
+
|
|
45
|
+
constructor(opts: GridCollectionOptions<T>) {
|
|
46
|
+
this.keyMap = new Map();
|
|
47
|
+
this.columnCount = opts?.columnCount;
|
|
48
|
+
this.rows = [];
|
|
49
|
+
|
|
50
|
+
let visit = (node: GridNode<T>) => {
|
|
51
|
+
// If the node is the same object as the previous node for the same key,
|
|
52
|
+
// we can skip this node and its children. We always visit columns though,
|
|
53
|
+
// because we depend on order to build the columns array.
|
|
54
|
+
let prevNode = this.keyMap.get(node.key);
|
|
55
|
+
if (opts.visitNode) {
|
|
56
|
+
node = opts.visitNode(node);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
this.keyMap.set(node.key, node);
|
|
60
|
+
|
|
61
|
+
let childKeys = new Set();
|
|
62
|
+
let last: GridNode<T> | null = null;
|
|
63
|
+
let rowHasCellWithColSpan = false;
|
|
64
|
+
|
|
65
|
+
if (node.type === 'item') {
|
|
66
|
+
for (let child of node.childNodes) {
|
|
67
|
+
if (child.props?.colSpan !== undefined) {
|
|
68
|
+
rowHasCellWithColSpan = true;
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
for (let child of node.childNodes as Iterable<GridNode<T>>) {
|
|
75
|
+
if (child.type === 'cell' && rowHasCellWithColSpan) {
|
|
76
|
+
child.colspan = child.props?.colSpan;
|
|
77
|
+
child.colSpan = child.props?.colSpan;
|
|
78
|
+
child.colIndex = !last
|
|
79
|
+
? child.index
|
|
80
|
+
: (last.colIndex ?? last.index) + (last.colSpan ?? 1);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (child.type === 'cell' && child.parentKey == null) {
|
|
84
|
+
// if child is a cell parent key isn't already established by the collection, match child node to parent row
|
|
85
|
+
child.parentKey = node.key;
|
|
86
|
+
}
|
|
87
|
+
childKeys.add(child.key);
|
|
88
|
+
|
|
89
|
+
if (last) {
|
|
90
|
+
last.nextKey = child.key;
|
|
91
|
+
child.prevKey = last.key;
|
|
92
|
+
} else {
|
|
93
|
+
child.prevKey = null;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
visit(child);
|
|
97
|
+
last = child;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (last) {
|
|
101
|
+
last.nextKey = null;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Remove deleted nodes and their children from the key map
|
|
105
|
+
if (prevNode) {
|
|
106
|
+
for (let child of prevNode.childNodes) {
|
|
107
|
+
if (!childKeys.has(child.key)) {
|
|
108
|
+
remove(child);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
let remove = (node: GridNode<T>) => {
|
|
115
|
+
this.keyMap.delete(node.key);
|
|
116
|
+
for (let child of node.childNodes) {
|
|
117
|
+
if (this.keyMap.get(child.key) === child) {
|
|
118
|
+
remove(child);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
let last: GridNode<T> | null = null;
|
|
124
|
+
for (let [i, node] of opts.items.entries()) {
|
|
125
|
+
let rowNode: GridNode<T> = {
|
|
126
|
+
...node,
|
|
127
|
+
level: node.level ?? 0,
|
|
128
|
+
key: node.key ?? 'row-' + i,
|
|
129
|
+
type: node.type ?? 'row',
|
|
130
|
+
value: node.value ?? null,
|
|
131
|
+
hasChildNodes: true,
|
|
132
|
+
childNodes: [...node.childNodes],
|
|
133
|
+
rendered: node.rendered,
|
|
134
|
+
textValue: node.textValue ?? '',
|
|
135
|
+
index: node.index ?? i,
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
if (last) {
|
|
139
|
+
last.nextKey = rowNode.key;
|
|
140
|
+
rowNode.prevKey = last.key;
|
|
141
|
+
} else {
|
|
142
|
+
rowNode.prevKey = null;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
this.rows.push(rowNode);
|
|
146
|
+
visit(rowNode);
|
|
147
|
+
|
|
148
|
+
last = rowNode;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (last) {
|
|
152
|
+
last.nextKey = null;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
*[Symbol.iterator](): IterableIterator<GridNode<T>> {
|
|
157
|
+
yield* [...this.rows];
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
get size(): number {
|
|
161
|
+
return [...this.rows].length;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
getKeys(): IterableIterator<Key> {
|
|
165
|
+
return this.keyMap.keys();
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
getKeyBefore(key: Key): Key | null {
|
|
169
|
+
let node = this.keyMap.get(key);
|
|
170
|
+
return node ? (node.prevKey ?? null) : null;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
getKeyAfter(key: Key): Key | null {
|
|
174
|
+
let node = this.keyMap.get(key);
|
|
175
|
+
return node ? (node.nextKey ?? null) : null;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
getFirstKey(): Key | null {
|
|
179
|
+
return [...this.rows][0]?.key;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
getLastKey(): Key | null {
|
|
183
|
+
let rows = [...this.rows];
|
|
184
|
+
return rows[rows.length - 1]?.key;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
getItem(key: Key): GridNode<T> | null {
|
|
188
|
+
return this.keyMap.get(key) ?? null;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
at(idx: number): GridNode<T> | null {
|
|
192
|
+
const keys = [...this.getKeys()];
|
|
193
|
+
return this.getItem(keys[idx]);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
getChildren(key: Key): Iterable<GridNode<T>> {
|
|
197
|
+
let node = this.keyMap.get(key);
|
|
198
|
+
return node?.childNodes || [];
|
|
199
|
+
}
|
|
200
|
+
}
|
|
@@ -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
|
+
}
|