ngwr 9.0.1 → 9.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/date-picker/styles/_index.scss +62 -0
- package/drawer/styles/_index.scss +23 -0
- package/fesm2022/ngwr-date-picker.mjs +509 -1
- package/fesm2022/ngwr-date-picker.mjs.map +1 -1
- package/fesm2022/ngwr-dialog.mjs +85 -12
- package/fesm2022/ngwr-dialog.mjs.map +1 -1
- package/fesm2022/ngwr-drawer.mjs +340 -6
- package/fesm2022/ngwr-drawer.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-en.mjs +5 -0
- package/fesm2022/ngwr-i18n-en.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-ru.mjs +5 -0
- package/fesm2022/ngwr-i18n-ru.mjs.map +1 -1
- package/fesm2022/ngwr-pagination.mjs +1 -1
- package/fesm2022/ngwr-pagination.mjs.map +1 -1
- package/fesm2022/ngwr-select.mjs +221 -38
- package/fesm2022/ngwr-select.mjs.map +1 -1
- package/fesm2022/ngwr-version.mjs +1 -1
- package/fesm2022/ngwr-version.mjs.map +1 -1
- package/llms-full.txt +11 -11
- package/llms.txt +12 -6
- package/package.json +21 -5
- package/schematics/use/symbol-map.json +5 -0
- package/types/ngwr-date-picker.d.ts +167 -1
- package/types/ngwr-dialog.d.ts +71 -8
- package/types/ngwr-drawer.d.ts +225 -6
- package/types/ngwr-select.d.ts +115 -9
- package/types/ngwr-version.d.ts +1 -1
|
@@ -27,6 +27,30 @@
|
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
.wr-date-range-picker {
|
|
31
|
+
display: inline-block;
|
|
32
|
+
|
|
33
|
+
// Each end shares the group's width so the pair reads as one control.
|
|
34
|
+
&__input {
|
|
35
|
+
flex: 1 1 0;
|
|
36
|
+
min-width: 0;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
&__separator {
|
|
40
|
+
display: inline-flex;
|
|
41
|
+
align-items: center;
|
|
42
|
+
padding: 0 0.25rem;
|
|
43
|
+
color: var(--wr-color-on-surface-muted);
|
|
44
|
+
user-select: none;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
&--disabled {
|
|
48
|
+
.wr-date-range-picker__separator {
|
|
49
|
+
opacity: 0.5;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
30
54
|
// Overlay pane — z-index isolation only. <wr-calendar> + the time panel
|
|
31
55
|
// bring their own chrome.
|
|
32
56
|
.wr-date-picker-overlay {
|
|
@@ -201,3 +225,41 @@
|
|
|
201
225
|
border-radius: 0 0 var(--wr-border-radius-base) var(--wr-border-radius-base);
|
|
202
226
|
}
|
|
203
227
|
}
|
|
228
|
+
|
|
229
|
+
// Range calendar + one time stepper per end, joined to the calendar the same
|
|
230
|
+
// way the single datetime panel is.
|
|
231
|
+
.wr-date-range-panel {
|
|
232
|
+
display: inline-flex;
|
|
233
|
+
flex-direction: column;
|
|
234
|
+
gap: 0;
|
|
235
|
+
|
|
236
|
+
> .wr-calendar {
|
|
237
|
+
border-bottom: 0;
|
|
238
|
+
border-bottom-left-radius: 0;
|
|
239
|
+
border-bottom-right-radius: 0;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
&__times {
|
|
243
|
+
display: flex;
|
|
244
|
+
flex-wrap: wrap;
|
|
245
|
+
justify-content: center;
|
|
246
|
+
gap: 0.75rem 1.25rem;
|
|
247
|
+
padding: 0.75rem;
|
|
248
|
+
background: var(--wr-color-surface);
|
|
249
|
+
border: 1px solid var(--wr-color-outline);
|
|
250
|
+
border-top: 0;
|
|
251
|
+
border-radius: 0 0 var(--wr-border-radius-base) var(--wr-border-radius-base);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
&__time {
|
|
255
|
+
display: inline-flex;
|
|
256
|
+
flex-direction: column;
|
|
257
|
+
gap: 0.25rem;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
&__time-label {
|
|
261
|
+
color: var(--wr-color-on-surface-muted);
|
|
262
|
+
font-size: var(--wr-text-xs);
|
|
263
|
+
font-weight: 600;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
@@ -18,6 +18,29 @@
|
|
|
18
18
|
height: 100dvh;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
// `WrDrawerManager.open()` attaches the content component straight into the
|
|
22
|
+
// overlay pane, so both class sets land on the same element and the panel's
|
|
23
|
+
// `height: 100%` would otherwise win over the rule above.
|
|
24
|
+
.wr-drawer-overlay--left.wr-drawer__panel,
|
|
25
|
+
.wr-drawer-overlay--right.wr-drawer__panel {
|
|
26
|
+
height: 100vh;
|
|
27
|
+
height: 100dvh;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// …and that same flattening puts the content component's own host element
|
|
31
|
+
// between the panel and the title / content / footer slots, so the panel's
|
|
32
|
+
// column layout stops one level short: the footer wouldn't pin to the bottom
|
|
33
|
+
// and the body wouldn't scroll. Pass the layout through the interposed host.
|
|
34
|
+
.wr-drawer-overlay.wr-drawer__panel > * {
|
|
35
|
+
display: flex;
|
|
36
|
+
flex: 1 1 auto;
|
|
37
|
+
flex-direction: column;
|
|
38
|
+
width: 100%;
|
|
39
|
+
// Let `wr-drawer__content`'s `overflow: auto` actually clip: a flex item
|
|
40
|
+
// defaults to `min-height: auto`, which refuses to shrink below its content.
|
|
41
|
+
min-height: 0;
|
|
42
|
+
}
|
|
43
|
+
|
|
21
44
|
.wr-drawer__panel {
|
|
22
45
|
--wr-drawer-bg: var(--wr-color-surface);
|
|
23
46
|
--wr-drawer-shadow: var(--wr-shadow-modal);
|
|
@@ -635,9 +635,517 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
|
|
|
635
635
|
args: [{ selector: 'wr-date-picker', encapsulation: ViewEncapsulation.None, host: { '[class]': 'classes()' }, imports: [WrInput, WrInputGroup, WrInputSuffix], template: "<wr-input-group>\n <input\n #input\n wrInput\n type=\"text\"\n autocomplete=\"off\"\n spellcheck=\"false\"\n [attr.aria-haspopup]=\"'dialog'\"\n [attr.aria-expanded]=\"overlayOpen()\"\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 [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" }]
|
|
636
636
|
}], 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"] }] } });
|
|
637
637
|
|
|
638
|
+
/**
|
|
639
|
+
* @license
|
|
640
|
+
*
|
|
641
|
+
* Use of this source code is governed by an MIT-style license that can be
|
|
642
|
+
* found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE
|
|
643
|
+
*/
|
|
644
|
+
/**
|
|
645
|
+
* Internal panel behind `<wr-date-range-picker>`: a range-mode
|
|
646
|
+
* `<wr-calendar>`, plus one `<wr-time-picker>` per end when `withTime` is on.
|
|
647
|
+
* Not part of the public API.
|
|
648
|
+
*
|
|
649
|
+
* The calendar owns range picking (including the start/end swap and the hover
|
|
650
|
+
* preview); this panel only re-attaches the times so a datetime range keeps the
|
|
651
|
+
* hours the user set while the dates move around.
|
|
652
|
+
*
|
|
653
|
+
* @internal
|
|
654
|
+
*/
|
|
655
|
+
class WrDateRangePanel {
|
|
656
|
+
value = input([null, null], /* @ts-ignore */
|
|
657
|
+
...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
|
|
658
|
+
min = input(null, /* @ts-ignore */
|
|
659
|
+
...(ngDevMode ? [{ debugName: "min" }] : /* istanbul ignore next */ []));
|
|
660
|
+
max = input(null, /* @ts-ignore */
|
|
661
|
+
...(ngDevMode ? [{ debugName: "max" }] : /* istanbul ignore next */ []));
|
|
662
|
+
dateFilter = input(null, /* @ts-ignore */
|
|
663
|
+
...(ngDevMode ? [{ debugName: "dateFilter" }] : /* istanbul ignore next */ []));
|
|
664
|
+
withTime = input(false, /* @ts-ignore */
|
|
665
|
+
...(ngDevMode ? [{ debugName: "withTime" }] : /* istanbul ignore next */ []));
|
|
666
|
+
timeFormat = input('auto', /* @ts-ignore */
|
|
667
|
+
...(ngDevMode ? [{ debugName: "timeFormat" }] : /* istanbul ignore next */ []));
|
|
668
|
+
showSeconds = input(false, /* @ts-ignore */
|
|
669
|
+
...(ngDevMode ? [{ debugName: "showSeconds" }] : /* istanbul ignore next */ []));
|
|
670
|
+
step = input(1, /* @ts-ignore */
|
|
671
|
+
...(ngDevMode ? [{ debugName: "step" }] : /* istanbul ignore next */ []));
|
|
672
|
+
changed = output();
|
|
673
|
+
adapter = inject(WrDateAdapter);
|
|
674
|
+
startTimeLabel = readI18nText('datePicker.startTime', 'Start time');
|
|
675
|
+
endTimeLabel = readI18nText('datePicker.endTime', 'End time');
|
|
676
|
+
/**
|
|
677
|
+
* A time set before its end has a date. The stepper is always live, so the
|
|
678
|
+
* hours are parked here until a date arrives to carry them — otherwise the
|
|
679
|
+
* edit would be silently dropped and the pick would land on midnight.
|
|
680
|
+
*/
|
|
681
|
+
pendingStartTime = signal(null, /* @ts-ignore */
|
|
682
|
+
...(ngDevMode ? [{ debugName: "pendingStartTime" }] : /* istanbul ignore next */ []));
|
|
683
|
+
pendingEndTime = signal(null, /* @ts-ignore */
|
|
684
|
+
...(ngDevMode ? [{ debugName: "pendingEndTime" }] : /* istanbul ignore next */ []));
|
|
685
|
+
/** The calendar's own range model shape — same tuple, narrower name. */
|
|
686
|
+
calendarRange = computed(() => this.value(), /* @ts-ignore */
|
|
687
|
+
...(ngDevMode ? [{ debugName: "calendarRange" }] : /* istanbul ignore next */ []));
|
|
688
|
+
/**
|
|
689
|
+
* Midnight today. The steppers need a concrete date to render, but the
|
|
690
|
+
* fallback must NOT be the current wall clock: picking a date carries no time
|
|
691
|
+
* across until a stepper is touched, so a `14:37` default would display a time
|
|
692
|
+
* the commit never actually stores.
|
|
693
|
+
*/
|
|
694
|
+
dayStart = computed(() => {
|
|
695
|
+
const t = this.adapter.today();
|
|
696
|
+
return this.adapter.createDate(this.adapter.getYear(t), this.adapter.getMonth(t), this.adapter.getDate(t));
|
|
697
|
+
}, /* @ts-ignore */
|
|
698
|
+
...(ngDevMode ? [{ debugName: "dayStart" }] : /* istanbul ignore next */ []));
|
|
699
|
+
/** Time steppers need a concrete date; fall back to a parked time, then midnight. */
|
|
700
|
+
startTime = computed(() => this.value()[0] ?? this.pendingStartTime() ?? this.dayStart(), /* @ts-ignore */
|
|
701
|
+
...(ngDevMode ? [{ debugName: "startTime" }] : /* istanbul ignore next */ []));
|
|
702
|
+
endTime = computed(() => this.value()[1] ?? this.pendingEndTime() ?? this.dayStart(), /* @ts-ignore */
|
|
703
|
+
...(ngDevMode ? [{ debugName: "endTime" }] : /* istanbul ignore next */ []));
|
|
704
|
+
onRangeChange(range) {
|
|
705
|
+
const [start, end] = range;
|
|
706
|
+
const [prevStart, prevEnd] = this.value();
|
|
707
|
+
// `WrCalendar.pickRange` restarts the range whenever it has no start, so a
|
|
708
|
+
// range that was end-only (typed into the end input alone) would lose that
|
|
709
|
+
// end on the next click. Read the click as filling in the missing start
|
|
710
|
+
// instead; the picker orders the pair afterwards.
|
|
711
|
+
const keptEnd = !prevStart && prevEnd && start && !end ? prevEnd : end;
|
|
712
|
+
// The calendar only moves dates, so carry each end's existing time across —
|
|
713
|
+
// or the time parked by a stepper that ran before this date existed.
|
|
714
|
+
this.changed.emit([
|
|
715
|
+
this.withTimeOf(start, prevStart ?? this.pendingStartTime()),
|
|
716
|
+
this.withTimeOf(keptEnd, prevEnd ?? this.pendingEndTime()),
|
|
717
|
+
]);
|
|
718
|
+
}
|
|
719
|
+
onStartTimeChange(time) {
|
|
720
|
+
if (!time)
|
|
721
|
+
return;
|
|
722
|
+
const [start, end] = this.value();
|
|
723
|
+
if (!start) {
|
|
724
|
+
this.pendingStartTime.set(time);
|
|
725
|
+
return;
|
|
726
|
+
}
|
|
727
|
+
this.changed.emit([this.withTimeOf(start, time), end]);
|
|
728
|
+
}
|
|
729
|
+
onEndTimeChange(time) {
|
|
730
|
+
if (!time)
|
|
731
|
+
return;
|
|
732
|
+
const [start, end] = this.value();
|
|
733
|
+
if (!end) {
|
|
734
|
+
this.pendingEndTime.set(time);
|
|
735
|
+
return;
|
|
736
|
+
}
|
|
737
|
+
this.changed.emit([start, this.withTimeOf(end, time)]);
|
|
738
|
+
}
|
|
739
|
+
/** `date`'s calendar day carrying `time`'s clock. Null date stays null. */
|
|
740
|
+
withTimeOf(date, time) {
|
|
741
|
+
if (!date)
|
|
742
|
+
return null;
|
|
743
|
+
if (!this.withTime() || !time)
|
|
744
|
+
return date;
|
|
745
|
+
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));
|
|
746
|
+
}
|
|
747
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: WrDateRangePanel, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
748
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", 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 } }, outputs: { changed: "changed" }, 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 (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 [ngModel]=\"startTime()\"\n [format]=\"timeFormat()\"\n [showSeconds]=\"showSeconds()\"\n [step]=\"step()\"\n (ngModelChange)=\"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 [ngModel]=\"endTime()\"\n [format]=\"timeFormat()\"\n [showSeconds]=\"showSeconds()\"\n [step]=\"step()\"\n (ngModelChange)=\"onEndTimeChange($event)\"\n />\n </div>\n </div>\n}\n", dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: WrCalendar, selector: "wr-calendar", inputs: ["mode", "date", "range", "min", "max", "dateFilter", "disabled"], outputs: ["dateChange", "rangeChange"] }, { kind: "component", type: WrTimePanel, selector: "wr-time-picker", inputs: ["format", "showSeconds", "step", "disabled", "readonly", "value"], outputs: ["valueChange", "touch"] }], encapsulation: i0.ViewEncapsulation.None });
|
|
749
|
+
}
|
|
750
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: WrDateRangePanel, decorators: [{
|
|
751
|
+
type: Component,
|
|
752
|
+
args: [{ selector: 'wr-date-range-panel', encapsulation: ViewEncapsulation.None, host: { class: 'wr-date-range-panel' }, imports: [FormsModule, WrCalendar, WrTimePanel], template: "<wr-calendar\n mode=\"range\"\n [range]=\"calendarRange()\"\n [min]=\"min()\"\n [max]=\"max()\"\n [dateFilter]=\"dateFilter()\"\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 [ngModel]=\"startTime()\"\n [format]=\"timeFormat()\"\n [showSeconds]=\"showSeconds()\"\n [step]=\"step()\"\n (ngModelChange)=\"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 [ngModel]=\"endTime()\"\n [format]=\"timeFormat()\"\n [showSeconds]=\"showSeconds()\"\n [step]=\"step()\"\n (ngModelChange)=\"onEndTimeChange($event)\"\n />\n </div>\n </div>\n}\n" }]
|
|
753
|
+
}], 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"] }] } });
|
|
754
|
+
|
|
755
|
+
/**
|
|
756
|
+
* @license
|
|
757
|
+
*
|
|
758
|
+
* Use of this source code is governed by an MIT-style license that can be
|
|
759
|
+
* found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE
|
|
760
|
+
*/
|
|
761
|
+
/**
|
|
762
|
+
* Date-range picker — two text inputs sharing one range calendar.
|
|
763
|
+
*
|
|
764
|
+
* Separate from `<wr-date-picker>` because the value is a different type: a
|
|
765
|
+
* range is `[start, end]`, and folding that into the single picker's
|
|
766
|
+
* `Date | null` would break `[formField]` inference for every existing usage.
|
|
767
|
+
* Everything else matches — same input skeleton, same overlay, same
|
|
768
|
+
* {@link WrDateAdapter} formats.
|
|
769
|
+
*
|
|
770
|
+
* - `'date'` (default) — popover renders a range calendar. Picking the second
|
|
771
|
+
* date closes the overlay.
|
|
772
|
+
* - `'datetime'` — popover adds a time stepper per end. Picking dates does NOT
|
|
773
|
+
* close, since the user typically sets the times next.
|
|
774
|
+
*
|
|
775
|
+
* A signal-forms native control: it implements
|
|
776
|
+
* `FormValueControl<WrDateRange | null>`, so `[formField]` binds straight to
|
|
777
|
+
* its `value` model. `[(value)]` works standalone, and classic `[(ngModel)]` /
|
|
778
|
+
* reactive forms keep working through Angular's bridge.
|
|
779
|
+
*
|
|
780
|
+
* Either end may be `null` while the range is half-picked. Out-of-order ends
|
|
781
|
+
* are swapped on commit, matching the calendar's own behaviour.
|
|
782
|
+
*
|
|
783
|
+
* @example
|
|
784
|
+
* ```html
|
|
785
|
+
* <!-- signal forms -->
|
|
786
|
+
* <wr-date-range-picker [formField]="form.period" format="dd.MM.yyyy" />
|
|
787
|
+
*
|
|
788
|
+
* <!-- standalone two-way binding -->
|
|
789
|
+
* <wr-date-range-picker [(value)]="period" [minDate]="minDate" />
|
|
790
|
+
*
|
|
791
|
+
* <!-- date + time on both ends -->
|
|
792
|
+
* <wr-date-range-picker mode="datetime" [(value)]="window" timeFormat="24h" />
|
|
793
|
+
* ```
|
|
794
|
+
*
|
|
795
|
+
* @see https://ngwr.dev/reference/components/date-picker
|
|
796
|
+
*/
|
|
797
|
+
class WrDateRangePicker {
|
|
798
|
+
/** Picker behavior — see class doc. @default 'date' */
|
|
799
|
+
mode = input('date', /* @ts-ignore */
|
|
800
|
+
...(ngDevMode ? [{ debugName: "mode" }] : /* istanbul ignore next */ []));
|
|
801
|
+
/**
|
|
802
|
+
* Format used for both display and parsing, on both ends. When `null`
|
|
803
|
+
* (default), it is derived from `mode` (`shortDate` / `shortDateTime`). Pass
|
|
804
|
+
* a named key or raw token string to override.
|
|
805
|
+
*/
|
|
806
|
+
format = input(null, /* @ts-ignore */
|
|
807
|
+
...(ngDevMode ? [{ debugName: "format" }] : /* istanbul ignore next */ []));
|
|
808
|
+
/** Placeholder for the start input. */
|
|
809
|
+
startPlaceholder = input('', /* @ts-ignore */
|
|
810
|
+
...(ngDevMode ? [{ debugName: "startPlaceholder" }] : /* istanbul ignore next */ []));
|
|
811
|
+
/** Placeholder for the end input. */
|
|
812
|
+
endPlaceholder = input('', /* @ts-ignore */
|
|
813
|
+
...(ngDevMode ? [{ debugName: "endPlaceholder" }] : /* istanbul ignore next */ []));
|
|
814
|
+
/** Glyph rendered between the two inputs. @default '–' */
|
|
815
|
+
separator = input('–', /* @ts-ignore */
|
|
816
|
+
...(ngDevMode ? [{ debugName: "separator" }] : /* istanbul ignore next */ []));
|
|
817
|
+
// Named `minDate` / `maxDate`, not `min` / `max`: `FormUiControl` reserves
|
|
818
|
+
// those slots for the control's own value type, which here is a range — a
|
|
819
|
+
// `min` typed as a range makes no sense. Matches `WrValidators.minDate`.
|
|
820
|
+
/** Min selectable date, applied to both ends. */
|
|
821
|
+
minDate = input(null, /* @ts-ignore */
|
|
822
|
+
...(ngDevMode ? [{ debugName: "minDate" }] : /* istanbul ignore next */ []));
|
|
823
|
+
/** Max selectable date, applied to both ends. */
|
|
824
|
+
maxDate = input(null, /* @ts-ignore */
|
|
825
|
+
...(ngDevMode ? [{ debugName: "maxDate" }] : /* istanbul ignore next */ []));
|
|
826
|
+
/** Predicate to disable specific dates (forwarded to the calendar). */
|
|
827
|
+
dateFilter = input(null, /* @ts-ignore */
|
|
828
|
+
...(ngDevMode ? [{ debugName: "dateFilter" }] : /* istanbul ignore next */ []));
|
|
829
|
+
/** Time-panel 12 / 24-hour format. Applies in `datetime` mode. @default 'auto' */
|
|
830
|
+
timeFormat = input('auto', /* @ts-ignore */
|
|
831
|
+
...(ngDevMode ? [{ debugName: "timeFormat" }] : /* istanbul ignore next */ []));
|
|
832
|
+
/** Render the seconds column. Applies in `datetime` mode. @default false */
|
|
833
|
+
showSeconds = input(false, { ...(ngDevMode ? { debugName: "showSeconds" } : /* istanbul ignore next */ {}), transform: coerceBooleanProperty });
|
|
834
|
+
/** Minute / second step for the time panels. @default 1 */
|
|
835
|
+
step = input(1, { ...(ngDevMode ? { debugName: "step" } : /* istanbul ignore next */ {}), transform: (v) => Math.max(1, coerceNumberProperty(v, 1)) });
|
|
836
|
+
/**
|
|
837
|
+
* Disable interaction. Bound automatically from the field's disabled state
|
|
838
|
+
* when used with `[formField]`.
|
|
839
|
+
*
|
|
840
|
+
* @default false
|
|
841
|
+
*/
|
|
842
|
+
disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: coerceBooleanProperty });
|
|
843
|
+
/** Read-only — inputs not typeable, but the trigger icon still opens the overlay. @default false */
|
|
844
|
+
readonly = input(false, { ...(ngDevMode ? { debugName: "readonly" } : /* istanbul ignore next */ {}), transform: coerceBooleanProperty });
|
|
845
|
+
adapter = inject(WrDateAdapter);
|
|
846
|
+
overlay = inject(WR_OVERLAY);
|
|
847
|
+
scrollStrategies = inject(ScrollStrategyOptions);
|
|
848
|
+
host = inject(ElementRef);
|
|
849
|
+
destroyRef = inject(DestroyRef);
|
|
850
|
+
startEl = viewChild.required('startInput', /* @ts-ignore */
|
|
851
|
+
...(ngDevMode ? [{ debugName: "startEl" }] : /* istanbul ignore next */ []));
|
|
852
|
+
/** The picked range. Bound by `[formField]`, or two-way via `[(value)]`. */
|
|
853
|
+
value = model(null, /* @ts-ignore */
|
|
854
|
+
...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
|
|
855
|
+
/** Emitted on blur so a bound field can mark itself touched. */
|
|
856
|
+
touch = output();
|
|
857
|
+
/** Text currently in each input (may be partial / invalid mid-type). */
|
|
858
|
+
startText = signal('', /* @ts-ignore */
|
|
859
|
+
...(ngDevMode ? [{ debugName: "startText" }] : /* istanbul ignore next */ []));
|
|
860
|
+
endText = signal('', /* @ts-ignore */
|
|
861
|
+
...(ngDevMode ? [{ debugName: "endText" }] : /* istanbul ignore next */ []));
|
|
862
|
+
/** Whether the popover is currently open. */
|
|
863
|
+
overlayOpen = signal(false, /* @ts-ignore */
|
|
864
|
+
...(ngDevMode ? [{ debugName: "overlayOpen" }] : /* istanbul ignore next */ []));
|
|
865
|
+
/** Resolved format — falls back to a mode-appropriate default. */
|
|
866
|
+
resolvedFormat = computed(() => this.format() ? String(this.format()) : this.mode() === 'datetime' ? 'shortDateTime' : 'shortDate', /* @ts-ignore */
|
|
867
|
+
...(ngDevMode ? [{ debugName: "resolvedFormat" }] : /* istanbul ignore next */ []));
|
|
868
|
+
isDateTime = computed(() => this.mode() === 'datetime', /* @ts-ignore */
|
|
869
|
+
...(ngDevMode ? [{ debugName: "isDateTime" }] : /* istanbul ignore next */ []));
|
|
870
|
+
triggerLabel = readI18nText('datePicker.openRange', 'Open range calendar');
|
|
871
|
+
startLabel = readI18nText('datePicker.rangeStart', 'Range start');
|
|
872
|
+
endLabel = readI18nText('datePicker.rangeEnd', 'Range end');
|
|
873
|
+
classes = computed(() => {
|
|
874
|
+
const parts = ['wr-date-range-picker', `wr-date-range-picker--${this.mode()}`];
|
|
875
|
+
if (this.disabled())
|
|
876
|
+
parts.push('wr-date-range-picker--disabled');
|
|
877
|
+
return parts.join(' ');
|
|
878
|
+
}, /* @ts-ignore */
|
|
879
|
+
...(ngDevMode ? [{ debugName: "classes" }] : /* istanbul ignore next */ []));
|
|
880
|
+
overlayRef = null;
|
|
881
|
+
/** Live panel ref while the popover is open — lets typed edits move the
|
|
882
|
+
* displayed month in real time (not just on reopen). */
|
|
883
|
+
panelRef = signal(null, /* @ts-ignore */
|
|
884
|
+
...(ngDevMode ? [{ debugName: "panelRef" }] : /* istanbul ignore next */ []));
|
|
885
|
+
/** Last range we pushed into the model ourselves, so the sync effect can skip
|
|
886
|
+
* the echo of our own edits and never reformat text mid-type. */
|
|
887
|
+
lastValue = null;
|
|
888
|
+
constructor() {
|
|
889
|
+
this.destroyRef.onDestroy(() => this.dispose());
|
|
890
|
+
// Mirror external writes to `value` into the two display texts.
|
|
891
|
+
effect(() => {
|
|
892
|
+
const v = this.value();
|
|
893
|
+
untracked(() => {
|
|
894
|
+
if (this.sameRange(v, this.lastValue))
|
|
895
|
+
return;
|
|
896
|
+
this.lastValue = v;
|
|
897
|
+
this.startText.set(this.display(v?.[0] ?? null));
|
|
898
|
+
this.endText.set(this.display(v?.[1] ?? null));
|
|
899
|
+
});
|
|
900
|
+
});
|
|
901
|
+
// Keep the open panel in step with the model.
|
|
902
|
+
effect(() => {
|
|
903
|
+
const ref = this.panelRef();
|
|
904
|
+
if (ref)
|
|
905
|
+
ref.setInput('value', this.value() ?? [null, null]);
|
|
906
|
+
});
|
|
907
|
+
// …and with every other input. Pushing these only at attach time meant a
|
|
908
|
+
// `[minDate]` (or filter, or time format) that changed while the popover was
|
|
909
|
+
// open silently kept the stale constraint until the next reopen.
|
|
910
|
+
effect(() => {
|
|
911
|
+
const ref = this.panelRef();
|
|
912
|
+
if (!ref)
|
|
913
|
+
return;
|
|
914
|
+
ref.setInput('min', this.minDate());
|
|
915
|
+
ref.setInput('max', this.maxDate());
|
|
916
|
+
ref.setInput('dateFilter', this.dateFilter());
|
|
917
|
+
ref.setInput('withTime', this.isDateTime());
|
|
918
|
+
ref.setInput('timeFormat', this.timeFormat());
|
|
919
|
+
ref.setInput('showSeconds', this.showSeconds());
|
|
920
|
+
ref.setInput('step', this.step());
|
|
921
|
+
});
|
|
922
|
+
}
|
|
923
|
+
// Template handlers
|
|
924
|
+
onStartInput(event) {
|
|
925
|
+
this.onRangeInput(event, 0);
|
|
926
|
+
}
|
|
927
|
+
onEndInput(event) {
|
|
928
|
+
this.onRangeInput(event, 1);
|
|
929
|
+
}
|
|
930
|
+
onBlur(event) {
|
|
931
|
+
// Moving between the two ends is still one interaction — only report the
|
|
932
|
+
// control as touched once focus actually leaves it, or tabbing from start to
|
|
933
|
+
// end would mark a bound field touched mid-entry.
|
|
934
|
+
const next = event.relatedTarget;
|
|
935
|
+
if (!next || !this.host.nativeElement.contains(next))
|
|
936
|
+
this.touch.emit();
|
|
937
|
+
// Typing leaves the ends in whatever order they were entered; settle it
|
|
938
|
+
// here, once the user has stopped.
|
|
939
|
+
const [start, end] = this.commitRange(this.current(), { normalise: true });
|
|
940
|
+
// Reformat to canonical on blur (cleans up `1/5/25` → `1/5/2025`).
|
|
941
|
+
this.startText.set(this.display(start));
|
|
942
|
+
this.endText.set(this.display(end));
|
|
943
|
+
}
|
|
944
|
+
/** Called by an input's click — opens the overlay if it isn't open already. */
|
|
945
|
+
openOnInput() {
|
|
946
|
+
if (this.disabled() || this.readonly() || this.overlayRef)
|
|
947
|
+
return;
|
|
948
|
+
this.openOverlay();
|
|
949
|
+
}
|
|
950
|
+
toggleOverlay() {
|
|
951
|
+
// `readonly` blocks the calendar too — otherwise the inputs refuse typing
|
|
952
|
+
// while the popover happily edits the same value.
|
|
953
|
+
if (this.disabled() || this.readonly())
|
|
954
|
+
return;
|
|
955
|
+
if (this.overlayRef)
|
|
956
|
+
this.closeOverlay();
|
|
957
|
+
else
|
|
958
|
+
this.openOverlay();
|
|
959
|
+
}
|
|
960
|
+
// Input parsing
|
|
961
|
+
onRangeInput(event, end) {
|
|
962
|
+
const raw = event.target.value;
|
|
963
|
+
(end === 0 ? this.startText : this.endText).set(raw);
|
|
964
|
+
const [start, finish] = this.current();
|
|
965
|
+
if (!raw) {
|
|
966
|
+
this.commitRange(end === 0 ? [null, finish] : [start, null], { normalise: false });
|
|
967
|
+
return;
|
|
968
|
+
}
|
|
969
|
+
const parsed = this.adapter.parse(raw, this.resolvedFormat());
|
|
970
|
+
if (!parsed || !this.adapter.isValid(parsed) || this.isOutOfBounds(parsed))
|
|
971
|
+
return;
|
|
972
|
+
// Never reorder mid-keystroke: a half-typed date can parse to an
|
|
973
|
+
// out-of-order value, and swapping there would yank the text the user is
|
|
974
|
+
// still typing over to the other input. Ordering is settled on blur.
|
|
975
|
+
this.commitRange(end === 0 ? [parsed, finish] : [start, parsed], { normalise: false });
|
|
976
|
+
}
|
|
977
|
+
// Overlay
|
|
978
|
+
openOverlay() {
|
|
979
|
+
if (this.overlayRef)
|
|
980
|
+
return;
|
|
981
|
+
const positionStrategy = this.overlay
|
|
982
|
+
.position()
|
|
983
|
+
.flexibleConnectedTo(this.host)
|
|
984
|
+
.withPositions([
|
|
985
|
+
{ originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4 },
|
|
986
|
+
{ originX: 'start', originY: 'top', overlayX: 'start', overlayY: 'bottom', offsetY: -4 },
|
|
987
|
+
{ originX: 'end', originY: 'bottom', overlayX: 'end', overlayY: 'top', offsetY: 4 },
|
|
988
|
+
{ originX: 'end', originY: 'top', overlayX: 'end', overlayY: 'bottom', offsetY: -4 },
|
|
989
|
+
])
|
|
990
|
+
.withPush(true);
|
|
991
|
+
this.overlayRef = this.overlay.create({
|
|
992
|
+
positionStrategy,
|
|
993
|
+
scrollStrategy: this.scrollStrategies.reposition(),
|
|
994
|
+
panelClass: 'wr-date-picker-overlay',
|
|
995
|
+
});
|
|
996
|
+
this.overlayOpen.set(true);
|
|
997
|
+
const ref = this.overlayRef.attach(new ComponentPortal(WrDateRangePanel));
|
|
998
|
+
ref.setInput('value', this.value() ?? [null, null]);
|
|
999
|
+
this.panelRef.set(ref);
|
|
1000
|
+
ref.instance.changed.subscribe(next => {
|
|
1001
|
+
const committed = this.commitRange(next, { normalise: true });
|
|
1002
|
+
// A complete date-only range is the end of the interaction; a datetime
|
|
1003
|
+
// range still needs its hours, so the panel stays put.
|
|
1004
|
+
if (!this.isDateTime() && committed[0] && committed[1])
|
|
1005
|
+
this.closeOverlay();
|
|
1006
|
+
});
|
|
1007
|
+
this.overlayRef
|
|
1008
|
+
.outsidePointerEvents()
|
|
1009
|
+
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
1010
|
+
.subscribe(event => {
|
|
1011
|
+
if (this.host.nativeElement.contains(event.target))
|
|
1012
|
+
return;
|
|
1013
|
+
this.closeOverlay();
|
|
1014
|
+
});
|
|
1015
|
+
this.overlayRef
|
|
1016
|
+
.keydownEvents()
|
|
1017
|
+
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
1018
|
+
.subscribe(event => {
|
|
1019
|
+
if (event.key === 'Escape') {
|
|
1020
|
+
event.preventDefault();
|
|
1021
|
+
this.closeOverlay();
|
|
1022
|
+
this.startEl().nativeElement.focus();
|
|
1023
|
+
}
|
|
1024
|
+
});
|
|
1025
|
+
}
|
|
1026
|
+
/**
|
|
1027
|
+
* Close the popover. `restoreFocus` returns focus to the start input — needed
|
|
1028
|
+
* when the overlay is dismissed while focus lives inside it (picking the
|
|
1029
|
+
* second date), because disposing then leaves focus on a removed element and
|
|
1030
|
+
* the browser falls back to `<body>`.
|
|
1031
|
+
*/
|
|
1032
|
+
closeOverlay(restoreFocus = false) {
|
|
1033
|
+
const inside = this.overlayRef?.overlayElement.contains(this.host.nativeElement.ownerDocument.activeElement);
|
|
1034
|
+
this.dispose();
|
|
1035
|
+
if (restoreFocus || inside)
|
|
1036
|
+
this.startEl().nativeElement.focus();
|
|
1037
|
+
}
|
|
1038
|
+
dispose() {
|
|
1039
|
+
if (this.overlayRef) {
|
|
1040
|
+
this.overlayRef.dispose();
|
|
1041
|
+
this.overlayRef = null;
|
|
1042
|
+
}
|
|
1043
|
+
this.panelRef.set(null);
|
|
1044
|
+
this.overlayOpen.set(false);
|
|
1045
|
+
}
|
|
1046
|
+
// Commit
|
|
1047
|
+
/**
|
|
1048
|
+
* Push to the model and refresh the text of whichever ends changed. Returns
|
|
1049
|
+
* what was actually committed.
|
|
1050
|
+
*
|
|
1051
|
+
* `normalise` swaps out-of-order ends. The calendar and blur ask for it; a
|
|
1052
|
+
* keystroke does not, because reordering while the user is still typing would
|
|
1053
|
+
* move their in-progress text into the other input.
|
|
1054
|
+
*/
|
|
1055
|
+
commitRange(next, options) {
|
|
1056
|
+
const normalised = options.normalise ? this.normalise(next) : next;
|
|
1057
|
+
// Only write when a date actually moved. `normalise()` allocates a fresh
|
|
1058
|
+
// tuple on every call and `model()` compares by reference, so an
|
|
1059
|
+
// unconditional write would emit on every blur — enough to mark a bound
|
|
1060
|
+
// `[formField]` dirty just by tabbing through the two inputs.
|
|
1061
|
+
if (!this.sameRange(normalised, this.value())) {
|
|
1062
|
+
this.lastValue = normalised;
|
|
1063
|
+
this.value.set(normalised);
|
|
1064
|
+
}
|
|
1065
|
+
// Only rewrite the text of an end whose date moved out from under it —
|
|
1066
|
+
// otherwise a half-typed date would be reformatted on every keystroke.
|
|
1067
|
+
if (!this.sameDate(normalised[0], this.parseText(this.startText()))) {
|
|
1068
|
+
this.startText.set(this.display(normalised[0]));
|
|
1069
|
+
}
|
|
1070
|
+
if (!this.sameDate(normalised[1], this.parseText(this.endText()))) {
|
|
1071
|
+
this.endText.set(this.display(normalised[1]));
|
|
1072
|
+
}
|
|
1073
|
+
return normalised;
|
|
1074
|
+
}
|
|
1075
|
+
// Helpers
|
|
1076
|
+
current() {
|
|
1077
|
+
return this.value() ?? [null, null];
|
|
1078
|
+
}
|
|
1079
|
+
display(date) {
|
|
1080
|
+
return date && this.adapter.isValid(date) ? this.adapter.format(date, this.resolvedFormat()) : '';
|
|
1081
|
+
}
|
|
1082
|
+
parseText(raw) {
|
|
1083
|
+
if (!raw)
|
|
1084
|
+
return null;
|
|
1085
|
+
const parsed = this.adapter.parse(raw, this.resolvedFormat());
|
|
1086
|
+
return parsed && this.adapter.isValid(parsed) ? parsed : null;
|
|
1087
|
+
}
|
|
1088
|
+
/** Keep `[start, end]` in chronological order once both ends exist. */
|
|
1089
|
+
normalise(range) {
|
|
1090
|
+
const [start, end] = range;
|
|
1091
|
+
if (start && end && this.compareEnds(start, end) > 0)
|
|
1092
|
+
return [end, start];
|
|
1093
|
+
return [start, end];
|
|
1094
|
+
}
|
|
1095
|
+
/**
|
|
1096
|
+
* Chronological comparison at the precision the mode needs. The adapter's
|
|
1097
|
+
* `compareDate` is day-precision by contract, which would leave an inverted
|
|
1098
|
+
* same-day `datetime` range (18:00 → 09:00) unswapped.
|
|
1099
|
+
*/
|
|
1100
|
+
compareEnds(a, b) {
|
|
1101
|
+
const byDay = this.adapter.compareDate(a, b);
|
|
1102
|
+
if (byDay !== 0 || !this.isDateTime())
|
|
1103
|
+
return byDay;
|
|
1104
|
+
const clock = (d) => this.adapter.getHours(d) * 3600 + this.adapter.getMinutes(d) * 60 + this.adapter.getSeconds(d);
|
|
1105
|
+
return clock(a) - clock(b);
|
|
1106
|
+
}
|
|
1107
|
+
/**
|
|
1108
|
+
* Whether a typed date must be rejected. Covers `dateFilter` as well as the
|
|
1109
|
+
* bounds — the calendar disables filtered days, so accepting them from the
|
|
1110
|
+
* keyboard would make the two entry paths disagree.
|
|
1111
|
+
*/
|
|
1112
|
+
isOutOfBounds(date) {
|
|
1113
|
+
const min = this.minDate();
|
|
1114
|
+
if (min && this.adapter.compareDate(date, min) < 0)
|
|
1115
|
+
return true;
|
|
1116
|
+
const max = this.maxDate();
|
|
1117
|
+
if (max && this.adapter.compareDate(date, max) > 0)
|
|
1118
|
+
return true;
|
|
1119
|
+
const filter = this.dateFilter();
|
|
1120
|
+
if (filter && !filter(date))
|
|
1121
|
+
return true;
|
|
1122
|
+
return false;
|
|
1123
|
+
}
|
|
1124
|
+
sameDate(a, b) {
|
|
1125
|
+
if (a === b)
|
|
1126
|
+
return true;
|
|
1127
|
+
if (!a || !b)
|
|
1128
|
+
return false;
|
|
1129
|
+
return a.getTime() === b.getTime();
|
|
1130
|
+
}
|
|
1131
|
+
sameRange(a, b) {
|
|
1132
|
+
if (a === b)
|
|
1133
|
+
return true;
|
|
1134
|
+
if (!a || !b)
|
|
1135
|
+
return false;
|
|
1136
|
+
return this.sameDate(a[0], b[0]) && this.sameDate(a[1], b[1]);
|
|
1137
|
+
}
|
|
1138
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: WrDateRangePicker, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1139
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", 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 } }, outputs: { value: "valueChange", touch: "touch" }, host: { properties: { "class": "classes()" } }, viewQueries: [{ propertyName: "startEl", first: true, predicate: ["startInput"], 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 [attr.aria-haspopup]=\"'dialog'\"\n [attr.aria-expanded]=\"overlayOpen()\"\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 [attr.aria-haspopup]=\"'dialog'\"\n [attr.aria-expanded]=\"overlayOpen()\"\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 [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", 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 });
|
|
1140
|
+
}
|
|
1141
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: WrDateRangePicker, decorators: [{
|
|
1142
|
+
type: Component,
|
|
1143
|
+
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 [attr.aria-haspopup]=\"'dialog'\"\n [attr.aria-expanded]=\"overlayOpen()\"\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 [attr.aria-haspopup]=\"'dialog'\"\n [attr.aria-expanded]=\"overlayOpen()\"\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 [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" }]
|
|
1144
|
+
}], 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"] }] } });
|
|
1145
|
+
|
|
638
1146
|
/**
|
|
639
1147
|
* Generated bundle index. Do not edit.
|
|
640
1148
|
*/
|
|
641
1149
|
|
|
642
|
-
export { WrDatePicker };
|
|
1150
|
+
export { WrDatePicker, WrDateRangePicker };
|
|
643
1151
|
//# sourceMappingURL=ngwr-date-picker.mjs.map
|