@astryxdesign/core 0.1.5 → 0.1.6-canary.02f2602

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 (564) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/README.md +2 -0
  3. package/dist/AlertDialog/AlertDialog.d.ts +1 -1
  4. package/dist/AlertDialog/AlertDialog.d.ts.map +1 -1
  5. package/dist/AlertDialog/AlertDialog.js +21 -18
  6. package/dist/AlertDialog/useImperativeAlertDialog.d.ts +0 -2
  7. package/dist/AlertDialog/useImperativeAlertDialog.d.ts.map +1 -1
  8. package/dist/AlertDialog/useImperativeAlertDialog.js +3 -5
  9. package/dist/AppShell/AppShell.d.ts.map +1 -1
  10. package/dist/AppShell/AppShell.js +55 -53
  11. package/dist/AspectRatio/AspectRatio.js +5 -5
  12. package/dist/Avatar/Avatar.js +19 -19
  13. package/dist/Avatar/AvatarStatusDot.js +5 -5
  14. package/dist/AvatarGroup/AvatarGroup.d.ts +1 -1
  15. package/dist/AvatarGroup/AvatarGroup.d.ts.map +1 -1
  16. package/dist/AvatarGroup/AvatarGroup.js +9 -6
  17. package/dist/AvatarGroup/AvatarGroupOverflow.js +5 -5
  18. package/dist/Badge/Badge.js +3 -3
  19. package/dist/Banner/Banner.d.ts.map +1 -1
  20. package/dist/Banner/Banner.js +32 -30
  21. package/dist/Blockquote/Blockquote.js +7 -7
  22. package/dist/Breadcrumbs/BreadcrumbItem.js +17 -17
  23. package/dist/Breadcrumbs/Breadcrumbs.d.ts +1 -1
  24. package/dist/Breadcrumbs/Breadcrumbs.d.ts.map +1 -1
  25. package/dist/Breadcrumbs/Breadcrumbs.js +11 -8
  26. package/dist/Button/Button.d.ts.map +1 -1
  27. package/dist/Button/Button.js +53 -23
  28. package/dist/ButtonGroup/ButtonGroup.d.ts +20 -5
  29. package/dist/ButtonGroup/ButtonGroup.d.ts.map +1 -1
  30. package/dist/ButtonGroup/ButtonGroup.js +34 -18
  31. package/dist/Calendar/Calendar.d.ts.map +1 -1
  32. package/dist/Calendar/Calendar.js +51 -48
  33. package/dist/Card/Card.js +3 -3
  34. package/dist/Carousel/Carousel.d.ts +1 -1
  35. package/dist/Carousel/Carousel.d.ts.map +1 -1
  36. package/dist/Carousel/Carousel.js +27 -24
  37. package/dist/Center/Center.js +3 -3
  38. package/dist/Chat/ChatComposer.d.ts.map +1 -1
  39. package/dist/Chat/ChatComposer.js +27 -24
  40. package/dist/Chat/ChatComposerDrawer.d.ts +1 -1
  41. package/dist/Chat/ChatComposerDrawer.d.ts.map +1 -1
  42. package/dist/Chat/ChatComposerDrawer.js +23 -16
  43. package/dist/Chat/ChatComposerInput.d.ts.map +1 -1
  44. package/dist/Chat/ChatComposerInput.js +20 -16
  45. package/dist/Chat/ChatDictationButton.js +10 -10
  46. package/dist/Chat/ChatLayout.d.ts +1 -11
  47. package/dist/Chat/ChatLayout.d.ts.map +1 -1
  48. package/dist/Chat/ChatLayout.js +23 -21
  49. package/dist/Chat/ChatLayoutScrollButton.d.ts.map +1 -1
  50. package/dist/Chat/ChatLayoutScrollButton.js +13 -11
  51. package/dist/Chat/ChatMessage.d.ts +1 -1
  52. package/dist/Chat/ChatMessage.d.ts.map +1 -1
  53. package/dist/Chat/ChatMessage.js +20 -14
  54. package/dist/Chat/ChatMessageBubble.d.ts +1 -1
  55. package/dist/Chat/ChatMessageBubble.d.ts.map +1 -1
  56. package/dist/Chat/ChatMessageBubble.js +10 -8
  57. package/dist/Chat/ChatMessageList.d.ts +1 -1
  58. package/dist/Chat/ChatMessageList.d.ts.map +1 -1
  59. package/dist/Chat/ChatMessageList.js +17 -15
  60. package/dist/Chat/ChatMessageMetadata.d.ts.map +1 -1
  61. package/dist/Chat/ChatMessageMetadata.js +24 -19
  62. package/dist/Chat/ChatPastedTextToken.d.ts.map +1 -1
  63. package/dist/Chat/ChatPastedTextToken.js +16 -14
  64. package/dist/Chat/ChatSendButton.d.ts.map +1 -1
  65. package/dist/Chat/ChatSendButton.js +6 -4
  66. package/dist/Chat/ChatSystemMessage.js +11 -11
  67. package/dist/Chat/ChatTokenizedText.js +8 -8
  68. package/dist/Chat/ChatToolCalls.js +73 -73
  69. package/dist/Chat/useSpeechRecognition.d.ts.map +1 -1
  70. package/dist/Chat/useSpeechRecognition.js +4 -2
  71. package/dist/Chat/useTriggerMenu.d.ts.map +1 -1
  72. package/dist/Chat/useTriggerMenu.js +19 -17
  73. package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
  74. package/dist/CheckboxInput/CheckboxInput.js +26 -24
  75. package/dist/CheckboxList/CheckboxList.d.ts +1 -1
  76. package/dist/CheckboxList/CheckboxList.d.ts.map +1 -1
  77. package/dist/CheckboxList/CheckboxList.js +12 -10
  78. package/dist/CheckboxList/CheckboxListItem.d.ts.map +1 -1
  79. package/dist/CheckboxList/CheckboxListItem.js +8 -6
  80. package/dist/Citation/Citation.d.ts.map +1 -1
  81. package/dist/Citation/Citation.js +20 -12
  82. package/dist/ClickableCard/ClickableCard.js +6 -6
  83. package/dist/Code/Code.js +3 -3
  84. package/dist/CodeBlock/CodeBlock.d.ts.map +1 -1
  85. package/dist/CodeBlock/CodeBlock.js +114 -96
  86. package/dist/Collapsible/Collapsible.js +11 -11
  87. package/dist/Collapsible/CollapsibleGroup.js +7 -7
  88. package/dist/CommandPalette/CommandPalette.d.ts +1 -1
  89. package/dist/CommandPalette/CommandPalette.d.ts.map +1 -1
  90. package/dist/CommandPalette/CommandPalette.js +39 -34
  91. package/dist/CommandPalette/CommandPaletteEmpty.js +3 -3
  92. package/dist/CommandPalette/CommandPaletteFooter.js +16 -16
  93. package/dist/CommandPalette/CommandPaletteGroup.js +5 -5
  94. package/dist/CommandPalette/CommandPaletteInput.d.ts +1 -1
  95. package/dist/CommandPalette/CommandPaletteInput.d.ts.map +1 -1
  96. package/dist/CommandPalette/CommandPaletteInput.js +17 -14
  97. package/dist/CommandPalette/CommandPaletteItem.js +3 -3
  98. package/dist/CommandPalette/CommandPaletteList.d.ts +1 -1
  99. package/dist/CommandPalette/CommandPaletteList.d.ts.map +1 -1
  100. package/dist/CommandPalette/CommandPaletteList.js +7 -4
  101. package/dist/ContextMenu/ContextMenu.d.ts +1 -1
  102. package/dist/ContextMenu/ContextMenu.d.ts.map +1 -1
  103. package/dist/ContextMenu/ContextMenu.js +14 -11
  104. package/dist/DateInput/DateInput.d.ts +1 -1
  105. package/dist/DateInput/DateInput.d.ts.map +1 -1
  106. package/dist/DateInput/DateInput.js +28 -23
  107. package/dist/DateRangeInput/DateRangeInput.d.ts +1 -1
  108. package/dist/DateRangeInput/DateRangeInput.d.ts.map +1 -1
  109. package/dist/DateRangeInput/DateRangeInput.js +32 -27
  110. package/dist/DateTimeInput/DateTimeInput.d.ts +1 -1
  111. package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
  112. package/dist/DateTimeInput/DateTimeInput.js +41 -33
  113. package/dist/Dialog/Dialog.js +9 -9
  114. package/dist/Dialog/DialogHeader.d.ts.map +1 -1
  115. package/dist/Dialog/DialogHeader.js +20 -18
  116. package/dist/Dialog/useImperativeDialog.js +3 -3
  117. package/dist/Divider/Divider.js +7 -7
  118. package/dist/DropdownMenu/DropdownMenu.d.ts +1 -1
  119. package/dist/DropdownMenu/DropdownMenu.d.ts.map +1 -1
  120. package/dist/DropdownMenu/DropdownMenu.js +21 -15
  121. package/dist/DropdownMenu/DropdownMenuItem.js +3 -3
  122. package/dist/DropdownMenu/renderDropdownItems.js +10 -10
  123. package/dist/EmptyState/EmptyState.d.ts +4 -1
  124. package/dist/EmptyState/EmptyState.d.ts.map +1 -1
  125. package/dist/EmptyState/EmptyState.js +9 -9
  126. package/dist/Field/Field.js +17 -17
  127. package/dist/Field/FieldLabel.js +15 -15
  128. package/dist/Field/InputClearButton.js +5 -5
  129. package/dist/FieldStatus/FieldStatus.js +3 -3
  130. package/dist/FileInput/FileInput.d.ts.map +1 -1
  131. package/dist/FileInput/FileInput.js +34 -30
  132. package/dist/FormLayout/FormLayout.js +5 -5
  133. package/dist/Grid/Grid.js +3 -3
  134. package/dist/Grid/GridSpan.js +3 -3
  135. package/dist/HStack/HStack.js +3 -3
  136. package/dist/Heading/Heading.js +10 -10
  137. package/dist/HoverCard/HoverCard.js +9 -9
  138. package/dist/HoverCard/useHoverCard.js +3 -3
  139. package/dist/Icon/Icon.js +11 -11
  140. package/dist/Icon/defaultIcons.js +116 -116
  141. package/dist/IconButton/IconButton.js +3 -3
  142. package/dist/InputGroup/InputGroup.js +9 -9
  143. package/dist/InputGroup/InputGroupText.js +3 -3
  144. package/dist/Item/Item.js +17 -17
  145. package/dist/Kbd/Kbd.js +5 -5
  146. package/dist/Layer/LayerProvider.js +7 -7
  147. package/dist/Layer/useLayer.js +5 -5
  148. package/dist/Layout/Layout.js +21 -21
  149. package/dist/Layout/LayoutContent.js +3 -3
  150. package/dist/Layout/LayoutFooter.js +5 -5
  151. package/dist/Layout/LayoutHeader.js +5 -5
  152. package/dist/Layout/LayoutPanel.js +3 -3
  153. package/dist/Lightbox/Lightbox.d.ts.map +1 -1
  154. package/dist/Lightbox/Lightbox.js +35 -33
  155. package/dist/Lightbox/useLightbox.d.ts +0 -1
  156. package/dist/Lightbox/useLightbox.d.ts.map +1 -1
  157. package/dist/Lightbox/useLightbox.js +3 -4
  158. package/dist/Link/Link.d.ts +5 -3
  159. package/dist/Link/Link.d.ts.map +1 -1
  160. package/dist/Link/Link.js +47 -18
  161. package/dist/Link/LinkProvider.js +3 -3
  162. package/dist/Link/useLinkify.js +3 -3
  163. package/dist/List/List.js +11 -11
  164. package/dist/List/ListItem.js +11 -11
  165. package/dist/Markdown/Markdown.d.ts.map +1 -1
  166. package/dist/Markdown/Markdown.js +111 -109
  167. package/dist/MetadataList/MetadataList.js +10 -10
  168. package/dist/MetadataList/MetadataListItem.js +15 -15
  169. package/dist/MobileNav/MobileNav.d.ts.map +1 -1
  170. package/dist/MobileNav/MobileNav.js +19 -17
  171. package/dist/MobileNav/MobileNavToggle.d.ts +1 -1
  172. package/dist/MobileNav/MobileNavToggle.d.ts.map +1 -1
  173. package/dist/MobileNav/MobileNavToggle.js +9 -6
  174. package/dist/MoreMenu/MoreMenu.d.ts +1 -1
  175. package/dist/MoreMenu/MoreMenu.d.ts.map +1 -1
  176. package/dist/MoreMenu/MoreMenu.js +7 -4
  177. package/dist/MultiSelector/MultiSelector.d.ts +1 -1
  178. package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
  179. package/dist/MultiSelector/MultiSelector.js +69 -62
  180. package/dist/NavIcon/NavIcon.js +3 -3
  181. package/dist/NavMenu/NavHeadingMenu.js +5 -5
  182. package/dist/NavMenu/NavHeadingMenuItem.js +8 -8
  183. package/dist/NumberInput/NumberInput.d.ts.map +1 -1
  184. package/dist/NumberInput/NumberInput.js +21 -17
  185. package/dist/Outline/Outline.d.ts +1 -1
  186. package/dist/Outline/Outline.d.ts.map +1 -1
  187. package/dist/Outline/Outline.js +19 -16
  188. package/dist/OverflowList/OverflowList.js +8 -8
  189. package/dist/Overlay/Overlay.js +3 -3
  190. package/dist/Overlay/OverlayScrim.js +5 -5
  191. package/dist/Overlay/useOverlay.d.ts +0 -3
  192. package/dist/Overlay/useOverlay.d.ts.map +1 -1
  193. package/dist/Overlay/useOverlay.js +3 -6
  194. package/dist/Pagination/Pagination.d.ts +1 -1
  195. package/dist/Pagination/Pagination.d.ts.map +1 -1
  196. package/dist/Pagination/Pagination.js +73 -46
  197. package/dist/Popover/Popover.js +16 -16
  198. package/dist/Popover/usePopover.d.ts.map +1 -1
  199. package/dist/Popover/usePopover.js +11 -8
  200. package/dist/PowerSearch/PowerSearch.d.ts +1 -1
  201. package/dist/PowerSearch/PowerSearch.d.ts.map +1 -1
  202. package/dist/PowerSearch/PowerSearch.js +95 -85
  203. package/dist/PowerSearch/PowerSearchEditPopover.d.ts +1 -1
  204. package/dist/PowerSearch/PowerSearchEditPopover.d.ts.map +1 -1
  205. package/dist/PowerSearch/PowerSearchEditPopover.js +109 -103
  206. package/dist/PowerSearch/PowerSearchFilterEditor.js +3 -3
  207. package/dist/PowerSearch/PowerSearchToken.d.ts.map +1 -1
  208. package/dist/PowerSearch/PowerSearchToken.js +12 -8
  209. package/dist/PowerSearch/PowerSearchValueEditor.d.ts.map +1 -1
  210. package/dist/PowerSearch/PowerSearchValueEditor.js +88 -75
  211. package/dist/PowerSearch/formatFilterValue.d.ts +2 -1
  212. package/dist/PowerSearch/formatFilterValue.d.ts.map +1 -1
  213. package/dist/PowerSearch/formatFilterValue.js +19 -9
  214. package/dist/PowerSearch/index.d.ts +2 -1
  215. package/dist/PowerSearch/index.d.ts.map +1 -1
  216. package/dist/PowerSearch/index.js +1 -0
  217. package/dist/PowerSearch/resolveOperatorLabel.d.ts +23 -0
  218. package/dist/PowerSearch/resolveOperatorLabel.d.ts.map +1 -0
  219. package/dist/PowerSearch/resolveOperatorLabel.js +28 -0
  220. package/dist/PowerSearch/types.d.ts +28 -2
  221. package/dist/PowerSearch/types.d.ts.map +1 -1
  222. package/dist/PowerSearch/usePowerSearchConfig.d.ts.map +1 -1
  223. package/dist/PowerSearch/usePowerSearchConfig.js +27 -27
  224. package/dist/PowerSearch/usePowerSearchSource.d.ts.map +1 -1
  225. package/dist/PowerSearch/usePowerSearchSource.js +15 -8
  226. package/dist/ProgressBar/ProgressBar.js +13 -13
  227. package/dist/RadioList/RadioList.js +7 -7
  228. package/dist/RadioList/RadioListItem.js +18 -18
  229. package/dist/Resizable/ResizeHandle.d.ts +1 -1
  230. package/dist/Resizable/ResizeHandle.d.ts.map +1 -1
  231. package/dist/Resizable/ResizeHandle.js +10 -7
  232. package/dist/Section/Section.js +5 -5
  233. package/dist/SegmentedControl/SegmentedControl.js +5 -5
  234. package/dist/SegmentedControl/SegmentedControlItem.js +7 -7
  235. package/dist/SelectableCard/SelectableCard.js +5 -5
  236. package/dist/Selector/Selector.d.ts.map +1 -1
  237. package/dist/Selector/Selector.js +57 -51
  238. package/dist/Selector/SelectorOption.js +3 -3
  239. package/dist/SideNav/SideNav.d.ts.map +1 -1
  240. package/dist/SideNav/SideNav.js +38 -36
  241. package/dist/SideNav/SideNavCollapseButton.d.ts.map +1 -1
  242. package/dist/SideNav/SideNavCollapseButton.js +8 -6
  243. package/dist/SideNav/SideNavHeading.d.ts.map +1 -1
  244. package/dist/SideNav/SideNavHeading.js +92 -90
  245. package/dist/SideNav/SideNavItem.d.ts.map +1 -1
  246. package/dist/SideNav/SideNavItem.js +49 -43
  247. package/dist/SideNav/SideNavSection.js +14 -14
  248. package/dist/Skeleton/Skeleton.js +3 -3
  249. package/dist/Slider/Slider.js +23 -23
  250. package/dist/Spinner/Spinner.js +9 -9
  251. package/dist/StatusDot/StatusDot.js +5 -5
  252. package/dist/Switch/Switch.d.ts.map +1 -1
  253. package/dist/Switch/Switch.js +29 -27
  254. package/dist/TabList/Tab.js +18 -18
  255. package/dist/TabList/TabList.d.ts +1 -1
  256. package/dist/TabList/TabList.d.ts.map +1 -1
  257. package/dist/TabList/TabList.js +9 -6
  258. package/dist/TabList/TabMenu.js +23 -23
  259. package/dist/Table/BaseTable.d.ts.map +1 -1
  260. package/dist/Table/BaseTable.js +39 -37
  261. package/dist/Table/Table.d.ts.map +1 -1
  262. package/dist/Table/Table.js +10 -8
  263. package/dist/Table/TableBody.js +3 -3
  264. package/dist/Table/TableCell.js +3 -3
  265. package/dist/Table/TableFooter.js +3 -3
  266. package/dist/Table/TableHeader.js +3 -3
  267. package/dist/Table/TableHeaderCell.js +3 -3
  268. package/dist/Table/TableRow.js +5 -5
  269. package/dist/Table/index.d.ts +4 -0
  270. package/dist/Table/index.d.ts.map +1 -1
  271. package/dist/Table/index.js +2 -0
  272. package/dist/Table/plugins/columnResize/useTableColumnResize.js +9 -9
  273. package/dist/Table/plugins/filtering/useTableFiltering.d.ts.map +1 -1
  274. package/dist/Table/plugins/filtering/useTableFiltering.js +127 -90
  275. package/dist/Table/plugins/groupedRows/index.d.ts +3 -0
  276. package/dist/Table/plugins/groupedRows/index.d.ts.map +1 -0
  277. package/dist/Table/plugins/groupedRows/index.js +3 -0
  278. package/dist/Table/plugins/groupedRows/useTableGroupedRows.d.ts +78 -0
  279. package/dist/Table/plugins/groupedRows/useTableGroupedRows.d.ts.map +1 -0
  280. package/dist/Table/plugins/groupedRows/useTableGroupedRows.js +257 -0
  281. package/dist/Table/plugins/pagination/useTablePagination.d.ts.map +1 -1
  282. package/dist/Table/plugins/pagination/useTablePagination.js +12 -9
  283. package/dist/Table/plugins/rowExpansion/useTableRowExpansion.d.ts +1 -1
  284. package/dist/Table/plugins/rowExpansion/useTableRowExpansion.d.ts.map +1 -1
  285. package/dist/Table/plugins/rowExpansion/useTableRowExpansion.js +30 -28
  286. package/dist/Table/plugins/rowIndex/index.d.ts +3 -0
  287. package/dist/Table/plugins/rowIndex/index.d.ts.map +1 -0
  288. package/dist/Table/plugins/rowIndex/index.js +3 -0
  289. package/dist/Table/plugins/rowIndex/useTableRowIndex.d.ts +50 -0
  290. package/dist/Table/plugins/rowIndex/useTableRowIndex.d.ts.map +1 -0
  291. package/dist/Table/plugins/rowIndex/useTableRowIndex.js +91 -0
  292. package/dist/Table/plugins/selection/useTableSelection.d.ts.map +1 -1
  293. package/dist/Table/plugins/selection/useTableSelection.js +23 -20
  294. package/dist/Table/plugins/sortable/useTableSortable.d.ts.map +1 -1
  295. package/dist/Table/plugins/sortable/useTableSortable.js +23 -21
  296. package/dist/Table/tableContextMenu.js +9 -9
  297. package/dist/Text/Text.js +10 -10
  298. package/dist/TextArea/TextArea.js +14 -14
  299. package/dist/TextInput/TextInput.d.ts.map +1 -1
  300. package/dist/TextInput/TextInput.js +20 -16
  301. package/dist/Thumbnail/Thumbnail.d.ts.map +1 -1
  302. package/dist/Thumbnail/Thumbnail.js +37 -31
  303. package/dist/TimeInput/TimeInput.d.ts +1 -1
  304. package/dist/TimeInput/TimeInput.d.ts.map +1 -1
  305. package/dist/TimeInput/TimeInput.js +28 -23
  306. package/dist/Timestamp/Timestamp.d.ts.map +1 -1
  307. package/dist/Timestamp/Timestamp.js +29 -14
  308. package/dist/Toast/Toast.d.ts.map +1 -1
  309. package/dist/Toast/Toast.js +17 -15
  310. package/dist/Toast/ToastViewport.d.ts.map +1 -1
  311. package/dist/Toast/ToastViewport.js +14 -12
  312. package/dist/Toast/useToast.js +4 -4
  313. package/dist/ToggleButton/ToggleButton.js +12 -12
  314. package/dist/ToggleButton/ToggleButtonGroup.js +5 -5
  315. package/dist/Token/Token.d.ts.map +1 -1
  316. package/dist/Token/Token.js +29 -23
  317. package/dist/Tokenizer/Tokenizer.d.ts.map +1 -1
  318. package/dist/Tokenizer/Tokenizer.js +33 -31
  319. package/dist/Toolbar/Toolbar.js +18 -18
  320. package/dist/Tooltip/Tooltip.js +11 -11
  321. package/dist/Tooltip/useTooltip.js +3 -3
  322. package/dist/TopNav/TopNav.d.ts +2 -1
  323. package/dist/TopNav/TopNav.d.ts.map +1 -1
  324. package/dist/TopNav/TopNav.js +36 -33
  325. package/dist/TopNav/TopNavHeading.d.ts.map +1 -1
  326. package/dist/TopNav/TopNavHeading.js +72 -70
  327. package/dist/TopNav/TopNavItem.js +5 -5
  328. package/dist/TopNav/TopNavMegaMenu.js +30 -30
  329. package/dist/TopNav/TopNavMegaMenuFeaturedCard.js +10 -10
  330. package/dist/TopNav/TopNavMegaMenuItem.js +16 -16
  331. package/dist/TopNav/TopNavMenu.js +32 -32
  332. package/dist/TreeList/TreeList.d.ts.map +1 -1
  333. package/dist/TreeList/TreeList.js +13 -9
  334. package/dist/TreeList/TreeListBranches.js +11 -11
  335. package/dist/TreeList/TreeListItem.d.ts.map +1 -1
  336. package/dist/TreeList/TreeListItem.js +37 -31
  337. package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
  338. package/dist/Typeahead/BaseTypeahead.js +26 -22
  339. package/dist/Typeahead/Typeahead.d.ts.map +1 -1
  340. package/dist/Typeahead/Typeahead.js +15 -13
  341. package/dist/Typeahead/TypeaheadItem.js +10 -10
  342. package/dist/VStack/VStack.js +3 -3
  343. package/dist/VisuallyHidden/VisuallyHidden.d.ts +1 -3
  344. package/dist/VisuallyHidden/VisuallyHidden.d.ts.map +1 -1
  345. package/dist/VisuallyHidden/VisuallyHidden.js +1 -3
  346. package/dist/astryx.css +20 -2
  347. package/dist/astryx.umd.js +66 -64
  348. package/dist/astryx.umd.js.map +4 -4
  349. package/dist/authoring/doc.d.ts +145 -0
  350. package/dist/authoring/doc.d.ts.map +1 -0
  351. package/dist/authoring/doc.js +100 -0
  352. package/dist/authoring/index.d.ts +29 -0
  353. package/dist/authoring/index.d.ts.map +1 -0
  354. package/dist/authoring/index.js +28 -0
  355. package/dist/authoring/integration.d.ts +35 -0
  356. package/dist/authoring/integration.d.ts.map +1 -0
  357. package/dist/authoring/integration.js +30 -0
  358. package/dist/authoring/template.d.ts +66 -0
  359. package/dist/authoring/template.d.ts.map +1 -0
  360. package/dist/authoring/template.js +55 -0
  361. package/dist/config.d.ts +80 -0
  362. package/dist/config.d.ts.map +1 -0
  363. package/dist/config.js +40 -0
  364. package/dist/hooks/useKeyboardHint.js +9 -9
  365. package/dist/i18n/InternationalizationContext.d.ts +12 -0
  366. package/dist/i18n/InternationalizationContext.d.ts.map +1 -0
  367. package/dist/i18n/InternationalizationContext.js +29 -0
  368. package/dist/i18n/InternationalizationProvider.d.ts +62 -0
  369. package/dist/i18n/InternationalizationProvider.d.ts.map +1 -0
  370. package/dist/i18n/InternationalizationProvider.js +44 -0
  371. package/dist/i18n/index.d.ts +24 -0
  372. package/dist/i18n/index.d.ts.map +1 -0
  373. package/dist/i18n/index.js +21 -0
  374. package/dist/i18n/resolve.d.ts +22 -0
  375. package/dist/i18n/resolve.d.ts.map +1 -0
  376. package/dist/i18n/resolve.js +141 -0
  377. package/dist/i18n/translator.d.ts +25 -0
  378. package/dist/i18n/translator.d.ts.map +1 -0
  379. package/dist/i18n/translator.js +1 -0
  380. package/dist/i18n/types.d.ts +47 -0
  381. package/dist/i18n/types.d.ts.map +1 -0
  382. package/dist/i18n/types.js +1 -0
  383. package/dist/i18n/useTranslator.d.ts +15 -0
  384. package/dist/i18n/useTranslator.d.ts.map +1 -0
  385. package/dist/i18n/useTranslator.js +39 -0
  386. package/dist/index.d.ts +1 -0
  387. package/dist/index.d.ts.map +1 -1
  388. package/dist/index.js +3 -0
  389. package/dist/theme/MediaTheme.js +3 -3
  390. package/dist/theme/Theme.js +7 -7
  391. package/dist/theme/syntax/SyntaxTheme.js +5 -5
  392. package/locales/en.json +746 -0
  393. package/locales/pseudo.json +560 -0
  394. package/package.json +24 -3
  395. package/src/AlertDialog/AlertDialog.doc.mjs +5 -0
  396. package/src/AlertDialog/AlertDialog.tsx +4 -1
  397. package/src/AlertDialog/useImperativeAlertDialog.tsx +0 -2
  398. package/src/AppShell/AppShell.doc.mjs +8 -2
  399. package/src/AppShell/AppShell.tsx +3 -1
  400. package/src/AvatarGroup/AvatarGroup.tsx +4 -1
  401. package/src/Banner/Banner.tsx +10 -5
  402. package/src/Breadcrumbs/Breadcrumbs.tsx +4 -1
  403. package/src/Button/Button.tsx +34 -4
  404. package/src/ButtonGroup/ButtonGroup.test.tsx +386 -2
  405. package/src/ButtonGroup/ButtonGroup.tsx +27 -11
  406. package/src/Calendar/Calendar.tsx +8 -5
  407. package/src/Card/Card.doc.mjs +3 -3
  408. package/src/Carousel/Carousel.tsx +10 -7
  409. package/src/Chat/ChatComposer.tsx +5 -1
  410. package/src/Chat/ChatComposerDrawer.tsx +9 -2
  411. package/src/Chat/ChatComposerInput.tsx +10 -4
  412. package/src/Chat/ChatLayout.tsx +5 -12
  413. package/src/Chat/ChatLayoutScrollButton.tsx +4 -2
  414. package/src/Chat/ChatMessage.tsx +5 -1
  415. package/src/Chat/ChatMessageBubble.tsx +2 -0
  416. package/src/Chat/ChatMessageList.doc.mjs +1 -1
  417. package/src/Chat/ChatMessageList.tsx +2 -0
  418. package/src/Chat/ChatMessageMetadata.tsx +15 -9
  419. package/src/Chat/ChatPastedTextToken.tsx +3 -1
  420. package/src/Chat/ChatSendButton.tsx +3 -1
  421. package/src/Chat/ChatToolCalls.doc.mjs +1 -1
  422. package/src/Chat/useSpeechRecognition.ts +11 -2
  423. package/src/Chat/useTriggerMenu.tsx +4 -2
  424. package/src/CheckboxInput/CheckboxInput.test.tsx +16 -0
  425. package/src/CheckboxInput/CheckboxInput.tsx +4 -2
  426. package/src/CheckboxList/CheckboxList.doc.mjs +6 -1
  427. package/src/CheckboxList/CheckboxList.tsx +2 -0
  428. package/src/CheckboxList/CheckboxListItem.tsx +7 -1
  429. package/src/Citation/Citation.tsx +4 -2
  430. package/src/ClickableCard/ClickableCard.doc.mjs +1 -1
  431. package/src/CodeBlock/CodeBlock.tsx +91 -40
  432. package/src/Collapsible/Collapsible.doc.mjs +3 -3
  433. package/src/CommandPalette/CommandPalette.tsx +10 -3
  434. package/src/CommandPalette/CommandPaletteInput.test.tsx +1 -1
  435. package/src/CommandPalette/CommandPaletteInput.tsx +5 -1
  436. package/src/CommandPalette/CommandPaletteList.tsx +4 -1
  437. package/src/ContextMenu/ContextMenu.tsx +4 -1
  438. package/src/DateInput/DateInput.doc.mjs +2 -2
  439. package/src/DateInput/DateInput.tsx +13 -5
  440. package/src/DateRangeInput/DateRangeInput.doc.mjs +7 -7
  441. package/src/DateRangeInput/DateRangeInput.tsx +14 -6
  442. package/src/DateTimeInput/DateTimeInput.doc.mjs +2 -2
  443. package/src/DateTimeInput/DateTimeInput.tsx +17 -7
  444. package/src/Dialog/DialogHeader.tsx +4 -2
  445. package/src/DropdownMenu/DropdownMenu.tsx +12 -8
  446. package/src/EmptyState/EmptyState.doc.mjs +3 -3
  447. package/src/EmptyState/EmptyState.tsx +4 -1
  448. package/src/Field/Field.doc.mjs +2 -2
  449. package/src/FileInput/FileInput.tsx +3 -1
  450. package/src/Grid/Grid.doc.mjs +5 -5
  451. package/src/InputGroup/InputGroup.doc.mjs +1 -1
  452. package/src/Layer/useLayer.doc.mjs +2 -2
  453. package/src/Lightbox/Lightbox.doc.mjs +7 -2
  454. package/src/Lightbox/Lightbox.tsx +6 -4
  455. package/src/Lightbox/useLightbox.tsx +0 -1
  456. package/src/Link/Link.test.tsx +74 -2
  457. package/src/Link/Link.tsx +48 -3
  458. package/src/Markdown/Markdown.tsx +12 -4
  459. package/src/MobileNav/MobileNav.tsx +4 -2
  460. package/src/MobileNav/MobileNavToggle.tsx +5 -3
  461. package/src/MoreMenu/MoreMenu.tsx +4 -1
  462. package/src/MultiSelector/MultiSelector.tsx +14 -5
  463. package/src/NumberInput/NumberInput.tsx +3 -1
  464. package/src/Outline/Outline.tsx +4 -1
  465. package/src/OverflowList/OverflowList.doc.mjs +2 -2
  466. package/src/Overlay/useOverlay.tsx +0 -3
  467. package/src/Pagination/Pagination.tsx +35 -12
  468. package/src/Popover/usePopover.tsx +6 -1
  469. package/src/PowerSearch/PowerSearch.doc.mjs +9 -4
  470. package/src/PowerSearch/PowerSearch.tsx +22 -10
  471. package/src/PowerSearch/PowerSearchEditPopover.tsx +27 -20
  472. package/src/PowerSearch/PowerSearchToken.tsx +17 -6
  473. package/src/PowerSearch/PowerSearchValueEditor.tsx +34 -21
  474. package/src/PowerSearch/formatFilterValue.test.ts +282 -0
  475. package/src/PowerSearch/formatFilterValue.ts +21 -8
  476. package/src/PowerSearch/index.ts +5 -0
  477. package/src/PowerSearch/resolveOperatorLabel.ts +34 -0
  478. package/src/PowerSearch/types.ts +33 -9
  479. package/src/PowerSearch/usePowerSearchConfig.ts +59 -27
  480. package/src/PowerSearch/usePowerSearchSource.ts +17 -8
  481. package/src/RadioList/RadioList.doc.mjs +1 -1
  482. package/src/Resizable/ResizeHandle.tsx +4 -1
  483. package/src/Section/Section.doc.mjs +4 -4
  484. package/src/SelectableCard/SelectableCard.doc.mjs +1 -1
  485. package/src/Selector/Selector.tsx +11 -6
  486. package/src/SideNav/SideNav.tsx +4 -2
  487. package/src/SideNav/SideNavCollapseButton.tsx +3 -1
  488. package/src/SideNav/SideNavHeading.tsx +5 -3
  489. package/src/SideNav/SideNavItem.tsx +3 -1
  490. package/src/Slider/Slider.doc.mjs +2 -2
  491. package/src/Stack/Stack.doc.mjs +10 -10
  492. package/src/Switch/Switch.doc.mjs +2 -2
  493. package/src/Switch/Switch.test.tsx +16 -0
  494. package/src/Switch/Switch.tsx +4 -2
  495. package/src/TabList/TabList.tsx +4 -1
  496. package/src/Table/BaseTable.tsx +8 -1
  497. package/src/Table/Table.doc.mjs +1 -1
  498. package/src/Table/Table.tsx +3 -1
  499. package/src/Table/index.ts +7 -0
  500. package/src/Table/plugins/filtering/useTableFiltering.tsx +31 -30
  501. package/src/Table/plugins/groupedRows/index.ts +7 -0
  502. package/src/Table/plugins/groupedRows/useTableGroupedRows.test.tsx +231 -0
  503. package/src/Table/plugins/groupedRows/useTableGroupedRows.tsx +351 -0
  504. package/src/Table/plugins/pagination/useTablePagination.tsx +4 -1
  505. package/src/Table/plugins/rowExpansion/useTableRowExpansion.tsx +8 -4
  506. package/src/Table/plugins/rowIndex/index.ts +4 -0
  507. package/src/Table/plugins/rowIndex/useTableRowIndex.test.tsx +132 -0
  508. package/src/Table/plugins/rowIndex/useTableRowIndex.tsx +114 -0
  509. package/src/Table/plugins/selection/useTableSelection.tsx +6 -7
  510. package/src/Table/plugins/sortable/useTableSortable.tsx +8 -5
  511. package/src/Table/useTableFiltering.doc.mjs +2 -2
  512. package/src/Table/useTableGroupedRows.doc.mjs +71 -0
  513. package/src/Table/useTableRowIndex.doc.mjs +50 -0
  514. package/src/Table/useTableSortable.doc.mjs +11 -11
  515. package/src/Text/Text.doc.mjs +2 -2
  516. package/src/TextInput/TextInput.tsx +3 -1
  517. package/src/Thumbnail/Thumbnail.tsx +4 -2
  518. package/src/TimeInput/TimeInput.doc.mjs +2 -2
  519. package/src/TimeInput/TimeInput.tsx +6 -2
  520. package/src/Timestamp/Timestamp.doc.mjs +4 -4
  521. package/src/Timestamp/Timestamp.test.tsx +120 -7
  522. package/src/Timestamp/Timestamp.tsx +21 -1
  523. package/src/Toast/Toast.tsx +3 -1
  524. package/src/Toast/ToastViewport.tsx +3 -1
  525. package/src/Token/Token.tsx +4 -2
  526. package/src/Tokenizer/Tokenizer.doc.mjs +2 -2
  527. package/src/Tokenizer/Tokenizer.tsx +4 -4
  528. package/src/Toolbar/Toolbar.doc.mjs +1 -1
  529. package/src/TopNav/TopNav.doc.mjs +1 -0
  530. package/src/TopNav/TopNav.test.tsx +30 -0
  531. package/src/TopNav/TopNav.tsx +6 -4
  532. package/src/TopNav/TopNavHeading.tsx +5 -3
  533. package/src/TreeList/TreeList.tsx +5 -1
  534. package/src/TreeList/TreeListItem.tsx +6 -1
  535. package/src/Typeahead/BaseTypeahead.tsx +11 -4
  536. package/src/Typeahead/Typeahead.doc.mjs +1 -1
  537. package/src/Typeahead/Typeahead.tsx +3 -1
  538. package/src/VisuallyHidden/VisuallyHidden.tsx +1 -3
  539. package/src/authoring/doc.ts +166 -0
  540. package/src/authoring/index.test.ts +52 -0
  541. package/src/authoring/index.ts +50 -0
  542. package/src/authoring/integration.ts +42 -0
  543. package/src/authoring/template.ts +74 -0
  544. package/src/config.test.ts +16 -0
  545. package/src/config.ts +92 -0
  546. package/src/docPropLiterals.test.ts +902 -0
  547. package/src/hooks/useHotkeys.doc.mjs +5 -5
  548. package/src/i18n/InternationalizationContext.ts +39 -0
  549. package/src/i18n/InternationalizationProvider.doc.mjs +72 -0
  550. package/src/i18n/InternationalizationProvider.tsx +83 -0
  551. package/src/i18n/__tests__/e2e-pagination.test.tsx +172 -0
  552. package/src/i18n/__tests__/e2e-powersearch.test.tsx +130 -0
  553. package/src/i18n/__tests__/resolve.test.ts +284 -0
  554. package/src/i18n/index.ts +32 -0
  555. package/src/i18n/resolve.ts +165 -0
  556. package/src/i18n/translator.ts +31 -0
  557. package/src/i18n/types.ts +53 -0
  558. package/src/i18n/useTranslator.doc.mjs +46 -0
  559. package/src/i18n/useTranslator.ts +50 -0
  560. package/src/index.ts +3 -0
  561. package/dist/NavItem/index.d.ts +0 -9
  562. package/dist/NavItem/index.d.ts.map +0 -1
  563. package/dist/NavItem/index.js +0 -10
  564. package/src/NavItem/index.ts +0 -11
@@ -0,0 +1,902 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /* eslint-disable @typescript-eslint/no-require-imports */
4
+ /**
5
+ * @file Guards doc prop types against opaque named unions (#1645).
6
+ * @input Every {Name}.doc.mjs plus the TypeScript sources they describe.
7
+ * @output Fails when a documented prop type hides its legal literal values.
8
+ * @position Repo-wide doc-shape guard, sibling of docPropReferences.test.ts.
9
+ *
10
+ * A prop type of `SpacingStep` or `TableSortState<TSortKey>` names a type
11
+ * without revealing what may be written into it. Readers without an IDE —
12
+ * agents especially — then guess: `gap={16}` (pixels, not a scale step),
13
+ * `direction: 'desc'` (the type says `'descending'`). Both fail to compile.
14
+ * `PropDoc.type` in docs-types.ts already asks for inlined unions
15
+ * (`"'primary' | 'secondary' | 'ghost'"`); this test holds docs to it.
16
+ *
17
+ * Two ways a doc prop can hide values, checked at different strictness:
18
+ *
19
+ * 1. DIRECT — the prop type *is* (or contains) a literal-union type name.
20
+ * `gap: SpacingStep` must read `gap: 0 | 0.5 | 1 | …`. The name never
21
+ * buys the reader anything the literals don't, so inlining is required;
22
+ * prose is not an escape (see ENUMERATED_IN_PROSE for the one exception).
23
+ *
24
+ * 2. INDIRECT — the prop type names a composite whose own definition
25
+ * carries a literal union: `status: InputStatus` hides
26
+ * `'warning' | 'error' | 'success'`, `sort: TableSortState` hides
27
+ * `direction: 'ascending' | 'descending'`. Here the values may be
28
+ * surfaced in the type (spell the shape out — preferred when the object
29
+ * is small) *or* in the description, because forcing a twelve-field
30
+ * object into the type column would make the docs worse, not better.
31
+ *
32
+ * Both registries are derived from the TypeScript sources at test time, so a
33
+ * new member on a union (or a renamed one) fails here until docs catch up.
34
+ */
35
+
36
+ import {describe, it, expect} from 'vitest';
37
+ import {readdirSync, readFileSync} from 'node:fs';
38
+ import {join, relative} from 'node:path';
39
+
40
+ const SRC_DIR = __dirname;
41
+
42
+ type DocExport = {
43
+ name?: string;
44
+ props?: PropEntry[];
45
+ components?: {name?: string; props?: PropEntry[]}[];
46
+ };
47
+
48
+ type DocModule = {
49
+ docs?: DocExport;
50
+ docsZh?: DocExport;
51
+ };
52
+
53
+ type PropEntry = {name: string; type?: string; description?: string};
54
+
55
+ /**
56
+ * Unions too large to read inside a prop-type cell. Exempt from inlining,
57
+ * but only while the prop's description enumerates every member — the
58
+ * values still have to be discoverable from the docs alone.
59
+ */
60
+ const ENUMERATED_IN_PROSE = new Set(['IconName']);
61
+
62
+ /**
63
+ * Generic wrappers whose type argument is still a value the consumer writes,
64
+ * so the walk descends through them. Every other generic (`ReactElement<P>`,
65
+ * `Record<K, V>`, `Ref<T>`) wraps a type the consumer never spells out by
66
+ * hand, and descending into it would demand, say, all 26 icon names in
67
+ * Button's `endContent` docs.
68
+ */
69
+ const TRANSPARENT_WRAPPERS = new Set(['Array', 'ReadonlyArray']);
70
+
71
+ /**
72
+ * A union of nothing but string/number literals (plus null/undefined).
73
+ *
74
+ * Built so each separator has exactly ONE way to be parsed. The obvious
75
+ * spelling — `(?:\s*\|?\s*LITERAL\s*(?:\|\s*)?)+` — lets a repetition's
76
+ * trailing `(?:\|\s*)?` and the next one's leading `\|?` both claim the same
77
+ * pipe, so every separator doubles the parse tree. On a union that *fails* to
78
+ * match (a literal prefix ending in a named type, e.g. `'a' | 'b' | … |
79
+ * ComponentType` — an ordinary TS pattern) the engine explores all 2^n of them:
80
+ * measured at 1.2s for 10 members and 7.8s for 12. This form is linear, and
81
+ * agrees with the old one on every type alias in the package.
82
+ */
83
+ const LITERAL = String.raw`(?:'[^']*'|-?\d+(?:\.\d+)?|null|undefined)`;
84
+ const LITERAL_UNION = new RegExp(
85
+ String.raw`^\s*\|?\s*${LITERAL}(?:\s*\|\s*${LITERAL})*\s*$`,
86
+ );
87
+
88
+ function findFiles(dir: string, suffixes: string[]): string[] {
89
+ const out: string[] = [];
90
+ for (const entry of readdirSync(dir, {withFileTypes: true})) {
91
+ const full = join(dir, entry.name);
92
+ if (entry.isDirectory()) {
93
+ out.push(...findFiles(full, suffixes));
94
+ } else if (suffixes.some(s => entry.name.endsWith(s))) {
95
+ out.push(full);
96
+ }
97
+ }
98
+ return out;
99
+ }
100
+
101
+ /** Strip comments so a doc block's prose never reads as type syntax. */
102
+ function stripComments(src: string): string {
103
+ return src.replace(/\/\*[\s\S]*?\*\//g, '').replace(/\/\/.*$/gm, '');
104
+ }
105
+
106
+ /**
107
+ * Names a consumer would have to write values for, given a type expression.
108
+ * Skips string literals, generic parameters, and the type arguments of any
109
+ * wrapper that isn't transparent.
110
+ */
111
+ function authoredTypeNames(expr: string): string[] {
112
+ const names: string[] = [];
113
+ const text = expr.replace(/'[^']*'/g, "''");
114
+ let i = 0;
115
+ while (i < text.length) {
116
+ const match = /[A-Za-z_$][A-Za-z0-9_$]*/.exec(text.slice(i));
117
+ if (!match) {
118
+ break;
119
+ }
120
+ const start = i + match.index;
121
+ const name = match[0];
122
+ let end = start + name.length;
123
+ if (/^[A-Z]/.test(name)) {
124
+ names.push(name);
125
+ }
126
+ // A '<' directly after the name opens that name's type arguments.
127
+ const rest = text.slice(end);
128
+ const open = /^\s*</.exec(rest);
129
+ if (open) {
130
+ let depth = 0;
131
+ let j = end + open[0].length - 1;
132
+ for (; j < text.length; j++) {
133
+ if (text[j] === '<') {
134
+ depth++;
135
+ } else if (text[j] === '>') {
136
+ depth--;
137
+ if (depth === 0) {
138
+ break;
139
+ }
140
+ }
141
+ }
142
+ const args = text.slice(end + open[0].length, j);
143
+ if (TRANSPARENT_WRAPPERS.has(name)) {
144
+ names.push(...authoredTypeNames(args));
145
+ }
146
+ end = j + 1;
147
+ }
148
+ i = end;
149
+ }
150
+ return names;
151
+ }
152
+
153
+ // --- Registries, read straight from the TypeScript sources -----------------
154
+
155
+ /** Literal-union type name → its members, e.g. "'start' | 'center'". */
156
+ const literalUnions = new Map<string, string>();
157
+ /** Every other exported type/interface name → its definition body. */
158
+ const namedBodies = new Map<string, string>();
159
+
160
+ for (const file of findFiles(SRC_DIR, ['.ts', '.tsx'])) {
161
+ if (file.includes('.test.')) {
162
+ continue;
163
+ }
164
+ const src = stripComments(readFileSync(file, 'utf8'));
165
+ for (const m of src.matchAll(
166
+ /export type ([A-Z][A-Za-z0-9]*)(?:<[^>]*>)?\s*=\s*([^;]+);/g,
167
+ )) {
168
+ const [, name, rhsRaw] = m;
169
+ const rhs = rhsRaw.replace(/\s+/g, ' ').trim();
170
+ if (LITERAL_UNION.test(rhs)) {
171
+ literalUnions.set(
172
+ name,
173
+ rhs
174
+ .replace(/^\|\s*/, '')
175
+ .split('|')
176
+ .map(s => s.trim())
177
+ .filter(Boolean)
178
+ .join(' | '),
179
+ );
180
+ } else {
181
+ namedBodies.set(name, rhs);
182
+ }
183
+ }
184
+ for (const m of src.matchAll(
185
+ /export interface ([A-Z][A-Za-z0-9]*)(?:<[^>]*>)?\s*(?:extends [^{]+)?\{/g,
186
+ )) {
187
+ const open = src.indexOf('{', m.index);
188
+ let depth = 0;
189
+ let close = open;
190
+ for (; close < src.length; close++) {
191
+ if (src[close] === '{') {
192
+ depth++;
193
+ } else if (src[close] === '}') {
194
+ depth--;
195
+ if (depth === 0) {
196
+ break;
197
+ }
198
+ }
199
+ }
200
+ namedBodies.set(m[1], src.slice(open, close + 1).replace(/\s+/g, ' '));
201
+ }
202
+ }
203
+
204
+ /** Literal unions a consumer must know to author a value of `name`. */
205
+ function requiredUnions(
206
+ name: string,
207
+ seen = new Set<string>(),
208
+ ): Map<string, string> {
209
+ const found = new Map<string, string>();
210
+ if (seen.has(name)) {
211
+ return found;
212
+ }
213
+ seen.add(name);
214
+ const literals = literalUnions.get(name);
215
+ if (literals) {
216
+ found.set(name, literals);
217
+ return found;
218
+ }
219
+ const body = namedBodies.get(name);
220
+ if (!body) {
221
+ return found;
222
+ }
223
+ for (const inner of authoredTypeNames(body)) {
224
+ for (const [k, v] of requiredUnions(inner, seen)) {
225
+ found.set(k, v);
226
+ }
227
+ }
228
+ return found;
229
+ }
230
+
231
+ // --- Source prop declarations, per component directory ---------------------
232
+
233
+ /**
234
+ * "{dir}::{Component}Props" → prop name → the type expression source declares.
235
+ *
236
+ * Keyed by the exact prop bag, never by directory: Chat/ alone holds
237
+ * ChatComposer, ChatMessage and ChatToolCalls, each with a differently-typed
238
+ * `status`, and judging one component's docs against a sibling's declaration
239
+ * is a false accusation. A component with no `{Name}Props` bag here (Heading
240
+ * re-exports its props from elsewhere) is simply not checked by this pass —
241
+ * a missed prop costs nothing, a wrong failure costs the guard its
242
+ * credibility.
243
+ */
244
+ const sourcePropTypes = new Map<string, Map<string, string>>();
245
+
246
+ /**
247
+ * Bag name → its members, for bags whose name is unique across the package;
248
+ * `null` marks a name declared in more than one place, which is never
249
+ * resolved rather than resolved wrongly.
250
+ *
251
+ * A hook's config bag does not live in the directory its doc does:
252
+ * `useTableSortable.doc.mjs` sits in `Table/`, but `UseTableSortableConfig`
253
+ * is declared in `Table/plugins/sortable/`. Directory-keying alone therefore
254
+ * cannot see it — which is why `sort`, the very prop #1645 was filed about,
255
+ * had no source-side guard at all until this index existed.
256
+ */
257
+ const bagsByName = new Map<string, Map<string, string> | null>();
258
+
259
+ /**
260
+ * Bags a hook's config may be called. Astryx spells it `Use{Name}Config` or
261
+ * `Use{Name}Options` (see UseTableSortableConfig, UsePopoverOptions); the
262
+ * other two are cheap insurance and cost nothing when absent.
263
+ */
264
+ const HOOK_BAG_SUFFIXES = ['Config', 'Options', 'Props', 'Params'];
265
+
266
+ for (const file of findFiles(SRC_DIR, ['.ts', '.tsx'])) {
267
+ if (file.includes('.test.')) {
268
+ continue;
269
+ }
270
+ const dir = file.slice(0, file.lastIndexOf('/'));
271
+ const src = stripComments(readFileSync(file, 'utf8'));
272
+ for (const m of src.matchAll(
273
+ /export (?:interface|type) ([A-Z][A-Za-z0-9]*(?:Props|Config|Options|Params))(?:<[^>]*>)?\s*(?:extends [^{]+|=\s*)?\{/g,
274
+ )) {
275
+ const open = src.indexOf('{', m.index);
276
+ let depth = 0;
277
+ let close = open;
278
+ for (; close < src.length; close++) {
279
+ if (src[close] === '{') {
280
+ depth++;
281
+ } else if (src[close] === '}') {
282
+ depth--;
283
+ if (depth === 0) {
284
+ break;
285
+ }
286
+ }
287
+ }
288
+ const body = src.slice(open + 1, close);
289
+ const byProp = new Map<string, string>();
290
+ // Top-level `name?: Type;` members only — nested object literals would
291
+ // need a real parser, and a missed prop costs nothing here.
292
+ let depthInBody = 0;
293
+ for (const rawLine of body.split(/[;\n]/)) {
294
+ // `=>` is an arrow, not a closing angle bracket: counting it as one
295
+ // drives the depth negative and silently swallows every prop below a
296
+ // callback member. (It did, until this line.)
297
+ const line = rawLine.replace(/=>/g, '');
298
+ const opens = (line.match(/[{(<]/g) || []).length;
299
+ const closes = (line.match(/[})>]/g) || []).length;
300
+ const member = /^\s*([a-z][A-Za-z0-9]*)\??\s*:\s*(.+?)\s*$/.exec(rawLine);
301
+ if (depthInBody === 0 && member) {
302
+ byProp.set(member[1], member[2]);
303
+ }
304
+ depthInBody = Math.max(0, depthInBody + opens - closes);
305
+ }
306
+ sourcePropTypes.set(`${dir}::${m[1]}`, byProp);
307
+ // Second index, keyed by bag name alone, for the cross-directory hook
308
+ // lookup below. A duplicate name poisons the entry: guessing between two
309
+ // declarations is how a guard earns a false accusation.
310
+ bagsByName.set(m[1], bagsByName.has(m[1]) ? null : byProp);
311
+ }
312
+ }
313
+
314
+ /**
315
+ * The type source declares for `Component.prop`, if it can be found.
316
+ *
317
+ * Two lookups, most-specific first:
318
+ * 1. The component's own `{Name}Props` bag in its own directory. Exact, and
319
+ * the only pass that runs for components — Chat/ holds three differently
320
+ * typed `status` props, so a directory-wide search would misjudge them.
321
+ * 2. For hooks only (`useThing`), the uniquely-named `UseThingConfig` /
322
+ * `UseThingOptions` bag, wherever in the package it is declared.
323
+ *
324
+ * Anything else stays undefined and is judged on the doc alone: a missed prop
325
+ * costs nothing, a wrong failure costs the guard its credibility. (Heading
326
+ * re-exports its props and so has no bag here — deliberately unresolved.)
327
+ */
328
+ function declaredType(
329
+ dir: string,
330
+ component: string,
331
+ prop: string,
332
+ ): string | undefined {
333
+ const own = sourcePropTypes.get(`${dir}::${component}Props`)?.get(prop);
334
+ if (own !== undefined) {
335
+ return own;
336
+ }
337
+ if (!/^use[A-Z]/.test(component)) {
338
+ return undefined;
339
+ }
340
+ const pascal = component[0].toUpperCase() + component.slice(1);
341
+ for (const suffix of HOOK_BAG_SUFFIXES) {
342
+ const declared = bagsByName.get(`${pascal}${suffix}`)?.get(prop);
343
+ if (declared !== undefined) {
344
+ return declared;
345
+ }
346
+ }
347
+ return undefined;
348
+ }
349
+
350
+ // --- Doc props -------------------------------------------------------------
351
+
352
+ const docFiles = findFiles(SRC_DIR, ['.doc.mjs']);
353
+
354
+ interface DocProp {
355
+ file: string;
356
+ dir: string;
357
+ component: string;
358
+ prop: string;
359
+ type: string;
360
+ description: string;
361
+ }
362
+
363
+ const docProps: DocProp[] = [];
364
+ for (const file of docFiles) {
365
+ const mod = require(file) as DocModule;
366
+ // A type is language-neutral: the translated docs carry their own props[]
367
+ // (the `--zh` CLI surface renders them), so they are held to the same bar.
368
+ for (const key of ['docs', 'docsZh'] as const) {
369
+ const docs = mod[key];
370
+ if (!docs) {
371
+ continue;
372
+ }
373
+ const entries = [
374
+ {name: docs.name, props: docs.props},
375
+ ...(docs.components || []),
376
+ ];
377
+ for (const {name, props} of entries) {
378
+ if (!name || !Array.isArray(props)) {
379
+ continue;
380
+ }
381
+ for (const prop of props) {
382
+ if (typeof prop.type !== 'string') {
383
+ continue;
384
+ }
385
+ docProps.push({
386
+ file: `${relative(SRC_DIR, file)}${key === 'docsZh' ? ' (zh)' : ''}`,
387
+ dir: file.slice(0, file.lastIndexOf('/')),
388
+ component: name,
389
+ prop: prop.name,
390
+ type: prop.type,
391
+ description: prop.description || '',
392
+ });
393
+ }
394
+ }
395
+ }
396
+ }
397
+
398
+ function escapeRe(s: string): string {
399
+ return s.replace(/[.*+?^${}()|[\]\\-]/g, '\\$&');
400
+ }
401
+
402
+ /** The value members of a union — `null`/`undefined` carry nothing to spell. */
403
+ function literalMembers(literals: string): string[] {
404
+ return literals
405
+ .split('|')
406
+ .map(s => s.trim())
407
+ .filter(s => s.startsWith("'") || /^-?\d/.test(s));
408
+ }
409
+
410
+ /**
411
+ * Where a literal has to be written to count as surfaced.
412
+ *
413
+ * `code` — a type column. The literal must appear *as a literal*: quotes
414
+ * intact for strings, digit-boundary-anchored for numbers.
415
+ * `prose` — a free-text description. Quotes are optional (prose may say
416
+ * `direction is ascending or descending`), but the boundary still holds.
417
+ *
418
+ * The boundary is the whole point. A bare `text.includes('sm')` is satisfied
419
+ * by `'xsm'`, and `text.includes('1')` by `1.5` or `10` — so a doc could drop
420
+ * a member and stay "green" purely because a longer sibling contains its
421
+ * characters. Every union whose members share substrings (TextSize, TextType,
422
+ * SpacingStep, IconName) is exposed to that, which is most of them.
423
+ */
424
+ type Surface = 'code' | 'prose';
425
+
426
+ function isSurfaced(text: string, literal: string, surface: Surface): boolean {
427
+ const isString = literal.startsWith("'");
428
+ const inner = isString ? literal.slice(1, -1) : literal;
429
+ if (isString) {
430
+ // In code, the quotes are part of the literal: 'xsm' does not contain
431
+ // the *literal* 'sm', only the characters. In prose they are optional.
432
+ const body = escapeRe(inner);
433
+ return surface === 'code'
434
+ ? new RegExp(`['"\`]${body}['"\`]`).test(text)
435
+ : new RegExp(`(?<![\\w-])${body}(?![\\w-])`).test(text);
436
+ }
437
+ // Numbers: reject a match that is really part of a longer number — `1` must
438
+ // not be found inside `1.5`, `10` or `-1`. A trailing dot is allowed only
439
+ // when it is sentence punctuation ("steps go 0, 0.5, 1."), not a decimal.
440
+ return new RegExp(`(?<![\\w.-])${escapeRe(inner)}(?![\\w-])(?!\\.\\d)`).test(
441
+ text,
442
+ );
443
+ }
444
+
445
+ /** Members of `literals` not written out in `text`. */
446
+ function missingFrom(
447
+ text: string,
448
+ literals: string,
449
+ surface: Surface = 'code',
450
+ ): string[] {
451
+ return literalMembers(literals).filter(
452
+ literal => !isSurfaced(text, literal, surface),
453
+ );
454
+ }
455
+
456
+ interface Violation {
457
+ where: string;
458
+ detail: string;
459
+ }
460
+
461
+ /**
462
+ * Reasons this one prop hides values a reader would have to guess at.
463
+ *
464
+ * `resolveFrom` is the type expression whose unions the reader must know;
465
+ * `type`/`description` are what the docs actually say. They are the same
466
+ * expression for the doc-side pass, and differ for the source-side pass,
467
+ * where the component's own `.tsx` declaration is the authority and the docs
468
+ * are the thing on trial. That second pass is what makes the guard
469
+ * drift-proof: inlined literals go stale the moment the union in source
470
+ * gains a member, and only the source knows that happened.
471
+ *
472
+ * Pure — the synthetic cases below exercise it directly, so the guard is
473
+ * proven to bite without dirtying a real doc.
474
+ */
475
+ function hiddenValueReasons(
476
+ type: string,
477
+ description: string,
478
+ resolveFrom: string = type,
479
+ ): string[] {
480
+ const reasons: string[] = [];
481
+ for (const named of authoredTypeNames(resolveFrom)) {
482
+ for (const [union, literals] of requiredUnions(named)) {
483
+ if (ENUMERATED_IN_PROSE.has(union)) {
484
+ const missing = missingFrom(description, literals, 'prose');
485
+ if (missing.length > 0) {
486
+ reasons.push(
487
+ `${union} is exempt from inlining only while its description ` +
488
+ `enumerates every value; these are missing from the ` +
489
+ `description: ${missing.join(', ')}.`,
490
+ );
491
+ }
492
+ } else if (named === union) {
493
+ // DIRECT: the name is the union. Inlining is always affordable, so
494
+ // the type column is the only surface that counts.
495
+ if (missingFrom(type, literals, 'code').length > 0) {
496
+ reasons.push(
497
+ `names the union ${union} instead of inlining it. ` +
498
+ `Write: "${literals}".`,
499
+ );
500
+ }
501
+ } else if (
502
+ // INDIRECT: the union hides inside a composite. Spell the shape out
503
+ // in the type, or name the values in the description. Each surface is
504
+ // read on its own terms — the type as code, the description as prose —
505
+ // rather than concatenated, so a value half-spelled in one and
506
+ // half-mentioned in the other cannot pass for a value that is written
507
+ // down anywhere.
508
+ literalMembers(literals).some(
509
+ literal =>
510
+ !isSurfaced(type, literal, 'code') &&
511
+ !isSurfaced(description, literal, 'prose'),
512
+ )
513
+ ) {
514
+ reasons.push(
515
+ `${named} hides ${union} = ${literals}, and neither the type nor ` +
516
+ `the description says so. Spell the shape out in the type ` +
517
+ `(preferred for small objects) or name the legal values in the ` +
518
+ `description, so a reader without an IDE can see what is legal.`,
519
+ );
520
+ }
521
+ }
522
+ }
523
+ return reasons;
524
+ }
525
+
526
+ function findViolations(): Violation[] {
527
+ const violations: Violation[] = [];
528
+ for (const {file, dir, component, prop, type, description} of docProps) {
529
+ const where = `${file} — ${component}.${prop}: "${type}"`;
530
+ const seen = new Set<string>();
531
+ const report = (detail: string) => {
532
+ if (!seen.has(detail)) {
533
+ seen.add(detail);
534
+ violations.push({where, detail});
535
+ }
536
+ };
537
+ // Pass 1: what the docs themselves name.
538
+ for (const detail of hiddenValueReasons(type, description)) {
539
+ report(detail);
540
+ }
541
+ // Pass 2: what the component actually declares in source.
542
+ const declared = declaredType(dir, component, prop);
543
+ if (declared) {
544
+ for (const detail of hiddenValueReasons(type, description, declared)) {
545
+ report(`source declares \`${prop}: ${declared}\` — ${detail}`);
546
+ }
547
+ }
548
+ }
549
+ return violations;
550
+ }
551
+
552
+ describe('doc prop types surface their literal values (#1645)', () => {
553
+ it('sanity: finds doc props and literal-union types in source', () => {
554
+ expect(docProps.length).toBeGreaterThan(0);
555
+ expect(literalUnions.size).toBeGreaterThan(0);
556
+ });
557
+
558
+ it('sanity: the registries read the real source definitions', () => {
559
+ expect(literalUnions.get('SpacingStep')).toBe(
560
+ '0 | 0.5 | 1 | 1.5 | 2 | 3 | 4 | 5 | 6 | 8 | 10',
561
+ );
562
+ expect(literalUnions.get('TableSortDirection')).toBe(
563
+ "'ascending' | 'descending'",
564
+ );
565
+ expect([...requiredUnions('InputStatus').keys()]).toContain(
566
+ 'InputStatusType',
567
+ );
568
+ expect([...requiredUnions('TableSortState').keys()]).toContain(
569
+ 'TableSortDirection',
570
+ );
571
+ });
572
+
573
+ it('sanity: does not descend into opaque generic wrappers', () => {
574
+ // Button's endContent is ReactElement<IconProps>; the consumer writes
575
+ // <Icon icon="check" />, not an IconProps object, so IconName's 26
576
+ // values are Icon's business, not Button's.
577
+ expect(authoredTypeNames('ReactElement<IconProps>')).toEqual([
578
+ 'ReactElement',
579
+ ]);
580
+ expect(authoredTypeNames('Record<string, MarkdownSource>')).toEqual([
581
+ 'Record',
582
+ ]);
583
+ expect(
584
+ authoredTypeNames('ReadonlyArray<TableSortEntry<TSortKey>>'),
585
+ ).toEqual(['ReadonlyArray', 'TableSortEntry']);
586
+ });
587
+
588
+ it('LITERAL_UNION rejects a long near-miss union in linear time', () => {
589
+ // Every `export type` in the package is fed to this regex, so it must stay
590
+ // cheap on the ones that DON'T match. The dangerous shape is a long literal
591
+ // prefix ending in something that isn't a literal — `'a' | 'b' | … |
592
+ // ComponentType`, the autocomplete escape hatch — which the regex can only
593
+ // reject after trying every way to parse it. An ambiguous pattern has 2^n
594
+ // of those. This union is 24 members long: linear, it is instant; ambiguous,
595
+ // it would outlive the CI job.
596
+ const nearMiss =
597
+ Array.from({length: 24}, (_, i) => `'v${i}'`).join(' | ') +
598
+ ' | ComponentType';
599
+ const start = performance.now();
600
+ expect(LITERAL_UNION.test(nearMiss)).toBe(false);
601
+ expect(performance.now() - start).toBeLessThan(100);
602
+ });
603
+
604
+ it('no documented prop type hides its legal values', () => {
605
+ const violations = findViolations();
606
+ expect(
607
+ violations,
608
+ violations.map(v => `${v.where}\n ${v.detail}`).join('\n'),
609
+ ).toEqual([]);
610
+ });
611
+
612
+ describe('the guard bites (synthetic props, not real docs)', () => {
613
+ it('flags a bare literal-union name and accepts the inlined form', () => {
614
+ expect(hiddenValueReasons('SpacingStep', 'Gap between items.')).toEqual([
615
+ expect.stringContaining('names the union SpacingStep'),
616
+ ]);
617
+ expect(
618
+ hiddenValueReasons(
619
+ '0 | 0.5 | 1 | 1.5 | 2 | 3 | 4 | 5 | 6 | 8 | 10',
620
+ 'Gap between items.',
621
+ ),
622
+ ).toEqual([]);
623
+ });
624
+
625
+ it('is not fooled by prose alone for a direct union', () => {
626
+ // The #1645 failure mode: prose hints at the scale, the type column
627
+ // still says nothing, and the CLI signature builder (which splits the
628
+ // type on `|`) still has no values to print.
629
+ expect(
630
+ hiddenValueReasons('SpacingStep', 'Steps: 0, 0.5, 1, 1.5, 2, 3, 4.'),
631
+ ).not.toEqual([]);
632
+ });
633
+
634
+ it('flags a partially inlined union — one forgotten member is enough', () => {
635
+ // Judged against what source declares, so a doc that drops 'stretch'
636
+ // (or an inlined SpacingStep that misses a step the scale later gains)
637
+ // is caught even though the doc no longer names the type at all.
638
+ expect(
639
+ hiddenValueReasons(
640
+ "'start' | 'center' | 'end'",
641
+ 'Alignment.',
642
+ 'GridAlignment',
643
+ ),
644
+ ).toEqual([expect.stringContaining('GridAlignment')]);
645
+ expect(
646
+ hiddenValueReasons(
647
+ "'start' | 'center' | 'end' | 'stretch'",
648
+ 'Alignment.',
649
+ 'GridAlignment',
650
+ ),
651
+ ).toEqual([]);
652
+ });
653
+
654
+ it('the source-side pass indexes the real component prop bags', () => {
655
+ const gridDir = docProps.find(p => p.component === 'Grid')!.dir;
656
+ expect(declaredType(gridDir, 'Grid', 'gap')).toBe('SpacingStep');
657
+ expect(declaredType(gridDir, 'Grid', 'align')).toBe('GridAlignment');
658
+ });
659
+
660
+ it('never judges a component against a sibling in the same directory', () => {
661
+ // Chat/ declares three differently-typed `status` props. Reading
662
+ // ChatComposer's docs against ChatMessageMetadata's declaration would
663
+ // be a false accusation, so the index is keyed by prop bag, not folder.
664
+ const chatDir = docProps.find(p => p.component === 'ChatComposer')!.dir;
665
+ expect(declaredType(chatDir, 'ChatComposer', 'status')).toBe(
666
+ 'ChatComposerStatus',
667
+ );
668
+ expect(declaredType(chatDir, 'ChatMessageMetadata', 'status')).toBe(
669
+ 'ChatMessageStatus',
670
+ );
671
+ // Heading re-exports its props, so there is no HeadingProps bag here:
672
+ // the pass must stay silent rather than borrow TextProps.
673
+ const textDir = docProps.find(p => p.component === 'Text')!.dir;
674
+ expect(declaredType(textDir, 'Heading', 'type')).toBeUndefined();
675
+ });
676
+
677
+ it('flags a composite that hides a union, and takes either surface', () => {
678
+ expect(hiddenValueReasons('InputStatus', 'Validation status.')).toEqual([
679
+ expect.stringContaining('InputStatus hides InputStatusType'),
680
+ ]);
681
+ // Spelled out in the type…
682
+ expect(
683
+ hiddenValueReasons(
684
+ "{type: 'warning' | 'error' | 'success', message?: string}",
685
+ 'Validation status.',
686
+ ),
687
+ ).toEqual([]);
688
+ // …or named in the description.
689
+ expect(
690
+ hiddenValueReasons(
691
+ 'InputStatus',
692
+ "Validation status. type is 'warning', 'error', or 'success'.",
693
+ ),
694
+ ).toEqual([]);
695
+ });
696
+
697
+ it("catches the issue's headline: sort direction is not 'asc'/'desc'", () => {
698
+ expect(
699
+ hiddenValueReasons(
700
+ 'TableSortState<TSortKey>',
701
+ 'Ordered array of {sortKey, direction} entries.',
702
+ ),
703
+ ).toEqual([expect.stringContaining("'ascending' | 'descending'")]);
704
+ // The word "descending" contains "ascending"'s literal only as a
705
+ // substring of itself, so a doc naming just one is still flagged.
706
+ expect(
707
+ hiddenValueReasons(
708
+ 'TableSortState<TSortKey>',
709
+ "Entries sort 'ascending' by default.",
710
+ ),
711
+ ).toEqual([expect.stringContaining("'ascending' | 'descending'")]);
712
+ });
713
+
714
+ it('lets a value-free type through untouched', () => {
715
+ expect(hiddenValueReasons('boolean', 'Disables the control.')).toEqual(
716
+ [],
717
+ );
718
+ expect(hiddenValueReasons('ReactNode', 'Slot content.')).toEqual([]);
719
+ expect(hiddenValueReasons('(value: string) => void', 'Called.')).toEqual(
720
+ [],
721
+ );
722
+ });
723
+
724
+ it('a longer sibling literal does not mask a shorter one', () => {
725
+ // A substring match lies: 'sm' looks "present" inside 'xsm', and the
726
+ // spacing step 1 looks "present" inside 1.5 and 10. Both unions below
727
+ // are complete EXCEPT for the masked member — so the *only* thing that
728
+ // can fail them is the guard noticing the masked one is really gone.
729
+ // (Every other member is still listed. A test that drops extra members
730
+ // too would pass on the substring match alone and prove nothing —
731
+ // which is precisely why GridAlignment, whose members share no
732
+ // substrings, cannot exercise this class at all.)
733
+ expect(
734
+ hiddenValueReasons(
735
+ // TextSize without 'sm'. 'xsm' is still there, masking it.
736
+ "'4xs' | '3xs' | '2xs' | 'xsm' | 'base' | 'lg' | 'xl' | '2xl' | " +
737
+ "'3xl' | '4xl'",
738
+ 'Size.',
739
+ 'TextSize',
740
+ ),
741
+ ).toEqual([expect.stringContaining('TextSize')]);
742
+ expect(
743
+ hiddenValueReasons(
744
+ // SpacingStep without the step 1. 1.5 and 10 still mask it.
745
+ '0 | 0.5 | 1.5 | 2 | 3 | 4 | 5 | 6 | 8 | 10',
746
+ 'Gap.',
747
+ 'SpacingStep',
748
+ ),
749
+ ).toEqual([expect.stringContaining('SpacingStep')]);
750
+ // …and the complete unions still pass, so the boundary match is not
751
+ // simply rejecting everything.
752
+ expect(
753
+ hiddenValueReasons(
754
+ "'4xs' | '3xs' | '2xs' | 'xsm' | 'sm' | 'base' | 'lg' | 'xl' | " +
755
+ "'2xl' | '3xl' | '4xl'",
756
+ 'Size.',
757
+ 'TextSize',
758
+ ),
759
+ ).toEqual([]);
760
+ expect(
761
+ hiddenValueReasons(
762
+ '0 | 0.5 | 1 | 1.5 | 2 | 3 | 4 | 5 | 6 | 8 | 10',
763
+ 'Gap.',
764
+ 'SpacingStep',
765
+ ),
766
+ ).toEqual([]);
767
+ });
768
+
769
+ it('holds the prose exemption to its bargain', () => {
770
+ // IconName may stay named — but only while every value is in the prose.
771
+ const enumerated = docProps.find(
772
+ p => p.component === 'Icon' && p.prop === 'icon',
773
+ );
774
+ expect(enumerated?.type).toContain('IconName');
775
+ expect(
776
+ hiddenValueReasons(enumerated!.type, enumerated!.description),
777
+ ).toEqual([]);
778
+ // Drop the enumeration and the exemption stops covering it.
779
+ expect(
780
+ hiddenValueReasons('IconName | ComponentType<SVGProps>', 'An icon.'),
781
+ ).toEqual([expect.stringContaining('exempt from inlining only while')]);
782
+ });
783
+ });
784
+
785
+ /**
786
+ * The synthetic cases above prove the predicate bites. These prove the
787
+ * *shipped docs are actually wired to it* — each one takes a real doc
788
+ * entry, stages the exact drift the PR exists to prevent, and asserts the
789
+ * guard fails. A guard that green-lights the regression it was built to
790
+ * catch is decoration; these are the tests that stop it becoming one.
791
+ */
792
+ describe('the guard bites the real docs when they drift', () => {
793
+ const realProp = (component: string, prop: string): DocProp => {
794
+ const found = docProps.find(
795
+ p =>
796
+ p.component === component &&
797
+ p.prop === prop &&
798
+ !p.file.includes('(zh)'),
799
+ );
800
+ if (!found) {
801
+ throw new Error(`no doc prop ${component}.${prop}`);
802
+ }
803
+ return found;
804
+ };
805
+
806
+ /** What source says this doc prop is — the authority drift is judged against. */
807
+ const authority = (p: DocProp): string => {
808
+ const declared = declaredType(p.dir, p.component, p.prop);
809
+ if (!declared) {
810
+ throw new Error(
811
+ `${p.component}.${p.prop} has no source declaration, so nothing ` +
812
+ `holds its inlined literals to the union in source.`,
813
+ );
814
+ }
815
+ return declared;
816
+ };
817
+
818
+ it("#1645's headline prop is guarded: useTableSortable.sort", () => {
819
+ // The hook's config bag is UseTableSortableConfig, and it lives in
820
+ // Table/plugins/sortable/ while the doc sits in Table/ — so this only
821
+ // resolves because the source pass looks past `{Name}Props` *and* past
822
+ // the doc's own directory.
823
+ const sort = realProp('useTableSortable', 'sort');
824
+ expect(authority(sort)).toBe('TableSortState<TSortKey>');
825
+ // The shipped doc is honest today…
826
+ expect(
827
+ hiddenValueReasons(sort.type, sort.description, authority(sort)),
828
+ ).toEqual([]);
829
+ // …and the moment it stops naming 'descending', the guard fails. This
830
+ // is the doc lying about the legal sort values all over again — the
831
+ // literal bug #1645 was filed for.
832
+ const stale = (s: string) => s.replace(/'?descending'?/g, '');
833
+ expect(stale(sort.type)).not.toBe(sort.type);
834
+ expect(
835
+ hiddenValueReasons(
836
+ stale(sort.type),
837
+ stale(sort.description),
838
+ authority(sort),
839
+ ),
840
+ ).toEqual([expect.stringContaining("'ascending' | 'descending'")]);
841
+ });
842
+
843
+ it('Timestamp.size cannot quietly lose a step masked by a sibling', () => {
844
+ const size = realProp('Timestamp', 'size');
845
+ expect(authority(size)).toBe('TextSize');
846
+ expect(
847
+ hiddenValueReasons(size.type, size.description, authority(size)),
848
+ ).toEqual([]);
849
+ // Delete 'sm'. A substring match calls it present — 'xsm' contains it.
850
+ const stale = size.type.replace("'sm' | ", '');
851
+ expect(stale).not.toBe(size.type);
852
+ expect(stale).toContain("'xsm'");
853
+ expect(
854
+ hiddenValueReasons(stale, size.description, authority(size)),
855
+ ).toEqual([expect.stringContaining('TextSize')]);
856
+ });
857
+
858
+ /**
859
+ * The two props that must NOT be inlined. Everything else in this file
860
+ * pushes toward spelling unions out; these two push back, because the
861
+ * docsite playground reads `type` to choose a control. What each one is
862
+ * really protecting differs, and the specifics live next to the playground
863
+ * itself — see apps/docsite/src/__tests__/component-prop-controls.test.ts,
864
+ * which exercises the actual gate. These only pin the doc's shape, so the
865
+ * next person to "finish the job" trips here first.
866
+ */
867
+ it('AppShell.mobileNav stays a bare ReactNode (the editor hides on an exact string)', () => {
868
+ const nav = realProp('AppShell', 'mobileNav');
869
+ // PropertyEditor's UNSUPPORTED_PROP_TYPES is a Set of exact type strings.
870
+ // Widening drops that match, and the prop would then be hidden only by
871
+ // the second, incidental gate (its slotElements). Keep the exact string.
872
+ expect(nav.type).toBe('ReactNode');
873
+ // The values still have to be discoverable — they live in the prose.
874
+ expect(nav.description).toContain('breakpoint');
875
+ expect(nav.description).toContain("'md'");
876
+ });
877
+
878
+ it('Lightbox.media keeps naming its type (spelling it out yields a string control)', () => {
879
+ const media = realProp('Lightbox', 'media');
880
+ expect(media.type).toBe('LightboxMedia | LightboxMedia[]');
881
+ // Same bargain as IconName: named is allowed only while the prose
882
+ // carries the values a consumer must write.
883
+ expect(media.description).toContain("'image'");
884
+ expect(media.description).toContain("'video'");
885
+ });
886
+
887
+ it('Grid.gap cannot quietly lose the step 1, masked by 1.5 and 10', () => {
888
+ const gap = realProp('Grid', 'gap');
889
+ expect(authority(gap)).toBe('SpacingStep');
890
+ expect(
891
+ hiddenValueReasons(gap.type, gap.description, authority(gap)),
892
+ ).toEqual([]);
893
+ const stale = gap.type.replace('1 | 1.5', '1.5');
894
+ expect(stale).not.toBe(gap.type);
895
+ expect(stale).toContain('1.5');
896
+ expect(stale).toContain('10');
897
+ expect(
898
+ hiddenValueReasons(stale, gap.description, authority(gap)),
899
+ ).toEqual([expect.stringContaining('SpacingStep')]);
900
+ });
901
+ });
902
+ });