@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
|
@@ -2,8 +2,8 @@ import { useLang } from "../config/context.js";
|
|
|
2
2
|
import { defaultLabelledBy, mergeAttrs } from "../utils/props.js";
|
|
3
3
|
import { isRtl } from "../utils/dom/direction.js";
|
|
4
4
|
import { handleSegmentKeydown } from "./date-segments.js";
|
|
5
|
-
import { createVNode,
|
|
6
|
-
import { _component, effect, readonlyProp, restProps } from "@zerodep-dev/vue-tsx-pro-tsx";
|
|
5
|
+
import { createVNode, nextTick, ref, shallowRef, useId } from "vue";
|
|
6
|
+
import { _component, 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/date/DateSegmentGroup.tsx
|
|
9
9
|
/** 角色与状态由组件管理 */
|
|
@@ -98,7 +98,7 @@ var DateSegmentGroup = /*#__PURE__*/ _component((_props, _ctx) => {
|
|
|
98
98
|
if (!root.value?.contains(event.relatedTarget)) segments.value.setFocused(null);
|
|
99
99
|
}
|
|
100
100
|
return () => {
|
|
101
|
-
return createVNode("div",
|
|
101
|
+
return createVNode("div", mergeJsxProps(mergeAttrs({
|
|
102
102
|
sx: dateSegmentStyles.root,
|
|
103
103
|
"aria-labelledby": defaultLabelledBy(rest, labelledBy.value),
|
|
104
104
|
"aria-describedby": describedBy.value
|
|
@@ -111,11 +111,11 @@ var DateSegmentGroup = /*#__PURE__*/ _component((_props, _ctx) => {
|
|
|
111
111
|
"data-invalid": invalid.value || void 0,
|
|
112
112
|
"data-disabled": disabled.value || void 0
|
|
113
113
|
}), [segments.value.segments.map((segment, index) => {
|
|
114
|
-
if (segment.type === "literal") return createVNode("span",
|
|
114
|
+
if (segment.type === "literal") return createVNode("span", mergeJsxProps({ "key": `literal-${index}` }, mergeAttrs({ sx: dateSegmentStyles.literal }, propsLiteral.value), { "aria-hidden": "true" }), [segment.text]);
|
|
115
115
|
const type = segment.type;
|
|
116
116
|
const segmentId = `${id}-${type}`;
|
|
117
117
|
const own = typeof propsSegment.value === "function" ? propsSegment.value(segment) : propsSegment.value;
|
|
118
|
-
return createVNode("span",
|
|
118
|
+
return createVNode("span", mergeJsxProps({ "key": `${type}-${composed.value}` }, mergeAttrs({
|
|
119
119
|
sx: [
|
|
120
120
|
dateSegmentStyles.segment,
|
|
121
121
|
segment.placeholder && dateSegmentStyles.placeholder,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DateSegmentGroup.js","names":["_vtp_readonlyProp","_vtp_restProps","_vue_shallowRef","_vue_ref","_createVNode","_mergeProps"],"sources":["../../src/date/DateSegmentGroup.tsx"],"sourcesContent":["/**\n * 一组日期分段(年 / 月 / 日、时 : 分):每段是 role=\"spinbutton\" 的可编辑元素,分隔符对读屏隐藏。\n * 状态由调用方的 useDateSegments 给;DateField、TimeField 放在输入框外壳里用,DateRangePicker 在一个外壳里放两组。\n *\n * - 每段 contenteditable 加 inputmode=\"numeric\":手机上弹出数字键盘,输入法打的全角数字也收得到;\n * 文字由组件渲染,beforeinput 一律拦下交给 useDateSegments(输入法组合结束后重新渲染这一段);\n * - 每段在 Tab 顺序里(同 React Aria),←→ 也能换段;输入满了自动跳到下一段,焦点跟过去;\n * - 读屏名称是“字段名 + 段名”(aria-labelledby 指向外部标签与这一段),没填的段取值念“未填写”。\n *\n * 部件:Root(div role=\"group\",主元素)、Segment(每段)、Literal(分隔符)。\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 { isRtl } from '../utils/dom/direction';\nimport { defaultLabelledBy, mergeAttrs, type PartProps } from '../utils/props';\nimport {\n handleSegmentKeydown,\n type DateSegment,\n type DateSegmentsState,\n type DateSegmentType,\n} from './date-segments';\nimport { useLang } from '../config/context';\n\n/** 角色与状态由组件管理 */\nexport type DateSegmentGroupManaged = 'role';\n\n/** 每段由组件管理的属性 */\nexport type DateSegmentManaged =\n | 'role'\n | 'id'\n | 'aria-label'\n | 'aria-labelledby'\n | 'tabindex'\n | 'contenteditable'\n | 'inputmode'\n | 'aria-valuenow'\n | 'aria-valuemin'\n | 'aria-valuemax'\n | 'aria-valuetext'\n | 'aria-required'\n | 'aria-invalid'\n | 'aria-readonly'\n | 'aria-disabled';\n\n/** 未声明的属性落到这一组(role=\"group\")上 */\nexport interface DateSegmentGroupProps extends PartProps<'div', DateSegmentGroupManaged> {\n /** 分段状态(useDateSegments) */\n segments: DateSegmentsState;\n /** 不可用:段不能聚焦 */\n disabled?: boolean;\n /** 只读:能聚焦、不能改 */\n readonly?: boolean;\n /** 必填(每段 aria-required) */\n required?: boolean;\n /** 校验未通过 */\n invalid?: boolean;\n /** 外部标签的 id:每段的读屏名称是“标签 + 段名” */\n labelledBy?: string;\n /** 说明与错误的 id(aria-describedby),给每一段 */\n describedBy?: string;\n /** 每段的部件属性:固定的对象,或按这一段返回 */\n propsSegment?:\n | PartProps<'span', DateSegmentManaged>\n | ((segment: DateSegment) => PartProps<'span', DateSegmentManaged>);\n /** 分隔符的部件属性 */\n propsLiteral?: PartProps<'span', 'aria-hidden'>;\n}\n\n/** DateSegmentGroup 的样式:root、segment、placeholder、literal */\nexport const dateSegmentStyles = css.create({\n root: {\n display: 'inline-flex',\n alignItems: 'center',\n minWidth: 0,\n paddingBlock: '_xs',\n whiteSpace: 'nowrap',\n fontVariantNumeric: 'tabular-nums',\n },\n segment: {\n boxSizing: 'content-box',\n paddingInline: '0.1em',\n borderRadius: '_xs',\n // 光标与选区由组件画(整段高亮),不显示浏览器的插入符\n caretColor: 'transparent',\n outlineStyle: 'none',\n textAlign: 'end',\n backgroundColor: { default: 'transparent', ':focus': '_accent' },\n color: { default: 'inherit', ':focus': '_accentContrast' },\n cursor: 'text',\n },\n // 占位文字是真正的文字(不是原生 placeholder),要满足 4.5:1:用 textMuted\n placeholder: { color: { default: '_textMuted', ':focus': '_accentContrast' } },\n literal: { color: '_textMuted', whiteSpace: 'pre' },\n disabled: { cursor: 'not-allowed' },\n});\n\n/** 一组日期分段 */\nexport const DateSegmentGroup = _component(\n ({\n segments,\n disabled = false,\n readonly = false,\n required = false,\n invalid = false,\n labelledBy,\n describedBy,\n propsSegment,\n propsLiteral,\n ...rest\n }: DateSegmentGroupProps) => {\n const lang = useLang();\n const id = useId();\n const elements = new Map<DateSegmentType, HTMLElement>();\n let root = _state.raw<HTMLElement | null>(null);\n // 输入法组合会直接改掉段里的文字(beforeinput 拦不住):结束后换个 key 重新渲染这一段\n let composed = _state(0);\n\n // 输入满了跳段、Backspace 删空退回上一段:焦点跟着 focusedSegment 走(只在焦点本来就在这组里时)\n _effect(() => {\n const type = segments.focusedSegment;\n const target = type && elements.get(type);\n if (!target || !root?.contains(document.activeElement) || document.activeElement === target)\n return;\n target.focus();\n });\n\n const editable = () => !disabled && !readonly;\n\n function onBeforeinput(event: InputEvent) {\n // 输入法组合中的文字取消不了,等 compositionend\n if (event.inputType === 'insertCompositionText') return;\n event.preventDefault();\n if (!editable()) return;\n if (event.inputType.startsWith('delete')) segments.backspace();\n else if (event.data) segments.type(event.data);\n }\n\n function onCompositionend(event: CompositionEvent) {\n const focused = segments.focusedSegment;\n if (editable() && event.data) segments.type(event.data);\n composed += 1;\n // 重新渲染后元素换了:焦点留在原来那段(或已经跳到的下一段)\n void nextTick(() => elements.get(segments.focusedSegment ?? focused!)?.focus());\n }\n\n function onKeydown(event: KeyboardEvent) {\n // Enter 同原生的输入框:写回打了一半的年份,再提交所在的表单(查询栏、对话框里的表单)\n if (event.key === 'Enter') {\n event.preventDefault();\n segments.commit();\n root?.closest('form')?.requestSubmit();\n return;\n }\n if (!root || disabled) return;\n // 只读时只能换段\n if (readonly && !['ArrowLeft', 'ArrowRight'].includes(event.key)) return;\n if (handleSegmentKeydown(segments, event, isRtl(root))) event.preventDefault();\n }\n\n function onFocusout(event: FocusEvent) {\n if (!root?.contains(event.relatedTarget as Node | null)) segments.setFocused(null);\n }\n\n return () => {\n return (\n <div\n {...mergeAttrs<'div'>(\n {\n sx: dateSegmentStyles.root,\n 'aria-labelledby': defaultLabelledBy(rest, labelledBy),\n 'aria-describedby': describedBy,\n },\n rest,\n // 整组标为不可用:标签(for 指向这一组)随之按不可用处理,不只是各段\n {\n ref: _toRef(root),\n role: 'group',\n 'aria-disabled': disabled || undefined,\n onFocusout,\n },\n )}\n data-invalid={invalid || undefined}\n data-disabled={disabled || undefined}\n >\n {segments.segments.map((segment, index) => {\n if (segment.type === 'literal') {\n return (\n <span\n key={`literal-${index}`}\n {...mergeAttrs<'span'>({ sx: dateSegmentStyles.literal }, propsLiteral)}\n aria-hidden=\"true\"\n >\n {segment.text}\n </span>\n );\n }\n const type = segment.type;\n const segmentId = `${id}-${type}`;\n const own = typeof propsSegment === 'function' ? propsSegment(segment) : propsSegment;\n return (\n <span\n key={`${type}-${composed}`}\n {...mergeAttrs<'span'>(\n {\n sx: [\n dateSegmentStyles.segment,\n segment.placeholder && dateSegmentStyles.placeholder,\n disabled && dateSegmentStyles.disabled,\n ],\n 'aria-describedby': describedBy,\n spellcheck: false,\n autocapitalize: 'off',\n autocorrect: 'off',\n enterkeyhint: 'next',\n },\n own,\n {\n // 每段的名称各不相同(年、月、日),由组件管理\n id: segmentId,\n 'aria-label': segment.label,\n 'aria-labelledby': labelledBy ? `${labelledBy} ${segmentId}` : undefined,\n ref: (element: unknown) => {\n if (element instanceof HTMLElement) elements.set(type, element);\n },\n role: 'spinbutton',\n tabindex: disabled ? -1 : 0,\n contenteditable: editable() ? 'true' : undefined,\n inputmode: type === 'dayPeriod' ? 'text' : 'numeric',\n 'aria-valuenow': segment.value ?? undefined,\n 'aria-valuemin': segment.min,\n 'aria-valuemax': segment.max,\n 'aria-valuetext': segment.placeholder ? lang.dateFieldEmpty : segment.text,\n 'aria-required': required || undefined,\n 'aria-invalid': invalid || undefined,\n 'aria-readonly': readonly || undefined,\n 'aria-disabled': disabled || undefined,\n onFocus: () => segments.setFocused(type),\n onBeforeinput,\n onCompositionend,\n onKeydown,\n // 粘贴的文字不进段里(会打乱渲染);需要时用输入\n onPaste: (event: ClipboardEvent) => event.preventDefault(),\n },\n )}\n data-type={type}\n data-placeholder={segment.placeholder || undefined}\n >\n {segment.text}\n </span>\n );\n })}\n </div>\n );\n };\n },\n);\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"DateSegmentGroup.js","names":["_vtp_readonlyProp","_vtp_restProps","_vue_shallowRef","_vue_ref","_createVNode","_mergeProps"],"sources":["../../src/date/DateSegmentGroup.tsx"],"sourcesContent":["/**\n * 一组日期分段(年 / 月 / 日、时 : 分):每段是 role=\"spinbutton\" 的可编辑元素,分隔符对读屏隐藏。\n * 状态由调用方的 useDateSegments 给;DateField、TimeField 放在输入框外壳里用,DateRangePicker 在一个外壳里放两组。\n *\n * - 每段 contenteditable 加 inputmode=\"numeric\":手机上弹出数字键盘,输入法打的全角数字也收得到;\n * 文字由组件渲染,beforeinput 一律拦下交给 useDateSegments(输入法组合结束后重新渲染这一段);\n * - 每段在 Tab 顺序里(同 React Aria),←→ 也能换段;输入满了自动跳到下一段,焦点跟过去;\n * - 读屏名称是“字段名 + 段名”(aria-labelledby 指向外部标签与这一段),没填的段取值念“未填写”。\n *\n * 部件:Root(div role=\"group\",主元素)、Segment(每段)、Literal(分隔符)。\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 { isRtl } from '../utils/dom/direction';\nimport { defaultLabelledBy, mergeAttrs, type PartProps } from '../utils/props';\nimport {\n handleSegmentKeydown,\n type DateSegment,\n type DateSegmentsState,\n type DateSegmentType,\n} from './date-segments';\nimport { useLang } from '../config/context';\n\n/** 角色与状态由组件管理 */\nexport type DateSegmentGroupManaged = 'role';\n\n/** 每段由组件管理的属性 */\nexport type DateSegmentManaged =\n | 'role'\n | 'id'\n | 'aria-label'\n | 'aria-labelledby'\n | 'tabindex'\n | 'contenteditable'\n | 'inputmode'\n | 'aria-valuenow'\n | 'aria-valuemin'\n | 'aria-valuemax'\n | 'aria-valuetext'\n | 'aria-required'\n | 'aria-invalid'\n | 'aria-readonly'\n | 'aria-disabled';\n\n/** 未声明的属性落到这一组(role=\"group\")上 */\nexport interface DateSegmentGroupProps extends PartProps<'div', DateSegmentGroupManaged> {\n /** 分段状态(useDateSegments) */\n segments: DateSegmentsState;\n /** 不可用:段不能聚焦 */\n disabled?: boolean;\n /** 只读:能聚焦、不能改 */\n readonly?: boolean;\n /** 必填(每段 aria-required) */\n required?: boolean;\n /** 校验未通过 */\n invalid?: boolean;\n /** 外部标签的 id:每段的读屏名称是“标签 + 段名” */\n labelledBy?: string;\n /** 说明与错误的 id(aria-describedby),给每一段 */\n describedBy?: string;\n /** 每段的部件属性:固定的对象,或按这一段返回 */\n propsSegment?:\n | PartProps<'span', DateSegmentManaged>\n | ((segment: DateSegment) => PartProps<'span', DateSegmentManaged>);\n /** 分隔符的部件属性 */\n propsLiteral?: PartProps<'span', 'aria-hidden'>;\n}\n\n/** DateSegmentGroup 的样式:root、segment、placeholder、literal */\nexport const dateSegmentStyles = css.create({\n root: {\n display: 'inline-flex',\n alignItems: 'center',\n minWidth: 0,\n paddingBlock: '_xs',\n whiteSpace: 'nowrap',\n fontVariantNumeric: 'tabular-nums',\n },\n segment: {\n boxSizing: 'content-box',\n paddingInline: '0.1em',\n borderRadius: '_xs',\n // 光标与选区由组件画(整段高亮),不显示浏览器的插入符\n caretColor: 'transparent',\n outlineStyle: 'none',\n textAlign: 'end',\n backgroundColor: { default: 'transparent', ':focus': '_accent' },\n color: { default: 'inherit', ':focus': '_accentContrast' },\n cursor: 'text',\n },\n // 占位文字是真正的文字(不是原生 placeholder),要满足 4.5:1:用 textMuted\n placeholder: { color: { default: '_textMuted', ':focus': '_accentContrast' } },\n literal: { color: '_textMuted', whiteSpace: 'pre' },\n disabled: { cursor: 'not-allowed' },\n});\n\n/** 一组日期分段 */\nexport const DateSegmentGroup = _component(\n ({\n segments,\n disabled = false,\n readonly = false,\n required = false,\n invalid = false,\n labelledBy,\n describedBy,\n propsSegment,\n propsLiteral,\n ...rest\n }: DateSegmentGroupProps) => {\n const lang = useLang();\n const id = useId();\n const elements = new Map<DateSegmentType, HTMLElement>();\n let root = _state.raw<HTMLElement | null>(null);\n // 输入法组合会直接改掉段里的文字(beforeinput 拦不住):结束后换个 key 重新渲染这一段\n let composed = _state(0);\n\n // 输入满了跳段、Backspace 删空退回上一段:焦点跟着 focusedSegment 走(只在焦点本来就在这组里时)\n _effect(() => {\n const type = segments.focusedSegment;\n const target = type && elements.get(type);\n if (!target || !root?.contains(document.activeElement) || document.activeElement === target)\n return;\n target.focus();\n });\n\n const editable = () => !disabled && !readonly;\n\n function onBeforeinput(event: InputEvent) {\n // 输入法组合中的文字取消不了,等 compositionend\n if (event.inputType === 'insertCompositionText') return;\n event.preventDefault();\n if (!editable()) return;\n if (event.inputType.startsWith('delete')) segments.backspace();\n else if (event.data) segments.type(event.data);\n }\n\n function onCompositionend(event: CompositionEvent) {\n const focused = segments.focusedSegment;\n if (editable() && event.data) segments.type(event.data);\n composed += 1;\n // 重新渲染后元素换了:焦点留在原来那段(或已经跳到的下一段)\n void nextTick(() => elements.get(segments.focusedSegment ?? focused!)?.focus());\n }\n\n function onKeydown(event: KeyboardEvent) {\n // Enter 同原生的输入框:写回打了一半的年份,再提交所在的表单(查询栏、对话框里的表单)\n if (event.key === 'Enter') {\n event.preventDefault();\n segments.commit();\n root?.closest('form')?.requestSubmit();\n return;\n }\n if (!root || disabled) return;\n // 只读时只能换段\n if (readonly && !['ArrowLeft', 'ArrowRight'].includes(event.key)) return;\n if (handleSegmentKeydown(segments, event, isRtl(root))) event.preventDefault();\n }\n\n function onFocusout(event: FocusEvent) {\n if (!root?.contains(event.relatedTarget as Node | null)) segments.setFocused(null);\n }\n\n return () => {\n return (\n <div\n {...mergeAttrs<'div'>(\n {\n sx: dateSegmentStyles.root,\n 'aria-labelledby': defaultLabelledBy(rest, labelledBy),\n 'aria-describedby': describedBy,\n },\n rest,\n // 整组标为不可用:标签(for 指向这一组)随之按不可用处理,不只是各段\n {\n ref: _toRef(root),\n role: 'group',\n 'aria-disabled': disabled || undefined,\n onFocusout,\n },\n )}\n data-invalid={invalid || undefined}\n data-disabled={disabled || undefined}\n >\n {segments.segments.map((segment, index) => {\n if (segment.type === 'literal') {\n return (\n <span\n key={`literal-${index}`}\n {...mergeAttrs<'span'>({ sx: dateSegmentStyles.literal }, propsLiteral)}\n aria-hidden=\"true\"\n >\n {segment.text}\n </span>\n );\n }\n const type = segment.type;\n const segmentId = `${id}-${type}`;\n const own = typeof propsSegment === 'function' ? propsSegment(segment) : propsSegment;\n return (\n <span\n key={`${type}-${composed}`}\n {...mergeAttrs<'span'>(\n {\n sx: [\n dateSegmentStyles.segment,\n segment.placeholder && dateSegmentStyles.placeholder,\n disabled && dateSegmentStyles.disabled,\n ],\n 'aria-describedby': describedBy,\n spellcheck: false,\n autocapitalize: 'off',\n autocorrect: 'off',\n enterkeyhint: 'next',\n },\n own,\n {\n // 每段的名称各不相同(年、月、日),由组件管理\n id: segmentId,\n 'aria-label': segment.label,\n 'aria-labelledby': labelledBy ? `${labelledBy} ${segmentId}` : undefined,\n ref: (element: unknown) => {\n if (element instanceof HTMLElement) elements.set(type, element);\n },\n role: 'spinbutton',\n tabindex: disabled ? -1 : 0,\n contenteditable: editable() ? 'true' : undefined,\n inputmode: type === 'dayPeriod' ? 'text' : 'numeric',\n 'aria-valuenow': segment.value ?? undefined,\n 'aria-valuemin': segment.min,\n 'aria-valuemax': segment.max,\n 'aria-valuetext': segment.placeholder ? lang.dateFieldEmpty : segment.text,\n 'aria-required': required || undefined,\n 'aria-invalid': invalid || undefined,\n 'aria-readonly': readonly || undefined,\n 'aria-disabled': disabled || undefined,\n onFocus: () => segments.setFocused(type),\n onBeforeinput,\n onCompositionend,\n onKeydown,\n // 粘贴的文字不进段里(会打乱渲染);需要时用输入\n onPaste: (event: ClipboardEvent) => event.preventDefault(),\n },\n )}\n data-type={type}\n data-placeholder={segment.placeholder || undefined}\n >\n {segment.text}\n </span>\n );\n })}\n </div>\n );\n };\n },\n);\n"],"mappings":"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"}
|
|
@@ -10,8 +10,8 @@ import { toIsoQuarter } from "../utils/data/date.js";
|
|
|
10
10
|
import { toDateLimits, useDateLocale } from "./date-locale.js";
|
|
11
11
|
import { calendarStyles } from "./CalendarView.js";
|
|
12
12
|
import { isPeriodDisabled } from "./DateField.js";
|
|
13
|
-
import { computed, createVNode, isVNode,
|
|
14
|
-
import { _component, bindable, readonlyProp, restProps, untrack } from "@zerodep-dev/vue-tsx-pro-tsx";
|
|
13
|
+
import { computed, createVNode, isVNode, nextTick, ref, shallowRef, useId } from "vue";
|
|
14
|
+
import { _component, bindable, mergeJsxProps, readonlyProp, restProps, untrack } 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 } from "@lucide/icons";
|
|
17
17
|
//#region src/date/QuarterCalendar.tsx
|
|
@@ -141,7 +141,7 @@ var QuarterCalendar = /*#__PURE__*/ _component((_props, _ctx) => {
|
|
|
141
141
|
const preview = range();
|
|
142
142
|
const today = current();
|
|
143
143
|
const heading = String(year.value);
|
|
144
|
-
return createVNode("div",
|
|
144
|
+
return createVNode("div", mergeJsxProps(mergeAttrs({ sx: calendarStyles.root }, rest, {
|
|
145
145
|
ref: root,
|
|
146
146
|
role: "group"
|
|
147
147
|
}), {
|
|
@@ -165,7 +165,7 @@ var QuarterCalendar = /*#__PURE__*/ _component((_props, _ctx) => {
|
|
|
165
165
|
"onClick": _cache[3] || (_cache[3] = () => year.value += 1)
|
|
166
166
|
}, null)
|
|
167
167
|
]),
|
|
168
|
-
createVNode("table",
|
|
168
|
+
createVNode("table", mergeJsxProps(css.attrs(calendarStyles.grid, calendarStyles.periodGrid), {
|
|
169
169
|
"role": "grid",
|
|
170
170
|
"aria-label": lang.calendarYear(year.value),
|
|
171
171
|
"aria-multiselectable": mode.value === "range" || void 0,
|
|
@@ -182,12 +182,12 @@ var QuarterCalendar = /*#__PURE__*/ _component((_props, _ctx) => {
|
|
|
182
182
|
const ends = preview != null && (key === preview.start || key === preview.end);
|
|
183
183
|
const selected = mode.value === "range" ? inRange : value.value === key;
|
|
184
184
|
const isFocused = key === focused.value || yearOf(focused.value) !== year.value && quarter === 1;
|
|
185
|
-
return createVNode("td",
|
|
185
|
+
return createVNode("td", mergeJsxProps({
|
|
186
186
|
"key": key,
|
|
187
187
|
"role": "gridcell",
|
|
188
188
|
"aria-selected": selected,
|
|
189
189
|
"aria-disabled": disabledHere || void 0
|
|
190
|
-
}, css.attrs(calendarStyles.cell, inRange && calendarStyles.inRange, preview != null && key === preview.start && calendarStyles.rangeStart, preview != null && key === preview.end && calendarStyles.rangeEnd)), [createVNode("button",
|
|
190
|
+
}, css.attrs(calendarStyles.cell, inRange && calendarStyles.inRange, preview != null && key === preview.start && calendarStyles.rangeStart, preview != null && key === preview.end && calendarStyles.rangeEnd)), [createVNode("button", mergeJsxProps({
|
|
191
191
|
"type": "button",
|
|
192
192
|
"tabindex": isFocused ? 0 : -1,
|
|
193
193
|
"aria-disabled": disabledHere || void 0,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"QuarterCalendar.js","names":["_isVNode","_vtp_readonlyProp","_vtp_bindable","_vtp_restProps","_vue_computed","_vue_ref","_vtp_untrack","_vue_shallowRef","_createVNode","_mergeProps"],"sources":["../../src/date/QuarterCalendar.tsx"],"sourcesContent":["/**\n * 季度面板:一年一页的四个季度(季度报表、考核周期),翻页换年;选一个季度,或选一段区间(先开始、再结束)。\n * DatePicker、DateRangePicker 的 granularity=\"quarter\" 弹出的就是它,也可以单独放在页面上。\n *\n * <QuarterCalendar _value={quarter} aria-label=\"考核季度\" /> // '2026-Q4'\n * <QuarterCalendar mode=\"range\" _value={range} aria-label=\"对比区间\" /> // { start: '2026-Q1', end: '2026-Q3' }\n *\n * 外观与 Calendar 的月份视图一致(同一套 calendarStyles):一行四格,区间连成一条,本季度带描边。\n * 焦点在当前季度上(roving tabindex):←→ 移动(到头时换年;从右到左排版时反过来),Home / End 到第一、第四季度,\n * Page Up / Down 换年,Enter / 空格选中;区间选到一半时悬停预览,Esc 取消。整段在 min–max 之外的季度不可用。\n *\n * 部件:Root(div,role=\"group\")、Header、Previous、Next(IconButton)、Heading、Grid(table)、Cell、CellButton。\n */\nimport { ChevronLeft, ChevronRight } from '@lucide/icons';\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 { buttonBaseStyles } from '../base/button-core';\nimport { IconButton } from '../base/IconButton';\nimport { VisuallyHidden, visuallyHiddenStyles } from '../base/VisuallyHidden';\nimport { useLang } from '../config/context';\nimport { toIsoQuarter, type DateRange } from '../utils/data/date';\nimport { isRtl } from '../utils/dom/direction';\nimport { mergeAttrs, type PartProps } from '../utils/props';\nimport { focusRingStyles } from '../utils/style/styles';\nimport { interactionTransitions } from '../utils/style/transitions';\nimport { calendarStyles } from './CalendarView';\nimport { isPeriodDisabled } from './DateField';\nimport { toDateLimits, useDateLocale, type DateInput } from './date-locale';\n\n/** 未声明的属性落到根元素上(aria-label 等) */\nexport interface QuarterCalendarProps extends PartProps<'div', 'role'> {\n /** 选一个季度(默认),或选一段区间 */\n mode?: 'single' | 'range';\n /** 单选时是 'YYYY-Qn',区间时是 `{ start, end }`:父级写 _value={x};没有时 null */\n _value?: string | DateRange<string> | null;\n /** 最早可选的日期:整个季度都早于它时不可用 */\n min?: DateInput;\n /** 最晚可选的日期:整个季度都晚于它时不可用 */\n max?: DateInput;\n /** 不可用:不能翻页、不能选 */\n disabled?: boolean;\n /** 只读:能翻页、移动焦点,不能改选择 */\n readonly?: boolean;\n /** 选好了(单选选了一个,区间选了结束):日期选择器据此关闭弹层 */\n onPick?: () => void;\n}\n\nconst quarterKey = (year: number, quarter: number) =>\n `${String(year).padStart(4, '0')}-Q${quarter}`;\nconst yearOf = (key: string) => Number(key.slice(0, 4));\nconst quarterOf = (key: string) => Number(key.slice(6));\n/** 两个季度排好序('YYYY-Qn' 按字符串比较就是先后) */\nconst ordered = (a: string, b: string): DateRange<string> =>\n a <= b ? { start: a, end: b } : { start: b, end: a };\n\n/** 季度面板:四个季度一页,单选或区间 */\nexport const QuarterCalendar = _component(\n ({\n mode = 'single',\n _value: value = _bindable<string | DateRange<string> | null>(null),\n min,\n max,\n disabled = false,\n readonly = false,\n onPick,\n ...rest\n }: QuarterCalendarProps) => {\n const lang = useLang();\n const locale = useDateLocale();\n const hintId = useId();\n const limits = _derived(toDateLimits(locale, { min, max }));\n const current = () => toIsoQuarter(locale.today());\n const chosen = () => (value == null ? null : typeof value === 'string' ? value : value.start);\n // 一开始显示已选(没有时是本季度)那一年,焦点也在它上面\n let focused = _state(chosen() ?? current());\n let year = _state(_untrack(yearOf(focused)));\n // 区间:选了开始、还没选结束时的开始,与悬停的季度(预览)\n let anchor = _state<string | null>(null);\n let hovered = _state<string | null>(null);\n let root = _state.raw<HTMLElement | null>(null);\n\n const off = (key: string) => isPeriodDisabled(key, 'quarter', limits, locale);\n const range = (): DateRange<string> | null => {\n if (mode !== 'range') return null;\n if (anchor) return ordered(anchor, hovered ?? anchor);\n return value != null && typeof value !== 'string' ? value : null;\n };\n\n function pick(key: string) {\n if (disabled || readonly || off(key)) return;\n focused = key;\n if (mode === 'single') {\n value = key;\n onPick?.();\n return;\n }\n if (!anchor) {\n anchor = key;\n hovered = key;\n return;\n }\n value = ordered(anchor, key);\n anchor = null;\n hovered = null;\n onPick?.();\n }\n\n /** 焦点移到 key(换到它那一年),再把 DOM 焦点交过去 */\n function moveTo(key: string) {\n focused = key;\n year = yearOf(key);\n if (anchor) hovered = key;\n void nextTick(() => root?.querySelector<HTMLElement>('[data-focused]')?.focus());\n }\n\n function onKeydown(event: KeyboardEvent) {\n if (disabled || event.altKey || event.ctrlKey || event.metaKey) return;\n const quarter = quarterOf(focused);\n const at = yearOf(focused);\n const forward = root && isRtl(root) ? 'ArrowLeft' : 'ArrowRight';\n const backward = forward === 'ArrowRight' ? 'ArrowLeft' : 'ArrowRight';\n const step = (delta: number) => {\n const index = at * 4 + quarter - 1 + delta;\n moveTo(quarterKey(Math.floor(index / 4), (index % 4) + 1));\n };\n switch (event.key) {\n case forward:\n step(1);\n break;\n case backward:\n step(-1);\n break;\n case 'Home':\n moveTo(quarterKey(at, 1));\n break;\n case 'End':\n moveTo(quarterKey(at, 4));\n break;\n case 'PageUp':\n moveTo(quarterKey(at - 1, quarter));\n break;\n case 'PageDown':\n moveTo(quarterKey(at + 1, quarter));\n break;\n case 'Escape':\n // 取消选到一半的区间;没有时交给弹层关闭\n if (!anchor) return;\n anchor = null;\n hovered = null;\n break;\n default:\n return;\n }\n event.preventDefault();\n }\n\n return () => {\n const preview = range();\n const today = current();\n const heading = String(year);\n return (\n <div\n {...mergeAttrs<'div'>({ sx: calendarStyles.root }, rest, {\n ref: _toRef(root),\n role: 'group',\n })}\n data-view=\"quarter\"\n data-disabled={disabled || undefined}\n >\n <div {...css.attrs(calendarStyles.header)}>\n <IconButton\n icon={ChevronLeft}\n aria-label={lang.calendarPreviousYear}\n propsIcon={{ flipInRtl: true }}\n disabled={disabled}\n onClick={() => (year -= 1)}\n />\n <span {...css.attrs(calendarStyles.heading, calendarStyles.headingStatic)}>\n {lang.calendarYear(year)}\n </span>\n <IconButton\n icon={ChevronRight}\n aria-label={lang.calendarNextYear}\n propsIcon={{ flipInRtl: true }}\n disabled={disabled}\n onClick={() => (year += 1)}\n />\n </div>\n <table\n {...css.attrs(calendarStyles.grid, calendarStyles.periodGrid)}\n role=\"grid\"\n aria-label={lang.calendarYear(year)}\n aria-multiselectable={mode === 'range' || undefined}\n onKeydown={onKeydown}\n >\n <tbody>\n <tr>\n {[1, 2, 3, 4].map((quarter) => {\n const key = quarterKey(year, quarter);\n const disabledHere = disabled || off(key);\n const inRange = preview != null && key >= preview.start && key <= preview.end;\n const ends = preview != null && (key === preview.start || key === preview.end);\n const selected = mode === 'range' ? inRange : value === key;\n const isFocused = key === focused || (yearOf(focused) !== year && quarter === 1);\n return (\n <td\n key={key}\n role=\"gridcell\"\n aria-selected={selected}\n aria-disabled={disabledHere || undefined}\n {...css.attrs(\n calendarStyles.cell,\n inRange && calendarStyles.inRange,\n preview != null && key === preview.start && calendarStyles.rangeStart,\n preview != null && key === preview.end && calendarStyles.rangeEnd,\n )}\n >\n <button\n type=\"button\"\n tabindex={isFocused ? 0 : -1}\n aria-disabled={disabledHere || undefined}\n aria-describedby={anchor ? hintId : undefined}\n {...css.attrs(\n buttonBaseStyles.root,\n interactionTransitions.colors,\n calendarStyles.button,\n calendarStyles.periodButton,\n focusRingStyles.tight,\n key === today && calendarStyles.today,\n inRange && !ends && calendarStyles.middle,\n (mode === 'range' ? ends : selected) && calendarStyles.selected,\n disabledHere && calendarStyles.disabled,\n )}\n data-quarter={key}\n data-focused={isFocused || undefined}\n data-selected={selected || undefined}\n data-disabled={disabledHere || undefined}\n onClick={() => pick(key)}\n onFocus={() => (focused = key)}\n onPointerenter={() => anchor && (hovered = key)}\n >\n <span {...css.attrs(visuallyHiddenStyles.hidden)}>\n {lang.dateFieldPeriodPatterns.quarter\n .replace('{year}', String(year))\n .replace('{quarter}', String(quarter))}\n </span>\n <span aria-hidden=\"true\">{lang.calendarQuarter(quarter)}</span>\n </button>\n </td>\n );\n })}\n </tr>\n </tbody>\n </table>\n <VisuallyHidden aria-live=\"polite\">{heading}</VisuallyHidden>\n {anchor && <VisuallyHidden id={hintId}>{lang.calendarPickEnd}</VisuallyHidden>}\n </div>\n );\n };\n },\n);\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"QuarterCalendar.js","names":["_isVNode","_vtp_readonlyProp","_vtp_bindable","_vtp_restProps","_vue_computed","_vue_ref","_vtp_untrack","_vue_shallowRef","_createVNode","_mergeProps"],"sources":["../../src/date/QuarterCalendar.tsx"],"sourcesContent":["/**\n * 季度面板:一年一页的四个季度(季度报表、考核周期),翻页换年;选一个季度,或选一段区间(先开始、再结束)。\n * DatePicker、DateRangePicker 的 granularity=\"quarter\" 弹出的就是它,也可以单独放在页面上。\n *\n * <QuarterCalendar _value={quarter} aria-label=\"考核季度\" /> // '2026-Q4'\n * <QuarterCalendar mode=\"range\" _value={range} aria-label=\"对比区间\" /> // { start: '2026-Q1', end: '2026-Q3' }\n *\n * 外观与 Calendar 的月份视图一致(同一套 calendarStyles):一行四格,区间连成一条,本季度带描边。\n * 焦点在当前季度上(roving tabindex):←→ 移动(到头时换年;从右到左排版时反过来),Home / End 到第一、第四季度,\n * Page Up / Down 换年,Enter / 空格选中;区间选到一半时悬停预览,Esc 取消。整段在 min–max 之外的季度不可用。\n *\n * 部件:Root(div,role=\"group\")、Header、Previous、Next(IconButton)、Heading、Grid(table)、Cell、CellButton。\n */\nimport { ChevronLeft, ChevronRight } from '@lucide/icons';\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 { buttonBaseStyles } from '../base/button-core';\nimport { IconButton } from '../base/IconButton';\nimport { VisuallyHidden, visuallyHiddenStyles } from '../base/VisuallyHidden';\nimport { useLang } from '../config/context';\nimport { toIsoQuarter, type DateRange } from '../utils/data/date';\nimport { isRtl } from '../utils/dom/direction';\nimport { mergeAttrs, type PartProps } from '../utils/props';\nimport { focusRingStyles } from '../utils/style/styles';\nimport { interactionTransitions } from '../utils/style/transitions';\nimport { calendarStyles } from './CalendarView';\nimport { isPeriodDisabled } from './DateField';\nimport { toDateLimits, useDateLocale, type DateInput } from './date-locale';\n\n/** 未声明的属性落到根元素上(aria-label 等) */\nexport interface QuarterCalendarProps extends PartProps<'div', 'role'> {\n /** 选一个季度(默认),或选一段区间 */\n mode?: 'single' | 'range';\n /** 单选时是 'YYYY-Qn',区间时是 `{ start, end }`:父级写 _value={x};没有时 null */\n _value?: string | DateRange<string> | null;\n /** 最早可选的日期:整个季度都早于它时不可用 */\n min?: DateInput;\n /** 最晚可选的日期:整个季度都晚于它时不可用 */\n max?: DateInput;\n /** 不可用:不能翻页、不能选 */\n disabled?: boolean;\n /** 只读:能翻页、移动焦点,不能改选择 */\n readonly?: boolean;\n /** 选好了(单选选了一个,区间选了结束):日期选择器据此关闭弹层 */\n onPick?: () => void;\n}\n\nconst quarterKey = (year: number, quarter: number) =>\n `${String(year).padStart(4, '0')}-Q${quarter}`;\nconst yearOf = (key: string) => Number(key.slice(0, 4));\nconst quarterOf = (key: string) => Number(key.slice(6));\n/** 两个季度排好序('YYYY-Qn' 按字符串比较就是先后) */\nconst ordered = (a: string, b: string): DateRange<string> =>\n a <= b ? { start: a, end: b } : { start: b, end: a };\n\n/** 季度面板:四个季度一页,单选或区间 */\nexport const QuarterCalendar = _component(\n ({\n mode = 'single',\n _value: value = _bindable<string | DateRange<string> | null>(null),\n min,\n max,\n disabled = false,\n readonly = false,\n onPick,\n ...rest\n }: QuarterCalendarProps) => {\n const lang = useLang();\n const locale = useDateLocale();\n const hintId = useId();\n const limits = _derived(toDateLimits(locale, { min, max }));\n const current = () => toIsoQuarter(locale.today());\n const chosen = () => (value == null ? null : typeof value === 'string' ? value : value.start);\n // 一开始显示已选(没有时是本季度)那一年,焦点也在它上面\n let focused = _state(chosen() ?? current());\n let year = _state(_untrack(yearOf(focused)));\n // 区间:选了开始、还没选结束时的开始,与悬停的季度(预览)\n let anchor = _state<string | null>(null);\n let hovered = _state<string | null>(null);\n let root = _state.raw<HTMLElement | null>(null);\n\n const off = (key: string) => isPeriodDisabled(key, 'quarter', limits, locale);\n const range = (): DateRange<string> | null => {\n if (mode !== 'range') return null;\n if (anchor) return ordered(anchor, hovered ?? anchor);\n return value != null && typeof value !== 'string' ? value : null;\n };\n\n function pick(key: string) {\n if (disabled || readonly || off(key)) return;\n focused = key;\n if (mode === 'single') {\n value = key;\n onPick?.();\n return;\n }\n if (!anchor) {\n anchor = key;\n hovered = key;\n return;\n }\n value = ordered(anchor, key);\n anchor = null;\n hovered = null;\n onPick?.();\n }\n\n /** 焦点移到 key(换到它那一年),再把 DOM 焦点交过去 */\n function moveTo(key: string) {\n focused = key;\n year = yearOf(key);\n if (anchor) hovered = key;\n void nextTick(() => root?.querySelector<HTMLElement>('[data-focused]')?.focus());\n }\n\n function onKeydown(event: KeyboardEvent) {\n if (disabled || event.altKey || event.ctrlKey || event.metaKey) return;\n const quarter = quarterOf(focused);\n const at = yearOf(focused);\n const forward = root && isRtl(root) ? 'ArrowLeft' : 'ArrowRight';\n const backward = forward === 'ArrowRight' ? 'ArrowLeft' : 'ArrowRight';\n const step = (delta: number) => {\n const index = at * 4 + quarter - 1 + delta;\n moveTo(quarterKey(Math.floor(index / 4), (index % 4) + 1));\n };\n switch (event.key) {\n case forward:\n step(1);\n break;\n case backward:\n step(-1);\n break;\n case 'Home':\n moveTo(quarterKey(at, 1));\n break;\n case 'End':\n moveTo(quarterKey(at, 4));\n break;\n case 'PageUp':\n moveTo(quarterKey(at - 1, quarter));\n break;\n case 'PageDown':\n moveTo(quarterKey(at + 1, quarter));\n break;\n case 'Escape':\n // 取消选到一半的区间;没有时交给弹层关闭\n if (!anchor) return;\n anchor = null;\n hovered = null;\n break;\n default:\n return;\n }\n event.preventDefault();\n }\n\n return () => {\n const preview = range();\n const today = current();\n const heading = String(year);\n return (\n <div\n {...mergeAttrs<'div'>({ sx: calendarStyles.root }, rest, {\n ref: _toRef(root),\n role: 'group',\n })}\n data-view=\"quarter\"\n data-disabled={disabled || undefined}\n >\n <div {...css.attrs(calendarStyles.header)}>\n <IconButton\n icon={ChevronLeft}\n aria-label={lang.calendarPreviousYear}\n propsIcon={{ flipInRtl: true }}\n disabled={disabled}\n onClick={() => (year -= 1)}\n />\n <span {...css.attrs(calendarStyles.heading, calendarStyles.headingStatic)}>\n {lang.calendarYear(year)}\n </span>\n <IconButton\n icon={ChevronRight}\n aria-label={lang.calendarNextYear}\n propsIcon={{ flipInRtl: true }}\n disabled={disabled}\n onClick={() => (year += 1)}\n />\n </div>\n <table\n {...css.attrs(calendarStyles.grid, calendarStyles.periodGrid)}\n role=\"grid\"\n aria-label={lang.calendarYear(year)}\n aria-multiselectable={mode === 'range' || undefined}\n onKeydown={onKeydown}\n >\n <tbody>\n <tr>\n {[1, 2, 3, 4].map((quarter) => {\n const key = quarterKey(year, quarter);\n const disabledHere = disabled || off(key);\n const inRange = preview != null && key >= preview.start && key <= preview.end;\n const ends = preview != null && (key === preview.start || key === preview.end);\n const selected = mode === 'range' ? inRange : value === key;\n const isFocused = key === focused || (yearOf(focused) !== year && quarter === 1);\n return (\n <td\n key={key}\n role=\"gridcell\"\n aria-selected={selected}\n aria-disabled={disabledHere || undefined}\n {...css.attrs(\n calendarStyles.cell,\n inRange && calendarStyles.inRange,\n preview != null && key === preview.start && calendarStyles.rangeStart,\n preview != null && key === preview.end && calendarStyles.rangeEnd,\n )}\n >\n <button\n type=\"button\"\n tabindex={isFocused ? 0 : -1}\n aria-disabled={disabledHere || undefined}\n aria-describedby={anchor ? hintId : undefined}\n {...css.attrs(\n buttonBaseStyles.root,\n interactionTransitions.colors,\n calendarStyles.button,\n calendarStyles.periodButton,\n focusRingStyles.tight,\n key === today && calendarStyles.today,\n inRange && !ends && calendarStyles.middle,\n (mode === 'range' ? ends : selected) && calendarStyles.selected,\n disabledHere && calendarStyles.disabled,\n )}\n data-quarter={key}\n data-focused={isFocused || undefined}\n data-selected={selected || undefined}\n data-disabled={disabledHere || undefined}\n onClick={() => pick(key)}\n onFocus={() => (focused = key)}\n onPointerenter={() => anchor && (hovered = key)}\n >\n <span {...css.attrs(visuallyHiddenStyles.hidden)}>\n {lang.dateFieldPeriodPatterns.quarter\n .replace('{year}', String(year))\n .replace('{quarter}', String(quarter))}\n </span>\n <span aria-hidden=\"true\">{lang.calendarQuarter(quarter)}</span>\n </button>\n </td>\n );\n })}\n </tr>\n </tbody>\n </table>\n <VisuallyHidden aria-live=\"polite\">{heading}</VisuallyHidden>\n {anchor && <VisuallyHidden id={hintId}>{lang.calendarPickEnd}</VisuallyHidden>}\n </div>\n );\n };\n },\n);\n"],"mappings":"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"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { useCalendarState } from "./calendar-state.js";
|
|
2
2
|
import { toDateLimits, useDateLocale, writeDateLike } from "./date-locale.js";
|
|
3
3
|
import { CalendarView } from "./CalendarView.js";
|
|
4
|
-
import { computed, createVNode
|
|
5
|
-
import { _component, bindable, readonlyProp, restProps } from "@zerodep-dev/vue-tsx-pro-tsx";
|
|
4
|
+
import { computed, createVNode } from "vue";
|
|
5
|
+
import { _component, bindable, mergeJsxProps, readonlyProp, restProps } from "@zerodep-dev/vue-tsx-pro-tsx";
|
|
6
6
|
import { endOfMonth, endOfYear, startOfMonth, startOfYear } from "@internationalized/date";
|
|
7
7
|
//#region src/date/RangeCalendar.tsx
|
|
8
8
|
/** 未声明的属性落到根元素上 */
|
|
@@ -58,7 +58,7 @@ var RangeCalendar = /*#__PURE__*/ _component((_props, _ctx) => {
|
|
|
58
58
|
readonly: () => readonly.value || disabled.value,
|
|
59
59
|
defaultFocused: () => locale.fromValue(defaultFocused.value)
|
|
60
60
|
});
|
|
61
|
-
return () => createVNode(CalendarView,
|
|
61
|
+
return () => createVNode(CalendarView, mergeJsxProps(rest, {
|
|
62
62
|
"calendar": calendar,
|
|
63
63
|
"firstDayOfWeek": firstDayOfWeek.value,
|
|
64
64
|
"disabled": disabled.value,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RangeCalendar.js","names":["_vtp_bindable","_vtp_readonlyProp","_vtp_restProps","_vue_computed","_createVNode","_mergeProps"],"sources":["../../src/date/RangeCalendar.tsx"],"sourcesContent":["/**\n * 区间日历:先点开始、再点结束,中间悬停时预览;酒店的入住与离店、报表的起止日期。参考 React Aria RangeCalendar。\n *\n * <RangeCalendar _value={stay} visibleMonths={2} min={today} isDateUnavailable={booked} />\n *\n * 值是 `{ start, end }`(两端都含;ISO 字符串或 DateValue,写回时保持同一种类型);反着点也会排好序。\n * 默认区间里不能夹着不可选的日期(订满的那天):选了开始之后,越过不可选日期的那一侧都不能再选;\n * 允许时写 allowsUnavailableInRange。选到一半按 Esc 取消。键盘与部件同 Calendar(部件见 CalendarView)。\n * granularity 是 month / year 时按月、按年选(季度报表、跨年的预算),写回的区间是整月、整年:开始那月的 1 日到结束那月的最后一天。\n */\nimport { _component } from '@zerodep-dev/vue-tsx-pro-tsx';\nimport {\n endOfMonth,\n endOfYear,\n startOfMonth,\n startOfYear,\n type CalendarDate,\n} from '@internationalized/date';\nimport type { DateRange, DateUnit } from '../utils/data/date';\nimport { useCalendarState } from './calendar-state';\nimport { CalendarView, type CalendarViewProps } from './CalendarView';\nimport {\n toDateLimits,\n useDateLocale,\n writeDateLike,\n type DateInput,\n type DateLimitProps,\n type DateValueTypes,\n} from './date-locale';\n\n/** 未声明的属性落到根元素上 */\nexport interface RangeCalendarProps<V extends DateInput = string>\n extends Omit<CalendarViewProps, 'calendar'>, DateLimitProps {\n /** 选中的区间(两端都含):父级写 _value={x};没有时 null */\n _value?: DateRange<V> | null;\n /** 选择的粒度,默认 day;month、year 时选整月、整年(写回的仍是日期:月初到月末、年初到年末) */\n granularity?: DateUnit;\n /** 并排显示几个月,默认 1;酒店、机票常用 2 */\n visibleMonths?: number;\n /** 区间里允许夹着不可选的日期,默认 false */\n allowsUnavailableInRange?: boolean;\n /** 没有已选区间时一开始显示哪天所在的月,默认今天 */\n defaultFocused?: DateInput;\n /** 值为空、推不出类型时写回哪一种,默认 iso(字符串) */\n valueType?: keyof DateValueTypes;\n /** 只读:能翻页、移动焦点,不能改选择 */\n readonly?: boolean;\n /** 选完了一个区间(点了结束):日期选择器据此关闭弹层 */\n onPick?: (range: DateRange<V>) => void;\n}\n\n/** 区间日历:选开始与结束 */\nexport const RangeCalendar = _component(\n <V extends DateInput>({\n _value: value = _bindable<DateRange<V> | null>(null),\n min,\n max,\n isDateUnavailable,\n granularity = 'day',\n visibleMonths = 1,\n allowsUnavailableInRange = false,\n defaultFocused,\n valueType,\n firstDayOfWeek,\n readonly = false,\n disabled = false,\n onPick,\n ...rest\n }: RangeCalendarProps<V>) => {\n const locale = useDateLocale(() => ({ firstDayOfWeek }));\n // 按月、按年选时,两端扩到整月、整年\n const periodStart = (date: CalendarDate) =>\n granularity === 'year'\n ? startOfYear(date)\n : granularity === 'month'\n ? startOfMonth(date)\n : date;\n const periodEnd = (date: CalendarDate) =>\n granularity === 'year' ? endOfYear(date) : granularity === 'month' ? endOfMonth(date) : date;\n const selected = _derived.by(() => {\n const start = locale.fromValue(value?.start);\n const end = locale.fromValue(value?.end);\n return start && end ? [start, end] : [];\n });\n // 值清空后再选:仍按清空前的类型写回\n let lastLike: V | undefined;\n const limits = _derived(toDateLimits(locale, { min, max, isDateUnavailable }));\n const calendar = useCalendarState({\n mode: () => 'range',\n selected: () => selected,\n onChange: ([start, end]) => {\n lastLike = value?.start ?? lastLike;\n const write = (date: CalendarDate) => writeDateLike(date, lastLike, valueType) as V;\n const range = { start: write(periodStart(start!)), end: write(periodEnd(end!)) };\n value = range;\n onPick?.(range);\n },\n today: () => locale.today(),\n unit: () => granularity,\n limits: () => limits,\n week: () => locale.week,\n visibleMonths: () => visibleMonths,\n allowsUnavailableInRange: () => allowsUnavailableInRange,\n readonly: () => readonly || disabled,\n defaultFocused: () => locale.fromValue(defaultFocused),\n });\n return () => (\n <CalendarView\n {...rest}\n calendar={calendar}\n firstDayOfWeek={firstDayOfWeek}\n disabled={disabled}\n data-invalid={calendar.invalid || undefined}\n />\n );\n },\n);\n"],"mappings":";;;;;;;;;AAoDA,IAAa,gBAAgB,0BAC3B,QAAA,SAe6B;CAAA,MAAA,QAAAA,SAAA,QAAA,gBAdoB,MAAI,KAAA,IAAA;CAAA,MAAA,MAAAC,aAAA,QAAA,KAAA;CAAA,MAAA,MAAAA,aAAA,QAAA,KAAA;CAAA,MAAA,oBAAAA,aAAA,QAAA,mBAAA;CAAA,MAAA,cAAAA,aAAA,QAAA,eAIrC,KAAK;CAAA,MAAA,gBAAAA,aAAA,QAAA,iBACH,CAAC;CAAA,MAAA,2BAAAA,aAAA,QAAA,4BACU,KAAK;CAAA,MAAA,iBAAAA,aAAA,QAAA,gBAAA;CAAA,MAAA,YAAAA,aAAA,QAAA,WAAA;CAAA,MAAA,iBAAAA,aAAA,QAAA,gBAAA;CAAA,MAAA,WAAAA,aAAA,QAAA,YAIrB,KAAK;CAAA,MAAA,WAAAA,aAAA,QAAA,YACL,KAAK;CAAA,MAAA,SAAAA,aAAA,QAAA,QAAA;CAAA,MAEb,OAAIC,UAAA,QAAA,MAAA,IAAA;CAEP,MAAM,SAAS,qBAAqB,EAAE,gBAAc,eAAA,MAAC,EAAE;CAEvD,MAAM,eAAe,SACnB,YAAA,UAAgB,SACZ,YAAY,IAAI,IAChB,YAAA,UAAgB,UACd,aAAa,IAAI,IACjB;CACR,MAAM,aAAa,SACjB,YAAA,UAAgB,SAAS,UAAU,IAAI,IAAI,YAAA,UAAgB,UAAU,WAAW,IAAI,IAAI;CAC1F,MAAM,WAAQC,eAAqB;EACjC,MAAM,QAAQ,OAAO,UAAU,MAAA,OAAO,KAAK;EAC3C,MAAM,MAAM,OAAO,UAAU,MAAA,OAAO,GAAG;EACvC,OAAO,SAAS,MAAM,CAAC,OAAO,GAAG,IAAI,CAAA;CACvC,CAAC;CAED,IAAI;CACJ,MAAM,SAAMA,eAAY,aAAa,QAAQ;EAAE,KAAG,IAAA;EAAE,KAAG,IAAA;EAAE,mBAAiB,kBAAA;CAAC,CAAC,CAAC;CAC7E,MAAM,WAAW,iBAAiB;EAChC,YAAY;EACZ,gBAAU,SAAA;EACV,WAAW,CAAC,OAAO,SAAS;GAC1B,WAAW,MAAA,OAAO,SAAS;GAC3B,MAAM,SAAS,SAAuB,cAAc,MAAM,UAAQ,UAAA,KAAW;GAC7E,MAAM,QAAQ;IAAE,OAAO,MAAM,YAAY,KAAM,CAAC;IAAG,KAAK,MAAM,UAAU,GAAI,CAAC;GAAE;GAC/E,MAAA,QAAQ;GACR,OAAA,QAAS,KAAK;EAChB;EACA,aAAa,OAAO,MAAM;EAC1B,YAAM,YAAA;EACN,cAAQ,OAAA;EACR,YAAY,OAAO;EACnB,qBAAe,cAAA;EACf,gCAA0B,yBAAA;EAC1B,gBAAgB,SAAA,SAAA,SAAA;EAChB,sBAAsB,OAAO,UAAS,eAAA,KAAe;CACvD,CAAC;CACD,aAAOC,YAAA,cAAAC,
|
|
1
|
+
{"version":3,"file":"RangeCalendar.js","names":["_vtp_bindable","_vtp_readonlyProp","_vtp_restProps","_vue_computed","_createVNode","_mergeProps"],"sources":["../../src/date/RangeCalendar.tsx"],"sourcesContent":["/**\n * 区间日历:先点开始、再点结束,中间悬停时预览;酒店的入住与离店、报表的起止日期。参考 React Aria RangeCalendar。\n *\n * <RangeCalendar _value={stay} visibleMonths={2} min={today} isDateUnavailable={booked} />\n *\n * 值是 `{ start, end }`(两端都含;ISO 字符串或 DateValue,写回时保持同一种类型);反着点也会排好序。\n * 默认区间里不能夹着不可选的日期(订满的那天):选了开始之后,越过不可选日期的那一侧都不能再选;\n * 允许时写 allowsUnavailableInRange。选到一半按 Esc 取消。键盘与部件同 Calendar(部件见 CalendarView)。\n * granularity 是 month / year 时按月、按年选(季度报表、跨年的预算),写回的区间是整月、整年:开始那月的 1 日到结束那月的最后一天。\n */\nimport { _component } from '@zerodep-dev/vue-tsx-pro-tsx';\nimport {\n endOfMonth,\n endOfYear,\n startOfMonth,\n startOfYear,\n type CalendarDate,\n} from '@internationalized/date';\nimport type { DateRange, DateUnit } from '../utils/data/date';\nimport { useCalendarState } from './calendar-state';\nimport { CalendarView, type CalendarViewProps } from './CalendarView';\nimport {\n toDateLimits,\n useDateLocale,\n writeDateLike,\n type DateInput,\n type DateLimitProps,\n type DateValueTypes,\n} from './date-locale';\n\n/** 未声明的属性落到根元素上 */\nexport interface RangeCalendarProps<V extends DateInput = string>\n extends Omit<CalendarViewProps, 'calendar'>, DateLimitProps {\n /** 选中的区间(两端都含):父级写 _value={x};没有时 null */\n _value?: DateRange<V> | null;\n /** 选择的粒度,默认 day;month、year 时选整月、整年(写回的仍是日期:月初到月末、年初到年末) */\n granularity?: DateUnit;\n /** 并排显示几个月,默认 1;酒店、机票常用 2 */\n visibleMonths?: number;\n /** 区间里允许夹着不可选的日期,默认 false */\n allowsUnavailableInRange?: boolean;\n /** 没有已选区间时一开始显示哪天所在的月,默认今天 */\n defaultFocused?: DateInput;\n /** 值为空、推不出类型时写回哪一种,默认 iso(字符串) */\n valueType?: keyof DateValueTypes;\n /** 只读:能翻页、移动焦点,不能改选择 */\n readonly?: boolean;\n /** 选完了一个区间(点了结束):日期选择器据此关闭弹层 */\n onPick?: (range: DateRange<V>) => void;\n}\n\n/** 区间日历:选开始与结束 */\nexport const RangeCalendar = _component(\n <V extends DateInput>({\n _value: value = _bindable<DateRange<V> | null>(null),\n min,\n max,\n isDateUnavailable,\n granularity = 'day',\n visibleMonths = 1,\n allowsUnavailableInRange = false,\n defaultFocused,\n valueType,\n firstDayOfWeek,\n readonly = false,\n disabled = false,\n onPick,\n ...rest\n }: RangeCalendarProps<V>) => {\n const locale = useDateLocale(() => ({ firstDayOfWeek }));\n // 按月、按年选时,两端扩到整月、整年\n const periodStart = (date: CalendarDate) =>\n granularity === 'year'\n ? startOfYear(date)\n : granularity === 'month'\n ? startOfMonth(date)\n : date;\n const periodEnd = (date: CalendarDate) =>\n granularity === 'year' ? endOfYear(date) : granularity === 'month' ? endOfMonth(date) : date;\n const selected = _derived.by(() => {\n const start = locale.fromValue(value?.start);\n const end = locale.fromValue(value?.end);\n return start && end ? [start, end] : [];\n });\n // 值清空后再选:仍按清空前的类型写回\n let lastLike: V | undefined;\n const limits = _derived(toDateLimits(locale, { min, max, isDateUnavailable }));\n const calendar = useCalendarState({\n mode: () => 'range',\n selected: () => selected,\n onChange: ([start, end]) => {\n lastLike = value?.start ?? lastLike;\n const write = (date: CalendarDate) => writeDateLike(date, lastLike, valueType) as V;\n const range = { start: write(periodStart(start!)), end: write(periodEnd(end!)) };\n value = range;\n onPick?.(range);\n },\n today: () => locale.today(),\n unit: () => granularity,\n limits: () => limits,\n week: () => locale.week,\n visibleMonths: () => visibleMonths,\n allowsUnavailableInRange: () => allowsUnavailableInRange,\n readonly: () => readonly || disabled,\n defaultFocused: () => locale.fromValue(defaultFocused),\n });\n return () => (\n <CalendarView\n {...rest}\n calendar={calendar}\n firstDayOfWeek={firstDayOfWeek}\n disabled={disabled}\n data-invalid={calendar.invalid || undefined}\n />\n );\n },\n);\n"],"mappings":";;;;;;;;;AAoDA,IAAa,gBAAgB,0BAC3B,QAAA,SAe6B;CAAA,MAAA,QAAAA,SAAA,QAAA,gBAdoB,MAAI,KAAA,IAAA;CAAA,MAAA,MAAAC,aAAA,QAAA,KAAA;CAAA,MAAA,MAAAA,aAAA,QAAA,KAAA;CAAA,MAAA,oBAAAA,aAAA,QAAA,mBAAA;CAAA,MAAA,cAAAA,aAAA,QAAA,eAIrC,KAAK;CAAA,MAAA,gBAAAA,aAAA,QAAA,iBACH,CAAC;CAAA,MAAA,2BAAAA,aAAA,QAAA,4BACU,KAAK;CAAA,MAAA,iBAAAA,aAAA,QAAA,gBAAA;CAAA,MAAA,YAAAA,aAAA,QAAA,WAAA;CAAA,MAAA,iBAAAA,aAAA,QAAA,gBAAA;CAAA,MAAA,WAAAA,aAAA,QAAA,YAIrB,KAAK;CAAA,MAAA,WAAAA,aAAA,QAAA,YACL,KAAK;CAAA,MAAA,SAAAA,aAAA,QAAA,QAAA;CAAA,MAEb,OAAIC,UAAA,QAAA,MAAA,IAAA;CAEP,MAAM,SAAS,qBAAqB,EAAE,gBAAc,eAAA,MAAC,EAAE;CAEvD,MAAM,eAAe,SACnB,YAAA,UAAgB,SACZ,YAAY,IAAI,IAChB,YAAA,UAAgB,UACd,aAAa,IAAI,IACjB;CACR,MAAM,aAAa,SACjB,YAAA,UAAgB,SAAS,UAAU,IAAI,IAAI,YAAA,UAAgB,UAAU,WAAW,IAAI,IAAI;CAC1F,MAAM,WAAQC,eAAqB;EACjC,MAAM,QAAQ,OAAO,UAAU,MAAA,OAAO,KAAK;EAC3C,MAAM,MAAM,OAAO,UAAU,MAAA,OAAO,GAAG;EACvC,OAAO,SAAS,MAAM,CAAC,OAAO,GAAG,IAAI,CAAA;CACvC,CAAC;CAED,IAAI;CACJ,MAAM,SAAMA,eAAY,aAAa,QAAQ;EAAE,KAAG,IAAA;EAAE,KAAG,IAAA;EAAE,mBAAiB,kBAAA;CAAC,CAAC,CAAC;CAC7E,MAAM,WAAW,iBAAiB;EAChC,YAAY;EACZ,gBAAU,SAAA;EACV,WAAW,CAAC,OAAO,SAAS;GAC1B,WAAW,MAAA,OAAO,SAAS;GAC3B,MAAM,SAAS,SAAuB,cAAc,MAAM,UAAQ,UAAA,KAAW;GAC7E,MAAM,QAAQ;IAAE,OAAO,MAAM,YAAY,KAAM,CAAC;IAAG,KAAK,MAAM,UAAU,GAAI,CAAC;GAAE;GAC/E,MAAA,QAAQ;GACR,OAAA,QAAS,KAAK;EAChB;EACA,aAAa,OAAO,MAAM;EAC1B,YAAM,YAAA;EACN,cAAQ,OAAA;EACR,YAAY,OAAO;EACnB,qBAAe,cAAA;EACf,gCAA0B,yBAAA;EAC1B,gBAAgB,SAAA,SAAA,SAAA;EAChB,sBAAsB,OAAO,UAAS,eAAA,KAAe;CACvD,CAAC;CACD,aAAOC,YAAA,cAAAC,cAEC,MAAI;EAAA,YACE;EAAQ,kBAAA,eAAA;EAAA,YAAA,SAAA;EAAA,gBAGJ,SAAS,WAAW,KAAA;CAAS,CAAA,GAAA,IAAA;AAGjD,GAAC;CAAA,MAAA;CAAA,OAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;CAAA;AAAA,CACH"}
|
|
@@ -8,8 +8,8 @@ import { FormValue } from "../form/FormValue.js";
|
|
|
8
8
|
import { useDateLocale } from "./date-locale.js";
|
|
9
9
|
import { fieldsToIso, isoToFields, useDateSegments } from "./date-segments.js";
|
|
10
10
|
import { DateSegmentGroup } from "./DateSegmentGroup.js";
|
|
11
|
-
import { createVNode,
|
|
12
|
-
import { _component, bindable, readonlyProp, restProps } from "@zerodep-dev/vue-tsx-pro-tsx";
|
|
11
|
+
import { createVNode, shallowRef } from "vue";
|
|
12
|
+
import { _component, bindable, mergeJsxProps, readonlyProp, restProps } from "@zerodep-dev/vue-tsx-pro-tsx";
|
|
13
13
|
import * as css from "@zerodep-dev/vue-tsx-pro-tsx/styles";
|
|
14
14
|
import { now } from "@internationalized/date";
|
|
15
15
|
//#region src/date/SegmentedField.tsx
|
|
@@ -112,7 +112,7 @@ var SegmentedField = /*#__PURE__*/ _component((_props, _ctx) => {
|
|
|
112
112
|
...state,
|
|
113
113
|
invalid: bad
|
|
114
114
|
});
|
|
115
|
-
return createVNode("div",
|
|
115
|
+
return createVNode("div", mergeJsxProps(mergeAttrs(s.root, propsRoot.value, { onPointerdown: state.disabled ? void 0 : focusOnShell(firstSegment) }), {
|
|
116
116
|
"data-invalid": bad || void 0,
|
|
117
117
|
"data-disabled": state.disabled || void 0
|
|
118
118
|
}), [
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SegmentedField.js","names":["_vtp_bindable","_vtp_readonlyProp","_vtp_restProps","_vue_shallowRef","_createVNode","_mergeProps"],"sources":["../../src/date/SegmentedField.tsx"],"sourcesContent":["/**\n * 分段输入框:输入框外壳(同 TextField)里放一组日期分段、可选的前置图标、清空按钮与尾部内容。\n * DateField、TimeField 是它的两种用法(只差有哪些段与怎样校验);日期选择器把打开日历的按钮放在 suffix 里。\n *\n * <SegmentedField fields={['hour', 'minute']} _value={time} />\n *\n * 值是 ISO 字符串(fieldsToIso);所有段都填了才写回,删掉一段写回 null。写了 name 时用隐藏的 input 随表单提交。\n * 点外壳的空白处时焦点进第一个没填的段。\n *\n * 部件:Root(外壳)、Input(DateSegmentGroup,主元素)、Segment、Literal、Icon、Clear。\n */\nimport { _component } from '@zerodep-dev/vue-tsx-pro-tsx';\nimport { now } from '@internationalized/date';\nimport * as css from '@zerodep-dev/vue-tsx-pro-tsx/styles';\nimport type { VNodeChild } from 'vue';\nimport type { PresetIconButtonProps } from '../base/CloseButton';\nimport { useLang } from '../config/context';\nimport type { HourCycles } from '../config/lang/lang';\nimport { useFieldControl, useFormReset } from '../form/Field';\nimport { FormValue } from '../form/FormValue';\nimport {\n controlClear,\n controlParts,\n focusOnShell,\n controlIcon,\n useControlAppearance,\n type ControlIconOptions,\n type ControlOptions,\n} from '../input/control';\nimport { isoWeekOf } from '../utils/data/date';\nimport { toElement } from '../utils/dom/element';\nimport { mergeAttrs, mergePartProps, type PartProps } from '../utils/props';\nimport { useDateLocale } from './date-locale';\nimport { fieldsToIso, isoToFields, useDateSegments, type DateSegmentType } from './date-segments';\nimport { DateSegmentGroup, type DateSegmentGroupProps } from './DateSegmentGroup';\n\n/** 由组件管理:角色与关联 */\nexport type SegmentedFieldManaged = 'role' | 'aria-describedby';\n\n/** 分段输入框共有的属性(DateField、TimeField 都有);未声明的属性落到这组分段上(主元素) */\nexport interface SegmentedFieldOptions\n extends\n PartProps<'div', SegmentedFieldManaged>,\n ControlOptions,\n ControlIconOptions,\n Pick<DateSegmentGroupProps, 'propsSegment' | 'propsLiteral'> {\n /** 12 / 24 小时制(有时的段时);不写时按语言 */\n hourCycle?: keyof HourCycles;\n /** 不可用 */\n disabled?: boolean;\n /** 只读 */\n readonly?: boolean;\n /** 必填 */\n required?: boolean;\n /** 校验未通过;不写时值超出范围、是不可选的日期也算 */\n invalid?: boolean;\n /** 表单提交时的 name(用隐藏的 input,值是 ISO 字符串) */\n name?: string;\n /** 有值时显示清空按钮 */\n clearable?: boolean;\n /** 框里最后的内容:日期选择器打开日历的按钮 */\n suffix?: VNodeChild;\n /** 外壳的部件属性 */\n propsRoot?: PartProps<'div'>;\n /** 清空按钮的部件属性 */\n propsClear?: Omit<Partial<PresetIconButtonProps>, 'onClick'>;\n}\n\n/** 未声明的属性落到这组分段上 */\nexport interface SegmentedFieldProps extends SegmentedFieldOptions {\n /** 值(ISO 字符串):父级写 _value={x};没填全时 null */\n _value?: string | null;\n /** 有哪些段,按年 → 秒的顺序(显示顺序按语言) */\n fields: readonly Exclude<DateSegmentType, 'dayPeriod'>[];\n /** 值超出范围、不可选(有值时调用):外壳显示出错 */\n outOfRange?: (value: string) => boolean;\n}\n\n/**\n * 一组分段的状态,按当前语言(段的顺序、小时制、占位文字,没填时占位的是此刻);值是 ISO 字符串,填全了才写回。\n * SegmentedField 与 DateRangePicker 的开始、结束两组分段用它。在组件 setup 里调用\n */\nexport function useFieldSegments(\n read: () => {\n fields: readonly Exclude<DateSegmentType, 'dayPeriod'>[];\n value: string | null;\n hourCycle?: keyof HourCycles;\n readonly: boolean;\n },\n onChange: (value: string | null) => void,\n onExit?: (direction: 1 | -1) => void,\n) {\n const lang = useLang();\n const locale = useDateLocale();\n const segments = useDateSegments({\n fields: () => read().fields,\n locale: () => lang.code,\n hourCycle: () => read().hourCycle ?? lang.hourCycle,\n value: () => isoToFields(read().value, read().fields),\n onChange: (next) => onChange(next ? fieldsToIso(next, read().fields) : null),\n placeholderValue: () => {\n const current = now(locale.timeZone);\n // 选周时从这一周所属的年起步(12 月底的那周可能已经算明年的第 1 周)\n const week = isoWeekOf(current);\n return {\n year: read().fields.includes('week') ? week.year : current.year,\n month: current.month,\n day: current.day,\n quarter: Math.ceil(current.month / 3),\n week: week.week,\n hour: current.hour,\n minute: current.minute,\n second: 0,\n };\n },\n placeholders: () => lang.dateFieldPlaceholders,\n periodPatterns: () => lang.dateFieldPeriodPatterns,\n readonly: () => read().readonly,\n onExit,\n });\n useFormReset(() => segments.reset());\n return segments;\n}\n\n/** 一组分段里第一个没填的段(都填了时第一段):点外壳空白处、清空后焦点去这里 */\nexport const firstSegmentOf = (group: Element | null | undefined) =>\n group?.querySelector<HTMLElement>('[role=\"spinbutton\"][data-placeholder]') ??\n group?.querySelector<HTMLElement>('[role=\"spinbutton\"]') ??\n null;\n\n/** SegmentedField 的样式:input */\nexport const segmentedFieldStyles = css.create({\n // 分段占满剩下的宽度:清空按钮、尾部内容贴着外壳的结束边\n input: { flexGrow: 1 },\n});\n\n/** 分段输入框:外壳 + 一组日期分段 */\nexport const SegmentedField = _component(\n ({\n _value: value = _bindable<string | null>(null),\n fields,\n outOfRange,\n hourCycle,\n variant,\n size,\n radius,\n disabled,\n readonly,\n required,\n invalid,\n name,\n clearable = false,\n icon,\n suffix,\n id,\n propsRoot,\n propsIcon,\n propsClear,\n propsSegment,\n propsLiteral,\n ...rest\n }: SegmentedFieldProps) => {\n const appearance = useControlAppearance(() => ({ variant, size, radius }));\n const control = useFieldControl(() => ({ id, invalid, required, disabled, readonly }));\n let group = _state.raw<HTMLElement | null>(null);\n\n const segments = useFieldSegments(\n () => ({\n fields,\n value,\n hourCycle,\n readonly: control().disabled || control().readonly,\n }),\n (next) => (value = next),\n );\n const firstSegment = () => firstSegmentOf(group);\n\n function clear() {\n segments.clear();\n value = null;\n firstSegment()?.focus();\n }\n\n return () => {\n const state = control();\n const bad = state.invalid || (value != null && (outOfRange?.(value) ?? false));\n const filled = value != null || segments.partial;\n const s = controlParts(appearance(), { ...state, invalid: bad });\n return (\n <div\n {...mergeAttrs<'div'>(s.root, propsRoot, {\n onPointerdown: state.disabled ? undefined : focusOnShell(firstSegment),\n })}\n data-invalid={bad || undefined}\n data-disabled={state.disabled || undefined}\n >\n {controlIcon(s, icon, propsIcon)}\n <DateSegmentGroup\n {...mergePartProps<DateSegmentGroupProps>(\n {\n sx: segmentedFieldStyles.input,\n segments,\n labelledBy: state.labelledBy,\n describedBy: state.describedBy,\n },\n rest,\n {\n ref: (instance: unknown) => (group = toElement(instance)),\n id: state.id,\n segments,\n disabled: state.disabled,\n readonly: state.readonly,\n required: state.required,\n invalid: bad,\n propsSegment,\n propsLiteral,\n },\n )}\n />\n {clearable &&\n filled &&\n !state.disabled &&\n !state.readonly &&\n controlClear(s, propsClear, clear)}\n {suffix}\n <FormValue name={name} value={value} disabled={state.disabled} />\n </div>\n );\n };\n },\n);\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"SegmentedField.js","names":["_vtp_bindable","_vtp_readonlyProp","_vtp_restProps","_vue_shallowRef","_createVNode","_mergeProps"],"sources":["../../src/date/SegmentedField.tsx"],"sourcesContent":["/**\n * 分段输入框:输入框外壳(同 TextField)里放一组日期分段、可选的前置图标、清空按钮与尾部内容。\n * DateField、TimeField 是它的两种用法(只差有哪些段与怎样校验);日期选择器把打开日历的按钮放在 suffix 里。\n *\n * <SegmentedField fields={['hour', 'minute']} _value={time} />\n *\n * 值是 ISO 字符串(fieldsToIso);所有段都填了才写回,删掉一段写回 null。写了 name 时用隐藏的 input 随表单提交。\n * 点外壳的空白处时焦点进第一个没填的段。\n *\n * 部件:Root(外壳)、Input(DateSegmentGroup,主元素)、Segment、Literal、Icon、Clear。\n */\nimport { _component } from '@zerodep-dev/vue-tsx-pro-tsx';\nimport { now } from '@internationalized/date';\nimport * as css from '@zerodep-dev/vue-tsx-pro-tsx/styles';\nimport type { VNodeChild } from 'vue';\nimport type { PresetIconButtonProps } from '../base/CloseButton';\nimport { useLang } from '../config/context';\nimport type { HourCycles } from '../config/lang/lang';\nimport { useFieldControl, useFormReset } from '../form/Field';\nimport { FormValue } from '../form/FormValue';\nimport {\n controlClear,\n controlParts,\n focusOnShell,\n controlIcon,\n useControlAppearance,\n type ControlIconOptions,\n type ControlOptions,\n} from '../input/control';\nimport { isoWeekOf } from '../utils/data/date';\nimport { toElement } from '../utils/dom/element';\nimport { mergeAttrs, mergePartProps, type PartProps } from '../utils/props';\nimport { useDateLocale } from './date-locale';\nimport { fieldsToIso, isoToFields, useDateSegments, type DateSegmentType } from './date-segments';\nimport { DateSegmentGroup, type DateSegmentGroupProps } from './DateSegmentGroup';\n\n/** 由组件管理:角色与关联 */\nexport type SegmentedFieldManaged = 'role' | 'aria-describedby';\n\n/** 分段输入框共有的属性(DateField、TimeField 都有);未声明的属性落到这组分段上(主元素) */\nexport interface SegmentedFieldOptions\n extends\n PartProps<'div', SegmentedFieldManaged>,\n ControlOptions,\n ControlIconOptions,\n Pick<DateSegmentGroupProps, 'propsSegment' | 'propsLiteral'> {\n /** 12 / 24 小时制(有时的段时);不写时按语言 */\n hourCycle?: keyof HourCycles;\n /** 不可用 */\n disabled?: boolean;\n /** 只读 */\n readonly?: boolean;\n /** 必填 */\n required?: boolean;\n /** 校验未通过;不写时值超出范围、是不可选的日期也算 */\n invalid?: boolean;\n /** 表单提交时的 name(用隐藏的 input,值是 ISO 字符串) */\n name?: string;\n /** 有值时显示清空按钮 */\n clearable?: boolean;\n /** 框里最后的内容:日期选择器打开日历的按钮 */\n suffix?: VNodeChild;\n /** 外壳的部件属性 */\n propsRoot?: PartProps<'div'>;\n /** 清空按钮的部件属性 */\n propsClear?: Omit<Partial<PresetIconButtonProps>, 'onClick'>;\n}\n\n/** 未声明的属性落到这组分段上 */\nexport interface SegmentedFieldProps extends SegmentedFieldOptions {\n /** 值(ISO 字符串):父级写 _value={x};没填全时 null */\n _value?: string | null;\n /** 有哪些段,按年 → 秒的顺序(显示顺序按语言) */\n fields: readonly Exclude<DateSegmentType, 'dayPeriod'>[];\n /** 值超出范围、不可选(有值时调用):外壳显示出错 */\n outOfRange?: (value: string) => boolean;\n}\n\n/**\n * 一组分段的状态,按当前语言(段的顺序、小时制、占位文字,没填时占位的是此刻);值是 ISO 字符串,填全了才写回。\n * SegmentedField 与 DateRangePicker 的开始、结束两组分段用它。在组件 setup 里调用\n */\nexport function useFieldSegments(\n read: () => {\n fields: readonly Exclude<DateSegmentType, 'dayPeriod'>[];\n value: string | null;\n hourCycle?: keyof HourCycles;\n readonly: boolean;\n },\n onChange: (value: string | null) => void,\n onExit?: (direction: 1 | -1) => void,\n) {\n const lang = useLang();\n const locale = useDateLocale();\n const segments = useDateSegments({\n fields: () => read().fields,\n locale: () => lang.code,\n hourCycle: () => read().hourCycle ?? lang.hourCycle,\n value: () => isoToFields(read().value, read().fields),\n onChange: (next) => onChange(next ? fieldsToIso(next, read().fields) : null),\n placeholderValue: () => {\n const current = now(locale.timeZone);\n // 选周时从这一周所属的年起步(12 月底的那周可能已经算明年的第 1 周)\n const week = isoWeekOf(current);\n return {\n year: read().fields.includes('week') ? week.year : current.year,\n month: current.month,\n day: current.day,\n quarter: Math.ceil(current.month / 3),\n week: week.week,\n hour: current.hour,\n minute: current.minute,\n second: 0,\n };\n },\n placeholders: () => lang.dateFieldPlaceholders,\n periodPatterns: () => lang.dateFieldPeriodPatterns,\n readonly: () => read().readonly,\n onExit,\n });\n useFormReset(() => segments.reset());\n return segments;\n}\n\n/** 一组分段里第一个没填的段(都填了时第一段):点外壳空白处、清空后焦点去这里 */\nexport const firstSegmentOf = (group: Element | null | undefined) =>\n group?.querySelector<HTMLElement>('[role=\"spinbutton\"][data-placeholder]') ??\n group?.querySelector<HTMLElement>('[role=\"spinbutton\"]') ??\n null;\n\n/** SegmentedField 的样式:input */\nexport const segmentedFieldStyles = css.create({\n // 分段占满剩下的宽度:清空按钮、尾部内容贴着外壳的结束边\n input: { flexGrow: 1 },\n});\n\n/** 分段输入框:外壳 + 一组日期分段 */\nexport const SegmentedField = _component(\n ({\n _value: value = _bindable<string | null>(null),\n fields,\n outOfRange,\n hourCycle,\n variant,\n size,\n radius,\n disabled,\n readonly,\n required,\n invalid,\n name,\n clearable = false,\n icon,\n suffix,\n id,\n propsRoot,\n propsIcon,\n propsClear,\n propsSegment,\n propsLiteral,\n ...rest\n }: SegmentedFieldProps) => {\n const appearance = useControlAppearance(() => ({ variant, size, radius }));\n const control = useFieldControl(() => ({ id, invalid, required, disabled, readonly }));\n let group = _state.raw<HTMLElement | null>(null);\n\n const segments = useFieldSegments(\n () => ({\n fields,\n value,\n hourCycle,\n readonly: control().disabled || control().readonly,\n }),\n (next) => (value = next),\n );\n const firstSegment = () => firstSegmentOf(group);\n\n function clear() {\n segments.clear();\n value = null;\n firstSegment()?.focus();\n }\n\n return () => {\n const state = control();\n const bad = state.invalid || (value != null && (outOfRange?.(value) ?? false));\n const filled = value != null || segments.partial;\n const s = controlParts(appearance(), { ...state, invalid: bad });\n return (\n <div\n {...mergeAttrs<'div'>(s.root, propsRoot, {\n onPointerdown: state.disabled ? undefined : focusOnShell(firstSegment),\n })}\n data-invalid={bad || undefined}\n data-disabled={state.disabled || undefined}\n >\n {controlIcon(s, icon, propsIcon)}\n <DateSegmentGroup\n {...mergePartProps<DateSegmentGroupProps>(\n {\n sx: segmentedFieldStyles.input,\n segments,\n labelledBy: state.labelledBy,\n describedBy: state.describedBy,\n },\n rest,\n {\n ref: (instance: unknown) => (group = toElement(instance)),\n id: state.id,\n segments,\n disabled: state.disabled,\n readonly: state.readonly,\n required: state.required,\n invalid: bad,\n propsSegment,\n propsLiteral,\n },\n )}\n />\n {clearable &&\n filled &&\n !state.disabled &&\n !state.readonly &&\n controlClear(s, propsClear, clear)}\n {suffix}\n <FormValue name={name} value={value} disabled={state.disabled} />\n </div>\n );\n };\n },\n);\n"],"mappings":"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"}
|
package/dist/date/TimeField.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { parseIsoTime, toIsoTime } from "../utils/data/date.js";
|
|
2
2
|
import { segmentFields } from "./date-segments.js";
|
|
3
3
|
import { SegmentedField } from "./SegmentedField.js";
|
|
4
|
-
import { computed, createVNode
|
|
5
|
-
import { _component, bindable, readonlyProp, restProps } from "@zerodep-dev/vue-tsx-pro-tsx";
|
|
4
|
+
import { computed, createVNode } from "vue";
|
|
5
|
+
import { _component, bindable, mergeJsxProps, readonlyProp, restProps } from "@zerodep-dev/vue-tsx-pro-tsx";
|
|
6
6
|
//#region src/date/TimeField.tsx
|
|
7
7
|
/** 时间精确到哪一段 */
|
|
8
8
|
/** 时间输入框接受的值:'HH:mm[:ss]' 字符串,或 @internationalized/date 的 Time */
|
|
@@ -32,7 +32,7 @@ var TimeField = /*#__PURE__*/ _component((_props, _ctx) => {
|
|
|
32
32
|
const asTime = lastLike != null ? typeof lastLike !== "string" : valueType.value === "dateValue";
|
|
33
33
|
value.value = next != null && asTime ? parseIsoTime(next) : next;
|
|
34
34
|
}
|
|
35
|
-
return () => createVNode(SegmentedField,
|
|
35
|
+
return () => createVNode(SegmentedField, mergeJsxProps(rest, {
|
|
36
36
|
"onUpdate:_value": write,
|
|
37
37
|
"_value": iso.value,
|
|
38
38
|
"fields": segmentFields(granularity.value, true),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TimeField.js","names":["_vtp_bindable","_vtp_readonlyProp","_vtp_restProps","_vue_computed","_createVNode","_mergeProps"],"sources":["../../src/date/TimeField.tsx"],"sourcesContent":["/**\n * 时间输入框:分段输入时、分(可选秒),12 / 24 小时制跟随语言(zh-CN 24 小时,en-US 12 小时加 AM / PM),也可以用 hourCycle 指定。\n * 参考 React Aria TimeField、原生 input type=\"time\"。要从按步长列出的时间里点选用 TimePicker。\n *\n * <Field label=\"提醒时间\"><TimeField _value={time} /></Field>\n * <TimeField granularity=\"second\" hourCycle=\"h12\" _value={at} aria-label=\"打卡时间\" />\n *\n * 值是 'HH:mm'(granularity 为 second 时 'HH:mm:ss'),时总是 24 小时制,与原生 input type=\"time\" 一致;\n * 也可以是 @internationalized/date 的 Time,写回时保持同一种类型。\n * 键盘同 DateField;12 小时制时在上午 / 下午那段打 a、p 切换。部件见 SegmentedField。\n */\nimport { _component } from '@zerodep-dev/vue-tsx-pro-tsx';\nimport type { Time } from '@internationalized/date';\nimport { parseIsoTime, toIsoTime } from '../utils/data/date';\nimport type { DateValueTypes } from './date-locale';\nimport { segmentFields } from './date-segments';\nimport { SegmentedField, type SegmentedFieldOptions } from './SegmentedField';\n\n/** 时间精确到哪一段 */\nexport interface TimeFieldGranularities {\n /** 只有时:'14:00' */\n hour: unknown;\n /** 时分(默认):'14:30' */\n minute: unknown;\n /** 时分秒:'14:30:15' */\n second: unknown;\n}\n\n/** 时间输入框接受的值:'HH:mm[:ss]' 字符串,或 @internationalized/date 的 Time */\nexport type TimeInput = string | Time;\n\n/** TimeInput → Time:ISO 字符串按 'HH:mm[:ss]' 解析,空值或格式不对时为 null */\nexport const toTime = (input: TimeInput | null | undefined): Time | null =>\n typeof input === 'string' || input == null ? parseIsoTime(input) : input;\n\n/** 未声明的属性落到这组分段上(主元素);外壳用 propsRoot */\nexport interface TimeFieldProps<V extends TimeInput = string> extends SegmentedFieldOptions {\n /** 值('HH:mm' 或 'HH:mm:ss',24 小时制;或 Time,写回时保持同一种类型):父级写 _value={x};没填全时 null */\n _value?: V | null;\n /** 值为空、推不出类型时写回哪一种,默认 iso(字符串);dateValue 写 Time */\n valueType?: keyof DateValueTypes;\n /** 精确到哪一段,默认 minute */\n granularity?: keyof TimeFieldGranularities;\n /** 最早的时间('09:00' 或 Time):早于它显示出错 */\n min?: TimeInput;\n /** 最晚的时间('18:00' 或 Time):晚于它显示出错 */\n max?: TimeInput;\n /**\n * 不可选的时间(午休、已约满的时段):键入时显示出错,TimePicker 的列表里不可用。\n * 第一个参数是 'HH:mm',第二个是同一时刻的 Time:`(_, time) => time.hour === 12`\n */\n isTimeUnavailable?: (time: string, value: Time) => boolean;\n}\n\n/** 时间输入框:分段输入 */\nexport const TimeField = _component(\n <V extends TimeInput>({\n _value: value = _bindable<V | null>(null),\n granularity = 'minute',\n valueType = 'iso',\n min,\n max,\n isTimeUnavailable,\n ...rest\n }: TimeFieldProps<V>) => {\n const outOfRange = (iso: string) => {\n const time = parseIsoTime(iso);\n if (time == null) return false;\n const low = toTime(min);\n const high = toTime(max);\n return (\n (low != null && time.compare(low) < 0) ||\n (high != null && time.compare(high) > 0) ||\n (isTimeUnavailable?.(toIsoTime(time), time) ?? false)\n );\n };\n const iso = _derived(\n value == null || typeof value === 'string'\n ? value\n : toIsoTime(value, granularity === 'second'),\n );\n // 值清空后再填:仍按清空前的类型写回\n let lastLike: V | undefined;\n function write(next: string | null) {\n lastLike = value ?? lastLike;\n const asTime = lastLike != null ? typeof lastLike !== 'string' : valueType === 'dateValue';\n value = (next != null && asTime ? parseIsoTime(next) : next) as V | null;\n }\n return () => (\n <SegmentedField\n {...rest}\n onUpdate:_value={write}\n _value={iso}\n fields={segmentFields(granularity, true)}\n outOfRange={outOfRange}\n />\n );\n },\n);\n"],"mappings":";;;;;;;;;AAgCA,IAAa,UAAU,UACrB,OAAO,UAAU,YAAY,SAAS,OAAO,aAAa,KAAK,IAAI;;;AAsBrE,IAAa,YAAY,0BACvB,QAAA,SAQyB;CAAA,MAAA,QAAAA,SAAA,QAAA,gBAPa,MAAI,KAAA,IAAA;CAAA,MAAA,cAAAC,aAAA,QAAA,eAC1B,QAAQ;CAAA,MAAA,YAAAA,aAAA,QAAA,aACV,KAAK;CAAA,MAAA,MAAAA,aAAA,QAAA,KAAA;CAAA,MAAA,MAAAA,aAAA,QAAA,KAAA;CAAA,MAAA,oBAAAA,aAAA,QAAA,mBAAA;CAAA,MAId,OAAIC,UAAA,QAAA,MAAA,IAAA;CAEP,MAAM,cAAc,QAAgB;EAClC,MAAM,OAAO,aAAa,GAAG;EAC7B,IAAI,QAAQ,MAAM,OAAO;EACzB,MAAM,MAAM,OAAM,IAAA,KAAI;EACtB,MAAM,OAAO,OAAM,IAAA,KAAI;EACvB,OACG,OAAO,QAAQ,KAAK,QAAQ,GAAG,IAAI,KACnC,QAAQ,QAAQ,KAAK,QAAQ,IAAI,IAAI,MACrC,kBAAA,QAAoB,UAAU,IAAI,GAAG,IAAI,KAAK;CAEnD;CACA,MAAM,MAAGC,eACP,MAAA,SAAS,QAAQ,OAAA,MAAA,UAAiB,WAAQ,MAAA,QAEtC,UAAS,MAAA,OAAQ,YAAA,UAAgB,QAAQ,CAAC;CAGhD,IAAI;CACJ,SAAS,MAAM,MAAqB;EAClC,WAAW,MAAA,SAAS;EACpB,MAAM,SAAS,YAAY,OAAO,OAAO,aAAa,WAAW,UAAA,UAAc;EAC/E,MAAA,QAAS,QAAQ,QAAQ,SAAS,aAAa,IAAI,IAAI;CACzD;CACA,aAAOC,YAAA,gBAAAC,
|
|
1
|
+
{"version":3,"file":"TimeField.js","names":["_vtp_bindable","_vtp_readonlyProp","_vtp_restProps","_vue_computed","_createVNode","_mergeProps"],"sources":["../../src/date/TimeField.tsx"],"sourcesContent":["/**\n * 时间输入框:分段输入时、分(可选秒),12 / 24 小时制跟随语言(zh-CN 24 小时,en-US 12 小时加 AM / PM),也可以用 hourCycle 指定。\n * 参考 React Aria TimeField、原生 input type=\"time\"。要从按步长列出的时间里点选用 TimePicker。\n *\n * <Field label=\"提醒时间\"><TimeField _value={time} /></Field>\n * <TimeField granularity=\"second\" hourCycle=\"h12\" _value={at} aria-label=\"打卡时间\" />\n *\n * 值是 'HH:mm'(granularity 为 second 时 'HH:mm:ss'),时总是 24 小时制,与原生 input type=\"time\" 一致;\n * 也可以是 @internationalized/date 的 Time,写回时保持同一种类型。\n * 键盘同 DateField;12 小时制时在上午 / 下午那段打 a、p 切换。部件见 SegmentedField。\n */\nimport { _component } from '@zerodep-dev/vue-tsx-pro-tsx';\nimport type { Time } from '@internationalized/date';\nimport { parseIsoTime, toIsoTime } from '../utils/data/date';\nimport type { DateValueTypes } from './date-locale';\nimport { segmentFields } from './date-segments';\nimport { SegmentedField, type SegmentedFieldOptions } from './SegmentedField';\n\n/** 时间精确到哪一段 */\nexport interface TimeFieldGranularities {\n /** 只有时:'14:00' */\n hour: unknown;\n /** 时分(默认):'14:30' */\n minute: unknown;\n /** 时分秒:'14:30:15' */\n second: unknown;\n}\n\n/** 时间输入框接受的值:'HH:mm[:ss]' 字符串,或 @internationalized/date 的 Time */\nexport type TimeInput = string | Time;\n\n/** TimeInput → Time:ISO 字符串按 'HH:mm[:ss]' 解析,空值或格式不对时为 null */\nexport const toTime = (input: TimeInput | null | undefined): Time | null =>\n typeof input === 'string' || input == null ? parseIsoTime(input) : input;\n\n/** 未声明的属性落到这组分段上(主元素);外壳用 propsRoot */\nexport interface TimeFieldProps<V extends TimeInput = string> extends SegmentedFieldOptions {\n /** 值('HH:mm' 或 'HH:mm:ss',24 小时制;或 Time,写回时保持同一种类型):父级写 _value={x};没填全时 null */\n _value?: V | null;\n /** 值为空、推不出类型时写回哪一种,默认 iso(字符串);dateValue 写 Time */\n valueType?: keyof DateValueTypes;\n /** 精确到哪一段,默认 minute */\n granularity?: keyof TimeFieldGranularities;\n /** 最早的时间('09:00' 或 Time):早于它显示出错 */\n min?: TimeInput;\n /** 最晚的时间('18:00' 或 Time):晚于它显示出错 */\n max?: TimeInput;\n /**\n * 不可选的时间(午休、已约满的时段):键入时显示出错,TimePicker 的列表里不可用。\n * 第一个参数是 'HH:mm',第二个是同一时刻的 Time:`(_, time) => time.hour === 12`\n */\n isTimeUnavailable?: (time: string, value: Time) => boolean;\n}\n\n/** 时间输入框:分段输入 */\nexport const TimeField = _component(\n <V extends TimeInput>({\n _value: value = _bindable<V | null>(null),\n granularity = 'minute',\n valueType = 'iso',\n min,\n max,\n isTimeUnavailable,\n ...rest\n }: TimeFieldProps<V>) => {\n const outOfRange = (iso: string) => {\n const time = parseIsoTime(iso);\n if (time == null) return false;\n const low = toTime(min);\n const high = toTime(max);\n return (\n (low != null && time.compare(low) < 0) ||\n (high != null && time.compare(high) > 0) ||\n (isTimeUnavailable?.(toIsoTime(time), time) ?? false)\n );\n };\n const iso = _derived(\n value == null || typeof value === 'string'\n ? value\n : toIsoTime(value, granularity === 'second'),\n );\n // 值清空后再填:仍按清空前的类型写回\n let lastLike: V | undefined;\n function write(next: string | null) {\n lastLike = value ?? lastLike;\n const asTime = lastLike != null ? typeof lastLike !== 'string' : valueType === 'dateValue';\n value = (next != null && asTime ? parseIsoTime(next) : next) as V | null;\n }\n return () => (\n <SegmentedField\n {...rest}\n onUpdate:_value={write}\n _value={iso}\n fields={segmentFields(granularity, true)}\n outOfRange={outOfRange}\n />\n );\n },\n);\n"],"mappings":";;;;;;;;;AAgCA,IAAa,UAAU,UACrB,OAAO,UAAU,YAAY,SAAS,OAAO,aAAa,KAAK,IAAI;;;AAsBrE,IAAa,YAAY,0BACvB,QAAA,SAQyB;CAAA,MAAA,QAAAA,SAAA,QAAA,gBAPa,MAAI,KAAA,IAAA;CAAA,MAAA,cAAAC,aAAA,QAAA,eAC1B,QAAQ;CAAA,MAAA,YAAAA,aAAA,QAAA,aACV,KAAK;CAAA,MAAA,MAAAA,aAAA,QAAA,KAAA;CAAA,MAAA,MAAAA,aAAA,QAAA,KAAA;CAAA,MAAA,oBAAAA,aAAA,QAAA,mBAAA;CAAA,MAId,OAAIC,UAAA,QAAA,MAAA,IAAA;CAEP,MAAM,cAAc,QAAgB;EAClC,MAAM,OAAO,aAAa,GAAG;EAC7B,IAAI,QAAQ,MAAM,OAAO;EACzB,MAAM,MAAM,OAAM,IAAA,KAAI;EACtB,MAAM,OAAO,OAAM,IAAA,KAAI;EACvB,OACG,OAAO,QAAQ,KAAK,QAAQ,GAAG,IAAI,KACnC,QAAQ,QAAQ,KAAK,QAAQ,IAAI,IAAI,MACrC,kBAAA,QAAoB,UAAU,IAAI,GAAG,IAAI,KAAK;CAEnD;CACA,MAAM,MAAGC,eACP,MAAA,SAAS,QAAQ,OAAA,MAAA,UAAiB,WAAQ,MAAA,QAEtC,UAAS,MAAA,OAAQ,YAAA,UAAgB,QAAQ,CAAC;CAGhD,IAAI;CACJ,SAAS,MAAM,MAAqB;EAClC,WAAW,MAAA,SAAS;EACpB,MAAM,SAAS,YAAY,OAAO,OAAO,aAAa,WAAW,UAAA,UAAc;EAC/E,MAAA,QAAS,QAAQ,QAAQ,SAAS,aAAa,IAAI,IAAI;CACzD;CACA,aAAOC,YAAA,gBAAAC,cAEC,MAAI;EAAA,mBACS;EAAK,UAAA,IAAA;EAAA,UAEd,cAAa,YAAA,OAAc,IAAI;EAAC,cAC5B;CAAU,CAAA,GAAA,IAAA;AAG5B,GAAC;CAAA,MAAA;CAAA,OAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;CAAA;AAAA,CACH"}
|
package/dist/date/TimePicker.js
CHANGED
|
@@ -9,8 +9,8 @@ import { Listbox } from "../input/Listbox.js";
|
|
|
9
9
|
import { useDateLocale } from "./date-locale.js";
|
|
10
10
|
import { usePickerPopover } from "./picker-popover.js";
|
|
11
11
|
import { TimeField, toTime } from "./TimeField.js";
|
|
12
|
-
import { Fragment, computed, createVNode
|
|
13
|
-
import { _component, bindable, readonlyProp, restProps } from "@zerodep-dev/vue-tsx-pro-tsx";
|
|
12
|
+
import { Fragment, computed, createVNode } from "vue";
|
|
13
|
+
import { _component, bindable, mergeJsxProps, readonlyProp, restProps } from "@zerodep-dev/vue-tsx-pro-tsx";
|
|
14
14
|
import * as css from "@zerodep-dev/vue-tsx-pro-tsx/styles";
|
|
15
15
|
import { Clock } from "@lucide/icons";
|
|
16
16
|
import { Time, now } from "@internationalized/date";
|
|
@@ -129,7 +129,7 @@ var TimePicker = /*#__PURE__*/ _component((_props, _ctx) => {
|
|
|
129
129
|
const high = toTime(max.value);
|
|
130
130
|
return !(low != null && time.compare(low) < 0 || high != null && time.compare(high) > 0 || (isTimeUnavailable.value?.(toIsoTime(time), time) ?? false));
|
|
131
131
|
}
|
|
132
|
-
return () => createVNode(Fragment, null, [createVNode(TimeField,
|
|
132
|
+
return () => createVNode(Fragment, null, [createVNode(TimeField, mergeJsxProps(rest, {
|
|
133
133
|
"_value": value.value,
|
|
134
134
|
"onUpdate:_value": _cache[0] || (_cache[0] = ($event) => value.value = $event),
|
|
135
135
|
"granularity": granularity.value,
|
|
@@ -159,7 +159,7 @@ var TimePicker = /*#__PURE__*/ _component((_props, _ctx) => {
|
|
|
159
159
|
_value: selected.value,
|
|
160
160
|
onPick: (iso) => write(parseIsoTime(iso)),
|
|
161
161
|
required: true
|
|
162
|
-
}), null), createVNode(Button,
|
|
162
|
+
}), null), createVNode(Button, mergeJsxProps(mergePartProps({
|
|
163
163
|
sx: timePickerStyles.now,
|
|
164
164
|
variant: "ghost",
|
|
165
165
|
size: "sm",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TimePicker.js","names":["_vtp_bindable","_vtp_readonlyProp","_vtp_restProps","_vue_computed","_createVNode","_Fragment","_mergeProps"],"sources":["../../src/date/TimePicker.tsx"],"sourcesContent":["/**\n * 时间选择器:TimeField 加一个打开时间列表的按钮,按 step 列出可选的时间(同 Google 日历的时间下拉);可以直接键入,也可以点选。\n * 参考 Mantine TimePicker 的 presets、MUI 的 digital clock;列表是 Listbox(↑↓、Home / End、按数字跳转)。\n *\n * <Field label=\"开会时间\"><TimePicker _value={time} min=\"09:00\" max=\"18:00\" /></Field>\n * <TimePicker step={15} _value={time} aria-label=\"提醒时间\" />\n *\n * 值同 TimeField('HH:mm',或 Time)。列表从 min(默认 00:00)起每 step 分钟一项,到 max 为止,按语言的 12 / 24 小时制显示;\n * 不在列表里的时间(14:47)照样能键入;isTimeUnavailable 的时间在列表里不可用、键入时显示出错。\n * 列表下方“此刻”选当前时间(此刻不可选时不可用)。选好后关闭,焦点回到按钮;Esc、点外部关闭,分段里 Alt + ↓ 打开。\n *\n * 部件:同 TimeField,另有 Button(打开按钮)、Popover(弹层)、List(Listbox)、Now(“此刻”按钮)。\n */\nimport { Clock } from '@lucide/icons';\nimport { _component } from '@zerodep-dev/vue-tsx-pro-tsx';\nimport { now, Time } from '@internationalized/date';\nimport * as css from '@zerodep-dev/vue-tsx-pro-tsx/styles';\nimport { Button, type ButtonProps } from '../base/Button';\nimport { useLang } from '../config/context';\nimport { useFormat } from '../config/lang/format';\nimport { useFieldControl } from '../form/Field';\nimport { Listbox, type ListboxProps } from '../input/Listbox';\nimport type { ListboxOption } from '../input/list-state';\nimport { parseIsoTime, toIsoTime } from '../utils/data/date';\nimport { openSetter, type OpenChangeProps } from '../utils/dom/open-change';\nimport { mergePartProps, type PartProps } from '../utils/props';\nimport { useDateLocale } from './date-locale';\nimport { usePickerPopover, type PickerPopoverParts } from './picker-popover';\nimport { TimeField, toTime, type TimeFieldProps, type TimeInput } from './TimeField';\n\n/** 未声明的属性落到这组分段上(主元素);外壳用 propsRoot */\nexport interface TimePickerProps<V extends TimeInput = string>\n extends TimeFieldProps<V>, PickerPopoverParts, OpenChangeProps {\n /** 列表是否打开:父级可写 _open={x} */\n _open?: boolean;\n /** 列表里相邻两项相隔几分钟,默认 30 */\n step?: number;\n /** 时间列表的部件属性 */\n propsList?: Omit<Partial<ListboxProps>, 'items' | '_value' | 'onPick'>;\n /** “此刻”按钮的部件属性 */\n propsNow?: Omit<Partial<ButtonProps>, 'onClick'>;\n}\n\n/** TimePicker 的样式:popover、list、now */\nexport const timePickerStyles = css.create({\n popover: { display: 'flex', flexDirection: 'column', padding: '_xs' },\n // 弹层与输入框同宽,列表最多十来项高,其余滚动\n list: { maxHeight: '16em' },\n now: { marginTop: '_xs' },\n});\n\nconst minutesOf = (time: Time) => time.hour * 60 + time.minute;\n\n/** 时间选择器:分段输入 + 按步长列出的时间 */\nexport const TimePicker = _component(\n <V extends TimeInput>({\n _value: value = _bindable<V | null>(null),\n _open: open = _bindable(false),\n onOpenChange,\n step = 30,\n granularity = 'minute',\n valueType = 'iso',\n min,\n max,\n isTimeUnavailable,\n hourCycle,\n placement,\n id,\n disabled,\n readonly,\n invalid,\n required,\n propsRoot,\n propsButton,\n propsPopover,\n propsList,\n propsNow,\n onKeydown,\n ...rest\n }: TimePickerProps<V>) => {\n const lang = useLang();\n const format = useFormat();\n const locale = useDateLocale();\n // 输入由里面的 TimeField 接 Field;这里只读状态决定能不能打开面板\n const control = useFieldControl(() => ({ id, invalid, required, disabled, readonly }), {\n delegate: true,\n });\n const setOpen = openSetter(\n () => open,\n (next) => (open = next),\n () => onOpenChange,\n );\n const picker = usePickerPopover(\n () => ({\n open,\n placement,\n matchWidth: true,\n editable: !control().disabled && !control().readonly,\n label: lang.timePickerOpen,\n size: rest.size,\n }),\n {\n toggle: setOpen,\n shown: (panel) => panel.querySelector<HTMLElement>('[role=\"listbox\"]')?.focus(),\n },\n );\n\n /** 列表的选项:min 到 max,每 step 分钟一项,按语言的小时制显示 */\n const options = _derived.by((): ListboxOption[] => {\n const first = minutesOf(toTime(min) ?? new Time(0, 0));\n const last = minutesOf(toTime(max) ?? new Time(23, 59));\n const result: ListboxOption[] = [];\n for (let minutes = first; minutes <= last; minutes += Math.max(step, 1)) {\n const time = new Time(Math.floor(minutes / 60), minutes % 60);\n const iso = toIsoTime(time);\n result.push({\n value: iso,\n label: format.date(iso, { timeStyle: 'short', hourCycle }),\n disabled: isTimeUnavailable?.(iso, time) ?? false,\n });\n }\n return result;\n });\n\n // 当前值正好是列表里的一项时高亮它(只比时分)\n const selected = _derived.by(() => {\n const time = toTime(value);\n return time ? toIsoTime(time) : undefined;\n });\n\n /** 写回:和当前值同一种类型(Time 或字符串),精确到秒时补 :00 */\n function write(time: Time, event?: Event) {\n const asTime = (value != null && typeof value !== 'string') || valueType === 'dateValue';\n value = (asTime ? time : toIsoTime(time, granularity === 'second')) as V;\n setOpen(false, 'select', event);\n }\n\n /** 此刻(精确到分) */\n const nowTime = () => {\n const current = now(locale.timeZone);\n return new Time(current.hour, current.minute);\n };\n /** 此刻能不能选:在 min–max 之内,不是不可选的时间 */\n function nowAvailable() {\n const time = nowTime();\n const low = toTime(min);\n const high = toTime(max);\n return !(\n (low != null && time.compare(low) < 0) ||\n (high != null && time.compare(high) > 0) ||\n (isTimeUnavailable?.(toIsoTime(time), time) ?? false)\n );\n }\n\n return () => (\n <>\n <TimeField\n {...rest}\n _value={value}\n granularity={granularity}\n valueType={valueType}\n min={min}\n max={max}\n isTimeUnavailable={isTimeUnavailable}\n hourCycle={hourCycle}\n id={id}\n disabled={disabled}\n readonly={readonly}\n invalid={invalid}\n required={required}\n onKeydown={(event) => {\n onKeydown?.(event);\n picker.onKeydown(event);\n }}\n propsRoot={mergePartProps<PartProps<'div'>>({}, propsRoot, { ref: picker.anchorRef })}\n suffix={picker.button(Clock, propsButton)}\n />\n {picker.panel(\n timePickerStyles.popover,\n propsPopover,\n open && [\n <Listbox\n {...mergePartProps<ListboxProps>(\n { sx: timePickerStyles.list, 'aria-label': lang.timePickerOpen, items: options },\n propsList,\n // 在 Field 里时 Listbox 默认取 Field 的控件 id:那是输入框的,列表要自己的 id\n {\n id: `${picker.id}-list`,\n items: options,\n _value: selected,\n onPick: (iso) => write(parseIsoTime(iso)!),\n required: true,\n },\n )}\n />,\n <Button\n {...mergePartProps<ButtonProps>(\n { sx: timePickerStyles.now, variant: 'ghost', size: 'sm', fullWidth: true },\n propsNow,\n {\n disabled: !nowAvailable(),\n onClick: (event: MouseEvent) => write(nowTime(), event),\n },\n )}\n >\n {lang.timePickerNow}\n </Button>,\n ],\n )}\n </>\n );\n },\n);\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"TimePicker.js","names":["_vtp_bindable","_vtp_readonlyProp","_vtp_restProps","_vue_computed","_createVNode","_Fragment","_mergeProps"],"sources":["../../src/date/TimePicker.tsx"],"sourcesContent":["/**\n * 时间选择器:TimeField 加一个打开时间列表的按钮,按 step 列出可选的时间(同 Google 日历的时间下拉);可以直接键入,也可以点选。\n * 参考 Mantine TimePicker 的 presets、MUI 的 digital clock;列表是 Listbox(↑↓、Home / End、按数字跳转)。\n *\n * <Field label=\"开会时间\"><TimePicker _value={time} min=\"09:00\" max=\"18:00\" /></Field>\n * <TimePicker step={15} _value={time} aria-label=\"提醒时间\" />\n *\n * 值同 TimeField('HH:mm',或 Time)。列表从 min(默认 00:00)起每 step 分钟一项,到 max 为止,按语言的 12 / 24 小时制显示;\n * 不在列表里的时间(14:47)照样能键入;isTimeUnavailable 的时间在列表里不可用、键入时显示出错。\n * 列表下方“此刻”选当前时间(此刻不可选时不可用)。选好后关闭,焦点回到按钮;Esc、点外部关闭,分段里 Alt + ↓ 打开。\n *\n * 部件:同 TimeField,另有 Button(打开按钮)、Popover(弹层)、List(Listbox)、Now(“此刻”按钮)。\n */\nimport { Clock } from '@lucide/icons';\nimport { _component } from '@zerodep-dev/vue-tsx-pro-tsx';\nimport { now, Time } from '@internationalized/date';\nimport * as css from '@zerodep-dev/vue-tsx-pro-tsx/styles';\nimport { Button, type ButtonProps } from '../base/Button';\nimport { useLang } from '../config/context';\nimport { useFormat } from '../config/lang/format';\nimport { useFieldControl } from '../form/Field';\nimport { Listbox, type ListboxProps } from '../input/Listbox';\nimport type { ListboxOption } from '../input/list-state';\nimport { parseIsoTime, toIsoTime } from '../utils/data/date';\nimport { openSetter, type OpenChangeProps } from '../utils/dom/open-change';\nimport { mergePartProps, type PartProps } from '../utils/props';\nimport { useDateLocale } from './date-locale';\nimport { usePickerPopover, type PickerPopoverParts } from './picker-popover';\nimport { TimeField, toTime, type TimeFieldProps, type TimeInput } from './TimeField';\n\n/** 未声明的属性落到这组分段上(主元素);外壳用 propsRoot */\nexport interface TimePickerProps<V extends TimeInput = string>\n extends TimeFieldProps<V>, PickerPopoverParts, OpenChangeProps {\n /** 列表是否打开:父级可写 _open={x} */\n _open?: boolean;\n /** 列表里相邻两项相隔几分钟,默认 30 */\n step?: number;\n /** 时间列表的部件属性 */\n propsList?: Omit<Partial<ListboxProps>, 'items' | '_value' | 'onPick'>;\n /** “此刻”按钮的部件属性 */\n propsNow?: Omit<Partial<ButtonProps>, 'onClick'>;\n}\n\n/** TimePicker 的样式:popover、list、now */\nexport const timePickerStyles = css.create({\n popover: { display: 'flex', flexDirection: 'column', padding: '_xs' },\n // 弹层与输入框同宽,列表最多十来项高,其余滚动\n list: { maxHeight: '16em' },\n now: { marginTop: '_xs' },\n});\n\nconst minutesOf = (time: Time) => time.hour * 60 + time.minute;\n\n/** 时间选择器:分段输入 + 按步长列出的时间 */\nexport const TimePicker = _component(\n <V extends TimeInput>({\n _value: value = _bindable<V | null>(null),\n _open: open = _bindable(false),\n onOpenChange,\n step = 30,\n granularity = 'minute',\n valueType = 'iso',\n min,\n max,\n isTimeUnavailable,\n hourCycle,\n placement,\n id,\n disabled,\n readonly,\n invalid,\n required,\n propsRoot,\n propsButton,\n propsPopover,\n propsList,\n propsNow,\n onKeydown,\n ...rest\n }: TimePickerProps<V>) => {\n const lang = useLang();\n const format = useFormat();\n const locale = useDateLocale();\n // 输入由里面的 TimeField 接 Field;这里只读状态决定能不能打开面板\n const control = useFieldControl(() => ({ id, invalid, required, disabled, readonly }), {\n delegate: true,\n });\n const setOpen = openSetter(\n () => open,\n (next) => (open = next),\n () => onOpenChange,\n );\n const picker = usePickerPopover(\n () => ({\n open,\n placement,\n matchWidth: true,\n editable: !control().disabled && !control().readonly,\n label: lang.timePickerOpen,\n size: rest.size,\n }),\n {\n toggle: setOpen,\n shown: (panel) => panel.querySelector<HTMLElement>('[role=\"listbox\"]')?.focus(),\n },\n );\n\n /** 列表的选项:min 到 max,每 step 分钟一项,按语言的小时制显示 */\n const options = _derived.by((): ListboxOption[] => {\n const first = minutesOf(toTime(min) ?? new Time(0, 0));\n const last = minutesOf(toTime(max) ?? new Time(23, 59));\n const result: ListboxOption[] = [];\n for (let minutes = first; minutes <= last; minutes += Math.max(step, 1)) {\n const time = new Time(Math.floor(minutes / 60), minutes % 60);\n const iso = toIsoTime(time);\n result.push({\n value: iso,\n label: format.date(iso, { timeStyle: 'short', hourCycle }),\n disabled: isTimeUnavailable?.(iso, time) ?? false,\n });\n }\n return result;\n });\n\n // 当前值正好是列表里的一项时高亮它(只比时分)\n const selected = _derived.by(() => {\n const time = toTime(value);\n return time ? toIsoTime(time) : undefined;\n });\n\n /** 写回:和当前值同一种类型(Time 或字符串),精确到秒时补 :00 */\n function write(time: Time, event?: Event) {\n const asTime = (value != null && typeof value !== 'string') || valueType === 'dateValue';\n value = (asTime ? time : toIsoTime(time, granularity === 'second')) as V;\n setOpen(false, 'select', event);\n }\n\n /** 此刻(精确到分) */\n const nowTime = () => {\n const current = now(locale.timeZone);\n return new Time(current.hour, current.minute);\n };\n /** 此刻能不能选:在 min–max 之内,不是不可选的时间 */\n function nowAvailable() {\n const time = nowTime();\n const low = toTime(min);\n const high = toTime(max);\n return !(\n (low != null && time.compare(low) < 0) ||\n (high != null && time.compare(high) > 0) ||\n (isTimeUnavailable?.(toIsoTime(time), time) ?? false)\n );\n }\n\n return () => (\n <>\n <TimeField\n {...rest}\n _value={value}\n granularity={granularity}\n valueType={valueType}\n min={min}\n max={max}\n isTimeUnavailable={isTimeUnavailable}\n hourCycle={hourCycle}\n id={id}\n disabled={disabled}\n readonly={readonly}\n invalid={invalid}\n required={required}\n onKeydown={(event) => {\n onKeydown?.(event);\n picker.onKeydown(event);\n }}\n propsRoot={mergePartProps<PartProps<'div'>>({}, propsRoot, { ref: picker.anchorRef })}\n suffix={picker.button(Clock, propsButton)}\n />\n {picker.panel(\n timePickerStyles.popover,\n propsPopover,\n open && [\n <Listbox\n {...mergePartProps<ListboxProps>(\n { sx: timePickerStyles.list, 'aria-label': lang.timePickerOpen, items: options },\n propsList,\n // 在 Field 里时 Listbox 默认取 Field 的控件 id:那是输入框的,列表要自己的 id\n {\n id: `${picker.id}-list`,\n items: options,\n _value: selected,\n onPick: (iso) => write(parseIsoTime(iso)!),\n required: true,\n },\n )}\n />,\n <Button\n {...mergePartProps<ButtonProps>(\n { sx: timePickerStyles.now, variant: 'ghost', size: 'sm', fullWidth: true },\n propsNow,\n {\n disabled: !nowAvailable(),\n onClick: (event: MouseEvent) => write(nowTime(), event),\n },\n )}\n >\n {lang.timePickerNow}\n </Button>,\n ],\n )}\n </>\n );\n },\n);\n"],"mappings":"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"}
|
|
@@ -71,7 +71,7 @@ function useDateSegments(config) {
|
|
|
71
71
|
const wanted = new Set(fields.value);
|
|
72
72
|
return parts.filter((part) => part.type === "literal" || wanted.has(part.type)).map((part) => ({
|
|
73
73
|
type: part.type,
|
|
74
|
-
literal: part.value
|
|
74
|
+
literal: part.type === "literal" ? part.value.replace(/[ ]/g, " ") : part.value
|
|
75
75
|
})).filter((part, index, all) => part.type !== "literal" || part.literal.trim() !== "" || index > 0 && index < all.length - 1);
|
|
76
76
|
});
|
|
77
77
|
const labels = computed(() => {
|