@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
@@ -33,7 +33,8 @@ function getMeasureCtx() {
33
33
  export const TAG_HEIGHT_PX = { sm: 20, md: 24, lg: 24 } as const
34
34
 
35
35
  const tagVariants = cva(
36
- "inline-flex items-center rounded-md border border-transparent transition-colors cursor-text",
36
+ // 停用(consumer 掛 aria-disabled;Combobox 的停用示範就是)→ 禁止符號蓋掉 cursor-text(hit-area-canonical.md 滑過原則一-3「停用的用禁止符號」,2026-09-29 全站掃出)
37
+ "inline-flex items-center rounded-md border border-transparent transition-colors cursor-text aria-disabled:cursor-not-allowed",
37
38
  {
38
39
  variants: {
39
40
  // color:categorical 色相(裝飾性分類,非語意狀態)。**消費 categorical-color SSOT**——
@@ -189,7 +190,6 @@ function TagInner(
189
190
  // 內部結構;原僅 jsdoc/spec 約束會靜默通過 → 2026-07-05 對齊 Button overlayBadge dev-warn
190
191
  // 先例(button.tsx「Dev-mode warning」段)補齊家族一致 runtime 防線。
191
192
  if (process.env.NODE_ENV !== 'production' && Icon && avatar) {
192
- // eslint-disable-next-line no-console
193
193
  console.warn(
194
194
  '[DS Tag] `icon` 與 `avatar` 互斥(同為 prefix slot),同時傳會並列渲染破壞 Tag 內部結構,請只擇一。SSOT:tag.spec.md Props 表 + tag.principles.stories.tsx IconRule。'
195
195
  )
@@ -295,7 +295,7 @@ export const tagMeta = {
295
295
  //(anatomy TOKEN_MAP 以 stripVar 機械衍生,零漂移)。
296
296
  bg: ['bg-neutral-active', 'bg-neutral-hover', 'bg-secondary', 'bg-transparent'],
297
297
  fg: ['text-foreground', 'text-inverse-fg'],
298
- ring: [],
298
+ ring: ['focus-ring-inset', '--ring'],
299
299
  },
300
300
  defaultVariant: 'neutral',
301
301
  defaultSize: 'md',
@@ -43,8 +43,14 @@ const textareaVariants = cva(
43
43
  // K10 fix(2026-05-04):disabled 時 placeholder + text 切 fg-disabled(parallel 到 bareInputStyles)
44
44
  // Textarea 自身 `<textarea disabled>` 帶 disabled HTML attribute,用 `disabled:` variant 直接命中
45
45
  'disabled:placeholder:text-fg-disabled disabled:text-fg-disabled',
46
+ // 停用游標:外框(fieldWrapperStyles disabled compound)是 cursor-not-allowed,但 <textarea disabled> 自己的 UA 樣式
47
+ // 是 cursor: default,指到文字區就變回箭頭 —— 同 Input(input.tsx 停用分支)在控件本身補上禁止符號
48
+ //(field-controls.spec.md「游標指引」disabled → cursor-not-allowed;hit-area-canonical.md 滑過原則一-3「停用的用禁止符號」)。
49
+ 'disabled:cursor-not-allowed',
46
50
  'px-[var(--field-px)]',
47
- 'transition-colors duration-150',
51
+ // 不寫 transition-colors:hover 外框一律瞬間(tokens/motion/motion.spec.md「hover 回饋不做過渡」;2026-09-26 由底色延伸到外框,待辦總帳 L9 / N4(3))。
52
+ // 外框規則來自 field-wrapper.tsx 的 fieldDefaultChromeCompounds —— 單行 wrapper(fieldWrapperStyles)、
53
+ // 複合宿主(fieldChromeStyles)與本檔三宿主共用同一份,三處必須一起不寫過渡(hover-instant-invariant 跨檔追這條 spread)。
48
54
  ],
49
55
  {
50
56
  variants: {
@@ -196,7 +196,11 @@ function TimeColumn({ values, selected, disabledSet, label, onSelect, withDivide
196
196
  // listbox 拿到鍵盤焦點時框畫在被指到的這一格、不畫容器整圈(A 類);何時畫交給瀏覽器的 :focus-visible。
197
197
  isSelected && 'bg-neutral-selected text-foreground hover:bg-neutral-selected',
198
198
  'group-focus-visible/listbox:aria-selected:focus-ring-inset',
199
- isDisabled && 'text-fg-disabled cursor-not-allowed hover:bg-transparent',
199
+ isDisabled && 'text-fg-disabled cursor-not-allowed',
200
+ // 停用項滑過不給回饋 = hover 釘在自己的靜止底色:未選中 → 透明;選中 → 維持上方的 hover:bg-neutral-selected
201
+ //(item-anatomy.spec.md「選中列 × 滑鼠 hover 釘住不變」+ time-picker.spec.md「欄內 item 狀態」表)。
202
+ // 之前一律 hover:bg-transparent,twMerge 會蓋掉選中那條,選中又停用的格滑過就變成透明。
203
+ isDisabled && !isSelected && 'hover:bg-transparent',
200
204
  )}
201
205
  >
202
206
  {String(v).padStart(2, '0')}
@@ -259,7 +263,8 @@ export function TimeColumns({
259
263
  values={hourValues}
260
264
  selected={safeValue.hours}
261
265
  disabledSet={disabledSets.hours}
262
- label="hours"
266
+ // 讀屏文字中文(全庫 SR label 中文,2026-07-04;同元件的「清除時間」「選擇時間」早已是中文,只有三欄的 listbox 名稱漏了)
267
+ label="時"
263
268
  onSelect={(h) => onChange({ ...safeValue, hours: h })}
264
269
  withDivider
265
270
  />
@@ -267,7 +272,7 @@ export function TimeColumns({
267
272
  values={minuteValues}
268
273
  selected={safeValue.minutes}
269
274
  disabledSet={disabledSets.minutes}
270
- label="minutes"
275
+ label="分"
271
276
  onSelect={(m) => onChange({ ...safeValue, minutes: m })}
272
277
  withDivider={showSeconds}
273
278
  />
@@ -276,7 +281,7 @@ export function TimeColumns({
276
281
  values={secondValues}
277
282
  selected={safeValue.seconds}
278
283
  disabledSet={disabledSets.seconds}
279
- label="seconds"
284
+ label="秒"
280
285
  onSelect={(s) => onChange({ ...safeValue, seconds: s })}
281
286
  />
282
287
  )}
@@ -142,7 +142,10 @@ Panel 展開後的 column picker 結構:
142
142
  | 正常 | 置中文字 | `text-foreground` |
143
143
  | hover | 灰底 | `hover:bg-neutral-hover` |
144
144
  | **selected** | **灰底**(非藍底,滿欄矩形填色 `w-full`,無圓角) | `bg-neutral-selected text-foreground` |
145
+ | selected × hover | 灰底**釘住不變**(item-anatomy「選中列 × 滑鼠 hover」) | `hover:bg-neutral-selected` |
145
146
  | disabled(`disabledTime`) | 灰字 | `text-fg-disabled cursor-not-allowed` |
147
+ | disabled × hover | 不給回饋,維持透明底 | `hover:bg-transparent` |
148
+ | selected × disabled(× hover) | 選中灰底 + 灰字;滑過**維持選中灰底**(停用不給回饋 = 釘在自己的靜止底色,不是變透明) | `bg-neutral-selected text-fg-disabled`,hover 沿用 `hover:bg-neutral-selected` |
146
149
 
147
150
  **為什麼 selected 走 neutral 非 primary**(2026-04-21 canonical):TimePicker panel 是「**列表選中**」語意 — user 在時 / 分 / 秒選項間切換,跟 `SelectMenu` / `MenuItem` 同流派(單選 list → `bg-neutral-selected`)。**DatePicker date cell selected 用 `bg-primary`** 是因為那是「**最終選定日期**」的強 affordance(確定性)。兩者不同語意,不互調。
148
151
 
@@ -212,7 +215,7 @@ N/A — TimePicker 是純同步輸入,無 async 狀態。
212
215
  - trigger `role="combobox"`,在 `<Field>` 內時以 `aria-labelledby` 指向 field label(`fieldCtx.labelId`)
213
216
  - **Standalone(無 `<Field>` 包裹)accessible name**:trigger 無預設 `aria-label`,`<Field>` 外使用時 consumer **必須**自帶 `aria-label` 或 `aria-labelledby`,否則 `role="combobox"` 成為無名稱控件(對齊 Select / Combobox / DatePicker 同家族 standalone 約定)。DS 不為 trigger 塞泛用 fallback name(如「時間」),避免遮蔽 consumer 的具體語意(如「會議開始時間」);Field 內則自動接線無需 consumer 介入。Panel 本體(Popover dialog)另有本 DS 提供的 `aria-label="選擇時間"`,與 trigger name 職責分離
214
217
  - Panel 開啟時 trigger `aria-expanded="true"` + `aria-haspopup="dialog"`
215
- - 每欄 `role="listbox"`(`aria-label` 為 `hours` / `minutes` / `seconds`),item `role="option" aria-selected`
218
+ - 每欄 `role="listbox"`(`aria-label` 為 `時` / `分` / `秒`;讀屏文字中文,2026-09-29 由 `hours` / `minutes` / `seconds` 改 —— 全庫 SR label 中文 2026-07-04,同元件的「清除時間」「選擇時間」早已是中文),item `role="option" aria-selected`
216
219
  - Screen reader 經 trigger 的 label + 內層可見值文字朗讀目前選取時間(目前值以可見 `<span>` 呈現,無額外 `aria-valuetext`)
217
220
 
218
221
  ---
@@ -489,7 +489,7 @@ export const timePickerMeta = {
489
489
  // panel selected 走 bg-neutral-selected(time-columns.tsx isSelected;spec「欄內 item 狀態」),非 bg-primary。
490
490
  bg: ['bg-neutral-hover', 'bg-neutral-selected', 'bg-transparent'],
491
491
  fg: ['text-fg-disabled', 'text-fg-muted', 'text-foreground'],
492
- ring: [],
492
+ ring: ['focus-ring-inset'],
493
493
  },
494
494
  } as const
495
495
 
@@ -15,6 +15,7 @@ import { useState } from 'react'
15
15
  import { Info, Settings } from 'lucide-react'
16
16
  import { Tooltip, TooltipContent, TooltipTrigger } from './tooltip'
17
17
  import { Button } from '../Button/button'
18
+ import { SegmentedControl, SegmentedControlItem } from '@/design-system/components/SegmentedControl/segmented-control'
18
19
 
19
20
  const meta: Meta = {
20
21
  title: 'Design System/Components/Tooltip/設計規格',
@@ -62,15 +63,6 @@ const Swatch = ({ value, size = 'md' }: { value: string; size?: 'sm' | 'md' }) =
62
63
  return <span className={`${s} rounded-md shrink-0 border border-black/10`} style={{ backgroundColor: `var(${value})` }} />
63
64
  }
64
65
 
65
- const Tab = ({ active, onClick, children }: { active: boolean; onClick: () => void; children: React.ReactNode }) => (
66
- <button type="button" onClick={onClick}
67
- className={`px-2.5 py-1 text-[12px] font-mono rounded-md cursor-pointer transition-colors ${
68
- active ? 'bg-primary text-white font-semibold' : 'bg-neutral-hover text-fg-secondary hover:bg-neutral-active'
69
- }`}>
70
- {children}
71
- </button>
72
- )
73
-
74
66
  const PropRow = ({ label, dot, children }: { label: string; dot?: string; children: React.ReactNode }) => (
75
67
  <div className="flex items-start gap-3 py-2 border-b border-divider last:border-b-0">
76
68
  <span className="text-[11px] text-fg-muted font-medium w-[72px] shrink-0 pt-0.5 flex items-center gap-1.5">
@@ -226,15 +218,14 @@ const InspectorInner = () => {
226
218
 
227
219
  return (
228
220
  <div className="flex flex-col gap-6">
229
- {/* Controls */}
221
+ {/* Controls — 互斥切換消費 SegmentedControl(segmented-control.spec.md「何時用」2–5 個互斥選項;
222
+ 取代手刻 Tab:它靜止借 neutral-hover、hover 借 neutral-active,是 color.spec.md 成對 token 的錯配) */}
230
223
  <div className="flex flex-col gap-2.5">
231
224
  <div className="flex items-center gap-2">
232
225
  <span className="text-[11px] text-fg-muted w-16 shrink-0">Side</span>
233
- <div className="flex gap-1.5">
234
- {(['top', 'right', 'bottom', 'left'] as const).map((s) => (
235
- <Tab key={s} active={side === s} onClick={() => setSide(s)}>{s}</Tab>
236
- ))}
237
- </div>
226
+ <SegmentedControl size="sm" aria-label="Side" value={side} onValueChange={(v) => setSide(v as SideKey)}>
227
+ {(['top', 'right', 'bottom', 'left'] as const).map((s) => <SegmentedControlItem key={s} value={s}>{s}</SegmentedControlItem>)}
228
+ </SegmentedControl>
238
229
  </div>
239
230
  </div>
240
231
 
@@ -4,9 +4,10 @@
4
4
  import React from 'react'
5
5
  import LinkTo from '@storybook/addon-links/react'
6
6
  import type { Meta, StoryObj } from '@storybook/react'
7
- import { Settings, Save, Info, AlertCircle } from 'lucide-react'
7
+ import { Settings, Save, AlertCircle } from 'lucide-react'
8
8
  import { Tooltip, TooltipTrigger, TooltipContent } from './tooltip'
9
9
  import { Button } from '@/design-system/components/Button/button'
10
+ import { FieldLabel } from '@/design-system/components/Field/field'
10
11
 
11
12
  const meta: Meta = {
12
13
  title: 'Design System/Components/Tooltip/設計原則',
@@ -93,15 +94,7 @@ export const UsageGuidance: Story = {
93
94
  title="✅ 補充資訊可以用 tooltip(錯過也不影響主流程)"
94
95
  note="「這個設定是什麼意思」「shortcut key 是什麼」等對主流程非必要的補充可以用 tooltip——看到更好,沒看到也無傷大雅"
95
96
  >
96
- <div className="flex items-center gap-2">
97
- <span>密度設定</span>
98
- <Tooltip>
99
- <TooltipTrigger asChild>
100
- <Button variant="text" size="sm" iconOnly startIcon={Info} aria-label="密度說明" />
101
- </TooltipTrigger>
102
- <TooltipContent>影響 Button / Input / row 的垂直空間</TooltipContent>
103
- </Tooltip>
104
- </div>
97
+ <FieldLabel info="影響 Button / Input / row 的垂直空間">密度設定</FieldLabel>
105
98
  <Label>↑ info icon 的補充說明,錯過不影響使用</Label>
106
99
  </Rule>
107
100
  </Section>
@@ -111,12 +104,7 @@ export const UsageGuidance: Story = {
111
104
  title="Tooltip — 純文字、語意為描述、不放互動元素"
112
105
  note="適合一句話的提示。語意是純描述(role=tooltip / aria-describedby),不該放可點擊元素。離開 trigger 往浮層移動時,靠 Radix 的 grace 區維持開啟(讓滑鼠可移入),非計時延遲。"
113
106
  >
114
- <Tooltip>
115
- <TooltipTrigger asChild>
116
- <Button variant="tertiary" size="sm" iconOnly startIcon={Info} aria-label="說明" />
117
- </TooltipTrigger>
118
- <TooltipContent>這個設定會影響所有專案的預設值</TooltipContent>
119
- </Tooltip>
107
+ <FieldLabel info="這個設定會影響所有專案的預設值">預設時區</FieldLabel>{/* 只給資訊的 ⓘ 用 FieldLabel info(平常淡、滑過深一階、一般箭頭),不用 Button:Button 會帶按鈕底色與手形,iconOnly 還會自己再掛一層提示 → 同時浮出兩個 */}
120
108
  <Label>↑ 純文字說明;滑鼠離開觸發器即關閉(移入浮層內容則保持開啟)</Label>
121
109
  </Rule>
122
110
 
@@ -133,7 +133,7 @@ Tooltip 是**單一職責 hover 提示 primitive**(一句話補充),刻意無變
133
133
  - Tab — focus trigger 時顯示
134
134
  - Esc — 關閉
135
135
 
136
- **Focus**:non-modal hover/focus surface — focus 開啟 surface,**不 trap**,focus 仍 在 trigger,Esc 關閉。Visible focus ring 由 trigger 元件自身的 focus-visible 樣式提供(各 trigger 元件 canonical;Tooltip 本體不另加 ring)。(2026-05-27 修正 codex M31 finding:Tooltip 非 modal,不該有 focus trap,原 wording 誤導)
136
+ **Focus**:non-modal hover/focus surface — focus 開啟 surface,**不 trap**,focus 仍 在 trigger,Esc 關閉。Visible focus ring 由 trigger 元件自身的 focus-visible 樣式提供(各 trigger 元件 canonical;Tooltip 本體不另加焦點框)。(2026-05-27 修正 codex M31 finding:Tooltip 非 modal,不該有 focus trap,原 wording 誤導)
137
137
 
138
138
  **驗證**:Storybook a11y addon panel 應 0 critical violation;鍵盤完整可操作(無需滑鼠)。WCAG AA contrast ≥ 4.5:1(text)/ 3:1(UI)。
139
139
 
@@ -141,6 +141,7 @@ Tooltip 是**單一職責 hover 提示 primitive**(一句話補充),刻意無變
141
141
 
142
142
  > 本節由 `scripts/add-reciprocal-pointers.mjs` 自動維護,列出在 SSOT 語境下指向本 spec 的其他 spec。若要手動補充,寫在本節之前。
143
143
 
144
+ - `agent-panel.spec.md`
144
145
  - `calendar.spec.md`
145
146
  - `coachmark.spec.md`
146
147
  - `date-picker.spec.md`
@@ -17,7 +17,7 @@ export const Overview: Story = {
17
17
  <div className="flex flex-col gap-10">
18
18
  <div>
19
19
  <H3>Anatomy</H3>
20
- <Desc>TreeView 是階層結構的遞迴元件——一個 TreeItem 就是一個 node,有 children 就可展開,沒有就是 leaf。基於 Radix Collapsible 實作展開/收合,自建 tree 結構 + ARIA tree 鍵盤導覽(Radix 沒有 Tree primitive)。</Desc>
20
+ <Desc>TreeView 是階層結構的遞迴元件——一個 TreeItem 就是一個 node,有 children 就可展開,沒有就是 leaf。展開/收合的高度動畫借用 Collapsible 元件,樹的結構與 ARIA 樹狀表格(treegrid)鍵盤導覽由元件自建(2026-09-25 由 tree 改為 treegrid,見 spec「鍵盤導覽」)。</Desc>
21
21
  <div className="border border-border rounded-lg p-4 max-w-md">
22
22
  <TreeView aria-label="文件樹範例" defaultExpandedIds={['docs', 'photos']}>
23
23
  <TreeItem id="docs" label="Documents" icon={Folder}>
@@ -47,7 +47,7 @@ export const Overview: Story = {
47
47
  <tbody>
48
48
  <tr><Td>1. 遞迴渲染 + indent</Td><Td mono>indentStep = chevronSize + gap-2(跟 item-layout 一致)</Td></tr>
49
49
  <tr><Td>2. 展開 / 收合狀態管理</Td><Td>TreeView 自管 expand state(受控 expandedIds / 非受控 defaultExpandedIds);Radix Collapsible 僅負責子節點高度動畫</Td></tr>
50
- <tr><Td>3. 鍵盤導覽 + ARIA tree</Td><Td>↑↓ 移動 / → 展開 / ← 收合 / Enter 選取</Td></tr>
50
+ <tr><Td>3. 鍵盤導覽 + ARIA 樹狀表格</Td><Td>整棵樹一個 Tab 停靠點 / ↑↓ 換列 / → 展開或進這一列的按鈕 / ← 收合或回上一層 / Enter 選取</Td></tr>
51
51
  </tbody>
52
52
  </table>
53
53
  </div>
@@ -362,8 +362,12 @@ export const StateBehavior: Story = {
362
362
  </div>
363
363
 
364
364
  <div>
365
- <H3>Hover inline actions(suffix)</H3>
366
- <Desc>hover node 時 suffix 顯示 inline action(重新命名、刪除等)。non-hover 時 suffix 隱藏。</Desc>
365
+ <H3>列上的動作(suffix)</H3>
366
+ <Desc>
367
+ 滑過列、或鍵盤焦點在這一列(列本身或列上的按鈕)時,suffix 顯示列上的動作(重新命名、刪除等);其他時候隱藏。
368
+ 鍵盤:↑↓ 換列、→ 進這一列的按鈕、← 回到列;Tab 一下就離開整棵樹 —— 別列的按鈕不在 Tab 路上
369
+ (2026-09-25 前,這棵樹從 Engineering 出發要按 5 下 Tab 才出得去,走過的全是 Alice、Bob 的按鈕)。
370
+ </Desc>
367
371
  <div className="border border-border rounded-lg p-4 max-w-md">
368
372
  <TreeView aria-label="工程團隊樹" defaultExpandedIds={['eng', 'frontend']}>
369
373
  <TreeItem id="eng" label="Engineering" icon={Users}>
@@ -397,21 +401,22 @@ export const StateBehavior: Story = {
397
401
  }
398
402
 
399
403
  export const KeyboardMatrix: Story = {
400
- name: '鍵盤導覽(ARIA tree)',
404
+ name: '鍵盤導覽(樹狀表格)',
401
405
  render: () => (
402
406
  <div className="flex flex-col gap-6">
403
407
  <div>
404
- <H3>鍵盤操作對照</H3>
405
- <Desc>TreeView 的 ARIA tree 鍵盤導覽是自建實作(Radix 沒有 Tree primitive)。重排鍵位(最後三列)僅在 `draggable` 時生效,每按一下立即 commit(發出 onDragEnd,同 pointer 契約)。</Desc>
408
+ <H3>鍵盤操作對照 — 焦點在列上</H3>
409
+ <Desc>整棵樹在 Tab 路上只佔一站(列上的 roving tabindex;2026-09-25 總帳 B9 由 tree 改為 treegrid)。Tab 進來落在上次停的那一列,沒有就落在選中的列,再沒有就第一列。重排鍵位(最後三列)僅在 `draggable` 時生效,每按一下立即 commit(發出 onDragEnd,同 pointer 契約)。</Desc>
406
410
  <div className="overflow-x-auto">
407
411
  <table className="text-caption border-collapse">
408
412
  <thead><tr><Th>按鍵</Th><Th>行為</Th></tr></thead>
409
413
  <tbody>
410
- <tr><Td mono>↑ / ↓</Td><Td>在可見 nodes 之間移動焦點(跳過已收合的 children)</Td></tr>
411
- <tr><Td mono>→</Td><Td>若 collapsed 則展開;若 expanded 則移到第一個 child</Td></tr>
412
- <tr><Td mono>←</Td><Td>若 expanded 則收合;若 collapsed 或 leaf 則移到 parent</Td></tr>
413
- <tr><Td mono>Enter / Space</Td><Td>選取當前 focus 的 node</Td></tr>
414
- <tr><Td mono>Home / End</Td><Td>跳到第一個 / 最後一個可見 node</Td></tr>
414
+ <tr><Td mono>↑ / ↓</Td><Td>在可見、未停用的列之間移動(跳過已收合的 children)</Td></tr>
415
+ <tr><Td mono>→</Td><Td>收著的資料夾 → 展開;已展開的資料夾或葉節點 → 進這一列的第一顆按鈕;沒有按鈕 → 不動</Td></tr>
416
+ <tr><Td mono>←</Td><Td>展開的資料夾 → 收合;收著的資料夾或葉節點 → 回上一層</Td></tr>
417
+ <tr><Td mono>Enter / Space</Td><Td>選取這一列</Td></tr>
418
+ <tr><Td mono>Home / End</Td><Td>跳到第一個 / 最後一個可見列</Td></tr>
419
+ <tr><Td mono>Tab / Shift+Tab</Td><Td>一下就離開整棵樹(別列、本列的按鈕都不在 Tab 路上)</Td></tr>
415
420
  <tr><Td mono>Cmd/Ctrl+Shift+↑ / ↓</Td><Td>重排:同層上移 / 下移(需 draggable;整個子樹一起動,結果經 SR live region 播報)</Td></tr>
416
421
  <tr><Td mono>Cmd/Ctrl+Shift+→</Td><Td>重排:移入上一個 sibling(需為 folder;收合時自動展開)</Td></tr>
417
422
  <tr><Td mono>Cmd/Ctrl+Shift+←</Td><Td>重排:移出,成為 parent 的下一個 sibling</Td></tr>
@@ -419,6 +424,23 @@ export const KeyboardMatrix: Story = {
419
424
  </table>
420
425
  </div>
421
426
  </div>
427
+ <div>
428
+ <H3>鍵盤操作對照 — 焦點在列上的按鈕</H3>
429
+ <Desc>列上的按鈕(更多、新增…)平常可能隱藏,焦點在這一列裡時一定看得到。完整規則見 spec「鍵盤導覽」;可實際操作的範例在「展示 / 列上的動作」。</Desc>
430
+ <div className="overflow-x-auto">
431
+ <table className="text-caption border-collapse">
432
+ <thead><tr><Th>按鍵</Th><Th>行為</Th></tr></thead>
433
+ <tbody>
434
+ <tr><Td mono>→</Td><Td>下一顆;已是最後一顆 → 不動</Td></tr>
435
+ <tr><Td mono>←</Td><Td>上一顆;已是第一顆 → 回到這一列</Td></tr>
436
+ <tr><Td mono>↑ / ↓</Td><Td>回到上 / 下一列(選單鈕的 ↓ 讓給它自己開選單)</Td></tr>
437
+ <tr><Td mono>Enter / Space</Td><Td>按鈕自己的動作</Td></tr>
438
+ <tr><Td mono>Tab</Td><Td>一下就離開整棵樹</Td></tr>
439
+ <tr><Td mono>Shift+Tab</Td><Td>回到這一列</Td></tr>
440
+ </tbody>
441
+ </table>
442
+ </div>
443
+ </div>
422
444
  </div>
423
445
  ),
424
446
  }
@@ -430,7 +452,7 @@ export const Accessibility = {
430
452
  render: () => (
431
453
  <div className="max-w-3xl text-body text-fg-secondary">
432
454
  <h3 className="text-h5 text-foreground mb-2">無障礙設計</h3>
433
- <p className="whitespace-pre-line">{"TreeView 的無障礙是元件自建的,不是沿用第三方套件的預設。\n\n 角色與屬性 :外層容器標記為 role=\"tree\",每個節點標記為 role=\"treeitem\",並逐一寫上目前展開狀態、是否選取、所在層級;多選時容器再加上「允許多選」標記。展開/收合的動畫是借用 Radix Collapsible,但樹的角色、屬性、鍵盤導覽都是元件自己實作的(Radix 沒有 Tree 元件)。\n\n 鍵盤操作 :\n\n- Tab — 焦點進入整棵樹\n- 上 / 下 — 在可見節點之間移動\n- 右 — 展開資料夾,已展開時移到第一個子節點\n- 左 — 收合資料夾,葉節點時跳回上層\n- Home / End — 跳到第一個 / 最後一個可見節點\n- Enter / 空白鍵 — 選取目前節點\n- Cmd(Ctrl)+Shift+方向鍵 — 重新排列節點(啟用拖曳時:上下=同層移動、右=移入資料夾、左=移出到上層,每按一下立即生效)\n\n 焦點 :焦點由元件自己管理——整棵樹是單一 Tab 停靠點,鍵盤移動時用一圈內描邊高亮(ring-2 ring-ring)標示目前位置。沒有焦點鎖定、也沒有焦點還原,因為樹不是浮層。\n\n 播報 :鍵盤重排的結果(移到第幾項、移入哪個資料夾)會透過隱藏的即時播報區域唸給螢幕閱讀器;無法移動時(已在最上方、不是資料夾等)也會說明原因。文案預設繁體中文,可用 reorderAnnouncements 屬性覆寫。\n\n 驗證 :Storybook a11y 面板應為 0 項嚴重問題;不靠滑鼠也能完整操作(含拖曳重排)。文字對比度達 WCAG AA(內文 4.5:1、介面元素 3:1)。"}</p>
455
+ <p className="whitespace-pre-line">{"TreeView 的無障礙是元件自建的,不是沿用第三方套件的預設。\n\n 角色與屬性 :外層容器標記為 role=\"treegrid\"(樹狀表格;2026-09-25 由 tree 改,因為只有樹狀表格定義了「列上有按鈕時鍵盤怎麼走」),每個節點的那一列標記為 role=\"row\",列裡分成主格與放按鈕的動作格(role=\"gridcell\");列上逐一寫著展開狀態、是否選取、所在層級,列名只取標籤文字。多選時容器再加上「允許多選」標記。展開/收合的動畫是借用 Collapsible 元件,樹的角色、屬性、鍵盤導覽都是元件自己實作的。讀螢幕軟體會念成「樹狀表格」——尚未用讀螢幕軟體實測。\n\n 鍵盤操作(焦點在列上) :\n\n- Tab — 進到樹,落在上次停的那一列(沒有就選中的列、再沒有就第一列);再按一下就離開整棵樹\n- 上 / 下 — 換到上 / 下一列\n- 右 — 收著的資料夾先展開;已展開的資料夾或葉節點,進到這一列的第一顆按鈕\n- 左 — 收合資料夾,收著或葉節點時回到上一層\n- Home / End — 跳到第一個 / 最後一個可見列\n- Enter / 空白鍵 — 選取這一列\n- Cmd(Ctrl)+Shift+方向鍵 — 重新排列節點(啟用拖曳時:上下=同層移動、右=移入資料夾、左=移出到上層,每按一下立即生效)\n\n 鍵盤操作(焦點在列上的按鈕) :右 / 左在按鈕之間走,第一顆再按左回到列;上 / 下換到上 / 下一列;Enter / 空白鍵執行按鈕;Tab 一下離開整棵樹,Shift+Tab 回到列。別列的按鈕永遠不在 Tab 路上。\n\n 焦點 :整棵樹只有一個 Tab 停靠點(那一列的 tabIndex 是 0,其他列與所有按鈕都是 -1),焦點是真的落在列或按鈕上。焦點在列上時,列畫一圈內描邊(focus-ring-inset:outline 2px solid var(--ring),往內 2px;由瀏覽器的 :focus-visible 決定畫不畫——滑鼠點列不畫、鍵盤畫);焦點在按鈕上時由按鈕自己畫框。平常隱藏的列上按鈕,焦點在這一列裡時一定看得到。沒有焦點鎖定;唯一的「還焦點」是鍵盤重排後把焦點還給被移動的那一列。\n\n 播報 :鍵盤重排的結果(移到第幾項、移入哪個資料夾)會透過隱藏的即時播報區域唸給螢幕閱讀器;無法移動時(已在最上方、不是資料夾等)也會說明原因。文案預設繁體中文,可用 reorderAnnouncements 屬性覆寫。\n\n 驗證 :Storybook a11y 面板應為 0 項嚴重問題;不靠滑鼠也能完整操作(含列上的按鈕與拖曳重排)。文字對比度達 WCAG AA(內文 4.5:1、介面元素 3:1)。"}</p>
434
456
  </div>
435
457
  ),
436
458
  }
@@ -24,14 +24,14 @@ benchmark:
24
24
 
25
25
  TreeView 是**階層結構的遞迴元件**。一個 TreeItem 就是一個 node——有 children 就可展開,沒有就是 leaf。沒有第二個概念。
26
26
 
27
- **實作基礎**:基於 Radix Collapsible 實作展開 / 收合,自建 tree 結構與 ARIA tree 鍵盤導覽(Radix 沒有 Tree primitive)。
27
+ **實作基礎**:基於 Radix Collapsible([`packages/react/collapsible`](https://github.com/radix-ui/primitives/tree/f7ecd5ab16f5e1e820eb5786a1419a98a2d594ae/packages/react/collapsible))實作展開 / 收合,自建樹狀結構與 ARIA **樹狀表格(`treegrid`)**鍵盤導覽(Radix 沒有 Tree / Treegrid primitive)。2026-09-25 由 `tree` 改為 `treegrid`,決定與理由見「鍵盤導覽」。
28
28
 
29
29
  **Layout Family**:本元件是 `patterns/element-anatomy/item-anatomy.spec.md` 所擁有的 **Family 1(Menu item layout)** 消費者。結構繼承其「Menu item layout」章節的 scanning-mode 規格。
30
30
 
31
31
  TreeView 本身只負責三件事:
32
32
  1. **遞迴渲染** + indent
33
33
  2. **展開/收合**狀態管理
34
- 3. **鍵盤導覽** + ARIA tree
34
+ 3. **鍵盤導覽** + ARIA 樹狀表格(`treegrid`)
35
35
 
36
36
  它不管 node 裡面長什麼樣——icon、badge、status indicator、inline action 等視覺都由 consumer 透過 props / slots 決定。不同使用情境(sidebar nav、file browser、stepper)是同一個 TreeView 的不同消費方式,不是不同元件。
37
37
 
@@ -70,8 +70,8 @@ TreeView 本身只負責三件事:
70
70
  </TreeView>
71
71
  ```
72
72
 
73
- - `TreeView`:外層容器,`role="tree"`,管理 expand state + focused node + keyboard
74
- - `TreeItem`:唯一的 node 元件,`role="treeitem"`。**有 children = expandable,沒有 = leaf**
73
+ - `TreeView`:外層容器,`role="treegrid"`,管理 expand state + 唯一的 Tab 停靠點 + keyboard
74
+ - `TreeItem`:唯一的 node 元件。**有 children = expandable,沒有 = leaf**。DOM 是「無角色的 node 容器 → 一列 `role="row"`(主格 `gridcell` + 有按鈕時的動作格 `gridcell`)→ 子項容器」:樹狀表格的列只能擁有格、不能包住子列,所以層級 / 展開 / 選取屬性都在 `row` 上(見「ARIA」)
75
75
 
76
76
  ---
77
77
 
@@ -82,7 +82,7 @@ TreeView 本身只負責三件事:
82
82
  | Slot | 說明 | 存在條件 |
83
83
  |---|---|---|
84
84
  | **indent** | `paddingLeft = depth × indentStep` | depth > 0 |
85
- | **chevron** | `ChevronRight`(展開時 `rotate-90` transition,見「動畫」)| 有 children |
85
+ | **chevron** | 共用行內小按鈕 `ItemInlineActionButton` + `ChevronRight`(展開時 `rotate-90` transition,見「動畫」)| 有 children |
86
86
  | **chevron placeholder** | 等寬空白,確保同層 leaf label 對齊 | 沒 children(每個 leaf 無條件留等寬空白,不檢查 siblings)|
87
87
  | **icon** | `LucideIcon`,跟 label 同色(內容 icon) | 可選 |
88
88
  | **label** | 主要文字 | 必有 |
@@ -92,6 +92,8 @@ TreeView 本身只負責三件事:
92
92
 
93
93
  Chevron 是**展開/收合控件**,不是 prefix icon:`fg-muted`(指示色,hover 時 `fg-secondary`,只前進一階)/ 位置在 indent 之後 icon 之前 / 點擊只 toggle expand 不觸發 selection / `rotate-90` transition。
94
94
 
95
+ **消費共用行內小按鈕**(2026-09-26,待辦總帳 L5,user:「是改成inline action對吧?」):箭頭本身就是 `ItemInlineActionButton`(`../../patterns/element-anatomy/item-anatomy.tsx`),尺寸照 `../../patterns/element-anatomy/inline-action.spec.md`「尺寸對照」TreeItem 列 —— 圖示與排版佔位 16(lg 20)、**滑過底色 18(lg 22)= 點得到的範圍**(`../../../ds-canonical/references/hit-area-canonical.md`)、按下多一階 `neutral-active`。與 DataTable 巢狀列的展開箭頭同一顆(`../DataTable/data-table.tsx` nestedPrefix)。縮排不變:箭頭仍住 `ItemPrefix` 鎖寬的槽,多出的 1px 靠溢出不吃版位。2026-09-26 之前這裡手刻一顆 16×16 的按鈕,滑過底色與點擊範圍都只有 16、也沒有按下態。箭頭 `tabIndex=-1`(不佔 Tab 停靠點;展開 / 收合的鍵盤在列上:`→` / `←`),**但保留名字與角色、不 `aria-hidden`**(2026-09-29 更正,待辦總帳 N46):`aria-label` 展開 / 收合 + `aria-expanded`,語音控制使用者喊「展開」才按得到、讀屏在列裡也叫得到它;與 DataTable 巢狀列的展開箭頭同一種寫法(`../DataTable/data-table.tsx` nestedPrefix)。2026-09-29 前寫 `aria-hidden`,一顆能聚焦(`tabIndex=-1` 仍可被程式與滑鼠聚焦)的按鈕對讀屏隱藏是 axe `aria-hidden-focus` 那一族。
96
+
95
97
  ### 佔位規則(chevron + icon)
96
98
 
97
99
  同層 node 間,label 需對齊。**Chevron 佔位**:每個無 children 的 leaf **無條件**渲染等寬 chevron placeholder(寬 16/16/20 @ sm/md/lg),不檢查 siblings——故有/無 expandable sibling 結果都對齊。**Icon 佔位**:icon slot 只在該 node 傳 `icon` prop 時渲染 `<ItemIcon>`,**無自動等寬 placeholder**(無 icon 的 leaf 不補空位);若要 icon 欄整齊,consumer 自行確保同層都傳 icon。
@@ -115,11 +117,11 @@ Chevron 是**展開/收合控件**,不是 prefix icon:`fg-muted`(指示色,hover
115
117
 
116
118
  ## 展開/收合
117
119
 
118
- **行為**:點整行(label / icon / 空白)→ select(預設所有 node 可選);點 chevron → toggle expand,不 select;鍵盤 `→`(收合 expandable)→ expand;`←`(展開 expandable)→ collapse;`←`(leaf / 收合 node)→ 焦點到 parent。
120
+ **行為**:點整行(label / icon / 空白)→ select(預設所有 node 可選);點 chevron → toggle expand,不 select;鍵盤 `→`(收合 expandable)→ expand;`←`(展開 expandable)→ collapse;`←`(leaf / 收合 node)→ 焦點到 parent。(點列上的按鈕目前**也會**選取該列 —— 既有行為,本次未改,焦點則留在按鈕上。)
119
121
 
120
- **預設 label 不 expand**——chevron 是展開唯一控件。理由:select / expand 語意獨立(sidebar「Documents」點 label 進頁面,點 chevron 才展開子列表)。Consumer `expandOnSelect` prop 可讓整行同時 select + expand(適合 stepper)——**此連帶展開僅限指標點擊(`handleRowClick`)**;鍵盤 `Enter` / `Space` 只觸發 select,展開 / 收合一律走 `→` / `←`(對齊 WAI-ARIA treeview「Enter 啟用、方向鍵展開收合」慣例)。
122
+ **預設 label 不 expand**——chevron 是展開唯一控件。理由:select / expand 語意獨立(sidebar「Documents」點 label 進頁面,點 chevron 才展開子列表)。Consumer `expandOnSelect` prop 可讓整行同時 select + expand(適合 stepper)——**此連帶展開僅限指標點擊(`handleRowClick`)**;鍵盤 `Enter` / `Space` 只觸發 select,展開 / 收合一律走 `→` / `←`(W3C 樹狀表格同樣是「方向鍵展開收合、`Enter` 執行預設動作」,[treegrid-pattern.html#L60-L80](https://github.com/w3c/aria-practices/blob/3f094fde1c81b25dfa69162563bf28d093f854d4/content/patterns/treegrid/treegrid-pattern.html#L60-L80))。
121
123
 
122
- **動畫**:children 用 Radix `Collapsible` height animation(0 → auto);chevron `transition-transform duration-150 rotate-0 → rotate-90`(2026-09-10:原本是 `transition-all`,連 hover 底色一起過渡;hover 底色改成瞬間後只留旋轉,owner = `tokens/motion/motion.spec.md`「hover 回饋不做過渡」)。
124
+ **動畫**:children 用 Radix `Collapsible` height animation(0 → auto);chevron 圖示(2026-09-26 起掛在 `ItemInlineActionButton` 的 `iconClassName`)`transition-transform duration-150 rotate-0 → rotate-90` —— 展開 / 收合是狀態切換的動畫,不是滑過回饋,保留(2026-09-10:原本是 `transition-all`,連 hover 底色一起過渡;hover 底色改成瞬間後只留旋轉,owner = `tokens/motion/motion.spec.md`「hover 回饋不做過渡」)。
123
125
 
124
126
  ---
125
127
 
@@ -161,20 +163,50 @@ Chevron 是**展開/收合控件**,不是 prefix icon:`fg-muted`(指示色,hover
161
163
 
162
164
  ## 鍵盤導覽
163
165
 
166
+ **整棵樹在 Tab 路上只佔一站**:列上 roving tabindex —— 停靠點那一列 `tabIndex=0`,其餘列、所有列上的按鈕、展開箭頭、勾選框都是 `-1`。Tab 進來落在:上次焦點停的那一列 → 選中的列 → 第一個可用列(W3C 對樹狀表格「回到上次停的那一個」與對樹「落在選中項,沒有就第一項」兩條合在一起,[keyboard-interface-practice.html#L270-L284](https://github.com/w3c/aria-practices/blob/3f094fde1c81b25dfa69162563bf28d093f854d4/content/practices/keyboard-interface/keyboard-interface-practice.html#L270-L284))。
167
+
168
+ 焦點在**列**上:
169
+
164
170
  | 按鍵 | 行為 |
165
171
  |---|---|
166
- | `↑` | 焦點移到上一個可見 node |
167
- | `↓` | 焦點移到下一個可見 node |
168
- | `→` | 展開(若收合);移到第一個 child(若已展開) |
169
- | `←` | 收合(若展開);移到 parent(若已收合或是 leaf) |
170
- | `Home` | 焦點移到第一個 node |
171
- | `End` | 焦點移到最後一個可見 node |
172
- | `Enter` / `Space` | 觸發 selection(等同點擊 label 的 select;`expandOnSelect` 的連帶展開僅限指標點擊,鍵盤展開 / 收合用 `→` / `←`) |
172
+ | `↑` / `↓` | 上 / 下一個可見、未停用的列 |
173
+ | `Home` / `End` | 第一個 / 最後一個可見列 |
174
+ | `→` | 收著的資料夾 → 展開(焦點不動);已展開的資料夾或葉節點 → 進**這一列**的第一顆按鈕;沒有按鈕 → 不動 |
175
+ | `←` | 展開的資料夾 → 收合;收著的資料夾或葉節點 → 回上一層;最外層 → 不動 |
176
+ | `Enter` / `Space` | 選取(等同點擊 label;`expandOnSelect` 的連帶展開僅限指標點擊) |
177
+ | `Tab` / `Shift+Tab` | **一下就離開整棵樹**(別列、本列的按鈕都不在 Tab 路上) |
178
+ | 打字(可見字元) | **打字跳位**(2026-09-29,待辦總帳 N46;`ds-canonical/references/keyboard-model-canonical.md` 五條判準第 2 條把「打字前導」列為樹必備):焦點移到**下一個**名字以該字開頭的可見列(從目前這一列的下一列往後找,到底繞回第一列;折疊大小寫);1 秒內連打多個字 = 以整串比對(「fr」→ Frontend);連打同一個字母而沒有名字以「bb」開頭時退回單字循環(在同字母的列之間輪流)。`Space` 不算(它是選取)。一手:W3C APG Tree View 逐字 "Type a character: focus moves to the next node with a name that starts with the typed character." / "Type multiple characters in rapid succession: focus moves to the next node with a name that starts with the string of characters typed."(<https://www.w3.org/WAI/ARIA/apg/patterns/treeview/>);1 秒的累積窗與同字母退回是 AI 推導。閘:`scripts/tree-view-keyboard-route-invariant.mjs` K1(真按鍵)+ `scripts/test-tree-keyboard-route.mjs`(判定表) |
173
179
  | `Cmd/Ctrl+Shift+↑` / `↓` | 重排:同層上移 / 下移(僅 `draggable` 時;詳「Drag and Drop → 鍵盤重排」) |
174
180
  | `Cmd/Ctrl+Shift+→` | 重排:移入上一個 sibling(需為 folder;收合時自動展開) |
175
181
  | `Cmd/Ctrl+Shift+←` | 重排:移出,成為 parent 的下一個 sibling |
176
182
 
177
- 遵循 [WAI-ARIA TreeView pattern](https://www.w3.org/WAI/ARIA/apg/patterns/treeview/)。重排鍵位在 modifier 層,與 APG 導覽鍵完全正交(APG 無 drag-and-drop pattern,重排屬 application-level 鍵位)。
183
+ **列被刪掉時焦點接力**(2026-09-29,待辦總帳 N46「刪除 / 消失後焦點去下一項」):consumer 從資料裡拿掉節點時,焦點若在那一列(或它的按鈕)上,接力到畫面順序的**下一個可見列**(可能是下一個頂層節點);沒有下一列 → **上一個可見列**(可能就是父節點);禁止掉到 `body`。落點順序同 `../FileUpload/file-upload.spec.md`「移除焦點」(下一個 → 上一個 → 擁有者)。收合中的分支不算候選(整段子樹一起卸載,落點是收合的那個父節點)。實作 = `tree-view.tsx` TreeItem 的 layout-effect cleanup(節點拆掉前鄰列都還在);驗證 = `tree-view.stories.tsx`「刪除後焦點接力」(play 實刪四次)。
184
+
185
+ 焦點在**列上的按鈕**上:
186
+
187
+ | 按鍵 | 行為 |
188
+ |---|---|
189
+ | `→` | 下一顆;已是最後一顆 → 不動 |
190
+ | `←` | 上一顆;已是第一顆 → 回到這一列 |
191
+ | `↑` / `↓` | 回到上 / 下一列(選單鈕也一樣;開選單用 `Enter` / `Space`) |
192
+ | `Home` / `End` | 第一個 / 最後一個可見列 |
193
+ | `Enter` / `Space` | 按鈕自己的動作(原生) |
194
+ | `Tab` / `Shift+Tab` | **一下就離開整棵樹**(焦點先回這一列,再由瀏覽器往下 / 往上走一站) |
195
+
196
+ **2026-09-26 統一成側欄做法的三格**(批次細節 X4 / X6 / X7,待辦總帳〇節「09-26 同意清單回覆」,user 逐字:「確保符合我們一致的設計語言且不違背世界級的設計且都有確保整個ds 是SSOT,避免漂移就照你建議做」):按鈕上 `Home` / `End` 原本不處理 → 換到第一 / 最後一列(X4);選單鈕上 `↓` 原本讓給按鈕開選單 → 換下一列(X6;同 `../Sidebar/sidebar.spec.md`「方向鍵整串歸清單」);按鈕上 `Shift+Tab` 原本先落回這一列、要按兩下 → 一下離開(X7,這一格原本就違反 B9「Tab 一下離開」)。
197
+
198
+ **滑過才出現的按鈕**(`actionsReveal="hover"`):焦點在這一列裡(列本身或列上的按鈕)時一定看得到;滑鼠點列不算(`:focus-visible` 判定)。
199
+
200
+ **決定來源**:總帳 `governance/planning/2026-09-25-interaction-and-hover-remediation.md` B9,user 原話「確定建議符合我們一致的設計語言且不違背世界級的設計就照建議」(附條件同意;條件經研究查證成立,同帳 A1)。與選單的同一套說法:Tab 換到下一區、方向鍵在區塊裡移動。
201
+
202
+ **一手依據**(路線本身;以下各格的細節 = AI 依這些來源對齊,不是 user 逐格裁示):
203
+ - 滑過才出現的東西用方向鍵走到、整組只佔一站:W3C grid [grid-pattern.html#L158](https://github.com/w3c/aria-practices/blob/3f094fde1c81b25dfa69162563bf28d093f854d4/content/patterns/grid/grid-pattern.html#L158)、[keyboard-interface-practice.html#L264](https://github.com/w3c/aria-practices/blob/3f094fde1c81b25dfa69162563bf28d093f854d4/content/practices/keyboard-interface/keyboard-interface-practice.html#L264)
204
+ - 樹狀表格的 `→`(收著先展開、展開後進列)、最右一格 `→` 不動、第一格 `←` 回列:[treegrid-pattern.html#L66-L80](https://github.com/w3c/aria-practices/blob/3f094fde1c81b25dfa69162563bf28d093f854d4/content/patterns/treegrid/treegrid-pattern.html#L66-L80)
205
+ - Adobe 正式版樹預設「方向鍵進列」:[s2 TreeView.mdx#L391](https://github.com/adobe/react-spectrum/blob/956ecbcb8803f0d0d5d5d973bb169d70c52aea02/packages/dev/s2-docs/pages/s2/TreeView.mdx#L391);它的樹是 `treegrid`:[useTree.ts#L57](https://github.com/adobe/react-spectrum/blob/956ecbcb8803f0d0d5d5d973bb169d70c52aea02/packages/react-aria/src/tree/useTree.ts#L57);`→` 進列裡的按鈕、`↓` 換列、Tab 直接離開:[GridList.test.js#L1118-L1129](https://github.com/adobe/react-spectrum/blob/956ecbcb8803f0d0d5d5d973bb169d70c52aea02/packages/react-aria-components/test/GridList.test.js#L1118-L1129)
206
+ - **AI 推導**(研究沒有明文要求的兩格):`←` 在收著 / 葉節點上回上一層 —— 樹的語意,Adobe 同([useGridListItem.ts#L501-L519](https://github.com/adobe/react-spectrum/blob/956ecbcb8803f0d0d5d5d973bb169d70c52aea02/packages/react-aria/src/gridlist/useGridListItem.ts#L501-L519));W3C 樹狀表格原文在這兩種列上是「不動」(#L78),本 DS 沿用樹的行為;按鈕上 `↑` / `↓` 換列 —— Adobe 方向鍵模式([useGridListItem.ts#L298-L309](https://github.com/adobe/react-spectrum/blob/956ecbcb8803f0d0d5d5d973bb169d70c52aea02/packages/react-aria/src/gridlist/useGridListItem.ts#L298-L309))。2026-09-26 前還有第三格「選單鈕的 `↓` 讓給按鈕」(對照微軟 Fluent 樹的例外 [useTreeItem.tsx#L201](https://github.com/microsoft/fluentui/blob/d27922755bebae866d9ffe86b7da44c27ec801ee/packages/react-components/react-tree/library/src/components/TreeItem/useTreeItem.tsx#L201)),已依 X6 統一成側欄做法(換下一列),理由是同一個 DS 裡「列上有小按鈕的一串」只有一張按鍵表。
207
+ - 判定與執行 = `../../lib/roving-list-keyboard.ts`(2026-09-26 由本元件的 `tree-keyboard-route.ts` 與 Sidebar / FileUpload / Command 四份合一),本元件只讀樹自己的狀態(哪一列、展開與否、上一層)與重排。判定表 `scripts/test-roving-list-keyboard.mjs`(全宿主)+ `scripts/test-tree-keyboard-route.mjs`(樹專屬格,含舊路線對照組);瀏覽器量測 `scripts/tree-view-keyboard-route-invariant.mjs`(R3 = X7、R4 = X4)。鍵盤處理掛在捕獲階段(同 Sidebar),所以選單鈕自己的 `↓` 搶不到。
208
+
209
+ 重排鍵位在 modifier 層,與導覽鍵完全正交(W3C 沒有拖放 pattern,重排屬 application-level 鍵位)。
178
210
 
179
211
  ---
180
212
 
@@ -182,9 +214,11 @@ Chevron 是**展開/收合控件**,不是 prefix icon:`fg-muted`(指示色,hover
182
214
 
183
215
  | 元素 | Role | 屬性 |
184
216
  |---|---|---|
185
- | TreeView 容器 | `role="tree"` | **`aria-label` 或 `aria-labelledby`(擇一必填 — accessible name)**,`aria-multiselectable`(多選時) |
186
- | TreeItem 外層 | `role="treeitem"` | `aria-expanded`(expandable 才有),`aria-selected`,`aria-level` |
187
- | TreeItem children 容器 | `role="group"` | — |
217
+ | TreeView 容器 | `role="treegrid"`(不可聚焦) | **`aria-label` 或 `aria-labelledby`(擇一必填 — accessible name)**,`aria-multiselectable`(多選時) |
218
+ | TreeItem 外層(node 容器) | 無角色 | — (只放內部用的 `data-tree-*`) |
219
+ | TreeItem 的列 | `role="row"`(`tabIndex` 0 = 停靠點 / -1) | `aria-level`,`aria-expanded`(expandable 才有),`aria-selected`(`selectionMode≠none`),`aria-disabled`,`aria-labelledby` → 自己的 label(列名只取 label,不併入按鈕名) |
220
+ | 主格 / 動作格 | `role="gridcell"` | 動作格裡每顆按鈕 `tabIndex=-1`、**必須有可讀名稱**(`inlineActions` 由 `label` 給 `aria-label`;`inlineActionsSlot` 缺名稱時 dev 模式 `console.warn`) |
221
+ | TreeItem children 容器 | 無角色(2026-09-25 前是 `group`;樹狀表格的列之間不允許 `group`) | — |
188
222
 
189
223
  ---
190
224
 
@@ -254,7 +288,9 @@ Sidebar 不會自動辨識任意 tree 容器;consumer 必須在承載 TreeView
254
288
 
255
289
  ## Hover-only Inline Actions
256
290
 
257
- TreeItem 右側 inline actions(宣告式 `inlineActions: InlineActionConfig[]` 或 escape-hatch `inlineActionsSlot`,**非** `actions` prop)預設在 hover 該列**或鍵盤 focus-visible 該列**時出現(opacity 0→1 transition);`actionsReveal={false}` 則常駐不 hover-reveal。**為什麼**:(a) 視覺清潔(10+ node × 2-3 icon = 20-30 灰 icon 同屏噪音極大);(b) 業界一致(Notion / VS Code / Figma 都 hover-only);(c) 不影響操作(要操作自然會 hover)。
291
+ TreeItem 右側 inline actions(宣告式 `inlineActions: InlineActionConfig[]` 或 escape-hatch `inlineActionsSlot`,**非** `actions` prop)預設在 hover 該列**或鍵盤 focus-visible 該列**時出現(opacity 0→1);`actionsReveal={false}` 則常駐不 hover-reveal。**為什麼**:(a) 視覺清潔(10+ node × 2-3 icon = 20-30 灰 icon 同屏噪音極大);(b) 業界一致(Notion / VS Code / Figma 都 hover-only);(c) 不影響操作(要操作自然會 hover)。
292
+
293
+ **出現是瞬間的、不淡入**(2026-09-26 待辦總帳 L9「全部瞬間」延伸到滑過才出現的按鈕,user:「確定這樣才是一致設計語言就做」;2026-09-26 前是 0.15 秒淡入)。出現規則只住 `ItemSuffix` 的 `hoverReveal`(`../../patterns/element-anatomy/item-anatomy.tsx`),SSOT = `../../tokens/motion/motion.spec.md`「hover 回饋不做過渡」。
258
294
 
259
295
  **Uniform 規則**:同類型 node 的 hover action 必須完全一致,否則 discovery 失敗。
260
296
  - **⋯ (more menu)**:所有 node 統一有,menu 內容由 consumer 依 node type 動態(rename / delete / duplicate)
@@ -266,7 +302,7 @@ TreeItem 右側 inline actions(宣告式 `inlineActions: InlineActionConfig[]`
266
302
 
267
303
  Icon 尺寸跟 size tier(sm/md=16, lg=20);色 `fg-muted` → hover `fg-secondary`;hover bg `neutral-hover`;action 間距 / 高度對齊規則見 `../../patterns/element-anatomy/inline-action.spec.md`(canonical SSOT,2026-04-24 自 item-anatomy 抽出)。
268
304
 
269
- **宣告式 API**(對齊 `inline-action.spec.md` + `SidebarMenuButton.inlineActions`):`inlineActions: InlineActionConfig[]` + `actionsReveal: false | "hover"`(預設 `"hover"`,鍵盤 focus-visible 也顯)。內部用 `<ItemInlineAction>` helper 渲染,consumer **不可手刻 button JSX**(canonical 在 `item-anatomy.tsx`,共用 size 查表 / Tooltip / hover bg / aria)。Hover-reveal 用 `group-has-[:focus-visible]/tree-item:opacity-100` 而非 `group-focus-within`(後者 mouse click 會永久顯示)。
305
+ **宣告式 API**(對齊 `inline-action.spec.md` + `SidebarMenuButton.inlineActions`):`inlineActions: InlineActionConfig[]` + `actionsReveal: false | "hover"`(預設 `"hover"`,鍵盤 focus-visible 也顯)。內部用 `<ItemInlineAction>` helper 渲染,consumer **不可手刻 button JSX**(canonical 在 `item-anatomy.tsx`,共用 size 查表 / Tooltip / hover bg / aria)。Hover-reveal 用 `group-has-[:focus-visible]/tree-item:opacity-100`(焦點在列上的按鈕)+ `group-focus-visible/tree-item:opacity-100`(焦點在列本身;2026-09-25 列改為真焦點後補,總帳 B9),而非 `group-focus-within`(後者 mouse click 會永久顯示)。鍵盤怎麼走到這些按鈕見「鍵盤導覽」。
270
306
 
271
307
  ---
272
308
 
@@ -287,7 +323,7 @@ Icon 尺寸跟 size tier(sm/md=16, lg=20);色 `fg-muted` → hover `fg-secondary
287
323
 
288
324
  ### 鍵盤重排(2026-07-14 v1)
289
325
 
290
- `draggable` 啟用後,鍵盤與指標同樣可重排(WCAG 2.1.1 keyboard parity——四鍵可組合出任何 pointer 可達的合法樹位置)。鍵位 `Cmd/Ctrl+Shift+方向鍵`(對齊 [Notion 移動 block 鍵位](https://www.notion.com/help/keyboard-shortcuts),WebFetch 驗證紀錄 `.claude/logs/treeview-keyboard-dnd-design.md#L25`;[Asana](https://keycombiner.com/collections/asana/) / [Todoist](https://www.todoist.com/help/articles/use-keyboard-shortcuts-in-todoist-Wyovn2) 同「modifier+方向鍵直接移動」派),對虛擬焦點(`aria-activedescendant`)節點操作,**每按一下立即 commit**——無 grab-mode、無預覽、無 cancel(反向鍵即 undo)。**不用** dnd-kit `KeyboardSensor`:其 activator 需可接收 DOM focus([dnd-kit sensors 文件](https://dndkit.com/api-documentation/sensors/keyboard)),與單一 tab stop 虛擬焦點模型(2026-07-05 D4 拍板)結構性不相容,故為容器 `handleKeyDown` 的自建分支。
326
+ `draggable` 啟用後,鍵盤與指標同樣可重排(WCAG 2.1.1 keyboard parity——四鍵可組合出任何 pointer 可達的合法樹位置)。鍵位 `Cmd/Ctrl+Shift+方向鍵`(對齊 [Notion 移動 block 鍵位](https://www.notion.com/help/keyboard-shortcuts),WebFetch 驗證紀錄 `.claude/logs/treeview-keyboard-dnd-design.md#L25`;[Asana](https://keycombiner.com/collections/asana/) / [Todoist](https://www.todoist.com/help/articles/use-keyboard-shortcuts-in-todoist-Wyovn2) 同「modifier+方向鍵直接移動」派),對焦點所在的那一列操作(只從列上發動;焦點在列上的按鈕時不重排),**每按一下立即 commit**——無 grab-mode、無預覽、無 cancel(反向鍵即 undo)。**不用** dnd-kit `KeyboardSensor`:它是「按下開始 → 方向鍵搬 → 再按放下」的抓取模式,預設用 `Enter` / `Space` 開始([dnd-kit sensors 文件](https://dndkit.com/api-documentation/sensors/keyboard)),而本元件這兩個鍵是「選取」、重排語意又是每按即 commit,故為容器 `handleKeyDown` 的自建分支。(舊理由「列不可聚焦,與虛擬焦點結構性不相容」在 2026-09-25 列改為真焦點後不再成立,已撤回。)
291
327
 
292
328
  | 按鍵 | 行為 | 發出事件 |
293
329
  |---|---|---|
@@ -299,7 +335,7 @@ Icon 尺寸跟 size tier(sm/md=16, lg=20);色 `fg-muted` → hover `fg-secondary
299
335
  - **契約同 pointer**:同一個 `onDragEnd` + `TreeDragEndEvent`,consumer API 零改動。**頻率差異**:pointer 一手勢一次、keyboard 每步一次——有伺服器持久化的 consumer 建議 optimistic update(不建議 debounce batch,會破壞每按即 commit 語意)。
300
336
  - **邊界 no-op**:已在最上方 / 最下方 / 最外層、無可移入的 folder → 不發事件,只 SR 播報原因。
301
337
  - **disabled**:disabled node 不可移動,也不可當 target 錨點——相鄰 sibling disabled 時錨到最近 enabled sibling 表達同一插槽(可達位置與 pointer 一致;pointer 的 `useDroppable` 同樣 disable disabled node)。
302
- - **視覺**:每按即 commit,節點真實移動即回饋——**不用** dropIndicator / DragOverlay(那是「預覽中」的視覺語言,用在已 commit 的操作上會說謊);保留 keyboard focus ring 跟隨(`focusedId` 是 node id,移動後 `aria-activedescendant` 自動指向新位置)+ 移動後 `scrollIntoView`。
338
+ - **視覺**:每按即 commit,節點真實移動即回饋——**不用** dropIndicator / DragOverlay(那是「預覽中」的視覺語言,用在已 commit 的操作上會說謊);保留 keyboard focus ring 跟隨 —— 移動後焦點還給被移動的那一列(React 搬動或重掛該列時瀏覽器會把焦點丟到 body,`tree-view.tsx` 在下一個影格把焦點放回去;2026-09-25 前靠 `aria-activedescendant` 自動跟上)+ 移動後 `scrollIntoView`。
303
339
  - **互斥**:pointer 拖曳進行中忽略鍵盤重排;`draggable` 未啟用時整組鍵位 no-op(不落入導覽)。
304
340
  - **SR 播報**:TreeView 自有 sr-only `role="status"` polite live region(消費 `select-menu.tsx` `SelectMenuLiveStatus` 先例;單一節點覆寫式更新,快速連按只播最新)。文案 zh-TW 預設(「已將『X』移到『Y』之後,第 n 項,共 m 項」/「已將『X』移入『Y』」/「已在最上方」等),`reorderAnnouncements` prop per-key 覆寫(i18n 覆寫點),含 `instructions` sr-only 操作說明(tree 容器 `aria-describedby` 指向,僅 `draggable` 渲染)。播報為 optimistic(事件發出即播,假設 consumer 依約 reorder);移入原本收合的 folder 時序數不可知,播報省略序數。
305
341
 
@@ -391,27 +427,20 @@ TreeView 真實展示需要**多層巢狀結構**才有意義(單節點無法體
391
427
 
392
428
  ## A11y 預設
393
429
 
394
- **ARIA / Pattern**:自建 ARIA tree(非沿用 Radix 預設)。容器 `role="tree"`(多選時加 `aria-multiselectable`),每個 node `role="treeitem"` + `aria-expanded`(expandable 才有)/ `aria-selected` / `aria-level`,皆由元件手動設定(`tree-view.tsx` TreeItem render)。Radix `collapsible` 僅用於 children 展開 / 收合的高度動畫,**不提供** tree 的 role / aria / 鍵盤導覽(Radix 沒有 Tree primitive,見「定位」段)。對齊 [WAI-ARIA TreeView pattern](https://www.w3.org/WAI/ARIA/apg/patterns/treeview/)。`aria-setsize` / `aria-posinset` **刻意省略**:APG 規定只有當節點未全數 render 進 DOM(lazy load)或 DOM 序 ≠ 閱讀序時才必需;本元件全可見節點皆在 DOM(`querySelectorAll('[role="treeitem"]:not([hidden])')`)且 DOM 序 = 閱讀序,故非必需。
395
-
396
- **Accessible name(必填契約)**:`role="tree"` 的名稱無法從子節點內容推導,consumer **必須**提供 `aria-label`(直接字串)或 `aria-labelledby`(指向可見標題的 id)其中之一——有可見標題時優先 `aria-labelledby`,無則用 `aria-label`。兩者皆缺 → 螢幕閱讀器只讀出「tree」無法辨識用途(WAI-ARIA APG 要求 `role="tree"` 具 accessible name)。`tree-view.tsx` 於 `process.env.NODE_ENV !== 'production'` 時 `console.warn` 提示,避免上線後才發現無名稱樹(對齊 Button / Tag 的 dev-only 誤用警告 idiom)。
430
+ **ARIA / Pattern**:自建 ARIA **樹狀表格**(2026-09-25 由 `tree` 改,總帳 B9)。容器 `role="treegrid"`(多選時加 `aria-multiselectable`),每個 node 的列 `role="row"` + `aria-level` / `aria-expanded`(expandable 才有)/ `aria-selected` / `aria-labelledby`,列裡是 `gridcell`(主格 + 動作格),皆由元件手動設定(`tree-view.tsx` TreeItem render;完整對照見上方「ARIA」表)。Radix Collapsible([`packages/react/collapsible`](https://github.com/radix-ui/primitives/tree/f7ecd5ab16f5e1e820eb5786a1419a98a2d594ae/packages/react/collapsible))僅用於 children 展開 / 收合的高度動畫,**不提供**任何角色 / aria / 鍵盤導覽。為什麼是樹狀表格:W3C 只在樹狀表格定義了「列裡有按鈕時鍵盤怎麼走」([treegrid-pattern.html#L62-L80](https://github.com/w3c/aria-practices/blob/3f094fde1c81b25dfa69162563bf28d093f854d4/content/patterns/treegrid/treegrid-pattern.html#L62-L80)),一般樹的頁面沒有;Adobe 的樹同樣是 `treegrid`([useTree.ts#L57](https://github.com/adobe/react-spectrum/blob/956ecbcb8803f0d0d5d5d973bb169d70c52aea02/packages/react-aria/src/tree/useTree.ts#L57))。讀螢幕軟體會念成「樹狀表格」—— **未用讀螢幕軟體實測**。`aria-setsize` / `aria-posinset` **刻意省略**:只有當列未全數 render 進 DOM(lazy load)或 DOM 序 ≠ 閱讀序時才必需;本元件可見列皆在 DOM 且 DOM 序 = 閱讀序。
397
431
 
398
- **Keyboard 行為**(自建 handler,`tree-view.tsx` handleKeyDown):
432
+ **Accessible name(必填契約)**:`role="treegrid"` 的名稱無法從子節點內容推導,consumer **必須**提供 `aria-label`(直接字串)或 `aria-labelledby`(指向可見標題的 id)其中之一——有可見標題時優先 `aria-labelledby`,無則用 `aria-label`。兩者皆缺 → 螢幕閱讀器只讀出角色名、無法辨識用途。`tree-view.tsx` 於 `process.env.NODE_ENV !== 'production'` 時 `console.warn` 提示,避免上線後才發現無名稱樹(對齊 Button / Tag 的 dev-only 誤用警告 idiom)。列上的每顆按鈕也必須有名稱(見「ARIA」表)。
399
433
 
400
- - Tab — 焦點進入整棵 tree(單一 tab stop)
401
- - ↑/↓ — 在可見 node 之間移動
402
- - → — 展開 node,已展開則移到第一個 child
403
- - ← — 收合 node,leaf 則跳回 parent
404
- - Home / End — 跳到第一個 / 最後一個可見 node
405
- - Enter / Space — 選取目前 node
406
- - Cmd/Ctrl+Shift+↑/↓/→/← — 重排目前 node(僅 `draggable` 時;上下 = 同層移動、→ = 移入 folder、← = 移出到上層,詳「鍵盤重排」段)
434
+ **Keyboard 行為**(`tree-view.tsx` handleKeyDownCapture → 共用純函式 `../../lib/roving-list-keyboard.ts` `resolveRovingKey`):完整按鍵表見「鍵盤導覽」段,此處不重複。
407
435
 
408
- **Focus**:焦點由元件自管(`aria-activedescendant` virtual focus,非 roving tabindex)——DOM focus 固定停在 tree 容器(單一 tab stop,root `tabIndex={0}`),鍵盤移動時 `aria-activedescendant` 指向目前 node,並用內描邊高亮標示(`focus-ring-inset` outline,2026-09-06 起不再是 box-shadow ring;原文 `ring-2 ring-ring ring-inset`,非 `outline`)。**無** focus trap、**無** focus restoration(tree 不是浮層,不需要)。
436
+ **Focus**:**列上的 roving tabindex、真 DOM 焦點**(2026-09-25 取代原本的 `aria-activedescendant` 虛擬焦點:`→` 要把焦點真的交給列裡的按鈕、`←` 再交回列,列若維持虛擬焦點,同一棵樹就有兩種焦點模型)。停靠點那一列 `tabIndex=0`,其餘 `-1`;容器不可聚焦。焦點框 = 列自己的 `focus-visible:focus-ring-inset`(瀏覽器 `:focus-visible` 決定畫不畫:滑鼠點列不畫、鍵盤畫;與 Sidebar 選單鈕同寫法);焦點在列上的按鈕時由按鈕自己畫框,列不再多畫。點列上的按鈕時焦點留在按鈕;點列的其他地方或展開箭頭時焦點給列。**無** focus trap、**無** focus restoration(樹不是浮層);唯一的「還焦點」是鍵盤重排後還給被移動的列(見「鍵盤重排」)。
409
437
 
410
- **「單一 tab stop」範圍限樹的 node 導覽**:auto-render checkbox 與 consumer 傳入的 `checkbox` element 都由 TreeItem 正規化為 `aria-hidden` + `tabIndex={-1}`,只鏡像 treeitem 的 `aria-selected`、不佔 tab 序;但公開 `inlineActions` / `inlineActionsSlot` 經 `ItemInlineAction` 渲染的原生 `<button>` 是各自獨立的 tab stop(對齊 GitHub / VS Code 檔案樹「row action 可 Tab」慣例)。故當某列有 inline actions 時,整頁 Tab 序會依序停在這些按鈕上——「單一 tab stop」指的是**樹形節點導覽**進出點,非「整棵樹含 action 只有一個 tab 停靠」。
438
+ **「單一 Tab 停靠點」包含列上的按鈕**:auto-render checkbox 與 consumer 傳入的 `checkbox` element 由 TreeItem 正規化為 `aria-hidden` + `tabIndex={-1}`(只鏡像列的 `aria-selected`);`inlineActions` / `inlineActionsSlot` 裡每一個可聚焦元素也都是 `tabIndex={-1}`,只能用 `→` / `←` 從自己那一列走到 —— 所以不論幾列有按鈕,Tab 一下就離開整棵樹。
439
+ 2026-09-25 撤回原句「按鈕是各自獨立的 tab stop(對齊 GitHub / VS Code 檔案樹『row action 可 Tab』慣例)」:查證後 GitHub 的樹列按鈕根本不讓 Tab 到([TreeView.tsx#L843-L875](https://github.com/primer/react/blob/f2c075a5d4d0b51a279c39effa18226ad909929d/packages/react/src/TreeView/TreeView.tsx#L843-L875)),VS Code 只有**目前那一列**的按鈕走得到([listWidget.ts#L609-L659](https://github.com/microsoft/vscode/blob/d2dacb76b2f5c1cafcf24dc3c1906e6f4f47918e/src/vs/base/browser/ui/list/listWidget.ts#L609-L659)),那句「對齊」不成立;舊做法在「工程團隊樹」要按 5 下 Tab 才出得去,而且走過的全是別列的按鈕(總帳 B9 / C3)。
411
440
 
412
- **SR 播報(重排)**:鍵盤重排結果經 TreeView 自有 sr-only `role="status"` polite live region 播報,tree 容器 `aria-describedby` 指向 sr-only 操作說明(兩者僅 `draggable` 時渲染、皆在 `role="tree"` 之外——tree 的合法 children 只有 treeitem / group);文案可經 `reorderAnnouncements` prop 覆寫,詳「鍵盤重排」段。
441
+ **SR 播報(重排)**:鍵盤重排結果經 TreeView 自有 sr-only `role="status"` polite live region 播報,容器 `aria-describedby` 指向 sr-only 操作說明(兩者僅 `draggable` 時渲染、皆在 `role="treegrid"` 之外——樹狀表格的合法 children 只有 row / rowgroup);文案可經 `reorderAnnouncements` prop 覆寫,詳「鍵盤重排」段。
413
442
 
414
- **驗證**:Storybook a11y addon panel 應 0 critical violation;鍵盤完整可操作導覽 / 選取 / 展開收合 / **拖曳重排**(無需滑鼠;2026-07-14 v1 補齊——重排走容器 handleKeyDown 的 `Cmd/Ctrl+Shift+方向鍵` 分支,非 dnd-kit `KeyboardSensor`,sensors 仍僅 `PointerSensor`,見「鍵盤重排」段)。WCAG AA contrast ≥ 4.5:1(text)/ 3:1(UI)。
443
+ **驗證**:Storybook a11y addon panel 應 0 critical violation;鍵盤完整可操作導覽 / 選取 / 展開收合 / 列上按鈕 / **拖曳重排**(無需滑鼠;重排走容器 handleKeyDown 的 `Cmd/Ctrl+Shift+方向鍵` 分支,非 dnd-kit `KeyboardSensor`,sensors 仍僅 `PointerSensor`,見「鍵盤重排」段)。鍵盤路線的機械驗證:`scripts/test-roving-list-keyboard.mjs` + `scripts/test-tree-keyboard-route.mjs`(判定表)+ `scripts/tree-view-keyboard-route-invariant.mjs`(瀏覽器實按,含對照組)+ 展示頁「列上的動作」story 的 play。WCAG AA contrast ≥ 4.5:1(text)/ 3:1(UI)。
415
444
 
416
445
  ## 被引用(auto-maintained,Dim 3 reciprocal audit)
417
446