@pine-ds/core 4.2.0 → 4.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/components/closest.js +1 -1
- package/components/floating-ui.dom.js +1 -1
- package/components/i18n-format.js +1 -1
- package/components/index.d.ts +2 -0
- package/components/index.js +1 -1
- package/components/index2.js +1 -1
- package/components/mock-pds-modal.js +1 -1
- package/components/pds-button2.js +1 -1
- package/components/pds-checkbox2.js +1 -1
- package/components/pds-chip2.js +1 -1
- package/components/pds-combobox.js +2 -2
- package/components/pds-container.js +1 -1
- package/components/pds-copytext.js +1 -1
- package/components/pds-datepicker.d.ts +11 -0
- package/components/pds-datepicker.js +1 -0
- package/components/pds-divider.js +1 -1
- package/components/pds-drawer-content.js +1 -1
- package/components/pds-drawer-footer.js +1 -1
- package/components/pds-drawer-header.js +1 -1
- package/components/pds-drawer.js +1 -1
- package/components/pds-dropdown-menu-item.js +1 -1
- package/components/pds-dropdown-menu-separator.js +1 -1
- package/components/pds-dropdown-menu.js +1 -1
- package/components/pds-filter.js +1 -1
- package/components/pds-filters.js +1 -1
- package/components/pds-icon2.js +1 -1
- package/components/pds-image.js +1 -1
- package/components/pds-input.js +1 -1
- package/components/pds-input2.js +1 -0
- package/components/pds-link2.js +1 -1
- package/components/pds-loader2.js +1 -1
- package/components/pds-modal-content.js +1 -1
- package/components/pds-modal-footer.js +1 -1
- package/components/pds-modal-header.js +1 -1
- package/components/pds-modal2.js +1 -1
- package/components/pds-multiselect.js +1 -1
- package/components/pds-popover.js +1 -1
- package/components/pds-progress.js +1 -1
- package/components/pds-property.js +1 -1
- package/components/pds-radio-group.js +1 -1
- package/components/pds-radio.js +1 -1
- package/components/pds-row.js +1 -1
- package/components/pds-select.js +1 -1
- package/components/pds-sortable-item.js +1 -1
- package/components/pds-sortable.js +2 -2
- package/components/pds-switch.js +1 -1
- package/components/pds-table-body.js +1 -1
- package/components/pds-table-cell2.js +1 -1
- package/components/pds-table-head-cell2.js +1 -1
- package/components/pds-table-head.js +1 -1
- package/components/pds-table-row.js +1 -1
- package/components/pds-tabpanel.js +1 -1
- package/components/pds-tabs.js +1 -1
- package/components/pds-text2.js +1 -1
- package/components/pds-textarea.js +1 -1
- package/components/pds-toast.js +1 -1
- package/components/pds-tooltip.js +1 -1
- package/components/utils.js +1 -1
- package/dist/cjs/closest-D0qyYtJ3.js +20 -0
- package/dist/cjs/{floating-ui.dom-BZk7Blsu.js → floating-ui.dom-Dc8Anyel.js} +79 -0
- package/dist/cjs/{i18n-format-CrpIhahc.js → i18n-format-BD0PmYzT.js} +1 -0
- package/dist/cjs/{index-CuQil7H0.js → index-Bl5KnQay.js} +4 -1
- package/dist/cjs/{index-Bu3NXpbK.js → index-D-QQKB0y.js} +9 -1
- package/dist/cjs/loader.cjs.js +2 -2
- package/dist/cjs/mock-pds-modal.cjs.entry.js +3 -3
- package/dist/cjs/pds-accordion.cjs.entry.js +2 -2
- package/dist/cjs/pds-alert.cjs.entry.js +1 -1
- package/dist/cjs/pds-avatar.cjs.entry.js +2 -2
- package/dist/cjs/pds-box.cjs.entry.js +2 -2
- package/dist/cjs/pds-button.cjs.entry.js +54 -5
- package/dist/cjs/pds-checkbox.cjs.entry.js +14 -6
- package/dist/cjs/pds-chip.cjs.entry.js +34 -9
- package/dist/cjs/pds-combobox.cjs.entry.js +1225 -117
- package/dist/cjs/pds-container.cjs.entry.js +2 -2
- package/dist/cjs/pds-copytext.cjs.entry.js +5 -5
- package/dist/cjs/pds-datepicker.cjs.entry.js +1962 -0
- package/dist/cjs/pds-divider.cjs.entry.js +2 -2
- package/dist/cjs/pds-drawer-content.cjs.entry.js +2 -2
- package/dist/cjs/pds-drawer-footer.cjs.entry.js +2 -2
- package/dist/cjs/pds-drawer-header.cjs.entry.js +2 -2
- package/dist/cjs/pds-drawer.cjs.entry.js +11 -4
- package/dist/cjs/pds-dropdown-menu-item.cjs.entry.js +2 -2
- package/dist/cjs/pds-dropdown-menu-separator.cjs.entry.js +2 -2
- package/dist/cjs/pds-dropdown-menu.cjs.entry.js +11 -4
- package/dist/cjs/pds-filter.cjs.entry.js +3 -3
- package/dist/cjs/pds-filters.cjs.entry.js +2 -2
- package/dist/cjs/pds-icon.cjs.entry.js +2 -2
- package/dist/cjs/pds-image.cjs.entry.js +3 -3
- package/dist/cjs/pds-input.cjs.entry.js +8 -5
- package/dist/cjs/pds-link.cjs.entry.js +5 -5
- package/dist/cjs/pds-loader.cjs.entry.js +2 -2
- package/dist/cjs/pds-modal-content.cjs.entry.js +3 -3
- package/dist/cjs/pds-modal-footer.cjs.entry.js +2 -2
- package/dist/cjs/pds-modal-header.cjs.entry.js +2 -2
- package/dist/cjs/pds-modal.cjs.entry.js +42 -8
- package/dist/cjs/pds-multiselect.cjs.entry.js +264 -51
- package/dist/cjs/pds-popover.cjs.entry.js +30 -12
- package/dist/cjs/pds-progress.cjs.entry.js +4 -2
- package/dist/cjs/pds-property.cjs.entry.js +2 -2
- package/dist/cjs/pds-radio-group.cjs.entry.js +5 -5
- package/dist/cjs/pds-radio.cjs.entry.js +6 -6
- package/dist/cjs/pds-row.cjs.entry.js +2 -2
- package/dist/cjs/pds-select.cjs.entry.js +3 -3
- package/dist/cjs/pds-sortable-item.cjs.entry.js +3 -3
- package/dist/cjs/pds-sortable.cjs.entry.js +2 -2
- package/dist/cjs/pds-switch.cjs.entry.js +14 -6
- package/dist/cjs/pds-tab.cjs.entry.js +1 -1
- package/dist/cjs/pds-table-body.cjs.entry.js +2 -2
- package/dist/cjs/pds-table-cell.cjs.entry.js +5 -5
- package/dist/cjs/pds-table-head-cell.cjs.entry.js +4 -4
- package/dist/cjs/pds-table-head.cjs.entry.js +3 -3
- package/dist/cjs/pds-table-row.cjs.entry.js +3 -3
- package/dist/cjs/pds-table.cjs.entry.js +1 -1
- package/dist/cjs/pds-tabpanel.cjs.entry.js +2 -2
- package/dist/cjs/pds-tabs.cjs.entry.js +2 -2
- package/dist/cjs/pds-text.cjs.entry.js +5 -5
- package/dist/cjs/pds-textarea.cjs.entry.js +14 -9
- package/dist/cjs/pds-toast.cjs.entry.js +4 -4
- package/dist/cjs/pds-tooltip.cjs.entry.js +4 -4
- package/dist/cjs/pine-core.cjs.js +2 -2
- package/dist/cjs/{truncation-tooltip-Bps7_rUy.js → truncation-tooltip-CYgQfBLt.js} +1 -1
- package/dist/cjs/{utils-7jx8T2mW.js → utils-BhRmyxT8.js} +51 -6
- package/dist/collection/collection-manifest.json +1 -0
- package/dist/collection/components/pds-button/pds-button.css +1 -2
- package/dist/collection/components/pds-button/pds-button.js +53 -2
- package/dist/collection/components/pds-checkbox/pds-checkbox.css +13 -4
- package/dist/collection/components/pds-checkbox/pds-checkbox.js +19 -3
- package/dist/collection/components/pds-chip/pds-chip.css +60 -6
- package/dist/collection/components/pds-chip/pds-chip.js +69 -4
- package/dist/collection/components/pds-chip/pds-chip.tokens.css +16 -0
- package/dist/collection/components/pds-chip/stories/pds-chip.stories.js +39 -1
- package/dist/collection/components/pds-combobox/grouped-options-panel.js +262 -0
- package/dist/collection/components/pds-combobox/pds-combobox.css +243 -15
- package/dist/collection/components/pds-combobox/pds-combobox.js +1129 -115
- package/dist/collection/components/pds-combobox/stories/pds-combobox.stories.js +240 -0
- package/dist/collection/components/pds-container/pds-container.js +1 -1
- package/dist/collection/components/pds-copytext/pds-copytext.js +1 -1
- package/dist/collection/components/pds-datepicker/calendar-portal.js +136 -0
- package/dist/collection/components/pds-datepicker/date-utils.js +132 -0
- package/dist/collection/components/pds-datepicker/datepicker-interface.js +1 -0
- package/dist/collection/components/pds-datepicker/pds-datepicker.css +545 -0
- package/dist/collection/components/pds-datepicker/pds-datepicker.js +2406 -0
- package/dist/collection/components/pds-datepicker/presets.js +96 -0
- package/dist/collection/components/pds-datepicker/stories/pds-datepicker.stories.js +195 -0
- package/dist/collection/components/pds-datepicker/value.js +238 -0
- package/dist/collection/components/pds-divider/pds-divider.js +1 -1
- package/dist/collection/components/pds-drawer/pds-drawer-content/pds-drawer-content.js +1 -1
- package/dist/collection/components/pds-drawer/pds-drawer-footer/pds-drawer-footer.js +1 -1
- package/dist/collection/components/pds-drawer/pds-drawer-header/pds-drawer-header.js +1 -1
- package/dist/collection/components/pds-drawer/pds-drawer.css +4 -0
- package/dist/collection/components/pds-drawer/pds-drawer.js +9 -2
- package/dist/collection/components/pds-dropdown-menu/pds-dropdown-menu-item/pds-dropdown-menu-item.js +1 -1
- package/dist/collection/components/pds-dropdown-menu/pds-dropdown-menu-separator/pds-dropdown-menu-separator.js +1 -1
- package/dist/collection/components/pds-dropdown-menu/pds-dropdown-menu.js +9 -2
- package/dist/collection/components/pds-filters/pds-filter/pds-filter.js +1 -1
- package/dist/collection/components/pds-filters/pds-filters.js +1 -1
- package/dist/collection/components/pds-image/pds-image.js +2 -2
- package/dist/collection/components/pds-input/pds-input.js +6 -3
- package/dist/collection/components/pds-link/pds-link.js +2 -2
- package/dist/collection/components/pds-loader/pds-loader.js +1 -1
- package/dist/collection/components/pds-modal/pds-modal-content/pds-modal-content.js +2 -2
- package/dist/collection/components/pds-modal/pds-modal-footer/pds-modal-footer.js +1 -1
- package/dist/collection/components/pds-modal/pds-modal-header/pds-modal-header.js +1 -1
- package/dist/collection/components/pds-modal/pds-modal.css +20 -0
- package/dist/collection/components/pds-modal/pds-modal.js +55 -10
- package/dist/collection/components/pds-modal/stories/pds-modal.stories.js +14 -0
- package/dist/collection/components/pds-modal/test/mock-pds-modal.js +1 -1
- package/dist/collection/components/pds-multiselect/pds-multiselect.css +19 -1
- package/dist/collection/components/pds-multiselect/pds-multiselect.js +390 -47
- package/dist/collection/components/pds-multiselect/stories/pds-multiselect.stories.js +136 -1
- package/dist/collection/components/pds-popover/pds-popover.js +29 -11
- package/dist/collection/components/pds-progress/pds-progress.js +3 -1
- package/dist/collection/components/pds-property/pds-property.js +1 -1
- package/dist/collection/components/pds-radio/pds-radio.css +1 -2
- package/dist/collection/components/pds-radio/pds-radio.js +3 -3
- package/dist/collection/components/pds-radio-group/pds-radio-group.js +3 -3
- package/dist/collection/components/pds-row/pds-row.js +1 -1
- package/dist/collection/components/pds-select/pds-select.js +1 -1
- package/dist/collection/components/pds-sortable/pds-sortable-item/pds-sortable-item.js +1 -1
- package/dist/collection/components/pds-sortable/pds-sortable.js +1 -1
- package/dist/collection/components/pds-switch/pds-switch.css +3 -0
- package/dist/collection/components/pds-switch/pds-switch.js +20 -4
- package/dist/collection/components/pds-table/pds-table-body/pds-table-body.js +1 -1
- package/dist/collection/components/pds-table/pds-table-cell/pds-table-cell.js +2 -2
- package/dist/collection/components/pds-table/pds-table-head/pds-table-head.js +1 -1
- package/dist/collection/components/pds-table/pds-table-head-cell/pds-table-head-cell.js +2 -2
- package/dist/collection/components/pds-table/pds-table-row/pds-table-row.js +1 -1
- package/dist/collection/components/pds-tabs/pds-tabpanel/pds-tabpanel.js +1 -1
- package/dist/collection/components/pds-tabs/pds-tabs.js +1 -1
- package/dist/collection/components/pds-text/pds-text.js +1 -1
- package/dist/collection/components/pds-textarea/pds-textarea.js +11 -6
- package/dist/collection/components/pds-toast/pds-toast.js +3 -3
- package/dist/collection/components/pds-tooltip/pds-tooltip.js +2 -2
- package/dist/collection/utils/closest.js +8 -0
- package/dist/collection/utils/utils.js +49 -6
- package/dist/docs.json +2055 -84
- package/dist/esm/closest-DHPzKMVS.js +17 -0
- package/dist/esm/{floating-ui.dom-DDtwbwIl.js → floating-ui.dom-BPqJgIq6.js} +79 -1
- package/dist/esm/{i18n-format-BKsJZTyn.js → i18n-format-BJgZHCWe.js} +1 -1
- package/dist/esm/{index-CTLu1TM4.js → index-CB8Yh0Nt.js} +4 -1
- package/dist/esm/{index-DdPaBxPo.js → index-DxI-61GK.js} +6 -2
- package/dist/esm/loader.js +3 -3
- package/dist/esm/mock-pds-modal.entry.js +3 -3
- package/dist/esm/pds-accordion.entry.js +2 -2
- package/dist/esm/pds-alert.entry.js +1 -1
- package/dist/esm/pds-avatar.entry.js +2 -2
- package/dist/esm/pds-box.entry.js +2 -2
- package/dist/esm/pds-button.entry.js +54 -5
- package/dist/esm/pds-checkbox.entry.js +14 -6
- package/dist/esm/pds-chip.entry.js +34 -9
- package/dist/esm/pds-combobox.entry.js +1225 -117
- package/dist/esm/pds-container.entry.js +2 -2
- package/dist/esm/pds-copytext.entry.js +5 -5
- package/dist/esm/pds-datepicker.entry.js +1960 -0
- package/dist/esm/pds-divider.entry.js +2 -2
- package/dist/esm/pds-drawer-content.entry.js +2 -2
- package/dist/esm/pds-drawer-footer.entry.js +2 -2
- package/dist/esm/pds-drawer-header.entry.js +2 -2
- package/dist/esm/pds-drawer.entry.js +11 -4
- package/dist/esm/pds-dropdown-menu-item.entry.js +2 -2
- package/dist/esm/pds-dropdown-menu-separator.entry.js +2 -2
- package/dist/esm/pds-dropdown-menu.entry.js +11 -4
- package/dist/esm/pds-filter.entry.js +3 -3
- package/dist/esm/pds-filters.entry.js +2 -2
- package/dist/esm/pds-icon.entry.js +2 -2
- package/dist/esm/pds-image.entry.js +3 -3
- package/dist/esm/pds-input.entry.js +8 -5
- package/dist/esm/pds-link.entry.js +5 -5
- package/dist/esm/pds-loader.entry.js +2 -2
- package/dist/esm/pds-modal-content.entry.js +3 -3
- package/dist/esm/pds-modal-footer.entry.js +2 -2
- package/dist/esm/pds-modal-header.entry.js +2 -2
- package/dist/esm/pds-modal.entry.js +42 -8
- package/dist/esm/pds-multiselect.entry.js +264 -51
- package/dist/esm/pds-popover.entry.js +30 -12
- package/dist/esm/pds-progress.entry.js +4 -2
- package/dist/esm/pds-property.entry.js +2 -2
- package/dist/esm/pds-radio-group.entry.js +5 -5
- package/dist/esm/pds-radio.entry.js +6 -6
- package/dist/esm/pds-row.entry.js +2 -2
- package/dist/esm/pds-select.entry.js +3 -3
- package/dist/esm/pds-sortable-item.entry.js +3 -3
- package/dist/esm/pds-sortable.entry.js +2 -2
- package/dist/esm/pds-switch.entry.js +14 -6
- package/dist/esm/pds-tab.entry.js +1 -1
- package/dist/esm/pds-table-body.entry.js +2 -2
- package/dist/esm/pds-table-cell.entry.js +5 -5
- package/dist/esm/pds-table-head-cell.entry.js +4 -4
- package/dist/esm/pds-table-head.entry.js +3 -3
- package/dist/esm/pds-table-row.entry.js +3 -3
- package/dist/esm/pds-table.entry.js +1 -1
- package/dist/esm/pds-tabpanel.entry.js +2 -2
- package/dist/esm/pds-tabs.entry.js +2 -2
- package/dist/esm/pds-text.entry.js +5 -5
- package/dist/esm/pds-textarea.entry.js +14 -9
- package/dist/esm/pds-toast.entry.js +4 -4
- package/dist/esm/pds-tooltip.entry.js +4 -4
- package/dist/esm/pine-core.js +3 -3
- package/dist/esm/{truncation-tooltip-CdtEBn_5.js → truncation-tooltip-_902ENWC.js} +1 -1
- package/dist/esm/{utils-tJa4KYNO.js → utils-OYK5-nwe.js} +50 -7
- package/dist/esm-es5/closest-DHPzKMVS.js +1 -0
- package/dist/esm-es5/floating-ui.dom-BPqJgIq6.js +1 -0
- package/dist/esm-es5/{i18n-format-BKsJZTyn.js → i18n-format-BJgZHCWe.js} +1 -1
- package/dist/esm-es5/index-CB8Yh0Nt.js +2 -0
- package/dist/esm-es5/index-DxI-61GK.js +1 -0
- package/dist/esm-es5/loader.js +1 -1
- package/dist/esm-es5/mock-pds-modal.entry.js +1 -1
- package/dist/esm-es5/pds-accordion.entry.js +1 -1
- package/dist/esm-es5/pds-alert.entry.js +1 -1
- package/dist/esm-es5/pds-avatar.entry.js +1 -1
- package/dist/esm-es5/pds-box.entry.js +1 -1
- package/dist/esm-es5/pds-button.entry.js +1 -1
- package/dist/esm-es5/pds-checkbox.entry.js +1 -1
- package/dist/esm-es5/pds-chip.entry.js +1 -1
- package/dist/esm-es5/pds-combobox.entry.js +2 -2
- package/dist/esm-es5/pds-container.entry.js +1 -1
- package/dist/esm-es5/pds-copytext.entry.js +1 -1
- package/dist/esm-es5/pds-datepicker.entry.js +1 -0
- package/dist/esm-es5/pds-divider.entry.js +1 -1
- package/dist/esm-es5/pds-drawer-content.entry.js +1 -1
- package/dist/esm-es5/pds-drawer-footer.entry.js +1 -1
- package/dist/esm-es5/pds-drawer-header.entry.js +1 -1
- package/dist/esm-es5/pds-drawer.entry.js +1 -1
- package/dist/esm-es5/pds-dropdown-menu-item.entry.js +1 -1
- package/dist/esm-es5/pds-dropdown-menu-separator.entry.js +1 -1
- package/dist/esm-es5/pds-dropdown-menu.entry.js +1 -1
- package/dist/esm-es5/pds-filter.entry.js +1 -1
- package/dist/esm-es5/pds-filters.entry.js +1 -1
- package/dist/esm-es5/pds-icon.entry.js +1 -1
- package/dist/esm-es5/pds-image.entry.js +1 -1
- package/dist/esm-es5/pds-input.entry.js +1 -1
- package/dist/esm-es5/pds-link.entry.js +1 -1
- package/dist/esm-es5/pds-loader.entry.js +1 -1
- package/dist/esm-es5/pds-modal-content.entry.js +1 -1
- package/dist/esm-es5/pds-modal-footer.entry.js +1 -1
- package/dist/esm-es5/pds-modal-header.entry.js +1 -1
- package/dist/esm-es5/pds-modal.entry.js +1 -1
- package/dist/esm-es5/pds-multiselect.entry.js +1 -1
- package/dist/esm-es5/pds-popover.entry.js +1 -1
- package/dist/esm-es5/pds-progress.entry.js +1 -1
- package/dist/esm-es5/pds-property.entry.js +1 -1
- package/dist/esm-es5/pds-radio-group.entry.js +1 -1
- package/dist/esm-es5/pds-radio.entry.js +1 -1
- package/dist/esm-es5/pds-row.entry.js +1 -1
- package/dist/esm-es5/pds-select.entry.js +1 -1
- package/dist/esm-es5/pds-sortable-item.entry.js +1 -1
- package/dist/esm-es5/pds-sortable.entry.js +2 -2
- package/dist/esm-es5/pds-switch.entry.js +1 -1
- package/dist/esm-es5/pds-tab.entry.js +1 -1
- package/dist/esm-es5/pds-table-body.entry.js +1 -1
- package/dist/esm-es5/pds-table-cell.entry.js +1 -1
- package/dist/esm-es5/pds-table-head-cell.entry.js +1 -1
- package/dist/esm-es5/pds-table-head.entry.js +1 -1
- package/dist/esm-es5/pds-table-row.entry.js +1 -1
- package/dist/esm-es5/pds-table.entry.js +1 -1
- package/dist/esm-es5/pds-tabpanel.entry.js +1 -1
- package/dist/esm-es5/pds-tabs.entry.js +1 -1
- package/dist/esm-es5/pds-text.entry.js +1 -1
- package/dist/esm-es5/pds-textarea.entry.js +1 -1
- package/dist/esm-es5/pds-toast.entry.js +1 -1
- package/dist/esm-es5/pds-tooltip.entry.js +1 -1
- package/dist/esm-es5/pine-core.js +1 -1
- package/dist/esm-es5/{truncation-tooltip-CdtEBn_5.js → truncation-tooltip-_902ENWC.js} +1 -1
- package/dist/esm-es5/utils-OYK5-nwe.js +1 -0
- package/dist/pine-core/p-0414092a.system.entry.js +1 -0
- package/dist/pine-core/p-04807201.entry.js +1 -0
- package/dist/pine-core/{p-56b34569.system.entry.js → p-07f0d168.system.entry.js} +1 -1
- package/dist/pine-core/p-08f5d19c.entry.js +1 -0
- package/dist/pine-core/{p-15d3a4bc.system.entry.js → p-0aaf8faa.system.entry.js} +1 -1
- package/dist/pine-core/p-0bad5955.entry.js +1 -0
- package/dist/pine-core/{p-4d388957.system.entry.js → p-0c3bf377.system.entry.js} +1 -1
- package/dist/pine-core/p-0ce79cd0.entry.js +1 -0
- package/dist/pine-core/{p-3464b456.system.entry.js → p-1057b8c3.system.entry.js} +1 -1
- package/dist/pine-core/{p-bf3afa16.system.entry.js → p-108c910a.system.entry.js} +1 -1
- package/dist/pine-core/{p-53a4852e.system.entry.js → p-166f61a6.system.entry.js} +1 -1
- package/dist/pine-core/{p-31e1722e.entry.js → p-16c8f5e3.entry.js} +1 -1
- package/dist/pine-core/p-16e258d1.entry.js +1 -0
- package/dist/pine-core/{p-591fbff5.entry.js → p-1be33c95.entry.js} +1 -1
- package/dist/pine-core/p-1e3a31da.entry.js +1 -0
- package/dist/pine-core/p-1f228db9.entry.js +1 -0
- package/dist/pine-core/p-257c6ef9.entry.js +1 -0
- package/dist/pine-core/p-26415245.system.entry.js +1 -0
- package/dist/pine-core/p-26e1e5bb.entry.js +1 -0
- package/dist/pine-core/p-27c06bf8.entry.js +2 -0
- package/dist/pine-core/p-31aee437.entry.js +1 -0
- package/dist/pine-core/p-330a76f6.system.entry.js +1 -0
- package/dist/pine-core/{p-3c4236a8.system.entry.js → p-33c63be6.system.entry.js} +1 -1
- package/dist/pine-core/{p-d46adf7d.entry.js → p-344609f9.entry.js} +2 -2
- package/dist/pine-core/{p-049681fc.entry.js → p-3a1711c1.entry.js} +1 -1
- package/dist/pine-core/p-3a9c58bd.system.entry.js +1 -0
- package/dist/pine-core/{p-e3a7d2db.entry.js → p-3ebd543f.entry.js} +1 -1
- package/dist/pine-core/p-3fbcb0c8.system.entry.js +1 -0
- package/dist/pine-core/{p-b923a70b.entry.js → p-427b4aeb.entry.js} +1 -1
- package/dist/pine-core/{p-5772f260.system.entry.js → p-42bd87ea.system.entry.js} +1 -1
- package/dist/pine-core/{p-1e2a9b5d.system.entry.js → p-43c691bf.system.entry.js} +1 -1
- package/dist/pine-core/p-4566d659.system.entry.js +1 -0
- package/dist/pine-core/p-4b8cf04a.system.entry.js +1 -0
- package/dist/pine-core/p-4d6f471c.entry.js +1 -0
- package/dist/pine-core/{p-3e0c6384.entry.js → p-4e7580c4.entry.js} +1 -1
- package/dist/pine-core/{p-0ab735aa.system.entry.js → p-4f4a2d2a.system.entry.js} +1 -1
- package/dist/pine-core/{p-7e2b934d.entry.js → p-5211aba7.entry.js} +1 -1
- package/dist/pine-core/{p-376f8286.entry.js → p-524b7d3f.entry.js} +1 -1
- package/dist/pine-core/{p-3f12afb1.entry.js → p-5613c64c.entry.js} +1 -1
- package/dist/pine-core/p-5bb24cb4.entry.js +1 -0
- package/dist/pine-core/{p-d7865297.entry.js → p-5bb80b3c.entry.js} +1 -1
- package/dist/pine-core/{p-e3cd4627.system.entry.js → p-5c6e0a26.system.entry.js} +1 -1
- package/dist/pine-core/{p-51dff5af.system.entry.js → p-5db57551.system.entry.js} +1 -1
- package/dist/pine-core/{p-f94667b5.entry.js → p-5eb00ba5.entry.js} +1 -1
- package/dist/pine-core/p-61756414.system.entry.js +1 -0
- package/dist/pine-core/{p-e5bbe8cc.entry.js → p-682e9e8f.entry.js} +1 -1
- package/dist/pine-core/p-6965db1d.entry.js +1 -0
- package/dist/pine-core/{p-5aea1082.entry.js → p-6b180ee2.entry.js} +1 -1
- package/dist/pine-core/{p-d21739eb.entry.js → p-6c53962f.entry.js} +1 -1
- package/dist/pine-core/p-6da6520b.system.entry.js +3 -0
- package/dist/pine-core/p-6e3bf862.system.entry.js +1 -0
- package/dist/pine-core/{p-053b0898.system.entry.js → p-71c9f866.system.entry.js} +1 -1
- package/dist/pine-core/{p-78fd593d.system.entry.js → p-7265b94c.system.entry.js} +1 -1
- package/dist/pine-core/{p-9187c26c.system.entry.js → p-74229b5e.system.entry.js} +1 -1
- package/dist/pine-core/p-7512c2d4.system.entry.js +1 -0
- package/dist/pine-core/p-76db406d.system.entry.js +1 -0
- package/dist/pine-core/{p-e1653d75.system.entry.js → p-79aa5f43.system.entry.js} +1 -1
- package/dist/pine-core/p-7bbcda0f.system.entry.js +1 -0
- package/dist/pine-core/p-7c8946e6.entry.js +1 -0
- package/dist/pine-core/p-7c98c90a.system.entry.js +1 -0
- package/dist/pine-core/p-81292c03.system.entry.js +1 -0
- package/dist/pine-core/p-81300046.system.entry.js +1 -0
- package/dist/pine-core/{p-a11a9c40.system.entry.js → p-8252cd84.system.entry.js} +1 -1
- package/dist/pine-core/{p-d0d2a886.system.entry.js → p-84ba0e6a.system.entry.js} +1 -1
- package/dist/pine-core/{p-c1f8a187.entry.js → p-86fc0bfa.entry.js} +1 -1
- package/dist/pine-core/p-90692bb6.entry.js +1 -0
- package/dist/pine-core/{p-2eb8d697.system.entry.js → p-90ea812c.system.entry.js} +1 -1
- package/dist/pine-core/p-948826db.entry.js +1 -0
- package/dist/pine-core/{p-714bfdbd.entry.js → p-9b66642d.entry.js} +1 -1
- package/dist/pine-core/p-BJgZHCWe.js +1 -0
- package/dist/pine-core/p-BPqJgIq6.js +1 -0
- package/dist/pine-core/p-BSRFZB4I.system.js +1 -0
- package/dist/pine-core/p-BZsj6eAn.system.js +1 -0
- package/dist/pine-core/p-CB8Yh0Nt.js +2 -0
- package/dist/pine-core/{p-B2qvvB3q.system.js → p-CBu9UrXr.system.js} +1 -1
- package/dist/pine-core/p-COtCa1Md.system.js +1 -0
- package/dist/pine-core/p-CTQZ2fkv.system.js +1 -0
- package/dist/pine-core/{p-B2nn1gSd.js → p-CnM3cCof.js} +1 -1
- package/dist/pine-core/p-CnO10OxH.system.js +1 -0
- package/dist/pine-core/p-D-akEPIU.system.js +2 -0
- package/dist/pine-core/p-DHPzKMVS.js +1 -0
- package/dist/pine-core/p-Dx8tEj0U.system.js +1 -0
- package/dist/pine-core/p-DxI-61GK.js +1 -0
- package/dist/pine-core/p-OYK5-nwe.js +1 -0
- package/dist/pine-core/p-a10af84b.system.entry.js +1 -0
- package/dist/pine-core/p-a21b530a.system.entry.js +1 -0
- package/dist/pine-core/p-a85535a4.system.entry.js +1 -0
- package/dist/pine-core/p-abc0fe2f.system.entry.js +1 -0
- package/dist/pine-core/p-ac6a5bf0.entry.js +1 -0
- package/dist/pine-core/{p-f7a5062f.entry.js → p-b0526f98.entry.js} +1 -1
- package/dist/pine-core/p-b130b716.entry.js +1 -0
- package/dist/pine-core/{p-09413b07.entry.js → p-b195d345.entry.js} +1 -1
- package/dist/pine-core/{p-e3600c53.entry.js → p-b4923acf.entry.js} +1 -1
- package/dist/pine-core/{p-e410b0d8.system.entry.js → p-b4e440e3.system.entry.js} +1 -1
- package/dist/pine-core/p-b8693a87.entry.js +1 -0
- package/dist/pine-core/{p-e95225cf.entry.js → p-b8ada1eb.entry.js} +1 -1
- package/dist/pine-core/{p-1614c000.system.entry.js → p-bb85bbb5.system.entry.js} +1 -1
- package/dist/pine-core/{p-fec298bb.system.entry.js → p-bbc539bc.system.entry.js} +2 -2
- package/dist/pine-core/{p-f21aea90.system.entry.js → p-bf833976.system.entry.js} +1 -1
- package/dist/pine-core/{p-c5ce6dea.entry.js → p-c23097af.entry.js} +1 -1
- package/dist/pine-core/p-c2dd84bd.system.entry.js +1 -0
- package/dist/pine-core/{p-869ed1c0.system.entry.js → p-c4520b04.system.entry.js} +1 -1
- package/dist/pine-core/{p-3605a5f5.entry.js → p-c4f68dfc.entry.js} +1 -1
- package/dist/pine-core/{p-d9ffb2d9.system.entry.js → p-c6fa6744.system.entry.js} +1 -1
- package/dist/pine-core/p-c7dba815.entry.js +1 -0
- package/dist/pine-core/p-cb6f55ff.entry.js +1 -0
- package/dist/pine-core/p-cfb8fa7c.system.entry.js +1 -0
- package/dist/pine-core/{p-86bc2a0c.entry.js → p-d0d7d1d4.entry.js} +1 -1
- package/dist/pine-core/{p-b109de2e.entry.js → p-d1112c8a.entry.js} +1 -1
- package/dist/pine-core/p-d4e91ad9.system.entry.js +1 -0
- package/dist/pine-core/{p-7b1e3e91.entry.js → p-da018dcb.entry.js} +1 -1
- package/dist/pine-core/{p-c09dab1a.system.entry.js → p-dc27abe6.system.entry.js} +1 -1
- package/dist/pine-core/{p-3eca0b00.entry.js → p-dc343b6d.entry.js} +1 -1
- package/dist/pine-core/p-dde753e7.system.entry.js +1 -0
- package/dist/pine-core/{p-f35596cd.entry.js → p-dea3a582.entry.js} +1 -1
- package/dist/pine-core/p-df29fd41.entry.js +1 -0
- package/dist/pine-core/{p-1f307e59.system.entry.js → p-e4e2eae2.system.entry.js} +1 -1
- package/dist/pine-core/{p-51b8490d.entry.js → p-e8668178.entry.js} +1 -1
- package/dist/pine-core/{p-5dd393cd.system.entry.js → p-eb28d21d.system.entry.js} +1 -1
- package/dist/pine-core/p-eb71a82d.system.entry.js +1 -0
- package/dist/pine-core/{p-8eef80d5.entry.js → p-ed63873c.entry.js} +1 -1
- package/dist/pine-core/{p-12182cab.entry.js → p-f4f6dc36.entry.js} +1 -1
- package/dist/pine-core/{p-be819bff.system.entry.js → p-f67a0ca8.system.entry.js} +1 -1
- package/dist/pine-core/p-fc455968.entry.js +1 -0
- package/dist/pine-core/{p-92ef1608.system.entry.js → p-fe3e2ebd.system.entry.js} +1 -1
- package/dist/pine-core/pine-core.css +1 -1
- package/dist/pine-core/pine-core.esm.js +1 -1
- package/dist/pine-core/pine-core.js +1 -1
- package/dist/types/components/pds-button/pds-button.d.ts +10 -0
- package/dist/types/components/pds-checkbox/pds-checkbox.d.ts +5 -0
- package/dist/types/components/pds-chip/pds-chip.d.ts +12 -0
- package/dist/types/components/pds-combobox/combobox-interface.d.ts +17 -0
- package/dist/types/components/pds-combobox/grouped-options-panel.d.ts +105 -0
- package/dist/types/components/pds-combobox/pds-combobox.d.ts +235 -3
- package/dist/types/components/pds-datepicker/calendar-portal.d.ts +43 -0
- package/dist/types/components/pds-datepicker/date-utils.d.ts +48 -0
- package/dist/types/components/pds-datepicker/datepicker-interface.d.ts +30 -0
- package/dist/types/components/pds-datepicker/pds-datepicker.d.ts +489 -0
- package/dist/types/components/pds-datepicker/presets.d.ts +23 -0
- package/dist/types/components/pds-datepicker/value.d.ts +47 -0
- package/dist/types/components/pds-dropdown-menu/pds-dropdown-menu.d.ts +1 -0
- package/dist/types/components/pds-modal/pds-modal.d.ts +9 -2
- package/dist/types/components/pds-multiselect/multiselect-interface.d.ts +19 -0
- package/dist/types/components/pds-multiselect/pds-multiselect.d.ts +56 -1
- package/dist/types/components/pds-popover/pds-popover.d.ts +1 -0
- package/dist/types/components/pds-switch/pds-switch.d.ts +5 -0
- package/dist/types/components.d.ts +702 -11
- package/dist/types/utils/closest.d.ts +2 -0
- package/dist/types/utils/types.d.ts +2 -1
- package/dist/types/utils/utils.d.ts +25 -2
- package/dist/vscode.html-data.json +497 -7
- package/hydrate/index.js +4059 -377
- package/hydrate/index.mjs +4059 -377
- package/package.json +2 -2
- package/dist/cjs/closest-BZXvuB_Y.js +0 -11
- package/dist/esm/closest-DnUJodVd.js +0 -9
- package/dist/esm-es5/closest-DnUJodVd.js +0 -1
- package/dist/esm-es5/floating-ui.dom-DDtwbwIl.js +0 -1
- package/dist/esm-es5/index-CTLu1TM4.js +0 -2
- package/dist/esm-es5/index-DdPaBxPo.js +0 -1
- package/dist/esm-es5/utils-tJa4KYNO.js +0 -1
- package/dist/pine-core/p--Llh7wKm.system.js +0 -1
- package/dist/pine-core/p-02a47ebb.entry.js +0 -1
- package/dist/pine-core/p-12ec8186.system.entry.js +0 -1
- package/dist/pine-core/p-17dc3f87.entry.js +0 -1
- package/dist/pine-core/p-18a59cdd.system.entry.js +0 -1
- package/dist/pine-core/p-2016c878.entry.js +0 -1
- package/dist/pine-core/p-222ff58e.system.entry.js +0 -1
- package/dist/pine-core/p-324a7ae4.system.entry.js +0 -1
- package/dist/pine-core/p-32f10aa9.entry.js +0 -1
- package/dist/pine-core/p-33c0f3c9.system.entry.js +0 -1
- package/dist/pine-core/p-34e38037.system.entry.js +0 -1
- package/dist/pine-core/p-3873f55b.entry.js +0 -1
- package/dist/pine-core/p-3aec9079.entry.js +0 -1
- package/dist/pine-core/p-3c229a00.system.entry.js +0 -1
- package/dist/pine-core/p-43338f77.system.entry.js +0 -1
- package/dist/pine-core/p-4661259e.system.entry.js +0 -1
- package/dist/pine-core/p-585ad368.system.entry.js +0 -1
- package/dist/pine-core/p-5d585aa8.entry.js +0 -1
- package/dist/pine-core/p-613a64f1.entry.js +0 -1
- package/dist/pine-core/p-630f33d6.system.entry.js +0 -1
- package/dist/pine-core/p-63686e2a.system.entry.js +0 -1
- package/dist/pine-core/p-6f7a7d55.entry.js +0 -1
- package/dist/pine-core/p-6fd863e5.system.entry.js +0 -1
- package/dist/pine-core/p-76286a68.system.entry.js +0 -1
- package/dist/pine-core/p-7691a875.system.entry.js +0 -3
- package/dist/pine-core/p-7e44106e.entry.js +0 -1
- package/dist/pine-core/p-84ade640.entry.js +0 -1
- package/dist/pine-core/p-8be301b6.entry.js +0 -1
- package/dist/pine-core/p-8cc270f0.system.entry.js +0 -1
- package/dist/pine-core/p-8cea0d7a.system.entry.js +0 -1
- package/dist/pine-core/p-957dbb0c.entry.js +0 -1
- package/dist/pine-core/p-BG6uGRQj.system.js +0 -1
- package/dist/pine-core/p-BKsJZTyn.js +0 -1
- package/dist/pine-core/p-BrLs99ML.system.js +0 -1
- package/dist/pine-core/p-CTLu1TM4.js +0 -2
- package/dist/pine-core/p-Cr7-rJtJ.system.js +0 -1
- package/dist/pine-core/p-DAIsVNlL.system.js +0 -2
- package/dist/pine-core/p-DDtwbwIl.js +0 -1
- package/dist/pine-core/p-DdPaBxPo.js +0 -1
- package/dist/pine-core/p-DnUJodVd.js +0 -1
- package/dist/pine-core/p-Dr-FzkdE.system.js +0 -1
- package/dist/pine-core/p-a4a58477.entry.js +0 -1
- package/dist/pine-core/p-a98bf89f.system.entry.js +0 -1
- package/dist/pine-core/p-ac0098fd.entry.js +0 -1
- package/dist/pine-core/p-b32e3174.system.entry.js +0 -1
- package/dist/pine-core/p-be4a6cc2.entry.js +0 -1
- package/dist/pine-core/p-c0b3f2c1.system.entry.js +0 -1
- package/dist/pine-core/p-c8bffb08.system.entry.js +0 -1
- package/dist/pine-core/p-cf24ed1e.system.entry.js +0 -1
- package/dist/pine-core/p-d12ef40d.entry.js +0 -1
- package/dist/pine-core/p-d280741f.entry.js +0 -1
- package/dist/pine-core/p-d415bb01.entry.js +0 -2
- package/dist/pine-core/p-d43db3ba.entry.js +0 -1
- package/dist/pine-core/p-db5703f2.entry.js +0 -1
- package/dist/pine-core/p-dd6d9807.system.entry.js +0 -1
- package/dist/pine-core/p-e0da17aa.system.entry.js +0 -1
- package/dist/pine-core/p-ed297f3e.entry.js +0 -1
- package/dist/pine-core/p-fbf5f315.entry.js +0 -1
- package/dist/pine-core/p-kYMYDfJY.system.js +0 -1
- package/dist/pine-core/p-tJa4KYNO.js +0 -1
|
@@ -0,0 +1,1962 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var index = require('./index-Bl5KnQay.js');
|
|
4
|
+
var floatingUi_dom = require('./floating-ui.dom-Dc8Anyel.js');
|
|
5
|
+
var index$1 = require('./index-D-QQKB0y.js');
|
|
6
|
+
var form = require('./form-MhwfKTuo.js');
|
|
7
|
+
var i18nFormat = require('./i18n-format-BD0PmYzT.js');
|
|
8
|
+
var closest = require('./closest-D0qyYtJ3.js');
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Date-only helpers for pds-datepicker. Dates are `YYYY-MM-DD` strings and all arithmetic runs in
|
|
12
|
+
* UTC, so a date never shifts by a day with the viewer's time zone or a daylight-saving change.
|
|
13
|
+
*/
|
|
14
|
+
const ISO_DATE = /^(\d{4})-(\d{2})-(\d{2})$/;
|
|
15
|
+
/** Parses a `YYYY-MM-DD` string to a UTC `Date`, or `null` when it isn't a real calendar date. */
|
|
16
|
+
function parseIsoDate(value) {
|
|
17
|
+
const match = ISO_DATE.exec((value !== null && value !== void 0 ? value : '').trim());
|
|
18
|
+
if (!match)
|
|
19
|
+
return null;
|
|
20
|
+
const [, year, month, day] = match.map(Number);
|
|
21
|
+
const date = new Date(Date.UTC(year, month - 1, day));
|
|
22
|
+
// Rejects dates like 2026-02-30, which Date.UTC would roll into March.
|
|
23
|
+
return date.getUTCFullYear() === year && date.getUTCMonth() === month - 1 && date.getUTCDate() === day ? date : null;
|
|
24
|
+
}
|
|
25
|
+
function toIsoDate(date) {
|
|
26
|
+
return date.toISOString().slice(0, 10);
|
|
27
|
+
}
|
|
28
|
+
/** Today in the viewer's time zone, as a UTC date. */
|
|
29
|
+
function today() {
|
|
30
|
+
const now = new Date();
|
|
31
|
+
return new Date(Date.UTC(now.getFullYear(), now.getMonth(), now.getDate()));
|
|
32
|
+
}
|
|
33
|
+
function addDays(date, days) {
|
|
34
|
+
return new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate() + days));
|
|
35
|
+
}
|
|
36
|
+
/** Moves by whole months, keeping the day where it exists (Jan 31 + 1 month is Feb 28 or 29). */
|
|
37
|
+
function addMonths(date, months) {
|
|
38
|
+
const first = new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth() + months, 1));
|
|
39
|
+
const lastDay = daysInMonth(first.getUTCFullYear(), first.getUTCMonth());
|
|
40
|
+
return new Date(Date.UTC(first.getUTCFullYear(), first.getUTCMonth(), Math.min(date.getUTCDate(), lastDay)));
|
|
41
|
+
}
|
|
42
|
+
function daysInMonth(year, month) {
|
|
43
|
+
return new Date(Date.UTC(year, month + 1, 0)).getUTCDate();
|
|
44
|
+
}
|
|
45
|
+
function isSameDay(a, b) {
|
|
46
|
+
return !!a && !!b && a.getTime() === b.getTime();
|
|
47
|
+
}
|
|
48
|
+
/** Clamps a date into the `[min, max]` range; either bound may be missing. */
|
|
49
|
+
function clampDate(date, min, max) {
|
|
50
|
+
if (min && date < min)
|
|
51
|
+
return min;
|
|
52
|
+
if (max && date > max)
|
|
53
|
+
return max;
|
|
54
|
+
return date;
|
|
55
|
+
}
|
|
56
|
+
function isOutOfRange(date, min, max) {
|
|
57
|
+
return (!!min && date < min) || (!!max && date > max);
|
|
58
|
+
}
|
|
59
|
+
/** The column (0–6) a date falls in, for a week starting on `firstDayOfWeek` (0 = Sunday). */
|
|
60
|
+
function weekdayColumn(date, firstDayOfWeek) {
|
|
61
|
+
return (date.getUTCDay() - firstDayOfWeek + 7) % 7;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* The month's weeks, each seven cells long. Days from the neighbouring months are `null`, so the
|
|
65
|
+
* grid only offers dates in the month on view.
|
|
66
|
+
*/
|
|
67
|
+
function monthWeeks(year, month, firstDayOfWeek) {
|
|
68
|
+
const first = new Date(Date.UTC(year, month, 1));
|
|
69
|
+
const cells = Array(weekdayColumn(first, firstDayOfWeek)).fill(null);
|
|
70
|
+
for (let day = 1; day <= daysInMonth(year, month); day++) {
|
|
71
|
+
cells.push(new Date(Date.UTC(year, month, day)));
|
|
72
|
+
}
|
|
73
|
+
while (cells.length % 7 !== 0)
|
|
74
|
+
cells.push(null);
|
|
75
|
+
const weeks = [];
|
|
76
|
+
for (let i = 0; i < cells.length; i += 7)
|
|
77
|
+
weeks.push(cells.slice(i, i + 7));
|
|
78
|
+
return weeks;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* The locale's first day of the week (0 = Sunday), from `Intl.Locale` week info where the browser
|
|
82
|
+
* has it. Falls back to Sunday.
|
|
83
|
+
*/
|
|
84
|
+
function localeFirstDayOfWeek(locale) {
|
|
85
|
+
var _a, _b, _c;
|
|
86
|
+
// Intl.Locale is newer than the library's ES2017 typings.
|
|
87
|
+
const IntlLocale = Intl.Locale;
|
|
88
|
+
if (!IntlLocale)
|
|
89
|
+
return 0;
|
|
90
|
+
try {
|
|
91
|
+
const intlLocale = new IntlLocale(locale);
|
|
92
|
+
const firstDay = (_b = (_a = intlLocale.getWeekInfo) === null || _a === void 0 ? void 0 : _a.call(intlLocale).firstDay) !== null && _b !== void 0 ? _b : (_c = intlLocale.weekInfo) === null || _c === void 0 ? void 0 : _c.firstDay;
|
|
93
|
+
// Intl numbers the days 1 (Monday) to 7 (Sunday).
|
|
94
|
+
return typeof firstDay === 'number' ? firstDay % 7 : 0;
|
|
95
|
+
}
|
|
96
|
+
catch (_d) {
|
|
97
|
+
return 0;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Weekday names, starting from `firstDayOfWeek`: a short form for the column heading and the long
|
|
102
|
+
* form for assistive tech. Latin-script names shorten to two letters (Su Mo, lu ma, Mo Di), as the
|
|
103
|
+
* admin date picker does, while the seven stay distinct; other scripts keep the locale's short form,
|
|
104
|
+
* which is already compact.
|
|
105
|
+
*/
|
|
106
|
+
function weekdayNames(locale, firstDayOfWeek) {
|
|
107
|
+
const shortFormat = dateFormatter(locale, { weekday: 'short' });
|
|
108
|
+
const longFormat = dateFormatter(locale, { weekday: 'long' });
|
|
109
|
+
// 2023-01-01 was a Sunday.
|
|
110
|
+
const days = Array.from({ length: 7 }, (_, i) => new Date(Date.UTC(2023, 0, 1 + ((firstDayOfWeek + i) % 7))));
|
|
111
|
+
const shorts = days.map((date) => shortFormat.format(date).replace(/\.$/, ''));
|
|
112
|
+
const latin = shorts.every((name) => /^[A-Za-z\u00C0-\u024F]+$/.test(name));
|
|
113
|
+
const twoLetters = shorts.map((name) => name.slice(0, 2));
|
|
114
|
+
const useTwoLetters = latin && new Set(twoLetters.map((name) => name.toLowerCase())).size === 7;
|
|
115
|
+
return days.map((date, i) => ({
|
|
116
|
+
short: useTwoLetters ? twoLetters[i] : shortFormat.format(date),
|
|
117
|
+
long: longFormat.format(date),
|
|
118
|
+
}));
|
|
119
|
+
}
|
|
120
|
+
/** The locale if the browser supports it, otherwise `en-US`, so a bad tag never throws while rendering. */
|
|
121
|
+
function supportedLocale(locale) {
|
|
122
|
+
var _a;
|
|
123
|
+
try {
|
|
124
|
+
return (_a = Intl.DateTimeFormat.supportedLocalesOf(locale)[0]) !== null && _a !== void 0 ? _a : 'en-US';
|
|
125
|
+
}
|
|
126
|
+
catch (_b) {
|
|
127
|
+
return 'en-US';
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* A formatter for the UTC dates these helpers return. Build it once per render, not per day.
|
|
132
|
+
* Always on the Gregorian calendar the grid is laid out in, while keeping the locale's language and
|
|
133
|
+
* digits: some locales default to another calendar (Persian for `fa`, Buddhist for `th`).
|
|
134
|
+
*/
|
|
135
|
+
function dateFormatter(locale, options) {
|
|
136
|
+
// `calendar` is newer than the library's ES2017 typings.
|
|
137
|
+
return new Intl.DateTimeFormat(locale, Object.assign(Object.assign({}, options), { timeZone: 'UTC', calendar: 'gregory' }));
|
|
138
|
+
}
|
|
139
|
+
function formatDate(date, locale, options) {
|
|
140
|
+
return dateFormatter(locale, options).format(date);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Where the open calendar lives: a `div` in `document.body`, or in the nearest `<dialog>` so it
|
|
145
|
+
* shares a modal's top layer, outside any `overflow: hidden` or transformed ancestor that would
|
|
146
|
+
* clip or misplace a fixed-position popup. The portal has its own shadow root carrying the
|
|
147
|
+
* datepicker's styles, so the calendar looks the same there without any global CSS.
|
|
148
|
+
*/
|
|
149
|
+
class CalendarPortal {
|
|
150
|
+
constructor(datepicker) {
|
|
151
|
+
this.datepicker = datepicker;
|
|
152
|
+
/** The custom properties copyContext set on the host, cleared before it copies again. */
|
|
153
|
+
this.copiedProperties = [];
|
|
154
|
+
this.host = document.createElement('div');
|
|
155
|
+
this.host.className = CalendarPortal.className;
|
|
156
|
+
// So an overlay can tell whether a click in this calendar belongs to a datepicker inside it.
|
|
157
|
+
this.host.pdsDatepicker = datepicker;
|
|
158
|
+
// The shared stylesheet's `:host` rules match this div too; keep it out of layout, so it never
|
|
159
|
+
// adds a line to the page, while it still passes `dir`, `lang` and the theme down.
|
|
160
|
+
this.host.style.display = 'contents';
|
|
161
|
+
this.root = this.host.attachShadow({ mode: 'open' });
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Moves the calendar into the portal, mounting the portal first. Context is copied when the portal
|
|
165
|
+
* is attached or the locale changes, not on every render while it's open (in range mode, every
|
|
166
|
+
* hovered day). A theme set on an ancestor while the calendar is open applies when it next opens.
|
|
167
|
+
*/
|
|
168
|
+
mount(calendar, locale) {
|
|
169
|
+
if (!this.host.isConnected) {
|
|
170
|
+
findPortalTarget(this.datepicker).appendChild(this.host);
|
|
171
|
+
this.copyContext(locale);
|
|
172
|
+
}
|
|
173
|
+
else if (locale !== this.locale) {
|
|
174
|
+
this.copyContext(locale);
|
|
175
|
+
}
|
|
176
|
+
if (calendar.parentNode !== this.root)
|
|
177
|
+
this.root.appendChild(calendar);
|
|
178
|
+
}
|
|
179
|
+
remove() {
|
|
180
|
+
this.host.remove();
|
|
181
|
+
}
|
|
182
|
+
/** Whether a node is the portal or inside it, as a focus or pointer target. */
|
|
183
|
+
contains(node) {
|
|
184
|
+
return !!node && (node === this.host || this.root.contains(node));
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Carries over what the calendar would otherwise inherit from the datepicker: its styles, the
|
|
188
|
+
* reading direction and language, a theme set on an ancestor, and size overrides.
|
|
189
|
+
*/
|
|
190
|
+
copyContext(locale) {
|
|
191
|
+
var _a, _b;
|
|
192
|
+
this.locale = locale;
|
|
193
|
+
const source = this.datepicker.shadowRoot;
|
|
194
|
+
if (source) {
|
|
195
|
+
if ((_a = source.adoptedStyleSheets) === null || _a === void 0 ? void 0 : _a.length) {
|
|
196
|
+
this.root.adoptedStyleSheets = source.adoptedStyleSheets;
|
|
197
|
+
}
|
|
198
|
+
else if (!this.root.querySelector('style')) {
|
|
199
|
+
source.querySelectorAll('style').forEach((style) => this.root.appendChild(style.cloneNode(true)));
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
const computed = getComputedStyle(this.datepicker);
|
|
203
|
+
this.host.dir = computed.direction || 'ltr';
|
|
204
|
+
// So assistive tech reads the month and day names in the calendar's own language.
|
|
205
|
+
this.host.lang = locale;
|
|
206
|
+
this.copyCustomProperties(computed);
|
|
207
|
+
// Across shadow boundaries: a theme set above an enclosing component still applies.
|
|
208
|
+
const theme = (_b = (closest.closest('[data-theme]', this.datepicker) || null)) === null || _b === void 0 ? void 0 : _b.getAttribute('data-theme');
|
|
209
|
+
if (theme) {
|
|
210
|
+
this.host.setAttribute('data-theme', theme);
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
this.host.removeAttribute('data-theme');
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Custom properties set on an ancestor of the datepicker that isn't an ancestor of the portal,
|
|
218
|
+
* as a token override on a card or panel, or the datepicker's own size properties: each one whose
|
|
219
|
+
* value at the datepicker differs from where the portal mounts is set on the portal host.
|
|
220
|
+
*/
|
|
221
|
+
copyCustomProperties(computed) {
|
|
222
|
+
for (const property of this.copiedProperties)
|
|
223
|
+
this.host.style.removeProperty(property);
|
|
224
|
+
this.copiedProperties = [];
|
|
225
|
+
const parent = this.host.parentElement;
|
|
226
|
+
const outside = parent ? getComputedStyle(parent) : null;
|
|
227
|
+
for (let i = 0; i < computed.length; i++) {
|
|
228
|
+
const property = computed[i];
|
|
229
|
+
if (!property.startsWith('--'))
|
|
230
|
+
continue;
|
|
231
|
+
const value = computed.getPropertyValue(property);
|
|
232
|
+
if (outside && value === outside.getPropertyValue(property))
|
|
233
|
+
continue;
|
|
234
|
+
this.host.style.setProperty(property, value.trim());
|
|
235
|
+
this.copiedProperties.push(property);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
CalendarPortal.className = 'pds-datepicker-calendar-portal';
|
|
240
|
+
/**
|
|
241
|
+
* The native `<dialog>` or open `popover` (as pds-filter's) the datepicker sits in, so the
|
|
242
|
+
* calendar shares its top layer and a click in it isn't outside it; otherwise `document.body`.
|
|
243
|
+
* `closest()` stops at shadow boundaries and misses one that only wraps slotted content, so this
|
|
244
|
+
* walks assigned slots and shadow hosts, as pds-combobox does.
|
|
245
|
+
*/
|
|
246
|
+
function findPortalTarget(from) {
|
|
247
|
+
const seen = new Set();
|
|
248
|
+
let current = from;
|
|
249
|
+
while (current && !seen.has(current)) {
|
|
250
|
+
seen.add(current);
|
|
251
|
+
if (current.nodeType === Node.ELEMENT_NODE && isTopLayerHost(current)) {
|
|
252
|
+
return current;
|
|
253
|
+
}
|
|
254
|
+
const assigned = current.nodeType === Node.ELEMENT_NODE ? current.assignedSlot : null;
|
|
255
|
+
if (assigned) {
|
|
256
|
+
current = assigned;
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
259
|
+
const parent = current.parentNode;
|
|
260
|
+
current = parent instanceof ShadowRoot ? parent.host : parent;
|
|
261
|
+
}
|
|
262
|
+
return document.body;
|
|
263
|
+
}
|
|
264
|
+
function isTopLayerHost(el) {
|
|
265
|
+
// Only an open dialog: inside a closed one, the calendar would be hidden with it.
|
|
266
|
+
if (el.localName === 'dialog')
|
|
267
|
+
return el.open;
|
|
268
|
+
if (!el.hasAttribute('popover'))
|
|
269
|
+
return false;
|
|
270
|
+
try {
|
|
271
|
+
return el.matches(':popover-open');
|
|
272
|
+
}
|
|
273
|
+
catch (_a) {
|
|
274
|
+
// A browser without the Popover API: no top layer to share.
|
|
275
|
+
return false;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const DEFAULT_LABELS = {
|
|
280
|
+
'today': 'Today',
|
|
281
|
+
'yesterday': 'Yesterday',
|
|
282
|
+
'last-7-days': 'Last 7 days',
|
|
283
|
+
'last-30-days': 'Last 30 days',
|
|
284
|
+
'last-90-days': 'Last 90 days',
|
|
285
|
+
'this-month': 'This month',
|
|
286
|
+
'last-month': 'Last month',
|
|
287
|
+
'this-quarter': 'This quarter',
|
|
288
|
+
'last-quarter': 'Last quarter',
|
|
289
|
+
'this-year': 'This year',
|
|
290
|
+
'last-year': 'Last year',
|
|
291
|
+
'year-to-date': 'Year to date',
|
|
292
|
+
'last-12-months': 'Last 12 months',
|
|
293
|
+
};
|
|
294
|
+
const utc = (year, month, day) => new Date(Date.UTC(year, month, day));
|
|
295
|
+
/**
|
|
296
|
+
* The range a built-in preset covers on the given day, or `null` for an unknown key. "Last N days"
|
|
297
|
+
* is N days ending today; "this" and "last" periods are whole periods; "to date" ends today.
|
|
298
|
+
*/
|
|
299
|
+
function builtInPresetRange(key, now) {
|
|
300
|
+
const year = now.getUTCFullYear();
|
|
301
|
+
const month = now.getUTCMonth();
|
|
302
|
+
const quarterStart = month - (month % 3);
|
|
303
|
+
switch (key) {
|
|
304
|
+
case 'today':
|
|
305
|
+
return [now, now];
|
|
306
|
+
case 'yesterday':
|
|
307
|
+
return [addDays(now, -1), addDays(now, -1)];
|
|
308
|
+
case 'last-7-days':
|
|
309
|
+
return [addDays(now, -6), now];
|
|
310
|
+
case 'last-30-days':
|
|
311
|
+
return [addDays(now, -29), now];
|
|
312
|
+
case 'last-90-days':
|
|
313
|
+
return [addDays(now, -89), now];
|
|
314
|
+
case 'this-month':
|
|
315
|
+
return [utc(year, month, 1), utc(year, month + 1, 0)];
|
|
316
|
+
case 'last-month':
|
|
317
|
+
return [utc(year, month - 1, 1), utc(year, month, 0)];
|
|
318
|
+
case 'this-quarter':
|
|
319
|
+
return [utc(year, quarterStart, 1), utc(year, quarterStart + 3, 0)];
|
|
320
|
+
case 'last-quarter':
|
|
321
|
+
return [utc(year, quarterStart - 3, 1), utc(year, quarterStart, 0)];
|
|
322
|
+
case 'this-year':
|
|
323
|
+
return [utc(year, 0, 1), utc(year, 11, 31)];
|
|
324
|
+
case 'last-year':
|
|
325
|
+
return [utc(year - 1, 0, 1), utc(year - 1, 11, 31)];
|
|
326
|
+
case 'year-to-date':
|
|
327
|
+
return [utc(year, 0, 1), now];
|
|
328
|
+
case 'last-12-months':
|
|
329
|
+
return [addMonths(now, -12), now];
|
|
330
|
+
default:
|
|
331
|
+
return null;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* Normalizes the `presets` prop: a comma-separated list of built-in keys, or an array of presets
|
|
336
|
+
* that may relabel a built-in (for translation) or give their own `start` and `end`. Entries that
|
|
337
|
+
* resolve to no range are dropped.
|
|
338
|
+
*/
|
|
339
|
+
function resolvePresets(presets, now) {
|
|
340
|
+
const entries = typeof presets === 'string'
|
|
341
|
+
? presets
|
|
342
|
+
.split(',')
|
|
343
|
+
.map((key) => key.trim())
|
|
344
|
+
.filter(Boolean)
|
|
345
|
+
.map((key) => ({ key }))
|
|
346
|
+
: Array.isArray(presets)
|
|
347
|
+
? presets
|
|
348
|
+
: [];
|
|
349
|
+
const resolved = [];
|
|
350
|
+
for (const preset of entries) {
|
|
351
|
+
// Presets often come from parsed JSON, so check types at runtime: a non-string label would
|
|
352
|
+
// otherwise reach the render as an object, which Stencil can turn into nodes.
|
|
353
|
+
if (!preset || typeof preset.key !== 'string')
|
|
354
|
+
continue;
|
|
355
|
+
const start = typeof preset.start === 'string' ? parseIsoDate(preset.start) : null;
|
|
356
|
+
const end = typeof preset.end === 'string' ? parseIsoDate(preset.end) : null;
|
|
357
|
+
const range = start && end && start <= end ? [start, end] : builtInPresetRange(preset.key, now);
|
|
358
|
+
const label = typeof preset.label === 'string' ? preset.label : DEFAULT_LABELS[preset.key];
|
|
359
|
+
if (range && label)
|
|
360
|
+
resolved.push({ key: preset.key, label, start: range[0], end: range[1] });
|
|
361
|
+
}
|
|
362
|
+
return resolved;
|
|
363
|
+
}
|
|
364
|
+
/** The first preset covering exactly this range. */
|
|
365
|
+
function matchPreset(presets, start, end) {
|
|
366
|
+
if (!start || !end)
|
|
367
|
+
return undefined;
|
|
368
|
+
return presets.find((preset) => preset.start.getTime() === start.getTime() && preset.end.getTime() === end.getTime());
|
|
369
|
+
}
|
|
370
|
+
/** Whether a preset's whole range can be picked within `min` and `max`. */
|
|
371
|
+
function presetInRange(preset, min, max) {
|
|
372
|
+
return !isOutOfRange(preset.start, min, max) && !isOutOfRange(preset.end, min, max);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
const DATE_TIME = /^(\d{4}-\d{2}-\d{2})(?:[T ](\d{2}):(\d{2}))?$/;
|
|
376
|
+
/** Parses `YYYY-MM-DD`, `YYYY-MM-DDTHH:mm` or `YYYY-MM-DD HH:mm`. */
|
|
377
|
+
function parseDateTime(text) {
|
|
378
|
+
const match = DATE_TIME.exec((text !== null && text !== void 0 ? text : '').trim());
|
|
379
|
+
if (!match)
|
|
380
|
+
return null;
|
|
381
|
+
const date = parseIsoDate(match[1]);
|
|
382
|
+
if (!date)
|
|
383
|
+
return null;
|
|
384
|
+
if (match[2] === undefined)
|
|
385
|
+
return { date, time: null };
|
|
386
|
+
const hours = Number(match[2]);
|
|
387
|
+
const minutes = Number(match[3]);
|
|
388
|
+
return hours < 24 && minutes < 60 ? { date, time: `${match[2]}:${match[3]}` } : null;
|
|
389
|
+
}
|
|
390
|
+
const DEFAULT_FORMAT = 'YYYY-MM-DD';
|
|
391
|
+
/**
|
|
392
|
+
* A format's parts: `[text]` is literal text, and the tokens are `YYYY`, `MMMM` (month name),
|
|
393
|
+
* `MMM` (short month name), `MM`/`M` (month), `DD`/`D` (day), `HH`/`H` (24-hour), `hh`/`h`
|
|
394
|
+
* (12-hour), `mm` (minutes) and `A`/`a` (AM/PM). Longest tokens first, so `MM` isn't read as two
|
|
395
|
+
* `M`s. Anything else is copied as is.
|
|
396
|
+
*/
|
|
397
|
+
const FORMAT_PARTS = /\[([^\]]*)\]|YYYY|MMMM|MMM|MM|M|DD|D|HH|H|hh|h|mm|A|a/g;
|
|
398
|
+
const HOUR_TOKEN = /HH|H|hh|h/;
|
|
399
|
+
const TWELVE_HOUR_TOKEN = /hh|h/;
|
|
400
|
+
const PERIOD_TOKEN = /A|a/;
|
|
401
|
+
/** Without its `[literal]` parts, to test which tokens a format uses. */
|
|
402
|
+
const tokensOnly = (format) => format.replace(/\[[^\]]*\]/g, '');
|
|
403
|
+
/**
|
|
404
|
+
* The field's format: the date format, with a time added when the time picker is on and it has
|
|
405
|
+
* none, on the same clock as the time picker. A 12-hour format gets AM/PM if it has none, as
|
|
406
|
+
* `1:30` alone can't say which.
|
|
407
|
+
*/
|
|
408
|
+
function fieldFormat(format, timePicker, hour12 = false) {
|
|
409
|
+
const date = (format === null || format === void 0 ? void 0 : format.trim()) || DEFAULT_FORMAT;
|
|
410
|
+
const tokens = tokensOnly(date);
|
|
411
|
+
if (TWELVE_HOUR_TOKEN.test(tokens) && !PERIOD_TOKEN.test(tokens))
|
|
412
|
+
return `${date} A`;
|
|
413
|
+
if (!timePicker || HOUR_TOKEN.test(tokens))
|
|
414
|
+
return date;
|
|
415
|
+
return `${date} ${hour12 ? 'h:mm A' : 'HH:mm'}`;
|
|
416
|
+
}
|
|
417
|
+
const pad = (n, width = 2) => String(n).padStart(width, '0');
|
|
418
|
+
const wordsCache = new Map();
|
|
419
|
+
/** Month names and AM/PM words in the locale, on the Gregorian calendar. */
|
|
420
|
+
function localeWords(locale) {
|
|
421
|
+
let words = wordsCache.get(locale);
|
|
422
|
+
if (!words) {
|
|
423
|
+
const month = (style) => {
|
|
424
|
+
const format = new Intl.DateTimeFormat(locale, {
|
|
425
|
+
month: style,
|
|
426
|
+
timeZone: 'UTC',
|
|
427
|
+
calendar: 'gregory',
|
|
428
|
+
});
|
|
429
|
+
return Array.from({ length: 12 }, (_, m) => format.format(new Date(Date.UTC(2023, m, 1))));
|
|
430
|
+
};
|
|
431
|
+
const clock = clockFor(locale);
|
|
432
|
+
words = { months: month('long'), shortMonths: month('short'), am: clock.am, pm: clock.pm };
|
|
433
|
+
wordsCache.set(locale, words);
|
|
434
|
+
}
|
|
435
|
+
return words;
|
|
436
|
+
}
|
|
437
|
+
/** Writes a date in a format of tokens (see `FORMAT_PARTS`), with month names and AM/PM in the locale. */
|
|
438
|
+
function formatWith(value, format, locale = 'en-US') {
|
|
439
|
+
var _a;
|
|
440
|
+
const words = localeWords(locale);
|
|
441
|
+
const [hours, minutes] = splitTime((_a = value.time) !== null && _a !== void 0 ? _a : '00:00');
|
|
442
|
+
const hour12 = hours % 12 || 12;
|
|
443
|
+
const month = value.date.getUTCMonth();
|
|
444
|
+
const period = hours < 12 ? words.am : words.pm;
|
|
445
|
+
const parts = {
|
|
446
|
+
YYYY: pad(value.date.getUTCFullYear(), 4),
|
|
447
|
+
MMMM: words.months[month],
|
|
448
|
+
MMM: words.shortMonths[month],
|
|
449
|
+
MM: pad(month + 1),
|
|
450
|
+
M: String(month + 1),
|
|
451
|
+
DD: pad(value.date.getUTCDate()),
|
|
452
|
+
D: String(value.date.getUTCDate()),
|
|
453
|
+
HH: pad(hours),
|
|
454
|
+
H: String(hours),
|
|
455
|
+
hh: pad(hour12),
|
|
456
|
+
h: String(hour12),
|
|
457
|
+
mm: pad(minutes),
|
|
458
|
+
A: period,
|
|
459
|
+
a: period.toLowerCase(),
|
|
460
|
+
};
|
|
461
|
+
return format.replace(FORMAT_PARTS, (token, literal) => literal !== null && literal !== void 0 ? literal : parts[token]);
|
|
462
|
+
}
|
|
463
|
+
const escapeRegExp = (text) => text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
464
|
+
/** Escapes a format's literal text for a pattern, letting its spaces match any run of spaces. */
|
|
465
|
+
const literalPattern = (literal) => escapeRegExp(literal).replace(/\s+/g, '\\s*');
|
|
466
|
+
const alternatives = (words) => `(${words.map(escapeRegExp).join('|')})`;
|
|
467
|
+
/**
|
|
468
|
+
* Reads typed text in a format of tokens, as written by `formatWith`. Leading zeros are optional,
|
|
469
|
+
* spaces are loose, and month names and AM/PM are matched without regard to case, in the locale or
|
|
470
|
+
* in English. Returns `null` for anything that isn't a real date and time in that format.
|
|
471
|
+
*/
|
|
472
|
+
function parseWith(text, format, locale = 'en-US') {
|
|
473
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
|
|
474
|
+
const words = localeWords(locale);
|
|
475
|
+
const english = localeWords('en-US');
|
|
476
|
+
const periods = [words.am, words.pm, english.am, english.pm];
|
|
477
|
+
const patterns = {
|
|
478
|
+
YYYY: '(\\d{4})',
|
|
479
|
+
MMMM: alternatives([...words.months, ...english.months]),
|
|
480
|
+
MMM: alternatives([...words.shortMonths, ...english.shortMonths]),
|
|
481
|
+
MM: '(\\d{1,2})',
|
|
482
|
+
M: '(\\d{1,2})',
|
|
483
|
+
DD: '(\\d{1,2})',
|
|
484
|
+
D: '(\\d{1,2})',
|
|
485
|
+
HH: '(\\d{1,2})',
|
|
486
|
+
H: '(\\d{1,2})',
|
|
487
|
+
hh: '(\\d{1,2})',
|
|
488
|
+
h: '(\\d{1,2})',
|
|
489
|
+
mm: '(\\d{2})',
|
|
490
|
+
A: alternatives(periods),
|
|
491
|
+
a: alternatives(periods),
|
|
492
|
+
};
|
|
493
|
+
const tokens = [];
|
|
494
|
+
const build = () => {
|
|
495
|
+
tokens.length = 0;
|
|
496
|
+
let pattern = '';
|
|
497
|
+
let last = 0;
|
|
498
|
+
format.replace(FORMAT_PARTS, (token, literal, offset) => {
|
|
499
|
+
pattern += literalPattern(format.slice(last, offset));
|
|
500
|
+
if (literal !== undefined) {
|
|
501
|
+
pattern += literalPattern(literal);
|
|
502
|
+
}
|
|
503
|
+
else {
|
|
504
|
+
pattern += patterns[token];
|
|
505
|
+
tokens.push(token);
|
|
506
|
+
}
|
|
507
|
+
last = offset + token.length;
|
|
508
|
+
return token;
|
|
509
|
+
});
|
|
510
|
+
return new RegExp(`^\\s*${pattern}${literalPattern(format.slice(last))}\\s*$`, 'i');
|
|
511
|
+
};
|
|
512
|
+
// Two-digit minutes first, so a format with no separator, as `HHmm`, still reads `930` as 9:30;
|
|
513
|
+
// then one digit, as leading zeros are optional.
|
|
514
|
+
let match = build().exec(text !== null && text !== void 0 ? text : '');
|
|
515
|
+
if (!match) {
|
|
516
|
+
patterns.mm = '(\\d{1,2})';
|
|
517
|
+
match = build().exec(text !== null && text !== void 0 ? text : '');
|
|
518
|
+
}
|
|
519
|
+
if (!match)
|
|
520
|
+
return null;
|
|
521
|
+
const found = match;
|
|
522
|
+
const read = {};
|
|
523
|
+
tokens.forEach((token, i) => (read[token] = found[i + 1]));
|
|
524
|
+
const monthIndex = (name, long) => {
|
|
525
|
+
if (name === undefined)
|
|
526
|
+
return undefined;
|
|
527
|
+
const lists = long ? [words.months, english.months] : [words.shortMonths, english.shortMonths];
|
|
528
|
+
for (const list of lists) {
|
|
529
|
+
const index = list.findIndex((candidate) => candidate.toLowerCase() === name.toLowerCase());
|
|
530
|
+
if (index >= 0)
|
|
531
|
+
return index + 1;
|
|
532
|
+
}
|
|
533
|
+
return undefined;
|
|
534
|
+
};
|
|
535
|
+
const year = Number(read.YYYY);
|
|
536
|
+
const month = (_b = (_a = monthIndex(read.MMMM, true)) !== null && _a !== void 0 ? _a : monthIndex(read.MMM, false)) !== null && _b !== void 0 ? _b : Number((_c = read.MM) !== null && _c !== void 0 ? _c : read.M);
|
|
537
|
+
const day = Number((_d = read.DD) !== null && _d !== void 0 ? _d : read.D);
|
|
538
|
+
if (!read.YYYY || !month || !day)
|
|
539
|
+
return null;
|
|
540
|
+
const date = parseIsoDate(`${pad(year, 4)}-${pad(month)}-${pad(day)}`);
|
|
541
|
+
if (!date)
|
|
542
|
+
return null;
|
|
543
|
+
const hourText = (_g = (_f = (_e = read.HH) !== null && _e !== void 0 ? _e : read.H) !== null && _f !== void 0 ? _f : read.hh) !== null && _g !== void 0 ? _g : read.h;
|
|
544
|
+
if (hourText === undefined)
|
|
545
|
+
return { date, time: null };
|
|
546
|
+
let hours = Number(hourText);
|
|
547
|
+
const minutes = Number((_h = read.mm) !== null && _h !== void 0 ? _h : 0);
|
|
548
|
+
const period = (_k = ((_j = read.A) !== null && _j !== void 0 ? _j : read.a)) === null || _k === void 0 ? void 0 : _k.toLowerCase();
|
|
549
|
+
if (read.hh !== undefined || read.h !== undefined) {
|
|
550
|
+
if (hours < 1 || hours > 12)
|
|
551
|
+
return null;
|
|
552
|
+
const pm = period === words.pm.toLowerCase() || period === english.pm.toLowerCase();
|
|
553
|
+
hours = (hours % 12) + (pm ? 12 : 0);
|
|
554
|
+
}
|
|
555
|
+
return hours < 24 && minutes < 60 ? { date, time: joinTime(hours, minutes) } : null;
|
|
556
|
+
}
|
|
557
|
+
/** Reads one typed date in the format, falling back to ISO, which is always accepted. */
|
|
558
|
+
function parseFieldText(text, format = DEFAULT_FORMAT, locale = 'en-US') {
|
|
559
|
+
var _a;
|
|
560
|
+
return (_a = parseWith(text, format, locale)) !== null && _a !== void 0 ? _a : parseDateTime(text);
|
|
561
|
+
}
|
|
562
|
+
/**
|
|
563
|
+
* Parses a typed range: two dates (with times, optionally) joined by a dash or `to` with spaces
|
|
564
|
+
* around it, as `2026-09-13 – 2026-10-13`, or by `/` when the format doesn't use slashes. The
|
|
565
|
+
* split is tried at each separator until both halves read as dates, so a format that itself has a
|
|
566
|
+
* spaced dash, as `D - M - YYYY`, still works.
|
|
567
|
+
*/
|
|
568
|
+
function parseRangeText(text, format = DEFAULT_FORMAT, locale = 'en-US') {
|
|
569
|
+
const trimmed = (text !== null && text !== void 0 ? text : '').trim();
|
|
570
|
+
const separators = new RegExp(`${escapeRegExp(RANGE_SEPARATOR)}|\\s+(?:–|—|-|to)\\s+${format.includes('/') ? '' : '|\\s*/\\s*'}`, 'gi');
|
|
571
|
+
let found;
|
|
572
|
+
while ((found = separators.exec(trimmed)) !== null) {
|
|
573
|
+
const start = parseFieldText(trimmed.slice(0, found.index), format, locale);
|
|
574
|
+
const end = parseFieldText(trimmed.slice(found.index + found[0].length), format, locale);
|
|
575
|
+
if (start && end)
|
|
576
|
+
return compareDateTime(start, end) <= 0 ? [start, end] : null;
|
|
577
|
+
}
|
|
578
|
+
return null;
|
|
579
|
+
}
|
|
580
|
+
function compareDateTime(a, b) {
|
|
581
|
+
var _a, _b;
|
|
582
|
+
const byDay = a.date.getTime() - b.date.getTime();
|
|
583
|
+
if (byDay !== 0)
|
|
584
|
+
return byDay;
|
|
585
|
+
return ((_a = a.time) !== null && _a !== void 0 ? _a : '00:00').localeCompare((_b = b.time) !== null && _b !== void 0 ? _b : '00:00');
|
|
586
|
+
}
|
|
587
|
+
/** The value form: `YYYY-MM-DD`, or `YYYY-MM-DDTHH:mm` with a time. */
|
|
588
|
+
function toValueString(value) {
|
|
589
|
+
return value.time ? `${toIsoDate(value.date)}T${value.time}` : toIsoDate(value.date);
|
|
590
|
+
}
|
|
591
|
+
/** The field form in a format, or by default `YYYY-MM-DD`, with ` HH:mm` when it has a time. */
|
|
592
|
+
function toFieldText(value, format, locale = 'en-US') {
|
|
593
|
+
return formatWith(value, format !== null && format !== void 0 ? format : fieldFormat(DEFAULT_FORMAT, !!value.time), locale);
|
|
594
|
+
}
|
|
595
|
+
const RANGE_SEPARATOR = ' – ';
|
|
596
|
+
/** Hour and minute numbers from `HH:mm`. */
|
|
597
|
+
function splitTime(time) {
|
|
598
|
+
const [hours, minutes] = time.split(':').map(Number);
|
|
599
|
+
return [hours || 0, minutes || 0];
|
|
600
|
+
}
|
|
601
|
+
function joinTime(hours, minutes) {
|
|
602
|
+
return `${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}`;
|
|
603
|
+
}
|
|
604
|
+
/** Whether the locale writes times on a 12-hour clock, and its AM and PM words. */
|
|
605
|
+
function clockFor(locale) {
|
|
606
|
+
var _a, _b;
|
|
607
|
+
const format = new Intl.DateTimeFormat(locale, { hour: 'numeric', timeZone: 'UTC' });
|
|
608
|
+
const hour12 = !!format.resolvedOptions().hour12;
|
|
609
|
+
const period = (hour) => { var _a; return (_a = format.formatToParts(new Date(Date.UTC(2023, 0, 1, hour))).find((part) => part.type === 'dayPeriod')) === null || _a === void 0 ? void 0 : _a.value; };
|
|
610
|
+
return { hour12, am: (_a = period(1)) !== null && _a !== void 0 ? _a : 'AM', pm: (_b = period(13)) !== null && _b !== void 0 ? _b : 'PM' };
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
const pdsDatepickerCss = () => `:host{--pds-datepicker-day-size:var(--pine-dimension-500);--pds-datepicker-presets-width:200px;display:inline-block}:host([full-width]){display:block}pds-input{display:block}:host(.pds-datepicker--time) pds-input{min-width:220px}:host(.pds-datepicker--range) pds-input{min-width:260px}:host(.pds-datepicker--range.pds-datepicker--time) pds-input{min-width:min(380px, 100%)}.pds-datepicker__trigger,.pds-datepicker__clear{-ms-flex-align:center;align-items:center;background:transparent;border:0;border-radius:var(--pine-border-radius-sm);color:var(--pine-color-text-secondary);cursor:pointer;display:-ms-inline-flexbox;display:inline-flex;-ms-flex-pack:center;justify-content:center;padding:var(--pine-dimension-2xs)}.pds-datepicker__trigger:hover:not(:disabled),.pds-datepicker__clear:hover:not(:disabled){background:var(--pine-color-background-muted);color:var(--pine-color-text-strong)}.pds-datepicker__trigger:focus-visible,.pds-datepicker__clear:focus-visible{outline:var(--pine-outline-focus)}.pds-datepicker__trigger:disabled,.pds-datepicker__clear:disabled{color:var(--pine-color-text-disabled);cursor:not-allowed}.pds-datepicker__clear{-webkit-margin-end:var(--pine-dimension-2xs);margin-inline-end:var(--pine-dimension-2xs)}.pds-datepicker__nav{-ms-flex-align:center;align-items:center;background:var(--pine-color-secondary);border:var(--pine-border);border-radius:var(--pine-border-radius-full);-webkit-box-sizing:border-box;box-sizing:border-box;color:var(--pine-color-text-secondary);cursor:pointer;display:-ms-inline-flexbox;display:inline-flex;height:var(--pine-dimension-400);-ms-flex-pack:center;justify-content:center;padding:0;width:var(--pine-dimension-400)}.pds-datepicker__nav:hover:not(:disabled){background:var(--pine-color-secondary-hover);border-color:var(--pine-color-border-hover)}.pds-datepicker__nav:focus-visible{outline:var(--pine-outline-focus);outline-offset:var(--pine-border-width)}.pds-datepicker__nav:disabled{border-color:var(--pine-color-border-disabled);color:var(--pine-color-text-secondary-disabled);cursor:not-allowed}.pds-datepicker__nav:dir(rtl) pds-icon{-webkit-transform:scaleX(-1);transform:scaleX(-1)}.pds-datepicker__dialog:focus{outline:none}.pds-datepicker__dialog{background:var(--pine-color-background-overlay);border-radius:var(--pine-dimension-150);-webkit-box-shadow:var(--pine-box-shadow-200);box-shadow:var(--pine-box-shadow-200);-webkit-box-sizing:border-box;box-sizing:border-box;left:0;max-height:calc(100vh - var(--pine-dimension-sm));max-height:calc(100dvh - var(--pine-dimension-sm));max-width:calc(100vw - var(--pine-dimension-sm));overflow:auto;-ms-scroll-chaining:none;overscroll-behavior:contain;padding:var(--pine-dimension-sm);pointer-events:auto;position:fixed;top:0;visibility:hidden;z-index:var(--pine-z-index-priority)}:host-context([data-theme=dark]) .pds-datepicker__dialog{border:var(--pine-border)}.pds-datepicker__header{-ms-flex-align:center;align-items:center;display:-ms-flexbox;display:flex;-ms-flex-pack:justify;justify-content:space-between;-webkit-margin-after:var(--pine-dimension-sm);margin-block-end:var(--pine-dimension-sm)}.pds-datepicker__nav-spacer{-ms-flex:none;flex:none;width:var(--pine-dimension-400)}.pds-datepicker__heading{color:var(--pine-color-text);font:var(--pine-typography-heading-5);font-weight:var(--pine-font-weight-semi-bold)}.pds-datepicker__grid{border-collapse:collapse;border-spacing:0}.pds-datepicker__weekday{color:var(--pine-color-text-muted);font:var(--pine-typography-body-sm);height:var(--pds-datepicker-day-size);padding:0;text-align:center;width:var(--pds-datepicker-day-size)}.pds-datepicker__day{border-radius:var(--pine-border-radius-full);color:var(--pine-color-text-secondary);cursor:pointer;font:var(--pine-typography-body);height:var(--pds-datepicker-day-size);padding:0;position:relative;text-align:center;-webkit-transition:background var(--pine-motion-duration-fast) var(--pine-motion-easing-in);transition:background var(--pine-motion-duration-fast) var(--pine-motion-easing-in);width:var(--pds-datepicker-day-size)}.pds-datepicker__day:hover{background:var(--pine-color-background-muted)}.pds-datepicker__day:focus-visible{outline:var(--pine-outline-focus);outline-offset:calc(var(--pine-border-width-thick) * -1)}.pds-datepicker__day--empty{cursor:default;pointer-events:none}.pds-datepicker__day--today::after{background:var(--pine-color-primary);border-radius:var(--pine-border-radius-full);content:"";height:var(--pine-dimension-050);inset-block-end:var(--pine-dimension-050);inset-inline-start:50%;position:absolute;-webkit-transform:translateX(-50%);transform:translateX(-50%);width:var(--pine-dimension-050)}.pds-datepicker__day--selected,.pds-datepicker__day--selected:hover{background:var(--pine-color-primary);border-radius:var(--pine-border-radius-full);color:var(--pine-color-text-primary)}.pds-datepicker__day--selected:focus-visible{outline-offset:var(--pine-border-width)}.pds-datepicker__day--range-start{border-end-end-radius:0;border-start-end-radius:0}.pds-datepicker__day--range-end{border-end-start-radius:0;border-start-start-radius:0}.pds-datepicker__day--range-start.pds-datepicker__day--row-end{border-end-end-radius:var(--pine-border-radius-full);border-start-end-radius:var(--pine-border-radius-full)}.pds-datepicker__day--range-end.pds-datepicker__day--row-start{border-end-start-radius:var(--pine-border-radius-full);border-start-start-radius:var(--pine-border-radius-full)}.pds-datepicker__day--selected.pds-datepicker__day--today::after{background:var(--pine-color-text-primary)}.pds-datepicker__day--in-range,.pds-datepicker__day--in-range:hover{background:var(--pine-color-background-muted);border-radius:0;color:var(--pine-color-text-secondary)}.pds-datepicker__day--row-start{border-end-start-radius:var(--pine-border-radius-full);border-start-start-radius:var(--pine-border-radius-full)}.pds-datepicker__day--row-end{border-end-end-radius:var(--pine-border-radius-full);border-start-end-radius:var(--pine-border-radius-full)}.pds-datepicker__day--disabled,.pds-datepicker__day--disabled:hover{background:transparent;color:var(--pine-color-text-disabled);cursor:not-allowed;text-decoration:line-through}.pds-datepicker__day--in-range:hover{background:var(--pine-color-background-inset)}.pds-datepicker__dialog--range{display:-ms-flexbox;display:flex;padding:0}.pds-datepicker__panel{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;min-width:0}.pds-datepicker__dialog--range .pds-datepicker__panel{padding:var(--pine-dimension-sm)}.pds-datepicker__months{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;gap:var(--pine-dimension-lg)}.pds-datepicker__presets-column{-webkit-border-end:var(--pine-border-width-thin) solid var(--pine-color-border-subtle);border-inline-end:var(--pine-border-width-thin) solid var(--pine-color-border-subtle);-ms-flex:none;flex:none;position:relative;width:var(--pds-datepicker-presets-width)}.pds-datepicker__presets{-webkit-box-sizing:border-box;box-sizing:border-box;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;gap:var(--pine-dimension-2xs);inset:0;list-style:none;margin:0;overflow-y:auto;padding:var(--pine-dimension-150);position:absolute}.pds-datepicker__preset{-ms-flex-align:center;align-items:center;background:transparent;border:0;border-radius:var(--pine-border-radius-md);color:var(--pine-color-text-secondary);cursor:pointer;display:-ms-flexbox;display:flex;font:var(--pine-typography-body);gap:var(--pine-dimension-xs);-ms-flex-pack:justify;justify-content:space-between;padding:var(--pine-dimension-xs) var(--pine-dimension-sm);text-align:start;-webkit-transition:background var(--pine-motion-duration-fast) var(--pine-motion-easing-in);transition:background var(--pine-motion-duration-fast) var(--pine-motion-easing-in);width:100%}.pds-datepicker__preset:hover:not(:disabled){background:var(--pine-color-background-muted)}.pds-datepicker__preset:focus-visible{outline:var(--pine-outline-focus)}.pds-datepicker__preset:disabled{color:var(--pine-color-text-disabled);cursor:not-allowed}.pds-datepicker__preset--selected{background:var(--pine-color-background-muted);color:var(--pine-color-text-active)}.pds-datepicker__footer{-ms-flex-align:center;align-items:center;background:var(--pine-color-background-overlay);-webkit-border-before:var(--pine-border-width-thin) solid var(--pine-color-border-subtle);border-block-start:var(--pine-border-width-thin) solid var(--pine-color-border-subtle);display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;gap:var(--pine-dimension-xs);inset-block-end:calc(var(--pine-dimension-sm) * -1);-ms-flex-pack:end;justify-content:flex-end;margin-block:var(--pine-dimension-sm) calc(var(--pine-dimension-sm) * -1);padding-block:var(--pine-dimension-sm);position:-webkit-sticky;position:sticky}.pds-datepicker__selection{color:var(--pine-color-text-muted);font:var(--pine-typography-body-sm-medium);-webkit-margin-end:auto;margin-inline-end:auto}.pds-datepicker__dropdowns{display:-ms-flexbox;display:flex;gap:var(--pine-dimension-2xs)}.pds-datepicker__select-wrap{-ms-flex-align:center;align-items:center;display:-ms-inline-flexbox;display:inline-flex;position:relative}.pds-datepicker__heading-select{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:transparent;border:0;border-radius:var(--pine-border-radius-md);color:var(--pine-color-text);cursor:pointer;field-sizing:content;font:var(--pine-typography-heading-5);font-weight:var(--pine-font-weight-semi-bold);padding-block:var(--pine-dimension-2xs);padding-inline:var(--pine-dimension-xs) calc(var(--pine-dimension-xs) + var(--pine-dimension-sm))}.pds-datepicker__heading-select:hover{background:var(--pine-color-background-muted)}.pds-datepicker__heading-select:focus-visible{outline:var(--pine-outline-focus)}.pds-datepicker__select-caret{color:var(--pine-color-text-secondary);inset-inline-end:var(--pine-dimension-xs);pointer-events:none;position:absolute}.pds-datepicker__time{-ms-flex-align:center;align-items:center;display:-ms-flexbox;display:flex;gap:var(--pine-dimension-xs);-ms-flex-pack:justify;justify-content:space-between;-webkit-margin-before:var(--pine-dimension-sm);margin-block-start:var(--pine-dimension-sm);padding-inline:var(--pine-dimension-xs)}.pds-datepicker__time-label{color:var(--pine-color-text-label);font:var(--pine-typography-body-medium)}.pds-datepicker__time-field{-ms-flex-align:center;align-items:center;background:var(--pine-color-background-container);border:var(--pine-border);border-radius:var(--pine-border-radius-md);display:-ms-inline-flexbox;display:inline-flex;min-height:var(--pine-dimension-400);padding-inline:var(--pine-dimension-2xs)}.pds-datepicker__time-field:hover{border-color:var(--pine-color-border-hover)}.pds-datepicker__time-field:focus-within{outline:var(--pine-outline-focus);outline-offset:var(--pine-border-width)}.pds-datepicker__time-select{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:transparent;border:0;border-radius:var(--pine-border-radius-sm);color:var(--pine-color-text-strong);cursor:pointer;font:var(--pine-typography-body);font-variant-numeric:tabular-nums;padding:var(--pine-dimension-2xs);text-align:center}.pds-datepicker__time-select:focus-visible{background:var(--pine-color-background-muted);outline:none}.pds-datepicker__time-select--period{-webkit-margin-start:var(--pine-dimension-2xs);margin-inline-start:var(--pine-dimension-2xs)}.pds-datepicker__time-separator{color:var(--pine-color-text-secondary)}:host-context([data-theme=dark]) select{color-scheme:dark}.pds-datepicker__action{background:var(--pine-color-secondary);border:var(--pine-border);border-radius:var(--pine-border-radius-full);color:var(--pine-color-text-secondary);cursor:pointer;font:var(--pine-typography-body-medium);padding:calc(var(--pine-dimension-xs) - var(--pine-border-width)) var(--pine-dimension-sm)}.pds-datepicker__action:hover:not(:disabled){background:var(--pine-color-secondary-hover);border-color:var(--pine-color-border-hover)}.pds-datepicker__action:focus-visible{outline:var(--pine-outline-focus);outline-offset:var(--pine-border-width)}.pds-datepicker__action:disabled{background:var(--pine-color-secondary-disabled);border-color:var(--pine-color-border-disabled);color:var(--pine-color-text-secondary-disabled);cursor:not-allowed}.pds-datepicker__action--primary{background:var(--pine-color-primary);border-color:var(--pine-color-primary);color:var(--pine-color-text-primary)}.pds-datepicker__action--primary:hover:not(:disabled){background:var(--pine-color-primary-hover);border-color:var(--pine-color-primary-hover)}.pds-datepicker__action--primary:disabled{background:var(--pine-color-primary-disabled);border-color:var(--pine-color-primary-disabled);color:var(--pine-color-text-primary-disabled)}@media (max-width: 600px){.pds-datepicker__dialog--range{-ms-flex-direction:column;flex-direction:column}.pds-datepicker__presets-column{-webkit-border-after:var(--pine-border-width-thin) solid var(--pine-color-border-subtle);border-block-end:var(--pine-border-width-thin) solid var(--pine-color-border-subtle);-webkit-border-end:0;border-inline-end:0;width:auto}.pds-datepicker__presets{-ms-flex-direction:row;flex-direction:row;overflow-x:auto;position:static}.pds-datepicker__preset{white-space:nowrap}.pds-datepicker__selection{-ms-flex-preferred-size:100%;flex-basis:100%}}.pds-datepicker__trigger,.pds-datepicker__clear,.pds-datepicker__nav,.pds-datepicker__weekday,.pds-datepicker__day,.pds-datepicker__preset,.pds-datepicker__action{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}@media (forced-colors: active){.pds-datepicker__day--selected,.pds-datepicker__day--selected:hover{background:Highlight;color:HighlightText;forced-color-adjust:none}.pds-datepicker__day--in-range,.pds-datepicker__day--in-range:hover{border-block:var(--pine-border-width-thin) solid Highlight}.pds-datepicker__day--today::after{background:CanvasText;forced-color-adjust:none}.pds-datepicker__day--selected.pds-datepicker__day--today::after{background:HighlightText}.pds-datepicker__day--disabled{color:GrayText}}.visually-hidden{border:0;clip:rect(0 0 0 0);-webkit-clip-path:polygon(0 0, 0 0, 0 0);clip-path:polygon(0 0, 0 0, 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;white-space:nowrap;width:1px}`;
|
|
614
|
+
|
|
615
|
+
const FULL_DATE = { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' };
|
|
616
|
+
const DEFAULT_START_TIME = '00:00';
|
|
617
|
+
const DEFAULT_END_TIME = '23:59';
|
|
618
|
+
/** Ids for datepickers without a `component-id`, so their calendars' ids stay unique. */
|
|
619
|
+
let fallbackIds = 0;
|
|
620
|
+
/** Below this window width, range mode shows one month rather than two side by side. */
|
|
621
|
+
const TWO_MONTHS_MIN_WIDTH = 640;
|
|
622
|
+
/** The least height the calendar keeps when there's little room beside its field; it may cover the field then. */
|
|
623
|
+
const CALENDAR_MIN_HEIGHT = 480;
|
|
624
|
+
/** The calendar's gap to the window's edges. */
|
|
625
|
+
const VIEWPORT_PADDING = 8;
|
|
626
|
+
/** How many years either side of today the year dropdown offers without `min` or `max`. */
|
|
627
|
+
const DROPDOWN_YEARS = 100;
|
|
628
|
+
/** Swallows the next click anywhere, or none if it doesn't come soon, as after a drag. */
|
|
629
|
+
function swallowNextClick() {
|
|
630
|
+
const swallow = (event) => {
|
|
631
|
+
event.stopPropagation();
|
|
632
|
+
event.preventDefault();
|
|
633
|
+
};
|
|
634
|
+
document.addEventListener('click', swallow, { capture: true, once: true });
|
|
635
|
+
window.setTimeout(() => document.removeEventListener('click', swallow, { capture: true }), 500);
|
|
636
|
+
}
|
|
637
|
+
const firstOfMonth = (date) => new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), 1));
|
|
638
|
+
/** Just the day's number, in the locale's digits: Japanese and Chinese add 日 to a formatted day. */
|
|
639
|
+
const dayNumberText = (formatter, date) => { var _a, _b; return (_b = (_a = formatter.formatToParts(date).find((part) => part.type === 'day')) === null || _a === void 0 ? void 0 : _a.value) !== null && _b !== void 0 ? _b : formatter.format(date); };
|
|
640
|
+
const PdsDatepicker = class {
|
|
641
|
+
constructor(hostRef) {
|
|
642
|
+
index.registerInstance(this, hostRef);
|
|
643
|
+
this.pdsDatepickerChange = index.createEvent(this, "pdsDatepickerChange");
|
|
644
|
+
this.pdsBlur = index.createEvent(this, "pdsBlur");
|
|
645
|
+
this.pdsFocus = index.createEvent(this, "pdsFocus");
|
|
646
|
+
/** Set when the active day should take focus on the next render. */
|
|
647
|
+
this.focusActiveDay = false;
|
|
648
|
+
this.fallbackId = `pds-datepicker-${++fallbackIds}`;
|
|
649
|
+
/** Set by a click on the label, whose forwarded click on the input shouldn't open the calendar. */
|
|
650
|
+
this.skipForwardedClick = false;
|
|
651
|
+
/** Set while an arrow key selects a preset, which auto-apply doesn't apply. */
|
|
652
|
+
this.presetByArrow = false;
|
|
653
|
+
/** The value when the calendar opened, or as last set from outside, which Cancel (without `clearable`) and Escape go back to. */
|
|
654
|
+
this.valueAtOpen = '';
|
|
655
|
+
/** Set while the datepicker commits a value itself, as opposed to one set from outside. */
|
|
656
|
+
this.committing = false;
|
|
657
|
+
/** Whether focus is in the datepicker or its calendar, for pdsFocus and pdsBlur. */
|
|
658
|
+
this.hasFocus = false;
|
|
659
|
+
/**
|
|
660
|
+
* Accessible label for the button that applies the chosen dates.
|
|
661
|
+
*/
|
|
662
|
+
this.applyLabel = 'Apply';
|
|
663
|
+
/**
|
|
664
|
+
* In range mode, applies the range as soon as its end or a preset is picked, without the Apply and
|
|
665
|
+
* Cancel buttons. Has no effect with the time picker, whose times still need Apply.
|
|
666
|
+
*/
|
|
667
|
+
this.autoApply = false;
|
|
668
|
+
/**
|
|
669
|
+
* Accessible label for the button that closes the calendar without changing the dates.
|
|
670
|
+
*/
|
|
671
|
+
this.cancelLabel = 'Cancel';
|
|
672
|
+
/**
|
|
673
|
+
* Adds a button in the field that empties it, shown while there's a value. Where Apply and Cancel
|
|
674
|
+
* buttons show, Clear also takes the place of Cancel.
|
|
675
|
+
*/
|
|
676
|
+
this.clearable = false;
|
|
677
|
+
/**
|
|
678
|
+
* Accessible label for the button that empties the field, when `clearable` is set.
|
|
679
|
+
*/
|
|
680
|
+
this.clearLabel = 'Clear';
|
|
681
|
+
/**
|
|
682
|
+
* The label of the presets entry that stands for a range of your own. Range mode with presets only.
|
|
683
|
+
*/
|
|
684
|
+
this.customRangeLabel = 'Custom range';
|
|
685
|
+
/**
|
|
686
|
+
* Accessible label for the end time, in range mode with the time picker.
|
|
687
|
+
*/
|
|
688
|
+
this.endTimeLabel = 'End time';
|
|
689
|
+
/**
|
|
690
|
+
* How the field shows dates and reads typed ones, as tokens: `YYYY`, `MMMM` or `MMM` (month name,
|
|
691
|
+
* in the locale), `MM` or `M` (month), `DD` or `D` (day), and with the time picker `HH` or `H`
|
|
692
|
+
* (24-hour), `hh` or `h` (12-hour), `mm` and `A` (AM/PM, in the locale), as `MM/DD/YYYY`,
|
|
693
|
+
* `MMM D, YYYY` or `D.M.YYYY h:mm A`. Put literal text in brackets, as `[Due] D/M/YYYY`. The value
|
|
694
|
+
* and the change event stay ISO (`YYYY-MM-DD`), whatever the format. Typed ISO dates are always
|
|
695
|
+
* accepted too.
|
|
696
|
+
*/
|
|
697
|
+
this.format = 'YYYY-MM-DD';
|
|
698
|
+
/**
|
|
699
|
+
* Accessible label for the hour in the time picker.
|
|
700
|
+
*/
|
|
701
|
+
this.hourLabel = 'Hour';
|
|
702
|
+
/**
|
|
703
|
+
* The validation message the form reports when the typed text isn't a date that can be picked.
|
|
704
|
+
*/
|
|
705
|
+
this.invalidDateMessage = 'Enter a date in the format shown.';
|
|
706
|
+
/**
|
|
707
|
+
* The validation message the form reports for a date after `max`. `{date}` is replaced by the
|
|
708
|
+
* latest date, in the field's format.
|
|
709
|
+
*/
|
|
710
|
+
this.maxDateMessage = 'Choose {date} or earlier.';
|
|
711
|
+
/**
|
|
712
|
+
* The validation message the form reports for a date before `min`. `{date}` is replaced by the
|
|
713
|
+
* earliest date, in the field's format.
|
|
714
|
+
*/
|
|
715
|
+
this.minDateMessage = 'Choose {date} or later.';
|
|
716
|
+
/**
|
|
717
|
+
* The minutes the time picker steps by, such as `15` or `30`.
|
|
718
|
+
*/
|
|
719
|
+
this.minuteIncrement = 1;
|
|
720
|
+
/**
|
|
721
|
+
* Accessible label for the minute in the time picker.
|
|
722
|
+
*/
|
|
723
|
+
this.minuteLabel = 'Minute';
|
|
724
|
+
/**
|
|
725
|
+
* Accessible label for the month dropdown, when `show-dropdowns` is set.
|
|
726
|
+
*/
|
|
727
|
+
this.monthLabel = 'Month';
|
|
728
|
+
/**
|
|
729
|
+
* Accessible label for the button that shows the next month.
|
|
730
|
+
*/
|
|
731
|
+
this.nextMonthLabel = 'Next month';
|
|
732
|
+
/**
|
|
733
|
+
* Accessible label for the AM or PM choice in the time picker, in locales with a 12-hour clock.
|
|
734
|
+
*/
|
|
735
|
+
this.periodLabel = 'AM or PM';
|
|
736
|
+
/**
|
|
737
|
+
* The placement of the calendar relative to the field.
|
|
738
|
+
*/
|
|
739
|
+
this.placement = 'bottom-start';
|
|
740
|
+
/**
|
|
741
|
+
* Accessible label for the list of presets, in range mode.
|
|
742
|
+
*/
|
|
743
|
+
this.presetsLabel = 'Presets';
|
|
744
|
+
/**
|
|
745
|
+
* Accessible label for the button that shows the previous month.
|
|
746
|
+
*/
|
|
747
|
+
this.previousMonthLabel = 'Previous month';
|
|
748
|
+
/**
|
|
749
|
+
* Picks a range: a start and an end date, from two months side by side, applied with the Apply
|
|
750
|
+
* button. The value is `YYYY-MM-DD/YYYY-MM-DD`.
|
|
751
|
+
*/
|
|
752
|
+
this.range = false;
|
|
753
|
+
/**
|
|
754
|
+
* Shows month and year dropdowns in place of the month heading, for jumping far, as to a birth year.
|
|
755
|
+
*/
|
|
756
|
+
this.showDropdowns = false;
|
|
757
|
+
/**
|
|
758
|
+
* Accessible label for the start time, in range mode with the time picker.
|
|
759
|
+
*/
|
|
760
|
+
this.startTimeLabel = 'Start time';
|
|
761
|
+
/**
|
|
762
|
+
* Adds hour and minute pickers below the calendar, and the value carries the time as
|
|
763
|
+
* `YYYY-MM-DDTHH:mm`. The dates are applied with the Apply button.
|
|
764
|
+
*/
|
|
765
|
+
this.timePicker = false;
|
|
766
|
+
/**
|
|
767
|
+
* Accessible label for the time, with the time picker and a single date.
|
|
768
|
+
*/
|
|
769
|
+
this.timeLabel = 'Time';
|
|
770
|
+
/**
|
|
771
|
+
* Accessible label for the button that opens the calendar. The selected date is added to it.
|
|
772
|
+
*/
|
|
773
|
+
this.triggerLabel = 'Choose date';
|
|
774
|
+
/**
|
|
775
|
+
* The selected date as `YYYY-MM-DD`, or in range mode the range as `YYYY-MM-DD/YYYY-MM-DD`; with
|
|
776
|
+
* the time picker, each date as `YYYY-MM-DDTHH:mm`. An empty string when nothing is selected.
|
|
777
|
+
*/
|
|
778
|
+
this.value = '';
|
|
779
|
+
/**
|
|
780
|
+
* Accessible label for the year dropdown, when `show-dropdowns` is set.
|
|
781
|
+
*/
|
|
782
|
+
this.yearLabel = 'Year';
|
|
783
|
+
this.isOpen = false;
|
|
784
|
+
/** The text in the field, which may not be a valid date yet. */
|
|
785
|
+
this.text = '';
|
|
786
|
+
/** Why the typed text wasn't committed: it isn't a date, or it's before `min` or after `max`. */
|
|
787
|
+
this.textError = '';
|
|
788
|
+
/** The day the keyboard is on in the calendar, as `YYYY-MM-DD`. */
|
|
789
|
+
this.activeDate = '';
|
|
790
|
+
/** The first month on view, as the first of the month, `YYYY-MM-DD`. */
|
|
791
|
+
this.viewMonth = '';
|
|
792
|
+
/**
|
|
793
|
+
* The dates being chosen, before Apply: range mode's start and end, or the time picker's date.
|
|
794
|
+
* The end is empty while it's being picked.
|
|
795
|
+
*/
|
|
796
|
+
this.draftStart = '';
|
|
797
|
+
this.draftEnd = '';
|
|
798
|
+
/** The time picker's times, as `HH:mm`. */
|
|
799
|
+
this.startTime = DEFAULT_START_TIME;
|
|
800
|
+
this.endTime = DEFAULT_END_TIME;
|
|
801
|
+
/** Range mode: the day under the pointer or keyboard, previewing the end of the range. */
|
|
802
|
+
this.previewDate = '';
|
|
803
|
+
this.onApply = () => {
|
|
804
|
+
const parts = this.draftParts();
|
|
805
|
+
if (!parts)
|
|
806
|
+
return;
|
|
807
|
+
this.commitParts(parts);
|
|
808
|
+
this.closeCalendar(true);
|
|
809
|
+
};
|
|
810
|
+
/** Cancel empties the field with `clearable`, and otherwise leaves the dates as they were. */
|
|
811
|
+
this.onCancel = () => {
|
|
812
|
+
if (this.clearable) {
|
|
813
|
+
this.commitValue('');
|
|
814
|
+
}
|
|
815
|
+
else {
|
|
816
|
+
this.restoreValueAtOpen();
|
|
817
|
+
}
|
|
818
|
+
this.closeCalendar(true);
|
|
819
|
+
};
|
|
820
|
+
this.onPresetClick = (event, preset) => {
|
|
821
|
+
this.draftStart = toIsoDate(preset.start);
|
|
822
|
+
this.draftEnd = toIsoDate(preset.end);
|
|
823
|
+
this.previewDate = '';
|
|
824
|
+
if (this.appliesOnPick && !this.presetByArrow) {
|
|
825
|
+
this.onApply();
|
|
826
|
+
return;
|
|
827
|
+
}
|
|
828
|
+
this.showDate(preset.end, true);
|
|
829
|
+
// Safari doesn't focus a clicked button; keep focus in the dialog for Escape and the Tab trap.
|
|
830
|
+
event.currentTarget.focus();
|
|
831
|
+
};
|
|
832
|
+
/** Arrow keys move between the presets, picking each, as in a radio group. Custom range only takes focus. */
|
|
833
|
+
this.onPresetsKeyDown = (event) => {
|
|
834
|
+
const step = { ArrowDown: 1, ArrowRight: 1, ArrowUp: -1, ArrowLeft: -1 }[event.key];
|
|
835
|
+
const toEdge = { Home: 'first', End: 'last' }[event.key];
|
|
836
|
+
if (!step && !toEdge)
|
|
837
|
+
return;
|
|
838
|
+
const radios = Array.from(event.currentTarget.querySelectorAll('[role="radio"]:not([disabled])'));
|
|
839
|
+
const at = radios.indexOf(event.target);
|
|
840
|
+
const next = toEdge === 'first' ? radios[0] : toEdge === 'last' ? radios[radios.length - 1] : radios[(at + step + radios.length) % radios.length];
|
|
841
|
+
if (!next)
|
|
842
|
+
return;
|
|
843
|
+
event.preventDefault();
|
|
844
|
+
next.focus();
|
|
845
|
+
if (next.classList.contains('pds-datepicker__preset') && next !== radios[radios.length - 1]) {
|
|
846
|
+
// Arrows select a preset without applying it, even with auto-apply, so every preset and
|
|
847
|
+
// Custom range stay reachable; Enter or Space applies it.
|
|
848
|
+
this.presetByArrow = true;
|
|
849
|
+
next.click();
|
|
850
|
+
this.presetByArrow = false;
|
|
851
|
+
}
|
|
852
|
+
};
|
|
853
|
+
/**
|
|
854
|
+
* Keeps a press on the clear button, by mouse or touch, from blurring the field, so the text
|
|
855
|
+
* typed in it isn't committed before the click empties it.
|
|
856
|
+
*/
|
|
857
|
+
this.onClearMouseDown = (event) => {
|
|
858
|
+
event.preventDefault();
|
|
859
|
+
};
|
|
860
|
+
/** The field's clear button: empties the field and returns focus to it. */
|
|
861
|
+
this.onClearClick = () => {
|
|
862
|
+
var _a;
|
|
863
|
+
this.commitValue('');
|
|
864
|
+
this.closeCalendar(false);
|
|
865
|
+
void ((_a = this.inputEl) === null || _a === void 0 ? void 0 : _a.setFocus());
|
|
866
|
+
};
|
|
867
|
+
/** "Custom range" points to the calendar, where a range of your own is picked. */
|
|
868
|
+
this.onCustomRangeClick = () => {
|
|
869
|
+
var _a, _b;
|
|
870
|
+
(_b = (_a = this.dialogEl) === null || _a === void 0 ? void 0 : _a.querySelector('[data-date][tabindex="0"]')) === null || _b === void 0 ? void 0 : _b.focus();
|
|
871
|
+
};
|
|
872
|
+
this.onDocumentPointerDown = (event) => {
|
|
873
|
+
var _a;
|
|
874
|
+
const path = event.composedPath();
|
|
875
|
+
if (path.includes(this.el) || (this.portal && path.includes(this.portal.host)))
|
|
876
|
+
return;
|
|
877
|
+
this.closeCalendar(false);
|
|
878
|
+
// A press on a modal's backdrop lands on its <dialog>. That press dismisses the calendar, the
|
|
879
|
+
// top layer; swallow the click it makes, so the modal under it stays open.
|
|
880
|
+
if (((_a = path[0]) === null || _a === void 0 ? void 0 : _a.localName) === 'dialog')
|
|
881
|
+
swallowNextClick();
|
|
882
|
+
};
|
|
883
|
+
/**
|
|
884
|
+
* Reports focus arriving from outside. Focus moving within the datepicker never reaches the host:
|
|
885
|
+
* once retargeted, its target and related target are both the host, so the browser skips it.
|
|
886
|
+
*/
|
|
887
|
+
this.onHostFocusIn = (event) => {
|
|
888
|
+
this.focusEntered(event);
|
|
889
|
+
};
|
|
890
|
+
/**
|
|
891
|
+
* The calendar is a modal dialog while focus is in it. Opened by a click in the field, focus stays
|
|
892
|
+
* in the field and the calendar is a plain popup beside it. Set on the element rather than through
|
|
893
|
+
* state: focus often moves in from `componentDidRender`, where a state change doesn't re-render.
|
|
894
|
+
*/
|
|
895
|
+
this.onCalendarFocusIn = (event) => {
|
|
896
|
+
var _a, _b;
|
|
897
|
+
this.focusEntered(event);
|
|
898
|
+
(_a = this.dialogEl) === null || _a === void 0 ? void 0 : _a.setAttribute('aria-modal', 'true');
|
|
899
|
+
// The dialog itself takes focus only from a click on a part that can't, as the month heading.
|
|
900
|
+
// Pass it to the active day, so Tab, Shift+Tab and the arrow keys keep working.
|
|
901
|
+
if (event.target === this.dialogEl) {
|
|
902
|
+
(_b = this.dialogEl.querySelector(`[data-date="${this.activeDate}"]`)) === null || _b === void 0 ? void 0 : _b.focus({ preventScroll: true });
|
|
903
|
+
}
|
|
904
|
+
};
|
|
905
|
+
/**
|
|
906
|
+
* The portaled calendar is outside the datepicker, so the host never hears focus leave it: close
|
|
907
|
+
* and report blur from here when focus goes from the calendar to the rest of the page. Checked
|
|
908
|
+
* once focus has settled, with `:focus-within`, rather than from `relatedTarget`, which is
|
|
909
|
+
* retargeted past the datepicker when it sits in another component's shadow root.
|
|
910
|
+
*/
|
|
911
|
+
this.onCalendarFocusOut = (event) => {
|
|
912
|
+
var _a, _b;
|
|
913
|
+
const next = event.relatedTarget;
|
|
914
|
+
if (!next || !((_a = this.dialogEl) === null || _a === void 0 ? void 0 : _a.contains(next)))
|
|
915
|
+
(_b = this.dialogEl) === null || _b === void 0 ? void 0 : _b.removeAttribute('aria-modal');
|
|
916
|
+
// Not gated on the calendar still being open: a press outside closes it on pointerdown, before
|
|
917
|
+
// this focusout, and focus has still left the datepicker.
|
|
918
|
+
this.checkFocusLeft(event, next !== null);
|
|
919
|
+
};
|
|
920
|
+
/** Closes the calendar and reports blur when focus leaves the datepicker, as when tabbing out. */
|
|
921
|
+
this.onHostFocusOut = (event) => {
|
|
922
|
+
const next = event.relatedTarget;
|
|
923
|
+
if (next && this.isInside(next))
|
|
924
|
+
return;
|
|
925
|
+
this.checkFocusLeft(event, next !== null);
|
|
926
|
+
};
|
|
927
|
+
// Inner pds-input events stay inside: the datepicker reports its own change, as a date.
|
|
928
|
+
this.stopInnerEvent = (event) => event.stopPropagation();
|
|
929
|
+
/** A click in the field opens the calendar beside it; the button inside toggles it on its own. */
|
|
930
|
+
this.onFieldClick = (event) => {
|
|
931
|
+
var _a, _b;
|
|
932
|
+
// The calendar and clear buttons sit in the field but act on their own.
|
|
933
|
+
const path = event.composedPath();
|
|
934
|
+
if ((this.triggerEl && path.includes(this.triggerEl)) || (this.clearEl && path.includes(this.clearEl)))
|
|
935
|
+
return;
|
|
936
|
+
// A click on the label is forwarded to the input as a second click; skip that one too.
|
|
937
|
+
const label = (_b = (_a = this.inputEl) === null || _a === void 0 ? void 0 : _a.shadowRoot) === null || _b === void 0 ? void 0 : _b.querySelector('label');
|
|
938
|
+
if (label && path.includes(label)) {
|
|
939
|
+
this.skipForwardedClick = true;
|
|
940
|
+
window.setTimeout(() => (this.skipForwardedClick = false));
|
|
941
|
+
return;
|
|
942
|
+
}
|
|
943
|
+
if (this.skipForwardedClick) {
|
|
944
|
+
this.skipForwardedClick = false;
|
|
945
|
+
return;
|
|
946
|
+
}
|
|
947
|
+
// Only the field's box: not its helper or error message.
|
|
948
|
+
const field = this.fieldReference();
|
|
949
|
+
if (field && field !== this.inputEl && !path.includes(field))
|
|
950
|
+
return;
|
|
951
|
+
this.openCalendar(false);
|
|
952
|
+
};
|
|
953
|
+
/** Typing a date or range the calendar can show moves the open calendar to it. */
|
|
954
|
+
this.onTextInput = (event) => {
|
|
955
|
+
var _a, _b;
|
|
956
|
+
event.stopPropagation();
|
|
957
|
+
this.text = String((_a = event.detail.value) !== null && _a !== void 0 ? _a : '');
|
|
958
|
+
if (!this.isOpen)
|
|
959
|
+
return;
|
|
960
|
+
const parts = this.parseText(this.text);
|
|
961
|
+
if (!parts)
|
|
962
|
+
return;
|
|
963
|
+
const [startTime, endTime] = [this.startTime, this.endTime];
|
|
964
|
+
// The times before the typed text changed them, for a pick in the calendar to go back to.
|
|
965
|
+
(_b = this.timesBeforeTyping) !== null && _b !== void 0 ? _b : (this.timesBeforeTyping = [startTime, endTime]);
|
|
966
|
+
this.loadDraft(parts);
|
|
967
|
+
if (this.timePicker) {
|
|
968
|
+
// A time picked in the calendar survives typing that doesn't change the time: text with no
|
|
969
|
+
// time, as a date part-way typed, or with the time as committed.
|
|
970
|
+
const format = this.resolvedFormat;
|
|
971
|
+
const locale = this.resolvedLocale;
|
|
972
|
+
const typed = this.range ? parseRangeText(this.text, format, locale) : [parseFieldText(this.text, format, locale)];
|
|
973
|
+
const committed = this.parsedValue();
|
|
974
|
+
const keeps = (i) => { var _a, _b, _c; return !((_a = typed === null || typed === void 0 ? void 0 : typed[i]) === null || _a === void 0 ? void 0 : _a.time) || ((_b = typed[i]) === null || _b === void 0 ? void 0 : _b.time) === ((_c = committed === null || committed === void 0 ? void 0 : committed[i]) === null || _c === void 0 ? void 0 : _c.time); };
|
|
975
|
+
if (keeps(0))
|
|
976
|
+
this.startTime = startTime;
|
|
977
|
+
if (this.range && keeps(1))
|
|
978
|
+
this.endTime = endTime;
|
|
979
|
+
}
|
|
980
|
+
this.showDate(parts[parts.length - 1].date);
|
|
981
|
+
};
|
|
982
|
+
this.onTextChange = (event) => {
|
|
983
|
+
event.stopPropagation();
|
|
984
|
+
if (!this.isOpen || !this.hasFooter) {
|
|
985
|
+
this.commitText();
|
|
986
|
+
return;
|
|
987
|
+
}
|
|
988
|
+
// With Apply showing, typed text is pending in the draft while focus stays in the datepicker
|
|
989
|
+
// (the calendar button, or the calendar): Apply commits it, Cancel or Escape drops it, and
|
|
990
|
+
// closing any other way commits it. Checked once focus settles.
|
|
991
|
+
window.setTimeout(() => {
|
|
992
|
+
var _a;
|
|
993
|
+
if (this.isOpen && (this.el.matches(':focus-within') || ((_a = this.portal) === null || _a === void 0 ? void 0 : _a.host.matches(':focus-within'))))
|
|
994
|
+
return;
|
|
995
|
+
this.commitPendingText();
|
|
996
|
+
});
|
|
997
|
+
};
|
|
998
|
+
this.onTextKeyDown = (event) => {
|
|
999
|
+
event.stopPropagation();
|
|
1000
|
+
const key = event.detail;
|
|
1001
|
+
if (key.altKey && key.key === 'ArrowDown') {
|
|
1002
|
+
key.preventDefault();
|
|
1003
|
+
if (this.isOpen) {
|
|
1004
|
+
this.focusActiveDay = true;
|
|
1005
|
+
this.flushActiveDayFocus();
|
|
1006
|
+
}
|
|
1007
|
+
else {
|
|
1008
|
+
this.openCalendar(true);
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
else if (key.key === 'Enter' && this.isOpen) {
|
|
1012
|
+
// Enter commits and closes. With Apply showing it does what Apply does, committing the
|
|
1013
|
+
// pending selection, typed or picked in the calendar. Typed text that isn't a date stays,
|
|
1014
|
+
// marked invalid.
|
|
1015
|
+
if (!this.hasFooter || (this.text !== this.fieldText() && !this.parseText(this.text))) {
|
|
1016
|
+
this.commitText();
|
|
1017
|
+
}
|
|
1018
|
+
else {
|
|
1019
|
+
const parts = this.draftParts();
|
|
1020
|
+
if (parts)
|
|
1021
|
+
this.commitParts(parts);
|
|
1022
|
+
}
|
|
1023
|
+
if (!this.textError)
|
|
1024
|
+
this.closeCalendar(false);
|
|
1025
|
+
}
|
|
1026
|
+
else if (key.key === 'Escape' && this.isOpen) {
|
|
1027
|
+
// Stop the field's own keydown, or a modal, popover or filter around the datepicker closes
|
|
1028
|
+
// too. pdsKeyDown is emitted during that keydown, so it can still be stopped here.
|
|
1029
|
+
key.preventDefault();
|
|
1030
|
+
key.stopPropagation();
|
|
1031
|
+
// Escape dismisses what was typed too, so the next blur doesn't commit it.
|
|
1032
|
+
this.restoreValueAtOpen();
|
|
1033
|
+
this.closeCalendar(false);
|
|
1034
|
+
}
|
|
1035
|
+
};
|
|
1036
|
+
/**
|
|
1037
|
+
* The calendar button's keys. Escape closes an open calendar, as in the field and the calendar,
|
|
1038
|
+
* and is stopped there, so a modal or popover around the datepicker stays open. Tab goes into a
|
|
1039
|
+
* calendar a click in the field opened: it's portaled to the end of the page, so Tab would
|
|
1040
|
+
* otherwise skip past it.
|
|
1041
|
+
*/
|
|
1042
|
+
this.onTriggerKeyDown = (event) => {
|
|
1043
|
+
var _a;
|
|
1044
|
+
if (!this.isOpen)
|
|
1045
|
+
return;
|
|
1046
|
+
if (event.key === 'Escape') {
|
|
1047
|
+
event.preventDefault();
|
|
1048
|
+
event.stopPropagation();
|
|
1049
|
+
this.restoreValueAtOpen();
|
|
1050
|
+
this.closeCalendar(false);
|
|
1051
|
+
return;
|
|
1052
|
+
}
|
|
1053
|
+
// A calendar that isn't placed and shown, as when the field is scrolled out of view, can't take
|
|
1054
|
+
// focus; let Tab move on from the trigger as usual.
|
|
1055
|
+
if (event.key !== 'Tab' || event.shiftKey || ((_a = this.dialogEl) === null || _a === void 0 ? void 0 : _a.style.visibility) !== 'visible')
|
|
1056
|
+
return;
|
|
1057
|
+
event.preventDefault();
|
|
1058
|
+
this.focusActiveDay = true;
|
|
1059
|
+
this.flushActiveDayFocus();
|
|
1060
|
+
};
|
|
1061
|
+
this.onTriggerClick = () => {
|
|
1062
|
+
if (this.isOpen) {
|
|
1063
|
+
this.closeCalendar(true);
|
|
1064
|
+
}
|
|
1065
|
+
else {
|
|
1066
|
+
this.openCalendar(true);
|
|
1067
|
+
}
|
|
1068
|
+
};
|
|
1069
|
+
this.onDialogKeyDown = (event) => {
|
|
1070
|
+
if (event.key === 'Escape') {
|
|
1071
|
+
event.preventDefault();
|
|
1072
|
+
event.stopPropagation();
|
|
1073
|
+
// As Cancel: the dates go back to how they were when the calendar opened.
|
|
1074
|
+
this.restoreValueAtOpen();
|
|
1075
|
+
this.closeCalendar(true);
|
|
1076
|
+
}
|
|
1077
|
+
else if (event.key === 'Tab') {
|
|
1078
|
+
this.trapTab(event);
|
|
1079
|
+
}
|
|
1080
|
+
};
|
|
1081
|
+
this.onGridKeyDown = (event) => {
|
|
1082
|
+
const active = parseIsoDate(this.activeDate);
|
|
1083
|
+
if (!active)
|
|
1084
|
+
return;
|
|
1085
|
+
const rtl = getComputedStyle(this.el).direction === 'rtl';
|
|
1086
|
+
const column = weekdayColumn(active, this.weekStart);
|
|
1087
|
+
let next = null;
|
|
1088
|
+
switch (event.key) {
|
|
1089
|
+
case 'ArrowLeft':
|
|
1090
|
+
next = addDays(active, rtl ? 1 : -1);
|
|
1091
|
+
break;
|
|
1092
|
+
case 'ArrowRight':
|
|
1093
|
+
next = addDays(active, rtl ? -1 : 1);
|
|
1094
|
+
break;
|
|
1095
|
+
case 'ArrowUp':
|
|
1096
|
+
next = addDays(active, -7);
|
|
1097
|
+
break;
|
|
1098
|
+
case 'ArrowDown':
|
|
1099
|
+
next = addDays(active, 7);
|
|
1100
|
+
break;
|
|
1101
|
+
case 'Home':
|
|
1102
|
+
next = addDays(active, -column);
|
|
1103
|
+
break;
|
|
1104
|
+
case 'End':
|
|
1105
|
+
next = addDays(active, 6 - column);
|
|
1106
|
+
break;
|
|
1107
|
+
case 'PageUp':
|
|
1108
|
+
next = addMonths(active, event.shiftKey ? -12 : -1);
|
|
1109
|
+
break;
|
|
1110
|
+
case 'PageDown':
|
|
1111
|
+
next = addMonths(active, event.shiftKey ? 12 : 1);
|
|
1112
|
+
break;
|
|
1113
|
+
case 'Enter':
|
|
1114
|
+
case ' ':
|
|
1115
|
+
event.preventDefault();
|
|
1116
|
+
this.pickDate(active);
|
|
1117
|
+
if (this.hasFooter)
|
|
1118
|
+
this.focusActiveDay = true;
|
|
1119
|
+
return;
|
|
1120
|
+
default:
|
|
1121
|
+
return;
|
|
1122
|
+
}
|
|
1123
|
+
event.preventDefault();
|
|
1124
|
+
const target = clampDate(next, this.minDate, this.maxDate);
|
|
1125
|
+
this.showDate(target);
|
|
1126
|
+
if (this.range)
|
|
1127
|
+
this.previewDate = toIsoDate(target);
|
|
1128
|
+
this.focusActiveDay = true;
|
|
1129
|
+
};
|
|
1130
|
+
// One handler per grid rather than per day, so hovering doesn't rebuild 84 closures each render.
|
|
1131
|
+
this.onGridClick = (event) => {
|
|
1132
|
+
const date = this.dayFromEvent(event);
|
|
1133
|
+
if (date)
|
|
1134
|
+
this.pickDate(date);
|
|
1135
|
+
};
|
|
1136
|
+
this.onGridPointerOver = (event) => {
|
|
1137
|
+
if (!this.range || !this.draftStart || this.draftEnd)
|
|
1138
|
+
return;
|
|
1139
|
+
const date = this.dayFromEvent(event);
|
|
1140
|
+
const iso = date && toIsoDate(date);
|
|
1141
|
+
if (iso && iso !== this.previewDate)
|
|
1142
|
+
this.previewDate = iso;
|
|
1143
|
+
};
|
|
1144
|
+
}
|
|
1145
|
+
/**
|
|
1146
|
+
* Opens the calendar and moves focus to the selected day, or today.
|
|
1147
|
+
*/
|
|
1148
|
+
async open() {
|
|
1149
|
+
this.openCalendar(true);
|
|
1150
|
+
}
|
|
1151
|
+
/**
|
|
1152
|
+
* Closes the calendar.
|
|
1153
|
+
*/
|
|
1154
|
+
async close() {
|
|
1155
|
+
this.closeCalendar(false);
|
|
1156
|
+
}
|
|
1157
|
+
/**
|
|
1158
|
+
* Sets focus on the datepicker's text field.
|
|
1159
|
+
*/
|
|
1160
|
+
async setFocus() {
|
|
1161
|
+
var _a;
|
|
1162
|
+
await ((_a = this.inputEl) === null || _a === void 0 ? void 0 : _a.setFocus());
|
|
1163
|
+
}
|
|
1164
|
+
valueChanged() {
|
|
1165
|
+
var _a;
|
|
1166
|
+
// A value set from outside while the calendar is open is what Cancel keeps.
|
|
1167
|
+
if (!this.committing)
|
|
1168
|
+
this.valueAtOpen = (_a = this.value) !== null && _a !== void 0 ? _a : '';
|
|
1169
|
+
this.refreshFromValue();
|
|
1170
|
+
}
|
|
1171
|
+
/**
|
|
1172
|
+
* Props that change how the value reads or what can be picked: refresh the field, and an open
|
|
1173
|
+
* calendar, as when a framework sets them after the datepicker loads.
|
|
1174
|
+
*/
|
|
1175
|
+
contextChanged() {
|
|
1176
|
+
this.refreshFromValue();
|
|
1177
|
+
}
|
|
1178
|
+
placementChanged() {
|
|
1179
|
+
// Restarted on the next render, from the new placement.
|
|
1180
|
+
this.stopPositioning();
|
|
1181
|
+
}
|
|
1182
|
+
refreshFromValue() {
|
|
1183
|
+
var _a;
|
|
1184
|
+
this.text = this.fieldText();
|
|
1185
|
+
this.textError = '';
|
|
1186
|
+
this.updateFormValue();
|
|
1187
|
+
if (!this.isOpen)
|
|
1188
|
+
return;
|
|
1189
|
+
const parts = this.parsedValue();
|
|
1190
|
+
this.loadDraft(parts);
|
|
1191
|
+
const day = (_a = (parts && parts[parts.length - 1].date)) !== null && _a !== void 0 ? _a : today();
|
|
1192
|
+
this.showDate(clampDate(day, this.minDate, this.maxDate), true);
|
|
1193
|
+
}
|
|
1194
|
+
requiredChanged() {
|
|
1195
|
+
this.updateFormValue();
|
|
1196
|
+
}
|
|
1197
|
+
componentWillLoad() {
|
|
1198
|
+
this.text = this.fieldText();
|
|
1199
|
+
}
|
|
1200
|
+
connectedCallback() {
|
|
1201
|
+
if (this.el.attachInternals && !this.internals) {
|
|
1202
|
+
this.internals = this.el.attachInternals();
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
componentDidLoad() {
|
|
1206
|
+
this.updateFormValue();
|
|
1207
|
+
}
|
|
1208
|
+
disconnectedCallback() {
|
|
1209
|
+
var _a;
|
|
1210
|
+
this.closeCalendar(false);
|
|
1211
|
+
(_a = this.portal) === null || _a === void 0 ? void 0 : _a.remove();
|
|
1212
|
+
}
|
|
1213
|
+
componentDidRender() {
|
|
1214
|
+
var _a, _b;
|
|
1215
|
+
if (!this.isOpen || !this.dialogEl) {
|
|
1216
|
+
(_a = this.portal) === null || _a === void 0 ? void 0 : _a.remove();
|
|
1217
|
+
return;
|
|
1218
|
+
}
|
|
1219
|
+
// Rendered here, then moved to the portal. Stencil keeps patching it by reference.
|
|
1220
|
+
(_b = this.portal) !== null && _b !== void 0 ? _b : (this.portal = new CalendarPortal(this.el));
|
|
1221
|
+
this.portal.mount(this.dialogEl, this.resolvedLocale);
|
|
1222
|
+
if (!this.cleanupAutoUpdate)
|
|
1223
|
+
this.startPositioning();
|
|
1224
|
+
this.flushActiveDayFocus();
|
|
1225
|
+
}
|
|
1226
|
+
/** Focuses the active day once the calendar is placed: a hidden calendar can't take focus. */
|
|
1227
|
+
flushActiveDayFocus() {
|
|
1228
|
+
var _a, _b;
|
|
1229
|
+
if (!this.focusActiveDay || ((_a = this.dialogEl) === null || _a === void 0 ? void 0 : _a.style.visibility) !== 'visible')
|
|
1230
|
+
return;
|
|
1231
|
+
this.focusActiveDay = false;
|
|
1232
|
+
(_b = this.dialogEl.querySelector(`[data-date="${this.activeDate}"]`)) === null || _b === void 0 ? void 0 : _b.focus();
|
|
1233
|
+
}
|
|
1234
|
+
/** The id the datepicker's parts are named from: `component-id`, or a generated one. */
|
|
1235
|
+
get baseId() {
|
|
1236
|
+
return this.componentId || this.fallbackId;
|
|
1237
|
+
}
|
|
1238
|
+
get resolvedLocale() {
|
|
1239
|
+
return supportedLocale(this.locale || i18nFormat.resolveLocale(this.el));
|
|
1240
|
+
}
|
|
1241
|
+
get weekStart() {
|
|
1242
|
+
const day = this.firstDayOfWeek;
|
|
1243
|
+
if (typeof day === 'number' && Number.isInteger(day) && day >= 0 && day <= 6)
|
|
1244
|
+
return day;
|
|
1245
|
+
return localeFirstDayOfWeek(this.resolvedLocale);
|
|
1246
|
+
}
|
|
1247
|
+
get minDate() {
|
|
1248
|
+
return parseIsoDate(this.min);
|
|
1249
|
+
}
|
|
1250
|
+
get maxDate() {
|
|
1251
|
+
return parseIsoDate(this.max);
|
|
1252
|
+
}
|
|
1253
|
+
get isInteractive() {
|
|
1254
|
+
return !this.disabled && !this.readonly;
|
|
1255
|
+
}
|
|
1256
|
+
/** How many months the calendar shows side by side. */
|
|
1257
|
+
get monthCount() {
|
|
1258
|
+
const narrow = typeof window !== 'undefined' && window.innerWidth < TWO_MONTHS_MIN_WIDTH;
|
|
1259
|
+
return this.range && !narrow ? 2 : 1;
|
|
1260
|
+
}
|
|
1261
|
+
/** Whether picks apply at once, without the footer: `auto-apply` in range mode, without times. */
|
|
1262
|
+
get appliesOnPick() {
|
|
1263
|
+
return this.range && this.autoApply && !this.timePicker;
|
|
1264
|
+
}
|
|
1265
|
+
/** Whether picks wait for Apply: for a range, unless it auto-applies, and for a date with a time. */
|
|
1266
|
+
get hasFooter() {
|
|
1267
|
+
return (this.range || this.timePicker) && !this.appliesOnPick;
|
|
1268
|
+
}
|
|
1269
|
+
get resolvedPresets() {
|
|
1270
|
+
return this.range ? resolvePresets(this.presets, today()) : [];
|
|
1271
|
+
}
|
|
1272
|
+
/** The committed value, parsed: one date, or a range's start and end. */
|
|
1273
|
+
parsedValue(value = this.value) {
|
|
1274
|
+
if (this.range) {
|
|
1275
|
+
const [start, end, extra] = (value !== null && value !== void 0 ? value : '').split('/');
|
|
1276
|
+
const startValue = parseDateTime(start);
|
|
1277
|
+
const endValue = parseDateTime(end);
|
|
1278
|
+
return extra === undefined && startValue && endValue && compareDateTime(startValue, endValue) <= 0
|
|
1279
|
+
? [startValue, endValue]
|
|
1280
|
+
: null;
|
|
1281
|
+
}
|
|
1282
|
+
const single = parseDateTime(value);
|
|
1283
|
+
return single ? [single] : null;
|
|
1284
|
+
}
|
|
1285
|
+
/** The format the field shows and reads, with a time when the time picker is on. */
|
|
1286
|
+
get resolvedFormat() {
|
|
1287
|
+
return fieldFormat(this.format, this.timePicker, this.timePicker && clockFor(this.resolvedLocale).hour12);
|
|
1288
|
+
}
|
|
1289
|
+
/** What the field shows for the committed value. */
|
|
1290
|
+
fieldText() {
|
|
1291
|
+
var _a;
|
|
1292
|
+
const parsed = this.parsedValue();
|
|
1293
|
+
// A value that isn't a date (or range) shows as it is, and the form reports it invalid.
|
|
1294
|
+
if (!parsed)
|
|
1295
|
+
return (_a = this.value) !== null && _a !== void 0 ? _a : '';
|
|
1296
|
+
return parsed
|
|
1297
|
+
.map((part) => toFieldText(this.withTimePicker(part), this.resolvedFormat, this.resolvedLocale))
|
|
1298
|
+
.join(RANGE_SEPARATOR);
|
|
1299
|
+
}
|
|
1300
|
+
/** Drops a time the time picker doesn't show, or adds the default one it does. */
|
|
1301
|
+
withTimePicker(value, fallback = DEFAULT_START_TIME) {
|
|
1302
|
+
var _a;
|
|
1303
|
+
return { date: value.date, time: this.timePicker ? ((_a = value.time) !== null && _a !== void 0 ? _a : fallback) : null };
|
|
1304
|
+
}
|
|
1305
|
+
get fieldPlaceholder() {
|
|
1306
|
+
if (this.placeholder)
|
|
1307
|
+
return this.placeholder;
|
|
1308
|
+
const one = this.resolvedFormat;
|
|
1309
|
+
return this.range ? `${one}${RANGE_SEPARATOR}${one}` : one;
|
|
1310
|
+
}
|
|
1311
|
+
/** Loads the committed value into the draft the calendar edits. */
|
|
1312
|
+
loadDraft(parts) {
|
|
1313
|
+
var _a, _b;
|
|
1314
|
+
const [start, end] = parts !== null && parts !== void 0 ? parts : [];
|
|
1315
|
+
this.draftStart = start ? toIsoDate(start.date) : '';
|
|
1316
|
+
this.draftEnd = this.range && end ? toIsoDate(end.date) : '';
|
|
1317
|
+
this.startTime = (_a = start === null || start === void 0 ? void 0 : start.time) !== null && _a !== void 0 ? _a : DEFAULT_START_TIME;
|
|
1318
|
+
this.endTime = (_b = end === null || end === void 0 ? void 0 : end.time) !== null && _b !== void 0 ? _b : DEFAULT_END_TIME;
|
|
1319
|
+
this.previewDate = '';
|
|
1320
|
+
}
|
|
1321
|
+
/**
|
|
1322
|
+
* Opens the calendar. From the button or the keyboard, focus moves to the active day; from a
|
|
1323
|
+
* click in the field, it stays there so typing keeps working.
|
|
1324
|
+
*/
|
|
1325
|
+
openCalendar(focusDay) {
|
|
1326
|
+
var _a, _b;
|
|
1327
|
+
if (!this.isInteractive || this.isOpen)
|
|
1328
|
+
return;
|
|
1329
|
+
const parts = this.parsedValue();
|
|
1330
|
+
this.loadDraft(parts);
|
|
1331
|
+
this.valueAtOpen = (_a = this.value) !== null && _a !== void 0 ? _a : '';
|
|
1332
|
+
const start = (_b = (parts && parts[parts.length - 1].date)) !== null && _b !== void 0 ? _b : today();
|
|
1333
|
+
this.showDate(clampDate(start, this.minDate, this.maxDate), true);
|
|
1334
|
+
this.focusActiveDay = focusDay;
|
|
1335
|
+
this.isOpen = true;
|
|
1336
|
+
document.addEventListener('pointerdown', this.onDocumentPointerDown, true);
|
|
1337
|
+
}
|
|
1338
|
+
closeCalendar(returnFocus) {
|
|
1339
|
+
var _a;
|
|
1340
|
+
if (!this.isOpen)
|
|
1341
|
+
return;
|
|
1342
|
+
this.commitPendingText();
|
|
1343
|
+
this.timesBeforeTyping = undefined;
|
|
1344
|
+
this.isOpen = false;
|
|
1345
|
+
this.stopPositioning();
|
|
1346
|
+
document.removeEventListener('pointerdown', this.onDocumentPointerDown, true);
|
|
1347
|
+
if (returnFocus)
|
|
1348
|
+
(_a = this.triggerEl) === null || _a === void 0 ? void 0 : _a.focus();
|
|
1349
|
+
}
|
|
1350
|
+
/**
|
|
1351
|
+
* Makes a day the active one and brings it into view. With two months, a day already on view
|
|
1352
|
+
* doesn't move the view; `reset` lines the view up so the day is in the last month shown.
|
|
1353
|
+
*/
|
|
1354
|
+
showDate(date, reset = false) {
|
|
1355
|
+
const first = firstOfMonth(date);
|
|
1356
|
+
const view = parseIsoDate(this.viewMonth);
|
|
1357
|
+
const lastView = view && addMonths(view, this.monthCount - 1);
|
|
1358
|
+
if (reset || !view || first < view) {
|
|
1359
|
+
this.viewMonth = toIsoDate(reset ? addMonths(first, 1 - this.monthCount) : first);
|
|
1360
|
+
}
|
|
1361
|
+
else if (lastView && first > lastView) {
|
|
1362
|
+
this.viewMonth = toIsoDate(addMonths(first, 1 - this.monthCount));
|
|
1363
|
+
}
|
|
1364
|
+
this.activeDate = toIsoDate(date);
|
|
1365
|
+
}
|
|
1366
|
+
pickDate(date) {
|
|
1367
|
+
if (isOutOfRange(date, this.minDate, this.maxDate))
|
|
1368
|
+
return;
|
|
1369
|
+
// A pick in the calendar takes over from typed text, so dismissing afterwards commits nothing,
|
|
1370
|
+
// as when nothing was typed.
|
|
1371
|
+
this.dropTypedText();
|
|
1372
|
+
if (this.range) {
|
|
1373
|
+
this.pickRangeDay(date);
|
|
1374
|
+
}
|
|
1375
|
+
else if (this.timePicker) {
|
|
1376
|
+
this.draftStart = toIsoDate(date);
|
|
1377
|
+
this.activeDate = this.draftStart;
|
|
1378
|
+
}
|
|
1379
|
+
else {
|
|
1380
|
+
this.commitValue(toIsoDate(date));
|
|
1381
|
+
this.closeCalendar(true);
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
/**
|
|
1385
|
+
* The first pick starts a range and the second ends it. A day before the start moves the start,
|
|
1386
|
+
* and a pick after a finished range starts a new one.
|
|
1387
|
+
*/
|
|
1388
|
+
pickRangeDay(date) {
|
|
1389
|
+
const start = parseIsoDate(this.draftStart);
|
|
1390
|
+
const iso = toIsoDate(date);
|
|
1391
|
+
if (!start || this.draftEnd) {
|
|
1392
|
+
this.draftStart = iso;
|
|
1393
|
+
this.draftEnd = '';
|
|
1394
|
+
}
|
|
1395
|
+
else if (date < start) {
|
|
1396
|
+
this.draftStart = iso;
|
|
1397
|
+
}
|
|
1398
|
+
else {
|
|
1399
|
+
this.draftEnd = iso;
|
|
1400
|
+
}
|
|
1401
|
+
this.previewDate = '';
|
|
1402
|
+
this.activeDate = iso;
|
|
1403
|
+
if (this.draftEnd && this.appliesOnPick)
|
|
1404
|
+
this.onApply();
|
|
1405
|
+
}
|
|
1406
|
+
/** Sets the value from the user, emitting the change event when it differs. */
|
|
1407
|
+
commitValue(next, detail = {}) {
|
|
1408
|
+
var _a;
|
|
1409
|
+
const changed = next !== ((_a = this.value) !== null && _a !== void 0 ? _a : '');
|
|
1410
|
+
this.timesBeforeTyping = undefined;
|
|
1411
|
+
this.committing = true;
|
|
1412
|
+
this.value = next;
|
|
1413
|
+
this.committing = false;
|
|
1414
|
+
this.text = this.fieldText();
|
|
1415
|
+
this.textError = '';
|
|
1416
|
+
// Always, not only when the value changes: fixing a typo back to the same value has to clear
|
|
1417
|
+
// the invalid state the form holds, and the value watcher won't fire for an unchanged value.
|
|
1418
|
+
this.updateFormValue();
|
|
1419
|
+
if (changed)
|
|
1420
|
+
this.pdsDatepickerChange.emit(Object.assign({ value: next }, detail));
|
|
1421
|
+
}
|
|
1422
|
+
/** Commits one date, or a range with its start, end and matching preset. */
|
|
1423
|
+
commitParts(parts) {
|
|
1424
|
+
if (!this.range) {
|
|
1425
|
+
this.commitValue(toValueString(parts[0]));
|
|
1426
|
+
return;
|
|
1427
|
+
}
|
|
1428
|
+
const [start, end] = parts;
|
|
1429
|
+
const preset = matchPreset(this.resolvedPresets, start.date, end.date);
|
|
1430
|
+
this.commitValue(`${toValueString(start)}/${toValueString(end)}`, Object.assign({ start: toValueString(start), end: toValueString(end) }, (preset ? { preset: preset.key } : {})));
|
|
1431
|
+
}
|
|
1432
|
+
/** The draft as values, or `null` before a start is picked. A lone start is a one-day range. */
|
|
1433
|
+
draftParts() {
|
|
1434
|
+
var _a;
|
|
1435
|
+
const startDate = parseIsoDate(this.draftStart);
|
|
1436
|
+
if (!startDate)
|
|
1437
|
+
return null;
|
|
1438
|
+
const start = this.withTimePicker({ date: startDate, time: this.startTime });
|
|
1439
|
+
if (!this.range)
|
|
1440
|
+
return [start];
|
|
1441
|
+
const end = this.withTimePicker({ date: (_a = parseIsoDate(this.draftEnd)) !== null && _a !== void 0 ? _a : startDate, time: this.endTime });
|
|
1442
|
+
// An end time before the start time on the same day ends at the start.
|
|
1443
|
+
return [start, compareDateTime(end, start) < 0 ? Object.assign(Object.assign({}, end), { time: start.time }) : end];
|
|
1444
|
+
}
|
|
1445
|
+
/**
|
|
1446
|
+
* Goes back to the value from when the calendar opened, as Cancel and Escape do, and drops any
|
|
1447
|
+
* typed text still pending. With Apply showing, typed text isn't committed while focus stays in
|
|
1448
|
+
* the datepicker, so usually there's only text to drop; without a footer, text typed in the
|
|
1449
|
+
* field is committed when it loses focus, so that commit is undone here.
|
|
1450
|
+
*/
|
|
1451
|
+
restoreValueAtOpen() {
|
|
1452
|
+
var _a;
|
|
1453
|
+
if (this.valueAtOpen === ((_a = this.value) !== null && _a !== void 0 ? _a : '')) {
|
|
1454
|
+
// Nothing was committed, so nothing to emit.
|
|
1455
|
+
this.dropTypedText();
|
|
1456
|
+
return;
|
|
1457
|
+
}
|
|
1458
|
+
const parts = this.parsedValue(this.valueAtOpen);
|
|
1459
|
+
if (parts)
|
|
1460
|
+
this.commitParts(parts);
|
|
1461
|
+
else
|
|
1462
|
+
this.commitValue(this.valueAtOpen);
|
|
1463
|
+
}
|
|
1464
|
+
/** Whether a node is the datepicker, inside it, or in its portaled calendar. */
|
|
1465
|
+
isInside(node) {
|
|
1466
|
+
var _a, _b;
|
|
1467
|
+
if (!node)
|
|
1468
|
+
return false;
|
|
1469
|
+
return (node === this.el || this.el.contains(node) || !!((_a = this.el.shadowRoot) === null || _a === void 0 ? void 0 : _a.contains(node)) || !!((_b = this.portal) === null || _b === void 0 ? void 0 : _b.contains(node)));
|
|
1470
|
+
}
|
|
1471
|
+
/**
|
|
1472
|
+
* Focus arriving in the datepicker or its portaled calendar from outside emits pdsFocus, once.
|
|
1473
|
+
* Tracked with a flag rather than `relatedTarget`, which is null or retargeted once the calendar
|
|
1474
|
+
* is portaled out of the datepicker.
|
|
1475
|
+
*/
|
|
1476
|
+
focusEntered(event) {
|
|
1477
|
+
if (this.hasFocus)
|
|
1478
|
+
return;
|
|
1479
|
+
this.hasFocus = true;
|
|
1480
|
+
this.pdsFocus.emit(event);
|
|
1481
|
+
}
|
|
1482
|
+
/**
|
|
1483
|
+
* Once focus settles, whether it left both the datepicker and its calendar: then pdsBlur, once,
|
|
1484
|
+
* and the calendar closes if focus went to another control. Focus that fell to the page, as on a
|
|
1485
|
+
* click on a blank area or when a focused control is disabled, leaves the calendar open.
|
|
1486
|
+
*/
|
|
1487
|
+
checkFocusLeft(event, toControl) {
|
|
1488
|
+
window.setTimeout(() => {
|
|
1489
|
+
var _a;
|
|
1490
|
+
if (this.el.matches(':focus-within') || ((_a = this.portal) === null || _a === void 0 ? void 0 : _a.host.matches(':focus-within')))
|
|
1491
|
+
return;
|
|
1492
|
+
if (toControl)
|
|
1493
|
+
this.closeCalendar(false);
|
|
1494
|
+
if (!this.hasFocus)
|
|
1495
|
+
return;
|
|
1496
|
+
this.hasFocus = false;
|
|
1497
|
+
this.pdsBlur.emit(event);
|
|
1498
|
+
});
|
|
1499
|
+
}
|
|
1500
|
+
startPositioning() {
|
|
1501
|
+
const reference = this.fieldReference();
|
|
1502
|
+
const floating = this.dialogEl;
|
|
1503
|
+
if (!reference || !floating)
|
|
1504
|
+
return;
|
|
1505
|
+
this.cleanupAutoUpdate = floatingUi_dom.autoUpdate(reference, floating, () => {
|
|
1506
|
+
floatingUi_dom.computePosition(reference, floating, {
|
|
1507
|
+
placement: this.placement,
|
|
1508
|
+
strategy: 'fixed',
|
|
1509
|
+
middleware: [
|
|
1510
|
+
floatingUi_dom.offset(4),
|
|
1511
|
+
floatingUi_dom.flip({ padding: VIEWPORT_PADDING }),
|
|
1512
|
+
// Never larger than the window: the calendar scrolls instead, with its footer pinned. With
|
|
1513
|
+
// little room on either side it keeps a usable height and may cover its own field.
|
|
1514
|
+
floatingUi_dom.size({
|
|
1515
|
+
padding: VIEWPORT_PADDING,
|
|
1516
|
+
apply({ availableWidth, availableHeight, elements }) {
|
|
1517
|
+
const windowHeight = window.innerHeight - VIEWPORT_PADDING * 2;
|
|
1518
|
+
const height = Math.max(availableHeight, Math.min(windowHeight, CALENDAR_MIN_HEIGHT));
|
|
1519
|
+
Object.assign(elements.floating.style, {
|
|
1520
|
+
maxHeight: `${Math.max(0, height)}px`,
|
|
1521
|
+
maxWidth: `${Math.max(0, availableWidth)}px`,
|
|
1522
|
+
});
|
|
1523
|
+
},
|
|
1524
|
+
}),
|
|
1525
|
+
floatingUi_dom.shift({ padding: VIEWPORT_PADDING, crossAxis: true }),
|
|
1526
|
+
floatingUi_dom.hide(),
|
|
1527
|
+
],
|
|
1528
|
+
}).then(({ x, y, middlewareData }) => {
|
|
1529
|
+
var _a;
|
|
1530
|
+
// Hidden once the field scrolls out of view, as behind a modal's header, rather than floating free.
|
|
1531
|
+
const hidden = !!((_a = middlewareData.hide) === null || _a === void 0 ? void 0 : _a.referenceHidden);
|
|
1532
|
+
Object.assign(floating.style, { left: `${x}px`, top: `${y}px`, visibility: hidden ? 'hidden' : 'visible' });
|
|
1533
|
+
this.flushActiveDayFocus();
|
|
1534
|
+
});
|
|
1535
|
+
});
|
|
1536
|
+
}
|
|
1537
|
+
stopPositioning() {
|
|
1538
|
+
var _a;
|
|
1539
|
+
(_a = this.cleanupAutoUpdate) === null || _a === void 0 ? void 0 : _a.call(this);
|
|
1540
|
+
this.cleanupAutoUpdate = undefined;
|
|
1541
|
+
}
|
|
1542
|
+
/** The field's box, so the calendar lines up with it rather than with the label or messages. */
|
|
1543
|
+
fieldReference() {
|
|
1544
|
+
var _a, _b, _c;
|
|
1545
|
+
return (_c = (_b = (_a = this.inputEl) === null || _a === void 0 ? void 0 : _a.shadowRoot) === null || _b === void 0 ? void 0 : _b.querySelector('.pds-input__field-wrapper')) !== null && _c !== void 0 ? _c : this.inputEl;
|
|
1546
|
+
}
|
|
1547
|
+
/**
|
|
1548
|
+
* Commits text typed while the calendar was open, when it closes without Apply or Cancel. A
|
|
1549
|
+
* valid date or range commits the draft, which also holds a time picked in the calendar; text
|
|
1550
|
+
* that isn't one is read as typed, so the field shows its error.
|
|
1551
|
+
*/
|
|
1552
|
+
commitPendingText() {
|
|
1553
|
+
if (this.text === this.fieldText())
|
|
1554
|
+
return;
|
|
1555
|
+
const parts = this.parseText(this.text) ? this.draftParts() : null;
|
|
1556
|
+
if (parts)
|
|
1557
|
+
this.commitParts(parts);
|
|
1558
|
+
else
|
|
1559
|
+
this.commitText();
|
|
1560
|
+
}
|
|
1561
|
+
/** The typed text as a date or range, or `null` if it isn't one; it may be outside `min` and `max`. */
|
|
1562
|
+
readText(text) {
|
|
1563
|
+
const format = this.resolvedFormat;
|
|
1564
|
+
const locale = this.resolvedLocale;
|
|
1565
|
+
const parts = this.range ? parseRangeText(text, format, locale) : [parseFieldText(text, format, locale)];
|
|
1566
|
+
if (!parts || parts.some((part) => !part))
|
|
1567
|
+
return null;
|
|
1568
|
+
return parts.map((part, i) => this.withTimePicker(part, i === 1 ? DEFAULT_END_TIME : DEFAULT_START_TIME));
|
|
1569
|
+
}
|
|
1570
|
+
/** The typed text as a date or range that can be picked, or `null`. */
|
|
1571
|
+
parseText(text) {
|
|
1572
|
+
const parts = this.readText(text);
|
|
1573
|
+
return parts && !this.rangeError(parts) ? parts : null;
|
|
1574
|
+
}
|
|
1575
|
+
/** Whether any of these dates is before `min` or after `max`. */
|
|
1576
|
+
rangeError(parts) {
|
|
1577
|
+
if (this.minDate && parts.some((part) => part.date < this.minDate))
|
|
1578
|
+
return 'min';
|
|
1579
|
+
if (this.maxDate && parts.some((part) => part.date > this.maxDate))
|
|
1580
|
+
return 'max';
|
|
1581
|
+
return '';
|
|
1582
|
+
}
|
|
1583
|
+
/** What's wrong with the committed value, as one set in code: not a date, or out of range. */
|
|
1584
|
+
valueError() {
|
|
1585
|
+
if (!this.value)
|
|
1586
|
+
return '';
|
|
1587
|
+
const parts = this.parsedValue();
|
|
1588
|
+
return parts ? this.rangeError(parts) : 'format';
|
|
1589
|
+
}
|
|
1590
|
+
commitText() {
|
|
1591
|
+
const text = this.text.trim();
|
|
1592
|
+
if (text === '') {
|
|
1593
|
+
this.commitValue('');
|
|
1594
|
+
return;
|
|
1595
|
+
}
|
|
1596
|
+
const parts = this.readText(text);
|
|
1597
|
+
const error = parts ? this.rangeError(parts) : 'format';
|
|
1598
|
+
if (parts && !error) {
|
|
1599
|
+
this.commitParts(parts);
|
|
1600
|
+
}
|
|
1601
|
+
else {
|
|
1602
|
+
this.textError = error;
|
|
1603
|
+
this.updateFormValue();
|
|
1604
|
+
}
|
|
1605
|
+
}
|
|
1606
|
+
/** Whether the month before (`-1`) or after (`1`) the ones on view has a day in range. */
|
|
1607
|
+
canShowMonth(direction) {
|
|
1608
|
+
var _a;
|
|
1609
|
+
const view = (_a = parseIsoDate(this.viewMonth)) !== null && _a !== void 0 ? _a : today();
|
|
1610
|
+
if (direction < 0)
|
|
1611
|
+
return !this.minDate || addDays(view, -1) >= this.minDate;
|
|
1612
|
+
return !this.maxDate || addMonths(view, this.monthCount) <= this.maxDate;
|
|
1613
|
+
}
|
|
1614
|
+
showMonth(event, direction) {
|
|
1615
|
+
var _a, _b;
|
|
1616
|
+
const view = (_a = parseIsoDate(this.viewMonth)) !== null && _a !== void 0 ? _a : today();
|
|
1617
|
+
const active = (_b = parseIsoDate(this.activeDate)) !== null && _b !== void 0 ? _b : today();
|
|
1618
|
+
this.viewMonth = toIsoDate(addMonths(view, direction));
|
|
1619
|
+
this.activeDate = toIsoDate(clampDate(addMonths(active, direction), this.minDate, this.maxDate));
|
|
1620
|
+
if (this.canShowMonth(direction)) {
|
|
1621
|
+
// Safari doesn't focus a clicked button; without this, focus falls to the page and Escape and
|
|
1622
|
+
// the Tab trap stop working.
|
|
1623
|
+
event.currentTarget.focus();
|
|
1624
|
+
}
|
|
1625
|
+
else {
|
|
1626
|
+
// The button is about to be disabled, which would drop focus.
|
|
1627
|
+
this.focusActiveDay = true;
|
|
1628
|
+
}
|
|
1629
|
+
}
|
|
1630
|
+
/** Shows the chosen month in the calendar at `index`, from the month or year dropdown. */
|
|
1631
|
+
jumpTo(index, year, month) {
|
|
1632
|
+
var _a;
|
|
1633
|
+
const target = clampDate(new Date(Date.UTC(year, month, 1)), this.minDate, this.maxDate);
|
|
1634
|
+
this.viewMonth = toIsoDate(addMonths(firstOfMonth(target), -index));
|
|
1635
|
+
const active = (_a = parseIsoDate(this.activeDate)) !== null && _a !== void 0 ? _a : target;
|
|
1636
|
+
const day = Math.min(active.getUTCDate(), new Date(Date.UTC(year, month + 1, 0)).getUTCDate());
|
|
1637
|
+
this.activeDate = toIsoDate(clampDate(new Date(Date.UTC(year, month, day)), this.minDate, this.maxDate));
|
|
1638
|
+
}
|
|
1639
|
+
/**
|
|
1640
|
+
* Drops text typed in the field and not committed, showing the committed value again. pds-input
|
|
1641
|
+
* is set directly: when typing and this land in one frame, the rendered text never changed.
|
|
1642
|
+
*/
|
|
1643
|
+
dropTypedText() {
|
|
1644
|
+
// Typed text may have set the times too; they go back with it.
|
|
1645
|
+
if (this.timesBeforeTyping)
|
|
1646
|
+
[this.startTime, this.endTime] = this.timesBeforeTyping;
|
|
1647
|
+
this.timesBeforeTyping = undefined;
|
|
1648
|
+
if (this.text === this.fieldText() && !this.textError)
|
|
1649
|
+
return;
|
|
1650
|
+
this.text = this.fieldText();
|
|
1651
|
+
if (this.inputEl)
|
|
1652
|
+
this.inputEl.value = this.text;
|
|
1653
|
+
this.textError = '';
|
|
1654
|
+
this.updateFormValue();
|
|
1655
|
+
}
|
|
1656
|
+
/** Keeps Tab within the calendar while it's open, as in a modal dialog. */
|
|
1657
|
+
trapTab(event) {
|
|
1658
|
+
var _a, _b, _c, _d;
|
|
1659
|
+
const focusable = Array.from((_b = (_a = this.dialogEl) === null || _a === void 0 ? void 0 : _a.querySelectorAll('button:not([disabled]):not([tabindex="-1"]), select:not([disabled]), [data-date][tabindex="0"]')) !== null && _b !== void 0 ? _b : []);
|
|
1660
|
+
if (focusable.length === 0)
|
|
1661
|
+
return;
|
|
1662
|
+
// The calendar may be in the portal's shadow root, so ask its own root what has focus.
|
|
1663
|
+
const current = (_d = (_c = this.dialogEl) === null || _c === void 0 ? void 0 : _c.getRootNode()) === null || _d === void 0 ? void 0 : _d.activeElement;
|
|
1664
|
+
const first = focusable[0];
|
|
1665
|
+
const last = focusable[focusable.length - 1];
|
|
1666
|
+
if (event.shiftKey && current === first) {
|
|
1667
|
+
event.preventDefault();
|
|
1668
|
+
last.focus();
|
|
1669
|
+
}
|
|
1670
|
+
else if (!event.shiftKey && current === last) {
|
|
1671
|
+
event.preventDefault();
|
|
1672
|
+
first.focus();
|
|
1673
|
+
}
|
|
1674
|
+
}
|
|
1675
|
+
/** The day under an event in a grid, from its cell. */
|
|
1676
|
+
dayFromEvent(event) {
|
|
1677
|
+
var _a, _b;
|
|
1678
|
+
const cell = (_b = (_a = event.target) === null || _a === void 0 ? void 0 : _a.closest) === null || _b === void 0 ? void 0 : _b.call(_a, '[data-date]');
|
|
1679
|
+
return parseIsoDate(cell === null || cell === void 0 ? void 0 : cell.dataset.date);
|
|
1680
|
+
}
|
|
1681
|
+
/**
|
|
1682
|
+
* Submits the value and reports validity to the form, as pds-input does: `required` with no
|
|
1683
|
+
* value, or typed text that isn't a date, blocks submission.
|
|
1684
|
+
*/
|
|
1685
|
+
updateFormValue() {
|
|
1686
|
+
var _a, _b, _c, _d;
|
|
1687
|
+
if (form.isSpecTest() || !((_a = this.internals) === null || _a === void 0 ? void 0 : _a.setFormValue))
|
|
1688
|
+
return;
|
|
1689
|
+
this.internals.setFormValue(this.value || null);
|
|
1690
|
+
if (!this.internals.setValidity)
|
|
1691
|
+
return;
|
|
1692
|
+
const anchor = (_d = (_c = (_b = this.inputEl) === null || _b === void 0 ? void 0 : _b.shadowRoot) === null || _c === void 0 ? void 0 : _c.querySelector('input')) !== null && _d !== void 0 ? _d : undefined;
|
|
1693
|
+
const error = this.textError || this.valueError();
|
|
1694
|
+
if (error === 'format') {
|
|
1695
|
+
this.internals.setValidity({ badInput: true }, this.invalidDateMessage, anchor);
|
|
1696
|
+
}
|
|
1697
|
+
else if (error === 'min') {
|
|
1698
|
+
this.internals.setValidity({ rangeUnderflow: true }, this.boundMessage(this.minDateMessage, this.minDate), anchor);
|
|
1699
|
+
}
|
|
1700
|
+
else if (error === 'max') {
|
|
1701
|
+
this.internals.setValidity({ rangeOverflow: true }, this.boundMessage(this.maxDateMessage, this.maxDate), anchor);
|
|
1702
|
+
}
|
|
1703
|
+
else if (this.required && !this.value) {
|
|
1704
|
+
this.internals.setValidity({ valueMissing: true }, (anchor === null || anchor === void 0 ? void 0 : anchor.validationMessage) || this.invalidDateMessage, anchor);
|
|
1705
|
+
}
|
|
1706
|
+
else {
|
|
1707
|
+
this.internals.setValidity({});
|
|
1708
|
+
}
|
|
1709
|
+
}
|
|
1710
|
+
/** A min or max message with its date, in the field's format. */
|
|
1711
|
+
boundMessage(template, bound) {
|
|
1712
|
+
const date = bound ? toFieldText({ date: bound, time: null }, this.format || undefined, this.resolvedLocale) : '';
|
|
1713
|
+
return i18nFormat.formatMessage(template, { date });
|
|
1714
|
+
}
|
|
1715
|
+
formResetCallback() {
|
|
1716
|
+
this.value = '';
|
|
1717
|
+
// The value may already be empty, so its watcher wouldn't clear typed text.
|
|
1718
|
+
this.text = '';
|
|
1719
|
+
this.textError = '';
|
|
1720
|
+
this.closeCalendar(false);
|
|
1721
|
+
this.updateFormValue();
|
|
1722
|
+
}
|
|
1723
|
+
/** Disabling the datepicker, or making it read-only, while the calendar is open closes it. */
|
|
1724
|
+
interactivityChanged() {
|
|
1725
|
+
if (!this.isInteractive)
|
|
1726
|
+
this.closeCalendar(false);
|
|
1727
|
+
}
|
|
1728
|
+
formDisabledCallback(disabled) {
|
|
1729
|
+
this.disabled = disabled;
|
|
1730
|
+
if (disabled)
|
|
1731
|
+
this.closeCalendar(false);
|
|
1732
|
+
}
|
|
1733
|
+
formStateRestoreCallback(state) {
|
|
1734
|
+
if (typeof state === 'string')
|
|
1735
|
+
this.value = state;
|
|
1736
|
+
}
|
|
1737
|
+
triggerAccessibleName(locale) {
|
|
1738
|
+
const parts = this.parsedValue();
|
|
1739
|
+
if (!parts)
|
|
1740
|
+
return this.triggerLabel;
|
|
1741
|
+
const dates = parts.map((part) => formatDate(part.date, locale, FULL_DATE));
|
|
1742
|
+
return `${this.triggerLabel}, ${dates.join(RANGE_SEPARATOR)}`;
|
|
1743
|
+
}
|
|
1744
|
+
/** The range to highlight: the chosen one, or the start to the previewed day while picking the end. */
|
|
1745
|
+
highlightedRange() {
|
|
1746
|
+
const start = parseIsoDate(this.draftStart);
|
|
1747
|
+
const end = parseIsoDate(this.draftEnd);
|
|
1748
|
+
if (!start || end)
|
|
1749
|
+
return [start, end];
|
|
1750
|
+
const preview = parseIsoDate(this.previewDate);
|
|
1751
|
+
return [start, preview && preview > start ? preview : null];
|
|
1752
|
+
}
|
|
1753
|
+
/** The day shown as selected: the draft, which opens as the committed day and follows typing. */
|
|
1754
|
+
selectedDay() {
|
|
1755
|
+
if (this.range)
|
|
1756
|
+
return null;
|
|
1757
|
+
return parseIsoDate(this.draftStart);
|
|
1758
|
+
}
|
|
1759
|
+
renderMonthHeading(view, index$2, locale, headingId) {
|
|
1760
|
+
const heading = formatDate(view, locale, { month: 'long', year: 'numeric' });
|
|
1761
|
+
// Only the first month announces itself as it changes; two months move together.
|
|
1762
|
+
const live = index$2 === 0 ? 'polite' : null;
|
|
1763
|
+
if (!this.showDropdowns) {
|
|
1764
|
+
return (index.h("div", { class: "pds-datepicker__heading", id: headingId, "aria-live": live }, heading));
|
|
1765
|
+
}
|
|
1766
|
+
const year = view.getUTCFullYear();
|
|
1767
|
+
const month = view.getUTCMonth();
|
|
1768
|
+
const min = this.minDate;
|
|
1769
|
+
const max = this.maxDate;
|
|
1770
|
+
const thisYear = today().getUTCFullYear();
|
|
1771
|
+
const firstYear = min ? min.getUTCFullYear() : thisYear - DROPDOWN_YEARS;
|
|
1772
|
+
const lastYear = max ? max.getUTCFullYear() : thisYear + DROPDOWN_YEARS;
|
|
1773
|
+
const monthName = dateFormatter(locale, { month: 'long' });
|
|
1774
|
+
const yearName = dateFormatter(locale, { year: 'numeric' });
|
|
1775
|
+
return (index.h("div", { class: "pds-datepicker__dropdowns" }, index.h("span", { class: "visually-hidden", id: headingId, "aria-live": live }, heading), index.h("span", { class: "pds-datepicker__select-wrap" }, index.h("select", { class: "pds-datepicker__heading-select", "aria-label": this.monthLabel, onChange: (event) => this.jumpTo(index$2, year, Number(event.target.value)) }, Array.from({ length: 12 }, (_, m) => {
|
|
1776
|
+
const outside = (!!min && new Date(Date.UTC(year, m + 1, 0)) < min) || (!!max && new Date(Date.UTC(year, m, 1)) > max);
|
|
1777
|
+
return (index.h("option", { value: String(m), selected: m === month, disabled: outside }, monthName.format(new Date(Date.UTC(year, m, 1)))));
|
|
1778
|
+
})), index.h("pds-icon", { class: "pds-datepicker__select-caret", icon: index$1.caretDown, size: "small", "aria-hidden": "true" })), index.h("span", { class: "pds-datepicker__select-wrap" }, index.h("select", { class: "pds-datepicker__heading-select", "aria-label": this.yearLabel, onChange: (event) => this.jumpTo(index$2, Number(event.target.value), month) }, Array.from({ length: Math.max(lastYear, year) - Math.min(firstYear, year) + 1 }, (_, i) => {
|
|
1779
|
+
const y = Math.min(firstYear, year) + i;
|
|
1780
|
+
return (index.h("option", { value: String(y), selected: y === year }, yearName.format(new Date(Date.UTC(y, 0, 1)))));
|
|
1781
|
+
})), index.h("pds-icon", { class: "pds-datepicker__select-caret", icon: index$1.caretDown, size: "small", "aria-hidden": "true" }))));
|
|
1782
|
+
}
|
|
1783
|
+
renderMonth(view, index$2, locale) {
|
|
1784
|
+
const year = view.getUTCFullYear();
|
|
1785
|
+
const month = view.getUTCMonth();
|
|
1786
|
+
const min = this.minDate;
|
|
1787
|
+
const max = this.maxDate;
|
|
1788
|
+
const selected = this.selectedDay();
|
|
1789
|
+
const [rangeStart, rangeEnd] = this.range ? this.highlightedRange() : [null, null];
|
|
1790
|
+
const now = today();
|
|
1791
|
+
const fullDate = dateFormatter(locale, FULL_DATE);
|
|
1792
|
+
const dayNumber = dateFormatter(locale, { day: 'numeric' });
|
|
1793
|
+
const headingId = `${this.baseId}-calendar-heading-${index$2}`;
|
|
1794
|
+
const isFirst = index$2 === 0;
|
|
1795
|
+
const isLast = index$2 === this.monthCount - 1;
|
|
1796
|
+
return (index.h("div", { class: "pds-datepicker__month" }, index.h("div", { class: "pds-datepicker__header" }, isFirst ? (index.h("button", { type: "button", class: "pds-datepicker__nav pds-datepicker__nav--previous", "aria-label": this.previousMonthLabel, disabled: !this.canShowMonth(-1), onClick: (event) => this.showMonth(event, -1) }, index.h("pds-icon", { icon: index$1.caretLeft, "aria-hidden": "true" }))) : (index.h("span", { class: "pds-datepicker__nav-spacer" })), this.renderMonthHeading(view, index$2, locale, headingId), isLast ? (index.h("button", { type: "button", class: "pds-datepicker__nav pds-datepicker__nav--next", "aria-label": this.nextMonthLabel, disabled: !this.canShowMonth(1), onClick: (event) => this.showMonth(event, 1) }, index.h("pds-icon", { icon: index$1.caretRight, "aria-hidden": "true" }))) : (index.h("span", { class: "pds-datepicker__nav-spacer" }))), index.h("table", { class: "pds-datepicker__grid", role: "grid", "aria-labelledby": headingId, "aria-multiselectable": this.range ? 'true' : null, onKeyDown: this.onGridKeyDown, onClick: this.onGridClick, onPointerOver: this.onGridPointerOver }, index.h("thead", null, index.h("tr", null, weekdayNames(locale, this.weekStart).map((name) => (index.h("th", { scope: "col", class: "pds-datepicker__weekday" }, index.h("span", { "aria-hidden": "true" }, name.short), index.h("span", { class: "visually-hidden" }, name.long)))))), index.h("tbody", null, monthWeeks(year, month, this.weekStart).map((week) => (index.h("tr", null, week.map((date, column) => {
|
|
1797
|
+
if (!date)
|
|
1798
|
+
return index.h("td", { class: "pds-datepicker__day pds-datepicker__day--empty" });
|
|
1799
|
+
const iso = toIsoDate(date);
|
|
1800
|
+
const unavailable = isOutOfRange(date, min, max);
|
|
1801
|
+
const isEndpoint = isSameDay(date, rangeStart) || isSameDay(date, rangeEnd);
|
|
1802
|
+
// A range's ends are capped on their outer side; a lone day stays round.
|
|
1803
|
+
const spansDays = !!rangeStart && !!rangeEnd && !isSameDay(rangeStart, rangeEnd);
|
|
1804
|
+
const inRange = !!rangeStart && !!rangeEnd && date >= rangeStart && date <= rangeEnd;
|
|
1805
|
+
const isSelected = isSameDay(date, selected) || isEndpoint || inRange;
|
|
1806
|
+
const isToday = isSameDay(date, now);
|
|
1807
|
+
return (index.h("td", { class: {
|
|
1808
|
+
'pds-datepicker__day': true,
|
|
1809
|
+
'pds-datepicker__day--selected': isSameDay(date, selected) || isEndpoint,
|
|
1810
|
+
'pds-datepicker__day--in-range': inRange && !isEndpoint,
|
|
1811
|
+
'pds-datepicker__day--range-start': spansDays && isSameDay(date, rangeStart),
|
|
1812
|
+
'pds-datepicker__day--range-end': spansDays && isSameDay(date, rangeEnd),
|
|
1813
|
+
'pds-datepicker__day--row-start': inRange && column === 0,
|
|
1814
|
+
'pds-datepicker__day--row-end': inRange && column === 6,
|
|
1815
|
+
'pds-datepicker__day--today': isToday,
|
|
1816
|
+
'pds-datepicker__day--disabled': unavailable,
|
|
1817
|
+
}, "data-date": iso, tabindex: iso === this.activeDate ? '0' : '-1', "aria-label": fullDate.format(date), "aria-selected": isSelected ? 'true' : null, "aria-current": isToday ? 'date' : null, "aria-disabled": unavailable ? 'true' : null }, dayNumberText(dayNumber, date)));
|
|
1818
|
+
})))))), this.timePicker && this.timesUnderMonth(index$2).map((which) => this.renderTime(which, locale))));
|
|
1819
|
+
}
|
|
1820
|
+
/**
|
|
1821
|
+
* The time pickers under a month: the start time under the first and the end time under the
|
|
1822
|
+
* second, or both under the one month a narrow range shows.
|
|
1823
|
+
*/
|
|
1824
|
+
timesUnderMonth(index) {
|
|
1825
|
+
if (!this.range)
|
|
1826
|
+
return ['start'];
|
|
1827
|
+
if (this.monthCount === 1)
|
|
1828
|
+
return ['start', 'end'];
|
|
1829
|
+
return [index === 0 ? 'start' : 'end'];
|
|
1830
|
+
}
|
|
1831
|
+
/** Hour, minute and (on a 12-hour clock) AM/PM pickers for the start or end time. */
|
|
1832
|
+
renderTime(which, locale) {
|
|
1833
|
+
var _a, _b, _c;
|
|
1834
|
+
// The end shows the time Apply saves: on one day, an end before the start ends at the start.
|
|
1835
|
+
const time = which === 'start' ? this.startTime : (_c = (_b = (_a = this.draftParts()) === null || _a === void 0 ? void 0 : _a[1]) === null || _b === void 0 ? void 0 : _b.time) !== null && _c !== void 0 ? _c : this.endTime;
|
|
1836
|
+
const [hours, minutes] = splitTime(time);
|
|
1837
|
+
const clock = clockFor(locale);
|
|
1838
|
+
const step = Math.max(1, Math.min(60, Math.floor(this.minuteIncrement) || 1));
|
|
1839
|
+
// Not `h`: that's the JSX factory.
|
|
1840
|
+
const setTime = (hour, minute) => {
|
|
1841
|
+
const time = joinTime(hour, minute);
|
|
1842
|
+
if (which === 'start')
|
|
1843
|
+
this.startTime = time;
|
|
1844
|
+
else
|
|
1845
|
+
this.endTime = time;
|
|
1846
|
+
// A time chosen here after typing stays when a pick later drops the typed text.
|
|
1847
|
+
if (this.timesBeforeTyping)
|
|
1848
|
+
this.timesBeforeTyping[which === 'start' ? 0 : 1] = time;
|
|
1849
|
+
};
|
|
1850
|
+
const groupLabel = !this.range ? this.timeLabel : which === 'start' ? this.startTimeLabel : this.endTimeLabel;
|
|
1851
|
+
const isPm = hours >= 12;
|
|
1852
|
+
const hourOptions = clock.hour12 ? Array.from({ length: 12 }, (_, i) => i + 1) : Array.from({ length: 24 }, (_, i) => i);
|
|
1853
|
+
const shownHour = clock.hour12 ? hours % 12 || 12 : hours;
|
|
1854
|
+
const minuteOptions = Array.from({ length: Math.ceil(60 / step) }, (_, i) => i * step);
|
|
1855
|
+
// A time off the step stays listed, so the select never shows a different minute than the value.
|
|
1856
|
+
if (!minuteOptions.includes(minutes))
|
|
1857
|
+
minuteOptions.push(minutes);
|
|
1858
|
+
minuteOptions.sort((a, b) => a - b);
|
|
1859
|
+
const labelId = `${this.baseId}-time-${which}`;
|
|
1860
|
+
return (index.h("div", { class: "pds-datepicker__time", role: "group", "aria-labelledby": labelId }, index.h("span", { class: "pds-datepicker__time-label", id: labelId }, groupLabel), index.h("span", { class: "pds-datepicker__time-field" }, index.h("select", { class: "pds-datepicker__time-select", "aria-label": this.hourLabel, onChange: (event) => {
|
|
1861
|
+
const picked = Number(event.target.value);
|
|
1862
|
+
setTime(clock.hour12 ? (picked % 12) + (isPm ? 12 : 0) : picked, minutes);
|
|
1863
|
+
} }, hourOptions.map((hour) => (index.h("option", { value: String(hour), selected: hour === shownHour }, clock.hour12 ? String(hour) : String(hour).padStart(2, '0'))))), index.h("span", { class: "pds-datepicker__time-separator", "aria-hidden": "true" }, ":"), index.h("select", { class: "pds-datepicker__time-select", "aria-label": this.minuteLabel, onChange: (event) => setTime(hours, Number(event.target.value)) }, minuteOptions.map((m) => (index.h("option", { value: String(m), selected: m === minutes }, String(m).padStart(2, '0'))))), clock.hour12 && (index.h("select", { class: "pds-datepicker__time-select pds-datepicker__time-select--period", "aria-label": this.periodLabel, onChange: (event) => {
|
|
1864
|
+
const pm = event.target.value === 'pm';
|
|
1865
|
+
setTime((hours % 12) + (pm ? 12 : 0), minutes);
|
|
1866
|
+
} }, index.h("option", { value: "am", selected: !isPm }, clock.am), index.h("option", { value: "pm", selected: isPm }, clock.pm))))));
|
|
1867
|
+
}
|
|
1868
|
+
renderPresets() {
|
|
1869
|
+
var _a;
|
|
1870
|
+
const presets = this.resolvedPresets;
|
|
1871
|
+
if (presets.length === 0)
|
|
1872
|
+
return null;
|
|
1873
|
+
const current = matchPreset(presets, parseIsoDate(this.draftStart), parseIsoDate(this.draftEnd));
|
|
1874
|
+
const isCustom = !current && !!this.draftStart;
|
|
1875
|
+
// One choice of range: a radio group, with one Tab stop on the checked choice and arrows between.
|
|
1876
|
+
// A disabled preset can't take focus, so the stop moves to the first one that can, or Custom range.
|
|
1877
|
+
const canPick = (preset) => presetInRange(preset, this.minDate, this.maxDate);
|
|
1878
|
+
const tabStop = current && canPick(current) ? current : isCustom ? 'custom' : (_a = presets.find(canPick)) !== null && _a !== void 0 ? _a : 'custom';
|
|
1879
|
+
return (index.h("div", { class: "pds-datepicker__presets-column" }, index.h("ul", { class: "pds-datepicker__presets", part: "presets", role: "radiogroup", "aria-label": this.presetsLabel, onKeyDown: this.onPresetsKeyDown }, presets.map((preset) => {
|
|
1880
|
+
const isCurrent = preset === current;
|
|
1881
|
+
return (index.h("li", { role: "none" }, index.h("button", { type: "button", role: "radio", class: { 'pds-datepicker__preset': true, 'pds-datepicker__preset--selected': isCurrent }, "aria-checked": isCurrent ? 'true' : 'false', tabindex: preset === tabStop ? '0' : '-1', disabled: !presetInRange(preset, this.minDate, this.maxDate), onClick: (event) => this.onPresetClick(event, preset) }, index.h("span", null, preset.label), isCurrent && index.h("pds-icon", { icon: index$1.check, "aria-hidden": "true" }))));
|
|
1882
|
+
}), index.h("li", { role: "none" }, index.h("button", { type: "button", role: "radio", class: { 'pds-datepicker__preset': true, 'pds-datepicker__preset--selected': isCustom }, "aria-checked": isCustom ? 'true' : 'false', tabindex: tabStop === 'custom' ? '0' : '-1', onClick: this.onCustomRangeClick }, index.h("span", null, this.customRangeLabel), isCustom && index.h("pds-icon", { icon: index$1.check, "aria-hidden": "true" }))))));
|
|
1883
|
+
}
|
|
1884
|
+
/** What Apply would set, in the field's format: shown in the footer, and announced as it changes. */
|
|
1885
|
+
selectionText() {
|
|
1886
|
+
const parts = this.draftParts();
|
|
1887
|
+
if (!parts)
|
|
1888
|
+
return '';
|
|
1889
|
+
const format = this.resolvedFormat;
|
|
1890
|
+
const locale = this.resolvedLocale;
|
|
1891
|
+
if (this.range && !this.draftEnd)
|
|
1892
|
+
return `${toFieldText(parts[0], format, locale)}${RANGE_SEPARATOR}`;
|
|
1893
|
+
return parts.map((part) => toFieldText(part, format, locale)).join(RANGE_SEPARATOR);
|
|
1894
|
+
}
|
|
1895
|
+
renderFooter() {
|
|
1896
|
+
const parts = this.draftParts();
|
|
1897
|
+
const selection = this.selectionText();
|
|
1898
|
+
return (index.h("div", { class: "pds-datepicker__footer" }, index.h("span", { class: "pds-datepicker__selection", "aria-live": "polite" }, selection), index.h("button", { type: "button", class: "pds-datepicker__action", onClick: this.onCancel }, this.clearable ? this.clearLabel : this.cancelLabel), index.h("button", { type: "button", class: "pds-datepicker__action pds-datepicker__action--primary", disabled: !parts, onClick: this.onApply }, this.applyLabel)));
|
|
1899
|
+
}
|
|
1900
|
+
renderCalendar(locale) {
|
|
1901
|
+
var _a;
|
|
1902
|
+
const view = (_a = parseIsoDate(this.viewMonth)) !== null && _a !== void 0 ? _a : today();
|
|
1903
|
+
const months = Array.from({ length: this.monthCount }, (_, i) => addMonths(view, i));
|
|
1904
|
+
return (index.h("div", { class: { 'pds-datepicker__dialog': true, 'pds-datepicker__dialog--range': this.range }, id: `${this.baseId}-calendar`, role: "dialog",
|
|
1905
|
+
// A click on a part that can't take focus, as the month heading, focuses the dialog
|
|
1906
|
+
// rather than dropping focus to the page, which would report blur mid-selection.
|
|
1907
|
+
tabindex: "-1", "aria-labelledby": this.range ? null : `${this.baseId}-calendar-heading-0`, "aria-label": this.range ? this.label || this.triggerLabel : null, part: "calendar", ref: (el) => (this.dialogEl = el), onKeyDown: this.onDialogKeyDown, onFocusin: this.onCalendarFocusIn, onFocusout: this.onCalendarFocusOut }, this.range && this.renderPresets(), index.h("div", { class: "pds-datepicker__panel" }, index.h("div", { class: "pds-datepicker__months" }, months.map((month, index) => this.renderMonth(month, index, locale))), this.hasFooter && this.renderFooter(), this.appliesOnPick && (
|
|
1908
|
+
// With no footer, still announce the start once it's picked, so it's clear an end comes next.
|
|
1909
|
+
index.h("span", { class: "visually-hidden", "aria-live": "polite" }, this.selectionText())))));
|
|
1910
|
+
}
|
|
1911
|
+
render() {
|
|
1912
|
+
const locale = this.resolvedLocale;
|
|
1913
|
+
return (index.h(index.Host, { key: 'a761f0ca6eeedcac3e4bd90760aa9781cd37dcdc', class: { 'pds-datepicker--range': this.range, 'pds-datepicker--time': this.timePicker }, "full-width": this.fullWidth ? 'true' : null, onFocusin: this.onHostFocusIn, onFocusout: this.onHostFocusOut }, index.h("pds-input", { key: '2eb04f9fd4e2f7a2437145d250c629c2cc8a0bf6', ref: (el) => (this.inputEl = el), componentId: this.baseId, label: this.label, hideLabel: this.hideLabel, helperMessage: this.helperMessage, errorMessage: this.errorMessage, invalid: this.invalid || !!this.textError || !!this.valueError(), disabled: this.disabled, readonly: this.readonly, required: this.required, placeholder: this.fieldPlaceholder, fullWidth: this.fullWidth, autocomplete: "off", value: this.text, onClick: this.onFieldClick, onPdsInput: this.onTextInput, onPdsChange: this.onTextChange, onPdsKeyDown: this.onTextKeyDown, onPdsFocus: this.stopInnerEvent, onPdsBlur: this.stopInnerEvent }, this.clearable && !!this.value && this.isInteractive && (index.h("button", {
|
|
1914
|
+
// Keyed, as is the calendar button: when Clear appears after a pick, the focused
|
|
1915
|
+
// calendar button must stay that button rather than be reused as Clear.
|
|
1916
|
+
key: "clear", slot: "suffix", type: "button", class: "pds-datepicker__clear", ref: (el) => (this.clearEl = el), "aria-label": this.label ? `${this.clearLabel} ${this.label}` : this.clearLabel,
|
|
1917
|
+
// Out of the tab order, as a native search field's clear button is: Tab from a field
|
|
1918
|
+
// just emptied would land on it as the change removes it, dropping focus to the page.
|
|
1919
|
+
tabindex: "-1", onMouseDown: this.onClearMouseDown, onClick: this.onClearClick
|
|
1920
|
+
}, index.h("pds-icon", { key: '98c325fbba8ea78b2d409bc77099db3206738508', icon: index$1.remove, "aria-hidden": "true" }))), index.h("button", { key: "trigger", slot: "suffix", type: "button", class: "pds-datepicker__trigger", ref: (el) => (this.triggerEl = el), "aria-label": this.triggerAccessibleName(locale), "aria-haspopup": "dialog", "aria-expanded": this.isOpen ? 'true' : 'false', disabled: !this.isInteractive, onClick: this.onTriggerClick, onKeyDown: this.onTriggerKeyDown }, index.h("pds-icon", { key: 'cb8483febe1b485058b7bb4271b749981a7747a1', icon: index$1.calendarSimple, "aria-hidden": "true" }))), this.isOpen && this.renderCalendar(locale)));
|
|
1921
|
+
}
|
|
1922
|
+
static get formAssociated() { return true; }
|
|
1923
|
+
get el() { return index.getElement(this); }
|
|
1924
|
+
static get watchers() { return {
|
|
1925
|
+
"value": [{
|
|
1926
|
+
"valueChanged": 0
|
|
1927
|
+
}],
|
|
1928
|
+
"format": [{
|
|
1929
|
+
"contextChanged": 0
|
|
1930
|
+
}],
|
|
1931
|
+
"locale": [{
|
|
1932
|
+
"contextChanged": 0
|
|
1933
|
+
}],
|
|
1934
|
+
"timePicker": [{
|
|
1935
|
+
"contextChanged": 0
|
|
1936
|
+
}],
|
|
1937
|
+
"range": [{
|
|
1938
|
+
"contextChanged": 0
|
|
1939
|
+
}],
|
|
1940
|
+
"min": [{
|
|
1941
|
+
"contextChanged": 0
|
|
1942
|
+
}],
|
|
1943
|
+
"max": [{
|
|
1944
|
+
"contextChanged": 0
|
|
1945
|
+
}],
|
|
1946
|
+
"placement": [{
|
|
1947
|
+
"placementChanged": 0
|
|
1948
|
+
}],
|
|
1949
|
+
"required": [{
|
|
1950
|
+
"requiredChanged": 0
|
|
1951
|
+
}],
|
|
1952
|
+
"disabled": [{
|
|
1953
|
+
"interactivityChanged": 0
|
|
1954
|
+
}],
|
|
1955
|
+
"readonly": [{
|
|
1956
|
+
"interactivityChanged": 0
|
|
1957
|
+
}]
|
|
1958
|
+
}; }
|
|
1959
|
+
};
|
|
1960
|
+
PdsDatepicker.style = pdsDatepickerCss();
|
|
1961
|
+
|
|
1962
|
+
exports.pds_datepicker = PdsDatepicker;
|