@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.
Files changed (66) hide show
  1. package/dist/commonjs/ai-ingress-button/lib/AIIngressButton.js +5 -5
  2. package/dist/commonjs/index.d.ts +1 -0
  3. package/dist/commonjs/index.d.ts.map +1 -1
  4. package/dist/commonjs/index.js +1 -0
  5. package/dist/commonjs/kbd/lib/KBD.js +6 -6
  6. package/dist/commonjs/kbd/lib/KBDItem.js +6 -6
  7. package/dist/commonjs/selection-group/index.d.ts +7 -0
  8. package/dist/commonjs/selection-group/index.d.ts.map +1 -0
  9. package/dist/commonjs/selection-group/index.js +22 -0
  10. package/dist/commonjs/selection-group/lib/SelectionGroup.d.ts +1499 -0
  11. package/dist/commonjs/selection-group/lib/SelectionGroup.d.ts.map +1 -0
  12. package/dist/commonjs/selection-group/lib/SelectionGroup.js +61 -0
  13. package/dist/commonjs/selection-group/lib/SelectionGroupIcon.d.ts +17 -0
  14. package/dist/commonjs/selection-group/lib/SelectionGroupIcon.d.ts.map +1 -0
  15. package/dist/commonjs/selection-group/lib/SelectionGroupIcon.js +24 -0
  16. package/dist/commonjs/selection-group/lib/SelectionGroupItem.d.ts +173 -0
  17. package/dist/commonjs/selection-group/lib/SelectionGroupItem.d.ts.map +1 -0
  18. package/dist/commonjs/selection-group/lib/SelectionGroupItem.js +37 -0
  19. package/dist/commonjs/selection-group/lib/SelectionGroupList.d.ts +172 -0
  20. package/dist/commonjs/selection-group/lib/SelectionGroupList.d.ts.map +1 -0
  21. package/dist/commonjs/selection-group/lib/SelectionGroupList.js +35 -0
  22. package/dist/commonjs/selection-group/lib/hooks/useSelectionGroupItem.d.ts +146 -0
  23. package/dist/commonjs/selection-group/lib/hooks/useSelectionGroupItem.d.ts.map +1 -0
  24. package/dist/commonjs/selection-group/lib/hooks/useSelectionGroupItem.js +29 -0
  25. package/dist/commonjs/selection-group/lib/hooks/useSelectionGroupModel.d.ts +1602 -0
  26. package/dist/commonjs/selection-group/lib/hooks/useSelectionGroupModel.d.ts.map +1 -0
  27. package/dist/commonjs/selection-group/lib/hooks/useSelectionGroupModel.js +166 -0
  28. package/dist/commonjs/version/lib/version.js +1 -1
  29. package/dist/es6/ai-ingress-button/lib/AIIngressButton.js +5 -5
  30. package/dist/es6/index.d.ts +1 -0
  31. package/dist/es6/index.d.ts.map +1 -1
  32. package/dist/es6/index.js +1 -0
  33. package/dist/es6/kbd/lib/KBD.js +6 -6
  34. package/dist/es6/kbd/lib/KBDItem.js +6 -6
  35. package/dist/es6/selection-group/index.d.ts +7 -0
  36. package/dist/es6/selection-group/index.d.ts.map +1 -0
  37. package/dist/es6/selection-group/index.js +6 -0
  38. package/dist/es6/selection-group/lib/SelectionGroup.d.ts +1499 -0
  39. package/dist/es6/selection-group/lib/SelectionGroup.d.ts.map +1 -0
  40. package/dist/es6/selection-group/lib/SelectionGroup.js +58 -0
  41. package/dist/es6/selection-group/lib/SelectionGroupIcon.d.ts +17 -0
  42. package/dist/es6/selection-group/lib/SelectionGroupIcon.d.ts.map +1 -0
  43. package/dist/es6/selection-group/lib/SelectionGroupIcon.js +21 -0
  44. package/dist/es6/selection-group/lib/SelectionGroupItem.d.ts +173 -0
  45. package/dist/es6/selection-group/lib/SelectionGroupItem.d.ts.map +1 -0
  46. package/dist/es6/selection-group/lib/SelectionGroupItem.js +34 -0
  47. package/dist/es6/selection-group/lib/SelectionGroupList.d.ts +172 -0
  48. package/dist/es6/selection-group/lib/SelectionGroupList.d.ts.map +1 -0
  49. package/dist/es6/selection-group/lib/SelectionGroupList.js +32 -0
  50. package/dist/es6/selection-group/lib/hooks/useSelectionGroupItem.d.ts +146 -0
  51. package/dist/es6/selection-group/lib/hooks/useSelectionGroupItem.d.ts.map +1 -0
  52. package/dist/es6/selection-group/lib/hooks/useSelectionGroupItem.js +26 -0
  53. package/dist/es6/selection-group/lib/hooks/useSelectionGroupModel.d.ts +1602 -0
  54. package/dist/es6/selection-group/lib/hooks/useSelectionGroupModel.d.ts.map +1 -0
  55. package/dist/es6/selection-group/lib/hooks/useSelectionGroupModel.js +160 -0
  56. package/dist/es6/version/lib/version.js +1 -1
  57. package/index.ts +1 -0
  58. package/package.json +4 -4
  59. package/selection-group/index.ts +6 -0
  60. package/selection-group/lib/SelectionGroup.tsx +65 -0
  61. package/selection-group/lib/SelectionGroupIcon.tsx +60 -0
  62. package/selection-group/lib/SelectionGroupItem.tsx +143 -0
  63. package/selection-group/lib/SelectionGroupList.tsx +89 -0
  64. package/selection-group/lib/hooks/useSelectionGroupItem.tsx +46 -0
  65. package/selection-group/lib/hooks/useSelectionGroupModel.tsx +195 -0
  66. 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
+ });
@@ -0,0 +1,6 @@
1
+ {
2
+ "main": "../dist/commonjs/selection-group",
3
+ "module": "../dist/es6/selection-group",
4
+ "sideEffects": false,
5
+ "types": "../dist/es6/selection-group"
6
+ }