ngwr 14.6.0 → 14.8.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/alert/styles/_index.scss +14 -3
- package/avatar/styles/_index.scss +44 -0
- package/bar-chart/styles/_index.scss +1 -0
- package/calendar-heatmap/styles/_index.scss +1 -0
- package/cascader/styles/_rules.scss +86 -18
- package/collapse/styles/_index.scss +0 -1
- package/date-picker/styles/_index.scss +30 -0
- package/density/styles/_index.scss +4 -2
- package/donut-chart/styles/_index.scss +10 -0
- package/dropdown/styles/_index.scss +67 -5
- package/event-calendar/styles/_index.scss +209 -35
- package/fesm2022/ngwr-avatar.mjs +19 -2
- package/fesm2022/ngwr-avatar.mjs.map +1 -1
- package/fesm2022/ngwr-bar-chart.mjs +29 -4
- package/fesm2022/ngwr-bar-chart.mjs.map +1 -1
- package/fesm2022/ngwr-calendar-heatmap.mjs +59 -4
- package/fesm2022/ngwr-calendar-heatmap.mjs.map +1 -1
- package/fesm2022/ngwr-cascader.mjs +2 -2
- package/fesm2022/ngwr-cascader.mjs.map +1 -1
- package/fesm2022/ngwr-checkbox.mjs +12 -2
- package/fesm2022/ngwr-checkbox.mjs.map +1 -1
- package/fesm2022/ngwr-date-picker.mjs +705 -46
- package/fesm2022/ngwr-date-picker.mjs.map +1 -1
- package/fesm2022/ngwr-donut-chart.mjs +46 -4
- package/fesm2022/ngwr-donut-chart.mjs.map +1 -1
- package/fesm2022/ngwr-dropdown.mjs +166 -17
- package/fesm2022/ngwr-dropdown.mjs.map +1 -1
- package/fesm2022/ngwr-event-calendar.mjs +9 -2
- package/fesm2022/ngwr-event-calendar.mjs.map +1 -1
- package/fesm2022/ngwr-form.mjs +52 -1
- package/fesm2022/ngwr-form.mjs.map +1 -1
- package/fesm2022/ngwr-gauge.mjs +33 -4
- package/fesm2022/ngwr-gauge.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-ar.mjs +2 -0
- package/fesm2022/ngwr-i18n-ar.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-cs.mjs +2 -0
- package/fesm2022/ngwr-i18n-cs.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-de.mjs +2 -0
- package/fesm2022/ngwr-i18n-de.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-es.mjs +2 -0
- package/fesm2022/ngwr-i18n-es.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-fr.mjs +2 -0
- package/fesm2022/ngwr-i18n-fr.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-he.mjs +2 -0
- package/fesm2022/ngwr-i18n-he.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-hi.mjs +2 -0
- package/fesm2022/ngwr-i18n-hi.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-id.mjs +2 -0
- package/fesm2022/ngwr-i18n-id.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-it.mjs +2 -0
- package/fesm2022/ngwr-i18n-it.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-ja.mjs +2 -0
- package/fesm2022/ngwr-i18n-ja.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-ko.mjs +2 -0
- package/fesm2022/ngwr-i18n-ko.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-nl.mjs +2 -0
- package/fesm2022/ngwr-i18n-nl.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-pl.mjs +2 -0
- package/fesm2022/ngwr-i18n-pl.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-pt.mjs +2 -0
- package/fesm2022/ngwr-i18n-pt.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-ru.mjs +2 -0
- package/fesm2022/ngwr-i18n-ru.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-sv.mjs +2 -0
- package/fesm2022/ngwr-i18n-sv.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-tr.mjs +2 -0
- package/fesm2022/ngwr-i18n-tr.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-uk.mjs +2 -0
- package/fesm2022/ngwr-i18n-uk.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-vi.mjs +2 -0
- package/fesm2022/ngwr-i18n-vi.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-zh-TW.mjs +2 -0
- package/fesm2022/ngwr-i18n-zh-TW.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-zh.mjs +2 -0
- package/fesm2022/ngwr-i18n-zh.mjs.map +1 -1
- package/fesm2022/ngwr-image-cropper.mjs +149 -62
- package/fesm2022/ngwr-image-cropper.mjs.map +1 -1
- package/fesm2022/ngwr-input-number.mjs +12 -0
- package/fesm2022/ngwr-input-number.mjs.map +1 -1
- package/fesm2022/ngwr-lightbox.mjs +2 -2
- package/fesm2022/ngwr-lightbox.mjs.map +1 -1
- package/fesm2022/ngwr-line-chart.mjs +16 -5
- package/fesm2022/ngwr-line-chart.mjs.map +1 -1
- package/fesm2022/ngwr-meter-group-testing.mjs +7 -3
- package/fesm2022/ngwr-meter-group-testing.mjs.map +1 -1
- package/fesm2022/ngwr-meter-group.mjs +24 -4
- package/fesm2022/ngwr-meter-group.mjs.map +1 -1
- package/fesm2022/ngwr-page-header.mjs +10 -2
- package/fesm2022/ngwr-page-header.mjs.map +1 -1
- package/fesm2022/ngwr-popover.mjs +569 -7
- package/fesm2022/ngwr-popover.mjs.map +1 -1
- package/fesm2022/ngwr-select.mjs +73 -22
- package/fesm2022/ngwr-select.mjs.map +1 -1
- package/fesm2022/ngwr-sidebar.mjs +7 -0
- package/fesm2022/ngwr-sidebar.mjs.map +1 -1
- package/fesm2022/ngwr-sparkline.mjs +82 -8
- package/fesm2022/ngwr-sparkline.mjs.map +1 -1
- package/fesm2022/ngwr-table.mjs +1 -1
- package/fesm2022/ngwr-table.mjs.map +1 -1
- package/fesm2022/ngwr-toolbar.mjs +7 -0
- package/fesm2022/ngwr-toolbar.mjs.map +1 -1
- package/fesm2022/ngwr-version.mjs +1 -1
- package/fesm2022/ngwr-version.mjs.map +1 -1
- package/form/styles/_index.scss +11 -1
- package/gauge/styles/_index.scss +1 -0
- package/i18n/ar.json +3 -1
- package/i18n/cs.json +3 -1
- package/i18n/de.json +3 -1
- package/i18n/en.json +3 -1
- package/i18n/es.json +3 -1
- package/i18n/fr.json +3 -1
- package/i18n/he.json +3 -1
- package/i18n/hi.json +3 -1
- package/i18n/id.json +3 -1
- package/i18n/it.json +3 -1
- package/i18n/ja.json +3 -1
- package/i18n/ko.json +3 -1
- package/i18n/nl.json +3 -1
- package/i18n/pl.json +3 -1
- package/i18n/pt.json +3 -1
- package/i18n/ru.json +3 -1
- package/i18n/sv.json +3 -1
- package/i18n/tr.json +3 -1
- package/i18n/uk.json +3 -1
- package/i18n/vi.json +3 -1
- package/i18n/zh-TW.json +3 -1
- package/i18n/zh.json +3 -1
- package/input/styles/_index.scss +27 -2
- package/input-number/styles/_index.scss +69 -0
- package/lightbox/styles/_rules.scss +7 -1
- package/line-chart/styles/_index.scss +30 -2
- package/llms-full.txt +3 -3
- package/meter-group/styles/_index.scss +1 -0
- package/overlay/styles/_arrow.scss +27 -2
- package/package.json +1 -1
- package/page-header/styles/_index.scss +37 -1
- package/pagination/styles/_index.scss +9 -5
- package/popover/styles/_index.scss +73 -5
- package/select/styles/_index.scss +64 -13
- package/sidebar/styles/_index.scss +34 -2
- package/sparkline/styles/_index.scss +19 -0
- package/table/styles/_index.scss +55 -0
- package/toast/styles/_index.scss +9 -3
- package/toolbar/styles/_index.scss +35 -1
- package/tree/styles/_index.scss +45 -9
- package/types/ngwr-avatar.d.ts +15 -1
- package/types/ngwr-bar-chart.d.ts +19 -1
- package/types/ngwr-calendar-heatmap.d.ts +29 -1
- package/types/ngwr-checkbox.d.ts +2 -0
- package/types/ngwr-date-picker.d.ts +308 -43
- package/types/ngwr-donut-chart.d.ts +24 -1
- package/types/ngwr-dropdown.d.ts +22 -2
- package/types/ngwr-event-calendar.d.ts +8 -0
- package/types/ngwr-form.d.ts +35 -5
- package/types/ngwr-gauge.d.ts +20 -1
- package/types/ngwr-image-cropper.d.ts +48 -0
- package/types/ngwr-input-number.d.ts +7 -0
- package/types/ngwr-line-chart.d.ts +9 -1
- package/types/ngwr-meter-group-testing.d.ts +3 -2
- package/types/ngwr-meter-group.d.ts +14 -1
- package/types/ngwr-page-header.d.ts +10 -2
- package/types/ngwr-popover.d.ts +154 -3
- package/types/ngwr-select.d.ts +63 -6
- package/types/ngwr-sidebar.d.ts +7 -0
- package/types/ngwr-sparkline.d.ts +43 -1
- package/types/ngwr-toolbar.d.ts +7 -0
- package/types/ngwr-version.d.ts +1 -1
|
@@ -3,14 +3,15 @@ import { coerceBooleanProperty, coerceNumberProperty } from '@angular/cdk/coerci
|
|
|
3
3
|
import { ScrollStrategyOptions } from '@angular/cdk/overlay';
|
|
4
4
|
import { ComponentPortal } from '@angular/cdk/portal';
|
|
5
5
|
import * as i0 from '@angular/core';
|
|
6
|
-
import { input, inject, ElementRef, model, output, signal, computed, afterNextRender, effect, untracked, ViewEncapsulation, Component, DestroyRef, Injector, viewChild, Directive } from '@angular/core';
|
|
6
|
+
import { input, inject, ElementRef, model, output, signal, computed, afterNextRender, effect, untracked, ViewEncapsulation, Component, Injectable, DestroyRef, Injector, viewChild, Directive } from '@angular/core';
|
|
7
7
|
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
8
8
|
import { WrCalendar } from 'ngwr/calendar';
|
|
9
9
|
import { WrDateAdapter, WR_DATE_LOCALE } from 'ngwr/date';
|
|
10
|
-
import { readI18nText, useI18nText } from 'ngwr/i18n';
|
|
10
|
+
import { readI18nText, useI18nFormatter, useI18nText } from 'ngwr/i18n';
|
|
11
11
|
import { WrInput, WrInputGroup, WrInputSuffix } from 'ngwr/input';
|
|
12
12
|
import { WR_OVERLAY, WrOutsideClick, wrFollowDirection } from 'ngwr/overlay';
|
|
13
13
|
import { clamp } from 'ngwr/utils';
|
|
14
|
+
import { transformedValue } from '@angular/forms/signals';
|
|
14
15
|
import { WR_FORM_FIELD } from 'ngwr/form';
|
|
15
16
|
|
|
16
17
|
/**
|
|
@@ -403,6 +404,200 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImpor
|
|
|
403
404
|
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" }]
|
|
404
405
|
}], 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"] }] } });
|
|
405
406
|
|
|
407
|
+
/**
|
|
408
|
+
* @license
|
|
409
|
+
*
|
|
410
|
+
* Use of this source code is governed by an MIT-style license that can be
|
|
411
|
+
* found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE
|
|
412
|
+
*/
|
|
413
|
+
/** Per-picker live-region ids — see {@link WrDateInputField.statusId}. */
|
|
414
|
+
let statusUid = 0;
|
|
415
|
+
/**
|
|
416
|
+
* The refusals both pickers raise, from one place — the calendar, the single
|
|
417
|
+
* picker and the range picker have to agree on what "refused" means, and the
|
|
418
|
+
* message has to be the one `<wr-form-field>` would show for the same key.
|
|
419
|
+
*
|
|
420
|
+
* Must be called from an injection context.
|
|
421
|
+
*
|
|
422
|
+
* @internal
|
|
423
|
+
*/
|
|
424
|
+
function useDateInputRefusals() {
|
|
425
|
+
const adapter = inject(WrDateAdapter);
|
|
426
|
+
// The same keys and the same English as `WR_FORM_ERROR_FALLBACKS`: the message a
|
|
427
|
+
// picker announces on its own and the one a surrounding field renders are one
|
|
428
|
+
// sentence, whichever of the two ends up saying it.
|
|
429
|
+
const formatText = useI18nFormatter('validation.dateFormat', 'Expected format: {{example}}');
|
|
430
|
+
const minText = useI18nFormatter('validation.minDate', 'Choose a later date.');
|
|
431
|
+
const maxText = useI18nFormatter('validation.maxDate', 'Choose an earlier date.');
|
|
432
|
+
const filterText = useI18nFormatter('validation.dateFilter', 'This date is not available.');
|
|
433
|
+
return {
|
|
434
|
+
unreadable(format) {
|
|
435
|
+
// The 31st of December, at 18:45: a day past 12 cannot be mistaken for a month,
|
|
436
|
+
// so the example shows the ORDER of the fields as well as their separators —
|
|
437
|
+
// which is the whole question for someone typing `07.09.1994` into a field
|
|
438
|
+
// that reads `9/7/1994`. The year is the current one so it never looks stale.
|
|
439
|
+
const reference = adapter.setTime(adapter.createDate(adapter.getYear(adapter.today()), 11, 31), 18, 45, 0);
|
|
440
|
+
const example = adapter.format(reference, format);
|
|
441
|
+
return { kind: 'dateFormat', example, message: formatText({ example }) };
|
|
442
|
+
},
|
|
443
|
+
outOfBounds(date, { min, max, filter }) {
|
|
444
|
+
if (min && adapter.compareDate(date, min) < 0)
|
|
445
|
+
return { kind: 'minDate', date, min, message: minText() };
|
|
446
|
+
if (max && adapter.compareDate(date, max) > 0)
|
|
447
|
+
return { kind: 'maxDate', date, max, message: maxText() };
|
|
448
|
+
if (filter && !filter(date))
|
|
449
|
+
return { kind: 'dateFilter', date, message: filterText() };
|
|
450
|
+
return null;
|
|
451
|
+
},
|
|
452
|
+
};
|
|
453
|
+
}
|
|
454
|
+
/**
|
|
455
|
+
* Hands the picker's refusals to the form it is bound to, so a refused entry makes
|
|
456
|
+
* the FORM invalid rather than only the field's border.
|
|
457
|
+
*
|
|
458
|
+
* Angular's `transformedValue` is the public way a custom control reports parse
|
|
459
|
+
* errors: bound with `[formField]` they join the field's errors, and bound with
|
|
460
|
+
* `[formControl]`, `formControlName` or `[(ngModel)]` they become a validator on
|
|
461
|
+
* the control. It is used here for that channel ONLY. The picker keeps its own text
|
|
462
|
+
* model — the echo guard, and the range picker's per-end ordering, are older and
|
|
463
|
+
* pinned — so `parse` never returns a `value`, which Angular documents as leaving the
|
|
464
|
+
* model untouched.
|
|
465
|
+
*
|
|
466
|
+
* The returned function reports the picker's WHOLE current list, every time. The form
|
|
467
|
+
* hears it from the keystroke — the picker's own wait for Enter or blur is about what
|
|
468
|
+
* it shows, and a form whose validity lagged the text in its field could submit the old
|
|
469
|
+
* value under text nobody meant.
|
|
470
|
+
*
|
|
471
|
+
* Angular's copy does not simply hold what was reported. It is a `linkedSignal` over the
|
|
472
|
+
* model, so ANY model write clears it, and a form reset clears it without a model write
|
|
473
|
+
* at all:
|
|
474
|
+
*
|
|
475
|
+
* - a write of the picker's own is followed by a report of its own, synchronously — the
|
|
476
|
+
* range picker committing one end while the other still holds refused text is the
|
|
477
|
+
* case that needs it;
|
|
478
|
+
* - a write from outside replaces the text a refusal was about, and the picker reports
|
|
479
|
+
* its emptied list;
|
|
480
|
+
* - what is left is a reset (`FormControl.reset()`, a Signal Forms field's `reset()`):
|
|
481
|
+
* the raw signal moved while the model did not. Angular has thrown the error away, and
|
|
482
|
+
* `onReset` is where the picker throws away the text it was about.
|
|
483
|
+
*
|
|
484
|
+
* Unbound, nothing resets it and it costs a signal and an effect.
|
|
485
|
+
*
|
|
486
|
+
* Must be called from an injection context.
|
|
487
|
+
*
|
|
488
|
+
* @internal
|
|
489
|
+
*/
|
|
490
|
+
function useFormParseErrors(value, onReset) {
|
|
491
|
+
const channel = transformedValue(value, {
|
|
492
|
+
parse: errors => ({ error: errors.length ? errors : undefined }),
|
|
493
|
+
format: () => [],
|
|
494
|
+
});
|
|
495
|
+
/** What the raw signal held after the last report, and the model it was reported against. */
|
|
496
|
+
let held = untracked(channel);
|
|
497
|
+
let against = untracked(value);
|
|
498
|
+
const report = (errors) => {
|
|
499
|
+
channel.set(errors);
|
|
500
|
+
held = untracked(channel);
|
|
501
|
+
against = untracked(value);
|
|
502
|
+
};
|
|
503
|
+
effect(() => {
|
|
504
|
+
const now = channel();
|
|
505
|
+
untracked(() => {
|
|
506
|
+
if (now === held)
|
|
507
|
+
return;
|
|
508
|
+
held = now;
|
|
509
|
+
if (Object.is(value(), against))
|
|
510
|
+
onReset();
|
|
511
|
+
against = value();
|
|
512
|
+
});
|
|
513
|
+
});
|
|
514
|
+
return report;
|
|
515
|
+
}
|
|
516
|
+
/**
|
|
517
|
+
* What a picker's own text input sees as its `<wr-form-field>`: the real field, with
|
|
518
|
+
* the refusal on show added to it. Provided per picker, and handed to the input
|
|
519
|
+
* through `viewProviders`.
|
|
520
|
+
*
|
|
521
|
+
* `wrInput` writes `aria-invalid` and `aria-describedby` from the field it injects, and
|
|
522
|
+
* a second binding on either attribute leaves it holding whichever wrote last — so
|
|
523
|
+
* the picker cannot bind them beside `wrInput` while a field is there. It stands
|
|
524
|
+
* between the two instead. The input then reads ONE state: invalid when the field
|
|
525
|
+
* has an error OR the picker is showing a refusal, and described by the field's
|
|
526
|
+
* message together with the picker's own reason while the field is not yet saying it.
|
|
527
|
+
*
|
|
528
|
+
* "Not yet" is ordinary rather than rare. A refusal reaches a bound form as a parse
|
|
529
|
+
* error, and `<wr-form-field>` shows an error only once its control is touched or
|
|
530
|
+
* dirty; a reactive `FormControl` learns of it through `updateValueAndValidity` with
|
|
531
|
+
* `emitEvent: false`, which the field does not hear until the control's next event.
|
|
532
|
+
* Until the field says it, the picker does — so the refusal is never silent,
|
|
533
|
+
* whichever of the two ends up announcing it.
|
|
534
|
+
*
|
|
535
|
+
* With no field around the picker, there is nothing to stand between and `context` is
|
|
536
|
+
* `null`: the input's `wrInput` then writes neither attribute, and the picker binds
|
|
537
|
+
* them itself.
|
|
538
|
+
*
|
|
539
|
+
* @internal
|
|
540
|
+
*/
|
|
541
|
+
class WrDateInputField {
|
|
542
|
+
/** The surrounding `<wr-form-field>` — skipping the picker's own node, whose view it is providing for. */
|
|
543
|
+
outer = inject(WR_FORM_FIELD, { optional: true, skipSelf: true });
|
|
544
|
+
/** Id of the picker's live region, which doubles as the reason `aria-describedby` points at. */
|
|
545
|
+
statusId = `wr-date-picker-status-${++statusUid}`;
|
|
546
|
+
source = signal(null, /* @ts-ignore */
|
|
547
|
+
...(ngDevMode ? [{ debugName: "source" }] : /* istanbul ignore next */ []));
|
|
548
|
+
/** The refusal on show for the input the field describes — the only input, or a range's start. */
|
|
549
|
+
described = computed(() => this.source()?.() ?? null, /* @ts-ignore */
|
|
550
|
+
...(ngDevMode ? [{ debugName: "described" }] : /* istanbul ignore next */ []));
|
|
551
|
+
/**
|
|
552
|
+
* Hand over the signal holding that refusal. Called from the picker's constructor:
|
|
553
|
+
* this service is built before the component it serves, so it cannot read the
|
|
554
|
+
* component's state at its own construction.
|
|
555
|
+
*/
|
|
556
|
+
describe(source) {
|
|
557
|
+
this.source.set(source);
|
|
558
|
+
}
|
|
559
|
+
/** Whether the field is already rendering a message for this kind of error. */
|
|
560
|
+
saysAlready(error) {
|
|
561
|
+
return this.outer?.errorKeys().includes(error.kind) ?? false;
|
|
562
|
+
}
|
|
563
|
+
context = this.outer ? this.wrap(this.outer) : null;
|
|
564
|
+
wrap(outer) {
|
|
565
|
+
return {
|
|
566
|
+
controlId: outer.controlId,
|
|
567
|
+
adoptControlId: id => outer.adoptControlId(id),
|
|
568
|
+
labelId: outer.labelId,
|
|
569
|
+
hintId: outer.hintId,
|
|
570
|
+
errorKeys: computed(() => {
|
|
571
|
+
const keys = outer.errorKeys();
|
|
572
|
+
const own = this.described();
|
|
573
|
+
return own && !keys.includes(own.kind) ? [...keys, own.kind] : keys;
|
|
574
|
+
}),
|
|
575
|
+
describedBy: computed(() => {
|
|
576
|
+
const own = this.described();
|
|
577
|
+
const ids = [outer.describedBy(), own && !this.saysAlready(own) ? this.statusId : null].filter(Boolean);
|
|
578
|
+
return ids.length ? ids.join(' ') : null;
|
|
579
|
+
}),
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: WrDateInputField, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
583
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: WrDateInputField });
|
|
584
|
+
}
|
|
585
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: WrDateInputField, decorators: [{
|
|
586
|
+
type: Injectable
|
|
587
|
+
}] });
|
|
588
|
+
/**
|
|
589
|
+
* The view half of {@link WrDateInputField}: what the picker's own template sees as
|
|
590
|
+
* its field. A VIEW provider, so it reaches the text inputs the picker renders and
|
|
591
|
+
* never content projected into it; the host half is `WrDateInputField` itself, in
|
|
592
|
+
* the component's `providers`.
|
|
593
|
+
*
|
|
594
|
+
* @internal
|
|
595
|
+
*/
|
|
596
|
+
const WR_DATE_INPUT_FIELD_VIEW_PROVIDER = {
|
|
597
|
+
provide: WR_FORM_FIELD,
|
|
598
|
+
useFactory: () => inject(WrDateInputField).context,
|
|
599
|
+
};
|
|
600
|
+
|
|
406
601
|
/**
|
|
407
602
|
* @license
|
|
408
603
|
*
|
|
@@ -410,6 +605,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImpor
|
|
|
410
605
|
* found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE
|
|
411
606
|
*/
|
|
412
607
|
let panelUid = 0;
|
|
608
|
+
/** How long a live region stays blank before a repeated refusal is written back. */
|
|
609
|
+
const SILENCE_MS$1 = 100;
|
|
413
610
|
/**
|
|
414
611
|
* Unified date / time / date-time picker. Same `<input>` + popover skeleton
|
|
415
612
|
* for every mode — the overlay content swaps based on `[mode]`:
|
|
@@ -427,8 +624,12 @@ let panelUid = 0;
|
|
|
427
624
|
* `[(ngModel)]` / reactive forms keep working through Angular's bridge. Value
|
|
428
625
|
* type is `Date | null` for every mode.
|
|
429
626
|
*
|
|
430
|
-
* Parses the input on every keystroke
|
|
431
|
-
*
|
|
627
|
+
* Parses the input on every keystroke and emits only a date it can read and the
|
|
628
|
+
* calendar would accept. Anything else is REFUSED, never guessed: the bound value
|
|
629
|
+
* stays where it was, and the text stays in the field so it can be corrected.
|
|
630
|
+
* Enter and leaving the field turn a refusal visible — see {@link inputError}.
|
|
631
|
+
* A committed date is re-formatted canonical on blur and on Enter. Format is
|
|
632
|
+
* driven by {@link WrDateAdapter}: it
|
|
432
633
|
* accepts both named keys (`'shortDate'`, `'mediumDateTime'`, …) and raw
|
|
433
634
|
* token strings (`'dd.MM.yyyy'`, `'HH:mm'`). When `format` is left at the
|
|
434
635
|
* default (`null`), the picker derives the right named key from `mode`:
|
|
@@ -611,10 +812,99 @@ class WrDatePicker {
|
|
|
611
812
|
return this.panelLabelDate();
|
|
612
813
|
}, /* @ts-ignore */
|
|
613
814
|
...(ngDevMode ? [{ debugName: "resolvedPanelLabel" }] : /* istanbul ignore next */ []));
|
|
815
|
+
refusals = useDateInputRefusals();
|
|
816
|
+
reportToForm = useFormParseErrors(this.value, () => this.onFormReset());
|
|
817
|
+
inputField = inject(WrDateInputField);
|
|
818
|
+
/**
|
|
819
|
+
* Why the text in the field was not committed — `null` while the text is the value.
|
|
820
|
+
* Raised on the keystroke that makes the text unusable, and reported to a bound form
|
|
821
|
+
* from that keystroke on.
|
|
822
|
+
*/
|
|
823
|
+
refusal = signal(null, /* @ts-ignore */
|
|
824
|
+
...(ngDevMode ? [{ debugName: "refusal" }] : /* istanbul ignore next */ []));
|
|
825
|
+
/**
|
|
826
|
+
* Whether the refusal is on show. A refusal is raised on the keystroke but shown
|
|
827
|
+
* only once the user has finished — on Enter, or on leaving the field — because
|
|
828
|
+
* every date is unreadable for most of the time it takes to type one, and a field
|
|
829
|
+
* that turned red on the first digit would be telling the user off for typing.
|
|
830
|
+
* Once shown it follows the text live, so a correction clears it immediately.
|
|
831
|
+
*
|
|
832
|
+
* Leaving counts wherever focus goes, including by way of the open panel: moving
|
|
833
|
+
* into the panel waits, since a pick there replaces the text, but focus that leaves
|
|
834
|
+
* the panel for anything other than the field — or a panel that closes without a
|
|
835
|
+
* pick and without handing focus back to the field — has left too.
|
|
836
|
+
*/
|
|
837
|
+
revealed = signal(false, /* @ts-ignore */
|
|
838
|
+
...(ngDevMode ? [{ debugName: "revealed" }] : /* istanbul ignore next */ []));
|
|
839
|
+
/**
|
|
840
|
+
* Why the text in the field is not the bound value, once the picker is showing it —
|
|
841
|
+
* `null` while the text is the value, and `null` while the user is still typing.
|
|
842
|
+
*
|
|
843
|
+
* A refusal is raised on the keystroke that makes the text unreadable (`dateFormat`),
|
|
844
|
+
* out of `min` / `max` (`minDate` / `maxDate`) or rejected by `dateFilter`, and goes
|
|
845
|
+
* on show on Enter or when the user leaves the field — the same moment the border
|
|
846
|
+
* turns and the reason is announced, so a message rendered from this signal never
|
|
847
|
+
* disagrees with them. Once on show it follows the text, and it clears the moment the
|
|
848
|
+
* text becomes a date the picker commits, is emptied, or is replaced by a pick from
|
|
849
|
+
* the panel, a value written from outside, or a form reset. The value itself is never
|
|
850
|
+
* touched by a refusal.
|
|
851
|
+
*
|
|
852
|
+
* A bound form does NOT wait: `[formField]`, `[formControl]`, `formControlName` and
|
|
853
|
+
* `[(ngModel)]` see the refusal as a parse error from the keystroke, so the form is
|
|
854
|
+
* invalid the whole time the field holds text it could not use, and
|
|
855
|
+
* `<wr-form-field>` shows `message` by its own rule — once the control is touched or
|
|
856
|
+
* dirty. Read this through a template reference when the picker is bound with
|
|
857
|
+
* `[(value)]` alone and the reason should be on screen.
|
|
858
|
+
*
|
|
859
|
+
* @example
|
|
860
|
+
* ```html
|
|
861
|
+
* <wr-date-picker #due [(value)]="due" format="dd.MM.yyyy" />
|
|
862
|
+
* @if (due.inputError(); as error) {
|
|
863
|
+
* <small>{{ error.message }}</small>
|
|
864
|
+
* }
|
|
865
|
+
* ```
|
|
866
|
+
*/
|
|
867
|
+
inputError = computed(() => (this.revealed() ? this.refusal() : null), /* @ts-ignore */
|
|
868
|
+
...(ngDevMode ? [{ debugName: "inputError" }] : /* istanbul ignore next */ []));
|
|
869
|
+
/**
|
|
870
|
+
* Blanked briefly so Enter on an unchanged refusal is announced again. A live region
|
|
871
|
+
* does not repeat text it already holds, and blanking it for one render is not
|
|
872
|
+
* enough: both changes land in the same task, and the accessibility tree sees
|
|
873
|
+
* only the end state. The gap is a timer for that reason — the CDK's
|
|
874
|
+
* `LiveAnnouncer` clears its region the same way.
|
|
875
|
+
*/
|
|
876
|
+
silenced = signal(false, /* @ts-ignore */
|
|
877
|
+
...(ngDevMode ? [{ debugName: "silenced" }] : /* istanbul ignore next */ []));
|
|
878
|
+
silenceTimer;
|
|
879
|
+
statusId = this.inputField.statusId;
|
|
880
|
+
/**
|
|
881
|
+
* What the picker's own live region says: the refusal on show, unless a surrounding
|
|
882
|
+
* `<wr-form-field>` is already rendering a message for it — that message is a
|
|
883
|
+
* `role="alert"`, and a second voice would repeat it.
|
|
884
|
+
*/
|
|
885
|
+
announcement = computed(() => {
|
|
886
|
+
const shown = this.inputError();
|
|
887
|
+
if (!shown || this.silenced() || this.inputField.saysAlready(shown))
|
|
888
|
+
return '';
|
|
889
|
+
return shown.message;
|
|
890
|
+
}, /* @ts-ignore */
|
|
891
|
+
...(ngDevMode ? [{ debugName: "announcement" }] : /* istanbul ignore next */ []));
|
|
892
|
+
/**
|
|
893
|
+
* `aria-invalid` and `aria-describedby` for the text input, bound only with NO
|
|
894
|
+
* field around the picker. Inside one, `wrInput` writes both from the field — see
|
|
895
|
+
* `WrDateInputField` for why the picker feeds them through it rather than binding
|
|
896
|
+
* a second writer beside it.
|
|
897
|
+
*/
|
|
898
|
+
ownAriaInvalid = computed(() => !this.inputField.outer && this.inputError() ? 'true' : null, /* @ts-ignore */
|
|
899
|
+
...(ngDevMode ? [{ debugName: "ownAriaInvalid" }] : /* istanbul ignore next */ []));
|
|
900
|
+
ownDescribedBy = computed(() => !this.inputField.outer && this.announcement() ? this.statusId : null, /* @ts-ignore */
|
|
901
|
+
...(ngDevMode ? [{ debugName: "ownDescribedBy" }] : /* istanbul ignore next */ []));
|
|
614
902
|
classes = computed(() => {
|
|
615
903
|
const parts = ['wr-date-picker', `wr-date-picker--${this.mode()}`];
|
|
616
904
|
if (this.disabled())
|
|
617
905
|
parts.push('wr-date-picker--disabled');
|
|
906
|
+
if (this.inputError())
|
|
907
|
+
parts.push('wr-date-picker--invalid');
|
|
618
908
|
return parts.join(' ');
|
|
619
909
|
}, /* @ts-ignore */
|
|
620
910
|
...(ngDevMode ? [{ debugName: "classes" }] : /* istanbul ignore next */ []));
|
|
@@ -638,7 +928,10 @@ class WrDatePicker {
|
|
|
638
928
|
* `[format]` / `[mode]` change makes it stale with the value untouched. */
|
|
639
929
|
lastFormat = null;
|
|
640
930
|
constructor() {
|
|
641
|
-
this.destroyRef.onDestroy(() =>
|
|
931
|
+
this.destroyRef.onDestroy(() => {
|
|
932
|
+
this.dispose();
|
|
933
|
+
clearTimeout(this.silenceTimer);
|
|
934
|
+
});
|
|
642
935
|
// Mirror external writes to `value` (from `[formField]`, `[(value)]`, or a
|
|
643
936
|
// classic-forms bridge) into the display text — this is the old
|
|
644
937
|
// `writeValue` body. The echo of our own edits is skipped, and a
|
|
@@ -657,12 +950,20 @@ class WrDatePicker {
|
|
|
657
950
|
const v = this.value();
|
|
658
951
|
const fmt = this.resolvedFormat();
|
|
659
952
|
untracked(() => {
|
|
953
|
+
// Our own commits hand `value` the very object `commitValue` remembered, so a
|
|
954
|
+
// DIFFERENT object holding the same date came from outside — a reset to a fresh
|
|
955
|
+
// copy of the day, say. That is no reason to re-render text the user is
|
|
956
|
+
// typing, but it does replace text the picker refused.
|
|
957
|
+
const outside = v !== this.lastValue;
|
|
660
958
|
const echo = this.sameDate(v, this.lastValue) && fmt === this.lastFormat;
|
|
661
959
|
this.lastValue = v;
|
|
662
960
|
this.lastFormat = fmt;
|
|
663
|
-
if (echo)
|
|
961
|
+
if (echo && !(outside && this.refusal()))
|
|
664
962
|
return;
|
|
665
963
|
this.text.set(v && this.adapter.isValid(v) ? this.adapter.format(v, fmt) : '');
|
|
964
|
+
// The text the refusal was about is gone — replaced by a value written from
|
|
965
|
+
// outside, or re-rendered in a new format.
|
|
966
|
+
this.refuse(null);
|
|
666
967
|
});
|
|
667
968
|
});
|
|
668
969
|
// A panel that is up when `disabled` arrives has to go. `readonly` is
|
|
@@ -679,6 +980,8 @@ class WrDatePicker {
|
|
|
679
980
|
this.closeOverlay();
|
|
680
981
|
});
|
|
681
982
|
});
|
|
983
|
+
// The input's field (see `WrDateInputField`) marks it invalid for the refusal on show.
|
|
984
|
+
this.inputField.describe(this.inputError);
|
|
682
985
|
// While the calendar popover is open, push every valid typed value into it
|
|
683
986
|
// so the displayed month follows the input live (the calendar snaps its
|
|
684
987
|
// own viewDate to the bound `date`).
|
|
@@ -731,12 +1034,21 @@ class WrDatePicker {
|
|
|
731
1034
|
this.text.set(raw);
|
|
732
1035
|
if (!raw) {
|
|
733
1036
|
this.commitValue(null);
|
|
1037
|
+
this.refuse(null);
|
|
734
1038
|
return;
|
|
735
1039
|
}
|
|
736
1040
|
const parsed = this.adapter.parse(raw, this.resolvedFormat());
|
|
737
|
-
if (parsed
|
|
738
|
-
this.
|
|
1041
|
+
if (!parsed || !this.adapter.isValid(parsed)) {
|
|
1042
|
+
this.refuse(this.refusals.unreadable(this.resolvedFormat()));
|
|
1043
|
+
return;
|
|
1044
|
+
}
|
|
1045
|
+
const outOfBounds = this.outOfBounds(parsed);
|
|
1046
|
+
if (outOfBounds) {
|
|
1047
|
+
this.refuse(outOfBounds);
|
|
1048
|
+
return;
|
|
739
1049
|
}
|
|
1050
|
+
this.commitValue(this.withKeptDate(parsed));
|
|
1051
|
+
this.refuse(null);
|
|
740
1052
|
}
|
|
741
1053
|
/**
|
|
742
1054
|
* In `time` mode the typed string carries no date, so the adapter fills one in — and
|
|
@@ -752,9 +1064,58 @@ class WrDatePicker {
|
|
|
752
1064
|
return parsed;
|
|
753
1065
|
return this.adapter.setTime(current, this.adapter.getHours(parsed), this.adapter.getMinutes(parsed), this.adapter.getSeconds(parsed));
|
|
754
1066
|
}
|
|
755
|
-
onBlur() {
|
|
1067
|
+
onBlur(event) {
|
|
756
1068
|
this.touch.emit();
|
|
1069
|
+
// A refused entry STAYS. Blur used to overwrite it with the last committed date,
|
|
1070
|
+
// which is how a refusal became invisible: `07.09.1994` typed into a field bounded
|
|
1071
|
+
// to 2026 simply turned back into the old date the moment the user moved on, with
|
|
1072
|
+
// nothing to say it had been refused or why. The text is what they have to
|
|
1073
|
+
// correct, and the value underneath was never changed. Focus moving INTO the open
|
|
1074
|
+
// panel is not leaving — a day click there is about to replace the text anyway;
|
|
1075
|
+
// `onPaneFocusOut` and `closeOverlay` decide when that visit ends.
|
|
1076
|
+
if (this.refusal()) {
|
|
1077
|
+
const next = event.relatedTarget;
|
|
1078
|
+
if (!next || !this.overlayRef?.overlayElement.contains(next))
|
|
1079
|
+
this.reveal();
|
|
1080
|
+
return;
|
|
1081
|
+
}
|
|
757
1082
|
// Reformat to canonical on blur (cleans up `1/5/25` → `1/5/2025`).
|
|
1083
|
+
this.settleText();
|
|
1084
|
+
}
|
|
1085
|
+
/**
|
|
1086
|
+
* Enter is "this is the date". A date the picker committed is re-formatted
|
|
1087
|
+
* canonical and the panel closes; text it refused turns invalid on the spot —
|
|
1088
|
+
* border, `aria-invalid`, the reason announced — and emits `touch`, which is what
|
|
1089
|
+
* makes a surrounding `<wr-form-field>` show its message. The panel closes in both
|
|
1090
|
+
* cases: it opens directly below the field, which is exactly where that message
|
|
1091
|
+
* renders.
|
|
1092
|
+
*
|
|
1093
|
+
* Never `preventDefault`: Enter inside a `<form>` still submits it, and a form
|
|
1094
|
+
* bound to this picker is invalid while the refusal stands.
|
|
1095
|
+
*/
|
|
1096
|
+
onEnter() {
|
|
1097
|
+
// Read before closing: a close can put the refusal on show itself.
|
|
1098
|
+
const wasShown = this.revealed();
|
|
1099
|
+
if (this.overlayRef)
|
|
1100
|
+
this.closeOverlay();
|
|
1101
|
+
const refusal = this.refusal();
|
|
1102
|
+
if (!refusal) {
|
|
1103
|
+
this.settleText();
|
|
1104
|
+
return;
|
|
1105
|
+
}
|
|
1106
|
+
this.touch.emit();
|
|
1107
|
+
if (wasShown) {
|
|
1108
|
+
// Already on show, so the live region already holds this sentence — and a live
|
|
1109
|
+
// region does not repeat text it already has. Blank it briefly so a second
|
|
1110
|
+
// Enter is answered rather than met with silence.
|
|
1111
|
+
this.silenced.set(true);
|
|
1112
|
+
clearTimeout(this.silenceTimer);
|
|
1113
|
+
this.silenceTimer = setTimeout(() => this.silenced.set(false), SILENCE_MS$1);
|
|
1114
|
+
}
|
|
1115
|
+
this.revealed.set(true);
|
|
1116
|
+
}
|
|
1117
|
+
/** Show the committed value canonically, or commit an emptied field as `null`. */
|
|
1118
|
+
settleText() {
|
|
758
1119
|
const v = this.value();
|
|
759
1120
|
if (v && this.adapter.isValid(v)) {
|
|
760
1121
|
this.text.set(this.adapter.format(v, this.resolvedFormat()));
|
|
@@ -763,6 +1124,45 @@ class WrDatePicker {
|
|
|
763
1124
|
this.commitValue(null);
|
|
764
1125
|
}
|
|
765
1126
|
}
|
|
1127
|
+
/**
|
|
1128
|
+
* Record why the current text was not committed, or that it was. Clearing a
|
|
1129
|
+
* refusal also takes it off show, so the next one waits for Enter or blur again.
|
|
1130
|
+
*/
|
|
1131
|
+
refuse(next) {
|
|
1132
|
+
this.refusal.set(next);
|
|
1133
|
+
if (!next)
|
|
1134
|
+
this.revealed.set(false);
|
|
1135
|
+
this.reportToForm(next ? [next] : []);
|
|
1136
|
+
}
|
|
1137
|
+
/** Put a standing refusal on show — the user has left the field. */
|
|
1138
|
+
reveal() {
|
|
1139
|
+
if (this.refusal())
|
|
1140
|
+
this.revealed.set(true);
|
|
1141
|
+
}
|
|
1142
|
+
/**
|
|
1143
|
+
* The bound form was reset while the model stayed where it was — Angular has thrown
|
|
1144
|
+
* the parse error away, so the text it was about goes too, and the field shows the
|
|
1145
|
+
* value again. (A reset that MOVES the model reaches the text through the sync
|
|
1146
|
+
* effect instead, like any other outside write.)
|
|
1147
|
+
*/
|
|
1148
|
+
onFormReset() {
|
|
1149
|
+
if (!this.refusal())
|
|
1150
|
+
return;
|
|
1151
|
+
const v = this.value();
|
|
1152
|
+
this.text.set(v && this.adapter.isValid(v) ? this.adapter.format(v, this.resolvedFormat()) : '');
|
|
1153
|
+
this.refuse(null);
|
|
1154
|
+
}
|
|
1155
|
+
/**
|
|
1156
|
+
* Focus leaving the open panel. Moving within it, or back to the text field, is
|
|
1157
|
+
* still the same visit; anywhere else — the page, the trigger, the next control — is
|
|
1158
|
+
* leaving, so a refusal that waited while focus went into the panel shows now.
|
|
1159
|
+
*/
|
|
1160
|
+
onPaneFocusOut = (event) => {
|
|
1161
|
+
const next = event.relatedTarget;
|
|
1162
|
+
if (next && (this.overlayRef?.overlayElement.contains(next) || next === this.inputEl().nativeElement))
|
|
1163
|
+
return;
|
|
1164
|
+
this.reveal();
|
|
1165
|
+
};
|
|
766
1166
|
/** Called by the input's click — opens the overlay if it isn't open already. */
|
|
767
1167
|
openOnInput() {
|
|
768
1168
|
if (this.disabled() || this.overlayRef)
|
|
@@ -795,6 +1195,10 @@ class WrDatePicker {
|
|
|
795
1195
|
onFieldKey(event) {
|
|
796
1196
|
if (this.disabled())
|
|
797
1197
|
return;
|
|
1198
|
+
if (event.key === 'Enter') {
|
|
1199
|
+
this.onEnter();
|
|
1200
|
+
return;
|
|
1201
|
+
}
|
|
798
1202
|
const vertical = event.key === 'ArrowDown' || event.key === 'ArrowUp';
|
|
799
1203
|
if (!vertical)
|
|
800
1204
|
return;
|
|
@@ -860,6 +1264,7 @@ class WrDatePicker {
|
|
|
860
1264
|
pane.setAttribute('role', 'dialog');
|
|
861
1265
|
pane.setAttribute('aria-modal', 'false');
|
|
862
1266
|
pane.setAttribute('aria-label', this.resolvedPanelLabel());
|
|
1267
|
+
pane.addEventListener('focusout', this.onPaneFocusOut);
|
|
863
1268
|
this.overlayOpen.set(true);
|
|
864
1269
|
// Dispatch by mode — pick the panel + wire its emissions.
|
|
865
1270
|
const m = this.mode();
|
|
@@ -950,6 +1355,7 @@ class WrDatePicker {
|
|
|
950
1355
|
commit(next) {
|
|
951
1356
|
this.text.set(this.adapter.format(next, this.resolvedFormat()));
|
|
952
1357
|
this.commitValue(next);
|
|
1358
|
+
this.refuse(null);
|
|
953
1359
|
}
|
|
954
1360
|
/** Push a value to the model while remembering it, so the sync effect treats
|
|
955
1361
|
* the resulting change as an echo and leaves the display text alone. */
|
|
@@ -981,9 +1387,18 @@ class WrDatePicker {
|
|
|
981
1387
|
closeOverlay() {
|
|
982
1388
|
const pane = this.overlayRef?.overlayElement;
|
|
983
1389
|
const inside = !!pane && pane.contains(document.activeElement);
|
|
1390
|
+
// Taking the pane out of the document moves focus without the user doing anything,
|
|
1391
|
+
// so it must not read as focus leaving the panel.
|
|
1392
|
+
pane?.removeEventListener('focusout', this.onPaneFocusOut);
|
|
984
1393
|
this.dispose();
|
|
985
1394
|
if (inside)
|
|
986
1395
|
this.restoreFocus();
|
|
1396
|
+
// A panel that closes is the end of the visit to it. Unless focus is back in the
|
|
1397
|
+
// field — Escape handing it there, or a close while the user never left it — the
|
|
1398
|
+
// user is somewhere else now, and a refusal that waited for the panel shows. A pick
|
|
1399
|
+
// has already cleared its refusal, so this only ever reaches text still refused.
|
|
1400
|
+
if (document.activeElement !== this.inputEl().nativeElement)
|
|
1401
|
+
this.reveal();
|
|
987
1402
|
}
|
|
988
1403
|
dispose() {
|
|
989
1404
|
if (this.overlayRef) {
|
|
@@ -997,22 +1412,13 @@ class WrDatePicker {
|
|
|
997
1412
|
this.autoFocusPanel = false;
|
|
998
1413
|
}
|
|
999
1414
|
// Helpers
|
|
1000
|
-
|
|
1415
|
+
outOfBounds(date) {
|
|
1001
1416
|
if (this.mode() === 'time')
|
|
1002
|
-
return
|
|
1003
|
-
const min = this.min();
|
|
1004
|
-
if (min && this.adapter.compareDate(date, min) < 0)
|
|
1005
|
-
return true;
|
|
1006
|
-
const max = this.max();
|
|
1007
|
-
if (max && this.adapter.compareDate(date, max) > 0)
|
|
1008
|
-
return true;
|
|
1417
|
+
return null;
|
|
1009
1418
|
// Covers `dateFilter` as well as the bounds — the calendar disables filtered
|
|
1010
1419
|
// days, so accepting them from the keyboard made the two entry paths
|
|
1011
1420
|
// disagree. Same check, same reason, as `wr-date-range-picker`.
|
|
1012
|
-
|
|
1013
|
-
if (filter && !filter(date))
|
|
1014
|
-
return true;
|
|
1015
|
-
return false;
|
|
1421
|
+
return this.refusals.outOfBounds(date, { min: this.min(), max: this.max(), filter: this.dateFilter() });
|
|
1016
1422
|
}
|
|
1017
1423
|
sameDate(a, b) {
|
|
1018
1424
|
if (a === b)
|
|
@@ -1022,11 +1428,11 @@ class WrDatePicker {
|
|
|
1022
1428
|
return a.getTime() === b.getTime();
|
|
1023
1429
|
}
|
|
1024
1430
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: WrDatePicker, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1025
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", 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: ["size", "rounded"] }, { kind: "component", type: WrInputGroup, selector: "wr-input-group", inputs: ["rounded"] }, { kind: "directive", type: WrInputSuffix, selector: "[wrInputSuffix]" }], encapsulation: i0.ViewEncapsulation.None });
|
|
1431
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", 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()" } }, providers: [WrDateInputField], 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 [attr.aria-invalid]=\"ownAriaInvalid()\"\n [attr.aria-describedby]=\"ownDescribedBy()\"\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($event)\"\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<!--\n Why a typed entry was refused, for a screen reader: written when the refusal is\n shown (Enter, or leaving the field) and emptied when the text is corrected. Silent\n inside a <wr-form-field> bound to the same control, whose message says it instead.\n-->\n<span class=\"wr-date-picker__sr-only\" role=\"status\" [id]=\"statusId\">{{ announcement() }}</span>\n", dependencies: [{ kind: "directive", type: WrInput, selector: "input[wrInput], textarea[wrInput]", inputs: ["size", "rounded"] }, { kind: "component", type: WrInputGroup, selector: "wr-input-group", inputs: ["rounded"] }, { kind: "directive", type: WrInputSuffix, selector: "[wrInputSuffix]" }], viewProviders: [WR_DATE_INPUT_FIELD_VIEW_PROVIDER], encapsulation: i0.ViewEncapsulation.None });
|
|
1026
1432
|
}
|
|
1027
1433
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: WrDatePicker, decorators: [{
|
|
1028
1434
|
type: Component,
|
|
1029
|
-
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" }]
|
|
1435
|
+
args: [{ selector: 'wr-date-picker', encapsulation: ViewEncapsulation.None, host: { '[class]': 'classes()' }, imports: [WrInput, WrInputGroup, WrInputSuffix], providers: [WrDateInputField], viewProviders: [WR_DATE_INPUT_FIELD_VIEW_PROVIDER], 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 [attr.aria-invalid]=\"ownAriaInvalid()\"\n [attr.aria-describedby]=\"ownDescribedBy()\"\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($event)\"\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<!--\n Why a typed entry was refused, for a screen reader: written when the refusal is\n shown (Enter, or leaving the field) and emptied when the text is corrected. Silent\n inside a <wr-form-field> bound to the same control, whose message says it instead.\n-->\n<span class=\"wr-date-picker__sr-only\" role=\"status\" [id]=\"statusId\">{{ announcement() }}</span>\n" }]
|
|
1030
1436
|
}], 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 }] }] } });
|
|
1031
1437
|
|
|
1032
1438
|
/**
|
|
@@ -1215,6 +1621,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImpor
|
|
|
1215
1621
|
*/
|
|
1216
1622
|
/** Per-instance popup ids, so `aria-controls` can point at one panel. */
|
|
1217
1623
|
let rangePanelUid = 0;
|
|
1624
|
+
/** How long a live region stays blank before a repeated refusal is written back. */
|
|
1625
|
+
const SILENCE_MS = 100;
|
|
1218
1626
|
/**
|
|
1219
1627
|
* Date-range picker — two text inputs sharing one range calendar.
|
|
1220
1628
|
*
|
|
@@ -1237,6 +1645,10 @@ let rangePanelUid = 0;
|
|
|
1237
1645
|
* Either end may be `null` while the range is half-picked. Out-of-order ends
|
|
1238
1646
|
* are swapped on commit, matching the calendar's own behaviour.
|
|
1239
1647
|
*
|
|
1648
|
+
* Each end refuses text it cannot use exactly as `<wr-date-picker>` does — never
|
|
1649
|
+
* guessing, keeping the text, and showing the refusal on Enter or when focus
|
|
1650
|
+
* leaves that end. See {@link inputErrors}.
|
|
1651
|
+
*
|
|
1240
1652
|
* @example
|
|
1241
1653
|
* ```html
|
|
1242
1654
|
* <!-- signal forms -->
|
|
@@ -1379,10 +1791,111 @@ class WrDateRangePicker {
|
|
|
1379
1791
|
return this.isDateTime() ? this.panelLabelRangeDateTime() : this.panelLabelRange();
|
|
1380
1792
|
}, /* @ts-ignore */
|
|
1381
1793
|
...(ngDevMode ? [{ debugName: "resolvedPanelLabel" }] : /* istanbul ignore next */ []));
|
|
1794
|
+
refusalBuilder = useDateInputRefusals();
|
|
1795
|
+
reportToForm = useFormParseErrors(this.value, () => this.onFormReset());
|
|
1796
|
+
inputField = inject(WrDateInputField);
|
|
1797
|
+
/** Why each end's text was not committed. */
|
|
1798
|
+
refusals = signal([null, null], /* @ts-ignore */
|
|
1799
|
+
...(ngDevMode ? [{ debugName: "refusals" }] : /* istanbul ignore next */ []));
|
|
1800
|
+
/**
|
|
1801
|
+
* Every refusal standing, on show or not, each marked with its end — what a bound form
|
|
1802
|
+
* hears, from the keystroke.
|
|
1803
|
+
*/
|
|
1804
|
+
raised = computed(() => {
|
|
1805
|
+
const [start, end] = this.refusals();
|
|
1806
|
+
const out = [];
|
|
1807
|
+
if (start)
|
|
1808
|
+
out.push({ ...start, end: 'start' });
|
|
1809
|
+
if (end)
|
|
1810
|
+
out.push({ ...end, end: 'end' });
|
|
1811
|
+
return out;
|
|
1812
|
+
}, /* @ts-ignore */
|
|
1813
|
+
...(ngDevMode ? [{ debugName: "raised" }] : /* istanbul ignore next */ []));
|
|
1814
|
+
/**
|
|
1815
|
+
* Which ends have their refusal on show — raised on the keystroke, shown on Enter
|
|
1816
|
+
* or when focus leaves that end, for the reason `wr-date-picker` gives at the same
|
|
1817
|
+
* name: a date is unreadable for most of the time it takes to type one. Leaving
|
|
1818
|
+
* includes leaving by way of the open panel, as it does there.
|
|
1819
|
+
*/
|
|
1820
|
+
revealed = signal([false, false], /* @ts-ignore */
|
|
1821
|
+
...(ngDevMode ? [{ debugName: "revealed" }] : /* istanbul ignore next */ []));
|
|
1822
|
+
shownErrors = computed(() => {
|
|
1823
|
+
const [start, end] = this.refusals();
|
|
1824
|
+
const [showStart, showEnd] = this.revealed();
|
|
1825
|
+
return [showStart ? start : null, showEnd ? end : null];
|
|
1826
|
+
}, /* @ts-ignore */
|
|
1827
|
+
...(ngDevMode ? [{ debugName: "shownErrors" }] : /* istanbul ignore next */ []));
|
|
1828
|
+
/**
|
|
1829
|
+
* Why the text in either field is not that end of the bound range, for the ends
|
|
1830
|
+
* whose refusal the picker is SHOWING — one entry per end, marked with the `end` it
|
|
1831
|
+
* came from, and empty while both fields hold their dates or the user is still
|
|
1832
|
+
* typing.
|
|
1833
|
+
*
|
|
1834
|
+
* An end's refusal is raised on the keystroke and goes on show on Enter or when
|
|
1835
|
+
* focus leaves that end — the moment its input turns invalid and the reason is
|
|
1836
|
+
* announced, so a message rendered from this never disagrees with them. It clears
|
|
1837
|
+
* the moment that end's text commits, is emptied, or is replaced by a pick from the
|
|
1838
|
+
* panel, a value written from outside, or a form reset.
|
|
1839
|
+
*
|
|
1840
|
+
* A bound form does NOT wait: it sees every refusal as a parse error from the
|
|
1841
|
+
* keystroke, so it is invalid the whole time either field holds text the picker
|
|
1842
|
+
* could not use — including while the OTHER end commits — and `<wr-form-field>`
|
|
1843
|
+
* shows `message` by its own rule. Read this through a template reference to put
|
|
1844
|
+
* the reason on screen for a picker bound with `[(value)]` alone.
|
|
1845
|
+
*/
|
|
1846
|
+
inputErrors = computed(() => {
|
|
1847
|
+
const [showStart, showEnd] = this.revealed();
|
|
1848
|
+
return this.raised().filter(error => (error.end === 'start' ? showStart : showEnd));
|
|
1849
|
+
}, /* @ts-ignore */
|
|
1850
|
+
...(ngDevMode ? [{ debugName: "inputErrors" }] : /* istanbul ignore next */ []));
|
|
1851
|
+
/**
|
|
1852
|
+
* Blanked briefly so Enter on an unchanged refusal is announced again. A live region
|
|
1853
|
+
* does not repeat text it already holds, and blanking it for one render is not
|
|
1854
|
+
* enough: both changes land in the same task, and the accessibility tree sees
|
|
1855
|
+
* only the end state. The gap is a timer for that reason — the CDK's
|
|
1856
|
+
* `LiveAnnouncer` clears its region the same way.
|
|
1857
|
+
*/
|
|
1858
|
+
silenced = signal(false, /* @ts-ignore */
|
|
1859
|
+
...(ngDevMode ? [{ debugName: "silenced" }] : /* istanbul ignore next */ []));
|
|
1860
|
+
silenceTimer;
|
|
1861
|
+
statusId = this.inputField.statusId;
|
|
1862
|
+
/**
|
|
1863
|
+
* The live region's text: every refusal on show that a surrounding `<wr-form-field>`
|
|
1864
|
+
* is not already rendering a message for, each sentence once.
|
|
1865
|
+
*/
|
|
1866
|
+
announcement = computed(() => {
|
|
1867
|
+
if (this.silenced())
|
|
1868
|
+
return '';
|
|
1869
|
+
const messages = this.shownErrors()
|
|
1870
|
+
.filter((error) => !!error && !this.inputField.saysAlready(error))
|
|
1871
|
+
.map(error => error.message);
|
|
1872
|
+
return [...new Set(messages)].join(' ');
|
|
1873
|
+
}, /* @ts-ignore */
|
|
1874
|
+
...(ngDevMode ? [{ debugName: "announcement" }] : /* istanbul ignore next */ []));
|
|
1875
|
+
/**
|
|
1876
|
+
* `aria-invalid` / `aria-describedby` for each input, where the picker binds them.
|
|
1877
|
+
*
|
|
1878
|
+
* The START input reads a surrounding field through `wrInput`, so inside one the
|
|
1879
|
+
* picker feeds its refusal through `WrDateInputField` rather than binding a second
|
|
1880
|
+
* writer beside `wrInput`, and binds these only with no field around it. The END
|
|
1881
|
+
* input is hidden from the field on purpose (see `wrDateRangeEnd`), so its `wrInput`
|
|
1882
|
+
* never writes either attribute and the picker always owns them: an unreadable end
|
|
1883
|
+
* date marks the end field, not only the start.
|
|
1884
|
+
*/
|
|
1885
|
+
startAriaInvalid = computed(() => !this.inputField.outer && this.shownErrors()[0] ? 'true' : null, /* @ts-ignore */
|
|
1886
|
+
...(ngDevMode ? [{ debugName: "startAriaInvalid" }] : /* istanbul ignore next */ []));
|
|
1887
|
+
endAriaInvalid = computed(() => (this.shownErrors()[1] ? 'true' : null), /* @ts-ignore */
|
|
1888
|
+
...(ngDevMode ? [{ debugName: "endAriaInvalid" }] : /* istanbul ignore next */ []));
|
|
1889
|
+
startDescribedBy = computed(() => !this.inputField.outer && this.shownErrors()[0] && this.announcement() ? this.statusId : null, /* @ts-ignore */
|
|
1890
|
+
...(ngDevMode ? [{ debugName: "startDescribedBy" }] : /* istanbul ignore next */ []));
|
|
1891
|
+
endDescribedBy = computed(() => this.shownErrors()[1] && this.announcement() ? this.statusId : null, /* @ts-ignore */
|
|
1892
|
+
...(ngDevMode ? [{ debugName: "endDescribedBy" }] : /* istanbul ignore next */ []));
|
|
1382
1893
|
classes = computed(() => {
|
|
1383
1894
|
const parts = ['wr-date-range-picker', `wr-date-range-picker--${this.mode()}`];
|
|
1384
1895
|
if (this.disabled())
|
|
1385
1896
|
parts.push('wr-date-range-picker--disabled');
|
|
1897
|
+
if (this.shownErrors().some(Boolean))
|
|
1898
|
+
parts.push('wr-date-range-picker--invalid');
|
|
1386
1899
|
return parts.join(' ');
|
|
1387
1900
|
}, /* @ts-ignore */
|
|
1388
1901
|
...(ngDevMode ? [{ debugName: "classes" }] : /* istanbul ignore next */ []));
|
|
@@ -1395,16 +1908,30 @@ class WrDateRangePicker {
|
|
|
1395
1908
|
* the echo of our own edits and never reformat text mid-type. */
|
|
1396
1909
|
lastValue = null;
|
|
1397
1910
|
constructor() {
|
|
1398
|
-
this.destroyRef.onDestroy(() =>
|
|
1399
|
-
|
|
1911
|
+
this.destroyRef.onDestroy(() => {
|
|
1912
|
+
this.dispose();
|
|
1913
|
+
clearTimeout(this.silenceTimer);
|
|
1914
|
+
});
|
|
1915
|
+
// The start input's field (see `WrDateInputField`) marks it invalid for the
|
|
1916
|
+
// start end's refusal on show; the end input carries its own.
|
|
1917
|
+
this.inputField.describe(computed(() => this.shownErrors()[0]));
|
|
1918
|
+
// Mirror external writes to `value` into the two display texts — and drop the
|
|
1919
|
+
// refusals, whose text that write has just replaced.
|
|
1400
1920
|
effect(() => {
|
|
1401
1921
|
const v = this.value();
|
|
1402
1922
|
untracked(() => {
|
|
1403
|
-
|
|
1923
|
+
// An equal range in a DIFFERENT tuple came from outside — `commitRange` hands the
|
|
1924
|
+
// model the very tuple it remembered. It leaves text being typed alone, but it
|
|
1925
|
+
// does replace text the picker refused.
|
|
1926
|
+
const outside = v !== this.lastValue;
|
|
1927
|
+
const [refusedStart, refusedEnd] = this.refusals();
|
|
1928
|
+
if (this.sameRange(v, this.lastValue) && !(outside && (refusedStart || refusedEnd)))
|
|
1404
1929
|
return;
|
|
1405
1930
|
this.lastValue = v;
|
|
1406
1931
|
this.startText.set(this.display(v?.[0] ?? null));
|
|
1407
1932
|
this.endText.set(this.display(v?.[1] ?? null));
|
|
1933
|
+
this.refuse(0, null);
|
|
1934
|
+
this.refuse(1, null);
|
|
1408
1935
|
});
|
|
1409
1936
|
});
|
|
1410
1937
|
// A panel that is up when `readonly` turns on has to go, and `disabled` is
|
|
@@ -1465,10 +1992,110 @@ class WrDateRangePicker {
|
|
|
1465
1992
|
// typed and moved it into the field they were tabbing into, leaving the old
|
|
1466
1993
|
// end date under their cursor. Same rule the time steppers follow.
|
|
1467
1994
|
const [start, end] = this.commitRange(this.current(), { normalise: leaving });
|
|
1468
|
-
// Reformat to canonical on blur (cleans up `1/5/25` → `1/5/2025`)
|
|
1995
|
+
// Reformat to canonical on blur (cleans up `1/5/25` → `1/5/2025`) — except a
|
|
1996
|
+
// refused end, whose text is what the user has to correct. Overwriting it with
|
|
1997
|
+
// the old date is how a refusal used to vanish without a word.
|
|
1998
|
+
const [refusedStart, refusedEnd] = this.refusals();
|
|
1999
|
+
if (!refusedStart)
|
|
2000
|
+
this.startText.set(this.display(start));
|
|
2001
|
+
if (!refusedEnd)
|
|
2002
|
+
this.endText.set(this.display(end));
|
|
2003
|
+
// Show the refusal of the end focus just left — both, once it leaves the pair.
|
|
2004
|
+
// Focus moving INTO the open panel is not leaving: a pick there replaces the text.
|
|
2005
|
+
if (next && this.overlayRef?.overlayElement.contains(next))
|
|
2006
|
+
return;
|
|
2007
|
+
const left = event.target === this.endEl().nativeElement ? 1 : 0;
|
|
2008
|
+
this.reveal(leaving ? [true, true] : left === 0 ? [true, false] : [false, true]);
|
|
2009
|
+
}
|
|
2010
|
+
/**
|
|
2011
|
+
* Enter in either field is "this is the range". Committed ends are settled — sorted
|
|
2012
|
+
* and re-formatted canonical — and the panel closes; a refused end turns invalid on
|
|
2013
|
+
* the spot, and `touch` is emitted so a surrounding `<wr-form-field>` shows its
|
|
2014
|
+
* message, which the panel would otherwise cover. Never `preventDefault`: Enter in a
|
|
2015
|
+
* `<form>` still submits it, and a bound form is invalid while a refusal stands.
|
|
2016
|
+
*/
|
|
2017
|
+
onEnter() {
|
|
2018
|
+
// Read before closing: a close can put refusals on show itself.
|
|
2019
|
+
const [shownStart, shownEnd] = this.revealed();
|
|
2020
|
+
if (this.overlayRef)
|
|
2021
|
+
this.closeOverlay();
|
|
2022
|
+
const [start, end] = this.commitRange(this.current(), { normalise: true });
|
|
2023
|
+
const [refusedStart, refusedEnd] = this.refusals();
|
|
2024
|
+
if (!refusedStart)
|
|
2025
|
+
this.startText.set(this.display(start));
|
|
2026
|
+
if (!refusedEnd)
|
|
2027
|
+
this.endText.set(this.display(end));
|
|
2028
|
+
if (!refusedStart && !refusedEnd)
|
|
2029
|
+
return;
|
|
2030
|
+
this.touch.emit();
|
|
2031
|
+
if ((!refusedStart || shownStart) && (!refusedEnd || shownEnd)) {
|
|
2032
|
+
// Everything refused is already on show, so the live region already holds this
|
|
2033
|
+
// text and would not repeat it. Blank it briefly so Enter is answered.
|
|
2034
|
+
this.silenced.set(true);
|
|
2035
|
+
clearTimeout(this.silenceTimer);
|
|
2036
|
+
this.silenceTimer = setTimeout(() => this.silenced.set(false), SILENCE_MS);
|
|
2037
|
+
}
|
|
2038
|
+
this.reveal([true, true]);
|
|
2039
|
+
}
|
|
2040
|
+
/** Put the refusals of the given ends on show; an end with no refusal stays clear. */
|
|
2041
|
+
reveal([start, end]) {
|
|
2042
|
+
const [refusedStart, refusedEnd] = this.refusals();
|
|
2043
|
+
const [shownStart, shownEnd] = this.revealed();
|
|
2044
|
+
this.revealed.set([shownStart || (start && !!refusedStart), shownEnd || (end && !!refusedEnd)]);
|
|
2045
|
+
}
|
|
2046
|
+
/**
|
|
2047
|
+
* Record why one end's text was not committed, or that it was. Clearing a refusal
|
|
2048
|
+
* takes it off show too, so the next one waits for Enter or blur again.
|
|
2049
|
+
*/
|
|
2050
|
+
refuse(end, next) {
|
|
2051
|
+
const current = this.refusals();
|
|
2052
|
+
if (current[end] === next)
|
|
2053
|
+
return;
|
|
2054
|
+
this.refusals.set(end === 0 ? [next, current[1]] : [current[0], next]);
|
|
2055
|
+
if (!next) {
|
|
2056
|
+
const [shownStart, shownEnd] = this.revealed();
|
|
2057
|
+
this.revealed.set(end === 0 ? [false, shownEnd] : [shownStart, false]);
|
|
2058
|
+
}
|
|
2059
|
+
this.reportToForm(this.raised());
|
|
2060
|
+
}
|
|
2061
|
+
/**
|
|
2062
|
+
* Put the refusals on show for wherever focus has gone: back into one field leaves
|
|
2063
|
+
* the OTHER end, and anywhere outside the pair leaves both.
|
|
2064
|
+
*/
|
|
2065
|
+
revealFor(focus) {
|
|
2066
|
+
if (focus === this.startEl().nativeElement)
|
|
2067
|
+
this.reveal([false, true]);
|
|
2068
|
+
else if (focus === this.endEl().nativeElement)
|
|
2069
|
+
this.reveal([true, false]);
|
|
2070
|
+
else
|
|
2071
|
+
this.reveal([true, true]);
|
|
2072
|
+
}
|
|
2073
|
+
/**
|
|
2074
|
+
* The bound form was reset while the model stayed where it was — Angular has thrown
|
|
2075
|
+
* the parse errors away, so the text they were about goes too, and both fields show
|
|
2076
|
+
* the range again. (A reset that MOVES the model reaches the text through the sync
|
|
2077
|
+
* effect, like any other outside write.)
|
|
2078
|
+
*/
|
|
2079
|
+
onFormReset() {
|
|
2080
|
+
const [refusedStart, refusedEnd] = this.refusals();
|
|
2081
|
+
if (!refusedStart && !refusedEnd)
|
|
2082
|
+
return;
|
|
2083
|
+
const [start, end] = this.current();
|
|
1469
2084
|
this.startText.set(this.display(start));
|
|
1470
2085
|
this.endText.set(this.display(end));
|
|
2086
|
+
this.refuse(0, null);
|
|
2087
|
+
this.refuse(1, null);
|
|
1471
2088
|
}
|
|
2089
|
+
/**
|
|
2090
|
+
* Focus leaving the open panel. Moving within it is the same visit; anywhere else has
|
|
2091
|
+
* left whichever end focus is not going back to — see {@link revealFor}.
|
|
2092
|
+
*/
|
|
2093
|
+
onPaneFocusOut = (event) => {
|
|
2094
|
+
const next = event.relatedTarget;
|
|
2095
|
+
if (next && this.overlayRef?.overlayElement.contains(next))
|
|
2096
|
+
return;
|
|
2097
|
+
this.revealFor(next);
|
|
2098
|
+
};
|
|
1472
2099
|
/** Called by an input's click — opens the overlay if it isn't open already. */
|
|
1473
2100
|
openOnInput(end) {
|
|
1474
2101
|
if (this.disabled() || this.readonly() || this.overlayRef)
|
|
@@ -1501,6 +2128,10 @@ class WrDateRangePicker {
|
|
|
1501
2128
|
onFieldKey(event, end) {
|
|
1502
2129
|
if (this.disabled() || this.readonly())
|
|
1503
2130
|
return;
|
|
2131
|
+
if (event.key === 'Enter') {
|
|
2132
|
+
this.onEnter();
|
|
2133
|
+
return;
|
|
2134
|
+
}
|
|
1504
2135
|
const vertical = event.key === 'ArrowDown' || event.key === 'ArrowUp';
|
|
1505
2136
|
if (!vertical)
|
|
1506
2137
|
return;
|
|
@@ -1551,15 +2182,24 @@ class WrDateRangePicker {
|
|
|
1551
2182
|
const [start, finish] = this.current();
|
|
1552
2183
|
if (!raw) {
|
|
1553
2184
|
this.commitRange(end === 0 ? [null, finish] : [start, null], { normalise: false });
|
|
2185
|
+
this.refuse(end, null);
|
|
1554
2186
|
return;
|
|
1555
2187
|
}
|
|
1556
2188
|
const parsed = this.adapter.parse(raw, this.resolvedFormat());
|
|
1557
|
-
if (!parsed || !this.adapter.isValid(parsed)
|
|
2189
|
+
if (!parsed || !this.adapter.isValid(parsed)) {
|
|
2190
|
+
this.refuse(end, this.refusalBuilder.unreadable(this.resolvedFormat()));
|
|
2191
|
+
return;
|
|
2192
|
+
}
|
|
2193
|
+
const outOfBounds = this.outOfBounds(parsed);
|
|
2194
|
+
if (outOfBounds) {
|
|
2195
|
+
this.refuse(end, outOfBounds);
|
|
1558
2196
|
return;
|
|
2197
|
+
}
|
|
1559
2198
|
// Never reorder mid-keystroke: a half-typed date can parse to an
|
|
1560
2199
|
// out-of-order value, and swapping there would yank the text the user is
|
|
1561
2200
|
// still typing over to the other input. Ordering is settled on blur.
|
|
1562
2201
|
this.commitRange(end === 0 ? [parsed, finish] : [start, parsed], { normalise: false });
|
|
2202
|
+
this.refuse(end, null);
|
|
1563
2203
|
}
|
|
1564
2204
|
// Overlay
|
|
1565
2205
|
openOverlay(openedFrom = null, autoFocus = false) {
|
|
@@ -1597,6 +2237,7 @@ class WrDateRangePicker {
|
|
|
1597
2237
|
pane.setAttribute('role', 'dialog');
|
|
1598
2238
|
pane.setAttribute('aria-modal', 'false');
|
|
1599
2239
|
pane.setAttribute('aria-label', this.resolvedPanelLabel());
|
|
2240
|
+
pane.addEventListener('focusout', this.onPaneFocusOut);
|
|
1600
2241
|
const ref = this.overlayRef.attach(new ComponentPortal(WrDateRangePanel));
|
|
1601
2242
|
ref.setInput('value', this.value() ?? [null, null]);
|
|
1602
2243
|
ref.setInput('autoFocus', this.autoFocusPanel);
|
|
@@ -1643,7 +2284,11 @@ class WrDateRangePicker {
|
|
|
1643
2284
|
*/
|
|
1644
2285
|
closeOverlay() {
|
|
1645
2286
|
const pane = this.overlayRef?.overlayElement;
|
|
1646
|
-
const
|
|
2287
|
+
const doc = this.host.nativeElement.ownerDocument;
|
|
2288
|
+
const inside = !!pane && pane.contains(doc.activeElement);
|
|
2289
|
+
// Taking the pane out of the document moves focus without the user doing anything,
|
|
2290
|
+
// so it must not read as focus leaving the panel.
|
|
2291
|
+
pane?.removeEventListener('focusout', this.onPaneFocusOut);
|
|
1647
2292
|
this.dispose();
|
|
1648
2293
|
// The interaction is over, so settle the ordering an in-progress time edit
|
|
1649
2294
|
// was allowed to leave inverted. Blur does the same for the text inputs;
|
|
@@ -1651,6 +2296,9 @@ class WrDateRangePicker {
|
|
|
1651
2296
|
this.commitRange(this.current(), { normalise: true });
|
|
1652
2297
|
if (inside)
|
|
1653
2298
|
this.restoreFocus();
|
|
2299
|
+
// A panel that closes ends the visit to it: wherever focus is now, the ends it is
|
|
2300
|
+
// not in have been left. A pick has already cleared the refusals it replaced.
|
|
2301
|
+
this.revealFor(doc.activeElement);
|
|
1654
2302
|
}
|
|
1655
2303
|
dispose() {
|
|
1656
2304
|
if (this.overlayRef) {
|
|
@@ -1671,6 +2319,7 @@ class WrDateRangePicker {
|
|
|
1671
2319
|
* move their in-progress text into the other input.
|
|
1672
2320
|
*/
|
|
1673
2321
|
commitRange(next, options) {
|
|
2322
|
+
const previous = this.current();
|
|
1674
2323
|
const normalised = options.normalise ? this.normalise(next) : next;
|
|
1675
2324
|
// Only write when a date actually moved, and never while `readonly`.
|
|
1676
2325
|
//
|
|
@@ -1690,12 +2339,27 @@ class WrDateRangePicker {
|
|
|
1690
2339
|
this.lastValue = normalised;
|
|
1691
2340
|
this.value.set(normalised);
|
|
1692
2341
|
}
|
|
2342
|
+
// An end whose DATE moved — a pick in the panel, a swap — has had its text
|
|
2343
|
+
// replaced by that date, so a refusal about the old text no longer applies.
|
|
2344
|
+
// An end that did not move keeps refused text as it is: committing the OTHER
|
|
2345
|
+
// end is no reason to throw away what the user is still correcting here.
|
|
2346
|
+
const [movedStart, movedEnd] = [0, 1].map(i => !this.sameDate(this.current()[i], previous[i]));
|
|
2347
|
+
if (movedStart)
|
|
2348
|
+
this.refuse(0, null);
|
|
2349
|
+
if (movedEnd)
|
|
2350
|
+
this.refuse(1, null);
|
|
2351
|
+
const [refusedStart, refusedEnd] = this.refusals();
|
|
2352
|
+
// Say the refusals again, whether or not either moved. Angular clears a control's
|
|
2353
|
+
// parse errors whenever its model changes, so committing ONE end used to leave the
|
|
2354
|
+
// form valid while the other end still held refused text — and able to submit the
|
|
2355
|
+
// old end under it.
|
|
2356
|
+
this.reportToForm(this.raised());
|
|
1693
2357
|
// Only rewrite the text of an end whose date moved out from under it —
|
|
1694
2358
|
// otherwise a half-typed date would be reformatted on every keystroke.
|
|
1695
|
-
if (!this.sameDate(normalised[0], this.parseText(this.startText()))) {
|
|
2359
|
+
if (!refusedStart && !this.sameDate(normalised[0], this.parseText(this.startText()))) {
|
|
1696
2360
|
this.startText.set(this.display(normalised[0]));
|
|
1697
2361
|
}
|
|
1698
|
-
if (!this.sameDate(normalised[1], this.parseText(this.endText()))) {
|
|
2362
|
+
if (!refusedEnd && !this.sameDate(normalised[1], this.parseText(this.endText()))) {
|
|
1699
2363
|
this.endText.set(this.display(normalised[1]));
|
|
1700
2364
|
}
|
|
1701
2365
|
return normalised;
|
|
@@ -1733,21 +2397,16 @@ class WrDateRangePicker {
|
|
|
1733
2397
|
return clock(a) - clock(b);
|
|
1734
2398
|
}
|
|
1735
2399
|
/**
|
|
1736
|
-
*
|
|
1737
|
-
* bounds — the calendar disables filtered days, so accepting them from the
|
|
2400
|
+
* Why a typed date must be rejected, or `null`. Covers `dateFilter` as well as
|
|
2401
|
+
* the bounds — the calendar disables filtered days, so accepting them from the
|
|
1738
2402
|
* keyboard would make the two entry paths disagree.
|
|
1739
2403
|
*/
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
return true;
|
|
1747
|
-
const filter = this.dateFilter();
|
|
1748
|
-
if (filter && !filter(date))
|
|
1749
|
-
return true;
|
|
1750
|
-
return false;
|
|
2404
|
+
outOfBounds(date) {
|
|
2405
|
+
return this.refusalBuilder.outOfBounds(date, {
|
|
2406
|
+
min: this.minDate(),
|
|
2407
|
+
max: this.maxDate(),
|
|
2408
|
+
filter: this.dateFilter(),
|
|
2409
|
+
});
|
|
1751
2410
|
}
|
|
1752
2411
|
sameDate(a, b) {
|
|
1753
2412
|
if (a === b)
|
|
@@ -1773,11 +2432,11 @@ class WrDateRangePicker {
|
|
|
1773
2432
|
return this.sameDate(aStart, bStart) && this.sameDate(aEnd, bEnd);
|
|
1774
2433
|
}
|
|
1775
2434
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: WrDateRangePicker, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1776
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", 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 <!--\n `wrDateRangeEnd` hides the surrounding `<wr-form-field>` from this input, so\n only the start one adopts its `controlId` \u2014 see the directive for why the\n second half of a range carries no id at all.\n -->\n <input\n #endInput\n wrInput\n wrDateRangeEnd\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: WrDateRangeEndInput, selector: "input[wrDateRangeEnd]" }, { kind: "directive", type: WrInput, selector: "input[wrInput], textarea[wrInput]", inputs: ["size", "rounded"] }, { kind: "component", type: WrInputGroup, selector: "wr-input-group", inputs: ["rounded"] }, { kind: "directive", type: WrInputSuffix, selector: "[wrInputSuffix]" }], encapsulation: i0.ViewEncapsulation.None });
|
|
2435
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", 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()" } }, providers: [WrDateInputField], 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 [attr.aria-invalid]=\"startAriaInvalid()\"\n [attr.aria-describedby]=\"startDescribedBy()\"\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 <!--\n `wrDateRangeEnd` hides the surrounding `<wr-form-field>` from this input, so\n only the start one adopts its `controlId` \u2014 see the directive for why the\n second half of a range carries no id at all.\n -->\n <input\n #endInput\n wrInput\n wrDateRangeEnd\n type=\"text\"\n autocomplete=\"off\"\n spellcheck=\"false\"\n class=\"wr-date-range-picker__input\"\n [attr.aria-label]=\"endLabel()\"\n [attr.aria-invalid]=\"endAriaInvalid()\"\n [attr.aria-describedby]=\"endDescribedBy()\"\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<!--\n Why a typed end was refused, for a screen reader \u2014 the same live region\n <wr-date-picker> carries, written when a refusal is shown and emptied when it is\n corrected. Silent inside a <wr-form-field> bound to the same control.\n-->\n<span class=\"wr-date-range-picker__sr-only\" role=\"status\" [id]=\"statusId\">{{ announcement() }}</span>\n", dependencies: [{ kind: "directive", type: WrDateRangeEndInput, selector: "input[wrDateRangeEnd]" }, { kind: "directive", type: WrInput, selector: "input[wrInput], textarea[wrInput]", inputs: ["size", "rounded"] }, { kind: "component", type: WrInputGroup, selector: "wr-input-group", inputs: ["rounded"] }, { kind: "directive", type: WrInputSuffix, selector: "[wrInputSuffix]" }], viewProviders: [WR_DATE_INPUT_FIELD_VIEW_PROVIDER], encapsulation: i0.ViewEncapsulation.None });
|
|
1777
2436
|
}
|
|
1778
2437
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: WrDateRangePicker, decorators: [{
|
|
1779
2438
|
type: Component,
|
|
1780
|
-
args: [{ selector: 'wr-date-range-picker', encapsulation: ViewEncapsulation.None, host: { '[class]': 'classes()' }, imports: [WrDateRangeEndInput, 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 <!--\n `wrDateRangeEnd` hides the surrounding `<wr-form-field>` from this input, so\n only the start one adopts its `controlId` \u2014 see the directive for why the\n second half of a range carries no id at all.\n -->\n <input\n #endInput\n wrInput\n wrDateRangeEnd\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" }]
|
|
2439
|
+
args: [{ selector: 'wr-date-range-picker', encapsulation: ViewEncapsulation.None, host: { '[class]': 'classes()' }, imports: [WrDateRangeEndInput, WrInput, WrInputGroup, WrInputSuffix], providers: [WrDateInputField], viewProviders: [WR_DATE_INPUT_FIELD_VIEW_PROVIDER], 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-invalid]=\"startAriaInvalid()\"\n [attr.aria-describedby]=\"startDescribedBy()\"\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 <!--\n `wrDateRangeEnd` hides the surrounding `<wr-form-field>` from this input, so\n only the start one adopts its `controlId` \u2014 see the directive for why the\n second half of a range carries no id at all.\n -->\n <input\n #endInput\n wrInput\n wrDateRangeEnd\n type=\"text\"\n autocomplete=\"off\"\n spellcheck=\"false\"\n class=\"wr-date-range-picker__input\"\n [attr.aria-label]=\"endLabel()\"\n [attr.aria-invalid]=\"endAriaInvalid()\"\n [attr.aria-describedby]=\"endDescribedBy()\"\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<!--\n Why a typed end was refused, for a screen reader \u2014 the same live region\n <wr-date-picker> carries, written when a refusal is shown and emptied when it is\n corrected. Silent inside a <wr-form-field> bound to the same control.\n-->\n<span class=\"wr-date-range-picker__sr-only\" role=\"status\" [id]=\"statusId\">{{ announcement() }}</span>\n" }]
|
|
1781
2440
|
}], 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 }] }] } });
|
|
1782
2441
|
|
|
1783
2442
|
/**
|