@chenzy-design/svelte 0.1.0 → 0.2.0

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 (476) hide show
  1. package/dist/components.json +1 -1
  2. package/package.json +7 -8
  3. package/src/_floating/use-floating.ts +0 -208
  4. package/src/anchor/Anchor.a11y.test.ts +0 -49
  5. package/src/anchor/Anchor.kbd.test.ts +0 -70
  6. package/src/anchor/Anchor.svelte +0 -359
  7. package/src/anchor/AnchorKbdFixture.svelte +0 -24
  8. package/src/anchor/index.ts +0 -3
  9. package/src/anchor/meta.ts +0 -70
  10. package/src/anchor/types.ts +0 -8
  11. package/src/autocomplete/AutoComplete.a11y.test.ts +0 -49
  12. package/src/autocomplete/AutoComplete.kbd.test.ts +0 -69
  13. package/src/autocomplete/AutoComplete.svelte +0 -706
  14. package/src/autocomplete/AutoCompleteKbdFixture.svelte +0 -24
  15. package/src/autocomplete/index.ts +0 -2
  16. package/src/autocomplete/meta.ts +0 -41
  17. package/src/avatar/Avatar.a11y.test.ts +0 -37
  18. package/src/avatar/Avatar.svelte +0 -315
  19. package/src/avatar/AvatarGroup.svelte +0 -142
  20. package/src/avatar/context.ts +0 -21
  21. package/src/avatar/index.ts +0 -10
  22. package/src/avatar/meta.ts +0 -73
  23. package/src/back-top/BackTop.a11y.test.ts +0 -40
  24. package/src/back-top/BackTop.svelte +0 -314
  25. package/src/back-top/index.ts +0 -2
  26. package/src/back-top/meta.ts +0 -108
  27. package/src/badge/Badge.a11y.test.ts +0 -35
  28. package/src/badge/Badge.svelte +0 -328
  29. package/src/badge/BadgeA11yFixture.svelte +0 -18
  30. package/src/badge/index.ts +0 -2
  31. package/src/badge/meta.ts +0 -62
  32. package/src/banner/Banner.a11y.test.ts +0 -48
  33. package/src/banner/Banner.svelte +0 -368
  34. package/src/banner/index.ts +0 -3
  35. package/src/banner/meta.ts +0 -121
  36. package/src/breadcrumb/Breadcrumb.a11y.test.ts +0 -35
  37. package/src/breadcrumb/Breadcrumb.kbd.test.ts +0 -45
  38. package/src/breadcrumb/Breadcrumb.svelte +0 -414
  39. package/src/breadcrumb/BreadcrumbKbdFixture.svelte +0 -23
  40. package/src/breadcrumb/Item.svelte +0 -77
  41. package/src/breadcrumb/context.ts +0 -27
  42. package/src/breadcrumb/index.ts +0 -21
  43. package/src/breadcrumb/meta.ts +0 -128
  44. package/src/breadcrumb/types.ts +0 -4
  45. package/src/button/Button.a11y.test.ts +0 -54
  46. package/src/button/Button.svelte +0 -176
  47. package/src/button/index.ts +0 -2
  48. package/src/button/meta.ts +0 -28
  49. package/src/calendar/Calendar.a11y.test.ts +0 -51
  50. package/src/calendar/Calendar.kbd.test.ts +0 -59
  51. package/src/calendar/Calendar.svelte +0 -1344
  52. package/src/calendar/CalendarKbdFixture.svelte +0 -23
  53. package/src/calendar/index.ts +0 -3
  54. package/src/calendar/meta.ts +0 -117
  55. package/src/card/Card.a11y.test.ts +0 -38
  56. package/src/card/Card.svelte +0 -341
  57. package/src/card/CardMeta.svelte +0 -60
  58. package/src/card/index.ts +0 -8
  59. package/src/card/meta.ts +0 -83
  60. package/src/carousel/Carousel.a11y.test.ts +0 -53
  61. package/src/carousel/Carousel.kbd.test.ts +0 -67
  62. package/src/carousel/Carousel.svelte +0 -806
  63. package/src/carousel/CarouselA11yFixture.svelte +0 -25
  64. package/src/carousel/CarouselKbdFixture.svelte +0 -24
  65. package/src/carousel/index.ts +0 -2
  66. package/src/carousel/meta.ts +0 -74
  67. package/src/cascader/Cascader.a11y.test.ts +0 -59
  68. package/src/cascader/Cascader.kbd.test.ts +0 -56
  69. package/src/cascader/Cascader.svelte +0 -1570
  70. package/src/cascader/CascaderKbdFixture.svelte +0 -31
  71. package/src/cascader/index.ts +0 -3
  72. package/src/cascader/meta.ts +0 -57
  73. package/src/cascader/types.ts +0 -9
  74. package/src/checkbox/Checkbox.svelte +0 -281
  75. package/src/checkbox/CheckboxGroup.a11y.test.ts +0 -43
  76. package/src/checkbox/CheckboxGroup.kbd.test.ts +0 -40
  77. package/src/checkbox/CheckboxGroup.svelte +0 -122
  78. package/src/checkbox/CheckboxGroupKbdFixture.svelte +0 -20
  79. package/src/checkbox/context.ts +0 -31
  80. package/src/checkbox/index.ts +0 -4
  81. package/src/checkbox/meta.ts +0 -67
  82. package/src/collapse/Collapse.a11y.test.ts +0 -53
  83. package/src/collapse/Collapse.kbd.test.ts +0 -89
  84. package/src/collapse/Collapse.svelte +0 -406
  85. package/src/collapse/CollapseA11yFixture.svelte +0 -27
  86. package/src/collapse/CollapseKbdFixture.svelte +0 -25
  87. package/src/collapse/CollapsePanel.svelte +0 -102
  88. package/src/collapse/context.ts +0 -54
  89. package/src/collapse/index.ts +0 -17
  90. package/src/collapse/meta.ts +0 -79
  91. package/src/collapse/types.ts +0 -5
  92. package/src/color-picker/ColorPicker.a11y.test.ts +0 -60
  93. package/src/color-picker/ColorPicker.kbd.test.ts +0 -73
  94. package/src/color-picker/ColorPicker.svelte +0 -985
  95. package/src/color-picker/ColorPickerKbdFixture.svelte +0 -25
  96. package/src/color-picker/index.ts +0 -2
  97. package/src/color-picker/meta.ts +0 -47
  98. package/src/config-provider/ConfigProvider.svelte +0 -227
  99. package/src/config-provider/context.ts +0 -29
  100. package/src/config-provider/index.ts +0 -48
  101. package/src/config-provider/meta.ts +0 -128
  102. package/src/date-picker/DatePicker.a11y.test.ts +0 -45
  103. package/src/date-picker/DatePicker.kbd.test.ts +0 -71
  104. package/src/date-picker/DatePicker.svelte +0 -1798
  105. package/src/date-picker/DatePickerKbdFixture.svelte +0 -26
  106. package/src/date-picker/RangePicker.kbd.test.ts +0 -69
  107. package/src/date-picker/RangePicker.svelte +0 -775
  108. package/src/date-picker/RangePickerKbdFixture.svelte +0 -30
  109. package/src/date-picker/index.ts +0 -4
  110. package/src/date-picker/meta.ts +0 -67
  111. package/src/date-picker/range-meta.ts +0 -44
  112. package/src/descriptions/Descriptions.a11y.test.ts +0 -33
  113. package/src/descriptions/Descriptions.svelte +0 -287
  114. package/src/descriptions/DescriptionsItem.svelte +0 -43
  115. package/src/descriptions/context.ts +0 -21
  116. package/src/descriptions/index.ts +0 -16
  117. package/src/descriptions/meta.ts +0 -45
  118. package/src/descriptions/types.ts +0 -6
  119. package/src/divider/Divider.a11y.test.ts +0 -23
  120. package/src/divider/Divider.svelte +0 -154
  121. package/src/divider/index.ts +0 -2
  122. package/src/divider/meta.ts +0 -23
  123. package/src/drawer/Drawer.a11y.test.ts +0 -41
  124. package/src/drawer/Drawer.kbd.test.ts +0 -67
  125. package/src/drawer/Drawer.svelte +0 -554
  126. package/src/drawer/DrawerKbdFixture.svelte +0 -28
  127. package/src/drawer/index.ts +0 -2
  128. package/src/drawer/meta.ts +0 -127
  129. package/src/dropdown/Dropdown.a11y.test.ts +0 -46
  130. package/src/dropdown/Dropdown.kbd.test.ts +0 -56
  131. package/src/dropdown/Dropdown.svelte +0 -573
  132. package/src/dropdown/DropdownA11yFixture.svelte +0 -24
  133. package/src/dropdown/DropdownDivider.svelte +0 -17
  134. package/src/dropdown/DropdownItem.svelte +0 -127
  135. package/src/dropdown/DropdownItemNode.svelte +0 -355
  136. package/src/dropdown/DropdownKbdFixture.svelte +0 -26
  137. package/src/dropdown/DropdownMenu.svelte +0 -24
  138. package/src/dropdown/DropdownSubMenu.svelte +0 -212
  139. package/src/dropdown/DropdownTitle.svelte +0 -34
  140. package/src/dropdown/context.ts +0 -13
  141. package/src/dropdown/index.ts +0 -36
  142. package/src/dropdown/meta.ts +0 -97
  143. package/src/dropdown/types.test.ts +0 -50
  144. package/src/dropdown/types.ts +0 -67
  145. package/src/empty/Empty.a11y.test.ts +0 -24
  146. package/src/empty/Empty.svelte +0 -304
  147. package/src/empty/index.ts +0 -2
  148. package/src/empty/meta.ts +0 -41
  149. package/src/form/Field.svelte +0 -492
  150. package/src/form/FieldArray.svelte +0 -75
  151. package/src/form/Form.a11y.test.ts +0 -39
  152. package/src/form/Form.svelte +0 -243
  153. package/src/form/FormA11yFixture.svelte +0 -31
  154. package/src/form/FormCascader.svelte +0 -82
  155. package/src/form/FormCheckbox.svelte +0 -83
  156. package/src/form/FormDatePicker.svelte +0 -80
  157. package/src/form/FormErrorMessage.svelte +0 -44
  158. package/src/form/FormInput.svelte +0 -95
  159. package/src/form/FormLabel.svelte +0 -46
  160. package/src/form/FormRadio.svelte +0 -83
  161. package/src/form/FormRating.svelte +0 -85
  162. package/src/form/FormSection.svelte +0 -45
  163. package/src/form/FormSelect.svelte +0 -96
  164. package/src/form/FormSlider.svelte +0 -95
  165. package/src/form/FormSlot.svelte +0 -51
  166. package/src/form/FormSwitch.svelte +0 -82
  167. package/src/form/FormTagInput.svelte +0 -79
  168. package/src/form/FormTreeSelect.svelte +0 -85
  169. package/src/form/FormUpload.svelte +0 -92
  170. package/src/form/context.ts +0 -47
  171. package/src/form/index.ts +0 -22
  172. package/src/form/meta.ts +0 -85
  173. package/src/grid/Col.svelte +0 -142
  174. package/src/grid/Grid.a11y.test.ts +0 -14
  175. package/src/grid/GridA11yFixture.svelte +0 -12
  176. package/src/grid/Row.svelte +0 -116
  177. package/src/grid/index.ts +0 -3
  178. package/src/grid/meta.ts +0 -72
  179. package/src/grid/use-breakpoint.svelte.ts +0 -48
  180. package/src/highlight/Highlight.a11y.test.ts +0 -25
  181. package/src/highlight/Highlight.svelte +0 -99
  182. package/src/highlight/index.ts +0 -2
  183. package/src/highlight/meta.ts +0 -80
  184. package/src/icon/Icon.svelte +0 -156
  185. package/src/icon/index.ts +0 -2
  186. package/src/icon/meta.ts +0 -41
  187. package/src/image/Image.a11y.test.ts +0 -30
  188. package/src/image/Image.svelte +0 -387
  189. package/src/image/ImagePreview.kbd.test.ts +0 -52
  190. package/src/image/ImagePreview.svelte +0 -309
  191. package/src/image/ImagePreviewGroup.svelte +0 -93
  192. package/src/image/ImagePreviewKbdFixture.svelte +0 -32
  193. package/src/image/context.ts +0 -26
  194. package/src/image/index.ts +0 -15
  195. package/src/image/meta.ts +0 -131
  196. package/src/index.ts +0 -208
  197. package/src/input/Input.a11y.test.ts +0 -58
  198. package/src/input/Input.svelte +0 -505
  199. package/src/input/index.ts +0 -2
  200. package/src/input/meta.ts +0 -40
  201. package/src/input-number/InputNumber.a11y.test.ts +0 -43
  202. package/src/input-number/InputNumber.kbd.test.ts +0 -41
  203. package/src/input-number/InputNumber.svelte +0 -674
  204. package/src/input-number/InputNumberKbdFixture.svelte +0 -15
  205. package/src/input-number/index.ts +0 -2
  206. package/src/input-number/meta.ts +0 -65
  207. package/src/layout/Content.svelte +0 -37
  208. package/src/layout/Footer.svelte +0 -49
  209. package/src/layout/Header.svelte +0 -46
  210. package/src/layout/Layout.a11y.test.ts +0 -34
  211. package/src/layout/Layout.svelte +0 -60
  212. package/src/layout/LayoutA11yFixture.svelte +0 -21
  213. package/src/layout/Sider.svelte +0 -207
  214. package/src/layout/index.ts +0 -26
  215. package/src/layout/meta.ts +0 -116
  216. package/src/list/List.a11y.test.ts +0 -69
  217. package/src/list/List.bench.ts +0 -26
  218. package/src/list/List.kbd.test.ts +0 -71
  219. package/src/list/List.svelte +0 -920
  220. package/src/list/ListA11yFixture.svelte +0 -38
  221. package/src/list/ListBenchFixture.svelte +0 -22
  222. package/src/list/ListItem.svelte +0 -175
  223. package/src/list/ListKbdFixture.svelte +0 -31
  224. package/src/list/ListMeta.svelte +0 -73
  225. package/src/list/context.ts +0 -39
  226. package/src/list/index.ts +0 -21
  227. package/src/list/meta.ts +0 -99
  228. package/src/locale-provider/LocaleProvider.svelte +0 -110
  229. package/src/locale-provider/context.ts +0 -22
  230. package/src/locale-provider/index.ts +0 -21
  231. package/src/locale-provider/meta.ts +0 -93
  232. package/src/locale-provider/use-locale.ts +0 -25
  233. package/src/lottie-icon/LottieIcon.svelte +0 -330
  234. package/src/lottie-icon/index.ts +0 -9
  235. package/src/lottie-icon/meta.ts +0 -162
  236. package/src/menu/Menu.a11y.test.ts +0 -54
  237. package/src/menu/Menu.kbd.test.ts +0 -69
  238. package/src/menu/Menu.svelte +0 -762
  239. package/src/menu/MenuKbdFixture.svelte +0 -22
  240. package/src/menu/MenuPopupNode.svelte +0 -525
  241. package/src/menu/index.ts +0 -4
  242. package/src/menu/meta.ts +0 -137
  243. package/src/menu/types.ts +0 -120
  244. package/src/meta.test.ts +0 -173
  245. package/src/modal/ConfirmModal.svelte +0 -173
  246. package/src/modal/Modal.a11y.test.ts +0 -46
  247. package/src/modal/Modal.kbd.test.ts +0 -65
  248. package/src/modal/Modal.svelte +0 -511
  249. package/src/modal/ModalKbdFixture.svelte +0 -24
  250. package/src/modal/command.svelte.ts +0 -103
  251. package/src/modal/index.ts +0 -30
  252. package/src/modal/meta.ts +0 -165
  253. package/src/modal/z-stack.ts +0 -26
  254. package/src/notification/NotificationContainer.svelte +0 -192
  255. package/src/notification/NotificationItem.a11y.test.ts +0 -73
  256. package/src/notification/NotificationItem.svelte +0 -368
  257. package/src/notification/index.ts +0 -9
  258. package/src/notification/meta.ts +0 -120
  259. package/src/notification/store.ts +0 -191
  260. package/src/overflow-list/OverflowList.a11y.test.ts +0 -46
  261. package/src/overflow-list/OverflowList.kbd.test.ts +0 -34
  262. package/src/overflow-list/OverflowList.svelte +0 -539
  263. package/src/overflow-list/OverflowListA11yFixture.svelte +0 -22
  264. package/src/overflow-list/OverflowListKbdFixture.svelte +0 -21
  265. package/src/overflow-list/index.ts +0 -2
  266. package/src/overflow-list/meta.ts +0 -138
  267. package/src/pagination/Pagination.a11y.test.ts +0 -62
  268. package/src/pagination/Pagination.kbd.test.ts +0 -66
  269. package/src/pagination/Pagination.svelte +0 -428
  270. package/src/pagination/PaginationKbdFixture.svelte +0 -15
  271. package/src/pagination/index.ts +0 -2
  272. package/src/pagination/meta.ts +0 -73
  273. package/src/popconfirm/Popconfirm.a11y.test.ts +0 -46
  274. package/src/popconfirm/Popconfirm.kbd.test.ts +0 -42
  275. package/src/popconfirm/Popconfirm.svelte +0 -724
  276. package/src/popconfirm/PopconfirmA11yFixture.svelte +0 -34
  277. package/src/popconfirm/PopconfirmKbdFixture.svelte +0 -17
  278. package/src/popconfirm/index.ts +0 -2
  279. package/src/popconfirm/meta.ts +0 -200
  280. package/src/popover/Popover.a11y.test.ts +0 -58
  281. package/src/popover/Popover.kbd.test.ts +0 -42
  282. package/src/popover/Popover.svelte +0 -643
  283. package/src/popover/PopoverA11yFixture.svelte +0 -35
  284. package/src/popover/PopoverKbdFixture.svelte +0 -18
  285. package/src/popover/index.ts +0 -2
  286. package/src/popover/meta.ts +0 -103
  287. package/src/progress/Progress.a11y.test.ts +0 -37
  288. package/src/progress/Progress.svelte +0 -423
  289. package/src/progress/index.ts +0 -3
  290. package/src/progress/meta.ts +0 -132
  291. package/src/radio/Radio.svelte +0 -330
  292. package/src/radio/RadioGroup.a11y.test.ts +0 -43
  293. package/src/radio/RadioGroup.kbd.test.ts +0 -75
  294. package/src/radio/RadioGroup.svelte +0 -199
  295. package/src/radio/RadioGroupKbdFixture.svelte +0 -21
  296. package/src/radio/context.ts +0 -42
  297. package/src/radio/index.ts +0 -4
  298. package/src/radio/meta.ts +0 -68
  299. package/src/rating/Rating.a11y.test.ts +0 -41
  300. package/src/rating/Rating.kbd.test.ts +0 -48
  301. package/src/rating/Rating.svelte +0 -407
  302. package/src/rating/RatingKbdFixture.svelte +0 -15
  303. package/src/rating/index.ts +0 -2
  304. package/src/rating/meta.ts +0 -40
  305. package/src/resize-observer/ResizeObserver.svelte +0 -169
  306. package/src/resize-observer/action.ts +0 -80
  307. package/src/resize-observer/index.ts +0 -9
  308. package/src/resize-observer/meta.ts +0 -102
  309. package/src/scroll-list/ScrollColumn.svelte +0 -592
  310. package/src/scroll-list/ScrollList.svelte +0 -257
  311. package/src/scroll-list/index.ts +0 -4
  312. package/src/scroll-list/meta.ts +0 -93
  313. package/src/scroll-list/types.ts +0 -18
  314. package/src/select/Select.a11y.test.ts +0 -59
  315. package/src/select/Select.kbd.test.ts +0 -59
  316. package/src/select/Select.svelte +0 -1155
  317. package/src/select/SelectKbdFixture.svelte +0 -29
  318. package/src/select/index.ts +0 -3
  319. package/src/select/meta.ts +0 -78
  320. package/src/select/types.ts +0 -2
  321. package/src/side-sheet/SideSheet.a11y.test.ts +0 -53
  322. package/src/side-sheet/SideSheet.kbd.test.ts +0 -63
  323. package/src/side-sheet/SideSheet.svelte +0 -659
  324. package/src/side-sheet/SideSheetKbdFixture.svelte +0 -30
  325. package/src/side-sheet/index.ts +0 -2
  326. package/src/side-sheet/meta.ts +0 -116
  327. package/src/skeleton/Skeleton.a11y.test.ts +0 -35
  328. package/src/skeleton/Skeleton.svelte +0 -131
  329. package/src/skeleton/SkeletonAvatar.svelte +0 -82
  330. package/src/skeleton/SkeletonButton.svelte +0 -90
  331. package/src/skeleton/SkeletonImage.svelte +0 -100
  332. package/src/skeleton/SkeletonParagraph.svelte +0 -103
  333. package/src/skeleton/SkeletonTitle.svelte +0 -74
  334. package/src/skeleton/context.ts +0 -11
  335. package/src/skeleton/index.ts +0 -8
  336. package/src/skeleton/meta.ts +0 -65
  337. package/src/slider/Slider.a11y.test.ts +0 -44
  338. package/src/slider/Slider.kbd.test.ts +0 -74
  339. package/src/slider/Slider.svelte +0 -716
  340. package/src/slider/SliderKbdFixture.svelte +0 -21
  341. package/src/slider/index.ts +0 -2
  342. package/src/slider/meta.ts +0 -55
  343. package/src/space/Space.a11y.test.ts +0 -22
  344. package/src/space/Space.svelte +0 -109
  345. package/src/space/SpaceA11yFixture.svelte +0 -17
  346. package/src/space/index.ts +0 -2
  347. package/src/space/meta.ts +0 -27
  348. package/src/spin/Spin.a11y.test.ts +0 -29
  349. package/src/spin/Spin.svelte +0 -293
  350. package/src/spin/SpinA11yFixture.svelte +0 -18
  351. package/src/spin/index.ts +0 -2
  352. package/src/spin/meta.ts +0 -72
  353. package/src/steps/Steps.a11y.test.ts +0 -39
  354. package/src/steps/Steps.kbd.test.ts +0 -73
  355. package/src/steps/Steps.svelte +0 -392
  356. package/src/steps/StepsKbdFixture.svelte +0 -21
  357. package/src/steps/index.ts +0 -3
  358. package/src/steps/meta.ts +0 -59
  359. package/src/steps/types.ts +0 -8
  360. package/src/switch/Switch.a11y.test.ts +0 -46
  361. package/src/switch/Switch.kbd.test.ts +0 -40
  362. package/src/switch/Switch.svelte +0 -310
  363. package/src/switch/SwitchKbdFixture.svelte +0 -22
  364. package/src/switch/index.ts +0 -2
  365. package/src/switch/meta.ts +0 -44
  366. package/src/table/Table.a11y.test.ts +0 -70
  367. package/src/table/Table.bench.ts +0 -51
  368. package/src/table/Table.kbd.test.ts +0 -94
  369. package/src/table/Table.svelte +0 -2327
  370. package/src/table/TableKbdFixture.svelte +0 -35
  371. package/src/table/grid-nav.test.ts +0 -173
  372. package/src/table/grid-nav.ts +0 -146
  373. package/src/table/index.ts +0 -13
  374. package/src/table/meta.ts +0 -121
  375. package/src/table/types.ts +0 -133
  376. package/src/tabs/TabPane.svelte +0 -92
  377. package/src/tabs/Tabs.a11y.test.ts +0 -47
  378. package/src/tabs/Tabs.kbd.test.ts +0 -74
  379. package/src/tabs/Tabs.svelte +0 -1128
  380. package/src/tabs/TabsA11yFixture.svelte +0 -20
  381. package/src/tabs/TabsKbdFixture.svelte +0 -25
  382. package/src/tabs/context.ts +0 -43
  383. package/src/tabs/index.ts +0 -14
  384. package/src/tabs/meta.ts +0 -152
  385. package/src/tabs/types.ts +0 -6
  386. package/src/tag/Tag.a11y.test.ts +0 -33
  387. package/src/tag/Tag.svelte +0 -366
  388. package/src/tag/TagA11yFixture.svelte +0 -18
  389. package/src/tag/index.ts +0 -2
  390. package/src/tag/meta.ts +0 -92
  391. package/src/tag-input/TagInput.a11y.test.ts +0 -41
  392. package/src/tag-input/TagInput.svelte +0 -544
  393. package/src/tag-input/index.ts +0 -2
  394. package/src/tag-input/meta.ts +0 -43
  395. package/src/tag-input/reorder.test.ts +0 -59
  396. package/src/tag-input/reorder.ts +0 -41
  397. package/src/test-utils/LocaleHarness.svelte +0 -29
  398. package/src/test-utils/a11y.ts +0 -108
  399. package/src/test-utils/kbd.ts +0 -52
  400. package/src/textarea/TextArea.a11y.test.ts +0 -44
  401. package/src/textarea/TextArea.svelte +0 -420
  402. package/src/textarea/index.ts +0 -2
  403. package/src/textarea/meta.ts +0 -104
  404. package/src/time-picker/TimePicker.a11y.test.ts +0 -45
  405. package/src/time-picker/TimePicker.kbd.test.ts +0 -70
  406. package/src/time-picker/TimePicker.svelte +0 -1023
  407. package/src/time-picker/TimePickerKbdFixture.svelte +0 -23
  408. package/src/time-picker/index.ts +0 -2
  409. package/src/time-picker/meta.ts +0 -64
  410. package/src/timeline/Timeline.a11y.test.ts +0 -54
  411. package/src/timeline/Timeline.svelte +0 -599
  412. package/src/timeline/TimelineItem.svelte +0 -164
  413. package/src/timeline/context.ts +0 -28
  414. package/src/timeline/index.ts +0 -16
  415. package/src/timeline/meta.ts +0 -129
  416. package/src/timeline/types.ts +0 -9
  417. package/src/toast/Toast.a11y.test.ts +0 -67
  418. package/src/toast/ToastContainer.svelte +0 -184
  419. package/src/toast/ToastItem.svelte +0 -247
  420. package/src/toast/index.ts +0 -9
  421. package/src/toast/live-region.ts +0 -78
  422. package/src/toast/meta.ts +0 -95
  423. package/src/toast/store.ts +0 -187
  424. package/src/tooltip/Tooltip.a11y.test.ts +0 -41
  425. package/src/tooltip/Tooltip.kbd.test.ts +0 -37
  426. package/src/tooltip/Tooltip.svelte +0 -416
  427. package/src/tooltip/TooltipA11yFixture.svelte +0 -31
  428. package/src/tooltip/TooltipKbdFixture.svelte +0 -16
  429. package/src/tooltip/index.ts +0 -2
  430. package/src/tooltip/meta.ts +0 -59
  431. package/src/tooltip/placement.ts +0 -6
  432. package/src/transfer/Transfer.a11y.test.ts +0 -63
  433. package/src/transfer/Transfer.kbd.test.ts +0 -75
  434. package/src/transfer/Transfer.svelte +0 -1451
  435. package/src/transfer/TransferKbdFixture.svelte +0 -23
  436. package/src/transfer/group.test.ts +0 -55
  437. package/src/transfer/group.ts +0 -61
  438. package/src/transfer/index.ts +0 -3
  439. package/src/transfer/meta.ts +0 -55
  440. package/src/transfer/reorder.test.ts +0 -52
  441. package/src/transfer/reorder.ts +0 -42
  442. package/src/transfer/tree.test.ts +0 -44
  443. package/src/transfer/tree.ts +0 -52
  444. package/src/transfer/types.ts +0 -32
  445. package/src/tree/Tree.a11y.test.ts +0 -63
  446. package/src/tree/Tree.kbd.test.ts +0 -91
  447. package/src/tree/Tree.svelte +0 -1500
  448. package/src/tree/TreeKbdFixture.svelte +0 -30
  449. package/src/tree/index.ts +0 -3
  450. package/src/tree/meta.ts +0 -152
  451. package/src/tree-select/TreeSelect.a11y.test.ts +0 -52
  452. package/src/tree-select/TreeSelect.kbd.test.ts +0 -53
  453. package/src/tree-select/TreeSelect.svelte +0 -1611
  454. package/src/tree-select/TreeSelectKbdFixture.svelte +0 -20
  455. package/src/tree-select/index.ts +0 -3
  456. package/src/tree-select/meta.ts +0 -73
  457. package/src/tree-select/types.ts +0 -11
  458. package/src/tree-select/virtual.test.ts +0 -104
  459. package/src/typography/Link.svelte +0 -120
  460. package/src/typography/Paragraph.svelte +0 -92
  461. package/src/typography/Text.svelte +0 -93
  462. package/src/typography/Title.svelte +0 -91
  463. package/src/typography/Typography.a11y.test.ts +0 -34
  464. package/src/typography/TypographyA11yFixture.svelte +0 -27
  465. package/src/typography/TypographyBase.svelte +0 -807
  466. package/src/typography/index.ts +0 -16
  467. package/src/typography/meta.ts +0 -158
  468. package/src/upload/Upload.a11y.test.ts +0 -58
  469. package/src/upload/Upload.svelte +0 -1094
  470. package/src/upload/UploadA11yFixture.svelte +0 -23
  471. package/src/upload/index.ts +0 -3
  472. package/src/upload/meta.ts +0 -64
  473. package/src/upload/types.ts +0 -16
  474. package/src/virtual-list/VirtualList.svelte +0 -401
  475. package/src/virtual-list/index.ts +0 -2
  476. package/src/virtual-list/meta.ts +0 -106
@@ -1,2327 +0,0 @@
1
- <!--
2
- Table — see specs/components/show/Table.spec.md
3
- 列定义驱动渲染:三态排序、客户端分页、行选择(含半选),受控/非受控双轨。
4
- 受控 sortState / rowSelection.selectedRowKeys / pagination.current 不回写,
5
- 仅通过 onSortChange / rowSelection.onChange / pagination.onChange 通知 (红线 #1)。
6
- 复用 @chenzy-design/core 纯函数算法与 Pagination 组件,不重复实现。
7
- 固定列:column.fixed='left'|'right',横滚时 sticky 锁定 + 逐列像素偏移 + 边界阴影。
8
- 列筛选:column.filters + onFilter,列头漏斗弹浮层多选过滤(复用 _floating + useDismiss)。
9
- 列宽拖拽:column.resizable,列头右侧拖拽手柄,指针几何命令式管理(红线 #3);
10
- 覆盖宽度存本地 SvelteMap 不写回 columns prop(红线 #1),进 cellStyle 宽度计算。
11
- 树形数据:tree=true 或 tree={{ childrenColumnName, indentSize, expandedRowKeys... }};
12
- 行含 children 自动嵌套,第一列内展开三角 + 逐级缩进;排序/分页/筛选作用于顶层行,
13
- 可见行经 core flattenTreeRows 纯函数扁平化驱动 tbody (红线 #2);受控展开 keys 不回写 (红线 #1)。
14
- 树形行选择父子联动:rowSelection.checkStrictly 默认 false=联动(勾父连带勾全部后代,
15
- 后代部分选中父行半选 indeterminate),true=父子独立(向后兼容)。联动 {checked,half} 经 core
16
- conductRows/toggleRowCheck 纯函数据整棵可见行树派生 (红线 #2);内部存叶子级 base,
17
- onChange 回传含父行的完整 checked 集;半选写 input.indeterminate 复用 attachment (红线 #3)。
18
- 行虚拟滚动:virtualized=true 时 .cd-table-wrap 自身纵向滚动(固定 height),thead sticky 固定顶部,
19
- tbody 仅渲染视口内行切片(复用 core fixedRange 算可见区间),首尾各一个 padding spacer tr 撑出
20
- 未渲染行总高(保持原生 <table>/<tr>/<td> 语义与 a11y)。scrollTop 命令式 scroll 回调 + rAF 节流写入
21
- 本地 $state,可见区间纯 $derived render 期只读(红线 #2/#3)。virtualized 与 pagination 互斥(虚拟时
22
- 忽略分页全量滚动);排序/筛选/行选择/树形/固定列均正常协同。假定行等高,不建议与 expandable 混用。
23
- -->
24
- <script lang="ts" generics="T extends Record<string, unknown>">
25
- import {
26
- toggleSort,
27
- applySort,
28
- paginate,
29
- selectAllState,
30
- toggleSelectAll,
31
- toggleRow,
32
- flattenTreeRows,
33
- conductRows,
34
- toggleRowCheck,
35
- fixedRange,
36
- useLiveAnnouncer,
37
- useId,
38
- type RowKey,
39
- type SortState,
40
- type FlatRow,
41
- } from '@chenzy-design/core';
42
- import { tick } from 'svelte';
43
- import type { Snippet } from 'svelte';
44
- import { SvelteMap } from 'svelte/reactivity';
45
- import { Pagination } from '../pagination/index.js';
46
- import { floating } from '../_floating/use-floating.js';
47
- import { useDismiss } from '@chenzy-design/core';
48
- import { useLocale } from '../locale-provider/index.js';
49
- import {
50
- buildGridCols,
51
- nextGridCoord,
52
- isFocusCell as isFocusCellPure,
53
- rovingTabindexAt,
54
- type GridCol,
55
- } from './grid-nav.js';
56
- import type {
57
- ColumnDef,
58
- RowSelection,
59
- Expandable,
60
- TreeTable,
61
- Align,
62
- TableSize,
63
- TableChangeInfo,
64
- TableChangeAction,
65
- TableScrollInfo,
66
- ScrollConfig,
67
- } from './types.js';
68
-
69
- // 泛型组件 props 用内联类型而非具名 interface Props:在 declaration:true 下,
70
- // 引用泛型参数 T 的具名 interface 会被当作私有名泄漏进生成的 .d.ts 公共签名而报错。
71
- let {
72
- columns = [],
73
- dataSource = [],
74
- rowKey = 'key',
75
- size = 'default',
76
- bordered = false,
77
- stripe = false,
78
- loading = false,
79
- sortState,
80
- defaultSortState = { key: null, order: null },
81
- onSortChange,
82
- pagination,
83
- rowSelection,
84
- expandable,
85
- tree,
86
- rowClassName,
87
- empty,
88
- ariaLabel,
89
- onRowClick,
90
- onChange,
91
- onFilterChange,
92
- onPaginationChange,
93
- onSelectChange,
94
- onExpandChange,
95
- onScroll,
96
- onReachBottom,
97
- reachBottomThreshold = 0,
98
- virtualized = false,
99
- height = 400,
100
- rowHeight = 48,
101
- gridNav,
102
- scroll,
103
- sticky = false,
104
- showHeader = true,
105
- defaultExpandAllRows = false,
106
- getPopupContainer,
107
- onRow,
108
- onHeaderRow,
109
- expandRowByClick = false,
110
- expandCellFixed,
111
- keepDOM = false,
112
- indentSize: indentSizeProp = 20,
113
- groupBy,
114
- renderGroupSection,
115
- titleSnippet,
116
- footerSnippet,
117
- }: {
118
- columns?: ColumnDef<T>[];
119
- dataSource?: T[];
120
- rowKey?: string | ((record: T) => RowKey);
121
- size?: TableSize;
122
- bordered?: boolean;
123
- stripe?: boolean;
124
- loading?: boolean;
125
- sortState?: SortState;
126
- defaultSortState?: SortState;
127
- onSortChange?: (state: SortState) => void;
128
- pagination?:
129
- | false
130
- | {
131
- pageSize?: number;
132
- current?: number;
133
- defaultCurrent?: number;
134
- onChange?: (page: number) => void;
135
- };
136
- rowSelection?: RowSelection<T>;
137
- expandable?: Expandable<T>;
138
- tree?: boolean | TreeTable;
139
- rowClassName?: (record: T, index: number) => string;
140
- empty?: string;
141
- ariaLabel?: string;
142
- onRowClick?: (info: { record: T; index: number }) => void;
143
- /** 聚合事件:排序/筛选/分页任一变化的主入口(受控数据回流)。spec §4 */
144
- onChange?: (info: TableChangeInfo) => void;
145
- /** 筛选状态变化(含重置)。spec §4 */
146
- onFilterChange?: (info: { dataIndex: string; values: (string | number)[] }) => void;
147
- /** 分页变化。spec §4 */
148
- onPaginationChange?: (info: { current: number; pageSize: number }) => void;
149
- /** 选择集变化(与 rowSelection.onChange 同时触发)。spec §4 */
150
- onSelectChange?: (info: { selectedRowKeys: RowKey[]; selectedRows: T[] }) => void;
151
- /** 行展开/收起(展开行与树形行均触发)。spec §4 */
152
- onExpandChange?: (info: {
153
- expanded: boolean;
154
- record: T;
155
- expandedRowKeys: RowKey[];
156
- }) => void;
157
- /** 滚动位置(含触底,用于无限加载)。命令式监听滚动容器。spec §4 */
158
- onScroll?: (info: TableScrollInfo) => void;
159
- /** 纵向触底(懒加载触发)。距底 reachBottomThreshold 像素内触发一次。spec §4 */
160
- onReachBottom?: () => void;
161
- /** onReachBottom 触发阈值(距底像素),默认 0(精确触底) */
162
- reachBottomThreshold?: number;
163
- /** 行虚拟滚动:仅渲染视口内行,适合大数据(1000+ 行)。默认 false(行为不变)。
164
- * 启用时忽略 pagination(全量滚动),表头 sticky 固定于滚动容器顶部。
165
- * 假定行等高(rowHeight);与 expandable 同用时展开内容行不计入高度,故不建议混用。 */
166
- virtualized?: boolean;
167
- /** 虚拟滚动视口高度(px)。virtualized 时生效,默认 400 */
168
- height?: number;
169
- /** 虚拟滚动行高(px)。virtualized 时生效,默认 48 */
170
- rowHeight?: number;
171
- /**
172
- * 交互态 WAI-ARIA Grid Pattern 开关(role=grid + 单元格二维方向键漫游 +
173
- * roving tabindex + 虚拟化焦点回收)。
174
- * - 缺省(undefined):当存在交互能力(排序/筛选/行选择/展开/树形/行点击)时自动启用 grid,
175
- * 纯展示表保持 role=table,省去漫游逻辑(spec §3 纯展示降级)。
176
- * - 显式 true/false:强制启用/关闭 grid 漫游。
177
- */
178
- gridNav?: boolean;
179
- /** 横/纵向滚动配置,x 设最小宽度并横向溢出,y 设最大高度并纵向溢出 */
180
- scroll?: ScrollConfig;
181
- /** 表头吸顶:true 时表头 sticky 定位;对象时可指定 offsetHeader(px) */
182
- sticky?: boolean | { offsetHeader?: number };
183
- /** 是否显示表头,默认 true */
184
- showHeader?: boolean;
185
- /** 默认展开全部行(包含树形行),默认 false */
186
- defaultExpandAllRows?: boolean;
187
- /** 筛选浮层挂载容器,默认跟随触发按钮 */
188
- getPopupContainer?: () => HTMLElement;
189
- /** 行级事件与属性(返回 onClick/onDoubleClick/className/style) */
190
- onRow?: (record: T, index: number) => { onClick?: (e: MouseEvent) => void; onDoubleClick?: (e: MouseEvent) => void; className?: string; style?: string };
191
- /** 表头行级事件与属性 */
192
- onHeaderRow?: (columns: ColumnDef<T>[], index: number) => { onClick?: (e: MouseEvent) => void; className?: string; style?: string };
193
- /** 点击行体时触发展开/收起,默认 false */
194
- expandRowByClick?: boolean;
195
- /** 展开图标列固定方向 */
196
- expandCellFixed?: boolean | 'left' | 'right';
197
- /** keepDOM=true 时保留已展开行 DOM 但隐藏(display:none),默认 false */
198
- keepDOM?: boolean;
199
- /** 树形缩进像素,默认 20(tree.indentSize 优先) */
200
- indentSize?: number;
201
- /** 按字段名或函数对数据行分组,插入分组标题行 */
202
- groupBy?: string | ((record: T) => string);
203
- /** 自定义分组标题渲染 */
204
- renderGroupSection?: Snippet<[{ groupKey: string; group: T[] }]>;
205
- /** 表格顶部标题区域 */
206
- titleSnippet?: Snippet;
207
- /** 表格底部内容区域(接收 currentData) */
208
- footerSnippet?: Snippet<[{ currentData: T[] }]>;
209
- } = $props();
210
-
211
- const loc = useLocale();
212
- // 单例 live region(polite):排序结果播报给屏幕阅读器(命令式写入在事件回调,红线 #3)。
213
- const announcer = useLiveAnnouncer();
214
-
215
- // --- 键解析 ---
216
- const getKey = (record: T): RowKey =>
217
- typeof rowKey === 'function' ? rowKey(record) : (record[rowKey] as RowKey);
218
-
219
- const colKeyOf = (col: ColumnDef<T>, index: number): string =>
220
- col.key ?? col.dataIndex ?? String(index);
221
-
222
- // --- 列宽拖拽:本地覆盖宽度 (colKey → px),不写回 columns prop (红线 #1) ---
223
- const MIN_COL_WIDTH = 40;
224
- const widthOverrides = new SvelteMap<string, number>();
225
- // 拖拽手柄所在列头引用(用于 pointerdown 读取起始几何)
226
- const resizeHandles: Record<string, HTMLElement | null> = $state({});
227
- // 当前正在拖拽的列 key(用于手柄高亮 / body class)
228
- let resizingKey = $state<string | null>(null);
229
-
230
- // 解析某列最终宽度:覆盖宽度优先,否则 col.width
231
- function resolveWidth(col: ColumnDef<T>, index: number): number | string | undefined {
232
- const ov = widthOverrides.get(colKeyOf(col, index));
233
- if (ov !== undefined) return ov;
234
- return col.width;
235
- }
236
-
237
- // 列头拖拽:命令式管理指针几何 (红线 #3)。
238
- // pointerdown 一次性把起始宽度 / clientX 存普通变量,document 上手动绑定
239
- // pointermove / pointerup,结束时解绑。绝不用响应式 attachment 读几何。
240
- function startResize(event: PointerEvent, col: ColumnDef<T>, index: number) {
241
- event.preventDefault();
242
- event.stopPropagation();
243
- const colKey = colKeyOf(col, index);
244
- const th = resizeHandles[colKey]?.closest('th') as HTMLElement | null;
245
- // 起始宽度:已有覆盖 > col.width 数值 > 实测列头宽度
246
- const ov = widthOverrides.get(colKey);
247
- const startWidth =
248
- ov ?? (typeof col.width === 'number' ? col.width : (th?.getBoundingClientRect().width ?? MIN_COL_WIDTH));
249
- const startX = event.clientX;
250
-
251
- resizingKey = colKey;
252
-
253
- const onMove = (e: PointerEvent) => {
254
- const next = Math.max(MIN_COL_WIDTH, Math.round(startWidth + (e.clientX - startX)));
255
- widthOverrides.set(colKey, next);
256
- };
257
- const onUp = () => {
258
- document.removeEventListener('pointermove', onMove);
259
- document.removeEventListener('pointerup', onUp);
260
- activeMove = null;
261
- activeUp = null;
262
- resizingKey = null;
263
- };
264
- activeMove = onMove;
265
- activeUp = onUp;
266
- document.addEventListener('pointermove', onMove);
267
- document.addEventListener('pointerup', onUp);
268
- }
269
-
270
- // 卸载兜底:若拖拽未结束就卸载,清掉可能遗留的全局监听 (红线 #3)。
271
- let activeMove: ((e: PointerEvent) => void) | null = null;
272
- let activeUp: (() => void) | null = null;
273
- $effect(() => () => {
274
- if (activeMove) document.removeEventListener('pointermove', activeMove);
275
- if (activeUp) document.removeEventListener('pointerup', activeUp);
276
- });
277
-
278
- // --- 排序:受控 sortState 不回写 (红线 #1) ---
279
- const isSortControlled = $derived(sortState !== undefined);
280
- let innerSort = $state<SortState>(initSort());
281
- const currentSort = $derived<SortState>(
282
- isSortControlled ? (sortState as SortState) : innerSort,
283
- );
284
- function initSort(): SortState {
285
- // Check for per-column defaultSortOrder
286
- for (let i = 0; i < columns.length; i++) {
287
- const col = columns[i] as ColumnDef<T>;
288
- if (col.defaultSortOrder != null) {
289
- return { key: colKeyOf(col, i), order: col.defaultSortOrder };
290
- }
291
- }
292
- return { ...defaultSortState };
293
- }
294
-
295
- // --- 列筛选:本地 state(colKey → 选中值集合),不写回 props (红线 #1) ---
296
- const filterState = new SvelteMap<string, Set<string | number>>();
297
- // 打开的筛选浮层列 key(同时只开一个)
298
- let openFilterKey = $state<string | null>(null);
299
- // 各列漏斗按钮引用(trigger)+ 当前浮层引用(dismiss extraTargets)
300
- const filterTriggers: Record<string, HTMLButtonElement | null> = $state({});
301
- let filterPanelEl = $state<HTMLDivElement | null>(null);
302
-
303
- // 浮层外点击/Esc 关闭(红线 #3:$effect 内 useDismiss,cleanup 解绑)
304
- $effect(() => {
305
- if (openFilterKey === null) return;
306
- const trigger = filterTriggers[openFilterKey];
307
- if (!trigger) return;
308
- return useDismiss(trigger, {
309
- onDismiss: () => (openFilterKey = null),
310
- escape: true,
311
- outsideClick: true,
312
- extraTargets: [filterPanelEl],
313
- });
314
- });
315
-
316
- function activeFilterValues(colKey: string): Set<string | number> {
317
- return filterState.get(colKey) ?? new Set();
318
- }
319
- function isFiltered(colKey: string): boolean {
320
- return (filterState.get(colKey)?.size ?? 0) > 0;
321
- }
322
- function toggleFilterValue(colKey: string, value: string | number) {
323
- const cur = new Set(filterState.get(colKey) ?? []);
324
- if (cur.has(value)) cur.delete(value);
325
- else cur.add(value);
326
- filterState.set(colKey, cur);
327
- emitFilterChange(colKey, [...cur]);
328
- }
329
- function resetFilter(colKey: string) {
330
- filterState.set(colKey, new Set());
331
- openFilterKey = null;
332
- emitFilterChange(colKey, []);
333
- }
334
- // 筛选变化:单列 onFilterChange + 聚合 onChange。dataIndex 优先列 dataIndex,回退 colKey。
335
- function emitFilterChange(colKey: string, values: (string | number)[]) {
336
- const col = columns.find((c, i) => colKeyOf(c, i) === colKey);
337
- onFilterChange?.({ dataIndex: col?.dataIndex ?? colKey, values });
338
- emitChange('filter');
339
- }
340
- // 行是否通过某列筛选:选中值任一 onFilter 命中(缺省按 dataIndex 全等)。
341
- function rowPassesColumn(col: ColumnDef<T>, colKey: string, record: T): boolean {
342
- const selected = filterState.get(colKey);
343
- if (!selected || selected.size === 0) return true;
344
- const test =
345
- col.onFilter ??
346
- ((value: string | number, rec: T): boolean =>
347
- col.dataIndex ? rec[col.dataIndex] === value : false);
348
- for (const v of selected) {
349
- if (test(v, record)) return true;
350
- }
351
- return false;
352
- }
353
-
354
- // 合并某列的有效筛选值:filteredValue(受控)优先,否则本地 filterState。
355
- function effectiveFilterValues(col: ColumnDef<T>, colKey: string): Set<string | number> {
356
- if (col.filteredValue !== undefined) {
357
- if (col.filteredValue === null) return new Set();
358
- return new Set(col.filteredValue);
359
- }
360
- return filterState.get(colKey) ?? new Set();
361
- }
362
- function isEffectivelyFiltered(col: ColumnDef<T>, colKey: string): boolean {
363
- return effectiveFilterValues(col, colKey).size > 0;
364
- }
365
-
366
- // --- 数据管道:列筛选 → 排序(客户端)。状态全来自 props / 本地 $state,派生安全 (红线 #2) ---
367
- const processed = $derived.by(() => {
368
- let data = [...dataSource];
369
- // 列筛选(多列 AND)
370
- for (let i = 0; i < columns.length; i++) {
371
- const col = columns[i] as ColumnDef<T>;
372
- const ck = colKeyOf(col, i);
373
- if (isEffectivelyFiltered(col, ck)) {
374
- const selected = effectiveFilterValues(col, ck);
375
- const test =
376
- col.onFilter ??
377
- ((value: string | number, rec: T): boolean =>
378
- col.dataIndex ? rec[col.dataIndex] === value : false);
379
- data = data.filter((rec) => {
380
- for (const v of selected) {
381
- if (test(v, rec)) return true;
382
- }
383
- return false;
384
- });
385
- }
386
- }
387
- const { key, order } = currentSort;
388
- if (key && order) {
389
- let target: ColumnDef<T> | undefined;
390
- columns.forEach((col, i) => {
391
- if (colKeyOf(col, i) === key) target = col;
392
- });
393
- if (target && target.sorter) {
394
- const dataIndex = target.dataIndex;
395
- const compare =
396
- typeof target.sorter === 'function'
397
- ? target.sorter
398
- : (a: T, b: T): number => {
399
- if (!dataIndex) return 0;
400
- const av = a[dataIndex];
401
- const bv = b[dataIndex];
402
- if (av == null && bv == null) return 0;
403
- if (av == null) return -1;
404
- if (bv == null) return 1;
405
- if (typeof av === 'number' && typeof bv === 'number') return av - bv;
406
- return String(av).localeCompare(String(bv));
407
- };
408
- data = applySort(data, order, compare);
409
- }
410
- }
411
- return data;
412
- });
413
-
414
- // --- 分页:受控 current 不回写 (红线 #1) ---
415
- // virtualized 与分页互斥:虚拟滚动时全量渲染滚动,忽略 pagination(取舍见 props 注释)。
416
- const paginationEnabled = $derived(!virtualized && pagination !== false);
417
- const pageSize = $derived(pagination ? (pagination.pageSize ?? 10) : 10);
418
- const isPageControlled = $derived(!!pagination && pagination.current !== undefined);
419
- let innerPage = $state(initPage());
420
- function initPage(): number {
421
- return pagination ? (pagination.defaultCurrent ?? 1) : 1;
422
- }
423
- const currentPage = $derived(
424
- pagination && pagination.current !== undefined ? pagination.current : innerPage,
425
- );
426
-
427
- const total = $derived(processed.length);
428
- const visibleRows = $derived(
429
- paginationEnabled ? paginate(processed, currentPage, pageSize) : processed,
430
- );
431
-
432
- // --- 行选择:受控 selectedRowKeys 不回写 (红线 #1) ---
433
- const isSelectionControlled = $derived(
434
- rowSelection?.selectedRowKeys !== undefined,
435
- );
436
- let innerSelected = $state<RowKey[]>(initSelected());
437
- function initSelected(): RowKey[] {
438
- return [...(rowSelection?.defaultSelectedRowKeys ?? [])];
439
- }
440
- const currentSelectedKeys = $derived<RowKey[]>(
441
- isSelectionControlled
442
- ? (rowSelection?.selectedRowKeys ?? [])
443
- : innerSelected,
444
- );
445
- const selectedSet = $derived(new Set(currentSelectedKeys));
446
-
447
- // --- 树形数据(嵌套行)---
448
- // tree 启用时:filter/sort/paginate 作用于顶层行(processed/visibleRows),
449
- // 然后据展开态把可见顶层行扁平化为带 level/hasChildren 的渲染行列表。
450
- // 受控 tree.expandedRowKeys 不回写,仅 onExpand 通知 (红线 #1)。
451
- const treeEnabled = $derived(tree !== undefined && tree !== false);
452
- const treeOpts = $derived<TreeTable>(typeof tree === 'object' ? tree : {});
453
- const childrenColumnName = $derived(treeOpts.childrenColumnName ?? 'children');
454
- const indentSize = $derived(treeOpts.indentSize ?? indentSizeProp);
455
-
456
- function getChildren(record: T): T[] | undefined {
457
- const kids = record[childrenColumnName];
458
- return Array.isArray(kids) ? (kids as T[]) : undefined;
459
- }
460
-
461
- // --- 树形行选择父子联动 ---
462
- // checkStrictly 默认 false=联动;true 时父子独立(与非树形逐行选择一致,向后兼容)。
463
- // 联动仅在树形 + 有行选择时生效。联动态下 base 选中集为叶子级,经纯函数
464
- // conductRows(顶层可见行树) 派生 {checked, half}(红线 #2:纯函数 + $derived)。
465
- const treeCheckable = $derived(
466
- treeEnabled && rowSelection !== undefined && rowSelection.checkStrictly !== true,
467
- );
468
- const rowDisabledFn = (record: T): boolean =>
469
- rowSelection?.getCheckboxProps?.(record)?.disabled ?? false;
470
- // 联动选择派生:覆盖整棵可见顶层行树(含未展开的子行)。
471
- const conducted = $derived.by(() => {
472
- if (!treeCheckable) return { checked: selectedSet, half: new Set<RowKey>() };
473
- return conductRows(visibleRows, selectedSet, getKey, getChildren, rowDisabledFn);
474
- });
475
-
476
- const isTreeExpandControlled = $derived(treeOpts.expandedRowKeys !== undefined);
477
- let innerTreeExpanded = $state<RowKey[]>(initTreeExpanded());
478
- function initTreeExpanded(): RowKey[] {
479
- if (typeof tree === 'object' && tree.defaultExpandedRowKeys) {
480
- return [...tree.defaultExpandedRowKeys];
481
- }
482
- if (defaultExpandAllRows) {
483
- // 递归收集所有含子行的行 key
484
- const keys: RowKey[] = [];
485
- const col = typeof tree === 'object' ? (tree.childrenColumnName ?? 'children') : 'children';
486
- const walk = (records: T[]): void => {
487
- for (const r of records) {
488
- const k = typeof rowKey === 'function' ? rowKey(r) : (r[rowKey as string] as RowKey);
489
- const kids = r[col];
490
- if (Array.isArray(kids) && kids.length > 0) {
491
- keys.push(k);
492
- walk(kids as T[]);
493
- }
494
- }
495
- };
496
- walk(dataSource);
497
- return keys;
498
- }
499
- return [];
500
- }
501
- const treeExpandedSet = $derived<Set<RowKey>>(
502
- new Set(isTreeExpandControlled ? (treeOpts.expandedRowKeys ?? []) : innerTreeExpanded),
503
- );
504
-
505
- function toggleTreeExpand(record: T) {
506
- const key = getKey(record);
507
- const next = new Set(treeExpandedSet);
508
- const willExpand = !next.has(key);
509
- if (willExpand) next.add(key);
510
- else next.delete(key);
511
- if (!isTreeExpandControlled) innerTreeExpanded = [...next];
512
- treeOpts.onExpand?.(willExpand, key);
513
- onExpandChange?.({ expanded: willExpand, record, expandedRowKeys: [...next] });
514
- }
515
-
516
- // 扁平化可见行:纯 $derived,不读 effect 写的状态 (红线 #2)。
517
- // 顶层行已是分页后的 visibleRows;树形时递归展开,否则等价 1:1 映射。
518
- const displayRows = $derived.by<FlatRow<T>[]>(() => {
519
- if (!treeEnabled) {
520
- return visibleRows.map((record, i) => ({
521
- record,
522
- key: getKey(record),
523
- level: 0,
524
- parentKey: null,
525
- hasChildren: false,
526
- topIndex: i,
527
- }));
528
- }
529
- return flattenTreeRows(visibleRows, treeExpandedSet, getKey, getChildren);
530
- });
531
-
532
- // --- 行虚拟滚动:仅渲染视口内行(复用 core fixedRange 纯函数)---
533
- // 视口容器自身滚动,scrollTop 由命令式 scroll 回调写入;可见区间纯 $derived
534
- // 仅依赖本地 $state,render 期只读不读 DOM(红线 #2)。
535
- const VIRTUAL_OVERSCAN = 4;
536
- let scrollEl = $state<HTMLDivElement | null>(null);
537
- // 仅由 scroll 回调写入的本地 scrollTop,render 期只读。
538
- let scrollTop = $state(0);
539
- // rAF 节流句柄(非响应式)。
540
- let rafId = 0;
541
- // onReachBottom 去抖:仅在「进入触底区」的那一帧触发一次,离开后复位(非响应式)。
542
- let reachedBottom = false;
543
-
544
- const vRowHeight = $derived(rowHeight > 0 ? rowHeight : 48);
545
- const vTotalHeight = $derived(displayRows.length * vRowHeight);
546
- const vRange = $derived(
547
- virtualized
548
- ? fixedRange(scrollTop, height, vRowHeight, displayRows.length, VIRTUAL_OVERSCAN)
549
- : { startIndex: 0, endIndex: displayRows.length },
550
- );
551
- // 实际喂给 #each 的行集合:virtualized 时只取视口内切片,否则全量。
552
- const renderRows = $derived(
553
- virtualized ? displayRows.slice(vRange.startIndex, vRange.endIndex) : displayRows,
554
- );
555
- // 上下 spacer 行高度:未渲染的上方/下方行总高,撑出正确总高(保持原生 table 语义)。
556
- const vTopPad = $derived(virtualized ? vRange.startIndex * vRowHeight : 0);
557
- const vBottomPad = $derived(
558
- virtualized ? Math.max(0, (displayRows.length - vRange.endIndex) * vRowHeight) : 0,
559
- );
560
-
561
- // 滚动监听需附着的容器是否需要纵向滚动能力:
562
- // 非虚拟化但提供了 onScroll/onReachBottom 时,约束高度并 overflow:auto,
563
- // 使表体可纵向滚动从而能上报位置/触底(虚拟化已自带固定高度滚动)。
564
- const scrollBody = $derived(!virtualized && (!!onScroll || !!onReachBottom));
565
-
566
- // 滚动监听(命令式 + rAF 节流 + cleanup)(红线 #3)。
567
- // 同时服务:① virtualized 视口区间计算(写 scrollTop)② onScroll 位置回调 ③ onReachBottom 触底。
568
- $effect(() => {
569
- const el = scrollEl;
570
- const needScroll = virtualized || !!onScroll || !!onReachBottom;
571
- if (!el || !needScroll) return;
572
- const handleScroll = () => {
573
- if (rafId) return;
574
- rafId = requestAnimationFrame(() => {
575
- rafId = 0;
576
- if (!el) return;
577
- if (virtualized) scrollTop = el.scrollTop;
578
- const { scrollLeft, scrollTop: sTop, scrollWidth, clientWidth, scrollHeight, clientHeight } = el;
579
- const atBottom = sTop + clientHeight >= scrollHeight - Math.max(0, reachBottomThreshold) - 1;
580
- if (onScroll) {
581
- onScroll({
582
- scrollLeft,
583
- scrollTop: sTop,
584
- atLeft: scrollLeft <= 0,
585
- atRight: scrollLeft + clientWidth >= scrollWidth - 1,
586
- atTop: sTop <= 0,
587
- atBottom,
588
- });
589
- }
590
- // 触底懒加载:仅在「刚进入触底区」触发一次,离开后复位(避免持续滚动重复触发)。
591
- if (onReachBottom) {
592
- if (atBottom && !reachedBottom) {
593
- reachedBottom = true;
594
- onReachBottom();
595
- } else if (!atBottom) {
596
- reachedBottom = false;
597
- }
598
- }
599
- });
600
- };
601
- el.addEventListener('scroll', handleScroll, { passive: true });
602
- return () => {
603
- el.removeEventListener('scroll', handleScroll);
604
- reachedBottom = false;
605
- if (rafId) {
606
- cancelAnimationFrame(rafId);
607
- rafId = 0;
608
- }
609
- };
610
- });
611
-
612
- // 全选范围 = 当前渲染行集(树形含已展开的子行);半选据可见行计算
613
- const visibleKeys = $derived(displayRows.map((r) => r.key));
614
- const disabledSet = $derived.by(() => {
615
- const set = new Set<RowKey>();
616
- const getProps = rowSelection?.getCheckboxProps;
617
- if (getProps) {
618
- for (const r of displayRows) {
619
- if (getProps(r.record).disabled) set.add(r.key);
620
- }
621
- }
622
- return set;
623
- });
624
- // 全选状态:联动态据「整棵可见行树」的顶层行 checked/half 计算
625
- // (顶层行全 checked → 全选;任一 half 或部分 checked → 半选),
626
- // 否则沿用扁平可见行的 selectAllState。
627
- const topKeys = $derived(visibleRows.map((r) => getKey(r)));
628
- const headerSelect = $derived.by(() => {
629
- if (!treeCheckable) return selectAllState(visibleKeys, selectedSet, disabledSet);
630
- const tops = topKeys.filter((k) => !disabledSet.has(k));
631
- if (tops.length === 0) return { checked: false, indeterminate: false };
632
- const allChecked = tops.every((k) => conducted.checked.has(k));
633
- const anyMarked = tops.some(
634
- (k) => conducted.checked.has(k) || conducted.half.has(k),
635
- );
636
- return { checked: allChecked, indeterminate: !allChecked && anyMarked };
637
- });
638
-
639
- const hasSelection = $derived(rowSelection !== undefined);
640
- const hasExpand = $derived(expandable !== undefined);
641
- const colSpan = $derived(
642
- columns.length + (hasSelection ? 1 : 0) + (hasExpand ? 1 : 0),
643
- );
644
-
645
- // --- 展开行:受控 expandedRowKeys 不回写 (红线 #1) ---
646
- const isExpandControlled = $derived(expandable?.expandedRowKeys !== undefined);
647
- let innerExpanded = $state<RowKey[]>(initExpanded());
648
- function initExpanded(): RowKey[] {
649
- if (expandable?.defaultExpandedRowKeys) {
650
- return [...expandable.defaultExpandedRowKeys];
651
- }
652
- if (defaultExpandAllRows && expandable) {
653
- return dataSource.map((r) =>
654
- typeof rowKey === 'function' ? rowKey(r) : (r[rowKey as string] as RowKey),
655
- );
656
- }
657
- return [];
658
- }
659
- const expandedSet = $derived<Set<RowKey>>(
660
- new Set(
661
- isExpandControlled
662
- ? (expandable?.expandedRowKeys ?? [])
663
- : innerExpanded,
664
- ),
665
- );
666
-
667
- function canExpand(record: T): boolean {
668
- return expandable?.rowExpandable ? expandable.rowExpandable(record) : true;
669
- }
670
-
671
- function toggleExpand(record: T) {
672
- const key = getKey(record);
673
- if (!canExpand(record)) return;
674
- const next = new Set(expandedSet);
675
- const willExpand = !next.has(key);
676
- if (willExpand) next.add(key);
677
- else next.delete(key);
678
- if (!isExpandControlled) innerExpanded = [...next];
679
- expandable?.onExpand?.(willExpand, record);
680
- onExpandChange?.({ expanded: willExpand, record, expandedRowKeys: [...next] });
681
- }
682
-
683
- // --- 选择变更:回调取对应行对象 ---
684
- // 联动树形需含未展开的子行,故据整棵可见行树建 key→record 映射;
685
- // 非树形/严格态沿用扁平 displayRows。
686
- const keyRecordMap = $derived.by(() => {
687
- const map = new Map<RowKey, T>();
688
- if (treeCheckable) {
689
- const walk = (record: T): void => {
690
- map.set(getKey(record), record);
691
- const kids = getChildren(record);
692
- if (kids) for (const c of kids) walk(c);
693
- };
694
- for (const r of visibleRows) walk(r);
695
- } else {
696
- for (const r of displayRows) map.set(r.key, r.record);
697
- }
698
- return map;
699
- });
700
- function rowsForKeys(keys: RowKey[]): T[] {
701
- const result: T[] = [];
702
- for (const k of keys) {
703
- const r = keyRecordMap.get(k);
704
- if (r !== undefined) result.push(r);
705
- }
706
- return result;
707
- }
708
-
709
- // next 是叶子级 base 集(联动态可经 conductRows round-trip)。
710
- // 内部态存 base;onChange 联动态回传含父行的完整 checked 集 + 行。
711
- function emitSelection(next: Set<RowKey>) {
712
- if (!isSelectionControlled) innerSelected = [...next];
713
- if (treeCheckable) {
714
- const { checked } = conductRows(visibleRows, next, getKey, getChildren, rowDisabledFn);
715
- const keys = [...checked];
716
- const rows = rowsForKeys(keys);
717
- rowSelection?.onChange?.(keys, rows);
718
- onSelectChange?.({ selectedRowKeys: keys, selectedRows: rows });
719
- } else {
720
- const keys = [...next];
721
- const rows = rowsForKeys(keys);
722
- rowSelection?.onChange?.(keys, rows);
723
- onSelectChange?.({ selectedRowKeys: keys, selectedRows: rows });
724
- }
725
- }
726
-
727
- function onToggleAll() {
728
- // radio 模式无全选
729
- if (rowSelection?.type === 'radio') return;
730
- if (treeCheckable) {
731
- // 全选:勾全部可见顶层行(连带后代叶子);已全选则清空。
732
- const tops = topKeys.filter((k) => !disabledSet.has(k));
733
- const allChecked = tops.length > 0 && tops.every((k) => conducted.checked.has(k));
734
- let next = new Set(selectedSet);
735
- for (const k of tops) {
736
- // 目标态:全选则要 off,否则要 on;与当前态不符才 toggle
737
- const isOn = conducted.checked.has(k);
738
- if (allChecked === isOn) {
739
- next = toggleRowCheck(visibleRows, next, k, getKey, getChildren, rowDisabledFn);
740
- }
741
- }
742
- const wasSelected = allChecked;
743
- emitSelection(next);
744
- // onSelectAll 回调
745
- if (rowSelection?.onSelectAll) {
746
- const { checked } = treeCheckable
747
- ? conductRows(visibleRows, next, getKey, getChildren, rowDisabledFn)
748
- : { checked: next };
749
- const selectedRows = rowsForKeys([...checked]);
750
- // changedRows: 非 disabled 的顶层行对应记录
751
- const changedRows = tops.map((k) => keyRecordMap.get(k)).filter((r): r is T => r !== undefined);
752
- rowSelection.onSelectAll(!wasSelected, selectedRows, changedRows);
753
- }
754
- return;
755
- }
756
- const prevKeys = [...selectedSet];
757
- const next = toggleSelectAll(visibleKeys, selectedSet, disabledSet);
758
- emitSelection(next);
759
- // onSelectAll 回调
760
- if (rowSelection?.onSelectAll) {
761
- const nextKeys = [...next];
762
- const isNowSelected = nextKeys.length > prevKeys.length;
763
- const selectedRows = rowsForKeys(nextKeys);
764
- const changedKeys = isNowSelected
765
- ? nextKeys.filter((k) => !selectedSet.has(k))
766
- : prevKeys.filter((k) => !next.has(k));
767
- const changedRows = rowsForKeys(changedKeys);
768
- rowSelection.onSelectAll(isNowSelected, selectedRows, changedRows);
769
- }
770
- }
771
-
772
- function onToggleRow(record: T) {
773
- const key = getKey(record);
774
- if (disabledSet.has(key)) return;
775
- const isRadio = rowSelection?.type === 'radio';
776
- if (isRadio) {
777
- // 单选:直接将选中集设为该行
778
- const next = new Set<RowKey>([key]);
779
- const wasSelected = selectedSet.has(key);
780
- emitSelection(next);
781
- rowSelection?.onSelect?.(record, !wasSelected, rowsForKeys([...next]));
782
- return;
783
- }
784
- const wasSelected = selectedSet.has(key);
785
- if (treeCheckable) {
786
- const next = toggleRowCheck(visibleRows, selectedSet, key, getKey, getChildren, rowDisabledFn);
787
- emitSelection(next);
788
- const { checked } = conductRows(visibleRows, next, getKey, getChildren, rowDisabledFn);
789
- rowSelection?.onSelect?.(record, !wasSelected, rowsForKeys([...checked]));
790
- return;
791
- }
792
- const next = toggleRow(selectedSet, key);
793
- emitSelection(next);
794
- rowSelection?.onSelect?.(record, !wasSelected, rowsForKeys([...next]));
795
- }
796
-
797
- // ===================================================================
798
- // 交互态 WAI-ARIA Grid Pattern:role=grid + 二维方向键漫游 + roving
799
- // tabindex + 虚拟化焦点回收。
800
- //
801
- // 焦点模型(红线 #2/#3):
802
- // - 焦点坐标 focusRow/focusCol 为「逻辑索引」存本地 $state:
803
- // focusRow = -1 表示表头行,0..displayRows.length-1 表示数据行(逻辑序,
804
- // 与虚拟化是否渲染无关);focusCol 索引进「网格列」扁平表
805
- // [expand?, selection?, ...dataColumns]。
806
- // - 每个渲染单元格的 tabindex 由纯派生函数 rovingTabindex(row,col) 计算:
807
- // 命中焦点坐标 → 0,否则 -1;整个 grid 只有一个 tabbable 入口(APG)。
808
- // - 方向键算下一坐标(纯函数 nextRovingIndex),命令式 focusCell() 聚焦:
809
- // 虚拟化下目标行未渲染先滚动进视口 + tick() 再 focus(render 期不写 $state)。
810
- // - 被虚拟化回收的焦点行:$effect 监测,焦点回退到 grid 容器并 announce,
811
- // 不让焦点掉到 <body>。
812
- // ===================================================================
813
-
814
- // grid 是否启用:缺省自动检测交互能力;显式 gridNav 覆盖。
815
- const isInteractive = $derived(
816
- hasSelection ||
817
- hasExpand ||
818
- treeEnabled ||
819
- !!onRowClick ||
820
- columns.some((c) => !!c.sorter || (!!c.filters && c.filters.length > 0)),
821
- );
822
- const gridEnabled = $derived(gridNav !== undefined ? gridNav : isInteractive);
823
-
824
- // grid 单元格 id 前缀(稳定、SSR 安全)。
825
- const gridId = useId('cd-table-grid');
826
- // 网格列扁平表:前置 expand/selection 占位列 + 数据列(纯函数 buildGridCols)。
827
- const gridCols = $derived<GridCol[]>(
828
- buildGridCols({ hasExpand, hasSelection, dataColumnCount: columns.length }),
829
- );
830
- const gridColCount = $derived(gridCols.length);
831
- // 总行数(含表头行)= aria-rowcount,虚拟化时为逻辑总数而非渲染数(spec §6)。
832
- const gridRowCount = $derived(displayRows.length + 1);
833
-
834
- // 焦点坐标(逻辑索引)。-1 行 = 表头;col 索引进 gridCols。
835
- // 初始未聚焦时为 null,首个 tab 停靠点回退到表头首格。
836
- let focusRow = $state(-1);
837
- let focusCol = $state(0);
838
- let hasFocused = $state(false);
839
-
840
- // 当前是否处于「单元格交互模式」(Enter/F2 进入,Esc 退出)。
841
- // 交互模式下单元格内可聚焦控件参与 Tab;导航模式下它们 tabindex=-1。
842
- let cellInteractive = $state(false);
843
-
844
- // 某 (row,col) 是否为当前 roving 焦点格(纯派生,render 期只读)。
845
- function isFocusCell(row: number, col: number): boolean {
846
- if (!gridEnabled) return false;
847
- return isFocusCellPure(row, col, { row: focusRow, col: focusCol, hasFocused });
848
- }
849
- // 单元格 roving tabindex:焦点格 0,其余 -1(仅 grid 启用时)。
850
- function rovingTabindex(row: number, col: number): 0 | -1 | undefined {
851
- if (!gridEnabled) return undefined;
852
- return rovingTabindexAt(row, col, { row: focusRow, col: focusCol, hasFocused });
853
- }
854
- // 单元格内交互控件的 tabindex:导航模式下 -1(不参与 Tab,靠 Enter/F2 进入),
855
- // 交互模式下当前焦点格内控件恢复 0。非 grid 时不接管(undefined)。
856
- function childTabindex(row: number, col: number): 0 | -1 | undefined {
857
- if (!gridEnabled) return undefined;
858
- if (cellInteractive && isFocusCell(row, col)) return 0;
859
- return -1;
860
- }
861
- // 单元格稳定 id(focusCell 命令式聚焦目标)。row=-1 表头记作 'h'。
862
- function cellId(row: number, col: number): string {
863
- return `${gridId}-r${row === -1 ? 'h' : row}-c${col}`;
864
- }
865
-
866
- // grid 容器(table 元素)引用 —— 焦点回收落点。
867
- let gridEl = $state<HTMLTableElement | null>(null);
868
-
869
- // 命令式聚焦某逻辑坐标的单元格:虚拟化下若行未渲染先滚动进视口 + tick 再聚焦。
870
- async function focusCell(row: number, col: number) {
871
- focusRow = row;
872
- focusCol = col;
873
- hasFocused = true;
874
- cellInteractive = false;
875
- // 数据行在虚拟化视口外:先滚动使其进入视口(render 期外,事件回调内,红线 #3)。
876
- if (virtualized && row >= 0 && scrollEl) {
877
- const inView = row >= vRange.startIndex && row < vRange.endIndex;
878
- if (!inView) {
879
- // 目标行顶部对齐到视口(留一行余量),写 scrollEl.scrollTop 触发 scroll 回调更新区间。
880
- const target = Math.max(0, row * vRowHeight);
881
- scrollEl.scrollTop = target;
882
- scrollTop = target;
883
- await tick();
884
- }
885
- }
886
- await tick();
887
- const el = gridEl?.querySelector<HTMLElement>(`#${cssEscape(cellId(row, col))}`);
888
- if (el) el.focus();
889
- else if (gridEl) gridEl.focus(); // 兜底:目标仍不可用,焦点回收到 grid 容器(不掉 body)。
890
- }
891
-
892
- // CSS.escape 兜底(id 仅含字母数字与 '-',本不需转义,留作健壮性)。
893
- function cssEscape(id: string): string {
894
- return typeof CSS !== 'undefined' && CSS.escape ? CSS.escape(id) : id;
895
- }
896
-
897
- // 二维方向键漫游 keydown(绑定在 grid table 上,事件委托冒泡)。
898
- function onGridKeydown(e: KeyboardEvent) {
899
- if (!gridEnabled) return;
900
- const key = e.key;
901
-
902
- // 交互模式:Esc 退出回导航模式并聚焦当前格;其余按键交给单元格内控件处理。
903
- if (cellInteractive) {
904
- if (key === 'Escape') {
905
- e.preventDefault();
906
- e.stopPropagation();
907
- cellInteractive = false;
908
- void focusCellSameCoord();
909
- }
910
- return;
911
- }
912
-
913
- // Enter/F2:进入交互模式(聚焦当前格内首个可聚焦控件,无则不拦截)。
914
- if (key === 'Enter' || key === 'F2') {
915
- const entered = enterCellInteractive();
916
- if (entered) {
917
- e.preventDefault();
918
- e.stopPropagation();
919
- }
920
- return;
921
- }
922
-
923
- // 导航模式:用纯函数算下一坐标(PageUp/Down 翻一屏行数)。
924
- const pageRows = Math.max(1, Math.floor(height / vRowHeight) || 10);
925
- const next = nextGridCoord(
926
- key,
927
- { ctrl: e.ctrlKey, meta: e.metaKey },
928
- {
929
- current: { row: focusRow, col: focusCol },
930
- rowCount: displayRows.length,
931
- colCount: gridColCount,
932
- pageRows,
933
- },
934
- );
935
- if (!next) return; // 非漫游键
936
- e.preventDefault();
937
- e.stopPropagation();
938
- if (next.row !== focusRow || next.col !== focusCol || !hasFocused) {
939
- void focusCell(next.row, next.col);
940
- }
941
- }
942
-
943
- // 重聚焦当前坐标(退出交互模式时用,避免改坐标)。
944
- async function focusCellSameCoord() {
945
- await tick();
946
- const el = gridEl?.querySelector<HTMLElement>(`#${cssEscape(cellId(focusRow, focusCol))}`);
947
- if (el) el.focus();
948
- }
949
-
950
- // 进入交互模式:聚焦当前焦点格内首个可聚焦元素。返回是否成功进入。
951
- function enterCellInteractive(): boolean {
952
- const cell = gridEl?.querySelector<HTMLElement>(`#${cssEscape(cellId(focusRow, focusCol))}`);
953
- if (!cell) return false;
954
- const focusable = cell.querySelector<HTMLElement>(
955
- 'button:not([disabled]),a[href],input:not([disabled]),select:not([disabled]),textarea:not([disabled]),[tabindex]:not([tabindex="-1"]):not(.cd-table__cell)',
956
- );
957
- if (!focusable) return false;
958
- cellInteractive = true;
959
- // tabindex 由 childTabindex 派生切到 0;命令式聚焦控件。
960
- void tick().then(() => focusable.focus());
961
- return true;
962
- }
963
-
964
- // 单元格被点击/获得焦点时,同步焦点坐标(鼠标与键盘一致)。
965
- function syncFocusCoord(row: number, col: number) {
966
- if (!gridEnabled) return;
967
- focusRow = row;
968
- focusCol = col;
969
- hasFocused = true;
970
- }
971
-
972
- // --- 虚拟化焦点回收 ---
973
- // 当聚焦数据行被滚出渲染区间(虚拟化卸载),焦点会掉到 <body>。监测并回收:
974
- // 把焦点移到 grid 容器(table,tabindex=-1)并 announce,保证键盘用户不丢失上下文。
975
- $effect(() => {
976
- if (!gridEnabled || !virtualized || !hasFocused) return;
977
- // 仅当焦点落在数据行、且该行不在渲染区间时回收。
978
- const row = focusRow;
979
- if (row < 0) return;
980
- const inView = row >= vRange.startIndex && row < vRange.endIndex;
981
- if (inView) return;
982
- const active = typeof document !== 'undefined' ? document.activeElement : null;
983
- // 仅当焦点确实在本 grid 内(即将随卸载丢失)时回收。
984
- if (gridEl && active && gridEl.contains(active)) {
985
- gridEl.focus();
986
- announcer.announce(
987
- loc().t('Table.rowCount', { count: displayRows.length }),
988
- );
989
- }
990
- });
991
-
992
- // --- 聚合 onChange 载荷快照(读 render 期派生态,仅在事件回调内调用,红线 #2)---
993
- // 当前各列筛选选中值(colKey → values[]),仅含非空筛选列。
994
- function snapshotFilters(): Record<string, (string | number)[]> {
995
- const out: Record<string, (string | number)[]> = {};
996
- columns.forEach((col, i) => {
997
- const ck = colKeyOf(col, i);
998
- const vals = filterState.get(ck);
999
- if (vals && vals.size > 0) out[ck] = [...vals];
1000
- });
1001
- return out;
1002
- }
1003
- // 触发聚合 onChange:sorterOverride/pageOverride 让排序/分页变化时用「即将生效」的值,
1004
- // 因 $derived 在同步事件回调内尚未重算(受控时本就不回写)。
1005
- function emitChange(
1006
- action: TableChangeAction,
1007
- sorterOverride?: SortState,
1008
- pageOverride?: number,
1009
- ) {
1010
- onChange?.({
1011
- pagination: {
1012
- current: pageOverride ?? currentPage,
1013
- pageSize,
1014
- },
1015
- filters: snapshotFilters(),
1016
- sorter: sorterOverride ?? currentSort,
1017
- extra: { action },
1018
- });
1019
- }
1020
-
1021
- // --- 排序点击 ---
1022
- function onSort(col: ColumnDef<T>, index: number) {
1023
- const key = colKeyOf(col, index);
1024
- const next = toggleSort(currentSort, key);
1025
- if (!isSortControlled) innerSort = next;
1026
- onSortChange?.(next);
1027
- emitChange('sort', next);
1028
- announceSort(col, next);
1029
- }
1030
-
1031
- // 排序变化播报:升/降序播 sortedAnnounce(含列名 + 方向),三态循环到 null 播 sortClearedAnnounce。
1032
- function announceSort(col: ColumnDef<T>, next: SortState) {
1033
- const column = columnLabel(col);
1034
- if (next.order) {
1035
- const order = loc().t(
1036
- next.order === 'ascend' ? 'Table.sortOrderAscend' : 'Table.sortOrderDescend',
1037
- );
1038
- announcer.announce(loc().t('Table.sortedAnnounce', { column, order }));
1039
- } else {
1040
- announcer.announce(loc().t('Table.sortClearedAnnounce', { column }));
1041
- }
1042
- }
1043
-
1044
- // 列的可读名:title 为字符串时直接用,否则回退列 key(snippet/复杂 title 无文本可取)。
1045
- function columnLabel(col: ColumnDef<T>): string {
1046
- return typeof col.title === 'string' ? col.title : String(col.dataIndex ?? col.key ?? '');
1047
- }
1048
-
1049
- function ariaSortFor(col: ColumnDef<T>, index: number): 'ascending' | 'descending' | 'none' {
1050
- const order = col.sortOrder !== undefined
1051
- ? col.sortOrder
1052
- : (currentSort.key === colKeyOf(col, index) ? currentSort.order : null);
1053
- if (!order) return 'none';
1054
- return order === 'ascend' ? 'ascending' : 'descending';
1055
- }
1056
-
1057
- // --- 分页变更 ---
1058
- function onPageChange(page: number) {
1059
- if (!isPageControlled) innerPage = page;
1060
- if (pagination) pagination.onChange?.(page);
1061
- onPaginationChange?.({ current: page, pageSize });
1062
- emitChange('paginate', undefined, page);
1063
- }
1064
-
1065
- // --- 单元格取值 ---
1066
- function cellValue(col: ColumnDef<T>, record: T): unknown {
1067
- return col.dataIndex ? record[col.dataIndex] : undefined;
1068
- }
1069
- function cellText(value: unknown): string {
1070
- return value == null ? '' : String(value);
1071
- }
1072
-
1073
- function alignOf(col: ColumnDef<T>): Align {
1074
- return col.align ?? 'left';
1075
- }
1076
- function widthStyle(col: ColumnDef<T>, index: number): string | undefined {
1077
- const w = resolveWidth(col, index);
1078
- if (w === undefined) return undefined;
1079
- return typeof w === 'number' ? `width:${w}px` : `width:${w}`;
1080
- }
1081
-
1082
- // --- 固定列:纯 CSS sticky + 逐列像素偏移计算 ---
1083
- // selection / expand 前置列宽(与 CSS .cd-table__cell--selection/--expand 对齐)
1084
- const LEADING_W = 48;
1085
- // 前置 leading 列(expand + selection)的总宽,作为 left 固定列偏移基数
1086
- const leadingWidth = $derived((hasExpand ? LEADING_W : 0) + (hasSelection ? LEADING_W : 0));
1087
- const hasFixed = $derived(columns.some((c) => c.fixed));
1088
- // 固定列时 table 的最小总宽(列宽和 + 前置列),撑过容器以触发横滚
1089
- const totalMinWidth = $derived(
1090
- leadingWidth +
1091
- columns.reduce((sum, c, i) => {
1092
- const w = resolveWidth(c, i);
1093
- return sum + (typeof w === 'number' ? w : 120);
1094
- }, 0),
1095
- );
1096
- const tableStyle = $derived(hasFixed ? `min-inline-size:${totalMinWidth}px` : undefined);
1097
-
1098
- function colNumWidth(col: ColumnDef<T>, index: number): number {
1099
- const w = resolveWidth(col, index);
1100
- return typeof w === 'number' ? w : 0;
1101
- }
1102
-
1103
- // 每个数据列的 left 偏移(左固定列):前置宽 + 之前所有左固定列宽之和
1104
- const fixedLeftOffsets = $derived.by(() => {
1105
- const out: (number | null)[] = [];
1106
- let acc = leadingWidth;
1107
- for (let i = 0; i < columns.length; i++) {
1108
- const col = columns[i] as ColumnDef<T>;
1109
- if (col.fixed === 'left') {
1110
- out.push(acc);
1111
- acc += colNumWidth(col, i);
1112
- } else {
1113
- out.push(null);
1114
- }
1115
- }
1116
- return out;
1117
- });
1118
- // 每个数据列的 right 偏移(右固定列):之后所有右固定列宽之和
1119
- const fixedRightOffsets = $derived.by(() => {
1120
- const out: (number | null)[] = new Array(columns.length).fill(null);
1121
- let acc = 0;
1122
- for (let i = columns.length - 1; i >= 0; i--) {
1123
- const col = columns[i] as ColumnDef<T>;
1124
- if (col.fixed === 'right') {
1125
- out[i] = acc;
1126
- acc += colNumWidth(col, i);
1127
- }
1128
- }
1129
- return out;
1130
- });
1131
- // 最后一个左固定列 / 第一个右固定列索引(用于阴影边界)
1132
- const lastLeftFixed = $derived.by(() => {
1133
- let idx = -1;
1134
- columns.forEach((c, i) => {
1135
- if (c.fixed === 'left') idx = i;
1136
- });
1137
- return idx;
1138
- });
1139
- const firstRightFixed = $derived(columns.findIndex((c) => c.fixed === 'right'));
1140
-
1141
- // 组合某数据列的 sticky 行内样式(含宽度)
1142
- function cellStyle(col: ColumnDef<T>, i: number): string | undefined {
1143
- const parts: string[] = [];
1144
- const w = widthStyle(col, i);
1145
- if (w) parts.push(w);
1146
- const left = fixedLeftOffsets[i];
1147
- const right = fixedRightOffsets[i];
1148
- if (left != null) parts.push(`position:sticky`, `inset-inline-start:${left}px`);
1149
- else if (right != null) parts.push(`position:sticky`, `inset-inline-end:${right}px`);
1150
- return parts.length ? parts.join(';') : undefined;
1151
- }
1152
-
1153
- function fixedCellClass(i: number): string {
1154
- if (fixedLeftOffsets[i] != null) {
1155
- return `cd-table__cell--fixed cd-table__cell--fixed-left${i === lastLeftFixed ? ' cd-table__cell--fixed-left-last' : ''}`;
1156
- }
1157
- if (fixedRightOffsets[i] != null) {
1158
- return `cd-table__cell--fixed cd-table__cell--fixed-right${i === firstRightFixed ? ' cd-table__cell--fixed-right-first' : ''}`;
1159
- }
1160
- return '';
1161
- }
1162
-
1163
- // 前置 leading 列在存在左固定列时也需 sticky 锁定在最左
1164
- function leadingStyle(slot: 'expand' | 'selection'): string | undefined {
1165
- if (!hasFixed || lastLeftFixed < 0) return undefined;
1166
- const offset = slot === 'expand' ? 0 : hasExpand ? LEADING_W : 0;
1167
- return `position:sticky;inset-inline-start:${offset}px`;
1168
- }
1169
- const leadingFixedClass = $derived(hasFixed && lastLeftFixed >= 0 ? 'cd-table__cell--fixed cd-table__cell--fixed-left' : '');
1170
-
1171
- // --- 半选 indeterminate:用 attachment 命令式写具体 input 元素属性。
1172
- // 仅读派生布尔值并写 DOM,不读几何,写属性不触发响应式,无循环风险 (不违反红线 #3)。
1173
- function indeterminate(value: boolean) {
1174
- return (node: HTMLInputElement) => {
1175
- node.indeterminate = value;
1176
- };
1177
- }
1178
-
1179
- const cls = $derived(
1180
- [
1181
- 'cd-table',
1182
- `cd-table--${size}`,
1183
- bordered && 'cd-table--bordered',
1184
- stripe && 'cd-table--stripe',
1185
- hasFixed && 'cd-table--fixed',
1186
- ]
1187
- .filter(Boolean)
1188
- .join(' '),
1189
- );
1190
-
1191
- // --- scroll prop: wrapper container style ---
1192
- // scroll.y → max-height + overflow-y:auto; scroll.x → min-width on table
1193
- const scrollWrapStyle = $derived.by(() => {
1194
- const parts: string[] = [];
1195
- if (virtualized || scrollBody) {
1196
- parts.push(`block-size:${height}px`, 'overflow:auto');
1197
- }
1198
- if (scroll?.y != null) {
1199
- const yVal = typeof scroll.y === 'number' ? `${scroll.y}px` : scroll.y;
1200
- parts.push(`max-block-size:${yVal}`, 'overflow-y:auto');
1201
- }
1202
- if (scroll?.x != null) {
1203
- parts.push('overflow-x:auto');
1204
- }
1205
- return parts.length ? parts.join(';') : undefined;
1206
- });
1207
-
1208
- const scrollTableStyle = $derived.by(() => {
1209
- const parts: string[] = [];
1210
- if (tableStyle) parts.push(tableStyle);
1211
- if (scroll?.x != null) {
1212
- const xVal = typeof scroll.x === 'number' ? `${scroll.x}px` : scroll.x;
1213
- parts.push(`min-inline-size:${xVal}`);
1214
- }
1215
- return parts.length ? parts.join(';') : undefined;
1216
- });
1217
-
1218
- // --- sticky prop: thead top offset ---
1219
- const stickyOffset = $derived.by((): number => {
1220
- if (!sticky) return 0;
1221
- if (typeof sticky === 'object' && sticky.offsetHeader != null) return sticky.offsetHeader;
1222
- return 0;
1223
- });
1224
- const isStickyHead = $derived(!!sticky || virtualized || scrollBody || scroll?.y != null);
1225
-
1226
- // --- selection column width style ---
1227
- const selectionColStyle = $derived.by((): string | undefined => {
1228
- const parts: string[] = [];
1229
- if (rowSelection?.columnWidth != null) {
1230
- const w = rowSelection.columnWidth;
1231
- parts.push(`inline-size:${typeof w === 'number' ? `${w}px` : w}`);
1232
- }
1233
- if (rowSelection?.fixed) {
1234
- // fixed selection column — sticky at start of row
1235
- parts.push('position:sticky', 'inset-inline-start:0');
1236
- }
1237
- return parts.length ? parts.join(';') : undefined;
1238
- });
1239
- const selectionFixedClass = $derived(rowSelection?.fixed ? 'cd-table__cell--fixed cd-table__cell--fixed-left' : '');
1240
-
1241
- // --- groupBy: build grouped display rows ---
1242
- type GroupRow = { type: 'group'; groupKey: string; group: T[] };
1243
- type DataDisplayRow = FlatRow<T> & { type: 'data' };
1244
- type RenderRow = DataDisplayRow | GroupRow;
1245
-
1246
- const groupedDisplayRows = $derived.by<RenderRow[]>(() => {
1247
- if (!groupBy) {
1248
- return displayRows.map((r) => ({ ...r, type: 'data' as const }));
1249
- }
1250
- const getGroupKey = (record: T): string => {
1251
- if (typeof groupBy === 'function') return groupBy(record);
1252
- return String(record[groupBy as string] ?? '');
1253
- };
1254
- // Build ordered group buckets (only top-level rows are grouped)
1255
- const groupOrder: string[] = [];
1256
- const groupMap = new Map<string, T[]>();
1257
- for (const row of displayRows) {
1258
- if (row.level === 0) {
1259
- const gk = getGroupKey(row.record);
1260
- if (!groupMap.has(gk)) {
1261
- groupOrder.push(gk);
1262
- groupMap.set(gk, []);
1263
- }
1264
- groupMap.get(gk)!.push(row.record);
1265
- }
1266
- }
1267
- const result: RenderRow[] = [];
1268
- for (const gk of groupOrder) {
1269
- const group = groupMap.get(gk)!;
1270
- result.push({ type: 'group', groupKey: gk, group });
1271
- // Include all displayRows belonging to this group (incl. tree children)
1272
- let inGroup = false;
1273
- for (const row of displayRows) {
1274
- if (row.level === 0) {
1275
- inGroup = getGroupKey(row.record) === gk;
1276
- }
1277
- if (inGroup) result.push({ ...row, type: 'data' as const });
1278
- }
1279
- }
1280
- return result;
1281
- });
1282
-
1283
- // For render purposes when no groupBy: cast displayRows to RenderRow[]
1284
- const isGrouped = $derived(groupBy !== undefined);
1285
- </script>
1286
-
1287
- {#if titleSnippet}
1288
- <div class="cd-table__title-area">
1289
- {@render titleSnippet()}
1290
- </div>
1291
- {/if}
1292
- <div
1293
- class="cd-table-wrap"
1294
- class:cd-table-wrap--virtual={virtualized}
1295
- class:cd-table-wrap--scroll-body={scrollBody}
1296
- bind:this={scrollEl}
1297
- style={scrollWrapStyle}
1298
- >
1299
- <!-- svelte-ignore a11y_no_noninteractive_tabindex -->
1300
- <!-- role=grid 是交互容器;tabindex=-1 仅作虚拟化焦点回收落点,不进 Tab 序列 -->
1301
- <table
1302
- bind:this={gridEl}
1303
- class={cls}
1304
- style={scrollTableStyle}
1305
- aria-label={ariaLabel}
1306
- role={gridEnabled ? 'grid' : undefined}
1307
- aria-rowcount={gridEnabled ? gridRowCount : undefined}
1308
- aria-colcount={gridEnabled ? gridColCount : undefined}
1309
- tabindex={gridEnabled ? -1 : undefined}
1310
- onkeydown={gridEnabled ? onGridKeydown : undefined}
1311
- >
1312
- {#if showHeader}
1313
- {@const headerRowProps = onHeaderRow ? onHeaderRow(columns, 0) : undefined}
1314
- <thead
1315
- class="cd-table__head"
1316
- class:cd-table__head--sticky={isStickyHead}
1317
- style={isStickyHead && stickyOffset > 0 ? `top:${stickyOffset}px` : undefined}
1318
- >
1319
- <tr
1320
- role={gridEnabled ? 'row' : undefined}
1321
- aria-rowindex={gridEnabled ? 1 : undefined}
1322
- class={headerRowProps?.className ?? undefined}
1323
- style={headerRowProps?.style ?? undefined}
1324
- onclick={headerRowProps?.onClick ?? undefined}
1325
- >
1326
- {#if hasExpand}
1327
- {@const gc = 0}
1328
- <th
1329
- class="cd-table__cell cd-table__cell--expand {leadingFixedClass}"
1330
- scope="col"
1331
- style={leadingStyle('expand')}
1332
- role={gridEnabled ? 'columnheader' : undefined}
1333
- id={gridEnabled ? cellId(-1, gc) : undefined}
1334
- tabindex={rovingTabindex(-1, gc)}
1335
- aria-colindex={gridEnabled ? gc + 1 : undefined}
1336
- onfocusin={gridEnabled ? () => syncFocusCoord(-1, gc) : undefined}
1337
- ></th>
1338
- {/if}
1339
- {#if hasSelection}
1340
- {@const gc = hasExpand ? 1 : 0}
1341
- {@const isRadio = rowSelection?.type === 'radio'}
1342
- {@const showSelectAll = !isRadio && !rowSelection?.hideSelectAll}
1343
- <th
1344
- class="cd-table__cell cd-table__cell--selection {selectionFixedClass || leadingFixedClass}"
1345
- scope="col"
1346
- style={selectionColStyle ?? leadingStyle('selection')}
1347
- role={gridEnabled ? 'columnheader' : undefined}
1348
- id={gridEnabled ? cellId(-1, gc) : undefined}
1349
- tabindex={rovingTabindex(-1, gc)}
1350
- aria-colindex={gridEnabled ? gc + 1 : undefined}
1351
- onfocusin={gridEnabled ? () => syncFocusCoord(-1, gc) : undefined}
1352
- >
1353
- {#if showSelectAll}
1354
- <input
1355
- type="checkbox"
1356
- class="cd-table__checkbox"
1357
- aria-label={loc().t('Table.selectAll')}
1358
- checked={headerSelect.checked}
1359
- tabindex={childTabindex(-1, gc)}
1360
- {@attach indeterminate(headerSelect.indeterminate)}
1361
- onchange={onToggleAll}
1362
- />
1363
- {/if}
1364
- </th>
1365
- {/if}
1366
- {#each columns as col, i (colKeyOf(col, i))}
1367
- {@const gc = (hasExpand ? 1 : 0) + (hasSelection ? 1 : 0) + i}
1368
- {@const sortable = !!col.sorter}
1369
- {@const colKey = colKeyOf(col, i)}
1370
- {@const hasFilter = !!col.filters && col.filters.length > 0}
1371
- {@const resizable = !!col.resizable}
1372
- <th
1373
- class="cd-table__cell cd-table__cell--head cd-table__cell--{alignOf(col)} {fixedCellClass(i)}"
1374
- class:cd-table__cell--ellipsis={col.ellipsis}
1375
- class:cd-table__cell--has-filter={hasFilter}
1376
- class:cd-table__cell--resizable={resizable}
1377
- class:cd-table__cell--resizing={resizingKey === colKey}
1378
- scope="col"
1379
- style={cellStyle(col, i)}
1380
- aria-sort={sortable ? ariaSortFor(col, i) : undefined}
1381
- role={gridEnabled ? 'columnheader' : undefined}
1382
- id={gridEnabled ? cellId(-1, gc) : undefined}
1383
- tabindex={rovingTabindex(-1, gc)}
1384
- aria-colindex={gridEnabled ? gc + 1 : undefined}
1385
- onfocusin={gridEnabled ? () => syncFocusCoord(-1, gc) : undefined}
1386
- >
1387
- {#if sortable}
1388
- {@const order = col.sortOrder !== undefined ? col.sortOrder : (currentSort.key === colKeyOf(col, i) ? currentSort.order : null)}
1389
- <button
1390
- type="button"
1391
- class="cd-table__sort-btn"
1392
- tabindex={childTabindex(-1, gc)}
1393
- onclick={() => onSort(col, i)}
1394
- >
1395
- <span class="cd-table__title">{col.title}</span>
1396
- <span class="cd-table__sort-icons" aria-hidden="true">
1397
- <svg
1398
- class="cd-table__sort-up"
1399
- class:cd-table__sort--active={order === 'ascend'}
1400
- viewBox="0 0 8 5"
1401
- width="8"
1402
- height="5"
1403
- focusable="false"
1404
- >
1405
- <path fill="currentColor" d="M4 0l4 5H0z" />
1406
- </svg>
1407
- <svg
1408
- class="cd-table__sort-down"
1409
- class:cd-table__sort--active={order === 'descend'}
1410
- viewBox="0 0 8 5"
1411
- width="8"
1412
- height="5"
1413
- focusable="false"
1414
- >
1415
- <path fill="currentColor" d="M4 5L0 0h8z" />
1416
- </svg>
1417
- </span>
1418
- </button>
1419
- {:else}
1420
- <span class="cd-table__title">{col.title}</span>
1421
- {/if}
1422
-
1423
- {#if hasFilter}
1424
- <button
1425
- type="button"
1426
- class="cd-table__filter-btn"
1427
- class:cd-table__filter-btn--active={isEffectivelyFiltered(col, colKey)}
1428
- aria-label={loc().t('Table.filter')}
1429
- aria-expanded={openFilterKey === colKey}
1430
- tabindex={childTabindex(-1, gc)}
1431
- bind:this={filterTriggers[colKey]}
1432
- onclick={(e) => {
1433
- e.stopPropagation();
1434
- openFilterKey = openFilterKey === colKey ? null : colKey;
1435
- }}
1436
- >
1437
- <svg viewBox="0 0 16 16" width="12" height="12" aria-hidden="true" focusable="false">
1438
- <path fill="currentColor" d="M2 3h12l-4.5 5.5V13L6.5 11V8.5L2 3Z" />
1439
- </svg>
1440
- </button>
1441
- {#if openFilterKey === colKey && filterTriggers[colKey]}
1442
- {@const filterMultiple = col.filterMultiple !== false}
1443
- <div
1444
- class="cd-table__filter-panel"
1445
- use:floating={{ trigger: filterTriggers[colKey], placement: 'bottomEnd', autoAdjust: true, offset: 4, getContainer: getPopupContainer }}
1446
- bind:this={filterPanelEl}
1447
- >
1448
- <ul class="cd-table__filter-list">
1449
- {#each col.filters ?? [] as f (f.value)}
1450
- <li class="cd-table__filter-option">
1451
- <label class="cd-table__filter-label">
1452
- {#if filterMultiple}
1453
- <input
1454
- type="checkbox"
1455
- checked={activeFilterValues(colKey).has(f.value)}
1456
- onchange={() => toggleFilterValue(colKey, f.value)}
1457
- />
1458
- {:else}
1459
- <input
1460
- type="radio"
1461
- name="cd-filter-{colKey}"
1462
- checked={activeFilterValues(colKey).has(f.value)}
1463
- onchange={() => {
1464
- filterState.set(colKey, new Set([f.value]));
1465
- emitFilterChange(colKey, [f.value]);
1466
- }}
1467
- />
1468
- {/if}
1469
- <span>{f.text}</span>
1470
- </label>
1471
- </li>
1472
- {/each}
1473
- </ul>
1474
- <div class="cd-table__filter-actions">
1475
- <button type="button" class="cd-table__filter-reset" onclick={() => resetFilter(colKey)}>
1476
- {loc().t('Table.filterReset')}
1477
- </button>
1478
- <button type="button" class="cd-table__filter-confirm" onclick={() => (openFilterKey = null)}>
1479
- {loc().t('Table.filterConfirm')}
1480
- </button>
1481
- </div>
1482
- </div>
1483
- {/if}
1484
- {/if}
1485
-
1486
- {#if resizable}
1487
- <span
1488
- class="cd-table__resize-handle"
1489
- role="separator"
1490
- aria-orientation="vertical"
1491
- aria-label={loc().t('Table.resizeColumn')}
1492
- bind:this={resizeHandles[colKey]}
1493
- onpointerdown={(e) => startResize(e, col, i)}
1494
- ></span>
1495
- {/if}
1496
- </th>
1497
- {/each}
1498
- </tr>
1499
- </thead>
1500
- {/if}
1501
- <tbody class="cd-table__body">
1502
- {#if visibleRows.length === 0}
1503
- <tr class="cd-table__row cd-table__row--empty" role={gridEnabled ? 'row' : undefined}>
1504
- <td
1505
- class="cd-table__cell cd-table__cell--empty"
1506
- colspan={colSpan}
1507
- role={gridEnabled ? 'gridcell' : undefined}
1508
- aria-colindex={gridEnabled ? 1 : undefined}
1509
- >
1510
- {empty ?? loc().t('Table.emptyText')}
1511
- </td>
1512
- </tr>
1513
- {:else}
1514
- {#if isGrouped}
1515
- {#each groupedDisplayRows as groupRow (groupRow.type === 'group' ? `__group__${(groupRow as GroupRow).groupKey}` : (groupRow as DataDisplayRow).key)}
1516
- {#if groupRow.type === 'group'}
1517
- {@const gRow = groupRow as GroupRow}
1518
- <tr class="cd-table__row cd-table__row--group-header" role={gridEnabled ? 'row' : undefined}>
1519
- <td class="cd-table__cell cd-table__cell--group-header" colspan={colSpan}>
1520
- {#if renderGroupSection}
1521
- {@render renderGroupSection({ groupKey: gRow.groupKey, group: gRow.group })}
1522
- {:else}
1523
- {gRow.groupKey}
1524
- {/if}
1525
- </td>
1526
- </tr>
1527
- {:else}
1528
- {@const row = groupRow as DataDisplayRow}
1529
- {@const record = row.record}
1530
- {@const key = row.key}
1531
- {@const index = row.topIndex}
1532
- {@const gridRow = index}
1533
- {@const selected = treeCheckable ? conducted.checked.has(key) : selectedSet.has(key)}
1534
- {@const rowHalf = treeCheckable && conducted.half.has(key)}
1535
- {@const rowDisabled = disabledSet.has(key)}
1536
- {@const extra = rowClassName ? rowClassName(record, index) : ''}
1537
- {@const clickable = !!onRowClick || expandRowByClick}
1538
- {@const rowProps = onRow ? onRow(record, index) : undefined}
1539
- <tr
1540
- class="cd-table__row {extra} {rowProps?.className ?? ''}"
1541
- class:cd-table__row--selected={selected}
1542
- class:cd-table__row--stripe={stripe && index % 2 === 1}
1543
- class:cd-table__row--clickable={clickable}
1544
- class:cd-table__row--child={treeEnabled && row.level > 0}
1545
- role={gridEnabled ? 'row' : undefined}
1546
- style={rowProps?.style ?? undefined}
1547
- onclick={(e) => {
1548
- if (expandRowByClick && hasExpand && canExpand(record)) toggleExpand(record);
1549
- if (expandRowByClick && treeEnabled && row.hasChildren) toggleTreeExpand(record);
1550
- if (onRowClick) onRowClick({ record, index });
1551
- if (rowProps?.onClick) rowProps.onClick(e);
1552
- }}
1553
- ondblclick={rowProps?.onDoubleClick ?? undefined}
1554
- >
1555
- {#if hasExpand}
1556
- {@const gc = 0}
1557
- <td
1558
- class="cd-table__cell cd-table__cell--expand {leadingFixedClass}"
1559
- style={leadingStyle('expand')}
1560
- role={gridEnabled ? 'gridcell' : undefined}
1561
- >
1562
- {#if canExpand(record)}
1563
- <button
1564
- type="button"
1565
- class="cd-table__expand-btn"
1566
- class:cd-table__expand-btn--open={expandedSet.has(key)}
1567
- aria-expanded={expandedSet.has(key)}
1568
- aria-label={expandedSet.has(key) ? loc().t('Table.collapseRow') : loc().t('Table.expandRow')}
1569
- onclick={(e) => { e.stopPropagation(); toggleExpand(record); }}
1570
- >
1571
- <svg viewBox="0 0 16 16" width="12" height="12" aria-hidden="true" focusable="false">
1572
- <path fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" d="M6 4l4 4-4 4" />
1573
- </svg>
1574
- </button>
1575
- {/if}
1576
- </td>
1577
- {/if}
1578
- {#if hasSelection}
1579
- {@const isRadio = rowSelection?.type === 'radio'}
1580
- <td
1581
- class="cd-table__cell cd-table__cell--selection {selectionFixedClass || leadingFixedClass}"
1582
- style={selectionColStyle ?? leadingStyle('selection')}
1583
- role={gridEnabled ? 'gridcell' : undefined}
1584
- >
1585
- {#if isRadio}
1586
- <input
1587
- type="radio"
1588
- class="cd-table__checkbox"
1589
- aria-label={loc().t('Table.selectRow')}
1590
- checked={selected}
1591
- disabled={rowDisabled}
1592
- onclick={(e) => e.stopPropagation()}
1593
- onchange={() => onToggleRow(record)}
1594
- />
1595
- {:else}
1596
- <input
1597
- type="checkbox"
1598
- class="cd-table__checkbox"
1599
- aria-label={loc().t('Table.selectRow')}
1600
- checked={selected}
1601
- disabled={rowDisabled}
1602
- {@attach indeterminate(rowHalf)}
1603
- onclick={(e) => e.stopPropagation()}
1604
- onchange={() => onToggleRow(record)}
1605
- />
1606
- {/if}
1607
- </td>
1608
- {/if}
1609
- {#each columns as col, i (colKeyOf(col, i))}
1610
- {@const value = cellValue(col, record)}
1611
- <td
1612
- class="cd-table__cell cd-table__cell--{alignOf(col)} {fixedCellClass(i)}"
1613
- class:cd-table__cell--ellipsis={col.ellipsis}
1614
- style={cellStyle(col, i)}
1615
- >
1616
- {#if treeEnabled && i === 0}
1617
- <span class="cd-table__tree-indent" style="inline-size:{row.level * indentSize}px" aria-hidden="true"></span>
1618
- {#if row.hasChildren}
1619
- <button
1620
- type="button"
1621
- class="cd-table__tree-toggle"
1622
- class:cd-table__tree-toggle--open={treeExpandedSet.has(key)}
1623
- aria-expanded={treeExpandedSet.has(key)}
1624
- aria-label={treeExpandedSet.has(key) ? loc().t('Table.collapseRow') : loc().t('Table.expandRow')}
1625
- onclick={(e) => { e.stopPropagation(); toggleTreeExpand(record); }}
1626
- >
1627
- <svg viewBox="0 0 16 16" width="12" height="12" aria-hidden="true" focusable="false">
1628
- <path fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" d="M6 4l4 4-4 4" />
1629
- </svg>
1630
- </button>
1631
- {:else}
1632
- <span class="cd-table__tree-toggle cd-table__tree-toggle--placeholder" aria-hidden="true"></span>
1633
- {/if}
1634
- {/if}
1635
- {#if col.render}
1636
- {@render col.render({ value, record, index })}
1637
- {:else}
1638
- {cellText(value)}
1639
- {/if}
1640
- </td>
1641
- {/each}
1642
- </tr>
1643
- {#if hasExpand && canExpand(record)}
1644
- {#if keepDOM}
1645
- <tr class="cd-table__row cd-table__row--expanded" role={gridEnabled ? 'row' : undefined} style={expandedSet.has(key) ? undefined : 'display:none'}>
1646
- <td class="cd-table__cell cd-table__cell--expanded-content" colspan={colSpan} role={gridEnabled ? 'gridcell' : undefined} aria-colindex={gridEnabled ? 1 : undefined}>
1647
- {@render expandable!.expandedRowRender({ record, index })}
1648
- </td>
1649
- </tr>
1650
- {:else if expandedSet.has(key)}
1651
- <tr class="cd-table__row cd-table__row--expanded" role={gridEnabled ? 'row' : undefined}>
1652
- <td class="cd-table__cell cd-table__cell--expanded-content" colspan={colSpan} role={gridEnabled ? 'gridcell' : undefined} aria-colindex={gridEnabled ? 1 : undefined}>
1653
- {@render expandable!.expandedRowRender({ record, index })}
1654
- </td>
1655
- </tr>
1656
- {/if}
1657
- {/if}
1658
- {/if}
1659
- {/each}
1660
- {:else}
1661
- {#if virtualized && vTopPad > 0}
1662
- <tr class="cd-table__row cd-table__row--spacer" aria-hidden="true">
1663
- <td colspan={colSpan} style="block-size:{vTopPad}px; padding:0; border:0"></td>
1664
- </tr>
1665
- {/if}
1666
- {#each renderRows as row, ri (row.key)}
1667
- {@const record = row.record}
1668
- {@const key = row.key}
1669
- {@const index = row.topIndex}
1670
- {@const gridRow = (virtualized ? vRange.startIndex : 0) + ri}
1671
- {@const selected = treeCheckable ? conducted.checked.has(key) : selectedSet.has(key)}
1672
- {@const rowHalf = treeCheckable && conducted.half.has(key)}
1673
- {@const rowDisabled = disabledSet.has(key)}
1674
- {@const extra = rowClassName ? rowClassName(record, index) : ''}
1675
- {@const clickable = !!onRowClick || expandRowByClick}
1676
- {@const rowProps = onRow ? onRow(record, index) : undefined}
1677
- <tr
1678
- class="cd-table__row {extra} {rowProps?.className ?? ''}"
1679
- class:cd-table__row--selected={selected}
1680
- class:cd-table__row--stripe={stripe && index % 2 === 1}
1681
- class:cd-table__row--clickable={clickable}
1682
- class:cd-table__row--child={treeEnabled && row.level > 0}
1683
- role={gridEnabled ? 'row' : undefined}
1684
- aria-rowindex={gridEnabled ? gridRow + 2 : undefined}
1685
- aria-selected={gridEnabled && hasSelection ? selected : undefined}
1686
- style={rowProps?.style ?? undefined}
1687
- onclick={(e) => {
1688
- if (expandRowByClick && hasExpand && canExpand(record)) toggleExpand(record);
1689
- if (expandRowByClick && treeEnabled && row.hasChildren) toggleTreeExpand(record);
1690
- if (onRowClick) onRowClick({ record, index });
1691
- if (rowProps?.onClick) rowProps.onClick(e);
1692
- }}
1693
- ondblclick={rowProps?.onDoubleClick ?? undefined}
1694
- >
1695
- {#if hasExpand}
1696
- {@const gc = 0}
1697
- <td
1698
- class="cd-table__cell cd-table__cell--expand {leadingFixedClass}"
1699
- style={leadingStyle('expand')}
1700
- role={gridEnabled ? 'gridcell' : undefined}
1701
- id={gridEnabled ? cellId(gridRow, gc) : undefined}
1702
- tabindex={rovingTabindex(gridRow, gc)}
1703
- aria-colindex={gridEnabled ? gc + 1 : undefined}
1704
- onfocusin={gridEnabled ? () => syncFocusCoord(gridRow, gc) : undefined}
1705
- >
1706
- {#if canExpand(record)}
1707
- <button
1708
- type="button"
1709
- class="cd-table__expand-btn"
1710
- class:cd-table__expand-btn--open={expandedSet.has(key)}
1711
- aria-expanded={expandedSet.has(key)}
1712
- aria-label={expandedSet.has(key) ? loc().t('Table.collapseRow') : loc().t('Table.expandRow')}
1713
- tabindex={childTabindex(gridRow, gc)}
1714
- onclick={(e) => {
1715
- e.stopPropagation();
1716
- toggleExpand(record);
1717
- }}
1718
- >
1719
- <svg viewBox="0 0 16 16" width="12" height="12" aria-hidden="true" focusable="false">
1720
- <path fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" d="M6 4l4 4-4 4" />
1721
- </svg>
1722
- </button>
1723
- {/if}
1724
- </td>
1725
- {/if}
1726
- {#if hasSelection}
1727
- {@const gc = hasExpand ? 1 : 0}
1728
- {@const isRadio = rowSelection?.type === 'radio'}
1729
- <td
1730
- class="cd-table__cell cd-table__cell--selection {selectionFixedClass || leadingFixedClass}"
1731
- style={selectionColStyle ?? leadingStyle('selection')}
1732
- role={gridEnabled ? 'gridcell' : undefined}
1733
- id={gridEnabled ? cellId(gridRow, gc) : undefined}
1734
- tabindex={rovingTabindex(gridRow, gc)}
1735
- aria-colindex={gridEnabled ? gc + 1 : undefined}
1736
- onfocusin={gridEnabled ? () => syncFocusCoord(gridRow, gc) : undefined}
1737
- >
1738
- {#if isRadio}
1739
- <input
1740
- type="radio"
1741
- class="cd-table__checkbox"
1742
- aria-label={loc().t('Table.selectRow')}
1743
- checked={selected}
1744
- disabled={rowDisabled}
1745
- tabindex={childTabindex(gridRow, gc)}
1746
- onclick={(e) => e.stopPropagation()}
1747
- onchange={() => onToggleRow(record)}
1748
- />
1749
- {:else}
1750
- <input
1751
- type="checkbox"
1752
- class="cd-table__checkbox"
1753
- aria-label={loc().t('Table.selectRow')}
1754
- checked={selected}
1755
- disabled={rowDisabled}
1756
- tabindex={childTabindex(gridRow, gc)}
1757
- {@attach indeterminate(rowHalf)}
1758
- onclick={(e) => e.stopPropagation()}
1759
- onchange={() => onToggleRow(record)}
1760
- />
1761
- {/if}
1762
- </td>
1763
- {/if}
1764
- {#each columns as col, i (colKeyOf(col, i))}
1765
- {@const value = cellValue(col, record)}
1766
- {@const gc = (hasExpand ? 1 : 0) + (hasSelection ? 1 : 0) + i}
1767
- {@const isRowHeader = gridEnabled && i === 0 && !hasSelection && !hasExpand}
1768
- <td
1769
- class="cd-table__cell cd-table__cell--{alignOf(col)} {fixedCellClass(i)}"
1770
- class:cd-table__cell--ellipsis={col.ellipsis}
1771
- style={cellStyle(col, i)}
1772
- role={gridEnabled ? (isRowHeader ? 'rowheader' : 'gridcell') : undefined}
1773
- id={gridEnabled ? cellId(gridRow, gc) : undefined}
1774
- tabindex={rovingTabindex(gridRow, gc)}
1775
- aria-colindex={gridEnabled ? gc + 1 : undefined}
1776
- onfocusin={gridEnabled ? () => syncFocusCoord(gridRow, gc) : undefined}
1777
- >
1778
- {#if treeEnabled && i === 0}
1779
- <span class="cd-table__tree-indent" style="inline-size:{row.level * indentSize}px" aria-hidden="true"></span>
1780
- {#if row.hasChildren}
1781
- <button
1782
- type="button"
1783
- class="cd-table__tree-toggle"
1784
- class:cd-table__tree-toggle--open={treeExpandedSet.has(key)}
1785
- aria-expanded={treeExpandedSet.has(key)}
1786
- aria-label={treeExpandedSet.has(key) ? loc().t('Table.collapseRow') : loc().t('Table.expandRow')}
1787
- tabindex={childTabindex(gridRow, gc)}
1788
- onclick={(e) => {
1789
- e.stopPropagation();
1790
- toggleTreeExpand(record);
1791
- }}
1792
- >
1793
- <svg viewBox="0 0 16 16" width="12" height="12" aria-hidden="true" focusable="false">
1794
- <path fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" d="M6 4l4 4-4 4" />
1795
- </svg>
1796
- </button>
1797
- {:else}
1798
- <span class="cd-table__tree-toggle cd-table__tree-toggle--placeholder" aria-hidden="true"></span>
1799
- {/if}
1800
- {/if}
1801
- {#if col.render}
1802
- {@render col.render({ value, record, index })}
1803
- {:else}
1804
- {cellText(value)}
1805
- {/if}
1806
- </td>
1807
- {/each}
1808
- </tr>
1809
- {#if hasExpand && canExpand(record)}
1810
- {#if keepDOM}
1811
- <tr
1812
- class="cd-table__row cd-table__row--expanded"
1813
- role={gridEnabled ? 'row' : undefined}
1814
- style={expandedSet.has(key) ? undefined : 'display:none'}
1815
- >
1816
- <td
1817
- class="cd-table__cell cd-table__cell--expanded-content"
1818
- colspan={colSpan}
1819
- role={gridEnabled ? 'gridcell' : undefined}
1820
- aria-colindex={gridEnabled ? 1 : undefined}
1821
- >
1822
- {@render expandable!.expandedRowRender({ record, index })}
1823
- </td>
1824
- </tr>
1825
- {:else if expandedSet.has(key)}
1826
- <tr class="cd-table__row cd-table__row--expanded" role={gridEnabled ? 'row' : undefined}>
1827
- <td
1828
- class="cd-table__cell cd-table__cell--expanded-content"
1829
- colspan={colSpan}
1830
- role={gridEnabled ? 'gridcell' : undefined}
1831
- aria-colindex={gridEnabled ? 1 : undefined}
1832
- >
1833
- {@render expandable!.expandedRowRender({ record, index })}
1834
- </td>
1835
- </tr>
1836
- {/if}
1837
- {/if}
1838
- {/each}
1839
- {#if virtualized && vBottomPad > 0}
1840
- <tr class="cd-table__row cd-table__row--spacer" aria-hidden="true">
1841
- <td colspan={colSpan} style="block-size:{vBottomPad}px; padding:0; border:0"></td>
1842
- </tr>
1843
- {/if}
1844
- {/if}
1845
- {/if}
1846
- </tbody>
1847
- </table>
1848
-
1849
- {#if paginationEnabled && total > 0}
1850
- <div class="cd-table__pagination">
1851
- <Pagination
1852
- {total}
1853
- currentPage={currentPage}
1854
- {pageSize}
1855
- size={size === 'large' ? 'default' : size}
1856
- onChange={onPageChange}
1857
- />
1858
- </div>
1859
- {/if}
1860
-
1861
- {#if loading}
1862
- <div class="cd-table__loading" aria-hidden="true">
1863
- <span class="cd-table__spinner"></span>
1864
- </div>
1865
- {/if}
1866
- </div>
1867
-
1868
- {#if footerSnippet}
1869
- <div class="cd-table__footer">
1870
- {@render footerSnippet({ currentData: visibleRows })}
1871
- </div>
1872
- {/if}
1873
-
1874
- <style>
1875
- .cd-table-wrap {
1876
- position: relative;
1877
- inline-size: 100%;
1878
- overflow-x: auto;
1879
- }
1880
-
1881
- /* 行虚拟滚动:容器自身纵向滚动,表头 sticky 固定于顶部 */
1882
- .cd-table-wrap--virtual {
1883
- overflow: auto;
1884
- }
1885
- /* 非虚拟化的可滚动表体(onScroll/onReachBottom):约束高度纵向滚动,表头 sticky */
1886
- .cd-table-wrap--scroll-body {
1887
- overflow: auto;
1888
- }
1889
- .cd-table__head--sticky th {
1890
- position: sticky;
1891
- inset-block-start: 0;
1892
- /* 高于固定列单元格(z-index:3),确保横向固定列表头不盖过纵向 sticky 表头 */
1893
- z-index: 5;
1894
- }
1895
- /* spacer 占位行无内容、无交互,仅撑高 */
1896
- .cd-table__row--spacer:hover {
1897
- background: transparent;
1898
- }
1899
-
1900
- .cd-table {
1901
- inline-size: 100%;
1902
- border-collapse: collapse;
1903
- background: var(--cd-table-bg);
1904
- color: var(--cd-table-cell-text);
1905
- border-radius: var(--cd-table-radius);
1906
- font-size: var(--cd-font-size-body);
1907
- }
1908
- /* 固定列:用 fixed 布局让列宽精确生效,min-width 撑过容器以触发横滚 */
1909
- .cd-table--fixed {
1910
- inline-size: auto;
1911
- min-inline-size: 100%;
1912
- table-layout: fixed;
1913
- }
1914
-
1915
- .cd-table__cell {
1916
- padding-block: var(--cd-table-cell-padding-y);
1917
- padding-inline: var(--cd-table-cell-padding-x);
1918
- text-align: left;
1919
- vertical-align: middle;
1920
- border-block-end: 1px solid var(--cd-table-border-color);
1921
- }
1922
- .cd-table--small .cd-table__cell {
1923
- padding-block: var(--cd-table-cell-padding-y-small);
1924
- }
1925
- .cd-table--large .cd-table__cell {
1926
- padding-block: var(--cd-table-cell-padding-y-large);
1927
- }
1928
-
1929
- .cd-table__cell--head {
1930
- color: var(--cd-table-header-text);
1931
- background: var(--cd-table-header-bg);
1932
- font-weight: 600;
1933
- }
1934
- .cd-table__head th {
1935
- background: var(--cd-table-header-bg);
1936
- color: var(--cd-table-header-text);
1937
- font-weight: 600;
1938
- }
1939
-
1940
- .cd-table__cell--center {
1941
- text-align: center;
1942
- }
1943
- .cd-table__cell--right {
1944
- text-align: right;
1945
- }
1946
- .cd-table__cell--selection {
1947
- inline-size: 48px;
1948
- text-align: center;
1949
- white-space: nowrap;
1950
- }
1951
- .cd-table__cell--expand {
1952
- inline-size: 48px;
1953
- white-space: nowrap;
1954
- text-align: center;
1955
- }
1956
- .cd-table__expand-btn {
1957
- display: inline-flex;
1958
- align-items: center;
1959
- justify-content: center;
1960
- padding: 0;
1961
- border: none;
1962
- background: transparent;
1963
- color: var(--cd-color-text-2);
1964
- cursor: pointer;
1965
- transition: transform var(--cd-motion-duration-fast) var(--cd-motion-ease-standard);
1966
- }
1967
- .cd-table__expand-btn:hover {
1968
- color: var(--cd-color-text-0);
1969
- }
1970
- .cd-table__expand-btn:focus-visible {
1971
- outline: none;
1972
- box-shadow: var(--cd-focus-ring);
1973
- border-radius: var(--cd-radius-1);
1974
- }
1975
- .cd-table__expand-btn--open {
1976
- transform: rotate(90deg);
1977
- }
1978
- .cd-table__cell--expanded-content {
1979
- padding: var(--cd-table-cell-padding, var(--cd-spacing-3));
1980
- background: var(--cd-color-fill-0);
1981
- }
1982
- @media (prefers-reduced-motion: reduce) {
1983
- .cd-table__expand-btn {
1984
- transition: none;
1985
- }
1986
- }
1987
- .cd-table__cell--ellipsis {
1988
- max-inline-size: 0;
1989
- overflow: hidden;
1990
- white-space: nowrap;
1991
- text-overflow: ellipsis;
1992
- }
1993
-
1994
- /* --- 树形数据:缩进 + 展开三角 --- */
1995
- .cd-table__tree-indent {
1996
- display: inline-block;
1997
- vertical-align: middle;
1998
- }
1999
- .cd-table__tree-toggle {
2000
- display: inline-flex;
2001
- align-items: center;
2002
- justify-content: center;
2003
- inline-size: 16px;
2004
- block-size: 16px;
2005
- margin-inline-end: var(--cd-spacing-1);
2006
- padding: 0;
2007
- border: none;
2008
- background: transparent;
2009
- color: var(--cd-color-text-2);
2010
- cursor: pointer;
2011
- vertical-align: middle;
2012
- transition: transform var(--cd-motion-duration-fast) var(--cd-motion-ease-standard);
2013
- }
2014
- .cd-table__tree-toggle:hover {
2015
- color: var(--cd-color-text-0);
2016
- }
2017
- .cd-table__tree-toggle:focus-visible {
2018
- outline: none;
2019
- box-shadow: var(--cd-focus-ring);
2020
- border-radius: var(--cd-radius-1);
2021
- }
2022
- .cd-table__tree-toggle--open {
2023
- transform: rotate(90deg);
2024
- }
2025
- .cd-table__tree-toggle--placeholder {
2026
- cursor: default;
2027
- }
2028
- @media (prefers-reduced-motion: reduce) {
2029
- .cd-table__tree-toggle {
2030
- transition: none;
2031
- }
2032
- }
2033
-
2034
- /* --- 固定列:sticky 单元格需不透明背景,避免透出横滚内容 --- */
2035
- .cd-table__cell--fixed {
2036
- z-index: 2;
2037
- background: var(--cd-table-bg);
2038
- }
2039
- .cd-table__head .cd-table__cell--fixed {
2040
- z-index: 3;
2041
- background: var(--cd-table-header-bg);
2042
- }
2043
- /* 固定列随行态变色:hover / stripe / selected 时同步背景 */
2044
- .cd-table__row:hover .cd-table__cell--fixed {
2045
- background: var(--cd-table-row-hover-bg);
2046
- }
2047
- .cd-table__row--stripe .cd-table__cell--fixed {
2048
- background: var(--cd-table-row-stripe-bg);
2049
- }
2050
- .cd-table__row--selected .cd-table__cell--fixed,
2051
- .cd-table__row--selected:hover .cd-table__cell--fixed {
2052
- background: var(--cd-table-row-selected-bg);
2053
- }
2054
- /* 边界阴影:最后一个左固定列右侧、第一个右固定列左侧 */
2055
- .cd-table__cell--fixed-left-last::after,
2056
- .cd-table__cell--fixed-right-first::after {
2057
- content: '';
2058
- position: absolute;
2059
- inset-block: 0;
2060
- inline-size: 6px;
2061
- pointer-events: none;
2062
- }
2063
- .cd-table__cell--fixed-left-last {
2064
- position: sticky;
2065
- }
2066
- .cd-table__cell--fixed-left-last::after {
2067
- inset-inline-end: -6px;
2068
- background: linear-gradient(
2069
- to right,
2070
- var(--cd-table-fixed-shadow, rgba(0, 0, 0, 0.12)),
2071
- transparent
2072
- );
2073
- }
2074
- .cd-table__cell--fixed-right-first::after {
2075
- inset-inline-start: -6px;
2076
- background: linear-gradient(
2077
- to left,
2078
- var(--cd-table-fixed-shadow, rgba(0, 0, 0, 0.12)),
2079
- transparent
2080
- );
2081
- }
2082
- /* sticky cell 需相对定位以承载 ::after 阴影 */
2083
- .cd-table__cell--fixed {
2084
- position: sticky;
2085
- }
2086
-
2087
- .cd-table--bordered .cd-table__cell {
2088
- border-inline-end: 1px solid var(--cd-table-border-color);
2089
- }
2090
- .cd-table--bordered .cd-table__cell:first-child {
2091
- border-inline-start: 1px solid var(--cd-table-border-color);
2092
- }
2093
- .cd-table--bordered .cd-table__head th {
2094
- border-block-start: 1px solid var(--cd-table-border-color);
2095
- }
2096
-
2097
- .cd-table__row--stripe {
2098
- background: var(--cd-table-row-stripe-bg);
2099
- }
2100
- .cd-table__row:hover {
2101
- background: var(--cd-table-row-hover-bg);
2102
- }
2103
- .cd-table__row--selected,
2104
- .cd-table__row--selected:hover {
2105
- background: var(--cd-table-row-selected-bg);
2106
- }
2107
- .cd-table__row--clickable {
2108
- cursor: pointer;
2109
- }
2110
-
2111
- .cd-table__cell--empty {
2112
- padding-block: var(--cd-spacing-6, 32px);
2113
- color: var(--cd-table-empty-color);
2114
- text-align: center;
2115
- }
2116
-
2117
- /* --- 列筛选 --- */
2118
- .cd-table__cell--has-filter {
2119
- position: relative;
2120
- }
2121
- .cd-table__filter-btn {
2122
- display: inline-flex;
2123
- align-items: center;
2124
- justify-content: center;
2125
- margin-inline-start: var(--cd-spacing-1);
2126
- padding: 2px;
2127
- border: none;
2128
- background: transparent;
2129
- color: var(--cd-table-sort-icon-color);
2130
- cursor: pointer;
2131
- border-radius: var(--cd-radius-1);
2132
- vertical-align: middle;
2133
- }
2134
- .cd-table__filter-btn:hover {
2135
- color: var(--cd-color-text-0);
2136
- }
2137
- .cd-table__filter-btn--active {
2138
- color: var(--cd-color-primary);
2139
- }
2140
- .cd-table__filter-btn:focus-visible {
2141
- outline: none;
2142
- box-shadow: var(--cd-focus-ring);
2143
- }
2144
- .cd-table__filter-panel {
2145
- z-index: var(--cd-select-dropdown-z, 1050);
2146
- min-inline-size: 10rem;
2147
- padding-block: var(--cd-spacing-1);
2148
- background: var(--cd-select-dropdown-bg, var(--cd-color-bg-0, #fff));
2149
- border-radius: var(--cd-select-dropdown-radius, 6px);
2150
- box-shadow: var(--cd-select-dropdown-shadow, 0 4px 12px rgba(0, 0, 0, 0.12));
2151
- font-weight: 400;
2152
- }
2153
- .cd-table__filter-list {
2154
- margin: 0;
2155
- padding: 0;
2156
- list-style: none;
2157
- max-block-size: 14rem;
2158
- overflow-y: auto;
2159
- }
2160
- .cd-table__filter-label {
2161
- display: flex;
2162
- align-items: center;
2163
- gap: var(--cd-spacing-2);
2164
- padding: var(--cd-spacing-1) var(--cd-spacing-3);
2165
- cursor: pointer;
2166
- }
2167
- .cd-table__filter-label:hover {
2168
- background: var(--cd-table-row-hover-bg);
2169
- }
2170
- .cd-table__filter-actions {
2171
- display: flex;
2172
- justify-content: space-between;
2173
- gap: var(--cd-spacing-2);
2174
- padding: var(--cd-spacing-1) var(--cd-spacing-3);
2175
- border-block-start: 1px solid var(--cd-table-border-color);
2176
- }
2177
- .cd-table__filter-reset,
2178
- .cd-table__filter-confirm {
2179
- padding: 0;
2180
- border: none;
2181
- background: transparent;
2182
- font: inherit;
2183
- font-size: var(--cd-font-size-1);
2184
- cursor: pointer;
2185
- }
2186
- .cd-table__filter-reset {
2187
- color: var(--cd-color-text-2);
2188
- }
2189
- .cd-table__filter-confirm {
2190
- color: var(--cd-color-primary);
2191
- }
2192
-
2193
- /* --- 列宽拖拽手柄 --- */
2194
- .cd-table__cell--resizable {
2195
- position: relative;
2196
- }
2197
- .cd-table__resize-handle {
2198
- position: absolute;
2199
- inset-block: 0;
2200
- inset-inline-end: 0;
2201
- inline-size: 8px;
2202
- cursor: col-resize;
2203
- /* 提到固定列阴影/内容之上,确保可命中 */
2204
- z-index: 4;
2205
- touch-action: none;
2206
- user-select: none;
2207
- }
2208
- .cd-table__resize-handle::after {
2209
- content: '';
2210
- position: absolute;
2211
- inset-block: 25%;
2212
- inset-inline-end: 3px;
2213
- inline-size: 2px;
2214
- background: transparent;
2215
- transition: background var(--cd-motion-duration-fast) var(--cd-motion-ease-standard);
2216
- }
2217
- .cd-table__resize-handle:hover::after,
2218
- .cd-table__cell--resizing .cd-table__resize-handle::after {
2219
- background: var(--cd-color-primary);
2220
- }
2221
- @media (prefers-reduced-motion: reduce) {
2222
- .cd-table__resize-handle::after {
2223
- transition: none;
2224
- }
2225
- }
2226
-
2227
- .cd-table__sort-btn {
2228
- display: inline-flex;
2229
- align-items: center;
2230
- gap: var(--cd-spacing-1);
2231
- padding: 0;
2232
- color: inherit;
2233
- font: inherit;
2234
- font-weight: 600;
2235
- background: none;
2236
- border: none;
2237
- cursor: pointer;
2238
- }
2239
- .cd-table__sort-btn:focus-visible {
2240
- outline: none;
2241
- box-shadow: var(--cd-focus-ring);
2242
- border-radius: var(--cd-radius-small, 4px);
2243
- }
2244
-
2245
- .cd-table__sort-icons {
2246
- display: inline-flex;
2247
- flex-direction: column;
2248
- gap: 1px;
2249
- color: var(--cd-table-sort-icon-color);
2250
- }
2251
- .cd-table__sort--active {
2252
- color: var(--cd-table-sort-active-color);
2253
- }
2254
-
2255
- .cd-table__checkbox {
2256
- cursor: pointer;
2257
- accent-color: var(--cd-color-primary);
2258
- }
2259
- .cd-table__checkbox:disabled {
2260
- cursor: not-allowed;
2261
- }
2262
- .cd-table__checkbox:focus-visible {
2263
- outline: none;
2264
- box-shadow: var(--cd-focus-ring);
2265
- }
2266
-
2267
- .cd-table__pagination {
2268
- display: flex;
2269
- justify-content: flex-end;
2270
- margin-block-start: var(--cd-spacing-3);
2271
- }
2272
-
2273
- .cd-table__loading {
2274
- position: absolute;
2275
- inset: 0;
2276
- display: flex;
2277
- align-items: center;
2278
- justify-content: center;
2279
- background: var(--cd-table-loading-mask);
2280
- opacity: 0.6;
2281
- }
2282
-
2283
- .cd-table__spinner {
2284
- inline-size: 28px;
2285
- block-size: 28px;
2286
- border: 3px solid var(--cd-color-fill-1, rgba(0, 0, 0, 0.1));
2287
- border-top-color: var(--cd-color-primary);
2288
- border-radius: 50%;
2289
- animation: cd-table-spin 0.8s linear infinite;
2290
- }
2291
-
2292
- @keyframes cd-table-spin {
2293
- to {
2294
- transform: rotate(360deg);
2295
- }
2296
- }
2297
-
2298
- @media (prefers-reduced-motion: reduce) {
2299
- .cd-table__spinner {
2300
- animation: none;
2301
- }
2302
- .cd-table__sort-up,
2303
- .cd-table__sort-down {
2304
- transition: none;
2305
- }
2306
- }
2307
-
2308
- /* --- 分组标题行 --- */
2309
- .cd-table__row--group-header {
2310
- background: var(--cd-table-header-bg);
2311
- }
2312
- .cd-table__cell--group-header {
2313
- padding-block: var(--cd-table-cell-padding-y);
2314
- padding-inline: var(--cd-table-cell-padding-x);
2315
- font-weight: 600;
2316
- color: var(--cd-table-header-text);
2317
- border-block-end: 1px solid var(--cd-table-border-color);
2318
- }
2319
-
2320
- /* --- 表格顶部标题/底部区域 --- */
2321
- .cd-table__title-area {
2322
- margin-block-end: var(--cd-spacing-2, 8px);
2323
- }
2324
- .cd-table__footer {
2325
- margin-block-start: var(--cd-spacing-3, 12px);
2326
- }
2327
- </style>