@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":"TagsInput.js","names":["_isVNode","_vtp_bindable","_vtp_readonlyProp","_vtp_childrenProp","_vtp_restProps","_vue_shallowRef","_createVNode","_Fragment","_mergeProps","_withDirectives","_vModelText"],"sources":["../../src/input/TagsInput.tsx"],"sourcesContent":["/**\n * 标签输入(多选的组合框):输入框里排着已选的 Tag,输入文字后回车(或输入逗号)加一个,可以从建议列表里选。\n * 关键词、收件人、标签、多个城市。参考 Mantine TagsInput、React Aria TagGroup + ComboBox。\n *\n * <TagsInput _value={keywords} aria-label=\"关键词\" /> 自由输入\n * <TagsInput _value={cities} items={cityOptions} allowCustomValue={false} /> 只能从建议里选\n *\n * - 回车、输入分隔符(默认 , , ; ;)把文字加成标签;粘贴时按分隔符与换行拆成多个;重复的不加;\n * - 输入框为空时按退格删掉最后一个标签;每个标签有移除按钮(离场后其余的平移补位);\n * - 有 items 时边输入边过滤建议(同 Combobox),↑↓ 移动、回车加入或移除高亮项(已选的打勾);\n * 异步取建议时绑定 inputValue 去请求、写 filter={false} 与 loading,结果没回来时列表显示加载中;\n * - 写了 name 时每个值一个隐藏的 input 随表单提交。\n *\n * 未声明的属性落到 input(主元素)上。部件:Root(外壳)、Tag、Input、Clear、Popover、Listbox。\n */\nimport { _component } from '@zerodep-dev/vue-tsx-pro-tsx';\nimport * as css from '@zerodep-dev/vue-tsx-pro-tsx/styles';\nimport { useId, watch, type VNodeChild } from 'vue';\nimport type { PresetIconButtonProps } from '../base/CloseButton';\nimport { Tag, type TagProps } from '../base/Tag';\nimport { useTextMatcher } from '../config/context';\nimport { isComposingKey } from '../utils/dom/hotkeys';\nimport { useAnchoredPopover, type FloatingPlacement } from '../utils/dom/floating';\nimport { openSetter, type OpenChangeProps } from '../utils/dom/open-change';\nimport { ListPopover } from './ListPopover';\nimport { useLayoutAnimation } from '../utils/motion/layout';\nimport { mergeAttrs, mergePartProps, type PartProps } from '../utils/props';\nimport {\n controlClear,\n controlParts,\n focusOnShell,\n controlIcon,\n useControlAppearance,\n type ControlIconOptions,\n type ControlOptions,\n} from './control';\nimport { useFieldControl } from '../form/Field';\nimport { FormValue } from '../form/FormValue';\nimport { activeOptionId, type ListboxViewProps, type OptionRenderState } from './Listbox';\nimport {\n handleListKeydown,\n useListState,\n useSelectedOptions,\n type ListboxOption,\n type OptionMapping,\n toListboxOption,\n} from './list-state';\n\n/** 角色、展开状态与可用性由组件管理 */\nexport type TagsInputManaged =\n | 'value'\n | 'role'\n | 'size'\n | 'disabled'\n | 'readonly'\n | 'autocomplete'\n | 'aria-autocomplete'\n | 'aria-expanded'\n | 'aria-controls'\n | 'aria-activedescendant'\n | 'aria-invalid'\n | 'aria-required'\n | 'aria-describedby';\n\n/** TagsInput 除 toOption 外的属性;未声明的属性落到 input(主元素)上,外壳用 propsRoot */\nexport interface TagsInputBaseProps<T>\n extends\n PartProps<'input', TagsInputManaged>,\n ControlOptions,\n ControlIconOptions,\n OpenChangeProps {\n /** 已选的值:父级写 _value={list} */\n _value?: string[];\n /** 建议:ListboxOption、字符串,或任意数据加 toOption;不给时是纯自由输入 */\n items?: readonly T[];\n /** 允许加入不在建议里的文字,默认 true */\n allowCustomValue?: boolean;\n /** 失焦时把输入框里还没确认的文字加成标签,默认 true(同 Mantine);false 时文字留在框里 */\n acceptOnBlur?: boolean;\n /** 把输入拆成标签的分隔符,默认 [',', ',', ';', ';'];回车与粘贴里的换行总是分隔 */\n delimiters?: readonly string[];\n /** 最多几个标签;到了上限不再加入 */\n max?: number;\n /** 输入框里的文字:父级可以写 _inputValue={x}(异步取建议时据此搜索) */\n _inputValue?: string;\n /** 建议列表是否打开 */\n _open?: boolean;\n /** 过滤建议:默认按显示文字包含输入;false 不过滤(异步搜索) */\n filter?: false | ((item: T, query: string) => boolean);\n /** 正在取建议 */\n loading?: boolean;\n /** 弹出位置,默认 bottom-start */\n placement?: FloatingPlacement;\n /** 有标签时显示清空按钮 */\n clearable?: boolean;\n /** 表单提交时的 name(每个值一个隐藏的 input) */\n name?: string;\n /** 不可用 */\n disabled?: boolean;\n /** 只读:标签不能移除,不能输入 */\n readonly?: boolean;\n /** 必填:至少要有一个标签(读屏念“必填”;校验交给表单) */\n required?: boolean;\n /** 校验未通过 */\n invalid?: boolean;\n /** 自定义建议的内容 */\n children?: (item: T, state: OptionRenderState) => VNodeChild;\n /** 外壳的部件属性 */\n propsRoot?: PartProps<'div'>;\n /** 每个标签的部件属性,可以按值计算 */\n propsTag?: Partial<TagProps> | ((value: string) => Partial<TagProps>);\n /** 清空按钮的部件属性 */\n propsClear?: Partial<PresetIconButtonProps>;\n /** 弹出层的部件属性 */\n propsPopover?: PartProps<'div', 'id' | 'popover'>;\n /** 建议列表的部件属性 */\n propsListbox?: Partial<ListboxViewProps<T>>;\n}\n\n/** 标签输入的属性;items 不是 ListboxOption 或字符串时要给 toOption */\nexport type TagsInputProps<T = ListboxOption> = TagsInputBaseProps<T> & OptionMapping<T>;\n\n/** TagsInput 的样式:root、input、popover */\nexport const tagsInputStyles = css.create({\n // 标签多了换行;行与行之间留出间距\n root: { flexWrap: 'wrap', rowGap: '_xs', paddingBlock: '_xs' },\n // 输入框至少留出几个字的位置,放不下时换到下一行\n input: { flexBasis: '6em' },\n});\n\n/** 默认的分隔符:英文、中文的逗号与分号 */\nconst DELIMITERS = [',', ',', ';', ';'];\n\n/** 标签输入:输入框里排着 Tag,回车或分隔符加入,退格删最后一个 */\nexport const TagsInput = _component(\n <T = ListboxOption,>({\n _value: value = _bindable<string[]>([]),\n items = [],\n toOption,\n allowCustomValue = true,\n acceptOnBlur = true,\n delimiters = DELIMITERS,\n max,\n _inputValue: inputValue = _bindable(''),\n _open: open = _bindable(false),\n onOpenChange,\n filter,\n loading = false,\n placement = 'bottom-start',\n clearable = false,\n name,\n variant,\n size,\n radius,\n disabled,\n readonly,\n required,\n invalid,\n id,\n children,\n propsRoot,\n icon,\n propsIcon,\n propsTag,\n propsClear,\n propsPopover,\n propsListbox,\n ...rest\n }: TagsInputProps<T>) => {\n const textMatcher = useTextMatcher();\n const listId = useId();\n const appearance = useControlAppearance(() => ({ variant, size, radius }));\n const control = useFieldControl(() => ({ id, invalid, required, disabled, readonly }));\n let shell = _state.raw<HTMLElement | null>(null);\n let input = _state.raw<HTMLInputElement | null>(null);\n let panel = _state.raw<HTMLElement | null>(null);\n // 标签增删时其余的平移补位\n const layout = useLayoutAnimation(() => shell, { children: '[data-tag]' });\n\n const setOpen = openSetter(\n () => open,\n (next) => (open = next),\n () => onOpenChange,\n );\n const optionOf = (item: T) => toListboxOption(item, toOption);\n // 异步搜索(filter={false})时结果可能还没回来:建议列表与 combobox 角色一直在,等待时显示加载中\n const suggestions = () => items.length > 0 || filter === false;\n // 标签的文字:异步建议换了 items 之后,已选的仍按之前见过的文字显示;自定义的值就是文字\n const selectedOf = useSelectedOptions({ items: () => items, toOption: optionOf });\n const labelOf = (tag: string) => selectedOf([tag])[0]!.label;\n\n const list = useListState({\n items: () => {\n const query = inputValue.trim();\n if (filter === false || !query) return items;\n const matcher = textMatcher();\n return items.filter((item) =>\n filter ? filter(item, query) : matcher(optionOf(item).label, query),\n );\n },\n toOption: optionOf,\n selected: () => value,\n mode: () => 'multiple',\n onChange: (next) => void layout.change(() => (value = [...next])),\n });\n\n /** 把一段文字变成值:是建议的文字或值时取建议的值,否则(允许时)原样 */\n function resolve(text: string): string | undefined {\n const trimmed = text.trim();\n if (!trimmed) return undefined;\n const item = items.find((candidate) => {\n const option = optionOf(candidate);\n return option.value === trimmed || option.label === trimmed;\n });\n if (item !== undefined) return optionOf(item).value;\n return allowCustomValue ? trimmed : undefined;\n }\n\n /** 加入几段文字;重复的、超出上限的不加;返回是否加入了 */\n function add(texts: readonly string[]) {\n const next = [...value];\n for (const text of texts) {\n const tag = resolve(text);\n if (tag === undefined || next.includes(tag) || (max != null && next.length >= max))\n continue;\n next.push(tag);\n }\n if (next.length === value.length) return false;\n void layout.change(() => (value = next));\n return true;\n }\n\n const remove = (tag: string) =>\n void layout.change(() => (value = value.filter((item) => item !== tag)));\n\n const splitter = () =>\n new RegExp(\n `[${[...delimiters, '\\n', '\\r', '\\t'].map((d) => d.replace(/[\\\\\\]^-]/g, '\\\\$&')).join('')}]`,\n );\n\n // 输入了分隔符:分隔符前的部分加成标签,剩下的留在输入框里\n watch(\n () => inputValue,\n (text) => {\n const parts = text.split(splitter());\n if (parts.length > 1) {\n add(parts.slice(0, -1));\n inputValue = parts.at(-1)!;\n return;\n }\n if (suggestions() && text && !control().readonly) {\n setOpen(true, 'trigger');\n list.move('first');\n }\n },\n );\n\n function onKeydown(event: KeyboardEvent) {\n const state = control();\n if (state.disabled || state.readonly || isComposingKey(event)) return;\n if (event.key === 'Backspace' && !inputValue && value.length) {\n event.preventDefault();\n remove(value.at(-1)!);\n return;\n }\n if (open && suggestions()) {\n if (handleListKeydown(list, event, { homeEnd: false })) {\n event.preventDefault();\n if (event.key === 'Enter') inputValue = '';\n return;\n }\n } else if (event.key === 'ArrowDown' && suggestions()) {\n event.preventDefault();\n list.activateDefault();\n setOpen(true, 'trigger', event);\n return;\n }\n if (event.key === 'Enter' && inputValue.trim()) {\n event.preventDefault();\n if (add([inputValue])) inputValue = '';\n }\n }\n\n // 粘贴多段(有分隔符、换行)时拆开加入;只有一段时照常粘贴\n function onPaste(event: ClipboardEvent) {\n const text = event.clipboardData?.getData('text') ?? '';\n const parts = text.split(splitter());\n if (parts.length < 2) return;\n event.preventDefault();\n add(parts);\n }\n\n const { onToggle } = useAnchoredPopover(\n () => ({\n open: open && suggestions(),\n anchor: shell,\n panel,\n floating: { placement, matchWidth: true, fitHeight: true },\n restoreFocus: false,\n }),\n { close: (reason, event) => setOpen(false, reason, event) },\n );\n\n return () => {\n const state = control();\n const s = controlParts(appearance(), state);\n const editable = !state.disabled && !state.readonly;\n return (\n <>\n <div\n {...mergeAttrs<'div'>({ ...s.root, sx: [s.root.sx, tagsInputStyles.root] }, propsRoot, {\n ref: _toRef(shell),\n // 外壳里的标签不是原生控件的一部分:不可用要写在外壳上,辅助技术才把它们一起当作不可用\n 'aria-disabled': state.disabled || undefined,\n onPointerdown: focusOnShell(() => input),\n })}\n data-state={open ? 'open' : 'closed'}\n data-invalid={state.invalid || undefined}\n data-disabled={state.disabled || undefined}\n >\n {controlIcon(s, icon, propsIcon)}\n {value.map((tag) => {\n const own = typeof propsTag === 'function' ? propsTag(tag) : propsTag;\n return (\n <Tag\n key={tag}\n {...mergePartProps<TagProps>({ variant: 'surface' }, own, {\n onRemove: editable ? () => remove(tag) : undefined,\n })}\n data-tag=\"\"\n >\n {labelOf(tag)}\n </Tag>\n );\n })}\n <input\n {...mergeAttrs<'input'>(\n { ...s.input, sx: [s.input.sx, tagsInputStyles.input] },\n rest,\n {\n ref: _toRef(input),\n id: state.id,\n role: suggestions() ? 'combobox' : undefined,\n autocomplete: 'off',\n disabled: state.disabled,\n readonly: state.readonly,\n 'aria-autocomplete': suggestions() ? 'list' : undefined,\n 'aria-expanded': suggestions() ? open : undefined,\n 'aria-controls': suggestions() ? listId : undefined,\n 'aria-activedescendant':\n open && suggestions() ? activeOptionId(list, listId) : undefined,\n 'aria-invalid': state.invalid || undefined,\n 'aria-required': state.required || undefined,\n 'aria-describedby': state.describedBy,\n onKeydown,\n onPaste,\n onBlur: (event: FocusEvent) => {\n setOpen(false, 'focus', event);\n if (\n acceptOnBlur &&\n inputValue.trim() &&\n !control().readonly &&\n add([inputValue])\n )\n inputValue = '';\n },\n },\n )}\n _value={inputValue}\n />\n {clearable &&\n value.length > 0 &&\n editable &&\n controlClear(s, propsClear, () => {\n void layout.change(() => (value = []));\n input?.focus();\n })}\n <FormValue name={name} value={value} disabled={state.disabled} />\n </div>\n {suggestions() && (\n <ListPopover\n panelRef={_toRef(panel)}\n onToggle={onToggle}\n size={appearance().size}\n labelledBy={state.labelledBy}\n label={rest['aria-label']}\n loading={loading}\n onPick={() => {\n inputValue = '';\n input?.focus();\n }}\n listbox={{ list, id: listId, multiple: true, renderOption: children }}\n propsListbox={propsListbox}\n propsPopover={propsPopover}\n />\n )}\n </>\n );\n };\n },\n);\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"TagsInput.js","names":["_isVNode","_vtp_bindable","_vtp_readonlyProp","_vtp_childrenProp","_vtp_restProps","_vue_shallowRef","_createVNode","_Fragment","_mergeProps","_withDirectives","_vModelText"],"sources":["../../src/input/TagsInput.tsx"],"sourcesContent":["/**\n * 标签输入(多选的组合框):输入框里排着已选的 Tag,输入文字后回车(或输入逗号)加一个,可以从建议列表里选。\n * 关键词、收件人、标签、多个城市。参考 Mantine TagsInput、React Aria TagGroup + ComboBox。\n *\n * <TagsInput _value={keywords} aria-label=\"关键词\" /> 自由输入\n * <TagsInput _value={cities} items={cityOptions} allowCustomValue={false} /> 只能从建议里选\n *\n * - 回车、输入分隔符(默认 , , ; ;)把文字加成标签;粘贴时按分隔符与换行拆成多个;重复的不加;\n * - 输入框为空时按退格删掉最后一个标签;每个标签有移除按钮(离场后其余的平移补位);\n * - 有 items 时边输入边过滤建议(同 Combobox),↑↓ 移动、回车加入或移除高亮项(已选的打勾);\n * 异步取建议时绑定 inputValue 去请求、写 filter={false} 与 loading,结果没回来时列表显示加载中;\n * - 写了 name 时每个值一个隐藏的 input 随表单提交。\n *\n * 未声明的属性落到 input(主元素)上。部件:Root(外壳)、Tag、Input、Clear、Popover、Listbox。\n */\nimport { _component } from '@zerodep-dev/vue-tsx-pro-tsx';\nimport * as css from '@zerodep-dev/vue-tsx-pro-tsx/styles';\nimport { useId, watch, type VNodeChild } from 'vue';\nimport type { PresetIconButtonProps } from '../base/CloseButton';\nimport { Tag, type TagProps } from '../base/Tag';\nimport { useTextMatcher } from '../config/context';\nimport { isComposingKey } from '../utils/dom/hotkeys';\nimport { useAnchoredPopover, type FloatingPlacement } from '../utils/dom/floating';\nimport { openSetter, type OpenChangeProps } from '../utils/dom/open-change';\nimport { ListPopover } from './ListPopover';\nimport { useLayoutAnimation } from '../utils/motion/layout';\nimport { mergeAttrs, mergePartProps, type PartProps } from '../utils/props';\nimport {\n controlClear,\n controlParts,\n focusOnShell,\n controlIcon,\n useControlAppearance,\n type ControlIconOptions,\n type ControlOptions,\n} from './control';\nimport { useFieldControl } from '../form/Field';\nimport { FormValue } from '../form/FormValue';\nimport { activeOptionId, type ListboxViewProps, type OptionRenderState } from './Listbox';\nimport {\n handleListKeydown,\n useListState,\n useSelectedOptions,\n type ListboxOption,\n type OptionMapping,\n toListboxOption,\n} from './list-state';\n\n/** 角色、展开状态与可用性由组件管理 */\nexport type TagsInputManaged =\n | 'value'\n | 'role'\n | 'size'\n | 'disabled'\n | 'readonly'\n | 'autocomplete'\n | 'aria-autocomplete'\n | 'aria-expanded'\n | 'aria-controls'\n | 'aria-activedescendant'\n | 'aria-invalid'\n | 'aria-required'\n | 'aria-describedby';\n\n/** TagsInput 除 toOption 外的属性;未声明的属性落到 input(主元素)上,外壳用 propsRoot */\nexport interface TagsInputBaseProps<T>\n extends\n PartProps<'input', TagsInputManaged>,\n ControlOptions,\n ControlIconOptions,\n OpenChangeProps {\n /** 已选的值:父级写 _value={list} */\n _value?: string[];\n /** 建议:ListboxOption、字符串,或任意数据加 toOption;不给时是纯自由输入 */\n items?: readonly T[];\n /** 允许加入不在建议里的文字,默认 true */\n allowCustomValue?: boolean;\n /** 失焦时把输入框里还没确认的文字加成标签,默认 true(同 Mantine);false 时文字留在框里 */\n acceptOnBlur?: boolean;\n /** 把输入拆成标签的分隔符,默认 [',', ',', ';', ';'];回车与粘贴里的换行总是分隔 */\n delimiters?: readonly string[];\n /** 最多几个标签;到了上限不再加入 */\n max?: number;\n /** 输入框里的文字:父级可以写 _inputValue={x}(异步取建议时据此搜索) */\n _inputValue?: string;\n /** 建议列表是否打开 */\n _open?: boolean;\n /** 过滤建议:默认按显示文字包含输入;false 不过滤(异步搜索) */\n filter?: false | ((item: T, query: string) => boolean);\n /** 正在取建议 */\n loading?: boolean;\n /** 弹出位置,默认 bottom-start */\n placement?: FloatingPlacement;\n /** 有标签时显示清空按钮 */\n clearable?: boolean;\n /** 表单提交时的 name(每个值一个隐藏的 input) */\n name?: string;\n /** 不可用 */\n disabled?: boolean;\n /** 只读:标签不能移除,不能输入 */\n readonly?: boolean;\n /** 必填:至少要有一个标签(读屏念“必填”;校验交给表单) */\n required?: boolean;\n /** 校验未通过 */\n invalid?: boolean;\n /** 自定义建议的内容 */\n children?: (item: T, state: OptionRenderState) => VNodeChild;\n /** 外壳的部件属性 */\n propsRoot?: PartProps<'div'>;\n /** 每个标签的部件属性,可以按值计算 */\n propsTag?: Partial<TagProps> | ((value: string) => Partial<TagProps>);\n /** 清空按钮的部件属性 */\n propsClear?: Partial<PresetIconButtonProps>;\n /** 弹出层的部件属性 */\n propsPopover?: PartProps<'div', 'id' | 'popover'>;\n /** 建议列表的部件属性 */\n propsListbox?: Partial<ListboxViewProps<T>>;\n}\n\n/** 标签输入的属性;items 不是 ListboxOption 或字符串时要给 toOption */\nexport type TagsInputProps<T = ListboxOption> = TagsInputBaseProps<T> & OptionMapping<T>;\n\n/** TagsInput 的样式:root、input、popover */\nexport const tagsInputStyles = css.create({\n // 标签多了换行;行与行之间留出间距\n root: { flexWrap: 'wrap', rowGap: '_xs', paddingBlock: '_xs' },\n // 输入框至少留出几个字的位置,放不下时换到下一行\n input: { flexBasis: '6em' },\n});\n\n/** 默认的分隔符:英文、中文的逗号与分号 */\nconst DELIMITERS = [',', ',', ';', ';'];\n\n/** 标签输入:输入框里排着 Tag,回车或分隔符加入,退格删最后一个 */\nexport const TagsInput = _component(\n <T = ListboxOption,>({\n _value: value = _bindable<string[]>([]),\n items = [],\n toOption,\n allowCustomValue = true,\n acceptOnBlur = true,\n delimiters = DELIMITERS,\n max,\n _inputValue: inputValue = _bindable(''),\n _open: open = _bindable(false),\n onOpenChange,\n filter,\n loading = false,\n placement = 'bottom-start',\n clearable = false,\n name,\n variant,\n size,\n radius,\n disabled,\n readonly,\n required,\n invalid,\n id,\n children,\n propsRoot,\n icon,\n propsIcon,\n propsTag,\n propsClear,\n propsPopover,\n propsListbox,\n ...rest\n }: TagsInputProps<T>) => {\n const textMatcher = useTextMatcher();\n const listId = useId();\n const appearance = useControlAppearance(() => ({ variant, size, radius }));\n const control = useFieldControl(() => ({ id, invalid, required, disabled, readonly }));\n let shell = _state.raw<HTMLElement | null>(null);\n let input = _state.raw<HTMLInputElement | null>(null);\n let panel = _state.raw<HTMLElement | null>(null);\n // 标签增删时其余的平移补位\n const layout = useLayoutAnimation(() => shell, { children: '[data-tag]' });\n\n const setOpen = openSetter(\n () => open,\n (next) => (open = next),\n () => onOpenChange,\n );\n const optionOf = (item: T) => toListboxOption(item, toOption);\n // 异步搜索(filter={false})时结果可能还没回来:建议列表与 combobox 角色一直在,等待时显示加载中\n const suggestions = () => items.length > 0 || filter === false;\n // 标签的文字:异步建议换了 items 之后,已选的仍按之前见过的文字显示;自定义的值就是文字\n const selectedOf = useSelectedOptions({ items: () => items, toOption: optionOf });\n const labelOf = (tag: string) => selectedOf([tag])[0]!.label;\n\n const list = useListState({\n items: () => {\n const query = inputValue.trim();\n if (filter === false || !query) return items;\n const matcher = textMatcher();\n return items.filter((item) =>\n filter ? filter(item, query) : matcher(optionOf(item).label, query),\n );\n },\n toOption: optionOf,\n selected: () => value,\n mode: () => 'multiple',\n onChange: (next) => void layout.change(() => (value = [...next])),\n });\n\n /** 把一段文字变成值:是建议的文字或值时取建议的值,否则(允许时)原样 */\n function resolve(text: string): string | undefined {\n const trimmed = text.trim();\n if (!trimmed) return undefined;\n const item = items.find((candidate) => {\n const option = optionOf(candidate);\n return option.value === trimmed || option.label === trimmed;\n });\n if (item !== undefined) return optionOf(item).value;\n return allowCustomValue ? trimmed : undefined;\n }\n\n /** 加入几段文字;重复的、超出上限的不加;返回是否加入了 */\n function add(texts: readonly string[]) {\n const next = [...value];\n for (const text of texts) {\n const tag = resolve(text);\n if (tag === undefined || next.includes(tag) || (max != null && next.length >= max))\n continue;\n next.push(tag);\n }\n if (next.length === value.length) return false;\n void layout.change(() => (value = next));\n return true;\n }\n\n const remove = (tag: string) =>\n void layout.change(() => (value = value.filter((item) => item !== tag)));\n\n const splitter = () =>\n new RegExp(\n `[${[...delimiters, '\\n', '\\r', '\\t'].map((d) => d.replace(/[\\\\\\]^-]/g, '\\\\$&')).join('')}]`,\n );\n\n // 输入了分隔符:分隔符前的部分加成标签,剩下的留在输入框里\n watch(\n () => inputValue,\n (text) => {\n const parts = text.split(splitter());\n if (parts.length > 1) {\n add(parts.slice(0, -1));\n inputValue = parts.at(-1)!;\n return;\n }\n if (suggestions() && text && !control().readonly) {\n setOpen(true, 'trigger');\n list.move('first');\n }\n },\n );\n\n function onKeydown(event: KeyboardEvent) {\n const state = control();\n if (state.disabled || state.readonly || isComposingKey(event)) return;\n if (event.key === 'Backspace' && !inputValue && value.length) {\n event.preventDefault();\n remove(value.at(-1)!);\n return;\n }\n if (open && suggestions()) {\n if (handleListKeydown(list, event, { homeEnd: false })) {\n event.preventDefault();\n if (event.key === 'Enter') inputValue = '';\n return;\n }\n } else if (event.key === 'ArrowDown' && suggestions()) {\n event.preventDefault();\n list.activateDefault();\n setOpen(true, 'trigger', event);\n return;\n }\n if (event.key === 'Enter' && inputValue.trim()) {\n event.preventDefault();\n if (add([inputValue])) inputValue = '';\n }\n }\n\n // 粘贴多段(有分隔符、换行)时拆开加入;只有一段时照常粘贴\n function onPaste(event: ClipboardEvent) {\n const text = event.clipboardData?.getData('text') ?? '';\n const parts = text.split(splitter());\n if (parts.length < 2) return;\n event.preventDefault();\n add(parts);\n }\n\n const { onToggle } = useAnchoredPopover(\n () => ({\n open: open && suggestions(),\n anchor: shell,\n panel,\n floating: { placement, matchWidth: true, fitHeight: true },\n restoreFocus: false,\n }),\n { close: (reason, event) => setOpen(false, reason, event) },\n );\n\n return () => {\n const state = control();\n const s = controlParts(appearance(), state);\n const editable = !state.disabled && !state.readonly;\n return (\n <>\n <div\n {...mergeAttrs<'div'>({ ...s.root, sx: [s.root.sx, tagsInputStyles.root] }, propsRoot, {\n ref: _toRef(shell),\n // 外壳里的标签不是原生控件的一部分:不可用要写在外壳上,辅助技术才把它们一起当作不可用\n 'aria-disabled': state.disabled || undefined,\n onPointerdown: focusOnShell(() => input),\n })}\n data-state={open ? 'open' : 'closed'}\n data-invalid={state.invalid || undefined}\n data-disabled={state.disabled || undefined}\n >\n {controlIcon(s, icon, propsIcon)}\n {value.map((tag) => {\n const own = typeof propsTag === 'function' ? propsTag(tag) : propsTag;\n return (\n <Tag\n key={tag}\n {...mergePartProps<TagProps>({ variant: 'surface' }, own, {\n onRemove: editable ? () => remove(tag) : undefined,\n })}\n data-tag=\"\"\n >\n {labelOf(tag)}\n </Tag>\n );\n })}\n <input\n {...mergeAttrs<'input'>(\n { ...s.input, sx: [s.input.sx, tagsInputStyles.input] },\n rest,\n {\n ref: _toRef(input),\n id: state.id,\n role: suggestions() ? 'combobox' : undefined,\n autocomplete: 'off',\n disabled: state.disabled,\n readonly: state.readonly,\n 'aria-autocomplete': suggestions() ? 'list' : undefined,\n 'aria-expanded': suggestions() ? open : undefined,\n 'aria-controls': suggestions() ? listId : undefined,\n 'aria-activedescendant':\n open && suggestions() ? activeOptionId(list, listId) : undefined,\n 'aria-invalid': state.invalid || undefined,\n 'aria-required': state.required || undefined,\n 'aria-describedby': state.describedBy,\n onKeydown,\n onPaste,\n onBlur: (event: FocusEvent) => {\n setOpen(false, 'focus', event);\n if (\n acceptOnBlur &&\n inputValue.trim() &&\n !control().readonly &&\n add([inputValue])\n )\n inputValue = '';\n },\n },\n )}\n _value={inputValue}\n />\n {clearable &&\n value.length > 0 &&\n editable &&\n controlClear(s, propsClear, () => {\n void layout.change(() => (value = []));\n input?.focus();\n })}\n <FormValue name={name} value={value} disabled={state.disabled} />\n </div>\n {suggestions() && (\n <ListPopover\n panelRef={_toRef(panel)}\n onToggle={onToggle}\n size={appearance().size}\n labelledBy={state.labelledBy}\n label={rest['aria-label']}\n loading={loading}\n onPick={() => {\n inputValue = '';\n input?.focus();\n }}\n listbox={{ list, id: listId, multiple: true, renderOption: children }}\n propsListbox={propsListbox}\n propsPopover={propsPopover}\n />\n )}\n </>\n );\n };\n },\n);\n"],"mappings":"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"}
|
package/dist/input/TextField.js
CHANGED
|
@@ -5,8 +5,8 @@ import { useFieldControl } from "../form/Field.js";
|
|
|
5
5
|
import { CharacterCount } from "./CharacterCount.js";
|
|
6
6
|
import { controlAffix, controlClear, controlIcon, controlParts, focusOnShell, useControlAppearance } from "./control.js";
|
|
7
7
|
import { displayPosition } from "./text-format.js";
|
|
8
|
-
import { createVNode,
|
|
9
|
-
import { _component, bindable, readonlyProp, restProps } from "@zerodep-dev/vue-tsx-pro-tsx";
|
|
8
|
+
import { createVNode, ref, shallowRef, useId, vModelText, withDirectives } from "vue";
|
|
9
|
+
import { _component, bindable, mergeJsxProps, readonlyProp, restProps } from "@zerodep-dev/vue-tsx-pro-tsx";
|
|
10
10
|
import { Eye, EyeOff } from "@lucide/icons";
|
|
11
11
|
//#region src/input/TextField.tsx
|
|
12
12
|
/**
|
|
@@ -119,16 +119,16 @@ var TextField = /*#__PURE__*/ _component((_props, _ctx) => {
|
|
|
119
119
|
"aria-invalid": state.invalid || void 0,
|
|
120
120
|
"aria-describedby": describedBy || void 0
|
|
121
121
|
});
|
|
122
|
-
return createVNode("div",
|
|
122
|
+
return createVNode("div", mergeJsxProps(mergeAttrs(s.root, propsRoot.value, { onPointerdown: focusOnShell(() => input.value) }), {
|
|
123
123
|
"data-invalid": state.invalid || void 0,
|
|
124
124
|
"data-disabled": state.disabled || void 0
|
|
125
125
|
}), [
|
|
126
126
|
controlIcon(s, icon.value, propsIcon.value),
|
|
127
127
|
controlAffix(s, prefix.value, inputId, propsPrefix.value),
|
|
128
|
-
format.value ? createVNode("input",
|
|
128
|
+
format.value ? createVNode("input", mergeJsxProps(inputAttrs, {
|
|
129
129
|
"value": format.value.format(value.value),
|
|
130
130
|
"onInput": onFormattedInput
|
|
131
|
-
}), null) : withDirectives(createVNode("input",
|
|
131
|
+
}), null) : withDirectives(createVNode("input", mergeJsxProps(inputAttrs, { "onUpdate:modelValue": ($event) => value.value = $event }), null), [[vModelText, value.value]]),
|
|
132
132
|
controlAffix(s, suffix.value, inputId, propsSuffix.value),
|
|
133
133
|
clearable.value && value.value && editable && controlClear(s, propsClear.value, clear),
|
|
134
134
|
password && createVNode(IconButton, mergePartProps({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TextField.js","names":["_vtp_bindable","_vtp_readonlyProp","_vtp_restProps","_vue_shallowRef","_vue_ref","_createVNode","_mergeProps","_withDirectives","_vModelText"],"sources":["../../src/input/TextField.tsx"],"sourcesContent":["/**\n * 单行文本框:外壳(div)里放原生 input,前后可以带图标,可以一键清空;type=\"password\" 时带显示 / 隐藏密码的按钮。\n *\n * <TextField _value={keyword} type=\"search\" icon={Search} clearable aria-label=\"搜索\" />\n * <Field label=\"密码\" error={errors.password}><TextField _value={password} type=\"password\" /></Field>\n *\n * 未声明的属性落到 input(主元素)上,外壳用 propsRoot(COMPONENTS.md §2)。\n * 双向绑定经 v-model:中文输入法组合输入期间不写回,组合结束才写回。\n * format 让输入时自动分组显示(手机号 138 0000 1111、身份证号、银行卡号,见 text-format.ts):值是去掉分隔的原文,\n * 重新分组后光标留在刚输入的字符后面。\n * 在 Field 里时自动关联标签、说明、错误信息,并沿用 Field 的 invalid / required / disabled / readonly。\n *\n * 部件:Root(外壳)、Input、Icon、IconEnd、Prefix、Suffix(固定文字)、Clear(ClearButton)、Reveal(显示密码的 IconButton)、\n * Count(字数计数 CharacterCount)。\n */\nimport { Eye, EyeOff } from '@lucide/icons';\nimport { _component } from '@zerodep-dev/vue-tsx-pro-tsx';\nimport { useId, type VNodeChild } from 'vue';\nimport type { PresetIconButtonProps } from '../base/CloseButton';\nimport type { IconData, IconProps } from '../base/Icon';\nimport { IconButton, type IconButtonProps } from '../base/IconButton';\nimport { useLang } from '../config/context';\nimport { CharacterCount, type CharacterCountProps } from './CharacterCount';\nimport { displayPosition, type TextFormat } from './text-format';\nimport { useFieldControl } from '../form/Field';\nimport { mergeAttrs, mergePartProps, type PartProps } from '../utils/props';\nimport {\n controlClear,\n controlParts,\n focusOnShell,\n controlAffix,\n controlIcon,\n useControlAppearance,\n type ControlIconOptions,\n type ControlOptions,\n} from './control';\n\n/** 值与状态由组件管理;原生的 size 属性(字符宽度)让给字号档位,prefix 属性(RDFa)让给前缀 */\nexport type TextFieldManaged =\n | 'value'\n | 'disabled'\n | 'readonly'\n | 'required'\n | 'size'\n | 'prefix'\n | 'aria-invalid'\n | 'aria-describedby';\n\n/** schema 的格式(JSON Schema format)→ 输入框的 type:手机上弹出对应的键盘 */\nconst FORMAT_TYPES: Readonly<Record<string, 'email' | 'url'>> = { email: 'email', uri: 'url' };\n\n/** 未声明的属性落到 input(主元素)上;外壳用 propsRoot */\nexport interface TextFieldProps\n extends PartProps<'input', TextFieldManaged>, ControlOptions, ControlIconOptions {\n /** 输入值:父级写 _value={x} 双向绑定 */\n _value?: string;\n /** 显示格式:输入时自动分组(phoneFormat、idCardFormat、bankCardFormat 或 createGroupedFormat),值是去掉分隔的原文 */\n format?: TextFormat;\n /** 不可用 */\n disabled?: boolean;\n /** 只读:可以选中、复制,不能修改 */\n readonly?: boolean;\n /** 必填 */\n required?: boolean;\n /** 校验未通过 */\n invalid?: boolean;\n /** 有内容时显示清空按钮 */\n clearable?: boolean;\n /** 后置图标 */\n iconEnd?: IconData;\n /** 输入框前的固定文字(如 `https://`、`¥`),不算在值里 */\n prefix?: VNodeChild;\n /** 输入框后的固定文字(如 `.com`、`元`),不算在值里 */\n suffix?: VNodeChild;\n /** 显示字数计数;上限取 maxlength(含 zod 推出的约束),超出时用危险色 */\n showCount?: boolean;\n /** 外壳(边框、焦点环)的部件属性 */\n propsRoot?: PartProps<'div'>;\n /** 后置图标的部件属性 */\n propsIconEnd?: Partial<IconProps>;\n /** 前缀文字的部件属性(指向输入框的 label) */\n propsPrefix?: PartProps<'label'>;\n /** 后缀文字的部件属性(指向输入框的 label) */\n propsSuffix?: PartProps<'label'>;\n /** 字数计数的部件属性 */\n propsCount?: Partial<CharacterCountProps>;\n /** 清空按钮的部件属性 */\n propsClear?: Partial<PresetIconButtonProps>;\n /** 显示 / 隐藏密码按钮的部件属性 */\n propsReveal?: Partial<IconButtonProps>;\n}\n\n/** 单行文本框:未声明的属性落到 input 上 */\nexport const TextField = _component(\n ({\n _value: value = _bindable(''),\n format,\n type,\n variant,\n size,\n radius,\n disabled,\n readonly,\n required,\n invalid,\n clearable = false,\n icon,\n iconEnd,\n prefix,\n suffix,\n showCount = false,\n id,\n propsRoot,\n propsIcon,\n propsIconEnd,\n propsPrefix,\n propsSuffix,\n propsCount,\n propsClear,\n propsReveal,\n ...rest\n }: TextFieldProps) => {\n const lang = useLang();\n const appearance = useControlAppearance(() => ({ variant, size, radius }));\n const control = useFieldControl(() => ({ id, invalid, required, disabled, readonly }));\n let input = _state.raw<HTMLInputElement | null>(null);\n let revealed = _state(false);\n const countId = useId();\n // 前后缀的 label 要指向输入框:不在 Field 里、也没写 id 时用自己生成的\n const ownId = useId();\n\n function clear() {\n value = '';\n input?.focus();\n }\n\n /** 有 format 时的输入:取出原文写回,再按格式显示,光标放回刚才那个字符的后面 */\n function onFormattedInput(event: Event) {\n const element = event.target as HTMLInputElement;\n const caret = element.selectionStart ?? element.value.length;\n const before = format!.parse(element.value.slice(0, caret)).length;\n const raw = format!.parse(element.value);\n value = raw;\n const shown = format!.format(raw);\n if (element.value !== shown) {\n element.value = shown;\n const position = displayPosition(format!, raw, before);\n element.setSelectionRange(position, position);\n }\n }\n\n return () => {\n const state = control();\n const s = controlParts(appearance(), state);\n const editable = !state.disabled && !state.readonly;\n const constraints = state.constraints;\n // 不写 type 时按 schema 的格式选:z.email() → email、z.url() → url\n const inputType = type ?? FORMAT_TYPES[constraints.format ?? ''] ?? 'text';\n const password = inputType === 'password';\n const limit = rest.maxlength ?? constraints.maxLength;\n const describedBy = [state.describedBy, showCount && countId].filter(Boolean).join(' ');\n const inputId = state.id ?? ownId;\n const inputAttrs = mergeAttrs<'input'>(\n {\n ...s.input,\n // 有 format 时输入框里多了分隔,按原文的长度限制交给 format.parse 截断\n minlength: format ? undefined : constraints.minLength,\n maxlength: format ? undefined : constraints.maxLength,\n inputmode: format?.inputmode,\n },\n rest,\n {\n ref: _toRef(input),\n id: inputId,\n type: password && revealed ? 'text' : inputType,\n disabled: state.disabled,\n readonly: state.readonly,\n required: state.required,\n 'aria-invalid': state.invalid || undefined,\n 'aria-describedby': describedBy || undefined,\n },\n );\n return (\n <div\n {...mergeAttrs<'div'>(s.root, propsRoot, {\n onPointerdown: focusOnShell(() => input),\n })}\n data-invalid={state.invalid || undefined}\n data-disabled={state.disabled || undefined}\n >\n {controlIcon(s, icon, propsIcon)}\n {controlAffix(s, prefix, inputId, propsPrefix)}\n {format ? (\n <input {...inputAttrs} value={format.format(value)} onInput={onFormattedInput} />\n ) : (\n // _bind 编译为 v-model:复用 Vue 对输入法组合输入的处理,组合结束才写回\n <input {...inputAttrs} _value={value} />\n )}\n {controlAffix(s, suffix, inputId, propsSuffix)}\n {clearable && value && editable && controlClear(s, propsClear, clear)}\n {password && (\n <IconButton\n {...mergePartProps<IconButtonProps>(\n {\n ...s.button,\n radius: 'full',\n icon: revealed ? EyeOff : Eye,\n // 名称不变、用 aria-pressed 表示状态:读屏念“显示密码,已按下”(名称跟着变会和按下状态打架)\n 'aria-label': lang.passwordShow,\n },\n propsReveal,\n {\n disabled: state.disabled,\n 'aria-pressed': revealed,\n onClick: () => (revealed = !revealed),\n },\n )}\n />\n )}\n {showCount && (\n <CharacterCount\n {...mergePartProps<CharacterCountProps>(\n { count: value.length, max: limit == null ? undefined : Number(limit) },\n propsCount,\n { id: countId },\n )}\n />\n )}\n {controlIcon(s, iconEnd, propsIconEnd)}\n </div>\n );\n };\n },\n);\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"TextField.js","names":["_vtp_bindable","_vtp_readonlyProp","_vtp_restProps","_vue_shallowRef","_vue_ref","_createVNode","_mergeProps","_withDirectives","_vModelText"],"sources":["../../src/input/TextField.tsx"],"sourcesContent":["/**\n * 单行文本框:外壳(div)里放原生 input,前后可以带图标,可以一键清空;type=\"password\" 时带显示 / 隐藏密码的按钮。\n *\n * <TextField _value={keyword} type=\"search\" icon={Search} clearable aria-label=\"搜索\" />\n * <Field label=\"密码\" error={errors.password}><TextField _value={password} type=\"password\" /></Field>\n *\n * 未声明的属性落到 input(主元素)上,外壳用 propsRoot(COMPONENTS.md §2)。\n * 双向绑定经 v-model:中文输入法组合输入期间不写回,组合结束才写回。\n * format 让输入时自动分组显示(手机号 138 0000 1111、身份证号、银行卡号,见 text-format.ts):值是去掉分隔的原文,\n * 重新分组后光标留在刚输入的字符后面。\n * 在 Field 里时自动关联标签、说明、错误信息,并沿用 Field 的 invalid / required / disabled / readonly。\n *\n * 部件:Root(外壳)、Input、Icon、IconEnd、Prefix、Suffix(固定文字)、Clear(ClearButton)、Reveal(显示密码的 IconButton)、\n * Count(字数计数 CharacterCount)。\n */\nimport { Eye, EyeOff } from '@lucide/icons';\nimport { _component } from '@zerodep-dev/vue-tsx-pro-tsx';\nimport { useId, type VNodeChild } from 'vue';\nimport type { PresetIconButtonProps } from '../base/CloseButton';\nimport type { IconData, IconProps } from '../base/Icon';\nimport { IconButton, type IconButtonProps } from '../base/IconButton';\nimport { useLang } from '../config/context';\nimport { CharacterCount, type CharacterCountProps } from './CharacterCount';\nimport { displayPosition, type TextFormat } from './text-format';\nimport { useFieldControl } from '../form/Field';\nimport { mergeAttrs, mergePartProps, type PartProps } from '../utils/props';\nimport {\n controlClear,\n controlParts,\n focusOnShell,\n controlAffix,\n controlIcon,\n useControlAppearance,\n type ControlIconOptions,\n type ControlOptions,\n} from './control';\n\n/** 值与状态由组件管理;原生的 size 属性(字符宽度)让给字号档位,prefix 属性(RDFa)让给前缀 */\nexport type TextFieldManaged =\n | 'value'\n | 'disabled'\n | 'readonly'\n | 'required'\n | 'size'\n | 'prefix'\n | 'aria-invalid'\n | 'aria-describedby';\n\n/** schema 的格式(JSON Schema format)→ 输入框的 type:手机上弹出对应的键盘 */\nconst FORMAT_TYPES: Readonly<Record<string, 'email' | 'url'>> = { email: 'email', uri: 'url' };\n\n/** 未声明的属性落到 input(主元素)上;外壳用 propsRoot */\nexport interface TextFieldProps\n extends PartProps<'input', TextFieldManaged>, ControlOptions, ControlIconOptions {\n /** 输入值:父级写 _value={x} 双向绑定 */\n _value?: string;\n /** 显示格式:输入时自动分组(phoneFormat、idCardFormat、bankCardFormat 或 createGroupedFormat),值是去掉分隔的原文 */\n format?: TextFormat;\n /** 不可用 */\n disabled?: boolean;\n /** 只读:可以选中、复制,不能修改 */\n readonly?: boolean;\n /** 必填 */\n required?: boolean;\n /** 校验未通过 */\n invalid?: boolean;\n /** 有内容时显示清空按钮 */\n clearable?: boolean;\n /** 后置图标 */\n iconEnd?: IconData;\n /** 输入框前的固定文字(如 `https://`、`¥`),不算在值里 */\n prefix?: VNodeChild;\n /** 输入框后的固定文字(如 `.com`、`元`),不算在值里 */\n suffix?: VNodeChild;\n /** 显示字数计数;上限取 maxlength(含 zod 推出的约束),超出时用危险色 */\n showCount?: boolean;\n /** 外壳(边框、焦点环)的部件属性 */\n propsRoot?: PartProps<'div'>;\n /** 后置图标的部件属性 */\n propsIconEnd?: Partial<IconProps>;\n /** 前缀文字的部件属性(指向输入框的 label) */\n propsPrefix?: PartProps<'label'>;\n /** 后缀文字的部件属性(指向输入框的 label) */\n propsSuffix?: PartProps<'label'>;\n /** 字数计数的部件属性 */\n propsCount?: Partial<CharacterCountProps>;\n /** 清空按钮的部件属性 */\n propsClear?: Partial<PresetIconButtonProps>;\n /** 显示 / 隐藏密码按钮的部件属性 */\n propsReveal?: Partial<IconButtonProps>;\n}\n\n/** 单行文本框:未声明的属性落到 input 上 */\nexport const TextField = _component(\n ({\n _value: value = _bindable(''),\n format,\n type,\n variant,\n size,\n radius,\n disabled,\n readonly,\n required,\n invalid,\n clearable = false,\n icon,\n iconEnd,\n prefix,\n suffix,\n showCount = false,\n id,\n propsRoot,\n propsIcon,\n propsIconEnd,\n propsPrefix,\n propsSuffix,\n propsCount,\n propsClear,\n propsReveal,\n ...rest\n }: TextFieldProps) => {\n const lang = useLang();\n const appearance = useControlAppearance(() => ({ variant, size, radius }));\n const control = useFieldControl(() => ({ id, invalid, required, disabled, readonly }));\n let input = _state.raw<HTMLInputElement | null>(null);\n let revealed = _state(false);\n const countId = useId();\n // 前后缀的 label 要指向输入框:不在 Field 里、也没写 id 时用自己生成的\n const ownId = useId();\n\n function clear() {\n value = '';\n input?.focus();\n }\n\n /** 有 format 时的输入:取出原文写回,再按格式显示,光标放回刚才那个字符的后面 */\n function onFormattedInput(event: Event) {\n const element = event.target as HTMLInputElement;\n const caret = element.selectionStart ?? element.value.length;\n const before = format!.parse(element.value.slice(0, caret)).length;\n const raw = format!.parse(element.value);\n value = raw;\n const shown = format!.format(raw);\n if (element.value !== shown) {\n element.value = shown;\n const position = displayPosition(format!, raw, before);\n element.setSelectionRange(position, position);\n }\n }\n\n return () => {\n const state = control();\n const s = controlParts(appearance(), state);\n const editable = !state.disabled && !state.readonly;\n const constraints = state.constraints;\n // 不写 type 时按 schema 的格式选:z.email() → email、z.url() → url\n const inputType = type ?? FORMAT_TYPES[constraints.format ?? ''] ?? 'text';\n const password = inputType === 'password';\n const limit = rest.maxlength ?? constraints.maxLength;\n const describedBy = [state.describedBy, showCount && countId].filter(Boolean).join(' ');\n const inputId = state.id ?? ownId;\n const inputAttrs = mergeAttrs<'input'>(\n {\n ...s.input,\n // 有 format 时输入框里多了分隔,按原文的长度限制交给 format.parse 截断\n minlength: format ? undefined : constraints.minLength,\n maxlength: format ? undefined : constraints.maxLength,\n inputmode: format?.inputmode,\n },\n rest,\n {\n ref: _toRef(input),\n id: inputId,\n type: password && revealed ? 'text' : inputType,\n disabled: state.disabled,\n readonly: state.readonly,\n required: state.required,\n 'aria-invalid': state.invalid || undefined,\n 'aria-describedby': describedBy || undefined,\n },\n );\n return (\n <div\n {...mergeAttrs<'div'>(s.root, propsRoot, {\n onPointerdown: focusOnShell(() => input),\n })}\n data-invalid={state.invalid || undefined}\n data-disabled={state.disabled || undefined}\n >\n {controlIcon(s, icon, propsIcon)}\n {controlAffix(s, prefix, inputId, propsPrefix)}\n {format ? (\n <input {...inputAttrs} value={format.format(value)} onInput={onFormattedInput} />\n ) : (\n // _bind 编译为 v-model:复用 Vue 对输入法组合输入的处理,组合结束才写回\n <input {...inputAttrs} _value={value} />\n )}\n {controlAffix(s, suffix, inputId, propsSuffix)}\n {clearable && value && editable && controlClear(s, propsClear, clear)}\n {password && (\n <IconButton\n {...mergePartProps<IconButtonProps>(\n {\n ...s.button,\n radius: 'full',\n icon: revealed ? EyeOff : Eye,\n // 名称不变、用 aria-pressed 表示状态:读屏念“显示密码,已按下”(名称跟着变会和按下状态打架)\n 'aria-label': lang.passwordShow,\n },\n propsReveal,\n {\n disabled: state.disabled,\n 'aria-pressed': revealed,\n onClick: () => (revealed = !revealed),\n },\n )}\n />\n )}\n {showCount && (\n <CharacterCount\n {...mergePartProps<CharacterCountProps>(\n { count: value.length, max: limit == null ? undefined : Number(limit) },\n propsCount,\n { id: countId },\n )}\n />\n )}\n {controlIcon(s, iconEnd, propsIconEnd)}\n </div>\n );\n };\n },\n);\n"],"mappings":"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"}
|
package/dist/input/Textarea.js
CHANGED
|
@@ -2,8 +2,8 @@ import { mergeAttrs, mergePartProps } from "../utils/props.js";
|
|
|
2
2
|
import { useFieldControl } from "../form/Field.js";
|
|
3
3
|
import { CharacterCount } from "./CharacterCount.js";
|
|
4
4
|
import { controlClear, controlParts, useControlAppearance } from "./control.js";
|
|
5
|
-
import { createVNode,
|
|
6
|
-
import { _component, bindable, effect, readonlyProp, restProps } from "@zerodep-dev/vue-tsx-pro-tsx";
|
|
5
|
+
import { createVNode, nextTick, shallowRef, useId, vModelText, withDirectives } from "vue";
|
|
6
|
+
import { _component, bindable, effect, 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/input/Textarea.tsx
|
|
9
9
|
/** 值与状态由组件管理 */
|
|
@@ -78,11 +78,11 @@ var Textarea = /*#__PURE__*/ _component((_props, _ctx) => {
|
|
|
78
78
|
const s = controlParts(appearance(), state, true);
|
|
79
79
|
const limit = rest.maxlength ?? state.constraints.maxLength;
|
|
80
80
|
const describedBy = [state.describedBy, showCount.value && countId].filter(Boolean).join(" ");
|
|
81
|
-
return createVNode("div",
|
|
81
|
+
return createVNode("div", mergeJsxProps(mergeAttrs(s.root, propsRoot.value), {
|
|
82
82
|
"data-invalid": state.invalid || void 0,
|
|
83
83
|
"data-disabled": state.disabled || void 0
|
|
84
84
|
}), [
|
|
85
|
-
withDirectives(createVNode("textarea",
|
|
85
|
+
withDirectives(createVNode("textarea", mergeJsxProps(mergeAttrs({
|
|
86
86
|
...s.input,
|
|
87
87
|
sx: [
|
|
88
88
|
s.input.sx,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Textarea.js","names":["_vtp_bindable","_vtp_readonlyProp","_vtp_restProps","_vue_shallowRef","_createVNode","_mergeProps","_withDirectives","_vModelText"],"sources":["../../src/input/Textarea.tsx"],"sourcesContent":["/**\n * 多行文本框:外壳与 TextField 相同(control),高度随内容增长,在 rows 与 maxRows 之间。\n *\n * <Field label=\"备注\"><Textarea _value={note} rows={3} maxRows={8} /></Field>\n *\n * 自动增高:内容变化后(含父级改值)把高度设为 scrollHeight,上下限用 lh 单位(行高的倍数)由 CSS 夹住。\n * 没用 field-sizing: content:Firefox 还不支持,样式的类型表里也没有。中文输入法照常(v-model)。\n *\n * 未声明的属性落到 textarea(主元素)上,外壳用 propsRoot。部件:Root(外壳)、Textarea。\n */\nimport { _component } from '@zerodep-dev/vue-tsx-pro-tsx';\nimport * as css from '@zerodep-dev/vue-tsx-pro-tsx/styles';\nimport { nextTick, useId } from 'vue';\nimport { CharacterCount, type CharacterCountProps } from './CharacterCount';\nimport { useFieldControl } from '../form/Field';\nimport { mergeAttrs, mergePartProps, type PartProps } from '../utils/props';\nimport type { PresetIconButtonProps } from '../base/CloseButton';\nimport { controlClear, controlParts, useControlAppearance, type ControlOptions } from './control';\n\n/** 值与状态由组件管理 */\nexport type TextareaManaged =\n 'value' | 'rows' | 'disabled' | 'readonly' | 'required' | 'aria-invalid' | 'aria-describedby';\n\n/** 未声明的属性落到 textarea(主元素)上;外壳用 propsRoot */\nexport interface TextareaProps extends PartProps<'textarea', TextareaManaged>, ControlOptions {\n /** 输入值:父级写 _value={x} 双向绑定 */\n _value?: string;\n /** 最少显示几行,默认 3 */\n rows?: number;\n /** 最多增高到几行,之后出现滚动条;默认不限 */\n maxRows?: number;\n /** 不随内容增高,固定为 rows 行(可以拖动右下角改变高度) */\n fixed?: boolean;\n /** 不可用 */\n disabled?: boolean;\n /** 只读 */\n readonly?: boolean;\n /** 必填 */\n required?: boolean;\n /** 校验未通过 */\n invalid?: boolean;\n /** 显示字数计数(右下角);上限取 maxlength(含 zod 推出的约束),超出时用危险色 */\n showCount?: boolean;\n /** 有内容时在右上角显示清空按钮 */\n clearable?: boolean;\n /** 清空按钮的部件属性 */\n propsClear?: Partial<PresetIconButtonProps>;\n /** 外壳的部件属性 */\n propsRoot?: PartProps<'div'>;\n /** 字数计数的部件属性 */\n propsCount?: Partial<CharacterCountProps>;\n}\n\n/** Textarea 的样式:textarea、fixed、count、clear */\nexport const textareaStyles = css.create({\n // 随外壳拉伸:外壳给了高度(propsRoot 的 height)时文字区占满,不停在基线上\n textarea: { alignSelf: 'stretch', resize: 'none', overflowY: 'auto' },\n fixed: { resize: 'vertical' },\n // 计数贴在右下角\n count: { alignSelf: 'flex-end', paddingBlockEnd: '_xs' },\n // 清空按钮贴在第一行(多行时不随内容居中)\n clear: { alignSelf: 'flex-start', marginBlockStart: '_xs' },\n});\n\n// 行数是任意值,用动态样式;单独一个 create,见 COMPONENTS.md §4\n/** Textarea 的样式:min、max */\nexport const textareaRowStyles = css.create({\n min: (rows: number) => ({ minHeight: `${rows}lh` }),\n max: (rows: number) => ({ maxHeight: `${rows}lh` }),\n});\n\n/** 多行文本框:高度随内容增长 */\nexport const Textarea = _component(\n ({\n _value: value = _bindable(''),\n rows = 3,\n maxRows,\n fixed = false,\n variant,\n size,\n radius,\n disabled,\n readonly,\n required,\n invalid,\n showCount = false,\n clearable = false,\n id,\n propsRoot,\n propsCount,\n propsClear,\n ...rest\n }: TextareaProps) => {\n const countId = useId();\n const appearance = useControlAppearance(() => ({ variant, size, radius }));\n const control = useFieldControl(() => ({ id, invalid, required, disabled, readonly }));\n let textarea = _state.raw<HTMLTextAreaElement | null>(null);\n\n // 内容变化(含父级改值)后按内容高度重设\n _effect(() => {\n if (fixed || !textarea) return;\n void value;\n const element = textarea;\n void nextTick(() => {\n element.style.height = 'auto';\n element.style.height = `${element.scrollHeight}px`;\n });\n });\n\n return () => {\n const state = control();\n const s = controlParts(appearance(), state, true);\n const limit = rest.maxlength ?? state.constraints.maxLength;\n const describedBy = [state.describedBy, showCount && countId].filter(Boolean).join(' ');\n return (\n <div\n {...mergeAttrs<'div'>(s.root, propsRoot)}\n data-invalid={state.invalid || undefined}\n data-disabled={state.disabled || undefined}\n >\n <textarea\n {...mergeAttrs<'textarea'>(\n {\n ...s.input,\n sx: [\n s.input.sx,\n textareaStyles.textarea,\n fixed && textareaStyles.fixed,\n textareaRowStyles.min(rows),\n maxRows != null && !fixed && textareaRowStyles.max(maxRows),\n ],\n minlength: state.constraints.minLength,\n maxlength: state.constraints.maxLength,\n },\n rest,\n {\n ref: _toRef(textarea),\n id: state.id,\n rows,\n disabled: state.disabled,\n readonly: state.readonly,\n required: state.required,\n 'aria-invalid': state.invalid || undefined,\n 'aria-describedby': describedBy || undefined,\n },\n )}\n _value={value}\n />\n {clearable &&\n value &&\n !state.disabled &&\n !state.readonly &&\n // 使用方的 sx 接在贴顶的样式之后,不把它整个换掉\n controlClear(s, { ...propsClear, sx: [textareaStyles.clear, propsClear?.sx] }, () => {\n value = '';\n textarea?.focus();\n })}\n {showCount && (\n <CharacterCount\n {...mergePartProps<CharacterCountProps>(\n {\n sx: textareaStyles.count,\n count: value.length,\n max: limit == null ? undefined : Number(limit),\n },\n propsCount,\n { id: countId },\n )}\n />\n )}\n </div>\n );\n };\n },\n);\n"],"mappings":";;;;;;;;;;;AAsDA,IAAa,iBAAiB,IAAI,OAAO;CAEvC,UAAU;EAAE,WAAW;EAAW,QAAQ;EAAQ,WAAW;CAAO;CACpE,OAAO,EAAE,QAAQ,WAAW;CAE5B,OAAO;EAAE,WAAW;EAAY,iBAAiB;CAAM;CAEvD,OAAO;EAAE,WAAW;EAAc,kBAAkB;CAAM;AAC5D,CAAC;;AAID,IAAa,oBAAoB,IAAI,OAAO;CAC1C,MAAM,UAAkB,EAAE,WAAW,GAAG,KAAI,IAAK;CACjD,MAAM,UAAkB,EAAE,WAAW,GAAG,KAAI,IAAK;AACnD,CAAC;;AAGD,IAAa,WAAW,0BACtB,QAAA,SAmBqB;CAAA,MAAA,QAAAA,SAAA,QAAA,gBAlBO,IAAE,KAAA,IAAA;CAAA,MAAA,OAAAC,aAAA,QAAA,QACrB,CAAC;CAAA,MAAA,UAAAA,aAAA,QAAA,SAAA;CAAA,MAAA,QAAAA,aAAA,QAAA,SAEA,KAAK;CAAA,MAAA,UAAAA,aAAA,QAAA,SAAA;CAAA,MAAA,OAAAA,aAAA,QAAA,MAAA;CAAA,MAAA,SAAAA,aAAA,QAAA,QAAA;CAAA,MAAA,WAAAA,aAAA,QAAA,UAAA;CAAA,MAAA,WAAAA,aAAA,QAAA,UAAA;CAAA,MAAA,WAAAA,aAAA,QAAA,UAAA;CAAA,MAAA,UAAAA,aAAA,QAAA,SAAA;CAAA,MAAA,YAAAA,aAAA,QAAA,aAQD,KAAK;CAAA,MAAA,YAAAA,aAAA,QAAA,aACL,KAAK;CAAA,MAAA,KAAAA,aAAA,QAAA,IAAA;CAAA,MAAA,YAAAA,aAAA,QAAA,WAAA;CAAA,MAAA,aAAAA,aAAA,QAAA,YAAA;CAAA,MAAA,aAAAA,aAAA,QAAA,YAAA;CAAA,MAKd,OAAIC,UAAA,QAAA,MAAA,IAAA;CAEP,MAAM,UAAU,MAAM;CACtB,MAAM,aAAa,4BAA4B;EAAE,SAAO,QAAA;EAAE,MAAI,KAAA;EAAE,QAAM,OAAA;CAAC,EAAE;CACzE,MAAM,UAAU,uBAAuB;EAAE,IAAE,GAAA;EAAE,SAAO,QAAA;EAAE,UAAQ,SAAA;EAAE,UAAQ,SAAA;EAAE,UAAQ,SAAA;CAAC,EAAE;CACrF,IAAI,WAAQC,WAA0C,IAAI;CAG1D,aAAc;EACZ,IAAI,MAAA,SAAS,CAAA,SAAA,OAAW;EACxB,MAAA;EACA,MAAM,UAAO,SAAA;EACb,eAAoB;GAClB,QAAQ,MAAM,SAAS;GACvB,QAAQ,MAAM,SAAS,GAAG,QAAQ,aAAY;EAChD,CAAC;CACH,CAAC;CAED,aAAa;EACX,MAAM,QAAQ,QAAQ;EACtB,MAAM,IAAI,aAAa,WAAW,GAAG,OAAO,IAAI;EAChD,MAAM,QAAQ,KAAK,aAAa,MAAM,YAAY;EAClD,MAAM,cAAc,CAAC,MAAM,aAAa,UAAA,SAAa,OAAO,CAAC,CAAC,OAAO,OAAO,CAAC,CAAC,KAAK,GAAG;EACtF,OAAAC,YAAA,OAAAC,
|
|
1
|
+
{"version":3,"file":"Textarea.js","names":["_vtp_bindable","_vtp_readonlyProp","_vtp_restProps","_vue_shallowRef","_createVNode","_mergeProps","_withDirectives","_vModelText"],"sources":["../../src/input/Textarea.tsx"],"sourcesContent":["/**\n * 多行文本框:外壳与 TextField 相同(control),高度随内容增长,在 rows 与 maxRows 之间。\n *\n * <Field label=\"备注\"><Textarea _value={note} rows={3} maxRows={8} /></Field>\n *\n * 自动增高:内容变化后(含父级改值)把高度设为 scrollHeight,上下限用 lh 单位(行高的倍数)由 CSS 夹住。\n * 没用 field-sizing: content:Firefox 还不支持,样式的类型表里也没有。中文输入法照常(v-model)。\n *\n * 未声明的属性落到 textarea(主元素)上,外壳用 propsRoot。部件:Root(外壳)、Textarea。\n */\nimport { _component } from '@zerodep-dev/vue-tsx-pro-tsx';\nimport * as css from '@zerodep-dev/vue-tsx-pro-tsx/styles';\nimport { nextTick, useId } from 'vue';\nimport { CharacterCount, type CharacterCountProps } from './CharacterCount';\nimport { useFieldControl } from '../form/Field';\nimport { mergeAttrs, mergePartProps, type PartProps } from '../utils/props';\nimport type { PresetIconButtonProps } from '../base/CloseButton';\nimport { controlClear, controlParts, useControlAppearance, type ControlOptions } from './control';\n\n/** 值与状态由组件管理 */\nexport type TextareaManaged =\n 'value' | 'rows' | 'disabled' | 'readonly' | 'required' | 'aria-invalid' | 'aria-describedby';\n\n/** 未声明的属性落到 textarea(主元素)上;外壳用 propsRoot */\nexport interface TextareaProps extends PartProps<'textarea', TextareaManaged>, ControlOptions {\n /** 输入值:父级写 _value={x} 双向绑定 */\n _value?: string;\n /** 最少显示几行,默认 3 */\n rows?: number;\n /** 最多增高到几行,之后出现滚动条;默认不限 */\n maxRows?: number;\n /** 不随内容增高,固定为 rows 行(可以拖动右下角改变高度) */\n fixed?: boolean;\n /** 不可用 */\n disabled?: boolean;\n /** 只读 */\n readonly?: boolean;\n /** 必填 */\n required?: boolean;\n /** 校验未通过 */\n invalid?: boolean;\n /** 显示字数计数(右下角);上限取 maxlength(含 zod 推出的约束),超出时用危险色 */\n showCount?: boolean;\n /** 有内容时在右上角显示清空按钮 */\n clearable?: boolean;\n /** 清空按钮的部件属性 */\n propsClear?: Partial<PresetIconButtonProps>;\n /** 外壳的部件属性 */\n propsRoot?: PartProps<'div'>;\n /** 字数计数的部件属性 */\n propsCount?: Partial<CharacterCountProps>;\n}\n\n/** Textarea 的样式:textarea、fixed、count、clear */\nexport const textareaStyles = css.create({\n // 随外壳拉伸:外壳给了高度(propsRoot 的 height)时文字区占满,不停在基线上\n textarea: { alignSelf: 'stretch', resize: 'none', overflowY: 'auto' },\n fixed: { resize: 'vertical' },\n // 计数贴在右下角\n count: { alignSelf: 'flex-end', paddingBlockEnd: '_xs' },\n // 清空按钮贴在第一行(多行时不随内容居中)\n clear: { alignSelf: 'flex-start', marginBlockStart: '_xs' },\n});\n\n// 行数是任意值,用动态样式;单独一个 create,见 COMPONENTS.md §4\n/** Textarea 的样式:min、max */\nexport const textareaRowStyles = css.create({\n min: (rows: number) => ({ minHeight: `${rows}lh` }),\n max: (rows: number) => ({ maxHeight: `${rows}lh` }),\n});\n\n/** 多行文本框:高度随内容增长 */\nexport const Textarea = _component(\n ({\n _value: value = _bindable(''),\n rows = 3,\n maxRows,\n fixed = false,\n variant,\n size,\n radius,\n disabled,\n readonly,\n required,\n invalid,\n showCount = false,\n clearable = false,\n id,\n propsRoot,\n propsCount,\n propsClear,\n ...rest\n }: TextareaProps) => {\n const countId = useId();\n const appearance = useControlAppearance(() => ({ variant, size, radius }));\n const control = useFieldControl(() => ({ id, invalid, required, disabled, readonly }));\n let textarea = _state.raw<HTMLTextAreaElement | null>(null);\n\n // 内容变化(含父级改值)后按内容高度重设\n _effect(() => {\n if (fixed || !textarea) return;\n void value;\n const element = textarea;\n void nextTick(() => {\n element.style.height = 'auto';\n element.style.height = `${element.scrollHeight}px`;\n });\n });\n\n return () => {\n const state = control();\n const s = controlParts(appearance(), state, true);\n const limit = rest.maxlength ?? state.constraints.maxLength;\n const describedBy = [state.describedBy, showCount && countId].filter(Boolean).join(' ');\n return (\n <div\n {...mergeAttrs<'div'>(s.root, propsRoot)}\n data-invalid={state.invalid || undefined}\n data-disabled={state.disabled || undefined}\n >\n <textarea\n {...mergeAttrs<'textarea'>(\n {\n ...s.input,\n sx: [\n s.input.sx,\n textareaStyles.textarea,\n fixed && textareaStyles.fixed,\n textareaRowStyles.min(rows),\n maxRows != null && !fixed && textareaRowStyles.max(maxRows),\n ],\n minlength: state.constraints.minLength,\n maxlength: state.constraints.maxLength,\n },\n rest,\n {\n ref: _toRef(textarea),\n id: state.id,\n rows,\n disabled: state.disabled,\n readonly: state.readonly,\n required: state.required,\n 'aria-invalid': state.invalid || undefined,\n 'aria-describedby': describedBy || undefined,\n },\n )}\n _value={value}\n />\n {clearable &&\n value &&\n !state.disabled &&\n !state.readonly &&\n // 使用方的 sx 接在贴顶的样式之后,不把它整个换掉\n controlClear(s, { ...propsClear, sx: [textareaStyles.clear, propsClear?.sx] }, () => {\n value = '';\n textarea?.focus();\n })}\n {showCount && (\n <CharacterCount\n {...mergePartProps<CharacterCountProps>(\n {\n sx: textareaStyles.count,\n count: value.length,\n max: limit == null ? undefined : Number(limit),\n },\n propsCount,\n { id: countId },\n )}\n />\n )}\n </div>\n );\n };\n },\n);\n"],"mappings":"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"}
|
|
@@ -5,8 +5,8 @@ import { useFieldControl } from "../form/Field.js";
|
|
|
5
5
|
import { rovingFocus } from "../utils/dom/roving-focus.js";
|
|
6
6
|
import { useActiveIndicator } from "../utils/motion/active-indicator.js";
|
|
7
7
|
import { ToolbarContext } from "../navigation/Toolbar.js";
|
|
8
|
-
import { computed, createVNode,
|
|
9
|
-
import { _component, bindable, childrenProp, createContext, effect, readonlyProp, restProps } from "@zerodep-dev/vue-tsx-pro-tsx";
|
|
8
|
+
import { computed, createVNode, shallowRef } from "vue";
|
|
9
|
+
import { _component, bindable, childrenProp, createContext, effect, 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/input/ToggleGroup.tsx
|
|
12
12
|
/** ToggleGroup 交给组内 ToggleGroupItem 的状态 */
|
|
@@ -114,7 +114,7 @@ var ToggleGroup = /*#__PURE__*/ _component((_props, _ctx) => {
|
|
|
114
114
|
});
|
|
115
115
|
return () => {
|
|
116
116
|
const field = control();
|
|
117
|
-
return createVNode("div",
|
|
117
|
+
return createVNode("div", mergeJsxProps(mergeAttrs({ sx: [toggleGroupStyles.root, orientation.value === "vertical" && toggleGroupStyles.vertical] }, rest, {
|
|
118
118
|
ref: root,
|
|
119
119
|
id: field.id,
|
|
120
120
|
role: "group",
|
|
@@ -173,7 +173,7 @@ var ToggleItem = /*#__PURE__*/ _component((_props2, _ctx2) => {
|
|
|
173
173
|
const pressed = group.selected.includes(value.value);
|
|
174
174
|
const off = disabled.value || group.disabled;
|
|
175
175
|
const base = appearance();
|
|
176
|
-
return createVNode("button",
|
|
176
|
+
return createVNode("button", mergeJsxProps(mergeAttrs({
|
|
177
177
|
...buttonPart,
|
|
178
178
|
sx: [buttonSx({
|
|
179
179
|
...base,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ToggleGroup.js","names":["_vtp_readonlyProp","_vtp_bindable","_vtp_childrenProp","_vtp_restProps","_vue_shallowRef","_vue_computed","_createVNode","_mergeProps"],"sources":["../../src/input/ToggleGroup.tsx"],"sourcesContent":["/**\n * 切换按钮组,复合组件:ToggleGroup(role=\"group\")> ToggleItem(带 aria-pressed 的 button)。\n * 单选像分段控件(视图切换、对齐方式),多选像格式按钮(加粗、斜体)。整组一个 Tab 位置,组内方向键移动(漫游焦点)。\n * 参考 Radix ToggleGroup、Ark UI SegmentGroup。\n *\n * <ToggleGroup _value={view} aria-label=\"视图\" required>\n * <ToggleItem value=\"list\" icon={List} aria-label=\"列表\" />\n * <ToggleItem value=\"grid\" icon={LayoutGrid} aria-label=\"网格\" />\n * </ToggleGroup>\n * <ToggleGroup selectionMode=\"multiple\" _value={marks} aria-label=\"格式\">…</ToggleGroup>\n */\nimport { createContext, _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 {\n buttonContent,\n buttonPart,\n buttonSx,\n useButtonAppearance,\n useButtonBehavior,\n type ButtonOptions,\n} from '../base/button-core';\nimport type { IconData, IconProps } from '../base/Icon';\nimport { useActiveIndicator } from '../utils/motion/active-indicator';\nimport { defaultLabelledBy, mergeAttrs, type PartProps } from '../utils/props';\nimport { rovingFocus } from '../utils/dom/roving-focus';\nimport { ToolbarContext } from '../navigation/Toolbar';\nimport { selectedValues, toggleSelection, type SelectionProps } from '../utils/data/selection';\nimport { useFieldControl } from '../form/Field';\nimport type { TextSize } from '../utils/style/typography';\n\n/** ToggleGroup 交给组内 ToggleGroupItem 的状态 */\nexport interface ToggleGroupState {\n /** 按下(选中)的项的值 */\n readonly selected: readonly string[];\n /** 字号档位;不写时继承 */\n readonly size: TextSize | undefined;\n /** 整组不可用 */\n readonly disabled: boolean;\n /** 整组只读:按钮照常显示与聚焦,点了不切换 */\n readonly readonly: boolean;\n /** 波纹;不写时沿用配置 */\n readonly ripple: boolean | undefined;\n /** 单选:选中底色由组里的滑块(Indicator)画,项本身不画底色 */\n readonly sliding: boolean;\n /** 切换一项(单选、多选的规则由组决定) */\n toggle(value: string): void;\n}\n\n/** ToggleGroup 的上下文:自定义的项可以 inject 它接入组 */\nexport const ToggleGroupContext = createContext<ToggleGroupState>('ToggleGroup');\n\n/** 方向 */\nexport interface ToggleGroupOrientations {\n /** 横排(默认),左右键移动 */\n horizontal: unknown;\n /** 竖排,上下键移动 */\n vertical: unknown;\n}\n\n/** 角色由组件管理(role=\"group\" 不允许 aria-orientation,方向只写在 data-orientation 上) */\nexport type ToggleGroupManaged = 'role';\n\n/** 两种选择方式共有的选项;未声明的属性落到 role=\"group\" 的 div 上,aria-label 是这组的名称 */\nexport interface ToggleGroupBaseProps extends PartProps<'div', ToggleGroupManaged> {\n /** 必须有选中项:最后一项不能取消(分段控件通常要求);在必填的 Field 里默认开启 */\n required?: boolean;\n /** 整组不可用 */\n disabled?: boolean;\n /** 整组只读:选中项照常显示、按钮能聚焦,点了不切换(查看详情、审批时) */\n readonly?: boolean;\n /** 方向,默认 horizontal */\n orientation?: keyof ToggleGroupOrientations;\n /** 字号档位,按钮随之缩放 */\n size?: TextSize;\n /** 按下时的波纹;不写时沿用全局配置 */\n ripple?: boolean;\n /** 单选时选中项下面的滑块(切换时滑过去)的部件属性 */\n propsIndicator?: PartProps<'span'>;\n children?: VNodeChild;\n}\n\n/** 单选或多选 */\nexport type ToggleGroupProps = ToggleGroupBaseProps & SelectionProps;\n\n/** ToggleGroup 的样式:root、vertical、indicator */\nexport const toggleGroupStyles = css.create({\n root: {\n // 滑块按项的 offsetLeft / offsetTop 定位;isolation 让 zIndex: -1 的滑块压在项下、组的底色之上\n position: 'relative',\n isolation: 'isolate',\n display: 'inline-flex',\n // 放在竖排的弹性容器里(Field 的控件列)时不被拉满整行\n width: 'fit-content',\n gap: '_xs',\n padding: '_xs',\n borderRadius: '_md',\n backgroundColor: '_fill',\n },\n vertical: { flexDirection: 'column' },\n // 只读、没选中的项:不像能点(悬停也不出底色)\n readonlyItem: { cursor: 'default', backgroundColor: 'transparent' },\n // 同按钮的 surface 外观;位置与宽高由脚本写(useActiveIndicator)\n indicator: {\n position: 'absolute',\n top: 0,\n left: 0,\n zIndex: -1,\n boxSizing: 'border-box',\n borderWidth: '_xs',\n borderStyle: 'solid',\n borderColor: '_accentBorder',\n borderRadius: '_sm',\n backgroundColor: '_accentSoft',\n pointerEvents: 'none',\n },\n});\n\n/** 切换按钮组:单选或多选,整组一个 Tab 位置 */\nexport const ToggleGroup = _component(\n ({\n selectionMode = 'single',\n _value: value = _bindable<string | string[] | null>(null),\n required,\n disabled,\n readonly,\n orientation = 'horizontal',\n size,\n ripple,\n propsIndicator,\n children,\n id,\n ...rest\n }: ToggleGroupProps) => {\n // 在 Field 里时:标签作为组的名称、关联说明与错误、沿用不可用与只读\n const control = useFieldControl(() => ({ id, disabled, readonly, required }));\n let root = _state.raw<HTMLElement | null>(null);\n // 在工具栏里时焦点交给工具栏:整栏一个 Tab 位置,方向键跨过组内外的按钮(WAI-ARIA Toolbar 模式)\n const inToolbar = ToolbarContext.inject(null) != null;\n _effect(() =>\n root && !inToolbar\n ? rovingFocus(root, { items: 'button', orientation, loop: true })\n : undefined,\n );\n const selected = _derived(selectedValues(value));\n let indicator = _state.raw<HTMLElement | null>(null);\n useActiveIndicator(() => ({\n container: root,\n indicator,\n active: '[aria-pressed=\"true\"]',\n axis: 'both',\n value: selected[0],\n }));\n\n ToggleGroupContext.provide({\n get selected() {\n return selected;\n },\n get size() {\n return size;\n },\n get disabled() {\n return control().disabled;\n },\n get readonly() {\n return control().readonly;\n },\n get ripple() {\n return ripple;\n },\n get sliding() {\n return selectionMode === 'single';\n },\n toggle(item) {\n if (control().readonly) return;\n const next = toggleSelection(selected, item, {\n mode: selectionMode,\n required: control().required,\n });\n if (next === selected) return;\n value = selectionMode === 'multiple' ? [...next] : (next[0] ?? null);\n },\n });\n\n return () => {\n const field = control();\n return (\n <div\n {...mergeAttrs<'div'>(\n {\n sx: [\n toggleGroupStyles.root,\n orientation === 'vertical' && toggleGroupStyles.vertical,\n ],\n },\n rest,\n {\n ref: _toRef(root),\n id: field.id,\n role: 'group',\n 'aria-labelledby': defaultLabelledBy(rest, field.labelledBy),\n 'aria-describedby': field.describedBy,\n },\n )}\n data-orientation={orientation}\n data-disabled={field.disabled || undefined}\n data-readonly={field.readonly || undefined}\n >\n {selectionMode === 'single' && selected.length > 0 && (\n <span\n {...mergeAttrs<'span'>({ sx: toggleGroupStyles.indicator }, propsIndicator, {\n ref: _toRef(indicator),\n 'aria-hidden': true,\n })}\n />\n )}\n {children}\n </div>\n );\n };\n },\n);\n\n/** 选中、不可用由组件管理 */\nexport type ToggleGroupItemManaged = 'aria-pressed' | 'disabled' | 'type';\n\n/** 未声明的属性落到 button 上;只有图标时写 aria-label */\nexport interface ToggleItemProps\n extends PartProps<'button', ToggleGroupItemManaged>, Pick<ButtonOptions, 'radius'> {\n /** 这一项的值 */\n value: string;\n /** 图标 */\n icon?: IconData;\n /** 不可用 */\n disabled?: boolean;\n /** 图标的部件属性 */\n propsIcon?: Partial<IconProps>;\n /** 内容层的部件属性 */\n propsContent?: PartProps<'span'>;\n children?: VNodeChild;\n}\n\n/** 切换按钮组里的一项(button,aria-pressed 表示是否选中) */\nexport const ToggleItem = _component(\n ({\n value,\n icon,\n disabled = false,\n radius,\n propsIcon,\n propsContent,\n children,\n ...rest\n }: ToggleItemProps) => {\n const group = ToggleGroupContext.inject();\n // 与 Button 同一套行为:不可用、拦截点击、波纹、状态 data 属性;只读时不出波纹\n const behavior = useButtonBehavior(\n () => ({\n disabled: disabled || group.disabled,\n loading: false,\n ripple: group.readonly ? false : group.ripple,\n }),\n () => () => group.toggle(value),\n );\n // 选中的是浅色底,其余是幽灵按钮;尺寸跟随组\n const appearance = useButtonAppearance(\n () => ({ variant: undefined, tone: 'neutral', size: group.size, radius: radius ?? 'sm' }),\n 'ghost',\n );\n\n return () => {\n const pressed = group.selected.includes(value);\n const off = disabled || group.disabled;\n const base = appearance();\n return (\n <button\n {...mergeAttrs<'button'>(\n {\n ...buttonPart,\n sx: [\n buttonSx(\n {\n ...base,\n // 单选时选中底色由组的滑块画\n variant: pressed && !group.sliding ? 'surface' : 'ghost',\n tone: pressed ? 'accent' : 'neutral',\n },\n { disabled: off, square: children == null },\n ),\n group.readonly && !pressed && toggleGroupStyles.readonlyItem,\n ],\n },\n rest,\n {\n type: 'button',\n 'aria-pressed': pressed,\n // 使用方的 onClick 先执行,preventDefault 可以拦下切换(mergeProps 的规则)\n ...behavior(),\n },\n )}\n data-state={pressed ? 'on' : 'off'}\n >\n {buttonContent({ icon, propsIcon, propsContent, children }, { loading: false })}\n </button>\n );\n };\n },\n);\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ToggleGroup.js","names":["_vtp_readonlyProp","_vtp_bindable","_vtp_childrenProp","_vtp_restProps","_vue_shallowRef","_vue_computed","_createVNode","_mergeProps"],"sources":["../../src/input/ToggleGroup.tsx"],"sourcesContent":["/**\n * 切换按钮组,复合组件:ToggleGroup(role=\"group\")> ToggleItem(带 aria-pressed 的 button)。\n * 单选像分段控件(视图切换、对齐方式),多选像格式按钮(加粗、斜体)。整组一个 Tab 位置,组内方向键移动(漫游焦点)。\n * 参考 Radix ToggleGroup、Ark UI SegmentGroup。\n *\n * <ToggleGroup _value={view} aria-label=\"视图\" required>\n * <ToggleItem value=\"list\" icon={List} aria-label=\"列表\" />\n * <ToggleItem value=\"grid\" icon={LayoutGrid} aria-label=\"网格\" />\n * </ToggleGroup>\n * <ToggleGroup selectionMode=\"multiple\" _value={marks} aria-label=\"格式\">…</ToggleGroup>\n */\nimport { createContext, _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 {\n buttonContent,\n buttonPart,\n buttonSx,\n useButtonAppearance,\n useButtonBehavior,\n type ButtonOptions,\n} from '../base/button-core';\nimport type { IconData, IconProps } from '../base/Icon';\nimport { useActiveIndicator } from '../utils/motion/active-indicator';\nimport { defaultLabelledBy, mergeAttrs, type PartProps } from '../utils/props';\nimport { rovingFocus } from '../utils/dom/roving-focus';\nimport { ToolbarContext } from '../navigation/Toolbar';\nimport { selectedValues, toggleSelection, type SelectionProps } from '../utils/data/selection';\nimport { useFieldControl } from '../form/Field';\nimport type { TextSize } from '../utils/style/typography';\n\n/** ToggleGroup 交给组内 ToggleGroupItem 的状态 */\nexport interface ToggleGroupState {\n /** 按下(选中)的项的值 */\n readonly selected: readonly string[];\n /** 字号档位;不写时继承 */\n readonly size: TextSize | undefined;\n /** 整组不可用 */\n readonly disabled: boolean;\n /** 整组只读:按钮照常显示与聚焦,点了不切换 */\n readonly readonly: boolean;\n /** 波纹;不写时沿用配置 */\n readonly ripple: boolean | undefined;\n /** 单选:选中底色由组里的滑块(Indicator)画,项本身不画底色 */\n readonly sliding: boolean;\n /** 切换一项(单选、多选的规则由组决定) */\n toggle(value: string): void;\n}\n\n/** ToggleGroup 的上下文:自定义的项可以 inject 它接入组 */\nexport const ToggleGroupContext = createContext<ToggleGroupState>('ToggleGroup');\n\n/** 方向 */\nexport interface ToggleGroupOrientations {\n /** 横排(默认),左右键移动 */\n horizontal: unknown;\n /** 竖排,上下键移动 */\n vertical: unknown;\n}\n\n/** 角色由组件管理(role=\"group\" 不允许 aria-orientation,方向只写在 data-orientation 上) */\nexport type ToggleGroupManaged = 'role';\n\n/** 两种选择方式共有的选项;未声明的属性落到 role=\"group\" 的 div 上,aria-label 是这组的名称 */\nexport interface ToggleGroupBaseProps extends PartProps<'div', ToggleGroupManaged> {\n /** 必须有选中项:最后一项不能取消(分段控件通常要求);在必填的 Field 里默认开启 */\n required?: boolean;\n /** 整组不可用 */\n disabled?: boolean;\n /** 整组只读:选中项照常显示、按钮能聚焦,点了不切换(查看详情、审批时) */\n readonly?: boolean;\n /** 方向,默认 horizontal */\n orientation?: keyof ToggleGroupOrientations;\n /** 字号档位,按钮随之缩放 */\n size?: TextSize;\n /** 按下时的波纹;不写时沿用全局配置 */\n ripple?: boolean;\n /** 单选时选中项下面的滑块(切换时滑过去)的部件属性 */\n propsIndicator?: PartProps<'span'>;\n children?: VNodeChild;\n}\n\n/** 单选或多选 */\nexport type ToggleGroupProps = ToggleGroupBaseProps & SelectionProps;\n\n/** ToggleGroup 的样式:root、vertical、indicator */\nexport const toggleGroupStyles = css.create({\n root: {\n // 滑块按项的 offsetLeft / offsetTop 定位;isolation 让 zIndex: -1 的滑块压在项下、组的底色之上\n position: 'relative',\n isolation: 'isolate',\n display: 'inline-flex',\n // 放在竖排的弹性容器里(Field 的控件列)时不被拉满整行\n width: 'fit-content',\n gap: '_xs',\n padding: '_xs',\n borderRadius: '_md',\n backgroundColor: '_fill',\n },\n vertical: { flexDirection: 'column' },\n // 只读、没选中的项:不像能点(悬停也不出底色)\n readonlyItem: { cursor: 'default', backgroundColor: 'transparent' },\n // 同按钮的 surface 外观;位置与宽高由脚本写(useActiveIndicator)\n indicator: {\n position: 'absolute',\n top: 0,\n left: 0,\n zIndex: -1,\n boxSizing: 'border-box',\n borderWidth: '_xs',\n borderStyle: 'solid',\n borderColor: '_accentBorder',\n borderRadius: '_sm',\n backgroundColor: '_accentSoft',\n pointerEvents: 'none',\n },\n});\n\n/** 切换按钮组:单选或多选,整组一个 Tab 位置 */\nexport const ToggleGroup = _component(\n ({\n selectionMode = 'single',\n _value: value = _bindable<string | string[] | null>(null),\n required,\n disabled,\n readonly,\n orientation = 'horizontal',\n size,\n ripple,\n propsIndicator,\n children,\n id,\n ...rest\n }: ToggleGroupProps) => {\n // 在 Field 里时:标签作为组的名称、关联说明与错误、沿用不可用与只读\n const control = useFieldControl(() => ({ id, disabled, readonly, required }));\n let root = _state.raw<HTMLElement | null>(null);\n // 在工具栏里时焦点交给工具栏:整栏一个 Tab 位置,方向键跨过组内外的按钮(WAI-ARIA Toolbar 模式)\n const inToolbar = ToolbarContext.inject(null) != null;\n _effect(() =>\n root && !inToolbar\n ? rovingFocus(root, { items: 'button', orientation, loop: true })\n : undefined,\n );\n const selected = _derived(selectedValues(value));\n let indicator = _state.raw<HTMLElement | null>(null);\n useActiveIndicator(() => ({\n container: root,\n indicator,\n active: '[aria-pressed=\"true\"]',\n axis: 'both',\n value: selected[0],\n }));\n\n ToggleGroupContext.provide({\n get selected() {\n return selected;\n },\n get size() {\n return size;\n },\n get disabled() {\n return control().disabled;\n },\n get readonly() {\n return control().readonly;\n },\n get ripple() {\n return ripple;\n },\n get sliding() {\n return selectionMode === 'single';\n },\n toggle(item) {\n if (control().readonly) return;\n const next = toggleSelection(selected, item, {\n mode: selectionMode,\n required: control().required,\n });\n if (next === selected) return;\n value = selectionMode === 'multiple' ? [...next] : (next[0] ?? null);\n },\n });\n\n return () => {\n const field = control();\n return (\n <div\n {...mergeAttrs<'div'>(\n {\n sx: [\n toggleGroupStyles.root,\n orientation === 'vertical' && toggleGroupStyles.vertical,\n ],\n },\n rest,\n {\n ref: _toRef(root),\n id: field.id,\n role: 'group',\n 'aria-labelledby': defaultLabelledBy(rest, field.labelledBy),\n 'aria-describedby': field.describedBy,\n },\n )}\n data-orientation={orientation}\n data-disabled={field.disabled || undefined}\n data-readonly={field.readonly || undefined}\n >\n {selectionMode === 'single' && selected.length > 0 && (\n <span\n {...mergeAttrs<'span'>({ sx: toggleGroupStyles.indicator }, propsIndicator, {\n ref: _toRef(indicator),\n 'aria-hidden': true,\n })}\n />\n )}\n {children}\n </div>\n );\n };\n },\n);\n\n/** 选中、不可用由组件管理 */\nexport type ToggleGroupItemManaged = 'aria-pressed' | 'disabled' | 'type';\n\n/** 未声明的属性落到 button 上;只有图标时写 aria-label */\nexport interface ToggleItemProps\n extends PartProps<'button', ToggleGroupItemManaged>, Pick<ButtonOptions, 'radius'> {\n /** 这一项的值 */\n value: string;\n /** 图标 */\n icon?: IconData;\n /** 不可用 */\n disabled?: boolean;\n /** 图标的部件属性 */\n propsIcon?: Partial<IconProps>;\n /** 内容层的部件属性 */\n propsContent?: PartProps<'span'>;\n children?: VNodeChild;\n}\n\n/** 切换按钮组里的一项(button,aria-pressed 表示是否选中) */\nexport const ToggleItem = _component(\n ({\n value,\n icon,\n disabled = false,\n radius,\n propsIcon,\n propsContent,\n children,\n ...rest\n }: ToggleItemProps) => {\n const group = ToggleGroupContext.inject();\n // 与 Button 同一套行为:不可用、拦截点击、波纹、状态 data 属性;只读时不出波纹\n const behavior = useButtonBehavior(\n () => ({\n disabled: disabled || group.disabled,\n loading: false,\n ripple: group.readonly ? false : group.ripple,\n }),\n () => () => group.toggle(value),\n );\n // 选中的是浅色底,其余是幽灵按钮;尺寸跟随组\n const appearance = useButtonAppearance(\n () => ({ variant: undefined, tone: 'neutral', size: group.size, radius: radius ?? 'sm' }),\n 'ghost',\n );\n\n return () => {\n const pressed = group.selected.includes(value);\n const off = disabled || group.disabled;\n const base = appearance();\n return (\n <button\n {...mergeAttrs<'button'>(\n {\n ...buttonPart,\n sx: [\n buttonSx(\n {\n ...base,\n // 单选时选中底色由组的滑块画\n variant: pressed && !group.sliding ? 'surface' : 'ghost',\n tone: pressed ? 'accent' : 'neutral',\n },\n { disabled: off, square: children == null },\n ),\n group.readonly && !pressed && toggleGroupStyles.readonlyItem,\n ],\n },\n rest,\n {\n type: 'button',\n 'aria-pressed': pressed,\n // 使用方的 onClick 先执行,preventDefault 可以拦下切换(mergeProps 的规则)\n ...behavior(),\n },\n )}\n data-state={pressed ? 'on' : 'off'}\n >\n {buttonContent({ icon, propsIcon, propsContent, children }, { loading: false })}\n </button>\n );\n };\n },\n);\n"],"mappings":"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"}
|
package/dist/input/Transfer.js
CHANGED
|
@@ -10,8 +10,8 @@ import { checkedByStrategy, indexTree } from "../utils/data/tree.js";
|
|
|
10
10
|
import { Tree } from "../display/Tree.js";
|
|
11
11
|
import { Listbox } from "./Listbox.js";
|
|
12
12
|
import { TextField } from "./TextField.js";
|
|
13
|
-
import { computed, createVNode, isVNode,
|
|
14
|
-
import { _component, bindable, readonlyProp, restProps } from "@zerodep-dev/vue-tsx-pro-tsx";
|
|
13
|
+
import { computed, createVNode, isVNode, ref, useId } from "vue";
|
|
14
|
+
import { _component, bindable, mergeJsxProps, readonlyProp, restProps } from "@zerodep-dev/vue-tsx-pro-tsx";
|
|
15
15
|
import * as css from "@zerodep-dev/vue-tsx-pro-tsx/styles";
|
|
16
16
|
import { ChevronLeft, ChevronRight, Search } from "@lucide/icons";
|
|
17
17
|
//#region src/input/Transfer.tsx
|
|
@@ -119,10 +119,10 @@ function renderPanel(lang, options) {
|
|
|
119
119
|
"disabled": options.disabled || options.selectable.length === 0,
|
|
120
120
|
"aria-label": lang.transferSelectAll(options.title)
|
|
121
121
|
}, null),
|
|
122
|
-
createVNode("span",
|
|
122
|
+
createVNode("span", mergeJsxProps({ "id": options.titleId }, css.attrs(transferStyles.title)), [options.title]),
|
|
123
123
|
createVNode(Small, { "tone": "muted" }, _isSlot(_slot = lang.transferCount(options.checked.length, options.total)) ? _slot : { default: () => [_slot] })
|
|
124
124
|
]),
|
|
125
|
-
options.searchable && createVNode("div", css.attrs(transferStyles.search), [createVNode(TextField,
|
|
125
|
+
options.searchable && createVNode("div", css.attrs(transferStyles.search), [createVNode(TextField, mergeJsxProps(mergePartProps({
|
|
126
126
|
icon: Search,
|
|
127
127
|
variant: "soft",
|
|
128
128
|
size: "sm",
|
|
@@ -140,7 +140,7 @@ function renderPanel(lang, options) {
|
|
|
140
140
|
}
|
|
141
141
|
/** 中间的两个按钮 */
|
|
142
142
|
function renderActions(lang, titles, enabled, move, propsAction) {
|
|
143
|
-
return createVNode("div", css.attrs(transferStyles.actions), [[1, 0].map((to) => createVNode(IconButton,
|
|
143
|
+
return createVNode("div", css.attrs(transferStyles.actions), [[1, 0].map((to) => createVNode(IconButton, mergeJsxProps({ "key": to }, mergePartProps({
|
|
144
144
|
icon: to === 1 ? ChevronRight : ChevronLeft,
|
|
145
145
|
"aria-label": lang.transferMoveTo(titles[to]),
|
|
146
146
|
variant: "soft",
|
|
@@ -206,7 +206,7 @@ var Transfer = /*#__PURE__*/ _component((_props, _ctx) => {
|
|
|
206
206
|
}
|
|
207
207
|
return () => {
|
|
208
208
|
const state = field();
|
|
209
|
-
const list = (side) => createVNode(Listbox,
|
|
209
|
+
const list = (side) => createVNode(Listbox, mergeJsxProps(mergePartProps({
|
|
210
210
|
items: shownEntries(side).map((entry) => entry.item),
|
|
211
211
|
toOption: (item) => toListboxOption(item, toOption.value),
|
|
212
212
|
selectionMode: "multiple",
|
|
@@ -329,7 +329,7 @@ var TreeTransfer = /*#__PURE__*/ _component((_props2, _ctx2) => {
|
|
|
329
329
|
return () => {
|
|
330
330
|
const state = field();
|
|
331
331
|
const sourceChecked = checked.value[0];
|
|
332
|
-
const tree = createVNode(Tree,
|
|
332
|
+
const tree = createVNode(Tree, mergeJsxProps(mergePartProps({
|
|
333
333
|
items: items.value,
|
|
334
334
|
getKey: getKey.value,
|
|
335
335
|
getLabel: getLabel.value,
|
|
@@ -344,7 +344,7 @@ var TreeTransfer = /*#__PURE__*/ _component((_props2, _ctx2) => {
|
|
|
344
344
|
"_expanded": expanded.value,
|
|
345
345
|
"onUpdate:_expanded": _cache[1] || (_cache[1] = ($event) => expanded.value = $event)
|
|
346
346
|
}), null);
|
|
347
|
-
const list = createVNode(Listbox,
|
|
347
|
+
const list = createVNode(Listbox, mergeJsxProps(mergePartProps({
|
|
348
348
|
items: targetOptions().filter((option) => matchesSearch(option.value, 1)),
|
|
349
349
|
selectionMode: "multiple",
|
|
350
350
|
sx: transferStyles.list,
|