@astryxdesign/core 0.1.2-canary.bfcbf64 → 0.1.2-canary.c395fca
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/Breadcrumbs/BreadcrumbItem.d.ts +1 -1
- package/dist/Breadcrumbs/BreadcrumbItem.d.ts.map +1 -1
- package/dist/Breadcrumbs/BreadcrumbItem.js +4 -1
- package/dist/Button/Button.d.ts.map +1 -1
- package/dist/Button/Button.js +2 -4
- package/dist/Calendar/Calendar.d.ts.map +1 -1
- package/dist/Calendar/Calendar.js +33 -14
- package/dist/Calendar/hooks/index.d.ts +0 -2
- package/dist/Calendar/hooks/index.d.ts.map +1 -1
- package/dist/Calendar/hooks/index.js +1 -2
- package/dist/Calendar/index.d.ts +2 -2
- package/dist/Calendar/index.d.ts.map +1 -1
- package/dist/Calendar/index.js +1 -1
- package/dist/CheckboxList/CheckboxList.js +3 -3
- package/dist/Citation/Citation.d.ts.map +1 -1
- package/dist/Citation/Citation.js +3 -3
- package/dist/ContextMenu/ContextMenu.d.ts +1 -7
- package/dist/ContextMenu/ContextMenu.d.ts.map +1 -1
- package/dist/ContextMenu/ContextMenu.js +7 -13
- package/dist/DropdownMenu/DropdownMenu.d.ts +1 -8
- package/dist/DropdownMenu/DropdownMenu.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenu.js +4 -9
- package/dist/Field/Field.d.ts +4 -4
- package/dist/Field/Field.d.ts.map +1 -1
- package/dist/Field/Field.js +2 -2
- package/dist/Field/FieldLabel.d.ts +4 -4
- package/dist/Field/FieldLabel.d.ts.map +1 -1
- package/dist/Field/FieldLabel.js +2 -2
- package/dist/InputGroup/InputGroup.js +3 -3
- package/dist/Link/Link.d.ts.map +1 -1
- package/dist/Link/Link.js +2 -4
- package/dist/MoreMenu/MoreMenu.d.ts +1 -7
- package/dist/MoreMenu/MoreMenu.d.ts.map +1 -1
- package/dist/MoreMenu/MoreMenu.js +0 -2
- package/dist/MultiSelector/MultiSelector.d.ts +25 -2
- package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
- package/dist/MultiSelector/MultiSelector.js +31 -6
- package/dist/ProgressBar/ProgressBar.d.ts.map +1 -1
- package/dist/ProgressBar/ProgressBar.js +2 -4
- package/dist/RadioList/RadioList.js +3 -3
- package/dist/SegmentedControl/SegmentedControl.d.ts +1 -1
- package/dist/SegmentedControl/SegmentedControl.d.ts.map +1 -1
- package/dist/SegmentedControl/SegmentedControl.js +51 -61
- package/dist/Selector/Selector.d.ts +22 -1
- package/dist/Selector/Selector.d.ts.map +1 -1
- package/dist/Selector/Selector.js +31 -6
- package/dist/Switch/Switch.d.ts.map +1 -1
- package/dist/Switch/Switch.js +2 -4
- package/dist/TabList/TabList.d.ts +3 -2
- package/dist/TabList/TabList.d.ts.map +1 -1
- package/dist/TabList/TabList.js +55 -33
- package/dist/Table/tableContextMenu.d.ts.map +1 -1
- package/dist/Table/tableContextMenu.js +0 -3
- package/dist/TextArea/TextArea.d.ts.map +1 -1
- package/dist/TextArea/TextArea.js +2 -4
- package/dist/Toolbar/Toolbar.d.ts +3 -3
- package/dist/Toolbar/Toolbar.d.ts.map +1 -1
- package/dist/Toolbar/Toolbar.js +46 -6
- package/dist/TreeList/TreeList.d.ts.map +1 -1
- package/dist/TreeList/TreeList.js +16 -22
- package/dist/TreeList/TreeListItem.d.ts +3 -2
- package/dist/TreeList/TreeListItem.d.ts.map +1 -1
- package/dist/astryx.css +3 -0
- package/dist/astryx.umd.js +47 -47
- package/dist/astryx.umd.js.map +4 -4
- package/dist/hooks/index.d.ts +4 -2
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +1 -0
- package/dist/hooks/useGridFocus.d.ts +56 -0
- package/dist/hooks/useGridFocus.d.ts.map +1 -1
- package/dist/hooks/useGridFocus.js +138 -24
- package/dist/hooks/useKeyboardHint.d.ts +82 -0
- package/dist/hooks/useKeyboardHint.d.ts.map +1 -0
- package/dist/hooks/useKeyboardHint.js +220 -0
- package/dist/hooks/useTreeFocus.d.ts +27 -3
- package/dist/hooks/useTreeFocus.d.ts.map +1 -1
- package/dist/hooks/useTreeFocus.js +69 -6
- package/package.json +1 -1
- package/src/Breadcrumbs/BreadcrumbItem.tsx +5 -2
- package/src/Button/Button.tsx +3 -16
- package/src/Calendar/Calendar.tsx +52 -22
- package/src/Calendar/hooks/index.ts +0 -6
- package/src/Calendar/index.ts +0 -3
- package/src/CheckboxList/CheckboxList.tsx +3 -3
- package/src/Citation/Citation.doc.mjs +10 -0
- package/src/Citation/Citation.test.tsx +117 -0
- package/src/Citation/Citation.tsx +9 -1
- package/src/ContextMenu/ContextMenu.doc.mjs +0 -6
- package/src/ContextMenu/ContextMenu.test.tsx +4 -6
- package/src/ContextMenu/ContextMenu.tsx +7 -19
- package/src/DropdownMenu/DropdownMenu.doc.mjs +1 -8
- package/src/DropdownMenu/DropdownMenu.test.tsx +0 -23
- package/src/DropdownMenu/DropdownMenu.tsx +4 -16
- package/src/Field/Field.test.tsx +2 -2
- package/src/Field/Field.tsx +5 -5
- package/src/Field/FieldLabel.tsx +5 -5
- package/src/InputGroup/InputGroup.tsx +3 -3
- package/src/Link/Link.tsx +2 -13
- package/src/MoreMenu/MoreMenu.doc.mjs +2 -17
- package/src/MoreMenu/MoreMenu.tsx +0 -9
- package/src/MultiSelector/MultiSelector.doc.mjs +25 -0
- package/src/MultiSelector/MultiSelector.test.tsx +150 -1
- package/src/MultiSelector/MultiSelector.tsx +55 -3
- package/src/ProgressBar/ProgressBar.tsx +2 -3
- package/src/RadioList/RadioList.tsx +3 -3
- package/src/SegmentedControl/SegmentedControl.tsx +51 -77
- package/src/Selector/Selector.doc.mjs +21 -0
- package/src/Selector/Selector.test.tsx +123 -1
- package/src/Selector/Selector.tsx +53 -3
- package/src/Switch/Switch.tsx +2 -16
- package/src/TabList/TabList.test.tsx +41 -0
- package/src/TabList/TabList.tsx +66 -39
- package/src/Table/tableContextMenu.tsx +1 -5
- package/src/TextArea/TextArea.tsx +3 -13
- package/src/Toolbar/Toolbar.test.tsx +64 -6
- package/src/Toolbar/Toolbar.tsx +55 -4
- package/src/TreeList/TreeList.tsx +17 -28
- package/src/TreeList/TreeListItem.tsx +3 -2
- package/src/VisuallyHidden/VisuallyHidden.doc.mjs +7 -7
- package/src/hooks/index.ts +12 -5
- package/src/hooks/useGridFocus.doc.mjs +40 -2
- package/src/hooks/useGridFocus.test.tsx +132 -0
- package/src/hooks/useGridFocus.ts +179 -23
- package/src/hooks/useKeyboardHint.doc.mjs +101 -0
- package/src/hooks/useKeyboardHint.test.tsx +70 -0
- package/src/hooks/useKeyboardHint.tsx +332 -0
- package/src/hooks/useTreeFocus.doc.mjs +15 -1
- package/src/hooks/useTreeFocus.ts +101 -6
- package/dist/Calendar/hooks/useCalendarRovingTabindex.d.ts +0 -57
- package/dist/Calendar/hooks/useCalendarRovingTabindex.d.ts.map +0 -1
- package/dist/Calendar/hooks/useCalendarRovingTabindex.js +0 -96
- package/src/Calendar/hooks/useCalendarRovingTabindex.ts +0 -118
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
'use client';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @file useKeyboardHint.tsx
|
|
7
|
+
* @input Uses React, StyleX, Kbd, useLayer
|
|
8
|
+
* @output Exports useKeyboardHint hook — ephemeral arrow-key navigation hint
|
|
9
|
+
* @position Core hook; shows sighted keyboard users how to navigate composite
|
|
10
|
+
* widgets that use roving tabindex (single Tab stop, arrows inside)
|
|
11
|
+
*
|
|
12
|
+
* SYNC: When modified, update:
|
|
13
|
+
* - /packages/core/src/hooks/index.ts
|
|
14
|
+
* - /packages/core/src/hooks/useKeyboardHint.doc.mjs
|
|
15
|
+
* - /apps/storybook/stories/useKeyboardHint.stories.tsx
|
|
16
|
+
* - /packages/cli/templates/blocks/components/Hooks/useKeyboardHintHookUsage.tsx
|
|
17
|
+
*/
|
|
18
|
+
import React, { useCallback, useEffect, useRef } from 'react';
|
|
19
|
+
import * as stylex from '@stylexjs/stylex';
|
|
20
|
+
import "../theme/tokens.stylex.js";
|
|
21
|
+
import { colorVars, radiusVars, shadowVars, spacingVars, typeScaleVars } from "../theme/tokens.stylex.js";
|
|
22
|
+
import { Kbd } from "../Kbd/index.js";
|
|
23
|
+
import { useLayer } from "../Layer/useLayer.js";
|
|
24
|
+
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
// Types
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
// Styles
|
|
31
|
+
// ---------------------------------------------------------------------------
|
|
32
|
+
|
|
33
|
+
const ARROW_KEYS = new Set(['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown']);
|
|
34
|
+
const ARROW_HINT_KEYS = {
|
|
35
|
+
horizontal: ['left', 'right'],
|
|
36
|
+
vertical: ['up', 'down'],
|
|
37
|
+
both: ['left', 'right', 'up', 'down']
|
|
38
|
+
};
|
|
39
|
+
const styles = {
|
|
40
|
+
hint: {
|
|
41
|
+
kVAEAm: "xixxii4",
|
|
42
|
+
kpwlN0: "x1anvkwx",
|
|
43
|
+
kogj98: "x1ghz6dp",
|
|
44
|
+
kWkggS: "x1prclbq",
|
|
45
|
+
kaIpWk: "xh6dtrn",
|
|
46
|
+
kGVxlE: "x1i5ehqx",
|
|
47
|
+
kLKAdn: "xfsso4q",
|
|
48
|
+
kGO01o: "xy143xn",
|
|
49
|
+
kZCmMZ: "x12gdq22",
|
|
50
|
+
kwRFfy: "x1djylfy",
|
|
51
|
+
kGuDYH: "x141an7d",
|
|
52
|
+
kLWn49: "x1ltkj2j",
|
|
53
|
+
kMwMTN: "xv1l7n4",
|
|
54
|
+
khDVqt: "xuxw1ft",
|
|
55
|
+
kSiTet: "xg01cxk xofkqq2",
|
|
56
|
+
k1ekBW: "xsm4mo9",
|
|
57
|
+
kIyJzY: "xx6bhzk",
|
|
58
|
+
kzIqYQ: "xd00j3c",
|
|
59
|
+
kfzvcC: "x47corl",
|
|
60
|
+
$$css: true
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
// ---------------------------------------------------------------------------
|
|
65
|
+
// Hook
|
|
66
|
+
// ---------------------------------------------------------------------------
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Shows an ephemeral visual hint ("← → to navigate") anchored to the focused
|
|
70
|
+
* item when a composite widget first receives keyboard focus. Teaches sighted
|
|
71
|
+
* keyboard users that arrows navigate within the group.
|
|
72
|
+
*
|
|
73
|
+
* The hint renders in the top layer (popover="manual") and is CSS-anchor-
|
|
74
|
+
* positioned to the currently focused element, so it is never clipped by
|
|
75
|
+
* overflow containers. It auto-dismisses on first arrow press, timeout, or
|
|
76
|
+
* blur, and does not re-show for that instance.
|
|
77
|
+
*
|
|
78
|
+
* @example
|
|
79
|
+
* ```
|
|
80
|
+
* const hint = useKeyboardHint({orientation: 'horizontal'});
|
|
81
|
+
* <div role="toolbar" onFocus={hint.onFocus} onBlur={hint.onBlur} onKeyDown={hint.onKeyDown}>
|
|
82
|
+
* {children}
|
|
83
|
+
* {hint.hintElement}
|
|
84
|
+
* </div>
|
|
85
|
+
* ```
|
|
86
|
+
*/
|
|
87
|
+
export function useKeyboardHint(options = {}) {
|
|
88
|
+
const {
|
|
89
|
+
orientation = 'horizontal',
|
|
90
|
+
dismissAfterMs = 3000,
|
|
91
|
+
isEnabled = true
|
|
92
|
+
} = options;
|
|
93
|
+
const timeoutRef = useRef(null);
|
|
94
|
+
const dismissedRef = useRef(false);
|
|
95
|
+
const isVisibleRef = useRef(false);
|
|
96
|
+
const layerAnchorRef = useRef(() => {});
|
|
97
|
+
const clearDismissTimeout = useCallback(() => {
|
|
98
|
+
if (timeoutRef.current) {
|
|
99
|
+
clearTimeout(timeoutRef.current);
|
|
100
|
+
timeoutRef.current = null;
|
|
101
|
+
}
|
|
102
|
+
}, []);
|
|
103
|
+
const handleLayerShow = useCallback(() => {
|
|
104
|
+
isVisibleRef.current = true;
|
|
105
|
+
}, []);
|
|
106
|
+
const handleLayerHide = useCallback(() => {
|
|
107
|
+
isVisibleRef.current = false;
|
|
108
|
+
clearDismissTimeout();
|
|
109
|
+
layerAnchorRef.current(null);
|
|
110
|
+
}, [clearDismissTimeout]);
|
|
111
|
+
const layer = useLayer({
|
|
112
|
+
mode: 'context',
|
|
113
|
+
onShow: handleLayerShow,
|
|
114
|
+
onHide: handleLayerHide
|
|
115
|
+
});
|
|
116
|
+
layerAnchorRef.current = layer.ref;
|
|
117
|
+
|
|
118
|
+
// Hide + mark dismissed (won't re-show for this instance)
|
|
119
|
+
const dismiss = useCallback(() => {
|
|
120
|
+
dismissedRef.current = true;
|
|
121
|
+
clearDismissTimeout();
|
|
122
|
+
layer.hide();
|
|
123
|
+
isVisibleRef.current = false;
|
|
124
|
+
layerAnchorRef.current(null);
|
|
125
|
+
}, [clearDismissTimeout, layer]);
|
|
126
|
+
|
|
127
|
+
// Show the layer anchored to the focused element
|
|
128
|
+
const show = useCallback(anchor => {
|
|
129
|
+
if (dismissedRef.current || !isEnabled) {
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
layerAnchorRef.current(anchor);
|
|
133
|
+
layer.show();
|
|
134
|
+
clearDismissTimeout();
|
|
135
|
+
timeoutRef.current = setTimeout(() => {
|
|
136
|
+
dismiss();
|
|
137
|
+
}, dismissAfterMs);
|
|
138
|
+
}, [clearDismissTimeout, dismiss, dismissAfterMs, isEnabled, layer]);
|
|
139
|
+
|
|
140
|
+
// Cleanup on unmount
|
|
141
|
+
useEffect(() => () => {
|
|
142
|
+
clearDismissTimeout();
|
|
143
|
+
layerAnchorRef.current(null);
|
|
144
|
+
}, [clearDismissTimeout]);
|
|
145
|
+
|
|
146
|
+
// --- Handlers ---
|
|
147
|
+
|
|
148
|
+
const onFocus = useCallback(e => {
|
|
149
|
+
if (dismissedRef.current || !isEnabled) {
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
// Only show on keyboard focus (focus-visible)
|
|
153
|
+
const target = e.target;
|
|
154
|
+
if (!target.matches(':focus-visible')) {
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
// Only show when focus enters from outside the container
|
|
158
|
+
const container = e.currentTarget;
|
|
159
|
+
if (e.relatedTarget instanceof Node && container.contains(e.relatedTarget)) {
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
show(target);
|
|
163
|
+
}, [show, isEnabled]);
|
|
164
|
+
const onBlur = useCallback(e => {
|
|
165
|
+
if (!isVisibleRef.current) {
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
const container = e.currentTarget;
|
|
169
|
+
// Only dismiss when focus leaves the container entirely
|
|
170
|
+
if (e.relatedTarget instanceof Node && container.contains(e.relatedTarget)) {
|
|
171
|
+
// Focus moved within — re-anchor to the new target
|
|
172
|
+
if (!dismissedRef.current && e.relatedTarget instanceof HTMLElement) {
|
|
173
|
+
layerAnchorRef.current(e.relatedTarget);
|
|
174
|
+
}
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
dismiss();
|
|
178
|
+
}, [dismiss]);
|
|
179
|
+
const onKeyDown = useCallback(e => {
|
|
180
|
+
if (!isVisibleRef.current) {
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
if (ARROW_KEYS.has(e.key)) {
|
|
184
|
+
dismiss();
|
|
185
|
+
}
|
|
186
|
+
}, [dismiss]);
|
|
187
|
+
|
|
188
|
+
// --- Render the hint element ---
|
|
189
|
+
|
|
190
|
+
const arrowContent = /*#__PURE__*/_jsx("span", {
|
|
191
|
+
...{
|
|
192
|
+
className: "x3nfvp2 x6s0dn4 xzye2dw"
|
|
193
|
+
},
|
|
194
|
+
children: ARROW_HINT_KEYS[orientation].map(key => /*#__PURE__*/_jsx(Kbd, {
|
|
195
|
+
keys: key
|
|
196
|
+
}, key))
|
|
197
|
+
});
|
|
198
|
+
const hintElement = layer.render(/*#__PURE__*/_jsxs("span", {
|
|
199
|
+
"aria-hidden": "true",
|
|
200
|
+
children: [arrowContent, /*#__PURE__*/_jsx("span", {
|
|
201
|
+
...{
|
|
202
|
+
className: "x11g1kdw"
|
|
203
|
+
},
|
|
204
|
+
children: "to navigate"
|
|
205
|
+
})]
|
|
206
|
+
}), {
|
|
207
|
+
placement: 'below',
|
|
208
|
+
alignment: 'start',
|
|
209
|
+
xstyle: styles.hint,
|
|
210
|
+
style: {
|
|
211
|
+
marginBlockStart: spacingVars['--spacing-2']
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
return {
|
|
215
|
+
hintElement,
|
|
216
|
+
onFocus,
|
|
217
|
+
onBlur,
|
|
218
|
+
onKeyDown
|
|
219
|
+
};
|
|
220
|
+
}
|
|
@@ -83,6 +83,24 @@ export interface UseTreeFocusOptions {
|
|
|
83
83
|
* TreeList uses this to move its single roving tab stop.
|
|
84
84
|
*/
|
|
85
85
|
onActiveChange?: (id: string | undefined) => void;
|
|
86
|
+
/**
|
|
87
|
+
* Roving-tabindex ownership. When true, the hook manages a single tab stop
|
|
88
|
+
* across the visible treeitems: exactly one enabled treeitem carries
|
|
89
|
+
* `tabindex="0"` and the rest `tabindex="-1"`. The tab stop is repaired on
|
|
90
|
+
* mount and whenever items mount/unmount or toggle disabled, and moves with
|
|
91
|
+
* keyboard navigation. Attach the returned {@link UseTreeFocusReturn.handleFocus}
|
|
92
|
+
* to the container's `onFocus` to keep the stop in sync after clicks or
|
|
93
|
+
* programmatic focus.
|
|
94
|
+
*
|
|
95
|
+
* On mount the hook preserves an existing `tabindex="0"` treeitem (so a
|
|
96
|
+
* consumer can seed the active item in its render); if none exists it
|
|
97
|
+
* promotes the first enabled treeitem.
|
|
98
|
+
*
|
|
99
|
+
* When false (the default), the hook only *moves* focus (`.focus()`) and
|
|
100
|
+
* never touches `tabindex` — the caller owns tab-stop management.
|
|
101
|
+
* @default false
|
|
102
|
+
*/
|
|
103
|
+
hasRovingTabIndex?: boolean;
|
|
86
104
|
}
|
|
87
105
|
/**
|
|
88
106
|
* Return type for useTreeFocus hook.
|
|
@@ -92,6 +110,12 @@ export interface UseTreeFocusReturn<T extends HTMLElement = HTMLElement> {
|
|
|
92
110
|
treeRef: React.RefObject<T | null>;
|
|
93
111
|
/** Key down handler to attach to the tree container. */
|
|
94
112
|
handleKeyDown: (e: React.KeyboardEvent) => void;
|
|
113
|
+
/**
|
|
114
|
+
* Focus handler to attach to the container's `onFocus`. Keeps the roving tab
|
|
115
|
+
* stop in sync when `hasRovingTabIndex` is enabled; a no-op otherwise, so it
|
|
116
|
+
* is always safe to attach.
|
|
117
|
+
*/
|
|
118
|
+
handleFocus: (e: React.FocusEvent) => void;
|
|
95
119
|
/** Focus the first enabled visible treeitem. */
|
|
96
120
|
focusFirst: () => void;
|
|
97
121
|
/** Focus the last enabled visible treeitem. */
|
|
@@ -119,12 +143,12 @@ export interface UseTreeFocusReturn<T extends HTMLElement = HTMLElement> {
|
|
|
119
143
|
*
|
|
120
144
|
* @example
|
|
121
145
|
* ```
|
|
122
|
-
* const {treeRef, handleKeyDown} = useTreeFocus<HTMLUListElement>({
|
|
146
|
+
* const {treeRef, handleKeyDown, handleFocus} = useTreeFocus<HTMLUListElement>({
|
|
123
147
|
* onToggleExpand: id => toggle(id),
|
|
124
|
-
*
|
|
148
|
+
* hasRovingTabIndex: true,
|
|
125
149
|
* });
|
|
126
150
|
*
|
|
127
|
-
* <ul ref={treeRef} role="tree" onKeyDown={handleKeyDown}>
|
|
151
|
+
* <ul ref={treeRef} role="tree" onKeyDown={handleKeyDown} onFocus={handleFocus}>
|
|
128
152
|
* {items.map(item => <li role="treeitem" tabIndex={-1}>{item.label}</li>)}
|
|
129
153
|
* </ul>
|
|
130
154
|
* ```
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useTreeFocus.d.ts","sourceRoot":"","sources":["../../src/hooks/useTreeFocus.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"useTreeFocus.d.ts","sourceRoot":"","sources":["../../src/hooks/useTreeFocus.ts"],"names":[],"mappings":"AAkCA;;;;;;;;;GASG;AACH,MAAM,WAAW,mBAAmB;IAClC;;;;;;OAMG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB;;;;;;OAMG;IACH,cAAc,CAAC,EAAE,CAAC,IAAI,EAAE,WAAW,KAAK,OAAO,CAAC;IAEhD;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,WAAW,KAAK,MAAM,CAAC;IAEzC;;;;;OAKG;IACH,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,WAAW,KAAK,OAAO,CAAC;IAE5C;;;;OAIG;IACH,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,WAAW,KAAK,OAAO,CAAC;IAE7C;;;;;OAKG;IACH,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,WAAW,KAAK,MAAM,GAAG,SAAS,CAAC;IAEtD;;;;OAIG;IACH,cAAc,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAEtC;;;;;;;;;OASG;IACH,UAAU,CAAC,EAAE,CACX,IAAI,EAAE,WAAW,EACjB,EAAE,EAAE,MAAM,GAAG,SAAS,KACnB,OAAO,GAAG,SAAS,CAAC;IAEzB;;;;OAIG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IAEpB,gEAAgE;IAChE,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAE1B;;;OAGG;IACH,cAAc,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,GAAG,SAAS,KAAK,IAAI,CAAC;IAElD;;;;;;;;;;;;;;;;OAgBG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC7B;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB,CAAC,CAAC,SAAS,WAAW,GAAG,WAAW;IACrE,iEAAiE;IACjE,OAAO,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;IAEnC,wDAAwD;IACxD,aAAa,EAAE,CAAC,CAAC,EAAE,KAAK,CAAC,aAAa,KAAK,IAAI,CAAC;IAEhD;;;;OAIG;IACH,WAAW,EAAE,CAAC,CAAC,EAAE,KAAK,CAAC,UAAU,KAAK,IAAI,CAAC;IAE3C,gDAAgD;IAChD,UAAU,EAAE,MAAM,IAAI,CAAC;IAEvB,+CAA+C;IAC/C,SAAS,EAAE,MAAM,IAAI,CAAC;CACvB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAgB,YAAY,CAAC,CAAC,SAAS,WAAW,GAAG,WAAW,EAC9D,OAAO,GAAE,mBAAwB,GAChC,kBAAkB,CAAC,CAAC,CAAC,CA2VvB"}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* @file useTreeFocus.ts
|
|
7
|
-
* @input Uses React useCallback, useRef
|
|
7
|
+
* @input Uses React useCallback, useRef, useIsomorphicLayoutEffect
|
|
8
8
|
* @output Exports useTreeFocus hook for WAI-ARIA tree keyboard navigation
|
|
9
9
|
* @position Core hook; used by TreeList for roving tabindex + APG tree keyboard model
|
|
10
10
|
*
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
* - /packages/core/src/hooks/useTreeFocus.test.tsx
|
|
15
15
|
*/
|
|
16
16
|
import { useCallback, useRef } from 'react';
|
|
17
|
+
import { useIsomorphicLayoutEffect } from "./useIsomorphicLayoutEffect.js";
|
|
17
18
|
|
|
18
19
|
/** Keys handled by the tree keyboard model (used to gate typeahead). */
|
|
19
20
|
const NAVIGATION_KEYS = new Set(['ArrowDown', 'ArrowUp', 'ArrowRight', 'ArrowLeft', 'Home', 'End', 'Enter', ' ']);
|
|
@@ -58,12 +59,12 @@ const DEFAULT_TYPEAHEAD_RESET_MS = 500;
|
|
|
58
59
|
*
|
|
59
60
|
* @example
|
|
60
61
|
* ```
|
|
61
|
-
* const {treeRef, handleKeyDown} = useTreeFocus<HTMLUListElement>({
|
|
62
|
+
* const {treeRef, handleKeyDown, handleFocus} = useTreeFocus<HTMLUListElement>({
|
|
62
63
|
* onToggleExpand: id => toggle(id),
|
|
63
|
-
*
|
|
64
|
+
* hasRovingTabIndex: true,
|
|
64
65
|
* });
|
|
65
66
|
*
|
|
66
|
-
* <ul ref={treeRef} role="tree" onKeyDown={handleKeyDown}>
|
|
67
|
+
* <ul ref={treeRef} role="tree" onKeyDown={handleKeyDown} onFocus={handleFocus}>
|
|
67
68
|
* {items.map(item => <li role="treeitem" tabIndex={-1}>{item.label}</li>)}
|
|
68
69
|
* </ul>
|
|
69
70
|
* ```
|
|
@@ -80,7 +81,8 @@ export function useTreeFocus(options = {}) {
|
|
|
80
81
|
onActivate,
|
|
81
82
|
typeahead = true,
|
|
82
83
|
typeaheadResetMs = DEFAULT_TYPEAHEAD_RESET_MS,
|
|
83
|
-
onActiveChange
|
|
84
|
+
onActiveChange,
|
|
85
|
+
hasRovingTabIndex = false
|
|
84
86
|
} = options;
|
|
85
87
|
const treeRef = useRef(null);
|
|
86
88
|
const typeaheadRef = useRef({
|
|
@@ -102,14 +104,63 @@ export function useTreeFocus(options = {}) {
|
|
|
102
104
|
const collapsedOf = useCallback(el => isCollapsed ? isCollapsed(el) : el.getAttribute('aria-expanded') === 'false', [isCollapsed]);
|
|
103
105
|
const idOf = useCallback(el => getItemId ? getItemId(el) : el.dataset.treeId, [getItemId]);
|
|
104
106
|
|
|
107
|
+
// --- Roving tabindex ownership (opt-in via `hasRovingTabIndex`) -------------
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Set `tabindex` on a treeitem, but only when it differs (avoids redundant
|
|
111
|
+
* DOM writes).
|
|
112
|
+
*/
|
|
113
|
+
const setTabIndex = useCallback((el, value) => {
|
|
114
|
+
if (el.getAttribute('tabindex') !== String(value)) {
|
|
115
|
+
el.setAttribute('tabindex', String(value));
|
|
116
|
+
}
|
|
117
|
+
}, []);
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Make `target` the sole tabbable treeitem: 0 on it, -1 on every other
|
|
121
|
+
* visible treeitem.
|
|
122
|
+
*/
|
|
123
|
+
const moveTabStop = useCallback((items, target) => {
|
|
124
|
+
for (const el of items) {
|
|
125
|
+
setTabIndex(el, el === target ? 0 : -1);
|
|
126
|
+
}
|
|
127
|
+
}, [setTabIndex]);
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Repair the roving tab stop: exactly one enabled treeitem is tabbable (0),
|
|
131
|
+
* the rest are -1. Prefer an existing `tabindex="0"` treeitem (so a consumer
|
|
132
|
+
* can seed the active item in its render); otherwise promote the first
|
|
133
|
+
* enabled treeitem.
|
|
134
|
+
*/
|
|
135
|
+
const syncTabStops = useCallback(() => {
|
|
136
|
+
const items = getItems();
|
|
137
|
+
const enabled = items.filter(el => !itemDisabled(el));
|
|
138
|
+
if (enabled.length === 0) {
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
const current = enabled.find(el => el.getAttribute('tabindex') === '0');
|
|
142
|
+
moveTabStop(items, current ?? enabled[0]);
|
|
143
|
+
}, [getItems, itemDisabled, moveTabStop]);
|
|
144
|
+
|
|
145
|
+
// Keep the tab stop valid across renders (items added/removed, disabled
|
|
146
|
+
// toggled). Runs after every commit but only when roving tabindex is on.
|
|
147
|
+
useIsomorphicLayoutEffect(() => {
|
|
148
|
+
if (hasRovingTabIndex) {
|
|
149
|
+
syncTabStops();
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
|
|
105
153
|
/** Move focus to a treeitem and notify the active-change listener. */
|
|
106
154
|
const focusItem = useCallback(el => {
|
|
107
155
|
if (el == null) {
|
|
108
156
|
return;
|
|
109
157
|
}
|
|
158
|
+
if (hasRovingTabIndex) {
|
|
159
|
+
moveTabStop(getItems(), el);
|
|
160
|
+
}
|
|
110
161
|
onActiveChange?.(idOf(el));
|
|
111
162
|
el.focus();
|
|
112
|
-
}, [idOf, onActiveChange]);
|
|
163
|
+
}, [idOf, onActiveChange, hasRovingTabIndex, moveTabStop, getItems]);
|
|
113
164
|
|
|
114
165
|
/**
|
|
115
166
|
* Focus the first enabled treeitem from `start`, moving by `dir`. No wrap —
|
|
@@ -265,9 +316,21 @@ export function useTreeFocus(options = {}) {
|
|
|
265
316
|
break;
|
|
266
317
|
}
|
|
267
318
|
}, [getItems, typeahead, runTypeahead, focusEnabledFrom, focusItem, collapsedOf, expandedOf, idOf, levelOf, itemDisabled, onToggleExpand, onActivate]);
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Keep the roving stop pointing at whatever ended up focused (e.g. a click
|
|
322
|
+
* or programmatic focus) so the next Tab behaves correctly. No-op unless
|
|
323
|
+
* roving tabindex is enabled.
|
|
324
|
+
*/
|
|
325
|
+
const handleFocus = useCallback(() => {
|
|
326
|
+
if (hasRovingTabIndex) {
|
|
327
|
+
syncTabStops();
|
|
328
|
+
}
|
|
329
|
+
}, [hasRovingTabIndex, syncTabStops]);
|
|
268
330
|
return {
|
|
269
331
|
treeRef,
|
|
270
332
|
handleKeyDown,
|
|
333
|
+
handleFocus,
|
|
271
334
|
focusFirst,
|
|
272
335
|
focusLast
|
|
273
336
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@astryxdesign/core",
|
|
3
|
-
"version": "0.1.2-canary.
|
|
3
|
+
"version": "0.1.2-canary.c395fca",
|
|
4
4
|
"displayName": "XDS Core",
|
|
5
5
|
"description": "The component library. Accessible, themeable React components with built-in spacing, dark mode, and StyleX styling.",
|
|
6
6
|
"author": "Meta Open Source",
|
|
@@ -179,6 +179,7 @@ export function BreadcrumbItem({
|
|
|
179
179
|
className,
|
|
180
180
|
style,
|
|
181
181
|
'data-testid': testId,
|
|
182
|
+
...rest
|
|
182
183
|
}: BreadcrumbItemProps) {
|
|
183
184
|
const ctx = use(BreadcrumbContext);
|
|
184
185
|
const LinkComponent = useLinkComponent(as);
|
|
@@ -252,7 +253,8 @@ export function BreadcrumbItem({
|
|
|
252
253
|
className,
|
|
253
254
|
style,
|
|
254
255
|
)}
|
|
255
|
-
data-testid={testId}
|
|
256
|
+
data-testid={testId}
|
|
257
|
+
{...rest}>
|
|
256
258
|
<span aria-hidden="true" {...stylex.props(itemStyles.separator)}>
|
|
257
259
|
{ctx.separator}
|
|
258
260
|
</span>
|
|
@@ -286,7 +288,8 @@ export function BreadcrumbItem({
|
|
|
286
288
|
className,
|
|
287
289
|
style,
|
|
288
290
|
)}
|
|
289
|
-
data-testid={testId}
|
|
291
|
+
data-testid={testId}
|
|
292
|
+
{...rest}>
|
|
290
293
|
<span aria-hidden="true" {...stylex.props(itemStyles.separator)}>
|
|
291
294
|
{ctx.separator}
|
|
292
295
|
</span>
|
package/src/Button/Button.tsx
CHANGED
|
@@ -34,6 +34,7 @@ import {
|
|
|
34
34
|
} from '../theme/tokens.stylex';
|
|
35
35
|
import {Tooltip} from '../Tooltip/Tooltip';
|
|
36
36
|
import {Spinner} from '../Spinner';
|
|
37
|
+
import {VisuallyHidden} from '../VisuallyHidden';
|
|
37
38
|
|
|
38
39
|
import {EDGE_COMP_ATTR} from '../Layout/edgeCompensation.stylex';
|
|
39
40
|
import {useSize} from '../SizeContext/SizeContext';
|
|
@@ -124,17 +125,6 @@ const styles = stylex.create({
|
|
|
124
125
|
textOverflow: 'ellipsis',
|
|
125
126
|
minWidth: 0,
|
|
126
127
|
},
|
|
127
|
-
visuallyHidden: {
|
|
128
|
-
position: 'absolute',
|
|
129
|
-
width: '1px',
|
|
130
|
-
height: '1px',
|
|
131
|
-
padding: 0,
|
|
132
|
-
margin: '-1px',
|
|
133
|
-
overflow: 'hidden',
|
|
134
|
-
clip: 'rect(0, 0, 0, 0)',
|
|
135
|
-
whiteSpace: 'nowrap',
|
|
136
|
-
borderWidth: 0,
|
|
137
|
-
},
|
|
138
128
|
link: {
|
|
139
129
|
textDecoration: 'none',
|
|
140
130
|
},
|
|
@@ -690,12 +680,9 @@ export function Button({
|
|
|
690
680
|
)}
|
|
691
681
|
</span>
|
|
692
682
|
{/* Live region for loading state announcements */}
|
|
693
|
-
<
|
|
694
|
-
{...stylex.props(styles.visuallyHidden)}
|
|
695
|
-
role="status"
|
|
696
|
-
aria-live="polite">
|
|
683
|
+
<VisuallyHidden role="status" aria-live="polite">
|
|
697
684
|
{isLoadingState ? 'Loading' : ''}
|
|
698
|
-
</
|
|
685
|
+
</VisuallyHidden>
|
|
699
686
|
</>
|
|
700
687
|
);
|
|
701
688
|
|
|
@@ -32,7 +32,6 @@ import {useGridFocus} from '../hooks';
|
|
|
32
32
|
import {
|
|
33
33
|
useCalendarDays,
|
|
34
34
|
useCalendarConstraints,
|
|
35
|
-
useCalendarRovingTabindex,
|
|
36
35
|
type CalendarDay,
|
|
37
36
|
} from './hooks';
|
|
38
37
|
import {
|
|
@@ -554,14 +553,34 @@ function MonthGrid({
|
|
|
554
553
|
return null;
|
|
555
554
|
}, [mode, value]);
|
|
556
555
|
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
556
|
+
// Seed the initial roving tab stop for this month. useGridFocus owns the
|
|
557
|
+
// live tab stop (see `hasRovingTabIndex` below) — it honors an existing
|
|
558
|
+
// `tabindex="0"` and repairs/moves it thereafter — so this only decides
|
|
559
|
+
// which day button starts tabbable. Priority: selected date (if visible and
|
|
560
|
+
// enabled) > today (if visible and enabled) > first enabled in-month day.
|
|
561
|
+
const seedTabbableIso = useMemo((): ISODateString | null => {
|
|
562
|
+
if (selectedDateForTabindex) {
|
|
563
|
+
const isSelectedInMonth =
|
|
564
|
+
selectedDateForTabindex.year === year &&
|
|
565
|
+
selectedDateForTabindex.month === month.month;
|
|
566
|
+
if (isSelectedInMonth && !isDateDisabled(selectedDateForTabindex)) {
|
|
567
|
+
return plainDateToISO(selectedDateForTabindex);
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
const isTodayInMonth = today.year === year && today.month === month.month;
|
|
572
|
+
if (isTodayInMonth && !isDateDisabled(today)) {
|
|
573
|
+
return plainDateToISO(today);
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
for (const day of days) {
|
|
577
|
+
if (!day.isOutside && !isDateDisabled(day.date)) {
|
|
578
|
+
return day.iso;
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
return null;
|
|
583
|
+
}, [days, today, year, month.month, isDateDisabled, selectedDateForTabindex]);
|
|
565
584
|
|
|
566
585
|
// Helper to get the focused date from the currently focused element.
|
|
567
586
|
// Reads the machine-readable `data-date` (ISO) attribute rather than parsing
|
|
@@ -628,18 +647,22 @@ function MonthGrid({
|
|
|
628
647
|
// focus (the day button inside the cell). Arrow keys move to the target
|
|
629
648
|
// row/column and, if that cell is disabled, continue in the same direction to
|
|
630
649
|
// the next enabled cell.
|
|
631
|
-
const {
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
650
|
+
const {
|
|
651
|
+
gridRef,
|
|
652
|
+
handleKeyDown: handleGridKeyDown,
|
|
653
|
+
handleFocus: handleGridFocus,
|
|
654
|
+
} = useGridFocus<HTMLDivElement>({
|
|
655
|
+
columns: 7,
|
|
656
|
+
cellSelector: '[role="gridcell"]',
|
|
657
|
+
isCellFocusable: cell =>
|
|
658
|
+
cell.querySelector('button:not([disabled])') !== null,
|
|
659
|
+
getFocusTarget: cell => cell.querySelector<HTMLElement>('button'),
|
|
660
|
+
hasRovingTabIndex: true,
|
|
661
|
+
onNavigateBefore: handleNavigatePrevious,
|
|
662
|
+
onNavigateAfter: handleNavigateNext,
|
|
663
|
+
onPageUp: handlePageUp,
|
|
664
|
+
onPageDown: handlePageDown,
|
|
665
|
+
});
|
|
643
666
|
|
|
644
667
|
// Handle pending focus after month navigation
|
|
645
668
|
useEffect(() => {
|
|
@@ -719,6 +742,7 @@ function MonthGrid({
|
|
|
719
742
|
role="grid"
|
|
720
743
|
aria-label={monthLabel}
|
|
721
744
|
onKeyDown={handleGridKeyDown}
|
|
745
|
+
onFocus={handleGridFocus}
|
|
722
746
|
{...stylex.props(
|
|
723
747
|
monthGridStyles.daysGrid,
|
|
724
748
|
hasWeekNumbers && monthGridStyles.daysGridWithNumbers,
|
|
@@ -774,7 +798,7 @@ function MonthGrid({
|
|
|
774
798
|
today={today}
|
|
775
799
|
hasOutsideDays={hasOutsideDays}
|
|
776
800
|
isDisabled={isDateDisabled(day.date)}
|
|
777
|
-
isTabbable={
|
|
801
|
+
isTabbable={day.iso === seedTabbableIso}
|
|
778
802
|
onDayClick={onDayClick}
|
|
779
803
|
onDayHover={onDayHover}
|
|
780
804
|
/>
|
|
@@ -803,6 +827,11 @@ interface DayCellProps {
|
|
|
803
827
|
today: PlainDate;
|
|
804
828
|
hasOutsideDays: boolean;
|
|
805
829
|
isDisabled: boolean;
|
|
830
|
+
/**
|
|
831
|
+
* Whether this day seeds the initial roving tab stop. useGridFocus
|
|
832
|
+
* (`hasRovingTabIndex`) owns the live tab stop thereafter — it honors an
|
|
833
|
+
* existing `tabindex="0"` and repairs/moves it on navigation and focus.
|
|
834
|
+
*/
|
|
806
835
|
isTabbable: boolean;
|
|
807
836
|
onDayClick: (date: PlainDate) => void;
|
|
808
837
|
onDayHover: (date: PlainDate | null) => void;
|
|
@@ -894,6 +923,7 @@ function DayCell({
|
|
|
894
923
|
aria-selected={state.isSelected || state.isInRange || undefined}
|
|
895
924
|
aria-disabled={state.effectivelyDisabled || undefined}
|
|
896
925
|
disabled={isDisabled}
|
|
926
|
+
// Initial roving tab-stop seed; useGridFocus owns it after mount.
|
|
897
927
|
tabIndex={isTabbableDay ? 0 : -1}
|
|
898
928
|
onClick={() => !state.effectivelyDisabled && onDayClick(date)}
|
|
899
929
|
onMouseEnter={() => !state.effectivelyDisabled && onDayHover(date)}
|
|
@@ -27,9 +27,3 @@ export type {
|
|
|
27
27
|
UseCalendarNavigationOptions,
|
|
28
28
|
UseCalendarNavigationReturn,
|
|
29
29
|
} from './useCalendarNavigation';
|
|
30
|
-
|
|
31
|
-
export {useCalendarRovingTabindex} from './useCalendarRovingTabindex';
|
|
32
|
-
export type {
|
|
33
|
-
UseCalendarRovingTabindexOptions,
|
|
34
|
-
UseCalendarRovingTabindexReturn,
|
|
35
|
-
} from './useCalendarRovingTabindex';
|
package/src/Calendar/index.ts
CHANGED
|
@@ -26,7 +26,6 @@ export {
|
|
|
26
26
|
useCalendarDays,
|
|
27
27
|
useCalendarConstraints,
|
|
28
28
|
useCalendarNavigation,
|
|
29
|
-
useCalendarRovingTabindex,
|
|
30
29
|
} from './hooks';
|
|
31
30
|
export type {
|
|
32
31
|
CalendarDay,
|
|
@@ -36,8 +35,6 @@ export type {
|
|
|
36
35
|
UseCalendarConstraintsReturn,
|
|
37
36
|
UseCalendarNavigationOptions,
|
|
38
37
|
UseCalendarNavigationReturn,
|
|
39
|
-
UseCalendarRovingTabindexOptions,
|
|
40
|
-
UseCalendarRovingTabindexReturn,
|
|
41
38
|
} from './hooks';
|
|
42
39
|
|
|
43
40
|
// Re-export calendar-specific utilities for advanced usage
|
|
@@ -151,7 +151,7 @@ export function CheckboxList({
|
|
|
151
151
|
'data-testid': dataTestId,
|
|
152
152
|
}: CheckboxListProps) {
|
|
153
153
|
const inputID = useId();
|
|
154
|
-
const
|
|
154
|
+
const labelID = useId();
|
|
155
155
|
const descriptionID = useId();
|
|
156
156
|
const statusMessageID = useId();
|
|
157
157
|
|
|
@@ -211,7 +211,7 @@ export function CheckboxList({
|
|
|
211
211
|
isLabelHidden={isLabelHidden}
|
|
212
212
|
description={description}
|
|
213
213
|
inputID={inputID}
|
|
214
|
-
|
|
214
|
+
labelID={labelID}
|
|
215
215
|
isGroupLabel
|
|
216
216
|
descriptionID={description ? descriptionID : undefined}
|
|
217
217
|
isDisabled={isDisabled}
|
|
@@ -231,7 +231,7 @@ export function CheckboxList({
|
|
|
231
231
|
<CheckboxListContext value={contextValue}>
|
|
232
232
|
<div
|
|
233
233
|
role="group"
|
|
234
|
-
aria-labelledby={
|
|
234
|
+
aria-labelledby={labelID}
|
|
235
235
|
aria-describedby={
|
|
236
236
|
[
|
|
237
237
|
description ? descriptionID : null,
|
|
@@ -56,6 +56,16 @@ export const docs = {
|
|
|
56
56
|
],
|
|
57
57
|
},
|
|
58
58
|
|
|
59
|
+
// `source` is a custom object type the docsite preview cannot generate
|
|
60
|
+
// automatically; without these defaults the properties tab shows the
|
|
61
|
+
// missing-required-props placeholder instead of an interactive preview.
|
|
62
|
+
playground: {
|
|
63
|
+
defaults: {
|
|
64
|
+
source: {title: 'Astryx Design', url: 'https://example.com'},
|
|
65
|
+
number: 1,
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
|
|
59
69
|
props: [
|
|
60
70
|
{
|
|
61
71
|
name: 'source',
|