@astryxdesign/core 0.4.5 → 0.4.6-canary.cea9ecd

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 (538) hide show
  1. package/CHANGELOG.md +134 -0
  2. package/dist/Avatar/Avatar.d.ts +10 -5
  3. package/dist/Avatar/Avatar.d.ts.map +1 -1
  4. package/dist/Avatar/Avatar.js +139 -30
  5. package/dist/Avatar/AvatarStatusDot.d.ts +2 -1
  6. package/dist/Avatar/AvatarStatusDot.d.ts.map +1 -1
  7. package/dist/Avatar/AvatarStatusDot.js +30 -4
  8. package/dist/Avatar/AvatarStatusLabelContext.d.ts +39 -0
  9. package/dist/Avatar/AvatarStatusLabelContext.d.ts.map +1 -0
  10. package/dist/Avatar/AvatarStatusLabelContext.js +41 -0
  11. package/dist/AvatarGroup/AvatarGroup.d.ts +3 -1
  12. package/dist/AvatarGroup/AvatarGroup.d.ts.map +1 -1
  13. package/dist/AvatarGroup/AvatarGroupOverflow.d.ts.map +1 -1
  14. package/dist/AvatarGroup/AvatarGroupOverflow.js +15 -9
  15. package/dist/Blockquote/Blockquote.d.ts +4 -3
  16. package/dist/Blockquote/Blockquote.d.ts.map +1 -1
  17. package/dist/Blockquote/Blockquote.js +9 -10
  18. package/dist/BottomSheet/BottomSheetPanel.d.ts.map +1 -1
  19. package/dist/BottomSheet/BottomSheetPanel.js +21 -5
  20. package/dist/Breadcrumbs/BreadcrumbItem.d.ts.map +1 -1
  21. package/dist/Breadcrumbs/BreadcrumbItem.js +47 -34
  22. package/dist/Button/Button.d.ts.map +1 -1
  23. package/dist/Button/Button.js +7 -7
  24. package/dist/Calendar/Calendar.d.ts.map +1 -1
  25. package/dist/Calendar/Calendar.js +21 -12
  26. package/dist/Calendar/hooks/useCalendarNavigation.d.ts.map +1 -1
  27. package/dist/Calendar/hooks/useCalendarNavigation.js +8 -4
  28. package/dist/Calendar/styles.d.ts +2 -2
  29. package/dist/Calendar/styles.d.ts.map +1 -1
  30. package/dist/Calendar/styles.js +5 -5
  31. package/dist/Center/Center.d.ts +23 -1
  32. package/dist/Center/Center.d.ts.map +1 -1
  33. package/dist/Center/Center.js +12 -6
  34. package/dist/Chat/ChatComposer.d.ts.map +1 -1
  35. package/dist/Chat/ChatComposer.js +3 -3
  36. package/dist/Chat/ChatComposerDrawer.d.ts.map +1 -1
  37. package/dist/Chat/ChatComposerDrawer.js +2 -2
  38. package/dist/Chat/ChatToolCalls.d.ts.map +1 -1
  39. package/dist/Chat/ChatToolCalls.js +2 -2
  40. package/dist/Chat/useChatDictation.d.ts +1 -1
  41. package/dist/Chat/useChatDictation.d.ts.map +1 -1
  42. package/dist/Chat/useSpeechRecognition.d.ts +1 -1
  43. package/dist/Chat/useSpeechRecognition.d.ts.map +1 -1
  44. package/dist/Chat/useSpeechRecognition.js +4 -3
  45. package/dist/Chat/useTriggerMenu.d.ts.map +1 -1
  46. package/dist/Chat/useTriggerMenu.js +2 -2
  47. package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
  48. package/dist/CheckboxInput/CheckboxInput.js +2 -2
  49. package/dist/Citation/Citation.d.ts.map +1 -1
  50. package/dist/Citation/Citation.js +5 -5
  51. package/dist/ClickableCard/ClickableCard.d.ts.map +1 -1
  52. package/dist/ClickableCard/ClickableCard.js +4 -4
  53. package/dist/CodeBlock/CodeBlock.d.ts.map +1 -1
  54. package/dist/CodeBlock/CodeBlock.js +1 -1
  55. package/dist/Collapsible/Collapsible.d.ts.map +1 -1
  56. package/dist/Collapsible/Collapsible.js +2 -2
  57. package/dist/CommandPalette/CommandPaletteItem.d.ts.map +1 -1
  58. package/dist/CommandPalette/CommandPaletteItem.js +3 -3
  59. package/dist/ComplexSelector/ComplexSelector.d.ts.map +1 -1
  60. package/dist/ComplexSelector/ComplexSelector.js +6 -6
  61. package/dist/DateInput/DateInput.d.ts +31 -2
  62. package/dist/DateInput/DateInput.d.ts.map +1 -1
  63. package/dist/DateInput/DateInput.js +90 -22
  64. package/dist/DateInput/MonthScroller.d.ts +35 -0
  65. package/dist/DateInput/MonthScroller.d.ts.map +1 -0
  66. package/dist/DateInput/MonthScroller.js +658 -0
  67. package/dist/DateInput/MonthYearWheels.d.ts +24 -0
  68. package/dist/DateInput/MonthYearWheels.d.ts.map +1 -0
  69. package/dist/DateInput/MonthYearWheels.js +114 -0
  70. package/dist/DateInput/TouchDateField.d.ts +10 -0
  71. package/dist/DateInput/TouchDateField.d.ts.map +1 -0
  72. package/dist/DateInput/TouchDateField.js +810 -0
  73. package/dist/DateInput/Wheel.d.ts +41 -0
  74. package/dist/DateInput/Wheel.d.ts.map +1 -0
  75. package/dist/DateInput/Wheel.js +315 -0
  76. package/dist/DateInput/monthGeometry.d.ts +65 -0
  77. package/dist/DateInput/monthGeometry.d.ts.map +1 -0
  78. package/dist/DateInput/monthGeometry.js +98 -0
  79. package/dist/DateInput/tokens.stylex.d.ts +63 -0
  80. package/dist/DateInput/tokens.stylex.d.ts.map +1 -0
  81. package/dist/DateInput/tokens.stylex.js +74 -0
  82. package/dist/DateInput/useOwnScrollGesture.d.ts +41 -0
  83. package/dist/DateInput/useOwnScrollGesture.d.ts.map +1 -0
  84. package/dist/DateInput/useOwnScrollGesture.js +202 -0
  85. package/dist/DateInput/usePointerDragScroll.d.ts +15 -0
  86. package/dist/DateInput/usePointerDragScroll.d.ts.map +1 -0
  87. package/dist/DateInput/usePointerDragScroll.js +216 -0
  88. package/dist/DateInput/useScrollSettle.d.ts +26 -0
  89. package/dist/DateInput/useScrollSettle.d.ts.map +1 -0
  90. package/dist/DateInput/useScrollSettle.js +149 -0
  91. package/dist/DateRangeInput/DateRangeInput.d.ts.map +1 -1
  92. package/dist/DateRangeInput/DateRangeInput.js +17 -14
  93. package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
  94. package/dist/DateTimeInput/DateTimeInput.js +17 -14
  95. package/dist/Dialog/Dialog.d.ts.map +1 -1
  96. package/dist/Dialog/Dialog.js +3 -3
  97. package/dist/DropdownMenu/DropdownMenuCheckboxItem.d.ts.map +1 -1
  98. package/dist/DropdownMenu/DropdownMenuCheckboxItem.js +2 -2
  99. package/dist/DropdownMenu/DropdownMenuItem.d.ts.map +1 -1
  100. package/dist/DropdownMenu/DropdownMenuItem.js +2 -2
  101. package/dist/DropdownMenu/DropdownMenuRadioItem.d.ts.map +1 -1
  102. package/dist/DropdownMenu/DropdownMenuRadioItem.js +2 -2
  103. package/dist/DropdownMenu/DropdownMenuSubMenu.d.ts.map +1 -1
  104. package/dist/DropdownMenu/DropdownMenuSubMenu.js +2 -2
  105. package/dist/Field/FieldLabel.d.ts.map +1 -1
  106. package/dist/Field/FieldLabel.js +4 -4
  107. package/dist/Field/InputClearButton.d.ts.map +1 -1
  108. package/dist/Field/InputClearButton.js +6 -0
  109. package/dist/Field/inputStyles.stylex.d.ts +1 -1
  110. package/dist/Field/inputStyles.stylex.d.ts.map +1 -1
  111. package/dist/Field/inputStyles.stylex.js +5 -5
  112. package/dist/FileInput/FileInput.d.ts.map +1 -1
  113. package/dist/FileInput/FileInput.js +6 -6
  114. package/dist/HoverCard/HoverCard.d.ts +13 -3
  115. package/dist/HoverCard/HoverCard.d.ts.map +1 -1
  116. package/dist/HoverCard/HoverCard.js +2 -0
  117. package/dist/HoverCard/index.d.ts +1 -1
  118. package/dist/HoverCard/index.d.ts.map +1 -1
  119. package/dist/HoverCard/useHoverCard.d.ts +17 -2
  120. package/dist/HoverCard/useHoverCard.d.ts.map +1 -1
  121. package/dist/HoverCard/useHoverCard.js +78 -9
  122. package/dist/InputGroup/InputGroup.js +1 -1
  123. package/dist/Item/Item.d.ts +9 -1
  124. package/dist/Item/Item.d.ts.map +1 -1
  125. package/dist/Item/Item.js +52 -14
  126. package/dist/Layer/index.d.ts +4 -2
  127. package/dist/Layer/index.d.ts.map +1 -1
  128. package/dist/Layer/index.js +2 -0
  129. package/dist/Layer/useLayer.d.ts.map +1 -1
  130. package/dist/Layer/useLayer.js +3 -2
  131. package/dist/Layer/useTouchTrigger.d.ts +113 -0
  132. package/dist/Layer/useTouchTrigger.d.ts.map +1 -0
  133. package/dist/Layer/useTouchTrigger.js +241 -0
  134. package/dist/Layout/container.stylex.d.ts +4 -2
  135. package/dist/Layout/container.stylex.d.ts.map +1 -1
  136. package/dist/Layout/container.stylex.js +19 -11
  137. package/dist/Layout/index.d.ts +1 -0
  138. package/dist/Layout/index.d.ts.map +1 -1
  139. package/dist/Layout/index.js +4 -0
  140. package/dist/Layout/padding.stylex.d.ts +315 -12
  141. package/dist/Layout/padding.stylex.d.ts.map +1 -1
  142. package/dist/Layout/padding.stylex.js +389 -12
  143. package/dist/Lightbox/Lightbox.d.ts.map +1 -1
  144. package/dist/Lightbox/Lightbox.js +5 -4
  145. package/dist/Link/Link.d.ts.map +1 -1
  146. package/dist/Link/Link.js +6 -6
  147. package/dist/MetadataList/MetadataList.d.ts.map +1 -1
  148. package/dist/MetadataList/MetadataList.js +1 -1
  149. package/dist/MobileNav/MobileNav.d.ts +1 -0
  150. package/dist/MobileNav/MobileNav.d.ts.map +1 -1
  151. package/dist/MobileNav/MobileNav.js +24 -6
  152. package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
  153. package/dist/MultiSelector/MultiSelector.js +8 -8
  154. package/dist/NavItem/navItemStyles.stylex.d.ts +4 -4
  155. package/dist/NavItem/navItemStyles.stylex.d.ts.map +1 -1
  156. package/dist/NavItem/navItemStyles.stylex.js +4 -4
  157. package/dist/NavMenu/NavHeadingMenuItem.d.ts.map +1 -1
  158. package/dist/NavMenu/NavHeadingMenuItem.js +3 -3
  159. package/dist/NumberInput/NumberInput.d.ts.map +1 -1
  160. package/dist/NumberInput/NumberInput.js +36 -8
  161. package/dist/Outline/Outline.d.ts.map +1 -1
  162. package/dist/Outline/Outline.js +3 -3
  163. package/dist/Pagination/Pagination.d.ts.map +1 -1
  164. package/dist/Pagination/Pagination.js +2 -2
  165. package/dist/PowerSearch/PowerSearch.d.ts.map +1 -1
  166. package/dist/PowerSearch/PowerSearch.js +11 -12
  167. package/dist/PowerSearch/PowerSearchToken.d.ts.map +1 -1
  168. package/dist/PowerSearch/PowerSearchToken.js +3 -1
  169. package/dist/PowerSearch/formatFilterValue.d.ts +11 -2
  170. package/dist/PowerSearch/formatFilterValue.d.ts.map +1 -1
  171. package/dist/PowerSearch/formatFilterValue.js +23 -8
  172. package/dist/RadioList/RadioListItem.d.ts +2 -2
  173. package/dist/RadioList/RadioListItem.d.ts.map +1 -1
  174. package/dist/RadioList/RadioListItem.js +70 -40
  175. package/dist/Resizable/ResizeHandle.d.ts.map +1 -1
  176. package/dist/Resizable/ResizeHandle.js +7 -7
  177. package/dist/Section/Section.d.ts +36 -1
  178. package/dist/Section/Section.d.ts.map +1 -1
  179. package/dist/Section/Section.js +12 -2
  180. package/dist/SegmentedControl/SegmentedControlItem.d.ts.map +1 -1
  181. package/dist/SegmentedControl/SegmentedControlItem.js +7 -2
  182. package/dist/SelectableCard/SelectableCard.d.ts.map +1 -1
  183. package/dist/SelectableCard/SelectableCard.js +3 -3
  184. package/dist/Selector/Selector.d.ts +26 -1
  185. package/dist/Selector/Selector.d.ts.map +1 -1
  186. package/dist/Selector/Selector.js +93 -20
  187. package/dist/Selector/SelectorOption.d.ts +14 -1
  188. package/dist/Selector/SelectorOption.d.ts.map +1 -1
  189. package/dist/Selector/SelectorOption.js +6 -0
  190. package/dist/Selector/SelectorRowLayoutContext.d.ts +8 -0
  191. package/dist/Selector/SelectorRowLayoutContext.d.ts.map +1 -0
  192. package/dist/Selector/SelectorRowLayoutContext.js +27 -0
  193. package/dist/Selector/types.d.ts +1 -0
  194. package/dist/Selector/types.d.ts.map +1 -1
  195. package/dist/SideNav/SideNavHeading.d.ts.map +1 -1
  196. package/dist/SideNav/SideNavHeading.js +4 -4
  197. package/dist/SideNav/SideNavItem.d.ts.map +1 -1
  198. package/dist/SideNav/SideNavItem.js +3 -3
  199. package/dist/Slider/Slider.d.ts.map +1 -1
  200. package/dist/Slider/Slider.js +7 -7
  201. package/dist/Stack/Stack.d.ts +23 -1
  202. package/dist/Stack/Stack.d.ts.map +1 -1
  203. package/dist/Stack/Stack.js +12 -6
  204. package/dist/Switch/Switch.d.ts.map +1 -1
  205. package/dist/Switch/Switch.js +2 -2
  206. package/dist/TabList/Tab.d.ts.map +1 -1
  207. package/dist/TabList/Tab.js +1 -1
  208. package/dist/TabList/TabMenu.d.ts.map +1 -1
  209. package/dist/TabList/TabMenu.js +3 -3
  210. package/dist/Table/TableRow.js +4 -4
  211. package/dist/Table/plugins/columnResize/useTableColumnResize.d.ts.map +1 -1
  212. package/dist/Table/plugins/columnResize/useTableColumnResize.js +1 -1
  213. package/dist/Table/plugins/filtering/useTableFiltering.d.ts.map +1 -1
  214. package/dist/Table/plugins/filtering/useTableFiltering.js +2 -2
  215. package/dist/Table/plugins/groupedRows/useTableGroupedRows.d.ts.map +1 -1
  216. package/dist/Table/plugins/groupedRows/useTableGroupedRows.js +2 -2
  217. package/dist/Table/plugins/rowExpansion/useTableRowExpansion.d.ts.map +1 -1
  218. package/dist/Table/plugins/rowExpansion/useTableRowExpansion.js +2 -2
  219. package/dist/Table/plugins/sortable/useTableSortable.d.ts.map +1 -1
  220. package/dist/Table/plugins/sortable/useTableSortable.js +1 -1
  221. package/dist/Table/plugins/sortable/useTableSortableState.d.ts +5 -3
  222. package/dist/Table/plugins/sortable/useTableSortableState.d.ts.map +1 -1
  223. package/dist/Table/plugins/sortable/useTableSortableState.js +12 -9
  224. package/dist/Table/plugins/tree/useTableTreeData.d.ts.map +1 -1
  225. package/dist/Table/plugins/tree/useTableTreeData.js +3 -3
  226. package/dist/TextArea/TextArea.js +1 -1
  227. package/dist/TextInput/TextInput.js +1 -1
  228. package/dist/Thumbnail/Thumbnail.d.ts.map +1 -1
  229. package/dist/Thumbnail/Thumbnail.js +3 -3
  230. package/dist/TimeInput/TimeInput.js +2 -2
  231. package/dist/Timestamp/Timestamp.d.ts.map +1 -1
  232. package/dist/Timestamp/Timestamp.js +6 -4
  233. package/dist/Timestamp/formatInstant.d.ts +3 -2
  234. package/dist/Timestamp/formatInstant.d.ts.map +1 -1
  235. package/dist/Timestamp/formatInstant.js +8 -8
  236. package/dist/Timestamp/tooltipEntries.d.ts +2 -1
  237. package/dist/Timestamp/tooltipEntries.d.ts.map +1 -1
  238. package/dist/Timestamp/tooltipEntries.js +5 -3
  239. package/dist/Toast/Toast.d.ts +4 -3
  240. package/dist/Toast/Toast.d.ts.map +1 -1
  241. package/dist/Toast/Toast.js +10 -7
  242. package/dist/Token/Token.d.ts.map +1 -1
  243. package/dist/Token/Token.js +6 -6
  244. package/dist/Tokenizer/Tokenizer.d.ts.map +1 -1
  245. package/dist/Tokenizer/Tokenizer.js +1 -1
  246. package/dist/Tooltip/Tooltip.d.ts +16 -3
  247. package/dist/Tooltip/Tooltip.d.ts.map +1 -1
  248. package/dist/Tooltip/Tooltip.js +2 -0
  249. package/dist/Tooltip/index.d.ts +1 -1
  250. package/dist/Tooltip/index.d.ts.map +1 -1
  251. package/dist/Tooltip/useTooltip.d.ts +21 -2
  252. package/dist/Tooltip/useTooltip.d.ts.map +1 -1
  253. package/dist/Tooltip/useTooltip.js +71 -13
  254. package/dist/TopNav/TopNavHeading.d.ts.map +1 -1
  255. package/dist/TopNav/TopNavHeading.js +4 -4
  256. package/dist/TopNav/TopNavItem.d.ts.map +1 -1
  257. package/dist/TopNav/TopNavItem.js +3 -3
  258. package/dist/TopNav/TopNavMegaMenu.d.ts.map +1 -1
  259. package/dist/TopNav/TopNavMegaMenu.js +2 -2
  260. package/dist/TopNav/TopNavMegaMenuItem.d.ts.map +1 -1
  261. package/dist/TopNav/TopNavMegaMenuItem.js +2 -2
  262. package/dist/TopNav/TopNavMenu.d.ts.map +1 -1
  263. package/dist/TopNav/TopNavMenu.js +4 -4
  264. package/dist/TreeList/TreeListItem.d.ts.map +1 -1
  265. package/dist/TreeList/TreeListItem.js +7 -7
  266. package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
  267. package/dist/Typeahead/BaseTypeahead.js +2 -2
  268. package/dist/Typeahead/Typeahead.d.ts.map +1 -1
  269. package/dist/Typeahead/Typeahead.js +1 -1
  270. package/dist/astryx.css +124 -62
  271. package/dist/hooks/containerReveal.stylex.d.ts.map +1 -1
  272. package/dist/hooks/containerReveal.stylex.js +18 -18
  273. package/dist/hooks/scrollbarGutter.d.ts +28 -0
  274. package/dist/hooks/scrollbarGutter.d.ts.map +1 -0
  275. package/dist/hooks/scrollbarGutter.js +103 -0
  276. package/dist/hooks/useAutoMediaMode.d.ts +31 -0
  277. package/dist/hooks/useAutoMediaMode.d.ts.map +1 -0
  278. package/dist/hooks/useAutoMediaMode.js +201 -0
  279. package/dist/hooks/useInputStatusIcon.d.ts.map +1 -1
  280. package/dist/hooks/useInputStatusIcon.js +1 -1
  281. package/dist/hooks/useListFocus.d.ts +5 -2
  282. package/dist/hooks/useListFocus.d.ts.map +1 -1
  283. package/dist/hooks/useListFocus.js +12 -6
  284. package/dist/hooks/useScrollLock.d.ts +4 -0
  285. package/dist/hooks/useScrollLock.d.ts.map +1 -1
  286. package/dist/hooks/useScrollLock.js +13 -1
  287. package/dist/i18n/InternationalizationContext.d.ts.map +1 -1
  288. package/dist/i18n/InternationalizationContext.js +2 -0
  289. package/dist/i18n/index.d.ts +6 -0
  290. package/dist/i18n/index.d.ts.map +1 -1
  291. package/dist/i18n/index.js +6 -0
  292. package/dist/i18n/useCollator.d.ts +12 -0
  293. package/dist/i18n/useCollator.d.ts.map +1 -0
  294. package/dist/i18n/useCollator.js +40 -0
  295. package/dist/i18n/useLocale.d.ts +3 -0
  296. package/dist/i18n/useLocale.d.ts.map +1 -0
  297. package/dist/i18n/useLocale.js +31 -0
  298. package/dist/theme/MediaTheme.d.ts +25 -5
  299. package/dist/theme/MediaTheme.d.ts.map +1 -1
  300. package/dist/theme/MediaTheme.js +31 -6
  301. package/dist/theme/defineTheme.d.ts +5 -0
  302. package/dist/theme/defineTheme.d.ts.map +1 -1
  303. package/dist/theme/defineTheme.js +5 -0
  304. package/dist/theme/derivedVarRegistry.d.ts.map +1 -1
  305. package/dist/theme/derivedVarRegistry.js +7 -0
  306. package/dist/theme/generateThemeRules.d.ts.map +1 -1
  307. package/dist/theme/generateThemeRules.js +66 -5
  308. package/dist/theme/index.d.ts +1 -1
  309. package/dist/theme/index.d.ts.map +1 -1
  310. package/dist/theme/syntax/index.d.ts +6 -0
  311. package/dist/theme/syntax/index.d.ts.map +1 -1
  312. package/dist/theme/syntax/index.js +7 -2
  313. package/dist/utils/dateParser.d.ts +4 -3
  314. package/dist/utils/dateParser.d.ts.map +1 -1
  315. package/dist/utils/dateParser.js +8 -8
  316. package/dist/utils/index.d.ts +1 -1
  317. package/dist/utils/index.d.ts.map +1 -1
  318. package/dist/utils/index.js +1 -1
  319. package/dist/utils/plainDate.d.ts +5 -2
  320. package/dist/utils/plainDate.d.ts.map +1 -1
  321. package/dist/utils/plainDate.js +20 -4
  322. package/locales/en.json +28 -0
  323. package/locales/pseudo.json +21 -0
  324. package/package.json +3 -3
  325. package/src/Avatar/Avatar.doc.mjs +10 -6
  326. package/src/Avatar/Avatar.test.tsx +219 -1
  327. package/src/Avatar/Avatar.tsx +179 -37
  328. package/src/Avatar/AvatarStatusDot.tsx +30 -4
  329. package/src/Avatar/AvatarStatusLabelContext.ts +47 -0
  330. package/src/AvatarGroup/AvatarGroup.doc.mjs +10 -6
  331. package/src/AvatarGroup/AvatarGroup.test.tsx +62 -0
  332. package/src/AvatarGroup/AvatarGroup.tsx +3 -1
  333. package/src/AvatarGroup/AvatarGroupOverflow.tsx +31 -8
  334. package/src/Banner/Banner.doc.mjs +2 -0
  335. package/src/Blockquote/Blockquote.doc.mjs +30 -7
  336. package/src/Blockquote/Blockquote.test.tsx +34 -3
  337. package/src/Blockquote/Blockquote.tsx +11 -9
  338. package/src/BottomSheet/BottomSheet.doc.mjs +3 -3
  339. package/src/BottomSheet/BottomSheet.test.tsx +24 -0
  340. package/src/BottomSheet/BottomSheetPanel.test.tsx +91 -0
  341. package/src/BottomSheet/BottomSheetPanel.tsx +58 -3
  342. package/src/Breadcrumbs/BreadcrumbItem.tsx +36 -23
  343. package/src/Breadcrumbs/Breadcrumbs.doc.mjs +26 -2
  344. package/src/Breadcrumbs/Breadcrumbs.test.tsx +31 -0
  345. package/src/Button/Button.tsx +12 -9
  346. package/src/Calendar/Calendar.tsx +15 -9
  347. package/src/Calendar/hooks/useCalendarNavigation.ts +6 -4
  348. package/src/Calendar/styles.ts +8 -5
  349. package/src/Center/Center.doc.mjs +48 -0
  350. package/src/Center/Center.test.tsx +158 -0
  351. package/src/Center/Center.tsx +50 -9
  352. package/src/Chat/ChatComposer.tsx +11 -5
  353. package/src/Chat/ChatComposerDrawer.tsx +4 -1
  354. package/src/Chat/ChatToolCalls.tsx +13 -4
  355. package/src/Chat/{useChatDictation.test.ts → useChatDictation.test.tsx} +29 -14
  356. package/src/Chat/useChatDictation.ts +1 -1
  357. package/src/Chat/useSpeechRecognition.ts +5 -3
  358. package/src/Chat/useTriggerMenu.tsx +4 -1
  359. package/src/CheckboxInput/CheckboxInput.tsx +5 -2
  360. package/src/Citation/Citation.test.tsx +6 -1
  361. package/src/Citation/Citation.tsx +11 -5
  362. package/src/ClickableCard/ClickableCard.tsx +7 -4
  363. package/src/CodeBlock/CodeBlock.tsx +4 -1
  364. package/src/Collapsible/Collapsible.tsx +5 -2
  365. package/src/CommandPalette/CommandPaletteItem.tsx +6 -3
  366. package/src/ComplexSelector/ComplexSelector.doc.mjs +10 -0
  367. package/src/ComplexSelector/ComplexSelector.tsx +14 -7
  368. package/src/DateInput/DateInput.test.tsx +17 -1
  369. package/src/DateInput/DateInput.tsx +93 -21
  370. package/src/DateInput/DateInputTouch.test.tsx +2582 -0
  371. package/src/DateInput/MonthScroller.tsx +878 -0
  372. package/src/DateInput/MonthYearWheels.tsx +147 -0
  373. package/src/DateInput/TouchDateField.tsx +1223 -0
  374. package/src/DateInput/Wheel.tsx +473 -0
  375. package/src/DateInput/monthGeometry.ts +118 -0
  376. package/src/DateInput/tokens.stylex.ts +99 -0
  377. package/src/DateInput/useOwnScrollGesture.ts +227 -0
  378. package/src/DateInput/usePointerDragScroll.ts +220 -0
  379. package/src/DateInput/useScrollSettle.ts +164 -0
  380. package/src/DateRangeInput/DateRangeInput.doc.mjs +1 -1
  381. package/src/DateRangeInput/DateRangeInput.tsx +31 -13
  382. package/src/DateTimeInput/DateTimeInput.tsx +25 -9
  383. package/src/Dialog/Dialog.test.tsx +24 -0
  384. package/src/Dialog/Dialog.tsx +3 -0
  385. package/src/DropdownMenu/DropdownMenuCheckboxItem.tsx +5 -2
  386. package/src/DropdownMenu/DropdownMenuItem.tsx +5 -2
  387. package/src/DropdownMenu/DropdownMenuRadioItem.tsx +5 -2
  388. package/src/DropdownMenu/DropdownMenuSubMenu.tsx +5 -2
  389. package/src/Field/Field.doc.mjs +2 -0
  390. package/src/Field/FieldLabel.tsx +9 -3
  391. package/src/Field/InputClearButton.test.tsx +110 -3
  392. package/src/Field/InputClearButton.tsx +38 -0
  393. package/src/Field/inputStyles.stylex.ts +17 -13
  394. package/src/FileInput/FileInput.tsx +15 -8
  395. package/src/FormLayout/FormLayout.doc.mjs +1 -1
  396. package/src/HoverCard/HoverCard.doc.mjs +18 -0
  397. package/src/HoverCard/HoverCard.test.tsx +189 -0
  398. package/src/HoverCard/HoverCard.tsx +22 -2
  399. package/src/HoverCard/index.ts +1 -0
  400. package/src/HoverCard/useHoverCard.doc.mjs +2 -0
  401. package/src/HoverCard/useHoverCard.tsx +103 -7
  402. package/src/InputGroup/InputGroup.tsx +1 -1
  403. package/src/Item/Item.doc.mjs +1 -0
  404. package/src/Item/Item.test.tsx +50 -0
  405. package/src/Item/Item.tsx +54 -6
  406. package/src/Layer/index.ts +10 -0
  407. package/src/Layer/useLayer.tsx +13 -2
  408. package/src/Layer/useTouchTrigger.test.ts +70 -0
  409. package/src/Layer/useTouchTrigger.ts +366 -0
  410. package/src/Layout/Layout.doc.mjs +18 -0
  411. package/src/Layout/container.stylex.ts +11 -3
  412. package/src/Layout/index.ts +4 -0
  413. package/src/Layout/overlayPaddingReset.test.tsx +222 -0
  414. package/src/Layout/padding.stylex.ts +192 -12
  415. package/src/Lightbox/Lightbox.tsx +11 -3
  416. package/src/Link/Link.tsx +9 -6
  417. package/src/MetadataList/MetadataList.tsx +4 -1
  418. package/src/MobileNav/MobileNav.tsx +61 -5
  419. package/src/MobileNav/MobileNavEntryAnimation.test.tsx +212 -0
  420. package/src/MobileNav/MobileNavScrollbarGutter.test.tsx +109 -0
  421. package/src/MultiSelector/MultiSelector.test.tsx +1 -1
  422. package/src/MultiSelector/MultiSelector.tsx +26 -10
  423. package/src/NavItem/navItemStyles.stylex.ts +7 -4
  424. package/src/NavMenu/NavHeadingMenuItem.tsx +6 -3
  425. package/src/NumberInput/NumberInput.doc.mjs +24 -0
  426. package/src/NumberInput/NumberInput.test.tsx +138 -0
  427. package/src/NumberInput/NumberInput.tsx +47 -8
  428. package/src/Outline/Outline.tsx +5 -2
  429. package/src/Pagination/Pagination.tsx +5 -2
  430. package/src/Popover/Popover.test.tsx +27 -1
  431. package/src/PowerSearch/PowerSearch.tsx +17 -8
  432. package/src/PowerSearch/PowerSearchToken.tsx +3 -0
  433. package/src/PowerSearch/formatFilterValue.test.ts +22 -2
  434. package/src/PowerSearch/formatFilterValue.ts +31 -6
  435. package/src/RadioList/RadioList.doc.mjs +1 -1
  436. package/src/RadioList/RadioList.test.tsx +170 -2
  437. package/src/RadioList/RadioListItem.tsx +88 -53
  438. package/src/Resizable/ResizeHandle.test.tsx +22 -0
  439. package/src/Resizable/ResizeHandle.tsx +25 -7
  440. package/src/Section/Section.doc.mjs +55 -0
  441. package/src/Section/Section.test.tsx +201 -4
  442. package/src/Section/Section.tsx +69 -0
  443. package/src/SegmentedControl/SegmentedControl.test.tsx +31 -0
  444. package/src/SegmentedControl/SegmentedControlItem.tsx +15 -3
  445. package/src/SelectableCard/SelectableCard.tsx +6 -3
  446. package/src/Selector/Selector.doc.mjs +16 -2
  447. package/src/Selector/Selector.test.tsx +525 -1
  448. package/src/Selector/Selector.tsx +180 -25
  449. package/src/Selector/SelectorOption.doc.mjs +7 -0
  450. package/src/Selector/SelectorOption.tsx +21 -0
  451. package/src/Selector/SelectorRowLayoutContext.ts +32 -0
  452. package/src/Selector/types.ts +4 -4
  453. package/src/SideNav/SideNavHeading.tsx +14 -5
  454. package/src/SideNav/SideNavItem.tsx +9 -3
  455. package/src/Slider/Slider.tsx +25 -6
  456. package/src/Stack/Stack.doc.mjs +72 -0
  457. package/src/Stack/Stack.test.tsx +156 -0
  458. package/src/Stack/Stack.tsx +50 -8
  459. package/src/Switch/Switch.tsx +5 -2
  460. package/src/TabList/Tab.tsx +4 -1
  461. package/src/TabList/TabMenu.tsx +9 -3
  462. package/src/Table/TableRow.tsx +4 -4
  463. package/src/Table/plugins/columnResize/useTableColumnResize.tsx +6 -2
  464. package/src/Table/plugins/filtering/useTableFiltering.tsx +4 -1
  465. package/src/Table/plugins/groupedRows/useTableGroupedRows.tsx +10 -3
  466. package/src/Table/plugins/rowExpansion/useTableRowExpansion.tsx +6 -3
  467. package/src/Table/plugins/sortable/useTableSortable.tsx +4 -1
  468. package/src/Table/plugins/sortable/useTableSortableState.test.tsx +34 -0
  469. package/src/Table/plugins/sortable/useTableSortableState.tsx +26 -23
  470. package/src/Table/plugins/tree/useTableTreeData.tsx +10 -4
  471. package/src/TextArea/TextArea.doc.mjs +1 -1
  472. package/src/TextArea/TextArea.tsx +1 -1
  473. package/src/TextInput/TextInput.tsx +1 -1
  474. package/src/Thumbnail/Thumbnail.tsx +9 -3
  475. package/src/TimeInput/TimeInput.tsx +1 -1
  476. package/src/Timestamp/Timestamp.test.tsx +27 -7
  477. package/src/Timestamp/Timestamp.tsx +8 -4
  478. package/src/Timestamp/formatInstant.ts +9 -7
  479. package/src/Timestamp/tooltipEntries.test.ts +11 -6
  480. package/src/Timestamp/tooltipEntries.ts +6 -2
  481. package/src/Toast/Toast.tsx +9 -7
  482. package/src/Token/Token.tsx +14 -5
  483. package/src/Tokenizer/Tokenizer.tsx +4 -1
  484. package/src/Tooltip/Tooltip.doc.mjs +18 -0
  485. package/src/Tooltip/Tooltip.test.tsx +254 -1
  486. package/src/Tooltip/Tooltip.tsx +22 -2
  487. package/src/Tooltip/index.ts +1 -0
  488. package/src/Tooltip/useTooltip.doc.mjs +2 -0
  489. package/src/Tooltip/useTooltip.tsx +99 -21
  490. package/src/TopNav/TopNavHeading.tsx +13 -4
  491. package/src/TopNav/TopNavItem.tsx +6 -3
  492. package/src/TopNav/TopNavMegaMenu.tsx +5 -2
  493. package/src/TopNav/TopNavMegaMenuItem.tsx +5 -2
  494. package/src/TopNav/TopNavMenu.tsx +10 -4
  495. package/src/TreeList/TreeListItem.tsx +22 -7
  496. package/src/Typeahead/BaseTypeahead.tsx +5 -2
  497. package/src/Typeahead/Typeahead.tsx +4 -1
  498. package/src/hooks/containerReveal.stylex.ts +54 -18
  499. package/src/hooks/scrollbarGutter.test.ts +167 -0
  500. package/src/hooks/scrollbarGutter.ts +121 -0
  501. package/src/hooks/useAutoMediaMode.test.ts +109 -0
  502. package/src/hooks/useAutoMediaMode.ts +233 -0
  503. package/src/hooks/useInputStatusIcon.tsx +4 -1
  504. package/src/hooks/useListFocus.doc.mjs +2 -2
  505. package/src/hooks/useListFocus.test.tsx +65 -3
  506. package/src/hooks/useListFocus.ts +15 -7
  507. package/src/hooks/useScrollLock.doc.mjs +4 -4
  508. package/src/hooks/useScrollLock.test.ts +73 -0
  509. package/src/hooks/useScrollLock.ts +14 -0
  510. package/src/i18n/InternationalizationContext.ts +2 -0
  511. package/src/i18n/__tests__/e2e-powersearch.test.tsx +37 -1
  512. package/src/i18n/__tests__/useCollator.test.tsx +115 -0
  513. package/src/i18n/__tests__/useLocale.test.tsx +52 -0
  514. package/src/i18n/index.ts +6 -0
  515. package/src/i18n/useCollator.doc.mjs +84 -0
  516. package/src/i18n/useCollator.ts +44 -0
  517. package/src/i18n/useLocale.doc.mjs +70 -0
  518. package/src/i18n/useLocale.ts +34 -0
  519. package/src/i18n/useTranslator.doc.mjs +1 -1
  520. package/src/reset.css +9 -2
  521. package/src/reset.test.ts +22 -0
  522. package/src/theme/MediaTheme.doc.mjs +23 -4
  523. package/src/theme/MediaTheme.dom.test.tsx +97 -0
  524. package/src/theme/MediaTheme.tsx +42 -7
  525. package/src/theme/defineTheme.test.ts +9 -3
  526. package/src/theme/defineTheme.ts +5 -0
  527. package/src/theme/derivedVarRegistry.test.ts +19 -5
  528. package/src/theme/derivedVarRegistry.ts +4 -0
  529. package/src/theme/generateThemeRules.test.ts +118 -0
  530. package/src/theme/generateThemeRules.ts +83 -5
  531. package/src/theme/index.ts +1 -1
  532. package/src/theme/syntax/index.ts +6 -2
  533. package/src/theme/syntax/serverSafeSyntax.test.ts +68 -0
  534. package/src/utils/dateParser.test.ts +20 -0
  535. package/src/utils/dateParser.ts +14 -7
  536. package/src/utils/index.ts +2 -0
  537. package/src/utils/plainDate.test.ts +6 -0
  538. package/src/utils/plainDate.ts +21 -4
@@ -66,6 +66,13 @@ export const docs = {
66
66
  description: 'Controls when focus events trigger the tooltip.',
67
67
  default: "'auto'",
68
68
  },
69
+ {
70
+ name: 'touchTrigger',
71
+ type: "'auto' | 'tap' | 'none'",
72
+ description:
73
+ 'What a tap does where there is no hover. auto opens on tap unless the trigger performs an action of its own (a button, link, or form control), whose tap belongs to the control. tap always opens; use it for an info icon rendered as a button, whose only job is to reveal the tooltip. none never opens on touch.',
74
+ default: "'auto'",
75
+ },
69
76
  {
70
77
  name: 'isEnabled',
71
78
  type: 'boolean',
@@ -103,6 +110,7 @@ export const docs = {
103
110
  bestPractices: [
104
111
  {guidance: true, description: 'Keep tooltip content concise: aim for under 140 characters of plain text.'},
105
112
  {guidance: true, description: 'Add a tooltip to icon-only buttons and controls that lack a visible label.'},
113
+ {guidance: true, description: 'Set touchTrigger to tap when the trigger is a button whose only job is revealing the tooltip, such as an info icon: touch has no hover, and auto keeps the tap for triggers that perform an action.'},
106
114
  {guidance: false, description: 'Place interactive elements like links or buttons inside a tooltip; use HoverCard or Popover instead.'},
107
115
  {guidance: false, description: 'Use tooltips for essential information that users must see to complete a task.'},
108
116
  ],
@@ -165,6 +173,13 @@ export const docsZh = {
165
173
  description: '控制聚焦事件何时触发工具提示。',
166
174
  default: "'auto'",
167
175
  },
176
+ {
177
+ name: 'touchTrigger',
178
+ type: "'auto' | 'tap' | 'none'",
179
+ description:
180
+ '在没有悬停的触摸设备上,轻点的行为。auto:轻点即打开,除非触发元素本身会执行操作(按钮、链接、表单控件),此时轻点归该控件所有。tap:始终轻点打开——适用于以按钮形式呈现、唯一作用就是显示工具提示的信息图标。none:触摸永不打开。',
181
+ default: "'auto'",
182
+ },
168
183
  {
169
184
  name: 'isEnabled',
170
185
  type: 'boolean',
@@ -202,6 +217,7 @@ export const docsZh = {
202
217
  bestPractices: [
203
218
  {guidance: true, description: 'Keep tooltip content concise: aim for under 140 characters of plain text.'},
204
219
  {guidance: true, description: 'Add a tooltip to icon-only buttons and controls that lack a visible label.'},
220
+ {guidance: true, description: 'Set touchTrigger to tap when the trigger is a button whose only job is revealing the tooltip, such as an info icon: touch has no hover, and auto keeps the tap for triggers that perform an action.'},
205
221
  {guidance: false, description: 'Place interactive elements like links or buttons inside a tooltip; use HoverCard or Popover instead.'},
206
222
  {guidance: false, description: 'Use tooltips for essential information that users must see to complete a task.'},
207
223
  ],
@@ -217,6 +233,7 @@ export const docsDense = {
217
233
  bestPractices: [
218
234
  {guidance: true, description: 'Keep tooltip content concise: aim for under 140 characters of plain text.'},
219
235
  {guidance: true, description: 'Add a tooltip to icon-only buttons and controls that lack a visible label.'},
236
+ {guidance: true, description: 'Set touchTrigger to tap when the trigger is a button whose only job is revealing the tooltip, such as an info icon: touch has no hover, and auto keeps the tap for triggers that perform an action.'},
220
237
  {guidance: false, description: 'Place interactive elements like links or buttons inside a tooltip; use HoverCard or Popover instead.'},
221
238
  {guidance: false, description: 'Use tooltips for essential information that users must see to complete a task.'},
222
239
  ],
@@ -235,6 +252,7 @@ export const docsDense = {
235
252
  delay: 'Show delay in ms.',
236
253
  hideDelay: 'Hide delay in ms.',
237
254
  focusTrigger: 'Controls when focus events trigger tooltip.',
255
+ touchTrigger: 'Tap behavior where there is no hover. auto = tap opens unless the trigger acts (button/link/control); tap = always opens (info icon rendered as a button); none = never on touch.',
238
256
  isEnabled: 'Enables/disables tooltip triggers.',
239
257
  onOpenChange: 'Callback when visibility changes; true=shown, false=hidden.',
240
258
  hasHoverIndication: 'Dashed underline on trigger element.',
@@ -9,9 +9,18 @@
9
9
  * SYNC: When Tooltip.tsx changes, update tests to match new behavior
10
10
  */
11
11
 
12
- import {describe, it, expect, vi, beforeAll, afterAll} from 'vitest';
12
+ import {
13
+ describe,
14
+ it,
15
+ expect,
16
+ vi,
17
+ beforeAll,
18
+ beforeEach,
19
+ afterAll,
20
+ } from 'vitest';
13
21
  import {render, screen, fireEvent, waitFor} from '@testing-library/react';
14
22
  import {Tooltip} from './Tooltip';
23
+ import {__resetInteractionModalityForTest} from '../utils/interactionModality';
15
24
 
16
25
  // Store original matches to restore later
17
26
  const originalMatches = HTMLElement.prototype.matches;
@@ -267,4 +276,248 @@ describe('Tooltip', () => {
267
276
  expect(onOpenChange).not.toHaveBeenCalledWith(false);
268
277
  });
269
278
  });
279
+ describe('touch', () => {
280
+ // The modality is document-global; a tap in one case must not decide the
281
+ // next one's answer.
282
+ beforeEach(() => {
283
+ __resetInteractionModalityForTest();
284
+ });
285
+
286
+ /** A tap: the pointer sequence a finger produces before hover is faked. */
287
+ const tap = (element: HTMLElement) => {
288
+ // A finger's arrival fires pointerenter too, and that is the path a pen
289
+ // must not take — cover it here rather than starting at pointerdown.
290
+ fireEvent.pointerEnter(element, {pointerType: 'touch'});
291
+ fireEvent.pointerDown(element, {pointerType: 'touch'});
292
+ fireEvent.pointerUp(element, {pointerType: 'touch'});
293
+ // Touch synthesizes hover after the press; the tooltip must not act on it.
294
+ fireEvent.mouseEnter(element);
295
+ };
296
+
297
+ it('opens on a tap when the trigger performs no action', async () => {
298
+ const onOpenChange = vi.fn();
299
+ render(
300
+ <Tooltip content="Tooltip text" onOpenChange={onOpenChange} delay={200}>
301
+ Abbreviation
302
+ </Tooltip>,
303
+ );
304
+
305
+ tap(screen.getByText('Abbreviation'));
306
+
307
+ // Immediately: a tap is a decision, not hover intent, so no delay applies.
308
+ await waitFor(() => {
309
+ expect(onOpenChange).toHaveBeenCalledWith(true);
310
+ });
311
+ });
312
+
313
+ it('stays shut on a tap when the trigger performs an action', async () => {
314
+ const onOpenChange = vi.fn();
315
+ render(
316
+ <Tooltip content="Tooltip text" onOpenChange={onOpenChange} delay={0}>
317
+ <button type="button">Save</button>
318
+ </Tooltip>,
319
+ );
320
+
321
+ tap(screen.getByRole('button', {name: 'Save'}));
322
+
323
+ await new Promise(resolve => setTimeout(resolve, 50));
324
+ expect(onOpenChange).not.toHaveBeenCalledWith(true);
325
+ });
326
+
327
+ it('opens on a tap of an action trigger when touchTrigger is "tap"', async () => {
328
+ const onOpenChange = vi.fn();
329
+ render(
330
+ <Tooltip
331
+ content="What this metric means"
332
+ onOpenChange={onOpenChange}
333
+ touchTrigger="tap"
334
+ delay={0}>
335
+ <button type="button">Info</button>
336
+ </Tooltip>,
337
+ );
338
+
339
+ tap(screen.getByRole('button', {name: 'Info'}));
340
+
341
+ await waitFor(() => {
342
+ expect(onOpenChange).toHaveBeenCalledWith(true);
343
+ });
344
+ });
345
+
346
+ it('never opens on a tap when touchTrigger is "none"', async () => {
347
+ const onOpenChange = vi.fn();
348
+ render(
349
+ <Tooltip
350
+ content="Tooltip text"
351
+ onOpenChange={onOpenChange}
352
+ touchTrigger="none"
353
+ delay={0}>
354
+ Abbreviation
355
+ </Tooltip>,
356
+ );
357
+
358
+ tap(screen.getByText('Abbreviation'));
359
+
360
+ await new Promise(resolve => setTimeout(resolve, 50));
361
+ expect(onOpenChange).not.toHaveBeenCalledWith(true);
362
+ });
363
+
364
+ it('closes on a second tap of the trigger', async () => {
365
+ const onOpenChange = vi.fn();
366
+ render(
367
+ <Tooltip content="Tooltip text" onOpenChange={onOpenChange} delay={0}>
368
+ Abbreviation
369
+ </Tooltip>,
370
+ );
371
+
372
+ const trigger = screen.getByText('Abbreviation');
373
+ tap(trigger);
374
+ await waitFor(() => {
375
+ expect(onOpenChange).toHaveBeenCalledWith(true);
376
+ });
377
+
378
+ tap(trigger);
379
+ await waitFor(() => {
380
+ expect(onOpenChange).toHaveBeenCalledWith(false);
381
+ });
382
+ });
383
+
384
+ it('closes on a tap outside — the dismissal a tap-open owes the user', async () => {
385
+ const onOpenChange = vi.fn();
386
+ render(
387
+ <>
388
+ <Tooltip content="Tooltip text" onOpenChange={onOpenChange} delay={0}>
389
+ Abbreviation
390
+ </Tooltip>
391
+ <button type="button">Elsewhere</button>
392
+ </>,
393
+ );
394
+
395
+ tap(screen.getByText('Abbreviation'));
396
+ await waitFor(() => {
397
+ expect(onOpenChange).toHaveBeenCalledWith(true);
398
+ });
399
+
400
+ fireEvent.pointerDown(screen.getByRole('button', {name: 'Elsewhere'}), {
401
+ pointerType: 'touch',
402
+ });
403
+
404
+ await waitFor(() => {
405
+ expect(onOpenChange).toHaveBeenCalledWith(false);
406
+ });
407
+ });
408
+
409
+ it('still opens on a real mouse hover after a tap', async () => {
410
+ const onOpenChange = vi.fn();
411
+ render(
412
+ <Tooltip content="Tooltip text" onOpenChange={onOpenChange} delay={0}>
413
+ <button type="button">Save</button>
414
+ </Tooltip>,
415
+ );
416
+
417
+ const trigger = screen.getByRole('button', {name: 'Save'});
418
+ tap(trigger);
419
+ await new Promise(resolve => setTimeout(resolve, 20));
420
+ expect(onOpenChange).not.toHaveBeenCalledWith(true);
421
+
422
+ // A hybrid device: the same trigger, now under a mouse.
423
+ fireEvent.pointerEnter(trigger, {pointerType: 'mouse'});
424
+ fireEvent.mouseEnter(trigger);
425
+
426
+ await waitFor(() => {
427
+ expect(onOpenChange).toHaveBeenCalledWith(true);
428
+ });
429
+ });
430
+
431
+ it('opens on a hovering pen, which is a hover and not a tap', async () => {
432
+ const onOpenChange = vi.fn();
433
+ render(
434
+ <Tooltip content="Tooltip text" onOpenChange={onOpenChange} delay={0}>
435
+ <button type="button">Save</button>
436
+ </Tooltip>,
437
+ );
438
+
439
+ const trigger = screen.getByRole('button', {name: 'Save'});
440
+ // A stylus in detection range: pointerenter with nothing in contact, on
441
+ // a device where `(hover: hover)` matches. Reading that as touch would
442
+ // bail out of the hover path and leave the user no tooltip at all.
443
+ fireEvent.pointerEnter(trigger, {pointerType: 'pen', buttons: 0});
444
+ fireEvent.mouseEnter(trigger);
445
+
446
+ await waitFor(() => {
447
+ expect(onOpenChange).toHaveBeenCalledWith(true);
448
+ });
449
+ });
450
+
451
+ it('tap-opens when a pen lands on an inert trigger', async () => {
452
+ const onOpenChange = vi.fn();
453
+ render(
454
+ <Tooltip content="Tooltip text" onOpenChange={onOpenChange} delay={200}>
455
+ Abbreviation
456
+ </Tooltip>,
457
+ );
458
+
459
+ const trigger = screen.getByText('Abbreviation');
460
+ // Hovering first, as a real pen does — then contact, which is a tap.
461
+ fireEvent.pointerEnter(trigger, {pointerType: 'pen', buttons: 0});
462
+ fireEvent.pointerDown(trigger, {pointerType: 'pen'});
463
+ fireEvent.pointerUp(trigger, {pointerType: 'pen'});
464
+
465
+ await waitFor(() => {
466
+ expect(onOpenChange).toHaveBeenCalledWith(true);
467
+ });
468
+ });
469
+
470
+ it('does not reopen from the focus a tapped text field takes', async () => {
471
+ const onOpenChange = vi.fn();
472
+ render(
473
+ <Tooltip content="Tooltip text" onOpenChange={onOpenChange} delay={0}>
474
+ <input type="text" aria-label="Amount" />
475
+ </Tooltip>,
476
+ );
477
+
478
+ const trigger = screen.getByLabelText('Amount');
479
+ // A tapped text field matches `:focus-visible` in a real browser —
480
+ // deliberately, per Selectors 4, so typing has a visible home. jsdom
481
+ // does not model that, so stand it up here; without it this case cannot
482
+ // fail, whatever the focus path does.
483
+ const realMatches = trigger.matches.bind(trigger);
484
+ vi.spyOn(trigger, 'matches').mockImplementation((selector: string) =>
485
+ selector === ':focus-visible' ? true : realMatches(selector),
486
+ );
487
+
488
+ // An `<input>` is an action trigger, so `auto` gives it the tap — and
489
+ // the focus it takes must not put the tooltip back over the field the
490
+ // user is about to type into.
491
+ tap(trigger);
492
+ fireEvent.focusIn(trigger);
493
+
494
+ await new Promise(resolve => setTimeout(resolve, 50));
495
+ expect(onOpenChange).not.toHaveBeenCalledWith(true);
496
+ });
497
+
498
+ it('still opens on keyboard focus of a trigger a finger touched', async () => {
499
+ const onOpenChange = vi.fn();
500
+ render(
501
+ <Tooltip content="Tooltip text" onOpenChange={onOpenChange} delay={0}>
502
+ <input type="text" aria-label="Amount" />
503
+ </Tooltip>,
504
+ );
505
+
506
+ const trigger = screen.getByLabelText('Amount');
507
+ const realMatches = trigger.matches.bind(trigger);
508
+ vi.spyOn(trigger, 'matches').mockImplementation((selector: string) =>
509
+ selector === ':focus-visible' ? true : realMatches(selector),
510
+ );
511
+
512
+ tap(trigger);
513
+ // Reaching for the keyboard ends the touch interaction; the guard is on
514
+ // the gesture in flight, not on the device the trigger last saw.
515
+ fireEvent.keyDown(document, {key: 'Tab'});
516
+ fireEvent.focusIn(trigger);
517
+
518
+ await waitFor(() => {
519
+ expect(onOpenChange).toHaveBeenCalledWith(true);
520
+ });
521
+ });
522
+ });
270
523
  });
@@ -22,11 +22,15 @@ import React, {
22
22
  } from 'react';
23
23
  import {useIsomorphicLayoutEffect} from '../hooks/useIsomorphicLayoutEffect';
24
24
  import * as stylex from '@stylexjs/stylex';
25
- import {useTooltip, type TooltipFocusTrigger} from './useTooltip';
25
+ import {
26
+ useTooltip,
27
+ type TooltipFocusTrigger,
28
+ type TooltipTouchTrigger,
29
+ } from './useTooltip';
26
30
  import type {LayerAlignment, LayerPlacement} from '../Layer/useLayer';
27
31
  import {colorVars} from '../theme/tokens.stylex';
28
32
 
29
- export type {TooltipFocusTrigger} from './useTooltip';
33
+ export type {TooltipFocusTrigger, TooltipTouchTrigger} from './useTooltip';
30
34
 
31
35
  const styles = stylex.create({
32
36
  wrapperContents: {
@@ -99,6 +103,20 @@ export interface TooltipProps {
99
103
  */
100
104
  focusTrigger?: TooltipFocusTrigger;
101
105
 
106
+ /**
107
+ * What a tap does on a touch pointer, where there is no hover:
108
+ * - `auto`: tap opens the tooltip, unless the trigger performs an action of
109
+ * its own (a button, a link, a form control) — that tap belongs to the
110
+ * control, and a hint about a control the user just operated is noise
111
+ * - `tap`: tap always opens the tooltip. This is what an info icon rendered
112
+ * as a button wants: it looks like an action to the DOM, but revealing the
113
+ * tooltip is the only thing it does
114
+ * - `none`: touch never opens the tooltip
115
+ *
116
+ * @default 'auto'
117
+ */
118
+ touchTrigger?: TooltipTouchTrigger;
119
+
102
120
  /**
103
121
  * Whether the tooltip is enabled.
104
122
  * When false, hover/focus triggers are disabled.
@@ -176,6 +194,7 @@ export function Tooltip({
176
194
  delay = 200,
177
195
  hideDelay = 0,
178
196
  focusTrigger = 'auto',
197
+ touchTrigger = 'auto',
179
198
  isEnabled = true,
180
199
  onOpenChange,
181
200
  hasHoverIndication = 'auto',
@@ -204,6 +223,7 @@ export function Tooltip({
204
223
  delay,
205
224
  hideDelay,
206
225
  focusTrigger,
226
+ touchTrigger,
207
227
  isEnabled,
208
228
  isOpen,
209
229
  isDefaultOpen,
@@ -15,6 +15,7 @@
15
15
  export {useTooltip} from './useTooltip';
16
16
  export type {
17
17
  TooltipFocusTrigger,
18
+ TooltipTouchTrigger,
18
19
  TooltipOptions,
19
20
  TooltipReturn,
20
21
  } from './useTooltip';
@@ -12,6 +12,7 @@ export const docs = {
12
12
  {name: 'delay', type: 'number', description: 'Delay before showing on hover, in milliseconds.', default: '200'},
13
13
  {name: 'hideDelay', type: 'number', description: 'Delay before hiding after mouse or focus leaves, in milliseconds.', default: '0'},
14
14
  {name: 'focusTrigger', type: "'auto' | 'always' | 'never'", description: 'When focus should open the tooltip. auto only attaches focus listeners to naturally focusable elements.', default: "'auto'"},
15
+ {name: 'touchTrigger', type: "'auto' | 'tap' | 'none'", description: 'What a tap does where there is no hover. auto opens on tap unless the trigger performs an action of its own; tap always opens (an info icon rendered as a button); none never opens on touch.', default: "'auto'"},
15
16
  {name: 'isEnabled', type: 'boolean', description: 'Whether hover and focus triggers are enabled.', default: 'true'},
16
17
  {name: 'isOpen', type: 'boolean', description: 'Controlled open state. true force-shows, false force-hides, undefined lets hover/focus manage visibility.'},
17
18
  {name: 'isDefaultOpen', type: 'boolean', description: 'Whether the tooltip should be shown on mount.', default: 'false'},
@@ -49,6 +50,7 @@ export const docsDense = {
49
50
  delay: 'show delay in ms.',
50
51
  hideDelay: 'hide delay after mouse/focus leave in ms.',
51
52
  focusTrigger: 'when focus opens tooltip; auto = naturally focusable elements only.',
53
+ touchTrigger: 'tap behavior with no hover; auto = tap unless trigger acts, tap = always, none = never.',
52
54
  isEnabled: 'whether hover/focus triggers are enabled.',
53
55
  isOpen: 'controlled open state: true force-show, false force-hide, undefined unmanaged.',
54
56
  isDefaultOpen: 'show on mount.',
@@ -4,8 +4,8 @@
4
4
 
5
5
  /**
6
6
  * @file useTooltip.tsx
7
- * @input Uses useLayer, React hooks
8
- * @output Exports useTooltip hook for hover/focus triggered tooltips
7
+ * @input Uses useLayer, useTouchTrigger, React hooks
8
+ * @output Exports useTooltip hook for hover/focus/tap triggered tooltips
9
9
  * @position Layer hook; builds on useLayer for tooltip behavior
10
10
  *
11
11
  * SYNC: When modified, update:
@@ -26,6 +26,10 @@ import {
26
26
  type LayerAlignment,
27
27
  type LayerPlacement,
28
28
  } from '../Layer/useLayer';
29
+ import {
30
+ useTouchTrigger,
31
+ type LayerTouchTrigger,
32
+ } from '../Layer/useTouchTrigger';
29
33
  import {layerAnimations} from '../Layer/layerAnimations.stylex';
30
34
  import {themeProps} from '../utils/themeProps';
31
35
  import {isImeKeyEvent} from '../utils/ime';
@@ -73,6 +77,11 @@ const styles = stylex.create({
73
77
  */
74
78
  export type TooltipFocusTrigger = 'auto' | 'always' | 'never';
75
79
 
80
+ /**
81
+ * Touch trigger behavior for tooltips
82
+ */
83
+ export type TooltipTouchTrigger = LayerTouchTrigger;
84
+
76
85
  export interface TooltipOptions {
77
86
  /**
78
87
  * Position placement relative to anchor
@@ -108,6 +117,20 @@ export interface TooltipOptions {
108
117
  */
109
118
  focusTrigger?: TooltipFocusTrigger;
110
119
 
120
+ /**
121
+ * What a tap does on a touch pointer, where there is no hover:
122
+ * - `auto`: tap opens the tooltip, unless the trigger performs an action of
123
+ * its own (a button, a link, a form control) — that tap belongs to the
124
+ * control, and a hint about a control the user just operated is noise
125
+ * - `tap`: tap always opens the tooltip. This is what an info icon rendered
126
+ * as a button wants: it looks like an action to the DOM, but revealing the
127
+ * tooltip is the only thing it does
128
+ * - `none`: touch never opens the tooltip
129
+ *
130
+ * @default 'auto'
131
+ */
132
+ touchTrigger?: TooltipTouchTrigger;
133
+
111
134
  /**
112
135
  * Whether the tooltip is enabled.
113
136
  * When false, hover/focus triggers are disabled.
@@ -218,6 +241,7 @@ function isFocusable(element: HTMLElement): boolean {
218
241
  * Builds on useLayer to add:
219
242
  * - Hover triggers with configurable delay
220
243
  * - Focus triggers with auto-detection for focusable elements
244
+ * - Tap triggers on touch, where there is no hover (see useTouchTrigger)
221
245
  * - Inverted color palette for high contrast
222
246
  *
223
247
  * Unlike HoverCard, tooltips:
@@ -242,6 +266,7 @@ export function useTooltip(options: TooltipOptions = {}): TooltipReturn {
242
266
  delay = 200,
243
267
  hideDelay = 0,
244
268
  focusTrigger = 'auto',
269
+ touchTrigger = 'auto',
245
270
  isEnabled = true,
246
271
  isOpen,
247
272
  isDefaultOpen = false,
@@ -273,6 +298,29 @@ export function useTooltip(options: TooltipOptions = {}): TooltipReturn {
273
298
  }
274
299
  }, []);
275
300
 
301
+ // Touch resolves immediately: the hover delays exist to filter out a pointer
302
+ // passing across the trigger, and a tap is never that.
303
+ const showNow = useCallback(() => {
304
+ clearTimeouts();
305
+ layer.show();
306
+ }, [clearTimeouts, layer]);
307
+
308
+ const hideNow = useCallback(() => {
309
+ clearTimeouts();
310
+ layer.hide();
311
+ }, [clearTimeouts, layer]);
312
+
313
+ const touch = useTouchTrigger({
314
+ touchTrigger,
315
+ isEnabled,
316
+ isControlled: isOpen !== undefined,
317
+ isOpen: layer.isOpen,
318
+ layerId: layer.id,
319
+ triggerRef,
320
+ show: showNow,
321
+ hide: hideNow,
322
+ });
323
+
276
324
  // Schedule show with delay (suppressed when isOpen is false)
277
325
  const scheduleShow = useCallback(() => {
278
326
  if (!isEnabled || isOpen === false) {
@@ -309,26 +357,38 @@ export function useTooltip(options: TooltipOptions = {}): TooltipReturn {
309
357
 
310
358
  // Event handlers
311
359
  const handleMouseEnter = useCallback(() => {
312
- // Suppress tooltips on touch devices hover is simulated and eats a tap
313
- if (
314
- typeof window !== 'undefined' &&
315
- typeof window.matchMedia === 'function' &&
316
- window.matchMedia('(hover: none)').matches
317
- ) {
360
+ // A tap synthesizes mouseenter. On touch the tap path owns the decision,
361
+ // so hover must not also fire — including the tap that just opened the
362
+ // tooltip, which would otherwise be double-handled.
363
+ if (touch.isTouchPointerRef.current) {
318
364
  return;
319
365
  }
320
366
  scheduleShow();
321
- }, [scheduleShow]);
367
+ }, [touch, scheduleShow]);
322
368
 
323
369
  const handleMouseLeave = useCallback(() => {
370
+ // On touch the synthesized mouseleave arrives with the next tap elsewhere,
371
+ // which the outside-tap dismissal already handles — and handling it here
372
+ // too would close the tooltip behind the tap-open bookkeeping's back.
373
+ if (touch.isTouchPointerRef.current) {
374
+ return;
375
+ }
324
376
  scheduleHide();
325
- }, [scheduleHide]);
377
+ }, [touch, scheduleHide]);
326
378
 
327
379
  const handleFocusIn = useCallback(
328
380
  (e: Event) => {
329
381
  if (!isEnabled) {
330
382
  return;
331
383
  }
384
+ // A tap focuses the trigger it activates, and `:focus-visible` does not
385
+ // filter that out on every element: a tapped `<input>` or contenteditable
386
+ // matches it. Those are exactly the action triggers `auto` just decided
387
+ // to keep shut, so without this the focus reopens what the tap
388
+ // suppressed, over the field the user is trying to type into.
389
+ if (touch.isTouchInteraction()) {
390
+ return;
391
+ }
332
392
  // Only show tooltip for keyboard focus (:focus-visible),
333
393
  // not programmatic focus (e.g. dialog auto-focus, touch tap)
334
394
  const target = e.target as HTMLElement;
@@ -338,7 +398,7 @@ export function useTooltip(options: TooltipOptions = {}): TooltipReturn {
338
398
  clearTimeouts();
339
399
  layer.show();
340
400
  },
341
- [isEnabled, clearTimeouts, layer],
401
+ [isEnabled, touch, clearTimeouts, layer],
342
402
  );
343
403
 
344
404
  const handleFocusOut = useCallback(() => {
@@ -350,15 +410,24 @@ export function useTooltip(options: TooltipOptions = {}): TooltipReturn {
350
410
  // just-pressed control reads as stale. Fires on pointerdown so it feels
351
411
  // immediate. Uncontrolled tooltips only — a controlled tooltip's visibility
352
412
  // is owned by the consumer. `layer.hide()` self-guards when already closed.
353
- const handlePointerDown = useCallback(() => {
354
- if (isOpen !== undefined) {
355
- return;
356
- }
357
- clearTimeouts();
358
- layer.hide();
359
- }, [isOpen, clearTimeouts, layer]);
413
+ // A touch press is a different gesture (it may be the only way to open the
414
+ // tooltip at all), so the touch path answers it first.
415
+ const handlePointerDown = useCallback(
416
+ (event: PointerEvent) => {
417
+ if (touch.handlePointerDown(event)) {
418
+ return;
419
+ }
420
+ if (isOpen !== undefined) {
421
+ return;
422
+ }
423
+ clearTimeouts();
424
+ layer.hide();
425
+ },
426
+ [touch, isOpen, clearTimeouts, layer],
427
+ );
360
428
 
361
429
  // Interaction ref that handles event listeners only
430
+ const {handlePointerEnter, clearTapOpen} = touch;
362
431
  const interactionRef: RefCallback<HTMLElement> = useCallback(
363
432
  (el: HTMLElement | null) => {
364
433
  // Cleanup previous element
@@ -367,6 +436,10 @@ export function useTooltip(options: TooltipOptions = {}): TooltipReturn {
367
436
  triggerRef.current.removeEventListener('mouseleave', handleMouseLeave);
368
437
  triggerRef.current.removeEventListener('focusin', handleFocusIn);
369
438
  triggerRef.current.removeEventListener('focusout', handleFocusOut);
439
+ triggerRef.current.removeEventListener(
440
+ 'pointerenter',
441
+ handlePointerEnter,
442
+ );
370
443
  triggerRef.current.removeEventListener(
371
444
  'pointerdown',
372
445
  handlePointerDown,
@@ -374,10 +447,13 @@ export function useTooltip(options: TooltipOptions = {}): TooltipReturn {
374
447
  }
375
448
 
376
449
  if (el) {
377
- // Attach hover listeners
450
+ // Attach hover listeners. `pointerenter` runs before the synthesized
451
+ // `mouseenter` a tap produces, which is what lets the hover path know
452
+ // it is looking at a finger.
453
+ el.addEventListener('pointerenter', handlePointerEnter);
378
454
  el.addEventListener('mouseenter', handleMouseEnter);
379
455
  el.addEventListener('mouseleave', handleMouseLeave);
380
- // Press-to-dismiss: activating the trigger hides its own tooltip.
456
+ // Press-to-dismiss on a mouse; tap-to-open on a touch pointer.
381
457
  el.addEventListener('pointerdown', handlePointerDown);
382
458
 
383
459
  // Attach focus listeners based on focusTrigger option
@@ -399,6 +475,7 @@ export function useTooltip(options: TooltipOptions = {}): TooltipReturn {
399
475
  handleMouseLeave,
400
476
  handleFocusIn,
401
477
  handleFocusOut,
478
+ handlePointerEnter,
402
479
  handlePointerDown,
403
480
  ],
404
481
  );
@@ -461,13 +538,14 @@ export function useTooltip(options: TooltipOptions = {}): TooltipReturn {
461
538
  return;
462
539
  }
463
540
  clearTimeouts();
541
+ clearTapOpen();
464
542
  layer.hide();
465
543
  };
466
544
  document.addEventListener('keydown', handleKeyDown);
467
545
  return () => {
468
546
  document.removeEventListener('keydown', handleKeyDown);
469
547
  };
470
- }, [isOpen, layer, clearTimeouts]);
548
+ }, [isOpen, layer, clearTimeouts, clearTapOpen]);
471
549
 
472
550
  // Render function that wraps layer.render with tooltip styling
473
551
  const renderTooltip = useCallback(