@oneluiz/dual-datepicker 3.9.3 → 4.0.1
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/README.md +26 -2
- package/fesm2022/oneluiz-dual-datepicker.mjs +696 -1040
- package/fesm2022/oneluiz-dual-datepicker.mjs.map +1 -1
- package/package.json +16 -8
- package/core/built-in-presets.d.ts +0 -98
- package/core/calendar-grid/cache.config.d.ts +0 -34
- package/core/calendar-grid/calendar-grid.cache.d.ts +0 -39
- package/core/calendar-grid/calendar-grid.factory.d.ts +0 -26
- package/core/calendar-grid/calendar-grid.types.d.ts +0 -57
- package/core/calendar-grid/index.d.ts +0 -56
- package/core/calendar-grid/range-highlighter.cache.d.ts +0 -106
- package/core/calendar-grid/range-highlighter.d.ts +0 -85
- package/core/calendar-grid/range-highlighter.types.d.ts +0 -182
- package/core/calendar-grid/virtual-weeks.logic.d.ts +0 -116
- package/core/calendar-grid/virtual-weeks.types.d.ts +0 -71
- package/core/date-adapter.d.ts +0 -298
- package/core/date-clock.d.ts +0 -82
- package/core/dual-date-range.store.d.ts +0 -113
- package/core/index.d.ts +0 -33
- package/core/native-date-adapter.d.ts +0 -152
- package/core/preset-providers.d.ts +0 -176
- package/core/preset-registry.d.ts +0 -181
- package/core/preset.engine.d.ts +0 -124
- package/core/range-preset.plugin.d.ts +0 -188
- package/core/range.validator.d.ts +0 -37
- package/core/system-clock.d.ts +0 -13
- package/date-adapter.d.ts +0 -116
- package/dual-datepicker.component.d.ts +0 -243
- package/esm2022/core/built-in-presets.mjs +0 -289
- package/esm2022/core/calendar-grid/cache.config.mjs +0 -35
- package/esm2022/core/calendar-grid/calendar-grid.cache.mjs +0 -98
- package/esm2022/core/calendar-grid/calendar-grid.factory.mjs +0 -97
- package/esm2022/core/calendar-grid/calendar-grid.types.mjs +0 -8
- package/esm2022/core/calendar-grid/index.mjs +0 -57
- package/esm2022/core/calendar-grid/range-highlighter.cache.mjs +0 -200
- package/esm2022/core/calendar-grid/range-highlighter.mjs +0 -185
- package/esm2022/core/calendar-grid/range-highlighter.types.mjs +0 -11
- package/esm2022/core/calendar-grid/virtual-weeks.logic.mjs +0 -149
- package/esm2022/core/calendar-grid/virtual-weeks.types.mjs +0 -11
- package/esm2022/core/date-adapter.mjs +0 -77
- package/esm2022/core/date-clock.mjs +0 -65
- package/esm2022/core/dual-date-range.store.mjs +0 -329
- package/esm2022/core/index.mjs +0 -37
- package/esm2022/core/native-date-adapter.mjs +0 -286
- package/esm2022/core/preset-providers.mjs +0 -243
- package/esm2022/core/preset-registry.mjs +0 -277
- package/esm2022/core/preset.engine.mjs +0 -179
- package/esm2022/core/range-preset.plugin.mjs +0 -70
- package/esm2022/core/range.validator.mjs +0 -105
- package/esm2022/core/system-clock.mjs +0 -34
- package/esm2022/date-adapter.mjs +0 -12
- package/esm2022/dual-datepicker.component.mjs +0 -1312
- package/esm2022/native-date-adapter.mjs +0 -117
- package/esm2022/oneluiz-dual-datepicker.mjs +0 -5
- package/esm2022/preset-utils.mjs +0 -276
- package/esm2022/public-api.mjs +0 -13
- package/index.d.ts +0 -5
- package/native-date-adapter.d.ts +0 -26
- package/preset-utils.d.ts +0 -91
- package/public-api.d.ts +0 -9
- package/src/themes/bootstrap.scss +0 -202
- package/src/themes/bulma.scss +0 -209
- package/src/themes/custom.scss +0 -236
- package/src/themes/foundation.scss +0 -201
- package/src/themes/tailwind.scss +0 -109
|
@@ -1,134 +1,9 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { InjectionToken, Injectable, inject, signal, computed, Inject, EventEmitter, effect,
|
|
2
|
+
import { InjectionToken, Injectable, inject, signal, computed, makeEnvironmentProviders, APP_INITIALIZER, Inject, EventEmitter, effect, forwardRef, HostListener, Output, Input, Component } from '@angular/core';
|
|
3
3
|
import * as i1 from '@angular/common';
|
|
4
4
|
import { CommonModule } from '@angular/common';
|
|
5
5
|
import { FormsModule, ReactiveFormsModule, NG_VALUE_ACCESSOR } from '@angular/forms';
|
|
6
6
|
|
|
7
|
-
/**
|
|
8
|
-
* Abstract class for date adapters.
|
|
9
|
-
* Allows the component to work with different date libraries (Date, DayJS, date-fns, Luxon, etc.)
|
|
10
|
-
*/
|
|
11
|
-
class DateAdapter {
|
|
12
|
-
}
|
|
13
|
-
/**
|
|
14
|
-
* Injection token for DateAdapter
|
|
15
|
-
*/
|
|
16
|
-
const DATE_ADAPTER$1 = new InjectionToken('DATE_ADAPTER');
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Date adapter implementation for native JavaScript Date objects
|
|
20
|
-
* This is the default adapter used by the component
|
|
21
|
-
*/
|
|
22
|
-
let NativeDateAdapter$1 = class NativeDateAdapter extends DateAdapter {
|
|
23
|
-
parse(value) {
|
|
24
|
-
if (!value)
|
|
25
|
-
return null;
|
|
26
|
-
if (value instanceof Date) {
|
|
27
|
-
return new Date(value);
|
|
28
|
-
}
|
|
29
|
-
if (typeof value === 'string' || typeof value === 'number') {
|
|
30
|
-
// Fix timezone issues: parse YYYY-MM-DD as local date, not UTC
|
|
31
|
-
if (typeof value === 'string' && /^\d{4}-\d{2}-\d{2}$/.test(value)) {
|
|
32
|
-
const [year, month, day] = value.split('-').map(Number);
|
|
33
|
-
return new Date(year, month - 1, day);
|
|
34
|
-
}
|
|
35
|
-
const date = new Date(value);
|
|
36
|
-
return this.isValid(date) ? date : null;
|
|
37
|
-
}
|
|
38
|
-
return null;
|
|
39
|
-
}
|
|
40
|
-
format(date, format = 'YYYY-MM-DD') {
|
|
41
|
-
if (!this.isValid(date))
|
|
42
|
-
return '';
|
|
43
|
-
const year = date.getFullYear();
|
|
44
|
-
const month = String(date.getMonth() + 1).padStart(2, '0');
|
|
45
|
-
const day = String(date.getDate()).padStart(2, '0');
|
|
46
|
-
// Simple format implementation
|
|
47
|
-
switch (format) {
|
|
48
|
-
case 'YYYY-MM-DD':
|
|
49
|
-
return `${year}-${month}-${day}`;
|
|
50
|
-
case 'MM/DD/YYYY':
|
|
51
|
-
return `${month}/${day}/${year}`;
|
|
52
|
-
case 'DD/MM/YYYY':
|
|
53
|
-
return `${day}/${month}/${year}`;
|
|
54
|
-
default:
|
|
55
|
-
return `${year}-${month}-${day}`;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
addDays(date, days) {
|
|
59
|
-
const result = this.clone(date);
|
|
60
|
-
result.setDate(result.getDate() + days);
|
|
61
|
-
return result;
|
|
62
|
-
}
|
|
63
|
-
addMonths(date, months) {
|
|
64
|
-
const result = this.clone(date);
|
|
65
|
-
result.setMonth(result.getMonth() + months);
|
|
66
|
-
return result;
|
|
67
|
-
}
|
|
68
|
-
getYear(date) {
|
|
69
|
-
return date.getFullYear();
|
|
70
|
-
}
|
|
71
|
-
getMonth(date) {
|
|
72
|
-
return date.getMonth();
|
|
73
|
-
}
|
|
74
|
-
getDate(date) {
|
|
75
|
-
return date.getDate();
|
|
76
|
-
}
|
|
77
|
-
getDay(date) {
|
|
78
|
-
return date.getDay();
|
|
79
|
-
}
|
|
80
|
-
createDate(year, month, date) {
|
|
81
|
-
return new Date(year, month, date);
|
|
82
|
-
}
|
|
83
|
-
today() {
|
|
84
|
-
return new Date();
|
|
85
|
-
}
|
|
86
|
-
isSameDay(a, b) {
|
|
87
|
-
if (!a || !b)
|
|
88
|
-
return false;
|
|
89
|
-
if (!this.isValid(a) || !this.isValid(b))
|
|
90
|
-
return false;
|
|
91
|
-
return (a.getFullYear() === b.getFullYear() &&
|
|
92
|
-
a.getMonth() === b.getMonth() &&
|
|
93
|
-
a.getDate() === b.getDate());
|
|
94
|
-
}
|
|
95
|
-
isBefore(a, b) {
|
|
96
|
-
if (!a || !b)
|
|
97
|
-
return false;
|
|
98
|
-
if (!this.isValid(a) || !this.isValid(b))
|
|
99
|
-
return false;
|
|
100
|
-
return a.getTime() < b.getTime();
|
|
101
|
-
}
|
|
102
|
-
isAfter(a, b) {
|
|
103
|
-
if (!a || !b)
|
|
104
|
-
return false;
|
|
105
|
-
if (!this.isValid(a) || !this.isValid(b))
|
|
106
|
-
return false;
|
|
107
|
-
return a.getTime() > b.getTime();
|
|
108
|
-
}
|
|
109
|
-
isBetween(date, start, end) {
|
|
110
|
-
if (!date || !start || !end)
|
|
111
|
-
return false;
|
|
112
|
-
if (!this.isValid(date) || !this.isValid(start) || !this.isValid(end))
|
|
113
|
-
return false;
|
|
114
|
-
const dateTime = date.getTime();
|
|
115
|
-
const startTime = start.getTime();
|
|
116
|
-
const endTime = end.getTime();
|
|
117
|
-
return dateTime >= startTime && dateTime <= endTime;
|
|
118
|
-
}
|
|
119
|
-
clone(date) {
|
|
120
|
-
return new Date(date.getTime());
|
|
121
|
-
}
|
|
122
|
-
isValid(date) {
|
|
123
|
-
return date instanceof Date && !isNaN(date.getTime());
|
|
124
|
-
}
|
|
125
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: NativeDateAdapter, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
|
|
126
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: NativeDateAdapter });
|
|
127
|
-
};
|
|
128
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: NativeDateAdapter$1, decorators: [{
|
|
129
|
-
type: Injectable
|
|
130
|
-
}] });
|
|
131
|
-
|
|
132
7
|
/**
|
|
133
8
|
* Pure validation functions for date ranges
|
|
134
9
|
* No dependencies, no side effects - just logic
|
|
@@ -320,10 +195,10 @@ class SystemClock {
|
|
|
320
195
|
now() {
|
|
321
196
|
return new Date();
|
|
322
197
|
}
|
|
323
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
324
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "
|
|
198
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SystemClock, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
199
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SystemClock, providedIn: 'root' });
|
|
325
200
|
}
|
|
326
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
201
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SystemClock, decorators: [{
|
|
327
202
|
type: Injectable,
|
|
328
203
|
args: [{
|
|
329
204
|
providedIn: 'root'
|
|
@@ -680,10 +555,10 @@ class NativeDateAdapter {
|
|
|
680
555
|
const startsWithMonday = mondayStartLocales.some(loc => normalizedLocale.startsWith(loc.toLowerCase()));
|
|
681
556
|
return startsWithMonday ? 1 : 0;
|
|
682
557
|
}
|
|
683
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
684
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "
|
|
558
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: NativeDateAdapter, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
559
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: NativeDateAdapter, providedIn: 'root' });
|
|
685
560
|
}
|
|
686
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
561
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: NativeDateAdapter, decorators: [{
|
|
687
562
|
type: Injectable,
|
|
688
563
|
args: [{
|
|
689
564
|
providedIn: 'root'
|
|
@@ -1024,10 +899,10 @@ class PresetRegistry {
|
|
|
1024
899
|
clear() {
|
|
1025
900
|
this.presets.clear();
|
|
1026
901
|
}
|
|
1027
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
1028
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "
|
|
902
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: PresetRegistry, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
903
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: PresetRegistry, providedIn: 'root' });
|
|
1029
904
|
}
|
|
1030
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
905
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: PresetRegistry, decorators: [{
|
|
1031
906
|
type: Injectable,
|
|
1032
907
|
args: [{
|
|
1033
908
|
providedIn: 'root'
|
|
@@ -1173,10 +1048,10 @@ class PresetEngine {
|
|
|
1173
1048
|
hasPreset(key) {
|
|
1174
1049
|
return this.registry.has(key);
|
|
1175
1050
|
}
|
|
1176
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
1177
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "
|
|
1051
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: PresetEngine, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1052
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: PresetEngine, providedIn: 'root' });
|
|
1178
1053
|
}
|
|
1179
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
1054
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: PresetEngine, decorators: [{
|
|
1180
1055
|
type: Injectable,
|
|
1181
1056
|
args: [{
|
|
1182
1057
|
providedIn: 'root'
|
|
@@ -1252,26 +1127,28 @@ class DualDateRangeStore {
|
|
|
1252
1127
|
catch {
|
|
1253
1128
|
this.adapter = new NativeDateAdapter();
|
|
1254
1129
|
}
|
|
1130
|
+
// Update _rightMonth to correct value after adapter is available
|
|
1131
|
+
this._rightMonth.set(this.getNextMonth(new Date()));
|
|
1255
1132
|
}
|
|
1256
1133
|
// Configuration
|
|
1257
1134
|
config = signal({
|
|
1258
1135
|
enableTimePicker: false,
|
|
1259
1136
|
defaultStartTime: '00:00',
|
|
1260
1137
|
defaultEndTime: '23:59'
|
|
1261
|
-
});
|
|
1138
|
+
}, ...(ngDevMode ? [{ debugName: "config" }] : /* istanbul ignore next */ []));
|
|
1262
1139
|
// Core state - using signals
|
|
1263
|
-
_startDate = signal(null);
|
|
1264
|
-
_endDate = signal(null);
|
|
1265
|
-
_leftMonth = signal(new Date());
|
|
1266
|
-
_rightMonth = signal(
|
|
1267
|
-
_selectingStart = signal(true);
|
|
1140
|
+
_startDate = signal(null, ...(ngDevMode ? [{ debugName: "_startDate" }] : /* istanbul ignore next */ []));
|
|
1141
|
+
_endDate = signal(null, ...(ngDevMode ? [{ debugName: "_endDate" }] : /* istanbul ignore next */ []));
|
|
1142
|
+
_leftMonth = signal(new Date(), ...(ngDevMode ? [{ debugName: "_leftMonth" }] : /* istanbul ignore next */ []));
|
|
1143
|
+
_rightMonth = signal(new Date(), ...(ngDevMode ? [{ debugName: "_rightMonth" }] : /* istanbul ignore next */ [])); // Temporary value, updated in constructor
|
|
1144
|
+
_selectingStart = signal(true, ...(ngDevMode ? [{ debugName: "_selectingStart" }] : /* istanbul ignore next */ []));
|
|
1268
1145
|
// Time state
|
|
1269
|
-
_startTime = signal('00:00');
|
|
1270
|
-
_endTime = signal('23:59');
|
|
1146
|
+
_startTime = signal('00:00', ...(ngDevMode ? [{ debugName: "_startTime" }] : /* istanbul ignore next */ []));
|
|
1147
|
+
_endTime = signal('23:59', ...(ngDevMode ? [{ debugName: "_endTime" }] : /* istanbul ignore next */ []));
|
|
1271
1148
|
// Pending state for requireApply mode
|
|
1272
|
-
_pendingStart = signal(null);
|
|
1273
|
-
_pendingEnd = signal(null);
|
|
1274
|
-
_hasPendingChanges = signal(false);
|
|
1149
|
+
_pendingStart = signal(null, ...(ngDevMode ? [{ debugName: "_pendingStart" }] : /* istanbul ignore next */ []));
|
|
1150
|
+
_pendingEnd = signal(null, ...(ngDevMode ? [{ debugName: "_pendingEnd" }] : /* istanbul ignore next */ []));
|
|
1151
|
+
_hasPendingChanges = signal(false, ...(ngDevMode ? [{ debugName: "_hasPendingChanges" }] : /* istanbul ignore next */ []));
|
|
1275
1152
|
// Public read-only signals
|
|
1276
1153
|
startDate = this._startDate.asReadonly();
|
|
1277
1154
|
endDate = this._endDate.asReadonly();
|
|
@@ -1295,7 +1172,7 @@ class DualDateRangeStore {
|
|
|
1295
1172
|
result.endTime = this._endTime();
|
|
1296
1173
|
}
|
|
1297
1174
|
return result;
|
|
1298
|
-
});
|
|
1175
|
+
}, ...(ngDevMode ? [{ debugName: "range" }] : /* istanbul ignore next */ []));
|
|
1299
1176
|
// Computed: validation state
|
|
1300
1177
|
isValid = computed(() => {
|
|
1301
1178
|
const start = this._startDate();
|
|
@@ -1306,7 +1183,7 @@ class DualDateRangeStore {
|
|
|
1306
1183
|
return false;
|
|
1307
1184
|
const boundsValidation = validateRangeBounds(start, end, cfg.minDate, cfg.maxDate);
|
|
1308
1185
|
return boundsValidation.valid;
|
|
1309
|
-
});
|
|
1186
|
+
}, ...(ngDevMode ? [{ debugName: "isValid" }] : /* istanbul ignore next */ []));
|
|
1310
1187
|
// Computed: range text for display
|
|
1311
1188
|
rangeText = computed(() => {
|
|
1312
1189
|
const start = this._startDate();
|
|
@@ -1318,7 +1195,7 @@ class DualDateRangeStore {
|
|
|
1318
1195
|
if (!end)
|
|
1319
1196
|
return `${this.formatDateShort(start)}`;
|
|
1320
1197
|
return `${this.formatDateShort(start)} - ${this.formatDateShort(end)}`;
|
|
1321
|
-
});
|
|
1198
|
+
}, ...(ngDevMode ? [{ debugName: "rangeText" }] : /* istanbul ignore next */ []));
|
|
1322
1199
|
/**
|
|
1323
1200
|
* Configure the store
|
|
1324
1201
|
*/
|
|
@@ -1514,13 +1391,13 @@ class DualDateRangeStore {
|
|
|
1514
1391
|
formatDateShort(date) {
|
|
1515
1392
|
const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
|
1516
1393
|
const month = this.adapter.getMonth(date);
|
|
1517
|
-
const day = this.adapter.
|
|
1394
|
+
const day = this.adapter.getDate(date); // FIX: use getDate() not getDay()
|
|
1518
1395
|
return `${day} ${months[month]}`;
|
|
1519
1396
|
}
|
|
1520
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
1521
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "
|
|
1397
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: DualDateRangeStore, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1398
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: DualDateRangeStore, providedIn: 'root' });
|
|
1522
1399
|
}
|
|
1523
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
1400
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: DualDateRangeStore, decorators: [{
|
|
1524
1401
|
type: Injectable,
|
|
1525
1402
|
args: [{
|
|
1526
1403
|
providedIn: 'root'
|
|
@@ -1817,289 +1694,536 @@ const BUILT_IN_PRESETS = [
|
|
|
1817
1694
|
];
|
|
1818
1695
|
|
|
1819
1696
|
/**
|
|
1820
|
-
*
|
|
1697
|
+
* Provider Functions for Built-in Presets
|
|
1698
|
+
*
|
|
1699
|
+
* Version: 3.6.0
|
|
1700
|
+
*
|
|
1701
|
+
* Automatic registration of built-in date range presets.
|
|
1702
|
+
* These providers ensure backward compatibility by auto-registering
|
|
1703
|
+
* all standard presets (TODAY, LAST_7_DAYS, THIS_MONTH, etc.)
|
|
1821
1704
|
*
|
|
1822
|
-
*
|
|
1823
|
-
*
|
|
1705
|
+
* USAGE IN LIBRARY (Internal):
|
|
1706
|
+
* Built-in presets are registered automatically via Angular providers.
|
|
1707
|
+
* Library consumers don't need to do anything.
|
|
1708
|
+
*
|
|
1709
|
+
* USAGE IN APP (Custom Presets):
|
|
1710
|
+
* ```typescript
|
|
1711
|
+
* // app.config.ts
|
|
1712
|
+
* export const appConfig: ApplicationConfig = {
|
|
1713
|
+
* providers: [
|
|
1714
|
+
* // ... other providers
|
|
1715
|
+
* provideCustomPresets([
|
|
1716
|
+
* {
|
|
1717
|
+
* key: 'THIS_FISCAL_QUARTER',
|
|
1718
|
+
* resolve: (clock, adapter) => {
|
|
1719
|
+
* const now = clock.now();
|
|
1720
|
+
* // ... fiscal logic
|
|
1721
|
+
* return { start, end };
|
|
1722
|
+
* }
|
|
1723
|
+
* }
|
|
1724
|
+
* ])
|
|
1725
|
+
* ]
|
|
1726
|
+
* };
|
|
1727
|
+
* ```
|
|
1824
1728
|
*/
|
|
1825
|
-
|
|
1826
1729
|
/**
|
|
1827
|
-
*
|
|
1730
|
+
* Initializer function that registers built-in presets
|
|
1828
1731
|
*
|
|
1829
|
-
*
|
|
1830
|
-
* timezone-safe date operations.
|
|
1732
|
+
* Runs at application startup (APP_INITIALIZER)
|
|
1831
1733
|
*
|
|
1832
|
-
*
|
|
1833
|
-
*
|
|
1834
|
-
* - Includes padding days from previous/next month
|
|
1835
|
-
* - No decorations (selected, disabled, etc.) - those are applied separately
|
|
1734
|
+
* @param registry - PresetRegistry instance
|
|
1735
|
+
* @returns Initialization function
|
|
1836
1736
|
*/
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
* @param weekStart - First day of week (0 = Sunday, 1 = Monday, etc.)
|
|
1847
|
-
* @param locale - Locale identifier (optional, for future use)
|
|
1848
|
-
* @returns CalendarGrid - 6 weeks x 7 days grid
|
|
1849
|
-
*/
|
|
1850
|
-
createGrid(monthDate, weekStart = 0, locale) {
|
|
1851
|
-
// Normalize to start of month
|
|
1852
|
-
const year = this.adapter.getYear(monthDate);
|
|
1853
|
-
const month = this.adapter.getMonth(monthDate);
|
|
1854
|
-
const firstDayOfMonth = new Date(year, month, 1);
|
|
1855
|
-
const normalizedFirst = this.adapter.normalize(firstDayOfMonth);
|
|
1856
|
-
// Get days in month (day 0 of next month = last day of current month)
|
|
1857
|
-
const lastDayOfMonth = new Date(year, month + 1, 0);
|
|
1858
|
-
const daysInMonth = this.adapter.getDate(lastDayOfMonth);
|
|
1859
|
-
// Get first day of week offset
|
|
1860
|
-
const firstDayOfWeek = this.adapter.getDay(normalizedFirst);
|
|
1861
|
-
const offset = this.calculateOffset(firstDayOfWeek, weekStart);
|
|
1862
|
-
// Generate 42 cells (6 weeks x 7 days)
|
|
1863
|
-
const cells = [];
|
|
1864
|
-
let currentDate = this.adapter.addDays(normalizedFirst, -offset);
|
|
1865
|
-
for (let i = 0; i < 42; i++) {
|
|
1866
|
-
const cellDate = this.adapter.normalize(currentDate);
|
|
1867
|
-
const cellYear = this.adapter.getYear(cellDate);
|
|
1868
|
-
const cellMonth = this.adapter.getMonth(cellDate);
|
|
1869
|
-
const cellDay = this.adapter.getDate(cellDate);
|
|
1870
|
-
const cellDayOfWeek = this.adapter.getDay(cellDate);
|
|
1871
|
-
cells.push({
|
|
1872
|
-
date: cellDate,
|
|
1873
|
-
inCurrentMonth: cellYear === year && cellMonth === month,
|
|
1874
|
-
iso: this.adapter.toISODate(cellDate),
|
|
1875
|
-
day: cellDay,
|
|
1876
|
-
month: cellMonth,
|
|
1877
|
-
year: cellYear,
|
|
1878
|
-
dayOfWeek: cellDayOfWeek
|
|
1879
|
-
});
|
|
1880
|
-
currentDate = this.adapter.addDays(currentDate, 1);
|
|
1881
|
-
}
|
|
1882
|
-
// Split into weeks
|
|
1883
|
-
const weeks = [];
|
|
1884
|
-
for (let i = 0; i < 6; i++) {
|
|
1885
|
-
weeks.push(cells.slice(i * 7, (i + 1) * 7));
|
|
1886
|
-
}
|
|
1887
|
-
return {
|
|
1888
|
-
month: { year, month },
|
|
1889
|
-
weekStart,
|
|
1890
|
-
locale,
|
|
1891
|
-
weeks,
|
|
1892
|
-
cells
|
|
1893
|
-
};
|
|
1894
|
-
}
|
|
1895
|
-
/**
|
|
1896
|
-
* Calculate offset (number of padding days from previous month)
|
|
1897
|
-
*
|
|
1898
|
-
* @param firstDayOfWeek - Day of week for first day of month (0-6)
|
|
1899
|
-
* @param weekStart - Desired week start (0-6)
|
|
1900
|
-
* @returns Number of padding days needed
|
|
1901
|
-
*/
|
|
1902
|
-
calculateOffset(firstDayOfWeek, weekStart) {
|
|
1903
|
-
let offset = firstDayOfWeek - weekStart;
|
|
1904
|
-
if (offset < 0) {
|
|
1905
|
-
offset += 7;
|
|
1737
|
+
function initializeBuiltInPresets(registry) {
|
|
1738
|
+
return () => {
|
|
1739
|
+
// Register all built-in presets
|
|
1740
|
+
BUILT_IN_PRESETS.forEach(preset => {
|
|
1741
|
+
registry.register(preset);
|
|
1742
|
+
});
|
|
1743
|
+
// Log registration for debugging (can be removed in production)
|
|
1744
|
+
if (typeof console !== 'undefined' && console.debug) {
|
|
1745
|
+
console.debug(`[ng-dual-datepicker] Registered ${BUILT_IN_PRESETS.length} built-in presets:`, BUILT_IN_PRESETS.map(p => p.key).join(', '));
|
|
1906
1746
|
}
|
|
1907
|
-
|
|
1908
|
-
}
|
|
1909
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: CalendarGridFactory, deps: [{ token: DATE_ADAPTER }], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1910
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: CalendarGridFactory, providedIn: 'root' });
|
|
1747
|
+
};
|
|
1911
1748
|
}
|
|
1912
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: CalendarGridFactory, decorators: [{
|
|
1913
|
-
type: Injectable,
|
|
1914
|
-
args: [{ providedIn: 'root' }]
|
|
1915
|
-
}], ctorParameters: () => [{ type: undefined, decorators: [{
|
|
1916
|
-
type: Inject,
|
|
1917
|
-
args: [DATE_ADAPTER]
|
|
1918
|
-
}] }] });
|
|
1919
|
-
|
|
1920
1749
|
/**
|
|
1921
|
-
*
|
|
1750
|
+
* Provide built-in date range presets
|
|
1922
1751
|
*
|
|
1923
|
-
*
|
|
1752
|
+
* This provider is automatically included in the library's root providers.
|
|
1753
|
+
* Library consumers don't need to add this manually.
|
|
1924
1754
|
*
|
|
1925
|
-
* @
|
|
1926
|
-
*
|
|
1755
|
+
* @returns EnvironmentProviders for built-in presets
|
|
1756
|
+
*
|
|
1757
|
+
* @internal
|
|
1927
1758
|
*/
|
|
1759
|
+
function provideBuiltInPresets() {
|
|
1760
|
+
return makeEnvironmentProviders([
|
|
1761
|
+
{
|
|
1762
|
+
provide: APP_INITIALIZER,
|
|
1763
|
+
multi: true,
|
|
1764
|
+
useFactory: initializeBuiltInPresets,
|
|
1765
|
+
deps: [PresetRegistry]
|
|
1766
|
+
}
|
|
1767
|
+
]);
|
|
1768
|
+
}
|
|
1928
1769
|
/**
|
|
1929
|
-
*
|
|
1770
|
+
* Provide custom date range presets
|
|
1930
1771
|
*
|
|
1931
|
-
*
|
|
1932
|
-
* -
|
|
1933
|
-
* -
|
|
1772
|
+
* Use this to register your own industry-specific presets:
|
|
1773
|
+
* - Fiscal presets
|
|
1774
|
+
* - Hotel/hospitality presets
|
|
1775
|
+
* - Logistics presets
|
|
1776
|
+
* - Custom business logic
|
|
1934
1777
|
*
|
|
1935
|
-
*
|
|
1936
|
-
*
|
|
1937
|
-
* - DecoratedGrid: ~15KB each
|
|
1938
|
-
* - Total worst case: (10KB + 15KB) × 48 = ~1.2MB
|
|
1778
|
+
* @param presets - Array of custom RangePresetPlugin implementations
|
|
1779
|
+
* @returns EnvironmentProviders for custom presets
|
|
1939
1780
|
*
|
|
1940
|
-
*
|
|
1941
|
-
*
|
|
1942
|
-
*
|
|
1781
|
+
* @example
|
|
1782
|
+
* ```typescript
|
|
1783
|
+
* // Fiscal presets
|
|
1784
|
+
* const FISCAL_PRESETS: RangePresetPlugin[] = [
|
|
1785
|
+
* {
|
|
1786
|
+
* key: 'THIS_FISCAL_QUARTER',
|
|
1787
|
+
* resolve: (clock, adapter) => {
|
|
1788
|
+
* const now = clock.now();
|
|
1789
|
+
* const month = adapter.getMonth(now);
|
|
1943
1790
|
*
|
|
1944
|
-
*
|
|
1945
|
-
*
|
|
1946
|
-
*
|
|
1947
|
-
* -
|
|
1948
|
-
* - Point-of-sale systems (report generation)
|
|
1791
|
+
* // Fiscal year starts April (month 3)
|
|
1792
|
+
* const fiscalMonth = (month + 9) % 12;
|
|
1793
|
+
* const quarterStart = Math.floor(fiscalMonth / 3) * 3;
|
|
1794
|
+
* const calendarMonth = (quarterStart - 9 + 12) % 12;
|
|
1949
1795
|
*
|
|
1950
|
-
*
|
|
1951
|
-
*
|
|
1952
|
-
*/
|
|
1953
|
-
const MAX_CACHE_ENTRIES = 48;
|
|
1954
|
-
|
|
1955
|
-
/**
|
|
1956
|
-
* Calendar Grid Cache
|
|
1796
|
+
* const year = adapter.getYear(now);
|
|
1797
|
+
* const fiscalYear = month < 3 ? year - 1 : year;
|
|
1957
1798
|
*
|
|
1958
|
-
*
|
|
1959
|
-
*
|
|
1799
|
+
* const start = new Date(fiscalYear, calendarMonth, 1);
|
|
1800
|
+
* const end = new Date(fiscalYear, calendarMonth + 3, 0);
|
|
1960
1801
|
*
|
|
1961
|
-
*
|
|
1962
|
-
*
|
|
1963
|
-
*
|
|
1964
|
-
*
|
|
1802
|
+
* return {
|
|
1803
|
+
* start: adapter.normalize(start),
|
|
1804
|
+
* end: adapter.normalize(end)
|
|
1805
|
+
* };
|
|
1806
|
+
* }
|
|
1807
|
+
* },
|
|
1808
|
+
* {
|
|
1809
|
+
* key: 'FISCAL_YEAR_TO_DATE',
|
|
1810
|
+
* resolve: (clock, adapter) => {
|
|
1811
|
+
* const now = clock.now();
|
|
1812
|
+
* const month = adapter.getMonth(now);
|
|
1813
|
+
* const year = adapter.getYear(now);
|
|
1965
1814
|
*
|
|
1966
|
-
*
|
|
1967
|
-
*
|
|
1968
|
-
*
|
|
1815
|
+
* // Fiscal year starts April 1
|
|
1816
|
+
* const fiscalYearStart = month >= 3
|
|
1817
|
+
* ? new Date(year, 3, 1)
|
|
1818
|
+
* : new Date(year - 1, 3, 1);
|
|
1969
1819
|
*
|
|
1970
|
-
*
|
|
1971
|
-
*
|
|
1972
|
-
*
|
|
1973
|
-
*
|
|
1820
|
+
* return {
|
|
1821
|
+
* start: adapter.normalize(fiscalYearStart),
|
|
1822
|
+
* end: adapter.normalize(now)
|
|
1823
|
+
* };
|
|
1824
|
+
* }
|
|
1825
|
+
* }
|
|
1826
|
+
* ];
|
|
1827
|
+
*
|
|
1828
|
+
* // In app.config.ts
|
|
1829
|
+
* export const appConfig: ApplicationConfig = {
|
|
1830
|
+
* providers: [
|
|
1831
|
+
* provideCustomPresets(FISCAL_PRESETS)
|
|
1832
|
+
* ]
|
|
1833
|
+
* };
|
|
1834
|
+
*
|
|
1835
|
+
* // Use in components
|
|
1836
|
+
* store.applyPreset('THIS_FISCAL_QUARTER');
|
|
1837
|
+
* store.applyPreset('FISCAL_YEAR_TO_DATE');
|
|
1838
|
+
* ```
|
|
1839
|
+
*
|
|
1840
|
+
* @example
|
|
1841
|
+
* ```typescript
|
|
1842
|
+
* // Hotel presets
|
|
1843
|
+
* const HOTEL_PRESETS: RangePresetPlugin[] = [
|
|
1844
|
+
* {
|
|
1845
|
+
* key: 'CHECK_IN_WEEK',
|
|
1846
|
+
* resolve: (clock, adapter) => {
|
|
1847
|
+
* const now = clock.now();
|
|
1848
|
+
* const dayOfWeek = adapter.getDayOfWeek(now);
|
|
1849
|
+
*
|
|
1850
|
+
* // Check-in week: Friday to Friday
|
|
1851
|
+
* const daysToNextFriday = dayOfWeek <= 5
|
|
1852
|
+
* ? 5 - dayOfWeek
|
|
1853
|
+
* : 7 - dayOfWeek + 5;
|
|
1854
|
+
*
|
|
1855
|
+
* const nextFriday = adapter.addDays(now, daysToNextFriday);
|
|
1856
|
+
* const followingFriday = adapter.addDays(nextFriday, 7);
|
|
1857
|
+
*
|
|
1858
|
+
* return { start: nextFriday, end: followingFriday };
|
|
1859
|
+
* }
|
|
1860
|
+
* },
|
|
1861
|
+
* {
|
|
1862
|
+
* key: 'NEXT_30_NIGHTS',
|
|
1863
|
+
* resolve: (clock, adapter) => {
|
|
1864
|
+
* const now = clock.now();
|
|
1865
|
+
* const tomorrow = adapter.addDays(now, 1);
|
|
1866
|
+
* const end = adapter.addDays(tomorrow, 30);
|
|
1867
|
+
* return { start: tomorrow, end };
|
|
1868
|
+
* }
|
|
1869
|
+
* }
|
|
1870
|
+
* ];
|
|
1871
|
+
*
|
|
1872
|
+
* providers: [provideCustomPresets(HOTEL_PRESETS)]
|
|
1873
|
+
* ```
|
|
1974
1874
|
*/
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
const key = this.buildKey(monthDate, weekStart, locale);
|
|
1992
|
-
// Check cache
|
|
1993
|
-
const cached = this.cache.get(key);
|
|
1994
|
-
if (cached) {
|
|
1995
|
-
// Move to end (LRU)
|
|
1996
|
-
this.cache.delete(key);
|
|
1997
|
-
this.cache.set(key, cached);
|
|
1998
|
-
return cached;
|
|
1999
|
-
}
|
|
2000
|
-
// Generate new grid
|
|
2001
|
-
const grid = this.factory.createGrid(monthDate, weekStart, locale);
|
|
2002
|
-
// Store in cache
|
|
2003
|
-
this.cache.set(key, grid);
|
|
2004
|
-
// Evict oldest if over limit (LRU)
|
|
2005
|
-
if (this.cache.size > this.maxSize) {
|
|
2006
|
-
const firstKey = this.cache.keys().next().value;
|
|
2007
|
-
this.cache.delete(firstKey);
|
|
1875
|
+
function provideCustomPresets(presets) {
|
|
1876
|
+
return makeEnvironmentProviders([
|
|
1877
|
+
{
|
|
1878
|
+
provide: APP_INITIALIZER,
|
|
1879
|
+
multi: true,
|
|
1880
|
+
useFactory: (registry) => {
|
|
1881
|
+
return () => {
|
|
1882
|
+
presets.forEach(preset => {
|
|
1883
|
+
registry.register(preset);
|
|
1884
|
+
});
|
|
1885
|
+
if (typeof console !== 'undefined' && console.debug) {
|
|
1886
|
+
console.debug(`[ng-dual-datepicker] Registered ${presets.length} custom presets:`, presets.map(p => p.key).join(', '));
|
|
1887
|
+
}
|
|
1888
|
+
};
|
|
1889
|
+
},
|
|
1890
|
+
deps: [PresetRegistry]
|
|
2008
1891
|
}
|
|
2009
|
-
|
|
2010
|
-
}
|
|
2011
|
-
/**
|
|
2012
|
-
* Build cache key from month parameters
|
|
2013
|
-
*
|
|
2014
|
-
* Format: "year-month-weekStart-locale"
|
|
2015
|
-
* Example: "2026-1-0-en" (Feb 2026, Sunday start, English)
|
|
2016
|
-
*/
|
|
2017
|
-
buildKey(monthDate, weekStart, locale) {
|
|
2018
|
-
const year = monthDate.getFullYear();
|
|
2019
|
-
const month = monthDate.getMonth();
|
|
2020
|
-
return `${year}-${month}-${weekStart}${locale ? '-' + locale : ''}`;
|
|
2021
|
-
}
|
|
2022
|
-
/**
|
|
2023
|
-
* Clear entire cache (for testing or manual reset)
|
|
2024
|
-
*/
|
|
2025
|
-
clear() {
|
|
2026
|
-
this.cache.clear();
|
|
2027
|
-
}
|
|
2028
|
-
/**
|
|
2029
|
-
* Get cache size (for debugging/testing)
|
|
2030
|
-
*/
|
|
2031
|
-
size() {
|
|
2032
|
-
return this.cache.size;
|
|
2033
|
-
}
|
|
2034
|
-
/**
|
|
2035
|
-
* Check if a specific month is cached
|
|
2036
|
-
*/
|
|
2037
|
-
has(monthDate, weekStart = 0, locale) {
|
|
2038
|
-
const key = this.buildKey(monthDate, weekStart, locale);
|
|
2039
|
-
return this.cache.has(key);
|
|
2040
|
-
}
|
|
2041
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: CalendarGridCache, deps: [{ token: CalendarGridFactory }], target: i0.ɵɵFactoryTarget.Injectable });
|
|
2042
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: CalendarGridCache, providedIn: 'root' });
|
|
1892
|
+
]);
|
|
2043
1893
|
}
|
|
2044
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: CalendarGridCache, decorators: [{
|
|
2045
|
-
type: Injectable,
|
|
2046
|
-
args: [{ providedIn: 'root' }]
|
|
2047
|
-
}], ctorParameters: () => [{ type: CalendarGridFactory }] });
|
|
2048
|
-
|
|
2049
1894
|
/**
|
|
2050
|
-
*
|
|
1895
|
+
* Provide preset package
|
|
2051
1896
|
*
|
|
2052
|
-
*
|
|
2053
|
-
* Used by RangeHighlighter to cache decorated grids and avoid recomputations.
|
|
1897
|
+
* Convenience function for external preset packages.
|
|
2054
1898
|
*
|
|
2055
|
-
* @
|
|
2056
|
-
* @
|
|
1899
|
+
* @param packageName - Name of the preset package (for logging)
|
|
1900
|
+
* @param presets - Array of presets from the package
|
|
1901
|
+
* @returns EnvironmentProviders
|
|
1902
|
+
*
|
|
1903
|
+
* @example
|
|
1904
|
+
* ```typescript
|
|
1905
|
+
* // @acme/fiscal-presets package
|
|
1906
|
+
* export function provideFiscalPresets(): EnvironmentProviders {
|
|
1907
|
+
* return providePresetPackage('@acme/fiscal-presets', FISCAL_PRESETS);
|
|
1908
|
+
* }
|
|
1909
|
+
*
|
|
1910
|
+
* // In app
|
|
1911
|
+
* import { provideFiscalPresets } from '@acme/fiscal-presets';
|
|
1912
|
+
*
|
|
1913
|
+
* providers: [provideFiscalPresets()]
|
|
1914
|
+
* ```
|
|
2057
1915
|
*/
|
|
1916
|
+
function providePresetPackage(packageName, presets) {
|
|
1917
|
+
return makeEnvironmentProviders([
|
|
1918
|
+
{
|
|
1919
|
+
provide: APP_INITIALIZER,
|
|
1920
|
+
multi: true,
|
|
1921
|
+
useFactory: (registry) => {
|
|
1922
|
+
return () => {
|
|
1923
|
+
presets.forEach(preset => {
|
|
1924
|
+
registry.register(preset);
|
|
1925
|
+
});
|
|
1926
|
+
if (typeof console !== 'undefined' && console.debug) {
|
|
1927
|
+
console.debug(`[${packageName}] Registered ${presets.length} presets:`, presets.map(p => p.key).join(', '));
|
|
1928
|
+
}
|
|
1929
|
+
};
|
|
1930
|
+
},
|
|
1931
|
+
deps: [PresetRegistry]
|
|
1932
|
+
}
|
|
1933
|
+
]);
|
|
1934
|
+
}
|
|
2058
1935
|
|
|
2059
1936
|
/**
|
|
2060
|
-
*
|
|
1937
|
+
* PUBLIC API for @oneluiz/dual-datepicker
|
|
2061
1938
|
*
|
|
2062
|
-
*
|
|
2063
|
-
*
|
|
1939
|
+
* This file defines the stable public API contract.
|
|
1940
|
+
* Exports from this barrel are guaranteed to follow semantic versioning.
|
|
2064
1941
|
*
|
|
2065
|
-
* @
|
|
2066
|
-
* @
|
|
1942
|
+
* @public
|
|
1943
|
+
* @packageDocumentation
|
|
1944
|
+
* @module @oneluiz/dual-datepicker/core
|
|
1945
|
+
* @since v4.0.0
|
|
2067
1946
|
*/
|
|
1947
|
+
// ============================================================================
|
|
1948
|
+
// HEADLESS STORE
|
|
1949
|
+
// ============================================================================
|
|
2068
1950
|
/**
|
|
2069
|
-
*
|
|
1951
|
+
* DualDateRangeStore - Signal-based headless date range store
|
|
2070
1952
|
*
|
|
2071
|
-
*
|
|
2072
|
-
*
|
|
2073
|
-
* - isInRange (cells within selected range)
|
|
2074
|
-
* - isInHoverRange (hover preview)
|
|
2075
|
-
* - isDisabled (constraints, custom predicates)
|
|
1953
|
+
* Core reactive store for managing date range state without UI dependencies.
|
|
1954
|
+
* Perfect for SSR, global state, and custom UI implementations.
|
|
2076
1955
|
*
|
|
2077
|
-
*
|
|
2078
|
-
*
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
1956
|
+
* @public
|
|
1957
|
+
* @since v3.5.0
|
|
1958
|
+
*/
|
|
1959
|
+
|
|
1960
|
+
/**
|
|
1961
|
+
* Calendar Grid Factory
|
|
2082
1962
|
*
|
|
2083
|
-
*
|
|
2084
|
-
*
|
|
2085
|
-
*
|
|
2086
|
-
*
|
|
2087
|
-
*
|
|
2088
|
-
*
|
|
2089
|
-
*
|
|
2090
|
-
* disabledDates: [...]
|
|
2091
|
-
* });
|
|
2092
|
-
* ```
|
|
1963
|
+
* Generates calendar month grids using DateAdapter for deterministic,
|
|
1964
|
+
* timezone-safe date operations.
|
|
1965
|
+
*
|
|
1966
|
+
* Grid structure:
|
|
1967
|
+
* - Always 6 weeks x 7 days (42 cells) for layout stability
|
|
1968
|
+
* - Includes padding days from previous/next month
|
|
1969
|
+
* - No decorations (selected, disabled, etc.) - those are applied separately
|
|
2093
1970
|
*/
|
|
2094
|
-
class
|
|
1971
|
+
class CalendarGridFactory {
|
|
2095
1972
|
adapter;
|
|
2096
1973
|
constructor(adapter) {
|
|
2097
1974
|
this.adapter = adapter;
|
|
2098
1975
|
}
|
|
2099
1976
|
/**
|
|
2100
|
-
*
|
|
1977
|
+
* Create a calendar grid for a given month
|
|
2101
1978
|
*
|
|
2102
|
-
* @param
|
|
1979
|
+
* @param monthDate - Any date within the target month (will be normalized to start of month)
|
|
1980
|
+
* @param weekStart - First day of week (0 = Sunday, 1 = Monday, etc.)
|
|
1981
|
+
* @param locale - Locale identifier (optional, for future use)
|
|
1982
|
+
* @returns CalendarGrid - 6 weeks x 7 days grid
|
|
1983
|
+
*/
|
|
1984
|
+
createGrid(monthDate, weekStart = 0, locale) {
|
|
1985
|
+
// Normalize to start of month
|
|
1986
|
+
const year = this.adapter.getYear(monthDate);
|
|
1987
|
+
const month = this.adapter.getMonth(monthDate);
|
|
1988
|
+
const firstDayOfMonth = new Date(year, month, 1);
|
|
1989
|
+
const normalizedFirst = this.adapter.normalize(firstDayOfMonth);
|
|
1990
|
+
// Get days in month (day 0 of next month = last day of current month)
|
|
1991
|
+
const lastDayOfMonth = new Date(year, month + 1, 0);
|
|
1992
|
+
const daysInMonth = this.adapter.getDate(lastDayOfMonth);
|
|
1993
|
+
// Get first day of week offset
|
|
1994
|
+
const firstDayOfWeek = this.adapter.getDay(normalizedFirst);
|
|
1995
|
+
const offset = this.calculateOffset(firstDayOfWeek, weekStart);
|
|
1996
|
+
// Generate 42 cells (6 weeks x 7 days)
|
|
1997
|
+
const cells = [];
|
|
1998
|
+
let currentDate = this.adapter.addDays(normalizedFirst, -offset);
|
|
1999
|
+
for (let i = 0; i < 42; i++) {
|
|
2000
|
+
const cellDate = this.adapter.normalize(currentDate);
|
|
2001
|
+
const cellYear = this.adapter.getYear(cellDate);
|
|
2002
|
+
const cellMonth = this.adapter.getMonth(cellDate);
|
|
2003
|
+
const cellDay = this.adapter.getDate(cellDate);
|
|
2004
|
+
const cellDayOfWeek = this.adapter.getDay(cellDate);
|
|
2005
|
+
cells.push({
|
|
2006
|
+
date: cellDate,
|
|
2007
|
+
inCurrentMonth: cellYear === year && cellMonth === month,
|
|
2008
|
+
iso: this.adapter.toISODate(cellDate),
|
|
2009
|
+
day: cellDay,
|
|
2010
|
+
month: cellMonth,
|
|
2011
|
+
year: cellYear,
|
|
2012
|
+
dayOfWeek: cellDayOfWeek
|
|
2013
|
+
});
|
|
2014
|
+
currentDate = this.adapter.addDays(currentDate, 1);
|
|
2015
|
+
}
|
|
2016
|
+
// Split into weeks
|
|
2017
|
+
const weeks = [];
|
|
2018
|
+
for (let i = 0; i < 6; i++) {
|
|
2019
|
+
weeks.push(cells.slice(i * 7, (i + 1) * 7));
|
|
2020
|
+
}
|
|
2021
|
+
return {
|
|
2022
|
+
month: { year, month },
|
|
2023
|
+
weekStart,
|
|
2024
|
+
locale,
|
|
2025
|
+
weeks,
|
|
2026
|
+
cells
|
|
2027
|
+
};
|
|
2028
|
+
}
|
|
2029
|
+
/**
|
|
2030
|
+
* Calculate offset (number of padding days from previous month)
|
|
2031
|
+
*
|
|
2032
|
+
* @param firstDayOfWeek - Day of week for first day of month (0-6)
|
|
2033
|
+
* @param weekStart - Desired week start (0-6)
|
|
2034
|
+
* @returns Number of padding days needed
|
|
2035
|
+
*/
|
|
2036
|
+
calculateOffset(firstDayOfWeek, weekStart) {
|
|
2037
|
+
let offset = firstDayOfWeek - weekStart;
|
|
2038
|
+
if (offset < 0) {
|
|
2039
|
+
offset += 7;
|
|
2040
|
+
}
|
|
2041
|
+
return offset;
|
|
2042
|
+
}
|
|
2043
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: CalendarGridFactory, deps: [{ token: DATE_ADAPTER }], target: i0.ɵɵFactoryTarget.Injectable });
|
|
2044
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: CalendarGridFactory, providedIn: 'root' });
|
|
2045
|
+
}
|
|
2046
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: CalendarGridFactory, decorators: [{
|
|
2047
|
+
type: Injectable,
|
|
2048
|
+
args: [{ providedIn: 'root' }]
|
|
2049
|
+
}], ctorParameters: () => [{ type: undefined, decorators: [{
|
|
2050
|
+
type: Inject,
|
|
2051
|
+
args: [DATE_ADAPTER]
|
|
2052
|
+
}] }] });
|
|
2053
|
+
|
|
2054
|
+
/**
|
|
2055
|
+
* Cache Configuration
|
|
2056
|
+
*
|
|
2057
|
+
* Centralized cache limits for memory safety.
|
|
2058
|
+
*
|
|
2059
|
+
* @module core/calendar-grid/cache.config
|
|
2060
|
+
* @version 3.9.2
|
|
2061
|
+
*/
|
|
2062
|
+
/**
|
|
2063
|
+
* Maximum cache entries before FIFO eviction
|
|
2064
|
+
*
|
|
2065
|
+
* Applied to both:
|
|
2066
|
+
* - CalendarGridCache: Raw month grids
|
|
2067
|
+
* - RangeHighlighterCache: Decorated grids
|
|
2068
|
+
*
|
|
2069
|
+
* Memory footprint:
|
|
2070
|
+
* - CalendarGrid (raw): ~10KB each
|
|
2071
|
+
* - DecoratedGrid: ~15KB each
|
|
2072
|
+
* - Total worst case: (10KB + 15KB) × 48 = ~1.2MB
|
|
2073
|
+
*
|
|
2074
|
+
* Coverage:
|
|
2075
|
+
* - 48 months = 4 years of navigation
|
|
2076
|
+
* - Sufficient for long-running sessions
|
|
2077
|
+
*
|
|
2078
|
+
* Critical for:
|
|
2079
|
+
* - ERP systems (invoice date selection)
|
|
2080
|
+
* - BI dashboards (date range filters)
|
|
2081
|
+
* - Hotel reservation systems (availability calendars)
|
|
2082
|
+
* - Point-of-sale systems (report generation)
|
|
2083
|
+
*
|
|
2084
|
+
* @constant
|
|
2085
|
+
* @since v3.9.2
|
|
2086
|
+
*/
|
|
2087
|
+
const MAX_CACHE_ENTRIES = 48;
|
|
2088
|
+
|
|
2089
|
+
/**
|
|
2090
|
+
* Calendar Grid Cache
|
|
2091
|
+
*
|
|
2092
|
+
* LRU cache for calendar month grids to avoid recomputing the same month grid
|
|
2093
|
+
* multiple times when only decorations (selected dates, hover, etc.) change.
|
|
2094
|
+
*
|
|
2095
|
+
* Strategy:
|
|
2096
|
+
* - Key: year-month-weekStart-locale
|
|
2097
|
+
* - LRU eviction (least recently used) when limit is reached
|
|
2098
|
+
* - Default limit: 48 months (covers 2 years of navigation)
|
|
2099
|
+
*
|
|
2100
|
+
* Performance impact:
|
|
2101
|
+
* - Eliminates ~90% of grid recalculations in typical usage
|
|
2102
|
+
* - Memory footprint: ~10KB per cached month (negligible)
|
|
2103
|
+
*
|
|
2104
|
+
* Memory safety (v3.9.2):
|
|
2105
|
+
* - MAX_CACHE_ENTRIES prevents unbounded growth in long-running sessions
|
|
2106
|
+
* - Critical for: ERP, BI dashboards, hotel reservation systems
|
|
2107
|
+
* - FIFO eviction when limit exceeded
|
|
2108
|
+
*/
|
|
2109
|
+
class CalendarGridCache {
|
|
2110
|
+
factory;
|
|
2111
|
+
cache = new Map();
|
|
2112
|
+
maxSize = MAX_CACHE_ENTRIES;
|
|
2113
|
+
constructor(factory) {
|
|
2114
|
+
this.factory = factory;
|
|
2115
|
+
}
|
|
2116
|
+
/**
|
|
2117
|
+
* Get or create a calendar grid
|
|
2118
|
+
*
|
|
2119
|
+
* @param monthDate - Any date within the target month
|
|
2120
|
+
* @param weekStart - First day of week (0 = Sunday, 1 = Monday, etc.)
|
|
2121
|
+
* @param locale - Locale identifier (optional)
|
|
2122
|
+
* @returns CalendarGrid - cached or newly created
|
|
2123
|
+
*/
|
|
2124
|
+
get(monthDate, weekStart = 0, locale) {
|
|
2125
|
+
const key = this.buildKey(monthDate, weekStart, locale);
|
|
2126
|
+
// Check cache
|
|
2127
|
+
const cached = this.cache.get(key);
|
|
2128
|
+
if (cached) {
|
|
2129
|
+
// Move to end (LRU)
|
|
2130
|
+
this.cache.delete(key);
|
|
2131
|
+
this.cache.set(key, cached);
|
|
2132
|
+
return cached;
|
|
2133
|
+
}
|
|
2134
|
+
// Generate new grid
|
|
2135
|
+
const grid = this.factory.createGrid(monthDate, weekStart, locale);
|
|
2136
|
+
// Store in cache
|
|
2137
|
+
this.cache.set(key, grid);
|
|
2138
|
+
// Evict oldest if over limit (LRU)
|
|
2139
|
+
if (this.cache.size > this.maxSize) {
|
|
2140
|
+
const firstKey = this.cache.keys().next().value;
|
|
2141
|
+
this.cache.delete(firstKey);
|
|
2142
|
+
}
|
|
2143
|
+
return grid;
|
|
2144
|
+
}
|
|
2145
|
+
/**
|
|
2146
|
+
* Build cache key from month parameters
|
|
2147
|
+
*
|
|
2148
|
+
* Format: "year-month-weekStart-locale"
|
|
2149
|
+
* Example: "2026-1-0-en" (Feb 2026, Sunday start, English)
|
|
2150
|
+
*/
|
|
2151
|
+
buildKey(monthDate, weekStart, locale) {
|
|
2152
|
+
const year = monthDate.getFullYear();
|
|
2153
|
+
const month = monthDate.getMonth();
|
|
2154
|
+
return `${year}-${month}-${weekStart}${locale ? '-' + locale : ''}`;
|
|
2155
|
+
}
|
|
2156
|
+
/**
|
|
2157
|
+
* Clear entire cache (for testing or manual reset)
|
|
2158
|
+
*/
|
|
2159
|
+
clear() {
|
|
2160
|
+
this.cache.clear();
|
|
2161
|
+
}
|
|
2162
|
+
/**
|
|
2163
|
+
* Get cache size (for debugging/testing)
|
|
2164
|
+
*/
|
|
2165
|
+
size() {
|
|
2166
|
+
return this.cache.size;
|
|
2167
|
+
}
|
|
2168
|
+
/**
|
|
2169
|
+
* Check if a specific month is cached
|
|
2170
|
+
*/
|
|
2171
|
+
has(monthDate, weekStart = 0, locale) {
|
|
2172
|
+
const key = this.buildKey(monthDate, weekStart, locale);
|
|
2173
|
+
return this.cache.has(key);
|
|
2174
|
+
}
|
|
2175
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: CalendarGridCache, deps: [{ token: CalendarGridFactory }], target: i0.ɵɵFactoryTarget.Injectable });
|
|
2176
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: CalendarGridCache, providedIn: 'root' });
|
|
2177
|
+
}
|
|
2178
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: CalendarGridCache, decorators: [{
|
|
2179
|
+
type: Injectable,
|
|
2180
|
+
args: [{ providedIn: 'root' }]
|
|
2181
|
+
}], ctorParameters: () => [{ type: CalendarGridFactory }] });
|
|
2182
|
+
|
|
2183
|
+
/**
|
|
2184
|
+
* Range Highlighter Service
|
|
2185
|
+
*
|
|
2186
|
+
* Decorates calendar grids with range highlights, hover previews, and disabled states.
|
|
2187
|
+
* Pure computation layer - no caching (see RangeHighlighterCache for memoization).
|
|
2188
|
+
*
|
|
2189
|
+
* @module core/calendar-grid/range-highlighter
|
|
2190
|
+
* @version 3.8.0
|
|
2191
|
+
*/
|
|
2192
|
+
/**
|
|
2193
|
+
* Range Highlighter
|
|
2194
|
+
*
|
|
2195
|
+
* Applies decorations to calendar grids:
|
|
2196
|
+
* - isSelectedStart, isSelectedEnd (range boundaries)
|
|
2197
|
+
* - isInRange (cells within selected range)
|
|
2198
|
+
* - isInHoverRange (hover preview)
|
|
2199
|
+
* - isDisabled (constraints, custom predicates)
|
|
2200
|
+
*
|
|
2201
|
+
* Design:
|
|
2202
|
+
* - Pure functions (same input = same output)
|
|
2203
|
+
* - Uses DateAdapter for all date operations (SSR-safe)
|
|
2204
|
+
* - No side effects, no state
|
|
2205
|
+
* - Fast (~1ms for 42 cells on mobile)
|
|
2206
|
+
*
|
|
2207
|
+
* Usage:
|
|
2208
|
+
* ```typescript
|
|
2209
|
+
* const grid = calendarGridCache.get(monthDate, 0);
|
|
2210
|
+
* const decorated = rangeHighlighter.decorate(grid, {
|
|
2211
|
+
* start: startDate,
|
|
2212
|
+
* end: endDate,
|
|
2213
|
+
* hoverDate: '2026-01-20',
|
|
2214
|
+
* disabledDates: [...]
|
|
2215
|
+
* });
|
|
2216
|
+
* ```
|
|
2217
|
+
*/
|
|
2218
|
+
class RangeHighlighter {
|
|
2219
|
+
adapter;
|
|
2220
|
+
constructor(adapter) {
|
|
2221
|
+
this.adapter = adapter;
|
|
2222
|
+
}
|
|
2223
|
+
/**
|
|
2224
|
+
* Decorate calendar grid with range highlights
|
|
2225
|
+
*
|
|
2226
|
+
* @param grid Base calendar grid (from CalendarGridCache)
|
|
2103
2227
|
* @param params Decoration parameters (start, end, hover, disabled, etc.)
|
|
2104
2228
|
* @returns Decorated grid with computed properties
|
|
2105
2229
|
*/
|
|
@@ -2227,10 +2351,10 @@ class RangeHighlighter {
|
|
|
2227
2351
|
const max = startISO > hoverISO ? startISO : hoverISO;
|
|
2228
2352
|
return { min, max };
|
|
2229
2353
|
}
|
|
2230
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
2231
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "
|
|
2354
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: RangeHighlighter, deps: [{ token: DATE_ADAPTER }], target: i0.ɵɵFactoryTarget.Injectable });
|
|
2355
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: RangeHighlighter, providedIn: 'root' });
|
|
2232
2356
|
}
|
|
2233
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
2357
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: RangeHighlighter, decorators: [{
|
|
2234
2358
|
type: Injectable,
|
|
2235
2359
|
args: [{ providedIn: 'root' }]
|
|
2236
2360
|
}], ctorParameters: () => [{ type: undefined, decorators: [{
|
|
@@ -2422,10 +2546,10 @@ class RangeHighlighterCache {
|
|
|
2422
2546
|
const key = this.buildKey(grid, params);
|
|
2423
2547
|
return this.cache.has(key.full);
|
|
2424
2548
|
}
|
|
2425
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
2426
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "
|
|
2549
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: RangeHighlighterCache, deps: [{ token: RangeHighlighter }, { token: DATE_ADAPTER }], target: i0.ɵɵFactoryTarget.Injectable });
|
|
2550
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: RangeHighlighterCache, providedIn: 'root' });
|
|
2427
2551
|
}
|
|
2428
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
2552
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: RangeHighlighterCache, decorators: [{
|
|
2429
2553
|
type: Injectable,
|
|
2430
2554
|
args: [{ providedIn: 'root' }]
|
|
2431
2555
|
}], ctorParameters: () => [{ type: RangeHighlighter }, { type: undefined, decorators: [{
|
|
@@ -2433,16 +2557,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
2433
2557
|
args: [DATE_ADAPTER]
|
|
2434
2558
|
}] }] });
|
|
2435
2559
|
|
|
2436
|
-
/**
|
|
2437
|
-
* Virtual Weeks Types
|
|
2438
|
-
*
|
|
2439
|
-
* Type definitions for windowed week rendering (virtual scrolling).
|
|
2440
|
-
* Reduces DOM nodes by rendering only visible weeks instead of full month.
|
|
2441
|
-
*
|
|
2442
|
-
* @module core/calendar-grid/virtual-weeks.types
|
|
2443
|
-
* @version 3.9.0
|
|
2444
|
-
*/
|
|
2445
|
-
|
|
2446
2560
|
/**
|
|
2447
2561
|
* Virtual Weeks Logic (Pure Functions)
|
|
2448
2562
|
*
|
|
@@ -2593,59 +2707,118 @@ function isVirtualWeeksEnabled(windowSize, totalWeeks) {
|
|
|
2593
2707
|
}
|
|
2594
2708
|
|
|
2595
2709
|
/**
|
|
2596
|
-
*
|
|
2710
|
+
* Fixed Clock for Deterministic Testing
|
|
2597
2711
|
*
|
|
2598
|
-
*
|
|
2599
|
-
*
|
|
2600
|
-
* v3.7.0: Grid Structure Cache
|
|
2601
|
-
* - CalendarGridFactory for deterministic 42-cell grids
|
|
2602
|
-
* - CalendarGridCache with LRU (24 months)
|
|
2603
|
-
*
|
|
2604
|
-
* v3.8.0: Range Highlight Cache
|
|
2605
|
-
* - RangeHighlighter for decoration logic
|
|
2606
|
-
* - RangeHighlighterCache with LRU (48 grids)
|
|
2607
|
-
* - Separates grid structure from decorations
|
|
2608
|
-
*
|
|
2609
|
-
* v3.9.0: Virtual Weeks (Windowed Rendering)
|
|
2610
|
-
* - Virtual-weeks logic for reduced DOM complexity
|
|
2611
|
-
* - Render only visible weeks (configurable window)
|
|
2612
|
-
* - ~50% reduction in DOM nodes with windowSize=3
|
|
2613
|
-
*
|
|
2614
|
-
* v3.9.2: Cache Bounds (Memory Safety)
|
|
2615
|
-
* - MAX_CACHE_ENTRIES = 48 prevents unbounded growth
|
|
2616
|
-
* - FIFO eviction for long-running sessions
|
|
2617
|
-
* - Critical for ERP, BI, hotel systems
|
|
2712
|
+
* Implements DateClock interface with a fixed date.
|
|
2618
2713
|
*
|
|
2619
2714
|
* Usage:
|
|
2620
2715
|
* ```typescript
|
|
2621
|
-
*
|
|
2622
|
-
*
|
|
2623
|
-
* private highlighterCache: RangeHighlighterCache
|
|
2624
|
-
* ) {}
|
|
2625
|
-
*
|
|
2626
|
-
* const grid = this.gridCache.get(monthDate, weekStart);
|
|
2627
|
-
* const decorated = this.highlighterCache.get(grid, {
|
|
2628
|
-
* start, end, hoverDate, disabledDates
|
|
2629
|
-
* });
|
|
2630
|
-
*
|
|
2631
|
-
* // Optional: Windowed rendering (v3.9.0+)
|
|
2632
|
-
* const windowSize = 3; // Render only 3 weeks
|
|
2633
|
-
* const visibleWeeks = getVisibleWeeks(
|
|
2634
|
-
* decorated.weeks,
|
|
2635
|
-
* weekStartIndex,
|
|
2636
|
-
* windowSize
|
|
2637
|
-
* );
|
|
2638
|
-
* // decorated.cells[0].iso => '2026-02-01'
|
|
2639
|
-
* // decorated.cells[0].isInRange => true
|
|
2716
|
+
* const clock = new FixedClock(new Date(2026, 1, 21)); // Feb 21, 2026
|
|
2717
|
+
* const now = clock.now(); // Always returns Feb 21, 2026 (cloned)
|
|
2640
2718
|
* ```
|
|
2719
|
+
*
|
|
2720
|
+
* Why clone?
|
|
2721
|
+
* - Prevents test pollution if consumer mutates the returned date
|
|
2722
|
+
* - Each call to now() returns a fresh Date instance
|
|
2641
2723
|
*/
|
|
2642
|
-
|
|
2724
|
+
class FixedClock {
|
|
2725
|
+
fixedDate;
|
|
2726
|
+
constructor(fixedDate) {
|
|
2727
|
+
this.fixedDate = fixedDate;
|
|
2728
|
+
}
|
|
2729
|
+
/**
|
|
2730
|
+
* Returns a cloned copy of the fixed date
|
|
2731
|
+
*
|
|
2732
|
+
* Cloning prevents test pollution from mutations
|
|
2733
|
+
*/
|
|
2734
|
+
now() {
|
|
2735
|
+
return new Date(this.fixedDate);
|
|
2736
|
+
}
|
|
2737
|
+
}
|
|
2738
|
+
|
|
2739
|
+
/**
|
|
2740
|
+
* Date Test Helpers
|
|
2741
|
+
*
|
|
2742
|
+
* Utilities for creating deterministic dates in tests.
|
|
2743
|
+
*/
|
|
2744
|
+
/**
|
|
2745
|
+
* Create a Date in local timezone
|
|
2746
|
+
*
|
|
2747
|
+
* @param year - Full year (e.g., 2026)
|
|
2748
|
+
* @param month - Month 1-12 (natural notation)
|
|
2749
|
+
* @param day - Day of month 1-31
|
|
2750
|
+
* @returns Date at 00:00:00.000 local time
|
|
2751
|
+
*
|
|
2752
|
+
* @example
|
|
2753
|
+
* makeDate(2026, 2, 21) // Feb 21, 2026 00:00:00.000
|
|
2754
|
+
*/
|
|
2755
|
+
function makeDate(year, month, day) {
|
|
2756
|
+
return new Date(year, month - 1, day); // month-1 because Date uses 0-indexed months
|
|
2757
|
+
}
|
|
2758
|
+
/**
|
|
2759
|
+
* Create ISO date string (YYYY-MM-DD)
|
|
2760
|
+
*
|
|
2761
|
+
* @param year - Full year (e.g., 2026)
|
|
2762
|
+
* @param month - Month 1-12 (natural notation)
|
|
2763
|
+
* @param day - Day of month 1-31
|
|
2764
|
+
* @returns ISO date string
|
|
2765
|
+
*
|
|
2766
|
+
* @example
|
|
2767
|
+
* iso(2026, 2, 21) // '2026-02-21'
|
|
2768
|
+
*/
|
|
2769
|
+
function iso(year, month, day) {
|
|
2770
|
+
const y = String(year).padStart(4, '0');
|
|
2771
|
+
const m = String(month).padStart(2, '0');
|
|
2772
|
+
const d = String(day).padStart(2, '0');
|
|
2773
|
+
return `${y}-${m}-${d}`;
|
|
2774
|
+
}
|
|
2775
|
+
/**
|
|
2776
|
+
* Compare two dates for equality (ignoring time)
|
|
2777
|
+
*
|
|
2778
|
+
* @param a - First date
|
|
2779
|
+
* @param b - Second date
|
|
2780
|
+
* @returns true if same calendar day
|
|
2781
|
+
*/
|
|
2782
|
+
function isSameCalendarDay(a, b) {
|
|
2783
|
+
return (a.getFullYear() === b.getFullYear() &&
|
|
2784
|
+
a.getMonth() === b.getMonth() &&
|
|
2785
|
+
a.getDate() === b.getDate());
|
|
2786
|
+
}
|
|
2787
|
+
|
|
2788
|
+
/**
|
|
2789
|
+
* Testing Utilities
|
|
2790
|
+
*
|
|
2791
|
+
* Helpers for deterministic date range testing.
|
|
2792
|
+
*/
|
|
2793
|
+
|
|
2794
|
+
/**
|
|
2795
|
+
* INTERNAL API for @oneluiz/dual-datepicker
|
|
2796
|
+
*
|
|
2797
|
+
* This file exports implementation details required by the component itself.
|
|
2798
|
+
* These exports are NOT part of the public API and may change without notice.
|
|
2799
|
+
*
|
|
2800
|
+
* ⚠️ DO NOT import from this file in your application code.
|
|
2801
|
+
* ⚠️ Use the public API from '@oneluiz/dual-datepicker/core' instead.
|
|
2802
|
+
*
|
|
2803
|
+
* @internal
|
|
2804
|
+
* @packageDocumentation
|
|
2805
|
+
* @since v4.0.0
|
|
2806
|
+
*/
|
|
2807
|
+
/**
|
|
2808
|
+
* CalendarGridFactory - Deterministic grid generation
|
|
2809
|
+
*
|
|
2810
|
+
* Creates normalized 42-cell grids (6 weeks × 7 days).
|
|
2811
|
+
* Pure function approach with no side effects.
|
|
2812
|
+
*
|
|
2813
|
+
* @internal
|
|
2814
|
+
*/
|
|
2815
|
+
|
|
2643
2816
|
class DualDatepickerComponent {
|
|
2644
2817
|
elementRef;
|
|
2645
2818
|
placeholder = 'Select date range';
|
|
2646
2819
|
set startDate(value) {
|
|
2647
2820
|
if (value) {
|
|
2648
|
-
const date = this.dateAdapter.
|
|
2821
|
+
const date = this.dateAdapter.parseISODate(value);
|
|
2649
2822
|
if (date)
|
|
2650
2823
|
this.rangeStore.setStart(date);
|
|
2651
2824
|
}
|
|
@@ -2656,7 +2829,7 @@ class DualDatepickerComponent {
|
|
|
2656
2829
|
}
|
|
2657
2830
|
set endDate(value) {
|
|
2658
2831
|
if (value) {
|
|
2659
|
-
const date = this.dateAdapter.
|
|
2832
|
+
const date = this.dateAdapter.parseISODate(value);
|
|
2660
2833
|
if (date)
|
|
2661
2834
|
this.rangeStore.setEnd(date);
|
|
2662
2835
|
}
|
|
@@ -2713,7 +2886,7 @@ class DualDatepickerComponent {
|
|
|
2713
2886
|
multiDateRangeChange = new EventEmitter();
|
|
2714
2887
|
multiDateRangeSelected = new EventEmitter();
|
|
2715
2888
|
// Date adapter injection
|
|
2716
|
-
dateAdapter = inject(DATE_ADAPTER
|
|
2889
|
+
dateAdapter = inject(DATE_ADAPTER);
|
|
2717
2890
|
// Calendar grid cache (v3.7.0+) - memoizes month grid generation
|
|
2718
2891
|
gridCache = inject(CalendarGridCache);
|
|
2719
2892
|
// Range highlighter cache (v3.8.0+) - memoizes decorations
|
|
@@ -2721,15 +2894,15 @@ class DualDatepickerComponent {
|
|
|
2721
2894
|
// Headless store for date range state (v3.5.0+)
|
|
2722
2895
|
rangeStore = inject(DualDateRangeStore);
|
|
2723
2896
|
// UI-only signals
|
|
2724
|
-
showDatePicker = signal(false);
|
|
2725
|
-
currentMonth = signal(this.dateAdapter.
|
|
2726
|
-
previousMonth = signal(this.dateAdapter.
|
|
2727
|
-
currentMonthDays = signal([]);
|
|
2728
|
-
previousMonthDays = signal([]);
|
|
2729
|
-
isDisabled = signal(false);
|
|
2730
|
-
showStartTimePicker = signal(false);
|
|
2731
|
-
showEndTimePicker = signal(false);
|
|
2732
|
-
hoverDate = signal(null);
|
|
2897
|
+
showDatePicker = signal(false, ...(ngDevMode ? [{ debugName: "showDatePicker" }] : /* istanbul ignore next */ []));
|
|
2898
|
+
currentMonth = signal(this.dateAdapter.normalize(new Date()), ...(ngDevMode ? [{ debugName: "currentMonth" }] : /* istanbul ignore next */ []));
|
|
2899
|
+
previousMonth = signal(this.dateAdapter.normalize(new Date()), ...(ngDevMode ? [{ debugName: "previousMonth" }] : /* istanbul ignore next */ []));
|
|
2900
|
+
currentMonthDays = signal([], ...(ngDevMode ? [{ debugName: "currentMonthDays" }] : /* istanbul ignore next */ []));
|
|
2901
|
+
previousMonthDays = signal([], ...(ngDevMode ? [{ debugName: "previousMonthDays" }] : /* istanbul ignore next */ []));
|
|
2902
|
+
isDisabled = signal(false, ...(ngDevMode ? [{ debugName: "isDisabled" }] : /* istanbul ignore next */ []));
|
|
2903
|
+
showStartTimePicker = signal(false, ...(ngDevMode ? [{ debugName: "showStartTimePicker" }] : /* istanbul ignore next */ []));
|
|
2904
|
+
showEndTimePicker = signal(false, ...(ngDevMode ? [{ debugName: "showEndTimePicker" }] : /* istanbul ignore next */ []));
|
|
2905
|
+
hoverDate = signal(null, ...(ngDevMode ? [{ debugName: "hoverDate" }] : /* istanbul ignore next */ []));
|
|
2733
2906
|
/**
|
|
2734
2907
|
* Virtual Weeks State (v3.9.0+)
|
|
2735
2908
|
*
|
|
@@ -2739,8 +2912,8 @@ class DualDatepickerComponent {
|
|
|
2739
2912
|
*
|
|
2740
2913
|
* Reset to 0 when month changes for consistent UX.
|
|
2741
2914
|
*/
|
|
2742
|
-
previousMonthWeekStart = signal(0);
|
|
2743
|
-
currentMonthWeekStart = signal(0);
|
|
2915
|
+
previousMonthWeekStart = signal(0, ...(ngDevMode ? [{ debugName: "previousMonthWeekStart" }] : /* istanbul ignore next */ []));
|
|
2916
|
+
currentMonthWeekStart = signal(0, ...(ngDevMode ? [{ debugName: "currentMonthWeekStart" }] : /* istanbul ignore next */ []));
|
|
2744
2917
|
/**
|
|
2745
2918
|
* Computed: Visible weeks for windowed rendering (v3.9.0+)
|
|
2746
2919
|
*
|
|
@@ -2761,7 +2934,7 @@ class DualDatepickerComponent {
|
|
|
2761
2934
|
}
|
|
2762
2935
|
const visibleWeeks = getVisibleWeeks(allWeeks, this.previousMonthWeekStart(), this.virtualWeeks.windowSize);
|
|
2763
2936
|
return visibleWeeks.flat();
|
|
2764
|
-
});
|
|
2937
|
+
}, ...(ngDevMode ? [{ debugName: "previousMonthVisibleDays" }] : /* istanbul ignore next */ []));
|
|
2765
2938
|
currentMonthVisibleDays = computed(() => {
|
|
2766
2939
|
const allDays = this.currentMonthDays();
|
|
2767
2940
|
if (!this.virtualWeeks || !isVirtualWeeksEnabled(this.virtualWeeks.windowSize, 6)) {
|
|
@@ -2774,7 +2947,7 @@ class DualDatepickerComponent {
|
|
|
2774
2947
|
}
|
|
2775
2948
|
const visibleWeeks = getVisibleWeeks(allWeeks, this.currentMonthWeekStart(), this.virtualWeeks.windowSize);
|
|
2776
2949
|
return visibleWeeks.flat();
|
|
2777
|
-
});
|
|
2950
|
+
}, ...(ngDevMode ? [{ debugName: "currentMonthVisibleDays" }] : /* istanbul ignore next */ []));
|
|
2778
2951
|
// Computed time properties from store
|
|
2779
2952
|
get startHour() {
|
|
2780
2953
|
const time = this.rangeStore.startTime();
|
|
@@ -2801,18 +2974,18 @@ class DualDatepickerComponent {
|
|
|
2801
2974
|
this.rangeStore.setEndTime(timeStr);
|
|
2802
2975
|
}
|
|
2803
2976
|
// Multi-range support (UI-specific)
|
|
2804
|
-
selectedRanges = signal([]);
|
|
2805
|
-
currentRangeIndex = signal(-1);
|
|
2977
|
+
selectedRanges = signal([], ...(ngDevMode ? [{ debugName: "selectedRanges" }] : /* istanbul ignore next */ []));
|
|
2978
|
+
currentRangeIndex = signal(-1, ...(ngDevMode ? [{ debugName: "currentRangeIndex" }] : /* istanbul ignore next */ []));
|
|
2806
2979
|
// Keyboard navigation (UI-specific)
|
|
2807
|
-
focusedDay = signal(null);
|
|
2980
|
+
focusedDay = signal(null, ...(ngDevMode ? [{ debugName: "focusedDay" }] : /* istanbul ignore next */ []));
|
|
2808
2981
|
// Computed values
|
|
2809
|
-
currentMonthName = computed(() => this.getMonthName(this.currentMonth()));
|
|
2810
|
-
previousMonthName = computed(() => this.getMonthName(this.previousMonth()));
|
|
2811
|
-
weekDayNames = computed(() => this.getDayNames());
|
|
2982
|
+
currentMonthName = computed(() => this.getMonthName(this.currentMonth()), ...(ngDevMode ? [{ debugName: "currentMonthName" }] : /* istanbul ignore next */ []));
|
|
2983
|
+
previousMonthName = computed(() => this.getMonthName(this.previousMonth()), ...(ngDevMode ? [{ debugName: "previousMonthName" }] : /* istanbul ignore next */ []));
|
|
2984
|
+
weekDayNames = computed(() => this.getDayNames(), ...(ngDevMode ? [{ debugName: "weekDayNames" }] : /* istanbul ignore next */ []));
|
|
2812
2985
|
// Computed from store
|
|
2813
|
-
dateRangeText = computed(() => this.rangeStore.rangeText());
|
|
2814
|
-
selectingStartDate = computed(() => this.rangeStore.selectingStart());
|
|
2815
|
-
hasPendingChanges = computed(() => this.rangeStore.hasPendingChanges());
|
|
2986
|
+
dateRangeText = computed(() => this.rangeStore.rangeText(), ...(ngDevMode ? [{ debugName: "dateRangeText" }] : /* istanbul ignore next */ []));
|
|
2987
|
+
selectingStartDate = computed(() => this.rangeStore.selectingStart(), ...(ngDevMode ? [{ debugName: "selectingStartDate" }] : /* istanbul ignore next */ []));
|
|
2988
|
+
hasPendingChanges = computed(() => this.rangeStore.hasPendingChanges(), ...(ngDevMode ? [{ debugName: "hasPendingChanges" }] : /* istanbul ignore next */ []));
|
|
2816
2989
|
// Computed for template access to pending dates (requireApply mode)
|
|
2817
2990
|
get pendingStartDate() {
|
|
2818
2991
|
// In requireApply mode, store keeps pending values
|
|
@@ -2936,7 +3109,7 @@ class DualDatepickerComponent {
|
|
|
2936
3109
|
}
|
|
2937
3110
|
else {
|
|
2938
3111
|
// startDate is not visible, focus on today if visible
|
|
2939
|
-
const today = this.dateAdapter.
|
|
3112
|
+
const today = this.dateAdapter.toISODate(this.dateAdapter.normalize(new Date()));
|
|
2940
3113
|
const inCurrMonth = this.isDateInMonth(today, this.currentMonth());
|
|
2941
3114
|
if (inCurrMonth) {
|
|
2942
3115
|
this.focusedDay.set({ date: today, monthIndex: 1 });
|
|
@@ -2958,7 +3131,7 @@ class DualDatepickerComponent {
|
|
|
2958
3131
|
}
|
|
2959
3132
|
else {
|
|
2960
3133
|
// endDate is not visible, focus on today if visible
|
|
2961
|
-
const today = this.dateAdapter.
|
|
3134
|
+
const today = this.dateAdapter.toISODate(this.dateAdapter.normalize(new Date()));
|
|
2962
3135
|
const inCurrMonth = this.isDateInMonth(today, this.currentMonth());
|
|
2963
3136
|
if (inCurrMonth) {
|
|
2964
3137
|
this.focusedDay.set({ date: today, monthIndex: 1 });
|
|
@@ -2974,7 +3147,7 @@ class DualDatepickerComponent {
|
|
|
2974
3147
|
}
|
|
2975
3148
|
else {
|
|
2976
3149
|
// Focus on today if visible, otherwise first day of current month
|
|
2977
|
-
const today = this.dateAdapter.
|
|
3150
|
+
const today = this.dateAdapter.toISODate(this.dateAdapter.normalize(new Date()));
|
|
2978
3151
|
const inCurrMonth = this.isDateInMonth(today, this.currentMonth());
|
|
2979
3152
|
if (inCurrMonth) {
|
|
2980
3153
|
this.focusedDay.set({ date: today, monthIndex: 1 });
|
|
@@ -2990,7 +3163,7 @@ class DualDatepickerComponent {
|
|
|
2990
3163
|
}
|
|
2991
3164
|
}
|
|
2992
3165
|
isDateInMonth(dateStr, monthDate) {
|
|
2993
|
-
const date = this.dateAdapter.
|
|
3166
|
+
const date = this.dateAdapter.parseISODate(dateStr);
|
|
2994
3167
|
if (!date)
|
|
2995
3168
|
return false;
|
|
2996
3169
|
const year = this.dateAdapter.getYear(date);
|
|
@@ -3005,7 +3178,7 @@ class DualDatepickerComponent {
|
|
|
3005
3178
|
this.initializeFocus();
|
|
3006
3179
|
return;
|
|
3007
3180
|
}
|
|
3008
|
-
const currentDate = this.dateAdapter.
|
|
3181
|
+
const currentDate = this.dateAdapter.parseISODate(focused.date);
|
|
3009
3182
|
if (!currentDate)
|
|
3010
3183
|
return;
|
|
3011
3184
|
const newDate = this.dateAdapter.addDays(currentDate, direction);
|
|
@@ -3042,7 +3215,7 @@ class DualDatepickerComponent {
|
|
|
3042
3215
|
this.initializeFocus();
|
|
3043
3216
|
return;
|
|
3044
3217
|
}
|
|
3045
|
-
const currentDate = this.dateAdapter.
|
|
3218
|
+
const currentDate = this.dateAdapter.parseISODate(focused.date);
|
|
3046
3219
|
if (!currentDate)
|
|
3047
3220
|
return;
|
|
3048
3221
|
const newDate = this.dateAdapter.addDays(currentDate, direction * 7); // Move by week
|
|
@@ -3093,17 +3266,17 @@ class DualDatepickerComponent {
|
|
|
3093
3266
|
this.initializeFocus();
|
|
3094
3267
|
return;
|
|
3095
3268
|
}
|
|
3096
|
-
const currentDate = this.dateAdapter.
|
|
3269
|
+
const currentDate = this.dateAdapter.parseISODate(focused.date);
|
|
3097
3270
|
if (!currentDate)
|
|
3098
3271
|
return;
|
|
3099
3272
|
const currentYear = this.dateAdapter.getYear(currentDate);
|
|
3100
3273
|
const currentMonth = this.dateAdapter.getMonth(currentDate);
|
|
3101
3274
|
const currentDay = this.dateAdapter.getDate(currentDate);
|
|
3102
|
-
const newDate = this.dateAdapter.
|
|
3275
|
+
const newDate = this.dateAdapter.normalize(new Date(currentYear + direction, currentMonth, currentDay));
|
|
3103
3276
|
const newDateStr = this.formatDate(newDate);
|
|
3104
3277
|
// Update months to show the new year
|
|
3105
|
-
this.currentMonth.set(this.dateAdapter.
|
|
3106
|
-
this.previousMonth.set(this.dateAdapter.
|
|
3278
|
+
this.currentMonth.set(this.dateAdapter.normalize(new Date(currentYear + direction, currentMonth, 1)));
|
|
3279
|
+
this.previousMonth.set(this.dateAdapter.normalize(new Date(currentYear + direction, currentMonth - 1, 1)));
|
|
3107
3280
|
this.generateCalendars();
|
|
3108
3281
|
const inPrevMonth = this.isDateInMonth(newDateStr, this.previousMonth());
|
|
3109
3282
|
this.focusedDay.set({
|
|
@@ -3153,12 +3326,12 @@ class DualDatepickerComponent {
|
|
|
3153
3326
|
});
|
|
3154
3327
|
// Initialize dates in store if provided
|
|
3155
3328
|
if (this.startDate) {
|
|
3156
|
-
const date = this.dateAdapter.
|
|
3329
|
+
const date = this.dateAdapter.parseISODate(this.startDate);
|
|
3157
3330
|
if (date)
|
|
3158
3331
|
this.rangeStore.setStart(date);
|
|
3159
3332
|
}
|
|
3160
3333
|
if (this.endDate) {
|
|
3161
|
-
const date = this.dateAdapter.
|
|
3334
|
+
const date = this.dateAdapter.parseISODate(this.endDate);
|
|
3162
3335
|
if (date)
|
|
3163
3336
|
this.rangeStore.setEnd(date);
|
|
3164
3337
|
}
|
|
@@ -3170,12 +3343,12 @@ class DualDatepickerComponent {
|
|
|
3170
3343
|
if (changes['startDate'] || changes['endDate']) {
|
|
3171
3344
|
// Sync with store
|
|
3172
3345
|
if (changes['startDate'] && this.startDate) {
|
|
3173
|
-
const date = this.dateAdapter.
|
|
3346
|
+
const date = this.dateAdapter.parseISODate(this.startDate);
|
|
3174
3347
|
if (date)
|
|
3175
3348
|
this.rangeStore.setStart(date);
|
|
3176
3349
|
}
|
|
3177
3350
|
if (changes['endDate'] && this.endDate) {
|
|
3178
|
-
const date = this.dateAdapter.
|
|
3351
|
+
const date = this.dateAdapter.parseISODate(this.endDate);
|
|
3179
3352
|
if (date)
|
|
3180
3353
|
this.rangeStore.setEnd(date);
|
|
3181
3354
|
}
|
|
@@ -3203,7 +3376,7 @@ class DualDatepickerComponent {
|
|
|
3203
3376
|
formatDateDisplay(dateStr) {
|
|
3204
3377
|
if (!dateStr)
|
|
3205
3378
|
return '';
|
|
3206
|
-
const date = this.dateAdapter.
|
|
3379
|
+
const date = this.dateAdapter.parseISODate(dateStr);
|
|
3207
3380
|
if (!date)
|
|
3208
3381
|
return '';
|
|
3209
3382
|
const year = this.dateAdapter.getYear(date);
|
|
@@ -3238,7 +3411,7 @@ class DualDatepickerComponent {
|
|
|
3238
3411
|
const currentMonthValue = this.currentMonth();
|
|
3239
3412
|
const year = this.dateAdapter.getYear(currentMonthValue);
|
|
3240
3413
|
const month = this.dateAdapter.getMonth(currentMonthValue);
|
|
3241
|
-
const previousMonthDate = this.dateAdapter.
|
|
3414
|
+
const previousMonthDate = this.dateAdapter.normalize(new Date(year, month - 1, 1));
|
|
3242
3415
|
this.previousMonth.set(previousMonthDate);
|
|
3243
3416
|
this.generateCalendars();
|
|
3244
3417
|
// Initialize keyboard focus only if keyboard navigation is enabled
|
|
@@ -3456,7 +3629,7 @@ class DualDatepickerComponent {
|
|
|
3456
3629
|
selectDay(dayObj) {
|
|
3457
3630
|
if (!dayObj.isCurrentMonth || this.isDisabled() || dayObj.isDisabled)
|
|
3458
3631
|
return;
|
|
3459
|
-
const selectedDate = this.dateAdapter.
|
|
3632
|
+
const selectedDate = this.dateAdapter.parseISODate(dayObj.date);
|
|
3460
3633
|
if (!selectedDate)
|
|
3461
3634
|
return;
|
|
3462
3635
|
if (this.multiRange) {
|
|
@@ -3568,11 +3741,11 @@ class DualDatepickerComponent {
|
|
|
3568
3741
|
const currentMonthValue = this.currentMonth();
|
|
3569
3742
|
const year = this.dateAdapter.getYear(currentMonthValue);
|
|
3570
3743
|
const month = this.dateAdapter.getMonth(currentMonthValue);
|
|
3571
|
-
const newCurrentMonth = this.dateAdapter.
|
|
3744
|
+
const newCurrentMonth = this.dateAdapter.normalize(new Date(year, month + direction, 1));
|
|
3572
3745
|
this.currentMonth.set(newCurrentMonth);
|
|
3573
3746
|
const newYear = this.dateAdapter.getYear(newCurrentMonth);
|
|
3574
3747
|
const newMonth = this.dateAdapter.getMonth(newCurrentMonth);
|
|
3575
|
-
const newPreviousMonth = this.dateAdapter.
|
|
3748
|
+
const newPreviousMonth = this.dateAdapter.normalize(new Date(newYear, newMonth - 1, 1));
|
|
3576
3749
|
this.previousMonth.set(newPreviousMonth);
|
|
3577
3750
|
this.generateCalendars();
|
|
3578
3751
|
}
|
|
@@ -3589,8 +3762,8 @@ class DualDatepickerComponent {
|
|
|
3589
3762
|
return;
|
|
3590
3763
|
}
|
|
3591
3764
|
const range = preset.getValue();
|
|
3592
|
-
const startDate = this.dateAdapter.
|
|
3593
|
-
const endDate = this.dateAdapter.
|
|
3765
|
+
const startDate = this.dateAdapter.parseISODate(range.start);
|
|
3766
|
+
const endDate = this.dateAdapter.parseISODate(range.end);
|
|
3594
3767
|
if (startDate && endDate) {
|
|
3595
3768
|
this.rangeStore.setRange(startDate, endDate);
|
|
3596
3769
|
}
|
|
@@ -3821,16 +3994,16 @@ class DualDatepickerComponent {
|
|
|
3821
3994
|
setDisabledState(isDisabled) {
|
|
3822
3995
|
this.isDisabled.set(isDisabled);
|
|
3823
3996
|
}
|
|
3824
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
3825
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "
|
|
3997
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: DualDatepickerComponent, deps: [{ token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Component });
|
|
3998
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: DualDatepickerComponent, isStandalone: true, selector: "ngx-dual-datepicker", inputs: { placeholder: "placeholder", startDate: "startDate", endDate: "endDate", showPresets: "showPresets", showClearButton: "showClearButton", multiRange: "multiRange", closeOnSelection: "closeOnSelection", closeOnPresetSelection: "closeOnPresetSelection", closeOnClickOutside: "closeOnClickOutside", enableKeyboardNavigation: "enableKeyboardNavigation", presets: "presets", theme: "theme", inputBackgroundColor: "inputBackgroundColor", inputTextColor: "inputTextColor", inputBorderColor: "inputBorderColor", inputBorderColorHover: "inputBorderColorHover", inputBorderColorFocus: "inputBorderColorFocus", inputPadding: "inputPadding", locale: "locale", disabledDates: "disabledDates", displayFormat: "displayFormat", requireApply: "requireApply", enableTimePicker: "enableTimePicker", timeFormat: "timeFormat", minuteStep: "minuteStep", defaultStartTime: "defaultStartTime", defaultEndTime: "defaultEndTime", virtualWeeks: "virtualWeeks" }, outputs: { dateRangeChange: "dateRangeChange", dateRangeSelected: "dateRangeSelected", multiDateRangeChange: "multiDateRangeChange", multiDateRangeSelected: "multiDateRangeSelected" }, host: { listeners: { "document:click": "onClickOutside($event)", "keydown": "handleKeyboardNavigation($event)" } }, providers: [
|
|
3826
3999
|
{
|
|
3827
4000
|
provide: NG_VALUE_ACCESSOR,
|
|
3828
4001
|
useExisting: forwardRef(() => DualDatepickerComponent),
|
|
3829
4002
|
multi: true
|
|
3830
4003
|
},
|
|
3831
4004
|
{
|
|
3832
|
-
provide: DATE_ADAPTER
|
|
3833
|
-
useClass: NativeDateAdapter
|
|
4005
|
+
provide: DATE_ADAPTER,
|
|
4006
|
+
useClass: NativeDateAdapter
|
|
3834
4007
|
},
|
|
3835
4008
|
{
|
|
3836
4009
|
provide: APP_INITIALIZER,
|
|
@@ -3843,9 +4016,9 @@ class DualDatepickerComponent {
|
|
|
3843
4016
|
},
|
|
3844
4017
|
deps: [PresetRegistry]
|
|
3845
4018
|
}
|
|
3846
|
-
], usesOnChanges: true, ngImport: i0, template: "<div class=\"datepicker-wrapper\" \n [class]=\"'theme-' + theme\"\n [style.--input-border-hover]=\"inputBorderColorHover\"\n [style.--input-border-focus]=\"inputBorderColorFocus\">\n <input \n type=\"text\" \n class=\"datepicker-input\" \n [value]=\"dateRangeText()\" \n (click)=\"toggleDatePicker()\" \n [placeholder]=\"placeholder\"\n [disabled]=\"isDisabled()\"\n [attr.aria-label]=\"placeholder\"\n [attr.aria-expanded]=\"showDatePicker()\"\n [attr.aria-haspopup]=\"'dialog'\"\n role=\"combobox\"\n [ngStyle]=\"{\n 'background-color': inputBackgroundColor,\n 'color': inputTextColor,\n 'border-color': inputBorderColor,\n 'padding': inputPadding\n }\"\n readonly>\n\n @if (showDatePicker()) {\n <div class=\"date-picker-dropdown\">\n @if (showPresets) {\n <div class=\"date-picker-presets\">\n @for (preset of presets; track preset.label) {\n <button type=\"button\" (click)=\"selectPresetRange(preset)\">{{ preset.label }}</button>\n }\n <button type=\"button\" class=\"btn-close-calendar\" (click)=\"closeDatePicker()\" title=\"Close\">\n \u00D7\n </button>\n </div>\n }\n\n @if (!showPresets) {\n <div class=\"date-picker-header-only-close\">\n <button type=\"button\" class=\"btn-close-calendar\" (click)=\"closeDatePicker()\" title=\"Close\">\n \u00D7\n </button>\n </div>\n }\n\n <!-- Calendars -->\n <div class=\"date-picker-calendars\">\n <!-- Previous month calendar -->\n <div class=\"date-picker-calendar\">\n <div class=\"date-picker-header\">\n <button type=\"button\" (click)=\"changeMonth(-1)\">\n <svg width=\"16\" height=\"16\" fill=\"currentColor\" viewBox=\"0 0 16 16\">\n <path fill-rule=\"evenodd\" d=\"M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z\"/>\n </svg>\n </button>\n <span>{{ previousMonthName() }}</span>\n <button type=\"button\" style=\"visibility: hidden;\">\n <svg width=\"16\" height=\"16\" fill=\"currentColor\" viewBox=\"0 0 16 16\">\n <path fill-rule=\"evenodd\" d=\"M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z\"/>\n </svg>\n </button>\n </div>\n <div class=\"date-picker-weekdays\">\n @for (dayName of weekDayNames(); track $index) {\n <span>{{ dayName }}</span>\n }\n </div>\n <div class=\"date-picker-days\">\n @for (dayObj of previousMonthVisibleDays(); track dayObj.date || $index) {\n <button \n type=\"button\"\n class=\"date-picker-day\" \n [class.empty]=\"!dayObj.isCurrentMonth\"\n [class.selected]=\"dayObj.isStart || dayObj.isEnd\"\n [class.in-range]=\"dayObj.inRange && !dayObj.isStart && !dayObj.isEnd\"\n [class.in-hover-range]=\"dayObj.inHoverRange && !dayObj.isStart && !dayObj.isEnd\"\n [class.disabled]=\"dayObj.isDisabled\"\n [class.keyboard-focused]=\"enableKeyboardNavigation && hasKeyboardFocus(dayObj.date, 0)\"\n [attr.tabindex]=\"enableKeyboardNavigation && dayObj.isCurrentMonth && hasKeyboardFocus(dayObj.date, 0) ? 0 : -1\"\n [attr.aria-label]=\"formatDateDisplay(dayObj.date)\"\n [attr.aria-selected]=\"dayObj.isStart || dayObj.isEnd\"\n [attr.aria-current]=\"dayObj.isStart ? 'date' : null\"\n [attr.aria-disabled]=\"dayObj.isDisabled\"\n (click)=\"selectDay(dayObj)\"\n (mouseenter)=\"onDayHover(dayObj)\"\n (mouseleave)=\"clearDayHover()\"\n [disabled]=\"!dayObj.isCurrentMonth || dayObj.isDisabled\">\n {{ dayObj.day }}\n </button>\n }\n </div>\n </div>\n\n <!-- Current month calendar -->\n <div class=\"date-picker-calendar\">\n <div class=\"date-picker-header\">\n <button type=\"button\" style=\"visibility: hidden;\">\n <svg width=\"16\" height=\"16\" fill=\"currentColor\" viewBox=\"0 0 16 16\">\n <path fill-rule=\"evenodd\" d=\"M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z\"/>\n </svg>\n </button>\n <span>{{ currentMonthName() }}</span>\n <button type=\"button\" (click)=\"changeMonth(1)\">\n <svg width=\"16\" height=\"16\" fill=\"currentColor\" viewBox=\"0 0 16 16\">\n <path fill-rule=\"evenodd\" d=\"M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z\"/>\n </svg>\n </button>\n </div>\n <div class=\"date-picker-weekdays\">\n @for (dayName of weekDayNames(); track $index) {\n <span>{{ dayName }}</span>\n }\n </div>\n <div class=\"date-picker-days\">\n @for (dayObj of currentMonthVisibleDays(); track dayObj.date || $index) {\n <button \n type=\"button\"\n class=\"date-picker-day\" \n [class.empty]=\"!dayObj.isCurrentMonth\"\n [class.selected]=\"dayObj.isStart || dayObj.isEnd\"\n [class.in-range]=\"dayObj.inRange && !dayObj.isStart && !dayObj.isEnd\"\n [class.in-hover-range]=\"dayObj.inHoverRange && !dayObj.isStart && !dayObj.isEnd\"\n [class.disabled]=\"dayObj.isDisabled\"\n [class.keyboard-focused]=\"enableKeyboardNavigation && hasKeyboardFocus(dayObj.date, 1)\"\n [attr.tabindex]=\"enableKeyboardNavigation && dayObj.isCurrentMonth && hasKeyboardFocus(dayObj.date, 1) ? 0 : -1\"\n [attr.aria-label]=\"formatDateDisplay(dayObj.date)\"\n [attr.aria-selected]=\"dayObj.isStart || dayObj.isEnd\"\n [attr.aria-current]=\"dayObj.isStart ? 'date' : null\"\n [attr.aria-disabled]=\"dayObj.isDisabled\"\n (click)=\"selectDay(dayObj)\"\n (mouseenter)=\"onDayHover(dayObj)\"\n (mouseleave)=\"clearDayHover()\"\n [disabled]=\"!dayObj.isCurrentMonth || dayObj.isDisabled\">\n {{ dayObj.day }}\n </button>\n }\n </div>\n </div>\n </div>\n\n <!-- Multi-Range List -->\n @if (multiRange && selectedRanges().length > 0) {\n <div class=\"multi-range-list\">\n <div class=\"multi-range-header\">\n <span class=\"multi-range-title\">Selected Ranges ({{ selectedRanges().length }})</span>\n </div>\n <div class=\"multi-range-items\">\n @for (range of selectedRanges(); track $index) {\n <div class=\"multi-range-item\">\n <span class=\"multi-range-text\">{{ range.rangeText }}</span>\n <button \n type=\"button\" \n class=\"btn-remove-range\" \n (click)=\"removeRange($index)\"\n title=\"Remove this range\">\n <svg width=\"14\" height=\"14\" fill=\"currentColor\" viewBox=\"0 0 16 16\">\n <path d=\"M2.146 2.854a.5.5 0 1 1 .708-.708L8 7.293l5.146-5.147a.5.5 0 0 1 .708.708L8.707 8l5.147 5.146a.5.5 0 0 1-.708.708L8 8.707l-5.146 5.147a.5.5 0 0 1-.708-.708L7.293 8 2.146 2.854Z\"/>\n </svg>\n </button>\n </div>\n }\n </div>\n </div>\n }\n\n <!-- Time Picker -->\n @if (enableTimePicker) {\n <div class=\"time-picker-container\">\n <!-- Start Time -->\n <div class=\"time-picker-section\">\n <div class=\"time-picker-label\">Start Time</div>\n <div class=\"time-picker-inputs\">\n <div class=\"time-input-group\">\n <button \n type=\"button\" \n class=\"time-btn time-btn-up\" \n (click)=\"incrementStartHour()\"\n title=\"Increment hour\">\n <svg width=\"12\" height=\"12\" fill=\"currentColor\" viewBox=\"0 0 16 16\">\n <path d=\"M7.247 4.86l-4.796 5.481c-.566.647-.106 1.659.753 1.659h9.592a1 1 0 0 0 .753-1.659l-4.796-5.48a1 1 0 0 0-1.506 0z\"/>\n </svg>\n </button>\n <input\n type=\"text\"\n class=\"time-input\"\n [value]=\"startHour.toString().padStart(2, '0')\"\n readonly\n title=\"Start hour\">\n <button \n type=\"button\" \n class=\"time-btn time-btn-down\" \n (click)=\"decrementStartHour()\"\n title=\"Decrement hour\">\n <svg width=\"12\" height=\"12\" fill=\"currentColor\" viewBox=\"0 0 16 16\">\n <path d=\"M7.247 11.14l-4.796-5.481C1.885 5.013 2.345 4 3.204 4h9.592a1 1 0 0 1 .753 1.659l-4.796 5.48a1 1 0 0 1-1.506 0z\"/>\n </svg>\n </button>\n </div>\n <span class=\"time-separator\">:</span>\n <div class=\"time-input-group\">\n <button \n type=\"button\" \n class=\"time-btn time-btn-up\" \n (click)=\"incrementStartMinute()\"\n title=\"Increment minute\">\n <svg width=\"12\" height=\"12\" fill=\"currentColor\" viewBox=\"0 0 16 16\">\n <path d=\"M7.247 4.86l-4.796 5.481c-.566.647-.106 1.659.753 1.659h9.592a1 1 0 0 0 .753-1.659l-4.796-5.48a1 1 0 0 0-1.506 0z\"/>\n </svg>\n </button>\n <input\n type=\"text\"\n class=\"time-input\"\n [value]=\"startMinute.toString().padStart(2, '0')\"\n readonly\n title=\"Start minute\">\n <button \n type=\"button\" \n class=\"time-btn time-btn-down\" \n (click)=\"decrementStartMinute()\"\n title=\"Decrement minute\">\n <svg width=\"12\" height=\"12\" fill=\"currentColor\" viewBox=\"0 0 16 16\">\n <path d=\"M7.247 11.14l-4.796-5.481C1.885 5.013 2.345 4 3.204 4h9.592a1 1 0 0 1 .753 1.659l-4.796 5.48a1 1 0 0 1-1.506 0z\"/>\n </svg>\n </button>\n </div>\n @if (timeFormat === '12h') {\n <div class=\"time-period\">\n {{ startHour >= 12 ? 'PM' : 'AM' }}\n </div>\n }\n </div>\n </div>\n\n <!-- Separator -->\n <div class=\"time-separator-vertical\"></div>\n\n <!-- End Time -->\n <div class=\"time-picker-section\">\n <div class=\"time-picker-label\">End Time</div>\n <div class=\"time-picker-inputs\">\n <div class=\"time-input-group\">\n <button \n type=\"button\" \n class=\"time-btn time-btn-up\" \n (click)=\"incrementEndHour()\"\n title=\"Increment hour\">\n <svg width=\"12\" height=\"12\" fill=\"currentColor\" viewBox=\"0 0 16 16\">\n <path d=\"M7.247 4.86l-4.796 5.481c-.566.647-.106 1.659.753 1.659h9.592a1 1 0 0 0 .753-1.659l-4.796-5.48a1 1 0 0 0-1.506 0z\"/>\n </svg>\n </button>\n <input\n type=\"text\"\n class=\"time-input\"\n [value]=\"endHour.toString().padStart(2, '0')\"\n readonly\n title=\"End hour\">\n <button \n type=\"button\" \n class=\"time-btn time-btn-down\" \n (click)=\"decrementEndHour()\"\n title=\"Decrement hour\">\n <svg width=\"12\" height=\"12\" fill=\"currentColor\" viewBox=\"0 0 16 16\">\n <path d=\"M7.247 11.14l-4.796-5.481C1.885 5.013 2.345 4 3.204 4h9.592a1 1 0 0 1 .753 1.659l-4.796 5.48a1 1 0 0 1-1.506 0z\"/>\n </svg>\n </button>\n </div>\n <span class=\"time-separator\">:</span>\n <div class=\"time-input-group\">\n <button \n type=\"button\" \n class=\"time-btn time-btn-up\" \n (click)=\"incrementEndMinute()\"\n title=\"Increment minute\">\n <svg width=\"12\" height=\"12\" fill=\"currentColor\" viewBox=\"0 0 16 16\">\n <path d=\"M7.247 4.86l-4.796 5.481c-.566.647-.106 1.659.753 1.659h9.592a1 1 0 0 0 .753-1.659l-4.796-5.48a1 1 0 0 0-1.506 0z\"/>\n </svg>\n </button>\n <input\n type=\"text\"\n class=\"time-input\"\n [value]=\"endMinute.toString().padStart(2, '0')\"\n readonly\n title=\"End minute\">\n <button \n type=\"button\" \n class=\"time-btn time-btn-down\" \n (click)=\"decrementEndMinute()\"\n title=\"Decrement minute\">\n <svg width=\"12\" height=\"12\" fill=\"currentColor\" viewBox=\"0 0 16 16\">\n <path d=\"M7.247 11.14l-4.796-5.481C1.885 5.013 2.345 4 3.204 4h9.592a1 1 0 0 1 .753 1.659l-4.796 5.48a1 1 0 0 1-1.506 0z\"/>\n </svg>\n </button>\n </div>\n @if (timeFormat === '12h') {\n <div class=\"time-period\">\n {{ endHour >= 12 ? 'PM' : 'AM' }}\n </div>\n }\n </div>\n </div>\n </div>\n }\n\n <!-- Footer with buttons -->\n @if (showClearButton || multiRange || requireApply) {\n <div class=\"date-picker-footer\">\n @if (requireApply && !multiRange) {\n <div class=\"apply-footer-actions\">\n <button \n type=\"button\" \n class=\"btn-cancel\" \n (click)=\"cancelSelection()\" \n [disabled]=\"!hasPendingChanges()\"\n title=\"Cancel selection\">\n Cancel\n </button>\n <button \n type=\"button\" \n class=\"btn-apply\" \n (click)=\"applySelection()\" \n [disabled]=\"!hasPendingChanges() || !pendingStartDate || !pendingEndDate\"\n title=\"Apply selection\">\n Apply\n </button>\n </div>\n }\n @if (multiRange) {\n <div class=\"multi-range-footer-actions\">\n <button type=\"button\" class=\"btn-clear\" (click)=\"clear()\" title=\"Clear all ranges\">\n Clear All\n </button>\n <button type=\"button\" class=\"btn-done\" (click)=\"closeDatePicker()\" title=\"Done selecting\">\n Done\n </button>\n </div>\n }\n @if (!multiRange && !requireApply && showClearButton) {\n <button type=\"button\" class=\"btn-clear\" (click)=\"clear()\" title=\"Clear selection\">\n Clear\n </button>\n }\n </div>\n }\n </div>\n }\n</div>\n", styles: [".datepicker-wrapper{position:relative;width:100%}.datepicker-wrapper .datepicker-input{display:block;width:100%;padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#495057;background-color:#fff;background-clip:padding-box;border:1px solid #ced4da;border-radius:.25rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out;cursor:pointer}.datepicker-wrapper .datepicker-input:hover{border-color:var(--input-border-hover, #ced4da)}.datepicker-wrapper .datepicker-input:focus{border-color:var(--input-border-focus, #80bdff);box-shadow:0 0 0 .2rem #007bff40;outline:0}.datepicker-wrapper .datepicker-input::placeholder{color:#6c757d;opacity:1}.datepicker-wrapper .datepicker-input:disabled,.datepicker-wrapper .datepicker-input[readonly]{background-color:#e9ecef;opacity:1}.date-picker-dropdown{position:absolute;top:100%;left:0;margin-top:4px;background:#fff;border:1px solid #e1e4e8;border-radius:8px;box-shadow:0 4px 12px #00000014,0 0 1px #00000014;padding:16px;z-index:1060;min-width:680px}@media (max-width: 768px){.date-picker-dropdown{min-width:100%;left:0;right:0}}.date-picker-header-only-close{display:flex;justify-content:flex-end;margin-bottom:12px}.date-picker-header-only-close .btn-close-calendar{background-color:transparent;border:1px solid transparent;color:#6b7280;padding:6px 10px;border-radius:6px;cursor:pointer;transition:all .15s ease;font-size:1.5rem;line-height:1}.date-picker-header-only-close .btn-close-calendar:hover{background-color:#fee;border-color:#fcc;color:#dc2626;transform:translateY(-1px);box-shadow:0 2px 4px #dc26261a}.date-picker-header-only-close .btn-close-calendar:active{transform:translateY(0);box-shadow:none}.date-picker-presets{display:flex;gap:6px;margin-bottom:16px;padding-bottom:12px;border-bottom:1px solid #e5e7eb;align-items:center}@media (max-width: 768px){.date-picker-presets{flex-wrap:wrap}}.date-picker-presets button{font-size:.75rem;padding:6px 14px;border:none;background-color:#f9fafb;color:#374151;border-radius:6px;transition:all .15s ease;font-weight:500;cursor:pointer;border:1px solid #e5e7eb}.date-picker-presets button:hover{background-color:#f3f4f6;border-color:#d1d5db;transform:translateY(-1px);box-shadow:0 2px 4px #0000000f}.date-picker-presets button:active{transform:translateY(0);box-shadow:none}.date-picker-presets .btn-close-calendar{margin-left:auto;background-color:transparent;border:1px solid transparent;color:#6b7280;padding:6px 10px;font-size:1.5rem;line-height:1}.date-picker-presets .btn-close-calendar:hover{background-color:#fee;border-color:#fcc;color:#dc2626;transform:translateY(-1px);box-shadow:0 2px 4px #dc26261a}.date-picker-presets .btn-close-calendar:active{transform:translateY(0);box-shadow:none}.date-picker-calendars{display:flex;gap:32px}@media (max-width: 768px){.date-picker-calendars{flex-direction:column;gap:16px}}.date-picker-calendar{flex:1;contain:layout paint}.date-picker-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:12px;padding:0 4px}.date-picker-header span{font-size:.813rem;font-weight:600;color:#111827}.date-picker-header button{padding:4px;color:#6b7280;text-decoration:none;border-radius:6px;transition:all .15s ease;border:none;background:transparent;cursor:pointer}.date-picker-header button:hover{background-color:#f3f4f6;color:#111827}.date-picker-weekdays{display:grid;grid-template-columns:repeat(7,1fr);gap:2px;margin-bottom:4px}.date-picker-weekdays span{text-align:center;font-size:.625rem;font-weight:600;color:#6b7280;padding:6px}.date-picker-days{display:grid;grid-template-columns:repeat(7,1fr);gap:2px;contain:layout paint}.date-picker-day{aspect-ratio:1;border:none;background:transparent;border-radius:50%;font-size:.75rem;cursor:pointer;transition:all .15s ease;color:#374151;font-weight:400;position:relative}.date-picker-day:hover:not(:disabled):not(.selected){background-color:#f3f4f6;color:#111827}.date-picker-day.empty{visibility:hidden}.date-picker-day.selected{background-color:#222;color:#fff;font-weight:600}.date-picker-day.in-range{background-color:#f9fafb;border-radius:0}.date-picker-day.in-hover-range{background-color:#e0e7ff;border-radius:0;opacity:.7;position:relative}.date-picker-day.in-hover-range:after{content:\"\";position:absolute;inset:0;border:1px dashed #6366f1;pointer-events:none}.date-picker-day:disabled{cursor:not-allowed;opacity:.3}.date-picker-day.disabled{cursor:not-allowed;opacity:.4;color:#9ca3af;background-color:#f9fafb;text-decoration:line-through}.date-picker-day.disabled:hover{background-color:#f9fafb;color:#9ca3af}.date-picker-day.keyboard-focused{outline:2px solid #3b82f6;outline-offset:2px;z-index:1}.date-picker-day.keyboard-focused:not(.selected){background-color:#eff6ff}.date-picker-day:focus-visible{outline:2px solid #3b82f6;outline-offset:2px;z-index:1}.date-picker-footer{padding:12px;border-top:1px solid #e1e4e8;display:flex;justify-content:center;gap:8px}.date-picker-footer .btn-clear{padding:8px 16px;background-color:#f6f8fa;border:1px solid #d0d7de;border-radius:6px;font-size:.875rem;font-weight:500;color:#24292f;cursor:pointer;transition:all .15s ease}.date-picker-footer .btn-clear:hover{background-color:#f3f4f6;border-color:#8c959f;transform:translateY(-1px);box-shadow:0 2px 4px #0000000d}.date-picker-footer .btn-clear:active{transform:translateY(0);box-shadow:none;background-color:#e9ecef}.date-picker-footer .btn-done{padding:8px 16px;background-color:#222;border:1px solid #222;border-radius:6px;font-size:.875rem;font-weight:500;color:#fff;cursor:pointer;transition:all .15s ease}.date-picker-footer .btn-done:hover{background-color:#000;border-color:#000;transform:translateY(-1px);box-shadow:0 2px 4px #00000026}.date-picker-footer .btn-done:active{transform:translateY(0);box-shadow:none}.date-picker-footer .btn-apply{padding:8px 24px;background-color:#2563eb;border:1px solid #2563eb;border-radius:6px;font-size:.875rem;font-weight:600;color:#fff;cursor:pointer;transition:all .15s ease}.date-picker-footer .btn-apply:hover:not(:disabled){background-color:#1d4ed8;border-color:#1d4ed8;transform:translateY(-1px);box-shadow:0 2px 8px #2563eb40}.date-picker-footer .btn-apply:active:not(:disabled){transform:translateY(0);box-shadow:none}.date-picker-footer .btn-apply:disabled{opacity:.5;cursor:not-allowed}.date-picker-footer .btn-cancel{padding:8px 24px;background-color:#f6f8fa;border:1px solid #d0d7de;border-radius:6px;font-size:.875rem;font-weight:500;color:#24292f;cursor:pointer;transition:all .15s ease}.date-picker-footer .btn-cancel:hover:not(:disabled){background-color:#f3f4f6;border-color:#8c959f;transform:translateY(-1px);box-shadow:0 2px 4px #0000000d}.date-picker-footer .btn-cancel:active:not(:disabled){transform:translateY(0);box-shadow:none;background-color:#e9ecef}.date-picker-footer .btn-cancel:disabled{opacity:.5;cursor:not-allowed}.date-picker-footer .apply-footer-actions{display:flex;gap:8px;width:100%;justify-content:flex-end}.date-picker-footer .multi-range-footer-actions{display:flex;gap:8px;width:100%;justify-content:space-between}.multi-range-list{border-top:1px solid #e1e4e8;border-bottom:1px solid #e1e4e8;padding:12px;margin-top:12px}.multi-range-list .multi-range-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:8px}.multi-range-list .multi-range-header .multi-range-title{font-size:.8125rem;font-weight:600;color:#24292f;text-transform:uppercase;letter-spacing:.025em}.multi-range-list .multi-range-items{display:flex;flex-direction:column;gap:6px;max-height:150px;overflow-y:auto}.multi-range-list .multi-range-items::-webkit-scrollbar{width:6px}.multi-range-list .multi-range-items::-webkit-scrollbar-track{background:#f1f3f4;border-radius:4px}.multi-range-list .multi-range-items::-webkit-scrollbar-thumb{background:#cbd5e0;border-radius:4px}.multi-range-list .multi-range-items::-webkit-scrollbar-thumb:hover{background:#a0aec0}.multi-range-list .multi-range-item{display:flex;align-items:center;justify-content:space-between;padding:8px 12px;background-color:#f6f8fa;border:1px solid #d0d7de;border-radius:6px;transition:all .15s ease}.multi-range-list .multi-range-item:hover{background-color:#f3f4f6;border-color:#8c959f}.multi-range-list .multi-range-item .multi-range-text{font-size:.875rem;color:#24292f;font-weight:500}.multi-range-list .multi-range-item .btn-remove-range{padding:4px;background-color:transparent;border:1px solid transparent;border-radius:4px;cursor:pointer;display:flex;align-items:center;justify-content:center;color:#6b7280;transition:all .15s ease}.multi-range-list .multi-range-item .btn-remove-range:hover{background-color:#fee;border-color:#fcc;color:#dc2626}.multi-range-list .multi-range-item .btn-remove-range:hover svg{transform:scale(1.1)}.multi-range-list .multi-range-item .btn-remove-range:active{transform:scale(.95)}.multi-range-list .multi-range-item .btn-remove-range svg{transition:transform .15s ease}.time-picker-container{display:flex;align-items:center;justify-content:center;gap:16px;padding:16px;border-top:1px solid #e5e7eb;border-bottom:1px solid #e5e7eb;margin:12px 0;background-color:#f9fafb}.time-picker-container .time-picker-section{display:flex;flex-direction:column;gap:8px;flex:1;align-items:center}.time-picker-container .time-picker-section .time-picker-label{font-size:.875rem;font-weight:600;color:#374151;text-align:center}.time-picker-container .time-picker-section .time-picker-inputs{display:flex;align-items:center;gap:8px}.time-picker-container .time-picker-section .time-picker-inputs .time-input-group{display:flex;flex-direction:column;gap:4px;align-items:center}.time-picker-container .time-picker-section .time-picker-inputs .time-input-group .time-input{width:48px;padding:8px 6px;text-align:center;font-size:1.25rem;font-weight:600;color:#1f2937;background-color:#fff;border:2px solid #e5e7eb;border-radius:6px;outline:none;transition:all .15s ease;cursor:default;-webkit-user-select:none;user-select:none}.time-picker-container .time-picker-section .time-picker-inputs .time-input-group .time-input:focus{border-color:var(--primary-color, #3b82f6);box-shadow:0 0 0 3px #3b82f61a}.time-picker-container .time-picker-section .time-picker-inputs .time-input-group .time-btn{padding:4px 8px;background-color:#fff;border:1px solid #d1d5db;border-radius:4px;cursor:pointer;display:flex;align-items:center;justify-content:center;color:#6b7280;transition:all .15s ease}.time-picker-container .time-picker-section .time-picker-inputs .time-input-group .time-btn:hover{background-color:var(--primary-color, #3b82f6);border-color:var(--primary-color, #3b82f6);color:#fff;transform:translateY(-1px);box-shadow:0 2px 4px #0000001a}.time-picker-container .time-picker-section .time-picker-inputs .time-input-group .time-btn:active{transform:translateY(0);box-shadow:none}.time-picker-container .time-picker-section .time-picker-inputs .time-input-group .time-btn svg{pointer-events:none}.time-picker-container .time-picker-section .time-picker-inputs .time-separator{font-size:1.5rem;font-weight:700;color:#9ca3af;margin:0 4px}.time-picker-container .time-picker-section .time-picker-inputs .time-period{font-size:.875rem;font-weight:600;color:#6b7280;padding:8px 12px;background-color:#f3f4f6;border-radius:6px;min-width:48px;text-align:center}.time-picker-container .time-separator-vertical{width:1px;height:80px;background-color:#e5e7eb}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: ReactiveFormsModule }] });
|
|
4019
|
+
], usesOnChanges: true, ngImport: i0, template: "<div class=\"datepicker-wrapper\" \n [class]=\"'theme-' + theme\"\n [style.--input-border-hover]=\"inputBorderColorHover\"\n [style.--input-border-focus]=\"inputBorderColorFocus\">\n <input \n type=\"text\" \n class=\"datepicker-input\" \n [value]=\"dateRangeText()\" \n (click)=\"toggleDatePicker()\" \n [placeholder]=\"placeholder\"\n [disabled]=\"isDisabled()\"\n [attr.aria-label]=\"placeholder\"\n [attr.aria-expanded]=\"showDatePicker()\"\n [attr.aria-haspopup]=\"'dialog'\"\n role=\"combobox\"\n [ngStyle]=\"{\n 'background-color': inputBackgroundColor,\n 'color': inputTextColor,\n 'border-color': inputBorderColor,\n 'padding': inputPadding\n }\"\n readonly>\n\n @if (showDatePicker()) {\n <div class=\"date-picker-dropdown\">\n @if (showPresets) {\n <div class=\"date-picker-presets\">\n @for (preset of presets; track preset.label) {\n <button type=\"button\" (click)=\"selectPresetRange(preset)\">{{ preset.label }}</button>\n }\n <button type=\"button\" class=\"btn-close-calendar\" (click)=\"closeDatePicker()\" title=\"Close\">\n \u00D7\n </button>\n </div>\n }\n\n @if (!showPresets) {\n <div class=\"date-picker-header-only-close\">\n <button type=\"button\" class=\"btn-close-calendar\" (click)=\"closeDatePicker()\" title=\"Close\">\n \u00D7\n </button>\n </div>\n }\n\n <!-- Calendars -->\n <div class=\"date-picker-calendars\">\n <!-- Previous month calendar -->\n <div class=\"date-picker-calendar\">\n <div class=\"date-picker-header\">\n <button type=\"button\" (click)=\"changeMonth(-1)\">\n <svg width=\"16\" height=\"16\" fill=\"currentColor\" viewBox=\"0 0 16 16\">\n <path fill-rule=\"evenodd\" d=\"M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z\"/>\n </svg>\n </button>\n <span>{{ previousMonthName() }}</span>\n <button type=\"button\" style=\"visibility: hidden;\">\n <svg width=\"16\" height=\"16\" fill=\"currentColor\" viewBox=\"0 0 16 16\">\n <path fill-rule=\"evenodd\" d=\"M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z\"/>\n </svg>\n </button>\n </div>\n <div class=\"date-picker-weekdays\">\n @for (dayName of weekDayNames(); track $index) {\n <span>{{ dayName }}</span>\n }\n </div>\n <div class=\"date-picker-days\">\n @for (dayObj of previousMonthVisibleDays(); track dayObj.date || $index) {\n <button \n type=\"button\"\n class=\"date-picker-day\" \n [class.empty]=\"!dayObj.isCurrentMonth\"\n [class.selected]=\"dayObj.isStart || dayObj.isEnd\"\n [class.in-range]=\"dayObj.inRange && !dayObj.isStart && !dayObj.isEnd\"\n [class.in-hover-range]=\"dayObj.inHoverRange && !dayObj.isStart && !dayObj.isEnd\"\n [class.disabled]=\"dayObj.isDisabled\"\n [class.keyboard-focused]=\"enableKeyboardNavigation && hasKeyboardFocus(dayObj.date, 0)\"\n [attr.tabindex]=\"enableKeyboardNavigation && dayObj.isCurrentMonth && hasKeyboardFocus(dayObj.date, 0) ? 0 : -1\"\n [attr.aria-label]=\"formatDateDisplay(dayObj.date)\"\n [attr.aria-selected]=\"dayObj.isStart || dayObj.isEnd\"\n [attr.aria-current]=\"dayObj.isStart ? 'date' : null\"\n [attr.aria-disabled]=\"dayObj.isDisabled\"\n (click)=\"selectDay(dayObj)\"\n (mouseenter)=\"onDayHover(dayObj)\"\n (mouseleave)=\"clearDayHover()\"\n [disabled]=\"!dayObj.isCurrentMonth || dayObj.isDisabled\">\n {{ dayObj.day }}\n </button>\n }\n </div>\n </div>\n\n <!-- Current month calendar -->\n <div class=\"date-picker-calendar\">\n <div class=\"date-picker-header\">\n <button type=\"button\" style=\"visibility: hidden;\">\n <svg width=\"16\" height=\"16\" fill=\"currentColor\" viewBox=\"0 0 16 16\">\n <path fill-rule=\"evenodd\" d=\"M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z\"/>\n </svg>\n </button>\n <span>{{ currentMonthName() }}</span>\n <button type=\"button\" (click)=\"changeMonth(1)\">\n <svg width=\"16\" height=\"16\" fill=\"currentColor\" viewBox=\"0 0 16 16\">\n <path fill-rule=\"evenodd\" d=\"M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z\"/>\n </svg>\n </button>\n </div>\n <div class=\"date-picker-weekdays\">\n @for (dayName of weekDayNames(); track $index) {\n <span>{{ dayName }}</span>\n }\n </div>\n <div class=\"date-picker-days\">\n @for (dayObj of currentMonthVisibleDays(); track dayObj.date || $index) {\n <button \n type=\"button\"\n class=\"date-picker-day\" \n [class.empty]=\"!dayObj.isCurrentMonth\"\n [class.selected]=\"dayObj.isStart || dayObj.isEnd\"\n [class.in-range]=\"dayObj.inRange && !dayObj.isStart && !dayObj.isEnd\"\n [class.in-hover-range]=\"dayObj.inHoverRange && !dayObj.isStart && !dayObj.isEnd\"\n [class.disabled]=\"dayObj.isDisabled\"\n [class.keyboard-focused]=\"enableKeyboardNavigation && hasKeyboardFocus(dayObj.date, 1)\"\n [attr.tabindex]=\"enableKeyboardNavigation && dayObj.isCurrentMonth && hasKeyboardFocus(dayObj.date, 1) ? 0 : -1\"\n [attr.aria-label]=\"formatDateDisplay(dayObj.date)\"\n [attr.aria-selected]=\"dayObj.isStart || dayObj.isEnd\"\n [attr.aria-current]=\"dayObj.isStart ? 'date' : null\"\n [attr.aria-disabled]=\"dayObj.isDisabled\"\n (click)=\"selectDay(dayObj)\"\n (mouseenter)=\"onDayHover(dayObj)\"\n (mouseleave)=\"clearDayHover()\"\n [disabled]=\"!dayObj.isCurrentMonth || dayObj.isDisabled\">\n {{ dayObj.day }}\n </button>\n }\n </div>\n </div>\n </div>\n\n <!-- Multi-Range List -->\n @if (multiRange && selectedRanges().length > 0) {\n <div class=\"multi-range-list\">\n <div class=\"multi-range-header\">\n <span class=\"multi-range-title\">Selected Ranges ({{ selectedRanges().length }})</span>\n </div>\n <div class=\"multi-range-items\">\n @for (range of selectedRanges(); track $index) {\n <div class=\"multi-range-item\">\n <span class=\"multi-range-text\">{{ range.rangeText }}</span>\n <button \n type=\"button\" \n class=\"btn-remove-range\" \n (click)=\"removeRange($index)\"\n title=\"Remove this range\">\n <svg width=\"14\" height=\"14\" fill=\"currentColor\" viewBox=\"0 0 16 16\">\n <path d=\"M2.146 2.854a.5.5 0 1 1 .708-.708L8 7.293l5.146-5.147a.5.5 0 0 1 .708.708L8.707 8l5.147 5.146a.5.5 0 0 1-.708.708L8 8.707l-5.146 5.147a.5.5 0 0 1-.708-.708L7.293 8 2.146 2.854Z\"/>\n </svg>\n </button>\n </div>\n }\n </div>\n </div>\n }\n\n <!-- Time Picker -->\n @if (enableTimePicker) {\n <div class=\"time-picker-container\">\n <!-- Start Time -->\n <div class=\"time-picker-section\">\n <div class=\"time-picker-label\">Start Time</div>\n <div class=\"time-picker-inputs\">\n <div class=\"time-input-group\">\n <button \n type=\"button\" \n class=\"time-btn time-btn-up\" \n (click)=\"incrementStartHour()\"\n title=\"Increment hour\">\n <svg width=\"12\" height=\"12\" fill=\"currentColor\" viewBox=\"0 0 16 16\">\n <path d=\"M7.247 4.86l-4.796 5.481c-.566.647-.106 1.659.753 1.659h9.592a1 1 0 0 0 .753-1.659l-4.796-5.48a1 1 0 0 0-1.506 0z\"/>\n </svg>\n </button>\n <input\n type=\"text\"\n class=\"time-input\"\n [value]=\"startHour.toString().padStart(2, '0')\"\n readonly\n title=\"Start hour\">\n <button \n type=\"button\" \n class=\"time-btn time-btn-down\" \n (click)=\"decrementStartHour()\"\n title=\"Decrement hour\">\n <svg width=\"12\" height=\"12\" fill=\"currentColor\" viewBox=\"0 0 16 16\">\n <path d=\"M7.247 11.14l-4.796-5.481C1.885 5.013 2.345 4 3.204 4h9.592a1 1 0 0 1 .753 1.659l-4.796 5.48a1 1 0 0 1-1.506 0z\"/>\n </svg>\n </button>\n </div>\n <span class=\"time-separator\">:</span>\n <div class=\"time-input-group\">\n <button \n type=\"button\" \n class=\"time-btn time-btn-up\" \n (click)=\"incrementStartMinute()\"\n title=\"Increment minute\">\n <svg width=\"12\" height=\"12\" fill=\"currentColor\" viewBox=\"0 0 16 16\">\n <path d=\"M7.247 4.86l-4.796 5.481c-.566.647-.106 1.659.753 1.659h9.592a1 1 0 0 0 .753-1.659l-4.796-5.48a1 1 0 0 0-1.506 0z\"/>\n </svg>\n </button>\n <input\n type=\"text\"\n class=\"time-input\"\n [value]=\"startMinute.toString().padStart(2, '0')\"\n readonly\n title=\"Start minute\">\n <button \n type=\"button\" \n class=\"time-btn time-btn-down\" \n (click)=\"decrementStartMinute()\"\n title=\"Decrement minute\">\n <svg width=\"12\" height=\"12\" fill=\"currentColor\" viewBox=\"0 0 16 16\">\n <path d=\"M7.247 11.14l-4.796-5.481C1.885 5.013 2.345 4 3.204 4h9.592a1 1 0 0 1 .753 1.659l-4.796 5.48a1 1 0 0 1-1.506 0z\"/>\n </svg>\n </button>\n </div>\n @if (timeFormat === '12h') {\n <div class=\"time-period\">\n {{ startHour >= 12 ? 'PM' : 'AM' }}\n </div>\n }\n </div>\n </div>\n\n <!-- Separator -->\n <div class=\"time-separator-vertical\"></div>\n\n <!-- End Time -->\n <div class=\"time-picker-section\">\n <div class=\"time-picker-label\">End Time</div>\n <div class=\"time-picker-inputs\">\n <div class=\"time-input-group\">\n <button \n type=\"button\" \n class=\"time-btn time-btn-up\" \n (click)=\"incrementEndHour()\"\n title=\"Increment hour\">\n <svg width=\"12\" height=\"12\" fill=\"currentColor\" viewBox=\"0 0 16 16\">\n <path d=\"M7.247 4.86l-4.796 5.481c-.566.647-.106 1.659.753 1.659h9.592a1 1 0 0 0 .753-1.659l-4.796-5.48a1 1 0 0 0-1.506 0z\"/>\n </svg>\n </button>\n <input\n type=\"text\"\n class=\"time-input\"\n [value]=\"endHour.toString().padStart(2, '0')\"\n readonly\n title=\"End hour\">\n <button \n type=\"button\" \n class=\"time-btn time-btn-down\" \n (click)=\"decrementEndHour()\"\n title=\"Decrement hour\">\n <svg width=\"12\" height=\"12\" fill=\"currentColor\" viewBox=\"0 0 16 16\">\n <path d=\"M7.247 11.14l-4.796-5.481C1.885 5.013 2.345 4 3.204 4h9.592a1 1 0 0 1 .753 1.659l-4.796 5.48a1 1 0 0 1-1.506 0z\"/>\n </svg>\n </button>\n </div>\n <span class=\"time-separator\">:</span>\n <div class=\"time-input-group\">\n <button \n type=\"button\" \n class=\"time-btn time-btn-up\" \n (click)=\"incrementEndMinute()\"\n title=\"Increment minute\">\n <svg width=\"12\" height=\"12\" fill=\"currentColor\" viewBox=\"0 0 16 16\">\n <path d=\"M7.247 4.86l-4.796 5.481c-.566.647-.106 1.659.753 1.659h9.592a1 1 0 0 0 .753-1.659l-4.796-5.48a1 1 0 0 0-1.506 0z\"/>\n </svg>\n </button>\n <input\n type=\"text\"\n class=\"time-input\"\n [value]=\"endMinute.toString().padStart(2, '0')\"\n readonly\n title=\"End minute\">\n <button \n type=\"button\" \n class=\"time-btn time-btn-down\" \n (click)=\"decrementEndMinute()\"\n title=\"Decrement minute\">\n <svg width=\"12\" height=\"12\" fill=\"currentColor\" viewBox=\"0 0 16 16\">\n <path d=\"M7.247 11.14l-4.796-5.481C1.885 5.013 2.345 4 3.204 4h9.592a1 1 0 0 1 .753 1.659l-4.796 5.48a1 1 0 0 1-1.506 0z\"/>\n </svg>\n </button>\n </div>\n @if (timeFormat === '12h') {\n <div class=\"time-period\">\n {{ endHour >= 12 ? 'PM' : 'AM' }}\n </div>\n }\n </div>\n </div>\n </div>\n }\n\n <!-- Footer with buttons -->\n @if (showClearButton || multiRange || requireApply) {\n <div class=\"date-picker-footer\">\n @if (requireApply && !multiRange) {\n <div class=\"apply-footer-actions\">\n <button \n type=\"button\" \n class=\"btn-cancel\" \n (click)=\"cancelSelection()\" \n [disabled]=\"!hasPendingChanges()\"\n title=\"Cancel selection\">\n Cancel\n </button>\n <button \n type=\"button\" \n class=\"btn-apply\" \n (click)=\"applySelection()\" \n [disabled]=\"!hasPendingChanges() || !pendingStartDate || !pendingEndDate\"\n title=\"Apply selection\">\n Apply\n </button>\n </div>\n }\n @if (multiRange) {\n <div class=\"multi-range-footer-actions\">\n <button type=\"button\" class=\"btn-clear\" (click)=\"clear()\" title=\"Clear all ranges\">\n Clear All\n </button>\n <button type=\"button\" class=\"btn-done\" (click)=\"closeDatePicker()\" title=\"Done selecting\">\n Done\n </button>\n </div>\n }\n @if (!multiRange && !requireApply && showClearButton) {\n <button type=\"button\" class=\"btn-clear\" (click)=\"clear()\" title=\"Clear selection\">\n Clear\n </button>\n }\n </div>\n }\n </div>\n }\n</div>\n", styles: [".datepicker-wrapper{position:relative;width:100%}.datepicker-wrapper .datepicker-input{display:block;width:100%;padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#495057;background-color:#fff;background-clip:padding-box;border:1px solid #ced4da;border-radius:.25rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out;cursor:pointer}.datepicker-wrapper .datepicker-input:hover{border-color:var(--input-border-hover, #ced4da)}.datepicker-wrapper .datepicker-input:focus{border-color:var(--input-border-focus, #80bdff);box-shadow:0 0 0 .2rem #007bff40;outline:0}.datepicker-wrapper .datepicker-input::placeholder{color:#6c757d;opacity:1}.datepicker-wrapper .datepicker-input:disabled,.datepicker-wrapper .datepicker-input[readonly]{background-color:#e9ecef;opacity:1}.date-picker-dropdown{position:absolute;top:100%;left:0;margin-top:4px;background:#fff;border:1px solid #e1e4e8;border-radius:8px;box-shadow:0 4px 12px #00000014,0 0 1px #00000014;padding:16px;z-index:1060;min-width:680px}@media(max-width:768px){.date-picker-dropdown{min-width:100%;left:0;right:0}}.date-picker-header-only-close{display:flex;justify-content:flex-end;margin-bottom:12px}.date-picker-header-only-close .btn-close-calendar{background-color:transparent;border:1px solid transparent;color:#6b7280;padding:6px 10px;border-radius:6px;cursor:pointer;transition:all .15s ease;font-size:1.5rem;line-height:1}.date-picker-header-only-close .btn-close-calendar:hover{background-color:#fee;border-color:#fcc;color:#dc2626;transform:translateY(-1px);box-shadow:0 2px 4px #dc26261a}.date-picker-header-only-close .btn-close-calendar:active{transform:translateY(0);box-shadow:none}.date-picker-presets{display:flex;gap:6px;margin-bottom:16px;padding-bottom:12px;border-bottom:1px solid #e5e7eb;align-items:center}@media(max-width:768px){.date-picker-presets{flex-wrap:wrap}}.date-picker-presets button{font-size:.75rem;padding:6px 14px;border:none;background-color:#f9fafb;color:#374151;border-radius:6px;transition:all .15s ease;font-weight:500;cursor:pointer;border:1px solid #e5e7eb}.date-picker-presets button:hover{background-color:#f3f4f6;border-color:#d1d5db;transform:translateY(-1px);box-shadow:0 2px 4px #0000000f}.date-picker-presets button:active{transform:translateY(0);box-shadow:none}.date-picker-presets .btn-close-calendar{margin-left:auto;background-color:transparent;border:1px solid transparent;color:#6b7280;padding:6px 10px;font-size:1.5rem;line-height:1}.date-picker-presets .btn-close-calendar:hover{background-color:#fee;border-color:#fcc;color:#dc2626;transform:translateY(-1px);box-shadow:0 2px 4px #dc26261a}.date-picker-presets .btn-close-calendar:active{transform:translateY(0);box-shadow:none}.date-picker-calendars{display:flex;gap:32px}@media(max-width:768px){.date-picker-calendars{flex-direction:column;gap:16px}}.date-picker-calendar{flex:1;contain:layout paint}.date-picker-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:12px;padding:0 4px}.date-picker-header span{font-size:.813rem;font-weight:600;color:#111827}.date-picker-header button{padding:4px;color:#6b7280;text-decoration:none;border-radius:6px;transition:all .15s ease;border:none;background:transparent;cursor:pointer}.date-picker-header button:hover{background-color:#f3f4f6;color:#111827}.date-picker-weekdays{display:grid;grid-template-columns:repeat(7,1fr);gap:2px;margin-bottom:4px}.date-picker-weekdays span{text-align:center;font-size:.625rem;font-weight:600;color:#6b7280;padding:6px}.date-picker-days{display:grid;grid-template-columns:repeat(7,1fr);gap:2px;contain:layout paint}.date-picker-day{aspect-ratio:1;border:none;background:transparent;border-radius:50%;font-size:.75rem;cursor:pointer;transition:all .15s ease;color:#374151;font-weight:400;position:relative}.date-picker-day:hover:not(:disabled):not(.selected){background-color:#f3f4f6;color:#111827}.date-picker-day.empty{visibility:hidden}.date-picker-day.selected{background-color:#222;color:#fff;font-weight:600}.date-picker-day.in-range{background-color:#f9fafb;border-radius:0}.date-picker-day.in-hover-range{background-color:#e0e7ff;border-radius:0;opacity:.7;position:relative}.date-picker-day.in-hover-range:after{content:\"\";position:absolute;inset:0;border:1px dashed #6366f1;pointer-events:none}.date-picker-day:disabled{cursor:not-allowed;opacity:.3}.date-picker-day.disabled{cursor:not-allowed;opacity:.4;color:#9ca3af;background-color:#f9fafb;text-decoration:line-through}.date-picker-day.disabled:hover{background-color:#f9fafb;color:#9ca3af}.date-picker-day.keyboard-focused{outline:2px solid #3b82f6;outline-offset:2px;z-index:1}.date-picker-day.keyboard-focused:not(.selected){background-color:#eff6ff}.date-picker-day:focus-visible{outline:2px solid #3b82f6;outline-offset:2px;z-index:1}.date-picker-footer{padding:12px;border-top:1px solid #e1e4e8;display:flex;justify-content:center;gap:8px}.date-picker-footer .btn-clear{padding:8px 16px;background-color:#f6f8fa;border:1px solid #d0d7de;border-radius:6px;font-size:.875rem;font-weight:500;color:#24292f;cursor:pointer;transition:all .15s ease}.date-picker-footer .btn-clear:hover{background-color:#f3f4f6;border-color:#8c959f;transform:translateY(-1px);box-shadow:0 2px 4px #0000000d}.date-picker-footer .btn-clear:active{transform:translateY(0);box-shadow:none;background-color:#e9ecef}.date-picker-footer .btn-done{padding:8px 16px;background-color:#222;border:1px solid #222;border-radius:6px;font-size:.875rem;font-weight:500;color:#fff;cursor:pointer;transition:all .15s ease}.date-picker-footer .btn-done:hover{background-color:#000;border-color:#000;transform:translateY(-1px);box-shadow:0 2px 4px #00000026}.date-picker-footer .btn-done:active{transform:translateY(0);box-shadow:none}.date-picker-footer .btn-apply{padding:8px 24px;background-color:#2563eb;border:1px solid #2563eb;border-radius:6px;font-size:.875rem;font-weight:600;color:#fff;cursor:pointer;transition:all .15s ease}.date-picker-footer .btn-apply:hover:not(:disabled){background-color:#1d4ed8;border-color:#1d4ed8;transform:translateY(-1px);box-shadow:0 2px 8px #2563eb40}.date-picker-footer .btn-apply:active:not(:disabled){transform:translateY(0);box-shadow:none}.date-picker-footer .btn-apply:disabled{opacity:.5;cursor:not-allowed}.date-picker-footer .btn-cancel{padding:8px 24px;background-color:#f6f8fa;border:1px solid #d0d7de;border-radius:6px;font-size:.875rem;font-weight:500;color:#24292f;cursor:pointer;transition:all .15s ease}.date-picker-footer .btn-cancel:hover:not(:disabled){background-color:#f3f4f6;border-color:#8c959f;transform:translateY(-1px);box-shadow:0 2px 4px #0000000d}.date-picker-footer .btn-cancel:active:not(:disabled){transform:translateY(0);box-shadow:none;background-color:#e9ecef}.date-picker-footer .btn-cancel:disabled{opacity:.5;cursor:not-allowed}.date-picker-footer .apply-footer-actions{display:flex;gap:8px;width:100%;justify-content:flex-end}.date-picker-footer .multi-range-footer-actions{display:flex;gap:8px;width:100%;justify-content:space-between}.multi-range-list{border-top:1px solid #e1e4e8;border-bottom:1px solid #e1e4e8;padding:12px;margin-top:12px}.multi-range-list .multi-range-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:8px}.multi-range-list .multi-range-header .multi-range-title{font-size:.8125rem;font-weight:600;color:#24292f;text-transform:uppercase;letter-spacing:.025em}.multi-range-list .multi-range-items{display:flex;flex-direction:column;gap:6px;max-height:150px;overflow-y:auto}.multi-range-list .multi-range-items::-webkit-scrollbar{width:6px}.multi-range-list .multi-range-items::-webkit-scrollbar-track{background:#f1f3f4;border-radius:4px}.multi-range-list .multi-range-items::-webkit-scrollbar-thumb{background:#cbd5e0;border-radius:4px}.multi-range-list .multi-range-items::-webkit-scrollbar-thumb:hover{background:#a0aec0}.multi-range-list .multi-range-item{display:flex;align-items:center;justify-content:space-between;padding:8px 12px;background-color:#f6f8fa;border:1px solid #d0d7de;border-radius:6px;transition:all .15s ease}.multi-range-list .multi-range-item:hover{background-color:#f3f4f6;border-color:#8c959f}.multi-range-list .multi-range-item .multi-range-text{font-size:.875rem;color:#24292f;font-weight:500}.multi-range-list .multi-range-item .btn-remove-range{padding:4px;background-color:transparent;border:1px solid transparent;border-radius:4px;cursor:pointer;display:flex;align-items:center;justify-content:center;color:#6b7280;transition:all .15s ease}.multi-range-list .multi-range-item .btn-remove-range:hover{background-color:#fee;border-color:#fcc;color:#dc2626}.multi-range-list .multi-range-item .btn-remove-range:hover svg{transform:scale(1.1)}.multi-range-list .multi-range-item .btn-remove-range:active{transform:scale(.95)}.multi-range-list .multi-range-item .btn-remove-range svg{transition:transform .15s ease}.time-picker-container{display:flex;align-items:center;justify-content:center;gap:16px;padding:16px;border-top:1px solid #e5e7eb;border-bottom:1px solid #e5e7eb;margin:12px 0;background-color:#f9fafb}.time-picker-container .time-picker-section{display:flex;flex-direction:column;gap:8px;flex:1;align-items:center}.time-picker-container .time-picker-section .time-picker-label{font-size:.875rem;font-weight:600;color:#374151;text-align:center}.time-picker-container .time-picker-section .time-picker-inputs{display:flex;align-items:center;gap:8px}.time-picker-container .time-picker-section .time-picker-inputs .time-input-group{display:flex;flex-direction:column;gap:4px;align-items:center}.time-picker-container .time-picker-section .time-picker-inputs .time-input-group .time-input{width:48px;padding:8px 6px;text-align:center;font-size:1.25rem;font-weight:600;color:#1f2937;background-color:#fff;border:2px solid #e5e7eb;border-radius:6px;outline:none;transition:all .15s ease;cursor:default;-webkit-user-select:none;user-select:none}.time-picker-container .time-picker-section .time-picker-inputs .time-input-group .time-input:focus{border-color:var(--primary-color, #3b82f6);box-shadow:0 0 0 3px #3b82f61a}.time-picker-container .time-picker-section .time-picker-inputs .time-input-group .time-btn{padding:4px 8px;background-color:#fff;border:1px solid #d1d5db;border-radius:4px;cursor:pointer;display:flex;align-items:center;justify-content:center;color:#6b7280;transition:all .15s ease}.time-picker-container .time-picker-section .time-picker-inputs .time-input-group .time-btn:hover{background-color:var(--primary-color, #3b82f6);border-color:var(--primary-color, #3b82f6);color:#fff;transform:translateY(-1px);box-shadow:0 2px 4px #0000001a}.time-picker-container .time-picker-section .time-picker-inputs .time-input-group .time-btn:active{transform:translateY(0);box-shadow:none}.time-picker-container .time-picker-section .time-picker-inputs .time-input-group .time-btn svg{pointer-events:none}.time-picker-container .time-picker-section .time-picker-inputs .time-separator{font-size:1.5rem;font-weight:700;color:#9ca3af;margin:0 4px}.time-picker-container .time-picker-section .time-picker-inputs .time-period{font-size:.875rem;font-weight:600;color:#6b7280;padding:8px 12px;background-color:#f3f4f6;border-radius:6px;min-width:48px;text-align:center}.time-picker-container .time-separator-vertical{width:1px;height:80px;background-color:#e5e7eb}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: ReactiveFormsModule }] });
|
|
3847
4020
|
}
|
|
3848
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
4021
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: DualDatepickerComponent, decorators: [{
|
|
3849
4022
|
type: Component,
|
|
3850
4023
|
args: [{ selector: 'ngx-dual-datepicker', standalone: true, imports: [CommonModule, FormsModule, ReactiveFormsModule], providers: [
|
|
3851
4024
|
{
|
|
@@ -3854,8 +4027,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
3854
4027
|
multi: true
|
|
3855
4028
|
},
|
|
3856
4029
|
{
|
|
3857
|
-
provide: DATE_ADAPTER
|
|
3858
|
-
useClass: NativeDateAdapter
|
|
4030
|
+
provide: DATE_ADAPTER,
|
|
4031
|
+
useClass: NativeDateAdapter
|
|
3859
4032
|
},
|
|
3860
4033
|
{
|
|
3861
4034
|
provide: APP_INITIALIZER,
|
|
@@ -3868,7 +4041,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
3868
4041
|
},
|
|
3869
4042
|
deps: [PresetRegistry]
|
|
3870
4043
|
}
|
|
3871
|
-
], template: "<div class=\"datepicker-wrapper\" \n [class]=\"'theme-' + theme\"\n [style.--input-border-hover]=\"inputBorderColorHover\"\n [style.--input-border-focus]=\"inputBorderColorFocus\">\n <input \n type=\"text\" \n class=\"datepicker-input\" \n [value]=\"dateRangeText()\" \n (click)=\"toggleDatePicker()\" \n [placeholder]=\"placeholder\"\n [disabled]=\"isDisabled()\"\n [attr.aria-label]=\"placeholder\"\n [attr.aria-expanded]=\"showDatePicker()\"\n [attr.aria-haspopup]=\"'dialog'\"\n role=\"combobox\"\n [ngStyle]=\"{\n 'background-color': inputBackgroundColor,\n 'color': inputTextColor,\n 'border-color': inputBorderColor,\n 'padding': inputPadding\n }\"\n readonly>\n\n @if (showDatePicker()) {\n <div class=\"date-picker-dropdown\">\n @if (showPresets) {\n <div class=\"date-picker-presets\">\n @for (preset of presets; track preset.label) {\n <button type=\"button\" (click)=\"selectPresetRange(preset)\">{{ preset.label }}</button>\n }\n <button type=\"button\" class=\"btn-close-calendar\" (click)=\"closeDatePicker()\" title=\"Close\">\n \u00D7\n </button>\n </div>\n }\n\n @if (!showPresets) {\n <div class=\"date-picker-header-only-close\">\n <button type=\"button\" class=\"btn-close-calendar\" (click)=\"closeDatePicker()\" title=\"Close\">\n \u00D7\n </button>\n </div>\n }\n\n <!-- Calendars -->\n <div class=\"date-picker-calendars\">\n <!-- Previous month calendar -->\n <div class=\"date-picker-calendar\">\n <div class=\"date-picker-header\">\n <button type=\"button\" (click)=\"changeMonth(-1)\">\n <svg width=\"16\" height=\"16\" fill=\"currentColor\" viewBox=\"0 0 16 16\">\n <path fill-rule=\"evenodd\" d=\"M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z\"/>\n </svg>\n </button>\n <span>{{ previousMonthName() }}</span>\n <button type=\"button\" style=\"visibility: hidden;\">\n <svg width=\"16\" height=\"16\" fill=\"currentColor\" viewBox=\"0 0 16 16\">\n <path fill-rule=\"evenodd\" d=\"M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z\"/>\n </svg>\n </button>\n </div>\n <div class=\"date-picker-weekdays\">\n @for (dayName of weekDayNames(); track $index) {\n <span>{{ dayName }}</span>\n }\n </div>\n <div class=\"date-picker-days\">\n @for (dayObj of previousMonthVisibleDays(); track dayObj.date || $index) {\n <button \n type=\"button\"\n class=\"date-picker-day\" \n [class.empty]=\"!dayObj.isCurrentMonth\"\n [class.selected]=\"dayObj.isStart || dayObj.isEnd\"\n [class.in-range]=\"dayObj.inRange && !dayObj.isStart && !dayObj.isEnd\"\n [class.in-hover-range]=\"dayObj.inHoverRange && !dayObj.isStart && !dayObj.isEnd\"\n [class.disabled]=\"dayObj.isDisabled\"\n [class.keyboard-focused]=\"enableKeyboardNavigation && hasKeyboardFocus(dayObj.date, 0)\"\n [attr.tabindex]=\"enableKeyboardNavigation && dayObj.isCurrentMonth && hasKeyboardFocus(dayObj.date, 0) ? 0 : -1\"\n [attr.aria-label]=\"formatDateDisplay(dayObj.date)\"\n [attr.aria-selected]=\"dayObj.isStart || dayObj.isEnd\"\n [attr.aria-current]=\"dayObj.isStart ? 'date' : null\"\n [attr.aria-disabled]=\"dayObj.isDisabled\"\n (click)=\"selectDay(dayObj)\"\n (mouseenter)=\"onDayHover(dayObj)\"\n (mouseleave)=\"clearDayHover()\"\n [disabled]=\"!dayObj.isCurrentMonth || dayObj.isDisabled\">\n {{ dayObj.day }}\n </button>\n }\n </div>\n </div>\n\n <!-- Current month calendar -->\n <div class=\"date-picker-calendar\">\n <div class=\"date-picker-header\">\n <button type=\"button\" style=\"visibility: hidden;\">\n <svg width=\"16\" height=\"16\" fill=\"currentColor\" viewBox=\"0 0 16 16\">\n <path fill-rule=\"evenodd\" d=\"M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z\"/>\n </svg>\n </button>\n <span>{{ currentMonthName() }}</span>\n <button type=\"button\" (click)=\"changeMonth(1)\">\n <svg width=\"16\" height=\"16\" fill=\"currentColor\" viewBox=\"0 0 16 16\">\n <path fill-rule=\"evenodd\" d=\"M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z\"/>\n </svg>\n </button>\n </div>\n <div class=\"date-picker-weekdays\">\n @for (dayName of weekDayNames(); track $index) {\n <span>{{ dayName }}</span>\n }\n </div>\n <div class=\"date-picker-days\">\n @for (dayObj of currentMonthVisibleDays(); track dayObj.date || $index) {\n <button \n type=\"button\"\n class=\"date-picker-day\" \n [class.empty]=\"!dayObj.isCurrentMonth\"\n [class.selected]=\"dayObj.isStart || dayObj.isEnd\"\n [class.in-range]=\"dayObj.inRange && !dayObj.isStart && !dayObj.isEnd\"\n [class.in-hover-range]=\"dayObj.inHoverRange && !dayObj.isStart && !dayObj.isEnd\"\n [class.disabled]=\"dayObj.isDisabled\"\n [class.keyboard-focused]=\"enableKeyboardNavigation && hasKeyboardFocus(dayObj.date, 1)\"\n [attr.tabindex]=\"enableKeyboardNavigation && dayObj.isCurrentMonth && hasKeyboardFocus(dayObj.date, 1) ? 0 : -1\"\n [attr.aria-label]=\"formatDateDisplay(dayObj.date)\"\n [attr.aria-selected]=\"dayObj.isStart || dayObj.isEnd\"\n [attr.aria-current]=\"dayObj.isStart ? 'date' : null\"\n [attr.aria-disabled]=\"dayObj.isDisabled\"\n (click)=\"selectDay(dayObj)\"\n (mouseenter)=\"onDayHover(dayObj)\"\n (mouseleave)=\"clearDayHover()\"\n [disabled]=\"!dayObj.isCurrentMonth || dayObj.isDisabled\">\n {{ dayObj.day }}\n </button>\n }\n </div>\n </div>\n </div>\n\n <!-- Multi-Range List -->\n @if (multiRange && selectedRanges().length > 0) {\n <div class=\"multi-range-list\">\n <div class=\"multi-range-header\">\n <span class=\"multi-range-title\">Selected Ranges ({{ selectedRanges().length }})</span>\n </div>\n <div class=\"multi-range-items\">\n @for (range of selectedRanges(); track $index) {\n <div class=\"multi-range-item\">\n <span class=\"multi-range-text\">{{ range.rangeText }}</span>\n <button \n type=\"button\" \n class=\"btn-remove-range\" \n (click)=\"removeRange($index)\"\n title=\"Remove this range\">\n <svg width=\"14\" height=\"14\" fill=\"currentColor\" viewBox=\"0 0 16 16\">\n <path d=\"M2.146 2.854a.5.5 0 1 1 .708-.708L8 7.293l5.146-5.147a.5.5 0 0 1 .708.708L8.707 8l5.147 5.146a.5.5 0 0 1-.708.708L8 8.707l-5.146 5.147a.5.5 0 0 1-.708-.708L7.293 8 2.146 2.854Z\"/>\n </svg>\n </button>\n </div>\n }\n </div>\n </div>\n }\n\n <!-- Time Picker -->\n @if (enableTimePicker) {\n <div class=\"time-picker-container\">\n <!-- Start Time -->\n <div class=\"time-picker-section\">\n <div class=\"time-picker-label\">Start Time</div>\n <div class=\"time-picker-inputs\">\n <div class=\"time-input-group\">\n <button \n type=\"button\" \n class=\"time-btn time-btn-up\" \n (click)=\"incrementStartHour()\"\n title=\"Increment hour\">\n <svg width=\"12\" height=\"12\" fill=\"currentColor\" viewBox=\"0 0 16 16\">\n <path d=\"M7.247 4.86l-4.796 5.481c-.566.647-.106 1.659.753 1.659h9.592a1 1 0 0 0 .753-1.659l-4.796-5.48a1 1 0 0 0-1.506 0z\"/>\n </svg>\n </button>\n <input\n type=\"text\"\n class=\"time-input\"\n [value]=\"startHour.toString().padStart(2, '0')\"\n readonly\n title=\"Start hour\">\n <button \n type=\"button\" \n class=\"time-btn time-btn-down\" \n (click)=\"decrementStartHour()\"\n title=\"Decrement hour\">\n <svg width=\"12\" height=\"12\" fill=\"currentColor\" viewBox=\"0 0 16 16\">\n <path d=\"M7.247 11.14l-4.796-5.481C1.885 5.013 2.345 4 3.204 4h9.592a1 1 0 0 1 .753 1.659l-4.796 5.48a1 1 0 0 1-1.506 0z\"/>\n </svg>\n </button>\n </div>\n <span class=\"time-separator\">:</span>\n <div class=\"time-input-group\">\n <button \n type=\"button\" \n class=\"time-btn time-btn-up\" \n (click)=\"incrementStartMinute()\"\n title=\"Increment minute\">\n <svg width=\"12\" height=\"12\" fill=\"currentColor\" viewBox=\"0 0 16 16\">\n <path d=\"M7.247 4.86l-4.796 5.481c-.566.647-.106 1.659.753 1.659h9.592a1 1 0 0 0 .753-1.659l-4.796-5.48a1 1 0 0 0-1.506 0z\"/>\n </svg>\n </button>\n <input\n type=\"text\"\n class=\"time-input\"\n [value]=\"startMinute.toString().padStart(2, '0')\"\n readonly\n title=\"Start minute\">\n <button \n type=\"button\" \n class=\"time-btn time-btn-down\" \n (click)=\"decrementStartMinute()\"\n title=\"Decrement minute\">\n <svg width=\"12\" height=\"12\" fill=\"currentColor\" viewBox=\"0 0 16 16\">\n <path d=\"M7.247 11.14l-4.796-5.481C1.885 5.013 2.345 4 3.204 4h9.592a1 1 0 0 1 .753 1.659l-4.796 5.48a1 1 0 0 1-1.506 0z\"/>\n </svg>\n </button>\n </div>\n @if (timeFormat === '12h') {\n <div class=\"time-period\">\n {{ startHour >= 12 ? 'PM' : 'AM' }}\n </div>\n }\n </div>\n </div>\n\n <!-- Separator -->\n <div class=\"time-separator-vertical\"></div>\n\n <!-- End Time -->\n <div class=\"time-picker-section\">\n <div class=\"time-picker-label\">End Time</div>\n <div class=\"time-picker-inputs\">\n <div class=\"time-input-group\">\n <button \n type=\"button\" \n class=\"time-btn time-btn-up\" \n (click)=\"incrementEndHour()\"\n title=\"Increment hour\">\n <svg width=\"12\" height=\"12\" fill=\"currentColor\" viewBox=\"0 0 16 16\">\n <path d=\"M7.247 4.86l-4.796 5.481c-.566.647-.106 1.659.753 1.659h9.592a1 1 0 0 0 .753-1.659l-4.796-5.48a1 1 0 0 0-1.506 0z\"/>\n </svg>\n </button>\n <input\n type=\"text\"\n class=\"time-input\"\n [value]=\"endHour.toString().padStart(2, '0')\"\n readonly\n title=\"End hour\">\n <button \n type=\"button\" \n class=\"time-btn time-btn-down\" \n (click)=\"decrementEndHour()\"\n title=\"Decrement hour\">\n <svg width=\"12\" height=\"12\" fill=\"currentColor\" viewBox=\"0 0 16 16\">\n <path d=\"M7.247 11.14l-4.796-5.481C1.885 5.013 2.345 4 3.204 4h9.592a1 1 0 0 1 .753 1.659l-4.796 5.48a1 1 0 0 1-1.506 0z\"/>\n </svg>\n </button>\n </div>\n <span class=\"time-separator\">:</span>\n <div class=\"time-input-group\">\n <button \n type=\"button\" \n class=\"time-btn time-btn-up\" \n (click)=\"incrementEndMinute()\"\n title=\"Increment minute\">\n <svg width=\"12\" height=\"12\" fill=\"currentColor\" viewBox=\"0 0 16 16\">\n <path d=\"M7.247 4.86l-4.796 5.481c-.566.647-.106 1.659.753 1.659h9.592a1 1 0 0 0 .753-1.659l-4.796-5.48a1 1 0 0 0-1.506 0z\"/>\n </svg>\n </button>\n <input\n type=\"text\"\n class=\"time-input\"\n [value]=\"endMinute.toString().padStart(2, '0')\"\n readonly\n title=\"End minute\">\n <button \n type=\"button\" \n class=\"time-btn time-btn-down\" \n (click)=\"decrementEndMinute()\"\n title=\"Decrement minute\">\n <svg width=\"12\" height=\"12\" fill=\"currentColor\" viewBox=\"0 0 16 16\">\n <path d=\"M7.247 11.14l-4.796-5.481C1.885 5.013 2.345 4 3.204 4h9.592a1 1 0 0 1 .753 1.659l-4.796 5.48a1 1 0 0 1-1.506 0z\"/>\n </svg>\n </button>\n </div>\n @if (timeFormat === '12h') {\n <div class=\"time-period\">\n {{ endHour >= 12 ? 'PM' : 'AM' }}\n </div>\n }\n </div>\n </div>\n </div>\n }\n\n <!-- Footer with buttons -->\n @if (showClearButton || multiRange || requireApply) {\n <div class=\"date-picker-footer\">\n @if (requireApply && !multiRange) {\n <div class=\"apply-footer-actions\">\n <button \n type=\"button\" \n class=\"btn-cancel\" \n (click)=\"cancelSelection()\" \n [disabled]=\"!hasPendingChanges()\"\n title=\"Cancel selection\">\n Cancel\n </button>\n <button \n type=\"button\" \n class=\"btn-apply\" \n (click)=\"applySelection()\" \n [disabled]=\"!hasPendingChanges() || !pendingStartDate || !pendingEndDate\"\n title=\"Apply selection\">\n Apply\n </button>\n </div>\n }\n @if (multiRange) {\n <div class=\"multi-range-footer-actions\">\n <button type=\"button\" class=\"btn-clear\" (click)=\"clear()\" title=\"Clear all ranges\">\n Clear All\n </button>\n <button type=\"button\" class=\"btn-done\" (click)=\"closeDatePicker()\" title=\"Done selecting\">\n Done\n </button>\n </div>\n }\n @if (!multiRange && !requireApply && showClearButton) {\n <button type=\"button\" class=\"btn-clear\" (click)=\"clear()\" title=\"Clear selection\">\n Clear\n </button>\n }\n </div>\n }\n </div>\n }\n</div>\n", styles: [".datepicker-wrapper{position:relative;width:100%}.datepicker-wrapper .datepicker-input{display:block;width:100%;padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#495057;background-color:#fff;background-clip:padding-box;border:1px solid #ced4da;border-radius:.25rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out;cursor:pointer}.datepicker-wrapper .datepicker-input:hover{border-color:var(--input-border-hover, #ced4da)}.datepicker-wrapper .datepicker-input:focus{border-color:var(--input-border-focus, #80bdff);box-shadow:0 0 0 .2rem #007bff40;outline:0}.datepicker-wrapper .datepicker-input::placeholder{color:#6c757d;opacity:1}.datepicker-wrapper .datepicker-input:disabled,.datepicker-wrapper .datepicker-input[readonly]{background-color:#e9ecef;opacity:1}.date-picker-dropdown{position:absolute;top:100%;left:0;margin-top:4px;background:#fff;border:1px solid #e1e4e8;border-radius:8px;box-shadow:0 4px 12px #00000014,0 0 1px #00000014;padding:16px;z-index:1060;min-width:680px}@media (max-width: 768px){.date-picker-dropdown{min-width:100%;left:0;right:0}}.date-picker-header-only-close{display:flex;justify-content:flex-end;margin-bottom:12px}.date-picker-header-only-close .btn-close-calendar{background-color:transparent;border:1px solid transparent;color:#6b7280;padding:6px 10px;border-radius:6px;cursor:pointer;transition:all .15s ease;font-size:1.5rem;line-height:1}.date-picker-header-only-close .btn-close-calendar:hover{background-color:#fee;border-color:#fcc;color:#dc2626;transform:translateY(-1px);box-shadow:0 2px 4px #dc26261a}.date-picker-header-only-close .btn-close-calendar:active{transform:translateY(0);box-shadow:none}.date-picker-presets{display:flex;gap:6px;margin-bottom:16px;padding-bottom:12px;border-bottom:1px solid #e5e7eb;align-items:center}@media (max-width: 768px){.date-picker-presets{flex-wrap:wrap}}.date-picker-presets button{font-size:.75rem;padding:6px 14px;border:none;background-color:#f9fafb;color:#374151;border-radius:6px;transition:all .15s ease;font-weight:500;cursor:pointer;border:1px solid #e5e7eb}.date-picker-presets button:hover{background-color:#f3f4f6;border-color:#d1d5db;transform:translateY(-1px);box-shadow:0 2px 4px #0000000f}.date-picker-presets button:active{transform:translateY(0);box-shadow:none}.date-picker-presets .btn-close-calendar{margin-left:auto;background-color:transparent;border:1px solid transparent;color:#6b7280;padding:6px 10px;font-size:1.5rem;line-height:1}.date-picker-presets .btn-close-calendar:hover{background-color:#fee;border-color:#fcc;color:#dc2626;transform:translateY(-1px);box-shadow:0 2px 4px #dc26261a}.date-picker-presets .btn-close-calendar:active{transform:translateY(0);box-shadow:none}.date-picker-calendars{display:flex;gap:32px}@media (max-width: 768px){.date-picker-calendars{flex-direction:column;gap:16px}}.date-picker-calendar{flex:1;contain:layout paint}.date-picker-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:12px;padding:0 4px}.date-picker-header span{font-size:.813rem;font-weight:600;color:#111827}.date-picker-header button{padding:4px;color:#6b7280;text-decoration:none;border-radius:6px;transition:all .15s ease;border:none;background:transparent;cursor:pointer}.date-picker-header button:hover{background-color:#f3f4f6;color:#111827}.date-picker-weekdays{display:grid;grid-template-columns:repeat(7,1fr);gap:2px;margin-bottom:4px}.date-picker-weekdays span{text-align:center;font-size:.625rem;font-weight:600;color:#6b7280;padding:6px}.date-picker-days{display:grid;grid-template-columns:repeat(7,1fr);gap:2px;contain:layout paint}.date-picker-day{aspect-ratio:1;border:none;background:transparent;border-radius:50%;font-size:.75rem;cursor:pointer;transition:all .15s ease;color:#374151;font-weight:400;position:relative}.date-picker-day:hover:not(:disabled):not(.selected){background-color:#f3f4f6;color:#111827}.date-picker-day.empty{visibility:hidden}.date-picker-day.selected{background-color:#222;color:#fff;font-weight:600}.date-picker-day.in-range{background-color:#f9fafb;border-radius:0}.date-picker-day.in-hover-range{background-color:#e0e7ff;border-radius:0;opacity:.7;position:relative}.date-picker-day.in-hover-range:after{content:\"\";position:absolute;inset:0;border:1px dashed #6366f1;pointer-events:none}.date-picker-day:disabled{cursor:not-allowed;opacity:.3}.date-picker-day.disabled{cursor:not-allowed;opacity:.4;color:#9ca3af;background-color:#f9fafb;text-decoration:line-through}.date-picker-day.disabled:hover{background-color:#f9fafb;color:#9ca3af}.date-picker-day.keyboard-focused{outline:2px solid #3b82f6;outline-offset:2px;z-index:1}.date-picker-day.keyboard-focused:not(.selected){background-color:#eff6ff}.date-picker-day:focus-visible{outline:2px solid #3b82f6;outline-offset:2px;z-index:1}.date-picker-footer{padding:12px;border-top:1px solid #e1e4e8;display:flex;justify-content:center;gap:8px}.date-picker-footer .btn-clear{padding:8px 16px;background-color:#f6f8fa;border:1px solid #d0d7de;border-radius:6px;font-size:.875rem;font-weight:500;color:#24292f;cursor:pointer;transition:all .15s ease}.date-picker-footer .btn-clear:hover{background-color:#f3f4f6;border-color:#8c959f;transform:translateY(-1px);box-shadow:0 2px 4px #0000000d}.date-picker-footer .btn-clear:active{transform:translateY(0);box-shadow:none;background-color:#e9ecef}.date-picker-footer .btn-done{padding:8px 16px;background-color:#222;border:1px solid #222;border-radius:6px;font-size:.875rem;font-weight:500;color:#fff;cursor:pointer;transition:all .15s ease}.date-picker-footer .btn-done:hover{background-color:#000;border-color:#000;transform:translateY(-1px);box-shadow:0 2px 4px #00000026}.date-picker-footer .btn-done:active{transform:translateY(0);box-shadow:none}.date-picker-footer .btn-apply{padding:8px 24px;background-color:#2563eb;border:1px solid #2563eb;border-radius:6px;font-size:.875rem;font-weight:600;color:#fff;cursor:pointer;transition:all .15s ease}.date-picker-footer .btn-apply:hover:not(:disabled){background-color:#1d4ed8;border-color:#1d4ed8;transform:translateY(-1px);box-shadow:0 2px 8px #2563eb40}.date-picker-footer .btn-apply:active:not(:disabled){transform:translateY(0);box-shadow:none}.date-picker-footer .btn-apply:disabled{opacity:.5;cursor:not-allowed}.date-picker-footer .btn-cancel{padding:8px 24px;background-color:#f6f8fa;border:1px solid #d0d7de;border-radius:6px;font-size:.875rem;font-weight:500;color:#24292f;cursor:pointer;transition:all .15s ease}.date-picker-footer .btn-cancel:hover:not(:disabled){background-color:#f3f4f6;border-color:#8c959f;transform:translateY(-1px);box-shadow:0 2px 4px #0000000d}.date-picker-footer .btn-cancel:active:not(:disabled){transform:translateY(0);box-shadow:none;background-color:#e9ecef}.date-picker-footer .btn-cancel:disabled{opacity:.5;cursor:not-allowed}.date-picker-footer .apply-footer-actions{display:flex;gap:8px;width:100%;justify-content:flex-end}.date-picker-footer .multi-range-footer-actions{display:flex;gap:8px;width:100%;justify-content:space-between}.multi-range-list{border-top:1px solid #e1e4e8;border-bottom:1px solid #e1e4e8;padding:12px;margin-top:12px}.multi-range-list .multi-range-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:8px}.multi-range-list .multi-range-header .multi-range-title{font-size:.8125rem;font-weight:600;color:#24292f;text-transform:uppercase;letter-spacing:.025em}.multi-range-list .multi-range-items{display:flex;flex-direction:column;gap:6px;max-height:150px;overflow-y:auto}.multi-range-list .multi-range-items::-webkit-scrollbar{width:6px}.multi-range-list .multi-range-items::-webkit-scrollbar-track{background:#f1f3f4;border-radius:4px}.multi-range-list .multi-range-items::-webkit-scrollbar-thumb{background:#cbd5e0;border-radius:4px}.multi-range-list .multi-range-items::-webkit-scrollbar-thumb:hover{background:#a0aec0}.multi-range-list .multi-range-item{display:flex;align-items:center;justify-content:space-between;padding:8px 12px;background-color:#f6f8fa;border:1px solid #d0d7de;border-radius:6px;transition:all .15s ease}.multi-range-list .multi-range-item:hover{background-color:#f3f4f6;border-color:#8c959f}.multi-range-list .multi-range-item .multi-range-text{font-size:.875rem;color:#24292f;font-weight:500}.multi-range-list .multi-range-item .btn-remove-range{padding:4px;background-color:transparent;border:1px solid transparent;border-radius:4px;cursor:pointer;display:flex;align-items:center;justify-content:center;color:#6b7280;transition:all .15s ease}.multi-range-list .multi-range-item .btn-remove-range:hover{background-color:#fee;border-color:#fcc;color:#dc2626}.multi-range-list .multi-range-item .btn-remove-range:hover svg{transform:scale(1.1)}.multi-range-list .multi-range-item .btn-remove-range:active{transform:scale(.95)}.multi-range-list .multi-range-item .btn-remove-range svg{transition:transform .15s ease}.time-picker-container{display:flex;align-items:center;justify-content:center;gap:16px;padding:16px;border-top:1px solid #e5e7eb;border-bottom:1px solid #e5e7eb;margin:12px 0;background-color:#f9fafb}.time-picker-container .time-picker-section{display:flex;flex-direction:column;gap:8px;flex:1;align-items:center}.time-picker-container .time-picker-section .time-picker-label{font-size:.875rem;font-weight:600;color:#374151;text-align:center}.time-picker-container .time-picker-section .time-picker-inputs{display:flex;align-items:center;gap:8px}.time-picker-container .time-picker-section .time-picker-inputs .time-input-group{display:flex;flex-direction:column;gap:4px;align-items:center}.time-picker-container .time-picker-section .time-picker-inputs .time-input-group .time-input{width:48px;padding:8px 6px;text-align:center;font-size:1.25rem;font-weight:600;color:#1f2937;background-color:#fff;border:2px solid #e5e7eb;border-radius:6px;outline:none;transition:all .15s ease;cursor:default;-webkit-user-select:none;user-select:none}.time-picker-container .time-picker-section .time-picker-inputs .time-input-group .time-input:focus{border-color:var(--primary-color, #3b82f6);box-shadow:0 0 0 3px #3b82f61a}.time-picker-container .time-picker-section .time-picker-inputs .time-input-group .time-btn{padding:4px 8px;background-color:#fff;border:1px solid #d1d5db;border-radius:4px;cursor:pointer;display:flex;align-items:center;justify-content:center;color:#6b7280;transition:all .15s ease}.time-picker-container .time-picker-section .time-picker-inputs .time-input-group .time-btn:hover{background-color:var(--primary-color, #3b82f6);border-color:var(--primary-color, #3b82f6);color:#fff;transform:translateY(-1px);box-shadow:0 2px 4px #0000001a}.time-picker-container .time-picker-section .time-picker-inputs .time-input-group .time-btn:active{transform:translateY(0);box-shadow:none}.time-picker-container .time-picker-section .time-picker-inputs .time-input-group .time-btn svg{pointer-events:none}.time-picker-container .time-picker-section .time-picker-inputs .time-separator{font-size:1.5rem;font-weight:700;color:#9ca3af;margin:0 4px}.time-picker-container .time-picker-section .time-picker-inputs .time-period{font-size:.875rem;font-weight:600;color:#6b7280;padding:8px 12px;background-color:#f3f4f6;border-radius:6px;min-width:48px;text-align:center}.time-picker-container .time-separator-vertical{width:1px;height:80px;background-color:#e5e7eb}\n"] }]
|
|
4044
|
+
], template: "<div class=\"datepicker-wrapper\" \n [class]=\"'theme-' + theme\"\n [style.--input-border-hover]=\"inputBorderColorHover\"\n [style.--input-border-focus]=\"inputBorderColorFocus\">\n <input \n type=\"text\" \n class=\"datepicker-input\" \n [value]=\"dateRangeText()\" \n (click)=\"toggleDatePicker()\" \n [placeholder]=\"placeholder\"\n [disabled]=\"isDisabled()\"\n [attr.aria-label]=\"placeholder\"\n [attr.aria-expanded]=\"showDatePicker()\"\n [attr.aria-haspopup]=\"'dialog'\"\n role=\"combobox\"\n [ngStyle]=\"{\n 'background-color': inputBackgroundColor,\n 'color': inputTextColor,\n 'border-color': inputBorderColor,\n 'padding': inputPadding\n }\"\n readonly>\n\n @if (showDatePicker()) {\n <div class=\"date-picker-dropdown\">\n @if (showPresets) {\n <div class=\"date-picker-presets\">\n @for (preset of presets; track preset.label) {\n <button type=\"button\" (click)=\"selectPresetRange(preset)\">{{ preset.label }}</button>\n }\n <button type=\"button\" class=\"btn-close-calendar\" (click)=\"closeDatePicker()\" title=\"Close\">\n \u00D7\n </button>\n </div>\n }\n\n @if (!showPresets) {\n <div class=\"date-picker-header-only-close\">\n <button type=\"button\" class=\"btn-close-calendar\" (click)=\"closeDatePicker()\" title=\"Close\">\n \u00D7\n </button>\n </div>\n }\n\n <!-- Calendars -->\n <div class=\"date-picker-calendars\">\n <!-- Previous month calendar -->\n <div class=\"date-picker-calendar\">\n <div class=\"date-picker-header\">\n <button type=\"button\" (click)=\"changeMonth(-1)\">\n <svg width=\"16\" height=\"16\" fill=\"currentColor\" viewBox=\"0 0 16 16\">\n <path fill-rule=\"evenodd\" d=\"M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z\"/>\n </svg>\n </button>\n <span>{{ previousMonthName() }}</span>\n <button type=\"button\" style=\"visibility: hidden;\">\n <svg width=\"16\" height=\"16\" fill=\"currentColor\" viewBox=\"0 0 16 16\">\n <path fill-rule=\"evenodd\" d=\"M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z\"/>\n </svg>\n </button>\n </div>\n <div class=\"date-picker-weekdays\">\n @for (dayName of weekDayNames(); track $index) {\n <span>{{ dayName }}</span>\n }\n </div>\n <div class=\"date-picker-days\">\n @for (dayObj of previousMonthVisibleDays(); track dayObj.date || $index) {\n <button \n type=\"button\"\n class=\"date-picker-day\" \n [class.empty]=\"!dayObj.isCurrentMonth\"\n [class.selected]=\"dayObj.isStart || dayObj.isEnd\"\n [class.in-range]=\"dayObj.inRange && !dayObj.isStart && !dayObj.isEnd\"\n [class.in-hover-range]=\"dayObj.inHoverRange && !dayObj.isStart && !dayObj.isEnd\"\n [class.disabled]=\"dayObj.isDisabled\"\n [class.keyboard-focused]=\"enableKeyboardNavigation && hasKeyboardFocus(dayObj.date, 0)\"\n [attr.tabindex]=\"enableKeyboardNavigation && dayObj.isCurrentMonth && hasKeyboardFocus(dayObj.date, 0) ? 0 : -1\"\n [attr.aria-label]=\"formatDateDisplay(dayObj.date)\"\n [attr.aria-selected]=\"dayObj.isStart || dayObj.isEnd\"\n [attr.aria-current]=\"dayObj.isStart ? 'date' : null\"\n [attr.aria-disabled]=\"dayObj.isDisabled\"\n (click)=\"selectDay(dayObj)\"\n (mouseenter)=\"onDayHover(dayObj)\"\n (mouseleave)=\"clearDayHover()\"\n [disabled]=\"!dayObj.isCurrentMonth || dayObj.isDisabled\">\n {{ dayObj.day }}\n </button>\n }\n </div>\n </div>\n\n <!-- Current month calendar -->\n <div class=\"date-picker-calendar\">\n <div class=\"date-picker-header\">\n <button type=\"button\" style=\"visibility: hidden;\">\n <svg width=\"16\" height=\"16\" fill=\"currentColor\" viewBox=\"0 0 16 16\">\n <path fill-rule=\"evenodd\" d=\"M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z\"/>\n </svg>\n </button>\n <span>{{ currentMonthName() }}</span>\n <button type=\"button\" (click)=\"changeMonth(1)\">\n <svg width=\"16\" height=\"16\" fill=\"currentColor\" viewBox=\"0 0 16 16\">\n <path fill-rule=\"evenodd\" d=\"M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z\"/>\n </svg>\n </button>\n </div>\n <div class=\"date-picker-weekdays\">\n @for (dayName of weekDayNames(); track $index) {\n <span>{{ dayName }}</span>\n }\n </div>\n <div class=\"date-picker-days\">\n @for (dayObj of currentMonthVisibleDays(); track dayObj.date || $index) {\n <button \n type=\"button\"\n class=\"date-picker-day\" \n [class.empty]=\"!dayObj.isCurrentMonth\"\n [class.selected]=\"dayObj.isStart || dayObj.isEnd\"\n [class.in-range]=\"dayObj.inRange && !dayObj.isStart && !dayObj.isEnd\"\n [class.in-hover-range]=\"dayObj.inHoverRange && !dayObj.isStart && !dayObj.isEnd\"\n [class.disabled]=\"dayObj.isDisabled\"\n [class.keyboard-focused]=\"enableKeyboardNavigation && hasKeyboardFocus(dayObj.date, 1)\"\n [attr.tabindex]=\"enableKeyboardNavigation && dayObj.isCurrentMonth && hasKeyboardFocus(dayObj.date, 1) ? 0 : -1\"\n [attr.aria-label]=\"formatDateDisplay(dayObj.date)\"\n [attr.aria-selected]=\"dayObj.isStart || dayObj.isEnd\"\n [attr.aria-current]=\"dayObj.isStart ? 'date' : null\"\n [attr.aria-disabled]=\"dayObj.isDisabled\"\n (click)=\"selectDay(dayObj)\"\n (mouseenter)=\"onDayHover(dayObj)\"\n (mouseleave)=\"clearDayHover()\"\n [disabled]=\"!dayObj.isCurrentMonth || dayObj.isDisabled\">\n {{ dayObj.day }}\n </button>\n }\n </div>\n </div>\n </div>\n\n <!-- Multi-Range List -->\n @if (multiRange && selectedRanges().length > 0) {\n <div class=\"multi-range-list\">\n <div class=\"multi-range-header\">\n <span class=\"multi-range-title\">Selected Ranges ({{ selectedRanges().length }})</span>\n </div>\n <div class=\"multi-range-items\">\n @for (range of selectedRanges(); track $index) {\n <div class=\"multi-range-item\">\n <span class=\"multi-range-text\">{{ range.rangeText }}</span>\n <button \n type=\"button\" \n class=\"btn-remove-range\" \n (click)=\"removeRange($index)\"\n title=\"Remove this range\">\n <svg width=\"14\" height=\"14\" fill=\"currentColor\" viewBox=\"0 0 16 16\">\n <path d=\"M2.146 2.854a.5.5 0 1 1 .708-.708L8 7.293l5.146-5.147a.5.5 0 0 1 .708.708L8.707 8l5.147 5.146a.5.5 0 0 1-.708.708L8 8.707l-5.146 5.147a.5.5 0 0 1-.708-.708L7.293 8 2.146 2.854Z\"/>\n </svg>\n </button>\n </div>\n }\n </div>\n </div>\n }\n\n <!-- Time Picker -->\n @if (enableTimePicker) {\n <div class=\"time-picker-container\">\n <!-- Start Time -->\n <div class=\"time-picker-section\">\n <div class=\"time-picker-label\">Start Time</div>\n <div class=\"time-picker-inputs\">\n <div class=\"time-input-group\">\n <button \n type=\"button\" \n class=\"time-btn time-btn-up\" \n (click)=\"incrementStartHour()\"\n title=\"Increment hour\">\n <svg width=\"12\" height=\"12\" fill=\"currentColor\" viewBox=\"0 0 16 16\">\n <path d=\"M7.247 4.86l-4.796 5.481c-.566.647-.106 1.659.753 1.659h9.592a1 1 0 0 0 .753-1.659l-4.796-5.48a1 1 0 0 0-1.506 0z\"/>\n </svg>\n </button>\n <input\n type=\"text\"\n class=\"time-input\"\n [value]=\"startHour.toString().padStart(2, '0')\"\n readonly\n title=\"Start hour\">\n <button \n type=\"button\" \n class=\"time-btn time-btn-down\" \n (click)=\"decrementStartHour()\"\n title=\"Decrement hour\">\n <svg width=\"12\" height=\"12\" fill=\"currentColor\" viewBox=\"0 0 16 16\">\n <path d=\"M7.247 11.14l-4.796-5.481C1.885 5.013 2.345 4 3.204 4h9.592a1 1 0 0 1 .753 1.659l-4.796 5.48a1 1 0 0 1-1.506 0z\"/>\n </svg>\n </button>\n </div>\n <span class=\"time-separator\">:</span>\n <div class=\"time-input-group\">\n <button \n type=\"button\" \n class=\"time-btn time-btn-up\" \n (click)=\"incrementStartMinute()\"\n title=\"Increment minute\">\n <svg width=\"12\" height=\"12\" fill=\"currentColor\" viewBox=\"0 0 16 16\">\n <path d=\"M7.247 4.86l-4.796 5.481c-.566.647-.106 1.659.753 1.659h9.592a1 1 0 0 0 .753-1.659l-4.796-5.48a1 1 0 0 0-1.506 0z\"/>\n </svg>\n </button>\n <input\n type=\"text\"\n class=\"time-input\"\n [value]=\"startMinute.toString().padStart(2, '0')\"\n readonly\n title=\"Start minute\">\n <button \n type=\"button\" \n class=\"time-btn time-btn-down\" \n (click)=\"decrementStartMinute()\"\n title=\"Decrement minute\">\n <svg width=\"12\" height=\"12\" fill=\"currentColor\" viewBox=\"0 0 16 16\">\n <path d=\"M7.247 11.14l-4.796-5.481C1.885 5.013 2.345 4 3.204 4h9.592a1 1 0 0 1 .753 1.659l-4.796 5.48a1 1 0 0 1-1.506 0z\"/>\n </svg>\n </button>\n </div>\n @if (timeFormat === '12h') {\n <div class=\"time-period\">\n {{ startHour >= 12 ? 'PM' : 'AM' }}\n </div>\n }\n </div>\n </div>\n\n <!-- Separator -->\n <div class=\"time-separator-vertical\"></div>\n\n <!-- End Time -->\n <div class=\"time-picker-section\">\n <div class=\"time-picker-label\">End Time</div>\n <div class=\"time-picker-inputs\">\n <div class=\"time-input-group\">\n <button \n type=\"button\" \n class=\"time-btn time-btn-up\" \n (click)=\"incrementEndHour()\"\n title=\"Increment hour\">\n <svg width=\"12\" height=\"12\" fill=\"currentColor\" viewBox=\"0 0 16 16\">\n <path d=\"M7.247 4.86l-4.796 5.481c-.566.647-.106 1.659.753 1.659h9.592a1 1 0 0 0 .753-1.659l-4.796-5.48a1 1 0 0 0-1.506 0z\"/>\n </svg>\n </button>\n <input\n type=\"text\"\n class=\"time-input\"\n [value]=\"endHour.toString().padStart(2, '0')\"\n readonly\n title=\"End hour\">\n <button \n type=\"button\" \n class=\"time-btn time-btn-down\" \n (click)=\"decrementEndHour()\"\n title=\"Decrement hour\">\n <svg width=\"12\" height=\"12\" fill=\"currentColor\" viewBox=\"0 0 16 16\">\n <path d=\"M7.247 11.14l-4.796-5.481C1.885 5.013 2.345 4 3.204 4h9.592a1 1 0 0 1 .753 1.659l-4.796 5.48a1 1 0 0 1-1.506 0z\"/>\n </svg>\n </button>\n </div>\n <span class=\"time-separator\">:</span>\n <div class=\"time-input-group\">\n <button \n type=\"button\" \n class=\"time-btn time-btn-up\" \n (click)=\"incrementEndMinute()\"\n title=\"Increment minute\">\n <svg width=\"12\" height=\"12\" fill=\"currentColor\" viewBox=\"0 0 16 16\">\n <path d=\"M7.247 4.86l-4.796 5.481c-.566.647-.106 1.659.753 1.659h9.592a1 1 0 0 0 .753-1.659l-4.796-5.48a1 1 0 0 0-1.506 0z\"/>\n </svg>\n </button>\n <input\n type=\"text\"\n class=\"time-input\"\n [value]=\"endMinute.toString().padStart(2, '0')\"\n readonly\n title=\"End minute\">\n <button \n type=\"button\" \n class=\"time-btn time-btn-down\" \n (click)=\"decrementEndMinute()\"\n title=\"Decrement minute\">\n <svg width=\"12\" height=\"12\" fill=\"currentColor\" viewBox=\"0 0 16 16\">\n <path d=\"M7.247 11.14l-4.796-5.481C1.885 5.013 2.345 4 3.204 4h9.592a1 1 0 0 1 .753 1.659l-4.796 5.48a1 1 0 0 1-1.506 0z\"/>\n </svg>\n </button>\n </div>\n @if (timeFormat === '12h') {\n <div class=\"time-period\">\n {{ endHour >= 12 ? 'PM' : 'AM' }}\n </div>\n }\n </div>\n </div>\n </div>\n }\n\n <!-- Footer with buttons -->\n @if (showClearButton || multiRange || requireApply) {\n <div class=\"date-picker-footer\">\n @if (requireApply && !multiRange) {\n <div class=\"apply-footer-actions\">\n <button \n type=\"button\" \n class=\"btn-cancel\" \n (click)=\"cancelSelection()\" \n [disabled]=\"!hasPendingChanges()\"\n title=\"Cancel selection\">\n Cancel\n </button>\n <button \n type=\"button\" \n class=\"btn-apply\" \n (click)=\"applySelection()\" \n [disabled]=\"!hasPendingChanges() || !pendingStartDate || !pendingEndDate\"\n title=\"Apply selection\">\n Apply\n </button>\n </div>\n }\n @if (multiRange) {\n <div class=\"multi-range-footer-actions\">\n <button type=\"button\" class=\"btn-clear\" (click)=\"clear()\" title=\"Clear all ranges\">\n Clear All\n </button>\n <button type=\"button\" class=\"btn-done\" (click)=\"closeDatePicker()\" title=\"Done selecting\">\n Done\n </button>\n </div>\n }\n @if (!multiRange && !requireApply && showClearButton) {\n <button type=\"button\" class=\"btn-clear\" (click)=\"clear()\" title=\"Clear selection\">\n Clear\n </button>\n }\n </div>\n }\n </div>\n }\n</div>\n", styles: [".datepicker-wrapper{position:relative;width:100%}.datepicker-wrapper .datepicker-input{display:block;width:100%;padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#495057;background-color:#fff;background-clip:padding-box;border:1px solid #ced4da;border-radius:.25rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out;cursor:pointer}.datepicker-wrapper .datepicker-input:hover{border-color:var(--input-border-hover, #ced4da)}.datepicker-wrapper .datepicker-input:focus{border-color:var(--input-border-focus, #80bdff);box-shadow:0 0 0 .2rem #007bff40;outline:0}.datepicker-wrapper .datepicker-input::placeholder{color:#6c757d;opacity:1}.datepicker-wrapper .datepicker-input:disabled,.datepicker-wrapper .datepicker-input[readonly]{background-color:#e9ecef;opacity:1}.date-picker-dropdown{position:absolute;top:100%;left:0;margin-top:4px;background:#fff;border:1px solid #e1e4e8;border-radius:8px;box-shadow:0 4px 12px #00000014,0 0 1px #00000014;padding:16px;z-index:1060;min-width:680px}@media(max-width:768px){.date-picker-dropdown{min-width:100%;left:0;right:0}}.date-picker-header-only-close{display:flex;justify-content:flex-end;margin-bottom:12px}.date-picker-header-only-close .btn-close-calendar{background-color:transparent;border:1px solid transparent;color:#6b7280;padding:6px 10px;border-radius:6px;cursor:pointer;transition:all .15s ease;font-size:1.5rem;line-height:1}.date-picker-header-only-close .btn-close-calendar:hover{background-color:#fee;border-color:#fcc;color:#dc2626;transform:translateY(-1px);box-shadow:0 2px 4px #dc26261a}.date-picker-header-only-close .btn-close-calendar:active{transform:translateY(0);box-shadow:none}.date-picker-presets{display:flex;gap:6px;margin-bottom:16px;padding-bottom:12px;border-bottom:1px solid #e5e7eb;align-items:center}@media(max-width:768px){.date-picker-presets{flex-wrap:wrap}}.date-picker-presets button{font-size:.75rem;padding:6px 14px;border:none;background-color:#f9fafb;color:#374151;border-radius:6px;transition:all .15s ease;font-weight:500;cursor:pointer;border:1px solid #e5e7eb}.date-picker-presets button:hover{background-color:#f3f4f6;border-color:#d1d5db;transform:translateY(-1px);box-shadow:0 2px 4px #0000000f}.date-picker-presets button:active{transform:translateY(0);box-shadow:none}.date-picker-presets .btn-close-calendar{margin-left:auto;background-color:transparent;border:1px solid transparent;color:#6b7280;padding:6px 10px;font-size:1.5rem;line-height:1}.date-picker-presets .btn-close-calendar:hover{background-color:#fee;border-color:#fcc;color:#dc2626;transform:translateY(-1px);box-shadow:0 2px 4px #dc26261a}.date-picker-presets .btn-close-calendar:active{transform:translateY(0);box-shadow:none}.date-picker-calendars{display:flex;gap:32px}@media(max-width:768px){.date-picker-calendars{flex-direction:column;gap:16px}}.date-picker-calendar{flex:1;contain:layout paint}.date-picker-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:12px;padding:0 4px}.date-picker-header span{font-size:.813rem;font-weight:600;color:#111827}.date-picker-header button{padding:4px;color:#6b7280;text-decoration:none;border-radius:6px;transition:all .15s ease;border:none;background:transparent;cursor:pointer}.date-picker-header button:hover{background-color:#f3f4f6;color:#111827}.date-picker-weekdays{display:grid;grid-template-columns:repeat(7,1fr);gap:2px;margin-bottom:4px}.date-picker-weekdays span{text-align:center;font-size:.625rem;font-weight:600;color:#6b7280;padding:6px}.date-picker-days{display:grid;grid-template-columns:repeat(7,1fr);gap:2px;contain:layout paint}.date-picker-day{aspect-ratio:1;border:none;background:transparent;border-radius:50%;font-size:.75rem;cursor:pointer;transition:all .15s ease;color:#374151;font-weight:400;position:relative}.date-picker-day:hover:not(:disabled):not(.selected){background-color:#f3f4f6;color:#111827}.date-picker-day.empty{visibility:hidden}.date-picker-day.selected{background-color:#222;color:#fff;font-weight:600}.date-picker-day.in-range{background-color:#f9fafb;border-radius:0}.date-picker-day.in-hover-range{background-color:#e0e7ff;border-radius:0;opacity:.7;position:relative}.date-picker-day.in-hover-range:after{content:\"\";position:absolute;inset:0;border:1px dashed #6366f1;pointer-events:none}.date-picker-day:disabled{cursor:not-allowed;opacity:.3}.date-picker-day.disabled{cursor:not-allowed;opacity:.4;color:#9ca3af;background-color:#f9fafb;text-decoration:line-through}.date-picker-day.disabled:hover{background-color:#f9fafb;color:#9ca3af}.date-picker-day.keyboard-focused{outline:2px solid #3b82f6;outline-offset:2px;z-index:1}.date-picker-day.keyboard-focused:not(.selected){background-color:#eff6ff}.date-picker-day:focus-visible{outline:2px solid #3b82f6;outline-offset:2px;z-index:1}.date-picker-footer{padding:12px;border-top:1px solid #e1e4e8;display:flex;justify-content:center;gap:8px}.date-picker-footer .btn-clear{padding:8px 16px;background-color:#f6f8fa;border:1px solid #d0d7de;border-radius:6px;font-size:.875rem;font-weight:500;color:#24292f;cursor:pointer;transition:all .15s ease}.date-picker-footer .btn-clear:hover{background-color:#f3f4f6;border-color:#8c959f;transform:translateY(-1px);box-shadow:0 2px 4px #0000000d}.date-picker-footer .btn-clear:active{transform:translateY(0);box-shadow:none;background-color:#e9ecef}.date-picker-footer .btn-done{padding:8px 16px;background-color:#222;border:1px solid #222;border-radius:6px;font-size:.875rem;font-weight:500;color:#fff;cursor:pointer;transition:all .15s ease}.date-picker-footer .btn-done:hover{background-color:#000;border-color:#000;transform:translateY(-1px);box-shadow:0 2px 4px #00000026}.date-picker-footer .btn-done:active{transform:translateY(0);box-shadow:none}.date-picker-footer .btn-apply{padding:8px 24px;background-color:#2563eb;border:1px solid #2563eb;border-radius:6px;font-size:.875rem;font-weight:600;color:#fff;cursor:pointer;transition:all .15s ease}.date-picker-footer .btn-apply:hover:not(:disabled){background-color:#1d4ed8;border-color:#1d4ed8;transform:translateY(-1px);box-shadow:0 2px 8px #2563eb40}.date-picker-footer .btn-apply:active:not(:disabled){transform:translateY(0);box-shadow:none}.date-picker-footer .btn-apply:disabled{opacity:.5;cursor:not-allowed}.date-picker-footer .btn-cancel{padding:8px 24px;background-color:#f6f8fa;border:1px solid #d0d7de;border-radius:6px;font-size:.875rem;font-weight:500;color:#24292f;cursor:pointer;transition:all .15s ease}.date-picker-footer .btn-cancel:hover:not(:disabled){background-color:#f3f4f6;border-color:#8c959f;transform:translateY(-1px);box-shadow:0 2px 4px #0000000d}.date-picker-footer .btn-cancel:active:not(:disabled){transform:translateY(0);box-shadow:none;background-color:#e9ecef}.date-picker-footer .btn-cancel:disabled{opacity:.5;cursor:not-allowed}.date-picker-footer .apply-footer-actions{display:flex;gap:8px;width:100%;justify-content:flex-end}.date-picker-footer .multi-range-footer-actions{display:flex;gap:8px;width:100%;justify-content:space-between}.multi-range-list{border-top:1px solid #e1e4e8;border-bottom:1px solid #e1e4e8;padding:12px;margin-top:12px}.multi-range-list .multi-range-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:8px}.multi-range-list .multi-range-header .multi-range-title{font-size:.8125rem;font-weight:600;color:#24292f;text-transform:uppercase;letter-spacing:.025em}.multi-range-list .multi-range-items{display:flex;flex-direction:column;gap:6px;max-height:150px;overflow-y:auto}.multi-range-list .multi-range-items::-webkit-scrollbar{width:6px}.multi-range-list .multi-range-items::-webkit-scrollbar-track{background:#f1f3f4;border-radius:4px}.multi-range-list .multi-range-items::-webkit-scrollbar-thumb{background:#cbd5e0;border-radius:4px}.multi-range-list .multi-range-items::-webkit-scrollbar-thumb:hover{background:#a0aec0}.multi-range-list .multi-range-item{display:flex;align-items:center;justify-content:space-between;padding:8px 12px;background-color:#f6f8fa;border:1px solid #d0d7de;border-radius:6px;transition:all .15s ease}.multi-range-list .multi-range-item:hover{background-color:#f3f4f6;border-color:#8c959f}.multi-range-list .multi-range-item .multi-range-text{font-size:.875rem;color:#24292f;font-weight:500}.multi-range-list .multi-range-item .btn-remove-range{padding:4px;background-color:transparent;border:1px solid transparent;border-radius:4px;cursor:pointer;display:flex;align-items:center;justify-content:center;color:#6b7280;transition:all .15s ease}.multi-range-list .multi-range-item .btn-remove-range:hover{background-color:#fee;border-color:#fcc;color:#dc2626}.multi-range-list .multi-range-item .btn-remove-range:hover svg{transform:scale(1.1)}.multi-range-list .multi-range-item .btn-remove-range:active{transform:scale(.95)}.multi-range-list .multi-range-item .btn-remove-range svg{transition:transform .15s ease}.time-picker-container{display:flex;align-items:center;justify-content:center;gap:16px;padding:16px;border-top:1px solid #e5e7eb;border-bottom:1px solid #e5e7eb;margin:12px 0;background-color:#f9fafb}.time-picker-container .time-picker-section{display:flex;flex-direction:column;gap:8px;flex:1;align-items:center}.time-picker-container .time-picker-section .time-picker-label{font-size:.875rem;font-weight:600;color:#374151;text-align:center}.time-picker-container .time-picker-section .time-picker-inputs{display:flex;align-items:center;gap:8px}.time-picker-container .time-picker-section .time-picker-inputs .time-input-group{display:flex;flex-direction:column;gap:4px;align-items:center}.time-picker-container .time-picker-section .time-picker-inputs .time-input-group .time-input{width:48px;padding:8px 6px;text-align:center;font-size:1.25rem;font-weight:600;color:#1f2937;background-color:#fff;border:2px solid #e5e7eb;border-radius:6px;outline:none;transition:all .15s ease;cursor:default;-webkit-user-select:none;user-select:none}.time-picker-container .time-picker-section .time-picker-inputs .time-input-group .time-input:focus{border-color:var(--primary-color, #3b82f6);box-shadow:0 0 0 3px #3b82f61a}.time-picker-container .time-picker-section .time-picker-inputs .time-input-group .time-btn{padding:4px 8px;background-color:#fff;border:1px solid #d1d5db;border-radius:4px;cursor:pointer;display:flex;align-items:center;justify-content:center;color:#6b7280;transition:all .15s ease}.time-picker-container .time-picker-section .time-picker-inputs .time-input-group .time-btn:hover{background-color:var(--primary-color, #3b82f6);border-color:var(--primary-color, #3b82f6);color:#fff;transform:translateY(-1px);box-shadow:0 2px 4px #0000001a}.time-picker-container .time-picker-section .time-picker-inputs .time-input-group .time-btn:active{transform:translateY(0);box-shadow:none}.time-picker-container .time-picker-section .time-picker-inputs .time-input-group .time-btn svg{pointer-events:none}.time-picker-container .time-picker-section .time-picker-inputs .time-separator{font-size:1.5rem;font-weight:700;color:#9ca3af;margin:0 4px}.time-picker-container .time-picker-section .time-picker-inputs .time-period{font-size:.875rem;font-weight:600;color:#6b7280;padding:8px 12px;background-color:#f3f4f6;border-radius:6px;min-width:48px;text-align:center}.time-picker-container .time-separator-vertical{width:1px;height:80px;background-color:#e5e7eb}\n"] }]
|
|
3872
4045
|
}], ctorParameters: () => [{ type: i0.ElementRef }], propDecorators: { placeholder: [{
|
|
3873
4046
|
type: Input
|
|
3874
4047
|
}], startDate: [{
|
|
@@ -3942,550 +4115,33 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
3942
4115
|
}] } });
|
|
3943
4116
|
|
|
3944
4117
|
/**
|
|
3945
|
-
*
|
|
3946
|
-
* Perfect for dashboards, reporting, POS, BI apps, and ERP systems
|
|
3947
|
-
*/
|
|
3948
|
-
/**
|
|
3949
|
-
* Format a date as YYYY-MM-DD string
|
|
3950
|
-
*/
|
|
3951
|
-
function formatDate(date) {
|
|
3952
|
-
const year = date.getFullYear();
|
|
3953
|
-
const month = String(date.getMonth() + 1).padStart(2, '0');
|
|
3954
|
-
const day = String(date.getDate()).padStart(2, '0');
|
|
3955
|
-
return `${year}-${month}-${day}`;
|
|
3956
|
-
}
|
|
3957
|
-
/**
|
|
3958
|
-
* Get the start of today
|
|
3959
|
-
*/
|
|
3960
|
-
function getToday() {
|
|
3961
|
-
const today = new Date();
|
|
3962
|
-
return {
|
|
3963
|
-
start: formatDate(today),
|
|
3964
|
-
end: formatDate(today)
|
|
3965
|
-
};
|
|
3966
|
-
}
|
|
3967
|
-
/**
|
|
3968
|
-
* Get yesterday's date range
|
|
3969
|
-
*/
|
|
3970
|
-
function getYesterday() {
|
|
3971
|
-
const yesterday = new Date();
|
|
3972
|
-
yesterday.setDate(yesterday.getDate() - 1);
|
|
3973
|
-
return {
|
|
3974
|
-
start: formatDate(yesterday),
|
|
3975
|
-
end: formatDate(yesterday)
|
|
3976
|
-
};
|
|
3977
|
-
}
|
|
3978
|
-
/**
|
|
3979
|
-
* Get last N days (including today)
|
|
3980
|
-
*/
|
|
3981
|
-
function getLastNDays(days) {
|
|
3982
|
-
const end = new Date();
|
|
3983
|
-
const start = new Date();
|
|
3984
|
-
start.setDate(start.getDate() - days + 1);
|
|
3985
|
-
return {
|
|
3986
|
-
start: formatDate(start),
|
|
3987
|
-
end: formatDate(end)
|
|
3988
|
-
};
|
|
3989
|
-
}
|
|
3990
|
-
/**
|
|
3991
|
-
* Get this week (Monday to Sunday)
|
|
3992
|
-
*/
|
|
3993
|
-
function getThisWeek() {
|
|
3994
|
-
const today = new Date();
|
|
3995
|
-
const dayOfWeek = today.getDay();
|
|
3996
|
-
const start = new Date(today);
|
|
3997
|
-
// Adjust to Monday (1) as first day of week
|
|
3998
|
-
const daysToMonday = dayOfWeek === 0 ? 6 : dayOfWeek - 1;
|
|
3999
|
-
start.setDate(start.getDate() - daysToMonday);
|
|
4000
|
-
const end = new Date(start);
|
|
4001
|
-
end.setDate(end.getDate() + 6);
|
|
4002
|
-
return {
|
|
4003
|
-
start: formatDate(start),
|
|
4004
|
-
end: formatDate(end)
|
|
4005
|
-
};
|
|
4006
|
-
}
|
|
4007
|
-
/**
|
|
4008
|
-
* Get last week (Monday to Sunday)
|
|
4009
|
-
*/
|
|
4010
|
-
function getLastWeek() {
|
|
4011
|
-
const today = new Date();
|
|
4012
|
-
const dayOfWeek = today.getDay();
|
|
4013
|
-
const daysToMonday = dayOfWeek === 0 ? 6 : dayOfWeek - 1;
|
|
4014
|
-
const lastMonday = new Date(today);
|
|
4015
|
-
lastMonday.setDate(lastMonday.getDate() - daysToMonday - 7);
|
|
4016
|
-
const lastSunday = new Date(lastMonday);
|
|
4017
|
-
lastSunday.setDate(lastSunday.getDate() + 6);
|
|
4018
|
-
return {
|
|
4019
|
-
start: formatDate(lastMonday),
|
|
4020
|
-
end: formatDate(lastSunday)
|
|
4021
|
-
};
|
|
4022
|
-
}
|
|
4023
|
-
/**
|
|
4024
|
-
* Get this month (1st to last day)
|
|
4025
|
-
*/
|
|
4026
|
-
function getThisMonth() {
|
|
4027
|
-
const today = new Date();
|
|
4028
|
-
const start = new Date(today.getFullYear(), today.getMonth(), 1);
|
|
4029
|
-
const end = new Date(today.getFullYear(), today.getMonth() + 1, 0);
|
|
4030
|
-
return {
|
|
4031
|
-
start: formatDate(start),
|
|
4032
|
-
end: formatDate(end)
|
|
4033
|
-
};
|
|
4034
|
-
}
|
|
4035
|
-
/**
|
|
4036
|
-
* Get last month (1st to last day)
|
|
4037
|
-
*/
|
|
4038
|
-
function getLastMonth() {
|
|
4039
|
-
const today = new Date();
|
|
4040
|
-
const start = new Date(today.getFullYear(), today.getMonth() - 1, 1);
|
|
4041
|
-
const end = new Date(today.getFullYear(), today.getMonth(), 0);
|
|
4042
|
-
return {
|
|
4043
|
-
start: formatDate(start),
|
|
4044
|
-
end: formatDate(end)
|
|
4045
|
-
};
|
|
4046
|
-
}
|
|
4047
|
-
/**
|
|
4048
|
-
* Get month to date (1st of current month to today)
|
|
4049
|
-
*/
|
|
4050
|
-
function getMonthToDate() {
|
|
4051
|
-
const today = new Date();
|
|
4052
|
-
const start = new Date(today.getFullYear(), today.getMonth(), 1);
|
|
4053
|
-
return {
|
|
4054
|
-
start: formatDate(start),
|
|
4055
|
-
end: formatDate(today)
|
|
4056
|
-
};
|
|
4057
|
-
}
|
|
4058
|
-
/**
|
|
4059
|
-
* Get this quarter (Q1, Q2, Q3, or Q4)
|
|
4060
|
-
*/
|
|
4061
|
-
function getThisQuarter() {
|
|
4062
|
-
const today = new Date();
|
|
4063
|
-
const currentMonth = today.getMonth();
|
|
4064
|
-
const quarterStartMonth = Math.floor(currentMonth / 3) * 3;
|
|
4065
|
-
const start = new Date(today.getFullYear(), quarterStartMonth, 1);
|
|
4066
|
-
const end = new Date(today.getFullYear(), quarterStartMonth + 3, 0);
|
|
4067
|
-
return {
|
|
4068
|
-
start: formatDate(start),
|
|
4069
|
-
end: formatDate(end)
|
|
4070
|
-
};
|
|
4071
|
-
}
|
|
4072
|
-
/**
|
|
4073
|
-
* Get last quarter
|
|
4074
|
-
*/
|
|
4075
|
-
function getLastQuarter() {
|
|
4076
|
-
const today = new Date();
|
|
4077
|
-
const currentMonth = today.getMonth();
|
|
4078
|
-
const lastQuarterStartMonth = Math.floor(currentMonth / 3) * 3 - 3;
|
|
4079
|
-
const start = new Date(today.getFullYear(), lastQuarterStartMonth, 1);
|
|
4080
|
-
const end = new Date(today.getFullYear(), lastQuarterStartMonth + 3, 0);
|
|
4081
|
-
return {
|
|
4082
|
-
start: formatDate(start),
|
|
4083
|
-
end: formatDate(end)
|
|
4084
|
-
};
|
|
4085
|
-
}
|
|
4086
|
-
/**
|
|
4087
|
-
* Get quarter to date (start of current quarter to today)
|
|
4088
|
-
*/
|
|
4089
|
-
function getQuarterToDate() {
|
|
4090
|
-
const today = new Date();
|
|
4091
|
-
const currentMonth = today.getMonth();
|
|
4092
|
-
const quarterStartMonth = Math.floor(currentMonth / 3) * 3;
|
|
4093
|
-
const start = new Date(today.getFullYear(), quarterStartMonth, 1);
|
|
4094
|
-
return {
|
|
4095
|
-
start: formatDate(start),
|
|
4096
|
-
end: formatDate(today)
|
|
4097
|
-
};
|
|
4098
|
-
}
|
|
4099
|
-
/**
|
|
4100
|
-
* Get this year (January 1 to December 31)
|
|
4101
|
-
*/
|
|
4102
|
-
function getThisYear() {
|
|
4103
|
-
const today = new Date();
|
|
4104
|
-
const start = new Date(today.getFullYear(), 0, 1);
|
|
4105
|
-
const end = new Date(today.getFullYear(), 11, 31);
|
|
4106
|
-
return {
|
|
4107
|
-
start: formatDate(start),
|
|
4108
|
-
end: formatDate(end)
|
|
4109
|
-
};
|
|
4110
|
-
}
|
|
4111
|
-
/**
|
|
4112
|
-
* Get last year
|
|
4113
|
-
*/
|
|
4114
|
-
function getLastYear() {
|
|
4115
|
-
const today = new Date();
|
|
4116
|
-
const start = new Date(today.getFullYear() - 1, 0, 1);
|
|
4117
|
-
const end = new Date(today.getFullYear() - 1, 11, 31);
|
|
4118
|
-
return {
|
|
4119
|
-
start: formatDate(start),
|
|
4120
|
-
end: formatDate(end)
|
|
4121
|
-
};
|
|
4122
|
-
}
|
|
4123
|
-
/**
|
|
4124
|
-
* Get year to date (January 1 to today)
|
|
4125
|
-
*/
|
|
4126
|
-
function getYearToDate() {
|
|
4127
|
-
const today = new Date();
|
|
4128
|
-
const start = new Date(today.getFullYear(), 0, 1);
|
|
4129
|
-
return {
|
|
4130
|
-
start: formatDate(start),
|
|
4131
|
-
end: formatDate(today)
|
|
4132
|
-
};
|
|
4133
|
-
}
|
|
4134
|
-
/**
|
|
4135
|
-
* Get last N months (including current partial month)
|
|
4136
|
-
*/
|
|
4137
|
-
function getLastNMonths(months) {
|
|
4138
|
-
const today = new Date();
|
|
4139
|
-
const start = new Date(today);
|
|
4140
|
-
start.setMonth(start.getMonth() - months);
|
|
4141
|
-
return {
|
|
4142
|
-
start: formatDate(start),
|
|
4143
|
-
end: formatDate(today)
|
|
4144
|
-
};
|
|
4145
|
-
}
|
|
4146
|
-
/**
|
|
4147
|
-
* Get last N years
|
|
4148
|
-
*/
|
|
4149
|
-
function getLastNYears(years) {
|
|
4150
|
-
const today = new Date();
|
|
4151
|
-
const start = new Date(today);
|
|
4152
|
-
start.setFullYear(start.getFullYear() - years);
|
|
4153
|
-
return {
|
|
4154
|
-
start: formatDate(start),
|
|
4155
|
-
end: formatDate(today)
|
|
4156
|
-
};
|
|
4157
|
-
}
|
|
4158
|
-
/**
|
|
4159
|
-
* Pre-built preset configurations for common use cases
|
|
4160
|
-
* Import and use these directly in your component
|
|
4161
|
-
*/
|
|
4162
|
-
const CommonPresets = {
|
|
4163
|
-
/**
|
|
4164
|
-
* Dashboard presets - Perfect for analytics dashboards
|
|
4165
|
-
*/
|
|
4166
|
-
dashboard: [
|
|
4167
|
-
{ label: 'Today', getValue: getToday },
|
|
4168
|
-
{ label: 'Yesterday', getValue: getYesterday },
|
|
4169
|
-
{ label: 'Last 7 days', getValue: () => getLastNDays(7) },
|
|
4170
|
-
{ label: 'Last 30 days', getValue: () => getLastNDays(30) },
|
|
4171
|
-
{ label: 'This month', getValue: getThisMonth },
|
|
4172
|
-
{ label: 'Last month', getValue: getLastMonth }
|
|
4173
|
-
],
|
|
4174
|
-
/**
|
|
4175
|
-
* Reporting presets - Perfect for business reporting
|
|
4176
|
-
*/
|
|
4177
|
-
reporting: [
|
|
4178
|
-
{ label: 'Today', getValue: getToday },
|
|
4179
|
-
{ label: 'This week', getValue: getThisWeek },
|
|
4180
|
-
{ label: 'Last week', getValue: getLastWeek },
|
|
4181
|
-
{ label: 'This month', getValue: getThisMonth },
|
|
4182
|
-
{ label: 'Last month', getValue: getLastMonth },
|
|
4183
|
-
{ label: 'This quarter', getValue: getThisQuarter },
|
|
4184
|
-
{ label: 'Last quarter', getValue: getLastQuarter }
|
|
4185
|
-
],
|
|
4186
|
-
/**
|
|
4187
|
-
* Financial presets - Perfect for ERP and accounting systems
|
|
4188
|
-
*/
|
|
4189
|
-
financial: [
|
|
4190
|
-
{ label: 'Month to date', getValue: getMonthToDate },
|
|
4191
|
-
{ label: 'Quarter to date', getValue: getQuarterToDate },
|
|
4192
|
-
{ label: 'Year to date', getValue: getYearToDate },
|
|
4193
|
-
{ label: 'Last month', getValue: getLastMonth },
|
|
4194
|
-
{ label: 'Last quarter', getValue: getLastQuarter },
|
|
4195
|
-
{ label: 'Last year', getValue: getLastYear }
|
|
4196
|
-
],
|
|
4197
|
-
/**
|
|
4198
|
-
* Analytics presets - Perfect for BI and data analysis
|
|
4199
|
-
*/
|
|
4200
|
-
analytics: [
|
|
4201
|
-
{ label: 'Last 7 days', getValue: () => getLastNDays(7) },
|
|
4202
|
-
{ label: 'Last 14 days', getValue: () => getLastNDays(14) },
|
|
4203
|
-
{ label: 'Last 30 days', getValue: () => getLastNDays(30) },
|
|
4204
|
-
{ label: 'Last 60 days', getValue: () => getLastNDays(60) },
|
|
4205
|
-
{ label: 'Last 90 days', getValue: () => getLastNDays(90) },
|
|
4206
|
-
{ label: 'Last 180 days', getValue: () => getLastNDays(180) },
|
|
4207
|
-
{ label: 'Last 365 days', getValue: () => getLastNDays(365) }
|
|
4208
|
-
],
|
|
4209
|
-
/**
|
|
4210
|
-
* Simple presets - Basic common ranges
|
|
4211
|
-
*/
|
|
4212
|
-
simple: [
|
|
4213
|
-
{ label: 'Today', getValue: getToday },
|
|
4214
|
-
{ label: 'Last 7 days', getValue: () => getLastNDays(7) },
|
|
4215
|
-
{ label: 'Last 30 days', getValue: () => getLastNDays(30) },
|
|
4216
|
-
{ label: 'This year', getValue: getThisYear }
|
|
4217
|
-
]
|
|
4218
|
-
};
|
|
4219
|
-
|
|
4220
|
-
/**
|
|
4221
|
-
* Provider Functions for Built-in Presets
|
|
4222
|
-
*
|
|
4223
|
-
* Version: 3.6.0
|
|
4224
|
-
*
|
|
4225
|
-
* Automatic registration of built-in date range presets.
|
|
4226
|
-
* These providers ensure backward compatibility by auto-registering
|
|
4227
|
-
* all standard presets (TODAY, LAST_7_DAYS, THIS_MONTH, etc.)
|
|
4228
|
-
*
|
|
4229
|
-
* USAGE IN LIBRARY (Internal):
|
|
4230
|
-
* Built-in presets are registered automatically via Angular providers.
|
|
4231
|
-
* Library consumers don't need to do anything.
|
|
4232
|
-
*
|
|
4233
|
-
* USAGE IN APP (Custom Presets):
|
|
4234
|
-
* ```typescript
|
|
4235
|
-
* // app.config.ts
|
|
4236
|
-
* export const appConfig: ApplicationConfig = {
|
|
4237
|
-
* providers: [
|
|
4238
|
-
* // ... other providers
|
|
4239
|
-
* provideCustomPresets([
|
|
4240
|
-
* {
|
|
4241
|
-
* key: 'THIS_FISCAL_QUARTER',
|
|
4242
|
-
* resolve: (clock, adapter) => {
|
|
4243
|
-
* const now = clock.now();
|
|
4244
|
-
* // ... fiscal logic
|
|
4245
|
-
* return { start, end };
|
|
4246
|
-
* }
|
|
4247
|
-
* }
|
|
4248
|
-
* ])
|
|
4249
|
-
* ]
|
|
4250
|
-
* };
|
|
4251
|
-
* ```
|
|
4252
|
-
*/
|
|
4253
|
-
/**
|
|
4254
|
-
* Initializer function that registers built-in presets
|
|
4255
|
-
*
|
|
4256
|
-
* Runs at application startup (APP_INITIALIZER)
|
|
4257
|
-
*
|
|
4258
|
-
* @param registry - PresetRegistry instance
|
|
4259
|
-
* @returns Initialization function
|
|
4260
|
-
*/
|
|
4261
|
-
function initializeBuiltInPresets(registry) {
|
|
4262
|
-
return () => {
|
|
4263
|
-
// Register all built-in presets
|
|
4264
|
-
BUILT_IN_PRESETS.forEach(preset => {
|
|
4265
|
-
registry.register(preset);
|
|
4266
|
-
});
|
|
4267
|
-
// Log registration for debugging (can be removed in production)
|
|
4268
|
-
if (typeof console !== 'undefined' && console.debug) {
|
|
4269
|
-
console.debug(`[ng-dual-datepicker] Registered ${BUILT_IN_PRESETS.length} built-in presets:`, BUILT_IN_PRESETS.map(p => p.key).join(', '));
|
|
4270
|
-
}
|
|
4271
|
-
};
|
|
4272
|
-
}
|
|
4273
|
-
/**
|
|
4274
|
-
* Provide built-in date range presets
|
|
4275
|
-
*
|
|
4276
|
-
* This provider is automatically included in the library's root providers.
|
|
4277
|
-
* Library consumers don't need to add this manually.
|
|
4278
|
-
*
|
|
4279
|
-
* @returns EnvironmentProviders for built-in presets
|
|
4280
|
-
*
|
|
4281
|
-
* @internal
|
|
4282
|
-
*/
|
|
4283
|
-
function provideBuiltInPresets() {
|
|
4284
|
-
return makeEnvironmentProviders([
|
|
4285
|
-
{
|
|
4286
|
-
provide: APP_INITIALIZER,
|
|
4287
|
-
multi: true,
|
|
4288
|
-
useFactory: initializeBuiltInPresets,
|
|
4289
|
-
deps: [PresetRegistry]
|
|
4290
|
-
}
|
|
4291
|
-
]);
|
|
4292
|
-
}
|
|
4293
|
-
/**
|
|
4294
|
-
* Provide custom date range presets
|
|
4295
|
-
*
|
|
4296
|
-
* Use this to register your own industry-specific presets:
|
|
4297
|
-
* - Fiscal presets
|
|
4298
|
-
* - Hotel/hospitality presets
|
|
4299
|
-
* - Logistics presets
|
|
4300
|
-
* - Custom business logic
|
|
4301
|
-
*
|
|
4302
|
-
* @param presets - Array of custom RangePresetPlugin implementations
|
|
4303
|
-
* @returns EnvironmentProviders for custom presets
|
|
4304
|
-
*
|
|
4305
|
-
* @example
|
|
4306
|
-
* ```typescript
|
|
4307
|
-
* // Fiscal presets
|
|
4308
|
-
* const FISCAL_PRESETS: RangePresetPlugin[] = [
|
|
4309
|
-
* {
|
|
4310
|
-
* key: 'THIS_FISCAL_QUARTER',
|
|
4311
|
-
* resolve: (clock, adapter) => {
|
|
4312
|
-
* const now = clock.now();
|
|
4313
|
-
* const month = adapter.getMonth(now);
|
|
4314
|
-
*
|
|
4315
|
-
* // Fiscal year starts April (month 3)
|
|
4316
|
-
* const fiscalMonth = (month + 9) % 12;
|
|
4317
|
-
* const quarterStart = Math.floor(fiscalMonth / 3) * 3;
|
|
4318
|
-
* const calendarMonth = (quarterStart - 9 + 12) % 12;
|
|
4319
|
-
*
|
|
4320
|
-
* const year = adapter.getYear(now);
|
|
4321
|
-
* const fiscalYear = month < 3 ? year - 1 : year;
|
|
4322
|
-
*
|
|
4323
|
-
* const start = new Date(fiscalYear, calendarMonth, 1);
|
|
4324
|
-
* const end = new Date(fiscalYear, calendarMonth + 3, 0);
|
|
4325
|
-
*
|
|
4326
|
-
* return {
|
|
4327
|
-
* start: adapter.normalize(start),
|
|
4328
|
-
* end: adapter.normalize(end)
|
|
4329
|
-
* };
|
|
4330
|
-
* }
|
|
4331
|
-
* },
|
|
4332
|
-
* {
|
|
4333
|
-
* key: 'FISCAL_YEAR_TO_DATE',
|
|
4334
|
-
* resolve: (clock, adapter) => {
|
|
4335
|
-
* const now = clock.now();
|
|
4336
|
-
* const month = adapter.getMonth(now);
|
|
4337
|
-
* const year = adapter.getYear(now);
|
|
4338
|
-
*
|
|
4339
|
-
* // Fiscal year starts April 1
|
|
4340
|
-
* const fiscalYearStart = month >= 3
|
|
4341
|
-
* ? new Date(year, 3, 1)
|
|
4342
|
-
* : new Date(year - 1, 3, 1);
|
|
4343
|
-
*
|
|
4344
|
-
* return {
|
|
4345
|
-
* start: adapter.normalize(fiscalYearStart),
|
|
4346
|
-
* end: adapter.normalize(now)
|
|
4347
|
-
* };
|
|
4348
|
-
* }
|
|
4349
|
-
* }
|
|
4350
|
-
* ];
|
|
4351
|
-
*
|
|
4352
|
-
* // In app.config.ts
|
|
4353
|
-
* export const appConfig: ApplicationConfig = {
|
|
4354
|
-
* providers: [
|
|
4355
|
-
* provideCustomPresets(FISCAL_PRESETS)
|
|
4356
|
-
* ]
|
|
4357
|
-
* };
|
|
4358
|
-
*
|
|
4359
|
-
* // Use in components
|
|
4360
|
-
* store.applyPreset('THIS_FISCAL_QUARTER');
|
|
4361
|
-
* store.applyPreset('FISCAL_YEAR_TO_DATE');
|
|
4362
|
-
* ```
|
|
4363
|
-
*
|
|
4364
|
-
* @example
|
|
4365
|
-
* ```typescript
|
|
4366
|
-
* // Hotel presets
|
|
4367
|
-
* const HOTEL_PRESETS: RangePresetPlugin[] = [
|
|
4368
|
-
* {
|
|
4369
|
-
* key: 'CHECK_IN_WEEK',
|
|
4370
|
-
* resolve: (clock, adapter) => {
|
|
4371
|
-
* const now = clock.now();
|
|
4372
|
-
* const dayOfWeek = adapter.getDayOfWeek(now);
|
|
4373
|
-
*
|
|
4374
|
-
* // Check-in week: Friday to Friday
|
|
4375
|
-
* const daysToNextFriday = dayOfWeek <= 5
|
|
4376
|
-
* ? 5 - dayOfWeek
|
|
4377
|
-
* : 7 - dayOfWeek + 5;
|
|
4378
|
-
*
|
|
4379
|
-
* const nextFriday = adapter.addDays(now, daysToNextFriday);
|
|
4380
|
-
* const followingFriday = adapter.addDays(nextFriday, 7);
|
|
4381
|
-
*
|
|
4382
|
-
* return { start: nextFriday, end: followingFriday };
|
|
4383
|
-
* }
|
|
4384
|
-
* },
|
|
4385
|
-
* {
|
|
4386
|
-
* key: 'NEXT_30_NIGHTS',
|
|
4387
|
-
* resolve: (clock, adapter) => {
|
|
4388
|
-
* const now = clock.now();
|
|
4389
|
-
* const tomorrow = adapter.addDays(now, 1);
|
|
4390
|
-
* const end = adapter.addDays(tomorrow, 30);
|
|
4391
|
-
* return { start: tomorrow, end };
|
|
4392
|
-
* }
|
|
4393
|
-
* }
|
|
4394
|
-
* ];
|
|
4395
|
-
*
|
|
4396
|
-
* providers: [provideCustomPresets(HOTEL_PRESETS)]
|
|
4397
|
-
* ```
|
|
4398
|
-
*/
|
|
4399
|
-
function provideCustomPresets(presets) {
|
|
4400
|
-
return makeEnvironmentProviders([
|
|
4401
|
-
{
|
|
4402
|
-
provide: APP_INITIALIZER,
|
|
4403
|
-
multi: true,
|
|
4404
|
-
useFactory: (registry) => {
|
|
4405
|
-
return () => {
|
|
4406
|
-
presets.forEach(preset => {
|
|
4407
|
-
registry.register(preset);
|
|
4408
|
-
});
|
|
4409
|
-
if (typeof console !== 'undefined' && console.debug) {
|
|
4410
|
-
console.debug(`[ng-dual-datepicker] Registered ${presets.length} custom presets:`, presets.map(p => p.key).join(', '));
|
|
4411
|
-
}
|
|
4412
|
-
};
|
|
4413
|
-
},
|
|
4414
|
-
deps: [PresetRegistry]
|
|
4415
|
-
}
|
|
4416
|
-
]);
|
|
4417
|
-
}
|
|
4418
|
-
/**
|
|
4419
|
-
* Provide preset package
|
|
4420
|
-
*
|
|
4421
|
-
* Convenience function for external preset packages.
|
|
4422
|
-
*
|
|
4423
|
-
* @param packageName - Name of the preset package (for logging)
|
|
4424
|
-
* @param presets - Array of presets from the package
|
|
4425
|
-
* @returns EnvironmentProviders
|
|
4426
|
-
*
|
|
4427
|
-
* @example
|
|
4428
|
-
* ```typescript
|
|
4429
|
-
* // @acme/fiscal-presets package
|
|
4430
|
-
* export function provideFiscalPresets(): EnvironmentProviders {
|
|
4431
|
-
* return providePresetPackage('@acme/fiscal-presets', FISCAL_PRESETS);
|
|
4432
|
-
* }
|
|
4118
|
+
* Public API Surface of @oneluiz/dual-datepicker
|
|
4433
4119
|
*
|
|
4434
|
-
*
|
|
4435
|
-
*
|
|
4120
|
+
* v4.0.0: Formalized Public API
|
|
4121
|
+
* - Only stable, documented APIs are exported
|
|
4122
|
+
* - Internal implementation details removed
|
|
4123
|
+
* - Backward compatibility maintained where possible
|
|
4436
4124
|
*
|
|
4437
|
-
*
|
|
4438
|
-
*
|
|
4125
|
+
* @public
|
|
4126
|
+
* @packageDocumentation
|
|
4127
|
+
* @module @oneluiz/dual-datepicker
|
|
4439
4128
|
*/
|
|
4440
|
-
|
|
4441
|
-
|
|
4442
|
-
|
|
4443
|
-
provide: APP_INITIALIZER,
|
|
4444
|
-
multi: true,
|
|
4445
|
-
useFactory: (registry) => {
|
|
4446
|
-
return () => {
|
|
4447
|
-
presets.forEach(preset => {
|
|
4448
|
-
registry.register(preset);
|
|
4449
|
-
});
|
|
4450
|
-
if (typeof console !== 'undefined' && console.debug) {
|
|
4451
|
-
console.debug(`[${packageName}] Registered ${presets.length} presets:`, presets.map(p => p.key).join(', '));
|
|
4452
|
-
}
|
|
4453
|
-
};
|
|
4454
|
-
},
|
|
4455
|
-
deps: [PresetRegistry]
|
|
4456
|
-
}
|
|
4457
|
-
]);
|
|
4458
|
-
}
|
|
4459
|
-
|
|
4129
|
+
// ============================================================================
|
|
4130
|
+
// UI COMPONENT
|
|
4131
|
+
// ============================================================================
|
|
4460
4132
|
/**
|
|
4461
|
-
*
|
|
4133
|
+
* DualDatepickerComponent - Main Angular component
|
|
4462
4134
|
*
|
|
4463
|
-
*
|
|
4464
|
-
*
|
|
4465
|
-
* - PresetRegistry for plugin management
|
|
4466
|
-
* - Built-in presets as plugins
|
|
4467
|
-
* - Provider functions for custom presets
|
|
4135
|
+
* Standalone component for dual-calendar date range selection.
|
|
4136
|
+
* Supports reactive forms, signals, themes, and extensive customization.
|
|
4468
4137
|
*
|
|
4469
|
-
*
|
|
4470
|
-
*
|
|
4471
|
-
* - CalendarGridCache with LRU memoization
|
|
4472
|
-
* - Separates grid structure from decorations for performance
|
|
4473
|
-
*
|
|
4474
|
-
* v3.8.0: Range Highlight Cache
|
|
4475
|
-
* - RangeHighlighter for decoration logic (pure computation)
|
|
4476
|
-
* - RangeHighlighterCache with LRU (48 grids)
|
|
4477
|
-
* - Eliminates redundant decoration recomputations
|
|
4478
|
-
*
|
|
4479
|
-
* Import from here for clean barrel exports
|
|
4480
|
-
*/
|
|
4481
|
-
|
|
4482
|
-
/**
|
|
4483
|
-
* Public API Surface of @oneluiz/dual-datepicker
|
|
4138
|
+
* @public
|
|
4139
|
+
* @since v1.0.0
|
|
4484
4140
|
*/
|
|
4485
4141
|
|
|
4486
4142
|
/**
|
|
4487
4143
|
* Generated bundle index. Do not edit.
|
|
4488
4144
|
*/
|
|
4489
4145
|
|
|
4490
|
-
export { BUILT_IN_PRESETS,
|
|
4146
|
+
export { BUILT_IN_PRESETS, DATE_ADAPTER, DualDateRangeStore, DualDatepickerComponent, LAST_30_DAYS_PRESET, LAST_7_DAYS_PRESET, LAST_MONTH_PRESET, LAST_WEEK_PRESET, LAST_YEAR_PRESET, NativeDateAdapter, PresetEngine, PresetRegistry, THIS_MONTH_PRESET, THIS_WEEK_PRESET, THIS_YEAR_PRESET, TODAY_PRESET, YESTERDAY_PRESET, applyBounds, isDateDisabled, provideCustomPresets, providePresetPackage, validateRangeOrder };
|
|
4491
4147
|
//# sourceMappingURL=oneluiz-dual-datepicker.mjs.map
|