@astryxdesign/core 0.6.2 → 0.6.3-canary.0788c49

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 (558) hide show
  1. package/CHANGELOG.md +81 -0
  2. package/dist/BottomSheet/BottomSheet.d.ts +1 -1
  3. package/dist/BottomSheet/BottomSheet.d.ts.map +1 -1
  4. package/dist/BottomSheet/BottomSheet.js +3 -1
  5. package/dist/BottomSheet/BottomSheetPanel.d.ts +7 -5
  6. package/dist/BottomSheet/BottomSheetPanel.d.ts.map +1 -1
  7. package/dist/BottomSheet/BottomSheetPanel.js +56 -19
  8. package/dist/Button/Button.d.ts.map +1 -1
  9. package/dist/Button/Button.js +13 -28
  10. package/dist/Chat/ChatComposer.d.ts.map +1 -1
  11. package/dist/Chat/ChatComposer.js +10 -5
  12. package/dist/Chat/ChatComposerDrawer.d.ts +19 -5
  13. package/dist/Chat/ChatComposerDrawer.d.ts.map +1 -1
  14. package/dist/Chat/ChatComposerDrawer.js +48 -23
  15. package/dist/Chat/ChatComposerInput.d.ts +11 -3
  16. package/dist/Chat/ChatComposerInput.d.ts.map +1 -1
  17. package/dist/Chat/ChatComposerInput.js +63 -16
  18. package/dist/Chat/ChatDictationButton.d.ts +3 -2
  19. package/dist/Chat/ChatDictationButton.d.ts.map +1 -1
  20. package/dist/Chat/ChatDictationButton.js +6 -4
  21. package/dist/Chat/ChatLayout.d.ts +19 -0
  22. package/dist/Chat/ChatLayout.d.ts.map +1 -1
  23. package/dist/Chat/ChatLayout.js +19 -0
  24. package/dist/Chat/ChatLayoutScrollButton.d.ts.map +1 -1
  25. package/dist/Chat/ChatLayoutScrollButton.js +44 -34
  26. package/dist/Chat/ChatMessageBubble.d.ts +2 -0
  27. package/dist/Chat/ChatMessageBubble.d.ts.map +1 -1
  28. package/dist/Chat/ChatMessageBubble.js +5 -3
  29. package/dist/Chat/ChatMessageList.d.ts +2 -1
  30. package/dist/Chat/ChatMessageList.d.ts.map +1 -1
  31. package/dist/Chat/ChatMessageList.js +4 -3
  32. package/dist/Chat/ChatMessageMetadata.d.ts +5 -4
  33. package/dist/Chat/ChatMessageMetadata.d.ts.map +1 -1
  34. package/dist/Chat/ChatMessageMetadata.js +12 -9
  35. package/dist/Chat/ChatSendButton.d.ts +2 -1
  36. package/dist/Chat/ChatSendButton.d.ts.map +1 -1
  37. package/dist/Chat/ChatSendButton.js +6 -4
  38. package/dist/Chat/index.d.ts +1 -1
  39. package/dist/Chat/index.d.ts.map +1 -1
  40. package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
  41. package/dist/CheckboxInput/CheckboxInput.js +12 -2
  42. package/dist/Citation/Citation.d.ts +1 -1
  43. package/dist/Citation/Citation.d.ts.map +1 -1
  44. package/dist/Citation/Citation.js +3 -2
  45. package/dist/CodeBlock/highlightRanges.d.ts.map +1 -1
  46. package/dist/CodeBlock/highlightRanges.js +14 -3
  47. package/dist/Collapsible/Collapsible.d.ts.map +1 -1
  48. package/dist/Collapsible/Collapsible.js +6 -1
  49. package/dist/CommandPalette/CommandPaletteInput.d.ts.map +1 -1
  50. package/dist/CommandPalette/CommandPaletteInput.js +1 -1
  51. package/dist/ComplexSelector/ComplexSelector.d.ts.map +1 -1
  52. package/dist/ComplexSelector/ComplexSelector.js +1 -1
  53. package/dist/DateInput/DateInput.d.ts.map +1 -1
  54. package/dist/DateInput/DateInput.js +4 -4
  55. package/dist/DateInput/NativeDateField.d.ts.map +1 -1
  56. package/dist/DateInput/NativeDateField.js +16 -16
  57. package/dist/DateInput/TouchDateField.d.ts.map +1 -1
  58. package/dist/DateInput/TouchDateField.js +2 -2
  59. package/dist/DateRangeInput/DateRangeInput.d.ts.map +1 -1
  60. package/dist/DateRangeInput/DateRangeInput.js +4 -4
  61. package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
  62. package/dist/DateTimeInput/DateTimeInput.js +8 -8
  63. package/dist/DateTimeInput/TouchDateTimeField.d.ts.map +1 -1
  64. package/dist/DateTimeInput/TouchDateTimeField.js +4 -4
  65. package/dist/DateTimeInput/nativePickerSegmentStyles.d.ts +9 -3
  66. package/dist/DateTimeInput/nativePickerSegmentStyles.d.ts.map +1 -1
  67. package/dist/DateTimeInput/nativePickerSegmentStyles.js +3 -3
  68. package/dist/Dialog/DialogHeader.d.ts +8 -1
  69. package/dist/Dialog/DialogHeader.d.ts.map +1 -1
  70. package/dist/Dialog/DialogHeader.js +13 -4
  71. package/dist/Field/InputClearButton.d.ts +4 -2
  72. package/dist/Field/InputClearButton.d.ts.map +1 -1
  73. package/dist/Field/InputClearButton.js +4 -2
  74. package/dist/Field/PanelSearchInput.d.ts.map +1 -1
  75. package/dist/Field/PanelSearchInput.js +1 -1
  76. package/dist/FileInput/FileInput.d.ts.map +1 -1
  77. package/dist/FileInput/FileInput.js +7 -7
  78. package/dist/Icon/Icon.d.ts +6 -31
  79. package/dist/Icon/Icon.d.ts.map +1 -1
  80. package/dist/Icon/Icon.js +8 -71
  81. package/dist/Icon/IconDefaultSizeContext.d.ts +4 -0
  82. package/dist/Icon/IconDefaultSizeContext.d.ts.map +1 -0
  83. package/dist/Icon/IconDefaultSizeContext.js +18 -0
  84. package/dist/Icon/IconSize.stylex.d.ts +65 -0
  85. package/dist/Icon/IconSize.stylex.d.ts.map +1 -0
  86. package/dist/Icon/IconSize.stylex.js +75 -0
  87. package/dist/Item/Item.d.ts +1 -1
  88. package/dist/Item/Item.d.ts.map +1 -1
  89. package/dist/Item/Item.js +4 -3
  90. package/dist/Kbd/Kbd.d.ts +5 -3
  91. package/dist/Kbd/Kbd.d.ts.map +1 -1
  92. package/dist/Kbd/Kbd.js +36 -42
  93. package/dist/Link/Link.d.ts.map +1 -1
  94. package/dist/Link/Link.js +6 -2
  95. package/dist/Link/useLinkComponent.d.ts +5 -15
  96. package/dist/Link/useLinkComponent.d.ts.map +1 -1
  97. package/dist/Link/useLinkComponent.js +34 -51
  98. package/dist/List/List.d.ts +19 -1
  99. package/dist/List/List.d.ts.map +1 -1
  100. package/dist/List/List.js +9 -2
  101. package/dist/List/ListContext.d.ts +1 -0
  102. package/dist/List/ListContext.d.ts.map +1 -1
  103. package/dist/List/ListContext.js +2 -1
  104. package/dist/List/ListItem.d.ts +1 -1
  105. package/dist/List/ListItem.d.ts.map +1 -1
  106. package/dist/List/ListItem.js +9 -2
  107. package/dist/Markdown/Markdown.d.ts +8 -2
  108. package/dist/Markdown/Markdown.d.ts.map +1 -1
  109. package/dist/Markdown/Markdown.fr23.bench.d.ts +2 -0
  110. package/dist/Markdown/Markdown.fr23.bench.d.ts.map +1 -0
  111. package/dist/Markdown/Markdown.fr23.bench.js +150 -0
  112. package/dist/Markdown/Markdown.fr23.sampling.d.ts +2 -0
  113. package/dist/Markdown/Markdown.fr23.sampling.d.ts.map +1 -0
  114. package/dist/Markdown/Markdown.fr23.sampling.js +43 -0
  115. package/dist/Markdown/Markdown.js +262 -109
  116. package/dist/Markdown/ast.d.ts +162 -0
  117. package/dist/Markdown/ast.d.ts.map +1 -0
  118. package/dist/Markdown/ast.js +74 -0
  119. package/dist/Markdown/index.d.ts +5 -3
  120. package/dist/Markdown/index.d.ts.map +1 -1
  121. package/dist/Markdown/index.js +3 -2
  122. package/dist/Markdown/parser/index.d.ts +10 -0
  123. package/dist/Markdown/parser/index.d.ts.map +1 -0
  124. package/dist/Markdown/parser/index.js +10 -0
  125. package/dist/Markdown/parser.d.ts +126 -58
  126. package/dist/Markdown/parser.d.ts.map +1 -1
  127. package/dist/Markdown/parser.js +726 -177
  128. package/dist/Markdown/plugins/frontmatter.d.ts +32 -0
  129. package/dist/Markdown/plugins/frontmatter.d.ts.map +1 -0
  130. package/dist/Markdown/plugins/frontmatter.js +132 -0
  131. package/dist/Markdown/plugins/index.d.ts +17 -0
  132. package/dist/Markdown/plugins/index.d.ts.map +1 -0
  133. package/dist/Markdown/plugins/index.js +14 -0
  134. package/dist/Markdown/plugins/protocol.d.ts +191 -0
  135. package/dist/Markdown/plugins/protocol.d.ts.map +1 -0
  136. package/dist/Markdown/plugins/protocol.js +957 -0
  137. package/dist/Markdown/plugins/semanticFence.d.ts +36 -0
  138. package/dist/Markdown/plugins/semanticFence.d.ts.map +1 -0
  139. package/dist/Markdown/plugins/semanticFence.js +213 -0
  140. package/dist/Markdown/plugins/softBreaks.d.ts +3 -0
  141. package/dist/Markdown/plugins/softBreaks.d.ts.map +1 -0
  142. package/dist/Markdown/plugins/softBreaks.js +172 -0
  143. package/dist/Markdown/plugins/sourceDecoration.d.ts +76 -0
  144. package/dist/Markdown/plugins/sourceDecoration.d.ts.map +1 -0
  145. package/dist/Markdown/plugins/sourceDecoration.js +393 -0
  146. package/dist/Markdown/plugins/textTransform.d.ts +30 -0
  147. package/dist/Markdown/plugins/textTransform.d.ts.map +1 -0
  148. package/dist/Markdown/plugins/textTransform.js +374 -0
  149. package/dist/Markdown/remark.d.ts +196 -0
  150. package/dist/Markdown/remark.d.ts.map +1 -0
  151. package/dist/Markdown/remark.js +1062 -0
  152. package/dist/Markdown/url.d.ts +6 -0
  153. package/dist/Markdown/url.d.ts.map +1 -0
  154. package/dist/Markdown/url.js +31 -0
  155. package/dist/Markdown/utils.d.ts +4 -2
  156. package/dist/Markdown/utils.d.ts.map +1 -1
  157. package/dist/Markdown/utils.js +2 -1
  158. package/dist/MetadataList/MetadataList.js +3 -3
  159. package/dist/NumberInput/NumberInput.d.ts.map +1 -1
  160. package/dist/NumberInput/NumberInput.js +4 -4
  161. package/dist/Outline/index.d.ts +1 -0
  162. package/dist/Outline/index.d.ts.map +1 -1
  163. package/dist/Outline/parseOutlineFromMarkdown.d.ts +7 -1
  164. package/dist/Outline/parseOutlineFromMarkdown.d.ts.map +1 -1
  165. package/dist/Outline/parseOutlineFromMarkdown.js +14 -8
  166. package/dist/Outline/useOutlineFromMarkdown.d.ts +3 -1
  167. package/dist/Outline/useOutlineFromMarkdown.d.ts.map +1 -1
  168. package/dist/Outline/useOutlineFromMarkdown.js +8 -3
  169. package/dist/PowerSearch/PowerSearchEditPopover.d.ts.map +1 -1
  170. package/dist/PowerSearch/PowerSearchEditPopover.js +46 -30
  171. package/dist/PowerSearch/PowerSearchValueEditor.d.ts.map +1 -1
  172. package/dist/PowerSearch/PowerSearchValueEditor.js +5 -0
  173. package/dist/ProgressBar/ProgressBar.d.ts.map +1 -1
  174. package/dist/ProgressBar/ProgressBar.js +36 -31
  175. package/dist/RadioList/RadioListItem.d.ts.map +1 -1
  176. package/dist/RadioList/RadioListItem.js +13 -1
  177. package/dist/ScrollableArea/ScrollableArea.d.ts +1 -0
  178. package/dist/ScrollableArea/ScrollableArea.d.ts.map +1 -1
  179. package/dist/ScrollableArea/ScrollableArea.js +1 -0
  180. package/dist/SegmentedControl/SegmentedControl.d.ts +1 -1
  181. package/dist/SegmentedControl/SegmentedControl.d.ts.map +1 -1
  182. package/dist/SegmentedControl/SegmentedControl.js +1 -0
  183. package/dist/SegmentedControl/SegmentedControlItem.d.ts.map +1 -1
  184. package/dist/SegmentedControl/SegmentedControlItem.js +5 -5
  185. package/dist/Selector/Selector.d.ts +9 -7
  186. package/dist/Selector/Selector.d.ts.map +1 -1
  187. package/dist/Selector/Selector.js +16 -7
  188. package/dist/SideNav/SideNav.d.ts +2 -1
  189. package/dist/SideNav/SideNav.d.ts.map +1 -1
  190. package/dist/SideNav/SideNav.js +9 -3
  191. package/dist/Slider/Slider.d.ts.map +1 -1
  192. package/dist/Slider/Slider.js +28 -12
  193. package/dist/Spinner/Spinner.d.ts.map +1 -1
  194. package/dist/Spinner/Spinner.js +23 -7
  195. package/dist/Switch/Switch.d.ts.map +1 -1
  196. package/dist/Switch/Switch.js +11 -0
  197. package/dist/TabList/Tab.d.ts +1 -1
  198. package/dist/TabList/Tab.d.ts.map +1 -1
  199. package/dist/TabList/Tab.js +20 -7
  200. package/dist/Table/Table.d.ts +1 -0
  201. package/dist/Table/Table.d.ts.map +1 -1
  202. package/dist/Table/Table.js +29 -13
  203. package/dist/TextArea/TextArea.d.ts.map +1 -1
  204. package/dist/TextArea/TextArea.js +1 -1
  205. package/dist/TextInput/TextInput.d.ts.map +1 -1
  206. package/dist/TextInput/TextInput.js +2 -2
  207. package/dist/TimeInput/TimeInput.d.ts.map +1 -1
  208. package/dist/TimeInput/TimeInput.js +4 -4
  209. package/dist/Timer/Timer.d.ts +49 -0
  210. package/dist/Timer/Timer.d.ts.map +1 -0
  211. package/dist/Timer/Timer.js +158 -0
  212. package/dist/Timer/index.d.ts +9 -0
  213. package/dist/Timer/index.d.ts.map +1 -0
  214. package/dist/Timer/index.js +10 -0
  215. package/dist/ToggleButton/ToggleButton.d.ts +9 -6
  216. package/dist/ToggleButton/ToggleButton.d.ts.map +1 -1
  217. package/dist/ToggleButton/ToggleButton.js +15 -27
  218. package/dist/TopNav/TopNavMenu.d.ts +1 -1
  219. package/dist/TopNav/TopNavMenu.d.ts.map +1 -1
  220. package/dist/TopNav/TopNavMenu.js +3 -2
  221. package/dist/TreeList/TreeList.d.ts.map +1 -1
  222. package/dist/TreeList/TreeList.js +3 -0
  223. package/dist/TreeList/TreeListItem.d.ts +6 -1
  224. package/dist/TreeList/TreeListItem.d.ts.map +1 -1
  225. package/dist/TreeList/TreeListItem.js +12 -2
  226. package/dist/TreeList/TreeListTypes.d.ts +8 -1
  227. package/dist/TreeList/TreeListTypes.d.ts.map +1 -1
  228. package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
  229. package/dist/Typeahead/BaseTypeahead.js +16 -7
  230. package/dist/astryx.css +27 -11
  231. package/dist/hooks/scrollKeyboardDelegation.d.ts +3 -0
  232. package/dist/hooks/scrollKeyboardDelegation.d.ts.map +1 -0
  233. package/dist/hooks/scrollKeyboardDelegation.js +146 -0
  234. package/dist/hooks/useClickableContainer.d.ts +10 -1
  235. package/dist/hooks/useClickableContainer.d.ts.map +1 -1
  236. package/dist/hooks/useClickableContainer.js +15 -4
  237. package/dist/hooks/useScrollableArea.d.ts +6 -2
  238. package/dist/hooks/useScrollableArea.d.ts.map +1 -1
  239. package/dist/hooks/useScrollableArea.js +17 -5
  240. package/dist/index.d.ts +1 -0
  241. package/dist/index.d.ts.map +1 -1
  242. package/dist/index.js +3 -0
  243. package/dist/theme/declarationBoundary.d.ts +17 -0
  244. package/dist/theme/declarationBoundary.d.ts.map +1 -0
  245. package/dist/theme/declarationBoundary.js +343 -0
  246. package/dist/theme/derivedVarRegistry.d.ts.map +1 -1
  247. package/dist/theme/derivedVarRegistry.js +4 -0
  248. package/dist/theme/generateThemeRules.d.ts +13 -7
  249. package/dist/theme/generateThemeRules.d.ts.map +1 -1
  250. package/dist/theme/generateThemeRules.js +140 -34
  251. package/dist/utils/interactionOverlay.stylex.d.ts +8 -0
  252. package/dist/utils/interactionOverlay.stylex.d.ts.map +1 -1
  253. package/dist/utils/interactionOverlay.stylex.js +9 -0
  254. package/dist/utils/safeUrl.d.ts +11 -0
  255. package/dist/utils/safeUrl.d.ts.map +1 -0
  256. package/dist/utils/safeUrl.js +78 -0
  257. package/locales/af-ZA.json +530 -1
  258. package/locales/ar-SA.json +490 -1
  259. package/locales/ca-ES.json +514 -1
  260. package/locales/cs-CZ.json +498 -1
  261. package/locales/da-DK.json +522 -1
  262. package/locales/de-DE.json +522 -1
  263. package/locales/el-GR.json +498 -1
  264. package/locales/es-ES.json +510 -1
  265. package/locales/fi-FI.json +498 -1
  266. package/locales/fr-FR.json +534 -1
  267. package/locales/he-IL.json +498 -1
  268. package/locales/hu-HU.json +498 -1
  269. package/locales/it-IT.json +510 -1
  270. package/locales/ja-JP.json +490 -1
  271. package/locales/ko-KR.json +498 -1
  272. package/locales/nl-NL.json +534 -1
  273. package/locales/no-NO.json +510 -1
  274. package/locales/pl-PL.json +506 -1
  275. package/locales/pt-BR.json +506 -1
  276. package/locales/pt-PT.json +506 -1
  277. package/locales/ro-RO.json +506 -1
  278. package/locales/ru-RU.json +498 -1
  279. package/locales/sr-SP.json +498 -1
  280. package/locales/sv-SE.json +502 -1
  281. package/locales/tr-TR.json +498 -1
  282. package/locales/uk-UA.json +498 -1
  283. package/locales/vi-VN.json +502 -1
  284. package/locales/zh-CN.json +490 -1
  285. package/locales/zh-TW.json +490 -1
  286. package/package.json +24 -4
  287. package/src/BottomSheet/BottomSheet.doc.mjs +8 -1
  288. package/src/BottomSheet/BottomSheet.spec.md +46 -20
  289. package/src/BottomSheet/BottomSheet.test.tsx +6 -3
  290. package/src/BottomSheet/BottomSheet.tsx +3 -1
  291. package/src/BottomSheet/BottomSheetKeyboard.test.tsx +195 -0
  292. package/src/BottomSheet/BottomSheetPanel.test.tsx +11 -1
  293. package/src/BottomSheet/BottomSheetPanel.tsx +49 -16
  294. package/src/BottomSheet/__tests__/BottomSheetKeyboard.a11y.browser.spec.ts +344 -0
  295. package/src/Button/Button.doc.mjs +5 -5
  296. package/src/Button/Button.tsx +15 -13
  297. package/src/Button/__tests__/Button.a11y.chromium.spec.ts +17 -1
  298. package/src/Button/__tests__/Button.a11y.known-failures.ts +0 -29
  299. package/src/Button/__tests__/Button.a11y.renders.tsx +9 -2
  300. package/src/Button/__tests__/Button.a11y.states.ts +9 -0
  301. package/src/Chat/ChatComposer.doc.mjs +14 -0
  302. package/src/Chat/ChatComposer.spec.md +230 -0
  303. package/src/Chat/ChatComposer.test.tsx +57 -0
  304. package/src/Chat/ChatComposer.tsx +21 -9
  305. package/src/Chat/ChatComposerDrawer.doc.mjs +63 -5
  306. package/src/Chat/ChatComposerDrawer.spec.md +267 -0
  307. package/src/Chat/ChatComposerDrawer.test.tsx +81 -12
  308. package/src/Chat/ChatComposerDrawer.tsx +38 -9
  309. package/src/Chat/ChatComposerInput.doc.mjs +114 -26
  310. package/src/Chat/ChatComposerInput.spec.md +185 -0
  311. package/src/Chat/ChatComposerInput.test.tsx +113 -3
  312. package/src/Chat/ChatComposerInput.tsx +78 -13
  313. package/src/Chat/ChatComposerTokenElement.spec.md +152 -0
  314. package/src/Chat/ChatComposerTokenElement.test.tsx +85 -0
  315. package/src/Chat/ChatDictationButton.doc.mjs +3 -3
  316. package/src/Chat/ChatDictationButton.spec.md +160 -0
  317. package/src/Chat/ChatDictationButton.test.tsx +92 -27
  318. package/src/Chat/ChatDictationButton.tsx +8 -6
  319. package/src/Chat/ChatLayout.doc.mjs +12 -4
  320. package/src/Chat/ChatLayout.spec.md +271 -0
  321. package/src/Chat/ChatLayout.test.tsx +28 -0
  322. package/src/Chat/ChatLayout.tsx +19 -0
  323. package/src/Chat/ChatLayoutScrollButton.spec.md +338 -0
  324. package/src/Chat/ChatLayoutScrollButton.test.tsx +31 -0
  325. package/src/Chat/ChatLayoutScrollButton.tsx +31 -14
  326. package/src/Chat/ChatMessageBubble.doc.mjs +28 -15
  327. package/src/Chat/ChatMessageBubble.spec.md +263 -0
  328. package/src/Chat/ChatMessageBubble.test.tsx +93 -0
  329. package/src/Chat/ChatMessageBubble.tsx +8 -3
  330. package/src/Chat/ChatMessageList.spec.md +222 -0
  331. package/src/Chat/ChatMessageList.test.tsx +64 -2
  332. package/src/Chat/ChatMessageList.tsx +4 -3
  333. package/src/Chat/ChatMessageMetadata.doc.mjs +57 -3
  334. package/src/Chat/ChatMessageMetadata.spec.md +220 -0
  335. package/src/Chat/ChatMessageMetadata.test.tsx +34 -0
  336. package/src/Chat/ChatMessageMetadata.tsx +13 -13
  337. package/src/Chat/ChatSendButton.doc.mjs +32 -0
  338. package/src/Chat/ChatSendButton.spec.md +227 -0
  339. package/src/Chat/ChatSendButton.test.tsx +128 -8
  340. package/src/Chat/ChatSendButton.tsx +6 -4
  341. package/src/Chat/__tests__/ChatLayoutScrollButton.a11y.chromium.spec.ts +514 -0
  342. package/src/Chat/__tests__/ChatLayoutScrollButtonSurface.a11y.chromium.spec.ts +740 -0
  343. package/src/Chat/__tests__/ChatMessageBubble.a11y.chromium.spec.ts +457 -0
  344. package/src/Chat/__tests__/ChatMessageBubble.auditProvenance.test.ts +91 -0
  345. package/src/Chat/__tests__/ChatMessageBubble.auditProvenance.ts +91 -0
  346. package/src/Chat/__tests__/ChatMessageList.a11y.chromium.spec.ts +456 -0
  347. package/src/Chat/__tests__/ChatMessageMetadata.a11y.chromium.spec.ts +413 -0
  348. package/src/Chat/__tests__/ChatSendButton.a11y.chromium.spec.ts +1522 -0
  349. package/src/Chat/index.ts +1 -0
  350. package/src/CheckboxInput/CheckboxInput.doc.mjs +11 -0
  351. package/src/CheckboxInput/CheckboxInput.test.tsx +34 -0
  352. package/src/CheckboxInput/CheckboxInput.tsx +21 -1
  353. package/src/Citation/Citation.doc.mjs +2 -2
  354. package/src/Citation/Citation.test.tsx +17 -0
  355. package/src/Citation/Citation.tsx +4 -2
  356. package/src/ClickableCard/ClickableCard.doc.mjs +2 -2
  357. package/src/ClickableCard/ClickableCard.test.tsx +102 -5
  358. package/src/CodeBlock/highlightRanges.test.ts +143 -1
  359. package/src/CodeBlock/highlightRanges.ts +15 -5
  360. package/src/Collapsible/Collapsible.doc.mjs +11 -0
  361. package/src/Collapsible/Collapsible.test.tsx +21 -0
  362. package/src/Collapsible/Collapsible.tsx +5 -0
  363. package/src/CommandPalette/CommandPaletteInput.tsx +6 -1
  364. package/src/ComplexSelector/ComplexSelector.tsx +6 -1
  365. package/src/DateInput/DateInput.tsx +6 -1
  366. package/src/DateInput/NativeDateField.tsx +24 -8
  367. package/src/DateInput/TouchDateField.tsx +6 -2
  368. package/src/DateRangeInput/DateRangeInput.tsx +6 -1
  369. package/src/DateTimeInput/DateTimeInput.tsx +6 -1
  370. package/src/DateTimeInput/TouchDateTimeField.tsx +6 -1
  371. package/src/DateTimeInput/nativePickerSegmentStyles.ts +24 -8
  372. package/src/Dialog/Dialog.doc.mjs +2 -0
  373. package/src/Dialog/DialogHeader.doc.mjs +22 -0
  374. package/src/Dialog/DialogHeader.test.tsx +167 -3
  375. package/src/Dialog/DialogHeader.tsx +37 -6
  376. package/src/Dialog/modules/DialogHeader.spec.md +101 -42
  377. package/src/DropdownMenu/DropdownMenu.test.tsx +5 -2
  378. package/src/Field/InputClearButton.test.tsx +17 -3
  379. package/src/Field/InputClearButton.tsx +5 -2
  380. package/src/Field/PanelSearchInput.tsx +5 -2
  381. package/src/FieldStatus/__tests__/StatusMessage.a11y.states.ts +1 -1
  382. package/src/FileInput/FileInput.tsx +11 -2
  383. package/src/Icon/Icon.doc.mjs +7 -5
  384. package/src/Icon/Icon.spec.md +81 -40
  385. package/src/Icon/Icon.test.tsx +6 -3
  386. package/src/Icon/Icon.tsx +20 -68
  387. package/src/Icon/IconDefaultSizeContext.ts +23 -0
  388. package/src/Icon/IconSize.stylex.ts +70 -0
  389. package/src/IconButton/IconButton.doc.mjs +3 -3
  390. package/src/IconButton/IconButton.test.tsx +40 -0
  391. package/src/Item/Item.doc.mjs +6 -2
  392. package/src/Item/Item.tsx +9 -3
  393. package/src/Kbd/Kbd.doc.mjs +3 -3
  394. package/src/Kbd/Kbd.test.tsx +57 -1
  395. package/src/Kbd/Kbd.tsx +57 -37
  396. package/src/Layout/LayoutFooter.doc.mjs +17 -0
  397. package/src/Link/Link.doc.mjs +17 -4
  398. package/src/Link/Link.test.tsx +24 -0
  399. package/src/Link/Link.tsx +5 -0
  400. package/src/Link/LinkProvider.doc.mjs +2 -2
  401. package/src/Link/__tests__/Link.navigation.a11y.chromium.spec.ts +261 -0
  402. package/src/Link/useLinkComponent.test.tsx +333 -0
  403. package/src/Link/useLinkComponent.ts +29 -57
  404. package/src/List/List.doc.mjs +6 -0
  405. package/src/List/List.spec.md +200 -0
  406. package/src/List/List.test.tsx +117 -1
  407. package/src/List/List.tsx +22 -2
  408. package/src/List/ListContext.tsx +3 -1
  409. package/src/List/ListItem.doc.mjs +2 -2
  410. package/src/List/ListItem.source-build.test.mjs +41 -0
  411. package/src/List/ListItem.tsx +26 -2
  412. package/src/Markdown/Markdown.doc.mjs +300 -2
  413. package/src/Markdown/Markdown.fr23.bench.ts +174 -0
  414. package/src/Markdown/Markdown.fr23.perf.test.ts +249 -0
  415. package/src/Markdown/Markdown.fr23.sampling.ts +49 -0
  416. package/src/Markdown/Markdown.helpers.perf.test.ts +489 -0
  417. package/src/Markdown/Markdown.public.test.ts +130 -3
  418. package/src/Markdown/Markdown.renderBoundary.test.tsx +72 -0
  419. package/src/Markdown/Markdown.spec.md +210 -45
  420. package/src/Markdown/Markdown.test.tsx +87 -3
  421. package/src/Markdown/Markdown.tsx +414 -135
  422. package/src/Markdown/ast.test.ts +307 -0
  423. package/src/Markdown/ast.ts +337 -0
  424. package/src/Markdown/incremental.test.ts +43 -0
  425. package/src/Markdown/index.ts +37 -1
  426. package/src/Markdown/modules/remark.spec.md +269 -0
  427. package/src/Markdown/modules/softBreaks.spec.md +137 -0
  428. package/src/Markdown/parser/index.ts +64 -0
  429. package/src/Markdown/parser.public.test.ts +103 -0
  430. package/src/Markdown/parser.test.ts +38 -6
  431. package/src/Markdown/parser.ts +1221 -242
  432. package/src/Markdown/plugins/frontmatter.test.tsx +125 -0
  433. package/src/Markdown/plugins/frontmatter.ts +198 -0
  434. package/src/Markdown/plugins/index.ts +54 -0
  435. package/src/Markdown/plugins/protocol.test.tsx +1022 -0
  436. package/src/Markdown/plugins/protocol.ts +1618 -0
  437. package/src/Markdown/plugins/semanticFence.test.tsx +546 -0
  438. package/src/Markdown/plugins/semanticFence.ts +424 -0
  439. package/src/Markdown/plugins/softBreaks.test.tsx +172 -0
  440. package/src/Markdown/plugins/softBreaks.ts +169 -0
  441. package/src/Markdown/plugins/sourceDecoration.test.tsx +799 -0
  442. package/src/Markdown/plugins/sourceDecoration.ts +539 -0
  443. package/src/Markdown/plugins/textTransform.test.ts +467 -0
  444. package/src/Markdown/plugins/textTransform.ts +554 -0
  445. package/src/Markdown/remark.test.tsx +1877 -0
  446. package/src/Markdown/remark.ts +1574 -0
  447. package/src/Markdown/url.ts +36 -0
  448. package/src/Markdown/utils.ts +13 -1
  449. package/src/MetadataList/MetadataList.test.tsx +4 -2
  450. package/src/MetadataList/MetadataList.tsx +3 -3
  451. package/src/MultiSelector/MultiSelector.test.tsx +1 -35
  452. package/src/MultiSelector/__tests__/MultiSelector.listbox.a11y.test.tsx +128 -0
  453. package/src/NumberInput/NumberInput.tsx +6 -1
  454. package/src/Outline/Outline.doc.mjs +7 -5
  455. package/src/Outline/Outline.spec.md +1 -1
  456. package/src/Outline/index.ts +1 -0
  457. package/src/Outline/modules/parseOutlineFromMarkdown.spec.md +142 -0
  458. package/src/Outline/parseOutlineFromMarkdown.ts +40 -10
  459. package/src/Outline/useOutlineFromMarkdown.ts +15 -3
  460. package/src/PowerSearch/PowerSearch.doc.mjs +2 -2
  461. package/src/PowerSearch/PowerSearchEditPopover.test.tsx +150 -1
  462. package/src/PowerSearch/PowerSearchEditPopover.tsx +51 -28
  463. package/src/PowerSearch/PowerSearchValueEditor.test.tsx +51 -0
  464. package/src/PowerSearch/PowerSearchValueEditor.tsx +4 -0
  465. package/src/ProgressBar/ProgressBar.test.tsx +29 -11
  466. package/src/ProgressBar/ProgressBar.tsx +59 -72
  467. package/src/RadioList/RadioList.doc.mjs +11 -0
  468. package/src/RadioList/RadioList.test.tsx +32 -0
  469. package/src/RadioList/RadioListItem.tsx +25 -1
  470. package/src/ScrollableArea/ScrollableArea.tsx +1 -0
  471. package/src/ScrollableArea/modules/useScrollableArea.spec.md +50 -22
  472. package/src/SegmentedControl/SegmentedControl.doc.mjs +2 -2
  473. package/src/SegmentedControl/SegmentedControl.test.tsx +61 -0
  474. package/src/SegmentedControl/SegmentedControl.tsx +2 -1
  475. package/src/SegmentedControl/SegmentedControlItem.tsx +6 -9
  476. package/src/Selector/Selector.doc.mjs +3 -3
  477. package/src/Selector/Selector.spec.md +51 -49
  478. package/src/Selector/Selector.test.tsx +61 -55
  479. package/src/Selector/Selector.tsx +34 -15
  480. package/src/Selector/__tests__/Listbox.a11y.chromium.spec.ts +130 -0
  481. package/src/Selector/__tests__/Listbox.a11y.dom.ts +38 -0
  482. package/src/Selector/__tests__/Listbox.a11y.inventory.test.ts +63 -0
  483. package/src/Selector/__tests__/Listbox.a11y.known-failures.ts +37 -0
  484. package/src/Selector/__tests__/Listbox.a11y.states.ts +232 -0
  485. package/src/Selector/__tests__/Selector.a11y.chromium.spec.ts +45 -0
  486. package/src/Selector/__tests__/Selector.listbox.a11y.test.tsx +121 -0
  487. package/src/SideNav/SideNav.doc.mjs +1 -1
  488. package/src/SideNav/SideNav.test.tsx +10 -0
  489. package/src/SideNav/SideNav.tsx +14 -2
  490. package/src/Slider/Slider.doc.mjs +17 -3
  491. package/src/Slider/Slider.test.tsx +221 -0
  492. package/src/Slider/Slider.tsx +40 -9
  493. package/src/Spinner/Spinner.test.tsx +4 -0
  494. package/src/Spinner/Spinner.tsx +31 -18
  495. package/src/Switch/Switch.doc.mjs +11 -0
  496. package/src/Switch/Switch.test.tsx +16 -0
  497. package/src/Switch/Switch.tsx +28 -0
  498. package/src/TabList/Tab.tsx +35 -7
  499. package/src/TabList/TabList.doc.mjs +11 -0
  500. package/src/TabList/TabList.spec.md +204 -0
  501. package/src/TabList/TabList.test.tsx +66 -0
  502. package/src/TabList/__tests__/Tabs.a11y.known-failures.ts +1 -34
  503. package/src/Table/Table.doc.mjs +1 -1
  504. package/src/Table/Table.spec.md +54 -40
  505. package/src/Table/Table.test.tsx +83 -10
  506. package/src/Table/Table.tsx +30 -22
  507. package/src/Table/__tests__/TableScroll.a11y.chromium.spec.ts +137 -0
  508. package/src/TextArea/TextArea.tsx +6 -1
  509. package/src/TextInput/TextInput.test.tsx +20 -1
  510. package/src/TextInput/TextInput.tsx +6 -1
  511. package/src/TimeInput/TimeInput.tsx +6 -1
  512. package/src/Timer/Timer.doc.mjs +183 -0
  513. package/src/Timer/Timer.spec.md +215 -0
  514. package/src/Timer/Timer.test.tsx +302 -0
  515. package/src/Timer/Timer.tsx +258 -0
  516. package/src/Timer/index.ts +11 -0
  517. package/src/ToggleButton/ToggleButton.doc.mjs +3 -3
  518. package/src/ToggleButton/ToggleButton.test.tsx +299 -4
  519. package/src/ToggleButton/ToggleButton.tsx +26 -33
  520. package/src/ToggleButton/ToggleButtonGroup.doc.mjs +3 -3
  521. package/src/ToggleButton/__tests__/ToggleButton.a11y.chromium.spec.ts +209 -0
  522. package/src/Token/Token.doc.mjs +2 -2
  523. package/src/Tokenizer/Tokenizer.spec.md +142 -75
  524. package/src/TopNav/TopNavMenu.doc.mjs +2 -2
  525. package/src/TopNav/TopNavMenu.test.tsx +30 -0
  526. package/src/TopNav/TopNavMenu.tsx +7 -2
  527. package/src/TreeList/TreeList.doc.mjs +3 -3
  528. package/src/TreeList/TreeList.spec.md +123 -75
  529. package/src/TreeList/TreeList.test.tsx +29 -0
  530. package/src/TreeList/TreeList.tsx +3 -0
  531. package/src/TreeList/TreeListItem.tsx +15 -1
  532. package/src/TreeList/TreeListTypes.ts +11 -1
  533. package/src/Typeahead/BaseTypeahead.spec.md +4 -3
  534. package/src/Typeahead/BaseTypeahead.tsx +21 -7
  535. package/src/Typeahead/Typeahead.test.tsx +53 -0
  536. package/src/__tests__/PressedState.a11y.chromium.spec.ts +813 -0
  537. package/src/__tests__/pressState.ts +93 -0
  538. package/src/hooks/scrollKeyboardDelegation.test.ts +155 -0
  539. package/src/hooks/scrollKeyboardDelegation.ts +233 -0
  540. package/src/hooks/useClickableContainer.test.tsx +236 -0
  541. package/src/hooks/useClickableContainer.ts +20 -4
  542. package/src/hooks/useScrollableArea.doc.mjs +15 -3
  543. package/src/hooks/useScrollableArea.test.tsx +59 -1
  544. package/src/hooks/useScrollableArea.ts +34 -10
  545. package/src/index.ts +3 -0
  546. package/src/inputFontFloor.test.ts +106 -0
  547. package/src/tailwind-theme.css +0 -6
  548. package/src/theme/Theme.test.tsx +32 -0
  549. package/src/theme/declarationBoundary.test.ts +126 -0
  550. package/src/theme/declarationBoundary.ts +347 -0
  551. package/src/theme/derivedVarRegistry.test.ts +7 -0
  552. package/src/theme/derivedVarRegistry.ts +1 -0
  553. package/src/theme/generateThemeRules.test.ts +526 -1
  554. package/src/theme/generateThemeRules.ts +228 -50
  555. package/src/theme/tokenValueCompat.test.ts +4 -1
  556. package/src/utils/interactionOverlay.stylex.ts +20 -0
  557. package/src/utils/safeUrl.test.ts +88 -0
  558. package/src/utils/safeUrl.ts +85 -0
@@ -208,6 +208,339 @@ describe('useLinkComponent — to prop', () => {
208
208
  });
209
209
  });
210
210
 
211
+ // =============================================================================
212
+ // href scheme rule
213
+ // =============================================================================
214
+
215
+ describe('useLinkComponent — href scheme rule', () => {
216
+ // Kept in a const so the JSX stays lint-clean — the test is ABOUT this URL.
217
+ const scriptUrl = 'javascript:alert(1)';
218
+
219
+ function HrefConsumer({href}: {href: string}) {
220
+ const LinkComponent = useLinkComponent();
221
+ return (
222
+ <LinkComponent href={href} data-testid="resolved-link">
223
+ Link
224
+ </LinkComponent>
225
+ );
226
+ }
227
+
228
+ function propSpy() {
229
+ return vi.fn(
230
+ ({
231
+ children,
232
+ ...props
233
+ }: {
234
+ children?: React.ReactNode;
235
+ [key: string]: unknown;
236
+ }) => (
237
+ <a data-testid="spy-link" href={props.href as string}>
238
+ {children}
239
+ </a>
240
+ ),
241
+ );
242
+ }
243
+
244
+ it('forwards ordinary hrefs to a custom component untouched', () => {
245
+ const spy = propSpy();
246
+ function SpyLink(props: {[key: string]: unknown}) {
247
+ return spy(props);
248
+ }
249
+ render(
250
+ <LinkProvider component={SpyLink}>
251
+ <HrefConsumer href="/docs/button" />
252
+ </LinkProvider>,
253
+ );
254
+ expect(spy).toHaveBeenCalledWith(
255
+ expect.objectContaining({href: '/docs/button', to: '/docs/button'}),
256
+ );
257
+ });
258
+
259
+ it('withholds a script-scheme href from a custom component (href and to alike)', () => {
260
+ const spy = propSpy();
261
+ function SpyLink(props: {[key: string]: unknown}) {
262
+ return spy(props);
263
+ }
264
+ render(
265
+ <LinkProvider component={SpyLink}>
266
+ {/* eslint-disable-next-line @eslint-react/dom-no-script-url -- the test proves this URL never navigates */}
267
+ <HrefConsumer href={scriptUrl} />
268
+ </LinkProvider>,
269
+ );
270
+ expect(spy).not.toHaveBeenCalled();
271
+ expect(screen.getByTestId('resolved-link')).not.toHaveAttribute('href');
272
+ });
273
+
274
+ it('an explicit `to` passes the same check instead of riding through ...rest', () => {
275
+ const spy = propSpy();
276
+ function SpyLink(props: {[key: string]: unknown}) {
277
+ return spy(props);
278
+ }
279
+ function ToConsumer({to}: {to: string}) {
280
+ const LinkComponent = useLinkComponent();
281
+ return (
282
+ <LinkComponent href="/fallback" to={to} data-testid="resolved-link">
283
+ Link
284
+ </LinkComponent>
285
+ );
286
+ }
287
+ render(
288
+ <LinkProvider component={SpyLink}>
289
+ {/* eslint-disable-next-line @eslint-react/dom-no-script-url -- the test proves this URL never reaches the router */}
290
+ <ToConsumer to={scriptUrl} />
291
+ </LinkProvider>,
292
+ );
293
+ expect(spy).not.toHaveBeenCalled();
294
+ expect(screen.getByTestId('resolved-link')).not.toHaveAttribute('href');
295
+ });
296
+
297
+ it('an ordinary explicit `to` keeps its precedence over href', () => {
298
+ const spy = propSpy();
299
+ function SpyLink(props: {[key: string]: unknown}) {
300
+ return spy(props);
301
+ }
302
+ function ToConsumer() {
303
+ const LinkComponent = useLinkComponent();
304
+ return (
305
+ <LinkComponent
306
+ href="/fallback"
307
+ to="/routed"
308
+ data-testid="resolved-link">
309
+ Link
310
+ </LinkComponent>
311
+ );
312
+ }
313
+ render(
314
+ <LinkProvider component={SpyLink}>
315
+ <ToConsumer />
316
+ </LinkProvider>,
317
+ );
318
+ expect(spy).toHaveBeenCalledWith(
319
+ expect.objectContaining({href: '/fallback', to: '/routed'}),
320
+ );
321
+ });
322
+ });
323
+
324
+ // =============================================================================
325
+ // structured router `to`
326
+ // =============================================================================
327
+
328
+ describe('useLinkComponent — structured router `to`', () => {
329
+ const scriptUrl = 'javascript:alert(1)';
330
+
331
+ function propSpy() {
332
+ return vi.fn(
333
+ ({
334
+ children,
335
+ ...props
336
+ }: {
337
+ children?: React.ReactNode;
338
+ [key: string]: unknown;
339
+ }) => (
340
+ <a data-testid="spy-link" href={props.href as string}>
341
+ {children}
342
+ </a>
343
+ ),
344
+ );
345
+ }
346
+
347
+ function ToConsumer({to}: {to: unknown}) {
348
+ const LinkComponent = useLinkComponent();
349
+ return (
350
+ <LinkComponent href="/fallback" to={to} data-testid="resolved-link">
351
+ Link
352
+ </LinkComponent>
353
+ );
354
+ }
355
+
356
+ it('preserves a structured destination for the router (no crash, same object)', () => {
357
+ const spy = propSpy();
358
+ function SpyLink(props: {[key: string]: unknown}) {
359
+ return spy(props);
360
+ }
361
+ const to = {pathname: '/docs', search: '?q=1', hash: '#top'};
362
+ render(
363
+ <LinkProvider component={SpyLink}>
364
+ <ToConsumer to={to} />
365
+ </LinkProvider>,
366
+ );
367
+ expect(spy).toHaveBeenCalledWith(
368
+ expect.objectContaining({href: '/fallback', to}),
369
+ );
370
+ expect(spy.mock.calls[0][0].to).toBe(to);
371
+ });
372
+
373
+ it('preserves a structured destination that carries no pathname', () => {
374
+ const spy = propSpy();
375
+ function SpyLink(props: {[key: string]: unknown}) {
376
+ return spy(props);
377
+ }
378
+ const to = {search: '?page=2'};
379
+ render(
380
+ <LinkProvider component={SpyLink}>
381
+ <ToConsumer to={to} />
382
+ </LinkProvider>,
383
+ );
384
+ expect(spy).toHaveBeenCalledWith(expect.objectContaining({to}));
385
+ });
386
+
387
+ it('withholds a structured destination whose pathname is a script scheme', () => {
388
+ const spy = propSpy();
389
+ function SpyLink(props: {[key: string]: unknown}) {
390
+ return spy(props);
391
+ }
392
+ render(
393
+ <LinkProvider component={SpyLink}>
394
+ <ToConsumer to={{pathname: scriptUrl}} />
395
+ </LinkProvider>,
396
+ );
397
+ expect(spy).not.toHaveBeenCalled();
398
+ expect(screen.getByTestId('resolved-link')).not.toHaveAttribute('href');
399
+ });
400
+
401
+ it('withholds a structured destination whose href is a script scheme', () => {
402
+ const spy = propSpy();
403
+ function SpyLink(props: {[key: string]: unknown}) {
404
+ return spy(props);
405
+ }
406
+ render(
407
+ <LinkProvider component={SpyLink}>
408
+ <ToConsumer to={{href: scriptUrl}} />
409
+ </LinkProvider>,
410
+ );
411
+ expect(spy).not.toHaveBeenCalled();
412
+ expect(screen.getByTestId('resolved-link')).not.toHaveAttribute('href');
413
+ });
414
+ });
415
+
416
+ // =============================================================================
417
+ // Shared native and router boundary
418
+ // =============================================================================
419
+
420
+ describe('useLinkComponent — rejected destinations are inert', () => {
421
+ function Consumer({
422
+ href,
423
+ to,
424
+ as,
425
+ onClick,
426
+ linkRef,
427
+ }: {
428
+ href?: unknown;
429
+ to?: unknown;
430
+ as?: LinkComponentType;
431
+ onClick?: () => void;
432
+ linkRef?: React.Ref<HTMLAnchorElement>;
433
+ }) {
434
+ const Component = useLinkComponent(as);
435
+ return (
436
+ <Component
437
+ href={href}
438
+ to={to}
439
+ ref={linkRef}
440
+ onClick={onClick}
441
+ className="consumer-class"
442
+ aria-label="Destination"
443
+ data-testid="destination">
444
+ Go
445
+ </Component>
446
+ );
447
+ }
448
+
449
+ for (const mode of ['native', 'provider', 'as'] as const) {
450
+ it.each([
451
+ {href: 'javascript:alert(1)'},
452
+ {href: 'vbscript:MsgBox(1)'},
453
+ {href: 'data:text/html,<b>x</b>'},
454
+ {href: 'java\nscript:alert(1)'},
455
+ {href: '/safe', to: 'javascript:alert(1)'},
456
+ {href: 'vbscript:MsgBox(1)', to: '/safe'},
457
+ {href: {protocol: 'javascript:', pathname: 'alert(1)'}},
458
+ {href: '/safe', to: {protocol: 'data:', pathname: 'text/html,x'}},
459
+ ])(`${mode}: rejects %j without calling a router`, props => {
460
+ const router = vi.fn(() => {
461
+ throw new Error('rejected router invoked');
462
+ });
463
+ const onClick = vi.fn();
464
+ const linkRef = vi.fn();
465
+ render(
466
+ <LinkProvider component={mode === 'provider' ? router : 'a'}>
467
+ <Consumer
468
+ {...props}
469
+ as={mode === 'as' ? router : undefined}
470
+ onClick={onClick}
471
+ linkRef={linkRef}
472
+ />
473
+ </LinkProvider>,
474
+ );
475
+ const node = screen.getByTestId('destination');
476
+ expect(router).not.toHaveBeenCalled();
477
+ expect(node.tagName).toBe('A');
478
+ expect(node).not.toHaveAttribute('href');
479
+ expect(node).not.toHaveAttribute('to');
480
+ expect(node).not.toHaveAttribute('aria-disabled');
481
+ expect(node).toHaveAttribute('aria-label', 'Destination');
482
+ expect(node).toHaveClass('consumer-class');
483
+ expect(linkRef.mock.calls[0][0]).toBe(node);
484
+ node.click();
485
+ expect(onClick).toHaveBeenCalledOnce();
486
+ });
487
+ }
488
+
489
+ it.each([
490
+ '/relative',
491
+ '#fragment',
492
+ 'https://example.com',
493
+ 'http://example.com',
494
+ 'mailto:a@example.com',
495
+ 'tel:+1234567890',
496
+ 'custom:document',
497
+ 'data:text/plain,download',
498
+ ])('preserves native href and download affordances for %s', href => {
499
+ function Download() {
500
+ const Component = useLinkComponent();
501
+ return (
502
+ <Component
503
+ href={href}
504
+ download="note.txt"
505
+ target="_blank"
506
+ rel="noopener"
507
+ data-testid="download">
508
+ Download
509
+ </Component>
510
+ );
511
+ }
512
+ render(<Download />);
513
+ const node = screen.getByTestId('download');
514
+ expect(node).toHaveAttribute('href', href);
515
+ expect(node).toHaveAttribute('download', 'note.txt');
516
+ expect(node).toHaveAttribute('target', '_blank');
517
+ expect(node).toHaveAttribute('rel', 'noopener');
518
+ });
519
+
520
+ it('preserves both structured prop identities and safe explicit precedence', () => {
521
+ const router = vi.fn<(props: {href?: unknown; to?: unknown}) => null>(
522
+ () => null,
523
+ );
524
+ const href = {
525
+ protocol: 'https:',
526
+ hostname: 'example.com',
527
+ pathname: '/docs',
528
+ };
529
+ const to = {pathname: '/next', search: '?q=1', hash: '#top'};
530
+ render(<Consumer as={router} href={href} to={to} />);
531
+ expect(router.mock.calls[0][0].href).toBe(href);
532
+ expect(router.mock.calls[0][0].to).toBe(to);
533
+ });
534
+
535
+ it('keeps the native node stable while accepted props update', () => {
536
+ const {rerender} = render(<Consumer href="/first" />);
537
+ const node = screen.getByTestId('destination');
538
+ rerender(<Consumer href="/second" />);
539
+ expect(screen.getByTestId('destination')).toBe(node);
540
+ expect(node).toHaveAttribute('href', '/second');
541
+ });
542
+ });
543
+
211
544
  // =============================================================================
212
545
  // LinkProvider
213
546
  // =============================================================================
@@ -4,89 +4,61 @@
4
4
 
5
5
  /**
6
6
  * @file useLinkComponent.ts
7
- * @input React use, useMemo, createElement, forwardRef, LinkContext, LinkComponentType
8
- * @output Exports useLinkComponent hook
9
- * @position Hook for resolving the link component in Astryx components
7
+ * @input Per-component override, LinkProvider context, and navigation props
8
+ * @output Stable link component with destination checks and custom-router `to` injection
9
+ * @position Shared native and custom link boundary; rejected destinations render
10
+ * as destination-less anchors without invoking a router.
10
11
  *
11
- * Resolution order: per-component `as` prop > LinkProvider context > native `<a>`.
12
- *
13
- * When the resolved component is a custom component (not native `<a>`),
14
- * wraps it to pass `to={href}` alongside `href`. This enables compatibility
15
- * with routers that use `to` (React Router, TanStack Router)
16
- * without requiring an adapter component.
17
- *
18
- * SYNC: When modified, update these files to stay in sync:
19
- * - /packages/core/src/Link/index.ts
20
- * - /packages/core/src/Link/Link.doc.mjs
12
+ * SYNC: Link.doc.mjs and LinkProvider.doc.mjs describe this boundary.
21
13
  */
22
14
 
23
15
  import {use, useMemo, createElement} from 'react';
24
16
  import {LinkContext} from './LinkContext';
17
+ import {isSafeDestination} from '../utils/safeUrl';
25
18
  import type {LinkComponentType} from './types';
26
19
 
27
- /**
28
- * Creates a wrapper component that passes both `href` and `to` props
29
- * to the underlying link component. This enables routers that use `to`
30
- * (React Router, TanStack Router) to work without an adapter.
31
- *
32
- * The wrapper is transparent: it forwards refs and all other props unchanged.
33
- * Native `<a>` elements ignore the unknown `to` prop harmlessly.
34
- */
35
- function createLinkWithTo(
36
- Component: LinkComponentType,
37
- ): LinkComponentType {
38
- function LinkWithTo({
20
+ function createSafeLink(Component: LinkComponentType): LinkComponentType {
21
+ function SafeLink({
39
22
  href,
23
+ to,
40
24
  ref,
41
25
  ...rest
42
26
  }: {
43
- href?: string;
44
- to?: string;
27
+ href?: unknown;
28
+ to?: unknown;
45
29
  ref?: React.Ref<unknown>;
46
30
  }) {
47
- return createElement(Component, {ref, href, to: href, ...rest});
31
+ // An undefined destination can crash a router or resolve to the current
32
+ // route. Reject the whole handoff, not just one prop or a fallback value.
33
+ if (!isSafeDestination(href) || !isSafeDestination(to)) {
34
+ return createElement('a', {ref, ...rest});
35
+ }
36
+ if (Component === 'a') {
37
+ return createElement('a', {ref, ...rest, href});
38
+ }
39
+ return createElement(Component, {ref, ...rest, href, to: to ?? href});
48
40
  }
49
- LinkWithTo.displayName = `LinkWithTo(${
41
+ SafeLink.displayName = `SafeLink(${
50
42
  typeof Component === 'string'
51
43
  ? Component
52
44
  : Component.displayName || Component.name || 'Component'
53
45
  })`;
54
- return LinkWithTo as LinkComponentType;
46
+ return SafeLink as LinkComponentType;
55
47
  }
56
48
 
57
49
  /**
58
- * Resolves the link component to use.
59
- *
60
- * Priority: `as` prop > `LinkProvider` context > native `<a>`.
61
- *
62
- * When the resolved component is a custom component (not the native `<a>`),
63
- * it is wrapped to receive both `href` and `to` props set to the same value.
64
- * This allows `to`-based routers (React Router, TanStack Router) to work
65
- * out of the box without a manual adapter.
66
- *
67
- * @param as - Per-component override. If provided, takes highest priority.
68
- * @returns The resolved link component (with `to` injection for custom components).
50
+ * Resolve `as`, then LinkProvider, then a native anchor. Both destination props
51
+ * are checked before rendering; accepted structured values retain identity.
52
+ * Custom components receive `to={href}` unless an explicit `to` is supplied.
69
53
  *
70
54
  * @example
71
55
  * ```
72
- * function MyComponent({ as }: { as?: LinkComponentType }) {
73
- * const LinkComponent = useLinkComponent(as);
74
- * return <LinkComponent href="/foo">Click me</LinkComponent>;
75
- * }
56
+ * const LinkComponent = useLinkComponent(as);
57
+ * return <LinkComponent href="/docs">Documentation</LinkComponent>;
76
58
  * ```
77
59
  */
78
- export function useLinkComponent(
79
- as?: LinkComponentType,
80
- ): LinkComponentType {
60
+ export function useLinkComponent(as?: LinkComponentType): LinkComponentType {
81
61
  const ctx = use(LinkContext);
82
62
  const resolved = as ?? ctx?.component ?? 'a';
83
-
84
- // Memoize the wrapper to maintain referential stability.
85
- // The wrapper is only created for custom components (not native <a>).
86
- return useMemo(() => {
87
- if (resolved === 'a') {
88
- return 'a';
89
- }
90
- return createLinkWithTo(resolved);
91
- }, [resolved]);
63
+ return useMemo(() => createSafeLink(resolved), [resolved]);
92
64
  }
@@ -41,6 +41,12 @@ export const docs = {
41
41
  description: 'Show dividers between items.',
42
42
  default: 'false',
43
43
  },
44
+ {
45
+ name: 'edgeCompensation',
46
+ type: "'inline'",
47
+ description:
48
+ "Compensate for item content inset on each inline edge by cancelling the smaller of each item's built-in horizontal inset and the container's published inline padding. The margin reads the same variable the items derive their inline padding from, so it tracks density and theme padding overrides automatically without pulling rows outside zero-padding or full-bleed surfaces. Use under a section heading to bring row text toward the heading text. Content aligns when container padding is at least the item inset; smaller padding leaves some inset uncompensated. Omit to leave item positions unchanged.",
49
+ },
44
50
  {
45
51
  name: 'header',
46
52
  type: 'ReactNode',
@@ -0,0 +1,200 @@
1
+ ---
2
+ schema_version: 3
3
+ template_version: 5
4
+ kind: component
5
+ id: component:List
6
+ authority: current
7
+ archive_reason: null
8
+ superseded_by: null
9
+ approved_by: cixzhang
10
+ approved_at: 2026-09-21
11
+ owners: [cixzhang]
12
+ review_triggers: [public-api, behavior, layout, theming]
13
+ verified_by:
14
+ [
15
+ packages/core/src/List/List.test.tsx,
16
+ packages/core/src/Item/Item.test.tsx,
17
+ packages/core/src/theme/derivedVarRegistry.test.ts,
18
+ packages/core/src/theme/generateThemeRules.test.ts,
19
+ apps/storybook/stories/List.stories.tsx,
20
+ scripts/check-knowledge.mjs,
21
+ ]
22
+ modules: []
23
+ families: []
24
+ design_specs: []
25
+ architecture:
26
+ [architecture:container-padding, architecture:public-component-api]
27
+ contributing: [contributing:api-conventions]
28
+ system_specs: [spec:AST-002/DEC-1, spec:AST-002/DEC-2]
29
+ ---
30
+
31
+ # List component contract
32
+
33
+ ## Contract at a glance
34
+
35
+ | Area | Contract |
36
+ | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
37
+ | Public contract | Add optional `edgeCompensation?: 'inline'`; omission preserves current layout. |
38
+ | Behavior | Every ListItem row moves its content toward a neutral host's inline content edges, reaching them when the host inset is at least the Item inset, while retaining Item-owned interaction padding. |
39
+ | End-user impact | List content can reduce or remove the extra inset beside its optional header and adjacent content without shrinking hover, press, selection, or focus paint. |
40
+ | Builder impact | Builders opt in only when the List is placed directly in an eligible container-padding host state. |
41
+ | Compatibility/readiness | Additive API; implementation and required real-browser evidence are pending. |
42
+ | Review checks | Reject header movement, unbounded negative margins, duplicated padding values, physical-edge logic, automatic opt-in, or a ListItem-level public prop. |
43
+ | Governing rules | `architecture:container-padding/INV9–INV11`; `spec:AST-002/DEC-1–DEC-2`. |
44
+
45
+ This table is a review projection; the body below is authoritative.
46
+
47
+ ## Intent
48
+
49
+ List owns collection-wide edge alignment because its optional header is not
50
+ horizontally inset while every ListItem retains Item's inline padding. The caller
51
+ knows whether the List is placed in a neutral host where these content lines
52
+ should align; List cannot derive that design relationship from its children.
53
+
54
+ ## Compatibility and migration
55
+
56
+ - Released default preserved: `yes`; omission keeps current List and ListItem geometry.
57
+ - Compatibility class: additive optional prop with no default behavior change.
58
+ - Controlled/uncontrolled behavior: not applicable.
59
+ - Migration decision: `component:List/DEC-1` and `component:List/DEC-2`.
60
+
61
+ Consumer migration instructions belong in consumer docs and release notes.
62
+
63
+ ## Ownership boundary
64
+
65
+ **Owns**
66
+
67
+ - The collection-wide `edgeCompensation` opt-in and its propagation to every
68
+ ListItem row.
69
+ - Keeping the optional header in place while moving row content toward its line.
70
+ - The supported relationship between ListItem row geometry and Item's normal
71
+ density- and theme-aware inline inset.
72
+
73
+ **Does not own / non-goals**
74
+
75
+ - Container padding values, nesting, or overlay resets — owned by
76
+ `architecture:container-padding`.
77
+ - A ListItem-level compensation prop, automatic host detection, full bleed, or
78
+ arbitrary margin controls.
79
+ - TabList or Button projections.
80
+
81
+ ## Public concepts
82
+
83
+ | Concept | Closed values or states | Meaning | Availability by variant/orientation/state | Default | Owner | Stability | Invalid-value behavior |
84
+ | ----------------- | ----------------------- | ----------------------------------------------------------------------------------------------------- | ------------------------------------------------------- | ------- | ---------------- | -------------------------------- | -------------------------------------------- |
85
+ | Edge compensation | omitted, `inline` | Move row content toward both logical host content edges while retaining row-local interaction padding | Every List density and marker style in an eligible host | omitted | `component:List` | accepted; implementation pending | Other values are rejected by the public type |
86
+
87
+ ## Behavioral and layout contract
88
+
89
+ | ID | Invariant | Basis | Status |
90
+ | --- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------- | ----------------------------------------- |
91
+ | FR1 | List MUST own `edgeCompensation?: 'inline'`; ListItem MUST NOT expose a parallel public prop. | `component:List/DEC-1` | Accepted contract; implementation pending |
92
+ | FR2 | Omission MUST preserve current header, list, and row geometry. Enabling `inline` MUST leave the optional header in place and apply one compensation policy to every ListItem, including non-interactive rows. | Owner direction | Accepted contract; implementation pending |
93
+ | FR3 | Each row MUST resolve each logical margin independently as the negative smaller of its own component-owned inline inset and the matching available host inset. Missing host inset MUST resolve to zero. Content reaches the host line only when the host inset is at least the row inset; otherwise it moves toward that line without crossing the host boundary. | `architecture:container-padding/INV9–INV10`; `component:List/DEC-2` | Accepted contract; implementation pending |
94
+ | FR4 | The retained row inset MUST come from the same private density- and theme-derived source as Item's applied inline padding. A consumer override of row padding through `xstyle`, `className`, or `style` is outside the alignment guarantee; `edgeCompensation` MUST NOT mirror or infer that arbitrary CSS. | Shared two-owner geometry contract | Accepted contract; implementation pending |
95
+ | FR5 | Compensation MUST preserve Item's hover, press, selection, focus, content, semantics, and hit target. It MUST NOT make the row a full-bleed surface or pull it past either available host edge. | Owner direction | Accepted contract; implementation pending |
96
+ | FR6 | The opt-in is valid only under a host state whose published inline inset matches its applied padding and whose relevant boundary is visually neutral. Current automatic LayoutContent/LayoutPanel states with known publication mismatch are not eligible until that conformance gap is fixed. | `architecture:container-padding/INV8–INV11` | Accepted contract; implementation pending |
97
+
98
+ ### Allowed variation
99
+
100
+ - Density and `item.paddingInline` theme overrides may change the retained inset;
101
+ compensation follows the same derived value.
102
+ - Container padding may be uniform, asymmetric, zero, or absent; each logical
103
+ edge resolves independently.
104
+ - Labels, descriptions, markers, start/end content, interactivity, and selection
105
+ may vary without changing the collection-wide alignment rule.
106
+
107
+ ### Representative states
108
+
109
+ | State | Required invariant | Allowed variation |
110
+ | ------------------------------------ | --------------------------------------------------------------------------------------- | --------------------------------------------------------- |
111
+ | Default/omitted | Header and every row keep current geometry. | Any current List content and density. |
112
+ | Host inset at least Item inset | Row content reaches the host content line; row-local paint keeps Item padding. | Uniform or asymmetric inline padding. |
113
+ | Smaller, zero, or missing host inset | The row moves only by the available inset and is never pulled beyond the host boundary. | Either logical edge may independently be smaller or zero. |
114
+ | LTR or RTL | Logical start/end mapping follows direction without changing the `inline` meaning. | Writing direction. |
115
+ | Themed Item padding | Row padding and compensation move together. | Supported `item.paddingInline` value. |
116
+
117
+ ### Transformation and precedence order
118
+
119
+ - **ORD1 — Host bound.** Read the matching published host inset for each selected
120
+ logical edge, with missing values falling back to zero.
121
+ - **ORD2 — Local inset.** Resolve Item's component-owned inline inset from density
122
+ and theme through the same private value that supplies applied padding.
123
+ - **ORD3 — Compensation.** Apply the negative minimum of those values to the row;
124
+ then compose supported consumer styles without claiming alignment for a consumer
125
+ override of row inline padding.
126
+
127
+ ### Performance and resources
128
+
129
+ - **PR1 — CSS geometry only.** Edge compensation MUST add no measurement,
130
+ observer, listener, state, Effect, or render pass.
131
+
132
+ ## Accessibility contract
133
+
134
+ - **AR1 — Interaction geometry remains local.** Compensation MUST NOT change the
135
+ interactive element, accessible role/name/state, focus target, tab order, or
136
+ hit target.
137
+ - **AR2 — Direction remains logical.** The same `inline` value MUST produce the
138
+ corresponding mirrored geometry under RTL without changing DOM order.
139
+
140
+ ## Design relationships
141
+
142
+ | Anatomy or state | Design requirement | Representation authority | Hierarchy role | Component contract |
143
+ | ---------------- | ----------------------------------------------------------------------------------------------------------- | ------------------------ | ------------------ | ------------------ |
144
+ | Optional header | Stays on the host content line and is not moved by row compensation. | `component:List/DEC-1` | Context | FR2 |
145
+ | ListItem row | Retains local interaction paint and hit-target padding. | Item composition | Supporting surface | FR3–FR5 |
146
+ | Row content | Moves toward each host content line and reaches it only when the host inset can fully absorb the row inset. | `component:List/DEC-2` | Prominent | FR2–FR5 |
147
+
148
+ ## Family and system relationships
149
+
150
+ - `architecture:container-padding` owns host publication, the shared
151
+ `edgeCompensation` vocabulary, eligibility, logical-edge semantics, and overlay
152
+ reset.
153
+ - `architecture:public-component-api` and `spec:AST-002` own public API admission.
154
+ - Item remains the implementation owner of row padding and interaction paint;
155
+ List owns only the collection-level opt-in and row application.
156
+
157
+ ## Verification map
158
+
159
+ | Contract | Verification | Representative states | Mutation or failure expectation | Audit section |
160
+ | ---------------- | ------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- | ------------------- |
161
+ | FR1–FR4 | List, Item, and theme-registry tests | omitted/enabled; all densities; theme override; static/interactive rows | Removing context propagation, duplicating a padding value, or using one host edge for both margins fails | `audit:List/layout` |
162
+ | FR2–FR6, AR1–AR2 | Real-browser List geometry evidence | header; LTR/RTL; uniform/asymmetric/zero/missing host inset; host inset smaller/equal/larger than Item inset; hover/selection/focus | Header movement, over-pull, wrong bounded movement, or lost row-local paint fails | `audit:List/visual` |
163
+ | Documentation | `List.doc.mjs`, Storybook, and `scripts/check-knowledge.mjs` | public prop/default and representative composition | Missing or stale contract, docs, or example fails | `audit:List/docs` |
164
+
165
+ ## Decision log
166
+
167
+ ### DEC-1 — List owns the collection-wide opt-in
168
+
169
+ **Reference:** `component:List/DEC-1`
170
+
171
+ **Decider:** cixzhang, 2026-09-21
172
+
173
+ The List header and ListItem rows otherwise begin on different content lines.
174
+ Only the caller knows when the containing composition requires alignment, while
175
+ List can apply one consistent decision to every row without exposing Item-level
176
+ configuration.
177
+
178
+ ### DEC-2 — Compensation is row-local and bounded
179
+
180
+ **Reference:** `component:List/DEC-2`
181
+
182
+ **Decider:** cixzhang, 2026-09-21
183
+
184
+ Each row cancels no more than its own component-owned inline inset or the
185
+ matching host inset. When the host inset is at least the row inset, content
186
+ reaches the host content line. When it is smaller, content moves toward that line
187
+ without pulling row paint beyond the host boundary.
188
+
189
+ Rejected: `isFullBleed`, because the row may stop inside the host padding and the
190
+ name would promise outer-edge bleed that the component does not provide.
191
+
192
+ ## Open questions
193
+
194
+ None.
195
+
196
+ ## Content boundary
197
+
198
+ This claim-scoped contract does not duplicate List's complete prop table,
199
+ container-padding internals, consumer examples, implementation steps, or future
200
+ TabList/Button projections. Those remain with their named owners.