@astryxdesign/core 0.6.2 → 0.6.3-canary.0899f21

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 (549) 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.js +1 -1
  36. package/dist/Chat/index.d.ts +1 -1
  37. package/dist/Chat/index.d.ts.map +1 -1
  38. package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
  39. package/dist/CheckboxInput/CheckboxInput.js +12 -2
  40. package/dist/Citation/Citation.d.ts +1 -1
  41. package/dist/Citation/Citation.d.ts.map +1 -1
  42. package/dist/Citation/Citation.js +3 -2
  43. package/dist/CodeBlock/highlightRanges.d.ts.map +1 -1
  44. package/dist/CodeBlock/highlightRanges.js +14 -3
  45. package/dist/Collapsible/Collapsible.d.ts.map +1 -1
  46. package/dist/Collapsible/Collapsible.js +6 -1
  47. package/dist/CommandPalette/CommandPaletteInput.d.ts.map +1 -1
  48. package/dist/CommandPalette/CommandPaletteInput.js +1 -1
  49. package/dist/ComplexSelector/ComplexSelector.d.ts.map +1 -1
  50. package/dist/ComplexSelector/ComplexSelector.js +1 -1
  51. package/dist/DateInput/DateInput.d.ts.map +1 -1
  52. package/dist/DateInput/DateInput.js +4 -4
  53. package/dist/DateInput/NativeDateField.d.ts.map +1 -1
  54. package/dist/DateInput/NativeDateField.js +16 -16
  55. package/dist/DateInput/TouchDateField.d.ts.map +1 -1
  56. package/dist/DateInput/TouchDateField.js +2 -2
  57. package/dist/DateRangeInput/DateRangeInput.d.ts.map +1 -1
  58. package/dist/DateRangeInput/DateRangeInput.js +4 -4
  59. package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
  60. package/dist/DateTimeInput/DateTimeInput.js +8 -8
  61. package/dist/DateTimeInput/TouchDateTimeField.d.ts.map +1 -1
  62. package/dist/DateTimeInput/TouchDateTimeField.js +4 -4
  63. package/dist/DateTimeInput/nativePickerSegmentStyles.d.ts +9 -3
  64. package/dist/DateTimeInput/nativePickerSegmentStyles.d.ts.map +1 -1
  65. package/dist/DateTimeInput/nativePickerSegmentStyles.js +3 -3
  66. package/dist/Dialog/DialogHeader.d.ts +8 -1
  67. package/dist/Dialog/DialogHeader.d.ts.map +1 -1
  68. package/dist/Dialog/DialogHeader.js +13 -4
  69. package/dist/Field/InputClearButton.d.ts +4 -2
  70. package/dist/Field/InputClearButton.d.ts.map +1 -1
  71. package/dist/Field/InputClearButton.js +4 -2
  72. package/dist/Field/PanelSearchInput.d.ts.map +1 -1
  73. package/dist/Field/PanelSearchInput.js +1 -1
  74. package/dist/FileInput/FileInput.d.ts.map +1 -1
  75. package/dist/FileInput/FileInput.js +7 -7
  76. package/dist/Icon/Icon.d.ts +6 -31
  77. package/dist/Icon/Icon.d.ts.map +1 -1
  78. package/dist/Icon/Icon.js +8 -71
  79. package/dist/Icon/IconDefaultSizeContext.d.ts +4 -0
  80. package/dist/Icon/IconDefaultSizeContext.d.ts.map +1 -0
  81. package/dist/Icon/IconDefaultSizeContext.js +18 -0
  82. package/dist/Icon/IconSize.stylex.d.ts +65 -0
  83. package/dist/Icon/IconSize.stylex.d.ts.map +1 -0
  84. package/dist/Icon/IconSize.stylex.js +75 -0
  85. package/dist/Item/Item.d.ts +1 -1
  86. package/dist/Item/Item.d.ts.map +1 -1
  87. package/dist/Item/Item.js +4 -3
  88. package/dist/Kbd/Kbd.d.ts +5 -3
  89. package/dist/Kbd/Kbd.d.ts.map +1 -1
  90. package/dist/Kbd/Kbd.js +36 -42
  91. package/dist/Link/Link.d.ts.map +1 -1
  92. package/dist/Link/Link.js +6 -2
  93. package/dist/Link/useLinkComponent.d.ts +5 -15
  94. package/dist/Link/useLinkComponent.d.ts.map +1 -1
  95. package/dist/Link/useLinkComponent.js +34 -51
  96. package/dist/List/List.d.ts +19 -1
  97. package/dist/List/List.d.ts.map +1 -1
  98. package/dist/List/List.js +9 -2
  99. package/dist/List/ListContext.d.ts +1 -0
  100. package/dist/List/ListContext.d.ts.map +1 -1
  101. package/dist/List/ListContext.js +2 -1
  102. package/dist/List/ListItem.d.ts +1 -1
  103. package/dist/List/ListItem.d.ts.map +1 -1
  104. package/dist/List/ListItem.js +9 -2
  105. package/dist/Markdown/Markdown.d.ts +8 -2
  106. package/dist/Markdown/Markdown.d.ts.map +1 -1
  107. package/dist/Markdown/Markdown.fr23.bench.d.ts +2 -0
  108. package/dist/Markdown/Markdown.fr23.bench.d.ts.map +1 -0
  109. package/dist/Markdown/Markdown.fr23.bench.js +150 -0
  110. package/dist/Markdown/Markdown.fr23.sampling.d.ts +2 -0
  111. package/dist/Markdown/Markdown.fr23.sampling.d.ts.map +1 -0
  112. package/dist/Markdown/Markdown.fr23.sampling.js +43 -0
  113. package/dist/Markdown/Markdown.js +262 -109
  114. package/dist/Markdown/ast.d.ts +162 -0
  115. package/dist/Markdown/ast.d.ts.map +1 -0
  116. package/dist/Markdown/ast.js +74 -0
  117. package/dist/Markdown/index.d.ts +5 -3
  118. package/dist/Markdown/index.d.ts.map +1 -1
  119. package/dist/Markdown/index.js +3 -2
  120. package/dist/Markdown/parser/index.d.ts +10 -0
  121. package/dist/Markdown/parser/index.d.ts.map +1 -0
  122. package/dist/Markdown/parser/index.js +10 -0
  123. package/dist/Markdown/parser.d.ts +126 -58
  124. package/dist/Markdown/parser.d.ts.map +1 -1
  125. package/dist/Markdown/parser.js +716 -167
  126. package/dist/Markdown/plugins/frontmatter.d.ts +32 -0
  127. package/dist/Markdown/plugins/frontmatter.d.ts.map +1 -0
  128. package/dist/Markdown/plugins/frontmatter.js +132 -0
  129. package/dist/Markdown/plugins/index.d.ts +17 -0
  130. package/dist/Markdown/plugins/index.d.ts.map +1 -0
  131. package/dist/Markdown/plugins/index.js +14 -0
  132. package/dist/Markdown/plugins/protocol.d.ts +191 -0
  133. package/dist/Markdown/plugins/protocol.d.ts.map +1 -0
  134. package/dist/Markdown/plugins/protocol.js +957 -0
  135. package/dist/Markdown/plugins/semanticFence.d.ts +36 -0
  136. package/dist/Markdown/plugins/semanticFence.d.ts.map +1 -0
  137. package/dist/Markdown/plugins/semanticFence.js +213 -0
  138. package/dist/Markdown/plugins/softBreaks.d.ts +3 -0
  139. package/dist/Markdown/plugins/softBreaks.d.ts.map +1 -0
  140. package/dist/Markdown/plugins/softBreaks.js +172 -0
  141. package/dist/Markdown/plugins/sourceDecoration.d.ts +76 -0
  142. package/dist/Markdown/plugins/sourceDecoration.d.ts.map +1 -0
  143. package/dist/Markdown/plugins/sourceDecoration.js +393 -0
  144. package/dist/Markdown/plugins/textTransform.d.ts +30 -0
  145. package/dist/Markdown/plugins/textTransform.d.ts.map +1 -0
  146. package/dist/Markdown/plugins/textTransform.js +374 -0
  147. package/dist/Markdown/remark.d.ts +196 -0
  148. package/dist/Markdown/remark.d.ts.map +1 -0
  149. package/dist/Markdown/remark.js +1062 -0
  150. package/dist/Markdown/url.d.ts +6 -0
  151. package/dist/Markdown/url.d.ts.map +1 -0
  152. package/dist/Markdown/url.js +31 -0
  153. package/dist/Markdown/utils.d.ts +4 -2
  154. package/dist/Markdown/utils.d.ts.map +1 -1
  155. package/dist/Markdown/utils.js +2 -1
  156. package/dist/MetadataList/MetadataList.js +3 -3
  157. package/dist/NumberInput/NumberInput.d.ts.map +1 -1
  158. package/dist/NumberInput/NumberInput.js +4 -4
  159. package/dist/Outline/index.d.ts +1 -0
  160. package/dist/Outline/index.d.ts.map +1 -1
  161. package/dist/Outline/parseOutlineFromMarkdown.d.ts +7 -1
  162. package/dist/Outline/parseOutlineFromMarkdown.d.ts.map +1 -1
  163. package/dist/Outline/parseOutlineFromMarkdown.js +14 -8
  164. package/dist/Outline/useOutlineFromMarkdown.d.ts +3 -1
  165. package/dist/Outline/useOutlineFromMarkdown.d.ts.map +1 -1
  166. package/dist/Outline/useOutlineFromMarkdown.js +8 -3
  167. package/dist/PowerSearch/PowerSearchEditPopover.d.ts.map +1 -1
  168. package/dist/PowerSearch/PowerSearchEditPopover.js +46 -30
  169. package/dist/PowerSearch/PowerSearchValueEditor.d.ts.map +1 -1
  170. package/dist/PowerSearch/PowerSearchValueEditor.js +5 -0
  171. package/dist/ProgressBar/ProgressBar.d.ts.map +1 -1
  172. package/dist/ProgressBar/ProgressBar.js +36 -31
  173. package/dist/RadioList/RadioListItem.d.ts.map +1 -1
  174. package/dist/RadioList/RadioListItem.js +13 -1
  175. package/dist/ScrollableArea/ScrollableArea.d.ts +1 -0
  176. package/dist/ScrollableArea/ScrollableArea.d.ts.map +1 -1
  177. package/dist/ScrollableArea/ScrollableArea.js +1 -0
  178. package/dist/SegmentedControl/SegmentedControlItem.d.ts.map +1 -1
  179. package/dist/SegmentedControl/SegmentedControlItem.js +5 -5
  180. package/dist/Selector/Selector.d.ts +9 -7
  181. package/dist/Selector/Selector.d.ts.map +1 -1
  182. package/dist/Selector/Selector.js +16 -7
  183. package/dist/SideNav/SideNav.d.ts +2 -1
  184. package/dist/SideNav/SideNav.d.ts.map +1 -1
  185. package/dist/SideNav/SideNav.js +9 -3
  186. package/dist/Slider/Slider.d.ts.map +1 -1
  187. package/dist/Slider/Slider.js +28 -12
  188. package/dist/Spinner/Spinner.d.ts.map +1 -1
  189. package/dist/Spinner/Spinner.js +23 -7
  190. package/dist/Switch/Switch.d.ts.map +1 -1
  191. package/dist/Switch/Switch.js +11 -0
  192. package/dist/TabList/Tab.d.ts +1 -1
  193. package/dist/TabList/Tab.d.ts.map +1 -1
  194. package/dist/TabList/Tab.js +20 -7
  195. package/dist/Table/Table.d.ts +1 -0
  196. package/dist/Table/Table.d.ts.map +1 -1
  197. package/dist/Table/Table.js +29 -13
  198. package/dist/TextArea/TextArea.d.ts.map +1 -1
  199. package/dist/TextArea/TextArea.js +1 -1
  200. package/dist/TextInput/TextInput.d.ts.map +1 -1
  201. package/dist/TextInput/TextInput.js +2 -2
  202. package/dist/TimeInput/TimeInput.d.ts.map +1 -1
  203. package/dist/TimeInput/TimeInput.js +4 -4
  204. package/dist/Timer/Timer.d.ts +49 -0
  205. package/dist/Timer/Timer.d.ts.map +1 -0
  206. package/dist/Timer/Timer.js +158 -0
  207. package/dist/Timer/index.d.ts +9 -0
  208. package/dist/Timer/index.d.ts.map +1 -0
  209. package/dist/Timer/index.js +10 -0
  210. package/dist/ToggleButton/ToggleButton.d.ts +9 -6
  211. package/dist/ToggleButton/ToggleButton.d.ts.map +1 -1
  212. package/dist/ToggleButton/ToggleButton.js +15 -27
  213. package/dist/TopNav/TopNavMenu.d.ts +1 -1
  214. package/dist/TopNav/TopNavMenu.d.ts.map +1 -1
  215. package/dist/TopNav/TopNavMenu.js +3 -2
  216. package/dist/TreeList/TreeList.d.ts.map +1 -1
  217. package/dist/TreeList/TreeList.js +3 -0
  218. package/dist/TreeList/TreeListItem.d.ts +6 -1
  219. package/dist/TreeList/TreeListItem.d.ts.map +1 -1
  220. package/dist/TreeList/TreeListItem.js +12 -2
  221. package/dist/TreeList/TreeListTypes.d.ts +8 -1
  222. package/dist/TreeList/TreeListTypes.d.ts.map +1 -1
  223. package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
  224. package/dist/Typeahead/BaseTypeahead.js +16 -7
  225. package/dist/astryx.css +27 -11
  226. package/dist/hooks/scrollKeyboardDelegation.d.ts +3 -0
  227. package/dist/hooks/scrollKeyboardDelegation.d.ts.map +1 -0
  228. package/dist/hooks/scrollKeyboardDelegation.js +146 -0
  229. package/dist/hooks/useClickableContainer.d.ts +10 -1
  230. package/dist/hooks/useClickableContainer.d.ts.map +1 -1
  231. package/dist/hooks/useClickableContainer.js +15 -4
  232. package/dist/hooks/useScrollableArea.d.ts +6 -2
  233. package/dist/hooks/useScrollableArea.d.ts.map +1 -1
  234. package/dist/hooks/useScrollableArea.js +17 -5
  235. package/dist/index.d.ts +1 -0
  236. package/dist/index.d.ts.map +1 -1
  237. package/dist/index.js +3 -0
  238. package/dist/theme/declarationBoundary.d.ts +17 -0
  239. package/dist/theme/declarationBoundary.d.ts.map +1 -0
  240. package/dist/theme/declarationBoundary.js +343 -0
  241. package/dist/theme/derivedVarRegistry.d.ts.map +1 -1
  242. package/dist/theme/derivedVarRegistry.js +4 -0
  243. package/dist/theme/generateThemeRules.d.ts +13 -7
  244. package/dist/theme/generateThemeRules.d.ts.map +1 -1
  245. package/dist/theme/generateThemeRules.js +140 -34
  246. package/dist/utils/interactionOverlay.stylex.d.ts +8 -0
  247. package/dist/utils/interactionOverlay.stylex.d.ts.map +1 -1
  248. package/dist/utils/interactionOverlay.stylex.js +9 -0
  249. package/dist/utils/safeUrl.d.ts +11 -0
  250. package/dist/utils/safeUrl.d.ts.map +1 -0
  251. package/dist/utils/safeUrl.js +78 -0
  252. package/locales/af-ZA.json +530 -1
  253. package/locales/ar-SA.json +490 -1
  254. package/locales/ca-ES.json +514 -1
  255. package/locales/cs-CZ.json +498 -1
  256. package/locales/da-DK.json +522 -1
  257. package/locales/de-DE.json +522 -1
  258. package/locales/el-GR.json +498 -1
  259. package/locales/es-ES.json +510 -1
  260. package/locales/fi-FI.json +498 -1
  261. package/locales/fr-FR.json +534 -1
  262. package/locales/he-IL.json +498 -1
  263. package/locales/hu-HU.json +498 -1
  264. package/locales/it-IT.json +510 -1
  265. package/locales/ja-JP.json +490 -1
  266. package/locales/ko-KR.json +498 -1
  267. package/locales/nl-NL.json +534 -1
  268. package/locales/no-NO.json +510 -1
  269. package/locales/pl-PL.json +506 -1
  270. package/locales/pt-BR.json +506 -1
  271. package/locales/pt-PT.json +506 -1
  272. package/locales/ro-RO.json +506 -1
  273. package/locales/ru-RU.json +498 -1
  274. package/locales/sr-SP.json +498 -1
  275. package/locales/sv-SE.json +502 -1
  276. package/locales/tr-TR.json +498 -1
  277. package/locales/uk-UA.json +498 -1
  278. package/locales/vi-VN.json +502 -1
  279. package/locales/zh-CN.json +490 -1
  280. package/locales/zh-TW.json +490 -1
  281. package/package.json +24 -4
  282. package/src/BottomSheet/BottomSheet.doc.mjs +8 -1
  283. package/src/BottomSheet/BottomSheet.spec.md +46 -20
  284. package/src/BottomSheet/BottomSheet.test.tsx +6 -3
  285. package/src/BottomSheet/BottomSheet.tsx +3 -1
  286. package/src/BottomSheet/BottomSheetKeyboard.test.tsx +195 -0
  287. package/src/BottomSheet/BottomSheetPanel.test.tsx +11 -1
  288. package/src/BottomSheet/BottomSheetPanel.tsx +49 -16
  289. package/src/BottomSheet/__tests__/BottomSheetKeyboard.a11y.browser.spec.ts +344 -0
  290. package/src/Button/Button.doc.mjs +5 -5
  291. package/src/Button/Button.tsx +15 -13
  292. package/src/Button/__tests__/Button.a11y.chromium.spec.ts +17 -1
  293. package/src/Button/__tests__/Button.a11y.known-failures.ts +0 -29
  294. package/src/Button/__tests__/Button.a11y.renders.tsx +9 -2
  295. package/src/Button/__tests__/Button.a11y.states.ts +9 -0
  296. package/src/Chat/ChatComposer.doc.mjs +14 -0
  297. package/src/Chat/ChatComposer.spec.md +230 -0
  298. package/src/Chat/ChatComposer.test.tsx +57 -0
  299. package/src/Chat/ChatComposer.tsx +21 -9
  300. package/src/Chat/ChatComposerDrawer.doc.mjs +63 -5
  301. package/src/Chat/ChatComposerDrawer.spec.md +267 -0
  302. package/src/Chat/ChatComposerDrawer.test.tsx +81 -12
  303. package/src/Chat/ChatComposerDrawer.tsx +38 -9
  304. package/src/Chat/ChatComposerInput.doc.mjs +114 -26
  305. package/src/Chat/ChatComposerInput.spec.md +185 -0
  306. package/src/Chat/ChatComposerInput.test.tsx +113 -3
  307. package/src/Chat/ChatComposerInput.tsx +78 -13
  308. package/src/Chat/ChatComposerTokenElement.spec.md +152 -0
  309. package/src/Chat/ChatComposerTokenElement.test.tsx +85 -0
  310. package/src/Chat/ChatDictationButton.doc.mjs +3 -3
  311. package/src/Chat/ChatDictationButton.spec.md +160 -0
  312. package/src/Chat/ChatDictationButton.test.tsx +92 -27
  313. package/src/Chat/ChatDictationButton.tsx +8 -6
  314. package/src/Chat/ChatLayout.doc.mjs +12 -4
  315. package/src/Chat/ChatLayout.spec.md +271 -0
  316. package/src/Chat/ChatLayout.test.tsx +28 -0
  317. package/src/Chat/ChatLayout.tsx +19 -0
  318. package/src/Chat/ChatLayoutScrollButton.spec.md +338 -0
  319. package/src/Chat/ChatLayoutScrollButton.test.tsx +31 -0
  320. package/src/Chat/ChatLayoutScrollButton.tsx +31 -14
  321. package/src/Chat/ChatMessageBubble.doc.mjs +28 -15
  322. package/src/Chat/ChatMessageBubble.spec.md +263 -0
  323. package/src/Chat/ChatMessageBubble.test.tsx +93 -0
  324. package/src/Chat/ChatMessageBubble.tsx +8 -3
  325. package/src/Chat/ChatMessageList.spec.md +222 -0
  326. package/src/Chat/ChatMessageList.test.tsx +64 -2
  327. package/src/Chat/ChatMessageList.tsx +4 -3
  328. package/src/Chat/ChatMessageMetadata.doc.mjs +57 -3
  329. package/src/Chat/ChatMessageMetadata.spec.md +220 -0
  330. package/src/Chat/ChatMessageMetadata.test.tsx +34 -0
  331. package/src/Chat/ChatMessageMetadata.tsx +13 -13
  332. package/src/Chat/ChatSendButton.test.tsx +1 -4
  333. package/src/Chat/ChatSendButton.tsx +1 -1
  334. package/src/Chat/__tests__/ChatLayoutScrollButton.a11y.chromium.spec.ts +514 -0
  335. package/src/Chat/__tests__/ChatLayoutScrollButtonSurface.a11y.chromium.spec.ts +740 -0
  336. package/src/Chat/__tests__/ChatMessageBubble.a11y.chromium.spec.ts +457 -0
  337. package/src/Chat/__tests__/ChatMessageBubble.auditProvenance.test.ts +91 -0
  338. package/src/Chat/__tests__/ChatMessageBubble.auditProvenance.ts +91 -0
  339. package/src/Chat/__tests__/ChatMessageList.a11y.chromium.spec.ts +456 -0
  340. package/src/Chat/__tests__/ChatMessageMetadata.a11y.chromium.spec.ts +413 -0
  341. package/src/Chat/index.ts +1 -0
  342. package/src/CheckboxInput/CheckboxInput.doc.mjs +11 -0
  343. package/src/CheckboxInput/CheckboxInput.test.tsx +34 -0
  344. package/src/CheckboxInput/CheckboxInput.tsx +21 -1
  345. package/src/Citation/Citation.doc.mjs +2 -2
  346. package/src/Citation/Citation.test.tsx +17 -0
  347. package/src/Citation/Citation.tsx +4 -2
  348. package/src/ClickableCard/ClickableCard.doc.mjs +2 -2
  349. package/src/ClickableCard/ClickableCard.test.tsx +102 -5
  350. package/src/CodeBlock/highlightRanges.test.ts +143 -1
  351. package/src/CodeBlock/highlightRanges.ts +15 -5
  352. package/src/Collapsible/Collapsible.doc.mjs +11 -0
  353. package/src/Collapsible/Collapsible.test.tsx +21 -0
  354. package/src/Collapsible/Collapsible.tsx +5 -0
  355. package/src/CommandPalette/CommandPaletteInput.tsx +6 -1
  356. package/src/ComplexSelector/ComplexSelector.tsx +6 -1
  357. package/src/DateInput/DateInput.tsx +6 -1
  358. package/src/DateInput/NativeDateField.tsx +24 -8
  359. package/src/DateInput/TouchDateField.tsx +6 -2
  360. package/src/DateRangeInput/DateRangeInput.tsx +6 -1
  361. package/src/DateTimeInput/DateTimeInput.tsx +6 -1
  362. package/src/DateTimeInput/TouchDateTimeField.tsx +6 -1
  363. package/src/DateTimeInput/nativePickerSegmentStyles.ts +24 -8
  364. package/src/Dialog/Dialog.doc.mjs +2 -0
  365. package/src/Dialog/DialogHeader.doc.mjs +22 -0
  366. package/src/Dialog/DialogHeader.test.tsx +167 -3
  367. package/src/Dialog/DialogHeader.tsx +37 -6
  368. package/src/Dialog/modules/DialogHeader.spec.md +101 -42
  369. package/src/DropdownMenu/DropdownMenu.test.tsx +5 -2
  370. package/src/Field/InputClearButton.test.tsx +17 -3
  371. package/src/Field/InputClearButton.tsx +5 -2
  372. package/src/Field/PanelSearchInput.tsx +5 -2
  373. package/src/FieldStatus/__tests__/StatusMessage.a11y.states.ts +1 -1
  374. package/src/FileInput/FileInput.tsx +11 -2
  375. package/src/Icon/Icon.doc.mjs +7 -5
  376. package/src/Icon/Icon.spec.md +81 -40
  377. package/src/Icon/Icon.test.tsx +6 -3
  378. package/src/Icon/Icon.tsx +20 -68
  379. package/src/Icon/IconDefaultSizeContext.ts +23 -0
  380. package/src/Icon/IconSize.stylex.ts +70 -0
  381. package/src/IconButton/IconButton.doc.mjs +3 -3
  382. package/src/IconButton/IconButton.test.tsx +40 -0
  383. package/src/Item/Item.doc.mjs +6 -2
  384. package/src/Item/Item.tsx +9 -3
  385. package/src/Kbd/Kbd.doc.mjs +3 -3
  386. package/src/Kbd/Kbd.test.tsx +57 -1
  387. package/src/Kbd/Kbd.tsx +57 -37
  388. package/src/Layout/LayoutFooter.doc.mjs +17 -0
  389. package/src/Link/Link.doc.mjs +17 -4
  390. package/src/Link/Link.test.tsx +24 -0
  391. package/src/Link/Link.tsx +5 -0
  392. package/src/Link/LinkProvider.doc.mjs +2 -2
  393. package/src/Link/__tests__/Link.navigation.a11y.chromium.spec.ts +261 -0
  394. package/src/Link/useLinkComponent.test.tsx +333 -0
  395. package/src/Link/useLinkComponent.ts +29 -57
  396. package/src/List/List.doc.mjs +6 -0
  397. package/src/List/List.spec.md +200 -0
  398. package/src/List/List.test.tsx +117 -1
  399. package/src/List/List.tsx +22 -2
  400. package/src/List/ListContext.tsx +3 -1
  401. package/src/List/ListItem.doc.mjs +2 -2
  402. package/src/List/ListItem.source-build.test.mjs +41 -0
  403. package/src/List/ListItem.tsx +26 -2
  404. package/src/Markdown/Markdown.doc.mjs +300 -2
  405. package/src/Markdown/Markdown.fr23.bench.ts +174 -0
  406. package/src/Markdown/Markdown.fr23.perf.test.ts +249 -0
  407. package/src/Markdown/Markdown.fr23.sampling.ts +49 -0
  408. package/src/Markdown/Markdown.helpers.perf.test.ts +489 -0
  409. package/src/Markdown/Markdown.public.test.ts +130 -3
  410. package/src/Markdown/Markdown.renderBoundary.test.tsx +72 -0
  411. package/src/Markdown/Markdown.spec.md +203 -45
  412. package/src/Markdown/Markdown.test.tsx +63 -0
  413. package/src/Markdown/Markdown.tsx +414 -135
  414. package/src/Markdown/ast.test.ts +307 -0
  415. package/src/Markdown/ast.ts +337 -0
  416. package/src/Markdown/incremental.test.ts +12 -0
  417. package/src/Markdown/index.ts +37 -1
  418. package/src/Markdown/modules/remark.spec.md +269 -0
  419. package/src/Markdown/modules/softBreaks.spec.md +137 -0
  420. package/src/Markdown/parser/index.ts +64 -0
  421. package/src/Markdown/parser.public.test.ts +103 -0
  422. package/src/Markdown/parser.test.ts +20 -0
  423. package/src/Markdown/parser.ts +1180 -235
  424. package/src/Markdown/plugins/frontmatter.test.tsx +125 -0
  425. package/src/Markdown/plugins/frontmatter.ts +198 -0
  426. package/src/Markdown/plugins/index.ts +54 -0
  427. package/src/Markdown/plugins/protocol.test.tsx +1022 -0
  428. package/src/Markdown/plugins/protocol.ts +1618 -0
  429. package/src/Markdown/plugins/semanticFence.test.tsx +546 -0
  430. package/src/Markdown/plugins/semanticFence.ts +424 -0
  431. package/src/Markdown/plugins/softBreaks.test.tsx +172 -0
  432. package/src/Markdown/plugins/softBreaks.ts +169 -0
  433. package/src/Markdown/plugins/sourceDecoration.test.tsx +799 -0
  434. package/src/Markdown/plugins/sourceDecoration.ts +539 -0
  435. package/src/Markdown/plugins/textTransform.test.ts +467 -0
  436. package/src/Markdown/plugins/textTransform.ts +554 -0
  437. package/src/Markdown/remark.test.tsx +1877 -0
  438. package/src/Markdown/remark.ts +1574 -0
  439. package/src/Markdown/url.ts +36 -0
  440. package/src/Markdown/utils.ts +13 -1
  441. package/src/MetadataList/MetadataList.test.tsx +4 -2
  442. package/src/MetadataList/MetadataList.tsx +3 -3
  443. package/src/MultiSelector/MultiSelector.test.tsx +1 -35
  444. package/src/MultiSelector/__tests__/MultiSelector.listbox.a11y.test.tsx +128 -0
  445. package/src/NumberInput/NumberInput.tsx +6 -1
  446. package/src/Outline/Outline.doc.mjs +7 -5
  447. package/src/Outline/Outline.spec.md +1 -1
  448. package/src/Outline/index.ts +1 -0
  449. package/src/Outline/modules/parseOutlineFromMarkdown.spec.md +142 -0
  450. package/src/Outline/parseOutlineFromMarkdown.ts +40 -10
  451. package/src/Outline/useOutlineFromMarkdown.ts +15 -3
  452. package/src/PowerSearch/PowerSearch.doc.mjs +2 -2
  453. package/src/PowerSearch/PowerSearchEditPopover.test.tsx +150 -1
  454. package/src/PowerSearch/PowerSearchEditPopover.tsx +51 -28
  455. package/src/PowerSearch/PowerSearchValueEditor.test.tsx +51 -0
  456. package/src/PowerSearch/PowerSearchValueEditor.tsx +4 -0
  457. package/src/ProgressBar/ProgressBar.test.tsx +29 -11
  458. package/src/ProgressBar/ProgressBar.tsx +59 -72
  459. package/src/RadioList/RadioList.doc.mjs +11 -0
  460. package/src/RadioList/RadioList.test.tsx +32 -0
  461. package/src/RadioList/RadioListItem.tsx +25 -1
  462. package/src/ScrollableArea/ScrollableArea.tsx +1 -0
  463. package/src/ScrollableArea/modules/useScrollableArea.spec.md +50 -22
  464. package/src/SegmentedControl/SegmentedControl.doc.mjs +2 -2
  465. package/src/SegmentedControl/SegmentedControl.test.tsx +31 -0
  466. package/src/SegmentedControl/SegmentedControlItem.tsx +6 -9
  467. package/src/Selector/Selector.doc.mjs +2 -2
  468. package/src/Selector/Selector.spec.md +51 -49
  469. package/src/Selector/Selector.test.tsx +61 -55
  470. package/src/Selector/Selector.tsx +34 -15
  471. package/src/Selector/__tests__/Listbox.a11y.chromium.spec.ts +130 -0
  472. package/src/Selector/__tests__/Listbox.a11y.dom.ts +38 -0
  473. package/src/Selector/__tests__/Listbox.a11y.inventory.test.ts +63 -0
  474. package/src/Selector/__tests__/Listbox.a11y.known-failures.ts +37 -0
  475. package/src/Selector/__tests__/Listbox.a11y.states.ts +232 -0
  476. package/src/Selector/__tests__/Selector.a11y.chromium.spec.ts +45 -0
  477. package/src/Selector/__tests__/Selector.listbox.a11y.test.tsx +121 -0
  478. package/src/SideNav/SideNav.doc.mjs +1 -1
  479. package/src/SideNav/SideNav.test.tsx +10 -0
  480. package/src/SideNav/SideNav.tsx +14 -2
  481. package/src/Slider/Slider.doc.mjs +17 -3
  482. package/src/Slider/Slider.test.tsx +221 -0
  483. package/src/Slider/Slider.tsx +40 -9
  484. package/src/Spinner/Spinner.test.tsx +4 -0
  485. package/src/Spinner/Spinner.tsx +31 -18
  486. package/src/Switch/Switch.doc.mjs +11 -0
  487. package/src/Switch/Switch.test.tsx +16 -0
  488. package/src/Switch/Switch.tsx +28 -0
  489. package/src/TabList/Tab.tsx +35 -7
  490. package/src/TabList/TabList.doc.mjs +11 -0
  491. package/src/TabList/TabList.spec.md +204 -0
  492. package/src/TabList/TabList.test.tsx +66 -0
  493. package/src/TabList/__tests__/Tabs.a11y.known-failures.ts +1 -34
  494. package/src/Table/Table.doc.mjs +1 -1
  495. package/src/Table/Table.spec.md +54 -40
  496. package/src/Table/Table.test.tsx +83 -10
  497. package/src/Table/Table.tsx +30 -22
  498. package/src/Table/__tests__/TableScroll.a11y.chromium.spec.ts +137 -0
  499. package/src/TextArea/TextArea.tsx +6 -1
  500. package/src/TextInput/TextInput.test.tsx +20 -1
  501. package/src/TextInput/TextInput.tsx +6 -1
  502. package/src/TimeInput/TimeInput.tsx +6 -1
  503. package/src/Timer/Timer.doc.mjs +183 -0
  504. package/src/Timer/Timer.spec.md +215 -0
  505. package/src/Timer/Timer.test.tsx +302 -0
  506. package/src/Timer/Timer.tsx +258 -0
  507. package/src/Timer/index.ts +11 -0
  508. package/src/ToggleButton/ToggleButton.doc.mjs +3 -3
  509. package/src/ToggleButton/ToggleButton.test.tsx +299 -4
  510. package/src/ToggleButton/ToggleButton.tsx +26 -33
  511. package/src/ToggleButton/ToggleButtonGroup.doc.mjs +3 -3
  512. package/src/ToggleButton/__tests__/ToggleButton.a11y.chromium.spec.ts +209 -0
  513. package/src/Token/Token.doc.mjs +2 -2
  514. package/src/Tokenizer/Tokenizer.spec.md +142 -75
  515. package/src/TopNav/TopNavMenu.doc.mjs +2 -2
  516. package/src/TopNav/TopNavMenu.test.tsx +30 -0
  517. package/src/TopNav/TopNavMenu.tsx +7 -2
  518. package/src/TreeList/TreeList.doc.mjs +3 -3
  519. package/src/TreeList/TreeList.spec.md +123 -75
  520. package/src/TreeList/TreeList.test.tsx +29 -0
  521. package/src/TreeList/TreeList.tsx +3 -0
  522. package/src/TreeList/TreeListItem.tsx +15 -1
  523. package/src/TreeList/TreeListTypes.ts +11 -1
  524. package/src/Typeahead/BaseTypeahead.spec.md +4 -3
  525. package/src/Typeahead/BaseTypeahead.tsx +21 -7
  526. package/src/Typeahead/Typeahead.test.tsx +53 -0
  527. package/src/__tests__/PressedState.a11y.chromium.spec.ts +813 -0
  528. package/src/__tests__/pressState.ts +93 -0
  529. package/src/hooks/scrollKeyboardDelegation.test.ts +155 -0
  530. package/src/hooks/scrollKeyboardDelegation.ts +233 -0
  531. package/src/hooks/useClickableContainer.test.tsx +236 -0
  532. package/src/hooks/useClickableContainer.ts +20 -4
  533. package/src/hooks/useScrollableArea.doc.mjs +15 -3
  534. package/src/hooks/useScrollableArea.test.tsx +59 -1
  535. package/src/hooks/useScrollableArea.ts +34 -10
  536. package/src/index.ts +3 -0
  537. package/src/inputFontFloor.test.ts +106 -0
  538. package/src/tailwind-theme.css +0 -6
  539. package/src/theme/Theme.test.tsx +32 -0
  540. package/src/theme/declarationBoundary.test.ts +126 -0
  541. package/src/theme/declarationBoundary.ts +347 -0
  542. package/src/theme/derivedVarRegistry.test.ts +7 -0
  543. package/src/theme/derivedVarRegistry.ts +1 -0
  544. package/src/theme/generateThemeRules.test.ts +526 -1
  545. package/src/theme/generateThemeRules.ts +228 -50
  546. package/src/theme/tokenValueCompat.test.ts +4 -1
  547. package/src/utils/interactionOverlay.stylex.ts +20 -0
  548. package/src/utils/safeUrl.test.ts +88 -0
  549. package/src/utils/safeUrl.ts +85 -0
@@ -0,0 +1,302 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file Timer.test.tsx
5
+ * @input Uses React Testing Library, fake clocks, React Profiler, and Timer
6
+ * @output Verifies formats, adaptive cadence, render isolation, typography, resources, semantics, and passthrough
7
+ */
8
+
9
+ import {Profiler, StrictMode, createRef} from 'react';
10
+ import {renderToString} from 'react-dom/server';
11
+ import {act, fireEvent, render, screen} from '@testing-library/react';
12
+ import {afterEach, beforeEach, describe, expect, it, vi} from 'vitest';
13
+ import {Timer} from './Timer';
14
+
15
+ describe('Timer', () => {
16
+ beforeEach(() => {
17
+ vi.useFakeTimers();
18
+ vi.setSystemTime(new Date('2026-09-22T00:00:00Z'));
19
+ });
20
+
21
+ afterEach(() => {
22
+ vi.useRealTimers();
23
+ vi.restoreAllMocks();
24
+ });
25
+
26
+ it('renders deterministic zero-duration markup for each standard format', () => {
27
+ const {rerender} = render(<Timer data-testid="timer" />);
28
+ const timer = screen.getByTestId('timer');
29
+
30
+ expect(timer.tagName).toBe('TIME');
31
+ expect(timer).toHaveTextContent('0s');
32
+ expect(timer).toHaveAttribute('datetime', 'PT0S');
33
+
34
+ rerender(<Timer format="clock" data-testid="timer" />);
35
+ expect(timer).toHaveTextContent('0:00');
36
+ expect(timer).toHaveAttribute('datetime', 'PT0S');
37
+ });
38
+
39
+ it('keeps server markup independent of the clock', () => {
40
+ const firstMarkup = renderToString(<Timer startTime={0} />);
41
+ const firstClockMarkup = renderToString(
42
+ <Timer startTime={0} format="clock" />,
43
+ );
44
+
45
+ vi.setSystemTime(new Date('2030-01-01T00:00:00Z'));
46
+ const secondMarkup = renderToString(<Timer startTime={0} />);
47
+ const secondClockMarkup = renderToString(
48
+ <Timer startTime={0} format="clock" />,
49
+ );
50
+
51
+ expect(firstMarkup).toBe(secondMarkup);
52
+ expect(firstMarkup).toContain('dateTime="PT0S"');
53
+ expect(firstMarkup).toContain('>0s</time>');
54
+ expect(firstClockMarkup).toBe(secondClockMarkup);
55
+ expect(firstClockMarkup).toContain('>0:00</time>');
56
+ });
57
+
58
+ it('derives elapsed time from the clock without accumulating callback count', () => {
59
+ let scheduledTick: (() => void) | undefined;
60
+ vi.spyOn(globalThis, 'setTimeout').mockImplementation(callback => {
61
+ scheduledTick = callback as () => void;
62
+ return 1 as unknown as ReturnType<typeof setTimeout>;
63
+ });
64
+
65
+ render(<Timer data-testid="timer" />);
66
+
67
+ vi.setSystemTime(new Date('2026-09-22T00:00:08.750Z'));
68
+ act(() => {
69
+ scheduledTick?.();
70
+ });
71
+
72
+ const timer = screen.getByTestId('timer');
73
+ expect(timer).toHaveTextContent('8s');
74
+ expect(timer).toHaveAttribute('datetime', 'PT8S');
75
+ });
76
+
77
+ it('formats elapsed durations across seconds, minutes, and hours', () => {
78
+ const now = Date.now();
79
+ const {rerender} = render(
80
+ <Timer startTime={now - 34_000} data-testid="timer" />,
81
+ );
82
+ const timer = screen.getByTestId('timer');
83
+ expect(timer).toHaveTextContent('34s');
84
+ expect(timer).toHaveAttribute('datetime', 'PT34S');
85
+
86
+ rerender(<Timer startTime={now - 128_000} data-testid="timer" />);
87
+ expect(timer).toHaveTextContent('2m 08s');
88
+ expect(timer).toHaveAttribute('datetime', 'PT128S');
89
+
90
+ rerender(<Timer startTime={now - 3_753_000} data-testid="timer" />);
91
+ expect(timer).toHaveTextContent('1h 02m');
92
+ expect(timer).toHaveAttribute('datetime', 'PT3720S');
93
+ });
94
+
95
+ it('formats clock durations across minutes and hours', () => {
96
+ const now = Date.now();
97
+ const {rerender} = render(
98
+ <Timer format="clock" startTime={now - 128_000} data-testid="timer" />,
99
+ );
100
+ const timer = screen.getByTestId('timer');
101
+ expect(timer).toHaveTextContent('2:08');
102
+ expect(timer).toHaveAttribute('datetime', 'PT128S');
103
+
104
+ rerender(
105
+ <Timer format="clock" startTime={now - 3_753_000} data-testid="timer" />,
106
+ );
107
+ expect(timer).toHaveTextContent('1:02:33');
108
+ expect(timer).toHaveAttribute('datetime', 'PT3753S');
109
+ });
110
+
111
+ it('uses second cadence until elapsed format reaches an hour, then minute cadence', () => {
112
+ const setTimeoutSpy = vi.spyOn(globalThis, 'setTimeout');
113
+ const startTime = Date.now() - 3_599_000;
114
+ render(<Timer startTime={startTime} data-testid="timer" />);
115
+ const timer = screen.getByTestId('timer');
116
+
117
+ expect(timer).toHaveTextContent('59m 59s');
118
+ expect(setTimeoutSpy).toHaveBeenLastCalledWith(expect.any(Function), 1000);
119
+
120
+ act(() => {
121
+ vi.advanceTimersByTime(1000);
122
+ });
123
+ expect(timer).toHaveTextContent('1h 00m');
124
+ expect(timer).toHaveAttribute('datetime', 'PT3600S');
125
+ expect(setTimeoutSpy).toHaveBeenLastCalledWith(
126
+ expect.any(Function),
127
+ 60_000,
128
+ );
129
+ });
130
+
131
+ it('keeps clock format on second cadence after an hour', () => {
132
+ const setTimeoutSpy = vi.spyOn(globalThis, 'setTimeout');
133
+ render(
134
+ <Timer
135
+ format="clock"
136
+ startTime={Date.now() - 3_723_000}
137
+ data-testid="timer"
138
+ />,
139
+ );
140
+
141
+ expect(screen.getByTestId('timer')).toHaveTextContent('1:02:03');
142
+ expect(setTimeoutSpy).toHaveBeenLastCalledWith(expect.any(Function), 1000);
143
+ });
144
+
145
+ it('counts from a finite caller-provided start time', () => {
146
+ const now = Date.now();
147
+ render(<Timer startTime={now - 12_400} data-testid="timer" />);
148
+
149
+ expect(screen.getByTestId('timer')).toHaveTextContent('12s');
150
+ expect(screen.getByTestId('timer')).toHaveAttribute('datetime', 'PT12S');
151
+ });
152
+
153
+ it('returns to the original mount origin when startTime is removed', () => {
154
+ const mountedAt = Date.now();
155
+ const {rerender} = render(
156
+ <Timer startTime={mountedAt - 10_000} data-testid="timer" />,
157
+ );
158
+
159
+ act(() => {
160
+ vi.advanceTimersByTime(2000);
161
+ });
162
+ rerender(<Timer data-testid="timer" />);
163
+
164
+ expect(screen.getByTestId('timer')).toHaveTextContent('2s');
165
+ expect(screen.getByTestId('timer')).toHaveAttribute('datetime', 'PT2S');
166
+ });
167
+
168
+ it('falls back to mount time for a non-finite start time', () => {
169
+ render(<Timer startTime={Number.NaN} data-testid="timer" />);
170
+
171
+ act(() => {
172
+ vi.advanceTimersByTime(2000);
173
+ });
174
+
175
+ expect(screen.getByTestId('timer')).toHaveTextContent('2s');
176
+ });
177
+
178
+ it('clamps a future origin to zero and schedules its first visible change directly', () => {
179
+ const setTimeoutSpy = vi.spyOn(globalThis, 'setTimeout');
180
+ render(<Timer startTime={Date.now() + 5000} data-testid="timer" />);
181
+
182
+ expect(screen.getByTestId('timer')).toHaveTextContent('0s');
183
+ expect(screen.getByTestId('timer')).toHaveAttribute('datetime', 'PT0S');
184
+ expect(setTimeoutSpy).toHaveBeenLastCalledWith(expect.any(Function), 6000);
185
+
186
+ act(() => {
187
+ vi.advanceTimersByTime(6000);
188
+ });
189
+
190
+ expect(screen.getByTestId('timer')).toHaveTextContent('1s');
191
+ expect(screen.getByTestId('timer')).toHaveAttribute('datetime', 'PT1S');
192
+ });
193
+
194
+ it('updates format without remounting or losing current elapsed time', () => {
195
+ const now = Date.now();
196
+ const {rerender} = render(
197
+ <Timer startTime={now - 3_723_000} data-testid="timer" />,
198
+ );
199
+ const timer = screen.getByTestId('timer');
200
+ expect(timer).toHaveTextContent('1h 02m');
201
+ expect(timer).toHaveAttribute('datetime', 'PT3720S');
202
+
203
+ rerender(
204
+ <Timer format="clock" startTime={now - 3_723_000} data-testid="timer" />,
205
+ );
206
+ expect(screen.getByTestId('timer')).toBe(timer);
207
+ expect(timer).toHaveTextContent('1:02:03');
208
+ expect(timer).toHaveAttribute('datetime', 'PT3723S');
209
+ });
210
+
211
+ it('does not schedule React update commits as time advances', () => {
212
+ const onRender = vi.fn();
213
+ render(
214
+ <Profiler id="timer" onRender={onRender}>
215
+ <Timer />
216
+ </Profiler>,
217
+ );
218
+ expect(onRender).toHaveBeenCalledTimes(1);
219
+
220
+ act(() => {
221
+ vi.advanceTimersByTime(5000);
222
+ });
223
+
224
+ expect(onRender).toHaveBeenCalledTimes(1);
225
+ });
226
+
227
+ it('owns one timer resource and cleans up under StrictMode replay', () => {
228
+ const setTimeoutSpy = vi.spyOn(window, 'setTimeout');
229
+ const clearTimeoutSpy = vi.spyOn(window, 'clearTimeout');
230
+ const {unmount} = render(
231
+ <StrictMode>
232
+ <Timer />
233
+ </StrictMode>,
234
+ );
235
+
236
+ expect(setTimeoutSpy).toHaveBeenCalledTimes(2);
237
+ expect(clearTimeoutSpy).toHaveBeenCalledTimes(1);
238
+
239
+ unmount();
240
+ expect(clearTimeoutSpy).toHaveBeenCalledTimes(2);
241
+ });
242
+
243
+ it('matches Timestamp typography defaults and accepts overrides', () => {
244
+ const {rerender} = render(<Timer data-testid="timer" />);
245
+ const timer = screen.getByTestId('timer');
246
+ const text = timer.parentElement;
247
+
248
+ expect(text).toHaveClass('astryx-text');
249
+ expect(text).toHaveClass('astryx-timer');
250
+ expect(text).toHaveAttribute('data-type', 'supporting');
251
+ expect(text).toHaveAttribute('data-color', 'secondary');
252
+
253
+ rerender(
254
+ <Timer
255
+ type="body"
256
+ size="lg"
257
+ color="primary"
258
+ weight="bold"
259
+ data-testid="timer"
260
+ />,
261
+ );
262
+ expect(text).toHaveAttribute('data-type', 'body');
263
+ expect(text).toHaveAttribute('data-size', 'lg');
264
+ expect(text).toHaveAttribute('data-color', 'primary');
265
+ });
266
+
267
+ it('forwards its time ref and preserves Timestamp-style root styling and time props', () => {
268
+ const ref = createRef<HTMLTimeElement>();
269
+ const onClick = vi.fn();
270
+ render(
271
+ <Timer
272
+ ref={ref}
273
+ id="elapsed"
274
+ className="custom-class"
275
+ style={{color: 'rgb(1, 2, 3)'}}
276
+ aria-live="polite"
277
+ onClick={onClick}
278
+ data-testid="timer"
279
+ />,
280
+ );
281
+
282
+ const timer = screen.getByTestId('timer');
283
+ const text = timer.parentElement;
284
+ expect(ref.current).toBe(timer);
285
+ expect(timer).toHaveAttribute('id', 'elapsed');
286
+ expect(text).toHaveClass('astryx-timer');
287
+ expect(text).toHaveClass('custom-class');
288
+ expect(text).toHaveStyle({color: 'rgb(1, 2, 3)'});
289
+ expect(timer).toHaveAttribute('aria-live', 'polite');
290
+
291
+ fireEvent.click(timer);
292
+ expect(onClick).toHaveBeenCalledTimes(1);
293
+ });
294
+
295
+ it('does not add live-region semantics by default', () => {
296
+ render(<Timer data-testid="timer" />);
297
+ const timer = screen.getByTestId('timer');
298
+
299
+ expect(timer).not.toHaveAttribute('aria-live');
300
+ expect(timer).not.toHaveAttribute('role');
301
+ });
302
+ });
@@ -0,0 +1,258 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ 'use client';
4
+
5
+ /**
6
+ * @file Timer.tsx
7
+ * @input Uses an optional start time, standardized format, Timestamp typography, BaseProps, and React ref
8
+ * @output Exports Timer, TimerProps, and TimerFormat with non-rendering elapsed-time updates
9
+ * @position Core content primitive for elapsed duration in active operations
10
+ *
11
+ * SYNC: When modified, update these files to stay in sync:
12
+ * - /packages/core/src/Timer/Timer.spec.md
13
+ * - /packages/core/src/Timer/Timer.doc.mjs
14
+ * - /packages/core/src/Timer/Timer.test.tsx
15
+ * - /packages/core/src/Timer/index.ts
16
+ * - /apps/storybook/stories/Timer.stories.tsx
17
+ * - /packages/cli/assets/templates/blocks/components/Timer/
18
+ */
19
+
20
+ import {useEffect, useRef, useState} from 'react';
21
+ import * as stylex from '@stylexjs/stylex';
22
+ import type {BaseProps} from '../BaseProps';
23
+ import {useMergedRefs} from '../hooks/useMergedRefs';
24
+ import {Text} from '../Text';
25
+ import type {TextColor, TextSize, TextType, TextWeight} from '../theme/types';
26
+ import {mergeProps} from '../utils';
27
+ import {themeProps} from '../utils/themeProps';
28
+
29
+ const ONE_SECOND_MS = 1000;
30
+ const ONE_MINUTE_MS = 60 * ONE_SECOND_MS;
31
+ const ONE_HOUR_SECONDS = 60 * 60;
32
+ const MAX_TIMEOUT_MS = 2_147_483_647;
33
+
34
+ const styles = stylex.create({
35
+ time: {
36
+ color: 'inherit',
37
+ display: 'inline',
38
+ fontFamily: 'inherit',
39
+ fontSize: 'inherit',
40
+ fontStyle: 'normal',
41
+ fontVariantNumeric: 'tabular-nums',
42
+ fontWeight: 'inherit',
43
+ lineHeight: 'inherit',
44
+ },
45
+ });
46
+
47
+ export type TimerFormat = 'elapsed' | 'clock';
48
+
49
+ type TimerPresentation = {
50
+ dateTime: string;
51
+ text: string;
52
+ };
53
+
54
+ function pad(value: number): string {
55
+ return String(value).padStart(2, '0');
56
+ }
57
+
58
+ function resolveFormat(format: TimerFormat): TimerFormat {
59
+ return format === 'clock' ? 'clock' : 'elapsed';
60
+ }
61
+
62
+ function getElapsedMilliseconds(now: number, startTime: number): number {
63
+ return Math.max(0, now - startTime);
64
+ }
65
+
66
+ function getPresentation(
67
+ elapsedMilliseconds: number,
68
+ format: TimerFormat,
69
+ ): TimerPresentation {
70
+ const elapsedSeconds = Math.floor(elapsedMilliseconds / ONE_SECOND_MS);
71
+
72
+ if (format === 'clock') {
73
+ const hours = Math.floor(elapsedSeconds / ONE_HOUR_SECONDS);
74
+ const minutes = Math.floor((elapsedSeconds % ONE_HOUR_SECONDS) / 60);
75
+ const seconds = elapsedSeconds % 60;
76
+ return {
77
+ dateTime: `PT${elapsedSeconds}S`,
78
+ text:
79
+ hours > 0
80
+ ? `${String(hours)}:${pad(minutes)}:${pad(seconds)}`
81
+ : `${String(minutes)}:${pad(seconds)}`,
82
+ };
83
+ }
84
+
85
+ if (elapsedSeconds < 60) {
86
+ return {
87
+ dateTime: `PT${elapsedSeconds}S`,
88
+ text: `${String(elapsedSeconds)}s`,
89
+ };
90
+ }
91
+
92
+ const totalMinutes = Math.floor(elapsedSeconds / 60);
93
+ if (totalMinutes < 60) {
94
+ return {
95
+ dateTime: `PT${elapsedSeconds}S`,
96
+ text: `${String(totalMinutes)}m ${pad(elapsedSeconds % 60)}s`,
97
+ };
98
+ }
99
+
100
+ const representedSeconds = totalMinutes * 60;
101
+ return {
102
+ dateTime: `PT${representedSeconds}S`,
103
+ text: `${String(Math.floor(totalMinutes / 60))}h ${pad(totalMinutes % 60)}m`,
104
+ };
105
+ }
106
+
107
+ function getMillisecondsUntilNextChange(
108
+ now: number,
109
+ startTime: number,
110
+ elapsedMilliseconds: number,
111
+ format: TimerFormat,
112
+ ): number {
113
+ if (now < startTime) {
114
+ return Math.min(startTime - now + ONE_SECOND_MS, MAX_TIMEOUT_MS);
115
+ }
116
+
117
+ const precision =
118
+ format === 'elapsed' &&
119
+ elapsedMilliseconds >= ONE_HOUR_SECONDS * ONE_SECOND_MS
120
+ ? ONE_MINUTE_MS
121
+ : ONE_SECOND_MS;
122
+ return precision - (elapsedMilliseconds % precision);
123
+ }
124
+
125
+ export interface TimerProps extends Omit<
126
+ BaseProps<HTMLTimeElement>,
127
+ 'dateTime'
128
+ > {
129
+ /** Ref forwarded to the rendered `<time>` element. */
130
+ ref?: React.Ref<HTMLTimeElement>;
131
+ /**
132
+ * Unix time in milliseconds when the measured operation began. Omit it to
133
+ * start counting from this Timer's mount.
134
+ */
135
+ startTime?: number;
136
+ /**
137
+ * Standard duration representation.
138
+ * @default 'elapsed'
139
+ */
140
+ format?: TimerFormat;
141
+ /**
142
+ * Semantic text type. Matches Timestamp typography behavior.
143
+ * @default 'supporting'
144
+ */
145
+ type?: TextType;
146
+ /** Explicit font size override. Overrides the size from `type`. */
147
+ size?: TextSize;
148
+ /**
149
+ * Text color.
150
+ * @default 'secondary'
151
+ */
152
+ color?: TextColor;
153
+ /** Font weight override. */
154
+ weight?: TextWeight;
155
+ }
156
+
157
+ /**
158
+ * Displays a standardized elapsed duration without scheduling React tick renders.
159
+ *
160
+ * Timer writes changing text and its ISO 8601 duration directly to the owned
161
+ * `<time>` node. Use `elapsed` for compact duration text or `clock` for a
162
+ * stopwatch-like reading.
163
+ *
164
+ * @example
165
+ * ```
166
+ * <Timer />
167
+ * <Timer format="clock" />
168
+ * ```
169
+ */
170
+ export function Timer({
171
+ startTime,
172
+ format = 'elapsed',
173
+ type = 'supporting',
174
+ size,
175
+ color = 'secondary',
176
+ weight,
177
+ ref,
178
+ xstyle,
179
+ className,
180
+ style,
181
+ ...rest
182
+ }: TimerProps) {
183
+ const [mountTime] = useState(() => Date.now());
184
+ const timerRef = useRef<HTMLTimeElement>(null);
185
+ const mergedRef = useMergedRefs(ref, timerRef);
186
+ const resolvedFormat = resolveFormat(format);
187
+ const initialPresentation = getPresentation(0, resolvedFormat);
188
+
189
+ useEffect(() => {
190
+ const resolvedStartTime =
191
+ startTime !== undefined && Number.isFinite(startTime)
192
+ ? startTime
193
+ : mountTime;
194
+ let timeoutID: ReturnType<typeof setTimeout> | undefined;
195
+ let previousDateTime: string | undefined;
196
+ let previousText: string | undefined;
197
+
198
+ const tick = () => {
199
+ const now = Date.now();
200
+ const elapsedMilliseconds = getElapsedMilliseconds(
201
+ now,
202
+ resolvedStartTime,
203
+ );
204
+ const presentation = getPresentation(elapsedMilliseconds, resolvedFormat);
205
+ const node = timerRef.current;
206
+
207
+ if (node != null) {
208
+ if (presentation.text !== previousText) {
209
+ node.textContent = presentation.text;
210
+ previousText = presentation.text;
211
+ }
212
+ if (presentation.dateTime !== previousDateTime) {
213
+ node.dateTime = presentation.dateTime;
214
+ previousDateTime = presentation.dateTime;
215
+ }
216
+ }
217
+
218
+ timeoutID = setTimeout(
219
+ tick,
220
+ getMillisecondsUntilNextChange(
221
+ now,
222
+ resolvedStartTime,
223
+ elapsedMilliseconds,
224
+ resolvedFormat,
225
+ ),
226
+ );
227
+ };
228
+
229
+ tick();
230
+ return () => {
231
+ if (timeoutID !== undefined) {
232
+ clearTimeout(timeoutID);
233
+ }
234
+ };
235
+ }, [mountTime, resolvedFormat, startTime]);
236
+
237
+ const timerProps = mergeProps(themeProps('timer'), {className, style});
238
+
239
+ return (
240
+ <Text
241
+ type={type}
242
+ size={size}
243
+ color={color}
244
+ weight={weight}
245
+ xstyle={xstyle}
246
+ {...timerProps}>
247
+ <time
248
+ {...rest}
249
+ ref={mergedRef}
250
+ dateTime="PT0S"
251
+ {...stylex.props(styles.time)}>
252
+ {initialPresentation.text}
253
+ </time>
254
+ </Text>
255
+ );
256
+ }
257
+
258
+ Timer.displayName = 'Timer';
@@ -0,0 +1,11 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file index.ts
5
+ * @input Imports Timer, TimerProps, and TimerFormat
6
+ * @output Public Timer component barrel
7
+ * @position Component subpath entry point for @astryxdesign/core/Timer
8
+ */
9
+
10
+ export {Timer} from './Timer';
11
+ export type {TimerFormat, TimerProps} from './Timer';
@@ -31,17 +31,17 @@ export const docs = {
31
31
  {
32
32
  name: 'isPressed',
33
33
  type: 'boolean',
34
- description: 'Whether the button is currently pressed. Ignored when inside a group.',
34
+ description: 'Whether the button is currently pressed. Ignored for a ToggleButtonGroup member with a value.',
35
35
  },
36
36
  {
37
37
  name: 'onPressedChange',
38
38
  type: '(isPressed: boolean, event: MouseEvent) => void',
39
- description: 'Called when pressed state should change. Receives the next state and the click event; call event.preventDefault() to skip pressedChangeAction. Ignored when inside a group.',
39
+ description: 'Called synchronously when pressed state should change. Receives the next state and the click event; call event.preventDefault() to skip pressedChangeAction. Without an Action, the callback produces no Action-pending feedback. Ignored for a ToggleButtonGroup member with a value.',
40
40
  },
41
41
  {
42
42
  name: 'pressedChangeAction',
43
43
  type: '(isPressed: boolean) => void | Promise<void>',
44
- description: 'Action handler for API- or navigation-backed toggles, run in a transition. Shows an optimistic pressed state immediately and a spinner while pending; the button stays interruptible by re-clicks.',
44
+ description: 'Action handler for API- or navigation-backed toggles, run in a transition after the synchronous onPressedChange callback unless that callback calls event.preventDefault(). Works without onPressedChange. Shows an optimistic pressed state and a spinner while pending, and remains interruptible by re-clicks. Omit it for callback-only toggles with no Action-pending feedback. Ignored for a ToggleButtonGroup member with a value.',
45
45
  },
46
46
  {
47
47
  name: 'size',