@astryxdesign/core 0.7.2-canary.5c75a75 → 0.7.2-canary.81b8df4
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.js +1 -1
- package/dist/DropdownMenu/DropdownMenuSubMenu.js +4 -4
- package/dist/Layer/layerViewportInset.stylex.d.ts +6 -2
- package/dist/Layer/layerViewportInset.stylex.d.ts.map +1 -1
- package/dist/Layer/layerViewportInset.stylex.js +6 -2
- package/dist/Layer/useLayer.d.ts.map +1 -1
- package/dist/Layer/useLayer.js +6 -6
- package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
- package/dist/MultiSelector/MultiSelector.js +22 -8
- package/dist/MultiSelector/OptionActions.a11y.chromium.spec.js +100 -2
- package/dist/Popover/Popover.js +2 -2
- package/dist/astryx.css +10 -10
- package/dist/utils/focusOutline.stylex.d.ts +4 -0
- package/dist/utils/focusOutline.stylex.d.ts.map +1 -1
- package/dist/utils/focusOutline.stylex.js +4 -0
- package/package.json +3 -3
- package/src/DropdownMenu/DropdownMenu.tsx +2 -2
- package/src/DropdownMenu/DropdownMenuSubMenu.tsx +2 -2
- package/src/Layer/layerViewportInset.source-build.test.mjs +90 -0
- package/src/Layer/layerViewportInset.stylex.ts +6 -2
- package/src/Layer/useLayer.tsx +14 -12
- package/src/MultiSelector/MultiSelector.test.tsx +159 -0
- package/src/MultiSelector/MultiSelector.tsx +40 -7
- package/src/MultiSelector/OptionActions.a11y.chromium.spec.ts +112 -2
- package/src/Popover/Popover.test.tsx +2 -2
- package/src/Popover/Popover.tsx +4 -4
- package/src/utils/focusOutline.stylex.ts +4 -0
|
@@ -659,16 +659,16 @@ export function DropdownMenuSubMenu(props) {
|
|
|
659
659
|
...menuPress.menuProps,
|
|
660
660
|
...mergeProps(themeProps('dropdown-menu'), {
|
|
661
661
|
0: {
|
|
662
|
-
className: "x9f619 x78zum5 xdt5ytf x1lsbc85
|
|
662
|
+
className: "x9f619 x78zum5 xdt5ytf x1lsbc85 xc0b1yq x1uh1i8a x1fcsqxe xgory14 x9epnlk x1n97fys x1prclbq x1i5ehqx x1hc1fzr x19991ni xuedmi6 xlr8y92 xpdipgo x87ps6o x1j4r261"
|
|
663
663
|
},
|
|
664
664
|
2: {
|
|
665
|
-
className: "x9f619 x78zum5 xdt5ytf x1lsbc85
|
|
665
|
+
className: "x9f619 x78zum5 xdt5ytf x1lsbc85 xc0b1yq x1uh1i8a x1fcsqxe xgory14 x9epnlk x1n97fys x1prclbq x1i5ehqx x1hc1fzr x19991ni xuedmi6 xlr8y92 xpdipgo x87ps6o xge0x1e xish69e"
|
|
666
666
|
},
|
|
667
667
|
1: {
|
|
668
|
-
className: "x9f619 x78zum5 xdt5ytf x1lsbc85
|
|
668
|
+
className: "x9f619 x78zum5 xdt5ytf x1lsbc85 xc0b1yq x1uh1i8a x1fcsqxe xgory14 x9epnlk x1n97fys x1prclbq x1i5ehqx x1hc1fzr x19991ni xuedmi6 xlr8y92 xpdipgo x87ps6o x1j4r261 x1odjw0f x6ikm8r xish69e"
|
|
669
669
|
},
|
|
670
670
|
3: {
|
|
671
|
-
className: "x9f619 x78zum5 xdt5ytf x1lsbc85
|
|
671
|
+
className: "x9f619 x78zum5 xdt5ytf x1lsbc85 xc0b1yq x1uh1i8a x1fcsqxe xgory14 x9epnlk x1n97fys x1prclbq x1i5ehqx x1hc1fzr x19991ni xuedmi6 xlr8y92 xpdipgo x87ps6o xge0x1e x1odjw0f x6ikm8r xish69e"
|
|
672
672
|
}
|
|
673
673
|
}[!!hasOverflow << 1 | !!hasOverflow << 0]),
|
|
674
674
|
children: nestedContent
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The viewport gutter and the caps derived from it. Every value has an
|
|
3
|
-
* `env()`-less fallback for engines without safe-area support; pair them
|
|
4
|
-
* `stylex.firstThatWorks(cap
|
|
3
|
+
* `env()`-less fallback for engines without safe-area support; pair them as
|
|
4
|
+
* `stylex.firstThatWorks(`calc(${cap})`, `calc(${capFallback})`)`. Pass
|
|
5
|
+
* each inside a `calc()` (or another function), never as a bare reference: a
|
|
6
|
+
* constant reference compiles to a `var()`, and `firstThatWorks` folds two
|
|
7
|
+
* `var()` arguments into one `var(cap, capFallback)` whose constant is never
|
|
8
|
+
* inlined, so every engine would take the fallback.
|
|
5
9
|
*/
|
|
6
10
|
export declare const layerViewportInset: {
|
|
7
11
|
/** Gutter from the block-start viewport edge. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"layerViewportInset.stylex.d.ts","sourceRoot":"","sources":["../../src/Layer/layerViewportInset.stylex.ts"],"names":[],"mappings":"AAwDA
|
|
1
|
+
{"version":3,"file":"layerViewportInset.stylex.d.ts","sourceRoot":"","sources":["../../src/Layer/layerViewportInset.stylex.ts"],"names":[],"mappings":"AAwDA;;;;;;;;GAQG;AACH,eAAO,MAAM,kBAAkB;IAC7B,iDAAiD;;IAEjD,+CAA+C;;IAE/C,+CAA+C;;IAE/C,gFAAgF;;;;;;IAMhF,iFAAiF;;;IAGjF,gFAAgF;;;CAGhF,CAAC"}
|
|
@@ -54,8 +54,12 @@ const GUTTER_BLOCK = `calc(max(${SPACING}, env(safe-area-inset-top, 0px), env(sa
|
|
|
54
54
|
|
|
55
55
|
/**
|
|
56
56
|
* The viewport gutter and the caps derived from it. Every value has an
|
|
57
|
-
* `env()`-less fallback for engines without safe-area support; pair them
|
|
58
|
-
* `stylex.firstThatWorks(cap
|
|
57
|
+
* `env()`-less fallback for engines without safe-area support; pair them as
|
|
58
|
+
* `stylex.firstThatWorks(`calc(${cap})`, `calc(${capFallback})`)`. Pass
|
|
59
|
+
* each inside a `calc()` (or another function), never as a bare reference: a
|
|
60
|
+
* constant reference compiles to a `var()`, and `firstThatWorks` folds two
|
|
61
|
+
* `var()` arguments into one `var(cap, capFallback)` whose constant is never
|
|
62
|
+
* inlined, so every engine would take the fallback.
|
|
59
63
|
*/
|
|
60
64
|
export const layerViewportInset = {
|
|
61
65
|
gutterBlockStart: "calc(max(var(--spacing-4), env(safe-area-inset-top, 0px)) + var(--astryx-layer-inset-block-start, 0px))",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useLayer.d.ts","sourceRoot":"","sources":["../../src/Layer/useLayer.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,EAOZ,KAAK,SAAS,EACd,KAAK,WAAW,EACjB,MAAM,OAAO,CAAC;AAEf,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"useLayer.d.ts","sourceRoot":"","sources":["../../src/Layer/useLayer.tsx"],"names":[],"mappings":"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"}
|
package/dist/Layer/useLayer.js
CHANGED
|
@@ -104,24 +104,24 @@ const styles = {
|
|
|
104
104
|
},
|
|
105
105
|
viewportFit: {
|
|
106
106
|
kB7OPa: "x9f619",
|
|
107
|
-
ks0D6T: "
|
|
108
|
-
kskxy: "
|
|
107
|
+
ks0D6T: "xc0b1yq",
|
|
108
|
+
kskxy: "x3v4n1",
|
|
109
109
|
$$css: true
|
|
110
110
|
},
|
|
111
111
|
gutterInlineEnd: {
|
|
112
|
-
k71WvV: "
|
|
112
|
+
k71WvV: "x144kxal",
|
|
113
113
|
$$css: true
|
|
114
114
|
},
|
|
115
115
|
gutterInlineStart: {
|
|
116
|
-
keTefX: "
|
|
116
|
+
keTefX: "xpdk1p0",
|
|
117
117
|
$$css: true
|
|
118
118
|
},
|
|
119
119
|
gutterBlockEnd: {
|
|
120
|
-
k1K539: "
|
|
120
|
+
k1K539: "x3z50q0",
|
|
121
121
|
$$css: true
|
|
122
122
|
},
|
|
123
123
|
gutterBlockStart: {
|
|
124
|
-
keoZOQ: "
|
|
124
|
+
keoZOQ: "x1j6tnbc",
|
|
125
125
|
$$css: true
|
|
126
126
|
}
|
|
127
127
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MultiSelector.d.ts","sourceRoot":"","sources":["../../src/MultiSelector/MultiSelector.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,EAUZ,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAGf,OAAO,EAAuB,KAAK,QAAQ,EAAC,MAAM,SAAS,CAAC;AAE5D,OAAO,EAKL,KAAK,kBAAkB,EACxB,MAAM,UAAU,CAAC;AAOlB,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"MultiSelector.d.ts","sourceRoot":"","sources":["../../src/MultiSelector/MultiSelector.tsx"],"names":[],"mappings":"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"}
|
|
@@ -29,13 +29,12 @@ import { PanelSearchInput } from "../Field/PanelSearchInput.js";
|
|
|
29
29
|
import { CheckboxInput } from "../CheckboxInput/index.js";
|
|
30
30
|
import { Badge } from "../Badge/index.js";
|
|
31
31
|
import "../theme/tokens.stylex.js";
|
|
32
|
-
import { colorVars, sizeVars, spacingVars, radiusVars, durationVars, easeVars, typographyVars, fontWeightVars, typeScaleVars } from "../theme/tokens.stylex.js";
|
|
32
|
+
import { colorVars, focusVars, sizeVars, spacingVars, radiusVars, durationVars, easeVars, typographyVars, fontWeightVars, typeScaleVars } from "../theme/tokens.stylex.js";
|
|
33
33
|
import { isOptionData, isDivider, isSection, normalizeOption, getSelectableOptions } from "../Selector/utils.js";
|
|
34
34
|
import { useMultiCombobox } from "./hooks.js";
|
|
35
35
|
import { getInputARIA, isImeKeyEvent, mergeProps } from "../utils/index.js";
|
|
36
36
|
import { devWarn, warnOnce } from "../utils/devWarning.js";
|
|
37
|
-
import "../utils/
|
|
38
|
-
import { FOCUS_OUTLINE_PARTS } from "../utils/focusOutline.stylex.js";
|
|
37
|
+
import { getInteractionModality, useInteractionModalityTracking } from "../utils/interactionModality.js";
|
|
39
38
|
import { useAnnounce } from "../hooks/useAnnounce.js";
|
|
40
39
|
import { FOCUSABLE_SELECTOR } from "../hooks/focusableSelector.js";
|
|
41
40
|
import { useAnnounceRenderedText } from "../hooks/useAnnounceRenderedText.js";
|
|
@@ -894,8 +893,8 @@ export function MultiSelector({
|
|
|
894
893
|
getActionCellId,
|
|
895
894
|
activeDescendantId,
|
|
896
895
|
onTriggerClick,
|
|
897
|
-
onKeyDown,
|
|
898
|
-
onItemMouseEnter
|
|
896
|
+
onKeyDown: onComboboxKeyDown,
|
|
897
|
+
onItemMouseEnter: onComboboxItemMouseEnter
|
|
899
898
|
} = useMultiCombobox({
|
|
900
899
|
selectableItems: sortedItems,
|
|
901
900
|
isDisabled: isDisabled || isEffectivelyReadOnly,
|
|
@@ -918,6 +917,21 @@ export function MultiSelector({
|
|
|
918
917
|
}, [getActionCellIdForActivate])
|
|
919
918
|
});
|
|
920
919
|
|
|
920
|
+
// A hover moves the highlight too, but the focus ring follows the shared
|
|
921
|
+
// interaction modality (architecture:interaction-modality INV1): it is read
|
|
922
|
+
// whenever a key or the pointer moves the highlight, so an action cell
|
|
923
|
+
// highlighted after pointer input keeps the hover treatment and no ring.
|
|
924
|
+
useInteractionModalityTracking();
|
|
925
|
+
const [isKeyboardHighlight, setIsKeyboardHighlight] = useState(() => getInteractionModality() === 'keyboard');
|
|
926
|
+
const onKeyDown = useCallback(event => {
|
|
927
|
+
onComboboxKeyDown(event);
|
|
928
|
+
setIsKeyboardHighlight(getInteractionModality() === 'keyboard');
|
|
929
|
+
}, [onComboboxKeyDown]);
|
|
930
|
+
const onItemMouseEnter = useCallback((...args) => {
|
|
931
|
+
onComboboxItemMouseEnter(...args);
|
|
932
|
+
setIsKeyboardHighlight(getInteractionModality() === 'keyboard');
|
|
933
|
+
}, [onComboboxItemMouseEnter]);
|
|
934
|
+
|
|
921
935
|
// The trigger button owns its click so a consumer `onClick`, which lands on
|
|
922
936
|
// the button, composes after the built-in toggle. A press elsewhere in the
|
|
923
937
|
// control box (padding, start icon, chevron) still opens the panel.
|
|
@@ -1179,14 +1193,14 @@ export function MultiSelector({
|
|
|
1179
1193
|
className: "x78zum5 x6s0dn4 x2lah0s xh6dtrn"
|
|
1180
1194
|
},
|
|
1181
1195
|
1: {
|
|
1182
|
-
className: "x78zum5 x6s0dn4 x2lah0s xh6dtrn
|
|
1196
|
+
className: "x78zum5 x6s0dn4 x2lah0s xh6dtrn xak0ai5 x9ssot5 x19bwoy8 x1eqjuhu"
|
|
1183
1197
|
}
|
|
1184
|
-
}[!!(isHighlighted && highlightedCell === 'action') << 0],
|
|
1198
|
+
}[!!(isHighlighted && highlightedCell === 'action' && isKeyboardHighlight) << 0],
|
|
1185
1199
|
children: isSelectAll || isCreate ? null : item.action
|
|
1186
1200
|
})]
|
|
1187
1201
|
}) : content
|
|
1188
1202
|
}, item.value);
|
|
1189
|
-
}, [isGrid, highlightedCell, getActionCellId, renderOption, indicatorPosition, highlightedIndex, optimisticValue, allEnabledSelected, selectAllState, selectAllLabel, getItemId, handleNavigableToggle, onItemMouseEnter, size, t]);
|
|
1203
|
+
}, [isGrid, highlightedCell, isKeyboardHighlight, getActionCellId, renderOption, indicatorPosition, highlightedIndex, optimisticValue, allEnabledSelected, selectAllState, selectAllLabel, getItemId, handleNavigableToggle, onItemMouseEnter, size, t]);
|
|
1190
1204
|
|
|
1191
1205
|
// Render all options. Uses sortedItems as the single source of truth for
|
|
1192
1206
|
// item order — no independent sorting here. Structural elements (dividers,
|
|
@@ -6,8 +6,10 @@
|
|
|
6
6
|
* built Storybook.
|
|
7
7
|
* @output Real-engine proof for option actions: the action is visible at rest
|
|
8
8
|
* beside its row (`spec:AST-058` FR8), the keyboard reaches it through the
|
|
9
|
-
* inline-end arrow in both directions and fires it with Enter,
|
|
10
|
-
*
|
|
9
|
+
* inline-end arrow in both directions and fires it with Enter, the active
|
|
10
|
+
* action cell draws the standard focus ring when the shared input modality
|
|
11
|
+
* is keyboard and not after pointer input, and the highlight follows a real
|
|
12
|
+
* mouse across rows and actions.
|
|
11
13
|
* @position Run with `pnpm test:a11y-contract`; paint, pointer paths and the
|
|
12
14
|
* active descendant as an engine exposes them are not jsdom claims.
|
|
13
15
|
*/
|
|
@@ -105,6 +107,102 @@ test('the keyboard reaches an action through the inline-end arrow and fires it w
|
|
|
105
107
|
await search.press('ArrowLeft');
|
|
106
108
|
await expect.poll(async () => activeDescendant(page)).toBe('row:Bug');
|
|
107
109
|
});
|
|
110
|
+
test('the active action cell draws the standard focus ring', async ({
|
|
111
|
+
page
|
|
112
|
+
}) => {
|
|
113
|
+
await page.goto(`${storybook.origin}/iframe.html?id=core-multiselector--row-actions&viewMode=story`);
|
|
114
|
+
const search = page.getByRole('combobox');
|
|
115
|
+
await expect(search).toBeFocused();
|
|
116
|
+
await search.press('ArrowDown'); // select-all
|
|
117
|
+
await search.press('ArrowDown'); // Feature (no action)
|
|
118
|
+
await search.press('ArrowDown'); // Bug
|
|
119
|
+
await search.press('ArrowRight');
|
|
120
|
+
await expect.poll(async () => activeDescendant(page)).toBe('gridcell:Edit Bug');
|
|
121
|
+
const ring = await page.evaluate(() => {
|
|
122
|
+
const id = document.activeElement?.getAttribute('aria-activedescendant');
|
|
123
|
+
const cell = id ? document.getElementById(id) : null;
|
|
124
|
+
if (!cell) {
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
const cs = getComputedStyle(cell);
|
|
128
|
+
// The ring the focus tokens describe, resolved by the browser on a probe.
|
|
129
|
+
const probe = document.createElement('div');
|
|
130
|
+
probe.style.cssText = 'outline-width:var(--focus-outline-width);outline-style:var(--focus-outline-style);outline-color:var(--focus-outline-color);outline-offset:var(--focus-outline-offset)';
|
|
131
|
+
document.body.appendChild(probe);
|
|
132
|
+
const want = getComputedStyle(probe);
|
|
133
|
+
const result = {
|
|
134
|
+
style: cs.outlineStyle,
|
|
135
|
+
width: cs.outlineWidth,
|
|
136
|
+
color: cs.outlineColor,
|
|
137
|
+
offset: cs.outlineOffset,
|
|
138
|
+
wantStyle: want.outlineStyle,
|
|
139
|
+
wantWidth: want.outlineWidth,
|
|
140
|
+
wantColor: want.outlineColor,
|
|
141
|
+
wantOffset: want.outlineOffset
|
|
142
|
+
};
|
|
143
|
+
probe.remove();
|
|
144
|
+
return result;
|
|
145
|
+
});
|
|
146
|
+
expect(ring).not.toBeNull();
|
|
147
|
+
expect(ring.style).not.toBe('none');
|
|
148
|
+
expect(ring.width).not.toBe('0px');
|
|
149
|
+
expect(ring.style).toBe(ring.wantStyle);
|
|
150
|
+
expect(ring.width).toBe(ring.wantWidth);
|
|
151
|
+
expect(ring.color).toBe(ring.wantColor);
|
|
152
|
+
expect(ring.offset).toBe(ring.wantOffset);
|
|
153
|
+
// Back on the option cell, the action cell drops the ring.
|
|
154
|
+
await search.press('ArrowLeft');
|
|
155
|
+
await expect.poll(async () => activeDescendant(page)).toBe('row:Bug');
|
|
156
|
+
const after = await page.getByRole('row', {
|
|
157
|
+
name: 'Bug'
|
|
158
|
+
}).locator('[data-multi-selector-action-cell]').evaluate(cell => getComputedStyle(cell).outlineStyle);
|
|
159
|
+
expect(after).toBe('none');
|
|
160
|
+
});
|
|
161
|
+
test('the action-cell ring follows the shared input modality', async ({
|
|
162
|
+
page
|
|
163
|
+
}) => {
|
|
164
|
+
await page.goto(`${storybook.origin}/iframe.html?id=core-multiselector--row-actions&viewMode=story`);
|
|
165
|
+
const search = page.getByRole('combobox');
|
|
166
|
+
await expect(search).toBeFocused();
|
|
167
|
+
const actionCell = name => page.getByRole('row', {
|
|
168
|
+
name
|
|
169
|
+
}).locator('[data-multi-selector-action-cell]');
|
|
170
|
+
const outlineStyle = async name => actionCell(name).evaluate(cell => getComputedStyle(cell).outlineStyle);
|
|
171
|
+
const hover = async name => {
|
|
172
|
+
const cell = await box(actionCell(name));
|
|
173
|
+
await page.mouse.move(cell.x + cell.width / 2, cell.y + cell.height / 2, {
|
|
174
|
+
steps: 6
|
|
175
|
+
});
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
// Pointer input: a click into the search field, then a hover onto an
|
|
179
|
+
// action, moves the highlight without the ring, and leaves none behind.
|
|
180
|
+
await search.click();
|
|
181
|
+
await hover('Bug');
|
|
182
|
+
await expect.poll(async () => activeDescendant(page)).toBe('gridcell:Edit Bug');
|
|
183
|
+
expect(await outlineStyle('Bug')).toBe('none');
|
|
184
|
+
// A modifier chord is not keyboard navigation for the shared tracker.
|
|
185
|
+
await search.press('Control+ArrowRight');
|
|
186
|
+
expect(await outlineStyle('Bug')).toBe('none');
|
|
187
|
+
await page.mouse.move(1, 1, {
|
|
188
|
+
steps: 6
|
|
189
|
+
});
|
|
190
|
+
expect(await outlineStyle('Bug')).toBe('none');
|
|
191
|
+
|
|
192
|
+
// Keyboard input: back onto the same cell, the ring is drawn.
|
|
193
|
+
await search.press('ArrowLeft');
|
|
194
|
+
await expect.poll(async () => activeDescendant(page)).toBe('row:Bug');
|
|
195
|
+
await search.press('ArrowRight');
|
|
196
|
+
await expect.poll(async () => activeDescendant(page)).toBe('gridcell:Edit Bug');
|
|
197
|
+
expect(await outlineStyle('Bug')).not.toBe('none');
|
|
198
|
+
|
|
199
|
+
// Still keyboard-driven, a hover that moves the highlight to another
|
|
200
|
+
// row's action carries the ring with it.
|
|
201
|
+
await hover('P0');
|
|
202
|
+
await expect.poll(async () => activeDescendant(page)).toBe('gridcell:Edit P0');
|
|
203
|
+
expect(await outlineStyle('P0')).not.toBe('none');
|
|
204
|
+
expect(await outlineStyle('Bug')).toBe('none');
|
|
205
|
+
});
|
|
108
206
|
test('under RTL the inline-end arrow is ArrowLeft', async ({
|
|
109
207
|
page
|
|
110
208
|
}) => {
|
package/dist/Popover/Popover.js
CHANGED
package/dist/astryx.css
CHANGED
|
@@ -1115,10 +1115,10 @@
|
|
|
1115
1115
|
.xkd40ry:not(#\#):not(#\#):not(#\#){margin-inline-end:calc(-1 * var(--spacing-1))}
|
|
1116
1116
|
.x3kzqx6:not(#\#):not(#\#):not(#\#){margin-inline-end:calc(-1 * var(--spacing-2))}
|
|
1117
1117
|
.xmequkd:not(#\#):not(#\#):not(#\#){margin-inline-end:calc(var(--spacing-1) * -1)}
|
|
1118
|
+
.x144kxal:not(#\#):not(#\#):not(#\#){margin-inline-end:calc(calc(var(--spacing-4) + max(var(--astryx-layer-inset-inline-start, 0px), var(--astryx-layer-inset-inline-end, 0px))));margin-inline-end:calc(calc(max(var(--spacing-4), env(safe-area-inset-left, 0px), env(safe-area-inset-right, 0px)) + max(var(--astryx-layer-inset-inline-start, 0px), var(--astryx-layer-inset-inline-end, 0px))))}
|
|
1118
1119
|
.xnur1sd:not(#\#):not(#\#):not(#\#){margin-inline-end:var(--spacing-1)}
|
|
1119
1120
|
.x1pezmd8:not(#\#):not(#\#):not(#\#){margin-inline-end:var(--spacing-2)}
|
|
1120
1121
|
.x1c9tiao:not(#\#):not(#\#):not(#\#){margin-inline-end:var(--x-marginInlineEnd)}
|
|
1121
|
-
.x1m9hk3x:not(#\#):not(#\#):not(#\#){margin-inline-end:var(--x3pzc91,calc(var(--spacing-4) + max(var(--astryx-layer-inset-inline-start, 0px), var(--astryx-layer-inset-inline-end, 0px))))}
|
|
1122
1122
|
.xa3ewmy:not(#\#):not(#\#):not(#\#){margin-inline-end:calc(max(var(--spacing-4), env(safe-area-inset-left, 0px), env(safe-area-inset-right, 0px)) + max(var(--astryx-layer-inset-inline-start, 0px), var(--astryx-layer-inset-inline-end, 0px)))}
|
|
1123
1123
|
.x1qfufaz:not(#\#):not(#\#):not(#\#){margin-inline-start:-8px}
|
|
1124
1124
|
.x1lziwak:not(#\#):not(#\#):not(#\#){margin-inline-start:0}
|
|
@@ -1134,6 +1134,7 @@
|
|
|
1134
1134
|
.x1idpjx9:not(#\#):not(#\#):not(#\#){margin-inline-start:calc((40px - min(var(--spacing-6),40px)) / 2 - var(--spacing-4))}
|
|
1135
1135
|
.x1xrm450:not(#\#):not(#\#):not(#\#){margin-inline-start:calc(var(--spacing-2) - var(--spacing-1) + 1px)}
|
|
1136
1136
|
.x6w1cxl:not(#\#):not(#\#):not(#\#){margin-inline-start:calc(var(--spacing-3) + var(--border-width) + var(--spacing-4))}
|
|
1137
|
+
.xpdk1p0:not(#\#):not(#\#):not(#\#){margin-inline-start:calc(calc(var(--spacing-4) + max(var(--astryx-layer-inset-inline-start, 0px), var(--astryx-layer-inset-inline-end, 0px))));margin-inline-start:calc(calc(max(var(--spacing-4), env(safe-area-inset-left, 0px), env(safe-area-inset-right, 0px)) + max(var(--astryx-layer-inset-inline-start, 0px), var(--astryx-layer-inset-inline-end, 0px))))}
|
|
1137
1138
|
.x13hpdyo:not(#\#):not(#\#):not(#\#){margin-inline-start:var(--_avatar-group-overlap)}
|
|
1138
1139
|
.x12vn87o:not(#\#):not(#\#):not(#\#){margin-inline-start:var(--_tree-indent,0px)}
|
|
1139
1140
|
.xladpz9:not(#\#):not(#\#):not(#\#){margin-inline-start:var(--spacing-0-5)}
|
|
@@ -1142,7 +1143,6 @@
|
|
|
1142
1143
|
.x6i7dpz:not(#\#):not(#\#):not(#\#){margin-inline-start:var(--spacing-3)}
|
|
1143
1144
|
.x1l10yog:not(#\#):not(#\#):not(#\#){margin-inline-start:var(--spacing-6)}
|
|
1144
1145
|
.x4lel18:not(#\#):not(#\#):not(#\#){margin-inline-start:var(--x-marginInlineStart)}
|
|
1145
|
-
.x1ey4id:not(#\#):not(#\#):not(#\#){margin-inline-start:var(--x3pzc91,calc(var(--spacing-4) + max(var(--astryx-layer-inset-inline-start, 0px), var(--astryx-layer-inset-inline-end, 0px))))}
|
|
1146
1146
|
.x1cytvie:not(#\#):not(#\#):not(#\#){margin-inline-start:calc(max(var(--spacing-4), env(safe-area-inset-left, 0px), env(safe-area-inset-right, 0px)) + max(var(--astryx-layer-inset-inline-start, 0px), var(--astryx-layer-inset-inline-end, 0px)))}
|
|
1147
1147
|
.xkm2don:not(#\#):not(#\#):not(#\#){mask-image:linear-gradient(to bottom,transparent,black 24px)}
|
|
1148
1148
|
.xw26lqc:not(#\#):not(#\#):not(#\#){mask-image:linear-gradient(to bottom,transparent,black 36px)}
|
|
@@ -1170,14 +1170,14 @@
|
|
|
1170
1170
|
.x1i7u4iq:not(#\#):not(#\#):not(#\#){opacity:var(--_toast-swipe-opacity,1)}
|
|
1171
1171
|
.x1clqncf:not(#\#):not(#\#):not(#\#){order:-1}
|
|
1172
1172
|
.x1g77sc7:not(#\#):not(#\#):not(#\#){order:0}
|
|
1173
|
-
.
|
|
1173
|
+
.x19bwoy8:not(#\#):not(#\#):not(#\#){outline-color:var(--focus-outline-color)}
|
|
1174
1174
|
.x1wfwxd8:not(#\#):not(#\#):not(#\#){outline-offset:0}
|
|
1175
1175
|
.x1kvmbwa:not(#\#):not(#\#):not(#\#){outline-offset:var(--_focus-outline-offset,0)}
|
|
1176
|
-
.
|
|
1176
|
+
.x1eqjuhu:not(#\#):not(#\#):not(#\#){outline-offset:var(--focus-outline-offset)}
|
|
1177
1177
|
.x1t137rt:not(#\#):not(#\#):not(#\#){outline-style:none}
|
|
1178
|
-
.
|
|
1178
|
+
.x9ssot5:not(#\#):not(#\#):not(#\#){outline-style:var(--focus-outline-style)}
|
|
1179
1179
|
.x1k57tk5:not(#\#):not(#\#):not(#\#){outline-width:0}
|
|
1180
|
-
.
|
|
1180
|
+
.xak0ai5:not(#\#):not(#\#):not(#\#){outline-width:var(--focus-outline-width)}
|
|
1181
1181
|
.x9y5y3v:not(#\#):not(#\#):not(#\#){overflow-clip-margin:1px}
|
|
1182
1182
|
.xj0a0fe:not(#\#):not(#\#):not(#\#){overflow-wrap:anywhere}
|
|
1183
1183
|
.x1mzt3pk:not(#\#):not(#\#):not(#\#){overflow-wrap:break-word}
|
|
@@ -1757,6 +1757,7 @@
|
|
|
1757
1757
|
.x6h7pi7:not(#\#):not(#\#):not(#\#):not(#\#){margin-bottom:calc(-1 * var(--container-padding-block-end,0px))}
|
|
1758
1758
|
.x1cdrix3:not(#\#):not(#\#):not(#\#):not(#\#){margin-bottom:calc(-1 * var(--radius-chat))}
|
|
1759
1759
|
.xdrfirk:not(#\#):not(#\#):not(#\#):not(#\#){margin-bottom:calc(-1 * var(--spacing-1-5))}
|
|
1760
|
+
.x3z50q0:not(#\#):not(#\#):not(#\#):not(#\#){margin-bottom:calc(calc(var(--spacing-4) + max(var(--astryx-layer-inset-block-start, 0px), var(--astryx-layer-inset-block-end, 0px))));margin-bottom:calc(calc(max(var(--spacing-4), env(safe-area-inset-top, 0px), env(safe-area-inset-bottom, 0px)) + max(var(--astryx-layer-inset-block-start, 0px), var(--astryx-layer-inset-block-end, 0px))))}
|
|
1760
1761
|
.xlstkdb:not(#\#):not(#\#):not(#\#):not(#\#){margin-bottom:var(--spacing-0-5)}
|
|
1761
1762
|
.x14cgwvg:not(#\#):not(#\#):not(#\#):not(#\#){margin-bottom:var(--spacing-1)}
|
|
1762
1763
|
.x1p37lm5:not(#\#):not(#\#):not(#\#):not(#\#){margin-bottom:var(--spacing-2)}
|
|
@@ -1764,7 +1765,6 @@
|
|
|
1764
1765
|
.xwh1t77:not(#\#):not(#\#):not(#\#):not(#\#){margin-bottom:var(--spacing-4)}
|
|
1765
1766
|
.x4d5cxa:not(#\#):not(#\#):not(#\#):not(#\#){margin-bottom:var(--spacing-6)}
|
|
1766
1767
|
.xlm3tn6:not(#\#):not(#\#):not(#\#):not(#\#){margin-bottom:var(--x-marginBlockEnd)}
|
|
1767
|
-
.x1chv945:not(#\#):not(#\#):not(#\#):not(#\#){margin-bottom:var(--x1ot8se4,calc(var(--spacing-4) + max(var(--astryx-layer-inset-block-start, 0px), var(--astryx-layer-inset-block-end, 0px))))}
|
|
1768
1768
|
.x119uzjn:not(#\#):not(#\#):not(#\#):not(#\#){margin-bottom:calc(max(var(--spacing-4), env(safe-area-inset-top, 0px), env(safe-area-inset-bottom, 0px)) + max(var(--astryx-layer-inset-block-start, 0px), var(--astryx-layer-inset-block-end, 0px)))}
|
|
1769
1769
|
.xdj266r:not(#\#):not(#\#):not(#\#):not(#\#){margin-top:0}
|
|
1770
1770
|
.xr1yuqi:not(#\#):not(#\#):not(#\#):not(#\#){margin-top:auto}
|
|
@@ -1778,6 +1778,7 @@
|
|
|
1778
1778
|
.x1ajqlyv:not(#\#):not(#\#):not(#\#):not(#\#){margin-top:calc(-1 * var(--spacing-2))}
|
|
1779
1779
|
.x1oms9s6:not(#\#):not(#\#):not(#\#):not(#\#){margin-top:calc((1em * var(--text-body-leading) - 20px) / 2)}
|
|
1780
1780
|
.x1233pnv:not(#\#):not(#\#):not(#\#):not(#\#){margin-top:calc((1em * var(--text-body-leading) - 6px) / 2)}
|
|
1781
|
+
.x1j6tnbc:not(#\#):not(#\#):not(#\#):not(#\#){margin-top:calc(calc(var(--spacing-4) + max(var(--astryx-layer-inset-block-start, 0px), var(--astryx-layer-inset-block-end, 0px))));margin-top:calc(calc(max(var(--spacing-4), env(safe-area-inset-top, 0px), env(safe-area-inset-bottom, 0px)) + max(var(--astryx-layer-inset-block-start, 0px), var(--astryx-layer-inset-block-end, 0px))))}
|
|
1781
1782
|
.xjc2qm6:not(#\#):not(#\#):not(#\#):not(#\#){margin-top:var(--spacing-0-5)}
|
|
1782
1783
|
.xcsaf9d:not(#\#):not(#\#):not(#\#):not(#\#){margin-top:var(--spacing-1)}
|
|
1783
1784
|
.xtbrsbv:not(#\#):not(#\#):not(#\#):not(#\#){margin-top:var(--spacing-2)}
|
|
@@ -1785,7 +1786,6 @@
|
|
|
1785
1786
|
.x1s1qpeq:not(#\#):not(#\#):not(#\#):not(#\#){margin-top:var(--spacing-4)}
|
|
1786
1787
|
.x13rv44w:not(#\#):not(#\#):not(#\#):not(#\#){margin-top:var(--spacing-6)}
|
|
1787
1788
|
.x1vhfslr:not(#\#):not(#\#):not(#\#):not(#\#){margin-top:var(--x-marginBlockStart)}
|
|
1788
|
-
.x3390rn:not(#\#):not(#\#):not(#\#):not(#\#){margin-top:var(--x1ot8se4,calc(var(--spacing-4) + max(var(--astryx-layer-inset-block-start, 0px), var(--astryx-layer-inset-block-end, 0px))))}
|
|
1789
1789
|
.x1lhixnp:not(#\#):not(#\#):not(#\#):not(#\#){margin-top:calc(max(var(--spacing-4), env(safe-area-inset-top, 0px), env(safe-area-inset-bottom, 0px)) + max(var(--astryx-layer-inset-block-start, 0px), var(--astryx-layer-inset-block-end, 0px)))}
|
|
1790
1790
|
.xmz0i5r:not(#\#):not(#\#):not(#\#):not(#\#){max-height:100%}
|
|
1791
1791
|
.x1wj9ous:not(#\#):not(#\#):not(#\#):not(#\#){max-height:100dvh}
|
|
@@ -1794,13 +1794,13 @@
|
|
|
1794
1794
|
.xuyqlj2:not(#\#):not(#\#):not(#\#):not(#\#){max-height:300px}
|
|
1795
1795
|
.xbhvqqn:not(#\#):not(#\#):not(#\#):not(#\#){max-height:calc(100% - var(--spacing-3))}
|
|
1796
1796
|
.x14bu9tk:not(#\#):not(#\#):not(#\#):not(#\#){max-height:calc(92dvh + 48px)}
|
|
1797
|
+
.x3v4n1:not(#\#):not(#\#):not(#\#):not(#\#){max-height:calc(calc(100vh - var(--spacing-4) - var(--spacing-4) - var(--astryx-layer-inset-block-start, 0px) - var(--astryx-layer-inset-block-end, 0px)));max-height:calc(calc(100dvb - calc(max(var(--spacing-4), env(safe-area-inset-top, 0px)) + var(--astryx-layer-inset-block-start, 0px)) - calc(max(var(--spacing-4), env(safe-area-inset-bottom, 0px)) + var(--astryx-layer-inset-block-end, 0px))))}
|
|
1797
1798
|
.x1uh1i8a:not(#\#):not(#\#):not(#\#):not(#\#){max-height:min(300px,calc(100vh - var(--spacing-4) - var(--spacing-4) - var(--astryx-layer-inset-block-start, 0px) - var(--astryx-layer-inset-block-end, 0px)));max-height:min(300px,calc(100dvb - calc(max(var(--spacing-4), env(safe-area-inset-top, 0px)) + var(--astryx-layer-inset-block-start, 0px)) - calc(max(var(--spacing-4), env(safe-area-inset-bottom, 0px)) + var(--astryx-layer-inset-block-end, 0px))))}
|
|
1798
1799
|
.xpnbb6b:not(#\#):not(#\#):not(#\#):not(#\#){max-height:min(480px,calc(100vh - 32px))}
|
|
1799
1800
|
.x7ab17h:not(#\#):not(#\#):not(#\#):not(#\#){max-height:none}
|
|
1800
1801
|
.xenllk4:not(#\#):not(#\#):not(#\#):not(#\#){max-height:var(--container-max-height,none)}
|
|
1801
1802
|
.xbmfmqd:not(#\#):not(#\#):not(#\#):not(#\#){max-height:var(--x-maxBlockSize)}
|
|
1802
1803
|
.x1jols5v:not(#\#):not(#\#):not(#\#):not(#\#){max-height:var(--x-maxHeight)}
|
|
1803
|
-
.xv8yize:not(#\#):not(#\#):not(#\#):not(#\#){max-height:var(--x17ekfff,calc(100vh - var(--spacing-4) - var(--spacing-4) - var(--astryx-layer-inset-block-start, 0px) - var(--astryx-layer-inset-block-end, 0px)))}
|
|
1804
1804
|
.x1m189uc:not(#\#):not(#\#):not(#\#):not(#\#){max-width:0}
|
|
1805
1805
|
.x193iq5w:not(#\#):not(#\#):not(#\#):not(#\#){max-width:100%}
|
|
1806
1806
|
.xlbgzzq:not(#\#):not(#\#):not(#\#):not(#\#){max-width:100dvw}
|
|
@@ -1815,13 +1815,13 @@
|
|
|
1815
1815
|
.x1ap80js:not(#\#):not(#\#):not(#\#):not(#\#){max-width:800px}
|
|
1816
1816
|
.xq8v1hd:not(#\#):not(#\#):not(#\#):not(#\#){max-width:90%}
|
|
1817
1817
|
.x1rfgw7b:not(#\#):not(#\#):not(#\#):not(#\#){max-width:calc(100% + var(--container-padding-inline-start,0px) + var(--container-padding-inline-end,0px))}
|
|
1818
|
+
.xc0b1yq:not(#\#):not(#\#):not(#\#):not(#\#){max-width:calc(calc(100vw - var(--spacing-4) - var(--spacing-4) - var(--astryx-layer-inset-inline-start, 0px) - var(--astryx-layer-inset-inline-end, 0px)));max-width:calc(calc(100vi - calc(max(var(--spacing-4), env(safe-area-inset-left, 0px)) + var(--astryx-layer-inset-inline-start, 0px)) - calc(max(var(--spacing-4), env(safe-area-inset-right, 0px)) + var(--astryx-layer-inset-inline-end, 0px))))}
|
|
1818
1819
|
.xl6temz:not(#\#):not(#\#):not(#\#):not(#\#){max-width:max(80%,280px)}
|
|
1819
1820
|
.x1m1aaou:not(#\#):not(#\#):not(#\#):not(#\#){max-width:min(960px,calc(100dvw - var(--spacing-4)))}
|
|
1820
1821
|
.x1x1rfll:not(#\#):not(#\#):not(#\#):not(#\#){max-width:none}
|
|
1821
1822
|
.xjl2t3p:not(#\#):not(#\#):not(#\#):not(#\#){max-width:var(--layout-content-width,none)}
|
|
1822
1823
|
.x1jys5uj:not(#\#):not(#\#):not(#\#):not(#\#){max-width:var(--size-element-md)}
|
|
1823
1824
|
.xf68679:not(#\#):not(#\#):not(#\#):not(#\#){max-width:var(--x-maxWidth)}
|
|
1824
|
-
.x1p5vr2b:not(#\#):not(#\#):not(#\#):not(#\#){max-width:var(--xdig2zt,calc(100vw - var(--spacing-4) - var(--spacing-4) - var(--astryx-layer-inset-inline-start, 0px) - var(--astryx-layer-inset-inline-end, 0px)))}
|
|
1825
1825
|
.x2lwn1j:not(#\#):not(#\#):not(#\#):not(#\#){min-height:0}
|
|
1826
1826
|
.x1us19tq:not(#\#):not(#\#):not(#\#):not(#\#){min-height:100%}
|
|
1827
1827
|
.x1ov3xa9:not(#\#):not(#\#):not(#\#):not(#\#){min-height:100dvh}
|
|
@@ -44,6 +44,10 @@ export declare const FOCUS_OUTLINE: string;
|
|
|
44
44
|
* independently overridable — and every part stays a var, so a theme's
|
|
45
45
|
* `--focus-outline-*` overrides still flow through.
|
|
46
46
|
*
|
|
47
|
+
* NOT usable inside another file's `stylex.create`, for the same reason as
|
|
48
|
+
* {@link FOCUS_OUTLINE}: StyleX compiles each member there to a CSS variable
|
|
49
|
+
* nothing defines. Compose the `focusVars` tokens instead.
|
|
50
|
+
*
|
|
47
51
|
* Keys are camelCase to match `HTMLElement.style`, so this spreads straight
|
|
48
52
|
* onto an element:
|
|
49
53
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"focusOutline.stylex.d.ts","sourceRoot":"","sources":["../../src/utils/focusOutline.stylex.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,MAAM,MAAM,kBAAkB,CAAC;AAS3C;;;;;;;;;GASG;AACH,eAAO,MAAM,aAAa,QAAyE,CAAC;AAgBpG
|
|
1
|
+
{"version":3,"file":"focusOutline.stylex.d.ts","sourceRoot":"","sources":["../../src/utils/focusOutline.stylex.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,MAAM,MAAM,kBAAkB,CAAC;AAS3C;;;;;;;;;GASG;AACH,eAAO,MAAM,aAAa,QAAyE,CAAC;AAgBpG;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,eAAO,MAAM,mBAAmB;;;;;CAKtB,CAAC;AAEX,+EAA+E;AAC/E,eAAO,MAAM,wBAAwB;;;;;CAK3B,CAAC;AAEX,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAiG7B,CAAC;AAIH,KAAK,cAAc,GAAG,OAAO,CAAC;AAiB9B,eAAO,MAAM,iBAAiB;uCAdT,cAAc,EAAE;;;;;;;sCAAhB,cAAc,EAAE;;;;;;;gDAAhB,cAAc,EAAE;;;;;;;qCAAhB,cAAc,EAAE;;;;;;;kDAAhB,cAAc,EAAE;;;;;;;iDAAhB,cAAc,EAAE;;;;;;;CA2B3B,CAAC"}
|
|
@@ -69,6 +69,10 @@ const focusOutlineLonghands = {
|
|
|
69
69
|
* independently overridable — and every part stays a var, so a theme's
|
|
70
70
|
* `--focus-outline-*` overrides still flow through.
|
|
71
71
|
*
|
|
72
|
+
* NOT usable inside another file's `stylex.create`, for the same reason as
|
|
73
|
+
* {@link FOCUS_OUTLINE}: StyleX compiles each member there to a CSS variable
|
|
74
|
+
* nothing defines. Compose the `focusVars` tokens instead.
|
|
75
|
+
*
|
|
72
76
|
* Keys are camelCase to match `HTMLElement.style`, so this spreads straight
|
|
73
77
|
* onto an element:
|
|
74
78
|
*
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@astryxdesign/core",
|
|
3
|
-
"version": "0.7.2-canary.
|
|
3
|
+
"version": "0.7.2-canary.81b8df4",
|
|
4
4
|
"displayName": "Astryx 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",
|
|
@@ -787,8 +787,8 @@
|
|
|
787
787
|
"react-dom": ">=19.0.0"
|
|
788
788
|
},
|
|
789
789
|
"devDependencies": {
|
|
790
|
-
"@astryxdesign/a11y-spec": "0.7.2-canary.
|
|
791
|
-
"@astryxdesign/cli": "0.7.2-canary.
|
|
790
|
+
"@astryxdesign/a11y-spec": "0.7.2-canary.81b8df4",
|
|
791
|
+
"@astryxdesign/cli": "0.7.2-canary.81b8df4",
|
|
792
792
|
"@babel/cli": "^7.29.7",
|
|
793
793
|
"@babel/core": "^7.29.7",
|
|
794
794
|
"@babel/preset-react": "^7.29.7",
|
|
@@ -229,8 +229,8 @@ const styles = stylex.create({
|
|
|
229
229
|
WebkitTouchCallout: 'none',
|
|
230
230
|
userSelect: 'none',
|
|
231
231
|
maxInlineSize: stylex.firstThatWorks(
|
|
232
|
-
layerViewportInset.maxInlineSize
|
|
233
|
-
layerViewportInset.maxInlineSizeFallback
|
|
232
|
+
`calc(${layerViewportInset.maxInlineSize})`,
|
|
233
|
+
`calc(${layerViewportInset.maxInlineSizeFallback})`,
|
|
234
234
|
),
|
|
235
235
|
maxHeight: stylex.firstThatWorks(
|
|
236
236
|
MENU_MAX_BLOCK_SIZE,
|
|
@@ -166,8 +166,8 @@ const flyoutStyles = stylex.create({
|
|
|
166
166
|
flexDirection: 'column',
|
|
167
167
|
gap: spacingVars['--spacing-0-5'],
|
|
168
168
|
maxInlineSize: stylex.firstThatWorks(
|
|
169
|
-
layerViewportInset.maxInlineSize
|
|
170
|
-
layerViewportInset.maxInlineSizeFallback
|
|
169
|
+
`calc(${layerViewportInset.maxInlineSize})`,
|
|
170
|
+
`calc(${layerViewportInset.maxInlineSizeFallback})`,
|
|
171
171
|
),
|
|
172
172
|
maxHeight: stylex.firstThatWorks(
|
|
173
173
|
MENU_MAX_BLOCK_SIZE,
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file layerViewportInset.source-build.test.mjs
|
|
5
|
+
* @input Uses Babel, the core StyleX build config, the StyleX rule processor,
|
|
6
|
+
* and the sources that cap or inset anchored layers
|
|
7
|
+
* @output Verifies the shipped stylesheet carries the dynamic-viewport,
|
|
8
|
+
* safe-area gutter and caps (spec:AST-059 FR1–FR3), not only the fallbacks
|
|
9
|
+
* @position Regression test for the compiled layer viewport inset
|
|
10
|
+
*
|
|
11
|
+
* `stylex.firstThatWorks(a, b)` turns two `var()` arguments into one nested
|
|
12
|
+
* `var(a, b)`. A `defineConsts` reference compiles to such a `var()`, and the
|
|
13
|
+
* rule processor inlines a constant only where its `var()` stands alone, so a
|
|
14
|
+
* pair of bare constant references shipped as `var(--<hash>, <fallback>)`
|
|
15
|
+
* with `--<hash>` defined nowhere: every engine took the fallback.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import fs from 'node:fs/promises';
|
|
19
|
+
import path from 'node:path';
|
|
20
|
+
import {fileURLToPath} from 'node:url';
|
|
21
|
+
import {transformAsync} from '@babel/core';
|
|
22
|
+
import stylexBabelPlugin from '@stylexjs/babel-plugin';
|
|
23
|
+
import {describe, expect, it} from 'vitest';
|
|
24
|
+
|
|
25
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
26
|
+
const SRC = path.resolve(__dirname, '..');
|
|
27
|
+
const CORE_ROOT = path.resolve(SRC, '..');
|
|
28
|
+
|
|
29
|
+
const SOURCES = [
|
|
30
|
+
'Layer/layerViewportInset.stylex.ts',
|
|
31
|
+
'Layer/useLayer.tsx',
|
|
32
|
+
'Popover/Popover.tsx',
|
|
33
|
+
'DropdownMenu/DropdownMenu.tsx',
|
|
34
|
+
'DropdownMenu/DropdownMenuSubMenu.tsx',
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
async function compileStylesheet() {
|
|
38
|
+
const rules = [];
|
|
39
|
+
for (const relative of SOURCES) {
|
|
40
|
+
const filename = path.join(SRC, relative);
|
|
41
|
+
const result = await transformAsync(await fs.readFile(filename, 'utf8'), {
|
|
42
|
+
babelrc: false,
|
|
43
|
+
configFile: path.join(CORE_ROOT, 'babel.config.json'),
|
|
44
|
+
filename,
|
|
45
|
+
});
|
|
46
|
+
rules.push(...(result?.metadata?.stylex ?? []));
|
|
47
|
+
}
|
|
48
|
+
return stylexBabelPlugin.processStylexRules(rules, false);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Every declaration of `property` in the compiled sheet. */
|
|
52
|
+
function declarations(css, property) {
|
|
53
|
+
return [...css.matchAll(new RegExp(`[{;]${property}:([^;}]+)`, 'g'))].map(
|
|
54
|
+
match => match[1],
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
describe('layer viewport inset stylesheet (spec:AST-059)', () => {
|
|
59
|
+
it('leaves no constant reference unresolved behind a fallback', async () => {
|
|
60
|
+
const css = await compileStylesheet();
|
|
61
|
+
const defined = new Set(
|
|
62
|
+
[...css.matchAll(/(--[\w-]+)\s*:/g)].map(match => match[1]),
|
|
63
|
+
);
|
|
64
|
+
const undefinedReferences = [
|
|
65
|
+
...css.matchAll(/var\((--x[a-z0-9]+),/g),
|
|
66
|
+
].flatMap(match => (defined.has(match[1]) ? [] : [match[0]]));
|
|
67
|
+
// Covers the caps and the four alignment-axis gutter margins.
|
|
68
|
+
expect(undefinedReferences).toEqual([]);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('caps both axes against the dynamic viewport and the safe-area gutters', async () => {
|
|
72
|
+
const css = await compileStylesheet();
|
|
73
|
+
const blockCaps = declarations(css, 'max-height');
|
|
74
|
+
const inlineCaps = declarations(css, 'max-width');
|
|
75
|
+
|
|
76
|
+
expect(blockCaps).toContainEqual(
|
|
77
|
+
expect.stringMatching(/^calc\(calc\(100dvb - .*safe-area-inset-top/),
|
|
78
|
+
);
|
|
79
|
+
expect(inlineCaps).toContainEqual(
|
|
80
|
+
expect.stringMatching(/^calc\(calc\(100vi - .*safe-area-inset-left/),
|
|
81
|
+
);
|
|
82
|
+
// The older-engine fallbacks still ship, ahead of the preferred value.
|
|
83
|
+
expect(blockCaps).toContainEqual(
|
|
84
|
+
expect.stringMatching(/^calc\(calc\(100vh - /),
|
|
85
|
+
);
|
|
86
|
+
expect(inlineCaps).toContainEqual(
|
|
87
|
+
expect.stringMatching(/^calc\(calc\(100vw - /),
|
|
88
|
+
);
|
|
89
|
+
});
|
|
90
|
+
});
|
|
@@ -56,8 +56,12 @@ const GUTTER_BLOCK = `calc(max(${SPACING}, env(safe-area-inset-top, 0px), env(sa
|
|
|
56
56
|
|
|
57
57
|
/**
|
|
58
58
|
* The viewport gutter and the caps derived from it. Every value has an
|
|
59
|
-
* `env()`-less fallback for engines without safe-area support; pair them
|
|
60
|
-
* `stylex.firstThatWorks(cap
|
|
59
|
+
* `env()`-less fallback for engines without safe-area support; pair them as
|
|
60
|
+
* `stylex.firstThatWorks(`calc(${cap})`, `calc(${capFallback})`)`. Pass
|
|
61
|
+
* each inside a `calc()` (or another function), never as a bare reference: a
|
|
62
|
+
* constant reference compiles to a `var()`, and `firstThatWorks` folds two
|
|
63
|
+
* `var()` arguments into one `var(cap, capFallback)` whose constant is never
|
|
64
|
+
* inlined, so every engine would take the fallback.
|
|
61
65
|
*/
|
|
62
66
|
export const layerViewportInset = stylex.defineConsts({
|
|
63
67
|
/** Gutter from the block-start viewport edge. */
|
package/src/Layer/useLayer.tsx
CHANGED
|
@@ -102,15 +102,17 @@ const styles = stylex.create({
|
|
|
102
102
|
// The cap is on the layer box itself: content wider than the viewport
|
|
103
103
|
// overflows inside the layer, where the composing component decides
|
|
104
104
|
// whether it scrolls or clips; the box never leaves the viewport.
|
|
105
|
+
// Each constant is wrapped in `calc()` so both declarations ship (see
|
|
106
|
+
// layerViewportInset).
|
|
105
107
|
viewportFit: {
|
|
106
108
|
boxSizing: 'border-box',
|
|
107
109
|
maxInlineSize: stylex.firstThatWorks(
|
|
108
|
-
layerViewportInset.maxInlineSize
|
|
109
|
-
layerViewportInset.maxInlineSizeFallback
|
|
110
|
+
`calc(${layerViewportInset.maxInlineSize})`,
|
|
111
|
+
`calc(${layerViewportInset.maxInlineSizeFallback})`,
|
|
110
112
|
),
|
|
111
113
|
maxBlockSize: stylex.firstThatWorks(
|
|
112
|
-
layerViewportInset.maxBlockSize
|
|
113
|
-
layerViewportInset.maxBlockSizeFallback
|
|
114
|
+
`calc(${layerViewportInset.maxBlockSize})`,
|
|
115
|
+
`calc(${layerViewportInset.maxBlockSizeFallback})`,
|
|
114
116
|
),
|
|
115
117
|
},
|
|
116
118
|
// Alignment-axis gutter (spec:AST-059 FR1): a margin on the far viewport
|
|
@@ -120,26 +122,26 @@ const styles = stylex.create({
|
|
|
120
122
|
// (see layerSlideRules.ts).
|
|
121
123
|
gutterInlineEnd: {
|
|
122
124
|
marginInlineEnd: stylex.firstThatWorks(
|
|
123
|
-
layerViewportInset.gutterInline
|
|
124
|
-
layerViewportInset.gutterInlineFallback
|
|
125
|
+
`calc(${layerViewportInset.gutterInline})`,
|
|
126
|
+
`calc(${layerViewportInset.gutterInlineFallback})`,
|
|
125
127
|
),
|
|
126
128
|
},
|
|
127
129
|
gutterInlineStart: {
|
|
128
130
|
marginInlineStart: stylex.firstThatWorks(
|
|
129
|
-
layerViewportInset.gutterInline
|
|
130
|
-
layerViewportInset.gutterInlineFallback
|
|
131
|
+
`calc(${layerViewportInset.gutterInline})`,
|
|
132
|
+
`calc(${layerViewportInset.gutterInlineFallback})`,
|
|
131
133
|
),
|
|
132
134
|
},
|
|
133
135
|
gutterBlockEnd: {
|
|
134
136
|
marginBlockEnd: stylex.firstThatWorks(
|
|
135
|
-
layerViewportInset.gutterBlock
|
|
136
|
-
layerViewportInset.gutterBlockFallback
|
|
137
|
+
`calc(${layerViewportInset.gutterBlock})`,
|
|
138
|
+
`calc(${layerViewportInset.gutterBlockFallback})`,
|
|
137
139
|
),
|
|
138
140
|
},
|
|
139
141
|
gutterBlockStart: {
|
|
140
142
|
marginBlockStart: stylex.firstThatWorks(
|
|
141
|
-
layerViewportInset.gutterBlock
|
|
142
|
-
layerViewportInset.gutterBlockFallback
|
|
143
|
+
`calc(${layerViewportInset.gutterBlock})`,
|
|
144
|
+
`calc(${layerViewportInset.gutterBlockFallback})`,
|
|
143
145
|
),
|
|
144
146
|
},
|
|
145
147
|
});
|
|
@@ -35,6 +35,7 @@ import {generateThemeCSS} from '../theme/generateThemeRules';
|
|
|
35
35
|
import {selectorPresentationStyles} from '../Selector/selectorPresentation.stylex';
|
|
36
36
|
import {groupStyles} from '../InputGroup/groupStyles';
|
|
37
37
|
import {InputGroup, InputGroupText} from '../InputGroup';
|
|
38
|
+
import {focusVars} from '../theme/tokens.stylex';
|
|
38
39
|
|
|
39
40
|
function generateThemeTestCSS(theme: Parameters<typeof generateThemeCSS>[0]) {
|
|
40
41
|
const {prose, component} = generateThemeCSS(theme);
|
|
@@ -4083,6 +4084,164 @@ describe('MultiSelector option actions (grid)', () => {
|
|
|
4083
4084
|
expect(onChange).not.toHaveBeenCalled();
|
|
4084
4085
|
});
|
|
4085
4086
|
|
|
4087
|
+
it('draws the standard focus ring on the active action cell, from the focus tokens', async () => {
|
|
4088
|
+
// jsdom resolves no custom property, so the ring is read from StyleX
|
|
4089
|
+
// classes: a class name hashes one property and its value, and the same
|
|
4090
|
+
// declaration yields the same class wherever it is written.
|
|
4091
|
+
const ring = stylex.create({
|
|
4092
|
+
active: {
|
|
4093
|
+
outlineWidth: focusVars['--focus-outline-width'],
|
|
4094
|
+
outlineStyle: focusVars['--focus-outline-style'],
|
|
4095
|
+
outlineColor: focusVars['--focus-outline-color'],
|
|
4096
|
+
outlineOffset: focusVars['--focus-outline-offset'],
|
|
4097
|
+
},
|
|
4098
|
+
});
|
|
4099
|
+
const ringClasses = stylex
|
|
4100
|
+
.props(ring.active)
|
|
4101
|
+
.className!.split(' ')
|
|
4102
|
+
.filter(token => /^x[a-z0-9]+$/.test(token));
|
|
4103
|
+
expect(ringClasses).toHaveLength(4);
|
|
4104
|
+
|
|
4105
|
+
const user = userEvent.setup();
|
|
4106
|
+
render(
|
|
4107
|
+
<MultiSelector
|
|
4108
|
+
label="Labels"
|
|
4109
|
+
options={OPTIONS}
|
|
4110
|
+
value={[]}
|
|
4111
|
+
onChange={() => {}}
|
|
4112
|
+
/>,
|
|
4113
|
+
);
|
|
4114
|
+
const trigger = screen.getByRole('combobox', {name: 'Labels'});
|
|
4115
|
+
await user.click(trigger);
|
|
4116
|
+
const bug = screen.getByRole('row', {name: 'Bug', ...h});
|
|
4117
|
+
const actionCell = bug.children[1] as HTMLElement;
|
|
4118
|
+
for (const token of ringClasses) {
|
|
4119
|
+
expect(actionCell).not.toHaveClass(token);
|
|
4120
|
+
}
|
|
4121
|
+
fireEvent.keyDown(trigger, {key: 'ArrowRight'});
|
|
4122
|
+
expect(trigger).toHaveAttribute('aria-activedescendant', actionCell.id);
|
|
4123
|
+
for (const token of ringClasses) {
|
|
4124
|
+
expect(actionCell).toHaveClass(token);
|
|
4125
|
+
}
|
|
4126
|
+
});
|
|
4127
|
+
|
|
4128
|
+
it('draws no focus ring on an action cell highlighted after pointer input, until a key moves the highlight', async () => {
|
|
4129
|
+
const ring = stylex.create({
|
|
4130
|
+
active: {
|
|
4131
|
+
outlineWidth: focusVars['--focus-outline-width'],
|
|
4132
|
+
outlineStyle: focusVars['--focus-outline-style'],
|
|
4133
|
+
outlineColor: focusVars['--focus-outline-color'],
|
|
4134
|
+
outlineOffset: focusVars['--focus-outline-offset'],
|
|
4135
|
+
},
|
|
4136
|
+
});
|
|
4137
|
+
const ringClasses = stylex
|
|
4138
|
+
.props(ring.active)
|
|
4139
|
+
.className!.split(' ')
|
|
4140
|
+
.filter(token => /^x[a-z0-9]+$/.test(token));
|
|
4141
|
+
expect(ringClasses).toHaveLength(4);
|
|
4142
|
+
|
|
4143
|
+
const user = userEvent.setup();
|
|
4144
|
+
render(
|
|
4145
|
+
<MultiSelector
|
|
4146
|
+
label="Labels"
|
|
4147
|
+
options={OPTIONS}
|
|
4148
|
+
value={[]}
|
|
4149
|
+
onChange={() => {}}
|
|
4150
|
+
/>,
|
|
4151
|
+
);
|
|
4152
|
+
const trigger = screen.getByRole('combobox', {name: 'Labels'});
|
|
4153
|
+
await user.click(trigger);
|
|
4154
|
+
const bug = screen.getByRole('row', {name: 'Bug', ...h});
|
|
4155
|
+
const actionCell = bug.children[1] as HTMLElement;
|
|
4156
|
+
fireEvent.mouseEnter(actionCell);
|
|
4157
|
+
// The hover moved the highlight onto the action cell, without the ring.
|
|
4158
|
+
expect(trigger).toHaveAttribute('aria-activedescendant', actionCell.id);
|
|
4159
|
+
for (const token of ringClasses) {
|
|
4160
|
+
expect(actionCell).not.toHaveClass(token);
|
|
4161
|
+
}
|
|
4162
|
+
// Keyboard navigation back onto the cell draws it.
|
|
4163
|
+
fireEvent.keyDown(trigger, {key: 'ArrowLeft'});
|
|
4164
|
+
fireEvent.keyDown(trigger, {key: 'ArrowRight'});
|
|
4165
|
+
expect(trigger).toHaveAttribute('aria-activedescendant', actionCell.id);
|
|
4166
|
+
for (const token of ringClasses) {
|
|
4167
|
+
expect(actionCell).toHaveClass(token);
|
|
4168
|
+
}
|
|
4169
|
+
});
|
|
4170
|
+
|
|
4171
|
+
describe('ring under mixed input', () => {
|
|
4172
|
+
const ringClasses = () => {
|
|
4173
|
+
const ring = stylex.create({
|
|
4174
|
+
active: {
|
|
4175
|
+
outlineWidth: focusVars['--focus-outline-width'],
|
|
4176
|
+
outlineStyle: focusVars['--focus-outline-style'],
|
|
4177
|
+
outlineColor: focusVars['--focus-outline-color'],
|
|
4178
|
+
outlineOffset: focusVars['--focus-outline-offset'],
|
|
4179
|
+
},
|
|
4180
|
+
});
|
|
4181
|
+
return stylex
|
|
4182
|
+
.props(ring.active)
|
|
4183
|
+
.className!.split(' ')
|
|
4184
|
+
.filter(token => /^x[a-z0-9]+$/.test(token));
|
|
4185
|
+
};
|
|
4186
|
+
const hasRing = (cell: HTMLElement) =>
|
|
4187
|
+
ringClasses().every(token => cell.classList.contains(token));
|
|
4188
|
+
|
|
4189
|
+
async function openWithKeyboardOnBugAction() {
|
|
4190
|
+
const user = userEvent.setup();
|
|
4191
|
+
render(
|
|
4192
|
+
<MultiSelector
|
|
4193
|
+
label="Labels"
|
|
4194
|
+
options={OPTIONS}
|
|
4195
|
+
value={[]}
|
|
4196
|
+
onChange={() => {}}
|
|
4197
|
+
/>,
|
|
4198
|
+
);
|
|
4199
|
+
const trigger = screen.getByRole('combobox', {name: 'Labels'});
|
|
4200
|
+
await user.click(trigger);
|
|
4201
|
+
const row = (name: string) => screen.getByRole('row', {name, ...h});
|
|
4202
|
+
fireEvent.keyDown(trigger, {key: 'ArrowRight'});
|
|
4203
|
+
const bugAction = row('Bug').children[1] as HTMLElement;
|
|
4204
|
+
expect(trigger).toHaveAttribute('aria-activedescendant', bugAction.id);
|
|
4205
|
+
expect(hasRing(bugAction)).toBe(true);
|
|
4206
|
+
return {trigger, row, bugAction};
|
|
4207
|
+
}
|
|
4208
|
+
|
|
4209
|
+
it('keeps the ring when the pointer crosses a disabled option the highlight ignores', async () => {
|
|
4210
|
+
const {trigger, row, bugAction} = await openWithKeyboardOnBugAction();
|
|
4211
|
+
fireEvent.mouseEnter(row('Docs').children[0]);
|
|
4212
|
+
expect(trigger).toHaveAttribute('aria-activedescendant', bugAction.id);
|
|
4213
|
+
expect(hasRing(bugAction)).toBe(true);
|
|
4214
|
+
});
|
|
4215
|
+
|
|
4216
|
+
it('follows the keyboard modality when a hover moves the highlight to another action', async () => {
|
|
4217
|
+
const {trigger, row} = await openWithKeyboardOnBugAction();
|
|
4218
|
+
const docsAction = row('Docs').children[1] as HTMLElement;
|
|
4219
|
+
fireEvent.mouseEnter(docsAction);
|
|
4220
|
+
expect(trigger).toHaveAttribute('aria-activedescendant', docsAction.id);
|
|
4221
|
+
expect(hasRing(docsAction)).toBe(true);
|
|
4222
|
+
});
|
|
4223
|
+
|
|
4224
|
+
it('draws no ring for a modifier chord after pointer input', async () => {
|
|
4225
|
+
const user = userEvent.setup();
|
|
4226
|
+
render(
|
|
4227
|
+
<MultiSelector
|
|
4228
|
+
label="Labels"
|
|
4229
|
+
options={OPTIONS}
|
|
4230
|
+
value={[]}
|
|
4231
|
+
onChange={() => {}}
|
|
4232
|
+
/>,
|
|
4233
|
+
);
|
|
4234
|
+
const trigger = screen.getByRole('combobox', {name: 'Labels'});
|
|
4235
|
+
await user.click(trigger);
|
|
4236
|
+
const bugAction = screen.getByRole('row', {name: 'Bug', ...h})
|
|
4237
|
+
.children[1] as HTMLElement;
|
|
4238
|
+
fireEvent.mouseEnter(bugAction);
|
|
4239
|
+
expect(hasRing(bugAction)).toBe(false);
|
|
4240
|
+
fireEvent.keyDown(trigger, {key: 'ArrowRight', ctrlKey: true});
|
|
4241
|
+
expect(hasRing(bugAction)).toBe(false);
|
|
4242
|
+
});
|
|
4243
|
+
});
|
|
4244
|
+
|
|
4086
4245
|
it('the inline-end arrow reaches the action cell, Enter fires it, inline-start returns', async () => {
|
|
4087
4246
|
const user = userEvent.setup();
|
|
4088
4247
|
const onChange = vi.fn();
|
|
@@ -50,6 +50,7 @@ import type {IndicatorPosition} from '../Indicator';
|
|
|
50
50
|
import {Badge} from '../Badge';
|
|
51
51
|
import {
|
|
52
52
|
colorVars,
|
|
53
|
+
focusVars,
|
|
53
54
|
sizeVars,
|
|
54
55
|
spacingVars,
|
|
55
56
|
radiusVars,
|
|
@@ -74,7 +75,10 @@ import {
|
|
|
74
75
|
import {useMultiCombobox} from './hooks';
|
|
75
76
|
import {getInputARIA, isImeKeyEvent, mergeProps} from '../utils';
|
|
76
77
|
import {devWarn, warnOnce} from '../utils/devWarning';
|
|
77
|
-
import {
|
|
78
|
+
import {
|
|
79
|
+
getInteractionModality,
|
|
80
|
+
useInteractionModalityTracking,
|
|
81
|
+
} from '../utils/interactionModality';
|
|
78
82
|
import {useAnnounce} from '../hooks/useAnnounce';
|
|
79
83
|
import {FOCUSABLE_SELECTOR} from '../hooks/focusableSelector';
|
|
80
84
|
import {useAnnounceRenderedText} from '../hooks/useAnnounceRenderedText';
|
|
@@ -436,11 +440,15 @@ const styles = stylex.create({
|
|
|
436
440
|
flexShrink: 0,
|
|
437
441
|
borderRadius: radiusVars['--radius-element'],
|
|
438
442
|
},
|
|
443
|
+
// The standard ring, from the focus tokens themselves: inside
|
|
444
|
+
// `stylex.create` StyleX resolves an imported `defineVars` group, and a
|
|
445
|
+
// plain object such as `FOCUS_OUTLINE_PARTS` compiles to variables nothing
|
|
446
|
+
// defines.
|
|
439
447
|
actionCellActive: {
|
|
440
|
-
outlineWidth:
|
|
441
|
-
outlineStyle:
|
|
442
|
-
outlineColor:
|
|
443
|
-
outlineOffset:
|
|
448
|
+
outlineWidth: focusVars['--focus-outline-width'],
|
|
449
|
+
outlineStyle: focusVars['--focus-outline-style'],
|
|
450
|
+
outlineColor: focusVars['--focus-outline-color'],
|
|
451
|
+
outlineOffset: focusVars['--focus-outline-offset'],
|
|
444
452
|
},
|
|
445
453
|
|
|
446
454
|
// Empty state
|
|
@@ -1752,8 +1760,8 @@ export function MultiSelector<T extends MultiSelectorOptionType>({
|
|
|
1752
1760
|
getActionCellId,
|
|
1753
1761
|
activeDescendantId,
|
|
1754
1762
|
onTriggerClick,
|
|
1755
|
-
onKeyDown,
|
|
1756
|
-
onItemMouseEnter,
|
|
1763
|
+
onKeyDown: onComboboxKeyDown,
|
|
1764
|
+
onItemMouseEnter: onComboboxItemMouseEnter,
|
|
1757
1765
|
} = useMultiCombobox({
|
|
1758
1766
|
selectableItems: sortedItems,
|
|
1759
1767
|
isDisabled: isDisabled || isEffectivelyReadOnly,
|
|
@@ -1784,6 +1792,29 @@ export function MultiSelector<T extends MultiSelectorOptionType>({
|
|
|
1784
1792
|
),
|
|
1785
1793
|
});
|
|
1786
1794
|
|
|
1795
|
+
// A hover moves the highlight too, but the focus ring follows the shared
|
|
1796
|
+
// interaction modality (architecture:interaction-modality INV1): it is read
|
|
1797
|
+
// whenever a key or the pointer moves the highlight, so an action cell
|
|
1798
|
+
// highlighted after pointer input keeps the hover treatment and no ring.
|
|
1799
|
+
useInteractionModalityTracking();
|
|
1800
|
+
const [isKeyboardHighlight, setIsKeyboardHighlight] = useState(
|
|
1801
|
+
() => getInteractionModality() === 'keyboard',
|
|
1802
|
+
);
|
|
1803
|
+
const onKeyDown = useCallback(
|
|
1804
|
+
(event: React.KeyboardEvent<HTMLElement>) => {
|
|
1805
|
+
onComboboxKeyDown(event);
|
|
1806
|
+
setIsKeyboardHighlight(getInteractionModality() === 'keyboard');
|
|
1807
|
+
},
|
|
1808
|
+
[onComboboxKeyDown],
|
|
1809
|
+
);
|
|
1810
|
+
const onItemMouseEnter = useCallback(
|
|
1811
|
+
(...args: Parameters<typeof onComboboxItemMouseEnter>) => {
|
|
1812
|
+
onComboboxItemMouseEnter(...args);
|
|
1813
|
+
setIsKeyboardHighlight(getInteractionModality() === 'keyboard');
|
|
1814
|
+
},
|
|
1815
|
+
[onComboboxItemMouseEnter],
|
|
1816
|
+
);
|
|
1817
|
+
|
|
1787
1818
|
// The trigger button owns its click so a consumer `onClick`, which lands on
|
|
1788
1819
|
// the button, composes after the built-in toggle. A press elsewhere in the
|
|
1789
1820
|
// control box (padding, start icon, chevron) still opens the panel.
|
|
@@ -2117,6 +2148,7 @@ export function MultiSelector<T extends MultiSelectorOptionType>({
|
|
|
2117
2148
|
styles.actionCell,
|
|
2118
2149
|
isHighlighted &&
|
|
2119
2150
|
highlightedCell === 'action' &&
|
|
2151
|
+
isKeyboardHighlight &&
|
|
2120
2152
|
styles.actionCellActive,
|
|
2121
2153
|
)}>
|
|
2122
2154
|
{isSelectAll || isCreate ? null : item.action}
|
|
@@ -2131,6 +2163,7 @@ export function MultiSelector<T extends MultiSelectorOptionType>({
|
|
|
2131
2163
|
[
|
|
2132
2164
|
isGrid,
|
|
2133
2165
|
highlightedCell,
|
|
2166
|
+
isKeyboardHighlight,
|
|
2134
2167
|
getActionCellId,
|
|
2135
2168
|
renderOption,
|
|
2136
2169
|
indicatorPosition,
|
|
@@ -6,8 +6,10 @@
|
|
|
6
6
|
* built Storybook.
|
|
7
7
|
* @output Real-engine proof for option actions: the action is visible at rest
|
|
8
8
|
* beside its row (`spec:AST-058` FR8), the keyboard reaches it through the
|
|
9
|
-
* inline-end arrow in both directions and fires it with Enter,
|
|
10
|
-
*
|
|
9
|
+
* inline-end arrow in both directions and fires it with Enter, the active
|
|
10
|
+
* action cell draws the standard focus ring when the shared input modality
|
|
11
|
+
* is keyboard and not after pointer input, and the highlight follows a real
|
|
12
|
+
* mouse across rows and actions.
|
|
11
13
|
* @position Run with `pnpm test:a11y-contract`; paint, pointer paths and the
|
|
12
14
|
* active descendant as an engine exposes them are not jsdom claims.
|
|
13
15
|
*/
|
|
@@ -143,6 +145,114 @@ test('the keyboard reaches an action through the inline-end arrow and fires it w
|
|
|
143
145
|
await expect.poll(async () => activeDescendant(page)).toBe('row:Bug');
|
|
144
146
|
});
|
|
145
147
|
|
|
148
|
+
test('the active action cell draws the standard focus ring', async ({page}) => {
|
|
149
|
+
await page.goto(
|
|
150
|
+
`${storybook.origin}/iframe.html?id=core-multiselector--row-actions&viewMode=story`,
|
|
151
|
+
);
|
|
152
|
+
const search = page.getByRole('combobox');
|
|
153
|
+
await expect(search).toBeFocused();
|
|
154
|
+
await search.press('ArrowDown'); // select-all
|
|
155
|
+
await search.press('ArrowDown'); // Feature (no action)
|
|
156
|
+
await search.press('ArrowDown'); // Bug
|
|
157
|
+
await search.press('ArrowRight');
|
|
158
|
+
await expect
|
|
159
|
+
.poll(async () => activeDescendant(page))
|
|
160
|
+
.toBe('gridcell:Edit Bug');
|
|
161
|
+
const ring = await page.evaluate(() => {
|
|
162
|
+
const id = document.activeElement?.getAttribute('aria-activedescendant');
|
|
163
|
+
const cell = id ? document.getElementById(id) : null;
|
|
164
|
+
if (!cell) {
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
const cs = getComputedStyle(cell);
|
|
168
|
+
// The ring the focus tokens describe, resolved by the browser on a probe.
|
|
169
|
+
const probe = document.createElement('div');
|
|
170
|
+
probe.style.cssText =
|
|
171
|
+
'outline-width:var(--focus-outline-width);outline-style:var(--focus-outline-style);outline-color:var(--focus-outline-color);outline-offset:var(--focus-outline-offset)';
|
|
172
|
+
document.body.appendChild(probe);
|
|
173
|
+
const want = getComputedStyle(probe);
|
|
174
|
+
const result = {
|
|
175
|
+
style: cs.outlineStyle,
|
|
176
|
+
width: cs.outlineWidth,
|
|
177
|
+
color: cs.outlineColor,
|
|
178
|
+
offset: cs.outlineOffset,
|
|
179
|
+
wantStyle: want.outlineStyle,
|
|
180
|
+
wantWidth: want.outlineWidth,
|
|
181
|
+
wantColor: want.outlineColor,
|
|
182
|
+
wantOffset: want.outlineOffset,
|
|
183
|
+
};
|
|
184
|
+
probe.remove();
|
|
185
|
+
return result;
|
|
186
|
+
});
|
|
187
|
+
expect(ring).not.toBeNull();
|
|
188
|
+
expect(ring!.style).not.toBe('none');
|
|
189
|
+
expect(ring!.width).not.toBe('0px');
|
|
190
|
+
expect(ring!.style).toBe(ring!.wantStyle);
|
|
191
|
+
expect(ring!.width).toBe(ring!.wantWidth);
|
|
192
|
+
expect(ring!.color).toBe(ring!.wantColor);
|
|
193
|
+
expect(ring!.offset).toBe(ring!.wantOffset);
|
|
194
|
+
// Back on the option cell, the action cell drops the ring.
|
|
195
|
+
await search.press('ArrowLeft');
|
|
196
|
+
await expect.poll(async () => activeDescendant(page)).toBe('row:Bug');
|
|
197
|
+
const after = await page
|
|
198
|
+
.getByRole('row', {name: 'Bug'})
|
|
199
|
+
.locator('[data-multi-selector-action-cell]')
|
|
200
|
+
.evaluate(cell => getComputedStyle(cell).outlineStyle);
|
|
201
|
+
expect(after).toBe('none');
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
test('the action-cell ring follows the shared input modality', async ({
|
|
205
|
+
page,
|
|
206
|
+
}) => {
|
|
207
|
+
await page.goto(
|
|
208
|
+
`${storybook.origin}/iframe.html?id=core-multiselector--row-actions&viewMode=story`,
|
|
209
|
+
);
|
|
210
|
+
const search = page.getByRole('combobox');
|
|
211
|
+
await expect(search).toBeFocused();
|
|
212
|
+
const actionCell = (name: string) =>
|
|
213
|
+
page.getByRole('row', {name}).locator('[data-multi-selector-action-cell]');
|
|
214
|
+
const outlineStyle = async (name: string) =>
|
|
215
|
+
actionCell(name).evaluate(cell => getComputedStyle(cell).outlineStyle);
|
|
216
|
+
const hover = async (name: string) => {
|
|
217
|
+
const cell = await box(actionCell(name));
|
|
218
|
+
await page.mouse.move(cell.x + cell.width / 2, cell.y + cell.height / 2, {
|
|
219
|
+
steps: 6,
|
|
220
|
+
});
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
// Pointer input: a click into the search field, then a hover onto an
|
|
224
|
+
// action, moves the highlight without the ring, and leaves none behind.
|
|
225
|
+
await search.click();
|
|
226
|
+
await hover('Bug');
|
|
227
|
+
await expect
|
|
228
|
+
.poll(async () => activeDescendant(page))
|
|
229
|
+
.toBe('gridcell:Edit Bug');
|
|
230
|
+
expect(await outlineStyle('Bug')).toBe('none');
|
|
231
|
+
// A modifier chord is not keyboard navigation for the shared tracker.
|
|
232
|
+
await search.press('Control+ArrowRight');
|
|
233
|
+
expect(await outlineStyle('Bug')).toBe('none');
|
|
234
|
+
await page.mouse.move(1, 1, {steps: 6});
|
|
235
|
+
expect(await outlineStyle('Bug')).toBe('none');
|
|
236
|
+
|
|
237
|
+
// Keyboard input: back onto the same cell, the ring is drawn.
|
|
238
|
+
await search.press('ArrowLeft');
|
|
239
|
+
await expect.poll(async () => activeDescendant(page)).toBe('row:Bug');
|
|
240
|
+
await search.press('ArrowRight');
|
|
241
|
+
await expect
|
|
242
|
+
.poll(async () => activeDescendant(page))
|
|
243
|
+
.toBe('gridcell:Edit Bug');
|
|
244
|
+
expect(await outlineStyle('Bug')).not.toBe('none');
|
|
245
|
+
|
|
246
|
+
// Still keyboard-driven, a hover that moves the highlight to another
|
|
247
|
+
// row's action carries the ring with it.
|
|
248
|
+
await hover('P0');
|
|
249
|
+
await expect
|
|
250
|
+
.poll(async () => activeDescendant(page))
|
|
251
|
+
.toBe('gridcell:Edit P0');
|
|
252
|
+
expect(await outlineStyle('P0')).not.toBe('none');
|
|
253
|
+
expect(await outlineStyle('Bug')).toBe('none');
|
|
254
|
+
});
|
|
255
|
+
|
|
146
256
|
test('under RTL the inline-end arrow is ArrowLeft', async ({page}) => {
|
|
147
257
|
await page.goto(
|
|
148
258
|
`${storybook.origin}/iframe.html?id=core-multiselector--row-actions-rtl&viewMode=story`,
|
|
@@ -280,10 +280,10 @@ describe('Popover', () => {
|
|
|
280
280
|
'utf8',
|
|
281
281
|
);
|
|
282
282
|
expect(popoverSource).toMatch(
|
|
283
|
-
/surfaceViewportFit:[\s\S]*?maxInlineSize: stylex\.firstThatWorks\(\s
|
|
283
|
+
/surfaceViewportFit:[\s\S]*?maxInlineSize: stylex\.firstThatWorks\(\s*`calc\(\$\{layerViewportInset\.maxInlineSize\}\)`/,
|
|
284
284
|
);
|
|
285
285
|
expect(popoverSource).toMatch(
|
|
286
|
-
/surfaceViewportFit:[\s\S]*?maxBlockSize: stylex\.firstThatWorks\(\s
|
|
286
|
+
/surfaceViewportFit:[\s\S]*?maxBlockSize: stylex\.firstThatWorks\(\s*`calc\(\$\{layerViewportInset\.maxBlockSize\}\)`/,
|
|
287
287
|
);
|
|
288
288
|
expect(popoverSource).not.toMatch(/safe-area-inset|calc\(100%/);
|
|
289
289
|
expect(surface?.className).not.toContain(
|
package/src/Popover/Popover.tsx
CHANGED
|
@@ -272,12 +272,12 @@ const styles = stylex.create({
|
|
|
272
272
|
surfaceViewportFit: {
|
|
273
273
|
boxSizing: 'border-box',
|
|
274
274
|
maxInlineSize: stylex.firstThatWorks(
|
|
275
|
-
layerViewportInset.maxInlineSize
|
|
276
|
-
layerViewportInset.maxInlineSizeFallback
|
|
275
|
+
`calc(${layerViewportInset.maxInlineSize})`,
|
|
276
|
+
`calc(${layerViewportInset.maxInlineSizeFallback})`,
|
|
277
277
|
),
|
|
278
278
|
maxBlockSize: stylex.firstThatWorks(
|
|
279
|
-
layerViewportInset.maxBlockSize
|
|
280
|
-
layerViewportInset.maxBlockSizeFallback
|
|
279
|
+
`calc(${layerViewportInset.maxBlockSize})`,
|
|
280
|
+
`calc(${layerViewportInset.maxBlockSizeFallback})`,
|
|
281
281
|
),
|
|
282
282
|
},
|
|
283
283
|
surfaceScrollable: {
|
|
@@ -70,6 +70,10 @@ const focusOutlineLonghands = {
|
|
|
70
70
|
* independently overridable — and every part stays a var, so a theme's
|
|
71
71
|
* `--focus-outline-*` overrides still flow through.
|
|
72
72
|
*
|
|
73
|
+
* NOT usable inside another file's `stylex.create`, for the same reason as
|
|
74
|
+
* {@link FOCUS_OUTLINE}: StyleX compiles each member there to a CSS variable
|
|
75
|
+
* nothing defines. Compose the `focusVars` tokens instead.
|
|
76
|
+
*
|
|
73
77
|
* Keys are camelCase to match `HTMLElement.style`, so this spreads straight
|
|
74
78
|
* onto an element:
|
|
75
79
|
*
|