@qijenchen/design-system 0.1.0-beta.80 → 0.1.0-beta.82

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 (548) hide show
  1. package/CLAUDE.md +1 -1
  2. package/dist/components/Alert/alert.d.ts +5 -0
  3. package/dist/components/Alert/alert.d.ts.map +1 -1
  4. package/dist/components/Alert/alert.js +4 -1
  5. package/dist/components/Alert/alert.js.map +1 -1
  6. package/dist/components/AppShell/app-shell.d.ts +1 -1
  7. package/dist/components/AppShell/app-shell.js.map +1 -1
  8. package/dist/components/AspectRatio/aspect-ratio.d.ts +2 -1
  9. package/dist/components/AspectRatio/aspect-ratio.d.ts.map +1 -1
  10. package/dist/components/AspectRatio/aspect-ratio.js +1 -1
  11. package/dist/components/AspectRatio/aspect-ratio.js.map +1 -1
  12. package/dist/components/Avatar/avatar.d.ts +3 -2
  13. package/dist/components/Avatar/avatar.d.ts.map +1 -1
  14. package/dist/components/Avatar/avatar.js +8 -3
  15. package/dist/components/Avatar/avatar.js.map +1 -1
  16. package/dist/components/Badge/badge.d.ts.map +1 -1
  17. package/dist/components/Badge/badge.js +2 -1
  18. package/dist/components/Badge/badge.js.map +1 -1
  19. package/dist/components/Breadcrumb/breadcrumb.d.ts +14 -1
  20. package/dist/components/Breadcrumb/breadcrumb.d.ts.map +1 -1
  21. package/dist/components/Breadcrumb/breadcrumb.js +9 -3
  22. package/dist/components/Breadcrumb/breadcrumb.js.map +1 -1
  23. package/dist/components/BulkActionBar/bulk-action-bar.d.ts +4 -1
  24. package/dist/components/BulkActionBar/bulk-action-bar.d.ts.map +1 -1
  25. package/dist/components/BulkActionBar/bulk-action-bar.js +2 -2
  26. package/dist/components/BulkActionBar/bulk-action-bar.js.map +1 -1
  27. package/dist/components/Button/button.d.ts +4 -4
  28. package/dist/components/Button/button.d.ts.map +1 -1
  29. package/dist/components/Button/button.js +73 -44
  30. package/dist/components/Button/button.js.map +1 -1
  31. package/dist/components/Button/index.d.ts +1 -0
  32. package/dist/components/Button/index.d.ts.map +1 -1
  33. package/dist/components/Button/index.js +3 -0
  34. package/dist/components/Button/index.js.map +1 -1
  35. package/dist/components/Calendar/calendar.d.ts +3 -1
  36. package/dist/components/Calendar/calendar.d.ts.map +1 -1
  37. package/dist/components/Calendar/calendar.js +40 -17
  38. package/dist/components/Calendar/calendar.js.map +1 -1
  39. package/dist/components/Carousel/carousel.d.ts +1 -1
  40. package/dist/components/Carousel/carousel.d.ts.map +1 -1
  41. package/dist/components/Carousel/carousel.js +3 -1
  42. package/dist/components/Carousel/carousel.js.map +1 -1
  43. package/dist/components/Chart/chart.d.ts.map +1 -1
  44. package/dist/components/Chart/chart.js +1 -1
  45. package/dist/components/Chart/chart.js.map +1 -1
  46. package/dist/components/Checkbox/checkbox.d.ts +6 -3
  47. package/dist/components/Checkbox/checkbox.d.ts.map +1 -1
  48. package/dist/components/Checkbox/checkbox.js +26 -4
  49. package/dist/components/Checkbox/checkbox.js.map +1 -1
  50. package/dist/components/Checkbox/index.d.ts +1 -0
  51. package/dist/components/Checkbox/index.d.ts.map +1 -1
  52. package/dist/components/Checkbox/index.js +3 -0
  53. package/dist/components/Checkbox/index.js.map +1 -1
  54. package/dist/components/Chip/chip.d.ts +9 -3
  55. package/dist/components/Chip/chip.d.ts.map +1 -1
  56. package/dist/components/Chip/chip.js +46 -13
  57. package/dist/components/Chip/chip.js.map +1 -1
  58. package/dist/components/Coachmark/coachmark.d.ts +5 -3
  59. package/dist/components/Coachmark/coachmark.d.ts.map +1 -1
  60. package/dist/components/Coachmark/coachmark.js +10 -7
  61. package/dist/components/Coachmark/coachmark.js.map +1 -1
  62. package/dist/components/Combobox/combobox.d.ts +3 -1
  63. package/dist/components/Combobox/combobox.d.ts.map +1 -1
  64. package/dist/components/Combobox/combobox.js +11 -7
  65. package/dist/components/Combobox/combobox.js.map +1 -1
  66. package/dist/components/Command/command.d.ts +2 -2
  67. package/dist/components/Command/command.d.ts.map +1 -1
  68. package/dist/components/Command/command.js +4 -2
  69. package/dist/components/Command/command.js.map +1 -1
  70. package/dist/components/DataTable/active-editor-controller.d.ts +4 -3
  71. package/dist/components/DataTable/active-editor-controller.d.ts.map +1 -1
  72. package/dist/components/DataTable/cell-registry.d.ts.map +1 -1
  73. package/dist/components/DataTable/cell-registry.js +3 -0
  74. package/dist/components/DataTable/cell-registry.js.map +1 -1
  75. package/dist/components/DataTable/data-table-filter-panel.d.ts.map +1 -1
  76. package/dist/components/DataTable/data-table-interaction-layer.d.ts +4 -3
  77. package/dist/components/DataTable/data-table-interaction-layer.d.ts.map +1 -1
  78. package/dist/components/DataTable/data-table-interaction-layer.js.map +1 -1
  79. package/dist/components/DataTable/data-table.d.ts +38 -7
  80. package/dist/components/DataTable/data-table.d.ts.map +1 -1
  81. package/dist/components/DataTable/data-table.js +213 -63
  82. package/dist/components/DataTable/data-table.js.map +1 -1
  83. package/dist/components/DataTable/filter-tree.d.ts +5 -0
  84. package/dist/components/DataTable/filter-tree.d.ts.map +1 -1
  85. package/dist/components/DateGrid/date-grid.d.ts +3 -3
  86. package/dist/components/DateGrid/date-grid.d.ts.map +1 -1
  87. package/dist/components/DateGrid/date-grid.js +52 -6
  88. package/dist/components/DateGrid/date-grid.js.map +1 -1
  89. package/dist/components/DatePicker/date-picker.d.ts +2 -8
  90. package/dist/components/DatePicker/date-picker.d.ts.map +1 -1
  91. package/dist/components/DatePicker/date-picker.js +139 -109
  92. package/dist/components/DatePicker/date-picker.js.map +1 -1
  93. package/dist/components/DescriptionList/description-list.d.ts.map +1 -1
  94. package/dist/components/DescriptionList/description-list.js +1 -1
  95. package/dist/components/DescriptionList/description-list.js.map +1 -1
  96. package/dist/components/Dialog/dialog.d.ts +1 -1
  97. package/dist/components/Dialog/dialog.d.ts.map +1 -1
  98. package/dist/components/Dialog/dialog.js +2 -1
  99. package/dist/components/Dialog/dialog.js.map +1 -1
  100. package/dist/components/DropdownMenu/dropdown-menu.d.ts +4 -4
  101. package/dist/components/DropdownMenu/dropdown-menu.d.ts.map +1 -1
  102. package/dist/components/DropdownMenu/dropdown-menu.js +25 -8
  103. package/dist/components/DropdownMenu/dropdown-menu.js.map +1 -1
  104. package/dist/components/Empty/empty.d.ts.map +1 -1
  105. package/dist/components/Empty/empty.js +1 -1
  106. package/dist/components/Empty/empty.js.map +1 -1
  107. package/dist/components/Field/field-wrapper.d.ts +1 -0
  108. package/dist/components/Field/field-wrapper.d.ts.map +1 -1
  109. package/dist/components/Field/field-wrapper.js +27 -7
  110. package/dist/components/Field/field-wrapper.js.map +1 -1
  111. package/dist/components/Field/field.d.ts +1 -1
  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/Field/use-form-validation.d.ts +5 -1
  116. package/dist/components/Field/use-form-validation.d.ts.map +1 -1
  117. package/dist/components/Field/use-form-validation.js +31 -21
  118. package/dist/components/Field/use-form-validation.js.map +1 -1
  119. package/dist/components/FieldControlGroup/field-control-group.d.ts +3 -3
  120. package/dist/components/FieldControlGroup/field-control-group.js.map +1 -1
  121. package/dist/components/FileItem/file-item.d.ts.map +1 -1
  122. package/dist/components/FileItem/file-item.js +3 -2
  123. package/dist/components/FileItem/file-item.js.map +1 -1
  124. package/dist/components/FileUpload/file-upload.d.ts +1 -1
  125. package/dist/components/FileUpload/file-upload.d.ts.map +1 -1
  126. package/dist/components/FileUpload/file-upload.js +22 -10
  127. package/dist/components/FileUpload/file-upload.js.map +1 -1
  128. package/dist/components/FileViewer/can-render-image.d.ts +6 -0
  129. package/dist/components/FileViewer/can-render-image.d.ts.map +1 -0
  130. package/dist/components/FileViewer/can-render-image.js +11 -0
  131. package/dist/components/FileViewer/can-render-image.js.map +1 -0
  132. package/dist/components/FileViewer/file-viewer.d.ts +2 -2
  133. package/dist/components/FileViewer/file-viewer.d.ts.map +1 -1
  134. package/dist/components/FileViewer/file-viewer.js +24 -16
  135. package/dist/components/FileViewer/file-viewer.js.map +1 -1
  136. package/dist/components/FileViewer/image-renderer.d.ts +0 -5
  137. package/dist/components/FileViewer/image-renderer.d.ts.map +1 -1
  138. package/dist/components/FileViewer/image-renderer.js +1 -9
  139. package/dist/components/FileViewer/image-renderer.js.map +1 -1
  140. package/dist/components/HoverCard/hover-card.d.ts.map +1 -1
  141. package/dist/components/HoverCard/hover-card.js +3 -1
  142. package/dist/components/HoverCard/hover-card.js.map +1 -1
  143. package/dist/components/Input/input.d.ts +26 -3
  144. package/dist/components/Input/input.d.ts.map +1 -1
  145. package/dist/components/Input/input.js +12 -8
  146. package/dist/components/Input/input.js.map +1 -1
  147. package/dist/components/LinkInput/link-input.d.ts +3 -3
  148. package/dist/components/LinkInput/link-input.d.ts.map +1 -1
  149. package/dist/components/LinkInput/link-input.js +29 -28
  150. package/dist/components/LinkInput/link-input.js.map +1 -1
  151. package/dist/components/Menu/menu-item.d.ts.map +1 -1
  152. package/dist/components/Menu/menu-item.js +5 -4
  153. package/dist/components/Menu/menu-item.js.map +1 -1
  154. package/dist/components/Notice/notice.d.ts +5 -0
  155. package/dist/components/Notice/notice.d.ts.map +1 -1
  156. package/dist/components/Notice/notice.js +11 -5
  157. package/dist/components/Notice/notice.js.map +1 -1
  158. package/dist/components/NumberInput/number-input.d.ts +1 -1
  159. package/dist/components/NumberInput/number-input.d.ts.map +1 -1
  160. package/dist/components/NumberInput/number-input.js +17 -8
  161. package/dist/components/NumberInput/number-input.js.map +1 -1
  162. package/dist/components/OverflowIndicator/overflow-indicator.d.ts +1 -1
  163. package/dist/components/OverflowIndicator/overflow-indicator.d.ts.map +1 -1
  164. package/dist/components/OverflowIndicator/overflow-indicator.js +2 -1
  165. package/dist/components/OverflowIndicator/overflow-indicator.js.map +1 -1
  166. package/dist/components/Pagination/index.d.ts +2 -0
  167. package/dist/components/Pagination/index.d.ts.map +1 -0
  168. package/dist/components/Pagination/index.js +6 -0
  169. package/dist/components/Pagination/index.js.map +1 -0
  170. package/dist/components/Pagination/pagination.d.ts +49 -0
  171. package/dist/components/Pagination/pagination.d.ts.map +1 -0
  172. package/dist/components/Pagination/pagination.js +182 -0
  173. package/dist/components/Pagination/pagination.js.map +1 -0
  174. package/dist/components/PeoplePicker/people-picker.d.ts +3 -3
  175. package/dist/components/PeoplePicker/people-picker.d.ts.map +1 -1
  176. package/dist/components/PeoplePicker/people-picker.js +38 -36
  177. package/dist/components/PeoplePicker/people-picker.js.map +1 -1
  178. package/dist/components/PeoplePicker/person-display.d.ts +4 -0
  179. package/dist/components/PeoplePicker/person-display.d.ts.map +1 -1
  180. package/dist/components/PeoplePicker/person-display.js +3 -3
  181. package/dist/components/PeoplePicker/person-display.js.map +1 -1
  182. package/dist/components/Popover/popover.d.ts.map +1 -1
  183. package/dist/components/Popover/popover.js +58 -32
  184. package/dist/components/Popover/popover.js.map +1 -1
  185. package/dist/components/ProfileCard/profile-card.d.ts +11 -3
  186. package/dist/components/ProfileCard/profile-card.d.ts.map +1 -1
  187. package/dist/components/ProfileCard/profile-card.js +14 -4
  188. package/dist/components/ProfileCard/profile-card.js.map +1 -1
  189. package/dist/components/RadioGroup/radio-group.d.ts +1 -1
  190. package/dist/components/RadioGroup/radio-group.d.ts.map +1 -1
  191. package/dist/components/RadioGroup/radio-group.js +28 -3
  192. package/dist/components/RadioGroup/radio-group.js.map +1 -1
  193. package/dist/components/Rating/rating.js +3 -3
  194. package/dist/components/Rating/rating.js.map +1 -1
  195. package/dist/components/ScrollArea/scroll-area.d.ts +1 -1
  196. package/dist/components/ScrollArea/scroll-area.js +2 -2
  197. package/dist/components/ScrollArea/scroll-area.js.map +1 -1
  198. package/dist/components/SegmentedControl/segmented-control.d.ts +2 -2
  199. package/dist/components/SegmentedControl/segmented-control.d.ts.map +1 -1
  200. package/dist/components/SegmentedControl/segmented-control.js +23 -10
  201. package/dist/components/SegmentedControl/segmented-control.js.map +1 -1
  202. package/dist/components/Select/select.d.ts +10 -4
  203. package/dist/components/Select/select.d.ts.map +1 -1
  204. package/dist/components/Select/select.js +57 -14
  205. package/dist/components/Select/select.js.map +1 -1
  206. package/dist/components/SelectMenu/index.js +4 -2
  207. package/dist/components/SelectMenu/select-menu.d.ts +31 -6
  208. package/dist/components/SelectMenu/select-menu.d.ts.map +1 -1
  209. package/dist/components/SelectMenu/select-menu.js +169 -85
  210. package/dist/components/SelectMenu/select-menu.js.map +1 -1
  211. package/dist/components/Separator/separator.js +1 -1
  212. package/dist/components/Separator/separator.js.map +1 -1
  213. package/dist/components/Sheet/sheet.d.ts +1 -1
  214. package/dist/components/Sheet/sheet.d.ts.map +1 -1
  215. package/dist/components/Sheet/sheet.js +3 -2
  216. package/dist/components/Sheet/sheet.js.map +1 -1
  217. package/dist/components/Sidebar/sidebar.d.ts +2 -2
  218. package/dist/components/Sidebar/sidebar.d.ts.map +1 -1
  219. package/dist/components/Sidebar/sidebar.js +28 -10
  220. package/dist/components/Sidebar/sidebar.js.map +1 -1
  221. package/dist/components/Skeleton/skeleton.d.ts +1 -1
  222. package/dist/components/Skeleton/skeleton.js +2 -2
  223. package/dist/components/Skeleton/skeleton.js.map +1 -1
  224. package/dist/components/Slider/slider.d.ts +1 -1
  225. package/dist/components/Slider/slider.d.ts.map +1 -1
  226. package/dist/components/Slider/slider.js +7 -3
  227. package/dist/components/Slider/slider.js.map +1 -1
  228. package/dist/components/Steps/steps.d.ts.map +1 -1
  229. package/dist/components/Steps/steps.js +6 -3
  230. package/dist/components/Steps/steps.js.map +1 -1
  231. package/dist/components/Switch/switch.d.ts +2 -2
  232. package/dist/components/Switch/switch.d.ts.map +1 -1
  233. package/dist/components/Switch/switch.js +47 -9
  234. package/dist/components/Switch/switch.js.map +1 -1
  235. package/dist/components/Tabs/tabs.d.ts +8 -5
  236. package/dist/components/Tabs/tabs.d.ts.map +1 -1
  237. package/dist/components/Tabs/tabs.js +45 -33
  238. package/dist/components/Tabs/tabs.js.map +1 -1
  239. package/dist/components/Tag/tag.d.ts.map +1 -1
  240. package/dist/components/Tag/tag.js +20 -4
  241. package/dist/components/Tag/tag.js.map +1 -1
  242. package/dist/components/Textarea/textarea.d.ts +2 -1
  243. package/dist/components/Textarea/textarea.d.ts.map +1 -1
  244. package/dist/components/Textarea/textarea.js +24 -13
  245. package/dist/components/Textarea/textarea.js.map +1 -1
  246. package/dist/components/TimePicker/time-picker.d.ts +2 -2
  247. package/dist/components/TimePicker/time-picker.d.ts.map +1 -1
  248. package/dist/components/TimePicker/time-picker.js +23 -12
  249. package/dist/components/TimePicker/time-picker.js.map +1 -1
  250. package/dist/components/Toast/toast.d.ts +1 -1
  251. package/dist/components/Toast/toast.d.ts.map +1 -1
  252. package/dist/components/Toast/toast.js +7 -3
  253. package/dist/components/Toast/toast.js.map +1 -1
  254. package/dist/components/Tooltip/tooltip.js +1 -1
  255. package/dist/components/Tooltip/tooltip.js.map +1 -1
  256. package/dist/components/TreeView/tree-view.d.ts +1 -1
  257. package/dist/components/TreeView/tree-view.d.ts.map +1 -1
  258. package/dist/components/TreeView/tree-view.js +11 -5
  259. package/dist/components/TreeView/tree-view.js.map +1 -1
  260. package/dist/hooks/use-controllable.d.ts.map +1 -1
  261. package/dist/hooks/use-controllable.js +4 -2
  262. package/dist/hooks/use-controllable.js.map +1 -1
  263. package/dist/hooks/use-is-touch-device.d.ts.map +1 -1
  264. package/dist/hooks/use-is-touch-device.js +3 -1
  265. package/dist/hooks/use-is-touch-device.js.map +1 -1
  266. package/dist/index.d.ts +2 -0
  267. package/dist/index.d.ts.map +1 -1
  268. package/dist/index.js +14 -0
  269. package/dist/index.js.map +1 -1
  270. package/dist/patterns/element-anatomy/item-anatomy.d.ts +2 -2
  271. package/dist/patterns/element-anatomy/item-anatomy.d.ts.map +1 -1
  272. package/dist/patterns/element-anatomy/item-anatomy.js.map +1 -1
  273. package/dist/patterns/overlay-surface/overlay-surface.js.map +1 -1
  274. package/dist/tokens/categorical-color.d.ts +1 -1
  275. package/dist/tokens/categorical-color.js.map +1 -1
  276. package/dist/tokens/uiSize/icon-size.d.ts +2 -2
  277. package/ds-canonical/fork/governance.lock +1 -1
  278. package/ds-canonical/fork/hooks/check_opacity_token_usage.sh +10 -1
  279. package/ds-canonical/hooks/check_audit_post_report_validator.sh +13 -0
  280. package/ds-canonical/hooks/check_full_story_visual_interaction_sweep.sh +4 -3
  281. package/ds-canonical/hooks/check_opacity_token_usage.sh +10 -1
  282. package/ds-canonical/hooks/check_plugin_fork_health.sh +30 -22
  283. package/ds-canonical/hooks/check_story_invariants.sh +17 -5
  284. package/ds-canonical/hooks/check_substantive_edit_approval_preflight.sh +5 -2
  285. package/ds-canonical/references/principle-dim-map.json +100 -0
  286. package/ds-canonical/references/story-baseline-registry.json +4 -3
  287. package/ds-canonical/skills/deep-audit-cross-codex/SKILL.md +1 -1
  288. package/ds-canonical/skills/design-system-audit/SKILL.md +2 -2
  289. package/ds-story-manifest.json +41 -18
  290. package/llms-full.txt +11 -1
  291. package/llms.txt +3 -1
  292. package/package.json +1 -1
  293. package/src/components/Alert/alert.anatomy.stories.tsx +9 -6
  294. package/src/components/Alert/alert.spec.md +3 -3
  295. package/src/components/Alert/alert.tsx +9 -1
  296. package/src/components/AppShell/app-shell.anatomy.stories.tsx +3 -0
  297. package/src/components/AppShell/app-shell.spec.md +3 -2
  298. package/src/components/AppShell/app-shell.tsx +1 -1
  299. package/src/components/AspectRatio/aspect-ratio.spec.md +1 -2
  300. package/src/components/AspectRatio/aspect-ratio.tsx +3 -2
  301. package/src/components/Avatar/avatar.principles.stories.tsx +1 -1
  302. package/src/components/Avatar/avatar.spec.md +10 -30
  303. package/src/components/Avatar/avatar.stories.tsx +1 -1
  304. package/src/components/Avatar/avatar.tsx +30 -16
  305. package/src/components/Badge/badge.anatomy.stories.tsx +2 -2
  306. package/src/components/Badge/badge.spec.md +5 -4
  307. package/src/components/Badge/badge.tsx +4 -1
  308. package/src/components/Breadcrumb/breadcrumb.anatomy.stories.tsx +2 -2
  309. package/src/components/Breadcrumb/breadcrumb.principles.stories.tsx +15 -10
  310. package/src/components/Breadcrumb/breadcrumb.spec.md +5 -3
  311. package/src/components/Breadcrumb/breadcrumb.tsx +10 -4
  312. package/src/components/BulkActionBar/bulk-action-bar.anatomy.stories.tsx +3 -2
  313. package/src/components/BulkActionBar/bulk-action-bar.spec.md +15 -14
  314. package/src/components/BulkActionBar/bulk-action-bar.tsx +10 -4
  315. package/src/components/Button/button.anatomy.stories.tsx +6 -6
  316. package/src/components/Button/button.spec.md +3 -2
  317. package/src/components/Button/button.tsx +67 -27
  318. package/src/components/Button/index.ts +1 -0
  319. package/src/components/Calendar/calendar.anatomy.stories.tsx +5 -3
  320. package/src/components/Calendar/calendar.principles.stories.tsx +3 -1
  321. package/src/components/Calendar/calendar.spec.md +9 -2
  322. package/src/components/Calendar/calendar.stories.tsx +3 -1
  323. package/src/components/Calendar/calendar.tsx +53 -19
  324. package/src/components/Carousel/carousel.principles.stories.tsx +1 -1
  325. package/src/components/Carousel/carousel.tsx +4 -1
  326. package/src/components/Chart/chart.spec.md +1 -1
  327. package/src/components/Chart/chart.tsx +2 -1
  328. package/src/components/Checkbox/checkbox.principles.stories.tsx +1 -1
  329. package/src/components/Checkbox/checkbox.spec.md +1 -1
  330. package/src/components/Checkbox/checkbox.tsx +41 -7
  331. package/src/components/Checkbox/index.ts +1 -0
  332. package/src/components/Chip/chip.anatomy.stories.tsx +9 -9
  333. package/src/components/Chip/chip.principles.stories.tsx +3 -3
  334. package/src/components/Chip/chip.spec.md +7 -4
  335. package/src/components/Chip/chip.tsx +60 -15
  336. package/src/components/CircularProgress/circular-progress.anatomy.stories.tsx +1 -1
  337. package/src/components/CircularProgress/circular-progress.principles.stories.tsx +7 -5
  338. package/src/components/CircularProgress/circular-progress.spec.md +2 -0
  339. package/src/components/CircularProgress/circular-progress.stories.tsx +3 -11
  340. package/src/components/Coachmark/coachmark.anatomy.stories.tsx +2 -2
  341. package/src/components/Coachmark/coachmark.principles.stories.tsx +1 -1
  342. package/src/components/Coachmark/coachmark.spec.md +3 -3
  343. package/src/components/Coachmark/coachmark.tsx +23 -13
  344. package/src/components/Combobox/combobox.anatomy.stories.tsx +4 -4
  345. package/src/components/Combobox/combobox.spec.md +1 -1
  346. package/src/components/Combobox/combobox.tsx +30 -17
  347. package/src/components/Command/command.principles.stories.tsx +10 -12
  348. package/src/components/Command/command.spec.md +5 -5
  349. package/src/components/Command/command.stories.tsx +4 -4
  350. package/src/components/Command/command.tsx +3 -2
  351. package/src/components/DataTable/active-editor-controller.ts +4 -3
  352. package/src/components/DataTable/cell-registry.tsx +8 -0
  353. package/src/components/DataTable/data-table-filter-panel.tsx +8 -2
  354. package/src/components/DataTable/data-table-interaction-layer.tsx +8 -7
  355. package/src/components/DataTable/data-table.anatomy.stories.tsx +4 -4
  356. package/src/components/DataTable/data-table.css +2 -2
  357. package/src/components/DataTable/data-table.spec.md +72 -115
  358. package/src/components/DataTable/data-table.stories.tsx +51 -3
  359. package/src/components/DataTable/data-table.tsx +348 -63
  360. package/src/components/DataTable/filter-operators.spec.md +4 -4
  361. package/src/components/DataTable/filter-tree.ts +192 -42
  362. package/src/components/DateGrid/date-grid.anatomy.stories.tsx +1 -1
  363. package/src/components/DateGrid/date-grid.spec.md +11 -8
  364. package/src/components/DateGrid/date-grid.tsx +63 -8
  365. package/src/components/DatePicker/date-picker.principles.stories.tsx +1 -1
  366. package/src/components/DatePicker/date-picker.spec.md +1 -0
  367. package/src/components/DatePicker/date-picker.stories.tsx +19 -0
  368. package/src/components/DatePicker/date-picker.tsx +52 -15
  369. package/src/components/DescriptionList/description-list.spec.md +1 -1
  370. package/src/components/DescriptionList/description-list.tsx +3 -1
  371. package/src/components/Dialog/dialog.spec.md +1 -1
  372. package/src/components/Dialog/dialog.tsx +2 -1
  373. package/src/components/DropdownMenu/dropdown-menu.anatomy.stories.tsx +3 -1
  374. package/src/components/DropdownMenu/dropdown-menu.spec.md +8 -5
  375. package/src/components/DropdownMenu/dropdown-menu.tsx +33 -7
  376. package/src/components/Empty/empty.anatomy.stories.tsx +1 -1
  377. package/src/components/Empty/empty.spec.md +2 -2
  378. package/src/components/Empty/empty.stories.tsx +4 -15
  379. package/src/components/Empty/empty.tsx +4 -1
  380. package/src/components/Field/field-controls.spec.md +2 -1
  381. package/src/components/Field/field-wrapper.tsx +28 -8
  382. package/src/components/Field/field.anatomy.stories.tsx +7 -7
  383. package/src/components/Field/field.principles.stories.tsx +1 -1
  384. package/src/components/Field/field.spec.md +9 -7
  385. package/src/components/Field/field.tsx +2 -1
  386. package/src/components/Field/form-validation.spec.md +6 -3
  387. package/src/components/Field/use-form-validation.ts +49 -21
  388. package/src/components/FieldControlGroup/field-control-group.anatomy.stories.tsx +11 -10
  389. package/src/components/FieldControlGroup/field-control-group.principles.stories.tsx +3 -3
  390. package/src/components/FieldControlGroup/field-control-group.spec.md +3 -3
  391. package/src/components/FieldControlGroup/field-control-group.tsx +3 -3
  392. package/src/components/FileItem/file-item.anatomy.stories.tsx +6 -4
  393. package/src/components/FileItem/file-item.tsx +3 -2
  394. package/src/components/FileUpload/file-upload.anatomy.stories.tsx +1 -1
  395. package/src/components/FileUpload/file-upload.spec.md +4 -1
  396. package/src/components/FileUpload/file-upload.tsx +36 -10
  397. package/src/components/FileViewer/can-render-image.ts +15 -0
  398. package/src/components/FileViewer/file-viewer.anatomy.stories.tsx +6 -4
  399. package/src/components/FileViewer/file-viewer.spec.md +2 -2
  400. package/src/components/FileViewer/file-viewer.tsx +47 -21
  401. package/src/components/FileViewer/image-renderer.tsx +3 -8
  402. package/src/components/HoverCard/hover-card.spec.md +1 -1
  403. package/src/components/HoverCard/hover-card.tsx +3 -1
  404. package/src/components/Input/input.anatomy.stories.tsx +4 -4
  405. package/src/components/Input/input.principles.stories.tsx +1 -1
  406. package/src/components/Input/input.spec.md +1 -1
  407. package/src/components/Input/input.stories.tsx +11 -5
  408. package/src/components/Input/input.tsx +11 -9
  409. package/src/components/LinkInput/link-input.anatomy.stories.tsx +2 -0
  410. package/src/components/LinkInput/link-input.tsx +49 -34
  411. package/src/components/Menu/menu-item.anatomy.stories.tsx +1 -1
  412. package/src/components/Menu/menu-item.spec.md +4 -3
  413. package/src/components/Menu/menu-item.tsx +15 -4
  414. package/src/components/Notice/notice.anatomy.stories.tsx +10 -5
  415. package/src/components/Notice/notice.spec.md +4 -3
  416. package/src/components/Notice/notice.tsx +22 -5
  417. package/src/components/NumberInput/number-input.anatomy.stories.tsx +5 -4
  418. package/src/components/NumberInput/number-input.principles.stories.tsx +2 -1
  419. package/src/components/NumberInput/number-input.spec.md +2 -1
  420. package/src/components/NumberInput/number-input.tsx +25 -9
  421. package/src/components/OverflowIndicator/overflow-indicator.anatomy.stories.tsx +3 -3
  422. package/src/components/OverflowIndicator/overflow-indicator.spec.md +1 -1
  423. package/src/components/OverflowIndicator/overflow-indicator.tsx +2 -1
  424. package/src/components/Pagination/index.ts +5 -0
  425. package/src/components/Pagination/pagination.anatomy.stories.tsx +143 -0
  426. package/src/components/Pagination/pagination.principles.stories.tsx +81 -0
  427. package/src/components/Pagination/pagination.spec.md +150 -0
  428. package/src/components/Pagination/pagination.stories.tsx +98 -0
  429. package/src/components/Pagination/pagination.tsx +263 -0
  430. package/src/components/PeoplePicker/people-picker.anatomy.stories.tsx +1 -1
  431. package/src/components/PeoplePicker/people-picker.spec.md +4 -4
  432. package/src/components/PeoplePicker/people-picker.stories.tsx +1 -1
  433. package/src/components/PeoplePicker/people-picker.tsx +65 -55
  434. package/src/components/PeoplePicker/person-display.tsx +10 -4
  435. package/src/components/Popover/popover.anatomy.stories.tsx +2 -2
  436. package/src/components/Popover/popover.spec.md +13 -14
  437. package/src/components/Popover/popover.tsx +88 -39
  438. package/src/components/ProfileCard/profile-card.anatomy.stories.tsx +4 -4
  439. package/src/components/ProfileCard/profile-card.principles.stories.tsx +2 -2
  440. package/src/components/ProfileCard/profile-card.spec.md +1 -2
  441. package/src/components/ProfileCard/profile-card.stories.tsx +1 -1
  442. package/src/components/ProfileCard/profile-card.tsx +29 -8
  443. package/src/components/RadioGroup/radio-group.principles.stories.tsx +1 -1
  444. package/src/components/RadioGroup/radio-group.spec.md +2 -2
  445. package/src/components/RadioGroup/radio-group.stories.tsx +3 -1
  446. package/src/components/RadioGroup/radio-group.tsx +37 -3
  447. package/src/components/Rating/rating.anatomy.stories.tsx +11 -8
  448. package/src/components/Rating/rating.principles.stories.tsx +1 -1
  449. package/src/components/Rating/rating.spec.md +2 -2
  450. package/src/components/Rating/rating.stories.tsx +1 -1
  451. package/src/components/Rating/rating.tsx +3 -3
  452. package/src/components/ScrollArea/scroll-area.spec.md +1 -0
  453. package/src/components/ScrollArea/scroll-area.tsx +1 -1
  454. package/src/components/SegmentedControl/segmented-control.anatomy.stories.tsx +3 -3
  455. package/src/components/SegmentedControl/segmented-control.spec.md +7 -3
  456. package/src/components/SegmentedControl/segmented-control.tsx +29 -11
  457. package/src/components/Select/select.anatomy.stories.tsx +2 -2
  458. package/src/components/Select/select.spec.md +3 -2
  459. package/src/components/Select/select.tsx +62 -19
  460. package/src/components/SelectMenu/select-menu.anatomy.stories.tsx +1 -1
  461. package/src/components/SelectMenu/select-menu.principles.stories.tsx +1 -1
  462. package/src/components/SelectMenu/select-menu.spec.md +12 -8
  463. package/src/components/SelectMenu/select-menu.tsx +176 -20
  464. package/src/components/SelectionControl/selection-item.anatomy.stories.tsx +1 -1
  465. package/src/components/SelectionControl/selection-item.principles.stories.tsx +2 -2
  466. package/src/components/SelectionControl/selection-item.spec.md +2 -1
  467. package/src/components/SelectionControl/selection-item.stories.tsx +1 -1
  468. package/src/components/Separator/separator.anatomy.stories.tsx +2 -2
  469. package/src/components/Separator/separator.tsx +1 -1
  470. package/src/components/Sheet/sheet.spec.md +2 -1
  471. package/src/components/Sheet/sheet.tsx +4 -2
  472. package/src/components/Sidebar/sidebar.anatomy.stories.tsx +1 -1
  473. package/src/components/Sidebar/sidebar.spec.md +2 -1
  474. package/src/components/Sidebar/sidebar.stories.tsx +6 -4
  475. package/src/components/Sidebar/sidebar.tsx +41 -13
  476. package/src/components/Skeleton/skeleton.principles.stories.tsx +1 -1
  477. package/src/components/Skeleton/skeleton.spec.md +1 -2
  478. package/src/components/Skeleton/skeleton.tsx +1 -1
  479. package/src/components/Slider/slider.anatomy.stories.tsx +4 -4
  480. package/src/components/Slider/slider.spec.md +19 -85
  481. package/src/components/Slider/slider.tsx +13 -5
  482. package/src/components/Steps/steps.anatomy.stories.tsx +1 -1
  483. package/src/components/Steps/steps.spec.md +6 -4
  484. package/src/components/Steps/steps.tsx +11 -3
  485. package/src/components/Switch/switch.spec.md +4 -3
  486. package/src/components/Switch/switch.tsx +59 -10
  487. package/src/components/Tabs/tabs.anatomy.stories.tsx +7 -6
  488. package/src/components/Tabs/tabs.principles.stories.tsx +5 -3
  489. package/src/components/Tabs/tabs.spec.md +15 -48
  490. package/src/components/Tabs/tabs.tsx +70 -34
  491. package/src/components/Tag/tag.principles.stories.tsx +2 -2
  492. package/src/components/Tag/tag.spec.md +11 -43
  493. package/src/components/Tag/tag.tsx +30 -4
  494. package/src/components/Textarea/textarea.principles.stories.tsx +2 -1
  495. package/src/components/Textarea/textarea.spec.md +2 -1
  496. package/src/components/Textarea/textarea.tsx +20 -10
  497. package/src/components/TimePicker/time-picker.anatomy.stories.tsx +2 -1
  498. package/src/components/TimePicker/time-picker.spec.md +2 -2
  499. package/src/components/TimePicker/time-picker.tsx +35 -16
  500. package/src/components/Toast/toast.anatomy.stories.tsx +5 -6
  501. package/src/components/Toast/toast.principles.stories.tsx +10 -11
  502. package/src/components/Toast/toast.spec.md +5 -5
  503. package/src/components/Toast/toast.stories.tsx +5 -1
  504. package/src/components/Toast/toast.tsx +13 -7
  505. package/src/components/Tooltip/tooltip.spec.md +3 -3
  506. package/src/components/Tooltip/tooltip.tsx +1 -1
  507. package/src/components/TreeView/tree-view.anatomy.stories.tsx +6 -1
  508. package/src/components/TreeView/tree-view.principles.stories.tsx +2 -2
  509. package/src/components/TreeView/tree-view.spec.md +2 -2
  510. package/src/components/TreeView/tree-view.tsx +27 -5
  511. package/src/hooks/use-controllable.ts +7 -2
  512. package/src/hooks/use-is-touch-device.ts +8 -1
  513. package/src/index.ts +2 -1
  514. package/src/patterns/element-anatomy/element-anatomy.spec.md +3 -3
  515. package/src/patterns/element-anatomy/inline-action.spec.md +1 -1
  516. package/src/patterns/element-anatomy/item-anatomy.spec.md +5 -14
  517. package/src/patterns/element-anatomy/item-anatomy.stories.tsx +1 -0
  518. package/src/patterns/element-anatomy/item-anatomy.tsx +5 -6
  519. package/src/patterns/header-canonical/header-canonical.css +1 -2
  520. package/src/patterns/header-canonical/header-canonical.spec.md +5 -5
  521. package/src/patterns/horizontal-overflow/horizontal-overflow.spec.md +5 -4
  522. package/src/patterns/horizontal-overflow/horizontal-overflow.stories.tsx +137 -0
  523. package/src/patterns/overlay-surface/overlay-surface.spec.md +8 -7
  524. package/src/patterns/overlay-surface/overlay-surface.stories.tsx +116 -0
  525. package/src/patterns/overlay-surface/overlay-surface.tsx +5 -5
  526. package/src/patterns/resize-handle/resize-handle.spec.md +1 -1
  527. package/src/tokens/categorical-color.ts +1 -1
  528. package/src/tokens/color/color.spec.md +11 -13
  529. package/src/tokens/color/color.stories.tsx +1 -1
  530. package/src/tokens/color/primitives.css +7 -5
  531. package/src/tokens/color/semantic.css +8 -2
  532. package/src/tokens/density/density.spec.md +2 -0
  533. package/src/tokens/elevation/elevation.spec.md +1 -1
  534. package/src/tokens/layoutSpace/layoutSpace.spec.md +2 -1
  535. package/src/tokens/layoutSpace/layoutSpace.stories.tsx +3 -3
  536. package/src/tokens/motion/motion.spec.md +1 -1
  537. package/src/tokens/motion/motion.stories.tsx +142 -0
  538. package/src/tokens/opacity/opacity.spec.md +1 -1
  539. package/src/tokens/opacity/opacity.stories.tsx +7 -11
  540. package/src/tokens/radius/radius.spec.md +1 -1
  541. package/src/tokens/typography/typography.css +1 -1
  542. package/src/tokens/typography/typography.spec.md +1 -1
  543. package/src/tokens/uiSize/icon-size.ts +2 -2
  544. package/src/tokens/uiSize/uiSize.spec.md +12 -11
  545. package/src/tokens/uiSize/uiSize.stories.tsx +103 -0
  546. package/src/tokens/utility-registry.json +5 -4
  547. package/dist/node_modules/react-hook-form/dist/index.esm.js +0 -1950
  548. package/dist/node_modules/react-hook-form/dist/index.esm.js.map +0 -1
@@ -15,11 +15,15 @@
15
15
  import * as React from 'react'
16
16
  import { createPortal } from 'react-dom'
17
17
  import { Empty } from '@/design-system/components/Empty/empty'
18
+ // L5 分頁(2026-07-06):Pagination = 分頁完整功能 SSOT(頁碼 + showTotal + 每頁筆數選單
19
+ // 全 own 在 Pagination;共用模式,Ant Table 消費 Pagination 同派)—— DataTable 只轉發 config
20
+ import { Pagination } from '@/design-system/components/Pagination/pagination'
18
21
  import {
19
22
  useReactTable,
20
23
  getCoreRowModel,
21
24
  getSortedRowModel,
22
25
  getExpandedRowModel,
26
+ getPaginationRowModel,
23
27
  flexRender,
24
28
  type ColumnDef,
25
29
  type SortingState,
@@ -41,7 +45,7 @@ import { Tooltip, TooltipContent, TooltipTrigger } from '@/design-system/compone
41
45
  import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator } from '@/design-system/components/DropdownMenu/dropdown-menu'
42
46
  import { ItemInlineActionButton } from '@/design-system/patterns/element-anatomy/item-anatomy'
43
47
  import { columnTypeDefaults, type ColumnType } from './column-types'
44
- import { resolveCellComponent } from './cell-registry'
48
+ import { resolveCellComponent, type CellComponentProps } from './cell-registry'
45
49
  import { DataTableInteractionLayer } from './data-table-interaction-layer'
46
50
  import { Checkbox } from '@/design-system/components/Checkbox/checkbox'
47
51
  import { RadioGroupItem } from '@/design-system/components/RadioGroup/radio-group'
@@ -73,6 +77,12 @@ export interface DataTableProps<TData>
73
77
  data: TData[]
74
78
  size?: TableSize
75
79
  autoRowHeight?: boolean
80
+ /**
81
+ * 容器高度。預設 '400px'(body 內捲動);'auto' = 自然高度(hug rows);'100%' = fill parent。
82
+ * L5 分頁(2026-07-06):啟用 `pagination` 且未顯式傳 height 時預設改 'auto' —— 分頁的
83
+ * 導覽通道是頁碼,再疊 body 內捲動 = 雙重導覽(一頁 20 筆只露 10 筆);對齊 Ant Table
84
+ * 無預設高度罩、`scroll.y` 顯式選配的慣例。顯式傳 height 照舊尊重(分頁 + 頁內捲動可並用)。
85
+ */
76
86
  height?: string
77
87
  overscan?: number
78
88
  emptyState?: React.ReactNode
@@ -105,7 +115,8 @@ export interface DataTableProps<TData>
105
115
  * Slice D Step 1B(2026-05-10):啟用 spreadsheet-grade interaction overlay。
106
116
  * Default false(backward-compat)。Enable 後 hover/editor/selected/range 由
107
117
  * `DataTableInteractionLayer` overlay 統一畫,per `.claude/planning/datatable-spreadsheet-rfc.md`。
108
- * 漸進切換階段,當前 v1 minimal:hover overlay 1 layer
118
+ * 現況:hover ring + selection ring + active editor host 三個 sub-layer
119
+ * (詳 `data-table-interaction-layer.tsx` 檔頭)。
109
120
  */
110
121
  experimentalSpreadsheetOverlay?: boolean
111
122
  /**
@@ -121,15 +132,16 @@ export interface DataTableProps<TData>
121
132
  * Slice D Step 4(spreadsheet semantics,2026-05-10 user 拍板 + codex Layer B Q2.1 confirm):
122
133
  * Excel-like cell selection:click 1=select / click 2=edit / Shift+click=range。
123
134
  * Default false opt-in(per codex「DataTable is not a spreadsheet」既有原則 +
124
- * data-table.principles.stories.tsx L283-292)。
135
+ * data-table.principles.stories.tsx「不是試算表」principle story)。
125
136
  * Enable 後 inlineEdit cell click 行為:
126
137
  * - Plain click → setSelectedCellId,**不**進 edit mode
127
138
  * - Click on already-selected → enter edit
128
139
  * - Shift+click → extend range from anchor
129
140
  * - Double-click / Enter / F2 / printable(deferred) → enter edit on selected
130
141
  * - Click empty area → clear selection
131
- * 視覺:Layer SelectionRect(solid `--primary` 2px border)+ RangeRect
132
- * (`--primary-subtle` bg fill) per user「不要 dash 直接實的就好」+ codex Q2.2 token。
142
+ * 視覺:selection ring 1px `--primary`(layer `CELL_RING_STYLES.selected`);range 視覺
143
+ * = cell-bg `--primary-subtle`(`[data-range-cell]` CSS),outer ring 2026-05-10 retire
144
+ * — per user「不要 dash 直接實的就好」+ codex Q2.2 token。
133
145
  */
134
146
  spreadsheetMode?: boolean
135
147
 
@@ -140,7 +152,8 @@ export interface DataTableProps<TData>
140
152
  defaultSelection?: string[] | DataTableSelection
141
153
  /** Selection 變更 callback(emit DataTableSelection union;include / all 兩模型) */
142
154
  onSelectionChange?: (next: DataTableSelection) => void
143
- /** 全資料集筆數 M(server-side / filter 後);all 模式 count = totalCount − excluded.length(consumer 計算) */
155
+ /** 全資料集筆數 M(server-side / filter 後);all 模式 count = totalCount − excluded.length
156
+ * **DS 內部零消費(no-op prop)**:僅型別文件 + destructure 防 spread 到 DOM;count 全由 consumer 計算 */
144
157
  totalCount?: number
145
158
  /** 是否啟用 selection / 模式;true 等同 'multi' */
146
159
  selectable?: boolean | 'single' | 'multi'
@@ -148,7 +161,7 @@ export interface DataTableProps<TData>
148
161
  isRowSelectable?: (row: TData) => boolean
149
162
  /** 取 row 唯一 ID(selection 用);default `(row, index) => String(index)` */
150
163
  getRowId?: (row: TData, index: number) => string
151
- /** Checkbox aria-label fallback;default `'Select row'` */
164
+ /** Checkbox aria-label fallback;default `'選取此列'` */
152
165
  getRowAriaLabel?: (row: TData) => string
153
166
  /** Filter 後 hidden selected rows 是否保留(default false,對齊 Material/AG Grid 共識) */
154
167
  preserveSelectionOnFilter?: boolean
@@ -237,6 +250,29 @@ export interface DataTableProps<TData>
237
250
  * @param position 'before' | 'after'
238
251
  */
239
252
  onColumnReorder?: (sourceId: string, targetId: string, position: 'before' | 'after') => void
253
+ /**
254
+ * L5 分頁(2026-07-06):`true` 或 config 物件啟用——內部接 TanStack `getPaginationRowModel`,
255
+ * 表格下方渲染分頁列(消費 `<Pagination>` 獨立元件,間距 tight、頁碼靠右)。
256
+ * 啟用時虛擬滾動自動關閉(TanStack 官方:互斥替代策略)。v1 = client-side only。
257
+ * 詳 data-table.spec.md「L5:分頁」段。
258
+ */
259
+ pagination?: boolean | DataTablePaginationOptions
260
+ }
261
+
262
+ /** L5 分頁 config(頁碼 1-based 對外,對齊 Pagination / Ant;詳 data-table.spec.md「L5:分頁」段) */
263
+ export interface DataTablePaginationOptions {
264
+ /** 每頁筆數(預設 20) */
265
+ pageSize?: number
266
+ /** 每頁筆數選項;傳了才渲染「每頁 N 筆」Select(消費既有 Select size="sm") */
267
+ pageSizeOptions?: number[]
268
+ /** 左側「共 N 筆」文字(opt-in,= Ant showTotal 邏輯;數源 = filter 後全集 getPrePaginationRowModel,非 selection all-mode 的 totalCount) */
269
+ showTotal?: boolean
270
+ /** 當前頁(1-based,controlled;不傳 = uncontrolled) */
271
+ page?: number
272
+ /** uncontrolled 初始頁(1-based,預設 1) */
273
+ defaultPage?: number
274
+ onPageChange?: (page: number) => void
275
+ onPageSizeChange?: (pageSize: number) => void
240
276
  }
241
277
 
242
278
  // ── Cell Rendering(Phase C 2026-05-05 — type-keyed registry SSOT)─────────────
@@ -484,7 +520,6 @@ function SourceRowProvider({
484
520
  droppable.setNodeRef(el)
485
521
  }, [draggable.setNodeRef, droppable.setNodeRef])
486
522
  const isDragging = draggable.isDragging
487
- const style: React.CSSProperties = { ...dragSourceStyle(isDragging) }
488
523
  // a11y(2026-05-07 v15.10 codex P1 fix):button-only drag mode 下,row 本身不該成為
489
524
  // keyboard tab stop。dnd-kit `useDraggable.attributes` 含 `role="button" tabIndex=0
490
525
  // aria-roledescription="..."` 全給 activator 用,套到 row div 會讓每筆 row tabbable
@@ -492,10 +527,14 @@ function SourceRowProvider({
492
527
  // **拆分**:rowAttributes 留空(row 是 passive container)/ handleAttributes 全給
493
528
  // RowDragHandle Button(它是真 activator,Button 自帶 role/tabIndex 完全相容)。
494
529
  const handleAttrs = draggable.attributes as unknown as Record<string, unknown>
495
- const ctxValue: SortableRowCtxValue = {
530
+ // 2026-07-05 D3 perf fix:ctxValue useMemo 原每 render 新 object,RowDragHandle 的
531
+ // useLayoutEffect deps [rowEl, ctx] 在每次 DataTableInner render 都 teardown/重掛
532
+ // MutationObserver + window scroll/resize listener(×每個 visible primary row)。
533
+ // memo 後 identity 只在 drag 相關值真變(isDragging / invalidDrop 等)時才換。
534
+ const ctxValue: SortableRowCtxValue = React.useMemo(() => ({
496
535
  setNodeRef: setRefs,
497
536
  role,
498
- style,
537
+ style: { ...dragSourceStyle(isDragging) },
499
538
  attributes: {},
500
539
  isDragging,
501
540
  // row 不接 listeners(button-only),baseRowDiv `{...(extra?.listeners ?? {})}` 自動 noop
@@ -508,7 +547,7 @@ function SourceRowProvider({
508
547
  handleListeners: draggable.listeners as unknown as Record<string, unknown> | undefined,
509
548
  handleAttributes: handleAttrs,
510
549
  invalidDrop,
511
- }
550
+ }), [setRefs, role, isDragging, draggable.setActivatorNodeRef, draggable.listeners, handleAttrs, invalidDrop])
512
551
  return <SortableRowCtx.Provider value={ctxValue}>{children(ctxValue)}</SortableRowCtx.Provider>
513
552
  }
514
553
 
@@ -536,7 +575,9 @@ function MirrorRowProvider({
536
575
  const droppable = useDroppable({ id, disabled, data: { type: 'row' } })
537
576
  const dndCtx = useDndContext()
538
577
  const isDragging = dndCtx.active?.id === id
539
- const ctxValue: SortableRowCtxValue = {
578
+ // 2026-07-05 D3 perf fix:ctxValue useMemo(同 SourceRowProvider — 消掉 RowDragHandle
579
+ // effect 的每 render observer/listener churn;mirror 雖不渲 handle,identity 穩定仍省下游 diff)
580
+ const ctxValue: SortableRowCtxValue = React.useMemo(() => ({
540
581
  setNodeRef: droppable.setNodeRef,
541
582
  role,
542
583
  style: { ...dragSourceStyle(isDragging) },
@@ -548,7 +589,7 @@ function MirrorRowProvider({
548
589
  handleListeners: undefined,
549
590
  handleAttributes: {},
550
591
  invalidDrop,
551
- }
592
+ }), [droppable.setNodeRef, role, isDragging, invalidDrop])
552
593
  return <SortableRowCtx.Provider value={ctxValue}>{children(ctxValue)}</SortableRowCtx.Provider>
553
594
  }
554
595
 
@@ -661,11 +702,16 @@ function RowDragHandle({ disabled, anyDragActive }: { disabled: boolean; anyDrag
661
702
  // v15.1:drag 期間 source button hide(visible 邏輯已 guard isDragging),
662
703
  // 此處只報「真實 hover」狀態,不疊 isDragging mask。
663
704
  const rowHovered = rowEl.hasAttribute('data-hovered')
664
- setPos({
665
- top: rRect.top + rRect.height / 2,
666
- left: tRect.left, // table outer border line position(viewport coords)
667
- rowHovered,
668
- })
705
+ const top = rRect.top + rRect.height / 2
706
+ const left = tRect.left // table outer 左 border line position(viewport coords)
707
+ // 2026-07-05 D3 perf fix:prev 值比對 位置/hover 沒變時回傳 prev reference,
708
+ // React Object.is bail out(原每 scroll frame 無條件新 object → 每個 visible handle
709
+ // 每 frame 必 re-render Button + Tooltip + portal,即使位置根本沒動)
710
+ setPos((prev) =>
711
+ prev && prev.top === top && prev.left === left && prev.rowHovered === rowHovered
712
+ ? prev
713
+ : { top, left, rowHovered },
714
+ )
669
715
  }
670
716
 
671
717
  update()
@@ -751,10 +797,11 @@ function RowDragHandle({ disabled, anyDragActive }: { disabled: boolean; anyDrag
751
797
  // 2026-05-12 fix v2(user 抓「drag column sort 啟用時 button 不是 disable 視覺」):
752
798
  // 前 Round 4.5 加 `aria-disabled:opacity-[var(--opacity-disabled)]` 在 Button cva
753
799
  // 沒生效 — 因為 inline style `opacity` 永遠 win over Tailwind class。Fix:把 disabled
754
- // state opacity 也 compute 進 inline style。priority order:invisible 0 → drag 0.5 →
755
- // canDrag=false(sort active)disabled visual var(--opacity-disabled) 0.45idle 1。
800
+ // state opacity 也 compute 進 inline style。priority order:invisible 0 → drag var(--opacity-disabled)
801
+ // 0.45(2026-07-04 修:原硬寫 0.5 違 lib/drag-visual.ts SSOT)canDrag=false(sort active)
802
+ // disabled visual var(--opacity-disabled) 0.45 → idle 1。
756
803
  opacity: visible
757
- ? (ctx.isDragging ? 0.5 : (canDrag ? 1 : 'var(--opacity-disabled)' as unknown as number))
804
+ ? (ctx.isDragging ? 'var(--opacity-disabled)' as unknown as number : (canDrag ? 1 : 'var(--opacity-disabled)' as unknown as number))
758
805
  : 0,
759
806
  pointerEvents: visible ? 'auto' : 'none',
760
807
  transition: 'opacity 150ms ease',
@@ -795,25 +842,83 @@ function RowDragHandle({ disabled, anyDragActive }: { disabled: boolean; anyDrag
795
842
  }
796
843
 
797
844
  // ══════════════════════════════════════════════════════════════════════════════
798
- // AG Grid 模式:header 在 scroll 外面,body 是唯一的垂直 scroll container。
845
+ // AG Grid 模式:header 在 scroll 外面,V scroll 在 center-body(AR44 後現況)。
799
846
  //
800
847
  // table
801
848
  // ├── header(固定頂部,不在 scroll 內)
802
849
  // │ ├── left-header
803
850
  // │ ├── center-header(overflow:hidden,JS sync scrollLeft)
804
851
  // │ └── right-header
805
- // └── body-viewport(overflow-y:auto,display:flex)
806
- // ├── left-body(overflow:hidden)
807
- // ├── center-body(overflow-x:auto, overflow-y:hidden)
808
- // └── right-body(overflow:hidden
852
+ // └── body-viewport(display:flex,無 overflow — AR44 後不再是 scroll container
853
+ // ├── left-body(overflow:hidden,JS sync scrollTop
854
+ // ├── center-body(overflow-x:auto, overflow-y:auto — 唯一 V scroll,
855
+ // │ onCenterBodyScroll 同步兩側 scrollTop + header scrollLeft
856
+ // └── right-body(overflow:hidden,JS sync scrollTop)
809
857
  //
810
- // 不用 CSS sticky。Header 永遠在頂部。
858
+ // 不用 CSS sticky。Header 永遠在頂部。細節見下方「JS scroll sync(AR44)」註解。
811
859
  // ══════════════════════════════════════════════════════════════════════════════
812
860
 
861
+ // ── MemoCellSlot(2026-07-05 D3 perf fix)────────────────────────────────────
862
+ // cell-registry 2026-05-13 已把每個 cell type 包成 module-level cached `React.memo(CellWithSurface)`,
863
+ // 但 renderCellContent 每次 render 傳 3 個全新 inline closure(onCommit / onCommitLive /
864
+ // onRequestEdit)+ `meta ?? {}` 新 object → memo shallow-compare 100% fail,防線靜默失效
865
+ // (DataTableInner 任何 re-render — scroll / resize / drag / spreadsheet 選取 / portal keystroke —
866
+ // 都讓全部 visible cells 的 Field display subtree 整棵重渲)。
867
+ // Fix:中介 memo slot — 從 parent 接 stable callback(commitCell / cancelCellEdit / onCellCommit /
868
+ // enterCellEdit)+ primitive rowId / colId,在 slot 內 useCallback 綁 per-cell closure;
869
+ // props 全 primitive / stable reference → memo 真命中,unchanged cells render 成本歸零。
870
+ // Cite world-class:AG Grid「cell renderer stable reference」/ MUI X DataGrid memoized subcomponents。
871
+ const EMPTY_META: Record<string, unknown> = {}
872
+
873
+ interface MemoCellSlotProps {
874
+ Cell: React.ComponentType<CellComponentProps>
875
+ rowId: string
876
+ colId: string
877
+ // any-allow: free-form column value(consumer-defined,同 CellComponentProps.value)
878
+ value: any
879
+ // any-allow: free-form consumer meta bag(同 CellComponentProps.meta)
880
+ meta: Record<string, any>
881
+ mode: 'display' | 'edit'
882
+ size: 'sm' | 'md' | 'lg'
883
+ autoRowHeight: boolean
884
+ isEditable: boolean
885
+ isDisabled: boolean
886
+ onCommitCell: (rowId: string, colId: string, next: unknown) => void
887
+ onCancelCell: (rowId: string, colId: string) => void
888
+ onCellCommitLive?: (rowId: string, colId: string, next: unknown) => void
889
+ onEnterEdit: (rowId: string, colId: string) => void
890
+ }
891
+
892
+ const MemoCellSlot = React.memo(function MemoCellSlot({
893
+ Cell, rowId, colId, value, meta, mode, size, autoRowHeight,
894
+ isEditable, isDisabled, onCommitCell, onCancelCell, onCellCommitLive, onEnterEdit,
895
+ }: MemoCellSlotProps) {
896
+ const onCommit = React.useCallback((next: unknown) => onCommitCell(rowId, colId, next), [onCommitCell, rowId, colId])
897
+ const onCommitLive = React.useCallback((next: unknown) => onCellCommitLive?.(rowId, colId, next), [onCellCommitLive, rowId, colId])
898
+ const onCancel = React.useCallback(() => onCancelCell(rowId, colId), [onCancelCell, rowId, colId])
899
+ // disabled guard 保留原 renderCellContent inline closure 語意(`() => !disabled && setEditingCellId(...)`)
900
+ const onRequestEdit = React.useCallback(() => { if (!isDisabled) onEnterEdit(rowId, colId) }, [onEnterEdit, isDisabled, rowId, colId])
901
+ return (
902
+ <Cell
903
+ value={value}
904
+ meta={meta}
905
+ mode={mode}
906
+ size={size}
907
+ autoRowHeight={autoRowHeight}
908
+ isEditable={isEditable}
909
+ isDisabled={isDisabled}
910
+ onCommit={onCommit}
911
+ onCommitLive={onCommitLive}
912
+ onCancel={onCancel}
913
+ onRequestEdit={onRequestEdit}
914
+ />
915
+ )
916
+ })
917
+
813
918
  // code-quality-allow: long-function — foundational composite main body — 拆 sub-fn 會複雜化 local state / ref / context binding
814
919
  function DataTableInner<TData>(
815
920
  {
816
- columns, data, size = 'md', autoRowHeight = false, height = '400px',
921
+ columns, data, size = 'md', autoRowHeight = false, height: heightProp,
817
922
  overscan = 5, emptyState, enableHover = true, bordered,
818
923
  estimateRowHeight, tableOptions, rowActions, cellErrors,
819
924
  pinnedLeftColumns, pinnedRightColumns, inlineEdit = false,
@@ -834,22 +939,25 @@ function DataTableInner<TData>(
834
939
  experimentalSpreadsheetOverlay = false,
835
940
  experimentalActiveEditorController = false,
836
941
  spreadsheetMode = false,
942
+ pagination,
837
943
  className, ...props
838
944
  }: DataTableProps<TData>,
839
945
  ref: React.ForwardedRef<HTMLDivElement>
840
946
  ) {
947
+ // L5 分頁 × height 預設(2026-07-06):未顯式傳 height 時 —— 分頁 = 'auto' 自然高度
948
+ // (頁碼是唯一導覽通道,不疊 body 內捲動;Ant Table 無預設高度罩慣例),
949
+ // 未分頁 = 既有 '400px' 預設不變。顯式傳 height 一律尊重(prop docblock 有完整規則)。
950
+ const height = heightProp ?? (pagination ? 'auto' : '400px')
841
951
  // ── L4 Inline edit state ──
842
952
  // editingCellId: `${rowId}__${columnId}` 標識當前進 edit mode 的 cell;null = 無
843
953
  const [editingCellId, setEditingCellId] = React.useState<string | null>(null)
844
954
  // Phase 7 D.3 portal Field virtualizer unmount preserve draft(2026-05-10 per codex Q-B4 verdict):
845
955
  // Lifted draft state in DataTable — Cell DOM unmount(virtualizer scroll out)時 draft 不丟,
846
956
  // mount-back 時 portal Field value=draft 而非 row.value,user 編輯中字保留。
847
- // editingCellId 變時 useEffect reset draft 到新 cell row.value(全新 edit session)。
957
+ // draft 生命週期(2026-07-05 D4 fix):cancelCellEdit / commitCell draft;Tab 換格
958
+ // (handleEditTab)先 commit 當前 draft 再換格 — 原註解宣稱「editingCellId 變時 useEffect
959
+ // reset draft」的 effect 從未存在,曾致 Tab 換格後下一格顯示上一格草稿。
848
960
  const [editingDraft, setEditingDraft] = React.useState<unknown>(undefined)
849
- const exitEdit = React.useCallback(() => {
850
- setEditingCellId(null)
851
- setEditingDraft(undefined)
852
- }, [])
853
961
 
854
962
  // ── Slice D Step 4 spreadsheet semantics state(2026-05-10) ──
855
963
  // selectedCellId:`${rowId}:${colId}` Excel-like 選取(click 1)
@@ -878,14 +986,44 @@ function DataTableInner<TData>(
878
986
  document.addEventListener('pointerdown', handler, true)
879
987
  return () => document.removeEventListener('pointerdown', handler, true)
880
988
  }, [spreadsheetMode, selectedCellId, rangeAnchor])
989
+ // 2026-07-05 D4 fix:edit 退出(commit / cancel)後還原 spreadsheet selection 到該 cell +
990
+ // 收回焦點到 table root — 原本只清 editingCellId,editor unmount 後 document.activeElement
991
+ // 掉到 body、selection 不還原 → 每次編輯後鍵盤導覽死巷(方向鍵全滅、必須滑鼠重點),違反
992
+ // datatable-spreadsheet-rfc.md Contract 11「退出後還給 active cell」(對齊 Excel / AG Grid / Sheets)。
993
+ const restoreCellSelection = React.useCallback((rowId: string, colId: string) => {
994
+ if (!spreadsheetMode) return
995
+ const cellId = `${rowId}:${colId}`
996
+ setSelectedCellId(cellId)
997
+ setRangeAnchor(cellId)
998
+ setRangeFocus(null)
999
+ // 等 editor unmount 完成後才收回焦點;activeElement 非 body(user 點了其他 focusable /
1000
+ // Tab 換格後下一格 editor autoFocus)時不搶焦點。
1001
+ requestAnimationFrame(() => {
1002
+ const el = tableRef.current
1003
+ if (!el) return
1004
+ const active = document.activeElement
1005
+ if (active === document.body || active === null) el.focus({ preventScroll: true })
1006
+ })
1007
+ }, [spreadsheetMode])
881
1008
  const commitCell = React.useCallback(
882
1009
  (rowId: string, colId: string, next: unknown) => {
883
1010
  onCellCommit?.(rowId, colId, next)
884
1011
  setEditingCellId(null)
885
1012
  setEditingDraft(undefined) // Phase 7:commit 後清 draft
1013
+ restoreCellSelection(rowId, colId) // 2026-07-05 D4:commit 後 selection 還原(Contract 11)
886
1014
  },
887
- [onCellCommit],
1015
+ [onCellCommit, restoreCellSelection],
888
1016
  )
1017
+ // cancel 路徑(Esc / popover dismiss)— 同 commit 還原 selection;取代舊無參數 exitEdit
1018
+ const cancelCellEdit = React.useCallback((rowId: string, colId: string) => {
1019
+ setEditingCellId(null)
1020
+ setEditingDraft(undefined)
1021
+ restoreCellSelection(rowId, colId)
1022
+ }, [restoreCellSelection])
1023
+ // edit 進入點(MemoCellSlot onEnterEdit 消費;stable identity 維持 memo 命中)
1024
+ const enterCellEdit = React.useCallback((rowId: string, colId: string) => {
1025
+ setEditingCellId(cellEditId(rowId, colId))
1026
+ }, [])
889
1027
  // 判 column meta.editable 對特定 row 是否成立(支援 fn)
890
1028
  // column meta 是 free-form consumer bag(同 renderTypedValue any policy),不適合窄型化
891
1029
  const isCellEditable = React.useCallback(
@@ -1000,6 +1138,20 @@ function DataTableInner<TData>(
1000
1138
  // 注意:`...tableOptions` 必 spread 在 `state` 前,否則 user 傳的 tableOptions 會
1001
1139
  // 整個 override 掉我們組的 state(含 __select__ 自動 pinning + columnOrder 注入)。
1002
1140
  // 之前 bug:checkbox column 跑到右邊 = 此處 spread 順序錯。
1141
+ // ── L5 分頁 state(2026-07-06)──────────────────────────────────────────────
1142
+ // 共用模式(user 拍板):<Pagination> 獨立元件為頁碼視覺 SSOT,DataTable 內建接 TanStack
1143
+ // 分頁模型消費它(Ant Table 消費 Pagination / Atlassian DynamicTable rowsPerPage / MUI
1144
+ // DataGrid 同派;shadcn data-table 教學同款「分頁 render 在元件 JSX 內」)。
1145
+ const paginationEnabled = !!pagination
1146
+ const paginationOpts = typeof pagination === 'object' ? pagination : undefined
1147
+ const [pageSizeState, setPageSizeState] = React.useState(paginationOpts?.pageSize ?? 20)
1148
+ // 1-based 對外(對齊 Pagination / Ant);餵 TanStack state 時換算 0-based
1149
+ const [currentPage, setCurrentPage] = useControllable<number>({
1150
+ value: paginationOpts?.page,
1151
+ defaultValue: paginationOpts?.defaultPage ?? 1,
1152
+ onChange: paginationOpts?.onPageChange,
1153
+ })
1154
+
1003
1155
  const table = useReactTable({
1004
1156
  ...tableOptions,
1005
1157
  data, columns: columnsWithSelection,
@@ -1009,6 +1161,9 @@ function DataTableInner<TData>(
1009
1161
  // columnPinning + columnOrder 在 user state 後 override,確保 __select__ 永遠左
1010
1162
  columnPinning: { left: effectivePinnedLeft, right: pinnedRightColumns ?? [] },
1011
1163
  ...(effectiveColumnOrder ? { columnOrder: effectiveColumnOrder } : {}),
1164
+ // L5 分頁:必在 ...tableOptions?.state 之後 spread(同 columnPinning override 理由——
1165
+ // 防 user state 蓋掉內建接線);頁碼變更由 <Pagination onPageChange> 驅動,不走 TanStack 內部 setter
1166
+ ...(paginationEnabled ? { pagination: { pageIndex: currentPage - 1, pageSize: pageSizeState } } : {}),
1012
1167
  },
1013
1168
  enableMultiSort,
1014
1169
  // **#1 fix(2026-05-04)**:chain user `tableOptions.onSortingChange`(spread 在前被 override = 之前 bug)
@@ -1026,6 +1181,8 @@ function DataTableInner<TData>(
1026
1181
  getSortedRowModel: getSortedRowModel(),
1027
1182
  // L4 nested rows:啟用 expanded row model(consumer 透過 tableOptions.getSubRows + state.expanded forward)
1028
1183
  getExpandedRowModel: getExpandedRowModel(),
1184
+ // L5 分頁:條件掛載(TanStack 官方:pagination 與 virtualization 為互斥替代策略)
1185
+ ...(paginationEnabled ? { getPaginationRowModel: getPaginationRowModel() } : {}),
1029
1186
  getRowId: getRowId,
1030
1187
  // 2026-05-06 v14 column resize:`onChange` mode → drag 中 column 即時跟動 cursor(world-class
1031
1188
  // canonical:TanStack docs / AG Grid / Excel / Google Sheets 全部 live resize)。前 v13.2
@@ -1040,10 +1197,20 @@ function DataTableInner<TData>(
1040
1197
  })
1041
1198
 
1042
1199
  // v13.2:onColumnResize callback 透過 useEffect 觀測 columnSizing state 變動 fire(uncontrolled state pattern)
1200
+ // 2026-07-06 D3 fix:兌現 prop docblock「commit-on-pointerup,非 live」契約 —
1201
+ // columnResizeMode:'onChange' 讓 drag 中 columnSizing 每 mousemove 變一次(視覺即時跟動,
1202
+ // 保留),原 effect 無 gate → callback 每 mousemove fire 一次;docblock 明文建議 consumer
1203
+ // 在此 callback 做 width persistence(localStorage / URL / API)= 每 mousemove I/O。
1204
+ // 改以 TanStack columnSizingInfo.isResizingColumn 為訊號:drag 進行中不 fire、也不推進
1205
+ // snapshot(保留 drag 前基準);pointerup(isResizingColumn 轉 false)才 diff snapshot,
1206
+ // fire 一次最終寬度。非 drag 路徑(「自動調整寬度」menu 的 setColumnSizing)不經
1207
+ // isResizingColumn,行為不變。
1043
1208
  const columnSizingState = table.getState().columnSizing
1209
+ const isResizingColumn = table.getState().columnSizingInfo.isResizingColumn
1044
1210
  const prevColumnSizingRef = React.useRef(columnSizingState)
1045
1211
  React.useEffect(() => {
1046
1212
  if (!onColumnResize) return
1213
+ if (isResizingColumn) return
1047
1214
  const prev = prevColumnSizingRef.current
1048
1215
  Object.keys(columnSizingState).forEach(id => {
1049
1216
  if (columnSizingState[id] !== prev[id]) {
@@ -1051,10 +1218,17 @@ function DataTableInner<TData>(
1051
1218
  }
1052
1219
  })
1053
1220
  prevColumnSizingRef.current = columnSizingState
1054
- }, [columnSizingState, onColumnResize])
1221
+ }, [columnSizingState, isResizingColumn, onColumnResize])
1055
1222
 
1056
1223
  const { rows } = table.getRowModel()
1057
1224
  const isEmpty = rows.length === 0
1225
+
1226
+ // L5 分頁:filter / data 縮小時 TanStack 不自動 clamp 當前頁(會停在空頁);對齊 MUI X 自動 clamp
1227
+ const pageCount = paginationEnabled ? table.getPageCount() : 0
1228
+ React.useEffect(() => {
1229
+ if (!paginationEnabled) return
1230
+ if (pageCount > 0 && currentPage > pageCount) setCurrentPage(pageCount)
1231
+ }, [paginationEnabled, pageCount, currentPage, setCurrentPage])
1058
1232
  const hasHeightConstraint = height !== 'auto'
1059
1233
  // Fill-parent mode:height='100%' / '100vh' / 'fill' 等百分比 / 視口語義 → outer flex column + body flex-1 撐滿。
1060
1234
  // 固定 px/rem 仍維持 maxHeight cap 行為(資料少 = 內容高度,資料多 = 上限後 scroll)— 對齊既有 stories 預期。
@@ -1066,7 +1240,8 @@ function DataTableInner<TData>(
1066
1240
  // direct render 完全 bypass 此 race,且小資料下虛擬化沒效益(浪費 reflow)。
1067
1241
  // 對齊 AG Grid `suppressVirtualization` / TanStack Table virtualization-when-needed idiom。
1068
1242
  const VIRTUAL_THRESHOLD = 30
1069
- const useVirtual = hasHeightConstraint && !isEmpty && rows.length > VIRTUAL_THRESHOLD
1243
+ // L5 分頁互斥:分頁後每頁 rows pageSize,虛擬化無效益(TanStack 官方:兩者為互斥替代策略)
1244
+ const useVirtual = !paginationEnabled && hasHeightConstraint && !isEmpty && rows.length > VIRTUAL_THRESHOLD
1070
1245
  const hasRowActions = !!rowActions
1071
1246
 
1072
1247
  // Refs
@@ -1118,6 +1293,8 @@ function DataTableInner<TData>(
1118
1293
  // 後續 measureElement 修正到 6×40 = 240,差 24px 視覺看起來像「table 慢慢長高」。fix = estimate
1119
1294
  // 預設 size-aware 對齊 token(見下方 estimateRowHeight default 計算)。
1120
1295
  const [bodyMaxHeight, setBodyMaxHeight] = React.useState<number | null>(null)
1296
+ // L5 分頁 × isFillHeight(2026-07-07 C12):bar 高度量測用 — compute() 需扣分頁列 footprint
1297
+ const paginationBarRef = React.useRef<HTMLDivElement | null>(null)
1121
1298
  React.useLayoutEffect(() => {
1122
1299
  if (!isFillHeight) { setBodyMaxHeight(null); return }
1123
1300
  // **R4 真根因 fix(2026-05-09 v2 — codex Q3.6 root cause + Q3.9 reproduce verified)**:
@@ -1160,7 +1337,15 @@ function DataTableInner<TData>(
1160
1337
  ?? tableRef.current.getBoundingClientRect().height
1161
1338
  const headerEl = tableRef.current.firstElementChild as HTMLElement | null
1162
1339
  const headerH = headerEl?.getBoundingClientRect().height ?? 0
1163
- const next = Math.max(0, slotH - headerH)
1340
+ // L5 分頁 × isFillHeight(2026-07-07 C12):啟用分頁時 parent slot = composedContent
1341
+ // wrapper(含分頁列 + tight gap),body 可用高必扣分頁列 footprint,否則 body 撐滿
1342
+ // 把分頁列擠出 / 被 overflow 裁掉
1343
+ const barEl = paginationBarRef.current
1344
+ const barFootprint = barEl
1345
+ ? barEl.getBoundingClientRect().height +
1346
+ (parseFloat(getComputedStyle(barEl.parentElement as Element).rowGap) || 0)
1347
+ : 0
1348
+ const next = Math.max(0, slotH - headerH - barFootprint)
1164
1349
  // Diff guard < 4px(濾 micro-step,real resize δ 必 ≫ 4px)
1165
1350
  if (lastValue != null && Math.abs(next - lastValue) < 4) return
1166
1351
  lastValue = next
@@ -1336,19 +1521,24 @@ function DataTableInner<TData>(
1336
1521
  (experimentalActiveEditorController && isEditingThisCell)
1337
1522
  ? 'display'
1338
1523
  : (isEditingThisCell && !disabled) ? 'edit' : 'display'
1524
+ // 2026-07-05 D3 perf fix:改經 MemoCellSlot — 原 inline closure props 讓 CellWithSurface
1525
+ // memo 100% miss(詳 MemoCellSlot 檔頭註解)。
1339
1526
  content = (
1340
- <Cell
1527
+ <MemoCellSlot
1528
+ Cell={Cell}
1529
+ rowId={rowId}
1530
+ colId={colId}
1341
1531
  value={cell.getValue()}
1342
- meta={meta ?? {}}
1532
+ meta={meta ?? EMPTY_META}
1343
1533
  mode={cellMode}
1344
1534
  size={size}
1345
1535
  autoRowHeight={autoRowHeight}
1346
1536
  isEditable={editable}
1347
1537
  isDisabled={disabled}
1348
- onCommit={(next) => commitCell(rowId, colId, next)}
1349
- onCommitLive={(next) => onCellCommit?.(rowId, colId, next)}
1350
- onCancel={exitEdit}
1351
- onRequestEdit={() => !disabled && setEditingCellId(cellEditId(rowId, colId))}
1538
+ onCommitCell={commitCell}
1539
+ onCancelCell={cancelCellEdit}
1540
+ onCellCommitLive={onCellCommit}
1541
+ onEnterEdit={enterCellEdit}
1352
1542
  />
1353
1543
  )
1354
1544
  } else {
@@ -1420,7 +1610,7 @@ function DataTableInner<TData>(
1420
1610
  const rowId = cell.row.id
1421
1611
  const rowOriginal = cell.row.original
1422
1612
  const isDisabled = isRowSelectable ? !isRowSelectable(rowOriginal) : false
1423
- const ariaLabel = getRowAriaLabel?.(rowOriginal) ?? 'Select row'
1613
+ const ariaLabel = getRowAriaLabel?.(rowOriginal) ?? '選取此列'
1424
1614
  const checkboxSize = size === 'lg' ? 'lg' : 'md'
1425
1615
  // Cell 整格可點:click cell padding 也觸發 toggle/select(對齊 Linear / Apple Mail / Material DataGrid)
1426
1616
  // 內部 checkbox/radio 用 stopPropagation 避免 double-fire
@@ -1603,9 +1793,11 @@ function DataTableInner<TData>(
1603
1793
  // Phase 9 Issue 8 fix(2026-05-10 user 撞 + codex 重比稿 verdict ADOPT):
1604
1794
  // 之前 `border-r border-divider` 只 right edge → hover overlay outline:-1px 只 right
1605
1795
  // 邊壓 cell border,上左下 sub-pixel 不一致(user 抓「右 1px / 上左下 2px」bug)。
1606
- // 改 `dtCellGrid` (data-table.css:96-110)用 `box-shadow: inset` 4 邊 1px divider,
1607
- // 不佔 layout(per user verbatim「在 cell 內容起始位置不變」前提)→ 4 邊一致 grid line
1608
- // overlay outline:-1px 4 邊都剛好壓 cell border line。
1796
+ // 改 `dtCellGrid`(data-table.css「dtCellGrid v2/v3」段)box-shadow inset **只保右邊**
1797
+ // `inset -1px 0 0 var(--divider)`(bottom row border-b 接管、:last-child none 2px;
1798
+ // 2026-07-04 對齊 css 現況 — 原「4 inset」敘述已被 v2/v3 取代),不佔 layout
1799
+ // (per user verbatim「在 cell 內容起始位置不變」前提)→ 視覺 4 邊 grid line 由
1800
+ // row border + 相鄰 cell 合成 → overlay outline:-1px 壓 cell border line。
1609
1801
  // Field naked edit border 仍 own(per Field SSOT)— 編輯時 Field 自帶 border 1px,
1610
1802
  // 跟 cell 4 邊 inset divider 視覺相疊(同 pixel)= 1 line visual,不雙線。
1611
1803
  inlineEdit && 'dtCellGrid',
@@ -1817,6 +2009,23 @@ function DataTableInner<TData>(
1817
2009
  // Phase B3 IME guard(2026-05-10 per codex Q-B3):中文輸入法組字中 ignore 所有 nav keys。
1818
2010
  // 2026-05-16 Round 5 audit Dim 27 fix:`keyCode` deprecated but still in KeyboardEvent type — no cast needed。
1819
2011
  if (e.nativeEvent.isComposing || e.nativeEvent.keyCode === 229) return
2012
+ // 2026-07-05 D4 fix:spreadsheet 鍵盤入口 — selection 尚未建立時按方向鍵,seed 第一個
2013
+ // visible cell(原本唯一 set 起點是 mouse click → 鍵盤-only 使用者 Tab 進 table 後方向鍵
2014
+ // 全 no-op,整個 spreadsheet 導覽被滑鼠 gate,違反 spec「鍵盤完整可操作」驗證宣稱。
2015
+ // 對齊 Excel / Google Sheets / AG Grid「focus grid → first cell active」canonical)。
2016
+ if (spreadsheetMode && selectedCellId == null && editingCellId == null
2017
+ && ['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].includes(e.key)) {
2018
+ const allRows = table.getRowModel().rows.map((r) => r.id)
2019
+ const allCols = table.getVisibleLeafColumns().map((c) => c.id).filter((id) => id !== SELECT_COL_ID)
2020
+ if (allRows.length > 0 && allCols.length > 0) {
2021
+ e.preventDefault()
2022
+ const firstCellId = `${allRows[0]}:${allCols[0]}`
2023
+ setSelectedCellId(firstCellId)
2024
+ setRangeAnchor(firstCellId)
2025
+ setRangeFocus(null)
2026
+ }
2027
+ return
2028
+ }
1820
2029
  if (spreadsheetMode && selectedCellId != null && editingCellId == null) {
1821
2030
  const navKeys = ['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'Enter', 'F2', 'Escape']
1822
2031
  if (!navKeys.includes(e.key)) return
@@ -1882,8 +2091,11 @@ function DataTableInner<TData>(
1882
2091
  return
1883
2092
  }
1884
2093
  },
2094
+ // D3 dep-hygiene fix(2026-07-06):handler 內(portal edit Enter 分支)呼叫的是 canEditCell,
2095
+ // 非 isCellEditable — 原 deps 列 isCellEditable 是靠 canEditCell identity-stable 才無實害的
2096
+ // stale-closure 地雷;改列真實依賴 canEditCell(其 useCallback deps 已含 isCellEditable + isCellDisabled)。
1885
2097
  [enabled, mode, hasAnySelection, selectableVisibleIds, setSelection,
1886
- spreadsheetMode, selectedCellId, editingCellId, table, isCellEditable]
2098
+ spreadsheetMode, selectedCellId, editingCellId, table, canEditCell]
1887
2099
  )
1888
2100
 
1889
2101
  // ── Header cell ──
@@ -1906,7 +2118,7 @@ function DataTableInner<TData>(
1906
2118
  checked={headerCheckedState}
1907
2119
  onClick={(e) => e.stopPropagation()}
1908
2120
  onCheckedChange={() => toggleHeaderCheckbox()}
1909
- aria-label="Select all visible rows"
2121
+ aria-label="全選可見列"
1910
2122
  disabled={selectableVisibleIds.length === 0}
1911
2123
  />
1912
2124
  )}
@@ -1959,7 +2171,8 @@ function DataTableInner<TData>(
1959
2171
  className={cn(
1960
2172
  'flex items-center min-w-0 flex-1 gap-1 outline-none',
1961
2173
  canSort && 'cursor-pointer hover:text-foreground transition-colors',
1962
- canSort && 'focus-visible:ring-2 focus-visible:ring-ring rounded-sm',
2174
+ // 2026-07-04:rounded-sm → rounded-md(radius.spec.md 設計哲學(4)rounded-sm 保留未使用,4px 一律 rounded-md)
2175
+ canSort && 'focus-visible:ring-2 focus-visible:ring-ring rounded-md',
1963
2176
  )}
1964
2177
  >
1965
2178
  <TruncateCell className={cn('min-w-0', align === 'right' && 'text-right', align === 'center' && 'text-center')}>
@@ -2122,6 +2335,18 @@ function DataTableInner<TData>(
2122
2335
  // 2026-05-06 v14.4 Notion blue line drop indicator(column reorder visual canonical)
2123
2336
  // 必須宣告在 renderHeaderRow 之前(closure 引用,避 minified bundler TDZ false-positive)
2124
2337
  const [dropIndicator, setDropIndicator] = React.useState<{ id: string; side: 'before' | 'after'; type: 'row' | 'column' } | null>(null)
2338
+ // 2026-07-05 D3 perf fix:shallow-compare setter — drag 中每次 over 變更 handleDragOver 都
2339
+ // set 全新 object,即使 id/side/type 沒變也觸發整個 DataTableInner 重渲(全 visible rows +
2340
+ // cells + header)。值相同 → 回傳 prev reference 讓 React bail out(對齊檔內 invalidRef guard
2341
+ // pattern;`setDropIndicator(null)` 自帶 Object.is bail out 不需經此)。
2342
+ const setDropIndicatorIfChanged = React.useCallback(
2343
+ (next: { id: string; side: 'before' | 'after'; type: 'row' | 'column' }) => {
2344
+ setDropIndicator((prev) =>
2345
+ prev && prev.id === next.id && prev.side === next.side && prev.type === next.type ? prev : next,
2346
+ )
2347
+ },
2348
+ [],
2349
+ )
2125
2350
  // ref for stable lookup in handleDragOver(避免 closure 抓舊值)
2126
2351
  const reorderableColumnIdsRef = React.useRef<string[]>([])
2127
2352
 
@@ -2213,8 +2438,9 @@ function DataTableInner<TData>(
2213
2438
  const effectiveAutoRow = autoRowHeight || rowHasError
2214
2439
 
2215
2440
  // L4 row drag:handle absolute 貼齊 row 左 border(Jira canonical),不佔 column 空間。
2216
- // 只在 primary region(left 若有,否則 center)+ depth===0 render — RowDragHandle
2217
- // 內部再用 ctx.role === 'primary' 守門避免 mirror region 重複 render。
2441
+ // 只在 primary region + depth===0 render — primary 永遠 = center(v15.4 撤銷「left 優先」,
2442
+ // 見上方 isPrimaryRegion 註解);RowDragHandle 內部再用 ctx.role === 'primary' 守門避免
2443
+ // mirror region 重複 render。
2218
2444
  const showDragHandle = enableRowDrag && (row.depth ?? 0) === 0 && isPrimaryRegion
2219
2445
  // v15.2 SSOT 對齊 TreeView:drag 期間 suppress 全表 hover state
2220
2446
  // (user directive「drag 時 row 不應 hover / drag button 不應出現」)
@@ -2252,7 +2478,10 @@ function DataTableInner<TData>(
2252
2478
  // 找 source row(避免 multi-region 場景挑錯 region 的 row 當 ghost)
2253
2479
  data-row-drag-source={enableRowDrag && isPrimaryRegion ? 'true' : undefined}
2254
2480
  role="row"
2255
- aria-rowindex={idx + 2}
2481
+ // L5 分頁(2026-07-07 C13):rowindex 加分頁 offset — aria-rowcount 是全集,
2482
+ // 第 2 頁起 index 必須接續(否則 AT 讀到「第 129 列中的第 2 列」錯位);
2483
+ // 不可用 row.index(sort/filter 後是原始資料序,非視覺序)
2484
+ aria-rowindex={(paginationEnabled ? (currentPage - 1) * pageSizeState : 0) + idx + 2}
2256
2485
  className={cn(
2257
2486
  'group/row flex relative',
2258
2487
  // H1 fix(2026-05-10):effectiveAutoRow 覆 global autoRowHeight,per-row 若有 cell error
@@ -2370,7 +2599,8 @@ function DataTableInner<TData>(
2370
2599
  // 行為:**永遠 hug rows**,只在被約束時才 cap + body shrink + V scroll。
2371
2600
  // 簡單需求:有約束 → rows 沒超就 hug;超就 cap+scroll;RWD 同理。
2372
2601
  style={isFillHeight ? { maxHeight: height } : undefined}
2373
- role="table" aria-rowcount={rows.length + 1}
2602
+ // L5 分頁:aria-rowcount = 全集筆數非當頁(ARIA 規範;getPrePaginationRowModel = filter 後全集)
2603
+ role="table" aria-rowcount={(paginationEnabled ? table.getPrePaginationRowModel().rows.length : rows.length) + 1}
2374
2604
  // Phase 9 Issue 12 fix(2026-05-10 codex 抓):**single tabIndex prop**,合併 selection
2375
2605
  // 跟 spreadsheet 兩 path。React 在 dup props 只 keep last 是 silent regression risk。
2376
2606
  tabIndex={enabled || spreadsheetMode ? 0 : undefined}
@@ -2573,12 +2803,25 @@ function DataTableInner<TData>(
2573
2803
  // 2026-05-13:canEditCell helper(per V4 consolidation,合 editable + !disabled invariant)
2574
2804
  if (!nextRow || !canEditCell(nextMeta, nextRow.original)) continue
2575
2805
  // 找到 next editable cell → commit current + start next edit
2806
+ // 2026-07-05 D4 fix:原本只 setEditingCellId —(a)同 type 相鄰 cell React 同位
2807
+ // 重用 input DOM,blur 不 fire → onBlur commit 不觸發,剛打的 draft 整段遺失;
2808
+ // (b)editingDraft 不清 → 下一格 portal value 顯示上一格草稿(甚至把上一格 draft
2809
+ // commit 進錯的 cell)。Fix:有 draft 先 commit 當前 cell(commitCell 內已清 draft),
2810
+ // 再換格;spreadsheet selection 對齊 edit-entry canonical 清空(commitCell 的
2811
+ // selection restore 對 Tab 換格不適用 — 下一格馬上進 edit)。
2812
+ if (editingDraft !== undefined) commitCell(rowId, colId, editingDraft)
2576
2813
  setEditingCellId(cellEditId(allRows[nextRowIdx], allCols[nextColIdx]))
2814
+ setSelectedCellId(null)
2815
+ setRangeAnchor(null)
2816
+ setRangeFocus(null)
2577
2817
  return
2578
2818
  }
2579
2819
  }
2580
2820
  return (
2581
- <div onKeyDownCapture={handleEditTab} style={{ width: '100%', height: '100%' }}>
2821
+ // 2026-07-05 D4 fix:key={cellId} 強制 per-cell remount 相鄰同型欄位 Tab 過去時
2822
+ // uncontrolled Input / NumberCell localValue 原封不動(上一格的字跑到下一格)。
2823
+ // 對齊 cell-registry「mode-keyed remount canonical」(per-cell edit session 是不同 mount cycle)。
2824
+ <div key={cellId} onKeyDownCapture={handleEditTab} style={{ width: '100%', height: '100%' }}>
2582
2825
  <Cell
2583
2826
  value={value}
2584
2827
  meta={meta}
@@ -2588,7 +2831,7 @@ function DataTableInner<TData>(
2588
2831
  isEditable={cellEditable}
2589
2832
  onCommit={(next) => commitCell(rowId, colId, next)}
2590
2833
  onCommitLive={(next) => onCellCommit?.(rowId, colId, next)}
2591
- onCancel={exitEdit}
2834
+ onCancel={() => cancelCellEdit(rowId, colId)} // 2026-07-05 D4:cancel 還原 selection(Contract 11)
2592
2835
  onDraft={setEditingDraft} // Phase 7:每 keystroke 寫 draft → preserve across virtualizer unmount
2593
2836
  />
2594
2837
  </div>
@@ -2799,7 +3042,7 @@ function DataTableInner<TData>(
2799
3042
  const side: 'before' | 'after' = activeIdx < overIdx ? 'after' : 'before'
2800
3043
  // **v15.3 noop suppress**:drop position 等同原位 → 不顯 indicator(對齊 handleDragEnd noop guard)
2801
3044
  if (isReorderNoop(activeIdx, overIdx, side)) { setDropIndicator(null); return }
2802
- setDropIndicator({ id: String(over.id), side, type: 'column' })
3045
+ setDropIndicatorIfChanged({ id: String(over.id), side, type: 'column' })
2803
3046
  } else {
2804
3047
  // Row drag — 用 allRowIds 算位置(只 same-parent siblings,跨 parent collisionDetection 已過濾)
2805
3048
  const activeIdx = allRowIds.indexOf(String(active.id))
@@ -2807,9 +3050,9 @@ function DataTableInner<TData>(
2807
3050
  if (activeIdx === -1 || overIdx === -1) { setDropIndicator(null); return }
2808
3051
  const side: 'before' | 'after' = activeIdx < overIdx ? 'after' : 'before'
2809
3052
  if (isReorderNoop(activeIdx, overIdx, side)) { setDropIndicator(null); return }
2810
- setDropIndicator({ id: String(over.id), side, type: 'row' })
3053
+ setDropIndicatorIfChanged({ id: String(over.id), side, type: 'row' })
2811
3054
  }
2812
- }, [allRowIds, isReorderNoop])
3055
+ }, [allRowIds, isReorderNoop, setDropIndicatorIfChanged])
2813
3056
 
2814
3057
  const handleDragCancel = React.useCallback(() => {
2815
3058
  setActiveDragId(null)
@@ -2833,8 +3076,11 @@ function DataTableInner<TData>(
2833
3076
  const meta = table.getColumn(id)?.columnDef.meta as { locked?: boolean } | undefined
2834
3077
  return !meta?.locked
2835
3078
  })
3079
+ // D3 dep-hygiene fix(2026-07-06):補 columnVisibility — memo 讀 table.getVisibleLeafColumns()
3080
+ // (live,受 visibility 影響)原 deps 漏列,隱藏欄位後清單 stale(隱藏欄仍在 reorderable
3081
+ // 列表 → handleDragOver 的 drop side 計算錯位)。
2836
3082
  // eslint-disable-next-line react-hooks/exhaustive-deps
2837
- }, [table, columnsWithSelection, tableOptions?.state?.columnOrder])
3083
+ }, [table, columnsWithSelection, tableOptions?.state?.columnOrder, columnVisibility])
2838
3084
  // Sync ref(handleDragOver closure 抓不到最新 reorderableColumnIds)
2839
3085
  React.useEffect(() => { reorderableColumnIdsRef.current = reorderableColumnIds }, [reorderableColumnIds])
2840
3086
 
@@ -2908,6 +3154,43 @@ function DataTableInner<TData>(
2908
3154
  return sameParentCollisionDetection(args)
2909
3155
  }, [sameParentCollisionDetection])
2910
3156
 
3157
+ // ── L5 分頁列(2026-07-06,詳 data-table.spec.md「L5:分頁」段)────────────────
3158
+ // 間距 tight = layoutSpace spec 規則 3「跨範疇 functional 交互」;頁碼靠右(shadcn justify-end /
3159
+ // Ant Table bottomEnd / MUI / Carbon 4 家實證,左側空也靠右);「共 N 筆」數源 = filter 後全集
3160
+ // getPrePaginationRowModel(≠ selection all-mode 的 totalCount prop,語意不同)。
3161
+ // 2026-07-06 user 拍板:「Pagination 元件本身提供完整功能(showTotal / 每頁筆數)= SSOT,
3162
+ // Table 按一致定義套用」—— 分頁列的資訊/選單/「資訊左、操作右」layout 全部 own 在
3163
+ // <Pagination> 完整形態;DataTable 只轉發 config + own TanStack state(controlled 消費:
3164
+ // page/pageSize 狀態在本層、定義在 Pagination)。
3165
+ const totalRowCount = paginationEnabled ? table.getPrePaginationRowModel().rows.length : 0
3166
+ const paginationBar = paginationEnabled && !isEmpty ? (
3167
+ // 無條件 justify-end(2026-07-07 C16:原 paginationHasExtras 判斷式 = 平行重複 Pagination
3168
+ // 內部 hasExtras 拼寫,未來加 extra 必 drift)——完整形態 Pagination 自帶 w-full
3169
+ // justify-between,justify-end 對其為 no-op;純頁碼形態靠右 = 拍板 #6(shadcn justify-end /
3170
+ // Ant Table bottomEnd / MUI / Carbon 4 家)。ref 供 isFillHeight bodyMaxHeight 扣 bar 高(C12)。
3171
+ <div ref={paginationBarRef} className="flex justify-end">
3172
+ <Pagination
3173
+ total={totalRowCount}
3174
+ page={currentPage}
3175
+ onPageChange={setCurrentPage}
3176
+ pageSize={pageSizeState}
3177
+ onPageSizeChange={(next) => {
3178
+ setPageSizeState(next)
3179
+ paginationOpts?.onPageSizeChange?.(next)
3180
+ }}
3181
+ showTotal={paginationOpts?.showTotal}
3182
+ pageSizeOptions={paginationOpts?.pageSizeOptions}
3183
+ />
3184
+ </div>
3185
+ ) : null
3186
+ // isFillHeight 並用:外層接管高度(h-full min-h-0),tableContent 的 maxHeight cap 在內層照常生效
3187
+ const composedContent = paginationBar ? (
3188
+ <div className={cn('flex flex-col gap-[var(--layout-space-tight)]', isFillHeight && 'h-full min-h-0')}>
3189
+ {tableContent}
3190
+ {paginationBar}
3191
+ </div>
3192
+ ) : tableContent
3193
+
2911
3194
  const wrapWithDnd = (node: React.ReactNode): React.ReactNode => {
2912
3195
  if (!enableRowDrag && !enableColumnReorder) return node
2913
3196
  return (
@@ -2957,11 +3240,11 @@ function DataTableInner<TData>(
2957
3240
  value={selection.mode === 'include' ? (selection.ids[0] ?? '') : ''}
2958
3241
  onValueChange={(v) => v && setSelection({ mode: 'include', ids: [v] })}
2959
3242
  >
2960
- {wrapWithDnd(tableContent)}
3243
+ {wrapWithDnd(composedContent)}
2961
3244
  </RadioGroupPrimitive.Root>
2962
3245
  )
2963
3246
  }
2964
- return wrapWithDnd(tableContent)
3247
+ return wrapWithDnd(composedContent)
2965
3248
  }
2966
3249
 
2967
3250
  export const DataTable = React.forwardRef(DataTableInner) as <TData>(
@@ -2981,7 +3264,9 @@ export const dataTableMeta = {
2981
3264
  sizes: {
2982
3265
 
2983
3266
  },
2984
- states: ['default', 'hover', 'active', 'focus-visible', 'disabled'],
3267
+ // 'active' 移除 — 全檔無 Tailwind 按壓 utility(rg `active:` 僅命中 dnd-kit `e.active` 物件),
3268
+ // 無按壓專屬視覺態;row action / drag handle 的按壓屬內嵌 Button meta(2026-07-07 對抗稽核補修)。
3269
+ states: ['default', 'hover', 'focus-visible', 'disabled'],
2985
3270
  tokens: {
2986
3271
  bg: ['bg-muted', 'bg-neutral-hover', 'bg-surface'],
2987
3272
  fg: ['text-fg-muted', 'text-fg-secondary', 'text-foreground'],