ngwr 10.2.0 → 10.2.1
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/dialog/styles/_index.scss +31 -0
- package/fesm2022/ngwr-action-sheet.mjs +3 -3
- package/fesm2022/ngwr-affix.mjs +3 -3
- package/fesm2022/ngwr-alert.mjs +3 -3
- package/fesm2022/ngwr-anchor.mjs +3 -3
- package/fesm2022/ngwr-aurora.mjs +3 -3
- package/fesm2022/ngwr-avatar.mjs +3 -3
- package/fesm2022/ngwr-back-top.mjs +3 -3
- package/fesm2022/ngwr-badge.mjs +6 -6
- package/fesm2022/ngwr-bar-chart.mjs +3 -3
- package/fesm2022/ngwr-blur-text.mjs +3 -3
- package/fesm2022/ngwr-border-glow.mjs +3 -3
- package/fesm2022/ngwr-breadcrumbs.mjs +8 -7
- package/fesm2022/ngwr-breadcrumbs.mjs.map +1 -1
- package/fesm2022/ngwr-burger.mjs +3 -3
- package/fesm2022/ngwr-button.mjs +59 -11
- package/fesm2022/ngwr-button.mjs.map +1 -1
- package/fesm2022/ngwr-calendar-heatmap.mjs +3 -3
- package/fesm2022/ngwr-calendar.mjs +88 -8
- package/fesm2022/ngwr-calendar.mjs.map +1 -1
- package/fesm2022/ngwr-card.mjs +9 -9
- package/fesm2022/ngwr-carousel.mjs +6 -6
- package/fesm2022/ngwr-cascader.mjs +3 -3
- package/fesm2022/ngwr-checkbox.mjs +6 -6
- package/fesm2022/ngwr-circular-text.mjs +3 -3
- package/fesm2022/ngwr-click-spark.mjs +3 -3
- package/fesm2022/ngwr-clipboard.mjs +18 -4
- package/fesm2022/ngwr-clipboard.mjs.map +1 -1
- package/fesm2022/ngwr-collapse.mjs +9 -9
- package/fesm2022/ngwr-color-picker.mjs +6 -6
- package/fesm2022/ngwr-command-palette.mjs +3 -3
- package/fesm2022/ngwr-compare.mjs +3 -3
- package/fesm2022/ngwr-confetti.mjs +3 -3
- package/fesm2022/ngwr-context-menu.mjs +12 -12
- package/fesm2022/ngwr-cookie.mjs +3 -3
- package/fesm2022/ngwr-counter.mjs +6 -6
- package/fesm2022/ngwr-date-adapter-fns.mjs +3 -3
- package/fesm2022/ngwr-date-adapter-luxon.mjs +3 -3
- package/fesm2022/ngwr-date-adapter.mjs +3 -3
- package/fesm2022/ngwr-date-picker.mjs +336 -54
- package/fesm2022/ngwr-date-picker.mjs.map +1 -1
- package/fesm2022/ngwr-decrypt-text.mjs +3 -3
- package/fesm2022/ngwr-density.mjs +6 -6
- package/fesm2022/ngwr-descriptions.mjs +6 -6
- package/fesm2022/ngwr-dialog.mjs +33 -17
- package/fesm2022/ngwr-dialog.mjs.map +1 -1
- package/fesm2022/ngwr-directives.mjs +12 -12
- package/fesm2022/ngwr-divider.mjs +3 -3
- package/fesm2022/ngwr-donut-chart.mjs +3 -3
- package/fesm2022/ngwr-drag-drop.mjs +6 -6
- package/fesm2022/ngwr-drawer.mjs +37 -19
- package/fesm2022/ngwr-drawer.mjs.map +1 -1
- package/fesm2022/ngwr-dropdown.mjs +9 -9
- package/fesm2022/ngwr-empty.mjs +3 -3
- package/fesm2022/ngwr-event-calendar.mjs +6 -6
- package/fesm2022/ngwr-falling-text.mjs +3 -3
- package/fesm2022/ngwr-file-upload.mjs +3 -3
- package/fesm2022/ngwr-form.mjs +9 -9
- package/fesm2022/ngwr-fuzzy-text.mjs +3 -3
- package/fesm2022/ngwr-gauge.mjs +3 -3
- package/fesm2022/ngwr-glitch-text.mjs +3 -3
- package/fesm2022/ngwr-gradient-text.mjs +3 -3
- package/fesm2022/ngwr-hotkey.mjs +31 -10
- package/fesm2022/ngwr-hotkey.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-en.mjs +11 -0
- package/fesm2022/ngwr-i18n-en.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-ru.mjs +11 -0
- package/fesm2022/ngwr-i18n-ru.mjs.map +1 -1
- package/fesm2022/ngwr-i18n.mjs +9 -9
- package/fesm2022/ngwr-icon.mjs +6 -6
- package/fesm2022/ngwr-image-cropper.mjs +3 -3
- package/fesm2022/ngwr-input-number.mjs +3 -3
- package/fesm2022/ngwr-input-otp.mjs +3 -3
- package/fesm2022/ngwr-input.mjs +15 -15
- package/fesm2022/ngwr-keyboard.mjs +3 -3
- package/fesm2022/ngwr-knob.mjs +3 -3
- package/fesm2022/ngwr-layout.mjs +15 -15
- package/fesm2022/ngwr-lightbox.mjs +3 -3
- package/fesm2022/ngwr-line-chart.mjs +3 -3
- package/fesm2022/ngwr-list.mjs +13 -8
- package/fesm2022/ngwr-list.mjs.map +1 -1
- package/fesm2022/ngwr-loading-bar.mjs +70 -30
- package/fesm2022/ngwr-loading-bar.mjs.map +1 -1
- package/fesm2022/ngwr-marquee.mjs +3 -3
- package/fesm2022/ngwr-media.mjs +3 -3
- package/fesm2022/ngwr-mention.mjs +6 -6
- package/fesm2022/ngwr-meta.mjs +6 -6
- package/fesm2022/ngwr-meter-group.mjs +3 -3
- package/fesm2022/ngwr-overlay.mjs +16 -7
- package/fesm2022/ngwr-overlay.mjs.map +1 -1
- package/fesm2022/ngwr-page-header.mjs +3 -3
- package/fesm2022/ngwr-pagination.mjs +13 -9
- package/fesm2022/ngwr-pagination.mjs.map +1 -1
- package/fesm2022/ngwr-pipes.mjs +21 -21
- package/fesm2022/ngwr-platform.mjs +9 -9
- package/fesm2022/ngwr-popconfirm.mjs +6 -6
- package/fesm2022/ngwr-popover.mjs +28 -8
- package/fesm2022/ngwr-popover.mjs.map +1 -1
- package/fesm2022/ngwr-progress.mjs +3 -3
- package/fesm2022/ngwr-pull-to-refresh.mjs +3 -3
- package/fesm2022/ngwr-qr.mjs +3 -3
- package/fesm2022/ngwr-radio.mjs +6 -6
- package/fesm2022/ngwr-rating.mjs +3 -3
- package/fesm2022/ngwr-result.mjs +12 -12
- package/fesm2022/ngwr-rotating-text.mjs +3 -3
- package/fesm2022/ngwr-scroll.mjs +3 -3
- package/fesm2022/ngwr-segmented.mjs +3 -3
- package/fesm2022/ngwr-select.mjs +9 -9
- package/fesm2022/ngwr-shiny-text.mjs +6 -6
- package/fesm2022/ngwr-sidebar.mjs +3 -3
- package/fesm2022/ngwr-skeleton.mjs +3 -3
- package/fesm2022/ngwr-slider.mjs +3 -3
- package/fesm2022/ngwr-sparkline.mjs +3 -3
- package/fesm2022/ngwr-speed-dial.mjs +3 -3
- package/fesm2022/ngwr-spinner.mjs +3 -3
- package/fesm2022/ngwr-splash-cursor.mjs +3 -3
- package/fesm2022/ngwr-split-text.mjs +3 -3
- package/fesm2022/ngwr-splitter.mjs +3 -3
- package/fesm2022/ngwr-spotlight-card.mjs +6 -6
- package/fesm2022/ngwr-squircle.mjs +6 -6
- package/fesm2022/ngwr-star-border.mjs +3 -3
- package/fesm2022/ngwr-statistic.mjs +9 -9
- package/fesm2022/ngwr-stepper.mjs +6 -6
- package/fesm2022/ngwr-storage.mjs +27 -17
- package/fesm2022/ngwr-storage.mjs.map +1 -1
- package/fesm2022/ngwr-switch.mjs +3 -3
- package/fesm2022/ngwr-table.mjs +19 -31
- package/fesm2022/ngwr-table.mjs.map +1 -1
- package/fesm2022/ngwr-tabs.mjs +6 -6
- package/fesm2022/ngwr-textarea.mjs +3 -3
- package/fesm2022/ngwr-theme.mjs +3 -3
- package/fesm2022/ngwr-tilt-card.mjs +6 -6
- package/fesm2022/ngwr-timeline.mjs +6 -6
- package/fesm2022/ngwr-toast.mjs +54 -20
- package/fesm2022/ngwr-toast.mjs.map +1 -1
- package/fesm2022/ngwr-toolbar.mjs +3 -3
- package/fesm2022/ngwr-tour.mjs +6 -6
- package/fesm2022/ngwr-transfer.mjs +3 -3
- package/fesm2022/ngwr-tree.mjs +3 -3
- package/fesm2022/ngwr-typewriter.mjs +3 -3
- package/fesm2022/ngwr-typography.mjs +3 -3
- package/fesm2022/ngwr-version.mjs +1 -1
- package/fesm2022/ngwr-version.mjs.map +1 -1
- package/fesm2022/ngwr-virtual-scroll.mjs +3 -3
- package/fesm2022/ngwr-waves.mjs +3 -3
- package/fesm2022/ngwr-window.mjs +80 -18
- package/fesm2022/ngwr-window.mjs.map +1 -1
- package/i18n/en.json +8 -0
- package/i18n/ru.json +8 -0
- package/package.json +1 -1
- package/types/ngwr-button.d.ts +32 -0
- package/types/ngwr-calendar.d.ts +27 -1
- package/types/ngwr-clipboard.d.ts +8 -1
- package/types/ngwr-date-picker.d.ts +124 -8
- package/types/ngwr-dialog.d.ts +13 -0
- package/types/ngwr-drawer.d.ts +15 -0
- package/types/ngwr-hotkey.d.ts +2 -0
- package/types/ngwr-list.d.ts +7 -2
- package/types/ngwr-loading-bar.d.ts +4 -7
- package/types/ngwr-pagination.d.ts +6 -1
- package/types/ngwr-popover.d.ts +10 -2
- package/types/ngwr-storage.d.ts +10 -1
- package/types/ngwr-toast.d.ts +19 -4
- package/types/ngwr-version.d.ts +1 -1
- package/types/ngwr-window.d.ts +23 -2
|
@@ -2,15 +2,13 @@ import { coerceBooleanProperty, coerceNumberProperty } from '@angular/cdk/coerci
|
|
|
2
2
|
import { ScrollStrategyOptions } from '@angular/cdk/overlay';
|
|
3
3
|
import { ComponentPortal } from '@angular/cdk/portal';
|
|
4
4
|
import * as i0 from '@angular/core';
|
|
5
|
-
import { input, inject, model, output, signal, computed, effect, untracked, ViewEncapsulation, Component,
|
|
5
|
+
import { input, inject, ElementRef, model, output, signal, computed, afterNextRender, effect, untracked, ViewEncapsulation, Component, DestroyRef, viewChild } from '@angular/core';
|
|
6
6
|
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
7
7
|
import { WrCalendar } from 'ngwr/calendar';
|
|
8
8
|
import { WrDateAdapter, WR_DATE_LOCALE } from 'ngwr/date-adapter';
|
|
9
9
|
import { readI18nText, useI18nText } from 'ngwr/i18n';
|
|
10
10
|
import { WrInput, WrInputGroup, WrInputSuffix } from 'ngwr/input';
|
|
11
11
|
import { WR_OVERLAY, WrOutsideClick } from 'ngwr/overlay';
|
|
12
|
-
import * as i1 from '@angular/forms';
|
|
13
|
-
import { FormsModule } from '@angular/forms';
|
|
14
12
|
import { clamp } from 'ngwr/utils';
|
|
15
13
|
|
|
16
14
|
/**
|
|
@@ -54,7 +52,13 @@ class WrTimePanel {
|
|
|
54
52
|
disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: coerceBooleanProperty });
|
|
55
53
|
/** Read-only — values cannot be changed. @default false */
|
|
56
54
|
readonly = input(false, { ...(ngDevMode ? { debugName: "readonly" } : /* istanbul ignore next */ {}), transform: coerceBooleanProperty });
|
|
55
|
+
/**
|
|
56
|
+
* Move focus onto the hours field once the panel is on screen. Off by
|
|
57
|
+
* default — see `WrCalendar.autoFocus` for why. @default false
|
|
58
|
+
*/
|
|
59
|
+
autoFocus = input(false, { ...(ngDevMode ? { debugName: "autoFocus" } : /* istanbul ignore next */ {}), transform: coerceBooleanProperty });
|
|
57
60
|
adapter = inject(WrDateAdapter);
|
|
61
|
+
host = inject(ElementRef);
|
|
58
62
|
locale = inject(WR_DATE_LOCALE);
|
|
59
63
|
/**
|
|
60
64
|
* Form value — the selected time as a `Date` (its date portion is preserved).
|
|
@@ -78,6 +82,14 @@ class WrTimePanel {
|
|
|
78
82
|
effectiveDisabled = computed(() => this.disabled(), /* @ts-ignore */
|
|
79
83
|
...(ngDevMode ? [{ debugName: "effectiveDisabled" }] : /* istanbul ignore next */ []));
|
|
80
84
|
constructor() {
|
|
85
|
+
// The hours field, not the increment button: a stepper is a shortcut, the
|
|
86
|
+
// number is the control. Deferred because the inputs are not in the DOM
|
|
87
|
+
// until the panel's first change-detection pass.
|
|
88
|
+
afterNextRender(() => {
|
|
89
|
+
if (!this.autoFocus())
|
|
90
|
+
return;
|
|
91
|
+
this.host.nativeElement.querySelector('.wr-time-picker__input')?.focus();
|
|
92
|
+
});
|
|
81
93
|
// Mirror an external `value` write into the internal h/m/s state (the old
|
|
82
94
|
// `writeValue` body). Guarded against the echo of our own `emit()` so a live
|
|
83
95
|
// edit is never clobbered by re-deriving it.
|
|
@@ -207,13 +219,13 @@ class WrTimePanel {
|
|
|
207
219
|
this.lastEmitted = next;
|
|
208
220
|
this.value.set(next);
|
|
209
221
|
}
|
|
210
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.
|
|
211
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.
|
|
222
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: WrTimePanel, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
223
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.1", type: WrTimePanel, isStandalone: true, selector: "wr-time-picker", inputs: { format: { classPropertyName: "format", publicName: "format", isSignal: true, isRequired: false, transformFunction: null }, showSeconds: { classPropertyName: "showSeconds", publicName: "showSeconds", isSignal: true, isRequired: false, transformFunction: null }, step: { classPropertyName: "step", publicName: "step", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, autoFocus: { classPropertyName: "autoFocus", publicName: "autoFocus", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", touch: "touch" }, host: { properties: { "class": "classes()" } }, ngImport: i0, template: "<div class=\"wr-time-picker__col\">\n <button\n type=\"button\"\n class=\"wr-time-picker__step wr-time-picker__step--up\"\n aria-label=\"Increment hours\"\n [disabled]=\"!interactive()\"\n (click)=\"stepHours(1)\"\n >\n <svg\n class=\"wr-time-picker__chevron\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"m18 15-6-6-6 6\" />\n </svg>\n </button>\n <input\n class=\"wr-time-picker__input\"\n type=\"text\"\n inputmode=\"numeric\"\n aria-label=\"Hours\"\n [value]=\"hoursDisplay()\"\n [disabled]=\"effectiveDisabled()\"\n [readonly]=\"readonly()\"\n (input)=\"onHoursInput($event)\"\n (blur)=\"onBlur()\"\n />\n <button\n type=\"button\"\n class=\"wr-time-picker__step wr-time-picker__step--down\"\n aria-label=\"Decrement hours\"\n [disabled]=\"!interactive()\"\n (click)=\"stepHours(-1)\"\n >\n <svg\n class=\"wr-time-picker__chevron\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"m6 9 6 6 6-6\" />\n </svg>\n </button>\n</div>\n\n<span class=\"wr-time-picker__sep\" aria-hidden=\"true\">:</span>\n\n<div class=\"wr-time-picker__col\">\n <button\n type=\"button\"\n class=\"wr-time-picker__step wr-time-picker__step--up\"\n aria-label=\"Increment minutes\"\n [disabled]=\"!interactive()\"\n (click)=\"stepMinutes(1)\"\n >\n <svg\n class=\"wr-time-picker__chevron\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"m18 15-6-6-6 6\" />\n </svg>\n </button>\n <input\n class=\"wr-time-picker__input\"\n type=\"text\"\n inputmode=\"numeric\"\n aria-label=\"Minutes\"\n [value]=\"minutesDisplay()\"\n [disabled]=\"effectiveDisabled()\"\n [readonly]=\"readonly()\"\n (input)=\"onMinutesInput($event)\"\n (blur)=\"onBlur()\"\n />\n <button\n type=\"button\"\n class=\"wr-time-picker__step wr-time-picker__step--down\"\n aria-label=\"Decrement minutes\"\n [disabled]=\"!interactive()\"\n (click)=\"stepMinutes(-1)\"\n >\n <svg\n class=\"wr-time-picker__chevron\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"m6 9 6 6 6-6\" />\n </svg>\n </button>\n</div>\n\n@if (showSeconds()) {\n <span class=\"wr-time-picker__sep\" aria-hidden=\"true\">:</span>\n\n <div class=\"wr-time-picker__col\">\n <button\n type=\"button\"\n class=\"wr-time-picker__step wr-time-picker__step--up\"\n aria-label=\"Increment seconds\"\n [disabled]=\"!interactive()\"\n (click)=\"stepSeconds(1)\"\n >\n <svg\n class=\"wr-time-picker__chevron\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"m18 15-6-6-6 6\" />\n </svg>\n </button>\n <input\n class=\"wr-time-picker__input\"\n type=\"text\"\n inputmode=\"numeric\"\n aria-label=\"Seconds\"\n [value]=\"secondsDisplay()\"\n [disabled]=\"effectiveDisabled()\"\n [readonly]=\"readonly()\"\n (input)=\"onSecondsInput($event)\"\n (blur)=\"onBlur()\"\n />\n <button\n type=\"button\"\n class=\"wr-time-picker__step wr-time-picker__step--down\"\n aria-label=\"Decrement seconds\"\n [disabled]=\"!interactive()\"\n (click)=\"stepSeconds(-1)\"\n >\n <svg\n class=\"wr-time-picker__chevron\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"m6 9 6 6 6-6\" />\n </svg>\n </button>\n </div>\n}\n\n@if (is12h()) {\n <div class=\"wr-time-picker__col wr-time-picker__col--ampm\">\n <button\n type=\"button\"\n class=\"wr-time-picker__step wr-time-picker__step--up\"\n aria-label=\"Toggle AM / PM\"\n [disabled]=\"!interactive()\"\n (click)=\"toggleAmPm()\"\n >\n <svg\n class=\"wr-time-picker__chevron\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"m18 15-6-6-6 6\" />\n </svg>\n </button>\n <span class=\"wr-time-picker__label\" aria-live=\"polite\">{{ amPmLabel() }}</span>\n <button\n type=\"button\"\n class=\"wr-time-picker__step wr-time-picker__step--down\"\n aria-label=\"Toggle AM / PM\"\n [disabled]=\"!interactive()\"\n (click)=\"toggleAmPm()\"\n >\n <svg\n class=\"wr-time-picker__chevron\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"m6 9 6 6 6-6\" />\n </svg>\n </button>\n </div>\n}\n", encapsulation: i0.ViewEncapsulation.None });
|
|
212
224
|
}
|
|
213
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.
|
|
225
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: WrTimePanel, decorators: [{
|
|
214
226
|
type: Component,
|
|
215
227
|
args: [{ selector: 'wr-time-picker', encapsulation: ViewEncapsulation.None, host: { '[class]': 'classes()' }, template: "<div class=\"wr-time-picker__col\">\n <button\n type=\"button\"\n class=\"wr-time-picker__step wr-time-picker__step--up\"\n aria-label=\"Increment hours\"\n [disabled]=\"!interactive()\"\n (click)=\"stepHours(1)\"\n >\n <svg\n class=\"wr-time-picker__chevron\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"m18 15-6-6-6 6\" />\n </svg>\n </button>\n <input\n class=\"wr-time-picker__input\"\n type=\"text\"\n inputmode=\"numeric\"\n aria-label=\"Hours\"\n [value]=\"hoursDisplay()\"\n [disabled]=\"effectiveDisabled()\"\n [readonly]=\"readonly()\"\n (input)=\"onHoursInput($event)\"\n (blur)=\"onBlur()\"\n />\n <button\n type=\"button\"\n class=\"wr-time-picker__step wr-time-picker__step--down\"\n aria-label=\"Decrement hours\"\n [disabled]=\"!interactive()\"\n (click)=\"stepHours(-1)\"\n >\n <svg\n class=\"wr-time-picker__chevron\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"m6 9 6 6 6-6\" />\n </svg>\n </button>\n</div>\n\n<span class=\"wr-time-picker__sep\" aria-hidden=\"true\">:</span>\n\n<div class=\"wr-time-picker__col\">\n <button\n type=\"button\"\n class=\"wr-time-picker__step wr-time-picker__step--up\"\n aria-label=\"Increment minutes\"\n [disabled]=\"!interactive()\"\n (click)=\"stepMinutes(1)\"\n >\n <svg\n class=\"wr-time-picker__chevron\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"m18 15-6-6-6 6\" />\n </svg>\n </button>\n <input\n class=\"wr-time-picker__input\"\n type=\"text\"\n inputmode=\"numeric\"\n aria-label=\"Minutes\"\n [value]=\"minutesDisplay()\"\n [disabled]=\"effectiveDisabled()\"\n [readonly]=\"readonly()\"\n (input)=\"onMinutesInput($event)\"\n (blur)=\"onBlur()\"\n />\n <button\n type=\"button\"\n class=\"wr-time-picker__step wr-time-picker__step--down\"\n aria-label=\"Decrement minutes\"\n [disabled]=\"!interactive()\"\n (click)=\"stepMinutes(-1)\"\n >\n <svg\n class=\"wr-time-picker__chevron\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"m6 9 6 6 6-6\" />\n </svg>\n </button>\n</div>\n\n@if (showSeconds()) {\n <span class=\"wr-time-picker__sep\" aria-hidden=\"true\">:</span>\n\n <div class=\"wr-time-picker__col\">\n <button\n type=\"button\"\n class=\"wr-time-picker__step wr-time-picker__step--up\"\n aria-label=\"Increment seconds\"\n [disabled]=\"!interactive()\"\n (click)=\"stepSeconds(1)\"\n >\n <svg\n class=\"wr-time-picker__chevron\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"m18 15-6-6-6 6\" />\n </svg>\n </button>\n <input\n class=\"wr-time-picker__input\"\n type=\"text\"\n inputmode=\"numeric\"\n aria-label=\"Seconds\"\n [value]=\"secondsDisplay()\"\n [disabled]=\"effectiveDisabled()\"\n [readonly]=\"readonly()\"\n (input)=\"onSecondsInput($event)\"\n (blur)=\"onBlur()\"\n />\n <button\n type=\"button\"\n class=\"wr-time-picker__step wr-time-picker__step--down\"\n aria-label=\"Decrement seconds\"\n [disabled]=\"!interactive()\"\n (click)=\"stepSeconds(-1)\"\n >\n <svg\n class=\"wr-time-picker__chevron\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"m6 9 6 6 6-6\" />\n </svg>\n </button>\n </div>\n}\n\n@if (is12h()) {\n <div class=\"wr-time-picker__col wr-time-picker__col--ampm\">\n <button\n type=\"button\"\n class=\"wr-time-picker__step wr-time-picker__step--up\"\n aria-label=\"Toggle AM / PM\"\n [disabled]=\"!interactive()\"\n (click)=\"toggleAmPm()\"\n >\n <svg\n class=\"wr-time-picker__chevron\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"m18 15-6-6-6 6\" />\n </svg>\n </button>\n <span class=\"wr-time-picker__label\" aria-live=\"polite\">{{ amPmLabel() }}</span>\n <button\n type=\"button\"\n class=\"wr-time-picker__step wr-time-picker__step--down\"\n aria-label=\"Toggle AM / PM\"\n [disabled]=\"!interactive()\"\n (click)=\"toggleAmPm()\"\n >\n <svg\n class=\"wr-time-picker__chevron\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"m6 9 6 6 6-6\" />\n </svg>\n </button>\n </div>\n}\n" }]
|
|
216
|
-
}], ctorParameters: () => [], propDecorators: { format: [{ type: i0.Input, args: [{ isSignal: true, alias: "format", required: false }] }], showSeconds: [{ type: i0.Input, args: [{ isSignal: true, alias: "showSeconds", required: false }] }], step: [{ type: i0.Input, args: [{ isSignal: true, alias: "step", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], touch: [{ type: i0.Output, args: ["touch"] }] } });
|
|
228
|
+
}], ctorParameters: () => [], propDecorators: { format: [{ type: i0.Input, args: [{ isSignal: true, alias: "format", required: false }] }], showSeconds: [{ type: i0.Input, args: [{ isSignal: true, alias: "showSeconds", required: false }] }], step: [{ type: i0.Input, args: [{ isSignal: true, alias: "step", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], autoFocus: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoFocus", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], touch: [{ type: i0.Output, args: ["touch"] }] } });
|
|
217
229
|
|
|
218
230
|
/**
|
|
219
231
|
* @license
|
|
@@ -243,6 +255,13 @@ class WrDateTimePanel {
|
|
|
243
255
|
...(ngDevMode ? [{ debugName: "showSeconds" }] : /* istanbul ignore next */ []));
|
|
244
256
|
step = input(1, /* @ts-ignore */
|
|
245
257
|
...(ngDevMode ? [{ debugName: "step" }] : /* istanbul ignore next */ []));
|
|
258
|
+
/**
|
|
259
|
+
* Forwarded to the calendar only: a datetime panel opened from the keyboard
|
|
260
|
+
* lands on the day grid, never on the hours field. Picking the date first is
|
|
261
|
+
* the order the panel is laid out in, and the time fields are one Tab away.
|
|
262
|
+
*/
|
|
263
|
+
autoFocus = input(false, /* @ts-ignore */
|
|
264
|
+
...(ngDevMode ? [{ debugName: "autoFocus" }] : /* istanbul ignore next */ []));
|
|
246
265
|
changed = output();
|
|
247
266
|
adapter = inject(WrDateAdapter);
|
|
248
267
|
/** Date portion used by the calendar — falls back to today when value is null. */
|
|
@@ -265,13 +284,13 @@ class WrDateTimePanel {
|
|
|
265
284
|
const combined = this.adapter.setTime(this.adapter.createDate(this.adapter.getYear(base), this.adapter.getMonth(base), this.adapter.getDate(base)), this.adapter.getHours(time), this.adapter.getMinutes(time), this.adapter.getSeconds(time));
|
|
266
285
|
this.changed.emit(combined);
|
|
267
286
|
}
|
|
268
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.
|
|
269
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.1.
|
|
287
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: WrDateTimePanel, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
288
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.1.1", type: WrDateTimePanel, isStandalone: true, selector: "wr-date-time-panel", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, min: { classPropertyName: "min", publicName: "min", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null }, dateFilter: { classPropertyName: "dateFilter", publicName: "dateFilter", isSignal: true, isRequired: false, transformFunction: null }, timeFormat: { classPropertyName: "timeFormat", publicName: "timeFormat", isSignal: true, isRequired: false, transformFunction: null }, showSeconds: { classPropertyName: "showSeconds", publicName: "showSeconds", isSignal: true, isRequired: false, transformFunction: null }, step: { classPropertyName: "step", publicName: "step", isSignal: true, isRequired: false, transformFunction: null }, autoFocus: { classPropertyName: "autoFocus", publicName: "autoFocus", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { changed: "changed" }, host: { classAttribute: "wr-date-time-panel" }, ngImport: i0, template: "<wr-calendar\n [date]=\"calendarValue()\"\n [min]=\"min()\"\n [max]=\"max()\"\n [dateFilter]=\"dateFilter()\"\n [autoFocus]=\"autoFocus()\"\n (dateChange)=\"onCalendarSelect($event)\"\n/>\n\n<div class=\"wr-date-time-panel__time\">\n <wr-time-picker\n [value]=\"timeValue()\"\n [format]=\"timeFormat()\"\n [showSeconds]=\"showSeconds()\"\n [step]=\"step()\"\n (valueChange)=\"onTimeChange($event)\"\n />\n</div>\n", dependencies: [{ kind: "component", type: WrCalendar, selector: "wr-calendar", inputs: ["mode", "date", "range", "min", "max", "dateFilter", "autoFocus", "disabled"], outputs: ["dateChange", "rangeChange"] }, { kind: "component", type: WrTimePanel, selector: "wr-time-picker", inputs: ["format", "showSeconds", "step", "disabled", "readonly", "autoFocus", "value"], outputs: ["valueChange", "touch"] }], encapsulation: i0.ViewEncapsulation.None });
|
|
270
289
|
}
|
|
271
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.
|
|
290
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: WrDateTimePanel, decorators: [{
|
|
272
291
|
type: Component,
|
|
273
|
-
args: [{ selector: 'wr-date-time-panel', encapsulation: ViewEncapsulation.None, host: { class: 'wr-date-time-panel' }, imports: [
|
|
274
|
-
}], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], min: [{ type: i0.Input, args: [{ isSignal: true, alias: "min", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], dateFilter: [{ type: i0.Input, args: [{ isSignal: true, alias: "dateFilter", required: false }] }], timeFormat: [{ type: i0.Input, args: [{ isSignal: true, alias: "timeFormat", required: false }] }], showSeconds: [{ type: i0.Input, args: [{ isSignal: true, alias: "showSeconds", required: false }] }], step: [{ type: i0.Input, args: [{ isSignal: true, alias: "step", required: false }] }], changed: [{ type: i0.Output, args: ["changed"] }] } });
|
|
292
|
+
args: [{ selector: 'wr-date-time-panel', encapsulation: ViewEncapsulation.None, host: { class: 'wr-date-time-panel' }, imports: [WrCalendar, WrTimePanel], template: "<wr-calendar\n [date]=\"calendarValue()\"\n [min]=\"min()\"\n [max]=\"max()\"\n [dateFilter]=\"dateFilter()\"\n [autoFocus]=\"autoFocus()\"\n (dateChange)=\"onCalendarSelect($event)\"\n/>\n\n<div class=\"wr-date-time-panel__time\">\n <wr-time-picker\n [value]=\"timeValue()\"\n [format]=\"timeFormat()\"\n [showSeconds]=\"showSeconds()\"\n [step]=\"step()\"\n (valueChange)=\"onTimeChange($event)\"\n />\n</div>\n" }]
|
|
293
|
+
}], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], min: [{ type: i0.Input, args: [{ isSignal: true, alias: "min", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], dateFilter: [{ type: i0.Input, args: [{ isSignal: true, alias: "dateFilter", required: false }] }], timeFormat: [{ type: i0.Input, args: [{ isSignal: true, alias: "timeFormat", required: false }] }], showSeconds: [{ type: i0.Input, args: [{ isSignal: true, alias: "showSeconds", required: false }] }], step: [{ type: i0.Input, args: [{ isSignal: true, alias: "step", required: false }] }], autoFocus: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoFocus", required: false }] }], changed: [{ type: i0.Output, args: ["changed"] }] } });
|
|
275
294
|
|
|
276
295
|
/**
|
|
277
296
|
* @license
|
|
@@ -279,6 +298,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
|
|
|
279
298
|
* Use of this source code is governed by an MIT-style license that can be
|
|
280
299
|
* found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE
|
|
281
300
|
*/
|
|
301
|
+
let panelUid = 0;
|
|
282
302
|
/**
|
|
283
303
|
* Unified date / time / date-time picker. Same `<input>` + popover skeleton
|
|
284
304
|
* for every mode — the overlay content swaps based on `[mode]`:
|
|
@@ -372,6 +392,8 @@ class WrDatePicker {
|
|
|
372
392
|
destroyRef = inject(DestroyRef);
|
|
373
393
|
inputEl = viewChild.required('input', /* @ts-ignore */
|
|
374
394
|
...(ngDevMode ? [{ debugName: "inputEl" }] : /* istanbul ignore next */ []));
|
|
395
|
+
triggerEl = viewChild('trigger', /* @ts-ignore */
|
|
396
|
+
...(ngDevMode ? [{ debugName: "triggerEl" }] : /* istanbul ignore next */ []));
|
|
375
397
|
/** The picked Date. Bound by `[formField]`, or two-way via `[(value)]`. */
|
|
376
398
|
value = model(null, /* @ts-ignore */
|
|
377
399
|
...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
|
|
@@ -383,6 +405,14 @@ class WrDatePicker {
|
|
|
383
405
|
/** Whether the popover is currently open. */
|
|
384
406
|
overlayOpen = signal(false, /* @ts-ignore */
|
|
385
407
|
...(ngDevMode ? [{ debugName: "overlayOpen" }] : /* istanbul ignore next */ []));
|
|
408
|
+
/**
|
|
409
|
+
* Where focus came from, captured as an explicit argument at open time —
|
|
410
|
+
* never read off `document.activeElement`, which by then may already be the
|
|
411
|
+
* pane. Every close path that took focus INTO the panel hands it back here.
|
|
412
|
+
*/
|
|
413
|
+
openedFrom = null;
|
|
414
|
+
/** Whether the panel this open cycle should take focus. */
|
|
415
|
+
autoFocusPanel = false;
|
|
386
416
|
/** Resolved format — falls back to a mode-appropriate default. */
|
|
387
417
|
resolvedFormat = computed(() => {
|
|
388
418
|
const explicit = this.format();
|
|
@@ -408,6 +438,9 @@ class WrDatePicker {
|
|
|
408
438
|
labelDate = readI18nText('datePicker.open', 'Open calendar');
|
|
409
439
|
labelTime = readI18nText('datePicker.openTime', 'Open time picker');
|
|
410
440
|
labelDateTime = readI18nText('datePicker.openDateTime', 'Open date and time picker');
|
|
441
|
+
panelLabelDate = readI18nText('datePicker.panel', 'Choose date');
|
|
442
|
+
panelLabelTime = readI18nText('datePicker.panelTime', 'Choose time');
|
|
443
|
+
panelLabelDateTime = readI18nText('datePicker.panelDateTime', 'Choose date and time');
|
|
411
444
|
/**
|
|
412
445
|
* Accessible name of the text field. Falls back to the placeholder, then to
|
|
413
446
|
* the same catalog string the calendar button uses — the field is a
|
|
@@ -434,6 +467,28 @@ class WrDatePicker {
|
|
|
434
467
|
return this.labelDate;
|
|
435
468
|
}, /* @ts-ignore */
|
|
436
469
|
...(ngDevMode ? [{ debugName: "triggerLabel" }] : /* istanbul ignore next */ []));
|
|
470
|
+
/**
|
|
471
|
+
* Accessible name of the popup. The trigger advertises
|
|
472
|
+
* `aria-haspopup="dialog"`, so the panel is a `role="dialog"` — and an unnamed
|
|
473
|
+
* dialog announces as a bare "dialog". Defaults to the catalog's
|
|
474
|
+
* `datePicker.panel*` string for the current `mode`.
|
|
475
|
+
*/
|
|
476
|
+
panelAriaLabel = input(null, /* @ts-ignore */
|
|
477
|
+
...(ngDevMode ? [{ debugName: "panelAriaLabel" }] : /* istanbul ignore next */ []));
|
|
478
|
+
/** Popup id — what the trigger's `aria-controls` points at while open. */
|
|
479
|
+
panelId = `wr-date-picker-panel-${++panelUid}`;
|
|
480
|
+
resolvedPanelLabel = computed(() => {
|
|
481
|
+
const explicit = this.panelAriaLabel();
|
|
482
|
+
if (explicit)
|
|
483
|
+
return explicit;
|
|
484
|
+
const m = this.mode();
|
|
485
|
+
if (m === 'time')
|
|
486
|
+
return this.panelLabelTime;
|
|
487
|
+
if (m === 'datetime')
|
|
488
|
+
return this.panelLabelDateTime;
|
|
489
|
+
return this.panelLabelDate;
|
|
490
|
+
}, /* @ts-ignore */
|
|
491
|
+
...(ngDevMode ? [{ debugName: "resolvedPanelLabel" }] : /* istanbul ignore next */ []));
|
|
437
492
|
classes = computed(() => {
|
|
438
493
|
const parts = ['wr-date-picker', `wr-date-picker--${this.mode()}`];
|
|
439
494
|
if (this.disabled())
|
|
@@ -503,22 +558,69 @@ class WrDatePicker {
|
|
|
503
558
|
openOnInput() {
|
|
504
559
|
if (this.disabled() || this.overlayRef)
|
|
505
560
|
return;
|
|
506
|
-
this
|
|
561
|
+
// Deliberately WITHOUT focus: this click placed a caret in the text field,
|
|
562
|
+
// and yanking focus into the grid would throw away what the user just did.
|
|
563
|
+
// `Alt+ArrowDown` / `ArrowDown` are the keyboard way in — see onFieldKey.
|
|
564
|
+
this.openOverlay(this.inputEl().nativeElement, false);
|
|
507
565
|
}
|
|
508
566
|
toggleOverlay() {
|
|
509
567
|
if (this.disabled())
|
|
510
568
|
return;
|
|
511
569
|
if (this.overlayRef) {
|
|
570
|
+
// Focus is already on the trigger — nothing to restore.
|
|
512
571
|
this.closeOverlay();
|
|
513
572
|
}
|
|
514
573
|
else {
|
|
515
|
-
this.openOverlay();
|
|
574
|
+
this.openOverlay(this.triggerEl()?.nativeElement ?? null, true);
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
/**
|
|
578
|
+
* The keyboard route into the panel, per the APG date-picker pattern.
|
|
579
|
+
*
|
|
580
|
+
* `Alt+ArrowDown` opens and takes focus; a bare arrow moves focus in when the
|
|
581
|
+
* panel is ALREADY open, which is how someone who opened it by clicking the
|
|
582
|
+
* field gets to the grid. Every other key — characters, Backspace, the
|
|
583
|
+
* horizontal arrows, Home / End — is left to the field untouched, so typing a
|
|
584
|
+
* date keeps working exactly as before.
|
|
585
|
+
*/
|
|
586
|
+
onFieldKey(event) {
|
|
587
|
+
if (this.disabled())
|
|
588
|
+
return;
|
|
589
|
+
const vertical = event.key === 'ArrowDown' || event.key === 'ArrowUp';
|
|
590
|
+
if (!vertical)
|
|
591
|
+
return;
|
|
592
|
+
if (event.altKey && event.key === 'ArrowDown' && !this.overlayRef) {
|
|
593
|
+
event.preventDefault();
|
|
594
|
+
this.openOverlay(this.inputEl().nativeElement, true);
|
|
595
|
+
return;
|
|
596
|
+
}
|
|
597
|
+
if (this.overlayRef) {
|
|
598
|
+
event.preventDefault();
|
|
599
|
+
this.focusPanel();
|
|
516
600
|
}
|
|
517
601
|
}
|
|
602
|
+
/**
|
|
603
|
+
* Move focus into an already-mounted panel. A plain query is right HERE and
|
|
604
|
+
* wrong at mount time: the panel has settled, so there is no deferral to get
|
|
605
|
+
* wrong — and it keeps `autoFocus` as the single piece of panel API.
|
|
606
|
+
*/
|
|
607
|
+
focusPanel() {
|
|
608
|
+
const pane = this.overlayRef?.overlayElement;
|
|
609
|
+
const target = pane?.querySelector('.wr-calendar__day--focused:not([disabled])') ??
|
|
610
|
+
pane?.querySelector('.wr-time-picker__input');
|
|
611
|
+
target?.focus();
|
|
612
|
+
}
|
|
613
|
+
/** Hand focus back to whatever opened the panel, if it is still on the page. */
|
|
614
|
+
restoreFocus() {
|
|
615
|
+
const target = this.openedFrom?.isConnected ? this.openedFrom : this.inputEl().nativeElement;
|
|
616
|
+
target?.focus();
|
|
617
|
+
}
|
|
518
618
|
// Overlay
|
|
519
|
-
openOverlay() {
|
|
619
|
+
openOverlay(openedFrom = null, autoFocus = false) {
|
|
520
620
|
if (this.overlayRef)
|
|
521
621
|
return;
|
|
622
|
+
this.openedFrom = openedFrom;
|
|
623
|
+
this.autoFocusPanel = autoFocus;
|
|
522
624
|
const positionStrategy = this.overlay
|
|
523
625
|
.position()
|
|
524
626
|
.flexibleConnectedTo(this.host)
|
|
@@ -534,6 +636,15 @@ class WrDatePicker {
|
|
|
534
636
|
scrollStrategy: this.scrollStrategies.reposition(),
|
|
535
637
|
panelClass: 'wr-date-picker-overlay',
|
|
536
638
|
});
|
|
639
|
+
// The trigger promises `aria-haspopup="dialog"`; the pane is the element it
|
|
640
|
+
// points at, so the role, the name and the id all belong here. Non-modal on
|
|
641
|
+
// purpose — focus is not trapped, and the panel closes on outside click or
|
|
642
|
+
// Escape.
|
|
643
|
+
const pane = this.overlayRef.overlayElement;
|
|
644
|
+
pane.id = this.panelId;
|
|
645
|
+
pane.setAttribute('role', 'dialog');
|
|
646
|
+
pane.setAttribute('aria-modal', 'false');
|
|
647
|
+
pane.setAttribute('aria-label', this.resolvedPanelLabel());
|
|
537
648
|
this.overlayOpen.set(true);
|
|
538
649
|
// Dispatch by mode — pick the panel + wire its emissions.
|
|
539
650
|
const m = this.mode();
|
|
@@ -571,6 +682,7 @@ class WrDatePicker {
|
|
|
571
682
|
ref.setInput('min', this.min());
|
|
572
683
|
ref.setInput('max', this.max());
|
|
573
684
|
ref.setInput('dateFilter', this.dateFilter());
|
|
685
|
+
ref.setInput('autoFocus', this.autoFocusPanel);
|
|
574
686
|
ref.instance.date.subscribe(next => {
|
|
575
687
|
if (!next)
|
|
576
688
|
return;
|
|
@@ -585,6 +697,7 @@ class WrDatePicker {
|
|
|
585
697
|
ref.setInput('format', this.timeFormat());
|
|
586
698
|
ref.setInput('showSeconds', this.showSeconds());
|
|
587
699
|
ref.setInput('step', this.step());
|
|
700
|
+
ref.setInput('autoFocus', this.autoFocusPanel);
|
|
588
701
|
ref.setInput('value', this.value() ?? this.adapter.today());
|
|
589
702
|
ref.instance.value.subscribe((next) => {
|
|
590
703
|
if (!next)
|
|
@@ -603,6 +716,7 @@ class WrDatePicker {
|
|
|
603
716
|
ref.setInput('timeFormat', this.timeFormat());
|
|
604
717
|
ref.setInput('showSeconds', this.showSeconds());
|
|
605
718
|
ref.setInput('step', this.step());
|
|
719
|
+
ref.setInput('autoFocus', this.autoFocusPanel);
|
|
606
720
|
ref.instance.changed.subscribe(next => {
|
|
607
721
|
this.commit(next);
|
|
608
722
|
ref.setInput('value', next);
|
|
@@ -618,8 +732,19 @@ class WrDatePicker {
|
|
|
618
732
|
this.lastValue = next;
|
|
619
733
|
this.value.set(next);
|
|
620
734
|
}
|
|
735
|
+
/**
|
|
736
|
+
* Focus goes back to the opener only when it was still INSIDE the panel —
|
|
737
|
+
* dropped there, it would land on `<body>` and the next Tab would restart
|
|
738
|
+
* from the top of the page. When focus had already moved elsewhere (a click
|
|
739
|
+
* on another control, which is itself what closed us) it is left alone:
|
|
740
|
+
* stealing it back would fight the user for the caret.
|
|
741
|
+
*/
|
|
621
742
|
closeOverlay() {
|
|
743
|
+
const pane = this.overlayRef?.overlayElement;
|
|
744
|
+
const inside = !!pane && pane.contains(document.activeElement);
|
|
622
745
|
this.dispose();
|
|
746
|
+
if (inside)
|
|
747
|
+
this.restoreFocus();
|
|
623
748
|
}
|
|
624
749
|
dispose() {
|
|
625
750
|
if (this.overlayRef) {
|
|
@@ -628,6 +753,7 @@ class WrDatePicker {
|
|
|
628
753
|
}
|
|
629
754
|
this.dateRef.set(null);
|
|
630
755
|
this.overlayOpen.set(false);
|
|
756
|
+
this.autoFocusPanel = false;
|
|
631
757
|
}
|
|
632
758
|
// Helpers
|
|
633
759
|
isOutOfBounds(date) {
|
|
@@ -648,13 +774,13 @@ class WrDatePicker {
|
|
|
648
774
|
return false;
|
|
649
775
|
return a.getTime() === b.getTime();
|
|
650
776
|
}
|
|
651
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.
|
|
652
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.
|
|
777
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: WrDatePicker, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
778
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.1", type: WrDatePicker, isStandalone: true, selector: "wr-date-picker", inputs: { mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: false, transformFunction: null }, format: { classPropertyName: "format", publicName: "format", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, min: { classPropertyName: "min", publicName: "min", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null }, dateFilter: { classPropertyName: "dateFilter", publicName: "dateFilter", isSignal: true, isRequired: false, transformFunction: null }, timeFormat: { classPropertyName: "timeFormat", publicName: "timeFormat", isSignal: true, isRequired: false, transformFunction: null }, showSeconds: { classPropertyName: "showSeconds", publicName: "showSeconds", isSignal: true, isRequired: false, transformFunction: null }, step: { classPropertyName: "step", publicName: "step", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, panelAriaLabel: { classPropertyName: "panelAriaLabel", publicName: "panelAriaLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", touch: "touch" }, host: { properties: { "class": "classes()" } }, viewQueries: [{ propertyName: "inputEl", first: true, predicate: ["input"], descendants: true, isSignal: true }, { propertyName: "triggerEl", first: true, predicate: ["trigger"], descendants: true, isSignal: true }], ngImport: i0, template: "<wr-input-group>\n <!--\n A plain textbox, per the APG date-picker-dialog pattern: `aria-haspopup` and\n `aria-expanded` are not permitted on `textbox` and belong to the trigger\n button below, which is the thing that opens the dialog.\n -->\n <input\n #input\n wrInput\n type=\"text\"\n autocomplete=\"off\"\n spellcheck=\"false\"\n [attr.aria-label]=\"resolvedAriaLabel()\"\n [value]=\"text()\"\n [placeholder]=\"placeholder()\"\n [disabled]=\"disabled()\"\n [readonly]=\"readonly()\"\n (click)=\"openOnInput()\"\n (keydown)=\"onFieldKey($event)\"\n (input)=\"onInput($event)\"\n (blur)=\"onBlur()\"\n />\n <button\n #trigger\n type=\"button\"\n wrInputSuffix\n class=\"wr-date-picker__trigger\"\n [attr.aria-label]=\"triggerLabel()\"\n [attr.aria-haspopup]=\"'dialog'\"\n [attr.aria-expanded]=\"overlayOpen()\"\n [attr.aria-controls]=\"overlayOpen() ? panelId : null\"\n [disabled]=\"disabled()\"\n (click)=\"toggleOverlay()\"\n >\n @if (isTime()) {\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 14 14\" aria-hidden=\"true\">\n <circle cx=\"7\" cy=\"7\" r=\"5.75\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.2\" />\n <line x1=\"7\" y1=\"7\" x2=\"7\" y2=\"3.75\" stroke=\"currentColor\" stroke-width=\"1.4\" stroke-linecap=\"round\" />\n <line x1=\"7\" y1=\"7\" x2=\"9.5\" y2=\"7\" stroke=\"currentColor\" stroke-width=\"1.4\" stroke-linecap=\"round\" />\n </svg>\n } @else {\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 14 14\" aria-hidden=\"true\">\n <rect x=\"1.5\" y=\"2.5\" width=\"11\" height=\"10\" rx=\"1.5\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.2\" />\n <line x1=\"1.5\" y1=\"5.5\" x2=\"12.5\" y2=\"5.5\" stroke=\"currentColor\" stroke-width=\"1.2\" />\n <line x1=\"4\" y1=\"1\" x2=\"4\" y2=\"4\" stroke=\"currentColor\" stroke-width=\"1.4\" stroke-linecap=\"round\" />\n <line x1=\"10\" y1=\"1\" x2=\"10\" y2=\"4\" stroke=\"currentColor\" stroke-width=\"1.4\" stroke-linecap=\"round\" />\n @if (isDateTime()) {\n <circle cx=\"7\" cy=\"9\" r=\"1.5\" fill=\"currentColor\" />\n }\n </svg>\n }\n </button>\n</wr-input-group>\n", dependencies: [{ kind: "directive", type: WrInput, selector: "input[wrInput], textarea[wrInput]", inputs: ["wrSize", "rounded"] }, { kind: "component", type: WrInputGroup, selector: "wr-input-group", inputs: ["rounded"] }, { kind: "directive", type: WrInputSuffix, selector: "[wrInputSuffix]" }], encapsulation: i0.ViewEncapsulation.None });
|
|
653
779
|
}
|
|
654
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.
|
|
780
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: WrDatePicker, decorators: [{
|
|
655
781
|
type: Component,
|
|
656
|
-
args: [{ selector: 'wr-date-picker', encapsulation: ViewEncapsulation.None, host: { '[class]': 'classes()' }, imports: [WrInput, WrInputGroup, WrInputSuffix], template: "<wr-input-group>\n <!--\n A plain textbox, per the APG date-picker-dialog pattern: `aria-haspopup` and\n `aria-expanded` are not permitted on `textbox` and belong to the trigger\n button below, which is the thing that opens the dialog.\n -->\n <input\n #input\n wrInput\n type=\"text\"\n autocomplete=\"off\"\n spellcheck=\"false\"\n [attr.aria-label]=\"resolvedAriaLabel()\"\n [value]=\"text()\"\n [placeholder]=\"placeholder()\"\n [disabled]=\"disabled()\"\n [readonly]=\"readonly()\"\n (click)=\"openOnInput()\"\n (input)=\"onInput($event)\"\n (blur)=\"onBlur()\"\n />\n <button\n type=\"button\"\n wrInputSuffix\n class=\"wr-date-picker__trigger\"\n [attr.aria-label]=\"triggerLabel()\"\n [attr.aria-haspopup]=\"'dialog'\"\n [attr.aria-expanded]=\"overlayOpen()\"\n [disabled]=\"disabled()\"\n (click)=\"toggleOverlay()\"\n >\n @if (isTime()) {\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 14 14\" aria-hidden=\"true\">\n <circle cx=\"7\" cy=\"7\" r=\"5.75\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.2\" />\n <line x1=\"7\" y1=\"7\" x2=\"7\" y2=\"3.75\" stroke=\"currentColor\" stroke-width=\"1.4\" stroke-linecap=\"round\" />\n <line x1=\"7\" y1=\"7\" x2=\"9.5\" y2=\"7\" stroke=\"currentColor\" stroke-width=\"1.4\" stroke-linecap=\"round\" />\n </svg>\n } @else {\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 14 14\" aria-hidden=\"true\">\n <rect x=\"1.5\" y=\"2.5\" width=\"11\" height=\"10\" rx=\"1.5\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.2\" />\n <line x1=\"1.5\" y1=\"5.5\" x2=\"12.5\" y2=\"5.5\" stroke=\"currentColor\" stroke-width=\"1.2\" />\n <line x1=\"4\" y1=\"1\" x2=\"4\" y2=\"4\" stroke=\"currentColor\" stroke-width=\"1.4\" stroke-linecap=\"round\" />\n <line x1=\"10\" y1=\"1\" x2=\"10\" y2=\"4\" stroke=\"currentColor\" stroke-width=\"1.4\" stroke-linecap=\"round\" />\n @if (isDateTime()) {\n <circle cx=\"7\" cy=\"9\" r=\"1.5\" fill=\"currentColor\" />\n }\n </svg>\n }\n </button>\n</wr-input-group>\n" }]
|
|
657
|
-
}], ctorParameters: () => [], propDecorators: { mode: [{ type: i0.Input, args: [{ isSignal: true, alias: "mode", required: false }] }], format: [{ type: i0.Input, args: [{ isSignal: true, alias: "format", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], min: [{ type: i0.Input, args: [{ isSignal: true, alias: "min", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], dateFilter: [{ type: i0.Input, args: [{ isSignal: true, alias: "dateFilter", required: false }] }], timeFormat: [{ type: i0.Input, args: [{ isSignal: true, alias: "timeFormat", required: false }] }], showSeconds: [{ type: i0.Input, args: [{ isSignal: true, alias: "showSeconds", required: false }] }], step: [{ type: i0.Input, args: [{ isSignal: true, alias: "step", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], inputEl: [{ type: i0.ViewChild, args: ['input', { isSignal: true }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], touch: [{ type: i0.Output, args: ["touch"] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }] } });
|
|
782
|
+
args: [{ selector: 'wr-date-picker', encapsulation: ViewEncapsulation.None, host: { '[class]': 'classes()' }, imports: [WrInput, WrInputGroup, WrInputSuffix], template: "<wr-input-group>\n <!--\n A plain textbox, per the APG date-picker-dialog pattern: `aria-haspopup` and\n `aria-expanded` are not permitted on `textbox` and belong to the trigger\n button below, which is the thing that opens the dialog.\n -->\n <input\n #input\n wrInput\n type=\"text\"\n autocomplete=\"off\"\n spellcheck=\"false\"\n [attr.aria-label]=\"resolvedAriaLabel()\"\n [value]=\"text()\"\n [placeholder]=\"placeholder()\"\n [disabled]=\"disabled()\"\n [readonly]=\"readonly()\"\n (click)=\"openOnInput()\"\n (keydown)=\"onFieldKey($event)\"\n (input)=\"onInput($event)\"\n (blur)=\"onBlur()\"\n />\n <button\n #trigger\n type=\"button\"\n wrInputSuffix\n class=\"wr-date-picker__trigger\"\n [attr.aria-label]=\"triggerLabel()\"\n [attr.aria-haspopup]=\"'dialog'\"\n [attr.aria-expanded]=\"overlayOpen()\"\n [attr.aria-controls]=\"overlayOpen() ? panelId : null\"\n [disabled]=\"disabled()\"\n (click)=\"toggleOverlay()\"\n >\n @if (isTime()) {\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 14 14\" aria-hidden=\"true\">\n <circle cx=\"7\" cy=\"7\" r=\"5.75\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.2\" />\n <line x1=\"7\" y1=\"7\" x2=\"7\" y2=\"3.75\" stroke=\"currentColor\" stroke-width=\"1.4\" stroke-linecap=\"round\" />\n <line x1=\"7\" y1=\"7\" x2=\"9.5\" y2=\"7\" stroke=\"currentColor\" stroke-width=\"1.4\" stroke-linecap=\"round\" />\n </svg>\n } @else {\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 14 14\" aria-hidden=\"true\">\n <rect x=\"1.5\" y=\"2.5\" width=\"11\" height=\"10\" rx=\"1.5\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.2\" />\n <line x1=\"1.5\" y1=\"5.5\" x2=\"12.5\" y2=\"5.5\" stroke=\"currentColor\" stroke-width=\"1.2\" />\n <line x1=\"4\" y1=\"1\" x2=\"4\" y2=\"4\" stroke=\"currentColor\" stroke-width=\"1.4\" stroke-linecap=\"round\" />\n <line x1=\"10\" y1=\"1\" x2=\"10\" y2=\"4\" stroke=\"currentColor\" stroke-width=\"1.4\" stroke-linecap=\"round\" />\n @if (isDateTime()) {\n <circle cx=\"7\" cy=\"9\" r=\"1.5\" fill=\"currentColor\" />\n }\n </svg>\n }\n </button>\n</wr-input-group>\n" }]
|
|
783
|
+
}], ctorParameters: () => [], propDecorators: { mode: [{ type: i0.Input, args: [{ isSignal: true, alias: "mode", required: false }] }], format: [{ type: i0.Input, args: [{ isSignal: true, alias: "format", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], min: [{ type: i0.Input, args: [{ isSignal: true, alias: "min", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], dateFilter: [{ type: i0.Input, args: [{ isSignal: true, alias: "dateFilter", required: false }] }], timeFormat: [{ type: i0.Input, args: [{ isSignal: true, alias: "timeFormat", required: false }] }], showSeconds: [{ type: i0.Input, args: [{ isSignal: true, alias: "showSeconds", required: false }] }], step: [{ type: i0.Input, args: [{ isSignal: true, alias: "step", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], inputEl: [{ type: i0.ViewChild, args: ['input', { isSignal: true }] }], triggerEl: [{ type: i0.ViewChild, args: ['trigger', { isSignal: true }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], touch: [{ type: i0.Output, args: ["touch"] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], panelAriaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "panelAriaLabel", required: false }] }] } });
|
|
658
784
|
|
|
659
785
|
/**
|
|
660
786
|
* @license
|
|
@@ -671,6 +797,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
|
|
|
671
797
|
* preview); this panel only re-attaches the times so a datetime range keeps the
|
|
672
798
|
* hours the user set while the dates move around.
|
|
673
799
|
*
|
|
800
|
+
* The steppers bind `WrTimePanel`'s own `value` model, NOT `[ngModel]`. The
|
|
801
|
+
* classic-forms bridge defers its first write, and under zoneless change
|
|
802
|
+
* detection that write lost the race with the panel's own echo guard: a range
|
|
803
|
+
* bound to 16:00–17:00 opened showing 00:00 on both ends, and the first click
|
|
804
|
+
* on a stepper committed midnight-plus-one over the real value.
|
|
805
|
+
*
|
|
674
806
|
* @internal
|
|
675
807
|
*/
|
|
676
808
|
class WrDateRangePanel {
|
|
@@ -690,7 +822,24 @@ class WrDateRangePanel {
|
|
|
690
822
|
...(ngDevMode ? [{ debugName: "showSeconds" }] : /* istanbul ignore next */ []));
|
|
691
823
|
step = input(1, /* @ts-ignore */
|
|
692
824
|
...(ngDevMode ? [{ debugName: "step" }] : /* istanbul ignore next */ []));
|
|
825
|
+
/**
|
|
826
|
+
* Forwarded to the calendar only: a range panel opened from the keyboard
|
|
827
|
+
* lands on the day grid, never on a time field. Dates come first here — the
|
|
828
|
+
* steppers are for the range you have already drawn.
|
|
829
|
+
*/
|
|
830
|
+
autoFocus = input(false, /* @ts-ignore */
|
|
831
|
+
...(ngDevMode ? [{ debugName: "autoFocus" }] : /* istanbul ignore next */ []));
|
|
832
|
+
/** A range change from the CALENDAR — the picker is free to sort it. */
|
|
693
833
|
changed = output();
|
|
834
|
+
/**
|
|
835
|
+
* A range change from one of the TIME steppers, which must NOT be sorted while
|
|
836
|
+
* it is happening. The steppers are bound to positions, so sorting mid-edit
|
|
837
|
+
* moves the user's change to the other end: raising a same-day start past the
|
|
838
|
+
* end left the start stuck and pushed the END up instead, once per click, on
|
|
839
|
+
* the button the user was not pressing. Ordering is settled when the popup
|
|
840
|
+
* closes — the same rule the text inputs already follow for typing.
|
|
841
|
+
*/
|
|
842
|
+
timeChanged = output();
|
|
694
843
|
adapter = inject(WrDateAdapter);
|
|
695
844
|
startTimeLabel = readI18nText('datePicker.startTime', 'Start time');
|
|
696
845
|
endTimeLabel = readI18nText('datePicker.endTime', 'End time');
|
|
@@ -745,7 +894,7 @@ class WrDateRangePanel {
|
|
|
745
894
|
this.pendingStartTime.set(time);
|
|
746
895
|
return;
|
|
747
896
|
}
|
|
748
|
-
this.
|
|
897
|
+
this.timeChanged.emit([this.withTimeOf(start, time), end]);
|
|
749
898
|
}
|
|
750
899
|
onEndTimeChange(time) {
|
|
751
900
|
if (!time)
|
|
@@ -755,7 +904,7 @@ class WrDateRangePanel {
|
|
|
755
904
|
this.pendingEndTime.set(time);
|
|
756
905
|
return;
|
|
757
906
|
}
|
|
758
|
-
this.
|
|
907
|
+
this.timeChanged.emit([start, this.withTimeOf(end, time)]);
|
|
759
908
|
}
|
|
760
909
|
/** `date`'s calendar day carrying `time`'s clock. Null date stays null. */
|
|
761
910
|
withTimeOf(date, time) {
|
|
@@ -765,13 +914,13 @@ class WrDateRangePanel {
|
|
|
765
914
|
return date;
|
|
766
915
|
return this.adapter.setTime(this.adapter.createDate(this.adapter.getYear(date), this.adapter.getMonth(date), this.adapter.getDate(date)), this.adapter.getHours(time), this.adapter.getMinutes(time), this.adapter.getSeconds(time));
|
|
767
916
|
}
|
|
768
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.
|
|
769
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.
|
|
917
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: WrDateRangePanel, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
918
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.1", type: WrDateRangePanel, isStandalone: true, selector: "wr-date-range-panel", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, min: { classPropertyName: "min", publicName: "min", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null }, dateFilter: { classPropertyName: "dateFilter", publicName: "dateFilter", isSignal: true, isRequired: false, transformFunction: null }, withTime: { classPropertyName: "withTime", publicName: "withTime", isSignal: true, isRequired: false, transformFunction: null }, timeFormat: { classPropertyName: "timeFormat", publicName: "timeFormat", isSignal: true, isRequired: false, transformFunction: null }, showSeconds: { classPropertyName: "showSeconds", publicName: "showSeconds", isSignal: true, isRequired: false, transformFunction: null }, step: { classPropertyName: "step", publicName: "step", isSignal: true, isRequired: false, transformFunction: null }, autoFocus: { classPropertyName: "autoFocus", publicName: "autoFocus", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { changed: "changed", timeChanged: "timeChanged" }, host: { classAttribute: "wr-date-range-panel" }, ngImport: i0, template: "<wr-calendar\n mode=\"range\"\n [range]=\"calendarRange()\"\n [min]=\"min()\"\n [max]=\"max()\"\n [dateFilter]=\"dateFilter()\"\n [autoFocus]=\"autoFocus()\"\n (rangeChange)=\"onRangeChange($event)\"\n/>\n\n@if (withTime()) {\n <div class=\"wr-date-range-panel__times\">\n <!-- A time picker is several inputs, not one control, so each end is a\n labelled group rather than a <label>. -->\n <div class=\"wr-date-range-panel__time\" role=\"group\" [attr.aria-label]=\"startTimeLabel\">\n <span class=\"wr-date-range-panel__time-label\" aria-hidden=\"true\">{{ startTimeLabel }}</span>\n <wr-time-picker\n [value]=\"startTime()\"\n [format]=\"timeFormat()\"\n [showSeconds]=\"showSeconds()\"\n [step]=\"step()\"\n (valueChange)=\"onStartTimeChange($event)\"\n />\n </div>\n <div class=\"wr-date-range-panel__time\" role=\"group\" [attr.aria-label]=\"endTimeLabel\">\n <span class=\"wr-date-range-panel__time-label\" aria-hidden=\"true\">{{ endTimeLabel }}</span>\n <wr-time-picker\n [value]=\"endTime()\"\n [format]=\"timeFormat()\"\n [showSeconds]=\"showSeconds()\"\n [step]=\"step()\"\n (valueChange)=\"onEndTimeChange($event)\"\n />\n </div>\n </div>\n}\n", dependencies: [{ kind: "component", type: WrCalendar, selector: "wr-calendar", inputs: ["mode", "date", "range", "min", "max", "dateFilter", "autoFocus", "disabled"], outputs: ["dateChange", "rangeChange"] }, { kind: "component", type: WrTimePanel, selector: "wr-time-picker", inputs: ["format", "showSeconds", "step", "disabled", "readonly", "autoFocus", "value"], outputs: ["valueChange", "touch"] }], encapsulation: i0.ViewEncapsulation.None });
|
|
770
919
|
}
|
|
771
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.
|
|
920
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: WrDateRangePanel, decorators: [{
|
|
772
921
|
type: Component,
|
|
773
|
-
args: [{ selector: 'wr-date-range-panel', encapsulation: ViewEncapsulation.None, host: { class: 'wr-date-range-panel' }, imports: [
|
|
774
|
-
}], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], min: [{ type: i0.Input, args: [{ isSignal: true, alias: "min", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], dateFilter: [{ type: i0.Input, args: [{ isSignal: true, alias: "dateFilter", required: false }] }], withTime: [{ type: i0.Input, args: [{ isSignal: true, alias: "withTime", required: false }] }], timeFormat: [{ type: i0.Input, args: [{ isSignal: true, alias: "timeFormat", required: false }] }], showSeconds: [{ type: i0.Input, args: [{ isSignal: true, alias: "showSeconds", required: false }] }], step: [{ type: i0.Input, args: [{ isSignal: true, alias: "step", required: false }] }], changed: [{ type: i0.Output, args: ["changed"] }] } });
|
|
922
|
+
args: [{ selector: 'wr-date-range-panel', encapsulation: ViewEncapsulation.None, host: { class: 'wr-date-range-panel' }, imports: [WrCalendar, WrTimePanel], template: "<wr-calendar\n mode=\"range\"\n [range]=\"calendarRange()\"\n [min]=\"min()\"\n [max]=\"max()\"\n [dateFilter]=\"dateFilter()\"\n [autoFocus]=\"autoFocus()\"\n (rangeChange)=\"onRangeChange($event)\"\n/>\n\n@if (withTime()) {\n <div class=\"wr-date-range-panel__times\">\n <!-- A time picker is several inputs, not one control, so each end is a\n labelled group rather than a <label>. -->\n <div class=\"wr-date-range-panel__time\" role=\"group\" [attr.aria-label]=\"startTimeLabel\">\n <span class=\"wr-date-range-panel__time-label\" aria-hidden=\"true\">{{ startTimeLabel }}</span>\n <wr-time-picker\n [value]=\"startTime()\"\n [format]=\"timeFormat()\"\n [showSeconds]=\"showSeconds()\"\n [step]=\"step()\"\n (valueChange)=\"onStartTimeChange($event)\"\n />\n </div>\n <div class=\"wr-date-range-panel__time\" role=\"group\" [attr.aria-label]=\"endTimeLabel\">\n <span class=\"wr-date-range-panel__time-label\" aria-hidden=\"true\">{{ endTimeLabel }}</span>\n <wr-time-picker\n [value]=\"endTime()\"\n [format]=\"timeFormat()\"\n [showSeconds]=\"showSeconds()\"\n [step]=\"step()\"\n (valueChange)=\"onEndTimeChange($event)\"\n />\n </div>\n </div>\n}\n" }]
|
|
923
|
+
}], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], min: [{ type: i0.Input, args: [{ isSignal: true, alias: "min", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], dateFilter: [{ type: i0.Input, args: [{ isSignal: true, alias: "dateFilter", required: false }] }], withTime: [{ type: i0.Input, args: [{ isSignal: true, alias: "withTime", required: false }] }], timeFormat: [{ type: i0.Input, args: [{ isSignal: true, alias: "timeFormat", required: false }] }], showSeconds: [{ type: i0.Input, args: [{ isSignal: true, alias: "showSeconds", required: false }] }], step: [{ type: i0.Input, args: [{ isSignal: true, alias: "step", required: false }] }], autoFocus: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoFocus", required: false }] }], changed: [{ type: i0.Output, args: ["changed"] }], timeChanged: [{ type: i0.Output, args: ["timeChanged"] }] } });
|
|
775
924
|
|
|
776
925
|
/**
|
|
777
926
|
* @license
|
|
@@ -779,6 +928,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
|
|
|
779
928
|
* Use of this source code is governed by an MIT-style license that can be
|
|
780
929
|
* found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE
|
|
781
930
|
*/
|
|
931
|
+
/** Per-instance popup ids, so `aria-controls` can point at one panel. */
|
|
932
|
+
let rangePanelUid = 0;
|
|
782
933
|
/**
|
|
783
934
|
* Date-range picker — two text inputs sharing one range calendar.
|
|
784
935
|
*
|
|
@@ -861,7 +1012,17 @@ class WrDateRangePicker {
|
|
|
861
1012
|
* @default false
|
|
862
1013
|
*/
|
|
863
1014
|
disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: coerceBooleanProperty });
|
|
864
|
-
/**
|
|
1015
|
+
/**
|
|
1016
|
+
* Read-only — neither the inputs nor the calendar can change the value.
|
|
1017
|
+
*
|
|
1018
|
+
* Deliberately stricter than `wr-date-picker`, which still opens its popup
|
|
1019
|
+
* while read-only: with two fields feeding one calendar there is no reading
|
|
1020
|
+
* of "untypeable" that leaves the grid free to rewrite both ends. The doc
|
|
1021
|
+
* used to promise the trigger still opened; the code has always refused, and
|
|
1022
|
+
* refusing is the behaviour worth keeping.
|
|
1023
|
+
*
|
|
1024
|
+
* @default false
|
|
1025
|
+
*/
|
|
865
1026
|
readonly = input(false, { ...(ngDevMode ? { debugName: "readonly" } : /* istanbul ignore next */ {}), transform: coerceBooleanProperty });
|
|
866
1027
|
adapter = inject(WrDateAdapter);
|
|
867
1028
|
overlay = inject(WR_OVERLAY);
|
|
@@ -871,6 +1032,10 @@ class WrDateRangePicker {
|
|
|
871
1032
|
destroyRef = inject(DestroyRef);
|
|
872
1033
|
startEl = viewChild.required('startInput', /* @ts-ignore */
|
|
873
1034
|
...(ngDevMode ? [{ debugName: "startEl" }] : /* istanbul ignore next */ []));
|
|
1035
|
+
endEl = viewChild.required('endInput', /* @ts-ignore */
|
|
1036
|
+
...(ngDevMode ? [{ debugName: "endEl" }] : /* istanbul ignore next */ []));
|
|
1037
|
+
triggerEl = viewChild('trigger', /* @ts-ignore */
|
|
1038
|
+
...(ngDevMode ? [{ debugName: "triggerEl" }] : /* istanbul ignore next */ []));
|
|
874
1039
|
/** The picked range. Bound by `[formField]`, or two-way via `[(value)]`. */
|
|
875
1040
|
value = model(null, /* @ts-ignore */
|
|
876
1041
|
...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
|
|
@@ -884,6 +1049,16 @@ class WrDateRangePicker {
|
|
|
884
1049
|
/** Whether the popover is currently open. */
|
|
885
1050
|
overlayOpen = signal(false, /* @ts-ignore */
|
|
886
1051
|
...(ngDevMode ? [{ debugName: "overlayOpen" }] : /* istanbul ignore next */ []));
|
|
1052
|
+
/**
|
|
1053
|
+
* Which element opened the popup — captured as an argument, never read off
|
|
1054
|
+
* `document.activeElement`, which by then may already be the pane. With TWO
|
|
1055
|
+
* text inputs this is the whole point: "restore focus to the field" is
|
|
1056
|
+
* ambiguous here, and the old code resolved it by always picking the START
|
|
1057
|
+
* input, so closing from the end input threw the caret across the control.
|
|
1058
|
+
*/
|
|
1059
|
+
openedFrom = null;
|
|
1060
|
+
/** Whether the panel this open cycle should take focus. */
|
|
1061
|
+
autoFocusPanel = false;
|
|
887
1062
|
/** Resolved format — falls back to a mode-appropriate default. */
|
|
888
1063
|
resolvedFormat = computed(() => this.format() ? String(this.format()) : this.mode() === 'datetime' ? 'shortDateTime' : 'shortDate', /* @ts-ignore */
|
|
889
1064
|
...(ngDevMode ? [{ debugName: "resolvedFormat" }] : /* istanbul ignore next */ []));
|
|
@@ -892,6 +1067,25 @@ class WrDateRangePicker {
|
|
|
892
1067
|
triggerLabel = readI18nText('datePicker.openRange', 'Open range calendar');
|
|
893
1068
|
startLabel = readI18nText('datePicker.rangeStart', 'Range start');
|
|
894
1069
|
endLabel = readI18nText('datePicker.rangeEnd', 'Range end');
|
|
1070
|
+
panelLabelRange = readI18nText('datePicker.panelRange', 'Choose date range');
|
|
1071
|
+
panelLabelRangeDateTime = readI18nText('datePicker.panelRangeDateTime', 'Choose date and time range');
|
|
1072
|
+
/**
|
|
1073
|
+
* Accessible name of the popup. The trigger advertises
|
|
1074
|
+
* `aria-haspopup="dialog"`, so the pane is a `role="dialog"` — and an unnamed
|
|
1075
|
+
* dialog announces as a bare "dialog". Defaults to the catalog's
|
|
1076
|
+
* `datePicker.panelRange*` string for the current `mode`.
|
|
1077
|
+
*/
|
|
1078
|
+
panelAriaLabel = input(null, /* @ts-ignore */
|
|
1079
|
+
...(ngDevMode ? [{ debugName: "panelAriaLabel" }] : /* istanbul ignore next */ []));
|
|
1080
|
+
/** Popup id — what the trigger's `aria-controls` points at while open. */
|
|
1081
|
+
panelId = `wr-date-range-picker-panel-${++rangePanelUid}`;
|
|
1082
|
+
resolvedPanelLabel = computed(() => {
|
|
1083
|
+
const explicit = this.panelAriaLabel();
|
|
1084
|
+
if (explicit)
|
|
1085
|
+
return explicit;
|
|
1086
|
+
return this.isDateTime() ? this.panelLabelRangeDateTime : this.panelLabelRange;
|
|
1087
|
+
}, /* @ts-ignore */
|
|
1088
|
+
...(ngDevMode ? [{ debugName: "resolvedPanelLabel" }] : /* istanbul ignore next */ []));
|
|
895
1089
|
classes = computed(() => {
|
|
896
1090
|
const parts = ['wr-date-range-picker', `wr-date-range-picker--${this.mode()}`];
|
|
897
1091
|
if (this.disabled())
|
|
@@ -954,30 +1148,83 @@ class WrDateRangePicker {
|
|
|
954
1148
|
// control as touched once focus actually leaves it, or tabbing from start to
|
|
955
1149
|
// end would mark a bound field touched mid-entry.
|
|
956
1150
|
const next = event.relatedTarget;
|
|
957
|
-
|
|
1151
|
+
const leaving = !next || !this.host.nativeElement.contains(next);
|
|
1152
|
+
if (leaving)
|
|
958
1153
|
this.touch.emit();
|
|
959
1154
|
// Typing leaves the ends in whatever order they were entered; settle it
|
|
960
|
-
// here, once the user has stopped
|
|
961
|
-
|
|
1155
|
+
// here, once the user has stopped — and moving from one end to the other is
|
|
1156
|
+
// NOT stopping. Sorting on that hop took the start date the user had just
|
|
1157
|
+
// typed and moved it into the field they were tabbing into, leaving the old
|
|
1158
|
+
// end date under their cursor. Same rule the time steppers follow.
|
|
1159
|
+
const [start, end] = this.commitRange(this.current(), { normalise: leaving });
|
|
962
1160
|
// Reformat to canonical on blur (cleans up `1/5/25` → `1/5/2025`).
|
|
963
1161
|
this.startText.set(this.display(start));
|
|
964
1162
|
this.endText.set(this.display(end));
|
|
965
1163
|
}
|
|
966
1164
|
/** Called by an input's click — opens the overlay if it isn't open already. */
|
|
967
|
-
openOnInput() {
|
|
1165
|
+
openOnInput(end) {
|
|
968
1166
|
if (this.disabled() || this.readonly() || this.overlayRef)
|
|
969
1167
|
return;
|
|
970
|
-
this
|
|
1168
|
+
// Deliberately WITHOUT focus: this click placed a caret in one of the text
|
|
1169
|
+
// fields, and pulling focus into the grid would throw that away.
|
|
1170
|
+
// `Alt+ArrowDown` / `ArrowDown` are the keyboard way in — see onFieldKey.
|
|
1171
|
+
this.openOverlay(this.fieldEl(end), false);
|
|
971
1172
|
}
|
|
972
1173
|
toggleOverlay() {
|
|
973
1174
|
// `readonly` blocks the calendar too — otherwise the inputs refuse typing
|
|
974
1175
|
// while the popover happily edits the same value.
|
|
975
1176
|
if (this.disabled() || this.readonly())
|
|
976
1177
|
return;
|
|
977
|
-
if (this.overlayRef)
|
|
1178
|
+
if (this.overlayRef) {
|
|
1179
|
+
// Focus is already on the trigger — nothing to restore.
|
|
978
1180
|
this.closeOverlay();
|
|
979
|
-
|
|
980
|
-
|
|
1181
|
+
}
|
|
1182
|
+
else {
|
|
1183
|
+
this.openOverlay(this.triggerEl()?.nativeElement ?? null, true);
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
/**
|
|
1187
|
+
* The keyboard route into the popup, per the APG date-picker pattern, and the
|
|
1188
|
+
* only one either field has. `Alt+ArrowDown` opens and takes focus; a bare
|
|
1189
|
+
* vertical arrow walks focus in when the popup is ALREADY open, which is how
|
|
1190
|
+
* someone who opened it by clicking a field reaches the grid. Every other key
|
|
1191
|
+
* belongs to the field, so typing a date keeps working with the popup open.
|
|
1192
|
+
*/
|
|
1193
|
+
onFieldKey(event, end) {
|
|
1194
|
+
if (this.disabled() || this.readonly())
|
|
1195
|
+
return;
|
|
1196
|
+
const vertical = event.key === 'ArrowDown' || event.key === 'ArrowUp';
|
|
1197
|
+
if (!vertical)
|
|
1198
|
+
return;
|
|
1199
|
+
if (event.altKey && event.key === 'ArrowDown' && !this.overlayRef) {
|
|
1200
|
+
event.preventDefault();
|
|
1201
|
+
this.openOverlay(this.fieldEl(end), true);
|
|
1202
|
+
return;
|
|
1203
|
+
}
|
|
1204
|
+
if (this.overlayRef) {
|
|
1205
|
+
event.preventDefault();
|
|
1206
|
+
this.focusPanel();
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
/** The input element for an end — the two are otherwise addressed by index. */
|
|
1210
|
+
fieldEl(end) {
|
|
1211
|
+
return end === 0 ? this.startEl().nativeElement : this.endEl().nativeElement;
|
|
1212
|
+
}
|
|
1213
|
+
/**
|
|
1214
|
+
* Move focus into an already-mounted panel. A plain query is right HERE and
|
|
1215
|
+
* wrong at mount time: the panel has settled, so there is no deferral to get
|
|
1216
|
+
* wrong — and it keeps `autoFocus` as the single piece of panel API.
|
|
1217
|
+
*/
|
|
1218
|
+
focusPanel() {
|
|
1219
|
+
const pane = this.overlayRef?.overlayElement;
|
|
1220
|
+
const target = pane?.querySelector('.wr-calendar__day--focused:not([disabled])') ??
|
|
1221
|
+
pane?.querySelector('.wr-time-picker__input');
|
|
1222
|
+
target?.focus();
|
|
1223
|
+
}
|
|
1224
|
+
/** Hand focus back to whatever opened the popup, if it is still on the page. */
|
|
1225
|
+
restoreFocus() {
|
|
1226
|
+
const target = this.openedFrom?.isConnected ? this.openedFrom : this.startEl().nativeElement;
|
|
1227
|
+
target.focus();
|
|
981
1228
|
}
|
|
982
1229
|
// Input parsing
|
|
983
1230
|
onRangeInput(event, end) {
|
|
@@ -997,9 +1244,11 @@ class WrDateRangePicker {
|
|
|
997
1244
|
this.commitRange(end === 0 ? [parsed, finish] : [start, parsed], { normalise: false });
|
|
998
1245
|
}
|
|
999
1246
|
// Overlay
|
|
1000
|
-
openOverlay() {
|
|
1247
|
+
openOverlay(openedFrom = null, autoFocus = false) {
|
|
1001
1248
|
if (this.overlayRef)
|
|
1002
1249
|
return;
|
|
1250
|
+
this.openedFrom = openedFrom;
|
|
1251
|
+
this.autoFocusPanel = autoFocus;
|
|
1003
1252
|
const positionStrategy = this.overlay
|
|
1004
1253
|
.position()
|
|
1005
1254
|
.flexibleConnectedTo(this.host)
|
|
@@ -1016,9 +1265,22 @@ class WrDateRangePicker {
|
|
|
1016
1265
|
panelClass: 'wr-date-picker-overlay',
|
|
1017
1266
|
});
|
|
1018
1267
|
this.overlayOpen.set(true);
|
|
1268
|
+
// The trigger promises `aria-haspopup="dialog"`; the pane is the element it
|
|
1269
|
+
// points at, so the role, the name and the id all belong here. Non-modal on
|
|
1270
|
+
// purpose — the two text fields stay reachable while the calendar is up,
|
|
1271
|
+
// which is the whole interaction model.
|
|
1272
|
+
const pane = this.overlayRef.overlayElement;
|
|
1273
|
+
pane.id = this.panelId;
|
|
1274
|
+
pane.setAttribute('role', 'dialog');
|
|
1275
|
+
pane.setAttribute('aria-modal', 'false');
|
|
1276
|
+
pane.setAttribute('aria-label', this.resolvedPanelLabel());
|
|
1019
1277
|
const ref = this.overlayRef.attach(new ComponentPortal(WrDateRangePanel));
|
|
1020
1278
|
ref.setInput('value', this.value() ?? [null, null]);
|
|
1279
|
+
ref.setInput('autoFocus', this.autoFocusPanel);
|
|
1021
1280
|
this.panelRef.set(ref);
|
|
1281
|
+
// A stepper edit belongs to the end it was made on. Sorting it here is what
|
|
1282
|
+
// made the start stepper stop responding once it passed the end.
|
|
1283
|
+
ref.instance.timeChanged.subscribe(next => this.commitRange(next, { normalise: false }));
|
|
1022
1284
|
ref.instance.changed.subscribe(next => {
|
|
1023
1285
|
const committed = this.commitRange(next, { normalise: true });
|
|
1024
1286
|
// A complete date-only range is the end of the interaction; a datetime
|
|
@@ -1041,21 +1303,31 @@ class WrDateRangePicker {
|
|
|
1041
1303
|
if (event.key === 'Escape') {
|
|
1042
1304
|
event.preventDefault();
|
|
1043
1305
|
this.closeOverlay();
|
|
1044
|
-
this.startEl().nativeElement.focus();
|
|
1045
1306
|
}
|
|
1046
1307
|
});
|
|
1047
1308
|
}
|
|
1048
1309
|
/**
|
|
1049
|
-
* Close the popover
|
|
1050
|
-
*
|
|
1051
|
-
*
|
|
1052
|
-
* the
|
|
1310
|
+
* Close the popover, handing focus back to whichever field or button opened
|
|
1311
|
+
* it — but ONLY when focus was still inside the pane. Dropped there it would
|
|
1312
|
+
* land on `<body>` and the next Tab would restart from the top of the page;
|
|
1313
|
+
* moved on by the user, it must be left where they put it.
|
|
1314
|
+
*
|
|
1315
|
+
* There is deliberately no "always restore" escape hatch. Escape reaches this
|
|
1316
|
+
* overlay from anywhere in the document (the CDK dispatches it to the topmost
|
|
1317
|
+
* one), so forcing a restore would drag the caret backwards out of the field
|
|
1318
|
+
* the user was actually typing in — into the one that happened to open the
|
|
1319
|
+
* popup, possibly several Tab stops away.
|
|
1053
1320
|
*/
|
|
1054
|
-
closeOverlay(
|
|
1055
|
-
const
|
|
1321
|
+
closeOverlay() {
|
|
1322
|
+
const pane = this.overlayRef?.overlayElement;
|
|
1323
|
+
const inside = !!pane && pane.contains(this.host.nativeElement.ownerDocument.activeElement);
|
|
1056
1324
|
this.dispose();
|
|
1057
|
-
|
|
1058
|
-
|
|
1325
|
+
// The interaction is over, so settle the ordering an in-progress time edit
|
|
1326
|
+
// was allowed to leave inverted. Blur does the same for the text inputs;
|
|
1327
|
+
// without this, closing by clicking outside never passes through one.
|
|
1328
|
+
this.commitRange(this.current(), { normalise: true });
|
|
1329
|
+
if (inside)
|
|
1330
|
+
this.restoreFocus();
|
|
1059
1331
|
}
|
|
1060
1332
|
dispose() {
|
|
1061
1333
|
if (this.overlayRef) {
|
|
@@ -1064,6 +1336,7 @@ class WrDateRangePicker {
|
|
|
1064
1336
|
}
|
|
1065
1337
|
this.panelRef.set(null);
|
|
1066
1338
|
this.overlayOpen.set(false);
|
|
1339
|
+
this.autoFocusPanel = false;
|
|
1067
1340
|
}
|
|
1068
1341
|
// Commit
|
|
1069
1342
|
/**
|
|
@@ -1150,20 +1423,29 @@ class WrDateRangePicker {
|
|
|
1150
1423
|
return false;
|
|
1151
1424
|
return a.getTime() === b.getTime();
|
|
1152
1425
|
}
|
|
1426
|
+
/**
|
|
1427
|
+
* `null` and `[null, null]` are the same VALUE — an empty range — and treating
|
|
1428
|
+
* them as different is what let the FIRST blur on an untouched picker write
|
|
1429
|
+
* `[null, null]` over the `null` it was bound to. No date had moved, but
|
|
1430
|
+
* `model()` emitted: a bound `[formField]` went dirty from nothing but tabbing
|
|
1431
|
+
* through, and a consumer's `@if (period())` flipped from empty to truthy with
|
|
1432
|
+
* neither end picked. The write guard in `commitRange` is only as good as this
|
|
1433
|
+
* comparison.
|
|
1434
|
+
*/
|
|
1153
1435
|
sameRange(a, b) {
|
|
1154
1436
|
if (a === b)
|
|
1155
1437
|
return true;
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
return this.sameDate(
|
|
1438
|
+
const [aStart, aEnd] = a ?? [null, null];
|
|
1439
|
+
const [bStart, bEnd] = b ?? [null, null];
|
|
1440
|
+
return this.sameDate(aStart, bStart) && this.sameDate(aEnd, bEnd);
|
|
1159
1441
|
}
|
|
1160
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.
|
|
1161
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.
|
|
1442
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: WrDateRangePicker, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1443
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.1", type: WrDateRangePicker, isStandalone: true, selector: "wr-date-range-picker", inputs: { mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: false, transformFunction: null }, format: { classPropertyName: "format", publicName: "format", isSignal: true, isRequired: false, transformFunction: null }, startPlaceholder: { classPropertyName: "startPlaceholder", publicName: "startPlaceholder", isSignal: true, isRequired: false, transformFunction: null }, endPlaceholder: { classPropertyName: "endPlaceholder", publicName: "endPlaceholder", isSignal: true, isRequired: false, transformFunction: null }, separator: { classPropertyName: "separator", publicName: "separator", isSignal: true, isRequired: false, transformFunction: null }, minDate: { classPropertyName: "minDate", publicName: "minDate", isSignal: true, isRequired: false, transformFunction: null }, maxDate: { classPropertyName: "maxDate", publicName: "maxDate", isSignal: true, isRequired: false, transformFunction: null }, dateFilter: { classPropertyName: "dateFilter", publicName: "dateFilter", isSignal: true, isRequired: false, transformFunction: null }, timeFormat: { classPropertyName: "timeFormat", publicName: "timeFormat", isSignal: true, isRequired: false, transformFunction: null }, showSeconds: { classPropertyName: "showSeconds", publicName: "showSeconds", isSignal: true, isRequired: false, transformFunction: null }, step: { classPropertyName: "step", publicName: "step", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, panelAriaLabel: { classPropertyName: "panelAriaLabel", publicName: "panelAriaLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", touch: "touch" }, host: { properties: { "class": "classes()" } }, viewQueries: [{ propertyName: "startEl", first: true, predicate: ["startInput"], descendants: true, isSignal: true }, { propertyName: "endEl", first: true, predicate: ["endInput"], descendants: true, isSignal: true }, { propertyName: "triggerEl", first: true, predicate: ["trigger"], descendants: true, isSignal: true }], ngImport: i0, template: "<wr-input-group>\n <input\n #startInput\n wrInput\n type=\"text\"\n autocomplete=\"off\"\n spellcheck=\"false\"\n class=\"wr-date-range-picker__input\"\n [attr.aria-label]=\"startLabel\"\n [value]=\"startText()\"\n [placeholder]=\"startPlaceholder()\"\n [disabled]=\"disabled()\"\n [readonly]=\"readonly()\"\n (click)=\"openOnInput(0)\"\n (keydown)=\"onFieldKey($event, 0)\"\n (input)=\"onStartInput($event)\"\n (blur)=\"onBlur($event)\"\n />\n <span class=\"wr-date-range-picker__separator\" aria-hidden=\"true\">{{ separator() }}</span>\n <input\n #endInput\n wrInput\n type=\"text\"\n autocomplete=\"off\"\n spellcheck=\"false\"\n class=\"wr-date-range-picker__input\"\n [attr.aria-label]=\"endLabel\"\n [value]=\"endText()\"\n [placeholder]=\"endPlaceholder()\"\n [disabled]=\"disabled()\"\n [readonly]=\"readonly()\"\n (click)=\"openOnInput(1)\"\n (keydown)=\"onFieldKey($event, 1)\"\n (input)=\"onEndInput($event)\"\n (blur)=\"onBlur($event)\"\n />\n <!--\n `readonly` refuses to open the popup, so the trigger is disabled with it \u2014\n otherwise it stays a focusable button advertising `aria-haspopup=\"dialog\"`\n for a dialog it will never show.\n -->\n <button\n #trigger\n type=\"button\"\n wrInputSuffix\n class=\"wr-date-picker__trigger\"\n [attr.aria-label]=\"triggerLabel\"\n [attr.aria-haspopup]=\"'dialog'\"\n [attr.aria-expanded]=\"overlayOpen()\"\n [attr.aria-controls]=\"overlayOpen() ? panelId : null\"\n [disabled]=\"disabled() || readonly()\"\n (click)=\"toggleOverlay()\"\n >\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 14 14\" aria-hidden=\"true\">\n <rect x=\"1.5\" y=\"2.5\" width=\"11\" height=\"10\" rx=\"1.5\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.2\" />\n <line x1=\"1.5\" y1=\"5.5\" x2=\"12.5\" y2=\"5.5\" stroke=\"currentColor\" stroke-width=\"1.2\" />\n <line x1=\"4\" y1=\"1\" x2=\"4\" y2=\"4\" stroke=\"currentColor\" stroke-width=\"1.4\" stroke-linecap=\"round\" />\n <line x1=\"10\" y1=\"1\" x2=\"10\" y2=\"4\" stroke=\"currentColor\" stroke-width=\"1.4\" stroke-linecap=\"round\" />\n @if (isDateTime()) {\n <circle cx=\"7\" cy=\"9\" r=\"1.5\" fill=\"currentColor\" />\n }\n </svg>\n </button>\n</wr-input-group>\n", dependencies: [{ kind: "directive", type: WrInput, selector: "input[wrInput], textarea[wrInput]", inputs: ["wrSize", "rounded"] }, { kind: "component", type: WrInputGroup, selector: "wr-input-group", inputs: ["rounded"] }, { kind: "directive", type: WrInputSuffix, selector: "[wrInputSuffix]" }], encapsulation: i0.ViewEncapsulation.None });
|
|
1162
1444
|
}
|
|
1163
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.
|
|
1445
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: WrDateRangePicker, decorators: [{
|
|
1164
1446
|
type: Component,
|
|
1165
|
-
args: [{ selector: 'wr-date-range-picker', encapsulation: ViewEncapsulation.None, host: { '[class]': 'classes()' }, imports: [WrInput, WrInputGroup, WrInputSuffix], template: "<wr-input-group>\n <input\n #startInput\n wrInput\n type=\"text\"\n autocomplete=\"off\"\n spellcheck=\"false\"\n class=\"wr-date-range-picker__input\"\n [attr.aria-label]=\"startLabel\"\n [value]=\"startText()\"\n [placeholder]=\"startPlaceholder()\"\n [disabled]=\"disabled()\"\n [readonly]=\"readonly()\"\n (click)=\"openOnInput()\"\n (input)=\"onStartInput($event)\"\n (blur)=\"onBlur($event)\"\n />\n <span class=\"wr-date-range-picker__separator\" aria-hidden=\"true\">{{ separator() }}</span>\n <input\n wrInput\n type=\"text\"\n autocomplete=\"off\"\n spellcheck=\"false\"\n class=\"wr-date-range-picker__input\"\n [attr.aria-label]=\"endLabel\"\n [value]=\"endText()\"\n [placeholder]=\"endPlaceholder()\"\n [disabled]=\"disabled()\"\n [readonly]=\"readonly()\"\n (click)=\"openOnInput()\"\n (input)=\"onEndInput($event)\"\n (blur)=\"onBlur($event)\"\n />\n <button\n type=\"button\"\n wrInputSuffix\n class=\"wr-date-picker__trigger\"\n [attr.aria-label]=\"triggerLabel\"\n [attr.aria-haspopup]=\"'dialog'\"\n [attr.aria-expanded]=\"overlayOpen()\"\n [disabled]=\"disabled()\"\n (click)=\"toggleOverlay()\"\n >\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 14 14\" aria-hidden=\"true\">\n <rect x=\"1.5\" y=\"2.5\" width=\"11\" height=\"10\" rx=\"1.5\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.2\" />\n <line x1=\"1.5\" y1=\"5.5\" x2=\"12.5\" y2=\"5.5\" stroke=\"currentColor\" stroke-width=\"1.2\" />\n <line x1=\"4\" y1=\"1\" x2=\"4\" y2=\"4\" stroke=\"currentColor\" stroke-width=\"1.4\" stroke-linecap=\"round\" />\n <line x1=\"10\" y1=\"1\" x2=\"10\" y2=\"4\" stroke=\"currentColor\" stroke-width=\"1.4\" stroke-linecap=\"round\" />\n @if (isDateTime()) {\n <circle cx=\"7\" cy=\"9\" r=\"1.5\" fill=\"currentColor\" />\n }\n </svg>\n </button>\n</wr-input-group>\n" }]
|
|
1166
|
-
}], ctorParameters: () => [], propDecorators: { mode: [{ type: i0.Input, args: [{ isSignal: true, alias: "mode", required: false }] }], format: [{ type: i0.Input, args: [{ isSignal: true, alias: "format", required: false }] }], startPlaceholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "startPlaceholder", required: false }] }], endPlaceholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "endPlaceholder", required: false }] }], separator: [{ type: i0.Input, args: [{ isSignal: true, alias: "separator", required: false }] }], minDate: [{ type: i0.Input, args: [{ isSignal: true, alias: "minDate", required: false }] }], maxDate: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxDate", required: false }] }], dateFilter: [{ type: i0.Input, args: [{ isSignal: true, alias: "dateFilter", required: false }] }], timeFormat: [{ type: i0.Input, args: [{ isSignal: true, alias: "timeFormat", required: false }] }], showSeconds: [{ type: i0.Input, args: [{ isSignal: true, alias: "showSeconds", required: false }] }], step: [{ type: i0.Input, args: [{ isSignal: true, alias: "step", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], startEl: [{ type: i0.ViewChild, args: ['startInput', { isSignal: true }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], touch: [{ type: i0.Output, args: ["touch"] }] } });
|
|
1447
|
+
args: [{ selector: 'wr-date-range-picker', encapsulation: ViewEncapsulation.None, host: { '[class]': 'classes()' }, imports: [WrInput, WrInputGroup, WrInputSuffix], template: "<wr-input-group>\n <input\n #startInput\n wrInput\n type=\"text\"\n autocomplete=\"off\"\n spellcheck=\"false\"\n class=\"wr-date-range-picker__input\"\n [attr.aria-label]=\"startLabel\"\n [value]=\"startText()\"\n [placeholder]=\"startPlaceholder()\"\n [disabled]=\"disabled()\"\n [readonly]=\"readonly()\"\n (click)=\"openOnInput(0)\"\n (keydown)=\"onFieldKey($event, 0)\"\n (input)=\"onStartInput($event)\"\n (blur)=\"onBlur($event)\"\n />\n <span class=\"wr-date-range-picker__separator\" aria-hidden=\"true\">{{ separator() }}</span>\n <input\n #endInput\n wrInput\n type=\"text\"\n autocomplete=\"off\"\n spellcheck=\"false\"\n class=\"wr-date-range-picker__input\"\n [attr.aria-label]=\"endLabel\"\n [value]=\"endText()\"\n [placeholder]=\"endPlaceholder()\"\n [disabled]=\"disabled()\"\n [readonly]=\"readonly()\"\n (click)=\"openOnInput(1)\"\n (keydown)=\"onFieldKey($event, 1)\"\n (input)=\"onEndInput($event)\"\n (blur)=\"onBlur($event)\"\n />\n <!--\n `readonly` refuses to open the popup, so the trigger is disabled with it \u2014\n otherwise it stays a focusable button advertising `aria-haspopup=\"dialog\"`\n for a dialog it will never show.\n -->\n <button\n #trigger\n type=\"button\"\n wrInputSuffix\n class=\"wr-date-picker__trigger\"\n [attr.aria-label]=\"triggerLabel\"\n [attr.aria-haspopup]=\"'dialog'\"\n [attr.aria-expanded]=\"overlayOpen()\"\n [attr.aria-controls]=\"overlayOpen() ? panelId : null\"\n [disabled]=\"disabled() || readonly()\"\n (click)=\"toggleOverlay()\"\n >\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 14 14\" aria-hidden=\"true\">\n <rect x=\"1.5\" y=\"2.5\" width=\"11\" height=\"10\" rx=\"1.5\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.2\" />\n <line x1=\"1.5\" y1=\"5.5\" x2=\"12.5\" y2=\"5.5\" stroke=\"currentColor\" stroke-width=\"1.2\" />\n <line x1=\"4\" y1=\"1\" x2=\"4\" y2=\"4\" stroke=\"currentColor\" stroke-width=\"1.4\" stroke-linecap=\"round\" />\n <line x1=\"10\" y1=\"1\" x2=\"10\" y2=\"4\" stroke=\"currentColor\" stroke-width=\"1.4\" stroke-linecap=\"round\" />\n @if (isDateTime()) {\n <circle cx=\"7\" cy=\"9\" r=\"1.5\" fill=\"currentColor\" />\n }\n </svg>\n </button>\n</wr-input-group>\n" }]
|
|
1448
|
+
}], ctorParameters: () => [], propDecorators: { mode: [{ type: i0.Input, args: [{ isSignal: true, alias: "mode", required: false }] }], format: [{ type: i0.Input, args: [{ isSignal: true, alias: "format", required: false }] }], startPlaceholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "startPlaceholder", required: false }] }], endPlaceholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "endPlaceholder", required: false }] }], separator: [{ type: i0.Input, args: [{ isSignal: true, alias: "separator", required: false }] }], minDate: [{ type: i0.Input, args: [{ isSignal: true, alias: "minDate", required: false }] }], maxDate: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxDate", required: false }] }], dateFilter: [{ type: i0.Input, args: [{ isSignal: true, alias: "dateFilter", required: false }] }], timeFormat: [{ type: i0.Input, args: [{ isSignal: true, alias: "timeFormat", required: false }] }], showSeconds: [{ type: i0.Input, args: [{ isSignal: true, alias: "showSeconds", required: false }] }], step: [{ type: i0.Input, args: [{ isSignal: true, alias: "step", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], startEl: [{ type: i0.ViewChild, args: ['startInput', { isSignal: true }] }], endEl: [{ type: i0.ViewChild, args: ['endInput', { isSignal: true }] }], triggerEl: [{ type: i0.ViewChild, args: ['trigger', { isSignal: true }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], touch: [{ type: i0.Output, args: ["touch"] }], panelAriaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "panelAriaLabel", required: false }] }] } });
|
|
1167
1449
|
|
|
1168
1450
|
/**
|
|
1169
1451
|
* Generated bundle index. Do not edit.
|