@adia-ai/web-components 0.8.39 → 0.8.41
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/CHANGELOG.md +59 -0
- package/MIGRATION.md +1269 -3
- package/README.md +25 -30
- package/USAGE.md +26 -0
- package/bin/audit-template-children.mjs +556 -0
- package/components/accordion/accordion-item.a2ui.json +4 -0
- package/components/accordion/accordion-item.yaml +2 -0
- package/components/accordion/accordion.a2ui.json +4 -0
- package/components/accordion/accordion.yaml +2 -0
- package/components/action-list/action-item.a2ui.json +24 -2
- package/components/action-list/action-item.yaml +28 -3
- package/components/action-list/action-list.a2ui.json +6 -1
- package/components/action-list/action-list.class.js +52 -11
- package/components/action-list/action-list.css +43 -3
- package/components/action-list/action-list.d.ts +4 -2
- package/components/action-list/action-list.yaml +5 -0
- package/components/adia-mark/adia-mark.a2ui.json +1 -0
- package/components/adia-wordmark/adia-wordmark.a2ui.json +1 -0
- package/components/agent-artifact/agent-artifact.a2ui.json +1 -0
- package/components/agent-artifact/agent-artifact.css +1 -1
- package/components/agent-feedback-bar/agent-feedback-bar.a2ui.json +6 -5
- package/components/agent-feedback-bar/agent-feedback-bar.yaml +6 -2
- package/components/agent-questions/agent-questions.a2ui.json +1 -0
- package/components/agent-reasoning/agent-reasoning.a2ui.json +6 -5
- package/components/agent-reasoning/agent-reasoning.css +2 -2
- package/components/agent-reasoning/agent-reasoning.yaml +6 -2
- package/components/agent-suggestions/agent-suggestions.a2ui.json +11 -2
- package/components/agent-suggestions/agent-suggestions.class.js +2 -1
- package/components/agent-suggestions/agent-suggestions.d.ts +4 -4
- package/components/agent-suggestions/agent-suggestions.yaml +4 -2
- package/components/agent-trace/agent-trace.a2ui.json +6 -5
- package/components/agent-trace/agent-trace.css +3 -3
- package/components/agent-trace/agent-trace.yaml +6 -2
- package/components/alert/alert.a2ui.json +1 -0
- package/components/alert/alert.css +2 -2
- package/components/anchor-bar/anchor-bar.a2ui.json +1 -0
- package/components/aside/aside.a2ui.json +1 -0
- package/components/avatar/avatar-group.a2ui.json +7 -1
- package/components/avatar/avatar-group.yaml +1 -0
- package/components/avatar/avatar.a2ui.json +12 -11
- package/components/avatar/avatar.css +1 -1
- package/components/avatar/avatar.d.ts +1 -1
- package/components/avatar/avatar.yaml +4 -4
- package/components/badge/badge.a2ui.json +1 -0
- package/components/badge/badge.css +1 -1
- package/components/block/block.a2ui.json +1 -0
- package/components/blockquote/blockquote.a2ui.json +1 -0
- package/components/breadcrumb/breadcrumb.a2ui.json +1 -0
- package/components/button/button.a2ui.json +1 -3
- package/components/button/button.css +6 -5
- package/components/button/button.yaml +1 -3
- package/components/calendar-grid/calendar-grid.a2ui.json +1 -0
- package/components/calendar-grid/calendar-grid.class.js +2 -1
- package/components/calendar-picker/calendar-picker.a2ui.json +143 -24
- package/components/calendar-picker/calendar-picker.class.js +490 -197
- package/components/calendar-picker/calendar-picker.css +121 -172
- package/components/calendar-picker/calendar-picker.yaml +225 -47
- package/components/canvas/canvas.a2ui.json +6 -5
- package/components/canvas/canvas.d.ts +37 -0
- package/components/canvas/canvas.js +72 -3
- package/components/canvas/canvas.yaml +6 -2
- package/components/card/card.a2ui.json +25 -2
- package/components/card/card.class.js +3 -0
- package/components/card/card.css +34 -4
- package/components/card/card.d.ts +2 -2
- package/components/card/card.examples.md +3 -3
- package/components/card/card.yaml +28 -4
- package/components/chart/chart.a2ui.json +13 -8
- package/components/chart/chart.class.js +10 -10
- package/components/chart/chart.css +12 -10
- package/components/chart/chart.d.ts +2 -2
- package/components/chart/chart.yaml +8 -5
- package/components/chart-legend/chart-legend.a2ui.json +1 -0
- package/components/chat-thread/chat-input.a2ui.json +1 -0
- package/components/chat-thread/chat-thread.a2ui.json +10 -9
- package/components/chat-thread/chat-thread.css +1 -1
- package/components/chat-thread/chat-thread.yaml +13 -20
- package/components/check/check.a2ui.json +10 -9
- package/components/check/check.yaml +4 -4
- package/components/code/code.a2ui.json +1 -0
- package/components/code/code.css +6 -6
- package/components/col/col.a2ui.json +1 -0
- package/components/color-input/color-input.a2ui.json +1 -0
- package/components/color-picker/color-picker.a2ui.json +1 -0
- package/components/combobox/combobox.a2ui.json +3 -2
- package/components/combobox/combobox.css +2 -2
- package/components/combobox/combobox.yaml +2 -2
- package/components/command/command.a2ui.json +12 -10
- package/components/command/command.class.js +3 -2
- package/components/command/command.css +3 -3
- package/components/command/command.yaml +17 -4
- package/components/context-menu/context-menu.a2ui.json +1 -0
- package/components/date-range-picker/date-range-picker.a2ui.json +2 -1
- package/components/date-range-picker/date-range-picker.css +1 -1
- package/components/date-range-picker/date-range-picker.yaml +1 -1
- package/components/datetime-picker/datetime-picker.a2ui.json +15 -108
- package/components/datetime-picker/datetime-picker.class.js +37 -690
- package/components/datetime-picker/datetime-picker.css +11 -183
- package/components/datetime-picker/datetime-picker.d.ts +4 -5
- package/components/datetime-picker/datetime-picker.yaml +44 -135
- package/components/demo-toggle/demo-toggle.a2ui.json +1 -0
- package/components/demo-toggle/demo-toggle.css +1 -1
- package/components/description-list/description-list.a2ui.json +1 -0
- package/components/description-list/description-list.css +2 -2
- package/components/display-field/display-field.a2ui.json +1 -0
- package/components/display-field/display-field.css +3 -3
- package/components/divider/divider.a2ui.json +1 -0
- package/components/divider/divider.css +1 -1
- package/components/drawer/drawer.a2ui.json +1 -3
- package/components/drawer/drawer.css +3 -3
- package/components/drawer/drawer.yaml +0 -2
- package/components/drilldown/drilldown.a2ui.json +22 -20
- package/components/drilldown/drilldown.class.js +4 -3
- package/components/drilldown/drilldown.css +8 -8
- package/components/drilldown/drilldown.yaml +40 -9
- package/components/embed/embed.a2ui.json +6 -5
- package/components/embed/embed.yaml +2 -2
- package/components/empty-state/empty-state.a2ui.json +1 -0
- package/components/empty-state/empty-state.css +2 -2
- package/components/feed/feed-item.a2ui.json +25 -5
- package/components/feed/feed-item.yaml +28 -7
- package/components/feed/feed.a2ui.json +9 -2
- package/components/feed/feed.class.js +19 -12
- package/components/feed/feed.css +8 -8
- package/components/feed/feed.yaml +14 -1
- package/components/field/field.a2ui.json +1 -0
- package/components/field/field.css +1 -1
- package/components/field/field.examples.md +1 -1
- package/components/fields/fields.a2ui.json +1 -0
- package/components/fields/fields.examples.md +1 -1
- package/components/footer/footer.a2ui.json +1 -0
- package/components/frame/frame.a2ui.json +1 -0
- package/components/grid/grid.a2ui.json +1 -0
- package/components/header/header.a2ui.json +3 -2
- package/components/header/header.yaml +3 -3
- package/components/heatmap/heatmap.a2ui.json +2 -1
- package/components/heatmap/heatmap.class.js +1 -1
- package/components/heatmap/heatmap.css +6 -6
- package/components/heatmap/heatmap.yaml +1 -1
- package/components/icon/icon.a2ui.json +1 -0
- package/components/image/image.a2ui.json +28 -14
- package/components/image/image.class.js +38 -23
- package/components/image/image.css +18 -8
- package/components/image/image.d.ts +2 -2
- package/components/image/image.examples.md +3 -3
- package/components/image/image.yaml +23 -7
- package/components/inline-edit/inline-edit.a2ui.json +1 -0
- package/components/inline-message/inline-message.a2ui.json +3 -1
- package/components/inline-message/inline-message.class.js +3 -1
- package/components/inline-message/inline-message.css +1 -1
- package/components/inline-message/inline-message.d.ts +2 -2
- package/components/inline-message/inline-message.yaml +2 -1
- package/components/input/input.a2ui.json +66 -16
- package/components/input/input.class.js +64 -7
- package/components/input/input.css +19 -18
- package/components/input/input.d.ts +9 -4
- package/components/input/input.yaml +83 -10
- package/components/inspector/inspector.a2ui.json +1 -0
- package/components/integration-card/integration-card.a2ui.json +1 -0
- package/components/integration-card/integration-card.css +4 -4
- package/components/kbd/kbd.a2ui.json +1 -0
- package/components/kbd/kbd.css +1 -1
- package/components/link/link.a2ui.json +2 -1
- package/components/link/link.yaml +1 -1
- package/components/list/list-item.a2ui.json +9 -0
- package/components/list/list-item.yaml +12 -0
- package/components/list/list.a2ui.json +9 -3
- package/components/list/list.class.js +14 -1
- package/components/list/list.css +14 -3
- package/components/list/list.d.ts +7 -5
- package/components/list/list.yaml +16 -6
- package/components/list-window/list-window.a2ui.json +1 -0
- package/components/list-window/list-window.class.js +2 -1
- package/components/loading-overlay/loading-overlay.a2ui.json +1 -0
- package/components/mark/mark.a2ui.json +2 -1
- package/components/mark/mark.yaml +1 -1
- package/components/menu/menu-divider.a2ui.json +1 -0
- package/components/menu/menu-item.a2ui.json +3 -1
- package/components/menu/menu-item.yaml +6 -0
- package/components/menu/menu-label.a2ui.json +1 -0
- package/components/menu/menu.a2ui.json +3 -2
- package/components/menu/menu.class.js +2 -2
- package/components/menu/menu.d.ts +2 -2
- package/components/menu/menu.yaml +3 -3
- package/components/modal/modal.a2ui.json +1 -0
- package/components/nav/nav.a2ui.json +1 -0
- package/components/nav/nav.class.js +14 -3
- package/components/nav/nav.css +2 -2
- package/components/nav-group/nav-group.a2ui.json +1 -0
- package/components/nav-group/nav-group.class.js +3 -1
- package/components/nav-group/nav-group.css +3 -3
- package/components/nav-item/nav-item.a2ui.json +1 -0
- package/components/nav-item/nav-item.css +2 -2
- package/components/noodles/noodles.a2ui.json +1 -0
- package/components/noodles/noodles.class.js +5 -1
- package/components/number-format/number-format.a2ui.json +1 -0
- package/components/option-card/option-card.a2ui.json +1 -0
- package/components/option-card/option-card.css +1 -1
- package/components/otp-input/otp-input.a2ui.json +3 -2
- package/components/otp-input/otp-input.css +1 -1
- package/components/otp-input/otp-input.yaml +9 -2
- package/components/page/page.a2ui.json +13 -0
- package/components/page/page.css +2 -2
- package/components/page/page.d.ts +1 -1
- package/components/page/page.yaml +1 -0
- package/components/pagination/pagination.a2ui.json +1 -0
- package/components/pagination/pagination.class.js +3 -2
- package/components/pagination/pagination.css +1 -1
- package/components/pane/pane.a2ui.json +6 -0
- package/components/pane/pane.class.js +3 -3
- package/components/pane/pane.css +3 -3
- package/components/pane/pane.yaml +3 -0
- package/components/password-strength/password-strength.a2ui.json +1 -0
- package/components/pipeline-status/pipeline-status.a2ui.json +1 -0
- package/components/popover/popover.a2ui.json +7 -6
- package/components/popover/popover.class.js +2 -2
- package/components/popover/popover.d.ts +2 -2
- package/components/popover/popover.yaml +4 -4
- package/components/preview/preview.a2ui.json +1 -0
- package/components/preview/preview.class.js +5 -1
- package/components/progress/progress.a2ui.json +47 -24
- package/components/progress/progress.class.js +66 -11
- package/components/progress/progress.css +44 -26
- package/components/progress/progress.d.ts +7 -5
- package/components/progress/progress.examples.md +2 -2
- package/components/progress/progress.yaml +235 -53
- package/components/progress-row/progress-row.a2ui.json +16 -12
- package/components/progress-row/progress-row.css +2 -2
- package/components/progress-row/progress-row.d.ts +2 -2
- package/components/progress-row/progress-row.examples.md +2 -14
- package/components/progress-row/progress-row.yaml +28 -36
- package/components/qr-code/qr-code.a2ui.json +13 -12
- package/components/qr-code/qr-code.class.js +7 -7
- package/components/qr-code/qr-code.css +1 -1
- package/components/qr-code/qr-code.d.ts +10 -7
- package/components/qr-code/qr-code.yaml +18 -12
- package/components/radio/radio.a2ui.json +8 -7
- package/components/radio/radio.class.js +8 -0
- package/components/radio/radio.yaml +3 -3
- package/components/radio-group/radio-group.a2ui.json +5 -3
- package/components/radio-group/radio-group.class.js +11 -0
- package/components/radio-group/radio-group.yaml +7 -1
- package/components/range/range.a2ui.json +6 -0
- package/components/range/range.class.js +2 -2
- package/components/range/range.css +3 -3
- package/components/range/range.yaml +3 -0
- package/components/rating/rating.a2ui.json +1 -0
- package/components/rating/rating.css +1 -1
- package/components/relative-time/relative-time.a2ui.json +1 -0
- package/components/richtext/richtext.a2ui.json +2 -1
- package/components/richtext/richtext.css +3 -3
- package/components/richtext/richtext.yaml +1 -1
- package/components/row/row.a2ui.json +1 -0
- package/components/search/search.a2ui.json +1 -0
- package/components/search/search.class.js +4 -1
- package/components/section/section.a2ui.json +1 -0
- package/components/segment/segment.a2ui.json +21 -10
- package/components/segment/segment.css +20 -4
- package/components/segment/segment.d.ts +2 -1
- package/components/segment/segment.yaml +20 -13
- package/components/segmented/segmented.a2ui.json +36 -7
- package/components/segmented/segmented.class.js +113 -22
- package/components/segmented/segmented.css +3 -0
- package/components/segmented/segmented.d.ts +3 -1
- package/components/segmented/segmented.yaml +80 -8
- package/components/select/select.a2ui.json +18 -13
- package/components/select/select.class.js +61 -37
- package/components/select/select.css +18 -16
- package/components/select/select.yaml +18 -7
- package/components/skeleton/skeleton.a2ui.json +8 -7
- package/components/skeleton/skeleton.class.js +8 -7
- package/components/skeleton/skeleton.css +21 -6
- package/components/skeleton/skeleton.d.ts +2 -2
- package/components/skeleton/skeleton.examples.md +2 -2
- package/components/skeleton/skeleton.yaml +3 -2
- package/components/skip-nav/skip-nav.a2ui.json +1 -0
- package/components/slider/slider.a2ui.json +6 -0
- package/components/slider/slider.class.js +20 -3
- package/components/slider/slider.css +2 -2
- package/components/slider/slider.yaml +3 -0
- package/components/spinner/spinner.a2ui.json +1 -0
- package/components/spinner/spinner.css +1 -1
- package/components/stack/stack.a2ui.json +1 -0
- package/components/stat/stat.a2ui.json +1 -0
- package/components/stat/stat.css +3 -3
- package/components/stat/stat.js +2 -2
- package/components/step-progress/step-progress.a2ui.json +1 -0
- package/components/step-progress/step-progress.css +1 -1
- package/components/step-progress/step-progress.yaml +1 -1
- package/components/stepper/stepper-item.a2ui.json +7 -3
- package/components/stepper/stepper-item.yaml +15 -5
- package/components/stepper/stepper.a2ui.json +5 -1
- package/components/stepper/stepper.class.js +31 -16
- package/components/stepper/stepper.css +2 -2
- package/components/stepper/stepper.d.ts +1 -1
- package/components/stepper/stepper.yaml +4 -2
- package/components/stream/stream.a2ui.json +1 -0
- package/components/swatch/swatch.a2ui.json +1 -0
- package/components/swatch/swatch.css +2 -2
- package/components/swiper/swiper.a2ui.json +1 -0
- package/components/swiper/swiper.class.js +5 -1
- package/components/switch/switch.a2ui.json +12 -11
- package/components/switch/switch.css +1 -1
- package/components/switch/switch.yaml +6 -6
- package/components/table/table.a2ui.json +1 -10
- package/components/table/table.class.js +18 -16
- package/components/table/table.css +17 -18
- package/components/table/table.d.ts +0 -2
- package/components/table/table.yaml +0 -9
- package/components/table-toolbar/table-toolbar.a2ui.json +1 -0
- package/components/table-toolbar/table-toolbar.class.js +38 -19
- package/components/table-toolbar/table-toolbar.css +2 -2
- package/components/tabs/tab.a2ui.json +9 -0
- package/components/tabs/tab.js +4 -0
- package/components/tabs/tab.yaml +15 -0
- package/components/tabs/tabs.a2ui.json +9 -4
- package/components/tabs/tabs.class.js +13 -1
- package/components/tabs/tabs.css +9 -1
- package/components/tabs/tabs.d.ts +2 -0
- package/components/tabs/tabs.yaml +9 -2
- package/components/tag/tag.a2ui.json +1 -0
- package/components/tag/tag.css +2 -2
- package/components/tags-input/tags-input.a2ui.json +22 -17
- package/components/tags-input/tags-input.class.js +13 -11
- package/components/tags-input/tags-input.css +11 -7
- package/components/tags-input/tags-input.d.ts +16 -7
- package/components/tags-input/tags-input.examples.md +2 -2
- package/components/tags-input/tags-input.yaml +25 -8
- package/components/text/text.a2ui.json +6 -5
- package/components/text/text.css +10 -5
- package/components/text/text.d.ts +2 -2
- package/components/text/text.yaml +9 -4
- package/components/textarea/textarea.a2ui.json +2 -1
- package/components/textarea/textarea.css +1 -1
- package/components/textarea/textarea.yaml +1 -1
- package/components/theme-provider/theme-provider.a2ui.json +2 -1
- package/components/theme-provider/theme-provider.d.ts +5 -3
- package/components/theme-provider/theme-provider.yaml +5 -3
- package/components/time-picker/time-picker.a2ui.json +1 -0
- package/components/time-picker/time-picker.class.js +4 -2
- package/components/time-picker/time-picker.css +1 -1
- package/components/timeline/timeline-item.a2ui.json +26 -3
- package/components/timeline/timeline-item.yaml +22 -7
- package/components/timeline/timeline.a2ui.json +4 -0
- package/components/timeline/timeline.class.js +29 -18
- package/components/timeline/timeline.css +4 -4
- package/components/timeline/timeline.d.ts +3 -3
- package/components/timeline/timeline.yaml +2 -0
- package/components/toast/toast.a2ui.json +10 -3
- package/components/toast/toast.yaml +20 -13
- package/components/toc/toc.a2ui.json +1 -0
- package/components/toc/toc.css +1 -1
- package/components/toggle-group/toggle-group.a2ui.json +7 -3
- package/components/toggle-group/toggle-group.class.js +43 -12
- package/components/toggle-group/toggle-group.css +7 -4
- package/components/toggle-group/toggle-group.d.ts +3 -4
- package/components/toggle-group/toggle-group.yaml +22 -16
- package/components/toggle-group/toggle-option.a2ui.json +8 -4
- package/components/toggle-group/toggle-option.yaml +15 -13
- package/components/toggle-scheme/toggle-scheme.a2ui.json +2 -1
- package/components/toggle-scheme/toggle-scheme.yaml +1 -1
- package/components/toolbar/toolbar-group.a2ui.json +1 -0
- package/components/toolbar/toolbar.a2ui.json +1 -0
- package/components/toolbar/toolbar.class.js +3 -0
- package/components/tooltip/tooltip.a2ui.json +1 -0
- package/components/tooltip/tooltip.css +1 -1
- package/components/tour/tour-step.a2ui.json +4 -0
- package/components/tour/tour-step.yaml +2 -0
- package/components/tour/tour.a2ui.json +4 -0
- package/components/tour/tour.css +2 -2
- package/components/tour/tour.yaml +2 -0
- package/components/tree/tree-item.a2ui.json +5 -0
- package/components/tree/tree-item.yaml +3 -0
- package/components/tree/tree.a2ui.json +4 -0
- package/components/tree/tree.class.js +2 -1
- package/components/tree/tree.css +1 -1
- package/components/tree/tree.yaml +2 -0
- package/components/upload/upload.a2ui.json +8 -1
- package/components/upload/upload.class.js +35 -5
- package/components/upload/upload.css +28 -0
- package/components/upload/upload.d.ts +16 -0
- package/components/upload/upload.yaml +27 -7
- package/components/visually-hidden/visually-hidden.a2ui.json +1 -0
- package/core/dom.d.ts +11 -0
- package/core/dom.js +34 -0
- package/core/form.js +7 -0
- package/core/index.d.ts +1 -0
- package/core/index.js +1 -0
- package/core/responsive.js +16 -4
- package/core/template.js +2 -1
- package/custom-elements.json +442 -502
- package/dist/host.min.css +1 -1
- package/dist/host.sheet.js +1 -1
- package/dist/theme-provider.min.js +5 -5
- package/dist/web-components.min.css +1 -1
- package/dist/web-components.min.js +91 -104
- package/dist/web-components.sheet.js +1 -1
- package/package.json +4 -2
- package/patterns/conversion-funnel/conversion-funnel.examples.html +15 -15
- package/patterns/inline-dialog/inline-dialog.examples.html +2 -2
- package/patterns/inline-dialog/inline-dialog.html +4 -0
- package/patterns/timezone-selector/timezone-selector.examples.html +1 -1
- package/patterns/timezone-selector/timezone-selector.html +2 -2
- package/styles/api/text.css +7 -0
- package/styles/type/scale.css +6 -7
- package/traits/_catalog.json +129 -41
- package/traits/anchor-positioning/anchor-positioning.js +1 -0
- package/traits/announcer/announcer.js +1 -0
- package/traits/arrow-grid-nav/arrow-grid-nav.js +1 -0
- package/traits/attention-pulse/attention-pulse.js +1 -0
- package/traits/count-up/count-up.js +1 -0
- package/traits/define.js +78 -0
- package/traits/focus-restore/focus-restore.js +2 -1
- package/traits/haptic-feedback/haptic-feedback.js +1 -0
- package/traits/input-mask/input-mask.js +1 -0
- package/traits/intersection-observer/intersection-observer.js +1 -0
- package/traits/layout-animation/layout-animation.js +1 -0
- package/traits/noise-texture/noise-texture.js +1 -0
- package/traits/resize-observer/resize-observer.js +1 -0
- package/traits/scroll-progress/scroll-progress.js +1 -0
- package/traits/snap-to-grid/snap-to-grid.js +1 -0
- package/traits/sound-feedback/sound-feedback.js +1 -0
- package/traits/spring-animate/spring-animate.js +1 -0
- package/traits/validation/validation.js +1 -0
|
@@ -12,32 +12,66 @@
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
|
-
* <calendar-picker-ui
|
|
15
|
+
* <calendar-picker-ui placeholder="Select date..." format="short"></calendar-picker-ui>
|
|
16
16
|
*
|
|
17
|
-
* Date picker with trigger button and
|
|
18
|
-
* Form-associated via UIFormElement + ElementInternals.
|
|
17
|
+
* Date (and, per `[precision]`, date+time) picker with trigger button and
|
|
18
|
+
* popover panel. Form-associated via UIFormElement + ElementInternals.
|
|
19
|
+
*
|
|
20
|
+
* gh#1372 (#1363 C3c) — this class now folds `<datetime-picker-ui>`'s surface
|
|
21
|
+
* in behind a widened `precision` axis (`date` | `minute` | `second`) rather
|
|
22
|
+
* than the two primitives duplicating the same trigger/popover/anchor/focus-
|
|
23
|
+
* trap machinery. `precision="date"` (the default) is exactly the prior
|
|
24
|
+
* single-pane behavior — composes only `<calendar-grid-ui>`. `precision`
|
|
25
|
+
* `"minute"`/`"second"` additionally composes `<divider-ui>` +
|
|
26
|
+
* `<time-picker-ui>` as a second pane, `value` becomes a full ISO 8601
|
|
27
|
+
* datetime string, and `hour-cycle`/`step`/`locale` become active — the same
|
|
28
|
+
* contract `<datetime-picker-ui>` shipped, which is now a deprecated thin
|
|
29
|
+
* alias subclass (see datetime-picker.class.js) mapping onto this class with
|
|
30
|
+
* `precision="minute"` by default. Per ADR-0027, the consumer must
|
|
31
|
+
* explicitly import every composed primitive (see `composes:` in
|
|
32
|
+
* calendar-picker.yaml) — this class doesn't auto-import them.
|
|
33
|
+
*
|
|
34
|
+
* #1363 C3d sibling-contract convergence, folded into this same build:
|
|
35
|
+
* - `readonly` (inherited from UIFormElement) now has real behavior —
|
|
36
|
+
* blocks the popover from opening; keyboard focus/inspection still
|
|
37
|
+
* works.
|
|
38
|
+
* - `input` / `open` / `close` / `invalid` events now fire (previously
|
|
39
|
+
* only `change` existed).
|
|
40
|
+
* - `name` / `min` / `max` are declared `reflect: true` in the yaml,
|
|
41
|
+
* matching the class's actual (already-reflecting) behavior.
|
|
42
|
+
* - The odd first-class `label` prop is REMOVED — siblings all delegate
|
|
43
|
+
* the label to `<field-ui label>`; this was calendar-picker's own
|
|
44
|
+
* outlier. Breaking; migration-guide entry + consumer scan accompany
|
|
45
|
+
* this change.
|
|
19
46
|
*
|
|
20
47
|
* Properties:
|
|
21
|
-
*
|
|
22
|
-
* placeholder — trigger text when no date selected
|
|
48
|
+
* placeholder — trigger text when no value selected
|
|
23
49
|
* format — display format: short | long | iso
|
|
24
|
-
*
|
|
25
|
-
*
|
|
50
|
+
* precision — date | minute | second (mode axis — widens the surface)
|
|
51
|
+
* min — minimum selectable date/datetime (ISO string)
|
|
52
|
+
* max — maximum selectable date/datetime (ISO string)
|
|
53
|
+
* step — per-Arrow-keypress time increment in seconds (time modes only)
|
|
54
|
+
* hourcycle — '' | h12 | h23 (time modes only; attribute hour-cycle)
|
|
55
|
+
* locale — BCP-47 locale for hour-cycle derivation (time modes only)
|
|
26
56
|
* open — popover visibility
|
|
57
|
+
* readonly — blocks edits; keyboard inspection still works
|
|
27
58
|
*
|
|
28
|
-
* Keyboard:
|
|
59
|
+
* Keyboard: Enter/Space (closed) opens; Escape (open) closes. Arrow keys,
|
|
60
|
+
* PageUp/PageDown, Home/End, and day-selection Enter/Space are handled by
|
|
61
|
+
* the composed calendar-grid-ui once DOM focus moves into it on open (see
|
|
62
|
+
* the queueMicrotask in render()) — calendar-grid-ui binds its own keydown
|
|
63
|
+
* listener to itself, not to this host, so focus must be inside it for
|
|
64
|
+
* that handling to receive events at all. In a time-pane mode, Tab cycles
|
|
65
|
+
* from the calendar pane into the time pane's Spinbutton segments.
|
|
29
66
|
*/
|
|
30
67
|
|
|
31
68
|
import { UIFormElement } from '../../core/form.js';
|
|
32
69
|
import { anchorPopover } from '../../core/anchor.js';
|
|
33
70
|
|
|
34
|
-
const DAYS = ['S', 'M', 'T', 'W', 'T', 'F', 'S'];
|
|
35
71
|
const MONTHS_SHORT = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
|
36
72
|
const MONTHS_LONG = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
|
|
37
73
|
|
|
38
|
-
function
|
|
39
|
-
return `${y}-${String(m + 1).padStart(2, '0')}-${String(d).padStart(2, '0')}`;
|
|
40
|
-
}
|
|
74
|
+
function pad2(n) { return String(n).padStart(2, '0'); }
|
|
41
75
|
|
|
42
76
|
function parseISO(str) {
|
|
43
77
|
if (!str) return null;
|
|
@@ -46,68 +80,276 @@ function parseISO(str) {
|
|
|
46
80
|
return new Date(y, m - 1, d);
|
|
47
81
|
}
|
|
48
82
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
83
|
+
/** Parse "YYYY-MM-DDTHH:mm" or "YYYY-MM-DDTHH:mm:ss" → {date, time} or null. */
|
|
84
|
+
function parseDatetime(str) {
|
|
85
|
+
if (!str) return null;
|
|
86
|
+
const s = String(str).trim();
|
|
87
|
+
// Tolerate space separator as a Generative-UI quality-of-life affordance;
|
|
88
|
+
// canonical wire form is always "T".
|
|
89
|
+
const m = /^(\d{4}-\d{2}-\d{2})[T ](\d{1,2}:\d{2}(?::\d{2})?)$/.exec(s);
|
|
90
|
+
if (!m) return null;
|
|
91
|
+
return { date: m[1], time: m[2] };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** Compose canonical "YYYY-MM-DDTHH:mm[:ss]" from a {date, time} pair. */
|
|
95
|
+
function formatDatetime(date, time, precision) {
|
|
96
|
+
if (!date || !time) return '';
|
|
97
|
+
const tm = /^(\d{1,2}):(\d{2})(?::(\d{2}))?$/.exec(time);
|
|
98
|
+
if (!tm) return '';
|
|
99
|
+
const hh = pad2(Number(tm[1]));
|
|
100
|
+
const mm = tm[2];
|
|
101
|
+
const ss = tm[3] || '00';
|
|
102
|
+
return precision === 'second' ? `${date}T${hh}:${mm}:${ss}` : `${date}T${hh}:${mm}`;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function formatDatePart(iso, fmt) {
|
|
106
|
+
const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(iso || '');
|
|
107
|
+
if (!m) return '';
|
|
108
|
+
const y = +m[1];
|
|
109
|
+
const mo = +m[2] - 1;
|
|
110
|
+
const d = +m[3];
|
|
111
|
+
switch (fmt) {
|
|
112
|
+
case 'long': return `${MONTHS_LONG[mo]} ${d}, ${y}`;
|
|
113
|
+
case 'iso': return iso;
|
|
114
|
+
case 'short':
|
|
115
|
+
default: return `${MONTHS_SHORT[mo]} ${d}, ${y}`;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function formatTimePart(time, precision, hourcycle) {
|
|
120
|
+
const m = /^(\d{1,2}):(\d{2})(?::(\d{2}))?$/.exec(time || '');
|
|
121
|
+
if (!m) return '';
|
|
122
|
+
const h = +m[1];
|
|
123
|
+
const mm = m[2];
|
|
124
|
+
const ss = m[3];
|
|
125
|
+
let hStr;
|
|
126
|
+
let meridiem = '';
|
|
127
|
+
if (hourcycle === 'h12') {
|
|
128
|
+
const isPM = h >= 12;
|
|
129
|
+
const dispH = (h % 12) || 12;
|
|
130
|
+
hStr = pad2(dispH);
|
|
131
|
+
meridiem = isPM ? ' PM' : ' AM';
|
|
132
|
+
} else {
|
|
133
|
+
hStr = pad2(h);
|
|
134
|
+
}
|
|
135
|
+
const sec = (precision === 'second' && ss != null) ? `:${ss}` : '';
|
|
136
|
+
return `${hStr}:${mm}${sec}${meridiem}`;
|
|
53
137
|
}
|
|
54
138
|
|
|
55
139
|
export class UICalendarPicker extends UIFormElement {
|
|
56
|
-
|
|
140
|
+
// §170 (v0.5.4)/gh#1372: label was first-class; removed per #1363 C3d —
|
|
141
|
+
// siblings all delegate to <field-ui label>. Base class still reads this
|
|
142
|
+
// static to decide whether to warn on an authored [label] attribute.
|
|
143
|
+
static labelDeprecated = true;
|
|
144
|
+
|
|
57
145
|
// §154 (v0.5.3): Phosphor icons this primitive auto-stamps (without
|
|
58
146
|
// consumer markup). Aggregated by installIconLoadersForRegistered()
|
|
59
147
|
// across all defined elements. Audited by check-required-icons.mjs
|
|
60
148
|
// (slot 11). Per FEEDBACK-06 §4 + FEEDBACK-07 §4.
|
|
61
|
-
|
|
149
|
+
// caret-left/caret-right dropped (gh#1371) — those nav-button icons are
|
|
150
|
+
// now stamped by the composed calendar-grid-ui, which declares its own
|
|
151
|
+
// requiredIcons; this class itself only stamps `calendar` on the trigger.
|
|
152
|
+
static requiredIcons = ['calendar'];
|
|
62
153
|
|
|
63
154
|
static properties = {
|
|
64
155
|
...UIFormElement.properties,
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
156
|
+
placeholder: { type: String, default: 'Select date...', reflect: true },
|
|
157
|
+
format: { type: String, default: 'short', reflect: true },
|
|
158
|
+
min: { type: String, default: '', reflect: true },
|
|
159
|
+
max: { type: String, default: '', reflect: true },
|
|
160
|
+
open: { type: Boolean, default: false, reflect: true },
|
|
161
|
+
// gh#1372 (#1363 C3c) — the widened mode axis. 'date' is the prior
|
|
162
|
+
// single-pane behavior; 'minute'/'second' fold in what
|
|
163
|
+
// <datetime-picker-ui> used to be its own primitive for.
|
|
164
|
+
precision: { type: String, default: 'date', reflect: true },
|
|
165
|
+
// Active only when precision !== 'date' — mirrors datetime-picker's
|
|
166
|
+
// former contract exactly (folded in, not reinvented).
|
|
167
|
+
step: { type: Number, default: 60, reflect: true },
|
|
168
|
+
hourcycle: { type: String, default: '', reflect: true, attribute: 'hour-cycle' },
|
|
169
|
+
locale: { type: String, default: '', reflect: false },
|
|
71
170
|
// Popover placement — yaml documented this as reflect:true since v1;
|
|
72
171
|
// the JS reads it via getAttribute('placement') in the anchorPopover
|
|
73
172
|
// call site. Declaring here so el.placement = 'top' also updates.
|
|
74
|
-
placement:
|
|
173
|
+
placement: { type: String, default: 'bottom', reflect: true },
|
|
75
174
|
};
|
|
76
175
|
|
|
77
176
|
static template = () => null;
|
|
78
177
|
|
|
79
178
|
#popover = null;
|
|
80
179
|
#trigger = null;
|
|
180
|
+
#grid = null;
|
|
181
|
+
#gridBound = false;
|
|
182
|
+
#divider = null;
|
|
183
|
+
#timePane = null;
|
|
184
|
+
#timePaneBound = false;
|
|
81
185
|
#anchorCleanup = null;
|
|
82
|
-
#viewYear = new Date().getFullYear();
|
|
83
|
-
#viewMonth = new Date().getMonth();
|
|
84
|
-
#focusedDay = null;
|
|
85
|
-
#popoverClickBound = false;
|
|
86
186
|
#openRaf = null;
|
|
187
|
+
// Tracks whether the popover was already open as of the LAST render, so
|
|
188
|
+
// the initial-focus microtask below fires only on the closed→open
|
|
189
|
+
// transition — not on every subsequent open-state render (e.g. the one
|
|
190
|
+
// #onGridChange triggers by writing `this.value`), which would otherwise
|
|
191
|
+
// re-steal focus back to the first day on every selection (review finding,
|
|
192
|
+
// PR #1437). Reset on close (naturally, via the render() assignment below)
|
|
193
|
+
// and explicitly on disconnect so a later reconnect-while-open re-arms it.
|
|
194
|
+
#wasOpen = false;
|
|
195
|
+
// Partial {date, time} selection state — only meaningful when
|
|
196
|
+
// precision !== 'date' (two panes must both commit before `value` is
|
|
197
|
+
// written). Mirrors the former datetime-picker.class.js `#pending`.
|
|
198
|
+
#pending = null;
|
|
199
|
+
// Records why the NEXT open/close transition happened, read once by
|
|
200
|
+
// render()'s wasOpen-diff below, then cleared. Centralizing the
|
|
201
|
+
// open/close event dispatch in render() (rather than at every call site
|
|
202
|
+
// that flips `this.open`) means no call site can forget to fire it.
|
|
203
|
+
#openTrigger = 'programmatic';
|
|
204
|
+
#closeReason = 'programmatic';
|
|
205
|
+
|
|
206
|
+
get #hasTimePane() { return this.precision === 'minute' || this.precision === 'second'; }
|
|
207
|
+
|
|
208
|
+
// Substrate change → adopt the selection, mirror it back out as this
|
|
209
|
+
// primitive's own `change` (same detail shape callers already expect),
|
|
210
|
+
// and stop the inner event from also reaching listeners upstream of us
|
|
211
|
+
// (mirrors date-range-picker's #onDaySelect — one `change`, not two).
|
|
212
|
+
#onGridChange = (e) => {
|
|
213
|
+
if (this.readonly) return;
|
|
214
|
+
e.stopPropagation();
|
|
215
|
+
const iso = e.detail?.value || this.#grid?.value || '';
|
|
216
|
+
if (!iso) return;
|
|
217
|
+
if (!this.#hasTimePane) {
|
|
218
|
+
this.value = iso;
|
|
219
|
+
this.syncValue(iso);
|
|
220
|
+
this.dispatchEvent(new CustomEvent('change', { bubbles: true, detail: { value: this.value } }));
|
|
221
|
+
// Popover stays open until the user clicks outside — matches the prior
|
|
222
|
+
// inlined-grid behavior (no auto-close on selection).
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
// Two-pane (datetime) mode — date half of a pending commit.
|
|
226
|
+
if (!this.#pending) this.#pending = { date: '', time: '' };
|
|
227
|
+
this.#pending.date = iso;
|
|
228
|
+
const existing = parseDatetime(this.value);
|
|
229
|
+
const timeCandidate = this.#pending.time || existing?.time || '';
|
|
230
|
+
const partial = timeCandidate ? formatDatetime(iso, timeCandidate, this.precision) : `${iso}T`;
|
|
231
|
+
this.dispatchEvent(new CustomEvent('input', { bubbles: true, detail: { value: partial } }));
|
|
232
|
+
if (timeCandidate) {
|
|
233
|
+
this.#commitFromParts({ date: iso, time: timeCandidate });
|
|
234
|
+
} else {
|
|
235
|
+
// Advance focus to the time pane's first segment — a11y model:
|
|
236
|
+
// date → time → commit.
|
|
237
|
+
queueMicrotask(() => {
|
|
238
|
+
if (!this.open) return;
|
|
239
|
+
const hourSeg = this.#timePane?.querySelector?.('[data-segment="hour"]');
|
|
240
|
+
hourSeg?.focus?.();
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
};
|
|
87
244
|
|
|
88
|
-
#
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
const
|
|
92
|
-
if (
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
const
|
|
96
|
-
|
|
245
|
+
#onTimePaneChange = (e) => {
|
|
246
|
+
if (this.readonly) return;
|
|
247
|
+
e.stopPropagation();
|
|
248
|
+
const time = e.detail?.value || this.#timePane?.value || '';
|
|
249
|
+
if (!time) return;
|
|
250
|
+
if (!this.#pending) this.#pending = { date: '', time: '' };
|
|
251
|
+
this.#pending.time = time;
|
|
252
|
+
const existing = parseDatetime(this.value);
|
|
253
|
+
const dateCandidate = this.#pending.date || existing?.date || '';
|
|
254
|
+
if (dateCandidate) {
|
|
255
|
+
this.#commitFromParts({ date: dateCandidate, time });
|
|
256
|
+
} else {
|
|
257
|
+
this.dispatchEvent(new CustomEvent('input', { bubbles: true, detail: { value: `T${time}` } }));
|
|
258
|
+
}
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
#onTimePaneInput = (e) => {
|
|
262
|
+
if (this.readonly) return;
|
|
263
|
+
e.stopPropagation();
|
|
264
|
+
const time = e.detail?.value || '';
|
|
265
|
+
const existing = parseDatetime(this.value);
|
|
266
|
+
const date = this.#pending?.date || existing?.date || '';
|
|
267
|
+
const partial = (date && time) ? formatDatetime(date, time, this.precision) : (time ? `T${time}` : '');
|
|
268
|
+
this.dispatchEvent(new CustomEvent('input', { bubbles: true, detail: { value: partial } }));
|
|
97
269
|
};
|
|
98
270
|
|
|
271
|
+
#onPaneInvalid = (e) => {
|
|
272
|
+
e.stopPropagation();
|
|
273
|
+
this.dispatchEvent(new CustomEvent('invalid', { bubbles: true, detail: { value: this.value, reason: e.detail?.reason || 'pane' } }));
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
/** Commit a candidate {date, time} pair once both halves are present. */
|
|
277
|
+
#commitFromParts(parts) {
|
|
278
|
+
if (!parts || !parts.date || !parts.time) return;
|
|
279
|
+
const next = formatDatetime(parts.date, parts.time, this.precision);
|
|
280
|
+
if (!next) {
|
|
281
|
+
this.dispatchEvent(new CustomEvent('invalid', { bubbles: true, detail: { value: next, reason: 'parse' } }));
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
if (this.min && next < this.min) {
|
|
285
|
+
this.dispatchEvent(new CustomEvent('invalid', { bubbles: true, detail: { value: next, reason: 'below-min' } }));
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
if (this.max && next > this.max) {
|
|
289
|
+
this.dispatchEvent(new CustomEvent('invalid', { bubbles: true, detail: { value: next, reason: 'above-max' } }));
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
this.value = next;
|
|
293
|
+
this.#pending = null;
|
|
294
|
+
this.syncValue(next);
|
|
295
|
+
this.dispatchEvent(new CustomEvent('change', { bubbles: true, detail: { value: next } }));
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Override `syncValue` to run date/datetime constraint checks in addition
|
|
300
|
+
* to the base UIFormElement::syncValue write.
|
|
301
|
+
*/
|
|
302
|
+
syncValue(val) {
|
|
303
|
+
const baseVal = val ?? this.value ?? '';
|
|
304
|
+
this.internals.setFormValue(baseVal);
|
|
305
|
+
this.#runConstraints(baseVal);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
#runConstraints(val) {
|
|
309
|
+
if (this.required && !val) {
|
|
310
|
+
this.internals.setValidity(
|
|
311
|
+
{ valueMissing: true },
|
|
312
|
+
this.getAttribute('data-msg-required') || (this.#hasTimePane ? 'Please select a date and time.' : 'Please select a date.'),
|
|
313
|
+
this,
|
|
314
|
+
);
|
|
315
|
+
return false;
|
|
316
|
+
}
|
|
317
|
+
if (!val) {
|
|
318
|
+
this.internals.setValidity({});
|
|
319
|
+
return true;
|
|
320
|
+
}
|
|
321
|
+
if (this.#hasTimePane) {
|
|
322
|
+
const parsed = parseDatetime(val);
|
|
323
|
+
if (!parsed) {
|
|
324
|
+
this.internals.setValidity({ badInput: true }, 'Invalid date or time format.', this);
|
|
325
|
+
return false;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
if (this.min && val < this.min) {
|
|
329
|
+
this.internals.setValidity(
|
|
330
|
+
{ rangeUnderflow: true },
|
|
331
|
+
`Earliest selectable ${this.#hasTimePane ? 'datetime' : 'date'} is ${this.min}.`,
|
|
332
|
+
this,
|
|
333
|
+
);
|
|
334
|
+
return false;
|
|
335
|
+
}
|
|
336
|
+
if (this.max && val > this.max) {
|
|
337
|
+
this.internals.setValidity(
|
|
338
|
+
{ rangeOverflow: true },
|
|
339
|
+
`Latest selectable ${this.#hasTimePane ? 'datetime' : 'date'} is ${this.max}.`,
|
|
340
|
+
this,
|
|
341
|
+
);
|
|
342
|
+
return false;
|
|
343
|
+
}
|
|
344
|
+
this.internals.setValidity({});
|
|
345
|
+
return true;
|
|
346
|
+
}
|
|
347
|
+
|
|
99
348
|
connected() {
|
|
100
349
|
super.connected();
|
|
101
350
|
this.setAttribute('role', 'group');
|
|
102
351
|
this.addEventListener('click', this.#onClick);
|
|
103
352
|
this.addEventListener('keydown', this.#onKey);
|
|
104
|
-
|
|
105
|
-
// Initialize view to selected date or today
|
|
106
|
-
const sel = parseISO(this.value);
|
|
107
|
-
if (sel) {
|
|
108
|
-
this.#viewYear = sel.getFullYear();
|
|
109
|
-
this.#viewMonth = sel.getMonth();
|
|
110
|
-
}
|
|
111
353
|
}
|
|
112
354
|
|
|
113
355
|
render() {
|
|
@@ -125,8 +367,6 @@ export class UICalendarPicker extends UIFormElement {
|
|
|
125
367
|
this.#popover.setAttribute('slot', 'popover');
|
|
126
368
|
this.#popover.setAttribute('popover', 'manual');
|
|
127
369
|
this.appendChild(this.#popover);
|
|
128
|
-
this.#popover.addEventListener('click', this.#onPopoverClick);
|
|
129
|
-
this.#popoverClickBound = true;
|
|
130
370
|
} else {
|
|
131
371
|
const display = this.querySelector('[slot="display"]');
|
|
132
372
|
if (display) display.textContent = this.#displayText();
|
|
@@ -134,17 +374,120 @@ export class UICalendarPicker extends UIFormElement {
|
|
|
134
374
|
|
|
135
375
|
if (!this.#popover) this.#popover = this.querySelector('[slot="popover"]');
|
|
136
376
|
if (!this.#trigger) this.#trigger = this.querySelector('[slot="trigger"]');
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
377
|
+
|
|
378
|
+
// Ensure the substrate grid exists inside the popover — composed once,
|
|
379
|
+
// reused across opens (matches date-range-picker's calFrom/calTo parts).
|
|
380
|
+
if (this.#popover && !this.#grid) {
|
|
381
|
+
this.#grid = this.#popover.querySelector('calendar-grid-ui');
|
|
382
|
+
if (!this.#grid) {
|
|
383
|
+
this.#grid = document.createElement('calendar-grid-ui');
|
|
384
|
+
this.#popover.appendChild(this.#grid);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
if (this.#grid && !this.#gridBound) {
|
|
388
|
+
this.#grid.addEventListener('change', this.#onGridChange);
|
|
389
|
+
this.#gridBound = true;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// gh#1372 — stamp/tear down the time pane + divider as `precision`
|
|
393
|
+
// dictates. Idempotent: a no-op once the pane already matches.
|
|
394
|
+
if (this.#popover) {
|
|
395
|
+
if (this.#hasTimePane) {
|
|
396
|
+
if (!this.#divider) {
|
|
397
|
+
this.#divider = this.#popover.querySelector('[data-pane-divider]');
|
|
398
|
+
if (!this.#divider) {
|
|
399
|
+
this.#divider = document.createElement('divider-ui');
|
|
400
|
+
this.#divider.setAttribute('data-pane-divider', '');
|
|
401
|
+
this.#divider.setAttribute('vertical', '');
|
|
402
|
+
this.#popover.appendChild(this.#divider);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
if (!this.#timePane) {
|
|
406
|
+
this.#timePane = this.#popover.querySelector('[data-time-pane]');
|
|
407
|
+
if (!this.#timePane) {
|
|
408
|
+
this.#timePane = document.createElement('time-picker-ui');
|
|
409
|
+
this.#timePane.setAttribute('data-time-pane', '');
|
|
410
|
+
this.#popover.appendChild(this.#timePane);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
if (!this.#timePaneBound) {
|
|
414
|
+
this.#timePane.addEventListener('change', this.#onTimePaneChange);
|
|
415
|
+
this.#timePane.addEventListener('input', this.#onTimePaneInput);
|
|
416
|
+
this.#timePane.addEventListener('invalid', this.#onPaneInvalid);
|
|
417
|
+
this.#timePaneBound = true;
|
|
418
|
+
}
|
|
419
|
+
} else if (this.#timePane || this.#divider) {
|
|
420
|
+
// precision switched back to 'date' at runtime — tear the pane down.
|
|
421
|
+
if (this.#timePaneBound) {
|
|
422
|
+
this.#timePane.removeEventListener('change', this.#onTimePaneChange);
|
|
423
|
+
this.#timePane.removeEventListener('input', this.#onTimePaneInput);
|
|
424
|
+
this.#timePane.removeEventListener('invalid', this.#onPaneInvalid);
|
|
425
|
+
this.#timePaneBound = false;
|
|
426
|
+
}
|
|
427
|
+
this.#timePane?.remove();
|
|
428
|
+
this.#divider?.remove();
|
|
429
|
+
this.#timePane = null;
|
|
430
|
+
this.#divider = null;
|
|
431
|
+
this.#pending = null;
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// Cascade value/min/max/precision knobs into the substrate(s) on every
|
|
436
|
+
// render so external prop writes (not just this class's own selection
|
|
437
|
+
// flow) stay in sync.
|
|
438
|
+
if (this.#grid) {
|
|
439
|
+
const parsed = this.#hasTimePane ? parseDatetime(this.value) : null;
|
|
440
|
+
const datePart = this.#hasTimePane ? (this.#pending?.date ?? parsed?.date ?? '') : (this.value || '');
|
|
441
|
+
this.#grid.value = datePart;
|
|
442
|
+
const minDate = this.#hasTimePane ? (this.min ? this.min.slice(0, 10) : '') : this.min;
|
|
443
|
+
const maxDate = this.#hasTimePane ? (this.max ? this.max.slice(0, 10) : '') : this.max;
|
|
444
|
+
if (minDate) this.#grid.setAttribute('min', minDate); else this.#grid.removeAttribute('min');
|
|
445
|
+
if (maxDate) this.#grid.setAttribute('max', maxDate); else this.#grid.removeAttribute('max');
|
|
446
|
+
this.#grid.toggleAttribute('disabled', !!this.disabled);
|
|
447
|
+
this.#grid.toggleAttribute('readonly', !!this.readonly);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
if (this.#hasTimePane && this.#timePane) {
|
|
451
|
+
const parsed = parseDatetime(this.value);
|
|
452
|
+
const timePart = this.#pending?.time ?? parsed?.time ?? '';
|
|
453
|
+
this.#timePane.setAttribute('precision', this.precision);
|
|
454
|
+
this.#timePane.setAttribute('step', String(this.step));
|
|
455
|
+
if (this.hourcycle) this.#timePane.setAttribute('hour-cycle', this.hourcycle); else this.#timePane.removeAttribute('hour-cycle');
|
|
456
|
+
if (this.locale) this.#timePane.setAttribute('locale', this.locale); else this.#timePane.removeAttribute('locale');
|
|
457
|
+
// Time-of-day portion of min/max only applies when the pending/parsed
|
|
458
|
+
// date matches the bound's own date — an advisory affordance; the
|
|
459
|
+
// host-level #commitFromParts constraint check is the real gate.
|
|
460
|
+
const datePart = this.#pending?.date ?? parsed?.date ?? '';
|
|
461
|
+
const minTime = this.min ? this.min.slice(11) : '';
|
|
462
|
+
const maxTime = this.max ? this.max.slice(11) : '';
|
|
463
|
+
if (minTime && datePart === this.min.slice(0, 10)) this.#timePane.setAttribute('min', minTime); else this.#timePane.removeAttribute('min');
|
|
464
|
+
if (maxTime && datePart === this.max.slice(0, 10)) this.#timePane.setAttribute('max', maxTime); else this.#timePane.removeAttribute('max');
|
|
465
|
+
if (this.#timePane.value !== timePart) this.#timePane.value = timePart;
|
|
466
|
+
this.#timePane.toggleAttribute('disabled', !!this.disabled);
|
|
467
|
+
this.#timePane.toggleAttribute('readonly', !!this.readonly);
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// Lift a consumer-authored [slot="footer"] (Apply/Cancel explicit-commit
|
|
471
|
+
// affordance) into the popover, after the pane(s).
|
|
472
|
+
const authorFooter = this.querySelector(':scope > [slot="footer"]');
|
|
473
|
+
if (authorFooter && this.#popover && authorFooter.parentElement !== this.#popover) {
|
|
474
|
+
this.#popover.appendChild(authorFooter);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// A disabled/readonly host can't legitimately be open — belt-and-braces
|
|
478
|
+
// circuit breaker (mirrors the former datetime-picker.class.js render()).
|
|
479
|
+
if (this.open && (this.disabled || this.readonly)) {
|
|
480
|
+
this.open = false;
|
|
140
481
|
}
|
|
141
482
|
|
|
142
483
|
// Toggle popover
|
|
484
|
+
const justOpened = this.open && !this.#wasOpen;
|
|
485
|
+
const justClosed = !this.open && this.#wasOpen;
|
|
143
486
|
if (this.open) {
|
|
144
|
-
this.#renderCalendar();
|
|
145
487
|
this.#popover?.showPopover?.();
|
|
146
|
-
// ADR-0034 Rule 2: calendar panel (~330px
|
|
147
|
-
//
|
|
488
|
+
// ADR-0034 Rule 2: calendar panel (~330px, or the two-pane datetime
|
|
489
|
+
// width) >> trigger button (~200px) → center. Consumer can override
|
|
490
|
+
// via placement="bottom-start|top|…" attribute.
|
|
148
491
|
this.#anchorCleanup = anchorPopover(this.#trigger, this.#popover, {
|
|
149
492
|
placement: this.getAttribute('placement') || 'bottom', gap: 4,
|
|
150
493
|
});
|
|
@@ -153,6 +496,36 @@ export class UICalendarPicker extends UIFormElement {
|
|
|
153
496
|
if (!this.isConnected || !this.open) return;
|
|
154
497
|
document.addEventListener('pointerdown', this.#onOutside, { once: true });
|
|
155
498
|
});
|
|
499
|
+
// Move DOM focus into the grid so ITS OWN keydown listener (bound to
|
|
500
|
+
// itself, not this host) receives arrow/Enter/PageUp/PageDown/Home/End
|
|
501
|
+
// — same technique date-range-picker uses for its panes (gh#1371: the
|
|
502
|
+
// old inlined grid worked because #onKey lived on this host and
|
|
503
|
+
// intercepted keys regardless of where focus was; the composed
|
|
504
|
+
// substrate requires focus to actually be inside it). Gated to the
|
|
505
|
+
// closed→open transition only (`justOpened`) — running this on EVERY
|
|
506
|
+
// open-state render would re-steal focus back to the first day after
|
|
507
|
+
// every selection, since #onGridChange's `this.value = iso` triggers
|
|
508
|
+
// another render while still open (review finding, PR #1437).
|
|
509
|
+
if (justOpened) {
|
|
510
|
+
queueMicrotask(() => {
|
|
511
|
+
if (!this.open || !this.#grid) return;
|
|
512
|
+
const selected = this.#grid.querySelector('[data-cal-day][data-selected]:not([data-outside])');
|
|
513
|
+
const target = selected || this.#grid.querySelector('[data-cal-day]:not([disabled]):not([data-outside])');
|
|
514
|
+
const day = target && Number(target.dataset.date?.split('-')[2]);
|
|
515
|
+
// Route through the grid's own focusDay() rather than a raw
|
|
516
|
+
// `target.focus()` — focusDay() sets calendar-grid-ui's internal
|
|
517
|
+
// `#focusedDay` (the roving-tabindex + Enter-to-select state)
|
|
518
|
+
// BEFORE moving DOM focus, so Enter and subsequent arrow-key nav
|
|
519
|
+
// have a correct baseline immediately. A bare `.focus()` moves
|
|
520
|
+
// DOM focus without touching that internal state, leaving Enter
|
|
521
|
+
// with no selection target and arrow nav starting from day 1
|
|
522
|
+
// (review finding, PR #1437).
|
|
523
|
+
if (day) this.#grid.focusDay(day);
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
if (justOpened) {
|
|
527
|
+
this.dispatchEvent(new CustomEvent('open', { bubbles: true, detail: { trigger: this.#openTrigger } }));
|
|
528
|
+
}
|
|
156
529
|
} else {
|
|
157
530
|
this.#anchorCleanup?.();
|
|
158
531
|
this.#anchorCleanup = null;
|
|
@@ -162,11 +535,23 @@ export class UICalendarPicker extends UIFormElement {
|
|
|
162
535
|
}
|
|
163
536
|
this.#popover?.hidePopover?.();
|
|
164
537
|
document.removeEventListener('pointerdown', this.#onOutside);
|
|
538
|
+
if (justClosed) {
|
|
539
|
+
this.dispatchEvent(new CustomEvent('close', { bubbles: true, detail: { reason: this.#closeReason } }));
|
|
540
|
+
}
|
|
165
541
|
}
|
|
542
|
+
this.#wasOpen = this.open;
|
|
166
543
|
}
|
|
167
544
|
|
|
168
545
|
#displayText() {
|
|
169
546
|
if (!this.value) return this.placeholder;
|
|
547
|
+
if (this.#hasTimePane) {
|
|
548
|
+
const parsed = parseDatetime(this.value);
|
|
549
|
+
if (!parsed) return this.value;
|
|
550
|
+
const dPart = formatDatePart(parsed.date, this.format);
|
|
551
|
+
const tPart = formatTimePart(parsed.time, this.precision, this.hourcycle);
|
|
552
|
+
if (!dPart || !tPart) return this.placeholder;
|
|
553
|
+
return `${dPart} ${tPart}`;
|
|
554
|
+
}
|
|
170
555
|
const d = parseISO(this.value);
|
|
171
556
|
if (!d) return this.value;
|
|
172
557
|
switch (this.format) {
|
|
@@ -180,125 +565,55 @@ export class UICalendarPicker extends UIFormElement {
|
|
|
180
565
|
}
|
|
181
566
|
}
|
|
182
567
|
|
|
183
|
-
#renderCalendar() {
|
|
184
|
-
if (!this.#popover) return;
|
|
185
|
-
|
|
186
|
-
const year = this.#viewYear;
|
|
187
|
-
const month = this.#viewMonth;
|
|
188
|
-
const today = new Date();
|
|
189
|
-
const selected = parseISO(this.value);
|
|
190
|
-
const minDate = parseISO(this.min);
|
|
191
|
-
const maxDate = parseISO(this.max);
|
|
192
|
-
|
|
193
|
-
// Header
|
|
194
|
-
// <button-ui variant="ghost" icon="…"> (gh issue 276 wave 4) — no
|
|
195
|
-
// tabindex here, matching the native version: these nav buttons stay in
|
|
196
|
-
// the default tab order (unlike calendar-grid-ui's, which are pulled out
|
|
197
|
-
// via tabindex="-1" for its inline roving-tabindex day grid).
|
|
198
|
-
let h = `<div data-cal-header>
|
|
199
|
-
<button-ui variant="ghost" size="sm" icon="caret-left" data-cal-prev aria-label="Previous month"></button-ui>
|
|
200
|
-
<span data-cal-title>${MONTHS_LONG[month]} ${year}</span>
|
|
201
|
-
<button-ui variant="ghost" size="sm" icon="caret-right" data-cal-next aria-label="Next month"></button-ui>
|
|
202
|
-
</div>`;
|
|
203
|
-
|
|
204
|
-
// Day-of-week header
|
|
205
|
-
h += `<div data-cal-weekdays>${DAYS.map(d => `<span>${d}</span>`).join('')}</div>`;
|
|
206
|
-
|
|
207
|
-
// Calendar grid
|
|
208
|
-
const firstDay = new Date(year, month, 1).getDay();
|
|
209
|
-
const daysInMonth = new Date(year, month + 1, 0).getDate();
|
|
210
|
-
const daysInPrev = new Date(year, month, 0).getDate();
|
|
211
|
-
|
|
212
|
-
h += '<div data-cal-grid>';
|
|
213
|
-
|
|
214
|
-
// Previous month trailing days
|
|
215
|
-
for (let i = firstDay - 1; i >= 0; i--) {
|
|
216
|
-
const day = daysInPrev - i;
|
|
217
|
-
h += `<button-ui variant="ghost" data-cal-day data-outside disabled tabindex="-1" text="${day}"></button-ui>`;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
// Current month days
|
|
221
|
-
for (let d = 1; d <= daysInMonth; d++) {
|
|
222
|
-
const date = new Date(year, month, d);
|
|
223
|
-
const iso = toISO(year, month, d);
|
|
224
|
-
const isToday = sameDay(date, today);
|
|
225
|
-
const isSelected = selected && sameDay(date, selected);
|
|
226
|
-
const isDisabled = (minDate && date < minDate) || (maxDate && date > maxDate);
|
|
227
|
-
const isFocused = this.#focusedDay === d;
|
|
228
|
-
|
|
229
|
-
const attrs = [
|
|
230
|
-
'data-cal-day',
|
|
231
|
-
`data-date="${iso}"`,
|
|
232
|
-
isToday ? 'data-today' : '',
|
|
233
|
-
isSelected ? 'data-selected' : '',
|
|
234
|
-
isDisabled ? 'disabled' : '',
|
|
235
|
-
isFocused ? 'data-focused' : '',
|
|
236
|
-
`tabindex="${isFocused ? '0' : '-1'}"`,
|
|
237
|
-
].filter(Boolean).join(' ');
|
|
238
|
-
|
|
239
|
-
// <button-ui variant="ghost" text="…"> (gh issue 276 wave 4) — the day
|
|
240
|
-
// number moves from element text content to the `text` attribute.
|
|
241
|
-
h += `<button-ui variant="ghost" ${attrs} text="${d}"></button-ui>`;
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
// Next month leading days
|
|
245
|
-
const totalCells = firstDay + daysInMonth;
|
|
246
|
-
const remaining = (7 - (totalCells % 7)) % 7;
|
|
247
|
-
for (let d = 1; d <= remaining; d++) {
|
|
248
|
-
h += `<button-ui variant="ghost" data-cal-day data-outside disabled tabindex="-1" text="${d}"></button-ui>`;
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
h += '</div>';
|
|
252
|
-
|
|
253
|
-
this.#popover.innerHTML = h;
|
|
254
|
-
// Events are delegated on #popover (see #onPopoverClick) — no per-element wiring.
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
#navigate(delta) {
|
|
258
|
-
this.#viewMonth += delta;
|
|
259
|
-
if (this.#viewMonth > 11) { this.#viewMonth = 0; this.#viewYear++; }
|
|
260
|
-
if (this.#viewMonth < 0) { this.#viewMonth = 11; this.#viewYear--; }
|
|
261
|
-
this.#focusedDay = null;
|
|
262
|
-
this.#renderCalendar();
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
#selectDate(iso) {
|
|
266
|
-
this.value = iso;
|
|
267
|
-
this.syncValue(iso);
|
|
268
|
-
this.dispatchEvent(new CustomEvent('change', { bubbles: true, detail: { value: this.value } }));
|
|
269
|
-
// Re-render grid to reflect new selection; keep popover open until user clicks outside.
|
|
270
|
-
if (this.open) this.#renderCalendar();
|
|
271
|
-
}
|
|
272
|
-
|
|
273
568
|
#onClick = (e) => {
|
|
274
|
-
if (this.disabled) return;
|
|
275
|
-
if (this.#popover && e.composedPath().includes(this.#popover))
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
569
|
+
if (this.disabled || this.readonly) return;
|
|
570
|
+
if (this.#popover && e.composedPath().includes(this.#popover)) {
|
|
571
|
+
// Footer button handling — Apply / Cancel semantics for explicit-
|
|
572
|
+
// commit flows when the consumer wires data-action attrs onto
|
|
573
|
+
// authored slot="footer" buttons.
|
|
574
|
+
const actionBtn = e.target.closest?.('[data-action]');
|
|
575
|
+
if (actionBtn && this.contains(actionBtn)) {
|
|
576
|
+
const action = actionBtn.dataset.action;
|
|
577
|
+
if (action === 'apply') {
|
|
578
|
+
this.#commitFromParts({
|
|
579
|
+
date: this.#pending?.date ?? this.#grid?.value ?? '',
|
|
580
|
+
time: this.#pending?.time ?? this.#timePane?.value ?? '',
|
|
581
|
+
});
|
|
582
|
+
this.#closeReason = 'apply';
|
|
583
|
+
this.open = false;
|
|
584
|
+
} else if (action === 'cancel') {
|
|
585
|
+
this.#pending = null;
|
|
586
|
+
this.#closeReason = 'cancel';
|
|
587
|
+
this.open = false;
|
|
588
|
+
}
|
|
288
589
|
}
|
|
590
|
+
return;
|
|
591
|
+
}
|
|
592
|
+
if (this.open) {
|
|
593
|
+
this.#closeReason = 'outside';
|
|
594
|
+
this.open = false;
|
|
595
|
+
} else {
|
|
596
|
+
this.#openTrigger = 'click';
|
|
597
|
+
this.open = true;
|
|
289
598
|
}
|
|
290
599
|
};
|
|
291
600
|
|
|
292
601
|
#onOutside = (e) => {
|
|
293
602
|
if (!this.contains(e.target) && !(this.#popover && e.composedPath().includes(this.#popover))) {
|
|
603
|
+
this.#closeReason = 'outside';
|
|
294
604
|
this.open = false;
|
|
295
605
|
}
|
|
296
606
|
};
|
|
297
607
|
|
|
608
|
+
// Only the OPEN/CLOSE affordance lives here now — day navigation and
|
|
609
|
+
// selection are the composed calendar-grid-ui's own keydown contract
|
|
610
|
+
// once focus is inside it (see the queueMicrotask in render()).
|
|
298
611
|
#onKey = (e) => {
|
|
612
|
+
if (this.disabled || this.readonly) return;
|
|
299
613
|
if (!this.open) {
|
|
300
614
|
if (e.key === 'Enter' || e.key === ' ') {
|
|
301
615
|
e.preventDefault();
|
|
616
|
+
this.#openTrigger = 'keyboard';
|
|
302
617
|
this.open = true;
|
|
303
618
|
}
|
|
304
619
|
return;
|
|
@@ -306,44 +621,8 @@ export class UICalendarPicker extends UIFormElement {
|
|
|
306
621
|
|
|
307
622
|
if (e.key === 'Escape') {
|
|
308
623
|
e.preventDefault();
|
|
624
|
+
this.#closeReason = 'escape';
|
|
309
625
|
this.open = false;
|
|
310
|
-
return;
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
if (e.key === 'Enter') {
|
|
314
|
-
e.preventDefault();
|
|
315
|
-
if (this.#focusedDay) {
|
|
316
|
-
const iso = toISO(this.#viewYear, this.#viewMonth, this.#focusedDay);
|
|
317
|
-
const date = new Date(this.#viewYear, this.#viewMonth, this.#focusedDay);
|
|
318
|
-
const minDate = parseISO(this.min);
|
|
319
|
-
const maxDate = parseISO(this.max);
|
|
320
|
-
if ((!minDate || date >= minDate) && (!maxDate || date <= maxDate)) {
|
|
321
|
-
this.#selectDate(iso);
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
return;
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
const arrows = { ArrowUp: -7, ArrowDown: 7, ArrowLeft: -1, ArrowRight: 1 };
|
|
328
|
-
if (arrows[e.key] !== undefined) {
|
|
329
|
-
e.preventDefault();
|
|
330
|
-
const daysInMonth = new Date(this.#viewYear, this.#viewMonth + 1, 0).getDate();
|
|
331
|
-
let day = (this.#focusedDay || 1) + arrows[e.key];
|
|
332
|
-
|
|
333
|
-
if (day < 1) {
|
|
334
|
-
this.#navigate(-1);
|
|
335
|
-
const prevDays = new Date(this.#viewYear, this.#viewMonth + 1, 0).getDate();
|
|
336
|
-
this.#focusedDay = prevDays + day;
|
|
337
|
-
} else if (day > daysInMonth) {
|
|
338
|
-
this.#navigate(1);
|
|
339
|
-
this.#focusedDay = day - daysInMonth;
|
|
340
|
-
} else {
|
|
341
|
-
this.#focusedDay = day;
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
this.#renderCalendar();
|
|
345
|
-
const focused = this.#popover?.querySelector('[data-focused]');
|
|
346
|
-
focused?.focus();
|
|
347
626
|
}
|
|
348
627
|
};
|
|
349
628
|
|
|
@@ -356,14 +635,28 @@ export class UICalendarPicker extends UIFormElement {
|
|
|
356
635
|
this.removeEventListener('click', this.#onClick);
|
|
357
636
|
this.removeEventListener('keydown', this.#onKey);
|
|
358
637
|
document.removeEventListener('pointerdown', this.#onOutside);
|
|
359
|
-
if (this.#
|
|
360
|
-
this.#
|
|
638
|
+
if (this.#grid && this.#gridBound) {
|
|
639
|
+
this.#grid.removeEventListener('change', this.#onGridChange);
|
|
640
|
+
}
|
|
641
|
+
if (this.#timePane && this.#timePaneBound) {
|
|
642
|
+
this.#timePane.removeEventListener('change', this.#onTimePaneChange);
|
|
643
|
+
this.#timePane.removeEventListener('input', this.#onTimePaneInput);
|
|
644
|
+
this.#timePane.removeEventListener('invalid', this.#onPaneInvalid);
|
|
361
645
|
}
|
|
362
|
-
this.#
|
|
646
|
+
this.#gridBound = false;
|
|
647
|
+
this.#timePaneBound = false;
|
|
363
648
|
this.#anchorCleanup?.();
|
|
364
649
|
this.#anchorCleanup = null;
|
|
365
650
|
this.#popover?.hidePopover?.();
|
|
366
651
|
this.#popover = null;
|
|
367
652
|
this.#trigger = null;
|
|
653
|
+
this.#grid = null;
|
|
654
|
+
this.#divider = null;
|
|
655
|
+
this.#timePane = null;
|
|
656
|
+
this.#pending = null;
|
|
657
|
+
// Re-arm the closed→open transition gate — a reconnect while `open`
|
|
658
|
+
// was never cleared (e.g. moved in the DOM mid-open) must still be
|
|
659
|
+
// treated as a fresh open on the next render, not silently skipped.
|
|
660
|
+
this.#wasOpen = false;
|
|
368
661
|
}
|
|
369
662
|
}
|