@astryxdesign/core 0.5.2-canary.2da7a39 → 0.5.2-canary.2e51213

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 (375) hide show
  1. package/dist/AppShell/AppShell.d.ts +2 -2
  2. package/dist/AppShell/AppShell.d.ts.map +1 -1
  3. package/dist/AppShell/AppShell.js +10 -7
  4. package/dist/Banner/Banner.d.ts +5 -0
  5. package/dist/Banner/Banner.d.ts.map +1 -1
  6. package/dist/Banner/Banner.js +7 -2
  7. package/dist/Breadcrumbs/BreadcrumbItem.d.ts +3 -2
  8. package/dist/Breadcrumbs/BreadcrumbItem.d.ts.map +1 -1
  9. package/dist/Breadcrumbs/BreadcrumbItem.js +22 -15
  10. package/dist/Carousel/Carousel.d.ts.map +1 -1
  11. package/dist/Carousel/Carousel.js +2 -2
  12. package/dist/Chat/ChatComposer.d.ts +2 -2
  13. package/dist/Chat/ChatComposer.d.ts.map +1 -1
  14. package/dist/Chat/ChatComposer.js +21 -2
  15. package/dist/Chat/ChatToolCalls.d.ts +1 -1
  16. package/dist/Chat/ChatToolCalls.d.ts.map +1 -1
  17. package/dist/Chat/ChatToolCalls.js +12 -34
  18. package/dist/Chat/useChatNewMessages.js +3 -3
  19. package/dist/CheckboxList/CheckboxListItem.d.ts +5 -3
  20. package/dist/CheckboxList/CheckboxListItem.d.ts.map +1 -1
  21. package/dist/DateTimeInput/DateTimeInput.d.ts +47 -13
  22. package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
  23. package/dist/DateTimeInput/DateTimeInput.js +269 -162
  24. package/dist/DateTimeInput/NativeDateSegment.d.ts +24 -0
  25. package/dist/DateTimeInput/NativeDateSegment.d.ts.map +1 -0
  26. package/dist/DateTimeInput/NativeDateSegment.js +235 -0
  27. package/dist/DateTimeInput/NativeTimeSegment.d.ts +30 -0
  28. package/dist/DateTimeInput/NativeTimeSegment.d.ts.map +1 -0
  29. package/dist/DateTimeInput/NativeTimeSegment.js +248 -0
  30. package/dist/DateTimeInput/index.d.ts +1 -1
  31. package/dist/DateTimeInput/index.d.ts.map +1 -1
  32. package/dist/DateTimeInput/nativePickerSegmentStyles.d.ts +95 -0
  33. package/dist/DateTimeInput/nativePickerSegmentStyles.d.ts.map +1 -0
  34. package/dist/DateTimeInput/nativePickerSegmentStyles.js +103 -0
  35. package/dist/DropdownMenu/DropdownMenu.d.ts.map +1 -1
  36. package/dist/DropdownMenu/DropdownMenu.js +2 -4
  37. package/dist/Field/Field.d.ts.map +1 -1
  38. package/dist/Field/Field.js +10 -4
  39. package/dist/Field/PanelSearchInput.d.ts.map +1 -1
  40. package/dist/Field/PanelSearchInput.js +3 -5
  41. package/dist/FieldStatus/FieldStatus.d.ts.map +1 -1
  42. package/dist/FieldStatus/FieldStatus.js +4 -2
  43. package/dist/Layer/useTouchTrigger.d.ts.map +1 -1
  44. package/dist/Layer/useTouchTrigger.js +2 -4
  45. package/dist/Layout/Layout.d.ts +7 -8
  46. package/dist/Layout/Layout.d.ts.map +1 -1
  47. package/dist/Layout/Layout.js +7 -8
  48. package/dist/MultiSelector/MultiSelector.d.ts +11 -1
  49. package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
  50. package/dist/MultiSelector/MultiSelector.js +89 -45
  51. package/dist/RadioList/RadioListItem.d.ts +30 -5
  52. package/dist/RadioList/RadioListItem.d.ts.map +1 -1
  53. package/dist/RadioList/RadioListItem.js +17 -4
  54. package/dist/Resizable/ResizeHandle.d.ts.map +1 -1
  55. package/dist/Resizable/ResizeHandle.js +102 -44
  56. package/dist/Resizable/index.d.ts +2 -0
  57. package/dist/Resizable/index.d.ts.map +1 -1
  58. package/dist/Resizable/index.js +1 -0
  59. package/dist/Resizable/useResizable.d.ts +89 -8
  60. package/dist/Resizable/useResizable.d.ts.map +1 -1
  61. package/dist/Resizable/useResizable.js +521 -38
  62. package/dist/Resizable/utils.d.ts +35 -0
  63. package/dist/Resizable/utils.d.ts.map +1 -0
  64. package/dist/Resizable/utils.js +27 -0
  65. package/dist/Selector/Selector.d.ts +10 -0
  66. package/dist/Selector/Selector.d.ts.map +1 -1
  67. package/dist/Selector/Selector.js +105 -57
  68. package/dist/Selector/useSelectorPresentation.d.ts +1 -0
  69. package/dist/Selector/useSelectorPresentation.d.ts.map +1 -1
  70. package/dist/Selector/useSelectorPresentation.js +17 -2
  71. package/dist/Slider/Slider.d.ts.map +1 -1
  72. package/dist/Slider/Slider.js +3 -5
  73. package/dist/Spinner/Spinner.d.ts.map +1 -1
  74. package/dist/Spinner/Spinner.js +9 -10
  75. package/dist/Stepper/Step.d.ts +2 -1
  76. package/dist/Stepper/Step.d.ts.map +1 -1
  77. package/dist/Stepper/Step.js +75 -11
  78. package/dist/Stepper/Stepper.d.ts.map +1 -1
  79. package/dist/Stepper/Stepper.js +1 -0
  80. package/dist/TabList/TabList.d.ts.map +1 -1
  81. package/dist/TabList/TabList.js +2 -3
  82. package/dist/Table/index.d.ts +16 -1
  83. package/dist/Table/index.d.ts.map +1 -1
  84. package/dist/Table/index.js +4 -0
  85. package/dist/Table/plugins/columnResize/useTableColumnResize.d.ts.map +1 -1
  86. package/dist/Table/plugins/columnResize/useTableColumnResize.js +8 -9
  87. package/dist/Table/plugins/rowStatus/index.d.ts +1 -1
  88. package/dist/Table/plugins/rowStatus/index.d.ts.map +1 -1
  89. package/dist/Table/plugins/rowStatus/useTableRowStatus.d.ts +11 -30
  90. package/dist/Table/plugins/rowStatus/useTableRowStatus.d.ts.map +1 -1
  91. package/dist/Table/plugins/rowStatus/useTableRowStatus.js +108 -62
  92. package/dist/Table/plugins/sortable/useTableSortable.d.ts.map +1 -1
  93. package/dist/Table/plugins/sortable/useTableSortable.js +9 -1
  94. package/dist/Text/useTruncation.d.ts +1 -1
  95. package/dist/Text/useTruncation.d.ts.map +1 -1
  96. package/dist/Text/useTruncation.js +8 -7
  97. package/dist/TimeInput/TimeInput.d.ts +16 -1
  98. package/dist/TimeInput/TimeInput.d.ts.map +1 -1
  99. package/dist/TimeInput/TimeInput.js +98 -60
  100. package/dist/TimeInput/index.d.ts +1 -1
  101. package/dist/TimeInput/index.d.ts.map +1 -1
  102. package/dist/Timestamp/Timestamp.d.ts +2 -2
  103. package/dist/Timestamp/Timestamp.d.ts.map +1 -1
  104. package/dist/Timestamp/Timestamp.js +4 -145
  105. package/dist/Timestamp/formatRelativeTime.d.ts +20 -0
  106. package/dist/Timestamp/formatRelativeTime.d.ts.map +1 -0
  107. package/dist/Timestamp/formatRelativeTime.js +81 -0
  108. package/dist/Toast/ToastViewport.d.ts.map +1 -1
  109. package/dist/Toast/ToastViewport.js +125 -77
  110. package/dist/Tokenizer/Tokenizer.d.ts +0 -38
  111. package/dist/Tokenizer/Tokenizer.d.ts.map +1 -1
  112. package/dist/Tokenizer/Tokenizer.js +94 -26
  113. package/dist/Tokenizer/useEndLaneReserve.d.ts +31 -0
  114. package/dist/Tokenizer/useEndLaneReserve.d.ts.map +1 -0
  115. package/dist/Tokenizer/useEndLaneReserve.js +136 -0
  116. package/dist/Tooltip/useTooltip.d.ts +1 -1
  117. package/dist/Tooltip/useTooltip.d.ts.map +1 -1
  118. package/dist/Tooltip/useTooltip.js +53 -42
  119. package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
  120. package/dist/Typeahead/BaseTypeahead.js +85 -30
  121. package/dist/Typeahead/Typeahead.d.ts +0 -21
  122. package/dist/Typeahead/Typeahead.d.ts.map +1 -1
  123. package/dist/Typeahead/Typeahead.js +108 -67
  124. package/dist/Typeahead/busyIndicatorLane.d.ts +62 -0
  125. package/dist/Typeahead/busyIndicatorLane.d.ts.map +1 -0
  126. package/dist/Typeahead/busyIndicatorLane.js +98 -0
  127. package/dist/astryx.css +25 -7
  128. package/dist/hooks/useFocusReturnVisibility.d.ts.map +1 -1
  129. package/dist/hooks/useFocusReturnVisibility.js +3 -5
  130. package/dist/hooks/useIsomorphicLayoutEffect.d.ts +2 -2
  131. package/dist/hooks/useIsomorphicLayoutEffect.d.ts.map +1 -1
  132. package/dist/hooks/useOverflow.js +11 -11
  133. package/dist/hooks/useScrollOverflow.d.ts.map +1 -1
  134. package/dist/hooks/useScrollOverflow.js +7 -4
  135. package/dist/theme/defineTheme.d.ts +13 -0
  136. package/dist/theme/defineTheme.d.ts.map +1 -1
  137. package/dist/theme/defineTheme.js +7 -0
  138. package/dist/theme/generateThemeRules.d.ts.map +1 -1
  139. package/dist/theme/generateThemeRules.js +1 -1
  140. package/dist/theme/localTokens.d.ts +18 -0
  141. package/dist/theme/localTokens.d.ts.map +1 -0
  142. package/dist/theme/localTokens.js +162 -0
  143. package/dist/utils/interactionModality.d.ts +6 -28
  144. package/dist/utils/interactionModality.d.ts.map +1 -1
  145. package/dist/utils/interactionModality.js +72 -37
  146. package/dist/utils/sharedResizeObserver.d.ts +17 -9
  147. package/dist/utils/sharedResizeObserver.d.ts.map +1 -1
  148. package/dist/utils/sharedResizeObserver.js +54 -12
  149. package/locales/en.json +8 -0
  150. package/locales/pseudo.json +6 -0
  151. package/package.json +7 -2
  152. package/src/AlertDialog/useImperativeAlertDialog.test.tsx +163 -0
  153. package/src/AppShell/AppShell.doc.mjs +4 -3
  154. package/src/AppShell/AppShell.test.tsx +21 -0
  155. package/src/AppShell/AppShell.tsx +12 -7
  156. package/src/Banner/Banner.doc.mjs +5 -1
  157. package/src/Banner/Banner.tsx +12 -1
  158. package/src/BottomSheet/BottomSheet.doc.mjs +31 -0
  159. package/src/BottomSheet/BottomSheet.spec.md +178 -0
  160. package/src/Breadcrumbs/BreadcrumbItem.tsx +18 -9
  161. package/src/Breadcrumbs/Breadcrumbs.doc.mjs +4 -1
  162. package/src/Breadcrumbs/Breadcrumbs.test.tsx +12 -0
  163. package/src/Button/Button.doc.mjs +56 -0
  164. package/src/ButtonGroup/ButtonGroup.doc.mjs +47 -0
  165. package/src/Calendar/hooks/useCalendarConstraints.test.ts +153 -0
  166. package/src/Calendar/hooks/useCalendarDays.test.ts +265 -0
  167. package/src/Calendar/hooks/useCalendarNavigation.test.ts +252 -0
  168. package/src/Carousel/Carousel.test.tsx +83 -0
  169. package/src/Carousel/Carousel.tsx +8 -2
  170. package/src/Chat/ChatComposer.doc.mjs +7 -4
  171. package/src/Chat/ChatComposer.test.tsx +84 -2
  172. package/src/Chat/ChatComposer.tsx +45 -3
  173. package/src/Chat/ChatSendButton.test.tsx +189 -0
  174. package/src/Chat/ChatToolCalls.doc.mjs +1 -1
  175. package/src/Chat/ChatToolCalls.test.tsx +33 -1
  176. package/src/Chat/ChatToolCalls.tsx +13 -34
  177. package/src/Chat/useChatNewMessages.ts +3 -3
  178. package/src/Chat/useChatPasteAsToken.test.ts +191 -0
  179. package/src/CheckboxList/CheckboxList.doc.mjs +172 -31
  180. package/src/CheckboxList/CheckboxList.spec.md +215 -0
  181. package/src/CheckboxList/CheckboxList.test.tsx +45 -0
  182. package/src/CheckboxList/CheckboxListItem.doc.mjs +11 -9
  183. package/src/CheckboxList/CheckboxListItem.tsx +5 -3
  184. package/src/Collapsible/useCollapsible.test.tsx +236 -0
  185. package/src/CommandPalette/CommandPalette.doc.mjs +72 -0
  186. package/src/CommandPalette/CommandPalette.spec.md +224 -0
  187. package/src/CommandPalette/CommandPaletteEmpty.test.tsx +101 -0
  188. package/src/ComplexSelector/ComplexSelector.doc.mjs +42 -0
  189. package/src/ComplexSelector/ComplexSelector.spec.md +190 -0
  190. package/src/ContextMenu/ContextMenu.doc.mjs +48 -0
  191. package/src/ContextMenu/ContextMenu.spec.md +207 -0
  192. package/src/DateTimeInput/DateTimeInput.doc.mjs +43 -23
  193. package/src/DateTimeInput/DateTimeInput.tsx +388 -181
  194. package/src/DateTimeInput/DateTimeInputTouch.test.tsx +10 -2
  195. package/src/DateTimeInput/NativeDateSegment.tsx +308 -0
  196. package/src/DateTimeInput/NativePickerSegments.test.tsx +626 -0
  197. package/src/DateTimeInput/NativeTimeSegment.tsx +331 -0
  198. package/src/DateTimeInput/index.ts +1 -0
  199. package/src/DateTimeInput/nativePickerSegmentStyles.ts +120 -0
  200. package/src/Divider/Divider.doc.mjs +24 -0
  201. package/src/Divider/Divider.spec.md +157 -0
  202. package/src/DropdownMenu/DropdownMenu.doc.mjs +104 -0
  203. package/src/DropdownMenu/DropdownMenu.spec.md +246 -0
  204. package/src/DropdownMenu/DropdownMenu.tsx +2 -4
  205. package/src/Field/Field.doc.mjs +1 -0
  206. package/src/Field/Field.test.tsx +39 -0
  207. package/src/Field/Field.tsx +18 -1
  208. package/src/Field/InputClearButton.test.tsx +68 -6
  209. package/src/Field/PanelSearchInput.tsx +3 -5
  210. package/src/FieldStatus/FieldStatus.doc.mjs +21 -0
  211. package/src/FieldStatus/FieldStatus.spec.md +213 -0
  212. package/src/FieldStatus/FieldStatus.test.tsx +17 -0
  213. package/src/FieldStatus/FieldStatus.tsx +7 -2
  214. package/src/Grid/Grid.doc.mjs +19 -0
  215. package/src/Grid/Grid.spec.md +162 -0
  216. package/src/Icon/Icon.doc.mjs +196 -48
  217. package/src/Icon/Icon.spec.md +192 -0
  218. package/src/IconButton/IconButton.doc.mjs +38 -0
  219. package/src/Kbd/Kbd.doc.mjs +18 -0
  220. package/src/Kbd/Kbd.spec.md +142 -0
  221. package/src/Layer/useTouchTrigger.ts +2 -4
  222. package/src/Layout/Layout.doc.mjs +57 -17
  223. package/src/Layout/Layout.spec.md +235 -0
  224. package/src/Layout/Layout.test.tsx +1 -1
  225. package/src/Layout/Layout.tsx +7 -8
  226. package/src/Layout/LayoutHeader.doc.mjs +9 -0
  227. package/src/Layout/LayoutPanel.doc.mjs +4 -3
  228. package/src/Lightbox/Lightbox.doc.mjs +149 -33
  229. package/src/Lightbox/Lightbox.spec.md +198 -0
  230. package/src/Lightbox/useLightbox.test.tsx +263 -0
  231. package/src/Markdown/Markdown.doc.mjs +60 -0
  232. package/src/Markdown/Markdown.spec.md +207 -0
  233. package/src/MobileNav/MobileNav.doc.mjs +140 -32
  234. package/src/MobileNav/MobileNav.spec.md +209 -0
  235. package/src/MoreMenu/MoreMenu.doc.mjs +42 -0
  236. package/src/MoreMenu/MoreMenu.spec.md +222 -0
  237. package/src/MultiSelector/MultiSelector.doc.mjs +124 -1
  238. package/src/MultiSelector/MultiSelector.spec.md +293 -0
  239. package/src/MultiSelector/MultiSelector.test.tsx +305 -2
  240. package/src/MultiSelector/MultiSelector.tsx +124 -55
  241. package/src/NavIcon/NavIcon.doc.mjs +17 -0
  242. package/src/NavIcon/NavIcon.spec.md +134 -0
  243. package/src/NavMenu/NavHeadingMenu.spec.md +210 -0
  244. package/src/NavMenu/NavMenu.doc.mjs +37 -0
  245. package/src/Outline/Outline.doc.mjs +35 -0
  246. package/src/Outline/Outline.spec.md +162 -0
  247. package/src/OverflowList/OverflowList.doc.mjs +24 -0
  248. package/src/OverflowList/OverflowList.spec.md +157 -0
  249. package/src/Pagination/Pagination.doc.mjs +74 -0
  250. package/src/Pagination/Pagination.spec.md +226 -0
  251. package/src/Popover/Popover.doc.mjs +264 -65
  252. package/src/Popover/Popover.spec.md +421 -0
  253. package/src/Popover/usePopover.doc.mjs +208 -33
  254. package/src/PowerSearch/PowerSearch.spec.md +291 -0
  255. package/src/PowerSearch/PowerSearchFilterEditor.test.tsx +253 -0
  256. package/src/PowerSearch/PowerSearchToken.test.tsx +228 -0
  257. package/src/ProgressBar/ProgressBar.doc.mjs +198 -38
  258. package/src/ProgressBar/ProgressBar.spec.md +261 -0
  259. package/src/RadioList/RadioList.test.tsx +136 -2
  260. package/src/RadioList/RadioListItem.doc.mjs +30 -11
  261. package/src/RadioList/RadioListItem.tsx +44 -8
  262. package/src/Resizable/Resizable.doc.mjs +130 -24
  263. package/src/Resizable/Resizable.spec.md +246 -0
  264. package/src/Resizable/ResizeHandle.test.tsx +177 -56
  265. package/src/Resizable/ResizeHandle.tsx +128 -45
  266. package/src/Resizable/index.ts +2 -0
  267. package/src/Resizable/shippedDirections.test.ts +462 -0
  268. package/src/Resizable/useResizable.doc.mjs +124 -29
  269. package/src/Resizable/useResizable.test.ts +1277 -3
  270. package/src/Resizable/useResizable.ts +760 -48
  271. package/src/Resizable/utils.test.ts +74 -0
  272. package/src/Resizable/utils.ts +35 -0
  273. package/src/Section/Section.doc.mjs +17 -0
  274. package/src/Section/Section.spec.md +139 -0
  275. package/src/SegmentedControl/SegmentedControl.doc.mjs +83 -0
  276. package/src/SegmentedControl/SegmentedControl.spec.md +152 -0
  277. package/src/Selector/Selector.doc.mjs +133 -97
  278. package/src/Selector/Selector.spec.md +300 -0
  279. package/src/Selector/Selector.test.tsx +318 -1
  280. package/src/Selector/Selector.tsx +127 -60
  281. package/src/Selector/useSelectorPresentation.ts +24 -4
  282. package/src/SideNav/SideNav.test.tsx +3 -3
  283. package/src/SideNav/SideNavCollapseButton.doc.mjs +8 -0
  284. package/src/SideNav/SideNavItem.doc.mjs +1 -1
  285. package/src/Skeleton/Skeleton.doc.mjs +12 -0
  286. package/src/Skeleton/Skeleton.spec.md +127 -0
  287. package/src/Slider/Slider.doc.mjs +184 -30
  288. package/src/Slider/Slider.spec.md +224 -0
  289. package/src/Slider/Slider.tsx +2 -5
  290. package/src/Spinner/Spinner.doc.mjs +5 -5
  291. package/src/Spinner/Spinner.test.tsx +32 -7
  292. package/src/Spinner/Spinner.tsx +46 -14
  293. package/src/Stack/Stack.doc.mjs +22 -0
  294. package/src/Stack/Stack.spec.md +155 -0
  295. package/src/StatusDot/StatusDot.doc.mjs +17 -0
  296. package/src/StatusDot/StatusDot.spec.md +138 -0
  297. package/src/Stepper/Step.doc.mjs +1 -1
  298. package/src/Stepper/Step.tsx +118 -5
  299. package/src/Stepper/Stepper.doc.mjs +51 -1
  300. package/src/Stepper/Stepper.spec.md +245 -0
  301. package/src/Stepper/Stepper.test.tsx +315 -0
  302. package/src/Stepper/Stepper.tsx +8 -0
  303. package/src/Switch/Switch.doc.mjs +40 -0
  304. package/src/Switch/Switch.spec.md +175 -0
  305. package/src/TabList/TabList.tsx +2 -3
  306. package/src/Table/Table.doc.mjs +102 -24
  307. package/src/Table/Table.perf.test.tsx +51 -1
  308. package/src/Table/Table.spec.md +325 -0
  309. package/src/Table/TableHeaderCell.test.tsx +291 -0
  310. package/src/Table/index.ts +19 -4
  311. package/src/Table/plugins/columnResize/useTableColumnResize.tsx +8 -12
  312. package/src/Table/plugins/filtering/useTableFilterState.test.tsx +143 -0
  313. package/src/Table/plugins/groupedRows/useTableGroupedRows.test.tsx +2 -1
  314. package/src/Table/plugins/rowStatus/index.ts +1 -4
  315. package/src/Table/plugins/rowStatus/useTableRowStatus.public.test.ts +92 -0
  316. package/src/Table/plugins/rowStatus/useTableRowStatus.spec.md +354 -0
  317. package/src/Table/plugins/rowStatus/useTableRowStatus.test.tsx +431 -56
  318. package/src/Table/plugins/rowStatus/useTableRowStatus.tsx +155 -84
  319. package/src/Table/plugins/sortable/useTableSortable.test.tsx +72 -0
  320. package/src/Table/plugins/sortable/useTableSortable.tsx +18 -1
  321. package/src/Table/useTableGroupedRows.doc.mjs +2 -2
  322. package/src/Table/useTableRowStatus.doc.mjs +5 -5
  323. package/src/Text/Text.doc.mjs +190 -43
  324. package/src/Text/Text.spec.md +206 -0
  325. package/src/Text/useTruncation.ts +8 -7
  326. package/src/TextArea/TextArea.doc.mjs +255 -45
  327. package/src/TextArea/TextArea.spec.md +205 -0
  328. package/src/TimeInput/NativeTimeInput.test.tsx +269 -0
  329. package/src/TimeInput/TimeInput.doc.mjs +47 -23
  330. package/src/TimeInput/TimeInput.tsx +109 -50
  331. package/src/TimeInput/index.ts +1 -0
  332. package/src/Timestamp/Timestamp.doc.mjs +2 -2
  333. package/src/Timestamp/Timestamp.test.tsx +75 -3
  334. package/src/Timestamp/Timestamp.tsx +7 -153
  335. package/src/Timestamp/formatRelativeTime.ts +96 -0
  336. package/src/Toast/ToastViewport.test.tsx +194 -0
  337. package/src/Toast/ToastViewport.tsx +136 -58
  338. package/src/ToggleButton/ToggleButton.doc.mjs +56 -0
  339. package/src/Tokenizer/Tokenizer.doc.mjs +2 -0
  340. package/src/Tokenizer/Tokenizer.spec.md +293 -0
  341. package/src/Tokenizer/Tokenizer.test.tsx +335 -1
  342. package/src/Tokenizer/Tokenizer.tsx +129 -46
  343. package/src/Tokenizer/useEndLaneReserve.ts +146 -0
  344. package/src/Toolbar/Toolbar.doc.mjs +19 -0
  345. package/src/Toolbar/Toolbar.spec.md +174 -0
  346. package/src/Tooltip/Tooltip.doc.mjs +17 -0
  347. package/src/Tooltip/Tooltip.spec.md +142 -0
  348. package/src/Tooltip/Tooltip.test.tsx +21 -1
  349. package/src/Tooltip/useTooltip.test.tsx +318 -0
  350. package/src/Tooltip/useTooltip.tsx +60 -39
  351. package/src/TreeList/TreeList.doc.mjs +58 -0
  352. package/src/TreeList/TreeList.spec.md +224 -0
  353. package/src/Typeahead/BaseTypeahead.tsx +96 -25
  354. package/src/Typeahead/Typeahead.doc.mjs +89 -1
  355. package/src/Typeahead/Typeahead.spec.md +257 -0
  356. package/src/Typeahead/Typeahead.test.tsx +502 -1
  357. package/src/Typeahead/Typeahead.tsx +187 -69
  358. package/src/Typeahead/TypeaheadItem.test.tsx +93 -2
  359. package/src/Typeahead/busyIndicatorLane.tsx +109 -0
  360. package/src/__tests__/stylexDeclarations.ts +51 -0
  361. package/src/hooks/useFocusReturnVisibility.ts +3 -5
  362. package/src/hooks/useOverflow.ts +11 -11
  363. package/src/hooks/useScrollLock.test.ts +103 -0
  364. package/src/hooks/useScrollOverflow.ts +7 -4
  365. package/src/i18n/useTranslator.test.tsx +197 -0
  366. package/src/theme/defineTheme.test.ts +227 -0
  367. package/src/theme/defineTheme.ts +30 -0
  368. package/src/theme/derivedVarRegistry.test.ts +6 -0
  369. package/src/theme/generateThemeRules.ts +4 -1
  370. package/src/theme/localTokens.ts +272 -0
  371. package/src/theme/themingTargets.test.ts +126 -28
  372. package/src/utils/interactionModality.test.ts +83 -0
  373. package/src/utils/interactionModality.ts +88 -36
  374. package/src/utils/sharedResizeObserver.test.ts +124 -5
  375. package/src/utils/sharedResizeObserver.ts +60 -14
@@ -5,39 +5,187 @@ export const docs = {
5
5
  name: 'usePopover',
6
6
  displayName: 'usePopover',
7
7
  group: 'Popover',
8
- keywords: ['popover', 'popup', 'dropdown', 'floating', 'anchor', 'dialog', 'overlay', 'flyout'],
8
+ keywords: [
9
+ 'popover',
10
+ 'popup',
11
+ 'dropdown',
12
+ 'floating',
13
+ 'anchor',
14
+ 'dialog',
15
+ 'overlay',
16
+ 'flyout',
17
+ ],
9
18
  params: [
10
- {name: 'onShow', type: '() => void', description: 'Callback fired when the popover becomes visible.'},
11
- {name: 'onHide', type: '() => void', description: 'Callback fired when the popover is hidden. Use this to return focus to the trigger when needed.'},
12
- {name: 'xstyle', type: 'StyleXStyles', description: 'StyleX styles applied to the popover content wrapper, after the default surface styles.'},
13
- {name: 'hasLightDismiss', type: 'boolean', description: 'Whether clicking outside dismisses the popover.', default: 'true'},
14
- {name: 'hasEscapeDismiss', type: 'boolean', description: 'Whether pressing Escape dismisses the popover. Only takes full effect together with hasLightDismiss: false, since native light dismiss also closes on Escape.', default: 'true'},
15
- {name: 'hasAutoFocus', type: 'boolean', description: 'Whether to automatically focus the first focusable element when opened.', default: 'true'},
16
- {name: 'hasCloseButton', type: 'boolean', description: 'Whether to include a hidden close button that appears for keyboard users.', default: 'true'},
17
- {name: 'closeButtonLabel', type: 'string', description: 'Accessible label for the hidden close button.', default: "'Close popover'"},
18
- {name: 'dialogLabel', type: 'string', description: 'Accessible label for the popover dialog (only applies when role is "dialog"). Provide one when there is no visible title.'},
19
- {name: 'role', type: "'dialog' | 'none'", description: 'ARIA role on the content wrapper. Use "dialog" for genuine dialog content; use "none" for listbox/menu popups whose own content role should be exposed and whose trigger keeps DOM focus.', default: "'dialog'"},
20
- {name: 'isModal', type: 'boolean', description: 'Whether a dialog-role popover is modal (aria-modal). Only applies when role is "dialog".', default: 'true'},
21
- {name: 'hasSurface', type: 'boolean', description: 'Whether to apply the default popover surface background, radius, and shadow.', default: 'true'},
19
+ {
20
+ name: 'onShow',
21
+ type: '() => void',
22
+ description: 'Callback fired when the popover becomes visible.',
23
+ },
24
+ {
25
+ name: 'onHide',
26
+ type: '() => void',
27
+ description:
28
+ 'Callback fired when the popover is hidden. Use this to return focus to the trigger when needed.',
29
+ },
30
+ {
31
+ name: 'xstyle',
32
+ type: 'StyleXStyles',
33
+ description:
34
+ 'StyleX styles applied to the popover content wrapper, after the default surface styles.',
35
+ },
36
+ {
37
+ name: 'hasLightDismiss',
38
+ type: 'boolean',
39
+ description: 'Whether clicking outside dismisses the popover.',
40
+ default: 'true',
41
+ },
42
+ {
43
+ name: 'hasEscapeDismiss',
44
+ type: 'boolean',
45
+ description:
46
+ 'Whether pressing Escape dismisses the popover. Only takes full effect together with hasLightDismiss: false, since native light dismiss also closes on Escape.',
47
+ default: 'true',
48
+ },
49
+ {
50
+ name: 'hasAutoFocus',
51
+ type: 'boolean',
52
+ description:
53
+ 'Whether to automatically focus the first focusable element when opened.',
54
+ default: 'true',
55
+ },
56
+ {
57
+ name: 'hasCloseButton',
58
+ type: 'boolean',
59
+ description:
60
+ 'Whether to include a hidden close button that appears for keyboard users.',
61
+ default: 'true',
62
+ },
63
+ {
64
+ name: 'closeButtonLabel',
65
+ type: 'string',
66
+ description: 'Accessible label for the hidden close button.',
67
+ default: "'Close popover'",
68
+ },
69
+ {
70
+ name: 'dialogLabel',
71
+ type: 'string',
72
+ description:
73
+ 'Accessible label for the popover dialog (only applies when role is "dialog"). Provide one when there is no visible title.',
74
+ },
75
+ {
76
+ name: 'role',
77
+ type: "'dialog' | 'none'",
78
+ description:
79
+ 'ARIA role on the content wrapper. Use "dialog" for genuine dialog content; use "none" for listbox/menu popups whose own content role should be exposed and whose trigger keeps DOM focus.',
80
+ default: "'dialog'",
81
+ },
82
+ {
83
+ name: 'isModal',
84
+ type: 'boolean',
85
+ description:
86
+ 'Whether a dialog-role popover is modal (aria-modal). Only applies when role is "dialog".',
87
+ default: 'true',
88
+ },
89
+ {
90
+ name: 'hasSurface',
91
+ type: 'boolean',
92
+ description:
93
+ 'Whether to apply the default popover surface background, radius, and shadow.',
94
+ default: 'true',
95
+ },
96
+ {
97
+ name: 'surfaceTarget',
98
+ type: 'string',
99
+ description:
100
+ 'Optional component-owned refinement target on the painted surface, without the astryx- prefix. Use and document one when a direct hook composition needs distinct theme reachability. Do not use popover-surface; it is a deprecated compatibility alias of the canonical popover target.',
101
+ },
22
102
  ],
23
103
  returns: [
24
- {name: 'triggerRef', type: '(el: HTMLElement | null) => void', description: 'Ref callback to attach to the trigger element for CSS anchor positioning.'},
25
- {name: 'contentRef', type: 'RefObject<HTMLDivElement | null>', description: 'Ref for the popover content container used by focus trapping.'},
26
- {name: 'anchorId', type: 'string', description: 'CSS anchor name for advanced positioning cases.'},
27
- {name: 'show', type: '(options?: {skipAutoFocus?: boolean}) => void', description: 'Imperatively show the popover. skipAutoFocus preserves current focus for input-triggered popovers.'},
28
- {name: 'hide', type: '() => void', description: 'Imperatively hide the popover.'},
29
- {name: 'toggle', type: '() => void', description: 'Toggle the popover open or closed.'},
30
- {name: 'isOpen', type: 'boolean', description: 'Whether the popover is currently open.'},
31
- {name: 'id', type: 'string', description: 'Unique ID for aria-describedby or aria-controls.'},
32
- {name: 'render', type: '(children: ReactNode, props?: ContextRenderProps) => ReactNode', description: 'Render function for anchor-positioned popover content. Pass placement and alignment here. Logical: start/end resolve against the popover\'s own inherited direction (RTL mirrors in pure CSS).'},
33
- {name: 'triggerProps', type: '{aria-haspopup: "dialog" | "true"; aria-expanded: boolean; aria-controls: string}', description: 'ARIA attributes to spread onto the trigger element. aria-haspopup reflects the popover role.'},
104
+ {
105
+ name: 'triggerRef',
106
+ type: '(el: HTMLElement | null) => void',
107
+ description:
108
+ 'Ref callback to attach to the trigger element for CSS anchor positioning.',
109
+ },
110
+ {
111
+ name: 'contentRef',
112
+ type: 'RefObject<HTMLDivElement | null>',
113
+ description:
114
+ 'Ref for the popover content container used by focus trapping.',
115
+ },
116
+ {
117
+ name: 'anchorId',
118
+ type: 'string',
119
+ description: 'CSS anchor name for advanced positioning cases.',
120
+ },
121
+ {
122
+ name: 'show',
123
+ type: '(options?: {skipAutoFocus?: boolean}) => void',
124
+ description:
125
+ 'Imperatively show the popover. skipAutoFocus preserves current focus for input-triggered popovers.',
126
+ },
127
+ {
128
+ name: 'hide',
129
+ type: '() => void',
130
+ description: 'Imperatively hide the popover.',
131
+ },
132
+ {
133
+ name: 'toggle',
134
+ type: '() => void',
135
+ description: 'Toggle the popover open or closed.',
136
+ },
137
+ {
138
+ name: 'isOpen',
139
+ type: 'boolean',
140
+ description: 'Whether the popover is currently open.',
141
+ },
142
+ {
143
+ name: 'id',
144
+ type: 'string',
145
+ description: 'Unique ID for aria-describedby or aria-controls.',
146
+ },
147
+ {
148
+ name: 'render',
149
+ type: '(children: ReactNode, props?: ContextRenderProps) => ReactNode',
150
+ description:
151
+ "Render function for anchor-positioned popover content. Pass placement and alignment here. Logical: start/end resolve against the popover's own inherited direction (RTL mirrors in pure CSS).",
152
+ },
153
+ {
154
+ name: 'triggerProps',
155
+ type: '{aria-haspopup: "dialog" | "true"; aria-expanded: boolean; aria-controls: string}',
156
+ description:
157
+ 'ARIA attributes to spread onto the trigger element. aria-haspopup reflects the popover role.',
158
+ },
34
159
  ],
35
160
  usage: {
36
- description: 'Headless hook for click-triggered popovers with focus trapping. Combines useLayer with useFocusTrap, auto-focus, light dismiss, Escape handling, and an optional hidden close button for accessible dialog-like popover behavior. Use for custom interactive floating content that needs keyboard navigation.',
161
+ description:
162
+ 'Headless hook for click-triggered popovers with focus trapping. Combines useLayer with useFocusTrap, auto-focus, light dismiss, Escape handling, and an optional hidden close button for accessible dialog-like popover behavior. Use for custom interactive floating content that needs keyboard navigation. The canonical broad surface target is popover; automatic hook-wide emission is separate migration work, so current direct compositions needing a distinct stable seam should pass and document their own surfaceTarget instead of depending on deprecated popover-surface.',
37
163
  bestPractices: [
38
- {guidance: true, description: 'Use for interactive content such as menus, pickers, forms, and command panels that need focus management.'},
39
- {guidance: true, description: 'Prefer the Popover component for standard trigger-content pairs; use the hook for custom trigger patterns.'},
40
- {guidance: false, description: 'Use for non-interactive hover previews: use useHoverCard or useTooltip instead.'},
164
+ {
165
+ guidance: true,
166
+ description:
167
+ 'Use for interactive content such as menus, pickers, forms, and command panels that need focus management.',
168
+ },
169
+ {
170
+ guidance: true,
171
+ description:
172
+ 'Prefer the Popover component for standard trigger-content pairs; use the hook for custom trigger patterns.',
173
+ },
174
+ {
175
+ guidance: true,
176
+ description:
177
+ 'Use popover as the broad surface target. During migration, popover-surface remains compatibility output only; do not create new theme dependencies on it.',
178
+ },
179
+ {
180
+ guidance: true,
181
+ description:
182
+ 'When a custom composition needs its own theme refinement, pass and document an owned surfaceTarget such as selector-popup. It refines the Popover surface rather than creating another anatomy part.',
183
+ },
184
+ {
185
+ guidance: false,
186
+ description:
187
+ 'Use for non-interactive hover previews: use useHoverCard or useTooltip instead.',
188
+ },
41
189
  ],
42
190
  },
43
191
  relatedComponents: ['Popover', 'DropdownMenu', 'HoverCard'],
@@ -48,13 +196,15 @@ export const docs = {
48
196
 
49
197
  /** @type {import('@astryxdesign/cli/authoring').HookTranslationDoc} */
50
198
  export const docsDense = {
51
- description: 'Headless click-triggered popovers w/ focus trap, auto-focus, light dismiss, Escape, optional hidden close button. Use for custom interactive floating content needing keyboard nav.',
199
+ description:
200
+ 'Headless click-triggered popovers w/ focus trap, auto-focus, light dismiss, Escape, optional hidden close button. Canonical broad target is popover; automatic hook-wide emission is separate migration work. Current direct compositions needing a distinct seam pass/document an owned surfaceTarget; do not depend on deprecated popover-surface.',
52
201
  paramDescriptions: {
53
202
  onShow: 'fires when popover becomes visible.',
54
203
  onHide: 'fires when popover hides; use to return focus when needed.',
55
204
  xstyle: 'StyleX styles for content wrapper, after default surface.',
56
205
  hasLightDismiss: 'whether outside click dismisses popover.',
57
- hasEscapeDismiss: 'whether Escape dismisses; full effect only w/ hasLightDismiss false.',
206
+ hasEscapeDismiss:
207
+ 'whether Escape dismisses; full effect only w/ hasLightDismiss false.',
58
208
  hasAutoFocus: 'whether first focusable element receives focus on open.',
59
209
  hasCloseButton: 'whether hidden keyboard close button is included.',
60
210
  closeButtonLabel: 'label for hidden close button.',
@@ -62,6 +212,8 @@ export const docsDense = {
62
212
  role: 'content wrapper ARIA role; "none" for listbox/menu popups.',
63
213
  isModal: 'whether a dialog-role popover is modal (aria-modal).',
64
214
  hasSurface: 'apply default surface background/radius/shadow.',
215
+ surfaceTarget:
216
+ 'optional owned refinement target; document it and do not use deprecated popover-surface.',
65
217
  },
66
218
  returnDescriptions: {
67
219
  triggerRef: 'trigger ref for CSS anchor positioning.',
@@ -76,11 +228,34 @@ export const docsDense = {
76
228
  triggerProps: 'ARIA attrs for trigger.',
77
229
  },
78
230
  usage: {
79
- description: 'Headless click-triggered popovers w/ focus trap, auto-focus, light dismiss, Escape, optional hidden close button. Use for custom interactive floating content needing keyboard nav.',
231
+ description:
232
+ 'Headless click-triggered popovers w/ focus trap, auto-focus, light dismiss, Escape, optional hidden close button. Canonical broad target is popover; automatic hook-wide emission is separate migration work. Current direct compositions needing a distinct seam pass/document an owned surfaceTarget; do not depend on deprecated popover-surface.',
80
233
  bestPractices: [
81
- {guidance: true, description: 'Use for menus, pickers, forms, command panels needing focus management.'},
82
- {guidance: true, description: 'Prefer Popover for standard trigger-content pairs; use hook for custom trigger patterns.'},
83
- {guidance: false, description: 'Use for non-interactive hover previews: use useHoverCard / useTooltip instead.'},
234
+ {
235
+ guidance: true,
236
+ description:
237
+ 'Use for menus, pickers, forms, command panels needing focus management.',
238
+ },
239
+ {
240
+ guidance: true,
241
+ description:
242
+ 'Prefer Popover for standard trigger-content pairs; use hook for custom trigger patterns.',
243
+ },
244
+ {
245
+ guidance: true,
246
+ description:
247
+ 'Use canonical popover for broad surface theming; deprecated popover-surface is compatibility output only.',
248
+ },
249
+ {
250
+ guidance: true,
251
+ description:
252
+ 'For component-specific reachability, pass and document an owned surfaceTarget; it refines the same surface.',
253
+ },
254
+ {
255
+ guidance: false,
256
+ description:
257
+ 'Use for non-interactive hover previews: use useHoverCard / useTooltip instead.',
258
+ },
84
259
  ],
85
260
  },
86
261
  };
@@ -0,0 +1,291 @@
1
+ ---
2
+ schema_version: 3
3
+ template_version: 3
4
+ kind: component
5
+ id: component:PowerSearch
6
+ authority: current
7
+ archive_reason: null
8
+ superseded_by: null
9
+ approved_by: cixzhang
10
+ approved_at: 2026-08-31
11
+ owners: [cixzhang]
12
+ review_triggers: [public-api, behavior, layout, accessibility]
13
+ verified_by:
14
+ [
15
+ packages/core/src/PowerSearch/PowerSearch.test.tsx,
16
+ packages/core/src/PowerSearch/PowerSearchEditPopover.test.tsx,
17
+ packages/core/src/Tokenizer/Tokenizer.test.tsx,
18
+ packages/core/src/Typeahead/Typeahead.test.tsx,
19
+ packages/core/src/Layer/useLayer.test.tsx,
20
+ scripts/check-knowledge.mjs,
21
+ ]
22
+ modules: []
23
+ families: [family:overlay-dismissal]
24
+ design_specs: []
25
+ architecture:
26
+ [
27
+ architecture:interaction-modality,
28
+ architecture:layer-runtime,
29
+ architecture:public-component-api,
30
+ ]
31
+ contributing: []
32
+ system_specs: []
33
+ ---
34
+
35
+ # PowerSearch component contract
36
+
37
+ ## Intent
38
+
39
+ PowerSearch provides structured filtering through field, operator, and value
40
+ selection. Its editor popover keeps the active editing task readable on both narrow
41
+ and wide search bars. When the editor can follow the PowerSearch width, it stays
42
+ aligned to that outer surface. When the 720 CSS px readability cap makes the editor
43
+ narrower, it stays near the stable control that opened it instead of stretching
44
+ across the page or depending on input modality.
45
+
46
+ ## Compatibility and migration
47
+
48
+ - Released default preserved: `no` for editor popovers anchored to a PowerSearch
49
+ wider than 720 CSS px; other widths preserve their current sizing behavior.
50
+ - Compatibility class: intentional default layout and placement change with no
51
+ public API change.
52
+ - Controlled/uncontrolled behavior: unchanged.
53
+ - Migration decisions: `component:PowerSearch/DEC-1` and
54
+ `component:PowerSearch/DEC-2`.
55
+
56
+ Consumer migration instructions belong in consumer docs and release notes.
57
+
58
+ ## Ownership boundary
59
+
60
+ **Owns**
61
+
62
+ - The readable width range of the filter-editor popover.
63
+ - Identifying the stable control that opened the editor: the inner typeahead input
64
+ for the add path, or the actual activated token/control for the edit path.
65
+ - Choosing which outer PowerSearch edge a width-capped editor aligns to from that
66
+ opening control's geometry.
67
+ - Preserving the editing flow, content, and focus behavior while the editor's width
68
+ and horizontal alignment change.
69
+
70
+ **Does not own / non-goals**
71
+
72
+ - Main field/search-menu width — the existing `menuWidth` consumer surface is an
73
+ exact pixel width forwarded to Tokenizer's main field menu, not a maximum and not
74
+ an editor width control.
75
+ - General anchor positioning, viewport collision handling, top-layer hosting, or
76
+ dismissal — owned by `architecture:layer-runtime`.
77
+ - Operator- and value-menu sizing inside the editor.
78
+ - A public prop for overriding editor width or placement.
79
+ - Pointer-coordinate placement, pointer-versus-keyboard branches, continuous
80
+ pointer tracking, or an always-on geometry observer.
81
+
82
+ ## Public concepts
83
+
84
+ No public concept or prop is introduced. The inclusive 400–720 CSS px range and
85
+ conditional alignment are internal PowerSearch readability policy, not callsite
86
+ options. Existing `menuWidth` remains an exact pixel width for only the main
87
+ field/search menu.
88
+
89
+ ## Behavioral and layout contract
90
+
91
+ Current requirements identify their basis and distinguish accepted contract from
92
+ implementation status. The contract is authoritative; the behavior remains unshipped
93
+ until its implementation and verification complete.
94
+
95
+ | ID | Invariant | Basis | Status |
96
+ | --- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- | ----------------------------------------- |
97
+ | FR1 | The filter-editor popover MUST have a rendered border-box width no greater than 720 CSS px. | Owner direction; legibility issue demonstrated by wide PowerSearch adopters | Accepted contract; implementation pending |
98
+ | FR2 | Before viewport collision handling, the editor's preferred width MUST be the outer PowerSearch width clamped to the inclusive 400–720 CSS px range. The 400 px floor MUST yield when the available viewport width cannot fit it. | Existing 400 px editing floor plus `component:PowerSearch/DEC-1` | Accepted contract; implementation pending |
99
+ | FR3 | When the 720 CSS px cap does not make the editor narrower than the outer PowerSearch, including when the 400 px floor is active, the editor MUST preserve current logical-start alignment to the outer PowerSearch. | Compatibility requirement | Accepted contract; implementation pending |
100
+ | FR4 | When the 720 CSS px cap makes the editor narrower, PowerSearch MUST resolve one stable opening control for that open: the inner typeahead input for the add path, or the actual activated token/control for the edit path. | Owner direction plus current composition analysis | Accepted contract; implementation pending |
101
+ | FR5 | For a capped editor, PowerSearch MUST compare the opening control's border-box distance from the outer PowerSearch's physical left and right edges, select the nearer outer edge, and align the editor to that edge. Equal distances MUST fall back to the outer PowerSearch's logical-start edge. The result MUST depend on control geometry, not pointer coordinates or pointer/keyboard modality. | `component:PowerSearch/DEC-2` | Accepted contract; implementation pending |
102
+ | FR6 | Opening-control identity and the selected edge MUST be latched for the open lifetime. Reopening from a control on the opposite side MUST recalculate both; movement after open MUST NOT move the editor. | Stable interaction and performance requirement | Accepted contract; implementation pending |
103
+ | FR7 | Width and preferred-edge selection MUST still yield to the layer runtime's viewport collision handling so the editor remains horizontally visible within the runtime's existing safe area. | `architecture:layer-runtime` | Accepted contract; implementation pending |
104
+ | FR8 | The width and alignment change MUST NOT alter editor content, field/operator/value state, vertical placement, focus movement, selection, dismissal, or screen-reader semantics. | Compatibility and accessibility requirement | Accepted contract; implementation pending |
105
+ | FR9 | The opening-control descriptor owned by `component:Tokenizer/DEC-1` and consumed by PowerSearch MUST remain package-internal. PowerSearch MUST NOT add a public width, maximum-width, input-ref, or positioning prop for this behavior. | `component:Tokenizer/DEC-1`, `architecture:public-component-api`, and `component:PowerSearch/DEC-1` | Accepted contract; implementation pending |
106
+
107
+ ### Allowed variation
108
+
109
+ - **AV1 — Viewport clamping.** The layer runtime may shift or shrink the editor
110
+ when the preferred width or edge alignment would cross its viewport safe area.
111
+ - **AV2 — Writing direction.** Implementations may express the selected physical
112
+ edge through logical alignment values as long as the rendered edge is correct in
113
+ both LTR and RTL.
114
+ - **AV3 — Submenus.** Operator and value menus may retain their own width and
115
+ collision behavior; this contract applies only to the filter-editor popover.
116
+ - **AV4 — Internal representation.** The shared opening-control descriptor may use
117
+ refs, elements, or an equivalent package-internal value. Its observable geometry,
118
+ lifetime, and precedence must satisfy FR3–FR9.
119
+
120
+ ### Representative states
121
+
122
+ | State | Required invariant | Allowed variation |
123
+ | ------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------- |
124
+ | Outer PowerSearch narrower than 400 px | Editor prefers 400 px and logical-start alignment to the outer surface, but yields to available viewport width. | Layer runtime may shift or shrink it to remain visible. |
125
+ | Outer PowerSearch from 400 through 720 px | Editor matches the outer surface and remains logical-start aligned. | Existing vertical collision behavior may flip. |
126
+ | Wider than 720 px; add from inner input near left | Editor is 720 px wide and prefers the outer surface's physical left edge. | Viewport collision handling may shift it. |
127
+ | Wider than 720 px; add from inner input near right | Editor is 720 px wide and prefers the outer surface's physical right edge. | Viewport collision handling may shift it. |
128
+ | Wider than 720 px; edit a token/control near either end | Editor is 720 px wide and prefers the outer edge nearest that activated control. | Custom token markup may vary; identity remains exact. |
129
+ | Wider than 720 px; opening control is equidistant | Editor is 720 px wide and uses the outer surface's logical-start edge. | Direction determines the physical start edge. |
130
+ | Pointer, keyboard, or programmatic activation | The same stable opening control produces the same preferred edge regardless of activation modality. | Focus behavior remains unchanged. |
131
+ | Reopen from a control on the opposite side | Opening-control identity and preferred edge are recalculated for the new open, then remain latched until close. | Later layout changes do not move the open editor. |
132
+
133
+ ### Transformation and precedence order
134
+
135
+ - **ORD1 — Width.** Resolve outer PowerSearch width → apply the 400 px readable
136
+ floor → apply the 720 px readable ceiling → yield to available viewport width.
137
+ - **ORD2 — Opening control.** Resolve the stable inner input or activated
138
+ token/control for the current open.
139
+ - **ORD3 — Horizontal alignment.** If the ceiling did not narrow the editor, use
140
+ outer logical start. Otherwise compare the opening control's physical edge gaps
141
+ inside the outer PowerSearch → choose the nearer outer edge → use outer logical
142
+ start on a tie.
143
+ - **ORD4 — Collision.** Let the layer runtime apply viewport collision handling
144
+ after preferred width and edge selection.
145
+
146
+ ### Performance and resources
147
+
148
+ - **PR1 — One batched open-time read.** PowerSearch MUST read the outer PowerSearch
149
+ and opening-control geometry together once in response to open intent, never
150
+ during React render.
151
+ - **PR2 — Hidden commit before show.** Placement state MUST commit while the editor
152
+ popover is hidden, and the popover MUST show on the next animation frame. Real
153
+ Chromium verification MUST prove there is no first-frame flash at a default or
154
+ stale position.
155
+ - **PR3 — Latch, do not track.** The resolved opening control and preferred edge
156
+ MUST remain latched until close. The implementation MUST NOT install continuous
157
+ pointer tracking or an always-on geometry observer.
158
+ - **PR4 — CSS owns responsive geometry.** CSS anchor sizing and the layer runtime
159
+ MUST own the 400–720 width policy and collision behavior. JavaScript may select
160
+ the per-open anchor/alignment descriptor but MUST NOT continuously calculate
161
+ popup width or collision offsets.
162
+
163
+ ## Accessibility contract
164
+
165
+ - **AR1 — Modality equivalence.** Pointer, keyboard, and programmatic activation of
166
+ the same control MUST resolve the same preferred edge. Keyboard users retain the
167
+ current field selection, editor focus, traversal, apply/cancel, Escape, and
168
+ focus-return behavior.
169
+ - **AR2 — Semantic stability.** Width and placement MUST NOT change the current
170
+ combobox, listbox, option, menu, or editor semantics and accessible names.
171
+ - **AR3 — Visible content.** Viewport clamping MUST keep all editor controls
172
+ horizontally reachable without requiring page-level horizontal scrolling.
173
+
174
+ ## Design relationships
175
+
176
+ | Anatomy or state | Design requirement | Representation authority | Hierarchy role | Component contract |
177
+ | ----------------------- | ----------------------------------------------------------------------------------------------------------------- | ------------------------------ | -------------- | ----------------------- |
178
+ | Search field and tokens | Continue presenting the structured query and its current interaction states. | Current source and public docs | Prominent | FR8 |
179
+ | Main field/search menu | Retain its exact `menuWidth` surface and current selection behavior; it does not inherit the editor's 720 px cap. | Current source and public docs | Prominent | Ownership boundary, FR9 |
180
+ | Filter-editor popover | Stay within the readable 400–720 px range before viewport clamping. | `component:PowerSearch/DEC-1` | Prominent | FR1–FR3, FR7 |
181
+ | Width-capped editor | Prefer the outer edge nearest the stable opening control, independent of activation modality. | `component:PowerSearch/DEC-2` | Prominent | FR4–FR8 |
182
+ | Keyboard-opened editor | Use the same control-geometry rule and preserve current focus behavior. | Accessibility contract | Prominent | FR5, FR8, AR1 |
183
+
184
+ This proposal changes editor geometry only. It does not introduce a new visual
185
+ state, theme target, or public configuration concept.
186
+
187
+ ## Family and system relationships
188
+
189
+ - `architecture:layer-runtime` owns anchor wiring, collision fallbacks, viewport
190
+ safety, top-layer hosting, and visibility reconciliation. PowerSearch supplies
191
+ its per-open preferred width source, stable opening control, and edge choice.
192
+ - `architecture:interaction-modality` continues to own shared modality state, but
193
+ the editor placement result intentionally does not branch on modality.
194
+ - `architecture:public-component-api` owns the public-surface bar. Width and
195
+ placement remain internal because they are component usability invariants rather
196
+ than adopter policy.
197
+ - `family:overlay-dismissal` continues to own shared Escape and platform-close
198
+ ordering; this proposal changes no dismissal behavior.
199
+ - `component:Tokenizer/DEC-1` is current authority for the package-internal stable
200
+ outer-field/opening-control descriptor and its safe fallback semantics.
201
+ PowerSearch consumes that identity only after resolving an editor narrower than
202
+ the outer field; Tokenizer contributes no PowerSearch width policy.
203
+
204
+ ### Current dependency
205
+
206
+ `component:Tokenizer` is current authority on `main` and satisfies PowerSearch's
207
+ opening-control prerequisite. Its DEC-1 owns the package-internal descriptor,
208
+ modality-neutral edge choice, hidden placement commit, one-read open preparation,
209
+ and collision-last ordering that this record consumes. Both current contracts still
210
+ mark implementation and focused real-Chromium verification as pending.
211
+
212
+ ### Required visual evidence stories
213
+
214
+ - PowerSearch MUST provide dedicated wide-field evidence above 720 CSS px with the
215
+ editor already open in the captured state. The matrix MUST cover opening controls
216
+ near the outer start edge, outer end edge, and equidistant; keyboard activation;
217
+ LTR and RTL; narrow-viewport collision; and the first painted frame with no
218
+ default-position flash.
219
+ - Separate Tokenizer evidence stories MUST cover its unchanged full-width menu,
220
+ wrapped-token and `unfocusedLayer` states, RTL, and viewport collision. Those
221
+ stories verify the package-internal opening-control prerequisite; they MUST NOT
222
+ inherit PowerSearch's 400–720 editor width policy.
223
+
224
+ ## Verification map
225
+
226
+ | Contract | Verification | Representative states | Mutation or failure expectation | Audit section |
227
+ | ---------------- | -------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- | --------------------------------- |
228
+ | FR1–FR3, FR7 | PowerSearch unit tests plus real-Chromium geometry assertions | 399/400, 720/721, wide outer surface, narrow viewport, LTR/RTL, collision | Removing either clamp, capping the main menu, or allowing viewport overflow fails. | `audit:PowerSearch/layout` |
229
+ | FR4–FR6, FR9 | Opening-path tests plus real-Chromium geometry assertions | Add from inner input; edit actual left/right/equidistant token/control; pointer/keyboard; reopen opposite side | Using pointer coordinates, modality, wrapper-only identity, stale identity, or public props fails. | `audit:PowerSearch/interaction` |
230
+ | PR1–PR4 | Instrumented unit tests plus first-frame real-Chromium capture | Open, close, reopen opposite side; hidden placement commit; first painted frame | Render-time reads, repeated reads, observers, pointer tracking, or a default-position flash fails. | `audit:PowerSearch/performance` |
231
+ | FR8, AR1–AR3 | Existing PowerSearch/editor suites plus focused browser checks | Pointer and keyboard edit, apply, cancel, Escape, light dismiss, focus return, selection, semantics | Focus, semantics, selection, dismissal, or reachability changes fail. | `audit:PowerSearch/accessibility` |
232
+ | Record structure | `scripts/check-knowledge.mjs` | Component-contract schema and current-authority dependency | Missing or invalid metadata and sections fail repository validation. | `audit:PowerSearch/knowledge` |
233
+
234
+ Current-source Chromium evidence establishes the geometry gap, not implementation
235
+ completion:
236
+
237
+ - At an 800 px PowerSearch with many filters, the active inner input's logical text
238
+ start was 570.8 px inward from the outer edge while the editor remained aligned
239
+ to the outer 800 px surface.
240
+ - In a 400 px Tokenizer with two selected tokens, the active inner input's logical
241
+ text start was 242.3 px inward while the suggestion surface remained aligned to
242
+ the outer wrapper. The same magnitude reproduced in LTR and RTL.
243
+
244
+ Completion still requires real-Chromium proof at the 400 and 720 boundaries, a
245
+ narrow viewport, opening controls near both edges and equidistant, reopen from the
246
+ opposite side, pointer and keyboard activation, LTR and RTL, collision handling,
247
+ first-frame stability, and unchanged focus, selection, and dismissal.
248
+
249
+ ## Decision log
250
+
251
+ ### DEC-1 — PowerSearch owns one internal readable editor width
252
+
253
+ **Reference:** `component:PowerSearch/DEC-1`
254
+ **Decider:** `cixzhang`, 2026-08-31
255
+
256
+ A very wide editor makes a small field/operator/value task harder to scan, while a
257
+ callsite width prop would turn readability policy into permanent public API and
258
+ produce inconsistent products. PowerSearch therefore owns one internal preferred
259
+ range: outer PowerSearch width clamped to 400–720 CSS px, then constrained by the
260
+ viewport. This rule does not change Tokenizer's main-menu sizing or PowerSearch's
261
+ existing exact-pixel `menuWidth` surface.
262
+
263
+ Rejected: a public editor width/max-width prop, applying the 720 cap to Tokenizer's
264
+ main menu, or treating `menuWidth` as an editor width.
265
+
266
+ ### DEC-2 — Only a capped editor follows the stable opening control
267
+
268
+ **Reference:** `component:PowerSearch/DEC-2`
269
+ **Decider:** `cixzhang`, 2026-08-31
270
+
271
+ A full-width or floor-expanded editor remains aligned to the outer PowerSearch.
272
+ Only when the 720 px ceiling makes the editor narrower does PowerSearch use the
273
+ stable opening control to choose the nearer outer edge. The add path uses the
274
+ inner typeahead input; the edit path uses the actual activated token/control. The
275
+ control's geometry produces the same result for pointer, keyboard, and programmatic
276
+ activation, and the result is latched until close.
277
+
278
+ Rejected: pointer-coordinate placement, pointer-versus-keyboard branches, always
279
+ using one edge, anchoring every width to the inner control, continuous tracking,
280
+ render-time geometry reads, and showing before hidden placement state commits.
281
+
282
+ ## Open questions
283
+
284
+ None. The contract is accepted and current. Implementation and focused real-Chromium
285
+ verification remain pending.
286
+
287
+ ## Content boundary
288
+
289
+ This file does not duplicate consumer prop tables, configuration examples,
290
+ operator/value editor internals, current audit results, implementation steps, or
291
+ shared layer positioning and dismissal rules. It links to their owners.