@zerodep-dev/vue-tsx-pro-ui 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/base/Badge.js +3 -3
- package/dist/base/Badge.js.map +1 -1
- package/dist/base/Can.d.ts +12 -0
- package/dist/base/Can.js +20 -0
- package/dist/base/Can.js.map +1 -0
- package/dist/base/ClientOnly.d.ts +10 -0
- package/dist/base/ClientOnly.js +18 -0
- package/dist/base/ClientOnly.js.map +1 -0
- package/dist/base/CloseButton.js +3 -3
- package/dist/base/CloseButton.js.map +1 -1
- package/dist/base/CodeBlock.js +5 -5
- package/dist/base/CodeBlock.js.map +1 -1
- package/dist/base/CopyButton.js +3 -3
- package/dist/base/CopyButton.js.map +1 -1
- package/dist/base/Icon.js +3 -3
- package/dist/base/Icon.js.map +1 -1
- package/dist/base/Indicator.js +3 -3
- package/dist/base/Indicator.js.map +1 -1
- package/dist/base/StatusDot.js +3 -3
- package/dist/base/StatusDot.js.map +1 -1
- package/dist/base/Tag.js +5 -5
- package/dist/base/Tag.js.map +1 -1
- package/dist/base/button-core.js +4 -4
- package/dist/base/button-core.js.map +1 -1
- package/dist/charts/AreaChart.js +3 -3
- package/dist/charts/AreaChart.js.map +1 -1
- package/dist/charts/BarChart.js +3 -3
- package/dist/charts/BarChart.js.map +1 -1
- package/dist/charts/ChordChart.js +3 -3
- package/dist/charts/ChordChart.js.map +1 -1
- package/dist/charts/ComboChart.js +3 -3
- package/dist/charts/ComboChart.js.map +1 -1
- package/dist/charts/DonutChart.js +3 -3
- package/dist/charts/DonutChart.js.map +1 -1
- package/dist/charts/FunnelChart.js +5 -5
- package/dist/charts/FunnelChart.js.map +1 -1
- package/dist/charts/GraphChart.js +3 -3
- package/dist/charts/GraphChart.js.map +1 -1
- package/dist/charts/LineChart.js +3 -3
- package/dist/charts/LineChart.js.map +1 -1
- package/dist/charts/MapChart.js +7 -7
- package/dist/charts/MapChart.js.map +1 -1
- package/dist/charts/RadarChart.js +16 -15
- package/dist/charts/RadarChart.js.map +1 -1
- package/dist/charts/SankeyChart.js +3 -3
- package/dist/charts/SankeyChart.js.map +1 -1
- package/dist/charts/ScatterChart.js +3 -3
- package/dist/charts/ScatterChart.js.map +1 -1
- package/dist/charts/TimelineChart.js +6 -5
- package/dist/charts/TimelineChart.js.map +1 -1
- package/dist/charts/TreemapChart.js +3 -3
- package/dist/charts/TreemapChart.js.map +1 -1
- package/dist/charts/WaterfallChart.js +3 -3
- package/dist/charts/WaterfallChart.js.map +1 -1
- package/dist/charts/XYChart.js +9 -7
- package/dist/charts/XYChart.js.map +1 -1
- package/dist/charts/frame.js +10 -8
- package/dist/charts/frame.js.map +1 -1
- package/dist/charts/single.js +4 -3
- package/dist/charts/single.js.map +1 -1
- package/dist/code-editor/CodeEditor.d.ts +18 -0
- package/dist/code-editor/CodeEditor.js +57 -0
- package/dist/code-editor/CodeEditor.js.map +1 -0
- package/dist/code-editor/editor.d.ts +52 -0
- package/dist/code-editor/editor.js +225 -0
- package/dist/code-editor/editor.js.map +1 -0
- package/dist/code-editor/index.d.ts +9 -0
- package/dist/code-editor/index.js +3 -0
- package/dist/config/Provider.d.ts +3 -1
- package/dist/config/Provider.js +9 -4
- package/dist/config/Provider.js.map +1 -1
- package/dist/config/context.d.ts +18 -0
- package/dist/config/context.js +16 -1
- package/dist/config/context.js.map +1 -1
- package/dist/config/createUi.d.ts +20 -3
- package/dist/config/createUi.js +80 -49
- package/dist/config/createUi.js.map +1 -1
- package/dist/config/lang/en-US.js +21 -3
- package/dist/config/lang/en-US.js.map +1 -1
- package/dist/config/lang/format.d.ts +2 -1
- package/dist/config/lang/format.js +9 -2
- package/dist/config/lang/format.js.map +1 -1
- package/dist/config/lang/lang.d.ts +19 -6
- package/dist/config/lang/lang.js +11 -2
- package/dist/config/lang/lang.js.map +1 -1
- package/dist/config/lang/zh-CN.js +21 -3
- package/dist/config/lang/zh-CN.js.map +1 -1
- package/dist/config/layout/preference.d.ts +43 -0
- package/dist/config/layout/preference.js +108 -0
- package/dist/config/layout/preference.js.map +1 -0
- package/dist/date/Calendar.js +3 -3
- package/dist/date/Calendar.js.map +1 -1
- package/dist/date/CalendarBoard.js +9 -9
- package/dist/date/CalendarBoard.js.map +1 -1
- package/dist/date/CalendarView.js +7 -7
- package/dist/date/CalendarView.js.map +1 -1
- package/dist/date/DateField.js +3 -3
- package/dist/date/DateField.js.map +1 -1
- package/dist/date/DatePicker.js +5 -5
- package/dist/date/DatePicker.js.map +1 -1
- package/dist/date/DateRangePicker.js +5 -5
- package/dist/date/DateRangePicker.js.map +1 -1
- package/dist/date/DateSegmentGroup.js +5 -5
- package/dist/date/DateSegmentGroup.js.map +1 -1
- package/dist/date/QuarterCalendar.js +6 -6
- package/dist/date/QuarterCalendar.js.map +1 -1
- package/dist/date/RangeCalendar.js +3 -3
- package/dist/date/RangeCalendar.js.map +1 -1
- package/dist/date/SegmentedField.js +3 -3
- package/dist/date/SegmentedField.js.map +1 -1
- package/dist/date/TimeField.js +3 -3
- package/dist/date/TimeField.js.map +1 -1
- package/dist/date/TimePicker.js +4 -4
- package/dist/date/TimePicker.js.map +1 -1
- package/dist/date/date-segments.js +1 -1
- package/dist/date/date-segments.js.map +1 -1
- package/dist/date/picker-popover.js +3 -2
- package/dist/date/picker-popover.js.map +1 -1
- package/dist/diagram/Diagram.js +12 -12
- package/dist/diagram/Diagram.js.map +1 -1
- package/dist/diagram/DiagramLanes.js +3 -3
- package/dist/diagram/DiagramLanes.js.map +1 -1
- package/dist/diagram/DiagramMinimap.js +4 -4
- package/dist/diagram/DiagramMinimap.js.map +1 -1
- package/dist/diagram/index.d.ts +2 -1
- package/dist/diagram/index.js +2 -1
- package/dist/diagram/layout.d.ts +57 -0
- package/dist/diagram/layout.js +50 -0
- package/dist/diagram/layout.js.map +1 -0
- package/dist/display/Accordion.js +6 -6
- package/dist/display/Accordion.js.map +1 -1
- package/dist/display/AnimatedNumber.js +4 -4
- package/dist/display/AnimatedNumber.js.map +1 -1
- package/dist/display/Avatar.js +4 -4
- package/dist/display/Avatar.js.map +1 -1
- package/dist/display/Checklist.js +7 -7
- package/dist/display/Checklist.js.map +1 -1
- package/dist/display/Collapse.js +8 -5
- package/dist/display/Collapse.js.map +1 -1
- package/dist/display/Collapsible.js +3 -3
- package/dist/display/Collapsible.js.map +1 -1
- package/dist/display/Descriptions.js +5 -5
- package/dist/display/Descriptions.js.map +1 -1
- package/dist/display/Ellipsis.js +3 -3
- package/dist/display/Ellipsis.js.map +1 -1
- package/dist/display/FieldCompare.js +4 -4
- package/dist/display/FieldCompare.js.map +1 -1
- package/dist/display/Gauge.js +11 -11
- package/dist/display/Gauge.js.map +1 -1
- package/dist/display/Image.js +4 -4
- package/dist/display/Image.js.map +1 -1
- package/dist/display/Marquee.js +3 -3
- package/dist/display/Marquee.js.map +1 -1
- package/dist/display/ProportionBar.js +4 -4
- package/dist/display/ProportionBar.js.map +1 -1
- package/dist/display/Sortable.js +4 -4
- package/dist/display/Sortable.js.map +1 -1
- package/dist/display/Sparkline.js +6 -6
- package/dist/display/Sparkline.js.map +1 -1
- package/dist/display/Stat.js +3 -3
- package/dist/display/Stat.js.map +1 -1
- package/dist/display/Timeline.js +7 -7
- package/dist/display/Timeline.js.map +1 -1
- package/dist/display/Tree.js +10 -10
- package/dist/display/Tree.js.map +1 -1
- package/dist/display/VirtualList.js +7 -7
- package/dist/display/VirtualList.js.map +1 -1
- package/dist/display/Watermark.js +3 -3
- package/dist/display/Watermark.js.map +1 -1
- package/dist/docs-kit/CodeGroup.js +5 -5
- package/dist/docs-kit/CodeGroup.js.map +1 -1
- package/dist/docs-kit/CodePreview.js +6 -6
- package/dist/docs-kit/CodePreview.js.map +1 -1
- package/dist/docs-kit/PropsTable.js +4 -4
- package/dist/docs-kit/PropsTable.js.map +1 -1
- package/dist/feedback/Alert.js +5 -5
- package/dist/feedback/Alert.js.map +1 -1
- package/dist/feedback/Banner.js +5 -5
- package/dist/feedback/Banner.js.map +1 -1
- package/dist/feedback/Empty.js +4 -4
- package/dist/feedback/Empty.js.map +1 -1
- package/dist/feedback/LoadingBar.js +20 -13
- package/dist/feedback/LoadingBar.js.map +1 -1
- package/dist/feedback/LoadingOverlay.js +3 -3
- package/dist/feedback/LoadingOverlay.js.map +1 -1
- package/dist/feedback/Progress.js +14 -14
- package/dist/feedback/Progress.js.map +1 -1
- package/dist/feedback/Result.js +4 -4
- package/dist/feedback/Result.js.map +1 -1
- package/dist/feedback/SkeletonText.js +3 -3
- package/dist/feedback/SkeletonText.js.map +1 -1
- package/dist/feedback/Toast.js +6 -6
- package/dist/feedback/Toast.js.map +1 -1
- package/dist/form/Field.d.ts +7 -0
- package/dist/form/Field.js +20 -9
- package/dist/form/Field.js.map +1 -1
- package/dist/form/Form.js +3 -3
- package/dist/form/Form.js.map +1 -1
- package/dist/form/FormErrorSummary.js +3 -3
- package/dist/form/FormErrorSummary.js.map +1 -1
- package/dist/form/FormSection.js +6 -6
- package/dist/form/FormSection.js.map +1 -1
- package/dist/form/useForm.d.ts +4 -1
- package/dist/form/useForm.js +35 -13
- package/dist/form/useForm.js.map +1 -1
- package/dist/index.d.ts +18 -10
- package/dist/index.js +17 -9
- package/dist/input/Autocomplete.js +3 -3
- package/dist/input/Autocomplete.js.map +1 -1
- package/dist/input/Cascader.js +7 -7
- package/dist/input/Cascader.js.map +1 -1
- package/dist/input/CharacterCount.js +3 -3
- package/dist/input/CharacterCount.js.map +1 -1
- package/dist/input/Checkbox.js +5 -5
- package/dist/input/Checkbox.js.map +1 -1
- package/dist/input/ColorField.js +4 -4
- package/dist/input/ColorField.js.map +1 -1
- package/dist/input/Combobox.js +5 -5
- package/dist/input/Combobox.js.map +1 -1
- package/dist/input/ConditionBuilder.js +15 -14
- package/dist/input/ConditionBuilder.js.map +1 -1
- package/dist/input/FileDrop.js +3 -3
- package/dist/input/FileDrop.js.map +1 -1
- package/dist/input/Listbox.js +9 -9
- package/dist/input/Listbox.js.map +1 -1
- package/dist/input/Mentions.js +4 -4
- package/dist/input/Mentions.js.map +1 -1
- package/dist/input/NumberField.js +3 -3
- package/dist/input/NumberField.js.map +1 -1
- package/dist/input/NumberRangeField.js +5 -5
- package/dist/input/NumberRangeField.js.map +1 -1
- package/dist/input/OtpField.js +5 -5
- package/dist/input/OtpField.js.map +1 -1
- package/dist/input/PeoplePicker.js +6 -6
- package/dist/input/PeoplePicker.js.map +1 -1
- package/dist/input/PeoplePickerPanel.js +15 -15
- package/dist/input/PeoplePickerPanel.js.map +1 -1
- package/dist/input/Radio.js +4 -4
- package/dist/input/Radio.js.map +1 -1
- package/dist/input/Rating.js +5 -5
- package/dist/input/Rating.js.map +1 -1
- package/dist/input/Select.js +5 -5
- package/dist/input/Select.js.map +1 -1
- package/dist/input/SelectedTags.js +4 -4
- package/dist/input/SelectedTags.js.map +1 -1
- package/dist/input/Slider.js +6 -6
- package/dist/input/Slider.js.map +1 -1
- package/dist/input/Switch.js +3 -3
- package/dist/input/Switch.js.map +1 -1
- package/dist/input/TagsInput.js +5 -5
- package/dist/input/TagsInput.js.map +1 -1
- package/dist/input/TextField.js +5 -5
- package/dist/input/TextField.js.map +1 -1
- package/dist/input/Textarea.js +4 -4
- package/dist/input/Textarea.js.map +1 -1
- package/dist/input/ToggleGroup.js +4 -4
- package/dist/input/ToggleGroup.js.map +1 -1
- package/dist/input/Transfer.js +8 -8
- package/dist/input/Transfer.js.map +1 -1
- package/dist/input/TreeSelect.js +7 -7
- package/dist/input/TreeSelect.js.map +1 -1
- package/dist/input/Upload.js +10 -9
- package/dist/input/Upload.js.map +1 -1
- package/dist/input/condition.d.ts +1 -1
- package/dist/input/condition.js +32 -14
- package/dist/input/condition.js.map +1 -1
- package/dist/input/people.d.ts +15 -14
- package/dist/input/people.js +5 -5
- package/dist/input/people.js.map +1 -1
- package/dist/input/remote-items.d.ts +4 -2
- package/dist/input/remote-items.js +7 -2
- package/dist/input/remote-items.js.map +1 -1
- package/dist/layout/Affix.js +4 -4
- package/dist/layout/Affix.js.map +1 -1
- package/dist/layout/ResizeHandle.d.ts +6 -2
- package/dist/layout/ResizeHandle.js +32 -9
- package/dist/layout/ResizeHandle.js.map +1 -1
- package/dist/layout/ScrollArea.js +3 -3
- package/dist/layout/ScrollArea.js.map +1 -1
- package/dist/layout/Separator.js +3 -3
- package/dist/layout/Separator.js.map +1 -1
- package/dist/layout/Splitter.js +3 -3
- package/dist/layout/Splitter.js.map +1 -1
- package/dist/layout/ViewportFill.d.ts +2 -2
- package/dist/layout/ViewportFill.js +4 -4
- package/dist/layout/ViewportFill.js.map +1 -1
- package/dist/navigation/Breadcrumbs.js +4 -4
- package/dist/navigation/Breadcrumbs.js.map +1 -1
- package/dist/navigation/NavBar.js +6 -6
- package/dist/navigation/NavBar.js.map +1 -1
- package/dist/navigation/NavList.d.ts +1 -0
- package/dist/navigation/NavList.js +20 -12
- package/dist/navigation/NavList.js.map +1 -1
- package/dist/navigation/NavMenu.d.ts +27 -0
- package/dist/navigation/NavMenu.js +54 -0
- package/dist/navigation/NavMenu.js.map +1 -0
- package/dist/navigation/PageJump.js +3 -3
- package/dist/navigation/PageJump.js.map +1 -1
- package/dist/navigation/PageSizeSelect.js +4 -4
- package/dist/navigation/PageSizeSelect.js.map +1 -1
- package/dist/navigation/Pagination.js +6 -6
- package/dist/navigation/Pagination.js.map +1 -1
- package/dist/navigation/PaginationBar.js +5 -5
- package/dist/navigation/PaginationBar.js.map +1 -1
- package/dist/navigation/Steps.js +4 -4
- package/dist/navigation/Steps.js.map +1 -1
- package/dist/navigation/TableOfContents.js +5 -5
- package/dist/navigation/TableOfContents.js.map +1 -1
- package/dist/navigation/Tabs.js +7 -7
- package/dist/navigation/Tabs.js.map +1 -1
- package/dist/navigation/Toolbar.js +3 -3
- package/dist/navigation/Toolbar.js.map +1 -1
- package/dist/overlay/AlertDialog.js +4 -4
- package/dist/overlay/AlertDialog.js.map +1 -1
- package/dist/overlay/CommandPalette.js +4 -4
- package/dist/overlay/CommandPalette.js.map +1 -1
- package/dist/overlay/ContextMenu.js +3 -3
- package/dist/overlay/ContextMenu.js.map +1 -1
- package/dist/overlay/Dialog.js +4 -4
- package/dist/overlay/Dialog.js.map +1 -1
- package/dist/overlay/Drawer.js +5 -5
- package/dist/overlay/Drawer.js.map +1 -1
- package/dist/overlay/HoverCard.js +5 -3
- package/dist/overlay/HoverCard.js.map +1 -1
- package/dist/overlay/Lightbox.js +5 -5
- package/dist/overlay/Lightbox.js.map +1 -1
- package/dist/overlay/Menu.js +7 -7
- package/dist/overlay/Menu.js.map +1 -1
- package/dist/overlay/ModalCore.js +9 -9
- package/dist/overlay/ModalCore.js.map +1 -1
- package/dist/overlay/Popconfirm.js +6 -6
- package/dist/overlay/Popconfirm.js.map +1 -1
- package/dist/overlay/Popover.js +5 -3
- package/dist/overlay/Popover.js.map +1 -1
- package/dist/overlay/Tooltip.js +5 -3
- package/dist/overlay/Tooltip.js.map +1 -1
- package/dist/overlay/Tour.js +8 -8
- package/dist/overlay/Tour.js.map +1 -1
- package/dist/overlay/detached.d.ts +2 -0
- package/dist/overlay/detached.js +73 -42
- package/dist/overlay/detached.js.map +1 -1
- package/dist/patterns/ActionBar.js +3 -3
- package/dist/patterns/ActionBar.js.map +1 -1
- package/dist/patterns/AppShell.d.ts +28 -1
- package/dist/patterns/AppShell.js +158 -52
- package/dist/patterns/AppShell.js.map +1 -1
- package/dist/patterns/AuthLayout.d.ts +28 -0
- package/dist/patterns/AuthLayout.js +77 -0
- package/dist/patterns/AuthLayout.js.map +1 -0
- package/dist/patterns/CommentThread.js +9 -9
- package/dist/patterns/CommentThread.js.map +1 -1
- package/dist/patterns/DataImport.js +4 -4
- package/dist/patterns/DataImport.js.map +1 -1
- package/dist/patterns/DeadlineBadge.js +3 -3
- package/dist/patterns/DeadlineBadge.js.map +1 -1
- package/dist/patterns/FilterBar.d.ts +11 -1
- package/dist/patterns/FilterBar.js +39 -10
- package/dist/patterns/FilterBar.js.map +1 -1
- package/dist/patterns/FormDialog.d.ts +18 -0
- package/dist/patterns/FormDialog.js +46 -0
- package/dist/patterns/FormDialog.js.map +1 -0
- package/dist/patterns/KanbanBoard.js +8 -8
- package/dist/patterns/KanbanBoard.js.map +1 -1
- package/dist/patterns/LayoutSwitcher.d.ts +12 -0
- package/dist/patterns/LayoutSwitcher.js +79 -0
- package/dist/patterns/LayoutSwitcher.js.map +1 -0
- package/dist/patterns/NotificationCenter.js +8 -8
- package/dist/patterns/NotificationCenter.js.map +1 -1
- package/dist/patterns/PageTabs.d.ts +12 -7
- package/dist/patterns/PageTabs.js +41 -29
- package/dist/patterns/PageTabs.js.map +1 -1
- package/dist/patterns/QueryTable.d.ts +10 -1
- package/dist/patterns/QueryTable.js +128 -68
- package/dist/patterns/QueryTable.js.map +1 -1
- package/dist/patterns/SlideCaptcha.d.ts +80 -0
- package/dist/patterns/SlideCaptcha.js +354 -0
- package/dist/patterns/SlideCaptcha.js.map +1 -0
- package/dist/patterns/StepsForm.js +7 -7
- package/dist/patterns/StepsForm.js.map +1 -1
- package/dist/patterns/ThemeSwitcher.js +3 -3
- package/dist/patterns/ThemeSwitcher.js.map +1 -1
- package/dist/patterns/index.d.ts +7 -3
- package/dist/patterns/index.js +5 -1
- package/dist/patterns/messages.d.ts +39 -1
- package/dist/patterns/messages.js +40 -2
- package/dist/patterns/messages.js.map +1 -1
- package/dist/table/ColumnHeaderActions.js +3 -3
- package/dist/table/ColumnHeaderActions.js.map +1 -1
- package/dist/table/ColumnResizer.js +3 -3
- package/dist/table/ColumnResizer.js.map +1 -1
- package/dist/table/ColumnSettings.js +5 -5
- package/dist/table/ColumnSettings.js.map +1 -1
- package/dist/table/ColumnTable.js +8 -8
- package/dist/table/ColumnTable.js.map +1 -1
- package/dist/table/DataTable.js +8 -8
- package/dist/table/DataTable.js.map +1 -1
- package/dist/table/DataTableView.js +21 -21
- package/dist/table/DataTableView.js.map +1 -1
- package/dist/table/PivotTable.js +3 -3
- package/dist/table/PivotTable.js.map +1 -1
- package/dist/table/SelectionBar.js +4 -4
- package/dist/table/SelectionBar.js.map +1 -1
- package/dist/table/TreeDataTable.js +5 -5
- package/dist/table/TreeDataTable.js.map +1 -1
- package/dist/theme-customize/ThemeCustomizer.js +7 -6
- package/dist/theme-customize/ThemeCustomizer.js.map +1 -1
- package/dist/theme-customize/ThemeThumbnail.js +3 -3
- package/dist/theme-customize/ThemeThumbnail.js.map +1 -1
- package/dist/three/background/AuroraBackground.js +3 -3
- package/dist/three/background/AuroraBackground.js.map +1 -1
- package/dist/three/background/MeshGradient.js +3 -3
- package/dist/three/background/MeshGradient.js.map +1 -1
- package/dist/three/background/ShaderBackground.js +3 -3
- package/dist/three/background/ShaderBackground.js.map +1 -1
- package/dist/three/core/EffectSurface.js +3 -3
- package/dist/three/core/EffectSurface.js.map +1 -1
- package/dist/three/core/ThreeCanvas.js +4 -4
- package/dist/three/core/ThreeCanvas.js.map +1 -1
- package/dist/three/effects/Confetti.js +3 -3
- package/dist/three/effects/Confetti.js.map +1 -1
- package/dist/three/effects/ParticleField.js +3 -3
- package/dist/three/effects/ParticleField.js.map +1 -1
- package/dist/three/effects/WaveGrid.js +3 -3
- package/dist/three/effects/WaveGrid.js.map +1 -1
- package/dist/three/globe/Globe.js +3 -3
- package/dist/three/globe/Globe.js.map +1 -1
- package/dist/three/model/ModelViewer.js +3 -3
- package/dist/three/model/ModelViewer.js.map +1 -1
- package/dist/utils/data/clone.d.ts +8 -0
- package/dist/utils/data/clone.js +21 -0
- package/dist/utils/data/clone.js.map +1 -0
- package/dist/utils/data/date.d.ts +10 -0
- package/dist/utils/data/date.js +13 -1
- package/dist/utils/data/date.js.map +1 -1
- package/dist/utils/data/number.d.ts +5 -0
- package/dist/utils/data/number.js +8 -1
- package/dist/utils/data/number.js.map +1 -1
- package/dist/utils/data/numeric.d.ts +7 -0
- package/dist/utils/data/numeric.js +46 -1
- package/dist/utils/data/numeric.js.map +1 -1
- package/dist/utils/dom/download.d.ts +8 -1
- package/dist/utils/dom/download.js +19 -2
- package/dist/utils/dom/download.js.map +1 -1
- package/dist/utils/dom/element.d.ts +3 -1
- package/dist/utils/dom/element.js +8 -4
- package/dist/utils/dom/element.js.map +1 -1
- package/dist/utils/dom/fullscreen.d.ts +1 -1
- package/dist/utils/dom/fullscreen.js +12 -8
- package/dist/utils/dom/fullscreen.js.map +1 -1
- package/dist/utils/dom/mounted.d.ts +7 -0
- package/dist/utils/dom/mounted.js +16 -0
- package/dist/utils/dom/mounted.js.map +1 -0
- package/dist/utils/dom/overflow.js +2 -1
- package/dist/utils/dom/overflow.js.map +1 -1
- package/dist/utils/dom/pan-zoom.js +2 -1
- package/dist/utils/dom/pan-zoom.js.map +1 -1
- package/dist/utils/props.js +3 -13
- package/dist/utils/props.js.map +1 -1
- package/dist/utils/request/error-message.d.ts +17 -0
- package/dist/utils/request/error-message.js +50 -0
- package/dist/utils/request/error-message.js.map +1 -0
- package/dist/utils/request/http.d.ts +80 -4
- package/dist/utils/request/http.js +121 -24
- package/dist/utils/request/http.js.map +1 -1
- package/dist/utils/request/use-autosave.d.ts +35 -0
- package/dist/utils/request/use-autosave.js +69 -0
- package/dist/utils/request/use-autosave.js.map +1 -0
- package/dist/utils/request/use-request.js +4 -2
- package/dist/utils/request/use-request.js.map +1 -1
- package/package.json +57 -4
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"element.js","names":[],"sources":["../../../src/utils/dom/element.ts"],"sourcesContent":["/** 模板 ref 的取值换成 DOM
|
|
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"}
|
|
@@ -1,33 +1,37 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { onBeforeUnmount, onMounted, shallowRef } from "vue";
|
|
2
2
|
//#region src/utils/dom/fullscreen.ts
|
|
3
3
|
/**
|
|
4
|
-
* 让一个元素全屏(Fullscreen API):表格、看板、图表的“全屏”按钮。按 Esc 或浏览器的退出全屏时 active
|
|
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 =
|
|
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
|
-
|
|
20
|
+
onMounted(() => {
|
|
21
|
+
supported.value = document.fullscreenEnabled === true;
|
|
20
22
|
document.addEventListener("fullscreenchange", sync);
|
|
21
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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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"}
|
package/dist/utils/props.js
CHANGED
|
@@ -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 = (
|
|
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
|
|
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 = [
|
package/dist/utils/props.js.map
CHANGED
|
@@ -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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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Lang } from '../../config/lang/lang';
|
|
2
|
+
/** 错误上的业务错误码;认不出时为 undefined */
|
|
3
|
+
export declare function errorCodeOf(error: unknown): string | undefined;
|
|
4
|
+
/**
|
|
5
|
+
* 给人看的错误文案:码在 lang.errorCodes 里有文案时用它(params 填进去);没有时用错误自己的 message(后端给的兜底、
|
|
6
|
+
* 应用的上传函数抛的说明),再没有用 lang.requestFailed
|
|
7
|
+
*/
|
|
8
|
+
export declare function errorMessage(error: unknown, lang: Lang): string;
|
|
9
|
+
/**
|
|
10
|
+
* 只按码取的文案;错误没有码、码没有文案时为 undefined。组件有自己的笼统提示时用它
|
|
11
|
+
* (远程选项:`errorCodeText(error, lang) ?? lang.listLoadFailed`),不把底层的英文说明露给用户
|
|
12
|
+
*/
|
|
13
|
+
export declare function errorCodeText(error: unknown, lang: Lang): string | undefined;
|
|
14
|
+
/** 一个码的文案(也用于 errors 明细里按字段的码);语言包里没有时为 undefined */
|
|
15
|
+
export declare function codeText(code: string, params: Readonly<Record<string, unknown>> | undefined, lang: Lang): string | undefined;
|
|
16
|
+
/** 当前语言下的 errorMessage(在 setup 里调用);返回的函数随 Provider 切换语言 */
|
|
17
|
+
export declare function useErrorMessage(): (error: unknown) => string;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { useLang } from "../../config/context.js";
|
|
2
|
+
import { HttpError, isAbortError } from "./http.js";
|
|
3
|
+
//#region src/utils/request/error-message.ts
|
|
4
|
+
/**
|
|
5
|
+
* 错误的文案:按业务错误码(flow 仓库 docs/decisions/0008)从语言包的 errorCodes 取,组件显示请求失败时都经过这里。
|
|
6
|
+
* 码的来源:HttpError 的 code(后端回的 `flow.`、`query.`、应用自己的,或请求层的 `http.`);取消是 `http.aborted`;
|
|
7
|
+
* 别的包的错误带 `包.码` 写法的 code 字段也认(query 的本地库 `query.quota-exceeded`、flow 的 FlowError)。
|
|
8
|
+
*
|
|
9
|
+
* const describe = useErrorMessage();
|
|
10
|
+
* describe(error); // → '数据已被他人修改或删除,请刷新后重试'
|
|
11
|
+
*
|
|
12
|
+
* 应用与 flow-ui 补自己命名空间的文案:`defineLang(zhCN, { errorCodes: { 'app.quota': '额度不足' } })`(与自带的合并)。
|
|
13
|
+
*/
|
|
14
|
+
/** 错误上的业务错误码;认不出时为 undefined */
|
|
15
|
+
function errorCodeOf(error) {
|
|
16
|
+
if (error instanceof HttpError) return error.code;
|
|
17
|
+
if (isAbortError(error)) return "http.aborted";
|
|
18
|
+
const code = typeof error === "object" && error !== null ? error.code : void 0;
|
|
19
|
+
return typeof code === "string" && code.includes(".") ? code : void 0;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* 给人看的错误文案:码在 lang.errorCodes 里有文案时用它(params 填进去);没有时用错误自己的 message(后端给的兜底、
|
|
23
|
+
* 应用的上传函数抛的说明),再没有用 lang.requestFailed
|
|
24
|
+
*/
|
|
25
|
+
function errorMessage(error, lang) {
|
|
26
|
+
return errorCodeText(error, lang) ?? (error instanceof Error && error.message ? error.message : lang.requestFailed);
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* 只按码取的文案;错误没有码、码没有文案时为 undefined。组件有自己的笼统提示时用它
|
|
30
|
+
* (远程选项:`errorCodeText(error, lang) ?? lang.listLoadFailed`),不把底层的英文说明露给用户
|
|
31
|
+
*/
|
|
32
|
+
function errorCodeText(error, lang) {
|
|
33
|
+
const code = errorCodeOf(error);
|
|
34
|
+
if (code === void 0) return void 0;
|
|
35
|
+
return codeText(code, error.params, lang);
|
|
36
|
+
}
|
|
37
|
+
/** 一个码的文案(也用于 errors 明细里按字段的码);语言包里没有时为 undefined */
|
|
38
|
+
function codeText(code, params, lang) {
|
|
39
|
+
const text = lang.errorCodes[code];
|
|
40
|
+
return typeof text === "function" ? text(params ?? {}) : text;
|
|
41
|
+
}
|
|
42
|
+
/** 当前语言下的 errorMessage(在 setup 里调用);返回的函数随 Provider 切换语言 */
|
|
43
|
+
function useErrorMessage() {
|
|
44
|
+
const lang = useLang();
|
|
45
|
+
return (error) => errorMessage(error, lang);
|
|
46
|
+
}
|
|
47
|
+
//#endregion
|
|
48
|
+
export { codeText, errorCodeOf, errorCodeText, errorMessage, useErrorMessage };
|
|
49
|
+
|
|
50
|
+
//# sourceMappingURL=error-message.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error-message.js","names":[],"sources":["../../../src/utils/request/error-message.ts"],"sourcesContent":["/**\n * 错误的文案:按业务错误码(flow 仓库 docs/decisions/0008)从语言包的 errorCodes 取,组件显示请求失败时都经过这里。\n * 码的来源:HttpError 的 code(后端回的 `flow.`、`query.`、应用自己的,或请求层的 `http.`);取消是 `http.aborted`;\n * 别的包的错误带 `包.码` 写法的 code 字段也认(query 的本地库 `query.quota-exceeded`、flow 的 FlowError)。\n *\n * const describe = useErrorMessage();\n * describe(error); // → '数据已被他人修改或删除,请刷新后重试'\n *\n * 应用与 flow-ui 补自己命名空间的文案:`defineLang(zhCN, { errorCodes: { 'app.quota': '额度不足' } })`(与自带的合并)。\n */\nimport { useLang } from '../../config/context';\nimport type { Lang } from '../../config/lang/lang';\nimport { HttpError, isAbortError } from './http';\n\n/** 错误上的业务错误码;认不出时为 undefined */\nexport function errorCodeOf(error: unknown): string | undefined {\n if (error instanceof HttpError) return error.code;\n if (isAbortError(error)) return 'http.aborted';\n const code =\n typeof error === 'object' && error !== null ? (error as { code?: unknown }).code : undefined;\n // 只认 `包.码` 的写法:Node 的 ENOENT、DOMException 的数字 code 不算\n return typeof code === 'string' && code.includes('.') ? code : undefined;\n}\n\n/**\n * 给人看的错误文案:码在 lang.errorCodes 里有文案时用它(params 填进去);没有时用错误自己的 message(后端给的兜底、\n * 应用的上传函数抛的说明),再没有用 lang.requestFailed\n */\nexport function errorMessage(error: unknown, lang: Lang): string {\n return (\n errorCodeText(error, lang) ??\n (error instanceof Error && error.message ? error.message : lang.requestFailed)\n );\n}\n\n/**\n * 只按码取的文案;错误没有码、码没有文案时为 undefined。组件有自己的笼统提示时用它\n * (远程选项:`errorCodeText(error, lang) ?? lang.listLoadFailed`),不把底层的英文说明露给用户\n */\nexport function errorCodeText(error: unknown, lang: Lang): string | undefined {\n const code = errorCodeOf(error);\n if (code === undefined) return undefined;\n return codeText(code, (error as { params?: Readonly<Record<string, unknown>> }).params, lang);\n}\n\n/** 一个码的文案(也用于 errors 明细里按字段的码);语言包里没有时为 undefined */\nexport function codeText(\n code: string,\n params: Readonly<Record<string, unknown>> | undefined,\n lang: Lang,\n): string | undefined {\n const text = lang.errorCodes[code];\n return typeof text === 'function' ? text(params ?? {}) : text;\n}\n\n/** 当前语言下的 errorMessage(在 setup 里调用);返回的函数随 Provider 切换语言 */\nexport function useErrorMessage(): (error: unknown) => string {\n const lang = useLang();\n return (error) => errorMessage(error, lang);\n}\n"],"mappings":";;;;;;;;;;;;;;AAeA,SAAgB,YAAY,OAAoC;CAC9D,IAAI,iBAAiB,WAAW,OAAO,MAAM;CAC7C,IAAI,aAAa,KAAK,GAAG,OAAO;CAChC,MAAM,OACJ,OAAO,UAAU,YAAY,UAAU,OAAQ,MAA6B,OAAO,KAAA;CAErF,OAAO,OAAO,SAAS,YAAY,KAAK,SAAS,GAAG,IAAI,OAAO,KAAA;AACjE;;;;;AAMA,SAAgB,aAAa,OAAgB,MAAoB;CAC/D,OACE,cAAc,OAAO,IAAI,MACxB,iBAAiB,SAAS,MAAM,UAAU,MAAM,UAAU,KAAK;AAEpE;;;;;AAMA,SAAgB,cAAc,OAAgB,MAAgC;CAC5E,MAAM,OAAO,YAAY,KAAK;CAC9B,IAAI,SAAS,KAAA,GAAW,OAAO,KAAA;CAC/B,OAAO,SAAS,MAAO,MAAyD,QAAQ,IAAI;AAC9F;;AAGA,SAAgB,SACd,MACA,QACA,MACoB;CACpB,MAAM,OAAO,KAAK,WAAW;CAC7B,OAAO,OAAO,SAAS,aAAa,KAAK,UAAU,CAAC,CAAC,IAAI;AAC3D;;AAGA,SAAgB,kBAA8C;CAC5D,MAAM,OAAO,QAAQ;CACrB,QAAQ,UAAU,aAAa,OAAO,IAAI;AAC5C"}
|
|
@@ -24,14 +24,70 @@ 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 {
|
|
30
42
|
/** 发出一次请求,返回解析(拆包)后的数据;失败时以 HttpError 拒绝,取消时以 AbortError 拒绝 */
|
|
31
43
|
request<T = unknown>(request: HttpRequest): Promise<T>;
|
|
32
44
|
}
|
|
33
|
-
/**
|
|
45
|
+
/** 成功的码 */
|
|
46
|
+
export declare const OK_CODE = "ok";
|
|
47
|
+
/**
|
|
48
|
+
* 请求层自己出的错的码(0008 的 `http.` 命名空间,只在前端产生)。取消不当作错误,仍以 AbortError 拒绝,
|
|
49
|
+
* errorCodeOf 把它认作 `http.aborted`
|
|
50
|
+
*/
|
|
51
|
+
export declare const HTTP_ERROR_CODES: readonly ['http.network', 'http.timeout', 'http.aborted', 'http.status', 'http.parse'];
|
|
52
|
+
/** 请求层的错误码 */
|
|
53
|
+
export type HttpErrorCode = (typeof HTTP_ERROR_CODES)[number];
|
|
54
|
+
/**
|
|
55
|
+
* 错误明细的一项(0008):按字段的(表单校验:field、code),或按位置的(流程定义的问题:node、edge);没有的部分省掉。
|
|
56
|
+
* 后端只给说明、不给代号时有 message
|
|
57
|
+
*/
|
|
58
|
+
export interface ApiErrorDetail {
|
|
59
|
+
/** 问题的代号(required、max……) */
|
|
60
|
+
readonly code?: string;
|
|
61
|
+
/** 字段路径(`email`、`lines[0].price`) */
|
|
62
|
+
readonly field?: string;
|
|
63
|
+
/** 后端给的说明,只作兜底 */
|
|
64
|
+
readonly message?: string;
|
|
65
|
+
/** 文案里要填的参数 */
|
|
66
|
+
readonly params?: Readonly<Record<string, unknown>>;
|
|
67
|
+
/** 别的位置信息(节点、连线……) */
|
|
68
|
+
readonly [key: string]: unknown;
|
|
69
|
+
}
|
|
70
|
+
/** 后端回的响应体(0008 第 1 节):成功的 code 是 `ok`,失败是业务错误码 */
|
|
71
|
+
export interface ApiEnvelope<T = unknown> {
|
|
72
|
+
/** `ok` 或业务错误码 */
|
|
73
|
+
readonly code: string;
|
|
74
|
+
/** 后端给的说明,只作兜底:界面按 code 取文案 */
|
|
75
|
+
readonly message?: string;
|
|
76
|
+
/** 文案里要填的参数 */
|
|
77
|
+
readonly params?: Readonly<Record<string, unknown>>;
|
|
78
|
+
/** 按字段、按问题的明细(表单校验) */
|
|
79
|
+
readonly errors?: readonly ApiErrorDetail[];
|
|
80
|
+
/** 成功时的数据 */
|
|
81
|
+
readonly data?: T;
|
|
82
|
+
}
|
|
83
|
+
/** 请求失败时的上下文:业务错误码、发生在哪个请求、拿到的响应 */
|
|
34
84
|
export interface HttpErrorContext {
|
|
85
|
+
/** 业务错误码:后端回的(`flow.`、`query.`、应用自己的)或请求层的(`http.`) */
|
|
86
|
+
code: string;
|
|
87
|
+
/** 文案里要填的参数 */
|
|
88
|
+
params?: Readonly<Record<string, unknown>>;
|
|
89
|
+
/** 按字段、按问题的明细 */
|
|
90
|
+
errors?: readonly ApiErrorDetail[];
|
|
35
91
|
/** 出错的请求 */
|
|
36
92
|
request: HttpRequest;
|
|
37
93
|
/** 响应;网络错误、超时时没有 */
|
|
@@ -41,10 +97,16 @@ export interface HttpErrorContext {
|
|
|
41
97
|
/** 底层的错误(网络错误、超时) */
|
|
42
98
|
cause?: unknown;
|
|
43
99
|
}
|
|
44
|
-
/**
|
|
100
|
+
/** 请求失败:后端回了业务错误码、状态码不是 2xx、网络错误、超时、响应读不懂,或 onResponse 判定的业务错误 */
|
|
45
101
|
export declare class HttpError extends Error {
|
|
46
102
|
/** 错误名,同类名:日志与 instanceof 之外的判断用 */
|
|
47
103
|
readonly name = "HttpError";
|
|
104
|
+
/** 业务错误码(见 HttpErrorContext.code);界面按它取文案 */
|
|
105
|
+
readonly code: string;
|
|
106
|
+
/** 文案里要填的参数 */
|
|
107
|
+
readonly params: Readonly<Record<string, unknown>>;
|
|
108
|
+
/** 按字段、按问题的明细;useForm 的 submit 据此标到字段上 */
|
|
109
|
+
readonly errors: readonly ApiErrorDetail[];
|
|
48
110
|
/** 出错的请求 */
|
|
49
111
|
readonly request: HttpRequest;
|
|
50
112
|
/** 响应;网络错误、超时时没有 */
|
|
@@ -53,7 +115,7 @@ export declare class HttpError extends Error {
|
|
|
53
115
|
readonly status: number;
|
|
54
116
|
/** 响应体(错误详情) */
|
|
55
117
|
readonly data?: unknown;
|
|
56
|
-
constructor(message: string, { request, response, data, cause }: HttpErrorContext);
|
|
118
|
+
constructor(message: string, { code, params, errors, request, response, data, cause }: HttpErrorContext);
|
|
57
119
|
}
|
|
58
120
|
/** 是不是主动取消(abort)造成的拒绝:界面上不当作错误 */
|
|
59
121
|
export declare const isAbortError: (error: unknown) => boolean;
|
|
@@ -67,7 +129,12 @@ export interface HttpClientOptions {
|
|
|
67
129
|
timeout?: number;
|
|
68
130
|
/** 发出之前改请求(加令牌、追踪编号);可以返回一个新的 Request */
|
|
69
131
|
onRequest?: (request: Request) => Request | void | Promise<Request | void>;
|
|
70
|
-
/**
|
|
132
|
+
/**
|
|
133
|
+
* 按 0008 的约定拆响应体(见文件开头),默认 true:键都在 code、message、params、errors、data 里、code 是文字的对象
|
|
134
|
+
* 才当作包装,其余原样交出。后端用别的包装时给 false,在 onResponse 里拆
|
|
135
|
+
*/
|
|
136
|
+
envelope?: boolean;
|
|
137
|
+
/** 成功的响应(拆包之后)的数据交给它:再加工、判定别的业务错误(抛 HttpError),返回值就是请求的结果 */
|
|
71
138
|
onResponse?: (data: unknown, context: {
|
|
72
139
|
request: HttpRequest;
|
|
73
140
|
response: Response;
|
|
@@ -95,6 +162,15 @@ export interface FetchClient extends HttpClient {
|
|
|
95
162
|
}
|
|
96
163
|
/** 原生 fetch 的薄封装(见文件开头) */
|
|
97
164
|
export declare function createHttpClient(options?: HttpClientOptions): FetchClient;
|
|
165
|
+
/**
|
|
166
|
+
* 在任意请求客户端上加常用方法的简写,相对地址前面拼上 prefix(斜杠不重复;完整地址原样)。
|
|
167
|
+
* 一个模块的接口都在同一个前缀下时用它,不必每次写全地址;useHttp() 拿到的客户端也能这样包:
|
|
168
|
+
*
|
|
169
|
+
* const api = scopeHttp(useHttp(), '/flow');
|
|
170
|
+
* api.get('/queries/todo', { query: { page: 1 }, signal }); // GET /flow/queries/todo?page=1
|
|
171
|
+
* api.post('/commands/claim', { workItemId });
|
|
172
|
+
*/
|
|
173
|
+
export declare function scopeHttp(client: HttpClient, prefix?: string): FetchClient;
|
|
98
174
|
/**
|
|
99
175
|
* 当前配置的请求客户端(在 setup 里调用):createUi / Provider 的 http;都没给时是一个默认的 createHttpClient()
|
|
100
176
|
*/
|