@qijenchen/design-system 0.1.0-beta.145 → 0.1.0-beta.147

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 (576) hide show
  1. package/dist/components/Accordion/accordion.d.ts +1 -1
  2. package/dist/components/Accordion/accordion.d.ts.map +1 -1
  3. package/dist/components/Accordion/accordion.js +8 -3
  4. package/dist/components/Accordion/accordion.js.map +1 -1
  5. package/dist/components/AccountMenu/account-menu.d.ts.map +1 -1
  6. package/dist/components/AccountMenu/account-menu.js.map +1 -1
  7. package/dist/components/AgentPanel/agent-panel-fab.d.ts +7 -1
  8. package/dist/components/AgentPanel/agent-panel-fab.d.ts.map +1 -1
  9. package/dist/components/AgentPanel/agent-panel-fab.js +36 -14
  10. package/dist/components/AgentPanel/agent-panel-fab.js.map +1 -1
  11. package/dist/components/AgentPanel/agent-panel-logo.d.ts +3 -3
  12. package/dist/components/AgentPanel/agent-panel-logo.js.map +1 -1
  13. package/dist/components/AgentPanel/agent-panel.d.ts +1 -1
  14. package/dist/components/AgentPanel/agent-panel.d.ts.map +1 -1
  15. package/dist/components/AgentPanel/agent-panel.js +28 -22
  16. package/dist/components/AgentPanel/agent-panel.js.map +1 -1
  17. package/dist/components/AppShell/app-shell.d.ts +1 -1
  18. package/dist/components/AppShell/app-shell.js +1 -1
  19. package/dist/components/AppShell/app-shell.js.map +1 -1
  20. package/dist/components/Avatar/avatar.d.ts +95 -1
  21. package/dist/components/Avatar/avatar.d.ts.map +1 -1
  22. package/dist/components/Avatar/avatar.js +106 -15
  23. package/dist/components/Avatar/avatar.js.map +1 -1
  24. package/dist/components/Avatar/index.js +14 -2
  25. package/dist/components/Breadcrumb/breadcrumb.d.ts +4 -2
  26. package/dist/components/Breadcrumb/breadcrumb.d.ts.map +1 -1
  27. package/dist/components/Breadcrumb/breadcrumb.js +2 -2
  28. package/dist/components/Breadcrumb/breadcrumb.js.map +1 -1
  29. package/dist/components/BulkActionBar/bulk-action-bar.d.ts.map +1 -1
  30. package/dist/components/BulkActionBar/bulk-action-bar.js +27 -2
  31. package/dist/components/BulkActionBar/bulk-action-bar.js.map +1 -1
  32. package/dist/components/Button/button.d.ts +2 -2
  33. package/dist/components/Button/button.d.ts.map +1 -1
  34. package/dist/components/Button/button.js +118 -22
  35. package/dist/components/Button/button.js.map +1 -1
  36. package/dist/components/Calendar/calendar.d.ts +34 -10
  37. package/dist/components/Calendar/calendar.d.ts.map +1 -1
  38. package/dist/components/Calendar/calendar.js +210 -36
  39. package/dist/components/Calendar/calendar.js.map +1 -1
  40. package/dist/components/Carousel/carousel.d.ts +1 -1
  41. package/dist/components/Carousel/carousel.d.ts.map +1 -1
  42. package/dist/components/Carousel/carousel.js +3 -2
  43. package/dist/components/Carousel/carousel.js.map +1 -1
  44. package/dist/components/Chart/chart.d.ts +1 -0
  45. package/dist/components/Chart/chart.d.ts.map +1 -1
  46. package/dist/components/Chart/chart.js +8 -0
  47. package/dist/components/Chart/chart.js.map +1 -1
  48. package/dist/components/Checkbox/checkbox.d.ts +1 -1
  49. package/dist/components/Checkbox/checkbox.d.ts.map +1 -1
  50. package/dist/components/Checkbox/checkbox.js +10 -2
  51. package/dist/components/Checkbox/checkbox.js.map +1 -1
  52. package/dist/components/Chip/chip.d.ts +1 -1
  53. package/dist/components/Chip/chip.d.ts.map +1 -1
  54. package/dist/components/Chip/chip.js +4 -2
  55. package/dist/components/Chip/chip.js.map +1 -1
  56. package/dist/components/Combobox/combobox.d.ts +1 -1
  57. package/dist/components/Combobox/combobox.d.ts.map +1 -1
  58. package/dist/components/Combobox/combobox.js +37 -11
  59. package/dist/components/Combobox/combobox.js.map +1 -1
  60. package/dist/components/Command/command.d.ts +1 -1
  61. package/dist/components/Command/command.d.ts.map +1 -1
  62. package/dist/components/Command/command.js +106 -8
  63. package/dist/components/Command/command.js.map +1 -1
  64. package/dist/components/DataTable/cell-registry.d.ts.map +1 -1
  65. package/dist/components/DataTable/cell-registry.js +3 -2
  66. package/dist/components/DataTable/cell-registry.js.map +1 -1
  67. package/dist/components/DataTable/data-table-column-visibility-panel.d.ts.map +1 -1
  68. package/dist/components/DataTable/data-table-column-visibility-panel.js +24 -24
  69. package/dist/components/DataTable/data-table-column-visibility-panel.js.map +1 -1
  70. package/dist/components/DataTable/data-table-interaction-layer.d.ts +2 -1
  71. package/dist/components/DataTable/data-table-interaction-layer.d.ts.map +1 -1
  72. package/dist/components/DataTable/data-table-interaction-layer.js +3 -4
  73. package/dist/components/DataTable/data-table-interaction-layer.js.map +1 -1
  74. package/dist/components/DataTable/data-table-sort-manager.d.ts.map +1 -1
  75. package/dist/components/DataTable/data-table-sort-manager.js +20 -20
  76. package/dist/components/DataTable/data-table-sort-manager.js.map +1 -1
  77. package/dist/components/DataTable/data-table.d.ts +8 -5
  78. package/dist/components/DataTable/data-table.d.ts.map +1 -1
  79. package/dist/components/DataTable/data-table.js +197 -71
  80. package/dist/components/DataTable/data-table.js.map +1 -1
  81. package/dist/components/DateGrid/date-grid.d.ts +1 -1
  82. package/dist/components/DateGrid/date-grid.d.ts.map +1 -1
  83. package/dist/components/DateGrid/date-grid.js +358 -225
  84. package/dist/components/DateGrid/date-grid.js.map +1 -1
  85. package/dist/components/DatePicker/date-picker.d.ts.map +1 -1
  86. package/dist/components/DatePicker/date-picker.js +16 -1
  87. package/dist/components/DatePicker/date-picker.js.map +1 -1
  88. package/dist/components/Dialog/dialog.d.ts.map +1 -1
  89. package/dist/components/Dialog/dialog.js +55 -43
  90. package/dist/components/Dialog/dialog.js.map +1 -1
  91. package/dist/components/DropdownMenu/dropdown-menu-keyboard.d.ts +86 -0
  92. package/dist/components/DropdownMenu/dropdown-menu-keyboard.d.ts.map +1 -0
  93. package/dist/components/DropdownMenu/dropdown-menu-keyboard.js +172 -0
  94. package/dist/components/DropdownMenu/dropdown-menu-keyboard.js.map +1 -0
  95. package/dist/components/DropdownMenu/dropdown-menu.d.ts +6 -1
  96. package/dist/components/DropdownMenu/dropdown-menu.d.ts.map +1 -1
  97. package/dist/components/DropdownMenu/dropdown-menu.js +50 -39
  98. package/dist/components/DropdownMenu/dropdown-menu.js.map +1 -1
  99. package/dist/components/Field/field-context.d.ts +4 -0
  100. package/dist/components/Field/field-context.d.ts.map +1 -1
  101. package/dist/components/Field/field-context.js +1 -2
  102. package/dist/components/Field/field-context.js.map +1 -1
  103. package/dist/components/Field/field-edit-keys.d.ts +8 -0
  104. package/dist/components/Field/field-edit-keys.d.ts.map +1 -1
  105. package/dist/components/Field/field-edit-keys.js +3 -0
  106. package/dist/components/Field/field-edit-keys.js.map +1 -1
  107. package/dist/components/Field/field-wrapper.d.ts +18 -1
  108. package/dist/components/Field/field-wrapper.d.ts.map +1 -1
  109. package/dist/components/Field/field-wrapper.js +39 -10
  110. package/dist/components/Field/field-wrapper.js.map +1 -1
  111. package/dist/components/Field/field.d.ts +3 -2
  112. package/dist/components/Field/field.d.ts.map +1 -1
  113. package/dist/components/Field/field.js +2 -1
  114. package/dist/components/Field/field.js.map +1 -1
  115. package/dist/components/FileItem/file-item.d.ts +26 -2
  116. package/dist/components/FileItem/file-item.d.ts.map +1 -1
  117. package/dist/components/FileItem/file-item.js +82 -25
  118. package/dist/components/FileItem/file-item.js.map +1 -1
  119. package/dist/components/FileItem/index.js +4 -2
  120. package/dist/components/FileUpload/file-upload.d.ts +1 -1
  121. package/dist/components/FileUpload/file-upload.d.ts.map +1 -1
  122. package/dist/components/FileUpload/file-upload.js +132 -55
  123. package/dist/components/FileUpload/file-upload.js.map +1 -1
  124. package/dist/components/FileViewer/file-viewer.d.ts +1 -1
  125. package/dist/components/FileViewer/file-viewer.d.ts.map +1 -1
  126. package/dist/components/FileViewer/file-viewer.js +9 -4
  127. package/dist/components/FileViewer/file-viewer.js.map +1 -1
  128. package/dist/components/InlineEdit/inline-edit.d.ts.map +1 -1
  129. package/dist/components/InlineEdit/inline-edit.js +1 -1
  130. package/dist/components/InlineEdit/inline-edit.js.map +1 -1
  131. package/dist/components/Input/input.d.ts.map +1 -1
  132. package/dist/components/Input/input.js +6 -1
  133. package/dist/components/Input/input.js.map +1 -1
  134. package/dist/components/LinkInput/link-input.d.ts.map +1 -1
  135. package/dist/components/LinkInput/link-input.js +20 -4
  136. package/dist/components/LinkInput/link-input.js.map +1 -1
  137. package/dist/components/Notice/notice.js +1 -1
  138. package/dist/components/Notice/notice.js.map +1 -1
  139. package/dist/components/NumberInput/number-input.d.ts.map +1 -1
  140. package/dist/components/NumberInput/number-input.js +6 -1
  141. package/dist/components/NumberInput/number-input.js.map +1 -1
  142. package/dist/components/OverflowIndicator/index.js +2 -1
  143. package/dist/components/OverflowIndicator/overflow-indicator.d.ts +19 -1
  144. package/dist/components/OverflowIndicator/overflow-indicator.d.ts.map +1 -1
  145. package/dist/components/OverflowIndicator/overflow-indicator.js +47 -27
  146. package/dist/components/OverflowIndicator/overflow-indicator.js.map +1 -1
  147. package/dist/components/Pagination/pagination.d.ts +1 -1
  148. package/dist/components/Pagination/pagination.js +1 -1
  149. package/dist/components/Pagination/pagination.js.map +1 -1
  150. package/dist/components/PeoplePicker/avatar-stack-overflow.d.ts +7 -6
  151. package/dist/components/PeoplePicker/avatar-stack-overflow.d.ts.map +1 -1
  152. package/dist/components/PeoplePicker/avatar-stack-overflow.js +6 -7
  153. package/dist/components/PeoplePicker/avatar-stack-overflow.js.map +1 -1
  154. package/dist/components/PeoplePicker/people-picker-helpers.d.ts.map +1 -1
  155. package/dist/components/PeoplePicker/people-picker-helpers.js +1 -1
  156. package/dist/components/PeoplePicker/people-picker-helpers.js.map +1 -1
  157. package/dist/components/PeoplePicker/people-picker.d.ts.map +1 -1
  158. package/dist/components/PeoplePicker/people-picker.js +2 -2
  159. package/dist/components/PeoplePicker/people-picker.js.map +1 -1
  160. package/dist/components/PeoplePicker/person-display.d.ts.map +1 -1
  161. package/dist/components/PeoplePicker/person-display.js +26 -28
  162. package/dist/components/PeoplePicker/person-display.js.map +1 -1
  163. package/dist/components/ProfileCard/profile-card.d.ts.map +1 -1
  164. package/dist/components/ProfileCard/profile-card.js.map +1 -1
  165. package/dist/components/RadioGroup/radio-group.d.ts +1 -1
  166. package/dist/components/RadioGroup/radio-group.d.ts.map +1 -1
  167. package/dist/components/RadioGroup/radio-group.js +10 -4
  168. package/dist/components/RadioGroup/radio-group.js.map +1 -1
  169. package/dist/components/Rating/rating.d.ts +28 -6
  170. package/dist/components/Rating/rating.d.ts.map +1 -1
  171. package/dist/components/Rating/rating.js +97 -80
  172. package/dist/components/Rating/rating.js.map +1 -1
  173. package/dist/components/ScrollArea/scroll-area.d.ts +1 -1
  174. package/dist/components/ScrollArea/scroll-area.js +1 -1
  175. package/dist/components/ScrollArea/scroll-area.js.map +1 -1
  176. package/dist/components/SegmentedControl/segmented-control.d.ts +1 -1
  177. package/dist/components/SegmentedControl/segmented-control.d.ts.map +1 -1
  178. package/dist/components/SegmentedControl/segmented-control.js +3 -2
  179. package/dist/components/SegmentedControl/segmented-control.js.map +1 -1
  180. package/dist/components/Select/select.d.ts.map +1 -1
  181. package/dist/components/Select/select.js +15 -8
  182. package/dist/components/Select/select.js.map +1 -1
  183. package/dist/components/SelectMenu/index.js +2 -1
  184. package/dist/components/SelectMenu/index.js.map +1 -1
  185. package/dist/components/SelectMenu/select-menu-keyboard.d.ts +59 -0
  186. package/dist/components/SelectMenu/select-menu-keyboard.d.ts.map +1 -0
  187. package/dist/components/SelectMenu/select-menu-keyboard.js +107 -0
  188. package/dist/components/SelectMenu/select-menu-keyboard.js.map +1 -0
  189. package/dist/components/SelectMenu/select-menu.d.ts +1 -22
  190. package/dist/components/SelectMenu/select-menu.d.ts.map +1 -1
  191. package/dist/components/SelectMenu/select-menu.js +18 -34
  192. package/dist/components/SelectMenu/select-menu.js.map +1 -1
  193. package/dist/components/SelectionControl/selection-item.d.ts.map +1 -1
  194. package/dist/components/SelectionControl/selection-item.js +1 -0
  195. package/dist/components/SelectionControl/selection-item.js.map +1 -1
  196. package/dist/components/Sidebar/sidebar.d.ts +7 -9
  197. package/dist/components/Sidebar/sidebar.d.ts.map +1 -1
  198. package/dist/components/Sidebar/sidebar.js +171 -50
  199. package/dist/components/Sidebar/sidebar.js.map +1 -1
  200. package/dist/components/Slider/slider.d.ts +1 -1
  201. package/dist/components/Slider/slider.d.ts.map +1 -1
  202. package/dist/components/Slider/slider.js +9 -4
  203. package/dist/components/Slider/slider.js.map +1 -1
  204. package/dist/components/Steps/steps.d.ts.map +1 -1
  205. package/dist/components/Steps/steps.js +54 -26
  206. package/dist/components/Steps/steps.js.map +1 -1
  207. package/dist/components/Switch/switch.d.ts +1 -1
  208. package/dist/components/Switch/switch.d.ts.map +1 -1
  209. package/dist/components/Switch/switch.js +7 -2
  210. package/dist/components/Switch/switch.js.map +1 -1
  211. package/dist/components/Tabs/tabs.d.ts +1 -1
  212. package/dist/components/Tabs/tabs.d.ts.map +1 -1
  213. package/dist/components/Tabs/tabs.js +39 -4
  214. package/dist/components/Tabs/tabs.js.map +1 -1
  215. package/dist/components/Tag/tag.d.ts +1 -1
  216. package/dist/components/Tag/tag.d.ts.map +1 -1
  217. package/dist/components/Tag/tag.js +3 -2
  218. package/dist/components/Tag/tag.js.map +1 -1
  219. package/dist/components/Textarea/textarea.d.ts.map +1 -1
  220. package/dist/components/Textarea/textarea.js +8 -2
  221. package/dist/components/Textarea/textarea.js.map +1 -1
  222. package/dist/components/TimePicker/time-columns.d.ts.map +1 -1
  223. package/dist/components/TimePicker/time-columns.js +8 -4
  224. package/dist/components/TimePicker/time-columns.js.map +1 -1
  225. package/dist/components/TimePicker/time-picker.d.ts +1 -1
  226. package/dist/components/TimePicker/time-picker.js +1 -1
  227. package/dist/components/TimePicker/time-picker.js.map +1 -1
  228. package/dist/components/TreeView/tree-view.d.ts +9 -5
  229. package/dist/components/TreeView/tree-view.d.ts.map +1 -1
  230. package/dist/components/TreeView/tree-view.js +266 -214
  231. package/dist/components/TreeView/tree-view.js.map +1 -1
  232. package/dist/hooks/use-input-modality.d.ts +12 -17
  233. package/dist/hooks/use-input-modality.d.ts.map +1 -1
  234. package/dist/hooks/use-input-modality.js +4 -34
  235. package/dist/hooks/use-input-modality.js.map +1 -1
  236. package/dist/hooks/use-overflow-items.d.ts.map +1 -1
  237. package/dist/hooks/use-overflow-items.js +2 -0
  238. package/dist/hooks/use-overflow-items.js.map +1 -1
  239. package/dist/hooks/use-truncated.d.ts.map +1 -1
  240. package/dist/hooks/use-truncated.js.map +1 -1
  241. package/dist/index.d.ts +3 -0
  242. package/dist/index.d.ts.map +1 -1
  243. package/dist/index.js +23 -3
  244. package/dist/index.js.map +1 -1
  245. package/dist/lib/drag-announcements.d.ts +32 -0
  246. package/dist/lib/drag-announcements.d.ts.map +1 -1
  247. package/dist/lib/drag-announcements.js +39 -1
  248. package/dist/lib/drag-announcements.js.map +1 -1
  249. package/dist/lib/focus-after-trigger.d.ts +45 -0
  250. package/dist/lib/focus-after-trigger.d.ts.map +1 -0
  251. package/dist/lib/focus-after-trigger.js +66 -0
  252. package/dist/lib/focus-after-trigger.js.map +1 -0
  253. package/dist/lib/overlay-coexistence.js +1 -1
  254. package/dist/lib/roving-list-keyboard.d.ts +208 -0
  255. package/dist/lib/roving-list-keyboard.d.ts.map +1 -0
  256. package/dist/lib/roving-list-keyboard.js +174 -0
  257. package/dist/lib/roving-list-keyboard.js.map +1 -0
  258. package/dist/lib/utils.d.ts.map +1 -1
  259. package/dist/lib/utils.js +6 -1
  260. package/dist/lib/utils.js.map +1 -1
  261. package/dist/patterns/element-anatomy/item-anatomy.d.ts +4 -4
  262. package/dist/patterns/element-anatomy/item-anatomy.d.ts.map +1 -1
  263. package/dist/patterns/element-anatomy/item-anatomy.js +14 -4
  264. package/dist/patterns/element-anatomy/item-anatomy.js.map +1 -1
  265. package/dist/patterns/horizontal-overflow/horizontal-overflow.d.ts.map +1 -1
  266. package/dist/patterns/horizontal-overflow/horizontal-overflow.js +25 -0
  267. package/dist/patterns/horizontal-overflow/horizontal-overflow.js.map +1 -1
  268. package/dist/patterns/overlay-surface/overlay-surface.d.ts.map +1 -1
  269. package/dist/patterns/overlay-surface/overlay-surface.js.map +1 -1
  270. package/dist/tokens/categorical-color.d.ts +6 -1
  271. package/dist/tokens/categorical-color.d.ts.map +1 -1
  272. package/dist/tokens/categorical-color.js.map +1 -1
  273. package/dist/tokens/uiSize/icon-size.d.ts +1 -1
  274. package/dist/tokens/uiSize/stack-gap.d.ts +18 -0
  275. package/dist/tokens/uiSize/stack-gap.d.ts.map +1 -0
  276. package/dist/tokens/uiSize/stack-gap.js +5 -0
  277. package/dist/tokens/uiSize/stack-gap.js.map +1 -0
  278. package/ds-canonical/fork/codex/agents/skills/performance-audit/SKILL.md +1 -1
  279. package/ds-canonical/fork/codex/hooks.json +1 -1
  280. package/ds-canonical/fork/consumer/lock.json +17 -14
  281. package/ds-canonical/fork/consumer/managed-files/423729840acdd43c812f17c245b871716c6b60f76e4d3e94864d0e34a815bf8d.blob +9 -0
  282. package/ds-canonical/fork/consumer/managed-files/46f10bc55cdfdf834d0749eebff42ea0bb8576f361b36d2b6ba1851354c50150.blob +47 -9
  283. package/ds-canonical/fork/consumer/managed-files/5a0d134a2645114c8caac20458070b00a7f0fd55b3e8805b7325cb4cf52fa15e.blob +110 -0
  284. package/ds-canonical/fork/consumer/managed-files/5af0a46760af338d51fd0f7950675f8e97f7d2f09386b8818fcd7a58e6654e75.blob +790 -0
  285. package/ds-canonical/fork/consumer/managed-files/68bfcddff9c4a16388c248cd34fb66163e9e64b3d809a48ef3984f6ae4ff6d12.blob +211 -0
  286. package/ds-canonical/fork/consumer/managed-files/7eb6cc60abfdabaf6617c336058fad1454ccde08889295767bfd9b7fd153cc6e.blob +45 -6
  287. package/ds-canonical/fork/consumer/managed-files/92e4828cdcb96ff0d01daa16f6885f24045a8e72820665650f2a1890dc3a446c.blob +42 -2
  288. package/ds-canonical/fork/consumer/managed-files/ab072e7f2eca51f0e18a5e18c4b333260ee5d219a12cc6df5bb2d683f0b2734d.blob +27 -1
  289. package/ds-canonical/fork/consumer/managed-files/e71c65f45c6af7ce12b6b3c0d8028f185da5b719e2050c9b2a6b243d41ae4878.blob +62 -14
  290. package/ds-canonical/fork/governance.lock +41 -17
  291. package/ds-canonical/fork/hooks/check_consumer_app_invariants.sh +16 -4
  292. package/ds-canonical/fork/hooks/check_consumer_code_quality.sh +3 -1
  293. package/ds-canonical/fork/hooks/check_pattern_invariants.sh +13 -9
  294. package/ds-canonical/fork/launchers/settings-hooks.json +1 -1
  295. package/ds-canonical/fork/manifest.json +17 -11
  296. package/ds-canonical/fork/providers/claude/skills/performance-audit/SKILL.md +1 -1
  297. package/ds-canonical/fork/providers/codex/hook-config.json +1 -1
  298. package/ds-canonical/fork/providers/codex/skills/performance-audit/SKILL.md +1 -1
  299. package/ds-canonical/fork/skills/performance-audit/SKILL.md +1 -1
  300. package/ds-canonical/hooks/check_consumer_app_invariants.sh +16 -4
  301. package/ds-canonical/hooks/check_consumer_code_quality.sh +3 -1
  302. package/ds-canonical/hooks/check_datatable_invariants.sh +4 -1
  303. package/ds-canonical/hooks/check_pattern_invariants.sh +13 -9
  304. package/ds-canonical/hooks/check_plugin_fork_health.sh +8 -1
  305. package/ds-canonical/hooks/check_propose_discipline.sh +8 -1
  306. package/ds-canonical/hooks/check_storybook_addon_packaging.sh +8 -1
  307. package/ds-canonical/hooks/check_substantive_edit_approval_preflight.sh +64 -2
  308. package/ds-canonical/hooks/lib/_overlay_handcraft.sh +5 -2
  309. package/ds-canonical/hooks/registrations.json +12 -7
  310. package/ds-canonical/hooks/session_start_governance_check.sh +16 -0
  311. package/ds-canonical/hooks/tests/test_check_consumer_app_invariants.sh +25 -0
  312. package/ds-canonical/hooks/tests/test_check_consumer_code_quality.sh +4 -0
  313. package/ds-canonical/hooks/tests/test_check_consumer_ds_primitive_misuse.sh +14 -0
  314. package/ds-canonical/hooks/tests/test_check_datatable_invariants.sh +24 -0
  315. package/ds-canonical/hooks/tests/test_check_overlay_handcraft.sh +32 -0
  316. package/ds-canonical/hooks/tests/test_check_pattern_invariants.sh +59 -0
  317. package/ds-canonical/hooks/tests/test_check_plugin_fork_health.sh +32 -1
  318. package/ds-canonical/hooks/tests/test_check_propose_discipline.sh +21 -0
  319. package/ds-canonical/hooks/tests/test_check_storybook_addon_packaging.sh +21 -0
  320. package/ds-canonical/hooks/tests/test_check_substantive_edit_approval_preflight.sh +97 -0
  321. package/ds-canonical/hooks/tests/test_session_start_governance_check.sh +49 -0
  322. package/ds-canonical/references/README.md +12 -6
  323. package/ds-canonical/references/drag-canonical.md +3 -1
  324. package/ds-canonical/references/focus-canonical.md +26 -13
  325. package/ds-canonical/references/governance-audit-coverage.md +1 -1
  326. package/ds-canonical/references/hit-area-canonical.md +361 -0
  327. package/ds-canonical/references/item-anatomy-recipe.md +4 -2
  328. package/ds-canonical/references/keyboard-model-canonical.md +559 -0
  329. package/ds-canonical/rules/meta-patterns.md +4 -4
  330. package/ds-canonical/rules/self-verify.md +4 -4
  331. package/ds-canonical/skills/design-system-audit/references/audit-prompts.md +1 -1
  332. package/ds-canonical/skills/design-system-audit/references/historical-bugs.md +96 -0
  333. package/ds-canonical/skills/knowledge-prune/SKILL.md +4 -1
  334. package/ds-canonical/skills/performance-audit/SKILL.md +1 -1
  335. package/ds-story-manifest.json +19 -7
  336. package/llms-full.txt +2 -2
  337. package/llms.txt +2 -2
  338. package/package.json +3 -1
  339. package/src/components/Accordion/accordion.anatomy.stories.tsx +3 -3
  340. package/src/components/Accordion/accordion.spec.md +1 -1
  341. package/src/components/Accordion/accordion.tsx +8 -3
  342. package/src/components/AccountMenu/account-menu.spec.md +2 -3
  343. package/src/components/AccountMenu/account-menu.tsx +0 -1
  344. package/src/components/AgentPanel/agent-decision-card.spec.md +88 -0
  345. package/src/components/AgentPanel/agent-panel-fab.spec.md +151 -0
  346. package/src/components/AgentPanel/agent-panel-fab.tsx +67 -24
  347. package/src/components/AgentPanel/agent-panel-logo.spec.md +87 -0
  348. package/src/components/AgentPanel/agent-panel-logo.tsx +3 -3
  349. package/src/components/AgentPanel/agent-panel.anatomy.stories.tsx +1 -1
  350. package/src/components/AgentPanel/agent-panel.principles.stories.tsx +1 -1
  351. package/src/components/AgentPanel/agent-panel.spec.md +30 -275
  352. package/src/components/AgentPanel/agent-panel.stories.tsx +113 -8
  353. package/src/components/AgentPanel/agent-panel.tsx +59 -22
  354. package/src/components/Alert/alert.anatomy.stories.tsx +1 -1
  355. package/src/components/AppShell/app-shell.tsx +1 -1
  356. package/src/components/AspectRatio/aspect-ratio.anatomy.stories.tsx +10 -15
  357. package/src/components/Avatar/avatar.anatomy.stories.tsx +23 -26
  358. package/src/components/Avatar/avatar.principles.stories.tsx +10 -10
  359. package/src/components/Avatar/avatar.spec.md +45 -6
  360. package/src/components/Avatar/avatar.stories.tsx +8 -8
  361. package/src/components/Avatar/avatar.tsx +227 -21
  362. package/src/components/Badge/badge.anatomy.stories.tsx +16 -21
  363. package/src/components/Breadcrumb/breadcrumb.anatomy.stories.tsx +3 -3
  364. package/src/components/Breadcrumb/breadcrumb.spec.md +3 -3
  365. package/src/components/Breadcrumb/breadcrumb.tsx +5 -3
  366. package/src/components/BulkActionBar/bulk-action-bar.anatomy.stories.tsx +1 -1
  367. package/src/components/BulkActionBar/bulk-action-bar.principles.stories.tsx +1 -1
  368. package/src/components/BulkActionBar/bulk-action-bar.spec.md +1 -0
  369. package/src/components/BulkActionBar/bulk-action-bar.stories.tsx +57 -2
  370. package/src/components/BulkActionBar/bulk-action-bar.tsx +32 -2
  371. package/src/components/Button/button.anatomy.stories.tsx +174 -33
  372. package/src/components/Button/button.spec.md +23 -8
  373. package/src/components/Button/button.tsx +116 -21
  374. package/src/components/Calendar/calendar.anatomy.stories.tsx +34 -11
  375. package/src/components/Calendar/calendar.principles.stories.tsx +44 -1
  376. package/src/components/Calendar/calendar.spec.md +97 -20
  377. package/src/components/Calendar/calendar.stories.tsx +101 -4
  378. package/src/components/Calendar/calendar.tsx +379 -74
  379. package/src/components/Carousel/carousel.anatomy.stories.tsx +16 -17
  380. package/src/components/Carousel/carousel.spec.md +48 -2
  381. package/src/components/Carousel/carousel.tsx +9 -3
  382. package/src/components/Chart/chart.anatomy.stories.tsx +7 -5
  383. package/src/components/Chart/chart.spec.md +32 -7
  384. package/src/components/Chart/chart.tsx +9 -0
  385. package/src/components/Checkbox/checkbox.anatomy.stories.tsx +21 -28
  386. package/src/components/Checkbox/checkbox.spec.md +4 -2
  387. package/src/components/Checkbox/checkbox.tsx +10 -2
  388. package/src/components/Chip/chip.anatomy.stories.tsx +1 -1
  389. package/src/components/Chip/chip.spec.md +2 -1
  390. package/src/components/Chip/chip.tsx +4 -2
  391. package/src/components/Combobox/combobox.anatomy.stories.tsx +30 -37
  392. package/src/components/Combobox/combobox.spec.md +11 -3
  393. package/src/components/Combobox/combobox.tsx +43 -12
  394. package/src/components/Command/command.spec.md +11 -0
  395. package/src/components/Command/command.tsx +143 -10
  396. package/src/components/DataTable/cell-registry.tsx +5 -1
  397. package/src/components/DataTable/data-table-column-visibility-panel.tsx +5 -6
  398. package/src/components/DataTable/data-table-interaction-layer.tsx +27 -14
  399. package/src/components/DataTable/data-table-known-defects.spec.md +72 -0
  400. package/src/components/DataTable/data-table-scroll-performance.spec.md +295 -0
  401. package/src/components/DataTable/data-table-sort-manager.tsx +5 -6
  402. package/src/components/DataTable/data-table.anatomy.stories.tsx +1 -1
  403. package/src/components/DataTable/data-table.css +64 -0
  404. package/src/components/DataTable/data-table.spec.md +70 -346
  405. package/src/components/DataTable/data-table.stories.tsx +56 -9
  406. package/src/components/DataTable/data-table.tsx +392 -103
  407. package/src/components/DateGrid/date-grid.anatomy.stories.tsx +2 -2
  408. package/src/components/DateGrid/date-grid.spec.md +163 -1
  409. package/src/components/DateGrid/date-grid.tsx +270 -7
  410. package/src/components/DatePicker/date-picker.anatomy.stories.tsx +18 -27
  411. package/src/components/DatePicker/date-picker.spec.md +6 -2
  412. package/src/components/DatePicker/date-picker.tsx +20 -1
  413. package/src/components/Dialog/dialog.spec.md +6 -2
  414. package/src/components/Dialog/dialog.stories.tsx +18 -9
  415. package/src/components/Dialog/dialog.tsx +83 -48
  416. package/src/components/DropdownMenu/dropdown-menu-keyboard.ts +255 -0
  417. package/src/components/DropdownMenu/dropdown-menu.anatomy.stories.tsx +32 -33
  418. package/src/components/DropdownMenu/dropdown-menu.spec.md +18 -5
  419. package/src/components/DropdownMenu/dropdown-menu.stories.tsx +58 -1
  420. package/src/components/DropdownMenu/dropdown-menu.tsx +70 -33
  421. package/src/components/Empty/empty.spec.md +1 -2
  422. package/src/components/Field/field-context.ts +9 -4
  423. package/src/components/Field/field-controls.spec.md +5 -2
  424. package/src/components/Field/field-edit-keys.ts +12 -0
  425. package/src/components/Field/field-wrapper.tsx +76 -10
  426. package/src/components/Field/field.anatomy.stories.tsx +1 -1
  427. package/src/components/Field/field.spec.md +11 -6
  428. package/src/components/Field/field.stories.tsx +24 -1
  429. package/src/components/Field/field.tsx +10 -4
  430. package/src/components/FileItem/file-item.anatomy.stories.tsx +20 -10
  431. package/src/components/FileItem/file-item.principles.stories.tsx +14 -7
  432. package/src/components/FileItem/file-item.spec.md +50 -48
  433. package/src/components/FileItem/file-item.stories.tsx +41 -28
  434. package/src/components/FileItem/file-item.tsx +184 -50
  435. package/src/components/FileUpload/file-upload.anatomy.stories.tsx +11 -15
  436. package/src/components/FileUpload/file-upload.spec.md +27 -1
  437. package/src/components/FileUpload/file-upload.stories.tsx +33 -0
  438. package/src/components/FileUpload/file-upload.tsx +173 -43
  439. package/src/components/FileViewer/file-viewer.tsx +9 -4
  440. package/src/components/HoverCard/hover-card.stories.tsx +2 -2
  441. package/src/components/InlineEdit/inline-edit.spec.md +2 -0
  442. package/src/components/InlineEdit/inline-edit.tsx +8 -3
  443. package/src/components/Input/input.anatomy.stories.tsx +23 -31
  444. package/src/components/Input/input.spec.md +11 -0
  445. package/src/components/Input/input.tsx +6 -1
  446. package/src/components/LinkInput/link-input.anatomy.stories.tsx +18 -21
  447. package/src/components/LinkInput/link-input.principles.stories.tsx +4 -4
  448. package/src/components/LinkInput/link-input.spec.md +12 -1
  449. package/src/components/LinkInput/link-input.tsx +37 -4
  450. package/src/components/Menu/menu-item.anatomy.stories.tsx +50 -56
  451. package/src/components/Menu/menu-item.spec.md +1 -1
  452. package/src/components/Menu/menu-item.stories.tsx +48 -42
  453. package/src/components/Notice/notice.anatomy.stories.tsx +8 -16
  454. package/src/components/Notice/notice.spec.md +1 -1
  455. package/src/components/Notice/notice.tsx +1 -1
  456. package/src/components/NumberInput/number-input.anatomy.stories.tsx +21 -28
  457. package/src/components/NumberInput/number-input.spec.md +2 -0
  458. package/src/components/NumberInput/number-input.tsx +8 -1
  459. package/src/components/OverflowIndicator/overflow-indicator.anatomy.stories.tsx +28 -48
  460. package/src/components/OverflowIndicator/overflow-indicator.spec.md +13 -1
  461. package/src/components/OverflowIndicator/overflow-indicator.stories.tsx +12 -9
  462. package/src/components/OverflowIndicator/overflow-indicator.tsx +32 -12
  463. package/src/components/Pagination/pagination.spec.md +1 -0
  464. package/src/components/Pagination/pagination.tsx +1 -1
  465. package/src/components/PeoplePicker/avatar-stack-overflow.ts +14 -13
  466. package/src/components/PeoplePicker/people-picker-helpers.ts +5 -2
  467. package/src/components/PeoplePicker/people-picker.anatomy.stories.tsx +1 -1
  468. package/src/components/PeoplePicker/people-picker.spec.md +4 -2
  469. package/src/components/PeoplePicker/people-picker.tsx +1 -4
  470. package/src/components/PeoplePicker/person-display.tsx +45 -28
  471. package/src/components/Popover/popover.anatomy.stories.tsx +1 -1
  472. package/src/components/Popover/popover.principles.stories.tsx +3 -12
  473. package/src/components/Popover/popover.spec.md +7 -3
  474. package/src/components/ProfileCard/profile-card.anatomy.stories.tsx +1 -1
  475. package/src/components/ProfileCard/profile-card.spec.md +1 -1
  476. package/src/components/ProfileCard/profile-card.tsx +0 -3
  477. package/src/components/ProgressBar/progress-bar.anatomy.stories.tsx +11 -21
  478. package/src/components/ProgressBar/progress-bar.spec.md +1 -1
  479. package/src/components/RadioGroup/radio-group.anatomy.stories.tsx +17 -24
  480. package/src/components/RadioGroup/radio-group.spec.md +1 -1
  481. package/src/components/RadioGroup/radio-group.tsx +22 -4
  482. package/src/components/Rating/rating.anatomy.stories.tsx +131 -124
  483. package/src/components/Rating/rating.principles.stories.tsx +69 -43
  484. package/src/components/Rating/rating.spec.md +142 -63
  485. package/src/components/Rating/rating.stories.tsx +11 -15
  486. package/src/components/Rating/rating.tsx +131 -117
  487. package/src/components/ScrollArea/scroll-area.anatomy.stories.tsx +1 -1
  488. package/src/components/ScrollArea/scroll-area.principles.stories.tsx +6 -3
  489. package/src/components/ScrollArea/scroll-area.spec.md +3 -1
  490. package/src/components/ScrollArea/scroll-area.stories.tsx +5 -2
  491. package/src/components/ScrollArea/scroll-area.tsx +1 -1
  492. package/src/components/SegmentedControl/segmented-control.anatomy.stories.tsx +1 -1
  493. package/src/components/SegmentedControl/segmented-control.spec.md +2 -2
  494. package/src/components/SegmentedControl/segmented-control.tsx +3 -2
  495. package/src/components/Select/select.anatomy.stories.tsx +26 -35
  496. package/src/components/Select/select.spec.md +4 -3
  497. package/src/components/Select/select.tsx +11 -4
  498. package/src/components/SelectMenu/select-menu-keyboard.ts +218 -0
  499. package/src/components/SelectMenu/select-menu.anatomy.stories.tsx +6 -17
  500. package/src/components/SelectMenu/select-menu.spec.md +12 -4
  501. package/src/components/SelectMenu/select-menu.tsx +21 -56
  502. package/src/components/SelectionControl/selection-item.anatomy.stories.tsx +9 -33
  503. package/src/components/SelectionControl/selection-item.spec.md +2 -2
  504. package/src/components/SelectionControl/selection-item.tsx +3 -1
  505. package/src/components/Separator/separator.stories.tsx +4 -4
  506. package/src/components/Sheet/sheet.principles.stories.tsx +31 -13
  507. package/src/components/Sidebar/sidebar.anatomy.stories.tsx +1 -1
  508. package/src/components/Sidebar/sidebar.spec.md +73 -6
  509. package/src/components/Sidebar/sidebar.stories.tsx +82 -4
  510. package/src/components/Sidebar/sidebar.tsx +281 -64
  511. package/src/components/Skeleton/skeleton.spec.md +1 -1
  512. package/src/components/Slider/slider.anatomy.stories.tsx +1 -1
  513. package/src/components/Slider/slider.spec.md +6 -5
  514. package/src/components/Slider/slider.tsx +13 -8
  515. package/src/components/Steps/steps-state-visuals.spec.md +157 -0
  516. package/src/components/Steps/steps.anatomy.stories.tsx +1 -0
  517. package/src/components/Steps/steps.spec.md +59 -142
  518. package/src/components/Steps/steps.stories.tsx +1 -1
  519. package/src/components/Steps/steps.tsx +125 -33
  520. package/src/components/Switch/switch.spec.md +3 -3
  521. package/src/components/Switch/switch.tsx +6 -1
  522. package/src/components/Tabs/tabs.spec.md +5 -1
  523. package/src/components/Tabs/tabs.tsx +42 -3
  524. package/src/components/Tag/tag.anatomy.stories.tsx +24 -28
  525. package/src/components/Tag/tag.spec.md +10 -11
  526. package/src/components/Tag/tag.tsx +3 -3
  527. package/src/components/Textarea/textarea.tsx +7 -1
  528. package/src/components/TimePicker/time-columns.tsx +9 -4
  529. package/src/components/TimePicker/time-picker.spec.md +4 -1
  530. package/src/components/TimePicker/time-picker.tsx +1 -1
  531. package/src/components/Tooltip/tooltip.anatomy.stories.tsx +6 -15
  532. package/src/components/Tooltip/tooltip.principles.stories.tsx +4 -16
  533. package/src/components/Tooltip/tooltip.spec.md +2 -1
  534. package/src/components/TreeView/tree-view.anatomy.stories.tsx +35 -13
  535. package/src/components/TreeView/tree-view.spec.md +67 -38
  536. package/src/components/TreeView/tree-view.stories.tsx +221 -12
  537. package/src/components/TreeView/tree-view.tsx +463 -356
  538. package/src/hooks/use-input-modality.ts +17 -46
  539. package/src/hooks/use-overflow-items.ts +4 -0
  540. package/src/hooks/use-truncated.ts +3 -2
  541. package/src/index.ts +17 -0
  542. package/src/lib/README.md +2 -0
  543. package/src/lib/drag-announcements.ts +78 -0
  544. package/src/lib/focus-after-trigger.ts +102 -0
  545. package/src/lib/roving-list-keyboard.ts +400 -0
  546. package/src/lib/utils.ts +5 -0
  547. package/src/patterns/action-bar/action-bar.spec.md +2 -0
  548. package/src/patterns/element-anatomy/inline-action.spec.md +16 -12
  549. package/src/patterns/element-anatomy/item-anatomy.spec.md +98 -6
  550. package/src/patterns/element-anatomy/item-anatomy.stories.tsx +75 -70
  551. package/src/patterns/element-anatomy/item-anatomy.tsx +17 -6
  552. package/src/patterns/element-anatomy/truncated-text.spec.md +1 -0
  553. package/src/patterns/header-canonical/header-canonical.spec.md +3 -2
  554. package/src/patterns/horizontal-overflow/horizontal-overflow.spec.md +2 -0
  555. package/src/patterns/horizontal-overflow/horizontal-overflow.stories.tsx +36 -0
  556. package/src/patterns/horizontal-overflow/horizontal-overflow.tsx +28 -0
  557. package/src/patterns/overlay-surface/overlay-chrome-sizing.spec.md +180 -0
  558. package/src/patterns/overlay-surface/overlay-surface.spec.md +8 -162
  559. package/src/patterns/overlay-surface/overlay-surface.tsx +5 -3
  560. package/src/patterns/resize-handle/resize-handle.spec.md +3 -3
  561. package/src/styles/base.css +1 -1
  562. package/src/tokens/categorical-color.ts +6 -1
  563. package/src/tokens/color/color.spec.md +91 -24
  564. package/src/tokens/color/color.stories.tsx +50 -2
  565. package/src/tokens/color/primitives.css +41 -38
  566. package/src/tokens/color/semantic.css +105 -36
  567. package/src/tokens/layoutSpace/layoutSpace.spec.md +2 -0
  568. package/src/tokens/motion/motion.spec.md +31 -8
  569. package/src/tokens/orphan-tokens.spec.md +1 -1
  570. package/src/tokens/uiSize/icon-size.ts +1 -1
  571. package/src/tokens/uiSize/stack-gap.ts +17 -0
  572. package/src/tokens/uiSize/uiSize.css +12 -0
  573. package/src/tokens/uiSize/uiSize.spec.md +17 -16
  574. package/src/tokens/uiSize/uiSize.stories.tsx +1 -1
  575. package/dist/node_modules/aria-hidden/dist/es2015/index.js +0 -147
  576. package/dist/node_modules/aria-hidden/dist/es2015/index.js.map +0 -1
@@ -0,0 +1,45 @@
1
+ /**
2
+ * @internal — 「彈出框開著按 Tab:收起,焦點從觸發點往下 / 往上走一站」的**唯一**落點計算(2026-09-26 由兩份合一)。
3
+ *
4
+ * ── 規則住所 ─────────────────────────────────────────────────────────────────
5
+ * `ds-canonical/references/keyboard-model-canonical.md`「彈出框開著時的 Tab 與 Esc」:
6
+ * 「下一站一律從觸發點算」—— 彈出內容掛在 <body> 最末端(Portal),從選單項 / 選項往下走會掉到頁尾;
7
+ * 「觸發點在對話框 / 面板裡時,照那個框的規則在框內繞圈;頁面上已經沒有下一站時,焦點留在觸發點」。
8
+ * 消費者:`components/DropdownMenu/dropdown-menu-keyboard.ts`(選單,待辦總帳 B11)、
9
+ * `components/SelectMenu/select-menu-keyboard.ts`(單選下拉 Tab 選定後離開、多選面板內繞圈,B11)。
10
+ *
11
+ * ── 決定來源 ─────────────────────────────────────────────────────────────────
12
+ * B11(`governance/planning/2026-09-25-interaction-and-hover-remediation.md`),user 逐字(附條件同意):
13
+ * 「你確定這個符合我們一致的設計語言且不違背世界級的設計就這樣做」;合併為一份 = 同檔〇節「09-26 同意清單回覆」
14
+ * 「按鍵規則合併」,user 逐字:「確保符合我們一致的設計語言且不違背世界級的設計且都有確保整個ds 是SSOT,避免漂移就照你建議做」。
15
+ * 批次細節 X20(同意清單):「選單在對話框裡 Tab 繞圈;無下一站時留觸發鈕」。
16
+ *
17
+ * 2026-09-26 之前兩份平行實作各差一點:選單版認正 tabindex 排序、單選鈕群組、觸發點本身不在 Tab 路上的情形,
18
+ * 到頁面邊緣時焦點留在觸發鈕;下拉版三者都不認,到頁面邊緣時交還瀏覽器 —— 那一下會落在 Radix 插在 <body> 首尾、
19
+ * 關閉動畫期間仍掛著的隱形焦點護欄上(@radix-ui/react-popover 的 PopoverContentImpl 也呼叫 useFocusGuards),
20
+ * 焦點消失一站。合一後兩者都照正本那一句:頁面上已經沒有下一站時,焦點留在觸發點。
21
+ */
22
+ /** 呼叫端要排除的元素(例:正在關的那個彈出框本身 —— 關閉動畫期間還在 DOM 裡) */
23
+ export type TabStopExclude = (el: HTMLElement) => boolean;
24
+ /**
25
+ * 這個元素此刻在不在頁面的 Tab 路上(照瀏覽器規則;判準對齊 Radix FocusScope `getTabbableCandidates` + `isHidden`)。
26
+ * Radix 焦點護欄(body 首尾 tabindex=0 的隱形 span)不是頁面上的一站。
27
+ */
28
+ export declare function isTabbableElement(el: HTMLElement): boolean;
29
+ /** 範圍內的 Tab 順序:正 tabindex 先(由小到大),其餘照文件順序 */
30
+ export declare function tabbableOrder(scope: HTMLElement, exclude?: TabStopExclude): HTMLElement[];
31
+ /**
32
+ * 從觸發點往下(或往上)的那一站 —— 等於「彈出框關著時,在觸發點上按同一個鍵會到的那一格」。
33
+ * 觸發點在對話框 / 面板裡 → 只在那個框裡找,到頭繞回(W3C:"Like non-modal dialogs, modal dialogs contain their tab sequence."
34
+ * https://github.com/w3c/aria-practices/blob/3f094fde1c81b25dfa69162563bf28d093f854d4/content/patterns/dialog-modal/dialog-modal-pattern.html#L28-L31
35
+ * ;Radix Dialog / Popover 的 FocusScope loop);頁面層沒有下一站 → null。
36
+ */
37
+ export declare function tabStopFromTrigger(trigger: HTMLElement, backward: boolean, exclude?: TabStopExclude): HTMLElement | null;
38
+ /**
39
+ * 呼叫端**先把彈出框同步關掉**(flushSync:modal 的 FocusScope 焦點鎖要先解除,可打字的輸入框要先換回顯示值),
40
+ * 再呼叫這一支:焦點落到觸發點的下一站 / 上一站;頁面上已經沒有下一站 → 留在觸發點(X20)。
41
+ * 呼叫端負責 preventDefault(瀏覽器預設的 Tab 會從彈出框的位置 —— 頁尾 —— 往下走)。
42
+ * 回傳最後拿到焦點的元素。
43
+ */
44
+ export declare function focusFromTrigger(trigger: HTMLElement, backward: boolean, exclude?: TabStopExclude): HTMLElement;
45
+ //# sourceMappingURL=focus-after-trigger.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"focus-after-trigger.d.ts","sourceRoot":"","sources":["../../src/lib/focus-after-trigger.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAQH,iDAAiD;AACjD,MAAM,MAAM,cAAc,GAAG,CAAC,EAAE,EAAE,WAAW,KAAK,OAAO,CAAA;AAEzD;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,WAAW,GAAG,OAAO,CAa1D;AAED,6CAA6C;AAC7C,wBAAgB,aAAa,CAAC,KAAK,EAAE,WAAW,EAAE,OAAO,CAAC,EAAE,cAAc,GAAG,WAAW,EAAE,CAMzF;AAOD;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,cAAc,GAAG,WAAW,GAAG,IAAI,CAiBxH;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,cAAc,GAAG,WAAW,CAM/G"}
@@ -0,0 +1,66 @@
1
+ const TABBABLE_CANDIDATES = [
2
+ "a[href]",
3
+ "area[href]",
4
+ "button",
5
+ "input",
6
+ "select",
7
+ "textarea",
8
+ "iframe",
9
+ "summary",
10
+ "audio[controls]",
11
+ "video[controls]",
12
+ '[contenteditable]:not([contenteditable="false"])',
13
+ "[tabindex]"
14
+ ].join(",");
15
+ function isTabbableElement(el) {
16
+ if (el.tabIndex < 0) return false;
17
+ if (el.matches(":disabled")) return false;
18
+ if (el instanceof HTMLInputElement && el.type === "hidden") return false;
19
+ if (el.closest("[inert],[data-radix-focus-guard]")) return false;
20
+ if (el.getClientRects().length === 0) return false;
21
+ if (getComputedStyle(el).visibility === "hidden") return false;
22
+ if (el instanceof HTMLInputElement && el.type === "radio" && el.name && !el.checked) {
23
+ const group = Array.from(el.ownerDocument.getElementsByName(el.name));
24
+ if (group.some((n) => n instanceof HTMLInputElement && n.type === "radio" && n.form === el.form && n.checked)) return false;
25
+ }
26
+ return true;
27
+ }
28
+ function tabbableOrder(scope, exclude) {
29
+ const candidates = Array.from(scope.querySelectorAll(TABBABLE_CANDIDATES)).filter(
30
+ (el) => isTabbableElement(el) && !(exclude == null ? void 0 : exclude(el))
31
+ );
32
+ const positive = candidates.filter((el) => el.tabIndex > 0).sort((a, b) => a.tabIndex - b.tabIndex);
33
+ return [...positive, ...candidates.filter((el) => el.tabIndex === 0)];
34
+ }
35
+ function dialogScopeOf(trigger) {
36
+ var _a;
37
+ return ((_a = trigger.parentElement) == null ? void 0 : _a.closest('[role="dialog"],[role="alertdialog"]')) ?? null;
38
+ }
39
+ function tabStopFromTrigger(trigger, backward, exclude) {
40
+ const scope = dialogScopeOf(trigger);
41
+ const order = tabbableOrder(scope ?? trigger.ownerDocument.body, exclude);
42
+ const at = order.indexOf(trigger);
43
+ let target;
44
+ if (at !== -1) {
45
+ target = order[backward ? at - 1 : at + 1];
46
+ } else {
47
+ const flow = order.filter((el) => el.tabIndex === 0);
48
+ target = backward ? [...flow].reverse().find((el) => trigger.compareDocumentPosition(el) & Node.DOCUMENT_POSITION_PRECEDING) : flow.find((el) => trigger.compareDocumentPosition(el) & Node.DOCUMENT_POSITION_FOLLOWING);
49
+ }
50
+ if (target) return target;
51
+ if (scope && order.length > 0) return backward ? order[order.length - 1] : order[0];
52
+ return null;
53
+ }
54
+ function focusFromTrigger(trigger, backward, exclude) {
55
+ const next = tabStopFromTrigger(trigger, backward, exclude) ?? trigger;
56
+ next.focus();
57
+ if (next.ownerDocument.activeElement !== next) window.setTimeout(() => next.focus(), 0);
58
+ return next;
59
+ }
60
+ export {
61
+ focusFromTrigger,
62
+ isTabbableElement,
63
+ tabStopFromTrigger,
64
+ tabbableOrder
65
+ };
66
+ //# sourceMappingURL=focus-after-trigger.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"focus-after-trigger.js","sources":["../../src/lib/focus-after-trigger.ts"],"sourcesContent":["/**\n * @internal — 「彈出框開著按 Tab:收起,焦點從觸發點往下 / 往上走一站」的**唯一**落點計算(2026-09-26 由兩份合一)。\n *\n * ── 規則住所 ─────────────────────────────────────────────────────────────────\n * `ds-canonical/references/keyboard-model-canonical.md`「彈出框開著時的 Tab 與 Esc」:\n * 「下一站一律從觸發點算」—— 彈出內容掛在 <body> 最末端(Portal),從選單項 / 選項往下走會掉到頁尾;\n * 「觸發點在對話框 / 面板裡時,照那個框的規則在框內繞圈;頁面上已經沒有下一站時,焦點留在觸發點」。\n * 消費者:`components/DropdownMenu/dropdown-menu-keyboard.ts`(選單,待辦總帳 B11)、\n * `components/SelectMenu/select-menu-keyboard.ts`(單選下拉 Tab 選定後離開、多選面板內繞圈,B11)。\n *\n * ── 決定來源 ─────────────────────────────────────────────────────────────────\n * B11(`governance/planning/2026-09-25-interaction-and-hover-remediation.md`),user 逐字(附條件同意):\n * 「你確定這個符合我們一致的設計語言且不違背世界級的設計就這樣做」;合併為一份 = 同檔〇節「09-26 同意清單回覆」\n * 「按鍵規則合併」,user 逐字:「確保符合我們一致的設計語言且不違背世界級的設計且都有確保整個ds 是SSOT,避免漂移就照你建議做」。\n * 批次細節 X20(同意清單):「選單在對話框裡 Tab 繞圈;無下一站時留觸發鈕」。\n *\n * 2026-09-26 之前兩份平行實作各差一點:選單版認正 tabindex 排序、單選鈕群組、觸發點本身不在 Tab 路上的情形,\n * 到頁面邊緣時焦點留在觸發鈕;下拉版三者都不認,到頁面邊緣時交還瀏覽器 —— 那一下會落在 Radix 插在 <body> 首尾、\n * 關閉動畫期間仍掛著的隱形焦點護欄上(@radix-ui/react-popover 的 PopoverContentImpl 也呼叫 useFocusGuards),\n * 焦點消失一站。合一後兩者都照正本那一句:頁面上已經沒有下一站時,焦點留在觸發點。\n */\n\n/** 瀏覽器可能放進 Tab 順序的元素(候選;再經 `isTabbableElement` 過濾) */\nconst TABBABLE_CANDIDATES = [\n 'a[href]', 'area[href]', 'button', 'input', 'select', 'textarea', 'iframe', 'summary',\n 'audio[controls]', 'video[controls]', '[contenteditable]:not([contenteditable=\"false\"])', '[tabindex]',\n].join(',')\n\n/** 呼叫端要排除的元素(例:正在關的那個彈出框本身 —— 關閉動畫期間還在 DOM 裡) */\nexport type TabStopExclude = (el: HTMLElement) => boolean\n\n/**\n * 這個元素此刻在不在頁面的 Tab 路上(照瀏覽器規則;判準對齊 Radix FocusScope `getTabbableCandidates` + `isHidden`)。\n * Radix 焦點護欄(body 首尾 tabindex=0 的隱形 span)不是頁面上的一站。\n */\nexport function isTabbableElement(el: HTMLElement): boolean {\n if (el.tabIndex < 0) return false\n if (el.matches(':disabled')) return false\n if (el instanceof HTMLInputElement && el.type === 'hidden') return false\n if (el.closest('[inert],[data-radix-focus-guard]')) return false\n if (el.getClientRects().length === 0) return false\n if (getComputedStyle(el).visibility === 'hidden') return false\n // 同名單選鈕:有勾選的那顆才在 Tab 路上(全未勾時整組都算,往下落第一顆、往上落最後一顆 —— 瀏覽器行為)\n if (el instanceof HTMLInputElement && el.type === 'radio' && el.name && !el.checked) {\n const group = Array.from(el.ownerDocument.getElementsByName(el.name))\n if (group.some((n) => n instanceof HTMLInputElement && n.type === 'radio' && n.form === el.form && n.checked)) return false\n }\n return true\n}\n\n/** 範圍內的 Tab 順序:正 tabindex 先(由小到大),其餘照文件順序 */\nexport function tabbableOrder(scope: HTMLElement, exclude?: TabStopExclude): HTMLElement[] {\n const candidates = Array.from(scope.querySelectorAll<HTMLElement>(TABBABLE_CANDIDATES)).filter(\n (el) => isTabbableElement(el) && !exclude?.(el),\n )\n const positive = candidates.filter((el) => el.tabIndex > 0).sort((a, b) => a.tabIndex - b.tabIndex)\n return [...positive, ...candidates.filter((el) => el.tabIndex === 0)]\n}\n\n/** 觸發點外層的對話框 / 面板(`role=\"dialog\"` / `\"alertdialog\"`):Tab 本來就在那個框裡繞圈(W3C dialog pattern) */\nfunction dialogScopeOf(trigger: HTMLElement): HTMLElement | null {\n return trigger.parentElement?.closest<HTMLElement>('[role=\"dialog\"],[role=\"alertdialog\"]') ?? null\n}\n\n/**\n * 從觸發點往下(或往上)的那一站 —— 等於「彈出框關著時,在觸發點上按同一個鍵會到的那一格」。\n * 觸發點在對話框 / 面板裡 → 只在那個框裡找,到頭繞回(W3C:\"Like non-modal dialogs, modal dialogs contain their tab sequence.\"\n * https://github.com/w3c/aria-practices/blob/3f094fde1c81b25dfa69162563bf28d093f854d4/content/patterns/dialog-modal/dialog-modal-pattern.html#L28-L31\n * ;Radix Dialog / Popover 的 FocusScope loop);頁面層沒有下一站 → null。\n */\nexport function tabStopFromTrigger(trigger: HTMLElement, backward: boolean, exclude?: TabStopExclude): HTMLElement | null {\n const scope = dialogScopeOf(trigger)\n const order = tabbableOrder(scope ?? trigger.ownerDocument.body, exclude)\n const at = order.indexOf(trigger)\n let target: HTMLElement | undefined\n if (at !== -1) {\n target = order[backward ? at - 1 : at + 1]\n } else {\n // 觸發點本身不在 Tab 路上(例:tabindex=-1 的列上小按鈕)→ 跟瀏覽器一樣,以它在文件裡的位置為起點\n const flow = order.filter((el) => el.tabIndex === 0)\n target = backward\n ? [...flow].reverse().find((el) => trigger.compareDocumentPosition(el) & Node.DOCUMENT_POSITION_PRECEDING)\n : flow.find((el) => trigger.compareDocumentPosition(el) & Node.DOCUMENT_POSITION_FOLLOWING)\n }\n if (target) return target\n if (scope && order.length > 0) return backward ? order[order.length - 1] : order[0]\n return null\n}\n\n/**\n * 呼叫端**先把彈出框同步關掉**(flushSync:modal 的 FocusScope 焦點鎖要先解除,可打字的輸入框要先換回顯示值),\n * 再呼叫這一支:焦點落到觸發點的下一站 / 上一站;頁面上已經沒有下一站 → 留在觸發點(X20)。\n * 呼叫端負責 preventDefault(瀏覽器預設的 Tab 會從彈出框的位置 —— 頁尾 —— 往下走)。\n * 回傳最後拿到焦點的元素。\n */\nexport function focusFromTrigger(trigger: HTMLElement, backward: boolean, exclude?: TabStopExclude): HTMLElement {\n const next = tabStopFromTrigger(trigger, backward, exclude) ?? trigger\n next.focus()\n // 焦點被同一個 task 裡的關閉流程拉走時(modal 選單的焦點鎖卸載),下一個 macrotask 再給一次\n if (next.ownerDocument.activeElement !== next) window.setTimeout(() => next.focus(), 0)\n return next\n}\n"],"names":[],"mappings":"AAuBA,MAAM,sBAAsB;AAAA,EAC1B;AAAA,EAAW;AAAA,EAAc;AAAA,EAAU;AAAA,EAAS;AAAA,EAAU;AAAA,EAAY;AAAA,EAAU;AAAA,EAC5E;AAAA,EAAmB;AAAA,EAAmB;AAAA,EAAoD;AAC5F,EAAE,KAAK,GAAG;AASH,SAAS,kBAAkB,IAA0B;AAC1D,MAAI,GAAG,WAAW,EAAG,QAAO;AAC5B,MAAI,GAAG,QAAQ,WAAW,EAAG,QAAO;AACpC,MAAI,cAAc,oBAAoB,GAAG,SAAS,SAAU,QAAO;AACnE,MAAI,GAAG,QAAQ,kCAAkC,EAAG,QAAO;AAC3D,MAAI,GAAG,eAAA,EAAiB,WAAW,EAAG,QAAO;AAC7C,MAAI,iBAAiB,EAAE,EAAE,eAAe,SAAU,QAAO;AAEzD,MAAI,cAAc,oBAAoB,GAAG,SAAS,WAAW,GAAG,QAAQ,CAAC,GAAG,SAAS;AACnF,UAAM,QAAQ,MAAM,KAAK,GAAG,cAAc,kBAAkB,GAAG,IAAI,CAAC;AACpE,QAAI,MAAM,KAAK,CAAC,MAAM,aAAa,oBAAoB,EAAE,SAAS,WAAW,EAAE,SAAS,GAAG,QAAQ,EAAE,OAAO,EAAG,QAAO;AAAA,EACxH;AACA,SAAO;AACT;AAGO,SAAS,cAAc,OAAoB,SAAyC;AACzF,QAAM,aAAa,MAAM,KAAK,MAAM,iBAA8B,mBAAmB,CAAC,EAAE;AAAA,IACtF,CAAC,OAAO,kBAAkB,EAAE,KAAK,EAAC,mCAAU;AAAA,EAAE;AAEhD,QAAM,WAAW,WAAW,OAAO,CAAC,OAAO,GAAG,WAAW,CAAC,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,WAAW,EAAE,QAAQ;AAClG,SAAO,CAAC,GAAG,UAAU,GAAG,WAAW,OAAO,CAAC,OAAO,GAAG,aAAa,CAAC,CAAC;AACtE;AAGA,SAAS,cAAc,SAA0C;AArCjE;AAsCE,WAAO,aAAQ,kBAAR,mBAAuB,QAAqB,4CAA2C;AAChG;AAQO,SAAS,mBAAmB,SAAsB,UAAmB,SAA8C;AACxH,QAAM,QAAQ,cAAc,OAAO;AACnC,QAAM,QAAQ,cAAc,SAAS,QAAQ,cAAc,MAAM,OAAO;AACxE,QAAM,KAAK,MAAM,QAAQ,OAAO;AAChC,MAAI;AACJ,MAAI,OAAO,IAAI;AACb,aAAS,MAAM,WAAW,KAAK,IAAI,KAAK,CAAC;AAAA,EAC3C,OAAO;AAEL,UAAM,OAAO,MAAM,OAAO,CAAC,OAAO,GAAG,aAAa,CAAC;AACnD,aAAS,WACL,CAAC,GAAG,IAAI,EAAE,UAAU,KAAK,CAAC,OAAO,QAAQ,wBAAwB,EAAE,IAAI,KAAK,2BAA2B,IACvG,KAAK,KAAK,CAAC,OAAO,QAAQ,wBAAwB,EAAE,IAAI,KAAK,2BAA2B;AAAA,EAC9F;AACA,MAAI,OAAQ,QAAO;AACnB,MAAI,SAAS,MAAM,SAAS,EAAG,QAAO,WAAW,MAAM,MAAM,SAAS,CAAC,IAAI,MAAM,CAAC;AAClF,SAAO;AACT;AAQO,SAAS,iBAAiB,SAAsB,UAAmB,SAAuC;AAC/G,QAAM,OAAO,mBAAmB,SAAS,UAAU,OAAO,KAAK;AAC/D,OAAK,MAAA;AAEL,MAAI,KAAK,cAAc,kBAAkB,KAAM,QAAO,WAAW,MAAM,KAAK,MAAA,GAAS,CAAC;AACtF,SAAO;AACT;"}
@@ -1,5 +1,5 @@
1
1
  import * as React from "react";
2
- import { suppressOthers } from "../node_modules/aria-hidden/dist/es2015/index.js";
2
+ import { suppressOthers } from "aria-hidden";
3
3
  import { cn } from "./utils.js";
4
4
  function useOverlayCoexistence(active, keep) {
5
5
  React.useEffect(() => {
@@ -0,0 +1,208 @@
1
+ /**
2
+ * @internal — 「列上有小按鈕的一串」鍵盤路線的**唯一**判定與執行(2026-09-26 由四份合一)。
3
+ *
4
+ * ── 規則住所 ─────────────────────────────────────────────────────────────────
5
+ * 規則(Tab 換區、方向鍵在區裡走、Esc 退一步)= `ds-canonical/references/keyboard-model-canonical.md`
6
+ * 「列上有小按鈕的一串」;本檔是那張按鍵表**唯一的程式版**,四個宿主只讀自己的 DOM、執行結果:
7
+ * - `components/Sidebar/sidebar.tsx` SidebarMenu(真焦點,roving tabindex)
8
+ * - `components/FileUpload/file-upload.tsx` 內建檔案清單(真焦點,roving tabindex)
9
+ * - `components/TreeView/tree-view.tsx` 樹狀表格(真焦點 + 樹的展開 / 收合 / 回上一層 + 鍵盤重排)
10
+ * - `components/Command/command.tsx` cmdk 清單(「這一項」= 反白列,焦點停在搜尋框 / 清單 = home)
11
+ *
12
+ * ── 決定來源 ─────────────────────────────────────────────────────────────────
13
+ * 路線乙:待辦總帳 `governance/planning/2026-09-25-interaction-and-hover-remediation.md` B9,user 逐字(附條件同意,
14
+ * 條件查證成立記在同列):「確定建議符合我們一致的設計語言且不違背世界級的設計就照建議」。
15
+ * 合併與 X1–X7 統一:同檔〇節「09-26 同意清單回覆」,user 逐字:「確保符合我們一致的設計語言且不違背世界級的設計且都有
16
+ * 確保整個ds 是SSOT,避免漂移就照你建議做」—— 同意的清單含「按鍵規則合併」與批次細節 X1–X35,其中
17
+ * X4(按鈕上 Home / End)、X6(列上選單鈕按 ↓)、X7(樹在按鈕上 Shift+Tab)三處「統一成側欄做法」。
18
+ * 2026-09-26 之前這張表有四份平行實作(側欄 / 檔案清單 / 樹 `tree-keyboard-route.ts` / Command),三處互相不一致:
19
+ * X4 側欄與檔案清單的按鈕上 Home / End 換到頭尾項,樹不處理;
20
+ * X6 側欄的選單鈕按 ↓ 換下一項,樹讓給按鈕開選單;
21
+ * X7 側欄在按鈕上 Shift+Tab 一下離開,樹要兩下(先落回本列)—— 違反 B9「Tab 一下離開」。
22
+ *
23
+ * ── 本檔兩層 ─────────────────────────────────────────────────────────────────
24
+ * (一) 判定(純函式,不碰 DOM):`resolveRovingKey` / `pickRovingTarget` / `pickRovingTabStop`
25
+ * —— 判定表 `scripts/test-roving-list-keyboard.mjs`(含舊路線對照組)逐格驗。
26
+ * (二) 宿主共用的 DOM 小工具:列裡可走到的東西、把它們收出 Tab 路、真焦點宿主的執行器 `applyRovingAction`。
27
+ * Command 是虛擬游標(cmdk 自己搬反白),執行方式不同,在 command.tsx 自己對應 —— 判定仍只讀本檔。
28
+ */
29
+ /** 按鍵發生時,焦點在「這一項」本身,還是這一項裡的某一個東西(小按鈕 / 連結 / 名片頭像…) */
30
+ export type RovingFocus = 'item' | 'control';
31
+ /** 樹才有的三個狀態;平面清單不傳 = 沒有「先展開」「回上一層」這兩步 */
32
+ export interface RovingTreeState {
33
+ hasChildren: boolean;
34
+ expanded: boolean;
35
+ hasParent: boolean;
36
+ }
37
+ export interface RovingKeyInput {
38
+ /** KeyboardEvent.key */
39
+ key: string;
40
+ focus: RovingFocus;
41
+ /** 這一項可以用 → 走到的東西數(宿主以 `listRovingControls` 數出來) */
42
+ controlCount: number;
43
+ /** focus === 'control' 時焦點在第幾個(0 起算);focus === 'item' 時不讀 */
44
+ controlIndex: number;
45
+ /** 焦點所在的東西是文字輸入(輸入框 / 多行框 / 下拉 / 可編輯區):方向鍵、Home / End、Enter / 空白鍵都屬於它自己 */
46
+ controlIsTextEntry?: boolean;
47
+ /** 「這一項」本身是文字輸入(只有 Command 的搜尋框):← / Home / End / 打字屬於插入點 */
48
+ itemIsTextEntry?: boolean;
49
+ /** itemIsTextEntry 時:插入點已在字尾(且沒有選取範圍)。不在字尾時 → 要留給插入點移動 */
50
+ caretAtEnd?: boolean;
51
+ /** 樹的狀態;平面清單不傳 */
52
+ tree?: RovingTreeState;
53
+ /** 可以用 Cmd / Ctrl + Shift + 方向鍵重排(只有 draggable 的樹) */
54
+ reorderable?: boolean;
55
+ /**
56
+ * 這一串有「打字跳位」(只有樹:keyboard-model-canonical.md 五條判準第 2 條「需要 tree 那一整套鍵盤模型(打字前導、…)」)。
57
+ * 焦點在項目上打一個可見字元 → 回 `typeahead`,由宿主找下一個名字以該字開頭的項目;平面清單(側欄 / 檔案清單)不傳 = 不處理。
58
+ * 出處:W3C APG Tree View 逐字 "Type a character: focus moves to the next node with a name that starts with the typed character."
59
+ * (<https://www.w3.org/WAI/ARIA/apg/patterns/treeview/>)。
60
+ */
61
+ typeahead?: boolean;
62
+ /** 事件已被更外層的捕獲處理程式 preventDefault */
63
+ defaultPrevented: boolean;
64
+ metaKey: boolean;
65
+ ctrlKey: boolean;
66
+ altKey: boolean;
67
+ shiftKey: boolean;
68
+ }
69
+ export type RovingReorderKey = 'ArrowUp' | 'ArrowDown' | 'ArrowLeft' | 'ArrowRight';
70
+ export type RovingMove = 'item-prev' | 'item-next' | 'item-first' | 'item-last';
71
+ export type RovingKeyAction =
72
+ /** 不處理 —— 交給瀏覽器、插入點或那個東西自己 */
73
+ {
74
+ type: 'none';
75
+ }
76
+ /** 吃掉按鍵(preventDefault)但不動焦點 —— 例:已在最後一顆按鈕上再按 → */
77
+ | {
78
+ type: 'consume';
79
+ }
80
+ /** 換到上 / 下 / 第一 / 最後一項(焦點落在項目上;不繞回) */
81
+ | {
82
+ type: RovingMove;
83
+ } | {
84
+ type: 'expand';
85
+ } | {
86
+ type: 'collapse';
87
+ } | {
88
+ type: 'parent';
89
+ }
90
+ /** 把焦點放到這一項第 index 個東西 */
91
+ | {
92
+ type: 'control';
93
+ index: number;
94
+ }
95
+ /** 焦點回到這一項本身 */
96
+ | {
97
+ type: 'item';
98
+ }
99
+ /** Tab / Shift+Tab:焦點先回到這一項(整串唯一的 Tab 停靠點),**不擋預設** —— 瀏覽器接著往下 / 往上走一站 = 一下離開這一串 */
100
+ | {
101
+ type: 'leave';
102
+ }
103
+ /** Enter / 空白鍵在項目上:這一項自己的預設動作(樹 = 選取;原生按鈕 = 瀏覽器自己點) */
104
+ | {
105
+ type: 'activate-item';
106
+ }
107
+ /** Enter / 空白鍵在項目裡的按鈕 / 連結上:那個東西自己的動作,**不是**「選這一項」 */
108
+ | {
109
+ type: 'activate-control';
110
+ }
111
+ /** 鍵盤重排(樹;修飾鍵層,與導覽鍵正交) */
112
+ | {
113
+ type: 'reorder';
114
+ key: RovingReorderKey;
115
+ }
116
+ /** 打字跳位(只有 `typeahead: true` 的宿主):焦點在項目上打了一個可見字元,由宿主找下一個名字以它開頭的項目 */
117
+ | {
118
+ type: 'typeahead';
119
+ char: string;
120
+ };
121
+ /**
122
+ * 按鍵表(規格 SSOT = keyboard-model-canonical.md「列上有小按鈕的一串」;每一格的來歷寫在該格旁):
123
+ *
124
+ * | 鍵 | 焦點在項目上 | 焦點在這一項的東西上 |
125
+ * |---|---|---|
126
+ * | ↑ ↓ | 上 / 下一項 | 回到上 / 下一項(X3,依 Adobe 方向鍵模式;選單鈕也一樣 —— X6 統一成側欄做法) |
127
+ * | Home End | 第一 / 最後一項 | 同左(X4 統一成側欄做法) |
128
+ * | → | 樹裡收著的資料夾先展開;否則進第一個東西;沒有就不動 | 下一個;最後一個停住 |
129
+ * | ← | 樹:展開的先收合、否則回上一層;平面清單:不動 | 上一個;第一個 → 回項目 |
130
+ * | Tab / Shift+Tab | 一下離開這一串 | 同左(X7:先回項目、再由瀏覽器往外走) |
131
+ * | Enter / 空白鍵 | 這一項自己的預設動作 | 那個東西自己的動作 |
132
+ * | 可見字元(只有 `typeahead` 宿主 = 樹)| 打字跳位:下一個名字以它開頭的項目(W3C APG Tree View) | 不處理(按鈕上打字沒有意義) |
133
+ */
134
+ export declare function resolveRovingKey(input: RovingKeyInput): RovingKeyAction;
135
+ /**
136
+ * 上 / 下 / 第一 / 最後一項落在哪 —— **不繞回**(同 W3C grid / tree;X2)。
137
+ * `items` 依畫面順序、含走不到的項目(停用、看不見);`isNavigable` 決定哪些能當落點。
138
+ * 目前這一項本身走不到(例:樹裡用 ← 回到停用的上一層)時,仍從它的位置往前 / 往後找。
139
+ */
140
+ export declare function pickRovingTarget<T>(move: RovingMove, items: readonly T[], current: T | null, isNavigable?: (item: T) => boolean): T | null;
141
+ export interface RovingTabStopOptions<T> {
142
+ /** 上次焦點停過的那一項 */
143
+ remembered?: T | null;
144
+ /** 「目前這一頁 / 選中」那一項(側欄 aria-current="page";樹 aria-selected) */
145
+ isCurrent?: (item: T) => boolean;
146
+ /** 能不能當「目前」或「第一項」的落點(停用、看不見的不算) */
147
+ isNavigable?: (item: T) => boolean;
148
+ /** 上次停過的那一項還能不能拿焦點(預設同 isNavigable;樹的停用列仍可經 ← 拿到焦點,傳 () => true) */
149
+ canHoldFocus?: (item: T) => boolean;
150
+ }
151
+ /**
152
+ * 整串唯一的 Tab 停靠點(X1):上次停過的那一項 → 目前這一頁 / 選中那一項 → 第一個可用項。
153
+ * 出處:W3C「the element that had focus the last time the composite contained focus」+ 導覽樹範例
154
+ * 「focus always lands on the item representing the current page」(keyboard-model-canonical.md 同節引文)。
155
+ */
156
+ export declare function pickRovingTabStop<T>(items: readonly T[], options?: RovingTabStopOptions<T>): T | null;
157
+ /**
158
+ * 項目裡「可以走到的東西」的候選(再經 `isRovingControlReachable` 過濾)。
159
+ * 2026-09-26 之前四份各寫一份、各差一點(樹多認 contenteditable、側欄只認按鈕類),合成這一份;
160
+ * 側欄另有自己的範圍(只認動作鈕的兩個出口),以參數傳入。
161
+ */
162
+ export declare const ROVING_CONTROL_SELECTOR = "a[href], button, input, select, textarea, [contenteditable]:not([contenteditable=\"false\"]), [tabindex]";
163
+ /** 把候選 selector 限定在某個範圍裡(`[cmdk-item] a[href], [cmdk-item] button, …`)。不用 `:is()`:舊版 jsdom 不認得。 */
164
+ export declare function scopeRovingSelector(scope: string, selector?: string): string;
165
+ export declare function isTextEntryElement(el: Element): boolean;
166
+ /**
167
+ * 此刻走得到:沒有停用、不在 inert / aria-hidden 裡(例:多選列的勾選框是 inert + aria-hidden 的純視覺)、
168
+ * 畫得出來(display:none 不算;opacity 0 的「滑過才出現」按鈕算 —— 鍵盤走到時它會浮出)。
169
+ */
170
+ export declare function isRovingControlReachable(el: HTMLElement): boolean;
171
+ /** 某一項裡可以走到的東西,依畫面(文件)順序。 */
172
+ export declare function listRovingControls(root: Element, selector?: string): HTMLElement[];
173
+ /** 只在值不同時才寫:同值 setAttribute 也會產生 mutation record,會讓宿主的 MutationObserver 自我觸發。 */
174
+ export declare function setRovingTabIndex(el: HTMLElement, value: 0 | -1): void;
175
+ /**
176
+ * 項目裡的東西一律不在 Tab 路上(-1,→ 才進得去;別項與本項的都一樣)。
177
+ * 用 DOM 設而不是逐一傳 prop:consumer 的插槽(inlineActionsSlot / description / 名片頭像)是任意 ReactNode,拿不到它們的 props。
178
+ */
179
+ export declare function removeRovingControlsFromTabOrder(elements: Iterable<HTMLElement>): void;
180
+ /** 真焦點宿主(側欄 / 檔案清單 / 樹)執行判定結果時要的東西 */
181
+ export interface RovingDomHost {
182
+ event: {
183
+ preventDefault(): void;
184
+ };
185
+ /** 目前這一項(整串唯一的 Tab 停靠點就在這種元素上) */
186
+ item: HTMLElement;
187
+ /** 這一項可以走到的東西(已過濾,`listRovingControls`) */
188
+ controls: readonly HTMLElement[];
189
+ /** 整串的項目,依畫面順序(含走不到的;由 isNavigable 篩) */
190
+ items: readonly HTMLElement[];
191
+ isNavigable?: (item: HTMLElement) => boolean;
192
+ /** 樹傳 { preventScroll: true }(捲動由列自己的 scrollIntoView 以 block:nearest 處理) */
193
+ focusOptions?: FocusOptions;
194
+ /** 樹才有:展開 / 收合 / 回上一層 */
195
+ onExpand?: () => void;
196
+ onCollapse?: () => void;
197
+ onParent?: () => void;
198
+ /** 項目本身的預設動作要由宿主執行時才傳(樹 = 選取);不傳 = 項目是原生按鈕,交給瀏覽器 */
199
+ onActivateItem?: () => void;
200
+ /** 打字跳位(只有 `typeahead: true` 的宿主):宿主自己累積字串、找項目、搬焦點 */
201
+ onTypeahead?: (char: string) => void;
202
+ }
203
+ /**
204
+ * 真焦點宿主的執行器:判定之外只剩「焦點怎麼搬、要不要擋預設」,三個宿主完全相同,所以也只寫一份。
205
+ * `reorder` 不在這裡(樹自己處理:要看是否正在用滑鼠拖曳)。
206
+ */
207
+ export declare function applyRovingAction(action: RovingKeyAction, host: RovingDomHost): void;
208
+ //# sourceMappingURL=roving-list-keyboard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"roving-list-keyboard.d.ts","sourceRoot":"","sources":["../../src/lib/roving-list-keyboard.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,174 @@
1
+ const REORDER_KEYS = ["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight"];
2
+ const isReorderKey = (key) => REORDER_KEYS.includes(key);
3
+ function resolveRovingKey(input) {
4
+ const { key } = input;
5
+ if (input.defaultPrevented) return { type: "none" };
6
+ if (input.focus === "item" && input.reorderable && (input.metaKey || input.ctrlKey) && input.shiftKey && isReorderKey(key)) {
7
+ return { type: "reorder", key };
8
+ }
9
+ if (input.metaKey || input.ctrlKey || input.altKey) return { type: "none" };
10
+ if (key === "Tab") return { type: "leave" };
11
+ if (input.focus === "control") {
12
+ if (input.controlIsTextEntry) return { type: "none" };
13
+ switch (key) {
14
+ case "ArrowRight":
15
+ return input.controlIndex < input.controlCount - 1 ? { type: "control", index: input.controlIndex + 1 } : { type: "consume" };
16
+ case "ArrowLeft":
17
+ return input.controlIndex > 0 ? { type: "control", index: input.controlIndex - 1 } : { type: "item" };
18
+ case "ArrowDown":
19
+ return { type: "item-next" };
20
+ case "ArrowUp":
21
+ return { type: "item-prev" };
22
+ case "Home":
23
+ return { type: "item-first" };
24
+ case "End":
25
+ return { type: "item-last" };
26
+ case "Enter":
27
+ case " ":
28
+ return { type: "activate-control" };
29
+ default:
30
+ return { type: "none" };
31
+ }
32
+ }
33
+ if (input.itemIsTextEntry) {
34
+ if (key === "ArrowRight") {
35
+ return !input.shiftKey && input.caretAtEnd !== false && input.controlCount > 0 ? { type: "control", index: 0 } : { type: "none" };
36
+ }
37
+ if (key === "ArrowDown") return { type: "item-next" };
38
+ if (key === "ArrowUp") return { type: "item-prev" };
39
+ return { type: "none" };
40
+ }
41
+ switch (key) {
42
+ case "ArrowDown":
43
+ return { type: "item-next" };
44
+ case "ArrowUp":
45
+ return { type: "item-prev" };
46
+ case "Home":
47
+ return { type: "item-first" };
48
+ case "End":
49
+ return { type: "item-last" };
50
+ case "ArrowRight": {
51
+ const tree = input.tree;
52
+ if (tree && tree.hasChildren && !tree.expanded) return { type: "expand" };
53
+ return input.controlCount > 0 ? { type: "control", index: 0 } : { type: "consume" };
54
+ }
55
+ case "ArrowLeft": {
56
+ const tree = input.tree;
57
+ if (tree && tree.hasChildren && tree.expanded) return { type: "collapse" };
58
+ return tree && tree.hasParent ? { type: "parent" } : { type: "consume" };
59
+ }
60
+ case "Enter":
61
+ case " ":
62
+ return { type: "activate-item" };
63
+ default:
64
+ if (input.typeahead && key.length === 1 && key !== " ") return { type: "typeahead", char: key };
65
+ return { type: "none" };
66
+ }
67
+ }
68
+ function pickRovingTarget(move, items, current, isNavigable = () => true) {
69
+ if (move === "item-first") return items.find(isNavigable) ?? null;
70
+ if (move === "item-last") {
71
+ for (let i = items.length - 1; i >= 0; i--) if (isNavigable(items[i])) return items[i];
72
+ return null;
73
+ }
74
+ const start = current == null ? -1 : items.indexOf(current);
75
+ if (start < 0) return null;
76
+ const dir = move === "item-next" ? 1 : -1;
77
+ for (let i = start + dir; i >= 0 && i < items.length; i += dir) {
78
+ if (isNavigable(items[i])) return items[i];
79
+ }
80
+ return null;
81
+ }
82
+ function pickRovingTabStop(items, options = {}) {
83
+ const isNavigable = options.isNavigable ?? (() => true);
84
+ const canHoldFocus = options.canHoldFocus ?? isNavigable;
85
+ const { remembered, isCurrent } = options;
86
+ if (remembered != null && items.includes(remembered) && canHoldFocus(remembered)) return remembered;
87
+ const current = isCurrent ? items.find((item) => isNavigable(item) && isCurrent(item)) : void 0;
88
+ return current ?? items.find(isNavigable) ?? null;
89
+ }
90
+ const ROVING_CONTROL_SELECTOR = 'a[href], button, input, select, textarea, [contenteditable]:not([contenteditable="false"]), [tabindex]';
91
+ function scopeRovingSelector(scope, selector = ROVING_CONTROL_SELECTOR) {
92
+ return selector.split(",").map((part) => `${scope} ${part.trim()}`).join(", ");
93
+ }
94
+ const NON_TEXT_INPUT_TYPES = /* @__PURE__ */ new Set(["checkbox", "radio", "range", "color", "file", "image", "button", "submit", "reset", "hidden"]);
95
+ function isTextEntryElement(el) {
96
+ if (el.tagName === "TEXTAREA" || el.tagName === "SELECT") return true;
97
+ if (el.tagName === "INPUT") return !NON_TEXT_INPUT_TYPES.has(el.type);
98
+ return el.isContentEditable === true;
99
+ }
100
+ function isRovingControlReachable(el) {
101
+ return !el.disabled && !el.closest('[inert], [aria-hidden="true"]') && el.getClientRects().length > 0;
102
+ }
103
+ function listRovingControls(root, selector = ROVING_CONTROL_SELECTOR) {
104
+ return Array.from(root.querySelectorAll(selector)).filter(isRovingControlReachable);
105
+ }
106
+ function setRovingTabIndex(el, value) {
107
+ if (el.getAttribute("tabindex") !== String(value)) el.setAttribute("tabindex", String(value));
108
+ }
109
+ function removeRovingControlsFromTabOrder(elements) {
110
+ for (const el of elements) setRovingTabIndex(el, -1);
111
+ }
112
+ function applyRovingAction(action, host) {
113
+ var _a, _b, _c, _d, _e;
114
+ const { event, item, controls, focusOptions } = host;
115
+ switch (action.type) {
116
+ case "none":
117
+ case "reorder":
118
+ case "activate-control":
119
+ return;
120
+ case "leave":
121
+ item.focus(focusOptions);
122
+ return;
123
+ case "activate-item":
124
+ if (!host.onActivateItem) return;
125
+ event.preventDefault();
126
+ host.onActivateItem();
127
+ return;
128
+ case "typeahead":
129
+ if (!host.onTypeahead) return;
130
+ event.preventDefault();
131
+ host.onTypeahead(action.char);
132
+ return;
133
+ }
134
+ event.preventDefault();
135
+ switch (action.type) {
136
+ case "consume":
137
+ return;
138
+ case "item-prev":
139
+ case "item-next":
140
+ case "item-first":
141
+ case "item-last":
142
+ (_a = pickRovingTarget(action.type, host.items, item, host.isNavigable)) == null ? void 0 : _a.focus(focusOptions);
143
+ return;
144
+ case "control":
145
+ (_b = controls[action.index]) == null ? void 0 : _b.focus(focusOptions);
146
+ return;
147
+ case "item":
148
+ item.focus(focusOptions);
149
+ return;
150
+ case "expand":
151
+ (_c = host.onExpand) == null ? void 0 : _c.call(host);
152
+ return;
153
+ case "collapse":
154
+ (_d = host.onCollapse) == null ? void 0 : _d.call(host);
155
+ return;
156
+ case "parent":
157
+ (_e = host.onParent) == null ? void 0 : _e.call(host);
158
+ return;
159
+ }
160
+ }
161
+ export {
162
+ ROVING_CONTROL_SELECTOR,
163
+ applyRovingAction,
164
+ isRovingControlReachable,
165
+ isTextEntryElement,
166
+ listRovingControls,
167
+ pickRovingTabStop,
168
+ pickRovingTarget,
169
+ removeRovingControlsFromTabOrder,
170
+ resolveRovingKey,
171
+ scopeRovingSelector,
172
+ setRovingTabIndex
173
+ };
174
+ //# sourceMappingURL=roving-list-keyboard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"roving-list-keyboard.js","sources":["../../src/lib/roving-list-keyboard.ts"],"sourcesContent":["/**\n * @internal — 「列上有小按鈕的一串」鍵盤路線的**唯一**判定與執行(2026-09-26 由四份合一)。\n *\n * ── 規則住所 ─────────────────────────────────────────────────────────────────\n * 規則(Tab 換區、方向鍵在區裡走、Esc 退一步)= `ds-canonical/references/keyboard-model-canonical.md`\n * 「列上有小按鈕的一串」;本檔是那張按鍵表**唯一的程式版**,四個宿主只讀自己的 DOM、執行結果:\n * - `components/Sidebar/sidebar.tsx` SidebarMenu(真焦點,roving tabindex)\n * - `components/FileUpload/file-upload.tsx` 內建檔案清單(真焦點,roving tabindex)\n * - `components/TreeView/tree-view.tsx` 樹狀表格(真焦點 + 樹的展開 / 收合 / 回上一層 + 鍵盤重排)\n * - `components/Command/command.tsx` cmdk 清單(「這一項」= 反白列,焦點停在搜尋框 / 清單 = home)\n *\n * ── 決定來源 ─────────────────────────────────────────────────────────────────\n * 路線乙:待辦總帳 `governance/planning/2026-09-25-interaction-and-hover-remediation.md` B9,user 逐字(附條件同意,\n * 條件查證成立記在同列):「確定建議符合我們一致的設計語言且不違背世界級的設計就照建議」。\n * 合併與 X1–X7 統一:同檔〇節「09-26 同意清單回覆」,user 逐字:「確保符合我們一致的設計語言且不違背世界級的設計且都有\n * 確保整個ds 是SSOT,避免漂移就照你建議做」—— 同意的清單含「按鍵規則合併」與批次細節 X1–X35,其中\n * X4(按鈕上 Home / End)、X6(列上選單鈕按 ↓)、X7(樹在按鈕上 Shift+Tab)三處「統一成側欄做法」。\n * 2026-09-26 之前這張表有四份平行實作(側欄 / 檔案清單 / 樹 `tree-keyboard-route.ts` / Command),三處互相不一致:\n * X4 側欄與檔案清單的按鈕上 Home / End 換到頭尾項,樹不處理;\n * X6 側欄的選單鈕按 ↓ 換下一項,樹讓給按鈕開選單;\n * X7 側欄在按鈕上 Shift+Tab 一下離開,樹要兩下(先落回本列)—— 違反 B9「Tab 一下離開」。\n *\n * ── 本檔兩層 ─────────────────────────────────────────────────────────────────\n * (一) 判定(純函式,不碰 DOM):`resolveRovingKey` / `pickRovingTarget` / `pickRovingTabStop`\n * —— 判定表 `scripts/test-roving-list-keyboard.mjs`(含舊路線對照組)逐格驗。\n * (二) 宿主共用的 DOM 小工具:列裡可走到的東西、把它們收出 Tab 路、真焦點宿主的執行器 `applyRovingAction`。\n * Command 是虛擬游標(cmdk 自己搬反白),執行方式不同,在 command.tsx 自己對應 —— 判定仍只讀本檔。\n */\n\n// ════════════════════════════════════════════════════════════════════════════\n// (一) 判定\n// ════════════════════════════════════════════════════════════════════════════\n\n/** 按鍵發生時,焦點在「這一項」本身,還是這一項裡的某一個東西(小按鈕 / 連結 / 名片頭像…) */\nexport type RovingFocus = 'item' | 'control'\n\n/** 樹才有的三個狀態;平面清單不傳 = 沒有「先展開」「回上一層」這兩步 */\nexport interface RovingTreeState {\n hasChildren: boolean\n expanded: boolean\n hasParent: boolean\n}\n\nexport interface RovingKeyInput {\n /** KeyboardEvent.key */\n key: string\n focus: RovingFocus\n /** 這一項可以用 → 走到的東西數(宿主以 `listRovingControls` 數出來) */\n controlCount: number\n /** focus === 'control' 時焦點在第幾個(0 起算);focus === 'item' 時不讀 */\n controlIndex: number\n /** 焦點所在的東西是文字輸入(輸入框 / 多行框 / 下拉 / 可編輯區):方向鍵、Home / End、Enter / 空白鍵都屬於它自己 */\n controlIsTextEntry?: boolean\n /** 「這一項」本身是文字輸入(只有 Command 的搜尋框):← / Home / End / 打字屬於插入點 */\n itemIsTextEntry?: boolean\n /** itemIsTextEntry 時:插入點已在字尾(且沒有選取範圍)。不在字尾時 → 要留給插入點移動 */\n caretAtEnd?: boolean\n /** 樹的狀態;平面清單不傳 */\n tree?: RovingTreeState\n /** 可以用 Cmd / Ctrl + Shift + 方向鍵重排(只有 draggable 的樹) */\n reorderable?: boolean\n /**\n * 這一串有「打字跳位」(只有樹:keyboard-model-canonical.md 五條判準第 2 條「需要 tree 那一整套鍵盤模型(打字前導、…)」)。\n * 焦點在項目上打一個可見字元 → 回 `typeahead`,由宿主找下一個名字以該字開頭的項目;平面清單(側欄 / 檔案清單)不傳 = 不處理。\n * 出處:W3C APG Tree View 逐字 \"Type a character: focus moves to the next node with a name that starts with the typed character.\"\n * (<https://www.w3.org/WAI/ARIA/apg/patterns/treeview/>)。\n */\n typeahead?: boolean\n /** 事件已被更外層的捕獲處理程式 preventDefault */\n defaultPrevented: boolean\n metaKey: boolean\n ctrlKey: boolean\n altKey: boolean\n shiftKey: boolean\n}\n\nexport type RovingReorderKey = 'ArrowUp' | 'ArrowDown' | 'ArrowLeft' | 'ArrowRight'\nexport type RovingMove = 'item-prev' | 'item-next' | 'item-first' | 'item-last'\n\nexport type RovingKeyAction =\n /** 不處理 —— 交給瀏覽器、插入點或那個東西自己 */\n | { type: 'none' }\n /** 吃掉按鍵(preventDefault)但不動焦點 —— 例:已在最後一顆按鈕上再按 → */\n | { type: 'consume' }\n /** 換到上 / 下 / 第一 / 最後一項(焦點落在項目上;不繞回) */\n | { type: RovingMove }\n | { type: 'expand' }\n | { type: 'collapse' }\n | { type: 'parent' }\n /** 把焦點放到這一項第 index 個東西 */\n | { type: 'control'; index: number }\n /** 焦點回到這一項本身 */\n | { type: 'item' }\n /** Tab / Shift+Tab:焦點先回到這一項(整串唯一的 Tab 停靠點),**不擋預設** —— 瀏覽器接著往下 / 往上走一站 = 一下離開這一串 */\n | { type: 'leave' }\n /** Enter / 空白鍵在項目上:這一項自己的預設動作(樹 = 選取;原生按鈕 = 瀏覽器自己點) */\n | { type: 'activate-item' }\n /** Enter / 空白鍵在項目裡的按鈕 / 連結上:那個東西自己的動作,**不是**「選這一項」 */\n | { type: 'activate-control' }\n /** 鍵盤重排(樹;修飾鍵層,與導覽鍵正交) */\n | { type: 'reorder'; key: RovingReorderKey }\n /** 打字跳位(只有 `typeahead: true` 的宿主):焦點在項目上打了一個可見字元,由宿主找下一個名字以它開頭的項目 */\n | { type: 'typeahead'; char: string }\n\nconst REORDER_KEYS: readonly string[] = ['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight']\nconst isReorderKey = (key: string): key is RovingReorderKey => REORDER_KEYS.includes(key)\n\n/**\n * 按鍵表(規格 SSOT = keyboard-model-canonical.md「列上有小按鈕的一串」;每一格的來歷寫在該格旁):\n *\n * | 鍵 | 焦點在項目上 | 焦點在這一項的東西上 |\n * |---|---|---|\n * | ↑ ↓ | 上 / 下一項 | 回到上 / 下一項(X3,依 Adobe 方向鍵模式;選單鈕也一樣 —— X6 統一成側欄做法) |\n * | Home End | 第一 / 最後一項 | 同左(X4 統一成側欄做法) |\n * | → | 樹裡收著的資料夾先展開;否則進第一個東西;沒有就不動 | 下一個;最後一個停住 |\n * | ← | 樹:展開的先收合、否則回上一層;平面清單:不動 | 上一個;第一個 → 回項目 |\n * | Tab / Shift+Tab | 一下離開這一串 | 同左(X7:先回項目、再由瀏覽器往外走) |\n * | Enter / 空白鍵 | 這一項自己的預設動作 | 那個東西自己的動作 |\n * | 可見字元(只有 `typeahead` 宿主 = 樹)| 打字跳位:下一個名字以它開頭的項目(W3C APG Tree View) | 不處理(按鈕上打字沒有意義) |\n */\nexport function resolveRovingKey(input: RovingKeyInput): RovingKeyAction {\n const { key } = input\n if (input.defaultPrevented) return { type: 'none' }\n\n // 鍵盤重排:修飾鍵層,只從項目上發動(tree-view.spec.md「鍵盤重排」,行為不變)\n if (input.focus === 'item' && input.reorderable && (input.metaKey || input.ctrlKey) && input.shiftKey && isReorderKey(key)) {\n return { type: 'reorder', key }\n }\n // 帶 Cmd / Ctrl / Alt 的組合留給瀏覽器、輔助科技與按鈕自己(例:Alt+↓)。Shift 不在內:Shift+Tab 是導覽鍵。\n if (input.metaKey || input.ctrlKey || input.altKey) return { type: 'none' }\n\n // Tab / Shift+Tab:不論焦點在項目或項目裡的東西上,都是一下離開這一串(B9;X7)\n if (key === 'Tab') return { type: 'leave' }\n\n // ── 焦點在這一項裡的東西上 ──\n if (input.focus === 'control') {\n // 文字輸入:方向鍵移插入點、Enter / 空白鍵是打字,全部屬於它(原樹的 isButtonLike 例外,現全宿主一致)\n if (input.controlIsTextEntry) return { type: 'none' }\n switch (key) {\n case 'ArrowRight':\n // 已是最後一個 → 不動(W3C 樹狀表格:最右一格按 → 焦點不移動)\n return input.controlIndex < input.controlCount - 1\n ? { type: 'control', index: input.controlIndex + 1 }\n : { type: 'consume' }\n case 'ArrowLeft':\n // 已是第一個 → 回到這一項(B9「← 回項目」)\n return input.controlIndex > 0 ? { type: 'control', index: input.controlIndex - 1 } : { type: 'item' }\n case 'ArrowDown':\n // X6:選單鈕(aria-haspopup)也一樣換下一項 —— 開選單用 Enter / 空白鍵(同側欄帳號列,sidebar.spec.md)\n return { type: 'item-next' }\n case 'ArrowUp':\n return { type: 'item-prev' }\n case 'Home':\n // X4:同側欄 / 檔案清單 —— 換到第一 / 最後一項\n return { type: 'item-first' }\n case 'End':\n return { type: 'item-last' }\n case 'Enter':\n case ' ':\n return { type: 'activate-control' }\n default:\n return { type: 'none' }\n }\n }\n\n // ── 焦點在項目本身(Command:焦點在 home、反白在這一列)──\n if (input.itemIsTextEntry) {\n // 搜尋框:插入點已在字尾、沒有按 Shift(Shift+→ 是選取文字)時,→ 才進反白列的第一個東西(AI 推導,command.spec.md「A11y」)\n if (key === 'ArrowRight') {\n return !input.shiftKey && input.caretAtEnd !== false && input.controlCount > 0\n ? { type: 'control', index: 0 }\n : { type: 'none' }\n }\n if (key === 'ArrowDown') return { type: 'item-next' }\n if (key === 'ArrowUp') return { type: 'item-prev' }\n // ← / Home / End / Enter / 空白鍵 / 打字:屬於搜尋框與清單函式庫\n return { type: 'none' }\n }\n switch (key) {\n case 'ArrowDown':\n return { type: 'item-next' }\n case 'ArrowUp':\n return { type: 'item-prev' }\n case 'Home':\n return { type: 'item-first' }\n case 'End':\n return { type: 'item-last' }\n case 'ArrowRight': {\n // 樹裡收著的資料夾 → 先展開(B9);已展開的資料夾或葉節點、平面清單 → 進第一個東西;沒有就不動\n const tree = input.tree\n if (tree && tree.hasChildren && !tree.expanded) return { type: 'expand' }\n return input.controlCount > 0 ? { type: 'control', index: 0 } : { type: 'consume' }\n }\n case 'ArrowLeft': {\n // 樹:展開的資料夾 → 收合;否則 → 回上一層;最外層不動。平面清單:不動(B9「← 在列上維持樹的語意」)\n const tree = input.tree\n if (tree && tree.hasChildren && tree.expanded) return { type: 'collapse' }\n return tree && tree.hasParent ? { type: 'parent' } : { type: 'consume' }\n }\n case 'Enter':\n case ' ':\n return { type: 'activate-item' }\n default:\n // 打字跳位(樹):單一可見字元(空白鍵已在上面 = 選取;Shift+字母 也是一個可見字元,大小寫由宿主比對時折疊)\n if (input.typeahead && key.length === 1 && key !== ' ') return { type: 'typeahead', char: key }\n return { type: 'none' }\n }\n}\n\n/**\n * 上 / 下 / 第一 / 最後一項落在哪 —— **不繞回**(同 W3C grid / tree;X2)。\n * `items` 依畫面順序、含走不到的項目(停用、看不見);`isNavigable` 決定哪些能當落點。\n * 目前這一項本身走不到(例:樹裡用 ← 回到停用的上一層)時,仍從它的位置往前 / 往後找。\n */\nexport function pickRovingTarget<T>(\n move: RovingMove,\n items: readonly T[],\n current: T | null,\n isNavigable: (item: T) => boolean = () => true,\n): T | null {\n if (move === 'item-first') return items.find(isNavigable) ?? null\n if (move === 'item-last') {\n for (let i = items.length - 1; i >= 0; i--) if (isNavigable(items[i])) return items[i]\n return null\n }\n const start = current == null ? -1 : items.indexOf(current)\n if (start < 0) return null\n const dir = move === 'item-next' ? 1 : -1\n for (let i = start + dir; i >= 0 && i < items.length; i += dir) {\n if (isNavigable(items[i])) return items[i]\n }\n return null\n}\n\nexport interface RovingTabStopOptions<T> {\n /** 上次焦點停過的那一項 */\n remembered?: T | null\n /** 「目前這一頁 / 選中」那一項(側欄 aria-current=\"page\";樹 aria-selected) */\n isCurrent?: (item: T) => boolean\n /** 能不能當「目前」或「第一項」的落點(停用、看不見的不算) */\n isNavigable?: (item: T) => boolean\n /** 上次停過的那一項還能不能拿焦點(預設同 isNavigable;樹的停用列仍可經 ← 拿到焦點,傳 () => true) */\n canHoldFocus?: (item: T) => boolean\n}\n\n/**\n * 整串唯一的 Tab 停靠點(X1):上次停過的那一項 → 目前這一頁 / 選中那一項 → 第一個可用項。\n * 出處:W3C「the element that had focus the last time the composite contained focus」+ 導覽樹範例\n * 「focus always lands on the item representing the current page」(keyboard-model-canonical.md 同節引文)。\n */\nexport function pickRovingTabStop<T>(items: readonly T[], options: RovingTabStopOptions<T> = {}): T | null {\n const isNavigable = options.isNavigable ?? (() => true)\n const canHoldFocus = options.canHoldFocus ?? isNavigable\n const { remembered, isCurrent } = options\n if (remembered != null && items.includes(remembered) && canHoldFocus(remembered)) return remembered\n const current = isCurrent ? items.find((item) => isNavigable(item) && isCurrent(item)) : undefined\n return current ?? items.find(isNavigable) ?? null\n}\n\n// ════════════════════════════════════════════════════════════════════════════\n// (二) 宿主共用的 DOM 小工具\n// ════════════════════════════════════════════════════════════════════════════\n\n/**\n * 項目裡「可以走到的東西」的候選(再經 `isRovingControlReachable` 過濾)。\n * 2026-09-26 之前四份各寫一份、各差一點(樹多認 contenteditable、側欄只認按鈕類),合成這一份;\n * 側欄另有自己的範圍(只認動作鈕的兩個出口),以參數傳入。\n */\nexport const ROVING_CONTROL_SELECTOR =\n 'a[href], button, input, select, textarea, [contenteditable]:not([contenteditable=\"false\"]), [tabindex]'\n\n/** 把候選 selector 限定在某個範圍裡(`[cmdk-item] a[href], [cmdk-item] button, …`)。不用 `:is()`:舊版 jsdom 不認得。 */\nexport function scopeRovingSelector(scope: string, selector: string = ROVING_CONTROL_SELECTOR): string {\n return selector.split(',').map((part) => `${scope} ${part.trim()}`).join(', ')\n}\n\n/**\n * 文字輸入(方向鍵、Home / End、打字屬於它自己)。沒有插入點的 input type 不算(同 React Aria `nonTextInputTypes`)。\n * 全 DS 只有這一份:清單鍵盤(本檔)、反白來歷(hooks/use-input-modality.ts)、下拉的空白鍵(SelectMenu/select-menu-keyboard.ts)\n * 都讀它(2026-09-26 之前各寫一份)。\n */\nconst NON_TEXT_INPUT_TYPES = new Set(['checkbox', 'radio', 'range', 'color', 'file', 'image', 'button', 'submit', 'reset', 'hidden'])\nexport function isTextEntryElement(el: Element): boolean {\n if (el.tagName === 'TEXTAREA' || el.tagName === 'SELECT') return true\n if (el.tagName === 'INPUT') return !NON_TEXT_INPUT_TYPES.has((el as HTMLInputElement).type)\n return (el as HTMLElement).isContentEditable === true\n}\n\n/**\n * 此刻走得到:沒有停用、不在 inert / aria-hidden 裡(例:多選列的勾選框是 inert + aria-hidden 的純視覺)、\n * 畫得出來(display:none 不算;opacity 0 的「滑過才出現」按鈕算 —— 鍵盤走到時它會浮出)。\n */\nexport function isRovingControlReachable(el: HTMLElement): boolean {\n return (\n !(el as HTMLButtonElement).disabled &&\n !el.closest('[inert], [aria-hidden=\"true\"]') &&\n el.getClientRects().length > 0\n )\n}\n\n/** 某一項裡可以走到的東西,依畫面(文件)順序。 */\nexport function listRovingControls(root: Element, selector: string = ROVING_CONTROL_SELECTOR): HTMLElement[] {\n return Array.from(root.querySelectorAll<HTMLElement>(selector)).filter(isRovingControlReachable)\n}\n\n/** 只在值不同時才寫:同值 setAttribute 也會產生 mutation record,會讓宿主的 MutationObserver 自我觸發。 */\nexport function setRovingTabIndex(el: HTMLElement, value: 0 | -1): void {\n if (el.getAttribute('tabindex') !== String(value)) el.setAttribute('tabindex', String(value))\n}\n\n/**\n * 項目裡的東西一律不在 Tab 路上(-1,→ 才進得去;別項與本項的都一樣)。\n * 用 DOM 設而不是逐一傳 prop:consumer 的插槽(inlineActionsSlot / description / 名片頭像)是任意 ReactNode,拿不到它們的 props。\n */\nexport function removeRovingControlsFromTabOrder(elements: Iterable<HTMLElement>): void {\n for (const el of elements) setRovingTabIndex(el, -1)\n}\n\n/** 真焦點宿主(側欄 / 檔案清單 / 樹)執行判定結果時要的東西 */\nexport interface RovingDomHost {\n event: { preventDefault(): void }\n /** 目前這一項(整串唯一的 Tab 停靠點就在這種元素上) */\n item: HTMLElement\n /** 這一項可以走到的東西(已過濾,`listRovingControls`) */\n controls: readonly HTMLElement[]\n /** 整串的項目,依畫面順序(含走不到的;由 isNavigable 篩) */\n items: readonly HTMLElement[]\n isNavigable?: (item: HTMLElement) => boolean\n /** 樹傳 { preventScroll: true }(捲動由列自己的 scrollIntoView 以 block:nearest 處理) */\n focusOptions?: FocusOptions\n /** 樹才有:展開 / 收合 / 回上一層 */\n onExpand?: () => void\n onCollapse?: () => void\n onParent?: () => void\n /** 項目本身的預設動作要由宿主執行時才傳(樹 = 選取);不傳 = 項目是原生按鈕,交給瀏覽器 */\n onActivateItem?: () => void\n /** 打字跳位(只有 `typeahead: true` 的宿主):宿主自己累積字串、找項目、搬焦點 */\n onTypeahead?: (char: string) => void\n}\n\n/**\n * 真焦點宿主的執行器:判定之外只剩「焦點怎麼搬、要不要擋預設」,三個宿主完全相同,所以也只寫一份。\n * `reorder` 不在這裡(樹自己處理:要看是否正在用滑鼠拖曳)。\n */\nexport function applyRovingAction(action: RovingKeyAction, host: RovingDomHost): void {\n const { event, item, controls, focusOptions } = host\n switch (action.type) {\n case 'none':\n case 'reorder':\n case 'activate-control':\n // 按鈕 / 連結的 Enter、空白鍵是瀏覽器原生行為;這裡不擋也不代點\n return\n case 'leave':\n // 焦點在項目裡的東西上時先放回這一項(整串唯一那顆 0),**不擋預設**:瀏覽器接著從這一項往下 / 往上走 = 一下離開。\n // 往前若不先放回,Shift+Tab 會先落回本項(X7 修的就是這個);往後在焦點鎖(手機抽屜 = Radix FocusScope)裡,\n // 鎖判斷「是不是最後一顆」看的是 tabindex≥0 的元素,-1 的按鈕永遠不是它 → 不先放回就繞不回開頭\n //(@radix-ui/react-focus-scope@1.1.7 dist/index.mjs:106-121)。焦點已在這一項上時 focus() 不做任何事。\n item.focus(focusOptions)\n return\n case 'activate-item':\n if (!host.onActivateItem) return\n event.preventDefault()\n host.onActivateItem()\n return\n case 'typeahead':\n // 判定只在 typeahead 宿主才會回這格;擋預設是為了不讓 Firefox「輸入時搜尋」之類的瀏覽器功能吃掉字元\n if (!host.onTypeahead) return\n event.preventDefault()\n host.onTypeahead(action.char)\n return\n default:\n break\n }\n event.preventDefault()\n switch (action.type) {\n case 'consume':\n return\n case 'item-prev':\n case 'item-next':\n case 'item-first':\n case 'item-last':\n pickRovingTarget(action.type, host.items, item, host.isNavigable)?.focus(focusOptions)\n return\n case 'control':\n controls[action.index]?.focus(focusOptions)\n return\n case 'item':\n item.focus(focusOptions)\n return\n case 'expand':\n host.onExpand?.()\n return\n case 'collapse':\n host.onCollapse?.()\n return\n case 'parent':\n host.onParent?.()\n return\n }\n}\n"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/lib/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,KAAK,UAAU,EAAE,MAAM,MAAM,CAAA;AAkF5C;;;;;;;;GAQG;AACH,wBAAgB,EAAE,CAAC,GAAG,MAAM,EAAE,UAAU,EAAE,UAEzC;AAUD,wBAAgB,cAAc,CAAC,CAAC,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,GAAG,MAAM,GAAG,SAAS,CAS/E"}
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/lib/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,KAAK,UAAU,EAAE,MAAM,MAAM,CAAA;AAuF5C;;;;;;;;GAQG;AACH,wBAAgB,EAAE,CAAC,GAAG,MAAM,EAAE,UAAU,EAAE,UAEzC;AAUD,wBAAgB,cAAc,CAAC,CAAC,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,GAAG,MAAM,GAAG,SAAS,CAS/E"}
package/dist/lib/utils.js CHANGED
@@ -68,7 +68,12 @@ const twMerge = extendTailwindMerge({
68
68
  ],
69
69
  // Custom opacity utility(對應 tokens/opacity/opacity.css `@utility opacity-disabled`)。
70
70
  // 不註冊 group → tailwind-merge 用 default opacity-N heuristic,可能誤判 class 衝突。
71
- "opacity": ["opacity-disabled"]
71
+ "opacity": ["opacity-disabled"],
72
+ // 「底」的滑過 / 按住疊層(對應 tokens/color/semantic.css `@utility bg-interaction-hover / -active`;
73
+ // color.spec.md「Hover 換色配對總則」,待辦總帳 B8)。它們寫的是 background-image,不是底色 ——
74
+ // 不註冊的話 tailwind-merge 把 `bg-*` 一律猜成 bg-color,`cn('bg-surface', 'bg-interaction-hover')`
75
+ // 會把 bg-surface 刪掉,底色整個消失(2026-09-25 以 tailwind-merge 3.5 實測)。登記在 bg-image 才對得上它真正寫的屬性。
76
+ "bg-image": ["bg-interaction-hover", "bg-interaction-active", "bg-interaction-selected", "bg-interaction-selected-hover", "bg-interaction-selected-active"]
72
77
  }
73
78
  }
74
79
  });