@qijenchen/design-system 0.1.0-beta.85 → 0.1.0-beta.87

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 (481) hide show
  1. package/AGENTS.md +210 -0
  2. package/CLAUDE.md +17 -191
  3. package/cli-init.mjs +30 -2
  4. package/dist/components/Accordion/accordion.d.ts +2 -2
  5. package/dist/components/Accordion/accordion.d.ts.map +1 -1
  6. package/dist/components/Accordion/accordion.js.map +1 -1
  7. package/dist/components/Avatar/avatar.js +1 -1
  8. package/dist/components/Avatar/avatar.js.map +1 -1
  9. package/dist/components/BulkActionBar/bulk-action-bar.d.ts +1 -1
  10. package/dist/components/BulkActionBar/bulk-action-bar.d.ts.map +1 -1
  11. package/dist/components/BulkActionBar/bulk-action-bar.js +3 -3
  12. package/dist/components/BulkActionBar/bulk-action-bar.js.map +1 -1
  13. package/dist/components/Button/button-group.d.ts +2 -2
  14. package/dist/components/Button/button-group.d.ts.map +1 -1
  15. package/dist/components/Button/button-group.js +2 -2
  16. package/dist/components/Button/button-group.js.map +1 -1
  17. package/dist/components/Button/button.d.ts +2 -1
  18. package/dist/components/Button/button.d.ts.map +1 -1
  19. package/dist/components/Button/button.js +1 -1
  20. package/dist/components/Button/button.js.map +1 -1
  21. package/dist/components/Carousel/carousel.d.ts +14 -8
  22. package/dist/components/Carousel/carousel.d.ts.map +1 -1
  23. package/dist/components/Carousel/carousel.js +64 -37
  24. package/dist/components/Carousel/carousel.js.map +1 -1
  25. package/dist/components/Checkbox/checkbox-group.d.ts.map +1 -1
  26. package/dist/components/Checkbox/checkbox-group.js +21 -14
  27. package/dist/components/Checkbox/checkbox-group.js.map +1 -1
  28. package/dist/components/Checkbox/checkbox.d.ts +2 -2
  29. package/dist/components/Checkbox/checkbox.d.ts.map +1 -1
  30. package/dist/components/Checkbox/checkbox.js +49 -10
  31. package/dist/components/Checkbox/checkbox.js.map +1 -1
  32. package/dist/components/Combobox/combobox.d.ts +15 -6
  33. package/dist/components/Combobox/combobox.d.ts.map +1 -1
  34. package/dist/components/Combobox/combobox.js +13 -7
  35. package/dist/components/Combobox/combobox.js.map +1 -1
  36. package/dist/components/Command/command.d.ts.map +1 -1
  37. package/dist/components/Command/command.js +11 -2
  38. package/dist/components/Command/command.js.map +1 -1
  39. package/dist/components/DataTable/cell-registry.d.ts +5 -5
  40. package/dist/components/DataTable/cell-registry.d.ts.map +1 -1
  41. package/dist/components/DataTable/cell-registry.js +41 -38
  42. package/dist/components/DataTable/cell-registry.js.map +1 -1
  43. package/dist/components/DataTable/column-types.d.ts +1 -17
  44. package/dist/components/DataTable/column-types.d.ts.map +1 -1
  45. package/dist/components/DataTable/column-types.js.map +1 -1
  46. package/dist/components/DataTable/data-table-column-visibility-panel.d.ts.map +1 -1
  47. package/dist/components/DataTable/data-table-column-visibility-panel.js +71 -64
  48. package/dist/components/DataTable/data-table-column-visibility-panel.js.map +1 -1
  49. package/dist/components/DataTable/data-table-filter-panel.d.ts +0 -2
  50. package/dist/components/DataTable/data-table-filter-panel.d.ts.map +1 -1
  51. package/dist/components/DataTable/data-table-filter-panel.js +3 -4
  52. package/dist/components/DataTable/data-table-filter-panel.js.map +1 -1
  53. package/dist/components/DataTable/data-table-interaction-layer.d.ts +2 -2
  54. package/dist/components/DataTable/data-table-interaction-layer.js.map +1 -1
  55. package/dist/components/DataTable/data-table-sort-manager.js +2 -2
  56. package/dist/components/DataTable/data-table-sort-manager.js.map +1 -1
  57. package/dist/components/DataTable/data-table.d.ts +1 -1
  58. package/dist/components/DataTable/data-table.d.ts.map +1 -1
  59. package/dist/components/DataTable/data-table.js +44 -32
  60. package/dist/components/DataTable/data-table.js.map +1 -1
  61. package/dist/components/DatePicker/date-picker.d.ts +3 -3
  62. package/dist/components/DatePicker/date-picker.js +4 -4
  63. package/dist/components/DatePicker/date-picker.js.map +1 -1
  64. package/dist/components/DescriptionList/description-list.d.ts +3 -3
  65. package/dist/components/DescriptionList/description-list.d.ts.map +1 -1
  66. package/dist/components/DescriptionList/description-list.js +6 -6
  67. package/dist/components/DescriptionList/description-list.js.map +1 -1
  68. package/dist/components/Dialog/dialog.d.ts +2 -2
  69. package/dist/components/Dialog/dialog.d.ts.map +1 -1
  70. package/dist/components/Dialog/dialog.js.map +1 -1
  71. package/dist/components/DropdownMenu/dropdown-menu.d.ts +8 -3
  72. package/dist/components/DropdownMenu/dropdown-menu.d.ts.map +1 -1
  73. package/dist/components/DropdownMenu/dropdown-menu.js +7 -7
  74. package/dist/components/DropdownMenu/dropdown-menu.js.map +1 -1
  75. package/dist/components/Field/field-context.d.ts +16 -10
  76. package/dist/components/Field/field-context.d.ts.map +1 -1
  77. package/dist/components/Field/field-context.js.map +1 -1
  78. package/dist/components/Field/field-types.d.ts +2 -2
  79. package/dist/components/Field/field-types.d.ts.map +1 -1
  80. package/dist/components/Field/field-types.js.map +1 -1
  81. package/dist/components/Field/field-wrapper.d.ts +20 -4
  82. package/dist/components/Field/field-wrapper.d.ts.map +1 -1
  83. package/dist/components/Field/field-wrapper.js +30 -31
  84. package/dist/components/Field/field-wrapper.js.map +1 -1
  85. package/dist/components/Field/field.d.ts +1 -1
  86. package/dist/components/Field/field.d.ts.map +1 -1
  87. package/dist/components/Field/field.js +25 -3
  88. package/dist/components/Field/field.js.map +1 -1
  89. package/dist/components/Field/use-form-validation.d.ts +1 -1
  90. package/dist/components/Field/use-form-validation.js.map +1 -1
  91. package/dist/components/FileItem/file-item.d.ts.map +1 -1
  92. package/dist/components/FileItem/file-item.js +2 -5
  93. package/dist/components/FileItem/file-item.js.map +1 -1
  94. package/dist/components/FileUpload/file-upload.d.ts.map +1 -1
  95. package/dist/components/FileUpload/file-upload.js +5 -1
  96. package/dist/components/FileUpload/file-upload.js.map +1 -1
  97. package/dist/components/FileViewer/file-viewer.d.ts.map +1 -1
  98. package/dist/components/FileViewer/file-viewer.js +2 -1
  99. package/dist/components/FileViewer/file-viewer.js.map +1 -1
  100. package/dist/components/HoverCard/hover-card.d.ts +2 -2
  101. package/dist/components/HoverCard/hover-card.d.ts.map +1 -1
  102. package/dist/components/HoverCard/hover-card.js +4 -2
  103. package/dist/components/HoverCard/hover-card.js.map +1 -1
  104. package/dist/components/InlineEdit/inline-edit.d.ts +23 -16
  105. package/dist/components/InlineEdit/inline-edit.d.ts.map +1 -1
  106. package/dist/components/InlineEdit/inline-edit.js +104 -62
  107. package/dist/components/InlineEdit/inline-edit.js.map +1 -1
  108. package/dist/components/Input/input.d.ts +1 -1
  109. package/dist/components/Input/input.d.ts.map +1 -1
  110. package/dist/components/Input/input.js +6 -6
  111. package/dist/components/Input/input.js.map +1 -1
  112. package/dist/components/LinkInput/link-input.d.ts +5 -5
  113. package/dist/components/LinkInput/link-input.d.ts.map +1 -1
  114. package/dist/components/LinkInput/link-input.js +3 -3
  115. package/dist/components/LinkInput/link-input.js.map +1 -1
  116. package/dist/components/Notice/notice.d.ts +17 -1
  117. package/dist/components/Notice/notice.d.ts.map +1 -1
  118. package/dist/components/Notice/notice.js +7 -1
  119. package/dist/components/Notice/notice.js.map +1 -1
  120. package/dist/components/NumberInput/number-input.d.ts +1 -1
  121. package/dist/components/NumberInput/number-input.d.ts.map +1 -1
  122. package/dist/components/NumberInput/number-input.js +12 -1
  123. package/dist/components/NumberInput/number-input.js.map +1 -1
  124. package/dist/components/PeoplePicker/avatar-stack-overflow.d.ts +1 -1
  125. package/dist/components/PeoplePicker/avatar-stack-overflow.js.map +1 -1
  126. package/dist/components/PeoplePicker/people-picker.d.ts +5 -5
  127. package/dist/components/PeoplePicker/people-picker.d.ts.map +1 -1
  128. package/dist/components/PeoplePicker/people-picker.js +7 -5
  129. package/dist/components/PeoplePicker/people-picker.js.map +1 -1
  130. package/dist/components/PeoplePicker/person-display.d.ts +1 -1
  131. package/dist/components/PeoplePicker/person-display.js.map +1 -1
  132. package/dist/components/ProfileCard/profile-card.d.ts +1 -1
  133. package/dist/components/ProfileCard/profile-card.js.map +1 -1
  134. package/dist/components/RadioGroup/radio-group.d.ts +3 -3
  135. package/dist/components/RadioGroup/radio-group.d.ts.map +1 -1
  136. package/dist/components/RadioGroup/radio-group.js +1 -1
  137. package/dist/components/RadioGroup/radio-group.js.map +1 -1
  138. package/dist/components/Rating/rating.d.ts.map +1 -1
  139. package/dist/components/Rating/rating.js +4 -0
  140. package/dist/components/Rating/rating.js.map +1 -1
  141. package/dist/components/ScrollArea/scroll-area.d.ts +3 -2
  142. package/dist/components/ScrollArea/scroll-area.d.ts.map +1 -1
  143. package/dist/components/ScrollArea/scroll-area.js.map +1 -1
  144. package/dist/components/SegmentedControl/segmented-control.d.ts +4 -3
  145. package/dist/components/SegmentedControl/segmented-control.d.ts.map +1 -1
  146. package/dist/components/SegmentedControl/segmented-control.js.map +1 -1
  147. package/dist/components/Select/select.d.ts +16 -6
  148. package/dist/components/Select/select.d.ts.map +1 -1
  149. package/dist/components/Select/select.js +17 -5
  150. package/dist/components/Select/select.js.map +1 -1
  151. package/dist/components/SelectMenu/select-menu.d.ts +17 -1
  152. package/dist/components/SelectMenu/select-menu.d.ts.map +1 -1
  153. package/dist/components/SelectMenu/select-menu.js +17 -3
  154. package/dist/components/SelectMenu/select-menu.js.map +1 -1
  155. package/dist/components/SelectionControl/boolean-value.d.ts +1 -1
  156. package/dist/components/SelectionControl/boolean-value.js.map +1 -1
  157. package/dist/components/Sheet/sheet.d.ts +2 -2
  158. package/dist/components/Sheet/sheet.d.ts.map +1 -1
  159. package/dist/components/Sheet/sheet.js.map +1 -1
  160. package/dist/components/Sidebar/sidebar.d.ts +1 -1
  161. package/dist/components/Sidebar/sidebar.js +2 -2
  162. package/dist/components/Sidebar/sidebar.js.map +1 -1
  163. package/dist/components/Slider/slider.d.ts.map +1 -1
  164. package/dist/components/Slider/slider.js +6 -3
  165. package/dist/components/Slider/slider.js.map +1 -1
  166. package/dist/components/Steps/steps.d.ts +6 -4
  167. package/dist/components/Steps/steps.d.ts.map +1 -1
  168. package/dist/components/Steps/steps.js +19 -5
  169. package/dist/components/Steps/steps.js.map +1 -1
  170. package/dist/components/Switch/switch.d.ts +4 -4
  171. package/dist/components/Switch/switch.d.ts.map +1 -1
  172. package/dist/components/Switch/switch.js +3 -1
  173. package/dist/components/Switch/switch.js.map +1 -1
  174. package/dist/components/Tabs/tabs.d.ts +1 -1
  175. package/dist/components/Tabs/tabs.d.ts.map +1 -1
  176. package/dist/components/Tabs/tabs.js +73 -23
  177. package/dist/components/Tabs/tabs.js.map +1 -1
  178. package/dist/components/Tag/tag.d.ts +3 -1
  179. package/dist/components/Tag/tag.d.ts.map +1 -1
  180. package/dist/components/Tag/tag.js +2 -2
  181. package/dist/components/Tag/tag.js.map +1 -1
  182. package/dist/components/Textarea/textarea.d.ts +4 -4
  183. package/dist/components/Textarea/textarea.d.ts.map +1 -1
  184. package/dist/components/Textarea/textarea.js +15 -15
  185. package/dist/components/Textarea/textarea.js.map +1 -1
  186. package/dist/components/TimePicker/time-picker.d.ts +4 -4
  187. package/dist/components/TimePicker/time-picker.d.ts.map +1 -1
  188. package/dist/components/TimePicker/time-picker.js +8 -5
  189. package/dist/components/TimePicker/time-picker.js.map +1 -1
  190. package/dist/components/Tooltip/tooltip.d.ts.map +1 -1
  191. package/dist/components/Tooltip/tooltip.js +27 -23
  192. package/dist/components/Tooltip/tooltip.js.map +1 -1
  193. package/dist/components/TreeView/tree-view.d.ts.map +1 -1
  194. package/dist/components/TreeView/tree-view.js +16 -0
  195. package/dist/components/TreeView/tree-view.js.map +1 -1
  196. package/dist/index.d.ts +7 -7
  197. package/dist/index.d.ts.map +1 -1
  198. package/dist/index.js +6 -13
  199. package/dist/index.js.map +1 -1
  200. package/dist/lib/drag-visual.d.ts +2 -2
  201. package/dist/lib/drag-visual.js.map +1 -1
  202. package/dist/lib/utils.d.ts +1 -0
  203. package/dist/lib/utils.d.ts.map +1 -1
  204. package/dist/lib/utils.js +21 -1
  205. package/dist/lib/utils.js.map +1 -1
  206. package/dist/patterns/element-anatomy/item-anatomy.d.ts +2 -0
  207. package/dist/patterns/element-anatomy/item-anatomy.d.ts.map +1 -1
  208. package/dist/patterns/element-anatomy/item-anatomy.js +2 -2
  209. package/dist/patterns/element-anatomy/item-anatomy.js.map +1 -1
  210. package/dist/tokens/categorical-color.d.ts +3 -3
  211. package/dist/tokens/categorical-color.d.ts.map +1 -1
  212. package/dist/tokens/categorical-color.js +2 -2
  213. package/dist/tokens/categorical-color.js.map +1 -1
  214. package/ds-canonical/fork/AGENTS.md +213 -0
  215. package/ds-canonical/fork/codex/agents/skills/independent-review/SKILL.md +33 -0
  216. package/ds-canonical/fork/codex/hooks.json +16 -0
  217. package/ds-canonical/fork/governance.lock +22 -7
  218. package/ds-canonical/fork/hooks/check_ds_anchor_preflight.sh +22 -1
  219. package/ds-canonical/fork/hooks/inject_deploy_url_after_push.sh +14 -2
  220. package/ds-canonical/fork/manifest.json +10 -1
  221. package/ds-canonical/fork/preamble.md +3 -2
  222. package/ds-canonical/fork/skills/product-ui-audit/SKILL.md +7 -7
  223. package/ds-canonical/fork/skills/ux-audit/SKILL.md +1 -1
  224. package/ds-canonical/fork/skills/visual-audit/SKILL.md +0 -1
  225. package/ds-canonical/hooks/check_codex_brief_invariants.sh +9 -3
  226. package/ds-canonical/hooks/check_ds_anchor_preflight.sh +22 -1
  227. package/ds-canonical/hooks/check_overlay_open_focus_escape_probe.sh +19 -2
  228. package/ds-canonical/hooks/check_plugin_fork_health.sh +17 -1
  229. package/ds-canonical/hooks/check_post_main_ssot_propagate.sh +21 -4
  230. package/ds-canonical/hooks/inject_deploy_url_after_push.sh +14 -2
  231. package/ds-canonical/hooks/session_start_governance_check.sh +10 -2
  232. package/ds-canonical/references/certified-surfaces.md +20 -0
  233. package/ds-canonical/references/preflight-gate-baseline.json +5 -0
  234. package/ds-canonical/references/principle-dim-map.json +20 -2
  235. package/ds-canonical/references/props-naming.md +2 -1
  236. package/ds-canonical/references/ssot-index.md +2 -2
  237. package/ds-canonical/references/story-baseline-registry.json +2 -2
  238. package/ds-canonical/rules/story-rules.md +1 -1
  239. package/ds-canonical/rules/ui-development.md +2 -1
  240. package/ds-canonical/skills/codex-collab/SKILL.md +7 -7
  241. package/ds-canonical/skills/codex-collab/references/brief-template.md +5 -1
  242. package/ds-canonical/skills/component-quality-gate/SKILL.md +1 -1
  243. package/ds-canonical/skills/deep-audit-cross-codex/SKILL.md +5 -5
  244. package/ds-canonical/skills/design-system-audit/SKILL.md +5 -5
  245. package/ds-canonical/skills/product-ui-audit/SKILL.md +7 -7
  246. package/ds-canonical/skills/ux-audit/SKILL.md +1 -1
  247. package/ds-canonical/skills/visual-audit/SKILL.md +0 -1
  248. package/ds-story-manifest.json +21 -21
  249. package/llms-full.txt +3 -3
  250. package/llms.txt +1 -1
  251. package/package.json +2 -1
  252. package/src/components/Accordion/accordion.anatomy.stories.tsx +11 -11
  253. package/src/components/Accordion/accordion.principles.stories.tsx +21 -6
  254. package/src/components/Accordion/accordion.spec.md +10 -1
  255. package/src/components/Accordion/accordion.stories.tsx +10 -5
  256. package/src/components/Accordion/accordion.tsx +7 -2
  257. package/src/components/Alert/alert.principles.stories.tsx +2 -8
  258. package/src/components/AppShell/app-shell.anatomy.stories.tsx +19 -13
  259. package/src/components/AppShell/app-shell.principles.stories.tsx +21 -21
  260. package/src/components/AppShell/app-shell.spec.md +4 -0
  261. package/src/components/AppShell/app-shell.stories.tsx +4 -5
  262. package/src/components/Avatar/avatar.tsx +1 -1
  263. package/src/components/Badge/badge.spec.md +2 -2
  264. package/src/components/Breadcrumb/breadcrumb.anatomy.stories.tsx +7 -3
  265. package/src/components/Breadcrumb/breadcrumb.principles.stories.tsx +4 -1
  266. package/src/components/Breadcrumb/breadcrumb.stories.tsx +1 -1
  267. package/src/components/BulkActionBar/bulk-action-bar.anatomy.stories.tsx +6 -6
  268. package/src/components/BulkActionBar/bulk-action-bar.principles.stories.tsx +18 -6
  269. package/src/components/BulkActionBar/bulk-action-bar.spec.md +15 -5
  270. package/src/components/BulkActionBar/bulk-action-bar.stories.tsx +8 -24
  271. package/src/components/BulkActionBar/bulk-action-bar.tsx +4 -4
  272. package/src/components/Button/button-group.tsx +4 -4
  273. package/src/components/Button/button.anatomy.stories.tsx +26 -7
  274. package/src/components/Button/button.principles.stories.tsx +1 -1
  275. package/src/components/Button/button.spec.md +2 -2
  276. package/src/components/Button/button.stories.tsx +22 -28
  277. package/src/components/Button/button.tsx +2 -1
  278. package/src/components/Calendar/calendar.anatomy.stories.tsx +1 -1
  279. package/src/components/Calendar/calendar.spec.md +1 -1
  280. package/src/components/Carousel/carousel.spec.md +17 -1
  281. package/src/components/Carousel/carousel.tsx +93 -42
  282. package/src/components/Checkbox/checkbox-group.tsx +26 -15
  283. package/src/components/Checkbox/checkbox.anatomy.stories.tsx +3 -3
  284. package/src/components/Checkbox/checkbox.spec.md +12 -3
  285. package/src/components/Checkbox/checkbox.stories.tsx +4 -4
  286. package/src/components/Checkbox/checkbox.tsx +79 -19
  287. package/src/components/Coachmark/coachmark.anatomy.stories.tsx +5 -5
  288. package/src/components/Coachmark/coachmark.principles.stories.tsx +1 -1
  289. package/src/components/Combobox/combobox.anatomy.stories.tsx +11 -4
  290. package/src/components/Combobox/combobox.principles.stories.tsx +10 -11
  291. package/src/components/Combobox/combobox.spec.md +6 -2
  292. package/src/components/Combobox/combobox.stories.tsx +9 -36
  293. package/src/components/Combobox/combobox.tsx +42 -27
  294. package/src/components/Command/command.spec.md +3 -3
  295. package/src/components/Command/command.tsx +9 -2
  296. package/src/components/DataTable/cell-registry.tsx +75 -76
  297. package/src/components/DataTable/column-types.ts +4 -17
  298. package/src/components/DataTable/data-table-column-visibility-panel.tsx +20 -7
  299. package/src/components/DataTable/data-table-filter-panel.tsx +6 -6
  300. package/src/components/DataTable/data-table-interaction-layer.tsx +3 -3
  301. package/src/components/DataTable/data-table-sort-manager.tsx +2 -2
  302. package/src/components/DataTable/data-table.anatomy.stories.tsx +25 -24
  303. package/src/components/DataTable/data-table.css +2 -3
  304. package/src/components/DataTable/data-table.principles.stories.tsx +17 -11
  305. package/src/components/DataTable/data-table.spec.md +8 -7
  306. package/src/components/DataTable/data-table.stories.tsx +39 -29
  307. package/src/components/DataTable/data-table.tsx +90 -67
  308. package/src/components/DataTable/filter-operators.spec.md +2 -0
  309. package/src/components/DateGrid/date-grid.spec.md +1 -1
  310. package/src/components/DatePicker/date-picker.anatomy.stories.tsx +9 -9
  311. package/src/components/DatePicker/date-picker.principles.stories.tsx +4 -4
  312. package/src/components/DatePicker/date-picker.spec.md +10 -9
  313. package/src/components/DatePicker/date-picker.stories.tsx +9 -9
  314. package/src/components/DatePicker/date-picker.tsx +13 -13
  315. package/src/components/DescriptionList/description-list.anatomy.stories.tsx +5 -5
  316. package/src/components/DescriptionList/description-list.principles.stories.tsx +2 -2
  317. package/src/components/DescriptionList/description-list.spec.md +12 -12
  318. package/src/components/DescriptionList/description-list.stories.tsx +3 -3
  319. package/src/components/DescriptionList/description-list.tsx +10 -10
  320. package/src/components/Dialog/dialog.tsx +4 -1
  321. package/src/components/DropdownMenu/dropdown-menu.tsx +27 -14
  322. package/src/components/Empty/empty.spec.md +0 -1
  323. package/src/components/Field/field-context.ts +17 -11
  324. package/src/components/Field/field-controls.spec.md +46 -37
  325. package/src/components/Field/field-types.ts +14 -12
  326. package/src/components/Field/field-wrapper.tsx +59 -43
  327. package/src/components/Field/field.anatomy.stories.tsx +6 -6
  328. package/src/components/Field/field.principles.stories.tsx +5 -28
  329. package/src/components/Field/field.spec.md +5 -4
  330. package/src/components/Field/field.stories.tsx +103 -72
  331. package/src/components/Field/field.tsx +36 -4
  332. package/src/components/Field/form-validation.spec.md +4 -0
  333. package/src/components/Field/use-form-validation.ts +1 -1
  334. package/src/components/FieldControlGroup/field-control-group.principles.stories.tsx +3 -3
  335. package/src/components/FieldControlGroup/field-control-group.spec.md +3 -3
  336. package/src/components/FileItem/file-item.tsx +10 -8
  337. package/src/components/FileUpload/file-upload.principles.stories.tsx +7 -7
  338. package/src/components/FileUpload/file-upload.spec.md +3 -2
  339. package/src/components/FileUpload/file-upload.tsx +5 -1
  340. package/src/components/FileViewer/file-viewer.anatomy.stories.tsx +1 -1
  341. package/src/components/FileViewer/file-viewer.principles.stories.tsx +1 -1
  342. package/src/components/FileViewer/file-viewer.spec.md +1 -1
  343. package/src/components/FileViewer/file-viewer.stories.tsx +1 -1
  344. package/src/components/FileViewer/file-viewer.tsx +3 -2
  345. package/src/components/HoverCard/hover-card.anatomy.stories.tsx +6 -4
  346. package/src/components/HoverCard/hover-card.principles.stories.tsx +6 -1
  347. package/src/components/HoverCard/hover-card.spec.md +2 -2
  348. package/src/components/HoverCard/hover-card.tsx +4 -2
  349. package/src/components/InlineEdit/inline-edit.anatomy.stories.tsx +184 -0
  350. package/src/components/InlineEdit/inline-edit.principles.stories.tsx +142 -0
  351. package/src/components/InlineEdit/inline-edit.spec.md +71 -46
  352. package/src/components/InlineEdit/inline-edit.stories.tsx +20 -19
  353. package/src/components/InlineEdit/inline-edit.tsx +211 -125
  354. package/src/components/Input/input.anatomy.stories.tsx +15 -15
  355. package/src/components/Input/input.principles.stories.tsx +3 -3
  356. package/src/components/Input/input.spec.md +4 -4
  357. package/src/components/Input/input.stories.tsx +3 -3
  358. package/src/components/Input/input.tsx +13 -13
  359. package/src/components/LinkInput/link-input.anatomy.stories.tsx +2 -2
  360. package/src/components/LinkInput/link-input.principles.stories.tsx +1 -1
  361. package/src/components/LinkInput/link-input.spec.md +6 -2
  362. package/src/components/LinkInput/link-input.stories.tsx +8 -8
  363. package/src/components/LinkInput/link-input.tsx +21 -19
  364. package/src/components/Notice/notice.principles.stories.tsx +1 -1
  365. package/src/components/Notice/notice.tsx +8 -2
  366. package/src/components/NumberInput/number-input.anatomy.stories.tsx +6 -6
  367. package/src/components/NumberInput/number-input.principles.stories.tsx +4 -4
  368. package/src/components/NumberInput/number-input.spec.md +6 -6
  369. package/src/components/NumberInput/number-input.stories.tsx +4 -4
  370. package/src/components/NumberInput/number-input.tsx +17 -5
  371. package/src/components/OverflowIndicator/overflow-indicator.anatomy.stories.tsx +8 -4
  372. package/src/components/OverflowIndicator/overflow-indicator.principles.stories.tsx +6 -7
  373. package/src/components/OverflowIndicator/overflow-indicator.spec.md +1 -1
  374. package/src/components/OverflowIndicator/overflow-indicator.stories.tsx +6 -30
  375. package/src/components/PeoplePicker/avatar-stack-overflow.ts +1 -1
  376. package/src/components/PeoplePicker/people-picker.anatomy.stories.tsx +13 -10
  377. package/src/components/PeoplePicker/people-picker.principles.stories.tsx +5 -1
  378. package/src/components/PeoplePicker/people-picker.stories.tsx +5 -5
  379. package/src/components/PeoplePicker/people-picker.tsx +39 -20
  380. package/src/components/PeoplePicker/person-display.tsx +3 -3
  381. package/src/components/Popover/popover.anatomy.stories.tsx +10 -7
  382. package/src/components/Popover/popover.principles.stories.tsx +49 -28
  383. package/src/components/Popover/popover.spec.md +4 -2
  384. package/src/components/ProfileCard/profile-card.anatomy.stories.tsx +1 -1
  385. package/src/components/ProfileCard/profile-card.tsx +1 -1
  386. package/src/components/ProgressBar/progress-bar.anatomy.stories.tsx +14 -13
  387. package/src/components/ProgressBar/progress-bar.principles.stories.tsx +8 -23
  388. package/src/components/ProgressBar/progress-bar.stories.tsx +8 -2
  389. package/src/components/README.md +1 -0
  390. package/src/components/RadioGroup/radio-group.anatomy.stories.tsx +8 -3
  391. package/src/components/RadioGroup/radio-group.stories.tsx +3 -3
  392. package/src/components/RadioGroup/radio-group.tsx +19 -14
  393. package/src/components/Rating/rating.anatomy.stories.tsx +1 -1
  394. package/src/components/Rating/rating.principles.stories.tsx +3 -3
  395. package/src/components/Rating/rating.spec.md +2 -2
  396. package/src/components/Rating/rating.stories.tsx +4 -18
  397. package/src/components/Rating/rating.tsx +5 -1
  398. package/src/components/ScrollArea/scroll-area.principles.stories.tsx +1 -1
  399. package/src/components/ScrollArea/scroll-area.spec.md +1 -1
  400. package/src/components/ScrollArea/scroll-area.tsx +5 -2
  401. package/src/components/SegmentedControl/segmented-control.principles.stories.tsx +4 -1
  402. package/src/components/SegmentedControl/segmented-control.spec.md +5 -10
  403. package/src/components/SegmentedControl/segmented-control.tsx +6 -3
  404. package/src/components/Select/select.anatomy.stories.tsx +13 -7
  405. package/src/components/Select/select.principles.stories.tsx +3 -3
  406. package/src/components/Select/select.spec.md +10 -1
  407. package/src/components/Select/select.stories.tsx +41 -27
  408. package/src/components/Select/select.tsx +44 -26
  409. package/src/components/SelectMenu/select-menu.anatomy.stories.tsx +44 -26
  410. package/src/components/SelectMenu/select-menu.principles.stories.tsx +0 -1
  411. package/src/components/SelectMenu/select-menu.stories.tsx +7 -19
  412. package/src/components/SelectMenu/select-menu.tsx +38 -5
  413. package/src/components/SelectionControl/boolean-value.tsx +2 -2
  414. package/src/components/SelectionControl/selection-item.anatomy.stories.tsx +1 -1
  415. package/src/components/SelectionControl/selection-item.principles.stories.tsx +4 -4
  416. package/src/components/Separator/separator.principles.stories.tsx +1 -1
  417. package/src/components/Sheet/sheet.principles.stories.tsx +5 -3
  418. package/src/components/Sheet/sheet.tsx +4 -1
  419. package/src/components/Sidebar/sidebar.tsx +1 -1
  420. package/src/components/Slider/slider.anatomy.stories.tsx +5 -4
  421. package/src/components/Slider/slider.principles.stories.tsx +6 -6
  422. package/src/components/Slider/slider.spec.md +13 -1
  423. package/src/components/Slider/slider.stories.tsx +44 -32
  424. package/src/components/Slider/slider.tsx +13 -4
  425. package/src/components/Steps/steps.anatomy.stories.tsx +7 -7
  426. package/src/components/Steps/steps.principles.stories.tsx +1 -1
  427. package/src/components/Steps/steps.spec.md +4 -2
  428. package/src/components/Steps/steps.stories.tsx +4 -56
  429. package/src/components/Steps/steps.tsx +31 -7
  430. package/src/components/Switch/switch.anatomy.stories.tsx +3 -3
  431. package/src/components/Switch/switch.principles.stories.tsx +1 -1
  432. package/src/components/Switch/switch.spec.md +4 -4
  433. package/src/components/Switch/switch.stories.tsx +3 -3
  434. package/src/components/Switch/switch.tsx +16 -10
  435. package/src/components/Tabs/tabs.anatomy.stories.tsx +6 -0
  436. package/src/components/Tabs/tabs.principles.stories.tsx +10 -2
  437. package/src/components/Tabs/tabs.spec.md +5 -2
  438. package/src/components/Tabs/tabs.stories.tsx +36 -44
  439. package/src/components/Tabs/tabs.tsx +101 -36
  440. package/src/components/Tag/tag.anatomy.stories.tsx +2 -2
  441. package/src/components/Tag/tag.principles.stories.tsx +4 -4
  442. package/src/components/Tag/tag.spec.md +4 -1
  443. package/src/components/Tag/tag.tsx +5 -3
  444. package/src/components/Textarea/textarea.anatomy.stories.tsx +6 -6
  445. package/src/components/Textarea/textarea.spec.md +5 -5
  446. package/src/components/Textarea/textarea.tsx +24 -24
  447. package/src/components/TimePicker/time-picker.anatomy.stories.tsx +12 -11
  448. package/src/components/TimePicker/time-picker.principles.stories.tsx +37 -44
  449. package/src/components/TimePicker/time-picker.spec.md +6 -5
  450. package/src/components/TimePicker/time-picker.stories.tsx +3 -3
  451. package/src/components/TimePicker/time-picker.tsx +24 -14
  452. package/src/components/Toast/toast.principles.stories.tsx +1 -1
  453. package/src/components/Toast/toast.stories.tsx +1 -1
  454. package/src/components/Tooltip/tooltip.anatomy.stories.tsx +15 -6
  455. package/src/components/Tooltip/tooltip.principles.stories.tsx +4 -4
  456. package/src/components/Tooltip/tooltip.spec.md +9 -3
  457. package/src/components/Tooltip/tooltip.stories.tsx +1 -1
  458. package/src/components/Tooltip/tooltip.tsx +12 -2
  459. package/src/components/TreeView/tree-view.anatomy.stories.tsx +16 -14
  460. package/src/components/TreeView/tree-view.principles.stories.tsx +44 -11
  461. package/src/components/TreeView/tree-view.spec.md +8 -6
  462. package/src/components/TreeView/tree-view.stories.tsx +9 -43
  463. package/src/components/TreeView/tree-view.tsx +36 -0
  464. package/src/index.ts +13 -10
  465. package/src/lib/drag-visual.ts +2 -2
  466. package/src/lib/utils.ts +19 -0
  467. package/src/patterns/action-bar/action-bar.spec.md +1 -0
  468. package/src/patterns/element-anatomy/inline-action.spec.md +1 -1
  469. package/src/patterns/element-anatomy/item-anatomy.spec.md +4 -0
  470. package/src/patterns/element-anatomy/item-anatomy.tsx +4 -2
  471. package/src/patterns/header-canonical/header-canonical.spec.md +9 -0
  472. package/src/patterns/header-canonical/header-canonical.stories.tsx +31 -5
  473. package/src/tokens/categorical-color.ts +5 -5
  474. package/src/tokens/color/color.spec.md +4 -4
  475. package/src/tokens/color/primitives.css +9 -2
  476. package/src/tokens/motion/motion.spec.md +6 -7
  477. package/src/tokens/opacity/opacity.spec.md +1 -1
  478. package/src/tokens/orphan-tokens.spec.md +1 -1
  479. package/src/tokens/typography/typography.spec.md +2 -0
  480. package/src/tokens/uiSize/uiSize.css +5 -0
  481. package/src/tokens/uiSize/uiSize.spec.md +1 -1
@@ -1,30 +1,33 @@
1
1
  // ── 消費的 SSOT ──
2
- // - components/Field/field-controls.spec.md(「Field 框架地圖」段:InlineEdit = read↔edit 疊在 Field 之上;edit-in-place)
2
+ // - components/Field/field-controls.spec.md(「軸二 就地編輯 host」段:InlineEdit = view↔edit 二態疊在 Field 之上;edit-in-place + editable 閘)
3
3
  // - components/Field/field-edit-keys.ts(makeEditSettleKeyHandler = Enter/Esc + 中文 IME guard 結算 SSOT,與 DataTable cell 同源)
4
+ // - components/Field/field-wrapper.tsx(fieldViewGeometry = view 幾何 class SSOT〔純值/標題路徑〕;fieldDisplayTextClass = view 字級)
4
5
  // - components/Input/input.tsx / Textarea/textarea.tsx(預設 renderEdit;不重刻 input chrome)
5
- // - components/Field/field-context.ts(FieldSize 型別)
6
- // - tokens/uiSize/uiSize.css(--field-px 水平內距 SSOT / --field-height-{sm,md,lg} read 態高度)
6
+ // - components/Field/field-context.ts(useFieldContext / useResolvedFieldSize;接 size/orientation/labelId cascade)
7
+ // - tokens/uiSize/uiSize.css(--field-px 水平內距 SSOT / --field-height-{sm,md,lg} view 態高度)
7
8
  // - tokens/color/color.spec.md(bg-neutral-hover = hover 回饋)
8
- // - components/Field/field-wrapper.tsx(read focus = border-primary,消費 Field edit focus 藍框語言;非 Button ring)
9
9
  // - 世界級對照(泛型 readView/editView 就地編輯 + read 態底色 tint + 隱形 Pressable):
10
- // Atlassian inline-edit(readView/editView 兩 render prop + 泛型 <FieldValue>)
11
- // https://github.com/pioug/atlassian-frontend-mirror/blob/main/design-system/inline-edit/src/types.tsx
12
- // read-view hover = 背景 tint 非邊框 + 隱形 Pressable 供鍵盤 focus+Enter:
10
+ // Atlassian inline-edit(readView/editView 兩 render prop + read wrapper 靠負邊距對齊 edit)
13
11
  // https://github.com/pioug/atlassian-frontend-mirror/blob/main/design-system/inline-edit/src/internal/read-view.tsx
12
+ // 就地編輯無 disabled 態(鎖定 = 純 view 無入口、不灰化):MUI X isCellEditable / cellMode
13
+ // https://mui.com/x/react-data-grid/editing/ + AG Grid editable https://www.ag-grid.com/react-data-grid/cell-editing/
14
14
  //
15
- // ── 型別化 read / 多行(2026-07-09 L2,edit-in-place SSOT 研究落地)──
16
- // InlineEdit 現為泛型 <T>(非只 string)。**格式化 read view 的 SSOT 鐵律**:renderRead 要顯示
17
- // Tag / 日期 / label 等格式化值時,**渲染對應 Field 控件的 `mode="display"`**(如 `<Select mode="display"
18
- // display="tag">`),**不可自刻 Tag / 格式化**——「select 值 Tag」的 SSOT 住在 Select 的 display mode
19
- // (= DataTable cell 消費的同一份)。這保證 read edit 用同一個控件、格式零分歧。多行(multiline)
20
- // Jira description 類欄位。
15
+ // ── Model A(2026-07-16 round16,user GO)──────────────────────────────────────
16
+ // InlineEdit **不自帶 geometry cva**( M17 重複):
17
+ // - 值-格式化(Select→Tag / Date / avatar)→ renderRead 委派 `<Control mode="view">`,幾何 + 內部間距
18
+ // 皆由控件 view×default 提供(read=edit 同一顆天生一致、多 tag/avatar 間距零偏移)。
19
+ // - 純值 / 標題 `<Tag as="h1">` `fieldViewGeometry(size, multiline)` helper(= view×default 幾何 SSOT)。
20
+ // InlineEdit 本體只給:`-mx`(orientation-aware,拉到欄左緣,= 上方 read-view 負邊距對照)+ hover bg + focus 藍框 + 隱形 Pressable。
21
+ // read↔edit 零跳 = view 與 edit 同一顆控件、只差 chrome。
22
+ // **格式化 read 鐵律**:renderRead 要顯示 Tag/日期/label,**渲染對應控件的 `mode="view"`**
23
+ // (如 `<Select mode="view" display="tag">`),不可自刻 —— 格式化 SSOT 住在該控件的 view mode。
21
24
  import * as React from 'react'
22
25
  import { cn } from '@/lib/utils'
23
26
  import { Input } from '@/design-system/components/Input/input'
24
27
  import { Textarea } from '@/design-system/components/Textarea/textarea'
25
28
  import type { FieldSize } from '@/design-system/components/Field/field-context'
26
- import { cva } from 'class-variance-authority'
27
- import { fieldDisplayTextClass } from '@/design-system/components/Field/field-wrapper'
29
+ import { useFieldContext, useResolvedFieldSize, FieldSurfaceProvider } from '@/design-system/components/Field/field-context'
30
+ import { fieldDisplayTextClass, fieldViewGeometry } from '@/design-system/components/Field/field-wrapper'
28
31
  import { makeEditSettleKeyHandler } from '@/design-system/components/Field/field-edit-keys'
29
32
 
30
33
  // ── Types ───────────────────────────────────────────────────────────────────
@@ -35,13 +38,13 @@ export interface InlineEditRenderProps<T = string> {
35
38
  value: T
36
39
  /** 草稿變更 */
37
40
  onChange: (next: T) => void
38
- /** 提交草稿 → onCommit(next) + 回 read 態 */
41
+ /** 提交草稿 → onCommit(next) + 回 view 態 */
39
42
  commit: () => void
40
- /** 放棄草稿 → 回 read 態不 commit */
43
+ /** 放棄草稿 → 回 view 態不 commit */
41
44
  cancel: () => void
42
45
  /** 進 edit 態時自動 focus 控件 */
43
46
  autoFocus: boolean
44
- /** 尺寸(cascade 給控件,與 read 態高度對齊) */
47
+ /** 尺寸(cascade 給控件,與 view 態高度對齊) */
45
48
  size: FieldSize
46
49
  /** 控件無障礙標籤(= label prop) */
47
50
  'aria-label'?: string
@@ -50,78 +53,57 @@ export interface InlineEditRenderProps<T = string> {
50
53
  type ReadTag = 'span' | 'p' | 'div' | 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'
51
54
 
52
55
  export interface InlineEditProps<T = string> {
53
- /** 已提交的欄位值(read 態顯示;edit 進入時作草稿初值)。泛型:string / number / string[] / PersonValue 等 */
56
+ /** 已提交的欄位值(view 態顯示;edit 進入時作草稿初值)。泛型:string / number / string[] / PersonValue 等 */
54
57
  value: T
55
58
  /** 提交回呼 —— blur / Enter 且草稿有變更時觸發 */
56
59
  onCommit: (next: T) => void
57
60
  /**
58
- * 自訂 edit 態控件(換 Textarea / Select / DatePicker 等 Field 控件)。
59
- * 不傳則預設:multiline → `<Textarea>`,否則 `<Input>`(僅 T=string 時預設有效;非 string 值必傳此 prop)。
61
+ * 自訂 edit 態控件(換 Textarea / Select / DatePicker 等 Field 控件,必用 `mode="edit"`)。
62
+ * 不傳則預設:multiline → `<Textarea mode="edit">`,否則 `<Input mode="edit">`(僅 T=string 時預設有效;非 string 值必傳此 prop)。
60
63
  */
61
64
  renderEdit?: (props: InlineEditRenderProps<T>) => React.ReactNode
62
65
  /**
63
- * 自訂 read 態格式化顯示。不傳則顯示 `String(value)`(空值顯 placeholder)。
64
- * **SSOT 鐵律**:要顯示 Tag / 日期格式 / option label 等,**渲染對應控件的 `mode="display"`**
65
- * (如 `renderRead={(v) => <Select mode="display" display="tag" value={v} options={opts} />}`),
66
- * 不可自刻 —— 格式化 SSOT 住在該控件的 display mode(見檔頭)。
66
+ * 自訂 view 態格式化顯示。不傳則顯示 `String(value)`(空值顯 placeholder)。
67
+ * **SSOT 鐵律**:要顯示 Tag / 日期格式 / option label 等,**渲染對應控件的 `mode="view"`**
68
+ * (如 `renderRead={(v) => <Select mode="view" display="tag" value={v} options={opts} />}`),
69
+ * 不可自刻 —— 格式化 + 內部間距 SSOT 住在該控件的 view mode(見檔頭 Model A)。
67
70
  */
68
71
  renderRead?: (value: T) => React.ReactNode
69
- /** 多行欄位(Jira description 類):read 態換行 + 預設 edit 態用 `<Textarea>`,Enter=換行、Cmd/Ctrl+Enter 或 blur 提交 */
72
+ /** 多行欄位(Jira description 類):view 態換行 + 預設 edit 態用 `<Textarea>`,Enter=換行、Cmd/Ctrl+Enter 或 blur 提交 */
70
73
  multiline?: boolean
71
- /** 欄位人類可讀名稱 —— 組 read 按鈕 aria-label(「編輯 {label}」)+ 控件 aria-label */
74
+ /**
75
+ * 是否可編輯(預設 true)。false → view 態無 hover 入口、無藍框、不可點進 edit(對齊世界級就地編輯
76
+ * **無 disabled 態**,鎖定 = 純 view 無入口、**不灰化**;cite 見檔頭 MUI X / AG Grid URL)。
77
+ * 永久唯讀資料 → 用 `<Control mode="view">` 或此 prop false;不用 disabled。
78
+ */
79
+ editable?: boolean
80
+ /** 欄位人類可讀名稱 —— 組 view 按鈕 aria-label(「編輯 {label}」)+ 控件 aria-label */
72
81
  label?: string
73
- /** 空值時 read 態顯示的提示文字(fg-muted) */
82
+ /** 空值時 view 態顯示的提示文字(fg-muted) */
74
83
  placeholder?: string
75
- /** 尺寸 —— read 態最小高度 + 預設 Input 尺寸( Field 家族一致) */
84
+ /** 尺寸 —— view 態最小高度 + 預設 Input 尺寸(不傳則經 useResolvedFieldSize 接 Field size cascade,standalone fallback sm) */
76
85
  size?: FieldSize
77
- /** read 態語意標籤(標題場景傳 'h1'/'h2' 維持文件大綱;預設 'span') */
86
+ /** view 態語意標籤(標題場景傳 'h1'/'h2' 維持文件大綱;預設 'span') */
78
87
  as?: ReadTag
79
- /** read 態文字樣式(如標題 `text-h4 font-bold`) */
88
+ /** view 態文字樣式(如標題 `text-h4 font-bold`) */
80
89
  readClassName?: string
81
90
  /** 外層容器 className */
82
91
  className?: string
83
92
  }
84
93
 
85
- // ── Read-view geometry cva ────────────────────────────────────────────────────
86
- // base = 排版結構;size variant = min-height(與 edit 態 Field 高度一致,降低態切換位移)。
87
- // multiline variant:單行 → `items-center`(文字垂直置中於 field-height);多行 → `items-start py-1.5`
88
- // (頂對齊 + 上下內距,對齊 Textarea `py-2` 多行閱讀語意,內容換行往下長)。
89
- // read 態幾何典範對照(M22 cite,2026-07-09 逐行核對原始碼,見檔頭 read-view URL):
90
- // hover = 底色 tint 非邊框;focus = 實線 solid border;wrapper 恆帶透明 border 預留邊框盒。
91
- const inlineEditReadStyles = cva(
92
- // relative(疊隱形 Pressable)/ border-transparent(預留 1px 邊框盒 → 態切換零位移;focus 才染色)/
93
- // px-[--field-px](文字↔底色水平內距 = Field 內距 SSOT,read=edit x 位置零跳)/ hover 底色 tint /
94
- // [&:has(button:focus-visible)]:border-primary(鍵盤 focus = Field edit 藍框語言,非 Button ring)。
95
- 'relative flex rounded-md border border-transparent px-[var(--field-px)] transition-colors duration-150 hover:bg-neutral-hover [&:has(button:focus-visible)]:border-primary',
96
- {
97
- variants: {
98
- size: {
99
- sm: 'min-h-[var(--field-height-sm)]',
100
- md: 'min-h-[var(--field-height-md)]',
101
- lg: 'min-h-[var(--field-height-lg)]',
102
- },
103
- multiline: {
104
- false: 'items-center',
105
- true: 'items-start py-1.5',
106
- },
107
- },
108
- // 預設 sm + 單行(2026-07-09 user 拍板):read 態無邊框、視覺 = 純文字,尺寸過大會讓版面鬆散。
109
- defaultVariants: { size: 'sm', multiline: false },
110
- },
111
- )
112
-
113
94
  /**
114
- * InlineEdit — 就地編輯 primitive(read ↔ edit 二態,泛型 <T>)
95
+ * InlineEdit — 就地編輯 primitive(view ↔ edit 二態,泛型 <T>)
115
96
  *
116
97
  * 互動模型 world-class 對照見檔頭「消費的 SSOT」+ inline-edit.spec.md benchmark 表:
117
- * - **read 態** = 純文字 / 格式化 renderRead、寬度 fill 容器、透明邊框(預留)→ 視覺 = 純內容
118
- * - **hover(read)** = 灰色底色 `bg-neutral-hover` + `rounded-md`(**非邊框**)+ cursor
119
- * - **鍵盤 focus(read)** = 外框染 `border-primary` 藍框(Field focus 語言,非 Button ring)
120
- * - **click / Enter/Space** = 切 edit 態 = 真正的 Field 控件(border + focus + input);多行走 Textarea
121
- * - **退出**:blur / Enter → commit;Esc cancel 還原不 commit;focus 返回 read 按鈕
98
+ * - **view 態** = 純文字 / 格式化 renderRead、寬度 fill 容器、透明邊框(預留)→ 視覺 = 純內容
99
+ * - **hover(view,editable)** = 灰色底色 `bg-neutral-hover` + `rounded-md`(**非邊框**)+ cursor
100
+ * - **鍵盤 focus(view,editable)** = 外框染 `border-primary` 藍框(Field focus 語言,非 Button ring)
101
+ * - **click / Enter/Space** = 切 edit 態 = 真正的 Field 控件(mode="edit":border + focus + input);多行走 Textarea
102
+ * - **退出**:blur(滑鼠)→ commit + 純 view 不搶焦;Enter(鍵盤)→ commit + 焦點回 view 按鈕 + 藍框;Esc → cancel 還原
122
103
  * - **鍵盤結算**:消費 `makeEditSettleKeyHandler`(含中文 IME guard,與 DataTable cell 同源)
104
+ * - **editable=false**:view 無 hover / 無 Pressable / 不可進 edit、**不灰化**(detail-pane 鎖定語意)
123
105
  *
124
- * read 態用隱形 Pressable(絕對定位 `<button>` 疊於內容上)—— 讓 `as="h1"` 等標題標籤保留文件大綱
106
+ * view 態用隱形 Pressable(絕對定位 `<button>` 疊於內容上)—— 讓 `as="h1"` 等標題標籤保留文件大綱
125
107
  * a11y,同時提供鍵盤 focus + Enter 進 edit(HTML 內容模型不允許 heading 巢狀於 button,故分離)。
126
108
  */
127
109
  function InlineEditImpl<T = string>(
@@ -131,24 +113,43 @@ function InlineEditImpl<T = string>(
131
113
  renderEdit,
132
114
  renderRead,
133
115
  multiline = false,
116
+ editable = true,
134
117
  label,
135
118
  placeholder,
136
- size = 'sm',
119
+ size: sizeProp,
137
120
  as = 'span',
138
121
  readClassName,
139
122
  className,
140
123
  }: InlineEditProps<T>,
141
124
  ref: React.Ref<HTMLDivElement>,
142
125
  ) {
126
+ // size:接 fieldCtx.size cascade,standalone fallback sm(InlineEdit 靜態 fieldPreferredSize='sm'
127
+ // 讓外層 `<Field>` 自動收 sm — 見檔尾 static)。
128
+ const size = useResolvedFieldSize(sizeProp, 'sm')
129
+ // orientation:vertical(值貼 label 左緣 → 用 -mx)/ horizontal(值落內容欄左緣+field-px = 對齊 sibling 控件 → 不用 -mx)。
130
+ const fieldCtx = useFieldContext()
131
+ const orientation = fieldCtx?.orientation ?? 'vertical'
132
+
143
133
  const [editing, setEditing] = React.useState(false)
144
134
  const [draft, setDraft] = React.useState<T>(value)
145
135
  // draft 最新值 ref —— commit 從事件 handler 呼叫時避免 stale closure
146
136
  const draftRef = React.useRef(draft)
147
137
  // 單次結算 guard —— Enter/Esc 已結算後,unmount 觸發的 onBlur 不再重複 commit
148
138
  const finalizedRef = React.useRef(false)
149
- // 鍵盤結算後把 focus 送回 read 按鈕(a11y:焦點不遺失)
139
+ // 鍵盤結算後把 focus 送回 view 按鈕(a11y:焦點不遺失);滑鼠 blur 不送回(不搶焦)
150
140
  const returnFocusRef = React.useRef(false)
151
141
  const readButtonRef = React.useRef<HTMLButtonElement>(null)
142
+ // edit 態容器 ref(pointerdown-outside 用);與 forwarded ref merge。
143
+ // 顯式 `| null` → mutable overload(手動賦值 .current;`useRef<T>(null)` 會匹配 readonly RefObject overload)。
144
+ const editContainerRef = React.useRef<HTMLDivElement | null>(null)
145
+ const setEditContainerRef = React.useCallback(
146
+ (node: HTMLDivElement | null) => {
147
+ editContainerRef.current = node
148
+ if (typeof ref === 'function') ref(node)
149
+ else if (ref) (ref as React.MutableRefObject<HTMLDivElement | null>).current = node
150
+ },
151
+ [ref],
152
+ )
152
153
 
153
154
  const setDraftValue = React.useCallback((next: T) => {
154
155
  draftRef.current = next
@@ -161,23 +162,47 @@ function InlineEditImpl<T = string>(
161
162
  setEditing(true)
162
163
  }, [value, setDraftValue])
163
164
 
164
- const commit = React.useCallback(() => {
165
- if (finalizedRef.current) return
166
- finalizedRef.current = true
167
- returnFocusRef.current = true
168
- setEditing(false)
169
- if (draftRef.current !== value) onCommit(draftRef.current)
170
- }, [value, onCommit])
165
+ // commit(returnFocus):鍵盤結算傳 true(焦點回 view 按鈕);滑鼠 blur 傳 false(不搶焦、純 view)
166
+ const commit = React.useCallback(
167
+ (returnFocus: boolean) => {
168
+ if (finalizedRef.current) return
169
+ finalizedRef.current = true
170
+ returnFocusRef.current = returnFocus
171
+ setEditing(false)
172
+ if (draftRef.current !== value) onCommit(draftRef.current)
173
+ },
174
+ [value, onCommit],
175
+ )
171
176
 
172
177
  const cancel = React.useCallback(() => {
173
178
  if (finalizedRef.current) return
174
179
  finalizedRef.current = true
175
- returnFocusRef.current = true
180
+ returnFocusRef.current = true // Esc = 鍵盤路徑,焦點回 view 按鈕
176
181
  setEditing(false)
177
182
  }, [])
178
183
 
179
- // 結算後把 focus 送回 read 按鈕(所有結算路徑含 blur / Enter / Esc 皆 return —— commit/cancel
180
- // 一律 set returnFocusRef=true;對齊 spec「任一路徑結算後 focus 送回 read 按鈕」L39/L141「焦點不遺失」)
184
+ // ── Exit-on-blur SSOT(2026-07-17 root cause 修:blur chrome 殘留)──────────────
185
+ // InlineEdit **保證**任何 edit 控件(預設 Input + 自訂 renderEdit 的 Select/DatePicker/Combobox…)
186
+ // blur 都回 view,**不靠** consumer 在 renderEdit 自接 onBlur(舊設計缺口:story Select 只接
187
+ // onChange→commit,不選直接 blur → commit 從沒呼叫 → 卡 editing=true,edit chrome 殘留)。
188
+ // portal-aware:overlay 控件(Select/DatePicker)開的 Radix popper/listbox 是 portaled 出子樹,
189
+ // 焦點在其中時**不誤結算**(dropdown 操作中);真正離開(都不在 edit 子樹 + 不在本 edit 的 overlay)才 commit。
190
+ // rAF 延一拍讓焦點落定(Radix 關 popover 時焦點先回 trigger,避免 blur 瞬間誤判)。
191
+ const handleEditBlur = React.useCallback((e: React.FocusEvent<HTMLDivElement>) => {
192
+ const wrapper = e.currentTarget
193
+ requestAnimationFrame(() => {
194
+ if (finalizedRef.current) return
195
+ const active = document.activeElement
196
+ if (active && wrapper.contains(active)) return // 仍在 edit 子樹(如 trigger)
197
+ // 焦點在本 edit 開的 Radix overlay 內 → dropdown 操作中,不結算
198
+ if (active && active.closest('[data-radix-popper-content-wrapper],[data-radix-select-viewport],[role="listbox"],[role="dialog"]')) return
199
+ commit(false) // blur 離開 → 回 view(滑鼠路徑不搶焦)
200
+ })
201
+ }, [commit])
202
+
203
+ // 鍵盤結算後把 focus 送回 view 按鈕(commit(true)/cancel);滑鼠 blur(commit(false))刻意不送回——
204
+ // 對齊 inline-edit.spec.md「退出 edit 態(focus 分流)」:鍵盤結算焦點回 view 按鈕 + 藍框、
205
+ // 滑鼠 blur 回純 view 不搶焦。focus-visible 保證只有鍵盤路徑顯藍框。
181
206
  React.useEffect(() => {
182
207
  if (!editing && returnFocusRef.current) {
183
208
  returnFocusRef.current = false
@@ -185,95 +210,153 @@ function InlineEditImpl<T = string>(
185
210
  }
186
211
  }, [editing])
187
212
 
213
+ // ── Pointerdown-outside exit(2026-07-17,blur 殘留 root cause 真修)──────────────
214
+ // focusout 不夠:overlay 控件(Select)點外面關 popover 時 Radix 把焦點送回 trigger(在 edit 子樹內)
215
+ // → onBlur 不觸發 → 卡 editing。改用 document-level pointerdown-outside 偵測(對齊 Radix
216
+ // DismissableLayer onPointerDownOutside https://www.radix-ui.com/primitives/docs/utilities/dismissable-layer
217
+ // + Atlassian inline-edit click-outside 退出
218
+ // https://github.com/pioug/atlassian-frontend-mirror/blob/main/design-system/inline-edit/src/inline-edit.tsx):
219
+ // editing 時點在 edit 子樹外且不在本 edit popover 內的 pointerdown → commit 回 view。
220
+ // 保證任何控件(含 overlay)點外面都回 display,不靠 consumer 在 renderEdit 自接。
221
+ React.useEffect(() => {
222
+ if (!editing) return
223
+ const onPointerDown = (e: PointerEvent) => {
224
+ if (finalizedRef.current) return
225
+ const target = e.target as Element | null
226
+ if (!target) return
227
+ const box = editContainerRef.current
228
+ if (box && box.contains(target)) return // 點在 edit 子樹內(trigger 等)
229
+ // 點在本 edit 開的 Radix overlay 內(popper / listbox / dialog)→ dropdown 操作中,不結算
230
+ if (target.closest('[data-radix-popper-content-wrapper],[data-radix-select-viewport],[role="listbox"],[role="dialog"]')) return
231
+ commit(false)
232
+ }
233
+ document.addEventListener('pointerdown', onPointerDown, true)
234
+ return () => document.removeEventListener('pointerdown', onPointerDown, true)
235
+ }, [editing, commit])
236
+
188
237
  const isEmpty =
189
238
  value == null || (value as unknown) === '' || (Array.isArray(value) && value.length === 0)
190
239
  const Tag = as as React.ElementType
191
- // 外框(read/edit 兩態共用同一幾何 → 保證「底色範圍 = 輸入框範圍」逐 pixel 一致):
192
- // -mx-[--field-px] 左右各外擴 field-px;w-[calc(100% + 2*--field-px)] 對稱補回 兩側等寬填滿。
193
- const rootClass = cn('-mx-[var(--field-px)] w-[calc(100%_+_2_*_var(--field-px))]', className)
240
+
241
+ // 對齊盒(orientation-aware,Model A;2026-07-17 移除 fieldCtx gate root cause 修):
242
+ // vertical(預設,含 standalone)→ `-mx-field-px` + `w-calc` 把整塊拉到欄左緣(值貼 label 左緣;
243
+ // view 委派控件在 `surface='inline-edit'` 下 px 統一 field-px → 被 -mx 精準抵消 → 落 x=0 對齊 label)。
244
+ // horizontal → 不用 -mx(純 `w-full`,值內縮 px = 對齊 sibling 控件)。
245
+ // ⚠️ **移除舊 `fieldCtx &&` gate(root cause)**:standalone 預設 orientation='vertical'(:131),舊 gate 讓
246
+ // standalone 掉到 `w-full` 無 -mx → 內容內縮 field-px 不對齊 label(圖一/圖二 bug)。spec Model A
247
+ // (field-controls.spec.md:71「orientation-aware -mx / vertical 貼 label」)只依 orientation、不依 fieldCtx。
248
+ // -mx bleed field-px 到容器 padding = 刻意(hover/edit 才顯真實邊界)→ consumer 放 ≥field-px padding 容器
249
+ // (detail panel / card;stories 容器 layout-space-loose 16px > field-px 12px 天然吸收)。
250
+ const alignBleed =
251
+ orientation === 'vertical'
252
+ ? '-mx-[var(--field-px)] w-[calc(100%_+_2_*_var(--field-px))]'
253
+ : 'w-full min-w-0'
194
254
 
195
255
  if (editing) {
196
256
  const editProps: InlineEditRenderProps<T> = {
197
257
  value: draft,
198
258
  onChange: setDraftValue,
199
- commit,
259
+ commit: () => commit(true),
200
260
  cancel,
201
261
  autoFocus: true,
202
262
  size,
203
263
  'aria-label': label,
204
264
  }
205
265
  // 預設 edit 控件:multiline → Textarea(Enter=換行、Cmd/Ctrl+Enter/blur 提交);否則 Input。
206
- // 兩者 Enter/Esc 結算都走 makeEditSettleKeyHandler(含 IME guard)。string-only 預設路徑;
207
- // string 值必傳 renderEdit。
266
+ // 兩者皆 mode="edit"(edit 態 = 真正的 edit-mode Field 控件),Enter/Esc 結算走 makeEditSettleKeyHandler(含 IME guard)。
267
+ // string-only 預設路徑;非 string 值必傳 renderEdit。
208
268
  const defaultEditNode = multiline ? (
209
269
  <Textarea
210
270
  autoFocus
271
+ mode="edit"
211
272
  value={draft as unknown as string}
212
273
  size={size}
213
274
  aria-label={label}
214
275
  onChange={(e) => setDraftValue(e.target.value as unknown as T)}
215
- onBlur={commit}
216
- // 多行:plain Enter=換行、Esc=cancel、Cmd/Ctrl+Enter=commit —— 全走
217
- // makeEditSettleKeyHandler SSOT( IME guard + Cmd/Ctrl+Enter),不再手刻 capture。
218
- onKeyDown={makeEditSettleKeyHandler({ onCommit: commit, onCancel: cancel, commitOnEnter: false })}
276
+ onBlur={() => commit(false)}
277
+ // 多行:plain Enter=換行、Esc=cancel、Cmd/Ctrl+Enter=commit —— 全走 makeEditSettleKeyHandler SSOT。
278
+ onKeyDown={makeEditSettleKeyHandler({ onCommit: () => commit(true), onCancel: cancel, commitOnEnter: false })}
219
279
  />
220
280
  ) : (
221
281
  <Input
222
282
  autoFocus
283
+ mode="edit"
223
284
  value={draft as unknown as string}
224
285
  size={size}
225
286
  aria-label={label}
226
287
  onChange={(e) => setDraftValue(e.target.value as unknown as T)}
227
- onBlur={commit}
228
- onKeyDown={makeEditSettleKeyHandler({ onCommit: commit, onCancel: cancel })}
288
+ onBlur={() => commit(false)}
289
+ onKeyDown={makeEditSettleKeyHandler({ onCommit: () => commit(true), onCancel: cancel })}
229
290
  />
230
291
  )
231
292
  const editNode = renderEdit ? renderEdit(editProps) : defaultEditNode
232
293
  return (
233
- <div ref={ref} data-editing={editing} className={rootClass}>
294
+ // exit SSOT 雙保險:onBlur(focusout,tab-out / 非 overlay 控件)+ document pointerdown-outside(overlay
295
+ // 點外面 Radix 送焦回 trigger 時 focusout 不觸發之補位)。任何 edit 控件離開 → 回 view。
296
+ // edit 態**不套** surface='inline-edit'(完全繼承原 field,含 tagPadding — Jira 式,user 2026-07-17 拍板)。
297
+ <div ref={setEditContainerRef} data-editing className={cn(alignBleed, className)} onBlur={handleEditBlur}>
234
298
  {editNode}
235
299
  </div>
236
300
  )
237
301
  }
238
302
 
303
+ // view 態:外框只承載對齊盒 + (editable 時) hover bg + 鍵盤 focus 藍框;幾何由內容(委派控件 view / fieldViewGeometry)提供。
304
+ const viewNode = renderRead ? (
305
+ // 格式化 view(consumer 傳 <Control mode="view"> 等)。**對齊 SSOT(2026-07-17)**:委派控件的
306
+ // bare view 一律 0px 左內距(audit 證實 Select/Combobox/DatePicker/TimePicker/PeoplePicker/LinkInput
307
+ // 預設 view 皆裸 span/Tag),故 InlineEdit 用**與純值路徑同一個 `fieldViewGeometry(field-px + min-h +
308
+ // py)`** 包住 renderRead → 委派控件 view 左 px 統一 field-px、高度統一 field-height → 固定
309
+ // `-mx-field-px` 精準抵消 → tag/avatar/值左緣落 label x=0。不碰任何控件 tagPadding(bare view 本就不用)。
310
+ // surface='inline-edit' 保留為 per-control 逃生訊號(若某控件 bare view 有內部 inset 破壞對齊,該控件可
311
+ // 讀 surface 自抑;目前全控件 bare view=0px 不需)。
312
+ <FieldSurfaceProvider surface="inline-edit">
313
+ <div className={fieldViewGeometry(size, multiline)}>{renderRead(value)}</div>
314
+ </FieldSurfaceProvider>
315
+ ) : (
316
+ <Tag
317
+ data-empty={isEmpty}
318
+ className={cn(
319
+ // 純值/標題共用 view 幾何 SSOT(px-field-px + min-h-field + 單行 items-center / 多行 items-start py-2)
320
+ fieldViewGeometry(size, multiline),
321
+ // view 態 typography 預設消費 Field view SSOT(sm/md→text-body,lg→text-body-lg)——
322
+ // plain 用法 view 字級 = edit <Input>(text-body)= <Input mode="view"> 字級,三者一致零跳字。
323
+ fieldDisplayTextClass(size),
324
+ // 多行:保留換行(pre-wrap)+ 長字斷行 —— view 忠實呈現 Textarea 內容(Jira description 類)。
325
+ multiline && 'whitespace-pre-wrap break-words',
326
+ readClassName,
327
+ isEmpty && 'text-fg-muted',
328
+ )}
329
+ >
330
+ {isEmpty ? placeholder : String(value)}
331
+ </Tag>
332
+ )
333
+
239
334
  return (
240
335
  <div
241
336
  ref={ref}
242
- data-editing={editing}
243
- // 外框(rootClass 幾何)+ read chrome(inlineEditReadStyles:border 預留 / px / min-h / hover 底色 / focus 藍框)
244
- className={cn(rootClass, inlineEditReadStyles({ size, multiline }))}
337
+ data-editing={false}
338
+ data-editable={editable || undefined}
339
+ className={cn(
340
+ 'relative flex min-w-0 rounded-md border border-transparent transition-colors duration-150',
341
+ alignBleed,
342
+ // editable 才有 hover 底色 + 鍵盤 focus 藍框(Field focus 語言,非 Button ring);
343
+ // editable=false = 純 view 鎖定,無入口、無藍框、不灰化。
344
+ editable && 'hover:bg-neutral-hover [&:has(button:focus-visible)]:border-primary',
345
+ className,
346
+ )}
245
347
  >
246
- {renderRead ? (
247
- // 格式化 read view(consumer <Select mode="display"> 等)。invisible Pressable 疊其上供點擊進 edit
248
- <div className="w-full min-w-0">{renderRead(value)}</div>
249
- ) : (
250
- <Tag
251
- data-empty={isEmpty}
252
- className={cn(
253
- 'w-full min-w-0',
254
- // read 態 typography 預設消費 Field display SSOT(sm/md→text-body,lg→text-body-lg)——
255
- // plain 用法 read 字級 = edit <Input>(text-body)= <Input mode="display"> 字級,三者一致零跳字。
256
- fieldDisplayTextClass(size),
257
- // 多行:保留換行(pre-wrap)+ 長字斷行 —— read 忠實呈現 Textarea 內容(Jira description 類),
258
- // 對齊 Textarea mode="display" 的 `whitespace-pre-wrap break-words`。
259
- multiline && 'whitespace-pre-wrap break-words',
260
- readClassName,
261
- isEmpty && 'text-fg-muted',
262
- )}
263
- >
264
- {isEmpty ? placeholder : String(value)}
265
- </Tag>
348
+ {viewNode}
349
+ {/* 隱形 Pressable( editable):提供 click + 鍵盤 Tab focus + Enter/Space 進 edit;透明疊於內容上。
350
+ hover 底色 + focus 藍框由外層 div 承載(Field focus 語言),故本 button 只需 outline-none 消瀏覽器預設外框。 */}
351
+ {editable && (
352
+ <button
353
+ ref={readButtonRef}
354
+ type="button"
355
+ aria-label={label ? `編輯 ${label}` : '編輯'}
356
+ onClick={enterEdit}
357
+ className="absolute inset-0 cursor-text rounded-md focus-visible:outline-none"
358
+ />
266
359
  )}
267
- {/* 隱形 Pressable:提供 click + 鍵盤 Tab focus + Enter/Space 進 edit;透明疊於內容上。
268
- hover 底色 + focus 藍框(border-primary)由外層 div 承載(Field focus 語言,非 ring),
269
- 故本 button 只需 outline-none 消除瀏覽器預設外框。 */}
270
- <button
271
- ref={readButtonRef}
272
- type="button"
273
- aria-label={label ? `編輯 ${label}` : '編輯'}
274
- onClick={enterEdit}
275
- className="absolute inset-0 cursor-text rounded-md focus-visible:outline-none"
276
- />
277
360
  </div>
278
361
  )
279
362
  }
@@ -281,7 +364,10 @@ function InlineEditImpl<T = string>(
281
364
  // 泛型 forwardRef:forwardRef 不原生支援泛型,以 cast 還原公開型別(標準 workaround)。
282
365
  const InlineEdit = React.forwardRef(InlineEditImpl) as (<T = string>(
283
366
  props: InlineEditProps<T> & { ref?: React.Ref<HTMLDivElement> },
284
- ) => React.ReactElement) & { displayName?: string }
367
+ ) => React.ReactElement) & { displayName?: string; fieldPreferredSize?: FieldSize }
285
368
  InlineEdit.displayName = 'InlineEdit'
369
+ // 靜態偏好尺寸:外層 `<Field>` render 時讀 `child.type.fieldPreferredSize` → Field.size 未顯式指定時收 sm
370
+ // (對齊 detectControlLayout 讀 fieldLayout 的機制;InlineEdit view 態無邊框,md 過大會讓版面鬆散)。
371
+ InlineEdit.fieldPreferredSize = 'sm'
286
372
 
287
373
  export { InlineEdit }
@@ -13,12 +13,12 @@ export default meta
13
13
  Types & Data
14
14
  ═══════════════════════════════════════════════════════════════════════════ */
15
15
 
16
- type ModeKey = 'edit' | 'display' | 'readonly' | 'disabled'
16
+ type ModeKey = 'edit' | 'view' | 'readonly' | 'disabled'
17
17
  type StateKey = 'default' | 'hover' | 'focus' | 'error' | 'disabled'
18
18
  type SizeKey = 'sm' | 'md' | 'lg'
19
19
  type ColorSpec = { bg: string; text: string; border: string; placeholder: string }
20
20
 
21
- const MODES: ModeKey[] = ['edit', 'display', 'readonly', 'disabled']
21
+ const MODES: ModeKey[] = ['edit', 'view', 'readonly', 'disabled']
22
22
  const EDIT_STATES: StateKey[] = ['default', 'hover', 'focus', 'error', 'disabled']
23
23
  const SIZES: SizeKey[] = ['sm', 'md', 'lg']
24
24
 
@@ -30,7 +30,7 @@ const COLOR_MAP: Record<ModeKey, Partial<Record<StateKey, ColorSpec>>> = {
30
30
  error: { bg: '--surface', text: '--foreground', border: '--error', placeholder: '--fg-muted' },
31
31
  disabled: { bg: '--bg-disabled', text: '--fg-disabled', border: 'transparent', placeholder: '--fg-disabled' },
32
32
  },
33
- display: {
33
+ view: {
34
34
  default: { bg: 'transparent', text: '--foreground', border: 'transparent', placeholder: '--fg-muted' },
35
35
  },
36
36
  readonly: {
@@ -58,7 +58,7 @@ const SIZE_SPECS: Record<SizeKey, SizeSpec> = {
58
58
 
59
59
  const MODE_DESC: Record<ModeKey, string> = {
60
60
  edit: '表單可編輯欄位 — bg-surface + border + hover/focus 回饋',
61
- display: '純展示資料 chrome(transparent + !px-0 !py-0 零內距,field-wrapper.tsx Path Ⅰ)+ 文字正常色,空值顯示半形 -(hyphen,同 value 前景色)',
61
+ view: '純展示值(非表單)— edit 幾何減 chrome(transparent bg/border,**保留 px-field-px 內距與高度**,Model A round16)+ 文字正常色,空值顯示半形 -(hyphen,同 value 前景色)。用在 cell / InlineEdit / 詳情',
62
62
  readonly: '不可編輯但可見 — bg-readonly(neutral-2) + 無邊框 + 文字正常色',
63
63
  disabled: '被停用的欄位 — bg-disabled(neutral-2) + 無邊框 + 文字灰化',
64
64
  }
@@ -211,9 +211,9 @@ export const Overview = {
211
211
  {MODES.map((m) => (
212
212
  <div key={m} className="flex items-center gap-4">
213
213
  <div className="w-64 shrink-0">
214
- {/* display + readonly 讀 controlled value:兩者「空值」皆走 <span> 顯 '-'(input.tsx:121),
214
+ {/* view + readonly 讀 controlled value:兩者「空值」皆走 <span> 顯 '-'(input.tsx:121),
215
215
  demo 要展示有值狀態故傳 value;edit / disabled 用 defaultValue 保持 uncontrolled */}
216
- {(m === 'display' || m === 'readonly') ? (
216
+ {(m === 'view' || m === 'readonly') ? (
217
217
  <Input mode={m} value="Wireless Bluetooth Headphones" size="md" />
218
218
  ) : (
219
219
  <Input mode={m} defaultValue="Wireless Bluetooth Headphones" size="md" />
@@ -242,8 +242,8 @@ export const Overview = {
242
242
  <thead><tr><Th>Prop</Th><Th>Type</Th><Th>Default</Th><Th>說明</Th></tr></thead>
243
243
  <tbody>
244
244
  {[
245
- ['mode', "'edit' | 'display' | 'readonly' | 'disabled'", "'edit'", 'mode 未顯式指定時依序推導:有效 disabled(prop 或 <Field disabled>)→ fieldCtx.mode → native readOnly → edit;顯式 mode prop 永遠最優先(field-context.ts useResolvedFieldMode)'],
246
- ['variant', "'default'", "'default'", '視覺 chrome(正交於 mode;公開型別僅 default,2026-07-14 收窄);naked = chrome,host cell 提供 border/focus(@internal cell-as-input);bare 2026-07-09 退役'],
245
+ ['mode', "'edit' | 'view' | 'readonly' | 'disabled'", "'edit'", 'mode 未顯式指定時依序推導:有效 disabled(prop 或 <Field disabled>)→ fieldCtx.mode → native readOnly → edit;顯式 mode prop 永遠最優先(field-context.ts useResolvedFieldMode)'],
246
+ ['variant', "'default'", "'default'", '視覺 chrome(正交於 mode;公開型別僅 default,2026-07-14 收窄);naked = cell-as-input substrate(@internal):edit×naked 自畫 border-based state machine(border → hover → focus-within primary → error 紅框),view×naked 才由 host cell 提供視覺邊框;bare 2026-07-09 退役'],
247
247
  ['error', 'boolean', 'false', '紅色邊框僅 edit 模式生效;aria-invalid 不分 mode(readonly / disabled 渲染的 input 同樣帶)'],
248
248
  ['size', "'sm' | 'md' | 'lg'", "'md'", '高度與字體,與 Button 共用 field-height token'],
249
249
  ['startIcon', 'LucideIcon', '—', '左側靜態 icon,fg-muted,pointer-events-none'],
@@ -334,8 +334,8 @@ const InspectorInner = () => {
334
334
  error={error}
335
335
  startIcon={hasStartIcon ? Search : undefined}
336
336
  endAction={hasEndAction && isEdit ? { icon: X, label: '清除', onClick: () => {} } : undefined}
337
- // display mode 讀 value 渲染 <span>;其餘 mode 用 defaultValue 保持 uncontrolled
338
- {...(mode === 'display'
337
+ // view mode 讀 value 渲染 <span>;其餘 mode 用 defaultValue 保持 uncontrolled
338
+ {...(mode === 'view'
339
339
  ? { value: 'Wireless Bluetooth Headphones' }
340
340
  : { defaultValue: 'Wireless Bluetooth Headphones' })}
341
341
  key={mode}
@@ -361,7 +361,7 @@ const InspectorInner = () => {
361
361
  </div>
362
362
  <div className="flex items-center">
363
363
  <div className="flex items-center rounded-md overflow-hidden" style={{ height: 52, outline: `2px solid ${Z.dim.text}22` }}>
364
- <BpZone w={44} color={Z.pad} label={mode === 'display' ? '!px-0' : s.pxToken} sub={mode === 'display' ? '0px' : `${s.px}px`} />
364
+ <BpZone w={44} color={Z.pad} label={s.pxToken} sub={`${s.px}px`} />
365
365
  {hasStartIcon && (
366
366
  <>
367
367
  <BpZone w={44} color={Z.icon} label={`${s.icon}px`} sub="icon" />
@@ -375,7 +375,7 @@ const InspectorInner = () => {
375
375
  <BpZone w={44} color={Z.action} label={`${s.icon}px`} sub="action" />
376
376
  </>
377
377
  )}
378
- <BpZone w={44} color={Z.pad} label={mode === 'display' ? '!px-0' : s.pxToken} sub={mode === 'display' ? '0px' : `${s.px}px`} />
378
+ <BpZone w={44} color={Z.pad} label={s.pxToken} sub={`${s.px}px`} />
379
379
  </div>
380
380
  <div className="ml-3 flex items-center" style={{ height: 52 }}>
381
381
  <svg width="10" height="52" className="shrink-0">
@@ -426,7 +426,7 @@ const InspectorInner = () => {
426
426
  <div className="px-4 py-1">
427
427
  <div className="py-2 border-b border-divider"><span className="text-[10px] font-semibold text-fg-muted uppercase tracking-wider">Layout</span></div>
428
428
  <PropRow label="高度" dot={Z.dim.text}><TkVal token={s.heightToken} value={s.height} /></PropRow>
429
- <PropRow label="左右內距" dot={Z.pad.text}><TkVal token={mode === 'display' ? '!px-0 !py-0' : s.pxToken} value={mode === 'display' ? '0px(零 chrome)' : `${s.px}px`} /></PropRow>
429
+ <PropRow label="左右內距" dot={Z.pad.text}><TkVal token={s.pxToken} value={`${s.px}px(view 保留 = edit 幾何減 chrome)`} /></PropRow>
430
430
  <PropRow label="元素間距" dot={Z.gap.text}><TkVal token={s.gapToken} value={`${s.gap}px`} /></PropRow>
431
431
  {hasStartIcon && (
432
432
  <PropRow label="Icon 尺寸" dot={Z.icon.text}>{s.icon}px</PropRow>
@@ -452,7 +452,7 @@ const InspectorInner = () => {
452
452
  <div className="py-2 border-b border-divider"><span className="text-[10px] font-semibold text-fg-muted uppercase tracking-wider">Style</span></div>
453
453
  <PropRow label="Radius"><TkVal token="rounded-md" value="4px" /></PropRow>
454
454
  <PropRow label="Border"><TkVal token="border" value="1px solid" /></PropRow>
455
- <PropRow label="Focus"><TkVal token="border-primary" value="1px — 無 ring" /></PropRow>
455
+ <PropRow label="Focus"><TkVal token="border-primary" value="1px — 無 ring(readonly 例外:ring-2 ring-ring,WCAG 2.4.7)" /></PropRow>
456
456
  <PropRow label="Transition"><TkVal token="transition-colors" value="150ms" /></PropRow>
457
457
  </div>
458
458
  </div>
@@ -675,7 +675,7 @@ export const StateBehavior = {
675
675
 
676
676
  {/* Focus behavior */}
677
677
  <div className="flex flex-col gap-4">
678
- <span className="text-caption font-medium text-fg-secondary">Focus — border-primary(1px),無 ring</span>
678
+ <span className="text-caption font-medium text-fg-secondary">Focus — border-primary(1px),無 ring(readonly 例外:ring-2 ring-ring focus 指示,WCAG 2.4.7)</span>
679
679
  <div className="flex flex-col gap-2 max-w-sm">
680
680
  <span className="text-[11px] text-fg-muted">文字輸入永遠 focus-visible(瀏覽器規範),click 和 Tab 觸發相同效果。點擊下方 input 或用 Tab 切換觀察。</span>
681
681
  <Input placeholder="點擊或 Tab 觀察 focus 邊框" />