@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
@@ -0,0 +1,225 @@
1
+ ---
2
+ schema_version: 3
3
+ template_version: 4
4
+ kind: component
5
+ id: component:Center
6
+ authority: draft
7
+ archive_reason: null
8
+ superseded_by: null
9
+ approved_by: null
10
+ approved_at: null
11
+ owners: [cixzhang]
12
+ review_triggers: [public-api, layout, theming, accessibility]
13
+ verified_by:
14
+ [
15
+ packages/core/src/Center/Center.test.tsx,
16
+ packages/core/src/theme/themingTargets.test.ts,
17
+ apps/storybook/rtl-audit/targets.json,
18
+ scripts/check-knowledge.mjs,
19
+ ]
20
+ modules: []
21
+ families: [family:layout-primitives]
22
+ design_specs: []
23
+ architecture:
24
+ [
25
+ architecture:knowledge-contracts,
26
+ architecture:public-component-api,
27
+ architecture:component-theming-surface,
28
+ architecture:container-padding,
29
+ ]
30
+ contributing: []
31
+ system_specs: [spec:AST-002, spec:AST-029]
32
+ ---
33
+
34
+ # Center component contract
35
+
36
+ ## Intent
37
+
38
+ Center arranges caller-supplied content at the horizontal center, vertical center,
39
+ or both within its own box. This draft records verified released behavior, the
40
+ objective owned-attribute repair in the accompanying audit, and the existing
41
+ vertical-writing mismatch against current family authority. It does not add public
42
+ API, defaults, responsive behavior, container-bleed participation, or product
43
+ meaning.
44
+
45
+ ## Compatibility and migration
46
+
47
+ - Released default preserved: `yes`
48
+ - Compatibility class: defect repair plus additive observational documentation;
49
+ public types, defaults, supported values, DOM element, and theme target remain
50
+ unchanged
51
+ - Controlled/uncontrolled behavior: not applicable
52
+ - Migration decision: none; the inherited vertical-writing mismatch remains
53
+ unchanged and requires a separate runtime correction with DOM, flow, RSC, and
54
+ caller-content compatibility evidence
55
+
56
+ Consumer migration instructions belong in consumer docs and release notes.
57
+
58
+ ## Ownership boundary
59
+
60
+ **Owns**
61
+
62
+ - The fixed `div` container and its flex or inline-flex display mode.
63
+ - Centering along the selected public axis or axes.
64
+ - The current box-size inputs and logical padding precedence.
65
+ - The current `center` theme target and its reflected `axis` value.
66
+
67
+ **Does not own / non-goals**
68
+
69
+ - The semantics, paint, interaction, intrinsic size, or reading order of caller
70
+ content.
71
+ - Page or structural-region layout; those remain with Layout, AppShell, and the
72
+ product callsite.
73
+ - Gap between multiple children, responsive breakpoints, overflow, or automatic
74
+ sizing on an axis.
75
+ - Container inset publication or descendant bleed behavior. Center padding remains
76
+ local under `architecture:container-padding`.
77
+ - New visual treatment, public values, or element polymorphism.
78
+
79
+ ## Public concepts
80
+
81
+ Consumer syntax remains in `Center.doc.mjs`. This table records observable concepts
82
+ rather than duplicating its prop table.
83
+
84
+ | Concept | Closed values or states | Meaning | Availability by variant/orientation/state | Default | Owner | Stability | Invalid-value behavior |
85
+ | -------------- | --------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------- | ------------------------------------------------ | ----------------------------------- | ------------------------- | ----------------------------------------------------------------------------------------------- |
86
+ | Centering axis | `both`, `horizontal`, `vertical` | Selects which physical axis or axes align caller content to the center of Center's box. | Every render. | `both` | `component:Center` | Observed released surface | Values rejected by the public type are outside this draft. |
87
+ | Display mode | flex or inline-flex | Selects a block-level or inline-level flex container without changing centering semantics. | Every axis value. | flex | `component:Center` | Observed released surface | The public boolean type admits only enabled or omitted. |
88
+ | Box size | number or CSS value string for current width/height constraints | Applies the shared `SizeValue` contract to Center's own box. | Every axis and display mode. | Intrinsic/containing-layout result when omitted. | `family:layout-primitives` | Observed released surface | Runtime-invalid CSS follows browser CSS handling; this draft adds no validation. |
89
+ | Local padding | Shared spacing step on uniform, axis, or logical edge inputs | Insets caller content inside Center without publishing container-bleed geometry. | Every axis and display mode; logical edges follow writing mode and direction. | No component padding when omitted. | `family:layout-primitives` | Observed released surface | Values rejected by `SpacingStep` are outside this draft. |
90
+ | DOM extension | Supported BaseProps inputs and ref | Supported DOM, ARIA, data, event, class, style, and StyleX inputs reach the current root; the ref reaches that `div`. | Every render. | No additional inputs. | `architecture:public-component-api` | Observed released surface | Component-owned target reflection remains authoritative when a generic data attribute collides. |
91
+
92
+ ## Behavioral and layout contract
93
+
94
+ | ID | Candidate invariant | Basis | Draft review state |
95
+ | --- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
96
+ | FR1 | The current root is one `div`. It renders as flex by default and inline-flex when the current inline flag is enabled. | Released declarations, source, docs, tests, and browser evidence | Verified released behavior; no element or display change decided |
97
+ | FR2 | `both` centers the physical horizontal and vertical axes. `horizontal` and `vertical` are physical-axis aliases under `family:layout-primitives`, independent of writing mode. The current flex implementation satisfies this in horizontal writing but swaps the one-axis results under vertical writing because it maps the names directly to flex main/cross axes. | Current family contract, source, docs, tests, and Chromium vertical-writing evidence | Pre-existing current-authority conformance gap; this audit records the BLOCK and leaves runtime remediation out because a safe fix must preserve DOM, flow, server rendering, and caller content writing mode |
98
+ | FR3 | Current width, height, maximum-width, and minimum-height inputs use `SizeValue`: numbers resolve as CSS pixels and strings pass through as CSS values. | Current family contract, public declarations, docs, source, and browser evidence | Verified released behavior; no sizing API change |
99
+ | FR4 | Padding resolves independently per logical edge: explicit logical edge, then matching axis, then uniform padding. Inline and block edges resolve from writing mode and direction. | `family:layout-primitives/FR1–FR3`, source, tests, RTL audit, and browser evidence | Settled shared behavior; audit coverage completed |
100
+ | FR5 | Center padding remains local. It does not publish the internal container-padding variables that descendants use for bleed compensation. | `family:layout-primitives/FR8` and `architecture:container-padding/INV8` | Verified released boundary; no protocol participation added |
101
+ | FR6 | The root carries the current `center` target and reflects the resolved axis. Supported consumer styling composes on that same element, while a colliding generic `data-axis` value does not replace component-owned target state. | `architecture:public-component-api/INV5–INV6`, current source, docs, target tests, and audit regression | Objective contract restoration in this audit; no target or API addition |
102
+ | FR7 | Center creates no breakpoint, automatic overflow behavior, gap between children, structural region, or content-specific semantic wrapper. | `family:layout-primitives/FR4, FR7, AV3–AV5`, source, and docs | Verified released boundary; no new capability inferred |
103
+
104
+ ### Allowed variation
105
+
106
+ - **AV1 — Caller content.** Any renderable caller content may appear inside the
107
+ current root and retains its own semantics, paint, interaction, and theme ownership.
108
+ - **AV2 — Available space.** Parent layout and current size inputs may change the
109
+ box available for centering without changing the axis contract.
110
+ - **AV3 — Consumer styling.** Supported class, style, and StyleX inputs may alter
111
+ the root through the documented escape hatches while the component-owned target
112
+ name and resolved axis reflection remain present.
113
+
114
+ ### Representative states
115
+
116
+ | State | Required invariant | Allowed variation |
117
+ | -------------------------------- | ---------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------- |
118
+ | Both axes | Physical horizontal and vertical alignment are centered in every writing mode. | Caller content and available width/height may vary. |
119
+ | Horizontal only | The physical horizontal axis is centered; Center does not add physical vertical centering. | Height and caller-owned vertical placement may vary. |
120
+ | Vertical only | The physical vertical axis is centered; Center does not add physical horizontal centering. | Width and caller-owned horizontal placement may vary. |
121
+ | Vertical writing | The physical-axis requirements above remain unchanged. Current source reverses the one-axis outcomes. | Writing mode and caller content may vary; the violation does not. |
122
+ | Inline | The root is inline-flex and retains the selected axis behavior. | Surrounding inline content and caller children may vary. |
123
+ | Asymmetric logical padding | Logical start/end values keep their meaning while resolved physical inline-axis edges swap with direction. | Spacing steps, writing mode, and block-edge padding may vary. |
124
+ | Narrow or coarse-pointer context | The passive container preserves its layout contract without adding a breakpoint or interaction mode. | Parent width and caller content may wrap or size themselves. |
125
+
126
+ ### Transformation and precedence order
127
+
128
+ - **ORD1 — Layout resolution.** Resolve display mode and preserve the public
129
+ physical-axis meaning across writing modes before applying current box-size inputs.
130
+ Current source instead maps `horizontal` to flex main-axis alignment and `vertical`
131
+ to flex cross-axis alignment, producing the FR2 conformance gap in vertical writing.
132
+ - **ORD2 — Padding resolution.** Resolve each edge from edge input to axis input to
133
+ uniform input, preserving zero as an explicit spacing step.
134
+ - **ORD3 — Root composition.** Combine the `center` target, resolved axis reflection,
135
+ component styles, and supported consumer styling on one root. Forward remaining
136
+ supported DOM inputs without allowing them to replace component-owned target state.
137
+
138
+ ### Performance and resources
139
+
140
+ - Center owns no state, Effect, listener, observer, timer, portal, measurement, or
141
+ asynchronous resource. Its current render performs only synchronous prop
142
+ resolution and one DOM render.
143
+
144
+ ## Accessibility contract
145
+
146
+ - **AR1 — Passive semantics.** Center adds no interactive role, accessible name,
147
+ state, focus behavior, keyboard handling, or live region. Caller content keeps its
148
+ own semantic and interaction ownership.
149
+ - **AR2 — Supported DOM semantics.** Supported caller ARIA, role, data, event, and
150
+ ref inputs reach the current root unless the attribute is component-owned target
151
+ reflection under FR6.
152
+ - **AR3 — Reading order.** Center renders caller children in caller order and does
153
+ not visually reorder them.
154
+
155
+ ## Design relationships
156
+
157
+ | Anatomy or state | Design requirement | Representation authority | Hierarchy role | Component contract |
158
+ | ---------------- | -------------------------------------------------------------------------------------------------------- | ---------------------------------------------------- | ----------------- | ------------------ |
159
+ | Container | Supplies one- or two-axis alignment and optional local inset without adding a visual surface by default. | Current source, docs, and `family:layout-primitives` | Supporting layout | FR1–FR7 |
160
+ | Content | Retains caller-owned semantics, paint, interaction, and hierarchy inside the container. | Caller-owned content | Context-dependent | AV1, AR1, AR3 |
161
+
162
+ This observational draft records current layout relationships. It does not decide a
163
+ new density, breakpoint, visual treatment, or content hierarchy.
164
+
165
+ ### Theming anatomy
166
+
167
+ <!-- anatomy-theming:v1 -->
168
+
169
+ ```json
170
+ {
171
+ "Container": {"target": "center"},
172
+ "Content": {
173
+ "none": {
174
+ "reason": "intentional: Caller-supplied content retains its own theming ownership; Center applies no content target."
175
+ }
176
+ }
177
+ }
178
+ ```
179
+
180
+ ## Family and system relationships
181
+
182
+ - `family:layout-primitives` owns the shared `SizeValue`, `SpacingStep`, logical
183
+ padding, padding precedence, and Center arrangement vocabulary.
184
+ - `architecture:container-padding` records that Center's current padding is local
185
+ and does not publish descendant bleed geometry.
186
+ - `architecture:component-theming-surface` owns anatomy qualification, target
187
+ placement, and axis reflection on the current target.
188
+ - `architecture:public-component-api` owns the released subpath, BaseProps
189
+ passthrough, styling composition, ref reachability, and owned-attribute boundary.
190
+ - `architecture:knowledge-contracts` and `spec:AST-029` keep this observational
191
+ draft from settling new behavior and require exact-head owner approval.
192
+ - `spec:AST-002` owns admission for any future public or behavioral delta.
193
+
194
+ ## Verification map
195
+
196
+ | Contract | Verification | Representative states | Mutation or failure expectation | Audit section |
197
+ | ------------------------- | ---------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ | ---------------------------- |
198
+ | FR1 | `Center.test.tsx`, `Center.stories.tsx`, and receipted Chromium evidence | Block and inline display | Removing a display style changes the public DOM/computed layout and fails focused or browser evidence. | `audit:Center/behavior` |
199
+ | FR2 | Receipted Chromium geometry in horizontal and `vertical-rl` writing modes | Both, horizontal, and vertical axes across writing modes | A one-axis value that centers the opposite physical dimension remains an open BLOCK until runtime behavior conforms. | `audit:Center/behavior` |
200
+ | FR3 | Public declarations, source, focused tests, and browser evidence | Numeric and string width/height constraints | Changing SizeValue lowering or dropping a size input changes the current root's computed box. | `audit:Center/api` |
201
+ | FR4 | `Center.test.tsx`, writing-mode-aware `targets.json` D8, and LTR/RTL browser receipts | Uniform, axis, per-edge, zero, horizontal writing, and vertical writing | Wrong precedence changes functional StyleX output; fixed left/right assumptions fail the vertical-writing D8 self-check. | `audit:Center/layout` |
202
+ | FR5, FR7 | Source review against family and container-padding authority | Padded and narrow containers | Publishing bleed geometry or adding implicit responsive/overflow behavior contradicts the current boundary. | `audit:Center/architecture` |
203
+ | FR6 | `Center.test.tsx` and `themingTargets.test.ts` | Every axis plus a colliding consumer data attribute | Missing target/axis metadata or consumer replacement of component-owned reflection fails focused tests. | `audit:Center/theming` |
204
+ | AR1–AR3 | Source, passthrough tests, component-scoped axe, and browser inspection | Passive content, caller ARIA/role, and multiple children | Added component semantics, dropped supported semantics, or visual reordering becomes observable. | `audit:Center/accessibility` |
205
+ | Documentation and surface | `Center.doc.mjs`, block/docsite checks, export checks, and `scripts/check-knowledge.mjs` | Consumer docs, examples, package entry points, and this draft | Missing or stale docs, exports, required structure, relationships, or anatomy mapping fails repository checks. | `audit:Center/docs` |
206
+
207
+ Current audit scores, screenshots, eligibility, and per-run receipts remain in their
208
+ existing wiki, pull-request, and trusted-check owners rather than this contract.
209
+
210
+ ## Decision log
211
+
212
+ None. Current family authority already settles the physical-axis meaning. The
213
+ vertical-writing mismatch is an implementation conformance gap, not an unresolved API
214
+ or design decision; this observational draft neither waives it nor prescribes a risky
215
+ fix.
216
+
217
+ ## Open questions
218
+
219
+ None.
220
+
221
+ ## Content boundary
222
+
223
+ This file does not duplicate consumer prop tables or examples, current audit scores or
224
+ screenshots, implementation steps, shared layout vocabulary, container-padding
225
+ mechanics, or system API/theming rules. It links to their owners.
@@ -11,6 +11,7 @@
11
11
 
12
12
  import {describe, it, expect, vi} from 'vitest';
13
13
  import {render, screen} from '@testing-library/react';
14
+ import {renderToString} from 'react-dom/server';
14
15
  import {Center} from './Center';
15
16
 
16
17
  /**
@@ -39,8 +40,11 @@ describe('Center', () => {
39
40
  const element = screen.getByTestId('center');
40
41
  expect(screen.getByText('Centered Content')).toBeInTheDocument();
41
42
  expect(element).toBeInTheDocument();
42
- // Check that it has flex display
43
- expect(element).toHaveStyle({display: 'flex'});
43
+ expect(element).toHaveStyle({
44
+ display: 'flex',
45
+ alignItems: 'center',
46
+ justifyContent: 'center',
47
+ });
44
48
  });
45
49
 
46
50
  it('centers horizontally only', () => {
@@ -51,7 +55,11 @@ describe('Center', () => {
51
55
  );
52
56
  const element = screen.getByTestId('center');
53
57
  expect(screen.getByText('Horizontal Center')).toBeInTheDocument();
54
- expect(element).toHaveStyle({display: 'flex'});
58
+ expect(element).toHaveStyle({
59
+ display: 'flex',
60
+ justifyContent: 'center',
61
+ });
62
+ expect(element).not.toHaveStyle({alignItems: 'center'});
55
63
  });
56
64
 
57
65
  it('centers vertically only', () => {
@@ -62,7 +70,11 @@ describe('Center', () => {
62
70
  );
63
71
  const element = screen.getByTestId('center');
64
72
  expect(screen.getByText('Vertical Center')).toBeInTheDocument();
65
- expect(element).toHaveStyle({display: 'flex'});
73
+ expect(element).toHaveStyle({
74
+ display: 'flex',
75
+ alignItems: 'center',
76
+ });
77
+ expect(element).not.toHaveStyle({justifyContent: 'center'});
66
78
  });
67
79
 
68
80
  it('applies height prop', () => {
@@ -214,6 +226,32 @@ describe('Center', () => {
214
226
  expect(element).toHaveAttribute('aria-label', 'centered container');
215
227
  });
216
228
 
229
+ it('does not let consumer data attributes override the reflected axis', () => {
230
+ const {container} = render(
231
+ <Center axis="horizontal" data-axis="vertical">
232
+ <div>Content</div>
233
+ </Center>,
234
+ );
235
+
236
+ expect(container.firstElementChild).toHaveAttribute(
237
+ 'data-axis',
238
+ 'horizontal',
239
+ );
240
+ });
241
+
242
+ it('renders on the server without a client boundary', () => {
243
+ const html = renderToString(
244
+ <Center axis="horizontal" paddingInlineStart={2}>
245
+ Centered content
246
+ </Center>,
247
+ );
248
+
249
+ expect(html).toContain('<div');
250
+ expect(html).toContain('astryx-center');
251
+ expect(html).toContain('data-axis="horizontal"');
252
+ expect(html).toContain('Centered content');
253
+ });
254
+
217
255
  it('renders as div element', () => {
218
256
  render(
219
257
  <Center data-testid="center">
@@ -4,10 +4,11 @@
4
4
  * @file Center.tsx
5
5
  * @input Uses React, StyleX for centering styles, Layout padding.stylex for spacing-scale padding
6
6
  * @output Exports Center component and CenterProps
7
- * @position Center component for centering children horizontally/vertically
7
+ * @position Center component for one- or two-axis flex centering
8
8
  *
9
9
  * SYNC: When modified, update these files to stay in sync:
10
10
  * - /packages/core/src/Center/Center.doc.mjs
11
+ * - /packages/core/src/Center/Center.spec.md
11
12
  * - /packages/core/src/Center/Center.test.tsx
12
13
  * - /apps/storybook/stories/Center.stories.tsx
13
14
  * - /packages/cli/assets/templates/blocks/components/Center/ (showcase blocks)
@@ -62,10 +63,13 @@ export interface CenterProps extends BaseProps<HTMLDivElement> {
62
63
  /** Ref forwarded to the root element */
63
64
  ref?: React.Ref<HTMLDivElement>;
64
65
  /**
65
- * Center axis - which direction(s) to center.
66
- * - `both`: Center both horizontally and vertically (default)
67
- * - `horizontal`: Center horizontally only (justifyContent: center)
68
- * - `vertical`: Center vertically only (alignItems: center)
66
+ * Center mode. In horizontal writing:
67
+ * - `both`: Center on the flex main and cross axes (default)
68
+ * - `horizontal`: Center on the flex main/inline axis
69
+ * - `vertical`: Center on the flex cross/block axis
70
+ *
71
+ * In vertical writing, the current single-axis behavior still follows those
72
+ * logical flex axes rather than the physical names. `both` centers both axes.
69
73
  * @default 'both'
70
74
  */
71
75
  axis?: CenterAxis;
@@ -103,39 +107,41 @@ export interface CenterProps extends BaseProps<HTMLDivElement> {
103
107
  padding?: SpacingStep;
104
108
 
105
109
  /**
106
- * Inline (horizontal) padding, using the spacing scale.
110
+ * Logical inline-axis padding, using the spacing scale.
107
111
  * Overrides `padding` on the inline axis when both are set.
108
112
  */
109
113
  paddingInline?: SpacingStep;
110
114
 
111
115
  /**
112
- * Inline-start padding, using the spacing scale. Logical: the left edge in
113
- * LTR, the right edge in RTL.
116
+ * Logical inline-start padding, using the spacing scale. The resolved
117
+ * physical edge depends on writing mode and direction.
114
118
  * Overrides `paddingInline` and `padding` on that edge only.
115
119
  */
116
120
  paddingInlineStart?: SpacingStep;
117
121
 
118
122
  /**
119
- * Inline-end padding, using the spacing scale. Logical: the right edge in
120
- * LTR, the left edge in RTL.
123
+ * Logical inline-end padding, using the spacing scale. The resolved
124
+ * physical edge depends on writing mode and direction.
121
125
  * Overrides `paddingInline` and `padding` on that edge only.
122
126
  */
123
127
  paddingInlineEnd?: SpacingStep;
124
128
 
125
129
  /**
126
- * Block (vertical) padding, using the spacing scale.
130
+ * Logical block-axis padding, using the spacing scale.
127
131
  * Overrides `padding` on the block axis when both are set.
128
132
  */
129
133
  paddingBlock?: SpacingStep;
130
134
 
131
135
  /**
132
- * Block-start (top) padding, using the spacing scale.
136
+ * Logical block-start padding, using the spacing scale. The resolved physical
137
+ * edge depends on writing mode.
133
138
  * Overrides `paddingBlock` and `padding` on that edge only.
134
139
  */
135
140
  paddingBlockStart?: SpacingStep;
136
141
 
137
142
  /**
138
- * Block-end (bottom) padding, using the spacing scale.
143
+ * Logical block-end padding, using the spacing scale. The resolved physical
144
+ * edge depends on writing mode.
139
145
  * Overrides `paddingBlock` and `padding` on that edge only.
140
146
  */
141
147
  paddingBlockEnd?: SpacingStep;
@@ -153,10 +159,11 @@ export interface CenterProps extends BaseProps<HTMLDivElement> {
153
159
  }
154
160
 
155
161
  /**
156
- * Center component for centering children horizontally and/or vertically.
162
+ * Center component for centering children on one or both flex axes.
157
163
  *
158
- * Uses flexbox for centering. By default, centers on both axes.
159
- * Use the `axis` prop to center on only one axis.
164
+ * In horizontal writing, the single-axis names match physical horizontal and
165
+ * vertical dimensions. In vertical writing, the current single-axis behavior
166
+ * follows flex main/cross axes; `both` still centers on both axes.
160
167
  *
161
168
  * @example
162
169
  * ```
@@ -222,7 +229,7 @@ export function Center({
222
229
  );
223
230
 
224
231
  return (
225
- <div ref={ref} {...stylexProps} {...props}>
232
+ <div ref={ref} {...props} {...stylexProps}>
226
233
  {children}
227
234
  </div>
228
235
  );
@@ -10,12 +10,6 @@ describe('ChatSystemMessage', () => {
10
10
  expect(screen.getByText('Conversation started')).toBeTruthy();
11
11
  });
12
12
 
13
- it('has role="status"', () => {
14
- render(<ChatSystemMessage data-testid="sys">Notice</ChatSystemMessage>);
15
- const el = screen.getByTestId('sys');
16
- expect(el.getAttribute('role')).toBe('status');
17
- });
18
-
19
13
  it('renders default variant without divider lines', () => {
20
14
  const {container} = render(<ChatSystemMessage>Hello</ChatSystemMessage>);
21
15
  // Divider lines have aria-hidden, so check there are none
@@ -57,7 +51,7 @@ describe('ChatSystemMessage', () => {
57
51
  expect(screen.getByTestId('my-sys')).toBeTruthy();
58
52
  });
59
53
 
60
- it('forwards rest props (data-*, id) while keeping its own role', () => {
54
+ it('forwards rest props (data-*, id)', () => {
61
55
  render(
62
56
  <ChatSystemMessage data-testid="sys" data-custom="x" id="sys-1">
63
57
  Hello
@@ -66,7 +60,6 @@ describe('ChatSystemMessage', () => {
66
60
  const el = screen.getByTestId('sys');
67
61
  expect(el).toHaveAttribute('data-custom', 'x');
68
62
  expect(el).toHaveAttribute('id', 'sys-1');
69
- expect(el.getAttribute('role')).toBe('status');
70
63
  });
71
64
 
72
65
  it('forwards rest props in the divider variant', () => {
@@ -77,6 +70,6 @@ describe('ChatSystemMessage', () => {
77
70
  );
78
71
  const el = screen.getByTestId('sys');
79
72
  expect(el).toHaveAttribute('data-custom', 'x');
80
- expect(el.getAttribute('role')).toBe('status');
73
+ expect(el).toHaveAttribute('role', 'status');
81
74
  });
82
75
  });
@@ -151,8 +151,8 @@ export const docs = {
151
151
  {className: 'astryx-code-block-header', visualProps: ['size', 'language', 'container']},
152
152
  {className: 'astryx-code-block-title', visualProps: ['size', 'language']},
153
153
  {className: 'astryx-code-block-copy-button'},
154
- // Still emitted beside the names above, so themes written against
155
- // them keep working. Drop in the next major.
154
+ // Retained beside the canonical names for backwards compatibility.
155
+ // New themes use the canonical targets above.
156
156
  {className: 'astryx-codeblock', visualProps: ['size', 'language', 'container'], deprecatedFor: 'code-block'},
157
157
  {className: 'astryx-codeblock-header', visualProps: ['size', 'language', 'container'], deprecatedFor: 'code-block-header'},
158
158
  {className: 'astryx-codeblock-title', visualProps: ['size', 'language'], deprecatedFor: 'code-block-title'},
@@ -930,8 +930,8 @@ export function CodeBlock({
930
930
  themeProps(
931
931
  'code-block',
932
932
  {size, language, container},
933
- // `codeblock` ran the compound name together; themes styling it keep
934
- // working until the next major.
933
+ // `codeblock` ran the compound name together; keep it emitted so
934
+ // existing themes continue to work.
935
935
  {legacyNames: ['codeblock']},
936
936
  ),
937
937
  stylex.props(
@@ -1122,8 +1122,8 @@ describe('DateInput clear icon theme target', () => {
1122
1122
  // The canonical target lands on the icon element itself (not the button),
1123
1123
  // so a theme can restyle just this glyph (color, size, hover) via
1124
1124
  // `defineTheme` — a button-level target could not reach the icon's own
1125
- // color/size. The original per-component name rides along for a
1126
- // deprecation window.
1125
+ // color/size. The original per-component name remains as a compatibility
1126
+ // alias.
1127
1127
  const icon = getClearIcon();
1128
1128
  expect(icon).toHaveClass('astryx-input-clear-icon');
1129
1129
  expect(icon).toHaveClass('astryx-date-input-clear-icon');
@@ -1149,8 +1149,8 @@ describe('DateInput clear icon theme target', () => {
1149
1149
  it('routes the clear glyph through the shared clear button, keeping the legacy target', () => {
1150
1150
  // The clear affordance now composes the shared InputClearButton (a ghost
1151
1151
  // Button with a secondary/sm glyph), so the icon carries the canonical
1152
- // `astryx-input-clear-icon` target and — for a deprecation window — the
1153
- // original `astryx-date-input-clear-icon`. Aside from those target classes
1152
+ // `astryx-input-clear-icon` target plus the supported compatibility alias
1153
+ // `astryx-date-input-clear-icon`. Aside from those target classes
1154
1154
  // it matches the shared button's own `close`/`sm`/`secondary` glyph
1155
1155
  // exactly, so the default look is defined in one place.
1156
1156
  render(
@@ -619,10 +619,21 @@ function PointerDateField({
619
619
  );
620
620
 
621
621
  // Handle clear button click
622
- const handleClear = useCallback(() => {
623
- fireChange(undefined);
624
- inputRef.current?.focus();
625
- }, [fireChange]);
622
+ const handleClear = useCallback(
623
+ (e?: React.MouseEvent<HTMLButtonElement>) => {
624
+ fireChange(undefined);
625
+ if (!e || e.detail === 0) {
626
+ inputRef.current?.focus();
627
+ } else {
628
+ // Defer focus restoration past the button's unmount task so iOS Safari
629
+ // and touch browsers don't jump the page scroll to 0 on tap.
630
+ requestAnimationFrame(() => {
631
+ inputRef.current?.focus({preventScroll: true});
632
+ });
633
+ }
634
+ },
635
+ [fireChange],
636
+ );
626
637
 
627
638
  // Handle date selection from calendar
628
639
  const handleDateSelect = useCallback(
@@ -628,8 +628,8 @@ describe('DateRangeInput icon theme targets', () => {
628
628
  // The canonical target lands on the icon element itself (not the button),
629
629
  // so a theme can restyle just this glyph (color, size, hover) via
630
630
  // defineTheme — a button-level target could not reach the icon's own
631
- // color/size. The original per-component name rides along for a
632
- // deprecation window.
631
+ // color/size. The original per-component name remains as a compatibility
632
+ // alias.
633
633
  const icon = iconIn(getButton('Clear Range'));
634
634
  expect(icon).toHaveClass('astryx-input-clear-icon');
635
635
  expect(icon).toHaveClass('astryx-date-range-input-clear-icon');
@@ -2346,10 +2346,11 @@ describe('DateTimeInput', () => {
2346
2346
  });
2347
2347
 
2348
2348
  it('leaves both leading glyphs byte-identical to a plain secondary/sm icon by default', () => {
2349
- // The targets are purely additive: the stable target class and its
2350
- // reflected state add nothing to the render until a theme targets them.
2351
- // Guard that by diffing each glyph's StyleX classes against a standalone
2352
- // secondary/sm icon, excluding only the additive target/state classes.
2349
+ // The targets are purely additive: the stable target class, reflected
2350
+ // state, and released bare-state compatibility class add nothing to the
2351
+ // render until a theme or consumer stylesheet targets them. Guard that by
2352
+ // diffing each glyph's StyleX classes against a standalone secondary/sm
2353
+ // icon, excluding only those additive theme metadata classes.
2353
2354
  const {container} = render(
2354
2355
  <DateTimeInput label="Meeting" onChange={() => {}} />,
2355
2356
  );
@@ -2368,6 +2369,7 @@ describe('DateTimeInput', () => {
2368
2369
  const themeTargetClasses = new Set([
2369
2370
  'astryx-date-time-input-toggle-icon',
2370
2371
  'astryx-date-time-input-clock-icon',
2372
+ 'collapsed',
2371
2373
  'expanded',
2372
2374
  ]);
2373
2375
  const styleClasses = (el: HTMLElement) =>
@@ -1520,13 +1520,24 @@ function PointerDateTimeField({
1520
1520
  );
1521
1521
 
1522
1522
  // --- Clear ---
1523
- const handleClear = useCallback(() => {
1524
- setNativeTimeDraft(undefined);
1525
- fireChange(undefined);
1526
- if (!usesNativePicker) {
1527
- dateInputRef.current?.focus();
1528
- }
1529
- }, [fireChange, usesNativePicker]);
1523
+ const handleClear = useCallback(
1524
+ (e?: React.MouseEvent<HTMLButtonElement>) => {
1525
+ setNativeTimeDraft(undefined);
1526
+ fireChange(undefined);
1527
+ if (!usesNativePicker) {
1528
+ if (!e || e.detail === 0) {
1529
+ dateInputRef.current?.focus();
1530
+ } else {
1531
+ // Defer focus restoration past the button's unmount task so iOS Safari
1532
+ // and touch browsers don't jump the page scroll to 0 on tap.
1533
+ requestAnimationFrame(() => {
1534
+ dateInputRef.current?.focus({preventScroll: true});
1535
+ });
1536
+ }
1537
+ }
1538
+ },
1539
+ [fireChange, usesNativePicker],
1540
+ );
1530
1541
 
1531
1542
  // Focus time input when clicking wrapper padding/icon
1532
1543
  const {onClick: handleTimeWrapperClick, onMouseUp: handleTimeWrapperMouseUp} =