@astryxdesign/core 0.4.6 → 0.4.7-canary.29be96d
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/CHANGELOG.md +35 -0
- package/dist/BottomSheet/BottomSheet.d.ts +1 -0
- package/dist/BottomSheet/BottomSheet.d.ts.map +1 -1
- package/dist/BottomSheet/BottomSheet.js +54 -15
- package/dist/BottomSheet/BottomSheetEdgeTint.d.ts +6 -0
- package/dist/BottomSheet/BottomSheetEdgeTint.d.ts.map +1 -0
- package/dist/BottomSheet/BottomSheetEdgeTint.js +62 -0
- package/dist/BottomSheet/BottomSheetPanel.d.ts.map +1 -1
- package/dist/BottomSheet/BottomSheetPanel.js +1 -0
- package/dist/BottomSheet/BottomSheetSwitcher.d.ts +1 -0
- package/dist/BottomSheet/BottomSheetSwitcher.d.ts.map +1 -1
- package/dist/BottomSheet/BottomSheetSwitcher.js +10 -4
- package/dist/BottomSheet/useSheetGestures.d.ts.map +1 -1
- package/dist/BottomSheet/useSheetGestures.js +23 -5
- package/dist/Breadcrumbs/BreadcrumbItem.d.ts.map +1 -1
- package/dist/Breadcrumbs/BreadcrumbItem.js +5 -5
- package/dist/Calendar/Calendar.d.ts +3 -1
- package/dist/Calendar/Calendar.d.ts.map +1 -1
- package/dist/Calendar/Calendar.js +13 -14
- package/dist/Calendar/getInitialFocusDate.d.ts +46 -0
- package/dist/Calendar/getInitialFocusDate.d.ts.map +1 -0
- package/dist/Calendar/getInitialFocusDate.js +62 -0
- package/dist/Chat/ChatMessageList.d.ts +22 -3
- package/dist/Chat/ChatMessageList.d.ts.map +1 -1
- package/dist/Chat/ChatMessageList.js +6 -3
- package/dist/DateInput/TouchDateField.d.ts.map +1 -1
- package/dist/DateInput/TouchDateField.js +34 -1
- package/dist/Dialog/Dialog.d.ts +7 -1
- package/dist/Dialog/Dialog.d.ts.map +1 -1
- package/dist/Dialog/Dialog.js +48 -22
- package/dist/Heading/Heading.d.ts.map +1 -1
- package/dist/Heading/Heading.js +6 -2
- package/dist/Kbd/Kbd.d.ts.map +1 -1
- package/dist/Kbd/Kbd.js +10 -3
- package/dist/Markdown/index.d.ts +1 -1
- package/dist/Markdown/index.d.ts.map +1 -1
- package/dist/Markdown/parser.d.ts +38 -1
- package/dist/Markdown/parser.d.ts.map +1 -1
- package/dist/Markdown/parser.js +149 -23
- package/dist/MultiSelector/MultiSelector.d.ts +15 -1
- package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
- package/dist/MultiSelector/MultiSelector.js +17 -6
- package/dist/MultiSelector/index.d.ts +1 -1
- package/dist/MultiSelector/index.d.ts.map +1 -1
- package/dist/Selector/Selector.d.ts.map +1 -1
- package/dist/Selector/Selector.js +5 -0
- package/dist/Stepper/Step.d.ts +140 -0
- package/dist/Stepper/Step.d.ts.map +1 -0
- package/dist/Stepper/Step.js +1045 -0
- package/dist/Stepper/StepStatus.d.ts +24 -0
- package/dist/Stepper/StepStatus.d.ts.map +1 -0
- package/dist/Stepper/StepStatus.js +1 -0
- package/dist/Stepper/Stepper.d.ts +93 -0
- package/dist/Stepper/Stepper.d.ts.map +1 -0
- package/dist/Stepper/Stepper.js +184 -0
- package/dist/Stepper/StepperContext.d.ts +41 -0
- package/dist/Stepper/StepperContext.d.ts.map +1 -0
- package/dist/Stepper/StepperContext.js +34 -0
- package/dist/Stepper/index.d.ts +8 -0
- package/dist/Stepper/index.d.ts.map +1 -0
- package/dist/Stepper/index.js +7 -0
- package/dist/Stepper/stepper.stylex.d.ts +15 -0
- package/dist/Stepper/stepper.stylex.d.ts.map +1 -0
- package/dist/Stepper/stepper.stylex.js +20 -0
- package/dist/TabList/Tab.d.ts.map +1 -1
- package/dist/TabList/Tab.js +5 -1
- package/dist/Table/BaseTable.d.ts.map +1 -1
- package/dist/Table/BaseTable.js +4 -1
- package/dist/Table/plugins/groupedRows/useTableGroupedRows.d.ts.map +1 -1
- package/dist/Table/plugins/groupedRows/useTableGroupedRows.js +20 -8
- package/dist/Table/plugins/rowStatus/useTableRowStatus.d.ts.map +1 -1
- package/dist/Table/plugins/rowStatus/useTableRowStatus.js +10 -3
- package/dist/Table/plugins/selection/useTableSelection.d.ts +16 -0
- package/dist/Table/plugins/selection/useTableSelection.d.ts.map +1 -1
- package/dist/Table/plugins/selection/useTableSelection.js +19 -5
- package/dist/Table/types.d.ts +22 -4
- package/dist/Table/types.d.ts.map +1 -1
- package/dist/Table/useBaseTablePlugins.d.ts.map +1 -1
- package/dist/Table/useBaseTablePlugins.js +5 -0
- package/dist/Text/Text.d.ts.map +1 -1
- package/dist/Text/Text.js +6 -2
- package/dist/astryx.css +41 -1
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +1 -0
- package/dist/hooks/useHotkeys.d.ts.map +1 -1
- package/dist/hooks/useHotkeys.js +10 -3
- package/dist/hooks/useListFocus.d.ts +5 -2
- package/dist/hooks/useListFocus.d.ts.map +1 -1
- package/dist/hooks/useListFocus.js +12 -6
- package/dist/hooks/useMergedRefs.d.ts +18 -0
- package/dist/hooks/useMergedRefs.d.ts.map +1 -0
- package/dist/hooks/useMergedRefs.js +25 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/utils/timeParser.d.ts +1 -1
- package/dist/utils/timeParser.d.ts.map +1 -1
- package/dist/utils/timeParser.js +26 -12
- package/locales/en.json +14 -6
- package/locales/pseudo.json +6 -0
- package/package.json +8 -3
- package/src/Avatar/Avatar.doc.mjs +2 -1
- package/src/BottomSheet/BottomSheet.test.tsx +123 -0
- package/src/BottomSheet/BottomSheet.tsx +36 -4
- package/src/BottomSheet/BottomSheetEdgeTint.test.tsx +225 -0
- package/src/BottomSheet/BottomSheetEdgeTint.tsx +82 -0
- package/src/BottomSheet/BottomSheetPanel.test.tsx +66 -0
- package/src/BottomSheet/BottomSheetPanel.tsx +19 -0
- package/src/BottomSheet/BottomSheetSwitcher.tsx +13 -0
- package/src/BottomSheet/useSheetGestures.test.ts +27 -0
- package/src/BottomSheet/useSheetGestures.ts +25 -5
- package/src/Breadcrumbs/BreadcrumbItem.tsx +2 -1
- package/src/Button/Button.doc.mjs +22 -0
- package/src/Calendar/Calendar.doc.mjs +4 -3
- package/src/Calendar/Calendar.test.tsx +52 -0
- package/src/Calendar/Calendar.tsx +18 -15
- package/src/Calendar/getInitialFocusDate.test.ts +94 -0
- package/src/Calendar/getInitialFocusDate.ts +93 -0
- package/src/Chat/ChatMessageList.doc.mjs +9 -1
- package/src/Chat/ChatMessageList.test.tsx +46 -0
- package/src/Chat/ChatMessageList.tsx +28 -4
- package/src/CodeBlock/CodeBlock.doc.mjs +6 -0
- package/src/ContextMenu/ContextMenu.doc.mjs +5 -0
- package/src/DateInput/DateInputTouch.test.tsx +36 -0
- package/src/DateInput/TouchDateField.tsx +35 -1
- package/src/Dialog/Dialog.doc.mjs +8 -3
- package/src/Dialog/Dialog.test.tsx +71 -0
- package/src/Dialog/Dialog.tsx +72 -20
- package/src/Field/Field.doc.mjs +11 -0
- package/src/Heading/Heading.test.tsx +23 -1
- package/src/Heading/Heading.tsx +6 -2
- package/src/Kbd/Kbd.test.tsx +17 -0
- package/src/Kbd/Kbd.tsx +10 -3
- package/src/Link/Link.doc.mjs +11 -0
- package/src/Markdown/index.ts +1 -0
- package/src/Markdown/parser.perf.test.ts +71 -1
- package/src/Markdown/parser.test.ts +145 -2
- package/src/Markdown/parser.ts +208 -22
- package/src/MoreMenu/MoreMenu.doc.mjs +7 -1
- package/src/MultiSelector/MultiSelector.doc.mjs +10 -0
- package/src/MultiSelector/MultiSelector.test.tsx +105 -0
- package/src/MultiSelector/MultiSelector.tsx +50 -6
- package/src/MultiSelector/index.ts +1 -0
- package/src/NumberInput/NumberInput.doc.mjs +5 -0
- package/src/Popover/Popover.test.tsx +27 -1
- package/src/PowerSearch/PowerSearch.doc.mjs +10 -0
- package/src/Selector/Selector.doc.mjs +11 -0
- package/src/Selector/Selector.test.tsx +21 -0
- package/src/Selector/Selector.tsx +11 -1
- package/src/Stepper/Step.doc.mjs +98 -0
- package/src/Stepper/Step.tsx +1638 -0
- package/src/Stepper/StepStatus.ts +26 -0
- package/src/Stepper/Stepper.doc.mjs +370 -0
- package/src/Stepper/Stepper.test.tsx +1024 -0
- package/src/Stepper/Stepper.tsx +258 -0
- package/src/Stepper/StepperContext.ts +71 -0
- package/src/Stepper/index.ts +18 -0
- package/src/Stepper/stepper.stylex.ts +19 -0
- package/src/TabList/Tab.tsx +5 -1
- package/src/TabList/TabList.test.tsx +21 -4
- package/src/Table/BaseTable.tsx +6 -3
- package/src/Table/Table.test.tsx +35 -0
- package/src/Table/plugins/groupedRows/useTableGroupedRows-perf.test.tsx +112 -0
- package/src/Table/plugins/groupedRows/useTableGroupedRows.test.tsx +100 -0
- package/src/Table/plugins/groupedRows/useTableGroupedRows.tsx +17 -8
- package/src/Table/plugins/rowStatus/useTableRowStatus.test.tsx +13 -7
- package/src/Table/plugins/rowStatus/useTableRowStatus.tsx +12 -3
- package/src/Table/plugins/selection/useTableSelection.test.tsx +76 -0
- package/src/Table/plugins/selection/useTableSelection.tsx +40 -7
- package/src/Table/types.ts +22 -4
- package/src/Table/useBaseTablePlugins.ts +5 -0
- package/src/Table/useTableGroupedRows.doc.mjs +5 -4
- package/src/Table/useTableRowStatus.doc.mjs +1 -1
- package/src/Table/useTableSelection.doc.mjs +31 -0
- package/src/Text/Text.test.tsx +23 -1
- package/src/Text/Text.tsx +6 -2
- package/src/TextInput/TextInput.doc.mjs +10 -0
- package/src/Toast/Toast.doc.mjs +6 -0
- package/src/Tokenizer/Tokenizer.doc.mjs +10 -0
- package/src/Toolbar/Toolbar.doc.mjs +5 -0
- package/src/__tests__/apiContractDrift.test.tsx +80 -0
- package/src/__tests__/fieldContract.test.tsx +24 -0
- package/src/__tests__/structuralComponentContract.test.tsx +39 -0
- package/src/hooks/index.ts +2 -0
- package/src/hooks/useHotkeys.test.ts +18 -0
- package/src/hooks/useHotkeys.ts +10 -3
- package/src/hooks/useListFocus.doc.mjs +2 -2
- package/src/hooks/useListFocus.test.tsx +65 -3
- package/src/hooks/useListFocus.ts +15 -7
- package/src/hooks/useMergedRefs.doc.mjs +79 -0
- package/src/hooks/useMergedRefs.test.tsx +62 -0
- package/src/hooks/useMergedRefs.ts +36 -0
- package/src/index.ts +1 -0
- package/src/theme/MediaTheme.doc.mjs +5 -5
- package/src/utils/timeParser.test.ts +6 -1
- package/src/utils/timeParser.ts +35 -13
|
@@ -71,6 +71,16 @@ export const docs = {
|
|
|
71
71
|
'Displays a "Required" indicator next to the label and sets aria-required. Mutually exclusive with isOptional.',
|
|
72
72
|
default: 'false',
|
|
73
73
|
},
|
|
74
|
+
{
|
|
75
|
+
name: 'onEnter',
|
|
76
|
+
type: '() => void',
|
|
77
|
+
description: 'Callback fired when the user presses the Enter key.',
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
name: 'onKeyDown',
|
|
81
|
+
type: '(e: KeyboardEvent<HTMLInputElement>) => void',
|
|
82
|
+
description: 'Callback fired on keydown events on the input.',
|
|
83
|
+
},
|
|
74
84
|
{
|
|
75
85
|
name: 'isDisabled',
|
|
76
86
|
type: 'boolean',
|
package/src/Toast/Toast.doc.mjs
CHANGED
|
@@ -60,6 +60,12 @@ export const docs = {
|
|
|
60
60
|
type: '(reason: "auto" | "manual") => void',
|
|
61
61
|
description: 'Callback fired when the toast is removed.',
|
|
62
62
|
},
|
|
63
|
+
{
|
|
64
|
+
name: 'onDismiss',
|
|
65
|
+
type: '(reason: "auto" | "manual") => void',
|
|
66
|
+
description: 'Callback fired when the toast is dismissed.',
|
|
67
|
+
required: true,
|
|
68
|
+
},
|
|
63
69
|
], theming: {
|
|
64
70
|
targets: [
|
|
65
71
|
{className: 'astryx-toast', visualProps: ['type']},
|
|
@@ -201,6 +201,16 @@ export const docs = {
|
|
|
201
201
|
description:
|
|
202
202
|
'Width of the field (number = pixels, string used as-is, e.g. "100%"). Sizes the whole field (label, control, and status) so they stay aligned.',
|
|
203
203
|
},
|
|
204
|
+
{
|
|
205
|
+
name: 'onFocus',
|
|
206
|
+
type: '(e: FocusEvent<HTMLInputElement>) => void',
|
|
207
|
+
description: 'Fires when focus enters the tokenizer input.',
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
name: 'onBlur',
|
|
211
|
+
type: '(e: FocusEvent<HTMLInputElement>) => void',
|
|
212
|
+
description: 'Fires when focus leaves the tokenizer input.',
|
|
213
|
+
},
|
|
204
214
|
{
|
|
205
215
|
name: 'xstyle',
|
|
206
216
|
type: 'StyleXStyles',
|
|
@@ -133,6 +133,11 @@ export const docs = {
|
|
|
133
133
|
description: 'Visual variant passed to Section.',
|
|
134
134
|
default: "'transparent'",
|
|
135
135
|
},
|
|
136
|
+
{
|
|
137
|
+
name: 'dividers',
|
|
138
|
+
type: "Array<'top' | 'bottom' | 'start' | 'end'>",
|
|
139
|
+
description: 'Borders/dividers rendered around the toolbar.',
|
|
140
|
+
},
|
|
136
141
|
{
|
|
137
142
|
name: 'xstyle',
|
|
138
143
|
type: 'StyleXStyles',
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
import {describe, it, expect} from 'vitest';
|
|
4
|
+
|
|
5
|
+
import {docs as ButtonDocs} from '../Button/Button.doc.mjs';
|
|
6
|
+
import {docs as ContextMenuDocs} from '../ContextMenu/ContextMenu.doc.mjs';
|
|
7
|
+
import {docs as DialogDocs} from '../Dialog/Dialog.doc.mjs';
|
|
8
|
+
import {docs as LinkDocs} from '../Link/Link.doc.mjs';
|
|
9
|
+
import {docs as MoreMenuDocs} from '../MoreMenu/MoreMenu.doc.mjs';
|
|
10
|
+
import {docs as NumberInputDocs} from '../NumberInput/NumberInput.doc.mjs';
|
|
11
|
+
import {docs as PowerSearchDocs} from '../PowerSearch/PowerSearch.doc.mjs';
|
|
12
|
+
import {docs as SelectorDocs} from '../Selector/Selector.doc.mjs';
|
|
13
|
+
import {docs as TextInputDocs} from '../TextInput/TextInput.doc.mjs';
|
|
14
|
+
import {docs as ToastDocs} from '../Toast/Toast.doc.mjs';
|
|
15
|
+
import {docs as TokenizerDocs} from '../Tokenizer/Tokenizer.doc.mjs';
|
|
16
|
+
|
|
17
|
+
// `docs` is the ComponentDoc union (single vs multi component); narrow it
|
|
18
|
+
// for structural access to either the top-level props or the first
|
|
19
|
+
// sub-component's props.
|
|
20
|
+
function getProps(docs: unknown): {name: string}[] {
|
|
21
|
+
const doc = docs as {
|
|
22
|
+
props?: {name: string}[];
|
|
23
|
+
components?: {props?: {name: string}[]}[];
|
|
24
|
+
};
|
|
25
|
+
return doc.props || doc.components?.[0]?.props || [];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
describe('API Contract Drift Audit (#4163)', () => {
|
|
29
|
+
it('documents Button link mode props', () => {
|
|
30
|
+
const props = getProps(ButtonDocs).map(p => p.name);
|
|
31
|
+
expect(props).toContain('href');
|
|
32
|
+
expect(props).toContain('as');
|
|
33
|
+
expect(props).toContain('target');
|
|
34
|
+
expect(props).toContain('rel');
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('documents Toast onDismiss callback', () => {
|
|
38
|
+
const props = getProps(ToastDocs).map(p => p.name);
|
|
39
|
+
expect(props).toContain('onDismiss');
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('documents ContextMenu and MoreMenu onOpenChange callback', () => {
|
|
43
|
+
const contextProps = getProps(ContextMenuDocs).map(p => p.name);
|
|
44
|
+
const moreProps = getProps(MoreMenuDocs).map(p => p.name);
|
|
45
|
+
expect(contextProps).toContain('onOpenChange');
|
|
46
|
+
expect(moreProps).toContain('onOpenChange');
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('documents Selector startIcon and isLoading props', () => {
|
|
50
|
+
const props = getProps(SelectorDocs).map(p => p.name);
|
|
51
|
+
expect(props).toContain('startIcon');
|
|
52
|
+
expect(props).toContain('isLoading');
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('documents TextInput and NumberInput onEnter / onKeyDown callbacks', () => {
|
|
56
|
+
const textProps = getProps(TextInputDocs).map(p => p.name);
|
|
57
|
+
const numProps = getProps(NumberInputDocs).map(p => p.name);
|
|
58
|
+
expect(textProps).toContain('onEnter');
|
|
59
|
+
expect(textProps).toContain('onKeyDown');
|
|
60
|
+
expect(numProps).toContain('onEnter');
|
|
61
|
+
expect(numProps).toContain('onKeyDown');
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('documents PowerSearch and Tokenizer onFocus / onBlur callbacks', () => {
|
|
65
|
+
const powerProps = getProps(PowerSearchDocs).map(p => p.name);
|
|
66
|
+
const tokProps = getProps(TokenizerDocs).map(p => p.name);
|
|
67
|
+
expect(powerProps).toContain('onFocus');
|
|
68
|
+
expect(powerProps).toContain('onBlur');
|
|
69
|
+
expect(tokProps).toContain('onFocus');
|
|
70
|
+
expect(tokProps).toContain('onBlur');
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('documents Link download / referrerPolicy and Dialog padding props', () => {
|
|
74
|
+
const linkProps = getProps(LinkDocs).map(p => p.name);
|
|
75
|
+
const dialogProps = getProps(DialogDocs).map(p => p.name);
|
|
76
|
+
expect(linkProps).toContain('download');
|
|
77
|
+
expect(linkProps).toContain('referrerPolicy');
|
|
78
|
+
expect(dialogProps).toContain('padding');
|
|
79
|
+
});
|
|
80
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
import {describe, it, expect} from 'vitest';
|
|
4
|
+
|
|
5
|
+
import {docs as FieldDocs} from '../Field/Field.doc.mjs';
|
|
6
|
+
|
|
7
|
+
// `docs` is the ComponentDoc union (single vs multi component); narrow it
|
|
8
|
+
// for structural access to either the top-level props or the first
|
|
9
|
+
// sub-component's props.
|
|
10
|
+
function getProps(docs: unknown): {name: string}[] {
|
|
11
|
+
const doc = docs as {
|
|
12
|
+
props?: {name: string}[];
|
|
13
|
+
components?: {props?: {name: string}[]}[];
|
|
14
|
+
};
|
|
15
|
+
return doc.props || doc.components?.[0]?.props || [];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
describe('Field Component API Contract Drift (#4163)', () => {
|
|
19
|
+
it('documents Field labelID and isGroupLabel props', () => {
|
|
20
|
+
const props = getProps(FieldDocs).map(p => p.name);
|
|
21
|
+
expect(props).toContain('labelID');
|
|
22
|
+
expect(props).toContain('isGroupLabel');
|
|
23
|
+
});
|
|
24
|
+
});
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
import {describe, it, expect} from 'vitest';
|
|
4
|
+
|
|
5
|
+
import {docs as CodeBlockDocs} from '../CodeBlock/CodeBlock.doc.mjs';
|
|
6
|
+
import {docs as StackDocs} from '../Stack/Stack.doc.mjs';
|
|
7
|
+
import {docs as ToolbarDocs} from '../Toolbar/Toolbar.doc.mjs';
|
|
8
|
+
|
|
9
|
+
// `docs` is the ComponentDoc union (single vs multi component); narrow it
|
|
10
|
+
// for structural access to either the top-level props or the first
|
|
11
|
+
// sub-component's props.
|
|
12
|
+
function getProps(docs: unknown): {name: string}[] {
|
|
13
|
+
const doc = docs as {
|
|
14
|
+
props?: {name: string}[];
|
|
15
|
+
components?: {props?: {name: string}[]}[];
|
|
16
|
+
};
|
|
17
|
+
return doc.props || doc.components?.[0]?.props || [];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
describe('Structural Component API Contract Drift (#4163)', () => {
|
|
21
|
+
it('documents CodeBlock highlightMode prop', () => {
|
|
22
|
+
const props = getProps(CodeBlockDocs).map(p => p.name);
|
|
23
|
+
expect(props).toContain('highlightMode');
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('documents Toolbar dividers prop', () => {
|
|
27
|
+
const props = getProps(ToolbarDocs).map(p => p.name);
|
|
28
|
+
expect(props).toContain('dividers');
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('documents Stack direction and base props', () => {
|
|
32
|
+
const props = getProps(StackDocs).map(p => p.name);
|
|
33
|
+
expect(props).toContain('direction');
|
|
34
|
+
expect(props).toContain('gap');
|
|
35
|
+
expect(props).toContain('padding');
|
|
36
|
+
expect(props).toContain('width');
|
|
37
|
+
expect(props).toContain('height');
|
|
38
|
+
});
|
|
39
|
+
});
|
package/src/hooks/index.ts
CHANGED
|
@@ -56,6 +56,8 @@ export type {UseTypeaheadOptions, UseTypeaheadReturn} from './useTypeahead';
|
|
|
56
56
|
|
|
57
57
|
export {useMediaQuery} from './useMediaQuery';
|
|
58
58
|
|
|
59
|
+
export {useMergedRefs} from './useMergedRefs';
|
|
60
|
+
|
|
59
61
|
export {useOverflow} from './useOverflow';
|
|
60
62
|
export type {UseOverflowOptions, UseOverflowReturn} from './useOverflow';
|
|
61
63
|
|
|
@@ -67,6 +67,24 @@ describe('useHotkeys', () => {
|
|
|
67
67
|
expect(onPress).toHaveBeenCalledTimes(1);
|
|
68
68
|
});
|
|
69
69
|
|
|
70
|
+
it('reads a blank userAgentData.platform as unknown, not as non-Apple', () => {
|
|
71
|
+
// Builds that rewrite their client-hints identity expose the key with an
|
|
72
|
+
// empty value; navigator.platform is the only surface left that answers.
|
|
73
|
+
vi.stubGlobal('navigator', {
|
|
74
|
+
userAgentData: {platform: ''},
|
|
75
|
+
platform: 'MacIntel',
|
|
76
|
+
});
|
|
77
|
+
const onPress = vi.fn();
|
|
78
|
+
renderHook(() => useHotkeys([{keys: 'mod+k', onPress}]));
|
|
79
|
+
|
|
80
|
+
press('k', {ctrlKey: true});
|
|
81
|
+
expect(onPress).not.toHaveBeenCalled();
|
|
82
|
+
|
|
83
|
+
const event = press('k', {metaKey: true});
|
|
84
|
+
expect(onPress).toHaveBeenCalledTimes(1);
|
|
85
|
+
expect(onPress).toHaveBeenCalledWith(event);
|
|
86
|
+
});
|
|
87
|
+
|
|
70
88
|
it('does not fire a bare key when modifiers are held', () => {
|
|
71
89
|
stubApplePlatform();
|
|
72
90
|
const onPress = vi.fn();
|
package/src/hooks/useHotkeys.ts
CHANGED
|
@@ -73,8 +73,9 @@ const KEY_ALIASES: Record<string, string> = {
|
|
|
73
73
|
|
|
74
74
|
/**
|
|
75
75
|
* Detects whether the current platform is macOS/iOS.
|
|
76
|
-
* Prefers the User-Agent Client Hints API when
|
|
77
|
-
* falls back to navigator.platform (deprecated but universally
|
|
76
|
+
* Prefers the User-Agent Client Hints API when it names a platform (modern
|
|
77
|
+
* Chrome/Edge), falls back to navigator.platform (deprecated but universally
|
|
78
|
+
* supported) when it is absent or blank.
|
|
78
79
|
* Mirrors the detection used by Kbd so displayed and handled shortcuts agree.
|
|
79
80
|
*/
|
|
80
81
|
function isApplePlatform(): boolean {
|
|
@@ -83,7 +84,13 @@ function isApplePlatform(): boolean {
|
|
|
83
84
|
}
|
|
84
85
|
const uaData = 'userAgentData' in navigator ? navigator.userAgentData : null;
|
|
85
86
|
if (uaData && typeof uaData === 'object' && 'platform' in uaData) {
|
|
86
|
-
|
|
87
|
+
const uaPlatform = (uaData as {platform?: unknown}).platform;
|
|
88
|
+
// A blank platform is no answer, not a negative one. Builds that rewrite
|
|
89
|
+
// their client-hints identity ship '', so fall through rather than
|
|
90
|
+
// reading it as "not Apple".
|
|
91
|
+
if (typeof uaPlatform === 'string' && uaPlatform.trim() !== '') {
|
|
92
|
+
return /mac/i.test(uaPlatform);
|
|
93
|
+
}
|
|
87
94
|
}
|
|
88
95
|
return /Mac|iPhone|iPad|iPod/.test(navigator.platform ?? '');
|
|
89
96
|
}
|
|
@@ -36,7 +36,7 @@ export const docs = {
|
|
|
36
36
|
{
|
|
37
37
|
name: 'options.onEscape',
|
|
38
38
|
type: '() => void',
|
|
39
|
-
description: 'Callback when Escape key is pressed (e.g., close menu).',
|
|
39
|
+
description: 'Callback when Escape key is pressed (e.g., close menu). Supplying it also consumes the key (preventDefault); without it Escape passes through to the surrounding layer.',
|
|
40
40
|
required: false,
|
|
41
41
|
},
|
|
42
42
|
{
|
|
@@ -144,7 +144,7 @@ export const docsDense = {
|
|
|
144
144
|
'options.itemSelector': 'selector for focusable items in list.',
|
|
145
145
|
'options.boundarySelector': "boundary selector for lists that contain nested lists (e.g. submenu flyouts); scopes items + key handling to this level.",
|
|
146
146
|
'options.wrap': 'whether arrow navigation wraps around at ends.',
|
|
147
|
-
'options.onEscape': 'callback when Escape key pressed (e.g. close menu).',
|
|
147
|
+
'options.onEscape': 'callback when Escape key pressed (e.g. close menu). Also consumes the key; without it Escape passes through to the surrounding layer.',
|
|
148
148
|
'options.orientation': "navigation orientation. 'horizontal' uses ArrowLeft/ArrowRight, 'vertical' uses ArrowUp/ArrowDown, 'both' accepts all four arrows.",
|
|
149
149
|
'options.hasHomeEnd': 'whether Home/End jump to first/last enabled item.',
|
|
150
150
|
'options.isRtl': 'ArrowLeft/ArrowRight swap for horizontal nav (RTL). default: auto-detect from container computed direction; explicit boolean wins.',
|
|
@@ -3,14 +3,14 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* @file useListFocus.test.tsx
|
|
5
5
|
* @input Uses vitest, @testing-library/react, useListFocus hook
|
|
6
|
-
* @output Unit tests for useListFocus disabled-item skipping, navigation,
|
|
7
|
-
* RTL auto-detection
|
|
6
|
+
* @output Unit tests for useListFocus disabled-item skipping, navigation,
|
|
7
|
+
* Escape consumption, and RTL auto-detection
|
|
8
8
|
* @position Testing; validates useListFocus.ts keyboard navigation
|
|
9
9
|
*
|
|
10
10
|
* SYNC: When useListFocus.ts changes, update tests to match new behavior
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import {describe, it, expect} from 'vitest';
|
|
13
|
+
import {describe, it, expect, vi} from 'vitest';
|
|
14
14
|
import type {KeyboardEvent as ReactKeyboardEvent} from 'react';
|
|
15
15
|
import {render, screen, fireEvent} from '@testing-library/react';
|
|
16
16
|
import {useListFocus} from './useListFocus';
|
|
@@ -525,3 +525,65 @@ describe('useListFocus boundarySelector (nested lists)', () => {
|
|
|
525
525
|
expect(innerProbe).toHaveAttribute('data-owns', 'false');
|
|
526
526
|
});
|
|
527
527
|
});
|
|
528
|
+
|
|
529
|
+
// A list inside a host that dismisses on Escape. The host's guard mirrors
|
|
530
|
+
// `useFocusTrap`: it acts only on a key no inner handler has consumed.
|
|
531
|
+
function EscapeHost({
|
|
532
|
+
onEscape,
|
|
533
|
+
onHostEscape,
|
|
534
|
+
}: {
|
|
535
|
+
onEscape?: () => void;
|
|
536
|
+
onHostEscape: () => void;
|
|
537
|
+
}) {
|
|
538
|
+
const {listRef, handleKeyDown} = useListFocus<HTMLDivElement>({onEscape});
|
|
539
|
+
return (
|
|
540
|
+
<div
|
|
541
|
+
data-testid="host"
|
|
542
|
+
onKeyDown={e => {
|
|
543
|
+
if (e.key === 'Escape' && !e.defaultPrevented) {
|
|
544
|
+
onHostEscape();
|
|
545
|
+
}
|
|
546
|
+
}}>
|
|
547
|
+
<div ref={listRef} role="menu" onKeyDown={handleKeyDown}>
|
|
548
|
+
<div role="menuitem" tabIndex={-1} data-testid="One">
|
|
549
|
+
One
|
|
550
|
+
</div>
|
|
551
|
+
<div role="menuitem" tabIndex={-1} data-testid="Two">
|
|
552
|
+
Two
|
|
553
|
+
</div>
|
|
554
|
+
</div>
|
|
555
|
+
</div>
|
|
556
|
+
);
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
describe('useListFocus Escape', () => {
|
|
560
|
+
it('leaves Escape to the host when no onEscape is supplied', () => {
|
|
561
|
+
const onHostEscape = vi.fn();
|
|
562
|
+
render(<EscapeHost onHostEscape={onHostEscape} />);
|
|
563
|
+
|
|
564
|
+
fireEvent.keyDown(screen.getByRole('menu'), {key: 'Escape'});
|
|
565
|
+
expect(onHostEscape).toHaveBeenCalledTimes(1);
|
|
566
|
+
});
|
|
567
|
+
|
|
568
|
+
it('consumes Escape and runs onEscape when one is supplied', () => {
|
|
569
|
+
const onEscape = vi.fn();
|
|
570
|
+
const onHostEscape = vi.fn();
|
|
571
|
+
render(<EscapeHost onEscape={onEscape} onHostEscape={onHostEscape} />);
|
|
572
|
+
|
|
573
|
+
fireEvent.keyDown(screen.getByRole('menu'), {key: 'Escape'});
|
|
574
|
+
expect(onEscape).toHaveBeenCalledTimes(1);
|
|
575
|
+
expect(onHostEscape).not.toHaveBeenCalled();
|
|
576
|
+
});
|
|
577
|
+
|
|
578
|
+
it('still consumes arrow keys with no onEscape (page-scroll suppression)', () => {
|
|
579
|
+
render(<EscapeHost onHostEscape={() => {}} />);
|
|
580
|
+
screen.getByTestId('One').focus();
|
|
581
|
+
|
|
582
|
+
// fireEvent returns false when a handler cancelled the event.
|
|
583
|
+
const wasCancelled = !fireEvent.keyDown(screen.getByRole('menu'), {
|
|
584
|
+
key: 'ArrowDown',
|
|
585
|
+
});
|
|
586
|
+
expect(wasCancelled).toBe(true);
|
|
587
|
+
expect(screen.getByTestId('Two')).toHaveFocus();
|
|
588
|
+
});
|
|
589
|
+
});
|
|
@@ -14,6 +14,8 @@
|
|
|
14
14
|
*
|
|
15
15
|
* SYNC: When modified, update:
|
|
16
16
|
* - /packages/core/src/hooks/index.ts
|
|
17
|
+
* - /packages/core/src/hooks/useListFocus.doc.mjs
|
|
18
|
+
* - /packages/core/src/hooks/useListFocus.test.tsx
|
|
17
19
|
*/
|
|
18
20
|
|
|
19
21
|
import {useCallback, useRef} from 'react';
|
|
@@ -64,7 +66,9 @@ export interface UseListFocusOptions {
|
|
|
64
66
|
wrap?: boolean;
|
|
65
67
|
|
|
66
68
|
/**
|
|
67
|
-
* Callback when Escape key is pressed.
|
|
69
|
+
* Callback when Escape key is pressed. Supplying it also makes the list
|
|
70
|
+
* consume the key (`preventDefault`); without it Escape passes through to
|
|
71
|
+
* the surrounding layer.
|
|
68
72
|
*/
|
|
69
73
|
onEscape?: () => void;
|
|
70
74
|
|
|
@@ -277,7 +281,8 @@ function shouldDeferToCaret(target: EventTarget | null, key: string): boolean {
|
|
|
277
281
|
* - ArrowUp/ArrowLeft: Move to previous item (wraps to last)
|
|
278
282
|
* - Home: Move to first item
|
|
279
283
|
* - End: Move to last item
|
|
280
|
-
* - Escape:
|
|
284
|
+
* - Escape: runs `onEscape` and consumes the key. With no `onEscape` the key
|
|
285
|
+
* is left alone, so a surrounding layer can still dismiss on it.
|
|
281
286
|
*
|
|
282
287
|
* By default the hook only *moves* focus and leaves `tabindex` management to
|
|
283
288
|
* the caller. Opt into {@link UseListFocusOptions.hasRovingTabIndex} for a hook
|
|
@@ -557,12 +562,15 @@ export function useListFocus<T extends HTMLElement = HTMLElement>(
|
|
|
557
562
|
return;
|
|
558
563
|
}
|
|
559
564
|
|
|
560
|
-
// Escape is handled regardless of orientation
|
|
561
|
-
//
|
|
562
|
-
//
|
|
565
|
+
// Escape is handled regardless of orientation, but only *consumed* when
|
|
566
|
+
// a handler asked for it: a list with no dismissal to perform must leave
|
|
567
|
+
// the key to whatever host layer does have one, and those defer to
|
|
568
|
+
// `defaultPrevented` (see `useFocusTrap`) or to the native popover.
|
|
563
569
|
if (e.key === 'Escape') {
|
|
564
|
-
|
|
565
|
-
|
|
570
|
+
if (onEscape) {
|
|
571
|
+
e.preventDefault();
|
|
572
|
+
onEscape();
|
|
573
|
+
}
|
|
566
574
|
return;
|
|
567
575
|
}
|
|
568
576
|
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/** @type {import('@astryxdesign/cli/authoring').HookDoc} */
|
|
4
|
+
export const docs = {
|
|
5
|
+
name: 'useMergedRefs',
|
|
6
|
+
displayName: 'useMergedRefs',
|
|
7
|
+
keywords: [
|
|
8
|
+
'ref',
|
|
9
|
+
'refs',
|
|
10
|
+
'merge',
|
|
11
|
+
'forwardRef',
|
|
12
|
+
'callback ref',
|
|
13
|
+
'stable ref',
|
|
14
|
+
],
|
|
15
|
+
params: [
|
|
16
|
+
{
|
|
17
|
+
name: 'refs',
|
|
18
|
+
type: 'Array<Ref<T> | undefined>',
|
|
19
|
+
description: 'Up to six refs that should all receive the same element.',
|
|
20
|
+
required: true,
|
|
21
|
+
},
|
|
22
|
+
],
|
|
23
|
+
returns: [
|
|
24
|
+
{
|
|
25
|
+
name: 'ref',
|
|
26
|
+
type: 'RefCallback<T>',
|
|
27
|
+
description:
|
|
28
|
+
'A merged callback ref that remains stable until an input ref changes.',
|
|
29
|
+
},
|
|
30
|
+
],
|
|
31
|
+
usage: {
|
|
32
|
+
description:
|
|
33
|
+
'Combines multiple object or callback refs into one stable callback ref. Use it when a component must forward a consumer ref while also attaching internal refs. Unlike calling mergeRefs during render, the callback identity stays stable across unrelated rerenders, so React does not detach and reattach the element.',
|
|
34
|
+
bestPractices: [
|
|
35
|
+
{
|
|
36
|
+
guidance: true,
|
|
37
|
+
description:
|
|
38
|
+
'Use useMergedRefs when one element must receive both a forwarded ref and one or more internal refs.',
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
guidance: false,
|
|
42
|
+
description:
|
|
43
|
+
'Call mergeRefs directly in a JSX ref prop; that creates a new callback on every render and forces unnecessary detach and attach work.',
|
|
44
|
+
},
|
|
45
|
+
],
|
|
46
|
+
},
|
|
47
|
+
relatedComponents: [],
|
|
48
|
+
relatedHooks: [],
|
|
49
|
+
importPath: '@astryxdesign/core/hooks',
|
|
50
|
+
category: 'utility',
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
/** @type {import('@astryxdesign/cli/authoring').HookTranslationDoc} */
|
|
54
|
+
export const docsDense = {
|
|
55
|
+
description:
|
|
56
|
+
'Combines object/callback refs into one stable callback ref. Identity changes only when an input ref changes, avoiding detach/attach churn from inline mergeRefs calls.',
|
|
57
|
+
paramDescriptions: {
|
|
58
|
+
refs: 'refs that should all receive the same element.',
|
|
59
|
+
},
|
|
60
|
+
returnDescriptions: {
|
|
61
|
+
ref: 'stable merged callback ref; changes only when an input ref changes.',
|
|
62
|
+
},
|
|
63
|
+
usage: {
|
|
64
|
+
description:
|
|
65
|
+
'Use when one element needs a forwarded consumer ref plus internal refs. Keeps the callback stable across unrelated rerenders.',
|
|
66
|
+
bestPractices: [
|
|
67
|
+
{
|
|
68
|
+
guidance: true,
|
|
69
|
+
description:
|
|
70
|
+
'Use for a forwarded ref plus internal measurement, focus, or anchor refs.',
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
guidance: false,
|
|
74
|
+
description:
|
|
75
|
+
'Call mergeRefs inline in JSX; it forces ref detach/attach on every render.',
|
|
76
|
+
},
|
|
77
|
+
],
|
|
78
|
+
},
|
|
79
|
+
};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file useMergedRefs.test.tsx
|
|
5
|
+
* @input Uses vitest, @testing-library/react, useMergedRefs hook
|
|
6
|
+
* @output Unit tests for stable merged ref behavior
|
|
7
|
+
* @position Testing; validates useMergedRefs.ts
|
|
8
|
+
*
|
|
9
|
+
* SYNC: When useMergedRefs.ts changes, update tests to match new behavior
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import {renderHook} from '@testing-library/react';
|
|
13
|
+
import {createRef} from 'react';
|
|
14
|
+
import {describe, expect, it, vi} from 'vitest';
|
|
15
|
+
import {useMergedRefs} from './useMergedRefs';
|
|
16
|
+
|
|
17
|
+
describe('useMergedRefs', () => {
|
|
18
|
+
it('forwards values to callback and object refs', () => {
|
|
19
|
+
const callbackRef = vi.fn();
|
|
20
|
+
const objectRef = createRef<HTMLDivElement>();
|
|
21
|
+
const {result} = renderHook(() =>
|
|
22
|
+
useMergedRefs<HTMLDivElement>(callbackRef, objectRef),
|
|
23
|
+
);
|
|
24
|
+
const element = document.createElement('div');
|
|
25
|
+
|
|
26
|
+
const cleanup = result.current(element);
|
|
27
|
+
|
|
28
|
+
expect(callbackRef).toHaveBeenCalledWith(element);
|
|
29
|
+
expect(objectRef.current).toBe(element);
|
|
30
|
+
|
|
31
|
+
cleanup?.();
|
|
32
|
+
expect(callbackRef).toHaveBeenLastCalledWith(null);
|
|
33
|
+
expect(objectRef.current).toBeNull();
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('keeps the merged callback stable when input refs are unchanged', () => {
|
|
37
|
+
const callbackRef = vi.fn();
|
|
38
|
+
const objectRef = createRef<HTMLDivElement>();
|
|
39
|
+
const {result, rerender} = renderHook(() =>
|
|
40
|
+
useMergedRefs<HTMLDivElement>(callbackRef, objectRef),
|
|
41
|
+
);
|
|
42
|
+
const first = result.current;
|
|
43
|
+
|
|
44
|
+
rerender();
|
|
45
|
+
|
|
46
|
+
expect(result.current).toBe(first);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('updates the merged callback when an input ref changes', () => {
|
|
50
|
+
const firstRef = vi.fn();
|
|
51
|
+
const secondRef = vi.fn();
|
|
52
|
+
const {result, rerender} = renderHook(
|
|
53
|
+
({ref}) => useMergedRefs<HTMLDivElement>(ref),
|
|
54
|
+
{initialProps: {ref: firstRef}},
|
|
55
|
+
);
|
|
56
|
+
const first = result.current;
|
|
57
|
+
|
|
58
|
+
rerender({ref: secondRef});
|
|
59
|
+
|
|
60
|
+
expect(result.current).not.toBe(first);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
'use client';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @file useMergedRefs.ts
|
|
7
|
+
* @input React refs to combine into one stable callback ref
|
|
8
|
+
* @output Exports useMergedRefs
|
|
9
|
+
* @position Core hook; use when one element must receive multiple refs
|
|
10
|
+
*
|
|
11
|
+
* SYNC: When modified, update:
|
|
12
|
+
* - /packages/core/src/hooks/index.ts
|
|
13
|
+
* - /packages/core/src/hooks/useMergedRefs.doc.mjs
|
|
14
|
+
* - /packages/core/src/hooks/useMergedRefs.test.tsx
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import {useMemo, type Ref, type RefCallback} from 'react';
|
|
18
|
+
import {mergeRefs} from '../utils/mergeRefs';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Combine up to six refs into a callback ref whose identity changes only when
|
|
22
|
+
* one of the input refs changes.
|
|
23
|
+
*/
|
|
24
|
+
export function useMergedRefs<T>(
|
|
25
|
+
refA?: Ref<T>,
|
|
26
|
+
refB?: Ref<T>,
|
|
27
|
+
refC?: Ref<T>,
|
|
28
|
+
refD?: Ref<T>,
|
|
29
|
+
refE?: Ref<T>,
|
|
30
|
+
refF?: Ref<T>,
|
|
31
|
+
): RefCallback<T> {
|
|
32
|
+
return useMemo(
|
|
33
|
+
() => mergeRefs(refA, refB, refC, refD, refE, refF),
|
|
34
|
+
[refA, refB, refC, refD, refE, refF],
|
|
35
|
+
);
|
|
36
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -68,7 +68,7 @@ export const docs = {
|
|
|
68
68
|
{
|
|
69
69
|
guidance: true,
|
|
70
70
|
description:
|
|
71
|
-
'Prefer mode="auto" when the surface color comes from a theme token. A token named "inverted" is not guaranteed to be inverted, and auto measures what was actually painted instead of trusting the name
|
|
71
|
+
'Prefer mode="auto" when the surface color comes from a theme token. A token named "inverted" is not guaranteed to be inverted, and auto measures what was actually painted instead of trusting the name. It can even decide that a surface needs no media context at all.',
|
|
72
72
|
},
|
|
73
73
|
{
|
|
74
74
|
guidance: true,
|
|
@@ -93,14 +93,14 @@ export const docs = {
|
|
|
93
93
|
type: "'dark' | 'light' | 'auto' | 'off'",
|
|
94
94
|
required: true,
|
|
95
95
|
description:
|
|
96
|
-
'Surface luminance context: dark for content over dark backgrounds (light text, white-tinted interactions), light for content over light backgrounds (dark text, black-tinted interactions), auto to decide from the painted surface
|
|
96
|
+
'Surface luminance context: dark for content over dark backgrounds (light text, white-tinted interactions), light for content over light backgrounds (dark text, black-tinted interactions), auto to decide from the painted surface (no media context when the ambient text already reads on the surface at 3:1, otherwise the side that reads better), and off to turn it off explicitly. The element renders either way, so a surface can switch contexts without remounting children.',
|
|
97
97
|
},
|
|
98
98
|
{
|
|
99
99
|
name: 'fallback',
|
|
100
100
|
type: "'dark' | 'light'",
|
|
101
101
|
default: "'dark'",
|
|
102
102
|
description:
|
|
103
|
-
'Which side auto uses when the surface cannot be measured: during SSR, on the first client frame, and whenever the backdrop is not knowable from CSS
|
|
103
|
+
'Which side auto uses when the surface cannot be measured: during SSR, on the first client frame, and whenever the backdrop is not knowable from CSS, most often a background-image, whose pixels need sampling (useImageMode) rather than a computed style. Ignored unless mode is auto.',
|
|
104
104
|
},
|
|
105
105
|
{
|
|
106
106
|
name: 'children',
|
|
@@ -126,7 +126,7 @@ export const docsDense = {
|
|
|
126
126
|
{
|
|
127
127
|
guidance: true,
|
|
128
128
|
description:
|
|
129
|
-
'Prefer mode="auto" when surface color comes from a theme token
|
|
129
|
+
'Prefer mode="auto" when surface color comes from a theme token; a token named "inverted" is not guaranteed to be; auto measures what was painted.',
|
|
130
130
|
},
|
|
131
131
|
{
|
|
132
132
|
guidance: true,
|
|
@@ -148,6 +148,6 @@ export const docsDense = {
|
|
|
148
148
|
propDescriptions: {
|
|
149
149
|
mode: 'surface luminance context: dark for content over dark backgrounds (light text, white-tinted interactions), light for content over light backgrounds (dark text, black-tinted interactions), auto to decide from painted surface (none if ambient text already reads at 3:1, else better-reading side), off to turn off explicitly (element still renders, so children never remount)',
|
|
150
150
|
fallback:
|
|
151
|
-
'side auto uses when surface is unmeasurable (SSR, first frame, background-image
|
|
151
|
+
'side auto uses when surface is unmeasurable (SSR, first frame, background-image, which needs useImageMode sampling); ignored unless mode is auto',
|
|
152
152
|
},
|
|
153
153
|
};
|