@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
@@ -11,8 +11,8 @@
11
11
  - **加 `column.meta.includeTime: boolean` 旗標** — 對齊 Notion idiom,不另設 datetime column type
12
12
  - **加 ValueShape `datetime_single` / `datetime_range`** — `includeTime=true` 時切到這兩個 shape
13
13
  - **datetime 不另設 relative shape** — `is_relative` 維持 `date_relative`(relative 邏輯不變;12-shape 表與 code `ValueShape` union 皆無 `datetime_relative`)
14
- - **filter 比對精度**:`includeTime=true` 走 ms 精度(避開 Airtable 著名地雷:filter 忽略 time)
15
- - **不新增 `time-only` column type**(業界沒人做,罕見場景)
14
+ - **filter 比對精度**:`includeTime=true` 走 ms 精度;default day 級(`startOfDay` 截斷,含 `is` 日期比對 — 2026-07-04 Q6 實作於 `filter-tree.ts` `datePrecision`)(避開 Airtable 著名地雷:filter 忽略 time)
15
+ - **不新增 `time-only` column type**(業界沒人做,罕見場景)—— 後續 Phase C(2026-05-05)已補 `'time'` columnType 供 cell render(`<TimePicker>`);advanced filter 暫 reuse `DATE_OPS`,`time_*` ValueShape 整合留 Phase D+(見 `filter-operators.ts` registry 註解)
16
16
 
17
17
  ## 1. ValueShape canonical(12 種)
18
18
 
@@ -28,7 +28,7 @@
28
28
  | `date_relative` | `<Select>` 預設選項 | 相對日期 |
29
29
  | `datetime_single` | `<DatePicker showTime>` | 特定 datetime(`includeTime=true`) |
30
30
  | `datetime_range` | `<DatePickerRange showTime>` | datetime 介於(`includeTime=true`) |
31
- | `select_multi` | `<SelectMenu multiple>` from `column.meta.options` | 多選(`select.is` 也走這 — OR 語意) |
31
+ | `select_multi` | `<Combobox>` from `column.meta.options` | 多選(`select.is` 也走這 — OR 語意) |
32
32
  | `person_multi` | `<PeoplePicker multiple>` | 多人員(`person.is` 也走這 — OR 語意) |
33
33
  | `select_single` | `<Select>` from `column.meta.options` | (預留)罕用 |
34
34
  | `person_single` | `<PeoplePicker>` 單選 | (預留)罕用 |
@@ -133,7 +133,7 @@
133
133
  | 1-to-1 比對 | ✓ `is` / `is_not`(picker 多選 OR-語意) | ✗ 不適用(陣列比單值 ambiguous) |
134
134
  | 集合運算 | ✗ 不適用 | ✓ `has_any_of` / `has_all_of` / `has_none_of` |
135
135
 
136
- 差異**根源於 cell 資料形狀**,不是 picker 不同。Value picker 兩者都共用 `SelectMenu` DS 既有元件。
136
+ 差異**根源於 cell 資料形狀**,不是 picker 不同。Value picker 兩者都共用 `<Combobox>` DS 既有元件(code 真值 `data-table-filter-panel.tsx` select_multi case)。
137
137
 
138
138
  ### `person`(4 ops — 跟 select 同邏輯)
139
139
  | op key | 中 | en | valueShape | CU | AT | NT |
@@ -53,6 +53,11 @@ export interface FilterCondition {
53
53
  op: string
54
54
  /** 依 ValueShape 解讀的值 */
55
55
  value: unknown
56
+ /** Date 欄位比對精度(2026-07-04 Q6 拍板,兌現 spec「includeTime」雙精度契約):
57
+ * panel 建 condition 時依 column meta 寫入 — includeTime=true → 'ms',否則 'day'
58
+ * (AG Grid midnight-truncate / MUI X setHours(0,0,0,0) 共識;避 Airtable 邊界漏抓地雷)。
59
+ * 非 date 欄位 / 舊 tree 無此欄 → undefined(date ops fallback 'day' 預設)。 */
60
+ datePrecision?: 'day' | 'ms'
56
61
  }
57
62
 
58
63
  export interface FilterGroup {
@@ -105,13 +110,21 @@ function isConditionComplete(c: FilterCondition): boolean {
105
110
  return c.value !== '' && c.value !== null && c.value !== undefined
106
111
  }
107
112
 
108
- /** loose lookup — 我們不知 columnType,試所有 type */
113
+ /** loose lookup — 我們不知 columnType,試所有 type
114
+ * D3 perf(2026-07-06):module-level Map cache — OPERATOR_REGISTRY 是 module 常數,
115
+ * 同一 op key 查詢結果永不變;原每 call 線性掃全 registry,而 evaluateTree 熱路徑
116
+ * per-row 呼叫時(10k rows × N conditions)放大成數十萬次比較,cache 後 O(1)。 */
117
+ const opSpecLooseCache = new Map<string, OperatorSpec | null>()
109
118
  function getOperatorSpecLoose(op: string): OperatorSpec | null {
119
+ const cached = opSpecLooseCache.get(op)
120
+ if (cached !== undefined) return cached
121
+ let found: OperatorSpec | null = null
110
122
  for (const list of Object.values(OPERATOR_REGISTRY)) {
111
- const found = list.find((o) => o.op === op)
112
- if (found) return found
123
+ const hit = list.find((o) => o.op === op)
124
+ if (hit) { found = hit; break }
113
125
  }
114
- return null
126
+ opSpecLooseCache.set(op, found)
127
+ return found
115
128
  }
116
129
 
117
130
  // ── Deep-equal compare(refresh icon 顯示判定用) ─────────────────────────
@@ -142,6 +155,68 @@ function isConditionEqual(a: FilterCondition, b: FilterCondition): boolean {
142
155
 
143
156
  // ── Tree Evaluation(globalFilter approach)──────────────────────────────
144
157
 
158
+ // ── D3 perf(2026-07-06):per-condition compile-once cache ────────────────
159
+ // TanStack globalFilterFn 逐 row 呼叫 evaluateTree,原實作每 row 重做「跨全 rows 不變」
160
+ // 的常數工作:isConditionComplete(operator registry 掃描)、dateMs(filterValue) 重 parse、
161
+ // filterValue 重 toLowerCase、is_relative 重算時間區間 — 10k rows × N conditions 全額放大。
162
+ // Fix:以 condition 物件為 key 的 WeakMap 快取 filterValue 側預解析結果,row loop 只轉
163
+ // cellValue 側。Panel 更新走 immutable `{ ...c, ...patch }`(新物件 = 新 cache entry),
164
+ // 舊 entry 隨舊 condition 物件 GC — cache 無失效問題。
165
+ interface PreparedCondition {
166
+ /** isConditionComplete 結果(registry 掃描一次,取代原 per-row filter 掃描) */
167
+ complete: boolean
168
+ /** filterValue 側是否 Person(物件或含 Person 的陣列) */
169
+ filterIsPerson: boolean
170
+ /** person 比對 ops 的 filter 側 id array(cellValue 是 Person 時也走 person path,故照 op 準備) */
171
+ filterPersonIds: string[] | null
172
+ /** 文字 ops 用:String(filterValue).toLowerCase() */
173
+ filterTextLower: string | null
174
+ /** is / is_not 陣列(多值 OR 語意)逐元素 lowercase */
175
+ filterTextLowerArr: string[] | null
176
+ /** 單值 date ops(is_before 家族;is / is_not with datePrecision)— datePrecision 已套 */
177
+ filterDateMs: number | null
178
+ /** is_between 兩端(空端 = ±Infinity,語意同原 inline 邏輯) */
179
+ filterDateStart: number | null
180
+ filterDateEnd: number | null
181
+ /** is_relative:快取區間 + 計算當日 stamp(跨日自動重算 — 所有 relative 區間都是
182
+ * calendar day 的純函數,同日內恆定,見 localDayKey) */
183
+ relRange: [number, number] | null
184
+ relDayKey: number
185
+ }
186
+
187
+ const PERSON_CAPABLE_OPS = new Set(['is', 'is_not', 'has_any_of', 'has_all_of', 'has_none_of'])
188
+ const DATE_SINGLE_OPS = new Set(['is_before', 'is_after', 'is_on_or_before', 'is_on_or_after'])
189
+ const TEXT_OPS = new Set(['contains', 'does_not_contain', 'starts_with', 'ends_with', 'is', 'is_not'])
190
+
191
+ const preparedCache = new WeakMap<FilterCondition, PreparedCondition>()
192
+
193
+ function prepareCondition(c: FilterCondition): PreparedCondition {
194
+ const hit = preparedCache.get(c)
195
+ if (hit) return hit
196
+ const fv = c.value
197
+ const isBetweenPair = c.op === 'is_between' && Array.isArray(fv) && fv.length === 2
198
+ const prepared: PreparedCondition = {
199
+ complete: isConditionComplete(c),
200
+ filterIsPerson: isPersonObject(fv) || (Array.isArray(fv) && fv.some(isPersonObject)),
201
+ filterPersonIds: PERSON_CAPABLE_OPS.has(c.op)
202
+ ? (Array.isArray(fv) ? fv.map(personId) : [personId(fv)])
203
+ : null,
204
+ filterTextLower: TEXT_OPS.has(c.op) ? String(fv).toLowerCase() : null,
205
+ filterTextLowerArr:
206
+ (c.op === 'is' || c.op === 'is_not') && Array.isArray(fv) ? fv.map((v) => String(v).toLowerCase()) : null,
207
+ filterDateMs:
208
+ DATE_SINGLE_OPS.has(c.op) || ((c.op === 'is' || c.op === 'is_not') && c.datePrecision !== undefined)
209
+ ? toDateMs(fv, c.datePrecision)
210
+ : null,
211
+ filterDateStart: isBetweenPair ? (fv[0] ? toDateMs(fv[0], c.datePrecision) : -Infinity) : null,
212
+ filterDateEnd: isBetweenPair ? (fv[1] ? toDateMs(fv[1], c.datePrecision) : Infinity) : null,
213
+ relRange: null,
214
+ relDayKey: 0,
215
+ }
216
+ preparedCache.set(c, prepared)
217
+ return prepared
218
+ }
219
+
145
220
  /**
146
221
  * 整棵 FilterTree 對 row 求 boolean。
147
222
  * 配合 useReactTable `globalFilterFn`:
@@ -159,33 +234,54 @@ export function evaluateTree(tree: FilterTree, row: any): boolean {
159
234
  if (tree.children.length === 0) return true
160
235
 
161
236
  if (tree.mode === 'flat') {
162
- const completed = tree.children.filter(isConditionComplete)
163
- if (completed.length === 0) return true
164
- const results = completed.map((c) => evaluateCondition(c, row))
165
- return tree.conjunction === 'and' ? results.every(Boolean) : results.some(Boolean)
237
+ // null = 無 complete condition → pass-through(語意同原 completed.length === 0 分支)
238
+ return evaluateConditionsJoined(tree.children, tree.conjunction, row) ?? true
166
239
  }
167
240
 
168
- // nested
169
- const groupResults = tree.children.map((g) => evaluateGroup(g, row))
170
- // group 沒任何 complete condition 的視為 pass-through(true)
171
- const meaningful = groupResults.filter((_, i) => tree.children[i].children.some(isConditionComplete))
172
- if (meaningful.length === 0) return true
173
- return tree.conjunction === 'and' ? meaningful.every(Boolean) : meaningful.some(Boolean)
241
+ // nested — group 沒任何 complete condition 的視為 pass-through(不參與 conjunction),
242
+ // 語意同原 meaningful filter
243
+ let sawMeaningful = false
244
+ for (const g of tree.children) {
245
+ const r = evaluateConditionsJoined(g.children, g.conjunction, row)
246
+ if (r === null) continue
247
+ sawMeaningful = true
248
+ if (tree.conjunction === 'and') {
249
+ if (!r) return false
250
+ } else if (r) {
251
+ return true
252
+ }
253
+ }
254
+ if (!sawMeaningful) return true
255
+ return tree.conjunction === 'and'
174
256
  }
175
257
 
258
+ /** 一組 conditions 依 conjunction 求 boolean;null = 組內無 complete condition(pass-through)。
259
+ * D3 perf:prepared.complete 取代原 per-row `children.filter(isConditionComplete)`
260
+ * (registry 掃描 + filter/map 兩個中間陣列 alloc);and/or 短路 — 條件是 pure predicate
261
+ * 無副作用,求值順序 / 省略不影響結果。 */
176
262
  // any-allow: row-generic
177
- function evaluateGroup(group: FilterGroup, row: any): boolean {
178
- const completed = group.children.filter(isConditionComplete)
179
- if (completed.length === 0) return true
180
- const results = completed.map((c) => evaluateCondition(c, row))
181
- return group.conjunction === 'and' ? results.every(Boolean) : results.some(Boolean)
263
+ function evaluateConditionsJoined(children: FilterCondition[], conjunction: Conjunction, row: any): boolean | null {
264
+ let sawComplete = false
265
+ for (const c of children) {
266
+ const prepared = prepareCondition(c)
267
+ if (!prepared.complete) continue
268
+ sawComplete = true
269
+ const matched = evaluateCondition(c, prepared, row)
270
+ if (conjunction === 'and') {
271
+ if (!matched) return false
272
+ } else if (matched) {
273
+ return true
274
+ }
275
+ }
276
+ if (!sawComplete) return null
277
+ return conjunction === 'and'
182
278
  }
183
279
 
184
280
  // any-allow: row-generic
185
- function evaluateCondition(cond: FilterCondition, row: any): boolean {
281
+ function evaluateCondition(cond: FilterCondition, prepared: PreparedCondition, row: any): boolean {
186
282
  if (!cond.field || !cond.op) return true
187
283
  const cellValue = row?.[cond.field]
188
- return matchOperator(cond.op, cellValue, cond.value)
284
+ return matchOperator(cond.op, cellValue, cond.value, cond.datePrecision, prepared)
189
285
  }
190
286
 
191
287
  // PersonValue identity helper(2026-05-07):
@@ -200,8 +296,24 @@ function isPersonObject(v: unknown): v is { name: string } {
200
296
  return !!v && typeof v === 'object' && 'name' in v
201
297
  }
202
298
 
299
+ // Q6(2026-07-04):date 比對精度 helper — 'ms' 全精度;其餘(含 undefined)day 級
300
+ // (本地時區 startOfDay 截斷,對齊 AG Grid / MUI X date 欄 midnight 慣例)。
301
+ // D3 perf(2026-07-06):自 matchOperator 內 closure 提升 top-level — prepareCondition
302
+ // (filterValue 側,每 condition 一次)與 matchOperator(cellValue 側,每 row)共用同一實作。
303
+ function toDateMs(v: unknown, datePrecision?: 'day' | 'ms'): number {
304
+ const d = new Date(String(v))
305
+ if (Number.isNaN(d.getTime())) return NaN
306
+ return datePrecision === 'ms' ? d.getTime() : startOfDay(d).getTime()
307
+ }
308
+
309
+ /** 本地日曆日 stamp(yyyymmdd)— is_relative 快取跨日失效判定(每 row 1 個 Date alloc,
310
+ * 取代原 relativeKeyToRange 每 row 4-10 個 Date alloc + 區間重算) */
311
+ function localDayKey(d: Date): number {
312
+ return d.getFullYear() * 10000 + (d.getMonth() + 1) * 100 + d.getDate()
313
+ }
314
+
203
315
  // code-quality-allow: long-function — 13-operator switch dispatch table,table-driven 重構會把 op-specific guards 拆出反增 indirection
204
- function matchOperator(op: string, cellValue: unknown, filterValue: unknown): boolean {
316
+ function matchOperator(op: string, cellValue: unknown, filterValue: unknown, datePrecision?: 'day' | 'ms', prepared?: PreparedCondition): boolean {
205
317
  // 不需 value 的 op
206
318
  switch (op) {
207
319
  case 'is_set': return cellValue !== null && cellValue !== undefined && cellValue !== ''
@@ -216,11 +328,14 @@ function matchOperator(op: string, cellValue: unknown, filterValue: unknown): bo
216
328
 
217
329
  // Person-aware specialization(person_single / person_multi)— 物件透過 name 比對。
218
330
  // 偵測:cellValue 或 filterValue 任一是 Person object(或包 Person 的陣列)→ 走 personId path。
331
+ // D3 perf:filterValue 側偵測 + id array 消費 prepared 快取(cellValue 側必然 per-row)。
219
332
  const cellIsPerson = isPersonObject(cellValue) || (Array.isArray(cellValue) && cellValue.some(isPersonObject))
220
- const filterIsPerson = isPersonObject(filterValue) || (Array.isArray(filterValue) && filterValue.some(isPersonObject))
333
+ const filterIsPerson = prepared
334
+ ? prepared.filterIsPerson
335
+ : isPersonObject(filterValue) || (Array.isArray(filterValue) && filterValue.some(isPersonObject))
221
336
  if (cellIsPerson || filterIsPerson) {
222
337
  const cellIds = Array.isArray(cellValue) ? cellValue.map(personId) : [personId(cellValue)]
223
- const filterIds = Array.isArray(filterValue) ? filterValue.map(personId) : [personId(filterValue)]
338
+ const filterIds = prepared?.filterPersonIds ?? (Array.isArray(filterValue) ? filterValue.map(personId) : [personId(filterValue)])
224
339
  switch (op) {
225
340
  case 'is': return cellIds.length === 1 && filterIds.length >= 1 && filterIds.includes(cellIds[0])
226
341
  case 'is_not': return !(cellIds.length === 1 && filterIds.length >= 1 && filterIds.includes(cellIds[0]))
@@ -231,9 +346,12 @@ function matchOperator(op: string, cellValue: unknown, filterValue: unknown): bo
231
346
  }
232
347
  }
233
348
 
349
+ // (date 精度 helper 已提升為 top-level `toDateMs`;文字 ops 的 filterValue lowercase 消費
350
+ // prepared 快取,prepared 缺席 = deprecated dataTableFilterMatch 路徑 inline fallback — D3 perf)
351
+
234
352
  switch (op) {
235
- case 'contains': return String(cellValue ?? '').toLowerCase().includes(String(filterValue).toLowerCase())
236
- case 'does_not_contain': return !String(cellValue ?? '').toLowerCase().includes(String(filterValue).toLowerCase())
353
+ case 'contains': return String(cellValue ?? '').toLowerCase().includes(prepared?.filterTextLower ?? String(filterValue).toLowerCase())
354
+ case 'does_not_contain': return !String(cellValue ?? '').toLowerCase().includes(prepared?.filterTextLower ?? String(filterValue).toLowerCase())
237
355
  // 2026-05-12 Round 6 fix(user 抓 Roadmap 進階篩選「全部」結果空)— impl-vs-spec drift。
238
356
  // Per `filter-operators.spec.md` L116「is 直接接受多值,不另設 is_any_of」+ L103-116
239
357
  // select(is/is_not + select_multi ValueShape OR 語意)+ L31-32(select.is / person.is 都走
@@ -241,15 +359,33 @@ function matchOperator(op: string, cellValue: unknown, filterValue: unknown): bo
241
359
  // array(全選 options)→ String(array)= "v1,v2" → 永遠 != single cellValue → 結果空。
242
360
  // Fix:加 array handling,走 OR 語意:any → match;is_not → every-not-match。
243
361
  case 'is':
244
- if (Array.isArray(filterValue))
245
- return filterValue.some((v) => String(cellValue ?? '').toLowerCase() === String(v).toLowerCase())
246
- return String(cellValue ?? '').toLowerCase() === String(filterValue).toLowerCase()
362
+ // Q6:date 欄位(datePrecision 有值)走日期比對非字串 — 「2026-06-01」與「2026-06-01 14:30」
363
+ // day 精度下相等(原字串小寫相等連同日不同格式都漏)
364
+ if (datePrecision) {
365
+ const a = toDateMs(cellValue, datePrecision)
366
+ const b = prepared?.filterDateMs ?? toDateMs(filterValue, datePrecision)
367
+ return !Number.isNaN(a) && a === b
368
+ }
369
+ if (Array.isArray(filterValue)) {
370
+ const cellLower = String(cellValue ?? '').toLowerCase()
371
+ const filterLowers = prepared?.filterTextLowerArr ?? filterValue.map((v) => String(v).toLowerCase())
372
+ return filterLowers.some((v) => cellLower === v)
373
+ }
374
+ return String(cellValue ?? '').toLowerCase() === (prepared?.filterTextLower ?? String(filterValue).toLowerCase())
247
375
  case 'is_not':
248
- if (Array.isArray(filterValue))
249
- return filterValue.every((v) => String(cellValue ?? '').toLowerCase() !== String(v).toLowerCase())
250
- return String(cellValue ?? '').toLowerCase() !== String(filterValue).toLowerCase()
251
- case 'starts_with': return String(cellValue ?? '').toLowerCase().startsWith(String(filterValue).toLowerCase())
252
- case 'ends_with': return String(cellValue ?? '').toLowerCase().endsWith(String(filterValue).toLowerCase())
376
+ if (datePrecision) {
377
+ const a = toDateMs(cellValue, datePrecision)
378
+ const b = prepared?.filterDateMs ?? toDateMs(filterValue, datePrecision)
379
+ return Number.isNaN(a) || a !== b
380
+ }
381
+ if (Array.isArray(filterValue)) {
382
+ const cellLower = String(cellValue ?? '').toLowerCase()
383
+ const filterLowers = prepared?.filterTextLowerArr ?? filterValue.map((v) => String(v).toLowerCase())
384
+ return filterLowers.every((v) => cellLower !== v)
385
+ }
386
+ return String(cellValue ?? '').toLowerCase() !== (prepared?.filterTextLower ?? String(filterValue).toLowerCase())
387
+ case 'starts_with': return String(cellValue ?? '').toLowerCase().startsWith(prepared?.filterTextLower ?? String(filterValue).toLowerCase())
388
+ case 'ends_with': return String(cellValue ?? '').toLowerCase().endsWith(prepared?.filterTextLower ?? String(filterValue).toLowerCase())
253
389
 
254
390
  case 'equals': return Number(cellValue) === Number(filterValue)
255
391
  case 'not_equals': return Number(cellValue) !== Number(filterValue)
@@ -258,20 +394,34 @@ function matchOperator(op: string, cellValue: unknown, filterValue: unknown): bo
258
394
  case 'lt': return Number(cellValue) < Number(filterValue)
259
395
  case 'lte': return Number(cellValue) <= Number(filterValue)
260
396
 
261
- case 'is_before': return new Date(String(cellValue)).getTime() < new Date(String(filterValue)).getTime()
262
- case 'is_after': return new Date(String(cellValue)).getTime() > new Date(String(filterValue)).getTime()
263
- case 'is_on_or_before': return new Date(String(cellValue)).getTime() <= new Date(String(filterValue)).getTime()
264
- case 'is_on_or_after': return new Date(String(cellValue)).getTime() >= new Date(String(filterValue)).getTime()
397
+ case 'is_before': return toDateMs(cellValue, datePrecision) < (prepared?.filterDateMs ?? toDateMs(filterValue, datePrecision))
398
+ case 'is_after': return toDateMs(cellValue, datePrecision) > (prepared?.filterDateMs ?? toDateMs(filterValue, datePrecision))
399
+ case 'is_on_or_before': return toDateMs(cellValue, datePrecision) <= (prepared?.filterDateMs ?? toDateMs(filterValue, datePrecision))
400
+ case 'is_on_or_after': return toDateMs(cellValue, datePrecision) >= (prepared?.filterDateMs ?? toDateMs(filterValue, datePrecision))
265
401
  case 'is_between': {
266
402
  if (!Array.isArray(filterValue) || filterValue.length !== 2) return true
267
- const cv = new Date(String(cellValue)).getTime()
268
- const start = filterValue[0] ? new Date(String(filterValue[0])).getTime() : -Infinity
269
- const end = filterValue[1] ? new Date(String(filterValue[1])).getTime() : Infinity
403
+ const cv = toDateMs(cellValue, datePrecision)
404
+ const start = prepared?.filterDateStart ?? (filterValue[0] ? toDateMs(filterValue[0], datePrecision) : -Infinity)
405
+ const end = prepared?.filterDateEnd ?? (filterValue[1] ? toDateMs(filterValue[1], datePrecision) : Infinity)
270
406
  return cv >= start && cv <= end
271
407
  }
272
408
  case 'is_relative': {
273
409
  // Phase D 完整實作 — relative key → time range → in-range test
274
- const range = relativeKeyToRange(String(filterValue))
410
+ // D3 perf:relativeKeyToRange 所有輸出都是 calendar day 的純函數(today / this_week /
411
+ // past_7_days… 全是日界),同日內恆定 → 以 localDayKey 判跨日,同日重用快取區間
412
+ // (原每 row 重算 4-10 個 Date alloc)。
413
+ let range: [number, number] | null
414
+ if (prepared) {
415
+ const now = new Date()
416
+ const dayKey = localDayKey(now)
417
+ if (prepared.relDayKey !== dayKey) {
418
+ prepared.relRange = relativeKeyToRange(String(filterValue), now)
419
+ prepared.relDayKey = dayKey
420
+ }
421
+ range = prepared.relRange
422
+ } else {
423
+ range = relativeKeyToRange(String(filterValue))
424
+ }
275
425
  if (!range) return true // unknown key,pass-through
276
426
  const cv = new Date(String(cellValue)).getTime()
277
427
  if (Number.isNaN(cv)) return false // invalid cellValue → 不命中
@@ -133,7 +133,7 @@ export const Overview = {
133
133
  </thead>
134
134
  <tbody>
135
135
  {[
136
- ['mode', "'single' | 'multiple' | 'range'", "'single'", '選擇模式(react-day-picker v9)'],
136
+ ['mode', "'single' | 'multiple' | 'range'", '', '選擇模式(react-day-picker v9;無預設,未傳時 grid 為非互動純展示)'],
137
137
  ['selected', 'Date | Date[] | { from; to? }', '—', '受控值,型別依 mode 決定'],
138
138
  ['onSelect', '(value) => void', '—', '值變動回呼,參數型別依 mode'],
139
139
  ['defaultMonth', 'Date', 'new Date()', '初始可見月份'],
@@ -68,17 +68,19 @@ DateGrid 是 internal primitive(見「定位」),一般 consumer 經 `DatePicker
68
68
 
69
69
  | mode | 選擇行為 | 典型場景 |
70
70
  |------|---------|---------|
71
- | `single`(預設) | 單日選取,點新日取代舊選 | DatePicker / 生日 / 到期日 |
71
+ | `single` | 單日選取,點新日取代舊選 | DatePicker / 生日 / 到期日 |
72
72
  | `multiple` | 可勾選多個不連續日期 | event 報名多日 |
73
73
  | `range` | from → to 連續範圍 | 訂單日期範圍 / 查詢時段 |
74
74
 
75
+ **mode 無預設值**(react-day-picker v9 `mode?: Mode | undefined`,本元件薄包裝不補 default):未傳 `mode` 時 grid 為**非互動純展示**(day 渲染純文字非 button,不可選);DS 內所有消費者(DatePicker / stories)皆顯式傳 `mode`。
76
+
75
77
  **range vs multiple 分界**:選的是「連續區間」(只在乎起迄兩端)→ `range`;選的是「各自獨立的日子」(可不連續、逐日增減)→ `multiple`。起迄日場景(訂單 / 查詢時段)固定走 `range`;簽核 / 報名這類逐日勾選走 `multiple`。
76
78
 
77
79
  ---
78
80
 
79
81
  ## 實作機制:classNames 鏡射 modifier keys(2026-04-21 AR43 修正)
80
82
 
81
- **重要**:react-day-picker v9 的 cell 只有 `data-selected / data-disabled / data-today / data-outside / data-focused` 這幾個 DOM attribute;**`data-range-start / data-range-middle / data-range-end` 不存在**。
83
+ **重要**:react-day-picker v9 的 cell state DOM attribute 只有 `data-selected / data-disabled / data-today / data-outside / data-focused`(另有非 state `data-day / data-month / data-hidden`);**`data-range-start / data-range-middle / data-range-end` 不存在**。
82
84
 
83
85
  因此用 `[&[data-range-middle=true]]:xxx` 這種 attribute selector **根本不會生效**(舊版做法錯誤)。
84
86
 
@@ -106,15 +108,16 @@ DateGrid 是 internal primitive(見「定位」),一般 consumer 經 `DatePicker
106
108
  | **selected / range 端點** | 藍底白字圓 | button `primary` 底 + `on-emphasis` 字 | range_start / range_end 共用此視覺 |
107
109
  | **range 端點 cell bg** | 灰底半圓 track,**高度 = button**,向 middle 外擴 2px bridge gap | `neutral-selected`;class 細節見「Range track canonical」+ tsx | 圓弧半徑 = button 半徑無錯位;舊版 cell-level bg 圓弧半徑 16px 比 button 14px 大 = 視覺 misalign |
108
110
  | **range track(中間)** | 灰底矩形,**高度 = button**(28×28 @ md),左右各外擴 2px 接合相鄰 cell | `neutral-selected`;button 透明顯露 track(class 細節見 tsx)| track 高度跟 selected 圓一致,不留 2px「fat」邊;相鄰 pseudo 接合連貫橫向 track |
109
- | **hover(未選中)** | 藍圈 outline(無 fill) | button hover ring 色 `primary`,無 bg(ring 寬度等 class 細節見 tsx)| 細 ring 對齊 Apple HIG `@benchmark-unverified` / Ant `@benchmark-unverified`(visual,non-source-citable) |
111
+ | **hover(未選中)** | 藍圈 outline(無 fill) | button hover ring 色 `primary-hover`(2026-07-07 user 拍板統一:瞬時 hover 進 primary 家族 = hover 階,FileUpload / Slider thumb hover 同族;base 專屬持續選中與 focus),無 bg(ring 寬度等 class 細節見 tsx)| 細 ring 對齊 Apple HIG `@benchmark-unverified` / Ant `@benchmark-unverified`(visual,non-source-citable) |
110
112
 
111
113
  ## 組合狀態(state stacking order)
112
114
 
113
115
  - today + selected → selected 勝出的**底色**(藍底白字圓);bar 跟著切 on-emphasis(白)保持可見
114
116
  - range-start / range-end → selected 規則(cell 半圓 track + button 圓)
115
117
  - range-middle → track 規則(cell 灰底矩形 + button 透明)
116
- - today + range-middle → track(灰底)+ today bar(藍色仍可見,cell selected)
117
- - hover 在 selected / disabled 上被 ring-0 壓制(避免二次 hover 出現方框 bug);range-middle button 透明顯露 track,hover ring 仍顯示( tsx `range_middle` 註解)
118
+ - today + range-middle → track(灰底)+ today bar **維持藍色**(2026-07-07 user 拍板:切白只屬「藍底白字圓」的選中日/端點;range 中段是淺灰底,白 bar 近乎隱形 = today 標記消失。range_middle 以 `!bg-primary` 覆寫 today 的 `data-selected` 切白——RDP v9 range 中段日同樣掛 selected modifier 故會誤觸發。對照 Ant panel.ts:cell-today 指示 = colorPrimary,in-range 只換底色、無規則隱藏/改色 today 指示)
119
+ - hover 在 selected / disabled 上被 ring-0 壓制(避免二次 hover 出現方框 bug);range-middle 因同樣掛 selected modifier,button hover ring **一併被 selected 的 ring-0 壓制**( selected 一致的 hover 抑制;button 透明僅顯露 track — 2026-07-05 對照 RDP source 修正舊句「hover ring 仍顯示」)
120
+ - **選中日 hover 底色升階**:ring 壓制之外,選中日 hover 時 `bg-primary → bg-primary-hover`(2026-07-06 補明文——「選中之上 hover = 同色相升 hover 階」家族,Checkbox / Switch checked hover 同款;code 已有此行為,本句消 spec-code 落差)
118
121
 
119
122
  ## Spacing canonical(2026-05-03 v8)
120
123
 
@@ -127,13 +130,13 @@ DateGrid 是 internal primitive(見「定位」),一般 consumer 經 `DatePicker
127
130
  - **Caption row alignment canonical**:`pt-3 + h-field-xs + mb-3 = 12 + 24 + 12 = 48px`
128
131
  - DateGrid month_caption + TimePickerSidePanel header **必走同樣 48px caption row 結構**
129
132
  - title text vertical center Y 一致 = 12 + 12 = 24px(from container top)
130
- - ⚠️ 改 DateGrid `p-3` → 必同步改 `TimePickerSidePanel py-3`,否則對齊破
133
+ - ⚠️ 改 DateGrid `p-3` → 必同步改 `TimePickerSidePanel pt-3`(root 刻意 bottom = 0,見 date-picker.tsx docblock),否則對齊破
131
134
 
132
135
  **為什麼用 `h-field-sm` 而非固定 `h-9`**:picker grid 在 lg density 下也要跟 Input 系統一起放大,`h-field-sm` 在 md = 28px / lg = 32px,比 Ant/Google 固定 36px(`@benchmark-unverified` visual measurement)稍緊但對齊本 DS 的 field 家族。day cell 尺寸雖固定,但**隨 density 縮放**,視覺跟 popup 內其他欄位(Input / Button)保持比例。
133
136
 
134
137
  ## Nav button canonical(2026-05-03 v9)
135
138
 
136
- Prev/Next chevron 用 `<Button variant="text" size="xs" iconOnly>`(DS primitive 消費,不 hand-coded inline-flex)。透過 RDP v9 `components.PreviousMonthButton / NextMonthButton` override(`node_modules/react-day-picker/dist/esm/components/Nav.js` 證實支援)。
139
+ Prev/Next chevron 用 `<Button variant="text" size="xs" iconOnly>`(DS primitive 消費,不 hand-coded inline-flex)。透過 RDP v9 `components.PreviousMonthButton / NextMonthButton` override(本元件 `navLayout="around"` 下按鈕由 `DayPicker.js` 於首/末月 caption 兩側直渲;無 navLayout 時才走 `Nav.js` — 兩路徑皆消費此 override,`node_modules/react-day-picker/dist/esm/` 證實)。
137
140
 
138
141
  **Icon 顏色 = Button 預設 `text-foreground`(neutral-9 / 85% 黑)**,**對齊 DS 一致設計語言**(M23):
139
142
  - Icon-only Button 預設 = neutral-9(本 DS 既有 canonical)
@@ -176,7 +179,7 @@ react-day-picker v9 自動處理:
176
179
  - **Focus**:`autoFocus` prop 自動 focus 到選中日(或今天)
177
180
  - **Locale**:`locale` prop 控制週首日、星期標頭語言
178
181
 
179
- **DS 自訂部分**:day button 的 focus ring 由本元件 classNames 覆寫為 DS canonical(`focus-visible:ring-2 ring-ring`,非 RDP 預設樣式);nav 按鈕為 DS `<Button>` 並帶 `aria-label`(上一個月 / 下一個月)。其餘 consumer 無需額外處理,保留 react-day-picker API 即可。
182
+ **DS 自訂部分**:day button 的 focus ring 由本元件 classNames 覆寫為 DS canonical(`focus-visible:ring-2 ring-ring`,非 RDP 預設樣式);SR 文案經 RDP `labels` API 提供中文 default(nav「上一個月 / 下一個月」、day / weekday / grid 等全繁中,對齊 2026-07-04 全庫 SR label 中文 canonical),consumer 傳 `labels` 可逐鍵覆寫(i18n),不在 components override 內 hardcode `aria-label`(會蓋死覆寫通道)。其餘 consumer 無需額外處理,保留 react-day-picker API 即可。
180
183
 
181
184
  ---
182
185
 
@@ -23,7 +23,7 @@ import { Button } from '@/design-system/components/Button/button'
23
23
  * | Nav + Month caption row | h-field-xs(24px)單行,chevron(xs)分居左右 / 月份置中垂直對齊 |
24
24
  * | Nav → Weekday gap | 12px(month_caption mb-3) |
25
25
  * | Weekday | text-body(14px)text-foreground font-medium(neutral-9,同 caption 權重;撤銷 v3 fg-secondary) |
26
- * | Cell gap(水平 + 垂直)| 4px(gap-1) |
26
+ * | Cell gap(水平 + 垂直)| 4px(table-native border-separate border-spacing-1;非 gap — grid 會 break,見下方 v7 註) |
27
27
  * | Day cell size | h-field-sm w-[var(--field-height-sm)](28×28 md / 32×32 lg) |
28
28
  * | Day button | rounded-full 填滿 cell |
29
29
  *
@@ -37,7 +37,7 @@ import { Button } from '@/design-system/components/Button/button'
37
37
  * | selected / range 端點 | 藍底白字圓 | [&>button]:bg-primary [&>button]:text-on-emphasis rounded-full |
38
38
  * | range middle | 灰底矩形 track(bg-neutral-selected = neutral-2),**高度 = cell 高度**(28×28 @ md) | before pseudo: `inset-y-0 -inset-x-[2px]` |
39
39
  * | range start/end 半圓 track | 左/右半圓 + selected 圓疊在上,**圓半徑 = button 半徑** | before pseudo: `rounded-l/r-full` + start `left-0 -right-[2px]` / end `-left-[2px] right-0`(向 middle 外擴 2px bridge gap)|
40
- * | hover(未選中) | 藍圈 outline | hover:ring-[1.5px] hover:ring-primary |
40
+ * | hover(未選中) | 藍圈 outline | hover:ring-[1.5px] hover:ring-primary-hover(2026-07-07 統一:瞬時 hover = hover 階)|
41
41
  *
42
42
  * ── Range track 高度 canonical(2026-05-03 v6,M8 4 家對照)──
43
43
  * Ant Design([picker source](https://github.com/ant-design/ant-design/blob/master/components/date-picker/style/panel.ts))/ Material X DateRangePicker([mui-x source](https://github.com/mui/mui-x/tree/master/packages/x-date-pickers-pro/src/DateRangeCalendar))/ Apple Calendar `@benchmark-unverified`(closed-source)/ Google Calendar `@benchmark-unverified`(closed-source)共識:
@@ -57,11 +57,19 @@ import { Button } from '@/design-system/components/Button/button'
57
57
 
58
58
  export type DateGridProps = React.ComponentProps<typeof DayPicker>
59
59
 
60
+ // ── SR label 中文 formatter(2026-07-05)──
61
+ // 走 Intl.DateTimeFormat(對齊 TimePicker / Calendar 既有 canonical,不引第二套 date lib);
62
+ // module-level 建一次 — label fn 每個 day cell 都會呼叫,避免 per-call new formatter。
63
+ const ZH_FULL_DATE = new Intl.DateTimeFormat('zh-TW', { dateStyle: 'full' })
64
+ const ZH_MONTH_YEAR = new Intl.DateTimeFormat('zh-TW', { year: 'numeric', month: 'long' })
65
+ const ZH_WEEKDAY = new Intl.DateTimeFormat('zh-TW', { weekday: 'long' })
66
+
60
67
  // code-quality-allow: long-function — foundational composite main body — 拆 sub-fn 會複雜化 local state / ref / context binding
61
68
  const DateGrid = React.forwardRef<HTMLDivElement, DateGridProps>(function DateGrid(
62
69
  {
63
70
  className,
64
71
  classNames,
72
+ labels,
65
73
  showOutsideDays = true,
66
74
  ...props
67
75
  },
@@ -114,7 +122,9 @@ const DateGrid = React.forwardRef<HTMLDivElement, DateGridProps>(function DateGr
114
122
  'absolute inset-0 z-[1] flex items-center justify-center',
115
123
  'font-normal text-body rounded-full transition-colors',
116
124
  // Hover 藍圈 1.5px(對齊 Apple HIG / Ant `@benchmark-unverified` visual ring measurement)— ring 在 button 之上 + 透明 bg 不擋 range track
117
- 'hover:ring-[1.5px] hover:ring-primary hover:bg-transparent',
125
+ // 2026-07-07 user 拍板統一:瞬時 hoverprimary 家族 = hover 階(FileUpload dropzone /
126
+ // Slider thumb hover 同族;base 專屬持續選中與 focus)——ring-primary → ring-primary-hover
127
+ 'hover:ring-[1.5px] hover:ring-primary-hover hover:bg-transparent',
118
128
  'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',
119
129
  ),
120
130
  // today:藍色 underline bar 貼近數字
@@ -124,7 +134,8 @@ const DateGrid = React.forwardRef<HTMLDivElement, DateGridProps>(function DateGr
124
134
  '[&>button]:after:bottom-[5px] [&>button]:after:left-1/2 [&>button]:after:-translate-x-1/2',
125
135
  '[&>button]:after:w-[40%] [&>button]:after:h-[1.5px] [&>button]:after:rounded-full',
126
136
  '[&>button]:after:bg-primary',
127
- // today + selected:bar 切 on-emphasis(白)
137
+ // today + selected:bar 切 on-emphasis(白)— 只為「藍底白字圓」的選中日/端點設計;
138
+ // range 中段(淺灰底)由 range_middle 的 !bg-primary 覆寫回藍(2026-07-07 user 拍板)。
128
139
  '[&[data-selected=true]>button]:after:bg-on-emphasis',
129
140
  ),
130
141
  outside: '[&>button]:text-fg-muted',
@@ -161,12 +172,53 @@ const DateGrid = React.forwardRef<HTMLDivElement, DateGridProps>(function DateGr
161
172
  range_middle: cn(
162
173
  "before:content-[''] before:absolute before:inset-y-0 before:-inset-x-[2px]",
163
174
  'before:bg-neutral-selected before:pointer-events-none',
164
- // button 透明顯露 track,但 hover ring 仍顯示
175
+ // 2026-07-07 user 拍板:range 中段的 today bar 維持藍(切白只屬藍底選中日;白條在
176
+ // neutral-selected 淺灰底上近乎隱形 = today 標記消失)。!important 確定性壓過 today 的
177
+ // data-selected 切白規則(同權重靠 stylesheet 順序不可靠)。Ant 源碼實錘:cell-today
178
+ // 指示 = colorPrimary,in-range 只換底色、無規則隱藏/改色 today 指示(panel.ts)。
179
+ // 非 today 的中段日無 after content,本規則無作用 — 安全。
180
+ '[&>button]:after:!bg-primary',
181
+ // button 透明顯露 track。2026-07-05 對齊 RDP v9 真實行為(deep-audit A.1b):
182
+ // range 中段日同樣掛 selected modifier(useRange rangeIncludesDate 不排除中段)→
183
+ // 上方 selected 的 `[&>button]:hover:ring-0` 一併壓制 hover ring(與 selected 一致
184
+ // 的 hover 抑制),非原註解宣稱的「hover ring 仍顯示」;today bar 亦被 data-selected
185
+ // selector 切 on-emphasis(見 spec「組合狀態」段)。
165
186
  '[&>button]:!bg-transparent [&>button]:!text-foreground',
166
187
  ),
167
188
  hidden: 'invisible',
168
189
  ...classNames,
169
190
  }}
191
+ // ── SR label 中文 defaults(2026-07-05,RDP labels API 正門)──
192
+ // 對齊 MUI X localeText「預設可覆寫」idiom + 2026-07-04 全庫 SR label 中文
193
+ // canonical(commit 241676c6):DS 給中文 default,consumer 傳 labels 逐鍵覆寫(i18n)。
194
+ // 禁在 components override 內 spread 後 hardcode aria-label — 會蓋死此覆寫通道
195
+ // (2026-07-05 修正 07-04 的錯誤修法:中文要走 labels 正門,不是蓋 RDP 算好的值)。
196
+ labels={{
197
+ labelPrevious: () => '上一個月',
198
+ labelNext: () => '下一個月',
199
+ // 鏡射 RDP default 結構(labels/labelDayButton.js:today 前綴 / selected 後綴),文案中文化
200
+ labelDayButton: (date, modifiers) => {
201
+ let label = ZH_FULL_DATE.format(date)
202
+ if (modifiers.today) label = `今天,${label}`
203
+ if (modifiers.selected) label = `${label},已選取`
204
+ return label
205
+ },
206
+ // 未傳 mode 的非互動 grid(spec「mode 無預設值」段)day cell 走 labelGridcell
207
+ labelGridcell: (date, modifiers) => {
208
+ let label = ZH_FULL_DATE.format(date)
209
+ if (modifiers?.today) label = `今天,${label}`
210
+ return label
211
+ },
212
+ labelGrid: (date) => ZH_MONTH_YEAR.format(date),
213
+ labelWeekday: (date) => ZH_WEEKDAY.format(date),
214
+ // 以下非 default 渲染面(captionLayout dropdown / showWeekNumber 由 consumer 經
215
+ // {...props} 開啟)— 一併補齊,避免開啟後 SR 中英夾雜
216
+ labelMonthDropdown: () => '選擇月份',
217
+ labelYearDropdown: () => '選擇年份',
218
+ labelWeekNumber: (weekNumber) => `第 ${weekNumber} 週`,
219
+ labelWeekNumberHeader: () => '週數',
220
+ ...labels,
221
+ }}
170
222
  components={{
171
223
  // ── Prev/Next nav(canonical 2026-05-03 v9,DS 一致設計語言)──
172
224
  // User 2026-05-03 audit:「icon-only Button icon 都用 neutral-9,只有 dismiss 用 45%」
@@ -174,14 +226,15 @@ const DateGrid = React.forwardRef<HTMLDivElement, DateGridProps>(function DateGr
174
226
  // 不開新 tier 自打嘴(撤銷 v6-v8 用 fg-muted override 的 mistake)。
175
227
  // RDP v9 `PreviousMonthButton / NextMonthButton` override(node_modules/react-day-picker/dist/esm/components/Nav.js)
176
228
  // ⚠️ children: _ 必丟棄(RDP 把 <Chevron> 當 children 傳 → 跟 Button startIcon 重疊變 double chevron)
229
+ // 2026-07-05 修:此處**不** hardcode aria-label — 中文走上方 labels API 正門,
230
+ // RDP 算好 labelPrevious()/labelNext() 後經 {...props} 流入 Button;
231
+ // spread 後 hardcode(07-04 舊修法)= 蓋死 consumer labels 覆寫通道。
177
232
  PreviousMonthButton: ({ className, children: _children, ...props }) => (
178
233
  <Button variant="text" size="xs" iconOnly startIcon={ChevronLeft}
179
- aria-label="上一個月"
180
234
  className={className} {...props} />
181
235
  ),
182
236
  NextMonthButton: ({ className, children: _children, ...props }) => (
183
237
  <Button variant="text" size="xs" iconOnly startIcon={ChevronRight}
184
- aria-label="下一個月"
185
238
  className={className} {...props} />
186
239
  ),
187
240
  }}
@@ -202,7 +255,9 @@ export const dateGridMeta = {
202
255
  sizes: {
203
256
 
204
257
  },
205
- states: ['default', 'hover', 'active', 'focus-visible', 'disabled'],
258
+ // 'selected' = 選中日期 / range 端點持續選中(藍底白字圓 bg-primary + range 帶 bg-neutral-selected);
259
+ // 'active' 移除 — 全檔無 Tailwind 按壓 utility,無按壓專屬視覺態(2026-07-07 詞彙統一對抗稽核補修)。
260
+ states: ['default', 'hover', 'selected', 'focus-visible', 'disabled'],
206
261
  tokens: {
207
262
  bg: ['bg-disabled', 'bg-neutral-selected', 'bg-on-emphasis', 'bg-primary', 'bg-primary-hover', 'bg-transparent'],
208
263
  fg: ['text-fg-disabled', 'text-fg-muted', 'text-foreground', 'text-on-emphasis'],
@@ -32,7 +32,7 @@ const Label = ({ children, warn }: { children: React.ReactNode; warn?: boolean }
32
32
 
33
33
  const Section = ({ title, children }: { title: string; children: React.ReactNode }) => (
34
34
  <section className="mb-12">
35
- <h2 className="text-heading-3 font-bold text-foreground mb-4 pb-2 border-b border-border">{title}</h2>
35
+ <h2 className="text-h3 font-bold text-foreground mb-4 pb-2 border-b border-border">{title}</h2>
36
36
  {children}
37
37
  </section>
38
38
  )
@@ -229,6 +229,7 @@ DateGrid cell 有 5 種語意視覺,每種用不同形狀/色彩語言避免混
229
229
  - Range showTime footer **無「此刻」**(對齊 Ant `showNow={multiple ? false : showNow}`)— 只「確定」走 SurfaceFooter justify-end
230
230
  - value 格式:`'YYYY-MM-DDTHH:MM:SS'`(local-time 語意,不帶 timezone)
231
231
  - needConfirm=true 時 user 編輯先暫存 draft,trigger text 即時讀 draft(canonical 2026-05-03 v8 修);按確定才 onChange;false 時邊編邊 commit
232
+ - **needConfirm 獨立於 showTime**(2026-07-05 D4 codify):date-only `<DatePicker needConfirm>` 亦成立,footer 渲染 gate = `showTime || needConfirm`(single 與 Range 一致)——needConfirm 時「確定」是唯一 commit 路徑,漏渲 footer = draft 永遠無法 commit 的死路(2026-07-05 修 single 漏 `|| needConfirm`)
232
233
  - showSeconds=false(default)→ TimeColumns 只顯示 H/M(對齊 Ant 預設)
233
234
  - Range cell disable(對齊 Ant `useRangeDisabledDate`):activeEnd='start' + end 已選 → date > end disabled;activeEnd='end' + start 已選 → date < start disabled
234
235
  - Range stadium pattern(canonical 2026-05-03 v8):rangeStart/End cell pseudo `before:rounded-l-full / rounded-r-full` 跟 button 圓的左/右半弧 EXACTLY OVERLAY → 無「凸出」(對齊 Ant `cell-range-start::before { border-radius: 9999px 0 0 9999px }`)
@@ -223,6 +223,25 @@ export const ShowTimePopoverOpen: Story = {
223
223
  play: openPopoverPlay('[role="combobox"]'),
224
224
  }
225
225
 
226
+ /* ── OpenSnapshot:needConfirm date-only(2026-07-05 D4 修 footer gate 死路)── */
227
+ export const NeedConfirmDateOnlyOpen: Story = {
228
+ name: 'needConfirm:合約生效日確認',
229
+ parameters: { docs: { description: { story: 'date-only + needConfirm:點日期只暫存 draft(trigger 即時顯示),footer「確定」才 commit onChange——footer 渲染 gate = showTime || needConfirm(2026-07-05 D4 修 single 漏 || needConfirm 時「點了日期什麼都不會發生」的死路)。適用選錯成本高的日期(合約生效日 / 帳單結算起算日)。Visual-audit OpenSnapshot verify date-only popover 有渲 footer(此刻 / 確定)。' } } },
230
+ render: () => {
231
+ const [effectiveDate, setEffectiveDate] = React.useState('2026-08-01')
232
+ return (
233
+ <div className="flex flex-col gap-4 max-w-xs" style={{ paddingBottom: 420 }}>
234
+ <p className="text-caption text-fg-muted">
235
+ 合約生效日選錯成本高:點日期先暫存,按「確定」才真正寫入。
236
+ </p>
237
+ <DatePicker needConfirm value={effectiveDate} onChange={setEffectiveDate} />
238
+ <p className="text-caption text-fg-muted">已提交值:{effectiveDate || '(空)'}</p>
239
+ </div>
240
+ )
241
+ },
242
+ play: openPopoverPlay('[role="combobox"]'),
243
+ }
244
+
226
245
  /* ── OpenSnapshot:showTime Range popover(activeEnd='start')── */
227
246
  export const ShowTimeRangePopoverOpen: Story = {
228
247
  name: 'showTime 範圍:浮層展開狀態',