@astryxdesign/core 0.5.1 → 0.5.2-canary.00477a5

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 (270) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/dist/BottomSheet/BottomSheet.d.ts +3 -1
  3. package/dist/BottomSheet/BottomSheet.d.ts.map +1 -1
  4. package/dist/BottomSheet/BottomSheet.js +4 -3
  5. package/dist/BottomSheet/BottomSheetEdgeTint.d.ts +2 -2
  6. package/dist/BottomSheet/BottomSheetEdgeTint.d.ts.map +1 -1
  7. package/dist/BottomSheet/BottomSheetEdgeTint.js +7 -10
  8. package/dist/BottomSheet/BottomSheetSwitcher.d.ts.map +1 -1
  9. package/dist/BottomSheet/BottomSheetSwitcher.js +1 -1
  10. package/dist/Button/Button.d.ts +2 -1
  11. package/dist/Button/Button.d.ts.map +1 -1
  12. package/dist/Button/Button.js +11 -4
  13. package/dist/Carousel/Carousel.d.ts.map +1 -1
  14. package/dist/Carousel/Carousel.js +2 -2
  15. package/dist/Chat/ChatToolCalls.d.ts.map +1 -1
  16. package/dist/Chat/ChatToolCalls.js +34 -17
  17. package/dist/ContextMenu/ContextMenu.d.ts +14 -3
  18. package/dist/ContextMenu/ContextMenu.d.ts.map +1 -1
  19. package/dist/ContextMenu/ContextMenu.js +149 -26
  20. package/dist/ContextMenu/index.d.ts +1 -0
  21. package/dist/ContextMenu/index.d.ts.map +1 -1
  22. package/dist/DropdownMenu/DropdownMenu.d.ts +24 -5
  23. package/dist/DropdownMenu/DropdownMenu.d.ts.map +1 -1
  24. package/dist/DropdownMenu/DropdownMenu.js +340 -26
  25. package/dist/DropdownMenu/DropdownMenuContext.d.ts +1 -1
  26. package/dist/DropdownMenu/DropdownMenuContext.d.ts.map +1 -1
  27. package/dist/DropdownMenu/DropdownMenuItem.d.ts.map +1 -1
  28. package/dist/DropdownMenu/DropdownMenuItem.js +1 -1
  29. package/dist/DropdownMenu/DropdownMenuSubMenu.d.ts +6 -2
  30. package/dist/DropdownMenu/DropdownMenuSubMenu.d.ts.map +1 -1
  31. package/dist/DropdownMenu/DropdownMenuSubMenu.js +43 -14
  32. package/dist/DropdownMenu/MenuBottomSheet.d.ts +20 -0
  33. package/dist/DropdownMenu/MenuBottomSheet.d.ts.map +1 -0
  34. package/dist/DropdownMenu/MenuBottomSheet.js +36 -0
  35. package/dist/DropdownMenu/MenuBottomSheetActionList.d.ts +10 -0
  36. package/dist/DropdownMenu/MenuBottomSheetActionList.d.ts.map +1 -0
  37. package/dist/DropdownMenu/MenuBottomSheetActionList.js +111 -0
  38. package/dist/DropdownMenu/index.d.ts +2 -1
  39. package/dist/DropdownMenu/index.d.ts.map +1 -1
  40. package/dist/DropdownMenu/menuWidth.d.ts +14 -0
  41. package/dist/DropdownMenu/menuWidth.d.ts.map +1 -0
  42. package/dist/DropdownMenu/menuWidth.js +35 -0
  43. package/dist/DropdownMenu/useMenuOverflow.d.ts +10 -0
  44. package/dist/DropdownMenu/useMenuOverflow.d.ts.map +1 -0
  45. package/dist/DropdownMenu/useMenuOverflow.js +60 -0
  46. package/dist/MoreMenu/MoreMenu.d.ts +8 -2
  47. package/dist/MoreMenu/MoreMenu.d.ts.map +1 -1
  48. package/dist/MoreMenu/MoreMenu.js +2 -0
  49. package/dist/MultiSelector/MultiSelector.d.ts +11 -1
  50. package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
  51. package/dist/MultiSelector/MultiSelector.js +97 -68
  52. package/dist/MultiSelector/index.d.ts +1 -1
  53. package/dist/MultiSelector/index.d.ts.map +1 -1
  54. package/dist/Popover/Popover.d.ts +10 -6
  55. package/dist/Popover/Popover.d.ts.map +1 -1
  56. package/dist/Popover/Popover.js +141 -16
  57. package/dist/Popover/usePopover.d.ts +4 -0
  58. package/dist/Popover/usePopover.d.ts.map +1 -1
  59. package/dist/Popover/usePopover.js +52 -5
  60. package/dist/Selector/Selector.d.ts +10 -0
  61. package/dist/Selector/Selector.d.ts.map +1 -1
  62. package/dist/Selector/Selector.js +106 -80
  63. package/dist/Selector/SelectorBottomSheet.d.ts +21 -0
  64. package/dist/Selector/SelectorBottomSheet.d.ts.map +1 -0
  65. package/dist/Selector/SelectorBottomSheet.js +83 -0
  66. package/dist/Selector/index.d.ts +1 -1
  67. package/dist/Selector/index.d.ts.map +1 -1
  68. package/dist/Selector/selectorPresentation.stylex.d.ts +16 -0
  69. package/dist/Selector/selectorPresentation.stylex.d.ts.map +1 -0
  70. package/dist/Selector/selectorPresentation.stylex.js +21 -0
  71. package/dist/Selector/useSelectorPresentation.d.ts +32 -0
  72. package/dist/Selector/useSelectorPresentation.d.ts.map +1 -0
  73. package/dist/Selector/useSelectorPresentation.js +90 -0
  74. package/dist/Toast/useToastGesture.d.ts.map +1 -1
  75. package/dist/Toast/useToastGesture.js +46 -9
  76. package/dist/astryx.css +19 -1
  77. package/dist/hooks/useAdaptivePresentation.d.ts +5 -0
  78. package/dist/hooks/useAdaptivePresentation.d.ts.map +1 -0
  79. package/dist/hooks/useAdaptivePresentation.js +19 -0
  80. package/dist/hooks/useFocusReturnVisibility.d.ts +7 -0
  81. package/dist/hooks/useFocusReturnVisibility.d.ts.map +1 -0
  82. package/dist/hooks/useFocusReturnVisibility.js +35 -0
  83. package/dist/hooks/useFocusTrap.d.ts.map +1 -1
  84. package/dist/hooks/useFocusTrap.js +12 -0
  85. package/dist/i18n/InternationalizationContext.d.ts +2 -0
  86. package/dist/i18n/InternationalizationContext.d.ts.map +1 -1
  87. package/dist/i18n/InternationalizationContext.js +3 -1
  88. package/dist/i18n/InternationalizationProvider.d.ts.map +1 -1
  89. package/dist/i18n/InternationalizationProvider.js +3 -1
  90. package/dist/i18n/resolve.d.ts +1 -1
  91. package/dist/i18n/resolve.d.ts.map +1 -1
  92. package/dist/i18n/resolve.js +37 -38
  93. package/dist/i18n/useTranslator.d.ts.map +1 -1
  94. package/dist/i18n/useTranslator.js +2 -3
  95. package/locales/af-ZA.json +0 -4
  96. package/locales/ar-SA.json +0 -4
  97. package/locales/ca-ES.json +0 -4
  98. package/locales/cs-CZ.json +0 -4
  99. package/locales/da-DK.json +0 -4
  100. package/locales/de-DE.json +0 -4
  101. package/locales/el-GR.json +0 -4
  102. package/locales/en.json +20 -0
  103. package/locales/es-ES.json +0 -4
  104. package/locales/fi-FI.json +0 -4
  105. package/locales/fr-FR.json +0 -4
  106. package/locales/he-IL.json +0 -4
  107. package/locales/hu-HU.json +0 -4
  108. package/locales/it-IT.json +0 -4
  109. package/locales/ja-JP.json +0 -4
  110. package/locales/ko-KR.json +0 -4
  111. package/locales/nl-NL.json +0 -4
  112. package/locales/no-NO.json +0 -4
  113. package/locales/pl-PL.json +0 -4
  114. package/locales/pseudo.json +15 -0
  115. package/locales/pt-BR.json +0 -4
  116. package/locales/pt-PT.json +0 -4
  117. package/locales/ro-RO.json +0 -4
  118. package/locales/ru-RU.json +0 -4
  119. package/locales/sr-SP.json +0 -4
  120. package/locales/sv-SE.json +0 -4
  121. package/locales/tr-TR.json +0 -4
  122. package/locales/uk-UA.json +0 -4
  123. package/locales/vi-VN.json +0 -4
  124. package/locales/zh-CN.json +0 -4
  125. package/locales/zh-TW.json +0 -4
  126. package/package.json +3 -3
  127. package/src/BottomSheet/BottomSheet.doc.mjs +37 -0
  128. package/src/BottomSheet/BottomSheet.spec.md +177 -0
  129. package/src/BottomSheet/BottomSheet.test.tsx +29 -0
  130. package/src/BottomSheet/BottomSheet.tsx +7 -4
  131. package/src/BottomSheet/BottomSheetEdgeTint.test.tsx +4 -6
  132. package/src/BottomSheet/BottomSheetEdgeTint.tsx +7 -10
  133. package/src/BottomSheet/BottomSheetSwitcher.tsx +1 -2
  134. package/src/Button/Button.doc.mjs +56 -0
  135. package/src/Button/Button.test.tsx +10 -0
  136. package/src/Button/Button.tsx +13 -5
  137. package/src/ButtonGroup/ButtonGroup.doc.mjs +47 -0
  138. package/src/Calendar/Calendar.doc.mjs +1 -1
  139. package/src/Carousel/Carousel.test.tsx +83 -0
  140. package/src/Carousel/Carousel.tsx +8 -2
  141. package/src/Chat/ChatToolCalls.test.tsx +44 -0
  142. package/src/Chat/ChatToolCalls.tsx +36 -15
  143. package/src/CheckboxInput/CheckboxInput.doc.mjs +1 -1
  144. package/src/CheckboxList/CheckboxList.doc.mjs +173 -32
  145. package/src/CheckboxList/CheckboxList.spec.md +214 -0
  146. package/src/CommandPalette/CommandPalette.doc.mjs +72 -0
  147. package/src/CommandPalette/CommandPalette.spec.md +223 -0
  148. package/src/ComplexSelector/ComplexSelector.doc.mjs +44 -2
  149. package/src/ComplexSelector/ComplexSelector.spec.md +189 -0
  150. package/src/ContextMenu/ContextMenu.doc.mjs +13 -1
  151. package/src/ContextMenu/ContextMenu.test.tsx +131 -1
  152. package/src/ContextMenu/ContextMenu.tsx +199 -32
  153. package/src/ContextMenu/index.ts +1 -0
  154. package/src/DateInput/DateInput.doc.mjs +1 -1
  155. package/src/DateRangeInput/DateRangeInput.doc.mjs +1 -1
  156. package/src/DateTimeInput/DateTimeInput.doc.mjs +1 -1
  157. package/src/Divider/Divider.doc.mjs +24 -0
  158. package/src/Divider/Divider.spec.md +150 -0
  159. package/src/DropdownMenu/DropdownMenu.doc.mjs +201 -40
  160. package/src/DropdownMenu/DropdownMenu.test.tsx +518 -7
  161. package/src/DropdownMenu/DropdownMenu.tsx +459 -28
  162. package/src/DropdownMenu/DropdownMenuContext.tsx +1 -1
  163. package/src/DropdownMenu/DropdownMenuItem.tsx +2 -0
  164. package/src/DropdownMenu/DropdownMenuSubMenu.doc.mjs +4 -2
  165. package/src/DropdownMenu/DropdownMenuSubMenu.test.tsx +124 -0
  166. package/src/DropdownMenu/DropdownMenuSubMenu.tsx +72 -17
  167. package/src/DropdownMenu/MenuBottomSheet.tsx +46 -0
  168. package/src/DropdownMenu/MenuBottomSheetActionList.tsx +141 -0
  169. package/src/DropdownMenu/index.ts +2 -0
  170. package/src/DropdownMenu/menuWidth.ts +51 -0
  171. package/src/DropdownMenu/useMenuOverflow.ts +77 -0
  172. package/src/Field/Field.doc.mjs +1 -1
  173. package/src/FieldStatus/FieldStatus.doc.mjs +22 -1
  174. package/src/FieldStatus/FieldStatus.spec.md +212 -0
  175. package/src/FileInput/FileInput.doc.mjs +1 -1
  176. package/src/Grid/Grid.doc.mjs +19 -0
  177. package/src/Grid/Grid.spec.md +161 -0
  178. package/src/Icon/Icon.doc.mjs +196 -48
  179. package/src/Icon/Icon.spec.md +191 -0
  180. package/src/IconButton/IconButton.doc.mjs +38 -0
  181. package/src/Indicator/Indicator.doc.mjs +1 -1
  182. package/src/InputGroup/InputGroup.doc.mjs +1 -1
  183. package/src/Kbd/Kbd.doc.mjs +18 -0
  184. package/src/Kbd/Kbd.spec.md +141 -0
  185. package/src/Layout/Layout.doc.mjs +32 -0
  186. package/src/Layout/Layout.spec.md +199 -0
  187. package/src/Lightbox/Lightbox.doc.mjs +149 -33
  188. package/src/Lightbox/Lightbox.spec.md +197 -0
  189. package/src/Markdown/Markdown.doc.mjs +60 -0
  190. package/src/Markdown/Markdown.spec.md +200 -0
  191. package/src/MobileNav/MobileNav.doc.mjs +140 -32
  192. package/src/MobileNav/MobileNav.spec.md +208 -0
  193. package/src/MoreMenu/MoreMenu.doc.mjs +17 -0
  194. package/src/MoreMenu/MoreMenu.test.tsx +47 -0
  195. package/src/MoreMenu/MoreMenu.tsx +13 -1
  196. package/src/MultiSelector/MultiSelector.doc.mjs +129 -1
  197. package/src/MultiSelector/MultiSelector.spec.md +266 -0
  198. package/src/MultiSelector/MultiSelector.test.tsx +127 -0
  199. package/src/MultiSelector/MultiSelector.tsx +121 -73
  200. package/src/MultiSelector/index.ts +1 -0
  201. package/src/NavIcon/NavIcon.doc.mjs +17 -0
  202. package/src/NavIcon/NavIcon.spec.md +133 -0
  203. package/src/NavMenu/NavHeadingMenu.spec.md +205 -0
  204. package/src/NavMenu/NavMenu.doc.mjs +37 -0
  205. package/src/NumberInput/NumberInput.doc.mjs +1 -1
  206. package/src/Outline/Outline.doc.mjs +35 -0
  207. package/src/Outline/Outline.spec.md +161 -0
  208. package/src/OverflowList/OverflowList.doc.mjs +24 -0
  209. package/src/OverflowList/OverflowList.spec.md +156 -0
  210. package/src/Pagination/Pagination.doc.mjs +74 -0
  211. package/src/Pagination/Pagination.spec.md +225 -0
  212. package/src/Popover/Popover.doc.mjs +11 -11
  213. package/src/Popover/Popover.test.tsx +397 -2
  214. package/src/Popover/Popover.tsx +228 -21
  215. package/src/Popover/usePopover.tsx +92 -15
  216. package/src/PowerSearch/PowerSearch.doc.mjs +1 -1
  217. package/src/ProgressBar/ProgressBar.doc.mjs +198 -38
  218. package/src/ProgressBar/ProgressBar.spec.md +260 -0
  219. package/src/RadioList/RadioList.doc.mjs +1 -1
  220. package/src/Resizable/Resizable.doc.mjs +59 -16
  221. package/src/Resizable/Resizable.spec.md +194 -0
  222. package/src/Section/Section.doc.mjs +17 -0
  223. package/src/Section/Section.spec.md +138 -0
  224. package/src/SegmentedControl/SegmentedControl.doc.mjs +83 -0
  225. package/src/SegmentedControl/SegmentedControl.spec.md +151 -0
  226. package/src/Selector/Selector.doc.mjs +13 -1
  227. package/src/Selector/Selector.test.tsx +135 -0
  228. package/src/Selector/Selector.tsx +141 -82
  229. package/src/Selector/SelectorBottomSheet.tsx +97 -0
  230. package/src/Selector/index.ts +1 -0
  231. package/src/Selector/selectorPresentation.stylex.ts +20 -0
  232. package/src/Selector/useSelectorPresentation.ts +147 -0
  233. package/src/Skeleton/Skeleton.doc.mjs +12 -0
  234. package/src/Skeleton/Skeleton.spec.md +126 -0
  235. package/src/Slider/Slider.doc.mjs +186 -32
  236. package/src/Slider/Slider.spec.md +223 -0
  237. package/src/Stack/Stack.doc.mjs +22 -0
  238. package/src/Stack/Stack.spec.md +154 -0
  239. package/src/StatusDot/StatusDot.doc.mjs +17 -0
  240. package/src/StatusDot/StatusDot.spec.md +137 -0
  241. package/src/Switch/Switch.doc.mjs +41 -1
  242. package/src/Switch/Switch.spec.md +174 -0
  243. package/src/Text/Text.doc.mjs +190 -43
  244. package/src/Text/Text.spec.md +205 -0
  245. package/src/TextArea/TextArea.doc.mjs +256 -46
  246. package/src/TextArea/TextArea.spec.md +204 -0
  247. package/src/TextInput/TextInput.doc.mjs +1 -1
  248. package/src/TimeInput/TimeInput.doc.mjs +1 -1
  249. package/src/Toast/ToastViewport.test.tsx +118 -4
  250. package/src/Toast/useToastGesture.ts +61 -8
  251. package/src/ToggleButton/ToggleButton.doc.mjs +56 -0
  252. package/src/Tokenizer/Tokenizer.doc.mjs +1 -1
  253. package/src/Toolbar/Toolbar.doc.mjs +19 -0
  254. package/src/Toolbar/Toolbar.spec.md +173 -0
  255. package/src/Tooltip/Tooltip.doc.mjs +17 -0
  256. package/src/Tooltip/Tooltip.spec.md +141 -0
  257. package/src/TreeList/TreeList.doc.mjs +58 -0
  258. package/src/TreeList/TreeList.spec.md +220 -0
  259. package/src/Typeahead/Typeahead.doc.mjs +84 -2
  260. package/src/Typeahead/Typeahead.spec.md +250 -0
  261. package/src/hooks/useAdaptivePresentation.ts +35 -0
  262. package/src/hooks/useFocusReturnVisibility.ts +45 -0
  263. package/src/hooks/useFocusTrap.test.tsx +40 -0
  264. package/src/hooks/useFocusTrap.ts +13 -0
  265. package/src/i18n/InternationalizationContext.ts +3 -0
  266. package/src/i18n/InternationalizationProvider.tsx +8 -1
  267. package/src/i18n/__tests__/resolve.test.ts +23 -1
  268. package/src/i18n/resolve.ts +46 -49
  269. package/src/i18n/useTranslator.ts +2 -7
  270. package/src/theme/themingTargets.test.ts +91 -25
@@ -0,0 +1,204 @@
1
+ ---
2
+ schema_version: 1
3
+ template_version: 3
4
+ kind: component
5
+ id: component:TextArea
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: [theming]
13
+ verified_by:
14
+ [
15
+ packages/core/src/TextArea/TextArea.test.tsx,
16
+ packages/core/src/theme/themingTargets.test.ts,
17
+ scripts/check-knowledge.mjs,
18
+ ]
19
+ families: []
20
+ design_specs: []
21
+ architecture: [architecture:component-theming-surface]
22
+ contributing: []
23
+ system_specs: []
24
+ ---
25
+
26
+ # TextArea component contract
27
+
28
+ ## Intent
29
+
30
+ TextArea presents a labeled multi-line text field. This draft records its current
31
+ consumer anatomy and the theming ownership of parts rendered by TextArea, Field,
32
+ FieldStatus, and Spinner. It changes no runtime behavior or public API.
33
+
34
+ ## Compatibility and migration
35
+
36
+ - Released default preserved: `yes`
37
+ - Compatibility class: additive documentation only; runtime, DOM, styling,
38
+ targets, aliases, and public API remain unchanged
39
+ - Controlled/uncontrolled behavior: unchanged; TextArea remains controlled
40
+ - Migration decision: none
41
+
42
+ Consumer migration instructions belong in consumer docs and release notes.
43
+
44
+ ## Ownership boundary
45
+
46
+ **Owns**
47
+
48
+ - The painted input container, native text area, and character counter represented
49
+ by the existing `text-area`, `text-area-control`, and `text-area-counter`
50
+ targets.
51
+
52
+ **Does not own / non-goals**
53
+
54
+ - Label and attached/detached validation-message presentation — owned by
55
+ `component:Field` and `component:FieldStatus`.
56
+ - The tooltip-variant status surface — owned by `component:Tooltip`.
57
+ - Standard start-icon presentation and the shared on-field status icon — owned
58
+ by `component:Icon` and `component:Field`, respectively.
59
+ - Loading-indicator presentation — owned by `component:Spinner`.
60
+ - Caller-provided custom start content outside Icon's supported source forms.
61
+ - The deprecated `textarea` alias as separate anatomy.
62
+
63
+ ## Public concepts
64
+
65
+ No new public concept is introduced. Consumer props, states, and usage remain
66
+ documented in `TextArea.doc.mjs`.
67
+
68
+ ## Behavioral and layout contract
69
+
70
+ | ID | Candidate invariant | Basis | Draft review state |
71
+ | --- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------- | -------------------------------------------------- |
72
+ | FR1 | The current render places the native text area inside its painted input container. | Current source, docs, and focused tests | Verified current behavior; no new behavior decided |
73
+ | FR2 | The input container, native control, and conditional character counter carry the three current local targets. | Current source and public docs | Verified current behavior; no target change |
74
+ | FR3 | Label, standard start icon, loading indicator, status icon, and status surfaces continue to use their shared Field, Icon, Spinner, FieldStatus, and Tooltip owners. | Current source | Verified conditional composition boundary |
75
+ | FR4 | Placeholder text remains part of the native control; the over-limit glyph remains part of the character counter. | Current source, docs, and focused tests | Verified current grouping |
76
+
77
+ ### Allowed variation
78
+
79
+ - Value, placeholder, row count, size, status, disabled/read-only state, and
80
+ optional slots remain current capabilities rather than separate target names.
81
+ - Start-icon content may be an Icon-supported value or caller-supplied ReactNode.
82
+
83
+ ### Representative states
84
+
85
+ | State | Required invariant | Allowed variation |
86
+ | -------------------- | ---------------------------------------------------------------------- | ----------------------------------- |
87
+ | Default editable | Label, input container, and native text area render. | Value, placeholder, rows, and size |
88
+ | With character limit | Character counter renders inside the input container. | Count and over-limit state |
89
+ | Busy or status | Shared Spinner or status presentation renders in its current location. | Status variant and message presence |
90
+
91
+ ### Transformation and precedence order
92
+
93
+ - No new value, layout, status, or styling precedence rule is introduced.
94
+
95
+ ### Performance and resources
96
+
97
+ - No new performance or resource rule is introduced.
98
+
99
+ ## Accessibility contract
100
+
101
+ This draft does not change or extend TextArea's existing label, description,
102
+ status, counter, busy state, disabled/read-only state, or announcement behavior.
103
+
104
+ ## Design relationships
105
+
106
+ | Anatomy or state | Design requirement | Representation authority | Hierarchy role | Component contract |
107
+ | --------------------- | -------------------------------------------------------------------- | ------------------------------- | -------------- | ------------------ |
108
+ | Input container | Presents the current painted field boundary. | Current source and public docs | Supporting | FR1, FR2 |
109
+ | Text area | Presents and edits the current multi-line value. | Current source and public docs | Prominent | FR1, FR2 |
110
+ | Character counter | Presents current and maximum character counts. | Current source and public docs | Supporting | FR2, FR4 |
111
+ | Shared field feedback | Presents current label, standard icons, status, and loading content. | Current shared-component source | Supporting | FR3 |
112
+
113
+ ### Theming anatomy
114
+
115
+ <!-- anatomy-theming:v1 -->
116
+
117
+ ```json
118
+ {
119
+ "Label": {
120
+ "delegatesTo": {"owner": "component:Field", "target": "field-label"}
121
+ },
122
+ "Description": {
123
+ "none": {
124
+ "reason": "unsettled: No current public target reaches the stable Description; future exposure still needs an owner decision"
125
+ }
126
+ },
127
+ "Input container": {"target": "text-area"},
128
+ "Text area": {"target": "text-area-control"},
129
+ "Placeholder": {"inherits": "text-area-control"},
130
+ "Start icon": {
131
+ "delegatesTo": {"owner": "component:Icon", "target": "icon"}
132
+ },
133
+ "Custom start content": {
134
+ "none": {
135
+ "reason": "intentional: Custom start content is caller-provided ReactNode content outside TextArea's public theming ownership"
136
+ }
137
+ },
138
+ "Spinner": {
139
+ "delegatesTo": {"owner": "component:Spinner", "target": "spinner"}
140
+ },
141
+ "Status icon": {
142
+ "delegatesTo": {
143
+ "owner": "component:Field",
144
+ "target": "input-status-icon"
145
+ }
146
+ },
147
+ "Character counter": {"target": "text-area-counter"},
148
+ "Field status message": {
149
+ "delegatesTo": {
150
+ "owner": "component:FieldStatus",
151
+ "target": "field-status"
152
+ }
153
+ },
154
+ "Tooltip status message": {
155
+ "delegatesTo": {"owner": "component:Tooltip", "target": "tooltip"}
156
+ }
157
+ }
158
+ ```
159
+
160
+ `Description` remains stable consumer anatomy, but no current public target
161
+ reaches it; future exposure remains unsettled. Semantic names and icon component
162
+ values render through Icon and delegate to its `icon` target; arbitrary ReactNode
163
+ start content is caller-provided and intentionally stays outside TextArea's
164
+ public theming ownership. Attached and detached status messages delegate to
165
+ FieldStatus, while the tooltip variant delegates its rendered surface to Tooltip.
166
+ The deprecated `textarea` alias is compatibility evidence only and does not
167
+ appear in the map.
168
+
169
+ ## Family and system relationships
170
+
171
+ - `architecture:component-theming-surface` owns anatomy qualification, factual
172
+ `none` dispositions, composition-preserving ownership, and alias exclusion.
173
+ - Field, FieldStatus, Icon, Spinner, and Tooltip retain their existing public
174
+ target contracts when composed by TextArea.
175
+
176
+ ## Verification map
177
+
178
+ | Contract | Verification | Representative states | Mutation or failure expectation | Audit section |
179
+ | ------------------- | --------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ |
180
+ | FR1, FR4 | `TextArea.test.tsx` structure and counter suites | Default, placeholder, counter, over limit | Removing or regrouping stable parts breaks existing role, content, or DOM assertions. | `audit:TextArea/anatomy` |
181
+ | FR2 | `TextArea.test.tsx` root-target suite and `themingTargets.test.ts` | Current and deprecated root names; current targets | Removing root compatibility classes fails focused coverage; source/docs drift fails the target guard. | `audit:TextArea/theming` |
182
+ | FR3 | `TextArea.test.tsx`, `useInputStatusIcon.test.tsx`, and source inspection of `renderIconSlot` | Standard/custom start content; Spinner; attached/detached/tooltip status | Removing or rerouting composed content breaks existing icon, spinner, tooltip, message, or association assertions. | `audit:TextArea/anatomy` |
183
+ | Theming anatomy map | `scripts/check-knowledge.mjs` | Canonical anatomy and current local targets | Canonical-key drift, invalid dispositions or target spelling, alias-backed local claims, or an unclaimed current local target fails validation. | `audit:TextArea/theming` |
184
+
185
+ The focused TextArea suite pins the current and deprecated root classes, but does
186
+ not separately assert the exact `text-area-control` or `text-area-counter` class
187
+ placement. The source/metadata target guard covers those local declarations. No
188
+ current repository check resolves `delegatesTo` owner/target pairs; the pairs in
189
+ this draft were verified manually, so semantic delegation drift remains a
190
+ validation gap.
191
+
192
+ ## Decision log
193
+
194
+ None. This draft records current facts and introduces no component-local design
195
+ or API decision.
196
+
197
+ ## Open questions
198
+
199
+ None.
200
+
201
+ ## Content boundary
202
+
203
+ This file does not duplicate consumer prop tables, examples, implementation
204
+ steps, or shared-component contracts. It links to their owners.
@@ -5,7 +5,7 @@
5
5
  export const docs = {
6
6
  name: 'TextInput',
7
7
  displayName: 'Text Input',
8
- category: 'Data Input',
8
+ category: 'Form Controls',
9
9
  keywords: ["textinput","textfield","input","search","clearable","prefix","suffix","adornment","validation"],
10
10
  props: [
11
11
  {
@@ -5,7 +5,7 @@
5
5
  export const docs = {
6
6
  name: 'TimeInput',
7
7
  displayName: 'Time Input',
8
- category: 'Data Input',
8
+ category: 'Form Controls',
9
9
  keywords: [
10
10
  'timeinput',
11
11
  'timepicker',
@@ -793,6 +793,11 @@ describe('Toast swipe dismissal', () => {
793
793
  expect(visualToast.style.getPropertyValue('--_toast-swipe-exit-y')).toBe(
794
794
  '120%',
795
795
  );
796
+ expect(visualToast.style.getPropertyValue('--_toast-swipe-y')).toBe('');
797
+ expect(visualToast.style.getPropertyValue('--_toast-swipe-opacity')).toBe(
798
+ '',
799
+ );
800
+ expect(visualToast.style.getPropertyValue('--_toast-swipe-scale')).toBe('');
796
801
  expect(getComputedStyle(visualToast).transform).not.toContain('translateX');
797
802
  });
798
803
 
@@ -1147,11 +1152,9 @@ describe('Toast swipe dismissal', () => {
1147
1152
 
1148
1153
  expect(onHide).toHaveBeenCalledWith('manual');
1149
1154
  expect(visualToast.style.getPropertyValue('--_toast-swipe-opacity')).toBe(
1150
- '0.700',
1151
- );
1152
- expect(visualToast.style.getPropertyValue('--_toast-swipe-scale')).toBe(
1153
- '0.985',
1155
+ '',
1154
1156
  );
1157
+ expect(visualToast.style.getPropertyValue('--_toast-swipe-scale')).toBe('');
1155
1158
  expect(visualToast.style.getPropertyValue('--_toast-swipe-exit-y')).toBe(
1156
1159
  'calc(-1 * 120%)',
1157
1160
  );
@@ -1248,6 +1251,117 @@ describe('Toast swipe dismissal', () => {
1248
1251
  );
1249
1252
  });
1250
1253
 
1254
+ it('abandons a one-finger swipe when a second touch starts', () => {
1255
+ const {visualToast, onHide} = renderSwipeToast();
1256
+ const dispatchTouch = (
1257
+ type: 'touchstart' | 'touchmove',
1258
+ touches: {identifier: number; clientX: number; clientY: number}[],
1259
+ changedTouches = touches,
1260
+ ) => {
1261
+ const event = new Event(type, {bubbles: true, cancelable: true});
1262
+ Object.defineProperty(event, 'touches', {value: touches});
1263
+ Object.defineProperty(event, 'changedTouches', {value: changedTouches});
1264
+ visualToast.dispatchEvent(event);
1265
+ return event;
1266
+ };
1267
+ const first = {identifier: 51, clientX: 10, clientY: 0};
1268
+ const second = {identifier: 52, clientX: 30, clientY: 40};
1269
+
1270
+ act(() => {
1271
+ dispatchTouch('touchstart', [first]);
1272
+ dispatchTouch('touchmove', [{...first, clientY: 40}]);
1273
+ });
1274
+ expect(visualToast.style.getPropertyValue('--_toast-swipe-y')).toBe('40px');
1275
+
1276
+ act(() => {
1277
+ dispatchTouch('touchstart', [first, second], [second]);
1278
+ });
1279
+ const multitouchMove = dispatchTouch('touchmove', [first, second]);
1280
+
1281
+ expect(multitouchMove.defaultPrevented).toBe(false);
1282
+ expect(visualToast.style.getPropertyValue('--_toast-swipe-y')).toBe('');
1283
+ expect(visualToast.style.getPropertyValue('--_toast-swipe-opacity')).toBe(
1284
+ '',
1285
+ );
1286
+ expect(onHide).not.toHaveBeenCalled();
1287
+ });
1288
+
1289
+ it('does not dismiss when another touch remains outside the Toast', () => {
1290
+ const {visualToast, onHide} = renderSwipeToast();
1291
+ const first = {identifier: 61, clientX: 10, clientY: 0};
1292
+ const second = {identifier: 62, clientX: 200, clientY: 200};
1293
+ const dispatchTouch = (
1294
+ type: 'touchstart' | 'touchmove' | 'touchend',
1295
+ touches: (typeof first)[],
1296
+ changedTouches: (typeof first)[],
1297
+ ) => {
1298
+ const event = new Event(type, {bubbles: true, cancelable: true});
1299
+ Object.defineProperty(event, 'touches', {value: touches});
1300
+ Object.defineProperty(event, 'changedTouches', {value: changedTouches});
1301
+ visualToast.dispatchEvent(event);
1302
+ return event;
1303
+ };
1304
+
1305
+ act(() => {
1306
+ dispatchTouch('touchstart', [first], [first]);
1307
+ dispatchTouch(
1308
+ 'touchmove',
1309
+ [{...first, clientY: 60}],
1310
+ [{...first, clientY: 60}],
1311
+ );
1312
+ });
1313
+ expect(visualToast.style.getPropertyValue('--_toast-swipe-y')).toBe('60px');
1314
+
1315
+ // The second contact began outside the Toast, so its touchstart did not
1316
+ // reach the Toast listener. Ending the tracked finger must still cancel
1317
+ // while that other contact remains active.
1318
+ act(() => {
1319
+ dispatchTouch('touchend', [second], [{...first, clientY: 60}]);
1320
+ });
1321
+
1322
+ expect(onHide).not.toHaveBeenCalled();
1323
+ expect(visualToast.style.getPropertyValue('--_toast-swipe-y')).toBe('');
1324
+ expect(visualToast.style.getPropertyValue('--_toast-swipe-opacity')).toBe(
1325
+ '',
1326
+ );
1327
+ });
1328
+
1329
+ it('does not let a pen event with the same numeric ID control a touch gesture', () => {
1330
+ const {visualToast, onHide} = renderSwipeToast();
1331
+ const touch = {identifier: 7, clientX: 10, clientY: 0};
1332
+ const touchStart = new Event('touchstart', {
1333
+ bubbles: true,
1334
+ cancelable: true,
1335
+ });
1336
+ Object.defineProperty(touchStart, 'touches', {value: [touch]});
1337
+ Object.defineProperty(touchStart, 'changedTouches', {value: [touch]});
1338
+
1339
+ act(() => {
1340
+ visualToast.dispatchEvent(touchStart);
1341
+ fireEvent.pointerMove(visualToast, {
1342
+ pointerId: 7,
1343
+ pointerType: 'pen',
1344
+ clientX: 10,
1345
+ clientY: 60,
1346
+ });
1347
+ fireEvent.pointerUp(visualToast, {
1348
+ pointerId: 7,
1349
+ pointerType: 'pen',
1350
+ clientX: 10,
1351
+ clientY: 60,
1352
+ });
1353
+ });
1354
+
1355
+ expect(onHide).not.toHaveBeenCalled();
1356
+ expect(visualToast.style.getPropertyValue('--_toast-swipe-y')).toBe('');
1357
+
1358
+ act(() => {
1359
+ visualToast.dispatchEvent(
1360
+ new Event('touchcancel', {bubbles: true, cancelable: true}),
1361
+ );
1362
+ });
1363
+ });
1364
+
1251
1365
  it('removes native touch listeners when a Toast unmounts', () => {
1252
1366
  const {visualToast, unmount} = renderSwipeToast();
1253
1367
  const removeListener = vi.spyOn(visualToast, 'removeEventListener');
@@ -25,12 +25,14 @@ const SWIPE_ACTIVE_SCALE_MAX = 0.02;
25
25
  export type ToastGestureDirection = 1 | -1;
26
26
 
27
27
  interface GesturePoint {
28
+ source: 'touch' | 'pen';
28
29
  pointerId: number;
29
30
  clientX: number;
30
31
  clientY: number;
31
32
  }
32
33
 
33
34
  interface GestureState {
35
+ source: GesturePoint['source'];
34
36
  pointerId: number;
35
37
  startX: number;
36
38
  startY: number;
@@ -126,6 +128,7 @@ export function useToastGesture({
126
128
  }
127
129
  const surfaceSize = Math.max(root.getBoundingClientRect().height, 1);
128
130
  gestureRef.current = {
131
+ source: point.source,
129
132
  pointerId: point.pointerId,
130
133
  startX: point.clientX,
131
134
  startY: point.clientY,
@@ -159,7 +162,12 @@ export function useToastGesture({
159
162
  ) => {
160
163
  const state = gestureRef.current;
161
164
  const root = rootRef.current;
162
- if (!state || !root || point.pointerId !== state.pointerId) {
165
+ if (
166
+ !state ||
167
+ !root ||
168
+ point.source !== state.source ||
169
+ point.pointerId !== state.pointerId
170
+ ) {
163
171
  return;
164
172
  }
165
173
  const deltaX = point.clientX - state.startX;
@@ -209,7 +217,12 @@ export function useToastGesture({
209
217
  (point: GesturePoint) => {
210
218
  const state = gestureRef.current;
211
219
  const root = rootRef.current;
212
- if (!state || !root || point.pointerId !== state.pointerId) {
220
+ if (
221
+ !state ||
222
+ !root ||
223
+ point.source !== state.source ||
224
+ point.pointerId !== state.pointerId
225
+ ) {
213
226
  return;
214
227
  }
215
228
  const travel = Math.max(
@@ -225,6 +238,10 @@ export function useToastGesture({
225
238
  gestureRef.current = null;
226
239
  root.style.removeProperty('transition-duration');
227
240
  if (isDismissed) {
241
+ // The exit animation owns the final translation. Clear the live-drag
242
+ // transform/fade/scale first so a standalone Toast does not remain
243
+ // partially translated or faded after its dismiss callback fires.
244
+ clearTransientStyles(root);
228
245
  root.style.setProperty(
229
246
  '--_toast-swipe-exit-y',
230
247
  state.direction === 1
@@ -247,7 +264,15 @@ export function useToastGesture({
247
264
  if (
248
265
  event.pointerType === 'pen' &&
249
266
  (event.button == null || event.button === 0) &&
250
- beginGesture(event, event.target)
267
+ beginGesture(
268
+ {
269
+ source: 'pen',
270
+ pointerId: event.pointerId,
271
+ clientX: event.clientX,
272
+ clientY: event.clientY,
273
+ },
274
+ event.target,
275
+ )
251
276
  ) {
252
277
  rootRef.current?.setPointerCapture?.(event.pointerId);
253
278
  }
@@ -261,7 +286,12 @@ export function useToastGesture({
261
286
  return;
262
287
  }
263
288
  moveGesture(
264
- event,
289
+ {
290
+ source: 'pen',
291
+ pointerId: event.pointerId,
292
+ clientX: event.clientX,
293
+ clientY: event.clientY,
294
+ },
265
295
  () => event.preventDefault(),
266
296
  () => rootRef.current?.releasePointerCapture?.(event.pointerId),
267
297
  );
@@ -275,7 +305,12 @@ export function useToastGesture({
275
305
  return;
276
306
  }
277
307
  rootRef.current?.releasePointerCapture?.(event.pointerId);
278
- endGesture(event);
308
+ endGesture({
309
+ source: 'pen',
310
+ pointerId: event.pointerId,
311
+ clientX: event.clientX,
312
+ clientY: event.clientY,
313
+ });
279
314
  },
280
315
  [endGesture],
281
316
  );
@@ -284,6 +319,7 @@ export function useToastGesture({
284
319
  (event: ReactPointerEvent<HTMLDivElement>) => {
285
320
  if (
286
321
  event.pointerType === 'pen' &&
322
+ gestureRef.current?.source === 'pen' &&
287
323
  gestureRef.current?.pointerId === event.pointerId
288
324
  ) {
289
325
  resetGesture(true);
@@ -298,12 +334,14 @@ export function useToastGesture({
298
334
  return;
299
335
  }
300
336
  const point = (touch: Touch): GesturePoint => ({
337
+ source: 'touch',
301
338
  pointerId: touch.identifier,
302
339
  clientX: touch.clientX,
303
340
  clientY: touch.clientY,
304
341
  });
305
342
  const changedTouch = (event: TouchEvent) => {
306
- const pointerId = gestureRef.current?.pointerId;
343
+ const state = gestureRef.current;
344
+ const pointerId = state?.source === 'touch' ? state.pointerId : null;
307
345
  return pointerId == null
308
346
  ? undefined
309
347
  : [...event.changedTouches].find(
@@ -311,12 +349,23 @@ export function useToastGesture({
311
349
  );
312
350
  };
313
351
  const handleTouchStart = (event: TouchEvent) => {
314
- const touch = event.touches.length === 1 ? event.changedTouches[0] : null;
315
- if (touch) {
352
+ if (event.touches?.length !== 1) {
353
+ // A second contact means the browser may be handling pinch zoom or
354
+ // two-finger scrolling. Abandon any accepted one-finger swipe before
355
+ // touchmove can suppress that native gesture.
356
+ resetGesture(true);
357
+ return;
358
+ }
359
+ const touch = event.changedTouches[0];
360
+ if (touch != null) {
316
361
  beginGesture(point(touch), event.target);
317
362
  }
318
363
  };
319
364
  const handleTouchMove = (event: TouchEvent) => {
365
+ if (event.touches?.length !== 1) {
366
+ resetGesture(true);
367
+ return;
368
+ }
320
369
  const touch = changedTouch(event);
321
370
  if (touch) {
322
371
  moveGesture(point(touch), () => {
@@ -327,6 +376,10 @@ export function useToastGesture({
327
376
  }
328
377
  };
329
378
  const handleTouchEnd = (event: TouchEvent) => {
379
+ if (event.touches.length > 0) {
380
+ resetGesture(true);
381
+ return;
382
+ }
330
383
  const touch = changedTouch(event);
331
384
  if (touch) {
332
385
  endGesture(point(touch));
@@ -122,6 +122,62 @@ export const docs = {
122
122
  usage: {
123
123
  description:
124
124
  'ToggleButton switches between selected and unselected states to represent a persistent on/off choice. Use it standalone for binary actions like bold, mute, or favorite, or inside a ToggleButtonGroup for single-select or multi-select toolbar controls.',
125
+ accessibility: [
126
+ {
127
+ name: 'Text label',
128
+ category: 'Color contrast',
129
+ criterion: '1.4.3 Contrast (Minimum)',
130
+ requirement: '4.5:1',
131
+ states: ['Unselected', 'Selected', 'Hover', 'Pointer down'],
132
+ description:
133
+ 'The label must have at least 4.5:1 contrast with the button background when selected and unselected. For Hover and Pointer down, measure the final background after the overlay is applied.',
134
+ },
135
+ {
136
+ name: 'Essential icon or spinner arc',
137
+ category: 'Color contrast',
138
+ criterion: '1.4.11 Non-text Contrast',
139
+ requirement: '3:1',
140
+ states: ['Icon only', 'Loading'],
141
+ description:
142
+ 'An icon-only ToggleButton must have at least 3:1 contrast between its icon and button background. The moving spinner arc must also meet 3:1. An icon beside a visible label does not need its own check.',
143
+ },
144
+ {
145
+ name: 'Selected state indicator',
146
+ category: 'Color contrast',
147
+ criterion: '1.4.11 Non-text Contrast',
148
+ requirement: '3:1 if relied upon',
149
+ states: ['Selected'],
150
+ description:
151
+ 'The selected background must reach 3:1 only when users need it to tell selected from unselected. Label weight or a changed icon can also show selection.',
152
+ },
153
+ {
154
+ name: 'Visible control boundary',
155
+ category: 'Color contrast',
156
+ criterion: '1.4.11 Non-text Contrast',
157
+ requirement: '3:1 if needed',
158
+ states: ['Rest'],
159
+ description:
160
+ 'The button edge needs 3:1 contrast only when users need it to see the control. A visible label or icon can show the control instead.',
161
+ },
162
+ {
163
+ name: 'Keyboard focus indicator',
164
+ category: 'Color contrast',
165
+ criterion: '1.4.11 Non-text Contrast',
166
+ requirement: '3:1',
167
+ states: ['Focus visible'],
168
+ description:
169
+ 'The focus outline must have at least 3:1 contrast with the area around the button. Check both selected and unselected states.',
170
+ },
171
+ {
172
+ name: 'Disabled appearance',
173
+ category: 'Color contrast',
174
+ criterion: '1.4.3 and 1.4.11 exceptions',
175
+ requirement: 'Not required',
176
+ states: ['Disabled'],
177
+ description:
178
+ 'Disabled controls do not need to meet these contrast ratios.',
179
+ },
180
+ ],
125
181
  bestPractices: [
126
182
  {guidance: true, description: 'Use a filled or colored icon for the pressed state so users can see the current state at a glance: an outline star vs a solid star, for example.'},
127
183
  {guidance: true, description: 'Keep the label identical between pressed and unpressed states. Let the visual treatment (icon, weight, background) communicate the change.'},
@@ -5,7 +5,7 @@
5
5
  export const docs = {
6
6
  name: 'Tokenizer',
7
7
  displayName: 'Tokenizer',
8
- category: 'Data Input',
8
+ category: 'Form Controls',
9
9
  keywords: ["tokenizer","multiselect","multi-select","chips","tags","combobox","autocomplete","taginput","chipinput"],
10
10
  props: [
11
11
  {
@@ -1,5 +1,21 @@
1
1
  // Copyright (c) Meta Platforms, Inc. and affiliates.
2
2
 
3
+ /** @type {import('@astryxdesign/cli/authoring').ComponentAnatomyElement[]} */
4
+ const anatomy = [
5
+ {
6
+ name: 'Outer chrome',
7
+ required: true,
8
+ description:
9
+ 'Section-owned container that supplies the toolbar surface, padding, and selected divider edges.',
10
+ },
11
+ {
12
+ name: 'Toolbar',
13
+ required: true,
14
+ description:
15
+ 'Named toolbar row that owns toolbar semantics and the start, optional center, and end layout.',
16
+ },
17
+ ];
18
+
3
19
  /** @type {import('@astryxdesign/cli/authoring').ComponentDoc} */
4
20
 
5
21
  export const docs = {
@@ -147,6 +163,7 @@ export const docs = {
147
163
  ], },
148
164
  ],
149
165
  usage: {
166
+ anatomy,
150
167
  description:
151
168
  'Toolbar is a horizontal bar with left, center, and right areas. Use it for contextual actions within a content area (above a table, inside a card, or in a panel), not as a page-level header. Set the size once on the toolbar and all buttons, inputs, and tabs inside it match automatically.',
152
169
  bestPractices: [
@@ -182,6 +199,7 @@ export const docsZh = {
182
199
  },
183
200
  ],
184
201
  usage: {
202
+ anatomy,
185
203
  description:
186
204
  'Toolbar is a horizontal bar with left, center, and right areas. Use it for contextual actions within a content area (above a table, inside a card, or in a panel), not as a page-level header. Set the size once on the toolbar and all buttons, inputs, and tabs inside it match automatically.',
187
205
  bestPractices: [
@@ -200,6 +218,7 @@ export const docsZh = {
200
218
  export const docsDense = {
201
219
  description: 'Horizontal bar w/ left, center, right areas. For contextual actions in content, not page headers. Size cascades to children.',
202
220
  usage: {
221
+ anatomy,
203
222
  description:
204
223
  'Horizontal bar w/ left, optional center, right. For contextual actions within content (tables, cards, panels), not page-level headers. Size cascades to children.',
205
224
  bestPractices: [