@astryxdesign/core 0.6.0 → 0.6.1
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 +41 -3
- package/dist/AppShell/AppShell.d.ts.map +1 -1
- package/dist/BottomSheet/BottomSheetSwitcher.d.ts +12 -1
- package/dist/BottomSheet/BottomSheetSwitcher.d.ts.map +1 -1
- package/dist/BottomSheet/BottomSheetSwitcher.js +44 -15
- package/dist/Breadcrumbs/BreadcrumbItem.d.ts +3 -2
- package/dist/Breadcrumbs/BreadcrumbItem.d.ts.map +1 -1
- package/dist/Breadcrumbs/BreadcrumbItem.js +3 -7
- package/dist/Center/Center.d.ts +23 -16
- package/dist/Center/Center.d.ts.map +1 -1
- package/dist/Center/Center.js +7 -5
- package/dist/CodeBlock/CodeBlock.js +2 -2
- package/dist/DateInput/DateInput.d.ts.map +1 -1
- package/dist/DateInput/DateInput.js +12 -2
- package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
- package/dist/DateTimeInput/DateTimeInput.js +12 -2
- package/dist/Field/Field.d.ts.map +1 -1
- package/dist/Field/Field.js +1 -0
- package/dist/Field/InputClearButton.d.ts +2 -2
- package/dist/Field/InputClearButton.d.ts.map +1 -1
- package/dist/Field/InputClearButton.js +5 -1
- package/dist/Field/PanelSearchInput.d.ts.map +1 -1
- package/dist/Field/PanelSearchInput.js +16 -4
- package/dist/FileInput/FileInput.d.ts.map +1 -1
- package/dist/FileInput/FileInput.js +12 -1
- package/dist/HoverCard/useHoverCard.js +2 -2
- package/dist/Indicator/CheckboxIndicator.js +2 -2
- package/dist/Indicator/RadioIndicator.js +2 -2
- package/dist/Layer/layerStack.d.ts +10 -0
- package/dist/Layer/layerStack.d.ts.map +1 -1
- package/dist/Layer/layerStack.js +21 -9
- package/dist/Layer/useLayerDismissal.d.ts +2 -3
- package/dist/Layer/useLayerDismissal.d.ts.map +1 -1
- package/dist/Layer/useLayerDismissal.js +2 -3
- package/dist/NavIcon/NavIcon.js +2 -2
- package/dist/NumberInput/NumberInput.d.ts.map +1 -1
- package/dist/NumberInput/NumberInput.js +12 -2
- package/dist/Popover/usePopover.d.ts +3 -2
- package/dist/Popover/usePopover.d.ts.map +1 -1
- package/dist/Popover/usePopover.js +4 -2
- package/dist/ProgressBar/ProgressBar.js +2 -2
- package/dist/ScrollableArea/ScrollableArea.d.ts +79 -0
- package/dist/ScrollableArea/ScrollableArea.d.ts.map +1 -0
- package/dist/ScrollableArea/ScrollableArea.js +144 -0
- package/dist/ScrollableArea/index.d.ts +11 -0
- package/dist/ScrollableArea/index.d.ts.map +1 -0
- package/dist/ScrollableArea/index.js +11 -0
- package/dist/StatusDot/StatusDot.js +2 -2
- package/dist/TextArea/TextArea.js +2 -2
- package/dist/TextInput/TextInput.d.ts.map +1 -1
- package/dist/TextInput/TextInput.js +19 -4
- package/dist/TimeInput/TimeInput.d.ts.map +1 -1
- package/dist/TimeInput/TimeInput.js +12 -2
- package/dist/Typeahead/BaseTypeahead.d.ts +21 -14
- package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
- package/dist/Typeahead/BaseTypeahead.js +56 -20
- package/dist/astryx.css +15 -0
- package/dist/hooks/index.d.ts +2 -0
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +1 -0
- package/dist/hooks/scrollGeometry.d.ts +24 -0
- package/dist/hooks/scrollGeometry.d.ts.map +1 -0
- package/dist/hooks/scrollGeometry.js +86 -0
- package/dist/hooks/scrollOwnerRegistry.d.ts +15 -0
- package/dist/hooks/scrollOwnerRegistry.d.ts.map +1 -0
- package/dist/hooks/scrollOwnerRegistry.js +24 -0
- package/dist/hooks/useFocusTrap.d.ts +8 -0
- package/dist/hooks/useFocusTrap.d.ts.map +1 -1
- package/dist/hooks/useFocusTrap.js +22 -11
- package/dist/hooks/useScrollableArea.d.ts +51 -0
- package/dist/hooks/useScrollableArea.d.ts.map +1 -0
- package/dist/hooks/useScrollableArea.js +287 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/theme/defineTheme.d.ts +2 -6
- package/dist/theme/defineTheme.d.ts.map +1 -1
- package/dist/theme/defineTheme.js +1 -1
- package/dist/theme/derivedVarRegistry.js +1 -1
- package/dist/theme/localTokens.d.ts +8 -11
- package/dist/theme/localTokens.d.ts.map +1 -1
- package/dist/theme/localTokens.js +17 -71
- package/dist/theme/themeAdaptations.d.ts.map +1 -1
- package/dist/theme/themeAdaptations.js +4 -4
- package/dist/utils/themeProps.d.ts +10 -10
- package/dist/utils/themeProps.d.ts.map +1 -1
- package/dist/utils/themeProps.js +27 -10
- package/locales/en.json +16 -0
- package/locales/pseudo.json +12 -0
- package/package.json +7 -2
- package/src/AppShell/AppShell.test.tsx +36 -0
- package/src/AppShell/AppShell.tsx +4 -1
- package/src/AspectRatio/AspectRatio.doc.mjs +3 -3
- package/src/Banner/Banner.test.tsx +3 -1
- package/src/BottomSheet/BottomSheetSwitcher.doc.mjs +56 -1
- package/src/BottomSheet/BottomSheetSwitcher.spec.md +211 -0
- package/src/BottomSheet/BottomSheetSwitcher.test.tsx +134 -2
- package/src/BottomSheet/BottomSheetSwitcher.tsx +43 -20
- package/src/Breadcrumbs/BreadcrumbItem.doc.mjs +10 -5
- package/src/Breadcrumbs/BreadcrumbItem.spec.md +225 -0
- package/src/Breadcrumbs/BreadcrumbItem.tsx +8 -13
- package/src/Breadcrumbs/Breadcrumbs.doc.mjs +2 -2
- package/src/Breadcrumbs/Breadcrumbs.test.tsx +49 -2
- package/src/Center/Center.doc.mjs +32 -28
- package/src/Center/Center.spec.md +225 -0
- package/src/Center/Center.test.tsx +42 -4
- package/src/Center/Center.tsx +24 -17
- package/src/Chat/ChatSystemMessage.test.tsx +2 -9
- package/src/CodeBlock/CodeBlock.doc.mjs +2 -2
- package/src/CodeBlock/CodeBlock.tsx +2 -2
- package/src/DateInput/DateInput.test.tsx +4 -4
- package/src/DateInput/DateInput.tsx +15 -4
- package/src/DateRangeInput/DateRangeInput.test.tsx +2 -2
- package/src/DateTimeInput/DateTimeInput.test.tsx +6 -4
- package/src/DateTimeInput/DateTimeInput.tsx +18 -7
- package/src/DropdownMenu/DropdownMenuSelectable.test.tsx +4 -77
- package/src/Field/Field.test.tsx +42 -0
- package/src/Field/Field.tsx +6 -0
- package/src/Field/InputClearButton.test.tsx +35 -1
- package/src/Field/InputClearButton.tsx +7 -3
- package/src/Field/PanelSearchInput.tsx +21 -8
- package/src/FieldStatus/FieldStatus.spec.md +27 -17
- package/src/FieldStatus/FieldStatus.test.tsx +7 -5
- package/src/FieldStatus/__tests__/StatusMessage.a11y.chromium.spec.ts +198 -0
- package/src/FieldStatus/__tests__/StatusMessage.a11y.known-failures.ts +13 -0
- package/src/FieldStatus/__tests__/StatusMessage.a11y.renders.tsx +305 -0
- package/src/FieldStatus/__tests__/StatusMessage.a11y.states.ts +317 -0
- package/src/FieldStatus/__tests__/StatusMessage.a11y.test.tsx +155 -0
- package/src/FileInput/FileInput.tsx +10 -1
- package/src/FormLayout/__snapshots__/FormLayout.test.tsx.snap +3 -3
- package/src/HoverCard/HoverCard.doc.mjs +4 -4
- package/src/HoverCard/useHoverCard.tsx +2 -2
- package/src/Indicator/CheckboxIndicator.tsx +2 -2
- package/src/Indicator/Indicator.doc.mjs +2 -2
- package/src/Indicator/Indicator.test.tsx +1 -1
- package/src/Indicator/RadioIndicator.tsx +2 -2
- package/src/Layer/layerStack.ts +20 -9
- package/src/Layer/useLayerDismissal.ts +2 -3
- package/src/MultiSelector/MultiSelector.test.tsx +4 -4
- package/src/NavIcon/NavIcon.doc.mjs +4 -4
- package/src/NavIcon/NavIcon.tsx +2 -2
- package/src/NumberInput/NumberInput.tsx +18 -7
- package/src/Popover/Popover.doc.mjs +10 -10
- package/src/Popover/Popover.spec.md +55 -65
- package/src/Popover/Popover.test.tsx +29 -0
- package/src/Popover/usePopover.doc.mjs +4 -4
- package/src/Popover/usePopover.tsx +7 -4
- package/src/ProgressBar/ProgressBar.doc.mjs +4 -4
- package/src/ProgressBar/ProgressBar.test.tsx +1 -31
- package/src/ProgressBar/ProgressBar.tsx +2 -2
- package/src/RadioList/RadioList.test.tsx +5 -144
- package/src/RadioList/__tests__/RadioGroup.a11y.chromium.spec.ts +255 -0
- package/src/RadioList/__tests__/RadioGroup.a11y.known-failures.ts +12 -0
- package/src/RadioList/__tests__/RadioGroup.a11y.renders.tsx +232 -0
- package/src/RadioList/__tests__/RadioGroup.a11y.states.ts +503 -0
- package/src/RadioList/__tests__/RadioGroup.a11y.test.tsx +217 -0
- package/src/ScrollableArea/ScrollableArea.doc.mjs +100 -0
- package/src/ScrollableArea/ScrollableArea.spec.md +189 -0
- package/src/ScrollableArea/ScrollableArea.test.tsx +299 -0
- package/src/ScrollableArea/ScrollableArea.tsx +259 -0
- package/src/ScrollableArea/index.ts +26 -0
- package/src/ScrollableArea/modules/useScrollableArea.spec.md +121 -0
- package/src/SegmentedControl/SegmentedControl.test.tsx +5 -172
- package/src/Selector/Selector.test.tsx +4 -4
- package/src/Spinner/Spinner.test.tsx +0 -18
- package/src/StatusDot/StatusDot.doc.mjs +4 -4
- package/src/StatusDot/StatusDot.tsx +2 -2
- package/src/TabList/TabList.test.tsx +5 -9
- package/src/TabList/__tests__/Tabs.a11y.chromium.spec.ts +191 -0
- package/src/TabList/__tests__/Tabs.a11y.known-failures.ts +45 -0
- package/src/TabList/__tests__/Tabs.a11y.renders.tsx +92 -0
- package/src/TabList/__tests__/Tabs.a11y.states.ts +247 -0
- package/src/TabList/__tests__/Tabs.a11y.test.tsx +153 -0
- package/src/Table/Table.doc.mjs +2 -2
- package/src/TextArea/TextArea.doc.mjs +4 -4
- package/src/TextArea/TextArea.tsx +2 -2
- package/src/TextInput/TextInput.doc.mjs +2 -1
- package/src/TextInput/TextInput.test.tsx +94 -0
- package/src/TextInput/TextInput.tsx +22 -6
- package/src/TimeInput/TimeInput.tsx +18 -7
- package/src/Toast/ToastViewport.test.tsx +1 -39
- package/src/Typeahead/BaseTypeahead.doc.mjs +229 -33
- package/src/Typeahead/BaseTypeahead.spec.md +269 -0
- package/src/Typeahead/BaseTypeahead.test.tsx +200 -0
- package/src/Typeahead/BaseTypeahead.tsx +99 -30
- package/src/hooks/index.ts +13 -0
- package/src/hooks/scrollGeometry.ts +155 -0
- package/src/hooks/scrollOwnerRegistry.ts +47 -0
- package/src/hooks/useFocusTrap.ts +22 -11
- package/src/hooks/useFocusTrapEscapeShim.test.tsx +4 -3
- package/src/hooks/useScrollableArea.doc.mjs +108 -0
- package/src/hooks/useScrollableArea.test.tsx +437 -0
- package/src/hooks/useScrollableArea.ts +469 -0
- package/src/index.ts +1 -0
- package/src/theme/defineTheme.test.ts +65 -105
- package/src/theme/defineTheme.ts +3 -9
- package/src/theme/derivedVarRegistry.ts +1 -1
- package/src/theme/localTokens.ts +25 -96
- package/src/theme/publicThemeHelperContract.test.ts +2 -2
- package/src/theme/themeAdaptations.test.ts +16 -42
- package/src/theme/themeAdaptations.ts +6 -9
- package/src/utils/themeProps.test.ts +29 -10
- package/src/utils/themeProps.ts +36 -17
|
@@ -80,40 +80,34 @@ describe('defineTheme', () => {
|
|
|
80
80
|
warn.mockRestore();
|
|
81
81
|
});
|
|
82
82
|
|
|
83
|
-
it(
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
backgroundColor:
|
|
96
|
-
'var(--astryx-theme-ocean-theme-color-status-fill-accent)',
|
|
83
|
+
it.each([
|
|
84
|
+
['ocean-theme', '--ac-selection-ink'],
|
|
85
|
+
['Theme.Owner', '--'],
|
|
86
|
+
['ocean-theme', '--astryx-theme-ocean-theme-color-status-fill-accent'],
|
|
87
|
+
])(
|
|
88
|
+
'accepts valid local token name %s / %s and preserves its exact bytes',
|
|
89
|
+
(themeName, token) => {
|
|
90
|
+
const theme = defineTheme({
|
|
91
|
+
name: themeName,
|
|
92
|
+
localTokens: {[token]: ['#0077b6', '#48cae4']},
|
|
93
|
+
components: {
|
|
94
|
+
badge: {
|
|
95
|
+
'variant:info': {backgroundColor: `var(${token})`},
|
|
97
96
|
},
|
|
98
97
|
},
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
expect(theme.__localTokenLineage).toEqual(['ocean-theme']);
|
|
113
|
-
expect(generateThemeTestCSS(theme)).toContain(
|
|
114
|
-
'--astryx-theme-ocean-theme-color-status-fill-accent: light-dark(#0077b6, #48cae4);',
|
|
115
|
-
);
|
|
116
|
-
});
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
expect(theme.tokens).not.toHaveProperty(token);
|
|
101
|
+
expect(theme.localTokens).toEqual({
|
|
102
|
+
[token]: 'light-dark(#0077b6, #48cae4)',
|
|
103
|
+
});
|
|
104
|
+
expect(theme.__localTokenOwners).toEqual({[token]: themeName});
|
|
105
|
+
expect(theme.__localTokenLineage).toEqual([themeName]);
|
|
106
|
+
expect(generateThemeTestCSS(theme)).toContain(
|
|
107
|
+
`${token}: light-dark(#0077b6, #48cae4);`,
|
|
108
|
+
);
|
|
109
|
+
},
|
|
110
|
+
);
|
|
117
111
|
|
|
118
112
|
it('rejects a name declared in both tokens and localTokens before CSS and token helpers can disagree', () => {
|
|
119
113
|
const token = '--astryx-theme-ocean-theme-color-status-fill-accent';
|
|
@@ -142,9 +136,16 @@ describe('defineTheme', () => {
|
|
|
142
136
|
},
|
|
143
137
|
}),
|
|
144
138
|
).toThrow(/both tokens and localTokens/);
|
|
139
|
+
|
|
140
|
+
expect(() =>
|
|
141
|
+
defineTheme({
|
|
142
|
+
name: 'ocean-theme',
|
|
143
|
+
localTokens: {'--color-accent': '#48cae4'},
|
|
144
|
+
}),
|
|
145
|
+
).toThrow(/both tokens and localTokens/);
|
|
145
146
|
});
|
|
146
147
|
|
|
147
|
-
it('does not reinterpret
|
|
148
|
+
it('does not reinterpret unenrolled custom-property references', () => {
|
|
148
149
|
const theme = defineTheme({
|
|
149
150
|
name: 'legacy',
|
|
150
151
|
tokens: {
|
|
@@ -164,65 +165,43 @@ describe('defineTheme', () => {
|
|
|
164
165
|
expect(theme).not.toHaveProperty('__localTokenLineage');
|
|
165
166
|
});
|
|
166
167
|
|
|
167
|
-
it
|
|
168
|
-
['Uppercase', '--astryx-theme-Uppercase-color-accent'],
|
|
169
|
-
['wrong-space', '--astryx-theme-other-color-accent'],
|
|
170
|
-
['wrong-space', '--astryx-theme-wrong-space-Color-accent'],
|
|
171
|
-
])('rejects malformed local token enrollment for %s', (name, token) => {
|
|
168
|
+
it('rejects an invalid CSS custom-property name', () => {
|
|
172
169
|
expect(() =>
|
|
173
170
|
defineTheme({
|
|
174
|
-
name,
|
|
175
|
-
localTokens: {
|
|
171
|
+
name: 'ocean',
|
|
172
|
+
localTokens: {'selection-ink': '#123456'},
|
|
176
173
|
}),
|
|
177
|
-
).toThrow(/
|
|
174
|
+
).toThrow(/valid CSS custom-property name/);
|
|
178
175
|
});
|
|
179
176
|
|
|
180
|
-
it
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
':hover': {
|
|
191
|
-
color: `${functionName}(--astryx-theme-ocean-color-missing)`,
|
|
192
|
-
},
|
|
193
|
-
},
|
|
194
|
-
},
|
|
195
|
-
},
|
|
196
|
-
}),
|
|
197
|
-
).toThrow(/has no declaration/);
|
|
198
|
-
|
|
199
|
-
expect(() =>
|
|
200
|
-
defineTheme({
|
|
201
|
-
name: 'ocean',
|
|
202
|
-
localTokens: {},
|
|
203
|
-
onDark: {
|
|
204
|
-
components: {
|
|
205
|
-
badge: {
|
|
206
|
-
base: {
|
|
207
|
-
color: `${functionName}(--astryx-theme-ocean-color-missing)`,
|
|
208
|
-
},
|
|
209
|
-
},
|
|
177
|
+
it('treats case-only and unrelated custom-property references as external', () => {
|
|
178
|
+
expect(() =>
|
|
179
|
+
defineTheme({
|
|
180
|
+
name: 'ocean',
|
|
181
|
+
localTokens: {'--Selection-Ink': '#123456'},
|
|
182
|
+
components: {
|
|
183
|
+
button: {
|
|
184
|
+
base: {
|
|
185
|
+
color: 'var(--selection-ink)',
|
|
186
|
+
backgroundColor: 'var(--product-surface)',
|
|
210
187
|
},
|
|
211
188
|
},
|
|
212
|
-
}
|
|
213
|
-
)
|
|
214
|
-
|
|
215
|
-
);
|
|
189
|
+
},
|
|
190
|
+
}),
|
|
191
|
+
).not.toThrow();
|
|
192
|
+
});
|
|
216
193
|
|
|
217
194
|
it.each(['VAR', 'vAr'])(
|
|
218
195
|
'rejects cycles between local tokens using %s()',
|
|
219
196
|
functionName => {
|
|
197
|
+
const escaped = '--\\66 oo';
|
|
198
|
+
const other = '--astryx-theme-cycle-color-b';
|
|
220
199
|
expect(() =>
|
|
221
200
|
defineTheme({
|
|
222
201
|
name: 'cycle',
|
|
223
202
|
localTokens: {
|
|
224
|
-
|
|
225
|
-
|
|
203
|
+
[escaped]: `${functionName}(${other})`,
|
|
204
|
+
[other]: `${functionName}(${escaped})`,
|
|
226
205
|
},
|
|
227
206
|
}),
|
|
228
207
|
).toThrow(/cycle detected/);
|
|
@@ -1178,28 +1157,28 @@ describe('container padding mapping', () => {
|
|
|
1178
1157
|
|
|
1179
1158
|
describe('defineTheme extends', () => {
|
|
1180
1159
|
it('inherits enrollment, allows exact replacement, and owns new child names', () => {
|
|
1160
|
+
const inherited = '--astryx-theme-base-theme-color-status-fill';
|
|
1161
|
+
const childOwned = '--child-surface-raised';
|
|
1181
1162
|
const base = defineTheme({
|
|
1182
1163
|
name: 'base-theme',
|
|
1183
|
-
localTokens: {
|
|
1184
|
-
'--astryx-theme-base-theme-color-status-fill': '#123456',
|
|
1185
|
-
},
|
|
1164
|
+
localTokens: {[inherited]: '#123456'},
|
|
1186
1165
|
});
|
|
1187
1166
|
const child = defineTheme({
|
|
1188
1167
|
name: 'child-theme',
|
|
1189
1168
|
extends: base,
|
|
1190
1169
|
localTokens: {
|
|
1191
|
-
|
|
1192
|
-
|
|
1170
|
+
[inherited]: '#654321',
|
|
1171
|
+
[childOwned]: '#abcdef',
|
|
1193
1172
|
},
|
|
1194
1173
|
});
|
|
1195
1174
|
|
|
1196
1175
|
expect(child.localTokens).toEqual({
|
|
1197
|
-
|
|
1198
|
-
|
|
1176
|
+
[inherited]: '#654321',
|
|
1177
|
+
[childOwned]: '#abcdef',
|
|
1199
1178
|
});
|
|
1200
1179
|
expect(child.__localTokenOwners).toEqual({
|
|
1201
|
-
|
|
1202
|
-
|
|
1180
|
+
[inherited]: 'base-theme',
|
|
1181
|
+
[childOwned]: 'child-theme',
|
|
1203
1182
|
});
|
|
1204
1183
|
expect(child.__localTokenLineage).toEqual(['base-theme', 'child-theme']);
|
|
1205
1184
|
});
|
|
@@ -1235,25 +1214,6 @@ describe('defineTheme extends', () => {
|
|
|
1235
1214
|
]);
|
|
1236
1215
|
});
|
|
1237
1216
|
|
|
1238
|
-
it('rejects a new declaration in another theme namespace', () => {
|
|
1239
|
-
const base = defineTheme({
|
|
1240
|
-
name: 'base-theme',
|
|
1241
|
-
localTokens: {
|
|
1242
|
-
'--astryx-theme-base-theme-color-status-fill': '#123456',
|
|
1243
|
-
},
|
|
1244
|
-
});
|
|
1245
|
-
|
|
1246
|
-
expect(() =>
|
|
1247
|
-
defineTheme({
|
|
1248
|
-
name: 'child-theme',
|
|
1249
|
-
extends: base,
|
|
1250
|
-
localTokens: {
|
|
1251
|
-
'--astryx-theme-base-theme-color-new-role': '#abcdef',
|
|
1252
|
-
},
|
|
1253
|
-
}),
|
|
1254
|
-
).toThrow(/exact namespace/);
|
|
1255
|
-
});
|
|
1256
|
-
|
|
1257
1217
|
it('inherits tokens from base theme', () => {
|
|
1258
1218
|
const base = defineTheme({
|
|
1259
1219
|
name: 'base',
|
package/src/theme/defineTheme.ts
CHANGED
|
@@ -308,12 +308,8 @@ export interface DefineThemeInput {
|
|
|
308
308
|
* Values can be a string or [light, dark] tuple.
|
|
309
309
|
* Only include tokens you want to override; defaults fill the rest. */
|
|
310
310
|
tokens?: Partial<Record<TokenName, TokenValue>>;
|
|
311
|
-
/**
|
|
312
|
-
*
|
|
313
|
-
* `--astryx-theme-${name}-...` spelling and values follow `TokenValue`.
|
|
314
|
-
* These names are available only to this enrolled theme lineage and do not
|
|
315
|
-
* become portable `TokenName` values.
|
|
316
|
-
*/
|
|
311
|
+
/** Theme-family-local values keyed by any valid CSS custom-property name;
|
|
312
|
+
* prefixes do not establish ownership. */
|
|
317
313
|
localTokens?: Record<string, TokenValue>;
|
|
318
314
|
/**
|
|
319
315
|
* Component style overrides — keyed by component name (lowercase).
|
|
@@ -581,9 +577,7 @@ export function defineTheme(input: DefineThemeInput): ResolvedDefinedTheme {
|
|
|
581
577
|
input,
|
|
582
578
|
base,
|
|
583
579
|
tokens,
|
|
584
|
-
|
|
585
|
-
__onDark,
|
|
586
|
-
__onLight,
|
|
580
|
+
tokenDefaults,
|
|
587
581
|
);
|
|
588
582
|
|
|
589
583
|
// Adaptations inherit their breakpoint map and ordered rules. Every rule is
|
|
@@ -98,7 +98,7 @@ export const derivedVarRegistry: Record<string, DerivedVarEntry[]> = {
|
|
|
98
98
|
* the old key still selects the element. Without this the rule would land but
|
|
99
99
|
* its derived vars would not expand, and the half that travels through a var
|
|
100
100
|
* (a hover card's radius, a text area's inline padding) would silently do
|
|
101
|
-
* nothing.
|
|
101
|
+
* nothing. Keep these aliases aligned with the emitted compatibility classes.
|
|
102
102
|
*/
|
|
103
103
|
const DEPRECATED_REGISTRY_KEYS: Record<string, string> = {
|
|
104
104
|
hovercard: 'hover-card',
|
package/src/theme/localTokens.ts
CHANGED
|
@@ -3,23 +3,22 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* @file localTokens.ts
|
|
5
5
|
* @input Root and adaptation theme-local token declarations and references
|
|
6
|
-
* @output Validated
|
|
7
|
-
* @position Theme-local
|
|
6
|
+
* @output Validated exact-name ownership metadata plus normalized conditional writes
|
|
7
|
+
* @position Theme-local ownership boundary; final adaptation-cycle validation is
|
|
8
8
|
* performed after the reachable ordered cascade is assembled.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import type {
|
|
12
|
-
import type {ResolvedOnMedia} from './onMediaTokens';
|
|
11
|
+
import type {DefinedTheme, TokenValue} from './defineTheme';
|
|
13
12
|
|
|
14
|
-
const
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
13
|
+
const CSS_NAME_CHARACTER = String.raw`(?:[-_a-zA-Z0-9]|\P{ASCII}|\\(?:[0-9a-fA-F]{1,6}[ \t\r\n\f]?|[^\r\n\f]))`;
|
|
14
|
+
const CSS_CUSTOM_PROPERTY_NAME_PATTERN = new RegExp(
|
|
15
|
+
`^--${CSS_NAME_CHARACTER}*$`,
|
|
16
|
+
'u',
|
|
17
|
+
);
|
|
18
|
+
const CSS_VAR_PATTERN = new RegExp(
|
|
19
|
+
`var\\(\\s*(--${CSS_NAME_CHARACTER}*)(?=\\s*(?:,|\\)))`,
|
|
20
|
+
'giu',
|
|
21
|
+
);
|
|
23
22
|
|
|
24
23
|
export interface ResolvedLocalTokenContract {
|
|
25
24
|
localTokens: Record<string, string>;
|
|
@@ -31,12 +30,8 @@ function hasOwn(object: object, key: PropertyKey): boolean {
|
|
|
31
30
|
return Object.prototype.hasOwnProperty.call(object, key);
|
|
32
31
|
}
|
|
33
32
|
|
|
34
|
-
function
|
|
35
|
-
|
|
36
|
-
return (
|
|
37
|
-
name.startsWith(prefix) &&
|
|
38
|
-
LOCAL_TOKEN_SUFFIX_PATTERN.test(name.slice(prefix.length))
|
|
39
|
-
);
|
|
33
|
+
function isValidCSSCustomPropertyName(name: string): boolean {
|
|
34
|
+
return !name.includes('\0') && CSS_CUSTOM_PROPERTY_NAME_PATTERN.test(name);
|
|
40
35
|
}
|
|
41
36
|
|
|
42
37
|
function resolveTokenValue(value: TokenValue, path: string): string {
|
|
@@ -84,16 +79,6 @@ function collectCustomPropertyReferences(
|
|
|
84
79
|
}
|
|
85
80
|
}
|
|
86
81
|
|
|
87
|
-
function collectLocalReferences(value: unknown, refs: Set<string>): void {
|
|
88
|
-
const customProperties = new Set<string>();
|
|
89
|
-
collectCustomPropertyReferences(value, customProperties);
|
|
90
|
-
for (const name of customProperties) {
|
|
91
|
-
if (isReservedThemeLocalTokenName(name)) {
|
|
92
|
-
refs.add(name);
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
82
|
export function assertNoTokenCycles(
|
|
98
83
|
tokenValues: Record<string, string>,
|
|
99
84
|
context?: string,
|
|
@@ -208,27 +193,6 @@ export function assertNoTokenCycles(
|
|
|
208
193
|
}
|
|
209
194
|
}
|
|
210
195
|
|
|
211
|
-
function assertDeclaredReferences(
|
|
212
|
-
localTokens: Record<string, string>,
|
|
213
|
-
components: ComponentStyleMap | undefined,
|
|
214
|
-
onDark: ResolvedOnMedia | undefined,
|
|
215
|
-
onLight: ResolvedOnMedia | undefined,
|
|
216
|
-
): void {
|
|
217
|
-
const refs = new Set<string>();
|
|
218
|
-
collectLocalReferences(localTokens, refs);
|
|
219
|
-
collectLocalReferences(components, refs);
|
|
220
|
-
collectLocalReferences(onDark?.components, refs);
|
|
221
|
-
collectLocalReferences(onLight?.components, refs);
|
|
222
|
-
|
|
223
|
-
for (const reference of refs) {
|
|
224
|
-
if (!hasOwn(localTokens, reference)) {
|
|
225
|
-
throw new Error(
|
|
226
|
-
`Theme-local token reference "${reference}" has no declaration in the enrolled theme lineage.`,
|
|
227
|
-
);
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
|
|
232
196
|
function assertInheritedContract(
|
|
233
197
|
themeName: string,
|
|
234
198
|
base: DefinedTheme,
|
|
@@ -267,7 +231,7 @@ function assertInheritedContract(
|
|
|
267
231
|
typeof value !== 'string' ||
|
|
268
232
|
!owner ||
|
|
269
233
|
!lineage.includes(owner) ||
|
|
270
|
-
!
|
|
234
|
+
!isValidCSSCustomPropertyName(name)
|
|
271
235
|
) {
|
|
272
236
|
throw new Error(
|
|
273
237
|
`defineTheme("${themeName}"): inherited local token "${name}" does not match its exact lineage metadata.`,
|
|
@@ -287,7 +251,7 @@ function assertInheritedContract(
|
|
|
287
251
|
* Resolve and validate the opt-in theme-local token contract.
|
|
288
252
|
*
|
|
289
253
|
* Themes that omit `localTokens` and do not extend an enrolled base bypass this
|
|
290
|
-
* function's
|
|
254
|
+
* function's exact owner and lineage checks so legacy token behavior stays intact.
|
|
291
255
|
*/
|
|
292
256
|
export function resolveLocalTokenContract(
|
|
293
257
|
input: {
|
|
@@ -296,9 +260,7 @@ export function resolveLocalTokenContract(
|
|
|
296
260
|
},
|
|
297
261
|
base: DefinedTheme | undefined,
|
|
298
262
|
tokens: Record<string, string>,
|
|
299
|
-
|
|
300
|
-
onDark: ResolvedOnMedia | undefined,
|
|
301
|
-
onLight: ResolvedOnMedia | undefined,
|
|
263
|
+
portableTokens: Readonly<Record<string, string>>,
|
|
302
264
|
): ResolvedLocalTokenContract | undefined {
|
|
303
265
|
const directlyEnrolled = hasOwn(input, 'localTokens');
|
|
304
266
|
const inherited = base?.__localTokenLineage !== undefined;
|
|
@@ -307,12 +269,6 @@ export function resolveLocalTokenContract(
|
|
|
307
269
|
return undefined;
|
|
308
270
|
}
|
|
309
271
|
|
|
310
|
-
if (directlyEnrolled && !THEME_NAME_PATTERN.test(input.name)) {
|
|
311
|
-
throw new Error(
|
|
312
|
-
`defineTheme("${input.name}"): themes using localTokens require a stable lower-kebab name.`,
|
|
313
|
-
);
|
|
314
|
-
}
|
|
315
|
-
|
|
316
272
|
if (inherited && base) {
|
|
317
273
|
assertInheritedContract(input.name, base);
|
|
318
274
|
}
|
|
@@ -335,13 +291,12 @@ export function resolveLocalTokenContract(
|
|
|
335
291
|
|
|
336
292
|
for (const [name, value] of Object.entries(declarations ?? {})) {
|
|
337
293
|
const inheritedOwner = owners[name];
|
|
294
|
+
if (!isValidCSSCustomPropertyName(name)) {
|
|
295
|
+
throw new Error(
|
|
296
|
+
`defineTheme("${input.name}"): local token "${name}" must be a valid CSS custom-property name.`,
|
|
297
|
+
);
|
|
298
|
+
}
|
|
338
299
|
if (!inheritedOwner) {
|
|
339
|
-
const expectedPrefix = `${LOCAL_TOKEN_PREFIX}${input.name}-`;
|
|
340
|
-
if (!isExactLocalTokenName(name, input.name)) {
|
|
341
|
-
throw new Error(
|
|
342
|
-
`defineTheme("${input.name}"): local token "${name}" must use the exact namespace "${expectedPrefix}" followed by a lowercase kebab-case purpose.`,
|
|
343
|
-
);
|
|
344
|
-
}
|
|
345
300
|
owners[name] = input.name;
|
|
346
301
|
}
|
|
347
302
|
localTokens[name] = resolveTokenValue(
|
|
@@ -356,14 +311,13 @@ export function resolveLocalTokenContract(
|
|
|
356
311
|
`defineTheme("${input.name}"): inherited local token "${name}" has no owner metadata.`,
|
|
357
312
|
);
|
|
358
313
|
}
|
|
359
|
-
if (hasOwn(tokens, name)) {
|
|
314
|
+
if (hasOwn(tokens, name) || hasOwn(portableTokens, name)) {
|
|
360
315
|
throw new Error(
|
|
361
316
|
`defineTheme("${input.name}"): token "${name}" cannot be declared in both tokens and localTokens.`,
|
|
362
317
|
);
|
|
363
318
|
}
|
|
364
319
|
}
|
|
365
320
|
|
|
366
|
-
assertDeclaredReferences(localTokens, components, onDark, onLight);
|
|
367
321
|
assertNoTokenCycles(localTokens, `defineTheme("${input.name}").localTokens`);
|
|
368
322
|
|
|
369
323
|
return {
|
|
@@ -377,29 +331,17 @@ export function resolveLocalTokenContract(
|
|
|
377
331
|
* Resolve theme-local values written by one adaptation rule.
|
|
378
332
|
*
|
|
379
333
|
* Adaptations may replace names already enrolled by the root theme lineage, but
|
|
380
|
-
* they never enroll names of their own.
|
|
381
|
-
*
|
|
334
|
+
* they never enroll names of their own. Cycle validation waits until all
|
|
335
|
+
* co-matching writes have cascaded in order.
|
|
382
336
|
*/
|
|
383
337
|
export function resolveAdaptationLocalTokens(
|
|
384
338
|
themeName: string,
|
|
385
339
|
ruleIndex: number,
|
|
386
340
|
declarations: Record<string, TokenValue> | undefined,
|
|
387
341
|
rootLocalTokens: Record<string, string> | undefined,
|
|
388
|
-
tokens: Record<string, string>,
|
|
389
|
-
components: ComponentStyleMap | undefined,
|
|
390
342
|
): Record<string, string> | undefined {
|
|
391
343
|
const path = `defineTheme("${themeName}").adaptations.rules[${ruleIndex}].value.localTokens`;
|
|
392
344
|
if (declarations === undefined) {
|
|
393
|
-
const refs = new Set<string>();
|
|
394
|
-
collectLocalReferences(tokens, refs);
|
|
395
|
-
collectLocalReferences(components, refs);
|
|
396
|
-
for (const reference of refs) {
|
|
397
|
-
if (!rootLocalTokens || !hasOwn(rootLocalTokens, reference)) {
|
|
398
|
-
throw new Error(
|
|
399
|
-
`${path}: theme-local token reference "${reference}" has no declaration in the enrolled root theme lineage.`,
|
|
400
|
-
);
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
345
|
return undefined;
|
|
404
346
|
}
|
|
405
347
|
if (
|
|
@@ -420,18 +362,5 @@ export function resolveAdaptationLocalTokens(
|
|
|
420
362
|
resolved[name] = resolveTokenValue(value, `${path}["${name}"]`);
|
|
421
363
|
}
|
|
422
364
|
|
|
423
|
-
const effective = {...rootLocalTokens, ...resolved};
|
|
424
|
-
const refs = new Set<string>();
|
|
425
|
-
collectLocalReferences(resolved, refs);
|
|
426
|
-
collectLocalReferences(tokens, refs);
|
|
427
|
-
collectLocalReferences(components, refs);
|
|
428
|
-
for (const reference of refs) {
|
|
429
|
-
if (!hasOwn(effective, reference)) {
|
|
430
|
-
throw new Error(
|
|
431
|
-
`${path}: theme-local token reference "${reference}" has no declaration in the enrolled root theme lineage.`,
|
|
432
|
-
);
|
|
433
|
-
}
|
|
434
|
-
}
|
|
435
|
-
|
|
436
365
|
return Object.keys(resolved).length > 0 ? resolved : undefined;
|
|
437
366
|
}
|
|
@@ -29,9 +29,9 @@ const theme = defineTheme({
|
|
|
29
29
|
const selector = '.astryx-button[data-variant="primary"]';
|
|
30
30
|
|
|
31
31
|
describe('public theme helper selector contract', () => {
|
|
32
|
-
it('keeps
|
|
32
|
+
it('keeps canonical data attributes alongside bare compatibility classes', () => {
|
|
33
33
|
expect(themeProps('button', {variant: 'primary', size: 'sm'})).toEqual({
|
|
34
|
-
className: 'astryx-button',
|
|
34
|
+
className: 'astryx-button primary sm',
|
|
35
35
|
'data-variant': 'primary',
|
|
36
36
|
'data-size': 'sm',
|
|
37
37
|
});
|
|
@@ -443,50 +443,24 @@ describe('theme-local adaptation values', () => {
|
|
|
443
443
|
).toThrow(/write it through value.localTokens/);
|
|
444
444
|
});
|
|
445
445
|
|
|
446
|
-
it('
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
},
|
|
460
|
-
],
|
|
461
|
-
},
|
|
462
|
-
} as unknown as DefineThemeInput),
|
|
463
|
-
).toThrow(/reserved.*value\.localTokens/i);
|
|
464
|
-
});
|
|
446
|
+
it('treats an unenrolled prefix-like adaptation token as portable', () => {
|
|
447
|
+
const name = '--astryx-theme-unrelated-owner-control-height';
|
|
448
|
+
const theme = defineTheme({
|
|
449
|
+
name: 'external-rule-token',
|
|
450
|
+
adaptations: {
|
|
451
|
+
rules: [
|
|
452
|
+
{
|
|
453
|
+
when: {pointer: 'coarse'},
|
|
454
|
+
value: {tokens: {[name]: '44px'}},
|
|
455
|
+
},
|
|
456
|
+
],
|
|
457
|
+
},
|
|
458
|
+
} as unknown as DefineThemeInput);
|
|
465
459
|
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
defineTheme({
|
|
469
|
-
name: 'local-root',
|
|
470
|
-
localTokens: {[localName]: '32px'},
|
|
471
|
-
adaptations: {
|
|
472
|
-
rules: [
|
|
473
|
-
{
|
|
474
|
-
when: {pointer: 'coarse'},
|
|
475
|
-
value: {
|
|
476
|
-
components: {
|
|
477
|
-
button: {
|
|
478
|
-
base: {
|
|
479
|
-
minHeight: 'var(--astryx-theme-local-root-missing-name)',
|
|
480
|
-
},
|
|
481
|
-
},
|
|
482
|
-
},
|
|
483
|
-
},
|
|
484
|
-
},
|
|
485
|
-
],
|
|
486
|
-
},
|
|
487
|
-
}),
|
|
488
|
-
).toThrow(/has no declaration/);
|
|
460
|
+
expect(theme.__adaptationRules?.[0].tokens[name]).toBe('44px');
|
|
461
|
+
});
|
|
489
462
|
|
|
463
|
+
it('rejects conditional local-token cycles', () => {
|
|
490
464
|
const a = '--astryx-theme-cycle-local-a';
|
|
491
465
|
const b = '--astryx-theme-cycle-local-b';
|
|
492
466
|
expect(() =>
|
|
@@ -27,11 +27,7 @@ import type {MotionScaleConfig} from './expandMotionScale';
|
|
|
27
27
|
import type {RadiusScaleConfig} from './expandRadiusScale';
|
|
28
28
|
import type {ColorScaleConfig} from './expandColorScale';
|
|
29
29
|
import {resolveThemeValues, type ThemeValuesInput} from './resolveThemeValues';
|
|
30
|
-
import {
|
|
31
|
-
assertNoTokenCycles,
|
|
32
|
-
isReservedThemeLocalTokenName,
|
|
33
|
-
resolveAdaptationLocalTokens,
|
|
34
|
-
} from './localTokens';
|
|
30
|
+
import {assertNoTokenCycles, resolveAdaptationLocalTokens} from './localTokens';
|
|
35
31
|
|
|
36
32
|
// =============================================================================
|
|
37
33
|
// Public authoring vocabulary
|
|
@@ -928,9 +924,12 @@ export function resolveThemeAdaptationRules(
|
|
|
928
924
|
}
|
|
929
925
|
|
|
930
926
|
for (const name of Object.keys(rule.value.tokens ?? {})) {
|
|
931
|
-
if (
|
|
927
|
+
if (
|
|
928
|
+
rootLocalTokens &&
|
|
929
|
+
Object.prototype.hasOwnProperty.call(rootLocalTokens, name)
|
|
930
|
+
) {
|
|
932
931
|
throw new Error(
|
|
933
|
-
`defineTheme("${themeName}").adaptations.rules[${index}].value.tokens["${name}"]
|
|
932
|
+
`defineTheme("${themeName}").adaptations.rules[${index}].value.tokens["${name}"] matches an enrolled theme-local declaration; write it through value.localTokens instead.`,
|
|
934
933
|
);
|
|
935
934
|
}
|
|
936
935
|
}
|
|
@@ -940,8 +939,6 @@ export function resolveThemeAdaptationRules(
|
|
|
940
939
|
index,
|
|
941
940
|
rule.value.localTokens,
|
|
942
941
|
rootLocalTokens,
|
|
943
|
-
resolved.tokens,
|
|
944
|
-
resolved.components,
|
|
945
942
|
);
|
|
946
943
|
|
|
947
944
|
return {
|