@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.
Files changed (65) hide show
  1. package/README.md +26 -2
  2. package/fesm2022/oneluiz-dual-datepicker.mjs +696 -1040
  3. package/fesm2022/oneluiz-dual-datepicker.mjs.map +1 -1
  4. package/package.json +16 -8
  5. package/core/built-in-presets.d.ts +0 -98
  6. package/core/calendar-grid/cache.config.d.ts +0 -34
  7. package/core/calendar-grid/calendar-grid.cache.d.ts +0 -39
  8. package/core/calendar-grid/calendar-grid.factory.d.ts +0 -26
  9. package/core/calendar-grid/calendar-grid.types.d.ts +0 -57
  10. package/core/calendar-grid/index.d.ts +0 -56
  11. package/core/calendar-grid/range-highlighter.cache.d.ts +0 -106
  12. package/core/calendar-grid/range-highlighter.d.ts +0 -85
  13. package/core/calendar-grid/range-highlighter.types.d.ts +0 -182
  14. package/core/calendar-grid/virtual-weeks.logic.d.ts +0 -116
  15. package/core/calendar-grid/virtual-weeks.types.d.ts +0 -71
  16. package/core/date-adapter.d.ts +0 -298
  17. package/core/date-clock.d.ts +0 -82
  18. package/core/dual-date-range.store.d.ts +0 -113
  19. package/core/index.d.ts +0 -33
  20. package/core/native-date-adapter.d.ts +0 -152
  21. package/core/preset-providers.d.ts +0 -176
  22. package/core/preset-registry.d.ts +0 -181
  23. package/core/preset.engine.d.ts +0 -124
  24. package/core/range-preset.plugin.d.ts +0 -188
  25. package/core/range.validator.d.ts +0 -37
  26. package/core/system-clock.d.ts +0 -13
  27. package/date-adapter.d.ts +0 -116
  28. package/dual-datepicker.component.d.ts +0 -243
  29. package/esm2022/core/built-in-presets.mjs +0 -289
  30. package/esm2022/core/calendar-grid/cache.config.mjs +0 -35
  31. package/esm2022/core/calendar-grid/calendar-grid.cache.mjs +0 -98
  32. package/esm2022/core/calendar-grid/calendar-grid.factory.mjs +0 -97
  33. package/esm2022/core/calendar-grid/calendar-grid.types.mjs +0 -8
  34. package/esm2022/core/calendar-grid/index.mjs +0 -57
  35. package/esm2022/core/calendar-grid/range-highlighter.cache.mjs +0 -200
  36. package/esm2022/core/calendar-grid/range-highlighter.mjs +0 -185
  37. package/esm2022/core/calendar-grid/range-highlighter.types.mjs +0 -11
  38. package/esm2022/core/calendar-grid/virtual-weeks.logic.mjs +0 -149
  39. package/esm2022/core/calendar-grid/virtual-weeks.types.mjs +0 -11
  40. package/esm2022/core/date-adapter.mjs +0 -77
  41. package/esm2022/core/date-clock.mjs +0 -65
  42. package/esm2022/core/dual-date-range.store.mjs +0 -329
  43. package/esm2022/core/index.mjs +0 -37
  44. package/esm2022/core/native-date-adapter.mjs +0 -286
  45. package/esm2022/core/preset-providers.mjs +0 -243
  46. package/esm2022/core/preset-registry.mjs +0 -277
  47. package/esm2022/core/preset.engine.mjs +0 -179
  48. package/esm2022/core/range-preset.plugin.mjs +0 -70
  49. package/esm2022/core/range.validator.mjs +0 -105
  50. package/esm2022/core/system-clock.mjs +0 -34
  51. package/esm2022/date-adapter.mjs +0 -12
  52. package/esm2022/dual-datepicker.component.mjs +0 -1312
  53. package/esm2022/native-date-adapter.mjs +0 -117
  54. package/esm2022/oneluiz-dual-datepicker.mjs +0 -5
  55. package/esm2022/preset-utils.mjs +0 -276
  56. package/esm2022/public-api.mjs +0 -13
  57. package/index.d.ts +0 -5
  58. package/native-date-adapter.d.ts +0 -26
  59. package/preset-utils.d.ts +0 -91
  60. package/public-api.d.ts +0 -9
  61. package/src/themes/bootstrap.scss +0 -202
  62. package/src/themes/bulma.scss +0 -209
  63. package/src/themes/custom.scss +0 -236
  64. package/src/themes/foundation.scss +0 -201
  65. 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, APP_INITIALIZER, forwardRef, HostListener, Output, Input, Component, makeEnvironmentProviders } from '@angular/core';
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: "18.2.14", ngImport: i0, type: SystemClock, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
324
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SystemClock, providedIn: 'root' });
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: "18.2.14", ngImport: i0, type: SystemClock, decorators: [{
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: "18.2.14", ngImport: i0, type: NativeDateAdapter, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
684
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: NativeDateAdapter, providedIn: 'root' });
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: "18.2.14", ngImport: i0, type: NativeDateAdapter, decorators: [{
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: "18.2.14", ngImport: i0, type: PresetRegistry, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1028
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: PresetRegistry, providedIn: 'root' });
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: "18.2.14", ngImport: i0, type: PresetRegistry, decorators: [{
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: "18.2.14", ngImport: i0, type: PresetEngine, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1177
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: PresetEngine, providedIn: 'root' });
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: "18.2.14", ngImport: i0, type: PresetEngine, decorators: [{
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(this.getNextMonth(new Date()));
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.getDay(date);
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: "18.2.14", ngImport: i0, type: DualDateRangeStore, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1521
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: DualDateRangeStore, providedIn: 'root' });
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: "18.2.14", ngImport: i0, type: DualDateRangeStore, decorators: [{
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
- * Calendar Grid Types
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
- * Defines the structure for memoized calendar month grids.
1823
- * Separates base grid structure (cacheable) from decorations (dynamic).
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
- * Calendar Grid Factory
1730
+ * Initializer function that registers built-in presets
1828
1731
  *
1829
- * Generates calendar month grids using DateAdapter for deterministic,
1830
- * timezone-safe date operations.
1732
+ * Runs at application startup (APP_INITIALIZER)
1831
1733
  *
1832
- * Grid structure:
1833
- * - Always 6 weeks x 7 days (42 cells) for layout stability
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
- class CalendarGridFactory {
1838
- adapter;
1839
- constructor(adapter) {
1840
- this.adapter = adapter;
1841
- }
1842
- /**
1843
- * Create a calendar grid for a given month
1844
- *
1845
- * @param monthDate - Any date within the target month (will be normalized to start of month)
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
- return offset;
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
- * Cache Configuration
1750
+ * Provide built-in date range presets
1922
1751
  *
1923
- * Centralized cache limits for memory safety.
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
- * @module core/calendar-grid/cache.config
1926
- * @version 3.9.2
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
- * Maximum cache entries before FIFO eviction
1770
+ * Provide custom date range presets
1930
1771
  *
1931
- * Applied to both:
1932
- * - CalendarGridCache: Raw month grids
1933
- * - RangeHighlighterCache: Decorated grids
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
- * Memory footprint:
1936
- * - CalendarGrid (raw): ~10KB each
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
- * Coverage:
1941
- * - 48 months = 4 years of navigation
1942
- * - Sufficient for long-running sessions
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
- * Critical for:
1945
- * - ERP systems (invoice date selection)
1946
- * - BI dashboards (date range filters)
1947
- * - Hotel reservation systems (availability calendars)
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
- * @constant
1951
- * @since v3.9.2
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
- * LRU cache for calendar month grids to avoid recomputing the same month grid
1959
- * multiple times when only decorations (selected dates, hover, etc.) change.
1799
+ * const start = new Date(fiscalYear, calendarMonth, 1);
1800
+ * const end = new Date(fiscalYear, calendarMonth + 3, 0);
1960
1801
  *
1961
- * Strategy:
1962
- * - Key: year-month-weekStart-locale
1963
- * - LRU eviction (least recently used) when limit is reached
1964
- * - Default limit: 48 months (covers 2 years of navigation)
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
- * Performance impact:
1967
- * - Eliminates ~90% of grid recalculations in typical usage
1968
- * - Memory footprint: ~10KB per cached month (negligible)
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
- * Memory safety (v3.9.2):
1971
- * - MAX_CACHE_ENTRIES prevents unbounded growth in long-running sessions
1972
- * - Critical for: ERP, BI dashboards, hotel reservation systems
1973
- * - FIFO eviction when limit exceeded
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
- class CalendarGridCache {
1976
- factory;
1977
- cache = new Map();
1978
- maxSize = MAX_CACHE_ENTRIES;
1979
- constructor(factory) {
1980
- this.factory = factory;
1981
- }
1982
- /**
1983
- * Get or create a calendar grid
1984
- *
1985
- * @param monthDate - Any date within the target month
1986
- * @param weekStart - First day of week (0 = Sunday, 1 = Monday, etc.)
1987
- * @param locale - Locale identifier (optional)
1988
- * @returns CalendarGrid - cached or newly created
1989
- */
1990
- get(monthDate, weekStart = 0, locale) {
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
- return grid;
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
- * Range Highlighter Types
1895
+ * Provide preset package
2051
1896
  *
2052
- * Type definitions for calendar cell decorations with range highlighting.
2053
- * Used by RangeHighlighter to cache decorated grids and avoid recomputations.
1897
+ * Convenience function for external preset packages.
2054
1898
  *
2055
- * @module core/calendar-grid/range-highlighter.types
2056
- * @version 3.8.0
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
- * Range Highlighter Service
1937
+ * PUBLIC API for @oneluiz/dual-datepicker
2061
1938
  *
2062
- * Decorates calendar grids with range highlights, hover previews, and disabled states.
2063
- * Pure computation layer - no caching (see RangeHighlighterCache for memoization).
1939
+ * This file defines the stable public API contract.
1940
+ * Exports from this barrel are guaranteed to follow semantic versioning.
2064
1941
  *
2065
- * @module core/calendar-grid/range-highlighter
2066
- * @version 3.8.0
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
- * Range Highlighter
1951
+ * DualDateRangeStore - Signal-based headless date range store
2070
1952
  *
2071
- * Applies decorations to calendar grids:
2072
- * - isSelectedStart, isSelectedEnd (range boundaries)
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
- * Design:
2078
- * - Pure functions (same input = same output)
2079
- * - Uses DateAdapter for all date operations (SSR-safe)
2080
- * - No side effects, no state
2081
- * - Fast (~1ms for 42 cells on mobile)
1956
+ * @public
1957
+ * @since v3.5.0
1958
+ */
1959
+
1960
+ /**
1961
+ * Calendar Grid Factory
2082
1962
  *
2083
- * Usage:
2084
- * ```typescript
2085
- * const grid = calendarGridCache.get(monthDate, 0);
2086
- * const decorated = rangeHighlighter.decorate(grid, {
2087
- * start: startDate,
2088
- * end: endDate,
2089
- * hoverDate: '2026-01-20',
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 RangeHighlighter {
1971
+ class CalendarGridFactory {
2095
1972
  adapter;
2096
1973
  constructor(adapter) {
2097
1974
  this.adapter = adapter;
2098
1975
  }
2099
1976
  /**
2100
- * Decorate calendar grid with range highlights
1977
+ * Create a calendar grid for a given month
2101
1978
  *
2102
- * @param grid Base calendar grid (from CalendarGridCache)
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: "18.2.14", ngImport: i0, type: RangeHighlighter, deps: [{ token: DATE_ADAPTER }], target: i0.ɵɵFactoryTarget.Injectable });
2231
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: RangeHighlighter, providedIn: 'root' });
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: "18.2.14", ngImport: i0, type: RangeHighlighter, decorators: [{
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: "18.2.14", ngImport: i0, type: RangeHighlighterCache, deps: [{ token: RangeHighlighter }, { token: DATE_ADAPTER }], target: i0.ɵɵFactoryTarget.Injectable });
2426
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: RangeHighlighterCache, providedIn: 'root' });
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: "18.2.14", ngImport: i0, type: RangeHighlighterCache, decorators: [{
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
- * Calendar Grid Module
2710
+ * Fixed Clock for Deterministic Testing
2597
2711
  *
2598
- * Performance-optimized calendar month grid generation with memoization.
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
- * constructor(
2622
- * private gridCache: CalendarGridCache,
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.parse(value);
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.parse(value);
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$1);
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.today());
2726
- previousMonth = signal(this.dateAdapter.today());
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.format(this.dateAdapter.today(), 'yyyy-MM-dd');
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.format(this.dateAdapter.today(), 'yyyy-MM-dd');
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.format(this.dateAdapter.today(), 'yyyy-MM-dd');
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.parse(dateStr);
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.parse(focused.date);
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.parse(focused.date);
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.parse(focused.date);
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.createDate(currentYear + direction, currentMonth, currentDay);
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.createDate(currentYear + direction, currentMonth, 1));
3106
- this.previousMonth.set(this.dateAdapter.createDate(currentYear + direction, currentMonth - 1, 1));
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.parse(this.startDate);
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.parse(this.endDate);
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.parse(this.startDate);
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.parse(this.endDate);
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.parse(dateStr);
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.createDate(year, month - 1, 1);
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.parse(dayObj.date);
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.createDate(year, month + direction, 1);
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.createDate(newYear, newMonth - 1, 1);
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.parse(range.start);
3593
- const endDate = this.dateAdapter.parse(range.end);
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: "18.2.14", ngImport: i0, type: DualDatepickerComponent, deps: [{ token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Component });
3825
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", 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: [
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$1,
3833
- useClass: NativeDateAdapter$1
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: "18.2.14", ngImport: i0, type: DualDatepickerComponent, decorators: [{
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$1,
3858
- useClass: NativeDateAdapter$1
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
- * Utility functions for creating common date range presets
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
- * // In app
4435
- * import { provideFiscalPresets } from '@acme/fiscal-presets';
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
- * providers: [provideFiscalPresets()]
4438
- * ```
4125
+ * @public
4126
+ * @packageDocumentation
4127
+ * @module @oneluiz/dual-datepicker
4439
4128
  */
4440
- function providePresetPackage(packageName, presets) {
4441
- return makeEnvironmentProviders([
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
- * Core headless date range logic
4133
+ * DualDatepickerComponent - Main Angular component
4462
4134
  *
4463
- * v3.6.0: Plugin Architecture
4464
- * - RangePresetPlugin interface for extensible presets
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
- * v3.7.0: Calendar Grid Cache
4470
- * - CalendarGridFactory for deterministic grid generation
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, CalendarGridCache, CalendarGridFactory, CommonPresets, DATE_ADAPTER$1 as DATE_ADAPTER, DATE_CLOCK, DateAdapter, DualDateRangeStore, DualDatepickerComponent, LAST_14_DAYS_PRESET, LAST_30_DAYS_PRESET, LAST_60_DAYS_PRESET, LAST_7_DAYS_PRESET, LAST_90_DAYS_PRESET, LAST_MONTH_PRESET, LAST_QUARTER_PRESET, LAST_WEEK_PRESET, LAST_YEAR_PRESET, MAX_CACHE_ENTRIES, MONTH_TO_DATE_PRESET, NativeDateAdapter$1 as NativeDateAdapter, PresetEngine, PresetRegistry, QUARTER_TO_DATE_PRESET, RangeHighlighter, RangeHighlighterCache, SystemClock, THIS_MONTH_PRESET, THIS_QUARTER_PRESET, THIS_WEEK_PRESET, THIS_YEAR_PRESET, TODAY_PRESET, YEAR_TO_DATE_PRESET, YESTERDAY_PRESET, applyBounds, clampWeekStart, createPreset, formatISODate, getLastMonth, getLastNDays, getLastNMonths, getLastNYears, getLastQuarter, getLastWeek, getLastYear, getMonthToDate, getQuarterToDate, getThisMonth, getThisQuarter, getThisWeek, getThisYear, getToday, getVirtualWeekWindow, getVisibleWeeks, getYearToDate, getYesterday, isDateDisabled, isRangePresetPlugin, isVirtualWeeksEnabled, navigateWeekWindow, parseISODate, presetEngine, provideBuiltInPresets, provideCustomPresets, providePresetPackage, validateDateBounds, validateRangeBounds, validateRangeOrder };
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