@raintonic/formaui 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/CHANGELOG.md +7 -0
- package/README.md +145 -0
- package/fesm2022/raintonic-formaui-cdk-drag-drop.mjs +806 -0
- package/fesm2022/raintonic-formaui-cdk-drag-drop.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-cdk-form-field.mjs +86 -0
- package/fesm2022/raintonic-formaui-cdk-form-field.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-cdk-overlay.mjs +1757 -0
- package/fesm2022/raintonic-formaui-cdk-overlay.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-cdk-virtual-scroll.mjs +287 -0
- package/fesm2022/raintonic-formaui-cdk-virtual-scroll.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-accordion.mjs +217 -0
- package/fesm2022/raintonic-formaui-components-accordion.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-alert.mjs +161 -0
- package/fesm2022/raintonic-formaui-components-alert.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-autocomplete.mjs +726 -0
- package/fesm2022/raintonic-formaui-components-autocomplete.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-avatar.mjs +92 -0
- package/fesm2022/raintonic-formaui-components-avatar.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-badge.mjs +107 -0
- package/fesm2022/raintonic-formaui-components-badge.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-big-menu.mjs +68 -0
- package/fesm2022/raintonic-formaui-components-big-menu.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-breadcrumb.mjs +55 -0
- package/fesm2022/raintonic-formaui-components-breadcrumb.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-button-group.mjs +103 -0
- package/fesm2022/raintonic-formaui-components-button-group.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-button.mjs +241 -0
- package/fesm2022/raintonic-formaui-components-button.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-card.mjs +270 -0
- package/fesm2022/raintonic-formaui-components-card.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-checkbox.mjs +295 -0
- package/fesm2022/raintonic-formaui-components-checkbox.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-data-table.mjs +631 -0
- package/fesm2022/raintonic-formaui-components-data-table.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-date-picker.mjs +1331 -0
- package/fesm2022/raintonic-formaui-components-date-picker.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-divider.mjs +41 -0
- package/fesm2022/raintonic-formaui-components-divider.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-drawer.mjs +190 -0
- package/fesm2022/raintonic-formaui-components-drawer.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-dynamic-form.mjs +266 -0
- package/fesm2022/raintonic-formaui-components-dynamic-form.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-empty-state.mjs +33 -0
- package/fesm2022/raintonic-formaui-components-empty-state.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-file-upload.mjs +246 -0
- package/fesm2022/raintonic-formaui-components-file-upload.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-form-field.mjs +482 -0
- package/fesm2022/raintonic-formaui-components-form-field.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-icon.mjs +117 -0
- package/fesm2022/raintonic-formaui-components-icon.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-input.mjs +327 -0
- package/fesm2022/raintonic-formaui-components-input.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-list.mjs +149 -0
- package/fesm2022/raintonic-formaui-components-list.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-menu.mjs +896 -0
- package/fesm2022/raintonic-formaui-components-menu.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-number-input.mjs +345 -0
- package/fesm2022/raintonic-formaui-components-number-input.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-paginator.mjs +139 -0
- package/fesm2022/raintonic-formaui-components-paginator.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-password-input.mjs +306 -0
- package/fesm2022/raintonic-formaui-components-password-input.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-popover.mjs +451 -0
- package/fesm2022/raintonic-formaui-components-popover.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-progressbar.mjs +148 -0
- package/fesm2022/raintonic-formaui-components-progressbar.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-radio.mjs +260 -0
- package/fesm2022/raintonic-formaui-components-radio.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-select.mjs +1011 -0
- package/fesm2022/raintonic-formaui-components-select.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-side-panel.mjs +150 -0
- package/fesm2022/raintonic-formaui-components-side-panel.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-sidebar.mjs +257 -0
- package/fesm2022/raintonic-formaui-components-sidebar.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-skeleton.mjs +50 -0
- package/fesm2022/raintonic-formaui-components-skeleton.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-slider.mjs +347 -0
- package/fesm2022/raintonic-formaui-components-slider.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-spinner.mjs +63 -0
- package/fesm2022/raintonic-formaui-components-spinner.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-stepper.mjs +317 -0
- package/fesm2022/raintonic-formaui-components-stepper.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-tab.mjs +197 -0
- package/fesm2022/raintonic-formaui-components-tab.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-tag.mjs +78 -0
- package/fesm2022/raintonic-formaui-components-tag.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-time-picker.mjs +644 -0
- package/fesm2022/raintonic-formaui-components-time-picker.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-toggle.mjs +171 -0
- package/fesm2022/raintonic-formaui-components-toggle.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-toolbar.mjs +140 -0
- package/fesm2022/raintonic-formaui-components-toolbar.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-tooltip.mjs +555 -0
- package/fesm2022/raintonic-formaui-components-tooltip.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-tree-select.mjs +314 -0
- package/fesm2022/raintonic-formaui-components-tree-select.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-tree-table.mjs +103 -0
- package/fesm2022/raintonic-formaui-components-tree-table.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-tree.mjs +430 -0
- package/fesm2022/raintonic-formaui-components-tree.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-core.mjs +62 -0
- package/fesm2022/raintonic-formaui-core.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-services-dialog.mjs +798 -0
- package/fesm2022/raintonic-formaui-services-dialog.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-services-notification.mjs +391 -0
- package/fesm2022/raintonic-formaui-services-notification.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-services-theme.mjs +248 -0
- package/fesm2022/raintonic-formaui-services-theme.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-test-utils.mjs +66 -0
- package/fesm2022/raintonic-formaui-test-utils.mjs.map +1 -0
- package/fesm2022/raintonic-formaui.mjs +15 -0
- package/fesm2022/raintonic-formaui.mjs.map +1 -0
- package/llms-full.txt +1627 -0
- package/llms.txt +60 -0
- package/package.json +251 -0
- package/styles/_fonts-entry.scss +3 -0
- package/styles/fonts/dm-mono-400-latin.woff2 +0 -0
- package/styles/fonts/inter-tight-latin-italic.woff2 +0 -0
- package/styles/fonts/inter-tight-latin.woff2 +0 -0
- package/styles/index.scss +127 -0
- package/styles/partials/_constants.scss +29 -0
- package/styles/partials/_fonts.scss +36 -0
- package/styles/partials/_grid.scss +171 -0
- package/styles/partials/_mixins.scss +145 -0
- package/styles/partials/_motion.scss +252 -0
- package/styles/partials/_theme.scss +275 -0
- package/styles/partials/_typography.scss +112 -0
- package/styles/partials/_utilities.scss +480 -0
- package/styles/partials/themes/_dark.scss +254 -0
- package/styles/partials/themes/_light.scss +254 -0
- package/types/raintonic-formaui-cdk-drag-drop.d.ts +196 -0
- package/types/raintonic-formaui-cdk-drag-drop.d.ts.map +1 -0
- package/types/raintonic-formaui-cdk-form-field.d.ts +62 -0
- package/types/raintonic-formaui-cdk-form-field.d.ts.map +1 -0
- package/types/raintonic-formaui-cdk-overlay.d.ts +843 -0
- package/types/raintonic-formaui-cdk-overlay.d.ts.map +1 -0
- package/types/raintonic-formaui-cdk-virtual-scroll.d.ts +112 -0
- package/types/raintonic-formaui-cdk-virtual-scroll.d.ts.map +1 -0
- package/types/raintonic-formaui-components-accordion.d.ts +124 -0
- package/types/raintonic-formaui-components-accordion.d.ts.map +1 -0
- package/types/raintonic-formaui-components-alert.d.ts +143 -0
- package/types/raintonic-formaui-components-alert.d.ts.map +1 -0
- package/types/raintonic-formaui-components-autocomplete.d.ts +193 -0
- package/types/raintonic-formaui-components-autocomplete.d.ts.map +1 -0
- package/types/raintonic-formaui-components-avatar.d.ts +52 -0
- package/types/raintonic-formaui-components-avatar.d.ts.map +1 -0
- package/types/raintonic-formaui-components-badge.d.ts +47 -0
- package/types/raintonic-formaui-components-badge.d.ts.map +1 -0
- package/types/raintonic-formaui-components-big-menu.d.ts +62 -0
- package/types/raintonic-formaui-components-big-menu.d.ts.map +1 -0
- package/types/raintonic-formaui-components-breadcrumb.d.ts +26 -0
- package/types/raintonic-formaui-components-breadcrumb.d.ts.map +1 -0
- package/types/raintonic-formaui-components-button-group.d.ts +61 -0
- package/types/raintonic-formaui-components-button-group.d.ts.map +1 -0
- package/types/raintonic-formaui-components-button.d.ts +116 -0
- package/types/raintonic-formaui-components-button.d.ts.map +1 -0
- package/types/raintonic-formaui-components-card.d.ts +191 -0
- package/types/raintonic-formaui-components-card.d.ts.map +1 -0
- package/types/raintonic-formaui-components-checkbox.d.ts +132 -0
- package/types/raintonic-formaui-components-checkbox.d.ts.map +1 -0
- package/types/raintonic-formaui-components-data-table.d.ts +368 -0
- package/types/raintonic-formaui-components-data-table.d.ts.map +1 -0
- package/types/raintonic-formaui-components-date-picker.d.ts +341 -0
- package/types/raintonic-formaui-components-date-picker.d.ts.map +1 -0
- package/types/raintonic-formaui-components-divider.d.ts +21 -0
- package/types/raintonic-formaui-components-divider.d.ts.map +1 -0
- package/types/raintonic-formaui-components-drawer.d.ts +48 -0
- package/types/raintonic-formaui-components-drawer.d.ts.map +1 -0
- package/types/raintonic-formaui-components-dynamic-form.d.ts +412 -0
- package/types/raintonic-formaui-components-dynamic-form.d.ts.map +1 -0
- package/types/raintonic-formaui-components-empty-state.d.ts +14 -0
- package/types/raintonic-formaui-components-empty-state.d.ts.map +1 -0
- package/types/raintonic-formaui-components-file-upload.d.ts +77 -0
- package/types/raintonic-formaui-components-file-upload.d.ts.map +1 -0
- package/types/raintonic-formaui-components-form-field.d.ts +271 -0
- package/types/raintonic-formaui-components-form-field.d.ts.map +1 -0
- package/types/raintonic-formaui-components-icon.d.ts +61 -0
- package/types/raintonic-formaui-components-icon.d.ts.map +1 -0
- package/types/raintonic-formaui-components-input.d.ts +149 -0
- package/types/raintonic-formaui-components-input.d.ts.map +1 -0
- package/types/raintonic-formaui-components-list.d.ts +48 -0
- package/types/raintonic-formaui-components-list.d.ts.map +1 -0
- package/types/raintonic-formaui-components-menu.d.ts +403 -0
- package/types/raintonic-formaui-components-menu.d.ts.map +1 -0
- package/types/raintonic-formaui-components-number-input.d.ts +127 -0
- package/types/raintonic-formaui-components-number-input.d.ts.map +1 -0
- package/types/raintonic-formaui-components-paginator.d.ts +37 -0
- package/types/raintonic-formaui-components-paginator.d.ts.map +1 -0
- package/types/raintonic-formaui-components-password-input.d.ts +111 -0
- package/types/raintonic-formaui-components-password-input.d.ts.map +1 -0
- package/types/raintonic-formaui-components-popover.d.ts +131 -0
- package/types/raintonic-formaui-components-popover.d.ts.map +1 -0
- package/types/raintonic-formaui-components-progressbar.d.ts +111 -0
- package/types/raintonic-formaui-components-progressbar.d.ts.map +1 -0
- package/types/raintonic-formaui-components-radio.d.ts +95 -0
- package/types/raintonic-formaui-components-radio.d.ts.map +1 -0
- package/types/raintonic-formaui-components-select.d.ts +307 -0
- package/types/raintonic-formaui-components-select.d.ts.map +1 -0
- package/types/raintonic-formaui-components-side-panel.d.ts +51 -0
- package/types/raintonic-formaui-components-side-panel.d.ts.map +1 -0
- package/types/raintonic-formaui-components-sidebar.d.ts +174 -0
- package/types/raintonic-formaui-components-sidebar.d.ts.map +1 -0
- package/types/raintonic-formaui-components-skeleton.d.ts +20 -0
- package/types/raintonic-formaui-components-skeleton.d.ts.map +1 -0
- package/types/raintonic-formaui-components-slider.d.ts +108 -0
- package/types/raintonic-formaui-components-slider.d.ts.map +1 -0
- package/types/raintonic-formaui-components-spinner.d.ts +42 -0
- package/types/raintonic-formaui-components-spinner.d.ts.map +1 -0
- package/types/raintonic-formaui-components-stepper.d.ts +126 -0
- package/types/raintonic-formaui-components-stepper.d.ts.map +1 -0
- package/types/raintonic-formaui-components-tab.d.ts +96 -0
- package/types/raintonic-formaui-components-tab.d.ts.map +1 -0
- package/types/raintonic-formaui-components-tag.d.ts +34 -0
- package/types/raintonic-formaui-components-tag.d.ts.map +1 -0
- package/types/raintonic-formaui-components-time-picker.d.ts +172 -0
- package/types/raintonic-formaui-components-time-picker.d.ts.map +1 -0
- package/types/raintonic-formaui-components-toggle.d.ts +70 -0
- package/types/raintonic-formaui-components-toggle.d.ts.map +1 -0
- package/types/raintonic-formaui-components-toolbar.d.ts +128 -0
- package/types/raintonic-formaui-components-toolbar.d.ts.map +1 -0
- package/types/raintonic-formaui-components-tooltip.d.ts +268 -0
- package/types/raintonic-formaui-components-tooltip.d.ts.map +1 -0
- package/types/raintonic-formaui-components-tree-select.d.ts +80 -0
- package/types/raintonic-formaui-components-tree-select.d.ts.map +1 -0
- package/types/raintonic-formaui-components-tree-table.d.ts +90 -0
- package/types/raintonic-formaui-components-tree-table.d.ts.map +1 -0
- package/types/raintonic-formaui-components-tree.d.ts +104 -0
- package/types/raintonic-formaui-components-tree.d.ts.map +1 -0
- package/types/raintonic-formaui-core.d.ts +115 -0
- package/types/raintonic-formaui-core.d.ts.map +1 -0
- package/types/raintonic-formaui-services-dialog.d.ts +451 -0
- package/types/raintonic-formaui-services-dialog.d.ts.map +1 -0
- package/types/raintonic-formaui-services-notification.d.ts +221 -0
- package/types/raintonic-formaui-services-notification.d.ts.map +1 -0
- package/types/raintonic-formaui-services-theme.d.ts +126 -0
- package/types/raintonic-formaui-services-theme.d.ts.map +1 -0
- package/types/raintonic-formaui-test-utils.d.ts +24 -0
- package/types/raintonic-formaui-test-utils.d.ts.map +1 -0
- package/types/raintonic-formaui.d.ts +4 -0
- package/types/raintonic-formaui.d.ts.map +1 -0
|
@@ -0,0 +1,1331 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { input, output, signal, computed, inject, ElementRef, ViewEncapsulation, ChangeDetectionStrategy, Component, NgZone, effect, ViewChild } from '@angular/core';
|
|
3
|
+
import { NgForm, FormGroupDirective, NG_VALUE_ACCESSOR, NG_VALIDATORS } from '@angular/forms';
|
|
4
|
+
import { DOCUMENT } from '@angular/common';
|
|
5
|
+
import { Subject, Subscription, fromEvent } from 'rxjs';
|
|
6
|
+
import { filter } from 'rxjs/operators';
|
|
7
|
+
import { injectNgControl, updateErrorState, syncRequiredState, syncNgControlDisabled } from '@raintonic/formaui/cdk/form-field';
|
|
8
|
+
import { DefaultErrorStateMatcher, FUI_FORM_FIELD_CONTROL } from '@raintonic/formaui/core';
|
|
9
|
+
import { FuiOverlayService } from '@raintonic/formaui/cdk/overlay';
|
|
10
|
+
import { FuiIconComponent } from '@raintonic/formaui/components/icon';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Normalize a date to noon to avoid DST edge cases.
|
|
14
|
+
*/
|
|
15
|
+
function normalizeDate(date) {
|
|
16
|
+
const d = new Date(date.getFullYear(), date.getMonth(), date.getDate(), 12, 0, 0, 0);
|
|
17
|
+
return d;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Check if two dates are the same day (ignoring time).
|
|
21
|
+
*/
|
|
22
|
+
function isSameDay(a, b) {
|
|
23
|
+
if (!a || !b)
|
|
24
|
+
return false;
|
|
25
|
+
return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Check if a date is within [min, max] bounds (day-level).
|
|
29
|
+
*/
|
|
30
|
+
function isDateInRange(date, min, max) {
|
|
31
|
+
const d = normalizeDate(date).getTime();
|
|
32
|
+
if (min && normalizeDate(min).getTime() > d)
|
|
33
|
+
return false;
|
|
34
|
+
if (max && normalizeDate(max).getTime() < d)
|
|
35
|
+
return false;
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Get number of days in a given month.
|
|
40
|
+
*/
|
|
41
|
+
function getDaysInMonth(year, month) {
|
|
42
|
+
return new Date(year, month + 1, 0).getDate();
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Get localized weekday labels.
|
|
46
|
+
* @param firstDayOfWeek 0=Sunday, 1=Monday, etc.
|
|
47
|
+
* @param format 'short' | 'narrow'
|
|
48
|
+
*/
|
|
49
|
+
function getWeekdayLabels(firstDayOfWeek = 1, format = 'narrow') {
|
|
50
|
+
const formatter = new Intl.DateTimeFormat(undefined, { weekday: format });
|
|
51
|
+
const labels = [];
|
|
52
|
+
// Jan 4, 2024 is a Thursday — use a known date to map
|
|
53
|
+
// Instead, iterate from a known Sunday (Jan 7, 2024)
|
|
54
|
+
const sunday = new Date(2024, 0, 7); // Known Sunday
|
|
55
|
+
for (let i = 0; i < 7; i++) {
|
|
56
|
+
const dayIndex = (firstDayOfWeek + i) % 7;
|
|
57
|
+
const date = new Date(sunday);
|
|
58
|
+
date.setDate(sunday.getDate() + dayIndex);
|
|
59
|
+
labels.push(formatter.format(date));
|
|
60
|
+
}
|
|
61
|
+
return labels;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Get localized month label.
|
|
65
|
+
*/
|
|
66
|
+
function getMonthLabel(year, month, format = 'long') {
|
|
67
|
+
const formatter = new Intl.DateTimeFormat(undefined, { month: format, year: 'numeric' });
|
|
68
|
+
return formatter.format(new Date(year, month, 1));
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Get short month name (for month view grid).
|
|
72
|
+
*/
|
|
73
|
+
function getShortMonthName(month) {
|
|
74
|
+
const formatter = new Intl.DateTimeFormat(undefined, { month: 'short' });
|
|
75
|
+
return formatter.format(new Date(2024, month, 1));
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Build a 6x7 calendar grid for a given month.
|
|
79
|
+
* @returns 6 rows of 7 Date objects each.
|
|
80
|
+
*/
|
|
81
|
+
function buildCalendarGrid(year, month, firstDayOfWeek = 1) {
|
|
82
|
+
const firstOfMonth = new Date(year, month, 1);
|
|
83
|
+
const dayOfWeek = firstOfMonth.getDay();
|
|
84
|
+
// How many days to show from the previous month
|
|
85
|
+
const offset = (dayOfWeek - firstDayOfWeek + 7) % 7;
|
|
86
|
+
const startDate = new Date(year, month, 1 - offset);
|
|
87
|
+
const weeks = [];
|
|
88
|
+
const current = new Date(startDate);
|
|
89
|
+
for (let week = 0; week < 6; week++) {
|
|
90
|
+
const row = [];
|
|
91
|
+
for (let day = 0; day < 7; day++) {
|
|
92
|
+
row.push(normalizeDate(new Date(current)));
|
|
93
|
+
current.setDate(current.getDate() + 1);
|
|
94
|
+
}
|
|
95
|
+
weeks.push(row);
|
|
96
|
+
}
|
|
97
|
+
return weeks;
|
|
98
|
+
}
|
|
99
|
+
// --- Format / Parse ---
|
|
100
|
+
function getSeparator(format) {
|
|
101
|
+
// Find first non-letter character
|
|
102
|
+
const match = /[^dMyy]/.exec(format);
|
|
103
|
+
return match ? match[0] : '/';
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Format a Date to a string using the given format pattern.
|
|
107
|
+
* Supported tokens: dd, d, MM, M, yyyy, yy
|
|
108
|
+
*/
|
|
109
|
+
function formatDate(date, format) {
|
|
110
|
+
if (!date || isNaN(date.getTime()))
|
|
111
|
+
return '';
|
|
112
|
+
const day = date.getDate();
|
|
113
|
+
const month = date.getMonth() + 1;
|
|
114
|
+
const year = date.getFullYear();
|
|
115
|
+
let result = format;
|
|
116
|
+
// Replace in order of longest token first to avoid partial matches
|
|
117
|
+
result = result.replace('dd', String(day).padStart(2, '0'));
|
|
118
|
+
result = result.replace(/\bd\b/, String(day));
|
|
119
|
+
result = result.replace('MM', String(month).padStart(2, '0'));
|
|
120
|
+
result = result.replace(/\bM\b/, String(month));
|
|
121
|
+
result = result.replace('yyyy', String(year).padStart(4, '0'));
|
|
122
|
+
result = result.replace('yy', String(year % 100).padStart(2, '0'));
|
|
123
|
+
return result;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Parse a date string to a Date using the given format.
|
|
127
|
+
* Returns null on failure.
|
|
128
|
+
*/
|
|
129
|
+
function parseDate(text, format) {
|
|
130
|
+
if (!text?.trim())
|
|
131
|
+
return null;
|
|
132
|
+
const sep = getSeparator(format);
|
|
133
|
+
const parts = text.split(sep);
|
|
134
|
+
// Build a map of token → position in split parts
|
|
135
|
+
let day = -1;
|
|
136
|
+
let month = -1;
|
|
137
|
+
let year = -1;
|
|
138
|
+
// Build expected parts from format
|
|
139
|
+
const formatParts = format.split(sep);
|
|
140
|
+
if (parts.length !== formatParts.length)
|
|
141
|
+
return null;
|
|
142
|
+
for (let i = 0; i < formatParts.length; i++) {
|
|
143
|
+
const fp = formatParts[i];
|
|
144
|
+
const val = parseInt(parts[i], 10);
|
|
145
|
+
if (isNaN(val))
|
|
146
|
+
return null;
|
|
147
|
+
if (fp === 'dd' || fp === 'd') {
|
|
148
|
+
day = val;
|
|
149
|
+
}
|
|
150
|
+
else if (fp === 'MM' || fp === 'M') {
|
|
151
|
+
month = val;
|
|
152
|
+
}
|
|
153
|
+
else if (fp === 'yyyy') {
|
|
154
|
+
year = val;
|
|
155
|
+
}
|
|
156
|
+
else if (fp === 'yy') {
|
|
157
|
+
year = val < 50 ? 2000 + val : 1900 + val;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
if (day < 1 || day > 31 || month < 1 || month > 12 || year < 0)
|
|
161
|
+
return null;
|
|
162
|
+
// Validate day for the given month
|
|
163
|
+
const daysInMonth = getDaysInMonth(year, month - 1);
|
|
164
|
+
if (day > daysInMonth)
|
|
165
|
+
return null;
|
|
166
|
+
return normalizeDate(new Date(year, month - 1, day));
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Apply date mask to raw input as user types.
|
|
170
|
+
* Returns the masked value and corrected cursor position.
|
|
171
|
+
*/
|
|
172
|
+
function applyDateMask(rawInput, format) {
|
|
173
|
+
const sep = getSeparator(format);
|
|
174
|
+
const digits = rawInput.replace(/\D/g, '');
|
|
175
|
+
// Build a pattern of digit counts from format tokens
|
|
176
|
+
// e.g., 'dd/MM/yyyy' → [2, 2, 4] with separators at positions 2, 5
|
|
177
|
+
const formatParts = format.split(sep);
|
|
178
|
+
const digitCounts = formatParts.map((p) => p.length);
|
|
179
|
+
const maxDigits = digitCounts.reduce((a, b) => a + b, 0);
|
|
180
|
+
const capped = digits.substring(0, maxDigits);
|
|
181
|
+
let masked = '';
|
|
182
|
+
let digitIndex = 0;
|
|
183
|
+
for (let partIdx = 0; partIdx < digitCounts.length; partIdx++) {
|
|
184
|
+
const count = digitCounts[partIdx];
|
|
185
|
+
const partDigits = capped.substring(digitIndex, digitIndex + count);
|
|
186
|
+
if (partDigits.length === 0)
|
|
187
|
+
break;
|
|
188
|
+
if (partIdx > 0) {
|
|
189
|
+
masked += sep;
|
|
190
|
+
}
|
|
191
|
+
masked += partDigits;
|
|
192
|
+
digitIndex += partDigits.length;
|
|
193
|
+
// Auto-insert separator when part is complete and there are more digits
|
|
194
|
+
if (partDigits.length === count && digitIndex < capped.length && partIdx < digitCounts.length - 1) {
|
|
195
|
+
// separator will be added in next iteration
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return { masked, cursor: masked.length };
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Check if date a is before date b (day-level).
|
|
202
|
+
*/
|
|
203
|
+
function isBefore(a, b) {
|
|
204
|
+
return normalizeDate(a).getTime() < normalizeDate(b).getTime();
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Check if date a is after date b (day-level).
|
|
208
|
+
*/
|
|
209
|
+
function isAfter(a, b) {
|
|
210
|
+
return normalizeDate(a).getTime() > normalizeDate(b).getTime();
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Check if date is between start and end (inclusive, day-level).
|
|
214
|
+
*/
|
|
215
|
+
function isBetween(date, start, end) {
|
|
216
|
+
if (!start || !end)
|
|
217
|
+
return false;
|
|
218
|
+
const d = normalizeDate(date).getTime();
|
|
219
|
+
const s = normalizeDate(start).getTime();
|
|
220
|
+
const e = normalizeDate(end).getTime();
|
|
221
|
+
const min = Math.min(s, e);
|
|
222
|
+
const max = Math.max(s, e);
|
|
223
|
+
return d >= min && d <= max;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* @component FuiCalendarComponent
|
|
228
|
+
* @selector fui-calendar
|
|
229
|
+
* @description Internal calendar grid used by `fui-date-picker`. Renders day, month,
|
|
230
|
+
* and year views with full keyboard navigation (Arrow keys, Home, End, PageUp/Down,
|
|
231
|
+
* Enter, Escape). Supports single-date selection and range highlighting with hover preview.
|
|
232
|
+
*
|
|
233
|
+
* @input selected - Currently selected date for single mode (Date | null)
|
|
234
|
+
* @input rangeStart - Start date of a range selection (Date | null)
|
|
235
|
+
* @input rangeEnd - End date of a range selection (Date | null)
|
|
236
|
+
* @input range - Whether range mode is active (default: false)
|
|
237
|
+
* @input min - Minimum selectable date (Date | null)
|
|
238
|
+
* @input max - Maximum selectable date (Date | null)
|
|
239
|
+
* @input dateFilter - Custom filter function to disable specific dates (DateFilterFn | null)
|
|
240
|
+
* @input startAt - Initial date to display when the calendar opens (Date | null)
|
|
241
|
+
* @input firstDayOfWeek - First day of the week: 0 = Sunday, 1 = Monday (default: 1)
|
|
242
|
+
* @input hoveredDate - Currently hovered date for range preview (Date | null)
|
|
243
|
+
*
|
|
244
|
+
* @output dateSelected - Emits the Date when a day cell is clicked or confirmed via keyboard
|
|
245
|
+
* @output dateHovered - Emits the hovered Date or null on mouse enter/leave
|
|
246
|
+
* @output monthChanged - Emits the new view Date when navigating months/years
|
|
247
|
+
*
|
|
248
|
+
* @example
|
|
249
|
+
* <fui-calendar
|
|
250
|
+
* [selected]="selectedDate"
|
|
251
|
+
* [min]="minDate"
|
|
252
|
+
* [max]="maxDate"
|
|
253
|
+
* (dateSelected)="onDateSelected($event)">
|
|
254
|
+
* </fui-calendar>
|
|
255
|
+
*/
|
|
256
|
+
class FuiCalendarComponent {
|
|
257
|
+
// Inputs
|
|
258
|
+
selected = input(null, ...(ngDevMode ? [{ debugName: "selected" }] : /* istanbul ignore next */ []));
|
|
259
|
+
rangeStart = input(null, ...(ngDevMode ? [{ debugName: "rangeStart" }] : /* istanbul ignore next */ []));
|
|
260
|
+
rangeEnd = input(null, ...(ngDevMode ? [{ debugName: "rangeEnd" }] : /* istanbul ignore next */ []));
|
|
261
|
+
range = input(false, ...(ngDevMode ? [{ debugName: "range" }] : /* istanbul ignore next */ []));
|
|
262
|
+
min = input(null, ...(ngDevMode ? [{ debugName: "min" }] : /* istanbul ignore next */ []));
|
|
263
|
+
max = input(null, ...(ngDevMode ? [{ debugName: "max" }] : /* istanbul ignore next */ []));
|
|
264
|
+
dateFilter = input(null, ...(ngDevMode ? [{ debugName: "dateFilter" }] : /* istanbul ignore next */ []));
|
|
265
|
+
startAt = input(null, ...(ngDevMode ? [{ debugName: "startAt" }] : /* istanbul ignore next */ []));
|
|
266
|
+
firstDayOfWeek = input(1, ...(ngDevMode ? [{ debugName: "firstDayOfWeek" }] : /* istanbul ignore next */ []));
|
|
267
|
+
hoveredDate = input(null, ...(ngDevMode ? [{ debugName: "hoveredDate" }] : /* istanbul ignore next */ []));
|
|
268
|
+
// Outputs
|
|
269
|
+
dateSelected = output();
|
|
270
|
+
dateHovered = output();
|
|
271
|
+
monthChanged = output();
|
|
272
|
+
// Internal state
|
|
273
|
+
currentView = signal('days', ...(ngDevMode ? [{ debugName: "currentView" }] : /* istanbul ignore next */ []));
|
|
274
|
+
viewDate = signal(normalizeDate(new Date()), ...(ngDevMode ? [{ debugName: "viewDate" }] : /* istanbul ignore next */ []));
|
|
275
|
+
activeDate = signal(normalizeDate(new Date()), ...(ngDevMode ? [{ debugName: "activeDate" }] : /* istanbul ignore next */ []));
|
|
276
|
+
// Computed: weekday header labels
|
|
277
|
+
weekDayHeaders = computed(() => getWeekdayLabels(this.firstDayOfWeek(), 'narrow'), ...(ngDevMode ? [{ debugName: "weekDayHeaders" }] : /* istanbul ignore next */ []));
|
|
278
|
+
// Computed: month/year labels for header
|
|
279
|
+
monthYearLabel = computed(() => {
|
|
280
|
+
const d = this.viewDate();
|
|
281
|
+
const formatter = new Intl.DateTimeFormat(undefined, { month: 'long', year: 'numeric' });
|
|
282
|
+
return formatter.format(d);
|
|
283
|
+
}, ...(ngDevMode ? [{ debugName: "monthYearLabel" }] : /* istanbul ignore next */ []));
|
|
284
|
+
yearLabel = computed(() => {
|
|
285
|
+
return String(this.viewDate().getFullYear());
|
|
286
|
+
}, ...(ngDevMode ? [{ debugName: "yearLabel" }] : /* istanbul ignore next */ []));
|
|
287
|
+
decadeLabel = computed(() => {
|
|
288
|
+
const year = this.viewDate().getFullYear();
|
|
289
|
+
const start = year - (year % 12);
|
|
290
|
+
return `${start} \u2013 ${start + 11}`;
|
|
291
|
+
}, ...(ngDevMode ? [{ debugName: "decadeLabel" }] : /* istanbul ignore next */ []));
|
|
292
|
+
// Accessible navigation button labels
|
|
293
|
+
prevButtonLabel = computed(() => {
|
|
294
|
+
switch (this.currentView()) {
|
|
295
|
+
case 'days':
|
|
296
|
+
return 'Previous month';
|
|
297
|
+
case 'months':
|
|
298
|
+
return 'Previous year';
|
|
299
|
+
case 'years':
|
|
300
|
+
return 'Previous decade';
|
|
301
|
+
}
|
|
302
|
+
}, ...(ngDevMode ? [{ debugName: "prevButtonLabel" }] : /* istanbul ignore next */ []));
|
|
303
|
+
nextButtonLabel = computed(() => {
|
|
304
|
+
switch (this.currentView()) {
|
|
305
|
+
case 'days':
|
|
306
|
+
return 'Next month';
|
|
307
|
+
case 'months':
|
|
308
|
+
return 'Next year';
|
|
309
|
+
case 'years':
|
|
310
|
+
return 'Next decade';
|
|
311
|
+
}
|
|
312
|
+
}, ...(ngDevMode ? [{ debugName: "nextButtonLabel" }] : /* istanbul ignore next */ []));
|
|
313
|
+
headerButtonLabel = computed(() => {
|
|
314
|
+
switch (this.currentView()) {
|
|
315
|
+
case 'days':
|
|
316
|
+
return 'Switch to month view';
|
|
317
|
+
case 'months':
|
|
318
|
+
return 'Switch to year view';
|
|
319
|
+
case 'years':
|
|
320
|
+
return this.decadeLabel();
|
|
321
|
+
}
|
|
322
|
+
}, ...(ngDevMode ? [{ debugName: "headerButtonLabel" }] : /* istanbul ignore next */ []));
|
|
323
|
+
// Computed: 6x7 calendar day grid
|
|
324
|
+
weeks = computed(() => {
|
|
325
|
+
const vd = this.viewDate();
|
|
326
|
+
const grid = buildCalendarGrid(vd.getFullYear(), vd.getMonth(), this.firstDayOfWeek());
|
|
327
|
+
const today = normalizeDate(new Date());
|
|
328
|
+
const sel = this.selected();
|
|
329
|
+
const rStart = this.rangeStart();
|
|
330
|
+
const rEnd = this.rangeEnd();
|
|
331
|
+
const hovered = this.hoveredDate();
|
|
332
|
+
const isRange = this.range();
|
|
333
|
+
const minDate = this.min();
|
|
334
|
+
const maxDate = this.max();
|
|
335
|
+
const filter = this.dateFilter();
|
|
336
|
+
return grid.map((week) => week.map((date) => {
|
|
337
|
+
const isCurrentMonth = date.getMonth() === vd.getMonth();
|
|
338
|
+
const isDisabled = !isDateInRange(date, minDate, maxDate) || (filter ? !filter(date) : false);
|
|
339
|
+
const isSelected = !isRange ? isSameDay(date, sel) : isSameDay(date, rStart) || isSameDay(date, rEnd);
|
|
340
|
+
const isRangeStart = isRange && isSameDay(date, rStart);
|
|
341
|
+
const isRangeEnd = isRange && rEnd ? isSameDay(date, rEnd) : false;
|
|
342
|
+
const isInRange = isRange && rStart && rEnd ? isBetween(date, rStart, rEnd) && !isRangeStart && !isRangeEnd : false;
|
|
343
|
+
const isRangePreview = isRange && rStart && !rEnd && hovered
|
|
344
|
+
? isBetween(date, rStart, hovered) && !isSameDay(date, rStart) && !isSameDay(date, hovered)
|
|
345
|
+
: false;
|
|
346
|
+
return {
|
|
347
|
+
date,
|
|
348
|
+
day: date.getDate(),
|
|
349
|
+
isCurrentMonth,
|
|
350
|
+
isToday: isSameDay(date, today),
|
|
351
|
+
isSelected,
|
|
352
|
+
isDisabled,
|
|
353
|
+
isRangeStart,
|
|
354
|
+
isRangeEnd,
|
|
355
|
+
isInRange,
|
|
356
|
+
isRangePreview,
|
|
357
|
+
};
|
|
358
|
+
}));
|
|
359
|
+
}, ...(ngDevMode ? [{ debugName: "weeks" }] : /* istanbul ignore next */ []));
|
|
360
|
+
// Computed: 3x4 months grid
|
|
361
|
+
monthsGrid = computed(() => {
|
|
362
|
+
const vd = this.viewDate();
|
|
363
|
+
const currentYear = vd.getFullYear();
|
|
364
|
+
const now = new Date();
|
|
365
|
+
const sel = this.selected();
|
|
366
|
+
const rStart = this.rangeStart();
|
|
367
|
+
const minDate = this.min();
|
|
368
|
+
const maxDate = this.max();
|
|
369
|
+
const months = [];
|
|
370
|
+
for (let m = 0; m < 12; m++) {
|
|
371
|
+
const isDisabled = this._isMonthDisabled(currentYear, m, minDate, maxDate);
|
|
372
|
+
const isSelected = sel ? sel.getFullYear() === currentYear && sel.getMonth() === m : false;
|
|
373
|
+
months.push({
|
|
374
|
+
month: m,
|
|
375
|
+
label: getShortMonthName(m),
|
|
376
|
+
isCurrentMonth: now.getFullYear() === currentYear && now.getMonth() === m,
|
|
377
|
+
isSelected: isSelected || (rStart ? rStart.getFullYear() === currentYear && rStart.getMonth() === m : false),
|
|
378
|
+
isDisabled,
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
// Arrange in 4 rows of 3
|
|
382
|
+
const grid = [];
|
|
383
|
+
for (let i = 0; i < 12; i += 3) {
|
|
384
|
+
grid.push(months.slice(i, i + 3));
|
|
385
|
+
}
|
|
386
|
+
return grid;
|
|
387
|
+
}, ...(ngDevMode ? [{ debugName: "monthsGrid" }] : /* istanbul ignore next */ []));
|
|
388
|
+
// Computed: 3x4 years grid
|
|
389
|
+
yearsGrid = computed(() => {
|
|
390
|
+
const vd = this.viewDate();
|
|
391
|
+
const baseYear = vd.getFullYear();
|
|
392
|
+
const startYear = baseYear - (baseYear % 12);
|
|
393
|
+
const now = new Date();
|
|
394
|
+
const sel = this.selected();
|
|
395
|
+
const rStart = this.rangeStart();
|
|
396
|
+
const minDate = this.min();
|
|
397
|
+
const maxDate = this.max();
|
|
398
|
+
const years = [];
|
|
399
|
+
for (let i = 0; i < 12; i++) {
|
|
400
|
+
const year = startYear + i;
|
|
401
|
+
const isDisabled = this._isYearDisabled(year, minDate, maxDate);
|
|
402
|
+
const isSelected = sel ? sel.getFullYear() === year : false;
|
|
403
|
+
years.push({
|
|
404
|
+
year,
|
|
405
|
+
isCurrentYear: now.getFullYear() === year,
|
|
406
|
+
isSelected: isSelected || (rStart ? rStart.getFullYear() === year : false),
|
|
407
|
+
isDisabled,
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
const grid = [];
|
|
411
|
+
for (let i = 0; i < 12; i += 3) {
|
|
412
|
+
grid.push(years.slice(i, i + 3));
|
|
413
|
+
}
|
|
414
|
+
return grid;
|
|
415
|
+
}, ...(ngDevMode ? [{ debugName: "yearsGrid" }] : /* istanbul ignore next */ []));
|
|
416
|
+
_elementRef = inject(ElementRef);
|
|
417
|
+
constructor() {
|
|
418
|
+
// Initialize viewDate from startAt or selected or now
|
|
419
|
+
const initial = this.startAt() ?? this.selected() ?? new Date();
|
|
420
|
+
this.viewDate.set(normalizeDate(initial));
|
|
421
|
+
this.activeDate.set(normalizeDate(initial));
|
|
422
|
+
}
|
|
423
|
+
// --- Header actions ---
|
|
424
|
+
onHeaderLabelClick() {
|
|
425
|
+
const view = this.currentView();
|
|
426
|
+
if (view === 'days') {
|
|
427
|
+
this.currentView.set('months');
|
|
428
|
+
}
|
|
429
|
+
else if (view === 'months') {
|
|
430
|
+
this.currentView.set('years');
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
onPrev() {
|
|
434
|
+
const view = this.currentView();
|
|
435
|
+
const vd = this.viewDate();
|
|
436
|
+
if (view === 'days') {
|
|
437
|
+
this.viewDate.set(new Date(vd.getFullYear(), vd.getMonth() - 1, 1, 12));
|
|
438
|
+
}
|
|
439
|
+
else if (view === 'months') {
|
|
440
|
+
this.viewDate.set(new Date(vd.getFullYear() - 1, vd.getMonth(), 1, 12));
|
|
441
|
+
}
|
|
442
|
+
else {
|
|
443
|
+
this.viewDate.set(new Date(vd.getFullYear() - 12, vd.getMonth(), 1, 12));
|
|
444
|
+
}
|
|
445
|
+
this.monthChanged.emit(this.viewDate());
|
|
446
|
+
}
|
|
447
|
+
onNext() {
|
|
448
|
+
const view = this.currentView();
|
|
449
|
+
const vd = this.viewDate();
|
|
450
|
+
if (view === 'days') {
|
|
451
|
+
this.viewDate.set(new Date(vd.getFullYear(), vd.getMonth() + 1, 1, 12));
|
|
452
|
+
}
|
|
453
|
+
else if (view === 'months') {
|
|
454
|
+
this.viewDate.set(new Date(vd.getFullYear() + 1, vd.getMonth(), 1, 12));
|
|
455
|
+
}
|
|
456
|
+
else {
|
|
457
|
+
this.viewDate.set(new Date(vd.getFullYear() + 12, vd.getMonth(), 1, 12));
|
|
458
|
+
}
|
|
459
|
+
this.monthChanged.emit(this.viewDate());
|
|
460
|
+
}
|
|
461
|
+
// --- Day view actions ---
|
|
462
|
+
onDayClick(day) {
|
|
463
|
+
if (day.isDisabled)
|
|
464
|
+
return;
|
|
465
|
+
this.dateSelected.emit(day.date);
|
|
466
|
+
}
|
|
467
|
+
onDayMouseEnter(day) {
|
|
468
|
+
if (day.isDisabled)
|
|
469
|
+
return;
|
|
470
|
+
this.dateHovered.emit(day.date);
|
|
471
|
+
}
|
|
472
|
+
onDayMouseLeave() {
|
|
473
|
+
this.dateHovered.emit(null);
|
|
474
|
+
}
|
|
475
|
+
// --- Month/year view actions ---
|
|
476
|
+
onMonthClick(month) {
|
|
477
|
+
if (month.isDisabled)
|
|
478
|
+
return;
|
|
479
|
+
const vd = this.viewDate();
|
|
480
|
+
this.viewDate.set(new Date(vd.getFullYear(), month.month, 1, 12));
|
|
481
|
+
this.currentView.set('days');
|
|
482
|
+
}
|
|
483
|
+
onYearClick(year) {
|
|
484
|
+
if (year.isDisabled)
|
|
485
|
+
return;
|
|
486
|
+
const vd = this.viewDate();
|
|
487
|
+
this.viewDate.set(new Date(year.year, vd.getMonth(), 1, 12));
|
|
488
|
+
this.currentView.set('months');
|
|
489
|
+
}
|
|
490
|
+
// --- Keyboard navigation ---
|
|
491
|
+
onKeydown(event) {
|
|
492
|
+
const view = this.currentView();
|
|
493
|
+
if (view === 'days') {
|
|
494
|
+
this._handleDayKeydown(event);
|
|
495
|
+
}
|
|
496
|
+
else if (view === 'months') {
|
|
497
|
+
this._handleMonthKeydown(event);
|
|
498
|
+
}
|
|
499
|
+
else {
|
|
500
|
+
this._handleYearKeydown(event);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
_handleDayKeydown(event) {
|
|
504
|
+
const active = this.activeDate();
|
|
505
|
+
let newDate = null;
|
|
506
|
+
switch (event.key) {
|
|
507
|
+
case 'ArrowLeft':
|
|
508
|
+
newDate = new Date(active.getFullYear(), active.getMonth(), active.getDate() - 1, 12);
|
|
509
|
+
break;
|
|
510
|
+
case 'ArrowRight':
|
|
511
|
+
newDate = new Date(active.getFullYear(), active.getMonth(), active.getDate() + 1, 12);
|
|
512
|
+
break;
|
|
513
|
+
case 'ArrowUp':
|
|
514
|
+
newDate = new Date(active.getFullYear(), active.getMonth(), active.getDate() - 7, 12);
|
|
515
|
+
break;
|
|
516
|
+
case 'ArrowDown':
|
|
517
|
+
newDate = new Date(active.getFullYear(), active.getMonth(), active.getDate() + 7, 12);
|
|
518
|
+
break;
|
|
519
|
+
case 'Home':
|
|
520
|
+
newDate = new Date(active.getFullYear(), active.getMonth(), 1, 12);
|
|
521
|
+
break;
|
|
522
|
+
case 'End':
|
|
523
|
+
newDate = new Date(active.getFullYear(), active.getMonth(), getDaysInMonth(active.getFullYear(), active.getMonth()), 12);
|
|
524
|
+
break;
|
|
525
|
+
case 'PageUp':
|
|
526
|
+
if (event.shiftKey) {
|
|
527
|
+
newDate = new Date(active.getFullYear() - 1, active.getMonth(), active.getDate(), 12);
|
|
528
|
+
}
|
|
529
|
+
else {
|
|
530
|
+
newDate = new Date(active.getFullYear(), active.getMonth() - 1, active.getDate(), 12);
|
|
531
|
+
}
|
|
532
|
+
break;
|
|
533
|
+
case 'PageDown':
|
|
534
|
+
if (event.shiftKey) {
|
|
535
|
+
newDate = new Date(active.getFullYear() + 1, active.getMonth(), active.getDate(), 12);
|
|
536
|
+
}
|
|
537
|
+
else {
|
|
538
|
+
newDate = new Date(active.getFullYear(), active.getMonth() + 1, active.getDate(), 12);
|
|
539
|
+
}
|
|
540
|
+
break;
|
|
541
|
+
case 'Enter':
|
|
542
|
+
case ' ':
|
|
543
|
+
event.preventDefault();
|
|
544
|
+
if (isDateInRange(active, this.min(), this.max())) {
|
|
545
|
+
const filter = this.dateFilter();
|
|
546
|
+
if (!filter || filter(active)) {
|
|
547
|
+
this.dateSelected.emit(active);
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
return;
|
|
551
|
+
case 'Escape':
|
|
552
|
+
// Let parent handle
|
|
553
|
+
return;
|
|
554
|
+
default:
|
|
555
|
+
return;
|
|
556
|
+
}
|
|
557
|
+
if (newDate) {
|
|
558
|
+
event.preventDefault();
|
|
559
|
+
this.activeDate.set(normalizeDate(newDate));
|
|
560
|
+
// If the new active date is in a different month, update viewDate
|
|
561
|
+
if (newDate.getMonth() !== this.viewDate().getMonth() ||
|
|
562
|
+
newDate.getFullYear() !== this.viewDate().getFullYear()) {
|
|
563
|
+
this.viewDate.set(new Date(newDate.getFullYear(), newDate.getMonth(), 1, 12));
|
|
564
|
+
this.monthChanged.emit(this.viewDate());
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
_handleMonthKeydown(event) {
|
|
569
|
+
// Simple arrow key handling for month grid (3 columns)
|
|
570
|
+
const vd = this.viewDate();
|
|
571
|
+
let month = vd.getMonth();
|
|
572
|
+
switch (event.key) {
|
|
573
|
+
case 'ArrowLeft':
|
|
574
|
+
month--;
|
|
575
|
+
break;
|
|
576
|
+
case 'ArrowRight':
|
|
577
|
+
month++;
|
|
578
|
+
break;
|
|
579
|
+
case 'ArrowUp':
|
|
580
|
+
month -= 3;
|
|
581
|
+
break;
|
|
582
|
+
case 'ArrowDown':
|
|
583
|
+
month += 3;
|
|
584
|
+
break;
|
|
585
|
+
case 'Enter':
|
|
586
|
+
case ' ':
|
|
587
|
+
event.preventDefault();
|
|
588
|
+
this.viewDate.set(new Date(vd.getFullYear(), month, 1, 12));
|
|
589
|
+
this.currentView.set('days');
|
|
590
|
+
return;
|
|
591
|
+
case 'Escape':
|
|
592
|
+
this.currentView.set('days');
|
|
593
|
+
return;
|
|
594
|
+
default:
|
|
595
|
+
return;
|
|
596
|
+
}
|
|
597
|
+
event.preventDefault();
|
|
598
|
+
if (month < 0) {
|
|
599
|
+
this.viewDate.set(new Date(vd.getFullYear() - 1, 11, 1, 12));
|
|
600
|
+
}
|
|
601
|
+
else if (month > 11) {
|
|
602
|
+
this.viewDate.set(new Date(vd.getFullYear() + 1, 0, 1, 12));
|
|
603
|
+
}
|
|
604
|
+
else {
|
|
605
|
+
this.viewDate.set(new Date(vd.getFullYear(), month, 1, 12));
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
_handleYearKeydown(event) {
|
|
609
|
+
const vd = this.viewDate();
|
|
610
|
+
let year = vd.getFullYear();
|
|
611
|
+
switch (event.key) {
|
|
612
|
+
case 'ArrowLeft':
|
|
613
|
+
year--;
|
|
614
|
+
break;
|
|
615
|
+
case 'ArrowRight':
|
|
616
|
+
year++;
|
|
617
|
+
break;
|
|
618
|
+
case 'ArrowUp':
|
|
619
|
+
year -= 3;
|
|
620
|
+
break;
|
|
621
|
+
case 'ArrowDown':
|
|
622
|
+
year += 3;
|
|
623
|
+
break;
|
|
624
|
+
case 'Enter':
|
|
625
|
+
case ' ':
|
|
626
|
+
event.preventDefault();
|
|
627
|
+
this.viewDate.set(new Date(year, vd.getMonth(), 1, 12));
|
|
628
|
+
this.currentView.set('months');
|
|
629
|
+
return;
|
|
630
|
+
case 'Escape':
|
|
631
|
+
this.currentView.set('months');
|
|
632
|
+
return;
|
|
633
|
+
default:
|
|
634
|
+
return;
|
|
635
|
+
}
|
|
636
|
+
event.preventDefault();
|
|
637
|
+
this.viewDate.set(new Date(year, vd.getMonth(), 1, 12));
|
|
638
|
+
}
|
|
639
|
+
// Track by functions
|
|
640
|
+
trackByWeek(_index, _week) {
|
|
641
|
+
return _index;
|
|
642
|
+
}
|
|
643
|
+
trackByDay(_index, day) {
|
|
644
|
+
return day.date.getTime();
|
|
645
|
+
}
|
|
646
|
+
trackByMonth(_index, month) {
|
|
647
|
+
return month.month;
|
|
648
|
+
}
|
|
649
|
+
trackByYear(_index, year) {
|
|
650
|
+
return year.year;
|
|
651
|
+
}
|
|
652
|
+
trackByRow(_index) {
|
|
653
|
+
return _index;
|
|
654
|
+
}
|
|
655
|
+
// Helper: is entire month disabled?
|
|
656
|
+
_isMonthDisabled(year, month, min, max) {
|
|
657
|
+
if (min) {
|
|
658
|
+
const lastOfMonth = new Date(year, month + 1, 0);
|
|
659
|
+
if (isBefore(lastOfMonth, min) && !isSameDay(lastOfMonth, min))
|
|
660
|
+
return true;
|
|
661
|
+
}
|
|
662
|
+
if (max) {
|
|
663
|
+
const firstOfMonth = new Date(year, month, 1);
|
|
664
|
+
if (isAfter(firstOfMonth, max) && !isSameDay(firstOfMonth, max))
|
|
665
|
+
return true;
|
|
666
|
+
}
|
|
667
|
+
return false;
|
|
668
|
+
}
|
|
669
|
+
// Helper: is entire year disabled?
|
|
670
|
+
_isYearDisabled(year, min, max) {
|
|
671
|
+
if (min && year < min.getFullYear())
|
|
672
|
+
return true;
|
|
673
|
+
if (max && year > max.getFullYear())
|
|
674
|
+
return true;
|
|
675
|
+
return false;
|
|
676
|
+
}
|
|
677
|
+
// Public: focus the calendar for keyboard nav
|
|
678
|
+
focus() {
|
|
679
|
+
this._elementRef.nativeElement.focus();
|
|
680
|
+
}
|
|
681
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: FuiCalendarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
682
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.6", type: FuiCalendarComponent, isStandalone: true, selector: "fui-calendar", inputs: { selected: { classPropertyName: "selected", publicName: "selected", isSignal: true, isRequired: false, transformFunction: null }, rangeStart: { classPropertyName: "rangeStart", publicName: "rangeStart", isSignal: true, isRequired: false, transformFunction: null }, rangeEnd: { classPropertyName: "rangeEnd", publicName: "rangeEnd", isSignal: true, isRequired: false, transformFunction: null }, range: { classPropertyName: "range", publicName: "range", isSignal: true, isRequired: false, transformFunction: null }, min: { classPropertyName: "min", publicName: "min", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null }, dateFilter: { classPropertyName: "dateFilter", publicName: "dateFilter", isSignal: true, isRequired: false, transformFunction: null }, startAt: { classPropertyName: "startAt", publicName: "startAt", isSignal: true, isRequired: false, transformFunction: null }, firstDayOfWeek: { classPropertyName: "firstDayOfWeek", publicName: "firstDayOfWeek", isSignal: true, isRequired: false, transformFunction: null }, hoveredDate: { classPropertyName: "hoveredDate", publicName: "hoveredDate", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { dateSelected: "dateSelected", dateHovered: "dateHovered", monthChanged: "monthChanged" }, host: { attributes: { "tabindex": "0", "role": "application", "aria-label": "Calendar" }, classAttribute: "fui-calendar" }, ngImport: i0, template: "<!-- Calendar Header -->\n<div class=\"fui-calendar__header\">\n <button type=\"button\" class=\"fui-calendar__nav-btn\" (click)=\"onPrev()\" [attr.aria-label]=\"prevButtonLabel()\">\n <fui-icon name=\"caret-left\" size=\"sm\" />\n </button>\n\n <button\n type=\"button\"\n class=\"fui-calendar__header-label\"\n (click)=\"onHeaderLabelClick()\"\n [attr.aria-label]=\"headerButtonLabel()\"\n >\n @switch (currentView()) {\n @case ('days') {\n {{ monthYearLabel() }}\n }\n @case ('months') {\n {{ yearLabel() }}\n }\n @case ('years') {\n {{ decadeLabel() }}\n }\n }\n </button>\n\n <button type=\"button\" class=\"fui-calendar__nav-btn\" (click)=\"onNext()\" [attr.aria-label]=\"nextButtonLabel()\">\n <fui-icon name=\"caret-right\" size=\"sm\" />\n </button>\n</div>\n\n<!-- Day View -->\n@if (currentView() === 'days') {\n <table class=\"fui-calendar__grid\" role=\"grid\" [attr.aria-label]=\"monthYearLabel()\" (keydown)=\"onKeydown($event)\">\n <thead>\n <tr>\n @for (header of weekDayHeaders(); track header) {\n <th class=\"fui-calendar__weekday\" scope=\"col\">{{ header }}</th>\n }\n </tr>\n </thead>\n <tbody>\n @for (week of weeks(); track trackByWeek($index, week)) {\n <tr>\n @for (day of week; track trackByDay($index, day)) {\n <td\n class=\"fui-calendar__cell\"\n [class.--today]=\"day.isToday\"\n [class.--selected]=\"day.isSelected\"\n [class.--outside]=\"!day.isCurrentMonth\"\n [class.--disabled]=\"day.isDisabled\"\n [class.--range-start]=\"day.isRangeStart\"\n [class.--range-end]=\"day.isRangeEnd\"\n [class.--in-range]=\"day.isInRange\"\n [class.--range-preview]=\"day.isRangePreview\"\n role=\"gridcell\"\n [attr.aria-selected]=\"day.isSelected\"\n [attr.aria-disabled]=\"day.isDisabled\"\n [attr.aria-current]=\"day.isToday ? 'date' : null\"\n [attr.tabindex]=\"day.isCurrentMonth && !day.isDisabled ? 0 : -1\"\n (click)=\"onDayClick(day)\"\n (mouseenter)=\"onDayMouseEnter(day)\"\n (mouseleave)=\"onDayMouseLeave()\"\n >\n <span class=\"fui-calendar__day\">{{ day.day }}</span>\n </td>\n }\n </tr>\n }\n </tbody>\n </table>\n}\n\n<!-- Month View -->\n@if (currentView() === 'months') {\n <div class=\"fui-calendar__months\" role=\"grid\" [attr.aria-label]=\"yearLabel()\" (keydown)=\"onKeydown($event)\">\n @for (row of monthsGrid(); track trackByRow($index)) {\n <div class=\"fui-calendar__months-row\" role=\"row\">\n @for (month of row; track trackByMonth($index, month)) {\n <button\n type=\"button\"\n class=\"fui-calendar__month-cell\"\n [class.--current]=\"month.isCurrentMonth\"\n [class.--selected]=\"month.isSelected\"\n [class.--disabled]=\"month.isDisabled\"\n role=\"gridcell\"\n [attr.aria-selected]=\"month.isSelected\"\n [attr.aria-disabled]=\"month.isDisabled\"\n [disabled]=\"month.isDisabled\"\n (click)=\"onMonthClick(month)\"\n >\n {{ month.label }}\n </button>\n }\n </div>\n }\n </div>\n}\n\n<!-- Year View -->\n@if (currentView() === 'years') {\n <div class=\"fui-calendar__years\" role=\"grid\" [attr.aria-label]=\"decadeLabel()\" (keydown)=\"onKeydown($event)\">\n @for (row of yearsGrid(); track trackByRow($index)) {\n <div class=\"fui-calendar__years-row\" role=\"row\">\n @for (year of row; track trackByYear($index, year)) {\n <button\n type=\"button\"\n class=\"fui-calendar__year-cell\"\n [class.--current]=\"year.isCurrentYear\"\n [class.--selected]=\"year.isSelected\"\n [class.--disabled]=\"year.isDisabled\"\n role=\"gridcell\"\n [attr.aria-selected]=\"year.isSelected\"\n [attr.aria-disabled]=\"year.isDisabled\"\n [disabled]=\"year.isDisabled\"\n (click)=\"onYearClick(year)\"\n >\n {{ year.year }}\n </button>\n }\n </div>\n }\n </div>\n}\n\n<!-- Live region for screen readers -->\n<div class=\"fui-calendar__announcer\" aria-live=\"polite\" aria-atomic=\"true\">\n @switch (currentView()) {\n @case ('days') {\n {{ monthYearLabel() }}\n }\n @case ('months') {\n {{ yearLabel() }}\n }\n @case ('years') {\n {{ decadeLabel() }}\n }\n }\n</div>\n", styles: ["@keyframes fui-skeleton-pulse{0%{opacity:1}50%{opacity:.4}to{opacity:1}}@keyframes fui-spin{to{transform:rotate(360deg)}}@keyframes fui-shake{0%,to{transform:translate(0)}10%,30%,50%,70%,90%{transform:translate(-2px)}20%,40%,60%,80%{transform:translate(2px)}}.fui-motion-fade-in{transition:opacity var(--fui-duration-fast-02) var(--fui-ease-entrance) 0ms}.fui-motion-fade-out{transition:opacity var(--fui-duration-fast-01) var(--fui-ease-exit) 0ms}.fui-motion-slide-in-bottom{transition:transform var(--fui-duration-moderate-01) var(--fui-ease-entrance) 0ms;transform:translateY(0)}.fui-motion-slide-in-bottom.fui-motion-entering{transform:translateY(1rem)}.fui-motion-slide-in-top{transition:transform var(--fui-duration-moderate-01) var(--fui-ease-entrance) 0ms;transform:translateY(0)}.fui-motion-slide-in-top.fui-motion-entering{transform:translateY(-1rem)}.fui-motion-scale-in{transition:transform,opacity var(--fui-duration-moderate-01) var(--fui-ease-entrance) 0ms;transform:scale(1);opacity:1}.fui-motion-scale-in.fui-motion-entering{transform:scale(.95);opacity:0}.fui-no-motion{transition:none!important;animation:none!important}@media(prefers-reduced-motion:reduce){*,*:before,*:after{animation-duration:.01ms!important;animation-iteration-count:1!important;transition-duration:.01ms!important}}@keyframes fui-pulse{0%{transform:scale(1);opacity:1}50%{transform:scale(1.05)}to{transform:scale(1);opacity:1}}@keyframes fui-slide-in{0%{transform:translate(120%)}to{transform:translate(0)}}.fui-slide-in{animation:fui-slide-in var(--fui-duration-moderate-01) var(--fui-ease-entrance)}.fui-calendar{display:block;width:280px;-webkit-user-select:none;user-select:none}.fui-calendar__header{display:flex;align-items:center;justify-content:space-between;padding:var(--fui-spacing-02) var(--fui-spacing-03)}.fui-calendar__nav-btn{background:none;border:none;padding:0;margin:0;font:inherit;color:inherit;cursor:pointer;outline:none}.fui-calendar__nav-btn:focus-visible{outline:2px solid var(--fui-primary);outline-offset:2px}.fui-calendar__nav-btn{display:flex;align-items:center;justify-content:center;width:2rem;height:2rem;border-radius:var(--fui-border-radius-sm);color:var(--fui-text-secondary);transition:background-color var(--fui-duration-fast-02) var(--fui-ease-standard) 0ms}.fui-calendar__nav-btn:hover{background-color:var(--fui-surface-02);color:var(--fui-text-primary)}.fui-calendar__nav-btn:focus-visible{outline:2px solid var(--fui-primary);outline-offset:0}.fui-calendar__header-label{background:none;border:none;padding:0;margin:0;font:inherit;color:inherit;cursor:pointer;outline:none}.fui-calendar__header-label:focus-visible{outline:2px solid var(--fui-primary);outline-offset:2px}.fui-calendar__header-label{font-size:var(--fui-font-size-02);font-weight:var(--fui-font-weight-semibold);color:var(--fui-text-primary);padding:var(--fui-spacing-01) var(--fui-spacing-02);border-radius:var(--fui-border-radius-sm);transition:background-color var(--fui-duration-fast-02) var(--fui-ease-standard) 0ms}.fui-calendar__header-label:hover{background-color:var(--fui-surface-02)}.fui-calendar__header-label:focus-visible{outline:2px solid var(--fui-primary);outline-offset:0}.fui-calendar__grid{width:100%;border-collapse:collapse;border-spacing:0;table-layout:fixed;padding:0 var(--fui-spacing-02)}.fui-calendar__weekday{font-size:var(--fui-font-size-01);font-weight:var(--fui-font-weight-semibold);color:var(--fui-text-secondary);text-align:center;padding-bottom:var(--fui-spacing-01);height:2rem;vertical-align:middle}.fui-calendar__cell{text-align:center;padding:1px;cursor:pointer;position:relative;outline:none}.fui-calendar__cell:focus-visible .fui-calendar__day{outline:2px solid var(--fui-primary);outline-offset:0}.fui-calendar__cell.--disabled{cursor:not-allowed}.fui-calendar__cell.--disabled .fui-calendar__day{color:var(--fui-text-disabled);cursor:not-allowed}.fui-calendar__cell.--disabled .fui-calendar__day:hover{background-color:transparent}.fui-calendar__cell.--outside .fui-calendar__day{color:var(--fui-text-disabled);opacity:.4}.fui-calendar__cell.--today .fui-calendar__day{position:relative;font-weight:var(--fui-font-weight-semibold)}.fui-calendar__cell.--today .fui-calendar__day:after{content:\"\";position:absolute;bottom:2px;left:50%;transform:translate(-50%);width:4px;height:4px;border-radius:50%;background-color:var(--fui-primary)}.fui-calendar__cell.--selected .fui-calendar__day{background-color:var(--fui-primary);color:var(--fui-text-on-primary);font-weight:var(--fui-font-weight-semibold)}.fui-calendar__cell.--selected .fui-calendar__day:after{display:none}.fui-calendar__cell.--range-start .fui-calendar__day,.fui-calendar__cell.--range-end .fui-calendar__day{background-color:var(--fui-primary);color:var(--fui-text-on-primary);font-weight:var(--fui-font-weight-semibold)}.fui-calendar__cell.--range-start{background:linear-gradient(to right,transparent 50%,var(--fui-primary-10) 50%)}.fui-calendar__cell.--range-end{background:linear-gradient(to left,transparent 50%,var(--fui-primary-10) 50%)}.fui-calendar__cell.--range-start.--range-end{background:transparent}.fui-calendar__cell.--in-range{background-color:var(--fui-primary-10)}.fui-calendar__cell.--in-range .fui-calendar__day{color:var(--fui-primary);font-weight:var(--fui-font-weight-medium)}.fui-calendar__cell.--range-preview{background-color:var(--fui-primary-10);opacity:.5}.fui-calendar__cell.--range-preview .fui-calendar__day{color:var(--fui-primary)}.fui-calendar__day{display:flex;align-items:center;justify-content:center;width:2rem;height:2rem;border-radius:50%;font-size:var(--fui-font-size-01);color:var(--fui-text-primary);margin:0 auto;transition:background-color var(--fui-duration-fast-02) var(--fui-ease-standard) 0ms}.fui-calendar__day:hover{background-color:var(--fui-surface-02)}.fui-calendar__months,.fui-calendar__years{padding:var(--fui-spacing-03)}.fui-calendar__months-row,.fui-calendar__years-row{display:flex;justify-content:space-around;margin-bottom:var(--fui-spacing-02)}.fui-calendar__months-row:last-child,.fui-calendar__years-row:last-child{margin-bottom:0}.fui-calendar__month-cell,.fui-calendar__year-cell{background:none;border:none;padding:0;margin:0;font:inherit;color:inherit;cursor:pointer;outline:none}.fui-calendar__month-cell:focus-visible,.fui-calendar__year-cell:focus-visible{outline:2px solid var(--fui-primary);outline-offset:2px}.fui-calendar__month-cell,.fui-calendar__year-cell{display:flex;align-items:center;justify-content:center;width:5rem;height:2.5rem;border-radius:var(--fui-border-radius-md);font-size:var(--fui-font-size-01);color:var(--fui-text-primary);transition:background-color var(--fui-duration-fast-02) var(--fui-ease-standard) 0ms}.fui-calendar__month-cell:hover:not(:disabled),.fui-calendar__year-cell:hover:not(:disabled){background-color:var(--fui-surface-02)}.fui-calendar__month-cell:focus-visible,.fui-calendar__year-cell:focus-visible{outline:2px solid var(--fui-primary);outline-offset:0}.fui-calendar__month-cell.--current,.fui-calendar__year-cell.--current{font-weight:var(--fui-font-weight-semibold);color:var(--fui-primary)}.fui-calendar__month-cell.--selected,.fui-calendar__year-cell.--selected{background-color:var(--fui-primary);color:var(--fui-text-on-primary);font-weight:var(--fui-font-weight-semibold)}.fui-calendar__month-cell.--disabled,.fui-calendar__year-cell.--disabled{color:var(--fui-text-disabled);cursor:not-allowed}.fui-calendar__announcer{position:absolute!important;width:1px!important;height:1px!important;padding:0!important;margin:-1px!important;overflow:hidden!important;clip:rect(0,0,0,0)!important;white-space:nowrap!important;border:0!important}@media(prefers-contrast:high){.fui-calendar__cell.--selected .fui-calendar__day,.fui-calendar__cell.--range-start .fui-calendar__day,.fui-calendar__cell.--range-end .fui-calendar__day{outline:2px solid}}@media(prefers-reduced-motion:reduce){.fui-calendar__day,.fui-calendar__nav-btn,.fui-calendar__header-label,.fui-calendar__month-cell,.fui-calendar__year-cell{transition:none}}\n"], dependencies: [{ kind: "component", type: FuiIconComponent, selector: "fui-icon", inputs: ["name", "size", "weight", "color", "ariaLabel", "spin", "pulse"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
683
|
+
}
|
|
684
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: FuiCalendarComponent, decorators: [{
|
|
685
|
+
type: Component,
|
|
686
|
+
args: [{ selector: 'fui-calendar', standalone: true, imports: [FuiIconComponent], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, host: {
|
|
687
|
+
class: 'fui-calendar',
|
|
688
|
+
tabindex: '0',
|
|
689
|
+
role: 'application',
|
|
690
|
+
'aria-label': 'Calendar',
|
|
691
|
+
}, template: "<!-- Calendar Header -->\n<div class=\"fui-calendar__header\">\n <button type=\"button\" class=\"fui-calendar__nav-btn\" (click)=\"onPrev()\" [attr.aria-label]=\"prevButtonLabel()\">\n <fui-icon name=\"caret-left\" size=\"sm\" />\n </button>\n\n <button\n type=\"button\"\n class=\"fui-calendar__header-label\"\n (click)=\"onHeaderLabelClick()\"\n [attr.aria-label]=\"headerButtonLabel()\"\n >\n @switch (currentView()) {\n @case ('days') {\n {{ monthYearLabel() }}\n }\n @case ('months') {\n {{ yearLabel() }}\n }\n @case ('years') {\n {{ decadeLabel() }}\n }\n }\n </button>\n\n <button type=\"button\" class=\"fui-calendar__nav-btn\" (click)=\"onNext()\" [attr.aria-label]=\"nextButtonLabel()\">\n <fui-icon name=\"caret-right\" size=\"sm\" />\n </button>\n</div>\n\n<!-- Day View -->\n@if (currentView() === 'days') {\n <table class=\"fui-calendar__grid\" role=\"grid\" [attr.aria-label]=\"monthYearLabel()\" (keydown)=\"onKeydown($event)\">\n <thead>\n <tr>\n @for (header of weekDayHeaders(); track header) {\n <th class=\"fui-calendar__weekday\" scope=\"col\">{{ header }}</th>\n }\n </tr>\n </thead>\n <tbody>\n @for (week of weeks(); track trackByWeek($index, week)) {\n <tr>\n @for (day of week; track trackByDay($index, day)) {\n <td\n class=\"fui-calendar__cell\"\n [class.--today]=\"day.isToday\"\n [class.--selected]=\"day.isSelected\"\n [class.--outside]=\"!day.isCurrentMonth\"\n [class.--disabled]=\"day.isDisabled\"\n [class.--range-start]=\"day.isRangeStart\"\n [class.--range-end]=\"day.isRangeEnd\"\n [class.--in-range]=\"day.isInRange\"\n [class.--range-preview]=\"day.isRangePreview\"\n role=\"gridcell\"\n [attr.aria-selected]=\"day.isSelected\"\n [attr.aria-disabled]=\"day.isDisabled\"\n [attr.aria-current]=\"day.isToday ? 'date' : null\"\n [attr.tabindex]=\"day.isCurrentMonth && !day.isDisabled ? 0 : -1\"\n (click)=\"onDayClick(day)\"\n (mouseenter)=\"onDayMouseEnter(day)\"\n (mouseleave)=\"onDayMouseLeave()\"\n >\n <span class=\"fui-calendar__day\">{{ day.day }}</span>\n </td>\n }\n </tr>\n }\n </tbody>\n </table>\n}\n\n<!-- Month View -->\n@if (currentView() === 'months') {\n <div class=\"fui-calendar__months\" role=\"grid\" [attr.aria-label]=\"yearLabel()\" (keydown)=\"onKeydown($event)\">\n @for (row of monthsGrid(); track trackByRow($index)) {\n <div class=\"fui-calendar__months-row\" role=\"row\">\n @for (month of row; track trackByMonth($index, month)) {\n <button\n type=\"button\"\n class=\"fui-calendar__month-cell\"\n [class.--current]=\"month.isCurrentMonth\"\n [class.--selected]=\"month.isSelected\"\n [class.--disabled]=\"month.isDisabled\"\n role=\"gridcell\"\n [attr.aria-selected]=\"month.isSelected\"\n [attr.aria-disabled]=\"month.isDisabled\"\n [disabled]=\"month.isDisabled\"\n (click)=\"onMonthClick(month)\"\n >\n {{ month.label }}\n </button>\n }\n </div>\n }\n </div>\n}\n\n<!-- Year View -->\n@if (currentView() === 'years') {\n <div class=\"fui-calendar__years\" role=\"grid\" [attr.aria-label]=\"decadeLabel()\" (keydown)=\"onKeydown($event)\">\n @for (row of yearsGrid(); track trackByRow($index)) {\n <div class=\"fui-calendar__years-row\" role=\"row\">\n @for (year of row; track trackByYear($index, year)) {\n <button\n type=\"button\"\n class=\"fui-calendar__year-cell\"\n [class.--current]=\"year.isCurrentYear\"\n [class.--selected]=\"year.isSelected\"\n [class.--disabled]=\"year.isDisabled\"\n role=\"gridcell\"\n [attr.aria-selected]=\"year.isSelected\"\n [attr.aria-disabled]=\"year.isDisabled\"\n [disabled]=\"year.isDisabled\"\n (click)=\"onYearClick(year)\"\n >\n {{ year.year }}\n </button>\n }\n </div>\n }\n </div>\n}\n\n<!-- Live region for screen readers -->\n<div class=\"fui-calendar__announcer\" aria-live=\"polite\" aria-atomic=\"true\">\n @switch (currentView()) {\n @case ('days') {\n {{ monthYearLabel() }}\n }\n @case ('months') {\n {{ yearLabel() }}\n }\n @case ('years') {\n {{ decadeLabel() }}\n }\n }\n</div>\n", styles: ["@keyframes fui-skeleton-pulse{0%{opacity:1}50%{opacity:.4}to{opacity:1}}@keyframes fui-spin{to{transform:rotate(360deg)}}@keyframes fui-shake{0%,to{transform:translate(0)}10%,30%,50%,70%,90%{transform:translate(-2px)}20%,40%,60%,80%{transform:translate(2px)}}.fui-motion-fade-in{transition:opacity var(--fui-duration-fast-02) var(--fui-ease-entrance) 0ms}.fui-motion-fade-out{transition:opacity var(--fui-duration-fast-01) var(--fui-ease-exit) 0ms}.fui-motion-slide-in-bottom{transition:transform var(--fui-duration-moderate-01) var(--fui-ease-entrance) 0ms;transform:translateY(0)}.fui-motion-slide-in-bottom.fui-motion-entering{transform:translateY(1rem)}.fui-motion-slide-in-top{transition:transform var(--fui-duration-moderate-01) var(--fui-ease-entrance) 0ms;transform:translateY(0)}.fui-motion-slide-in-top.fui-motion-entering{transform:translateY(-1rem)}.fui-motion-scale-in{transition:transform,opacity var(--fui-duration-moderate-01) var(--fui-ease-entrance) 0ms;transform:scale(1);opacity:1}.fui-motion-scale-in.fui-motion-entering{transform:scale(.95);opacity:0}.fui-no-motion{transition:none!important;animation:none!important}@media(prefers-reduced-motion:reduce){*,*:before,*:after{animation-duration:.01ms!important;animation-iteration-count:1!important;transition-duration:.01ms!important}}@keyframes fui-pulse{0%{transform:scale(1);opacity:1}50%{transform:scale(1.05)}to{transform:scale(1);opacity:1}}@keyframes fui-slide-in{0%{transform:translate(120%)}to{transform:translate(0)}}.fui-slide-in{animation:fui-slide-in var(--fui-duration-moderate-01) var(--fui-ease-entrance)}.fui-calendar{display:block;width:280px;-webkit-user-select:none;user-select:none}.fui-calendar__header{display:flex;align-items:center;justify-content:space-between;padding:var(--fui-spacing-02) var(--fui-spacing-03)}.fui-calendar__nav-btn{background:none;border:none;padding:0;margin:0;font:inherit;color:inherit;cursor:pointer;outline:none}.fui-calendar__nav-btn:focus-visible{outline:2px solid var(--fui-primary);outline-offset:2px}.fui-calendar__nav-btn{display:flex;align-items:center;justify-content:center;width:2rem;height:2rem;border-radius:var(--fui-border-radius-sm);color:var(--fui-text-secondary);transition:background-color var(--fui-duration-fast-02) var(--fui-ease-standard) 0ms}.fui-calendar__nav-btn:hover{background-color:var(--fui-surface-02);color:var(--fui-text-primary)}.fui-calendar__nav-btn:focus-visible{outline:2px solid var(--fui-primary);outline-offset:0}.fui-calendar__header-label{background:none;border:none;padding:0;margin:0;font:inherit;color:inherit;cursor:pointer;outline:none}.fui-calendar__header-label:focus-visible{outline:2px solid var(--fui-primary);outline-offset:2px}.fui-calendar__header-label{font-size:var(--fui-font-size-02);font-weight:var(--fui-font-weight-semibold);color:var(--fui-text-primary);padding:var(--fui-spacing-01) var(--fui-spacing-02);border-radius:var(--fui-border-radius-sm);transition:background-color var(--fui-duration-fast-02) var(--fui-ease-standard) 0ms}.fui-calendar__header-label:hover{background-color:var(--fui-surface-02)}.fui-calendar__header-label:focus-visible{outline:2px solid var(--fui-primary);outline-offset:0}.fui-calendar__grid{width:100%;border-collapse:collapse;border-spacing:0;table-layout:fixed;padding:0 var(--fui-spacing-02)}.fui-calendar__weekday{font-size:var(--fui-font-size-01);font-weight:var(--fui-font-weight-semibold);color:var(--fui-text-secondary);text-align:center;padding-bottom:var(--fui-spacing-01);height:2rem;vertical-align:middle}.fui-calendar__cell{text-align:center;padding:1px;cursor:pointer;position:relative;outline:none}.fui-calendar__cell:focus-visible .fui-calendar__day{outline:2px solid var(--fui-primary);outline-offset:0}.fui-calendar__cell.--disabled{cursor:not-allowed}.fui-calendar__cell.--disabled .fui-calendar__day{color:var(--fui-text-disabled);cursor:not-allowed}.fui-calendar__cell.--disabled .fui-calendar__day:hover{background-color:transparent}.fui-calendar__cell.--outside .fui-calendar__day{color:var(--fui-text-disabled);opacity:.4}.fui-calendar__cell.--today .fui-calendar__day{position:relative;font-weight:var(--fui-font-weight-semibold)}.fui-calendar__cell.--today .fui-calendar__day:after{content:\"\";position:absolute;bottom:2px;left:50%;transform:translate(-50%);width:4px;height:4px;border-radius:50%;background-color:var(--fui-primary)}.fui-calendar__cell.--selected .fui-calendar__day{background-color:var(--fui-primary);color:var(--fui-text-on-primary);font-weight:var(--fui-font-weight-semibold)}.fui-calendar__cell.--selected .fui-calendar__day:after{display:none}.fui-calendar__cell.--range-start .fui-calendar__day,.fui-calendar__cell.--range-end .fui-calendar__day{background-color:var(--fui-primary);color:var(--fui-text-on-primary);font-weight:var(--fui-font-weight-semibold)}.fui-calendar__cell.--range-start{background:linear-gradient(to right,transparent 50%,var(--fui-primary-10) 50%)}.fui-calendar__cell.--range-end{background:linear-gradient(to left,transparent 50%,var(--fui-primary-10) 50%)}.fui-calendar__cell.--range-start.--range-end{background:transparent}.fui-calendar__cell.--in-range{background-color:var(--fui-primary-10)}.fui-calendar__cell.--in-range .fui-calendar__day{color:var(--fui-primary);font-weight:var(--fui-font-weight-medium)}.fui-calendar__cell.--range-preview{background-color:var(--fui-primary-10);opacity:.5}.fui-calendar__cell.--range-preview .fui-calendar__day{color:var(--fui-primary)}.fui-calendar__day{display:flex;align-items:center;justify-content:center;width:2rem;height:2rem;border-radius:50%;font-size:var(--fui-font-size-01);color:var(--fui-text-primary);margin:0 auto;transition:background-color var(--fui-duration-fast-02) var(--fui-ease-standard) 0ms}.fui-calendar__day:hover{background-color:var(--fui-surface-02)}.fui-calendar__months,.fui-calendar__years{padding:var(--fui-spacing-03)}.fui-calendar__months-row,.fui-calendar__years-row{display:flex;justify-content:space-around;margin-bottom:var(--fui-spacing-02)}.fui-calendar__months-row:last-child,.fui-calendar__years-row:last-child{margin-bottom:0}.fui-calendar__month-cell,.fui-calendar__year-cell{background:none;border:none;padding:0;margin:0;font:inherit;color:inherit;cursor:pointer;outline:none}.fui-calendar__month-cell:focus-visible,.fui-calendar__year-cell:focus-visible{outline:2px solid var(--fui-primary);outline-offset:2px}.fui-calendar__month-cell,.fui-calendar__year-cell{display:flex;align-items:center;justify-content:center;width:5rem;height:2.5rem;border-radius:var(--fui-border-radius-md);font-size:var(--fui-font-size-01);color:var(--fui-text-primary);transition:background-color var(--fui-duration-fast-02) var(--fui-ease-standard) 0ms}.fui-calendar__month-cell:hover:not(:disabled),.fui-calendar__year-cell:hover:not(:disabled){background-color:var(--fui-surface-02)}.fui-calendar__month-cell:focus-visible,.fui-calendar__year-cell:focus-visible{outline:2px solid var(--fui-primary);outline-offset:0}.fui-calendar__month-cell.--current,.fui-calendar__year-cell.--current{font-weight:var(--fui-font-weight-semibold);color:var(--fui-primary)}.fui-calendar__month-cell.--selected,.fui-calendar__year-cell.--selected{background-color:var(--fui-primary);color:var(--fui-text-on-primary);font-weight:var(--fui-font-weight-semibold)}.fui-calendar__month-cell.--disabled,.fui-calendar__year-cell.--disabled{color:var(--fui-text-disabled);cursor:not-allowed}.fui-calendar__announcer{position:absolute!important;width:1px!important;height:1px!important;padding:0!important;margin:-1px!important;overflow:hidden!important;clip:rect(0,0,0,0)!important;white-space:nowrap!important;border:0!important}@media(prefers-contrast:high){.fui-calendar__cell.--selected .fui-calendar__day,.fui-calendar__cell.--range-start .fui-calendar__day,.fui-calendar__cell.--range-end .fui-calendar__day{outline:2px solid}}@media(prefers-reduced-motion:reduce){.fui-calendar__day,.fui-calendar__nav-btn,.fui-calendar__header-label,.fui-calendar__month-cell,.fui-calendar__year-cell{transition:none}}\n"] }]
|
|
692
|
+
}], ctorParameters: () => [], propDecorators: { selected: [{ type: i0.Input, args: [{ isSignal: true, alias: "selected", required: false }] }], rangeStart: [{ type: i0.Input, args: [{ isSignal: true, alias: "rangeStart", required: false }] }], rangeEnd: [{ type: i0.Input, args: [{ isSignal: true, alias: "rangeEnd", required: false }] }], range: [{ type: i0.Input, args: [{ isSignal: true, alias: "range", required: false }] }], min: [{ type: i0.Input, args: [{ isSignal: true, alias: "min", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], dateFilter: [{ type: i0.Input, args: [{ isSignal: true, alias: "dateFilter", required: false }] }], startAt: [{ type: i0.Input, args: [{ isSignal: true, alias: "startAt", required: false }] }], firstDayOfWeek: [{ type: i0.Input, args: [{ isSignal: true, alias: "firstDayOfWeek", required: false }] }], hoveredDate: [{ type: i0.Input, args: [{ isSignal: true, alias: "hoveredDate", required: false }] }], dateSelected: [{ type: i0.Output, args: ["dateSelected"] }], dateHovered: [{ type: i0.Output, args: ["dateHovered"] }], monthChanged: [{ type: i0.Output, args: ["monthChanged"] }] } });
|
|
693
|
+
|
|
694
|
+
const DEFAULT_DATE_FORMAT = 'dd/MM/yyyy';
|
|
695
|
+
const RANGE_SEPARATOR = ' \u2014 ';
|
|
696
|
+
|
|
697
|
+
/**
|
|
698
|
+
* @component FuiDatePickerComponent
|
|
699
|
+
* @selector fui-date-picker
|
|
700
|
+
* @description A date picker form control with inline text input, calendar overlay panel,
|
|
701
|
+
* and optional range selection. Integrates with `fui-form-field` via `FuiFormFieldControl`
|
|
702
|
+
* and supports Reactive Forms (ControlValueAccessor) plus built-in Validator for min/max/filter
|
|
703
|
+
* constraints. Uses the overlay service for proper dropdown positioning.
|
|
704
|
+
*
|
|
705
|
+
* @input placeholder - Placeholder text for the date input (alias: 'placeholder')
|
|
706
|
+
* @input disabled - Whether the picker is disabled (alias: 'disabled')
|
|
707
|
+
* @input readonly - Whether the picker is read-only
|
|
708
|
+
* @input range - Enables date-range mode with start/end inputs (default: false)
|
|
709
|
+
* @input format - Display format string (default: 'DD/MM/YYYY')
|
|
710
|
+
* @input min - Minimum selectable date (Date | null)
|
|
711
|
+
* @input max - Maximum selectable date (Date | null)
|
|
712
|
+
* @input dateFilter - Custom filter function to disable specific dates (DateFilterFn | null)
|
|
713
|
+
* @input startAt - Initial calendar view date when opened (Date | null)
|
|
714
|
+
* @input firstDayOfWeek - First day of the week: 0 = Sunday, 1 = Monday (default: 1)
|
|
715
|
+
* @input rangeSeparator - Separator string between range start/end display (default: RANGE_SEPARATOR)
|
|
716
|
+
* @input errorStateMatcher - Custom error state matcher (ErrorStateMatcher | null)
|
|
717
|
+
*
|
|
718
|
+
* @output valueChange - Emits the new model value on change (Date | DateRange | null)
|
|
719
|
+
* @output dateChange - Emits the new model value on date selection (Date | DateRange | null)
|
|
720
|
+
* @output openedChange - Emits boolean when the calendar panel opens or closes
|
|
721
|
+
*
|
|
722
|
+
* @cssvar --fui-date-picker-font-size - Font size of the input text
|
|
723
|
+
* @cssvar --fui-date-picker-panel-border-radius - Border radius of the calendar panel
|
|
724
|
+
* @cssvar --fui-date-picker-panel-shadow - Box shadow of the calendar panel
|
|
725
|
+
* @cssvar --fui-date-picker-panel-bg - Background color of the calendar panel
|
|
726
|
+
* @cssvar --fui-date-picker-panel-border-color - Border color of the calendar panel
|
|
727
|
+
* @cssvar --fui-date-picker-toggle-size - Size of the calendar toggle icon button
|
|
728
|
+
*
|
|
729
|
+
* @example
|
|
730
|
+
* <fui-form-field>
|
|
731
|
+
* <fui-label>Birth date</fui-label>
|
|
732
|
+
* <fui-date-picker formControlName="birthDate" placeholder="DD/MM/YYYY"></fui-date-picker>
|
|
733
|
+
* </fui-form-field>
|
|
734
|
+
*
|
|
735
|
+
* @example
|
|
736
|
+
* <fui-form-field>
|
|
737
|
+
* <fui-label>Stay period</fui-label>
|
|
738
|
+
* <fui-date-picker [range]="true" formControlName="period"></fui-date-picker>
|
|
739
|
+
* </fui-form-field>
|
|
740
|
+
*/
|
|
741
|
+
class FuiDatePickerComponent {
|
|
742
|
+
static nextId = 0;
|
|
743
|
+
controlType = 'fui-date-picker';
|
|
744
|
+
// Inputs
|
|
745
|
+
placeholderInput = input('', { ...(ngDevMode ? { debugName: "placeholderInput" } : /* istanbul ignore next */ {}), alias: 'placeholder' });
|
|
746
|
+
disabledInput = input(false, { ...(ngDevMode ? { debugName: "disabledInput" } : /* istanbul ignore next */ {}), alias: 'disabled' });
|
|
747
|
+
readonly = input(false, ...(ngDevMode ? [{ debugName: "readonly" }] : /* istanbul ignore next */ []));
|
|
748
|
+
range = input(false, ...(ngDevMode ? [{ debugName: "range" }] : /* istanbul ignore next */ []));
|
|
749
|
+
format = input(DEFAULT_DATE_FORMAT, ...(ngDevMode ? [{ debugName: "format" }] : /* istanbul ignore next */ []));
|
|
750
|
+
min = input(null, ...(ngDevMode ? [{ debugName: "min" }] : /* istanbul ignore next */ []));
|
|
751
|
+
max = input(null, ...(ngDevMode ? [{ debugName: "max" }] : /* istanbul ignore next */ []));
|
|
752
|
+
dateFilter = input(null, ...(ngDevMode ? [{ debugName: "dateFilter" }] : /* istanbul ignore next */ []));
|
|
753
|
+
startAt = input(null, ...(ngDevMode ? [{ debugName: "startAt" }] : /* istanbul ignore next */ []));
|
|
754
|
+
firstDayOfWeek = input(1, ...(ngDevMode ? [{ debugName: "firstDayOfWeek" }] : /* istanbul ignore next */ []));
|
|
755
|
+
rangeSeparator = input(RANGE_SEPARATOR, ...(ngDevMode ? [{ debugName: "rangeSeparator" }] : /* istanbul ignore next */ []));
|
|
756
|
+
errorStateMatcher = input(null, ...(ngDevMode ? [{ debugName: "errorStateMatcher" }] : /* istanbul ignore next */ []));
|
|
757
|
+
// Outputs
|
|
758
|
+
valueChange = output();
|
|
759
|
+
dateChange = output();
|
|
760
|
+
openedChange = output();
|
|
761
|
+
// Internal state
|
|
762
|
+
_value = signal(null, ...(ngDevMode ? [{ debugName: "_value" }] : /* istanbul ignore next */ []));
|
|
763
|
+
_focused = signal(false, ...(ngDevMode ? [{ debugName: "_focused" }] : /* istanbul ignore next */ []));
|
|
764
|
+
_disabled = signal(false, ...(ngDevMode ? [{ debugName: "_disabled" }] : /* istanbul ignore next */ []));
|
|
765
|
+
_ngControlDisabled = signal(false, ...(ngDevMode ? [{ debugName: "_ngControlDisabled" }] : /* istanbul ignore next */ []));
|
|
766
|
+
_required = signal(false, ...(ngDevMode ? [{ debugName: "_required" }] : /* istanbul ignore next */ []));
|
|
767
|
+
_errorState = signal(false, ...(ngDevMode ? [{ debugName: "_errorState" }] : /* istanbul ignore next */ []));
|
|
768
|
+
_readOnly = signal(false, ...(ngDevMode ? [{ debugName: "_readOnly" }] : /* istanbul ignore next */ []));
|
|
769
|
+
panelOpen = signal(false, ...(ngDevMode ? [{ debugName: "panelOpen" }] : /* istanbul ignore next */ []));
|
|
770
|
+
// Range hover
|
|
771
|
+
hoveredDate = signal(null, ...(ngDevMode ? [{ debugName: "hoveredDate" }] : /* istanbul ignore next */ []));
|
|
772
|
+
// FuiFormFieldControl
|
|
773
|
+
stateChanges = new Subject();
|
|
774
|
+
_uid = `fui-date-picker-${FuiDatePickerComponent.nextId++}`;
|
|
775
|
+
_ariaDescribedby = null;
|
|
776
|
+
// Form references
|
|
777
|
+
_parentForm = inject(NgForm, { optional: true });
|
|
778
|
+
_parentFormGroup = inject(FormGroupDirective, { optional: true });
|
|
779
|
+
_defaultErrorStateMatcher = inject(DefaultErrorStateMatcher);
|
|
780
|
+
_ngControlRef = injectNgControl();
|
|
781
|
+
get ngControl() {
|
|
782
|
+
return this._ngControlRef.ngControl;
|
|
783
|
+
}
|
|
784
|
+
// Interface signals
|
|
785
|
+
placeholder = computed(() => this.placeholderInput(), ...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
|
|
786
|
+
required = this._required;
|
|
787
|
+
value = this._value;
|
|
788
|
+
focused = this._focused;
|
|
789
|
+
disabled = computed(() => this._disabled() || this.disabledInput() || this._ngControlDisabled(), ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
|
|
790
|
+
errorState = this._errorState;
|
|
791
|
+
id = this._uid;
|
|
792
|
+
empty = computed(() => {
|
|
793
|
+
const val = this._value();
|
|
794
|
+
if (val === null)
|
|
795
|
+
return true;
|
|
796
|
+
if (this.range() && this._isDateRange(val)) {
|
|
797
|
+
return val.start === null && val.end === null;
|
|
798
|
+
}
|
|
799
|
+
return false;
|
|
800
|
+
}, ...(ngDevMode ? [{ debugName: "empty" }] : /* istanbul ignore next */ []));
|
|
801
|
+
// Computed: selected date for single mode calendar input
|
|
802
|
+
selectedDate = computed(() => {
|
|
803
|
+
const val = this._value();
|
|
804
|
+
if (val instanceof Date)
|
|
805
|
+
return val;
|
|
806
|
+
return null;
|
|
807
|
+
}, ...(ngDevMode ? [{ debugName: "selectedDate" }] : /* istanbul ignore next */ []));
|
|
808
|
+
rangeStartDate = computed(() => {
|
|
809
|
+
const val = this._value();
|
|
810
|
+
if (this._isDateRange(val))
|
|
811
|
+
return val.start;
|
|
812
|
+
return null;
|
|
813
|
+
}, ...(ngDevMode ? [{ debugName: "rangeStartDate" }] : /* istanbul ignore next */ []));
|
|
814
|
+
rangeEndDate = computed(() => {
|
|
815
|
+
const val = this._value();
|
|
816
|
+
if (this._isDateRange(val))
|
|
817
|
+
return val.end;
|
|
818
|
+
return null;
|
|
819
|
+
}, ...(ngDevMode ? [{ debugName: "rangeEndDate" }] : /* istanbul ignore next */ []));
|
|
820
|
+
// ViewChildren
|
|
821
|
+
triggerEl;
|
|
822
|
+
calendarPanel;
|
|
823
|
+
startInputEl;
|
|
824
|
+
endInputEl;
|
|
825
|
+
calendarComponent;
|
|
826
|
+
// Overlay
|
|
827
|
+
_overlayRef = null;
|
|
828
|
+
_overlaySubscriptions = new Subscription();
|
|
829
|
+
_overlayService = inject(FuiOverlayService);
|
|
830
|
+
_elementRef = inject(ElementRef);
|
|
831
|
+
_document = inject(DOCUMENT);
|
|
832
|
+
_ngZone = inject(NgZone);
|
|
833
|
+
_outsideClickSub;
|
|
834
|
+
// CVA callbacks
|
|
835
|
+
_onChange = () => {
|
|
836
|
+
/* noop */
|
|
837
|
+
};
|
|
838
|
+
_onTouched = () => {
|
|
839
|
+
/* noop */
|
|
840
|
+
};
|
|
841
|
+
_onValidatorChange = () => {
|
|
842
|
+
/* noop */
|
|
843
|
+
};
|
|
844
|
+
// Input text tracking
|
|
845
|
+
startInputText = signal('', ...(ngDevMode ? [{ debugName: "startInputText" }] : /* istanbul ignore next */ []));
|
|
846
|
+
endInputText = signal('', ...(ngDevMode ? [{ debugName: "endInputText" }] : /* istanbul ignore next */ []));
|
|
847
|
+
// Display value for form-field readOnly mode (duck-typed like select/autocomplete)
|
|
848
|
+
displayValue = computed(() => {
|
|
849
|
+
if (this.range()) {
|
|
850
|
+
const start = this.startInputText();
|
|
851
|
+
const end = this.endInputText();
|
|
852
|
+
if (!start && !end)
|
|
853
|
+
return '';
|
|
854
|
+
return `${start || '—'} ${this.rangeSeparator()} ${end || '—'}`;
|
|
855
|
+
}
|
|
856
|
+
return this.startInputText() || '';
|
|
857
|
+
}, ...(ngDevMode ? [{ debugName: "displayValue" }] : /* istanbul ignore next */ []));
|
|
858
|
+
constructor() {
|
|
859
|
+
void Promise.resolve().then(() => {
|
|
860
|
+
if (this._ngControlRef.ngControl) {
|
|
861
|
+
this._ngControlRef.ngControl.valueAccessor = this;
|
|
862
|
+
}
|
|
863
|
+
});
|
|
864
|
+
// Effect to emit state changes
|
|
865
|
+
effect(() => {
|
|
866
|
+
this.placeholderInput();
|
|
867
|
+
this.readonly();
|
|
868
|
+
this.disabledInput();
|
|
869
|
+
this.range();
|
|
870
|
+
this.format();
|
|
871
|
+
this.errorStateMatcher();
|
|
872
|
+
this._focused();
|
|
873
|
+
this._disabled();
|
|
874
|
+
this._value();
|
|
875
|
+
this._ngControlDisabled();
|
|
876
|
+
this._required();
|
|
877
|
+
this._errorState();
|
|
878
|
+
this.stateChanges.next();
|
|
879
|
+
});
|
|
880
|
+
// Re-trigger validation when min/max/dateFilter change
|
|
881
|
+
effect(() => {
|
|
882
|
+
this.min();
|
|
883
|
+
this.max();
|
|
884
|
+
this.dateFilter();
|
|
885
|
+
this._onValidatorChange();
|
|
886
|
+
});
|
|
887
|
+
// Sync display text when value changes
|
|
888
|
+
effect(() => {
|
|
889
|
+
const val = this._value();
|
|
890
|
+
const fmt = this.format();
|
|
891
|
+
if (this.range() && this._isDateRange(val)) {
|
|
892
|
+
this.startInputText.set(formatDate(val.start, fmt));
|
|
893
|
+
this.endInputText.set(formatDate(val.end, fmt));
|
|
894
|
+
}
|
|
895
|
+
else if (val instanceof Date) {
|
|
896
|
+
this.startInputText.set(formatDate(val, fmt));
|
|
897
|
+
}
|
|
898
|
+
else {
|
|
899
|
+
this.startInputText.set('');
|
|
900
|
+
this.endInputText.set('');
|
|
901
|
+
}
|
|
902
|
+
});
|
|
903
|
+
}
|
|
904
|
+
ngDoCheck() {
|
|
905
|
+
if (this.ngControl) {
|
|
906
|
+
updateErrorState(this.ngControl, this._errorState, this.errorStateMatcher(), this._defaultErrorStateMatcher, this._parentForm, this._parentFormGroup, this.stateChanges);
|
|
907
|
+
syncRequiredState(this.ngControl, this._required, this.stateChanges);
|
|
908
|
+
syncNgControlDisabled(this.ngControl, this._ngControlDisabled, this.stateChanges);
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
ngOnDestroy() {
|
|
912
|
+
this.stateChanges.complete();
|
|
913
|
+
this._outsideClickSub?.unsubscribe();
|
|
914
|
+
this._disposeOverlay();
|
|
915
|
+
}
|
|
916
|
+
// --- CVA ---
|
|
917
|
+
writeValue(value) {
|
|
918
|
+
if (typeof value === 'string') {
|
|
919
|
+
const parsed = parseDate(value, this.format());
|
|
920
|
+
this._value.set(parsed);
|
|
921
|
+
}
|
|
922
|
+
else if (value instanceof Date) {
|
|
923
|
+
this._value.set(normalizeDate(value));
|
|
924
|
+
}
|
|
925
|
+
else if (value && typeof value === 'object' && ('start' in value || 'end' in value)) {
|
|
926
|
+
const range = value;
|
|
927
|
+
this._value.set({
|
|
928
|
+
start: range.start ? normalizeDate(range.start) : null,
|
|
929
|
+
end: range.end ? normalizeDate(range.end) : null,
|
|
930
|
+
});
|
|
931
|
+
}
|
|
932
|
+
else {
|
|
933
|
+
this._value.set(null);
|
|
934
|
+
}
|
|
935
|
+
this.stateChanges.next();
|
|
936
|
+
}
|
|
937
|
+
registerOnChange(fn) {
|
|
938
|
+
this._onChange = fn;
|
|
939
|
+
}
|
|
940
|
+
registerOnTouched(fn) {
|
|
941
|
+
this._onTouched = fn;
|
|
942
|
+
}
|
|
943
|
+
setDisabledState(isDisabled) {
|
|
944
|
+
this._disabled.set(isDisabled);
|
|
945
|
+
this.stateChanges.next();
|
|
946
|
+
}
|
|
947
|
+
// --- Validator ---
|
|
948
|
+
validate(_control) {
|
|
949
|
+
const value = this._value();
|
|
950
|
+
if (value === null)
|
|
951
|
+
return null;
|
|
952
|
+
const minDate = this.min();
|
|
953
|
+
const maxDate = this.max();
|
|
954
|
+
const filter = this.dateFilter();
|
|
955
|
+
if (value instanceof Date) {
|
|
956
|
+
return this._validateSingleDate(value, minDate, maxDate, filter);
|
|
957
|
+
}
|
|
958
|
+
if (this._isDateRange(value)) {
|
|
959
|
+
const errors = {};
|
|
960
|
+
if (value.start) {
|
|
961
|
+
const startErrors = this._validateSingleDate(value.start, minDate, maxDate, filter);
|
|
962
|
+
if (startErrors)
|
|
963
|
+
Object.assign(errors, startErrors);
|
|
964
|
+
}
|
|
965
|
+
if (value.end) {
|
|
966
|
+
const endErrors = this._validateSingleDate(value.end, minDate, maxDate, filter);
|
|
967
|
+
if (endErrors)
|
|
968
|
+
Object.assign(errors, endErrors);
|
|
969
|
+
}
|
|
970
|
+
return Object.keys(errors).length > 0 ? errors : null;
|
|
971
|
+
}
|
|
972
|
+
return null;
|
|
973
|
+
}
|
|
974
|
+
registerOnValidatorChange(fn) {
|
|
975
|
+
this._onValidatorChange = fn;
|
|
976
|
+
}
|
|
977
|
+
_validateSingleDate(date, min, max, filter) {
|
|
978
|
+
if (min && isBefore(date, min)) {
|
|
979
|
+
return { dateMin: { min, actual: date } };
|
|
980
|
+
}
|
|
981
|
+
if (max && isAfter(date, max)) {
|
|
982
|
+
return { dateMax: { max, actual: date } };
|
|
983
|
+
}
|
|
984
|
+
if (filter && !filter(date)) {
|
|
985
|
+
return { dateFilter: { actual: date } };
|
|
986
|
+
}
|
|
987
|
+
return null;
|
|
988
|
+
}
|
|
989
|
+
// --- FuiFormFieldControl ---
|
|
990
|
+
onContainerClick(_event) {
|
|
991
|
+
if (!this.disabled()) {
|
|
992
|
+
this.startInputEl?.nativeElement.focus();
|
|
993
|
+
if (!this.panelOpen()) {
|
|
994
|
+
this.open();
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
setDescribedByIds(ids) {
|
|
999
|
+
this._ariaDescribedby = ids.length ? ids.join(' ') : null;
|
|
1000
|
+
}
|
|
1001
|
+
setReadOnly(readOnly) {
|
|
1002
|
+
this._readOnly.set(readOnly);
|
|
1003
|
+
}
|
|
1004
|
+
// --- Panel open/close ---
|
|
1005
|
+
open() {
|
|
1006
|
+
if (this.disabled() || this.readonly() || this.panelOpen())
|
|
1007
|
+
return;
|
|
1008
|
+
requestAnimationFrame(() => {
|
|
1009
|
+
this.panelOpen.set(true);
|
|
1010
|
+
this._focused.set(true);
|
|
1011
|
+
this.openedChange.emit(true);
|
|
1012
|
+
setTimeout(() => {
|
|
1013
|
+
this._createOverlay();
|
|
1014
|
+
this._listenForOutsideClicks();
|
|
1015
|
+
this.calendarComponent?.focus();
|
|
1016
|
+
});
|
|
1017
|
+
});
|
|
1018
|
+
}
|
|
1019
|
+
close() {
|
|
1020
|
+
if (!this.panelOpen())
|
|
1021
|
+
return;
|
|
1022
|
+
this._outsideClickSub?.unsubscribe();
|
|
1023
|
+
this.panelOpen.set(false);
|
|
1024
|
+
this._focused.set(false);
|
|
1025
|
+
this.hoveredDate.set(null);
|
|
1026
|
+
this._disposeOverlay();
|
|
1027
|
+
this.openedChange.emit(false);
|
|
1028
|
+
this._onTouched();
|
|
1029
|
+
setTimeout(() => {
|
|
1030
|
+
this.startInputEl?.nativeElement.focus();
|
|
1031
|
+
}, 0);
|
|
1032
|
+
}
|
|
1033
|
+
toggle() {
|
|
1034
|
+
if (this.panelOpen()) {
|
|
1035
|
+
this.close();
|
|
1036
|
+
}
|
|
1037
|
+
else {
|
|
1038
|
+
this.open();
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
// --- Trigger click ---
|
|
1042
|
+
onTriggerClick() {
|
|
1043
|
+
if (!this.disabled() && !this.readonly()) {
|
|
1044
|
+
this.toggle();
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
onToggleClick(event) {
|
|
1048
|
+
event.stopPropagation();
|
|
1049
|
+
if (!this.disabled() && !this.readonly()) {
|
|
1050
|
+
this.toggle();
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
// --- Input masking (start input) ---
|
|
1054
|
+
onInput(event) {
|
|
1055
|
+
const input = event.target;
|
|
1056
|
+
const { masked, cursor } = applyDateMask(input.value, this.format());
|
|
1057
|
+
input.value = masked;
|
|
1058
|
+
input.setSelectionRange(cursor, cursor);
|
|
1059
|
+
this.startInputText.set(masked);
|
|
1060
|
+
}
|
|
1061
|
+
onBlur() {
|
|
1062
|
+
const text = this.startInputText();
|
|
1063
|
+
if (!text) {
|
|
1064
|
+
if (!this.range()) {
|
|
1065
|
+
this._setValue(null);
|
|
1066
|
+
}
|
|
1067
|
+
return;
|
|
1068
|
+
}
|
|
1069
|
+
const parsed = parseDate(text, this.format());
|
|
1070
|
+
if (parsed) {
|
|
1071
|
+
if (this.range()) {
|
|
1072
|
+
const current = this._value();
|
|
1073
|
+
const rangeVal = this._isDateRange(current) ? current : { start: null, end: null };
|
|
1074
|
+
this._setValue({ ...rangeVal, start: parsed });
|
|
1075
|
+
}
|
|
1076
|
+
else {
|
|
1077
|
+
this._setValue(parsed);
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
else {
|
|
1081
|
+
// Invalid input — revert to formatted value
|
|
1082
|
+
const val = this._value();
|
|
1083
|
+
if (this.range() && this._isDateRange(val)) {
|
|
1084
|
+
this.startInputText.set(formatDate(val.start, this.format()));
|
|
1085
|
+
}
|
|
1086
|
+
else if (val instanceof Date) {
|
|
1087
|
+
this.startInputText.set(formatDate(val, this.format()));
|
|
1088
|
+
}
|
|
1089
|
+
else {
|
|
1090
|
+
this.startInputText.set('');
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
if (!this.panelOpen()) {
|
|
1094
|
+
this._onTouched();
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
onKeydown(event) {
|
|
1098
|
+
if (event.key === 'Escape' && this.panelOpen()) {
|
|
1099
|
+
event.preventDefault();
|
|
1100
|
+
this.close();
|
|
1101
|
+
}
|
|
1102
|
+
else if ((event.key === 'ArrowDown' || event.key === 'Enter') && !this.panelOpen()) {
|
|
1103
|
+
event.preventDefault();
|
|
1104
|
+
this.open();
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
// --- End input (range mode) ---
|
|
1108
|
+
onEndInput(event) {
|
|
1109
|
+
const input = event.target;
|
|
1110
|
+
const { masked, cursor } = applyDateMask(input.value, this.format());
|
|
1111
|
+
input.value = masked;
|
|
1112
|
+
input.setSelectionRange(cursor, cursor);
|
|
1113
|
+
this.endInputText.set(masked);
|
|
1114
|
+
}
|
|
1115
|
+
onEndBlur() {
|
|
1116
|
+
const text = this.endInputText();
|
|
1117
|
+
if (!text)
|
|
1118
|
+
return;
|
|
1119
|
+
const parsed = parseDate(text, this.format());
|
|
1120
|
+
if (parsed) {
|
|
1121
|
+
const current = this._value();
|
|
1122
|
+
const rangeVal = this._isDateRange(current) ? current : { start: null, end: null };
|
|
1123
|
+
this._setValue({ ...rangeVal, end: parsed });
|
|
1124
|
+
}
|
|
1125
|
+
else {
|
|
1126
|
+
const val = this._value();
|
|
1127
|
+
if (this._isDateRange(val)) {
|
|
1128
|
+
this.endInputText.set(formatDate(val.end, this.format()));
|
|
1129
|
+
}
|
|
1130
|
+
else {
|
|
1131
|
+
this.endInputText.set('');
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
if (!this.panelOpen()) {
|
|
1135
|
+
this._onTouched();
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
onEndKeydown(event) {
|
|
1139
|
+
if (event.key === 'Escape' && this.panelOpen()) {
|
|
1140
|
+
event.preventDefault();
|
|
1141
|
+
this.close();
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
onInputFocus() {
|
|
1145
|
+
this._focused.set(true);
|
|
1146
|
+
}
|
|
1147
|
+
// --- Calendar events ---
|
|
1148
|
+
onDateSelected(date) {
|
|
1149
|
+
if (this.range()) {
|
|
1150
|
+
this._handleRangeSelection(date);
|
|
1151
|
+
}
|
|
1152
|
+
else {
|
|
1153
|
+
this._setValue(date);
|
|
1154
|
+
this.close();
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
onDateHovered(date) {
|
|
1158
|
+
this.hoveredDate.set(date);
|
|
1159
|
+
}
|
|
1160
|
+
// Start listening for outside clicks when panel opens
|
|
1161
|
+
_listenForOutsideClicks() {
|
|
1162
|
+
this._outsideClickSub?.unsubscribe();
|
|
1163
|
+
this._ngZone.runOutsideAngular(() => {
|
|
1164
|
+
setTimeout(() => {
|
|
1165
|
+
this._outsideClickSub = fromEvent(this._document, 'click')
|
|
1166
|
+
.pipe(filter(() => this.panelOpen()), filter((event) => {
|
|
1167
|
+
const target = event.target;
|
|
1168
|
+
const hostElement = this._elementRef.nativeElement;
|
|
1169
|
+
const overlayElement = this._overlayRef?.overlayElement;
|
|
1170
|
+
return !hostElement.contains(target) && !overlayElement?.contains(target);
|
|
1171
|
+
}))
|
|
1172
|
+
.subscribe(() => {
|
|
1173
|
+
this._ngZone.run(() => {
|
|
1174
|
+
this.close();
|
|
1175
|
+
});
|
|
1176
|
+
});
|
|
1177
|
+
});
|
|
1178
|
+
});
|
|
1179
|
+
}
|
|
1180
|
+
// --- Private helpers ---
|
|
1181
|
+
_handleRangeSelection(date) {
|
|
1182
|
+
const current = this._value();
|
|
1183
|
+
const rangeVal = this._isDateRange(current) ? current : { start: null, end: null };
|
|
1184
|
+
if (!rangeVal.start || rangeVal.end) {
|
|
1185
|
+
// First click or resetting
|
|
1186
|
+
this._setValue({ start: date, end: null });
|
|
1187
|
+
this.hoveredDate.set(null);
|
|
1188
|
+
}
|
|
1189
|
+
else {
|
|
1190
|
+
// Second click — auto-swap if needed
|
|
1191
|
+
let start = rangeVal.start;
|
|
1192
|
+
let end = date;
|
|
1193
|
+
if (normalizeDate(end).getTime() < normalizeDate(start).getTime()) {
|
|
1194
|
+
[start, end] = [end, start];
|
|
1195
|
+
}
|
|
1196
|
+
this._setValue({ start, end });
|
|
1197
|
+
this.close();
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
_setValue(value) {
|
|
1201
|
+
this._value.set(value);
|
|
1202
|
+
this._onChange(value);
|
|
1203
|
+
this.valueChange.emit(value);
|
|
1204
|
+
this.dateChange.emit(value);
|
|
1205
|
+
this.stateChanges.next();
|
|
1206
|
+
}
|
|
1207
|
+
_createOverlay() {
|
|
1208
|
+
if (this._overlayRef || !this.calendarPanel || !this.triggerEl)
|
|
1209
|
+
return;
|
|
1210
|
+
const triggerElement = this.triggerEl.nativeElement;
|
|
1211
|
+
const triggerWidth = triggerElement.getBoundingClientRect().width;
|
|
1212
|
+
const positions = [
|
|
1213
|
+
{ originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4 },
|
|
1214
|
+
{ originX: 'start', originY: 'top', overlayX: 'start', overlayY: 'bottom', offsetY: -4 },
|
|
1215
|
+
];
|
|
1216
|
+
const positionStrategy = this._overlayService
|
|
1217
|
+
.position()
|
|
1218
|
+
.connectedTo(triggerElement, positions)
|
|
1219
|
+
.withPush(true)
|
|
1220
|
+
.withViewportMargin(8);
|
|
1221
|
+
this._overlayRef = this._overlayService.create({
|
|
1222
|
+
positionStrategy,
|
|
1223
|
+
scrollStrategy: this._overlayService.scrollStrategies.reposition(),
|
|
1224
|
+
hasBackdrop: true,
|
|
1225
|
+
backdropClass: 'fui-date-picker-backdrop',
|
|
1226
|
+
backdropClickBehavior: 'close',
|
|
1227
|
+
panelClass: ['fui-date-picker-overlay-panel'],
|
|
1228
|
+
minWidth: Math.max(triggerWidth, 280),
|
|
1229
|
+
});
|
|
1230
|
+
// Track overlay subscriptions for proper cleanup
|
|
1231
|
+
this._overlaySubscriptions.unsubscribe();
|
|
1232
|
+
this._overlaySubscriptions = new Subscription();
|
|
1233
|
+
this._overlaySubscriptions.add(this._overlayRef.backdropClick.subscribe(() => {
|
|
1234
|
+
this.close();
|
|
1235
|
+
}));
|
|
1236
|
+
this._overlaySubscriptions.add(this._overlayRef.keydownEvents.subscribe((event) => {
|
|
1237
|
+
if (event.key === 'Escape') {
|
|
1238
|
+
this.close();
|
|
1239
|
+
}
|
|
1240
|
+
}));
|
|
1241
|
+
const panelElement = this.calendarPanel.nativeElement;
|
|
1242
|
+
this._overlayRef.attach(panelElement);
|
|
1243
|
+
}
|
|
1244
|
+
_disposeOverlay() {
|
|
1245
|
+
this._overlaySubscriptions.unsubscribe();
|
|
1246
|
+
if (this._overlayRef) {
|
|
1247
|
+
this._overlayRef.dispose();
|
|
1248
|
+
this._overlayRef = null;
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
_isDateRange(val) {
|
|
1252
|
+
return val !== null && !(val instanceof Date) && typeof val === 'object' && 'start' in val;
|
|
1253
|
+
}
|
|
1254
|
+
// Public: get the computed placeholder for the start input
|
|
1255
|
+
getStartPlaceholder() {
|
|
1256
|
+
const p = this.placeholderInput();
|
|
1257
|
+
if (p)
|
|
1258
|
+
return p;
|
|
1259
|
+
return this.format().toLowerCase();
|
|
1260
|
+
}
|
|
1261
|
+
getEndPlaceholder() {
|
|
1262
|
+
return this.format().toLowerCase();
|
|
1263
|
+
}
|
|
1264
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: FuiDatePickerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1265
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.6", type: FuiDatePickerComponent, isStandalone: true, selector: "fui-date-picker", inputs: { placeholderInput: { classPropertyName: "placeholderInput", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, disabledInput: { classPropertyName: "disabledInput", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, range: { classPropertyName: "range", publicName: "range", isSignal: true, isRequired: false, transformFunction: null }, format: { classPropertyName: "format", publicName: "format", isSignal: true, isRequired: false, transformFunction: null }, min: { classPropertyName: "min", publicName: "min", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null }, dateFilter: { classPropertyName: "dateFilter", publicName: "dateFilter", isSignal: true, isRequired: false, transformFunction: null }, startAt: { classPropertyName: "startAt", publicName: "startAt", isSignal: true, isRequired: false, transformFunction: null }, firstDayOfWeek: { classPropertyName: "firstDayOfWeek", publicName: "firstDayOfWeek", isSignal: true, isRequired: false, transformFunction: null }, rangeSeparator: { classPropertyName: "rangeSeparator", publicName: "rangeSeparator", isSignal: true, isRequired: false, transformFunction: null }, errorStateMatcher: { classPropertyName: "errorStateMatcher", publicName: "errorStateMatcher", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { valueChange: "valueChange", dateChange: "dateChange", openedChange: "openedChange" }, host: { properties: { "attr.id": "id", "class.fui-date-picker--open": "panelOpen()", "class.fui-date-picker--disabled": "disabled()", "class.fui-date-picker--range": "range()", "class.fui-date-picker--focused": "focused()", "class.fui-date-picker--error": "errorState()", "class.fui-date-picker--readonly": "_readOnly()" }, classAttribute: "fui-date-picker" }, providers: [
|
|
1266
|
+
{
|
|
1267
|
+
provide: NG_VALUE_ACCESSOR,
|
|
1268
|
+
useExisting: FuiDatePickerComponent,
|
|
1269
|
+
multi: true,
|
|
1270
|
+
},
|
|
1271
|
+
{
|
|
1272
|
+
provide: NG_VALIDATORS,
|
|
1273
|
+
useExisting: FuiDatePickerComponent,
|
|
1274
|
+
multi: true,
|
|
1275
|
+
},
|
|
1276
|
+
{
|
|
1277
|
+
provide: FUI_FORM_FIELD_CONTROL,
|
|
1278
|
+
useExisting: FuiDatePickerComponent,
|
|
1279
|
+
},
|
|
1280
|
+
], viewQueries: [{ propertyName: "triggerEl", first: true, predicate: ["triggerEl"], descendants: true }, { propertyName: "calendarPanel", first: true, predicate: ["calendarPanel"], descendants: true }, { propertyName: "startInputEl", first: true, predicate: ["startInput"], descendants: true }, { propertyName: "endInputEl", first: true, predicate: ["endInput"], descendants: true }, { propertyName: "calendarComponent", first: true, predicate: ["calendar"], descendants: true }], ngImport: i0, template: "<!-- Trigger area -->\n<div #triggerEl class=\"fui-date-picker__trigger\" (click)=\"onTriggerClick()\">\n <!-- Start input -->\n <input\n #startInput\n class=\"fui-date-picker__input\"\n [id]=\"id\"\n [value]=\"startInputText()\"\n [placeholder]=\"getStartPlaceholder()\"\n [disabled]=\"disabled()\"\n [readonly]=\"readonly()\"\n [attr.aria-describedby]=\"_ariaDescribedby\"\n [attr.aria-required]=\"required()\"\n [attr.aria-invalid]=\"errorState()\"\n [attr.aria-expanded]=\"panelOpen()\"\n aria-haspopup=\"dialog\"\n aria-autocomplete=\"none\"\n role=\"combobox\"\n inputmode=\"numeric\"\n autocomplete=\"off\"\n (input)=\"onInput($event)\"\n (blur)=\"onBlur()\"\n (focus)=\"onInputFocus()\"\n (keydown)=\"onKeydown($event)\"\n (click)=\"$event.stopPropagation()\"\n />\n\n <!-- Range separator + end input -->\n @if (range()) {\n <span class=\"fui-date-picker__range-separator\" aria-hidden=\"true\">{{ rangeSeparator() }}</span>\n <input\n #endInput\n class=\"fui-date-picker__input fui-date-picker__input--end\"\n [value]=\"endInputText()\"\n [placeholder]=\"getEndPlaceholder()\"\n [disabled]=\"disabled()\"\n [readonly]=\"readonly()\"\n [attr.aria-label]=\"'End date'\"\n [attr.aria-invalid]=\"errorState()\"\n inputmode=\"numeric\"\n autocomplete=\"off\"\n (input)=\"onEndInput($event)\"\n (blur)=\"onEndBlur()\"\n (focus)=\"onInputFocus()\"\n (keydown)=\"onEndKeydown($event)\"\n (click)=\"$event.stopPropagation()\"\n />\n }\n\n <!-- Calendar toggle button \u2014 hidden in readOnly mode -->\n @if (!_readOnly()) {\n <button\n type=\"button\"\n class=\"fui-date-picker__toggle\"\n tabindex=\"-1\"\n [attr.aria-expanded]=\"panelOpen()\"\n [disabled]=\"disabled()\"\n aria-haspopup=\"dialog\"\n aria-label=\"Open calendar\"\n (click)=\"onToggleClick($event)\"\n >\n <fui-icon name=\"calendar-blank\" size=\"sm\" />\n </button>\n }\n</div>\n\n<!-- Calendar panel -->\n@if (panelOpen()) {\n <div #calendarPanel class=\"fui-date-picker__panel\" role=\"dialog\" aria-modal=\"true\" aria-label=\"Calendar\">\n <fui-calendar\n #calendar\n [selected]=\"selectedDate()\"\n [rangeStart]=\"rangeStartDate()\"\n [rangeEnd]=\"rangeEndDate()\"\n [range]=\"range()\"\n [min]=\"min()\"\n [max]=\"max()\"\n [dateFilter]=\"dateFilter()\"\n [startAt]=\"startAt()\"\n [firstDayOfWeek]=\"firstDayOfWeek()\"\n [hoveredDate]=\"hoveredDate()\"\n (dateSelected)=\"onDateSelected($event)\"\n (dateHovered)=\"onDateHovered($event)\"\n />\n </div>\n}\n", styles: ["@keyframes fui-skeleton-pulse{0%{opacity:1}50%{opacity:.4}to{opacity:1}}@keyframes fui-spin{to{transform:rotate(360deg)}}@keyframes fui-shake{0%,to{transform:translate(0)}10%,30%,50%,70%,90%{transform:translate(-2px)}20%,40%,60%,80%{transform:translate(2px)}}.fui-motion-fade-in{transition:opacity var(--fui-duration-fast-02) var(--fui-ease-entrance) 0ms}.fui-motion-fade-out{transition:opacity var(--fui-duration-fast-01) var(--fui-ease-exit) 0ms}.fui-motion-slide-in-bottom{transition:transform var(--fui-duration-moderate-01) var(--fui-ease-entrance) 0ms;transform:translateY(0)}.fui-motion-slide-in-bottom.fui-motion-entering{transform:translateY(1rem)}.fui-motion-slide-in-top{transition:transform var(--fui-duration-moderate-01) var(--fui-ease-entrance) 0ms;transform:translateY(0)}.fui-motion-slide-in-top.fui-motion-entering{transform:translateY(-1rem)}.fui-motion-scale-in{transition:transform,opacity var(--fui-duration-moderate-01) var(--fui-ease-entrance) 0ms;transform:scale(1);opacity:1}.fui-motion-scale-in.fui-motion-entering{transform:scale(.95);opacity:0}.fui-no-motion{transition:none!important;animation:none!important}@media(prefers-reduced-motion:reduce){*,*:before,*:after{animation-duration:.01ms!important;animation-iteration-count:1!important;transition-duration:.01ms!important}}@keyframes fui-pulse{0%{transform:scale(1);opacity:1}50%{transform:scale(1.05)}to{transform:scale(1);opacity:1}}@keyframes fui-slide-in{0%{transform:translate(120%)}to{transform:translate(0)}}.fui-slide-in{animation:fui-slide-in var(--fui-duration-moderate-01) var(--fui-ease-entrance)}.fui-date-picker{--fui-date-picker-font-size: var(--fui-font-size-02);--fui-date-picker-panel-border-radius: var(--fui-border-radius-md);--fui-date-picker-panel-shadow: var(--fui-shadow-03);--fui-date-picker-panel-bg: var(--fui-surface-00);--fui-date-picker-panel-border-color: var(--fui-border-color);--fui-date-picker-toggle-size: 1.5rem;display:inline-block;width:100%;position:relative}.fui-date-picker__trigger{width:100%;min-height:100%;display:flex;align-items:center;gap:var(--fui-spacing-01);cursor:pointer;background:transparent;border:none}.fui-date-picker__input{flex:1;min-width:0;border:none;outline:none;background:transparent;font-family:var(--fui-font-family-sans);font-size:var(--fui-font-size-02);font-weight:var(--fui-font-weight-regular);line-height:var(--fui-line-height-02);letter-spacing:var(--fui-letter-spacing-normal);color:var(--fui-text-primary);padding:0;cursor:text}.fui-date-picker__input::placeholder{color:var(--fui-text-disabled)}.fui-date-picker__input:disabled{cursor:not-allowed;opacity:.5}.fui-date-picker__range-separator{flex-shrink:0;color:var(--fui-text-secondary);font-size:var(--fui-font-size-02);-webkit-user-select:none;user-select:none}.fui-date-picker__toggle{background:none;border:none;padding:0;margin:0;font:inherit;color:inherit;cursor:pointer;outline:none}.fui-date-picker__toggle:focus-visible{outline:2px solid var(--fui-primary);outline-offset:2px}.fui-date-picker__toggle{display:flex;align-items:center;justify-content:center;flex-shrink:0;width:1.5rem;height:1.5rem;color:var(--fui-text-secondary);border-radius:var(--fui-border-radius-sm);transition:color var(--fui-duration-fast-02) var(--fui-ease-standard) 0ms}.fui-date-picker__toggle:hover:not(:disabled){color:var(--fui-text-primary)}.fui-date-picker__toggle:disabled{color:var(--fui-text-disabled);cursor:not-allowed}.fui-date-picker__panel{background:var(--fui-date-picker-panel-bg);border:1px solid var(--fui-date-picker-panel-border-color);border-radius:var(--fui-date-picker-panel-border-radius);box-shadow:var(--fui-date-picker-panel-shadow);padding:var(--fui-spacing-02);transition:opacity,transform var(--fui-duration-fast-02) var(--fui-ease-standard) 0ms}.fui-date-picker--disabled{pointer-events:none}.fui-date-picker--disabled .fui-date-picker__trigger{cursor:not-allowed;opacity:.5}.fui-date-picker--readonly{pointer-events:none}.fui-date-picker--readonly .fui-date-picker__trigger{cursor:default;opacity:1}.fui-date-picker--readonly .fui-date-picker__input{color:var(--fui-text-primary)!important;-webkit-text-fill-color:var(--fui-text-primary)!important;cursor:default;opacity:1}.fui-date-picker--readonly .fui-date-picker__input:disabled{opacity:1;cursor:default}.fui-date-picker-backdrop{background:transparent}.fui-date-picker-overlay-panel{z-index:var(--fui-z-popover, 1060)}@media(prefers-contrast:high){.fui-date-picker__panel{border-width:2px}}@media(prefers-reduced-motion:reduce){.fui-date-picker__panel{transition:none}}\n"], dependencies: [{ kind: "component", type: FuiIconComponent, selector: "fui-icon", inputs: ["name", "size", "weight", "color", "ariaLabel", "spin", "pulse"] }, { kind: "component", type: FuiCalendarComponent, selector: "fui-calendar", inputs: ["selected", "rangeStart", "rangeEnd", "range", "min", "max", "dateFilter", "startAt", "firstDayOfWeek", "hoveredDate"], outputs: ["dateSelected", "dateHovered", "monthChanged"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
1281
|
+
}
|
|
1282
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: FuiDatePickerComponent, decorators: [{
|
|
1283
|
+
type: Component,
|
|
1284
|
+
args: [{ selector: 'fui-date-picker', standalone: true, imports: [FuiIconComponent, FuiCalendarComponent], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, host: {
|
|
1285
|
+
class: 'fui-date-picker',
|
|
1286
|
+
'[attr.id]': 'id',
|
|
1287
|
+
'[class.fui-date-picker--open]': 'panelOpen()',
|
|
1288
|
+
'[class.fui-date-picker--disabled]': 'disabled()',
|
|
1289
|
+
'[class.fui-date-picker--range]': 'range()',
|
|
1290
|
+
'[class.fui-date-picker--focused]': 'focused()',
|
|
1291
|
+
'[class.fui-date-picker--error]': 'errorState()',
|
|
1292
|
+
'[class.fui-date-picker--readonly]': '_readOnly()',
|
|
1293
|
+
}, providers: [
|
|
1294
|
+
{
|
|
1295
|
+
provide: NG_VALUE_ACCESSOR,
|
|
1296
|
+
useExisting: FuiDatePickerComponent,
|
|
1297
|
+
multi: true,
|
|
1298
|
+
},
|
|
1299
|
+
{
|
|
1300
|
+
provide: NG_VALIDATORS,
|
|
1301
|
+
useExisting: FuiDatePickerComponent,
|
|
1302
|
+
multi: true,
|
|
1303
|
+
},
|
|
1304
|
+
{
|
|
1305
|
+
provide: FUI_FORM_FIELD_CONTROL,
|
|
1306
|
+
useExisting: FuiDatePickerComponent,
|
|
1307
|
+
},
|
|
1308
|
+
], template: "<!-- Trigger area -->\n<div #triggerEl class=\"fui-date-picker__trigger\" (click)=\"onTriggerClick()\">\n <!-- Start input -->\n <input\n #startInput\n class=\"fui-date-picker__input\"\n [id]=\"id\"\n [value]=\"startInputText()\"\n [placeholder]=\"getStartPlaceholder()\"\n [disabled]=\"disabled()\"\n [readonly]=\"readonly()\"\n [attr.aria-describedby]=\"_ariaDescribedby\"\n [attr.aria-required]=\"required()\"\n [attr.aria-invalid]=\"errorState()\"\n [attr.aria-expanded]=\"panelOpen()\"\n aria-haspopup=\"dialog\"\n aria-autocomplete=\"none\"\n role=\"combobox\"\n inputmode=\"numeric\"\n autocomplete=\"off\"\n (input)=\"onInput($event)\"\n (blur)=\"onBlur()\"\n (focus)=\"onInputFocus()\"\n (keydown)=\"onKeydown($event)\"\n (click)=\"$event.stopPropagation()\"\n />\n\n <!-- Range separator + end input -->\n @if (range()) {\n <span class=\"fui-date-picker__range-separator\" aria-hidden=\"true\">{{ rangeSeparator() }}</span>\n <input\n #endInput\n class=\"fui-date-picker__input fui-date-picker__input--end\"\n [value]=\"endInputText()\"\n [placeholder]=\"getEndPlaceholder()\"\n [disabled]=\"disabled()\"\n [readonly]=\"readonly()\"\n [attr.aria-label]=\"'End date'\"\n [attr.aria-invalid]=\"errorState()\"\n inputmode=\"numeric\"\n autocomplete=\"off\"\n (input)=\"onEndInput($event)\"\n (blur)=\"onEndBlur()\"\n (focus)=\"onInputFocus()\"\n (keydown)=\"onEndKeydown($event)\"\n (click)=\"$event.stopPropagation()\"\n />\n }\n\n <!-- Calendar toggle button \u2014 hidden in readOnly mode -->\n @if (!_readOnly()) {\n <button\n type=\"button\"\n class=\"fui-date-picker__toggle\"\n tabindex=\"-1\"\n [attr.aria-expanded]=\"panelOpen()\"\n [disabled]=\"disabled()\"\n aria-haspopup=\"dialog\"\n aria-label=\"Open calendar\"\n (click)=\"onToggleClick($event)\"\n >\n <fui-icon name=\"calendar-blank\" size=\"sm\" />\n </button>\n }\n</div>\n\n<!-- Calendar panel -->\n@if (panelOpen()) {\n <div #calendarPanel class=\"fui-date-picker__panel\" role=\"dialog\" aria-modal=\"true\" aria-label=\"Calendar\">\n <fui-calendar\n #calendar\n [selected]=\"selectedDate()\"\n [rangeStart]=\"rangeStartDate()\"\n [rangeEnd]=\"rangeEndDate()\"\n [range]=\"range()\"\n [min]=\"min()\"\n [max]=\"max()\"\n [dateFilter]=\"dateFilter()\"\n [startAt]=\"startAt()\"\n [firstDayOfWeek]=\"firstDayOfWeek()\"\n [hoveredDate]=\"hoveredDate()\"\n (dateSelected)=\"onDateSelected($event)\"\n (dateHovered)=\"onDateHovered($event)\"\n />\n </div>\n}\n", styles: ["@keyframes fui-skeleton-pulse{0%{opacity:1}50%{opacity:.4}to{opacity:1}}@keyframes fui-spin{to{transform:rotate(360deg)}}@keyframes fui-shake{0%,to{transform:translate(0)}10%,30%,50%,70%,90%{transform:translate(-2px)}20%,40%,60%,80%{transform:translate(2px)}}.fui-motion-fade-in{transition:opacity var(--fui-duration-fast-02) var(--fui-ease-entrance) 0ms}.fui-motion-fade-out{transition:opacity var(--fui-duration-fast-01) var(--fui-ease-exit) 0ms}.fui-motion-slide-in-bottom{transition:transform var(--fui-duration-moderate-01) var(--fui-ease-entrance) 0ms;transform:translateY(0)}.fui-motion-slide-in-bottom.fui-motion-entering{transform:translateY(1rem)}.fui-motion-slide-in-top{transition:transform var(--fui-duration-moderate-01) var(--fui-ease-entrance) 0ms;transform:translateY(0)}.fui-motion-slide-in-top.fui-motion-entering{transform:translateY(-1rem)}.fui-motion-scale-in{transition:transform,opacity var(--fui-duration-moderate-01) var(--fui-ease-entrance) 0ms;transform:scale(1);opacity:1}.fui-motion-scale-in.fui-motion-entering{transform:scale(.95);opacity:0}.fui-no-motion{transition:none!important;animation:none!important}@media(prefers-reduced-motion:reduce){*,*:before,*:after{animation-duration:.01ms!important;animation-iteration-count:1!important;transition-duration:.01ms!important}}@keyframes fui-pulse{0%{transform:scale(1);opacity:1}50%{transform:scale(1.05)}to{transform:scale(1);opacity:1}}@keyframes fui-slide-in{0%{transform:translate(120%)}to{transform:translate(0)}}.fui-slide-in{animation:fui-slide-in var(--fui-duration-moderate-01) var(--fui-ease-entrance)}.fui-date-picker{--fui-date-picker-font-size: var(--fui-font-size-02);--fui-date-picker-panel-border-radius: var(--fui-border-radius-md);--fui-date-picker-panel-shadow: var(--fui-shadow-03);--fui-date-picker-panel-bg: var(--fui-surface-00);--fui-date-picker-panel-border-color: var(--fui-border-color);--fui-date-picker-toggle-size: 1.5rem;display:inline-block;width:100%;position:relative}.fui-date-picker__trigger{width:100%;min-height:100%;display:flex;align-items:center;gap:var(--fui-spacing-01);cursor:pointer;background:transparent;border:none}.fui-date-picker__input{flex:1;min-width:0;border:none;outline:none;background:transparent;font-family:var(--fui-font-family-sans);font-size:var(--fui-font-size-02);font-weight:var(--fui-font-weight-regular);line-height:var(--fui-line-height-02);letter-spacing:var(--fui-letter-spacing-normal);color:var(--fui-text-primary);padding:0;cursor:text}.fui-date-picker__input::placeholder{color:var(--fui-text-disabled)}.fui-date-picker__input:disabled{cursor:not-allowed;opacity:.5}.fui-date-picker__range-separator{flex-shrink:0;color:var(--fui-text-secondary);font-size:var(--fui-font-size-02);-webkit-user-select:none;user-select:none}.fui-date-picker__toggle{background:none;border:none;padding:0;margin:0;font:inherit;color:inherit;cursor:pointer;outline:none}.fui-date-picker__toggle:focus-visible{outline:2px solid var(--fui-primary);outline-offset:2px}.fui-date-picker__toggle{display:flex;align-items:center;justify-content:center;flex-shrink:0;width:1.5rem;height:1.5rem;color:var(--fui-text-secondary);border-radius:var(--fui-border-radius-sm);transition:color var(--fui-duration-fast-02) var(--fui-ease-standard) 0ms}.fui-date-picker__toggle:hover:not(:disabled){color:var(--fui-text-primary)}.fui-date-picker__toggle:disabled{color:var(--fui-text-disabled);cursor:not-allowed}.fui-date-picker__panel{background:var(--fui-date-picker-panel-bg);border:1px solid var(--fui-date-picker-panel-border-color);border-radius:var(--fui-date-picker-panel-border-radius);box-shadow:var(--fui-date-picker-panel-shadow);padding:var(--fui-spacing-02);transition:opacity,transform var(--fui-duration-fast-02) var(--fui-ease-standard) 0ms}.fui-date-picker--disabled{pointer-events:none}.fui-date-picker--disabled .fui-date-picker__trigger{cursor:not-allowed;opacity:.5}.fui-date-picker--readonly{pointer-events:none}.fui-date-picker--readonly .fui-date-picker__trigger{cursor:default;opacity:1}.fui-date-picker--readonly .fui-date-picker__input{color:var(--fui-text-primary)!important;-webkit-text-fill-color:var(--fui-text-primary)!important;cursor:default;opacity:1}.fui-date-picker--readonly .fui-date-picker__input:disabled{opacity:1;cursor:default}.fui-date-picker-backdrop{background:transparent}.fui-date-picker-overlay-panel{z-index:var(--fui-z-popover, 1060)}@media(prefers-contrast:high){.fui-date-picker__panel{border-width:2px}}@media(prefers-reduced-motion:reduce){.fui-date-picker__panel{transition:none}}\n"] }]
|
|
1309
|
+
}], ctorParameters: () => [], propDecorators: { placeholderInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], disabledInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], range: [{ type: i0.Input, args: [{ isSignal: true, alias: "range", required: false }] }], format: [{ type: i0.Input, args: [{ isSignal: true, alias: "format", required: false }] }], min: [{ type: i0.Input, args: [{ isSignal: true, alias: "min", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], dateFilter: [{ type: i0.Input, args: [{ isSignal: true, alias: "dateFilter", required: false }] }], startAt: [{ type: i0.Input, args: [{ isSignal: true, alias: "startAt", required: false }] }], firstDayOfWeek: [{ type: i0.Input, args: [{ isSignal: true, alias: "firstDayOfWeek", required: false }] }], rangeSeparator: [{ type: i0.Input, args: [{ isSignal: true, alias: "rangeSeparator", required: false }] }], errorStateMatcher: [{ type: i0.Input, args: [{ isSignal: true, alias: "errorStateMatcher", required: false }] }], valueChange: [{ type: i0.Output, args: ["valueChange"] }], dateChange: [{ type: i0.Output, args: ["dateChange"] }], openedChange: [{ type: i0.Output, args: ["openedChange"] }], triggerEl: [{
|
|
1310
|
+
type: ViewChild,
|
|
1311
|
+
args: ['triggerEl', { static: false }]
|
|
1312
|
+
}], calendarPanel: [{
|
|
1313
|
+
type: ViewChild,
|
|
1314
|
+
args: ['calendarPanel', { static: false }]
|
|
1315
|
+
}], startInputEl: [{
|
|
1316
|
+
type: ViewChild,
|
|
1317
|
+
args: ['startInput', { static: false }]
|
|
1318
|
+
}], endInputEl: [{
|
|
1319
|
+
type: ViewChild,
|
|
1320
|
+
args: ['endInput', { static: false }]
|
|
1321
|
+
}], calendarComponent: [{
|
|
1322
|
+
type: ViewChild,
|
|
1323
|
+
args: ['calendar', { static: false }]
|
|
1324
|
+
}] } });
|
|
1325
|
+
|
|
1326
|
+
/**
|
|
1327
|
+
* Generated bundle index. Do not edit.
|
|
1328
|
+
*/
|
|
1329
|
+
|
|
1330
|
+
export { DEFAULT_DATE_FORMAT, FuiCalendarComponent, FuiDatePickerComponent, RANGE_SEPARATOR, applyDateMask, buildCalendarGrid, formatDate, getDaysInMonth, getMonthLabel, getShortMonthName, getWeekdayLabels, isAfter, isBefore, isBetween, isDateInRange, isSameDay, normalizeDate, parseDate };
|
|
1331
|
+
//# sourceMappingURL=raintonic-formaui-components-date-picker.mjs.map
|