@astryxdesign/core 0.7.6-canary.d4b2d76 → 0.7.6-canary.d545bc5
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/DropdownMenu/DropdownMenu.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenu.js +1 -58
- package/dist/Grid/GridSpan.d.ts +2 -0
- package/dist/Grid/GridSpan.d.ts.map +1 -1
- package/dist/Grid/GridSpan.js +2 -0
- package/dist/SegmentedControl/SegmentedControl.d.ts +7 -5
- package/dist/SegmentedControl/SegmentedControl.d.ts.map +1 -1
- package/dist/SegmentedControl/SegmentedControl.js +13 -20
- package/dist/SegmentedControl/SegmentedControlContext.d.ts +2 -9
- package/dist/SegmentedControl/SegmentedControlContext.d.ts.map +1 -1
- package/dist/SegmentedControl/SegmentedControlItem.d.ts.map +1 -1
- package/dist/SegmentedControl/SegmentedControlItem.js +4 -14
- package/dist/SideNav/SideNav.d.ts.map +1 -1
- package/dist/SideNav/SideNav.js +128 -5
- package/dist/SideNav/SideNavCollapseButton.d.ts.map +1 -1
- package/dist/SideNav/SideNavCollapseButton.js +11 -2
- package/dist/SideNav/SideNavFocusTargetsContext.d.ts +11 -0
- package/dist/SideNav/SideNavFocusTargetsContext.d.ts.map +1 -0
- package/dist/SideNav/SideNavFocusTargetsContext.js +16 -0
- package/dist/SideNav/SideNavItem.d.ts.map +1 -1
- package/dist/SideNav/SideNavItem.js +13 -1
- package/dist/hooks/useFocusTrap.d.ts.map +1 -1
- package/dist/hooks/useFocusTrap.js +2 -3
- package/dist/hooks/useListFocus.d.ts +10 -7
- package/dist/hooks/useListFocus.d.ts.map +1 -1
- package/dist/hooks/useListFocus.js +29 -17
- package/dist/motion/animator.d.ts.map +1 -1
- package/dist/motion/animator.js +69 -3
- package/dist/utils/accessibleName.d.ts +8 -0
- package/dist/utils/accessibleName.d.ts.map +1 -0
- package/dist/utils/accessibleName.js +72 -0
- package/dist/utils/focusReturn.d.ts +35 -2
- package/dist/utils/focusReturn.d.ts.map +1 -1
- package/dist/utils/focusReturn.js +53 -2
- package/package.json +4 -4
- package/src/DropdownMenu/DropdownMenu.tsx +1 -71
- package/src/Grid/GridSpan.doc.mjs +51 -2
- package/src/Grid/GridSpan.tsx +2 -0
- package/src/SegmentedControl/SegmentedControl.doc.mjs +5 -5
- package/src/SegmentedControl/SegmentedControl.tsx +23 -30
- package/src/SegmentedControl/SegmentedControlContext.ts +2 -9
- package/src/SegmentedControl/SegmentedControlItem.doc.mjs +1 -1
- package/src/SegmentedControl/SegmentedControlItem.tsx +4 -17
- package/src/SideNav/SideNav.tsx +177 -6
- package/src/SideNav/SideNavCollapseButton.tsx +18 -2
- package/src/SideNav/SideNavFocusTargetsContext.ts +29 -0
- package/src/SideNav/SideNavItem.tsx +19 -3
- package/src/hooks/useFocusTrap.ts +2 -8
- package/src/hooks/useListFocus.ts +51 -22
- package/src/motion/animator.ts +81 -11
- package/src/utils/accessibleName.ts +87 -0
- package/src/utils/focusReturn.ts +67 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DropdownMenu.d.ts","sourceRoot":"","sources":["../../src/DropdownMenu/DropdownMenu.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH,OAAO,KAAK,EAAE,EAOZ,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"DropdownMenu.d.ts","sourceRoot":"","sources":["../../src/DropdownMenu/DropdownMenu.tsx"],"names":[],"mappings":"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"}
|
|
@@ -35,6 +35,7 @@
|
|
|
35
35
|
* - /packages/cli/assets/templates/blocks/components/DropdownMenu/ (showcase blocks)
|
|
36
36
|
*/
|
|
37
37
|
import React, { useCallback, useEffect, useId, useMemo, useRef, useState } from 'react';
|
|
38
|
+
import { readAccessibleName } from "../utils/accessibleName.js";
|
|
38
39
|
import * as stylex from '@stylexjs/stylex';
|
|
39
40
|
import { usePopover } from "../Popover/usePopover.js";
|
|
40
41
|
import { Button } from "../Button/index.js";
|
|
@@ -276,64 +277,6 @@ function holdInvokerThroughPress(control, popoverId) {
|
|
|
276
277
|
doc.addEventListener('pointerup', onPressEnd, true);
|
|
277
278
|
doc.addEventListener('pointercancel', onPressEnd, true);
|
|
278
279
|
}
|
|
279
|
-
|
|
280
|
-
/** Text an assistive technology reads from `element`, `aria-hidden` left out. */
|
|
281
|
-
function visibleText(element) {
|
|
282
|
-
const copy = element.cloneNode(true);
|
|
283
|
-
for (const hidden of copy.querySelectorAll('[aria-hidden="true"]')) {
|
|
284
|
-
hidden.remove();
|
|
285
|
-
}
|
|
286
|
-
// An icon-only control is named by its image's alt, an inner aria-label, or
|
|
287
|
-
// an svg title rather than by text, so those count as its contents too.
|
|
288
|
-
for (const named of copy.querySelectorAll('img[alt], [aria-label]')) {
|
|
289
|
-
const name = named.getAttribute(named.tagName === 'IMG' ? 'alt' : 'aria-label') ?? '';
|
|
290
|
-
named.replaceWith(copy.ownerDocument.createTextNode(` ${name} `));
|
|
291
|
-
}
|
|
292
|
-
for (const svg of copy.querySelectorAll('svg')) {
|
|
293
|
-
const title = svg.querySelector('title')?.textContent ?? '';
|
|
294
|
-
svg.replaceWith(copy.ownerDocument.createTextNode(` ${title} `));
|
|
295
|
-
}
|
|
296
|
-
return (copy.textContent ?? '').replace(/\s+/g, ' ').trim();
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
/**
|
|
300
|
-
* The name a custom trigger gives the menu it labels (`aria-labelledby` on
|
|
301
|
-
* the menu points at it): its own `aria-labelledby` targets, its
|
|
302
|
-
* `aria-label`, its text, or its `title`, in that order.
|
|
303
|
-
*/
|
|
304
|
-
function readAccessibleName(element) {
|
|
305
|
-
if (element == null) {
|
|
306
|
-
return undefined;
|
|
307
|
-
}
|
|
308
|
-
const labelledBy = element.getAttribute('aria-labelledby');
|
|
309
|
-
if (labelledBy != null && labelledBy.trim() !== '') {
|
|
310
|
-
const text = labelledBy.split(/\s+/).map(id => element.ownerDocument.getElementById(id)).filter(node => node != null).map(visibleText).join(' ').trim();
|
|
311
|
-
if (text !== '') {
|
|
312
|
-
return text;
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
const label = element.getAttribute('aria-label')?.trim();
|
|
316
|
-
if (label) {
|
|
317
|
-
return label;
|
|
318
|
-
}
|
|
319
|
-
// A form control is named by its own <label>s; an input button by its value.
|
|
320
|
-
const labels = 'labels' in element ? Array.from(element.labels ?? []) : [];
|
|
321
|
-
const labelText = labels.map(visibleText).join(' ').trim();
|
|
322
|
-
if (labelText !== '') {
|
|
323
|
-
return labelText;
|
|
324
|
-
}
|
|
325
|
-
if (element instanceof HTMLInputElement && ['button', 'submit', 'reset'].includes(element.getAttribute('type') ?? '')) {
|
|
326
|
-
const value = element.value.trim();
|
|
327
|
-
if (value !== '') {
|
|
328
|
-
return value;
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
const text = visibleText(element);
|
|
332
|
-
if (text !== '') {
|
|
333
|
-
return text;
|
|
334
|
-
}
|
|
335
|
-
return element.getAttribute('title')?.trim() || undefined;
|
|
336
|
-
}
|
|
337
280
|
function DropdownMenuBottomSheet({
|
|
338
281
|
button: buttonFromProps,
|
|
339
282
|
renderTrigger,
|
package/dist/Grid/GridSpan.d.ts
CHANGED
|
@@ -5,8 +5,10 @@
|
|
|
5
5
|
* @position Grid span component; controls grid item span
|
|
6
6
|
*
|
|
7
7
|
* SYNC: When modified, update these files to stay in sync:
|
|
8
|
+
* - /packages/core/src/Grid/GridSpan.doc.mjs
|
|
8
9
|
* - /packages/core/src/Grid/Grid.doc.mjs
|
|
9
10
|
* - /packages/core/src/Grid/Grid.test.tsx
|
|
11
|
+
* - /apps/storybook/stories/GridSpan.stories.tsx
|
|
10
12
|
* - /apps/storybook/stories/Grid.stories.tsx
|
|
11
13
|
* - /packages/cli/assets/templates/blocks/components/Grid/ (showcase blocks)
|
|
12
14
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GridSpan.d.ts","sourceRoot":"","sources":["../../src/Grid/GridSpan.tsx"],"names":[],"mappings":"AAEA
|
|
1
|
+
{"version":3,"file":"GridSpan.d.ts","sourceRoot":"","sources":["../../src/Grid/GridSpan.tsx"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,OAAO,CAAC;AACrC,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAK5C,MAAM,WAAW,aAAc,SAAQ,SAAS,CAAC,cAAc,CAAC;IAC9D,wCAAwC;IACxC,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IAChC;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAE1B;;;OAGG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd;;OAEG;IACH,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB;AAeD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,QAAQ,CAAC,EACvB,OAAO,EACP,IAAI,EACJ,MAAM,EACN,SAAS,EACT,KAAK,EACL,QAAQ,EACR,GAAG,EACH,GAAG,KAAK,EACT,EAAE,aAAa,+BAwBf;yBAjCe,QAAQ"}
|
package/dist/Grid/GridSpan.js
CHANGED
|
@@ -7,8 +7,10 @@
|
|
|
7
7
|
* @position Grid span component; controls grid item span
|
|
8
8
|
*
|
|
9
9
|
* SYNC: When modified, update these files to stay in sync:
|
|
10
|
+
* - /packages/core/src/Grid/GridSpan.doc.mjs
|
|
10
11
|
* - /packages/core/src/Grid/Grid.doc.mjs
|
|
11
12
|
* - /packages/core/src/Grid/Grid.test.tsx
|
|
13
|
+
* - /apps/storybook/stories/GridSpan.stories.tsx
|
|
12
14
|
* - /apps/storybook/stories/Grid.stories.tsx
|
|
13
15
|
* - /packages/cli/assets/templates/blocks/components/Grid/ (showcase blocks)
|
|
14
16
|
*/
|
|
@@ -41,7 +41,9 @@ export interface SegmentedControlProps extends Omit<BaseProps<HTMLDivElement>, '
|
|
|
41
41
|
*/
|
|
42
42
|
layout?: SegmentedControlLayout;
|
|
43
43
|
/**
|
|
44
|
-
* Whether the entire control is disabled.
|
|
44
|
+
* Whether the entire control is disabled. The selected segment keeps the
|
|
45
|
+
* control's Tab stop, announced as disabled (`aria-disabled`), and nothing
|
|
46
|
+
* can be selected.
|
|
45
47
|
* @default false
|
|
46
48
|
*/
|
|
47
49
|
isDisabled?: boolean;
|
|
@@ -49,11 +51,11 @@ export interface SegmentedControlProps extends Omit<BaseProps<HTMLDivElement>, '
|
|
|
49
51
|
* Explains why the control is disabled. Applies to the whole-group disabled
|
|
50
52
|
* state (`isDisabled`), not individual segments. When set together with
|
|
51
53
|
* `isDisabled`, the control shows a tooltip with this text on hover and
|
|
52
|
-
* keyboard focus, and
|
|
53
|
-
*
|
|
54
|
+
* keyboard focus, and the reason describes each segment for assistive
|
|
55
|
+
* technology. Selection stays blocked.
|
|
54
56
|
*
|
|
55
|
-
* Use this instead of wrapping a disabled control in `Tooltip
|
|
56
|
-
*
|
|
57
|
+
* Use this instead of wrapping a disabled control in `Tooltip`, so the
|
|
58
|
+
* reason is attached to the control itself.
|
|
57
59
|
*/
|
|
58
60
|
disabledMessage?: string;
|
|
59
61
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SegmentedControl.d.ts","sourceRoot":"","sources":["../../src/SegmentedControl/SegmentedControl.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,EAAuB,KAAK,SAAS,EAAC,MAAM,OAAO,CAAC;AAQlE,OAAO,KAAK,EACV,oBAAoB,EACpB,sBAAsB,EACvB,MAAM,2BAA2B,CAAC;AAInC,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAI5C,MAAM,WAAW,qBAAsB,SAAQ,IAAI,CACjD,SAAS,CAAC,cAAc,CAAC,EACzB,UAAU,CACX;IACC,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IAChC;;OAEG;IACH,KAAK,EAAE,MAAM,CAAC;IACd;;OAEG;IACH,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC;;OAEG;IACH,KAAK,EAAE,MAAM,CAAC;IACd;;;OAGG;IACH,IAAI,CAAC,EAAE,oBAAoB,CAAC;IAC5B;;;;;;OAMG;IACH,MAAM,CAAC,EAAE,sBAAsB,CAAC;IAChC
|
|
1
|
+
{"version":3,"file":"SegmentedControl.d.ts","sourceRoot":"","sources":["../../src/SegmentedControl/SegmentedControl.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,EAAuB,KAAK,SAAS,EAAC,MAAM,OAAO,CAAC;AAQlE,OAAO,KAAK,EACV,oBAAoB,EACpB,sBAAsB,EACvB,MAAM,2BAA2B,CAAC;AAInC,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAI5C,MAAM,WAAW,qBAAsB,SAAQ,IAAI,CACjD,SAAS,CAAC,cAAc,CAAC,EACzB,UAAU,CACX;IACC,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IAChC;;OAEG;IACH,KAAK,EAAE,MAAM,CAAC;IACd;;OAEG;IACH,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC;;OAEG;IACH,KAAK,EAAE,MAAM,CAAC;IACd;;;OAGG;IACH,IAAI,CAAC,EAAE,oBAAoB,CAAC;IAC5B;;;;;;OAMG;IACH,MAAM,CAAC,EAAE,sBAAsB,CAAC;IAChC;;;;;OAKG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;;;;;;;OASG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;OAEG;IACH,QAAQ,EAAE,SAAS,CAAC;CACrB;AA0DD;;;;;;;;;;;;GAYG;AACH,wBAAgB,gBAAgB,CAAC,EAC/B,GAAG,EACH,KAAK,EACL,QAAQ,EACR,KAAK,EACL,IAAI,EAAE,QAAQ,EACd,MAAc,EACd,UAAkB,EAClB,eAAe,EACf,QAAQ,EACR,MAAM,EACN,SAAS,EACT,KAAK,EACL,SAAS,EAAE,aAAa,EACxB,OAAO,EAAE,WAAW,EACpB,MAAM,EAAE,UAAU,EAClB,kBAAkB,EAAE,mBAAmB,EACvC,GAAG,IAAI,EACR,EAAE,qBAAqB,qBAoJvB;yBAtKe,gBAAgB"}
|
|
@@ -52,11 +52,6 @@ const styles = {
|
|
|
52
52
|
$$css: true
|
|
53
53
|
},
|
|
54
54
|
disabled: {
|
|
55
|
-
kSiTet: "xbyyjgo",
|
|
56
|
-
kfzvcC: "x47corl",
|
|
57
|
-
$$css: true
|
|
58
|
-
},
|
|
59
|
-
disabledWithMessage: {
|
|
60
55
|
kSiTet: "xbyyjgo",
|
|
61
56
|
$$css: true
|
|
62
57
|
}
|
|
@@ -113,11 +108,10 @@ export function SegmentedControl({
|
|
|
113
108
|
}) {
|
|
114
109
|
const size = useSize(sizeProp, 'md');
|
|
115
110
|
|
|
116
|
-
// Disabled-reason tooltip. Applies to the whole-group disabled state.
|
|
117
|
-
//
|
|
118
|
-
//
|
|
119
|
-
//
|
|
120
|
-
// blocked by the isDisabled guards in the click/focus handlers.
|
|
111
|
+
// Disabled-reason tooltip. Applies to the whole-group disabled state. The
|
|
112
|
+
// tooltip listeners attach to the radiogroup container, which keeps pointer
|
|
113
|
+
// events while disabled; the radios stay perceivable via aria-disabled.
|
|
114
|
+
// Selection is blocked by the isDisabled guards in the click/focus handlers.
|
|
121
115
|
const showsDisabledMessage = isDisabled && !!disabledMessage;
|
|
122
116
|
const disabledMessageTooltip = useTooltip({
|
|
123
117
|
placement: 'above',
|
|
@@ -139,10 +133,10 @@ export function SegmentedControl({
|
|
|
139
133
|
} = useInternalListFocus({
|
|
140
134
|
itemSelector: '[role="radio"]:not([aria-disabled="true"])',
|
|
141
135
|
hasRovingTabIndex: true,
|
|
142
|
-
//
|
|
143
|
-
//
|
|
144
|
-
// disabled by its own isDisabled, else the first.
|
|
145
|
-
|
|
136
|
+
// One Tab stop, placed by the control (FR2, FR7): the selected segment,
|
|
137
|
+
// enabled or not and in a disabled control too, else the first segment
|
|
138
|
+
// not disabled by its own isDisabled, else the first.
|
|
139
|
+
tabStopOrder: orderTabStop,
|
|
146
140
|
wrap: true,
|
|
147
141
|
orientation: 'horizontal'
|
|
148
142
|
});
|
|
@@ -200,7 +194,6 @@ export function SegmentedControl({
|
|
|
200
194
|
size,
|
|
201
195
|
layout,
|
|
202
196
|
isDisabled,
|
|
203
|
-
hasDisabledMessage: showsDisabledMessage,
|
|
204
197
|
disabledReasonID: showsDisabledMessage ? disabledMessageTooltip.describedBy : undefined
|
|
205
198
|
}), [value, onChange, size, layout, isDisabled, showsDisabledMessage, disabledMessageTooltip.describedBy]);
|
|
206
199
|
return /*#__PURE__*/_jsxs(SegmentedControlContext, {
|
|
@@ -219,7 +212,7 @@ export function SegmentedControl({
|
|
|
219
212
|
onBlur: composeEventHandlers(onBlurProp, hint.onBlur),
|
|
220
213
|
...mergeProps(themeProps('segmented-control', {
|
|
221
214
|
size
|
|
222
|
-
}), stylex.props(styles.container, sizeStyles[size], layout === 'fill' && styles.fill, isDisabled &&
|
|
215
|
+
}), stylex.props(styles.container, sizeStyles[size], layout === 'fill' && styles.fill, isDisabled && styles.disabled, xstyle), className, style),
|
|
223
216
|
children: [children, hint.hintElement]
|
|
224
217
|
}), showsDisabledMessage && disabledMessageTooltip.renderTooltip(disabledMessage)]
|
|
225
218
|
});
|
|
@@ -227,11 +220,11 @@ export function SegmentedControl({
|
|
|
227
220
|
SegmentedControl.displayName = 'SegmentedControl';
|
|
228
221
|
|
|
229
222
|
/**
|
|
230
|
-
* The
|
|
231
|
-
*
|
|
232
|
-
*
|
|
223
|
+
* The control's segments, its Tab stop first: the selected segment, enabled
|
|
224
|
+
* or not, else the first segment not disabled by its own isDisabled, else the
|
|
225
|
+
* first (FR2, FR7).
|
|
233
226
|
*/
|
|
234
|
-
function
|
|
227
|
+
function orderTabStop(list) {
|
|
235
228
|
const segments = Array.from(list.querySelectorAll('[role="radio"]'));
|
|
236
229
|
const stop = segments.find(segment => segment.getAttribute('aria-checked') === 'true') ?? segments.find(segment => !isSegmentDisabledOnItsOwn(segment)) ?? segments[0];
|
|
237
230
|
return stop == null ? [] : [stop, ...segments.filter(segment => segment !== stop)];
|
|
@@ -7,15 +7,8 @@ export interface SegmentedControlContextValue {
|
|
|
7
7
|
layout: SegmentedControlLayout;
|
|
8
8
|
isDisabled: boolean;
|
|
9
9
|
/**
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* rather than being dropped from the tab order) so the group's disabled-reason
|
|
13
|
-
* tooltip is keyboard-discoverable; selection is still blocked.
|
|
14
|
-
*/
|
|
15
|
-
hasDisabledMessage?: boolean;
|
|
16
|
-
/**
|
|
17
|
-
* The id of the group's disabled-reason tooltip while `hasDisabledMessage`
|
|
18
|
-
* is true. It also describes each segment, so the segment that has focus is
|
|
10
|
+
* The id of the group's disabled-reason tooltip while the group is disabled
|
|
11
|
+
* with a `disabledMessage`. It also describes each segment, so the segment that has focus is
|
|
19
12
|
* described by the reason (spec:AST-085 FR5).
|
|
20
13
|
*/
|
|
21
14
|
disabledReasonID?: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SegmentedControlContext.d.ts","sourceRoot":"","sources":["../../src/SegmentedControl/SegmentedControlContext.ts"],"names":[],"mappings":"AAgBA,MAAM,MAAM,oBAAoB,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AACtD,MAAM,MAAM,sBAAsB,GAAG,KAAK,GAAG,MAAM,CAAC;AAEpD,MAAM,WAAW,4BAA4B;IAC3C,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,IAAI,EAAE,oBAAoB,CAAC;IAC3B,MAAM,EAAE,sBAAsB,CAAC;IAC/B,UAAU,EAAE,OAAO,CAAC;IACpB
|
|
1
|
+
{"version":3,"file":"SegmentedControlContext.d.ts","sourceRoot":"","sources":["../../src/SegmentedControl/SegmentedControlContext.ts"],"names":[],"mappings":"AAgBA,MAAM,MAAM,oBAAoB,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AACtD,MAAM,MAAM,sBAAsB,GAAG,KAAK,GAAG,MAAM,CAAC;AAEpD,MAAM,WAAW,4BAA4B;IAC3C,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,IAAI,EAAE,oBAAoB,CAAC;IAC3B,MAAM,EAAE,sBAAsB,CAAC;IAC/B,UAAU,EAAE,OAAO,CAAC;IACpB;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,eAAO,MAAM,uBAAuB,8DACsB,CAAC;AAG3D,wBAAgB,0BAA0B,IAAI,4BAA4B,CASzE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SegmentedControlItem.d.ts","sourceRoot":"","sources":["../../src/SegmentedControl/SegmentedControlItem.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,EAAC,KAAK,SAAS,EAAC,MAAM,OAAO,CAAC;AAiB5C,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAM5C,MAAM,WAAW,yBAA0B,SAAQ,SAAS,CAAC,iBAAiB,CAAC;IAC7E,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC;IACnC;;OAEG;IACH,KAAK,EAAE,MAAM,CAAC;IACd;;;OAGG;IACH,KAAK,EAAE,MAAM,CAAC;IACd;;;;OAIG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB;;OAEG;IACH,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAgHD;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAAC,EACnC,GAAG,EACH,KAAK,EACL,KAAK,EACL,aAAqB,EACrB,IAAI,EACJ,UAAkB,EAClB,OAAO,EAAE,WAAW,EACpB,MAAM,EACN,kBAAkB,EAAE,mBAAmB,EACvC,GAAG,IAAI,EACR,EAAE,yBAAyB,
|
|
1
|
+
{"version":3,"file":"SegmentedControlItem.d.ts","sourceRoot":"","sources":["../../src/SegmentedControl/SegmentedControlItem.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,EAAC,KAAK,SAAS,EAAC,MAAM,OAAO,CAAC;AAiB5C,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAM5C,MAAM,WAAW,yBAA0B,SAAQ,SAAS,CAAC,iBAAiB,CAAC;IAC7E,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC;IACnC;;OAEG;IACH,KAAK,EAAE,MAAM,CAAC;IACd;;;OAGG;IACH,KAAK,EAAE,MAAM,CAAC;IACd;;;;OAIG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB;;OAEG;IACH,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAgHD;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAAC,EACnC,GAAG,EACH,KAAK,EACL,KAAK,EACL,aAAqB,EACrB,IAAI,EACJ,UAAkB,EAClB,OAAO,EAAE,WAAW,EACpB,MAAM,EACN,kBAAkB,EAAE,mBAAmB,EACvC,GAAG,IAAI,EACR,EAAE,yBAAyB,qBA2E3B;yBAtFe,oBAAoB"}
|
|
@@ -151,14 +151,6 @@ export function SegmentedControlItem({
|
|
|
151
151
|
const ctx = useSegmentedControlContext();
|
|
152
152
|
const isSelected = ctx.value === value;
|
|
153
153
|
const isItemDisabled = isDisabled || ctx.isDisabled;
|
|
154
|
-
// When the whole group is disabled with a disabledMessage, keep the selected
|
|
155
|
-
// segment focusable so the group's reason tooltip is keyboard-discoverable.
|
|
156
|
-
// Per-item disabling (`isDisabled` on the item) always drops out of the tab
|
|
157
|
-
// order. Activation stays blocked by the isItemDisabled guard in handleClick.
|
|
158
|
-
// Disabled with a reason, the selected segment keeps the Tab stop even when
|
|
159
|
-
// it is itself disabled (FR4); with no selected segment, the group places
|
|
160
|
-
// the stop (SegmentedControl FR2).
|
|
161
|
-
const keepsSelectedFocusable = isSelected && (ctx.hasDisabledMessage ?? false);
|
|
162
154
|
const size = ctx.size;
|
|
163
155
|
const isFill = ctx.layout === 'fill';
|
|
164
156
|
|
|
@@ -193,12 +185,10 @@ export function SegmentedControlItem({
|
|
|
193
185
|
,
|
|
194
186
|
...segmentDisabledProps(isDisabled),
|
|
195
187
|
...(!isSelected && !isItemDisabled ? pressable : undefined),
|
|
196
|
-
//
|
|
197
|
-
//
|
|
198
|
-
//
|
|
199
|
-
|
|
200
|
-
// stays focusable so the reason tooltip is keyboard-discoverable.
|
|
201
|
-
tabIndex: isSelected && !isItemDisabled || keepsSelectedFocusable ? 0 : -1,
|
|
188
|
+
// The selected segment holds the Tab stop, disabled or not (spec:AST-085
|
|
189
|
+
// FR1); activation stays blocked by the isItemDisabled guard. With no
|
|
190
|
+
// selected segment, the group places the stop (SegmentedControl FR2).
|
|
191
|
+
tabIndex: isSelected ? 0 : -1,
|
|
202
192
|
onClick: handleClick,
|
|
203
193
|
...mergeProps(themeProps('segmented-control-item', {
|
|
204
194
|
size,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SideNav.d.ts","sourceRoot":"","sources":["../../src/SideNav/SideNav.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAQL,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AACf,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAE5C,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,kBAAkB,CAAC;AAGnD,OAAO,EAGL,KAAK,wBAAwB,EAC7B,KAAK,+BAA+B,EACrC,MAAM,0BAA0B,CAAC;AAKlC,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,2BAA2B,CAAC;
|
|
1
|
+
{"version":3,"file":"SideNav.d.ts","sourceRoot":"","sources":["../../src/SideNav/SideNav.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAQL,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AACf,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAE5C,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,kBAAkB,CAAC;AAGnD,OAAO,EAGL,KAAK,wBAAwB,EAC7B,KAAK,+BAA+B,EACrC,MAAM,0BAA0B,CAAC;AAKlC,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,2BAA2B,CAAC;AA2Q/D,MAAM,WAAW,YAAa,SAAQ,SAAS,CAAC,WAAW,CAAC;IAC1D,wCAAwC;IACxC,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;IAE7B;;;;;;;OAOG;IACH,SAAS,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,+BAA+B,CAAC,CAAC;IAEvD;;OAEG;IACH,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB;;OAEG;IACH,UAAU,CAAC,EAAE,SAAS,CAAC;IACvB;;OAEG;IACH,QAAQ,EAAE,SAAS,CAAC;IACpB;;OAEG;IACH,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB;;OAEG;IACH,WAAW,CAAC,EAAE,SAAS,CAAC;IACxB;;;;;;;;;OASG;IACH,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;IAC5B;;OAEG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB;;;;;;;;;;;;;;;;;;;OAmBG;IACH,SAAS,CAAC,EAAE,OAAO,GAAG,eAAe,CAAC;IAEtC;;;;;;;;;;;;;OAaG;IACH,WAAW,CAAC,EAAE,OAAO,GAAG,wBAAwB,CAAC;CAClD;AAMD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,OAAO,CAAC,EACtB,MAAM,EACN,UAAU,EACV,QAAQ,EACR,MAAM,EACN,WAAW,EACX,WAAmB,EACnB,SAAiB,EACjB,MAAM,EACN,SAAS,EACT,KAAK,EACL,aAAa,EAAE,MAAM,EACrB,GAAG,EACH,SAAS,EACT,GAAG,KAAK,EACT,EAAE,YAAY,+BA4dd;yBA3ee,OAAO"}
|
package/dist/SideNav/SideNav.js
CHANGED
|
@@ -38,6 +38,11 @@ import { useDevWarning } from "../hooks/useDevWarning.js";
|
|
|
38
38
|
import { useTranslator } from "../i18n/index.js";
|
|
39
39
|
import { useMergedRefs } from "../hooks/useMergedRefs.js";
|
|
40
40
|
import { LayoutChangeSnapshot } from "../motion/LayoutChangeSnapshot.js";
|
|
41
|
+
import { getFocusableElements } from "../hooks/useFocusTrap.js";
|
|
42
|
+
import { readAccessibleName } from "../utils/accessibleName.js";
|
|
43
|
+
import { moveFocusLeftBehind, noteFocusWithin } from "../utils/focusReturn.js";
|
|
44
|
+
import { useInteractionModalityTracking } from "../utils/interactionModality.js";
|
|
45
|
+
import { SideNavFocusTargetsContext } from "./SideNavFocusTargetsContext.js";
|
|
41
46
|
import { createRailLayout, playRail, snapshotRail, SideNavShellContext } from "./railMotion.js";
|
|
42
47
|
// =============================================================================
|
|
43
48
|
// Constants
|
|
@@ -189,7 +194,12 @@ export function SideNav({
|
|
|
189
194
|
// nav needs an id; a consumer's own id wins.
|
|
190
195
|
const generatedNavId = useId();
|
|
191
196
|
const navId = props.id ?? generatedNavId;
|
|
192
|
-
|
|
197
|
+
// The nav's own document's last input decides the moved focus's ring
|
|
198
|
+
// (AR3), so a SideNav in a second document tracks input there.
|
|
199
|
+
const {
|
|
200
|
+
ref: modalityRef
|
|
201
|
+
} = useInteractionModalityTracking();
|
|
202
|
+
const mergedNavRef = useMergedRefs(ref, navRef, modalityRef);
|
|
193
203
|
const collapseStateRef = useRef({
|
|
194
204
|
isCollapsed: false,
|
|
195
205
|
toggle: () => {},
|
|
@@ -257,6 +267,97 @@ export function SideNav({
|
|
|
257
267
|
motionRef.current?.rail.end();
|
|
258
268
|
motionRef.current?.endContent();
|
|
259
269
|
}, []);
|
|
270
|
+
|
|
271
|
+
// Focus on something the collapsed rail does not render would fall to the
|
|
272
|
+
// page when the rail collapses (`component:SideNav` AR3). On a row the rail
|
|
273
|
+
// keeps (its link replaced, its expand toggle or row actions gone), focus
|
|
274
|
+
// stays on that row's control; from a row the rail drops (no icon, or
|
|
275
|
+
// nested), it moves to the rail's collapse toggle, or, with none, to the
|
|
276
|
+
// nearest row the rail keeps. Rows and the toggle register through their
|
|
277
|
+
// callback refs. Rows are matched across the commit by their place among
|
|
278
|
+
// the rows the rail keeps, not by element: a resizable SideNav remounts its
|
|
279
|
+
// nav when it collapses. Focus on a header or footer control that a
|
|
280
|
+
// resizable collapse remounts goes back to the control with the same
|
|
281
|
+
// accessible name. From one the collapse removes or renames, or the resize
|
|
282
|
+
// handle, focus goes to the toggle, or, with none, to the first kept row.
|
|
283
|
+
// Focus moves only if the collapse lost it.
|
|
284
|
+
const rowsRef = useRef(new Map());
|
|
285
|
+
const togglesRef = useRef(new Set());
|
|
286
|
+
const focusTargets = useMemo(() => ({
|
|
287
|
+
registerRow: (element, isKeptInRail) => {
|
|
288
|
+
rowsRef.current.set(element, isKeptInRail);
|
|
289
|
+
return () => {
|
|
290
|
+
rowsRef.current.delete(element);
|
|
291
|
+
};
|
|
292
|
+
},
|
|
293
|
+
registerToggle: element => {
|
|
294
|
+
togglesRef.current.add(element);
|
|
295
|
+
return () => {
|
|
296
|
+
togglesRef.current.delete(element);
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
}), []);
|
|
300
|
+
const resizeWrapperRef = useRef(null);
|
|
301
|
+
const pendingFocusRef = useRef(null);
|
|
302
|
+
const noteRailFocus = useCallback(wasCollapsed => {
|
|
303
|
+
pendingFocusRef.current = null;
|
|
304
|
+
const nav = navRef.current;
|
|
305
|
+
// The resize handle sits beside the nav, in the resize wrapper, and goes
|
|
306
|
+
// with a collapse its Enter starts.
|
|
307
|
+
const focus = wasCollapsed ? null : noteFocusWithin(nav) ?? noteFocusWithin(resizeWrapperRef.current);
|
|
308
|
+
if (focus == null || nav == null) {
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
const rows = railRows(nav, rowsRef.current);
|
|
312
|
+
// The innermost row holding focus, the last in document order: a nested
|
|
313
|
+
// row's parent holds it too.
|
|
314
|
+
let index = -1;
|
|
315
|
+
rows.forEach((row, at) => {
|
|
316
|
+
if (row.contains(focus.element)) {
|
|
317
|
+
index = at;
|
|
318
|
+
}
|
|
319
|
+
});
|
|
320
|
+
let outside = null;
|
|
321
|
+
if (index < 0) {
|
|
322
|
+
const controls = controlsOutsideRows(nav, rows);
|
|
323
|
+
const at = controls.findIndex(control => control.contains(focus.element));
|
|
324
|
+
outside = at < 0 ? null : {
|
|
325
|
+
name: controlName(controls[at])
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
pendingFocusRef.current = {
|
|
329
|
+
focus,
|
|
330
|
+
outside,
|
|
331
|
+
wasOnToggle: [...togglesRef.current].some(toggle => toggle.contains(focus.element)),
|
|
332
|
+
isRowKept: index >= 0 && rowsRef.current.get(rows[index]) === true,
|
|
333
|
+
keptBefore: rows.slice(0, Math.max(index, 0)).filter(row => rowsRef.current.get(row) === true).length
|
|
334
|
+
};
|
|
335
|
+
}, []);
|
|
336
|
+
useLayoutEffect(() => {
|
|
337
|
+
const pending = pendingFocusRef.current;
|
|
338
|
+
pendingFocusRef.current = null;
|
|
339
|
+
const nav = navRef.current;
|
|
340
|
+
if (pending == null || !collapsed || nav == null) {
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
// Collapsed, every row the nav renders is one the rail keeps, in order.
|
|
344
|
+
const kept = railRows(nav, rowsRef.current);
|
|
345
|
+
const controlOf = row => row == null ? undefined : getFocusableElements(row)[0];
|
|
346
|
+
const toggle = [...togglesRef.current].find(element => nav.contains(element));
|
|
347
|
+
let sameOutside;
|
|
348
|
+
if (pending.wasOnToggle) {
|
|
349
|
+
sameOutside = toggle;
|
|
350
|
+
} else if (pending.outside != null) {
|
|
351
|
+
const controls = controlsOutsideRows(nav, kept);
|
|
352
|
+
sameOutside = controls.find(control => controlName(control) === pending.outside?.name);
|
|
353
|
+
}
|
|
354
|
+
const target = pending.isRowKept ? controlOf(kept[pending.keptBefore]) : sameOutside ?? toggle ?? controlOf(kept[pending.keptBefore - 1]) ?? controlOf(kept[pending.keptBefore]);
|
|
355
|
+
moveFocusLeftBehind(pending.focus, target);
|
|
356
|
+
}, [collapsed]);
|
|
357
|
+
const beforeRailChange = useCallback(wasCollapsed => {
|
|
358
|
+
noteRailFocus(wasCollapsed);
|
|
359
|
+
measureRail(wasCollapsed);
|
|
360
|
+
}, [noteRailFocus, measureRail]);
|
|
260
361
|
const toggle = useCallback(() => {
|
|
261
362
|
const next = !collapsed;
|
|
262
363
|
|
|
@@ -459,6 +560,7 @@ export function SideNav({
|
|
|
459
560
|
// Uses ResizeHandle in overlay mode so the handle sits inside
|
|
460
561
|
// the panel's overflow: clip bounds.
|
|
461
562
|
const rail = showResizeHandle ? /*#__PURE__*/_jsxs("div", {
|
|
563
|
+
ref: resizeWrapperRef,
|
|
462
564
|
...{
|
|
463
565
|
className: "x1n2onr6 x78zum5 x2lah0s x5yr21d x7giv3"
|
|
464
566
|
},
|
|
@@ -479,15 +581,36 @@ export function SideNav({
|
|
|
479
581
|
const content = /*#__PURE__*/_jsxs(_Fragment, {
|
|
480
582
|
children: [/*#__PURE__*/_jsx(LayoutChangeSnapshot, {
|
|
481
583
|
value: collapsed,
|
|
482
|
-
onBeforeChange:
|
|
584
|
+
onBeforeChange: beforeRailChange
|
|
483
585
|
}), rail]
|
|
484
586
|
});
|
|
485
587
|
if (isCollapsible) {
|
|
486
|
-
return /*#__PURE__*/_jsx(
|
|
487
|
-
value:
|
|
488
|
-
children:
|
|
588
|
+
return /*#__PURE__*/_jsx(SideNavFocusTargetsContext, {
|
|
589
|
+
value: focusTargets,
|
|
590
|
+
children: /*#__PURE__*/_jsx(SideNavCollapseContext, {
|
|
591
|
+
value: collapseContext,
|
|
592
|
+
children: content
|
|
593
|
+
})
|
|
489
594
|
});
|
|
490
595
|
}
|
|
491
596
|
return content;
|
|
492
597
|
}
|
|
598
|
+
|
|
599
|
+
/** The focusable controls in `nav` outside every row: header and footer. */
|
|
600
|
+
function controlsOutsideRows(nav, rows) {
|
|
601
|
+
return getFocusableElements(nav).filter(control => !rows.some(row => row.contains(control)));
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
/** A control's accessible name, so a re-rendered control is found again. */
|
|
605
|
+
function controlName(control) {
|
|
606
|
+
return readAccessibleName(control) ?? '';
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
/**
|
|
610
|
+
* The registered rows inside `nav`, in document order, leaving out rows in a
|
|
611
|
+
* collapsed item's flyout, which is a layer of its own.
|
|
612
|
+
*/
|
|
613
|
+
function railRows(nav, rows) {
|
|
614
|
+
return [...rows.keys()].filter(row => nav.contains(row) && row.closest('[popover]') == null).sort((a, b) => a.compareDocumentPosition(b) & Node.DOCUMENT_POSITION_FOLLOWING ? -1 : 1);
|
|
615
|
+
}
|
|
493
616
|
SideNav.displayName = 'SideNav';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SideNavCollapseButton.d.ts","sourceRoot":"","sources":["../../src/SideNav/SideNavCollapseButton.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"SideNavCollapseButton.d.ts","sourceRoot":"","sources":["../../src/SideNav/SideNavCollapseButton.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,EAAmB,KAAK,SAAS,EAAC,MAAM,OAAO,CAAC;AAK9D,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAE5C,OAAO,EAGL,KAAK,4BAA4B,EACjC,KAAK,+BAA+B,EACrC,MAAM,0BAA0B,CAAC;AAKlC,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,4BAA4B,CAAC;AAkC5D,MAAM,WAAW,0BAA2B,SAAQ,SAAS,CAAC,iBAAiB,CAAC;IAC9E,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC;IACnC;;;;OAIG;IACH,WAAW,CAAC,EAAE,4BAA4B,CAAC;IAE3C;;;;OAIG;IACH,SAAS,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC,+BAA+B,GAAG,IAAI,CAAC,CAAC;IAEpE;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf;;;;OAIG;IACH,IAAI,CAAC,EAAE,WAAW,CAAC;IAEnB;;OAEG;IACH,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB;AAMD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,qBAAqB,CAAC,EACpC,GAAG,EACH,WAAW,EACX,SAAS,EACT,KAAK,EACL,IAAI,EACJ,QAAQ,EACR,OAAO,EAAE,WAAW,EACpB,GAAG,KAAK,EACT,EAAE,0BAA0B,4BA+D5B;yBAxEe,qBAAqB"}
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* - /packages/core/src/SideNav/index.ts
|
|
17
17
|
* - /packages/cli/assets/templates/blocks/components/SideNav/ (showcase blocks)
|
|
18
18
|
*/
|
|
19
|
-
import React, { useCallback } from 'react';
|
|
19
|
+
import React, { use, useCallback } from 'react';
|
|
20
20
|
import * as stylex from '@stylexjs/stylex';
|
|
21
21
|
import "../theme/tokens.stylex.js";
|
|
22
22
|
import { durationVars, easeVars } from "../theme/tokens.stylex.js";
|
|
@@ -26,6 +26,8 @@ import { composeEventHandlers, rtlStyles } from "../utils/index.js";
|
|
|
26
26
|
import { useSideNavCollapse } from "./SideNavCollapseContext.js";
|
|
27
27
|
import { useAppShellMobile } from "../AppShell/AppShellMobileContext.js";
|
|
28
28
|
import { useTranslator } from "../i18n/index.js";
|
|
29
|
+
import { useMergedRefs } from "../hooks/useMergedRefs.js";
|
|
30
|
+
import { SideNavFocusTargetsContext } from "./SideNavFocusTargetsContext.js";
|
|
29
31
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
30
32
|
// =============================================================================
|
|
31
33
|
// Styles
|
|
@@ -102,6 +104,13 @@ export function SideNavCollapseButton({
|
|
|
102
104
|
const {
|
|
103
105
|
isMobile
|
|
104
106
|
} = useAppShellMobile();
|
|
107
|
+
// A toggle inside its SideNav registers there, so a collapse that removes
|
|
108
|
+
// the focused row can hand focus to it (`component:SideNav` AR3). One wired
|
|
109
|
+
// to state outside the SideNav (`collapsible`, `handleRef`) does not.
|
|
110
|
+
const focusTargets = use(SideNavFocusTargetsContext);
|
|
111
|
+
const isInSideNav = focusTargets != null && collapsible == null && handleRef == null;
|
|
112
|
+
const registerToggle = useCallback(element => element == null || !isInSideNav || focusTargets == null ? undefined : focusTargets.registerToggle(element), [focusTargets, isInSideNav]);
|
|
113
|
+
const mergedRef = useMergedRefs(ref, registerToggle);
|
|
105
114
|
|
|
106
115
|
// Hide when not collapsible, or when in mobile mode (sidenav is in
|
|
107
116
|
// the mobile drawer — collapse doesn't apply there)
|
|
@@ -109,7 +118,7 @@ export function SideNavCollapseButton({
|
|
|
109
118
|
return null;
|
|
110
119
|
}
|
|
111
120
|
return /*#__PURE__*/_jsx(Button, {
|
|
112
|
-
ref:
|
|
121
|
+
ref: mergedRef,
|
|
113
122
|
label: label ?? (isCollapsed ? t('@astryx.sideNavCollapseButton.expandSidebar') : t('@astryx.sideNavCollapseButton.collapseSidebar')),
|
|
114
123
|
variant: "ghost",
|
|
115
124
|
size: size,
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface SideNavFocusTargets {
|
|
2
|
+
/**
|
|
3
|
+
* Registers a row's root element, and whether the collapsed rail renders
|
|
4
|
+
* the row (it has an icon and is not nested); returns its unregister.
|
|
5
|
+
*/
|
|
6
|
+
registerRow: (element: HTMLElement, isKeptInRail: boolean) => () => void;
|
|
7
|
+
/** Registers a collapse toggle rendered inside the SideNav. */
|
|
8
|
+
registerToggle: (element: HTMLElement) => () => void;
|
|
9
|
+
}
|
|
10
|
+
export declare const SideNavFocusTargetsContext: import("react").Context<SideNavFocusTargets | null>;
|
|
11
|
+
//# sourceMappingURL=SideNavFocusTargetsContext.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SideNavFocusTargetsContext.d.ts","sourceRoot":"","sources":["../../src/SideNav/SideNavFocusTargetsContext.ts"],"names":[],"mappings":"AAgBA,MAAM,WAAW,mBAAmB;IAClC;;;OAGG;IACH,WAAW,EAAE,CAAC,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,OAAO,KAAK,MAAM,IAAI,CAAC;IACzE,+DAA+D;IAC/D,cAAc,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,MAAM,IAAI,CAAC;CACtD;AAED,eAAO,MAAM,0BAA0B,qDACU,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
'use client';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @file SideNavFocusTargetsContext.ts
|
|
7
|
+
* @input The SideNav rail's rows and its collapse toggle, registered by their
|
|
8
|
+
* callback refs
|
|
9
|
+
* @output Where focus goes when collapsing the rail removes the focused row
|
|
10
|
+
* @position Internal to SideNav: not exported from the SideNav entry. SideNav
|
|
11
|
+
* provides it; SideNavItem registers its row and SideNavCollapseButton,
|
|
12
|
+
* rendered inside the SideNav, registers itself (`component:SideNav` AR3).
|
|
13
|
+
*/
|
|
14
|
+
import { createContext } from 'react';
|
|
15
|
+
export const SideNavFocusTargetsContext = /*#__PURE__*/createContext(null);
|
|
16
|
+
SideNavFocusTargetsContext.displayName = 'SideNavFocusTargetsContext';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SideNavItem.d.ts","sourceRoot":"","sources":["../../src/SideNav/SideNavItem.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,OAAO,EAOL,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAYf,OAAO,EAAuB,KAAK,QAAQ,EAAC,MAAM,SAAS,CAAC;AAE5D,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,eAAe,CAAC;AAIrD,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAE5C,OAAO,EAAgB,KAAK,WAAW,EAAC,MAAM,iCAAiC,CAAC;
|
|
1
|
+
{"version":3,"file":"SideNavItem.d.ts","sourceRoot":"","sources":["../../src/SideNav/SideNavItem.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,OAAO,EAOL,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAYf,OAAO,EAAuB,KAAK,QAAQ,EAAC,MAAM,SAAS,CAAC;AAE5D,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,eAAe,CAAC;AAIrD,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAE5C,OAAO,EAAgB,KAAK,WAAW,EAAC,MAAM,iCAAiC,CAAC;AA8ThF,MAAM,WAAW,gBAAiB,SAAQ,SAAS,CAAC,WAAW,CAAC;IAC9D,wCAAwC;IACxC,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;IAC7B;;;;OAIG;IACH,EAAE,CAAC,EAAE,iBAAiB,CAAC;IACvB;;OAEG;IACH,KAAK,EAAE,MAAM,CAAC;IACd;;OAEG;IACH,IAAI,CAAC,EAAE,SAAS,GAAG,QAAQ,CAAC;IAC5B;;OAEG;IACH,YAAY,CAAC,EAAE,SAAS,GAAG,QAAQ,CAAC;IACpC;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;OAEG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;OAEG;IACH,OAAO,CAAC,EAAE,CAAC,CAAC,EAAE,KAAK,CAAC,UAAU,KAAK,IAAI,CAAC;IACxC;;;;OAIG;IACH,UAAU,CAAC,EAAE,SAAS,CAAC;IACvB;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,EAAE,SAAS,CAAC;IACpB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB;;;;;;;;;;OAUG;IACH,WAAW,CAAC,EACR,OAAO,GACP;QACE,kBAAkB,CAAC,EAAE,OAAO,CAAC;QAC7B,WAAW,CAAC,EAAE,OAAO,CAAC;QACtB,iBAAiB,CAAC,EAAE,CAAC,WAAW,EAAE,OAAO,KAAK,IAAI,CAAC;KACpD,CAAC;IACN;;;OAGG;IACH,IAAI,CAAC,EAAE,WAAW,CAAC;CACpB;AAMD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,WAAW,CAAC,EAC1B,EAAE,EACF,KAAK,EACL,IAAI,EACJ,YAAY,EACZ,UAAkB,EAClB,UAAkB,EAClB,IAAI,EACJ,OAAO,EACP,UAAU,EACV,OAAO,EACP,QAAQ,EACR,WAAW,EAAE,eAAe,EAC5B,IAAW,EACX,aAAa,EAAE,MAAM,EACrB,GAAG,EACH,MAAM,EACN,GAAG,IAAI,EACR,EAAE,gBAAgB,sCAgdlB;yBAlee,WAAW"}
|
|
@@ -55,6 +55,7 @@ import { themeProps } from "../utils/themeProps.js";
|
|
|
55
55
|
import { disabledInteractionProps } from "../utils/disabledInteraction.js";
|
|
56
56
|
import { useTranslator } from "../i18n/index.js";
|
|
57
57
|
import { useMergedRefs } from "../hooks/useMergedRefs.js";
|
|
58
|
+
import { SideNavFocusTargetsContext } from "./SideNavFocusTargetsContext.js";
|
|
58
59
|
// =============================================================================
|
|
59
60
|
// Styles
|
|
60
61
|
// =============================================================================
|
|
@@ -350,6 +351,15 @@ export function SideNavItem({
|
|
|
350
351
|
});
|
|
351
352
|
const railLeafRef = useMergedRefs(ref, railTooltip.ref);
|
|
352
353
|
|
|
354
|
+
// The row's root registers with its SideNav, so a collapse that removes the
|
|
355
|
+
// focused row can hand focus to the toggle or the nearest row still in the
|
|
356
|
+
// rail (`component:SideNav` AR3).
|
|
357
|
+
const focusTargets = use(SideNavFocusTargetsContext);
|
|
358
|
+
// The collapsed rail renders a top-level row with an icon (FR2).
|
|
359
|
+
const isKeptInRail = !!icon && depth === 0;
|
|
360
|
+
const rowRef = useCallback(element => element == null || focusTargets == null ? undefined : focusTargets.registerRow(element, isKeptInRail), [focusTargets, isKeptInRail]);
|
|
361
|
+
const rowTooltipRef = useMergedRefs(rowRef, railTooltip.ref);
|
|
362
|
+
|
|
353
363
|
// Collapse state for items with children
|
|
354
364
|
const itemCollapsibleConfig = useMemo(() => typeof itemCollapsible === 'object' ? itemCollapsible : {}, [itemCollapsible]);
|
|
355
365
|
const isItemCollapsible = hasChildren && itemCollapsible !== false;
|
|
@@ -447,6 +457,7 @@ export function SideNavItem({
|
|
|
447
457
|
// Items with children: popover trigger + popover
|
|
448
458
|
if (hasChildren) {
|
|
449
459
|
return /*#__PURE__*/_jsxs("div", {
|
|
460
|
+
ref: rowRef,
|
|
450
461
|
...stylex.props(styles.root, xstyle),
|
|
451
462
|
children: [/*#__PURE__*/_jsx("button", {
|
|
452
463
|
ref: mergedTriggerRef,
|
|
@@ -511,7 +522,7 @@ export function SideNavItem({
|
|
|
511
522
|
children: collapsedIcon
|
|
512
523
|
});
|
|
513
524
|
return /*#__PURE__*/_jsxs("div", {
|
|
514
|
-
ref: isRowTrigger ?
|
|
525
|
+
ref: isRowTrigger ? rowTooltipRef : rowRef,
|
|
515
526
|
...stylex.props(styles.root, xstyle),
|
|
516
527
|
children: [collapsedElement, railTooltip.renderTooltip(label)]
|
|
517
528
|
});
|
|
@@ -663,6 +674,7 @@ export function SideNavItem({
|
|
|
663
674
|
});
|
|
664
675
|
}
|
|
665
676
|
const item = /*#__PURE__*/_jsxs("div", {
|
|
677
|
+
ref: rowRef,
|
|
666
678
|
...stylex.props(styles.root, xstyle),
|
|
667
679
|
children: [itemElement, hasChildren && !isCollapsed && /*#__PURE__*/_jsx("div", {
|
|
668
680
|
id: `${id}-children`,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useFocusTrap.d.ts","sourceRoot":"","sources":["../../src/hooks/useFocusTrap.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"useFocusTrap.d.ts","sourceRoot":"","sources":["../../src/hooks/useFocusTrap.ts"],"names":[],"mappings":"AA+BA;;;;;;;;GAQG;AACH,wBAAgB,wBAAwB,IAAI,OAAO,CAElD;AAED;;;;;;GAMG;AACH,wBAAgB,qCAAqC,CAAC,QAAQ,EAAE,OAAO,GAAG,IAAI,CAU7E;AA+BD;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,WAAW,GAAG,WAAW,EAAE,CAI1E;AA8DD;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC;;OAEG;IACH,QAAQ,EAAE,OAAO,CAAC;IAElB;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB,CAAC,CAAC,SAAS,WAAW,GAAG,WAAW;IACrE;;OAEG;IACH,YAAY,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;IAExC;;OAEG;IACH,UAAU,EAAE,MAAM,IAAI,CAAC;CACxB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAgB,YAAY,CAAC,CAAC,SAAS,WAAW,GAAG,WAAW,EAC9D,OAAO,EAAE,mBAAmB,GAC3B,kBAAkB,CAAC,CAAC,CAAC,CAuQvB"}
|