@zerodep-dev/vue-tsx-pro-ui 0.1.0 → 0.3.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 (470) hide show
  1. package/dist/base/Badge.js +3 -3
  2. package/dist/base/Badge.js.map +1 -1
  3. package/dist/base/Can.d.ts +12 -0
  4. package/dist/base/Can.js +20 -0
  5. package/dist/base/Can.js.map +1 -0
  6. package/dist/base/ClientOnly.d.ts +10 -0
  7. package/dist/base/ClientOnly.js +18 -0
  8. package/dist/base/ClientOnly.js.map +1 -0
  9. package/dist/base/CloseButton.js +3 -3
  10. package/dist/base/CloseButton.js.map +1 -1
  11. package/dist/base/CodeBlock.js +5 -5
  12. package/dist/base/CodeBlock.js.map +1 -1
  13. package/dist/base/CopyButton.js +3 -3
  14. package/dist/base/CopyButton.js.map +1 -1
  15. package/dist/base/Icon.js +3 -3
  16. package/dist/base/Icon.js.map +1 -1
  17. package/dist/base/Indicator.js +3 -3
  18. package/dist/base/Indicator.js.map +1 -1
  19. package/dist/base/StatusDot.js +3 -3
  20. package/dist/base/StatusDot.js.map +1 -1
  21. package/dist/base/Tag.js +5 -5
  22. package/dist/base/Tag.js.map +1 -1
  23. package/dist/base/button-core.js +4 -4
  24. package/dist/base/button-core.js.map +1 -1
  25. package/dist/charts/AreaChart.js +3 -3
  26. package/dist/charts/AreaChart.js.map +1 -1
  27. package/dist/charts/BarChart.js +3 -3
  28. package/dist/charts/BarChart.js.map +1 -1
  29. package/dist/charts/ChordChart.js +3 -3
  30. package/dist/charts/ChordChart.js.map +1 -1
  31. package/dist/charts/ComboChart.js +3 -3
  32. package/dist/charts/ComboChart.js.map +1 -1
  33. package/dist/charts/DonutChart.js +3 -3
  34. package/dist/charts/DonutChart.js.map +1 -1
  35. package/dist/charts/FunnelChart.js +5 -5
  36. package/dist/charts/FunnelChart.js.map +1 -1
  37. package/dist/charts/GraphChart.js +3 -3
  38. package/dist/charts/GraphChart.js.map +1 -1
  39. package/dist/charts/LineChart.js +3 -3
  40. package/dist/charts/LineChart.js.map +1 -1
  41. package/dist/charts/MapChart.js +7 -7
  42. package/dist/charts/MapChart.js.map +1 -1
  43. package/dist/charts/RadarChart.js +16 -15
  44. package/dist/charts/RadarChart.js.map +1 -1
  45. package/dist/charts/SankeyChart.js +3 -3
  46. package/dist/charts/SankeyChart.js.map +1 -1
  47. package/dist/charts/ScatterChart.js +3 -3
  48. package/dist/charts/ScatterChart.js.map +1 -1
  49. package/dist/charts/TimelineChart.js +6 -5
  50. package/dist/charts/TimelineChart.js.map +1 -1
  51. package/dist/charts/TreemapChart.js +3 -3
  52. package/dist/charts/TreemapChart.js.map +1 -1
  53. package/dist/charts/WaterfallChart.js +3 -3
  54. package/dist/charts/WaterfallChart.js.map +1 -1
  55. package/dist/charts/XYChart.js +9 -7
  56. package/dist/charts/XYChart.js.map +1 -1
  57. package/dist/charts/frame.js +10 -8
  58. package/dist/charts/frame.js.map +1 -1
  59. package/dist/charts/single.js +4 -3
  60. package/dist/charts/single.js.map +1 -1
  61. package/dist/code-editor/CodeEditor.d.ts +18 -0
  62. package/dist/code-editor/CodeEditor.js +57 -0
  63. package/dist/code-editor/CodeEditor.js.map +1 -0
  64. package/dist/code-editor/editor.d.ts +52 -0
  65. package/dist/code-editor/editor.js +225 -0
  66. package/dist/code-editor/editor.js.map +1 -0
  67. package/dist/code-editor/index.d.ts +9 -0
  68. package/dist/code-editor/index.js +3 -0
  69. package/dist/config/Provider.d.ts +3 -1
  70. package/dist/config/Provider.js +9 -4
  71. package/dist/config/Provider.js.map +1 -1
  72. package/dist/config/context.d.ts +18 -0
  73. package/dist/config/context.js +16 -1
  74. package/dist/config/context.js.map +1 -1
  75. package/dist/config/createUi.d.ts +20 -3
  76. package/dist/config/createUi.js +80 -49
  77. package/dist/config/createUi.js.map +1 -1
  78. package/dist/config/lang/en-US.js +21 -3
  79. package/dist/config/lang/en-US.js.map +1 -1
  80. package/dist/config/lang/format.d.ts +2 -1
  81. package/dist/config/lang/format.js +9 -2
  82. package/dist/config/lang/format.js.map +1 -1
  83. package/dist/config/lang/lang.d.ts +19 -6
  84. package/dist/config/lang/lang.js +11 -2
  85. package/dist/config/lang/lang.js.map +1 -1
  86. package/dist/config/lang/zh-CN.js +21 -3
  87. package/dist/config/lang/zh-CN.js.map +1 -1
  88. package/dist/config/layout/preference.d.ts +43 -0
  89. package/dist/config/layout/preference.js +108 -0
  90. package/dist/config/layout/preference.js.map +1 -0
  91. package/dist/date/Calendar.js +3 -3
  92. package/dist/date/Calendar.js.map +1 -1
  93. package/dist/date/CalendarBoard.js +9 -9
  94. package/dist/date/CalendarBoard.js.map +1 -1
  95. package/dist/date/CalendarView.js +7 -7
  96. package/dist/date/CalendarView.js.map +1 -1
  97. package/dist/date/DateField.js +3 -3
  98. package/dist/date/DateField.js.map +1 -1
  99. package/dist/date/DatePicker.js +5 -5
  100. package/dist/date/DatePicker.js.map +1 -1
  101. package/dist/date/DateRangePicker.js +5 -5
  102. package/dist/date/DateRangePicker.js.map +1 -1
  103. package/dist/date/DateSegmentGroup.js +5 -5
  104. package/dist/date/DateSegmentGroup.js.map +1 -1
  105. package/dist/date/QuarterCalendar.js +6 -6
  106. package/dist/date/QuarterCalendar.js.map +1 -1
  107. package/dist/date/RangeCalendar.js +3 -3
  108. package/dist/date/RangeCalendar.js.map +1 -1
  109. package/dist/date/SegmentedField.js +3 -3
  110. package/dist/date/SegmentedField.js.map +1 -1
  111. package/dist/date/TimeField.js +3 -3
  112. package/dist/date/TimeField.js.map +1 -1
  113. package/dist/date/TimePicker.js +4 -4
  114. package/dist/date/TimePicker.js.map +1 -1
  115. package/dist/date/date-segments.js +1 -1
  116. package/dist/date/date-segments.js.map +1 -1
  117. package/dist/date/picker-popover.js +3 -2
  118. package/dist/date/picker-popover.js.map +1 -1
  119. package/dist/diagram/Diagram.js +12 -12
  120. package/dist/diagram/Diagram.js.map +1 -1
  121. package/dist/diagram/DiagramLanes.js +3 -3
  122. package/dist/diagram/DiagramLanes.js.map +1 -1
  123. package/dist/diagram/DiagramMinimap.js +4 -4
  124. package/dist/diagram/DiagramMinimap.js.map +1 -1
  125. package/dist/diagram/index.d.ts +2 -1
  126. package/dist/diagram/index.js +2 -1
  127. package/dist/diagram/layout.d.ts +57 -0
  128. package/dist/diagram/layout.js +50 -0
  129. package/dist/diagram/layout.js.map +1 -0
  130. package/dist/display/Accordion.js +6 -6
  131. package/dist/display/Accordion.js.map +1 -1
  132. package/dist/display/AnimatedNumber.js +4 -4
  133. package/dist/display/AnimatedNumber.js.map +1 -1
  134. package/dist/display/Avatar.js +4 -4
  135. package/dist/display/Avatar.js.map +1 -1
  136. package/dist/display/Checklist.js +7 -7
  137. package/dist/display/Checklist.js.map +1 -1
  138. package/dist/display/Collapse.js +8 -5
  139. package/dist/display/Collapse.js.map +1 -1
  140. package/dist/display/Collapsible.js +3 -3
  141. package/dist/display/Collapsible.js.map +1 -1
  142. package/dist/display/Descriptions.js +5 -5
  143. package/dist/display/Descriptions.js.map +1 -1
  144. package/dist/display/Ellipsis.js +3 -3
  145. package/dist/display/Ellipsis.js.map +1 -1
  146. package/dist/display/FieldCompare.js +4 -4
  147. package/dist/display/FieldCompare.js.map +1 -1
  148. package/dist/display/Gauge.js +11 -11
  149. package/dist/display/Gauge.js.map +1 -1
  150. package/dist/display/Image.js +4 -4
  151. package/dist/display/Image.js.map +1 -1
  152. package/dist/display/Marquee.js +3 -3
  153. package/dist/display/Marquee.js.map +1 -1
  154. package/dist/display/ProportionBar.js +4 -4
  155. package/dist/display/ProportionBar.js.map +1 -1
  156. package/dist/display/Sortable.js +4 -4
  157. package/dist/display/Sortable.js.map +1 -1
  158. package/dist/display/Sparkline.js +6 -6
  159. package/dist/display/Sparkline.js.map +1 -1
  160. package/dist/display/Stat.js +3 -3
  161. package/dist/display/Stat.js.map +1 -1
  162. package/dist/display/Timeline.js +7 -7
  163. package/dist/display/Timeline.js.map +1 -1
  164. package/dist/display/Tree.js +10 -10
  165. package/dist/display/Tree.js.map +1 -1
  166. package/dist/display/VirtualList.js +7 -7
  167. package/dist/display/VirtualList.js.map +1 -1
  168. package/dist/display/Watermark.js +3 -3
  169. package/dist/display/Watermark.js.map +1 -1
  170. package/dist/docs-kit/CodeGroup.js +5 -5
  171. package/dist/docs-kit/CodeGroup.js.map +1 -1
  172. package/dist/docs-kit/CodePreview.js +6 -6
  173. package/dist/docs-kit/CodePreview.js.map +1 -1
  174. package/dist/docs-kit/PropsTable.js +4 -4
  175. package/dist/docs-kit/PropsTable.js.map +1 -1
  176. package/dist/feedback/Alert.js +5 -5
  177. package/dist/feedback/Alert.js.map +1 -1
  178. package/dist/feedback/Banner.js +5 -5
  179. package/dist/feedback/Banner.js.map +1 -1
  180. package/dist/feedback/Empty.js +4 -4
  181. package/dist/feedback/Empty.js.map +1 -1
  182. package/dist/feedback/LoadingBar.js +20 -13
  183. package/dist/feedback/LoadingBar.js.map +1 -1
  184. package/dist/feedback/LoadingOverlay.js +3 -3
  185. package/dist/feedback/LoadingOverlay.js.map +1 -1
  186. package/dist/feedback/Progress.js +14 -14
  187. package/dist/feedback/Progress.js.map +1 -1
  188. package/dist/feedback/Result.js +4 -4
  189. package/dist/feedback/Result.js.map +1 -1
  190. package/dist/feedback/SkeletonText.js +3 -3
  191. package/dist/feedback/SkeletonText.js.map +1 -1
  192. package/dist/feedback/Toast.js +6 -6
  193. package/dist/feedback/Toast.js.map +1 -1
  194. package/dist/form/Field.d.ts +7 -0
  195. package/dist/form/Field.js +20 -9
  196. package/dist/form/Field.js.map +1 -1
  197. package/dist/form/Form.js +3 -3
  198. package/dist/form/Form.js.map +1 -1
  199. package/dist/form/FormErrorSummary.js +3 -3
  200. package/dist/form/FormErrorSummary.js.map +1 -1
  201. package/dist/form/FormSection.js +6 -6
  202. package/dist/form/FormSection.js.map +1 -1
  203. package/dist/form/useForm.d.ts +4 -1
  204. package/dist/form/useForm.js +35 -13
  205. package/dist/form/useForm.js.map +1 -1
  206. package/dist/index.d.ts +18 -10
  207. package/dist/index.js +17 -9
  208. package/dist/input/Autocomplete.js +3 -3
  209. package/dist/input/Autocomplete.js.map +1 -1
  210. package/dist/input/Cascader.js +7 -7
  211. package/dist/input/Cascader.js.map +1 -1
  212. package/dist/input/CharacterCount.js +3 -3
  213. package/dist/input/CharacterCount.js.map +1 -1
  214. package/dist/input/Checkbox.js +5 -5
  215. package/dist/input/Checkbox.js.map +1 -1
  216. package/dist/input/ColorField.js +4 -4
  217. package/dist/input/ColorField.js.map +1 -1
  218. package/dist/input/Combobox.js +5 -5
  219. package/dist/input/Combobox.js.map +1 -1
  220. package/dist/input/ConditionBuilder.js +15 -14
  221. package/dist/input/ConditionBuilder.js.map +1 -1
  222. package/dist/input/FileDrop.js +3 -3
  223. package/dist/input/FileDrop.js.map +1 -1
  224. package/dist/input/Listbox.js +9 -9
  225. package/dist/input/Listbox.js.map +1 -1
  226. package/dist/input/Mentions.js +4 -4
  227. package/dist/input/Mentions.js.map +1 -1
  228. package/dist/input/NumberField.js +3 -3
  229. package/dist/input/NumberField.js.map +1 -1
  230. package/dist/input/NumberRangeField.js +5 -5
  231. package/dist/input/NumberRangeField.js.map +1 -1
  232. package/dist/input/OtpField.js +5 -5
  233. package/dist/input/OtpField.js.map +1 -1
  234. package/dist/input/PeoplePicker.js +6 -6
  235. package/dist/input/PeoplePicker.js.map +1 -1
  236. package/dist/input/PeoplePickerPanel.js +15 -15
  237. package/dist/input/PeoplePickerPanel.js.map +1 -1
  238. package/dist/input/Radio.js +4 -4
  239. package/dist/input/Radio.js.map +1 -1
  240. package/dist/input/Rating.js +5 -5
  241. package/dist/input/Rating.js.map +1 -1
  242. package/dist/input/Select.js +5 -5
  243. package/dist/input/Select.js.map +1 -1
  244. package/dist/input/SelectedTags.js +4 -4
  245. package/dist/input/SelectedTags.js.map +1 -1
  246. package/dist/input/Slider.js +6 -6
  247. package/dist/input/Slider.js.map +1 -1
  248. package/dist/input/Switch.js +3 -3
  249. package/dist/input/Switch.js.map +1 -1
  250. package/dist/input/TagsInput.js +5 -5
  251. package/dist/input/TagsInput.js.map +1 -1
  252. package/dist/input/TextField.js +5 -5
  253. package/dist/input/TextField.js.map +1 -1
  254. package/dist/input/Textarea.js +4 -4
  255. package/dist/input/Textarea.js.map +1 -1
  256. package/dist/input/ToggleGroup.js +4 -4
  257. package/dist/input/ToggleGroup.js.map +1 -1
  258. package/dist/input/Transfer.js +8 -8
  259. package/dist/input/Transfer.js.map +1 -1
  260. package/dist/input/TreeSelect.js +7 -7
  261. package/dist/input/TreeSelect.js.map +1 -1
  262. package/dist/input/Upload.js +10 -9
  263. package/dist/input/Upload.js.map +1 -1
  264. package/dist/input/condition.d.ts +1 -1
  265. package/dist/input/condition.js +32 -14
  266. package/dist/input/condition.js.map +1 -1
  267. package/dist/input/people.d.ts +15 -14
  268. package/dist/input/people.js +5 -5
  269. package/dist/input/people.js.map +1 -1
  270. package/dist/input/remote-items.d.ts +4 -2
  271. package/dist/input/remote-items.js +7 -2
  272. package/dist/input/remote-items.js.map +1 -1
  273. package/dist/layout/Affix.js +4 -4
  274. package/dist/layout/Affix.js.map +1 -1
  275. package/dist/layout/ResizeHandle.d.ts +6 -2
  276. package/dist/layout/ResizeHandle.js +32 -9
  277. package/dist/layout/ResizeHandle.js.map +1 -1
  278. package/dist/layout/ScrollArea.js +3 -3
  279. package/dist/layout/ScrollArea.js.map +1 -1
  280. package/dist/layout/Separator.js +3 -3
  281. package/dist/layout/Separator.js.map +1 -1
  282. package/dist/layout/Splitter.js +3 -3
  283. package/dist/layout/Splitter.js.map +1 -1
  284. package/dist/layout/ViewportFill.d.ts +2 -2
  285. package/dist/layout/ViewportFill.js +4 -4
  286. package/dist/layout/ViewportFill.js.map +1 -1
  287. package/dist/navigation/Breadcrumbs.js +4 -4
  288. package/dist/navigation/Breadcrumbs.js.map +1 -1
  289. package/dist/navigation/NavBar.js +6 -6
  290. package/dist/navigation/NavBar.js.map +1 -1
  291. package/dist/navigation/NavList.d.ts +1 -0
  292. package/dist/navigation/NavList.js +20 -12
  293. package/dist/navigation/NavList.js.map +1 -1
  294. package/dist/navigation/NavMenu.d.ts +27 -0
  295. package/dist/navigation/NavMenu.js +54 -0
  296. package/dist/navigation/NavMenu.js.map +1 -0
  297. package/dist/navigation/PageJump.js +3 -3
  298. package/dist/navigation/PageJump.js.map +1 -1
  299. package/dist/navigation/PageSizeSelect.js +4 -4
  300. package/dist/navigation/PageSizeSelect.js.map +1 -1
  301. package/dist/navigation/Pagination.js +6 -6
  302. package/dist/navigation/Pagination.js.map +1 -1
  303. package/dist/navigation/PaginationBar.js +5 -5
  304. package/dist/navigation/PaginationBar.js.map +1 -1
  305. package/dist/navigation/Steps.js +4 -4
  306. package/dist/navigation/Steps.js.map +1 -1
  307. package/dist/navigation/TableOfContents.js +5 -5
  308. package/dist/navigation/TableOfContents.js.map +1 -1
  309. package/dist/navigation/Tabs.js +7 -7
  310. package/dist/navigation/Tabs.js.map +1 -1
  311. package/dist/navigation/Toolbar.js +3 -3
  312. package/dist/navigation/Toolbar.js.map +1 -1
  313. package/dist/overlay/AlertDialog.js +4 -4
  314. package/dist/overlay/AlertDialog.js.map +1 -1
  315. package/dist/overlay/CommandPalette.js +4 -4
  316. package/dist/overlay/CommandPalette.js.map +1 -1
  317. package/dist/overlay/ContextMenu.js +3 -3
  318. package/dist/overlay/ContextMenu.js.map +1 -1
  319. package/dist/overlay/Dialog.js +4 -4
  320. package/dist/overlay/Dialog.js.map +1 -1
  321. package/dist/overlay/Drawer.js +5 -5
  322. package/dist/overlay/Drawer.js.map +1 -1
  323. package/dist/overlay/HoverCard.js +5 -3
  324. package/dist/overlay/HoverCard.js.map +1 -1
  325. package/dist/overlay/Lightbox.js +5 -5
  326. package/dist/overlay/Lightbox.js.map +1 -1
  327. package/dist/overlay/Menu.js +7 -7
  328. package/dist/overlay/Menu.js.map +1 -1
  329. package/dist/overlay/ModalCore.js +9 -9
  330. package/dist/overlay/ModalCore.js.map +1 -1
  331. package/dist/overlay/Popconfirm.js +6 -6
  332. package/dist/overlay/Popconfirm.js.map +1 -1
  333. package/dist/overlay/Popover.js +5 -3
  334. package/dist/overlay/Popover.js.map +1 -1
  335. package/dist/overlay/Tooltip.js +5 -3
  336. package/dist/overlay/Tooltip.js.map +1 -1
  337. package/dist/overlay/Tour.js +8 -8
  338. package/dist/overlay/Tour.js.map +1 -1
  339. package/dist/overlay/detached.d.ts +2 -0
  340. package/dist/overlay/detached.js +73 -42
  341. package/dist/overlay/detached.js.map +1 -1
  342. package/dist/patterns/ActionBar.js +3 -3
  343. package/dist/patterns/ActionBar.js.map +1 -1
  344. package/dist/patterns/AppShell.d.ts +28 -1
  345. package/dist/patterns/AppShell.js +158 -52
  346. package/dist/patterns/AppShell.js.map +1 -1
  347. package/dist/patterns/AuthLayout.d.ts +28 -0
  348. package/dist/patterns/AuthLayout.js +77 -0
  349. package/dist/patterns/AuthLayout.js.map +1 -0
  350. package/dist/patterns/CommentThread.js +9 -9
  351. package/dist/patterns/CommentThread.js.map +1 -1
  352. package/dist/patterns/DataImport.js +4 -4
  353. package/dist/patterns/DataImport.js.map +1 -1
  354. package/dist/patterns/DeadlineBadge.js +3 -3
  355. package/dist/patterns/DeadlineBadge.js.map +1 -1
  356. package/dist/patterns/FilterBar.d.ts +11 -1
  357. package/dist/patterns/FilterBar.js +39 -10
  358. package/dist/patterns/FilterBar.js.map +1 -1
  359. package/dist/patterns/FormDialog.d.ts +18 -0
  360. package/dist/patterns/FormDialog.js +46 -0
  361. package/dist/patterns/FormDialog.js.map +1 -0
  362. package/dist/patterns/KanbanBoard.js +8 -8
  363. package/dist/patterns/KanbanBoard.js.map +1 -1
  364. package/dist/patterns/LayoutSwitcher.d.ts +12 -0
  365. package/dist/patterns/LayoutSwitcher.js +79 -0
  366. package/dist/patterns/LayoutSwitcher.js.map +1 -0
  367. package/dist/patterns/NotificationCenter.js +8 -8
  368. package/dist/patterns/NotificationCenter.js.map +1 -1
  369. package/dist/patterns/PageTabs.d.ts +12 -7
  370. package/dist/patterns/PageTabs.js +41 -29
  371. package/dist/patterns/PageTabs.js.map +1 -1
  372. package/dist/patterns/QueryTable.d.ts +10 -1
  373. package/dist/patterns/QueryTable.js +128 -68
  374. package/dist/patterns/QueryTable.js.map +1 -1
  375. package/dist/patterns/SlideCaptcha.d.ts +80 -0
  376. package/dist/patterns/SlideCaptcha.js +354 -0
  377. package/dist/patterns/SlideCaptcha.js.map +1 -0
  378. package/dist/patterns/StepsForm.js +7 -7
  379. package/dist/patterns/StepsForm.js.map +1 -1
  380. package/dist/patterns/ThemeSwitcher.js +3 -3
  381. package/dist/patterns/ThemeSwitcher.js.map +1 -1
  382. package/dist/patterns/index.d.ts +7 -3
  383. package/dist/patterns/index.js +5 -1
  384. package/dist/patterns/messages.d.ts +39 -1
  385. package/dist/patterns/messages.js +40 -2
  386. package/dist/patterns/messages.js.map +1 -1
  387. package/dist/table/ColumnHeaderActions.js +3 -3
  388. package/dist/table/ColumnHeaderActions.js.map +1 -1
  389. package/dist/table/ColumnResizer.js +3 -3
  390. package/dist/table/ColumnResizer.js.map +1 -1
  391. package/dist/table/ColumnSettings.js +5 -5
  392. package/dist/table/ColumnSettings.js.map +1 -1
  393. package/dist/table/ColumnTable.js +8 -8
  394. package/dist/table/ColumnTable.js.map +1 -1
  395. package/dist/table/DataTable.js +8 -8
  396. package/dist/table/DataTable.js.map +1 -1
  397. package/dist/table/DataTableView.js +21 -21
  398. package/dist/table/DataTableView.js.map +1 -1
  399. package/dist/table/PivotTable.js +3 -3
  400. package/dist/table/PivotTable.js.map +1 -1
  401. package/dist/table/SelectionBar.js +4 -4
  402. package/dist/table/SelectionBar.js.map +1 -1
  403. package/dist/table/TreeDataTable.js +5 -5
  404. package/dist/table/TreeDataTable.js.map +1 -1
  405. package/dist/theme-customize/ThemeCustomizer.js +7 -6
  406. package/dist/theme-customize/ThemeCustomizer.js.map +1 -1
  407. package/dist/theme-customize/ThemeThumbnail.js +3 -3
  408. package/dist/theme-customize/ThemeThumbnail.js.map +1 -1
  409. package/dist/three/background/AuroraBackground.js +3 -3
  410. package/dist/three/background/AuroraBackground.js.map +1 -1
  411. package/dist/three/background/MeshGradient.js +3 -3
  412. package/dist/three/background/MeshGradient.js.map +1 -1
  413. package/dist/three/background/ShaderBackground.js +3 -3
  414. package/dist/three/background/ShaderBackground.js.map +1 -1
  415. package/dist/three/core/EffectSurface.js +3 -3
  416. package/dist/three/core/EffectSurface.js.map +1 -1
  417. package/dist/three/core/ThreeCanvas.js +4 -4
  418. package/dist/three/core/ThreeCanvas.js.map +1 -1
  419. package/dist/three/effects/Confetti.js +3 -3
  420. package/dist/three/effects/Confetti.js.map +1 -1
  421. package/dist/three/effects/ParticleField.js +3 -3
  422. package/dist/three/effects/ParticleField.js.map +1 -1
  423. package/dist/three/effects/WaveGrid.js +3 -3
  424. package/dist/three/effects/WaveGrid.js.map +1 -1
  425. package/dist/three/globe/Globe.js +3 -3
  426. package/dist/three/globe/Globe.js.map +1 -1
  427. package/dist/three/model/ModelViewer.js +3 -3
  428. package/dist/three/model/ModelViewer.js.map +1 -1
  429. package/dist/utils/data/clone.d.ts +8 -0
  430. package/dist/utils/data/clone.js +21 -0
  431. package/dist/utils/data/clone.js.map +1 -0
  432. package/dist/utils/data/date.d.ts +10 -0
  433. package/dist/utils/data/date.js +13 -1
  434. package/dist/utils/data/date.js.map +1 -1
  435. package/dist/utils/data/number.d.ts +5 -0
  436. package/dist/utils/data/number.js +8 -1
  437. package/dist/utils/data/number.js.map +1 -1
  438. package/dist/utils/data/numeric.d.ts +7 -0
  439. package/dist/utils/data/numeric.js +46 -1
  440. package/dist/utils/data/numeric.js.map +1 -1
  441. package/dist/utils/dom/download.d.ts +8 -1
  442. package/dist/utils/dom/download.js +19 -2
  443. package/dist/utils/dom/download.js.map +1 -1
  444. package/dist/utils/dom/element.d.ts +3 -1
  445. package/dist/utils/dom/element.js +8 -4
  446. package/dist/utils/dom/element.js.map +1 -1
  447. package/dist/utils/dom/fullscreen.d.ts +1 -1
  448. package/dist/utils/dom/fullscreen.js +12 -8
  449. package/dist/utils/dom/fullscreen.js.map +1 -1
  450. package/dist/utils/dom/mounted.d.ts +7 -0
  451. package/dist/utils/dom/mounted.js +16 -0
  452. package/dist/utils/dom/mounted.js.map +1 -0
  453. package/dist/utils/dom/overflow.js +2 -1
  454. package/dist/utils/dom/overflow.js.map +1 -1
  455. package/dist/utils/dom/pan-zoom.js +2 -1
  456. package/dist/utils/dom/pan-zoom.js.map +1 -1
  457. package/dist/utils/props.js +3 -13
  458. package/dist/utils/props.js.map +1 -1
  459. package/dist/utils/request/error-message.d.ts +17 -0
  460. package/dist/utils/request/error-message.js +50 -0
  461. package/dist/utils/request/error-message.js.map +1 -0
  462. package/dist/utils/request/http.d.ts +80 -4
  463. package/dist/utils/request/http.js +121 -24
  464. package/dist/utils/request/http.js.map +1 -1
  465. package/dist/utils/request/use-autosave.d.ts +35 -0
  466. package/dist/utils/request/use-autosave.js +69 -0
  467. package/dist/utils/request/use-autosave.js.map +1 -0
  468. package/dist/utils/request/use-request.js +4 -2
  469. package/dist/utils/request/use-request.js.map +1 -1
  470. package/package.json +57 -4
@@ -1 +1 @@
1
- {"version":3,"file":"FormSection.js","names":["_isVNode","_vtp_readonlyProp","_vtp_bindable","_vtp_childrenProp","_vtp_restProps","_createVNode","_mergeProps","Text","_vue_unref"],"sources":["../../src/form/FormSection.tsx"],"sourcesContent":["/**\n * 表单里的一组字段:标题、说明、右侧的操作,下面一条细线,再下面是字段的栅格。长表单按主题分组(基本信息、计划与实施……)。\n *\n * <FormSection title=\"项目基本信息\" columns={2}>\n * <Field label=\"项目名称\">…</Field>\n * <Field label=\"任务类别\">…</Field>\n * <GridItem span=\"full\"><Field label=\"简要描述\">…</Field></GridItem>\n * </FormSection>\n *\n * columns 是最多几列:每列不窄于 minColumnWidth,容器窄时自动减少列数,不需要断点;跨列用 GridItem(多列时用 span=\"full\")。\n * 也是一层字段布局(field-layout):标签位置、宽度、对齐、只读、不可用写在这里只作用于这一组,没写的沿用 Form 的。\n * collapsible 时标题是展开收起的按钮(aria-expanded),收起的字段不能被 Tab 到。\n * 给了 id 就能当锚点:长表单旁边放一个 TableOfContents(items 用各组的 id 与标题)做分节导航,跳过来时让开粘性顶栏。\n * 语义:role=\"group\" 以标题为名称、说明为描述,标题是 h2–h6(headingLevel),读屏能按标题跳转。\n *\n * 部件:Root(div)、Header(div)、Heading(h2–h6)、Trigger(ButtonBase,collapsible 时)、Chevron(Icon)、\n * Description(Text)、Extra(div)、Body(div,字段的栅格)、Collapse(collapsible 时包住 Body)。\n */\nimport { ChevronDown } from '@lucide/icons';\nimport { _component } from '@zerodep-dev/vue-tsx-pro-tsx';\nimport * as css from '@zerodep-dev/vue-tsx-pro-tsx/styles';\nimport { h, useId, type VNodeChild } from 'vue';\nimport { ButtonBase, type ButtonBaseProps } from '../base/ButtonBase';\nimport { Icon, type IconProps } from '../base/Icon';\nimport { Text, type TextProps } from '../base/Text';\nimport { space, t } from '../config/theme/tokens';\nimport { Collapse, type CollapseProps } from '../display/Collapse';\nimport { provideGridColumns } from '../layout/Grid';\nimport { hasContent, mergeAttrs, mergePartProps, type PartProps } from '../utils/props';\nimport { disclosureStyles } from '../utils/style/transitions';\nimport { layoutVars } from '../utils/style/vars';\nimport { provideFieldLayout, type FieldLayout } from './field-layout';\n\n/** 角色与关联由组件管理;原生的 title 属性让给标题内容 */\nexport type FormSectionManaged = 'role' | 'title' | 'aria-labelledby' | 'aria-describedby';\n\n/** 未声明的属性落到外层 div 上 */\nexport interface FormSectionProps extends PartProps<'div', FormSectionManaged>, FieldLayout {\n /** 标题;不写时没有标题栏,只是一层栅格与字段布局 */\n title?: VNodeChild;\n /** 标题下面的说明 */\n description?: VNodeChild;\n /** 标题栏结束边的内容:链接、按钮(“查看关联明细”“添加”) */\n extra?: VNodeChild;\n /** 标题的级别(h2–h6),默认 3 */\n headingLevel?: 2 | 3 | 4 | 5 | 6;\n /** 最多几列,默认 1;容器窄时按 minColumnWidth 自动减少 */\n columns?: number;\n /** 每列的最小宽度(CSS 长度),默认 `'26em'`:标签在起始边时放得下标签、控件与横排的三四个选项 */\n minColumnWidth?: string;\n /** 可以展开收起:标题变成按钮 */\n collapsible?: boolean;\n /** 是否展开(collapsible 时):父级写 _open={x},默认展开 */\n _open?: boolean;\n /** 标题栏的部件属性 */\n propsHeader?: PartProps<'div'>;\n /** 标题(h2–h6)的部件属性 */\n propsHeading?: PartProps<'h3'>;\n /** 展开收起按钮的部件属性 */\n propsTrigger?: Omit<Partial<ButtonBaseProps>, 'aria-expanded' | 'aria-controls'>;\n /** 展开箭头的部件属性 */\n propsChevron?: Partial<IconProps>;\n /** 说明的部件属性 */\n propsDescription?: Partial<TextProps>;\n /** 标题栏结束边内容的部件属性 */\n propsExtra?: PartProps<'div'>;\n /** 字段栅格的部件属性 */\n propsBody?: PartProps<'div'>;\n /** 展开收起层的部件属性(collapsible 时) */\n propsCollapse?: Partial<CollapseProps>;\n /** 字段 */\n children?: VNodeChild;\n}\n\n/** FormSection 的样式:root、header、headerText、heading、trigger、chevron、description、extra、body、singleColumn */\nexport const formSectionStyles = css.create({\n root: {\n display: 'flex',\n flexDirection: 'column',\n gap: '_lg',\n minWidth: 0,\n // 分节导航(TableOfContents、锚点)跳过来时让开粘性顶栏,再留一点空\n scrollMarginBlockStart: `calc(${layoutVars.stickyOffset} + ${space.lg})`,\n },\n header: {\n display: 'flex',\n alignItems: 'flex-end',\n justifyContent: 'space-between',\n flexWrap: 'wrap',\n columnGap: '_lg',\n rowGap: '_xs',\n paddingBlockEnd: '_sm',\n borderBlockEndWidth: '_xs',\n borderBlockEndStyle: 'solid',\n borderBlockEndColor: '_borderMuted',\n },\n headerText: { display: 'flex', flexDirection: 'column', gap: '_xs', minWidth: 0 },\n heading: {\n margin: 0,\n fontSize: '_md',\n fontWeight: '_semibold',\n lineHeight: '_tight',\n color: '_text',\n },\n trigger: {\n display: 'inline-flex',\n alignItems: 'center',\n gap: '_xs',\n marginInline: `calc(-1 * ${t.space.xs})`,\n paddingInline: '_xs',\n borderRadius: '_sm',\n fontWeight: 'inherit',\n color: 'inherit',\n textAlign: 'start',\n backgroundColor: { default: 'transparent', ':hover': '_fill' },\n },\n chevron: { flexShrink: 0, color: '_textMuted' },\n chevronClosed: { rotate: '-90deg' },\n description: { fontSize: '_sm', lineHeight: '_normal' },\n extra: { display: 'flex', alignItems: 'center', gap: '_sm', flexShrink: 0 },\n // 多列时列间距比行间距大:一眼看出哪几项是同一列\n body: {\n '--form-section-column-gap': '_space.xl',\n display: 'grid',\n columnGap: 'var(--form-section-column-gap)',\n rowGap: '_md',\n gridTemplateColumns: 'minmax(0, 1fr)',\n alignItems: 'start',\n },\n});\n\n/** FormSection 的样式:columns(最多几列、每列不窄于多少) */\nexport const formSectionColumnStyles = css.create({\n columns: (count: number, minWidth: string) => ({\n gridTemplateColumns: `repeat(auto-fill, minmax(max(min(${minWidth}, 100%), calc((100% - ${count - 1} * var(--form-section-column-gap)) / ${count})), 1fr))`,\n }),\n});\n\n/** 表单里的一组字段:标题栏、字段栅格,可折叠;这一组的标签布局、只读、不可用 */\nexport const FormSection = _component(\n ({\n title,\n description,\n extra,\n headingLevel = 3,\n columns = 1,\n minColumnWidth = '26em',\n collapsible = false,\n _open: open = _bindable(true),\n labelPlacement,\n labelWidth,\n labelAlign,\n disabled,\n readonly,\n propsHeader,\n propsHeading,\n propsTrigger,\n propsChevron,\n propsDescription,\n propsExtra,\n propsBody,\n propsCollapse,\n children,\n ...rest\n }: FormSectionProps) => {\n const id = useId();\n const ids = { title: `${id}-title`, description: `${id}-description`, body: `${id}-body` };\n provideFieldLayout(() => ({ labelPlacement, labelWidth, labelAlign, disabled, readonly }));\n const bodyRef = provideGridColumns(() =>\n columns > 1 ? { columns, minItemWidth: minColumnWidth } : { columns: 1 },\n );\n\n return () => {\n const expanded = !collapsible || open;\n const state = expanded ? 'open' : 'closed';\n const titleContent = collapsible ? (\n <ButtonBase\n {...mergePartProps<ButtonBaseProps>({ sx: formSectionStyles.trigger }, propsTrigger, {\n id: ids.title,\n 'aria-expanded': open,\n 'aria-controls': ids.body,\n onClick: () => (open = !open),\n })}\n data-state={state}\n >\n <span>{title}</span>\n <Icon\n {...mergePartProps<IconProps>(\n {\n icon: ChevronDown,\n sx: [\n formSectionStyles.chevron,\n disclosureStyles.icon,\n !open && formSectionStyles.chevronClosed,\n ],\n },\n propsChevron,\n )}\n />\n </ButtonBase>\n ) : (\n <span id={ids.title}>{title}</span>\n );\n const body = (\n <div\n {...mergeAttrs<'div'>(\n {\n sx: [\n formSectionStyles.body,\n columns > 1 && formSectionColumnStyles.columns(columns, minColumnWidth),\n ],\n },\n propsBody,\n { ref: bodyRef, ...(collapsible ? {} : { id: ids.body }) },\n )}\n >\n {children}\n </div>\n );\n return (\n <div\n {...mergeAttrs<'div'>({ sx: formSectionStyles.root }, rest, {\n role: hasContent(title) ? 'group' : undefined,\n 'aria-labelledby': hasContent(title) ? ids.title : undefined,\n 'aria-describedby': hasContent(description) ? ids.description : undefined,\n })}\n data-state={collapsible ? state : undefined}\n >\n {(title != null || extra != null) && (\n <div {...mergeAttrs<'div'>({ sx: formSectionStyles.header }, propsHeader)}>\n <div {...css.attrs(formSectionStyles.headerText)}>\n {hasContent(title) &&\n h(\n `h${headingLevel}`,\n mergeAttrs<'h3'>({ sx: formSectionStyles.heading }, propsHeading),\n [titleContent],\n )}\n {hasContent(description) && (\n <Text\n {...mergePartProps<TextProps>(\n { tone: 'muted', sx: formSectionStyles.description },\n propsDescription,\n { id: ids.description },\n )}\n >\n {description}\n </Text>\n )}\n </div>\n {hasContent(extra) && (\n <div {...mergeAttrs<'div'>({ sx: formSectionStyles.extra }, propsExtra)}>\n {extra}\n </div>\n )}\n </div>\n )}\n {collapsible ? (\n <Collapse\n {...mergePartProps<CollapseProps>({}, propsCollapse, { id: ids.body })}\n _open={open}\n >\n {body}\n </Collapse>\n ) : (\n body\n )}\n </div>\n );\n };\n },\n);\n"],"mappings":"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"}
1
+ {"version":3,"file":"FormSection.js","names":["_isVNode","_vtp_readonlyProp","_vtp_bindable","_vtp_childrenProp","_vtp_restProps","_createVNode","_mergeProps","Text","_vue_unref"],"sources":["../../src/form/FormSection.tsx"],"sourcesContent":["/**\n * 表单里的一组字段:标题、说明、右侧的操作,下面一条细线,再下面是字段的栅格。长表单按主题分组(基本信息、计划与实施……)。\n *\n * <FormSection title=\"项目基本信息\" columns={2}>\n * <Field label=\"项目名称\">…</Field>\n * <Field label=\"任务类别\">…</Field>\n * <GridItem span=\"full\"><Field label=\"简要描述\">…</Field></GridItem>\n * </FormSection>\n *\n * columns 是最多几列:每列不窄于 minColumnWidth,容器窄时自动减少列数,不需要断点;跨列用 GridItem(多列时用 span=\"full\")。\n * 也是一层字段布局(field-layout):标签位置、宽度、对齐、只读、不可用写在这里只作用于这一组,没写的沿用 Form 的。\n * collapsible 时标题是展开收起的按钮(aria-expanded),收起的字段不能被 Tab 到。\n * 给了 id 就能当锚点:长表单旁边放一个 TableOfContents(items 用各组的 id 与标题)做分节导航,跳过来时让开粘性顶栏。\n * 语义:role=\"group\" 以标题为名称、说明为描述,标题是 h2–h6(headingLevel),读屏能按标题跳转。\n *\n * 部件:Root(div)、Header(div)、Heading(h2–h6)、Trigger(ButtonBase,collapsible 时)、Chevron(Icon)、\n * Description(Text)、Extra(div)、Body(div,字段的栅格)、Collapse(collapsible 时包住 Body)。\n */\nimport { ChevronDown } from '@lucide/icons';\nimport { _component } from '@zerodep-dev/vue-tsx-pro-tsx';\nimport * as css from '@zerodep-dev/vue-tsx-pro-tsx/styles';\nimport { h, useId, type VNodeChild } from 'vue';\nimport { ButtonBase, type ButtonBaseProps } from '../base/ButtonBase';\nimport { Icon, type IconProps } from '../base/Icon';\nimport { Text, type TextProps } from '../base/Text';\nimport { space, t } from '../config/theme/tokens';\nimport { Collapse, type CollapseProps } from '../display/Collapse';\nimport { provideGridColumns } from '../layout/Grid';\nimport { hasContent, mergeAttrs, mergePartProps, type PartProps } from '../utils/props';\nimport { disclosureStyles } from '../utils/style/transitions';\nimport { layoutVars } from '../utils/style/vars';\nimport { provideFieldLayout, type FieldLayout } from './field-layout';\n\n/** 角色与关联由组件管理;原生的 title 属性让给标题内容 */\nexport type FormSectionManaged = 'role' | 'title' | 'aria-labelledby' | 'aria-describedby';\n\n/** 未声明的属性落到外层 div 上 */\nexport interface FormSectionProps extends PartProps<'div', FormSectionManaged>, FieldLayout {\n /** 标题;不写时没有标题栏,只是一层栅格与字段布局 */\n title?: VNodeChild;\n /** 标题下面的说明 */\n description?: VNodeChild;\n /** 标题栏结束边的内容:链接、按钮(“查看关联明细”“添加”) */\n extra?: VNodeChild;\n /** 标题的级别(h2–h6),默认 3 */\n headingLevel?: 2 | 3 | 4 | 5 | 6;\n /** 最多几列,默认 1;容器窄时按 minColumnWidth 自动减少 */\n columns?: number;\n /** 每列的最小宽度(CSS 长度),默认 `'26em'`:标签在起始边时放得下标签、控件与横排的三四个选项 */\n minColumnWidth?: string;\n /** 可以展开收起:标题变成按钮 */\n collapsible?: boolean;\n /** 是否展开(collapsible 时):父级写 _open={x},默认展开 */\n _open?: boolean;\n /** 标题栏的部件属性 */\n propsHeader?: PartProps<'div'>;\n /** 标题(h2–h6)的部件属性 */\n propsHeading?: PartProps<'h3'>;\n /** 展开收起按钮的部件属性 */\n propsTrigger?: Omit<Partial<ButtonBaseProps>, 'aria-expanded' | 'aria-controls'>;\n /** 展开箭头的部件属性 */\n propsChevron?: Partial<IconProps>;\n /** 说明的部件属性 */\n propsDescription?: Partial<TextProps>;\n /** 标题栏结束边内容的部件属性 */\n propsExtra?: PartProps<'div'>;\n /** 字段栅格的部件属性 */\n propsBody?: PartProps<'div'>;\n /** 展开收起层的部件属性(collapsible 时) */\n propsCollapse?: Partial<CollapseProps>;\n /** 字段 */\n children?: VNodeChild;\n}\n\n/** FormSection 的样式:root、header、headerText、heading、trigger、chevron、description、extra、body、singleColumn */\nexport const formSectionStyles = css.create({\n root: {\n display: 'flex',\n flexDirection: 'column',\n gap: '_lg',\n minWidth: 0,\n // 分节导航(TableOfContents、锚点)跳过来时让开粘性顶栏,再留一点空\n scrollMarginBlockStart: `calc(${layoutVars.stickyOffset} + ${space.lg})`,\n },\n header: {\n display: 'flex',\n alignItems: 'flex-end',\n justifyContent: 'space-between',\n flexWrap: 'wrap',\n columnGap: '_lg',\n rowGap: '_xs',\n paddingBlockEnd: '_sm',\n borderBlockEndWidth: '_xs',\n borderBlockEndStyle: 'solid',\n borderBlockEndColor: '_borderMuted',\n },\n headerText: { display: 'flex', flexDirection: 'column', gap: '_xs', minWidth: 0 },\n heading: {\n margin: 0,\n fontSize: '_md',\n fontWeight: '_semibold',\n lineHeight: '_tight',\n color: '_text',\n },\n trigger: {\n display: 'inline-flex',\n alignItems: 'center',\n gap: '_xs',\n marginInline: `calc(-1 * ${t.space.xs})`,\n paddingInline: '_xs',\n borderRadius: '_sm',\n fontWeight: 'inherit',\n color: 'inherit',\n textAlign: 'start',\n backgroundColor: { default: 'transparent', ':hover': '_fill' },\n },\n chevron: { flexShrink: 0, color: '_textMuted' },\n chevronClosed: { rotate: '-90deg' },\n description: { fontSize: '_sm', lineHeight: '_normal' },\n extra: { display: 'flex', alignItems: 'center', gap: '_sm', flexShrink: 0 },\n // 多列时列间距比行间距大:一眼看出哪几项是同一列\n body: {\n '--form-section-column-gap': '_space.xl',\n display: 'grid',\n columnGap: 'var(--form-section-column-gap)',\n rowGap: '_md',\n gridTemplateColumns: 'minmax(0, 1fr)',\n alignItems: 'start',\n },\n});\n\n/** FormSection 的样式:columns(最多几列、每列不窄于多少) */\nexport const formSectionColumnStyles = css.create({\n columns: (count: number, minWidth: string) => ({\n gridTemplateColumns: `repeat(auto-fill, minmax(max(min(${minWidth}, 100%), calc((100% - ${count - 1} * var(--form-section-column-gap)) / ${count})), 1fr))`,\n }),\n});\n\n/** 表单里的一组字段:标题栏、字段栅格,可折叠;这一组的标签布局、只读、不可用 */\nexport const FormSection = _component(\n ({\n title,\n description,\n extra,\n headingLevel = 3,\n columns = 1,\n minColumnWidth = '26em',\n collapsible = false,\n _open: open = _bindable(true),\n labelPlacement,\n labelWidth,\n labelAlign,\n disabled,\n readonly,\n propsHeader,\n propsHeading,\n propsTrigger,\n propsChevron,\n propsDescription,\n propsExtra,\n propsBody,\n propsCollapse,\n children,\n ...rest\n }: FormSectionProps) => {\n const id = useId();\n const ids = { title: `${id}-title`, description: `${id}-description`, body: `${id}-body` };\n provideFieldLayout(() => ({ labelPlacement, labelWidth, labelAlign, disabled, readonly }));\n const bodyRef = provideGridColumns(() =>\n columns > 1 ? { columns, minItemWidth: minColumnWidth } : { columns: 1 },\n );\n\n return () => {\n const expanded = !collapsible || open;\n const state = expanded ? 'open' : 'closed';\n const titleContent = collapsible ? (\n <ButtonBase\n {...mergePartProps<ButtonBaseProps>({ sx: formSectionStyles.trigger }, propsTrigger, {\n id: ids.title,\n 'aria-expanded': open,\n 'aria-controls': ids.body,\n onClick: () => (open = !open),\n })}\n data-state={state}\n >\n <span>{title}</span>\n <Icon\n {...mergePartProps<IconProps>(\n {\n icon: ChevronDown,\n sx: [\n formSectionStyles.chevron,\n disclosureStyles.icon,\n !open && formSectionStyles.chevronClosed,\n ],\n },\n propsChevron,\n )}\n />\n </ButtonBase>\n ) : (\n <span id={ids.title}>{title}</span>\n );\n const body = (\n <div\n {...mergeAttrs<'div'>(\n {\n sx: [\n formSectionStyles.body,\n columns > 1 && formSectionColumnStyles.columns(columns, minColumnWidth),\n ],\n },\n propsBody,\n { ref: bodyRef, ...(collapsible ? {} : { id: ids.body }) },\n )}\n >\n {children}\n </div>\n );\n return (\n <div\n {...mergeAttrs<'div'>({ sx: formSectionStyles.root }, rest, {\n role: hasContent(title) ? 'group' : undefined,\n 'aria-labelledby': hasContent(title) ? ids.title : undefined,\n 'aria-describedby': hasContent(description) ? ids.description : undefined,\n })}\n data-state={collapsible ? state : undefined}\n >\n {(title != null || extra != null) && (\n <div {...mergeAttrs<'div'>({ sx: formSectionStyles.header }, propsHeader)}>\n <div {...css.attrs(formSectionStyles.headerText)}>\n {hasContent(title) &&\n h(\n `h${headingLevel}`,\n mergeAttrs<'h3'>({ sx: formSectionStyles.heading }, propsHeading),\n [titleContent],\n )}\n {hasContent(description) && (\n <Text\n {...mergePartProps<TextProps>(\n { tone: 'muted', sx: formSectionStyles.description },\n propsDescription,\n { id: ids.description },\n )}\n >\n {description}\n </Text>\n )}\n </div>\n {hasContent(extra) && (\n <div {...mergeAttrs<'div'>({ sx: formSectionStyles.extra }, propsExtra)}>\n {extra}\n </div>\n )}\n </div>\n )}\n {collapsible ? (\n <Collapse\n {...mergePartProps<CollapseProps>({}, propsCollapse, { id: ids.body })}\n _open={open}\n >\n {body}\n </Collapse>\n ) : (\n body\n )}\n </div>\n );\n };\n },\n);\n"],"mappings":"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"}
@@ -137,7 +137,10 @@ export interface FormControllerBase {
137
137
  * 返回它们是否都通过(分步表单进下一步前用)
138
138
  */
139
139
  validate(paths?: readonly string[]): Promise<boolean>;
140
- /** 提交:先校验,通过后调用 onSubmit;返回是否通过校验 */
140
+ /**
141
+ * 提交:先校验,通过后调用 onSubmit;返回是否提交成功。校验没通过,或 onSubmit 抛了带业务错误码的错误(后端回的
142
+ * 校验失败、数据已被修改等,按字段的明细标到字段上,其余是 formError)时为 false;别的错误原样抛出
143
+ */
141
144
  submit(): Promise<boolean>;
142
145
  /** 回到初始值,清空错误与失焦记录 */
143
146
  reset(): void;
@@ -1,8 +1,10 @@
1
1
  import { useLang } from "../config/context.js";
2
2
  import { sameValue } from "../utils/data/equality.js";
3
3
  import { Field } from "./Field.js";
4
+ import { codeText, errorCodeOf, errorMessage } from "../utils/request/error-message.js";
4
5
  import { createConstraints } from "./form-constraints.js";
5
- import { computed, reactive, shallowReactive, toRaw, watch } from "vue";
6
+ import { cloneValue } from "../utils/data/clone.js";
7
+ import { computed, reactive, shallowReactive, watch } from "vue";
6
8
  import { safeParseAsync } from "zod/v4/core";
7
9
  //#region src/form/useForm.ts
8
10
  /** 字段路径 path 是否在 scope 里:就是它,或是它的子路径('address' 包含 'address.city') */
@@ -50,18 +52,8 @@ function shiftIndices(key, prefix, map) {
50
52
  ].join(".");
51
53
  }
52
54
  var nextArrayKey = 0;
53
- /**
54
- * 复制表单的值:普通对象与数组逐层复制(各份互不影响),其余值原样保留。
55
- * 不用 structuredClone:Decimal、@internationalized/date 的日期这些带方法的值克隆不了(会抛错),
56
- * 它们本身不可变,共用同一个实例没有问题
57
- */
58
- function cloneValue(value) {
59
- const raw = toRaw(value);
60
- if (Array.isArray(raw)) return raw.map(cloneValue);
61
- if (raw === null || typeof raw !== "object") return raw;
62
- const prototype = Object.getPrototypeOf(raw);
63
- if (prototype !== Object.prototype && prototype !== null) return raw;
64
- return Object.fromEntries(Object.entries(raw).map(([key, item]) => [key, cloneValue(item)]));
55
+ function isFieldIssue(item) {
56
+ return typeof item === "object" && item !== null && typeof item.field === "string";
65
57
  }
66
58
  /**
67
59
  * 创建表单控制器(组件 setup 里或模块顶层都可以)。
@@ -172,6 +164,31 @@ function useForm(options) {
172
164
  output
173
165
  };
174
166
  }
167
+ /**
168
+ * onSubmit 抛的带业务错误码的错误(后端回的、请求层的,见 utils/request/error-message.ts)标到表单上:
169
+ * errors 明细里带 field 的标到字段(文案按明细的码取,没有时用它的 message),一项都没有时整份表单的错误是错误的文案。
170
+ * 和 setError 一样,值改了就不再显示;下一次提交开始时清掉(交给后端重新判断:网络断了之后不改值也能再提交)。
171
+ * 认不出的错误返回 false,由 submit 原样抛出
172
+ */
173
+ function markSubmitError(error) {
174
+ if (errorCodeOf(error) === void 0) return false;
175
+ const details = error.errors;
176
+ let marked = false;
177
+ for (const item of Array.isArray(details) ? details : []) {
178
+ if (!isFieldIssue(item)) continue;
179
+ const text = item.code && codeText(item.code, item.params, lang) || item.message || item.code;
180
+ controller.setError(item.field, text ?? lang.requestFailed);
181
+ submitErrorPaths.add(item.field);
182
+ marked = true;
183
+ }
184
+ if (!marked) {
185
+ controller.setError("", errorMessage(error, lang));
186
+ submitErrorPaths.add("");
187
+ }
188
+ return true;
189
+ }
190
+ /** 上一次提交失败时标上的路径 */
191
+ const submitErrorPaths = /* @__PURE__ */ new Set();
175
192
  /** setError 给的错误,值改过就不算了;'' 是整份表单的错误,任一字段改了都不算 */
176
193
  function externalError(path) {
177
194
  const entry = state.externalErrors[path];
@@ -317,11 +334,16 @@ function useForm(options) {
317
334
  submit() {
318
335
  submission ??= (async () => {
319
336
  state.submitCount++;
337
+ for (const path of submitErrorPaths) controller.setError(path, void 0);
338
+ submitErrorPaths.clear();
320
339
  const valid = await check(true);
321
340
  if (!valid || !onSubmit) return valid;
322
341
  state.submitting = true;
323
342
  try {
324
343
  await onSubmit(parsed);
344
+ } catch (error) {
345
+ if (!markSubmitError(error)) throw error;
346
+ return false;
325
347
  } finally {
326
348
  state.submitting = false;
327
349
  }
@@ -1 +1 @@
1
- {"version":3,"file":"useForm.js","names":[],"sources":["../../src/form/useForm.ts"],"sourcesContent":["/**\n * 表单控制器:值、校验、错误、提交状态放在一个对象里,交给 <Form controller>(COMPONENTS.md §1 的“命令式能力”)。\n * 校验用 zod(使用方安装的 peer 依赖,zod 与 zod/mini 都可以),也可以写 validate 函数。\n * 参考 TanStack Form、React Hook Form 的做法,取其中最常用的部分。\n *\n * const form = useForm({\n * schema: z.object({ email: z.email('邮箱格式不对'), age: z.coerce.number().int().min(18, '未满 18 岁') }),\n * initial: { email: '', age: '' }, // 类型是 schema 的输入(z.input)\n * onSubmit: (values) => api.signup(values), // 拿到解析后的输出(z.output):age 已是数字\n * });\n * <Form controller={form}>\n * <FormErrorSummary /> // 提交没通过时在顶上列出各项错误,点了跳到字段\n * <form.Field name=\"email\" label=\"邮箱\"><TextField /></form.Field> // 只有一个控件时自动绑定值\n * <Button type=\"submit\" loading={form.submitting}>登录</Button>\n * </Form>\n *\n * form.Field 里只有一个控件、控件上没写值时,自动绑定 form.values 的这个字段(同 Ant Design Form.Item 的 name):\n * 写回的是 _value;声明了 _checked 而没有 _value 的控件(Switch、单个 Checkbox)写回 _checked,也可以用 Field 的 valueProp 指定。\n * 自己写了 _value={form.values.email} 时照旧。\n *\n * 何时显示错误(“迟一点批评、早一点肯定”):字段失焦后才显示它的错误,之后它随输入实时更新;提交过一次之后,所有字段随输入实时更新。\n * 不属于哪个字段的错误(对象级 refine 没写 path、validate 返回的 '' 键、setError('', …))是整份表单的错误,见 formError。\n * 字段的值经 v-model 写回,中文输入法组合输入期间不写回,所以组合中途不会触发校验。\n *\n * schema 还决定字段的约束(form-constraints.ts):标签的必填标记、输入框的 maxlength、NumberField 的 min / max\n * 都从 schema 来,不用在控件上再写一遍。\n *\n * - 数组字段:`form.array('items')` 增删、插入、移动,给每项一个稳定的 key;错误与失焦记录跟着下标移动。\n * - 单字段异步校验:`fields: { username: { validate: (name, values, signal) => api.taken(name, { signal }) } }`,\n * 这个字段变化时防抖运行、只采用最后一次的结果,提交时等它完成;`form.validating('username')` 显示校验中。\n * - 改动:`form.dirty` 是值与初始值(或上次 reset 的值)不同;未保存离开的提醒用 useLeaveConfirmation(() => form.dirty)。\n * - 局部校验:`form.validate(['name', 'address'])` 只校验这些字段(含子路径)并显示它们的错误,分步表单进下一步前用。\n * - 字段联动:`dependencies: { confirm: ['password'] }`,password 改了重新校验 confirm(confirm 填过或失焦过时显示它的错误),\n * confirm 的单字段校验也跟着重跑。\n */\nimport type { TsxComponent } from '@zerodep-dev/vue-tsx-pro-tsx';\nimport { computed, reactive, shallowReactive, toRaw, watch } from 'vue';\nimport { safeParseAsync, type $ZodIssue, type $ZodRawIssue, type $ZodType } from 'zod/v4/core';\nimport { useLang } from '../config/context';\nimport { Field, type FieldProps } from './Field';\nimport { createConstraints, type FieldConstraints } from './form-constraints';\nimport { sameValue } from '../utils/data/equality';\n\n/** 字段路径 → 错误信息;路径用点连接(`address.city`、`items.0.name`) */\nexport type FormErrors = Partial<Record<string, string>>;\n\n/** 当作一个整体的值:不再往里展开路径 */\ntype FieldLeaf = string | number | boolean | bigint | symbol | null | undefined | Date | Blob;\n\n/**\n * 值里的字段路径:点连接,数组下标是数字(`email`、`address.city`、`items.0.name`)。\n * 最多展开 6 层,再深的结构写成 string 也照样能用(Field 的 name、setError 都接受)\n */\nexport type FieldPath<Values, Depth extends unknown[] = []> = Depth['length'] extends 6\n ? never\n : Values extends readonly (infer Item)[]\n ? `${number}` | `${number}.${FieldPath<Item, [...Depth, unknown]>}`\n : Values extends object\n ? {\n [K in keyof Values & string]:\n | K\n | (NonNullable<Values[K]> extends FieldLeaf\n ? never\n : `${K}.${FieldPath<NonNullable<Values[K]>, [...Depth, unknown]>}`);\n }[keyof Values & string]\n : never;\n\n/** 字段路径 P 处的值的类型(`PathValue<Values, 'items.0.name'>` → string) */\nexport type PathValue<Values, P extends string> = P extends `${infer Key}.${infer Rest}`\n ? Values extends readonly (infer Item)[]\n ? PathValue<NonNullable<Item>, Rest>\n : Key extends keyof Values\n ? PathValue<NonNullable<Values[Key]>, Rest>\n : never\n : Values extends readonly (infer Item)[]\n ? Item\n : P extends keyof Values\n ? Values[P]\n : never;\n\n/** 值是数组的字段路径:form.array 只接受这些 */\nexport type ArrayPath<Values> = {\n [P in FieldPath<Values>]: NonNullable<PathValue<Values, P>> extends readonly unknown[]\n ? P\n : never;\n}[FieldPath<Values>];\n\n/** 单个字段的校验:只在这个字段变化时运行(防抖),适合要请求接口的“用户名已占用” */\nexport interface FieldValidator<Value, Values> {\n /** 返回错误信息,通过时返回 undefined;signal 在值又变了、表单重置时中止 */\n validate: (\n value: Value,\n values: Values,\n signal: AbortSignal,\n ) => string | undefined | Promise<string | undefined>;\n /** 值停止变化多久(毫秒)后运行,默认 300 */\n debounce?: number;\n}\n\n/** 字段路径 path 是否在 scope 里:就是它,或是它的子路径('address' 包含 'address.city') */\nconst inScope = (path: string, scope: readonly string[]) =>\n scope.some((prefix) => path === prefix || path.startsWith(`${prefix}.`));\n\n/** 数组字段的操作:form.array(path) 的返回值 */\nexport interface FieldArray<Item> {\n /** 当前的各项(就是 values 里的数组) */\n readonly items: readonly Item[];\n /** 每项一个稳定的 key:渲染列表时用,增删、移动后不变 */\n readonly keys: readonly string[];\n /** 加到末尾 */\n append(item: Item): void;\n /** 插到 index 处 */\n insert(index: number, item: Item): void;\n /** 删掉 index 处的一项 */\n remove(index: number): void;\n /** 把 from 处的一项移到 to 处 */\n move(from: number, to: number): void;\n}\n\n/** 什么时候开始校验;提交过一次之后总是随输入更新 */\nexport interface FormValidateTimings {\n /** 失焦后(默认) */\n blur: unknown;\n /** 输入时 */\n change: unknown;\n /** 只在提交时 */\n submit: unknown;\n}\n\n/** useForm 的选项;Values 是表单里的值(有 schema 时是它的输入类型),Output 是校验通过后交给 onSubmit 的值 */\nexport interface FormOptions<Values extends object, Output = Values> {\n /** 初始值;reset() 回到它 */\n initial: Values;\n /** zod schema:整份值一起校验,issue 的 path 决定错误挂在哪个字段上;字段约束也从它推出 */\n schema?: $ZodType<Output, Values>;\n /** 自定义校验:返回各字段的错误(可以是 Promise),与 schema 的结果合并 */\n validate?: (values: Values) => FormErrors | Promise<FormErrors>;\n /**\n * 单字段异步校验:按字段路径给出,这个字段变化时防抖运行,结果与 schema 的合并(schema 的错误优先)。\n * NoInfer:Values 只从 initial 与 schema 推出,validate 的参数类型才能跟着推出来\n */\n fields?: NoInfer<{ [P in FieldPath<Values>]?: FieldValidator<PathValue<Values, P>, Values> }>;\n /** 什么时候开始校验,默认 blur;提交过一次之后总是随输入更新 */\n validateOn?: keyof FormValidateTimings;\n /**\n * 字段联动:字段 → 它依赖的字段。依赖的值改了就重新校验它(它填过或失焦过时显示错误),它的单字段校验也重跑。\n * 如 `{ confirm: ['password'], endDate: ['startDate'] }`\n */\n dependencies?: NoInfer<{ [P in FieldPath<Values>]?: readonly FieldPath<Values>[] }>;\n /** 校验通过后调用,拿到 schema 解析后的值(转换、默认值、coerce 都已生效);返回 Promise 时 submitting 一直为 true 直到完成 */\n onSubmit?: (values: Output) => unknown;\n}\n\n/** 控制器里与值的类型无关的部分:Form、Field 只用到它,任何 FormController 都能交给 <Form controller> */\nexport interface FormControllerBase {\n /** 所有字段当前的校验错误(含还没显示的);整份表单的错误在 '' 键上 */\n readonly errors: Readonly<FormErrors>;\n /** 整份表单的错误(不属于哪个字段),提交过之后才有;放在提交按钮旁或表单顶上 */\n readonly formError: string | undefined;\n /** 正在执行 onSubmit */\n readonly submitting: boolean;\n /** 提交过几次(不论成功与否) */\n readonly submitCount: number;\n /** 重置过几次:控件据此丢掉自己还没写回的输入(useFormReset) */\n readonly resetCount: number;\n /** 值与初始值(或上次 reset 的值)不同 */\n readonly dirty: boolean;\n /** 字段(不写时为任一字段)的单字段校验正在进行 */\n validating(path?: string): boolean;\n /** 字段的错误是否该显示:失焦过、或提交过之后才显示;Field 用它 */\n visibleError(path: string): string | undefined;\n /** 字段失焦:Field 在焦点离开字段时调用 */\n blur(path: string): void;\n /**\n * 校验整份表单,返回是否通过。给了 paths 时只看这些字段(含子路径):它们的错误立即显示,\n * 返回它们是否都通过(分步表单进下一步前用)\n */\n validate(paths?: readonly string[]): Promise<boolean>;\n /** 提交:先校验,通过后调用 onSubmit;返回是否通过校验 */\n submit(): Promise<boolean>;\n /** 回到初始值,清空错误与失焦记录 */\n reset(): void;\n /** 字段从 schema 推出的约束(必填、长度、范围…);Field 交给控件,控件上显式写的优先 */\n constraints(path: string): FieldConstraints;\n /** 字段的当前值(按点连接的路径):Field 自动绑定值时用 */\n getValue(path: string): unknown;\n /** 写字段的值:中途是 null / undefined 时什么也不做 */\n setValue(path: string, value: unknown): void;\n /** 写了 name 的 Field 登记标签与控件的 id:错误汇总据此显示字段名、跳到控件。返回注销函数 */\n register(path: string, field: FormFieldInfo): () => void;\n /** 登记过的字段,按登记(渲染)的顺序 */\n readonly fields: ReadonlyMap<string, FormFieldInfo>;\n}\n\n/** Field 登记的字段信息 */\nexport interface FormFieldInfo {\n /** 标签的文字 */\n label: string;\n /** 控件的 id:错误汇总的链接指向它 */\n controlId: string;\n}\n\n/** 表单控制器:useForm 的返回值 */\nexport interface FormController<Values extends object> extends FormControllerBase {\n /** 当前值(响应式):控件写 _value={form.values.email} */\n readonly values: Values;\n /** 回到初始值(或给定的值,之后 dirty 以它为准),清空错误与失焦记录;保存成功后写 reset(form.values) */\n reset(values?: Values): void;\n /** 字段与初始值不同 */\n isDirty(path: FieldPath<Values>): boolean;\n /** 校验整份表单,或只校验 paths 里的字段(含子路径)并显示它们的错误;返回是否通过 */\n validate(paths?: readonly FieldPath<Values>[]): Promise<boolean>;\n /** 数组字段的增删、插入、移动 */\n array<P extends ArrayPath<Values>>(\n path: P,\n ): FieldArray<NonNullable<PathValue<Values, P>> extends readonly (infer Item)[] ? Item : never>;\n /**\n * 设置某个字段的错误(如服务端返回的“邮箱已注册”),undefined 清除;'' 是整份表单的错误。\n * 校验不会冲掉它,这个字段(整份表单的错误是任一字段)的值改了之后自动消失\n */\n setError(path: FieldPath<Values> | '', message: string | undefined): void;\n /**\n * 就是 Field 组件,只是 name 的类型收窄为这份值的字段路径,写错字段名时类型检查报错:\n * `<form.Field name=\"email\" label=\"邮箱\">…</form.Field>`(参考 TanStack Form 的 form.Field)\n */\n readonly Field: TsxComponent<Omit<FieldProps, 'name'> & { name?: FieldPath<Values> }>;\n}\n\n/** zod issue 的路径 → 点连接的字符串 */\nconst issuePath = (issue: $ZodIssue) => issue.path.map(String).join('.');\n\n/**\n * 该填的没有填:类型不对且是空值(null、undefined,包括缺少的键),或长度不够且是空字符串、空数组。\n * 选择、数字、日期控件清空后是 null(COMPONENTS.md §1 空值),z.string() 遇到它报的是类型不对,读起来应当是“必填”\n */\nfunction isBlankIssue({ code, input }: $ZodRawIssue): boolean {\n if (code === 'invalid_type') return input == null;\n if (code === 'too_small') return input === '' || (Array.isArray(input) && input.length === 0);\n return false;\n}\n\n/** 按点连接的路径写值;中途是 null / undefined 时什么也不做 */\nfunction writePath(target: unknown, path: string, value: unknown): void {\n const keys = path.split('.');\n const parent = readPath(target, keys.slice(0, -1).join('.'));\n if (keys.length === 1) (target as Record<string, unknown>)[keys[0]!] = value;\n else if (parent != null) (parent as Record<string, unknown>)[keys.at(-1)!] = value;\n}\n\n/** 按点连接的路径取值;中途是 null / undefined 时为 undefined */\nfunction readPath(source: unknown, path: string): unknown {\n let current = source;\n for (const key of path.split('.')) {\n if (current == null) return undefined;\n current = (current as Record<string, unknown>)[key];\n }\n return current;\n}\n\n/**\n * 数组字段的下标变了:`prefix.旧下标…` 的键改成 `prefix.新下标…`,map 返回 undefined 的(删掉的项)丢弃\n */\nfunction shiftIndices(key: string, prefix: string, map: (index: number) => number | undefined) {\n if (!key.startsWith(`${prefix}.`)) return key;\n const rest = key.slice(prefix.length + 1);\n const [head, ...tail] = rest.split('.');\n const index = Number(head);\n if (!Number.isInteger(index)) return key;\n const next = map(index);\n return next === undefined ? undefined : [prefix, next, ...tail].join('.');\n}\n\nlet nextArrayKey = 0;\n\n/** 一个字段的单字段校验的进行状态 */\ninterface FieldRun {\n /** 防抖的定时器 */\n timer?: ReturnType<typeof setTimeout>;\n /** 正在校验的那次 */\n running?: { value: unknown; controller: AbortController; done: Promise<void> };\n /** 上次校验完成时的值 */\n checked?: unknown;\n}\n\n/**\n * 复制表单的值:普通对象与数组逐层复制(各份互不影响),其余值原样保留。\n * 不用 structuredClone:Decimal、@internationalized/date 的日期这些带方法的值克隆不了(会抛错),\n * 它们本身不可变,共用同一个实例没有问题\n */\nfunction cloneValue<T>(value: T): T {\n const raw = toRaw(value);\n if (Array.isArray(raw)) return raw.map(cloneValue) as T;\n if (raw === null || typeof raw !== 'object') return raw;\n const prototype = Object.getPrototypeOf(raw);\n if (prototype !== Object.prototype && prototype !== null) return raw;\n return Object.fromEntries(Object.entries(raw).map(([key, item]) => [key, cloneValue(item)])) as T;\n}\n\n/**\n * 创建表单控制器(组件 setup 里或模块顶层都可以)。\n * 有 schema 时值的类型取自 schema(initial 要符合它的输入类型),onSubmit 拿到解析后的输出\n */\nexport function useForm<Values extends object, Output = Values>(\n options: FormOptions<Values, Output>,\n): FormController<Values> {\n const { schema, validateOn = 'blur', onSubmit } = options;\n const lang = useLang();\n // zod 的报错跟随语言包;schema 里自己写的提示优先于它(zod 的规则)\n const parseOptions = {\n error: (issue: $ZodRawIssue) =>\n isBlankIssue(issue) ? lang.formRequired : lang.formIssues(issue),\n };\n const fieldValidators = Object.entries(options.fields ?? {}) as [\n string,\n FieldValidator<unknown, Values>,\n ][];\n const values = reactive(cloneValue(options.initial)) as Values;\n const state = shallowReactive({\n errors: {} as FormErrors,\n // 单字段校验的结果,与 errors 分开存:整份校验不会冲掉还在路上的单字段结果\n fieldErrors: {} as FormErrors,\n // setError 给的错误与当时的值:值一改就不再算\n externalErrors: {} as Record<string, { message: string; value: unknown }>,\n touched: new Set<string>(),\n pending: new Set<string>(),\n submitting: false,\n submitCount: 0,\n resetCount: 0,\n // dirty 的比较基准:初始值,或上次 reset 的值\n baseline: cloneValue(options.initial),\n });\n // 异步校验可能乱序返回:只采用最后一次的结果\n let run = 0;\n // 正在进行的提交:校验、onSubmit 期间再次提交(连按回车)拿到同一个结果,不重复调用 onSubmit\n let submission: Promise<boolean> | undefined;\n // 最近一次通过校验时 schema 解析出的值,交给 onSubmit\n let parsed: Output | undefined;\n const constraints = createConstraints(schema);\n const dirty = computed(() => !sameValue(values, state.baseline));\n // 数组字段每项的 key,与数组等长;数组被外部整个换掉(长度不同)时重新生成\n const arrayKeys = new Map<string, string[]>();\n\n // 写了 name 的 Field:错误汇总按它显示字段名、跳到控件\n const fields = shallowReactive(new Map<string, FormFieldInfo>());\n\n /** 单字段校验的进行状态:每个字段一份 */\n const fieldRuns = new Map<string, FieldRun>();\n\n function setPending(path: string, pending: boolean) {\n if (state.pending.has(path) === pending) return;\n const next = new Set(state.pending);\n if (pending) next.add(path);\n else next.delete(path);\n state.pending = next;\n }\n\n /**\n * 运行一个字段的校验。同一个值正在校验时等它;上次校验的就是这个值时直接用上次的结果;\n * 值变了就中止还在路上的那次(只采用最后一次的结果)\n */\n function runField(path: string, validator: FieldValidator<unknown, Values>): Promise<void> {\n const entry = fieldRuns.get(path) ?? {};\n fieldRuns.set(path, entry);\n clearTimeout(entry.timer);\n // 依赖的字段也算进“这次校验的输入”:依赖变了,同一个值也要重新校验\n const value = cloneValue([\n readPath(values, path),\n ...dependsOn(path).map((dep) => readPath(values, dep)),\n ]);\n if (entry.running && sameValue(entry.running.value, value)) return entry.running.done;\n if (!entry.running && 'checked' in entry && sameValue(entry.checked, value)) {\n return Promise.resolve();\n }\n entry.running?.controller.abort();\n const controller = new AbortController();\n setPending(path, true);\n const done = (async () => {\n try {\n const message = await validator.validate(value[0], values, controller.signal);\n if (controller.signal.aborted) return;\n entry.checked = value;\n state.fieldErrors = { ...state.fieldErrors, [path]: message };\n } catch (error) {\n // 校验本身失败(接口出错):当作没有结论,不挡提交(服务端还会再校验),但要报出来\n if (!controller.signal.aborted) console.error(`useForm:字段 ${path} 的校验出错`, error);\n } finally {\n if (entry.running?.controller === controller) {\n entry.running = undefined;\n setPending(path, false);\n }\n }\n })();\n entry.running = { value, controller, done };\n return done;\n }\n\n function cancelFieldRuns() {\n for (const entry of fieldRuns.values()) {\n clearTimeout(entry.timer);\n entry.running?.controller.abort();\n }\n fieldRuns.clear();\n state.pending = new Set();\n state.fieldErrors = {};\n }\n\n /** 字段依赖的字段 */\n const dependsOn = (path: string): readonly string[] =>\n (options.dependencies as Partial<Record<string, readonly string[]>> | undefined)?.[path] ?? [];\n\n /**\n * 校验整份值:schema 与 validate 的错误(errors)。full 时单字段校验也立刻跑完(提交、显式调用 validate;\n * 给了 scope 时只跑 scope 里的),结果在 state.fieldErrors;失焦、输入时只算 schema 与 validate,\n * 单字段校验照旧防抖、只在它自己(或它依赖的字段)的值变了时跑\n */\n async function collect(\n full: boolean,\n scope?: readonly string[],\n ): Promise<{ errors: FormErrors; output: Output }> {\n const errors: FormErrors = {};\n let output = values as unknown as Output;\n if (schema) {\n const result = await safeParseAsync(schema, values, parseOptions);\n if (result.success) output = result.data;\n for (const issue of result.error?.issues ?? []) {\n errors[issuePath(issue)] ??= issue.message;\n }\n }\n if (options.validate) {\n for (const [path, message] of Object.entries(await options.validate(values))) {\n if (message) errors[path] ??= message;\n }\n }\n // 单字段校验:不等防抖,立刻跑完(值没变的直接用上次结果)\n if (full) {\n await Promise.all(\n fieldValidators\n .filter(([path]) => !scope || inScope(path, scope))\n .map(([path, validator]) => runField(path, validator)),\n );\n }\n return { errors, output };\n }\n\n /** setError 给的错误,值改过就不算了;'' 是整份表单的错误,任一字段改了都不算 */\n function externalError(path: string): string | undefined {\n const entry = state.externalErrors[path];\n if (!entry) return undefined;\n const current = path === '' ? values : readPath(values, path);\n return sameValue(current, entry.value) ? entry.message : undefined;\n }\n\n /** schema 与 validate 的错误优先,再补上 setError 与单字段校验的 */\n function mergedErrors(): FormErrors {\n const merged = { ...state.errors };\n for (const path of Object.keys(state.externalErrors)) {\n const message = externalError(path);\n if (message) merged[path] ??= message;\n }\n for (const [path, message] of Object.entries(state.fieldErrors)) {\n if (message) merged[path] ??= message;\n }\n return merged;\n }\n\n /**\n * 校验并记下结果(只采用最后一次);返回是否通过:算出的、setError 给的、单字段校验的都没有错误。\n * 给了 scope 时只看 scope 里的字段,别的字段的错误保持原样\n */\n async function check(full: boolean, scope?: readonly string[]): Promise<boolean> {\n const current = ++run;\n const { errors, output } = await collect(full, scope);\n if (!scope) {\n if (current === run) {\n state.errors = errors;\n parsed = output;\n }\n return Object.keys(errors).length === 0 && Object.keys(mergedErrors()).length === 0;\n }\n const scoped = (source: FormErrors) =>\n Object.entries(source).filter(([path, message]) => message && inScope(path, scope));\n if (current === run) {\n state.errors = {\n ...Object.fromEntries(\n Object.entries(state.errors).filter(([path]) => !inScope(path, scope)),\n ),\n ...Object.fromEntries(scoped(errors)),\n };\n }\n return scoped(errors).length === 0 && scoped(mergedErrors()).length === 0;\n }\n\n /** 把这些字段记为失焦过:它们的错误开始显示 */\n function touch(paths: readonly string[]) {\n const next = new Set(state.touched);\n for (const path of paths) next.add(path);\n if (next.size !== state.touched.size) state.touched = next;\n }\n\n /** 数组字段的下标变了:错误、失焦记录、单字段校验的结果跟着移 */\n function remapIndices(prefix: string, map: (index: number) => number | undefined) {\n const remap = (errors: FormErrors) =>\n Object.fromEntries(\n Object.entries(errors).flatMap(([key, message]) => {\n const next = shiftIndices(key, prefix, map);\n return next === undefined ? [] : [[next, message]];\n }),\n ) as FormErrors;\n state.errors = remap(state.errors);\n state.fieldErrors = remap(state.fieldErrors);\n state.externalErrors = Object.fromEntries(\n Object.entries(state.externalErrors).flatMap(([key, entry]) => {\n const next = shiftIndices(key, prefix, map);\n return next === undefined ? [] : [[next, entry]];\n }),\n );\n state.touched = new Set(\n [...state.touched].flatMap((key) => shiftIndices(key, prefix, map) ?? []),\n );\n }\n\n function fieldArray(path: string): FieldArray<unknown> {\n const list = () => (readPath(values, path) as unknown[] | undefined) ?? [];\n const keys = () => {\n let current = arrayKeys.get(path);\n if (current?.length !== list().length) {\n current = list().map(() => `item-${++nextArrayKey}`);\n arrayKeys.set(path, current);\n }\n return current;\n };\n /** 改数组与 key:splice 的参数同时作用在两者上 */\n const splice = (start: number, deleteCount: number, ...items: unknown[]) => {\n keys().splice(start, deleteCount, ...items.map(() => `item-${++nextArrayKey}`));\n list().splice(start, deleteCount, ...items);\n };\n return {\n get items() {\n return list();\n },\n get keys() {\n return keys();\n },\n append: (item) => splice(list().length, 0, item),\n insert(index, item) {\n remapIndices(path, (old) => (old >= index ? old + 1 : old));\n splice(index, 0, item);\n },\n remove(index) {\n remapIndices(path, (old) => (old === index ? undefined : old > index ? old - 1 : old));\n splice(index, 1);\n },\n move(from, to) {\n if (from === to) return;\n remapIndices(path, (old) => {\n if (old === from) return to;\n if (from < to && old > from && old <= to) return old - 1;\n if (from > to && old >= to && old < from) return old + 1;\n return old;\n });\n const [key] = keys().splice(from, 1);\n const [item] = list().splice(from, 1);\n keys().splice(to, 0, key!);\n list().splice(to, 0, item);\n },\n };\n }\n\n const controller: FormController<Values> = {\n values,\n get errors() {\n return mergedErrors();\n },\n get formError() {\n if (state.submitCount === 0) return undefined;\n return state.errors[''] ?? externalError('');\n },\n get submitting() {\n return state.submitting;\n },\n get resetCount() {\n return state.resetCount;\n },\n get submitCount() {\n return state.submitCount;\n },\n get dirty() {\n return dirty.value;\n },\n validating(path) {\n return path === undefined ? state.pending.size > 0 : state.pending.has(path);\n },\n visibleError(path) {\n // 失焦过的字段,或局部校验过的字段(含子路径:validate(['address']) 之后 address.city 也显示)\n if (state.submitCount === 0 && !inScope(path, [...state.touched])) return undefined;\n return state.errors[path] ?? externalError(path) ?? state.fieldErrors[path];\n },\n blur(path) {\n if (state.touched.has(path)) return;\n state.touched = new Set(state.touched).add(path);\n if (validateOn !== 'submit') void check(false);\n },\n validate(paths) {\n if (!paths) return check(true);\n touch(paths);\n return check(true, paths);\n },\n submit() {\n submission ??= (async () => {\n state.submitCount++;\n const valid = await check(true);\n if (!valid || !onSubmit) return valid;\n state.submitting = true;\n try {\n await onSubmit(parsed as Output);\n } finally {\n state.submitting = false;\n }\n return true;\n })().finally(() => (submission = undefined));\n return submission;\n },\n reset(next = options.initial) {\n run++;\n const snapshot = cloneValue(next);\n cancelFieldRuns();\n Object.assign(values, cloneValue(snapshot));\n arrayKeys.clear();\n state.baseline = snapshot;\n state.errors = {};\n state.externalErrors = {};\n state.touched = new Set();\n state.submitCount = 0;\n state.resetCount++;\n },\n isDirty(path) {\n return !sameValue(readPath(values, path), readPath(state.baseline, path));\n },\n array(path) {\n return fieldArray(path) as never;\n },\n setError(path, message) {\n const { [path]: _previous, ...rest } = state.externalErrors;\n const value = path === '' ? values : readPath(values, path);\n state.externalErrors =\n message === undefined ? rest : { ...rest, [path]: { message, value: cloneValue(value) } };\n },\n constraints,\n Field,\n getValue: (path) => readPath(values, path),\n setValue: (path, value) => writePath(values, path, value),\n register(path, field) {\n fields.set(path, field);\n return () => {\n if (fields.get(path) === field) fields.delete(path);\n };\n },\n fields,\n };\n\n // 输入时:change 模式一开始就校验;blur 模式在有字段失焦过之后(只显示失焦过的字段);提交过之后总是\n watch(\n () => values,\n () => {\n const live =\n validateOn === 'change' ||\n state.submitCount > 0 ||\n (validateOn === 'blur' && state.touched.size > 0);\n if (live) void check(false);\n },\n { deep: true },\n );\n\n // 字段联动:依赖的值改了,字段填过或失焦过就显示它的错误并重新校验(单字段校验由下面的监听跟着重跑)\n for (const [path, deps] of Object.entries(options.dependencies ?? {}) as [string, string[]][]) {\n watch(\n () => deps.map((dep) => readPath(values, dep)),\n () => {\n const value = readPath(values, path);\n const filled = !(value == null || value === '' || (Array.isArray(value) && !value.length));\n if (!filled && !state.touched.has(path)) return;\n touch([path]);\n if (validateOn !== 'submit' || state.submitCount > 0) void check(false);\n },\n { deep: true },\n );\n }\n\n // 单字段校验:这个字段(或它依赖的字段)的值变了,防抖之后运行\n for (const [path, validator] of fieldValidators) {\n watch(\n () => [readPath(values, path), ...dependsOn(path).map((dep) => readPath(values, dep))],\n () => {\n const entry = fieldRuns.get(path) ?? {};\n fieldRuns.set(path, entry);\n clearTimeout(entry.timer);\n entry.timer = setTimeout(() => void runField(path, validator), validator.debounce ?? 300);\n },\n { deep: true },\n );\n }\n\n return controller;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"useForm.js","names":[],"sources":["../../src/form/useForm.ts"],"sourcesContent":["/**\n * 表单控制器:值、校验、错误、提交状态放在一个对象里,交给 <Form controller>(COMPONENTS.md §1 的“命令式能力”)。\n * 校验用 zod(使用方安装的 peer 依赖,zod 与 zod/mini 都可以),也可以写 validate 函数。\n * 参考 TanStack Form、React Hook Form 的做法,取其中最常用的部分。\n *\n * const form = useForm({\n * schema: z.object({ email: z.email('邮箱格式不对'), age: z.coerce.number().int().min(18, '未满 18 岁') }),\n * initial: { email: '', age: '' }, // 类型是 schema 的输入(z.input)\n * onSubmit: (values) => api.signup(values), // 拿到解析后的输出(z.output):age 已是数字\n * });\n * <Form controller={form}>\n * <FormErrorSummary /> // 提交没通过时在顶上列出各项错误,点了跳到字段\n * <form.Field name=\"email\" label=\"邮箱\"><TextField /></form.Field> // 只有一个控件时自动绑定值\n * <Button type=\"submit\" loading={form.submitting}>登录</Button>\n * </Form>\n *\n * form.Field 里只有一个控件、控件上没写值时,自动绑定 form.values 的这个字段(同 Ant Design Form.Item 的 name):\n * 写回的是 _value;声明了 _checked 而没有 _value 的控件(Switch、单个 Checkbox)写回 _checked,也可以用 Field 的 valueProp 指定。\n * 自己写了 _value={form.values.email} 时照旧。\n *\n * 何时显示错误(“迟一点批评、早一点肯定”):字段失焦后才显示它的错误,之后它随输入实时更新;提交过一次之后,所有字段随输入实时更新。\n * 不属于哪个字段的错误(对象级 refine 没写 path、validate 返回的 '' 键、setError('', …))是整份表单的错误,见 formError。\n * 字段的值经 v-model 写回,中文输入法组合输入期间不写回,所以组合中途不会触发校验。\n *\n * schema 还决定字段的约束(form-constraints.ts):标签的必填标记、输入框的 maxlength、NumberField 的 min / max\n * 都从 schema 来,不用在控件上再写一遍。\n *\n * - 数组字段:`form.array('items')` 增删、插入、移动,给每项一个稳定的 key;错误与失焦记录跟着下标移动。\n * - 单字段异步校验:`fields: { username: { validate: (name, values, signal) => api.taken(name, { signal }) } }`,\n * 这个字段变化时防抖运行、只采用最后一次的结果,提交时等它完成;`form.validating('username')` 显示校验中。\n * - 改动:`form.dirty` 是值与初始值(或上次 reset 的值)不同;未保存离开的提醒用 useLeaveConfirmation(() => form.dirty)。\n * - 局部校验:`form.validate(['name', 'address'])` 只校验这些字段(含子路径)并显示它们的错误,分步表单进下一步前用。\n * - 字段联动:`dependencies: { confirm: ['password'] }`,password 改了重新校验 confirm(confirm 填过或失焦过时显示它的错误),\n * confirm 的单字段校验也跟着重跑。\n */\nimport type { TsxComponent } from '@zerodep-dev/vue-tsx-pro-tsx';\nimport { computed, reactive, shallowReactive, watch } from 'vue';\nimport { safeParseAsync, type $ZodIssue, type $ZodRawIssue, type $ZodType } from 'zod/v4/core';\nimport { useLang } from '../config/context';\nimport { Field, type FieldProps } from './Field';\nimport { createConstraints, type FieldConstraints } from './form-constraints';\nimport { cloneValue } from '../utils/data/clone';\nimport { sameValue } from '../utils/data/equality';\nimport { codeText, errorCodeOf, errorMessage } from '../utils/request/error-message';\n\n/** 字段路径 → 错误信息;路径用点连接(`address.city`、`items.0.name`) */\nexport type FormErrors = Partial<Record<string, string>>;\n\n/** 当作一个整体的值:不再往里展开路径 */\ntype FieldLeaf = string | number | boolean | bigint | symbol | null | undefined | Date | Blob;\n\n/**\n * 值里的字段路径:点连接,数组下标是数字(`email`、`address.city`、`items.0.name`)。\n * 最多展开 6 层,再深的结构写成 string 也照样能用(Field 的 name、setError 都接受)\n */\nexport type FieldPath<Values, Depth extends unknown[] = []> = Depth['length'] extends 6\n ? never\n : Values extends readonly (infer Item)[]\n ? `${number}` | `${number}.${FieldPath<Item, [...Depth, unknown]>}`\n : Values extends object\n ? {\n [K in keyof Values & string]:\n | K\n | (NonNullable<Values[K]> extends FieldLeaf\n ? never\n : `${K}.${FieldPath<NonNullable<Values[K]>, [...Depth, unknown]>}`);\n }[keyof Values & string]\n : never;\n\n/** 字段路径 P 处的值的类型(`PathValue<Values, 'items.0.name'>` → string) */\nexport type PathValue<Values, P extends string> = P extends `${infer Key}.${infer Rest}`\n ? Values extends readonly (infer Item)[]\n ? PathValue<NonNullable<Item>, Rest>\n : Key extends keyof Values\n ? PathValue<NonNullable<Values[Key]>, Rest>\n : never\n : Values extends readonly (infer Item)[]\n ? Item\n : P extends keyof Values\n ? Values[P]\n : never;\n\n/** 值是数组的字段路径:form.array 只接受这些 */\nexport type ArrayPath<Values> = {\n [P in FieldPath<Values>]: NonNullable<PathValue<Values, P>> extends readonly unknown[]\n ? P\n : never;\n}[FieldPath<Values>];\n\n/** 单个字段的校验:只在这个字段变化时运行(防抖),适合要请求接口的“用户名已占用” */\nexport interface FieldValidator<Value, Values> {\n /** 返回错误信息,通过时返回 undefined;signal 在值又变了、表单重置时中止 */\n validate: (\n value: Value,\n values: Values,\n signal: AbortSignal,\n ) => string | undefined | Promise<string | undefined>;\n /** 值停止变化多久(毫秒)后运行,默认 300 */\n debounce?: number;\n}\n\n/** 字段路径 path 是否在 scope 里:就是它,或是它的子路径('address' 包含 'address.city') */\nconst inScope = (path: string, scope: readonly string[]) =>\n scope.some((prefix) => path === prefix || path.startsWith(`${prefix}.`));\n\n/** 数组字段的操作:form.array(path) 的返回值 */\nexport interface FieldArray<Item> {\n /** 当前的各项(就是 values 里的数组) */\n readonly items: readonly Item[];\n /** 每项一个稳定的 key:渲染列表时用,增删、移动后不变 */\n readonly keys: readonly string[];\n /** 加到末尾 */\n append(item: Item): void;\n /** 插到 index 处 */\n insert(index: number, item: Item): void;\n /** 删掉 index 处的一项 */\n remove(index: number): void;\n /** 把 from 处的一项移到 to 处 */\n move(from: number, to: number): void;\n}\n\n/** 什么时候开始校验;提交过一次之后总是随输入更新 */\nexport interface FormValidateTimings {\n /** 失焦后(默认) */\n blur: unknown;\n /** 输入时 */\n change: unknown;\n /** 只在提交时 */\n submit: unknown;\n}\n\n/** useForm 的选项;Values 是表单里的值(有 schema 时是它的输入类型),Output 是校验通过后交给 onSubmit 的值 */\nexport interface FormOptions<Values extends object, Output = Values> {\n /** 初始值;reset() 回到它 */\n initial: Values;\n /** zod schema:整份值一起校验,issue 的 path 决定错误挂在哪个字段上;字段约束也从它推出 */\n schema?: $ZodType<Output, Values>;\n /** 自定义校验:返回各字段的错误(可以是 Promise),与 schema 的结果合并 */\n validate?: (values: Values) => FormErrors | Promise<FormErrors>;\n /**\n * 单字段异步校验:按字段路径给出,这个字段变化时防抖运行,结果与 schema 的合并(schema 的错误优先)。\n * NoInfer:Values 只从 initial 与 schema 推出,validate 的参数类型才能跟着推出来\n */\n fields?: NoInfer<{ [P in FieldPath<Values>]?: FieldValidator<PathValue<Values, P>, Values> }>;\n /** 什么时候开始校验,默认 blur;提交过一次之后总是随输入更新 */\n validateOn?: keyof FormValidateTimings;\n /**\n * 字段联动:字段 → 它依赖的字段。依赖的值改了就重新校验它(它填过或失焦过时显示错误),它的单字段校验也重跑。\n * 如 `{ confirm: ['password'], endDate: ['startDate'] }`\n */\n dependencies?: NoInfer<{ [P in FieldPath<Values>]?: readonly FieldPath<Values>[] }>;\n /** 校验通过后调用,拿到 schema 解析后的值(转换、默认值、coerce 都已生效);返回 Promise 时 submitting 一直为 true 直到完成 */\n onSubmit?: (values: Output) => unknown;\n}\n\n/** 控制器里与值的类型无关的部分:Form、Field 只用到它,任何 FormController 都能交给 <Form controller> */\nexport interface FormControllerBase {\n /** 所有字段当前的校验错误(含还没显示的);整份表单的错误在 '' 键上 */\n readonly errors: Readonly<FormErrors>;\n /** 整份表单的错误(不属于哪个字段),提交过之后才有;放在提交按钮旁或表单顶上 */\n readonly formError: string | undefined;\n /** 正在执行 onSubmit */\n readonly submitting: boolean;\n /** 提交过几次(不论成功与否) */\n readonly submitCount: number;\n /** 重置过几次:控件据此丢掉自己还没写回的输入(useFormReset) */\n readonly resetCount: number;\n /** 值与初始值(或上次 reset 的值)不同 */\n readonly dirty: boolean;\n /** 字段(不写时为任一字段)的单字段校验正在进行 */\n validating(path?: string): boolean;\n /** 字段的错误是否该显示:失焦过、或提交过之后才显示;Field 用它 */\n visibleError(path: string): string | undefined;\n /** 字段失焦:Field 在焦点离开字段时调用 */\n blur(path: string): void;\n /**\n * 校验整份表单,返回是否通过。给了 paths 时只看这些字段(含子路径):它们的错误立即显示,\n * 返回它们是否都通过(分步表单进下一步前用)\n */\n validate(paths?: readonly string[]): Promise<boolean>;\n /**\n * 提交:先校验,通过后调用 onSubmit;返回是否提交成功。校验没通过,或 onSubmit 抛了带业务错误码的错误(后端回的\n * 校验失败、数据已被修改等,按字段的明细标到字段上,其余是 formError)时为 false;别的错误原样抛出\n */\n submit(): Promise<boolean>;\n /** 回到初始值,清空错误与失焦记录 */\n reset(): void;\n /** 字段从 schema 推出的约束(必填、长度、范围…);Field 交给控件,控件上显式写的优先 */\n constraints(path: string): FieldConstraints;\n /** 字段的当前值(按点连接的路径):Field 自动绑定值时用 */\n getValue(path: string): unknown;\n /** 写字段的值:中途是 null / undefined 时什么也不做 */\n setValue(path: string, value: unknown): void;\n /** 写了 name 的 Field 登记标签与控件的 id:错误汇总据此显示字段名、跳到控件。返回注销函数 */\n register(path: string, field: FormFieldInfo): () => void;\n /** 登记过的字段,按登记(渲染)的顺序 */\n readonly fields: ReadonlyMap<string, FormFieldInfo>;\n}\n\n/** Field 登记的字段信息 */\nexport interface FormFieldInfo {\n /** 标签的文字 */\n label: string;\n /** 控件的 id:错误汇总的链接指向它 */\n controlId: string;\n}\n\n/** 表单控制器:useForm 的返回值 */\nexport interface FormController<Values extends object> extends FormControllerBase {\n /** 当前值(响应式):控件写 _value={form.values.email} */\n readonly values: Values;\n /** 回到初始值(或给定的值,之后 dirty 以它为准),清空错误与失焦记录;保存成功后写 reset(form.values) */\n reset(values?: Values): void;\n /** 字段与初始值不同 */\n isDirty(path: FieldPath<Values>): boolean;\n /** 校验整份表单,或只校验 paths 里的字段(含子路径)并显示它们的错误;返回是否通过 */\n validate(paths?: readonly FieldPath<Values>[]): Promise<boolean>;\n /** 数组字段的增删、插入、移动 */\n array<P extends ArrayPath<Values>>(\n path: P,\n ): FieldArray<NonNullable<PathValue<Values, P>> extends readonly (infer Item)[] ? Item : never>;\n /**\n * 设置某个字段的错误(如服务端返回的“邮箱已注册”),undefined 清除;'' 是整份表单的错误。\n * 校验不会冲掉它,这个字段(整份表单的错误是任一字段)的值改了之后自动消失\n */\n setError(path: FieldPath<Values> | '', message: string | undefined): void;\n /**\n * 就是 Field 组件,只是 name 的类型收窄为这份值的字段路径,写错字段名时类型检查报错:\n * `<form.Field name=\"email\" label=\"邮箱\">…</form.Field>`(参考 TanStack Form 的 form.Field)\n */\n readonly Field: TsxComponent<Omit<FieldProps, 'name'> & { name?: FieldPath<Values> }>;\n}\n\n/** zod issue 的路径 → 点连接的字符串 */\nconst issuePath = (issue: $ZodIssue) => issue.path.map(String).join('.');\n\n/**\n * 该填的没有填:类型不对且是空值(null、undefined,包括缺少的键),或长度不够且是空字符串、空数组。\n * 选择、数字、日期控件清空后是 null(COMPONENTS.md §1 空值),z.string() 遇到它报的是类型不对,读起来应当是“必填”\n */\nfunction isBlankIssue({ code, input }: $ZodRawIssue): boolean {\n if (code === 'invalid_type') return input == null;\n if (code === 'too_small') return input === '' || (Array.isArray(input) && input.length === 0);\n return false;\n}\n\n/** 按点连接的路径写值;中途是 null / undefined 时什么也不做 */\nfunction writePath(target: unknown, path: string, value: unknown): void {\n const keys = path.split('.');\n const parent = readPath(target, keys.slice(0, -1).join('.'));\n if (keys.length === 1) (target as Record<string, unknown>)[keys[0]!] = value;\n else if (parent != null) (parent as Record<string, unknown>)[keys.at(-1)!] = value;\n}\n\n/** 按点连接的路径取值;中途是 null / undefined 时为 undefined */\nfunction readPath(source: unknown, path: string): unknown {\n let current = source;\n for (const key of path.split('.')) {\n if (current == null) return undefined;\n current = (current as Record<string, unknown>)[key];\n }\n return current;\n}\n\n/**\n * 数组字段的下标变了:`prefix.旧下标…` 的键改成 `prefix.新下标…`,map 返回 undefined 的(删掉的项)丢弃\n */\nfunction shiftIndices(key: string, prefix: string, map: (index: number) => number | undefined) {\n if (!key.startsWith(`${prefix}.`)) return key;\n const rest = key.slice(prefix.length + 1);\n const [head, ...tail] = rest.split('.');\n const index = Number(head);\n if (!Number.isInteger(index)) return key;\n const next = map(index);\n return next === undefined ? undefined : [prefix, next, ...tail].join('.');\n}\n\nlet nextArrayKey = 0;\n\n/** 一个字段的单字段校验的进行状态 */\ninterface FieldRun {\n /** 防抖的定时器 */\n timer?: ReturnType<typeof setTimeout>;\n /** 正在校验的那次 */\n running?: { value: unknown; controller: AbortController; done: Promise<void> };\n /** 上次校验完成时的值 */\n checked?: unknown;\n}\n\n/** 业务错误明细里按字段的一项(0008 的 errors:表单校验的问题) */\ninterface FieldIssue {\n readonly field: string;\n readonly code?: string;\n readonly message?: string;\n readonly params?: Readonly<Record<string, unknown>>;\n}\n\nfunction isFieldIssue(item: unknown): item is FieldIssue {\n return (\n typeof item === 'object' && item !== null && typeof (item as FieldIssue).field === 'string'\n );\n}\n\n/**\n * 创建表单控制器(组件 setup 里或模块顶层都可以)。\n * 有 schema 时值的类型取自 schema(initial 要符合它的输入类型),onSubmit 拿到解析后的输出\n */\nexport function useForm<Values extends object, Output = Values>(\n options: FormOptions<Values, Output>,\n): FormController<Values> {\n const { schema, validateOn = 'blur', onSubmit } = options;\n const lang = useLang();\n // zod 的报错跟随语言包;schema 里自己写的提示优先于它(zod 的规则)\n const parseOptions = {\n error: (issue: $ZodRawIssue) =>\n isBlankIssue(issue) ? lang.formRequired : lang.formIssues(issue),\n };\n const fieldValidators = Object.entries(options.fields ?? {}) as [\n string,\n FieldValidator<unknown, Values>,\n ][];\n const values = reactive(cloneValue(options.initial)) as Values;\n const state = shallowReactive({\n errors: {} as FormErrors,\n // 单字段校验的结果,与 errors 分开存:整份校验不会冲掉还在路上的单字段结果\n fieldErrors: {} as FormErrors,\n // setError 给的错误与当时的值:值一改就不再算\n externalErrors: {} as Record<string, { message: string; value: unknown }>,\n touched: new Set<string>(),\n pending: new Set<string>(),\n submitting: false,\n submitCount: 0,\n resetCount: 0,\n // dirty 的比较基准:初始值,或上次 reset 的值\n baseline: cloneValue(options.initial),\n });\n // 异步校验可能乱序返回:只采用最后一次的结果\n let run = 0;\n // 正在进行的提交:校验、onSubmit 期间再次提交(连按回车)拿到同一个结果,不重复调用 onSubmit\n let submission: Promise<boolean> | undefined;\n // 最近一次通过校验时 schema 解析出的值,交给 onSubmit\n let parsed: Output | undefined;\n const constraints = createConstraints(schema);\n const dirty = computed(() => !sameValue(values, state.baseline));\n // 数组字段每项的 key,与数组等长;数组被外部整个换掉(长度不同)时重新生成\n const arrayKeys = new Map<string, string[]>();\n\n // 写了 name 的 Field:错误汇总按它显示字段名、跳到控件\n const fields = shallowReactive(new Map<string, FormFieldInfo>());\n\n /** 单字段校验的进行状态:每个字段一份 */\n const fieldRuns = new Map<string, FieldRun>();\n\n function setPending(path: string, pending: boolean) {\n if (state.pending.has(path) === pending) return;\n const next = new Set(state.pending);\n if (pending) next.add(path);\n else next.delete(path);\n state.pending = next;\n }\n\n /**\n * 运行一个字段的校验。同一个值正在校验时等它;上次校验的就是这个值时直接用上次的结果;\n * 值变了就中止还在路上的那次(只采用最后一次的结果)\n */\n function runField(path: string, validator: FieldValidator<unknown, Values>): Promise<void> {\n const entry = fieldRuns.get(path) ?? {};\n fieldRuns.set(path, entry);\n clearTimeout(entry.timer);\n // 依赖的字段也算进“这次校验的输入”:依赖变了,同一个值也要重新校验\n const value = cloneValue([\n readPath(values, path),\n ...dependsOn(path).map((dep) => readPath(values, dep)),\n ]);\n if (entry.running && sameValue(entry.running.value, value)) return entry.running.done;\n if (!entry.running && 'checked' in entry && sameValue(entry.checked, value)) {\n return Promise.resolve();\n }\n entry.running?.controller.abort();\n const controller = new AbortController();\n setPending(path, true);\n const done = (async () => {\n try {\n const message = await validator.validate(value[0], values, controller.signal);\n if (controller.signal.aborted) return;\n entry.checked = value;\n state.fieldErrors = { ...state.fieldErrors, [path]: message };\n } catch (error) {\n // 校验本身失败(接口出错):当作没有结论,不挡提交(服务端还会再校验),但要报出来\n if (!controller.signal.aborted) console.error(`useForm:字段 ${path} 的校验出错`, error);\n } finally {\n if (entry.running?.controller === controller) {\n entry.running = undefined;\n setPending(path, false);\n }\n }\n })();\n entry.running = { value, controller, done };\n return done;\n }\n\n function cancelFieldRuns() {\n for (const entry of fieldRuns.values()) {\n clearTimeout(entry.timer);\n entry.running?.controller.abort();\n }\n fieldRuns.clear();\n state.pending = new Set();\n state.fieldErrors = {};\n }\n\n /** 字段依赖的字段 */\n const dependsOn = (path: string): readonly string[] =>\n (options.dependencies as Partial<Record<string, readonly string[]>> | undefined)?.[path] ?? [];\n\n /**\n * 校验整份值:schema 与 validate 的错误(errors)。full 时单字段校验也立刻跑完(提交、显式调用 validate;\n * 给了 scope 时只跑 scope 里的),结果在 state.fieldErrors;失焦、输入时只算 schema 与 validate,\n * 单字段校验照旧防抖、只在它自己(或它依赖的字段)的值变了时跑\n */\n async function collect(\n full: boolean,\n scope?: readonly string[],\n ): Promise<{ errors: FormErrors; output: Output }> {\n const errors: FormErrors = {};\n let output = values as unknown as Output;\n if (schema) {\n const result = await safeParseAsync(schema, values, parseOptions);\n if (result.success) output = result.data;\n for (const issue of result.error?.issues ?? []) {\n errors[issuePath(issue)] ??= issue.message;\n }\n }\n if (options.validate) {\n for (const [path, message] of Object.entries(await options.validate(values))) {\n if (message) errors[path] ??= message;\n }\n }\n // 单字段校验:不等防抖,立刻跑完(值没变的直接用上次结果)\n if (full) {\n await Promise.all(\n fieldValidators\n .filter(([path]) => !scope || inScope(path, scope))\n .map(([path, validator]) => runField(path, validator)),\n );\n }\n return { errors, output };\n }\n\n /**\n * onSubmit 抛的带业务错误码的错误(后端回的、请求层的,见 utils/request/error-message.ts)标到表单上:\n * errors 明细里带 field 的标到字段(文案按明细的码取,没有时用它的 message),一项都没有时整份表单的错误是错误的文案。\n * 和 setError 一样,值改了就不再显示;下一次提交开始时清掉(交给后端重新判断:网络断了之后不改值也能再提交)。\n * 认不出的错误返回 false,由 submit 原样抛出\n */\n function markSubmitError(error: unknown): boolean {\n if (errorCodeOf(error) === undefined) return false;\n const details = (error as { errors?: unknown }).errors;\n let marked = false;\n for (const item of Array.isArray(details) ? (details as unknown[]) : []) {\n if (!isFieldIssue(item)) continue;\n const text =\n (item.code && codeText(item.code, item.params, lang)) || item.message || item.code;\n controller.setError(item.field as FieldPath<Values>, text ?? lang.requestFailed);\n submitErrorPaths.add(item.field);\n marked = true;\n }\n if (!marked) {\n controller.setError('', errorMessage(error, lang));\n submitErrorPaths.add('');\n }\n return true;\n }\n\n /** 上一次提交失败时标上的路径 */\n const submitErrorPaths = new Set<string>();\n\n /** setError 给的错误,值改过就不算了;'' 是整份表单的错误,任一字段改了都不算 */\n function externalError(path: string): string | undefined {\n const entry = state.externalErrors[path];\n if (!entry) return undefined;\n const current = path === '' ? values : readPath(values, path);\n return sameValue(current, entry.value) ? entry.message : undefined;\n }\n\n /** schema 与 validate 的错误优先,再补上 setError 与单字段校验的 */\n function mergedErrors(): FormErrors {\n const merged = { ...state.errors };\n for (const path of Object.keys(state.externalErrors)) {\n const message = externalError(path);\n if (message) merged[path] ??= message;\n }\n for (const [path, message] of Object.entries(state.fieldErrors)) {\n if (message) merged[path] ??= message;\n }\n return merged;\n }\n\n /**\n * 校验并记下结果(只采用最后一次);返回是否通过:算出的、setError 给的、单字段校验的都没有错误。\n * 给了 scope 时只看 scope 里的字段,别的字段的错误保持原样\n */\n async function check(full: boolean, scope?: readonly string[]): Promise<boolean> {\n const current = ++run;\n const { errors, output } = await collect(full, scope);\n if (!scope) {\n if (current === run) {\n state.errors = errors;\n parsed = output;\n }\n return Object.keys(errors).length === 0 && Object.keys(mergedErrors()).length === 0;\n }\n const scoped = (source: FormErrors) =>\n Object.entries(source).filter(([path, message]) => message && inScope(path, scope));\n if (current === run) {\n state.errors = {\n ...Object.fromEntries(\n Object.entries(state.errors).filter(([path]) => !inScope(path, scope)),\n ),\n ...Object.fromEntries(scoped(errors)),\n };\n }\n return scoped(errors).length === 0 && scoped(mergedErrors()).length === 0;\n }\n\n /** 把这些字段记为失焦过:它们的错误开始显示 */\n function touch(paths: readonly string[]) {\n const next = new Set(state.touched);\n for (const path of paths) next.add(path);\n if (next.size !== state.touched.size) state.touched = next;\n }\n\n /** 数组字段的下标变了:错误、失焦记录、单字段校验的结果跟着移 */\n function remapIndices(prefix: string, map: (index: number) => number | undefined) {\n const remap = (errors: FormErrors) =>\n Object.fromEntries(\n Object.entries(errors).flatMap(([key, message]) => {\n const next = shiftIndices(key, prefix, map);\n return next === undefined ? [] : [[next, message]];\n }),\n ) as FormErrors;\n state.errors = remap(state.errors);\n state.fieldErrors = remap(state.fieldErrors);\n state.externalErrors = Object.fromEntries(\n Object.entries(state.externalErrors).flatMap(([key, entry]) => {\n const next = shiftIndices(key, prefix, map);\n return next === undefined ? [] : [[next, entry]];\n }),\n );\n state.touched = new Set(\n [...state.touched].flatMap((key) => shiftIndices(key, prefix, map) ?? []),\n );\n }\n\n function fieldArray(path: string): FieldArray<unknown> {\n const list = () => (readPath(values, path) as unknown[] | undefined) ?? [];\n const keys = () => {\n let current = arrayKeys.get(path);\n if (current?.length !== list().length) {\n current = list().map(() => `item-${++nextArrayKey}`);\n arrayKeys.set(path, current);\n }\n return current;\n };\n /** 改数组与 key:splice 的参数同时作用在两者上 */\n const splice = (start: number, deleteCount: number, ...items: unknown[]) => {\n keys().splice(start, deleteCount, ...items.map(() => `item-${++nextArrayKey}`));\n list().splice(start, deleteCount, ...items);\n };\n return {\n get items() {\n return list();\n },\n get keys() {\n return keys();\n },\n append: (item) => splice(list().length, 0, item),\n insert(index, item) {\n remapIndices(path, (old) => (old >= index ? old + 1 : old));\n splice(index, 0, item);\n },\n remove(index) {\n remapIndices(path, (old) => (old === index ? undefined : old > index ? old - 1 : old));\n splice(index, 1);\n },\n move(from, to) {\n if (from === to) return;\n remapIndices(path, (old) => {\n if (old === from) return to;\n if (from < to && old > from && old <= to) return old - 1;\n if (from > to && old >= to && old < from) return old + 1;\n return old;\n });\n const [key] = keys().splice(from, 1);\n const [item] = list().splice(from, 1);\n keys().splice(to, 0, key!);\n list().splice(to, 0, item);\n },\n };\n }\n\n const controller: FormController<Values> = {\n values,\n get errors() {\n return mergedErrors();\n },\n get formError() {\n if (state.submitCount === 0) return undefined;\n return state.errors[''] ?? externalError('');\n },\n get submitting() {\n return state.submitting;\n },\n get resetCount() {\n return state.resetCount;\n },\n get submitCount() {\n return state.submitCount;\n },\n get dirty() {\n return dirty.value;\n },\n validating(path) {\n return path === undefined ? state.pending.size > 0 : state.pending.has(path);\n },\n visibleError(path) {\n // 失焦过的字段,或局部校验过的字段(含子路径:validate(['address']) 之后 address.city 也显示)\n if (state.submitCount === 0 && !inScope(path, [...state.touched])) return undefined;\n return state.errors[path] ?? externalError(path) ?? state.fieldErrors[path];\n },\n blur(path) {\n if (state.touched.has(path)) return;\n state.touched = new Set(state.touched).add(path);\n if (validateOn !== 'submit') void check(false);\n },\n validate(paths) {\n if (!paths) return check(true);\n touch(paths);\n return check(true, paths);\n },\n submit() {\n submission ??= (async () => {\n state.submitCount++;\n for (const path of submitErrorPaths)\n controller.setError(path as FieldPath<Values>, undefined);\n submitErrorPaths.clear();\n const valid = await check(true);\n if (!valid || !onSubmit) return valid;\n state.submitting = true;\n try {\n await onSubmit(parsed as Output);\n } catch (error) {\n if (!markSubmitError(error)) throw error;\n return false;\n } finally {\n state.submitting = false;\n }\n return true;\n })().finally(() => (submission = undefined));\n return submission;\n },\n reset(next = options.initial) {\n run++;\n const snapshot = cloneValue(next);\n cancelFieldRuns();\n Object.assign(values, cloneValue(snapshot));\n arrayKeys.clear();\n state.baseline = snapshot;\n state.errors = {};\n state.externalErrors = {};\n state.touched = new Set();\n state.submitCount = 0;\n state.resetCount++;\n },\n isDirty(path) {\n return !sameValue(readPath(values, path), readPath(state.baseline, path));\n },\n array(path) {\n return fieldArray(path) as never;\n },\n setError(path, message) {\n const { [path]: _previous, ...rest } = state.externalErrors;\n const value = path === '' ? values : readPath(values, path);\n state.externalErrors =\n message === undefined ? rest : { ...rest, [path]: { message, value: cloneValue(value) } };\n },\n constraints,\n Field,\n getValue: (path) => readPath(values, path),\n setValue: (path, value) => writePath(values, path, value),\n register(path, field) {\n fields.set(path, field);\n return () => {\n if (fields.get(path) === field) fields.delete(path);\n };\n },\n fields,\n };\n\n // 输入时:change 模式一开始就校验;blur 模式在有字段失焦过之后(只显示失焦过的字段);提交过之后总是\n watch(\n () => values,\n () => {\n const live =\n validateOn === 'change' ||\n state.submitCount > 0 ||\n (validateOn === 'blur' && state.touched.size > 0);\n if (live) void check(false);\n },\n { deep: true },\n );\n\n // 字段联动:依赖的值改了,字段填过或失焦过就显示它的错误并重新校验(单字段校验由下面的监听跟着重跑)\n for (const [path, deps] of Object.entries(options.dependencies ?? {}) as [string, string[]][]) {\n watch(\n () => deps.map((dep) => readPath(values, dep)),\n () => {\n const value = readPath(values, path);\n const filled = !(value == null || value === '' || (Array.isArray(value) && !value.length));\n if (!filled && !state.touched.has(path)) return;\n touch([path]);\n if (validateOn !== 'submit' || state.submitCount > 0) void check(false);\n },\n { deep: true },\n );\n }\n\n // 单字段校验:这个字段(或它依赖的字段)的值变了,防抖之后运行\n for (const [path, validator] of fieldValidators) {\n watch(\n () => [readPath(values, path), ...dependsOn(path).map((dep) => readPath(values, dep))],\n () => {\n const entry = fieldRuns.get(path) ?? {};\n fieldRuns.set(path, entry);\n clearTimeout(entry.timer);\n entry.timer = setTimeout(() => void runField(path, validator), validator.debounce ?? 300);\n },\n { deep: true },\n );\n }\n\n return controller;\n}\n"],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -3,6 +3,8 @@ export { Blockquote, blockquoteStyles, type BlockquoteProps } from './base/Block
3
3
  export { Button, type ButtonManaged, type ButtonProps } from './base/Button';
4
4
  export { ButtonBase, type ButtonBaseManaged, type ButtonBaseProps } from './base/ButtonBase';
5
5
  export { ButtonGroup, buttonGroupStyles, type ButtonGroupManaged, type ButtonGroupProps, } from './base/ButtonGroup';
6
+ export { Can, type CanProps } from './base/Can';
7
+ export { ClientOnly, type ClientOnlyProps } from './base/ClientOnly';
6
8
  export { ClearButton, CloseButton, createPresetIconButton, type PresetIconButtonProps, } from './base/CloseButton';
7
9
  export { Code, codeStyles, type CodeProps } from './base/Code';
8
10
  export { CodeBlock, codeBlockDynamicStyles, codeBlockStyles, parseLineSpec, type CodeBlockProps, type LineSpec, } from './base/CodeBlock';
@@ -83,10 +85,11 @@ export { Popconfirm, popconfirmStyles, type PopconfirmProps } from './overlay/Po
83
85
  export { Popover, popoverStyles, type PopoverProps, type PopoverTriggerProps, } from './overlay/Popover';
84
86
  export { Tooltip, tooltipStyles, type TooltipProps, type TooltipTriggerProps, } from './overlay/Tooltip';
85
87
  export { Tour, tourDynamicStyles, tourStyles, type TourProps, type TourStep } from './overlay/Tour';
86
- export { useDetachedDialog, useDetachedOverlay, type DetachedDialogHost, type DetachedOverlayHandle, type DetachedOverlayOptions, } from './overlay/detached';
88
+ export { useDetachedDialog, useDetachedOverlay, useDetachedRender, type DetachedDialogHost, type DetachedOverlayHandle, type DetachedOverlayOptions, } from './overlay/detached';
87
89
  export { Breadcrumbs, breadcrumbsStyles, type BreadcrumbItem, type BreadcrumbsProps, } from './navigation/Breadcrumbs';
88
90
  export { NavBar, navBarStyles, type NavBarItem, type NavBarProps } from './navigation/NavBar';
89
91
  export { NavCompactContext, NavGroup, NavGroupContext, NavItem, NavList, navListStyles, NavSection, type NavGroupProps, type NavGroupState, type NavItemManaged, type NavItemProps, type NavListProps, type NavSectionProps, } from './navigation/NavList';
92
+ export { NavMenu, type NavMenuItem, type NavMenuProps, type NavMenuTopLevels, } from './navigation/NavMenu';
90
93
  export { PageJump, pageJumpStyles, type PageJumpProps } from './navigation/PageJump';
91
94
  export { PageSizeSelect, pageSizeSelectStyles, type PageSizeSelectProps, } from './navigation/PageSizeSelect';
92
95
  export { Pager, pagerStyles, type PagerLink, type PagerProps } from './navigation/Pager';
@@ -182,8 +185,8 @@ export { parseCsv, readCsvFile, sheetFromMatrix, type SheetMatrixOptions, type S
182
185
  export { dataTableRecipe, dataTableStateStyles, type DataTableDensities, type DataTableOptions, type DataTablePart, type DataTableVariants, } from './table/table-styles';
183
186
  export { tableParams, useTableQuery, type TablePage, type TableQuery, type TableQueryOptions, type TableQueryState, } from './table/use-table-query';
184
187
  export { ConfigScope, Provider, providerStyles, type ConfigScopeProps, type ProviderProps, } from './config/Provider';
185
- export { ConfigContext, defaultConfig, useConfig, useLang, useTextMatcher, type Highlighter, type QueryPatch, type QueryValues, type TextMatcher, type UiConfig, type UiRouter, } from './config/context';
186
- export { createUi, documentStyles, type UiOptions } from './config/createUi';
188
+ export { ConfigContext, defaultConfig, useCan, useConfig, useLang, useTextMatcher, type Highlighter, type QueryPatch, type QueryValues, type TextMatcher, type UiConfig, type UiPermissions, type UiRouter, } from './config/context';
189
+ export { createUi, documentStyles, type DocumentAttrs, type UiOptions, type UiPlugin, } from './config/createUi';
187
190
  export { mergeDefaults, useDefaults, type ComponentDefaults, type ResolvedDefaults, } from './config/defaults';
188
191
  export { queryCodecs, useQueryState, useQueryStates, type QueryCodec, type QueryCodecs, } from './config/query-state';
189
192
  export { mergeStyles, partAttrs, useRecipe, type ComponentStyles, type PartAttrs, type PartLayer, type StyleLayers, } from './config/styles';
@@ -205,9 +208,10 @@ export { sakuraColors, sakuraRadii, sakuraShadows, sakuraTheme } from './config/
205
208
  export { defineTheme, readToken, resolveTheme, themeAttrs, themeVariables, tokenValue, type ColorSchemes, type GroupTheme, type Theme, type ThemeInput, type ThemeOverrides, } from './config/theme/theme';
206
209
  import './config/theme/token-names';
207
210
  export { t } from './config/theme/tokens';
211
+ export { createLayoutPreference, DEFAULT_LAYOUT_HEIGHT, DEFAULT_LAYOUT_STORAGE_KEY, DEFAULT_LAYOUT_WIDTH, LAYOUT_MODES, LAYOUT_SIDES, type LayoutMode, type LayoutPreference, type LayoutPreferenceOptions, type LayoutSide, type LayoutState, } from './config/layout/preference';
208
212
  export { enUS } from './config/lang/en-US';
209
213
  export { useFormat, type NumericFormat, type RelativeFormatOptions } from './config/lang/format';
210
- export { defineLang, type Directions, type HourCycles, type Lang, type LangMessages, } from './config/lang/lang';
214
+ export { defineLang, type Directions, type ErrorCodeText, type HourCycles, type Lang, type LangMessages, } from './config/lang/lang';
211
215
  export { useMessages, type MessageTable } from './config/lang/messages';
212
216
  export { zhCN } from './config/lang/zh-CN';
213
217
  export { applyStyleAttrs, dataAttrs, defaultLabelledBy, elementProps, hasContent, mergeAttrs, mergePartProps, mergeProps, type PartProps, type TriggerProps, } from './utils/props';
@@ -224,8 +228,8 @@ export { announce, type Politeness, type Politenesses } from './utils/dom/announ
224
228
  export { caretAnchor, caretRect } from './utils/dom/caret';
225
229
  export { useClipboard, type ClipboardOptions, type ClipboardState } from './utils/dom/clipboard';
226
230
  export { inlineArrows, isRtl, type InlineArrows } from './utils/dom/direction';
227
- export { downloadFile } from './utils/dom/download';
228
- export { toElement } from './utils/dom/element';
231
+ export { downloadFile, pickFile } from './utils/dom/download';
232
+ export { isHTMLElement, toElement } from './utils/dom/element';
229
233
  export { useEventListener, type ListenerTarget } from './utils/dom/event-listener';
230
234
  export { anchoredPanelSx, anchorFloating, createAnchoredLayer, floatingStyles, pointAnchor, useAnchoredPopover, type Anchored, type AnchoredLayer, type AnchoredPopoverEvents, type AnchoredPopoverState, type FloatingAnchor, type FloatingOptions, type FloatingPlacement, type FloatingPlacements, type RestoreFocus, } from './utils/dom/floating';
231
235
  export { focusAfterRemoval, focusControl, focusFirst, focusOrigin, returnFocus, trapFocus, } from './utils/dom/focus';
@@ -237,6 +241,7 @@ export { useIntersectionObserver, type IntersectionOptions } from './utils/dom/i
237
241
  export { batchLayout } from './utils/dom/layout-batch';
238
242
  export { externalLinkAttrs, shouldRouteClick, useRouterLink, type RouterLink, } from './utils/dom/link';
239
243
  export { useMediaQuery, usePrefersDark, useReducedMotion } from './utils/dom/media-query';
244
+ export { useMounted } from './utils/dom/mounted';
240
245
  export { startMove, type MoveOptions, type MovePoint } from './utils/dom/move';
241
246
  export { openSetter, type OpenChangeDetails, type OpenChangeProps, type OpenChangeReasons, type SetOpen, } from './utils/dom/open-change';
242
247
  export { fitCount, useOverflowItems, type OverflowItems, type OverflowItemsOptions, } from './utils/dom/overflow';
@@ -251,16 +256,17 @@ export { collectHeadings, useScrollSpy, type HeadingEntry, type ScrollSpyOptions
251
256
  export { useSortable, type SortableGhost, type SortableGroups, type SortableOptions, type SortableOrientations, type SortableState, type SortableTarget, } from './utils/dom/sortable';
252
257
  export { usePersistentState, type PersistentStateOptions } from './utils/dom/storage';
253
258
  export { createTypeahead, type Typeahead } from './utils/dom/typeahead';
259
+ export { cloneValue } from './utils/data/clone';
254
260
  export { createComparer, createMatcher, isDateValue, sortByValue, sortByValues, type SortKey, type SortValue, } from './utils/data/collation';
255
261
  export { CONTRAST_AA, contrastRatio, parseColor, readableText, toHex, withAlpha, } from './utils/data/color';
256
- export { calendarOf, clampDate, compareDates, daysBetween, fullIsoDay, isDateDisabled, isInRange, isoDayOfWeek, isOutOfRange, isOutsideDecade, isoWeekOf, isoWeeksInYear, isSameDate, lastIsoDay, monthGrid, monthsOfYear, orderedRange, parseIsoDate, parseIsoDateTime, parseIsoRange, parseIsoTime, presetIsoRange, presetRange, rangeDays, rangeHasUnavailable, relativeUnit, shiftIsoDate, todayIso, toDayOfWeek, toInstant, toIsoDate, toIsoDateTime, toIsoQuarter, toIsoRange, toIsoTime, toIsoWeek, truncateDate, weekdayNames, weekOfYear, yearsOfDecade, type DateLimits, type DateRange, type DateRangePreset, type DateRangePresets, type DateUnit, type DateUnits, type MonthGridOptions, type WeekdayFormats, type WeekOptions, } from './utils/data/date';
262
+ export { calendarOf, clampDate, compareDates, dayRangeBounds, daysBetween, fullIsoDay, isDateDisabled, isInRange, isoDayOfWeek, isOutOfRange, isOutsideDecade, isoWeekOf, isoWeeksInYear, isSameDate, lastIsoDay, monthGrid, monthsOfYear, orderedRange, parseIsoDate, parseIsoDateTime, parseIsoRange, parseIsoTime, presetIsoRange, presetRange, rangeDays, rangeHasUnavailable, relativeUnit, shiftIsoDate, todayIso, toDayOfWeek, toInstant, toIsoDate, toIsoDateTime, toIsoQuarter, toIsoRange, toIsoTime, toIsoWeek, truncateDate, weekdayNames, weekOfYear, yearsOfDecade, type DateLimits, type DateRange, type DateRangePreset, type DateRangePresets, type DateUnit, type DateUnits, type MonthGridOptions, type WeekdayFormats, type WeekOptions, } from './utils/data/date';
257
263
  export { diffFields, type FieldDiff } from './utils/data/diff';
258
264
  export { sameValue } from './utils/data/equality';
259
265
  export { createHistory, type History, type HistoryCommitOptions, type HistoryGesture, type HistoryOptions, } from './utils/data/history';
260
266
  export { cachedIntl } from './utils/data/intl-cache';
261
267
  export { normalizeNumber, parseNumber } from './utils/data/number-parse';
262
- export { clamp, snapToStep } from './utils/data/number';
263
- export { addNumeric, compareNumeric, isDecimal, isNumeric, isNumericText, multiplyNumeric, numericKind, numericText, sameNumeric, shiftDecimal, snapNumeric, toNumber, toNumericKind, type NumericKind, type NumericValue, } from './utils/data/numeric';
268
+ export { clamp, snapToStep, svgNumber } from './utils/data/number';
269
+ export { addNumeric, compareDecimalText, compareNumeric, decimalTextOf, isDecimal, isNumeric, isNumericText, multiplyNumeric, numericKind, numericText, sameNumeric, shiftDecimal, snapNumeric, toNumber, toNumericKind, type NumericKind, type NumericValue, } from './utils/data/numeric';
264
270
  export { paginationItems, type PaginationEllipsis, type PaginationOptions, } from './utils/data/pagination';
265
271
  export { selectedValues, toggleSelection, type MultipleSelectionProps, type SelectionModes, type SelectionOptions, type SelectionProps, type SingleSelectionProps, } from './utils/data/selection';
266
272
  export { createStatusMap, type StatusDefinition, type StatusMap } from './utils/data/status';
@@ -269,7 +275,9 @@ export { buildTree, checkedByStrategy, checkStates, filterTree, flattenTree, ind
269
275
  export { addWorkdays, createWorkCalendar, deadlineOf, useWorkCalendar, weekdayCalendar, workdaysBetween, type Deadline, type DeadlineInput, type DeadlineStates, type WorkCalendar, type WorkCalendarOptions, } from './utils/data/workdays';
270
276
  export { useVirtualizer, type UseVirtualizerOptions, type VirtualView, } from './utils/virtual/use-virtualizer';
271
277
  export { computeMeasurements, defaultRangeExtractor, findVisibleRange, isWindow, Virtualizer, type ExtractorRange, type ScrollAligns, type VirtualItem, type VirtualizerOptions, type VirtualScrollOptions, type VisibleRange, } from './utils/virtual/virtualizer';
272
- export { createHttpClient, HttpError, isAbortError, useHttp, type FetchClient, type HttpClient, type HttpClientOptions, type HttpErrorContext, type HttpMethod, type HttpRequest, type QueryValue, } from './utils/request/http';
278
+ export { codeText, errorCodeOf, errorCodeText, errorMessage, useErrorMessage, } from './utils/request/error-message';
279
+ export { createHttpClient, HTTP_ERROR_CODES, HttpError, isAbortError, OK_CODE, scopeHttp, useHttp, type ApiEnvelope, type ApiErrorDetail, type FetchClient, type HttpClient, type HttpClientOptions, type HttpErrorCode, type HttpErrorContext, type HttpMethod, type HttpRequest, type HttpWire, type QueryValue, } from './utils/request/http';
280
+ export { useAutosave, type Autosave, type AutosaveOptions, type AutosaveStatus, } from './utils/request/use-autosave';
273
281
  export { useRequest, type RequestOptions, type RequestState } from './utils/request/use-request';
274
282
  export { fromWire, toWire, wireText, type WireReviveOptions, type WireScalar, type WireType, type WireTypes, } from './utils/request/wire';
275
283
  export { useActiveIndicator, type ActiveIndicatorOptions, type IndicatorAxes, } from './utils/motion/active-indicator';