@cdevhub/ngx-tw 0.2.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/LICENSE +21 -0
- package/README.md +134 -0
- package/fesm2022/cdevhub-ngx-tw-accordion.mjs +108 -0
- package/fesm2022/cdevhub-ngx-tw-accordion.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-alert.mjs +240 -0
- package/fesm2022/cdevhub-ngx-tw-alert.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-avatar.mjs +287 -0
- package/fesm2022/cdevhub-ngx-tw-avatar.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-badge.mjs +332 -0
- package/fesm2022/cdevhub-ngx-tw-badge.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-breadcrumbs.mjs +452 -0
- package/fesm2022/cdevhub-ngx-tw-breadcrumbs.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-button.mjs +227 -0
- package/fesm2022/cdevhub-ngx-tw-button.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-calendar-luxon.mjs +417 -0
- package/fesm2022/cdevhub-ngx-tw-calendar-luxon.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-calendar-testing.mjs +232 -0
- package/fesm2022/cdevhub-ngx-tw-calendar-testing.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-calendar.mjs +4482 -0
- package/fesm2022/cdevhub-ngx-tw-calendar.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-card.mjs +157 -0
- package/fesm2022/cdevhub-ngx-tw-card.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-carousel.mjs +1454 -0
- package/fesm2022/cdevhub-ngx-tw-carousel.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-checkbox.mjs +593 -0
- package/fesm2022/cdevhub-ngx-tw-checkbox.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-code-block.mjs +228 -0
- package/fesm2022/cdevhub-ngx-tw-code-block.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-collapsible.mjs +510 -0
- package/fesm2022/cdevhub-ngx-tw-collapsible.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-combobox.mjs +1721 -0
- package/fesm2022/cdevhub-ngx-tw-combobox.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-command-palette.mjs +721 -0
- package/fesm2022/cdevhub-ngx-tw-command-palette.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-core.mjs +861 -0
- package/fesm2022/cdevhub-ngx-tw-core.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-date-picker.mjs +1558 -0
- package/fesm2022/cdevhub-ngx-tw-date-picker.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-date-range-picker.mjs +1833 -0
- package/fesm2022/cdevhub-ngx-tw-date-range-picker.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-dialog.mjs +693 -0
- package/fesm2022/cdevhub-ngx-tw-dialog.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-empty-state.mjs +331 -0
- package/fesm2022/cdevhub-ngx-tw-empty-state.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-flip-card.mjs +289 -0
- package/fesm2022/cdevhub-ngx-tw-flip-card.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-form-field.mjs +572 -0
- package/fesm2022/cdevhub-ngx-tw-form-field.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-icon-lucide.mjs +36 -0
- package/fesm2022/cdevhub-ngx-tw-icon-lucide.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-icon.mjs +278 -0
- package/fesm2022/cdevhub-ngx-tw-icon.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-input.mjs +355 -0
- package/fesm2022/cdevhub-ngx-tw-input.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-item.mjs +241 -0
- package/fesm2022/cdevhub-ngx-tw-item.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-menu.mjs +433 -0
- package/fesm2022/cdevhub-ngx-tw-menu.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-paginator.mjs +691 -0
- package/fesm2022/cdevhub-ngx-tw-paginator.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-popover.mjs +678 -0
- package/fesm2022/cdevhub-ngx-tw-popover.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-progress-bar.mjs +335 -0
- package/fesm2022/cdevhub-ngx-tw-progress-bar.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-radio.mjs +708 -0
- package/fesm2022/cdevhub-ngx-tw-radio.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-segmented-control.mjs +319 -0
- package/fesm2022/cdevhub-ngx-tw-segmented-control.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-select.mjs +1722 -0
- package/fesm2022/cdevhub-ngx-tw-select.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-separator.mjs +115 -0
- package/fesm2022/cdevhub-ngx-tw-separator.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-sheet.mjs +768 -0
- package/fesm2022/cdevhub-ngx-tw-sheet.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-skeleton.mjs +149 -0
- package/fesm2022/cdevhub-ngx-tw-skeleton.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-slider.mjs +1147 -0
- package/fesm2022/cdevhub-ngx-tw-slider.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-sort.mjs +346 -0
- package/fesm2022/cdevhub-ngx-tw-sort.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-spinner.mjs +186 -0
- package/fesm2022/cdevhub-ngx-tw-spinner.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-split.mjs +1082 -0
- package/fesm2022/cdevhub-ngx-tw-split.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-stat.mjs +579 -0
- package/fesm2022/cdevhub-ngx-tw-stat.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-stepper.mjs +443 -0
- package/fesm2022/cdevhub-ngx-tw-stepper.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-switch.mjs +391 -0
- package/fesm2022/cdevhub-ngx-tw-switch.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-tab-nav.mjs +315 -0
- package/fesm2022/cdevhub-ngx-tw-tab-nav.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-table.mjs +994 -0
- package/fesm2022/cdevhub-ngx-tw-table.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-tabs.mjs +508 -0
- package/fesm2022/cdevhub-ngx-tw-tabs.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-textarea.mjs +147 -0
- package/fesm2022/cdevhub-ngx-tw-textarea.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-theme.mjs +178 -0
- package/fesm2022/cdevhub-ngx-tw-theme.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-time-picker.mjs +1344 -0
- package/fesm2022/cdevhub-ngx-tw-time-picker.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-timeline.mjs +1136 -0
- package/fesm2022/cdevhub-ngx-tw-timeline.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-toast.mjs +1311 -0
- package/fesm2022/cdevhub-ngx-tw-toast.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-tooltip.mjs +533 -0
- package/fesm2022/cdevhub-ngx-tw-tooltip.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw.mjs +61 -0
- package/fesm2022/cdevhub-ngx-tw.mjs.map +1 -0
- package/package.json +265 -0
- package/theme/_base.css +394 -0
- package/theme/_dark.css +510 -0
- package/theme/_high-contrast.css +290 -0
- package/theme/_semantic.css +317 -0
- package/theme/_typography.css +9 -0
- package/theme/index.css +54 -0
- package/types/cdevhub-ngx-tw-accordion.d.ts +48 -0
- package/types/cdevhub-ngx-tw-alert.d.ts +67 -0
- package/types/cdevhub-ngx-tw-avatar.d.ts +79 -0
- package/types/cdevhub-ngx-tw-badge.d.ts +94 -0
- package/types/cdevhub-ngx-tw-breadcrumbs.d.ts +227 -0
- package/types/cdevhub-ngx-tw-button.d.ts +74 -0
- package/types/cdevhub-ngx-tw-calendar-luxon.d.ts +200 -0
- package/types/cdevhub-ngx-tw-calendar-testing.d.ts +113 -0
- package/types/cdevhub-ngx-tw-calendar.d.ts +1704 -0
- package/types/cdevhub-ngx-tw-card.d.ts +48 -0
- package/types/cdevhub-ngx-tw-carousel.d.ts +406 -0
- package/types/cdevhub-ngx-tw-checkbox.d.ts +136 -0
- package/types/cdevhub-ngx-tw-code-block.d.ts +93 -0
- package/types/cdevhub-ngx-tw-collapsible.d.ts +151 -0
- package/types/cdevhub-ngx-tw-combobox.d.ts +389 -0
- package/types/cdevhub-ngx-tw-command-palette.d.ts +215 -0
- package/types/cdevhub-ngx-tw-core.d.ts +719 -0
- package/types/cdevhub-ngx-tw-date-picker.d.ts +317 -0
- package/types/cdevhub-ngx-tw-date-range-picker.d.ts +313 -0
- package/types/cdevhub-ngx-tw-dialog.d.ts +303 -0
- package/types/cdevhub-ngx-tw-empty-state.d.ts +104 -0
- package/types/cdevhub-ngx-tw-flip-card.d.ts +95 -0
- package/types/cdevhub-ngx-tw-form-field.d.ts +210 -0
- package/types/cdevhub-ngx-tw-icon-lucide.d.ts +29 -0
- package/types/cdevhub-ngx-tw-icon.d.ts +134 -0
- package/types/cdevhub-ngx-tw-input.d.ts +110 -0
- package/types/cdevhub-ngx-tw-item.d.ts +70 -0
- package/types/cdevhub-ngx-tw-menu.d.ts +99 -0
- package/types/cdevhub-ngx-tw-paginator.d.ts +305 -0
- package/types/cdevhub-ngx-tw-popover.d.ts +157 -0
- package/types/cdevhub-ngx-tw-progress-bar.d.ts +100 -0
- package/types/cdevhub-ngx-tw-radio.d.ts +174 -0
- package/types/cdevhub-ngx-tw-segmented-control.d.ts +66 -0
- package/types/cdevhub-ngx-tw-select.d.ts +384 -0
- package/types/cdevhub-ngx-tw-separator.d.ts +28 -0
- package/types/cdevhub-ngx-tw-sheet.d.ts +335 -0
- package/types/cdevhub-ngx-tw-skeleton.d.ts +52 -0
- package/types/cdevhub-ngx-tw-slider.d.ts +258 -0
- package/types/cdevhub-ngx-tw-sort.d.ts +136 -0
- package/types/cdevhub-ngx-tw-spinner.d.ts +50 -0
- package/types/cdevhub-ngx-tw-split.d.ts +265 -0
- package/types/cdevhub-ngx-tw-stat.d.ts +140 -0
- package/types/cdevhub-ngx-tw-stepper.d.ts +127 -0
- package/types/cdevhub-ngx-tw-switch.d.ts +88 -0
- package/types/cdevhub-ngx-tw-tab-nav.d.ts +107 -0
- package/types/cdevhub-ngx-tw-table.d.ts +479 -0
- package/types/cdevhub-ngx-tw-tabs.d.ts +121 -0
- package/types/cdevhub-ngx-tw-textarea.d.ts +62 -0
- package/types/cdevhub-ngx-tw-theme.d.ts +106 -0
- package/types/cdevhub-ngx-tw-time-picker.d.ts +303 -0
- package/types/cdevhub-ngx-tw-timeline.d.ts +278 -0
- package/types/cdevhub-ngx-tw-toast.d.ts +388 -0
- package/types/cdevhub-ngx-tw-tooltip.d.ts +95 -0
- package/types/cdevhub-ngx-tw.d.ts +49 -0
|
@@ -0,0 +1,1833 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { inject, ElementRef, signal, Directive, viewChild, viewChildren, computed, afterRenderEffect, ChangeDetectionStrategy, Component, input, booleanAttribute, model, output, ViewContainerRef, Injector, DestroyRef, linkedSignal, effect, untracked, afterNextRender, isDevMode, forwardRef } from '@angular/core';
|
|
3
|
+
import { NgForm, FormGroupDirective, Validators, NgControl, NG_VALIDATORS } from '@angular/forms';
|
|
4
|
+
import { Overlay } from '@angular/cdk/overlay';
|
|
5
|
+
import { FocusMonitor, LiveAnnouncer } from '@angular/cdk/a11y';
|
|
6
|
+
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
7
|
+
import { merge } from 'rxjs';
|
|
8
|
+
import { tv } from 'tailwind-variants';
|
|
9
|
+
import { PickerOverlayCoordinator, TW_ERROR_STATE_MATCHER, resolveSelectScrollStrategy, buildSelectLikePositions } from '@cdevhub/ngx-tw/core';
|
|
10
|
+
import { FormFieldControl, FormFieldComponent, TW_FORM_FIELD_CONTROL } from '@cdevhub/ngx-tw/form-field';
|
|
11
|
+
import { TwDateRange, CalendarComponent, provideRangeSelectionStrategy, DATE_ADAPTER, calendarValidator } from '@cdevhub/ngx-tw/calendar';
|
|
12
|
+
import { ButtonDirective } from '@cdevhub/ngx-tw/button';
|
|
13
|
+
import { TimePickerComponent } from '@cdevhub/ngx-tw/time-picker';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Internal directive that adopts a preset `<button>` into the picker's
|
|
17
|
+
* roving-tabindex listbox model. The directive's own host binding wins over
|
|
18
|
+
* `twButton`'s default `tabindex=null`, ensuring the active option carries
|
|
19
|
+
* `tabindex="0"` on the very first paint.
|
|
20
|
+
*
|
|
21
|
+
* @internal
|
|
22
|
+
*/
|
|
23
|
+
class DateRangePresetOptionDirective {
|
|
24
|
+
elementRef = inject((ElementRef));
|
|
25
|
+
/** @internal Tab-index — `'0'` on the active option, `'-1'` on the rest. */
|
|
26
|
+
tabindex = signal('-1', ...(ngDevMode ? [{ debugName: "tabindex" }] : /* istanbul ignore next */ []));
|
|
27
|
+
/** @internal */
|
|
28
|
+
focus() {
|
|
29
|
+
this.elementRef.nativeElement.focus();
|
|
30
|
+
}
|
|
31
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: DateRangePresetOptionDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
32
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.14", type: DateRangePresetOptionDirective, isStandalone: true, selector: "button[twDateRangePresetOption]", host: { properties: { "attr.tabindex": "tabindex()" } }, ngImport: i0 });
|
|
33
|
+
}
|
|
34
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: DateRangePresetOptionDirective, decorators: [{
|
|
35
|
+
type: Directive,
|
|
36
|
+
args: [{
|
|
37
|
+
selector: 'button[twDateRangePresetOption]',
|
|
38
|
+
host: {
|
|
39
|
+
'[attr.tabindex]': 'tabindex()',
|
|
40
|
+
},
|
|
41
|
+
}]
|
|
42
|
+
}] });
|
|
43
|
+
/**
|
|
44
|
+
* Internal overlay panel for `tw-date-range-picker`. Hosts the optional preset
|
|
45
|
+
* list, a single `tw-calendar` in `selectionMode="range"` configured with
|
|
46
|
+
* `numberOfMonths` (1 or 2 months under one header), an optional time row, and
|
|
47
|
+
* an optional action bar. Receives configuration and interaction callbacks via
|
|
48
|
+
* signal-backed fields set from the outer component. Not exported from the
|
|
49
|
+
* public API.
|
|
50
|
+
*/
|
|
51
|
+
class DateRangePickerOverlayComponent {
|
|
52
|
+
elementRef = inject((ElementRef));
|
|
53
|
+
calendarRef = viewChild('calendar', ...(ngDevMode ? [{ debugName: "calendarRef" }] : /* istanbul ignore next */ []));
|
|
54
|
+
presetOptions = viewChildren(DateRangePresetOptionDirective, ...(ngDevMode ? [{ debugName: "presetOptions" }] : /* istanbul ignore next */ []));
|
|
55
|
+
// ── Config signals set by the parent ──
|
|
56
|
+
/** @internal */
|
|
57
|
+
size = signal('md', ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
|
|
58
|
+
/** @internal */
|
|
59
|
+
color = signal('primary', ...(ngDevMode ? [{ debugName: "color" }] : /* istanbul ignore next */ []));
|
|
60
|
+
/** @internal */
|
|
61
|
+
minDate = signal(null, ...(ngDevMode ? [{ debugName: "minDate" }] : /* istanbul ignore next */ []));
|
|
62
|
+
/** @internal */
|
|
63
|
+
maxDate = signal(null, ...(ngDevMode ? [{ debugName: "maxDate" }] : /* istanbul ignore next */ []));
|
|
64
|
+
/** @internal */
|
|
65
|
+
dateFilter = signal(null, ...(ngDevMode ? [{ debugName: "dateFilter" }] : /* istanbul ignore next */ []));
|
|
66
|
+
/** @internal */
|
|
67
|
+
startView = signal('day', ...(ngDevMode ? [{ debugName: "startView" }] : /* istanbul ignore next */ []));
|
|
68
|
+
/** @internal */
|
|
69
|
+
numberOfMonths = signal(2, ...(ngDevMode ? [{ debugName: "numberOfMonths" }] : /* istanbul ignore next */ []));
|
|
70
|
+
/** @internal Current pending range shown as selected in the calendar. */
|
|
71
|
+
pendingRange = signal(null, ...(ngDevMode ? [{ debugName: "pendingRange" }] : /* istanbul ignore next */ []));
|
|
72
|
+
/** @internal */
|
|
73
|
+
currentView = signal('day', ...(ngDevMode ? [{ debugName: "currentView" }] : /* istanbul ignore next */ []));
|
|
74
|
+
/** @internal */
|
|
75
|
+
presets = signal([], ...(ngDevMode ? [{ debugName: "presets" }] : /* istanbul ignore next */ []));
|
|
76
|
+
/** @internal */
|
|
77
|
+
activePresetId = signal(undefined, ...(ngDevMode ? [{ debugName: "activePresetId" }] : /* istanbul ignore next */ []));
|
|
78
|
+
/** @internal */
|
|
79
|
+
showActions = signal(false, ...(ngDevMode ? [{ debugName: "showActions" }] : /* istanbul ignore next */ []));
|
|
80
|
+
/** @internal */
|
|
81
|
+
showTime = signal(false, ...(ngDevMode ? [{ debugName: "showTime" }] : /* istanbul ignore next */ []));
|
|
82
|
+
/** @internal */
|
|
83
|
+
timeFormat = signal('24h', ...(ngDevMode ? [{ debugName: "timeFormat" }] : /* istanbul ignore next */ []));
|
|
84
|
+
/** @internal */
|
|
85
|
+
showSeconds = signal(false, ...(ngDevMode ? [{ debugName: "showSeconds" }] : /* istanbul ignore next */ []));
|
|
86
|
+
/** @internal */
|
|
87
|
+
hourStep = signal(1, ...(ngDevMode ? [{ debugName: "hourStep" }] : /* istanbul ignore next */ []));
|
|
88
|
+
/** @internal */
|
|
89
|
+
minuteStep = signal(1, ...(ngDevMode ? [{ debugName: "minuteStep" }] : /* istanbul ignore next */ []));
|
|
90
|
+
/** @internal */
|
|
91
|
+
secondStep = signal(1, ...(ngDevMode ? [{ debugName: "secondStep" }] : /* istanbul ignore next */ []));
|
|
92
|
+
/** @internal */
|
|
93
|
+
todayLabel = signal('Today', ...(ngDevMode ? [{ debugName: "todayLabel" }] : /* istanbul ignore next */ []));
|
|
94
|
+
/** @internal */
|
|
95
|
+
clearLabel = signal('Clear', ...(ngDevMode ? [{ debugName: "clearLabel" }] : /* istanbul ignore next */ []));
|
|
96
|
+
/** @internal */
|
|
97
|
+
cancelLabel = signal('Cancel', ...(ngDevMode ? [{ debugName: "cancelLabel" }] : /* istanbul ignore next */ []));
|
|
98
|
+
/** @internal */
|
|
99
|
+
applyLabel = signal('Apply', ...(ngDevMode ? [{ debugName: "applyLabel" }] : /* istanbul ignore next */ []));
|
|
100
|
+
/** @internal */
|
|
101
|
+
rangeSeparator = signal(' – ', ...(ngDevMode ? [{ debugName: "rangeSeparator" }] : /* istanbul ignore next */ []));
|
|
102
|
+
/** @internal */
|
|
103
|
+
dialogId = signal('', ...(ngDevMode ? [{ debugName: "dialogId" }] : /* istanbul ignore next */ []));
|
|
104
|
+
/** @internal */
|
|
105
|
+
dialogAriaLabel = signal('Choose a date range', ...(ngDevMode ? [{ debugName: "dialogAriaLabel" }] : /* istanbul ignore next */ []));
|
|
106
|
+
/** @internal */
|
|
107
|
+
panelClassValue = signal('', ...(ngDevMode ? [{ debugName: "panelClassValue" }] : /* istanbul ignore next */ []));
|
|
108
|
+
/** @internal */
|
|
109
|
+
leaving = signal(false, ...(ngDevMode ? [{ debugName: "leaving" }] : /* istanbul ignore next */ []));
|
|
110
|
+
// ── Forwarded calendar config ──
|
|
111
|
+
/** @internal */
|
|
112
|
+
minRangeLength = signal(null, ...(ngDevMode ? [{ debugName: "minRangeLength" }] : /* istanbul ignore next */ []));
|
|
113
|
+
/** @internal */
|
|
114
|
+
maxRangeLength = signal(null, ...(ngDevMode ? [{ debugName: "maxRangeLength" }] : /* istanbul ignore next */ []));
|
|
115
|
+
/** @internal */
|
|
116
|
+
rangeBehavior = signal({}, ...(ngDevMode ? [{ debugName: "rangeBehavior" }] : /* istanbul ignore next */ []));
|
|
117
|
+
/** @internal */
|
|
118
|
+
rangeClickBehavior = signal('restart', ...(ngDevMode ? [{ debugName: "rangeClickBehavior" }] : /* istanbul ignore next */ []));
|
|
119
|
+
/** @internal */
|
|
120
|
+
firstDayOfWeek = signal(null, ...(ngDevMode ? [{ debugName: "firstDayOfWeek" }] : /* istanbul ignore next */ []));
|
|
121
|
+
/** @internal */
|
|
122
|
+
locale = signal(null, ...(ngDevMode ? [{ debugName: "locale" }] : /* istanbul ignore next */ []));
|
|
123
|
+
/** @internal */
|
|
124
|
+
dateClass = signal(null, ...(ngDevMode ? [{ debugName: "dateClass" }] : /* istanbul ignore next */ []));
|
|
125
|
+
/** @internal */
|
|
126
|
+
cellTemplate = signal(null, ...(ngDevMode ? [{ debugName: "cellTemplate" }] : /* istanbul ignore next */ []));
|
|
127
|
+
// ── Callbacks set by the parent ──
|
|
128
|
+
/** @internal */
|
|
129
|
+
onCalendarSelect = signal(() => { }, ...(ngDevMode ? [{ debugName: "onCalendarSelect" }] : /* istanbul ignore next */ []));
|
|
130
|
+
/** @internal */
|
|
131
|
+
onPresetSelect = signal(() => { }, ...(ngDevMode ? [{ debugName: "onPresetSelect" }] : /* istanbul ignore next */ []));
|
|
132
|
+
/** @internal */
|
|
133
|
+
onStartTimeChange = signal(() => { }, ...(ngDevMode ? [{ debugName: "onStartTimeChange" }] : /* istanbul ignore next */ []));
|
|
134
|
+
/** @internal */
|
|
135
|
+
onEndTimeChange = signal(() => { }, ...(ngDevMode ? [{ debugName: "onEndTimeChange" }] : /* istanbul ignore next */ []));
|
|
136
|
+
/** @internal */
|
|
137
|
+
onViewChange = signal(() => { }, ...(ngDevMode ? [{ debugName: "onViewChange" }] : /* istanbul ignore next */ []));
|
|
138
|
+
/** @internal */
|
|
139
|
+
onToday = signal(() => { }, ...(ngDevMode ? [{ debugName: "onToday" }] : /* istanbul ignore next */ []));
|
|
140
|
+
/** @internal */
|
|
141
|
+
onClear = signal(() => { }, ...(ngDevMode ? [{ debugName: "onClear" }] : /* istanbul ignore next */ []));
|
|
142
|
+
/** @internal */
|
|
143
|
+
onCancel = signal(() => { }, ...(ngDevMode ? [{ debugName: "onCancel" }] : /* istanbul ignore next */ []));
|
|
144
|
+
/** @internal */
|
|
145
|
+
onApply = signal(() => { }, ...(ngDevMode ? [{ debugName: "onApply" }] : /* istanbul ignore next */ []));
|
|
146
|
+
// ── Derived ──
|
|
147
|
+
/** @internal */
|
|
148
|
+
panelClasses = computed(() => {
|
|
149
|
+
const base = 'block bg-surface-overlay border border-border rounded-lg shadow-md overflow-hidden flex max-w-[calc(100vw-16px)]';
|
|
150
|
+
const custom = this.panelClassValue();
|
|
151
|
+
return custom ? `${base} ${custom}` : base;
|
|
152
|
+
}, ...(ngDevMode ? [{ debugName: "panelClasses" }] : /* istanbul ignore next */ []));
|
|
153
|
+
/** @internal */
|
|
154
|
+
calendarAriaLabel = computed(() => this.numberOfMonths() === 2 ? 'Date range calendar (two months)' : 'Date range calendar', ...(ngDevMode ? [{ debugName: "calendarAriaLabel" }] : /* istanbul ignore next */ []));
|
|
155
|
+
/** @internal The time row requires both endpoints to be set — each time-picker needs a concrete date. */
|
|
156
|
+
hasTimeablePending = computed(() => {
|
|
157
|
+
const v = this.pendingRange();
|
|
158
|
+
return v !== null && v.start !== null && v.end !== null;
|
|
159
|
+
}, ...(ngDevMode ? [{ debugName: "hasTimeablePending" }] : /* istanbul ignore next */ []));
|
|
160
|
+
/** @internal Adapts `TwDateRange<D> | null` to the `CalendarRangeValue<D>` shape the calendar's `value` input expects. */
|
|
161
|
+
pendingRangeAsRange = computed(() => {
|
|
162
|
+
const v = this.pendingRange();
|
|
163
|
+
return v ? { start: v.start, end: v.end } : { start: null, end: null };
|
|
164
|
+
}, ...(ngDevMode ? [{ debugName: "pendingRangeAsRange" }] : /* istanbul ignore next */ []));
|
|
165
|
+
/** @internal Seeds the calendar's active month on first attach so it anchors to the current value rather than today. */
|
|
166
|
+
initialStartAt = computed(() => {
|
|
167
|
+
const v = this.pendingRange();
|
|
168
|
+
return v?.start ?? null;
|
|
169
|
+
}, ...(ngDevMode ? [{ debugName: "initialStartAt" }] : /* istanbul ignore next */ []));
|
|
170
|
+
// ── Template handlers ──
|
|
171
|
+
/** @internal */
|
|
172
|
+
presetKey(index, preset) {
|
|
173
|
+
return preset.id ?? preset.label ?? index;
|
|
174
|
+
}
|
|
175
|
+
/** @internal */
|
|
176
|
+
isActivePreset(preset) {
|
|
177
|
+
return !!preset.id && this.activePresetId() === preset.id;
|
|
178
|
+
}
|
|
179
|
+
/** @internal */
|
|
180
|
+
onCalendarRangeSelected(value) {
|
|
181
|
+
// `selectedChange` from the calendar emits the range strategy's completed selection
|
|
182
|
+
// (a `DateRange<D>`). Wrap it into a `TwDateRange` for downstream consumers.
|
|
183
|
+
if (value && typeof value === 'object' && 'start' in value && 'end' in value) {
|
|
184
|
+
const r = value;
|
|
185
|
+
this.onCalendarSelect()(new TwDateRange(r.start, r.end));
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
/** @internal */
|
|
189
|
+
onViewChanged(event) {
|
|
190
|
+
this.currentView.set(event.to);
|
|
191
|
+
this.onViewChange()(event.to);
|
|
192
|
+
}
|
|
193
|
+
/** @internal */
|
|
194
|
+
onStartTime(event) {
|
|
195
|
+
if (event.source === 'programmatic')
|
|
196
|
+
return;
|
|
197
|
+
this.onStartTimeChange()(event.value);
|
|
198
|
+
}
|
|
199
|
+
/** @internal */
|
|
200
|
+
onEndTime(event) {
|
|
201
|
+
if (event.source === 'programmatic')
|
|
202
|
+
return;
|
|
203
|
+
this.onEndTimeChange()(event.value);
|
|
204
|
+
}
|
|
205
|
+
// ── Preset listbox keyboard ──
|
|
206
|
+
/** @internal Roving tabindex pointer — index of the focusable preset option. */
|
|
207
|
+
activeOptionIndex = signal(0, ...(ngDevMode ? [{ debugName: "activeOptionIndex" }] : /* istanbul ignore next */ []));
|
|
208
|
+
constructor() {
|
|
209
|
+
// Drive the directive's `tabindex` signal from `activeOptionIndex`. We
|
|
210
|
+
// use `afterRenderEffect` so the directive instances are already created
|
|
211
|
+
// when we read them (regular `effect` may run before the view init).
|
|
212
|
+
afterRenderEffect(() => {
|
|
213
|
+
const options = this.presetOptions();
|
|
214
|
+
const active = this.activeOptionIndex();
|
|
215
|
+
for (let i = 0; i < options.length; i++) {
|
|
216
|
+
options[i].tabindex.set(i === active ? '0' : '-1');
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
/** @internal */
|
|
221
|
+
onPresetListKeydown(event) {
|
|
222
|
+
const options = this.presetOptions();
|
|
223
|
+
const count = options.length;
|
|
224
|
+
if (count === 0)
|
|
225
|
+
return;
|
|
226
|
+
const current = this.activeOptionIndex();
|
|
227
|
+
const move = (next) => {
|
|
228
|
+
const idx = ((next % count) + count) % count;
|
|
229
|
+
this.activeOptionIndex.set(idx);
|
|
230
|
+
options[idx]?.focus();
|
|
231
|
+
};
|
|
232
|
+
switch (event.key) {
|
|
233
|
+
case 'ArrowDown':
|
|
234
|
+
event.preventDefault();
|
|
235
|
+
move(current + 1);
|
|
236
|
+
return;
|
|
237
|
+
case 'ArrowUp':
|
|
238
|
+
event.preventDefault();
|
|
239
|
+
move(current - 1);
|
|
240
|
+
return;
|
|
241
|
+
case 'Home':
|
|
242
|
+
event.preventDefault();
|
|
243
|
+
move(0);
|
|
244
|
+
return;
|
|
245
|
+
case 'End':
|
|
246
|
+
event.preventDefault();
|
|
247
|
+
move(count - 1);
|
|
248
|
+
return;
|
|
249
|
+
case 'Enter':
|
|
250
|
+
case ' ': {
|
|
251
|
+
const preset = this.presets()[current];
|
|
252
|
+
if (preset) {
|
|
253
|
+
event.preventDefault();
|
|
254
|
+
this.handlePreset(preset, current);
|
|
255
|
+
}
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
default:
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
/** @internal */
|
|
263
|
+
handlePreset(preset, index) {
|
|
264
|
+
if (typeof index === 'number')
|
|
265
|
+
this.activeOptionIndex.set(index);
|
|
266
|
+
this.onPresetSelect()(preset);
|
|
267
|
+
}
|
|
268
|
+
/** @internal */
|
|
269
|
+
handleToday() {
|
|
270
|
+
this.onToday()();
|
|
271
|
+
}
|
|
272
|
+
/** @internal */
|
|
273
|
+
handleClear() {
|
|
274
|
+
this.onClear()();
|
|
275
|
+
}
|
|
276
|
+
/** @internal */
|
|
277
|
+
handleCancel() {
|
|
278
|
+
this.onCancel()();
|
|
279
|
+
}
|
|
280
|
+
/** @internal */
|
|
281
|
+
handleApply() {
|
|
282
|
+
this.onApply()();
|
|
283
|
+
}
|
|
284
|
+
/** @internal Moves focus to the active cell of the calendar. Called by the parent after open. */
|
|
285
|
+
focusCalendar() {
|
|
286
|
+
this.calendarRef()?.focusActiveCell();
|
|
287
|
+
}
|
|
288
|
+
/** @internal Exposes the root overlay element for tests. */
|
|
289
|
+
getElement() {
|
|
290
|
+
return this.elementRef.nativeElement;
|
|
291
|
+
}
|
|
292
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: DateRangePickerOverlayComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
293
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", type: DateRangePickerOverlayComponent, isStandalone: true, selector: "tw-date-range-picker-overlay", host: { properties: { "class": "panelClasses()", "id": "dialogId()", "attr.role": "\"dialog\"", "attr.aria-modal": "\"true\"", "attr.aria-label": "dialogAriaLabel()", "animate.enter": "\"scale-in\"", "animate.leave": "leaving() ? \"scale-out\" : \"\"" } }, providers: [provideRangeSelectionStrategy()], viewQueries: [{ propertyName: "calendarRef", first: true, predicate: ["calendar"], descendants: true, isSignal: true }, { propertyName: "presetOptions", predicate: DateRangePresetOptionDirective, descendants: true, isSignal: true }], ngImport: i0, template: `
|
|
294
|
+
@if (presets().length) {
|
|
295
|
+
<div
|
|
296
|
+
class="flex flex-col gap-1 p-2 border-r border-border bg-surface-muted min-w-40"
|
|
297
|
+
role="listbox"
|
|
298
|
+
tabindex="-1"
|
|
299
|
+
aria-label="Preset ranges"
|
|
300
|
+
(keydown)="onPresetListKeydown($event)"
|
|
301
|
+
>
|
|
302
|
+
@for (preset of presets(); track presetKey($index, preset); let i = $index) {
|
|
303
|
+
<button
|
|
304
|
+
twButton
|
|
305
|
+
twDateRangePresetOption
|
|
306
|
+
variant="ghost"
|
|
307
|
+
size="sm"
|
|
308
|
+
type="button"
|
|
309
|
+
role="option"
|
|
310
|
+
class="w-full justify-start"
|
|
311
|
+
[attr.aria-selected]="isActivePreset(preset) ? 'true' : 'false'"
|
|
312
|
+
(click)="handlePreset(preset, i)"
|
|
313
|
+
>
|
|
314
|
+
{{ preset.label }}
|
|
315
|
+
</button>
|
|
316
|
+
}
|
|
317
|
+
</div>
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
<div class="flex flex-col flex-1">
|
|
321
|
+
<tw-calendar
|
|
322
|
+
#calendar
|
|
323
|
+
mode="range"
|
|
324
|
+
class="!rounded-none !border-0 !bg-transparent !p-2"
|
|
325
|
+
[bordered]="false"
|
|
326
|
+
[value]="$any(pendingRangeAsRange())"
|
|
327
|
+
[startAt]="$any(initialStartAt())"
|
|
328
|
+
[startView]="startView()"
|
|
329
|
+
[monthColumns]="numberOfMonths()"
|
|
330
|
+
[minDate]="minDate()"
|
|
331
|
+
[maxDate]="maxDate()"
|
|
332
|
+
[dateFilter]="dateFilter()"
|
|
333
|
+
[dateClass]="dateClass()"
|
|
334
|
+
[cellTemplate]="cellTemplate()"
|
|
335
|
+
[locale]="locale()"
|
|
336
|
+
[firstDayOfWeek]="firstDayOfWeek()"
|
|
337
|
+
[minRangeLength]="minRangeLength()"
|
|
338
|
+
[maxRangeLength]="maxRangeLength()"
|
|
339
|
+
[rangeBehavior]="rangeBehavior()"
|
|
340
|
+
[rangeClickBehavior]="rangeClickBehavior()"
|
|
341
|
+
[attr.aria-label]="calendarAriaLabel()"
|
|
342
|
+
(valueChange)="onCalendarRangeSelected($event)"
|
|
343
|
+
(viewChange)="onViewChanged($event)"
|
|
344
|
+
/>
|
|
345
|
+
|
|
346
|
+
@if (showTime() && hasTimeablePending()) {
|
|
347
|
+
<div class="border-t border-border flex items-center justify-between gap-3 px-3 py-2">
|
|
348
|
+
<div class="flex items-center gap-2">
|
|
349
|
+
<span class="text-xs font-medium text-fg-muted">Start</span>
|
|
350
|
+
<tw-time-picker
|
|
351
|
+
variant="naked"
|
|
352
|
+
[value]="pendingRange()!.start"
|
|
353
|
+
[size]="size()"
|
|
354
|
+
[color]="color()"
|
|
355
|
+
[format]="timeFormat()"
|
|
356
|
+
[showSeconds]="showSeconds()"
|
|
357
|
+
[hourStep]="hourStep()"
|
|
358
|
+
[minuteStep]="minuteStep()"
|
|
359
|
+
[secondStep]="secondStep()"
|
|
360
|
+
[showClear]="false"
|
|
361
|
+
[referenceDate]="pendingRange()!.start"
|
|
362
|
+
aria-label="Start time"
|
|
363
|
+
(timeChange)="onStartTime($event)"
|
|
364
|
+
/>
|
|
365
|
+
</div>
|
|
366
|
+
<span class="text-fg-subtle shrink-0">{{ rangeSeparator() }}</span>
|
|
367
|
+
<div class="flex items-center gap-2">
|
|
368
|
+
<span class="text-xs font-medium text-fg-muted">End</span>
|
|
369
|
+
<tw-time-picker
|
|
370
|
+
variant="naked"
|
|
371
|
+
[value]="pendingRange()!.end"
|
|
372
|
+
[size]="size()"
|
|
373
|
+
[color]="color()"
|
|
374
|
+
[format]="timeFormat()"
|
|
375
|
+
[showSeconds]="showSeconds()"
|
|
376
|
+
[hourStep]="hourStep()"
|
|
377
|
+
[minuteStep]="minuteStep()"
|
|
378
|
+
[secondStep]="secondStep()"
|
|
379
|
+
[showClear]="false"
|
|
380
|
+
[referenceDate]="pendingRange()!.end"
|
|
381
|
+
aria-label="End time"
|
|
382
|
+
(timeChange)="onEndTime($event)"
|
|
383
|
+
/>
|
|
384
|
+
</div>
|
|
385
|
+
</div>
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
@if (showActions()) {
|
|
389
|
+
<div class="border-t border-border flex items-center justify-between gap-2 px-3 py-2">
|
|
390
|
+
<div class="flex items-center gap-2">
|
|
391
|
+
<button twButton variant="ghost" size="sm" type="button" (click)="handleToday()">
|
|
392
|
+
{{ todayLabel() }}
|
|
393
|
+
</button>
|
|
394
|
+
@if (pendingRange() !== null) {
|
|
395
|
+
<button twButton variant="ghost" size="sm" type="button" (click)="handleClear()">
|
|
396
|
+
{{ clearLabel() }}
|
|
397
|
+
</button>
|
|
398
|
+
}
|
|
399
|
+
</div>
|
|
400
|
+
<div class="flex items-center gap-2">
|
|
401
|
+
<button twButton variant="ghost" size="sm" type="button" (click)="handleCancel()">
|
|
402
|
+
{{ cancelLabel() }}
|
|
403
|
+
</button>
|
|
404
|
+
<button twButton variant="solid" size="sm" type="button" (click)="handleApply()">
|
|
405
|
+
{{ applyLabel() }}
|
|
406
|
+
</button>
|
|
407
|
+
</div>
|
|
408
|
+
</div>
|
|
409
|
+
}
|
|
410
|
+
</div>
|
|
411
|
+
`, isInline: true, dependencies: [{ kind: "component", type: CalendarComponent, selector: "tw-calendar", inputs: ["mode", "value", "startAt", "minDate", "maxDate", "dateFilter", "disabledDates", "disabledDaysOfWeek", "constraints", "minRangeLength", "maxRangeLength", "maxSelections", "maxSelectionBehavior", "errorAriaDescribedBy", "rangeClickBehavior", "rangeBehavior", "dateClass", "bordered", "startView", "firstDayOfWeek", "monthColumns", "disabled", "readonly", "resetBehavior", "cellTemplate", "locale", "intl"], outputs: ["modeChange", "valueChange", "selectionStart", "rangePreview", "selectionComplete", "selectionRestart", "selectionCleared", "selectionLimitReached", "presetChange", "viewChange", "activeDateChange", "monthChange", "yearChange", "cellClick", "cellHover"] }, { kind: "component", type: TimePickerComponent, selector: "tw-time-picker", inputs: ["id", "disabled", "required", "readonly", "size", "color", "variant", "format", "showSeconds", "hourStep", "minuteStep", "secondStep", "minTime", "maxTime", "referenceDate", "placeholder", "showSteppers", "showClear", "clearLabel", "errorStateMatcher", "aria-label", "aria-labelledby", "aria-describedby", "value"], outputs: ["valueChange", "timeInput", "timeChange"] }, { kind: "directive", type: ButtonDirective, selector: "[twButton]", inputs: ["variant", "color", "size", "disabled", "loading"], exportAs: ["twButton"] }, { kind: "directive", type: DateRangePresetOptionDirective, selector: "button[twDateRangePresetOption]" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
412
|
+
}
|
|
413
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: DateRangePickerOverlayComponent, decorators: [{
|
|
414
|
+
type: Component,
|
|
415
|
+
args: [{
|
|
416
|
+
selector: 'tw-date-range-picker-overlay',
|
|
417
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
418
|
+
imports: [
|
|
419
|
+
CalendarComponent,
|
|
420
|
+
TimePickerComponent,
|
|
421
|
+
ButtonDirective,
|
|
422
|
+
DateRangePresetOptionDirective,
|
|
423
|
+
],
|
|
424
|
+
providers: [provideRangeSelectionStrategy()],
|
|
425
|
+
host: {
|
|
426
|
+
'[class]': 'panelClasses()',
|
|
427
|
+
'[id]': 'dialogId()',
|
|
428
|
+
'[attr.role]': '"dialog"',
|
|
429
|
+
'[attr.aria-modal]': '"true"',
|
|
430
|
+
'[attr.aria-label]': 'dialogAriaLabel()',
|
|
431
|
+
'[animate.enter]': '"scale-in"',
|
|
432
|
+
'[animate.leave]': 'leaving() ? "scale-out" : ""',
|
|
433
|
+
},
|
|
434
|
+
template: `
|
|
435
|
+
@if (presets().length) {
|
|
436
|
+
<div
|
|
437
|
+
class="flex flex-col gap-1 p-2 border-r border-border bg-surface-muted min-w-40"
|
|
438
|
+
role="listbox"
|
|
439
|
+
tabindex="-1"
|
|
440
|
+
aria-label="Preset ranges"
|
|
441
|
+
(keydown)="onPresetListKeydown($event)"
|
|
442
|
+
>
|
|
443
|
+
@for (preset of presets(); track presetKey($index, preset); let i = $index) {
|
|
444
|
+
<button
|
|
445
|
+
twButton
|
|
446
|
+
twDateRangePresetOption
|
|
447
|
+
variant="ghost"
|
|
448
|
+
size="sm"
|
|
449
|
+
type="button"
|
|
450
|
+
role="option"
|
|
451
|
+
class="w-full justify-start"
|
|
452
|
+
[attr.aria-selected]="isActivePreset(preset) ? 'true' : 'false'"
|
|
453
|
+
(click)="handlePreset(preset, i)"
|
|
454
|
+
>
|
|
455
|
+
{{ preset.label }}
|
|
456
|
+
</button>
|
|
457
|
+
}
|
|
458
|
+
</div>
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
<div class="flex flex-col flex-1">
|
|
462
|
+
<tw-calendar
|
|
463
|
+
#calendar
|
|
464
|
+
mode="range"
|
|
465
|
+
class="!rounded-none !border-0 !bg-transparent !p-2"
|
|
466
|
+
[bordered]="false"
|
|
467
|
+
[value]="$any(pendingRangeAsRange())"
|
|
468
|
+
[startAt]="$any(initialStartAt())"
|
|
469
|
+
[startView]="startView()"
|
|
470
|
+
[monthColumns]="numberOfMonths()"
|
|
471
|
+
[minDate]="minDate()"
|
|
472
|
+
[maxDate]="maxDate()"
|
|
473
|
+
[dateFilter]="dateFilter()"
|
|
474
|
+
[dateClass]="dateClass()"
|
|
475
|
+
[cellTemplate]="cellTemplate()"
|
|
476
|
+
[locale]="locale()"
|
|
477
|
+
[firstDayOfWeek]="firstDayOfWeek()"
|
|
478
|
+
[minRangeLength]="minRangeLength()"
|
|
479
|
+
[maxRangeLength]="maxRangeLength()"
|
|
480
|
+
[rangeBehavior]="rangeBehavior()"
|
|
481
|
+
[rangeClickBehavior]="rangeClickBehavior()"
|
|
482
|
+
[attr.aria-label]="calendarAriaLabel()"
|
|
483
|
+
(valueChange)="onCalendarRangeSelected($event)"
|
|
484
|
+
(viewChange)="onViewChanged($event)"
|
|
485
|
+
/>
|
|
486
|
+
|
|
487
|
+
@if (showTime() && hasTimeablePending()) {
|
|
488
|
+
<div class="border-t border-border flex items-center justify-between gap-3 px-3 py-2">
|
|
489
|
+
<div class="flex items-center gap-2">
|
|
490
|
+
<span class="text-xs font-medium text-fg-muted">Start</span>
|
|
491
|
+
<tw-time-picker
|
|
492
|
+
variant="naked"
|
|
493
|
+
[value]="pendingRange()!.start"
|
|
494
|
+
[size]="size()"
|
|
495
|
+
[color]="color()"
|
|
496
|
+
[format]="timeFormat()"
|
|
497
|
+
[showSeconds]="showSeconds()"
|
|
498
|
+
[hourStep]="hourStep()"
|
|
499
|
+
[minuteStep]="minuteStep()"
|
|
500
|
+
[secondStep]="secondStep()"
|
|
501
|
+
[showClear]="false"
|
|
502
|
+
[referenceDate]="pendingRange()!.start"
|
|
503
|
+
aria-label="Start time"
|
|
504
|
+
(timeChange)="onStartTime($event)"
|
|
505
|
+
/>
|
|
506
|
+
</div>
|
|
507
|
+
<span class="text-fg-subtle shrink-0">{{ rangeSeparator() }}</span>
|
|
508
|
+
<div class="flex items-center gap-2">
|
|
509
|
+
<span class="text-xs font-medium text-fg-muted">End</span>
|
|
510
|
+
<tw-time-picker
|
|
511
|
+
variant="naked"
|
|
512
|
+
[value]="pendingRange()!.end"
|
|
513
|
+
[size]="size()"
|
|
514
|
+
[color]="color()"
|
|
515
|
+
[format]="timeFormat()"
|
|
516
|
+
[showSeconds]="showSeconds()"
|
|
517
|
+
[hourStep]="hourStep()"
|
|
518
|
+
[minuteStep]="minuteStep()"
|
|
519
|
+
[secondStep]="secondStep()"
|
|
520
|
+
[showClear]="false"
|
|
521
|
+
[referenceDate]="pendingRange()!.end"
|
|
522
|
+
aria-label="End time"
|
|
523
|
+
(timeChange)="onEndTime($event)"
|
|
524
|
+
/>
|
|
525
|
+
</div>
|
|
526
|
+
</div>
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
@if (showActions()) {
|
|
530
|
+
<div class="border-t border-border flex items-center justify-between gap-2 px-3 py-2">
|
|
531
|
+
<div class="flex items-center gap-2">
|
|
532
|
+
<button twButton variant="ghost" size="sm" type="button" (click)="handleToday()">
|
|
533
|
+
{{ todayLabel() }}
|
|
534
|
+
</button>
|
|
535
|
+
@if (pendingRange() !== null) {
|
|
536
|
+
<button twButton variant="ghost" size="sm" type="button" (click)="handleClear()">
|
|
537
|
+
{{ clearLabel() }}
|
|
538
|
+
</button>
|
|
539
|
+
}
|
|
540
|
+
</div>
|
|
541
|
+
<div class="flex items-center gap-2">
|
|
542
|
+
<button twButton variant="ghost" size="sm" type="button" (click)="handleCancel()">
|
|
543
|
+
{{ cancelLabel() }}
|
|
544
|
+
</button>
|
|
545
|
+
<button twButton variant="solid" size="sm" type="button" (click)="handleApply()">
|
|
546
|
+
{{ applyLabel() }}
|
|
547
|
+
</button>
|
|
548
|
+
</div>
|
|
549
|
+
</div>
|
|
550
|
+
}
|
|
551
|
+
</div>
|
|
552
|
+
`,
|
|
553
|
+
}]
|
|
554
|
+
}], ctorParameters: () => [], propDecorators: { calendarRef: [{ type: i0.ViewChild, args: ['calendar', { isSignal: true }] }], presetOptions: [{ type: i0.ViewChildren, args: [i0.forwardRef(() => DateRangePresetOptionDirective), { isSignal: true }] }] } });
|
|
555
|
+
|
|
556
|
+
// ── Constants ─────────────────────────────────────────────────────
|
|
557
|
+
const DEFAULT_DISPLAY_FORMAT = {
|
|
558
|
+
dateTimeFormat: { year: 'numeric', month: 'short', day: 'numeric' },
|
|
559
|
+
};
|
|
560
|
+
// ── Helpers ───────────────────────────────────────────────────────
|
|
561
|
+
function rangesEqual(a, b, adapter) {
|
|
562
|
+
if (a === null && b === null)
|
|
563
|
+
return true;
|
|
564
|
+
if (a === null || b === null)
|
|
565
|
+
return false;
|
|
566
|
+
const startEq = (a.start === null && b.start === null) ||
|
|
567
|
+
(a.start !== null && b.start !== null && adapter.sameDate(a.start, b.start));
|
|
568
|
+
const endEq = (a.end === null && b.end === null) ||
|
|
569
|
+
(a.end !== null && b.end !== null && adapter.sameDate(a.end, b.end));
|
|
570
|
+
return startEq && endEq;
|
|
571
|
+
}
|
|
572
|
+
// ── tv() config ───────────────────────────────────────────────────
|
|
573
|
+
const dateRangePickerVariants = tv({
|
|
574
|
+
slots: {
|
|
575
|
+
root: 'relative inline-flex items-center w-full text-fg transition-[color,border-color,box-shadow] duration-normal motion-reduce:transition-none',
|
|
576
|
+
trigger: 'flex-1 inline-flex items-center gap-2 min-w-0 bg-transparent text-left text-fg outline-none border-0 p-0 m-0 cursor-pointer focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary-500 disabled:cursor-not-allowed',
|
|
577
|
+
startText: 'truncate',
|
|
578
|
+
separator: 'shrink-0 text-fg-subtle',
|
|
579
|
+
endText: 'truncate',
|
|
580
|
+
placeholderText: 'text-fg-subtle truncate flex-1',
|
|
581
|
+
triggerIconWrapper: 'inline-flex items-center justify-center shrink-0 text-fg-muted',
|
|
582
|
+
triggerIcon: 'shrink-0 text-fg-muted transition-colors duration-normal motion-reduce:transition-none',
|
|
583
|
+
// size-6 (24×24 CSS px) is the WCAG AA minimum interactive target — bumping past size-5 keeps the affordance hittable without growing the inline row.
|
|
584
|
+
clearButton: 'inline-flex items-center justify-center shrink-0 rounded-md text-fg-muted hover:text-fg hover:bg-surface-muted transition-colors duration-normal motion-reduce:transition-none size-6 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary-500',
|
|
585
|
+
},
|
|
586
|
+
variants: {
|
|
587
|
+
size: {
|
|
588
|
+
xs: {
|
|
589
|
+
root: 'gap-1 text-xs',
|
|
590
|
+
// size-3.5 (14px) is a permitted half-step decorative chevron for xs
|
|
591
|
+
// density where neither size-3 nor size-4 lines up with adjacent text.
|
|
592
|
+
triggerIcon: 'size-3.5',
|
|
593
|
+
},
|
|
594
|
+
sm: {
|
|
595
|
+
root: 'gap-1.5 text-sm',
|
|
596
|
+
triggerIcon: 'size-4',
|
|
597
|
+
},
|
|
598
|
+
md: {
|
|
599
|
+
root: 'gap-2 text-sm',
|
|
600
|
+
triggerIcon: 'size-4',
|
|
601
|
+
},
|
|
602
|
+
lg: {
|
|
603
|
+
root: 'gap-2 text-base',
|
|
604
|
+
triggerIcon: 'size-5',
|
|
605
|
+
},
|
|
606
|
+
xl: {
|
|
607
|
+
root: 'gap-2 text-base',
|
|
608
|
+
triggerIcon: 'size-5',
|
|
609
|
+
},
|
|
610
|
+
},
|
|
611
|
+
variant: {
|
|
612
|
+
default: {
|
|
613
|
+
root: 'rounded-md border border-border bg-surface px-3 py-2 hover:border-border-strong',
|
|
614
|
+
},
|
|
615
|
+
naked: {
|
|
616
|
+
root: 'bg-transparent border-0 rounded-none p-0 focus-within:outline-none',
|
|
617
|
+
},
|
|
618
|
+
},
|
|
619
|
+
open: { true: { triggerIcon: 'text-fg' }, false: {} },
|
|
620
|
+
disabled: {
|
|
621
|
+
true: { root: 'opacity-50 pointer-events-none cursor-not-allowed' },
|
|
622
|
+
false: {},
|
|
623
|
+
},
|
|
624
|
+
focused: { true: {}, false: {} },
|
|
625
|
+
errorState: { true: {}, false: {} },
|
|
626
|
+
color: {
|
|
627
|
+
primary: {},
|
|
628
|
+
secondary: {},
|
|
629
|
+
accent: {},
|
|
630
|
+
neutral: {},
|
|
631
|
+
info: {},
|
|
632
|
+
success: {},
|
|
633
|
+
warning: {},
|
|
634
|
+
error: {},
|
|
635
|
+
},
|
|
636
|
+
},
|
|
637
|
+
compoundVariants: [
|
|
638
|
+
{ variant: 'default', focused: true, color: 'primary', class: { root: 'border-primary-500' } },
|
|
639
|
+
{ variant: 'default', focused: true, color: 'secondary', class: { root: 'border-secondary-500' } },
|
|
640
|
+
{ variant: 'default', focused: true, color: 'accent', class: { root: 'border-accent-500' } },
|
|
641
|
+
{ variant: 'default', focused: true, color: 'neutral', class: { root: 'border-border-strong' } },
|
|
642
|
+
{ variant: 'default', focused: true, color: 'info', class: { root: 'border-info-500' } },
|
|
643
|
+
{ variant: 'default', focused: true, color: 'success', class: { root: 'border-success-500' } },
|
|
644
|
+
{ variant: 'default', focused: true, color: 'warning', class: { root: 'border-warning-500' } },
|
|
645
|
+
{ variant: 'default', focused: true, color: 'error', class: { root: 'border-error-500' } },
|
|
646
|
+
{ variant: 'default', errorState: true, class: { root: 'border-error-500' } },
|
|
647
|
+
],
|
|
648
|
+
defaultVariants: {
|
|
649
|
+
size: 'md',
|
|
650
|
+
variant: 'default',
|
|
651
|
+
open: false,
|
|
652
|
+
disabled: false,
|
|
653
|
+
errorState: false,
|
|
654
|
+
focused: false,
|
|
655
|
+
color: 'primary',
|
|
656
|
+
},
|
|
657
|
+
}, { twMerge: true });
|
|
658
|
+
// ── ID generator ──────────────────────────────────────────────────
|
|
659
|
+
let nextDateRangePickerId = 0;
|
|
660
|
+
/**
|
|
661
|
+
* ARIA date-picker dialog for two-endpoint date ranges. Composes `tw-calendar`
|
|
662
|
+
* in `selectionMode="range"` inside a CDK overlay. Supports optional time
|
|
663
|
+
* selection via embedded `tw-time-picker`s and optional quick-select presets.
|
|
664
|
+
*
|
|
665
|
+
* All three Angular forms strategies (reactive, template-driven, signal-forms)
|
|
666
|
+
* are supported via `ControlValueAccessor`, and the component integrates with
|
|
667
|
+
* `<tw-form-field>` by implementing `FormFieldControl`.
|
|
668
|
+
*
|
|
669
|
+
* All date operations go through the injected `DateAdapter<D>`; call
|
|
670
|
+
* `provideNativeDateAdapter()` in your app providers to bootstrap the default.
|
|
671
|
+
*/
|
|
672
|
+
class DateRangePickerComponent extends FormFieldControl {
|
|
673
|
+
// ── Inputs ──
|
|
674
|
+
/** Id on the trigger element. Auto-generated when not provided. Used by the form-field's `<label for>` attribute. */
|
|
675
|
+
idInput = input(undefined, { ...(ngDevMode ? { debugName: "idInput" } : /* istanbul ignore next */ {}), alias: 'id' });
|
|
676
|
+
/** Earliest selectable date for either endpoint. Presets and calendar cells earlier than this are rejected or disabled. Defaults to `null`. */
|
|
677
|
+
minDate = input(null, ...(ngDevMode ? [{ debugName: "minDate" }] : /* istanbul ignore next */ []));
|
|
678
|
+
/** Latest selectable date for either endpoint. Presets and calendar cells later than this are rejected or disabled. Defaults to `null`. */
|
|
679
|
+
maxDate = input(null, ...(ngDevMode ? [{ debugName: "maxDate" }] : /* istanbul ignore next */ []));
|
|
680
|
+
/** Per-date predicate — return `false` to disable. Applied in both calendars. Presets that fall on a filtered date are skipped. */
|
|
681
|
+
dateFilter = input(null, ...(ngDevMode ? [{ debugName: "dateFilter" }] : /* istanbul ignore next */ []));
|
|
682
|
+
/** Which calendar view opens first — `'day'`, `'month'`, or `'year'`. Defaults to `'day'`. */
|
|
683
|
+
startView = input('day', ...(ngDevMode ? [{ debugName: "startView" }] : /* istanbul ignore next */ []));
|
|
684
|
+
/** Date the left calendar focuses on when opened with no value. Falls back to today. Ignored when a value is already set. */
|
|
685
|
+
startAt = input(null, ...(ngDevMode ? [{ debugName: "startAt" }] : /* istanbul ignore next */ []));
|
|
686
|
+
/** Display format for each endpoint, passed to `DateAdapter.format()`. When `showTime` is true and this is left at the default, hour/minute (and optional seconds) are folded in automatically. */
|
|
687
|
+
format = input(DEFAULT_DISPLAY_FORMAT, ...(ngDevMode ? [{ debugName: "format" }] : /* istanbul ignore next */ []));
|
|
688
|
+
/** Separator rendered between the two formatted endpoints in the trigger. Defaults to `" – "`. */
|
|
689
|
+
rangeSeparator = input(' – ', ...(ngDevMode ? [{ debugName: "rangeSeparator" }] : /* istanbul ignore next */ []));
|
|
690
|
+
/** Placeholder text shown in the trigger for an empty `start` endpoint. */
|
|
691
|
+
emptyStartLabel = input('Start date', ...(ngDevMode ? [{ debugName: "emptyStartLabel" }] : /* istanbul ignore next */ []));
|
|
692
|
+
/** Placeholder text shown in the trigger for an empty `end` endpoint. */
|
|
693
|
+
emptyEndLabel = input('End date', ...(ngDevMode ? [{ debugName: "emptyEndLabel" }] : /* istanbul ignore next */ []));
|
|
694
|
+
/** When set, overrides the composed `${emptyStartLabel}${rangeSeparator}${emptyEndLabel}` placeholder with a single string. */
|
|
695
|
+
placeholder = input(undefined, ...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
|
|
696
|
+
/** When true, the trigger cannot open the overlay and `aria-disabled="true"` is set. Defaults to `false`. */
|
|
697
|
+
disabledInput = input(false, { ...(ngDevMode ? { debugName: "disabledInput" } : /* istanbul ignore next */ {}), alias: 'disabled',
|
|
698
|
+
transform: booleanAttribute });
|
|
699
|
+
/** When true, exposes `aria-required="true"`. `Validators.required` on a bound `NgControl` is also honoured. Defaults to `false`. */
|
|
700
|
+
requiredInput = input(false, { ...(ngDevMode ? { debugName: "requiredInput" } : /* istanbul ignore next */ {}), alias: 'required',
|
|
701
|
+
transform: booleanAttribute });
|
|
702
|
+
/** Trigger padding, font size, and calendar cell density. Uses the shared `TwSize` scale. Defaults to `'md'`. */
|
|
703
|
+
size = input('md', ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
|
|
704
|
+
/** Semantic color for focused border, calendar range fill, and preset active state. Defaults to `'primary'`. */
|
|
705
|
+
color = input('primary', ...(ngDevMode ? [{ debugName: "color" }] : /* istanbul ignore next */ []));
|
|
706
|
+
/** Visual style of the trigger. `'default'` draws its own border; `'naked'` strips chrome so a parent (e.g. `tw-form-field`) owns it. Auto-resolves to `'naked'` when inside a form-field; otherwise `'default'`. */
|
|
707
|
+
variant = input(undefined, ...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []));
|
|
708
|
+
/** How many months the overlay shows side-by-side. `2` is the standard range-picker layout; use `1` for compact contexts. Defaults to `2`. */
|
|
709
|
+
numberOfMonths = input(2, ...(ngDevMode ? [{ debugName: "numberOfMonths" }] : /* istanbul ignore next */ []));
|
|
710
|
+
/** Optional quick-select presets rendered as a vertical list before the calendars. Each preset provides a label and a factory returning a `TwDateRange<D>`. An empty array hides the preset panel. */
|
|
711
|
+
presets = input([], ...(ngDevMode ? [{ debugName: "presets" }] : /* istanbul ignore next */ []));
|
|
712
|
+
/** Whether to show a clear-button affordance inside the trigger when a value is set. Defaults to `true`. */
|
|
713
|
+
// TRUE-default: a picker without a clear affordance forces consumers to wire one — most form pickers expect the inline clear, matching `<tw-input>`'s clear button.
|
|
714
|
+
showClear = input(true, ...(ngDevMode ? [{ debugName: "showClear" }] : /* istanbul ignore next */ []));
|
|
715
|
+
/** Minimum range length in days, inclusive. Commits shorter than this are rejected and surface `calendarRangeTooShort` on the bound `NgControl`. `null` = no minimum. Defaults to `null`. */
|
|
716
|
+
minRangeLength = input(null, ...(ngDevMode ? [{ debugName: "minRangeLength" }] : /* istanbul ignore next */ []));
|
|
717
|
+
/** Maximum range length in days, inclusive. Commits longer than this are rejected and surface `calendarRangeTooLong` on the bound `NgControl`. `null` = no maximum. Defaults to `null`. */
|
|
718
|
+
maxRangeLength = input(null, ...(ngDevMode ? [{ debugName: "maxRangeLength" }] : /* istanbul ignore next */ []));
|
|
719
|
+
/**
|
|
720
|
+
* Range-mode behavior knobs forwarded to the embedded calendar. Accepts a
|
|
721
|
+
* partial config — unset fields use the documented defaults on
|
|
722
|
+
* `RangeBehaviorConfig`. Defaults: `{ allowSingleDayRange: true, persistPartialRange: true, allowBackwardRange: false, disableRangesCrossingDisabledDates: false }`.
|
|
723
|
+
*/
|
|
724
|
+
rangeBehavior = input({}, ...(ngDevMode ? [{ debugName: "rangeBehavior" }] : /* istanbul ignore next */ []));
|
|
725
|
+
/** How the embedded calendar reacts to a click after a complete range. `'restart'` (default) starts a fresh draft; `'nearest-edge'` moves the nearer endpoint; `'require-clear'` blocks until cleared. */
|
|
726
|
+
rangeClickBehavior = input('restart', ...(ngDevMode ? [{ debugName: "rangeClickBehavior" }] : /* istanbul ignore next */ []));
|
|
727
|
+
/** Override first day of week (0=Sun, 1=Mon) on the embedded calendar. Falls back to the adapter's default. */
|
|
728
|
+
firstDayOfWeek = input(null, ...(ngDevMode ? [{ debugName: "firstDayOfWeek" }] : /* istanbul ignore next */ []));
|
|
729
|
+
/** Per-instance locale override. Forwarded to the embedded calendar and the underlying `DateAdapter` so the trigger display tracks the picker's locale. Falls back to Angular `LOCALE_ID` when `null`. */
|
|
730
|
+
locale = input(null, ...(ngDevMode ? [{ debugName: "locale" }] : /* istanbul ignore next */ []));
|
|
731
|
+
/** Function producing per-cell CSS classes on the embedded calendar. */
|
|
732
|
+
dateClass = input(null, ...(ngDevMode ? [{ debugName: "dateClass" }] : /* istanbul ignore next */ []));
|
|
733
|
+
/** Optional cell-content template, forwarded to the embedded calendar. Use to customize cell visuals beyond `dateClass`. */
|
|
734
|
+
cellTemplate = input(null, ...(ngDevMode ? [{ debugName: "cellTemplate" }] : /* istanbul ignore next */ []));
|
|
735
|
+
/** When true, renders a `Today / Clear / Cancel / Apply` action bar at the bottom of the overlay. The calendar commits on the second click by default — turn this on for touch-heavy contexts. Defaults to `false`. */
|
|
736
|
+
showActions = input(false, ...(ngDevMode ? [{ debugName: "showActions" }] : /* istanbul ignore next */ []));
|
|
737
|
+
/** When true, the overlay renders two `<tw-time-picker>` instances so users can pick times for the start and end of the range. Defaults to `false`. */
|
|
738
|
+
showTime = input(false, ...(ngDevMode ? [{ debugName: "showTime" }] : /* istanbul ignore next */ []));
|
|
739
|
+
/** Format of the embedded time-pickers. `'24h'` renders 00–23; `'12h'` adds an AM/PM toggle. Defaults to `'24h'`. */
|
|
740
|
+
timeFormat = input('24h', ...(ngDevMode ? [{ debugName: "timeFormat" }] : /* istanbul ignore next */ []));
|
|
741
|
+
/** Whether the embedded time-pickers expose a seconds field. Defaults to `false`. */
|
|
742
|
+
showSeconds = input(false, ...(ngDevMode ? [{ debugName: "showSeconds" }] : /* istanbul ignore next */ []));
|
|
743
|
+
/** Step for the embedded time-pickers' hour fields. Defaults to `1`. */
|
|
744
|
+
hourStep = input(1, ...(ngDevMode ? [{ debugName: "hourStep" }] : /* istanbul ignore next */ []));
|
|
745
|
+
/** Step for the embedded time-pickers' minute fields. Defaults to `1`. */
|
|
746
|
+
minuteStep = input(1, ...(ngDevMode ? [{ debugName: "minuteStep" }] : /* istanbul ignore next */ []));
|
|
747
|
+
/** Step for the embedded time-pickers' second fields. Defaults to `1`. */
|
|
748
|
+
secondStep = input(1, ...(ngDevMode ? [{ debugName: "secondStep" }] : /* istanbul ignore next */ []));
|
|
749
|
+
/** Label for the `Today` action in the overlay's action bar. */
|
|
750
|
+
todayLabel = input('Today', ...(ngDevMode ? [{ debugName: "todayLabel" }] : /* istanbul ignore next */ []));
|
|
751
|
+
/** Label for the `Clear` action in the overlay's action bar. */
|
|
752
|
+
clearLabel = input('Clear', ...(ngDevMode ? [{ debugName: "clearLabel" }] : /* istanbul ignore next */ []));
|
|
753
|
+
/** Label for the `Cancel` action in the overlay's action bar. */
|
|
754
|
+
cancelLabel = input('Cancel', ...(ngDevMode ? [{ debugName: "cancelLabel" }] : /* istanbul ignore next */ []));
|
|
755
|
+
/** Label for the `Apply` action in the overlay's action bar. */
|
|
756
|
+
applyLabel = input('Apply', ...(ngDevMode ? [{ debugName: "applyLabel" }] : /* istanbul ignore next */ []));
|
|
757
|
+
/** Extra class(es) applied to the overlay panel element. `twMerge` resolves conflicts with internal classes. */
|
|
758
|
+
panelClass = input('', ...(ngDevMode ? [{ debugName: "panelClass" }] : /* istanbul ignore next */ []));
|
|
759
|
+
/** CDK scroll strategy for the overlay. Defaults to `'reposition'`. */
|
|
760
|
+
scrollStrategy = input('reposition', ...(ngDevMode ? [{ debugName: "scrollStrategy" }] : /* istanbul ignore next */ []));
|
|
761
|
+
/** Pixel distance between trigger and overlay. Defaults to `4`. */
|
|
762
|
+
offset = input(4, ...(ngDevMode ? [{ debugName: "offset" }] : /* istanbul ignore next */ []));
|
|
763
|
+
/** Accessible label for the clear button. Defaults to `'Clear date range'`. */
|
|
764
|
+
clearAriaLabel = input('Clear date range', ...(ngDevMode ? [{ debugName: "clearAriaLabel" }] : /* istanbul ignore next */ []));
|
|
765
|
+
/** Per-instance override of the `ErrorStateMatcher`. When omitted, uses the injected `TW_ERROR_STATE_MATCHER`. */
|
|
766
|
+
errorStateMatcher = input(undefined, ...(ngDevMode ? [{ debugName: "errorStateMatcher" }] : /* istanbul ignore next */ []));
|
|
767
|
+
/** Accessible name for the trigger. Required when no visible label is supplied via `tw-form-field` or an external `aria-labelledby`. Alias: `aria-label`. */
|
|
768
|
+
ariaLabel = input(undefined, { ...(ngDevMode ? { debugName: "ariaLabel" } : /* istanbul ignore next */ {}), alias: 'aria-label' });
|
|
769
|
+
/** ID of an external element that labels the trigger. Alias: `aria-labelledby`. */
|
|
770
|
+
ariaLabelledby = input(undefined, { ...(ngDevMode ? { debugName: "ariaLabelledby" } : /* istanbul ignore next */ {}), alias: 'aria-labelledby' });
|
|
771
|
+
/** Consumer-supplied `aria-describedby` ids. The form-field preserves these when merging hint/error ids. Alias: `aria-describedby`. */
|
|
772
|
+
userAriaDescribedByInput = input(undefined, { ...(ngDevMode ? { debugName: "userAriaDescribedByInput" } : /* istanbul ignore next */ {}), alias: 'aria-describedby' });
|
|
773
|
+
// ── Models (two-way) ──
|
|
774
|
+
/** Two-way bound selected range. `null` when no selection. Setting programmatically updates the trigger display and the calendar selection; it does NOT trigger `onChange`. */
|
|
775
|
+
value = model(null, ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
|
|
776
|
+
/** Two-way bound open state of the overlay. Setting to `true` opens; setting to `false` closes. */
|
|
777
|
+
open = model(false, ...(ngDevMode ? [{ debugName: "open" }] : /* istanbul ignore next */ []));
|
|
778
|
+
// ── Outputs ──
|
|
779
|
+
/** Fires after the overlay's enter animation completes. Payload is the trigger element. */
|
|
780
|
+
opened = output();
|
|
781
|
+
/** Fires after the overlay's leave animation completes. Payload is the reason it closed. */
|
|
782
|
+
closed = output();
|
|
783
|
+
/** Fires after a commit — either from completing a range in the calendar, picking a preset, or applying via the action bar. */
|
|
784
|
+
rangeChange = output();
|
|
785
|
+
/** Fires when the user picks a preset from the list. Payload is the preset descriptor. Fires in addition to `rangeChange`. */
|
|
786
|
+
presetSelected = output();
|
|
787
|
+
// ── Injected deps ──
|
|
788
|
+
adapter = inject(DATE_ADAPTER);
|
|
789
|
+
overlayService = inject(Overlay);
|
|
790
|
+
focusMonitor = inject(FocusMonitor);
|
|
791
|
+
liveAnnouncer = inject(LiveAnnouncer);
|
|
792
|
+
coordinator = inject(PickerOverlayCoordinator);
|
|
793
|
+
elementRef = inject((ElementRef));
|
|
794
|
+
viewContainerRef = inject(ViewContainerRef);
|
|
795
|
+
injector = inject(Injector);
|
|
796
|
+
destroyRef = inject(DestroyRef);
|
|
797
|
+
formField = inject(FormFieldComponent, { optional: true });
|
|
798
|
+
// Lazy NgControl lookup avoids the construction-time cycle with NG_VALIDATORS
|
|
799
|
+
// (the host registers itself as a validator via `useExisting`, and NgModel's
|
|
800
|
+
// constructor pulls the validator set — eager `inject(NgControl, {self})`
|
|
801
|
+
// would resolve to this component while it is still being created).
|
|
802
|
+
ngControl = null;
|
|
803
|
+
parentForm = inject(NgForm, { optional: true });
|
|
804
|
+
parentFormGroup = inject(FormGroupDirective, { optional: true });
|
|
805
|
+
defaultMatcher = inject(TW_ERROR_STATE_MATCHER);
|
|
806
|
+
// ── View refs ──
|
|
807
|
+
triggerRef = viewChild.required('trigger');
|
|
808
|
+
// ── Identity ──
|
|
809
|
+
uid = nextDateRangePickerId++;
|
|
810
|
+
/** @internal */
|
|
811
|
+
hostId = `tw-date-range-picker-${this.uid}`;
|
|
812
|
+
/** @internal */
|
|
813
|
+
dialogId = `${this.hostId}-dialog`;
|
|
814
|
+
// ── Internal state ──
|
|
815
|
+
/** @internal Decouples programmatic writes from user commits. */
|
|
816
|
+
internalValue = linkedSignal(() => this.value(), ...(ngDevMode ? [{ debugName: "internalValue" }] : /* istanbul ignore next */ []));
|
|
817
|
+
/** @internal */
|
|
818
|
+
parseError = signal(false, ...(ngDevMode ? [{ debugName: "parseError" }] : /* istanbul ignore next */ []));
|
|
819
|
+
/** @internal */
|
|
820
|
+
rangeError = signal(false, ...(ngDevMode ? [{ debugName: "rangeError" }] : /* istanbul ignore next */ []));
|
|
821
|
+
/** @internal */
|
|
822
|
+
focusedSignal = signal(false, ...(ngDevMode ? [{ debugName: "focusedSignal" }] : /* istanbul ignore next */ []));
|
|
823
|
+
cvaDisabled = signal(false, ...(ngDevMode ? [{ debugName: "cvaDisabled" }] : /* istanbul ignore next */ []));
|
|
824
|
+
describedByIdsSignal = signal([], ...(ngDevMode ? [{ debugName: "describedByIdsSignal" }] : /* istanbul ignore next */ []));
|
|
825
|
+
closingSignal = signal(false, ...(ngDevMode ? [{ debugName: "closingSignal" }] : /* istanbul ignore next */ []));
|
|
826
|
+
lastValueBeforeOpen = signal(null, ...(ngDevMode ? [{ debugName: "lastValueBeforeOpen" }] : /* istanbul ignore next */ []));
|
|
827
|
+
pendingRange = signal(null, ...(ngDevMode ? [{ debugName: "pendingRange" }] : /* istanbul ignore next */ []));
|
|
828
|
+
currentView = signal('day', ...(ngDevMode ? [{ debugName: "currentView" }] : /* istanbul ignore next */ []));
|
|
829
|
+
_ngControlRev = signal(0, ...(ngDevMode ? [{ debugName: "_ngControlRev" }] : /* istanbul ignore next */ []));
|
|
830
|
+
_formSubmitRev = signal(0, ...(ngDevMode ? [{ debugName: "_formSubmitRev" }] : /* istanbul ignore next */ []));
|
|
831
|
+
onChange = () => { };
|
|
832
|
+
onTouched = () => { };
|
|
833
|
+
overlayInstanceSignal = signal(null, ...(ngDevMode ? [{ debugName: "overlayInstanceSignal" }] : /* istanbul ignore next */ []));
|
|
834
|
+
get overlayInstance() {
|
|
835
|
+
return this.overlayInstanceSignal();
|
|
836
|
+
}
|
|
837
|
+
// ── Derived state ──
|
|
838
|
+
/** @internal */
|
|
839
|
+
isDisabled = computed(() => {
|
|
840
|
+
this._ngControlRev();
|
|
841
|
+
return this.disabledInput() || this.cvaDisabled() || !!this.ngControl?.disabled;
|
|
842
|
+
}, ...(ngDevMode ? [{ debugName: "isDisabled" }] : /* istanbul ignore next */ []));
|
|
843
|
+
/** @internal Effective variant — auto-naked when wrapped in tw-form-field. */
|
|
844
|
+
resolvedVariant = computed(() => this.variant() ?? (this.formField ? 'naked' : 'default'), ...(ngDevMode ? [{ debugName: "resolvedVariant" }] : /* istanbul ignore next */ []));
|
|
845
|
+
/** @internal */
|
|
846
|
+
isEmpty = computed(() => {
|
|
847
|
+
const v = this.internalValue();
|
|
848
|
+
return v === null || (v.start === null && v.end === null);
|
|
849
|
+
}, ...(ngDevMode ? [{ debugName: "isEmpty" }] : /* istanbul ignore next */ []));
|
|
850
|
+
/** @internal Effective display format — folds hour/minute (and optional seconds) into the default when `showTime` is on and the user hasn't overridden `format`. */
|
|
851
|
+
effectiveFormat = computed(() => {
|
|
852
|
+
const fmt = this.format();
|
|
853
|
+
if (!this.showTime() || fmt !== DEFAULT_DISPLAY_FORMAT)
|
|
854
|
+
return fmt;
|
|
855
|
+
const hour12 = this.timeFormat() === '12h';
|
|
856
|
+
const base = {
|
|
857
|
+
year: 'numeric',
|
|
858
|
+
month: 'short',
|
|
859
|
+
day: 'numeric',
|
|
860
|
+
hour: '2-digit',
|
|
861
|
+
minute: '2-digit',
|
|
862
|
+
hour12,
|
|
863
|
+
};
|
|
864
|
+
if (this.showSeconds())
|
|
865
|
+
base.second = '2-digit';
|
|
866
|
+
return { dateTimeFormat: base };
|
|
867
|
+
}, ...(ngDevMode ? [{ debugName: "effectiveFormat" }] : /* istanbul ignore next */ []));
|
|
868
|
+
/** @internal */
|
|
869
|
+
startDisplay = computed(() => {
|
|
870
|
+
const v = this.internalValue();
|
|
871
|
+
if (v === null)
|
|
872
|
+
return '';
|
|
873
|
+
return v.start !== null
|
|
874
|
+
? this.adapter.format(v.start, this.effectiveFormat())
|
|
875
|
+
: this.emptyStartLabel();
|
|
876
|
+
}, ...(ngDevMode ? [{ debugName: "startDisplay" }] : /* istanbul ignore next */ []));
|
|
877
|
+
/** @internal */
|
|
878
|
+
endDisplay = computed(() => {
|
|
879
|
+
const v = this.internalValue();
|
|
880
|
+
if (v === null)
|
|
881
|
+
return '';
|
|
882
|
+
return v.end !== null
|
|
883
|
+
? this.adapter.format(v.end, this.effectiveFormat())
|
|
884
|
+
: this.emptyEndLabel();
|
|
885
|
+
}, ...(ngDevMode ? [{ debugName: "endDisplay" }] : /* istanbul ignore next */ []));
|
|
886
|
+
/** @internal */
|
|
887
|
+
placeholderDisplay = computed(() => {
|
|
888
|
+
const custom = this.placeholder();
|
|
889
|
+
if (custom)
|
|
890
|
+
return custom;
|
|
891
|
+
return `${this.emptyStartLabel()}${this.rangeSeparator()}${this.emptyEndLabel()}`;
|
|
892
|
+
}, ...(ngDevMode ? [{ debugName: "placeholderDisplay" }] : /* istanbul ignore next */ []));
|
|
893
|
+
/** @internal Accessible name composed from explicit aria-label plus the current range. */
|
|
894
|
+
triggerAccessibleName = computed(() => {
|
|
895
|
+
const label = this.ariaLabel();
|
|
896
|
+
if (this.ariaLabelledby())
|
|
897
|
+
return undefined;
|
|
898
|
+
const v = this.internalValue();
|
|
899
|
+
if (label && v !== null && v.start !== null && v.end !== null) {
|
|
900
|
+
const start = this.adapter.format(v.start, this.effectiveFormat());
|
|
901
|
+
const end = this.adapter.format(v.end, this.effectiveFormat());
|
|
902
|
+
return `${label}. Current range: ${start} to ${end}.`;
|
|
903
|
+
}
|
|
904
|
+
if (label)
|
|
905
|
+
return label;
|
|
906
|
+
return undefined;
|
|
907
|
+
}, ...(ngDevMode ? [{ debugName: "triggerAccessibleName" }] : /* istanbul ignore next */ []));
|
|
908
|
+
/** @internal */
|
|
909
|
+
activePresetId = computed(() => {
|
|
910
|
+
const v = this.internalValue();
|
|
911
|
+
if (v === null)
|
|
912
|
+
return undefined;
|
|
913
|
+
for (const preset of this.presets()) {
|
|
914
|
+
if (!preset.id)
|
|
915
|
+
continue;
|
|
916
|
+
let candidate;
|
|
917
|
+
try {
|
|
918
|
+
candidate = preset.range();
|
|
919
|
+
}
|
|
920
|
+
catch {
|
|
921
|
+
continue;
|
|
922
|
+
}
|
|
923
|
+
if (rangesEqual(candidate, v, this.adapter))
|
|
924
|
+
return preset.id;
|
|
925
|
+
}
|
|
926
|
+
return undefined;
|
|
927
|
+
}, ...(ngDevMode ? [{ debugName: "activePresetId" }] : /* istanbul ignore next */ []));
|
|
928
|
+
/** @internal */
|
|
929
|
+
describedBy = computed(() => {
|
|
930
|
+
const extra = this.describedByIdsSignal();
|
|
931
|
+
const user = this.userAriaDescribedByInput();
|
|
932
|
+
const merged = [...extra];
|
|
933
|
+
if (user) {
|
|
934
|
+
for (const id of user.split(/\s+/)) {
|
|
935
|
+
if (id)
|
|
936
|
+
merged.push(id);
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
return merged.length ? merged.join(' ') : '';
|
|
940
|
+
}, ...(ngDevMode ? [{ debugName: "describedBy" }] : /* istanbul ignore next */ []));
|
|
941
|
+
// ── tv() output ──
|
|
942
|
+
variantResult = computed(() => dateRangePickerVariants({
|
|
943
|
+
size: this.size(),
|
|
944
|
+
variant: this.resolvedVariant(),
|
|
945
|
+
open: this.open(),
|
|
946
|
+
disabled: this.isDisabled(),
|
|
947
|
+
errorState: this.errorState(),
|
|
948
|
+
focused: this.focusedSignal(),
|
|
949
|
+
color: this.color(),
|
|
950
|
+
}), ...(ngDevMode ? [{ debugName: "variantResult" }] : /* istanbul ignore next */ []));
|
|
951
|
+
/** @internal */
|
|
952
|
+
rootClasses = computed(() => this.variantResult().root(), ...(ngDevMode ? [{ debugName: "rootClasses" }] : /* istanbul ignore next */ []));
|
|
953
|
+
/** @internal */
|
|
954
|
+
triggerClasses = computed(() => this.variantResult().trigger(), ...(ngDevMode ? [{ debugName: "triggerClasses" }] : /* istanbul ignore next */ []));
|
|
955
|
+
/** @internal */
|
|
956
|
+
startTextClasses = computed(() => this.variantResult().startText(), ...(ngDevMode ? [{ debugName: "startTextClasses" }] : /* istanbul ignore next */ []));
|
|
957
|
+
/** @internal */
|
|
958
|
+
separatorClasses = computed(() => this.variantResult().separator(), ...(ngDevMode ? [{ debugName: "separatorClasses" }] : /* istanbul ignore next */ []));
|
|
959
|
+
/** @internal */
|
|
960
|
+
endTextClasses = computed(() => this.variantResult().endText(), ...(ngDevMode ? [{ debugName: "endTextClasses" }] : /* istanbul ignore next */ []));
|
|
961
|
+
/** @internal */
|
|
962
|
+
placeholderClasses = computed(() => this.variantResult().placeholderText(), ...(ngDevMode ? [{ debugName: "placeholderClasses" }] : /* istanbul ignore next */ []));
|
|
963
|
+
/** @internal */
|
|
964
|
+
triggerIconClasses = computed(() => this.variantResult().triggerIcon(), ...(ngDevMode ? [{ debugName: "triggerIconClasses" }] : /* istanbul ignore next */ []));
|
|
965
|
+
/** @internal */
|
|
966
|
+
triggerIconWrapperClasses = computed(() => this.variantResult().triggerIconWrapper(), ...(ngDevMode ? [{ debugName: "triggerIconWrapperClasses" }] : /* istanbul ignore next */ []));
|
|
967
|
+
/** @internal */
|
|
968
|
+
clearButtonClasses = computed(() => this.variantResult().clearButton(), ...(ngDevMode ? [{ debugName: "clearButtonClasses" }] : /* istanbul ignore next */ []));
|
|
969
|
+
// ── FormFieldControl impl ──
|
|
970
|
+
/** @internal */
|
|
971
|
+
id = computed(() => this.idInput() ?? this.hostId, ...(ngDevMode ? [{ debugName: "id" }] : /* istanbul ignore next */ []));
|
|
972
|
+
/** @internal */
|
|
973
|
+
focused = this.focusedSignal.asReadonly();
|
|
974
|
+
/** @internal */
|
|
975
|
+
empty = this.isEmpty;
|
|
976
|
+
/** @internal */
|
|
977
|
+
disabled = this.isDisabled;
|
|
978
|
+
/** @internal */
|
|
979
|
+
required = computed(() => {
|
|
980
|
+
this._ngControlRev();
|
|
981
|
+
if (this.requiredInput())
|
|
982
|
+
return true;
|
|
983
|
+
return !!this.ngControl?.control?.hasValidator(Validators.required);
|
|
984
|
+
}, ...(ngDevMode ? [{ debugName: "required" }] : /* istanbul ignore next */ []));
|
|
985
|
+
/** @internal */
|
|
986
|
+
errorState = computed(() => {
|
|
987
|
+
this._ngControlRev();
|
|
988
|
+
this._formSubmitRev();
|
|
989
|
+
this.focusedSignal();
|
|
990
|
+
if (this.parseError() || this.rangeError())
|
|
991
|
+
return true;
|
|
992
|
+
const matcher = this.errorStateMatcher() ?? this.defaultMatcher;
|
|
993
|
+
const form = this.parentFormGroup ??
|
|
994
|
+
this.parentForm;
|
|
995
|
+
return matcher.isErrorState(this.ngControl?.control ?? null, form);
|
|
996
|
+
}, ...(ngDevMode ? [{ debugName: "errorState" }] : /* istanbul ignore next */ []));
|
|
997
|
+
/** @internal */
|
|
998
|
+
controlType = 'date-range-picker';
|
|
999
|
+
/** @internal */
|
|
1000
|
+
userAriaDescribedBy = computed(() => this.userAriaDescribedByInput(), ...(ngDevMode ? [{ debugName: "userAriaDescribedBy" }] : /* istanbul ignore next */ []));
|
|
1001
|
+
// ── Constructor ──
|
|
1002
|
+
constructor() {
|
|
1003
|
+
super();
|
|
1004
|
+
// Mirror the `open` model into the overlay lifecycle.
|
|
1005
|
+
effect(() => {
|
|
1006
|
+
const shouldOpen = this.open();
|
|
1007
|
+
const disabled = this.isDisabled();
|
|
1008
|
+
if (disabled && this.overlayInstance) {
|
|
1009
|
+
this.closeOverlay('programmatic');
|
|
1010
|
+
return;
|
|
1011
|
+
}
|
|
1012
|
+
if (shouldOpen && !this.overlayInstance && !disabled && !this.closingSignal()) {
|
|
1013
|
+
this.openOverlay();
|
|
1014
|
+
}
|
|
1015
|
+
else if (!shouldOpen && this.overlayInstance && !this.closingSignal()) {
|
|
1016
|
+
this.closeOverlay('programmatic');
|
|
1017
|
+
}
|
|
1018
|
+
});
|
|
1019
|
+
// Push config into the overlay whenever anything relevant changes.
|
|
1020
|
+
// Reads happen in the tracked phase; writes to the overlay instance are
|
|
1021
|
+
// wrapped in `untracked()` so they never feed back into this effect.
|
|
1022
|
+
effect(() => {
|
|
1023
|
+
const instance = this.overlayInstance;
|
|
1024
|
+
if (!instance)
|
|
1025
|
+
return;
|
|
1026
|
+
const size = this.size();
|
|
1027
|
+
const color = this.color();
|
|
1028
|
+
const minDate = this.minDate();
|
|
1029
|
+
const maxDate = this.maxDate();
|
|
1030
|
+
const dateFilter = this.dateFilter();
|
|
1031
|
+
const startView = this.startView();
|
|
1032
|
+
const numberOfMonths = this.numberOfMonths();
|
|
1033
|
+
const pendingRange = this.pendingRange();
|
|
1034
|
+
const currentView = this.currentView();
|
|
1035
|
+
const presets = this.presets();
|
|
1036
|
+
const activePresetId = this.activePresetId();
|
|
1037
|
+
const showActions = this.showActions();
|
|
1038
|
+
const showTime = this.showTime();
|
|
1039
|
+
const timeFormat = this.timeFormat();
|
|
1040
|
+
const showSeconds = this.showSeconds();
|
|
1041
|
+
const hourStep = this.hourStep();
|
|
1042
|
+
const minuteStep = this.minuteStep();
|
|
1043
|
+
const secondStep = this.secondStep();
|
|
1044
|
+
const todayLabel = this.todayLabel();
|
|
1045
|
+
const clearLabel = this.clearLabel();
|
|
1046
|
+
const cancelLabel = this.cancelLabel();
|
|
1047
|
+
const applyLabel = this.applyLabel();
|
|
1048
|
+
const rangeSeparator = this.rangeSeparator();
|
|
1049
|
+
const dialogAriaLabel = this.resolveDialogAriaLabel();
|
|
1050
|
+
const panelClassValue = this.resolvePanelClass();
|
|
1051
|
+
const minRangeLength = this.minRangeLength();
|
|
1052
|
+
const maxRangeLength = this.maxRangeLength();
|
|
1053
|
+
const rangeBehavior = this.rangeBehavior();
|
|
1054
|
+
const rangeClickBehavior = this.rangeClickBehavior();
|
|
1055
|
+
const firstDayOfWeek = this.firstDayOfWeek();
|
|
1056
|
+
const locale = this.locale();
|
|
1057
|
+
const dateClass = this.dateClass();
|
|
1058
|
+
const cellTemplate = this.cellTemplate();
|
|
1059
|
+
untracked(() => {
|
|
1060
|
+
instance.size.set(size);
|
|
1061
|
+
instance.color.set(color);
|
|
1062
|
+
instance.minDate.set(minDate);
|
|
1063
|
+
instance.maxDate.set(maxDate);
|
|
1064
|
+
instance.dateFilter.set(dateFilter);
|
|
1065
|
+
instance.startView.set(startView);
|
|
1066
|
+
instance.numberOfMonths.set(numberOfMonths);
|
|
1067
|
+
instance.pendingRange.set(pendingRange);
|
|
1068
|
+
instance.currentView.set(currentView);
|
|
1069
|
+
instance.presets.set(presets);
|
|
1070
|
+
instance.activePresetId.set(activePresetId);
|
|
1071
|
+
instance.showActions.set(showActions);
|
|
1072
|
+
instance.showTime.set(showTime);
|
|
1073
|
+
instance.timeFormat.set(timeFormat);
|
|
1074
|
+
instance.showSeconds.set(showSeconds);
|
|
1075
|
+
instance.hourStep.set(hourStep);
|
|
1076
|
+
instance.minuteStep.set(minuteStep);
|
|
1077
|
+
instance.secondStep.set(secondStep);
|
|
1078
|
+
instance.todayLabel.set(todayLabel);
|
|
1079
|
+
instance.clearLabel.set(clearLabel);
|
|
1080
|
+
instance.cancelLabel.set(cancelLabel);
|
|
1081
|
+
instance.applyLabel.set(applyLabel);
|
|
1082
|
+
instance.rangeSeparator.set(rangeSeparator);
|
|
1083
|
+
instance.dialogId.set(this.dialogId);
|
|
1084
|
+
instance.dialogAriaLabel.set(dialogAriaLabel);
|
|
1085
|
+
instance.panelClassValue.set(panelClassValue);
|
|
1086
|
+
instance.minRangeLength.set(minRangeLength);
|
|
1087
|
+
instance.maxRangeLength.set(maxRangeLength);
|
|
1088
|
+
instance.rangeBehavior.set(rangeBehavior);
|
|
1089
|
+
instance.rangeClickBehavior.set(rangeClickBehavior);
|
|
1090
|
+
instance.firstDayOfWeek.set(firstDayOfWeek);
|
|
1091
|
+
instance.locale.set(locale);
|
|
1092
|
+
instance.dateClass.set(dateClass);
|
|
1093
|
+
instance.cellTemplate.set(cellTemplate);
|
|
1094
|
+
});
|
|
1095
|
+
});
|
|
1096
|
+
// Push locale into the adapter so the trigger display tracks the picker's
|
|
1097
|
+
// locale; the calendar runs its own setLocale internally for its grid.
|
|
1098
|
+
effect(() => {
|
|
1099
|
+
const locale = this.locale();
|
|
1100
|
+
if (locale === null)
|
|
1101
|
+
return;
|
|
1102
|
+
untracked(() => this.adapter.setLocale(locale));
|
|
1103
|
+
});
|
|
1104
|
+
// Re-run validation whenever a constraint input changes so consumer
|
|
1105
|
+
// statusChanges fires (mirrors the calendar's pattern).
|
|
1106
|
+
effect(() => {
|
|
1107
|
+
this.minDate();
|
|
1108
|
+
this.maxDate();
|
|
1109
|
+
this.dateFilter();
|
|
1110
|
+
this.minRangeLength();
|
|
1111
|
+
this.maxRangeLength();
|
|
1112
|
+
untracked(() => this.validatorOnChange());
|
|
1113
|
+
});
|
|
1114
|
+
// Dev-mode accessible-name warning.
|
|
1115
|
+
afterNextRender(() => {
|
|
1116
|
+
if (!isDevMode())
|
|
1117
|
+
return;
|
|
1118
|
+
const hasLabel = !!this.ariaLabel() ||
|
|
1119
|
+
!!this.ariaLabelledby() ||
|
|
1120
|
+
!!this.formField?.labelChild();
|
|
1121
|
+
if (!hasLabel) {
|
|
1122
|
+
console.warn('[tw-date-range-picker] The date-range-picker has no accessible name. Set aria-label, aria-labelledby, or wrap the component in a <tw-form-field> with a <label twLabel>.');
|
|
1123
|
+
}
|
|
1124
|
+
});
|
|
1125
|
+
// Monitor focus on the host (descendants-aware so the overlay counts while open).
|
|
1126
|
+
const monitorSub = this.focusMonitor
|
|
1127
|
+
.monitor(this.elementRef, true)
|
|
1128
|
+
.subscribe((origin) => {
|
|
1129
|
+
const wasFocused = this.focusedSignal();
|
|
1130
|
+
this.focusedSignal.set(!!origin);
|
|
1131
|
+
if (wasFocused && !origin) {
|
|
1132
|
+
this.onTouched();
|
|
1133
|
+
this._ngControlRev.update((n) => n + 1);
|
|
1134
|
+
}
|
|
1135
|
+
});
|
|
1136
|
+
this.destroyRef.onDestroy(() => {
|
|
1137
|
+
monitorSub.unsubscribe();
|
|
1138
|
+
this.focusMonitor.stopMonitoring(this.elementRef);
|
|
1139
|
+
// The coordinator's own DestroyRef.onDestroy disposes the overlay and
|
|
1140
|
+
// clears its timers; we only need to drop the local instance signal.
|
|
1141
|
+
this.overlayInstanceSignal.set(null);
|
|
1142
|
+
});
|
|
1143
|
+
}
|
|
1144
|
+
ngOnInit() {
|
|
1145
|
+
// Resolve the bound NgControl lazily (see field declaration). By the time
|
|
1146
|
+
// ngOnInit runs the host's FormControlName / NgModel is fully constructed.
|
|
1147
|
+
this.ngControl = this.injector.get(NgControl, null, {
|
|
1148
|
+
self: true,
|
|
1149
|
+
optional: true,
|
|
1150
|
+
});
|
|
1151
|
+
// Wire this component as the NgControl's value accessor. NG_VALUE_ACCESSOR
|
|
1152
|
+
// is not registered as a provider to keep `inject(NgControl, { self })`
|
|
1153
|
+
// possible — we assign it here instead.
|
|
1154
|
+
if (this.ngControl) {
|
|
1155
|
+
this.ngControl.valueAccessor = this;
|
|
1156
|
+
}
|
|
1157
|
+
const ctrl = this.ngControl?.control;
|
|
1158
|
+
if (ctrl) {
|
|
1159
|
+
const streams = [ctrl.statusChanges, ctrl.valueChanges].filter((s) => !!s);
|
|
1160
|
+
if (streams.length) {
|
|
1161
|
+
merge(...streams)
|
|
1162
|
+
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
1163
|
+
.subscribe(() => this._ngControlRev.update((n) => n + 1));
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
const submit = this.parentFormGroup?.ngSubmit ?? this.parentForm?.ngSubmit;
|
|
1167
|
+
if (submit) {
|
|
1168
|
+
submit
|
|
1169
|
+
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
1170
|
+
.subscribe(() => this._formSubmitRev.update((n) => n + 1));
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
// ── Public imperative API ──
|
|
1174
|
+
/** Opens the overlay. No-op when disabled or already open. */
|
|
1175
|
+
openPicker() {
|
|
1176
|
+
if (this.isDisabled() || this.open())
|
|
1177
|
+
return;
|
|
1178
|
+
this.open.set(true);
|
|
1179
|
+
}
|
|
1180
|
+
/** Closes the overlay. No-op when already closed. */
|
|
1181
|
+
closePicker() {
|
|
1182
|
+
if (!this.open())
|
|
1183
|
+
return;
|
|
1184
|
+
this.open.set(false);
|
|
1185
|
+
}
|
|
1186
|
+
/** Toggles the overlay's open state. */
|
|
1187
|
+
toggle() {
|
|
1188
|
+
if (this.open())
|
|
1189
|
+
this.closePicker();
|
|
1190
|
+
else
|
|
1191
|
+
this.openPicker();
|
|
1192
|
+
}
|
|
1193
|
+
/** Clears the current range and emits `rangeChange` with `source: 'clear'`. */
|
|
1194
|
+
clear() {
|
|
1195
|
+
this.commit(null, 'clear');
|
|
1196
|
+
}
|
|
1197
|
+
// ── Trigger interactions ──
|
|
1198
|
+
/** @internal */
|
|
1199
|
+
onTriggerClick() {
|
|
1200
|
+
if (this.isDisabled())
|
|
1201
|
+
return;
|
|
1202
|
+
this.toggle();
|
|
1203
|
+
}
|
|
1204
|
+
/** @internal */
|
|
1205
|
+
onTriggerKeydown(event) {
|
|
1206
|
+
if (this.isDisabled())
|
|
1207
|
+
return;
|
|
1208
|
+
if (event.altKey && event.key === 'ArrowDown') {
|
|
1209
|
+
event.preventDefault();
|
|
1210
|
+
this.openPicker();
|
|
1211
|
+
return;
|
|
1212
|
+
}
|
|
1213
|
+
if (event.altKey && event.key === 'ArrowUp') {
|
|
1214
|
+
if (this.open()) {
|
|
1215
|
+
event.preventDefault();
|
|
1216
|
+
this.closeOverlay('cancel', /* restore */ true);
|
|
1217
|
+
}
|
|
1218
|
+
return;
|
|
1219
|
+
}
|
|
1220
|
+
if (event.key === 'Escape' && this.open()) {
|
|
1221
|
+
event.preventDefault();
|
|
1222
|
+
this.closeOverlay('escape', /* restore */ true);
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
/** @internal */
|
|
1226
|
+
onClearClick(event) {
|
|
1227
|
+
event.stopPropagation();
|
|
1228
|
+
event.preventDefault();
|
|
1229
|
+
this.clear();
|
|
1230
|
+
}
|
|
1231
|
+
// ── Commit ──
|
|
1232
|
+
commit(next, source) {
|
|
1233
|
+
const normalised = this.normalizeRange(next);
|
|
1234
|
+
const previous = this.internalValue();
|
|
1235
|
+
if (rangesEqual(normalised, previous, this.adapter) && source !== 'programmatic') {
|
|
1236
|
+
// No change from a user action — still mark as touched for CVA parity,
|
|
1237
|
+
// but skip onChange and the rangeChange emission.
|
|
1238
|
+
this.onTouched();
|
|
1239
|
+
return;
|
|
1240
|
+
}
|
|
1241
|
+
this.internalValue.set(normalised);
|
|
1242
|
+
this.value.set(normalised);
|
|
1243
|
+
this.parseError.set(false);
|
|
1244
|
+
this.rangeError.set(false);
|
|
1245
|
+
if (source !== 'programmatic') {
|
|
1246
|
+
this.onChange(normalised);
|
|
1247
|
+
this.onTouched();
|
|
1248
|
+
this.announce(normalised);
|
|
1249
|
+
}
|
|
1250
|
+
this.rangeChange.emit({ value: normalised, previousValue: previous, source });
|
|
1251
|
+
}
|
|
1252
|
+
announce(value) {
|
|
1253
|
+
if (value === null) {
|
|
1254
|
+
this.liveAnnouncer.announce('Date range cleared', 'polite');
|
|
1255
|
+
return;
|
|
1256
|
+
}
|
|
1257
|
+
const fmt = this.effectiveFormat();
|
|
1258
|
+
if (value.start !== null && value.end !== null) {
|
|
1259
|
+
const start = this.adapter.format(value.start, fmt);
|
|
1260
|
+
const end = this.adapter.format(value.end, fmt);
|
|
1261
|
+
this.liveAnnouncer.announce(`${start} to ${end} selected`, 'polite');
|
|
1262
|
+
}
|
|
1263
|
+
else if (value.start !== null) {
|
|
1264
|
+
const start = this.adapter.format(value.start, fmt);
|
|
1265
|
+
this.liveAnnouncer.announce(`${start} selected. Pick end date.`, 'polite');
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1268
|
+
normalizeRange(v) {
|
|
1269
|
+
if (v === null)
|
|
1270
|
+
return null;
|
|
1271
|
+
if (v.start === null && v.end === null)
|
|
1272
|
+
return null;
|
|
1273
|
+
return v;
|
|
1274
|
+
}
|
|
1275
|
+
clampRange(range) {
|
|
1276
|
+
const min = this.minDate();
|
|
1277
|
+
const max = this.maxDate();
|
|
1278
|
+
const start = range.start !== null ? this.adapter.clampDate(range.start, min, max) : null;
|
|
1279
|
+
const end = range.end !== null ? this.adapter.clampDate(range.end, min, max) : null;
|
|
1280
|
+
return new TwDateRange(start, end);
|
|
1281
|
+
}
|
|
1282
|
+
isRangeValid(range) {
|
|
1283
|
+
const min = this.minDate();
|
|
1284
|
+
const max = this.maxDate();
|
|
1285
|
+
const filter = this.dateFilter();
|
|
1286
|
+
const endpoints = [range.start, range.end].filter((d) => d !== null);
|
|
1287
|
+
for (const d of endpoints) {
|
|
1288
|
+
if (!this.adapter.isValid(d))
|
|
1289
|
+
return false;
|
|
1290
|
+
if (min && this.adapter.compare(d, min) < 0)
|
|
1291
|
+
return false;
|
|
1292
|
+
if (max && this.adapter.compare(d, max) > 0)
|
|
1293
|
+
return false;
|
|
1294
|
+
if (filter && !filter(d))
|
|
1295
|
+
return false;
|
|
1296
|
+
}
|
|
1297
|
+
// Same-day ranges with time mode: end time must not be earlier than start time.
|
|
1298
|
+
if (this.showTime() &&
|
|
1299
|
+
range.start !== null &&
|
|
1300
|
+
range.end !== null &&
|
|
1301
|
+
this.adapter.sameDate(range.start, range.end) &&
|
|
1302
|
+
this.adapter.compare(range.start, range.end) > 0) {
|
|
1303
|
+
return false;
|
|
1304
|
+
}
|
|
1305
|
+
// Range-length constraints (Phase 4 codes).
|
|
1306
|
+
if (range.start !== null && range.end !== null) {
|
|
1307
|
+
const minLen = this.minRangeLength();
|
|
1308
|
+
const maxLen = this.maxRangeLength();
|
|
1309
|
+
if (minLen !== null || maxLen !== null) {
|
|
1310
|
+
const length = this.rangeLengthInDays(range.start, range.end);
|
|
1311
|
+
if (minLen !== null && length < minLen)
|
|
1312
|
+
return false;
|
|
1313
|
+
if (maxLen !== null && length > maxLen)
|
|
1314
|
+
return false;
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1317
|
+
return true;
|
|
1318
|
+
}
|
|
1319
|
+
rangeLengthInDays(start, end) {
|
|
1320
|
+
// Inclusive day count (start + 1 day = length 2). Mirrors
|
|
1321
|
+
// `rangeLengthDays` in calendar.utils so picker-level validation matches
|
|
1322
|
+
// the calendar's commit-time validation.
|
|
1323
|
+
const [lo, hi] = this.adapter.compare(start, end) <= 0 ? [start, end] : [end, start];
|
|
1324
|
+
const a = this.adapter.startOfDay(lo);
|
|
1325
|
+
const b = this.adapter.startOfDay(hi);
|
|
1326
|
+
let count = 1;
|
|
1327
|
+
let cursor = a;
|
|
1328
|
+
while (this.adapter.compare(cursor, b) < 0) {
|
|
1329
|
+
cursor = this.adapter.addDays(cursor, 1);
|
|
1330
|
+
count++;
|
|
1331
|
+
}
|
|
1332
|
+
return count;
|
|
1333
|
+
}
|
|
1334
|
+
// ── Overlay lifecycle ──
|
|
1335
|
+
openOverlay() {
|
|
1336
|
+
const current = this.internalValue();
|
|
1337
|
+
this.lastValueBeforeOpen.set(current);
|
|
1338
|
+
this.pendingRange.set(current);
|
|
1339
|
+
this.currentView.set(this.startView());
|
|
1340
|
+
const result = this.coordinator.open({
|
|
1341
|
+
origin: this.elementRef,
|
|
1342
|
+
portalComponent: DateRangePickerOverlayComponent,
|
|
1343
|
+
viewContainerRef: this.viewContainerRef,
|
|
1344
|
+
injector: this.injector,
|
|
1345
|
+
positions: buildSelectLikePositions(this.offset()),
|
|
1346
|
+
scrollStrategy: resolveSelectScrollStrategy(this.scrollStrategy(), this.overlayService),
|
|
1347
|
+
panelClass: 'tw-date-range-picker-panel',
|
|
1348
|
+
});
|
|
1349
|
+
if (!result)
|
|
1350
|
+
return;
|
|
1351
|
+
const instance = result.instance;
|
|
1352
|
+
instance.onCalendarSelect.set((range) => this.onCalendarSelection(range));
|
|
1353
|
+
instance.onPresetSelect.set((preset) => this.onPresetClick(preset));
|
|
1354
|
+
instance.onStartTimeChange.set((date) => this.onStartTimeChange(date));
|
|
1355
|
+
instance.onEndTimeChange.set((date) => this.onEndTimeChange(date));
|
|
1356
|
+
instance.onViewChange.set((view) => this.onViewChanged(view));
|
|
1357
|
+
instance.onToday.set(() => this.onTodayClicked());
|
|
1358
|
+
instance.onClear.set(() => this.onClearAction());
|
|
1359
|
+
instance.onCancel.set(() => this.onCancelAction());
|
|
1360
|
+
instance.onApply.set(() => this.onApplyAction());
|
|
1361
|
+
// Push the initial config synchronously so the first render reflects the
|
|
1362
|
+
// current inputs; the effect in the constructor keeps things in sync afterwards.
|
|
1363
|
+
instance.size.set(this.size());
|
|
1364
|
+
instance.color.set(this.color());
|
|
1365
|
+
instance.minDate.set(this.minDate());
|
|
1366
|
+
instance.maxDate.set(this.maxDate());
|
|
1367
|
+
instance.dateFilter.set(this.dateFilter());
|
|
1368
|
+
instance.startView.set(this.startView());
|
|
1369
|
+
instance.numberOfMonths.set(this.numberOfMonths());
|
|
1370
|
+
instance.pendingRange.set(this.pendingRange());
|
|
1371
|
+
instance.currentView.set(this.currentView());
|
|
1372
|
+
instance.presets.set(this.presets());
|
|
1373
|
+
instance.activePresetId.set(this.activePresetId());
|
|
1374
|
+
instance.showActions.set(this.showActions());
|
|
1375
|
+
instance.showTime.set(this.showTime());
|
|
1376
|
+
instance.timeFormat.set(this.timeFormat());
|
|
1377
|
+
instance.showSeconds.set(this.showSeconds());
|
|
1378
|
+
instance.hourStep.set(this.hourStep());
|
|
1379
|
+
instance.minuteStep.set(this.minuteStep());
|
|
1380
|
+
instance.secondStep.set(this.secondStep());
|
|
1381
|
+
instance.todayLabel.set(this.todayLabel());
|
|
1382
|
+
instance.clearLabel.set(this.clearLabel());
|
|
1383
|
+
instance.cancelLabel.set(this.cancelLabel());
|
|
1384
|
+
instance.applyLabel.set(this.applyLabel());
|
|
1385
|
+
instance.rangeSeparator.set(this.rangeSeparator());
|
|
1386
|
+
instance.minRangeLength.set(this.minRangeLength());
|
|
1387
|
+
instance.maxRangeLength.set(this.maxRangeLength());
|
|
1388
|
+
instance.rangeBehavior.set(this.rangeBehavior());
|
|
1389
|
+
instance.rangeClickBehavior.set(this.rangeClickBehavior());
|
|
1390
|
+
instance.firstDayOfWeek.set(this.firstDayOfWeek());
|
|
1391
|
+
instance.locale.set(this.locale());
|
|
1392
|
+
instance.dateClass.set(this.dateClass());
|
|
1393
|
+
instance.cellTemplate.set(this.cellTemplate());
|
|
1394
|
+
this.overlayInstanceSignal.set(instance);
|
|
1395
|
+
// Flush the initial config into the overlay's first render so view children
|
|
1396
|
+
// (calendar, time pickers, action bar) observe the picker's current inputs.
|
|
1397
|
+
result.componentRef.changeDetectorRef.detectChanges();
|
|
1398
|
+
// Per-open streams from the coordinator complete on close, so no
|
|
1399
|
+
// takeUntilDestroyed is needed.
|
|
1400
|
+
this.coordinator.backdropClick$().subscribe(() => {
|
|
1401
|
+
// When an action bar is present, backdrop click acts as cancel.
|
|
1402
|
+
if (this.showActions()) {
|
|
1403
|
+
this.closeOverlay('backdrop', /* restore */ true);
|
|
1404
|
+
}
|
|
1405
|
+
else {
|
|
1406
|
+
this.closeOverlay('backdrop');
|
|
1407
|
+
}
|
|
1408
|
+
});
|
|
1409
|
+
this.coordinator.escape$().subscribe((event) => {
|
|
1410
|
+
event.preventDefault();
|
|
1411
|
+
this.closeOverlay('escape', /* restore */ true);
|
|
1412
|
+
});
|
|
1413
|
+
// Defer `opened` emission until after the enter animation completes — was
|
|
1414
|
+
// previously fired synchronously on open() (mirrors the date-picker fix).
|
|
1415
|
+
this.coordinator
|
|
1416
|
+
.opened$()
|
|
1417
|
+
.subscribe(() => this.opened.emit({ trigger: this.elementRef.nativeElement }));
|
|
1418
|
+
queueMicrotask(() => {
|
|
1419
|
+
this.overlayInstance?.focusCalendar();
|
|
1420
|
+
});
|
|
1421
|
+
}
|
|
1422
|
+
closeOverlay(reason, restore = false) {
|
|
1423
|
+
if (this.closingSignal() || !this.overlayInstance)
|
|
1424
|
+
return;
|
|
1425
|
+
this.closingSignal.set(true);
|
|
1426
|
+
this.overlayInstance.leaving.set(true);
|
|
1427
|
+
if (restore) {
|
|
1428
|
+
const previous = this.lastValueBeforeOpen();
|
|
1429
|
+
if (!rangesEqual(previous, this.internalValue(), this.adapter)) {
|
|
1430
|
+
// Silent restore — no emit of rangeChange.
|
|
1431
|
+
this.internalValue.set(previous);
|
|
1432
|
+
this.value.set(previous);
|
|
1433
|
+
this.parseError.set(false);
|
|
1434
|
+
this.rangeError.set(false);
|
|
1435
|
+
}
|
|
1436
|
+
}
|
|
1437
|
+
this.triggerRef().nativeElement.focus();
|
|
1438
|
+
this.coordinator.close(() => {
|
|
1439
|
+
this.overlayInstanceSignal.set(null);
|
|
1440
|
+
this.pendingRange.set(null);
|
|
1441
|
+
untracked(() => this.open.set(false));
|
|
1442
|
+
this.closingSignal.set(false);
|
|
1443
|
+
this.closed.emit(reason);
|
|
1444
|
+
});
|
|
1445
|
+
}
|
|
1446
|
+
// ── Overlay callbacks ──
|
|
1447
|
+
/** @internal */
|
|
1448
|
+
onCalendarSelection(range) {
|
|
1449
|
+
this.pendingRange.set(range);
|
|
1450
|
+
if (!range.complete)
|
|
1451
|
+
return;
|
|
1452
|
+
if (this.showActions() || this.showTime())
|
|
1453
|
+
return;
|
|
1454
|
+
if (!this.isRangeValid(range)) {
|
|
1455
|
+
this.rangeError.set(true);
|
|
1456
|
+
return;
|
|
1457
|
+
}
|
|
1458
|
+
this.commit(range, 'calendar');
|
|
1459
|
+
this.closeOverlay('select');
|
|
1460
|
+
}
|
|
1461
|
+
onPresetClick(preset) {
|
|
1462
|
+
let raw;
|
|
1463
|
+
try {
|
|
1464
|
+
raw = preset.range();
|
|
1465
|
+
}
|
|
1466
|
+
catch {
|
|
1467
|
+
this.liveAnnouncer.announce(`${preset.label} could not be applied.`, 'polite');
|
|
1468
|
+
return;
|
|
1469
|
+
}
|
|
1470
|
+
const clamped = this.clampRange(raw);
|
|
1471
|
+
if (!this.isRangeValid(clamped) || !clamped.complete) {
|
|
1472
|
+
this.liveAnnouncer.announce(`${preset.label} is not available in the current filter.`, 'polite');
|
|
1473
|
+
return;
|
|
1474
|
+
}
|
|
1475
|
+
this.pendingRange.set(clamped);
|
|
1476
|
+
this.presetSelected.emit(preset);
|
|
1477
|
+
if (this.showActions() || this.showTime())
|
|
1478
|
+
return;
|
|
1479
|
+
this.commit(clamped, 'preset');
|
|
1480
|
+
this.closeOverlay('select');
|
|
1481
|
+
}
|
|
1482
|
+
onStartTimeChange(date) {
|
|
1483
|
+
const pending = this.pendingRange();
|
|
1484
|
+
if (pending === null || pending.start === null || date === null)
|
|
1485
|
+
return;
|
|
1486
|
+
const updatedStart = this.adapter.withTime(pending.start, this.adapter.getHours(date), this.adapter.getMinutes(date), this.adapter.getSeconds(date));
|
|
1487
|
+
const next = new TwDateRange(updatedStart, pending.end);
|
|
1488
|
+
this.pendingRange.set(next);
|
|
1489
|
+
if (!this.showActions() && next.complete) {
|
|
1490
|
+
if (!this.isRangeValid(next)) {
|
|
1491
|
+
this.rangeError.set(true);
|
|
1492
|
+
return;
|
|
1493
|
+
}
|
|
1494
|
+
this.commit(next, 'time');
|
|
1495
|
+
}
|
|
1496
|
+
}
|
|
1497
|
+
onEndTimeChange(date) {
|
|
1498
|
+
const pending = this.pendingRange();
|
|
1499
|
+
if (pending === null || pending.end === null || date === null)
|
|
1500
|
+
return;
|
|
1501
|
+
const updatedEnd = this.adapter.withTime(pending.end, this.adapter.getHours(date), this.adapter.getMinutes(date), this.adapter.getSeconds(date));
|
|
1502
|
+
const next = new TwDateRange(pending.start, updatedEnd);
|
|
1503
|
+
this.pendingRange.set(next);
|
|
1504
|
+
if (!this.showActions() && next.complete) {
|
|
1505
|
+
if (!this.isRangeValid(next)) {
|
|
1506
|
+
this.rangeError.set(true);
|
|
1507
|
+
return;
|
|
1508
|
+
}
|
|
1509
|
+
this.commit(next, 'time');
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
1512
|
+
onViewChanged(view) {
|
|
1513
|
+
this.currentView.set(view);
|
|
1514
|
+
}
|
|
1515
|
+
onTodayClicked() {
|
|
1516
|
+
const today = this.adapter.today();
|
|
1517
|
+
const range = new TwDateRange(today, today);
|
|
1518
|
+
if (!this.isRangeValid(range))
|
|
1519
|
+
return;
|
|
1520
|
+
this.pendingRange.set(range);
|
|
1521
|
+
// When the action bar is off, Today behaves like a direct commit shortcut —
|
|
1522
|
+
// mirrors the date-picker's parallel behaviour.
|
|
1523
|
+
if (!this.showActions()) {
|
|
1524
|
+
this.commit(range, 'preset');
|
|
1525
|
+
this.closeOverlay('select');
|
|
1526
|
+
}
|
|
1527
|
+
}
|
|
1528
|
+
onClearAction() {
|
|
1529
|
+
// Stage-only: clearing in the action bar just drops the pending range.
|
|
1530
|
+
// The user must press Apply to commit, consistent with Today.
|
|
1531
|
+
this.pendingRange.set(null);
|
|
1532
|
+
}
|
|
1533
|
+
onCancelAction() {
|
|
1534
|
+
this.closeOverlay('cancel', /* restore */ true);
|
|
1535
|
+
}
|
|
1536
|
+
onApplyAction() {
|
|
1537
|
+
const pending = this.pendingRange();
|
|
1538
|
+
const normalised = this.normalizeRange(pending);
|
|
1539
|
+
if (normalised !== null && !this.isRangeValid(normalised)) {
|
|
1540
|
+
this.rangeError.set(true);
|
|
1541
|
+
this.closeOverlay('cancel', /* restore */ true);
|
|
1542
|
+
return;
|
|
1543
|
+
}
|
|
1544
|
+
this.commit(normalised, 'apply');
|
|
1545
|
+
this.closeOverlay('apply');
|
|
1546
|
+
}
|
|
1547
|
+
// ── Helpers ──
|
|
1548
|
+
resolveDialogAriaLabel() {
|
|
1549
|
+
return this.ariaLabel() || 'Choose a date range';
|
|
1550
|
+
}
|
|
1551
|
+
resolvePanelClass() {
|
|
1552
|
+
const raw = this.panelClass();
|
|
1553
|
+
return Array.isArray(raw) ? raw.join(' ') : raw;
|
|
1554
|
+
}
|
|
1555
|
+
// ── ControlValueAccessor ──
|
|
1556
|
+
writeValue(value) {
|
|
1557
|
+
const previous = this.internalValue();
|
|
1558
|
+
if (value === null || value === undefined || value === '') {
|
|
1559
|
+
this.internalValue.set(null);
|
|
1560
|
+
this.value.set(null);
|
|
1561
|
+
this.parseError.set(false);
|
|
1562
|
+
this.rangeError.set(false);
|
|
1563
|
+
this.rangeChange.emit({
|
|
1564
|
+
value: null,
|
|
1565
|
+
previousValue: previous,
|
|
1566
|
+
source: 'programmatic',
|
|
1567
|
+
});
|
|
1568
|
+
return;
|
|
1569
|
+
}
|
|
1570
|
+
const coerced = this.coerceRange(value);
|
|
1571
|
+
const normalised = this.normalizeRange(coerced);
|
|
1572
|
+
this.internalValue.set(normalised);
|
|
1573
|
+
this.value.set(normalised);
|
|
1574
|
+
this.parseError.set(false);
|
|
1575
|
+
this.rangeError.set(normalised !== null && !this.isRangeValid(normalised));
|
|
1576
|
+
this.rangeChange.emit({
|
|
1577
|
+
value: normalised,
|
|
1578
|
+
previousValue: previous,
|
|
1579
|
+
source: 'programmatic',
|
|
1580
|
+
});
|
|
1581
|
+
}
|
|
1582
|
+
coerceRange(value) {
|
|
1583
|
+
if (value === null || value === undefined)
|
|
1584
|
+
return null;
|
|
1585
|
+
if (value instanceof TwDateRange) {
|
|
1586
|
+
const start = this.coerceEndpoint(value.start);
|
|
1587
|
+
const end = this.coerceEndpoint(value.end);
|
|
1588
|
+
return new TwDateRange(start, end);
|
|
1589
|
+
}
|
|
1590
|
+
const obj = value;
|
|
1591
|
+
return new TwDateRange(this.coerceEndpoint(obj.start ?? null), this.coerceEndpoint(obj.end ?? null));
|
|
1592
|
+
}
|
|
1593
|
+
coerceEndpoint(raw) {
|
|
1594
|
+
if (raw === null)
|
|
1595
|
+
return null;
|
|
1596
|
+
const d = this.adapter.deserialize(raw);
|
|
1597
|
+
if (d === null)
|
|
1598
|
+
return null;
|
|
1599
|
+
return this.adapter.isValid(d) ? d : null;
|
|
1600
|
+
}
|
|
1601
|
+
registerOnChange(fn) {
|
|
1602
|
+
this.onChange = fn;
|
|
1603
|
+
}
|
|
1604
|
+
registerOnTouched(fn) {
|
|
1605
|
+
this.onTouched = fn;
|
|
1606
|
+
}
|
|
1607
|
+
setDisabledState(isDisabled) {
|
|
1608
|
+
this.cvaDisabled.set(isDisabled);
|
|
1609
|
+
}
|
|
1610
|
+
// ── Validator ──
|
|
1611
|
+
validatorOnChange = () => { };
|
|
1612
|
+
/** @internal */
|
|
1613
|
+
validate(control) {
|
|
1614
|
+
// Delegate to the calendar validator so consumers see the same
|
|
1615
|
+
// CalendarErrorCode set on a date-range-picker as on a bound `tw-calendar`.
|
|
1616
|
+
const validator = calendarValidator({
|
|
1617
|
+
mode: 'range',
|
|
1618
|
+
lastInvalidFormValue: null,
|
|
1619
|
+
constraints: {
|
|
1620
|
+
minDate: this.minDate(),
|
|
1621
|
+
maxDate: this.maxDate(),
|
|
1622
|
+
dateFilter: this.dateFilter(),
|
|
1623
|
+
},
|
|
1624
|
+
adapter: this.adapter,
|
|
1625
|
+
minRangeLength: this.minRangeLength(),
|
|
1626
|
+
maxRangeLength: this.maxRangeLength(),
|
|
1627
|
+
});
|
|
1628
|
+
const errors = validator(control);
|
|
1629
|
+
return errors;
|
|
1630
|
+
}
|
|
1631
|
+
/** @internal */
|
|
1632
|
+
registerOnValidatorChange(fn) {
|
|
1633
|
+
this.validatorOnChange = fn;
|
|
1634
|
+
}
|
|
1635
|
+
// ── FormFieldControl methods ──
|
|
1636
|
+
/** @internal */
|
|
1637
|
+
setDescribedByIds(ids) {
|
|
1638
|
+
this.describedByIdsSignal.set([...ids]);
|
|
1639
|
+
}
|
|
1640
|
+
/** @internal */
|
|
1641
|
+
onContainerClick(event) {
|
|
1642
|
+
if (this.isDisabled())
|
|
1643
|
+
return;
|
|
1644
|
+
const target = event.target;
|
|
1645
|
+
const triggerEl = this.triggerRef()?.nativeElement;
|
|
1646
|
+
if (triggerEl && target && triggerEl.contains(target))
|
|
1647
|
+
return;
|
|
1648
|
+
if (triggerEl && document.activeElement !== triggerEl) {
|
|
1649
|
+
triggerEl.focus();
|
|
1650
|
+
}
|
|
1651
|
+
}
|
|
1652
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: DateRangePickerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1653
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", type: DateRangePickerComponent, isStandalone: true, selector: "tw-date-range-picker", inputs: { idInput: { classPropertyName: "idInput", publicName: "id", 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 }, startView: { classPropertyName: "startView", publicName: "startView", isSignal: true, isRequired: false, transformFunction: null }, startAt: { classPropertyName: "startAt", publicName: "startAt", isSignal: true, isRequired: false, transformFunction: null }, format: { classPropertyName: "format", publicName: "format", isSignal: true, isRequired: false, transformFunction: null }, rangeSeparator: { classPropertyName: "rangeSeparator", publicName: "rangeSeparator", isSignal: true, isRequired: false, transformFunction: null }, emptyStartLabel: { classPropertyName: "emptyStartLabel", publicName: "emptyStartLabel", isSignal: true, isRequired: false, transformFunction: null }, emptyEndLabel: { classPropertyName: "emptyEndLabel", publicName: "emptyEndLabel", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, disabledInput: { classPropertyName: "disabledInput", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, requiredInput: { classPropertyName: "requiredInput", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, color: { classPropertyName: "color", publicName: "color", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, numberOfMonths: { classPropertyName: "numberOfMonths", publicName: "numberOfMonths", isSignal: true, isRequired: false, transformFunction: null }, presets: { classPropertyName: "presets", publicName: "presets", isSignal: true, isRequired: false, transformFunction: null }, showClear: { classPropertyName: "showClear", publicName: "showClear", isSignal: true, isRequired: false, transformFunction: null }, minRangeLength: { classPropertyName: "minRangeLength", publicName: "minRangeLength", isSignal: true, isRequired: false, transformFunction: null }, maxRangeLength: { classPropertyName: "maxRangeLength", publicName: "maxRangeLength", isSignal: true, isRequired: false, transformFunction: null }, rangeBehavior: { classPropertyName: "rangeBehavior", publicName: "rangeBehavior", isSignal: true, isRequired: false, transformFunction: null }, rangeClickBehavior: { classPropertyName: "rangeClickBehavior", publicName: "rangeClickBehavior", isSignal: true, isRequired: false, transformFunction: null }, firstDayOfWeek: { classPropertyName: "firstDayOfWeek", publicName: "firstDayOfWeek", isSignal: true, isRequired: false, transformFunction: null }, locale: { classPropertyName: "locale", publicName: "locale", isSignal: true, isRequired: false, transformFunction: null }, dateClass: { classPropertyName: "dateClass", publicName: "dateClass", isSignal: true, isRequired: false, transformFunction: null }, cellTemplate: { classPropertyName: "cellTemplate", publicName: "cellTemplate", isSignal: true, isRequired: false, transformFunction: null }, showActions: { classPropertyName: "showActions", publicName: "showActions", isSignal: true, isRequired: false, transformFunction: null }, showTime: { classPropertyName: "showTime", publicName: "showTime", 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 }, hourStep: { classPropertyName: "hourStep", publicName: "hourStep", isSignal: true, isRequired: false, transformFunction: null }, minuteStep: { classPropertyName: "minuteStep", publicName: "minuteStep", isSignal: true, isRequired: false, transformFunction: null }, secondStep: { classPropertyName: "secondStep", publicName: "secondStep", isSignal: true, isRequired: false, transformFunction: null }, todayLabel: { classPropertyName: "todayLabel", publicName: "todayLabel", isSignal: true, isRequired: false, transformFunction: null }, clearLabel: { classPropertyName: "clearLabel", publicName: "clearLabel", isSignal: true, isRequired: false, transformFunction: null }, cancelLabel: { classPropertyName: "cancelLabel", publicName: "cancelLabel", isSignal: true, isRequired: false, transformFunction: null }, applyLabel: { classPropertyName: "applyLabel", publicName: "applyLabel", isSignal: true, isRequired: false, transformFunction: null }, panelClass: { classPropertyName: "panelClass", publicName: "panelClass", isSignal: true, isRequired: false, transformFunction: null }, scrollStrategy: { classPropertyName: "scrollStrategy", publicName: "scrollStrategy", isSignal: true, isRequired: false, transformFunction: null }, offset: { classPropertyName: "offset", publicName: "offset", isSignal: true, isRequired: false, transformFunction: null }, clearAriaLabel: { classPropertyName: "clearAriaLabel", publicName: "clearAriaLabel", isSignal: true, isRequired: false, transformFunction: null }, errorStateMatcher: { classPropertyName: "errorStateMatcher", publicName: "errorStateMatcher", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "aria-label", isSignal: true, isRequired: false, transformFunction: null }, ariaLabelledby: { classPropertyName: "ariaLabelledby", publicName: "aria-labelledby", isSignal: true, isRequired: false, transformFunction: null }, userAriaDescribedByInput: { classPropertyName: "userAriaDescribedByInput", publicName: "aria-describedby", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", open: "openChange", opened: "opened", closed: "closed", rangeChange: "rangeChange", presetSelected: "presetSelected" }, host: { properties: { "class": "rootClasses()" } }, providers: [
|
|
1654
|
+
{
|
|
1655
|
+
provide: TW_FORM_FIELD_CONTROL,
|
|
1656
|
+
useExisting: forwardRef(() => DateRangePickerComponent),
|
|
1657
|
+
},
|
|
1658
|
+
{
|
|
1659
|
+
provide: NG_VALIDATORS,
|
|
1660
|
+
useExisting: forwardRef(() => DateRangePickerComponent),
|
|
1661
|
+
multi: true,
|
|
1662
|
+
},
|
|
1663
|
+
PickerOverlayCoordinator,
|
|
1664
|
+
], viewQueries: [{ propertyName: "triggerRef", first: true, predicate: ["trigger"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: `
|
|
1665
|
+
<button
|
|
1666
|
+
#trigger
|
|
1667
|
+
type="button"
|
|
1668
|
+
[id]="hostId"
|
|
1669
|
+
[class]="triggerClasses()"
|
|
1670
|
+
[attr.role]="'combobox'"
|
|
1671
|
+
[attr.aria-haspopup]="'dialog'"
|
|
1672
|
+
[attr.aria-expanded]="open() ? 'true' : 'false'"
|
|
1673
|
+
[attr.aria-controls]="dialogId"
|
|
1674
|
+
[attr.aria-label]="triggerAccessibleName() || null"
|
|
1675
|
+
[attr.aria-labelledby]="ariaLabelledby() || null"
|
|
1676
|
+
[attr.aria-describedby]="describedBy() || null"
|
|
1677
|
+
[attr.aria-required]="requiredInput() || null"
|
|
1678
|
+
[attr.aria-invalid]="errorState() || null"
|
|
1679
|
+
[attr.aria-disabled]="isDisabled() || null"
|
|
1680
|
+
[attr.data-variant]="resolvedVariant()"
|
|
1681
|
+
[disabled]="isDisabled()"
|
|
1682
|
+
(click)="onTriggerClick()"
|
|
1683
|
+
(keydown)="onTriggerKeydown($event)"
|
|
1684
|
+
>
|
|
1685
|
+
<span class="flex-1 inline-flex items-center gap-2 min-w-0">
|
|
1686
|
+
@if (internalValue() !== null) {
|
|
1687
|
+
<span [class]="startTextClasses()">{{ startDisplay() }}</span>
|
|
1688
|
+
<span [class]="separatorClasses()">{{ rangeSeparator() }}</span>
|
|
1689
|
+
<span [class]="endTextClasses()">{{ endDisplay() }}</span>
|
|
1690
|
+
} @else {
|
|
1691
|
+
<span [class]="placeholderClasses()">{{ placeholderDisplay() }}</span>
|
|
1692
|
+
}
|
|
1693
|
+
</span>
|
|
1694
|
+
|
|
1695
|
+
<span [class]="triggerIconWrapperClasses()">
|
|
1696
|
+
<ng-content select="[slot=trigger-icon]">
|
|
1697
|
+
<svg
|
|
1698
|
+
[class]="triggerIconClasses()"
|
|
1699
|
+
viewBox="0 0 24 24"
|
|
1700
|
+
fill="none"
|
|
1701
|
+
stroke="currentColor"
|
|
1702
|
+
stroke-width="2"
|
|
1703
|
+
stroke-linecap="round"
|
|
1704
|
+
stroke-linejoin="round"
|
|
1705
|
+
aria-hidden="true"
|
|
1706
|
+
>
|
|
1707
|
+
<rect x="3" y="4" width="18" height="18" rx="2" ry="2" />
|
|
1708
|
+
<line x1="16" y1="2" x2="16" y2="6" />
|
|
1709
|
+
<line x1="8" y1="2" x2="8" y2="6" />
|
|
1710
|
+
<line x1="3" y1="10" x2="21" y2="10" />
|
|
1711
|
+
</svg>
|
|
1712
|
+
</ng-content>
|
|
1713
|
+
</span>
|
|
1714
|
+
</button>
|
|
1715
|
+
|
|
1716
|
+
@if (showClear() && !isEmpty() && !isDisabled()) {
|
|
1717
|
+
<button
|
|
1718
|
+
type="button"
|
|
1719
|
+
tabindex="-1"
|
|
1720
|
+
[class]="clearButtonClasses()"
|
|
1721
|
+
[attr.aria-label]="clearAriaLabel()"
|
|
1722
|
+
(click)="onClearClick($event)"
|
|
1723
|
+
>
|
|
1724
|
+
<svg viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" class="size-3">
|
|
1725
|
+
<path
|
|
1726
|
+
fill-rule="evenodd"
|
|
1727
|
+
d="M10 8.586 4.707 3.293a1 1 0 0 0-1.414 1.414L8.586 10l-5.293 5.293a1 1 0 1 0 1.414 1.414L10 11.414l5.293 5.293a1 1 0 0 0 1.414-1.414L11.414 10l5.293-5.293a1 1 0 0 0-1.414-1.414L10 8.586Z"
|
|
1728
|
+
clip-rule="evenodd"
|
|
1729
|
+
/>
|
|
1730
|
+
</svg>
|
|
1731
|
+
</button>
|
|
1732
|
+
}
|
|
1733
|
+
`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1734
|
+
}
|
|
1735
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: DateRangePickerComponent, decorators: [{
|
|
1736
|
+
type: Component,
|
|
1737
|
+
args: [{
|
|
1738
|
+
selector: 'tw-date-range-picker',
|
|
1739
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
1740
|
+
providers: [
|
|
1741
|
+
{
|
|
1742
|
+
provide: TW_FORM_FIELD_CONTROL,
|
|
1743
|
+
useExisting: forwardRef(() => DateRangePickerComponent),
|
|
1744
|
+
},
|
|
1745
|
+
{
|
|
1746
|
+
provide: NG_VALIDATORS,
|
|
1747
|
+
useExisting: forwardRef(() => DateRangePickerComponent),
|
|
1748
|
+
multi: true,
|
|
1749
|
+
},
|
|
1750
|
+
PickerOverlayCoordinator,
|
|
1751
|
+
],
|
|
1752
|
+
template: `
|
|
1753
|
+
<button
|
|
1754
|
+
#trigger
|
|
1755
|
+
type="button"
|
|
1756
|
+
[id]="hostId"
|
|
1757
|
+
[class]="triggerClasses()"
|
|
1758
|
+
[attr.role]="'combobox'"
|
|
1759
|
+
[attr.aria-haspopup]="'dialog'"
|
|
1760
|
+
[attr.aria-expanded]="open() ? 'true' : 'false'"
|
|
1761
|
+
[attr.aria-controls]="dialogId"
|
|
1762
|
+
[attr.aria-label]="triggerAccessibleName() || null"
|
|
1763
|
+
[attr.aria-labelledby]="ariaLabelledby() || null"
|
|
1764
|
+
[attr.aria-describedby]="describedBy() || null"
|
|
1765
|
+
[attr.aria-required]="requiredInput() || null"
|
|
1766
|
+
[attr.aria-invalid]="errorState() || null"
|
|
1767
|
+
[attr.aria-disabled]="isDisabled() || null"
|
|
1768
|
+
[attr.data-variant]="resolvedVariant()"
|
|
1769
|
+
[disabled]="isDisabled()"
|
|
1770
|
+
(click)="onTriggerClick()"
|
|
1771
|
+
(keydown)="onTriggerKeydown($event)"
|
|
1772
|
+
>
|
|
1773
|
+
<span class="flex-1 inline-flex items-center gap-2 min-w-0">
|
|
1774
|
+
@if (internalValue() !== null) {
|
|
1775
|
+
<span [class]="startTextClasses()">{{ startDisplay() }}</span>
|
|
1776
|
+
<span [class]="separatorClasses()">{{ rangeSeparator() }}</span>
|
|
1777
|
+
<span [class]="endTextClasses()">{{ endDisplay() }}</span>
|
|
1778
|
+
} @else {
|
|
1779
|
+
<span [class]="placeholderClasses()">{{ placeholderDisplay() }}</span>
|
|
1780
|
+
}
|
|
1781
|
+
</span>
|
|
1782
|
+
|
|
1783
|
+
<span [class]="triggerIconWrapperClasses()">
|
|
1784
|
+
<ng-content select="[slot=trigger-icon]">
|
|
1785
|
+
<svg
|
|
1786
|
+
[class]="triggerIconClasses()"
|
|
1787
|
+
viewBox="0 0 24 24"
|
|
1788
|
+
fill="none"
|
|
1789
|
+
stroke="currentColor"
|
|
1790
|
+
stroke-width="2"
|
|
1791
|
+
stroke-linecap="round"
|
|
1792
|
+
stroke-linejoin="round"
|
|
1793
|
+
aria-hidden="true"
|
|
1794
|
+
>
|
|
1795
|
+
<rect x="3" y="4" width="18" height="18" rx="2" ry="2" />
|
|
1796
|
+
<line x1="16" y1="2" x2="16" y2="6" />
|
|
1797
|
+
<line x1="8" y1="2" x2="8" y2="6" />
|
|
1798
|
+
<line x1="3" y1="10" x2="21" y2="10" />
|
|
1799
|
+
</svg>
|
|
1800
|
+
</ng-content>
|
|
1801
|
+
</span>
|
|
1802
|
+
</button>
|
|
1803
|
+
|
|
1804
|
+
@if (showClear() && !isEmpty() && !isDisabled()) {
|
|
1805
|
+
<button
|
|
1806
|
+
type="button"
|
|
1807
|
+
tabindex="-1"
|
|
1808
|
+
[class]="clearButtonClasses()"
|
|
1809
|
+
[attr.aria-label]="clearAriaLabel()"
|
|
1810
|
+
(click)="onClearClick($event)"
|
|
1811
|
+
>
|
|
1812
|
+
<svg viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" class="size-3">
|
|
1813
|
+
<path
|
|
1814
|
+
fill-rule="evenodd"
|
|
1815
|
+
d="M10 8.586 4.707 3.293a1 1 0 0 0-1.414 1.414L8.586 10l-5.293 5.293a1 1 0 1 0 1.414 1.414L10 11.414l5.293 5.293a1 1 0 0 0 1.414-1.414L11.414 10l5.293-5.293a1 1 0 0 0-1.414-1.414L10 8.586Z"
|
|
1816
|
+
clip-rule="evenodd"
|
|
1817
|
+
/>
|
|
1818
|
+
</svg>
|
|
1819
|
+
</button>
|
|
1820
|
+
}
|
|
1821
|
+
`,
|
|
1822
|
+
host: {
|
|
1823
|
+
'[class]': 'rootClasses()',
|
|
1824
|
+
},
|
|
1825
|
+
}]
|
|
1826
|
+
}], ctorParameters: () => [], propDecorators: { idInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", 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 }] }], startView: [{ type: i0.Input, args: [{ isSignal: true, alias: "startView", required: false }] }], startAt: [{ type: i0.Input, args: [{ isSignal: true, alias: "startAt", required: false }] }], format: [{ type: i0.Input, args: [{ isSignal: true, alias: "format", required: false }] }], rangeSeparator: [{ type: i0.Input, args: [{ isSignal: true, alias: "rangeSeparator", required: false }] }], emptyStartLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyStartLabel", required: false }] }], emptyEndLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyEndLabel", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], disabledInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], requiredInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], color: [{ type: i0.Input, args: [{ isSignal: true, alias: "color", required: false }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], numberOfMonths: [{ type: i0.Input, args: [{ isSignal: true, alias: "numberOfMonths", required: false }] }], presets: [{ type: i0.Input, args: [{ isSignal: true, alias: "presets", required: false }] }], showClear: [{ type: i0.Input, args: [{ isSignal: true, alias: "showClear", required: false }] }], minRangeLength: [{ type: i0.Input, args: [{ isSignal: true, alias: "minRangeLength", required: false }] }], maxRangeLength: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxRangeLength", required: false }] }], rangeBehavior: [{ type: i0.Input, args: [{ isSignal: true, alias: "rangeBehavior", required: false }] }], rangeClickBehavior: [{ type: i0.Input, args: [{ isSignal: true, alias: "rangeClickBehavior", required: false }] }], firstDayOfWeek: [{ type: i0.Input, args: [{ isSignal: true, alias: "firstDayOfWeek", required: false }] }], locale: [{ type: i0.Input, args: [{ isSignal: true, alias: "locale", required: false }] }], dateClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "dateClass", required: false }] }], cellTemplate: [{ type: i0.Input, args: [{ isSignal: true, alias: "cellTemplate", required: false }] }], showActions: [{ type: i0.Input, args: [{ isSignal: true, alias: "showActions", required: false }] }], showTime: [{ type: i0.Input, args: [{ isSignal: true, alias: "showTime", required: false }] }], timeFormat: [{ type: i0.Input, args: [{ isSignal: true, alias: "timeFormat", required: false }] }], showSeconds: [{ type: i0.Input, args: [{ isSignal: true, alias: "showSeconds", required: false }] }], hourStep: [{ type: i0.Input, args: [{ isSignal: true, alias: "hourStep", required: false }] }], minuteStep: [{ type: i0.Input, args: [{ isSignal: true, alias: "minuteStep", required: false }] }], secondStep: [{ type: i0.Input, args: [{ isSignal: true, alias: "secondStep", required: false }] }], todayLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "todayLabel", required: false }] }], clearLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearLabel", required: false }] }], cancelLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "cancelLabel", required: false }] }], applyLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "applyLabel", required: false }] }], panelClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "panelClass", required: false }] }], scrollStrategy: [{ type: i0.Input, args: [{ isSignal: true, alias: "scrollStrategy", required: false }] }], offset: [{ type: i0.Input, args: [{ isSignal: true, alias: "offset", required: false }] }], clearAriaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearAriaLabel", required: false }] }], errorStateMatcher: [{ type: i0.Input, args: [{ isSignal: true, alias: "errorStateMatcher", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "aria-label", required: false }] }], ariaLabelledby: [{ type: i0.Input, args: [{ isSignal: true, alias: "aria-labelledby", required: false }] }], userAriaDescribedByInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "aria-describedby", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }, { type: i0.Output, args: ["openChange"] }], opened: [{ type: i0.Output, args: ["opened"] }], closed: [{ type: i0.Output, args: ["closed"] }], rangeChange: [{ type: i0.Output, args: ["rangeChange"] }], presetSelected: [{ type: i0.Output, args: ["presetSelected"] }], triggerRef: [{ type: i0.ViewChild, args: ['trigger', { isSignal: true }] }] } });
|
|
1827
|
+
|
|
1828
|
+
/**
|
|
1829
|
+
* Generated bundle index. Do not edit.
|
|
1830
|
+
*/
|
|
1831
|
+
|
|
1832
|
+
export { DateRangePickerComponent };
|
|
1833
|
+
//# sourceMappingURL=cdevhub-ngx-tw-date-range-picker.mjs.map
|