@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.
Files changed (203) hide show
  1. package/CHANGELOG.md +41 -3
  2. package/dist/AppShell/AppShell.d.ts.map +1 -1
  3. package/dist/BottomSheet/BottomSheetSwitcher.d.ts +12 -1
  4. package/dist/BottomSheet/BottomSheetSwitcher.d.ts.map +1 -1
  5. package/dist/BottomSheet/BottomSheetSwitcher.js +44 -15
  6. package/dist/Breadcrumbs/BreadcrumbItem.d.ts +3 -2
  7. package/dist/Breadcrumbs/BreadcrumbItem.d.ts.map +1 -1
  8. package/dist/Breadcrumbs/BreadcrumbItem.js +3 -7
  9. package/dist/Center/Center.d.ts +23 -16
  10. package/dist/Center/Center.d.ts.map +1 -1
  11. package/dist/Center/Center.js +7 -5
  12. package/dist/CodeBlock/CodeBlock.js +2 -2
  13. package/dist/DateInput/DateInput.d.ts.map +1 -1
  14. package/dist/DateInput/DateInput.js +12 -2
  15. package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
  16. package/dist/DateTimeInput/DateTimeInput.js +12 -2
  17. package/dist/Field/Field.d.ts.map +1 -1
  18. package/dist/Field/Field.js +1 -0
  19. package/dist/Field/InputClearButton.d.ts +2 -2
  20. package/dist/Field/InputClearButton.d.ts.map +1 -1
  21. package/dist/Field/InputClearButton.js +5 -1
  22. package/dist/Field/PanelSearchInput.d.ts.map +1 -1
  23. package/dist/Field/PanelSearchInput.js +16 -4
  24. package/dist/FileInput/FileInput.d.ts.map +1 -1
  25. package/dist/FileInput/FileInput.js +12 -1
  26. package/dist/HoverCard/useHoverCard.js +2 -2
  27. package/dist/Indicator/CheckboxIndicator.js +2 -2
  28. package/dist/Indicator/RadioIndicator.js +2 -2
  29. package/dist/Layer/layerStack.d.ts +10 -0
  30. package/dist/Layer/layerStack.d.ts.map +1 -1
  31. package/dist/Layer/layerStack.js +21 -9
  32. package/dist/Layer/useLayerDismissal.d.ts +2 -3
  33. package/dist/Layer/useLayerDismissal.d.ts.map +1 -1
  34. package/dist/Layer/useLayerDismissal.js +2 -3
  35. package/dist/NavIcon/NavIcon.js +2 -2
  36. package/dist/NumberInput/NumberInput.d.ts.map +1 -1
  37. package/dist/NumberInput/NumberInput.js +12 -2
  38. package/dist/Popover/usePopover.d.ts +3 -2
  39. package/dist/Popover/usePopover.d.ts.map +1 -1
  40. package/dist/Popover/usePopover.js +4 -2
  41. package/dist/ProgressBar/ProgressBar.js +2 -2
  42. package/dist/ScrollableArea/ScrollableArea.d.ts +79 -0
  43. package/dist/ScrollableArea/ScrollableArea.d.ts.map +1 -0
  44. package/dist/ScrollableArea/ScrollableArea.js +144 -0
  45. package/dist/ScrollableArea/index.d.ts +11 -0
  46. package/dist/ScrollableArea/index.d.ts.map +1 -0
  47. package/dist/ScrollableArea/index.js +11 -0
  48. package/dist/StatusDot/StatusDot.js +2 -2
  49. package/dist/TextArea/TextArea.js +2 -2
  50. package/dist/TextInput/TextInput.d.ts.map +1 -1
  51. package/dist/TextInput/TextInput.js +19 -4
  52. package/dist/TimeInput/TimeInput.d.ts.map +1 -1
  53. package/dist/TimeInput/TimeInput.js +12 -2
  54. package/dist/Typeahead/BaseTypeahead.d.ts +21 -14
  55. package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
  56. package/dist/Typeahead/BaseTypeahead.js +56 -20
  57. package/dist/astryx.css +15 -0
  58. package/dist/hooks/index.d.ts +2 -0
  59. package/dist/hooks/index.d.ts.map +1 -1
  60. package/dist/hooks/index.js +1 -0
  61. package/dist/hooks/scrollGeometry.d.ts +24 -0
  62. package/dist/hooks/scrollGeometry.d.ts.map +1 -0
  63. package/dist/hooks/scrollGeometry.js +86 -0
  64. package/dist/hooks/scrollOwnerRegistry.d.ts +15 -0
  65. package/dist/hooks/scrollOwnerRegistry.d.ts.map +1 -0
  66. package/dist/hooks/scrollOwnerRegistry.js +24 -0
  67. package/dist/hooks/useFocusTrap.d.ts +8 -0
  68. package/dist/hooks/useFocusTrap.d.ts.map +1 -1
  69. package/dist/hooks/useFocusTrap.js +22 -11
  70. package/dist/hooks/useScrollableArea.d.ts +51 -0
  71. package/dist/hooks/useScrollableArea.d.ts.map +1 -0
  72. package/dist/hooks/useScrollableArea.js +287 -0
  73. package/dist/index.d.ts +1 -0
  74. package/dist/index.d.ts.map +1 -1
  75. package/dist/index.js +1 -0
  76. package/dist/theme/defineTheme.d.ts +2 -6
  77. package/dist/theme/defineTheme.d.ts.map +1 -1
  78. package/dist/theme/defineTheme.js +1 -1
  79. package/dist/theme/derivedVarRegistry.js +1 -1
  80. package/dist/theme/localTokens.d.ts +8 -11
  81. package/dist/theme/localTokens.d.ts.map +1 -1
  82. package/dist/theme/localTokens.js +17 -71
  83. package/dist/theme/themeAdaptations.d.ts.map +1 -1
  84. package/dist/theme/themeAdaptations.js +4 -4
  85. package/dist/utils/themeProps.d.ts +10 -10
  86. package/dist/utils/themeProps.d.ts.map +1 -1
  87. package/dist/utils/themeProps.js +27 -10
  88. package/locales/en.json +16 -0
  89. package/locales/pseudo.json +12 -0
  90. package/package.json +7 -2
  91. package/src/AppShell/AppShell.test.tsx +36 -0
  92. package/src/AppShell/AppShell.tsx +4 -1
  93. package/src/AspectRatio/AspectRatio.doc.mjs +3 -3
  94. package/src/Banner/Banner.test.tsx +3 -1
  95. package/src/BottomSheet/BottomSheetSwitcher.doc.mjs +56 -1
  96. package/src/BottomSheet/BottomSheetSwitcher.spec.md +211 -0
  97. package/src/BottomSheet/BottomSheetSwitcher.test.tsx +134 -2
  98. package/src/BottomSheet/BottomSheetSwitcher.tsx +43 -20
  99. package/src/Breadcrumbs/BreadcrumbItem.doc.mjs +10 -5
  100. package/src/Breadcrumbs/BreadcrumbItem.spec.md +225 -0
  101. package/src/Breadcrumbs/BreadcrumbItem.tsx +8 -13
  102. package/src/Breadcrumbs/Breadcrumbs.doc.mjs +2 -2
  103. package/src/Breadcrumbs/Breadcrumbs.test.tsx +49 -2
  104. package/src/Center/Center.doc.mjs +32 -28
  105. package/src/Center/Center.spec.md +225 -0
  106. package/src/Center/Center.test.tsx +42 -4
  107. package/src/Center/Center.tsx +24 -17
  108. package/src/Chat/ChatSystemMessage.test.tsx +2 -9
  109. package/src/CodeBlock/CodeBlock.doc.mjs +2 -2
  110. package/src/CodeBlock/CodeBlock.tsx +2 -2
  111. package/src/DateInput/DateInput.test.tsx +4 -4
  112. package/src/DateInput/DateInput.tsx +15 -4
  113. package/src/DateRangeInput/DateRangeInput.test.tsx +2 -2
  114. package/src/DateTimeInput/DateTimeInput.test.tsx +6 -4
  115. package/src/DateTimeInput/DateTimeInput.tsx +18 -7
  116. package/src/DropdownMenu/DropdownMenuSelectable.test.tsx +4 -77
  117. package/src/Field/Field.test.tsx +42 -0
  118. package/src/Field/Field.tsx +6 -0
  119. package/src/Field/InputClearButton.test.tsx +35 -1
  120. package/src/Field/InputClearButton.tsx +7 -3
  121. package/src/Field/PanelSearchInput.tsx +21 -8
  122. package/src/FieldStatus/FieldStatus.spec.md +27 -17
  123. package/src/FieldStatus/FieldStatus.test.tsx +7 -5
  124. package/src/FieldStatus/__tests__/StatusMessage.a11y.chromium.spec.ts +198 -0
  125. package/src/FieldStatus/__tests__/StatusMessage.a11y.known-failures.ts +13 -0
  126. package/src/FieldStatus/__tests__/StatusMessage.a11y.renders.tsx +305 -0
  127. package/src/FieldStatus/__tests__/StatusMessage.a11y.states.ts +317 -0
  128. package/src/FieldStatus/__tests__/StatusMessage.a11y.test.tsx +155 -0
  129. package/src/FileInput/FileInput.tsx +10 -1
  130. package/src/FormLayout/__snapshots__/FormLayout.test.tsx.snap +3 -3
  131. package/src/HoverCard/HoverCard.doc.mjs +4 -4
  132. package/src/HoverCard/useHoverCard.tsx +2 -2
  133. package/src/Indicator/CheckboxIndicator.tsx +2 -2
  134. package/src/Indicator/Indicator.doc.mjs +2 -2
  135. package/src/Indicator/Indicator.test.tsx +1 -1
  136. package/src/Indicator/RadioIndicator.tsx +2 -2
  137. package/src/Layer/layerStack.ts +20 -9
  138. package/src/Layer/useLayerDismissal.ts +2 -3
  139. package/src/MultiSelector/MultiSelector.test.tsx +4 -4
  140. package/src/NavIcon/NavIcon.doc.mjs +4 -4
  141. package/src/NavIcon/NavIcon.tsx +2 -2
  142. package/src/NumberInput/NumberInput.tsx +18 -7
  143. package/src/Popover/Popover.doc.mjs +10 -10
  144. package/src/Popover/Popover.spec.md +55 -65
  145. package/src/Popover/Popover.test.tsx +29 -0
  146. package/src/Popover/usePopover.doc.mjs +4 -4
  147. package/src/Popover/usePopover.tsx +7 -4
  148. package/src/ProgressBar/ProgressBar.doc.mjs +4 -4
  149. package/src/ProgressBar/ProgressBar.test.tsx +1 -31
  150. package/src/ProgressBar/ProgressBar.tsx +2 -2
  151. package/src/RadioList/RadioList.test.tsx +5 -144
  152. package/src/RadioList/__tests__/RadioGroup.a11y.chromium.spec.ts +255 -0
  153. package/src/RadioList/__tests__/RadioGroup.a11y.known-failures.ts +12 -0
  154. package/src/RadioList/__tests__/RadioGroup.a11y.renders.tsx +232 -0
  155. package/src/RadioList/__tests__/RadioGroup.a11y.states.ts +503 -0
  156. package/src/RadioList/__tests__/RadioGroup.a11y.test.tsx +217 -0
  157. package/src/ScrollableArea/ScrollableArea.doc.mjs +100 -0
  158. package/src/ScrollableArea/ScrollableArea.spec.md +189 -0
  159. package/src/ScrollableArea/ScrollableArea.test.tsx +299 -0
  160. package/src/ScrollableArea/ScrollableArea.tsx +259 -0
  161. package/src/ScrollableArea/index.ts +26 -0
  162. package/src/ScrollableArea/modules/useScrollableArea.spec.md +121 -0
  163. package/src/SegmentedControl/SegmentedControl.test.tsx +5 -172
  164. package/src/Selector/Selector.test.tsx +4 -4
  165. package/src/Spinner/Spinner.test.tsx +0 -18
  166. package/src/StatusDot/StatusDot.doc.mjs +4 -4
  167. package/src/StatusDot/StatusDot.tsx +2 -2
  168. package/src/TabList/TabList.test.tsx +5 -9
  169. package/src/TabList/__tests__/Tabs.a11y.chromium.spec.ts +191 -0
  170. package/src/TabList/__tests__/Tabs.a11y.known-failures.ts +45 -0
  171. package/src/TabList/__tests__/Tabs.a11y.renders.tsx +92 -0
  172. package/src/TabList/__tests__/Tabs.a11y.states.ts +247 -0
  173. package/src/TabList/__tests__/Tabs.a11y.test.tsx +153 -0
  174. package/src/Table/Table.doc.mjs +2 -2
  175. package/src/TextArea/TextArea.doc.mjs +4 -4
  176. package/src/TextArea/TextArea.tsx +2 -2
  177. package/src/TextInput/TextInput.doc.mjs +2 -1
  178. package/src/TextInput/TextInput.test.tsx +94 -0
  179. package/src/TextInput/TextInput.tsx +22 -6
  180. package/src/TimeInput/TimeInput.tsx +18 -7
  181. package/src/Toast/ToastViewport.test.tsx +1 -39
  182. package/src/Typeahead/BaseTypeahead.doc.mjs +229 -33
  183. package/src/Typeahead/BaseTypeahead.spec.md +269 -0
  184. package/src/Typeahead/BaseTypeahead.test.tsx +200 -0
  185. package/src/Typeahead/BaseTypeahead.tsx +99 -30
  186. package/src/hooks/index.ts +13 -0
  187. package/src/hooks/scrollGeometry.ts +155 -0
  188. package/src/hooks/scrollOwnerRegistry.ts +47 -0
  189. package/src/hooks/useFocusTrap.ts +22 -11
  190. package/src/hooks/useFocusTrapEscapeShim.test.tsx +4 -3
  191. package/src/hooks/useScrollableArea.doc.mjs +108 -0
  192. package/src/hooks/useScrollableArea.test.tsx +437 -0
  193. package/src/hooks/useScrollableArea.ts +469 -0
  194. package/src/index.ts +1 -0
  195. package/src/theme/defineTheme.test.ts +65 -105
  196. package/src/theme/defineTheme.ts +3 -9
  197. package/src/theme/derivedVarRegistry.ts +1 -1
  198. package/src/theme/localTokens.ts +25 -96
  199. package/src/theme/publicThemeHelperContract.test.ts +2 -2
  200. package/src/theme/themeAdaptations.test.ts +16 -42
  201. package/src/theme/themeAdaptations.ts +6 -9
  202. package/src/utils/themeProps.test.ts +29 -10
  203. package/src/utils/themeProps.ts +36 -17
@@ -3,26 +3,19 @@
3
3
  /**
4
4
  * @file localTokens.ts
5
5
  * @input Root and adaptation theme-local token declarations and references
6
- * @output Validated enrollment metadata plus normalized conditional writes
7
- * @position Theme-local namespace owner; final adaptation-cycle validation is
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
- const LOCAL_TOKEN_PREFIX = '--astryx-theme-';
12
- const THEME_NAME_PATTERN = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
13
- const LOCAL_TOKEN_SUFFIX_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
14
- const CSS_VAR_PATTERN = /var\(\s*(--[^,\s)]+)/gi;
15
-
16
- /** Whether a custom-property name belongs to Astryx's reserved local namespace. */
17
- export function isReservedThemeLocalTokenName(name) {
18
- return name.startsWith(LOCAL_TOKEN_PREFIX);
19
- }
11
+ 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]))`;
12
+ const CSS_CUSTOM_PROPERTY_NAME_PATTERN = new RegExp(`^--${CSS_NAME_CHARACTER}*$`, 'u');
13
+ const CSS_VAR_PATTERN = new RegExp(`var\\(\\s*(--${CSS_NAME_CHARACTER}*)(?=\\s*(?:,|\\)))`, 'giu');
20
14
  function hasOwn(object, key) {
21
15
  return Object.prototype.hasOwnProperty.call(object, key);
22
16
  }
23
- function isExactLocalTokenName(name, owner) {
24
- const prefix = `${LOCAL_TOKEN_PREFIX}${owner}-`;
25
- return name.startsWith(prefix) && LOCAL_TOKEN_SUFFIX_PATTERN.test(name.slice(prefix.length));
17
+ function isValidCSSCustomPropertyName(name) {
18
+ return !name.includes('\0') && CSS_CUSTOM_PROPERTY_NAME_PATTERN.test(name);
26
19
  }
27
20
  function resolveTokenValue(value, path) {
28
21
  if (typeof value === 'string') {
@@ -53,15 +46,6 @@ function collectCustomPropertyReferences(value, refs) {
53
46
  }
54
47
  }
55
48
  }
56
- function collectLocalReferences(value, refs) {
57
- const customProperties = new Set();
58
- collectCustomPropertyReferences(value, customProperties);
59
- for (const name of customProperties) {
60
- if (isReservedThemeLocalTokenName(name)) {
61
- refs.add(name);
62
- }
63
- }
64
- }
65
49
  export function assertNoTokenCycles(tokenValues, context, relevantNames) {
66
50
  const dependencies = new Map();
67
51
  for (const [name, value] of Object.entries(tokenValues)) {
@@ -140,18 +124,6 @@ export function assertNoTokenCycles(tokenValues, context, relevantNames) {
140
124
  }
141
125
  }
142
126
  }
143
- function assertDeclaredReferences(localTokens, components, onDark, onLight) {
144
- const refs = new Set();
145
- collectLocalReferences(localTokens, refs);
146
- collectLocalReferences(components, refs);
147
- collectLocalReferences(onDark?.components, refs);
148
- collectLocalReferences(onLight?.components, refs);
149
- for (const reference of refs) {
150
- if (!hasOwn(localTokens, reference)) {
151
- throw new Error(`Theme-local token reference "${reference}" has no declaration in the enrolled theme lineage.`);
152
- }
153
- }
154
- }
155
127
  function assertInheritedContract(themeName, base) {
156
128
  const lineage = base.__localTokenLineage;
157
129
  if (!Array.isArray(lineage) || lineage.length === 0 || lineage[lineage.length - 1] !== base.name || lineage.some(name => typeof name !== 'string' || name.length === 0)) {
@@ -162,7 +134,7 @@ function assertInheritedContract(themeName, base) {
162
134
  }
163
135
  for (const [name, value] of Object.entries(base.localTokens)) {
164
136
  const owner = base.__localTokenOwners[name];
165
- if (typeof value !== 'string' || !owner || !lineage.includes(owner) || !isExactLocalTokenName(name, owner)) {
137
+ if (typeof value !== 'string' || !owner || !lineage.includes(owner) || !isValidCSSCustomPropertyName(name)) {
166
138
  throw new Error(`defineTheme("${themeName}"): inherited local token "${name}" does not match its exact lineage metadata.`);
167
139
  }
168
140
  }
@@ -177,17 +149,14 @@ function assertInheritedContract(themeName, base) {
177
149
  * Resolve and validate the opt-in theme-local token contract.
178
150
  *
179
151
  * Themes that omit `localTokens` and do not extend an enrolled base bypass this
180
- * function's reserved-namespace checks so legacy token behavior stays intact.
152
+ * function's exact owner and lineage checks so legacy token behavior stays intact.
181
153
  */
182
- export function resolveLocalTokenContract(input, base, tokens, components, onDark, onLight) {
154
+ export function resolveLocalTokenContract(input, base, tokens, portableTokens) {
183
155
  const directlyEnrolled = hasOwn(input, 'localTokens');
184
156
  const inherited = base?.__localTokenLineage !== undefined;
185
157
  if (!directlyEnrolled && !inherited) {
186
158
  return undefined;
187
159
  }
188
- if (directlyEnrolled && !THEME_NAME_PATTERN.test(input.name)) {
189
- throw new Error(`defineTheme("${input.name}"): themes using localTokens require a stable lower-kebab name.`);
190
- }
191
160
  if (inherited && base) {
192
161
  assertInheritedContract(input.name, base);
193
162
  }
@@ -203,11 +172,10 @@ export function resolveLocalTokenContract(input, base, tokens, components, onDar
203
172
  }
204
173
  for (const [name, value] of Object.entries(declarations ?? {})) {
205
174
  const inheritedOwner = owners[name];
175
+ if (!isValidCSSCustomPropertyName(name)) {
176
+ throw new Error(`defineTheme("${input.name}"): local token "${name}" must be a valid CSS custom-property name.`);
177
+ }
206
178
  if (!inheritedOwner) {
207
- const expectedPrefix = `${LOCAL_TOKEN_PREFIX}${input.name}-`;
208
- if (!isExactLocalTokenName(name, input.name)) {
209
- throw new Error(`defineTheme("${input.name}"): local token "${name}" must use the exact namespace "${expectedPrefix}" followed by a lowercase kebab-case purpose.`);
210
- }
211
179
  owners[name] = input.name;
212
180
  }
213
181
  localTokens[name] = resolveTokenValue(value, `defineTheme("${input.name}").localTokens["${name}"]`);
@@ -216,11 +184,10 @@ export function resolveLocalTokenContract(input, base, tokens, components, onDar
216
184
  if (!owners[name]) {
217
185
  throw new Error(`defineTheme("${input.name}"): inherited local token "${name}" has no owner metadata.`);
218
186
  }
219
- if (hasOwn(tokens, name)) {
187
+ if (hasOwn(tokens, name) || hasOwn(portableTokens, name)) {
220
188
  throw new Error(`defineTheme("${input.name}"): token "${name}" cannot be declared in both tokens and localTokens.`);
221
189
  }
222
190
  }
223
- assertDeclaredReferences(localTokens, components, onDark, onLight);
224
191
  assertNoTokenCycles(localTokens, `defineTheme("${input.name}").localTokens`);
225
192
  return {
226
193
  localTokens,
@@ -233,20 +200,12 @@ export function resolveLocalTokenContract(input, base, tokens, components, onDar
233
200
  * Resolve theme-local values written by one adaptation rule.
234
201
  *
235
202
  * Adaptations may replace names already enrolled by the root theme lineage, but
236
- * they never enroll names of their own. This validates one rule's references;
237
- * cycle validation waits until all co-matching writes have cascaded in order.
203
+ * they never enroll names of their own. Cycle validation waits until all
204
+ * co-matching writes have cascaded in order.
238
205
  */
239
- export function resolveAdaptationLocalTokens(themeName, ruleIndex, declarations, rootLocalTokens, tokens, components) {
206
+ export function resolveAdaptationLocalTokens(themeName, ruleIndex, declarations, rootLocalTokens) {
240
207
  const path = `defineTheme("${themeName}").adaptations.rules[${ruleIndex}].value.localTokens`;
241
208
  if (declarations === undefined) {
242
- const refs = new Set();
243
- collectLocalReferences(tokens, refs);
244
- collectLocalReferences(components, refs);
245
- for (const reference of refs) {
246
- if (!rootLocalTokens || !hasOwn(rootLocalTokens, reference)) {
247
- throw new Error(`${path}: theme-local token reference "${reference}" has no declaration in the enrolled root theme lineage.`);
248
- }
249
- }
250
209
  return undefined;
251
210
  }
252
211
  if (declarations === null || typeof declarations !== 'object' || Array.isArray(declarations)) {
@@ -259,18 +218,5 @@ export function resolveAdaptationLocalTokens(themeName, ruleIndex, declarations,
259
218
  }
260
219
  resolved[name] = resolveTokenValue(value, `${path}["${name}"]`);
261
220
  }
262
- const effective = {
263
- ...rootLocalTokens,
264
- ...resolved
265
- };
266
- const refs = new Set();
267
- collectLocalReferences(resolved, refs);
268
- collectLocalReferences(tokens, refs);
269
- collectLocalReferences(components, refs);
270
- for (const reference of refs) {
271
- if (!hasOwn(effective, reference)) {
272
- throw new Error(`${path}: theme-local token reference "${reference}" has no declaration in the enrolled root theme lineage.`);
273
- }
274
- }
275
221
  return Object.keys(resolved).length > 0 ? resolved : undefined;
276
222
  }
@@ -1 +1 @@
1
- {"version":3,"file":"themeAdaptations.d.ts","sourceRoot":"","sources":["../../src/theme/themeAdaptations.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"themeAdaptations.d.ts","sourceRoot":"","sources":["../../src/theme/themeAdaptations.ts"],"names":[],"mappings":"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"}
@@ -22,7 +22,7 @@
22
22
  */
23
23
 
24
24
  import { resolveThemeValues } from "./resolveThemeValues.js";
25
- import { assertNoTokenCycles, isReservedThemeLocalTokenName, resolveAdaptationLocalTokens } from "./localTokens.js";
25
+ import { assertNoTokenCycles, resolveAdaptationLocalTokens } from "./localTokens.js";
26
26
 
27
27
  // =============================================================================
28
28
  // Public authoring vocabulary
@@ -593,11 +593,11 @@ export function resolveThemeAdaptationRules(themeName, adaptations, axes, rootTo
593
593
  assertConcreteLeaf(resolved.components, `defineTheme("${themeName}").adaptations.rules[${index}].value.components`);
594
594
  }
595
595
  for (const name of Object.keys(rule.value.tokens ?? {})) {
596
- if (isReservedThemeLocalTokenName(name)) {
597
- throw new Error(`defineTheme("${themeName}").adaptations.rules[${index}].value.tokens["${name}"] uses the reserved --astryx-theme-* namespace; write it through value.localTokens instead.`);
596
+ if (rootLocalTokens && Object.prototype.hasOwnProperty.call(rootLocalTokens, name)) {
597
+ throw new Error(`defineTheme("${themeName}").adaptations.rules[${index}].value.tokens["${name}"] matches an enrolled theme-local declaration; write it through value.localTokens instead.`);
598
598
  }
599
599
  }
600
- const localTokens = resolveAdaptationLocalTokens(themeName, index, rule.value.localTokens, rootLocalTokens, resolved.tokens, resolved.components);
600
+ const localTokens = resolveAdaptationLocalTokens(themeName, index, rule.value.localTokens, rootLocalTokens);
601
601
  return {
602
602
  when: rule.when,
603
603
  query: mediaQueryForCondition(themeName, index, rule.when, adaptations.widthBreakpoints),
@@ -17,12 +17,13 @@ export declare function themeDataAttributes(props?: ClassProps): ThemeDataAttrib
17
17
  * Build the props object components should spread onto the same element that
18
18
  * receives the stable Astryx class name.
19
19
  *
20
- * This emits one stable astryx target class plus data-attribute reflection for
21
- * visual props and runtime states. For example:
20
+ * This emits one stable Astryx target, deprecated bare compatibility classes,
21
+ * and canonical data-attribute reflection for visual props and runtime states.
22
+ * For example:
22
23
  *
23
24
  * ```ts
24
25
  * themeProps('button', { variant: 'primary', size: 'sm' })
25
- * // → { className: 'astryx-button', data-variant: 'primary', data-size: 'sm' }
26
+ * // → { className: 'astryx-button primary sm', data-variant: 'primary', data-size: 'sm' }
26
27
  * ```
27
28
  */
28
29
  /**
@@ -30,17 +31,16 @@ export declare function themeDataAttributes(props?: ClassProps): ThemeDataAttrib
30
31
  */
31
32
  export type ThemePropsOptions = {
32
33
  /**
33
- * Stable class names to emit ALONGSIDE the component's own, for targets that
34
- * have been renamed.
34
+ * Stable target names to emit alongside the canonical target for backwards
35
+ * compatibility.
35
36
  *
36
37
  * A theme target is public API: renaming one silently breaks every theme
37
- * that styles it. Emitting the old name beside the new one keeps those
38
- * themes working through a deprecation window, at the cost of one extra
39
- * class on the element until the old name is dropped in a major.
38
+ * that styles it. Keep aliases emitted unless a separate compatibility
39
+ * decision explicitly retires them.
40
40
  *
41
41
  * Pass plain string literals — the theming guards scan for them statically.
42
- * Document the old name with `deprecated` in the component's
43
- * `theming.targets` so the docsite says which to use.
42
+ * Document each old name with `deprecatedFor` in the component's
43
+ * `theming.targets` so discovery and diagnostics name the replacement.
44
44
  */
45
45
  legacyNames?: ReadonlyArray<string>;
46
46
  };
@@ -1 +1 @@
1
- {"version":3,"file":"themeProps.d.ts","sourceRoot":"","sources":["../../src/utils/themeProps.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,MAAM,GAAG,SAAS,GAAG,IAAI,CAAC;AAC5D,MAAM,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;AACpD,MAAM,MAAM,mBAAmB,GAAG,MAAM,CAAC,QAAQ,MAAM,EAAE,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;AAC/E,MAAM,MAAM,UAAU,GAAG;IAAC,SAAS,EAAE,MAAM,CAAA;CAAC,GAAG,mBAAmB,CAAC;AAEnE,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,MAAM,EAAE,CAErE;AAuBD;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,CAAC,EAAE,UAAU,GAAG,mBAAmB,CAa3E;AAED;;;;;;;;;;;GAWG;AACH;;GAEG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC9B;;;;;;;;;;;;OAYG;IACH,WAAW,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;CACrC,CAAC;AAEF,wBAAgB,UAAU,CACxB,SAAS,EAAE,MAAM,EACjB,KAAK,CAAC,EAAE,UAAU,EAClB,OAAO,CAAC,EAAE,iBAAiB,GAC1B,UAAU,CAQZ"}
1
+ {"version":3,"file":"themeProps.d.ts","sourceRoot":"","sources":["../../src/utils/themeProps.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,MAAM,GAAG,SAAS,GAAG,IAAI,CAAC;AAC5D,MAAM,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;AACpD,MAAM,MAAM,mBAAmB,GAAG,MAAM,CAAC,QAAQ,MAAM,EAAE,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;AAC/E,MAAM,MAAM,UAAU,GAAG;IAAC,SAAS,EAAE,MAAM,CAAA;CAAC,GAAG,mBAAmB,CAAC;AAEnE,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,MAAM,EAAE,CAErE;AA0CD;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,CAAC,EAAE,UAAU,GAAG,mBAAmB,CAa3E;AAED;;;;;;;;;;;;GAYG;AACH;;GAEG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC9B;;;;;;;;;;;OAWG;IACH,WAAW,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;CACrC,CAAC;AAEF,wBAAgB,UAAU,CACxB,SAAS,EAAE,MAAM,EACjB,KAAK,CAAC,EAAE,UAAU,EAClB,OAAO,CAAC,EAAE,iBAAiB,GAC1B,UAAU,CAQZ"}
@@ -4,14 +4,20 @@ import { stableClassName } from "../naming.js";
4
4
  export function themeDataAttributeName(prop) {
5
5
  return `data-${prop.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase()}`;
6
6
  }
7
+ function legacyClassTokenForPropValue(prop, value) {
8
+ // CSS classes cannot start with a digit, so preserve the released prop prefix.
9
+ return /^\d/.test(value) ? `${prop}-${value}` : value;
10
+ }
7
11
 
8
12
  /**
9
13
  * Build the stable astryx-* class name string for a component.
10
14
  *
11
15
  * Every component renders one stable target class (`astryx-button`,
12
- * `astryx-card`, etc.). Visual props and runtime states are reflected only as
13
- * data attributes (`data-variant`, `data-size`, `data-selected`, etc.), which
14
- * preserve the axis name and cannot collide when two axes share a value.
16
+ * `astryx-card`, etc.). Visual props and runtime states use explicit data
17
+ * attributes (`data-variant`, `data-size`, `data-selected`, etc.) as their
18
+ * canonical selector surface. Released bare value/state classes remain on the
19
+ * same element as deprecated compatibility aliases until their 0.7.0 removal
20
+ * window.
15
21
  *
16
22
  * The `astryx-` prefix comes from the centralized naming module
17
23
  * (`packages/core/src/naming.ts`) so the namespace lives in one place.
@@ -20,10 +26,20 @@ export function themeDataAttributeName(prop) {
20
26
  * <!-- SYNC: packages/core/src/utils/parseStyleKey.ts -->
21
27
  *
22
28
  * @param component - Component name in lowercase (e.g. 'button', 'card')
23
- * @returns Stable class name (e.g. "astryx-button")
29
+ * @param props - Visual prop values whose released bare classes remain aliases
30
+ * @returns Stable target and deprecated compatibility classes
24
31
  */
25
- function buildClassName(component) {
26
- return stableClassName(component);
32
+ function buildClassName(component, props) {
33
+ const classes = [stableClassName(component)];
34
+ if (props) {
35
+ for (const [prop, value] of Object.entries(props)) {
36
+ if (value == null) {
37
+ continue;
38
+ }
39
+ classes.push(legacyClassTokenForPropValue(prop, String(value)));
40
+ }
41
+ }
42
+ return classes.join(' ');
27
43
  }
28
44
 
29
45
  /**
@@ -50,12 +66,13 @@ export function themeDataAttributes(props) {
50
66
  * Build the props object components should spread onto the same element that
51
67
  * receives the stable Astryx class name.
52
68
  *
53
- * This emits one stable astryx target class plus data-attribute reflection for
54
- * visual props and runtime states. For example:
69
+ * This emits one stable Astryx target, deprecated bare compatibility classes,
70
+ * and canonical data-attribute reflection for visual props and runtime states.
71
+ * For example:
55
72
  *
56
73
  * ```ts
57
74
  * themeProps('button', { variant: 'primary', size: 'sm' })
58
- * // → { className: 'astryx-button', data-variant: 'primary', data-size: 'sm' }
75
+ * // → { className: 'astryx-button primary sm', data-variant: 'primary', data-size: 'sm' }
59
76
  * ```
60
77
  */
61
78
  /**
@@ -63,7 +80,7 @@ export function themeDataAttributes(props) {
63
80
  */
64
81
 
65
82
  export function themeProps(component, props, options) {
66
- const className = buildClassName(component);
83
+ const className = buildClassName(component, props);
67
84
  const legacy = options?.legacyNames?.map(name => stableClassName(name)) ?? [];
68
85
  return {
69
86
  className: legacy.length > 0 ? [className, ...legacy].join(' ') : className,
package/locales/en.json CHANGED
@@ -347,6 +347,22 @@
347
347
  "defaultMessage": "Slide {current, number} of {total, number}",
348
348
  "description": "Screen-reader accessible name for one slide in a Carousel, giving its position. `current` is the 1-based slide number; `total` is the slide count."
349
349
  },
350
+ "@astryx.chart.label": {
351
+ "defaultMessage": "Chart",
352
+ "description": "Generic screen-reader name for a Chart with no primary series and no consumer-supplied title. Noun (a data visualization), not a verb."
353
+ },
354
+ "@astryx.chart.labelWithSeries": {
355
+ "defaultMessage": "Chart of {series} by {xKey}",
356
+ "description": "Generated screen-reader name for a Chart without a consumer-supplied title. `{series}` is a locale-formatted list of series labels; `{xKey}` is the consumer's horizontal data-field name."
357
+ },
358
+ "@astryx.chart.dataTableCaption": {
359
+ "defaultMessage": "{label} data",
360
+ "description": "Screen-reader-only caption for the small-data table that mirrors a Chart. `{label}` is the Chart's resolved accessible name."
361
+ },
362
+ "@astryx.chartLegend.label": {
363
+ "defaultMessage": "Chart legend",
364
+ "description": "Screen-reader-only accessible name for the list of series labels and swatches rendered by ChartLegend."
365
+ },
350
366
  "@astryx.chat.status.sending": {
351
367
  "defaultMessage": "Sending",
352
368
  "description": "Chat send-status caption under an outgoing message while it is being transmitted. Part of the set sending → sent → delivered → read (or failed) — keep tense/aspect consistent."
@@ -260,6 +260,18 @@
260
260
  "@astryx.carousel.slideLabel": {
261
261
  "defaultMessage": "⟦Šłíðé {current, number} óƒ {total, number}⟧"
262
262
  },
263
+ "@astryx.chart.label": {
264
+ "defaultMessage": "⟦Çĥàřţ⟧"
265
+ },
266
+ "@astryx.chart.labelWithSeries": {
267
+ "defaultMessage": "⟦Çĥàřţ óƒ {series} ƀý {xKey}⟧"
268
+ },
269
+ "@astryx.chart.dataTableCaption": {
270
+ "defaultMessage": "⟦{label} ðàţà⟧"
271
+ },
272
+ "@astryx.chartLegend.label": {
273
+ "defaultMessage": "⟦Çĥàřţ łéĝéñð⟧"
274
+ },
263
275
  "@astryx.chat.status.sending": {
264
276
  "defaultMessage": "⟦Šéñðíñĝ⟧"
265
277
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astryxdesign/core",
3
- "version": "0.6.0",
3
+ "version": "0.6.1",
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",
@@ -434,6 +434,11 @@
434
434
  "types": "./dist/Resizable/index.d.ts",
435
435
  "default": "./dist/Resizable/index.js"
436
436
  },
437
+ "./ScrollableArea": {
438
+ "source": "./src/ScrollableArea/index.ts",
439
+ "types": "./dist/ScrollableArea/index.d.ts",
440
+ "default": "./dist/ScrollableArea/index.js"
441
+ },
437
442
  "./Section": {
438
443
  "source": "./src/Section/index.ts",
439
444
  "types": "./dist/Section/index.d.ts",
@@ -676,7 +681,7 @@
676
681
  "@testing-library/jest-dom": "^6.6.0",
677
682
  "@testing-library/react": "^16.3.2",
678
683
  "@astryxdesign/a11y-spec": "0.0.0",
679
- "@astryxdesign/cli": "0.6.0"
684
+ "@astryxdesign/cli": "0.6.1"
680
685
  },
681
686
  "dependencies": {
682
687
  "intl-messageformat": "^11.2.9"
@@ -26,6 +26,7 @@ import {InternationalizationProvider} from '../i18n';
26
26
  import {MobileNav} from '../MobileNav';
27
27
  import {SideNav, SideNavItem, SideNavSection} from '../SideNav';
28
28
  import {TopNav, TopNavHeading, TopNavItem} from '../TopNav';
29
+ import {TextInput} from '../TextInput';
29
30
  import {useAppShellMobile} from './AppShellMobileContext';
30
31
  import {Theme, defineTheme} from '../theme';
31
32
 
@@ -706,6 +707,41 @@ describe('AppShell', () => {
706
707
  // Sticky navigation in auto mode
707
708
  // ===========================================================================
708
709
 
710
+ // A Field input wrapper carries a local z-index (it paints above the
711
+ // attached status message box via a negative-margin overlap), so without a
712
+ // component-owned isolation boundary it competes with page-level stacking —
713
+ // a field scrolled underneath the sticky header then paints over it (#5689).
714
+ // The fix is local containment: the Field root isolates the wrapper's local
715
+ // layer, so the header needs no escalated z-index to stay above it. This
716
+ // asserts that ownership boundary — the field's painted surface is an
717
+ // isolated stacking context — not a comparison between two page-level
718
+ // z-index values.
719
+ it('contains Field input stacking locally so the sticky header needs no escalated z-index in auto mode', () => {
720
+ render(
721
+ <AppShell height="auto" topNav={<div>Nav</div>}>
722
+ <TextInput
723
+ label="Name"
724
+ statusVariant="detached"
725
+ value=""
726
+ onChange={() => {}}
727
+ />
728
+ </AppShell>,
729
+ );
730
+ const header = screen.getByRole('banner');
731
+ const inputWrapper = screen.getByRole('textbox').parentElement!;
732
+ // The detached variant renders the input wrapper outside Field's isolated
733
+ // attached-status wrapper, so the Field root must own the boundary.
734
+ const fieldRoot = inputWrapper.parentElement!;
735
+ expect(inputWrapper).toBeTruthy();
736
+ expect(getComputedStyle(header).position).toBe('sticky');
737
+ // The header stays at its normal local stacking level — no escalation.
738
+ expect(Number(getComputedStyle(header).zIndex)).toBe(1);
739
+ expect(getComputedStyle(inputWrapper).position).toBe('relative');
740
+ // Field's local layers cannot escape into page-level stacking: the root
741
+ // establishes an isolation boundary around the input wrapper's z-index.
742
+ expect(getComputedStyle(fieldRoot).isolation).toBe('isolate');
743
+ });
744
+
709
745
  it('wraps header in sticky container in auto mode', () => {
710
746
  render(
711
747
  <AppShell
@@ -395,7 +395,10 @@ const styles = stylex.create({
395
395
  height: spacingVars['--spacing-12'],
396
396
  paddingInline: spacingVars['--spacing-2'],
397
397
  },
398
- // Sticky header for auto height mode
398
+ // Sticky header for auto height mode. The header stays at its local
399
+ // stacking level — Field-owned input surfaces contain their own local
400
+ // z-index layers (AST-027), so no escalated page-level value is needed
401
+ // for the header to paint above scrolled content.
399
402
  headerSticky: {
400
403
  position: 'sticky',
401
404
  top: 0,
@@ -11,7 +11,7 @@ export const docs = {
11
11
  description:
12
12
  'Maintains a fixed width-to-height ratio for its children as its container resizes. Use it for media containers like videos, images, thumbnails, or any content that needs consistent proportions. It takes its width from the container and derives its height from the ratio, so it needs an ancestor with a definite width.',
13
13
  bestPractices: [
14
- {guidance: true, description: 'Express the ratio as a fraction like `16/9` or `4/3` for readability.'},
14
+ {guidance: true, description: 'Express the ratio as a fraction for readability — `ratio={16 / 9}` rather than `ratio={1.78}`. It is a number, so the string form `ratio="16/9"` is a type error.'},
15
15
  {guidance: true, description: 'Use for media that needs consistent proportions across screen sizes.'},
16
16
  {guidance: true, description: 'Use `fit="cover"` for images and video so the component sizes the child; the child should not repeat `width`/`height`/`objectFit` styles.'},
17
17
  {guidance: true, description: 'Pass one child. With `fit` set, every direct child is stretched to fill the box, so put an overlay or caption inside a single wrapper child rather than passing it as a second child.'},
@@ -79,7 +79,7 @@ export const docsZh = {
79
79
  description:
80
80
  'Maintains a fixed width-to-height ratio for its children as its container resizes. Use it for media containers like videos, images, thumbnails, or any content that needs consistent proportions. It takes its width from the container and derives its height from the ratio, so it needs an ancestor with a definite width.',
81
81
  bestPractices: [
82
- {guidance: true, description: 'Express the ratio as a fraction like `16/9` or `4/3` for readability.'},
82
+ {guidance: true, description: 'Express the ratio as a fraction for readability — `ratio={16 / 9}` rather than `ratio={1.78}`. It is a number, so the string form `ratio="16/9"` is a type error.'},
83
83
  {guidance: true, description: 'Use for media that needs consistent proportions across screen sizes.'},
84
84
  {guidance: true, description: 'Use `fit="cover"` for images and video so the component sizes the child; the child should not repeat `width`/`height`/`objectFit` styles.'},
85
85
  {guidance: true, description: 'Pass one child. With `fit` set, every direct child is stretched to fill the box, so put an overlay or caption inside a single wrapper child rather than passing it as a second child.'},
@@ -130,7 +130,7 @@ export const docsDense = {
130
130
  description:
131
131
  'Maintains a fixed width-to-height ratio for its children as its container resizes. Use it for media containers like videos, images, thumbnails, or any content that needs consistent proportions. It takes its width from the container and derives its height from the ratio, so it needs an ancestor with a definite width.',
132
132
  bestPractices: [
133
- {guidance: true, description: 'Express the ratio as a fraction like `16/9` or `4/3` for readability.'},
133
+ {guidance: true, description: 'Express the ratio as a fraction for readability — `ratio={16 / 9}` rather than `ratio={1.78}`. It is a number, so the string form `ratio="16/9"` is a type error.'},
134
134
  {guidance: true, description: 'Use for media that needs consistent proportions across screen sizes.'},
135
135
  {guidance: true, description: 'Use `fit="cover"` for images and video so the component sizes the child; the child should not repeat `width`/`height`/`objectFit` styles.'},
136
136
  {guidance: true, description: 'Pass one child. With `fit` set, every direct child is stretched to fill the box, so put an overlay or caption inside a single wrapper child rather than passing it as a second child.'},
@@ -531,7 +531,9 @@ describe('Banner', () => {
531
531
  );
532
532
  const frame = container.firstElementChild;
533
533
  expect(frame).toHaveClass('astryx-banner-frame');
534
- expect(frame).not.toHaveClass(containerType, elevation);
534
+ // The data attributes are canonical; the released bare values remain
535
+ // same-element compatibility classes through the 0.7.0 window.
536
+ expect(frame).toHaveClass(containerType, elevation);
535
537
  expect(frame).toHaveAttribute('data-container', containerType);
536
538
  expect(frame).toHaveAttribute('data-elevation', elevation);
537
539
  expect(frame?.firstElementChild).toHaveClass('astryx-banner');
@@ -1,6 +1,28 @@
1
1
  // Copyright (c) Meta Platforms, Inc. and affiliates.
2
- /** @type {import('@astryxdesign/cli/authoring').ComponentDoc} */
2
+ /** @type {import('@astryxdesign/cli/authoring').ComponentAnatomyElement[]} */
3
+
4
+ const anatomy = [
5
+ {
6
+ name: 'Shared dialog',
7
+ required: true,
8
+ description:
9
+ 'One native dialog that owns modality, focus, dismissal, and lifecycle for the complete flow.',
10
+ },
11
+ {
12
+ name: 'Sheet panels',
13
+ required: true,
14
+ description:
15
+ 'Direct BottomSheet children; exactly one is interactive while a previous panel may remain visible and inert during a handoff.',
16
+ },
17
+ {
18
+ name: 'Scrim',
19
+ required: false,
20
+ description:
21
+ 'Native dialog backdrop shown by the default scrim-backed modal presentation.',
22
+ },
23
+ ];
3
24
 
25
+ /** @type {import('@astryxdesign/cli/authoring').ComponentDoc} */
4
26
  export const docs = {
5
27
  name: 'BottomSheetSwitcher',
6
28
  displayName: 'Bottom Sheet Switcher',
@@ -122,6 +144,17 @@ export const docs = {
122
144
  description:
123
145
  'Coordinates multiple BottomSheets as a mutually exclusive flow. One activeSheet ID selects the only interactive sheet; during a handoff, the new sheet enters above the inert previous sheet. If the new sheet is shorter, the previous sheet simultaneously moves down until their top edges align, then fades after both transforms complete. The switcher owns one shared native <dialog>: modal flows call showModal() once for one top-layer boundary and one ::backdrop across the whole flow, while no-scrim flows use a non-modal show() shell. Its ref and shared DOM props target that dialog.',
124
146
  props: [
147
+ {
148
+ name: 'ref',
149
+ type: 'Ref<HTMLDialogElement>',
150
+ description: 'Ref forwarded to the one shared native dialog.',
151
+ },
152
+ {
153
+ name: 'onCancel',
154
+ type: '(event: SyntheticEvent<HTMLDialogElement>) => void',
155
+ description:
156
+ 'Called before the switcher handles a native dialog cancel request. Calling preventDefault() keeps the controlled flow open.',
157
+ },
125
158
  {
126
159
  name: 'activeSheet',
127
160
  type: 'string | null',
@@ -151,6 +184,7 @@ export const docs = {
151
184
  },
152
185
  ],
153
186
  usage: {
187
+ anatomy,
154
188
  description:
155
189
  "Coordinates a multi-step bottom-sheet flow in one shared dialog; set activeSheet to a nested BottomSheet's sheetId to open or switch steps, and to null to close.",
156
190
  bestPractices: [
@@ -159,11 +193,21 @@ export const docs = {
159
193
  description:
160
194
  'Use when each step depends on the previous one and only one step needs attention at a time.',
161
195
  },
196
+ {
197
+ guidance: true,
198
+ description:
199
+ 'Give every child a unique sheetId and non-empty label, choose its purpose to match dismissal requirements, and follow the WAI-ARIA Dialog (Modal) pattern for scrim-backed flows: https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/.',
200
+ },
162
201
  {
163
202
  guidance: false,
164
203
  description:
165
204
  "Don't split information across sheets when people need to compare it; use a full-page layout that keeps the relevant content visible together instead.",
166
205
  },
206
+ {
207
+ guidance: false,
208
+ description:
209
+ "Don't use the switcher when multiple panels must stay interactive or visible together; activeSheet intentionally selects one interactive step.",
210
+ },
167
211
  ],
168
212
  },
169
213
  examples: [
@@ -201,6 +245,7 @@ export const docsDense = {
201
245
  description:
202
246
  'controller with one shared native dialog for mutually exclusive multi-step BottomSheets',
203
247
  usage: {
248
+ anatomy,
204
249
  description:
205
250
  "Coordinates a multi-step bottom-sheet flow in one shared dialog; set activeSheet to a nested BottomSheet's sheetId to open or switch steps, and to null to close.",
206
251
  bestPractices: [
@@ -209,11 +254,21 @@ export const docsDense = {
209
254
  description:
210
255
  'Use when each step depends on the previous one and only one step needs attention at a time.',
211
256
  },
257
+ {
258
+ guidance: true,
259
+ description:
260
+ 'Give every child a unique sheetId and non-empty label, choose its purpose to match dismissal requirements, and follow the WAI-ARIA Dialog (Modal) pattern for scrim-backed flows: https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/.',
261
+ },
212
262
  {
213
263
  guidance: false,
214
264
  description:
215
265
  "Don't split information across sheets when people need to compare it; use a full-page layout that keeps the relevant content visible together instead.",
216
266
  },
267
+ {
268
+ guidance: false,
269
+ description:
270
+ "Don't use the switcher when multiple panels must stay interactive or visible together; activeSheet intentionally selects one interactive step.",
271
+ },
217
272
  ],
218
273
  },
219
274
  };