@workday/canvas-kit-labs-react 16.1.3 → 16.2.0-0604-next.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commonjs/ai-ingress-button/lib/AIIngressButton.js +5 -5
- package/dist/commonjs/index.d.ts +1 -0
- package/dist/commonjs/index.d.ts.map +1 -1
- package/dist/commonjs/index.js +1 -0
- package/dist/commonjs/kbd/lib/KBD.js +6 -6
- package/dist/commonjs/kbd/lib/KBDItem.js +6 -6
- package/dist/commonjs/selection-group/index.d.ts +7 -0
- package/dist/commonjs/selection-group/index.d.ts.map +1 -0
- package/dist/commonjs/selection-group/index.js +22 -0
- package/dist/commonjs/selection-group/lib/SelectionGroup.d.ts +1499 -0
- package/dist/commonjs/selection-group/lib/SelectionGroup.d.ts.map +1 -0
- package/dist/commonjs/selection-group/lib/SelectionGroup.js +61 -0
- package/dist/commonjs/selection-group/lib/SelectionGroupIcon.d.ts +17 -0
- package/dist/commonjs/selection-group/lib/SelectionGroupIcon.d.ts.map +1 -0
- package/dist/commonjs/selection-group/lib/SelectionGroupIcon.js +24 -0
- package/dist/commonjs/selection-group/lib/SelectionGroupItem.d.ts +173 -0
- package/dist/commonjs/selection-group/lib/SelectionGroupItem.d.ts.map +1 -0
- package/dist/commonjs/selection-group/lib/SelectionGroupItem.js +37 -0
- package/dist/commonjs/selection-group/lib/SelectionGroupList.d.ts +172 -0
- package/dist/commonjs/selection-group/lib/SelectionGroupList.d.ts.map +1 -0
- package/dist/commonjs/selection-group/lib/SelectionGroupList.js +35 -0
- package/dist/commonjs/selection-group/lib/hooks/useSelectionGroupItem.d.ts +146 -0
- package/dist/commonjs/selection-group/lib/hooks/useSelectionGroupItem.d.ts.map +1 -0
- package/dist/commonjs/selection-group/lib/hooks/useSelectionGroupItem.js +29 -0
- package/dist/commonjs/selection-group/lib/hooks/useSelectionGroupModel.d.ts +1602 -0
- package/dist/commonjs/selection-group/lib/hooks/useSelectionGroupModel.d.ts.map +1 -0
- package/dist/commonjs/selection-group/lib/hooks/useSelectionGroupModel.js +166 -0
- package/dist/commonjs/version/lib/version.js +1 -1
- package/dist/es6/ai-ingress-button/lib/AIIngressButton.js +5 -5
- package/dist/es6/index.d.ts +1 -0
- package/dist/es6/index.d.ts.map +1 -1
- package/dist/es6/index.js +1 -0
- package/dist/es6/kbd/lib/KBD.js +6 -6
- package/dist/es6/kbd/lib/KBDItem.js +6 -6
- package/dist/es6/selection-group/index.d.ts +7 -0
- package/dist/es6/selection-group/index.d.ts.map +1 -0
- package/dist/es6/selection-group/index.js +6 -0
- package/dist/es6/selection-group/lib/SelectionGroup.d.ts +1499 -0
- package/dist/es6/selection-group/lib/SelectionGroup.d.ts.map +1 -0
- package/dist/es6/selection-group/lib/SelectionGroup.js +58 -0
- package/dist/es6/selection-group/lib/SelectionGroupIcon.d.ts +17 -0
- package/dist/es6/selection-group/lib/SelectionGroupIcon.d.ts.map +1 -0
- package/dist/es6/selection-group/lib/SelectionGroupIcon.js +21 -0
- package/dist/es6/selection-group/lib/SelectionGroupItem.d.ts +173 -0
- package/dist/es6/selection-group/lib/SelectionGroupItem.d.ts.map +1 -0
- package/dist/es6/selection-group/lib/SelectionGroupItem.js +34 -0
- package/dist/es6/selection-group/lib/SelectionGroupList.d.ts +172 -0
- package/dist/es6/selection-group/lib/SelectionGroupList.d.ts.map +1 -0
- package/dist/es6/selection-group/lib/SelectionGroupList.js +32 -0
- package/dist/es6/selection-group/lib/hooks/useSelectionGroupItem.d.ts +146 -0
- package/dist/es6/selection-group/lib/hooks/useSelectionGroupItem.d.ts.map +1 -0
- package/dist/es6/selection-group/lib/hooks/useSelectionGroupItem.js +26 -0
- package/dist/es6/selection-group/lib/hooks/useSelectionGroupModel.d.ts +1602 -0
- package/dist/es6/selection-group/lib/hooks/useSelectionGroupModel.d.ts.map +1 -0
- package/dist/es6/selection-group/lib/hooks/useSelectionGroupModel.js +160 -0
- package/dist/es6/version/lib/version.js +1 -1
- package/index.ts +1 -0
- package/package.json +4 -4
- package/selection-group/index.ts +6 -0
- package/selection-group/lib/SelectionGroup.tsx +65 -0
- package/selection-group/lib/SelectionGroupIcon.tsx +60 -0
- package/selection-group/lib/SelectionGroupItem.tsx +143 -0
- package/selection-group/lib/SelectionGroupList.tsx +89 -0
- package/selection-group/lib/hooks/useSelectionGroupItem.tsx +46 -0
- package/selection-group/lib/hooks/useSelectionGroupModel.tsx +195 -0
- package/selection-group/package.json +6 -0
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
SelectionManager,
|
|
5
|
+
multiSelectionManager,
|
|
6
|
+
singleSelectionManager,
|
|
7
|
+
useListModel,
|
|
8
|
+
} from '@workday/canvas-kit-react/collection';
|
|
9
|
+
import {createModelHook} from '@workday/canvas-kit-react/common';
|
|
10
|
+
|
|
11
|
+
export type SelectionGroupMode = 'single' | 'multiple';
|
|
12
|
+
export type SelectionGroupWidth = 'content' | 'equal' | 'column';
|
|
13
|
+
export type SelectionGroupError = 'error' | 'caution';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* `createModelHook` runs a model's guards and callbacks unless the event it is wrapping is already
|
|
17
|
+
* a wrapped event. The overrides below delegate to events from the list model, which runs those
|
|
18
|
+
* same guards and callbacks because both models share a config, so they are marked as wrapped too.
|
|
19
|
+
* Without this, the `onGoTo*` callbacks would fire twice for a single key press.
|
|
20
|
+
*/
|
|
21
|
+
const asWrappedEvent = <T extends (...args: any[]) => void>(fn: T): T => {
|
|
22
|
+
(fn as any)._wrapped = true;
|
|
23
|
+
return fn;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export const useSelectionGroupModel = createModelHook({
|
|
27
|
+
defaultConfig: {
|
|
28
|
+
...useListModel.defaultConfig,
|
|
29
|
+
/**
|
|
30
|
+
* Optional id for the whole `SelectionGroup`. If not provided, a unique id will be created.
|
|
31
|
+
* @default useUniqueId()
|
|
32
|
+
*/
|
|
33
|
+
id: '',
|
|
34
|
+
/**
|
|
35
|
+
* The selection mode of the group. `single` allows one selection at a time and behaves like a
|
|
36
|
+
* radio group. `multiple` allows more than one selection and behaves like a checkbox group.
|
|
37
|
+
*
|
|
38
|
+
* This drives more than selection - it also determines the `radio`/`checkbox` role of an item,
|
|
39
|
+
* the `radiogroup`/`group` role of the list, whether the group is one tab stop or many, and the
|
|
40
|
+
* shape of the item's icon. The matching `selection` manager is derived from it, so most groups
|
|
41
|
+
* should set `mode` rather than passing a `selection` manager directly.
|
|
42
|
+
* @default 'single'
|
|
43
|
+
*/
|
|
44
|
+
mode: 'single' as SelectionGroupMode,
|
|
45
|
+
/**
|
|
46
|
+
* The selection manager the underlying list model uses. Leave this unset unless you need
|
|
47
|
+
* selection to behave differently from what `mode` provides - `mode` picks
|
|
48
|
+
* `singleSelectionManager` or `multiSelectionManager` for you. Setting this does not change
|
|
49
|
+
* the roles or keyboard behavior that `mode` controls.
|
|
50
|
+
* @default `singleSelectionManager`, or `multiSelectionManager` when `mode` is `'multiple'`
|
|
51
|
+
*/
|
|
52
|
+
selection: undefined as SelectionManager | undefined,
|
|
53
|
+
/**
|
|
54
|
+
* Controls how item widths are calculated within the group. All items in a group use the same
|
|
55
|
+
* sizing method.
|
|
56
|
+
* - `content`: each item is sized to its own label.
|
|
57
|
+
* - `equal`: each item is sized to match the widest item.
|
|
58
|
+
* - `column`: each item spans the full width of the group.
|
|
59
|
+
* @default 'content'
|
|
60
|
+
*/
|
|
61
|
+
width: 'content' as SelectionGroupWidth,
|
|
62
|
+
/**
|
|
63
|
+
* The initially selected ids of the group. Use this for uncontrolled groups. For `single` mode,
|
|
64
|
+
* provide a single id. If not provided, nothing is selected until the user interacts.
|
|
65
|
+
*/
|
|
66
|
+
initialSelectedIds: [] as string[],
|
|
67
|
+
/**
|
|
68
|
+
* The selected ids of the group. Providing this prop puts the model in a controlled state: the
|
|
69
|
+
* group renders exactly what is passed in and will not change on its own. Use `onSelect` to
|
|
70
|
+
* respond to selection.
|
|
71
|
+
*/
|
|
72
|
+
selectedIds: undefined as string[] | undefined,
|
|
73
|
+
/**
|
|
74
|
+
* Sets disabled state for every item in the group. To disable individual items instead, pass
|
|
75
|
+
* their ids to `nonInteractiveIds` so keyboard navigation skips over them.
|
|
76
|
+
* @default false
|
|
77
|
+
*/
|
|
78
|
+
disabled: false,
|
|
79
|
+
/**
|
|
80
|
+
* The type of error associated with the group (if applicable). Notification states apply to the
|
|
81
|
+
* whole group rather than to individual items.
|
|
82
|
+
*
|
|
83
|
+
* This prop only changes the group's border color, which is not sufficient on its own. Always
|
|
84
|
+
* pair it with visible text describing the state and reference that text with
|
|
85
|
+
* `aria-describedby`. Wrapping the group in a `FormField` with a `FormField.Hint` does this
|
|
86
|
+
* for you.
|
|
87
|
+
*/
|
|
88
|
+
error: undefined as SelectionGroupError | undefined,
|
|
89
|
+
/**
|
|
90
|
+
* If true, the group is marked invalid to assistive technology. Only applies when `mode` is
|
|
91
|
+
* `'single'`, because `aria-invalid` is not allowed on the `group` role that a multi-select
|
|
92
|
+
* group renders.
|
|
93
|
+
* @default true when `error` is `'error'`, otherwise undefined
|
|
94
|
+
*/
|
|
95
|
+
'aria-invalid': undefined as boolean | undefined,
|
|
96
|
+
/**
|
|
97
|
+
* The id of the element describing the group, such as the hint text of a `FormField`. This is
|
|
98
|
+
* set automatically when the group is used as a `FormField.Input`.
|
|
99
|
+
*/
|
|
100
|
+
'aria-describedby': undefined as string | undefined,
|
|
101
|
+
/**
|
|
102
|
+
* The orientation of keyboard navigation within the group.
|
|
103
|
+
* @default 'horizontal'
|
|
104
|
+
*/
|
|
105
|
+
orientation: 'horizontal' as typeof useListModel.defaultConfig.orientation,
|
|
106
|
+
},
|
|
107
|
+
requiredConfig: useListModel.requiredConfig,
|
|
108
|
+
})(config => {
|
|
109
|
+
const isControlled = config.selectedIds !== undefined;
|
|
110
|
+
const selection =
|
|
111
|
+
config.selection ||
|
|
112
|
+
(config.mode === 'multiple' ? multiSelectionManager : singleSelectionManager);
|
|
113
|
+
|
|
114
|
+
const listModel = useListModel(
|
|
115
|
+
useListModel.mergeConfig(config, {
|
|
116
|
+
orientation: config.orientation || 'horizontal',
|
|
117
|
+
selection,
|
|
118
|
+
initialSelectedIds: config.selectedIds || config.initialSelectedIds,
|
|
119
|
+
shouldVirtualize: false,
|
|
120
|
+
// `mergeConfig` combines this with a `shouldSelect` from the consumer rather than replacing
|
|
121
|
+
// it. Guarding here means a blocked selection never reaches `onSelect`.
|
|
122
|
+
shouldSelect: () => !config.disabled,
|
|
123
|
+
})
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
// A single-select group is a radio group, so selection follows focus while arrowing through
|
|
127
|
+
// items. The cursor events below set this, and the effect selects whatever the cursor landed on.
|
|
128
|
+
const shouldSelectCursorRef = React.useRef(false);
|
|
129
|
+
// A navigation event doesn't always move the cursor - a single-item group, or a non-wrapping
|
|
130
|
+
// navigation manager at either end of the list, leaves it where it is. Counting the events gives
|
|
131
|
+
// the effect below something that always changes, so an arrow key still selects in those cases.
|
|
132
|
+
const [navigationCount, setNavigationCount] = React.useState(0);
|
|
133
|
+
|
|
134
|
+
const state = {
|
|
135
|
+
...listModel.state,
|
|
136
|
+
mode: config.mode,
|
|
137
|
+
width: config.width,
|
|
138
|
+
disabled: config.disabled,
|
|
139
|
+
error: config.error,
|
|
140
|
+
'aria-invalid': config['aria-invalid'] ?? (config.error === 'error' ? true : undefined),
|
|
141
|
+
'aria-describedby': config['aria-describedby'],
|
|
142
|
+
selectedIds: isControlled ? config.selectedIds! : listModel.state.selectedIds,
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
// The cursor model already maps each of its events to a navigation method, so the events are
|
|
146
|
+
// read off the model rather than restated here. Every cursor event except the direct `goTo`
|
|
147
|
+
// moves the cursor through the navigation manager. `goTo` is excluded because it also places the
|
|
148
|
+
// initial focus stop and responds to clicks, neither of which should select.
|
|
149
|
+
type NavigationEvent = Exclude<Extract<keyof typeof listModel.events, `goTo${string}`>, 'goTo'>;
|
|
150
|
+
|
|
151
|
+
const navigationEvents = Object.keys(listModel.events).filter(
|
|
152
|
+
name => name.startsWith('goTo') && name !== 'goTo'
|
|
153
|
+
) as NavigationEvent[];
|
|
154
|
+
|
|
155
|
+
const selectFollowsFocusEvents = navigationEvents.reduce(
|
|
156
|
+
(result, eventName) => {
|
|
157
|
+
result[eventName] = asWrappedEvent(() => {
|
|
158
|
+
// Let the cursor model resolve the target and move the cursor. Navigation skips
|
|
159
|
+
// `nonInteractiveIds`, so this can't land selection on a non-interactive item.
|
|
160
|
+
listModel.events[eventName]();
|
|
161
|
+
shouldSelectCursorRef.current = true;
|
|
162
|
+
setNavigationCount(count => count + 1);
|
|
163
|
+
});
|
|
164
|
+
return result;
|
|
165
|
+
},
|
|
166
|
+
{} as Record<NavigationEvent, () => void>
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
const events = {
|
|
170
|
+
...listModel.events,
|
|
171
|
+
...(config.mode === 'single' ? selectFollowsFocusEvents : {}),
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
const {cursorId} = listModel.state;
|
|
175
|
+
|
|
176
|
+
React.useEffect(() => {
|
|
177
|
+
if (!shouldSelectCursorRef.current) {
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
shouldSelectCursorRef.current = false;
|
|
181
|
+
|
|
182
|
+
if (typeof cursorId === 'string' && cursorId && !state.nonInteractiveIds.includes(cursorId)) {
|
|
183
|
+
listModel.events.select({id: cursorId});
|
|
184
|
+
}
|
|
185
|
+
// Selection follows the cursor, so this should only run after a navigation event, whether or
|
|
186
|
+
// not that event moved the cursor.
|
|
187
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
188
|
+
}, [cursorId, navigationCount]);
|
|
189
|
+
|
|
190
|
+
return {
|
|
191
|
+
...listModel,
|
|
192
|
+
state,
|
|
193
|
+
events,
|
|
194
|
+
};
|
|
195
|
+
});
|