@zerodep-dev/vue-tsx-pro-ui 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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/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/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/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.js +7 -7
- 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/index.d.ts +11 -6
- package/dist/index.js +10 -5
- 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 +4 -4
- 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 +3 -3
- 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 +4 -4
- 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 +4 -4
- 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 +6 -6
- package/dist/input/TreeSelect.js.map +1 -1
- package/dist/input/Upload.js +8 -8
- package/dist/input/Upload.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 +7 -1
- package/dist/patterns/QueryTable.js +116 -81
- 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 +37 -1
- package/dist/patterns/messages.js +38 -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/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/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/http.d.ts +12 -0
- package/dist/utils/request/http.js +6 -2
- package/dist/utils/request/http.js.map +1 -1
- package/dist/utils/request/use-request.js +4 -2
- package/dist/utils/request/use-request.js.map +1 -1
- package/package.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Descriptions.js","names":["_vtp_readonlyProp","_vtp_restProps","_vue_shallowRef","_vue_ref","_createVNode","_mergeProps"],"sources":["../../src/display/Descriptions.tsx"],"sourcesContent":["/**\n * 描述列表(div > dl):键值对的详情(订单信息、用户资料),可分栏、可跨列,标签在左(横排)或在上(竖排)。\n * 语义是 dl > div > dt + dd(HTML 允许用 div 给 dt / dd 分组),读屏按“名称、值”念。参考 Ant Design Descriptions。\n *\n * <Descriptions\n * title=\"订单信息\"\n * columns={3}\n * items={[\n * { label: '订单号', value: 'A-1024' },\n * { label: '状态', value: <Badge tone=\"success\">已发货</Badge> },\n * { label: '收货地址', value: address, span: 3 },\n * ]}\n * />\n *\n * 容器变窄(不到 32em)时自动排成一列(容器查询)。columns=\"auto\" 时按容器的宽度自己定栏数:每栏不窄于 minItemWidth,\n * 最多 maxColumns 栏(同一份详情放在抽屉、整页里各自合适);跨列(span)按算出的栏数封顶。\n * 项写 copyable 时值后面有复制按钮(编号、单号、地址这类要复制去别处的),复制的文字默认是值的文字,可以用 copyText 指定。\n *\n * 部件:Root(div)、Title、List(dl)、Item(div)、Label(dt)、Value(dd)、Copy(CopyButton)。\n */\nimport { _component } from '@zerodep-dev/vue-tsx-pro-tsx';\nimport * as css from '@zerodep-dev/vue-tsx-pro-tsx/styles';\nimport type { VNodeChild } from 'vue';\nimport { clamp } from '../utils/data/number';\nimport { CopyButton, type CopyButtonProps } from '../base/CopyButton';\nimport { useLang } from '../config/context';\nimport { plainText } from '../utils/data/text';\nimport { useResizeObserver } from '../utils/dom/resize';\nimport { hasContent, mergeAttrs, mergePartProps, type PartProps } from '../utils/props';\nimport { fontSizeStyles, type TextSize } from '../utils/style/typography';\nimport { t } from '../config/theme/tokens';\n\n/** 描述列表里的一项 */\nexport interface DescriptionItem {\n /** 唯一的 key;不写时用 label(label 是文字时) */\n key?: string;\n /** 名称 */\n label: VNodeChild;\n /** 值 */\n value: VNodeChild;\n /** 占几列,默认 1;超过栏数时按栏数 */\n span?: number;\n /** 值后面放复制按钮 */\n copyable?: boolean;\n /** 复制的文字;不写时是值的文字(值是文字或数字时) */\n copyText?: string;\n}\n\n/** 标签与值的排列 */\nexport interface DescriptionsLayouts {\n /** 横排(默认):标签在左、值在右 */\n horizontal: unknown;\n /** 竖排:标签在上、值在下 */\n vertical: unknown;\n /** 行内:各项一行排开、放不下换行(页头里的编号、团队、主办这类关键信息);columns 不起作用 */\n inline: unknown;\n}\n\n/** 未声明的属性落到外层 div 上;原生的 title 属性(悬停提示)让给标题 */\nexport interface DescriptionsProps extends PartProps<'div', 'title'> {\n /** 各项 */\n items: readonly DescriptionItem[];\n /** 标题 */\n title?: VNodeChild;\n /** 分几栏,默认 1;'auto' 时按容器的宽度定(见 minItemWidth、maxColumns) */\n columns?: number | 'auto';\n /** columns 为 'auto' 时每栏的最小宽度(px),默认 240 */\n minItemWidth?: number;\n /** columns 为 'auto' 时最多几栏,默认 4 */\n maxColumns?: number;\n /** 标签与值的排列,默认 horizontal */\n layout?: keyof DescriptionsLayouts;\n /** 带边框(表格样式):标签有底色,各项之间有分隔线 */\n bordered?: boolean;\n /** 字号档位;不写时继承 */\n size?: TextSize;\n /** 标题的部件属性 */\n propsTitle?: PartProps<'div'>;\n /** 列表(dl)的部件属性 */\n propsList?: PartProps<'dl'>;\n /** 每一项(div)的部件属性,可以按项计算 */\n propsItem?: PartProps<'div'> | ((item: DescriptionItem, index: number) => PartProps<'div'>);\n /** 名称(dt)的部件属性 */\n propsLabel?: PartProps<'dt'>;\n /** 值(dd)的部件属性 */\n propsValue?: PartProps<'dd'>;\n /** 复制按钮的部件属性 */\n propsCopy?: Partial<CopyButtonProps>;\n}\n\n/** Descriptions 的样式:root、title、list、item、label、value 与各排列、边框 */\nexport const descriptionsStyles = css.create({\n // 容器查询的容器:dl 按它的宽度决定要不要排成一列\n root: { containerType: 'inline-size', display: 'grid', gap: '_md' },\n title: { fontWeight: '_semibold', fontSize: '_md' },\n list: {\n display: 'grid',\n columnGap: '_xl',\n rowGap: '_md',\n margin: 0,\n },\n // 用长写:长写优先于简写(引擎把简写排在前面),gap: 0 压不住 list 的 columnGap / rowGap\n listBordered: {\n columnGap: 0,\n rowGap: 0,\n borderWidth: '_xs',\n borderStyle: 'solid',\n borderColor: '_borderMuted',\n borderRadius: '_md',\n overflow: 'hidden',\n },\n item: { minWidth: 0 },\n itemHorizontal: { display: 'flex', gap: '_md', alignItems: 'baseline' },\n itemVertical: { display: 'grid', gap: '_xs' },\n // 行内:dl 改成换行的一行,项内标签与值挨着\n listInline: { display: 'flex', flexWrap: 'wrap', columnGap: '_xl', rowGap: '_xs' },\n itemInline: { display: 'flex', gap: '_xs', alignItems: 'baseline' },\n itemBordered: {\n alignItems: 'stretch',\n gap: 0,\n // 每格的上、起始边画线,外框由 dl 画;第一行、第一列的线被 dl 的边框盖住\n marginBlockStart: `calc(-1 * ${t.borderWidths.xs})`,\n marginInlineStart: `calc(-1 * ${t.borderWidths.xs})`,\n borderBlockStartWidth: '_xs',\n borderInlineStartWidth: '_xs',\n borderBlockStartStyle: 'solid',\n borderInlineStartStyle: 'solid',\n borderColor: '_borderMuted',\n },\n label: { flexShrink: 0, margin: 0, color: '_textMuted' },\n cellBordered: { paddingBlock: '_sm', paddingInline: '_md' },\n // 暗色主题的 surfaceMuted 与 surface 同色,标签格要在两种模式下都看得出来\n labelBordered: { backgroundColor: '_fill', minWidth: '8em' },\n value: { minWidth: 0, margin: 0, color: '_text', overflowWrap: 'anywhere' },\n // 有复制按钮的值:按钮紧跟在文字后面,文字换行时按钮在第一行\n valueCopyable: { display: 'flex', alignItems: 'baseline', gap: '_xs' },\n copy: { flexShrink: 0, marginBlock: '-0.25em' },\n});\n\n// 栏数与跨度是任意值,用动态样式;单独一个 create,见 COMPONENTS.md §4\n/** Descriptions 的样式:columns(栏数)、span(跨几栏) */\nexport const descriptionsGridStyles = css.create({\n // 容器不到 32em 时排成一列\n columns: (count: number) => ({\n gridTemplateColumns: {\n default: `repeat(${count}, minmax(0, 1fr))`,\n '@container (max-width: 32em)': '1fr',\n },\n }),\n span: (count: number) => ({\n gridColumn: { default: `span ${count}`, '@container (max-width: 32em)': 'auto' },\n }),\n // columns=\"auto\":栏数已经按宽度算好,不再用容器查询\n autoColumns: (count: number) => ({ gridTemplateColumns: `repeat(${count}, minmax(0, 1fr))` }),\n autoSpan: (count: number) => ({ gridColumn: `span ${count}` }),\n});\n\n/** 描述列表:键值对,可分栏、可跨列 */\nexport const Descriptions = _component(\n ({\n items,\n title,\n columns = 1,\n minItemWidth = 240,\n maxColumns = 4,\n layout = 'horizontal',\n bordered = false,\n size,\n propsTitle,\n propsList,\n propsItem,\n propsLabel,\n propsValue,\n propsCopy,\n ...rest\n }: DescriptionsProps) => {\n const lang = useLang();\n let list = _state.raw<HTMLElement | null>(null);\n // columns=\"auto\":按列表的宽度算栏数\n let width = _state(0);\n useResizeObserver(\n () => list,\n ([entry]) => (width = entry?.contentRect.width ?? 0),\n );\n const auto = () => columns === 'auto';\n const count = () =>\n auto() ? clamp(Math.floor(width / minItemWidth), 1, maxColumns) : (columns as number);\n\n /** 复制的文字:copyText,否则值是文字或数字时就是它 */\n const copyTextOf = (item: DescriptionItem) =>\n item.copyText ??\n (typeof item.value === 'string' || typeof item.value === 'number' ? String(item.value) : '');\n\n return () => (\n <div\n {...mergeAttrs<'div'>(\n { sx: [descriptionsStyles.root, size && fontSizeStyles[size]] },\n rest,\n )}\n data-layout={layout}\n >\n {hasContent(title) && (\n <div {...mergeAttrs<'div'>({ sx: descriptionsStyles.title }, propsTitle)}>{title}</div>\n )}\n <dl\n {...mergeAttrs<'dl'>(\n {\n sx: [\n descriptionsStyles.list,\n layout === 'inline'\n ? descriptionsStyles.listInline\n : auto()\n ? descriptionsGridStyles.autoColumns(count())\n : descriptionsGridStyles.columns(count()),\n bordered && descriptionsStyles.listBordered,\n ],\n },\n propsList,\n { ref: _toRef(list) },\n )}\n >\n {items.map((item, index) => {\n const own = typeof propsItem === 'function' ? propsItem(item, index) : propsItem;\n const span = clamp(item.span ?? 1, 1, count());\n return (\n <div\n key={item.key ?? (typeof item.label === 'string' ? item.label : index)}\n {...mergeAttrs<'div'>(\n {\n sx: [\n layout === 'vertical'\n ? descriptionsStyles.itemVertical\n : layout === 'inline'\n ? descriptionsStyles.itemInline\n : descriptionsStyles.itemHorizontal,\n bordered && descriptionsStyles.itemBordered,\n descriptionsStyles.item,\n layout !== 'inline' &&\n span > 1 &&\n (auto()\n ? descriptionsGridStyles.autoSpan(span)\n : descriptionsGridStyles.span(span)),\n ],\n },\n own,\n )}\n >\n <dt\n {...mergeAttrs<'dt'>(\n {\n sx: [\n descriptionsStyles.label,\n bordered && [\n descriptionsStyles.cellBordered,\n descriptionsStyles.labelBordered,\n ],\n ],\n },\n propsLabel,\n )}\n >\n {item.label}\n </dt>\n <dd\n {...mergeAttrs<'dd'>(\n {\n sx: [\n descriptionsStyles.value,\n bordered && descriptionsStyles.cellBordered,\n item.copyable && descriptionsStyles.valueCopyable,\n ],\n },\n propsValue,\n )}\n >\n {item.value}\n {item.copyable && (\n <CopyButton\n {...mergePartProps<Partial<CopyButtonProps>>(\n {\n size: 'xs',\n sx: descriptionsStyles.copy,\n 'aria-label': lang.descriptionsCopy(plainText(item.label) ?? lang.copy),\n },\n propsCopy,\n )}\n value={() => copyTextOf(item)}\n />\n )}\n </dd>\n </div>\n );\n })}\n </dl>\n </div>\n );\n },\n);\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Descriptions.js","names":["_vtp_readonlyProp","_vtp_restProps","_vue_shallowRef","_vue_ref","_createVNode","_mergeProps"],"sources":["../../src/display/Descriptions.tsx"],"sourcesContent":["/**\n * 描述列表(div > dl):键值对的详情(订单信息、用户资料),可分栏、可跨列,标签在左(横排)或在上(竖排)。\n * 语义是 dl > div > dt + dd(HTML 允许用 div 给 dt / dd 分组),读屏按“名称、值”念。参考 Ant Design Descriptions。\n *\n * <Descriptions\n * title=\"订单信息\"\n * columns={3}\n * items={[\n * { label: '订单号', value: 'A-1024' },\n * { label: '状态', value: <Badge tone=\"success\">已发货</Badge> },\n * { label: '收货地址', value: address, span: 3 },\n * ]}\n * />\n *\n * 容器变窄(不到 32em)时自动排成一列(容器查询)。columns=\"auto\" 时按容器的宽度自己定栏数:每栏不窄于 minItemWidth,\n * 最多 maxColumns 栏(同一份详情放在抽屉、整页里各自合适);跨列(span)按算出的栏数封顶。\n * 项写 copyable 时值后面有复制按钮(编号、单号、地址这类要复制去别处的),复制的文字默认是值的文字,可以用 copyText 指定。\n *\n * 部件:Root(div)、Title、List(dl)、Item(div)、Label(dt)、Value(dd)、Copy(CopyButton)。\n */\nimport { _component } from '@zerodep-dev/vue-tsx-pro-tsx';\nimport * as css from '@zerodep-dev/vue-tsx-pro-tsx/styles';\nimport type { VNodeChild } from 'vue';\nimport { clamp } from '../utils/data/number';\nimport { CopyButton, type CopyButtonProps } from '../base/CopyButton';\nimport { useLang } from '../config/context';\nimport { plainText } from '../utils/data/text';\nimport { useResizeObserver } from '../utils/dom/resize';\nimport { hasContent, mergeAttrs, mergePartProps, type PartProps } from '../utils/props';\nimport { fontSizeStyles, type TextSize } from '../utils/style/typography';\nimport { t } from '../config/theme/tokens';\n\n/** 描述列表里的一项 */\nexport interface DescriptionItem {\n /** 唯一的 key;不写时用 label(label 是文字时) */\n key?: string;\n /** 名称 */\n label: VNodeChild;\n /** 值 */\n value: VNodeChild;\n /** 占几列,默认 1;超过栏数时按栏数 */\n span?: number;\n /** 值后面放复制按钮 */\n copyable?: boolean;\n /** 复制的文字;不写时是值的文字(值是文字或数字时) */\n copyText?: string;\n}\n\n/** 标签与值的排列 */\nexport interface DescriptionsLayouts {\n /** 横排(默认):标签在左、值在右 */\n horizontal: unknown;\n /** 竖排:标签在上、值在下 */\n vertical: unknown;\n /** 行内:各项一行排开、放不下换行(页头里的编号、团队、主办这类关键信息);columns 不起作用 */\n inline: unknown;\n}\n\n/** 未声明的属性落到外层 div 上;原生的 title 属性(悬停提示)让给标题 */\nexport interface DescriptionsProps extends PartProps<'div', 'title'> {\n /** 各项 */\n items: readonly DescriptionItem[];\n /** 标题 */\n title?: VNodeChild;\n /** 分几栏,默认 1;'auto' 时按容器的宽度定(见 minItemWidth、maxColumns) */\n columns?: number | 'auto';\n /** columns 为 'auto' 时每栏的最小宽度(px),默认 240 */\n minItemWidth?: number;\n /** columns 为 'auto' 时最多几栏,默认 4 */\n maxColumns?: number;\n /** 标签与值的排列,默认 horizontal */\n layout?: keyof DescriptionsLayouts;\n /** 带边框(表格样式):标签有底色,各项之间有分隔线 */\n bordered?: boolean;\n /** 字号档位;不写时继承 */\n size?: TextSize;\n /** 标题的部件属性 */\n propsTitle?: PartProps<'div'>;\n /** 列表(dl)的部件属性 */\n propsList?: PartProps<'dl'>;\n /** 每一项(div)的部件属性,可以按项计算 */\n propsItem?: PartProps<'div'> | ((item: DescriptionItem, index: number) => PartProps<'div'>);\n /** 名称(dt)的部件属性 */\n propsLabel?: PartProps<'dt'>;\n /** 值(dd)的部件属性 */\n propsValue?: PartProps<'dd'>;\n /** 复制按钮的部件属性 */\n propsCopy?: Partial<CopyButtonProps>;\n}\n\n/** Descriptions 的样式:root、title、list、item、label、value 与各排列、边框 */\nexport const descriptionsStyles = css.create({\n // 容器查询的容器:dl 按它的宽度决定要不要排成一列\n root: { containerType: 'inline-size', display: 'grid', gap: '_md' },\n title: { fontWeight: '_semibold', fontSize: '_md' },\n list: {\n display: 'grid',\n columnGap: '_xl',\n rowGap: '_md',\n margin: 0,\n },\n // 用长写:长写优先于简写(引擎把简写排在前面),gap: 0 压不住 list 的 columnGap / rowGap\n listBordered: {\n columnGap: 0,\n rowGap: 0,\n borderWidth: '_xs',\n borderStyle: 'solid',\n borderColor: '_borderMuted',\n borderRadius: '_md',\n overflow: 'hidden',\n },\n item: { minWidth: 0 },\n itemHorizontal: { display: 'flex', gap: '_md', alignItems: 'baseline' },\n itemVertical: { display: 'grid', gap: '_xs' },\n // 行内:dl 改成换行的一行,项内标签与值挨着\n listInline: { display: 'flex', flexWrap: 'wrap', columnGap: '_xl', rowGap: '_xs' },\n itemInline: { display: 'flex', gap: '_xs', alignItems: 'baseline' },\n itemBordered: {\n alignItems: 'stretch',\n gap: 0,\n // 每格的上、起始边画线,外框由 dl 画;第一行、第一列的线被 dl 的边框盖住\n marginBlockStart: `calc(-1 * ${t.borderWidths.xs})`,\n marginInlineStart: `calc(-1 * ${t.borderWidths.xs})`,\n borderBlockStartWidth: '_xs',\n borderInlineStartWidth: '_xs',\n borderBlockStartStyle: 'solid',\n borderInlineStartStyle: 'solid',\n borderColor: '_borderMuted',\n },\n label: { flexShrink: 0, margin: 0, color: '_textMuted' },\n cellBordered: { paddingBlock: '_sm', paddingInline: '_md' },\n // 暗色主题的 surfaceMuted 与 surface 同色,标签格要在两种模式下都看得出来\n labelBordered: { backgroundColor: '_fill', minWidth: '8em' },\n value: { minWidth: 0, margin: 0, color: '_text', overflowWrap: 'anywhere' },\n // 有复制按钮的值:按钮紧跟在文字后面,文字换行时按钮在第一行\n valueCopyable: { display: 'flex', alignItems: 'baseline', gap: '_xs' },\n copy: { flexShrink: 0, marginBlock: '-0.25em' },\n});\n\n// 栏数与跨度是任意值,用动态样式;单独一个 create,见 COMPONENTS.md §4\n/** Descriptions 的样式:columns(栏数)、span(跨几栏) */\nexport const descriptionsGridStyles = css.create({\n // 容器不到 32em 时排成一列\n columns: (count: number) => ({\n gridTemplateColumns: {\n default: `repeat(${count}, minmax(0, 1fr))`,\n '@container (max-width: 32em)': '1fr',\n },\n }),\n span: (count: number) => ({\n gridColumn: { default: `span ${count}`, '@container (max-width: 32em)': 'auto' },\n }),\n // columns=\"auto\":栏数已经按宽度算好,不再用容器查询\n autoColumns: (count: number) => ({ gridTemplateColumns: `repeat(${count}, minmax(0, 1fr))` }),\n autoSpan: (count: number) => ({ gridColumn: `span ${count}` }),\n});\n\n/** 描述列表:键值对,可分栏、可跨列 */\nexport const Descriptions = _component(\n ({\n items,\n title,\n columns = 1,\n minItemWidth = 240,\n maxColumns = 4,\n layout = 'horizontal',\n bordered = false,\n size,\n propsTitle,\n propsList,\n propsItem,\n propsLabel,\n propsValue,\n propsCopy,\n ...rest\n }: DescriptionsProps) => {\n const lang = useLang();\n let list = _state.raw<HTMLElement | null>(null);\n // columns=\"auto\":按列表的宽度算栏数\n let width = _state(0);\n useResizeObserver(\n () => list,\n ([entry]) => (width = entry?.contentRect.width ?? 0),\n );\n const auto = () => columns === 'auto';\n const count = () =>\n auto() ? clamp(Math.floor(width / minItemWidth), 1, maxColumns) : (columns as number);\n\n /** 复制的文字:copyText,否则值是文字或数字时就是它 */\n const copyTextOf = (item: DescriptionItem) =>\n item.copyText ??\n (typeof item.value === 'string' || typeof item.value === 'number' ? String(item.value) : '');\n\n return () => (\n <div\n {...mergeAttrs<'div'>(\n { sx: [descriptionsStyles.root, size && fontSizeStyles[size]] },\n rest,\n )}\n data-layout={layout}\n >\n {hasContent(title) && (\n <div {...mergeAttrs<'div'>({ sx: descriptionsStyles.title }, propsTitle)}>{title}</div>\n )}\n <dl\n {...mergeAttrs<'dl'>(\n {\n sx: [\n descriptionsStyles.list,\n layout === 'inline'\n ? descriptionsStyles.listInline\n : auto()\n ? descriptionsGridStyles.autoColumns(count())\n : descriptionsGridStyles.columns(count()),\n bordered && descriptionsStyles.listBordered,\n ],\n },\n propsList,\n { ref: _toRef(list) },\n )}\n >\n {items.map((item, index) => {\n const own = typeof propsItem === 'function' ? propsItem(item, index) : propsItem;\n const span = clamp(item.span ?? 1, 1, count());\n return (\n <div\n key={item.key ?? (typeof item.label === 'string' ? item.label : index)}\n {...mergeAttrs<'div'>(\n {\n sx: [\n layout === 'vertical'\n ? descriptionsStyles.itemVertical\n : layout === 'inline'\n ? descriptionsStyles.itemInline\n : descriptionsStyles.itemHorizontal,\n bordered && descriptionsStyles.itemBordered,\n descriptionsStyles.item,\n layout !== 'inline' &&\n span > 1 &&\n (auto()\n ? descriptionsGridStyles.autoSpan(span)\n : descriptionsGridStyles.span(span)),\n ],\n },\n own,\n )}\n >\n <dt\n {...mergeAttrs<'dt'>(\n {\n sx: [\n descriptionsStyles.label,\n bordered && [\n descriptionsStyles.cellBordered,\n descriptionsStyles.labelBordered,\n ],\n ],\n },\n propsLabel,\n )}\n >\n {item.label}\n </dt>\n <dd\n {...mergeAttrs<'dd'>(\n {\n sx: [\n descriptionsStyles.value,\n bordered && descriptionsStyles.cellBordered,\n item.copyable && descriptionsStyles.valueCopyable,\n ],\n },\n propsValue,\n )}\n >\n {item.value}\n {item.copyable && (\n <CopyButton\n {...mergePartProps<Partial<CopyButtonProps>>(\n {\n size: 'xs',\n sx: descriptionsStyles.copy,\n 'aria-label': lang.descriptionsCopy(plainText(item.label) ?? lang.copy),\n },\n propsCopy,\n )}\n value={() => copyTextOf(item)}\n />\n )}\n </dd>\n </div>\n );\n })}\n </dl>\n </div>\n );\n },\n);\n"],"mappings":"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"}
|
package/dist/display/Ellipsis.js
CHANGED
|
@@ -5,8 +5,8 @@ import { focusRingStyles } from "../utils/style/styles.js";
|
|
|
5
5
|
import { batchLayout } from "../utils/dom/layout-batch.js";
|
|
6
6
|
import { useResizeObserver } from "../utils/dom/resize.js";
|
|
7
7
|
import { Tooltip } from "../overlay/Tooltip.js";
|
|
8
|
-
import { createVNode,
|
|
9
|
-
import { _component, bindable, childrenProp, childrenStable, readonlyProp, restProps } from "@zerodep-dev/vue-tsx-pro-tsx";
|
|
8
|
+
import { createVNode, onMounted, onUpdated, ref, shallowRef } from "vue";
|
|
9
|
+
import { _component, bindable, childrenProp, childrenStable, mergeJsxProps, readonlyProp, restProps } from "@zerodep-dev/vue-tsx-pro-tsx";
|
|
10
10
|
import * as css from "@zerodep-dev/vue-tsx-pro-tsx/styles";
|
|
11
11
|
//#region src/display/Ellipsis.tsx
|
|
12
12
|
/** 未声明的属性落到外层 span 上 */
|
|
@@ -78,7 +78,7 @@ var Ellipsis = /*#__PURE__*/ _component((_props, _ctx) => {
|
|
|
78
78
|
tabindex: showTooltip ? 0 : void 0,
|
|
79
79
|
...props
|
|
80
80
|
}), [children.value]);
|
|
81
|
-
return createVNode("span",
|
|
81
|
+
return createVNode("span", mergeJsxProps(mergeAttrs({ sx: [ellipsisStyles.root, lines.value > 1 && ellipsisStyles.multiline] }, rest), {
|
|
82
82
|
"data-truncated": truncated.value || void 0,
|
|
83
83
|
"data-expanded": expanded.value || void 0
|
|
84
84
|
}), [showTooltip ? createVNode(Tooltip, {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Ellipsis.js","names":["_vtp_readonlyProp","_vtp_bindable","_vtp_childrenProp","_vtp_restProps","_vue_shallowRef","_vue_ref","_createVNode","_mergeProps","_vtp_childrenStable"],"sources":["../../src/display/Ellipsis.tsx"],"sourcesContent":["/**\n * 截断的文字:超出一行(或 lines 行)时省略,真的被截断了才给出看全文的办法——悬停或聚焦时的提示,或展开按钮。\n * 表格里的长名称、卡片里的描述、列表里的备注。\n *\n * <Ellipsis>杭州远山精密机械有限公司(萧山二厂)</Ellipsis> 一行,截断时悬停看全文\n * <Ellipsis lines={3} expandable>{description}</Ellipsis> 三行,截断时后面有“展开”\n *\n * 是否截断由 ResizeObserver 量出(内容或宽度变了重新量),没截断时就是普通文字:不能聚焦、没有提示、没有按钮。\n * 截断时文字能用 Tab 聚焦(键盘用户也能看全文),提示的内容默认是 children,tooltip 可以换成别的;\n * expandable 时展开后显示全部,再点“收起”。部件:Root(span)、Text(span,截断的那段)、Toggle(展开按钮)。\n */\nimport { _component } from '@zerodep-dev/vue-tsx-pro-tsx';\nimport * as css from '@zerodep-dev/vue-tsx-pro-tsx/styles';\nimport { onMounted, onUpdated, type VNodeChild } from 'vue';\nimport { useLang } from '../config/context';\nimport { Tooltip } from '../overlay/Tooltip';\nimport { useResizeObserver } from '../utils/dom/resize';\nimport { batchLayout } from '../utils/dom/layout-batch';\nimport { mergeAttrs, type PartProps } from '../utils/props';\nimport { focusRingStyles } from '../utils/style/styles';\nimport { lineClampStyles, textStyles } from '../utils/style/typography';\n\n/** 未声明的属性落到外层 span 上 */\nexport interface EllipsisProps extends PartProps<'span'> {\n /** 最多几行,默认 1 */\n lines?: number;\n /** 截断时后面放“展开”按钮,展开后显示全部 */\n expandable?: boolean;\n /** 是否展开:父级可以写 _expanded={x} */\n _expanded?: boolean;\n /** 截断时悬停、聚焦显示的提示,默认是 children;false 不提示(expandable 时默认不提示) */\n tooltip?: VNodeChild | false;\n /** 截断的那段文字的部件属性 */\n propsText?: PartProps<'span'>;\n /** 展开按钮的部件属性 */\n propsToggle?: PartProps<'button'>;\n /** 文字 */\n children?: VNodeChild;\n}\n\n/** Ellipsis 的样式:root、multiline、text、toggle */\nexport const ellipsisStyles = css.create({\n root: {\n display: 'inline-flex',\n alignItems: 'baseline',\n gap: '_xs',\n minWidth: 0,\n maxWidth: '100%',\n },\n // 多行时块级:段落里的描述\n multiline: { display: 'flex', alignItems: 'flex-end' },\n text: { minWidth: 0, borderRadius: '_xs' },\n // 展开按钮:链接样式,不换行\n toggle: {\n flexShrink: 0,\n padding: 0,\n borderWidth: 0,\n backgroundColor: 'transparent',\n color: '_accentText',\n font: 'inherit',\n cursor: 'pointer',\n whiteSpace: 'nowrap',\n textDecorationLine: { default: 'none', ':hover': 'underline' },\n },\n});\n\n/** 截断的文字:真的截断了才有提示或展开按钮 */\nexport const Ellipsis = _component(\n ({\n lines = 1,\n expandable = false,\n _expanded: expanded = _bindable(false),\n tooltip,\n propsText,\n propsToggle,\n children,\n ...rest\n }: EllipsisProps) => {\n const lang = useLang();\n let text = _state.raw<HTMLElement | null>(null);\n let truncated = _state(false);\n\n function measure() {\n batchLayout(() => {\n const element = text;\n if (!element || expanded) return;\n // 多行比高度、单行比宽度;差 1px 以内是小数取整\n const over =\n lines > 1\n ? element.scrollHeight > element.clientHeight + 1\n : element.scrollWidth > element.clientWidth + 1;\n return () => (truncated = over);\n });\n }\n useResizeObserver(() => text, measure);\n // 挂载后量一次;每次重新渲染(内容、行数、展开变了)后再量:截断后的大小可能不变,ResizeObserver 不会通知。\n // 不 watch children:在渲染函数外读 children 会调用插槽,Vue 会警告且不跟踪依赖\n onMounted(measure);\n onUpdated(measure);\n\n return () => {\n const clamped = !expanded;\n const showTooltip =\n truncated && clamped && tooltip !== false && (tooltip !== undefined || !expandable);\n const body = (props: Record<string, unknown> = {}) => (\n <span\n {...mergeAttrs<'span'>(\n {\n sx: [\n ellipsisStyles.text,\n clamped &&\n (lines > 1\n ? [textStyles.clamp, lineClampStyles.lines(lines)]\n : textStyles.truncate),\n showTooltip && focusRingStyles.visible,\n ],\n },\n propsText,\n {\n ref: _toRef(text),\n // 截断时能聚焦:键盘用户也能看到提示里的全文\n tabindex: showTooltip ? 0 : undefined,\n ...props,\n },\n )}\n >\n {children}\n </span>\n );\n return (\n <span\n {...mergeAttrs<'span'>(\n { sx: [ellipsisStyles.root, lines > 1 && ellipsisStyles.multiline] },\n rest,\n )}\n data-truncated={truncated || undefined}\n data-expanded={expanded || undefined}\n >\n {showTooltip ? (\n <Tooltip trigger={(props) => body(props as unknown as Record<string, unknown>)}>\n {tooltip ?? children}\n </Tooltip>\n ) : (\n body()\n )}\n {expandable && (truncated || expanded) && (\n <button\n {...mergeAttrs<'button'>({ sx: ellipsisStyles.toggle }, propsToggle, {\n type: 'button',\n 'aria-expanded': expanded,\n onClick: () => (expanded = !expanded),\n })}\n >\n {expanded ? lang.ellipsisCollapse : lang.ellipsisExpand}\n </button>\n )}\n </span>\n );\n };\n },\n);\n"],"mappings":";;;;;;;;;;;;;AAyCA,IAAa,iBAAiB,IAAI,OAAO;CACvC,MAAM;EACJ,SAAS;EACT,YAAY;EACZ,KAAK;EACL,UAAU;EACV,UAAU;CACZ;CAEA,WAAW;EAAE,SAAS;EAAQ,YAAY;CAAW;CACrD,MAAM;EAAE,UAAU;EAAG,cAAc;CAAM;CAEzC,QAAQ;EACN,YAAY;EACZ,SAAS;EACT,aAAa;EACb,iBAAiB;EACjB,OAAO;EACP,MAAM;EACN,QAAQ;EACR,YAAY;EACZ,oBAAoB;GAAE,SAAS;GAAQ,UAAU;EAAY;CAC/D;AACF,CAAC;;AAGD,IAAa,WAAW,0BACtB,QAAA,SASqB;CAAA,MAAA,QAAAA,aAAA,QAAA,SARX,CAAC;CAAA,MAAA,aAAAA,aAAA,QAAA,cACI,KAAK;CAAA,MAAA,WAAAC,SAAA,QAAA,mBACc,OAAK,KAAA,IAAA;CAAA,MAAA,UAAAD,aAAA,QAAA,SAAA;CAAA,MAAA,YAAAA,aAAA,QAAA,WAAA;CAAA,MAAA,cAAAA,aAAA,QAAA,aAAA;CAAA,MAAA,WAAAE,aAAA,MAAA;CAAA,MAKlC,OAAIC,UAAA,QAAA,MAAA,KAAA;CAEP,MAAM,OAAO,QAAQ;CACrB,IAAI,OAAIC,WAAkC,IAAI;CAC9C,IAAI,YAASC,IAAU,KAAK;CAE5B,SAAS,UAAU;EACjB,kBAAkB;GAChB,MAAM,UAAO,KAAA;GACb,IAAI,CAAC,WAAO,SAAA,OAAc;GAE1B,MAAM,OACJ,MAAA,QAAQ,IACJ,QAAQ,eAAe,QAAQ,eAAe,IAC9C,QAAQ,cAAc,QAAQ,cAAc;GAClD,aAAc,UAAA,QAAY;EAC5B,CAAC;CACH;CACA,wBAAkB,KAAA,OAAY,OAAO;CAGrC,UAAU,OAAO;CACjB,UAAU,OAAO;CAEjB,aAAa;EACX,MAAM,UAAU,CAAA,SAAA;EAChB,MAAM,cACJ,UAAA,SAAa,WAAW,QAAA,UAAY,UAAU,QAAA,UAAY,KAAA,KAAa,CAAA,WAAA;EACzE,MAAM,QAAQ,QAAiC,CAAC,MAACC,YAAA,QAEzC,WACF,EACE,IAAI;GACF,eAAe;GACf,YACG,MAAA,QAAQ,IACL,CAAC,WAAW,OAAO,gBAAgB,MAAK,MAAA,KAAM,CAAC,IAC/C,WAAW;GACjB,eAAe,gBAAgB;EAAO,EAE1C,GAAC,UAAA,OAED;GACE,KAAG;GAEH,UAAU,cAAc,IAAI,KAAA;GAC5B,GAAG;EACL,CACF,GAAC,CAAA,SAAA,KAAA,CAAA;EAKL,OAAAA,YAAA,QAAAC,
|
|
1
|
+
{"version":3,"file":"Ellipsis.js","names":["_vtp_readonlyProp","_vtp_bindable","_vtp_childrenProp","_vtp_restProps","_vue_shallowRef","_vue_ref","_createVNode","_mergeProps","_vtp_childrenStable"],"sources":["../../src/display/Ellipsis.tsx"],"sourcesContent":["/**\n * 截断的文字:超出一行(或 lines 行)时省略,真的被截断了才给出看全文的办法——悬停或聚焦时的提示,或展开按钮。\n * 表格里的长名称、卡片里的描述、列表里的备注。\n *\n * <Ellipsis>杭州远山精密机械有限公司(萧山二厂)</Ellipsis> 一行,截断时悬停看全文\n * <Ellipsis lines={3} expandable>{description}</Ellipsis> 三行,截断时后面有“展开”\n *\n * 是否截断由 ResizeObserver 量出(内容或宽度变了重新量),没截断时就是普通文字:不能聚焦、没有提示、没有按钮。\n * 截断时文字能用 Tab 聚焦(键盘用户也能看全文),提示的内容默认是 children,tooltip 可以换成别的;\n * expandable 时展开后显示全部,再点“收起”。部件:Root(span)、Text(span,截断的那段)、Toggle(展开按钮)。\n */\nimport { _component } from '@zerodep-dev/vue-tsx-pro-tsx';\nimport * as css from '@zerodep-dev/vue-tsx-pro-tsx/styles';\nimport { onMounted, onUpdated, type VNodeChild } from 'vue';\nimport { useLang } from '../config/context';\nimport { Tooltip } from '../overlay/Tooltip';\nimport { useResizeObserver } from '../utils/dom/resize';\nimport { batchLayout } from '../utils/dom/layout-batch';\nimport { mergeAttrs, type PartProps } from '../utils/props';\nimport { focusRingStyles } from '../utils/style/styles';\nimport { lineClampStyles, textStyles } from '../utils/style/typography';\n\n/** 未声明的属性落到外层 span 上 */\nexport interface EllipsisProps extends PartProps<'span'> {\n /** 最多几行,默认 1 */\n lines?: number;\n /** 截断时后面放“展开”按钮,展开后显示全部 */\n expandable?: boolean;\n /** 是否展开:父级可以写 _expanded={x} */\n _expanded?: boolean;\n /** 截断时悬停、聚焦显示的提示,默认是 children;false 不提示(expandable 时默认不提示) */\n tooltip?: VNodeChild | false;\n /** 截断的那段文字的部件属性 */\n propsText?: PartProps<'span'>;\n /** 展开按钮的部件属性 */\n propsToggle?: PartProps<'button'>;\n /** 文字 */\n children?: VNodeChild;\n}\n\n/** Ellipsis 的样式:root、multiline、text、toggle */\nexport const ellipsisStyles = css.create({\n root: {\n display: 'inline-flex',\n alignItems: 'baseline',\n gap: '_xs',\n minWidth: 0,\n maxWidth: '100%',\n },\n // 多行时块级:段落里的描述\n multiline: { display: 'flex', alignItems: 'flex-end' },\n text: { minWidth: 0, borderRadius: '_xs' },\n // 展开按钮:链接样式,不换行\n toggle: {\n flexShrink: 0,\n padding: 0,\n borderWidth: 0,\n backgroundColor: 'transparent',\n color: '_accentText',\n font: 'inherit',\n cursor: 'pointer',\n whiteSpace: 'nowrap',\n textDecorationLine: { default: 'none', ':hover': 'underline' },\n },\n});\n\n/** 截断的文字:真的截断了才有提示或展开按钮 */\nexport const Ellipsis = _component(\n ({\n lines = 1,\n expandable = false,\n _expanded: expanded = _bindable(false),\n tooltip,\n propsText,\n propsToggle,\n children,\n ...rest\n }: EllipsisProps) => {\n const lang = useLang();\n let text = _state.raw<HTMLElement | null>(null);\n let truncated = _state(false);\n\n function measure() {\n batchLayout(() => {\n const element = text;\n if (!element || expanded) return;\n // 多行比高度、单行比宽度;差 1px 以内是小数取整\n const over =\n lines > 1\n ? element.scrollHeight > element.clientHeight + 1\n : element.scrollWidth > element.clientWidth + 1;\n return () => (truncated = over);\n });\n }\n useResizeObserver(() => text, measure);\n // 挂载后量一次;每次重新渲染(内容、行数、展开变了)后再量:截断后的大小可能不变,ResizeObserver 不会通知。\n // 不 watch children:在渲染函数外读 children 会调用插槽,Vue 会警告且不跟踪依赖\n onMounted(measure);\n onUpdated(measure);\n\n return () => {\n const clamped = !expanded;\n const showTooltip =\n truncated && clamped && tooltip !== false && (tooltip !== undefined || !expandable);\n const body = (props: Record<string, unknown> = {}) => (\n <span\n {...mergeAttrs<'span'>(\n {\n sx: [\n ellipsisStyles.text,\n clamped &&\n (lines > 1\n ? [textStyles.clamp, lineClampStyles.lines(lines)]\n : textStyles.truncate),\n showTooltip && focusRingStyles.visible,\n ],\n },\n propsText,\n {\n ref: _toRef(text),\n // 截断时能聚焦:键盘用户也能看到提示里的全文\n tabindex: showTooltip ? 0 : undefined,\n ...props,\n },\n )}\n >\n {children}\n </span>\n );\n return (\n <span\n {...mergeAttrs<'span'>(\n { sx: [ellipsisStyles.root, lines > 1 && ellipsisStyles.multiline] },\n rest,\n )}\n data-truncated={truncated || undefined}\n data-expanded={expanded || undefined}\n >\n {showTooltip ? (\n <Tooltip trigger={(props) => body(props as unknown as Record<string, unknown>)}>\n {tooltip ?? children}\n </Tooltip>\n ) : (\n body()\n )}\n {expandable && (truncated || expanded) && (\n <button\n {...mergeAttrs<'button'>({ sx: ellipsisStyles.toggle }, propsToggle, {\n type: 'button',\n 'aria-expanded': expanded,\n onClick: () => (expanded = !expanded),\n })}\n >\n {expanded ? lang.ellipsisCollapse : lang.ellipsisExpand}\n </button>\n )}\n </span>\n );\n };\n },\n);\n"],"mappings":";;;;;;;;;;;;;AAyCA,IAAa,iBAAiB,IAAI,OAAO;CACvC,MAAM;EACJ,SAAS;EACT,YAAY;EACZ,KAAK;EACL,UAAU;EACV,UAAU;CACZ;CAEA,WAAW;EAAE,SAAS;EAAQ,YAAY;CAAW;CACrD,MAAM;EAAE,UAAU;EAAG,cAAc;CAAM;CAEzC,QAAQ;EACN,YAAY;EACZ,SAAS;EACT,aAAa;EACb,iBAAiB;EACjB,OAAO;EACP,MAAM;EACN,QAAQ;EACR,YAAY;EACZ,oBAAoB;GAAE,SAAS;GAAQ,UAAU;EAAY;CAC/D;AACF,CAAC;;AAGD,IAAa,WAAW,0BACtB,QAAA,SASqB;CAAA,MAAA,QAAAA,aAAA,QAAA,SARX,CAAC;CAAA,MAAA,aAAAA,aAAA,QAAA,cACI,KAAK;CAAA,MAAA,WAAAC,SAAA,QAAA,mBACc,OAAK,KAAA,IAAA;CAAA,MAAA,UAAAD,aAAA,QAAA,SAAA;CAAA,MAAA,YAAAA,aAAA,QAAA,WAAA;CAAA,MAAA,cAAAA,aAAA,QAAA,aAAA;CAAA,MAAA,WAAAE,aAAA,MAAA;CAAA,MAKlC,OAAIC,UAAA,QAAA,MAAA,KAAA;CAEP,MAAM,OAAO,QAAQ;CACrB,IAAI,OAAIC,WAAkC,IAAI;CAC9C,IAAI,YAASC,IAAU,KAAK;CAE5B,SAAS,UAAU;EACjB,kBAAkB;GAChB,MAAM,UAAO,KAAA;GACb,IAAI,CAAC,WAAO,SAAA,OAAc;GAE1B,MAAM,OACJ,MAAA,QAAQ,IACJ,QAAQ,eAAe,QAAQ,eAAe,IAC9C,QAAQ,cAAc,QAAQ,cAAc;GAClD,aAAc,UAAA,QAAY;EAC5B,CAAC;CACH;CACA,wBAAkB,KAAA,OAAY,OAAO;CAGrC,UAAU,OAAO;CACjB,UAAU,OAAO;CAEjB,aAAa;EACX,MAAM,UAAU,CAAA,SAAA;EAChB,MAAM,cACJ,UAAA,SAAa,WAAW,QAAA,UAAY,UAAU,QAAA,UAAY,KAAA,KAAa,CAAA,WAAA;EACzE,MAAM,QAAQ,QAAiC,CAAC,MAACC,YAAA,QAEzC,WACF,EACE,IAAI;GACF,eAAe;GACf,YACG,MAAA,QAAQ,IACL,CAAC,WAAW,OAAO,gBAAgB,MAAK,MAAA,KAAM,CAAC,IAC/C,WAAW;GACjB,eAAe,gBAAgB;EAAO,EAE1C,GAAC,UAAA,OAED;GACE,KAAG;GAEH,UAAU,cAAc,IAAI,KAAA;GAC5B,GAAG;EACL,CACF,GAAC,CAAA,SAAA,KAAA,CAAA;EAKL,OAAAA,YAAA,QAAAC,cAEQ,WACF,EAAE,IAAI,CAAC,eAAe,MAAM,MAAA,QAAQ,KAAK,eAAe,SAAS,EAAE,GACnE,IACF,GAAC;GAAA,kBACe,UAAA,SAAa,KAAA;GAAS,iBACvB,SAAA,SAAY,KAAA;EAAS,CAAA,GAAA,CAEnC,cAAWD,YAAA,SAAA;GAAA,YACS,UAAU,KAAK,KAA2C;GAAC,OAAA;EAAA,GAAA;GAAA,eAAA,CAC3E,QAAA,SAAA,SAAA,KAAmB;GAAA,SAAAE,eAAA;EAAA,CAAA,IAGtB,KAAK,GAEN,WAAA,UAAe,UAAA,SAAA,SAAA,UAAsBF,YAAA,UAE9B,WAAqB,EAAE,IAAI,eAAe,OAAO,GAAC,YAAA,OAAe;GACnE,MAAM;GACN,iBAAe,SAAA;GACf,eAAgB,SAAA,QAAW,CAAA,SAAA;EAC7B,CAAC,GAAC,CAED,SAAA,QAAW,KAAK,mBAAmB,KAAK,cAAc,CAAA,CAE1D,CAAA;CAGP;AACF,GAAC;CAAA,MAAA;CAAA,OAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;CAAA;AAAA,CACH"}
|
|
@@ -9,8 +9,8 @@ import { diffFields } from "../utils/data/diff.js";
|
|
|
9
9
|
import { columnType, columnValue, formatCell } from "../table/columns.js";
|
|
10
10
|
import { useColumnDisplay } from "../table/column-display.js";
|
|
11
11
|
import { ColumnTable } from "../table/ColumnTable.js";
|
|
12
|
-
import { Fragment, createVNode
|
|
13
|
-
import { _component, readonlyProp, restProps } from "@zerodep-dev/vue-tsx-pro-tsx";
|
|
12
|
+
import { Fragment, createVNode } from "vue";
|
|
13
|
+
import { _component, mergeJsxProps, readonlyProp, restProps } from "@zerodep-dev/vue-tsx-pro-tsx";
|
|
14
14
|
import * as css from "@zerodep-dev/vue-tsx-pro-tsx/styles";
|
|
15
15
|
import { ArrowRight } from "@lucide/icons";
|
|
16
16
|
//#region src/display/FieldCompare.tsx
|
|
@@ -158,14 +158,14 @@ var FieldCompare = /*#__PURE__*/ _component((_props, _ctx) => {
|
|
|
158
158
|
cell: change
|
|
159
159
|
}] : []
|
|
160
160
|
];
|
|
161
|
-
return createVNode(ColumnTable,
|
|
161
|
+
return createVNode(ColumnTable, mergeJsxProps(mergePartProps({ density: "compact" }, propsTable.value), {
|
|
162
162
|
"rows": list,
|
|
163
163
|
"columns": columns,
|
|
164
164
|
"getKey": _cache[0] || (_cache[0] = (row) => row.field.key),
|
|
165
165
|
"propsRow": _cache[1] || (_cache[1] = (row) => ({ "data-changed": row.changed || void 0 }))
|
|
166
166
|
}), null);
|
|
167
167
|
};
|
|
168
|
-
const list = (items) => createVNode("dl", mergeAttrs({ sx: fieldCompareStyles.list }, propsList.value), [items.map((row) => createVNode("div",
|
|
168
|
+
const list = (items) => createVNode("dl", mergeAttrs({ sx: fieldCompareStyles.list }, propsList.value), [items.map((row) => createVNode("div", mergeJsxProps({ "key": row.field.key }, mergeAttrs({ sx: fieldCompareStyles.item }, propsItem.value)), [createVNode("dt", mergeAttrs({ sx: fieldCompareStyles.label }, propsLabel.value), [row.field.header]), createVNode("dd", mergeAttrs({ sx: fieldCompareStyles.value }, propsValue.value), [
|
|
169
169
|
before.value != null && createVNode(Fragment, null, [
|
|
170
170
|
createVNode("del", css.attrs(fieldCompareStyles.before), [shown(row, 0)]),
|
|
171
171
|
createVNode(Icon, {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FieldCompare.js","names":["_vtp_readonlyProp","_vtp_restProps","_createVNode","_mergeProps","_Fragment"],"sources":["../../src/display/FieldCompare.tsx"],"sourcesContent":["/**\n * 字段对比:两份记录(编辑前后、测算的两个版本、审批前后)按字段列出原值与新值,数值字段带上变化量与变化率。\n * 建在 diffFields 上;字段的写法与表格的列定义相同(key、header、type、format、statuses、empty、value、cell),\n * 表格里已有的列定义可以直接拿来用。\n *\n * <FieldCompare fields={columns} before={v1} after={v2} /> // 表格:字段、原值、新值、变化\n * <FieldCompare fields={columns} before={old} after={row} layout=\"list\" /> // 列表:只列改了的,“¥1,200 → ¥1,350 +12.5%”\n *\n * 表格默认列出全部字段(没改的淡显),列表默认只列改了的(放在时间线、通知、审批意见里);onlyChanged 可以改。\n * 数值字段的变化量按字段的 type、format 显示,带正负号;写了 better 时按好坏着色(成本、耗时写 'lower')。\n *\n * 读屏:列表里原值是 del、新值是 ins,两者之间有“改为”(画面上是箭头)。\n * 部件:Root(div)、Table(ColumnTable,表格时)、List(dl,列表时)、Item(div)、Label(dt)、Value(dd)。\n */\nimport { ArrowRight } from '@lucide/icons';\nimport { _component } from '@zerodep-dev/vue-tsx-pro-tsx';\nimport * as css from '@zerodep-dev/vue-tsx-pro-tsx/styles';\nimport type { VNodeChild } from 'vue';\nimport { Icon } from '../base/Icon';\nimport { VisuallyHidden } from '../base/VisuallyHidden';\nimport { useLang } from '../config/context';\nimport { useFormat } from '../config/lang/format';\nimport { diffFields, type FieldDiff } from '../utils/data/diff';\nimport { toNumber } from '../utils/data/numeric';\nimport type { SortValue } from '../utils/data/collation';\nimport { mergeAttrs, mergePartProps, type PartProps } from '../utils/props';\nimport { toneStyles } from '../utils/style/typography';\nimport { useColumnDisplay } from '../table/column-display';\nimport { ColumnTable, type ColumnTableProps } from '../table/ColumnTable';\nimport {\n columnType,\n columnValue,\n formatCell,\n type ColumnTypes,\n type DataTableColumn,\n} from '../table/columns';\n\n/**\n * 一个要对比的字段:写法同表格的列(key、header、type、format、statuses、empty、value),\n * cell 的第二个参数是 0(原值那一份)或 1(新值那一份)\n */\nexport interface CompareField<T> extends Pick<\n DataTableColumn<T>,\n 'key' | 'header' | 'type' | 'format' | 'statuses' | 'empty' | 'value' | 'cell'\n> {\n /** 数值变化的好坏:'higher' 越高越好(变化量升是绿、降是红),'lower' 越低越好(成本、耗时);不写时不着色 */\n better?: 'higher' | 'lower';\n}\n\n/** 对比的一行:字段与它的比较结果 */\nexport interface FieldCompareRow<T> extends FieldDiff<Record<string, SortValue>, string> {\n /** 字段 */\n field: CompareField<T>;\n}\n\n/** 未声明的属性落到外层 div 上 */\nexport interface FieldCompareProps<T> extends PartProps<'div'> {\n /** 对比哪些字段,按这个顺序 */\n fields: readonly CompareField<T>[];\n /** 原来的记录;没有(新建)时每个字段都当作空 */\n before: T | null | undefined;\n /** 现在的记录 */\n after: T | null | undefined;\n /** 'table' 字段、原值、新值、变化四列(默认);'list' 一行一个字段:原值 → 新值 变化 */\n layout?: 'table' | 'list';\n /** 只列改了的字段;默认表格列出全部、列表只列改了的 */\n onlyChanged?: boolean;\n /** 表格的部件属性(layout 为 'table' 时) */\n propsTable?: Partial<ColumnTableProps<FieldCompareRow<T>>>;\n /** 列表(dl)的部件属性(layout 为 'list' 时) */\n propsList?: PartProps<'dl'>;\n /** 列表里每一项的部件属性 */\n propsItem?: PartProps<'div'>;\n /** 字段名(dt)的部件属性 */\n propsLabel?: PartProps<'dt'>;\n /** 值(dd)的部件属性 */\n propsValue?: PartProps<'dd'>;\n}\n\n/** FieldCompare 的样式:list、item、label、value、before、after、arrow、unchanged、changed */\nexport const fieldCompareStyles = css.create({\n // 字段名一列、值一列:多项时字段名对齐\n list: {\n display: 'grid',\n gridTemplateColumns: 'auto 1fr',\n columnGap: '_md',\n rowGap: '_xs',\n margin: 0,\n },\n item: { display: 'contents' },\n label: { color: '_textMuted' },\n value: {\n display: 'flex',\n flexWrap: 'wrap',\n alignItems: 'baseline',\n columnGap: '_xs',\n margin: 0,\n minWidth: 0,\n overflowWrap: 'anywhere',\n },\n before: { color: '_textMuted', textDecorationLine: 'line-through' },\n after: { textDecorationLine: 'none', color: '_text', fontWeight: '_medium' },\n arrow: { alignSelf: 'center', color: '_textMuted', flexShrink: 0 },\n change: { fontSize: '_sm', fontVariantNumeric: 'tabular-nums' },\n // 表格里没改的字段淡显,改了的新值加粗\n unchanged: { color: '_textMuted' },\n changed: { fontWeight: '_medium' },\n none: { color: '_textMuted' },\n});\n\n/** 字段对比:表格或列表 */\nexport const FieldCompare = _component(\n <T,>({\n fields,\n before,\n after,\n layout = 'table',\n onlyChanged,\n propsTable,\n propsList,\n propsItem,\n propsLabel,\n propsValue,\n ...rest\n }: FieldCompareProps<T>) => {\n const lang = useLang();\n const format = useFormat();\n const display = useColumnDisplay<T>();\n const column = (field: CompareField<T>) => field as DataTableColumn<T>;\n\n /** 每个字段在一份记录里的值(按 value 函数或字段名取) */\n const valuesOf = (record: T | null | undefined) =>\n Object.fromEntries(\n fields.map((field) => [\n field.key,\n record == null ? undefined : columnValue(column(field), record),\n ]),\n );\n\n const rows = (): FieldCompareRow<T>[] => {\n const diffs = diffFields(\n valuesOf(before),\n valuesOf(after),\n fields.map((field) => field.key),\n );\n const all = diffs.map((diff, index) => ({ ...diff, field: fields[index]! }));\n return (onlyChanged ?? layout === 'list') ? all.filter((row) => row.changed) : all;\n };\n\n /** 字段的值类型:写了 type 用它,否则按两份记录推断(同表格的列) */\n const typeOf = (field: CompareField<T>): keyof ColumnTypes =>\n columnType(\n column(field),\n [before, after].filter((record): record is T => record != null),\n );\n\n /** 一份记录里这个字段的显示:cell 优先,否则按类型格式化(同表格) */\n const shown = (row: FieldCompareRow<T>, side: 0 | 1): VNodeChild => {\n const record = side === 0 ? before : after;\n if (row.field.cell && record != null) return row.field.cell(record, side);\n return display.value(\n column(row.field),\n typeOf(row.field),\n side === 0 ? row.before : row.after,\n );\n };\n\n /** 数值字段的变化:变化量(按字段的格式,带正负号)与变化率;没变或不是数值时没有 */\n const change = (row: FieldCompareRow<T>): VNodeChild => {\n if (!row.changed || row.delta === undefined) return null;\n const type = typeOf(row.field);\n const signed = { ...row.field.format, signDisplay: 'exceptZero' } as const;\n const delta = formatCell(row.delta, type === 'text' ? 'number' : type, format, signed);\n // 百分比字段的变化量本身就是百分点,不再给变化率\n const ratio =\n row.ratio !== undefined && type !== 'percent'\n ? format.number(row.ratio, {\n style: 'percent',\n maximumFractionDigits: 1,\n signDisplay: 'exceptZero',\n })\n : null;\n const sign = Math.sign(toNumber(row.delta));\n const tone =\n !row.field.better || sign === 0\n ? toneStyles.muted\n : sign > 0 === (row.field.better === 'higher')\n ? toneStyles.success\n : toneStyles.danger;\n return (\n <span {...css.attrs(fieldCompareStyles.change, tone)}>\n {delta}\n {ratio && `(${ratio})`}\n </span>\n );\n };\n\n const none = () => <span {...css.attrs(fieldCompareStyles.none)}>{lang.fieldCompareNone}</span>;\n\n const table = (list: FieldCompareRow<T>[]) => {\n const [field, from, to, delta] = lang.fieldCompareHeaders;\n const numeric = list.some((row) => row.changed && row.delta !== undefined);\n const columns: DataTableColumn<FieldCompareRow<T>>[] = [\n { key: 'field', header: field, cell: (row) => row.field.header },\n {\n key: 'before',\n align: 'start',\n header: from,\n cell: (row) => <span {...css.attrs(fieldCompareStyles.unchanged)}>{shown(row, 0)}</span>,\n },\n {\n key: 'after',\n align: 'start',\n header: to,\n cell: (row) => (\n <span\n {...css.attrs(\n row.changed ? fieldCompareStyles.changed : fieldCompareStyles.unchanged,\n )}\n >\n {shown(row, 1)}\n </span>\n ),\n },\n ...(numeric\n ? [{ key: 'change', header: delta, align: 'start' as const, cell: change }]\n : []),\n ];\n return (\n <ColumnTable\n {...mergePartProps<Partial<ColumnTableProps<FieldCompareRow<T>>>>(\n { density: 'compact' },\n propsTable,\n )}\n rows={list}\n columns={columns}\n getKey={(row) => row.field.key}\n propsRow={(row) => ({ 'data-changed': row.changed || undefined }) as PartProps<'tr'>}\n />\n );\n };\n\n const list = (items: FieldCompareRow<T>[]) => (\n <dl {...mergeAttrs<'dl'>({ sx: fieldCompareStyles.list }, propsList)}>\n {items.map((row) => (\n <div\n key={row.field.key}\n {...mergeAttrs<'div'>({ sx: fieldCompareStyles.item }, propsItem)}\n >\n <dt {...mergeAttrs<'dt'>({ sx: fieldCompareStyles.label }, propsLabel)}>\n {row.field.header}\n </dt>\n <dd {...mergeAttrs<'dd'>({ sx: fieldCompareStyles.value }, propsValue)}>\n {before != null && (\n <>\n <del {...css.attrs(fieldCompareStyles.before)}>{shown(row, 0)}</del>\n <Icon icon={ArrowRight} sx={fieldCompareStyles.arrow} />\n <VisuallyHidden>{lang.fieldCompareTo}</VisuallyHidden>\n </>\n )}\n <ins {...css.attrs(fieldCompareStyles.after)}>{shown(row, 1)}</ins>\n {change(row)}\n </dd>\n </div>\n ))}\n </dl>\n );\n\n return () => {\n const items = rows();\n return (\n <div {...mergeAttrs<'div'>({}, rest)}>\n {items.length === 0 ? none() : layout === 'list' ? list(items) : table(items)}\n </div>\n );\n };\n },\n);\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"FieldCompare.js","names":["_vtp_readonlyProp","_vtp_restProps","_createVNode","_mergeProps","_Fragment"],"sources":["../../src/display/FieldCompare.tsx"],"sourcesContent":["/**\n * 字段对比:两份记录(编辑前后、测算的两个版本、审批前后)按字段列出原值与新值,数值字段带上变化量与变化率。\n * 建在 diffFields 上;字段的写法与表格的列定义相同(key、header、type、format、statuses、empty、value、cell),\n * 表格里已有的列定义可以直接拿来用。\n *\n * <FieldCompare fields={columns} before={v1} after={v2} /> // 表格:字段、原值、新值、变化\n * <FieldCompare fields={columns} before={old} after={row} layout=\"list\" /> // 列表:只列改了的,“¥1,200 → ¥1,350 +12.5%”\n *\n * 表格默认列出全部字段(没改的淡显),列表默认只列改了的(放在时间线、通知、审批意见里);onlyChanged 可以改。\n * 数值字段的变化量按字段的 type、format 显示,带正负号;写了 better 时按好坏着色(成本、耗时写 'lower')。\n *\n * 读屏:列表里原值是 del、新值是 ins,两者之间有“改为”(画面上是箭头)。\n * 部件:Root(div)、Table(ColumnTable,表格时)、List(dl,列表时)、Item(div)、Label(dt)、Value(dd)。\n */\nimport { ArrowRight } from '@lucide/icons';\nimport { _component } from '@zerodep-dev/vue-tsx-pro-tsx';\nimport * as css from '@zerodep-dev/vue-tsx-pro-tsx/styles';\nimport type { VNodeChild } from 'vue';\nimport { Icon } from '../base/Icon';\nimport { VisuallyHidden } from '../base/VisuallyHidden';\nimport { useLang } from '../config/context';\nimport { useFormat } from '../config/lang/format';\nimport { diffFields, type FieldDiff } from '../utils/data/diff';\nimport { toNumber } from '../utils/data/numeric';\nimport type { SortValue } from '../utils/data/collation';\nimport { mergeAttrs, mergePartProps, type PartProps } from '../utils/props';\nimport { toneStyles } from '../utils/style/typography';\nimport { useColumnDisplay } from '../table/column-display';\nimport { ColumnTable, type ColumnTableProps } from '../table/ColumnTable';\nimport {\n columnType,\n columnValue,\n formatCell,\n type ColumnTypes,\n type DataTableColumn,\n} from '../table/columns';\n\n/**\n * 一个要对比的字段:写法同表格的列(key、header、type、format、statuses、empty、value),\n * cell 的第二个参数是 0(原值那一份)或 1(新值那一份)\n */\nexport interface CompareField<T> extends Pick<\n DataTableColumn<T>,\n 'key' | 'header' | 'type' | 'format' | 'statuses' | 'empty' | 'value' | 'cell'\n> {\n /** 数值变化的好坏:'higher' 越高越好(变化量升是绿、降是红),'lower' 越低越好(成本、耗时);不写时不着色 */\n better?: 'higher' | 'lower';\n}\n\n/** 对比的一行:字段与它的比较结果 */\nexport interface FieldCompareRow<T> extends FieldDiff<Record<string, SortValue>, string> {\n /** 字段 */\n field: CompareField<T>;\n}\n\n/** 未声明的属性落到外层 div 上 */\nexport interface FieldCompareProps<T> extends PartProps<'div'> {\n /** 对比哪些字段,按这个顺序 */\n fields: readonly CompareField<T>[];\n /** 原来的记录;没有(新建)时每个字段都当作空 */\n before: T | null | undefined;\n /** 现在的记录 */\n after: T | null | undefined;\n /** 'table' 字段、原值、新值、变化四列(默认);'list' 一行一个字段:原值 → 新值 变化 */\n layout?: 'table' | 'list';\n /** 只列改了的字段;默认表格列出全部、列表只列改了的 */\n onlyChanged?: boolean;\n /** 表格的部件属性(layout 为 'table' 时) */\n propsTable?: Partial<ColumnTableProps<FieldCompareRow<T>>>;\n /** 列表(dl)的部件属性(layout 为 'list' 时) */\n propsList?: PartProps<'dl'>;\n /** 列表里每一项的部件属性 */\n propsItem?: PartProps<'div'>;\n /** 字段名(dt)的部件属性 */\n propsLabel?: PartProps<'dt'>;\n /** 值(dd)的部件属性 */\n propsValue?: PartProps<'dd'>;\n}\n\n/** FieldCompare 的样式:list、item、label、value、before、after、arrow、unchanged、changed */\nexport const fieldCompareStyles = css.create({\n // 字段名一列、值一列:多项时字段名对齐\n list: {\n display: 'grid',\n gridTemplateColumns: 'auto 1fr',\n columnGap: '_md',\n rowGap: '_xs',\n margin: 0,\n },\n item: { display: 'contents' },\n label: { color: '_textMuted' },\n value: {\n display: 'flex',\n flexWrap: 'wrap',\n alignItems: 'baseline',\n columnGap: '_xs',\n margin: 0,\n minWidth: 0,\n overflowWrap: 'anywhere',\n },\n before: { color: '_textMuted', textDecorationLine: 'line-through' },\n after: { textDecorationLine: 'none', color: '_text', fontWeight: '_medium' },\n arrow: { alignSelf: 'center', color: '_textMuted', flexShrink: 0 },\n change: { fontSize: '_sm', fontVariantNumeric: 'tabular-nums' },\n // 表格里没改的字段淡显,改了的新值加粗\n unchanged: { color: '_textMuted' },\n changed: { fontWeight: '_medium' },\n none: { color: '_textMuted' },\n});\n\n/** 字段对比:表格或列表 */\nexport const FieldCompare = _component(\n <T,>({\n fields,\n before,\n after,\n layout = 'table',\n onlyChanged,\n propsTable,\n propsList,\n propsItem,\n propsLabel,\n propsValue,\n ...rest\n }: FieldCompareProps<T>) => {\n const lang = useLang();\n const format = useFormat();\n const display = useColumnDisplay<T>();\n const column = (field: CompareField<T>) => field as DataTableColumn<T>;\n\n /** 每个字段在一份记录里的值(按 value 函数或字段名取) */\n const valuesOf = (record: T | null | undefined) =>\n Object.fromEntries(\n fields.map((field) => [\n field.key,\n record == null ? undefined : columnValue(column(field), record),\n ]),\n );\n\n const rows = (): FieldCompareRow<T>[] => {\n const diffs = diffFields(\n valuesOf(before),\n valuesOf(after),\n fields.map((field) => field.key),\n );\n const all = diffs.map((diff, index) => ({ ...diff, field: fields[index]! }));\n return (onlyChanged ?? layout === 'list') ? all.filter((row) => row.changed) : all;\n };\n\n /** 字段的值类型:写了 type 用它,否则按两份记录推断(同表格的列) */\n const typeOf = (field: CompareField<T>): keyof ColumnTypes =>\n columnType(\n column(field),\n [before, after].filter((record): record is T => record != null),\n );\n\n /** 一份记录里这个字段的显示:cell 优先,否则按类型格式化(同表格) */\n const shown = (row: FieldCompareRow<T>, side: 0 | 1): VNodeChild => {\n const record = side === 0 ? before : after;\n if (row.field.cell && record != null) return row.field.cell(record, side);\n return display.value(\n column(row.field),\n typeOf(row.field),\n side === 0 ? row.before : row.after,\n );\n };\n\n /** 数值字段的变化:变化量(按字段的格式,带正负号)与变化率;没变或不是数值时没有 */\n const change = (row: FieldCompareRow<T>): VNodeChild => {\n if (!row.changed || row.delta === undefined) return null;\n const type = typeOf(row.field);\n const signed = { ...row.field.format, signDisplay: 'exceptZero' } as const;\n const delta = formatCell(row.delta, type === 'text' ? 'number' : type, format, signed);\n // 百分比字段的变化量本身就是百分点,不再给变化率\n const ratio =\n row.ratio !== undefined && type !== 'percent'\n ? format.number(row.ratio, {\n style: 'percent',\n maximumFractionDigits: 1,\n signDisplay: 'exceptZero',\n })\n : null;\n const sign = Math.sign(toNumber(row.delta));\n const tone =\n !row.field.better || sign === 0\n ? toneStyles.muted\n : sign > 0 === (row.field.better === 'higher')\n ? toneStyles.success\n : toneStyles.danger;\n return (\n <span {...css.attrs(fieldCompareStyles.change, tone)}>\n {delta}\n {ratio && `(${ratio})`}\n </span>\n );\n };\n\n const none = () => <span {...css.attrs(fieldCompareStyles.none)}>{lang.fieldCompareNone}</span>;\n\n const table = (list: FieldCompareRow<T>[]) => {\n const [field, from, to, delta] = lang.fieldCompareHeaders;\n const numeric = list.some((row) => row.changed && row.delta !== undefined);\n const columns: DataTableColumn<FieldCompareRow<T>>[] = [\n { key: 'field', header: field, cell: (row) => row.field.header },\n {\n key: 'before',\n align: 'start',\n header: from,\n cell: (row) => <span {...css.attrs(fieldCompareStyles.unchanged)}>{shown(row, 0)}</span>,\n },\n {\n key: 'after',\n align: 'start',\n header: to,\n cell: (row) => (\n <span\n {...css.attrs(\n row.changed ? fieldCompareStyles.changed : fieldCompareStyles.unchanged,\n )}\n >\n {shown(row, 1)}\n </span>\n ),\n },\n ...(numeric\n ? [{ key: 'change', header: delta, align: 'start' as const, cell: change }]\n : []),\n ];\n return (\n <ColumnTable\n {...mergePartProps<Partial<ColumnTableProps<FieldCompareRow<T>>>>(\n { density: 'compact' },\n propsTable,\n )}\n rows={list}\n columns={columns}\n getKey={(row) => row.field.key}\n propsRow={(row) => ({ 'data-changed': row.changed || undefined }) as PartProps<'tr'>}\n />\n );\n };\n\n const list = (items: FieldCompareRow<T>[]) => (\n <dl {...mergeAttrs<'dl'>({ sx: fieldCompareStyles.list }, propsList)}>\n {items.map((row) => (\n <div\n key={row.field.key}\n {...mergeAttrs<'div'>({ sx: fieldCompareStyles.item }, propsItem)}\n >\n <dt {...mergeAttrs<'dt'>({ sx: fieldCompareStyles.label }, propsLabel)}>\n {row.field.header}\n </dt>\n <dd {...mergeAttrs<'dd'>({ sx: fieldCompareStyles.value }, propsValue)}>\n {before != null && (\n <>\n <del {...css.attrs(fieldCompareStyles.before)}>{shown(row, 0)}</del>\n <Icon icon={ArrowRight} sx={fieldCompareStyles.arrow} />\n <VisuallyHidden>{lang.fieldCompareTo}</VisuallyHidden>\n </>\n )}\n <ins {...css.attrs(fieldCompareStyles.after)}>{shown(row, 1)}</ins>\n {change(row)}\n </dd>\n </div>\n ))}\n </dl>\n );\n\n return () => {\n const items = rows();\n return (\n <div {...mergeAttrs<'div'>({}, rest)}>\n {items.length === 0 ? none() : layout === 'list' ? list(items) : table(items)}\n </div>\n );\n };\n },\n);\n"],"mappings":"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"}
|
package/dist/display/Gauge.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { clamp } from "../utils/data/number.js";
|
|
1
|
+
import { clamp, svgNumber } from "../utils/data/number.js";
|
|
2
2
|
import { toNumber } from "../utils/data/numeric.js";
|
|
3
3
|
import { useLang } from "../config/context.js";
|
|
4
4
|
import { hasContent, mergeAttrs } from "../utils/props.js";
|
|
5
5
|
import { fontSizeStyles } from "../utils/style/typography.js";
|
|
6
6
|
import { useFormat } from "../config/lang/format.js";
|
|
7
|
-
import { createVNode
|
|
8
|
-
import { _component, readonlyProp, restProps } from "@zerodep-dev/vue-tsx-pro-tsx";
|
|
7
|
+
import { createVNode } from "vue";
|
|
8
|
+
import { _component, mergeJsxProps, readonlyProp, restProps } from "@zerodep-dev/vue-tsx-pro-tsx";
|
|
9
9
|
import * as css from "@zerodep-dev/vue-tsx-pro-tsx/styles";
|
|
10
10
|
//#region src/display/Gauge.tsx
|
|
11
11
|
/** 外圈的一段区间:从上一段的终点(第一段从 min)到 to */
|
|
@@ -106,8 +106,8 @@ var BAND_RADIUS = 94;
|
|
|
106
106
|
function point(fraction, r) {
|
|
107
107
|
const angle = Math.PI * (1 - fraction);
|
|
108
108
|
return {
|
|
109
|
-
x: CENTER + r * Math.cos(angle),
|
|
110
|
-
y: CENTER - r * Math.sin(angle)
|
|
109
|
+
x: svgNumber(CENTER + r * Math.cos(angle)),
|
|
110
|
+
y: svgNumber(CENTER - r * Math.sin(angle))
|
|
111
111
|
};
|
|
112
112
|
}
|
|
113
113
|
/** 从 from 到 to(比例)的弧 */
|
|
@@ -171,23 +171,23 @@ var Gauge = /*#__PURE__*/ _component((_props, _ctx) => {
|
|
|
171
171
|
"aria-valuenow": clamp(current, min.value, max.value),
|
|
172
172
|
"aria-valuetext": target.value === void 0 ? spoken : `${spoken},${lang.progressTarget(text(target.value))}`
|
|
173
173
|
}), [
|
|
174
|
-
createVNode("div", css.attrs(gaugeStyles.dial), [createVNode("svg",
|
|
174
|
+
createVNode("div", css.attrs(gaugeStyles.dial), [createVNode("svg", mergeJsxProps(css.attrs(gaugeStyles.svg), {
|
|
175
175
|
"viewBox": "0 -4 200 108",
|
|
176
176
|
"aria-hidden": "true"
|
|
177
177
|
}), [
|
|
178
|
-
createVNode("path",
|
|
179
|
-
fraction > 0 && createVNode("path",
|
|
178
|
+
createVNode("path", mergeJsxProps({ "d": arc(0, 1, RADIUS) }, css.attrs(gaugeStyles.track)), null),
|
|
179
|
+
fraction > 0 && createVNode("path", mergeJsxProps({ "d": arc(0, fraction, RADIUS) }, css.attrs(gaugeStyles.value)), null),
|
|
180
180
|
(bands.value ?? []).map((item, position) => {
|
|
181
181
|
const from = fractionOf(position === 0 ? min.value : bands.value[position - 1].to);
|
|
182
182
|
const to = fractionOf(item.to);
|
|
183
|
-
return to > from ? createVNode("path",
|
|
183
|
+
return to > from ? createVNode("path", mergeJsxProps({
|
|
184
184
|
"key": position,
|
|
185
185
|
"d": arc(from, to, BAND_RADIUS)
|
|
186
186
|
}, css.attrs(gaugeStyles.band, css.select(() => bandColors[item.tone]))), null) : null;
|
|
187
187
|
}),
|
|
188
|
-
target.value !== void 0 && createVNode("line",
|
|
188
|
+
target.value !== void 0 && createVNode("line", mergeJsxProps(tick(fractionOf(target.value)), css.attrs(gaugeStyles.target)), null)
|
|
189
189
|
]), createVNode("span", mergeAttrs({ sx: gaugeStyles.center }, propsValue.value, { "aria-hidden": true }), [valueLabel.value ?? spoken])]),
|
|
190
|
-
createVNode("div",
|
|
190
|
+
createVNode("div", mergeJsxProps(css.attrs(gaugeStyles.ends), { "aria-hidden": "true" }), [createVNode("span", null, [format.number(min.value, valueFormat.value)]), createVNode("span", null, [format.number(max.value, valueFormat.value)])]),
|
|
191
191
|
hasContent(label.value) && createVNode("span", mergeAttrs({ sx: gaugeStyles.label }, propsLabel.value), [label.value])
|
|
192
192
|
]);
|
|
193
193
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Gauge.js","names":["_vtp_readonlyProp","_vtp_restProps","_createVNode","_mergeProps"],"sources":["../../src/display/Gauge.tsx"],"sourcesContent":["/**\n * 仪表盘(role=\"meter\"):半圆的刻度上显示一个量落在哪个区间——完成率、利用率、风险分。外圈是分段的区间\n * (危险、提醒、良好),里面的弧是当前值(颜色取它所在区间的色调),中间是数值,下面是名称。\n *\n * <Gauge value={0.82} max={1} valueFormat={{ style: 'percent' }} label=\"销售完成率\"\n * bands={[{ to: 0.6, tone: 'danger' }, { to: 0.9, tone: 'warning' }, { to: 1, tone: 'success' }]} target={0.9} />\n *\n * 和 Progress 的分工:Progress 是做到哪了(上传、步骤);Gauge 是一个量处在好坏的哪一段(有区间、有目标)。\n * 纯 SVG,大小随字号缩放(宽 10em)。读屏按 meter 念:数值(按 valueFormat 与单位格式化)与目标。\n *\n * 部件:Root(div,role=\"meter\")、Value(中间的数值)、Label(名称)。\n */\nimport { _component } from '@zerodep-dev/vue-tsx-pro-tsx';\nimport * as css from '@zerodep-dev/vue-tsx-pro-tsx/styles';\nimport type { VNodeChild } from 'vue';\nimport { useLang } from '../config/context';\nimport { useFormat, type NumericFormat } from '../config/lang/format';\nimport { clamp } from '../utils/data/number';\nimport { toNumber, type NumericValue } from '../utils/data/numeric';\nimport { hasContent, mergeAttrs, type PartProps } from '../utils/props';\nimport type { StatusTone } from '../utils/style/tone-styles';\nimport { fontSizeStyles, type TextSize } from '../utils/style/typography';\n\n/** 外圈的一段区间:从上一段的终点(第一段从 min)到 to */\nexport interface GaugeBand {\n /** 终点 */\n to: number;\n /** 色调:落在这一段时数值弧也用它 */\n tone: StatusTone;\n}\n\n/** 角色与取值由组件管理 */\nexport type GaugeManaged =\n 'role' | 'aria-valuemin' | 'aria-valuemax' | 'aria-valuenow' | 'aria-valuetext';\n\n/** 未声明的属性落到外层 div(role=\"meter\")上 */\nexport interface GaugeProps extends PartProps<'div', GaugeManaged> {\n /** 当前值 */\n value: NumericValue;\n /** 最小值,默认 0 */\n min?: number;\n /** 最大值,默认 100 */\n max?: number;\n /** 数值的格式(百分比写 `{ style: 'percent' }`,配 max={1}) */\n valueFormat?: NumericFormat;\n /** 单位:数值后面带上 */\n unit?: string;\n /** 外圈的区间,按 to 从小到大;不写时外圈是一整段轨道色 */\n bands?: readonly GaugeBand[];\n /** 目标:外圈上画一道刻度,读屏念在数值后面 */\n target?: number;\n /** 数值弧的色调;不写时取它所在区间的色调,没有区间时 accent */\n tone?: StatusTone;\n /** 名称:显示在下方,也是 meter 的名称(没写 aria-label 时) */\n label?: VNodeChild;\n /** 中间显示的文字;不写时是格式化的数值 */\n valueLabel?: VNodeChild;\n /** 字号档位:仪表盘的大小随之缩放;不写时继承 */\n size?: TextSize;\n /** 中间数值的部件属性 */\n propsValue?: PartProps<'span'>;\n /** 名称的部件属性 */\n propsLabel?: PartProps<'span'>;\n}\n\n/** Gauge 的样式 */\nexport const gaugeStyles = css.create({\n root: {\n display: 'inline-grid',\n justifyItems: 'center',\n gap: '_xs',\n width: '10em',\n maxWidth: '100%',\n },\n dial: { position: 'relative', width: '100%' },\n svg: { display: 'block', width: '100%', height: 'auto', overflow: 'visible' },\n track: { fill: 'none', stroke: '_fill', strokeWidth: 14 },\n value: {\n fill: 'none',\n stroke: 'var(--gauge-color)',\n strokeWidth: 14,\n forcedColorAdjust: { default: null, _forcedColors: 'none' },\n },\n band: { fill: 'none', strokeWidth: 4 },\n target: { stroke: '_text', strokeWidth: 2.5, strokeLinecap: 'round' },\n // 中间的数值:压在半圆的底边上\n center: {\n position: 'absolute',\n insetInline: 0,\n bottom: 0,\n textAlign: 'center',\n fontSize: '1.5em',\n fontWeight: '_semibold',\n fontVariantNumeric: 'tabular-nums',\n lineHeight: 1,\n color: '_text',\n },\n ends: {\n display: 'flex',\n justifyContent: 'space-between',\n width: '100%',\n fontSize: '0.75em',\n color: '_textMuted',\n fontVariantNumeric: 'tabular-nums',\n },\n label: { fontSize: '_sm', color: '_textMuted', textAlign: 'center' },\n});\n\n/**\n * 色调的实色:数值弧(--gauge-color)与外圈的区间。弧与区间只是辅助,数值以文字给出(中间与读屏),\n * 所以用明亮的实色而不是够文字对比度的深色\n */\nconst gaugeToneStyles = css.create({\n neutral: { '--gauge-color': '_colors.borderStrong' },\n accent: { '--gauge-color': '_colors.accent' },\n info: { '--gauge-color': '_colors.info' },\n success: { '--gauge-color': '_colors.success' },\n warning: { '--gauge-color': '_colors.warning' },\n danger: { '--gauge-color': '_colors.danger' },\n});\n\n/** 外圈区间的颜色 */\nconst bandColors = css.create({\n neutral: { stroke: '_borderStrong' },\n accent: { stroke: '_accent' },\n info: { stroke: '_info' },\n success: { stroke: '_success' },\n warning: { stroke: '_warning' },\n danger: { stroke: '_danger' },\n});\n\n// 半圆:圆心 (100, 100),从左(180°)经上方到右(0°)\nconst CENTER = 100;\nconst RADIUS = 80;\nconst BAND_RADIUS = 94;\n\n/** 比例 fraction(0–1)在半径 r 上的点 */\nfunction point(fraction: number, r: number) {\n const angle = Math.PI * (1 - fraction);\n return { x: CENTER + r * Math.cos(angle), y: CENTER - r * Math.sin(angle) };\n}\n\n/** 从 from 到 to(比例)的弧 */\nfunction arc(from: number, to: number, r: number) {\n const a = point(from, r);\n const b = point(to, r);\n return `M${a.x} ${a.y} A${r} ${r} 0 0 1 ${b.x} ${b.y}`;\n}\n\n/** 目标刻度:从数值弧的内沿穿过外圈 */\nfunction tick(fraction: number) {\n const inner = point(fraction, RADIUS - 10);\n const outer = point(fraction, BAND_RADIUS + 4);\n return { x1: inner.x, y1: inner.y, x2: outer.x, y2: outer.y };\n}\n\n/** 仪表盘:半圆刻度、分段区间、当前值与目标 */\nexport const Gauge = _component(\n ({\n value,\n min = 0,\n max = 100,\n valueFormat,\n unit,\n bands,\n target,\n tone,\n label,\n valueLabel,\n size,\n propsValue,\n propsLabel,\n ...rest\n }: GaugeProps) => {\n const lang = useLang();\n const format = useFormat();\n const text = (number: NumericValue) =>\n format.number(number, unit ? { ...valueFormat, suffix: unit } : valueFormat);\n return () => {\n const current = toNumber(value);\n const fractionOf = (number: number) => clamp((number - min) / (max - min || 1), 0, 1);\n const fraction = fractionOf(current);\n const band = bands?.find((item) => current <= item.to) ?? bands?.at(-1);\n const color = tone ?? band?.tone ?? 'accent';\n const spoken = text(value);\n const name = typeof label === 'string' ? label : undefined;\n return (\n <div\n {...mergeAttrs<'div'>(\n {\n sx: [gaugeStyles.root, size && fontSizeStyles[size], gaugeToneStyles[color]],\n 'aria-label': name,\n },\n rest,\n {\n role: 'meter',\n 'aria-valuemin': min,\n 'aria-valuemax': max,\n 'aria-valuenow': clamp(current, min, max),\n 'aria-valuetext':\n target === undefined ? spoken : `${spoken},${lang.progressTarget(text(target))}`,\n },\n )}\n >\n <div {...css.attrs(gaugeStyles.dial)}>\n <svg {...css.attrs(gaugeStyles.svg)} viewBox=\"0 -4 200 108\" aria-hidden=\"true\">\n <path d={arc(0, 1, RADIUS)} {...css.attrs(gaugeStyles.track)} />\n {fraction > 0 && (\n <path d={arc(0, fraction, RADIUS)} {...css.attrs(gaugeStyles.value)} />\n )}\n {(bands ?? []).map((item, position) => {\n const from = fractionOf(position === 0 ? min : bands![position - 1]!.to);\n const to = fractionOf(item.to);\n return to > from ? (\n <path\n key={position}\n d={arc(from, to, BAND_RADIUS)}\n {...css.attrs(gaugeStyles.band, bandColors[item.tone])}\n />\n ) : null;\n })}\n {target !== undefined && (\n <line {...tick(fractionOf(target))} {...css.attrs(gaugeStyles.target)} />\n )}\n </svg>\n <span\n {...mergeAttrs<'span'>({ sx: gaugeStyles.center }, propsValue, {\n 'aria-hidden': true,\n })}\n >\n {valueLabel ?? spoken}\n </span>\n </div>\n <div {...css.attrs(gaugeStyles.ends)} aria-hidden=\"true\">\n <span>{format.number(min, valueFormat)}</span>\n <span>{format.number(max, valueFormat)}</span>\n </div>\n {hasContent(label) && (\n <span {...mergeAttrs<'span'>({ sx: gaugeStyles.label }, propsLabel)}>{label}</span>\n )}\n </div>\n );\n };\n },\n);\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Gauge.js","names":["_vtp_readonlyProp","_vtp_restProps","_createVNode","_mergeProps"],"sources":["../../src/display/Gauge.tsx"],"sourcesContent":["/**\n * 仪表盘(role=\"meter\"):半圆的刻度上显示一个量落在哪个区间——完成率、利用率、风险分。外圈是分段的区间\n * (危险、提醒、良好),里面的弧是当前值(颜色取它所在区间的色调),中间是数值,下面是名称。\n *\n * <Gauge value={0.82} max={1} valueFormat={{ style: 'percent' }} label=\"销售完成率\"\n * bands={[{ to: 0.6, tone: 'danger' }, { to: 0.9, tone: 'warning' }, { to: 1, tone: 'success' }]} target={0.9} />\n *\n * 和 Progress 的分工:Progress 是做到哪了(上传、步骤);Gauge 是一个量处在好坏的哪一段(有区间、有目标)。\n * 纯 SVG,大小随字号缩放(宽 10em)。读屏按 meter 念:数值(按 valueFormat 与单位格式化)与目标。\n *\n * 部件:Root(div,role=\"meter\")、Value(中间的数值)、Label(名称)。\n */\nimport { _component } from '@zerodep-dev/vue-tsx-pro-tsx';\nimport * as css from '@zerodep-dev/vue-tsx-pro-tsx/styles';\nimport type { VNodeChild } from 'vue';\nimport { useLang } from '../config/context';\nimport { useFormat, type NumericFormat } from '../config/lang/format';\nimport { clamp, svgNumber } from '../utils/data/number';\nimport { toNumber, type NumericValue } from '../utils/data/numeric';\nimport { hasContent, mergeAttrs, type PartProps } from '../utils/props';\nimport type { StatusTone } from '../utils/style/tone-styles';\nimport { fontSizeStyles, type TextSize } from '../utils/style/typography';\n\n/** 外圈的一段区间:从上一段的终点(第一段从 min)到 to */\nexport interface GaugeBand {\n /** 终点 */\n to: number;\n /** 色调:落在这一段时数值弧也用它 */\n tone: StatusTone;\n}\n\n/** 角色与取值由组件管理 */\nexport type GaugeManaged =\n 'role' | 'aria-valuemin' | 'aria-valuemax' | 'aria-valuenow' | 'aria-valuetext';\n\n/** 未声明的属性落到外层 div(role=\"meter\")上 */\nexport interface GaugeProps extends PartProps<'div', GaugeManaged> {\n /** 当前值 */\n value: NumericValue;\n /** 最小值,默认 0 */\n min?: number;\n /** 最大值,默认 100 */\n max?: number;\n /** 数值的格式(百分比写 `{ style: 'percent' }`,配 max={1}) */\n valueFormat?: NumericFormat;\n /** 单位:数值后面带上 */\n unit?: string;\n /** 外圈的区间,按 to 从小到大;不写时外圈是一整段轨道色 */\n bands?: readonly GaugeBand[];\n /** 目标:外圈上画一道刻度,读屏念在数值后面 */\n target?: number;\n /** 数值弧的色调;不写时取它所在区间的色调,没有区间时 accent */\n tone?: StatusTone;\n /** 名称:显示在下方,也是 meter 的名称(没写 aria-label 时) */\n label?: VNodeChild;\n /** 中间显示的文字;不写时是格式化的数值 */\n valueLabel?: VNodeChild;\n /** 字号档位:仪表盘的大小随之缩放;不写时继承 */\n size?: TextSize;\n /** 中间数值的部件属性 */\n propsValue?: PartProps<'span'>;\n /** 名称的部件属性 */\n propsLabel?: PartProps<'span'>;\n}\n\n/** Gauge 的样式 */\nexport const gaugeStyles = css.create({\n root: {\n display: 'inline-grid',\n justifyItems: 'center',\n gap: '_xs',\n width: '10em',\n maxWidth: '100%',\n },\n dial: { position: 'relative', width: '100%' },\n svg: { display: 'block', width: '100%', height: 'auto', overflow: 'visible' },\n track: { fill: 'none', stroke: '_fill', strokeWidth: 14 },\n value: {\n fill: 'none',\n stroke: 'var(--gauge-color)',\n strokeWidth: 14,\n forcedColorAdjust: { default: null, _forcedColors: 'none' },\n },\n band: { fill: 'none', strokeWidth: 4 },\n target: { stroke: '_text', strokeWidth: 2.5, strokeLinecap: 'round' },\n // 中间的数值:压在半圆的底边上\n center: {\n position: 'absolute',\n insetInline: 0,\n bottom: 0,\n textAlign: 'center',\n fontSize: '1.5em',\n fontWeight: '_semibold',\n fontVariantNumeric: 'tabular-nums',\n lineHeight: 1,\n color: '_text',\n },\n ends: {\n display: 'flex',\n justifyContent: 'space-between',\n width: '100%',\n fontSize: '0.75em',\n color: '_textMuted',\n fontVariantNumeric: 'tabular-nums',\n },\n label: { fontSize: '_sm', color: '_textMuted', textAlign: 'center' },\n});\n\n/**\n * 色调的实色:数值弧(--gauge-color)与外圈的区间。弧与区间只是辅助,数值以文字给出(中间与读屏),\n * 所以用明亮的实色而不是够文字对比度的深色\n */\nconst gaugeToneStyles = css.create({\n neutral: { '--gauge-color': '_colors.borderStrong' },\n accent: { '--gauge-color': '_colors.accent' },\n info: { '--gauge-color': '_colors.info' },\n success: { '--gauge-color': '_colors.success' },\n warning: { '--gauge-color': '_colors.warning' },\n danger: { '--gauge-color': '_colors.danger' },\n});\n\n/** 外圈区间的颜色 */\nconst bandColors = css.create({\n neutral: { stroke: '_borderStrong' },\n accent: { stroke: '_accent' },\n info: { stroke: '_info' },\n success: { stroke: '_success' },\n warning: { stroke: '_warning' },\n danger: { stroke: '_danger' },\n});\n\n// 半圆:圆心 (100, 100),从左(180°)经上方到右(0°)\nconst CENTER = 100;\nconst RADIUS = 80;\nconst BAND_RADIUS = 94;\n\n/** 比例 fraction(0–1)在半径 r 上的点 */\nfunction point(fraction: number, r: number) {\n const angle = Math.PI * (1 - fraction);\n return { x: svgNumber(CENTER + r * Math.cos(angle)), y: svgNumber(CENTER - r * Math.sin(angle)) };\n}\n\n/** 从 from 到 to(比例)的弧 */\nfunction arc(from: number, to: number, r: number) {\n const a = point(from, r);\n const b = point(to, r);\n return `M${a.x} ${a.y} A${r} ${r} 0 0 1 ${b.x} ${b.y}`;\n}\n\n/** 目标刻度:从数值弧的内沿穿过外圈 */\nfunction tick(fraction: number) {\n const inner = point(fraction, RADIUS - 10);\n const outer = point(fraction, BAND_RADIUS + 4);\n return { x1: inner.x, y1: inner.y, x2: outer.x, y2: outer.y };\n}\n\n/** 仪表盘:半圆刻度、分段区间、当前值与目标 */\nexport const Gauge = _component(\n ({\n value,\n min = 0,\n max = 100,\n valueFormat,\n unit,\n bands,\n target,\n tone,\n label,\n valueLabel,\n size,\n propsValue,\n propsLabel,\n ...rest\n }: GaugeProps) => {\n const lang = useLang();\n const format = useFormat();\n const text = (number: NumericValue) =>\n format.number(number, unit ? { ...valueFormat, suffix: unit } : valueFormat);\n return () => {\n const current = toNumber(value);\n const fractionOf = (number: number) => clamp((number - min) / (max - min || 1), 0, 1);\n const fraction = fractionOf(current);\n const band = bands?.find((item) => current <= item.to) ?? bands?.at(-1);\n const color = tone ?? band?.tone ?? 'accent';\n const spoken = text(value);\n const name = typeof label === 'string' ? label : undefined;\n return (\n <div\n {...mergeAttrs<'div'>(\n {\n sx: [gaugeStyles.root, size && fontSizeStyles[size], gaugeToneStyles[color]],\n 'aria-label': name,\n },\n rest,\n {\n role: 'meter',\n 'aria-valuemin': min,\n 'aria-valuemax': max,\n 'aria-valuenow': clamp(current, min, max),\n 'aria-valuetext':\n target === undefined ? spoken : `${spoken},${lang.progressTarget(text(target))}`,\n },\n )}\n >\n <div {...css.attrs(gaugeStyles.dial)}>\n <svg {...css.attrs(gaugeStyles.svg)} viewBox=\"0 -4 200 108\" aria-hidden=\"true\">\n <path d={arc(0, 1, RADIUS)} {...css.attrs(gaugeStyles.track)} />\n {fraction > 0 && (\n <path d={arc(0, fraction, RADIUS)} {...css.attrs(gaugeStyles.value)} />\n )}\n {(bands ?? []).map((item, position) => {\n const from = fractionOf(position === 0 ? min : bands![position - 1]!.to);\n const to = fractionOf(item.to);\n return to > from ? (\n <path\n key={position}\n d={arc(from, to, BAND_RADIUS)}\n {...css.attrs(gaugeStyles.band, bandColors[item.tone])}\n />\n ) : null;\n })}\n {target !== undefined && (\n <line {...tick(fractionOf(target))} {...css.attrs(gaugeStyles.target)} />\n )}\n </svg>\n <span\n {...mergeAttrs<'span'>({ sx: gaugeStyles.center }, propsValue, {\n 'aria-hidden': true,\n })}\n >\n {valueLabel ?? spoken}\n </span>\n </div>\n <div {...css.attrs(gaugeStyles.ends)} aria-hidden=\"true\">\n <span>{format.number(min, valueFormat)}</span>\n <span>{format.number(max, valueFormat)}</span>\n </div>\n {hasContent(label) && (\n <span {...mergeAttrs<'span'>({ sx: gaugeStyles.label }, propsLabel)}>{label}</span>\n )}\n </div>\n );\n };\n },\n);\n"],"mappings":"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"}
|
package/dist/display/Image.js
CHANGED
|
@@ -4,8 +4,8 @@ import { focusRingStyles, radiusStyles } from "../utils/style/styles.js";
|
|
|
4
4
|
import { Icon } from "../base/Icon.js";
|
|
5
5
|
import { shimmerStyles } from "../utils/style/shimmer.js";
|
|
6
6
|
import { Lightbox } from "../overlay/Lightbox.js";
|
|
7
|
-
import { Fragment, createVNode,
|
|
8
|
-
import { _component, childrenProp, createContext, effect, readonlyProp, restProps, untrack } from "@zerodep-dev/vue-tsx-pro-tsx";
|
|
7
|
+
import { Fragment, createVNode, ref, shallowRef, watch } from "vue";
|
|
8
|
+
import { _component, childrenProp, createContext, effect, mergeJsxProps, readonlyProp, restProps, untrack } from "@zerodep-dev/vue-tsx-pro-tsx";
|
|
9
9
|
import * as css from "@zerodep-dev/vue-tsx-pro-tsx/styles";
|
|
10
10
|
import { ImageOff } from "@lucide/icons";
|
|
11
11
|
//#region src/display/Image.tsx
|
|
@@ -128,7 +128,7 @@ var Image = /*#__PURE__*/ _component((_props, _ctx) => {
|
|
|
128
128
|
alt: alt.value,
|
|
129
129
|
onLoad: () => status.value = "loaded",
|
|
130
130
|
onError: () => status.value = "error"
|
|
131
|
-
}), null), status.value === "error" && createVNode("span",
|
|
131
|
+
}), null), status.value === "error" && createVNode("span", mergeJsxProps(mergeAttrs({ sx: imageStyles.fallback }, propsFallback.value, {
|
|
132
132
|
role: "img",
|
|
133
133
|
"aria-label": alt.value
|
|
134
134
|
}), { "aria-description": lang.imageError }), [fallback.value ?? createVNode(Fragment, null, [createVNode(Icon, { "icon": ImageOff }, null), createVNode("span", { "aria-hidden": "true" }, [alt.value])])])]);
|
|
@@ -195,7 +195,7 @@ var ImageGroup = /*#__PURE__*/ _component((_props2, _ctx2) => {
|
|
|
195
195
|
open.value = true;
|
|
196
196
|
}
|
|
197
197
|
});
|
|
198
|
-
return () => createVNode("div", mergeAttrs({ sx: imageGroupStyles.root }, rest), [children.value, createVNode(Lightbox,
|
|
198
|
+
return () => createVNode("div", mergeAttrs({ sx: imageGroupStyles.root }, rest), [children.value, createVNode(Lightbox, mergeJsxProps(mergePartProps({ loop: loop.value }, propsLightbox.value), {
|
|
199
199
|
"items": items.value,
|
|
200
200
|
"_open": open.value,
|
|
201
201
|
"onUpdate:_open": _cache2[0] || (_cache2[0] = ($event) => open.value = $event),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Image.js","names":["_vtp_readonlyProp","_vtp_restProps","_vue_ref","_vtp_untrack","_createVNode","_Fragment","_mergeProps","_vtp_childrenProp","_vue_shallowRef"],"sources":["../../src/display/Image.tsx"],"sourcesContent":["/**\n * 图片:懒加载,加载时是扫光的占位,加载失败显示占位与原因;写了 preview 时点击(或 Enter)看大图(Lightbox)。\n * 同一个 ImageGroup 里的图片共用一个 Lightbox,大图里可以左右切换(相册、工单附件、验收照片)。\n *\n * <Image src=\"/photos/1-thumb.jpg\" alt=\"支架正面\" width={160} height={120} preview={{ src: '/photos/1.jpg' }} />\n * <ImageGroup>\n * {photos.map((photo) => <Image src={photo.thumb} alt={photo.title} preview={{ src: photo.url }} />)}\n * </ImageGroup>\n *\n * 可以预览的图片是按钮:名称“查看大图:alt”,键盘能到。尺寸写 width、height(数字是 px),object-fit 用 fit。\n * 部件:Root(span,可以预览时是 button)、Image(img)、Fallback(span)。\n */\nimport { ImageOff } from '@lucide/icons';\nimport { createContext, _component } from '@zerodep-dev/vue-tsx-pro-tsx';\nimport * as css from '@zerodep-dev/vue-tsx-pro-tsx/styles';\nimport { watch, type VNodeChild } from 'vue';\nimport { Icon } from '../base/Icon';\nimport { useLang } from '../config/context';\nimport { Lightbox, type LightboxItem, type LightboxProps } from '../overlay/Lightbox';\nimport { mergeAttrs, mergePartProps, type PartProps } from '../utils/props';\nimport { shimmerStyles } from '../utils/style/shimmer';\nimport { focusRingStyles, radiusStyles, type Radius } from '../utils/style/styles';\n\n/** object-fit 的取值 */\nexport interface ImageFits {\n /** 铺满、裁掉多余(默认):缩略图、头像 */\n cover: unknown;\n /** 整张放进去、留白:商品图、图纸 */\n contain: unknown;\n /** 拉伸铺满 */\n fill: unknown;\n /** 原大,超出的裁掉 */\n none: unknown;\n /** 比框大时同 contain,否则原大 */\n 'scale-down': unknown;\n}\n\n/** 预览的选项 */\nexport interface ImagePreviewOptions {\n /** 大图的地址;不写时用 src */\n src?: string;\n /** 大图下方的说明 */\n caption?: VNodeChild;\n /** 下载的文件名;false 不显示下载按钮 */\n download?: string | false;\n}\n\n/** 未声明的属性落到 img 上;外层用 propsRoot */\nexport interface ImageProps extends PartProps<'img', 'src' | 'alt' | 'width' | 'height'> {\n /** 图片地址 */\n src: string;\n /** 图片的说明:读屏念它,加载失败时也显示 */\n alt: string;\n /** 宽度:数字是 px,也可以写 CSS 长度;不写时按图片 */\n width?: number | string;\n /** 高度:数字是 px,也可以写 CSS 长度 */\n height?: number | string;\n /** 图片在框里怎么放,默认 cover */\n fit?: keyof ImageFits;\n /** 圆角档位,默认 md */\n radius?: Radius;\n /** 点击看大图:true 或预览的选项(大图地址、说明、下载) */\n preview?: boolean | ImagePreviewOptions;\n /** 加载失败时显示的内容;默认是图标与 alt */\n fallback?: VNodeChild;\n /** 外层(span,可以预览时是 button)的部件属性 */\n propsRoot?: PartProps<'span'>;\n /** 加载失败的占位的部件属性 */\n propsFallback?: PartProps<'span'>;\n}\n\n/** Image 的样式:root、button、image、hidden、fallback */\nexport const imageStyles = css.create({\n root: {\n position: 'relative',\n display: 'inline-flex',\n alignItems: 'center',\n justifyContent: 'center',\n overflow: 'hidden',\n verticalAlign: 'middle',\n backgroundColor: '_fill',\n color: '_textMuted',\n },\n // 可以预览:去掉按钮外观,指针变成放大镜\n button: {\n padding: 0,\n borderWidth: 0,\n font: 'inherit',\n cursor: 'zoom-in',\n },\n image: { display: 'block', width: '100%', height: '100%' },\n // 加载完之前不显示(占位在扫光);没写尺寸时仍占位,量得出图片大小\n hidden: { opacity: 0 },\n fallback: {\n display: 'flex',\n flexDirection: 'column',\n alignItems: 'center',\n justifyContent: 'center',\n gap: '_xs',\n padding: '_sm',\n fontSize: '_xs',\n textAlign: 'center',\n },\n});\n\n/** Image 的 object-fit:cover、contain、fill、none、scale-down */\nexport const imageFitStyles = css.create({\n cover: { objectFit: 'cover' },\n contain: { objectFit: 'contain' },\n fill: { objectFit: 'fill' },\n none: { objectFit: 'none' },\n 'scale-down': { objectFit: 'scale-down' },\n});\n\n/** Image 的动态样式:尺寸 */\nexport const imageDynamicStyles = css.create({\n size: (width: string, height: string) => ({ width, height }),\n});\n\n/** ImageGroup 交给里面的 Image 的:登记自己、打开预览 */\ninterface ImageGroupState {\n /** 登记一张可以预览的图,返回读它当前序号的函数与注销函数 */\n register(item: () => LightboxItem): { index: () => number; release: () => void };\n /** 从第 index 张打开预览 */\n open(index: number): void;\n}\n\nconst ImageGroupContext = createContext<ImageGroupState>('ImageGroup');\n\nconst toLength = (value: number | string | undefined) =>\n value == null ? 'auto' : typeof value === 'number' ? `${value}px` : value;\n\n/** 图片:懒加载、加载中占位、失败占位,可以点开看大图 */\nexport const Image = _component(\n ({\n src,\n alt,\n width,\n height,\n fit = 'cover',\n radius = 'md',\n preview = false,\n fallback,\n propsRoot,\n propsFallback,\n ...rest\n }: ImageProps) => {\n const lang = useLang();\n const group = ImageGroupContext.inject(null);\n let status = _state<'loading' | 'loaded' | 'error'>('loading');\n let viewing = _state(false);\n const options = () => (typeof preview === 'object' ? preview : {});\n const item = (): LightboxItem => ({\n src: options().src ?? src,\n alt,\n caption: options().caption,\n download: options().download,\n });\n // 在 ImageGroup 里:登记自己,预览交给组\n const membership = _untrack(preview) && group ? group.register(item) : undefined;\n if (membership) _effect(() => membership.release);\n\n // 地址换了:重新加载(只在变化时;挂载后再置回 loading 会盖掉已经到了的 load,缓存里的图就一直不显示)\n watch(\n () => src,\n () => (status = 'loading'),\n );\n\n function open() {\n if (membership) group!.open(membership.index());\n else viewing = true;\n }\n\n return () => {\n const sx = [\n imageStyles.root,\n radiusStyles[radius],\n imageDynamicStyles.size(toLength(width), toLength(height)),\n status === 'loading' && shimmerStyles.wave,\n ];\n const content = (\n <>\n {status !== 'error' && (\n <img\n {...mergeAttrs<'img'>(\n {\n sx: [\n imageStyles.image,\n imageFitStyles[fit],\n status === 'loading' && imageStyles.hidden,\n status === 'loaded' && shimmerStyles.reveal,\n ],\n loading: 'lazy',\n decoding: 'async',\n },\n rest,\n {\n src,\n alt,\n onLoad: () => (status = 'loaded'),\n onError: () => (status = 'error'),\n },\n )}\n />\n )}\n {status === 'error' && (\n <span\n {...mergeAttrs<'span'>({ sx: imageStyles.fallback }, propsFallback, {\n role: 'img',\n 'aria-label': alt,\n })}\n aria-description={lang.imageError}\n >\n {fallback ?? (\n <>\n <Icon icon={ImageOff} />\n <span aria-hidden=\"true\">{alt}</span>\n </>\n )}\n </span>\n )}\n </>\n );\n if (!preview || status === 'error')\n return <span {...mergeAttrs<'span'>({ sx }, propsRoot)}>{content}</span>;\n return (\n <>\n <button\n {...mergeAttrs<'button'>(\n { sx: [...sx, imageStyles.button, focusRingStyles.visible] },\n propsRoot as PartProps<'button'>,\n { type: 'button', 'aria-label': lang.imagePreview(alt), onClick: open },\n )}\n >\n {content}\n </button>\n {!membership && <Lightbox items={[item()]} _open={viewing} />}\n </>\n );\n };\n },\n);\n\n/** 未声明的属性落到外层 div 上 */\nexport interface ImageGroupProps extends PartProps<'div'> {\n /** 到头之后从另一头继续,默认 false */\n loop?: boolean;\n /** 预览(Lightbox)的部件属性 */\n propsLightbox?: Partial<LightboxProps>;\n /** 里面的 Image(也可以夹着别的内容) */\n children?: VNodeChild;\n}\n\n/** ImageGroup 的样式:root */\nexport const imageGroupStyles = css.create({\n root: { display: 'flex', flexWrap: 'wrap', gap: '_sm' },\n});\n\n/** 一组图片:共用一个预览,大图里左右切换 */\nexport const ImageGroup = _component(\n ({ loop = false, propsLightbox, children, ...rest }: ImageGroupProps) => {\n // 登记的图片:按登记(渲染)的顺序。不是响应式的:组的渲染不能读它们的属性——\n // 渲染里生成的子节点会给它们新的属性,读了就又触发组的渲染,无限循环\n let entries: { read: () => LightboxItem }[] = [];\n // 打开预览时取的一份\n let items = _state.raw<LightboxItem[]>([]);\n let open = _state(false);\n let index = _state(0);\n\n ImageGroupContext.provide({\n register(read) {\n const entry = { read };\n entries = [...entries, entry];\n return {\n index: () => entries.indexOf(entry),\n release: () => (entries = entries.filter((item) => item !== entry)),\n };\n },\n open(at) {\n items = entries.map((entry) => entry.read());\n index = at;\n open = true;\n },\n });\n\n return () => (\n <div {...mergeAttrs<'div'>({ sx: imageGroupStyles.root }, rest)}>\n {children}\n <Lightbox\n {...mergePartProps<Partial<LightboxProps>>({ loop }, propsLightbox)}\n items={items}\n _open={open}\n _index={index}\n />\n </div>\n );\n },\n);\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Image.js","names":["_vtp_readonlyProp","_vtp_restProps","_vue_ref","_vtp_untrack","_createVNode","_Fragment","_mergeProps","_vtp_childrenProp","_vue_shallowRef"],"sources":["../../src/display/Image.tsx"],"sourcesContent":["/**\n * 图片:懒加载,加载时是扫光的占位,加载失败显示占位与原因;写了 preview 时点击(或 Enter)看大图(Lightbox)。\n * 同一个 ImageGroup 里的图片共用一个 Lightbox,大图里可以左右切换(相册、工单附件、验收照片)。\n *\n * <Image src=\"/photos/1-thumb.jpg\" alt=\"支架正面\" width={160} height={120} preview={{ src: '/photos/1.jpg' }} />\n * <ImageGroup>\n * {photos.map((photo) => <Image src={photo.thumb} alt={photo.title} preview={{ src: photo.url }} />)}\n * </ImageGroup>\n *\n * 可以预览的图片是按钮:名称“查看大图:alt”,键盘能到。尺寸写 width、height(数字是 px),object-fit 用 fit。\n * 部件:Root(span,可以预览时是 button)、Image(img)、Fallback(span)。\n */\nimport { ImageOff } from '@lucide/icons';\nimport { createContext, _component } from '@zerodep-dev/vue-tsx-pro-tsx';\nimport * as css from '@zerodep-dev/vue-tsx-pro-tsx/styles';\nimport { watch, type VNodeChild } from 'vue';\nimport { Icon } from '../base/Icon';\nimport { useLang } from '../config/context';\nimport { Lightbox, type LightboxItem, type LightboxProps } from '../overlay/Lightbox';\nimport { mergeAttrs, mergePartProps, type PartProps } from '../utils/props';\nimport { shimmerStyles } from '../utils/style/shimmer';\nimport { focusRingStyles, radiusStyles, type Radius } from '../utils/style/styles';\n\n/** object-fit 的取值 */\nexport interface ImageFits {\n /** 铺满、裁掉多余(默认):缩略图、头像 */\n cover: unknown;\n /** 整张放进去、留白:商品图、图纸 */\n contain: unknown;\n /** 拉伸铺满 */\n fill: unknown;\n /** 原大,超出的裁掉 */\n none: unknown;\n /** 比框大时同 contain,否则原大 */\n 'scale-down': unknown;\n}\n\n/** 预览的选项 */\nexport interface ImagePreviewOptions {\n /** 大图的地址;不写时用 src */\n src?: string;\n /** 大图下方的说明 */\n caption?: VNodeChild;\n /** 下载的文件名;false 不显示下载按钮 */\n download?: string | false;\n}\n\n/** 未声明的属性落到 img 上;外层用 propsRoot */\nexport interface ImageProps extends PartProps<'img', 'src' | 'alt' | 'width' | 'height'> {\n /** 图片地址 */\n src: string;\n /** 图片的说明:读屏念它,加载失败时也显示 */\n alt: string;\n /** 宽度:数字是 px,也可以写 CSS 长度;不写时按图片 */\n width?: number | string;\n /** 高度:数字是 px,也可以写 CSS 长度 */\n height?: number | string;\n /** 图片在框里怎么放,默认 cover */\n fit?: keyof ImageFits;\n /** 圆角档位,默认 md */\n radius?: Radius;\n /** 点击看大图:true 或预览的选项(大图地址、说明、下载) */\n preview?: boolean | ImagePreviewOptions;\n /** 加载失败时显示的内容;默认是图标与 alt */\n fallback?: VNodeChild;\n /** 外层(span,可以预览时是 button)的部件属性 */\n propsRoot?: PartProps<'span'>;\n /** 加载失败的占位的部件属性 */\n propsFallback?: PartProps<'span'>;\n}\n\n/** Image 的样式:root、button、image、hidden、fallback */\nexport const imageStyles = css.create({\n root: {\n position: 'relative',\n display: 'inline-flex',\n alignItems: 'center',\n justifyContent: 'center',\n overflow: 'hidden',\n verticalAlign: 'middle',\n backgroundColor: '_fill',\n color: '_textMuted',\n },\n // 可以预览:去掉按钮外观,指针变成放大镜\n button: {\n padding: 0,\n borderWidth: 0,\n font: 'inherit',\n cursor: 'zoom-in',\n },\n image: { display: 'block', width: '100%', height: '100%' },\n // 加载完之前不显示(占位在扫光);没写尺寸时仍占位,量得出图片大小\n hidden: { opacity: 0 },\n fallback: {\n display: 'flex',\n flexDirection: 'column',\n alignItems: 'center',\n justifyContent: 'center',\n gap: '_xs',\n padding: '_sm',\n fontSize: '_xs',\n textAlign: 'center',\n },\n});\n\n/** Image 的 object-fit:cover、contain、fill、none、scale-down */\nexport const imageFitStyles = css.create({\n cover: { objectFit: 'cover' },\n contain: { objectFit: 'contain' },\n fill: { objectFit: 'fill' },\n none: { objectFit: 'none' },\n 'scale-down': { objectFit: 'scale-down' },\n});\n\n/** Image 的动态样式:尺寸 */\nexport const imageDynamicStyles = css.create({\n size: (width: string, height: string) => ({ width, height }),\n});\n\n/** ImageGroup 交给里面的 Image 的:登记自己、打开预览 */\ninterface ImageGroupState {\n /** 登记一张可以预览的图,返回读它当前序号的函数与注销函数 */\n register(item: () => LightboxItem): { index: () => number; release: () => void };\n /** 从第 index 张打开预览 */\n open(index: number): void;\n}\n\nconst ImageGroupContext = createContext<ImageGroupState>('ImageGroup');\n\nconst toLength = (value: number | string | undefined) =>\n value == null ? 'auto' : typeof value === 'number' ? `${value}px` : value;\n\n/** 图片:懒加载、加载中占位、失败占位,可以点开看大图 */\nexport const Image = _component(\n ({\n src,\n alt,\n width,\n height,\n fit = 'cover',\n radius = 'md',\n preview = false,\n fallback,\n propsRoot,\n propsFallback,\n ...rest\n }: ImageProps) => {\n const lang = useLang();\n const group = ImageGroupContext.inject(null);\n let status = _state<'loading' | 'loaded' | 'error'>('loading');\n let viewing = _state(false);\n const options = () => (typeof preview === 'object' ? preview : {});\n const item = (): LightboxItem => ({\n src: options().src ?? src,\n alt,\n caption: options().caption,\n download: options().download,\n });\n // 在 ImageGroup 里:登记自己,预览交给组\n const membership = _untrack(preview) && group ? group.register(item) : undefined;\n if (membership) _effect(() => membership.release);\n\n // 地址换了:重新加载(只在变化时;挂载后再置回 loading 会盖掉已经到了的 load,缓存里的图就一直不显示)\n watch(\n () => src,\n () => (status = 'loading'),\n );\n\n function open() {\n if (membership) group!.open(membership.index());\n else viewing = true;\n }\n\n return () => {\n const sx = [\n imageStyles.root,\n radiusStyles[radius],\n imageDynamicStyles.size(toLength(width), toLength(height)),\n status === 'loading' && shimmerStyles.wave,\n ];\n const content = (\n <>\n {status !== 'error' && (\n <img\n {...mergeAttrs<'img'>(\n {\n sx: [\n imageStyles.image,\n imageFitStyles[fit],\n status === 'loading' && imageStyles.hidden,\n status === 'loaded' && shimmerStyles.reveal,\n ],\n loading: 'lazy',\n decoding: 'async',\n },\n rest,\n {\n src,\n alt,\n onLoad: () => (status = 'loaded'),\n onError: () => (status = 'error'),\n },\n )}\n />\n )}\n {status === 'error' && (\n <span\n {...mergeAttrs<'span'>({ sx: imageStyles.fallback }, propsFallback, {\n role: 'img',\n 'aria-label': alt,\n })}\n aria-description={lang.imageError}\n >\n {fallback ?? (\n <>\n <Icon icon={ImageOff} />\n <span aria-hidden=\"true\">{alt}</span>\n </>\n )}\n </span>\n )}\n </>\n );\n if (!preview || status === 'error')\n return <span {...mergeAttrs<'span'>({ sx }, propsRoot)}>{content}</span>;\n return (\n <>\n <button\n {...mergeAttrs<'button'>(\n { sx: [...sx, imageStyles.button, focusRingStyles.visible] },\n propsRoot as PartProps<'button'>,\n { type: 'button', 'aria-label': lang.imagePreview(alt), onClick: open },\n )}\n >\n {content}\n </button>\n {!membership && <Lightbox items={[item()]} _open={viewing} />}\n </>\n );\n };\n },\n);\n\n/** 未声明的属性落到外层 div 上 */\nexport interface ImageGroupProps extends PartProps<'div'> {\n /** 到头之后从另一头继续,默认 false */\n loop?: boolean;\n /** 预览(Lightbox)的部件属性 */\n propsLightbox?: Partial<LightboxProps>;\n /** 里面的 Image(也可以夹着别的内容) */\n children?: VNodeChild;\n}\n\n/** ImageGroup 的样式:root */\nexport const imageGroupStyles = css.create({\n root: { display: 'flex', flexWrap: 'wrap', gap: '_sm' },\n});\n\n/** 一组图片:共用一个预览,大图里左右切换 */\nexport const ImageGroup = _component(\n ({ loop = false, propsLightbox, children, ...rest }: ImageGroupProps) => {\n // 登记的图片:按登记(渲染)的顺序。不是响应式的:组的渲染不能读它们的属性——\n // 渲染里生成的子节点会给它们新的属性,读了就又触发组的渲染,无限循环\n let entries: { read: () => LightboxItem }[] = [];\n // 打开预览时取的一份\n let items = _state.raw<LightboxItem[]>([]);\n let open = _state(false);\n let index = _state(0);\n\n ImageGroupContext.provide({\n register(read) {\n const entry = { read };\n entries = [...entries, entry];\n return {\n index: () => entries.indexOf(entry),\n release: () => (entries = entries.filter((item) => item !== entry)),\n };\n },\n open(at) {\n items = entries.map((entry) => entry.read());\n index = at;\n open = true;\n },\n });\n\n return () => (\n <div {...mergeAttrs<'div'>({ sx: imageGroupStyles.root }, rest)}>\n {children}\n <Lightbox\n {...mergePartProps<Partial<LightboxProps>>({ loop }, propsLightbox)}\n items={items}\n _open={open}\n _index={index}\n />\n </div>\n );\n },\n);\n"],"mappings":"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"}
|
package/dist/display/Marquee.js
CHANGED
|
@@ -2,8 +2,8 @@ import { mergeAttrs } from "../utils/props.js";
|
|
|
2
2
|
import { focusRingStyles, gapStyles } from "../utils/style/styles.js";
|
|
3
3
|
import { useResizeObserver } from "../utils/dom/resize.js";
|
|
4
4
|
import { useReducedMotion } from "../utils/dom/media-query.js";
|
|
5
|
-
import { createVNode,
|
|
6
|
-
import { _component, childrenProp, readonlyProp, restProps } from "@zerodep-dev/vue-tsx-pro-tsx";
|
|
5
|
+
import { createVNode, ref, shallowRef } from "vue";
|
|
6
|
+
import { _component, childrenProp, mergeJsxProps, readonlyProp, restProps } from "@zerodep-dev/vue-tsx-pro-tsx";
|
|
7
7
|
import * as css from "@zerodep-dev/vue-tsx-pro-tsx/styles";
|
|
8
8
|
//#region src/display/Marquee.tsx
|
|
9
9
|
/** 滚动方向 */
|
|
@@ -116,7 +116,7 @@ var Marquee = /*#__PURE__*/ _component((_props, _ctx) => {
|
|
|
116
116
|
"aria-hidden": true,
|
|
117
117
|
inert: true
|
|
118
118
|
} : { ref: group }), [children.value]);
|
|
119
|
-
return () => createVNode("div",
|
|
119
|
+
return () => createVNode("div", mergeJsxProps(mergeAttrs({
|
|
120
120
|
sx: [
|
|
121
121
|
marqueeStyles.root,
|
|
122
122
|
css.select(() => vertical() && marqueeStyles.vertical, true),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Marquee.js","names":["_vtp_readonlyProp","_vtp_childrenProp","_vtp_restProps","_vue_shallowRef","_vue_ref","_createVNode","_mergeProps"],"sources":["../../src/display/Marquee.tsx"],"sourcesContent":["/**\n * 跑马灯(div):内容循环滚动(合作伙伴 logo、公告、标签墙)。纯 CSS 动画:内容复制一份接在后面,\n * 平移到正好一份的长度时与开头重合,无缝循环。\n *\n * <Marquee speed={40} pauseOnHover aria-label=\"合作伙伴\">\n * {partners.map((partner) => <img src={partner.logo} alt={partner.name} />)}\n * </Marquee>\n *\n * - 方向 left / right / up / down;speed 是每秒多少 px(按一份内容的长度换算成动画时长,内容变化时重新量);\n * - 指针悬停、焦点在里面时暂停(pauseOnHover);paused 由外部控制(WCAG 2.2.2:给用户一个暂停按钮);\n * - 减少动态效果时不滚动,内容可以手动滚动查看(容器可以聚焦,键盘也能滚);\n * - role=\"marquee\"(ARIA 的跑马灯角色,用 aria-label 命名);复制的那份对读屏隐藏且不可聚焦(inert),读屏只念一遍。\n *\n * 部件:Root(div)、Track(div,做动画的那一层)、Group(div,一份内容)。\n */\nimport { _component } from '@zerodep-dev/vue-tsx-pro-tsx';\nimport * as css from '@zerodep-dev/vue-tsx-pro-tsx/styles';\nimport type { VNodeChild } from 'vue';\nimport { useReducedMotion } from '../utils/dom/media-query';\nimport { useResizeObserver } from '../utils/dom/resize';\nimport { mergeAttrs, type PartProps } from '../utils/props';\nimport { focusRingStyles, gapStyles, type Space, type StyleTable } from '../utils/style/styles';\n\n/** 滚动方向 */\nexport interface MarqueeDirections {\n /** 向左(默认) */\n left: unknown;\n /** 向右 */\n right: unknown;\n /** 向上 */\n up: unknown;\n /** 向下 */\n down: unknown;\n}\n\n/** 未声明的属性落到外层 div 上 */\nexport interface MarqueeProps extends PartProps<'div'> {\n /** 滚动方向,默认 left */\n direction?: keyof MarqueeDirections;\n /** 速度:每秒多少 px,默认 40 */\n speed?: number;\n /** 指针悬停、焦点在里面时暂停,默认 true */\n pauseOnHover?: boolean;\n /** 暂停 */\n paused?: boolean;\n /** 两端渐隐 */\n fade?: boolean;\n /** 项之间(以及两份之间)的间距,默认 lg */\n gap?: Space;\n /** 动画层的部件属性 */\n propsTrack?: PartProps<'div'>;\n /** 每一份内容的部件属性 */\n propsGroup?: PartProps<'div'>;\n /** 内容 */\n children?: VNodeChild;\n}\n\nconst scrollX = css.keyframes({\n from: { transform: 'none' },\n to: { transform: 'translateX(-50%)' },\n});\nconst scrollY = css.keyframes({\n from: { transform: 'none' },\n to: { transform: 'translateY(-50%)' },\n});\n\n/** Marquee 的样式:root、vertical、fade、track、trackVertical、reverse、pausable、paused、group */\nexport const marqueeStyles = css.create({\n root: {\n display: 'flex',\n overflow: { default: 'hidden', _reducedMotion: 'auto' },\n },\n vertical: { flexDirection: 'column' },\n fade: {\n maskImage: 'linear-gradient(to right, transparent, black 10%, black 90%, transparent)',\n },\n fadeVertical: {\n maskImage: 'linear-gradient(to bottom, transparent, black 10%, black 90%, transparent)',\n },\n // 两份内容并排;平移 -50% 正好是一份(含它后面的间距)\n track: {\n display: 'flex',\n flexShrink: 0,\n width: 'max-content',\n animationName: { default: scrollX, _reducedMotion: 'none' },\n animationTimingFunction: 'linear',\n animationIterationCount: 'infinite',\n },\n trackVertical: {\n flexDirection: 'column',\n width: 'auto',\n height: 'max-content',\n animationName: { default: scrollY, _reducedMotion: 'none' },\n },\n reverse: { animationDirection: 'reverse' },\n pausable: {\n animationPlayState: { default: 'running', ':hover': 'paused', ':focus-within': 'paused' },\n },\n paused: { animationPlayState: 'paused' },\n group: { display: 'flex', flexShrink: 0, alignItems: 'center' },\n groupVertical: { flexDirection: 'column' },\n // 不滚动时用不着复制的那份\n copy: { display: { default: null, _reducedMotion: 'none' } },\n});\n\n/** Marquee 的样式:每份内容后面的间距(横排时在行尾、竖排时在下方),与项之间的 gap 一样宽 */\nexport const marqueeTrailStyles: Readonly<Record<'inline' | 'block', StyleTable<Space>>> = {\n inline: css.create({\n none: { paddingInlineEnd: '_none' },\n xs: { paddingInlineEnd: '_xs' },\n sm: { paddingInlineEnd: '_sm' },\n md: { paddingInlineEnd: '_md' },\n lg: { paddingInlineEnd: '_lg' },\n xl: { paddingInlineEnd: '_xl' },\n }),\n block: css.create({\n none: { paddingBlockEnd: '_none' },\n xs: { paddingBlockEnd: '_xs' },\n sm: { paddingBlockEnd: '_sm' },\n md: { paddingBlockEnd: '_md' },\n lg: { paddingBlockEnd: '_lg' },\n xl: { paddingBlockEnd: '_xl' },\n }),\n};\n\n// 时长是任意值,用动态样式;单独一个 create,见 COMPONENTS.md §4\n/** Marquee 的样式:duration(一圈的时长) */\nexport const marqueeDynamicStyles = css.create({\n duration: (seconds: number) => ({ animationDuration: `${seconds}s` }),\n});\n\n/** 跑马灯:内容复制一份,纯 CSS 无缝循环滚动 */\nexport const Marquee = _component(\n ({\n direction = 'left',\n speed = 40,\n pauseOnHover = true,\n paused = false,\n fade = false,\n gap = 'lg',\n propsTrack,\n propsGroup,\n children,\n ...rest\n }: MarqueeProps) => {\n let group = _state.raw<HTMLElement | null>(null);\n // 一份内容(含间距)的长度:换算动画时长\n let length = _state(0);\n // 减少动态效果时变成手动滚动的区域:键盘要能聚焦它才能滚动\n const reducedMotion = useReducedMotion();\n const vertical = () => direction === 'up' || direction === 'down';\n\n useResizeObserver(\n () => group,\n () => {\n if (group) length = vertical() ? group.offsetHeight : group.offsetWidth;\n },\n );\n\n const renderGroup = (copy: boolean) => (\n <div\n {...mergeAttrs<'div'>(\n {\n sx: [\n marqueeStyles.group,\n vertical() && marqueeStyles.groupVertical,\n gapStyles[gap],\n // 每份后面留一个间距:两份之间与项之间一样宽\n marqueeTrailStyles[vertical() ? 'block' : 'inline'][gap],\n copy && marqueeStyles.copy,\n ],\n },\n propsGroup,\n copy ? { 'aria-hidden': true, inert: true } : { ref: _toRef(group) },\n )}\n >\n {children}\n </div>\n );\n\n return () => (\n <div\n {...mergeAttrs<'div'>(\n {\n sx: [\n marqueeStyles.root,\n vertical() && marqueeStyles.vertical,\n fade && (vertical() ? marqueeStyles.fadeVertical : marqueeStyles.fade),\n reducedMotion.value && focusRingStyles.visible,\n ],\n role: 'marquee',\n tabindex: reducedMotion.value ? 0 : undefined,\n },\n rest,\n )}\n data-direction={direction}\n data-paused={paused || undefined}\n >\n <div\n {...mergeAttrs<'div'>(\n {\n sx: [\n marqueeStyles.track,\n vertical() && marqueeStyles.trackVertical,\n (direction === 'right' || direction === 'down') && marqueeStyles.reverse,\n pauseOnHover && marqueeStyles.pausable,\n paused && marqueeStyles.paused,\n marqueeDynamicStyles.duration(Math.max(length, 1) / Math.max(speed, 1)),\n ],\n },\n propsTrack,\n )}\n >\n {renderGroup(false)}\n {renderGroup(true)}\n </div>\n </div>\n );\n },\n);\n"],"mappings":"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
|
|
1
|
+
{"version":3,"file":"Marquee.js","names":["_vtp_readonlyProp","_vtp_childrenProp","_vtp_restProps","_vue_shallowRef","_vue_ref","_createVNode","_mergeProps"],"sources":["../../src/display/Marquee.tsx"],"sourcesContent":["/**\n * 跑马灯(div):内容循环滚动(合作伙伴 logo、公告、标签墙)。纯 CSS 动画:内容复制一份接在后面,\n * 平移到正好一份的长度时与开头重合,无缝循环。\n *\n * <Marquee speed={40} pauseOnHover aria-label=\"合作伙伴\">\n * {partners.map((partner) => <img src={partner.logo} alt={partner.name} />)}\n * </Marquee>\n *\n * - 方向 left / right / up / down;speed 是每秒多少 px(按一份内容的长度换算成动画时长,内容变化时重新量);\n * - 指针悬停、焦点在里面时暂停(pauseOnHover);paused 由外部控制(WCAG 2.2.2:给用户一个暂停按钮);\n * - 减少动态效果时不滚动,内容可以手动滚动查看(容器可以聚焦,键盘也能滚);\n * - role=\"marquee\"(ARIA 的跑马灯角色,用 aria-label 命名);复制的那份对读屏隐藏且不可聚焦(inert),读屏只念一遍。\n *\n * 部件:Root(div)、Track(div,做动画的那一层)、Group(div,一份内容)。\n */\nimport { _component } from '@zerodep-dev/vue-tsx-pro-tsx';\nimport * as css from '@zerodep-dev/vue-tsx-pro-tsx/styles';\nimport type { VNodeChild } from 'vue';\nimport { useReducedMotion } from '../utils/dom/media-query';\nimport { useResizeObserver } from '../utils/dom/resize';\nimport { mergeAttrs, type PartProps } from '../utils/props';\nimport { focusRingStyles, gapStyles, type Space, type StyleTable } from '../utils/style/styles';\n\n/** 滚动方向 */\nexport interface MarqueeDirections {\n /** 向左(默认) */\n left: unknown;\n /** 向右 */\n right: unknown;\n /** 向上 */\n up: unknown;\n /** 向下 */\n down: unknown;\n}\n\n/** 未声明的属性落到外层 div 上 */\nexport interface MarqueeProps extends PartProps<'div'> {\n /** 滚动方向,默认 left */\n direction?: keyof MarqueeDirections;\n /** 速度:每秒多少 px,默认 40 */\n speed?: number;\n /** 指针悬停、焦点在里面时暂停,默认 true */\n pauseOnHover?: boolean;\n /** 暂停 */\n paused?: boolean;\n /** 两端渐隐 */\n fade?: boolean;\n /** 项之间(以及两份之间)的间距,默认 lg */\n gap?: Space;\n /** 动画层的部件属性 */\n propsTrack?: PartProps<'div'>;\n /** 每一份内容的部件属性 */\n propsGroup?: PartProps<'div'>;\n /** 内容 */\n children?: VNodeChild;\n}\n\nconst scrollX = css.keyframes({\n from: { transform: 'none' },\n to: { transform: 'translateX(-50%)' },\n});\nconst scrollY = css.keyframes({\n from: { transform: 'none' },\n to: { transform: 'translateY(-50%)' },\n});\n\n/** Marquee 的样式:root、vertical、fade、track、trackVertical、reverse、pausable、paused、group */\nexport const marqueeStyles = css.create({\n root: {\n display: 'flex',\n overflow: { default: 'hidden', _reducedMotion: 'auto' },\n },\n vertical: { flexDirection: 'column' },\n fade: {\n maskImage: 'linear-gradient(to right, transparent, black 10%, black 90%, transparent)',\n },\n fadeVertical: {\n maskImage: 'linear-gradient(to bottom, transparent, black 10%, black 90%, transparent)',\n },\n // 两份内容并排;平移 -50% 正好是一份(含它后面的间距)\n track: {\n display: 'flex',\n flexShrink: 0,\n width: 'max-content',\n animationName: { default: scrollX, _reducedMotion: 'none' },\n animationTimingFunction: 'linear',\n animationIterationCount: 'infinite',\n },\n trackVertical: {\n flexDirection: 'column',\n width: 'auto',\n height: 'max-content',\n animationName: { default: scrollY, _reducedMotion: 'none' },\n },\n reverse: { animationDirection: 'reverse' },\n pausable: {\n animationPlayState: { default: 'running', ':hover': 'paused', ':focus-within': 'paused' },\n },\n paused: { animationPlayState: 'paused' },\n group: { display: 'flex', flexShrink: 0, alignItems: 'center' },\n groupVertical: { flexDirection: 'column' },\n // 不滚动时用不着复制的那份\n copy: { display: { default: null, _reducedMotion: 'none' } },\n});\n\n/** Marquee 的样式:每份内容后面的间距(横排时在行尾、竖排时在下方),与项之间的 gap 一样宽 */\nexport const marqueeTrailStyles: Readonly<Record<'inline' | 'block', StyleTable<Space>>> = {\n inline: css.create({\n none: { paddingInlineEnd: '_none' },\n xs: { paddingInlineEnd: '_xs' },\n sm: { paddingInlineEnd: '_sm' },\n md: { paddingInlineEnd: '_md' },\n lg: { paddingInlineEnd: '_lg' },\n xl: { paddingInlineEnd: '_xl' },\n }),\n block: css.create({\n none: { paddingBlockEnd: '_none' },\n xs: { paddingBlockEnd: '_xs' },\n sm: { paddingBlockEnd: '_sm' },\n md: { paddingBlockEnd: '_md' },\n lg: { paddingBlockEnd: '_lg' },\n xl: { paddingBlockEnd: '_xl' },\n }),\n};\n\n// 时长是任意值,用动态样式;单独一个 create,见 COMPONENTS.md §4\n/** Marquee 的样式:duration(一圈的时长) */\nexport const marqueeDynamicStyles = css.create({\n duration: (seconds: number) => ({ animationDuration: `${seconds}s` }),\n});\n\n/** 跑马灯:内容复制一份,纯 CSS 无缝循环滚动 */\nexport const Marquee = _component(\n ({\n direction = 'left',\n speed = 40,\n pauseOnHover = true,\n paused = false,\n fade = false,\n gap = 'lg',\n propsTrack,\n propsGroup,\n children,\n ...rest\n }: MarqueeProps) => {\n let group = _state.raw<HTMLElement | null>(null);\n // 一份内容(含间距)的长度:换算动画时长\n let length = _state(0);\n // 减少动态效果时变成手动滚动的区域:键盘要能聚焦它才能滚动\n const reducedMotion = useReducedMotion();\n const vertical = () => direction === 'up' || direction === 'down';\n\n useResizeObserver(\n () => group,\n () => {\n if (group) length = vertical() ? group.offsetHeight : group.offsetWidth;\n },\n );\n\n const renderGroup = (copy: boolean) => (\n <div\n {...mergeAttrs<'div'>(\n {\n sx: [\n marqueeStyles.group,\n vertical() && marqueeStyles.groupVertical,\n gapStyles[gap],\n // 每份后面留一个间距:两份之间与项之间一样宽\n marqueeTrailStyles[vertical() ? 'block' : 'inline'][gap],\n copy && marqueeStyles.copy,\n ],\n },\n propsGroup,\n copy ? { 'aria-hidden': true, inert: true } : { ref: _toRef(group) },\n )}\n >\n {children}\n </div>\n );\n\n return () => (\n <div\n {...mergeAttrs<'div'>(\n {\n sx: [\n marqueeStyles.root,\n vertical() && marqueeStyles.vertical,\n fade && (vertical() ? marqueeStyles.fadeVertical : marqueeStyles.fade),\n reducedMotion.value && focusRingStyles.visible,\n ],\n role: 'marquee',\n tabindex: reducedMotion.value ? 0 : undefined,\n },\n rest,\n )}\n data-direction={direction}\n data-paused={paused || undefined}\n >\n <div\n {...mergeAttrs<'div'>(\n {\n sx: [\n marqueeStyles.track,\n vertical() && marqueeStyles.trackVertical,\n (direction === 'right' || direction === 'down') && marqueeStyles.reverse,\n pauseOnHover && marqueeStyles.pausable,\n paused && marqueeStyles.paused,\n marqueeDynamicStyles.duration(Math.max(length, 1) / Math.max(speed, 1)),\n ],\n },\n propsTrack,\n )}\n >\n {renderGroup(false)}\n {renderGroup(true)}\n </div>\n </div>\n );\n },\n);\n"],"mappings":"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"}
|