@zerodep-dev/vue-tsx-pro-ui 0.2.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 (416) 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/config/Provider.d.ts +3 -1
  62. package/dist/config/Provider.js +9 -4
  63. package/dist/config/Provider.js.map +1 -1
  64. package/dist/config/context.d.ts +18 -0
  65. package/dist/config/context.js +16 -1
  66. package/dist/config/context.js.map +1 -1
  67. package/dist/config/createUi.d.ts +20 -3
  68. package/dist/config/createUi.js +80 -49
  69. package/dist/config/createUi.js.map +1 -1
  70. package/dist/config/layout/preference.d.ts +43 -0
  71. package/dist/config/layout/preference.js +108 -0
  72. package/dist/config/layout/preference.js.map +1 -0
  73. package/dist/date/Calendar.js +3 -3
  74. package/dist/date/Calendar.js.map +1 -1
  75. package/dist/date/CalendarBoard.js +9 -9
  76. package/dist/date/CalendarBoard.js.map +1 -1
  77. package/dist/date/CalendarView.js +7 -7
  78. package/dist/date/CalendarView.js.map +1 -1
  79. package/dist/date/DateField.js +3 -3
  80. package/dist/date/DateField.js.map +1 -1
  81. package/dist/date/DatePicker.js +5 -5
  82. package/dist/date/DatePicker.js.map +1 -1
  83. package/dist/date/DateRangePicker.js +5 -5
  84. package/dist/date/DateRangePicker.js.map +1 -1
  85. package/dist/date/DateSegmentGroup.js +5 -5
  86. package/dist/date/DateSegmentGroup.js.map +1 -1
  87. package/dist/date/QuarterCalendar.js +6 -6
  88. package/dist/date/QuarterCalendar.js.map +1 -1
  89. package/dist/date/RangeCalendar.js +3 -3
  90. package/dist/date/RangeCalendar.js.map +1 -1
  91. package/dist/date/SegmentedField.js +3 -3
  92. package/dist/date/SegmentedField.js.map +1 -1
  93. package/dist/date/TimeField.js +3 -3
  94. package/dist/date/TimeField.js.map +1 -1
  95. package/dist/date/TimePicker.js +4 -4
  96. package/dist/date/TimePicker.js.map +1 -1
  97. package/dist/date/date-segments.js +1 -1
  98. package/dist/date/date-segments.js.map +1 -1
  99. package/dist/date/picker-popover.js +3 -2
  100. package/dist/date/picker-popover.js.map +1 -1
  101. package/dist/diagram/Diagram.js +12 -12
  102. package/dist/diagram/Diagram.js.map +1 -1
  103. package/dist/diagram/DiagramLanes.js +3 -3
  104. package/dist/diagram/DiagramLanes.js.map +1 -1
  105. package/dist/diagram/DiagramMinimap.js +4 -4
  106. package/dist/diagram/DiagramMinimap.js.map +1 -1
  107. package/dist/display/Accordion.js +6 -6
  108. package/dist/display/Accordion.js.map +1 -1
  109. package/dist/display/AnimatedNumber.js +4 -4
  110. package/dist/display/AnimatedNumber.js.map +1 -1
  111. package/dist/display/Avatar.js +4 -4
  112. package/dist/display/Avatar.js.map +1 -1
  113. package/dist/display/Checklist.js +7 -7
  114. package/dist/display/Checklist.js.map +1 -1
  115. package/dist/display/Collapse.js +8 -5
  116. package/dist/display/Collapse.js.map +1 -1
  117. package/dist/display/Collapsible.js +3 -3
  118. package/dist/display/Collapsible.js.map +1 -1
  119. package/dist/display/Descriptions.js +5 -5
  120. package/dist/display/Descriptions.js.map +1 -1
  121. package/dist/display/Ellipsis.js +3 -3
  122. package/dist/display/Ellipsis.js.map +1 -1
  123. package/dist/display/FieldCompare.js +4 -4
  124. package/dist/display/FieldCompare.js.map +1 -1
  125. package/dist/display/Gauge.js +11 -11
  126. package/dist/display/Gauge.js.map +1 -1
  127. package/dist/display/Image.js +4 -4
  128. package/dist/display/Image.js.map +1 -1
  129. package/dist/display/Marquee.js +3 -3
  130. package/dist/display/Marquee.js.map +1 -1
  131. package/dist/display/ProportionBar.js +4 -4
  132. package/dist/display/ProportionBar.js.map +1 -1
  133. package/dist/display/Sortable.js +4 -4
  134. package/dist/display/Sortable.js.map +1 -1
  135. package/dist/display/Sparkline.js +6 -6
  136. package/dist/display/Sparkline.js.map +1 -1
  137. package/dist/display/Stat.js +3 -3
  138. package/dist/display/Stat.js.map +1 -1
  139. package/dist/display/Timeline.js +7 -7
  140. package/dist/display/Timeline.js.map +1 -1
  141. package/dist/display/Tree.js +10 -10
  142. package/dist/display/Tree.js.map +1 -1
  143. package/dist/display/VirtualList.js +7 -7
  144. package/dist/display/VirtualList.js.map +1 -1
  145. package/dist/display/Watermark.js +3 -3
  146. package/dist/display/Watermark.js.map +1 -1
  147. package/dist/docs-kit/CodeGroup.js +5 -5
  148. package/dist/docs-kit/CodeGroup.js.map +1 -1
  149. package/dist/docs-kit/CodePreview.js +6 -6
  150. package/dist/docs-kit/CodePreview.js.map +1 -1
  151. package/dist/docs-kit/PropsTable.js +4 -4
  152. package/dist/docs-kit/PropsTable.js.map +1 -1
  153. package/dist/feedback/Alert.js +5 -5
  154. package/dist/feedback/Alert.js.map +1 -1
  155. package/dist/feedback/Banner.js +5 -5
  156. package/dist/feedback/Banner.js.map +1 -1
  157. package/dist/feedback/Empty.js +4 -4
  158. package/dist/feedback/Empty.js.map +1 -1
  159. package/dist/feedback/LoadingBar.js +20 -13
  160. package/dist/feedback/LoadingBar.js.map +1 -1
  161. package/dist/feedback/LoadingOverlay.js +3 -3
  162. package/dist/feedback/LoadingOverlay.js.map +1 -1
  163. package/dist/feedback/Progress.js +14 -14
  164. package/dist/feedback/Progress.js.map +1 -1
  165. package/dist/feedback/Result.js +4 -4
  166. package/dist/feedback/Result.js.map +1 -1
  167. package/dist/feedback/SkeletonText.js +3 -3
  168. package/dist/feedback/SkeletonText.js.map +1 -1
  169. package/dist/feedback/Toast.js +6 -6
  170. package/dist/feedback/Toast.js.map +1 -1
  171. package/dist/form/Field.js +7 -7
  172. package/dist/form/Field.js.map +1 -1
  173. package/dist/form/Form.js +3 -3
  174. package/dist/form/Form.js.map +1 -1
  175. package/dist/form/FormErrorSummary.js +3 -3
  176. package/dist/form/FormErrorSummary.js.map +1 -1
  177. package/dist/form/FormSection.js +6 -6
  178. package/dist/form/FormSection.js.map +1 -1
  179. package/dist/index.d.ts +11 -6
  180. package/dist/index.js +10 -5
  181. package/dist/input/Autocomplete.js +3 -3
  182. package/dist/input/Autocomplete.js.map +1 -1
  183. package/dist/input/Cascader.js +7 -7
  184. package/dist/input/Cascader.js.map +1 -1
  185. package/dist/input/CharacterCount.js +3 -3
  186. package/dist/input/CharacterCount.js.map +1 -1
  187. package/dist/input/Checkbox.js +5 -5
  188. package/dist/input/Checkbox.js.map +1 -1
  189. package/dist/input/ColorField.js +4 -4
  190. package/dist/input/ColorField.js.map +1 -1
  191. package/dist/input/Combobox.js +4 -4
  192. package/dist/input/Combobox.js.map +1 -1
  193. package/dist/input/ConditionBuilder.js +15 -14
  194. package/dist/input/ConditionBuilder.js.map +1 -1
  195. package/dist/input/FileDrop.js +3 -3
  196. package/dist/input/FileDrop.js.map +1 -1
  197. package/dist/input/Listbox.js +9 -9
  198. package/dist/input/Listbox.js.map +1 -1
  199. package/dist/input/Mentions.js +3 -3
  200. package/dist/input/Mentions.js.map +1 -1
  201. package/dist/input/NumberField.js +3 -3
  202. package/dist/input/NumberField.js.map +1 -1
  203. package/dist/input/NumberRangeField.js +5 -5
  204. package/dist/input/NumberRangeField.js.map +1 -1
  205. package/dist/input/OtpField.js +5 -5
  206. package/dist/input/OtpField.js.map +1 -1
  207. package/dist/input/PeoplePicker.js +6 -6
  208. package/dist/input/PeoplePicker.js.map +1 -1
  209. package/dist/input/PeoplePickerPanel.js +4 -4
  210. package/dist/input/PeoplePickerPanel.js.map +1 -1
  211. package/dist/input/Radio.js +4 -4
  212. package/dist/input/Radio.js.map +1 -1
  213. package/dist/input/Rating.js +5 -5
  214. package/dist/input/Rating.js.map +1 -1
  215. package/dist/input/Select.js +4 -4
  216. package/dist/input/Select.js.map +1 -1
  217. package/dist/input/SelectedTags.js +4 -4
  218. package/dist/input/SelectedTags.js.map +1 -1
  219. package/dist/input/Slider.js +6 -6
  220. package/dist/input/Slider.js.map +1 -1
  221. package/dist/input/Switch.js +3 -3
  222. package/dist/input/Switch.js.map +1 -1
  223. package/dist/input/TagsInput.js +5 -5
  224. package/dist/input/TagsInput.js.map +1 -1
  225. package/dist/input/TextField.js +5 -5
  226. package/dist/input/TextField.js.map +1 -1
  227. package/dist/input/Textarea.js +4 -4
  228. package/dist/input/Textarea.js.map +1 -1
  229. package/dist/input/ToggleGroup.js +4 -4
  230. package/dist/input/ToggleGroup.js.map +1 -1
  231. package/dist/input/Transfer.js +8 -8
  232. package/dist/input/Transfer.js.map +1 -1
  233. package/dist/input/TreeSelect.js +6 -6
  234. package/dist/input/TreeSelect.js.map +1 -1
  235. package/dist/input/Upload.js +8 -8
  236. package/dist/input/Upload.js.map +1 -1
  237. package/dist/layout/Affix.js +4 -4
  238. package/dist/layout/Affix.js.map +1 -1
  239. package/dist/layout/ResizeHandle.d.ts +6 -2
  240. package/dist/layout/ResizeHandle.js +32 -9
  241. package/dist/layout/ResizeHandle.js.map +1 -1
  242. package/dist/layout/ScrollArea.js +3 -3
  243. package/dist/layout/ScrollArea.js.map +1 -1
  244. package/dist/layout/Separator.js +3 -3
  245. package/dist/layout/Separator.js.map +1 -1
  246. package/dist/layout/Splitter.js +3 -3
  247. package/dist/layout/Splitter.js.map +1 -1
  248. package/dist/layout/ViewportFill.d.ts +2 -2
  249. package/dist/layout/ViewportFill.js +4 -4
  250. package/dist/layout/ViewportFill.js.map +1 -1
  251. package/dist/navigation/Breadcrumbs.js +4 -4
  252. package/dist/navigation/Breadcrumbs.js.map +1 -1
  253. package/dist/navigation/NavBar.js +6 -6
  254. package/dist/navigation/NavBar.js.map +1 -1
  255. package/dist/navigation/NavList.d.ts +1 -0
  256. package/dist/navigation/NavList.js +20 -12
  257. package/dist/navigation/NavList.js.map +1 -1
  258. package/dist/navigation/NavMenu.d.ts +27 -0
  259. package/dist/navigation/NavMenu.js +54 -0
  260. package/dist/navigation/NavMenu.js.map +1 -0
  261. package/dist/navigation/PageJump.js +3 -3
  262. package/dist/navigation/PageJump.js.map +1 -1
  263. package/dist/navigation/PageSizeSelect.js +4 -4
  264. package/dist/navigation/PageSizeSelect.js.map +1 -1
  265. package/dist/navigation/Pagination.js +6 -6
  266. package/dist/navigation/Pagination.js.map +1 -1
  267. package/dist/navigation/PaginationBar.js +5 -5
  268. package/dist/navigation/PaginationBar.js.map +1 -1
  269. package/dist/navigation/Steps.js +4 -4
  270. package/dist/navigation/Steps.js.map +1 -1
  271. package/dist/navigation/TableOfContents.js +5 -5
  272. package/dist/navigation/TableOfContents.js.map +1 -1
  273. package/dist/navigation/Tabs.js +7 -7
  274. package/dist/navigation/Tabs.js.map +1 -1
  275. package/dist/navigation/Toolbar.js +3 -3
  276. package/dist/navigation/Toolbar.js.map +1 -1
  277. package/dist/overlay/AlertDialog.js +4 -4
  278. package/dist/overlay/AlertDialog.js.map +1 -1
  279. package/dist/overlay/CommandPalette.js +4 -4
  280. package/dist/overlay/CommandPalette.js.map +1 -1
  281. package/dist/overlay/ContextMenu.js +3 -3
  282. package/dist/overlay/ContextMenu.js.map +1 -1
  283. package/dist/overlay/Dialog.js +4 -4
  284. package/dist/overlay/Dialog.js.map +1 -1
  285. package/dist/overlay/Drawer.js +5 -5
  286. package/dist/overlay/Drawer.js.map +1 -1
  287. package/dist/overlay/HoverCard.js +5 -3
  288. package/dist/overlay/HoverCard.js.map +1 -1
  289. package/dist/overlay/Lightbox.js +5 -5
  290. package/dist/overlay/Lightbox.js.map +1 -1
  291. package/dist/overlay/Menu.js +7 -7
  292. package/dist/overlay/Menu.js.map +1 -1
  293. package/dist/overlay/ModalCore.js +9 -9
  294. package/dist/overlay/ModalCore.js.map +1 -1
  295. package/dist/overlay/Popconfirm.js +6 -6
  296. package/dist/overlay/Popconfirm.js.map +1 -1
  297. package/dist/overlay/Popover.js +5 -3
  298. package/dist/overlay/Popover.js.map +1 -1
  299. package/dist/overlay/Tooltip.js +5 -3
  300. package/dist/overlay/Tooltip.js.map +1 -1
  301. package/dist/overlay/Tour.js +8 -8
  302. package/dist/overlay/Tour.js.map +1 -1
  303. package/dist/overlay/detached.d.ts +2 -0
  304. package/dist/overlay/detached.js +73 -42
  305. package/dist/overlay/detached.js.map +1 -1
  306. package/dist/patterns/ActionBar.js +3 -3
  307. package/dist/patterns/ActionBar.js.map +1 -1
  308. package/dist/patterns/AppShell.d.ts +28 -1
  309. package/dist/patterns/AppShell.js +158 -52
  310. package/dist/patterns/AppShell.js.map +1 -1
  311. package/dist/patterns/AuthLayout.d.ts +28 -0
  312. package/dist/patterns/AuthLayout.js +77 -0
  313. package/dist/patterns/AuthLayout.js.map +1 -0
  314. package/dist/patterns/CommentThread.js +9 -9
  315. package/dist/patterns/CommentThread.js.map +1 -1
  316. package/dist/patterns/DataImport.js +4 -4
  317. package/dist/patterns/DataImport.js.map +1 -1
  318. package/dist/patterns/DeadlineBadge.js +3 -3
  319. package/dist/patterns/DeadlineBadge.js.map +1 -1
  320. package/dist/patterns/FilterBar.d.ts +11 -1
  321. package/dist/patterns/FilterBar.js +39 -10
  322. package/dist/patterns/FilterBar.js.map +1 -1
  323. package/dist/patterns/FormDialog.d.ts +18 -0
  324. package/dist/patterns/FormDialog.js +46 -0
  325. package/dist/patterns/FormDialog.js.map +1 -0
  326. package/dist/patterns/KanbanBoard.js +8 -8
  327. package/dist/patterns/KanbanBoard.js.map +1 -1
  328. package/dist/patterns/LayoutSwitcher.d.ts +12 -0
  329. package/dist/patterns/LayoutSwitcher.js +79 -0
  330. package/dist/patterns/LayoutSwitcher.js.map +1 -0
  331. package/dist/patterns/NotificationCenter.js +8 -8
  332. package/dist/patterns/NotificationCenter.js.map +1 -1
  333. package/dist/patterns/PageTabs.d.ts +12 -7
  334. package/dist/patterns/PageTabs.js +41 -29
  335. package/dist/patterns/PageTabs.js.map +1 -1
  336. package/dist/patterns/QueryTable.d.ts +7 -1
  337. package/dist/patterns/QueryTable.js +116 -81
  338. package/dist/patterns/QueryTable.js.map +1 -1
  339. package/dist/patterns/SlideCaptcha.d.ts +80 -0
  340. package/dist/patterns/SlideCaptcha.js +354 -0
  341. package/dist/patterns/SlideCaptcha.js.map +1 -0
  342. package/dist/patterns/StepsForm.js +7 -7
  343. package/dist/patterns/StepsForm.js.map +1 -1
  344. package/dist/patterns/ThemeSwitcher.js +3 -3
  345. package/dist/patterns/ThemeSwitcher.js.map +1 -1
  346. package/dist/patterns/index.d.ts +7 -3
  347. package/dist/patterns/index.js +5 -1
  348. package/dist/patterns/messages.d.ts +37 -1
  349. package/dist/patterns/messages.js +38 -2
  350. package/dist/patterns/messages.js.map +1 -1
  351. package/dist/table/ColumnHeaderActions.js +3 -3
  352. package/dist/table/ColumnHeaderActions.js.map +1 -1
  353. package/dist/table/ColumnResizer.js +3 -3
  354. package/dist/table/ColumnResizer.js.map +1 -1
  355. package/dist/table/ColumnSettings.js +5 -5
  356. package/dist/table/ColumnSettings.js.map +1 -1
  357. package/dist/table/ColumnTable.js +8 -8
  358. package/dist/table/ColumnTable.js.map +1 -1
  359. package/dist/table/DataTable.js +8 -8
  360. package/dist/table/DataTable.js.map +1 -1
  361. package/dist/table/DataTableView.js +21 -21
  362. package/dist/table/DataTableView.js.map +1 -1
  363. package/dist/table/PivotTable.js +3 -3
  364. package/dist/table/PivotTable.js.map +1 -1
  365. package/dist/table/SelectionBar.js +4 -4
  366. package/dist/table/SelectionBar.js.map +1 -1
  367. package/dist/table/TreeDataTable.js +5 -5
  368. package/dist/table/TreeDataTable.js.map +1 -1
  369. package/dist/theme-customize/ThemeCustomizer.js +7 -6
  370. package/dist/theme-customize/ThemeCustomizer.js.map +1 -1
  371. package/dist/theme-customize/ThemeThumbnail.js +3 -3
  372. package/dist/theme-customize/ThemeThumbnail.js.map +1 -1
  373. package/dist/three/background/AuroraBackground.js +3 -3
  374. package/dist/three/background/AuroraBackground.js.map +1 -1
  375. package/dist/three/background/MeshGradient.js +3 -3
  376. package/dist/three/background/MeshGradient.js.map +1 -1
  377. package/dist/three/background/ShaderBackground.js +3 -3
  378. package/dist/three/background/ShaderBackground.js.map +1 -1
  379. package/dist/three/core/EffectSurface.js +3 -3
  380. package/dist/three/core/EffectSurface.js.map +1 -1
  381. package/dist/three/core/ThreeCanvas.js +4 -4
  382. package/dist/three/core/ThreeCanvas.js.map +1 -1
  383. package/dist/three/effects/Confetti.js +3 -3
  384. package/dist/three/effects/Confetti.js.map +1 -1
  385. package/dist/three/effects/ParticleField.js +3 -3
  386. package/dist/three/effects/ParticleField.js.map +1 -1
  387. package/dist/three/effects/WaveGrid.js +3 -3
  388. package/dist/three/effects/WaveGrid.js.map +1 -1
  389. package/dist/three/globe/Globe.js +3 -3
  390. package/dist/three/globe/Globe.js.map +1 -1
  391. package/dist/three/model/ModelViewer.js +3 -3
  392. package/dist/three/model/ModelViewer.js.map +1 -1
  393. package/dist/utils/data/number.d.ts +5 -0
  394. package/dist/utils/data/number.js +8 -1
  395. package/dist/utils/data/number.js.map +1 -1
  396. package/dist/utils/dom/element.d.ts +3 -1
  397. package/dist/utils/dom/element.js +8 -4
  398. package/dist/utils/dom/element.js.map +1 -1
  399. package/dist/utils/dom/fullscreen.d.ts +1 -1
  400. package/dist/utils/dom/fullscreen.js +12 -8
  401. package/dist/utils/dom/fullscreen.js.map +1 -1
  402. package/dist/utils/dom/mounted.d.ts +7 -0
  403. package/dist/utils/dom/mounted.js +16 -0
  404. package/dist/utils/dom/mounted.js.map +1 -0
  405. package/dist/utils/dom/overflow.js +2 -1
  406. package/dist/utils/dom/overflow.js.map +1 -1
  407. package/dist/utils/dom/pan-zoom.js +2 -1
  408. package/dist/utils/dom/pan-zoom.js.map +1 -1
  409. package/dist/utils/props.js +3 -13
  410. package/dist/utils/props.js.map +1 -1
  411. package/dist/utils/request/http.d.ts +12 -0
  412. package/dist/utils/request/http.js +6 -2
  413. package/dist/utils/request/http.js.map +1 -1
  414. package/dist/utils/request/use-request.js +4 -2
  415. package/dist/utils/request/use-request.js.map +1 -1
  416. package/package.json +2 -2
@@ -1 +1 @@
1
- {"version":3,"file":"ModelViewer.js","names":["_vtp_readonlyProp","_vtp_restProps","_vue_ref","_vtp_untrack","_createVNode","_mergeProps"],"sources":["../../../src/three/model/ModelViewer.tsx"],"sourcesContent":["/**\n * 模型查看器(div):加载 glTF / GLB(或直接给一个 three 的对象),自动居中、缩放到合适的大小,\n * 拖动环绕查看(松手带惯性),方向键也能转;没在拖时缓缓自转。参考 Google 的 <model-viewer>。\n *\n * <ModelViewer src=\"/models/chair.glb\" alt=\"扶手椅,胡桃木与灰色布面\" />\n * <ModelViewer object={mesh} rotationSpeed={0} zoom sx={styles.viewer} />\n *\n * 光照是一间柔和的摄影棚(RoomEnvironment 的环境光),金属、粗糙度材质都能正确地反光;exposure 调整体亮度。\n * 加载时中间显示转圈,失败时显示语言包里的提示(onError 拿到原因)。写了 alt 时整块是带名称的图像(role=\"img\")。\n * 默认正方形,尺寸由使用方决定。减少动态效果时不自转,拖动仍可用。\n *\n * 部件:Root(div)、Canvas(ThreeCanvas)、Content(加载中与出错的提示)。\n */\nimport { _component } from '@zerodep-dev/vue-tsx-pro-tsx';\nimport * as css from '@zerodep-dev/vue-tsx-pro-tsx/styles';\nimport {\n ACESFilmicToneMapping,\n Box3,\n Group,\n PerspectiveCamera,\n PMREMGenerator,\n Sphere,\n Vector3,\n type Object3D,\n} from 'three';\nimport { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';\nimport { RoomEnvironment } from 'three/addons/environments/RoomEnvironment.js';\nimport { Small } from '../../base/Text';\nimport { Spinner } from '../../base/Spinner';\nimport { useLang } from '../../config/context';\nimport { EffectSurface, type EffectProps } from '../core/EffectSurface';\nimport { createOrbit } from '../core/orbit';\nimport { mergePartProps, type PartProps } from '../../utils/props';\n\n/** 加载状态 */\nexport interface ModelViewerStatuses {\n /** 加载中 */\n loading: unknown;\n /** 已显示 */\n loaded: unknown;\n /** 加载失败 */\n error: unknown;\n}\n\n/** 未声明的属性落到外层 div 上 */\nexport interface ModelViewerProps extends Omit<EffectProps, 'children' | 'onLoad' | 'onError'> {\n /** 模型的地址(glTF / GLB);和 object 二选一 */\n src?: string;\n /** 直接给一个 three 的对象(程序生成的模型);和 src 二选一 */\n object?: Object3D;\n /** 模型的文字描述:写了时整块是 role=\"img\"、以它为名称 */\n alt?: string;\n /**\n * 自转速度(度 / 秒);0 时不自转\n * @default 20\n */\n rotationSpeed?: number;\n /**\n * 可以拖动、用方向键环绕查看\n * @default true\n */\n controls?: boolean;\n /**\n * 滚轮、+ / - 键缩放\n * @default false\n */\n zoom?: boolean;\n /**\n * 一开始的角度(度):yaw 绕竖轴,pitch 从上往下看为正\n * @default { yaw: 30, pitch: 15 }\n */\n orbit?: { yaw: number; pitch: number };\n /**\n * 曝光:整体亮度\n * @default 1\n */\n exposure?: number;\n /** 加载完成 */\n onLoad?: (model: Object3D) => void;\n /** 加载失败 */\n onError?: (error: unknown) => void;\n}\n\n/** ModelViewer 的样式:root、interactive、dragging、content、overlay */\nexport const modelViewerStyles = css.create({\n root: { aspectRatio: '1', width: '100%', outlineStyle: 'none' },\n interactive: { cursor: 'grab', touchAction: 'pan-y' },\n dragging: { cursor: 'grabbing' },\n // 内容层不定位:提示按外层(整块)铺满、居中\n content: { position: 'static' },\n // 加载中、出错的提示:盖在画布中间\n overlay: {\n position: 'absolute',\n inset: 0,\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n pointerEvents: 'none',\n },\n});\n\nconst DEG = Math.PI / 180;\nconst FOV = 35;\n/** 模型(外接球)占画面短边的比例 */\nconst FIT = 0.85;\n/** 缩放的范围:相对合适距离的倍数 */\nconst ZOOM_RANGE = [0.5, 2.5] as const;\n\n/** 模型查看器 */\nexport const ModelViewer = _component(\n ({\n src,\n object,\n alt,\n rotationSpeed = 20,\n controls = true,\n zoom = false,\n orbit: initial = { yaw: 30, pitch: 15 },\n exposure = 1,\n onLoad,\n onError,\n ...rest\n }: ModelViewerProps) => {\n const lang = useLang();\n let status = _state<keyof ModelViewerStatuses>('loading');\n const start = _untrack(initial);\n const orbit = createOrbit({ yaw: start.yaw * DEG, pitch: start.pitch * DEG, pitchLimit: 1.4 });\n let distance = 1;\n // 场景建好后换成真正的 invalidate:静止模式下交互也要重画\n let redraw = () => {};\n\n function onKeydown(event: KeyboardEvent) {\n if (!controls) return;\n const step = 15 * DEG;\n const moves: Record<string, [number, number]> = {\n ArrowLeft: [-step, 0],\n ArrowRight: [step, 0],\n ArrowUp: [0, -step],\n ArrowDown: [0, step],\n };\n const move = moves[event.key];\n if (move) orbit.nudge(...move);\n else if (zoom && (event.key === '+' || event.key === '='))\n distance = Math.max(ZOOM_RANGE[0], distance / 1.15);\n else if (zoom && event.key === '-') distance = Math.min(ZOOM_RANGE[1], distance * 1.15);\n else return;\n event.preventDefault();\n redraw();\n }\n\n function onWheel(event: WheelEvent) {\n if (!zoom) return;\n event.preventDefault();\n distance = Math.min(\n ZOOM_RANGE[1],\n Math.max(ZOOM_RANGE[0], distance * Math.exp(event.deltaY * 0.001)),\n );\n redraw();\n }\n\n return () => (\n <EffectSurface\n {...rest}\n sx={[\n modelViewerStyles.root,\n controls && modelViewerStyles.interactive,\n orbit.dragging && modelViewerStyles.dragging,\n rest.sx,\n ]}\n role={alt ? 'img' : undefined}\n aria-label={alt}\n aria-busy={status === 'loading' || undefined}\n tabindex={controls ? 0 : undefined}\n data-status={status}\n {...(controls ? orbit.handlers : {})}\n onKeydown={onKeydown}\n onWheel={onWheel}\n propsContent={mergePartProps<PartProps<'div'>>(\n { sx: modelViewerStyles.content },\n rest.propsContent,\n )}\n sceneKey={src ?? object?.uuid}\n setup={({ renderer, scene, still, invalidate }) => {\n redraw = invalidate;\n renderer.toneMapping = ACESFilmicToneMapping;\n renderer.toneMappingExposure = exposure;\n const environment = new PMREMGenerator(renderer);\n scene.environment = environment.fromScene(new RoomEnvironment(), 0.04).texture;\n\n const camera = new PerspectiveCamera(FOV, 1, 0.01, 100);\n // 合适的距离:外接球(半径 1)正好占短边的 FIT\n let fit = 1 / Math.sin((FOV * DEG) / 2) / FIT;\n const holder = new Group();\n scene.add(holder);\n let disposed = false;\n\n status = 'loading';\n const source = object\n ? Promise.resolve(object)\n : new GLTFLoader().loadAsync(src ?? '').then((gltf) => gltf.scene);\n source.then(\n (model) => {\n if (disposed) return;\n // 居中,缩放到外接球半径为 1\n const sphere = new Box3().setFromObject(model).getBoundingSphere(new Sphere());\n const scale = sphere.radius > 0 ? 1 / sphere.radius : 1;\n holder.scale.setScalar(scale);\n holder.position.copy(sphere.center).multiplyScalar(-scale);\n holder.add(model);\n status = 'loaded';\n onLoad?.(model);\n invalidate();\n },\n (error: unknown) => {\n if (disposed) return;\n status = 'error';\n onError?.(error);\n },\n );\n\n const eye = new Vector3();\n const place = () => {\n const d = fit * distance;\n eye.set(\n Math.sin(orbit.yaw) * Math.cos(orbit.pitch),\n Math.sin(orbit.pitch),\n Math.cos(orbit.yaw) * Math.cos(orbit.pitch),\n );\n camera.position.copy(eye.multiplyScalar(d));\n camera.lookAt(0, 0, 0);\n };\n\n return {\n resize: (size, context) => {\n camera.aspect = size.width / Math.max(size.height, 1);\n fit = 1 / Math.sin((FOV * DEG) / 2) / FIT / Math.min(1, camera.aspect);\n camera.updateProjectionMatrix();\n context.camera = camera;\n place();\n },\n frame: ({ delta }) => {\n // 自转是相机绕模型反向转:看起来模型朝 rotationSpeed 的方向转\n orbit.step(delta, still ? 0 : -rotationSpeed * DEG);\n place();\n },\n dispose: () => {\n disposed = true;\n scene.environment?.dispose();\n environment.dispose();\n redraw = () => {};\n },\n };\n }}\n >\n {status !== 'loaded' && (\n <div {...css.attrs(modelViewerStyles.overlay)}>\n {status === 'loading' ? (\n <Spinner label={lang.modelViewerLoading} />\n ) : (\n <Small tone=\"muted\">{lang.modelViewerError}</Small>\n )}\n </div>\n )}\n </EffectSurface>\n );\n },\n);\n"],"mappings":"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"}
1
+ {"version":3,"file":"ModelViewer.js","names":["_vtp_readonlyProp","_vtp_restProps","_vue_ref","_vtp_untrack","_createVNode","_mergeProps"],"sources":["../../../src/three/model/ModelViewer.tsx"],"sourcesContent":["/**\n * 模型查看器(div):加载 glTF / GLB(或直接给一个 three 的对象),自动居中、缩放到合适的大小,\n * 拖动环绕查看(松手带惯性),方向键也能转;没在拖时缓缓自转。参考 Google 的 <model-viewer>。\n *\n * <ModelViewer src=\"/models/chair.glb\" alt=\"扶手椅,胡桃木与灰色布面\" />\n * <ModelViewer object={mesh} rotationSpeed={0} zoom sx={styles.viewer} />\n *\n * 光照是一间柔和的摄影棚(RoomEnvironment 的环境光),金属、粗糙度材质都能正确地反光;exposure 调整体亮度。\n * 加载时中间显示转圈,失败时显示语言包里的提示(onError 拿到原因)。写了 alt 时整块是带名称的图像(role=\"img\")。\n * 默认正方形,尺寸由使用方决定。减少动态效果时不自转,拖动仍可用。\n *\n * 部件:Root(div)、Canvas(ThreeCanvas)、Content(加载中与出错的提示)。\n */\nimport { _component } from '@zerodep-dev/vue-tsx-pro-tsx';\nimport * as css from '@zerodep-dev/vue-tsx-pro-tsx/styles';\nimport {\n ACESFilmicToneMapping,\n Box3,\n Group,\n PerspectiveCamera,\n PMREMGenerator,\n Sphere,\n Vector3,\n type Object3D,\n} from 'three';\nimport { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';\nimport { RoomEnvironment } from 'three/addons/environments/RoomEnvironment.js';\nimport { Small } from '../../base/Text';\nimport { Spinner } from '../../base/Spinner';\nimport { useLang } from '../../config/context';\nimport { EffectSurface, type EffectProps } from '../core/EffectSurface';\nimport { createOrbit } from '../core/orbit';\nimport { mergePartProps, type PartProps } from '../../utils/props';\n\n/** 加载状态 */\nexport interface ModelViewerStatuses {\n /** 加载中 */\n loading: unknown;\n /** 已显示 */\n loaded: unknown;\n /** 加载失败 */\n error: unknown;\n}\n\n/** 未声明的属性落到外层 div 上 */\nexport interface ModelViewerProps extends Omit<EffectProps, 'children' | 'onLoad' | 'onError'> {\n /** 模型的地址(glTF / GLB);和 object 二选一 */\n src?: string;\n /** 直接给一个 three 的对象(程序生成的模型);和 src 二选一 */\n object?: Object3D;\n /** 模型的文字描述:写了时整块是 role=\"img\"、以它为名称 */\n alt?: string;\n /**\n * 自转速度(度 / 秒);0 时不自转\n * @default 20\n */\n rotationSpeed?: number;\n /**\n * 可以拖动、用方向键环绕查看\n * @default true\n */\n controls?: boolean;\n /**\n * 滚轮、+ / - 键缩放\n * @default false\n */\n zoom?: boolean;\n /**\n * 一开始的角度(度):yaw 绕竖轴,pitch 从上往下看为正\n * @default { yaw: 30, pitch: 15 }\n */\n orbit?: { yaw: number; pitch: number };\n /**\n * 曝光:整体亮度\n * @default 1\n */\n exposure?: number;\n /** 加载完成 */\n onLoad?: (model: Object3D) => void;\n /** 加载失败 */\n onError?: (error: unknown) => void;\n}\n\n/** ModelViewer 的样式:root、interactive、dragging、content、overlay */\nexport const modelViewerStyles = css.create({\n root: { aspectRatio: '1', width: '100%', outlineStyle: 'none' },\n interactive: { cursor: 'grab', touchAction: 'pan-y' },\n dragging: { cursor: 'grabbing' },\n // 内容层不定位:提示按外层(整块)铺满、居中\n content: { position: 'static' },\n // 加载中、出错的提示:盖在画布中间\n overlay: {\n position: 'absolute',\n inset: 0,\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n pointerEvents: 'none',\n },\n});\n\nconst DEG = Math.PI / 180;\nconst FOV = 35;\n/** 模型(外接球)占画面短边的比例 */\nconst FIT = 0.85;\n/** 缩放的范围:相对合适距离的倍数 */\nconst ZOOM_RANGE = [0.5, 2.5] as const;\n\n/** 模型查看器 */\nexport const ModelViewer = _component(\n ({\n src,\n object,\n alt,\n rotationSpeed = 20,\n controls = true,\n zoom = false,\n orbit: initial = { yaw: 30, pitch: 15 },\n exposure = 1,\n onLoad,\n onError,\n ...rest\n }: ModelViewerProps) => {\n const lang = useLang();\n let status = _state<keyof ModelViewerStatuses>('loading');\n const start = _untrack(initial);\n const orbit = createOrbit({ yaw: start.yaw * DEG, pitch: start.pitch * DEG, pitchLimit: 1.4 });\n let distance = 1;\n // 场景建好后换成真正的 invalidate:静止模式下交互也要重画\n let redraw = () => {};\n\n function onKeydown(event: KeyboardEvent) {\n if (!controls) return;\n const step = 15 * DEG;\n const moves: Record<string, [number, number]> = {\n ArrowLeft: [-step, 0],\n ArrowRight: [step, 0],\n ArrowUp: [0, -step],\n ArrowDown: [0, step],\n };\n const move = moves[event.key];\n if (move) orbit.nudge(...move);\n else if (zoom && (event.key === '+' || event.key === '='))\n distance = Math.max(ZOOM_RANGE[0], distance / 1.15);\n else if (zoom && event.key === '-') distance = Math.min(ZOOM_RANGE[1], distance * 1.15);\n else return;\n event.preventDefault();\n redraw();\n }\n\n function onWheel(event: WheelEvent) {\n if (!zoom) return;\n event.preventDefault();\n distance = Math.min(\n ZOOM_RANGE[1],\n Math.max(ZOOM_RANGE[0], distance * Math.exp(event.deltaY * 0.001)),\n );\n redraw();\n }\n\n return () => (\n <EffectSurface\n {...rest}\n sx={[\n modelViewerStyles.root,\n controls && modelViewerStyles.interactive,\n orbit.dragging && modelViewerStyles.dragging,\n rest.sx,\n ]}\n role={alt ? 'img' : undefined}\n aria-label={alt}\n aria-busy={status === 'loading' || undefined}\n tabindex={controls ? 0 : undefined}\n data-status={status}\n {...(controls ? orbit.handlers : {})}\n onKeydown={onKeydown}\n onWheel={onWheel}\n propsContent={mergePartProps<PartProps<'div'>>(\n { sx: modelViewerStyles.content },\n rest.propsContent,\n )}\n sceneKey={src ?? object?.uuid}\n setup={({ renderer, scene, still, invalidate }) => {\n redraw = invalidate;\n renderer.toneMapping = ACESFilmicToneMapping;\n renderer.toneMappingExposure = exposure;\n const environment = new PMREMGenerator(renderer);\n scene.environment = environment.fromScene(new RoomEnvironment(), 0.04).texture;\n\n const camera = new PerspectiveCamera(FOV, 1, 0.01, 100);\n // 合适的距离:外接球(半径 1)正好占短边的 FIT\n let fit = 1 / Math.sin((FOV * DEG) / 2) / FIT;\n const holder = new Group();\n scene.add(holder);\n let disposed = false;\n\n status = 'loading';\n const source = object\n ? Promise.resolve(object)\n : new GLTFLoader().loadAsync(src ?? '').then((gltf) => gltf.scene);\n source.then(\n (model) => {\n if (disposed) return;\n // 居中,缩放到外接球半径为 1\n const sphere = new Box3().setFromObject(model).getBoundingSphere(new Sphere());\n const scale = sphere.radius > 0 ? 1 / sphere.radius : 1;\n holder.scale.setScalar(scale);\n holder.position.copy(sphere.center).multiplyScalar(-scale);\n holder.add(model);\n status = 'loaded';\n onLoad?.(model);\n invalidate();\n },\n (error: unknown) => {\n if (disposed) return;\n status = 'error';\n onError?.(error);\n },\n );\n\n const eye = new Vector3();\n const place = () => {\n const d = fit * distance;\n eye.set(\n Math.sin(orbit.yaw) * Math.cos(orbit.pitch),\n Math.sin(orbit.pitch),\n Math.cos(orbit.yaw) * Math.cos(orbit.pitch),\n );\n camera.position.copy(eye.multiplyScalar(d));\n camera.lookAt(0, 0, 0);\n };\n\n return {\n resize: (size, context) => {\n camera.aspect = size.width / Math.max(size.height, 1);\n fit = 1 / Math.sin((FOV * DEG) / 2) / FIT / Math.min(1, camera.aspect);\n camera.updateProjectionMatrix();\n context.camera = camera;\n place();\n },\n frame: ({ delta }) => {\n // 自转是相机绕模型反向转:看起来模型朝 rotationSpeed 的方向转\n orbit.step(delta, still ? 0 : -rotationSpeed * DEG);\n place();\n },\n dispose: () => {\n disposed = true;\n scene.environment?.dispose();\n environment.dispose();\n redraw = () => {};\n },\n };\n }}\n >\n {status !== 'loaded' && (\n <div {...css.attrs(modelViewerStyles.overlay)}>\n {status === 'loading' ? (\n <Spinner label={lang.modelViewerLoading} />\n ) : (\n <Small tone=\"muted\">{lang.modelViewerError}</Small>\n )}\n </div>\n )}\n </EffectSurface>\n );\n },\n);\n"],"mappings":"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"}
@@ -8,3 +8,8 @@ export declare function clamp(value: number, min: number, max: number): number;
8
8
  * 结果按 step 与 min 的小数位取整,不带进 0.1 + 0.2 这类浮点误差。Slider、NumberField 共用
9
9
  */
10
10
  export declare function snapToStep(value: number, step: number, min?: number, max?: number): number;
11
+ /**
12
+ * SVG 里的坐标保留三位小数:三角函数在不同的 JS 引擎、平台上末几位可能不同,服务端渲染与浏览器算出的路径就对不上(水合不一致);
13
+ * 千分之一个单位看不出差别,HTML 也短一些
14
+ */
15
+ export declare function svgNumber(value: number): number;
@@ -22,7 +22,14 @@ function snapToStep(value, step, min = -Infinity, max = Infinity) {
22
22
  const places = Math.max(decimals(step), Number.isFinite(min) ? decimals(min) : 0);
23
23
  return Number(clamp(stepped, min, max).toFixed(places));
24
24
  }
25
+ /**
26
+ * SVG 里的坐标保留三位小数:三角函数在不同的 JS 引擎、平台上末几位可能不同,服务端渲染与浏览器算出的路径就对不上(水合不一致);
27
+ * 千分之一个单位看不出差别,HTML 也短一些
28
+ */
29
+ function svgNumber(value) {
30
+ return Math.round(value * 1e3) / 1e3;
31
+ }
25
32
  //#endregion
26
- export { clamp, snapToStep };
33
+ export { clamp, snapToStep, svgNumber };
27
34
 
28
35
  //# sourceMappingURL=number.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"number.js","names":[],"sources":["../../../src/utils/data/number.ts"],"sourcesContent":["/**\n * 数值的小工具:取值夹在范围内、对齐到步长(滑块、数字输入、分页、虚拟滚动共用)。\n */\n\n/** 把 value 夹在 [min, max] 之间 */\nexport function clamp(value: number, min: number, max: number): number {\n return Math.min(max, Math.max(min, value));\n}\n\n/** 小数位数:0.1 → 1,1e-7 → 7(String(1e-7) 是科学计数法,不能按小数点拆) */\nfunction decimals(value: number): number {\n if (!Number.isFinite(value)) return 0;\n const [mantissa = '', exponent = '0'] = value.toExponential().split('e');\n return Math.max(0, (mantissa.split('.')[1] ?? '').length - Number(exponent));\n}\n\n/**\n * 夹在 [min, max] 之间并对齐到 min + n × step(没有 min 时对齐到 0 + n × step);\n * 结果按 step 与 min 的小数位取整,不带进 0.1 + 0.2 这类浮点误差。Slider、NumberField 共用\n */\nexport function snapToStep(value: number, step: number, min = -Infinity, max = Infinity): number {\n const base = Number.isFinite(min) ? min : 0;\n const stepped = base + Math.round((value - base) / step) * step;\n const places = Math.max(decimals(step), Number.isFinite(min) ? decimals(min) : 0);\n return Number(clamp(stepped, min, max).toFixed(places));\n}\n"],"mappings":";;;;;AAKA,SAAgB,MAAM,OAAe,KAAa,KAAqB;CACrE,OAAO,KAAK,IAAI,KAAK,KAAK,IAAI,KAAK,KAAK,CAAC;AAC3C;;AAGA,SAAS,SAAS,OAAuB;CACvC,IAAI,CAAC,OAAO,SAAS,KAAK,GAAG,OAAO;CACpC,MAAM,CAAC,WAAW,IAAI,WAAW,OAAO,MAAM,cAAc,CAAC,CAAC,MAAM,GAAG;CACvE,OAAO,KAAK,IAAI,IAAI,SAAS,MAAM,GAAG,CAAC,CAAC,MAAM,GAAA,CAAI,SAAS,OAAO,QAAQ,CAAC;AAC7E;;;;;AAMA,SAAgB,WAAW,OAAe,MAAc,MAAM,WAAW,MAAM,UAAkB;CAC/F,MAAM,OAAO,OAAO,SAAS,GAAG,IAAI,MAAM;CAC1C,MAAM,UAAU,OAAO,KAAK,OAAO,QAAQ,QAAQ,IAAI,IAAI;CAC3D,MAAM,SAAS,KAAK,IAAI,SAAS,IAAI,GAAG,OAAO,SAAS,GAAG,IAAI,SAAS,GAAG,IAAI,CAAC;CAChF,OAAO,OAAO,MAAM,SAAS,KAAK,GAAG,CAAC,CAAC,QAAQ,MAAM,CAAC;AACxD"}
1
+ {"version":3,"file":"number.js","names":[],"sources":["../../../src/utils/data/number.ts"],"sourcesContent":["/**\n * 数值的小工具:取值夹在范围内、对齐到步长(滑块、数字输入、分页、虚拟滚动共用)。\n */\n\n/** 把 value 夹在 [min, max] 之间 */\nexport function clamp(value: number, min: number, max: number): number {\n return Math.min(max, Math.max(min, value));\n}\n\n/** 小数位数:0.1 → 1,1e-7 → 7(String(1e-7) 是科学计数法,不能按小数点拆) */\nfunction decimals(value: number): number {\n if (!Number.isFinite(value)) return 0;\n const [mantissa = '', exponent = '0'] = value.toExponential().split('e');\n return Math.max(0, (mantissa.split('.')[1] ?? '').length - Number(exponent));\n}\n\n/**\n * 夹在 [min, max] 之间并对齐到 min + n × step(没有 min 时对齐到 0 + n × step);\n * 结果按 step 与 min 的小数位取整,不带进 0.1 + 0.2 这类浮点误差。Slider、NumberField 共用\n */\nexport function snapToStep(value: number, step: number, min = -Infinity, max = Infinity): number {\n const base = Number.isFinite(min) ? min : 0;\n const stepped = base + Math.round((value - base) / step) * step;\n const places = Math.max(decimals(step), Number.isFinite(min) ? decimals(min) : 0);\n return Number(clamp(stepped, min, max).toFixed(places));\n}\n\n/**\n * SVG 里的坐标保留三位小数:三角函数在不同的 JS 引擎、平台上末几位可能不同,服务端渲染与浏览器算出的路径就对不上(水合不一致);\n * 千分之一个单位看不出差别,HTML 也短一些\n */\nexport function svgNumber(value: number): number {\n return Math.round(value * 1000) / 1000;\n}\n"],"mappings":";;;;;AAKA,SAAgB,MAAM,OAAe,KAAa,KAAqB;CACrE,OAAO,KAAK,IAAI,KAAK,KAAK,IAAI,KAAK,KAAK,CAAC;AAC3C;;AAGA,SAAS,SAAS,OAAuB;CACvC,IAAI,CAAC,OAAO,SAAS,KAAK,GAAG,OAAO;CACpC,MAAM,CAAC,WAAW,IAAI,WAAW,OAAO,MAAM,cAAc,CAAC,CAAC,MAAM,GAAG;CACvE,OAAO,KAAK,IAAI,IAAI,SAAS,MAAM,GAAG,CAAC,CAAC,MAAM,GAAA,CAAI,SAAS,OAAO,QAAQ,CAAC;AAC7E;;;;;AAMA,SAAgB,WAAW,OAAe,MAAc,MAAM,WAAW,MAAM,UAAkB;CAC/F,MAAM,OAAO,OAAO,SAAS,GAAG,IAAI,MAAM;CAC1C,MAAM,UAAU,OAAO,KAAK,OAAO,QAAQ,QAAQ,IAAI,IAAI;CAC3D,MAAM,SAAS,KAAK,IAAI,SAAS,IAAI,GAAG,OAAO,SAAS,GAAG,IAAI,SAAS,GAAG,IAAI,CAAC;CAChF,OAAO,OAAO,MAAM,SAAS,KAAK,GAAG,CAAC,CAAC,QAAQ,MAAM,CAAC;AACxD;;;;;AAMA,SAAgB,UAAU,OAAuB;CAC/C,OAAO,KAAK,MAAM,QAAQ,GAAI,IAAI;AACpC"}
@@ -1,3 +1,5 @@
1
- /** 模板 ref 的取值换成 DOM 元素 */
1
+ /** 模板 ref 的取值换成 DOM 元素;判断是不是元素(服务端渲染时没有 HTMLElement 这个全局,也能安全调用) */
2
+ /** 是不是 HTML 元素;服务端渲染时总是 false(直接写 `instanceof HTMLElement` 在服务端会抛 ReferenceError) */
3
+ export declare function isHTMLElement(value: unknown): value is HTMLElement;
2
4
  /** ref 落在组件上拿到的是组件实例,取它的根元素;本来就是元素时原样返回 */
3
5
  export declare function toElement(value: unknown): HTMLElement | null;
@@ -1,12 +1,16 @@
1
1
  //#region src/utils/dom/element.ts
2
- /** 模板 ref 的取值换成 DOM 元素 */
2
+ /** 模板 ref 的取值换成 DOM 元素;判断是不是元素(服务端渲染时没有 HTMLElement 这个全局,也能安全调用) */
3
+ /** 是不是 HTML 元素;服务端渲染时总是 false(直接写 `instanceof HTMLElement` 在服务端会抛 ReferenceError) */
4
+ function isHTMLElement(value) {
5
+ return typeof HTMLElement !== "undefined" && value instanceof HTMLElement;
6
+ }
3
7
  /** ref 落在组件上拿到的是组件实例,取它的根元素;本来就是元素时原样返回 */
4
8
  function toElement(value) {
5
- if (value instanceof HTMLElement) return value;
9
+ if (isHTMLElement(value)) return value;
6
10
  const root = value?.$el;
7
- return root instanceof HTMLElement ? root : null;
11
+ return isHTMLElement(root) ? root : null;
8
12
  }
9
13
  //#endregion
10
- export { toElement };
14
+ export { isHTMLElement, toElement };
11
15
 
12
16
  //# sourceMappingURL=element.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"element.js","names":[],"sources":["../../../src/utils/dom/element.ts"],"sourcesContent":["/** 模板 ref 的取值换成 DOM 元素 */\n\n/** ref 落在组件上拿到的是组件实例,取它的根元素;本来就是元素时原样返回 */\nexport function toElement(value: unknown): HTMLElement | null {\n if (value instanceof HTMLElement) return value;\n const root = (value as { $el?: unknown } | null)?.$el;\n return root instanceof HTMLElement ? root : null;\n}\n"],"mappings":";;;AAGA,SAAgB,UAAU,OAAoC;CAC5D,IAAI,iBAAiB,aAAa,OAAO;CACzC,MAAM,OAAQ,OAAoC;CAClD,OAAO,gBAAgB,cAAc,OAAO;AAC9C"}
1
+ {"version":3,"file":"element.js","names":[],"sources":["../../../src/utils/dom/element.ts"],"sourcesContent":["/** 模板 ref 的取值换成 DOM 元素;判断是不是元素(服务端渲染时没有 HTMLElement 这个全局,也能安全调用) */\n\n/** 是不是 HTML 元素;服务端渲染时总是 false(直接写 `instanceof HTMLElement` 在服务端会抛 ReferenceError) */\nexport function isHTMLElement(value: unknown): value is HTMLElement {\n return typeof HTMLElement !== 'undefined' && value instanceof HTMLElement;\n}\n\n/** ref 落在组件上拿到的是组件实例,取它的根元素;本来就是元素时原样返回 */\nexport function toElement(value: unknown): HTMLElement | null {\n if (isHTMLElement(value)) return value;\n const root = (value as { $el?: unknown } | null)?.$el;\n return isHTMLElement(root) ? root : null;\n}\n"],"mappings":";;;AAGA,SAAgB,cAAc,OAAsC;CAClE,OAAO,OAAO,gBAAgB,eAAe,iBAAiB;AAChE;;AAGA,SAAgB,UAAU,OAAoC;CAC5D,IAAI,cAAc,KAAK,GAAG,OAAO;CACjC,MAAM,OAAQ,OAAoC;CAClD,OAAO,cAAc,IAAI,IAAI,OAAO;AACtC"}
@@ -2,7 +2,7 @@
2
2
  export interface Fullscreen {
3
3
  /** 这个元素正在全屏 */
4
4
  readonly active: boolean;
5
- /** 浏览器支持全屏 */
5
+ /** 浏览器支持全屏(挂载后才判断) */
6
6
  readonly supported: boolean;
7
7
  /** 进入或退出全屏 */
8
8
  toggle(): Promise<void>;
@@ -1,33 +1,37 @@
1
- import { onScopeDispose, shallowRef } from "vue";
1
+ import { onBeforeUnmount, onMounted, shallowRef } from "vue";
2
2
  //#region src/utils/dom/fullscreen.ts
3
3
  /**
4
- * 让一个元素全屏(Fullscreen API):表格、看板、图表的“全屏”按钮。按 Esc 或浏览器的退出全屏时 active 跟着变。在 setup 里调用。
4
+ * 让一个元素全屏(Fullscreen API):表格、看板、图表的“全屏”按钮。按 Esc 或浏览器的退出全屏时 active 跟着变。在组件的 setup 里调用。
5
5
  *
6
6
  * const fullscreen = useFullscreen(() => panel);
7
7
  * <IconButton icon={fullscreen.active ? Minimize : Maximize} onClick={fullscreen.toggle} />
8
8
  *
9
9
  * 浏览器不支持(iOS Safari 的非视频元素)或被拒绝时什么也不做,supported 为 false 时可以不显示按钮。
10
+ * 挂载前 supported 总是 false:服务端渲染与水合时都不出按钮,两边一致。
10
11
  */
11
12
  /** 元素的全屏状态与切换 */
12
13
  function useFullscreen(target) {
13
14
  const active = shallowRef(false);
14
- const supported = typeof document !== "undefined" && document.fullscreenEnabled === true;
15
+ const supported = shallowRef(false);
15
16
  const sync = () => {
16
17
  const element = target();
17
18
  active.value = !!element && document.fullscreenElement === element;
18
19
  };
19
- if (typeof document !== "undefined") {
20
+ onMounted(() => {
21
+ supported.value = document.fullscreenEnabled === true;
20
22
  document.addEventListener("fullscreenchange", sync);
21
- onScopeDispose(() => document.removeEventListener("fullscreenchange", sync));
22
- }
23
+ });
24
+ onBeforeUnmount(() => document.removeEventListener("fullscreenchange", sync));
23
25
  return {
24
26
  get active() {
25
27
  return active.value;
26
28
  },
27
- supported,
29
+ get supported() {
30
+ return supported.value;
31
+ },
28
32
  async toggle() {
29
33
  const element = target();
30
- if (!supported || !element) return;
34
+ if (!supported.value || !element) return;
31
35
  try {
32
36
  if (document.fullscreenElement === element) await document.exitFullscreen();
33
37
  else await element.requestFullscreen();
@@ -1 +1 @@
1
- {"version":3,"file":"fullscreen.js","names":[],"sources":["../../../src/utils/dom/fullscreen.ts"],"sourcesContent":["/**\n * 让一个元素全屏(Fullscreen API):表格、看板、图表的“全屏”按钮。按 Esc 或浏览器的退出全屏时 active 跟着变。在 setup 里调用。\n *\n * const fullscreen = useFullscreen(() => panel);\n * <IconButton icon={fullscreen.active ? Minimize : Maximize} onClick={fullscreen.toggle} />\n *\n * 浏览器不支持(iOS Safari 的非视频元素)或被拒绝时什么也不做,supported 为 false 时可以不显示按钮。\n */\nimport { onScopeDispose, shallowRef } from 'vue';\n\n/** useFullscreen 的返回值 */\nexport interface Fullscreen {\n /** 这个元素正在全屏 */\n readonly active: boolean;\n /** 浏览器支持全屏 */\n readonly supported: boolean;\n /** 进入或退出全屏 */\n toggle(): Promise<void>;\n}\n\n/** 元素的全屏状态与切换 */\nexport function useFullscreen(target: () => HTMLElement | null | undefined): Fullscreen {\n const active = shallowRef(false);\n const supported = typeof document !== 'undefined' && document.fullscreenEnabled === true;\n\n const sync = () => {\n const element = target();\n active.value = !!element && document.fullscreenElement === element;\n };\n if (typeof document !== 'undefined') {\n document.addEventListener('fullscreenchange', sync);\n onScopeDispose(() => document.removeEventListener('fullscreenchange', sync));\n }\n\n return {\n get active() {\n return active.value;\n },\n supported,\n async toggle() {\n const element = target();\n if (!supported || !element) return;\n try {\n if (document.fullscreenElement === element) await document.exitFullscreen();\n else await element.requestFullscreen();\n } catch (error) {\n // 被浏览器拒绝(不是用户手势触发、权限策略):保持原样,留下记录\n console.warn('全屏切换被浏览器拒绝', error);\n }\n },\n };\n}\n"],"mappings":";;;;;;;;;;;AAqBA,SAAgB,cAAc,QAA0D;CACtF,MAAM,SAAS,WAAW,KAAK;CAC/B,MAAM,YAAY,OAAO,aAAa,eAAe,SAAS,sBAAsB;CAEpF,MAAM,aAAa;EACjB,MAAM,UAAU,OAAO;EACvB,OAAO,QAAQ,CAAC,CAAC,WAAW,SAAS,sBAAsB;CAC7D;CACA,IAAI,OAAO,aAAa,aAAa;EACnC,SAAS,iBAAiB,oBAAoB,IAAI;EAClD,qBAAqB,SAAS,oBAAoB,oBAAoB,IAAI,CAAC;CAC7E;CAEA,OAAO;EACL,IAAI,SAAS;GACX,OAAO,OAAO;EAChB;EACA;EACA,MAAM,SAAS;GACb,MAAM,UAAU,OAAO;GACvB,IAAI,CAAC,aAAa,CAAC,SAAS;GAC5B,IAAI;IACF,IAAI,SAAS,sBAAsB,SAAS,MAAM,SAAS,eAAe;SACrE,MAAM,QAAQ,kBAAkB;GACvC,SAAS,OAAO;IAEd,QAAQ,KAAK,cAAc,KAAK;GAClC;EACF;CACF;AACF"}
1
+ {"version":3,"file":"fullscreen.js","names":[],"sources":["../../../src/utils/dom/fullscreen.ts"],"sourcesContent":["/**\n * 让一个元素全屏(Fullscreen API):表格、看板、图表的“全屏”按钮。按 Esc 或浏览器的退出全屏时 active 跟着变。在组件的 setup 里调用。\n *\n * const fullscreen = useFullscreen(() => panel);\n * <IconButton icon={fullscreen.active ? Minimize : Maximize} onClick={fullscreen.toggle} />\n *\n * 浏览器不支持(iOS Safari 的非视频元素)或被拒绝时什么也不做,supported 为 false 时可以不显示按钮。\n * 挂载前 supported 总是 false:服务端渲染与水合时都不出按钮,两边一致。\n */\nimport { onBeforeUnmount, onMounted, shallowRef } from 'vue';\n\n/** useFullscreen 的返回值 */\nexport interface Fullscreen {\n /** 这个元素正在全屏 */\n readonly active: boolean;\n /** 浏览器支持全屏(挂载后才判断) */\n readonly supported: boolean;\n /** 进入或退出全屏 */\n toggle(): Promise<void>;\n}\n\n/** 元素的全屏状态与切换 */\nexport function useFullscreen(target: () => HTMLElement | null | undefined): Fullscreen {\n const active = shallowRef(false);\n const supported = shallowRef(false);\n\n const sync = () => {\n const element = target();\n active.value = !!element && document.fullscreenElement === element;\n };\n onMounted(() => {\n supported.value = document.fullscreenEnabled === true;\n document.addEventListener('fullscreenchange', sync);\n });\n onBeforeUnmount(() => document.removeEventListener('fullscreenchange', sync));\n\n return {\n get active() {\n return active.value;\n },\n get supported() {\n return supported.value;\n },\n async toggle() {\n const element = target();\n if (!supported.value || !element) return;\n try {\n if (document.fullscreenElement === element) await document.exitFullscreen();\n else await element.requestFullscreen();\n } catch (error) {\n // 被浏览器拒绝(不是用户手势触发、权限策略):保持原样,留下记录\n console.warn('全屏切换被浏览器拒绝', error);\n }\n },\n };\n}\n"],"mappings":";;;;;;;;;;;;AAsBA,SAAgB,cAAc,QAA0D;CACtF,MAAM,SAAS,WAAW,KAAK;CAC/B,MAAM,YAAY,WAAW,KAAK;CAElC,MAAM,aAAa;EACjB,MAAM,UAAU,OAAO;EACvB,OAAO,QAAQ,CAAC,CAAC,WAAW,SAAS,sBAAsB;CAC7D;CACA,gBAAgB;EACd,UAAU,QAAQ,SAAS,sBAAsB;EACjD,SAAS,iBAAiB,oBAAoB,IAAI;CACpD,CAAC;CACD,sBAAsB,SAAS,oBAAoB,oBAAoB,IAAI,CAAC;CAE5E,OAAO;EACL,IAAI,SAAS;GACX,OAAO,OAAO;EAChB;EACA,IAAI,YAAY;GACd,OAAO,UAAU;EACnB;EACA,MAAM,SAAS;GACb,MAAM,UAAU,OAAO;GACvB,IAAI,CAAC,UAAU,SAAS,CAAC,SAAS;GAClC,IAAI;IACF,IAAI,SAAS,sBAAsB,SAAS,MAAM,SAAS,eAAe;SACrE,MAAM,QAAQ,kBAAkB;GACvC,SAAS,OAAO;IAEd,QAAQ,KAAK,cAAc,KAAK;GAClC;EACF;CACF;AACF"}
@@ -0,0 +1,7 @@
1
+ /** 组件挂载了没有:服务端渲染与水合时为 false,挂载后变成 true */
2
+ import { type ShallowRef } from 'vue';
3
+ /**
4
+ * 挂载后才变成 true 的状态(在 setup 里调用)。只能在浏览器里判断的东西(某个 API 支不支持、量出来的尺寸)
5
+ * 在挂载前按服务端的结果渲染,水合时两边一致;挂载后再换成浏览器里的结果
6
+ */
7
+ export declare function useMounted(): Readonly<ShallowRef<boolean>>;
@@ -0,0 +1,16 @@
1
+ import { onMounted, shallowRef } from "vue";
2
+ //#region src/utils/dom/mounted.ts
3
+ /** 组件挂载了没有:服务端渲染与水合时为 false,挂载后变成 true */
4
+ /**
5
+ * 挂载后才变成 true 的状态(在 setup 里调用)。只能在浏览器里判断的东西(某个 API 支不支持、量出来的尺寸)
6
+ * 在挂载前按服务端的结果渲染,水合时两边一致;挂载后再换成浏览器里的结果
7
+ */
8
+ function useMounted() {
9
+ const mounted = shallowRef(false);
10
+ onMounted(() => mounted.value = true);
11
+ return mounted;
12
+ }
13
+ //#endregion
14
+ export { useMounted };
15
+
16
+ //# sourceMappingURL=mounted.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mounted.js","names":[],"sources":["../../../src/utils/dom/mounted.ts"],"sourcesContent":["/** 组件挂载了没有:服务端渲染与水合时为 false,挂载后变成 true */\nimport { onMounted, shallowRef, type ShallowRef } from 'vue';\n\n/**\n * 挂载后才变成 true 的状态(在 setup 里调用)。只能在浏览器里判断的东西(某个 API 支不支持、量出来的尺寸)\n * 在挂载前按服务端的结果渲染,水合时两边一致;挂载后再换成浏览器里的结果\n */\nexport function useMounted(): Readonly<ShallowRef<boolean>> {\n const mounted = shallowRef(false);\n onMounted(() => (mounted.value = true));\n return mounted;\n}\n"],"mappings":";;;;;;;AAOA,SAAgB,aAA4C;CAC1D,MAAM,UAAU,WAAW,KAAK;CAChC,gBAAiB,QAAQ,QAAQ,IAAK;CACtC,OAAO;AACT"}
@@ -1,5 +1,6 @@
1
1
  import { batchLayout } from "./layout-batch.js";
2
2
  import { useResizeObserver } from "./resize.js";
3
+ import { isHTMLElement } from "./element.js";
3
4
  import { shallowRef, watch } from "vue";
4
5
  //#region src/utils/dom/overflow.ts
5
6
  /**
@@ -32,7 +33,7 @@ function useOverflowItems(options) {
32
33
  const visible = shallowRef(Number.POSITIVE_INFINITY);
33
34
  const container = () => {
34
35
  const value = options.container();
35
- return value instanceof HTMLElement ? value : null;
36
+ return isHTMLElement(value) ? value : null;
36
37
  };
37
38
  function measure() {
38
39
  batchLayout(() => {
@@ -1 +1 @@
1
- {"version":3,"file":"overflow.js","names":[],"sources":["../../../src/utils/dom/overflow.ts"],"sourcesContent":["/**\n * 一行放不下时折起后面的项:量出容器放得下前几项,其余收进“+N”(或“更多”菜单)。\n * Select / TreeSelect 的 maxTagCount、Tabs 溢出、横向导航、工具栏都用它。参考 rc-overflow(Ant Design)的做法:\n * 所有项都渲染,放不下的移出排版、藏起来但仍可测量,宽度变了、项数变了就重新算。\n *\n * const overflow = useOverflowItems({ container: () => row, count: () => tags.length });\n * <div ref={row} style=\"display: flex; gap: 4px\">\n * {tags.map((tag, index) => <Tag {...overflow.itemProps(index)}>{tag}</Tag>)}\n * <Tag {...overflow.restProps()}>+{overflow.hidden}</Tag>\n * </div>\n *\n * - 容器是一行(flex、不换行、position: relative);项与“+N”都是它的直接子元素,间距取容器的 column-gap。\n * itemProps、restProps 里有 style(藏起来时),与项自己的 style 一起用 mergeAttrs 合并。\n * - 藏起来的项:绝对定位、visibility: hidden、aria-hidden 与 inert,不占位、读屏与 Tab 都跳过。\n * “+N”在没有折起的项时同样藏起来(仍要量它的宽度,决定最后一项放不放得下)。\n * - 宽度在下一次布局时读(batchLayout,与别的组件的读取合在一次布局里);量不到布局(服务端、测试环境)时全部显示。\n */\nimport { shallowRef, watch } from 'vue';\nimport { batchLayout } from './layout-batch';\nimport { useResizeObserver } from './resize';\n\n/** useOverflowItems 的选项 */\nexport interface OverflowItemsOptions {\n /** 一行的容器(元素或组件实例) */\n container: () => unknown;\n /** 项数 */\n count: () => number;\n /** 至少显示几项(再窄也不折起),默认 0 */\n min?: () => number;\n /** 同一行里还要给别的东西留出的宽度(px),如 TagsInput 的输入框,默认 0 */\n reserve?: () => number;\n}\n\n/** useOverflowItems 的返回值 */\nexport interface OverflowItems {\n /** 显示前几项 */\n readonly visible: number;\n /** 折起了几项(count − visible) */\n readonly hidden: number;\n /** 第 index 项的属性:展开到项的根元素上 */\n itemProps(index: number): Record<string, unknown>;\n /** “+N”的属性:展开到它的根元素上 */\n restProps(): Record<string, unknown>;\n}\n\n/** 藏起来但仍可测量:移出排版、不可见,读屏与 Tab 跳过 */\nconst MEASURE_ONLY = {\n position: 'absolute',\n visibility: 'hidden',\n pointerEvents: 'none',\n insetInlineStart: 0,\n insetBlockStart: 0,\n} as const;\n\n/** 量出一行放得下几项(在 setup 里调用) */\nexport function useOverflowItems(options: OverflowItemsOptions): OverflowItems {\n const visible = shallowRef(Number.POSITIVE_INFINITY);\n const container = () => {\n const value = options.container();\n return value instanceof HTMLElement ? value : null;\n };\n\n function measure() {\n batchLayout(() => {\n const root = container();\n if (!root) return;\n const items = [...root.querySelectorAll<HTMLElement>(':scope > [data-overflow-item]')];\n const rest = root.querySelector<HTMLElement>(':scope > [data-overflow-rest]');\n const style = getComputedStyle(root);\n const available =\n root.clientWidth -\n (parseFloat(style.paddingInlineStart) || 0) -\n (parseFloat(style.paddingInlineEnd) || 0) -\n (options.reserve?.() ?? 0);\n const gap = parseFloat(style.columnGap) || 0;\n const widths = items.map((item) => item.getBoundingClientRect().width);\n const restWidth = rest?.getBoundingClientRect().width ?? 0;\n // 量不到布局:全部显示\n if (available <= 0 && widths.every((width) => width === 0)) {\n return () => (visible.value = Number.POSITIVE_INFINITY);\n }\n const fit = fitCount(widths, restWidth, gap, available);\n const next = Math.max(fit, Math.min(options.min?.() ?? 0, widths.length));\n return () => (visible.value = next);\n });\n }\n\n useResizeObserver(() => {\n const root = container();\n if (!root) return [];\n return [root, ...root.querySelectorAll<HTMLElement>(':scope > [data-overflow-item]')];\n }, measure);\n // 挂载后量一次;项数变了、留出的宽度变了:新的项渲染后重新量\n watch(() => [options.count(), options.reserve?.(), options.min?.()], measure, {\n immediate: true,\n flush: 'post',\n });\n\n const shown = () => Math.min(visible.value, options.count());\n return {\n get visible() {\n return shown();\n },\n get hidden() {\n return options.count() - shown();\n },\n itemProps: (index) =>\n index < shown()\n ? { 'data-overflow-item': index }\n : { 'data-overflow-item': index, 'aria-hidden': 'true', inert: true, style: MEASURE_ONLY },\n restProps: () =>\n shown() < options.count()\n ? { 'data-overflow-rest': '' }\n : { 'data-overflow-rest': '', 'aria-hidden': 'true', inert: true, style: MEASURE_ONLY },\n };\n}\n\n/**\n * 前几项放得下:都放得下时不需要“+N”;否则在留出“+N”的宽度后尽量多放\n * (widths 是各项的宽度,gap 是项之间的间距)\n */\nexport function fitCount(\n widths: readonly number[],\n restWidth: number,\n gap: number,\n available: number,\n): number {\n const total = widths.reduce((sum, width, index) => sum + width + (index ? gap : 0), 0);\n if (total <= available) return widths.length;\n let used = restWidth;\n let count = 0;\n for (const width of widths) {\n const next = used + gap + width;\n if (next > available) break;\n used = next;\n count++;\n }\n return count;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AA8CA,IAAM,eAAe;CACnB,UAAU;CACV,YAAY;CACZ,eAAe;CACf,kBAAkB;CAClB,iBAAiB;AACnB;;AAGA,SAAgB,iBAAiB,SAA8C;CAC7E,MAAM,UAAU,WAAW,OAAO,iBAAiB;CACnD,MAAM,kBAAkB;EACtB,MAAM,QAAQ,QAAQ,UAAU;EAChC,OAAO,iBAAiB,cAAc,QAAQ;CAChD;CAEA,SAAS,UAAU;EACjB,kBAAkB;GAChB,MAAM,OAAO,UAAU;GACvB,IAAI,CAAC,MAAM;GACX,MAAM,QAAQ,CAAC,GAAG,KAAK,iBAA8B,+BAA+B,CAAC;GACrF,MAAM,OAAO,KAAK,cAA2B,+BAA+B;GAC5E,MAAM,QAAQ,iBAAiB,IAAI;GACnC,MAAM,YACJ,KAAK,eACJ,WAAW,MAAM,kBAAkB,KAAK,MACxC,WAAW,MAAM,gBAAgB,KAAK,MACtC,QAAQ,UAAU,KAAK;GAC1B,MAAM,MAAM,WAAW,MAAM,SAAS,KAAK;GAC3C,MAAM,SAAS,MAAM,KAAK,SAAS,KAAK,sBAAsB,CAAC,CAAC,KAAK;GACrE,MAAM,YAAY,MAAM,sBAAsB,CAAC,CAAC,SAAS;GAEzD,IAAI,aAAa,KAAK,OAAO,OAAO,UAAU,UAAU,CAAC,GACvD,aAAc,QAAQ,QAAQ,OAAO;GAEvC,MAAM,MAAM,SAAS,QAAQ,WAAW,KAAK,SAAS;GACtD,MAAM,OAAO,KAAK,IAAI,KAAK,KAAK,IAAI,QAAQ,MAAM,KAAK,GAAG,OAAO,MAAM,CAAC;GACxE,aAAc,QAAQ,QAAQ;EAChC,CAAC;CACH;CAEA,wBAAwB;EACtB,MAAM,OAAO,UAAU;EACvB,IAAI,CAAC,MAAM,OAAO,CAAC;EACnB,OAAO,CAAC,MAAM,GAAG,KAAK,iBAA8B,+BAA+B,CAAC;CACtF,GAAG,OAAO;CAEV,YAAY;EAAC,QAAQ,MAAM;EAAG,QAAQ,UAAU;EAAG,QAAQ,MAAM;CAAC,GAAG,SAAS;EAC5E,WAAW;EACX,OAAO;CACT,CAAC;CAED,MAAM,cAAc,KAAK,IAAI,QAAQ,OAAO,QAAQ,MAAM,CAAC;CAC3D,OAAO;EACL,IAAI,UAAU;GACZ,OAAO,MAAM;EACf;EACA,IAAI,SAAS;GACX,OAAO,QAAQ,MAAM,IAAI,MAAM;EACjC;EACA,YAAY,UACV,QAAQ,MAAM,IACV,EAAE,sBAAsB,MAAM,IAC9B;GAAE,sBAAsB;GAAO,eAAe;GAAQ,OAAO;GAAM,OAAO;EAAa;EAC7F,iBACE,MAAM,IAAI,QAAQ,MAAM,IACpB,EAAE,sBAAsB,GAAG,IAC3B;GAAE,sBAAsB;GAAI,eAAe;GAAQ,OAAO;GAAM,OAAO;EAAa;CAC5F;AACF;;;;;AAMA,SAAgB,SACd,QACA,WACA,KACA,WACQ;CAER,IADc,OAAO,QAAQ,KAAK,OAAO,UAAU,MAAM,SAAS,QAAQ,MAAM,IAAI,CAChF,KAAS,WAAW,OAAO,OAAO;CACtC,IAAI,OAAO;CACX,IAAI,QAAQ;CACZ,KAAK,MAAM,SAAS,QAAQ;EAC1B,MAAM,OAAO,OAAO,MAAM;EAC1B,IAAI,OAAO,WAAW;EACtB,OAAO;EACP;CACF;CACA,OAAO;AACT"}
1
+ {"version":3,"file":"overflow.js","names":[],"sources":["../../../src/utils/dom/overflow.ts"],"sourcesContent":["/**\n * 一行放不下时折起后面的项:量出容器放得下前几项,其余收进“+N”(或“更多”菜单)。\n * Select / TreeSelect 的 maxTagCount、Tabs 溢出、横向导航、工具栏都用它。参考 rc-overflow(Ant Design)的做法:\n * 所有项都渲染,放不下的移出排版、藏起来但仍可测量,宽度变了、项数变了就重新算。\n *\n * const overflow = useOverflowItems({ container: () => row, count: () => tags.length });\n * <div ref={row} style=\"display: flex; gap: 4px\">\n * {tags.map((tag, index) => <Tag {...overflow.itemProps(index)}>{tag}</Tag>)}\n * <Tag {...overflow.restProps()}>+{overflow.hidden}</Tag>\n * </div>\n *\n * - 容器是一行(flex、不换行、position: relative);项与“+N”都是它的直接子元素,间距取容器的 column-gap。\n * itemProps、restProps 里有 style(藏起来时),与项自己的 style 一起用 mergeAttrs 合并。\n * - 藏起来的项:绝对定位、visibility: hidden、aria-hidden 与 inert,不占位、读屏与 Tab 都跳过。\n * “+N”在没有折起的项时同样藏起来(仍要量它的宽度,决定最后一项放不放得下)。\n * - 宽度在下一次布局时读(batchLayout,与别的组件的读取合在一次布局里);量不到布局(服务端、测试环境)时全部显示。\n */\nimport { shallowRef, watch } from 'vue';\nimport { batchLayout } from './layout-batch';\nimport { useResizeObserver } from './resize';\nimport { isHTMLElement } from './element';\n\n/** useOverflowItems 的选项 */\nexport interface OverflowItemsOptions {\n /** 一行的容器(元素或组件实例) */\n container: () => unknown;\n /** 项数 */\n count: () => number;\n /** 至少显示几项(再窄也不折起),默认 0 */\n min?: () => number;\n /** 同一行里还要给别的东西留出的宽度(px),如 TagsInput 的输入框,默认 0 */\n reserve?: () => number;\n}\n\n/** useOverflowItems 的返回值 */\nexport interface OverflowItems {\n /** 显示前几项 */\n readonly visible: number;\n /** 折起了几项(count − visible) */\n readonly hidden: number;\n /** 第 index 项的属性:展开到项的根元素上 */\n itemProps(index: number): Record<string, unknown>;\n /** “+N”的属性:展开到它的根元素上 */\n restProps(): Record<string, unknown>;\n}\n\n/** 藏起来但仍可测量:移出排版、不可见,读屏与 Tab 跳过 */\nconst MEASURE_ONLY = {\n position: 'absolute',\n visibility: 'hidden',\n pointerEvents: 'none',\n insetInlineStart: 0,\n insetBlockStart: 0,\n} as const;\n\n/** 量出一行放得下几项(在 setup 里调用) */\nexport function useOverflowItems(options: OverflowItemsOptions): OverflowItems {\n const visible = shallowRef(Number.POSITIVE_INFINITY);\n const container = () => {\n const value = options.container();\n return isHTMLElement(value) ? value : null;\n };\n\n function measure() {\n batchLayout(() => {\n const root = container();\n if (!root) return;\n const items = [...root.querySelectorAll<HTMLElement>(':scope > [data-overflow-item]')];\n const rest = root.querySelector<HTMLElement>(':scope > [data-overflow-rest]');\n const style = getComputedStyle(root);\n const available =\n root.clientWidth -\n (parseFloat(style.paddingInlineStart) || 0) -\n (parseFloat(style.paddingInlineEnd) || 0) -\n (options.reserve?.() ?? 0);\n const gap = parseFloat(style.columnGap) || 0;\n const widths = items.map((item) => item.getBoundingClientRect().width);\n const restWidth = rest?.getBoundingClientRect().width ?? 0;\n // 量不到布局:全部显示\n if (available <= 0 && widths.every((width) => width === 0)) {\n return () => (visible.value = Number.POSITIVE_INFINITY);\n }\n const fit = fitCount(widths, restWidth, gap, available);\n const next = Math.max(fit, Math.min(options.min?.() ?? 0, widths.length));\n return () => (visible.value = next);\n });\n }\n\n useResizeObserver(() => {\n const root = container();\n if (!root) return [];\n return [root, ...root.querySelectorAll<HTMLElement>(':scope > [data-overflow-item]')];\n }, measure);\n // 挂载后量一次;项数变了、留出的宽度变了:新的项渲染后重新量\n watch(() => [options.count(), options.reserve?.(), options.min?.()], measure, {\n immediate: true,\n flush: 'post',\n });\n\n const shown = () => Math.min(visible.value, options.count());\n return {\n get visible() {\n return shown();\n },\n get hidden() {\n return options.count() - shown();\n },\n itemProps: (index) =>\n index < shown()\n ? { 'data-overflow-item': index }\n : { 'data-overflow-item': index, 'aria-hidden': 'true', inert: true, style: MEASURE_ONLY },\n restProps: () =>\n shown() < options.count()\n ? { 'data-overflow-rest': '' }\n : { 'data-overflow-rest': '', 'aria-hidden': 'true', inert: true, style: MEASURE_ONLY },\n };\n}\n\n/**\n * 前几项放得下:都放得下时不需要“+N”;否则在留出“+N”的宽度后尽量多放\n * (widths 是各项的宽度,gap 是项之间的间距)\n */\nexport function fitCount(\n widths: readonly number[],\n restWidth: number,\n gap: number,\n available: number,\n): number {\n const total = widths.reduce((sum, width, index) => sum + width + (index ? gap : 0), 0);\n if (total <= available) return widths.length;\n let used = restWidth;\n let count = 0;\n for (const width of widths) {\n const next = used + gap + width;\n if (next > available) break;\n used = next;\n count++;\n }\n return count;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AA+CA,IAAM,eAAe;CACnB,UAAU;CACV,YAAY;CACZ,eAAe;CACf,kBAAkB;CAClB,iBAAiB;AACnB;;AAGA,SAAgB,iBAAiB,SAA8C;CAC7E,MAAM,UAAU,WAAW,OAAO,iBAAiB;CACnD,MAAM,kBAAkB;EACtB,MAAM,QAAQ,QAAQ,UAAU;EAChC,OAAO,cAAc,KAAK,IAAI,QAAQ;CACxC;CAEA,SAAS,UAAU;EACjB,kBAAkB;GAChB,MAAM,OAAO,UAAU;GACvB,IAAI,CAAC,MAAM;GACX,MAAM,QAAQ,CAAC,GAAG,KAAK,iBAA8B,+BAA+B,CAAC;GACrF,MAAM,OAAO,KAAK,cAA2B,+BAA+B;GAC5E,MAAM,QAAQ,iBAAiB,IAAI;GACnC,MAAM,YACJ,KAAK,eACJ,WAAW,MAAM,kBAAkB,KAAK,MACxC,WAAW,MAAM,gBAAgB,KAAK,MACtC,QAAQ,UAAU,KAAK;GAC1B,MAAM,MAAM,WAAW,MAAM,SAAS,KAAK;GAC3C,MAAM,SAAS,MAAM,KAAK,SAAS,KAAK,sBAAsB,CAAC,CAAC,KAAK;GACrE,MAAM,YAAY,MAAM,sBAAsB,CAAC,CAAC,SAAS;GAEzD,IAAI,aAAa,KAAK,OAAO,OAAO,UAAU,UAAU,CAAC,GACvD,aAAc,QAAQ,QAAQ,OAAO;GAEvC,MAAM,MAAM,SAAS,QAAQ,WAAW,KAAK,SAAS;GACtD,MAAM,OAAO,KAAK,IAAI,KAAK,KAAK,IAAI,QAAQ,MAAM,KAAK,GAAG,OAAO,MAAM,CAAC;GACxE,aAAc,QAAQ,QAAQ;EAChC,CAAC;CACH;CAEA,wBAAwB;EACtB,MAAM,OAAO,UAAU;EACvB,IAAI,CAAC,MAAM,OAAO,CAAC;EACnB,OAAO,CAAC,MAAM,GAAG,KAAK,iBAA8B,+BAA+B,CAAC;CACtF,GAAG,OAAO;CAEV,YAAY;EAAC,QAAQ,MAAM;EAAG,QAAQ,UAAU;EAAG,QAAQ,MAAM;CAAC,GAAG,SAAS;EAC5E,WAAW;EACX,OAAO;CACT,CAAC;CAED,MAAM,cAAc,KAAK,IAAI,QAAQ,OAAO,QAAQ,MAAM,CAAC;CAC3D,OAAO;EACL,IAAI,UAAU;GACZ,OAAO,MAAM;EACf;EACA,IAAI,SAAS;GACX,OAAO,QAAQ,MAAM,IAAI,MAAM;EACjC;EACA,YAAY,UACV,QAAQ,MAAM,IACV,EAAE,sBAAsB,MAAM,IAC9B;GAAE,sBAAsB;GAAO,eAAe;GAAQ,OAAO;GAAM,OAAO;EAAa;EAC7F,iBACE,MAAM,IAAI,QAAQ,MAAM,IACpB,EAAE,sBAAsB,GAAG,IAC3B;GAAE,sBAAsB;GAAI,eAAe;GAAQ,OAAO;GAAM,OAAO;EAAa;CAC5F;AACF;;;;;AAMA,SAAgB,SACd,QACA,WACA,KACA,WACQ;CAER,IADc,OAAO,QAAQ,KAAK,OAAO,UAAU,MAAM,SAAS,QAAQ,MAAM,IAAI,CAChF,KAAS,WAAW,OAAO,OAAO;CACtC,IAAI,OAAO;CACX,IAAI,QAAQ;CACZ,KAAK,MAAM,SAAS,QAAQ;EAC1B,MAAM,OAAO,OAAO,MAAM;EAC1B,IAAI,OAAO,WAAW;EACtB,OAAO;EACP;CACF;CACA,OAAO;AACT"}
@@ -1,5 +1,6 @@
1
1
  import { clamp } from "../data/number.js";
2
2
  import { startMove } from "./move.js";
3
+ import { isHTMLElement } from "./element.js";
3
4
  import { useEventListener } from "./event-listener.js";
4
5
  import { shallowRef } from "vue";
5
6
  //#region src/utils/dom/pan-zoom.ts
@@ -72,7 +73,7 @@ function usePanZoom(options) {
72
73
  let pinch;
73
74
  const viewport = () => {
74
75
  const value = options.viewport();
75
- return value instanceof HTMLElement ? value : null;
76
+ return isHTMLElement(value) ? value : null;
76
77
  };
77
78
  const viewportSize = () => {
78
79
  const element = viewport();
@@ -1 +1 @@
1
- {"version":3,"file":"pan-zoom.js","names":[],"sources":["../../../src/utils/dom/pan-zoom.ts"],"sourcesContent":["/**\n * 平移与缩放:拖动平移,滚轮、触控板双指、触屏捏合以指针为中心缩放,键盘也能操作。\n * 图片预览(Lightbox)、流程图、大图表这类“在视口里看一块更大的内容”的场景用它。\n * 参考 panzoom、React Flow 的视口:状态只有平移 x、y 与缩放 scale,内容自己写 transform。\n *\n * const view = usePanZoom({ viewport: () => box, min: 0.5, max: 4 });\n * <div ref={box} tabindex=\"0\" {...view.viewportProps}>\n * <img style={{ transform: view.transform, transformOrigin: '0 0' }} />\n * </div>\n * <IconButton onClick={() => view.zoomIn()} … />\n *\n * - 指针:按下拖动平移(建在 startMove 上,拖过之后的 click 不算点击);双击放大一档,已放大时回到初始;\n * 两个触点捏合缩放(中心是两指的中点)。\n * - 滚轮:wheel 为 'zoom'(默认,图片预览)时滚轮缩放;为 'pan' 时滚轮平移、按住 Ctrl / ⌘ 才缩放(流程图、画布)。\n * 触控板的双指缩放在浏览器里就是按着 Ctrl 的滚轮,两种模式下都是缩放。\n * - 键盘(视口要能聚焦):+ / − 缩放,0 回到初始,方向键平移。\n * - bounds 给出内容的大小时限制平移:内容比视口大就不露出边外的空白,比视口小就居中。\n * - filter 挑出要处理的事件(同 d3-zoom 的 filter):画布里按在节点上、按着 Shift 框选时返回 false,平移让给它们。\n * 视口要设 touch-action: none(viewportProps 里已带),否则触屏拖动会被当成页面滚动。\n */\nimport { shallowRef } from 'vue';\nimport { clamp } from '../data/number';\nimport { useEventListener } from './event-listener';\nimport { startMove } from './move';\n\n/** 平移与缩放的状态:内容先缩放、再平移(transform-origin 为左上角) */\nexport interface PanZoomState {\n /** 横向平移(px) */\n x: number;\n /** 纵向平移(px) */\n y: number;\n /** 缩放比例,1 为原大 */\n scale: number;\n}\n\n/** 滚轮的作用 */\nexport interface PanZoomWheelModes {\n /** 滚轮缩放(图片预览) */\n zoom: unknown;\n /** 滚轮平移,按住 Ctrl / ⌘ 缩放(画布、流程图) */\n pan: unknown;\n}\n\n/** usePanZoom 的选项 */\nexport interface PanZoomOptions {\n /** 接收拖动、滚轮的视口(元素或组件实例) */\n viewport: () => unknown;\n /** 最小缩放,默认 0.25 */\n min?: number;\n /** 最大缩放,默认 4 */\n max?: number;\n /** 每一档缩放的倍数(按钮、键盘、双击),默认 1.25 */\n step?: number;\n /** 初始状态,默认 { x: 0, y: 0, scale: 1 };reset 回到它 */\n initial?: () => PanZoomState;\n /** 滚轮的作用,默认 zoom */\n wheel?: keyof PanZoomWheelModes;\n /** 内容的大小(缩放前):给了就限制平移,不露出内容外的空白 */\n bounds?: () => { width: number; height: number } | undefined;\n /** 不可用:不响应拖动、滚轮与键盘 */\n disabled?: () => boolean;\n /** 只处理返回 true 的事件(按下、滚轮、双击、按键);默认都处理。返回 false 的事件原样留给页面与别的处理函数 */\n filter?: (event: PointerEvent | WheelEvent | MouseEvent | KeyboardEvent) => boolean;\n}\n\n/** fit 要放进视口的内容:缩放前的位置与大小,x、y 不写时从 0 开始 */\nexport interface PanZoomContent {\n /** 内容左边的横坐标,默认 0 */\n x?: number;\n /** 内容上边的纵坐标,默认 0 */\n y?: number;\n /** 宽 */\n width: number;\n /** 高 */\n height: number;\n}\n\n/** usePanZoom 的返回值 */\nexport interface PanZoom {\n /** 横向平移(px) */\n readonly x: number;\n /** 纵向平移(px) */\n readonly y: number;\n /** 缩放比例 */\n readonly scale: number;\n /** 正在拖动或捏合:这期间内容不要加 transition */\n readonly interacting: boolean;\n /** 内容的 transform:`translate(xpx, ypx) scale(s)`,配 transform-origin: 0 0 */\n readonly transform: string;\n /** 放大一档;origin 是缩放中心(视口内坐标),默认视口中心 */\n zoomIn(origin?: Point): void;\n /** 缩小一档 */\n zoomOut(origin?: Point): void;\n /** 缩放到 scale(限制在 min–max) */\n zoomTo(scale: number, origin?: Point): void;\n /** 平移到 x、y */\n panTo(x: number, y: number): void;\n /** 回到初始状态 */\n reset(): void;\n /** 把内容整个放进视口并居中(不超过原大),四周留出 padding(px),返回用到的缩放 */\n fit(content: PanZoomContent, padding?: number): number;\n /** 视口的属性:展开到视口元素上 */\n readonly viewportProps: Record<string, unknown>;\n}\n\n/** 视口内的一点(相对视口左上角,px) */\nexport interface Point {\n /** 横坐标 */\n x: number;\n /** 纵坐标 */\n y: number;\n}\n\n/** 以 origin 为中心缩放到 next:origin 下的内容点缩放前后不动 */\nexport function zoomAround(state: PanZoomState, next: number, origin: Point): PanZoomState {\n const ratio = next / state.scale;\n return {\n x: origin.x - (origin.x - state.x) * ratio,\n y: origin.y - (origin.y - state.y) * ratio,\n scale: next,\n };\n}\n\n/** 平移的限制:内容比视口大时边缘不进到视口里,比视口小时居中 */\nexport function clampPan(\n state: PanZoomState,\n viewport: { width: number; height: number },\n content: { width: number; height: number },\n): PanZoomState {\n const axis = (offset: number, view: number, size: number) =>\n size <= view ? (view - size) / 2 : clamp(offset, view - size, 0);\n return {\n x: axis(state.x, viewport.width, content.width * state.scale),\n y: axis(state.y, viewport.height, content.height * state.scale),\n scale: state.scale,\n };\n}\n\n/**\n * 把内容整个放进视口并居中的状态:缩放取放得下的最大值(不超过原大,再限制在 min–max),四周留出 padding(px)。\n * 视口或内容的宽高为 0 时缩放按 1 算\n */\nexport function fitTransform(\n viewport: { width: number; height: number },\n { x = 0, y = 0, width, height }: PanZoomContent,\n { padding = 0, min = 0, max = Infinity }: { padding?: number; min?: number; max?: number } = {},\n): PanZoomState {\n const fits = (room: number, size: number) => (size > 0 && room > 0 ? room / size : 1);\n const scale = clamp(\n Math.min(\n fits(viewport.width - padding * 2, width),\n fits(viewport.height - padding * 2, height),\n 1,\n ),\n min,\n max,\n );\n return {\n x: (viewport.width - width * scale) / 2 - x * scale,\n y: (viewport.height - height * scale) / 2 - y * scale,\n scale,\n };\n}\n\n/** 键盘方向键每次平移多少 px */\nconst KEY_PAN = 40;\n\n/** 平移与缩放的状态与事件(在 setup 里调用) */\nexport function usePanZoom(options: PanZoomOptions): PanZoom {\n const min = options.min ?? 0.25;\n const max = options.max ?? 4;\n const step = options.step ?? 1.25;\n const initial = () => options.initial?.() ?? { x: 0, y: 0, scale: 1 };\n const state = shallowRef<PanZoomState>(initial());\n const interacting = shallowRef(false);\n // 按着的触点:两个时捏合\n const pointers = new Map<number, Point>();\n let pinch: { distance: number; state: PanZoomState } | undefined;\n\n const viewport = () => {\n const value = options.viewport();\n return value instanceof HTMLElement ? value : null;\n };\n const viewportSize = () => {\n const element = viewport();\n return { width: element?.clientWidth ?? 0, height: element?.clientHeight ?? 0 };\n };\n const center = (): Point => {\n const { width, height } = viewportSize();\n return { x: width / 2, y: height / 2 };\n };\n /** 事件的位置换成视口内坐标 */\n const local = (event: { clientX: number; clientY: number }): Point => {\n const rect = viewport()?.getBoundingClientRect();\n return { x: event.clientX - (rect?.left ?? 0), y: event.clientY - (rect?.top ?? 0) };\n };\n\n function apply(next: PanZoomState) {\n const content = options.bounds?.();\n state.value = content ? clampPan(next, viewportSize(), content) : next;\n }\n\n function zoomTo(scale: number, origin: Point = center()) {\n const next = clamp(scale, min, max);\n if (next === state.value.scale) return;\n apply(zoomAround(state.value, next, origin));\n }\n\n /** 这个事件要不要处理:不可用时都不处理,filter 挑掉的也不处理 */\n const handles = (event: PointerEvent | WheelEvent | MouseEvent | KeyboardEvent) =>\n !options.disabled?.() && (options.filter?.(event) ?? true);\n\n function onWheel(event: WheelEvent) {\n if (!handles(event)) return;\n const zoom = options.wheel !== 'pan' || event.ctrlKey || event.metaKey;\n event.preventDefault();\n if (!zoom) {\n apply({ ...state.value, x: state.value.x - event.deltaX, y: state.value.y - event.deltaY });\n return;\n }\n // 触控板的捏合每次 delta 很小、鼠标滚轮一格约 100:按 delta 连续缩放,两种都顺手\n const factor = Math.exp(-event.deltaY * (event.deltaMode === 1 ? 0.05 : 0.002));\n zoomTo(state.value.scale * factor, local(event));\n }\n // 要能 preventDefault(不让页面跟着滚、不让浏览器整页缩放):不能是 passive\n useEventListener(viewport, 'wheel', onWheel, { passive: false });\n\n function onPointerdown(event: PointerEvent) {\n if (event.button !== 0 || !handles(event)) return;\n pointers.set(event.pointerId, local(event));\n if (pointers.size === 2) {\n const [a, b] = [...pointers.values()] as [Point, Point];\n pinch = { distance: Math.hypot(a.x - b.x, a.y - b.y), state: state.value };\n return;\n }\n const start = state.value;\n startMove(event, {\n threshold: 3,\n onMove: (point, move) => {\n pointers.set(move.pointerId, local(move));\n interacting.value = true;\n if (pinch) return;\n apply({ ...start, x: start.x + point.deltaX, y: start.y + point.deltaY });\n },\n onEnd: () => {\n pointers.clear();\n pinch = undefined;\n interacting.value = false;\n },\n });\n }\n\n // 第二个触点的移动:捏合(第一个触点的移动由 startMove 收到)\n function onPointermove(event: PointerEvent) {\n if (!pinch || !pointers.has(event.pointerId)) return;\n pointers.set(event.pointerId, local(event));\n const [a, b] = [...pointers.values()] as [Point, Point];\n const distance = Math.hypot(a.x - b.x, a.y - b.y);\n const scale = clamp((pinch.state.scale * distance) / pinch.distance, min, max);\n apply(zoomAround(pinch.state, scale, { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 }));\n }\n\n function onPointerup(event: PointerEvent) {\n pointers.delete(event.pointerId);\n if (pointers.size < 2) pinch = undefined;\n }\n\n function onDblclick(event: MouseEvent) {\n if (!handles(event)) return;\n if (state.value.scale > initial().scale) apply(initial());\n else zoomTo(state.value.scale * step * step, local(event));\n }\n\n function onKeydown(event: KeyboardEvent) {\n if (event.altKey || event.ctrlKey || event.metaKey || !handles(event)) return;\n const { x, y } = state.value;\n const actions: Record<string, () => void> = {\n '+': () => zoomTo(state.value.scale * step),\n '=': () => zoomTo(state.value.scale * step),\n '-': () => zoomTo(state.value.scale / step),\n '0': () => apply(initial()),\n ArrowLeft: () => apply({ ...state.value, x: x + KEY_PAN }),\n ArrowRight: () => apply({ ...state.value, x: x - KEY_PAN }),\n ArrowUp: () => apply({ ...state.value, y: y + KEY_PAN }),\n ArrowDown: () => apply({ ...state.value, y: y - KEY_PAN }),\n };\n const action = actions[event.key];\n if (!action) return;\n event.preventDefault();\n action();\n }\n\n const viewportProps = {\n style: { touchAction: 'none', overflow: 'hidden' },\n onPointerdown,\n onPointermove,\n onPointerup,\n onPointercancel: onPointerup,\n onDblclick,\n onKeydown,\n };\n\n return {\n get x() {\n return state.value.x;\n },\n get y() {\n return state.value.y;\n },\n get scale() {\n return state.value.scale;\n },\n get interacting() {\n return interacting.value;\n },\n get transform() {\n const { x, y, scale } = state.value;\n return `translate(${x}px, ${y}px) scale(${scale})`;\n },\n zoomIn: (origin) => zoomTo(state.value.scale * step, origin),\n zoomOut: (origin) => zoomTo(state.value.scale / step, origin),\n zoomTo,\n panTo: (x, y) => apply({ ...state.value, x, y }),\n reset: () => apply(initial()),\n fit(content, padding = 0) {\n state.value = fitTransform(viewportSize(), content, { padding, min, max });\n return state.value.scale;\n },\n viewportProps,\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"pan-zoom.js","names":[],"sources":["../../../src/utils/dom/pan-zoom.ts"],"sourcesContent":["/**\n * 平移与缩放:拖动平移,滚轮、触控板双指、触屏捏合以指针为中心缩放,键盘也能操作。\n * 图片预览(Lightbox)、流程图、大图表这类“在视口里看一块更大的内容”的场景用它。\n * 参考 panzoom、React Flow 的视口:状态只有平移 x、y 与缩放 scale,内容自己写 transform。\n *\n * const view = usePanZoom({ viewport: () => box, min: 0.5, max: 4 });\n * <div ref={box} tabindex=\"0\" {...view.viewportProps}>\n * <img style={{ transform: view.transform, transformOrigin: '0 0' }} />\n * </div>\n * <IconButton onClick={() => view.zoomIn()} … />\n *\n * - 指针:按下拖动平移(建在 startMove 上,拖过之后的 click 不算点击);双击放大一档,已放大时回到初始;\n * 两个触点捏合缩放(中心是两指的中点)。\n * - 滚轮:wheel 为 'zoom'(默认,图片预览)时滚轮缩放;为 'pan' 时滚轮平移、按住 Ctrl / ⌘ 才缩放(流程图、画布)。\n * 触控板的双指缩放在浏览器里就是按着 Ctrl 的滚轮,两种模式下都是缩放。\n * - 键盘(视口要能聚焦):+ / − 缩放,0 回到初始,方向键平移。\n * - bounds 给出内容的大小时限制平移:内容比视口大就不露出边外的空白,比视口小就居中。\n * - filter 挑出要处理的事件(同 d3-zoom 的 filter):画布里按在节点上、按着 Shift 框选时返回 false,平移让给它们。\n * 视口要设 touch-action: none(viewportProps 里已带),否则触屏拖动会被当成页面滚动。\n */\nimport { shallowRef } from 'vue';\nimport { clamp } from '../data/number';\nimport { useEventListener } from './event-listener';\nimport { startMove } from './move';\nimport { isHTMLElement } from './element';\n\n/** 平移与缩放的状态:内容先缩放、再平移(transform-origin 为左上角) */\nexport interface PanZoomState {\n /** 横向平移(px) */\n x: number;\n /** 纵向平移(px) */\n y: number;\n /** 缩放比例,1 为原大 */\n scale: number;\n}\n\n/** 滚轮的作用 */\nexport interface PanZoomWheelModes {\n /** 滚轮缩放(图片预览) */\n zoom: unknown;\n /** 滚轮平移,按住 Ctrl / ⌘ 缩放(画布、流程图) */\n pan: unknown;\n}\n\n/** usePanZoom 的选项 */\nexport interface PanZoomOptions {\n /** 接收拖动、滚轮的视口(元素或组件实例) */\n viewport: () => unknown;\n /** 最小缩放,默认 0.25 */\n min?: number;\n /** 最大缩放,默认 4 */\n max?: number;\n /** 每一档缩放的倍数(按钮、键盘、双击),默认 1.25 */\n step?: number;\n /** 初始状态,默认 { x: 0, y: 0, scale: 1 };reset 回到它 */\n initial?: () => PanZoomState;\n /** 滚轮的作用,默认 zoom */\n wheel?: keyof PanZoomWheelModes;\n /** 内容的大小(缩放前):给了就限制平移,不露出内容外的空白 */\n bounds?: () => { width: number; height: number } | undefined;\n /** 不可用:不响应拖动、滚轮与键盘 */\n disabled?: () => boolean;\n /** 只处理返回 true 的事件(按下、滚轮、双击、按键);默认都处理。返回 false 的事件原样留给页面与别的处理函数 */\n filter?: (event: PointerEvent | WheelEvent | MouseEvent | KeyboardEvent) => boolean;\n}\n\n/** fit 要放进视口的内容:缩放前的位置与大小,x、y 不写时从 0 开始 */\nexport interface PanZoomContent {\n /** 内容左边的横坐标,默认 0 */\n x?: number;\n /** 内容上边的纵坐标,默认 0 */\n y?: number;\n /** 宽 */\n width: number;\n /** 高 */\n height: number;\n}\n\n/** usePanZoom 的返回值 */\nexport interface PanZoom {\n /** 横向平移(px) */\n readonly x: number;\n /** 纵向平移(px) */\n readonly y: number;\n /** 缩放比例 */\n readonly scale: number;\n /** 正在拖动或捏合:这期间内容不要加 transition */\n readonly interacting: boolean;\n /** 内容的 transform:`translate(xpx, ypx) scale(s)`,配 transform-origin: 0 0 */\n readonly transform: string;\n /** 放大一档;origin 是缩放中心(视口内坐标),默认视口中心 */\n zoomIn(origin?: Point): void;\n /** 缩小一档 */\n zoomOut(origin?: Point): void;\n /** 缩放到 scale(限制在 min–max) */\n zoomTo(scale: number, origin?: Point): void;\n /** 平移到 x、y */\n panTo(x: number, y: number): void;\n /** 回到初始状态 */\n reset(): void;\n /** 把内容整个放进视口并居中(不超过原大),四周留出 padding(px),返回用到的缩放 */\n fit(content: PanZoomContent, padding?: number): number;\n /** 视口的属性:展开到视口元素上 */\n readonly viewportProps: Record<string, unknown>;\n}\n\n/** 视口内的一点(相对视口左上角,px) */\nexport interface Point {\n /** 横坐标 */\n x: number;\n /** 纵坐标 */\n y: number;\n}\n\n/** 以 origin 为中心缩放到 next:origin 下的内容点缩放前后不动 */\nexport function zoomAround(state: PanZoomState, next: number, origin: Point): PanZoomState {\n const ratio = next / state.scale;\n return {\n x: origin.x - (origin.x - state.x) * ratio,\n y: origin.y - (origin.y - state.y) * ratio,\n scale: next,\n };\n}\n\n/** 平移的限制:内容比视口大时边缘不进到视口里,比视口小时居中 */\nexport function clampPan(\n state: PanZoomState,\n viewport: { width: number; height: number },\n content: { width: number; height: number },\n): PanZoomState {\n const axis = (offset: number, view: number, size: number) =>\n size <= view ? (view - size) / 2 : clamp(offset, view - size, 0);\n return {\n x: axis(state.x, viewport.width, content.width * state.scale),\n y: axis(state.y, viewport.height, content.height * state.scale),\n scale: state.scale,\n };\n}\n\n/**\n * 把内容整个放进视口并居中的状态:缩放取放得下的最大值(不超过原大,再限制在 min–max),四周留出 padding(px)。\n * 视口或内容的宽高为 0 时缩放按 1 算\n */\nexport function fitTransform(\n viewport: { width: number; height: number },\n { x = 0, y = 0, width, height }: PanZoomContent,\n { padding = 0, min = 0, max = Infinity }: { padding?: number; min?: number; max?: number } = {},\n): PanZoomState {\n const fits = (room: number, size: number) => (size > 0 && room > 0 ? room / size : 1);\n const scale = clamp(\n Math.min(\n fits(viewport.width - padding * 2, width),\n fits(viewport.height - padding * 2, height),\n 1,\n ),\n min,\n max,\n );\n return {\n x: (viewport.width - width * scale) / 2 - x * scale,\n y: (viewport.height - height * scale) / 2 - y * scale,\n scale,\n };\n}\n\n/** 键盘方向键每次平移多少 px */\nconst KEY_PAN = 40;\n\n/** 平移与缩放的状态与事件(在 setup 里调用) */\nexport function usePanZoom(options: PanZoomOptions): PanZoom {\n const min = options.min ?? 0.25;\n const max = options.max ?? 4;\n const step = options.step ?? 1.25;\n const initial = () => options.initial?.() ?? { x: 0, y: 0, scale: 1 };\n const state = shallowRef<PanZoomState>(initial());\n const interacting = shallowRef(false);\n // 按着的触点:两个时捏合\n const pointers = new Map<number, Point>();\n let pinch: { distance: number; state: PanZoomState } | undefined;\n\n const viewport = () => {\n const value = options.viewport();\n return isHTMLElement(value) ? value : null;\n };\n const viewportSize = () => {\n const element = viewport();\n return { width: element?.clientWidth ?? 0, height: element?.clientHeight ?? 0 };\n };\n const center = (): Point => {\n const { width, height } = viewportSize();\n return { x: width / 2, y: height / 2 };\n };\n /** 事件的位置换成视口内坐标 */\n const local = (event: { clientX: number; clientY: number }): Point => {\n const rect = viewport()?.getBoundingClientRect();\n return { x: event.clientX - (rect?.left ?? 0), y: event.clientY - (rect?.top ?? 0) };\n };\n\n function apply(next: PanZoomState) {\n const content = options.bounds?.();\n state.value = content ? clampPan(next, viewportSize(), content) : next;\n }\n\n function zoomTo(scale: number, origin: Point = center()) {\n const next = clamp(scale, min, max);\n if (next === state.value.scale) return;\n apply(zoomAround(state.value, next, origin));\n }\n\n /** 这个事件要不要处理:不可用时都不处理,filter 挑掉的也不处理 */\n const handles = (event: PointerEvent | WheelEvent | MouseEvent | KeyboardEvent) =>\n !options.disabled?.() && (options.filter?.(event) ?? true);\n\n function onWheel(event: WheelEvent) {\n if (!handles(event)) return;\n const zoom = options.wheel !== 'pan' || event.ctrlKey || event.metaKey;\n event.preventDefault();\n if (!zoom) {\n apply({ ...state.value, x: state.value.x - event.deltaX, y: state.value.y - event.deltaY });\n return;\n }\n // 触控板的捏合每次 delta 很小、鼠标滚轮一格约 100:按 delta 连续缩放,两种都顺手\n const factor = Math.exp(-event.deltaY * (event.deltaMode === 1 ? 0.05 : 0.002));\n zoomTo(state.value.scale * factor, local(event));\n }\n // 要能 preventDefault(不让页面跟着滚、不让浏览器整页缩放):不能是 passive\n useEventListener(viewport, 'wheel', onWheel, { passive: false });\n\n function onPointerdown(event: PointerEvent) {\n if (event.button !== 0 || !handles(event)) return;\n pointers.set(event.pointerId, local(event));\n if (pointers.size === 2) {\n const [a, b] = [...pointers.values()] as [Point, Point];\n pinch = { distance: Math.hypot(a.x - b.x, a.y - b.y), state: state.value };\n return;\n }\n const start = state.value;\n startMove(event, {\n threshold: 3,\n onMove: (point, move) => {\n pointers.set(move.pointerId, local(move));\n interacting.value = true;\n if (pinch) return;\n apply({ ...start, x: start.x + point.deltaX, y: start.y + point.deltaY });\n },\n onEnd: () => {\n pointers.clear();\n pinch = undefined;\n interacting.value = false;\n },\n });\n }\n\n // 第二个触点的移动:捏合(第一个触点的移动由 startMove 收到)\n function onPointermove(event: PointerEvent) {\n if (!pinch || !pointers.has(event.pointerId)) return;\n pointers.set(event.pointerId, local(event));\n const [a, b] = [...pointers.values()] as [Point, Point];\n const distance = Math.hypot(a.x - b.x, a.y - b.y);\n const scale = clamp((pinch.state.scale * distance) / pinch.distance, min, max);\n apply(zoomAround(pinch.state, scale, { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 }));\n }\n\n function onPointerup(event: PointerEvent) {\n pointers.delete(event.pointerId);\n if (pointers.size < 2) pinch = undefined;\n }\n\n function onDblclick(event: MouseEvent) {\n if (!handles(event)) return;\n if (state.value.scale > initial().scale) apply(initial());\n else zoomTo(state.value.scale * step * step, local(event));\n }\n\n function onKeydown(event: KeyboardEvent) {\n if (event.altKey || event.ctrlKey || event.metaKey || !handles(event)) return;\n const { x, y } = state.value;\n const actions: Record<string, () => void> = {\n '+': () => zoomTo(state.value.scale * step),\n '=': () => zoomTo(state.value.scale * step),\n '-': () => zoomTo(state.value.scale / step),\n '0': () => apply(initial()),\n ArrowLeft: () => apply({ ...state.value, x: x + KEY_PAN }),\n ArrowRight: () => apply({ ...state.value, x: x - KEY_PAN }),\n ArrowUp: () => apply({ ...state.value, y: y + KEY_PAN }),\n ArrowDown: () => apply({ ...state.value, y: y - KEY_PAN }),\n };\n const action = actions[event.key];\n if (!action) return;\n event.preventDefault();\n action();\n }\n\n const viewportProps = {\n style: { touchAction: 'none', overflow: 'hidden' },\n onPointerdown,\n onPointermove,\n onPointerup,\n onPointercancel: onPointerup,\n onDblclick,\n onKeydown,\n };\n\n return {\n get x() {\n return state.value.x;\n },\n get y() {\n return state.value.y;\n },\n get scale() {\n return state.value.scale;\n },\n get interacting() {\n return interacting.value;\n },\n get transform() {\n const { x, y, scale } = state.value;\n return `translate(${x}px, ${y}px) scale(${scale})`;\n },\n zoomIn: (origin) => zoomTo(state.value.scale * step, origin),\n zoomOut: (origin) => zoomTo(state.value.scale / step, origin),\n zoomTo,\n panTo: (x, y) => apply({ ...state.value, x, y }),\n reset: () => apply(initial()),\n fit(content, padding = 0) {\n state.value = fitTransform(viewportSize(), content, { padding, min, max });\n return state.value.scale;\n },\n viewportProps,\n };\n}\n"],"mappings":"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"}
@@ -1,4 +1,5 @@
1
1
  import "../config/theme/tokens.js";
2
+ import { mergeRefs } from "@zerodep-dev/vue-tsx-pro-tsx";
2
3
  import * as css from "@zerodep-dev/vue-tsx-pro-tsx/styles";
3
4
  //#region src/utils/props.ts
4
5
  /**
@@ -42,7 +43,7 @@ function mergeProps(defaults, user, managed) {
42
43
  if (values) merged[key] = values;
43
44
  }
44
45
  const refs = present3(base.ref, own?.ref, fixed?.ref);
45
- if (refs && refs.length > 1) merged.ref = (element) => refs.forEach((ref) => setRef(ref, element));
46
+ if (refs && refs.length > 1) merged.ref = mergeRefs(refs);
46
47
  if (own) for (const key in own) {
47
48
  if (!isHandlerKey(key)) continue;
48
49
  if (base[key] == null && fixed?.[key] == null) {
@@ -82,14 +83,7 @@ function elementProps(props) {
82
83
  if (sx == null) return rest;
83
84
  const compiled = compileSx(sx);
84
85
  const element = rest;
85
- if (compiled.ref && element.ref != null) {
86
- const own = element.ref;
87
- const bound = compiled.ref;
88
- element.ref = (target) => {
89
- setRef(own, target);
90
- bound(target);
91
- };
92
- } else if (compiled.ref) element.ref = compiled.ref;
86
+ if (compiled.ref) element.ref = element.ref != null ? mergeRefs([element.ref, compiled.ref]) : compiled.ref;
93
87
  if (rest.class == null && rest.style == null) {
94
88
  if (compiled.class != null) element.class = compiled.class;
95
89
  if (compiled.style != null) element.style = compiled.style;
@@ -134,10 +128,6 @@ function mergeHandlers(user, internal) {
134
128
  own.forEach((handler) => handler(...args));
135
129
  };
136
130
  }
137
- function setRef(ref, element) {
138
- if (typeof ref === "function") ref(element);
139
- else ref.value = element;
140
- }
141
131
  var present = (values) => values.filter((value) => value != null);
142
132
  /** 按层排列、要合并成数组的属性 */
143
133
  var LISTED_KEYS = [
@@ -1 +1 @@
1
- {"version":3,"file":"props.js","names":[],"sources":["../../src/utils/props.ts"],"sourcesContent":["/**\n * 部件 props 的类型与合并规则(COMPONENTS.md §2)。\n *\n * 组件给部件的属性分两层:可被使用方覆盖的默认属性(defaults),以及组件状态决定、\n * 不允许覆盖的受管属性(managed,如 role、aria-expanded)。合并规则:\n * - sx:默认在前、使用方其次、受管最后,后者覆盖前者\n * - class、style:依次排列成数组,交给 Vue 统一处理(它们也可能是数组或对象)\n * - 事件 onXxx:使用方先执行,调用了 event.preventDefault() 就跳过组件自己的处理\n * - ref:各方都拿到元素\n * - 其他属性:受管 > 使用方 > 默认\n */\nimport * as css from '@zerodep-dev/vue-tsx-pro-tsx/styles';\nimport type { IntrinsicProps, JSX } from '@zerodep-dev/vue-tsx-pro-tsx/jsx-runtime';\n// 登记主题 tokens(样式里的 '_md' 靠它):每个组件都经过这里合并属性,渲染之前一定登记过\nimport '../config/theme/tokens';\nimport type { StyleAttrs, SxStyles } from '@zerodep-dev/vue-tsx-pro-tsx/styles';\nimport type { Ref, VNodeChild } from 'vue';\n\n/**\n * 原生元素部件的 props:该元素的属性加上 sx;去掉组件管理的属性与 children(内容走组件自己的 prop)。\n * 形如 `propsInput?: PartProps<'input', 'value' | 'aria-invalid'>`。\n */\nexport type PartProps<\n Tag extends keyof JSX.IntrinsicElements,\n Managed extends string = never,\n> = Omit<IntrinsicProps<Tag>, Managed | 'children'> & { sx?: SxStyles };\n\n/**\n * 展开收起类组件(Collapsible、Popover)交给触发器的属性:展开到任意可聚焦元素上,\n * 形如 `trigger={(props) => <Button {...props}>更多</Button>}`。弹层另外加上 id、ref、aria-haspopup。\n */\nexport interface TriggerProps {\n /** 切换展开与收起 */\n onClick: (event: MouseEvent) => void;\n /** 当前是否展开,读屏据此播报“已展开 / 已折叠” */\n 'aria-expanded': boolean;\n /** 被控制的内容区的 id */\n 'aria-controls': string;\n /** 不可用:组件设了 disabled 时为 true,展开到按钮上就是原生的 disabled */\n disabled?: boolean;\n}\n\n/**\n * data-* 属性写进对象字面量(部件属性、mergeAttrs 的各层):元素的属性类型不收 data-* 键,\n * 用它包一层再展开。JSX 上直接写 data-* 不需要它。\n *\n * mergeAttrs<'td'>(defaults, user, { role: 'gridcell', ...dataAttrs({ 'data-in-range': inRange || undefined }) })\n */\nexport const dataAttrs = (attrs: Record<`data-${string}`, string | number | boolean | undefined>) =>\n attrs;\n\ntype Props = Record<string, unknown>;\ntype Handler = (...args: unknown[]) => unknown;\ntype RefSetter = ((element: unknown) => void) | Ref<unknown>;\n\n/**\n * 一层属性:Target 是标签名('svg')时为该元素的属性加 sx,是 props 类型(IconProps)时就是它。\n * 标签名的联合('h1' | 'h2',一个工厂生成的几个组件共用实现时)取这些标签共有的属性,所以这里不按联合分配\n */\ntype PropsLayer<Target> = Partial<\n [Target] extends [keyof JSX.IntrinsicElements] ? PartProps<Target> : Target\n>;\n\n/**\n * 合并部件的三层属性:组件默认、使用方传入、组件受管,规则见文件开头。\n * 用类型参数写明属性落到哪里,三层的对象字面量才有补全、悬停说明与类型检查:\n * mergeProps<IconProps>({ sx: … }, propsIcon);原生元素用 mergeAttrs<'svg'>(默认, rest, 受管)\n *\n * 渲染里每个部件每次都要合并,按最常见的情况走捷径:只有默认层时原样返回;\n * 只有两层以上都给了同名事件时才包一层回调。\n */\nexport function mergeProps<Target = Props>(\n defaults: PropsLayer<Target>,\n user?: PropsLayer<Target>,\n managed?: PropsLayer<Target>,\n): PropsLayer<Target> {\n const base = defaults as Props;\n const own = hasKeys(user) ? (user as Props) : undefined;\n const fixed = hasKeys(managed) ? (managed as Props) : undefined;\n if (!own && !fixed) return defaults;\n const merged: Props = { ...base, ...own, ...fixed };\n\n for (const key of LISTED_KEYS) {\n const values = present3(base[key], own?.[key], fixed?.[key]);\n if (values) merged[key] = values;\n }\n\n const refs = present3(base.ref, own?.ref, fixed?.ref) as RefSetter[] | undefined;\n if (refs && refs.length > 1) {\n merged.ref = (element: unknown) => refs.forEach((ref) => setRef(ref, element));\n }\n\n // 同名事件:使用方的与组件的(默认层、受管层)合并;只有组件两层都有时也要合并\n if (own) {\n for (const key in own) {\n if (!isHandlerKey(key)) continue;\n if (base[key] == null && fixed?.[key] == null) {\n // 受管层写了 onXxx: undefined(这时没有自己的处理)也不盖掉使用方的事件\n merged[key] = own[key];\n continue;\n }\n // 使用方传了 undefined(onClick={cond ? fn : undefined})时也保留组件自己的\n merged[key] =\n mergeHandlers(own[key] as Handler | undefined, [base[key], fixed?.[key]]) ??\n fixed?.[key] ??\n base[key];\n }\n }\n if (fixed) {\n for (const key in fixed) {\n if (!isHandlerKey(key) || base[key] == null || (own && key in own)) continue;\n const handler = mergeHandlers(undefined, [base[key], fixed[key]]);\n if (handler) merged[key] = handler;\n }\n }\n return merged as PropsLayer<Target>;\n}\n\n/**\n * 子组件部件的合并:mergeProps 的规则,但默认层要写齐子组件的必填 props(如 Icon 的 icon),\n * 返回完整的 props 类型,展开到子组件上不必再断言:\n * <Icon {...mergePartProps<IconProps>({ icon: X, sx: styles.icon }, propsIcon)} />\n */\nexport function mergePartProps<P extends object>(\n defaults: P,\n user?: Partial<P>,\n managed?: Partial<P>,\n): P {\n // P 是组件 props 类型,不是标签名:按对象合并,结果的类型就是 P\n return mergeProps(\n defaults as Props,\n user as Props | undefined,\n managed as Props | undefined,\n ) as P;\n}\n\n/**\n * 原生元素部件的合并:mergeProps 的规则,再把 sx 换成 class 与 style(elementProps),结果直接展开到元素上:\n * <svg {...mergeAttrs<'svg'>({ sx: styles.icon }, rest, { 'aria-hidden': true })} />\n */\nexport function mergeAttrs<Tag extends keyof JSX.IntrinsicElements>(\n defaults: PropsLayer<Tag>,\n user?: PropsLayer<Tag>,\n managed?: PropsLayer<Tag>,\n): Omit<PropsLayer<Tag>, 'sx' | 'children'> {\n return elementProps(mergeProps<Tag>(defaults, user, managed));\n}\n\n/**\n * 原生元素部件:把 sx 换成样式引擎生成的 class 与 style,再与已有的 class、style 合并。\n * 去掉 children:它是内容不是元素属性(组件的 ...rest 里带着它,转交给子组件时要用)。\n */\nexport function elementProps<P extends object>(props: P): Omit<P, 'sx' | 'children'> {\n // 解构出的 rest 是新对象:之后直接往上写,不再复制一次(每个元素每次渲染都要走这里)\n const { sx, children: _children, ...rest } = props as P & Props;\n if (sx == null) return rest as Omit<P, 'sx' | 'children'>;\n const compiled = compileSx(sx as SxStyles);\n const element = rest as Props;\n // 响应式绑定的 ref(元素挂上后改变量)与使用方的 ref 都要拿到元素\n if (compiled.ref && element.ref != null) {\n const own = element.ref as RefSetter;\n const bound = compiled.ref;\n element.ref = (target: unknown) => {\n setRef(own, target);\n bound(target);\n };\n } else if (compiled.ref) element.ref = compiled.ref;\n if (rest.class == null && rest.style == null) {\n if (compiled.class != null) element.class = compiled.class;\n if (compiled.style != null) element.style = compiled.style;\n return rest as Omit<P, 'sx' | 'children'>;\n }\n element.class = present3(rest.class, compiled.class) ?? [];\n element.style = present3(compiled.style, rest.style) ?? [];\n return rest as Omit<P, 'sx' | 'children'>;\n}\n\n/**\n * 编译好的样式是不变的常量:单个样式(不是数组)按对象缓存 css.attrs 的结果,\n * 根元素、图标这类每次渲染都是同一个样式的部件不必重复合并\n */\nconst compiledCache = new WeakMap<object, ReturnType<typeof css.attrs>>();\nfunction compileSx(sx: SxStyles) {\n if (Array.isArray(sx) || typeof sx !== 'object' || sx === null) return css.attrs(sx);\n let compiled = compiledCache.get(sx);\n if (!compiled) compiledCache.set(sx, (compiled = css.attrs(sx)));\n return compiled;\n}\n\n/** onClick、onUpdate:_value:on 后面跟大写字母 */\nfunction isHandlerKey(key: string) {\n const third = key.charCodeAt(2);\n return key.charCodeAt(0) === 111 && key.charCodeAt(1) === 110 && third >= 65 && third <= 90;\n}\n\nfunction hasKeys(layer: object | undefined): boolean {\n if (!layer) return false;\n for (const _ in layer) return true;\n return false;\n}\n\n/**\n * 使用方的回调先执行,它调用了 event.preventDefault() 就跳过组件自己的回调。\n * 只有一方提供回调时返回 undefined,保留展开合并的结果即可。\n */\nfunction mergeHandlers(user: Handler | undefined, internal: unknown[]): Handler | undefined {\n const own = present(internal) as Handler[];\n if (own.length + (user ? 1 : 0) < 2) return undefined;\n return (...args) => {\n user?.(...args);\n const event = args[0];\n if (event instanceof Event && event.defaultPrevented) return;\n own.forEach((handler) => handler(...args));\n };\n}\n\nfunction setRef(ref: RefSetter, element: unknown) {\n if (typeof ref === 'function') ref(element);\n else ref.value = element;\n}\n\nconst present = <T>(values: readonly (T | null | undefined)[]) =>\n values.filter((value): value is T => value != null);\n\n/** 按层排列、要合并成数组的属性 */\nconst LISTED_KEYS = ['sx', 'class', 'style'] as const;\n\n/** 最多三个值里不为空的那些,按原顺序成数组;都为空时返回 undefined。渲染路径上用,不建中间数组 */\nfunction present3(a: unknown, b: unknown, c?: unknown): unknown[] | undefined {\n if (a == null) {\n if (b == null) return c == null ? undefined : [c];\n return c == null ? [b] : [b, c];\n }\n if (b == null) return c == null ? [a] : [a, c];\n return c == null ? [a, b] : [a, b, c];\n}\n\n/**\n * 默认的 aria-labelledby(Field 的标签、弹层的触发器):使用方写的 aria-labelledby 优先;使用方写了 aria-label 时\n * 不加默认的(aria-labelledby 会盖过 aria-label,使用方写的名称就白写了)\n */\nexport function defaultLabelledBy(\n attrs: { 'aria-label'?: string; 'aria-labelledby'?: string },\n fallback: string | undefined,\n): string | undefined {\n return attrs['aria-labelledby'] ?? (attrs['aria-label'] == null ? fallback : undefined);\n}\n\n/**\n * 内容(插槽、标题、说明、错误这类 VNodeChild)有没有东西:null、undefined、空字符串与布尔都算没有。\n * 使用方常写 `error={cond && '…'}`、`aside={wide && <Panel />}`,条件不成立时是 false,不该渲染出空的外层\n */\nexport function hasContent(node: VNodeChild): boolean {\n return node != null && typeof node !== 'boolean' && node !== '';\n}\n\n/**\n * 把 css.attrs 的结果写到命令式创建的元素上(波纹、量取主题的探针):class 与行内样式,\n * 自定义属性(动态样式的变量)名字原样、用 setProperty\n */\nexport function applyStyleAttrs(element: HTMLElement, { class: className, style }: StyleAttrs) {\n element.className = className ?? '';\n for (const [name, value] of Object.entries(style ?? {})) {\n const property = name.startsWith('--')\n ? name\n : name.replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`);\n element.style.setProperty(property, String(value));\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"props.js","names":[],"sources":["../../src/utils/props.ts"],"sourcesContent":["/**\n * 部件 props 的类型与合并规则(COMPONENTS.md §2)。\n *\n * 组件给部件的属性分两层:可被使用方覆盖的默认属性(defaults),以及组件状态决定、\n * 不允许覆盖的受管属性(managed,如 role、aria-expanded)。合并规则:\n * - sx:默认在前、使用方其次、受管最后,后者覆盖前者\n * - class、style:依次排列成数组,交给 Vue 统一处理(它们也可能是数组或对象)\n * - 事件 onXxx:使用方先执行,调用了 event.preventDefault() 就跳过组件自己的处理\n * - ref:各方都拿到元素\n * - 其他属性:受管 > 使用方 > 默认\n */\nimport * as css from '@zerodep-dev/vue-tsx-pro-tsx/styles';\nimport type { IntrinsicProps, JSX } from '@zerodep-dev/vue-tsx-pro-tsx/jsx-runtime';\n// 登记主题 tokens(样式里的 '_md' 靠它):每个组件都经过这里合并属性,渲染之前一定登记过\nimport '../config/theme/tokens';\nimport type { StyleAttrs, SxStyles } from '@zerodep-dev/vue-tsx-pro-tsx/styles';\nimport { mergeRefs, type RefSetter } from '@zerodep-dev/vue-tsx-pro-tsx';\nimport type { VNodeChild } from 'vue';\n\n/**\n * 原生元素部件的 props:该元素的属性加上 sx;去掉组件管理的属性与 children(内容走组件自己的 prop)。\n * 形如 `propsInput?: PartProps<'input', 'value' | 'aria-invalid'>`。\n */\nexport type PartProps<\n Tag extends keyof JSX.IntrinsicElements,\n Managed extends string = never,\n> = Omit<IntrinsicProps<Tag>, Managed | 'children'> & { sx?: SxStyles };\n\n/**\n * 展开收起类组件(Collapsible、Popover)交给触发器的属性:展开到任意可聚焦元素上,\n * 形如 `trigger={(props) => <Button {...props}>更多</Button>}`。弹层另外加上 id、ref、aria-haspopup。\n */\nexport interface TriggerProps {\n /** 切换展开与收起 */\n onClick: (event: MouseEvent) => void;\n /** 当前是否展开,读屏据此播报“已展开 / 已折叠” */\n 'aria-expanded': boolean;\n /** 被控制的内容区的 id */\n 'aria-controls': string;\n /** 不可用:组件设了 disabled 时为 true,展开到按钮上就是原生的 disabled */\n disabled?: boolean;\n}\n\n/**\n * data-* 属性写进对象字面量(部件属性、mergeAttrs 的各层):元素的属性类型不收 data-* 键,\n * 用它包一层再展开。JSX 上直接写 data-* 不需要它。\n *\n * mergeAttrs<'td'>(defaults, user, { role: 'gridcell', ...dataAttrs({ 'data-in-range': inRange || undefined }) })\n */\nexport const dataAttrs = (attrs: Record<`data-${string}`, string | number | boolean | undefined>) =>\n attrs;\n\ntype Props = Record<string, unknown>;\ntype Handler = (...args: unknown[]) => unknown;\n\n/**\n * 一层属性:Target 是标签名('svg')时为该元素的属性加 sx,是 props 类型(IconProps)时就是它。\n * 标签名的联合('h1' | 'h2',一个工厂生成的几个组件共用实现时)取这些标签共有的属性,所以这里不按联合分配\n */\ntype PropsLayer<Target> = Partial<\n [Target] extends [keyof JSX.IntrinsicElements] ? PartProps<Target> : Target\n>;\n\n/**\n * 合并部件的三层属性:组件默认、使用方传入、组件受管,规则见文件开头。\n * 用类型参数写明属性落到哪里,三层的对象字面量才有补全、悬停说明与类型检查:\n * mergeProps<IconProps>({ sx: … }, propsIcon);原生元素用 mergeAttrs<'svg'>(默认, rest, 受管)\n *\n * 渲染里每个部件每次都要合并,按最常见的情况走捷径:只有默认层时原样返回;\n * 只有两层以上都给了同名事件时才包一层回调。\n */\nexport function mergeProps<Target = Props>(\n defaults: PropsLayer<Target>,\n user?: PropsLayer<Target>,\n managed?: PropsLayer<Target>,\n): PropsLayer<Target> {\n const base = defaults as Props;\n const own = hasKeys(user) ? (user as Props) : undefined;\n const fixed = hasKeys(managed) ? (managed as Props) : undefined;\n if (!own && !fixed) return defaults;\n const merged: Props = { ...base, ...own, ...fixed };\n\n for (const key of LISTED_KEYS) {\n const values = present3(base[key], own?.[key], fixed?.[key]);\n if (values) merged[key] = values;\n }\n\n const refs = present3(base.ref, own?.ref, fixed?.ref) as RefSetter[] | undefined;\n if (refs && refs.length > 1) merged.ref = mergeRefs(refs);\n\n // 同名事件:使用方的与组件的(默认层、受管层)合并;只有组件两层都有时也要合并\n if (own) {\n for (const key in own) {\n if (!isHandlerKey(key)) continue;\n if (base[key] == null && fixed?.[key] == null) {\n // 受管层写了 onXxx: undefined(这时没有自己的处理)也不盖掉使用方的事件\n merged[key] = own[key];\n continue;\n }\n // 使用方传了 undefined(onClick={cond ? fn : undefined})时也保留组件自己的\n merged[key] =\n mergeHandlers(own[key] as Handler | undefined, [base[key], fixed?.[key]]) ??\n fixed?.[key] ??\n base[key];\n }\n }\n if (fixed) {\n for (const key in fixed) {\n if (!isHandlerKey(key) || base[key] == null || (own && key in own)) continue;\n const handler = mergeHandlers(undefined, [base[key], fixed[key]]);\n if (handler) merged[key] = handler;\n }\n }\n return merged as PropsLayer<Target>;\n}\n\n/**\n * 子组件部件的合并:mergeProps 的规则,但默认层要写齐子组件的必填 props(如 Icon 的 icon),\n * 返回完整的 props 类型,展开到子组件上不必再断言:\n * <Icon {...mergePartProps<IconProps>({ icon: X, sx: styles.icon }, propsIcon)} />\n */\nexport function mergePartProps<P extends object>(\n defaults: P,\n user?: Partial<P>,\n managed?: Partial<P>,\n): P {\n // P 是组件 props 类型,不是标签名:按对象合并,结果的类型就是 P\n return mergeProps(\n defaults as Props,\n user as Props | undefined,\n managed as Props | undefined,\n ) as P;\n}\n\n/**\n * 原生元素部件的合并:mergeProps 的规则,再把 sx 换成 class 与 style(elementProps),结果直接展开到元素上:\n * <svg {...mergeAttrs<'svg'>({ sx: styles.icon }, rest, { 'aria-hidden': true })} />\n */\nexport function mergeAttrs<Tag extends keyof JSX.IntrinsicElements>(\n defaults: PropsLayer<Tag>,\n user?: PropsLayer<Tag>,\n managed?: PropsLayer<Tag>,\n): Omit<PropsLayer<Tag>, 'sx' | 'children'> {\n return elementProps(mergeProps<Tag>(defaults, user, managed));\n}\n\n/**\n * 原生元素部件:把 sx 换成样式引擎生成的 class 与 style,再与已有的 class、style 合并。\n * 去掉 children:它是内容不是元素属性(组件的 ...rest 里带着它,转交给子组件时要用)。\n */\nexport function elementProps<P extends object>(props: P): Omit<P, 'sx' | 'children'> {\n // 解构出的 rest 是新对象:之后直接往上写,不再复制一次(每个元素每次渲染都要走这里)\n const { sx, children: _children, ...rest } = props as P & Props;\n if (sx == null) return rest as Omit<P, 'sx' | 'children'>;\n const compiled = compileSx(sx as SxStyles);\n const element = rest as Props;\n // 响应式绑定的 ref(元素挂上后改变量)与使用方的 ref 都要拿到元素\n if (compiled.ref) {\n element.ref =\n element.ref != null ? mergeRefs([element.ref as RefSetter, compiled.ref]) : compiled.ref;\n }\n if (rest.class == null && rest.style == null) {\n if (compiled.class != null) element.class = compiled.class;\n if (compiled.style != null) element.style = compiled.style;\n return rest as Omit<P, 'sx' | 'children'>;\n }\n element.class = present3(rest.class, compiled.class) ?? [];\n element.style = present3(compiled.style, rest.style) ?? [];\n return rest as Omit<P, 'sx' | 'children'>;\n}\n\n/**\n * 编译好的样式是不变的常量:单个样式(不是数组)按对象缓存 css.attrs 的结果,\n * 根元素、图标这类每次渲染都是同一个样式的部件不必重复合并\n */\nconst compiledCache = new WeakMap<object, ReturnType<typeof css.attrs>>();\nfunction compileSx(sx: SxStyles) {\n if (Array.isArray(sx) || typeof sx !== 'object' || sx === null) return css.attrs(sx);\n let compiled = compiledCache.get(sx);\n if (!compiled) compiledCache.set(sx, (compiled = css.attrs(sx)));\n return compiled;\n}\n\n/** onClick、onUpdate:_value:on 后面跟大写字母 */\nfunction isHandlerKey(key: string) {\n const third = key.charCodeAt(2);\n return key.charCodeAt(0) === 111 && key.charCodeAt(1) === 110 && third >= 65 && third <= 90;\n}\n\nfunction hasKeys(layer: object | undefined): boolean {\n if (!layer) return false;\n for (const _ in layer) return true;\n return false;\n}\n\n/**\n * 使用方的回调先执行,它调用了 event.preventDefault() 就跳过组件自己的回调。\n * 只有一方提供回调时返回 undefined,保留展开合并的结果即可。\n */\nfunction mergeHandlers(user: Handler | undefined, internal: unknown[]): Handler | undefined {\n const own = present(internal) as Handler[];\n if (own.length + (user ? 1 : 0) < 2) return undefined;\n return (...args) => {\n user?.(...args);\n const event = args[0];\n if (event instanceof Event && event.defaultPrevented) return;\n own.forEach((handler) => handler(...args));\n };\n}\n\nconst present = <T>(values: readonly (T | null | undefined)[]) =>\n values.filter((value): value is T => value != null);\n\n/** 按层排列、要合并成数组的属性 */\nconst LISTED_KEYS = ['sx', 'class', 'style'] as const;\n\n/** 最多三个值里不为空的那些,按原顺序成数组;都为空时返回 undefined。渲染路径上用,不建中间数组 */\nfunction present3(a: unknown, b: unknown, c?: unknown): unknown[] | undefined {\n if (a == null) {\n if (b == null) return c == null ? undefined : [c];\n return c == null ? [b] : [b, c];\n }\n if (b == null) return c == null ? [a] : [a, c];\n return c == null ? [a, b] : [a, b, c];\n}\n\n/**\n * 默认的 aria-labelledby(Field 的标签、弹层的触发器):使用方写的 aria-labelledby 优先;使用方写了 aria-label 时\n * 不加默认的(aria-labelledby 会盖过 aria-label,使用方写的名称就白写了)\n */\nexport function defaultLabelledBy(\n attrs: { 'aria-label'?: string; 'aria-labelledby'?: string },\n fallback: string | undefined,\n): string | undefined {\n return attrs['aria-labelledby'] ?? (attrs['aria-label'] == null ? fallback : undefined);\n}\n\n/**\n * 内容(插槽、标题、说明、错误这类 VNodeChild)有没有东西:null、undefined、空字符串与布尔都算没有。\n * 使用方常写 `error={cond && '…'}`、`aside={wide && <Panel />}`,条件不成立时是 false,不该渲染出空的外层\n */\nexport function hasContent(node: VNodeChild): boolean {\n return node != null && typeof node !== 'boolean' && node !== '';\n}\n\n/**\n * 把 css.attrs 的结果写到命令式创建的元素上(波纹、量取主题的探针):class 与行内样式,\n * 自定义属性(动态样式的变量)名字原样、用 setProperty\n */\nexport function applyStyleAttrs(element: HTMLElement, { class: className, style }: StyleAttrs) {\n element.className = className ?? '';\n for (const [name, value] of Object.entries(style ?? {})) {\n const property = name.startsWith('--')\n ? name\n : name.replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`);\n element.style.setProperty(property, String(value));\n }\n}\n"],"mappings":"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"}
@@ -24,6 +24,18 @@ export interface HttpRequest {
24
24
  timeout?: number;
25
25
  /** 怎样读响应体,默认按 content-type:JSON 解析,text/* 读成文字,其余读成 Blob */
26
26
  responseType?: 'json' | 'text' | 'blob';
27
+ /**
28
+ * 这一次的请求体、响应按不按线上的 typed JSON,盖过客户端的 wire:false 时请求体按普通 JSON 发(里面不能有 bigint)、
29
+ * 响应不还原;true 时按 typed JSON(客户端 wire: false 时还原选项是默认的 `{}`)。没给的一头跟着客户端
30
+ */
31
+ wire?: HttpWire;
32
+ }
33
+ /** 一次请求的请求体(request)、响应(response)各自按不按 typed JSON */
34
+ export interface HttpWire {
35
+ /** 请求体写成 typed JSON */
36
+ request?: boolean;
37
+ /** 响应按 `$types` 还原 */
38
+ response?: boolean;
27
39
  }
28
40
  /** 组件依赖的请求接口:发出请求,返回解析(拆包)后的数据;失败时拒绝 */
29
41
  export interface HttpClient {
@@ -10,6 +10,7 @@ import { fromWire, toWire, wireText } from "./wire.js";
10
10
  *
11
11
  * 值按线上的 typed JSON 传(见 wire.ts):请求体里的 bigint、Decimal、CalendarDate 写成文字并附上 `$types`,
12
12
  * 地址栏参数写成同样的文字,带 `$types` 的响应先还原再交给 onResponse;`wire: false` 时按普通 JSON。
13
+ * 单个接口不一样时(对接只认普通 JSON 的外部系统)在这次请求上给 `wire: { request: false }`、`{ response: false }`,两头分开定。
13
14
  *
14
15
  * 响应体按业务错误码的约定拆开(flow 仓库 docs/decisions/0008,query、flow 的后端都这样回):HTTP 一律 200,
15
16
  * `{ code: 'ok', data }` 交出 data,`{ code: 'flow.work-item-not-open', message, params, errors }` 抛带这个码的 HttpError;
@@ -73,9 +74,12 @@ var isAbortError = (error) => error instanceof DOMException && error.name === "A
73
74
  /** 原生 fetch 的薄封装(见文件开头) */
74
75
  function createHttpClient(options = {}) {
75
76
  const { baseURL = "", timeout: defaultTimeout = 3e4, onRequest, onResponse, wire = {}, envelope: unwrap = true } = options;
76
- const revive = (value) => wire === false ? value : fromWire(value, wire);
77
+ const reviveOptions = wire === false ? {} : wire;
77
78
  async function request(spec) {
78
79
  const { method = "GET", body, signal, timeout = defaultTimeout } = spec;
80
+ const sendWire = spec.wire?.request ?? wire !== false;
81
+ const readWire = spec.wire?.response ?? wire !== false;
82
+ const revive = (value) => readWire ? fromWire(value, reviveOptions) : value;
79
83
  const headers = new Headers({
80
84
  ...options.headers,
81
85
  ...spec.headers
@@ -86,7 +90,7 @@ function createHttpClient(options = {}) {
86
90
  let prepared = new Request(buildUrl(baseURL, spec.url, spec.query), {
87
91
  method,
88
92
  headers,
89
- body: json ? JSON.stringify(wire === false ? body : toWire(body)) : body,
93
+ body: json ? JSON.stringify(sendWire ? toWire(body) : body) : body,
90
94
  signal: signals.length > 1 ? AbortSignal.any(signals) : signals[0]
91
95
  });
92
96
  prepared = await onRequest?.(prepared) ?? prepared;