@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,1558 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { inject, ElementRef, viewChild, signal, computed, ChangeDetectionStrategy, Component, input, booleanAttribute, model, output, ViewContainerRef, Injector, DestroyRef, linkedSignal, effect, untracked, afterNextRender, isDevMode, forwardRef } from '@angular/core';
|
|
3
|
+
import { NgControl, NgForm, FormGroupDirective, 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, timeOfDaySeconds, resolveSelectScrollStrategy, buildSelectLikePositions } from '@cdevhub/ngx-tw/core';
|
|
10
|
+
import { FormFieldControl, FormFieldComponent, TW_FORM_FIELD_CONTROL } from '@cdevhub/ngx-tw/form-field';
|
|
11
|
+
import { CalendarComponent, provideSingleSelectionStrategy, DATE_ADAPTER } 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 overlay panel for `tw-date-picker`. Hosts the calendar and, when
|
|
17
|
+
* the parent enables `showActions`, the action bar. Receives configuration
|
|
18
|
+
* and interaction callbacks via signal-backed fields set from the outer
|
|
19
|
+
* component. Not exported from the public API.
|
|
20
|
+
*/
|
|
21
|
+
class DatePickerOverlayComponent {
|
|
22
|
+
elementRef = inject((ElementRef));
|
|
23
|
+
calendarRef = viewChild('calendar', ...(ngDevMode ? [{ debugName: "calendarRef" }] : /* istanbul ignore next */ []));
|
|
24
|
+
// ── Config signals set by the parent component ──
|
|
25
|
+
/** @internal */
|
|
26
|
+
size = signal('md', ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
|
|
27
|
+
/** @internal */
|
|
28
|
+
color = signal('primary', ...(ngDevMode ? [{ debugName: "color" }] : /* istanbul ignore next */ []));
|
|
29
|
+
/** @internal */
|
|
30
|
+
minDate = signal(null, ...(ngDevMode ? [{ debugName: "minDate" }] : /* istanbul ignore next */ []));
|
|
31
|
+
/** @internal */
|
|
32
|
+
maxDate = signal(null, ...(ngDevMode ? [{ debugName: "maxDate" }] : /* istanbul ignore next */ []));
|
|
33
|
+
/** @internal */
|
|
34
|
+
dateFilter = signal(null, ...(ngDevMode ? [{ debugName: "dateFilter" }] : /* istanbul ignore next */ []));
|
|
35
|
+
/** @internal */
|
|
36
|
+
startView = signal('day', ...(ngDevMode ? [{ debugName: "startView" }] : /* istanbul ignore next */ []));
|
|
37
|
+
/** @internal */
|
|
38
|
+
startAt = signal(null, ...(ngDevMode ? [{ debugName: "startAt" }] : /* istanbul ignore next */ []));
|
|
39
|
+
/** @internal Current committed-or-pending value shown as selected in the calendar. */
|
|
40
|
+
pendingValue = signal(null, ...(ngDevMode ? [{ debugName: "pendingValue" }] : /* istanbul ignore next */ []));
|
|
41
|
+
/** @internal */
|
|
42
|
+
showActions = signal(false, ...(ngDevMode ? [{ debugName: "showActions" }] : /* istanbul ignore next */ []));
|
|
43
|
+
/** @internal */
|
|
44
|
+
todayLabel = signal('Today', ...(ngDevMode ? [{ debugName: "todayLabel" }] : /* istanbul ignore next */ []));
|
|
45
|
+
/** @internal */
|
|
46
|
+
clearLabel = signal('Clear', ...(ngDevMode ? [{ debugName: "clearLabel" }] : /* istanbul ignore next */ []));
|
|
47
|
+
/** @internal */
|
|
48
|
+
cancelLabel = signal('Cancel', ...(ngDevMode ? [{ debugName: "cancelLabel" }] : /* istanbul ignore next */ []));
|
|
49
|
+
/** @internal */
|
|
50
|
+
applyLabel = signal('Apply', ...(ngDevMode ? [{ debugName: "applyLabel" }] : /* istanbul ignore next */ []));
|
|
51
|
+
/** @internal */
|
|
52
|
+
dialogId = signal('', ...(ngDevMode ? [{ debugName: "dialogId" }] : /* istanbul ignore next */ []));
|
|
53
|
+
/** @internal */
|
|
54
|
+
dialogAriaLabel = signal('Choose a date', ...(ngDevMode ? [{ debugName: "dialogAriaLabel" }] : /* istanbul ignore next */ []));
|
|
55
|
+
/** @internal */
|
|
56
|
+
panelClassValue = signal('', ...(ngDevMode ? [{ debugName: "panelClassValue" }] : /* istanbul ignore next */ []));
|
|
57
|
+
/** @internal */
|
|
58
|
+
leaving = signal(false, ...(ngDevMode ? [{ debugName: "leaving" }] : /* istanbul ignore next */ []));
|
|
59
|
+
/** @internal Forwarded to the calendar — per-cell CSS classes. */
|
|
60
|
+
dateClass = signal(null, ...(ngDevMode ? [{ debugName: "dateClass" }] : /* istanbul ignore next */ []));
|
|
61
|
+
/** @internal Forwarded to the calendar — cell-content template. */
|
|
62
|
+
cellTemplate = signal(null, ...(ngDevMode ? [{ debugName: "cellTemplate" }] : /* istanbul ignore next */ []));
|
|
63
|
+
/** @internal Forwarded to the calendar — per-instance locale override. */
|
|
64
|
+
locale = signal(null, ...(ngDevMode ? [{ debugName: "locale" }] : /* istanbul ignore next */ []));
|
|
65
|
+
/** @internal Quick-select presets rendered above the calendar. */
|
|
66
|
+
presets = signal([], ...(ngDevMode ? [{ debugName: "presets" }] : /* istanbul ignore next */ []));
|
|
67
|
+
// ── Time-picker config ──
|
|
68
|
+
/** @internal */
|
|
69
|
+
withTime = signal(false, ...(ngDevMode ? [{ debugName: "withTime" }] : /* istanbul ignore next */ []));
|
|
70
|
+
/** @internal */
|
|
71
|
+
timeFormat = signal('24h', ...(ngDevMode ? [{ debugName: "timeFormat" }] : /* istanbul ignore next */ []));
|
|
72
|
+
/** @internal */
|
|
73
|
+
showSeconds = signal(false, ...(ngDevMode ? [{ debugName: "showSeconds" }] : /* istanbul ignore next */ []));
|
|
74
|
+
/** @internal */
|
|
75
|
+
hourStep = signal(1, ...(ngDevMode ? [{ debugName: "hourStep" }] : /* istanbul ignore next */ []));
|
|
76
|
+
/** @internal */
|
|
77
|
+
minuteStep = signal(1, ...(ngDevMode ? [{ debugName: "minuteStep" }] : /* istanbul ignore next */ []));
|
|
78
|
+
/** @internal */
|
|
79
|
+
secondStep = signal(1, ...(ngDevMode ? [{ debugName: "secondStep" }] : /* istanbul ignore next */ []));
|
|
80
|
+
/** @internal */
|
|
81
|
+
minTime = signal(null, ...(ngDevMode ? [{ debugName: "minTime" }] : /* istanbul ignore next */ []));
|
|
82
|
+
/** @internal */
|
|
83
|
+
maxTime = signal(null, ...(ngDevMode ? [{ debugName: "maxTime" }] : /* istanbul ignore next */ []));
|
|
84
|
+
// ── Callbacks set by the parent component ──
|
|
85
|
+
/** @internal */
|
|
86
|
+
onCalendarSelect = signal(() => { }, ...(ngDevMode ? [{ debugName: "onCalendarSelect" }] : /* istanbul ignore next */ []));
|
|
87
|
+
/** @internal Fires whenever the embedded time-picker commits a new time. */
|
|
88
|
+
onTimeInput = signal(() => { }, ...(ngDevMode ? [{ debugName: "onTimeInput" }] : /* istanbul ignore next */ []));
|
|
89
|
+
/** @internal */
|
|
90
|
+
onToday = signal(() => { }, ...(ngDevMode ? [{ debugName: "onToday" }] : /* istanbul ignore next */ []));
|
|
91
|
+
/** @internal */
|
|
92
|
+
onClear = signal(() => { }, ...(ngDevMode ? [{ debugName: "onClear" }] : /* istanbul ignore next */ []));
|
|
93
|
+
/** @internal */
|
|
94
|
+
onCancel = signal(() => { }, ...(ngDevMode ? [{ debugName: "onCancel" }] : /* istanbul ignore next */ []));
|
|
95
|
+
/** @internal */
|
|
96
|
+
onApply = signal(() => { }, ...(ngDevMode ? [{ debugName: "onApply" }] : /* istanbul ignore next */ []));
|
|
97
|
+
/** @internal */
|
|
98
|
+
onPresetSelect = signal(() => { }, ...(ngDevMode ? [{ debugName: "onPresetSelect" }] : /* istanbul ignore next */ []));
|
|
99
|
+
// ── Classes ──
|
|
100
|
+
/** @internal */
|
|
101
|
+
panelClasses = computed(() => {
|
|
102
|
+
const base = 'block bg-surface-overlay border border-border rounded-lg shadow-md overflow-hidden flex flex-col';
|
|
103
|
+
const custom = this.panelClassValue();
|
|
104
|
+
return custom ? `${base} ${custom}` : base;
|
|
105
|
+
}, ...(ngDevMode ? [{ debugName: "panelClasses" }] : /* istanbul ignore next */ []));
|
|
106
|
+
// ── Template callbacks ──
|
|
107
|
+
/** @internal */
|
|
108
|
+
onCalendarDateSelected(value) {
|
|
109
|
+
// Single-mode strategy emits a scalar `D`. Guard against the wider calendar
|
|
110
|
+
// output type for safety.
|
|
111
|
+
if (value === null || value === undefined)
|
|
112
|
+
return;
|
|
113
|
+
if (Array.isArray(value))
|
|
114
|
+
return;
|
|
115
|
+
if (typeof value === 'object' && value !== null && 'start' in value && 'end' in value)
|
|
116
|
+
return;
|
|
117
|
+
this.onCalendarSelect()(value);
|
|
118
|
+
}
|
|
119
|
+
/** @internal */
|
|
120
|
+
onTimeChange(event) {
|
|
121
|
+
if (event.source === 'programmatic')
|
|
122
|
+
return;
|
|
123
|
+
this.onTimeInput()(event.value);
|
|
124
|
+
}
|
|
125
|
+
/** @internal */
|
|
126
|
+
handleToday() {
|
|
127
|
+
this.onToday()();
|
|
128
|
+
}
|
|
129
|
+
/** @internal */
|
|
130
|
+
handleClear() {
|
|
131
|
+
this.onClear()();
|
|
132
|
+
}
|
|
133
|
+
/** @internal */
|
|
134
|
+
handleCancel() {
|
|
135
|
+
this.onCancel()();
|
|
136
|
+
}
|
|
137
|
+
/** @internal */
|
|
138
|
+
handleApply() {
|
|
139
|
+
this.onApply()();
|
|
140
|
+
}
|
|
141
|
+
/** @internal */
|
|
142
|
+
handlePreset(preset) {
|
|
143
|
+
this.onPresetSelect()(preset);
|
|
144
|
+
}
|
|
145
|
+
/** @internal */
|
|
146
|
+
presetKey(index, preset) {
|
|
147
|
+
return preset.id ?? preset.label ?? index;
|
|
148
|
+
}
|
|
149
|
+
/** @internal */
|
|
150
|
+
isActivePreset(preset) {
|
|
151
|
+
return !!preset.id && this.activePresetId() === preset.id;
|
|
152
|
+
}
|
|
153
|
+
/** @internal Tracks the most recently picked preset for visual state. Set by the parent. */
|
|
154
|
+
activePresetId = signal(undefined, ...(ngDevMode ? [{ debugName: "activePresetId" }] : /* istanbul ignore next */ []));
|
|
155
|
+
/** @internal Moves focus to the active calendar cell. Called by the parent after open. */
|
|
156
|
+
focusCalendar() {
|
|
157
|
+
this.calendarRef()?.focusActiveCell();
|
|
158
|
+
}
|
|
159
|
+
/** @internal Exposes the root overlay element for tests. */
|
|
160
|
+
getElement() {
|
|
161
|
+
return this.elementRef.nativeElement;
|
|
162
|
+
}
|
|
163
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: DatePickerOverlayComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
164
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", type: DatePickerOverlayComponent, isStandalone: true, selector: "tw-date-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: [provideSingleSelectionStrategy()], viewQueries: [{ propertyName: "calendarRef", first: true, predicate: ["calendar"], descendants: true, isSignal: true }], ngImport: i0, template: `
|
|
165
|
+
@if (presets().length) {
|
|
166
|
+
<div
|
|
167
|
+
class="flex flex-col gap-1 p-2 border-b border-border bg-surface-muted"
|
|
168
|
+
role="listbox"
|
|
169
|
+
aria-label="Preset dates"
|
|
170
|
+
>
|
|
171
|
+
@for (preset of presets(); track presetKey($index, preset)) {
|
|
172
|
+
<button
|
|
173
|
+
twButton
|
|
174
|
+
variant="ghost"
|
|
175
|
+
size="sm"
|
|
176
|
+
type="button"
|
|
177
|
+
role="option"
|
|
178
|
+
class="w-full justify-start"
|
|
179
|
+
[attr.aria-selected]="isActivePreset(preset) ? 'true' : 'false'"
|
|
180
|
+
(click)="handlePreset(preset)"
|
|
181
|
+
>
|
|
182
|
+
{{ preset.label }}
|
|
183
|
+
</button>
|
|
184
|
+
}
|
|
185
|
+
</div>
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
<tw-calendar
|
|
189
|
+
#calendar
|
|
190
|
+
class="!block !rounded-none !border-0 !bg-transparent !p-2"
|
|
191
|
+
[bordered]="false"
|
|
192
|
+
[value]="pendingValue()"
|
|
193
|
+
[startAt]="startAt()"
|
|
194
|
+
[startView]="startView()"
|
|
195
|
+
[minDate]="minDate()"
|
|
196
|
+
[maxDate]="maxDate()"
|
|
197
|
+
[dateFilter]="dateFilter()"
|
|
198
|
+
[dateClass]="dateClass()"
|
|
199
|
+
[cellTemplate]="cellTemplate()"
|
|
200
|
+
[locale]="locale()"
|
|
201
|
+
aria-label="Calendar"
|
|
202
|
+
(valueChange)="onCalendarDateSelected($event)"
|
|
203
|
+
/>
|
|
204
|
+
|
|
205
|
+
@if (withTime()) {
|
|
206
|
+
<div class="border-t border-border px-3 py-2 flex items-center justify-center">
|
|
207
|
+
<tw-time-picker
|
|
208
|
+
variant="naked"
|
|
209
|
+
[value]="pendingValue()"
|
|
210
|
+
[size]="size()"
|
|
211
|
+
[color]="color()"
|
|
212
|
+
[format]="timeFormat()"
|
|
213
|
+
[showSeconds]="showSeconds()"
|
|
214
|
+
[hourStep]="hourStep()"
|
|
215
|
+
[minuteStep]="minuteStep()"
|
|
216
|
+
[secondStep]="secondStep()"
|
|
217
|
+
[minTime]="minTime()"
|
|
218
|
+
[maxTime]="maxTime()"
|
|
219
|
+
[showClear]="false"
|
|
220
|
+
[referenceDate]="pendingValue() ?? startAt() ?? null"
|
|
221
|
+
aria-label="Time"
|
|
222
|
+
(timeChange)="onTimeChange($event)"
|
|
223
|
+
/>
|
|
224
|
+
</div>
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
@if (showActions()) {
|
|
228
|
+
<div class="border-t border-border flex items-center justify-between gap-2 px-3 py-2">
|
|
229
|
+
<div class="flex items-center gap-2">
|
|
230
|
+
<button twButton variant="ghost" size="sm" type="button" (click)="handleToday()">
|
|
231
|
+
{{ todayLabel() }}
|
|
232
|
+
</button>
|
|
233
|
+
@if (pendingValue() !== null) {
|
|
234
|
+
<button twButton variant="ghost" size="sm" type="button" (click)="handleClear()">
|
|
235
|
+
{{ clearLabel() }}
|
|
236
|
+
</button>
|
|
237
|
+
}
|
|
238
|
+
</div>
|
|
239
|
+
<div class="flex items-center gap-2">
|
|
240
|
+
<button twButton variant="ghost" size="sm" type="button" (click)="handleCancel()">
|
|
241
|
+
{{ cancelLabel() }}
|
|
242
|
+
</button>
|
|
243
|
+
<button twButton variant="solid" size="sm" type="button" (click)="handleApply()">
|
|
244
|
+
{{ applyLabel() }}
|
|
245
|
+
</button>
|
|
246
|
+
</div>
|
|
247
|
+
</div>
|
|
248
|
+
}
|
|
249
|
+
`, 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"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
250
|
+
}
|
|
251
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: DatePickerOverlayComponent, decorators: [{
|
|
252
|
+
type: Component,
|
|
253
|
+
args: [{
|
|
254
|
+
selector: 'tw-date-picker-overlay',
|
|
255
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
256
|
+
imports: [CalendarComponent, TimePickerComponent, ButtonDirective],
|
|
257
|
+
providers: [provideSingleSelectionStrategy()],
|
|
258
|
+
host: {
|
|
259
|
+
'[class]': 'panelClasses()',
|
|
260
|
+
'[id]': 'dialogId()',
|
|
261
|
+
'[attr.role]': '"dialog"',
|
|
262
|
+
'[attr.aria-modal]': '"true"',
|
|
263
|
+
'[attr.aria-label]': 'dialogAriaLabel()',
|
|
264
|
+
'[animate.enter]': '"scale-in"',
|
|
265
|
+
'[animate.leave]': 'leaving() ? "scale-out" : ""',
|
|
266
|
+
},
|
|
267
|
+
template: `
|
|
268
|
+
@if (presets().length) {
|
|
269
|
+
<div
|
|
270
|
+
class="flex flex-col gap-1 p-2 border-b border-border bg-surface-muted"
|
|
271
|
+
role="listbox"
|
|
272
|
+
aria-label="Preset dates"
|
|
273
|
+
>
|
|
274
|
+
@for (preset of presets(); track presetKey($index, preset)) {
|
|
275
|
+
<button
|
|
276
|
+
twButton
|
|
277
|
+
variant="ghost"
|
|
278
|
+
size="sm"
|
|
279
|
+
type="button"
|
|
280
|
+
role="option"
|
|
281
|
+
class="w-full justify-start"
|
|
282
|
+
[attr.aria-selected]="isActivePreset(preset) ? 'true' : 'false'"
|
|
283
|
+
(click)="handlePreset(preset)"
|
|
284
|
+
>
|
|
285
|
+
{{ preset.label }}
|
|
286
|
+
</button>
|
|
287
|
+
}
|
|
288
|
+
</div>
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
<tw-calendar
|
|
292
|
+
#calendar
|
|
293
|
+
class="!block !rounded-none !border-0 !bg-transparent !p-2"
|
|
294
|
+
[bordered]="false"
|
|
295
|
+
[value]="pendingValue()"
|
|
296
|
+
[startAt]="startAt()"
|
|
297
|
+
[startView]="startView()"
|
|
298
|
+
[minDate]="minDate()"
|
|
299
|
+
[maxDate]="maxDate()"
|
|
300
|
+
[dateFilter]="dateFilter()"
|
|
301
|
+
[dateClass]="dateClass()"
|
|
302
|
+
[cellTemplate]="cellTemplate()"
|
|
303
|
+
[locale]="locale()"
|
|
304
|
+
aria-label="Calendar"
|
|
305
|
+
(valueChange)="onCalendarDateSelected($event)"
|
|
306
|
+
/>
|
|
307
|
+
|
|
308
|
+
@if (withTime()) {
|
|
309
|
+
<div class="border-t border-border px-3 py-2 flex items-center justify-center">
|
|
310
|
+
<tw-time-picker
|
|
311
|
+
variant="naked"
|
|
312
|
+
[value]="pendingValue()"
|
|
313
|
+
[size]="size()"
|
|
314
|
+
[color]="color()"
|
|
315
|
+
[format]="timeFormat()"
|
|
316
|
+
[showSeconds]="showSeconds()"
|
|
317
|
+
[hourStep]="hourStep()"
|
|
318
|
+
[minuteStep]="minuteStep()"
|
|
319
|
+
[secondStep]="secondStep()"
|
|
320
|
+
[minTime]="minTime()"
|
|
321
|
+
[maxTime]="maxTime()"
|
|
322
|
+
[showClear]="false"
|
|
323
|
+
[referenceDate]="pendingValue() ?? startAt() ?? null"
|
|
324
|
+
aria-label="Time"
|
|
325
|
+
(timeChange)="onTimeChange($event)"
|
|
326
|
+
/>
|
|
327
|
+
</div>
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
@if (showActions()) {
|
|
331
|
+
<div class="border-t border-border flex items-center justify-between gap-2 px-3 py-2">
|
|
332
|
+
<div class="flex items-center gap-2">
|
|
333
|
+
<button twButton variant="ghost" size="sm" type="button" (click)="handleToday()">
|
|
334
|
+
{{ todayLabel() }}
|
|
335
|
+
</button>
|
|
336
|
+
@if (pendingValue() !== null) {
|
|
337
|
+
<button twButton variant="ghost" size="sm" type="button" (click)="handleClear()">
|
|
338
|
+
{{ clearLabel() }}
|
|
339
|
+
</button>
|
|
340
|
+
}
|
|
341
|
+
</div>
|
|
342
|
+
<div class="flex items-center gap-2">
|
|
343
|
+
<button twButton variant="ghost" size="sm" type="button" (click)="handleCancel()">
|
|
344
|
+
{{ cancelLabel() }}
|
|
345
|
+
</button>
|
|
346
|
+
<button twButton variant="solid" size="sm" type="button" (click)="handleApply()">
|
|
347
|
+
{{ applyLabel() }}
|
|
348
|
+
</button>
|
|
349
|
+
</div>
|
|
350
|
+
</div>
|
|
351
|
+
}
|
|
352
|
+
`,
|
|
353
|
+
}]
|
|
354
|
+
}], propDecorators: { calendarRef: [{ type: i0.ViewChild, args: ['calendar', { isSignal: true }] }] } });
|
|
355
|
+
|
|
356
|
+
// ── tv() config ───────────────────────────────────────────────────
|
|
357
|
+
const datePickerVariants = tv({
|
|
358
|
+
slots: {
|
|
359
|
+
root: 'relative inline-flex items-center w-full text-fg transition-[color,border-color,box-shadow] duration-normal motion-reduce:transition-none',
|
|
360
|
+
input: 'flex-1 min-w-0 bg-transparent text-fg placeholder:text-fg-subtle outline-none border-0 p-0 m-0 font-inherit',
|
|
361
|
+
triggerButton: '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 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary-500',
|
|
362
|
+
triggerIcon: 'shrink-0',
|
|
363
|
+
// 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.
|
|
364
|
+
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',
|
|
365
|
+
},
|
|
366
|
+
variants: {
|
|
367
|
+
size: {
|
|
368
|
+
xs: {
|
|
369
|
+
root: 'gap-1 text-xs',
|
|
370
|
+
input: 'text-xs',
|
|
371
|
+
triggerButton: 'size-6',
|
|
372
|
+
// size-3.5 (14px) is the codified half-step for xs-density chevrons — size-3 (12px) looks pinched next to text-xs and size-4 (16px) crowds the 24px trigger.
|
|
373
|
+
triggerIcon: 'size-3.5',
|
|
374
|
+
},
|
|
375
|
+
sm: {
|
|
376
|
+
root: 'gap-1.5 text-sm',
|
|
377
|
+
input: 'text-sm',
|
|
378
|
+
triggerButton: 'size-7',
|
|
379
|
+
triggerIcon: 'size-4',
|
|
380
|
+
},
|
|
381
|
+
md: {
|
|
382
|
+
root: 'gap-2 text-sm',
|
|
383
|
+
input: 'text-sm',
|
|
384
|
+
triggerButton: 'size-8',
|
|
385
|
+
triggerIcon: 'size-4',
|
|
386
|
+
},
|
|
387
|
+
lg: {
|
|
388
|
+
root: 'gap-2 text-base',
|
|
389
|
+
input: 'text-base',
|
|
390
|
+
triggerButton: 'size-9',
|
|
391
|
+
triggerIcon: 'size-5',
|
|
392
|
+
},
|
|
393
|
+
xl: {
|
|
394
|
+
root: 'gap-2 text-base',
|
|
395
|
+
input: 'text-base',
|
|
396
|
+
// size-10 (40px) extends the codified xs..lg square-interactive scale (24/28/32/36) by one step for xl. Stepping down to size-9 would make lg and xl visually identical since both use text-base; matches paginator's xl handling (size-11).
|
|
397
|
+
triggerButton: 'size-10',
|
|
398
|
+
triggerIcon: 'size-5',
|
|
399
|
+
},
|
|
400
|
+
},
|
|
401
|
+
variant: {
|
|
402
|
+
default: {
|
|
403
|
+
root: 'rounded-md border border-border bg-surface px-3 py-2 hover:border-border-strong',
|
|
404
|
+
},
|
|
405
|
+
naked: {
|
|
406
|
+
root: 'bg-transparent border-0 rounded-none p-0 focus-within:outline-none',
|
|
407
|
+
},
|
|
408
|
+
},
|
|
409
|
+
open: { true: { triggerButton: 'bg-surface-muted text-fg' }, false: {} },
|
|
410
|
+
disabled: {
|
|
411
|
+
true: { root: 'opacity-50 pointer-events-none cursor-not-allowed' },
|
|
412
|
+
false: {},
|
|
413
|
+
},
|
|
414
|
+
focused: { true: {}, false: {} },
|
|
415
|
+
errorState: { true: {}, false: {} },
|
|
416
|
+
color: {
|
|
417
|
+
primary: {},
|
|
418
|
+
secondary: {},
|
|
419
|
+
accent: {},
|
|
420
|
+
neutral: {},
|
|
421
|
+
info: {},
|
|
422
|
+
success: {},
|
|
423
|
+
warning: {},
|
|
424
|
+
error: {},
|
|
425
|
+
},
|
|
426
|
+
},
|
|
427
|
+
compoundVariants: [
|
|
428
|
+
// Default variant — focused border per color
|
|
429
|
+
{ variant: 'default', focused: true, color: 'primary', class: { root: 'border-primary-500' } },
|
|
430
|
+
{ variant: 'default', focused: true, color: 'secondary', class: { root: 'border-secondary-500' } },
|
|
431
|
+
{ variant: 'default', focused: true, color: 'accent', class: { root: 'border-accent-500' } },
|
|
432
|
+
{ variant: 'default', focused: true, color: 'neutral', class: { root: 'border-border-strong' } },
|
|
433
|
+
{ variant: 'default', focused: true, color: 'info', class: { root: 'border-info-500' } },
|
|
434
|
+
{ variant: 'default', focused: true, color: 'success', class: { root: 'border-success-500' } },
|
|
435
|
+
{ variant: 'default', focused: true, color: 'warning', class: { root: 'border-warning-500' } },
|
|
436
|
+
{ variant: 'default', focused: true, color: 'error', class: { root: 'border-error-500' } },
|
|
437
|
+
// Default variant + error state → red outline
|
|
438
|
+
{ variant: 'default', errorState: true, class: { root: 'border-error-500' } },
|
|
439
|
+
],
|
|
440
|
+
defaultVariants: {
|
|
441
|
+
size: 'md',
|
|
442
|
+
variant: 'default',
|
|
443
|
+
open: false,
|
|
444
|
+
disabled: false,
|
|
445
|
+
errorState: false,
|
|
446
|
+
focused: false,
|
|
447
|
+
color: 'primary',
|
|
448
|
+
},
|
|
449
|
+
}, { twMerge: true });
|
|
450
|
+
// ── ID generator ──────────────────────────────────────────────────
|
|
451
|
+
let nextDatePickerId = 0;
|
|
452
|
+
// ── Default display format ────────────────────────────────────────
|
|
453
|
+
const DEFAULT_DISPLAY_FORMAT = {
|
|
454
|
+
dateTimeFormat: { year: 'numeric', month: 'short', day: 'numeric' },
|
|
455
|
+
};
|
|
456
|
+
/**
|
|
457
|
+
* ARIA date-picker dialog. Combines a typable text input with a popover
|
|
458
|
+
* calendar. All three Angular forms strategies (reactive, template-driven,
|
|
459
|
+
* signal-forms) are supported via `ControlValueAccessor`, and the component
|
|
460
|
+
* integrates with `<tw-form-field>` by implementing `FormFieldControl`.
|
|
461
|
+
*
|
|
462
|
+
* All date operations go through the injected `DateAdapter<D>`; call
|
|
463
|
+
* `provideNativeDateAdapter()` in your app providers to bootstrap the default.
|
|
464
|
+
*/
|
|
465
|
+
class DatePickerComponent extends FormFieldControl {
|
|
466
|
+
// ── Inputs ──
|
|
467
|
+
/** Id on the date-picker's input element. Auto-generated when not provided. Used by the form-field's `<label for>` attribute. */
|
|
468
|
+
idInput = input(undefined, { ...(ngDevMode ? { debugName: "idInput" } : /* istanbul ignore next */ {}), alias: 'id' });
|
|
469
|
+
/** Minimum selectable date. Typed input earlier than this sets `errorState` and the calendar disables the cell. Defaults to `null`. */
|
|
470
|
+
minDate = input(null, ...(ngDevMode ? [{ debugName: "minDate" }] : /* istanbul ignore next */ []));
|
|
471
|
+
/** Maximum selectable date. Typed input later than this sets `errorState` and the calendar disables the cell. Defaults to `null`. */
|
|
472
|
+
maxDate = input(null, ...(ngDevMode ? [{ debugName: "maxDate" }] : /* istanbul ignore next */ []));
|
|
473
|
+
/** Per-date predicate — return `false` to disable. Applied in both the calendar and the text-parse path. */
|
|
474
|
+
dateFilter = input(null, ...(ngDevMode ? [{ debugName: "dateFilter" }] : /* istanbul ignore next */ []));
|
|
475
|
+
/** Which calendar view opens first — `'day'`, `'month'`, or `'year'`. Defaults to `'day'`. */
|
|
476
|
+
startView = input('day', ...(ngDevMode ? [{ debugName: "startView" }] : /* istanbul ignore next */ []));
|
|
477
|
+
/** Date to focus when the calendar opens with no selection. Falls back to today. */
|
|
478
|
+
startAt = input(null, ...(ngDevMode ? [{ debugName: "startAt" }] : /* istanbul ignore next */ []));
|
|
479
|
+
/** Display format passed to `DateAdapter.format()`. With the default adapter, accepts `{ dateTimeFormat: Intl.DateTimeFormatOptions }`. When `withTime` is true and this is left at the default, hour/minute (and optional seconds) are folded in automatically. */
|
|
480
|
+
format = input(DEFAULT_DISPLAY_FORMAT, ...(ngDevMode ? [{ debugName: "format" }] : /* istanbul ignore next */ []));
|
|
481
|
+
/** Optional format hint passed to `DateAdapter.parse()`. Ignored by the native adapter. */
|
|
482
|
+
parseFormat = input(undefined, ...(ngDevMode ? [{ debugName: "parseFormat" }] : /* istanbul ignore next */ []));
|
|
483
|
+
/** Placeholder text shown in the input when no value is entered. */
|
|
484
|
+
placeholder = input(undefined, ...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
|
|
485
|
+
/** When true, the input is disabled, the trigger cannot open the calendar, and `aria-disabled="true"` is set. Defaults to `false`. */
|
|
486
|
+
disabledInput = input(false, { ...(ngDevMode ? { debugName: "disabledInput" } : /* istanbul ignore next */ {}), alias: 'disabled',
|
|
487
|
+
transform: booleanAttribute });
|
|
488
|
+
/** When true, exposes `aria-required="true"`. Validators.required on a bound NgControl is also honoured. Defaults to `false`. */
|
|
489
|
+
requiredInput = input(false, { ...(ngDevMode ? { debugName: "requiredInput" } : /* istanbul ignore next */ {}), alias: 'required',
|
|
490
|
+
transform: booleanAttribute });
|
|
491
|
+
/** When true, blocks typing but still allows picking via the calendar trigger. Defaults to `false`. */
|
|
492
|
+
readonlyInput = input(false, { ...(ngDevMode ? { debugName: "readonlyInput" } : /* istanbul ignore next */ {}), alias: 'readonly',
|
|
493
|
+
transform: booleanAttribute });
|
|
494
|
+
/** Trigger padding, font size, and calendar cell density. Defaults to `'md'`. */
|
|
495
|
+
size = input('md', ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
|
|
496
|
+
/** Semantic color for focused border, calendar selection ring, and today marker. Defaults to `'primary'`. */
|
|
497
|
+
color = input('primary', ...(ngDevMode ? [{ debugName: "color" }] : /* istanbul ignore next */ []));
|
|
498
|
+
/** Visual style of the trigger. `'default'` draws its own border; `'naked'` strips chrome. Auto-resolves to `'naked'` when inside `<tw-form-field>`. */
|
|
499
|
+
variant = input(undefined, ...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []));
|
|
500
|
+
/** Whether to show a clear-button affordance inside the trigger when a value is set. Defaults to `true`. */
|
|
501
|
+
// 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.
|
|
502
|
+
showClear = input(true, ...(ngDevMode ? [{ debugName: "showClear" }] : /* istanbul ignore next */ []));
|
|
503
|
+
/** When true, renders a `Today / Clear / Cancel / Apply` action bar at the bottom of the overlay. Defaults to `false`. */
|
|
504
|
+
showActions = input(false, ...(ngDevMode ? [{ debugName: "showActions" }] : /* istanbul ignore next */ []));
|
|
505
|
+
/** Label for the `Today` action in the overlay's action bar. */
|
|
506
|
+
todayLabel = input('Today', ...(ngDevMode ? [{ debugName: "todayLabel" }] : /* istanbul ignore next */ []));
|
|
507
|
+
/** Label for the `Clear` action in the overlay's action bar. */
|
|
508
|
+
clearLabel = input('Clear', ...(ngDevMode ? [{ debugName: "clearLabel" }] : /* istanbul ignore next */ []));
|
|
509
|
+
/** Label for the `Cancel` action in the overlay's action bar. */
|
|
510
|
+
cancelLabel = input('Cancel', ...(ngDevMode ? [{ debugName: "cancelLabel" }] : /* istanbul ignore next */ []));
|
|
511
|
+
/** Label for the `Apply` action in the overlay's action bar. */
|
|
512
|
+
applyLabel = input('Apply', ...(ngDevMode ? [{ debugName: "applyLabel" }] : /* istanbul ignore next */ []));
|
|
513
|
+
/** When true, focusing the text input opens the overlay. Defaults to `false`. */
|
|
514
|
+
openOnFocus = input(false, ...(ngDevMode ? [{ debugName: "openOnFocus" }] : /* istanbul ignore next */ []));
|
|
515
|
+
/** Extra class(es) applied to the overlay panel element. */
|
|
516
|
+
panelClass = input('', ...(ngDevMode ? [{ debugName: "panelClass" }] : /* istanbul ignore next */ []));
|
|
517
|
+
/** CDK scroll strategy for the overlay. Defaults to `'reposition'`. */
|
|
518
|
+
scrollStrategy = input('reposition', ...(ngDevMode ? [{ debugName: "scrollStrategy" }] : /* istanbul ignore next */ []));
|
|
519
|
+
/** Pixel distance between trigger and overlay. Defaults to `4`. */
|
|
520
|
+
offset = input(4, ...(ngDevMode ? [{ debugName: "offset" }] : /* istanbul ignore next */ []));
|
|
521
|
+
/** Accessible name for the calendar trigger button. Defaults to `'Open calendar'`. */
|
|
522
|
+
triggerAriaLabel = input('Open calendar', ...(ngDevMode ? [{ debugName: "triggerAriaLabel" }] : /* istanbul ignore next */ []));
|
|
523
|
+
/**
|
|
524
|
+
* Bundles the time-of-day configuration. Passing a non-null object turns on the embedded `<tw-time-picker>` and forwards each field.
|
|
525
|
+
*
|
|
526
|
+
* Defaults to `null` (no time field). Pass an empty object `{}` to enable the time picker with all defaults.
|
|
527
|
+
*/
|
|
528
|
+
timeConfig = input(null, ...(ngDevMode ? [{ debugName: "timeConfig" }] : /* istanbul ignore next */ []));
|
|
529
|
+
/** Per-instance locale override. Forwarded to the embedded calendar and the underlying `DateAdapter` for parse/format. Falls back to Angular `LOCALE_ID` when `null`. */
|
|
530
|
+
locale = input(null, ...(ngDevMode ? [{ debugName: "locale" }] : /* istanbul ignore next */ []));
|
|
531
|
+
/** Function producing per-cell CSS classes, forwarded to the embedded calendar. Useful for visually marking special dates (holidays, billing cycles). */
|
|
532
|
+
dateClass = input(null, ...(ngDevMode ? [{ debugName: "dateClass" }] : /* istanbul ignore next */ []));
|
|
533
|
+
/** Optional cell-content template, forwarded to the embedded calendar. Use to customize cell visuals beyond `dateClass`. */
|
|
534
|
+
cellTemplate = input(null, ...(ngDevMode ? [{ debugName: "cellTemplate" }] : /* istanbul ignore next */ []));
|
|
535
|
+
/** Optional quick-select presets rendered as a vertical list before the calendar. Each preset provides a `label` and a `date` factory. An empty array hides the preset panel. */
|
|
536
|
+
presets = input([], ...(ngDevMode ? [{ debugName: "presets" }] : /* istanbul ignore next */ []));
|
|
537
|
+
/** Per-instance override of the `ErrorStateMatcher`. */
|
|
538
|
+
errorStateMatcher = input(undefined, ...(ngDevMode ? [{ debugName: "errorStateMatcher" }] : /* istanbul ignore next */ []));
|
|
539
|
+
/** Accessible name for the input. Required when no visible label is supplied. Alias: `aria-label`. */
|
|
540
|
+
ariaLabel = input(undefined, { ...(ngDevMode ? { debugName: "ariaLabel" } : /* istanbul ignore next */ {}), alias: 'aria-label' });
|
|
541
|
+
/** ID of an external element that labels the input. Alias: `aria-labelledby`. */
|
|
542
|
+
ariaLabelledby = input(undefined, { ...(ngDevMode ? { debugName: "ariaLabelledby" } : /* istanbul ignore next */ {}), alias: 'aria-labelledby' });
|
|
543
|
+
/** Consumer-supplied `aria-describedby` ids. The form-field preserves these when merging hint/error ids. Alias: `aria-describedby`. */
|
|
544
|
+
userAriaDescribedByInput = input(undefined, { ...(ngDevMode ? { debugName: "userAriaDescribedByInput" } : /* istanbul ignore next */ {}), alias: 'aria-describedby' });
|
|
545
|
+
// ── Models (two-way) ──
|
|
546
|
+
/** Two-way bound selected date. `null` when no selection. Setting programmatically updates the display; does NOT trigger `onChange`. */
|
|
547
|
+
value = model(null, ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
|
|
548
|
+
/** Two-way bound open state of the calendar overlay. */
|
|
549
|
+
open = model(false, ...(ngDevMode ? [{ debugName: "open" }] : /* istanbul ignore next */ []));
|
|
550
|
+
// ── Outputs ──
|
|
551
|
+
/** Fires after the overlay's enter animation completes. Payload is the trigger element. */
|
|
552
|
+
opened = output();
|
|
553
|
+
/** Fires after the overlay's leave animation completes. Payload is the reason it closed. */
|
|
554
|
+
closed = output();
|
|
555
|
+
/** Fires on every keystroke in the text input (before parsing). Does NOT mean the value has committed. */
|
|
556
|
+
dateInput = output();
|
|
557
|
+
/** Fires after a commit — from parsing typed input or picking in the calendar. */
|
|
558
|
+
dateChange = output();
|
|
559
|
+
/** Fires when the user picks one of the entries from `presets`. Payload is the preset that fired. */
|
|
560
|
+
presetSelected = output();
|
|
561
|
+
// ── Injected deps ──
|
|
562
|
+
adapter = inject(DATE_ADAPTER);
|
|
563
|
+
overlayService = inject(Overlay);
|
|
564
|
+
focusMonitor = inject(FocusMonitor);
|
|
565
|
+
liveAnnouncer = inject(LiveAnnouncer);
|
|
566
|
+
coordinator = inject(PickerOverlayCoordinator);
|
|
567
|
+
elementRef = inject((ElementRef));
|
|
568
|
+
viewContainerRef = inject(ViewContainerRef);
|
|
569
|
+
injector = inject(Injector);
|
|
570
|
+
destroyRef = inject(DestroyRef);
|
|
571
|
+
formField = inject(FormFieldComponent, { optional: true });
|
|
572
|
+
ngControl = inject(NgControl, { optional: true, self: true });
|
|
573
|
+
parentForm = inject(NgForm, { optional: true });
|
|
574
|
+
parentFormGroup = inject(FormGroupDirective, { optional: true });
|
|
575
|
+
defaultMatcher = inject(TW_ERROR_STATE_MATCHER);
|
|
576
|
+
// ── View refs ──
|
|
577
|
+
inputRef = viewChild.required('dateInput');
|
|
578
|
+
triggerRef = viewChild('triggerBtn', ...(ngDevMode ? [{ debugName: "triggerRef" }] : /* istanbul ignore next */ []));
|
|
579
|
+
/** @internal Whether a `[slot=trigger]` is projected. Detected post-mount via DOM query so we can drop the default chrome. */
|
|
580
|
+
hasCustomTrigger = signal(false, ...(ngDevMode ? [{ debugName: "hasCustomTrigger" }] : /* istanbul ignore next */ []));
|
|
581
|
+
// ── Identity ──
|
|
582
|
+
uid = nextDatePickerId++;
|
|
583
|
+
/** @internal */
|
|
584
|
+
hostId = `tw-date-picker-${this.uid}`;
|
|
585
|
+
/** @internal */
|
|
586
|
+
dialogId = `${this.hostId}-dialog`;
|
|
587
|
+
// ── Internal state ──
|
|
588
|
+
/** @internal Decouples programmatic writes from user commits so `writeValue` doesn't re-trigger parent effects. */
|
|
589
|
+
internalValue = linkedSignal(() => this.value(), ...(ngDevMode ? [{ debugName: "internalValue" }] : /* istanbul ignore next */ []));
|
|
590
|
+
/** @internal */
|
|
591
|
+
rawInputText = signal('', ...(ngDevMode ? [{ debugName: "rawInputText" }] : /* istanbul ignore next */ []));
|
|
592
|
+
/** @internal */
|
|
593
|
+
parseError = signal(false, ...(ngDevMode ? [{ debugName: "parseError" }] : /* istanbul ignore next */ []));
|
|
594
|
+
/** @internal */
|
|
595
|
+
rangeError = signal(false, ...(ngDevMode ? [{ debugName: "rangeError" }] : /* istanbul ignore next */ []));
|
|
596
|
+
/** @internal */
|
|
597
|
+
focusedSignal = signal(false, ...(ngDevMode ? [{ debugName: "focusedSignal" }] : /* istanbul ignore next */ []));
|
|
598
|
+
cvaDisabled = signal(false, ...(ngDevMode ? [{ debugName: "cvaDisabled" }] : /* istanbul ignore next */ []));
|
|
599
|
+
describedByIdsSignal = signal([], ...(ngDevMode ? [{ debugName: "describedByIdsSignal" }] : /* istanbul ignore next */ []));
|
|
600
|
+
closingSignal = signal(false, ...(ngDevMode ? [{ debugName: "closingSignal" }] : /* istanbul ignore next */ []));
|
|
601
|
+
lastValueBeforeOpen = signal(null, ...(ngDevMode ? [{ debugName: "lastValueBeforeOpen" }] : /* istanbul ignore next */ []));
|
|
602
|
+
pendingCalendarValue = signal(null, ...(ngDevMode ? [{ debugName: "pendingCalendarValue" }] : /* istanbul ignore next */ []));
|
|
603
|
+
activePresetIdSignal = signal(undefined, ...(ngDevMode ? [{ debugName: "activePresetIdSignal" }] : /* istanbul ignore next */ []));
|
|
604
|
+
_ngControlRev = signal(0, ...(ngDevMode ? [{ debugName: "_ngControlRev" }] : /* istanbul ignore next */ []));
|
|
605
|
+
_formSubmitRev = signal(0, ...(ngDevMode ? [{ debugName: "_formSubmitRev" }] : /* istanbul ignore next */ []));
|
|
606
|
+
onChange = () => { };
|
|
607
|
+
onTouched = () => { };
|
|
608
|
+
overlayInstanceSignal = signal(null, ...(ngDevMode ? [{ debugName: "overlayInstanceSignal" }] : /* istanbul ignore next */ []));
|
|
609
|
+
returnFocusTo = null;
|
|
610
|
+
get overlayInstance() {
|
|
611
|
+
return this.overlayInstanceSignal();
|
|
612
|
+
}
|
|
613
|
+
// ── Derived state ──
|
|
614
|
+
/** @internal */
|
|
615
|
+
isDisabled = computed(() => {
|
|
616
|
+
this._ngControlRev();
|
|
617
|
+
return this.disabledInput() || this.cvaDisabled() || !!this.ngControl?.disabled;
|
|
618
|
+
}, ...(ngDevMode ? [{ debugName: "isDisabled" }] : /* istanbul ignore next */ []));
|
|
619
|
+
/** @internal Effective variant — auto-naked when wrapped in tw-form-field. */
|
|
620
|
+
resolvedVariant = computed(() => this.variant() ?? (this.formField ? 'naked' : 'default'), ...(ngDevMode ? [{ debugName: "resolvedVariant" }] : /* istanbul ignore next */ []));
|
|
621
|
+
/** @internal */
|
|
622
|
+
isEmpty = computed(() => this.internalValue() === null && !this.rawInputText(), ...(ngDevMode ? [{ debugName: "isEmpty" }] : /* istanbul ignore next */ []));
|
|
623
|
+
/** Whether the time picker is rendered inside the overlay. True iff `timeConfig` is non-null. */
|
|
624
|
+
withTime = computed(() => this.timeConfig() !== null, ...(ngDevMode ? [{ debugName: "withTime" }] : /* istanbul ignore next */ []));
|
|
625
|
+
/** @internal Effective time-of-day configuration — merges `timeConfig` over the documented per-field defaults. */
|
|
626
|
+
effectiveTimeConfig = computed(() => {
|
|
627
|
+
const cfg = this.timeConfig();
|
|
628
|
+
return {
|
|
629
|
+
format: cfg?.format ?? '24h',
|
|
630
|
+
showSeconds: cfg?.showSeconds ?? false,
|
|
631
|
+
hourStep: cfg?.hourStep ?? 1,
|
|
632
|
+
minuteStep: cfg?.minuteStep ?? 1,
|
|
633
|
+
secondStep: cfg?.secondStep ?? 1,
|
|
634
|
+
minTime: cfg?.minTime ?? null,
|
|
635
|
+
maxTime: cfg?.maxTime ?? null,
|
|
636
|
+
};
|
|
637
|
+
}, ...(ngDevMode ? [{ debugName: "effectiveTimeConfig" }] : /* istanbul ignore next */ []));
|
|
638
|
+
/** @internal Effective display format — folds hour/minute (and optional seconds) into the default when `withTime` is on and the user hasn't overridden `format`. */
|
|
639
|
+
effectiveFormat = computed(() => {
|
|
640
|
+
const fmt = this.format();
|
|
641
|
+
if (!this.withTime() || fmt !== DEFAULT_DISPLAY_FORMAT)
|
|
642
|
+
return fmt;
|
|
643
|
+
const time = this.effectiveTimeConfig();
|
|
644
|
+
const hour12 = time.format === '12h';
|
|
645
|
+
const base = {
|
|
646
|
+
year: 'numeric',
|
|
647
|
+
month: 'short',
|
|
648
|
+
day: 'numeric',
|
|
649
|
+
hour: '2-digit',
|
|
650
|
+
minute: '2-digit',
|
|
651
|
+
hour12,
|
|
652
|
+
};
|
|
653
|
+
if (time.showSeconds)
|
|
654
|
+
base.second = '2-digit';
|
|
655
|
+
return { dateTimeFormat: base };
|
|
656
|
+
}, ...(ngDevMode ? [{ debugName: "effectiveFormat" }] : /* istanbul ignore next */ []));
|
|
657
|
+
/** @internal */
|
|
658
|
+
describedBy = computed(() => {
|
|
659
|
+
const extra = this.describedByIdsSignal();
|
|
660
|
+
const user = this.userAriaDescribedByInput();
|
|
661
|
+
const merged = [...extra];
|
|
662
|
+
if (user) {
|
|
663
|
+
for (const id of user.split(/\s+/)) {
|
|
664
|
+
if (id)
|
|
665
|
+
merged.push(id);
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
return merged.length ? merged.join(' ') : '';
|
|
669
|
+
}, ...(ngDevMode ? [{ debugName: "describedBy" }] : /* istanbul ignore next */ []));
|
|
670
|
+
// ── tv() output ──
|
|
671
|
+
variantResult = computed(() => datePickerVariants({
|
|
672
|
+
size: this.size(),
|
|
673
|
+
variant: this.resolvedVariant(),
|
|
674
|
+
open: this.open(),
|
|
675
|
+
disabled: this.isDisabled(),
|
|
676
|
+
errorState: this.errorState(),
|
|
677
|
+
focused: this.focusedSignal(),
|
|
678
|
+
color: this.color(),
|
|
679
|
+
}), ...(ngDevMode ? [{ debugName: "variantResult" }] : /* istanbul ignore next */ []));
|
|
680
|
+
/** @internal */
|
|
681
|
+
rootClasses = computed(() => this.variantResult().root(), ...(ngDevMode ? [{ debugName: "rootClasses" }] : /* istanbul ignore next */ []));
|
|
682
|
+
/** @internal */
|
|
683
|
+
inputClasses = computed(() => this.variantResult().input(), ...(ngDevMode ? [{ debugName: "inputClasses" }] : /* istanbul ignore next */ []));
|
|
684
|
+
/** @internal */
|
|
685
|
+
triggerButtonClasses = computed(() => this.variantResult().triggerButton(), ...(ngDevMode ? [{ debugName: "triggerButtonClasses" }] : /* istanbul ignore next */ []));
|
|
686
|
+
/** @internal */
|
|
687
|
+
triggerIconClasses = computed(() => this.variantResult().triggerIcon(), ...(ngDevMode ? [{ debugName: "triggerIconClasses" }] : /* istanbul ignore next */ []));
|
|
688
|
+
/** @internal */
|
|
689
|
+
clearButtonClasses = computed(() => this.variantResult().clearButton(), ...(ngDevMode ? [{ debugName: "clearButtonClasses" }] : /* istanbul ignore next */ []));
|
|
690
|
+
// ── FormFieldControl impl ──
|
|
691
|
+
/** @internal */
|
|
692
|
+
id = computed(() => this.idInput() ?? this.hostId, ...(ngDevMode ? [{ debugName: "id" }] : /* istanbul ignore next */ []));
|
|
693
|
+
/** @internal */
|
|
694
|
+
focused = this.focusedSignal.asReadonly();
|
|
695
|
+
/** @internal */
|
|
696
|
+
empty = this.isEmpty;
|
|
697
|
+
/** @internal */
|
|
698
|
+
disabled = this.isDisabled;
|
|
699
|
+
/** @internal */
|
|
700
|
+
required = computed(() => {
|
|
701
|
+
this._ngControlRev();
|
|
702
|
+
if (this.requiredInput())
|
|
703
|
+
return true;
|
|
704
|
+
return !!this.ngControl?.control?.hasValidator(Validators.required);
|
|
705
|
+
}, ...(ngDevMode ? [{ debugName: "required" }] : /* istanbul ignore next */ []));
|
|
706
|
+
/** @internal */
|
|
707
|
+
errorState = computed(() => {
|
|
708
|
+
this._ngControlRev();
|
|
709
|
+
this._formSubmitRev();
|
|
710
|
+
this.focusedSignal();
|
|
711
|
+
if (this.parseError() || this.rangeError())
|
|
712
|
+
return true;
|
|
713
|
+
const matcher = this.errorStateMatcher() ?? this.defaultMatcher;
|
|
714
|
+
const form = this.parentFormGroup ??
|
|
715
|
+
this.parentForm;
|
|
716
|
+
return matcher.isErrorState(this.ngControl?.control ?? null, form);
|
|
717
|
+
}, ...(ngDevMode ? [{ debugName: "errorState" }] : /* istanbul ignore next */ []));
|
|
718
|
+
/** @internal */
|
|
719
|
+
controlType = 'date-picker';
|
|
720
|
+
/** @internal */
|
|
721
|
+
userAriaDescribedBy = computed(() => this.userAriaDescribedByInput(), ...(ngDevMode ? [{ debugName: "userAriaDescribedBy" }] : /* istanbul ignore next */ []));
|
|
722
|
+
// ── Constructor ──
|
|
723
|
+
constructor() {
|
|
724
|
+
super();
|
|
725
|
+
// Wire this component as the NgControl's value accessor without registering
|
|
726
|
+
// NG_VALUE_ACCESSOR in providers, which would create a circular DI with the
|
|
727
|
+
// `inject(NgControl, { self: true })` above.
|
|
728
|
+
if (this.ngControl) {
|
|
729
|
+
this.ngControl.valueAccessor = this;
|
|
730
|
+
}
|
|
731
|
+
// Sync rawInputText whenever the underlying value changes (programmatic or user).
|
|
732
|
+
effect(() => {
|
|
733
|
+
const v = this.internalValue();
|
|
734
|
+
const fmt = this.effectiveFormat();
|
|
735
|
+
untracked(() => {
|
|
736
|
+
const display = v === null ? '' : this.adapter.format(v, fmt);
|
|
737
|
+
this.rawInputText.set(display);
|
|
738
|
+
this.parseError.set(false);
|
|
739
|
+
this.rangeError.set(false);
|
|
740
|
+
});
|
|
741
|
+
});
|
|
742
|
+
// Mirror the `open` model into the overlay lifecycle.
|
|
743
|
+
effect(() => {
|
|
744
|
+
const shouldOpen = this.open();
|
|
745
|
+
const disabled = this.isDisabled();
|
|
746
|
+
if (disabled && this.overlayInstance) {
|
|
747
|
+
this.closeOverlay('programmatic');
|
|
748
|
+
return;
|
|
749
|
+
}
|
|
750
|
+
if (shouldOpen && !this.overlayInstance && !disabled && !this.closingSignal()) {
|
|
751
|
+
this.openOverlay();
|
|
752
|
+
}
|
|
753
|
+
else if (!shouldOpen && this.overlayInstance && !this.closingSignal()) {
|
|
754
|
+
this.closeOverlay('programmatic');
|
|
755
|
+
}
|
|
756
|
+
});
|
|
757
|
+
// Push state into the overlay whenever anything relevant changes.
|
|
758
|
+
// Reads happen in the tracked phase; writes to the overlay instance are
|
|
759
|
+
// wrapped in `untracked()` so they never feed back into this effect.
|
|
760
|
+
effect(() => {
|
|
761
|
+
const instance = this.overlayInstance;
|
|
762
|
+
if (!instance)
|
|
763
|
+
return;
|
|
764
|
+
const time = this.effectiveTimeConfig();
|
|
765
|
+
const size = this.size();
|
|
766
|
+
const color = this.color();
|
|
767
|
+
const minDate = this.minDate();
|
|
768
|
+
const maxDate = this.maxDate();
|
|
769
|
+
const dateFilter = this.dateFilter();
|
|
770
|
+
const startView = this.startView();
|
|
771
|
+
const startAt = this.startAt() ?? this.internalValue() ?? null;
|
|
772
|
+
const pendingValue = this.pendingCalendarValue();
|
|
773
|
+
const showActions = this.showActions();
|
|
774
|
+
const todayLabel = this.todayLabel();
|
|
775
|
+
const clearLabel = this.clearLabel();
|
|
776
|
+
const cancelLabel = this.cancelLabel();
|
|
777
|
+
const applyLabel = this.applyLabel();
|
|
778
|
+
const dialogAriaLabel = this.resolveDialogAriaLabel();
|
|
779
|
+
const panelClassValue = this.resolvePanelClass();
|
|
780
|
+
const withTime = this.withTime();
|
|
781
|
+
const locale = this.locale();
|
|
782
|
+
const dateClass = this.dateClass();
|
|
783
|
+
const cellTemplate = this.cellTemplate();
|
|
784
|
+
const presets = this.presets();
|
|
785
|
+
const activePresetId = this.activePresetIdSignal();
|
|
786
|
+
untracked(() => {
|
|
787
|
+
instance.size.set(size);
|
|
788
|
+
instance.color.set(color);
|
|
789
|
+
instance.minDate.set(minDate);
|
|
790
|
+
instance.maxDate.set(maxDate);
|
|
791
|
+
instance.dateFilter.set(dateFilter);
|
|
792
|
+
instance.startView.set(startView);
|
|
793
|
+
instance.startAt.set(startAt);
|
|
794
|
+
instance.pendingValue.set(pendingValue);
|
|
795
|
+
instance.showActions.set(showActions);
|
|
796
|
+
instance.todayLabel.set(todayLabel);
|
|
797
|
+
instance.clearLabel.set(clearLabel);
|
|
798
|
+
instance.cancelLabel.set(cancelLabel);
|
|
799
|
+
instance.applyLabel.set(applyLabel);
|
|
800
|
+
instance.dialogId.set(this.dialogId);
|
|
801
|
+
instance.dialogAriaLabel.set(dialogAriaLabel);
|
|
802
|
+
instance.panelClassValue.set(panelClassValue);
|
|
803
|
+
instance.withTime.set(withTime);
|
|
804
|
+
instance.timeFormat.set(time.format);
|
|
805
|
+
instance.showSeconds.set(time.showSeconds);
|
|
806
|
+
instance.hourStep.set(time.hourStep);
|
|
807
|
+
instance.minuteStep.set(time.minuteStep);
|
|
808
|
+
instance.secondStep.set(time.secondStep);
|
|
809
|
+
instance.minTime.set(time.minTime);
|
|
810
|
+
instance.maxTime.set(time.maxTime);
|
|
811
|
+
instance.locale.set(locale);
|
|
812
|
+
instance.dateClass.set(dateClass);
|
|
813
|
+
instance.cellTemplate.set(cellTemplate);
|
|
814
|
+
instance.presets.set(presets);
|
|
815
|
+
instance.activePresetId.set(activePresetId);
|
|
816
|
+
});
|
|
817
|
+
});
|
|
818
|
+
// Push locale into the adapter for parse/format. The calendar also calls
|
|
819
|
+
// setLocale, but a standalone picker without a calendar open still needs
|
|
820
|
+
// the parser to honour the picker's locale.
|
|
821
|
+
effect(() => {
|
|
822
|
+
const locale = this.locale();
|
|
823
|
+
if (locale === null)
|
|
824
|
+
return;
|
|
825
|
+
untracked(() => this.adapter.setLocale(locale));
|
|
826
|
+
});
|
|
827
|
+
// Dev-mode accessible-name warning + projected-trigger detection.
|
|
828
|
+
afterNextRender(() => {
|
|
829
|
+
const host = this.elementRef.nativeElement;
|
|
830
|
+
// Detect [slot=trigger] projection by querying for projected children that carry the attribute.
|
|
831
|
+
// Angular's <ng-content select="[slot=trigger]"> moves them into the rendered DOM under the host.
|
|
832
|
+
if (host.querySelector('[slot="trigger"]')) {
|
|
833
|
+
this.hasCustomTrigger.set(true);
|
|
834
|
+
}
|
|
835
|
+
if (!isDevMode())
|
|
836
|
+
return;
|
|
837
|
+
const hasLabel = !!this.ariaLabel() ||
|
|
838
|
+
!!this.ariaLabelledby() ||
|
|
839
|
+
!!this.formField?.labelChild();
|
|
840
|
+
if (!hasLabel) {
|
|
841
|
+
console.warn('[tw-date-picker] The date-picker has no accessible name. Set aria-label, aria-labelledby, or wrap the component in a <tw-form-field> with a <label twLabel>.');
|
|
842
|
+
}
|
|
843
|
+
});
|
|
844
|
+
// Monitor focus on the host (trigger + input + overlay when opened).
|
|
845
|
+
const monitorSub = this.focusMonitor
|
|
846
|
+
.monitor(this.elementRef, true)
|
|
847
|
+
.subscribe((origin) => {
|
|
848
|
+
const wasFocused = this.focusedSignal();
|
|
849
|
+
this.focusedSignal.set(!!origin);
|
|
850
|
+
if (wasFocused && !origin) {
|
|
851
|
+
this.onTouched();
|
|
852
|
+
this._ngControlRev.update((n) => n + 1);
|
|
853
|
+
}
|
|
854
|
+
});
|
|
855
|
+
this.destroyRef.onDestroy(() => {
|
|
856
|
+
monitorSub.unsubscribe();
|
|
857
|
+
this.focusMonitor.stopMonitoring(this.elementRef);
|
|
858
|
+
// The coordinator's own DestroyRef.onDestroy disposes the overlay and
|
|
859
|
+
// clears its timers; we only need to drop the local instance signal.
|
|
860
|
+
this.overlayInstanceSignal.set(null);
|
|
861
|
+
});
|
|
862
|
+
}
|
|
863
|
+
ngOnInit() {
|
|
864
|
+
const ctrl = this.ngControl?.control;
|
|
865
|
+
if (ctrl) {
|
|
866
|
+
const streams = [ctrl.statusChanges, ctrl.valueChanges].filter((s) => !!s);
|
|
867
|
+
if (streams.length) {
|
|
868
|
+
merge(...streams)
|
|
869
|
+
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
870
|
+
.subscribe(() => this._ngControlRev.update((n) => n + 1));
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
const submit = this.parentFormGroup?.ngSubmit ?? this.parentForm?.ngSubmit;
|
|
874
|
+
if (submit) {
|
|
875
|
+
submit
|
|
876
|
+
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
877
|
+
.subscribe(() => this._formSubmitRev.update((n) => n + 1));
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
// ── Public imperative API ──
|
|
881
|
+
/** Opens the overlay. No-op when disabled or already open. */
|
|
882
|
+
openPicker() {
|
|
883
|
+
if (this.isDisabled() || this.open())
|
|
884
|
+
return;
|
|
885
|
+
this.open.set(true);
|
|
886
|
+
}
|
|
887
|
+
/** Closes the overlay. No-op when already closed. */
|
|
888
|
+
closePicker() {
|
|
889
|
+
if (!this.open())
|
|
890
|
+
return;
|
|
891
|
+
this.open.set(false);
|
|
892
|
+
}
|
|
893
|
+
/** Toggles the overlay's open state. */
|
|
894
|
+
toggle() {
|
|
895
|
+
if (this.open())
|
|
896
|
+
this.closePicker();
|
|
897
|
+
else
|
|
898
|
+
this.openPicker();
|
|
899
|
+
}
|
|
900
|
+
/** Clears the current value and emits `dateChange` with `source: 'clear'`. */
|
|
901
|
+
clear() {
|
|
902
|
+
this.commit(null, 'clear');
|
|
903
|
+
}
|
|
904
|
+
// ── Input interactions ──
|
|
905
|
+
/** @internal */
|
|
906
|
+
onInputEvent(event) {
|
|
907
|
+
const target = event.target;
|
|
908
|
+
const rawText = target.value;
|
|
909
|
+
this.rawInputText.set(rawText);
|
|
910
|
+
this.parseError.set(false);
|
|
911
|
+
this.rangeError.set(false);
|
|
912
|
+
// Non-committing parse so the payload's `parsed` field reflects success when achievable.
|
|
913
|
+
let parsed = null;
|
|
914
|
+
if (rawText.trim()) {
|
|
915
|
+
const candidate = this.adapter.parse(rawText, this.parseFormat());
|
|
916
|
+
if (candidate && this.adapter.isValid(candidate) && this.isInRange(candidate)) {
|
|
917
|
+
parsed = candidate;
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
this.dateInput.emit({ rawText, parsed, target });
|
|
921
|
+
}
|
|
922
|
+
/** @internal */
|
|
923
|
+
onInputBlur() {
|
|
924
|
+
this.commitFromInput();
|
|
925
|
+
}
|
|
926
|
+
/** @internal */
|
|
927
|
+
onInputFocus() {
|
|
928
|
+
if (this.openOnFocus() && !this.isDisabled() && !this.readonlyInput()) {
|
|
929
|
+
this.returnFocusTo = this.inputRef().nativeElement;
|
|
930
|
+
this.openPicker();
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
/** @internal */
|
|
934
|
+
onInputKeydown(event) {
|
|
935
|
+
if (this.isDisabled())
|
|
936
|
+
return;
|
|
937
|
+
const key = event.key;
|
|
938
|
+
if (event.altKey && key === 'ArrowDown') {
|
|
939
|
+
event.preventDefault();
|
|
940
|
+
this.returnFocusTo = this.inputRef().nativeElement;
|
|
941
|
+
this.openPicker();
|
|
942
|
+
return;
|
|
943
|
+
}
|
|
944
|
+
if (event.altKey && key === 'ArrowUp') {
|
|
945
|
+
if (this.open()) {
|
|
946
|
+
event.preventDefault();
|
|
947
|
+
this.closeOverlay('cancel', /* restore */ true);
|
|
948
|
+
}
|
|
949
|
+
return;
|
|
950
|
+
}
|
|
951
|
+
switch (key) {
|
|
952
|
+
case 'Enter':
|
|
953
|
+
event.preventDefault();
|
|
954
|
+
this.commitFromInput();
|
|
955
|
+
if (this.open()) {
|
|
956
|
+
this.closeOverlay('select');
|
|
957
|
+
}
|
|
958
|
+
return;
|
|
959
|
+
case 'Escape':
|
|
960
|
+
if (this.open()) {
|
|
961
|
+
event.preventDefault();
|
|
962
|
+
this.closeOverlay('escape', /* restore */ true);
|
|
963
|
+
}
|
|
964
|
+
return;
|
|
965
|
+
case 'Tab':
|
|
966
|
+
if (this.open()) {
|
|
967
|
+
// Let focus move out naturally; close the overlay.
|
|
968
|
+
this.closeOverlay('programmatic');
|
|
969
|
+
}
|
|
970
|
+
return;
|
|
971
|
+
default:
|
|
972
|
+
return;
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
/** @internal */
|
|
976
|
+
onClearClick(event) {
|
|
977
|
+
event.stopPropagation();
|
|
978
|
+
event.preventDefault();
|
|
979
|
+
this.clear();
|
|
980
|
+
}
|
|
981
|
+
/** @internal */
|
|
982
|
+
onTriggerClick() {
|
|
983
|
+
if (this.isDisabled())
|
|
984
|
+
return;
|
|
985
|
+
const trigger = this.triggerRef();
|
|
986
|
+
if (trigger)
|
|
987
|
+
this.returnFocusTo = trigger.nativeElement;
|
|
988
|
+
this.toggle();
|
|
989
|
+
}
|
|
990
|
+
/** @internal */
|
|
991
|
+
onTriggerKeydown(event) {
|
|
992
|
+
if (this.isDisabled())
|
|
993
|
+
return;
|
|
994
|
+
if (event.key === 'Enter' || event.key === ' ' || event.key === 'Spacebar') {
|
|
995
|
+
event.preventDefault();
|
|
996
|
+
const trigger = this.triggerRef();
|
|
997
|
+
if (trigger)
|
|
998
|
+
this.returnFocusTo = trigger.nativeElement;
|
|
999
|
+
if (!this.open())
|
|
1000
|
+
this.openPicker();
|
|
1001
|
+
}
|
|
1002
|
+
else if (event.key === 'Escape' && this.open()) {
|
|
1003
|
+
event.preventDefault();
|
|
1004
|
+
this.closeOverlay('escape', /* restore */ true);
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
/** @internal */
|
|
1008
|
+
onCustomTriggerClick(event) {
|
|
1009
|
+
if (!this.hasCustomTrigger() || this.isDisabled())
|
|
1010
|
+
return;
|
|
1011
|
+
// The projected element itself is the focus target on close.
|
|
1012
|
+
const target = event.currentTarget;
|
|
1013
|
+
if (target)
|
|
1014
|
+
this.returnFocusTo = target;
|
|
1015
|
+
event.preventDefault();
|
|
1016
|
+
this.toggle();
|
|
1017
|
+
}
|
|
1018
|
+
/** @internal */
|
|
1019
|
+
onCustomTriggerKeydown(event) {
|
|
1020
|
+
if (!this.hasCustomTrigger() || this.isDisabled())
|
|
1021
|
+
return;
|
|
1022
|
+
if (event.key === 'Enter' || event.key === ' ' || event.key === 'Spacebar') {
|
|
1023
|
+
event.preventDefault();
|
|
1024
|
+
const target = event.currentTarget;
|
|
1025
|
+
if (target)
|
|
1026
|
+
this.returnFocusTo = target;
|
|
1027
|
+
if (!this.open())
|
|
1028
|
+
this.openPicker();
|
|
1029
|
+
}
|
|
1030
|
+
else if (event.key === 'Escape' && this.open()) {
|
|
1031
|
+
event.preventDefault();
|
|
1032
|
+
this.closeOverlay('escape', /* restore */ true);
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
// ── Parse & commit ──
|
|
1036
|
+
commitFromInput() {
|
|
1037
|
+
const raw = this.rawInputText().trim();
|
|
1038
|
+
if (!raw) {
|
|
1039
|
+
if (this.internalValue() !== null || this.parseError() || this.rangeError()) {
|
|
1040
|
+
this.commit(null, 'input');
|
|
1041
|
+
}
|
|
1042
|
+
else {
|
|
1043
|
+
this.parseError.set(false);
|
|
1044
|
+
this.rangeError.set(false);
|
|
1045
|
+
}
|
|
1046
|
+
return;
|
|
1047
|
+
}
|
|
1048
|
+
const parsed = this.adapter.parse(raw, this.parseFormat());
|
|
1049
|
+
if (!parsed || !this.adapter.isValid(parsed)) {
|
|
1050
|
+
this.parseError.set(true);
|
|
1051
|
+
this.rangeError.set(false);
|
|
1052
|
+
return;
|
|
1053
|
+
}
|
|
1054
|
+
if (!this.isInRange(parsed)) {
|
|
1055
|
+
this.parseError.set(false);
|
|
1056
|
+
this.rangeError.set(true);
|
|
1057
|
+
return;
|
|
1058
|
+
}
|
|
1059
|
+
this.commit(parsed, 'input');
|
|
1060
|
+
}
|
|
1061
|
+
commit(next, source) {
|
|
1062
|
+
const previous = this.internalValue();
|
|
1063
|
+
this.internalValue.set(next);
|
|
1064
|
+
this.value.set(next);
|
|
1065
|
+
this.parseError.set(false);
|
|
1066
|
+
this.rangeError.set(false);
|
|
1067
|
+
const display = next === null ? '' : this.adapter.format(next, this.effectiveFormat());
|
|
1068
|
+
this.rawInputText.set(display);
|
|
1069
|
+
const el = this.inputRef().nativeElement;
|
|
1070
|
+
if (el.value !== display)
|
|
1071
|
+
el.value = display;
|
|
1072
|
+
if (source !== 'programmatic') {
|
|
1073
|
+
this.onChange(next);
|
|
1074
|
+
this.onTouched();
|
|
1075
|
+
if (next === null) {
|
|
1076
|
+
this.liveAnnouncer.announce('Date cleared', 'polite');
|
|
1077
|
+
}
|
|
1078
|
+
else {
|
|
1079
|
+
this.liveAnnouncer.announce(`${display} selected`, 'polite');
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
this.dateChange.emit({ value: next, previousValue: previous, source });
|
|
1083
|
+
}
|
|
1084
|
+
isInRange(d) {
|
|
1085
|
+
const min = this.minDate();
|
|
1086
|
+
if (min && this.adapter.compare(d, min) < 0)
|
|
1087
|
+
return false;
|
|
1088
|
+
const max = this.maxDate();
|
|
1089
|
+
if (max && this.adapter.compare(d, max) > 0)
|
|
1090
|
+
return false;
|
|
1091
|
+
const filter = this.dateFilter();
|
|
1092
|
+
if (filter && !filter(d))
|
|
1093
|
+
return false;
|
|
1094
|
+
if (this.withTime() && !this.isTimeInRange(d))
|
|
1095
|
+
return false;
|
|
1096
|
+
return true;
|
|
1097
|
+
}
|
|
1098
|
+
isTimeInRange(d) {
|
|
1099
|
+
const { minTime: min, maxTime: max } = this.effectiveTimeConfig();
|
|
1100
|
+
if (!min && !max)
|
|
1101
|
+
return true;
|
|
1102
|
+
const adapter = this.adapter;
|
|
1103
|
+
const vSecs = timeOfDaySeconds(adapter.getHours(d), adapter.getMinutes(d), adapter.getSeconds(d));
|
|
1104
|
+
if (min && adapter.isValid(min)) {
|
|
1105
|
+
const minSecs = timeOfDaySeconds(adapter.getHours(min), adapter.getMinutes(min), adapter.getSeconds(min));
|
|
1106
|
+
if (vSecs < minSecs)
|
|
1107
|
+
return false;
|
|
1108
|
+
}
|
|
1109
|
+
if (max && adapter.isValid(max)) {
|
|
1110
|
+
const maxSecs = timeOfDaySeconds(adapter.getHours(max), adapter.getMinutes(max), adapter.getSeconds(max));
|
|
1111
|
+
if (vSecs > maxSecs)
|
|
1112
|
+
return false;
|
|
1113
|
+
}
|
|
1114
|
+
return true;
|
|
1115
|
+
}
|
|
1116
|
+
// ── Overlay lifecycle ──
|
|
1117
|
+
openOverlay() {
|
|
1118
|
+
if (!this.returnFocusTo) {
|
|
1119
|
+
this.returnFocusTo = this.resolveFocusTarget();
|
|
1120
|
+
}
|
|
1121
|
+
this.lastValueBeforeOpen.set(this.internalValue());
|
|
1122
|
+
this.pendingCalendarValue.set(this.internalValue());
|
|
1123
|
+
const result = this.coordinator.open({
|
|
1124
|
+
origin: this.elementRef,
|
|
1125
|
+
portalComponent: DatePickerOverlayComponent,
|
|
1126
|
+
viewContainerRef: this.viewContainerRef,
|
|
1127
|
+
injector: this.injector,
|
|
1128
|
+
positions: buildSelectLikePositions(this.offset()),
|
|
1129
|
+
scrollStrategy: resolveSelectScrollStrategy(this.scrollStrategy(), this.overlayService),
|
|
1130
|
+
panelClass: 'tw-date-picker-panel',
|
|
1131
|
+
});
|
|
1132
|
+
if (!result)
|
|
1133
|
+
return;
|
|
1134
|
+
const instance = result.instance;
|
|
1135
|
+
instance.onCalendarSelect.set((date) => this.onCalendarSelection(date));
|
|
1136
|
+
instance.onTimeInput.set((date) => this.onTimeInput(date));
|
|
1137
|
+
instance.onToday.set(() => this.onTodayClicked());
|
|
1138
|
+
instance.onClear.set(() => this.onClearAction());
|
|
1139
|
+
instance.onCancel.set(() => this.onCancelAction());
|
|
1140
|
+
instance.onApply.set(() => this.onApplyAction());
|
|
1141
|
+
instance.onPresetSelect.set((preset) => this.onPresetClick(preset));
|
|
1142
|
+
instance.activePresetId.set(this.activePresetIdSignal());
|
|
1143
|
+
this.overlayInstanceSignal.set(instance);
|
|
1144
|
+
// Per-open streams from the coordinator complete on close, so no
|
|
1145
|
+
// takeUntilDestroyed is needed — accumulating subscribers across multiple
|
|
1146
|
+
// open/close cycles is the bug we'd hit otherwise.
|
|
1147
|
+
this.coordinator
|
|
1148
|
+
.backdropClick$()
|
|
1149
|
+
.subscribe(() => this.closeOverlay('backdrop'));
|
|
1150
|
+
this.coordinator.escape$().subscribe((event) => {
|
|
1151
|
+
event.preventDefault();
|
|
1152
|
+
this.closeOverlay('escape', /* restore */ true);
|
|
1153
|
+
});
|
|
1154
|
+
// Defer `opened` emission until after the enter animation completes — was
|
|
1155
|
+
// previously fired synchronously on open() (audit Medium, date-picker.ts:1199).
|
|
1156
|
+
this.coordinator
|
|
1157
|
+
.opened$()
|
|
1158
|
+
.subscribe(() => this.opened.emit({ trigger: this.elementRef.nativeElement }));
|
|
1159
|
+
queueMicrotask(() => {
|
|
1160
|
+
this.overlayInstance?.focusCalendar();
|
|
1161
|
+
});
|
|
1162
|
+
}
|
|
1163
|
+
closeOverlay(reason, restore = false) {
|
|
1164
|
+
if (this.closingSignal() || !this.overlayInstance)
|
|
1165
|
+
return;
|
|
1166
|
+
this.closingSignal.set(true);
|
|
1167
|
+
this.overlayInstance.leaving.set(true);
|
|
1168
|
+
if (restore) {
|
|
1169
|
+
const previous = this.lastValueBeforeOpen();
|
|
1170
|
+
if (previous !== this.internalValue()) {
|
|
1171
|
+
// Silent restore — no emit of dateChange.
|
|
1172
|
+
this.internalValue.set(previous);
|
|
1173
|
+
this.value.set(previous);
|
|
1174
|
+
const display = previous === null ? '' : this.adapter.format(previous, this.effectiveFormat());
|
|
1175
|
+
this.rawInputText.set(display);
|
|
1176
|
+
this.parseError.set(false);
|
|
1177
|
+
this.rangeError.set(false);
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
// Fallback chain ensures focus always returns to a visible element even when
|
|
1181
|
+
// returnFocusTo was never set (e.g. open() flipped programmatically).
|
|
1182
|
+
(this.returnFocusTo ?? this.resolveFocusTarget()).focus();
|
|
1183
|
+
this.coordinator.close(() => {
|
|
1184
|
+
this.overlayInstanceSignal.set(null);
|
|
1185
|
+
this.pendingCalendarValue.set(null);
|
|
1186
|
+
this.returnFocusTo = null;
|
|
1187
|
+
untracked(() => this.open.set(false));
|
|
1188
|
+
this.closingSignal.set(false);
|
|
1189
|
+
this.closed.emit(reason);
|
|
1190
|
+
});
|
|
1191
|
+
}
|
|
1192
|
+
// ── Calendar / action-bar callbacks ──
|
|
1193
|
+
onCalendarSelection(date) {
|
|
1194
|
+
const withTime = this.withTime() ? this.carryTimeOfDay(date) : date;
|
|
1195
|
+
// When time-of-day editing is active, picking a day never auto-commits —
|
|
1196
|
+
// the user needs a chance to tweak the time before closing.
|
|
1197
|
+
if (this.showActions() || this.withTime()) {
|
|
1198
|
+
this.pendingCalendarValue.set(withTime);
|
|
1199
|
+
return;
|
|
1200
|
+
}
|
|
1201
|
+
this.commit(withTime, 'calendar');
|
|
1202
|
+
this.closeOverlay('select');
|
|
1203
|
+
}
|
|
1204
|
+
/** @internal Called by the overlay when the embedded time-picker reports a new value. */
|
|
1205
|
+
onTimeInput(date) {
|
|
1206
|
+
if (date === null) {
|
|
1207
|
+
this.pendingCalendarValue.set(null);
|
|
1208
|
+
if (!this.showActions()) {
|
|
1209
|
+
this.commit(null, 'calendar');
|
|
1210
|
+
}
|
|
1211
|
+
return;
|
|
1212
|
+
}
|
|
1213
|
+
this.pendingCalendarValue.set(date);
|
|
1214
|
+
if (!this.showActions()) {
|
|
1215
|
+
this.commit(date, 'calendar');
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
onTodayClicked() {
|
|
1219
|
+
const today = this.adapter.today();
|
|
1220
|
+
if (!this.isInRange(today))
|
|
1221
|
+
return;
|
|
1222
|
+
const withTime = this.withTime() ? this.carryTimeOfDay(today) : today;
|
|
1223
|
+
this.pendingCalendarValue.set(withTime);
|
|
1224
|
+
}
|
|
1225
|
+
/** Copies the time-of-day from the current pending/selected value onto `picked`. */
|
|
1226
|
+
carryTimeOfDay(picked) {
|
|
1227
|
+
const source = this.pendingCalendarValue() ?? this.internalValue();
|
|
1228
|
+
if (source === null || !this.adapter.isValid(source))
|
|
1229
|
+
return picked;
|
|
1230
|
+
return this.adapter.withTime(picked, this.adapter.getHours(source), this.adapter.getMinutes(source), this.adapter.getSeconds(source));
|
|
1231
|
+
}
|
|
1232
|
+
onClearAction() {
|
|
1233
|
+
this.commit(null, 'clear');
|
|
1234
|
+
this.closeOverlay('apply');
|
|
1235
|
+
}
|
|
1236
|
+
onCancelAction() {
|
|
1237
|
+
this.closeOverlay('cancel', /* restore */ true);
|
|
1238
|
+
}
|
|
1239
|
+
onApplyAction() {
|
|
1240
|
+
const pending = this.pendingCalendarValue();
|
|
1241
|
+
if (pending !== null && !this.isInRange(pending)) {
|
|
1242
|
+
this.closeOverlay('cancel', /* restore */ true);
|
|
1243
|
+
return;
|
|
1244
|
+
}
|
|
1245
|
+
this.commit(pending, 'apply');
|
|
1246
|
+
this.closeOverlay('apply');
|
|
1247
|
+
}
|
|
1248
|
+
onPresetClick(preset) {
|
|
1249
|
+
const next = preset.date();
|
|
1250
|
+
if (!this.adapter.isValid(next) || !this.isInRange(next))
|
|
1251
|
+
return;
|
|
1252
|
+
this.activePresetIdSignal.set(preset.id);
|
|
1253
|
+
this.presetSelected.emit(preset);
|
|
1254
|
+
if (this.showActions()) {
|
|
1255
|
+
this.pendingCalendarValue.set(next);
|
|
1256
|
+
return;
|
|
1257
|
+
}
|
|
1258
|
+
this.commit(next, 'calendar');
|
|
1259
|
+
this.closeOverlay('select');
|
|
1260
|
+
}
|
|
1261
|
+
// ── Helpers ──
|
|
1262
|
+
resolveDialogAriaLabel() {
|
|
1263
|
+
return this.ariaLabel() || 'Choose a date';
|
|
1264
|
+
}
|
|
1265
|
+
resolvePanelClass() {
|
|
1266
|
+
const raw = this.panelClass();
|
|
1267
|
+
return Array.isArray(raw) ? raw.join(' ') : raw;
|
|
1268
|
+
}
|
|
1269
|
+
/** Picks the best element to receive focus on overlay close. Prefers the explicit `returnFocusTo` source, then the trigger button, the projected custom trigger, and finally the input. */
|
|
1270
|
+
resolveFocusTarget() {
|
|
1271
|
+
const trigger = this.triggerRef();
|
|
1272
|
+
if (trigger)
|
|
1273
|
+
return trigger.nativeElement;
|
|
1274
|
+
const host = this.elementRef.nativeElement;
|
|
1275
|
+
const custom = host.querySelector('[slot="trigger"]');
|
|
1276
|
+
if (custom)
|
|
1277
|
+
return custom;
|
|
1278
|
+
return this.inputRef().nativeElement;
|
|
1279
|
+
}
|
|
1280
|
+
// ── ControlValueAccessor ──
|
|
1281
|
+
writeValue(value) {
|
|
1282
|
+
if (value === null || value === undefined || value === '') {
|
|
1283
|
+
this.internalValue.set(null);
|
|
1284
|
+
this.value.set(null);
|
|
1285
|
+
this.parseError.set(false);
|
|
1286
|
+
this.rangeError.set(false);
|
|
1287
|
+
this.rawInputText.set('');
|
|
1288
|
+
this.dateChange.emit({
|
|
1289
|
+
value: null,
|
|
1290
|
+
previousValue: this.internalValue(),
|
|
1291
|
+
source: 'programmatic',
|
|
1292
|
+
});
|
|
1293
|
+
return;
|
|
1294
|
+
}
|
|
1295
|
+
const coerced = this.adapter.deserialize(value);
|
|
1296
|
+
if (coerced === null) {
|
|
1297
|
+
this.internalValue.set(null);
|
|
1298
|
+
this.value.set(null);
|
|
1299
|
+
this.rawInputText.set('');
|
|
1300
|
+
this.parseError.set(false);
|
|
1301
|
+
this.rangeError.set(false);
|
|
1302
|
+
return;
|
|
1303
|
+
}
|
|
1304
|
+
if (!this.adapter.isValid(coerced)) {
|
|
1305
|
+
this.parseError.set(true);
|
|
1306
|
+
this.rawInputText.set(String(value));
|
|
1307
|
+
return;
|
|
1308
|
+
}
|
|
1309
|
+
const previous = this.internalValue();
|
|
1310
|
+
this.internalValue.set(coerced);
|
|
1311
|
+
this.value.set(coerced);
|
|
1312
|
+
this.parseError.set(false);
|
|
1313
|
+
this.rangeError.set(false);
|
|
1314
|
+
this.rawInputText.set(this.adapter.format(coerced, this.effectiveFormat()));
|
|
1315
|
+
this.dateChange.emit({ value: coerced, previousValue: previous, source: 'programmatic' });
|
|
1316
|
+
}
|
|
1317
|
+
registerOnChange(fn) {
|
|
1318
|
+
this.onChange = fn;
|
|
1319
|
+
}
|
|
1320
|
+
registerOnTouched(fn) {
|
|
1321
|
+
this.onTouched = fn;
|
|
1322
|
+
}
|
|
1323
|
+
setDisabledState(isDisabled) {
|
|
1324
|
+
this.cvaDisabled.set(isDisabled);
|
|
1325
|
+
}
|
|
1326
|
+
// ── FormFieldControl methods ──
|
|
1327
|
+
/** @internal */
|
|
1328
|
+
setDescribedByIds(ids) {
|
|
1329
|
+
this.describedByIdsSignal.set([...ids]);
|
|
1330
|
+
}
|
|
1331
|
+
/** @internal */
|
|
1332
|
+
onContainerClick(event) {
|
|
1333
|
+
if (this.isDisabled() || this.hasCustomTrigger())
|
|
1334
|
+
return;
|
|
1335
|
+
// Don't steal focus when the user clicks the trigger button directly.
|
|
1336
|
+
const target = event.target;
|
|
1337
|
+
const triggerEl = this.triggerRef()?.nativeElement;
|
|
1338
|
+
if (triggerEl && target && triggerEl.contains(target))
|
|
1339
|
+
return;
|
|
1340
|
+
const inputEl = this.inputRef()?.nativeElement;
|
|
1341
|
+
if (inputEl && document.activeElement !== inputEl) {
|
|
1342
|
+
inputEl.focus();
|
|
1343
|
+
}
|
|
1344
|
+
}
|
|
1345
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: DatePickerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1346
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", type: DatePickerComponent, isStandalone: true, selector: "tw-date-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 }, parseFormat: { classPropertyName: "parseFormat", publicName: "parseFormat", 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 }, readonlyInput: { classPropertyName: "readonlyInput", publicName: "readonly", 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 }, showClear: { classPropertyName: "showClear", publicName: "showClear", isSignal: true, isRequired: false, transformFunction: null }, showActions: { classPropertyName: "showActions", publicName: "showActions", 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 }, openOnFocus: { classPropertyName: "openOnFocus", publicName: "openOnFocus", 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 }, triggerAriaLabel: { classPropertyName: "triggerAriaLabel", publicName: "triggerAriaLabel", isSignal: true, isRequired: false, transformFunction: null }, timeConfig: { classPropertyName: "timeConfig", publicName: "timeConfig", 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 }, presets: { classPropertyName: "presets", publicName: "presets", 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", dateInput: "dateInput", dateChange: "dateChange", presetSelected: "presetSelected" }, host: { properties: { "class": "rootClasses()" } }, providers: [
|
|
1347
|
+
{
|
|
1348
|
+
provide: TW_FORM_FIELD_CONTROL,
|
|
1349
|
+
useExisting: forwardRef(() => DatePickerComponent),
|
|
1350
|
+
},
|
|
1351
|
+
PickerOverlayCoordinator,
|
|
1352
|
+
], viewQueries: [{ propertyName: "inputRef", first: true, predicate: ["dateInput"], descendants: true, isSignal: true }, { propertyName: "triggerRef", first: true, predicate: ["triggerBtn"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: `
|
|
1353
|
+
<!-- Optional rich-label trigger. When projected (a child carries [slot=trigger]), the default input/clear/trigger chrome is hidden. Clicks anywhere in the projected content open the overlay. -->
|
|
1354
|
+
<div
|
|
1355
|
+
class="contents"
|
|
1356
|
+
(click)="onCustomTriggerClick($event)"
|
|
1357
|
+
(keydown)="onCustomTriggerKeydown($event)"
|
|
1358
|
+
>
|
|
1359
|
+
<ng-content select="[slot=trigger]" />
|
|
1360
|
+
</div>
|
|
1361
|
+
|
|
1362
|
+
<input
|
|
1363
|
+
#dateInput
|
|
1364
|
+
type="text"
|
|
1365
|
+
[id]="hostId"
|
|
1366
|
+
[class]="inputClasses()"
|
|
1367
|
+
[class.sr-only]="hasCustomTrigger()"
|
|
1368
|
+
[value]="rawInputText()"
|
|
1369
|
+
[placeholder]="placeholder() || ''"
|
|
1370
|
+
[disabled]="isDisabled()"
|
|
1371
|
+
[tabindex]="hasCustomTrigger() ? -1 : null"
|
|
1372
|
+
[attr.readonly]="readonlyInput() ? 'true' : null"
|
|
1373
|
+
[attr.role]="'combobox'"
|
|
1374
|
+
[attr.aria-haspopup]="'dialog'"
|
|
1375
|
+
[attr.aria-expanded]="open() ? 'true' : 'false'"
|
|
1376
|
+
[attr.aria-controls]="dialogId"
|
|
1377
|
+
[attr.aria-autocomplete]="'none'"
|
|
1378
|
+
[attr.aria-label]="ariaLabel() || null"
|
|
1379
|
+
[attr.aria-labelledby]="ariaLabelledby() || null"
|
|
1380
|
+
[attr.aria-describedby]="describedBy() || null"
|
|
1381
|
+
[attr.aria-required]="requiredInput() || null"
|
|
1382
|
+
[attr.aria-invalid]="errorState() || null"
|
|
1383
|
+
[attr.aria-disabled]="isDisabled() || null"
|
|
1384
|
+
[attr.data-variant]="resolvedVariant()"
|
|
1385
|
+
(input)="onInputEvent($event)"
|
|
1386
|
+
(blur)="onInputBlur()"
|
|
1387
|
+
(focus)="onInputFocus()"
|
|
1388
|
+
(keydown)="onInputKeydown($event)"
|
|
1389
|
+
/>
|
|
1390
|
+
|
|
1391
|
+
@if (!hasCustomTrigger() && showClear() && !isEmpty() && !isDisabled() && !readonlyInput()) {
|
|
1392
|
+
<button
|
|
1393
|
+
type="button"
|
|
1394
|
+
tabindex="-1"
|
|
1395
|
+
[class]="clearButtonClasses()"
|
|
1396
|
+
[attr.aria-label]="'Clear date'"
|
|
1397
|
+
(click)="onClearClick($event)"
|
|
1398
|
+
>
|
|
1399
|
+
<svg viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" class="size-4">
|
|
1400
|
+
<path
|
|
1401
|
+
fill-rule="evenodd"
|
|
1402
|
+
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"
|
|
1403
|
+
clip-rule="evenodd"
|
|
1404
|
+
/>
|
|
1405
|
+
</svg>
|
|
1406
|
+
</button>
|
|
1407
|
+
}
|
|
1408
|
+
|
|
1409
|
+
@if (!hasCustomTrigger()) {
|
|
1410
|
+
<button
|
|
1411
|
+
#triggerBtn
|
|
1412
|
+
type="button"
|
|
1413
|
+
[class]="triggerButtonClasses()"
|
|
1414
|
+
[attr.aria-label]="triggerAriaLabel()"
|
|
1415
|
+
[attr.aria-haspopup]="'dialog'"
|
|
1416
|
+
[attr.aria-expanded]="open() ? 'true' : 'false'"
|
|
1417
|
+
[attr.aria-controls]="dialogId"
|
|
1418
|
+
[attr.aria-disabled]="isDisabled() || null"
|
|
1419
|
+
[disabled]="isDisabled()"
|
|
1420
|
+
(click)="onTriggerClick()"
|
|
1421
|
+
(keydown)="onTriggerKeydown($event)"
|
|
1422
|
+
>
|
|
1423
|
+
<ng-content select="[slot=trigger-icon]">
|
|
1424
|
+
<svg
|
|
1425
|
+
[class]="triggerIconClasses()"
|
|
1426
|
+
viewBox="0 0 24 24"
|
|
1427
|
+
fill="none"
|
|
1428
|
+
stroke="currentColor"
|
|
1429
|
+
stroke-width="2"
|
|
1430
|
+
stroke-linecap="round"
|
|
1431
|
+
stroke-linejoin="round"
|
|
1432
|
+
aria-hidden="true"
|
|
1433
|
+
>
|
|
1434
|
+
<rect x="3" y="4" width="18" height="18" rx="2" ry="2" />
|
|
1435
|
+
<line x1="16" y1="2" x2="16" y2="6" />
|
|
1436
|
+
<line x1="8" y1="2" x2="8" y2="6" />
|
|
1437
|
+
<line x1="3" y1="10" x2="21" y2="10" />
|
|
1438
|
+
</svg>
|
|
1439
|
+
</ng-content>
|
|
1440
|
+
</button>
|
|
1441
|
+
}
|
|
1442
|
+
`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1443
|
+
}
|
|
1444
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: DatePickerComponent, decorators: [{
|
|
1445
|
+
type: Component,
|
|
1446
|
+
args: [{
|
|
1447
|
+
selector: 'tw-date-picker',
|
|
1448
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
1449
|
+
providers: [
|
|
1450
|
+
{
|
|
1451
|
+
provide: TW_FORM_FIELD_CONTROL,
|
|
1452
|
+
useExisting: forwardRef(() => DatePickerComponent),
|
|
1453
|
+
},
|
|
1454
|
+
PickerOverlayCoordinator,
|
|
1455
|
+
],
|
|
1456
|
+
template: `
|
|
1457
|
+
<!-- Optional rich-label trigger. When projected (a child carries [slot=trigger]), the default input/clear/trigger chrome is hidden. Clicks anywhere in the projected content open the overlay. -->
|
|
1458
|
+
<div
|
|
1459
|
+
class="contents"
|
|
1460
|
+
(click)="onCustomTriggerClick($event)"
|
|
1461
|
+
(keydown)="onCustomTriggerKeydown($event)"
|
|
1462
|
+
>
|
|
1463
|
+
<ng-content select="[slot=trigger]" />
|
|
1464
|
+
</div>
|
|
1465
|
+
|
|
1466
|
+
<input
|
|
1467
|
+
#dateInput
|
|
1468
|
+
type="text"
|
|
1469
|
+
[id]="hostId"
|
|
1470
|
+
[class]="inputClasses()"
|
|
1471
|
+
[class.sr-only]="hasCustomTrigger()"
|
|
1472
|
+
[value]="rawInputText()"
|
|
1473
|
+
[placeholder]="placeholder() || ''"
|
|
1474
|
+
[disabled]="isDisabled()"
|
|
1475
|
+
[tabindex]="hasCustomTrigger() ? -1 : null"
|
|
1476
|
+
[attr.readonly]="readonlyInput() ? 'true' : null"
|
|
1477
|
+
[attr.role]="'combobox'"
|
|
1478
|
+
[attr.aria-haspopup]="'dialog'"
|
|
1479
|
+
[attr.aria-expanded]="open() ? 'true' : 'false'"
|
|
1480
|
+
[attr.aria-controls]="dialogId"
|
|
1481
|
+
[attr.aria-autocomplete]="'none'"
|
|
1482
|
+
[attr.aria-label]="ariaLabel() || null"
|
|
1483
|
+
[attr.aria-labelledby]="ariaLabelledby() || null"
|
|
1484
|
+
[attr.aria-describedby]="describedBy() || null"
|
|
1485
|
+
[attr.aria-required]="requiredInput() || null"
|
|
1486
|
+
[attr.aria-invalid]="errorState() || null"
|
|
1487
|
+
[attr.aria-disabled]="isDisabled() || null"
|
|
1488
|
+
[attr.data-variant]="resolvedVariant()"
|
|
1489
|
+
(input)="onInputEvent($event)"
|
|
1490
|
+
(blur)="onInputBlur()"
|
|
1491
|
+
(focus)="onInputFocus()"
|
|
1492
|
+
(keydown)="onInputKeydown($event)"
|
|
1493
|
+
/>
|
|
1494
|
+
|
|
1495
|
+
@if (!hasCustomTrigger() && showClear() && !isEmpty() && !isDisabled() && !readonlyInput()) {
|
|
1496
|
+
<button
|
|
1497
|
+
type="button"
|
|
1498
|
+
tabindex="-1"
|
|
1499
|
+
[class]="clearButtonClasses()"
|
|
1500
|
+
[attr.aria-label]="'Clear date'"
|
|
1501
|
+
(click)="onClearClick($event)"
|
|
1502
|
+
>
|
|
1503
|
+
<svg viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" class="size-4">
|
|
1504
|
+
<path
|
|
1505
|
+
fill-rule="evenodd"
|
|
1506
|
+
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"
|
|
1507
|
+
clip-rule="evenodd"
|
|
1508
|
+
/>
|
|
1509
|
+
</svg>
|
|
1510
|
+
</button>
|
|
1511
|
+
}
|
|
1512
|
+
|
|
1513
|
+
@if (!hasCustomTrigger()) {
|
|
1514
|
+
<button
|
|
1515
|
+
#triggerBtn
|
|
1516
|
+
type="button"
|
|
1517
|
+
[class]="triggerButtonClasses()"
|
|
1518
|
+
[attr.aria-label]="triggerAriaLabel()"
|
|
1519
|
+
[attr.aria-haspopup]="'dialog'"
|
|
1520
|
+
[attr.aria-expanded]="open() ? 'true' : 'false'"
|
|
1521
|
+
[attr.aria-controls]="dialogId"
|
|
1522
|
+
[attr.aria-disabled]="isDisabled() || null"
|
|
1523
|
+
[disabled]="isDisabled()"
|
|
1524
|
+
(click)="onTriggerClick()"
|
|
1525
|
+
(keydown)="onTriggerKeydown($event)"
|
|
1526
|
+
>
|
|
1527
|
+
<ng-content select="[slot=trigger-icon]">
|
|
1528
|
+
<svg
|
|
1529
|
+
[class]="triggerIconClasses()"
|
|
1530
|
+
viewBox="0 0 24 24"
|
|
1531
|
+
fill="none"
|
|
1532
|
+
stroke="currentColor"
|
|
1533
|
+
stroke-width="2"
|
|
1534
|
+
stroke-linecap="round"
|
|
1535
|
+
stroke-linejoin="round"
|
|
1536
|
+
aria-hidden="true"
|
|
1537
|
+
>
|
|
1538
|
+
<rect x="3" y="4" width="18" height="18" rx="2" ry="2" />
|
|
1539
|
+
<line x1="16" y1="2" x2="16" y2="6" />
|
|
1540
|
+
<line x1="8" y1="2" x2="8" y2="6" />
|
|
1541
|
+
<line x1="3" y1="10" x2="21" y2="10" />
|
|
1542
|
+
</svg>
|
|
1543
|
+
</ng-content>
|
|
1544
|
+
</button>
|
|
1545
|
+
}
|
|
1546
|
+
`,
|
|
1547
|
+
host: {
|
|
1548
|
+
'[class]': 'rootClasses()',
|
|
1549
|
+
},
|
|
1550
|
+
}]
|
|
1551
|
+
}], 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 }] }], parseFormat: [{ type: i0.Input, args: [{ isSignal: true, alias: "parseFormat", 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 }] }], readonlyInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", 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 }] }], showClear: [{ type: i0.Input, args: [{ isSignal: true, alias: "showClear", required: false }] }], showActions: [{ type: i0.Input, args: [{ isSignal: true, alias: "showActions", 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 }] }], openOnFocus: [{ type: i0.Input, args: [{ isSignal: true, alias: "openOnFocus", 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 }] }], triggerAriaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "triggerAriaLabel", required: false }] }], timeConfig: [{ type: i0.Input, args: [{ isSignal: true, alias: "timeConfig", 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 }] }], presets: [{ type: i0.Input, args: [{ isSignal: true, alias: "presets", 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"] }], dateInput: [{ type: i0.Output, args: ["dateInput"] }], dateChange: [{ type: i0.Output, args: ["dateChange"] }], presetSelected: [{ type: i0.Output, args: ["presetSelected"] }], inputRef: [{ type: i0.ViewChild, args: ['dateInput', { isSignal: true }] }], triggerRef: [{ type: i0.ViewChild, args: ['triggerBtn', { isSignal: true }] }] } });
|
|
1552
|
+
|
|
1553
|
+
/**
|
|
1554
|
+
* Generated bundle index. Do not edit.
|
|
1555
|
+
*/
|
|
1556
|
+
|
|
1557
|
+
export { DatePickerComponent };
|
|
1558
|
+
//# sourceMappingURL=cdevhub-ngx-tw-date-picker.mjs.map
|