@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,400 @@
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
+ // ════════════════════════════════════════════════════════════════════════════
31
+ // (一) 判定
32
+ // ════════════════════════════════════════════════════════════════════════════
33
+
34
+ /** 按鍵發生時,焦點在「這一項」本身,還是這一項裡的某一個東西(小按鈕 / 連結 / 名片頭像…) */
35
+ export type RovingFocus = 'item' | 'control'
36
+
37
+ /** 樹才有的三個狀態;平面清單不傳 = 沒有「先展開」「回上一層」這兩步 */
38
+ export interface RovingTreeState {
39
+ hasChildren: boolean
40
+ expanded: boolean
41
+ hasParent: boolean
42
+ }
43
+
44
+ export interface RovingKeyInput {
45
+ /** KeyboardEvent.key */
46
+ key: string
47
+ focus: RovingFocus
48
+ /** 這一項可以用 → 走到的東西數(宿主以 `listRovingControls` 數出來) */
49
+ controlCount: number
50
+ /** focus === 'control' 時焦點在第幾個(0 起算);focus === 'item' 時不讀 */
51
+ controlIndex: number
52
+ /** 焦點所在的東西是文字輸入(輸入框 / 多行框 / 下拉 / 可編輯區):方向鍵、Home / End、Enter / 空白鍵都屬於它自己 */
53
+ controlIsTextEntry?: boolean
54
+ /** 「這一項」本身是文字輸入(只有 Command 的搜尋框):← / Home / End / 打字屬於插入點 */
55
+ itemIsTextEntry?: boolean
56
+ /** itemIsTextEntry 時:插入點已在字尾(且沒有選取範圍)。不在字尾時 → 要留給插入點移動 */
57
+ caretAtEnd?: boolean
58
+ /** 樹的狀態;平面清單不傳 */
59
+ tree?: RovingTreeState
60
+ /** 可以用 Cmd / Ctrl + Shift + 方向鍵重排(只有 draggable 的樹) */
61
+ reorderable?: boolean
62
+ /**
63
+ * 這一串有「打字跳位」(只有樹:keyboard-model-canonical.md 五條判準第 2 條「需要 tree 那一整套鍵盤模型(打字前導、…)」)。
64
+ * 焦點在項目上打一個可見字元 → 回 `typeahead`,由宿主找下一個名字以該字開頭的項目;平面清單(側欄 / 檔案清單)不傳 = 不處理。
65
+ * 出處:W3C APG Tree View 逐字 "Type a character: focus moves to the next node with a name that starts with the typed character."
66
+ * (<https://www.w3.org/WAI/ARIA/apg/patterns/treeview/>)。
67
+ */
68
+ typeahead?: boolean
69
+ /** 事件已被更外層的捕獲處理程式 preventDefault */
70
+ defaultPrevented: boolean
71
+ metaKey: boolean
72
+ ctrlKey: boolean
73
+ altKey: boolean
74
+ shiftKey: boolean
75
+ }
76
+
77
+ export type RovingReorderKey = 'ArrowUp' | 'ArrowDown' | 'ArrowLeft' | 'ArrowRight'
78
+ export type RovingMove = 'item-prev' | 'item-next' | 'item-first' | 'item-last'
79
+
80
+ export type RovingKeyAction =
81
+ /** 不處理 —— 交給瀏覽器、插入點或那個東西自己 */
82
+ | { type: 'none' }
83
+ /** 吃掉按鍵(preventDefault)但不動焦點 —— 例:已在最後一顆按鈕上再按 → */
84
+ | { type: 'consume' }
85
+ /** 換到上 / 下 / 第一 / 最後一項(焦點落在項目上;不繞回) */
86
+ | { type: RovingMove }
87
+ | { type: 'expand' }
88
+ | { type: 'collapse' }
89
+ | { type: 'parent' }
90
+ /** 把焦點放到這一項第 index 個東西 */
91
+ | { type: 'control'; index: number }
92
+ /** 焦點回到這一項本身 */
93
+ | { type: 'item' }
94
+ /** Tab / Shift+Tab:焦點先回到這一項(整串唯一的 Tab 停靠點),**不擋預設** —— 瀏覽器接著往下 / 往上走一站 = 一下離開這一串 */
95
+ | { type: 'leave' }
96
+ /** Enter / 空白鍵在項目上:這一項自己的預設動作(樹 = 選取;原生按鈕 = 瀏覽器自己點) */
97
+ | { type: 'activate-item' }
98
+ /** Enter / 空白鍵在項目裡的按鈕 / 連結上:那個東西自己的動作,**不是**「選這一項」 */
99
+ | { type: 'activate-control' }
100
+ /** 鍵盤重排(樹;修飾鍵層,與導覽鍵正交) */
101
+ | { type: 'reorder'; key: RovingReorderKey }
102
+ /** 打字跳位(只有 `typeahead: true` 的宿主):焦點在項目上打了一個可見字元,由宿主找下一個名字以它開頭的項目 */
103
+ | { type: 'typeahead'; char: string }
104
+
105
+ const REORDER_KEYS: readonly string[] = ['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight']
106
+ const isReorderKey = (key: string): key is RovingReorderKey => REORDER_KEYS.includes(key)
107
+
108
+ /**
109
+ * 按鍵表(規格 SSOT = keyboard-model-canonical.md「列上有小按鈕的一串」;每一格的來歷寫在該格旁):
110
+ *
111
+ * | 鍵 | 焦點在項目上 | 焦點在這一項的東西上 |
112
+ * |---|---|---|
113
+ * | ↑ ↓ | 上 / 下一項 | 回到上 / 下一項(X3,依 Adobe 方向鍵模式;選單鈕也一樣 —— X6 統一成側欄做法) |
114
+ * | Home End | 第一 / 最後一項 | 同左(X4 統一成側欄做法) |
115
+ * | → | 樹裡收著的資料夾先展開;否則進第一個東西;沒有就不動 | 下一個;最後一個停住 |
116
+ * | ← | 樹:展開的先收合、否則回上一層;平面清單:不動 | 上一個;第一個 → 回項目 |
117
+ * | Tab / Shift+Tab | 一下離開這一串 | 同左(X7:先回項目、再由瀏覽器往外走) |
118
+ * | Enter / 空白鍵 | 這一項自己的預設動作 | 那個東西自己的動作 |
119
+ * | 可見字元(只有 `typeahead` 宿主 = 樹)| 打字跳位:下一個名字以它開頭的項目(W3C APG Tree View) | 不處理(按鈕上打字沒有意義) |
120
+ */
121
+ export function resolveRovingKey(input: RovingKeyInput): RovingKeyAction {
122
+ const { key } = input
123
+ if (input.defaultPrevented) return { type: 'none' }
124
+
125
+ // 鍵盤重排:修飾鍵層,只從項目上發動(tree-view.spec.md「鍵盤重排」,行為不變)
126
+ if (input.focus === 'item' && input.reorderable && (input.metaKey || input.ctrlKey) && input.shiftKey && isReorderKey(key)) {
127
+ return { type: 'reorder', key }
128
+ }
129
+ // 帶 Cmd / Ctrl / Alt 的組合留給瀏覽器、輔助科技與按鈕自己(例:Alt+↓)。Shift 不在內:Shift+Tab 是導覽鍵。
130
+ if (input.metaKey || input.ctrlKey || input.altKey) return { type: 'none' }
131
+
132
+ // Tab / Shift+Tab:不論焦點在項目或項目裡的東西上,都是一下離開這一串(B9;X7)
133
+ if (key === 'Tab') return { type: 'leave' }
134
+
135
+ // ── 焦點在這一項裡的東西上 ──
136
+ if (input.focus === 'control') {
137
+ // 文字輸入:方向鍵移插入點、Enter / 空白鍵是打字,全部屬於它(原樹的 isButtonLike 例外,現全宿主一致)
138
+ if (input.controlIsTextEntry) return { type: 'none' }
139
+ switch (key) {
140
+ case 'ArrowRight':
141
+ // 已是最後一個 → 不動(W3C 樹狀表格:最右一格按 → 焦點不移動)
142
+ return input.controlIndex < input.controlCount - 1
143
+ ? { type: 'control', index: input.controlIndex + 1 }
144
+ : { type: 'consume' }
145
+ case 'ArrowLeft':
146
+ // 已是第一個 → 回到這一項(B9「← 回項目」)
147
+ return input.controlIndex > 0 ? { type: 'control', index: input.controlIndex - 1 } : { type: 'item' }
148
+ case 'ArrowDown':
149
+ // X6:選單鈕(aria-haspopup)也一樣換下一項 —— 開選單用 Enter / 空白鍵(同側欄帳號列,sidebar.spec.md)
150
+ return { type: 'item-next' }
151
+ case 'ArrowUp':
152
+ return { type: 'item-prev' }
153
+ case 'Home':
154
+ // X4:同側欄 / 檔案清單 —— 換到第一 / 最後一項
155
+ return { type: 'item-first' }
156
+ case 'End':
157
+ return { type: 'item-last' }
158
+ case 'Enter':
159
+ case ' ':
160
+ return { type: 'activate-control' }
161
+ default:
162
+ return { type: 'none' }
163
+ }
164
+ }
165
+
166
+ // ── 焦點在項目本身(Command:焦點在 home、反白在這一列)──
167
+ if (input.itemIsTextEntry) {
168
+ // 搜尋框:插入點已在字尾、沒有按 Shift(Shift+→ 是選取文字)時,→ 才進反白列的第一個東西(AI 推導,command.spec.md「A11y」)
169
+ if (key === 'ArrowRight') {
170
+ return !input.shiftKey && input.caretAtEnd !== false && input.controlCount > 0
171
+ ? { type: 'control', index: 0 }
172
+ : { type: 'none' }
173
+ }
174
+ if (key === 'ArrowDown') return { type: 'item-next' }
175
+ if (key === 'ArrowUp') return { type: 'item-prev' }
176
+ // ← / Home / End / Enter / 空白鍵 / 打字:屬於搜尋框與清單函式庫
177
+ return { type: 'none' }
178
+ }
179
+ switch (key) {
180
+ case 'ArrowDown':
181
+ return { type: 'item-next' }
182
+ case 'ArrowUp':
183
+ return { type: 'item-prev' }
184
+ case 'Home':
185
+ return { type: 'item-first' }
186
+ case 'End':
187
+ return { type: 'item-last' }
188
+ case 'ArrowRight': {
189
+ // 樹裡收著的資料夾 → 先展開(B9);已展開的資料夾或葉節點、平面清單 → 進第一個東西;沒有就不動
190
+ const tree = input.tree
191
+ if (tree && tree.hasChildren && !tree.expanded) return { type: 'expand' }
192
+ return input.controlCount > 0 ? { type: 'control', index: 0 } : { type: 'consume' }
193
+ }
194
+ case 'ArrowLeft': {
195
+ // 樹:展開的資料夾 → 收合;否則 → 回上一層;最外層不動。平面清單:不動(B9「← 在列上維持樹的語意」)
196
+ const tree = input.tree
197
+ if (tree && tree.hasChildren && tree.expanded) return { type: 'collapse' }
198
+ return tree && tree.hasParent ? { type: 'parent' } : { type: 'consume' }
199
+ }
200
+ case 'Enter':
201
+ case ' ':
202
+ return { type: 'activate-item' }
203
+ default:
204
+ // 打字跳位(樹):單一可見字元(空白鍵已在上面 = 選取;Shift+字母 也是一個可見字元,大小寫由宿主比對時折疊)
205
+ if (input.typeahead && key.length === 1 && key !== ' ') return { type: 'typeahead', char: key }
206
+ return { type: 'none' }
207
+ }
208
+ }
209
+
210
+ /**
211
+ * 上 / 下 / 第一 / 最後一項落在哪 —— **不繞回**(同 W3C grid / tree;X2)。
212
+ * `items` 依畫面順序、含走不到的項目(停用、看不見);`isNavigable` 決定哪些能當落點。
213
+ * 目前這一項本身走不到(例:樹裡用 ← 回到停用的上一層)時,仍從它的位置往前 / 往後找。
214
+ */
215
+ export function pickRovingTarget<T>(
216
+ move: RovingMove,
217
+ items: readonly T[],
218
+ current: T | null,
219
+ isNavigable: (item: T) => boolean = () => true,
220
+ ): T | null {
221
+ if (move === 'item-first') return items.find(isNavigable) ?? null
222
+ if (move === 'item-last') {
223
+ for (let i = items.length - 1; i >= 0; i--) if (isNavigable(items[i])) return items[i]
224
+ return null
225
+ }
226
+ const start = current == null ? -1 : items.indexOf(current)
227
+ if (start < 0) return null
228
+ const dir = move === 'item-next' ? 1 : -1
229
+ for (let i = start + dir; i >= 0 && i < items.length; i += dir) {
230
+ if (isNavigable(items[i])) return items[i]
231
+ }
232
+ return null
233
+ }
234
+
235
+ export interface RovingTabStopOptions<T> {
236
+ /** 上次焦點停過的那一項 */
237
+ remembered?: T | null
238
+ /** 「目前這一頁 / 選中」那一項(側欄 aria-current="page";樹 aria-selected) */
239
+ isCurrent?: (item: T) => boolean
240
+ /** 能不能當「目前」或「第一項」的落點(停用、看不見的不算) */
241
+ isNavigable?: (item: T) => boolean
242
+ /** 上次停過的那一項還能不能拿焦點(預設同 isNavigable;樹的停用列仍可經 ← 拿到焦點,傳 () => true) */
243
+ canHoldFocus?: (item: T) => boolean
244
+ }
245
+
246
+ /**
247
+ * 整串唯一的 Tab 停靠點(X1):上次停過的那一項 → 目前這一頁 / 選中那一項 → 第一個可用項。
248
+ * 出處:W3C「the element that had focus the last time the composite contained focus」+ 導覽樹範例
249
+ * 「focus always lands on the item representing the current page」(keyboard-model-canonical.md 同節引文)。
250
+ */
251
+ export function pickRovingTabStop<T>(items: readonly T[], options: RovingTabStopOptions<T> = {}): T | null {
252
+ const isNavigable = options.isNavigable ?? (() => true)
253
+ const canHoldFocus = options.canHoldFocus ?? isNavigable
254
+ const { remembered, isCurrent } = options
255
+ if (remembered != null && items.includes(remembered) && canHoldFocus(remembered)) return remembered
256
+ const current = isCurrent ? items.find((item) => isNavigable(item) && isCurrent(item)) : undefined
257
+ return current ?? items.find(isNavigable) ?? null
258
+ }
259
+
260
+ // ════════════════════════════════════════════════════════════════════════════
261
+ // (二) 宿主共用的 DOM 小工具
262
+ // ════════════════════════════════════════════════════════════════════════════
263
+
264
+ /**
265
+ * 項目裡「可以走到的東西」的候選(再經 `isRovingControlReachable` 過濾)。
266
+ * 2026-09-26 之前四份各寫一份、各差一點(樹多認 contenteditable、側欄只認按鈕類),合成這一份;
267
+ * 側欄另有自己的範圍(只認動作鈕的兩個出口),以參數傳入。
268
+ */
269
+ export const ROVING_CONTROL_SELECTOR =
270
+ 'a[href], button, input, select, textarea, [contenteditable]:not([contenteditable="false"]), [tabindex]'
271
+
272
+ /** 把候選 selector 限定在某個範圍裡(`[cmdk-item] a[href], [cmdk-item] button, …`)。不用 `:is()`:舊版 jsdom 不認得。 */
273
+ export function scopeRovingSelector(scope: string, selector: string = ROVING_CONTROL_SELECTOR): string {
274
+ return selector.split(',').map((part) => `${scope} ${part.trim()}`).join(', ')
275
+ }
276
+
277
+ /**
278
+ * 文字輸入(方向鍵、Home / End、打字屬於它自己)。沒有插入點的 input type 不算(同 React Aria `nonTextInputTypes`)。
279
+ * 全 DS 只有這一份:清單鍵盤(本檔)、反白來歷(hooks/use-input-modality.ts)、下拉的空白鍵(SelectMenu/select-menu-keyboard.ts)
280
+ * 都讀它(2026-09-26 之前各寫一份)。
281
+ */
282
+ const NON_TEXT_INPUT_TYPES = new Set(['checkbox', 'radio', 'range', 'color', 'file', 'image', 'button', 'submit', 'reset', 'hidden'])
283
+ export function isTextEntryElement(el: Element): boolean {
284
+ if (el.tagName === 'TEXTAREA' || el.tagName === 'SELECT') return true
285
+ if (el.tagName === 'INPUT') return !NON_TEXT_INPUT_TYPES.has((el as HTMLInputElement).type)
286
+ return (el as HTMLElement).isContentEditable === true
287
+ }
288
+
289
+ /**
290
+ * 此刻走得到:沒有停用、不在 inert / aria-hidden 裡(例:多選列的勾選框是 inert + aria-hidden 的純視覺)、
291
+ * 畫得出來(display:none 不算;opacity 0 的「滑過才出現」按鈕算 —— 鍵盤走到時它會浮出)。
292
+ */
293
+ export function isRovingControlReachable(el: HTMLElement): boolean {
294
+ return (
295
+ !(el as HTMLButtonElement).disabled &&
296
+ !el.closest('[inert], [aria-hidden="true"]') &&
297
+ el.getClientRects().length > 0
298
+ )
299
+ }
300
+
301
+ /** 某一項裡可以走到的東西,依畫面(文件)順序。 */
302
+ export function listRovingControls(root: Element, selector: string = ROVING_CONTROL_SELECTOR): HTMLElement[] {
303
+ return Array.from(root.querySelectorAll<HTMLElement>(selector)).filter(isRovingControlReachable)
304
+ }
305
+
306
+ /** 只在值不同時才寫:同值 setAttribute 也會產生 mutation record,會讓宿主的 MutationObserver 自我觸發。 */
307
+ export function setRovingTabIndex(el: HTMLElement, value: 0 | -1): void {
308
+ if (el.getAttribute('tabindex') !== String(value)) el.setAttribute('tabindex', String(value))
309
+ }
310
+
311
+ /**
312
+ * 項目裡的東西一律不在 Tab 路上(-1,→ 才進得去;別項與本項的都一樣)。
313
+ * 用 DOM 設而不是逐一傳 prop:consumer 的插槽(inlineActionsSlot / description / 名片頭像)是任意 ReactNode,拿不到它們的 props。
314
+ */
315
+ export function removeRovingControlsFromTabOrder(elements: Iterable<HTMLElement>): void {
316
+ for (const el of elements) setRovingTabIndex(el, -1)
317
+ }
318
+
319
+ /** 真焦點宿主(側欄 / 檔案清單 / 樹)執行判定結果時要的東西 */
320
+ export interface RovingDomHost {
321
+ event: { preventDefault(): void }
322
+ /** 目前這一項(整串唯一的 Tab 停靠點就在這種元素上) */
323
+ item: HTMLElement
324
+ /** 這一項可以走到的東西(已過濾,`listRovingControls`) */
325
+ controls: readonly HTMLElement[]
326
+ /** 整串的項目,依畫面順序(含走不到的;由 isNavigable 篩) */
327
+ items: readonly HTMLElement[]
328
+ isNavigable?: (item: HTMLElement) => boolean
329
+ /** 樹傳 { preventScroll: true }(捲動由列自己的 scrollIntoView 以 block:nearest 處理) */
330
+ focusOptions?: FocusOptions
331
+ /** 樹才有:展開 / 收合 / 回上一層 */
332
+ onExpand?: () => void
333
+ onCollapse?: () => void
334
+ onParent?: () => void
335
+ /** 項目本身的預設動作要由宿主執行時才傳(樹 = 選取);不傳 = 項目是原生按鈕,交給瀏覽器 */
336
+ onActivateItem?: () => void
337
+ /** 打字跳位(只有 `typeahead: true` 的宿主):宿主自己累積字串、找項目、搬焦點 */
338
+ onTypeahead?: (char: string) => void
339
+ }
340
+
341
+ /**
342
+ * 真焦點宿主的執行器:判定之外只剩「焦點怎麼搬、要不要擋預設」,三個宿主完全相同,所以也只寫一份。
343
+ * `reorder` 不在這裡(樹自己處理:要看是否正在用滑鼠拖曳)。
344
+ */
345
+ export function applyRovingAction(action: RovingKeyAction, host: RovingDomHost): void {
346
+ const { event, item, controls, focusOptions } = host
347
+ switch (action.type) {
348
+ case 'none':
349
+ case 'reorder':
350
+ case 'activate-control':
351
+ // 按鈕 / 連結的 Enter、空白鍵是瀏覽器原生行為;這裡不擋也不代點
352
+ return
353
+ case 'leave':
354
+ // 焦點在項目裡的東西上時先放回這一項(整串唯一那顆 0),**不擋預設**:瀏覽器接著從這一項往下 / 往上走 = 一下離開。
355
+ // 往前若不先放回,Shift+Tab 會先落回本項(X7 修的就是這個);往後在焦點鎖(手機抽屜 = Radix FocusScope)裡,
356
+ // 鎖判斷「是不是最後一顆」看的是 tabindex≥0 的元素,-1 的按鈕永遠不是它 → 不先放回就繞不回開頭
357
+ //(@radix-ui/react-focus-scope@1.1.7 dist/index.mjs:106-121)。焦點已在這一項上時 focus() 不做任何事。
358
+ item.focus(focusOptions)
359
+ return
360
+ case 'activate-item':
361
+ if (!host.onActivateItem) return
362
+ event.preventDefault()
363
+ host.onActivateItem()
364
+ return
365
+ case 'typeahead':
366
+ // 判定只在 typeahead 宿主才會回這格;擋預設是為了不讓 Firefox「輸入時搜尋」之類的瀏覽器功能吃掉字元
367
+ if (!host.onTypeahead) return
368
+ event.preventDefault()
369
+ host.onTypeahead(action.char)
370
+ return
371
+ default:
372
+ break
373
+ }
374
+ event.preventDefault()
375
+ switch (action.type) {
376
+ case 'consume':
377
+ return
378
+ case 'item-prev':
379
+ case 'item-next':
380
+ case 'item-first':
381
+ case 'item-last':
382
+ pickRovingTarget(action.type, host.items, item, host.isNavigable)?.focus(focusOptions)
383
+ return
384
+ case 'control':
385
+ controls[action.index]?.focus(focusOptions)
386
+ return
387
+ case 'item':
388
+ item.focus(focusOptions)
389
+ return
390
+ case 'expand':
391
+ host.onExpand?.()
392
+ return
393
+ case 'collapse':
394
+ host.onCollapse?.()
395
+ return
396
+ case 'parent':
397
+ host.onParent?.()
398
+ return
399
+ }
400
+ }
package/src/lib/utils.ts CHANGED
@@ -76,6 +76,11 @@ const twMerge = extendTailwindMerge({
76
76
  // Custom opacity utility(對應 tokens/opacity/opacity.css `@utility opacity-disabled`)。
77
77
  // 不註冊 group → tailwind-merge 用 default opacity-N heuristic,可能誤判 class 衝突。
78
78
  'opacity': ['opacity-disabled'],
79
+ // 「底」的滑過 / 按住疊層(對應 tokens/color/semantic.css `@utility bg-interaction-hover / -active`;
80
+ // color.spec.md「Hover 換色配對總則」,待辦總帳 B8)。它們寫的是 background-image,不是底色 ——
81
+ // 不註冊的話 tailwind-merge 把 `bg-*` 一律猜成 bg-color,`cn('bg-surface', 'bg-interaction-hover')`
82
+ // 會把 bg-surface 刪掉,底色整個消失(2026-09-25 以 tailwind-merge 3.5 實測)。登記在 bg-image 才對得上它真正寫的屬性。
83
+ 'bg-image': ['bg-interaction-hover', 'bg-interaction-active', 'bg-interaction-selected', 'bg-interaction-selected-hover', 'bg-interaction-selected-active'],
79
84
  },
80
85
  },
81
86
  })
@@ -483,6 +483,7 @@ Icon 的目的是幫助辨識,不是視覺對稱。
483
483
 
484
484
  > 本節由 `scripts/add-reciprocal-pointers.mjs` 自動維護,列出在 SSOT 語境下指向本 spec 的其他 spec。若要手動補充,寫在本節之前。
485
485
 
486
+ - `alert.spec.md`
486
487
  - `bulk-action-bar.spec.md`
487
488
  - `button.spec.md`
488
489
  - `calendar.spec.md`
@@ -490,3 +491,4 @@ Icon 的目的是幫助辨識,不是視覺對稱。
490
491
  - `file-viewer.spec.md`
491
492
  - `popover.spec.md`
492
493
  - `separator.spec.md`
494
+ - `toast.spec.md`
@@ -69,15 +69,19 @@ Inline action icon 色彩規則 **依 host 是否有自帶色彩分兩支**。
69
69
 
70
70
  ### 尺寸對照
71
71
 
72
- | 宿主 | Icon 視覺 | Hover 背景 | 圓角 | 排版佔位 |
73
- |---|---|---|---|---|
74
- | Tag sm (20px) | 16px | 18px | rounded-md | 16px |
75
- | Tag md/lg (24px) | 16px | 18px | rounded-md | 16px |
76
- | Field sm/md | 16px | 18px | rounded-md | 16px |
77
- | Field lg | 20px | 22px | rounded-md | 20px |
78
- | TreeItem sm/md | 16px | 18px | rounded-md | 16px |
79
- | TreeItem lg | 20px | 22px | rounded-md | 20px |
80
- | Panel list row(visibility / sort / filter panels)| 16px | 18px | rounded-md | 16px |
72
+ | 宿主 | Icon 視覺 | Hover 背景 | 圓角 | 可點範圍 | 排版佔位 |
73
+ |---|---|---|---|---|---|
74
+ | Tag sm (20px) | 16px | 18px | rounded-md | 18px | 16px |
75
+ | Tag md/lg (24px) | 16px | 18px | rounded-md | 18px | 16px |
76
+ | Field sm/md | 16px | 18px | rounded-md | 18px | 16px |
77
+ | Field lg | 20px | 22px | rounded-md | 22px | 20px |
78
+ | TreeItem sm/md | 16px | 18px | rounded-md | 18px | 16px |
79
+ | TreeItem lg | 20px | 22px | rounded-md | 22px | 20px |
80
+ | Panel list row(visibility / sort / filter panels)| 16px | 18px | rounded-md | 18px | 16px |
81
+
82
+ **「可點範圍」欄恆等於「Hover 背景」欄**(user 2026-09-24 逐字:「重點是要讓 inline action 的可點擊範圍跟其 hover 底色一樣吧?都是 18*18」)——
83
+ 那塊底色就是使用者看得到的形狀,看得到的每一點都要點得到。跨元件規則與唯一例外的 owner = `ds-canonical/references/hit-area-canonical.md`;
84
+ 本表只放本元件的值,不重述規則。**排版佔位不變,仍是 icon 尺寸**(16 / 20):多出來的 2px 靠溢出(absolute / negative margin)承擔,不吃版位 —— 見上方「視覺規則」3 與下方 Consumer wrapping rule。
81
85
 
82
86
  **Consumer wrapping rule**:當 consumer 包 `ItemInlineActionButton` 在 reserve slot(如 hover-reveal opacity wrapper),**reserve 寬度 = 排版佔位**(sm/md=16,lg=20),**禁止自訂為 24 / 28**(過大會在 cell 留 phantom space)。歷史:DataTable header reserve 24 → 16 修正(2026-04-29)。
83
87
 
@@ -87,7 +91,7 @@ Inline action icon 色彩規則 **依 host 是否有自帶色彩分兩支**。
87
91
 
88
92
  - **間距**:`gap-2`(8px)——跟 fieldWrapperStyles 的元素間距一致(Select 的 clear X 和 ChevronDown 就是 gap-2)
89
93
  - **對齊**:全部垂直置中在同一行(`flex items-center`)
90
- - **出現時機**:全部一起出現(TreeItem 的 hover-reveal 是同時淡入所有 action,不逐個)
94
+ - **出現時機**:全部一起出現(TreeItem 的 hover-reveal 是同時出現所有 action,不逐個);**瞬間出現、不淡入**(owner = `tokens/motion/motion.spec.md`「hover 回饋不做過渡」,2026-09-26 延伸到滑過才出現的按鈕;實作 = `item-anatomy.tsx` 的 `SUFFIX_HOVER_REVEAL_BY_GROUP`)
91
95
 
92
96
  ### API 設計
93
97
 
@@ -191,7 +195,7 @@ Row action 的 affordance 是「次要功能」,不是 primary CTA。Button chro
191
195
  ```
192
196
 
193
197
  **chrome corner action group(Alert / Toast / Dialog / Popover header corner)** 屬 **Button family**(action group region,不是 inline action)。Canonical 不在本 spec — 詳:
194
- - 尺寸:`patterns/overlay-surface/overlay-surface.spec.md`「Chrome dismiss size canonical」(overlay sm + v5 trick / banner xs explicit)
198
+ - 尺寸:`patterns/overlay-surface/overlay-chrome-sizing.spec.md`「Chrome dismiss size canonical」(overlay sm + v5 trick / banner xs explicit)
195
199
  - variant / divider:`patterns/action-bar/action-bar.spec.md`(corner action group)
196
200
 
197
201
  ### 邊界案例
@@ -317,7 +321,7 @@ hook `check_story_invariants.sh` R1(原 `check_story_anatomy.sh` 規則 B,已 fo
317
321
  - `data-table.spec.md`
318
322
  - `field-controls.spec.md`
319
323
  - `item-anatomy.spec.md`
320
- - `overlay-surface.spec.md`
324
+ - `overlay-chrome-sizing.spec.md`
321
325
  - `popover.spec.md`
322
326
  - `sheet.spec.md`
323
327
  - `sidebar.spec.md`
@@ -190,6 +190,96 @@ rg 'grid-cols-\[[0-9]+px_1fr\]' packages/design-system/src -g '*.tsx'
190
190
  - **Row message(訊息列,2026-09-08 user 拍板)**: 用 `MenuItem message={true}` 模式——選單裡「不是選項的列」(沒有結果 / 沒有選項 / 載入中)。與 Row header 同族:`text-fg-muted pointer-events-none` + 與 items **完全相同**的 row geometry;差別是**一般字重**、內容**置中**、可帶前綴槽(列圖示尺寸的 `CircularProgress` 等)。必住在 group 裡(見「Group auto-separation」);一列訊息與一列選項等高,不撐最小高度。owner `components/SelectMenu/select-menu.spec.md`「Empty state」;樣式 `components/Menu/menu-item.spec.md`「Message row(訊息列)」
191
191
  - **可收合 section header 組合 canonical**(2026-07-08 R3-7 拍板 + 2026-07-10 codify 進 DS;**組合非元件** — SectionHeader 留產品客製,但 layout 全消費本 canonical):(1) chevron = **title 後的 suffix inline action**(非 prefix、非獨立按鈕群);(2) 可選 description 與 title 間距用 `--item-gap-label-desc-*` token;(3) endSlot(操作鈕)只有 title 一行時垂直置中;(4) 同構標題列 ≥ 2 份必抽共用元件(WM `SectionHeader.tsx` 錨例 — Description / Attachments / Child work items 三份同構收斂)。手刻簽名(Chevron + justify-between + 可點且無共用元件)由 consumer 防線攔(escape `@section-header-ok:`)。
192
192
 
193
+ ### 整列可點時,誰當那顆控件(2026-09-24 立;本節是全家族唯一 owner,消費者禁自行發明)
194
+
195
+ ### 這一節在回答什麼(先講目的)
196
+
197
+ **目的只有一個:拿到一份「列」的設計需求時,不必看任何既有元件,就能推出它該長什麼樣、怎麼操作。**
198
+
199
+ 它要防的失敗是具體的:DS 裡已經有 `SidebarMenuButton`(列自己是一顆按鈕)與 `FileItem`(列不是按鈕、鍵盤由一顆隱形控件承接)兩種做法。沒有這一節,下一個人建新列元件時**只能挑一個看起來像的來抄**,而抄錯的那一半會在無障礙與鍵盤上靜默壞掉。
200
+
201
+ ### ⛔ 2026-09-24 重寫:先前這一節是從實作切入的
202
+
203
+ 原本的四題,第二題是「動作鈕排到外面後,剩下的內容裝不裝得進一顆 `<button>`?」——**那不是設計問題,那是 HTML 的限制。** user 逐字:「我要的是從設計面去定義這些,而不是導果為因,我他媽今天講了一百次了」(commit `c8821266` 訊息第 3 行;2026-09-27 補回先前被截掉的後半句)。把 markup 的限制寫成判準,等於用「能怎麼蓋」回答「這是什麼」。下面改成先問設計,實作是後果。
204
+
205
+ ---
206
+
207
+ ### 第一層(設計):這一串是**一組去處**,還是**一組東西**?
208
+
209
+ 這是最根本的一刀,而且**不是本檔歸納的,是世界級各家自己的分類**:
210
+
211
+ | 出處 | 逐字 |
212
+ |---|---|
213
+ | **Ant Design** 原始碼 frontmatter(釘 6.6.5) | [`components/menu/index.en-US.md#L3-L5`](https://github.com/ant-design/ant-design/blob/6.6.5/components/menu/index.en-US.md#L3-L5):`group: Navigation`,描述 "A versatile menu for **navigation**"<br>[`components/tree/index.en-US.md#L3-L5`](https://github.com/ant-design/ant-design/blob/6.6.5/components/tree/index.en-US.md#L3-L5):`group: **Data Display**",描述 "Multiple-level structure list" |
214
+ | **Primer** `content/ui-patterns/navigation.mdx`(釘 2026-09-27 抓到的 main HEAD) | "A tree view **isn't semantically a form of navigation**: it's for **activating an option from a hierarchical list**."([`navigation.mdx#L150`](https://github.com/primer/design/blob/87f799f202ec95df15c99f473c9c0c803da8e6b3/content/ui-patterns/navigation.mdx#L150)) |
215
+ | **Carbon** tree-view usage「When not to use」(釘 carbon-website `d8783ad2`) | "**As the primary navigation in a product's UI.** Instead, use the UI Shell left panel for product navigation."([`tree-view/usage.mdx#L92`](https://github.com/carbon-design-system/carbon-website/blob/d8783ad2ae3b5e59c58f58311491f8a2c4e62631/src/pages/components/tree-view/usage.mdx#L92))<br>⚠️ **這句的範圍是「主導覽」,不是「側邊欄」** —— 跟 Primer 那句 "global sidebar navigation" 同一層級。它支持的是「樹不是導覽」這個分類,**不是「側邊欄不能有樹」**。Notion 的側邊欄下半就是樹。2026-09-24 我先前就是把 Primer 同類的句子裸引而讀成禁令,user 逐字戳破;這裡同樣不得裸引 —— 判準住在 `ds-canonical/rules/meta-patterns.md` M22 的子規則「引用『不要拿 X 當 Y』必須帶 Y 的範圍」,本檔不重述。 |
216
+
217
+ **兩類東西,不是同一類的兩種做法:**
218
+
219
+ - **一組去處(導覽)**:使用者對它做的唯一的事是**去那裡**。項目本身沒有被操作的價值,只有被抵達的價值。它由產品定義,使用者不能改動它。
220
+ - **一組東西(資料)**:項目本身就是**被操作的對象**。使用者會挑它、對它做事、看它的狀態。它通常由使用者產生,而且能被改動。
221
+
222
+ **判斷句**:問「使用者對這一項**做**什麼?」——答「去某個地方」= 去處;答「挑它、然後對它做某件事」= 東西。
223
+
224
+ ### 第二層(只對「一組東西」問):使用者會不會把這一整串當成**一個東西**來操作?
225
+
226
+ - **會** —— 會一次挑好幾個一起處理、會在裡面搬移、項目多到需要快速跳。→ 它在使用者心中就是**一個東西**,所以鍵盤上也該是一個東西:整串一個停靠點,進去之後用方向鍵。
227
+ - **不會** —— 每一筆各自處理,彼此沒有「一起」的概念。→ 每一筆各自獨立,預設各自一個停靠點;但**列上帶小按鈕的一串**(側欄、FileUpload 內建檔案清單…)因停靠點太多另收成一站 —— 那是第二個理由,規則與適用範圍只住 `keyboard-model-canonical.md`「列上有小按鈕的一串」(2026-09-25 待辦總帳 B9)。
228
+
229
+ **這一層的完整判準(五條)與一手依據住在 `ds-canonical/references/keyboard-model-canonical.md`**,本節不重述。那五條講的是同一件事的可操作版本:節點會不會被選取並施加動作、需不需要那一整套鍵盤、深度有沒有上界、內容是不是使用者產生且可改動、啟動會不會換 URL。
230
+
231
+ ### 第三層(對每一列問):這一列除了主要動作,還有沒有別的動作?
232
+
233
+ - **沒有** —— 整列就是一個目標。
234
+ - **有** —— 那些動作**各自是獨立的目標**,不能被主要動作吞掉。
235
+ 這就是為什麼「按刪除不該連帶把檔案打開」不是體貼,是必須。
236
+
237
+ ---
238
+
239
+ ### 實作是**後果**,不是判準
240
+
241
+ 上面三層一旦答完,markup 只剩一種寫法 —— 因為 HTML 與 ARIA 把其餘的路堵死了。
242
+ **這一段列的是後果,不要拿它來做設計決定。**
243
+
244
+ | 設計面的事實 | 被規範堵死的後果 | 規範依據 |
245
+ |---|---|---|
246
+ | 這一列裡有**必須被唸出來的狀態**(進度、頭像、多段說明) | 列**不能**是一顆按鈕 —— 按鈕裡的東西會被輔助科技當成裝飾唸掉,進度百分比會消失 | ARIA `button` 的 Children Presentational:"The DOM descendants are presentational. User agents SHOULD NOT expose descendants of this element through the platform accessibility API." |
247
+ | 這一列裡有**可以聚焦的子元素** | 列**不能**是一顆按鈕 —— 直接踩 content model 明文 | WHATWG HTML `<button>`:"Phrasing content, but there must be no interactive content descendant and **no descendant with the `tabindex` attribute specified**." |
248
+ | 這一列是「一個東西的一部分」(第二層答「會」) | 列拿 `treeitem` / `row` / `option` / `gridcell`,**整組只有一個鍵盤停靠點**,由容器管理焦點(列上 roving tabindex,或容器 `aria-activedescendant`) | WAI-ARIA §4.3.1:"Authors MUST manage focus on the following container roles";`composite`:"SHOULD ensure that a composite widget exists as a single navigation stop" |
249
+ | 列不是按鈕、又必須可鍵盤操作 | 列內鋪一顆**覆蓋整列的原生控件**;它不可見,所以焦點框改畫在列上 | 焦點框畫在別的元素上的規則 → `ds-canonical/references/focus-canonical.md` |
250
+
251
+ **先前那一版把上表第一、二列寫成「第 2 題」,等於用 HTML 的限制當設計判準。** 已撤回。
252
+
253
+ ### 走一遍:當初引發這一連串問題的兩個元件
254
+
255
+ user 2026-09-24 原始提問:「所有可以被點擊且其中提供按鈕的 list item 都會有一樣問題?」
256
+ 「就結果是一樣的,但為何設計方式是不一樣的?是否是合理的?未來遇到這種有多 action 且自身可以點擊的
257
+ list item 元件都是要用類似的處理方式?」
258
+
259
+ | | `SidebarMenuButton` | `FileItem` | `TreeItem` |
260
+ |---|---|---|---|
261
+ | **第一層:去處還是東西?** | **一組去處** —— 每條通往不同頁面,使用者對它做的唯一的事是「去那裡」 | **一組東西**,但不是一個整體 —— 每個檔案是被操作的對象(有狀態、有動作),但使用者不會把它們當成一個東西一起處理 | **一組東西,而且是一個整體** —— 使用者會一次挑好幾個、在裡面搬移、項目多到需要快速跳 |
262
+ | **列裡有沒有必須被念出來的狀態 / 可聚焦子元素?**(實作後果) | **沒有** —— 只有圖示與文字,所以列可以是一顆按鈕 | **有** —— 進度條(自帶 `role="progressbar"`)、頭像、可聚焦的 hover 按鈕,所以列**不能**是按鈕 | 不適用 —— 第一層已經決定它是一個整體,列拿 `row`(樹狀表格;2026-09-25 前是 `treeitem`) |
263
+ | **誰承接鍵盤?**(實作後果) | **列本身**(它就是那顆 `<button>`)| **列內一顆覆蓋整列的透明控件**,焦點框畫在列上 | **列本身**(整棵樹同一時間只有一列 `tabIndex=0`;2026-09-25 前是容器 + `aria-activedescendant`,待辦總帳 B9) |
264
+ | **一串幾個 Tab 停靠點?**(不歸本節,住 `ds-canonical/references/keyboard-model-canonical.md`「列上有小按鈕的一串」) | 一串一站 + 方向鍵(B9) | 單獨成串:每列一站、每顆動作鈕各一站;在 FileUpload 內建清單(有 `onRemove`):一串一站 + 方向鍵(B9) | 一棵樹一站 + 方向鍵 |
265
+
266
+ **所以對原始問題的答案是:`SidebarMenuButton` 與 `FileItem` 設計上不一樣的只有「焦點停在哪個元素上」,而那不是選擇,是 HTML content model 逼出來的**:FileItem 的列裡有進度條與可聚焦按鈕,包進 `<button>` 會讓輔助科技讀不到(children-presentational),還直接踩 content model 明文禁止的 `tabindex` 那一句。與 `TreeItem` 的差別在第一層(樹是一個整體),不是「列裡裝了什麼」。
267
+
268
+ **一串有幾個 Tab 停靠點是另一件事,不由本節決定**:2026-09-24 從頁首連按 Tab 實測時,FileItem 與側欄都是「每列一站 + 每顆動作鈕各一站」、樹整棵一站(量測原文見本檔 git 歷史 `16c8e2f5` 版);2026-09-25 user 選路線乙之後,側欄與 FileUpload 內建檔案清單改成一串一站 + 方向鍵,規則與適用範圍只住 `keyboard-model-canonical.md`「列上有小按鈕的一串」(待辦總帳 B9)。
269
+
270
+ ### 這條程序涵蓋所有情況嗎(逐枝窮舉)
271
+
272
+ | 第一層 | 第二層 | 列裡有沒有必須被念出來的狀態 / 可聚焦子元素 | 結果 | 本 DS 實例 |
273
+ |---|---|---|---|---|
274
+ | **一組去處** | 不適用 | 沒有 | 列 = `<button>` / `<a>`,動作鈕是絕對定位的同層兄弟 | `SidebarMenuButton`、`MenuItem` |
275
+ | **一組去處** | 不適用 | 有 | 列 = 非互動容器 + 覆蓋整列的透明控件,焦點框畫在列上 | DS 內目前無實例(去處很少帶狀態) |
276
+ | **一組東西**,不是整體 | — | 沒有 | 列 = `<button>` | `MenuItem`(當它裝的是選項而非去處)|
277
+ | **一組東西**,不是整體 | — | 有 | 列 = 非互動容器 + 覆蓋控件,焦點框畫在列上 | **`FileItem`** |
278
+ | **一組東西,而且是整體** | — | — | 列拿 `treeitem` / `row` / `option` / `gridcell`,整組一個停靠點 + 方向鍵(列上 roving tabindex 或容器 `aria-activedescendant`) | `TreeItem`(`row`,2026-09-25 起)、`TimePicker` 的欄、`DataTable` 的格、`Calendar` 的格 |
279
+
280
+ **窮盡**:第一層三分(去處 / 東西不成整體 / 東西成整體)× 列內容二分,而「成整體」那一枝的列內容不影響結論(角色已由第一層決定)。其餘枝「一串幾站」照 `keyboard-model-canonical.md`(預設每列一站;列名在「列上有小按鈕的一串」的清單收成一站,B9)。
281
+ **新元件若覺得自己不屬於任何一枝,那是第一層沒答清楚,不是需要新增分枝。**第一層答不出來時的正解是回去問「使用者對這一項**做**什麼」,不是去看別的元件怎麼寫。
282
+
193
283
  ### Prefix 垂直對齊:`items-start` + `h-[1lh]` wrapper(**永遠這樣**,不要做例外)
194
284
 
195
285
  所有 row primitives 的 outer flex 用 **`items-start`** + prefix(icon / avatar / checkbox / indicator)包在 **`h-[1lh] shrink-0 flex items-center`** 容器。這是**底層規則,不可跳過**。
@@ -373,8 +463,8 @@ Consumer 不需手動插 Separator——把同類 items 包進 Group,自動分
373
463
  | **MenuItem / Select / DropdownMenuItem / SidebarMenuButton / TreeItem** | permanent transparent(radius 只在 state 可見) | **0 gap 合法** | state 稀疏不創造永久相連 |
374
464
  | **SelectionItem**(Radio / Checkbox row) | permanent transparent(code 驗證 `flex items-start gap-2`,無 bg/border/radius) | **0 gap 合法** | 選中靠 control 視覺非 row bg |
375
465
  | **DataTable row** | flush + border-b 分隔線 | **0 gap 合法** | border-b 分隔線型 affordance |
376
- | **FileItem compact + progress bar**(Type A upload manager) | flush + 底部 progress bar 分隔線 | **0 gap 合法** | 同 DataTable;progress bar 分隔線型 affordance(user 2026-04-22 確認) |
377
- | **FileItem rich**(永遠 card) | `border + bg-surface + rounded-md + inset` → **standalone card** | **必 gap ≥ 8px** | Card 邊框融合失去 identity |
466
+ | **FileItem `surface=upload-manager`**(rich + compact;Type A upload manager) | flush 透明列(compact 另有底部 progress bar 分隔線);可點的列滑過時整列鋪到面板邊 | **0 gap 合法** | 同 DataTable;progress bar 分隔線型 affordance(user 2026-04-22 確認);列間 ink 距由兩列各自上下 `tight/2` 相加(`components/FileItem/file-item.spec.md`「List wrapper canonical」,2026-09-25 B12) |
467
+ | **FileItem rich `surface=form`**(card) | `border + bg-surface + rounded-md + inset` → **standalone card** | **必 gap ≥ 8px** | Card 邊框融合失去 identity |
378
468
  | **FileItem compact + bg-secondary**(Type B form attachment 靜態) | `bg-secondary + rounded-md + inset` → **standalone pill** | **必 gap ≥ 4px** | bg 塊融合消失 item 邊界 |
379
469
  | **StepItem / Timeline item** | permanent transparent item + connector line | **0 gap 合法** | connector 提供連接(分隔線型 affordance 的連接版) |
380
470
 
@@ -936,7 +1026,7 @@ World-class benchmark(6 家 DS):
936
1026
 
937
1027
  | 屬性 | 值 | 原因 |
938
1028
  |---|---|---|
939
- | padding-y | 12px (`py-3`) | 舒適的列表行高,觸控友好 |
1029
+ | padding-y | 12px (`py-3`) | 頁面級列表的寬鬆行高——同表的閱讀模式(頁面內停留閱讀)與較大的 avatar / thumbnail 要對應的呼吸量(2026-09-24 撤回原文的「觸控友好」:本 DS 以滑鼠指標的精度為前提,尺寸不以觸控門檻推導,見 `ds-canonical/references/hit-area-canonical.md`)|
940
1030
  | padding-x | 16px (`px-4`) | 頁面內容的標準水平間距 |
941
1031
  | prefix ↔ content gap | 12px (`gap-3`) | 較寬鬆,適合較大的 avatar / thumbnail |
942
1032
  | 閱讀模式 | 閱讀模式 | 頁面內停留閱讀 |
@@ -1066,9 +1156,9 @@ Selection control(Dropdown / Menu / List / SegmentedControl / Chip)的 item 視
1066
1156
 
1067
1157
  ---
1068
1158
 
1069
- ## Recipe + 自我檢查 → `.claude/references/item-anatomy-recipe.md`
1159
+ ## Recipe + 自我檢查 → `ds-canonical/references/item-anatomy-recipe.md`
1070
1160
 
1071
- 建立新 row primitive 的 7 步 workflow + audit grep guard + SidebarMenuButton 獨立實作風險,搬到 `.claude/references/item-anatomy-recipe.md`(spec 2026-04-24 prune 瘦身 — Recipe 是 workflow 類,搬 reference 減 spec 體積)。
1161
+ 建立新 row primitive 的 7 步 workflow + audit grep guard + SidebarMenuButton 獨立實作風險,搬到 `ds-canonical/references/item-anatomy-recipe.md`(canonical owner,不指產生檢視;spec 2026-04-24 prune 瘦身 — Recipe 是 workflow 類,搬 reference 減 spec 體積)。
1072
1162
 
1073
1163
  本節僅保留 pointer。建 row primitive → 讀 reference + 本 spec 的結構 / padding / 24px 閾值 / slot 規範 / 選擇狀態視覺 / Inline Action spec 等 canonical。
1074
1164
 
@@ -1077,9 +1167,9 @@ Selection control(Dropdown / Menu / List / SegmentedControl / Chip)的 item 視
1077
1167
  > 本節由 `scripts/add-reciprocal-pointers.mjs` 自動維護,列出在 SSOT 語境下指向本 spec 的其他 spec。若要手動補充,寫在本節之前。
1078
1168
 
1079
1169
  - `alert.spec.md`
1170
+ - `button.spec.md`
1080
1171
  - `checkbox.spec.md`
1081
1172
  - `color.spec.md`
1082
- - `command.spec.md`
1083
1173
  - `dropdown-menu.spec.md`
1084
1174
  - `element-anatomy.spec.md`
1085
1175
  - `empty.spec.md`
@@ -1091,10 +1181,12 @@ Selection control(Dropdown / Menu / List / SegmentedControl / Chip)的 item 視
1091
1181
  - `layoutSpace.spec.md`
1092
1182
  - `menu-item.spec.md`
1093
1183
  - `notice.spec.md`
1184
+ - `overlay-surface.spec.md`
1094
1185
  - `select-menu.spec.md`
1095
1186
  - `select.spec.md`
1096
1187
  - `selection-item.spec.md`
1097
1188
  - `sidebar.spec.md`
1189
+ - `steps-state-visuals.spec.md`
1098
1190
  - `steps.spec.md`
1099
1191
  - `tabs.spec.md`
1100
1192
  - `time-picker.spec.md`