@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,1570 +0,0 @@
1
- <!--
2
- Cascader — see specs/components/input/Cascader.spec.md
3
- 基础子集: 单选、点击逐级展开级联列、叶子选中。Token-driven, a11y-correct, 受控/非受控。
4
- 面板 portal 到 body + position:fixed(脱离 overflow:hidden 裁剪),flip 避让。
5
- 异步 loadData:点击非叶子且无 children 的节点时调 loadData 动态加载,
6
- 加载中显示 spinner,结果缓存到本地 extraChildren(不改 treeData prop)。
7
- multiple:每列 checkbox 多选 + 父子联动(复用 core conduct/toggleCheck,以 value 为 key),
8
- trigger 按选中叶子路径多 tag 回显可单独移除;value 为 Key[][](多条路径)。
9
- filterable:搜索时切换为扁平路径列表,按 label 链过滤 + 高亮命中,点击直接选中整条路径。
10
- expandTrigger='hover':悬停非叶子节点即展开子级列(pointerenter 设 activePath,选中仍用点击)。
11
- displayRender:自定义触发器选中路径回显(单选 + 多选每个 tag 共用)。
12
- changeOnSelect(单选):点击任一层级节点(含中间非叶子)立即提交从根到该
13
- 节点的路径并触发 onChange;非叶子同时展开子列、不关闭面板(可停在任意层级
14
- 或继续深入),叶子提交并关闭。关闭时仅叶子提交并关闭,非叶子仅展开(默认)。
15
- spec §4 补齐:separator(分隔符)、displayProp(回显字段 label/value)、
16
- maxTagCount(多选 tag 折叠 +N)、leafOnly(多选完全勾选父级折叠为父路径)、
17
- filterTreeNode(filterable 的超集:true=默认子串匹配,函数=自定义谓词)、
18
- filterLeafOnly(搜索是否仅到叶子)、emptyContent(空态 string|Snippet)、
19
- destroyOnClose(关闭保留/卸载浮层 DOM)、zIndex、getPopupContainer(浮层容器)、
20
- columnWidth(列宽 number|number[])。
21
- 新增:borderless/prefix/suffix/clearIcon/expandIcon/motion/keyMaps/remote/
22
- checkRelation/autoMergeValue/showNext/triggerRender/filterRender/filterSorter/
23
- topSlot/bottomSlot/onClear/onLoad/onSelect/onDropdownVisibleChange/onListScroll 等。
24
- -->
25
- <script lang="ts">
26
- import {
27
- useId,
28
- useDismiss,
29
- conduct,
30
- toggleCheck,
31
- resolveColumnWidth,
32
- rovingKeyFromEvent,
33
- nextRovingIndex,
34
- type TreeNodeData,
35
- type CascaderFlatPath,
36
- } from '@chenzy-design/core';
37
- import { SvelteMap, SvelteSet } from 'svelte/reactivity';
38
- import type { Snippet } from 'svelte';
39
- import { useLocale } from '../locale-provider/index.js';
40
- import { getGlobalPopupContainer } from '../config-provider/index.js';
41
- import { floating } from '../_floating/use-floating.js';
42
- import Tag from '../tag/Tag.svelte';
43
- import type { CascaderNode } from './types.js';
44
-
45
- type Key = string | number;
46
- type Size = 'small' | 'default' | 'large';
47
- type Status = 'default' | 'warning' | 'error';
48
-
49
- interface Props {
50
- /** 单选为单条路径 Key[];多选为多条路径 Key[][] */
51
- value?: Key[] | Key[][];
52
- defaultValue?: Key[] | Key[][];
53
- treeData?: CascaderNode[];
54
- open?: boolean;
55
- defaultOpen?: boolean;
56
- /** 多选:每列 checkbox + 父子联动,trigger 多 tag 回显 */
57
- multiple?: boolean;
58
- size?: Size;
59
- status?: Status;
60
- placeholder?: string;
61
- disabled?: boolean;
62
- clearable?: boolean;
63
- changeOnSelect?: boolean;
64
- /** 展开触发方式:'click' 点击展开(默认);'hover' 悬停非叶子节点即展开子级列(legacy alias,推荐用 showNext) */
65
- expandTrigger?: 'click' | 'hover';
66
- /** 多选时只回传/计数叶子节点(父节点不进 value、tag、计数) */
67
- leafOnly?: boolean;
68
- /** 搜索时切换为扁平路径列表,按 label 链过滤 + 高亮命中(向后兼容别名,等价 filterTreeNode=true) */
69
- filterable?: boolean;
70
- /** 是否可搜索及自定义匹配:false 关闭;true 默认按 label 链子串匹配;函数自定义谓词 */
71
- filterTreeNode?: boolean | ((query: string, path: CascaderFlatPath<CascaderNode>) => boolean);
72
- /** 搜索结果是否仅展示到叶子路径(默认 true;false 时含中间层级路径) */
73
- filterLeafOnly?: boolean;
74
- /** 触发器回显使用的字段:'label'(默认)或 'value' */
75
- displayProp?: 'label' | 'value';
76
- /** 路径分隔符(回显 / 搜索 label 链拼接) */
77
- separator?: string;
78
- /** 多选 Tag 溢出折叠阈值:超出显示前 N 个 + +M */
79
- maxTagCount?: number;
80
- /** 列为空(无可选项)时内容;缺省走 i18n */
81
- emptyContent?: string | Snippet;
82
- /** 关闭即卸载浮层内容({#if}),重开重建。默认 false:首开后保留 DOM 仅隐藏。 */
83
- destroyOnClose?: boolean;
84
- /** 浮层层级 */
85
- zIndex?: number;
86
- /** 浮层挂载容器,缺省 document.body。非 body 容器时改 absolute 定位相对该容器。 */
87
- getPopupContainer?: () => HTMLElement | null | undefined;
88
- /** 列宽:统一(number)或逐列(number[],超出取末项) */
89
- columnWidth?: number | number[];
90
- /** 动态加载子节点;点击非叶子且无 children 的节点时调用 */
91
- loadData?: (node: CascaderNode) => Promise<CascaderNode[]>;
92
- /** 自定义触发器选中路径的回显文本(单选 + 多选每个 tag 均走此函数) */
93
- displayRender?: (labels: string[], selectedNodes: CascaderNode[]) => string;
94
- /** 单选回调单条路径;多选回调多条叶子路径 */
95
- onChange?: (value: Key[] | Key[][]) => void;
96
- onOpenChange?: (open: boolean) => void;
97
- ariaLabel?: string;
98
-
99
- // --- 外观 ---
100
- /** 无边框模式 */
101
- borderless?: boolean;
102
- /** 前置内容(Snippet 或 string) */
103
- prefix?: Snippet | string;
104
- /** 后置内容(Snippet 或 string) */
105
- suffix?: Snippet | string;
106
- /** 自定义清除图标 */
107
- clearIcon?: Snippet;
108
- /** 自定义展开图标(expandIcon / arrowIcon 均可,expandIcon 优先) */
109
- expandIcon?: Snippet;
110
- /** 自定义右侧下拉箭头(expandIcon 的别名) */
111
- arrowIcon?: Snippet;
112
- /** 下拉框展开动画(默认 true) */
113
- motion?: boolean;
114
- /** 鼠标移入延迟(ms),默认 50 */
115
- mouseEnterDelay?: number;
116
- /** 鼠标移出延迟(ms),默认 50 */
117
- mouseLeaveDelay?: number;
118
- /** 下拉框自动调整方向(默认 true) */
119
- autoAdjustOverflow?: boolean;
120
- /** 下拉框方向(默认 'bottomStart') */
121
- position?: string;
122
- /** 选择框样式 */
123
- style?: string;
124
- /** 下拉菜单样式 */
125
- dropdownStyle?: string | Record<string, string>;
126
- /** 失焦回调 */
127
- onBlur?: (e: MouseEvent) => void;
128
- /** 聚焦回调 */
129
- onFocus?: (e: MouseEvent) => void;
130
- /** 聚焦时阻止滚动 */
131
- preventScroll?: boolean;
132
- /** 阻止下拉框点击冒泡(默认 true) */
133
- stopPropagation?: boolean;
134
-
135
- // --- 插槽 ---
136
- /** 面板顶部插槽 */
137
- topSlot?: Snippet;
138
- /** 面板底部插槽 */
139
- bottomSlot?: Snippet;
140
-
141
- // --- 搜索增强 ---
142
- /** 搜索框位置:'trigger'(默认,内置);'custom'(自定义,不渲染内置搜索框) */
143
- searchPosition?: 'trigger' | 'custom';
144
- /** 搜索框占位文字 */
145
- searchPlaceholder?: string;
146
- /** 搜索时过滤的属性(默认 'label') */
147
- treeNodeFilterProp?: string;
148
- /** 自定义搜索结果项渲染 */
149
- filterRender?: Snippet<[{ path: FlatPath }]>;
150
- /** 搜索结果自定义排序 */
151
- filterSorter?: (a: FlatPath, b: FlatPath, input: string) => number;
152
- /** 搜索输入回调 */
153
- onSearch?: (value: string) => void;
154
- /** 远程搜索模式:跳过本地过滤,只触发 onSearch */
155
- remote?: boolean;
156
- /** 搜索结果虚拟滚动配置 */
157
- virtualizeInSearch?: { height: number; width: number; itemSize: number };
158
-
159
- // --- 多选增强 ---
160
- /** 选中父节点时 value 不包含后代(默认 true) */
161
- autoMergeValue?: boolean;
162
- /** 勾选关系:'related' 父子联动(默认);'unRelated' 独立 */
163
- checkRelation?: 'related' | 'unRelated';
164
- /** onChange 回调是否返回完整 option 对象 */
165
- onChangeWithObject?: boolean;
166
- /** 超出 max 时回调 */
167
- onExceed?: (items: unknown[]) => void;
168
- /** 超出 maxTagCount 时是否显示 popover */
169
- showRestTagsPopover?: boolean;
170
- /** 超出 tag popover 的 props */
171
- restTagsPopoverProps?: Record<string, unknown>;
172
- /** 是否显示清除按钮(clearable 的别名) */
173
- showClear?: boolean;
174
-
175
- // --- 节点配置 ---
176
- /** 自定义字段名映射 */
177
- keyMaps?: { value?: string; label?: string; children?: string; disabled?: string; isLeaf?: string };
178
- /** 点击任意节点即选中(含非叶子) */
179
- clickToSelect?: boolean;
180
- /** 多选模式下点击叶子节点触发勾选 */
181
- enableLeafClick?: boolean;
182
- /** 禁用不从父节点继承(严格禁用) */
183
- disableStrictly?: boolean;
184
- /** 展开触发方式(新版 canonical prop,优先于 expandTrigger) */
185
- showNext?: 'click' | 'hover';
186
-
187
- // --- 事件 ---
188
- /** 清空回调 */
189
- onClear?: () => void;
190
- /** 异步加载完成回调 */
191
- onLoad?: (loadedKeys: Key[], data: CascaderNode) => void;
192
- /** 节点选中回调(单选叶子选中时) */
193
- onSelect?: (value: Key) => void;
194
- /** 下拉面板显隐回调 */
195
- onDropdownVisibleChange?: (visible: boolean) => void;
196
- /** 列滚动回调 */
197
- onListScroll?: (e: Event, info: { panelIndex: number; activeNode: CascaderNode | null }) => void;
198
-
199
- // --- 自定义渲染 ---
200
- /** 完全自定义触发器渲染 */
201
- triggerRender?: Snippet<[{ value: Key[] | Key[][] | undefined; placeholder: string; isOpen: boolean; disabled: boolean }]>;
202
- }
203
-
204
- let {
205
- value,
206
- defaultValue,
207
- treeData = [],
208
- open,
209
- defaultOpen = false,
210
- multiple = false,
211
- size = 'default',
212
- status = 'default',
213
- placeholder = '请选择',
214
- disabled = false,
215
- clearable = false,
216
- changeOnSelect = false,
217
- expandTrigger = 'click',
218
- leafOnly = false,
219
- filterable = false,
220
- filterTreeNode,
221
- filterLeafOnly = true,
222
- displayProp = 'label',
223
- separator = ' / ',
224
- maxTagCount,
225
- emptyContent,
226
- destroyOnClose = false,
227
- zIndex = 1030,
228
- getPopupContainer,
229
- columnWidth = 180,
230
- loadData,
231
- displayRender,
232
- onChange,
233
- onOpenChange,
234
- ariaLabel,
235
- borderless = false,
236
- prefix,
237
- suffix,
238
- clearIcon,
239
- expandIcon,
240
- arrowIcon,
241
- motion = true,
242
- mouseEnterDelay = 50,
243
- mouseLeaveDelay = 50,
244
- autoAdjustOverflow = true,
245
- position,
246
- style,
247
- dropdownStyle,
248
- onBlur,
249
- onFocus,
250
- preventScroll = false,
251
- stopPropagation = true,
252
- topSlot,
253
- bottomSlot,
254
- searchPosition = 'trigger',
255
- searchPlaceholder,
256
- treeNodeFilterProp = 'label',
257
- filterRender,
258
- filterSorter,
259
- onSearch,
260
- remote = false,
261
- virtualizeInSearch,
262
- autoMergeValue = true,
263
- checkRelation = 'related',
264
- onChangeWithObject = false,
265
- onExceed,
266
- showRestTagsPopover = false,
267
- restTagsPopoverProps,
268
- showClear: showClearProp,
269
- keyMaps,
270
- clickToSelect = false,
271
- enableLeafClick = false,
272
- disableStrictly = false,
273
- showNext = 'click',
274
- onClear,
275
- onLoad,
276
- onSelect,
277
- onDropdownVisibleChange,
278
- onListScroll,
279
- triggerRender,
280
- }: Props = $props();
281
-
282
- const loc = useLocale();
283
- // ConfigProvider 全局浮层容器默认;自身 getPopupContainer prop 优先,未传时回退此值(再回退 body)。
284
- const globalPopupContainer = getGlobalPopupContainer();
285
- const resolvePopupContainer = $derived(getPopupContainer ?? globalPopupContainer);
286
-
287
- // --- keyMaps: 字段名映射,规整 treeData ---
288
- const kmValue = $derived(keyMaps?.value ?? 'value');
289
- const kmLabel = $derived(keyMaps?.label ?? 'label');
290
- const kmChildren = $derived(keyMaps?.children ?? 'children');
291
- const kmDisabled = $derived(keyMaps?.disabled ?? 'disabled');
292
- const kmIsLeaf = $derived(keyMaps?.isLeaf ?? 'isLeaf');
293
- const keyMapsDefault = $derived(
294
- kmValue === 'value' &&
295
- kmLabel === 'label' &&
296
- kmChildren === 'children' &&
297
- kmDisabled === 'disabled' &&
298
- kmIsLeaf === 'isLeaf',
299
- );
300
- function normalizeNodes(nodes: CascaderNode[]): CascaderNode[] {
301
- if (keyMapsDefault) return nodes;
302
- return nodes.map((raw) => {
303
- const r = raw as unknown as Record<string, unknown>;
304
- const kids = r[kmChildren] as CascaderNode[] | undefined;
305
- const rawDisabled = r[kmDisabled];
306
- const rawIsLeaf = r[kmIsLeaf];
307
- const out = {
308
- ...raw,
309
- value: r[kmValue] as Key,
310
- label: r[kmLabel] as string,
311
- } as CascaderNode;
312
- if (rawDisabled !== undefined) out.disabled = rawDisabled as boolean;
313
- if (rawIsLeaf !== undefined) out.isLeaf = rawIsLeaf as boolean;
314
- if (kids) out.children = normalizeNodes(kids);
315
- else delete out.children;
316
- return out;
317
- });
318
- }
319
- const normalizedTreeData = $derived(keyMapsDefault ? treeData : normalizeNodes(treeData));
320
-
321
- // showNext 是 canonical prop;expandTrigger 是 legacy alias。showNext 优先。
322
- const effectiveExpandTrigger = $derived(
323
- showNext === 'hover' || expandTrigger === 'hover' ? 'hover' : 'click',
324
- );
325
-
326
- // filterTreeNode 优先;未显式传时回退 filterable 别名(向后兼容)。
327
- // 归一为:是否可搜索 + 实际谓词(true=默认子串匹配,函数=自定义)。
328
- const filterSpec = $derived<boolean | ((q: string, p: CascaderFlatPath<CascaderNode>) => boolean)>(
329
- remote ? true : (filterTreeNode !== undefined ? filterTreeNode : filterable),
330
- );
331
- const isFilterable = $derived(filterSpec !== false || remote);
332
- const showBuiltinSearch = $derived(isFilterable && searchPosition !== 'custom');
333
-
334
- // unRelated 勾选模式
335
- const isUnRelated = $derived(checkRelation === 'unRelated');
336
-
337
- // 异步加载:已加载子节点缓存(node.value → children)+ 加载中节点集合。
338
- // 不写回 treeData prop(红线 #1:不改受控数据源)。
339
- const extraChildren = new SvelteMap<Key, CascaderNode[]>();
340
- const loadingKeys = new SvelteSet<Key>();
341
-
342
- // 取节点的有效子节点:优先 node.children,否则查 extraChildren 缓存。
343
- function childrenOf(node: CascaderNode): CascaderNode[] | undefined {
344
- if (node.children && node.children.length > 0) return node.children;
345
- return extraChildren.get(node.value);
346
- }
347
-
348
- const listId = useId('cd-cascader-panel');
349
- const flatListId = useId('cd-cascader-flat');
350
- // 列项 / 扁平结果项 id 基(aria-activedescendant 指向当前键盘高亮项)。
351
- const itemBaseId = useId('cd-cascader-item');
352
- function colItemId(colIndex: number, value: Key): string {
353
- return `${itemBaseId}-c${colIndex}-${String(value)}`;
354
- }
355
- function flatItemId(values: Key[]): string {
356
- return `${itemBaseId}-f-${values.join('-')}`;
357
- }
358
-
359
- // --- 纯函数: 按 key 路径取节点链 (用于回显 label) ---
360
- function findPath(data: CascaderNode[], valuePath: Key[]): CascaderNode[] {
361
- const chain: CascaderNode[] = [];
362
- let level = data;
363
- for (const key of valuePath) {
364
- const node = level.find((n) => n.value === key);
365
- if (!node) break;
366
- chain.push(node);
367
- level = node.children ?? [];
368
- }
369
- return chain;
370
- }
371
-
372
- // --- 由 activePath 生成各列数据(含异步加载的子节点缓存)---
373
- function columnsFor(data: CascaderNode[], path: Key[]): CascaderNode[][] {
374
- const columns: CascaderNode[][] = [data];
375
- let level = data;
376
- for (const key of path) {
377
- const node = level.find((n) => n.value === key);
378
- const kids = node ? childrenOf(node) : undefined;
379
- if (!node || !kids || kids.length === 0) break;
380
- columns.push(kids);
381
- level = kids;
382
- }
383
- return columns;
384
- }
385
-
386
- // 联合 value 规整:单选取单条路径,多选取多条路径
387
- function asSinglePath(v: Key[] | Key[][] | undefined): Key[] {
388
- if (!v || v.length === 0) return [];
389
- return Array.isArray(v[0]) ? ((v[0] as Key[]) ?? []) : (v as Key[]);
390
- }
391
- function asMultiPaths(v: Key[] | Key[][] | undefined): Key[][] {
392
- if (!v || v.length === 0) return [];
393
- return Array.isArray(v[0]) ? (v as Key[][]) : [v as Key[]];
394
- }
395
-
396
- function getInitialValue(): Key[] {
397
- return asSinglePath(defaultValue);
398
- }
399
- function getInitialPaths(): Key[][] {
400
- return asMultiPaths(defaultValue).map((p) => p.slice());
401
- }
402
- function getInitialOpen(): boolean {
403
- return defaultOpen;
404
- }
405
- function getInitialPath(): Key[] {
406
- return asSinglePath(defaultValue).slice();
407
- }
408
-
409
- // --- 受控 value (红线 #1): 不无条件回写 value,仅 onChange ---
410
- const isValueControlled = $derived(value !== undefined);
411
- // 单选:当前路径
412
- let innerValue = $state<Key[]>(getInitialValue());
413
- const currentValue = $derived<Key[]>(
414
- isValueControlled ? asSinglePath(value) : innerValue,
415
- );
416
- // 多选:选中的多条叶子路径
417
- let innerPaths = $state<Key[][]>(getInitialPaths());
418
- const currentPaths = $derived<Key[][]>(
419
- isValueControlled ? asMultiPaths(value) : innerPaths,
420
- );
421
-
422
- // --- 受控 open (红线 #1): 不无条件回写 open,仅 onOpenChange ---
423
- const isOpenControlled = $derived(open !== undefined);
424
- let innerOpen = $state(getInitialOpen());
425
- const isOpen = $derived(isOpenControlled ? !!open : innerOpen);
426
-
427
- // --- 本地展开状态 (红线 #2): activePath 本地 $state,不依赖挂载 registry ---
428
- let activePath = $state<Key[]>(getInitialPath());
429
-
430
- const columns = $derived(columnsFor(normalizedTreeData, activePath));
431
-
432
- // --- 键盘 roving 高亮(aria-activedescendant 模型):焦点留触发器,方向键移动高亮 ---
433
- // kbCol:当前高亮所在列;kbValue:该列内高亮节点 value。render 不读 DOM(红线 #2)。
434
- let kbCol = $state(0);
435
- let kbValue = $state<Key | null>(null);
436
- // 扁平搜索结果高亮索引。
437
- let kbFlatIndex = $state(-1);
438
- const activeDescId = $derived.by(() => {
439
- if (searchActive) {
440
- if (kbFlatIndex < 0 || kbFlatIndex >= filteredPaths.length) return undefined;
441
- return flatItemId((filteredPaths[kbFlatIndex] as FlatPath).values);
442
- }
443
- if (kbValue === null) return undefined;
444
- const col = columns[kbCol];
445
- if (!col || !col.some((n) => n.value === kbValue)) return undefined;
446
- return colItemId(kbCol, kbValue);
447
- });
448
- function isKbActive(colIndex: number, node: CascaderNode): boolean {
449
- return !searchActive && kbCol === colIndex && kbValue === node.value;
450
- }
451
-
452
- // --- 多选:合并树(含异步缓存)转 core TreeNodeData(以 value 为 key),跑 conduct ---
453
- function toTreeData(nodes: CascaderNode[]): TreeNodeData[] {
454
- return nodes.map((n) => {
455
- const kids = childrenOf(n);
456
- const td: TreeNodeData = { key: n.value, label: n.label };
457
- if (n.disabled) td.disabled = true;
458
- if (kids && kids.length > 0) td.children = toTreeData(kids);
459
- return td;
460
- });
461
- }
462
- const mergedTreeData = $derived(toTreeData(normalizedTreeData));
463
-
464
- // --- filterable:扁平路径列表 + 搜索过滤 ---
465
- let searchValue = $state('');
466
- const trimmedSearch = $derived(searchValue.trim());
467
- const searchActive = $derived(isFilterable && trimmedSearch.length > 0);
468
-
469
- // onSearch 回调:搜索值变化时触发
470
- $effect(() => {
471
- if (isFilterable && trimmedSearch) {
472
- onSearch?.(trimmedSearch);
473
- }
474
- });
475
-
476
- interface FlatPath {
477
- values: Key[];
478
- labels: string[];
479
- nodes: CascaderNode[];
480
- isLeaf: boolean;
481
- disabled: boolean;
482
- }
483
- // 收集所有可选路径:叶子路径;filterLeafOnly=false 或 changeOnSelect 时含非叶子路径。
484
- // (含非叶子时由 filteredPaths 按 filterLeafOnly 再裁剪。)
485
- const flatPaths = $derived.by<FlatPath[]>(() => {
486
- const out: FlatPath[] = [];
487
- const walk = (
488
- nodes: CascaderNode[],
489
- vals: Key[],
490
- labels: string[],
491
- chain: CascaderNode[],
492
- parentDisabled: boolean,
493
- ) => {
494
- for (const n of nodes) {
495
- const kids = childrenOf(n);
496
- const isLeaf = !kids || kids.length === 0;
497
- const nv = [...vals, n.value];
498
- const nl = [...labels, n.label];
499
- const nc = [...chain, n];
500
- const dis = disableStrictly ? !!n.disabled : (parentDisabled || !!n.disabled);
501
- if (isLeaf || changeOnSelect || !filterLeafOnly)
502
- out.push({ values: nv, labels: nl, nodes: nc, isLeaf, disabled: dis });
503
- if (!isLeaf) walk(kids, nv, nl, nc, dis);
504
- }
505
- };
506
- walk(normalizedTreeData, [], [], [], false);
507
- return out;
508
- });
509
-
510
- const filteredPaths = $derived.by<FlatPath[]>(() => {
511
- if (!searchActive) return [];
512
- if (remote) return flatPaths; // remote: 显示全部,onSearch 负责外部更新
513
- const spec = filterSpec;
514
- if (spec === false) return [];
515
- let results: FlatPath[];
516
- if (typeof spec === 'function') {
517
- results = flatPaths.filter((p) => spec(trimmedSearch, p));
518
- } else {
519
- const lower = trimmedSearch.toLowerCase();
520
- results = flatPaths.filter((p) => {
521
- if (filterLeafOnly && !p.isLeaf) return false;
522
- return p.labels.join(separator).toLowerCase().includes(lower);
523
- });
524
- }
525
- if (filterSorter) {
526
- results = [...results].sort((a, b) => filterSorter(a, b, trimmedSearch));
527
- }
528
- return results;
529
- });
530
-
531
- // 命中文本高亮(作用于整条 label 链字符串)
532
- type HlPart = { text: string; mark: boolean };
533
- function highlightParts(text: string): HlPart[] {
534
- if (!searchActive) return [{ text, mark: false }];
535
- const lower = text.toLowerCase();
536
- const term = trimmedSearch.toLowerCase();
537
- const parts: HlPart[] = [];
538
- let from = 0;
539
- let idx = lower.indexOf(term, from);
540
- while (idx !== -1) {
541
- if (idx > from) parts.push({ text: text.slice(from, idx), mark: false });
542
- parts.push({ text: text.slice(idx, idx + term.length), mark: true });
543
- from = idx + term.length;
544
- idx = lower.indexOf(term, from);
545
- }
546
- if (from < text.length) parts.push({ text: text.slice(from), mark: false });
547
- return parts;
548
- }
549
-
550
- // 点击扁平路径:单选选中关闭;多选切换勾选(取末端节点)
551
- function selectFlatPath(p: FlatPath) {
552
- if (p.disabled || disabled) return;
553
- if (multiple) {
554
- const leaf = p.values[p.values.length - 1] as Key;
555
- const nextBase = toggleCheck(mergedTreeData, checkedBase, leaf);
556
- const resolved = conduct(mergedTreeData, nextBase);
557
- setPaths(leafBaseToPaths(resolved.checked));
558
- searchValue = '';
559
- } else {
560
- setValue(p.values.slice());
561
- setOpen(false);
562
- }
563
- }
564
-
565
- // 多选勾选 base:把每条选中路径的叶子 value 作为显式勾选项
566
- function pathsToLeafBase(paths: Key[][]): Set<Key> {
567
- const set = new Set<Key>();
568
- for (const p of paths) {
569
- if (p.length > 0) set.add(p[p.length - 1] as Key);
570
- }
571
- return set;
572
- }
573
- const checkedBase = $derived(pathsToLeafBase(currentPaths));
574
- const checkState = $derived.by(() =>
575
- multiple
576
- ? conduct(mergedTreeData, checkedBase)
577
- : { checked: new Set<Key>(), half: new Set<Key>() },
578
- );
579
-
580
- // 选中路径链(用于多 tag 回显)。遍历合并树收集 checked 路径:
581
- // leafOnly=false(默认):仅叶子(现状不变)。
582
- // leafOnly=true:完全勾选的父级折叠为父路径并停止下钻(父 tag 代表整子树)。
583
- const checkedLeafPaths = $derived.by<{ path: Key[]; labels: string[]; nodes: CascaderNode[] }[]>(() => {
584
- if (!multiple) return [];
585
- const out: { path: Key[]; labels: string[]; nodes: CascaderNode[] }[] = [];
586
- const walk = (nodes: CascaderNode[], path: Key[], labels: string[], chain: CascaderNode[]) => {
587
- for (const n of nodes) {
588
- const kids = childrenOf(n);
589
- const np = [...path, n.value];
590
- const nl = [...labels, n.label];
591
- const nc = [...chain, n];
592
- const isLeaf = !kids || kids.length === 0;
593
- if (isLeaf) {
594
- if (checkState.checked.has(n.value)) out.push({ path: np, labels: nl, nodes: nc });
595
- } else if (leafOnly && checkState.checked.has(n.value)) {
596
- // 父级完全勾选 → 折叠为父路径
597
- out.push({ path: np, labels: nl, nodes: nc });
598
- } else {
599
- walk(kids, np, nl, nc);
600
- }
601
- }
602
- };
603
- walk(normalizedTreeData, [], [], []);
604
- return out;
605
- });
606
-
607
- // maxTagCount 折叠:显示前 N 个 tag + 隐藏数(仅影响显示,不改 value,红线 #1/#2)。
608
- const visibleTagPaths = $derived(
609
- maxTagCount !== undefined && maxTagCount >= 0
610
- ? checkedLeafPaths.slice(0, maxTagCount)
611
- : checkedLeafPaths,
612
- );
613
- const hiddenTagCount = $derived(
614
- maxTagCount !== undefined && maxTagCount >= 0
615
- ? Math.max(0, checkedLeafPaths.length - maxTagCount)
616
- : 0,
617
- );
618
-
619
- // clearable / showClear 别名归一(showClear prop 优先 clearable)
620
- const effClearable = $derived(showClearProp ?? clearable ?? false);
621
- // expandIcon / arrowIcon 别名归一
622
- const effExpandIcon = $derived(expandIcon ?? arrowIcon);
623
-
624
- const selectedChain = $derived(findPath(normalizedTreeData, currentValue));
625
- const displayLabel = $derived(renderPath(selectedChain.map((n) => n.label), selectedChain));
626
- const hasSelection = $derived(
627
- multiple ? checkedLeafPaths.length > 0 : selectedChain.length > 0,
628
- );
629
- const showClear = $derived(effClearable && !disabled && hasSelection);
630
-
631
- // 单条路径回显文本:有 displayRender 走自定义(仍传 label 链 + 节点链);
632
- // 否则按 displayProp 取 label 或 value 链,用 separator 连接。
633
- // 多选每个 tag 与单选回显共用此逻辑。
634
- function renderPath(labels: string[], nodes: CascaderNode[]): string {
635
- if (displayRender) return displayRender(labels, nodes);
636
- const fields = displayProp === 'value' ? nodes.map((n) => String(n.value)) : labels;
637
- return fields.join(separator);
638
- }
639
-
640
- function setValue(next: Key[]) {
641
- if (!isValueControlled) innerValue = next;
642
- onChange?.(next);
643
- }
644
-
645
- // 多选:由 conduct 解析后的 checked 全集生成多条路径回调。
646
- // autoMergeValue=true(默认):选中父节点时 value 不包含后代。
647
- // autoMergeValue=false:包含父节点 AND 所有后代路径。
648
- // leafOnly=true:完全勾选的父级折叠为父路径并停止下钻。
649
- function leafBaseToPaths(checkedSet: Set<Key>): Key[][] {
650
- const out: Key[][] = [];
651
- const walk = (nodes: CascaderNode[], path: Key[]) => {
652
- for (const n of nodes) {
653
- const kids = childrenOf(n);
654
- const np = [...path, n.value];
655
- const isLeaf = !kids || kids.length === 0;
656
- if (isLeaf) {
657
- if (checkedSet.has(n.value)) out.push(np);
658
- } else if (autoMergeValue && checkedSet.has(n.value)) {
659
- // 父级完全勾选,不展开后代(合并)
660
- out.push(np);
661
- } else if (!autoMergeValue && checkedSet.has(n.value)) {
662
- // 包含父节点 + 继续下钻后代
663
- out.push(np);
664
- walk(kids ?? [], np);
665
- } else if (leafOnly && checkedSet.has(n.value)) {
666
- out.push(np);
667
- } else {
668
- walk(kids ?? [], np);
669
- }
670
- }
671
- };
672
- walk(normalizedTreeData, []);
673
- return out;
674
- }
675
-
676
- function setPaths(nextPaths: Key[][]) {
677
- if (!isValueControlled) innerPaths = nextPaths;
678
- onChange?.(nextPaths);
679
- }
680
-
681
- // 切换某节点勾选(支持 related/unRelated 两种模式)
682
- function toggleCheckNode(node: CascaderNode) {
683
- if (node.disabled || disabled) return;
684
- if (isUnRelated) {
685
- // unRelated: 独立切换,不联动父子
686
- const nextPaths = currentPaths.slice();
687
- const nodeVal = node.value;
688
- const idx = nextPaths.findIndex((p) => p.length > 0 && p[p.length - 1] === nodeVal);
689
- if (idx >= 0) {
690
- nextPaths.splice(idx, 1);
691
- } else {
692
- // 从 activePath 构建完整路径
693
- const colIdx = activePath.indexOf(nodeVal);
694
- if (colIdx >= 0) {
695
- nextPaths.push(activePath.slice(0, colIdx + 1));
696
- } else {
697
- nextPaths.push([nodeVal]);
698
- }
699
- }
700
- setPaths(nextPaths);
701
- return;
702
- }
703
- const nextBase = toggleCheck(mergedTreeData, checkedBase, node.value);
704
- const resolved = conduct(mergedTreeData, nextBase);
705
- setPaths(leafBaseToPaths(resolved.checked));
706
- }
707
-
708
- // 移除某 tag(按叶子 value 取消勾选,联动祖先半选更新)
709
- function removeLeaf(leafValue: Key) {
710
- if (disabled) return;
711
- if (isUnRelated) {
712
- const nextPaths = currentPaths.filter((p) => p.length === 0 || p[p.length - 1] !== leafValue);
713
- setPaths(nextPaths);
714
- return;
715
- }
716
- const nextBase = new Set(checkedBase);
717
- nextBase.delete(leafValue);
718
- const resolved = conduct(mergedTreeData, nextBase);
719
- setPaths(leafBaseToPaths(resolved.checked));
720
- }
721
-
722
- function nodeCheck(node: CascaderNode): { checked: boolean; half: boolean } {
723
- if (isUnRelated) {
724
- const checked = currentPaths.some((p) => p.length > 0 && p[p.length - 1] === node.value);
725
- return { checked, half: false };
726
- }
727
- return {
728
- checked: checkState.checked.has(node.value),
729
- half: !checkState.checked.has(node.value) && checkState.half.has(node.value),
730
- };
731
- }
732
-
733
- function setOpen(next: boolean) {
734
- if (next === isOpen) return;
735
- if (!next) searchValue = '';
736
- if (!isOpenControlled) innerOpen = next;
737
- onOpenChange?.(next);
738
- onDropdownVisibleChange?.(next);
739
- if (next) {
740
- // 打开时同步 activePath 到当前选中路径
741
- activePath = currentValue.slice();
742
- // 复位键盘高亮(首次方向键再起步)。
743
- kbCol = 0;
744
- kbValue = null;
745
- kbFlatIndex = -1;
746
- } else {
747
- kbCol = 0;
748
- kbValue = null;
749
- kbFlatIndex = -1;
750
- }
751
- }
752
-
753
- function toggleOpen() {
754
- if (disabled) return;
755
- setOpen(!isOpen);
756
- }
757
-
758
- function isActiveAt(colIndex: number, node: CascaderNode): boolean {
759
- return activePath[colIndex] === node.value;
760
- }
761
-
762
- function isSelectedLeaf(colIndex: number, node: CascaderNode): boolean {
763
- return (
764
- currentValue.length === colIndex + 1 &&
765
- currentValue[colIndex] === node.value
766
- );
767
- }
768
-
769
- function hasChildren(node: CascaderNode): boolean {
770
- const kids = childrenOf(node);
771
- return !!kids && kids.length > 0;
772
- }
773
-
774
- // 节点是否可展开:已有子节点,或非叶子且配置了 loadData(可异步加载)。
775
- function canExpand(node: CascaderNode): boolean {
776
- if (hasChildren(node)) return true;
777
- return !node.isLeaf && loadData !== undefined;
778
- }
779
-
780
- function isLoading(node: CascaderNode): boolean {
781
- return loadingKeys.has(node.value);
782
- }
783
-
784
- // 异步加载子节点:标记 loading → await loadData → 缓存结果 → 清 loading。
785
- async function loadChildren(node: CascaderNode): Promise<boolean> {
786
- if (!loadData || loadingKeys.has(node.value)) return false;
787
- loadingKeys.add(node.value);
788
- try {
789
- const kids = await loadData(node);
790
- extraChildren.set(node.value, kids);
791
- onLoad?.(Array.from(extraChildren.keys()), node);
792
- return kids.length > 0;
793
- } catch {
794
- return false;
795
- } finally {
796
- loadingKeys.delete(node.value);
797
- }
798
- }
799
-
800
- function selectNode(colIndex: number, node: CascaderNode) {
801
- if (node.disabled || disabled) return;
802
- const nextPath = activePath.slice(0, colIndex);
803
- nextPath.push(node.value);
804
- activePath = nextPath;
805
-
806
- // 多选:非叶子展开列 / 异步加载;叶子切换勾选且不关面板
807
- if (multiple) {
808
- if (hasChildren(node)) {
809
- return;
810
- }
811
- if (!node.isLeaf && loadData) {
812
- void loadChildren(node);
813
- return;
814
- }
815
- // enableLeafClick: 只有 true 时叶子点击触发勾选
816
- if (enableLeafClick || !node.isLeaf) {
817
- toggleCheckNode(node);
818
- }
819
- return;
820
- }
821
-
822
- // clickToSelect:点击任意节点(含非叶子)立即选中
823
- if (clickToSelect) {
824
- setValue(nextPath.slice());
825
- onSelect?.(node.value);
826
- if (!hasChildren(node)) setOpen(false);
827
- return;
828
- }
829
-
830
- // changeOnSelect 完整语义(单选):点击任一层级(含中间非叶子)立即提交
831
- // 从根到该节点的路径并触发 onChange。非叶子同时展开其子列且不关闭面板
832
- // (让用户「停在任意层级」或继续深入);叶子提交并关闭。
833
- if (hasChildren(node)) {
834
- // 非叶子: 展开下一列;changeOnSelect 时同时提交当前路径、保持面板
835
- if (changeOnSelect) setValue(nextPath.slice());
836
- } else if (!node.isLeaf && loadData) {
837
- // 可异步加载的非叶子:changeOnSelect 时先提交本路径,再加载子节点
838
- // (加载完成后子节点经 extraChildren 进列,activePath 已指向本节点)。
839
- if (changeOnSelect) setValue(nextPath.slice());
840
- void loadChildren(node);
841
- } else {
842
- // 叶子: 提交完整路径并关闭
843
- setValue(nextPath.slice());
844
- onSelect?.(node.value);
845
- setOpen(false);
846
- }
847
- }
848
-
849
- // effectiveExpandTrigger='hover':悬停非叶子(或可异步加载)节点即展开其子级列。
850
- // 仅设 activePath + 触发 loadData,不做选中(选中仍走 click)。
851
- function hoverExpand(colIndex: number, node: CascaderNode) {
852
- if (effectiveExpandTrigger !== 'hover' || disabled || node.disabled) return;
853
- if (!canExpand(node)) return;
854
- const nextPath = activePath.slice(0, colIndex);
855
- nextPath.push(node.value);
856
- activePath = nextPath;
857
- if (!hasChildren(node) && !node.isLeaf && loadData) {
858
- void loadChildren(node);
859
- }
860
- }
861
-
862
- function clearAll(e: MouseEvent) {
863
- e.stopPropagation();
864
- if (disabled) return;
865
- if (multiple) {
866
- setPaths([]);
867
- } else {
868
- setValue([]);
869
- }
870
- activePath = [];
871
- onClear?.();
872
- }
873
-
874
- // --- 键盘 roving 导航(aria-activedescendant 模型)---
875
- // 列内 ↑↓ 移动、→ 进下一列、← 回上一列、Home/End 跳列首尾;
876
- // Enter 在叶子(或 changeOnSelect 任意级)确认,多选 Space 切换勾选;焦点留触发器。
877
- function colNodes(colIndex: number): CascaderNode[] {
878
- return columns[colIndex] ?? [];
879
- }
880
- function indexInCol(colIndex: number, value: Key | null): number {
881
- if (value === null) return -1;
882
- return colNodes(colIndex).findIndex((n) => n.value === value);
883
- }
884
- function moveInCol(colIndex: number, intent: 'prev' | 'next' | 'first' | 'last') {
885
- const nodes = colNodes(colIndex);
886
- if (nodes.length === 0) return;
887
- let i = nextRovingIndex(indexInCol(colIndex, kbValue), nodes.length, intent);
888
- // 跳过禁用项,朝移动方向继续。
889
- const step = intent === 'prev' || intent === 'last' ? -1 : 1;
890
- while (i >= 0 && i < nodes.length && (nodes[i] as CascaderNode).disabled) i += step;
891
- if (i < 0 || i >= nodes.length) return;
892
- kbCol = colIndex;
893
- kbValue = (nodes[i] as CascaderNode).value;
894
- }
895
- // 进入某列:高亮该列首个未禁用项(若该列已有 activePath 选中项则高亮它)。
896
- function enterCol(colIndex: number) {
897
- const nodes = colNodes(colIndex);
898
- if (nodes.length === 0) return;
899
- kbCol = colIndex;
900
- const fromPath = activePath[colIndex];
901
- if (fromPath !== undefined && nodes.some((n) => n.value === fromPath)) {
902
- kbValue = fromPath;
903
- } else {
904
- kbValue = null;
905
- moveInCol(colIndex, 'first');
906
- }
907
- }
908
- function kbCurrentNode(): CascaderNode | undefined {
909
- return colNodes(kbCol).find((n) => n.value === kbValue);
910
- }
911
-
912
- function onFlatKeydown(e: KeyboardEvent) {
913
- if (e.key === 'Escape') {
914
- setOpen(false);
915
- return;
916
- }
917
- const count = filteredPaths.length;
918
- if (count === 0) return;
919
- const intent = rovingKeyFromEvent(e.key);
920
- if (intent === 'prev' || intent === 'next' || intent === 'first' || intent === 'last') {
921
- e.preventDefault();
922
- let i = nextRovingIndex(kbFlatIndex, count, intent);
923
- const step = intent === 'prev' || intent === 'last' ? -1 : 1;
924
- while (i >= 0 && i < count && (filteredPaths[i] as FlatPath).disabled) i += step;
925
- if (i >= 0 && i < count) kbFlatIndex = i;
926
- return;
927
- }
928
- if (e.key === 'Enter') {
929
- e.preventDefault();
930
- if (kbFlatIndex >= 0 && kbFlatIndex < count) selectFlatPath(filteredPaths[kbFlatIndex] as FlatPath);
931
- }
932
- }
933
-
934
- function onPanelKeydown(e: KeyboardEvent) {
935
- if (disabled) return;
936
- if (e.key === 'Escape') {
937
- setOpen(false);
938
- return;
939
- }
940
- if (searchActive) {
941
- onFlatKeydown(e);
942
- return;
943
- }
944
- // 列模式:首次方向键从首列首项起步。
945
- if (kbValue === null && rovingKeyFromEvent(e.key)) {
946
- e.preventDefault();
947
- enterCol(0);
948
- return;
949
- }
950
- const node = kbCurrentNode();
951
- switch (e.key) {
952
- case 'ArrowUp':
953
- e.preventDefault();
954
- moveInCol(kbCol, 'prev');
955
- break;
956
- case 'ArrowDown':
957
- e.preventDefault();
958
- moveInCol(kbCol, 'next');
959
- break;
960
- case 'Home':
961
- e.preventDefault();
962
- moveInCol(kbCol, 'first');
963
- break;
964
- case 'End':
965
- e.preventDefault();
966
- moveInCol(kbCol, 'last');
967
- break;
968
- case 'ArrowRight':
969
- e.preventDefault();
970
- // 展开高亮节点的下一列并进入它。
971
- if (node && canExpand(node)) {
972
- selectNode(kbCol, node);
973
- enterCol(kbCol + 1);
974
- }
975
- break;
976
- case 'ArrowLeft':
977
- e.preventDefault();
978
- if (kbCol > 0) enterCol(kbCol - 1);
979
- break;
980
- case 'Enter':
981
- e.preventDefault();
982
- if (node) selectNode(kbCol, node);
983
- break;
984
- case ' ':
985
- e.preventDefault();
986
- if (node) {
987
- if (multiple) toggleCheckNode(node);
988
- else selectNode(kbCol, node);
989
- }
990
- break;
991
- default:
992
- break;
993
- }
994
- }
995
-
996
- // --- DOM 引用:触发根 + portal 面板(定位由 use:floating action 接管)---
997
- let rootEl = $state<HTMLDivElement | null>(null);
998
- let panelEl = $state<HTMLDivElement | null>(null);
999
-
1000
- // --- destroyOnClose:默认 false 时首开后保留浮层 DOM(仅 --hidden 切显隐),
1001
- // true 时关闭即从 DOM 卸载({#if shouldRender}),重开重建。 ---
1002
- let hasBeenOpened = $state(false);
1003
- $effect(() => {
1004
- if (isOpen) hasBeenOpened = true;
1005
- });
1006
- const shouldRender = $derived(destroyOnClose ? isOpen : isOpen || hasBeenOpened);
1007
-
1008
- // 空态文本(emptyContent 为 string 时用,Snippet 时模板直接渲染)。
1009
- const emptyText = $derived(
1010
- typeof emptyContent === 'string' ? emptyContent : loc().t('Cascader.emptyText'),
1011
- );
1012
- const isEmptySnippet = $derived(typeof emptyContent === 'function');
1013
-
1014
- // --- useDismiss (红线 #3): panel portal 出 root 子树后列入 extraTargets ---
1015
- $effect(() => {
1016
- if (!isOpen || !rootEl) return;
1017
- return useDismiss(rootEl, {
1018
- onDismiss: () => setOpen(false),
1019
- escape: true,
1020
- outsideClick: true,
1021
- extraTargets: [panelEl],
1022
- });
1023
- });
1024
-
1025
- const cls = $derived(
1026
- [
1027
- 'cd-cascader',
1028
- `cd-cascader--${size}`,
1029
- `cd-cascader--${status}`,
1030
- disabled && 'cd-cascader--disabled',
1031
- isOpen && 'cd-cascader--open',
1032
- position && `cd-cascader--${position}`,
1033
- borderless && 'cd-cascader--borderless',
1034
- motion === false && 'cd-cascader--no-motion',
1035
- ]
1036
- .filter(Boolean)
1037
- .join(' '),
1038
- );
1039
- </script>
1040
-
1041
- <div class={cls} bind:this={rootEl} {style}>
1042
- <!-- combobox 容器用 div 以合法承载多选 tags / clear 等内部交互元素 -->
1043
- {#if triggerRender}
1044
- {@render triggerRender({ value, placeholder, isOpen, disabled })}
1045
- {:else}
1046
- <div
1047
- class="cd-cascader__trigger"
1048
- role="combobox"
1049
- aria-haspopup="listbox"
1050
- aria-expanded={isOpen}
1051
- aria-controls={listId}
1052
- aria-activedescendant={isOpen && !searchActive ? activeDescId : undefined}
1053
- aria-label={ariaLabel}
1054
- aria-invalid={status === 'error' || undefined}
1055
- aria-disabled={disabled || undefined}
1056
- tabindex={disabled ? -1 : 0}
1057
- onclick={toggleOpen}
1058
- onfocus={(e) => onFocus?.(e as unknown as MouseEvent)}
1059
- onblur={(e) => onBlur?.(e as unknown as MouseEvent)}
1060
- onkeydown={(e) => {
1061
- if (disabled) return;
1062
- if (!isOpen) {
1063
- // 关闭态:Enter/Space/Down 打开浮层。
1064
- if (e.key === 'Enter' || e.key === ' ' || e.key === 'ArrowDown') {
1065
- e.preventDefault();
1066
- setOpen(true);
1067
- }
1068
- return;
1069
- }
1070
- // 打开态:搜索激活时由搜索框处理 roving,焦点不在触发器;
1071
- // 否则触发器(焦点宿主)驱动列 roving(aria-activedescendant 模型)。
1072
- if (!searchActive) onPanelKeydown(e);
1073
- else if (e.key === 'Escape') setOpen(false);
1074
- }}
1075
- >
1076
- {#if prefix}
1077
- <span class="cd-cascader__prefix" aria-hidden="true">
1078
- {#if typeof prefix === 'string'}{prefix}{:else}{@render (prefix as Snippet)()}{/if}
1079
- </span>
1080
- {/if}
1081
-
1082
- <span class="cd-cascader__content">
1083
- {#if multiple}
1084
- {#if checkedLeafPaths.length > 0}
1085
- <span class="cd-cascader__tags">
1086
- {#each visibleTagPaths as leaf (leaf.path.join('/'))}
1087
- <Tag
1088
- size={size === 'large' ? 'default' : 'small'}
1089
- closable={!disabled}
1090
- onClose={() => removeLeaf(leaf.path[leaf.path.length - 1] as Key)}
1091
- >
1092
- {renderPath(leaf.labels, leaf.nodes)}
1093
- </Tag>
1094
- {/each}
1095
- {#if hiddenTagCount > 0}
1096
- <Tag size={size === 'large' ? 'default' : 'small'}>+{hiddenTagCount}</Tag>
1097
- {/if}
1098
- </span>
1099
- {:else}
1100
- <span class="cd-cascader__placeholder">{placeholder}</span>
1101
- {/if}
1102
- {:else if hasSelection}
1103
- <span class="cd-cascader__value">{displayLabel}</span>
1104
- {:else}
1105
- <span class="cd-cascader__placeholder">{placeholder}</span>
1106
- {/if}
1107
- </span>
1108
-
1109
- {#if suffix}
1110
- <span class="cd-cascader__suffix" aria-hidden="true">
1111
- {#if typeof suffix === 'string'}{suffix}{:else}{@render (suffix as Snippet)()}{/if}
1112
- </span>
1113
- {/if}
1114
-
1115
- {#if showClear}
1116
- <span
1117
- class="cd-cascader__clear"
1118
- role="button"
1119
- tabindex="-1"
1120
- aria-label={loc().t('Cascader.clear')}
1121
- onclick={clearAll}
1122
- onkeydown={(e) => {
1123
- if (e.key === 'Enter' || e.key === ' ') {
1124
- e.preventDefault();
1125
- clearAll(e as unknown as MouseEvent);
1126
- }
1127
- }}
1128
- >
1129
- {#if clearIcon}
1130
- {@render clearIcon()}
1131
- {:else}
1132
- <svg viewBox="0 0 16 16" width="14" height="14" aria-hidden="true" focusable="false">
1133
- <path
1134
- fill="currentColor"
1135
- d="M8 1a7 7 0 1 0 0 14A7 7 0 0 0 8 1Zm2.5 9.1-1.4 1.4L8 9.4 6.5 11l-1.4-1.4L6.6 8 5.1 6.5 6.5 5.1 8 6.6 9.5 5.1l1.4 1.4L9.4 8l1.1 1.1Z"
1136
- />
1137
- </svg>
1138
- {/if}
1139
- </span>
1140
- {/if}
1141
-
1142
- <span class="cd-cascader__arrow" aria-hidden="true">
1143
- {#if effExpandIcon}
1144
- {@render effExpandIcon()}
1145
- {:else}
1146
- <svg viewBox="0 0 16 16" width="12" height="12" focusable="false">
1147
- <path fill="currentColor" d="M3.5 6 8 10.5 12.5 6l-1-1L8 8.5 4.5 5l-1 1Z" />
1148
- </svg>
1149
- {/if}
1150
- </span>
1151
- </div>
1152
- {/if}
1153
-
1154
- {#if shouldRender}
1155
- <div
1156
- class="cd-cascader__panel"
1157
- class:cd-cascader__panel--hidden={!isOpen}
1158
- bind:this={panelEl}
1159
- use:floating={{
1160
- trigger: rootEl,
1161
- placement: 'bottomStart',
1162
- autoAdjust: true,
1163
- offset: 4,
1164
- getContainer: resolvePopupContainer,
1165
- open: isOpen,
1166
- }}
1167
- id={listId}
1168
- style:z-index={zIndex}
1169
- >
1170
- {#if topSlot}{@render topSlot()}{/if}
1171
-
1172
- {#if showBuiltinSearch}
1173
- <div class="cd-cascader__search">
1174
- <input
1175
- class="cd-cascader__search-input"
1176
- type="text"
1177
- role="combobox"
1178
- aria-expanded={searchActive}
1179
- aria-controls={flatListId}
1180
- aria-activedescendant={searchActive ? activeDescId : undefined}
1181
- placeholder={loc().t('Cascader.searchPlaceholder')}
1182
- aria-label={loc().t('Cascader.searchPlaceholder')}
1183
- bind:value={searchValue}
1184
- onkeydown={onFlatKeydown}
1185
- />
1186
- </div>
1187
- {/if}
1188
- {#if searchActive}
1189
- <ul
1190
- class="cd-cascader__flat"
1191
- role="listbox"
1192
- id={flatListId}
1193
- aria-label={loc().t('Cascader.searchResults')}
1194
- >
1195
- {#if filteredPaths.length === 0}
1196
- {#if isEmptySnippet}
1197
- <li class="cd-cascader__empty">{@render (emptyContent as Snippet)()}</li>
1198
- {:else}
1199
- <li class="cd-cascader__empty">{emptyText}</li>
1200
- {/if}
1201
- {:else}
1202
- {#each filteredPaths as p, fi (p.values.join('/'))}
1203
- <!-- 键盘经搜索框 aria-activedescendant 漫游管理,选项 tabindex=-1 click-only -->
1204
- <!-- svelte-ignore a11y_click_events_have_key_events -->
1205
- <li
1206
- id={flatItemId(p.values)}
1207
- class="cd-cascader__option cd-cascader__flat-option"
1208
- class:cd-cascader__option--active={kbFlatIndex === fi}
1209
- role="option"
1210
- aria-selected={kbFlatIndex === fi}
1211
- aria-disabled={p.disabled || undefined}
1212
- onclick={() => selectFlatPath(p)}
1213
- tabindex={-1}
1214
- >
1215
- {#if filterRender}
1216
- {@render filterRender({ path: p })}
1217
- {:else}
1218
- <span class="cd-cascader__option-label">
1219
- {#each highlightParts(p.labels.join(separator)) as part, i (i)}
1220
- {#if part.mark}<mark class="cd-cascader__highlight">{part.text}</mark>{:else}{part.text}{/if}
1221
- {/each}
1222
- </span>
1223
- {/if}
1224
- </li>
1225
- {/each}
1226
- {/if}
1227
- </ul>
1228
- {:else}
1229
- <div class="cd-cascader__columns">
1230
- {#each columns as column, colIndex (colIndex)}
1231
- <ul
1232
- class="cd-cascader__column"
1233
- role="listbox"
1234
- aria-label={loc().t('Cascader.columnLabel', { level: colIndex + 1 })}
1235
- style:inline-size="{resolveColumnWidth(columnWidth, colIndex, 180)}px"
1236
- onscroll={(e) => onListScroll?.(e, {
1237
- panelIndex: colIndex,
1238
- activeNode: columns[colIndex]?.find((n) => isActiveAt(colIndex, n)) ?? null,
1239
- })}
1240
- >
1241
- {#if column.length === 0}
1242
- {#if isEmptySnippet}
1243
- <li class="cd-cascader__empty">{@render (emptyContent as Snippet)()}</li>
1244
- {:else}
1245
- <li class="cd-cascader__empty">{emptyText}</li>
1246
- {/if}
1247
- {/if}
1248
- {#each column as node (node.value)}
1249
- <!-- 键盘经触发器 aria-activedescendant 漫游管理,选项 tabindex=-1 click-only -->
1250
- <!-- svelte-ignore a11y_click_events_have_key_events -->
1251
- <li
1252
- id={colItemId(colIndex, node.value)}
1253
- class="cd-cascader__option"
1254
- class:cd-cascader__option--active={isActiveAt(colIndex, node)}
1255
- class:cd-cascader__option--kbactive={isKbActive(colIndex, node)}
1256
- class:cd-cascader__option--selected={isSelectedLeaf(colIndex, node)}
1257
- role="option"
1258
- aria-selected={isActiveAt(colIndex, node)}
1259
- aria-disabled={node.disabled || undefined}
1260
- onclick={() => selectNode(colIndex, node)}
1261
- onpointerenter={() => hoverExpand(colIndex, node)}
1262
- tabindex={-1}
1263
- >
1264
- {#if multiple}
1265
- {@const cs = nodeCheck(node)}
1266
- <span
1267
- class="cd-cascader__checkbox"
1268
- class:cd-cascader__checkbox--checked={cs.checked}
1269
- class:cd-cascader__checkbox--half={cs.half}
1270
- role="button"
1271
- tabindex="-1"
1272
- aria-hidden="true"
1273
- onclick={(e) => {
1274
- e.stopPropagation();
1275
- toggleCheckNode(node);
1276
- }}
1277
- >
1278
- {#if cs.checked}
1279
- <svg viewBox="0 0 16 16" width="12" height="12" aria-hidden="true" focusable="false">
1280
- <path fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" d="M3.5 8.5l3 3 6-6.5" />
1281
- </svg>
1282
- {:else if cs.half}
1283
- <svg viewBox="0 0 16 16" width="12" height="12" aria-hidden="true" focusable="false">
1284
- <path fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" d="M4 8h8" />
1285
- </svg>
1286
- {/if}
1287
- </span>
1288
- {/if}
1289
- <span class="cd-cascader__option-label">{node.label}</span>
1290
- {#if isLoading(node)}
1291
- <span class="cd-cascader__option-loading" aria-label={loc().t('Cascader.loading')}></span>
1292
- {:else if canExpand(node)}
1293
- <span class="cd-cascader__option-expand" aria-hidden="true">
1294
- {#if expandIcon}{@render expandIcon()}{:else}›{/if}
1295
- </span>
1296
- {/if}
1297
- </li>
1298
- {/each}
1299
- </ul>
1300
- {/each}
1301
- </div>
1302
- {/if}
1303
-
1304
- {#if bottomSlot}{@render bottomSlot()}{/if}
1305
- </div>
1306
- {/if}
1307
- </div>
1308
-
1309
- <style>
1310
- .cd-cascader {
1311
- position: relative;
1312
- display: inline-flex;
1313
- inline-size: 100%;
1314
- font-size: var(--cd-select-font-size);
1315
- }
1316
- .cd-cascader__trigger {
1317
- display: flex;
1318
- align-items: center;
1319
- gap: var(--cd-spacing-2);
1320
- inline-size: 100%;
1321
- min-block-size: var(--cd-select-height-default);
1322
- padding-inline: var(--cd-select-padding-x);
1323
- background: var(--cd-select-bg);
1324
- border: 1px solid var(--cd-select-border);
1325
- border-radius: var(--cd-select-radius);
1326
- color: inherit;
1327
- font: inherit;
1328
- text-align: start;
1329
- cursor: pointer;
1330
- transition: border-color var(--cd-motion-duration-fast) var(--cd-motion-ease-standard);
1331
- }
1332
- .cd-cascader--small .cd-cascader__trigger {
1333
- min-block-size: var(--cd-select-height-small);
1334
- }
1335
- .cd-cascader--large .cd-cascader__trigger {
1336
- min-block-size: var(--cd-select-height-large);
1337
- }
1338
- .cd-cascader__trigger:focus-visible {
1339
- outline: none;
1340
- border-color: var(--cd-select-border-active);
1341
- box-shadow: var(--cd-focus-ring);
1342
- }
1343
- .cd-cascader--open .cd-cascader__trigger {
1344
- border-color: var(--cd-select-border-active);
1345
- }
1346
- .cd-cascader--error .cd-cascader__trigger {
1347
- border-color: var(--cd-select-border-error);
1348
- }
1349
- .cd-cascader__trigger[aria-disabled='true'] {
1350
- background: var(--cd-color-fill-0);
1351
- color: var(--cd-color-text-3);
1352
- cursor: not-allowed;
1353
- }
1354
- .cd-cascader__content {
1355
- display: flex;
1356
- flex: 1 1 auto;
1357
- align-items: center;
1358
- min-inline-size: 0;
1359
- }
1360
- .cd-cascader__value {
1361
- overflow: hidden;
1362
- white-space: nowrap;
1363
- text-overflow: ellipsis;
1364
- }
1365
- .cd-cascader__placeholder {
1366
- color: var(--cd-input-color-placeholder);
1367
- }
1368
- .cd-cascader__clear,
1369
- .cd-cascader__arrow {
1370
- display: inline-flex;
1371
- align-items: center;
1372
- justify-content: center;
1373
- flex: 0 0 auto;
1374
- color: var(--cd-color-text-2);
1375
- }
1376
- .cd-cascader__clear {
1377
- cursor: pointer;
1378
- }
1379
- .cd-cascader__clear:hover {
1380
- color: var(--cd-color-text-0);
1381
- }
1382
- .cd-cascader__arrow {
1383
- transition: transform var(--cd-motion-duration-fast) var(--cd-motion-ease-standard);
1384
- }
1385
- .cd-cascader--open .cd-cascader__arrow {
1386
- transform: rotate(180deg);
1387
- }
1388
- /* 面板 portal 到 body,由 JS 写 position:fixed + transform 定位 */
1389
- .cd-cascader__panel {
1390
- z-index: var(--cd-select-dropdown-z);
1391
- display: flex;
1392
- flex-direction: column;
1393
- padding: 0;
1394
- background: var(--cd-select-dropdown-bg);
1395
- border-radius: var(--cd-select-dropdown-radius);
1396
- box-shadow: var(--cd-select-dropdown-shadow);
1397
- }
1398
- /* destroyOnClose=false 关闭后保留 DOM 但不可见、不可交互、不占位 */
1399
- .cd-cascader__panel--hidden {
1400
- display: none;
1401
- }
1402
- /* 级联列容器:横向排列各列 */
1403
- .cd-cascader__columns {
1404
- display: flex;
1405
- max-block-size: 16rem;
1406
- }
1407
- .cd-cascader__search {
1408
- padding: var(--cd-spacing-1) var(--cd-spacing-2);
1409
- border-block-end: 1px solid var(--cd-cascader-column-border);
1410
- }
1411
- .cd-cascader__search-input {
1412
- inline-size: 100%;
1413
- block-size: var(--cd-input-height-small);
1414
- padding-inline: var(--cd-input-padding-x);
1415
- background: var(--cd-input-bg, transparent);
1416
- color: inherit;
1417
- border: 1px solid var(--cd-input-border);
1418
- border-radius: var(--cd-input-radius);
1419
- font: inherit;
1420
- font-size: var(--cd-font-size-1);
1421
- }
1422
- .cd-cascader__search-input:focus-visible {
1423
- outline: none;
1424
- border-color: var(--cd-input-border-active);
1425
- box-shadow: var(--cd-focus-ring);
1426
- }
1427
- /* 搜索结果:扁平路径列表(单列纵向滚动) */
1428
- .cd-cascader__flat {
1429
- margin: 0;
1430
- padding-block: var(--cd-spacing-1);
1431
- padding-inline: 0;
1432
- list-style: none;
1433
- max-block-size: 16rem;
1434
- min-inline-size: var(--cd-cascader-column-width);
1435
- overflow-y: auto;
1436
- }
1437
- .cd-cascader__flat-option {
1438
- justify-content: flex-start;
1439
- }
1440
- .cd-cascader__empty {
1441
- padding: var(--cd-tree-node-padding-x);
1442
- color: var(--cd-color-text-3);
1443
- text-align: center;
1444
- }
1445
- .cd-cascader__highlight {
1446
- padding: 0;
1447
- color: var(--cd-tree-search-highlight-color);
1448
- background: var(--cd-tree-search-highlight-bg);
1449
- }
1450
- .cd-cascader__column {
1451
- margin: 0;
1452
- padding-block: var(--cd-spacing-1);
1453
- padding-inline: 0;
1454
- list-style: none;
1455
- overflow-y: auto;
1456
- inline-size: var(--cd-cascader-column-width);
1457
- border-inline-end: 1px solid var(--cd-cascader-column-border);
1458
- }
1459
- .cd-cascader__column:last-child {
1460
- border-inline-end: none;
1461
- }
1462
- .cd-cascader__option {
1463
- display: flex;
1464
- align-items: center;
1465
- justify-content: space-between;
1466
- gap: var(--cd-spacing-2);
1467
- block-size: var(--cd-tree-node-height);
1468
- padding-inline: var(--cd-tree-node-padding-x);
1469
- cursor: pointer;
1470
- }
1471
- .cd-cascader__option:hover {
1472
- background: var(--cd-tree-node-bg-hover);
1473
- }
1474
- .cd-cascader__option--active {
1475
- background: var(--cd-tree-node-bg-active);
1476
- }
1477
- /* 键盘 roving 高亮(aria-activedescendant 当前项),焦点环不依赖真实 DOM 焦点 */
1478
- .cd-cascader__option--kbactive {
1479
- box-shadow: var(--cd-focus-ring);
1480
- }
1481
- .cd-cascader__option--selected {
1482
- color: var(--cd-tree-node-color-selected);
1483
- }
1484
- .cd-cascader__option:focus-visible {
1485
- outline: none;
1486
- box-shadow: var(--cd-focus-ring);
1487
- }
1488
- .cd-cascader__option[aria-disabled='true'] {
1489
- color: var(--cd-color-text-3);
1490
- cursor: not-allowed;
1491
- }
1492
- .cd-cascader__option-label {
1493
- overflow: hidden;
1494
- white-space: nowrap;
1495
- text-overflow: ellipsis;
1496
- flex: 1 1 auto;
1497
- }
1498
- .cd-cascader__checkbox {
1499
- display: inline-flex;
1500
- align-items: center;
1501
- justify-content: center;
1502
- flex: 0 0 auto;
1503
- inline-size: 1rem;
1504
- block-size: 1rem;
1505
- color: #fff;
1506
- background: var(--cd-color-bg-1, #fff);
1507
- border: 1px solid var(--cd-color-border);
1508
- border-radius: var(--cd-radius-small, 3px);
1509
- cursor: pointer;
1510
- }
1511
- .cd-cascader__checkbox--checked,
1512
- .cd-cascader__checkbox--half {
1513
- background: var(--cd-color-primary);
1514
- border-color: var(--cd-color-primary);
1515
- }
1516
- .cd-cascader__tags {
1517
- display: flex;
1518
- flex-wrap: wrap;
1519
- gap: var(--cd-spacing-1);
1520
- align-items: center;
1521
- min-inline-size: 0;
1522
- }
1523
- .cd-cascader__option-expand {
1524
- flex: 0 0 auto;
1525
- color: var(--cd-tree-expand-icon-color);
1526
- }
1527
- .cd-cascader__option-loading {
1528
- flex: 0 0 auto;
1529
- inline-size: 12px;
1530
- block-size: 12px;
1531
- border: 2px solid var(--cd-tree-expand-icon-color, currentColor);
1532
- border-block-start-color: transparent;
1533
- border-radius: var(--cd-radius-full);
1534
- animation: cd-cascader-spin 0.7s linear infinite;
1535
- }
1536
- @keyframes cd-cascader-spin {
1537
- to {
1538
- transform: rotate(360deg);
1539
- }
1540
- }
1541
- @media (prefers-reduced-motion: reduce) {
1542
- .cd-cascader__option-loading {
1543
- animation: none;
1544
- }
1545
- .cd-cascader__trigger,
1546
- .cd-cascader__arrow {
1547
- transition: none;
1548
- }
1549
- }
1550
- /* --- 新增样式 --- */
1551
- .cd-cascader--borderless .cd-cascader__trigger {
1552
- border-color: transparent;
1553
- background: transparent;
1554
- }
1555
- .cd-cascader--borderless .cd-cascader__trigger:focus-visible {
1556
- border-color: var(--cd-select-border-active);
1557
- background: var(--cd-select-bg);
1558
- }
1559
- .cd-cascader--no-motion .cd-cascader__trigger,
1560
- .cd-cascader--no-motion .cd-cascader__arrow {
1561
- transition: none;
1562
- }
1563
- .cd-cascader__prefix,
1564
- .cd-cascader__suffix {
1565
- display: inline-flex;
1566
- align-items: center;
1567
- flex: 0 0 auto;
1568
- color: var(--cd-color-text-2);
1569
- }
1570
- </style>