dou-ui 0.1.0-alpha.1 → 0.1.0-alpha.3
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/components/alert/alert.js.map +1 -1
- package/dist/components/alert/alert.vue_vue_type_script_setup_true_lang.js +5 -6
- package/dist/components/alert/alert.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/button/button.js.map +1 -1
- package/dist/components/button/button.vue_vue_type_script_setup_true_lang.js +2 -1
- package/dist/components/button/button.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/calendar-view/calendar-view.js.map +1 -1
- package/dist/components/calendar-view/calendar-view.vue_vue_type_script_setup_true_lang.js +7 -2
- package/dist/components/calendar-view/calendar-view.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/cascader/cascader.js.map +1 -1
- package/dist/components/cascader/cascader.vue_vue_type_script_setup_true_lang.js +3 -3
- package/dist/components/cascader/cascader.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/chat-artifact/chat-artifact.js.map +1 -1
- package/dist/components/chat-artifact/chat-artifact.vue_vue_type_script_setup_true_lang.js +8 -2
- package/dist/components/chat-artifact/chat-artifact.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/chat-attachments/chat-attachments.js.map +1 -1
- package/dist/components/chat-attachments/chat-attachments.vue_vue_type_script_setup_true_lang.js +11 -2
- package/dist/components/chat-attachments/chat-attachments.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/chat-attachments/chat-attachments2.js.map +1 -1
- package/dist/components/chat-confirm/chat-confirm.js.map +1 -1
- package/dist/components/chat-confirm/chat-confirm.vue_vue_type_script_setup_true_lang.js +38 -14
- package/dist/components/chat-confirm/chat-confirm.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/chat-context-meter/chat-context-meter.vue_vue_type_script_setup_true_lang.js +2 -2
- package/dist/components/chat-context-meter/chat-context-meter.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/chat-context-meter/chat-context-meter2.js.map +1 -1
- package/dist/components/chat-message/chat-message.js.map +1 -1
- package/dist/components/chat-message/chat-message.vue_vue_type_script_setup_true_lang.js +88 -33
- package/dist/components/chat-message/chat-message.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/chat-message/chat-message2.js.map +1 -1
- package/dist/components/chat-message/style.js +5 -0
- package/dist/components/chat-plan/chat-plan.js +9 -4
- package/dist/components/chat-plan/chat-plan.js.map +1 -1
- package/dist/components/chat-plan/chat-plan.vue_vue_type_script_setup_true_lang.js +64 -32
- package/dist/components/chat-plan/chat-plan.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/chat-plan/chat-plan2.js +6 -0
- package/dist/components/chat-plan/chat-plan2.js.map +1 -0
- package/dist/components/chat-plan/index.js +1 -1
- package/dist/components/chat-plan/style.js +1 -0
- package/dist/components/chat-reasoning/chat-reasoning.js.map +1 -1
- package/dist/components/chat-reasoning/chat-reasoning.vue_vue_type_script_setup_true_lang.js +4 -4
- package/dist/components/chat-reasoning/chat-reasoning.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/chat-sources/chat-citation.js.map +1 -1
- package/dist/components/chat-sources/chat-citation.vue_vue_type_script_setup_true_lang.js +52 -14
- package/dist/components/chat-sources/chat-citation.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/chat-sources/chat-sources.js.map +1 -1
- package/dist/components/chat-sources/chat-sources.vue_vue_type_script_setup_true_lang.js +6 -4
- package/dist/components/chat-sources/chat-sources.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/chat-sources/chat-sources2.js.map +1 -1
- package/dist/components/chat-sources/index.js +1 -1
- package/dist/components/chat-steps/chat-steps.js +9 -4
- package/dist/components/chat-steps/chat-steps.js.map +1 -1
- package/dist/components/chat-steps/chat-steps.vue_vue_type_script_setup_true_lang.js +20 -13
- package/dist/components/chat-steps/chat-steps.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/chat-steps/chat-steps2.js +6 -0
- package/dist/components/chat-steps/chat-steps2.js.map +1 -0
- package/dist/components/chat-steps/index.js +1 -1
- package/dist/components/chat-steps/style.js +1 -0
- package/dist/components/chat-tool-call/chat-tool-call.js.map +1 -1
- package/dist/components/chat-tool-call/chat-tool-call.vue_vue_type_script_setup_true_lang.js +38 -12
- package/dist/components/chat-tool-call/chat-tool-call.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/chat-tool-call/chat-tool-call2.js.map +1 -1
- package/dist/components/chat-tool-call/style.js +1 -0
- package/dist/components/code-block/code-block.js.map +1 -1
- package/dist/components/code-block/code-block.vue_vue_type_script_setup_true_lang.js +37 -5
- package/dist/components/code-block/code-block.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/collapse/collapse-item.js.map +1 -1
- package/dist/components/collapse/collapse-item.vue_vue_type_script_setup_true_lang.js +2 -2
- package/dist/components/collapse/collapse-item.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/collapse/collapse-transition.js +3 -1
- package/dist/components/collapse/collapse-transition.js.map +1 -1
- package/dist/components/collapse/index.js +1 -2
- package/dist/components/collapse/index.js.map +1 -1
- package/dist/components/color-mode/color-mode-button.js.map +1 -1
- package/dist/components/color-mode/color-mode-button.vue_vue_type_script_setup_true_lang.js +2 -2
- package/dist/components/color-mode/color-mode-button.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/color-mode/color-mode-select.js.map +1 -1
- package/dist/components/color-mode/color-mode-select.vue_vue_type_script_setup_true_lang.js +2 -2
- package/dist/components/color-mode/color-mode-select.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/color-mode/color-mode-switch.js.map +1 -1
- package/dist/components/color-mode/color-mode-switch.vue_vue_type_script_setup_true_lang.js +2 -2
- package/dist/components/color-mode/color-mode-switch.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/color-panel/color-panel.js.map +1 -1
- package/dist/components/color-panel/color-panel.vue_vue_type_script_setup_true_lang.js +5 -5
- package/dist/components/color-panel/color-panel.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/color-picker/color-picker.js.map +1 -1
- package/dist/components/color-picker/color-picker.vue_vue_type_script_setup_true_lang.js +2 -2
- package/dist/components/color-picker/color-picker.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/comment-thread/comment-thread.js.map +1 -1
- package/dist/components/comment-thread/comment-thread.vue_vue_type_script_setup_true_lang.js +15 -15
- package/dist/components/comment-thread/comment-thread.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/comment-thread/style.js +2 -0
- package/dist/components/context-menu/context-menu.js.map +1 -1
- package/dist/components/context-menu/context-menu.vue_vue_type_script_setup_true_lang.js +26 -11
- package/dist/components/context-menu/context-menu.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/context-menu/index.js +2 -0
- package/dist/components/context-menu/index.js.map +1 -1
- package/dist/components/copy-button/copy-button.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/components/descriptions/descriptions.js.map +1 -1
- package/dist/components/descriptions/descriptions.vue_vue_type_script_setup_true_lang.js +3 -1
- package/dist/components/descriptions/descriptions.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/descriptions/descriptions2.js.map +1 -1
- package/dist/components/dropdown/dropdown-button.js.map +1 -1
- package/dist/components/dropdown/dropdown-button.vue_vue_type_script_setup_true_lang.js +5 -5
- package/dist/components/dropdown/dropdown-button.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/dropdown/dropdown-group.js +6 -0
- package/dist/components/dropdown/dropdown-group.js.map +1 -0
- package/dist/components/dropdown/dropdown-group.vue_vue_type_script_setup_true_lang.js +42 -0
- package/dist/components/dropdown/dropdown-group.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/dropdown/dropdown-item.js.map +1 -1
- package/dist/components/dropdown/dropdown-item.vue_vue_type_script_setup_true_lang.js +31 -9
- package/dist/components/dropdown/dropdown-item.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/dropdown/dropdown-items.js.map +1 -1
- package/dist/components/dropdown/dropdown-items.vue_vue_type_script_setup_true_lang.js +77 -52
- package/dist/components/dropdown/dropdown-items.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/dropdown/dropdown-radio-group.js.map +1 -1
- package/dist/components/dropdown/dropdown-radio-group.vue_vue_type_script_setup_true_lang.js +23 -5
- package/dist/components/dropdown/dropdown-radio-group.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/dropdown/dropdown-sub.js.map +1 -1
- package/dist/components/dropdown/dropdown-sub.vue_vue_type_script_setup_true_lang.js +5 -4
- package/dist/components/dropdown/dropdown-sub.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/dropdown/dropdown.js.map +1 -1
- package/dist/components/dropdown/dropdown.vue_vue_type_script_setup_true_lang.js +2 -1
- package/dist/components/dropdown/dropdown.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/dropdown/dropdown2.js.map +1 -1
- package/dist/components/dropdown/index.js +4 -5
- package/dist/components/dropdown/index.js.map +1 -1
- package/dist/components/dropdown/item-components.js +11 -0
- package/dist/components/dropdown/item-components.js.map +1 -0
- package/dist/components/dropdown/menu-entries.js +14 -0
- package/dist/components/dropdown/menu-entries.js.map +1 -0
- package/dist/components/dropdown/menu-keys.js +1 -1
- package/dist/components/dropdown/menu-keys.js.map +1 -1
- package/dist/components/ellipsis/ellipsis.js.map +1 -1
- package/dist/components/ellipsis/ellipsis.vue_vue_type_script_setup_true_lang.js +2 -2
- package/dist/components/ellipsis/ellipsis.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/image-cropper/image-cropper.js.map +1 -1
- package/dist/components/image-cropper/image-cropper.vue_vue_type_script_setup_true_lang.js +10 -10
- package/dist/components/image-cropper/image-cropper.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/infinite-scroll/infinite-scroll.js.map +1 -1
- package/dist/components/infinite-scroll/infinite-scroll.vue_vue_type_script_setup_true_lang.js +2 -2
- package/dist/components/infinite-scroll/infinite-scroll.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/input-date/segment-field.js.map +1 -1
- package/dist/components/input-date/segment-field.vue_vue_type_script_setup_true_lang.js +2 -2
- package/dist/components/input-date/segment-field.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/input-date/style.js +1 -0
- package/dist/components/input-time/style.js +1 -0
- package/dist/components/layout/layout-aside-trigger.js.map +1 -1
- package/dist/components/layout/layout-aside-trigger.vue_vue_type_script_setup_true_lang.js +2 -2
- package/dist/components/layout/layout-aside-trigger.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/layout/layout-aside.js.map +1 -1
- package/dist/components/layout/layout-aside.vue_vue_type_script_setup_true_lang.js +3 -3
- package/dist/components/layout/layout-aside.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/layout/layout-footer.js.map +1 -1
- package/dist/components/layout/layout-footer.vue_vue_type_script_setup_true_lang.js +5 -1
- package/dist/components/layout/layout-footer.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/layout/layout.js.map +1 -1
- package/dist/components/markdown/index.js +9 -0
- package/dist/components/markdown/index.js.map +1 -0
- package/dist/components/markdown/markdown.js +6 -0
- package/dist/components/markdown/markdown.js.map +1 -0
- package/dist/components/markdown/markdown.vue_vue_type_script_setup_true_lang.js +225 -0
- package/dist/components/markdown/markdown.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/markdown/parse-block.js +577 -0
- package/dist/components/markdown/parse-block.js.map +1 -0
- package/dist/components/markdown/parse-inline.js +626 -0
- package/dist/components/markdown/parse-inline.js.map +1 -0
- package/dist/components/markdown/parser.js +308 -0
- package/dist/components/markdown/parser.js.map +1 -0
- package/dist/components/markdown/render.js +424 -0
- package/dist/components/markdown/render.js.map +1 -0
- package/dist/components/markdown/style.d.ts +1 -0
- package/dist/components/markdown/style.js +10 -0
- package/dist/components/markdown/url.js +16 -0
- package/dist/components/markdown/url.js.map +1 -0
- package/dist/components/menu/menu-item.js.map +1 -1
- package/dist/components/menu/menu-item.vue_vue_type_script_setup_true_lang.js +3 -2
- package/dist/components/menu/menu-item.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/menu/menu-list.js.map +1 -1
- package/dist/components/menu/menu-list.vue_vue_type_script_setup_true_lang.js +2 -2
- package/dist/components/menu/menu-list.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/menu/menu-popup.js.map +1 -1
- package/dist/components/menu/menu-popup.vue_vue_type_script_setup_true_lang.js +33 -21
- package/dist/components/menu/menu-popup.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/menu/menu.js +16 -13
- package/dist/components/menu/menu.js.map +1 -1
- package/dist/components/menu/menu.vue_vue_type_script_setup_true_lang.js +4 -1
- package/dist/components/menu/menu.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/menu/menu2.js.map +1 -1
- package/dist/components/menubar/menubar.js.map +1 -1
- package/dist/components/node-graph/layout.js +136 -31
- package/dist/components/node-graph/layout.js.map +1 -1
- package/dist/components/node-graph/node-graph-legend.js.map +1 -1
- package/dist/components/node-graph/node-graph-legend.vue_vue_type_script_setup_true_lang.js +5 -5
- package/dist/components/node-graph/node-graph-legend.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/node-graph/node-graph.js.map +1 -1
- package/dist/components/node-graph/node-graph.vue_vue_type_script_setup_true_lang.js +3 -3
- package/dist/components/node-graph/node-graph.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/pagination/pagination-gap.js.map +1 -1
- package/dist/components/pagination/pagination-gap.vue_vue_type_script_setup_true_lang.js +2 -2
- package/dist/components/pagination/pagination-gap.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/pagination/pagination.js.map +1 -1
- package/dist/components/pagination/pagination.vue_vue_type_script_setup_true_lang.js +7 -7
- package/dist/components/pagination/pagination.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/popconfirm/index.js +2 -0
- package/dist/components/popconfirm/index.js.map +1 -1
- package/dist/components/popconfirm/popconfirm.js.map +1 -1
- package/dist/components/popconfirm/popconfirm.vue_vue_type_script_setup_true_lang.js +6 -2
- package/dist/components/popconfirm/popconfirm.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/popover/index.js +2 -0
- package/dist/components/popover/index.js.map +1 -1
- package/dist/components/popover/popover.js.map +1 -1
- package/dist/components/popover/popover.vue_vue_type_script_setup_true_lang.js +12 -5
- package/dist/components/popover/popover.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/progress-group/progress-group.js.map +1 -1
- package/dist/components/progress-group/progress-group.vue_vue_type_script_setup_true_lang.js +11 -4
- package/dist/components/progress-group/progress-group.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/qrcode/qrcode.js.map +1 -1
- package/dist/components/qrcode/qrcode.vue_vue_type_script_setup_true_lang.js +10 -2
- package/dist/components/qrcode/qrcode.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/select/select.js.map +1 -1
- package/dist/components/select/select.vue_vue_type_script_setup_true_lang.js +2 -2
- package/dist/components/select/select.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/slider/slider.js.map +1 -1
- package/dist/components/slider/slider.vue_vue_type_script_setup_true_lang.js +2 -2
- package/dist/components/slider/slider.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/status-dot/index.js +7 -0
- package/dist/components/status-dot/index.js.map +1 -0
- package/dist/components/status-dot/status-dot.js +26 -0
- package/dist/components/status-dot/status-dot.js.map +1 -0
- package/dist/components/status-dot/status-dot.vue_vue_type_script_lang.js +43 -0
- package/dist/components/status-dot/status-dot.vue_vue_type_script_lang.js.map +1 -0
- package/dist/components/status-dot/status-dot2.js +6 -0
- package/dist/components/status-dot/status-dot2.js.map +1 -0
- package/dist/components/status-dot/style.d.ts +1 -0
- package/dist/components/status-dot/style.js +2 -0
- package/dist/components/steps/steps.js.map +1 -1
- package/dist/components/steps/steps.vue_vue_type_script_setup_true_lang.js +6 -6
- package/dist/components/steps/steps.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/table/table.js.map +1 -1
- package/dist/components/table/table.vue_vue_type_script_setup_true_lang.js +19 -14
- package/dist/components/table/table.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/tabs/tab-panel.vue_vue_type_script_setup_true_lang.js +1 -0
- package/dist/components/tabs/tab-panel.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/tabs/tabs.js.map +1 -1
- package/dist/components/tabs/tabs.vue_vue_type_script_setup_true_lang.js +117 -47
- package/dist/components/tabs/tabs.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/tabs/tabs2.js.map +1 -1
- package/dist/components/tag/tag.js.map +1 -1
- package/dist/components/tag/tag.vue_vue_type_script_setup_true_lang.js +21 -4
- package/dist/components/tag/tag.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/timeline/style.js +1 -0
- package/dist/components/timeline/timeline-item.js.map +1 -1
- package/dist/components/timeline/timeline-item.vue_vue_type_script_setup_true_lang.js +6 -2
- package/dist/components/timeline/timeline-item.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/tooltip/index.js +2 -0
- package/dist/components/tooltip/index.js.map +1 -1
- package/dist/components/tooltip/tooltip.js.map +1 -1
- package/dist/components/tooltip/tooltip.vue_vue_type_script_setup_true_lang.js +55 -8
- package/dist/components/tooltip/tooltip.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/tour/tour.js.map +1 -1
- package/dist/components/tour/tour.vue_vue_type_script_setup_true_lang.js +3 -3
- package/dist/components/tour/tour.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/transfer/transfer-list.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/components/tree/tree.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/components/tree-select/tree-select.js.map +1 -1
- package/dist/components/tree-select/tree-select.vue_vue_type_script_setup_true_lang.js +3 -3
- package/dist/components/tree-select/tree-select.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/upload/upload.vue_vue_type_script_setup_true_lang.js +2 -2
- package/dist/components/upload/upload.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/upload/upload2.js.map +1 -1
- package/dist/components/user/user.js.map +1 -1
- package/dist/components/user/user.vue_vue_type_script_setup_true_lang.js +2 -2
- package/dist/components/user/user.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/composables/use-popup.js +30 -5
- package/dist/composables/use-popup.js.map +1 -1
- package/dist/index.js +23 -17
- package/dist/index.js.map +1 -1
- package/dist/locale/en.js +13 -8
- package/dist/locale/en.js.map +1 -1
- package/dist/locale/zh-cn.js +13 -8
- package/dist/locale/zh-cn.js.map +1 -1
- package/dist/locale/zh-tw.js +13 -8
- package/dist/locale/zh-tw.js.map +1 -1
- package/dist/plugin.js +21 -14
- package/dist/plugin.js.map +1 -1
- package/dist/style.css +1 -1
- package/dist/types/components/button/button.d.ts +5 -1
- package/dist/types/components/calendar-view/calendar-view.d.ts +6 -0
- package/dist/types/components/calendar-view/calendar-view.vue.d.ts +1 -0
- package/dist/types/components/calendar-view/index.d.ts +3 -0
- package/dist/types/components/chat-artifact/chat-artifact.d.ts +2 -1
- package/dist/types/components/chat-attachments/chat-attachments.d.ts +2 -2
- package/dist/types/components/chat-confirm/chat-confirm.d.ts +7 -2
- package/dist/types/components/chat-list/index.d.ts +3 -3
- package/dist/types/components/chat-message/chat-message.d.ts +26 -3
- package/dist/types/components/chat-message/chat-message.vue.d.ts +4 -3
- package/dist/types/components/chat-message/index.d.ts +7 -4
- package/dist/types/components/chat-plan/chat-plan.d.ts +19 -2
- package/dist/types/components/chat-plan/chat-plan.vue.d.ts +2 -0
- package/dist/types/components/chat-plan/index.d.ts +6 -0
- package/dist/types/components/chat-sources/chat-sources.d.ts +6 -1
- package/dist/types/components/chat-steps/chat-steps.d.ts +10 -4
- package/dist/types/components/chat-steps/chat-steps.vue.d.ts +1 -1
- package/dist/types/components/chat-steps/index.d.ts +3 -3
- package/dist/types/components/chat-tool-call/chat-tool-call.d.ts +12 -3
- package/dist/types/components/chat-tool-call/chat-tool-call.vue.d.ts +1 -0
- package/dist/types/components/chat-tool-call/index.d.ts +3 -0
- package/dist/types/components/checkbox/index.d.ts +5 -5
- package/dist/types/components/code-block/code-block.d.ts +11 -0
- package/dist/types/components/code-block/code-block.vue.d.ts +3 -1
- package/dist/types/components/code-block/index.d.ts +9 -3
- package/dist/types/components/collapse/collapse-transition.d.ts +2 -7
- package/dist/types/components/collapse/collapse.vue.d.ts +1 -1
- package/dist/types/components/collapse/index.d.ts +5 -6
- package/dist/types/components/context-menu/context-menu.d.ts +37 -4
- package/dist/types/components/context-menu/context-menu.vue.d.ts +2 -0
- package/dist/types/components/context-menu/index.d.ts +5 -0
- package/dist/types/components/copy-button/copy-button.vue.d.ts +1 -1
- package/dist/types/components/copy-button/index.d.ts +1 -1
- package/dist/types/components/descriptions/descriptions.d.ts +9 -1
- package/dist/types/components/descriptions/descriptions.vue.d.ts +1 -0
- package/dist/types/components/descriptions/index.d.ts +3 -0
- package/dist/types/components/dropdown/dropdown-group.vue.d.ts +13 -0
- package/dist/types/components/dropdown/dropdown-items.vue.d.ts +2 -2
- package/dist/types/components/dropdown/dropdown-radio-group.vue.d.ts +8 -4
- package/dist/types/components/dropdown/dropdown.d.ts +28 -6
- package/dist/types/components/dropdown/dropdown.vue.d.ts +1 -1
- package/dist/types/components/dropdown/index.d.ts +10 -81
- package/dist/types/components/dropdown/item-components.d.ts +103 -0
- package/dist/types/components/dropdown/menu-entries.d.ts +4 -0
- package/dist/types/components/index.d.ts +2 -0
- package/dist/types/components/layout/index.d.ts +12 -6
- package/dist/types/components/layout/layout-aside.vue.d.ts +1 -1
- package/dist/types/components/layout/layout-footer.vue.d.ts +4 -2
- package/dist/types/components/layout/layout.d.ts +4 -0
- package/dist/types/components/markdown/index.d.ts +58 -0
- package/dist/types/components/markdown/markdown.d.ts +251 -0
- package/dist/types/components/markdown/markdown.vue.d.ts +22 -0
- package/dist/types/components/markdown/parse-block.d.ts +61 -0
- package/dist/types/components/markdown/parse-inline.d.ts +19 -0
- package/dist/types/components/markdown/parser.d.ts +22 -0
- package/dist/types/components/markdown/render.d.ts +84 -0
- package/dist/types/components/markdown/url.d.ts +10 -0
- package/dist/types/components/masonry/index.d.ts +3 -3
- package/dist/types/components/menu/index.d.ts +8 -3
- package/dist/types/components/menu/menu.d.ts +16 -5
- package/dist/types/components/menu/menu.vue.d.ts +3 -1
- package/dist/types/components/menubar/menubar.d.ts +1 -1
- package/dist/types/components/node-graph/layout.d.ts +16 -0
- package/dist/types/components/node-graph/node-graph.d.ts +1 -1
- package/dist/types/components/popover/index.d.ts +3 -3
- package/dist/types/components/popover/popover.vue.d.ts +1 -1
- package/dist/types/components/progress-group/index.d.ts +3 -0
- package/dist/types/components/progress-group/progress-group.d.ts +7 -0
- package/dist/types/components/progress-group/progress-group.vue.d.ts +1 -0
- package/dist/types/components/qrcode/index.d.ts +3 -0
- package/dist/types/components/qrcode/qrcode.d.ts +5 -0
- package/dist/types/components/qrcode/qrcode.vue.d.ts +1 -0
- package/dist/types/components/sortable/index.d.ts +3 -3
- package/dist/types/components/status-dot/index.d.ts +51 -0
- package/dist/types/components/status-dot/status-dot.d.ts +46 -0
- package/dist/types/components/status-dot/status-dot.vue.d.ts +46 -0
- package/dist/types/components/steps/steps.d.ts +1 -1
- package/dist/types/components/switch/index.d.ts +3 -3
- package/dist/types/components/table/index.d.ts +4 -3
- package/dist/types/components/table/table.d.ts +6 -1
- package/dist/types/components/tabs/index.d.ts +3 -0
- package/dist/types/components/tabs/tabs.d.ts +16 -3
- package/dist/types/components/tabs/tabs.vue.d.ts +1 -0
- package/dist/types/components/timeline/index.d.ts +3 -3
- package/dist/types/components/timeline/timeline-item.vue.d.ts +1 -1
- package/dist/types/components/virtual-list/index.d.ts +3 -3
- package/dist/types/composables/use-popup.d.ts +15 -5
- package/dist/types/locale/types.d.ts +18 -7
- package/dist/types/utils/only-child.d.ts +3 -1
- package/dist/types/utils/pointer.d.ts +5 -0
- package/dist/types/utils/safe-url.d.ts +14 -0
- package/dist/types/utils/status-check.d.ts +8 -0
- package/dist/utils/install.js +5 -0
- package/dist/utils/install.js.map +1 -1
- package/dist/utils/only-child.js +56 -5
- package/dist/utils/only-child.js.map +1 -1
- package/dist/utils/pointer.js +6 -1
- package/dist/utils/pointer.js.map +1 -1
- package/dist/utils/safe-url.js +57 -0
- package/dist/utils/safe-url.js.map +1 -0
- package/dist/utils/status-check.js +27 -0
- package/dist/utils/status-check.js.map +1 -0
- package/package.json +3 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"alert.js","names":["_sfc_main"],"sources":["../../../src/components/alert/alert.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { CloseIcon } from '@dou-ui/icons'\nimport { computed, ref } from 'vue'\nimport { useNamespace } from '../../utils/namespace'\nimport { toneIcon } from '../../utils/tone-icons'\nimport { useLocale } from '../config-provider/use-config'\nimport type { AlertEmits, AlertProps, AlertSlots } from './alert'\n\ndefineOptions({ name: 'DouAlert' })\n\nconst props = withDefaults(defineProps<AlertProps>(), {\n tone: 'neutral',\n closable: false,\n showIcon: true,\n})\nconst emit = defineEmits<AlertEmits>()\nconst slots = defineSlots<AlertSlots>()\n\nconst ns = useNamespace('alert')\nconst { t } = useLocale()\nconst visible = ref(true)\n\n// Warnings and errors interrupt a screen reader; everything else waits its turn.\nconst role = computed(() =>\n props.tone === 'warning' || props.tone === 'danger' ? 'alert' : 'status',\n)\nconst hasTitle = computed(() => !!props.title || !!slots.title)\nconst Icon = () => toneIcon(props.tone)\n\nfunction close(event: MouseEvent) {\n visible.value = false\n emit('close', event)\n}\n</script>\n\n<template>\n <div v-if=\"visible\" :class=\"[ns.b(), ns.m(tone), ns.is('titled', hasTitle)]\" :role=\"role\">\n <span v-if=\"showIcon\" :class=\"ns.e('icon')\">\n <slot name=\"icon\"><Icon /></slot>\n </span>\n <div :class=\"ns.e('
|
|
1
|
+
{"version":3,"file":"alert.js","names":["_sfc_main"],"sources":["../../../src/components/alert/alert.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { CloseIcon } from '@dou-ui/icons'\nimport { computed, ref } from 'vue'\nimport { useNamespace } from '../../utils/namespace'\nimport { toneIcon } from '../../utils/tone-icons'\nimport { useLocale } from '../config-provider/use-config'\nimport type { AlertEmits, AlertProps, AlertSlots } from './alert'\n\ndefineOptions({ name: 'DouAlert' })\n\nconst props = withDefaults(defineProps<AlertProps>(), {\n tone: 'neutral',\n closable: false,\n showIcon: true,\n})\nconst emit = defineEmits<AlertEmits>()\nconst slots = defineSlots<AlertSlots>()\n\nconst ns = useNamespace('alert')\nconst { t } = useLocale()\nconst visible = ref(true)\n\n// Warnings and errors interrupt a screen reader; everything else waits its turn.\nconst role = computed(() =>\n props.tone === 'warning' || props.tone === 'danger' ? 'alert' : 'status',\n)\nconst hasTitle = computed(() => !!props.title || !!slots.title)\nconst Icon = () => toneIcon(props.tone)\n\nfunction close(event: MouseEvent) {\n visible.value = false\n emit('close', event)\n}\n</script>\n\n<template>\n <div v-if=\"visible\" :class=\"[ns.b(), ns.m(tone), ns.is('titled', hasTitle)]\" :role=\"role\">\n <span v-if=\"showIcon\" :class=\"ns.e('icon')\">\n <slot name=\"icon\"><Icon /></slot>\n </span>\n <!-- text and actions share a wrapping row: when the text would get\n narrower than --dou-alert-text-min, the actions drop below it -->\n <div :class=\"ns.e('body')\">\n <div :class=\"ns.e('content')\">\n <div v-if=\"hasTitle\" :class=\"ns.e('title')\">\n <slot name=\"title\">{{ title }}</slot>\n </div>\n <div v-if=\"slots.default\" :class=\"ns.e('description')\"><slot /></div>\n </div>\n <div v-if=\"slots.action\" :class=\"ns.e('action')\"><slot name=\"action\" /></div>\n </div>\n <button\n v-if=\"closable\"\n type=\"button\"\n :class=\"ns.e('close')\"\n :aria-label=\"t('alert.close')\"\n @click=\"close\"\n >\n <CloseIcon />\n </button>\n </div>\n</template>\n"],"mappings":""}
|
|
@@ -50,19 +50,18 @@ var alert_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineComp
|
|
|
50
50
|
key: 0,
|
|
51
51
|
class: normalizeClass(unref(ns).e("icon"))
|
|
52
52
|
}, [renderSlot(_ctx.$slots, "icon", {}, () => [createVNode(Icon)])], 2)) : createCommentVNode("", true),
|
|
53
|
-
createElementVNode("div", { class: normalizeClass(unref(ns).e("content")) }, [hasTitle.value ? (openBlock(), createElementBlock("div", {
|
|
53
|
+
createElementVNode("div", { class: normalizeClass(unref(ns).e("body")) }, [createElementVNode("div", { class: normalizeClass(unref(ns).e("content")) }, [hasTitle.value ? (openBlock(), createElementBlock("div", {
|
|
54
54
|
key: 0,
|
|
55
55
|
class: normalizeClass(unref(ns).e("title"))
|
|
56
56
|
}, [renderSlot(_ctx.$slots, "title", {}, () => [createTextVNode(toDisplayString(__props.title), 1)])], 2)) : createCommentVNode("", true), slots.default ? (openBlock(), createElementBlock("div", {
|
|
57
57
|
key: 1,
|
|
58
58
|
class: normalizeClass(unref(ns).e("description"))
|
|
59
|
-
}, [renderSlot(_ctx.$slots, "default")], 2)) : createCommentVNode("", true)], 2),
|
|
60
|
-
|
|
61
|
-
key: 1,
|
|
59
|
+
}, [renderSlot(_ctx.$slots, "default")], 2)) : createCommentVNode("", true)], 2), slots.action ? (openBlock(), createElementBlock("div", {
|
|
60
|
+
key: 0,
|
|
62
61
|
class: normalizeClass(unref(ns).e("action"))
|
|
63
|
-
}, [renderSlot(_ctx.$slots, "action")], 2)) : createCommentVNode("", true),
|
|
62
|
+
}, [renderSlot(_ctx.$slots, "action")], 2)) : createCommentVNode("", true)], 2),
|
|
64
63
|
__props.closable ? (openBlock(), createElementBlock("button", {
|
|
65
|
-
key:
|
|
64
|
+
key: 1,
|
|
66
65
|
type: "button",
|
|
67
66
|
class: normalizeClass(unref(ns).e("close")),
|
|
68
67
|
"aria-label": unref(t)("alert.close"),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"alert.vue_vue_type_script_setup_true_lang.js","names":["_useSlots","_openBlock","_createElementBlock","_normalizeClass","_unref","_renderSlot","_createVNode","_createCommentVNode","_createElementVNode","_createTextVNode","_toDisplayString"],"sources":["../../../src/components/alert/alert.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { CloseIcon } from '@dou-ui/icons'\nimport { computed, ref } from 'vue'\nimport { useNamespace } from '../../utils/namespace'\nimport { toneIcon } from '../../utils/tone-icons'\nimport { useLocale } from '../config-provider/use-config'\nimport type { AlertEmits, AlertProps, AlertSlots } from './alert'\n\ndefineOptions({ name: 'DouAlert' })\n\nconst props = withDefaults(defineProps<AlertProps>(), {\n tone: 'neutral',\n closable: false,\n showIcon: true,\n})\nconst emit = defineEmits<AlertEmits>()\nconst slots = defineSlots<AlertSlots>()\n\nconst ns = useNamespace('alert')\nconst { t } = useLocale()\nconst visible = ref(true)\n\n// Warnings and errors interrupt a screen reader; everything else waits its turn.\nconst role = computed(() =>\n props.tone === 'warning' || props.tone === 'danger' ? 'alert' : 'status',\n)\nconst hasTitle = computed(() => !!props.title || !!slots.title)\nconst Icon = () => toneIcon(props.tone)\n\nfunction close(event: MouseEvent) {\n visible.value = false\n emit('close', event)\n}\n</script>\n\n<template>\n <div v-if=\"visible\" :class=\"[ns.b(), ns.m(tone), ns.is('titled', hasTitle)]\" :role=\"role\">\n <span v-if=\"showIcon\" :class=\"ns.e('icon')\">\n <slot name=\"icon\"><Icon /></slot>\n </span>\n <div :class=\"ns.e('
|
|
1
|
+
{"version":3,"file":"alert.vue_vue_type_script_setup_true_lang.js","names":["_useSlots","_openBlock","_createElementBlock","_normalizeClass","_unref","_renderSlot","_createVNode","_createCommentVNode","_createElementVNode","_createTextVNode","_toDisplayString"],"sources":["../../../src/components/alert/alert.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { CloseIcon } from '@dou-ui/icons'\nimport { computed, ref } from 'vue'\nimport { useNamespace } from '../../utils/namespace'\nimport { toneIcon } from '../../utils/tone-icons'\nimport { useLocale } from '../config-provider/use-config'\nimport type { AlertEmits, AlertProps, AlertSlots } from './alert'\n\ndefineOptions({ name: 'DouAlert' })\n\nconst props = withDefaults(defineProps<AlertProps>(), {\n tone: 'neutral',\n closable: false,\n showIcon: true,\n})\nconst emit = defineEmits<AlertEmits>()\nconst slots = defineSlots<AlertSlots>()\n\nconst ns = useNamespace('alert')\nconst { t } = useLocale()\nconst visible = ref(true)\n\n// Warnings and errors interrupt a screen reader; everything else waits its turn.\nconst role = computed(() =>\n props.tone === 'warning' || props.tone === 'danger' ? 'alert' : 'status',\n)\nconst hasTitle = computed(() => !!props.title || !!slots.title)\nconst Icon = () => toneIcon(props.tone)\n\nfunction close(event: MouseEvent) {\n visible.value = false\n emit('close', event)\n}\n</script>\n\n<template>\n <div v-if=\"visible\" :class=\"[ns.b(), ns.m(tone), ns.is('titled', hasTitle)]\" :role=\"role\">\n <span v-if=\"showIcon\" :class=\"ns.e('icon')\">\n <slot name=\"icon\"><Icon /></slot>\n </span>\n <!-- text and actions share a wrapping row: when the text would get\n narrower than --dou-alert-text-min, the actions drop below it -->\n <div :class=\"ns.e('body')\">\n <div :class=\"ns.e('content')\">\n <div v-if=\"hasTitle\" :class=\"ns.e('title')\">\n <slot name=\"title\">{{ title }}</slot>\n </div>\n <div v-if=\"slots.default\" :class=\"ns.e('description')\"><slot /></div>\n </div>\n <div v-if=\"slots.action\" :class=\"ns.e('action')\"><slot name=\"action\" /></div>\n </div>\n <button\n v-if=\"closable\"\n type=\"button\"\n :class=\"ns.e('close')\"\n :aria-label=\"t('alert.close')\"\n @click=\"close\"\n >\n <CloseIcon />\n </button>\n </div>\n</template>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;EAUA,MAAM,QAAQ;EAKd,MAAM,OAAO;EACb,MAAM,QAAQA,SAAA;EAEd,MAAM,KAAK,aAAa,OAAO;EAC/B,MAAM,EAAE,MAAM,UAAU;EACxB,MAAM,UAAU,IAAI,IAAI;EAGxB,MAAM,OAAO,eACX,MAAM,SAAS,aAAa,MAAM,SAAS,WAAW,UAAU,QAClE;EACA,MAAM,WAAW,eAAe,CAAC,CAAC,MAAM,SAAS,CAAC,CAAC,MAAM,KAAK;EAC9D,MAAM,aAAa,SAAS,MAAM,IAAI;EAEtC,SAAS,MAAM,OAAmB;GAChC,QAAQ,QAAQ;GAChB,KAAK,SAAS,KAAK;EACrB;;GAIa,OAAA,QAAA,SAAXC,UAAA,GAAAC,mBAwBM,OAAA;;IAxBe,OAAKC,eAAA;KAAGC,MAAA,EAAA,CAAE,CAAC,EAAC;KAAIA,MAAA,EAAA,CAAE,CAAC,EAAE,QAAA,IAAI;KAAGA,MAAA,EAAA,CAAE,CAAC,GAAE,UAAW,SAAA,KAAQ;IAAA,CAAA;IAAK,MAAM,KAAA;;IACtE,QAAA,YAAZH,UAAA,GAAAC,mBAEO,QAAA;;KAFgB,OAAKC,eAAEC,MAAA,EAAA,CAAE,CAAC,EAAC,MAAA,CAAA;I,IAChCC,WAAiC,KAAA,QAAA,QAAA,CAAA,SAAA,CAAfC,YAAQ,IAAA,C;IAI5BE,mBAQM,OAAA,EARA,OAAKL,eAAEC,MAAA,EAAA,CAAE,CAAC,EAAC,MAAA,CAAA,E,IACfI,mBAKM,OAAA,EALA,OAAKL,eAAEC,MAAA,EAAA,CAAE,CAAC,EAAC,SAAA,CAAA,E,IACJ,SAAA,SAAXH,UAAA,GAAAC,mBAEM,OAAA;;KAFgB,OAAKC,eAAEC,MAAA,EAAA,CAAE,CAAC,EAAC,OAAA,CAAA;I,IAC/BC,WAAqC,KAAA,QAAA,SAAA,CAAA,SAAA,CAAfI,gBAAAC,gBAAA,QAAA,KAAK,GAAA,CAAA,C,yCAElB,MAAM,WAAjBT,UAAA,GAAAC,mBAAqE,OAAA;;KAA1C,OAAKC,eAAEC,MAAA,EAAA,CAAE,CAAC,EAAC,aAAA,CAAA;I,IAAiBC,WAAQ,KAAA,QAAA,SAAA,C,4CAEtD,MAAM,UAAjBJ,UAAA,GAAAC,mBAA6E,OAAA;;KAAnD,OAAKC,eAAEC,MAAA,EAAA,CAAE,CAAC,EAAC,QAAA,CAAA;I,IAAYC,WAAsB,KAAA,QAAA,QAAA,C;IAGjE,QAAA,YADRJ,UAAA,GAAAC,mBAQS,UAAA;;KANP,MAAK;KACJ,OAAKC,eAAEC,MAAA,EAAA,CAAE,CAAC,EAAC,OAAA,CAAA;KACX,cAAYA,MAAA,CAAA,CAAC,CAAA,aAAA;KACb,SAAO;I,IAERE,YAAaF,MAAA,SAAA,CAAA,C"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"button.js","names":["_sfc_main"],"sources":["../../../src/components/button/button.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { Comment, Fragment, Text, computed, isVNode } from 'vue'\nimport { useNamespace } from '../../utils/namespace'\nimport { useFormDisabled, useFormSize } from '../form/use-form-item'\nimport type { ButtonEmits, ButtonProps, ButtonSlots } from './button'\n\ndefineOptions({ name: 'DouButton' })\n\nconst props = withDefaults(defineProps<ButtonProps>(), {\n variant: 'solid',\n tone: 'neutral',\n disabled: false,\n loading: false,\n block: false,\n nativeType: 'button',\n})\nconst emit = defineEmits<ButtonEmits>()\nconst slots = defineSlots<ButtonSlots>()\n\nconst ns = useNamespace('button')\n\nconst
|
|
1
|
+
{"version":3,"file":"button.js","names":["_sfc_main"],"sources":["../../../src/components/button/button.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { Comment, Fragment, Text, computed, isVNode } from 'vue'\nimport { useNamespace } from '../../utils/namespace'\nimport { useFormDisabled, useFormSize } from '../form/use-form-item'\nimport type { ButtonEmits, ButtonProps, ButtonSlots } from './button'\n\ndefineOptions({ name: 'DouButton' })\n\nconst props = withDefaults(defineProps<ButtonProps>(), {\n variant: 'solid',\n tone: 'neutral',\n disabled: false,\n loading: false,\n block: false,\n nativeType: 'button',\n})\nconst emit = defineEmits<ButtonEmits>()\nconst slots = defineSlots<ButtonSlots>()\n\nconst ns = useNamespace('button')\n\n// xs exists only on buttons, so a form or field group never sets it\nconst formSize = useFormSize(() => (props.size === 'xs' ? undefined : props.size))\nconst size = computed(() => (props.size === 'xs' ? 'xs' : formSize.value))\nconst disabled = useFormDisabled(() => props.disabled)\nconst inert = computed(() => disabled.value || props.loading)\n\nconst classes = computed(() => [\n ns.b(),\n ns.m(props.variant),\n ns.m(props.tone),\n ns.m(size.value),\n ns.is('loading', props.loading),\n ns.is('block', props.block),\n])\n\n// An icon passed as the only content of the default slot goes where the\n// leading icon would, so it is centered and the button turns square.\n// Components and svg/img count as icons; anything with text does not.\nfunction hasText(nodes: unknown): boolean {\n return (Array.isArray(nodes) ? nodes : [nodes]).some((node) => {\n if (!isVNode(node)) return typeof node === 'string' && node.trim() !== ''\n if (node.type === Comment) return false\n if (node.type === Text) return String(node.children).trim() !== ''\n if (\n node.type === Fragment ||\n (typeof node.type === 'string' && !['svg', 'img'].includes(node.type))\n )\n return hasText(node.children)\n return false\n })\n}\n\n// Read while rendering: slot content can change from one render to the next.\n/** The default slot holds only an icon, so it is sized and centered like one. */\nfunction iconLabel() {\n return !!slots.default && !hasText(slots.default())\n}\n\nfunction iconOnly() {\n if (slots.trailing) return false\n if (slots.default) return !slots.leading && iconLabel()\n return !!(slots.leading || props.loading)\n}\n\nfunction onClick(event: MouseEvent) {\n if (inert.value) return\n emit('click', event)\n}\n</script>\n\n<template>\n <button\n :class=\"[classes, ns.is('icon-only', iconOnly())]\"\n :type=\"nativeType\"\n :disabled=\"disabled\"\n :aria-disabled=\"loading || undefined\"\n :aria-busy=\"loading || undefined\"\n @click=\"onClick\"\n >\n <span v-if=\"loading\" :class=\"ns.e('icon')\" aria-hidden=\"true\">\n <span :class=\"ns.e('spinner')\" />\n </span>\n <span v-else-if=\"slots.leading\" :class=\"ns.e('icon')\">\n <slot name=\"leading\" />\n </span>\n <template v-if=\"slots.default\">\n <span v-if=\"!iconLabel()\" :class=\"ns.e('label')\"><slot /></span>\n <span v-else-if=\"!loading\" :class=\"ns.e('icon')\"><slot /></span>\n </template>\n <span v-if=\"slots.trailing\" :class=\"ns.e('icon')\">\n <slot name=\"trailing\" />\n </span>\n </button>\n</template>\n"],"mappings":""}
|
|
@@ -35,7 +35,8 @@ var button_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineCom
|
|
|
35
35
|
const emit = __emit;
|
|
36
36
|
const slots = useSlots();
|
|
37
37
|
const ns = useNamespace("button");
|
|
38
|
-
const
|
|
38
|
+
const formSize = useFormSize(() => props.size === "xs" ? void 0 : props.size);
|
|
39
|
+
const size = computed(() => props.size === "xs" ? "xs" : formSize.value);
|
|
39
40
|
const disabled = useFormDisabled(() => props.disabled);
|
|
40
41
|
const inert = computed(() => disabled.value || props.loading);
|
|
41
42
|
const classes = computed(() => [
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"button.vue_vue_type_script_setup_true_lang.js","names":["_useSlots","_openBlock","_createElementBlock","_normalizeClass","_unref","_createElementVNode","_renderSlot","_createCommentVNode","_Fragment"],"sources":["../../../src/components/button/button.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { Comment, Fragment, Text, computed, isVNode } from 'vue'\nimport { useNamespace } from '../../utils/namespace'\nimport { useFormDisabled, useFormSize } from '../form/use-form-item'\nimport type { ButtonEmits, ButtonProps, ButtonSlots } from './button'\n\ndefineOptions({ name: 'DouButton' })\n\nconst props = withDefaults(defineProps<ButtonProps>(), {\n variant: 'solid',\n tone: 'neutral',\n disabled: false,\n loading: false,\n block: false,\n nativeType: 'button',\n})\nconst emit = defineEmits<ButtonEmits>()\nconst slots = defineSlots<ButtonSlots>()\n\nconst ns = useNamespace('button')\n\nconst
|
|
1
|
+
{"version":3,"file":"button.vue_vue_type_script_setup_true_lang.js","names":["_useSlots","_openBlock","_createElementBlock","_normalizeClass","_unref","_createElementVNode","_renderSlot","_createCommentVNode","_Fragment"],"sources":["../../../src/components/button/button.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { Comment, Fragment, Text, computed, isVNode } from 'vue'\nimport { useNamespace } from '../../utils/namespace'\nimport { useFormDisabled, useFormSize } from '../form/use-form-item'\nimport type { ButtonEmits, ButtonProps, ButtonSlots } from './button'\n\ndefineOptions({ name: 'DouButton' })\n\nconst props = withDefaults(defineProps<ButtonProps>(), {\n variant: 'solid',\n tone: 'neutral',\n disabled: false,\n loading: false,\n block: false,\n nativeType: 'button',\n})\nconst emit = defineEmits<ButtonEmits>()\nconst slots = defineSlots<ButtonSlots>()\n\nconst ns = useNamespace('button')\n\n// xs exists only on buttons, so a form or field group never sets it\nconst formSize = useFormSize(() => (props.size === 'xs' ? undefined : props.size))\nconst size = computed(() => (props.size === 'xs' ? 'xs' : formSize.value))\nconst disabled = useFormDisabled(() => props.disabled)\nconst inert = computed(() => disabled.value || props.loading)\n\nconst classes = computed(() => [\n ns.b(),\n ns.m(props.variant),\n ns.m(props.tone),\n ns.m(size.value),\n ns.is('loading', props.loading),\n ns.is('block', props.block),\n])\n\n// An icon passed as the only content of the default slot goes where the\n// leading icon would, so it is centered and the button turns square.\n// Components and svg/img count as icons; anything with text does not.\nfunction hasText(nodes: unknown): boolean {\n return (Array.isArray(nodes) ? nodes : [nodes]).some((node) => {\n if (!isVNode(node)) return typeof node === 'string' && node.trim() !== ''\n if (node.type === Comment) return false\n if (node.type === Text) return String(node.children).trim() !== ''\n if (\n node.type === Fragment ||\n (typeof node.type === 'string' && !['svg', 'img'].includes(node.type))\n )\n return hasText(node.children)\n return false\n })\n}\n\n// Read while rendering: slot content can change from one render to the next.\n/** The default slot holds only an icon, so it is sized and centered like one. */\nfunction iconLabel() {\n return !!slots.default && !hasText(slots.default())\n}\n\nfunction iconOnly() {\n if (slots.trailing) return false\n if (slots.default) return !slots.leading && iconLabel()\n return !!(slots.leading || props.loading)\n}\n\nfunction onClick(event: MouseEvent) {\n if (inert.value) return\n emit('click', event)\n}\n</script>\n\n<template>\n <button\n :class=\"[classes, ns.is('icon-only', iconOnly())]\"\n :type=\"nativeType\"\n :disabled=\"disabled\"\n :aria-disabled=\"loading || undefined\"\n :aria-busy=\"loading || undefined\"\n @click=\"onClick\"\n >\n <span v-if=\"loading\" :class=\"ns.e('icon')\" aria-hidden=\"true\">\n <span :class=\"ns.e('spinner')\" />\n </span>\n <span v-else-if=\"slots.leading\" :class=\"ns.e('icon')\">\n <slot name=\"leading\" />\n </span>\n <template v-if=\"slots.default\">\n <span v-if=\"!iconLabel()\" :class=\"ns.e('label')\"><slot /></span>\n <span v-else-if=\"!loading\" :class=\"ns.e('icon')\"><slot /></span>\n </template>\n <span v-if=\"slots.trailing\" :class=\"ns.e('icon')\">\n <slot name=\"trailing\" />\n </span>\n </button>\n</template>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAQA,MAAM,QAAQ;EAQd,MAAM,OAAO;EACb,MAAM,QAAQA,SAAA;EAEd,MAAM,KAAK,aAAa,QAAQ;EAGhC,MAAM,WAAW,kBAAmB,MAAM,SAAS,OAAO,KAAA,IAAY,MAAM,IAAK;EACjF,MAAM,OAAO,eAAgB,MAAM,SAAS,OAAO,OAAO,SAAS,KAAM;EACzE,MAAM,WAAW,sBAAsB,MAAM,QAAQ;EACrD,MAAM,QAAQ,eAAe,SAAS,SAAS,MAAM,OAAO;EAE5D,MAAM,UAAU,eAAe;GAC7B,GAAG,EAAE;GACL,GAAG,EAAE,MAAM,OAAO;GAClB,GAAG,EAAE,MAAM,IAAI;GACf,GAAG,EAAE,KAAK,KAAK;GACf,GAAG,GAAG,WAAW,MAAM,OAAO;GAC9B,GAAG,GAAG,SAAS,MAAM,KAAK;EAC5B,CAAC;EAKD,SAAS,QAAQ,OAAyB;GACxC,QAAQ,MAAM,QAAQ,KAAK,IAAI,QAAQ,CAAC,KAAK,EAAA,CAAG,MAAM,SAAS;IAC7D,IAAI,CAAC,QAAQ,IAAI,GAAG,OAAO,OAAO,SAAS,YAAY,KAAK,KAAK,MAAM;IACvE,IAAI,KAAK,SAAS,SAAS,OAAO;IAClC,IAAI,KAAK,SAAS,MAAM,OAAO,OAAO,KAAK,QAAQ,CAAC,CAAC,KAAK,MAAM;IAChE,IACE,KAAK,SAAS,YACb,OAAO,KAAK,SAAS,YAAY,CAAC,CAAC,OAAO,KAAK,CAAC,CAAC,SAAS,KAAK,IAAI,GAEpE,OAAO,QAAQ,KAAK,QAAQ;IAC9B,OAAO;GACT,CAAC;EACH;;EAIA,SAAS,YAAY;GACnB,OAAO,CAAC,CAAC,MAAM,WAAW,CAAC,QAAQ,MAAM,QAAQ,CAAC;EACpD;EAEA,SAAS,WAAW;GAClB,IAAI,MAAM,UAAU,OAAO;GAC3B,IAAI,MAAM,SAAS,OAAO,CAAC,MAAM,WAAW,UAAU;GACtD,OAAO,CAAC,EAAE,MAAM,WAAW,MAAM;EACnC;EAEA,SAAS,QAAQ,OAAmB;GAClC,IAAI,MAAM,OAAO;GACjB,KAAK,SAAS,KAAK;EACrB;;GAIE,OAAAC,UAAA,GAAAC,mBAqBS,UAAA;IApBN,OAAKC,eAAA,CAAG,QAAA,OAASC,MAAA,EAAA,CAAE,CAAC,GAAE,aAAc,SAAQ,CAAA,CAAA,CAAA;IAC5C,MAAM,QAAA;IACN,UAAUA,MAAA,QAAA;IACV,iBAAe,QAAA,WAAW,KAAA;IAC1B,aAAW,QAAA,WAAW,KAAA;IACf;;IAEI,QAAA,WAAZH,UAAA,GAAAC,mBAEO,QAAA;;KAFe,OAAKC,eAAEC,MAAA,EAAA,CAAE,CAAC,EAAC,MAAA,CAAA;KAAU,eAAY;I,IACrDC,mBAAiC,QAAA,EAA1B,OAAKF,eAAEC,MAAA,EAAA,CAAE,CAAC,EAAC,SAAA,CAAA,E,oBAEH,MAAM,WAAvBH,UAAA,GAAAC,mBAEO,QAAA;;KAF0B,OAAKC,eAAEC,MAAA,EAAA,CAAE,CAAC,EAAC,MAAA,CAAA;I,IAC1CE,WAAuB,KAAA,QAAA,SAAA,C;IAET,MAAM,WAAtBL,UAAA,GAAAC,mBAGWM,UAAA,EAAA,KAAA,EAAA,GAAA,CAFI,CAAA,UAAS,KAAtBP,UAAA,GAAAC,mBAAgE,QAAA;;KAArC,OAAKC,eAAEC,MAAA,EAAA,CAAE,CAAC,EAAC,OAAA,CAAA;I,IAAWE,WAAQ,KAAA,QAAA,SAAA,C,SACvC,CAAA,QAAA,WAAlBL,UAAA,GAAAC,mBAAgE,QAAA;;KAApC,OAAKC,eAAEC,MAAA,EAAA,CAAE,CAAC,EAAC,MAAA,CAAA;I,IAAUE,WAAQ,KAAA,QAAA,SAAA,C;IAE/C,MAAM,YAAlBL,UAAA,GAAAC,mBAEO,QAAA;;KAFsB,OAAKC,eAAEC,MAAA,EAAA,CAAE,CAAC,EAAC,MAAA,CAAA;I,IACtCE,WAAwB,KAAA,QAAA,UAAA,C"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"calendar-view.js","names":["_sfc_main"],"sources":["../../../src/components/calendar-view/calendar-view.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport type { ContextMenuItem } from '../context-menu/context-menu'\nimport { ChevronLeftIcon, ChevronRightIcon, TrashIcon } from '@dou-ui/icons'\nimport {\n computed,\n getCurrentInstance,\n nextTick,\n onBeforeUnmount,\n ref,\n shallowRef,\n triggerRef,\n useTemplateRef,\n watch,\n} from 'vue'\nimport { useNamespace } from '../../utils/namespace'\nimport { holdPointer } from '../../utils/pointer'\nimport type { PointerHold } from '../../utils/pointer'\nimport { useLocale } from '../config-provider/use-config'\nimport { DouContextMenu } from '../context-menu'\nimport {\n addDays,\n addMonths,\n daysInMonth,\n parts,\n today,\n toDateString,\n weekday,\n} from '../date-picker/date'\nimport { DouSegmentedControl } from '../segmented-control'\nimport type {\n CalendarChange,\n CalendarDay,\n CalendarEvent,\n CalendarMenuTarget,\n CalendarMove,\n CalendarRange,\n CalendarViewExpose,\n CalendarViewMode,\n CalendarViewProps,\n CalendarViewSlots,\n} from './calendar-view'\nimport {\n dayIndex,\n endOf,\n formatStamp,\n formatTime,\n moveEvent,\n sameTime,\n stampsOf,\n toStamp,\n} from './editing'\nimport TimeGridComponent from './time-grid.vue'\nimport type { HTMLAttributes } from 'vue'\nimport type { WithAttributes } from '../../utils/install'\nimport { visibleRange } from './range'\nimport { segmentOn, spanOf } from './time-layout'\nimport type { EventSpan } from './time-layout'\n\n// 让模板里也能写这个组件转交的原生属性\nconst TimeGrid = TimeGridComponent as WithAttributes<typeof TimeGridComponent, HTMLAttributes>\n\ndefineOptions({ name: 'DouCalendarView' })\n\nconst props = withDefaults(defineProps<CalendarViewProps>(), {\n maxEvents: 3,\n hours: () => [0, 24],\n scrollTime: '08:00',\n snap: 15,\n menu: true,\n})\nconst model = defineModel<string | null>()\nconst month = defineModel<string>('month')\nconst view = defineModel<CalendarViewMode>('view', { default: 'month' })\nconst events = defineModel<CalendarEvent[]>('events', { default: () => [] })\nconst emit = defineEmits<{\n select: [date: string]\n eventClick: [event: CalendarEvent]\n timeClick: [start: string]\n change: [change: CalendarChange]\n create: [range: CalendarRange]\n eventContextmenu: [event: CalendarEvent, nativeEvent: MouseEvent]\n rangeChange: [range: CalendarRange]\n}>()\nconst slots = defineSlots<CalendarViewSlots>()\n\nconst ns = useNamespace('calendar-view')\nconst { t, locale } = useLocale()\n\nconst weekStart = computed(() => props.weekStart ?? locale.value.region.weekStart)\nconst todayValue = today()\n\n/** The day the keyboard is on; its month is the one shown. */\nconst cursor = ref(model.value || (month.value ? `${month.value}-01` : todayValue))\nconst shown = computed(() => cursor.value.slice(0, 7))\n\nwatch(shown, (value) => {\n if (value !== month.value) month.value = value\n})\nwatch(month, (value) => {\n if (value && value !== shown.value) cursor.value = `${value}-01`\n})\nwatch(model, (value) => {\n if (value && value.slice(0, 7) !== shown.value) cursor.value = value\n})\n\nconst asDate = (date: string) => {\n const { year, month: m, day } = parts(date)\n const value = new Date(Date.UTC(2000, m - 1, day))\n value.setUTCFullYear(year)\n return value\n}\nconst formatter = (options: Intl.DateTimeFormatOptions) =>\n new Intl.DateTimeFormat(locale.value.name, { timeZone: 'UTC', ...options })\n\nconst fullDate = computed(() => formatter({ dateStyle: 'full' }))\nconst title = computed(() => {\n if (view.value === 'day') return fullDate.value.format(asDate(cursor.value))\n if (view.value === 'week') {\n // Composed by hand: some locales fall back to all-numeric dates in formatRange.\n const first = weekDays.value[0]!.date\n const last = weekDays.value.at(-1)!.date\n const full = formatter({ year: 'numeric', month: 'short', day: 'numeric' })\n const end =\n first.slice(0, 4) === last.slice(0, 4) ? formatter({ month: 'short', day: 'numeric' }) : full\n return `${full.format(asDate(first))} – ${end.format(asDate(last))}`\n }\n return formatter({ year: 'numeric', month: 'long' }).format(asDate(`${shown.value}-01`))\n})\nconst weekdays = computed(() => {\n const short = formatter({ weekday: 'short' })\n const long = formatter({ weekday: 'long' })\n // 2024-01-07 is a Sunday.\n return Array.from({ length: 7 }, (_, index) => {\n const date = asDate(toDateString(2024, 1, 7 + ((weekStart.value + index) % 7)))\n return { short: short.format(date), long: long.format(date) }\n })\n})\n\ntype Surface = 'time' | 'day'\ninterface Drag {\n /** `press` is a finger resting where nothing can be dragged: letting go opens the menu. */\n kind: 'move' | 'resize' | 'create' | 'press'\n surface: Surface\n event: CalendarEvent | null\n span: EventSpan | null\n /** Where the press began: the day and, on the time axis, minutes since midnight. */\n origin: { date: string; minutes: number }\n pointerId: number\n touch: boolean\n x0: number\n y0: number\n x: number\n y: number\n started: boolean\n /** The event at its new time, while moving or resizing. */\n draft: CalendarEvent | null\n /** The stretch being drawn, while creating. */\n range: CalendarRange | null\n refusal: string | null\n /** What a resting finger opens the menu on. */\n target: CalendarMenuTarget | null\n}\n\nconst drag = shallowRef<Drag | null>(null)\nconst dragged = computed(() => (drag.value?.started && drag.value.draft) || null)\nconst drawing = computed(() => (drag.value?.started && drag.value.range) || null)\nconst refused = computed(() => !!(drag.value?.started && drag.value.refusal))\n\n/** The events as shown: the one being dragged at its new time. */\nconst shownEvents = computed(() => {\n const d = drag.value\n if (!d?.started || !d.event || !d.draft) return events.value\n return events.value.map((event) => (event === d.event ? d.draft! : event))\n})\n\n/** Where each event falls; events whose `start` can't be read are left out. */\nconst spans = computed(() => {\n const map = new Map<CalendarEvent, EventSpan>()\n for (const event of shownEvents.value) {\n const span = spanOf(event)\n if (span) map.set(event, span)\n }\n return map\n})\n\n// Events that can't be read are left out; say so once per event while developing.\nconst warned = new WeakSet<CalendarEvent>()\nwatch(\n events,\n (list) => {\n // the app's bundler replaces this, as for Vue's own warnings\n if (process.env.NODE_ENV === 'production') return\n for (const event of list) {\n if (spans.value.has(event) || warned.has(event)) continue\n warned.add(event)\n console.warn(\n `[dou-ui] CalendarView event \"${event.id}\" has a start that can't be read: \"${event.start}\". Use YYYY-MM-DD or YYYY-MM-DD HH:mm.`,\n )\n }\n },\n { immediate: true },\n)\n\n/**\n * Events that run over several days first, so their bars tend to line up\n * from day to day, then the other all-day ones, then timed ones by when\n * they start on the day.\n */\nconst startOn = (event: CalendarEvent, date: string) => {\n // the event being dragged comes first, so it never folds into \"+N\"\n if (event === dragged.value) return -3\n const span = spans.value.get(event)!\n if (span.from !== span.to) return -2\n return span.start === null ? -1 : segmentOn(span, date).start\n}\n\n/** Events by day, a span listed on every day it covers. */\nconst byDay = computed(() => {\n const map = new Map<string, CalendarEvent[]>()\n for (const span of spans.value.values()) {\n for (let date = span.from, guard = 0; date <= span.to && guard < 366; guard++) {\n const list = map.get(date) ?? []\n list.push(span.event)\n map.set(date, list)\n date = addDays(date, 1)\n }\n }\n for (const [date, list] of map)\n // oxlint-disable-next-line unicorn/no-array-sort -- sorts the lists built above\n list.sort((a, b) => startOn(a, date) - startOn(b, date))\n return map\n})\n\nconst weeks = computed<CalendarDay[][]>(() => {\n const first = `${shown.value}-01`\n const offset = (weekday(first) - weekStart.value + 7) % 7\n const start = addDays(first, -offset)\n return Array.from({ length: 6 }, (_row, row) =>\n Array.from({ length: 7 }, (_column, column) => {\n const date = addDays(start, row * 7 + column)\n return {\n date,\n day: parts(date).day,\n outside: date.slice(0, 7) !== shown.value,\n today: date === todayValue,\n selected: date === model.value,\n disabled: !!props.disabledDate?.(date),\n events: byDay.value.get(date) ?? [],\n }\n }),\n )\n})\n\nconst dayOf = (date: string): CalendarDay => ({\n date,\n day: parts(date).day,\n outside: false,\n today: date === todayValue,\n selected: date === model.value,\n disabled: !!props.disabledDate?.(date),\n events: byDay.value.get(date) ?? [],\n})\n\n/** The days on the time axis: the cursor's week, or the cursor alone. */\nconst weekDays = computed<CalendarDay[]>(() => {\n if (view.value === 'day') return [dayOf(cursor.value)]\n const start = addDays(cursor.value, -((weekday(cursor.value) - weekStart.value + 7) % 7))\n return Array.from({ length: 7 }, (_, index) => dayOf(addDays(start, index)))\n})\n\n/** The days on screen, first to last, whatever the view. */\nconst visible = computed(() => visibleRange(cursor.value, view.value, weekStart.value))\nwatch(\n () => `${visible.value.start}/${visible.value.end}`,\n () => emit('rangeChange', { ...visible.value }),\n { immediate: true },\n)\n\n/** The agenda: the days of the month shown that have events, each with its events. */\nconst agenda = computed(() => {\n if (view.value !== 'agenda') return []\n const first = `${shown.value}-01`\n const { year, month: m } = parts(first)\n return Array.from({ length: daysInMonth(year, m) }, (_, index) =>\n dayOf(addDays(first, index)),\n ).filter((day) => day.events.length)\n})\n\n/** When an event takes place on one day of the agenda. */\nfunction agendaTime(event: CalendarEvent, date: string) {\n const span = spans.value.get(event)!\n if (span.start === null) return t('calendarView.allDay')\n const { start, end } = segmentOn(span, date)\n return `${timeText(start)}–${timeText(end)}`\n}\n\nconst viewOptions = computed(() =>\n (props.views ?? []).map((value) => ({ value, label: t(`calendarView.${value}`) })),\n)\n\n/** A timed event starts with its time in a month cell, on its first day. */\nconst monthTime = (event: CalendarEvent, date: string) => {\n const span = spans.value.get(event)!\n if (span.start === null || span.from !== date) return ''\n return `${String(Math.floor(span.start / 60)).padStart(2, '0')}:${String(span.start % 60).padStart(2, '0')}`\n}\n\nconst isStart = (event: CalendarEvent, date: string) => spans.value.get(event)!.from === date\n/** Whether the day opens a row, where a running span shows its title again. */\nconst weekStarts = (date: string) => (weekday(date) - weekStart.value + 7) % 7 === 0\nconst isEnd = (event: CalendarEvent, date: string) => spans.value.get(event)!.to <= date\n\n/** Whether a day is inside the stretch being drawn. */\nconst inDrawing = (date: string) =>\n !!drawing.value && drawing.value.start <= date && date <= drawing.value.end.slice(0, 10)\n/** An all-day event can have its last day dragged, from the day it ends on. */\nconst resizable = (event: CalendarEvent, date: string) =>\n !!props.editable &&\n !event.locked &&\n spans.value.get(event)!.start === null &&\n spans.value.get(event)!.to === date\n/** The agenda moves events by day only: up and down there walk the list. */\nconst keyShortcuts = computed(() =>\n view.value === 'agenda'\n ? 'Alt+ArrowLeft Alt+ArrowRight Alt+Shift+ArrowLeft Alt+Shift+ArrowRight Delete Backspace Shift+F10'\n : 'Alt+ArrowLeft Alt+ArrowRight Alt+ArrowUp Alt+ArrowDown Alt+Shift+ArrowLeft Alt+Shift+ArrowRight Alt+Shift+ArrowUp Alt+Shift+ArrowDown Delete Backspace Shift+F10',\n)\n\n/* choosing and moving */\n\nconst gridRef = useTemplateRef<HTMLElement>('grid')\n\nconst rootRef = useTemplateRef<HTMLElement>('root')\n\nasync function focusCursor() {\n await nextTick()\n ;(gridRef.value ?? rootRef.value)\n ?.querySelector<HTMLElement>(`[data-date=\"${cursor.value}\"]`)\n ?.focus({ preventScroll: true })\n}\n\nfunction choose(day: CalendarDay) {\n if (day.disabled) return\n cursor.value = day.date\n model.value = day.date\n emit('select', day.date)\n}\n\nconst weekdayOf = (date: string) => formatter({ weekday: 'short' }).format(asDate(date))\n\nfunction step(direction: 1 | -1) {\n if (view.value === 'week') cursor.value = addDays(cursor.value, direction * 7)\n else if (view.value === 'day') cursor.value = addDays(cursor.value, direction)\n else cursor.value = addMonths(cursor.value, direction)\n}\n\nconst stepLabels = computed(() =>\n view.value === 'week'\n ? [t('calendarView.prevWeek'), t('calendarView.nextWeek')]\n : view.value === 'day'\n ? [t('calendarView.prevDay'), t('calendarView.nextDay')]\n : [t('datePicker.prevMonth'), t('datePicker.nextMonth')],\n)\n\nfunction goToday() {\n cursor.value = todayValue\n}\n\n/** Left and right move between the days at the head of the week view. */\nfunction onTimeKeydown(event: KeyboardEvent) {\n if (view.value !== 'week' || !(event.target as HTMLElement).dataset.date) return\n const moves: Record<string, number> = { ArrowLeft: -1, ArrowRight: 1 }\n const move = moves[event.key]\n if (!move) return\n event.preventDefault()\n const days = weekDays.value\n const index = days.findIndex((day) => day.date === cursor.value) + move\n if (index < 0 || index >= days.length) return\n cursor.value = days[index]!.date\n void focusCursor()\n}\n\nfunction onKeydown(event: KeyboardEvent) {\n const date = cursor.value\n const column = (weekday(date) - weekStart.value + 7) % 7\n const keys: Record<string, () => string> = {\n ArrowLeft: () => addDays(date, -1),\n ArrowRight: () => addDays(date, 1),\n ArrowUp: () => addDays(date, -7),\n ArrowDown: () => addDays(date, 7),\n Home: () => addDays(date, -column),\n End: () => addDays(date, 6 - column),\n PageUp: () => addMonths(date, event.shiftKey ? -12 : -1),\n PageDown: () => addMonths(date, event.shiftKey ? 12 : 1),\n }\n const next = keys[event.key]?.()\n if (next) {\n event.preventDefault()\n cursor.value = next\n void focusCursor()\n return\n }\n if (event.key === 'Enter' || event.key === ' ') {\n if ((event.target as HTMLElement).closest('button')) return\n event.preventDefault()\n const day = weeks.value.flat().find((cell) => cell.date === date)\n if (day) choose(day)\n }\n}\n\nfunction dayLabel(day: CalendarDay) {\n const date = fullDate.value.format(asDate(day.date))\n if (!day.events.length) return date\n return t('calendarView.dayWithEvents', { date, count: day.events.length })\n}\n\n/* editing: dragging, drawing, the menu and the keys */\n\nconst LONG_PRESS = 300\nconst THRESHOLD = { mouse: 3, touch: 6 }\n/** How close to the top or bottom of the time axis a drag starts scrolling it, in px. */\nconst EDGE = 32\n\nconst live = ref('')\nfunction announce(text: string) {\n // the same text twice in a row is read again\n live.value = live.value === text ? `${text} ` : text\n}\n\nlet hold: PointerHold | null = null\nlet pressTimer: ReturnType<typeof setTimeout> | undefined\nlet frame = 0\n/** Beside the pointer while dragging: the new time, or why it can't go there. */\nconst tip = computed(() => {\n const d = drag.value\n if (!d?.started || d.kind === 'press') return ''\n if (d.refusal) return d.refusal\n if (d.draft) return rangeText(d.draft)\n return d.range ? rangeText({ id: '', title: '', ...d.range }) : ''\n})\n\n/** Where the pointer is, kept apart from the drag so the tip follows it between snaps. */\nconst pointer = ref({ x: 0, y: 0 })\n/**\n * Beside the pointer, where it stays in view: on its left once past the\n * middle of the window, and above it for a finger, which would cover it,\n * or near the bottom of the window.\n */\nconst tipStyle = computed(() => {\n const d = drag.value\n if (!d || typeof window === 'undefined') return {}\n const { x, y } = pointer.value\n const left = x > window.innerWidth / 2\n const above = d.touch || y + 16 + 40 > window.innerHeight\n return {\n left: `${left ? x - 12 : x + 12}px`,\n top: `${above ? y - (d.touch ? 40 : 16) : y + 16}px`,\n transform: `translate(${left ? '-100%' : '0'}, ${above ? '-100%' : '0'})`,\n }\n})\n\n/** A drag ended a moment ago: the click that follows it is not a click. */\nlet swallow = false\n/** When a finger last went down; the browser's own long-press menu is ignored after it. */\nlet touchedAt = 0\n\nconst byId = (id: string | undefined) => events.value.find((event) => event.id === id)\nconst canEdit = (event: CalendarEvent) => !!props.editable && !event.locked\n\nconst dayFormat = computed(() => formatter({ month: 'short', day: 'numeric' }))\nconst clock = computed(() =>\n formatter({\n hour: 'numeric',\n minute: '2-digit',\n hourCycle: /A/.test(locale.value.region.time) ? 'h12' : 'h23',\n }),\n)\nconst timeText = (minutes: number) => clock.value.format(new Date(Date.UTC(2000, 0, 1, 0, minutes)))\n\n/** When an event takes place, as read out after a change. */\nfunction rangeText(event: CalendarEvent) {\n const span = spanOf(event)\n if (!span) return ''\n const from = dayFormat.value.format(asDate(span.from))\n const to = dayFormat.value.format(asDate(span.to))\n if (span.start === null) return span.from === span.to ? from : `${from} – ${to}`\n if (span.from === span.to) return `${from} ${timeText(span.start)}–${timeText(span.end!)}`\n return `${from} ${timeText(span.start)} – ${to} ${timeText(span.end!)}`\n}\n\n/**\n * The day and time under a point. The nearest column or day is taken when\n * the point is off to the side, so a drag past the edge stays on the last\n * day; on the time axis the time is kept inside the hours shown.\n */\nfunction pointAt(surface: Surface, x: number, y: number) {\n const cells = rootRef.value?.querySelectorAll<HTMLElement>(`[data-drop=\"${surface}\"]`) ?? []\n let best: { cell: HTMLElement; rect: DOMRect } | null = null\n let distance = Infinity\n for (const cell of cells) {\n const rect = cell.getBoundingClientRect()\n const dx = Math.max(rect.left - x, 0, x - rect.right)\n const dy = surface === 'time' ? 0 : Math.max(rect.top - y, 0, y - rect.bottom)\n if (dx * dx + dy * dy < distance) {\n distance = dx * dx + dy * dy\n best = { cell, rect }\n }\n }\n if (!best) return null\n const date = best.cell.dataset.date!\n if (surface === 'day') return { date, minutes: 0 }\n const from = Number(best.cell.dataset.from)\n const to = Number(best.cell.dataset.to)\n const ratio = best.rect.height ? (y - best.rect.top) / best.rect.height : 0\n return { date, minutes: Math.min(to, Math.max(from, from + ratio * (to - from))) }\n}\n\n/** `null` when the change may be made, else why not. */\nfunction refusalOf(kind: CalendarMove['kind'], event: CalendarEvent | null, next: CalendarRange) {\n const span = spanOf({ id: '', title: '', start: next.start, end: next.end })\n if (span && props.disabledDate) {\n for (let date = span.from, guard = 0; date <= span.to && guard < 366; guard++) {\n if (props.disabledDate(date)) return t('calendarView.disabledDay')\n date = addDays(date, 1)\n }\n }\n if (!props.canMove) return null\n const answer = props.canMove({ kind, event, start: next.start, end: next.end }, events.value)\n if (answer === true) return null\n return typeof answer === 'string' && answer ? answer : t('calendarView.refused')\n}\n\nconst DAY = 24 * 60\n/** The minutes the time axis covers, as the time grid draws it. */\nconst axis = computed(() => {\n const from = Math.max(0, Math.min(23, Math.floor(props.hours[0])))\n const to = Math.max(from + 1, Math.min(24, Math.ceil(props.hours[1])))\n return { from: from * 60, to: to * 60 }\n})\n\n/**\n * Where a timed event moved on the time axis starts: an event that sits\n * within one day stays inside the hours shown, so it is never written to a\n * time the axis can't show. One that runs over midnight moves freely.\n */\nfunction fitHours(span: EventSpan, start: number) {\n const { from, to } = axis.value\n const length = stampsOf(span).end - stampsOf(span).start\n if (span.from !== span.to || length > to - from) return start\n const day = Math.floor(start / DAY)\n return day * DAY + Math.min(Math.max(start - day * DAY, from), to - length)\n}\n\nconst roundTo = (value: number, by: number) => Math.round(value / by) * by\nconst floorTo = (value: number, by: number) => Math.floor(value / by) * by\n\n/** Work out what the drag would change, with the pointer where it is now. */\nfunction update(d: Drag) {\n const point = pointAt(d.surface, d.x, d.y)\n if (!point || d.kind === 'press') return\n const snap = Math.max(1, Math.round(props.snap))\n let draft: CalendarEvent | null = null\n let range: CalendarRange | null = null\n if (d.surface === 'time') {\n const now = toStamp(point.date, point.minutes)\n const origin = toStamp(d.origin.date, d.origin.minutes)\n if (d.kind === 'create') {\n const a = floorTo(origin, snap)\n const b = floorTo(now, snap)\n range = { start: formatStamp(Math.min(a, b)), end: formatStamp(Math.max(a, b) + snap) }\n } else {\n const { start } = stampsOf(d.span!)\n if (d.kind === 'move') {\n const next = fitHours(d.span!, roundTo(start + now - origin, snap))\n draft = moveEvent(d.event!, d.span!, { minutes: next - start })\n } else {\n draft = { ...d.event!, end: formatStamp(Math.max(start + snap, roundTo(now, snap))) }\n }\n }\n } else {\n const days = dayIndex(point.date) - dayIndex(d.origin.date)\n if (d.kind === 'create') {\n const [a, b] = [d.origin.date, point.date]\n range = a <= b ? { start: a, end: b } : { start: b, end: a }\n } else if (d.kind === 'move') {\n draft = moveEvent(d.event!, d.span!, { days })\n } else {\n draft = { ...d.event!, end: point.date < d.span!.from ? d.span!.from : point.date }\n }\n }\n if (draft) {\n if (d.draft && sameTime(d.draft, draft)) return\n d.draft = draft\n d.refusal = sameTime(draft, d.event!)\n ? null\n : refusalOf(d.kind, d.event, { start: draft.start, end: endOf(draft) })\n } else if (range) {\n if (d.range?.start === range.start && d.range.end === range.end) return\n d.range = range\n d.refusal = refusalOf('create', null, range)\n }\n triggerRef(drag)\n}\n\n/** The menu target under a pointer or a key press. */\nfunction targetOf(el: HTMLElement, x: number, y: number): CalendarMenuTarget | null {\n const chip = el.closest<HTMLElement>('[data-event-id]')\n const event = chip && byId(chip.dataset.eventId)\n if (event) return { kind: 'event', event }\n const cell = el.closest<HTMLElement>('[data-drop]')\n if (!cell) return null\n if (cell.dataset.drop === 'day') return { kind: 'day', date: cell.dataset.date! }\n const point = pointAt('time', x, y)\n if (!point) return null\n const minutes = Math.min(\n floorTo(point.minutes, Math.max(1, props.snap)),\n Number(cell.dataset.to) - props.snap,\n )\n return { kind: 'time', start: `${point.date} ${formatTime(Math.max(0, minutes))}` }\n}\n\nfunction onPointerDown(event: PointerEvent) {\n if (event.button !== 0 || drag.value) return\n const el = event.target as HTMLElement\n const touch = event.pointerType !== 'mouse'\n const chip = el.closest<HTMLElement>('[data-event-id]')\n const item = chip ? byId(chip.dataset.eventId) : undefined\n const cell = el.closest<HTMLElement>('[data-drop]')\n if (!cell) {\n // the agenda has nothing to drag: a finger resting on an event opens its menu\n const day = el.closest<HTMLElement>('[data-agenda-date]')?.dataset.agendaDate\n if (touch && item && day) {\n startDrag(event, {\n kind: 'press',\n surface: 'day',\n event: item,\n span: spans.value.get(item) ?? null,\n origin: { date: day, minutes: 0 },\n target: { kind: 'event', event: item },\n })\n }\n return\n }\n const surface = cell.dataset.drop as Surface\n let kind: Drag['kind']\n if (item) {\n const span = spans.value.get(item)\n if (!span) return\n if (!canEdit(item)) kind = 'press'\n else if (el.closest('[data-resize]')) kind = 'resize'\n else kind = 'move'\n } else {\n if (chip || el.closest('button, a, input')) return\n if (cell.matches('.is-disabled')) kind = 'press'\n else kind = props.editable ? 'create' : 'press'\n }\n // a mouse has its own menu button; only a finger needs the resting press\n if (kind === 'press' && !touch) return\n const origin = pointAt(surface, event.clientX, event.clientY)\n if (!origin) return\n startDrag(event, {\n kind,\n surface,\n event: item ?? null,\n span: item ? spans.value.get(item)! : null,\n origin,\n target: targetOf(el, event.clientX, event.clientY),\n })\n}\n\n/** Starts following a pointer that went down; a finger picks up after a short rest. */\nfunction startDrag(\n event: PointerEvent,\n start: Pick<Drag, 'kind' | 'surface' | 'event' | 'span' | 'origin' | 'target'>,\n) {\n const touch = event.pointerType !== 'mouse'\n if (touch) touchedAt = Date.now()\n drag.value = {\n ...start,\n pointerId: event.pointerId,\n touch,\n x0: event.clientX,\n y0: event.clientY,\n x: event.clientX,\n y: event.clientY,\n started: false,\n draft: null,\n range: null,\n refusal: null,\n }\n pointer.value = { x: event.clientX, y: event.clientY }\n hold = holdPointer(event, rootRef.value)\n window.addEventListener('pointermove', onDragMove)\n window.addEventListener('pointerup', onDragEnd)\n window.addEventListener('pointercancel', stopDrag)\n window.addEventListener('keydown', onDragKey, true)\n // switching away mid-drag drops it\n window.addEventListener('blur', stopDrag)\n document.addEventListener('visibilitychange', stopDrag)\n // a finger picks an event up after a short rest; moving sooner scrolls instead\n if (touch) {\n const d = drag.value\n pressTimer = setTimeout(() => {\n if (drag.value !== d) return\n begin(d)\n navigator.vibrate?.(8)\n }, LONG_PRESS)\n }\n}\n\nfunction begin(d: Drag) {\n d.started = true\n hold?.grab()\n if (d.kind !== 'press') {\n update(d)\n if (d.surface === 'time') frame = requestAnimationFrame(autoScroll)\n }\n triggerRef(drag)\n}\n\nfunction onDragMove(event: PointerEvent) {\n const d = drag.value\n if (!d || event.pointerId !== d.pointerId) return\n d.x = event.clientX\n d.y = event.clientY\n pointer.value = { x: d.x, y: d.y }\n const far = Math.hypot(d.x - d.x0, d.y - d.y0) > THRESHOLD[d.touch ? 'touch' : 'mouse']\n if (!d.started) {\n // a finger that moves before resting scrolls the page\n if (d.touch) return void (far && stopDrag())\n if (far) begin(d)\n return\n }\n if (d.kind === 'press') return\n update(d)\n}\n\nfunction onDragEnd(event: PointerEvent) {\n const d = drag.value\n if (!d || event.pointerId !== d.pointerId) return\n const still = Math.hypot(d.x - d.x0, d.y - d.y0) <= THRESHOLD.touch\n stopDrag()\n if (!d.started) return\n swallow = true\n setTimeout(() => (swallow = false))\n // a finger rested and let go without moving: the menu\n if (d.touch && still) {\n if (d.target && menuAllowed(d.target, event.clientX, event.clientY))\n showMenu(d.target, event.clientX, event.clientY)\n return\n }\n if (d.refusal) return announce(d.refusal)\n if (d.kind === 'create' && d.range) emit('create', { ...d.range })\n else if ((d.kind === 'move' || d.kind === 'resize') && d.draft && !sameTime(d.draft, d.event!))\n commit(d.kind, d.event!, d.draft)\n}\n\nfunction stopDrag() {\n clearTimeout(pressTimer)\n cancelAnimationFrame(frame)\n hold?.release()\n hold = null\n window.removeEventListener('pointermove', onDragMove)\n window.removeEventListener('pointerup', onDragEnd)\n window.removeEventListener('pointercancel', stopDrag)\n window.removeEventListener('keydown', onDragKey, true)\n window.removeEventListener('blur', stopDrag)\n if (typeof document !== 'undefined') document.removeEventListener('visibilitychange', stopDrag)\n drag.value = null\n}\n\nfunction onDragKey(event: KeyboardEvent) {\n if (event.key !== 'Escape') return\n event.preventDefault()\n event.stopPropagation()\n if (drag.value?.started) {\n swallow = true\n setTimeout(() => (swallow = false))\n }\n stopDrag()\n}\n\n/** Auto-scroll speed in px per frame, `gap` px from the edge. */\nconst speed = (gap: number) => (gap < EDGE ? Math.ceil(((EDGE - gap) / EDGE) * 12) : 0)\n\n/** Scrolls the time axis while a drag rests near its top or bottom. */\nfunction autoScroll() {\n const d = drag.value\n const el = rootRef.value?.querySelector<HTMLElement>(`.${ns.e('scroller')}`)\n if (!d?.started || !el) return\n const rect = el.getBoundingClientRect()\n const dy = speed(d.y - rect.top) ? -speed(d.y - rect.top) : speed(rect.bottom - d.y)\n if (dy) {\n const before = el.scrollTop\n el.scrollTop += dy\n if (el.scrollTop !== before) update(d)\n }\n frame = requestAnimationFrame(autoScroll)\n}\n\nfunction onClickCapture(event: MouseEvent) {\n if (!swallow) return\n event.stopPropagation()\n event.preventDefault()\n}\n\n/** Writes the new version of an event, then keeps the keyboard and the view on it. */\nfunction commit(kind: 'move' | 'resize', previous: CalendarEvent, next: CalendarEvent) {\n events.value = events.value.map((event) => (event === previous ? next : event))\n emit('change', { kind, event: next, previous })\n announce(t('calendarView.changed', { title: next.title, range: rangeText(next) }))\n}\n\nfunction remove(event: CalendarEvent) {\n events.value = events.value.filter((item) => item !== event)\n emit('change', { kind: 'remove', event })\n announce(t('calendarView.removed', { title: event.title }))\n}\n\n/** The element of an event on screen. */\nconst chipOf = (id: string | undefined) =>\n id ? rootRef.value?.querySelector<HTMLElement>(`[data-event-id=\"${CSS.escape(id)}\"]`) : null\n\n/**\n * Removes an event from the keyboard, then puts the keyboard on the next\n * event of the same day, or the one before, or else on the day itself.\n */\nasync function removeByKey(item: CalendarEvent, chip: HTMLElement) {\n const siblings = [...(chip.parentElement?.querySelectorAll<HTMLElement>('[data-event-id]') ?? [])]\n const index = siblings.indexOf(chip)\n // oxlint-disable-next-line unicorn/no-array-reverse -- reverses a fresh slice\n const near = [...siblings.slice(index + 1), ...siblings.slice(0, index).reverse()]\n .map((el) => el.dataset.eventId!)\n .find((id) => id !== item.id)\n const holder = chip.closest<HTMLElement>('[data-date], [data-agenda-date]')\n const day = holder?.dataset.date ?? holder?.dataset.agendaDate\n // in the agenda a day without events goes away: fall back to the next day's first event, or the one before's\n const listed = chip.closest<HTMLElement>('[data-agenda-date]')\n const neighbour = (listed?.nextElementSibling ?? listed?.previousElementSibling) as\n HTMLElement | null | undefined\n const fallback = neighbour?.querySelector<HTMLElement>('[data-event-id]')?.dataset.eventId\n remove(item)\n await nextTick()\n const next = chipOf(near) ?? chipOf(fallback)\n if (next) return next.focus()\n if (day) cursor.value = day\n void focusCursor()\n}\n\n/* the menu */\n\nconst menuRef = useTemplateRef<InstanceType<typeof DouContextMenu>>('menu')\nconst menuOpen = ref(false)\nconst menuItems = shallowRef<ContextMenuItem[]>([])\n\n/**\n * Sends `eventContextmenu` before an event's menu opens; `false` when the\n * app kept it closed. A resting finger or the menu key has no browser event\n * of its own, so it gets a stand-in.\n */\nfunction menuAllowed(target: CalendarMenuTarget, x: number, y: number, native?: MouseEvent) {\n if (target.kind !== 'event') return true\n const event =\n native ?? new MouseEvent('contextmenu', { cancelable: true, clientX: x, clientY: y })\n emit('eventContextmenu', target.event, event)\n return !event.defaultPrevented\n}\n\n/** Opens the menu for a target; `false` when it would be empty. */\nfunction showMenu(target: CalendarMenuTarget, x: number, y: number) {\n const items = [...(props.contextMenu?.(target) ?? [])]\n if (props.menu && target.kind === 'event' && canEdit(target.event)) {\n items.push({\n key: '\\0delete',\n label: t('calendarView.deleteEvent'),\n icon: TrashIcon,\n shortcut: 'Delete',\n tone: 'danger',\n divided: items.length > 0,\n run: () => remove(target.event),\n })\n }\n if (!items.length) return false\n menuItems.value = items\n menuRef.value?.open(x, y)\n return true\n}\n\nfunction onContextmenu(event: MouseEvent) {\n // a finger's long press opens the menu itself, when it lets go\n if (Date.now() - touchedAt < 1000) return event.preventDefault()\n const target = targetOf(event.target as HTMLElement, event.clientX, event.clientY)\n if (!target) return\n if (!menuAllowed(target, event.clientX, event.clientY, event)) return\n if (showMenu(target, event.clientX, event.clientY)) event.preventDefault()\n}\n\n/* keys on an event */\n\n/**\n * Puts the keyboard back on an event after a change; when the app stored it\n * under a new id, e.g. one occurrence of a series, on the day it went to.\n */\nasync function focusEvent(id: string) {\n await nextTick()\n const chip = chipOf(id)\n if (chip) chip.focus()\n else void focusCursor()\n}\n\n/**\n * On a focused event: the menu key opens its menu, `Delete` removes it,\n * `Alt` with the arrows moves it and `Alt` + `Shift` with the arrows moves\n * its end. Left and right go by a day; up and down by `snap` on the time\n * axis, or by a week in the month.\n */\nfunction onEventKeydown(event: KeyboardEvent) {\n const chip = (event.target as HTMLElement).closest<HTMLElement>('[data-event-id]')\n const item = chip && byId(chip.dataset.eventId)\n if (!item) return\n const done = () => {\n event.preventDefault()\n event.stopPropagation()\n }\n if (event.key === 'ContextMenu' || (event.shiftKey && event.key === 'F10')) {\n const rect = chip.getBoundingClientRect()\n const target = { kind: 'event', event: item } as const\n if (!menuAllowed(target, rect.left, rect.bottom)) return done()\n if (showMenu(target, rect.left, rect.bottom)) done()\n return\n }\n if (!canEdit(item)) return\n if (event.key === 'Delete' || event.key === 'Backspace') {\n done()\n removeByKey(item, chip)\n return\n }\n const span = spans.value.get(item)\n if (!event.altKey || !span) return\n const timed = span.start !== null\n const onAxis = timed && view.value !== 'month' && view.value !== 'agenda'\n const snap = Math.max(1, Math.round(props.snap))\n const keys: Record<string, { days?: number; minutes?: number }> = {\n ArrowLeft: { days: -1 },\n ArrowRight: { days: 1 },\n ArrowUp: onAxis ? { minutes: -snap } : view.value === 'month' ? { days: -7 } : {},\n ArrowDown: onAxis ? { minutes: snap } : view.value === 'month' ? { days: 7 } : {},\n }\n const by = keys[event.key]\n if (!by || (!by.days && !by.minutes)) return\n done()\n let next: CalendarEvent\n let kind: 'move' | 'resize' = 'move'\n if (event.shiftKey) {\n kind = 'resize'\n if (timed) {\n const { start, end } = stampsOf(span)\n const delta = (by.days ?? 0) * DAY + (by.minutes ?? 0)\n let last = Math.max(start + snap, end + delta)\n // an event within one day on the time axis ends by the end of the hours shown\n if (onAxis && span.from === span.to) {\n const day = Math.floor(start / DAY)\n last = Math.min(last, day * DAY + axis.value.to)\n }\n next = { ...item, end: formatStamp(last) }\n } else {\n const last = addDays(span.to, by.days ?? 0)\n next = { ...item, end: last < span.from ? span.from : last }\n }\n } else if (onAxis) {\n const { start } = stampsOf(span)\n const at = fitHours(span, start + (by.days ?? 0) * DAY + (by.minutes ?? 0))\n next = moveEvent(item, span, { minutes: at - start })\n } else {\n next = moveEvent(item, span, by)\n }\n if (sameTime(next, item)) return\n const refusal = refusalOf(kind, item, { start: next.start, end: endOf(next) })\n if (refusal) return announce(refusal)\n commit(kind, item, next)\n const placed = spanOf(next)\n if (placed) cursor.value = kind === 'resize' ? placed.to : placed.from\n void focusEvent(next.id)\n}\n\nonBeforeUnmount(stopDrag)\n\n// Editing without anywhere to write the change: each drop springs back. Say so once while developing.\nconst instance = getCurrentInstance()\nwatch(\n () => props.editable,\n (editable) => {\n if (process.env.NODE_ENV === 'production' || !editable) return\n const bound = instance?.vnode.props ?? {}\n const has = (...names: string[]) => names.some((name) => name in bound)\n if (has('events') && !has('onUpdate:events', 'onChange')) {\n console.warn(\n '[dou-ui] CalendarView is editable but nothing receives the changes, so they spring back. Bind v-model:events, or listen to change.',\n )\n }\n },\n { immediate: true },\n)\n\ndefineExpose<CalendarViewExpose>({\n show: (date) => (cursor.value = date.length === 7 ? `${date}-01` : date),\n focus: () => void focusCursor(),\n})\n</script>\n\n<template>\n <div\n ref=\"root\"\n :class=\"[\n ns.b(),\n ns.m(view),\n ns.is('editable', !!editable),\n ns.is('dragging', !!drag?.started && drag.kind !== 'press'),\n drag?.started && drag.kind !== 'press' ? `is-dragging-${drag.kind}` : '',\n ]\"\n @pointerdown=\"onPointerDown\"\n @contextmenu=\"onContextmenu\"\n @click.capture=\"onClickCapture\"\n @keydown.capture=\"onEventKeydown\"\n >\n <div :class=\"ns.e('header')\">\n <h2 :class=\"ns.e('title')\" aria-live=\"polite\">{{ title }}</h2>\n <div :class=\"ns.e('nav')\">\n <slot name=\"extra\" />\n <DouSegmentedControl\n v-if=\"viewOptions.length > 1\"\n v-model=\"view\"\n :options=\"viewOptions\"\n size=\"sm\"\n />\n <button type=\"button\" :class=\"ns.e('today')\" @click=\"goToday\">\n {{ t('datePicker.today') }}\n </button>\n <button type=\"button\" :class=\"ns.e('step')\" :aria-label=\"stepLabels[0]\" @click=\"step(-1)\">\n <ChevronLeftIcon />\n </button>\n <button type=\"button\" :class=\"ns.e('step')\" :aria-label=\"stepLabels[1]\" @click=\"step(1)\">\n <ChevronRightIcon />\n </button>\n </div>\n </div>\n <div\n v-if=\"view === 'agenda'\"\n ref=\"grid\"\n role=\"list\"\n :class=\"ns.e('agenda')\"\n :aria-label=\"title\"\n >\n <p v-if=\"!agenda.length\" :class=\"ns.e('agenda-empty')\">{{ t('calendarView.empty') }}</p>\n <div\n v-for=\"day in agenda\"\n :key=\"day.date\"\n role=\"listitem\"\n :data-agenda-date=\"day.date\"\n :class=\"[ns.e('agenda-day'), ns.is('today', day.today), ns.is('selected', day.selected)]\"\n >\n <button\n type=\"button\"\n :class=\"ns.e('agenda-date')\"\n :data-date=\"day.date\"\n :disabled=\"day.disabled\"\n :aria-label=\"dayLabel(day)\"\n :aria-current=\"day.today ? 'date' : undefined\"\n :aria-pressed=\"day.selected\"\n @click=\"choose(day)\"\n >\n <span :class=\"ns.e('number')\">{{ day.day }}</span>\n <span :class=\"ns.e('agenda-weekday')\">{{ weekdayOf(day.date) }}</span>\n </button>\n <div :class=\"ns.e('agenda-events')\">\n <button\n v-for=\"event in day.events\"\n :key=\"event.id\"\n type=\"button\"\n :data-event-id=\"event.id\"\n :class=\"[\n ns.e('agenda-event'),\n `${ns.e('event')}--${event.tone ?? 'primary'}`,\n ns.is('locked', !!editable && !!event.locked),\n ]\"\n :aria-keyshortcuts=\"editable && !event.locked ? keyShortcuts : undefined\"\n @click=\"emit('eventClick', event)\"\n >\n <span :class=\"ns.e('agenda-time')\">{{ agendaTime(event, day.date) }}</span>\n <span :class=\"ns.e('agenda-title')\">\n <slot name=\"event\" :event=\"event\" :day=\"day\">{{ event.title }}</slot>\n </span>\n </button>\n </div>\n </div>\n </div>\n <TimeGrid\n v-else-if=\"view !== 'month'\"\n :days=\"weekDays\"\n :spans=\"spans\"\n :hours=\"hours\"\n :scroll-time=\"scrollTime\"\n :cursor=\"cursor\"\n :as-date=\"asDate\"\n :editable=\"!!editable\"\n :dragged=\"dragged\"\n :refused=\"refused\"\n :drawing=\"drawing\"\n :snap=\"snap\"\n @choose=\"choose\"\n @event-click=\"emit('eventClick', $event)\"\n @time-click=\"emit('timeClick', $event)\"\n @keydown=\"onTimeKeydown\"\n >\n <template v-if=\"slots.event\" #event=\"scope\">\n <slot name=\"event\" v-bind=\"scope\" />\n </template>\n </TimeGrid>\n <div\n v-else\n ref=\"grid\"\n role=\"grid\"\n :class=\"ns.e('grid')\"\n :aria-label=\"title\"\n @keydown=\"onKeydown\"\n >\n <div role=\"row\" :class=\"ns.e('weekdays')\">\n <div\n v-for=\"weekdayName in weekdays\"\n :key=\"weekdayName.long\"\n role=\"columnheader\"\n :class=\"ns.e('weekday')\"\n :aria-label=\"weekdayName.long\"\n >\n {{ weekdayName.short }}\n </div>\n </div>\n <div v-for=\"(week, row) in weeks\" :key=\"row\" role=\"row\" :class=\"ns.e('week')\">\n <div\n v-for=\"day in week\"\n :key=\"day.date\"\n role=\"gridcell\"\n data-drop=\"day\"\n :data-date=\"day.date\"\n :tabindex=\"day.date === cursor ? 0 : -1\"\n :aria-selected=\"day.selected\"\n :aria-disabled=\"day.disabled || undefined\"\n :aria-current=\"day.today ? 'date' : undefined\"\n :aria-label=\"dayLabel(day)\"\n :class=\"[\n ns.e('day'),\n ns.is('outside', day.outside),\n ns.is('today', day.today),\n ns.is('selected', day.selected),\n ns.is('disabled', day.disabled),\n ns.is('drawing', inDrawing(day.date)),\n ns.is('refused', inDrawing(day.date) && refused),\n ]\"\n @click=\"choose(day)\"\n >\n <span :class=\"ns.e('number')\">{{ day.day }}</span>\n <div :class=\"ns.e('content')\">\n <slot name=\"cell\" v-bind=\"day\">\n <button\n v-for=\"event in day.events.slice(0, maxEvents)\"\n :key=\"event.id\"\n type=\"button\"\n :data-event-id=\"event.id\"\n :tabindex=\"day.date === cursor ? 0 : -1\"\n :class=\"[\n ns.e('event'),\n `${ns.e('event')}--${event.tone ?? 'primary'}`,\n ns.is('start', isStart(event, day.date)),\n ns.is('continued', !isStart(event, day.date) && !weekStarts(day.date)),\n ns.is('end', isEnd(event, day.date)),\n ns.is('locked', !!editable && !!event.locked),\n ns.is('dragging', event === dragged),\n ns.is('refused', event === dragged && refused),\n ]\"\n :title=\"event.title\"\n :aria-keyshortcuts=\"editable && !event.locked ? keyShortcuts : undefined\"\n @click.stop=\"emit('eventClick', event)\"\n >\n <slot name=\"event\" :event=\"event\" :day=\"day\"\n ><span v-if=\"monthTime(event, day.date)\" :class=\"ns.e('event-clock')\">{{\n monthTime(event, day.date)\n }}</span\n >{{ event.title }}</slot\n ><span\n v-if=\"resizable(event, day.date)\"\n :class=\"[ns.e('resize'), `${ns.e('resize')}--x`]\"\n data-resize\n aria-hidden=\"true\"\n />\n </button>\n <span v-if=\"day.events.length > maxEvents\" :class=\"ns.e('more')\">\n {{ t('calendarView.more', { count: day.events.length - maxEvents }) }}\n </span>\n </slot>\n </div>\n </div>\n </div>\n </div>\n <span :class=\"ns.e('live')\" aria-live=\"polite\">{{ live }}</span>\n <div\n v-if=\"tip\"\n :class=\"[ns.e('tip'), ns.is('refused', refused)]\"\n :style=\"tipStyle\"\n aria-hidden=\"true\"\n >\n {{ tip }}\n </div>\n <!-- The calendar opens the menu itself; the empty anchor only gives it a place in the page. -->\n <DouContextMenu ref=\"menu\" v-model:open=\"menuOpen\" :items=\"menuItems\">\n <span :class=\"ns.e('menu-anchor')\" aria-hidden=\"true\" />\n </DouContextMenu>\n </div>\n</template>\n"],"mappings":""}
|
|
1
|
+
{"version":3,"file":"calendar-view.js","names":["_sfc_main"],"sources":["../../../src/components/calendar-view/calendar-view.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport type { ContextMenuItem } from '../context-menu/context-menu'\nimport { ChevronLeftIcon, ChevronRightIcon, TrashIcon } from '@dou-ui/icons'\nimport {\n computed,\n getCurrentInstance,\n nextTick,\n onBeforeUnmount,\n ref,\n shallowRef,\n triggerRef,\n useTemplateRef,\n watch,\n} from 'vue'\nimport { useNamespace } from '../../utils/namespace'\nimport { holdPointer } from '../../utils/pointer'\nimport type { PointerHold } from '../../utils/pointer'\nimport { useLocale } from '../config-provider/use-config'\nimport { DouContextMenu } from '../context-menu'\nimport {\n addDays,\n addMonths,\n daysInMonth,\n parts,\n today,\n toDateString,\n weekday,\n} from '../date-picker/date'\nimport { DouSegmentedControl } from '../segmented-control'\nimport type {\n CalendarChange,\n CalendarDay,\n CalendarEvent,\n CalendarMenuTarget,\n CalendarMove,\n CalendarRange,\n CalendarViewExpose,\n CalendarViewMode,\n CalendarViewProps,\n CalendarViewSlots,\n} from './calendar-view'\nimport {\n dayIndex,\n endOf,\n formatStamp,\n formatTime,\n moveEvent,\n sameTime,\n stampsOf,\n toStamp,\n} from './editing'\nimport TimeGridComponent from './time-grid.vue'\nimport type { HTMLAttributes } from 'vue'\nimport type { WithAttributes } from '../../utils/install'\nimport { visibleRange } from './range'\nimport { segmentOn, spanOf } from './time-layout'\nimport type { EventSpan } from './time-layout'\n\n// 让模板里也能写这个组件转交的原生属性\nconst TimeGrid = TimeGridComponent as WithAttributes<typeof TimeGridComponent, HTMLAttributes>\n\ndefineOptions({ name: 'DouCalendarView' })\n\nconst props = withDefaults(defineProps<CalendarViewProps>(), {\n maxEvents: 3,\n hours: () => [0, 24],\n scrollTime: '08:00',\n snap: 15,\n menu: true,\n bordered: true,\n})\nconst model = defineModel<string | null>()\nconst month = defineModel<string>('month')\nconst view = defineModel<CalendarViewMode>('view', { default: 'month' })\nconst events = defineModel<CalendarEvent[]>('events', { default: () => [] })\nconst emit = defineEmits<{\n select: [date: string]\n eventClick: [event: CalendarEvent]\n timeClick: [start: string]\n change: [change: CalendarChange]\n create: [range: CalendarRange]\n eventContextmenu: [event: CalendarEvent, nativeEvent: MouseEvent]\n rangeChange: [range: CalendarRange]\n}>()\nconst slots = defineSlots<CalendarViewSlots>()\n\nconst ns = useNamespace('calendar-view')\nconst { t, locale } = useLocale()\n\nconst weekStart = computed(() => props.weekStart ?? locale.value.region.weekStart)\nconst todayValue = today()\n\n/** The day the keyboard is on; its month is the one shown. */\nconst cursor = ref(model.value || (month.value ? `${month.value}-01` : todayValue))\nconst shown = computed(() => cursor.value.slice(0, 7))\n\nwatch(shown, (value) => {\n if (value !== month.value) month.value = value\n})\nwatch(month, (value) => {\n if (value && value !== shown.value) cursor.value = `${value}-01`\n})\nwatch(model, (value) => {\n if (value && value.slice(0, 7) !== shown.value) cursor.value = value\n})\n\nconst asDate = (date: string) => {\n const { year, month: m, day } = parts(date)\n const value = new Date(Date.UTC(2000, m - 1, day))\n value.setUTCFullYear(year)\n return value\n}\nconst formatter = (options: Intl.DateTimeFormatOptions) =>\n new Intl.DateTimeFormat(locale.value.name, { timeZone: 'UTC', ...options })\n\nconst fullDate = computed(() => formatter({ dateStyle: 'full' }))\nconst title = computed(() => {\n if (view.value === 'day') return fullDate.value.format(asDate(cursor.value))\n if (view.value === 'week') {\n // Composed by hand: some locales fall back to all-numeric dates in formatRange.\n const first = weekDays.value[0]!.date\n const last = weekDays.value.at(-1)!.date\n const full = formatter({ year: 'numeric', month: 'short', day: 'numeric' })\n const end =\n first.slice(0, 4) === last.slice(0, 4) ? formatter({ month: 'short', day: 'numeric' }) : full\n return `${full.format(asDate(first))} – ${end.format(asDate(last))}`\n }\n return formatter({ year: 'numeric', month: 'long' }).format(asDate(`${shown.value}-01`))\n})\nconst weekdays = computed(() => {\n const short = formatter({ weekday: 'short' })\n const long = formatter({ weekday: 'long' })\n // 2024-01-07 is a Sunday.\n return Array.from({ length: 7 }, (_, index) => {\n const date = asDate(toDateString(2024, 1, 7 + ((weekStart.value + index) % 7)))\n return { short: short.format(date), long: long.format(date) }\n })\n})\n\ntype Surface = 'time' | 'day'\ninterface Drag {\n /** `press` is a finger resting where nothing can be dragged: letting go opens the menu. */\n kind: 'move' | 'resize' | 'create' | 'press'\n surface: Surface\n event: CalendarEvent | null\n span: EventSpan | null\n /** Where the press began: the day and, on the time axis, minutes since midnight. */\n origin: { date: string; minutes: number }\n pointerId: number\n touch: boolean\n x0: number\n y0: number\n x: number\n y: number\n started: boolean\n /** The event at its new time, while moving or resizing. */\n draft: CalendarEvent | null\n /** The stretch being drawn, while creating. */\n range: CalendarRange | null\n refusal: string | null\n /** What a resting finger opens the menu on. */\n target: CalendarMenuTarget | null\n}\n\nconst drag = shallowRef<Drag | null>(null)\nconst dragged = computed(() => (drag.value?.started && drag.value.draft) || null)\nconst drawing = computed(() => (drag.value?.started && drag.value.range) || null)\nconst refused = computed(() => !!(drag.value?.started && drag.value.refusal))\n\n/** The events as shown: the one being dragged at its new time. */\nconst shownEvents = computed(() => {\n const d = drag.value\n if (!d?.started || !d.event || !d.draft) return events.value\n return events.value.map((event) => (event === d.event ? d.draft! : event))\n})\n\n/** Where each event falls; events whose `start` can't be read are left out. */\nconst spans = computed(() => {\n const map = new Map<CalendarEvent, EventSpan>()\n for (const event of shownEvents.value) {\n const span = spanOf(event)\n if (span) map.set(event, span)\n }\n return map\n})\n\n// Events that can't be read are left out; say so once per event while developing.\nconst warned = new WeakSet<CalendarEvent>()\nwatch(\n events,\n (list) => {\n // the app's bundler replaces this, as for Vue's own warnings\n if (process.env.NODE_ENV === 'production') return\n for (const event of list) {\n if (spans.value.has(event) || warned.has(event)) continue\n warned.add(event)\n console.warn(\n `[dou-ui] CalendarView event \"${event.id}\" has a start that can't be read: \"${event.start}\". Use YYYY-MM-DD or YYYY-MM-DD HH:mm.`,\n )\n }\n },\n { immediate: true },\n)\n\n/**\n * Events that run over several days first, so their bars tend to line up\n * from day to day, then the other all-day ones, then timed ones by when\n * they start on the day.\n */\nconst startOn = (event: CalendarEvent, date: string) => {\n // the event being dragged comes first, so it never folds into \"+N\"\n if (event === dragged.value) return -3\n const span = spans.value.get(event)!\n if (span.from !== span.to) return -2\n return span.start === null ? -1 : segmentOn(span, date).start\n}\n\n/** Events by day, a span listed on every day it covers. */\nconst byDay = computed(() => {\n const map = new Map<string, CalendarEvent[]>()\n for (const span of spans.value.values()) {\n for (let date = span.from, guard = 0; date <= span.to && guard < 366; guard++) {\n const list = map.get(date) ?? []\n list.push(span.event)\n map.set(date, list)\n date = addDays(date, 1)\n }\n }\n for (const [date, list] of map)\n // oxlint-disable-next-line unicorn/no-array-sort -- sorts the lists built above\n list.sort((a, b) => startOn(a, date) - startOn(b, date))\n return map\n})\n\nconst weeks = computed<CalendarDay[][]>(() => {\n const first = `${shown.value}-01`\n const offset = (weekday(first) - weekStart.value + 7) % 7\n const start = addDays(first, -offset)\n return Array.from({ length: 6 }, (_row, row) =>\n Array.from({ length: 7 }, (_column, column) => {\n const date = addDays(start, row * 7 + column)\n return {\n date,\n day: parts(date).day,\n outside: date.slice(0, 7) !== shown.value,\n today: date === todayValue,\n selected: date === model.value,\n disabled: !!props.disabledDate?.(date),\n events: byDay.value.get(date) ?? [],\n }\n }),\n )\n})\n\nconst dayOf = (date: string): CalendarDay => ({\n date,\n day: parts(date).day,\n outside: false,\n today: date === todayValue,\n selected: date === model.value,\n disabled: !!props.disabledDate?.(date),\n events: byDay.value.get(date) ?? [],\n})\n\n/** The days on the time axis: the cursor's week, or the cursor alone. */\nconst weekDays = computed<CalendarDay[]>(() => {\n if (view.value === 'day') return [dayOf(cursor.value)]\n const start = addDays(cursor.value, -((weekday(cursor.value) - weekStart.value + 7) % 7))\n return Array.from({ length: 7 }, (_, index) => dayOf(addDays(start, index)))\n})\n\n/** The days on screen, first to last, whatever the view. */\nconst visible = computed(() => visibleRange(cursor.value, view.value, weekStart.value))\nwatch(\n () => `${visible.value.start}/${visible.value.end}`,\n () => emit('rangeChange', { ...visible.value }),\n { immediate: true },\n)\n\n/** The agenda: the days of the month shown that have events, each with its events. */\nconst agenda = computed(() => {\n if (view.value !== 'agenda') return []\n const first = `${shown.value}-01`\n const { year, month: m } = parts(first)\n return Array.from({ length: daysInMonth(year, m) }, (_, index) =>\n dayOf(addDays(first, index)),\n ).filter((day) => day.events.length)\n})\n\n/** When an event takes place on one day of the agenda. */\nfunction agendaTime(event: CalendarEvent, date: string) {\n const span = spans.value.get(event)!\n if (span.start === null) return t('calendarView.allDay')\n const { start, end } = segmentOn(span, date)\n return `${timeText(start)}–${timeText(end)}`\n}\n\nconst viewOptions = computed(() =>\n (props.views ?? []).map((value) => ({ value, label: t(`calendarView.${value}`) })),\n)\n\n/** A timed event starts with its time in a month cell, on its first day. */\nconst monthTime = (event: CalendarEvent, date: string) => {\n const span = spans.value.get(event)!\n if (span.start === null || span.from !== date) return ''\n return `${String(Math.floor(span.start / 60)).padStart(2, '0')}:${String(span.start % 60).padStart(2, '0')}`\n}\n\nconst isStart = (event: CalendarEvent, date: string) => spans.value.get(event)!.from === date\n/** Whether the day opens a row, where a running span shows its title again. */\nconst weekStarts = (date: string) => (weekday(date) - weekStart.value + 7) % 7 === 0\nconst isEnd = (event: CalendarEvent, date: string) => spans.value.get(event)!.to <= date\n\n/** Whether a day is inside the stretch being drawn. */\nconst inDrawing = (date: string) =>\n !!drawing.value && drawing.value.start <= date && date <= drawing.value.end.slice(0, 10)\n/** An all-day event can have its last day dragged, from the day it ends on. */\nconst resizable = (event: CalendarEvent, date: string) =>\n !!props.editable &&\n !event.locked &&\n spans.value.get(event)!.start === null &&\n spans.value.get(event)!.to === date\n/** The agenda moves events by day only: up and down there walk the list. */\nconst keyShortcuts = computed(() =>\n view.value === 'agenda'\n ? 'Alt+ArrowLeft Alt+ArrowRight Alt+Shift+ArrowLeft Alt+Shift+ArrowRight Delete Backspace Shift+F10'\n : 'Alt+ArrowLeft Alt+ArrowRight Alt+ArrowUp Alt+ArrowDown Alt+Shift+ArrowLeft Alt+Shift+ArrowRight Alt+Shift+ArrowUp Alt+Shift+ArrowDown Delete Backspace Shift+F10',\n)\n\n/* choosing and moving */\n\nconst gridRef = useTemplateRef<HTMLElement>('grid')\n\nconst rootRef = useTemplateRef<HTMLElement>('root')\n\nasync function focusCursor() {\n await nextTick()\n ;(gridRef.value ?? rootRef.value)\n ?.querySelector<HTMLElement>(`[data-date=\"${cursor.value}\"]`)\n ?.focus({ preventScroll: true })\n}\n\nfunction choose(day: CalendarDay) {\n if (day.disabled) return\n cursor.value = day.date\n model.value = day.date\n emit('select', day.date)\n}\n\nconst weekdayOf = (date: string) => formatter({ weekday: 'short' }).format(asDate(date))\n\nfunction step(direction: 1 | -1) {\n if (view.value === 'week') cursor.value = addDays(cursor.value, direction * 7)\n else if (view.value === 'day') cursor.value = addDays(cursor.value, direction)\n else cursor.value = addMonths(cursor.value, direction)\n}\n\nconst stepLabels = computed(() =>\n view.value === 'week'\n ? [t('calendarView.prevWeek'), t('calendarView.nextWeek')]\n : view.value === 'day'\n ? [t('calendarView.prevDay'), t('calendarView.nextDay')]\n : [t('datePicker.prevMonth'), t('datePicker.nextMonth')],\n)\n\nfunction goToday() {\n cursor.value = todayValue\n}\n\n/** Left and right move between the days at the head of the week view. */\nfunction onTimeKeydown(event: KeyboardEvent) {\n if (view.value !== 'week' || !(event.target as HTMLElement).dataset.date) return\n const moves: Record<string, number> = { ArrowLeft: -1, ArrowRight: 1 }\n const move = moves[event.key]\n if (!move) return\n event.preventDefault()\n const days = weekDays.value\n const index = days.findIndex((day) => day.date === cursor.value) + move\n if (index < 0 || index >= days.length) return\n cursor.value = days[index]!.date\n void focusCursor()\n}\n\nfunction onKeydown(event: KeyboardEvent) {\n const date = cursor.value\n const column = (weekday(date) - weekStart.value + 7) % 7\n const keys: Record<string, () => string> = {\n ArrowLeft: () => addDays(date, -1),\n ArrowRight: () => addDays(date, 1),\n ArrowUp: () => addDays(date, -7),\n ArrowDown: () => addDays(date, 7),\n Home: () => addDays(date, -column),\n End: () => addDays(date, 6 - column),\n PageUp: () => addMonths(date, event.shiftKey ? -12 : -1),\n PageDown: () => addMonths(date, event.shiftKey ? 12 : 1),\n }\n const next = keys[event.key]?.()\n if (next) {\n event.preventDefault()\n cursor.value = next\n void focusCursor()\n return\n }\n if (event.key === 'Enter' || event.key === ' ') {\n if ((event.target as HTMLElement).closest('button')) return\n event.preventDefault()\n const day = weeks.value.flat().find((cell) => cell.date === date)\n if (day) choose(day)\n }\n}\n\nfunction dayLabel(day: CalendarDay) {\n const date = fullDate.value.format(asDate(day.date))\n if (!day.events.length) return date\n return t('calendarView.dayWithEvents', { date, count: day.events.length })\n}\n\n/* editing: dragging, drawing, the menu and the keys */\n\nconst LONG_PRESS = 300\nconst THRESHOLD = { mouse: 3, touch: 6 }\n/** How close to the top or bottom of the time axis a drag starts scrolling it, in px. */\nconst EDGE = 32\n\nconst live = ref('')\nfunction announce(text: string) {\n // the same text twice in a row is read again\n live.value = live.value === text ? `${text} ` : text\n}\n\nlet hold: PointerHold | null = null\nlet pressTimer: ReturnType<typeof setTimeout> | undefined\nlet frame = 0\n/** Beside the pointer while dragging: the new time, or why it can't go there. */\nconst tip = computed(() => {\n const d = drag.value\n if (!d?.started || d.kind === 'press') return ''\n if (d.refusal) return d.refusal\n if (d.draft) return rangeText(d.draft)\n return d.range ? rangeText({ id: '', title: '', ...d.range }) : ''\n})\n\n/** Where the pointer is, kept apart from the drag so the tip follows it between snaps. */\nconst pointer = ref({ x: 0, y: 0 })\n/**\n * Beside the pointer, where it stays in view: on its left once past the\n * middle of the window, and above it for a finger, which would cover it,\n * or near the bottom of the window.\n */\nconst tipStyle = computed(() => {\n const d = drag.value\n if (!d || typeof window === 'undefined') return {}\n const { x, y } = pointer.value\n const left = x > window.innerWidth / 2\n const above = d.touch || y + 16 + 40 > window.innerHeight\n return {\n left: `${left ? x - 12 : x + 12}px`,\n top: `${above ? y - (d.touch ? 40 : 16) : y + 16}px`,\n transform: `translate(${left ? '-100%' : '0'}, ${above ? '-100%' : '0'})`,\n }\n})\n\n/** A drag ended a moment ago: the click that follows it is not a click. */\nlet swallow = false\n/** When a finger last went down; the browser's own long-press menu is ignored after it. */\nlet touchedAt = 0\n\nconst byId = (id: string | undefined) => events.value.find((event) => event.id === id)\nconst canEdit = (event: CalendarEvent) => !!props.editable && !event.locked\n\nconst dayFormat = computed(() => formatter({ month: 'short', day: 'numeric' }))\nconst clock = computed(() =>\n formatter({\n hour: 'numeric',\n minute: '2-digit',\n hourCycle: /A/.test(locale.value.region.time) ? 'h12' : 'h23',\n }),\n)\nconst timeText = (minutes: number) => clock.value.format(new Date(Date.UTC(2000, 0, 1, 0, minutes)))\n\n/** When an event takes place, as read out after a change. */\nfunction rangeText(event: CalendarEvent) {\n const span = spanOf(event)\n if (!span) return ''\n const from = dayFormat.value.format(asDate(span.from))\n const to = dayFormat.value.format(asDate(span.to))\n if (span.start === null) return span.from === span.to ? from : `${from} – ${to}`\n if (span.from === span.to) return `${from} ${timeText(span.start)}–${timeText(span.end!)}`\n return `${from} ${timeText(span.start)} – ${to} ${timeText(span.end!)}`\n}\n\n/**\n * The day and time under a point. The nearest column or day is taken when\n * the point is off to the side, so a drag past the edge stays on the last\n * day; on the time axis the time is kept inside the hours shown.\n */\nfunction pointAt(surface: Surface, x: number, y: number) {\n const cells = rootRef.value?.querySelectorAll<HTMLElement>(`[data-drop=\"${surface}\"]`) ?? []\n let best: { cell: HTMLElement; rect: DOMRect } | null = null\n let distance = Infinity\n for (const cell of cells) {\n const rect = cell.getBoundingClientRect()\n const dx = Math.max(rect.left - x, 0, x - rect.right)\n const dy = surface === 'time' ? 0 : Math.max(rect.top - y, 0, y - rect.bottom)\n if (dx * dx + dy * dy < distance) {\n distance = dx * dx + dy * dy\n best = { cell, rect }\n }\n }\n if (!best) return null\n const date = best.cell.dataset.date!\n if (surface === 'day') return { date, minutes: 0 }\n const from = Number(best.cell.dataset.from)\n const to = Number(best.cell.dataset.to)\n const ratio = best.rect.height ? (y - best.rect.top) / best.rect.height : 0\n return { date, minutes: Math.min(to, Math.max(from, from + ratio * (to - from))) }\n}\n\n/** `null` when the change may be made, else why not. */\nfunction refusalOf(kind: CalendarMove['kind'], event: CalendarEvent | null, next: CalendarRange) {\n const span = spanOf({ id: '', title: '', start: next.start, end: next.end })\n if (span && props.disabledDate) {\n for (let date = span.from, guard = 0; date <= span.to && guard < 366; guard++) {\n if (props.disabledDate(date)) return t('calendarView.disabledDay')\n date = addDays(date, 1)\n }\n }\n if (!props.canMove) return null\n const answer = props.canMove({ kind, event, start: next.start, end: next.end }, events.value)\n if (answer === true) return null\n return typeof answer === 'string' && answer ? answer : t('calendarView.refused')\n}\n\nconst DAY = 24 * 60\n/** The minutes the time axis covers, as the time grid draws it. */\nconst axis = computed(() => {\n const from = Math.max(0, Math.min(23, Math.floor(props.hours[0])))\n const to = Math.max(from + 1, Math.min(24, Math.ceil(props.hours[1])))\n return { from: from * 60, to: to * 60 }\n})\n\n/**\n * Where a timed event moved on the time axis starts: an event that sits\n * within one day stays inside the hours shown, so it is never written to a\n * time the axis can't show. One that runs over midnight moves freely.\n */\nfunction fitHours(span: EventSpan, start: number) {\n const { from, to } = axis.value\n const length = stampsOf(span).end - stampsOf(span).start\n if (span.from !== span.to || length > to - from) return start\n const day = Math.floor(start / DAY)\n return day * DAY + Math.min(Math.max(start - day * DAY, from), to - length)\n}\n\nconst roundTo = (value: number, by: number) => Math.round(value / by) * by\nconst floorTo = (value: number, by: number) => Math.floor(value / by) * by\n\n/** Work out what the drag would change, with the pointer where it is now. */\nfunction update(d: Drag) {\n const point = pointAt(d.surface, d.x, d.y)\n if (!point || d.kind === 'press') return\n const snap = Math.max(1, Math.round(props.snap))\n let draft: CalendarEvent | null = null\n let range: CalendarRange | null = null\n if (d.surface === 'time') {\n const now = toStamp(point.date, point.minutes)\n const origin = toStamp(d.origin.date, d.origin.minutes)\n if (d.kind === 'create') {\n const a = floorTo(origin, snap)\n const b = floorTo(now, snap)\n range = { start: formatStamp(Math.min(a, b)), end: formatStamp(Math.max(a, b) + snap) }\n } else {\n const { start } = stampsOf(d.span!)\n if (d.kind === 'move') {\n const next = fitHours(d.span!, roundTo(start + now - origin, snap))\n draft = moveEvent(d.event!, d.span!, { minutes: next - start })\n } else {\n draft = { ...d.event!, end: formatStamp(Math.max(start + snap, roundTo(now, snap))) }\n }\n }\n } else {\n const days = dayIndex(point.date) - dayIndex(d.origin.date)\n if (d.kind === 'create') {\n const [a, b] = [d.origin.date, point.date]\n range = a <= b ? { start: a, end: b } : { start: b, end: a }\n } else if (d.kind === 'move') {\n draft = moveEvent(d.event!, d.span!, { days })\n } else {\n draft = { ...d.event!, end: point.date < d.span!.from ? d.span!.from : point.date }\n }\n }\n if (draft) {\n if (d.draft && sameTime(d.draft, draft)) return\n d.draft = draft\n d.refusal = sameTime(draft, d.event!)\n ? null\n : refusalOf(d.kind, d.event, { start: draft.start, end: endOf(draft) })\n } else if (range) {\n if (d.range?.start === range.start && d.range.end === range.end) return\n d.range = range\n d.refusal = refusalOf('create', null, range)\n }\n triggerRef(drag)\n}\n\n/** The menu target under a pointer or a key press. */\nfunction targetOf(el: HTMLElement, x: number, y: number): CalendarMenuTarget | null {\n const chip = el.closest<HTMLElement>('[data-event-id]')\n const event = chip && byId(chip.dataset.eventId)\n if (event) return { kind: 'event', event }\n const cell = el.closest<HTMLElement>('[data-drop]')\n if (!cell) return null\n if (cell.dataset.drop === 'day') return { kind: 'day', date: cell.dataset.date! }\n const point = pointAt('time', x, y)\n if (!point) return null\n const minutes = Math.min(\n floorTo(point.minutes, Math.max(1, props.snap)),\n Number(cell.dataset.to) - props.snap,\n )\n return { kind: 'time', start: `${point.date} ${formatTime(Math.max(0, minutes))}` }\n}\n\nfunction onPointerDown(event: PointerEvent) {\n if (event.button !== 0 || drag.value) return\n const el = event.target as HTMLElement\n const touch = event.pointerType !== 'mouse'\n const chip = el.closest<HTMLElement>('[data-event-id]')\n const item = chip ? byId(chip.dataset.eventId) : undefined\n const cell = el.closest<HTMLElement>('[data-drop]')\n if (!cell) {\n // the agenda has nothing to drag: a finger resting on an event opens its menu\n const day = el.closest<HTMLElement>('[data-agenda-date]')?.dataset.agendaDate\n if (touch && item && day) {\n startDrag(event, {\n kind: 'press',\n surface: 'day',\n event: item,\n span: spans.value.get(item) ?? null,\n origin: { date: day, minutes: 0 },\n target: { kind: 'event', event: item },\n })\n }\n return\n }\n const surface = cell.dataset.drop as Surface\n let kind: Drag['kind']\n if (item) {\n const span = spans.value.get(item)\n if (!span) return\n if (!canEdit(item)) kind = 'press'\n else if (el.closest('[data-resize]')) kind = 'resize'\n else kind = 'move'\n } else {\n if (chip || el.closest('button, a, input')) return\n if (cell.matches('.is-disabled')) kind = 'press'\n else kind = props.editable ? 'create' : 'press'\n }\n // a mouse has its own menu button; only a finger needs the resting press\n if (kind === 'press' && !touch) return\n const origin = pointAt(surface, event.clientX, event.clientY)\n if (!origin) return\n startDrag(event, {\n kind,\n surface,\n event: item ?? null,\n span: item ? spans.value.get(item)! : null,\n origin,\n target: targetOf(el, event.clientX, event.clientY),\n })\n}\n\n/** Starts following a pointer that went down; a finger picks up after a short rest. */\nfunction startDrag(\n event: PointerEvent,\n start: Pick<Drag, 'kind' | 'surface' | 'event' | 'span' | 'origin' | 'target'>,\n) {\n const touch = event.pointerType !== 'mouse'\n if (touch) touchedAt = Date.now()\n drag.value = {\n ...start,\n pointerId: event.pointerId,\n touch,\n x0: event.clientX,\n y0: event.clientY,\n x: event.clientX,\n y: event.clientY,\n started: false,\n draft: null,\n range: null,\n refusal: null,\n }\n pointer.value = { x: event.clientX, y: event.clientY }\n hold = holdPointer(event, rootRef.value)\n window.addEventListener('pointermove', onDragMove)\n window.addEventListener('pointerup', onDragEnd)\n window.addEventListener('pointercancel', stopDrag)\n window.addEventListener('keydown', onDragKey, true)\n // switching away mid-drag drops it\n window.addEventListener('blur', stopDrag)\n document.addEventListener('visibilitychange', stopDrag)\n // a finger picks an event up after a short rest; moving sooner scrolls instead\n if (touch) {\n const d = drag.value\n pressTimer = setTimeout(() => {\n if (drag.value !== d) return\n begin(d)\n navigator.vibrate?.(8)\n }, LONG_PRESS)\n }\n}\n\nfunction begin(d: Drag) {\n d.started = true\n hold?.grab()\n if (d.kind !== 'press') {\n update(d)\n if (d.surface === 'time') frame = requestAnimationFrame(autoScroll)\n }\n triggerRef(drag)\n}\n\nfunction onDragMove(event: PointerEvent) {\n const d = drag.value\n if (!d || event.pointerId !== d.pointerId) return\n d.x = event.clientX\n d.y = event.clientY\n pointer.value = { x: d.x, y: d.y }\n const far = Math.hypot(d.x - d.x0, d.y - d.y0) > THRESHOLD[d.touch ? 'touch' : 'mouse']\n if (!d.started) {\n // a finger that moves before resting scrolls the page\n if (d.touch) return void (far && stopDrag())\n if (far) begin(d)\n return\n }\n if (d.kind === 'press') return\n update(d)\n}\n\nfunction onDragEnd(event: PointerEvent) {\n const d = drag.value\n if (!d || event.pointerId !== d.pointerId) return\n const still = Math.hypot(d.x - d.x0, d.y - d.y0) <= THRESHOLD.touch\n stopDrag()\n if (!d.started) return\n swallow = true\n setTimeout(() => (swallow = false))\n // a finger rested and let go without moving: the menu\n if (d.touch && still) {\n if (d.target && menuAllowed(d.target, event.clientX, event.clientY))\n showMenu(d.target, event.clientX, event.clientY)\n return\n }\n if (d.refusal) return announce(d.refusal)\n if (d.kind === 'create' && d.range) emit('create', { ...d.range })\n else if ((d.kind === 'move' || d.kind === 'resize') && d.draft && !sameTime(d.draft, d.event!))\n commit(d.kind, d.event!, d.draft)\n}\n\nfunction stopDrag() {\n clearTimeout(pressTimer)\n cancelAnimationFrame(frame)\n hold?.release()\n hold = null\n window.removeEventListener('pointermove', onDragMove)\n window.removeEventListener('pointerup', onDragEnd)\n window.removeEventListener('pointercancel', stopDrag)\n window.removeEventListener('keydown', onDragKey, true)\n window.removeEventListener('blur', stopDrag)\n if (typeof document !== 'undefined') document.removeEventListener('visibilitychange', stopDrag)\n drag.value = null\n}\n\nfunction onDragKey(event: KeyboardEvent) {\n if (event.key !== 'Escape') return\n event.preventDefault()\n event.stopPropagation()\n if (drag.value?.started) {\n swallow = true\n setTimeout(() => (swallow = false))\n }\n stopDrag()\n}\n\n/** Auto-scroll speed in px per frame, `gap` px from the edge. */\nconst speed = (gap: number) => (gap < EDGE ? Math.ceil(((EDGE - gap) / EDGE) * 12) : 0)\n\n/** Scrolls the time axis while a drag rests near its top or bottom. */\nfunction autoScroll() {\n const d = drag.value\n const el = rootRef.value?.querySelector<HTMLElement>(`.${ns.e('scroller')}`)\n if (!d?.started || !el) return\n const rect = el.getBoundingClientRect()\n const dy = speed(d.y - rect.top) ? -speed(d.y - rect.top) : speed(rect.bottom - d.y)\n if (dy) {\n const before = el.scrollTop\n el.scrollTop += dy\n if (el.scrollTop !== before) update(d)\n }\n frame = requestAnimationFrame(autoScroll)\n}\n\nfunction onClickCapture(event: MouseEvent) {\n if (!swallow) return\n event.stopPropagation()\n event.preventDefault()\n}\n\n/** Writes the new version of an event, then keeps the keyboard and the view on it. */\nfunction commit(kind: 'move' | 'resize', previous: CalendarEvent, next: CalendarEvent) {\n events.value = events.value.map((event) => (event === previous ? next : event))\n emit('change', { kind, event: next, previous })\n announce(t('calendarView.changed', { title: next.title, range: rangeText(next) }))\n}\n\nfunction remove(event: CalendarEvent) {\n events.value = events.value.filter((item) => item !== event)\n emit('change', { kind: 'remove', event })\n announce(t('calendarView.removed', { title: event.title }))\n}\n\n/** The element of an event on screen. */\nconst chipOf = (id: string | undefined) =>\n id ? rootRef.value?.querySelector<HTMLElement>(`[data-event-id=\"${CSS.escape(id)}\"]`) : null\n\n/**\n * Removes an event from the keyboard, then puts the keyboard on the next\n * event of the same day, or the one before, or else on the day itself.\n */\nasync function removeByKey(item: CalendarEvent, chip: HTMLElement) {\n const siblings = [...(chip.parentElement?.querySelectorAll<HTMLElement>('[data-event-id]') ?? [])]\n const index = siblings.indexOf(chip)\n // oxlint-disable-next-line unicorn/no-array-reverse -- reverses a fresh slice\n const near = [...siblings.slice(index + 1), ...siblings.slice(0, index).reverse()]\n .map((el) => el.dataset.eventId!)\n .find((id) => id !== item.id)\n const holder = chip.closest<HTMLElement>('[data-date], [data-agenda-date]')\n const day = holder?.dataset.date ?? holder?.dataset.agendaDate\n // in the agenda a day without events goes away: fall back to the next day's first event, or the one before's\n const listed = chip.closest<HTMLElement>('[data-agenda-date]')\n const neighbour = (listed?.nextElementSibling ?? listed?.previousElementSibling) as\n HTMLElement | null | undefined\n const fallback = neighbour?.querySelector<HTMLElement>('[data-event-id]')?.dataset.eventId\n remove(item)\n await nextTick()\n const next = chipOf(near) ?? chipOf(fallback)\n if (next) return next.focus()\n if (day) cursor.value = day\n void focusCursor()\n}\n\n/* the menu */\n\nconst menuRef = useTemplateRef<InstanceType<typeof DouContextMenu>>('menu')\nconst menuOpen = ref(false)\nconst menuItems = shallowRef<ContextMenuItem[]>([])\n\n/**\n * Sends `eventContextmenu` before an event's menu opens; `false` when the\n * app kept it closed. A resting finger or the menu key has no browser event\n * of its own, so it gets a stand-in.\n */\nfunction menuAllowed(target: CalendarMenuTarget, x: number, y: number, native?: MouseEvent) {\n if (target.kind !== 'event') return true\n const event =\n native ?? new MouseEvent('contextmenu', { cancelable: true, clientX: x, clientY: y })\n emit('eventContextmenu', target.event, event)\n return !event.defaultPrevented\n}\n\n/** Opens the menu for a target; `false` when it would be empty. */\nfunction showMenu(target: CalendarMenuTarget, x: number, y: number) {\n const items = [...(props.contextMenu?.(target) ?? [])]\n if (props.menu && target.kind === 'event' && canEdit(target.event)) {\n items.push({\n key: '\\0delete',\n label: t('calendarView.deleteEvent'),\n icon: TrashIcon,\n shortcut: 'Delete',\n tone: 'danger',\n divided: items.length > 0,\n run: () => remove(target.event),\n })\n }\n if (!items.length) return false\n menuItems.value = items\n menuRef.value?.open(x, y)\n return true\n}\n\nfunction onContextmenu(event: MouseEvent) {\n // a finger's long press opens the menu itself, when it lets go\n if (Date.now() - touchedAt < 1000) return event.preventDefault()\n const target = targetOf(event.target as HTMLElement, event.clientX, event.clientY)\n if (!target) return\n if (!menuAllowed(target, event.clientX, event.clientY, event)) return\n if (showMenu(target, event.clientX, event.clientY)) event.preventDefault()\n}\n\n/* keys on an event */\n\n/**\n * Puts the keyboard back on an event after a change; when the app stored it\n * under a new id, e.g. one occurrence of a series, on the day it went to.\n */\nasync function focusEvent(id: string) {\n await nextTick()\n const chip = chipOf(id)\n if (chip) chip.focus()\n else void focusCursor()\n}\n\n/**\n * On a focused event: the menu key opens its menu, `Delete` removes it,\n * `Alt` with the arrows moves it and `Alt` + `Shift` with the arrows moves\n * its end. Left and right go by a day; up and down by `snap` on the time\n * axis, or by a week in the month.\n */\nfunction onEventKeydown(event: KeyboardEvent) {\n const chip = (event.target as HTMLElement).closest<HTMLElement>('[data-event-id]')\n const item = chip && byId(chip.dataset.eventId)\n if (!item) return\n const done = () => {\n event.preventDefault()\n event.stopPropagation()\n }\n if (event.key === 'ContextMenu' || (event.shiftKey && event.key === 'F10')) {\n const rect = chip.getBoundingClientRect()\n const target = { kind: 'event', event: item } as const\n if (!menuAllowed(target, rect.left, rect.bottom)) return done()\n if (showMenu(target, rect.left, rect.bottom)) done()\n return\n }\n if (!canEdit(item)) return\n if (event.key === 'Delete' || event.key === 'Backspace') {\n done()\n removeByKey(item, chip)\n return\n }\n const span = spans.value.get(item)\n if (!event.altKey || !span) return\n const timed = span.start !== null\n const onAxis = timed && view.value !== 'month' && view.value !== 'agenda'\n const snap = Math.max(1, Math.round(props.snap))\n const keys: Record<string, { days?: number; minutes?: number }> = {\n ArrowLeft: { days: -1 },\n ArrowRight: { days: 1 },\n ArrowUp: onAxis ? { minutes: -snap } : view.value === 'month' ? { days: -7 } : {},\n ArrowDown: onAxis ? { minutes: snap } : view.value === 'month' ? { days: 7 } : {},\n }\n const by = keys[event.key]\n if (!by || (!by.days && !by.minutes)) return\n done()\n let next: CalendarEvent\n let kind: 'move' | 'resize' = 'move'\n if (event.shiftKey) {\n kind = 'resize'\n if (timed) {\n const { start, end } = stampsOf(span)\n const delta = (by.days ?? 0) * DAY + (by.minutes ?? 0)\n let last = Math.max(start + snap, end + delta)\n // an event within one day on the time axis ends by the end of the hours shown\n if (onAxis && span.from === span.to) {\n const day = Math.floor(start / DAY)\n last = Math.min(last, day * DAY + axis.value.to)\n }\n next = { ...item, end: formatStamp(last) }\n } else {\n const last = addDays(span.to, by.days ?? 0)\n next = { ...item, end: last < span.from ? span.from : last }\n }\n } else if (onAxis) {\n const { start } = stampsOf(span)\n const at = fitHours(span, start + (by.days ?? 0) * DAY + (by.minutes ?? 0))\n next = moveEvent(item, span, { minutes: at - start })\n } else {\n next = moveEvent(item, span, by)\n }\n if (sameTime(next, item)) return\n const refusal = refusalOf(kind, item, { start: next.start, end: endOf(next) })\n if (refusal) return announce(refusal)\n commit(kind, item, next)\n const placed = spanOf(next)\n if (placed) cursor.value = kind === 'resize' ? placed.to : placed.from\n void focusEvent(next.id)\n}\n\nonBeforeUnmount(stopDrag)\n\n// Editing without anywhere to write the change: each drop springs back. Say so once while developing.\nconst instance = getCurrentInstance()\nwatch(\n () => props.editable,\n (editable) => {\n if (process.env.NODE_ENV === 'production' || !editable) return\n const bound = instance?.vnode.props ?? {}\n const has = (...names: string[]) => names.some((name) => name in bound)\n if (has('events') && !has('onUpdate:events', 'onChange')) {\n console.warn(\n '[dou-ui] CalendarView is editable but nothing receives the changes, so they spring back. Bind v-model:events, or listen to change.',\n )\n }\n },\n { immediate: true },\n)\n\ndefineExpose<CalendarViewExpose>({\n show: (date) => (cursor.value = date.length === 7 ? `${date}-01` : date),\n focus: () => void focusCursor(),\n})\n</script>\n\n<template>\n <div\n ref=\"root\"\n :class=\"[\n ns.b(),\n ns.m(view),\n ns.is('editable', !!editable),\n ns.is('bordered', bordered),\n ns.is('dragging', !!drag?.started && drag.kind !== 'press'),\n drag?.started && drag.kind !== 'press' ? `is-dragging-${drag.kind}` : '',\n ]\"\n @pointerdown=\"onPointerDown\"\n @contextmenu=\"onContextmenu\"\n @click.capture=\"onClickCapture\"\n @keydown.capture=\"onEventKeydown\"\n >\n <div :class=\"ns.e('header')\">\n <h2 :class=\"ns.e('title')\" aria-live=\"polite\">{{ title }}</h2>\n <div :class=\"ns.e('nav')\">\n <slot name=\"extra\" />\n <DouSegmentedControl\n v-if=\"viewOptions.length > 1\"\n v-model=\"view\"\n :options=\"viewOptions\"\n size=\"sm\"\n />\n <button type=\"button\" :class=\"ns.e('today')\" @click=\"goToday\">\n {{ t('datePicker.today') }}\n </button>\n <button type=\"button\" :class=\"ns.e('step')\" :aria-label=\"stepLabels[0]\" @click=\"step(-1)\">\n <ChevronLeftIcon />\n </button>\n <button type=\"button\" :class=\"ns.e('step')\" :aria-label=\"stepLabels[1]\" @click=\"step(1)\">\n <ChevronRightIcon />\n </button>\n </div>\n </div>\n <div\n v-if=\"view === 'agenda'\"\n ref=\"grid\"\n role=\"list\"\n :class=\"ns.e('agenda')\"\n :aria-label=\"title\"\n >\n <p v-if=\"!agenda.length\" :class=\"ns.e('agenda-empty')\">{{ t('calendarView.empty') }}</p>\n <div\n v-for=\"day in agenda\"\n :key=\"day.date\"\n role=\"listitem\"\n :data-agenda-date=\"day.date\"\n :class=\"[ns.e('agenda-day'), ns.is('today', day.today), ns.is('selected', day.selected)]\"\n >\n <button\n type=\"button\"\n :class=\"ns.e('agenda-date')\"\n :data-date=\"day.date\"\n :disabled=\"day.disabled\"\n :aria-label=\"dayLabel(day)\"\n :aria-current=\"day.today ? 'date' : undefined\"\n :aria-pressed=\"day.selected\"\n @click=\"choose(day)\"\n >\n <span :class=\"ns.e('number')\">{{ day.day }}</span>\n <span :class=\"ns.e('agenda-weekday')\">{{ weekdayOf(day.date) }}</span>\n </button>\n <div :class=\"ns.e('agenda-events')\">\n <button\n v-for=\"event in day.events\"\n :key=\"event.id\"\n type=\"button\"\n :data-event-id=\"event.id\"\n :class=\"[\n ns.e('agenda-event'),\n `${ns.e('event')}--${event.tone ?? 'primary'}`,\n ns.is('locked', !!editable && !!event.locked),\n ]\"\n :aria-keyshortcuts=\"editable && !event.locked ? keyShortcuts : undefined\"\n @click=\"emit('eventClick', event)\"\n >\n <span :class=\"ns.e('agenda-time')\">{{ agendaTime(event, day.date) }}</span>\n <span :class=\"ns.e('agenda-title')\">\n <slot name=\"event\" :event=\"event\" :day=\"day\">{{ event.title }}</slot>\n </span>\n </button>\n </div>\n </div>\n </div>\n <TimeGrid\n v-else-if=\"view !== 'month'\"\n :days=\"weekDays\"\n :spans=\"spans\"\n :hours=\"hours\"\n :scroll-time=\"scrollTime\"\n :cursor=\"cursor\"\n :as-date=\"asDate\"\n :editable=\"!!editable\"\n :dragged=\"dragged\"\n :refused=\"refused\"\n :drawing=\"drawing\"\n :snap=\"snap\"\n @choose=\"choose\"\n @event-click=\"emit('eventClick', $event)\"\n @time-click=\"emit('timeClick', $event)\"\n @keydown=\"onTimeKeydown\"\n >\n <template v-if=\"slots.event\" #event=\"scope\">\n <slot name=\"event\" v-bind=\"scope\" />\n </template>\n </TimeGrid>\n <div\n v-else\n ref=\"grid\"\n role=\"grid\"\n :class=\"ns.e('grid')\"\n :aria-label=\"title\"\n @keydown=\"onKeydown\"\n >\n <div role=\"row\" :class=\"ns.e('weekdays')\">\n <div\n v-for=\"weekdayName in weekdays\"\n :key=\"weekdayName.long\"\n role=\"columnheader\"\n :class=\"ns.e('weekday')\"\n :aria-label=\"weekdayName.long\"\n >\n {{ weekdayName.short }}\n </div>\n </div>\n <div v-for=\"(week, row) in weeks\" :key=\"row\" role=\"row\" :class=\"ns.e('week')\">\n <div\n v-for=\"day in week\"\n :key=\"day.date\"\n role=\"gridcell\"\n data-drop=\"day\"\n :data-date=\"day.date\"\n :tabindex=\"day.date === cursor ? 0 : -1\"\n :aria-selected=\"day.selected\"\n :aria-disabled=\"day.disabled || undefined\"\n :aria-current=\"day.today ? 'date' : undefined\"\n :aria-label=\"dayLabel(day)\"\n :class=\"[\n ns.e('day'),\n ns.is('outside', day.outside),\n ns.is('today', day.today),\n ns.is('selected', day.selected),\n ns.is('disabled', day.disabled),\n ns.is('drawing', inDrawing(day.date)),\n ns.is('refused', inDrawing(day.date) && refused),\n ]\"\n @click=\"choose(day)\"\n >\n <span :class=\"ns.e('number')\">{{ day.day }}</span>\n <div :class=\"ns.e('content')\">\n <slot name=\"cell\" v-bind=\"day\">\n <button\n v-for=\"event in day.events.slice(0, maxEvents)\"\n :key=\"event.id\"\n type=\"button\"\n :data-event-id=\"event.id\"\n :tabindex=\"day.date === cursor ? 0 : -1\"\n :class=\"[\n ns.e('event'),\n `${ns.e('event')}--${event.tone ?? 'primary'}`,\n ns.is('start', isStart(event, day.date)),\n ns.is('continued', !isStart(event, day.date) && !weekStarts(day.date)),\n ns.is('end', isEnd(event, day.date)),\n ns.is('locked', !!editable && !!event.locked),\n ns.is('dragging', event === dragged),\n ns.is('refused', event === dragged && refused),\n ]\"\n :title=\"event.title\"\n :aria-keyshortcuts=\"editable && !event.locked ? keyShortcuts : undefined\"\n @click.stop=\"emit('eventClick', event)\"\n >\n <slot name=\"event\" :event=\"event\" :day=\"day\"\n ><span v-if=\"monthTime(event, day.date)\" :class=\"ns.e('event-clock')\">{{\n monthTime(event, day.date)\n }}</span\n >{{ event.title }}</slot\n ><span\n v-if=\"resizable(event, day.date)\"\n :class=\"[ns.e('resize'), `${ns.e('resize')}--x`]\"\n data-resize\n aria-hidden=\"true\"\n />\n </button>\n <span v-if=\"day.events.length > maxEvents\" :class=\"ns.e('more')\">\n {{ t('calendarView.more', { count: day.events.length - maxEvents }) }}\n </span>\n </slot>\n </div>\n </div>\n </div>\n </div>\n <span :class=\"ns.e('live')\" aria-live=\"polite\">{{ live }}</span>\n <div\n v-if=\"tip\"\n :class=\"[ns.e('tip'), ns.is('refused', refused)]\"\n :style=\"tipStyle\"\n aria-hidden=\"true\"\n >\n {{ tip }}\n </div>\n <!-- The calendar opens the menu itself; the empty anchor only gives it a place in the page. -->\n <DouContextMenu ref=\"menu\" v-model:open=\"menuOpen\" :items=\"menuItems\">\n <span :class=\"ns.e('menu-anchor')\" aria-hidden=\"true\" />\n </DouContextMenu>\n </div>\n</template>\n"],"mappings":""}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useNamespace } from "../../utils/namespace.js";
|
|
2
2
|
import { useLocale } from "../config-provider/use-config.js";
|
|
3
|
-
import { addDays, addMonths, daysInMonth, parts, toDateString, today, weekday } from "../date-picker/date.js";
|
|
4
3
|
import { holdPointer } from "../../utils/pointer.js";
|
|
4
|
+
import { addDays, addMonths, daysInMonth, parts, toDateString, today, weekday } from "../date-picker/date.js";
|
|
5
5
|
import { DouContextMenu } from "../context-menu/index.js";
|
|
6
6
|
import { DouSegmentedControl } from "../segmented-control/index.js";
|
|
7
7
|
import { segmentOn, spanOf } from "./time-layout.js";
|
|
@@ -71,7 +71,11 @@ var calendar_view_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ de
|
|
|
71
71
|
default: true
|
|
72
72
|
},
|
|
73
73
|
maxEvents: { default: 3 },
|
|
74
|
-
disabledDate: {}
|
|
74
|
+
disabledDate: {},
|
|
75
|
+
bordered: {
|
|
76
|
+
type: Boolean,
|
|
77
|
+
default: true
|
|
78
|
+
}
|
|
75
79
|
}, {
|
|
76
80
|
"modelValue": {},
|
|
77
81
|
"modelModifiers": {},
|
|
@@ -954,6 +958,7 @@ var calendar_view_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ de
|
|
|
954
958
|
unref(ns).b(),
|
|
955
959
|
unref(ns).m(view.value),
|
|
956
960
|
unref(ns).is("editable", !!__props.editable),
|
|
961
|
+
unref(ns).is("bordered", __props.bordered),
|
|
957
962
|
unref(ns).is("dragging", !!drag.value?.started && drag.value.kind !== "press"),
|
|
958
963
|
drag.value?.started && drag.value.kind !== "press" ? `is-dragging-${drag.value.kind}` : ""
|
|
959
964
|
]),
|