ngx-datex 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +320 -0
- package/fesm2022/ngx-datex.mjs +3562 -0
- package/package.json +81 -0
- package/types/ngx-datex.d.ts +2161 -0
|
@@ -0,0 +1,3562 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { Injectable, inject, PLATFORM_ID, ViewContainerRef, input, computed, output, signal, afterNextRender, forwardRef, ViewChild, ChangeDetectionStrategy, Component } from '@angular/core';
|
|
3
|
+
import { isPlatformBrowser, NgClass } from '@angular/common';
|
|
4
|
+
import { NG_VALUE_ACCESSOR } from '@angular/forms';
|
|
5
|
+
import { MatFormFieldModule } from '@angular/material/form-field';
|
|
6
|
+
import * as i2 from '@angular/material/input';
|
|
7
|
+
import { MatInputModule } from '@angular/material/input';
|
|
8
|
+
import * as i1 from '@angular/material/icon';
|
|
9
|
+
import { MatIconModule } from '@angular/material/icon';
|
|
10
|
+
import { MatButtonModule } from '@angular/material/button';
|
|
11
|
+
import * as i3 from '@angular/material/checkbox';
|
|
12
|
+
import { MatCheckboxModule } from '@angular/material/checkbox';
|
|
13
|
+
import { addDay, addMonth, sameDay, sameYear, format, parse, isBefore } from '@formkit/tempo';
|
|
14
|
+
import { Overlay, OverlayConfig } from '@angular/cdk/overlay';
|
|
15
|
+
import { TemplatePortal } from '@angular/cdk/portal';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @fileoverview Date utility functions for NgxDatex component.
|
|
19
|
+
* Provides date manipulation, formatting, and validation utilities.
|
|
20
|
+
* Built on top of @formkit/tempo for reliable date operations.
|
|
21
|
+
*/
|
|
22
|
+
/**
|
|
23
|
+
* Adds the specified number of days to a date.
|
|
24
|
+
*
|
|
25
|
+
* @param date - The base date
|
|
26
|
+
* @param days - Number of days to add (can be negative)
|
|
27
|
+
* @returns New date with days added
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```typescript
|
|
31
|
+
* const tomorrow = addDays(new Date(), 1);
|
|
32
|
+
* const lastWeek = addDays(new Date(), -7);
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
function addDays(date, days) {
|
|
36
|
+
return addDay(date, days);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Adds the specified number of months to a date.
|
|
40
|
+
*
|
|
41
|
+
* @param date - The base date
|
|
42
|
+
* @param months - Number of months to add (can be negative)
|
|
43
|
+
* @returns New date with months added
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* ```typescript
|
|
47
|
+
* const nextMonth = addMonths(new Date(), 1);
|
|
48
|
+
* const lastYear = addMonths(new Date(), -12);
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
51
|
+
function addMonths(date, months) {
|
|
52
|
+
return addMonth(date, months);
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Returns a new date set to the start of the day (00:00:00.000).
|
|
56
|
+
*
|
|
57
|
+
* @param date - The input date
|
|
58
|
+
* @returns New date at start of day
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* ```typescript
|
|
62
|
+
* const today = startOfDay(new Date()); // Today at 00:00:00
|
|
63
|
+
* ```
|
|
64
|
+
*/
|
|
65
|
+
function startOfDay(date) {
|
|
66
|
+
const result = new Date(date);
|
|
67
|
+
result.setHours(0, 0, 0, 0);
|
|
68
|
+
return result;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Returns a new date set to the end of the day (23:59:59.999).
|
|
72
|
+
*
|
|
73
|
+
* @param date - The input date
|
|
74
|
+
* @returns New date at end of day
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* ```typescript
|
|
78
|
+
* const endToday = endOfDay(new Date()); // Today at 23:59:59.999
|
|
79
|
+
* ```
|
|
80
|
+
*/
|
|
81
|
+
function endOfDay(date) {
|
|
82
|
+
const result = new Date(date);
|
|
83
|
+
result.setHours(23, 59, 59, 999);
|
|
84
|
+
return result;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Returns a new date set to the first day of the month.
|
|
88
|
+
*
|
|
89
|
+
* @param date - The input date
|
|
90
|
+
* @returns New date at start of month
|
|
91
|
+
*
|
|
92
|
+
* @example
|
|
93
|
+
* ```typescript
|
|
94
|
+
* const monthStart = startOfMonth(new Date()); // 1st day of current month
|
|
95
|
+
* ```
|
|
96
|
+
*/
|
|
97
|
+
function startOfMonth(date) {
|
|
98
|
+
return new Date(date.getFullYear(), date.getMonth(), 1);
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Returns a new date set to the start of the week.
|
|
102
|
+
*
|
|
103
|
+
* @param date - The input date
|
|
104
|
+
* @param firstDay - First day of week (0 = Sunday, 1 = Monday, etc.)
|
|
105
|
+
* @returns New date at start of week
|
|
106
|
+
*
|
|
107
|
+
* @example
|
|
108
|
+
* ```typescript
|
|
109
|
+
* const mondayStart = startOfWeek(new Date(), 1); // Start of week (Monday)
|
|
110
|
+
* const sundayStart = startOfWeek(new Date(), 0); // Start of week (Sunday)
|
|
111
|
+
* ```
|
|
112
|
+
*/
|
|
113
|
+
function startOfWeek(date, firstDay = 1) {
|
|
114
|
+
const day = date.getDay();
|
|
115
|
+
const daysBack = (day - firstDay + 7) % 7;
|
|
116
|
+
const result = new Date(date);
|
|
117
|
+
result.setDate(date.getDate() - daysBack);
|
|
118
|
+
return result;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Checks if two dates represent the same day.
|
|
122
|
+
*
|
|
123
|
+
* @param date1 - First date to compare
|
|
124
|
+
* @param date2 - Second date to compare
|
|
125
|
+
* @returns True if dates are on the same day
|
|
126
|
+
*
|
|
127
|
+
* @example
|
|
128
|
+
* ```typescript
|
|
129
|
+
* const today = new Date();
|
|
130
|
+
* const todayEvening = new Date();
|
|
131
|
+
* todayEvening.setHours(23, 59, 59);
|
|
132
|
+
*
|
|
133
|
+
* isSameDay(today, todayEvening); // true
|
|
134
|
+
* ```
|
|
135
|
+
*/
|
|
136
|
+
function isSameDay(date1, date2) {
|
|
137
|
+
return sameDay(date1, date2);
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Checks if two dates are in the same month and year.
|
|
141
|
+
*
|
|
142
|
+
* @param date1 - First date to compare
|
|
143
|
+
* @param date2 - Second date to compare
|
|
144
|
+
* @returns True if dates are in the same month
|
|
145
|
+
*
|
|
146
|
+
* @example
|
|
147
|
+
* ```typescript
|
|
148
|
+
* const jan1 = new Date(2024, 0, 1);
|
|
149
|
+
* const jan31 = new Date(2024, 0, 31);
|
|
150
|
+
*
|
|
151
|
+
* isSameMonth(jan1, jan31); // true
|
|
152
|
+
* ```
|
|
153
|
+
*/
|
|
154
|
+
function isSameMonth(date1, date2) {
|
|
155
|
+
return date1.getFullYear() === date2.getFullYear() && date1.getMonth() === date2.getMonth();
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Checks if two dates are in the same year.
|
|
159
|
+
*
|
|
160
|
+
* @param date1 - First date to compare
|
|
161
|
+
* @param date2 - Second date to compare
|
|
162
|
+
* @returns True if dates are in the same year
|
|
163
|
+
*
|
|
164
|
+
* @example
|
|
165
|
+
* ```typescript
|
|
166
|
+
* const jan2024 = new Date(2024, 0, 1);
|
|
167
|
+
* const dec2024 = new Date(2024, 11, 31);
|
|
168
|
+
*
|
|
169
|
+
* isSameYear(jan2024, dec2024); // true
|
|
170
|
+
* ```
|
|
171
|
+
*/
|
|
172
|
+
function isSameYear(date1, date2) {
|
|
173
|
+
return sameYear(date1, date2);
|
|
174
|
+
}
|
|
175
|
+
function formatDateValue(date, formatStr) {
|
|
176
|
+
try {
|
|
177
|
+
return format(date, formatStr);
|
|
178
|
+
}
|
|
179
|
+
catch {
|
|
180
|
+
const year = date.getFullYear();
|
|
181
|
+
const month = String(date.getMonth() + 1).padStart(2, '0');
|
|
182
|
+
const day = String(date.getDate()).padStart(2, '0');
|
|
183
|
+
const hours24 = date.getHours();
|
|
184
|
+
const hours12 = hours24 === 0 ? 12 : hours24 > 12 ? hours24 - 12 : hours24;
|
|
185
|
+
const minutes = String(date.getMinutes()).padStart(2, '0');
|
|
186
|
+
const seconds = String(date.getSeconds()).padStart(2, '0');
|
|
187
|
+
const ampm = hours24 >= 12 ? 'PM' : 'AM';
|
|
188
|
+
return formatStr
|
|
189
|
+
.replace(/YYYY/g, String(year))
|
|
190
|
+
.replace(/MM/g, month)
|
|
191
|
+
.replace(/DD/g, day)
|
|
192
|
+
.replace(/HH/g, String(hours24).padStart(2, '0'))
|
|
193
|
+
.replace(/hh/g, String(hours12).padStart(2, '0'))
|
|
194
|
+
.replace(/mm/g, minutes)
|
|
195
|
+
.replace(/ss/g, seconds)
|
|
196
|
+
.replace(/A/g, ampm);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
function parseDateValue(dateStr, formatStr) {
|
|
200
|
+
try {
|
|
201
|
+
const parsed = parse(dateStr, formatStr);
|
|
202
|
+
if (isValidDate(parsed)) {
|
|
203
|
+
return parsed;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
catch {
|
|
207
|
+
// Fallback to manual parsing
|
|
208
|
+
}
|
|
209
|
+
if (formatStr === 'YYYY-MM-DD') {
|
|
210
|
+
const parts = dateStr.split('-');
|
|
211
|
+
if (parts.length === 3) {
|
|
212
|
+
const year = parseInt(parts[0], 10);
|
|
213
|
+
const month = parseInt(parts[1], 10) - 1;
|
|
214
|
+
const day = parseInt(parts[2], 10);
|
|
215
|
+
return new Date(year, month, day);
|
|
216
|
+
}
|
|
217
|
+
return new Date(dateStr);
|
|
218
|
+
}
|
|
219
|
+
if (formatStr === 'DD/MM/YYYY' || formatStr.startsWith('DD/MM/YYYY')) {
|
|
220
|
+
const parts = dateStr.split(' ');
|
|
221
|
+
const datePart = parts[0];
|
|
222
|
+
const timePart = parts[1];
|
|
223
|
+
const dateComponents = datePart.split('/');
|
|
224
|
+
if (dateComponents.length === 3) {
|
|
225
|
+
const day = parseInt(dateComponents[0], 10);
|
|
226
|
+
const month = parseInt(dateComponents[1], 10) - 1;
|
|
227
|
+
const year = parseInt(dateComponents[2], 10);
|
|
228
|
+
const date = new Date(year, month, day);
|
|
229
|
+
if (timePart) {
|
|
230
|
+
const timeComponents = timePart.split(':');
|
|
231
|
+
if (timeComponents.length >= 2) {
|
|
232
|
+
let hours = parseInt(timeComponents[0], 10);
|
|
233
|
+
const minutes = parseInt(timeComponents[1], 10);
|
|
234
|
+
if (timePart.includes('PM') && hours !== 12) {
|
|
235
|
+
hours += 12;
|
|
236
|
+
}
|
|
237
|
+
else if (timePart.includes('AM') && hours === 12) {
|
|
238
|
+
hours = 0;
|
|
239
|
+
}
|
|
240
|
+
date.setHours(hours, minutes, 0, 0);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
return date;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
return new Date(dateStr);
|
|
247
|
+
}
|
|
248
|
+
function isSameDate(date1, date2, unit = 'day') {
|
|
249
|
+
if (!date1 || !date2)
|
|
250
|
+
return false;
|
|
251
|
+
switch (unit) {
|
|
252
|
+
case 'day':
|
|
253
|
+
return isSameDay(date1, date2);
|
|
254
|
+
case 'month':
|
|
255
|
+
return isSameMonth(date1, date2);
|
|
256
|
+
case 'year':
|
|
257
|
+
return isSameYear(date1, date2);
|
|
258
|
+
default:
|
|
259
|
+
return false;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
function isValidDate(date) {
|
|
263
|
+
return date instanceof Date && !isNaN(date.getTime());
|
|
264
|
+
}
|
|
265
|
+
function isBeforeDate(date1, date2) {
|
|
266
|
+
return isBefore(date1, date2);
|
|
267
|
+
}
|
|
268
|
+
function getStartOfMonth(date) {
|
|
269
|
+
return startOfMonth(date);
|
|
270
|
+
}
|
|
271
|
+
function getStartOfWeek(date, firstDay = 1) {
|
|
272
|
+
return startOfWeek(date, firstDay);
|
|
273
|
+
}
|
|
274
|
+
function isMobileDevice() {
|
|
275
|
+
return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* @fileoverview Default configurations for NgxDatex component.
|
|
280
|
+
*
|
|
281
|
+
* This file contains predefined locales, themes, and date ranges
|
|
282
|
+
* that can be used out-of-the-box or as starting points for customization.
|
|
283
|
+
*/
|
|
284
|
+
/**
|
|
285
|
+
* Default Spanish locale configuration.
|
|
286
|
+
*
|
|
287
|
+
* Provides Spanish language support with DD/MM/YYYY format,
|
|
288
|
+
* Spanish day/month names, and Monday as the first day of the week.
|
|
289
|
+
*
|
|
290
|
+
* @example
|
|
291
|
+
* ```typescript
|
|
292
|
+
* <ngx-datex [locale]="SPANISH_LOCALE"></ngx-datex>
|
|
293
|
+
* ```
|
|
294
|
+
*/
|
|
295
|
+
const SPANISH_LOCALE = {
|
|
296
|
+
direction: 'ltr',
|
|
297
|
+
format: 'DD/MM/YYYY',
|
|
298
|
+
separator: ' - ',
|
|
299
|
+
applyLabel: 'Aplicar',
|
|
300
|
+
cancelLabel: 'Cancelar',
|
|
301
|
+
weekLabel: 'S',
|
|
302
|
+
customRangeLabel: 'Personalizado',
|
|
303
|
+
daysOfWeek: ['Lu', 'Ma', 'Mi', 'Ju', 'Vi', 'Sa', 'Do'],
|
|
304
|
+
monthNames: [
|
|
305
|
+
'Enero',
|
|
306
|
+
'Febrero',
|
|
307
|
+
'Marzo',
|
|
308
|
+
'Abril',
|
|
309
|
+
'Mayo',
|
|
310
|
+
'Junio',
|
|
311
|
+
'Julio',
|
|
312
|
+
'Agosto',
|
|
313
|
+
'Septiembre',
|
|
314
|
+
'Octubre',
|
|
315
|
+
'Noviembre',
|
|
316
|
+
'Diciembre',
|
|
317
|
+
],
|
|
318
|
+
firstDay: 1, // Lunes como primer día
|
|
319
|
+
};
|
|
320
|
+
/**
|
|
321
|
+
* Default Material Design light theme.
|
|
322
|
+
*
|
|
323
|
+
* A clean, modern theme following Material Design principles
|
|
324
|
+
* with blue primary colors and proper contrast ratios.
|
|
325
|
+
*
|
|
326
|
+
* @example
|
|
327
|
+
* ```typescript
|
|
328
|
+
* <ngx-datex [theme]="MATERIAL_LIGHT_THEME"></ngx-datex>
|
|
329
|
+
* ```
|
|
330
|
+
*/
|
|
331
|
+
const MATERIAL_LIGHT_THEME = {
|
|
332
|
+
name: 'material-light',
|
|
333
|
+
colors: {
|
|
334
|
+
primary: '#1976d2',
|
|
335
|
+
secondary: '#424242',
|
|
336
|
+
accent: '#82b1ff',
|
|
337
|
+
background: '#fafafa',
|
|
338
|
+
surface: '#ffffff',
|
|
339
|
+
text: '#212121',
|
|
340
|
+
textSecondary: '#757575',
|
|
341
|
+
border: '#e0e0e0',
|
|
342
|
+
hover: '#f5f5f5',
|
|
343
|
+
selected: '#1976d2',
|
|
344
|
+
selectedText: '#ffffff',
|
|
345
|
+
disabled: '#bdbdbd',
|
|
346
|
+
error: '#f44336',
|
|
347
|
+
success: '#4caf50',
|
|
348
|
+
warning: '#ff9800',
|
|
349
|
+
},
|
|
350
|
+
typography: {
|
|
351
|
+
fontFamily: 'Roboto, sans-serif',
|
|
352
|
+
fontSize: '14px',
|
|
353
|
+
fontWeight: '400',
|
|
354
|
+
lineHeight: '1.5',
|
|
355
|
+
},
|
|
356
|
+
spacing: {
|
|
357
|
+
xs: '4px',
|
|
358
|
+
sm: '8px',
|
|
359
|
+
md: '16px',
|
|
360
|
+
lg: '24px',
|
|
361
|
+
xl: '32px',
|
|
362
|
+
},
|
|
363
|
+
borderRadius: {
|
|
364
|
+
sm: '4px',
|
|
365
|
+
md: '8px',
|
|
366
|
+
lg: '12px',
|
|
367
|
+
},
|
|
368
|
+
shadows: {
|
|
369
|
+
sm: '0 1px 3px rgba(0,0,0,0.12)',
|
|
370
|
+
md: '0 4px 6px rgba(0,0,0,0.16)',
|
|
371
|
+
lg: '0 10px 20px rgba(0,0,0,0.19)',
|
|
372
|
+
},
|
|
373
|
+
};
|
|
374
|
+
/**
|
|
375
|
+
* Default predefined date ranges in Spanish.
|
|
376
|
+
*
|
|
377
|
+
* Provides commonly used date ranges like "Today", "Last 7 days", etc.
|
|
378
|
+
* All ranges are calculated dynamically based on the current date.
|
|
379
|
+
*
|
|
380
|
+
* @example
|
|
381
|
+
* ```typescript
|
|
382
|
+
* <ngx-datex [ranges]="DEFAULT_RANGES"></ngx-datex>
|
|
383
|
+
*
|
|
384
|
+
* // Or customize with your own ranges
|
|
385
|
+
* const customRanges = {
|
|
386
|
+
* ...DEFAULT_RANGES,
|
|
387
|
+
* 'Custom Range': [startDate, endDate]
|
|
388
|
+
* };
|
|
389
|
+
* ```
|
|
390
|
+
*/
|
|
391
|
+
const DEFAULT_RANGES = {
|
|
392
|
+
Hoy: [startOfDay(new Date()), endOfDay(new Date())],
|
|
393
|
+
Ayer: [
|
|
394
|
+
(() => {
|
|
395
|
+
const yesterday = new Date();
|
|
396
|
+
yesterday.setDate(yesterday.getDate() - 1);
|
|
397
|
+
return startOfDay(yesterday);
|
|
398
|
+
})(),
|
|
399
|
+
(() => {
|
|
400
|
+
const yesterday = new Date();
|
|
401
|
+
yesterday.setDate(yesterday.getDate() - 1);
|
|
402
|
+
return endOfDay(yesterday);
|
|
403
|
+
})(),
|
|
404
|
+
],
|
|
405
|
+
'Últimos 5 días': [
|
|
406
|
+
(() => {
|
|
407
|
+
const date = new Date();
|
|
408
|
+
date.setDate(date.getDate() - 4);
|
|
409
|
+
return startOfDay(date);
|
|
410
|
+
})(),
|
|
411
|
+
endOfDay(new Date()),
|
|
412
|
+
],
|
|
413
|
+
'Últimos 7 días': [
|
|
414
|
+
(() => {
|
|
415
|
+
const date = new Date();
|
|
416
|
+
date.setDate(date.getDate() - 6);
|
|
417
|
+
return startOfDay(date);
|
|
418
|
+
})(),
|
|
419
|
+
endOfDay(new Date()),
|
|
420
|
+
],
|
|
421
|
+
'Últimos 10 días': [
|
|
422
|
+
(() => {
|
|
423
|
+
const date = new Date();
|
|
424
|
+
date.setDate(date.getDate() - 9);
|
|
425
|
+
return startOfDay(date);
|
|
426
|
+
})(),
|
|
427
|
+
endOfDay(new Date()),
|
|
428
|
+
],
|
|
429
|
+
'Últimos 15 días': [
|
|
430
|
+
(() => {
|
|
431
|
+
const date = new Date();
|
|
432
|
+
date.setDate(date.getDate() - 14);
|
|
433
|
+
return startOfDay(date);
|
|
434
|
+
})(),
|
|
435
|
+
endOfDay(new Date()),
|
|
436
|
+
],
|
|
437
|
+
'Últimos 30 días': [
|
|
438
|
+
(() => {
|
|
439
|
+
const date = new Date();
|
|
440
|
+
date.setDate(date.getDate() - 29);
|
|
441
|
+
return startOfDay(date);
|
|
442
|
+
})(),
|
|
443
|
+
endOfDay(new Date()),
|
|
444
|
+
],
|
|
445
|
+
'Esta semana': [
|
|
446
|
+
(() => {
|
|
447
|
+
const today = new Date();
|
|
448
|
+
const dayOfWeek = today.getDay();
|
|
449
|
+
const mondayOffset = dayOfWeek === 0 ? -6 : 1 - dayOfWeek; // Monday as first day
|
|
450
|
+
const monday = new Date(today);
|
|
451
|
+
monday.setDate(today.getDate() + mondayOffset);
|
|
452
|
+
return startOfDay(monday);
|
|
453
|
+
})(),
|
|
454
|
+
endOfDay(new Date()),
|
|
455
|
+
],
|
|
456
|
+
'Este mes': [
|
|
457
|
+
startOfDay(new Date(new Date().getFullYear(), new Date().getMonth(), 1)),
|
|
458
|
+
(() => {
|
|
459
|
+
const lastDayOfCurrentMonth = new Date(new Date().getFullYear(), new Date().getMonth() + 1, 0);
|
|
460
|
+
return endOfDay(lastDayOfCurrentMonth);
|
|
461
|
+
})(),
|
|
462
|
+
],
|
|
463
|
+
'El mes pasado': [
|
|
464
|
+
startOfDay(new Date(new Date().getFullYear(), new Date().getMonth() - 1, 1)),
|
|
465
|
+
(() => {
|
|
466
|
+
const lastDayOfPrevMonth = new Date(new Date().getFullYear(), new Date().getMonth(), 0);
|
|
467
|
+
return endOfDay(lastDayOfPrevMonth);
|
|
468
|
+
})(),
|
|
469
|
+
],
|
|
470
|
+
};
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
* Core service for NgxDatex component operations.
|
|
474
|
+
*
|
|
475
|
+
* Provides essential functionality for:
|
|
476
|
+
* - Configuration management
|
|
477
|
+
* - Locale handling
|
|
478
|
+
* - Date formatting
|
|
479
|
+
* - Calendar matrix generation
|
|
480
|
+
* - Date validation
|
|
481
|
+
*
|
|
482
|
+
* @example
|
|
483
|
+
* ```typescript
|
|
484
|
+
* // Inject the service
|
|
485
|
+
* constructor(private datexService: NgxDatexService) {}
|
|
486
|
+
*
|
|
487
|
+
* // Update configuration
|
|
488
|
+
* this.datexService.updateConfig({
|
|
489
|
+
* dateFormat: 'DD/MM/YYYY',
|
|
490
|
+
* firstDayOfWeek: 1
|
|
491
|
+
* });
|
|
492
|
+
*
|
|
493
|
+
* // Format a date range
|
|
494
|
+
* const formatted = this.datexService.formatDateValue(dateRange, 'DD/MM/YYYY');
|
|
495
|
+
* ```
|
|
496
|
+
*/
|
|
497
|
+
class NgxDatexService {
|
|
498
|
+
config = {};
|
|
499
|
+
locale = SPANISH_LOCALE;
|
|
500
|
+
/**
|
|
501
|
+
* Updates the component configuration.
|
|
502
|
+
*
|
|
503
|
+
* @param config - Partial configuration object to merge with existing config
|
|
504
|
+
*
|
|
505
|
+
* @example
|
|
506
|
+
* ```typescript
|
|
507
|
+
* this.datexService.updateConfig({
|
|
508
|
+
* dateFormat: 'YYYY-MM-DD',
|
|
509
|
+
* firstDayOfWeek: 0, // Sunday
|
|
510
|
+
* businessDaysOnly: true
|
|
511
|
+
* });
|
|
512
|
+
* ```
|
|
513
|
+
*/
|
|
514
|
+
updateConfig(config) {
|
|
515
|
+
this.config = { ...this.config, ...config };
|
|
516
|
+
}
|
|
517
|
+
/**
|
|
518
|
+
* Sets the locale configuration for internationalization.
|
|
519
|
+
*
|
|
520
|
+
* @param locale - Locale configuration object
|
|
521
|
+
*
|
|
522
|
+
* @example
|
|
523
|
+
* ```typescript
|
|
524
|
+
* this.datexService.setLocale({
|
|
525
|
+
* format: 'DD/MM/YYYY',
|
|
526
|
+
* daysOfWeek: ['Lu', 'Ma', 'Mi', 'Ju', 'Vi', 'Sa', 'Do'],
|
|
527
|
+
* monthNames: ['Enero', 'Febrero', ...],
|
|
528
|
+
* applyLabel: 'Aplicar',
|
|
529
|
+
* cancelLabel: 'Cancelar'
|
|
530
|
+
* });
|
|
531
|
+
* ```
|
|
532
|
+
*/
|
|
533
|
+
setLocale(locale) {
|
|
534
|
+
this.locale = { ...this.locale, ...locale };
|
|
535
|
+
}
|
|
536
|
+
/**
|
|
537
|
+
* Formats a date range value into a display string.
|
|
538
|
+
*
|
|
539
|
+
* @param value - The date range value to format
|
|
540
|
+
* @param format - Optional format string (uses locale format if not provided)
|
|
541
|
+
* @returns Formatted date string
|
|
542
|
+
*
|
|
543
|
+
* @example
|
|
544
|
+
* ```typescript
|
|
545
|
+
* const dateRange: NgxDatexValue = {
|
|
546
|
+
* startDate: new Date('2024-01-01'),
|
|
547
|
+
* endDate: new Date('2024-01-31')
|
|
548
|
+
* };
|
|
549
|
+
*
|
|
550
|
+
* const formatted = this.datexService.formatDateValue(dateRange, 'DD/MM/YYYY');
|
|
551
|
+
* // Returns: "01/01/2024 - 31/01/2024"
|
|
552
|
+
* ```
|
|
553
|
+
*/
|
|
554
|
+
formatDateValue(value, format) {
|
|
555
|
+
if (!value)
|
|
556
|
+
return '';
|
|
557
|
+
const dateFormat = format || this.locale.format || 'DD/MM/YYYY';
|
|
558
|
+
const start = formatDateValue(value.startDate, dateFormat);
|
|
559
|
+
if (!value.endDate || value.startDate.getTime() === value.endDate.getTime()) {
|
|
560
|
+
return start;
|
|
561
|
+
}
|
|
562
|
+
const end = formatDateValue(value.endDate, dateFormat);
|
|
563
|
+
return `${start}${this.locale.separator || ' - '}${end}`;
|
|
564
|
+
}
|
|
565
|
+
buildCalendarMatrix(month) {
|
|
566
|
+
const firstDay = getStartOfMonth(month);
|
|
567
|
+
const firstDayOfWeek = this.locale.firstDay || 1;
|
|
568
|
+
const startCalendar = getStartOfWeek(firstDay, firstDayOfWeek);
|
|
569
|
+
const calendarDays = [];
|
|
570
|
+
let currentWeek = [];
|
|
571
|
+
const startDate = new Date(startCalendar);
|
|
572
|
+
for (let i = 0; i < 42; i++) {
|
|
573
|
+
if (i > 0 && i % 7 === 0) {
|
|
574
|
+
calendarDays.push(currentWeek);
|
|
575
|
+
currentWeek = [];
|
|
576
|
+
}
|
|
577
|
+
const currentDate = new Date(startDate);
|
|
578
|
+
currentDate.setDate(startDate.getDate() + i);
|
|
579
|
+
currentDate.setHours(12, 0, 0, 0);
|
|
580
|
+
currentWeek.push(currentDate);
|
|
581
|
+
}
|
|
582
|
+
if (currentWeek.length > 0) {
|
|
583
|
+
calendarDays.push(currentWeek);
|
|
584
|
+
}
|
|
585
|
+
return calendarDays;
|
|
586
|
+
}
|
|
587
|
+
validateDate(date) {
|
|
588
|
+
if (!date || isNaN(date.getTime())) {
|
|
589
|
+
return { isValid: false, error: 'Fecha inválida', errorCode: 'INVALID_DATE' };
|
|
590
|
+
}
|
|
591
|
+
return { isValid: true };
|
|
592
|
+
}
|
|
593
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: NgxDatexService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
594
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: NgxDatexService, providedIn: 'root' });
|
|
595
|
+
}
|
|
596
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: NgxDatexService, decorators: [{
|
|
597
|
+
type: Injectable,
|
|
598
|
+
args: [{
|
|
599
|
+
providedIn: 'root',
|
|
600
|
+
}]
|
|
601
|
+
}] });
|
|
602
|
+
|
|
603
|
+
/**
|
|
604
|
+
* @fileoverview NgxDatex Overlay Service
|
|
605
|
+
*
|
|
606
|
+
* Service responsible for managing the calendar overlay positioning, creation, and lifecycle.
|
|
607
|
+
* Handles both desktop and mobile overlay strategies with proper event management.
|
|
608
|
+
*/
|
|
609
|
+
/**
|
|
610
|
+
* Service for managing calendar overlay positioning and lifecycle.
|
|
611
|
+
*
|
|
612
|
+
* Provides methods to create, position, and manage the calendar overlay
|
|
613
|
+
* with support for both desktop and mobile layouts. Handles click outside
|
|
614
|
+
* detection, keyboard events, and responsive positioning.
|
|
615
|
+
*
|
|
616
|
+
* @example
|
|
617
|
+
* ```typescript
|
|
618
|
+
* // In component
|
|
619
|
+
* constructor(private overlayService: NgxDatexOverlayService) {}
|
|
620
|
+
*
|
|
621
|
+
* openCalendar() {
|
|
622
|
+
* this.overlayService.createOverlay(
|
|
623
|
+
* this.inputElement,
|
|
624
|
+
* this.calendarTemplate,
|
|
625
|
+
* this.viewContainerRef,
|
|
626
|
+
* this.locale,
|
|
627
|
+
* 'center',
|
|
628
|
+
* 'auto',
|
|
629
|
+
* () => this.closeCalendar(),
|
|
630
|
+
* (event) => this.handleKeydown(event),
|
|
631
|
+
* (position) => this.updatePosition(position)
|
|
632
|
+
* );
|
|
633
|
+
* }
|
|
634
|
+
* ```
|
|
635
|
+
*/
|
|
636
|
+
class NgxDatexOverlayService {
|
|
637
|
+
overlayRef;
|
|
638
|
+
overlay = inject(Overlay);
|
|
639
|
+
/**
|
|
640
|
+
* Creates and configures the calendar overlay.
|
|
641
|
+
*
|
|
642
|
+
* Sets up the overlay with appropriate positioning strategy, event handlers,
|
|
643
|
+
* and responsive behavior. Reuses existing overlay if already created.
|
|
644
|
+
*
|
|
645
|
+
* @param inputElement - Reference to the input element for positioning
|
|
646
|
+
* @param calendarTemplate - Template reference for the calendar content
|
|
647
|
+
* @param viewContainerRef - View container for template portal
|
|
648
|
+
* @param locale - Localization settings for RTL/LTR direction
|
|
649
|
+
* @param opens - Horizontal alignment preference
|
|
650
|
+
* @param drops - Vertical alignment preference
|
|
651
|
+
* @param onOutsideClick - Callback for clicks outside the overlay
|
|
652
|
+
* @param onKeydown - Callback for keyboard events
|
|
653
|
+
* @param onPositionChange - Callback for position updates
|
|
654
|
+
* @returns The created or existing overlay reference
|
|
655
|
+
*
|
|
656
|
+
* @example
|
|
657
|
+
* ```typescript
|
|
658
|
+
* const overlayRef = this.overlayService.createOverlay(
|
|
659
|
+
* this.inputElement,
|
|
660
|
+
* this.calendarTemplate,
|
|
661
|
+
* this.viewContainerRef,
|
|
662
|
+
* this.locale(),
|
|
663
|
+
* this.opens(),
|
|
664
|
+
* this.drops(),
|
|
665
|
+
* () => this.closeCalendar(),
|
|
666
|
+
* (event) => {
|
|
667
|
+
* if (event.key === 'Escape') {
|
|
668
|
+
* this.closeCalendar();
|
|
669
|
+
* }
|
|
670
|
+
* },
|
|
671
|
+
* (position) => this.updateArrowPosition(position)
|
|
672
|
+
* );
|
|
673
|
+
* ```
|
|
674
|
+
*/
|
|
675
|
+
createOverlay(inputElement, calendarTemplate, viewContainerRef, locale, opens, drops, onOutsideClick, onKeydown, onPositionChange) {
|
|
676
|
+
if (this.overlayRef && this.overlayRef.hasAttached()) {
|
|
677
|
+
return this.overlayRef;
|
|
678
|
+
}
|
|
679
|
+
if (!this.overlayRef) {
|
|
680
|
+
const config = this.getOverlayConfig(inputElement, locale, opens, drops, onPositionChange);
|
|
681
|
+
this.overlayRef = this.overlay.create(config);
|
|
682
|
+
this.setupOverlayEvents(inputElement, onOutsideClick, onKeydown);
|
|
683
|
+
}
|
|
684
|
+
if (!this.overlayRef.hasAttached()) {
|
|
685
|
+
const portal = new TemplatePortal(calendarTemplate, viewContainerRef);
|
|
686
|
+
this.overlayRef.attach(portal);
|
|
687
|
+
}
|
|
688
|
+
return this.overlayRef;
|
|
689
|
+
}
|
|
690
|
+
/**
|
|
691
|
+
* Closes the calendar overlay without disposing it.
|
|
692
|
+
*
|
|
693
|
+
* Detaches the overlay content while keeping the overlay reference
|
|
694
|
+
* for potential reuse. The overlay can be reopened without recreation.
|
|
695
|
+
*
|
|
696
|
+
* @example
|
|
697
|
+
* ```typescript
|
|
698
|
+
* // Close the overlay
|
|
699
|
+
* this.overlayService.closeOverlay();
|
|
700
|
+
* ```
|
|
701
|
+
*/
|
|
702
|
+
closeOverlay() {
|
|
703
|
+
if (this.overlayRef && this.overlayRef.hasAttached()) {
|
|
704
|
+
this.overlayRef.detach();
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
/**
|
|
708
|
+
* Completely disposes of the overlay and cleans up resources.
|
|
709
|
+
*
|
|
710
|
+
* Detaches content, disposes the overlay reference, and cleans up
|
|
711
|
+
* all associated resources. Should be called during component destruction.
|
|
712
|
+
*
|
|
713
|
+
* @example
|
|
714
|
+
* ```typescript
|
|
715
|
+
* ngOnDestroy() {
|
|
716
|
+
* this.overlayService.disposeOverlay();
|
|
717
|
+
* }
|
|
718
|
+
* ```
|
|
719
|
+
*/
|
|
720
|
+
disposeOverlay() {
|
|
721
|
+
if (this.overlayRef) {
|
|
722
|
+
if (this.overlayRef.hasAttached()) {
|
|
723
|
+
this.overlayRef.detach();
|
|
724
|
+
}
|
|
725
|
+
this.overlayRef.dispose();
|
|
726
|
+
this.overlayRef = undefined;
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
/**
|
|
730
|
+
* Sets up event listeners for the overlay.
|
|
731
|
+
*
|
|
732
|
+
* Configures click outside detection and keyboard event handling
|
|
733
|
+
* with different strategies for desktop and mobile devices.
|
|
734
|
+
*
|
|
735
|
+
* @private
|
|
736
|
+
* @param inputElement - Reference to the input element
|
|
737
|
+
* @param onOutsideClick - Callback for outside clicks
|
|
738
|
+
* @param onKeydown - Callback for keyboard events
|
|
739
|
+
*/
|
|
740
|
+
setupOverlayEvents(inputElement, onOutsideClick, onKeydown) {
|
|
741
|
+
if (!this.overlayRef)
|
|
742
|
+
return;
|
|
743
|
+
if (!isMobileDevice()) {
|
|
744
|
+
this.overlayRef.outsidePointerEvents().subscribe((event) => {
|
|
745
|
+
const target = event.target;
|
|
746
|
+
const inputEl = inputElement?.nativeElement;
|
|
747
|
+
// Check if click was inside calendar or input
|
|
748
|
+
if ((inputEl && inputEl.contains(target)) ||
|
|
749
|
+
target.closest('.ngx-datex-overlay') ||
|
|
750
|
+
target.closest('.ngx-datex-calendar-grid') ||
|
|
751
|
+
target.closest('.ngx-datex-time-picker') ||
|
|
752
|
+
target.closest('.ngx-datex-ranges-sidebar') ||
|
|
753
|
+
target.closest('.ngx-datex-footer-buttons') ||
|
|
754
|
+
target.closest('.ngx-datex-mobile-header-buttons')) {
|
|
755
|
+
return;
|
|
756
|
+
}
|
|
757
|
+
onOutsideClick();
|
|
758
|
+
});
|
|
759
|
+
}
|
|
760
|
+
else {
|
|
761
|
+
this.overlayRef.backdropClick().subscribe(() => {
|
|
762
|
+
onOutsideClick();
|
|
763
|
+
});
|
|
764
|
+
}
|
|
765
|
+
this.overlayRef.keydownEvents().subscribe((event) => {
|
|
766
|
+
onKeydown(event);
|
|
767
|
+
});
|
|
768
|
+
}
|
|
769
|
+
/**
|
|
770
|
+
* Creates the overlay configuration based on device type and preferences.
|
|
771
|
+
*
|
|
772
|
+
* Returns different configurations for mobile and desktop devices,
|
|
773
|
+
* with appropriate positioning strategies and styling.
|
|
774
|
+
*
|
|
775
|
+
* @private
|
|
776
|
+
* @param inputElement - Reference to the input element
|
|
777
|
+
* @param locale - Localization settings
|
|
778
|
+
* @param opens - Horizontal alignment preference
|
|
779
|
+
* @param drops - Vertical alignment preference
|
|
780
|
+
* @param onPositionChange - Callback for position updates
|
|
781
|
+
* @returns Overlay configuration object
|
|
782
|
+
*/
|
|
783
|
+
getOverlayConfig(inputElement, locale, opens, drops, onPositionChange) {
|
|
784
|
+
const positionStrategy = this.getPositionStrategy(inputElement, opens, drops, onPositionChange);
|
|
785
|
+
if (isMobileDevice()) {
|
|
786
|
+
return new OverlayConfig({
|
|
787
|
+
positionStrategy: this.overlay.position().global().bottom('0').centerHorizontally(),
|
|
788
|
+
hasBackdrop: true,
|
|
789
|
+
backdropClass: 'cdk-overlay-transparent-backdrop',
|
|
790
|
+
panelClass: ['ngx-datex-overlay-panel', 'ngx-datex-mobile-overlay'],
|
|
791
|
+
scrollStrategy: this.overlay.scrollStrategies.block(),
|
|
792
|
+
direction: locale.direction || 'ltr',
|
|
793
|
+
width: '100vw',
|
|
794
|
+
maxHeight: '85vh',
|
|
795
|
+
});
|
|
796
|
+
}
|
|
797
|
+
return new OverlayConfig({
|
|
798
|
+
positionStrategy,
|
|
799
|
+
hasBackdrop: false,
|
|
800
|
+
backdropClass: '',
|
|
801
|
+
panelClass: 'ngx-datex-overlay-panel',
|
|
802
|
+
scrollStrategy: this.overlay.scrollStrategies.reposition(),
|
|
803
|
+
direction: locale.direction || 'ltr',
|
|
804
|
+
});
|
|
805
|
+
}
|
|
806
|
+
/**
|
|
807
|
+
* Creates the flexible positioning strategy for desktop overlays.
|
|
808
|
+
*
|
|
809
|
+
* Configures multiple fallback positions based on user preferences
|
|
810
|
+
* and available space, with automatic repositioning on scroll.
|
|
811
|
+
*
|
|
812
|
+
* @private
|
|
813
|
+
* @param inputElement - Reference to the input element
|
|
814
|
+
* @param opens - Horizontal alignment preference
|
|
815
|
+
* @param drops - Vertical alignment preference
|
|
816
|
+
* @param onPositionChange - Callback for position updates
|
|
817
|
+
* @returns Flexible connected position strategy
|
|
818
|
+
*/
|
|
819
|
+
getPositionStrategy(inputElement, opens, drops, onPositionChange) {
|
|
820
|
+
const positions = this.getPositions(opens, drops);
|
|
821
|
+
const strategy = this.overlay
|
|
822
|
+
.position()
|
|
823
|
+
.flexibleConnectedTo(inputElement)
|
|
824
|
+
.withPositions(positions)
|
|
825
|
+
.withFlexibleDimensions(true)
|
|
826
|
+
.withPush(true)
|
|
827
|
+
.withGrowAfterOpen(true)
|
|
828
|
+
.withViewportMargin(8);
|
|
829
|
+
strategy.positionChanges.subscribe((change) => {
|
|
830
|
+
if (change.connectionPair) {
|
|
831
|
+
onPositionChange({
|
|
832
|
+
originX: change.connectionPair.originX,
|
|
833
|
+
originY: change.connectionPair.originY,
|
|
834
|
+
overlayX: change.connectionPair.overlayX,
|
|
835
|
+
overlayY: change.connectionPair.overlayY,
|
|
836
|
+
});
|
|
837
|
+
}
|
|
838
|
+
});
|
|
839
|
+
return strategy;
|
|
840
|
+
}
|
|
841
|
+
/**
|
|
842
|
+
* Generates position configurations based on user preferences.
|
|
843
|
+
*
|
|
844
|
+
* Creates an array of connected positions with fallbacks,
|
|
845
|
+
* prioritizing user preferences while providing alternatives
|
|
846
|
+
* when space is limited.
|
|
847
|
+
*
|
|
848
|
+
* @private
|
|
849
|
+
* @param opens - Horizontal alignment preference
|
|
850
|
+
* @param drops - Vertical alignment preference
|
|
851
|
+
* @returns Array of connected position configurations
|
|
852
|
+
*/
|
|
853
|
+
getPositions(opens, drops) {
|
|
854
|
+
let positions = [];
|
|
855
|
+
if (drops === 'auto') {
|
|
856
|
+
if (opens === 'left') {
|
|
857
|
+
positions = [
|
|
858
|
+
{ originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4 },
|
|
859
|
+
{ originX: 'start', originY: 'top', overlayX: 'start', overlayY: 'bottom', offsetY: -4 },
|
|
860
|
+
];
|
|
861
|
+
}
|
|
862
|
+
else if (opens === 'right') {
|
|
863
|
+
positions = [
|
|
864
|
+
{ originX: 'end', originY: 'bottom', overlayX: 'end', overlayY: 'top', offsetY: 4 },
|
|
865
|
+
{ originX: 'end', originY: 'top', overlayX: 'end', overlayY: 'bottom', offsetY: -4 },
|
|
866
|
+
];
|
|
867
|
+
}
|
|
868
|
+
else {
|
|
869
|
+
positions = [
|
|
870
|
+
{ originX: 'center', originY: 'bottom', overlayX: 'center', overlayY: 'top', offsetY: 4 },
|
|
871
|
+
{
|
|
872
|
+
originX: 'center',
|
|
873
|
+
originY: 'top',
|
|
874
|
+
overlayX: 'center',
|
|
875
|
+
overlayY: 'bottom',
|
|
876
|
+
offsetY: -4,
|
|
877
|
+
},
|
|
878
|
+
];
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
else if (drops === 'up') {
|
|
882
|
+
if (opens === 'left') {
|
|
883
|
+
positions = [
|
|
884
|
+
{ originX: 'start', originY: 'top', overlayX: 'start', overlayY: 'bottom', offsetY: -4 },
|
|
885
|
+
];
|
|
886
|
+
}
|
|
887
|
+
else if (opens === 'right') {
|
|
888
|
+
positions = [
|
|
889
|
+
{ originX: 'end', originY: 'top', overlayX: 'end', overlayY: 'bottom', offsetY: -4 },
|
|
890
|
+
];
|
|
891
|
+
}
|
|
892
|
+
else {
|
|
893
|
+
positions = [
|
|
894
|
+
{
|
|
895
|
+
originX: 'center',
|
|
896
|
+
originY: 'top',
|
|
897
|
+
overlayX: 'center',
|
|
898
|
+
overlayY: 'bottom',
|
|
899
|
+
offsetY: -4,
|
|
900
|
+
},
|
|
901
|
+
];
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
else {
|
|
905
|
+
if (opens === 'left') {
|
|
906
|
+
positions = [
|
|
907
|
+
{ originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4 },
|
|
908
|
+
];
|
|
909
|
+
}
|
|
910
|
+
else if (opens === 'right') {
|
|
911
|
+
positions = [
|
|
912
|
+
{ originX: 'end', originY: 'bottom', overlayX: 'end', overlayY: 'top', offsetY: 4 },
|
|
913
|
+
];
|
|
914
|
+
}
|
|
915
|
+
else {
|
|
916
|
+
positions = [
|
|
917
|
+
{ originX: 'center', originY: 'bottom', overlayX: 'center', overlayY: 'top', offsetY: 4 },
|
|
918
|
+
];
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
return positions;
|
|
922
|
+
}
|
|
923
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: NgxDatexOverlayService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
924
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: NgxDatexOverlayService });
|
|
925
|
+
}
|
|
926
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: NgxDatexOverlayService, decorators: [{
|
|
927
|
+
type: Injectable
|
|
928
|
+
}] });
|
|
929
|
+
|
|
930
|
+
/**
|
|
931
|
+
* @fileoverview NgxDatex Time Picker Service
|
|
932
|
+
*
|
|
933
|
+
* Service responsible for managing time picker functionality including time validation,
|
|
934
|
+
* time component updates, and availability calculations based on date constraints.
|
|
935
|
+
*/
|
|
936
|
+
/**
|
|
937
|
+
* Service for managing time picker operations and validations.
|
|
938
|
+
*
|
|
939
|
+
* Handles time component updates, validates time selections against date constraints,
|
|
940
|
+
* and provides available time options based on minimum/maximum date restrictions.
|
|
941
|
+
* Supports both 12-hour and 24-hour time formats with proper AM/PM handling.
|
|
942
|
+
*
|
|
943
|
+
* @example
|
|
944
|
+
* ```typescript
|
|
945
|
+
* // In component
|
|
946
|
+
* constructor(private timePickerService: NgxDatexTimePickerService) {}
|
|
947
|
+
*
|
|
948
|
+
* onTimeChange(side: 'start' | 'end', component: 'hour' | 'minute', value: number) {
|
|
949
|
+
* this.timePickerService.updateTimeFromPicker(
|
|
950
|
+
* this.currentStartDate,
|
|
951
|
+
* this.currentEndDate,
|
|
952
|
+
* side,
|
|
953
|
+
* component,
|
|
954
|
+
* value,
|
|
955
|
+
* this.startTime,
|
|
956
|
+
* this.endTime,
|
|
957
|
+
* this.timePicker24Hour,
|
|
958
|
+
* this.singleDatePicker,
|
|
959
|
+
* (date) => this.setStartDate(date),
|
|
960
|
+
* (date) => this.setEndDate(date),
|
|
961
|
+
* (side, time) => this.updateTimeSignal(side, time)
|
|
962
|
+
* );
|
|
963
|
+
* }
|
|
964
|
+
* ```
|
|
965
|
+
*/
|
|
966
|
+
class NgxDatexTimePickerService {
|
|
967
|
+
/**
|
|
968
|
+
* Updates time from picker component changes and applies to dates.
|
|
969
|
+
*
|
|
970
|
+
* Handles time component updates (hour, minute, AM/PM) and applies the changes
|
|
971
|
+
* to the corresponding dates. Manages single date picker logic and ensures
|
|
972
|
+
* end dates don't precede start dates on the same day.
|
|
973
|
+
*
|
|
974
|
+
* @param currentStartDate - Current start date
|
|
975
|
+
* @param currentEndDate - Current end date (can be null)
|
|
976
|
+
* @param side - Which time picker is being updated ('start' or 'end')
|
|
977
|
+
* @param component - Which time component is changing ('hour', 'minute', or 'ampm')
|
|
978
|
+
* @param value - New value for the time component
|
|
979
|
+
* @param startTime - Current start time value
|
|
980
|
+
* @param endTime - Current end time value
|
|
981
|
+
* @param timePicker24Hour - Whether using 24-hour format
|
|
982
|
+
* @param singleDatePicker - Whether in single date picker mode
|
|
983
|
+
* @param onStartDateChange - Callback for start date changes
|
|
984
|
+
* @param onEndDateChange - Callback for end date changes
|
|
985
|
+
* @param onTimeChange - Callback for time signal updates
|
|
986
|
+
*
|
|
987
|
+
* @example
|
|
988
|
+
* ```typescript
|
|
989
|
+
* // Update start hour to 14 (2 PM)
|
|
990
|
+
* this.timePickerService.updateTimeFromPicker(
|
|
991
|
+
* this.startDate,
|
|
992
|
+
* this.endDate,
|
|
993
|
+
* 'start',
|
|
994
|
+
* 'hour',
|
|
995
|
+
* 14,
|
|
996
|
+
* this.startTime,
|
|
997
|
+
* this.endTime,
|
|
998
|
+
* true, // 24-hour format
|
|
999
|
+
* false, // range picker
|
|
1000
|
+
* (date) => this.updateStartDate(date),
|
|
1001
|
+
* (date) => this.updateEndDate(date),
|
|
1002
|
+
* (side, time) => this.updateTimeSignal(side, time)
|
|
1003
|
+
* );
|
|
1004
|
+
* ```
|
|
1005
|
+
*/
|
|
1006
|
+
updateTimeFromPicker(currentStartDate, currentEndDate, side, component, value, startTime, endTime, timePicker24Hour, singleDatePicker, onStartDateChange, onEndDateChange, onTimeChange) {
|
|
1007
|
+
if (!currentStartDate)
|
|
1008
|
+
return;
|
|
1009
|
+
// Update the time signal
|
|
1010
|
+
if (side === 'start') {
|
|
1011
|
+
const newStartTime = { ...startTime, [component]: value };
|
|
1012
|
+
onTimeChange('start', newStartTime);
|
|
1013
|
+
// Apply time to date
|
|
1014
|
+
const newStartDate = this.applyTimeToDate(currentStartDate, newStartTime, timePicker24Hour);
|
|
1015
|
+
onStartDateChange(newStartDate);
|
|
1016
|
+
// Handle single date picker
|
|
1017
|
+
if (singleDatePicker) {
|
|
1018
|
+
onEndDateChange(new Date(newStartDate));
|
|
1019
|
+
}
|
|
1020
|
+
else if (currentEndDate &&
|
|
1021
|
+
isSameDate(currentEndDate, newStartDate, 'day') &&
|
|
1022
|
+
currentEndDate < newStartDate) {
|
|
1023
|
+
onEndDateChange(new Date(newStartDate));
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
else if (currentEndDate) {
|
|
1027
|
+
const newEndTime = { ...endTime, [component]: value };
|
|
1028
|
+
onTimeChange('end', newEndTime);
|
|
1029
|
+
// Apply time to date
|
|
1030
|
+
const newEndDate = this.applyTimeToDate(currentEndDate, newEndTime, timePicker24Hour);
|
|
1031
|
+
onEndDateChange(newEndDate);
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
/**
|
|
1035
|
+
* Applies time values to a date object.
|
|
1036
|
+
*
|
|
1037
|
+
* Creates a new date with the specified time components applied.
|
|
1038
|
+
* Handles 12-hour to 24-hour conversion and ensures proper time formatting.
|
|
1039
|
+
*
|
|
1040
|
+
* @private
|
|
1041
|
+
* @param date - Base date to apply time to
|
|
1042
|
+
* @param time - Time value with hour, minute, and AM/PM
|
|
1043
|
+
* @param is24Hour - Whether using 24-hour format
|
|
1044
|
+
* @returns New date with applied time
|
|
1045
|
+
*
|
|
1046
|
+
* @example
|
|
1047
|
+
* ```typescript
|
|
1048
|
+
* const date = new Date('2024-01-15');
|
|
1049
|
+
* const time = { hour: 2, minute: 30, ampm: 'PM' };
|
|
1050
|
+
* const result = this.applyTimeToDate(date, time, false);
|
|
1051
|
+
* // Result: 2024-01-15 14:30:00
|
|
1052
|
+
* ```
|
|
1053
|
+
*/
|
|
1054
|
+
applyTimeToDate(date, time, is24Hour) {
|
|
1055
|
+
const newDate = new Date(date);
|
|
1056
|
+
let hour24 = time.hour;
|
|
1057
|
+
if (!is24Hour) {
|
|
1058
|
+
if (time.ampm === 'PM' && time.hour < 12)
|
|
1059
|
+
hour24 += 12;
|
|
1060
|
+
if (time.ampm === 'AM' && time.hour === 12)
|
|
1061
|
+
hour24 = 0;
|
|
1062
|
+
}
|
|
1063
|
+
newDate.setHours(hour24, time.minute, 0, 0);
|
|
1064
|
+
return newDate;
|
|
1065
|
+
}
|
|
1066
|
+
/**
|
|
1067
|
+
* Extracts time components from a date object.
|
|
1068
|
+
*
|
|
1069
|
+
* Converts a date to time picker format, handling both 12-hour and 24-hour
|
|
1070
|
+
* formats with proper AM/PM designation.
|
|
1071
|
+
*
|
|
1072
|
+
* @param date - Date to extract time from
|
|
1073
|
+
* @param timePicker24Hour - Whether using 24-hour format
|
|
1074
|
+
* @returns Time value object with hour, minute, and AM/PM
|
|
1075
|
+
*
|
|
1076
|
+
* @example
|
|
1077
|
+
* ```typescript
|
|
1078
|
+
* const date = new Date('2024-01-15 14:30:00');
|
|
1079
|
+
*
|
|
1080
|
+
* // 24-hour format
|
|
1081
|
+
* const time24 = this.updateTimeSignalsFromDate(date, true);
|
|
1082
|
+
* // Result: { hour: 14, minute: 30, ampm: 'AM' }
|
|
1083
|
+
*
|
|
1084
|
+
* // 12-hour format
|
|
1085
|
+
* const time12 = this.updateTimeSignalsFromDate(date, false);
|
|
1086
|
+
* // Result: { hour: 2, minute: 30, ampm: 'PM' }
|
|
1087
|
+
* ```
|
|
1088
|
+
*/
|
|
1089
|
+
updateTimeSignalsFromDate(date, timePicker24Hour) {
|
|
1090
|
+
const hour24 = date.getHours();
|
|
1091
|
+
const minute = date.getMinutes();
|
|
1092
|
+
if (timePicker24Hour) {
|
|
1093
|
+
return { hour: hour24, minute, ampm: 'AM' };
|
|
1094
|
+
}
|
|
1095
|
+
else {
|
|
1096
|
+
const hour12 = hour24 === 0 ? 12 : hour24 > 12 ? hour24 - 12 : hour24;
|
|
1097
|
+
const ampm = hour24 >= 12 ? 'PM' : 'AM';
|
|
1098
|
+
return { hour: hour12, minute, ampm };
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
/**
|
|
1102
|
+
* Gets available hours based on date constraints.
|
|
1103
|
+
*
|
|
1104
|
+
* Returns an array of hour options with disabled state based on minimum/maximum
|
|
1105
|
+
* date restrictions and start date constraints for end time selection.
|
|
1106
|
+
*
|
|
1107
|
+
* @param date - Date to get hours for
|
|
1108
|
+
* @param minDate - Minimum allowed date
|
|
1109
|
+
* @param maxDate - Maximum allowed date
|
|
1110
|
+
* @param timePicker24Hour - Whether using 24-hour format
|
|
1111
|
+
* @param timeValue - Current time value for AM/PM context
|
|
1112
|
+
* @param isEndTime - Whether this is for end time selection
|
|
1113
|
+
* @param startDate - Start date for end time validation
|
|
1114
|
+
* @returns Array of hour options with availability status
|
|
1115
|
+
*
|
|
1116
|
+
* @example
|
|
1117
|
+
* ```typescript
|
|
1118
|
+
* const availableHours = this.timePickerService.getAvailableHours(
|
|
1119
|
+
* new Date('2024-01-15'),
|
|
1120
|
+
* new Date('2024-01-15 10:00:00'), // minDate
|
|
1121
|
+
* new Date('2024-01-15 18:00:00'), // maxDate
|
|
1122
|
+
* false, // 12-hour format
|
|
1123
|
+
* { hour: 2, minute: 0, ampm: 'PM' },
|
|
1124
|
+
* false, // start time
|
|
1125
|
+
* );
|
|
1126
|
+
* // Result: [
|
|
1127
|
+
* // { value: 1, disabled: true }, // Before minDate
|
|
1128
|
+
* // { value: 2, disabled: false }, // Available
|
|
1129
|
+
* // { value: 6, disabled: true }, // After maxDate
|
|
1130
|
+
* // ]
|
|
1131
|
+
* ```
|
|
1132
|
+
*/
|
|
1133
|
+
getAvailableHours(date, minDate, maxDate, timePicker24Hour, timeValue, isEndTime = false, startDate) {
|
|
1134
|
+
if (!date)
|
|
1135
|
+
return [];
|
|
1136
|
+
const hours = timePicker24Hour
|
|
1137
|
+
? Array.from({ length: 24 }, (_, i) => i)
|
|
1138
|
+
: Array.from({ length: 12 }, (_, i) => i + 1);
|
|
1139
|
+
return hours.map((hour) => {
|
|
1140
|
+
let hour24 = hour;
|
|
1141
|
+
if (!timePicker24Hour) {
|
|
1142
|
+
hour24 =
|
|
1143
|
+
timeValue.ampm === 'PM' && hour !== 12
|
|
1144
|
+
? hour + 12
|
|
1145
|
+
: timeValue.ampm === 'AM' && hour === 12
|
|
1146
|
+
? 0
|
|
1147
|
+
: hour;
|
|
1148
|
+
}
|
|
1149
|
+
const testDate = new Date(date);
|
|
1150
|
+
testDate.setHours(hour24, 59, 59, 999);
|
|
1151
|
+
const testDateMin = new Date(date);
|
|
1152
|
+
testDateMin.setHours(hour24, 0, 0, 0);
|
|
1153
|
+
let disabled = false;
|
|
1154
|
+
if (minDate && testDate < minDate)
|
|
1155
|
+
disabled = true;
|
|
1156
|
+
if (maxDate && testDateMin > maxDate)
|
|
1157
|
+
disabled = true;
|
|
1158
|
+
// For end time, check against start date
|
|
1159
|
+
if (isEndTime && startDate && isSameDate(startDate, date, 'day')) {
|
|
1160
|
+
if (testDate < startDate)
|
|
1161
|
+
disabled = true;
|
|
1162
|
+
}
|
|
1163
|
+
return { value: hour, disabled };
|
|
1164
|
+
});
|
|
1165
|
+
}
|
|
1166
|
+
/**
|
|
1167
|
+
* Gets available minutes based on date and hour constraints.
|
|
1168
|
+
*
|
|
1169
|
+
* Returns an array of minute options with disabled state based on minimum/maximum
|
|
1170
|
+
* date restrictions, time picker increment, and start date constraints.
|
|
1171
|
+
*
|
|
1172
|
+
* @param date - Date to get minutes for
|
|
1173
|
+
* @param minDate - Minimum allowed date
|
|
1174
|
+
* @param maxDate - Maximum allowed date
|
|
1175
|
+
* @param timeValue - Current time value for hour context
|
|
1176
|
+
* @param timePickerIncrement - Minute increment step
|
|
1177
|
+
* @param timePicker24Hour - Whether using 24-hour format
|
|
1178
|
+
* @param isEndTime - Whether this is for end time selection
|
|
1179
|
+
* @param startDate - Start date for end time validation
|
|
1180
|
+
* @returns Array of minute options with availability status
|
|
1181
|
+
*
|
|
1182
|
+
* @example
|
|
1183
|
+
* ```typescript
|
|
1184
|
+
* const availableMinutes = this.timePickerService.getAvailableMinutes(
|
|
1185
|
+
* new Date('2024-01-15'),
|
|
1186
|
+
* new Date('2024-01-15 14:15:00'), // minDate
|
|
1187
|
+
* new Date('2024-01-15 14:45:00'), // maxDate
|
|
1188
|
+
* { hour: 14, minute: 30, ampm: 'PM' },
|
|
1189
|
+
* 15, // 15-minute increments
|
|
1190
|
+
* true, // 24-hour format
|
|
1191
|
+
* false, // start time
|
|
1192
|
+
* );
|
|
1193
|
+
* // Result: [
|
|
1194
|
+
* // { value: 0, disabled: true }, // Before minDate
|
|
1195
|
+
* // { value: 15, disabled: false }, // Available
|
|
1196
|
+
* // { value: 30, disabled: false }, // Available
|
|
1197
|
+
* // { value: 45, disabled: false }, // Available
|
|
1198
|
+
* // ]
|
|
1199
|
+
* ```
|
|
1200
|
+
*/
|
|
1201
|
+
getAvailableMinutes(date, minDate, maxDate, timeValue, timePickerIncrement, timePicker24Hour, isEndTime = false, startDate) {
|
|
1202
|
+
if (!date)
|
|
1203
|
+
return [];
|
|
1204
|
+
const minutes = [];
|
|
1205
|
+
for (let i = 0; i < 60; i += timePickerIncrement) {
|
|
1206
|
+
minutes.push(i);
|
|
1207
|
+
}
|
|
1208
|
+
// Convert current hour to 24h format
|
|
1209
|
+
let hour24 = timeValue.hour;
|
|
1210
|
+
if (!timePicker24Hour) {
|
|
1211
|
+
hour24 =
|
|
1212
|
+
timeValue.ampm === 'PM' && timeValue.hour !== 12
|
|
1213
|
+
? timeValue.hour + 12
|
|
1214
|
+
: timeValue.ampm === 'AM' && timeValue.hour === 12
|
|
1215
|
+
? 0
|
|
1216
|
+
: timeValue.hour;
|
|
1217
|
+
}
|
|
1218
|
+
return minutes.map((minute) => {
|
|
1219
|
+
const testDate = new Date(date);
|
|
1220
|
+
testDate.setHours(hour24, minute, 59, 999);
|
|
1221
|
+
const testDateMin = new Date(date);
|
|
1222
|
+
testDateMin.setHours(hour24, minute, 0, 0);
|
|
1223
|
+
let disabled = false;
|
|
1224
|
+
if (minDate && testDate < minDate)
|
|
1225
|
+
disabled = true;
|
|
1226
|
+
if (maxDate && testDateMin > maxDate)
|
|
1227
|
+
disabled = true;
|
|
1228
|
+
// For end time, check against start date
|
|
1229
|
+
if (isEndTime && startDate && isSameDate(startDate, date, 'day')) {
|
|
1230
|
+
const startHour = startDate.getHours();
|
|
1231
|
+
if (hour24 === startHour) {
|
|
1232
|
+
if (minute < startDate.getMinutes())
|
|
1233
|
+
disabled = true;
|
|
1234
|
+
}
|
|
1235
|
+
else if (hour24 < startHour) {
|
|
1236
|
+
disabled = true;
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
return { value: minute, disabled };
|
|
1240
|
+
});
|
|
1241
|
+
}
|
|
1242
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: NgxDatexTimePickerService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1243
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: NgxDatexTimePickerService });
|
|
1244
|
+
}
|
|
1245
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: NgxDatexTimePickerService, decorators: [{
|
|
1246
|
+
type: Injectable
|
|
1247
|
+
}] });
|
|
1248
|
+
|
|
1249
|
+
class NgxDatexCalendarService {
|
|
1250
|
+
updateMonthsInView(startDate, endDate, currentLeftMonth, currentRightMonth, singleDatePicker, linkedCalendars, maxDate, onLeftMonthChange, onRightMonthChange) {
|
|
1251
|
+
if (!startDate)
|
|
1252
|
+
return;
|
|
1253
|
+
if (endDate) {
|
|
1254
|
+
// Check if both dates are already visible
|
|
1255
|
+
if (!singleDatePicker && currentLeftMonth && currentRightMonth) {
|
|
1256
|
+
const startYearMonth = `${startDate.getFullYear()}-${String(startDate.getMonth() + 1).padStart(2, '0')}`;
|
|
1257
|
+
const endYearMonth = `${endDate.getFullYear()}-${String(endDate.getMonth() + 1).padStart(2, '0')}`;
|
|
1258
|
+
const leftYearMonth = `${currentLeftMonth.getFullYear()}-${String(currentLeftMonth.getMonth() + 1).padStart(2, '0')}`;
|
|
1259
|
+
const rightYearMonth = `${currentRightMonth.getFullYear()}-${String(currentRightMonth.getMonth() + 1).padStart(2, '0')}`;
|
|
1260
|
+
if ((startYearMonth === leftYearMonth || startYearMonth === rightYearMonth) &&
|
|
1261
|
+
(endYearMonth === leftYearMonth || endYearMonth === rightYearMonth)) {
|
|
1262
|
+
return;
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1265
|
+
// Set left calendar to start date month
|
|
1266
|
+
const newLeftMonth = new Date(startDate.getFullYear(), startDate.getMonth(), 1);
|
|
1267
|
+
onLeftMonthChange(newLeftMonth);
|
|
1268
|
+
if (!linkedCalendars &&
|
|
1269
|
+
(endDate.getMonth() !== startDate.getMonth() ||
|
|
1270
|
+
endDate.getFullYear() !== startDate.getFullYear())) {
|
|
1271
|
+
// Show end date month on right
|
|
1272
|
+
const newRightMonth = new Date(endDate.getFullYear(), endDate.getMonth(), 1);
|
|
1273
|
+
onRightMonthChange(newRightMonth);
|
|
1274
|
+
}
|
|
1275
|
+
else {
|
|
1276
|
+
// Show next month on right
|
|
1277
|
+
const nextMonth = addMonths(newLeftMonth, 1);
|
|
1278
|
+
onRightMonthChange(nextMonth);
|
|
1279
|
+
}
|
|
1280
|
+
}
|
|
1281
|
+
else {
|
|
1282
|
+
// Only start date - navigate if not visible
|
|
1283
|
+
const startYearMonth = `${startDate.getFullYear()}-${String(startDate.getMonth() + 1).padStart(2, '0')}`;
|
|
1284
|
+
const leftYearMonth = currentLeftMonth
|
|
1285
|
+
? `${currentLeftMonth.getFullYear()}-${String(currentLeftMonth.getMonth() + 1).padStart(2, '0')}`
|
|
1286
|
+
: '';
|
|
1287
|
+
const rightYearMonth = currentRightMonth
|
|
1288
|
+
? `${currentRightMonth.getFullYear()}-${String(currentRightMonth.getMonth() + 1).padStart(2, '0')}`
|
|
1289
|
+
: '';
|
|
1290
|
+
if (leftYearMonth !== startYearMonth && rightYearMonth !== startYearMonth) {
|
|
1291
|
+
const newLeftMonth = new Date(startDate.getFullYear(), startDate.getMonth(), 1);
|
|
1292
|
+
onLeftMonthChange(newLeftMonth);
|
|
1293
|
+
const nextMonth = addMonths(newLeftMonth, 1);
|
|
1294
|
+
onRightMonthChange(nextMonth);
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
// Check max date limits
|
|
1298
|
+
if (maxDate && linkedCalendars && !singleDatePicker && currentRightMonth > maxDate) {
|
|
1299
|
+
const maxRightMonth = new Date(maxDate.getFullYear(), maxDate.getMonth(), 1);
|
|
1300
|
+
const maxLeftMonth = addMonths(maxRightMonth, -1);
|
|
1301
|
+
onRightMonthChange(maxRightMonth);
|
|
1302
|
+
onLeftMonthChange(maxLeftMonth);
|
|
1303
|
+
}
|
|
1304
|
+
}
|
|
1305
|
+
setStartDate(startDate, minDate, maxDate, timePicker, timePickerIncrement) {
|
|
1306
|
+
let newStartDate = new Date(startDate);
|
|
1307
|
+
if (!timePicker) {
|
|
1308
|
+
newStartDate = startOfDay(newStartDate);
|
|
1309
|
+
}
|
|
1310
|
+
if (timePicker && timePickerIncrement) {
|
|
1311
|
+
const minutes = newStartDate.getMinutes();
|
|
1312
|
+
const roundedMinutes = Math.round(minutes / timePickerIncrement) * timePickerIncrement;
|
|
1313
|
+
newStartDate.setMinutes(roundedMinutes);
|
|
1314
|
+
}
|
|
1315
|
+
if (minDate && isBeforeDate(newStartDate, minDate)) {
|
|
1316
|
+
newStartDate = new Date(minDate);
|
|
1317
|
+
if (timePicker && timePickerIncrement) {
|
|
1318
|
+
const minutes = newStartDate.getMinutes();
|
|
1319
|
+
const roundedMinutes = Math.round(minutes / timePickerIncrement) * timePickerIncrement;
|
|
1320
|
+
newStartDate.setMinutes(roundedMinutes);
|
|
1321
|
+
}
|
|
1322
|
+
}
|
|
1323
|
+
if (maxDate && newStartDate > maxDate) {
|
|
1324
|
+
newStartDate = new Date(maxDate);
|
|
1325
|
+
if (timePicker && timePickerIncrement) {
|
|
1326
|
+
const minutes = newStartDate.getMinutes();
|
|
1327
|
+
const flooredMinutes = Math.floor(minutes / timePickerIncrement) * timePickerIncrement;
|
|
1328
|
+
newStartDate.setMinutes(flooredMinutes);
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1331
|
+
return newStartDate;
|
|
1332
|
+
}
|
|
1333
|
+
setEndDate(endDate, startDate, maxDate, maxSpan, timePicker, timePickerIncrement) {
|
|
1334
|
+
let newEndDate = new Date(endDate);
|
|
1335
|
+
if (!timePicker) {
|
|
1336
|
+
newEndDate = endOfDay(newEndDate);
|
|
1337
|
+
}
|
|
1338
|
+
if (timePicker && timePickerIncrement) {
|
|
1339
|
+
const minutes = newEndDate.getMinutes();
|
|
1340
|
+
const roundedMinutes = Math.round(minutes / timePickerIncrement) * timePickerIncrement;
|
|
1341
|
+
newEndDate.setMinutes(roundedMinutes);
|
|
1342
|
+
}
|
|
1343
|
+
if (startDate && isBeforeDate(newEndDate, startDate)) {
|
|
1344
|
+
newEndDate = new Date(startDate);
|
|
1345
|
+
}
|
|
1346
|
+
if (maxDate && newEndDate > maxDate) {
|
|
1347
|
+
newEndDate = new Date(maxDate);
|
|
1348
|
+
}
|
|
1349
|
+
// Validate maxSpan
|
|
1350
|
+
if (maxSpan && startDate) {
|
|
1351
|
+
const maxLimit = addDays(startDate, this.getMaxSpanDays(maxSpan));
|
|
1352
|
+
if (newEndDate > maxLimit) {
|
|
1353
|
+
newEndDate = new Date(maxLimit);
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1356
|
+
return newEndDate;
|
|
1357
|
+
}
|
|
1358
|
+
getMaxSpanDays(maxSpan) {
|
|
1359
|
+
let days = 0;
|
|
1360
|
+
if (maxSpan['days'])
|
|
1361
|
+
days += maxSpan['days'];
|
|
1362
|
+
if (maxSpan['weeks'])
|
|
1363
|
+
days += maxSpan['weeks'] * 7;
|
|
1364
|
+
if (maxSpan['months'])
|
|
1365
|
+
days += maxSpan['months'] * 30;
|
|
1366
|
+
if (maxSpan['years'])
|
|
1367
|
+
days += maxSpan['years'] * 365;
|
|
1368
|
+
return days;
|
|
1369
|
+
}
|
|
1370
|
+
isDateDisabled(date, minDate, maxDate, maxSpan, currentStartDate, currentEndDate, singleDatePicker, isInvalidDateFn) {
|
|
1371
|
+
if (minDate && date < minDate)
|
|
1372
|
+
return true;
|
|
1373
|
+
if (maxDate && date > maxDate)
|
|
1374
|
+
return true;
|
|
1375
|
+
if (maxSpan && !singleDatePicker && currentStartDate && !currentEndDate) {
|
|
1376
|
+
const maxLimit = addDays(currentStartDate, this.getMaxSpanDays(maxSpan));
|
|
1377
|
+
if (date > maxLimit)
|
|
1378
|
+
return true;
|
|
1379
|
+
}
|
|
1380
|
+
if (isInvalidDateFn && isInvalidDateFn(date))
|
|
1381
|
+
return true;
|
|
1382
|
+
return false;
|
|
1383
|
+
}
|
|
1384
|
+
isDateSelected(date, currentStartDate, currentEndDate, singleDatePicker) {
|
|
1385
|
+
if (!currentStartDate)
|
|
1386
|
+
return false;
|
|
1387
|
+
if (singleDatePicker) {
|
|
1388
|
+
return isSameDate(date, currentStartDate, 'day');
|
|
1389
|
+
}
|
|
1390
|
+
return (isSameDate(date, currentStartDate, 'day') ||
|
|
1391
|
+
(currentEndDate !== null && isSameDate(date, currentEndDate, 'day')));
|
|
1392
|
+
}
|
|
1393
|
+
isDateInRange(date, currentStartDate, currentEndDate, singleDatePicker) {
|
|
1394
|
+
if (!currentStartDate || !currentEndDate || singleDatePicker)
|
|
1395
|
+
return false;
|
|
1396
|
+
return date > currentStartDate && date < currentEndDate;
|
|
1397
|
+
}
|
|
1398
|
+
isDateRangeStart(date, currentStartDate) {
|
|
1399
|
+
if (!currentStartDate)
|
|
1400
|
+
return false;
|
|
1401
|
+
return isSameDate(date, currentStartDate, 'day');
|
|
1402
|
+
}
|
|
1403
|
+
isDateRangeEnd(date, currentEndDate) {
|
|
1404
|
+
if (!currentEndDate)
|
|
1405
|
+
return false;
|
|
1406
|
+
return isSameDate(date, currentEndDate, 'day');
|
|
1407
|
+
}
|
|
1408
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: NgxDatexCalendarService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1409
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: NgxDatexCalendarService });
|
|
1410
|
+
}
|
|
1411
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: NgxDatexCalendarService, decorators: [{
|
|
1412
|
+
type: Injectable
|
|
1413
|
+
}] });
|
|
1414
|
+
|
|
1415
|
+
/**
|
|
1416
|
+
* @fileoverview NgxDatex - Advanced Angular Date Range Picker Component
|
|
1417
|
+
*
|
|
1418
|
+
* A comprehensive date picker component for Angular applications that supports:
|
|
1419
|
+
* - Single date and date range selection
|
|
1420
|
+
* - Time picker integration
|
|
1421
|
+
* - Predefined date ranges
|
|
1422
|
+
* - Multiple themes and localization
|
|
1423
|
+
* - Mobile-responsive design
|
|
1424
|
+
* - Accessibility features
|
|
1425
|
+
* - Form integration with ControlValueAccessor
|
|
1426
|
+
*
|
|
1427
|
+
* Based on the popular vanilla daterangepicker library but built specifically for Angular
|
|
1428
|
+
* with modern Angular patterns including signals, standalone components, and CDK overlays.
|
|
1429
|
+
*
|
|
1430
|
+
* @example
|
|
1431
|
+
* Basic usage:
|
|
1432
|
+
* ```html
|
|
1433
|
+
* <ngx-datex
|
|
1434
|
+
* [(ngModel)]="selectedRange"
|
|
1435
|
+
* [ranges]="predefinedRanges"
|
|
1436
|
+
* [locale]="spanishLocale">
|
|
1437
|
+
* </ngx-datex>
|
|
1438
|
+
* ```
|
|
1439
|
+
*
|
|
1440
|
+
* @example
|
|
1441
|
+
* Advanced configuration:
|
|
1442
|
+
* ```html
|
|
1443
|
+
* <ngx-datex
|
|
1444
|
+
* [singleDatePicker]="false"
|
|
1445
|
+
* [timePicker]="true"
|
|
1446
|
+
* [autoApply]="true"
|
|
1447
|
+
* [showDropdowns]="true"
|
|
1448
|
+
* [linkedCalendars]="true"
|
|
1449
|
+
* [minDate]="minDate"
|
|
1450
|
+
* [maxDate]="maxDate"
|
|
1451
|
+
* (dateChange)="onDateChange($event)"
|
|
1452
|
+
* (rangeChange)="onRangeChange($event)">
|
|
1453
|
+
* </ngx-datex>
|
|
1454
|
+
* ```
|
|
1455
|
+
*/
|
|
1456
|
+
/**
|
|
1457
|
+
* NgxDatex - Advanced Angular Date Range Picker Component
|
|
1458
|
+
*
|
|
1459
|
+
* A feature-rich, accessible date picker component that provides both single date
|
|
1460
|
+
* and date range selection capabilities. Built with Angular signals for optimal
|
|
1461
|
+
* performance and modern Angular patterns.
|
|
1462
|
+
*
|
|
1463
|
+
* ## Key Features
|
|
1464
|
+
* - 📅 Single date and date range selection
|
|
1465
|
+
* - ⏰ Optional time picker with 12/24 hour formats
|
|
1466
|
+
* - 🎨 Customizable themes and styling
|
|
1467
|
+
* - 🌍 Full internationalization support
|
|
1468
|
+
* - 📱 Mobile-responsive design
|
|
1469
|
+
* - ♿ WCAG accessibility compliant
|
|
1470
|
+
* - 🔧 Extensive configuration options
|
|
1471
|
+
* - 📋 Angular Forms integration (ControlValueAccessor)
|
|
1472
|
+
*
|
|
1473
|
+
* ## Basic Usage
|
|
1474
|
+
* ```html
|
|
1475
|
+
* <ngx-datex
|
|
1476
|
+
* [(ngModel)]="selectedRange"
|
|
1477
|
+
* [locale]="locale"
|
|
1478
|
+
* [ranges]="predefinedRanges">
|
|
1479
|
+
* </ngx-datex>
|
|
1480
|
+
* ```
|
|
1481
|
+
*
|
|
1482
|
+
* ## Advanced Configuration
|
|
1483
|
+
* ```typescript
|
|
1484
|
+
* // Component
|
|
1485
|
+
* export class MyComponent {
|
|
1486
|
+
* selectedRange: NgxDatexValue | null = null;
|
|
1487
|
+
*
|
|
1488
|
+
* customRanges = {
|
|
1489
|
+
* 'Today': [startOfDay(new Date()), endOfDay(new Date())],
|
|
1490
|
+
* 'Last 7 Days': [startOfDay(subDays(new Date(), 6)), endOfDay(new Date())]
|
|
1491
|
+
* };
|
|
1492
|
+
*
|
|
1493
|
+
* onDateChange(value: NgxDatexValue | null) {
|
|
1494
|
+
* console.log('Selected range:', value);
|
|
1495
|
+
* }
|
|
1496
|
+
* }
|
|
1497
|
+
* ```
|
|
1498
|
+
*
|
|
1499
|
+
* ```html
|
|
1500
|
+
* <!-- Template -->
|
|
1501
|
+
* <ngx-datex
|
|
1502
|
+
* [singleDatePicker]="false"
|
|
1503
|
+
* [timePicker]="true"
|
|
1504
|
+
* [autoApply]="false"
|
|
1505
|
+
* [showDropdowns]="true"
|
|
1506
|
+
* [ranges]="customRanges"
|
|
1507
|
+
* [minDate]="minDate"
|
|
1508
|
+
* [maxDate]="maxDate"
|
|
1509
|
+
* (dateChange)="onDateChange($event)"
|
|
1510
|
+
* (rangeChange)="onRangeChange($event)">
|
|
1511
|
+
* </ngx-datex>
|
|
1512
|
+
* ```
|
|
1513
|
+
*
|
|
1514
|
+
* @see {@link NgxDatexValue} for the value interface
|
|
1515
|
+
* @see {@link NgxDatexConfig} for configuration options
|
|
1516
|
+
* @see {@link NgxDatexLocale} for localization settings
|
|
1517
|
+
* @see {@link NgxDatexTheme} for theming options
|
|
1518
|
+
*/
|
|
1519
|
+
class NgxDatex {
|
|
1520
|
+
inputElement;
|
|
1521
|
+
calendarTemplate;
|
|
1522
|
+
// Services
|
|
1523
|
+
datexService = inject(NgxDatexService);
|
|
1524
|
+
overlayService = inject(NgxDatexOverlayService);
|
|
1525
|
+
timePickerService = inject(NgxDatexTimePickerService);
|
|
1526
|
+
calendarService = inject(NgxDatexCalendarService);
|
|
1527
|
+
platformId = inject(PLATFORM_ID);
|
|
1528
|
+
viewContainerRef = inject(ViewContainerRef);
|
|
1529
|
+
// Configuration inputs
|
|
1530
|
+
/**
|
|
1531
|
+
* General configuration options for the date picker.
|
|
1532
|
+
*
|
|
1533
|
+
* @example
|
|
1534
|
+
* ```typescript
|
|
1535
|
+
* const config: NgxDatexConfig = {
|
|
1536
|
+
* dateFormat: 'DD/MM/YYYY',
|
|
1537
|
+
* firstDayOfWeek: 1,
|
|
1538
|
+
* businessDaysOnly: true
|
|
1539
|
+
* };
|
|
1540
|
+
* ```
|
|
1541
|
+
*/
|
|
1542
|
+
config = input({}, ...(ngDevMode ? [{ debugName: "config" }] : []));
|
|
1543
|
+
/**
|
|
1544
|
+
* Localization settings for internationalization.
|
|
1545
|
+
* Controls language, date formats, and UI text.
|
|
1546
|
+
*
|
|
1547
|
+
* @default SPANISH_LOCALE
|
|
1548
|
+
*
|
|
1549
|
+
* @example
|
|
1550
|
+
* ```typescript
|
|
1551
|
+
* const frenchLocale: NgxDatexLocale = {
|
|
1552
|
+
* format: 'DD/MM/YYYY',
|
|
1553
|
+
* daysOfWeek: ['Lu', 'Ma', 'Me', 'Je', 'Ve', 'Sa', 'Di'],
|
|
1554
|
+
* applyLabel: 'Appliquer',
|
|
1555
|
+
* cancelLabel: 'Annuler'
|
|
1556
|
+
* };
|
|
1557
|
+
* ```
|
|
1558
|
+
*/
|
|
1559
|
+
locale = input(SPANISH_LOCALE, ...(ngDevMode ? [{ debugName: "locale" }] : []));
|
|
1560
|
+
/**
|
|
1561
|
+
* Theme configuration for styling the date picker.
|
|
1562
|
+
* Defines colors, typography, spacing, and visual appearance.
|
|
1563
|
+
*
|
|
1564
|
+
* @default MATERIAL_LIGHT_THEME
|
|
1565
|
+
*
|
|
1566
|
+
* @example
|
|
1567
|
+
* ```typescript
|
|
1568
|
+
* const darkTheme: NgxDatexTheme = {
|
|
1569
|
+
* name: 'dark',
|
|
1570
|
+
* colors: { primary: '#bb86fc', background: '#121212', ... }
|
|
1571
|
+
* };
|
|
1572
|
+
* ```
|
|
1573
|
+
*/
|
|
1574
|
+
theme = input(MATERIAL_LIGHT_THEME, ...(ngDevMode ? [{ debugName: "theme" }] : []));
|
|
1575
|
+
// Appearance inputs
|
|
1576
|
+
/**
|
|
1577
|
+
* Material form field appearance style.
|
|
1578
|
+
*
|
|
1579
|
+
* @default 'outline'
|
|
1580
|
+
*
|
|
1581
|
+
* @example
|
|
1582
|
+
* ```html
|
|
1583
|
+
* <ngx-datex appearance="fill"></ngx-datex>
|
|
1584
|
+
* <ngx-datex appearance="outline"></ngx-datex>
|
|
1585
|
+
* ```
|
|
1586
|
+
*/
|
|
1587
|
+
appearance = input('outline', ...(ngDevMode ? [{ debugName: "appearance" }] : []));
|
|
1588
|
+
/**
|
|
1589
|
+
* Controls when the form field label should float.
|
|
1590
|
+
*
|
|
1591
|
+
* @default 'auto'
|
|
1592
|
+
*
|
|
1593
|
+
* @example
|
|
1594
|
+
* ```html
|
|
1595
|
+
* <ngx-datex floatLabel="always"></ngx-datex>
|
|
1596
|
+
* <ngx-datex floatLabel="auto"></ngx-datex>
|
|
1597
|
+
* ```
|
|
1598
|
+
*/
|
|
1599
|
+
floatLabel = input('auto', ...(ngDevMode ? [{ debugName: "floatLabel" }] : []));
|
|
1600
|
+
/**
|
|
1601
|
+
* Label text displayed above the input field.
|
|
1602
|
+
*
|
|
1603
|
+
* @default ''
|
|
1604
|
+
*
|
|
1605
|
+
* @example
|
|
1606
|
+
* ```html
|
|
1607
|
+
* <ngx-datex label="Select Date Range"></ngx-datex>
|
|
1608
|
+
* ```
|
|
1609
|
+
*/
|
|
1610
|
+
label = input('', ...(ngDevMode ? [{ debugName: "label" }] : []));
|
|
1611
|
+
/**
|
|
1612
|
+
* Placeholder text shown when no date is selected.
|
|
1613
|
+
*
|
|
1614
|
+
* @default 'Seleccionar fecha'
|
|
1615
|
+
*
|
|
1616
|
+
* @example
|
|
1617
|
+
* ```html
|
|
1618
|
+
* <ngx-datex placeholder="Choose your dates"></ngx-datex>
|
|
1619
|
+
* ```
|
|
1620
|
+
*/
|
|
1621
|
+
placeholder = input('Seleccionar fecha', ...(ngDevMode ? [{ debugName: "placeholder" }] : []));
|
|
1622
|
+
/**
|
|
1623
|
+
* Material icon name for the calendar icon.
|
|
1624
|
+
*
|
|
1625
|
+
* @default 'calendar_today'
|
|
1626
|
+
*
|
|
1627
|
+
* @example
|
|
1628
|
+
* ```html
|
|
1629
|
+
* <ngx-datex calendarIcon="event" [showCalendarIcon]="true"></ngx-datex>
|
|
1630
|
+
* ```
|
|
1631
|
+
*/
|
|
1632
|
+
calendarIcon = input('calendar_today', ...(ngDevMode ? [{ debugName: "calendarIcon" }] : []));
|
|
1633
|
+
/**
|
|
1634
|
+
* Whether to display the calendar icon.
|
|
1635
|
+
*
|
|
1636
|
+
* @default false
|
|
1637
|
+
*
|
|
1638
|
+
* @example
|
|
1639
|
+
* ```html
|
|
1640
|
+
* <ngx-datex [showCalendarIcon]="true"></ngx-datex>
|
|
1641
|
+
* ```
|
|
1642
|
+
*/
|
|
1643
|
+
showCalendarIcon = input(false, ...(ngDevMode ? [{ debugName: "showCalendarIcon" }] : []));
|
|
1644
|
+
/**
|
|
1645
|
+
* Position of the calendar icon relative to the input.
|
|
1646
|
+
*
|
|
1647
|
+
* @default 'suffix'
|
|
1648
|
+
*
|
|
1649
|
+
* @example
|
|
1650
|
+
* ```html
|
|
1651
|
+
* <ngx-datex [showCalendarIcon]="true" calendarIconPosition="prefix"></ngx-datex>
|
|
1652
|
+
* ```
|
|
1653
|
+
*/
|
|
1654
|
+
calendarIconPosition = input('suffix', ...(ngDevMode ? [{ debugName: "calendarIconPosition" }] : []));
|
|
1655
|
+
/**
|
|
1656
|
+
* Whether to display a checkbox alongside the input.
|
|
1657
|
+
*
|
|
1658
|
+
* @default false
|
|
1659
|
+
*
|
|
1660
|
+
* @example
|
|
1661
|
+
* ```html
|
|
1662
|
+
* <ngx-datex [showCheckbox]="true" (checkboxChange)="onCheckboxChange($event)"></ngx-datex>
|
|
1663
|
+
* ```
|
|
1664
|
+
*/
|
|
1665
|
+
showCheckbox = input(false, ...(ngDevMode ? [{ debugName: "showCheckbox" }] : []));
|
|
1666
|
+
/**
|
|
1667
|
+
* Position of the checkbox relative to the input.
|
|
1668
|
+
*
|
|
1669
|
+
* @default 'prefix'
|
|
1670
|
+
*
|
|
1671
|
+
* @example
|
|
1672
|
+
* ```html
|
|
1673
|
+
* <ngx-datex [showCheckbox]="true" checkboxPosition="suffix"></ngx-datex>
|
|
1674
|
+
* ```
|
|
1675
|
+
*/
|
|
1676
|
+
checkboxPosition = input('prefix', ...(ngDevMode ? [{ debugName: "checkboxPosition" }] : []));
|
|
1677
|
+
/**
|
|
1678
|
+
* Whether the input field is readonly.
|
|
1679
|
+
* When true, users can only interact with the calendar popup.
|
|
1680
|
+
*
|
|
1681
|
+
* @default false
|
|
1682
|
+
*
|
|
1683
|
+
* @example
|
|
1684
|
+
* ```html
|
|
1685
|
+
* <ngx-datex [readonly]="true"></ngx-datex>
|
|
1686
|
+
* ```
|
|
1687
|
+
*/
|
|
1688
|
+
readonly = input(false, ...(ngDevMode ? [{ debugName: "readonly" }] : []));
|
|
1689
|
+
/**
|
|
1690
|
+
* Whether the entire component is disabled.
|
|
1691
|
+
*
|
|
1692
|
+
* @default false
|
|
1693
|
+
*
|
|
1694
|
+
* @example
|
|
1695
|
+
* ```html
|
|
1696
|
+
* <ngx-datex [disabled]="isFormDisabled"></ngx-datex>
|
|
1697
|
+
* ```
|
|
1698
|
+
*/
|
|
1699
|
+
disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : []));
|
|
1700
|
+
/**
|
|
1701
|
+
* Whether to show the calendar header with navigation controls.
|
|
1702
|
+
*
|
|
1703
|
+
* @default true
|
|
1704
|
+
*
|
|
1705
|
+
* @example
|
|
1706
|
+
* ```html
|
|
1707
|
+
* <ngx-datex [showHeader]="false"></ngx-datex>
|
|
1708
|
+
* ```
|
|
1709
|
+
*/
|
|
1710
|
+
showHeader = input(true, ...(ngDevMode ? [{ debugName: "showHeader" }] : []));
|
|
1711
|
+
/**
|
|
1712
|
+
* Whether to show the calendar footer with apply/cancel buttons.
|
|
1713
|
+
*
|
|
1714
|
+
* @default true
|
|
1715
|
+
*
|
|
1716
|
+
* @example
|
|
1717
|
+
* ```html
|
|
1718
|
+
* <ngx-datex [showFooter]="false" [autoApply]="true"></ngx-datex>
|
|
1719
|
+
* ```
|
|
1720
|
+
*/
|
|
1721
|
+
showFooter = input(true, ...(ngDevMode ? [{ debugName: "showFooter" }] : []));
|
|
1722
|
+
// Behavior inputs
|
|
1723
|
+
/**
|
|
1724
|
+
* Whether to use single date selection mode instead of date range selection.
|
|
1725
|
+
* When true, only one date can be selected at a time.
|
|
1726
|
+
*
|
|
1727
|
+
* @default false
|
|
1728
|
+
*
|
|
1729
|
+
* @example
|
|
1730
|
+
* ```html
|
|
1731
|
+
* <!-- Single date picker -->
|
|
1732
|
+
* <ngx-datex [singleDatePicker]="true"></ngx-datex>
|
|
1733
|
+
*
|
|
1734
|
+
* <!-- Date range picker (default) -->
|
|
1735
|
+
* <ngx-datex [singleDatePicker]="false"></ngx-datex>
|
|
1736
|
+
* ```
|
|
1737
|
+
*/
|
|
1738
|
+
singleDatePicker = input(false, ...(ngDevMode ? [{ debugName: "singleDatePicker" }] : []));
|
|
1739
|
+
/**
|
|
1740
|
+
* Whether to automatically apply the selection without requiring the Apply button.
|
|
1741
|
+
* When true, selections are applied immediately. Automatically disabled when timePicker is enabled.
|
|
1742
|
+
*
|
|
1743
|
+
* @default false
|
|
1744
|
+
*
|
|
1745
|
+
* @example
|
|
1746
|
+
* ```html
|
|
1747
|
+
* <!-- Auto-apply selections -->
|
|
1748
|
+
* <ngx-datex [autoApply]="true"></ngx-datex>
|
|
1749
|
+
*
|
|
1750
|
+
* <!-- Require Apply button -->
|
|
1751
|
+
* <ngx-datex [autoApply]="false" [showFooter]="true"></ngx-datex>
|
|
1752
|
+
* ```
|
|
1753
|
+
*/
|
|
1754
|
+
autoApply = input(false, ...(ngDevMode ? [{ debugName: "autoApply" }] : []));
|
|
1755
|
+
/**
|
|
1756
|
+
* Whether to show month and year dropdown selectors in the calendar header.
|
|
1757
|
+
* Allows quick navigation to distant dates.
|
|
1758
|
+
*
|
|
1759
|
+
* @default false
|
|
1760
|
+
*
|
|
1761
|
+
* @example
|
|
1762
|
+
* ```html
|
|
1763
|
+
* <ngx-datex [showDropdowns]="true"></ngx-datex>
|
|
1764
|
+
* ```
|
|
1765
|
+
*/
|
|
1766
|
+
showDropdowns = input(false, ...(ngDevMode ? [{ debugName: "showDropdowns" }] : []));
|
|
1767
|
+
/**
|
|
1768
|
+
* Whether to enable time selection alongside date selection.
|
|
1769
|
+
* Adds time picker controls to the calendar.
|
|
1770
|
+
*
|
|
1771
|
+
* @default false
|
|
1772
|
+
*
|
|
1773
|
+
* @example
|
|
1774
|
+
* ```html
|
|
1775
|
+
* <!-- Date and time picker -->
|
|
1776
|
+
* <ngx-datex [timePicker]="true" [timePicker24Hour]="true"></ngx-datex>
|
|
1777
|
+
*
|
|
1778
|
+
* <!-- Date only picker -->
|
|
1779
|
+
* <ngx-datex [timePicker]="false"></ngx-datex>
|
|
1780
|
+
* ```
|
|
1781
|
+
*/
|
|
1782
|
+
timePicker = input(false, ...(ngDevMode ? [{ debugName: "timePicker" }] : []));
|
|
1783
|
+
/**
|
|
1784
|
+
* Whether to use 24-hour time format instead of 12-hour AM/PM format.
|
|
1785
|
+
* Only applies when timePicker is enabled.
|
|
1786
|
+
*
|
|
1787
|
+
* @default true
|
|
1788
|
+
*
|
|
1789
|
+
* @example
|
|
1790
|
+
* ```html
|
|
1791
|
+
* <!-- 24-hour format: 14:30 -->
|
|
1792
|
+
* <ngx-datex [timePicker]="true" [timePicker24Hour]="true"></ngx-datex>
|
|
1793
|
+
*
|
|
1794
|
+
* <!-- 12-hour format: 2:30 PM -->
|
|
1795
|
+
* <ngx-datex [timePicker]="true" [timePicker24Hour]="false"></ngx-datex>
|
|
1796
|
+
* ```
|
|
1797
|
+
*/
|
|
1798
|
+
timePicker24Hour = input(true, ...(ngDevMode ? [{ debugName: "timePicker24Hour" }] : []));
|
|
1799
|
+
/**
|
|
1800
|
+
* The increment step for minute selection in the time picker.
|
|
1801
|
+
* Minutes will be rounded to the nearest increment.
|
|
1802
|
+
*
|
|
1803
|
+
* @default 1
|
|
1804
|
+
*
|
|
1805
|
+
* @example
|
|
1806
|
+
* ```html
|
|
1807
|
+
* <!-- 15-minute increments: 00, 15, 30, 45 -->
|
|
1808
|
+
* <ngx-datex [timePicker]="true" [timePickerIncrement]="15"></ngx-datex>
|
|
1809
|
+
*
|
|
1810
|
+
* <!-- 5-minute increments: 00, 05, 10, 15, 20... -->
|
|
1811
|
+
* <ngx-datex [timePicker]="true" [timePickerIncrement]="5"></ngx-datex>
|
|
1812
|
+
* ```
|
|
1813
|
+
*/
|
|
1814
|
+
timePickerIncrement = input(1, ...(ngDevMode ? [{ debugName: "timePickerIncrement" }] : []));
|
|
1815
|
+
/**
|
|
1816
|
+
* Whether to show seconds in the time picker.
|
|
1817
|
+
* Only applies when timePicker is enabled.
|
|
1818
|
+
*
|
|
1819
|
+
* @default false
|
|
1820
|
+
*
|
|
1821
|
+
* @example
|
|
1822
|
+
* ```html
|
|
1823
|
+
* <ngx-datex [timePicker]="true" [timePickerSeconds]="true"></ngx-datex>
|
|
1824
|
+
* ```
|
|
1825
|
+
*/
|
|
1826
|
+
timePickerSeconds = input(false, ...(ngDevMode ? [{ debugName: "timePickerSeconds" }] : []));
|
|
1827
|
+
/**
|
|
1828
|
+
* Whether the left and right calendars should be linked for navigation.
|
|
1829
|
+
* When true, navigating one calendar automatically updates the other.
|
|
1830
|
+
*
|
|
1831
|
+
* @default true
|
|
1832
|
+
*
|
|
1833
|
+
* @example
|
|
1834
|
+
* ```html
|
|
1835
|
+
* <!-- Linked navigation -->
|
|
1836
|
+
* <ngx-datex [linkedCalendars]="true"></ngx-datex>
|
|
1837
|
+
*
|
|
1838
|
+
* <!-- Independent navigation -->
|
|
1839
|
+
* <ngx-datex [linkedCalendars]="false"></ngx-datex>
|
|
1840
|
+
* ```
|
|
1841
|
+
*/
|
|
1842
|
+
linkedCalendars = input(true, ...(ngDevMode ? [{ debugName: "linkedCalendars" }] : []));
|
|
1843
|
+
/**
|
|
1844
|
+
* Whether to automatically update the input field with the selected value.
|
|
1845
|
+
* When false, the input remains unchanged until manually updated.
|
|
1846
|
+
*
|
|
1847
|
+
* @default true
|
|
1848
|
+
*
|
|
1849
|
+
* @example
|
|
1850
|
+
* ```html
|
|
1851
|
+
* <!-- Auto-update input -->
|
|
1852
|
+
* <ngx-datex [autoUpdateInput]="true"></ngx-datex>
|
|
1853
|
+
*
|
|
1854
|
+
* <!-- Manual input updates -->
|
|
1855
|
+
* <ngx-datex [autoUpdateInput]="false"></ngx-datex>
|
|
1856
|
+
* ```
|
|
1857
|
+
*/
|
|
1858
|
+
autoUpdateInput = input(true, ...(ngDevMode ? [{ debugName: "autoUpdateInput" }] : []));
|
|
1859
|
+
/**
|
|
1860
|
+
* Whether to always show both calendars even in single date picker mode.
|
|
1861
|
+
* Useful for consistent layout regardless of picker mode.
|
|
1862
|
+
*
|
|
1863
|
+
* @default false
|
|
1864
|
+
*
|
|
1865
|
+
* @example
|
|
1866
|
+
* ```html
|
|
1867
|
+
* <ngx-datex [singleDatePicker]="true" [alwaysShowCalendars]="true"></ngx-datex>
|
|
1868
|
+
* ```
|
|
1869
|
+
*/
|
|
1870
|
+
alwaysShowCalendars = input(false, ...(ngDevMode ? [{ debugName: "alwaysShowCalendars" }] : []));
|
|
1871
|
+
/**
|
|
1872
|
+
* Whether to show the "Custom Range" option in the predefined ranges list.
|
|
1873
|
+
* Only applies when ranges are configured.
|
|
1874
|
+
*
|
|
1875
|
+
* @default true
|
|
1876
|
+
*
|
|
1877
|
+
* @example
|
|
1878
|
+
* ```html
|
|
1879
|
+
* <!-- Show custom range option -->
|
|
1880
|
+
* <ngx-datex [ranges]="predefinedRanges" [showCustomRangeLabel]="true"></ngx-datex>
|
|
1881
|
+
*
|
|
1882
|
+
* <!-- Hide custom range option -->
|
|
1883
|
+
* <ngx-datex [ranges]="predefinedRanges" [showCustomRangeLabel]="false"></ngx-datex>
|
|
1884
|
+
* ```
|
|
1885
|
+
*/
|
|
1886
|
+
showCustomRangeLabel = input(true, ...(ngDevMode ? [{ debugName: "showCustomRangeLabel" }] : []));
|
|
1887
|
+
// Computed autoApply following vanilla daterangepicker logic
|
|
1888
|
+
effectiveAutoApply = computed(() => {
|
|
1889
|
+
const autoApplyInput = this.autoApply();
|
|
1890
|
+
const timePickerEnabled = this.timePicker();
|
|
1891
|
+
return timePickerEnabled ? false : autoApplyInput;
|
|
1892
|
+
}, ...(ngDevMode ? [{ debugName: "effectiveAutoApply" }] : []));
|
|
1893
|
+
// Date range inputs
|
|
1894
|
+
/**
|
|
1895
|
+
* The initial start date for the date picker.
|
|
1896
|
+
* If not provided, defaults to today.
|
|
1897
|
+
*
|
|
1898
|
+
* @default null
|
|
1899
|
+
*
|
|
1900
|
+
* @example
|
|
1901
|
+
* ```typescript
|
|
1902
|
+
* const startDate = new Date('2024-01-01');
|
|
1903
|
+
* ```
|
|
1904
|
+
*
|
|
1905
|
+
* ```html
|
|
1906
|
+
* <ngx-datex [startDate]="startDate" [endDate]="endDate"></ngx-datex>
|
|
1907
|
+
* ```
|
|
1908
|
+
*/
|
|
1909
|
+
startDate = input(null, ...(ngDevMode ? [{ debugName: "startDate" }] : []));
|
|
1910
|
+
/**
|
|
1911
|
+
* The initial end date for the date picker.
|
|
1912
|
+
* If not provided, defaults to the start date (single date) or today (range).
|
|
1913
|
+
*
|
|
1914
|
+
* @default null
|
|
1915
|
+
*
|
|
1916
|
+
* @example
|
|
1917
|
+
* ```typescript
|
|
1918
|
+
* const endDate = new Date('2024-01-31');
|
|
1919
|
+
* ```
|
|
1920
|
+
*
|
|
1921
|
+
* ```html
|
|
1922
|
+
* <ngx-datex [startDate]="startDate" [endDate]="endDate"></ngx-datex>
|
|
1923
|
+
* ```
|
|
1924
|
+
*/
|
|
1925
|
+
endDate = input(null, ...(ngDevMode ? [{ debugName: "endDate" }] : []));
|
|
1926
|
+
/**
|
|
1927
|
+
* The minimum selectable date.
|
|
1928
|
+
* Users cannot select dates before this date.
|
|
1929
|
+
*
|
|
1930
|
+
* @default null
|
|
1931
|
+
*
|
|
1932
|
+
* @example
|
|
1933
|
+
* ```typescript
|
|
1934
|
+
* // Restrict to dates from today onwards
|
|
1935
|
+
* const minDate = new Date();
|
|
1936
|
+
* ```
|
|
1937
|
+
*
|
|
1938
|
+
* ```html
|
|
1939
|
+
* <ngx-datex [minDate]="minDate"></ngx-datex>
|
|
1940
|
+
* ```
|
|
1941
|
+
*/
|
|
1942
|
+
minDate = input(null, ...(ngDevMode ? [{ debugName: "minDate" }] : []));
|
|
1943
|
+
/**
|
|
1944
|
+
* The maximum selectable date.
|
|
1945
|
+
* Users cannot select dates after this date.
|
|
1946
|
+
*
|
|
1947
|
+
* @default null
|
|
1948
|
+
*
|
|
1949
|
+
* @example
|
|
1950
|
+
* ```typescript
|
|
1951
|
+
* // Restrict to dates within the next year
|
|
1952
|
+
* const maxDate = new Date();
|
|
1953
|
+
* maxDate.setFullYear(maxDate.getFullYear() + 1);
|
|
1954
|
+
* ```
|
|
1955
|
+
*
|
|
1956
|
+
* ```html
|
|
1957
|
+
* <ngx-datex [maxDate]="maxDate"></ngx-datex>
|
|
1958
|
+
* ```
|
|
1959
|
+
*/
|
|
1960
|
+
maxDate = input(null, ...(ngDevMode ? [{ debugName: "maxDate" }] : []));
|
|
1961
|
+
/**
|
|
1962
|
+
* The maximum allowed span between start and end dates.
|
|
1963
|
+
* Prevents users from selecting ranges that exceed the specified duration.
|
|
1964
|
+
*
|
|
1965
|
+
* @default null
|
|
1966
|
+
*
|
|
1967
|
+
* @example
|
|
1968
|
+
* ```typescript
|
|
1969
|
+
* // Limit to 30 days maximum
|
|
1970
|
+
* const maxSpan = { days: 30 };
|
|
1971
|
+
*
|
|
1972
|
+
* // Limit to 3 months maximum
|
|
1973
|
+
* const maxSpan = { months: 3 };
|
|
1974
|
+
*
|
|
1975
|
+
* // Limit to 1 year maximum
|
|
1976
|
+
* const maxSpan = { years: 1 };
|
|
1977
|
+
* ```
|
|
1978
|
+
*
|
|
1979
|
+
* ```html
|
|
1980
|
+
* <ngx-datex [maxSpan]="maxSpan"></ngx-datex>
|
|
1981
|
+
* ```
|
|
1982
|
+
*/
|
|
1983
|
+
maxSpan = input(null, ...(ngDevMode ? [{ debugName: "maxSpan" }] : []));
|
|
1984
|
+
/**
|
|
1985
|
+
* Whether to show week numbers in the calendar.
|
|
1986
|
+
* Displays ISO week numbers on the left side of each week row.
|
|
1987
|
+
*
|
|
1988
|
+
* @default false
|
|
1989
|
+
*
|
|
1990
|
+
* @example
|
|
1991
|
+
* ```html
|
|
1992
|
+
* <ngx-datex [showWeekNumbers]="true"></ngx-datex>
|
|
1993
|
+
* ```
|
|
1994
|
+
*/
|
|
1995
|
+
showWeekNumbers = input(false, ...(ngDevMode ? [{ debugName: "showWeekNumbers" }] : []));
|
|
1996
|
+
/**
|
|
1997
|
+
* Whether to show ISO week numbers instead of standard week numbers.
|
|
1998
|
+
* Only applies when showWeekNumbers is true.
|
|
1999
|
+
*
|
|
2000
|
+
* @default false
|
|
2001
|
+
*
|
|
2002
|
+
* @example
|
|
2003
|
+
* ```html
|
|
2004
|
+
* <ngx-datex [showWeekNumbers]="true" [showISOWeekNumbers]="true"></ngx-datex>
|
|
2005
|
+
* ```
|
|
2006
|
+
*/
|
|
2007
|
+
showISOWeekNumbers = input(false, ...(ngDevMode ? [{ debugName: "showISOWeekNumbers" }] : []));
|
|
2008
|
+
/**
|
|
2009
|
+
* CSS classes to apply to calendar buttons.
|
|
2010
|
+
* Used for styling navigation and action buttons.
|
|
2011
|
+
*
|
|
2012
|
+
* @default 'btn btn-sm'
|
|
2013
|
+
*
|
|
2014
|
+
* @example
|
|
2015
|
+
* ```html
|
|
2016
|
+
* <ngx-datex buttonClasses="btn btn-outline-primary btn-sm"></ngx-datex>
|
|
2017
|
+
* ```
|
|
2018
|
+
*/
|
|
2019
|
+
buttonClasses = input('btn btn-sm', ...(ngDevMode ? [{ debugName: "buttonClasses" }] : []));
|
|
2020
|
+
/**
|
|
2021
|
+
* CSS classes to apply to the Apply button.
|
|
2022
|
+
* Combined with buttonClasses for the final styling.
|
|
2023
|
+
*
|
|
2024
|
+
* @default 'btn-success'
|
|
2025
|
+
*
|
|
2026
|
+
* @example
|
|
2027
|
+
* ```html
|
|
2028
|
+
* <ngx-datex applyButtonClasses="btn-primary"></ngx-datex>
|
|
2029
|
+
* ```
|
|
2030
|
+
*/
|
|
2031
|
+
applyButtonClasses = input('btn-success', ...(ngDevMode ? [{ debugName: "applyButtonClasses" }] : []));
|
|
2032
|
+
/**
|
|
2033
|
+
* CSS classes to apply to the Cancel button.
|
|
2034
|
+
* Combined with buttonClasses for the final styling.
|
|
2035
|
+
*
|
|
2036
|
+
* @default 'btn-danger'
|
|
2037
|
+
*
|
|
2038
|
+
* @example
|
|
2039
|
+
* ```html
|
|
2040
|
+
* <ngx-datex cancelButtonClasses="btn-secondary"></ngx-datex>
|
|
2041
|
+
* ```
|
|
2042
|
+
*/
|
|
2043
|
+
cancelButtonClasses = input('btn-danger', ...(ngDevMode ? [{ debugName: "cancelButtonClasses" }] : []));
|
|
2044
|
+
/**
|
|
2045
|
+
* Function to determine if a specific date should be disabled.
|
|
2046
|
+
* Return true to disable the date, false to enable it.
|
|
2047
|
+
*
|
|
2048
|
+
* @default null
|
|
2049
|
+
*
|
|
2050
|
+
* @example
|
|
2051
|
+
* ```typescript
|
|
2052
|
+
* // Disable weekends
|
|
2053
|
+
* const isInvalidDate = (date: Date): boolean => {
|
|
2054
|
+
* const day = date.getDay();
|
|
2055
|
+
* return day === 0 || day === 6; // Sunday or Saturday
|
|
2056
|
+
* };
|
|
2057
|
+
*
|
|
2058
|
+
* // Disable specific dates
|
|
2059
|
+
* const blackoutDates = [new Date('2024-12-25'), new Date('2024-01-01')];
|
|
2060
|
+
* const isInvalidDate = (date: Date): boolean => {
|
|
2061
|
+
* return blackoutDates.some(blackout =>
|
|
2062
|
+
* date.toDateString() === blackout.toDateString()
|
|
2063
|
+
* );
|
|
2064
|
+
* };
|
|
2065
|
+
* ```
|
|
2066
|
+
*
|
|
2067
|
+
* ```html
|
|
2068
|
+
* <ngx-datex [isInvalidDate]="isInvalidDate"></ngx-datex>
|
|
2069
|
+
* ```
|
|
2070
|
+
*/
|
|
2071
|
+
isInvalidDate = input(null, ...(ngDevMode ? [{ debugName: "isInvalidDate" }] : []));
|
|
2072
|
+
/**
|
|
2073
|
+
* Function to apply custom CSS classes to specific dates.
|
|
2074
|
+
* Return a string or array of strings for CSS classes, or false for no custom styling.
|
|
2075
|
+
*
|
|
2076
|
+
* @default null
|
|
2077
|
+
*
|
|
2078
|
+
* @example
|
|
2079
|
+
* ```typescript
|
|
2080
|
+
* // Highlight holidays
|
|
2081
|
+
* const isCustomDate = (date: Date): string | string[] | false => {
|
|
2082
|
+
* const holidays = [new Date('2024-12-25'), new Date('2024-01-01')];
|
|
2083
|
+
* if (holidays.some(holiday => date.toDateString() === holiday.toDateString())) {
|
|
2084
|
+
* return 'holiday-date';
|
|
2085
|
+
* }
|
|
2086
|
+
* return false;
|
|
2087
|
+
* };
|
|
2088
|
+
*
|
|
2089
|
+
* // Multiple classes for different date types
|
|
2090
|
+
* const isCustomDate = (date: Date): string | string[] | false => {
|
|
2091
|
+
* if (date.getDay() === 0) return ['weekend', 'sunday'];
|
|
2092
|
+
* if (date.getDay() === 6) return ['weekend', 'saturday'];
|
|
2093
|
+
* return false;
|
|
2094
|
+
* };
|
|
2095
|
+
* ```
|
|
2096
|
+
*
|
|
2097
|
+
* ```html
|
|
2098
|
+
* <ngx-datex [isCustomDate]="isCustomDate"></ngx-datex>
|
|
2099
|
+
* ```
|
|
2100
|
+
*/
|
|
2101
|
+
isCustomDate = input(null, ...(ngDevMode ? [{ debugName: "isCustomDate" }] : []));
|
|
2102
|
+
/**
|
|
2103
|
+
* The minimum year to show in the year dropdown.
|
|
2104
|
+
* Only applies when showDropdowns is true.
|
|
2105
|
+
*
|
|
2106
|
+
* @default Current year - 100
|
|
2107
|
+
*
|
|
2108
|
+
* @example
|
|
2109
|
+
* ```html
|
|
2110
|
+
* <ngx-datex [showDropdowns]="true" [minYear]="2020" [maxYear]="2030"></ngx-datex>
|
|
2111
|
+
* ```
|
|
2112
|
+
*/
|
|
2113
|
+
minYear = input(new Date().getFullYear() - 100, ...(ngDevMode ? [{ debugName: "minYear" }] : []));
|
|
2114
|
+
/**
|
|
2115
|
+
* The maximum year to show in the year dropdown.
|
|
2116
|
+
* Only applies when showDropdowns is true.
|
|
2117
|
+
*
|
|
2118
|
+
* @default Current year + 100
|
|
2119
|
+
*
|
|
2120
|
+
* @example
|
|
2121
|
+
* ```html
|
|
2122
|
+
* <ngx-datex [showDropdowns]="true" [minYear]="2020" [maxYear]="2030"></ngx-datex>
|
|
2123
|
+
* ```
|
|
2124
|
+
*/
|
|
2125
|
+
maxYear = input(new Date().getFullYear() + 100, ...(ngDevMode ? [{ debugName: "maxYear" }] : []));
|
|
2126
|
+
/**
|
|
2127
|
+
* Predefined date ranges that users can quickly select.
|
|
2128
|
+
* Displayed as a sidebar list for easy access to common date ranges.
|
|
2129
|
+
*
|
|
2130
|
+
* @default DEFAULT_RANGES
|
|
2131
|
+
*
|
|
2132
|
+
* @example
|
|
2133
|
+
* ```typescript
|
|
2134
|
+
* const customRanges = {
|
|
2135
|
+
* 'Today': [startOfDay(new Date()), endOfDay(new Date())],
|
|
2136
|
+
* 'Yesterday': [
|
|
2137
|
+
* startOfDay(subDays(new Date(), 1)),
|
|
2138
|
+
* endOfDay(subDays(new Date(), 1))
|
|
2139
|
+
* ],
|
|
2140
|
+
* 'Last 7 Days': [
|
|
2141
|
+
* startOfDay(subDays(new Date(), 6)),
|
|
2142
|
+
* endOfDay(new Date())
|
|
2143
|
+
* ],
|
|
2144
|
+
* 'Last 30 Days': [
|
|
2145
|
+
* startOfDay(subDays(new Date(), 29)),
|
|
2146
|
+
* endOfDay(new Date())
|
|
2147
|
+
* ],
|
|
2148
|
+
* 'This Month': [
|
|
2149
|
+
* startOfMonth(new Date()),
|
|
2150
|
+
* endOfMonth(new Date())
|
|
2151
|
+
* ]
|
|
2152
|
+
* };
|
|
2153
|
+
* ```
|
|
2154
|
+
*
|
|
2155
|
+
* ```html
|
|
2156
|
+
* <ngx-datex [ranges]="customRanges"></ngx-datex>
|
|
2157
|
+
* ```
|
|
2158
|
+
*/
|
|
2159
|
+
ranges = input(DEFAULT_RANGES, ...(ngDevMode ? [{ debugName: "ranges" }] : []));
|
|
2160
|
+
// Positioning inputs
|
|
2161
|
+
/**
|
|
2162
|
+
* The horizontal alignment of the calendar overlay relative to the input.
|
|
2163
|
+
* Controls where the calendar appears horizontally.
|
|
2164
|
+
*
|
|
2165
|
+
* @default 'center'
|
|
2166
|
+
*
|
|
2167
|
+
* @example
|
|
2168
|
+
* ```html
|
|
2169
|
+
* <!-- Calendar opens to the left of the input -->
|
|
2170
|
+
* <ngx-datex opens="left"></ngx-datex>
|
|
2171
|
+
*
|
|
2172
|
+
* <!-- Calendar opens to the right of the input -->
|
|
2173
|
+
* <ngx-datex opens="right"></ngx-datex>
|
|
2174
|
+
*
|
|
2175
|
+
* <!-- Calendar opens centered on the input -->
|
|
2176
|
+
* <ngx-datex opens="center"></ngx-datex>
|
|
2177
|
+
* ```
|
|
2178
|
+
*/
|
|
2179
|
+
opens = input('center', ...(ngDevMode ? [{ debugName: "opens" }] : []));
|
|
2180
|
+
/**
|
|
2181
|
+
* The vertical alignment of the calendar overlay relative to the input.
|
|
2182
|
+
* Controls where the calendar appears vertically.
|
|
2183
|
+
*
|
|
2184
|
+
* @default 'auto'
|
|
2185
|
+
*
|
|
2186
|
+
* @example
|
|
2187
|
+
* ```html
|
|
2188
|
+
* <!-- Calendar always opens below the input -->
|
|
2189
|
+
* <ngx-datex drops="down"></ngx-datex>
|
|
2190
|
+
*
|
|
2191
|
+
* <!-- Calendar always opens above the input -->
|
|
2192
|
+
* <ngx-datex drops="up"></ngx-datex>
|
|
2193
|
+
*
|
|
2194
|
+
* <!-- Calendar opens in the best available space -->
|
|
2195
|
+
* <ngx-datex drops="auto"></ngx-datex>
|
|
2196
|
+
* ```
|
|
2197
|
+
*/
|
|
2198
|
+
drops = input('auto', ...(ngDevMode ? [{ debugName: "drops" }] : []));
|
|
2199
|
+
// Template inputs
|
|
2200
|
+
/**
|
|
2201
|
+
* Custom template for the calendar header.
|
|
2202
|
+
* Allows complete customization of the header area including navigation controls.
|
|
2203
|
+
*
|
|
2204
|
+
* @default null
|
|
2205
|
+
*
|
|
2206
|
+
* @example
|
|
2207
|
+
* ```html
|
|
2208
|
+
* <ngx-datex [headerTemplate]="customHeader">
|
|
2209
|
+
* </ngx-datex>
|
|
2210
|
+
*
|
|
2211
|
+
* <ng-template #customHeader>
|
|
2212
|
+
* <div class="custom-header">
|
|
2213
|
+
* <button (click)="previousMonth()">Previous</button>
|
|
2214
|
+
* <span>{{ currentMonth }}</span>
|
|
2215
|
+
* <button (click)="nextMonth()">Next</button>
|
|
2216
|
+
* </div>
|
|
2217
|
+
* </ng-template>
|
|
2218
|
+
* ```
|
|
2219
|
+
*/
|
|
2220
|
+
headerTemplate = input(null, ...(ngDevMode ? [{ debugName: "headerTemplate" }] : []));
|
|
2221
|
+
/**
|
|
2222
|
+
* Custom template for the calendar footer.
|
|
2223
|
+
* Allows complete customization of the footer area including action buttons.
|
|
2224
|
+
*
|
|
2225
|
+
* @default null
|
|
2226
|
+
*
|
|
2227
|
+
* @example
|
|
2228
|
+
* ```html
|
|
2229
|
+
* <ngx-datex [footerTemplate]="customFooter">
|
|
2230
|
+
* </ngx-datex>
|
|
2231
|
+
*
|
|
2232
|
+
* <ng-template #customFooter>
|
|
2233
|
+
* <div class="custom-footer">
|
|
2234
|
+
* <button (click)="clearSelection()">Clear</button>
|
|
2235
|
+
* <button (click)="applySelection()">Apply</button>
|
|
2236
|
+
* </div>
|
|
2237
|
+
* </ng-template>
|
|
2238
|
+
* ```
|
|
2239
|
+
*/
|
|
2240
|
+
footerTemplate = input(null, ...(ngDevMode ? [{ debugName: "footerTemplate" }] : []));
|
|
2241
|
+
/**
|
|
2242
|
+
* Custom template for individual calendar day cells.
|
|
2243
|
+
* Allows complete customization of how each date is displayed.
|
|
2244
|
+
*
|
|
2245
|
+
* @default null
|
|
2246
|
+
*
|
|
2247
|
+
* @example
|
|
2248
|
+
* ```html
|
|
2249
|
+
* <ngx-datex [dayTemplate]="customDay">
|
|
2250
|
+
* </ngx-datex>
|
|
2251
|
+
*
|
|
2252
|
+
* <ng-template #customDay let-date="date" let-isSelected="isSelected">
|
|
2253
|
+
* <div class="custom-day" [class.selected]="isSelected">
|
|
2254
|
+
* <span>{{ date.getDate() }}</span>
|
|
2255
|
+
* <small *ngIf="hasEvent(date)">•</small>
|
|
2256
|
+
* </div>
|
|
2257
|
+
* </ng-template>
|
|
2258
|
+
* ```
|
|
2259
|
+
*/
|
|
2260
|
+
dayTemplate = input(null, ...(ngDevMode ? [{ debugName: "dayTemplate" }] : []));
|
|
2261
|
+
// Accessibility inputs
|
|
2262
|
+
/**
|
|
2263
|
+
* ARIA label for the date picker input.
|
|
2264
|
+
* Provides accessible description for screen readers.
|
|
2265
|
+
*
|
|
2266
|
+
* @default 'Date picker'
|
|
2267
|
+
*
|
|
2268
|
+
* @example
|
|
2269
|
+
* ```html
|
|
2270
|
+
* <ngx-datex ariaLabel="Select booking dates"></ngx-datex>
|
|
2271
|
+
* <ngx-datex ariaLabel="Choose event date range"></ngx-datex>
|
|
2272
|
+
* ```
|
|
2273
|
+
*/
|
|
2274
|
+
ariaLabel = input('Date picker', ...(ngDevMode ? [{ debugName: "ariaLabel" }] : []));
|
|
2275
|
+
/**
|
|
2276
|
+
* ARIA described-by attribute for the date picker input.
|
|
2277
|
+
* References additional descriptive text for screen readers.
|
|
2278
|
+
*
|
|
2279
|
+
* @default ''
|
|
2280
|
+
*
|
|
2281
|
+
* @example
|
|
2282
|
+
* ```html
|
|
2283
|
+
* <ngx-datex ariaDescribedBy="date-help-text"></ngx-datex>
|
|
2284
|
+
* <div id="date-help-text">Select your preferred date range</div>
|
|
2285
|
+
* ```
|
|
2286
|
+
*/
|
|
2287
|
+
ariaDescribedBy = input('', ...(ngDevMode ? [{ debugName: "ariaDescribedBy" }] : []));
|
|
2288
|
+
// Outputs
|
|
2289
|
+
/**
|
|
2290
|
+
* Emitted when the selected date or date range changes.
|
|
2291
|
+
* Provides the complete NgxDatexValue with start and end dates.
|
|
2292
|
+
*
|
|
2293
|
+
* @example
|
|
2294
|
+
* ```html
|
|
2295
|
+
* <ngx-datex (dateChange)="onDateChange($event)"></ngx-datex>
|
|
2296
|
+
* ```
|
|
2297
|
+
*
|
|
2298
|
+
* ```typescript
|
|
2299
|
+
* onDateChange(value: NgxDatexValue | null) {
|
|
2300
|
+
* if (value) {
|
|
2301
|
+
* console.log('Start:', value.startDate);
|
|
2302
|
+
* console.log('End:', value.endDate);
|
|
2303
|
+
* }
|
|
2304
|
+
* }
|
|
2305
|
+
* ```
|
|
2306
|
+
*/
|
|
2307
|
+
dateChange = output();
|
|
2308
|
+
/**
|
|
2309
|
+
* Emitted when a date range is selected (not for single date picker).
|
|
2310
|
+
* Provides start and end dates separately for convenience.
|
|
2311
|
+
*
|
|
2312
|
+
* @example
|
|
2313
|
+
* ```html
|
|
2314
|
+
* <ngx-datex (rangeChange)="onRangeChange($event)"></ngx-datex>
|
|
2315
|
+
* ```
|
|
2316
|
+
*
|
|
2317
|
+
* ```typescript
|
|
2318
|
+
* onRangeChange(range: { startDate: Date; endDate: Date | null }) {
|
|
2319
|
+
* console.log('Range selected:', range.startDate, 'to', range.endDate);
|
|
2320
|
+
* }
|
|
2321
|
+
* ```
|
|
2322
|
+
*/
|
|
2323
|
+
rangeChange = output();
|
|
2324
|
+
/**
|
|
2325
|
+
* Emitted when the calendar overlay is opened.
|
|
2326
|
+
*
|
|
2327
|
+
* @example
|
|
2328
|
+
* ```html
|
|
2329
|
+
* <ngx-datex (openEvent)="onCalendarOpen()"></ngx-datex>
|
|
2330
|
+
* ```
|
|
2331
|
+
*
|
|
2332
|
+
* ```typescript
|
|
2333
|
+
* onCalendarOpen() {
|
|
2334
|
+
* console.log('Calendar opened');
|
|
2335
|
+
* // Perform actions when calendar opens
|
|
2336
|
+
* }
|
|
2337
|
+
* ```
|
|
2338
|
+
*/
|
|
2339
|
+
openEvent = output();
|
|
2340
|
+
/**
|
|
2341
|
+
* Emitted when the calendar overlay is closed.
|
|
2342
|
+
*
|
|
2343
|
+
* @example
|
|
2344
|
+
* ```html
|
|
2345
|
+
* <ngx-datex (closeEvent)="onCalendarClose()"></ngx-datex>
|
|
2346
|
+
* ```
|
|
2347
|
+
*
|
|
2348
|
+
* ```typescript
|
|
2349
|
+
* onCalendarClose() {
|
|
2350
|
+
* console.log('Calendar closed');
|
|
2351
|
+
* // Perform cleanup or validation
|
|
2352
|
+
* }
|
|
2353
|
+
* ```
|
|
2354
|
+
*/
|
|
2355
|
+
closeEvent = output();
|
|
2356
|
+
/**
|
|
2357
|
+
* Emitted when the displayed month changes in the calendar.
|
|
2358
|
+
*
|
|
2359
|
+
* @example
|
|
2360
|
+
* ```html
|
|
2361
|
+
* <ngx-datex (monthChange)="onMonthChange($event)"></ngx-datex>
|
|
2362
|
+
* ```
|
|
2363
|
+
*
|
|
2364
|
+
* ```typescript
|
|
2365
|
+
* onMonthChange(date: Date) {
|
|
2366
|
+
* console.log('Month changed to:', date.getMonth() + 1, date.getFullYear());
|
|
2367
|
+
* }
|
|
2368
|
+
* ```
|
|
2369
|
+
*/
|
|
2370
|
+
monthChange = output();
|
|
2371
|
+
/**
|
|
2372
|
+
* Emitted when the displayed year changes in the calendar.
|
|
2373
|
+
*
|
|
2374
|
+
* @example
|
|
2375
|
+
* ```html
|
|
2376
|
+
* <ngx-datex (yearChange)="onYearChange($event)"></ngx-datex>
|
|
2377
|
+
* ```
|
|
2378
|
+
*
|
|
2379
|
+
* ```typescript
|
|
2380
|
+
* onYearChange(year: number) {
|
|
2381
|
+
* console.log('Year changed to:', year);
|
|
2382
|
+
* }
|
|
2383
|
+
* ```
|
|
2384
|
+
*/
|
|
2385
|
+
yearChange = output();
|
|
2386
|
+
/**
|
|
2387
|
+
* Emitted when a date is hovered in the calendar.
|
|
2388
|
+
* Useful for showing preview information or highlighting date ranges.
|
|
2389
|
+
*
|
|
2390
|
+
* @example
|
|
2391
|
+
* ```html
|
|
2392
|
+
* <ngx-datex (dateHover)="onDateHover($event)"></ngx-datex>
|
|
2393
|
+
* ```
|
|
2394
|
+
*
|
|
2395
|
+
* ```typescript
|
|
2396
|
+
* onDateHover(date: Date) {
|
|
2397
|
+
* console.log('Hovering over:', date);
|
|
2398
|
+
* // Show preview or update UI
|
|
2399
|
+
* }
|
|
2400
|
+
* ```
|
|
2401
|
+
*/
|
|
2402
|
+
dateHover = output();
|
|
2403
|
+
/**
|
|
2404
|
+
* Emitted when a validation error occurs.
|
|
2405
|
+
* Provides error message and optional error code for handling.
|
|
2406
|
+
*
|
|
2407
|
+
* @example
|
|
2408
|
+
* ```html
|
|
2409
|
+
* <ngx-datex (validationError)="onValidationError($event)"></ngx-datex>
|
|
2410
|
+
* ```
|
|
2411
|
+
*
|
|
2412
|
+
* ```typescript
|
|
2413
|
+
* onValidationError(error: { error: string; errorCode?: string }) {
|
|
2414
|
+
* console.error('Validation error:', error.error);
|
|
2415
|
+
* if (error.errorCode) {
|
|
2416
|
+
* // Handle specific error types
|
|
2417
|
+
* switch (error.errorCode) {
|
|
2418
|
+
* case 'MIN_DATE':
|
|
2419
|
+
* // Handle minimum date error
|
|
2420
|
+
* break;
|
|
2421
|
+
* case 'MAX_DATE':
|
|
2422
|
+
* // Handle maximum date error
|
|
2423
|
+
* break;
|
|
2424
|
+
* }
|
|
2425
|
+
* }
|
|
2426
|
+
* }
|
|
2427
|
+
* ```
|
|
2428
|
+
*/
|
|
2429
|
+
validationError = output();
|
|
2430
|
+
/**
|
|
2431
|
+
* Emitted when the checkbox state changes (if showCheckbox is enabled).
|
|
2432
|
+
*
|
|
2433
|
+
* @example
|
|
2434
|
+
* ```html
|
|
2435
|
+
* <ngx-datex [showCheckbox]="true" (checkboxChange)="onCheckboxChange($event)"></ngx-datex>
|
|
2436
|
+
* ```
|
|
2437
|
+
*
|
|
2438
|
+
* ```typescript
|
|
2439
|
+
* onCheckboxChange(checked: boolean) {
|
|
2440
|
+
* console.log('Checkbox is now:', checked ? 'checked' : 'unchecked');
|
|
2441
|
+
* }
|
|
2442
|
+
* ```
|
|
2443
|
+
*/
|
|
2444
|
+
checkboxChange = output();
|
|
2445
|
+
// Internal state signals
|
|
2446
|
+
_isOpen = signal(false, ...(ngDevMode ? [{ debugName: "_isOpen" }] : []));
|
|
2447
|
+
_currentValue = signal(null, ...(ngDevMode ? [{ debugName: "_currentValue" }] : []));
|
|
2448
|
+
_leftCalendarMonth = signal(new Date(), ...(ngDevMode ? [{ debugName: "_leftCalendarMonth" }] : []));
|
|
2449
|
+
_rightCalendarMonth = signal(new Date(), ...(ngDevMode ? [{ debugName: "_rightCalendarMonth" }] : []));
|
|
2450
|
+
_hoverDate = signal(null, ...(ngDevMode ? [{ debugName: "_hoverDate" }] : []));
|
|
2451
|
+
_errorMessage = signal('', ...(ngDevMode ? [{ debugName: "_errorMessage" }] : []));
|
|
2452
|
+
_startTime = signal({ hour: 0, minute: 0, ampm: 'AM' }, ...(ngDevMode ? [{ debugName: "_startTime" }] : []));
|
|
2453
|
+
_endTime = signal({ hour: 0, minute: 0, ampm: 'AM' }, ...(ngDevMode ? [{ debugName: "_endTime" }] : []));
|
|
2454
|
+
_overlayPosition = signal(null, ...(ngDevMode ? [{ debugName: "_overlayPosition" }] : []));
|
|
2455
|
+
_inputFocused = signal(false, ...(ngDevMode ? [{ debugName: "_inputFocused" }] : []));
|
|
2456
|
+
_displayValue = signal('', ...(ngDevMode ? [{ debugName: "_displayValue" }] : []));
|
|
2457
|
+
_checkboxValue = signal(false, ...(ngDevMode ? [{ debugName: "_checkboxValue" }] : []));
|
|
2458
|
+
_leftCalendarMatrix = signal([], ...(ngDevMode ? [{ debugName: "_leftCalendarMatrix" }] : []));
|
|
2459
|
+
_rightCalendarMatrix = signal([], ...(ngDevMode ? [{ debugName: "_rightCalendarMatrix" }] : []));
|
|
2460
|
+
// Internal date properties following vanilla daterangepicker pattern
|
|
2461
|
+
_internalStartDate = new Date();
|
|
2462
|
+
_internalEndDate = null;
|
|
2463
|
+
oldStartDate = null;
|
|
2464
|
+
oldEndDate = null;
|
|
2465
|
+
previousRightTime = null;
|
|
2466
|
+
// Public getters for current dates
|
|
2467
|
+
get currentStartDate() {
|
|
2468
|
+
return this._internalStartDate;
|
|
2469
|
+
}
|
|
2470
|
+
set currentStartDate(value) {
|
|
2471
|
+
this._internalStartDate = value;
|
|
2472
|
+
}
|
|
2473
|
+
get currentEndDate() {
|
|
2474
|
+
return this._internalEndDate;
|
|
2475
|
+
}
|
|
2476
|
+
set currentEndDate(value) {
|
|
2477
|
+
this._internalEndDate = value;
|
|
2478
|
+
}
|
|
2479
|
+
// Computed signals for template
|
|
2480
|
+
isOpen = this._isOpen.asReadonly();
|
|
2481
|
+
currentValue = this._currentValue.asReadonly();
|
|
2482
|
+
displayValue = this._displayValue.asReadonly();
|
|
2483
|
+
hasStartDate = computed(() => !!this.currentStartDate, ...(ngDevMode ? [{ debugName: "hasStartDate" }] : []));
|
|
2484
|
+
hasEndDate = computed(() => !!this.currentEndDate, ...(ngDevMode ? [{ debugName: "hasEndDate" }] : []));
|
|
2485
|
+
leftCalendarMonth = this._leftCalendarMonth.asReadonly();
|
|
2486
|
+
rightCalendarMonth = this._rightCalendarMonth.asReadonly();
|
|
2487
|
+
leftCalendarMatrix = this._leftCalendarMatrix.asReadonly();
|
|
2488
|
+
rightCalendarMatrix = this._rightCalendarMatrix.asReadonly();
|
|
2489
|
+
errorMessage = this._errorMessage.asReadonly();
|
|
2490
|
+
hasError = computed(() => this._errorMessage().length > 0, ...(ngDevMode ? [{ debugName: "hasError" }] : []));
|
|
2491
|
+
checkboxValue = this._checkboxValue.asReadonly();
|
|
2492
|
+
// Calendar computed values
|
|
2493
|
+
leftCalendarMonthValue = computed(() => this._leftCalendarMonth().getMonth(), ...(ngDevMode ? [{ debugName: "leftCalendarMonthValue" }] : []));
|
|
2494
|
+
leftCalendarYearValue = computed(() => this._leftCalendarMonth().getFullYear(), ...(ngDevMode ? [{ debugName: "leftCalendarYearValue" }] : []));
|
|
2495
|
+
rightCalendarMonthValue = computed(() => this._rightCalendarMonth().getMonth(), ...(ngDevMode ? [{ debugName: "rightCalendarMonthValue" }] : []));
|
|
2496
|
+
rightCalendarYearValue = computed(() => this._rightCalendarMonth().getFullYear(), ...(ngDevMode ? [{ debugName: "rightCalendarYearValue" }] : []));
|
|
2497
|
+
// Locale and theme computed values
|
|
2498
|
+
monthNames = computed(() => this.locale().monthNames || SPANISH_LOCALE.monthNames || [], ...(ngDevMode ? [{ debugName: "monthNames" }] : []));
|
|
2499
|
+
daysOfWeek = computed(() => {
|
|
2500
|
+
const days = this.locale().daysOfWeek || SPANISH_LOCALE.daysOfWeek || [];
|
|
2501
|
+
return days.slice(0, 7);
|
|
2502
|
+
}, ...(ngDevMode ? [{ debugName: "daysOfWeek" }] : []));
|
|
2503
|
+
availableYears = computed(() => {
|
|
2504
|
+
const years = [];
|
|
2505
|
+
const minYear = this.minYear();
|
|
2506
|
+
const maxYear = this.maxYear();
|
|
2507
|
+
for (let year = minYear; year <= maxYear; year++) {
|
|
2508
|
+
years.push(year);
|
|
2509
|
+
}
|
|
2510
|
+
return years;
|
|
2511
|
+
}, ...(ngDevMode ? [{ debugName: "availableYears" }] : []));
|
|
2512
|
+
// Range computed values
|
|
2513
|
+
rangeEntries = computed(() => {
|
|
2514
|
+
const ranges = this.ranges();
|
|
2515
|
+
return Object.entries(ranges).map(([label, range]) => ({ label, range }));
|
|
2516
|
+
}, ...(ngDevMode ? [{ debugName: "rangeEntries" }] : []));
|
|
2517
|
+
showRanges = computed(() => {
|
|
2518
|
+
const ranges = this.ranges();
|
|
2519
|
+
return ranges && Object.keys(ranges).length > 0;
|
|
2520
|
+
}, ...(ngDevMode ? [{ debugName: "showRanges" }] : []));
|
|
2521
|
+
// Device and UI computed values
|
|
2522
|
+
isMobileDevice = computed(() => isPlatformBrowser(this.platformId) && isMobileDevice(), ...(ngDevMode ? [{ debugName: "isMobileDevice" }] : []));
|
|
2523
|
+
headerTitle = computed(() => this.singleDatePicker() ? 'Seleccionar fecha' : 'Seleccionar rango de fechas', ...(ngDevMode ? [{ debugName: "headerTitle" }] : []));
|
|
2524
|
+
canApplyValue = computed(() => {
|
|
2525
|
+
const startDate = this.currentStartDate;
|
|
2526
|
+
const endDate = this.currentEndDate;
|
|
2527
|
+
if (!startDate)
|
|
2528
|
+
return false;
|
|
2529
|
+
if (this.singleDatePicker())
|
|
2530
|
+
return !!startDate;
|
|
2531
|
+
return !!(startDate && endDate);
|
|
2532
|
+
}, ...(ngDevMode ? [{ debugName: "canApplyValue" }] : []));
|
|
2533
|
+
// Arrow positioning computed values
|
|
2534
|
+
arrowDirection = computed(() => {
|
|
2535
|
+
const position = this._overlayPosition();
|
|
2536
|
+
if (!position)
|
|
2537
|
+
return 'down';
|
|
2538
|
+
if (position.originY === 'top' && position.overlayY === 'bottom') {
|
|
2539
|
+
return 'up';
|
|
2540
|
+
}
|
|
2541
|
+
else if (position.originY === 'bottom' && position.overlayY === 'top') {
|
|
2542
|
+
return 'down';
|
|
2543
|
+
}
|
|
2544
|
+
else if (position.originX === 'end' && position.overlayX === 'start') {
|
|
2545
|
+
return 'left';
|
|
2546
|
+
}
|
|
2547
|
+
else if (position.originX === 'start' && position.overlayX === 'end') {
|
|
2548
|
+
return 'right';
|
|
2549
|
+
}
|
|
2550
|
+
return 'down';
|
|
2551
|
+
}, ...(ngDevMode ? [{ debugName: "arrowDirection" }] : []));
|
|
2552
|
+
arrowAlignment = computed(() => {
|
|
2553
|
+
const position = this._overlayPosition();
|
|
2554
|
+
if (!position)
|
|
2555
|
+
return 'center';
|
|
2556
|
+
if (this.arrowDirection() === 'up' || this.arrowDirection() === 'down') {
|
|
2557
|
+
if (position.overlayX === 'start')
|
|
2558
|
+
return 'start';
|
|
2559
|
+
if (position.overlayX === 'end')
|
|
2560
|
+
return 'end';
|
|
2561
|
+
return 'center';
|
|
2562
|
+
}
|
|
2563
|
+
else {
|
|
2564
|
+
if (position.overlayY === 'top')
|
|
2565
|
+
return 'start';
|
|
2566
|
+
if (position.overlayY === 'bottom')
|
|
2567
|
+
return 'end';
|
|
2568
|
+
return 'center';
|
|
2569
|
+
}
|
|
2570
|
+
}, ...(ngDevMode ? [{ debugName: "arrowAlignment" }] : []));
|
|
2571
|
+
// Formatted range display
|
|
2572
|
+
formattedSelectedRange = computed(() => {
|
|
2573
|
+
const startDate = this.currentStartDate;
|
|
2574
|
+
const endDate = this.currentEndDate;
|
|
2575
|
+
if (!startDate)
|
|
2576
|
+
return '';
|
|
2577
|
+
const format = this.locale().format || 'DD/MM/YYYY';
|
|
2578
|
+
const timeFormat = this.timePicker() ? (this.timePicker24Hour() ? ' HH:mm' : ' hh:mm A') : '';
|
|
2579
|
+
const fullFormat = format + timeFormat;
|
|
2580
|
+
const start = formatDateValue(startDate, fullFormat);
|
|
2581
|
+
if (this.singleDatePicker()) {
|
|
2582
|
+
return start;
|
|
2583
|
+
}
|
|
2584
|
+
if (!endDate) {
|
|
2585
|
+
return start;
|
|
2586
|
+
}
|
|
2587
|
+
const end = formatDateValue(endDate, fullFormat);
|
|
2588
|
+
return `${start} - ${end}`;
|
|
2589
|
+
}, ...(ngDevMode ? [{ debugName: "formattedSelectedRange" }] : []));
|
|
2590
|
+
// Current label for selected range
|
|
2591
|
+
currentLabel = computed(() => {
|
|
2592
|
+
const startDate = this.currentStartDate;
|
|
2593
|
+
const endDate = this.currentEndDate;
|
|
2594
|
+
if (!startDate)
|
|
2595
|
+
return '';
|
|
2596
|
+
if (!endDate)
|
|
2597
|
+
return '';
|
|
2598
|
+
let customRange = true;
|
|
2599
|
+
const ranges = this.ranges();
|
|
2600
|
+
for (const [label, [rangeStart, rangeEnd]] of Object.entries(ranges)) {
|
|
2601
|
+
let startMatches;
|
|
2602
|
+
let endMatches;
|
|
2603
|
+
if (this.timePicker()) {
|
|
2604
|
+
const format = this.timePickerSeconds() ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD HH:mm';
|
|
2605
|
+
const startFormatted = formatDateValue(startDate, format);
|
|
2606
|
+
const endFormatted = formatDateValue(endDate, format);
|
|
2607
|
+
const rangeStartFormatted = formatDateValue(rangeStart, format);
|
|
2608
|
+
const rangeEndFormatted = formatDateValue(rangeEnd, format);
|
|
2609
|
+
startMatches = startFormatted === rangeStartFormatted;
|
|
2610
|
+
endMatches = endFormatted === rangeEndFormatted;
|
|
2611
|
+
}
|
|
2612
|
+
else {
|
|
2613
|
+
startMatches = isSameDate(startDate, rangeStart, 'day');
|
|
2614
|
+
endMatches = isSameDate(endDate, rangeEnd, 'day');
|
|
2615
|
+
}
|
|
2616
|
+
if (startMatches && endMatches) {
|
|
2617
|
+
customRange = false;
|
|
2618
|
+
return label;
|
|
2619
|
+
}
|
|
2620
|
+
}
|
|
2621
|
+
if (customRange) {
|
|
2622
|
+
return this.locale().customRangeLabel || 'Rango Personalizado';
|
|
2623
|
+
}
|
|
2624
|
+
return '';
|
|
2625
|
+
}, ...(ngDevMode ? [{ debugName: "currentLabel" }] : []));
|
|
2626
|
+
isCustomRange = computed(() => {
|
|
2627
|
+
const startDate = this.currentStartDate;
|
|
2628
|
+
const endDate = this.currentEndDate;
|
|
2629
|
+
if (!startDate || !endDate)
|
|
2630
|
+
return false;
|
|
2631
|
+
const ranges = this.ranges();
|
|
2632
|
+
for (const [rangeStart, rangeEnd] of Object.values(ranges)) {
|
|
2633
|
+
let startMatches;
|
|
2634
|
+
let endMatches;
|
|
2635
|
+
if (this.timePicker()) {
|
|
2636
|
+
const format = this.timePickerSeconds() ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD HH:mm';
|
|
2637
|
+
const startFormatted = formatDateValue(startDate, format);
|
|
2638
|
+
const endFormatted = formatDateValue(endDate, format);
|
|
2639
|
+
const rangeStartFormatted = formatDateValue(rangeStart, format);
|
|
2640
|
+
const rangeEndFormatted = formatDateValue(rangeEnd, format);
|
|
2641
|
+
startMatches = startFormatted === rangeStartFormatted;
|
|
2642
|
+
endMatches = endFormatted === rangeEndFormatted;
|
|
2643
|
+
}
|
|
2644
|
+
else {
|
|
2645
|
+
startMatches = isSameDate(startDate, rangeStart, 'day');
|
|
2646
|
+
endMatches = isSameDate(endDate, rangeEnd, 'day');
|
|
2647
|
+
}
|
|
2648
|
+
if (startMatches && endMatches) {
|
|
2649
|
+
return false;
|
|
2650
|
+
}
|
|
2651
|
+
}
|
|
2652
|
+
return true;
|
|
2653
|
+
}, ...(ngDevMode ? [{ debugName: "isCustomRange" }] : []));
|
|
2654
|
+
// Time picker computed values
|
|
2655
|
+
selectedStartHour = computed(() => this._startTime().hour, ...(ngDevMode ? [{ debugName: "selectedStartHour" }] : []));
|
|
2656
|
+
selectedStartMinute = computed(() => this._startTime().minute, ...(ngDevMode ? [{ debugName: "selectedStartMinute" }] : []));
|
|
2657
|
+
selectedStartAmPm = computed(() => this._startTime().ampm, ...(ngDevMode ? [{ debugName: "selectedStartAmPm" }] : []));
|
|
2658
|
+
selectedEndHour = computed(() => this._endTime().hour, ...(ngDevMode ? [{ debugName: "selectedEndHour" }] : []));
|
|
2659
|
+
selectedEndMinute = computed(() => this._endTime().minute, ...(ngDevMode ? [{ debugName: "selectedEndMinute" }] : []));
|
|
2660
|
+
selectedEndAmPm = computed(() => this._endTime().ampm, ...(ngDevMode ? [{ debugName: "selectedEndAmPm" }] : []));
|
|
2661
|
+
hours24 = computed(() => Array.from({ length: 24 }, (_, i) => i), ...(ngDevMode ? [{ debugName: "hours24" }] : []));
|
|
2662
|
+
hours12 = computed(() => Array.from({ length: 12 }, (_, i) => i + 1), ...(ngDevMode ? [{ debugName: "hours12" }] : []));
|
|
2663
|
+
minutes = computed(() => {
|
|
2664
|
+
const increment = this.timePickerIncrement();
|
|
2665
|
+
const minutes = [];
|
|
2666
|
+
for (let i = 0; i < 60; i += increment) {
|
|
2667
|
+
minutes.push(i);
|
|
2668
|
+
}
|
|
2669
|
+
return minutes;
|
|
2670
|
+
}, ...(ngDevMode ? [{ debugName: "minutes" }] : []));
|
|
2671
|
+
// Available time options
|
|
2672
|
+
availableStartHours = computed(() => {
|
|
2673
|
+
return this.timePickerService.getAvailableHours(this.currentStartDate, this.minDate(), this.maxDate(), this.timePicker24Hour(), this._startTime(), false);
|
|
2674
|
+
}, ...(ngDevMode ? [{ debugName: "availableStartHours" }] : []));
|
|
2675
|
+
availableEndHours = computed(() => {
|
|
2676
|
+
return this.timePickerService.getAvailableHours(this.currentEndDate, this.minDate(), this.maxDate(), this.timePicker24Hour(), this._endTime(), true, this.currentStartDate);
|
|
2677
|
+
}, ...(ngDevMode ? [{ debugName: "availableEndHours" }] : []));
|
|
2678
|
+
availableStartMinutes = computed(() => {
|
|
2679
|
+
return this.timePickerService.getAvailableMinutes(this.currentStartDate, this.minDate(), this.maxDate(), this._startTime(), this.timePickerIncrement(), this.timePicker24Hour(), false);
|
|
2680
|
+
}, ...(ngDevMode ? [{ debugName: "availableStartMinutes" }] : []));
|
|
2681
|
+
availableEndMinutes = computed(() => {
|
|
2682
|
+
return this.timePickerService.getAvailableMinutes(this.currentEndDate, this.minDate(), this.maxDate(), this._endTime(), this.timePickerIncrement(), this.timePicker24Hour(), true, this.currentStartDate);
|
|
2683
|
+
}, ...(ngDevMode ? [{ debugName: "availableEndMinutes" }] : []));
|
|
2684
|
+
// ControlValueAccessor implementation
|
|
2685
|
+
onChange = () => {
|
|
2686
|
+
// Implementation provided by registerOnChange
|
|
2687
|
+
};
|
|
2688
|
+
onTouched = () => {
|
|
2689
|
+
// Implementation provided by registerOnTouched
|
|
2690
|
+
};
|
|
2691
|
+
constructor() {
|
|
2692
|
+
afterNextRender(() => {
|
|
2693
|
+
this.initializeComponent();
|
|
2694
|
+
});
|
|
2695
|
+
}
|
|
2696
|
+
ngOnInit() {
|
|
2697
|
+
this.datexService.updateConfig(this.config());
|
|
2698
|
+
this.datexService.setLocale(this.locale());
|
|
2699
|
+
}
|
|
2700
|
+
ngOnDestroy() {
|
|
2701
|
+
this._isOpen.set(false);
|
|
2702
|
+
this.overlayService.disposeOverlay();
|
|
2703
|
+
}
|
|
2704
|
+
// ControlValueAccessor methods
|
|
2705
|
+
writeValue(value) {
|
|
2706
|
+
if (value) {
|
|
2707
|
+
this.currentStartDate = new Date(value.startDate);
|
|
2708
|
+
this.currentEndDate = value.endDate ? new Date(value.endDate) : null;
|
|
2709
|
+
this._currentValue.set(value);
|
|
2710
|
+
if (value.startDate) {
|
|
2711
|
+
this.updateMonthsInView();
|
|
2712
|
+
}
|
|
2713
|
+
if (this.timePicker()) {
|
|
2714
|
+
this.updateTimeSignalsFromDate(value.startDate, 'start');
|
|
2715
|
+
if (value.endDate) {
|
|
2716
|
+
this.updateTimeSignalsFromDate(value.endDate, 'end');
|
|
2717
|
+
}
|
|
2718
|
+
}
|
|
2719
|
+
this.updateElement();
|
|
2720
|
+
}
|
|
2721
|
+
else {
|
|
2722
|
+
this.initializeDefaultDates();
|
|
2723
|
+
}
|
|
2724
|
+
}
|
|
2725
|
+
registerOnChange(fn) {
|
|
2726
|
+
this.onChange = fn;
|
|
2727
|
+
}
|
|
2728
|
+
registerOnTouched(fn) {
|
|
2729
|
+
this.onTouched = fn;
|
|
2730
|
+
}
|
|
2731
|
+
setDisabledState(isDisabled) {
|
|
2732
|
+
// Implementation for disabled state
|
|
2733
|
+
void isDisabled;
|
|
2734
|
+
}
|
|
2735
|
+
// Public methods
|
|
2736
|
+
/**
|
|
2737
|
+
* Toggles the calendar visibility.
|
|
2738
|
+
* Opens the calendar if closed, closes it if open.
|
|
2739
|
+
* Does nothing if the component is disabled or readonly.
|
|
2740
|
+
*
|
|
2741
|
+
* @example
|
|
2742
|
+
* ```typescript
|
|
2743
|
+
* // In component
|
|
2744
|
+
* @ViewChild(NgxDatex) datePicker!: NgxDatex;
|
|
2745
|
+
*
|
|
2746
|
+
* togglePicker() {
|
|
2747
|
+
* this.datePicker.toggle();
|
|
2748
|
+
* }
|
|
2749
|
+
* ```
|
|
2750
|
+
*/
|
|
2751
|
+
toggle() {
|
|
2752
|
+
if (this.disabled() || this.readonly())
|
|
2753
|
+
return;
|
|
2754
|
+
if (this._isOpen()) {
|
|
2755
|
+
this.closeCalendar();
|
|
2756
|
+
}
|
|
2757
|
+
else {
|
|
2758
|
+
this.openCalendar();
|
|
2759
|
+
}
|
|
2760
|
+
}
|
|
2761
|
+
/**
|
|
2762
|
+
* Opens the calendar overlay.
|
|
2763
|
+
* Saves current state for potential cancellation and initializes the calendar view.
|
|
2764
|
+
*
|
|
2765
|
+
* @example
|
|
2766
|
+
* ```typescript
|
|
2767
|
+
* // Programmatically open the calendar
|
|
2768
|
+
* this.datePicker.openCalendar();
|
|
2769
|
+
* ```
|
|
2770
|
+
*/
|
|
2771
|
+
openCalendar() {
|
|
2772
|
+
if (this.disabled() || this.readonly() || this._isOpen())
|
|
2773
|
+
return;
|
|
2774
|
+
// Save old values for potential cancellation
|
|
2775
|
+
this.oldStartDate = new Date(this.currentStartDate);
|
|
2776
|
+
this.oldEndDate = this.currentEndDate ? new Date(this.currentEndDate) : null;
|
|
2777
|
+
this.previousRightTime = this.currentEndDate ? new Date(this.currentEndDate) : null;
|
|
2778
|
+
this.createOverlay();
|
|
2779
|
+
this._isOpen.set(true);
|
|
2780
|
+
if (this.timePicker()) {
|
|
2781
|
+
this.updateTimeSignalsFromDate(this.currentStartDate, 'start');
|
|
2782
|
+
if (this.currentEndDate) {
|
|
2783
|
+
this.updateTimeSignalsFromDate(this.currentEndDate, 'end');
|
|
2784
|
+
}
|
|
2785
|
+
}
|
|
2786
|
+
this.updateView();
|
|
2787
|
+
this.openEvent.emit();
|
|
2788
|
+
}
|
|
2789
|
+
/**
|
|
2790
|
+
* Closes the calendar overlay.
|
|
2791
|
+
* Handles incomplete selections and emits change events if values have changed.
|
|
2792
|
+
*
|
|
2793
|
+
* @example
|
|
2794
|
+
* ```typescript
|
|
2795
|
+
* // Programmatically close the calendar
|
|
2796
|
+
* this.datePicker.closeCalendar();
|
|
2797
|
+
* ```
|
|
2798
|
+
*/
|
|
2799
|
+
closeCalendar() {
|
|
2800
|
+
this._isOpen.set(false);
|
|
2801
|
+
this._hoverDate.set(null);
|
|
2802
|
+
// Handle incomplete selection
|
|
2803
|
+
if (!this.currentEndDate && this.oldStartDate && this.oldEndDate) {
|
|
2804
|
+
this.currentStartDate = this.oldStartDate;
|
|
2805
|
+
this.currentEndDate = this.oldEndDate;
|
|
2806
|
+
this.updateCurrentValue();
|
|
2807
|
+
}
|
|
2808
|
+
if (this.oldStartDate && this.oldEndDate) {
|
|
2809
|
+
const hasChanges = !isSameDate(this.currentStartDate, this.oldStartDate, 'day') ||
|
|
2810
|
+
(this.currentEndDate && !isSameDate(this.currentEndDate, this.oldEndDate, 'day'));
|
|
2811
|
+
if (hasChanges && this.currentEndDate) {
|
|
2812
|
+
this.emitValueChange();
|
|
2813
|
+
}
|
|
2814
|
+
}
|
|
2815
|
+
this.updateElement();
|
|
2816
|
+
this.overlayService.closeOverlay();
|
|
2817
|
+
this.closeEvent.emit();
|
|
2818
|
+
}
|
|
2819
|
+
/**
|
|
2820
|
+
* Applies the current selection and closes the calendar.
|
|
2821
|
+
* Only works if there's a valid selection to apply.
|
|
2822
|
+
*
|
|
2823
|
+
* @example
|
|
2824
|
+
* ```typescript
|
|
2825
|
+
* // Apply current selection programmatically
|
|
2826
|
+
* if (this.datePicker.canApplyValue()) {
|
|
2827
|
+
* this.datePicker.apply();
|
|
2828
|
+
* }
|
|
2829
|
+
* ```
|
|
2830
|
+
*/
|
|
2831
|
+
apply() {
|
|
2832
|
+
if (!this.canApplyValue())
|
|
2833
|
+
return;
|
|
2834
|
+
this.updateElement();
|
|
2835
|
+
this.closeCalendar();
|
|
2836
|
+
}
|
|
2837
|
+
/**
|
|
2838
|
+
* Cancels the current selection and reverts to the previous values.
|
|
2839
|
+
* Closes the calendar without applying changes.
|
|
2840
|
+
*
|
|
2841
|
+
* @example
|
|
2842
|
+
* ```typescript
|
|
2843
|
+
* // Cancel current selection
|
|
2844
|
+
* this.datePicker.cancel();
|
|
2845
|
+
* ```
|
|
2846
|
+
*/
|
|
2847
|
+
cancel() {
|
|
2848
|
+
if (this.oldStartDate && this.oldEndDate) {
|
|
2849
|
+
this.currentStartDate = this.oldStartDate;
|
|
2850
|
+
this.currentEndDate = this.oldEndDate;
|
|
2851
|
+
this.updateCurrentValue();
|
|
2852
|
+
}
|
|
2853
|
+
this.closeCalendar();
|
|
2854
|
+
}
|
|
2855
|
+
/**
|
|
2856
|
+
* Selects a specific date in the calendar.
|
|
2857
|
+
* Handles both single date and range selection logic.
|
|
2858
|
+
*
|
|
2859
|
+
* @param date - The date to select
|
|
2860
|
+
*
|
|
2861
|
+
* @example
|
|
2862
|
+
* ```typescript
|
|
2863
|
+
* // Programmatically select a date
|
|
2864
|
+
* const today = new Date();
|
|
2865
|
+
* this.datePicker.selectDate(today);
|
|
2866
|
+
* ```
|
|
2867
|
+
*/
|
|
2868
|
+
selectDate(date) {
|
|
2869
|
+
if (this.isDisabled(date))
|
|
2870
|
+
return;
|
|
2871
|
+
const validation = this.datexService.validateDate(date);
|
|
2872
|
+
if (!validation.isValid) {
|
|
2873
|
+
this._errorMessage.set(validation.error || '');
|
|
2874
|
+
this.validationError.emit({
|
|
2875
|
+
error: validation.error || '',
|
|
2876
|
+
errorCode: validation.errorCode,
|
|
2877
|
+
});
|
|
2878
|
+
return;
|
|
2879
|
+
}
|
|
2880
|
+
this._errorMessage.set('');
|
|
2881
|
+
this._hoverDate.set(null);
|
|
2882
|
+
if (this.singleDatePicker()) {
|
|
2883
|
+
this.setStartDate(date);
|
|
2884
|
+
this.setEndDate(date);
|
|
2885
|
+
if (this.effectiveAutoApply()) {
|
|
2886
|
+
this.emitValueChange();
|
|
2887
|
+
}
|
|
2888
|
+
if (!this.timePicker() && this.effectiveAutoApply()) {
|
|
2889
|
+
this.closeCalendar();
|
|
2890
|
+
}
|
|
2891
|
+
}
|
|
2892
|
+
else {
|
|
2893
|
+
// Range selection logic following vanilla daterangepicker
|
|
2894
|
+
if (this.currentEndDate ||
|
|
2895
|
+
(this.currentStartDate && isBeforeDate(date, this.currentStartDate))) {
|
|
2896
|
+
// Picking start
|
|
2897
|
+
const startDate = new Date(date);
|
|
2898
|
+
if (this.timePicker()) {
|
|
2899
|
+
const startTime = this._startTime();
|
|
2900
|
+
let hour = startTime.hour;
|
|
2901
|
+
if (!this.timePicker24Hour()) {
|
|
2902
|
+
if (startTime.ampm === 'PM' && hour < 12)
|
|
2903
|
+
hour += 12;
|
|
2904
|
+
if (startTime.ampm === 'AM' && hour === 12)
|
|
2905
|
+
hour = 0;
|
|
2906
|
+
}
|
|
2907
|
+
startDate.setHours(hour, startTime.minute, 0, 0);
|
|
2908
|
+
}
|
|
2909
|
+
this.currentEndDate = null;
|
|
2910
|
+
this.setStartDate(startDate);
|
|
2911
|
+
}
|
|
2912
|
+
else {
|
|
2913
|
+
// Picking end
|
|
2914
|
+
const endDate = new Date(date);
|
|
2915
|
+
if (this.timePicker()) {
|
|
2916
|
+
const endTime = this._endTime();
|
|
2917
|
+
let hour = endTime.hour;
|
|
2918
|
+
if (!this.timePicker24Hour()) {
|
|
2919
|
+
if (endTime.ampm === 'PM' && hour < 12)
|
|
2920
|
+
hour += 12;
|
|
2921
|
+
if (endTime.ampm === 'AM' && hour === 12)
|
|
2922
|
+
hour = 0;
|
|
2923
|
+
}
|
|
2924
|
+
endDate.setHours(hour, endTime.minute, 0, 0);
|
|
2925
|
+
}
|
|
2926
|
+
this.setEndDate(endDate);
|
|
2927
|
+
if (this.effectiveAutoApply()) {
|
|
2928
|
+
this.emitValueChange();
|
|
2929
|
+
this.closeCalendar();
|
|
2930
|
+
}
|
|
2931
|
+
}
|
|
2932
|
+
}
|
|
2933
|
+
this.updateView();
|
|
2934
|
+
}
|
|
2935
|
+
/**
|
|
2936
|
+
* Selects a predefined date range by label.
|
|
2937
|
+
*
|
|
2938
|
+
* @param label - The label of the range to select (must exist in ranges configuration)
|
|
2939
|
+
*
|
|
2940
|
+
* @example
|
|
2941
|
+
* ```typescript
|
|
2942
|
+
* // Select "Last 7 days" range
|
|
2943
|
+
* this.datePicker.selectRange('Last 7 days');
|
|
2944
|
+
* ```
|
|
2945
|
+
*/
|
|
2946
|
+
selectRange(label) {
|
|
2947
|
+
const ranges = this.ranges();
|
|
2948
|
+
const range = ranges[label];
|
|
2949
|
+
if (!range)
|
|
2950
|
+
return;
|
|
2951
|
+
const [rangeStart, rangeEnd] = range;
|
|
2952
|
+
this.currentStartDate = startOfDay(new Date(rangeStart));
|
|
2953
|
+
this.currentEndDate = endOfDay(new Date(rangeEnd));
|
|
2954
|
+
this.updateCurrentValue();
|
|
2955
|
+
this.updateMonthsInView();
|
|
2956
|
+
this.apply();
|
|
2957
|
+
}
|
|
2958
|
+
selectCustomRange() {
|
|
2959
|
+
// Custom range selection logic - mainly visual
|
|
2960
|
+
}
|
|
2961
|
+
// Event handlers
|
|
2962
|
+
onInputClick() {
|
|
2963
|
+
if (this.readonly())
|
|
2964
|
+
return;
|
|
2965
|
+
if (this._inputFocused() && this._isOpen())
|
|
2966
|
+
return;
|
|
2967
|
+
if (!this._isOpen()) {
|
|
2968
|
+
this.openCalendar();
|
|
2969
|
+
}
|
|
2970
|
+
}
|
|
2971
|
+
onInputFocus() {
|
|
2972
|
+
this._inputFocused.set(true);
|
|
2973
|
+
this.onTouched();
|
|
2974
|
+
}
|
|
2975
|
+
onInputBlur() {
|
|
2976
|
+
this._inputFocused.set(false);
|
|
2977
|
+
this.elementChanged();
|
|
2978
|
+
}
|
|
2979
|
+
onInputKeydown(event) {
|
|
2980
|
+
if (event.key === 'Enter' || event.key === ' ') {
|
|
2981
|
+
event.preventDefault();
|
|
2982
|
+
this.toggle();
|
|
2983
|
+
}
|
|
2984
|
+
else if (event.key === 'Escape') {
|
|
2985
|
+
if (!this._inputFocused() || (this._inputFocused() && this._isOpen())) {
|
|
2986
|
+
this.closeCalendar();
|
|
2987
|
+
}
|
|
2988
|
+
}
|
|
2989
|
+
else if (event.key === 'Tab' || event.key === 'Enter') {
|
|
2990
|
+
if (this._isOpen()) {
|
|
2991
|
+
this.closeCalendar();
|
|
2992
|
+
}
|
|
2993
|
+
}
|
|
2994
|
+
}
|
|
2995
|
+
onInputKeyup() {
|
|
2996
|
+
this.elementChanged();
|
|
2997
|
+
}
|
|
2998
|
+
onDateHover(date) {
|
|
2999
|
+
if (!this.currentEndDate && !this.singleDatePicker() && this.currentStartDate) {
|
|
3000
|
+
if (!isBeforeDate(date, this.currentStartDate)) {
|
|
3001
|
+
this._hoverDate.set(date);
|
|
3002
|
+
this.dateHover.emit(date);
|
|
3003
|
+
}
|
|
3004
|
+
}
|
|
3005
|
+
}
|
|
3006
|
+
onCalendarMouseLeave() {
|
|
3007
|
+
this._hoverDate.set(null);
|
|
3008
|
+
}
|
|
3009
|
+
onCalendarKeydown(event) {
|
|
3010
|
+
if (event.key === 'Escape' && !this._inputFocused()) {
|
|
3011
|
+
this.closeCalendar();
|
|
3012
|
+
}
|
|
3013
|
+
}
|
|
3014
|
+
onCheckboxChange(checked) {
|
|
3015
|
+
this._checkboxValue.set(checked);
|
|
3016
|
+
this.checkboxChange.emit(checked);
|
|
3017
|
+
}
|
|
3018
|
+
// Calendar navigation
|
|
3019
|
+
previousMonth(calendar) {
|
|
3020
|
+
if (calendar === 'left') {
|
|
3021
|
+
const current = this._leftCalendarMonth();
|
|
3022
|
+
const newMonth = addMonths(current, -1);
|
|
3023
|
+
this._leftCalendarMonth.set(newMonth);
|
|
3024
|
+
this.updateLeftCalendarMatrix();
|
|
3025
|
+
if (this.linkedCalendars() && !this.singleDatePicker()) {
|
|
3026
|
+
const rightMonth = addMonths(newMonth, 1);
|
|
3027
|
+
this._rightCalendarMonth.set(rightMonth);
|
|
3028
|
+
this.updateRightCalendarMatrix();
|
|
3029
|
+
}
|
|
3030
|
+
}
|
|
3031
|
+
else {
|
|
3032
|
+
const current = this._rightCalendarMonth();
|
|
3033
|
+
const newMonth = addMonths(current, -1);
|
|
3034
|
+
this._rightCalendarMonth.set(newMonth);
|
|
3035
|
+
this.updateRightCalendarMatrix();
|
|
3036
|
+
if (this.linkedCalendars()) {
|
|
3037
|
+
const leftMonth = addMonths(newMonth, -1);
|
|
3038
|
+
this._leftCalendarMonth.set(leftMonth);
|
|
3039
|
+
this.updateLeftCalendarMatrix();
|
|
3040
|
+
}
|
|
3041
|
+
}
|
|
3042
|
+
}
|
|
3043
|
+
nextMonth(calendar) {
|
|
3044
|
+
if (calendar === 'left') {
|
|
3045
|
+
const current = this._leftCalendarMonth();
|
|
3046
|
+
const newMonth = addMonths(current, 1);
|
|
3047
|
+
this._leftCalendarMonth.set(newMonth);
|
|
3048
|
+
this.updateLeftCalendarMatrix();
|
|
3049
|
+
if (this.linkedCalendars() && !this.singleDatePicker()) {
|
|
3050
|
+
const rightMonth = addMonths(newMonth, 1);
|
|
3051
|
+
this._rightCalendarMonth.set(rightMonth);
|
|
3052
|
+
this.updateRightCalendarMatrix();
|
|
3053
|
+
}
|
|
3054
|
+
}
|
|
3055
|
+
else {
|
|
3056
|
+
const current = this._rightCalendarMonth();
|
|
3057
|
+
const newMonth = addMonths(current, 1);
|
|
3058
|
+
this._rightCalendarMonth.set(newMonth);
|
|
3059
|
+
this.updateRightCalendarMatrix();
|
|
3060
|
+
if (this.linkedCalendars()) {
|
|
3061
|
+
const leftMonth = addMonths(newMonth, -1);
|
|
3062
|
+
this._leftCalendarMonth.set(leftMonth);
|
|
3063
|
+
this.updateLeftCalendarMatrix();
|
|
3064
|
+
}
|
|
3065
|
+
}
|
|
3066
|
+
}
|
|
3067
|
+
// Navigation methods
|
|
3068
|
+
canNavigatePrevious(calendar) {
|
|
3069
|
+
const month = calendar === 'left' ? this._leftCalendarMonth() : this._rightCalendarMonth();
|
|
3070
|
+
const minDate = this.minDate();
|
|
3071
|
+
if (minDate) {
|
|
3072
|
+
return month > minDate;
|
|
3073
|
+
}
|
|
3074
|
+
return true;
|
|
3075
|
+
}
|
|
3076
|
+
canNavigateNext(calendar) {
|
|
3077
|
+
const month = calendar === 'left' ? this._leftCalendarMonth() : this._rightCalendarMonth();
|
|
3078
|
+
const maxDate = this.maxDate();
|
|
3079
|
+
if (maxDate) {
|
|
3080
|
+
return month < maxDate;
|
|
3081
|
+
}
|
|
3082
|
+
return true;
|
|
3083
|
+
}
|
|
3084
|
+
onMonthChange(calendar, event) {
|
|
3085
|
+
const target = event.target;
|
|
3086
|
+
const monthIndex = parseInt(target.value, 10);
|
|
3087
|
+
if (calendar === 'left') {
|
|
3088
|
+
const current = this._leftCalendarMonth();
|
|
3089
|
+
let newMonth = new Date(current.getFullYear(), monthIndex, 1);
|
|
3090
|
+
const minDate = this.minDate();
|
|
3091
|
+
const maxDate = this.maxDate();
|
|
3092
|
+
if (minDate &&
|
|
3093
|
+
(newMonth.getFullYear() < minDate.getFullYear() ||
|
|
3094
|
+
(newMonth.getFullYear() === minDate.getFullYear() && monthIndex < minDate.getMonth()))) {
|
|
3095
|
+
newMonth = new Date(minDate.getFullYear(), minDate.getMonth(), 1);
|
|
3096
|
+
}
|
|
3097
|
+
if (maxDate &&
|
|
3098
|
+
(newMonth.getFullYear() > maxDate.getFullYear() ||
|
|
3099
|
+
(newMonth.getFullYear() === maxDate.getFullYear() && monthIndex > maxDate.getMonth()))) {
|
|
3100
|
+
newMonth = new Date(maxDate.getFullYear(), maxDate.getMonth(), 1);
|
|
3101
|
+
}
|
|
3102
|
+
this._leftCalendarMonth.set(newMonth);
|
|
3103
|
+
this.updateLeftCalendarMatrix();
|
|
3104
|
+
if (this.linkedCalendars() && !this.singleDatePicker()) {
|
|
3105
|
+
const rightMonth = addMonths(newMonth, 1);
|
|
3106
|
+
this._rightCalendarMonth.set(rightMonth);
|
|
3107
|
+
this.updateRightCalendarMatrix();
|
|
3108
|
+
}
|
|
3109
|
+
}
|
|
3110
|
+
else {
|
|
3111
|
+
const current = this._rightCalendarMonth();
|
|
3112
|
+
let newMonth = new Date(current.getFullYear(), monthIndex, 1);
|
|
3113
|
+
const startDate = this.currentStartDate;
|
|
3114
|
+
if (startDate &&
|
|
3115
|
+
(newMonth.getFullYear() < startDate.getFullYear() ||
|
|
3116
|
+
(newMonth.getFullYear() === startDate.getFullYear() && monthIndex < startDate.getMonth()))) {
|
|
3117
|
+
newMonth = new Date(startDate.getFullYear(), startDate.getMonth(), 1);
|
|
3118
|
+
}
|
|
3119
|
+
this._rightCalendarMonth.set(newMonth);
|
|
3120
|
+
this.updateRightCalendarMatrix();
|
|
3121
|
+
if (this.linkedCalendars()) {
|
|
3122
|
+
const leftMonth = addMonths(newMonth, -1);
|
|
3123
|
+
this._leftCalendarMonth.set(leftMonth);
|
|
3124
|
+
this.updateLeftCalendarMatrix();
|
|
3125
|
+
}
|
|
3126
|
+
}
|
|
3127
|
+
}
|
|
3128
|
+
onYearChange(calendar, event) {
|
|
3129
|
+
const target = event.target;
|
|
3130
|
+
const year = parseInt(target.value, 10);
|
|
3131
|
+
if (calendar === 'left') {
|
|
3132
|
+
const current = this._leftCalendarMonth();
|
|
3133
|
+
let newMonth = new Date(year, current.getMonth(), 1);
|
|
3134
|
+
const minDate = this.minDate();
|
|
3135
|
+
const maxDate = this.maxDate();
|
|
3136
|
+
if (minDate &&
|
|
3137
|
+
(year < minDate.getFullYear() ||
|
|
3138
|
+
(year === minDate.getFullYear() && current.getMonth() < minDate.getMonth()))) {
|
|
3139
|
+
newMonth = new Date(minDate.getFullYear(), minDate.getMonth(), 1);
|
|
3140
|
+
}
|
|
3141
|
+
if (maxDate &&
|
|
3142
|
+
(year > maxDate.getFullYear() ||
|
|
3143
|
+
(year === maxDate.getFullYear() && current.getMonth() > maxDate.getMonth()))) {
|
|
3144
|
+
newMonth = new Date(maxDate.getFullYear(), maxDate.getMonth(), 1);
|
|
3145
|
+
}
|
|
3146
|
+
this._leftCalendarMonth.set(newMonth);
|
|
3147
|
+
this.updateLeftCalendarMatrix();
|
|
3148
|
+
if (this.linkedCalendars() && !this.singleDatePicker()) {
|
|
3149
|
+
const rightMonth = addMonths(newMonth, 1);
|
|
3150
|
+
this._rightCalendarMonth.set(rightMonth);
|
|
3151
|
+
this.updateRightCalendarMatrix();
|
|
3152
|
+
}
|
|
3153
|
+
}
|
|
3154
|
+
else {
|
|
3155
|
+
const current = this._rightCalendarMonth();
|
|
3156
|
+
let newMonth = new Date(year, current.getMonth(), 1);
|
|
3157
|
+
const startDate = this.currentStartDate;
|
|
3158
|
+
if (startDate &&
|
|
3159
|
+
(year < startDate.getFullYear() ||
|
|
3160
|
+
(year === startDate.getFullYear() && current.getMonth() < startDate.getMonth()))) {
|
|
3161
|
+
newMonth = new Date(startDate.getFullYear(), startDate.getMonth(), 1);
|
|
3162
|
+
}
|
|
3163
|
+
this._rightCalendarMonth.set(newMonth);
|
|
3164
|
+
this.updateRightCalendarMatrix();
|
|
3165
|
+
if (this.linkedCalendars()) {
|
|
3166
|
+
const leftMonth = addMonths(newMonth, -1);
|
|
3167
|
+
this._leftCalendarMonth.set(leftMonth);
|
|
3168
|
+
this.updateLeftCalendarMatrix();
|
|
3169
|
+
}
|
|
3170
|
+
}
|
|
3171
|
+
}
|
|
3172
|
+
// Helper methods for template
|
|
3173
|
+
isRangeActive(label) {
|
|
3174
|
+
if (!this.currentStartDate)
|
|
3175
|
+
return false;
|
|
3176
|
+
const ranges = this.ranges();
|
|
3177
|
+
const range = ranges[label];
|
|
3178
|
+
if (!range)
|
|
3179
|
+
return false;
|
|
3180
|
+
const [rangeStart, rangeEnd] = range;
|
|
3181
|
+
const startMatches = isSameDate(this.currentStartDate, rangeStart, 'day');
|
|
3182
|
+
const endMatches = this.currentEndDate
|
|
3183
|
+
? isSameDate(this.currentEndDate, rangeEnd, 'day')
|
|
3184
|
+
: isSameDate(this.currentStartDate, rangeEnd, 'day');
|
|
3185
|
+
return startMatches && endMatches;
|
|
3186
|
+
}
|
|
3187
|
+
isDisabled(date) {
|
|
3188
|
+
return this.calendarService.isDateDisabled(date, this.minDate(), this.maxDate(), this.maxSpan(), this.currentStartDate, this.currentEndDate, this.singleDatePicker(), this.isInvalidDate());
|
|
3189
|
+
}
|
|
3190
|
+
isSelected(date) {
|
|
3191
|
+
return this.calendarService.isDateSelected(date, this.currentStartDate, this.currentEndDate, this.singleDatePicker());
|
|
3192
|
+
}
|
|
3193
|
+
isInRange(date) {
|
|
3194
|
+
return this.calendarService.isDateInRange(date, this.currentStartDate, this.currentEndDate, this.singleDatePicker());
|
|
3195
|
+
}
|
|
3196
|
+
isRangeStart(date) {
|
|
3197
|
+
return this.calendarService.isDateRangeStart(date, this.currentStartDate);
|
|
3198
|
+
}
|
|
3199
|
+
isRangeEnd(date) {
|
|
3200
|
+
return this.calendarService.isDateRangeEnd(date, this.currentEndDate);
|
|
3201
|
+
}
|
|
3202
|
+
isToday(date) {
|
|
3203
|
+
const today = new Date();
|
|
3204
|
+
return isSameDate(date, today, 'day');
|
|
3205
|
+
}
|
|
3206
|
+
isOtherMonth(date, calendarMonth) {
|
|
3207
|
+
return (date.getMonth() !== calendarMonth.getMonth() ||
|
|
3208
|
+
date.getFullYear() !== calendarMonth.getFullYear());
|
|
3209
|
+
}
|
|
3210
|
+
// Hover methods
|
|
3211
|
+
isHovered(date) {
|
|
3212
|
+
const hoverDate = this._hoverDate();
|
|
3213
|
+
if (!hoverDate)
|
|
3214
|
+
return false;
|
|
3215
|
+
if (!this.currentStartDate || this.currentEndDate || this.singleDatePicker())
|
|
3216
|
+
return false;
|
|
3217
|
+
const startDate = this.currentStartDate;
|
|
3218
|
+
const rangeStart = hoverDate < startDate ? hoverDate : startDate;
|
|
3219
|
+
const rangeEnd = hoverDate < startDate ? startDate : hoverDate;
|
|
3220
|
+
const dateTime = startOfDay(date).getTime();
|
|
3221
|
+
const rangeStartTime = startOfDay(rangeStart).getTime();
|
|
3222
|
+
const rangeEndTime = startOfDay(rangeEnd).getTime();
|
|
3223
|
+
return dateTime > rangeStartTime && dateTime < rangeEndTime;
|
|
3224
|
+
}
|
|
3225
|
+
isHoverStart(date) {
|
|
3226
|
+
const hoverDate = this._hoverDate();
|
|
3227
|
+
if (!hoverDate)
|
|
3228
|
+
return false;
|
|
3229
|
+
if (!this.currentStartDate || this.currentEndDate || this.singleDatePicker())
|
|
3230
|
+
return false;
|
|
3231
|
+
const startDate = this.currentStartDate;
|
|
3232
|
+
const rangeStart = hoverDate < startDate ? hoverDate : startDate;
|
|
3233
|
+
return isSameDate(date, rangeStart, 'day');
|
|
3234
|
+
}
|
|
3235
|
+
isHoverEnd(date) {
|
|
3236
|
+
const hoverDate = this._hoverDate();
|
|
3237
|
+
if (!hoverDate)
|
|
3238
|
+
return false;
|
|
3239
|
+
if (!this.currentStartDate || this.currentEndDate || this.singleDatePicker())
|
|
3240
|
+
return false;
|
|
3241
|
+
const startDate = this.currentStartDate;
|
|
3242
|
+
const rangeEnd = hoverDate < startDate ? startDate : hoverDate;
|
|
3243
|
+
return isSameDate(date, rangeEnd, 'day');
|
|
3244
|
+
}
|
|
3245
|
+
formatDateForAria(date) {
|
|
3246
|
+
const dayNames = ['Domingo', 'Lunes', 'Martes', 'Miércoles', 'Jueves', 'Viernes', 'Sábado'];
|
|
3247
|
+
const monthNames = this.monthNames();
|
|
3248
|
+
return `${dayNames[date.getDay()]}, ${date.getDate()} de ${monthNames[date.getMonth()]} de ${date.getFullYear()}`;
|
|
3249
|
+
}
|
|
3250
|
+
// Time picker event handlers
|
|
3251
|
+
onStartHourChange(event) {
|
|
3252
|
+
const target = event.target;
|
|
3253
|
+
const hour = parseInt(target.value, 10);
|
|
3254
|
+
this.updateTimeFromPicker('start', 'hour', hour);
|
|
3255
|
+
}
|
|
3256
|
+
onStartMinuteChange(event) {
|
|
3257
|
+
const target = event.target;
|
|
3258
|
+
const minute = parseInt(target.value, 10);
|
|
3259
|
+
this.updateTimeFromPicker('start', 'minute', minute);
|
|
3260
|
+
}
|
|
3261
|
+
onStartAmPmChange(event) {
|
|
3262
|
+
const target = event.target;
|
|
3263
|
+
const ampm = target.value;
|
|
3264
|
+
this.updateTimeFromPicker('start', 'ampm', ampm);
|
|
3265
|
+
}
|
|
3266
|
+
onEndHourChange(event) {
|
|
3267
|
+
const target = event.target;
|
|
3268
|
+
const hour = parseInt(target.value, 10);
|
|
3269
|
+
this.updateTimeFromPicker('end', 'hour', hour);
|
|
3270
|
+
}
|
|
3271
|
+
onEndMinuteChange(event) {
|
|
3272
|
+
const target = event.target;
|
|
3273
|
+
const minute = parseInt(target.value, 10);
|
|
3274
|
+
this.updateTimeFromPicker('end', 'minute', minute);
|
|
3275
|
+
}
|
|
3276
|
+
onEndAmPmChange(event) {
|
|
3277
|
+
const target = event.target;
|
|
3278
|
+
const ampm = target.value;
|
|
3279
|
+
this.updateTimeFromPicker('end', 'ampm', ampm);
|
|
3280
|
+
}
|
|
3281
|
+
// Private methods
|
|
3282
|
+
initializeComponent() {
|
|
3283
|
+
const inputStartDate = this.startDate();
|
|
3284
|
+
const inputEndDate = this.endDate();
|
|
3285
|
+
if (!inputStartDate && !inputEndDate) {
|
|
3286
|
+
this.initializeDefaultDates();
|
|
3287
|
+
}
|
|
3288
|
+
else {
|
|
3289
|
+
this.initializeWithInputDates(inputStartDate, inputEndDate);
|
|
3290
|
+
}
|
|
3291
|
+
this.datexService.updateConfig(this.config());
|
|
3292
|
+
this.datexService.setLocale(this.locale());
|
|
3293
|
+
this.updateElement();
|
|
3294
|
+
}
|
|
3295
|
+
initializeDefaultDates() {
|
|
3296
|
+
const today = new Date();
|
|
3297
|
+
const startDate = startOfDay(today);
|
|
3298
|
+
const endDate = this.singleDatePicker() ? startOfDay(today) : endOfDay(today);
|
|
3299
|
+
this.currentStartDate = startDate;
|
|
3300
|
+
this.currentEndDate = endDate;
|
|
3301
|
+
this.initializeCalendars(startDate, endDate);
|
|
3302
|
+
this.updateCurrentValue();
|
|
3303
|
+
if (this.timePicker()) {
|
|
3304
|
+
this.updateTimeSignalsFromDate(today, 'start');
|
|
3305
|
+
this.updateTimeSignalsFromDate(today, 'end');
|
|
3306
|
+
}
|
|
3307
|
+
}
|
|
3308
|
+
initializeWithInputDates(inputStartDate, inputEndDate) {
|
|
3309
|
+
const startDate = inputStartDate
|
|
3310
|
+
? startOfDay(new Date(inputStartDate))
|
|
3311
|
+
: startOfDay(new Date());
|
|
3312
|
+
const endDate = this.singleDatePicker()
|
|
3313
|
+
? startDate
|
|
3314
|
+
: inputEndDate
|
|
3315
|
+
? endOfDay(new Date(inputEndDate))
|
|
3316
|
+
: endOfDay(startDate);
|
|
3317
|
+
this.currentStartDate = startDate;
|
|
3318
|
+
this.currentEndDate = endDate;
|
|
3319
|
+
this.initializeCalendars(startDate, endDate);
|
|
3320
|
+
this.updateCurrentValue();
|
|
3321
|
+
if (this.timePicker()) {
|
|
3322
|
+
this.updateTimeSignalsFromDate(startDate, 'start');
|
|
3323
|
+
if (endDate) {
|
|
3324
|
+
this.updateTimeSignalsFromDate(endDate, 'end');
|
|
3325
|
+
}
|
|
3326
|
+
}
|
|
3327
|
+
}
|
|
3328
|
+
initializeCalendars(startDate, endDate) {
|
|
3329
|
+
this._leftCalendarMonth.set(new Date(startDate.getFullYear(), startDate.getMonth(), 1));
|
|
3330
|
+
this.updateLeftCalendarMatrix();
|
|
3331
|
+
if (!this.singleDatePicker()) {
|
|
3332
|
+
if (this.linkedCalendars()) {
|
|
3333
|
+
const nextMonth = addMonths(new Date(startDate.getFullYear(), startDate.getMonth(), 1), 1);
|
|
3334
|
+
this._rightCalendarMonth.set(nextMonth);
|
|
3335
|
+
}
|
|
3336
|
+
else if (endDate &&
|
|
3337
|
+
(endDate.getMonth() !== startDate.getMonth() ||
|
|
3338
|
+
endDate.getFullYear() !== startDate.getFullYear())) {
|
|
3339
|
+
this._rightCalendarMonth.set(new Date(endDate.getFullYear(), endDate.getMonth(), 1));
|
|
3340
|
+
}
|
|
3341
|
+
else {
|
|
3342
|
+
const nextMonth = addMonths(new Date(startDate.getFullYear(), startDate.getMonth(), 1), 1);
|
|
3343
|
+
this._rightCalendarMonth.set(nextMonth);
|
|
3344
|
+
}
|
|
3345
|
+
this.updateRightCalendarMatrix();
|
|
3346
|
+
}
|
|
3347
|
+
}
|
|
3348
|
+
updateMonthsInView() {
|
|
3349
|
+
this.calendarService.updateMonthsInView(this.currentStartDate, this.currentEndDate, this._leftCalendarMonth(), this._rightCalendarMonth(), this.singleDatePicker(), this.linkedCalendars(), this.maxDate(), (month) => {
|
|
3350
|
+
this._leftCalendarMonth.set(month);
|
|
3351
|
+
this.updateLeftCalendarMatrix();
|
|
3352
|
+
}, (month) => {
|
|
3353
|
+
this._rightCalendarMonth.set(month);
|
|
3354
|
+
this.updateRightCalendarMatrix();
|
|
3355
|
+
});
|
|
3356
|
+
}
|
|
3357
|
+
updateLeftCalendarMatrix() {
|
|
3358
|
+
const matrix = this.datexService.buildCalendarMatrix(this._leftCalendarMonth());
|
|
3359
|
+
this._leftCalendarMatrix.set(matrix);
|
|
3360
|
+
}
|
|
3361
|
+
updateRightCalendarMatrix() {
|
|
3362
|
+
const matrix = this.datexService.buildCalendarMatrix(this._rightCalendarMonth());
|
|
3363
|
+
this._rightCalendarMatrix.set(matrix);
|
|
3364
|
+
}
|
|
3365
|
+
updateTimeSignalsFromDate(date, type) {
|
|
3366
|
+
const timeValue = this.timePickerService.updateTimeSignalsFromDate(date, this.timePicker24Hour());
|
|
3367
|
+
if (type === 'start') {
|
|
3368
|
+
this._startTime.set(timeValue);
|
|
3369
|
+
}
|
|
3370
|
+
else {
|
|
3371
|
+
this._endTime.set(timeValue);
|
|
3372
|
+
}
|
|
3373
|
+
}
|
|
3374
|
+
setStartDate(startDate) {
|
|
3375
|
+
const newStartDate = this.calendarService.setStartDate(startDate, this.minDate(), this.maxDate(), this.timePicker(), this.timePickerIncrement());
|
|
3376
|
+
this.currentStartDate = newStartDate;
|
|
3377
|
+
this.updateCurrentValue();
|
|
3378
|
+
this.updateMonthsInView();
|
|
3379
|
+
if (!this._isOpen()) {
|
|
3380
|
+
this.updateElement();
|
|
3381
|
+
}
|
|
3382
|
+
}
|
|
3383
|
+
setEndDate(endDate) {
|
|
3384
|
+
const newEndDate = this.calendarService.setEndDate(endDate, this.currentStartDate, this.maxDate(), this.maxSpan(), this.timePicker(), this.timePickerIncrement());
|
|
3385
|
+
this.previousRightTime = new Date(newEndDate);
|
|
3386
|
+
this.currentEndDate = newEndDate;
|
|
3387
|
+
this.updateCurrentValue();
|
|
3388
|
+
this.updateMonthsInView();
|
|
3389
|
+
if (!this._isOpen()) {
|
|
3390
|
+
this.updateElement();
|
|
3391
|
+
}
|
|
3392
|
+
}
|
|
3393
|
+
updateElement() {
|
|
3394
|
+
if (!this.autoUpdateInput())
|
|
3395
|
+
return;
|
|
3396
|
+
const startDate = this._internalStartDate;
|
|
3397
|
+
const endDate = this._internalEndDate;
|
|
3398
|
+
if (!startDate)
|
|
3399
|
+
return;
|
|
3400
|
+
const format = this.locale().format || 'DD/MM/YYYY';
|
|
3401
|
+
const timeFormat = this.timePicker() ? (this.timePicker24Hour() ? ' HH:mm' : ' hh:mm A') : '';
|
|
3402
|
+
const fullFormat = format + timeFormat;
|
|
3403
|
+
const separator = this.locale().separator || ' - ';
|
|
3404
|
+
let newValue = formatDateValue(startDate, fullFormat);
|
|
3405
|
+
if (!this.singleDatePicker() && endDate) {
|
|
3406
|
+
newValue += separator + formatDateValue(endDate, fullFormat);
|
|
3407
|
+
}
|
|
3408
|
+
this._displayValue.set(newValue);
|
|
3409
|
+
}
|
|
3410
|
+
updateCurrentValue() {
|
|
3411
|
+
const value = {
|
|
3412
|
+
startDate: this.currentStartDate,
|
|
3413
|
+
endDate: this.currentEndDate,
|
|
3414
|
+
};
|
|
3415
|
+
this._currentValue.set(value);
|
|
3416
|
+
}
|
|
3417
|
+
emitValueChange() {
|
|
3418
|
+
const value = {
|
|
3419
|
+
startDate: this.currentStartDate,
|
|
3420
|
+
endDate: this.currentEndDate,
|
|
3421
|
+
};
|
|
3422
|
+
this._currentValue.set(value);
|
|
3423
|
+
this.onChange(value);
|
|
3424
|
+
this.dateChange.emit(value);
|
|
3425
|
+
if (this.currentEndDate) {
|
|
3426
|
+
this.rangeChange.emit({
|
|
3427
|
+
startDate: this.currentStartDate,
|
|
3428
|
+
endDate: this.currentEndDate,
|
|
3429
|
+
});
|
|
3430
|
+
}
|
|
3431
|
+
}
|
|
3432
|
+
updateView() {
|
|
3433
|
+
if (this.timePicker()) {
|
|
3434
|
+
this.updateTimePickers();
|
|
3435
|
+
}
|
|
3436
|
+
this.updateLeftCalendarMatrix();
|
|
3437
|
+
this.updateRightCalendarMatrix();
|
|
3438
|
+
}
|
|
3439
|
+
updateTimePickers() {
|
|
3440
|
+
// Time picker update logic handled by service
|
|
3441
|
+
}
|
|
3442
|
+
updateTimeFromPicker(side, component, value) {
|
|
3443
|
+
this.timePickerService.updateTimeFromPicker(this.currentStartDate, this.currentEndDate, side, component, value, this._startTime(), this._endTime(), this.timePicker24Hour(), this.singleDatePicker(), (date) => this.setStartDate(date), (date) => this.setEndDate(date), (side, time) => {
|
|
3444
|
+
if (side === 'start') {
|
|
3445
|
+
this._startTime.set(time);
|
|
3446
|
+
}
|
|
3447
|
+
else {
|
|
3448
|
+
this._endTime.set(time);
|
|
3449
|
+
}
|
|
3450
|
+
});
|
|
3451
|
+
this.updateView();
|
|
3452
|
+
}
|
|
3453
|
+
elementChanged() {
|
|
3454
|
+
const input = this.inputElement?.nativeElement;
|
|
3455
|
+
if (!input || !input.value || !input.value.length)
|
|
3456
|
+
return;
|
|
3457
|
+
const inputValue = input.value;
|
|
3458
|
+
const separator = this.locale().separator || ' - ';
|
|
3459
|
+
const dateString = inputValue.split(separator);
|
|
3460
|
+
let start = null;
|
|
3461
|
+
let end = null;
|
|
3462
|
+
if (dateString.length === 2) {
|
|
3463
|
+
start = this.parseInputDate(dateString[0].trim());
|
|
3464
|
+
end = this.parseInputDate(dateString[1].trim());
|
|
3465
|
+
}
|
|
3466
|
+
if (this.singleDatePicker() || start === null || end === null) {
|
|
3467
|
+
start = this.parseInputDate(inputValue);
|
|
3468
|
+
end = start;
|
|
3469
|
+
}
|
|
3470
|
+
if (!start || !isValidDate(start) || !end || !isValidDate(end)) {
|
|
3471
|
+
this._errorMessage.set('Formato de fecha inválido');
|
|
3472
|
+
return;
|
|
3473
|
+
}
|
|
3474
|
+
this._errorMessage.set('');
|
|
3475
|
+
try {
|
|
3476
|
+
this.setStartDate(start);
|
|
3477
|
+
this.setEndDate(end);
|
|
3478
|
+
this.updateView();
|
|
3479
|
+
if (!this._isOpen()) {
|
|
3480
|
+
this.emitValueChange();
|
|
3481
|
+
}
|
|
3482
|
+
}
|
|
3483
|
+
catch {
|
|
3484
|
+
this._errorMessage.set('Fecha fuera del rango permitido');
|
|
3485
|
+
}
|
|
3486
|
+
}
|
|
3487
|
+
parseInputDate(dateStr) {
|
|
3488
|
+
if (!dateStr)
|
|
3489
|
+
return null;
|
|
3490
|
+
const format = this.locale().format || 'DD/MM/YYYY';
|
|
3491
|
+
try {
|
|
3492
|
+
const date = parseDateValue(dateStr, format);
|
|
3493
|
+
if (isValidDate(date)) {
|
|
3494
|
+
return date;
|
|
3495
|
+
}
|
|
3496
|
+
const nativeDate = new Date(dateStr);
|
|
3497
|
+
if (isValidDate(nativeDate)) {
|
|
3498
|
+
return nativeDate;
|
|
3499
|
+
}
|
|
3500
|
+
return null;
|
|
3501
|
+
}
|
|
3502
|
+
catch {
|
|
3503
|
+
return null;
|
|
3504
|
+
}
|
|
3505
|
+
}
|
|
3506
|
+
createOverlay() {
|
|
3507
|
+
this.overlayService.createOverlay(this.inputElement, this.calendarTemplate, this.viewContainerRef, this.locale(), this.opens(), this.drops(), () => this.closeCalendar(), (event) => {
|
|
3508
|
+
if (event.key === 'Escape' && !this._inputFocused()) {
|
|
3509
|
+
this.closeCalendar();
|
|
3510
|
+
}
|
|
3511
|
+
}, (position) => this._overlayPosition.set(position));
|
|
3512
|
+
}
|
|
3513
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: NgxDatex, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3514
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.1", type: NgxDatex, isStandalone: true, selector: "ngx-datex", inputs: { config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: false, transformFunction: null }, locale: { classPropertyName: "locale", publicName: "locale", isSignal: true, isRequired: false, transformFunction: null }, theme: { classPropertyName: "theme", publicName: "theme", isSignal: true, isRequired: false, transformFunction: null }, appearance: { classPropertyName: "appearance", publicName: "appearance", isSignal: true, isRequired: false, transformFunction: null }, floatLabel: { classPropertyName: "floatLabel", publicName: "floatLabel", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, calendarIcon: { classPropertyName: "calendarIcon", publicName: "calendarIcon", isSignal: true, isRequired: false, transformFunction: null }, showCalendarIcon: { classPropertyName: "showCalendarIcon", publicName: "showCalendarIcon", isSignal: true, isRequired: false, transformFunction: null }, calendarIconPosition: { classPropertyName: "calendarIconPosition", publicName: "calendarIconPosition", isSignal: true, isRequired: false, transformFunction: null }, showCheckbox: { classPropertyName: "showCheckbox", publicName: "showCheckbox", isSignal: true, isRequired: false, transformFunction: null }, checkboxPosition: { classPropertyName: "checkboxPosition", publicName: "checkboxPosition", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, showHeader: { classPropertyName: "showHeader", publicName: "showHeader", isSignal: true, isRequired: false, transformFunction: null }, showFooter: { classPropertyName: "showFooter", publicName: "showFooter", isSignal: true, isRequired: false, transformFunction: null }, singleDatePicker: { classPropertyName: "singleDatePicker", publicName: "singleDatePicker", isSignal: true, isRequired: false, transformFunction: null }, autoApply: { classPropertyName: "autoApply", publicName: "autoApply", isSignal: true, isRequired: false, transformFunction: null }, showDropdowns: { classPropertyName: "showDropdowns", publicName: "showDropdowns", isSignal: true, isRequired: false, transformFunction: null }, timePicker: { classPropertyName: "timePicker", publicName: "timePicker", isSignal: true, isRequired: false, transformFunction: null }, timePicker24Hour: { classPropertyName: "timePicker24Hour", publicName: "timePicker24Hour", isSignal: true, isRequired: false, transformFunction: null }, timePickerIncrement: { classPropertyName: "timePickerIncrement", publicName: "timePickerIncrement", isSignal: true, isRequired: false, transformFunction: null }, timePickerSeconds: { classPropertyName: "timePickerSeconds", publicName: "timePickerSeconds", isSignal: true, isRequired: false, transformFunction: null }, linkedCalendars: { classPropertyName: "linkedCalendars", publicName: "linkedCalendars", isSignal: true, isRequired: false, transformFunction: null }, autoUpdateInput: { classPropertyName: "autoUpdateInput", publicName: "autoUpdateInput", isSignal: true, isRequired: false, transformFunction: null }, alwaysShowCalendars: { classPropertyName: "alwaysShowCalendars", publicName: "alwaysShowCalendars", isSignal: true, isRequired: false, transformFunction: null }, showCustomRangeLabel: { classPropertyName: "showCustomRangeLabel", publicName: "showCustomRangeLabel", isSignal: true, isRequired: false, transformFunction: null }, startDate: { classPropertyName: "startDate", publicName: "startDate", isSignal: true, isRequired: false, transformFunction: null }, endDate: { classPropertyName: "endDate", publicName: "endDate", isSignal: true, isRequired: false, transformFunction: null }, minDate: { classPropertyName: "minDate", publicName: "minDate", isSignal: true, isRequired: false, transformFunction: null }, maxDate: { classPropertyName: "maxDate", publicName: "maxDate", isSignal: true, isRequired: false, transformFunction: null }, maxSpan: { classPropertyName: "maxSpan", publicName: "maxSpan", isSignal: true, isRequired: false, transformFunction: null }, showWeekNumbers: { classPropertyName: "showWeekNumbers", publicName: "showWeekNumbers", isSignal: true, isRequired: false, transformFunction: null }, showISOWeekNumbers: { classPropertyName: "showISOWeekNumbers", publicName: "showISOWeekNumbers", isSignal: true, isRequired: false, transformFunction: null }, buttonClasses: { classPropertyName: "buttonClasses", publicName: "buttonClasses", isSignal: true, isRequired: false, transformFunction: null }, applyButtonClasses: { classPropertyName: "applyButtonClasses", publicName: "applyButtonClasses", isSignal: true, isRequired: false, transformFunction: null }, cancelButtonClasses: { classPropertyName: "cancelButtonClasses", publicName: "cancelButtonClasses", isSignal: true, isRequired: false, transformFunction: null }, isInvalidDate: { classPropertyName: "isInvalidDate", publicName: "isInvalidDate", isSignal: true, isRequired: false, transformFunction: null }, isCustomDate: { classPropertyName: "isCustomDate", publicName: "isCustomDate", isSignal: true, isRequired: false, transformFunction: null }, minYear: { classPropertyName: "minYear", publicName: "minYear", isSignal: true, isRequired: false, transformFunction: null }, maxYear: { classPropertyName: "maxYear", publicName: "maxYear", isSignal: true, isRequired: false, transformFunction: null }, ranges: { classPropertyName: "ranges", publicName: "ranges", isSignal: true, isRequired: false, transformFunction: null }, opens: { classPropertyName: "opens", publicName: "opens", isSignal: true, isRequired: false, transformFunction: null }, drops: { classPropertyName: "drops", publicName: "drops", isSignal: true, isRequired: false, transformFunction: null }, headerTemplate: { classPropertyName: "headerTemplate", publicName: "headerTemplate", isSignal: true, isRequired: false, transformFunction: null }, footerTemplate: { classPropertyName: "footerTemplate", publicName: "footerTemplate", isSignal: true, isRequired: false, transformFunction: null }, dayTemplate: { classPropertyName: "dayTemplate", publicName: "dayTemplate", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, ariaDescribedBy: { classPropertyName: "ariaDescribedBy", publicName: "ariaDescribedBy", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { dateChange: "dateChange", rangeChange: "rangeChange", openEvent: "openEvent", closeEvent: "closeEvent", monthChange: "monthChange", yearChange: "yearChange", dateHover: "dateHover", validationError: "validationError", checkboxChange: "checkboxChange" }, providers: [
|
|
3515
|
+
{
|
|
3516
|
+
provide: NG_VALUE_ACCESSOR,
|
|
3517
|
+
useExisting: forwardRef(() => NgxDatex),
|
|
3518
|
+
multi: true,
|
|
3519
|
+
},
|
|
3520
|
+
NgxDatexOverlayService,
|
|
3521
|
+
NgxDatexTimePickerService,
|
|
3522
|
+
NgxDatexCalendarService,
|
|
3523
|
+
], viewQueries: [{ propertyName: "inputElement", first: true, predicate: ["inputElement"], descendants: true }, { propertyName: "calendarTemplate", first: true, predicate: ["calendarTemplate"], descendants: true }], ngImport: i0, template: "<div class=\"ngx-datex-container\" [class.ngx-datex-mobile]=\"isMobileDevice()\">\r\n <!-- Input Field -->\r\n <mat-form-field\r\n [appearance]=\"appearance()\"\r\n [floatLabel]=\"floatLabel()\"\r\n class=\"ngx-datex-input-field\"\r\n >\r\n @if (label()) {\r\n <mat-label>{{ label() }}</mat-label>\r\n }\r\n\r\n <!-- Checkbox como prefix -->\r\n @if (showCheckbox() && checkboxPosition() === 'prefix') {\r\n <mat-checkbox\r\n matPrefix\r\n [checked]=\"checkboxValue()\"\r\n (change)=\"onCheckboxChange($event.checked)\"\r\n class=\"ngx-datex-checkbox ngx-datex-checkbox-prefix\"\r\n >\r\n </mat-checkbox>\r\n }\r\n\r\n <!-- \u00CDcono como prefix -->\r\n @if (showCalendarIcon() && calendarIconPosition() === 'prefix') {\r\n <mat-icon\r\n matPrefix\r\n class=\"ngx-datex-calendar-icon\"\r\n [class.ngx-datex-icon-active]=\"isOpen()\"\r\n (click)=\"toggle()\"\r\n (keydown.enter)=\"toggle()\"\r\n (keydown.space)=\"toggle()\"\r\n tabindex=\"0\"\r\n role=\"button\"\r\n [attr.aria-label]=\"'Open calendar'\"\r\n >\r\n {{ calendarIcon() }}\r\n </mat-icon>\r\n }\r\n\r\n <input\r\n matInput\r\n #inputElement\r\n [value]=\"displayValue()\"\r\n [placeholder]=\"placeholder()\"\r\n [readonly]=\"readonly()\"\r\n [disabled]=\"disabled()\"\r\n [attr.aria-label]=\"ariaLabel()\"\r\n [attr.aria-describedby]=\"ariaDescribedBy()\"\r\n (click)=\"onInputClick()\"\r\n (focus)=\"onInputFocus()\"\r\n (blur)=\"onInputBlur()\"\r\n (keydown)=\"onInputKeydown($event)\"\r\n (keyup)=\"onInputKeyup()\"\r\n autocomplete=\"off\"\r\n />\r\n\r\n <!-- \u00CDcono como suffix -->\r\n @if (showCalendarIcon() && calendarIconPosition() === 'suffix') {\r\n <mat-icon\r\n matSuffix\r\n class=\"ngx-datex-calendar-icon\"\r\n [class.ngx-datex-icon-active]=\"isOpen()\"\r\n (click)=\"toggle()\"\r\n (keydown.enter)=\"toggle()\"\r\n (keydown.space)=\"toggle()\"\r\n tabindex=\"0\"\r\n role=\"button\"\r\n [attr.aria-label]=\"'Open calendar'\"\r\n >\r\n {{ calendarIcon() }}\r\n </mat-icon>\r\n }\r\n\r\n <!-- Checkbox como suffix -->\r\n @if (showCheckbox() && checkboxPosition() === 'suffix') {\r\n <mat-checkbox\r\n matSuffix\r\n [checked]=\"checkboxValue()\"\r\n (change)=\"onCheckboxChange($event.checked)\"\r\n class=\"ngx-datex-checkbox ngx-datex-checkbox-suffix\"\r\n >\r\n </mat-checkbox>\r\n }\r\n\r\n @if (hasError()) {\r\n <mat-error>{{ errorMessage() }}</mat-error>\r\n }\r\n </mat-form-field>\r\n\r\n <!-- Calendar Template for CDK Overlay -->\r\n <ng-template #calendarTemplate>\r\n <div\r\n class=\"ngx-datex-overlay\"\r\n [class.ngx-datex-overlay-mobile]=\"isMobileDevice()\"\r\n [class.ngx-datex-single]=\"singleDatePicker()\"\r\n [class.ngx-datex-arrow-up]=\"arrowDirection() === 'up'\"\r\n [class.ngx-datex-arrow-down]=\"arrowDirection() === 'down'\"\r\n [class.ngx-datex-arrow-left]=\"arrowDirection() === 'left'\"\r\n [class.ngx-datex-arrow-right]=\"arrowDirection() === 'right'\"\r\n [class.ngx-datex-arrow-align-start]=\"arrowAlignment() === 'start'\"\r\n [class.ngx-datex-arrow-align-center]=\"arrowAlignment() === 'center'\"\r\n [class.ngx-datex-arrow-align-end]=\"arrowAlignment() === 'end'\"\r\n role=\"dialog\"\r\n aria-modal=\"true\"\r\n [attr.aria-label]=\"headerTitle()\"\r\n tabindex=\"-1\"\r\n (click)=\"$event.stopPropagation()\"\r\n (keydown)=\"onCalendarKeydown($event)\"\r\n >\r\n <!-- Mobile Header (only shown on mobile) -->\r\n @if (isMobileDevice()) {\r\n <div class=\"ngx-datex-mobile-header\">\r\n <div class=\"ngx-datex-mobile-header-content\">\r\n <div class=\"ngx-datex-mobile-selected-range\">\r\n @if (hasStartDate()) {\r\n {{ formattedSelectedRange() }}\r\n } @else {\r\n Selecciona un rango de fechas\r\n }\r\n </div>\r\n <div class=\"ngx-datex-mobile-range-label\">\r\n @if (currentLabel()) {\r\n {{ currentLabel() }}\r\n }\r\n </div>\r\n </div>\r\n <div class=\"ngx-datex-mobile-header-buttons\">\r\n <button\r\n type=\"button\"\r\n class=\"ngx-datex-mobile-cancel-button\"\r\n [ngClass]=\"[buttonClasses(), cancelButtonClasses()]\"\r\n (click)=\"cancel()\"\r\n >\r\n {{ locale().cancelLabel || 'Cancelar' }}\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"ngx-datex-mobile-apply-button\"\r\n [ngClass]=\"[buttonClasses(), applyButtonClasses()]\"\r\n [disabled]=\"!canApplyValue()\"\r\n (click)=\"apply()\"\r\n >\r\n {{ locale().applyLabel || 'Aplicar' }}\r\n </button>\r\n </div>\r\n </div>\r\n }\r\n\r\n <div class=\"ngx-datex-content\">\r\n <!-- Predefined Ranges - Desktop: Sidebar, Mobile: Horizontal Chips -->\r\n @if (showRanges()) {\r\n <!-- Desktop Ranges Sidebar -->\r\n @if (!isMobileDevice()) {\r\n <div class=\"ngx-datex-ranges-sidebar\">\r\n @for (rangeEntry of rangeEntries(); track rangeEntry.label) {\r\n <button\r\n type=\"button\"\r\n class=\"ngx-datex-range-item\"\r\n [class.ngx-datex-range-active]=\"isRangeActive(rangeEntry.label)\"\r\n (click)=\"selectRange(rangeEntry.label)\"\r\n >\r\n {{ rangeEntry.label }}\r\n </button>\r\n }\r\n <!-- Rango Personalizado -->\r\n @if (showCustomRangeLabel()) {\r\n <button\r\n type=\"button\"\r\n class=\"ngx-datex-range-item\"\r\n [class.ngx-datex-range-active]=\"isCustomRange()\"\r\n (click)=\"selectCustomRange()\"\r\n >\r\n {{ locale().customRangeLabel || 'Rango Personalizado' }}\r\n </button>\r\n }\r\n </div>\r\n }\r\n\r\n <!-- Mobile Ranges Chips -->\r\n @if (isMobileDevice()) {\r\n <div class=\"ngx-datex-ranges-chips\">\r\n <div class=\"ngx-datex-ranges-chips-container\">\r\n @for (rangeEntry of rangeEntries(); track rangeEntry.label) {\r\n <button\r\n type=\"button\"\r\n class=\"ngx-datex-range-chip\"\r\n [class.ngx-datex-range-chip-active]=\"isRangeActive(rangeEntry.label)\"\r\n (click)=\"selectRange(rangeEntry.label)\"\r\n >\r\n {{ rangeEntry.label }}\r\n </button>\r\n }\r\n <!-- Rango Personalizado -->\r\n @if (showCustomRangeLabel()) {\r\n <button\r\n type=\"button\"\r\n class=\"ngx-datex-range-chip\"\r\n [class.ngx-datex-range-chip-active]=\"isCustomRange()\"\r\n (click)=\"selectCustomRange()\"\r\n >\r\n {{ locale().customRangeLabel || 'Personalizado' }}\r\n </button>\r\n }\r\n </div>\r\n </div>\r\n }\r\n }\r\n\r\n <!-- Calendar Section -->\r\n <div class=\"ngx-datex-calendar-section\">\r\n <!-- Calendar Container -->\r\n <div class=\"ngx-datex-calendars\" (mouseleave)=\"onCalendarMouseLeave()\">\r\n <!-- Left Calendar -->\r\n <div\r\n class=\"ngx-datex-calendar ngx-datex-calendar-left\"\r\n [class.ngx-datex-calendar-single]=\"singleDatePicker()\"\r\n >\r\n <div class=\"ngx-datex-calendar-header\">\r\n <button\r\n type=\"button\"\r\n class=\"ngx-datex-nav-button\"\r\n (click)=\"previousMonth('left')\"\r\n [disabled]=\"!canNavigatePrevious('left')\"\r\n aria-label=\"Mes anterior\"\r\n >\r\n <mat-icon>chevron_left</mat-icon>\r\n </button>\r\n\r\n <div class=\"ngx-datex-month-year\">\r\n @if (showDropdowns()) {\r\n <select\r\n class=\"ngx-datex-month-select\"\r\n [value]=\"leftCalendarMonthValue()\"\r\n (change)=\"onMonthChange('left', $event)\"\r\n aria-label=\"Seleccionar mes\"\r\n >\r\n @for (month of monthNames(); track $index) {\r\n <option [value]=\"$index\">{{ month }}</option>\r\n }\r\n </select>\r\n\r\n <select\r\n class=\"ngx-datex-year-select\"\r\n [value]=\"leftCalendarYearValue()\"\r\n (change)=\"onYearChange('left', $event)\"\r\n aria-label=\"Seleccionar a\u00F1o\"\r\n >\r\n @for (year of availableYears(); track year) {\r\n <option [value]=\"year\">{{ year }}</option>\r\n }\r\n </select>\r\n } @else {\r\n <span class=\"ngx-datex-month-year-display\">\r\n {{ monthNames()[leftCalendarMonthValue()] }} {{ leftCalendarYearValue() }}\r\n </span>\r\n }\r\n </div>\r\n\r\n <button\r\n type=\"button\"\r\n class=\"ngx-datex-nav-button\"\r\n (click)=\"nextMonth('left')\"\r\n [disabled]=\"!canNavigateNext('left')\"\r\n aria-label=\"Mes siguiente\"\r\n >\r\n <mat-icon>chevron_right</mat-icon>\r\n </button>\r\n </div>\r\n\r\n <!-- Days of Week Header -->\r\n <div class=\"ngx-datex-days-header\">\r\n <div class=\"ngx-datex-days-header-row\">\r\n @for (day of daysOfWeek(); track day) {\r\n <div class=\"ngx-datex-day-header\">{{ day }}</div>\r\n }\r\n </div>\r\n </div>\r\n\r\n <!-- Calendar Grid -->\r\n <div class=\"ngx-datex-calendar-grid\">\r\n @for (week of leftCalendarMatrix(); track $index) {\r\n <div class=\"ngx-datex-week\">\r\n @for (date of week; track date.getTime()) {\r\n <button\r\n type=\"button\"\r\n class=\"ngx-datex-day\"\r\n [class.ngx-datex-day-other-month]=\"isOtherMonth(date, leftCalendarMonth())\"\r\n [class.ngx-datex-day-today]=\"isToday(date)\"\r\n [class.ngx-datex-day-selected]=\"isSelected(date)\"\r\n [class.ngx-datex-day-active]=\"isSelected(date)\"\r\n [class.ngx-datex-day-in-range]=\"isInRange(date)\"\r\n [class.ngx-datex-day-range-start]=\"isRangeStart(date)\"\r\n [class.ngx-datex-day-range-end]=\"isRangeEnd(date)\"\r\n [class.ngx-datex-day-hover-range]=\"\r\n isHovered(date) && !isHoverStart(date) && !isHoverEnd(date)\r\n \"\r\n [class.ngx-datex-day-hover-start]=\"isHoverStart(date)\"\r\n [class.ngx-datex-day-hover-end]=\"isHoverEnd(date)\"\r\n [class.ngx-datex-day-disabled]=\"isDisabled(date)\"\r\n [disabled]=\"isDisabled(date)\"\r\n (click)=\"selectDate(date)\"\r\n (mouseenter)=\"onDateHover(date)\"\r\n [attr.aria-label]=\"formatDateForAria(date)\"\r\n >\r\n {{ date.getDate() }}\r\n </button>\r\n }\r\n </div>\r\n }\r\n </div>\r\n\r\n <!-- Time Picker for Left Calendar -->\r\n @if (timePicker()) {\r\n <div class=\"ngx-datex-time-picker\">\r\n <div class=\"ngx-datex-time-controls\">\r\n <select\r\n class=\"ngx-datex-time-select ngx-datex-hour-select\"\r\n [value]=\"selectedStartHour()\"\r\n (change)=\"onStartHourChange($event)\"\r\n aria-label=\"Seleccionar hora\"\r\n >\r\n @if (timePicker24Hour()) {\r\n @for (hourOption of availableStartHours(); track hourOption.value) {\r\n <option\r\n [value]=\"hourOption.value\"\r\n [selected]=\"selectedStartHour() === hourOption.value\"\r\n [disabled]=\"hourOption.disabled\"\r\n >\r\n {{ hourOption.value.toString().padStart(2, '0') }}\r\n </option>\r\n }\r\n } @else {\r\n @for (hourOption of availableStartHours(); track hourOption.value) {\r\n <option\r\n [value]=\"hourOption.value\"\r\n [selected]=\"selectedStartHour() === hourOption.value\"\r\n [disabled]=\"hourOption.disabled\"\r\n >\r\n {{ hourOption.value }}\r\n </option>\r\n }\r\n }\r\n </select>\r\n\r\n <span class=\"ngx-datex-time-separator\">:</span>\r\n\r\n <select\r\n class=\"ngx-datex-time-select ngx-datex-minute-select\"\r\n [value]=\"selectedStartMinute()\"\r\n (change)=\"onStartMinuteChange($event)\"\r\n aria-label=\"Seleccionar minuto\"\r\n >\r\n @for (minuteOption of availableStartMinutes(); track minuteOption.value) {\r\n <option\r\n [value]=\"minuteOption.value\"\r\n [selected]=\"selectedStartMinute() === minuteOption.value\"\r\n [disabled]=\"minuteOption.disabled\"\r\n >\r\n {{ minuteOption.value.toString().padStart(2, '0') }}\r\n </option>\r\n }\r\n </select>\r\n\r\n @if (!timePicker24Hour()) {\r\n <select\r\n class=\"ngx-datex-time-select ngx-datex-ampm-select\"\r\n [value]=\"selectedStartAmPm()\"\r\n (change)=\"onStartAmPmChange($event)\"\r\n aria-label=\"Seleccionar AM/PM\"\r\n >\r\n <option value=\"AM\">AM</option>\r\n <option value=\"PM\">PM</option>\r\n </select>\r\n }\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n\r\n <!-- Right Calendar (for range picker) -->\r\n @if (!singleDatePicker()) {\r\n <div class=\"ngx-datex-calendar ngx-datex-calendar-right\">\r\n <div class=\"ngx-datex-calendar-header\">\r\n <button\r\n type=\"button\"\r\n class=\"ngx-datex-nav-button\"\r\n (click)=\"previousMonth('right')\"\r\n [disabled]=\"!canNavigatePrevious('right')\"\r\n aria-label=\"Mes anterior\"\r\n >\r\n <mat-icon>chevron_left</mat-icon>\r\n </button>\r\n\r\n <div class=\"ngx-datex-month-year\">\r\n @if (showDropdowns()) {\r\n <select\r\n class=\"ngx-datex-month-select\"\r\n [value]=\"rightCalendarMonthValue()\"\r\n (change)=\"onMonthChange('right', $event)\"\r\n aria-label=\"Seleccionar mes\"\r\n >\r\n @for (month of monthNames(); track $index) {\r\n <option [value]=\"$index\">{{ month }}</option>\r\n }\r\n </select>\r\n\r\n <select\r\n class=\"ngx-datex-year-select\"\r\n [value]=\"rightCalendarYearValue()\"\r\n (change)=\"onYearChange('right', $event)\"\r\n aria-label=\"Seleccionar a\u00F1o\"\r\n >\r\n @for (year of availableYears(); track year) {\r\n <option [value]=\"year\">{{ year }}</option>\r\n }\r\n </select>\r\n } @else {\r\n <span class=\"ngx-datex-month-year-display\">\r\n {{ monthNames()[rightCalendarMonthValue()] }} {{ rightCalendarYearValue() }}\r\n </span>\r\n }\r\n </div>\r\n\r\n <button\r\n type=\"button\"\r\n class=\"ngx-datex-nav-button\"\r\n (click)=\"nextMonth('right')\"\r\n [disabled]=\"!canNavigateNext('right')\"\r\n aria-label=\"Mes siguiente\"\r\n >\r\n <mat-icon>chevron_right</mat-icon>\r\n </button>\r\n </div>\r\n\r\n <!-- Days of Week Header -->\r\n <div class=\"ngx-datex-days-header\">\r\n <div class=\"ngx-datex-days-header-row\">\r\n @for (day of daysOfWeek(); track day) {\r\n <div class=\"ngx-datex-day-header\">{{ day }}</div>\r\n }\r\n </div>\r\n </div>\r\n\r\n <!-- Calendar Grid -->\r\n <div class=\"ngx-datex-calendar-grid\">\r\n @for (week of rightCalendarMatrix(); track $index) {\r\n <div class=\"ngx-datex-week\">\r\n @for (date of week; track date.getTime()) {\r\n <button\r\n type=\"button\"\r\n class=\"ngx-datex-day\"\r\n [class.ngx-datex-day-other-month]=\"\r\n isOtherMonth(date, rightCalendarMonth())\r\n \"\r\n [class.ngx-datex-day-today]=\"isToday(date)\"\r\n [class.ngx-datex-day-selected]=\"isSelected(date)\"\r\n [class.ngx-datex-day-active]=\"isSelected(date)\"\r\n [class.ngx-datex-day-in-range]=\"isInRange(date)\"\r\n [class.ngx-datex-day-range-start]=\"isRangeStart(date)\"\r\n [class.ngx-datex-day-range-end]=\"isRangeEnd(date)\"\r\n [class.ngx-datex-day-hover-range]=\"\r\n isHovered(date) && !isHoverStart(date) && !isHoverEnd(date)\r\n \"\r\n [class.ngx-datex-day-hover-start]=\"isHoverStart(date)\"\r\n [class.ngx-datex-day-hover-end]=\"isHoverEnd(date)\"\r\n [class.ngx-datex-day-disabled]=\"isDisabled(date)\"\r\n [disabled]=\"isDisabled(date)\"\r\n (click)=\"selectDate(date)\"\r\n (mouseenter)=\"onDateHover(date)\"\r\n [attr.aria-label]=\"formatDateForAria(date)\"\r\n >\r\n {{ date.getDate() }}\r\n </button>\r\n }\r\n </div>\r\n }\r\n </div>\r\n\r\n <!-- Time Picker for Right Calendar -->\r\n @if (timePicker() && !singleDatePicker()) {\r\n <div class=\"ngx-datex-time-picker\">\r\n <div class=\"ngx-datex-time-controls\">\r\n <select\r\n class=\"ngx-datex-time-select ngx-datex-hour-select\"\r\n [value]=\"selectedEndHour()\"\r\n [disabled]=\"!hasEndDate()\"\r\n (change)=\"onEndHourChange($event)\"\r\n aria-label=\"Seleccionar hora\"\r\n >\r\n @if (timePicker24Hour()) {\r\n @for (hourOption of availableEndHours(); track hourOption.value) {\r\n <option\r\n [value]=\"hourOption.value\"\r\n [selected]=\"selectedEndHour() === hourOption.value\"\r\n [disabled]=\"hourOption.disabled\"\r\n >\r\n {{ hourOption.value.toString().padStart(2, '0') }}\r\n </option>\r\n }\r\n } @else {\r\n @for (hourOption of availableEndHours(); track hourOption.value) {\r\n <option\r\n [value]=\"hourOption.value\"\r\n [selected]=\"selectedEndHour() === hourOption.value\"\r\n [disabled]=\"hourOption.disabled\"\r\n >\r\n {{ hourOption.value }}\r\n </option>\r\n }\r\n }\r\n </select>\r\n\r\n <span class=\"ngx-datex-time-separator\">:</span>\r\n\r\n <select\r\n class=\"ngx-datex-time-select ngx-datex-minute-select\"\r\n [value]=\"selectedEndMinute()\"\r\n [disabled]=\"!hasEndDate()\"\r\n (change)=\"onEndMinuteChange($event)\"\r\n aria-label=\"Seleccionar minuto\"\r\n >\r\n @for (minuteOption of availableEndMinutes(); track minuteOption.value) {\r\n <option\r\n [value]=\"minuteOption.value\"\r\n [selected]=\"selectedEndMinute() === minuteOption.value\"\r\n [disabled]=\"minuteOption.disabled\"\r\n >\r\n {{ minuteOption.value.toString().padStart(2, '0') }}\r\n </option>\r\n }\r\n </select>\r\n\r\n @if (!timePicker24Hour()) {\r\n <select\r\n class=\"ngx-datex-time-select ngx-datex-ampm-select\"\r\n [value]=\"selectedEndAmPm()\"\r\n [disabled]=\"!hasEndDate()\"\r\n (change)=\"onEndAmPmChange($event)\"\r\n aria-label=\"Seleccionar AM/PM\"\r\n >\r\n <option value=\"AM\">AM</option>\r\n <option value=\"PM\">PM</option>\r\n </select>\r\n }\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n }\r\n </div>\r\n\r\n <!-- Footer with selected range and buttons (desktop only) -->\r\n @if (!isMobileDevice()) {\r\n <div class=\"ngx-datex-footer\">\r\n <div class=\"ngx-datex-selected-range\">\r\n @if (hasStartDate()) {\r\n {{ formattedSelectedRange() }}\r\n } @else {\r\n Selecciona un rango de fechas\r\n }\r\n </div>\r\n <div class=\"ngx-datex-footer-buttons\">\r\n <button\r\n type=\"button\"\r\n class=\"ngx-datex-cancel-button\"\r\n [ngClass]=\"[buttonClasses(), cancelButtonClasses()]\"\r\n (click)=\"cancel()\"\r\n >\r\n {{ locale().cancelLabel || 'Cancelar' }}\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"ngx-datex-apply-button\"\r\n [ngClass]=\"[buttonClasses(), applyButtonClasses()]\"\r\n [disabled]=\"!canApplyValue()\"\r\n (click)=\"apply()\"\r\n >\r\n {{ locale().applyLabel || 'Aplicar' }}\r\n </button>\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n </div>\r\n </ng-template>\r\n</div>\r\n", styles: [".ngx-datex-container{position:relative;display:inline-block;width:100%}.ngx-datex-input-field{width:100%}.ngx-datex-calendar-icon{cursor:pointer;transition:color .2s ease;font-size:20px}.ngx-datex-calendar-icon:hover,.ngx-datex-calendar-icon.ngx-datex-icon-active{color:var(--ngx-datex-primary-color, #1976d2)}.ngx-datex-overlay-panel{z-index:1000}.ngx-datex-mobile-overlay{z-index:1001}.ngx-datex-overlay{background:#fff;border-radius:8px;box-shadow:0 5px 15px #00000026,0 2px 4px #0000001f;border:1px solid #e0e0e0;width:650px;font-family:Roboto,sans-serif;font-size:12px;line-height:1em;position:relative;color:#212121}.ngx-datex-overlay:before,.ngx-datex-overlay:after{position:absolute;content:\"\";display:none;z-index:10}.ngx-datex-overlay.ngx-datex-arrow-down:before{display:block;border-left:7px solid transparent;border-right:7px solid transparent;border-bottom:7px solid #e0e0e0;top:-7px}.ngx-datex-overlay.ngx-datex-arrow-down:after{display:block;border-left:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid #ffffff;top:-6px}.ngx-datex-overlay.ngx-datex-arrow-up:before{display:block;border-left:7px solid transparent;border-right:7px solid transparent;border-top:7px solid #e0e0e0;bottom:-7px}.ngx-datex-overlay.ngx-datex-arrow-up:after{display:block;border-left:6px solid transparent;border-right:6px solid transparent;border-top:6px solid #ffffff;bottom:-6px}.ngx-datex-overlay.ngx-datex-arrow-right:before{display:block;border-top:7px solid transparent;border-bottom:7px solid transparent;border-right:7px solid #e0e0e0;left:-7px}.ngx-datex-overlay.ngx-datex-arrow-right:after{display:block;border-top:6px solid transparent;border-bottom:6px solid transparent;border-right:6px solid #ffffff;left:-6px}.ngx-datex-overlay.ngx-datex-arrow-left:before{display:block;border-top:7px solid transparent;border-bottom:7px solid transparent;border-left:7px solid #e0e0e0;right:-7px}.ngx-datex-overlay.ngx-datex-arrow-left:after{display:block;border-top:6px solid transparent;border-bottom:6px solid transparent;border-left:6px solid #ffffff;right:-6px}.ngx-datex-overlay.ngx-datex-arrow-up.ngx-datex-arrow-align-start:before,.ngx-datex-overlay.ngx-datex-arrow-down.ngx-datex-arrow-align-start:before{left:20px}.ngx-datex-overlay.ngx-datex-arrow-up.ngx-datex-arrow-align-start:after,.ngx-datex-overlay.ngx-datex-arrow-down.ngx-datex-arrow-align-start:after{left:21px}.ngx-datex-overlay.ngx-datex-arrow-up.ngx-datex-arrow-align-center:before,.ngx-datex-overlay.ngx-datex-arrow-down.ngx-datex-arrow-align-center:before{left:50%;transform:translate(-50%)}.ngx-datex-overlay.ngx-datex-arrow-up.ngx-datex-arrow-align-center:after,.ngx-datex-overlay.ngx-datex-arrow-down.ngx-datex-arrow-align-center:after{left:50%;transform:translate(-50%)}.ngx-datex-overlay.ngx-datex-arrow-up.ngx-datex-arrow-align-end:before,.ngx-datex-overlay.ngx-datex-arrow-down.ngx-datex-arrow-align-end:before{right:20px}.ngx-datex-overlay.ngx-datex-arrow-up.ngx-datex-arrow-align-end:after,.ngx-datex-overlay.ngx-datex-arrow-down.ngx-datex-arrow-align-end:after{right:21px}.ngx-datex-overlay.ngx-datex-arrow-left.ngx-datex-arrow-align-start:before,.ngx-datex-overlay.ngx-datex-arrow-right.ngx-datex-arrow-align-start:before{top:20px}.ngx-datex-overlay.ngx-datex-arrow-left.ngx-datex-arrow-align-start:after,.ngx-datex-overlay.ngx-datex-arrow-right.ngx-datex-arrow-align-start:after{top:21px}.ngx-datex-overlay.ngx-datex-arrow-left.ngx-datex-arrow-align-center:before,.ngx-datex-overlay.ngx-datex-arrow-right.ngx-datex-arrow-align-center:before{top:50%;transform:translateY(-50%)}.ngx-datex-overlay.ngx-datex-arrow-left.ngx-datex-arrow-align-center:after,.ngx-datex-overlay.ngx-datex-arrow-right.ngx-datex-arrow-align-center:after{top:50%;transform:translateY(-50%)}.ngx-datex-overlay.ngx-datex-arrow-left.ngx-datex-arrow-align-end:before,.ngx-datex-overlay.ngx-datex-arrow-right.ngx-datex-arrow-align-end:before{bottom:20px}.ngx-datex-overlay.ngx-datex-arrow-left.ngx-datex-arrow-align-end:after,.ngx-datex-overlay.ngx-datex-arrow-right.ngx-datex-arrow-align-end:after{bottom:21px}.ngx-datex-overlay.ngx-datex-single{width:300px}.ngx-datex-overlay.ngx-datex-single .ngx-datex-ranges-sidebar{display:none}.ngx-datex-overlay.ngx-datex-overlay-mobile{width:100vw;max-width:100vw;min-width:100vw;border-radius:16px 16px 0 0;max-height:90vh;overflow-y:auto;box-shadow:0 -8px 32px #0003;border:none;margin:0;padding:0}.ngx-datex-overlay.ngx-datex-overlay-mobile:before,.ngx-datex-overlay.ngx-datex-overlay-mobile:after{display:none}.ngx-datex-overlay.ngx-datex-overlay-mobile .ngx-datex-content{min-height:auto;padding:0;margin:0;width:100%;display:flex;flex-direction:column}.ngx-datex-overlay.ngx-datex-overlay-mobile .ngx-datex-calendar-section{padding:0;margin:0;width:100%;flex:1}.ngx-datex-overlay.ngx-datex-overlay-mobile .ngx-datex-calendars{padding:16px;gap:20px;margin:0;width:100%;box-sizing:border-box}.ngx-datex-overlay.ngx-datex-overlay-mobile .ngx-datex-calendar{padding:0;margin:0;border-radius:0;background:transparent;border:none;box-shadow:none;width:100%}.ngx-datex-content{display:flex;min-height:350px}.ngx-datex-ranges-sidebar{width:140px;background:#fff;border-right:1px solid #e0e0e0;padding:0;border-radius:8px 0 0 8px;display:flex;flex-direction:column}.ngx-datex-range-item{background:none;border:none;padding:8px 12px;text-align:left;cursor:pointer;font-size:12px;color:#212121;transition:all .2s ease;border-radius:0;margin:2px 0}.ngx-datex-range-item:hover{background:#f5f5f5;color:#1976d2}.ngx-datex-range-item.ngx-datex-range-active{background:#1976d2;color:#fff;font-weight:500}.ngx-datex-ranges-chips{width:100%;background:#fff;border-bottom:1px solid #e0e0e0;padding:16px 0;overflow-x:auto;overflow-y:hidden;-webkit-overflow-scrolling:touch;scrollbar-width:none;-ms-overflow-style:none}.ngx-datex-ranges-chips::-webkit-scrollbar{display:none}.ngx-datex-ranges-chips-container{display:flex;gap:12px;padding:0 16px;min-width:max-content}.ngx-datex-range-chip{background:#f8f9fa;border:1px solid #dee2e6;border-radius:20px;padding:10px 16px;font-size:14px;font-weight:500;color:#495057;cursor:pointer;transition:all .3s ease;white-space:nowrap;flex-shrink:0;min-height:40px;display:flex;align-items:center;justify-content:center;min-width:80px;text-align:center}.ngx-datex-range-chip:hover{background:#e3f2fd;border-color:#2196f3;color:#1976d2;transform:translateY(-2px);box-shadow:0 4px 12px #2196f340}.ngx-datex-range-chip.ngx-datex-range-chip-active{background:linear-gradient(135deg,#2196f3,#1976d2);border-color:#1976d2;color:#fff;font-weight:600;box-shadow:0 4px 16px #1976d266;transform:translateY(-1px)}.ngx-datex-range-chip:active{transform:translateY(0)}.ngx-datex-calendar-section{flex:1;display:flex;flex-direction:column}@media(max-width:768px){.ngx-datex-ranges-chips+.ngx-datex-calendar-section{padding-top:0}.ngx-datex-calendar-section{padding:0;background:#fff;width:100%;margin:0}}.ngx-datex-calendars{display:flex;padding:0;gap:0;flex:1}@media(max-width:768px){.ngx-datex-calendars{flex-direction:column;gap:16px;padding:12px;width:100%;margin:0;box-sizing:border-box}}.ngx-datex-calendar{flex:1;padding:8px;min-width:0}.ngx-datex-calendar.ngx-datex-calendar-right{padding:8px}@media(max-width:768px){.ngx-datex-calendar{padding:0;background:transparent;border:none;box-shadow:none;width:100%;flex:none;min-width:unset;box-sizing:border-box;margin:0}.ngx-datex-calendar .ngx-datex-calendar-grid{width:100%;margin:0}.ngx-datex-calendar .ngx-datex-week{width:100%;display:table-row;margin:0}.ngx-datex-calendar .ngx-datex-day{display:table-cell;width:14.28%;margin:0;padding:0}.ngx-datex-calendar .ngx-datex-days-header{width:100%;margin:0 0 8px}.ngx-datex-calendar .ngx-datex-calendar-header{margin:0 0 16px;padding:0}}.ngx-datex-calendar.ngx-datex-calendar-single{padding:8px}.ngx-datex-calendar-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:8px;padding:0;height:28px}@media(max-width:768px){.ngx-datex-calendar-header{height:40px;margin-bottom:12px;padding:0 4px}}.ngx-datex-nav-button{background:none;border:none;cursor:pointer;padding:4px;border-radius:3px;display:flex;align-items:center;justify-content:center;transition:background-color .2s ease;color:#757575;width:32px;height:28px}@media(max-width:768px){.ngx-datex-nav-button{width:40px;height:40px;border-radius:6px;background:#f5f5f5}.ngx-datex-nav-button mat-icon{font-size:18px}}.ngx-datex-nav-button:hover:not(:disabled){background:#f5f5f5;color:#1976d2}.ngx-datex-nav-button:disabled{opacity:.5;cursor:not-allowed}.ngx-datex-nav-button mat-icon{font-size:18px;width:18px;height:18px;color:inherit}.ngx-datex-month-year{display:flex;align-items:center;gap:4px;font-weight:500;font-size:12px}@media(max-width:768px){.ngx-datex-month-year{font-size:16px;font-weight:600;gap:8px}}.ngx-datex-month-select,.ngx-datex-year-select{border:1px solid #e0e0e0;border-radius:3px;padding:1px 2px;font-size:12px;background:#fff;cursor:pointer;color:#212121;height:auto;margin:0;outline:none;appearance:auto;-webkit-appearance:menulist;-moz-appearance:menulist;min-height:20px}@media(max-width:768px){.ngx-datex-month-select,.ngx-datex-year-select{font-size:14px;padding:4px 8px;border-radius:6px;min-height:32px;border:2px solid #e0e0e0}}.ngx-datex-month-select:focus,.ngx-datex-year-select:focus{outline:none;border-color:#1976d2;box-shadow:0 0 0 2px #1976d233}.ngx-datex-month-select option,.ngx-datex-year-select option{background:#fff;color:#212121}.ngx-datex-month-select{margin-right:2%;width:56%;min-width:50px}.ngx-datex-year-select{width:40%;min-width:50px}.ngx-datex-days-header{display:table;width:100%;margin-bottom:0;border-collapse:collapse;border-spacing:0}@media(max-width:768px){.ngx-datex-days-header{width:100%;margin:0 0 8px;padding:0}}.ngx-datex-days-header-row{display:table-row}.ngx-datex-day-header{display:table-cell;text-align:center;font-size:12px;font-weight:500;width:14.28%;height:28px;line-height:28px;background:#fff;color:#212121;box-sizing:border-box;vertical-align:middle;padding:0}@media(max-width:768px){.ngx-datex-day-header{height:36px;line-height:36px;font-size:13px;font-weight:600;background:#f8f9fa;color:#495057;border-bottom:1px solid #e0e0e0}}.ngx-datex-calendar-grid{display:table;width:100%;margin:0;padding:0;border-collapse:collapse;border-spacing:0;table-layout:fixed}@media(max-width:768px){.ngx-datex-calendar-grid{border-radius:0;overflow:visible;width:100%;margin:0;padding:0}}.ngx-datex-week{display:table-row}.ngx-datex-day{display:table-cell;width:14.28%;height:28px;text-align:center;font-size:12px;padding:0;margin:0;box-sizing:border-box;border:1px solid transparent;background:#fff;cursor:pointer}@media(max-width:768px){.ngx-datex-day{height:40px;font-size:15px;font-weight:500;line-height:40px;vertical-align:middle;border:1px solid #f0f0f0}}.ngx-datex-day{transition:all .15s ease;position:relative;border-radius:4px;line-height:28px;white-space:nowrap;color:#212121;vertical-align:middle}.ngx-datex-day:hover:not(:disabled){background:#f5f5f5;color:#212121}.ngx-datex-day.ngx-datex-day-other-month{background-color:#fff!important;border:1px solid transparent!important;border-color:transparent!important;color:#999!important}.ngx-datex-day.ngx-datex-day-other-month.ngx-datex-day-in-range,.ngx-datex-day.ngx-datex-day-other-month.ngx-datex-day-range-start,.ngx-datex-day.ngx-datex-day-other-month.ngx-datex-day-range-end,.ngx-datex-day.ngx-datex-day-other-month.ngx-datex-day-selected,.ngx-datex-day.ngx-datex-day-other-month.ngx-datex-day-active,.ngx-datex-day.ngx-datex-day-other-month.ngx-datex-day-hover-range,.ngx-datex-day.ngx-datex-day-other-month.ngx-datex-day-hover-start,.ngx-datex-day.ngx-datex-day-other-month.ngx-datex-day-hover-end{background-color:#fff!important;border:1px solid transparent!important;border-color:transparent!important;color:#999!important;border-radius:4px!important;box-shadow:none!important;outline:none!important}.ngx-datex-day.ngx-datex-day-other-month:hover{background-color:#eee!important;border:1px solid transparent!important;border-color:transparent!important;color:inherit!important;box-shadow:none!important;outline:none!important}.ngx-datex-day.ngx-datex-day-today{font-weight:700;background:#1976d21a;color:#1976d2}.ngx-datex-day.ngx-datex-day-selected,.ngx-datex-day.ngx-datex-day-active{background-color:#1976d2;border-color:transparent;color:#fff}.ngx-datex-day.ngx-datex-day-selected:hover,.ngx-datex-day.ngx-datex-day-active:hover{background-color:#1976d2;opacity:.9}.ngx-datex-day.ngx-datex-day-in-range{background-color:#ebf4f8;border-color:transparent;color:#212121;border-radius:0}.ngx-datex-day.ngx-datex-day-range-start{background-color:#1976d2;border-color:transparent;color:#fff;border-radius:4px 0 0 4px}.ngx-datex-day.ngx-datex-day-range-end{background-color:#1976d2;border-color:transparent;color:#fff;border-radius:0 4px 4px 0}.ngx-datex-day.ngx-datex-day-range-start.ngx-datex-day-range-end{border-radius:4px}.ngx-datex-day.ngx-datex-day-hover-range{background-color:#ebf4f8;border-color:transparent;color:#212121;border-radius:0}.ngx-datex-day.ngx-datex-day-hover-start{background-color:#1976d2;border-color:transparent;color:#fff;border-radius:4px 0 0 4px}.ngx-datex-day.ngx-datex-day-hover-end{background-color:#1976d2;border-color:transparent;color:#fff;border-radius:0 4px 4px 0}.ngx-datex-day.ngx-datex-day-hover-start.ngx-datex-day-hover-end{border-radius:4px}.ngx-datex-day.ngx-datex-day-disabled{color:#bdbdbd;cursor:not-allowed;text-decoration:line-through}.ngx-datex-day.ngx-datex-day-disabled:hover{background:#fff;color:#bdbdbd}.ngx-datex-footer{padding:8px;border-top:1px solid #e0e0e0;background:#fff;border-radius:0 0 8px 8px;display:flex;justify-content:space-between;align-items:center;line-height:10px;vertical-align:middle;margin:0}.ngx-datex-selected-range{font-size:12px;color:#212121;font-weight:400;display:inline-block;padding-right:8px}.ngx-datex-footer-buttons{display:flex;gap:4px}.ngx-datex-cancel-button,.ngx-datex-apply-button{min-width:70px;font-weight:700;font-size:12px;padding:7px 8px;border-radius:3px;border:1px solid transparent;cursor:pointer;transition:all .15s ease-in-out;margin-left:4px}.ngx-datex-cancel-button{background-color:#dc3545;border-color:#dc3545;color:#fff}.ngx-datex-cancel-button:hover:not(:disabled){background-color:#dc3545;border-color:#dc3545;opacity:.8}.ngx-datex-apply-button{background-color:#22c55e;border-color:#22c55e;color:#fff}.ngx-datex-apply-button:disabled{background:#9ca3af;border-color:#9ca3af;opacity:.6;cursor:not-allowed}.ngx-datex-apply-button:hover:not(:disabled){background-color:#22c55e;border-color:#22c55e;opacity:.8}.ngx-datex-mobile .ngx-datex-overlay{position:fixed;inset:auto 0 0;width:100%;max-width:100vw;min-width:100vw;border-radius:12px 12px 0 0;margin:0;max-height:85vh;overflow-y:auto;z-index:999999;box-shadow:0 -4px 20px #00000026}.ngx-datex-mobile .ngx-datex-overlay:before,.ngx-datex-mobile .ngx-datex-overlay:after{display:none}.ngx-datex-mobile .ngx-datex-content{flex-direction:column}.ngx-datex-mobile .ngx-datex-ranges-sidebar{width:100%;border-right:none;border-bottom:1px solid #e0e0e0;border-radius:12px 12px 0 0;flex-direction:row;overflow-x:auto;padding:8px 0;-webkit-overflow-scrolling:touch;scrollbar-width:none;-ms-overflow-style:none}.ngx-datex-mobile .ngx-datex-ranges-sidebar::-webkit-scrollbar{display:none}.ngx-datex-mobile .ngx-datex-range-item{white-space:nowrap;padding:8px 12px;margin:0 4px;border-radius:20px;background-color:#f5f5f5;border:1px solid #e0e0e0;flex:0 0 auto;min-width:60px;text-align:center;user-select:none;-webkit-user-select:none;-webkit-tap-highlight-color:transparent}.ngx-datex-mobile .ngx-datex-range-item:hover{background-color:#1976d21a;transform:translateY(-1px);box-shadow:0 2px 4px #0000001a}.ngx-datex-mobile .ngx-datex-range-item.ngx-datex-range-active{background-color:#1976d2;color:#fff;border-color:#1976d2;font-weight:600}.ngx-datex-mobile .ngx-datex-calendars{flex-direction:column;padding:6px}.ngx-datex-mobile .ngx-datex-calendar{width:100%;padding:6px}.ngx-datex-mobile .ngx-datex-days-header{display:table;width:100%;border-collapse:collapse;border-spacing:0}.ngx-datex-mobile .ngx-datex-days-header-row{display:table-row}.ngx-datex-mobile .ngx-datex-calendar-grid{display:table;width:100%;border-collapse:collapse;border-spacing:0}.ngx-datex-mobile .ngx-datex-week{display:table-row}.ngx-datex-mobile .ngx-datex-week-number-header,.ngx-datex-mobile .ngx-datex-week-number{display:none}.ngx-datex-mobile .ngx-datex-day-header{display:table-cell;height:28px;line-height:28px;font-size:11px;font-weight:600;text-align:center;padding:0;margin:0;box-sizing:border-box;vertical-align:middle}.ngx-datex-mobile .ngx-datex-day{display:table-cell;height:40px;line-height:40px;font-size:15px;font-weight:500;border-radius:6px;-webkit-tap-highlight-color:transparent;touch-action:manipulation;text-align:center;vertical-align:middle}.ngx-datex-mobile .ngx-datex-footer{display:none}.ngx-datex-mobile-header{width:100%;background-color:#fff;color:#333;border-radius:16px 16px 0 0;box-sizing:border-box;position:relative;z-index:3;border-bottom:1px solid #e0e0e0;display:flex;flex-direction:column;gap:0;padding:12px 0 8px}.ngx-datex-mobile-header-content{padding:0 16px 8px;text-align:center;flex:1}.ngx-datex-mobile-header-buttons{display:flex;justify-content:space-between;align-items:center;padding:8px 16px 0;gap:12px;border-top:1px solid #f0f0f0;background-color:#fff}.ngx-datex-mobile-selected-range{display:block;font-size:16px;font-weight:600;line-height:1.3;color:#1a1a1a;margin-bottom:2px}.ngx-datex-mobile-range-label{display:block;font-size:13px;font-weight:500;color:#2196f3;margin-top:2px}.ngx-datex-mobile-cancel-button,.ngx-datex-mobile-apply-button{flex:1;padding:10px 16px;border-radius:8px;font-size:14px;font-weight:600;cursor:pointer;transition:all .3s ease;border:2px solid transparent;text-transform:none;letter-spacing:0;min-height:40px;display:flex;align-items:center;justify-content:center}.ngx-datex-mobile-cancel-button{background:#dc3545;border:2px solid #dc3545;color:#fff}.ngx-datex-mobile-cancel-button:hover{background:#c82333;border-color:#c82333;transform:translateY(-1px);box-shadow:0 4px 12px #dc35454d}.ngx-datex-mobile-cancel-button:active{transform:translateY(0);box-shadow:0 2px 4px #dc354533}.ngx-datex-mobile-apply-button{background:#28a745;border:2px solid #28a745;color:#fff}.ngx-datex-mobile-apply-button:hover{background:#218838;border-color:#218838;transform:translateY(-1px);box-shadow:0 4px 16px #28a7454d}.ngx-datex-mobile-apply-button:active{transform:translateY(0);box-shadow:0 2px 8px #28a74533}.ngx-datex-mobile-apply-button:disabled{background:#e0e0e0;border-color:#e0e0e0;color:#9e9e9e;cursor:not-allowed;transform:none;box-shadow:none}.ngx-datex-day:focus{outline:2px solid #1976d2;outline-offset:-2px;z-index:1}.ngx-datex-nav-button:focus{outline:2px solid #1976d2;outline-offset:2px}.ngx-datex-range-item:focus{outline:2px solid #1976d2;outline-offset:-2px}.ngx-datex-overlay{animation:fadeInScale .2s ease-out}.ngx-datex-overlay:before,.ngx-datex-overlay:after{animation:fadeInArrow .2s ease-out .1s both}.ngx-datex-overlay.ngx-datex-arrow-align-center.ngx-datex-arrow-up:before,.ngx-datex-overlay.ngx-datex-arrow-align-center.ngx-datex-arrow-up:after,.ngx-datex-overlay.ngx-datex-arrow-align-center.ngx-datex-arrow-down:before,.ngx-datex-overlay.ngx-datex-arrow-align-center.ngx-datex-arrow-down:after{animation:fadeInArrowCenterHorizontal .2s ease-out .1s both}.ngx-datex-overlay.ngx-datex-arrow-align-center.ngx-datex-arrow-left:before,.ngx-datex-overlay.ngx-datex-arrow-align-center.ngx-datex-arrow-left:after,.ngx-datex-overlay.ngx-datex-arrow-align-center.ngx-datex-arrow-right:before,.ngx-datex-overlay.ngx-datex-arrow-align-center.ngx-datex-arrow-right:after{animation:fadeInArrowCenterVertical .2s ease-out .1s both}@keyframes fadeInScale{0%{opacity:0;transform:scale(.95) translateY(-10px)}to{opacity:1;transform:scale(1) translateY(0)}}@keyframes fadeInArrow{0%{opacity:0;transform:scale(.8)}to{opacity:1;transform:scale(1)}}@keyframes fadeInArrowCenterHorizontal{0%{opacity:0;transform:translate(-50%) scale(.8)}to{opacity:1;transform:translate(-50%) scale(1)}}@keyframes fadeInArrowCenterVertical{0%{opacity:0;transform:translateY(-50%) scale(.8)}to{opacity:1;transform:translateY(-50%) scale(1)}}.ngx-datex-overlay.ngx-datex-single .ngx-datex-calendar.ngx-datex-calendar-right{display:none}.ngx-datex-overlay.ngx-datex-single .ngx-datex-calendar.ngx-datex-calendar-left{padding:8px}.ngx-datex-time-picker{border-top:1px solid #e0e0e0;padding:8px;background:#fff;margin-top:8px}.ngx-datex-time-controls{display:flex;justify-content:center;align-items:center;gap:4px;flex-wrap:nowrap}.ngx-datex-time-separator{font-size:14px;font-weight:700;color:#333;margin:0 2px;line-height:1}.ngx-datex-time-select{border:1px solid #ccc;border-radius:3px;padding:2px 4px;font-size:11px;background:#fff;color:#333;outline:none;cursor:pointer;appearance:auto;-webkit-appearance:menulist;-moz-appearance:menulist;min-height:22px;line-height:1}.ngx-datex-time-select:focus{border-color:#1976d2;box-shadow:0 0 0 1px #1976d233}.ngx-datex-time-select:disabled{background:#f5f5f5;color:#999;cursor:not-allowed;opacity:.6}.ngx-datex-time-select option{padding:2px 4px;font-size:11px}.ngx-datex-hour-select,.ngx-datex-minute-select{width:50px;text-align:center}.ngx-datex-ampm-select{width:45px;text-align:center}.ngx-datex-mobile .ngx-datex-time-picker{margin-top:12px;padding:12px}.ngx-datex-mobile .ngx-datex-time-controls{gap:6px}.ngx-datex-mobile .ngx-datex-time-separator{font-size:16px;margin:0 4px}.ngx-datex-mobile .ngx-datex-time-select{font-size:14px;padding:4px 6px;min-height:32px;border-radius:4px}.ngx-datex-mobile .ngx-datex-hour-select,.ngx-datex-mobile .ngx-datex-minute-select{width:60px}.ngx-datex-mobile .ngx-datex-ampm-select{width:55px}@media(min-width:564px)and (max-width:768px){.ngx-datex-overlay.ngx-datex-overlay-mobile{position:fixed;width:90%;max-width:500px;inset:auto auto 20px 50%;transform:translate(-50%);border-radius:8px}}@media(min-width:564px){.ngx-datex-mobile .ngx-datex-overlay{position:absolute;inset:100% auto auto 0;width:650px;max-width:90vw;border-radius:8px;margin-top:7px;max-height:none}.ngx-datex-mobile .ngx-datex-overlay:before,.ngx-datex-mobile .ngx-datex-overlay:after{display:inline-block}.ngx-datex-mobile .ngx-datex-footer{display:flex}.ngx-datex-mobile .ngx-datex-mobile-header{display:none}.ngx-datex-mobile .ngx-datex-ranges-sidebar{width:140px;flex-direction:column;border-right:1px solid #e0e0e0;border-bottom:none;border-radius:8px 0 0 8px;overflow-x:visible;padding:0}.ngx-datex-mobile .ngx-datex-range-item{white-space:normal;padding:8px 12px;margin:2px 0;border-radius:0;background:none;border:none;text-align:left;min-width:auto}.ngx-datex-mobile .ngx-datex-calendars{flex-direction:row;padding:0}.ngx-datex-mobile .ngx-datex-calendar{padding:8px 0 8px 8px}.ngx-datex-mobile .ngx-datex-calendar.ngx-datex-calendar-right{padding:8px 8px 8px 0}.ngx-datex-mobile .ngx-datex-days-header{display:table;width:100%;border-collapse:collapse;border-spacing:0}.ngx-datex-mobile .ngx-datex-days-header-row{display:table-row}.ngx-datex-mobile .ngx-datex-calendar-grid{display:table;width:100%;border-collapse:collapse;border-spacing:0}.ngx-datex-mobile .ngx-datex-week{display:table-row}.ngx-datex-mobile .ngx-datex-week-number-header,.ngx-datex-mobile .ngx-datex-week-number{display:none}.ngx-datex-mobile .ngx-datex-day-header{display:table-cell;width:14.28%;height:28px;line-height:28px;font-size:12px;box-sizing:border-box;text-align:center;vertical-align:middle}.ngx-datex-mobile .ngx-datex-day{display:table-cell;width:14.28%;height:28px;line-height:28px;font-size:12px;border-radius:4px;box-sizing:border-box;text-align:center;vertical-align:middle}}.ngx-datex-day.ngx-datex-day-other-month.ngx-datex-day-selected,.ngx-datex-day.ngx-datex-day-other-month.ngx-datex-day-active,.ngx-datex-day.ngx-datex-day-other-month.ngx-datex-day-in-range,.ngx-datex-day.ngx-datex-day-other-month.ngx-datex-day-range-start,.ngx-datex-day.ngx-datex-day-other-month.ngx-datex-day-range-end,.ngx-datex-day.ngx-datex-day-other-month.ngx-datex-day-hover-range,.ngx-datex-day.ngx-datex-day-other-month.ngx-datex-day-hover-start,.ngx-datex-day.ngx-datex-day-other-month.ngx-datex-day-hover-end{background-color:#fff!important;border:1px solid transparent!important;border-color:transparent!important;color:#999!important;border-radius:4px!important;box-shadow:none!important;outline:none!important}.ngx-datex-day.ngx-datex-day-other-month:hover,.ngx-datex-day.ngx-datex-day-other-month:focus{background-color:#eee!important;border:1px solid transparent!important;border-color:transparent!important;color:#999!important;box-shadow:none!important;outline:none!important}.ngx-datex-day.ngx-datex-day-other-month:focus{background-color:#eee!important;border:1px solid transparent!important;border-color:transparent!important;color:#999!important;box-shadow:none!important;outline:none!important}\n"], dependencies: [{ kind: "ngmodule", type: MatButtonModule }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i2.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "component", type: i2.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i2.MatLabel, selector: "mat-label" }, { kind: "directive", type: i2.MatError, selector: "mat-error, [matError]", inputs: ["id"] }, { kind: "directive", type: i2.MatPrefix, selector: "[matPrefix], [matIconPrefix], [matTextPrefix]", inputs: ["matTextPrefix"] }, { kind: "directive", type: i2.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "ngmodule", type: MatCheckboxModule }, { kind: "component", type: i3.MatCheckbox, selector: "mat-checkbox", inputs: ["aria-label", "aria-labelledby", "aria-describedby", "aria-expanded", "aria-controls", "aria-owns", "id", "required", "labelPosition", "name", "value", "disableRipple", "tabIndex", "color", "disabledInteractive", "checked", "disabled", "indeterminate"], outputs: ["change", "indeterminateChange"], exportAs: ["matCheckbox"] }, { kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
3524
|
+
}
|
|
3525
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: NgxDatex, decorators: [{
|
|
3526
|
+
type: Component,
|
|
3527
|
+
args: [{ selector: 'ngx-datex', imports: [
|
|
3528
|
+
MatButtonModule,
|
|
3529
|
+
MatIconModule,
|
|
3530
|
+
MatInputModule,
|
|
3531
|
+
MatFormFieldModule,
|
|
3532
|
+
MatCheckboxModule,
|
|
3533
|
+
NgClass,
|
|
3534
|
+
], providers: [
|
|
3535
|
+
{
|
|
3536
|
+
provide: NG_VALUE_ACCESSOR,
|
|
3537
|
+
useExisting: forwardRef(() => NgxDatex),
|
|
3538
|
+
multi: true,
|
|
3539
|
+
},
|
|
3540
|
+
NgxDatexOverlayService,
|
|
3541
|
+
NgxDatexTimePickerService,
|
|
3542
|
+
NgxDatexCalendarService,
|
|
3543
|
+
], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"ngx-datex-container\" [class.ngx-datex-mobile]=\"isMobileDevice()\">\r\n <!-- Input Field -->\r\n <mat-form-field\r\n [appearance]=\"appearance()\"\r\n [floatLabel]=\"floatLabel()\"\r\n class=\"ngx-datex-input-field\"\r\n >\r\n @if (label()) {\r\n <mat-label>{{ label() }}</mat-label>\r\n }\r\n\r\n <!-- Checkbox como prefix -->\r\n @if (showCheckbox() && checkboxPosition() === 'prefix') {\r\n <mat-checkbox\r\n matPrefix\r\n [checked]=\"checkboxValue()\"\r\n (change)=\"onCheckboxChange($event.checked)\"\r\n class=\"ngx-datex-checkbox ngx-datex-checkbox-prefix\"\r\n >\r\n </mat-checkbox>\r\n }\r\n\r\n <!-- \u00CDcono como prefix -->\r\n @if (showCalendarIcon() && calendarIconPosition() === 'prefix') {\r\n <mat-icon\r\n matPrefix\r\n class=\"ngx-datex-calendar-icon\"\r\n [class.ngx-datex-icon-active]=\"isOpen()\"\r\n (click)=\"toggle()\"\r\n (keydown.enter)=\"toggle()\"\r\n (keydown.space)=\"toggle()\"\r\n tabindex=\"0\"\r\n role=\"button\"\r\n [attr.aria-label]=\"'Open calendar'\"\r\n >\r\n {{ calendarIcon() }}\r\n </mat-icon>\r\n }\r\n\r\n <input\r\n matInput\r\n #inputElement\r\n [value]=\"displayValue()\"\r\n [placeholder]=\"placeholder()\"\r\n [readonly]=\"readonly()\"\r\n [disabled]=\"disabled()\"\r\n [attr.aria-label]=\"ariaLabel()\"\r\n [attr.aria-describedby]=\"ariaDescribedBy()\"\r\n (click)=\"onInputClick()\"\r\n (focus)=\"onInputFocus()\"\r\n (blur)=\"onInputBlur()\"\r\n (keydown)=\"onInputKeydown($event)\"\r\n (keyup)=\"onInputKeyup()\"\r\n autocomplete=\"off\"\r\n />\r\n\r\n <!-- \u00CDcono como suffix -->\r\n @if (showCalendarIcon() && calendarIconPosition() === 'suffix') {\r\n <mat-icon\r\n matSuffix\r\n class=\"ngx-datex-calendar-icon\"\r\n [class.ngx-datex-icon-active]=\"isOpen()\"\r\n (click)=\"toggle()\"\r\n (keydown.enter)=\"toggle()\"\r\n (keydown.space)=\"toggle()\"\r\n tabindex=\"0\"\r\n role=\"button\"\r\n [attr.aria-label]=\"'Open calendar'\"\r\n >\r\n {{ calendarIcon() }}\r\n </mat-icon>\r\n }\r\n\r\n <!-- Checkbox como suffix -->\r\n @if (showCheckbox() && checkboxPosition() === 'suffix') {\r\n <mat-checkbox\r\n matSuffix\r\n [checked]=\"checkboxValue()\"\r\n (change)=\"onCheckboxChange($event.checked)\"\r\n class=\"ngx-datex-checkbox ngx-datex-checkbox-suffix\"\r\n >\r\n </mat-checkbox>\r\n }\r\n\r\n @if (hasError()) {\r\n <mat-error>{{ errorMessage() }}</mat-error>\r\n }\r\n </mat-form-field>\r\n\r\n <!-- Calendar Template for CDK Overlay -->\r\n <ng-template #calendarTemplate>\r\n <div\r\n class=\"ngx-datex-overlay\"\r\n [class.ngx-datex-overlay-mobile]=\"isMobileDevice()\"\r\n [class.ngx-datex-single]=\"singleDatePicker()\"\r\n [class.ngx-datex-arrow-up]=\"arrowDirection() === 'up'\"\r\n [class.ngx-datex-arrow-down]=\"arrowDirection() === 'down'\"\r\n [class.ngx-datex-arrow-left]=\"arrowDirection() === 'left'\"\r\n [class.ngx-datex-arrow-right]=\"arrowDirection() === 'right'\"\r\n [class.ngx-datex-arrow-align-start]=\"arrowAlignment() === 'start'\"\r\n [class.ngx-datex-arrow-align-center]=\"arrowAlignment() === 'center'\"\r\n [class.ngx-datex-arrow-align-end]=\"arrowAlignment() === 'end'\"\r\n role=\"dialog\"\r\n aria-modal=\"true\"\r\n [attr.aria-label]=\"headerTitle()\"\r\n tabindex=\"-1\"\r\n (click)=\"$event.stopPropagation()\"\r\n (keydown)=\"onCalendarKeydown($event)\"\r\n >\r\n <!-- Mobile Header (only shown on mobile) -->\r\n @if (isMobileDevice()) {\r\n <div class=\"ngx-datex-mobile-header\">\r\n <div class=\"ngx-datex-mobile-header-content\">\r\n <div class=\"ngx-datex-mobile-selected-range\">\r\n @if (hasStartDate()) {\r\n {{ formattedSelectedRange() }}\r\n } @else {\r\n Selecciona un rango de fechas\r\n }\r\n </div>\r\n <div class=\"ngx-datex-mobile-range-label\">\r\n @if (currentLabel()) {\r\n {{ currentLabel() }}\r\n }\r\n </div>\r\n </div>\r\n <div class=\"ngx-datex-mobile-header-buttons\">\r\n <button\r\n type=\"button\"\r\n class=\"ngx-datex-mobile-cancel-button\"\r\n [ngClass]=\"[buttonClasses(), cancelButtonClasses()]\"\r\n (click)=\"cancel()\"\r\n >\r\n {{ locale().cancelLabel || 'Cancelar' }}\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"ngx-datex-mobile-apply-button\"\r\n [ngClass]=\"[buttonClasses(), applyButtonClasses()]\"\r\n [disabled]=\"!canApplyValue()\"\r\n (click)=\"apply()\"\r\n >\r\n {{ locale().applyLabel || 'Aplicar' }}\r\n </button>\r\n </div>\r\n </div>\r\n }\r\n\r\n <div class=\"ngx-datex-content\">\r\n <!-- Predefined Ranges - Desktop: Sidebar, Mobile: Horizontal Chips -->\r\n @if (showRanges()) {\r\n <!-- Desktop Ranges Sidebar -->\r\n @if (!isMobileDevice()) {\r\n <div class=\"ngx-datex-ranges-sidebar\">\r\n @for (rangeEntry of rangeEntries(); track rangeEntry.label) {\r\n <button\r\n type=\"button\"\r\n class=\"ngx-datex-range-item\"\r\n [class.ngx-datex-range-active]=\"isRangeActive(rangeEntry.label)\"\r\n (click)=\"selectRange(rangeEntry.label)\"\r\n >\r\n {{ rangeEntry.label }}\r\n </button>\r\n }\r\n <!-- Rango Personalizado -->\r\n @if (showCustomRangeLabel()) {\r\n <button\r\n type=\"button\"\r\n class=\"ngx-datex-range-item\"\r\n [class.ngx-datex-range-active]=\"isCustomRange()\"\r\n (click)=\"selectCustomRange()\"\r\n >\r\n {{ locale().customRangeLabel || 'Rango Personalizado' }}\r\n </button>\r\n }\r\n </div>\r\n }\r\n\r\n <!-- Mobile Ranges Chips -->\r\n @if (isMobileDevice()) {\r\n <div class=\"ngx-datex-ranges-chips\">\r\n <div class=\"ngx-datex-ranges-chips-container\">\r\n @for (rangeEntry of rangeEntries(); track rangeEntry.label) {\r\n <button\r\n type=\"button\"\r\n class=\"ngx-datex-range-chip\"\r\n [class.ngx-datex-range-chip-active]=\"isRangeActive(rangeEntry.label)\"\r\n (click)=\"selectRange(rangeEntry.label)\"\r\n >\r\n {{ rangeEntry.label }}\r\n </button>\r\n }\r\n <!-- Rango Personalizado -->\r\n @if (showCustomRangeLabel()) {\r\n <button\r\n type=\"button\"\r\n class=\"ngx-datex-range-chip\"\r\n [class.ngx-datex-range-chip-active]=\"isCustomRange()\"\r\n (click)=\"selectCustomRange()\"\r\n >\r\n {{ locale().customRangeLabel || 'Personalizado' }}\r\n </button>\r\n }\r\n </div>\r\n </div>\r\n }\r\n }\r\n\r\n <!-- Calendar Section -->\r\n <div class=\"ngx-datex-calendar-section\">\r\n <!-- Calendar Container -->\r\n <div class=\"ngx-datex-calendars\" (mouseleave)=\"onCalendarMouseLeave()\">\r\n <!-- Left Calendar -->\r\n <div\r\n class=\"ngx-datex-calendar ngx-datex-calendar-left\"\r\n [class.ngx-datex-calendar-single]=\"singleDatePicker()\"\r\n >\r\n <div class=\"ngx-datex-calendar-header\">\r\n <button\r\n type=\"button\"\r\n class=\"ngx-datex-nav-button\"\r\n (click)=\"previousMonth('left')\"\r\n [disabled]=\"!canNavigatePrevious('left')\"\r\n aria-label=\"Mes anterior\"\r\n >\r\n <mat-icon>chevron_left</mat-icon>\r\n </button>\r\n\r\n <div class=\"ngx-datex-month-year\">\r\n @if (showDropdowns()) {\r\n <select\r\n class=\"ngx-datex-month-select\"\r\n [value]=\"leftCalendarMonthValue()\"\r\n (change)=\"onMonthChange('left', $event)\"\r\n aria-label=\"Seleccionar mes\"\r\n >\r\n @for (month of monthNames(); track $index) {\r\n <option [value]=\"$index\">{{ month }}</option>\r\n }\r\n </select>\r\n\r\n <select\r\n class=\"ngx-datex-year-select\"\r\n [value]=\"leftCalendarYearValue()\"\r\n (change)=\"onYearChange('left', $event)\"\r\n aria-label=\"Seleccionar a\u00F1o\"\r\n >\r\n @for (year of availableYears(); track year) {\r\n <option [value]=\"year\">{{ year }}</option>\r\n }\r\n </select>\r\n } @else {\r\n <span class=\"ngx-datex-month-year-display\">\r\n {{ monthNames()[leftCalendarMonthValue()] }} {{ leftCalendarYearValue() }}\r\n </span>\r\n }\r\n </div>\r\n\r\n <button\r\n type=\"button\"\r\n class=\"ngx-datex-nav-button\"\r\n (click)=\"nextMonth('left')\"\r\n [disabled]=\"!canNavigateNext('left')\"\r\n aria-label=\"Mes siguiente\"\r\n >\r\n <mat-icon>chevron_right</mat-icon>\r\n </button>\r\n </div>\r\n\r\n <!-- Days of Week Header -->\r\n <div class=\"ngx-datex-days-header\">\r\n <div class=\"ngx-datex-days-header-row\">\r\n @for (day of daysOfWeek(); track day) {\r\n <div class=\"ngx-datex-day-header\">{{ day }}</div>\r\n }\r\n </div>\r\n </div>\r\n\r\n <!-- Calendar Grid -->\r\n <div class=\"ngx-datex-calendar-grid\">\r\n @for (week of leftCalendarMatrix(); track $index) {\r\n <div class=\"ngx-datex-week\">\r\n @for (date of week; track date.getTime()) {\r\n <button\r\n type=\"button\"\r\n class=\"ngx-datex-day\"\r\n [class.ngx-datex-day-other-month]=\"isOtherMonth(date, leftCalendarMonth())\"\r\n [class.ngx-datex-day-today]=\"isToday(date)\"\r\n [class.ngx-datex-day-selected]=\"isSelected(date)\"\r\n [class.ngx-datex-day-active]=\"isSelected(date)\"\r\n [class.ngx-datex-day-in-range]=\"isInRange(date)\"\r\n [class.ngx-datex-day-range-start]=\"isRangeStart(date)\"\r\n [class.ngx-datex-day-range-end]=\"isRangeEnd(date)\"\r\n [class.ngx-datex-day-hover-range]=\"\r\n isHovered(date) && !isHoverStart(date) && !isHoverEnd(date)\r\n \"\r\n [class.ngx-datex-day-hover-start]=\"isHoverStart(date)\"\r\n [class.ngx-datex-day-hover-end]=\"isHoverEnd(date)\"\r\n [class.ngx-datex-day-disabled]=\"isDisabled(date)\"\r\n [disabled]=\"isDisabled(date)\"\r\n (click)=\"selectDate(date)\"\r\n (mouseenter)=\"onDateHover(date)\"\r\n [attr.aria-label]=\"formatDateForAria(date)\"\r\n >\r\n {{ date.getDate() }}\r\n </button>\r\n }\r\n </div>\r\n }\r\n </div>\r\n\r\n <!-- Time Picker for Left Calendar -->\r\n @if (timePicker()) {\r\n <div class=\"ngx-datex-time-picker\">\r\n <div class=\"ngx-datex-time-controls\">\r\n <select\r\n class=\"ngx-datex-time-select ngx-datex-hour-select\"\r\n [value]=\"selectedStartHour()\"\r\n (change)=\"onStartHourChange($event)\"\r\n aria-label=\"Seleccionar hora\"\r\n >\r\n @if (timePicker24Hour()) {\r\n @for (hourOption of availableStartHours(); track hourOption.value) {\r\n <option\r\n [value]=\"hourOption.value\"\r\n [selected]=\"selectedStartHour() === hourOption.value\"\r\n [disabled]=\"hourOption.disabled\"\r\n >\r\n {{ hourOption.value.toString().padStart(2, '0') }}\r\n </option>\r\n }\r\n } @else {\r\n @for (hourOption of availableStartHours(); track hourOption.value) {\r\n <option\r\n [value]=\"hourOption.value\"\r\n [selected]=\"selectedStartHour() === hourOption.value\"\r\n [disabled]=\"hourOption.disabled\"\r\n >\r\n {{ hourOption.value }}\r\n </option>\r\n }\r\n }\r\n </select>\r\n\r\n <span class=\"ngx-datex-time-separator\">:</span>\r\n\r\n <select\r\n class=\"ngx-datex-time-select ngx-datex-minute-select\"\r\n [value]=\"selectedStartMinute()\"\r\n (change)=\"onStartMinuteChange($event)\"\r\n aria-label=\"Seleccionar minuto\"\r\n >\r\n @for (minuteOption of availableStartMinutes(); track minuteOption.value) {\r\n <option\r\n [value]=\"minuteOption.value\"\r\n [selected]=\"selectedStartMinute() === minuteOption.value\"\r\n [disabled]=\"minuteOption.disabled\"\r\n >\r\n {{ minuteOption.value.toString().padStart(2, '0') }}\r\n </option>\r\n }\r\n </select>\r\n\r\n @if (!timePicker24Hour()) {\r\n <select\r\n class=\"ngx-datex-time-select ngx-datex-ampm-select\"\r\n [value]=\"selectedStartAmPm()\"\r\n (change)=\"onStartAmPmChange($event)\"\r\n aria-label=\"Seleccionar AM/PM\"\r\n >\r\n <option value=\"AM\">AM</option>\r\n <option value=\"PM\">PM</option>\r\n </select>\r\n }\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n\r\n <!-- Right Calendar (for range picker) -->\r\n @if (!singleDatePicker()) {\r\n <div class=\"ngx-datex-calendar ngx-datex-calendar-right\">\r\n <div class=\"ngx-datex-calendar-header\">\r\n <button\r\n type=\"button\"\r\n class=\"ngx-datex-nav-button\"\r\n (click)=\"previousMonth('right')\"\r\n [disabled]=\"!canNavigatePrevious('right')\"\r\n aria-label=\"Mes anterior\"\r\n >\r\n <mat-icon>chevron_left</mat-icon>\r\n </button>\r\n\r\n <div class=\"ngx-datex-month-year\">\r\n @if (showDropdowns()) {\r\n <select\r\n class=\"ngx-datex-month-select\"\r\n [value]=\"rightCalendarMonthValue()\"\r\n (change)=\"onMonthChange('right', $event)\"\r\n aria-label=\"Seleccionar mes\"\r\n >\r\n @for (month of monthNames(); track $index) {\r\n <option [value]=\"$index\">{{ month }}</option>\r\n }\r\n </select>\r\n\r\n <select\r\n class=\"ngx-datex-year-select\"\r\n [value]=\"rightCalendarYearValue()\"\r\n (change)=\"onYearChange('right', $event)\"\r\n aria-label=\"Seleccionar a\u00F1o\"\r\n >\r\n @for (year of availableYears(); track year) {\r\n <option [value]=\"year\">{{ year }}</option>\r\n }\r\n </select>\r\n } @else {\r\n <span class=\"ngx-datex-month-year-display\">\r\n {{ monthNames()[rightCalendarMonthValue()] }} {{ rightCalendarYearValue() }}\r\n </span>\r\n }\r\n </div>\r\n\r\n <button\r\n type=\"button\"\r\n class=\"ngx-datex-nav-button\"\r\n (click)=\"nextMonth('right')\"\r\n [disabled]=\"!canNavigateNext('right')\"\r\n aria-label=\"Mes siguiente\"\r\n >\r\n <mat-icon>chevron_right</mat-icon>\r\n </button>\r\n </div>\r\n\r\n <!-- Days of Week Header -->\r\n <div class=\"ngx-datex-days-header\">\r\n <div class=\"ngx-datex-days-header-row\">\r\n @for (day of daysOfWeek(); track day) {\r\n <div class=\"ngx-datex-day-header\">{{ day }}</div>\r\n }\r\n </div>\r\n </div>\r\n\r\n <!-- Calendar Grid -->\r\n <div class=\"ngx-datex-calendar-grid\">\r\n @for (week of rightCalendarMatrix(); track $index) {\r\n <div class=\"ngx-datex-week\">\r\n @for (date of week; track date.getTime()) {\r\n <button\r\n type=\"button\"\r\n class=\"ngx-datex-day\"\r\n [class.ngx-datex-day-other-month]=\"\r\n isOtherMonth(date, rightCalendarMonth())\r\n \"\r\n [class.ngx-datex-day-today]=\"isToday(date)\"\r\n [class.ngx-datex-day-selected]=\"isSelected(date)\"\r\n [class.ngx-datex-day-active]=\"isSelected(date)\"\r\n [class.ngx-datex-day-in-range]=\"isInRange(date)\"\r\n [class.ngx-datex-day-range-start]=\"isRangeStart(date)\"\r\n [class.ngx-datex-day-range-end]=\"isRangeEnd(date)\"\r\n [class.ngx-datex-day-hover-range]=\"\r\n isHovered(date) && !isHoverStart(date) && !isHoverEnd(date)\r\n \"\r\n [class.ngx-datex-day-hover-start]=\"isHoverStart(date)\"\r\n [class.ngx-datex-day-hover-end]=\"isHoverEnd(date)\"\r\n [class.ngx-datex-day-disabled]=\"isDisabled(date)\"\r\n [disabled]=\"isDisabled(date)\"\r\n (click)=\"selectDate(date)\"\r\n (mouseenter)=\"onDateHover(date)\"\r\n [attr.aria-label]=\"formatDateForAria(date)\"\r\n >\r\n {{ date.getDate() }}\r\n </button>\r\n }\r\n </div>\r\n }\r\n </div>\r\n\r\n <!-- Time Picker for Right Calendar -->\r\n @if (timePicker() && !singleDatePicker()) {\r\n <div class=\"ngx-datex-time-picker\">\r\n <div class=\"ngx-datex-time-controls\">\r\n <select\r\n class=\"ngx-datex-time-select ngx-datex-hour-select\"\r\n [value]=\"selectedEndHour()\"\r\n [disabled]=\"!hasEndDate()\"\r\n (change)=\"onEndHourChange($event)\"\r\n aria-label=\"Seleccionar hora\"\r\n >\r\n @if (timePicker24Hour()) {\r\n @for (hourOption of availableEndHours(); track hourOption.value) {\r\n <option\r\n [value]=\"hourOption.value\"\r\n [selected]=\"selectedEndHour() === hourOption.value\"\r\n [disabled]=\"hourOption.disabled\"\r\n >\r\n {{ hourOption.value.toString().padStart(2, '0') }}\r\n </option>\r\n }\r\n } @else {\r\n @for (hourOption of availableEndHours(); track hourOption.value) {\r\n <option\r\n [value]=\"hourOption.value\"\r\n [selected]=\"selectedEndHour() === hourOption.value\"\r\n [disabled]=\"hourOption.disabled\"\r\n >\r\n {{ hourOption.value }}\r\n </option>\r\n }\r\n }\r\n </select>\r\n\r\n <span class=\"ngx-datex-time-separator\">:</span>\r\n\r\n <select\r\n class=\"ngx-datex-time-select ngx-datex-minute-select\"\r\n [value]=\"selectedEndMinute()\"\r\n [disabled]=\"!hasEndDate()\"\r\n (change)=\"onEndMinuteChange($event)\"\r\n aria-label=\"Seleccionar minuto\"\r\n >\r\n @for (minuteOption of availableEndMinutes(); track minuteOption.value) {\r\n <option\r\n [value]=\"minuteOption.value\"\r\n [selected]=\"selectedEndMinute() === minuteOption.value\"\r\n [disabled]=\"minuteOption.disabled\"\r\n >\r\n {{ minuteOption.value.toString().padStart(2, '0') }}\r\n </option>\r\n }\r\n </select>\r\n\r\n @if (!timePicker24Hour()) {\r\n <select\r\n class=\"ngx-datex-time-select ngx-datex-ampm-select\"\r\n [value]=\"selectedEndAmPm()\"\r\n [disabled]=\"!hasEndDate()\"\r\n (change)=\"onEndAmPmChange($event)\"\r\n aria-label=\"Seleccionar AM/PM\"\r\n >\r\n <option value=\"AM\">AM</option>\r\n <option value=\"PM\">PM</option>\r\n </select>\r\n }\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n }\r\n </div>\r\n\r\n <!-- Footer with selected range and buttons (desktop only) -->\r\n @if (!isMobileDevice()) {\r\n <div class=\"ngx-datex-footer\">\r\n <div class=\"ngx-datex-selected-range\">\r\n @if (hasStartDate()) {\r\n {{ formattedSelectedRange() }}\r\n } @else {\r\n Selecciona un rango de fechas\r\n }\r\n </div>\r\n <div class=\"ngx-datex-footer-buttons\">\r\n <button\r\n type=\"button\"\r\n class=\"ngx-datex-cancel-button\"\r\n [ngClass]=\"[buttonClasses(), cancelButtonClasses()]\"\r\n (click)=\"cancel()\"\r\n >\r\n {{ locale().cancelLabel || 'Cancelar' }}\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"ngx-datex-apply-button\"\r\n [ngClass]=\"[buttonClasses(), applyButtonClasses()]\"\r\n [disabled]=\"!canApplyValue()\"\r\n (click)=\"apply()\"\r\n >\r\n {{ locale().applyLabel || 'Aplicar' }}\r\n </button>\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n </div>\r\n </ng-template>\r\n</div>\r\n", styles: [".ngx-datex-container{position:relative;display:inline-block;width:100%}.ngx-datex-input-field{width:100%}.ngx-datex-calendar-icon{cursor:pointer;transition:color .2s ease;font-size:20px}.ngx-datex-calendar-icon:hover,.ngx-datex-calendar-icon.ngx-datex-icon-active{color:var(--ngx-datex-primary-color, #1976d2)}.ngx-datex-overlay-panel{z-index:1000}.ngx-datex-mobile-overlay{z-index:1001}.ngx-datex-overlay{background:#fff;border-radius:8px;box-shadow:0 5px 15px #00000026,0 2px 4px #0000001f;border:1px solid #e0e0e0;width:650px;font-family:Roboto,sans-serif;font-size:12px;line-height:1em;position:relative;color:#212121}.ngx-datex-overlay:before,.ngx-datex-overlay:after{position:absolute;content:\"\";display:none;z-index:10}.ngx-datex-overlay.ngx-datex-arrow-down:before{display:block;border-left:7px solid transparent;border-right:7px solid transparent;border-bottom:7px solid #e0e0e0;top:-7px}.ngx-datex-overlay.ngx-datex-arrow-down:after{display:block;border-left:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid #ffffff;top:-6px}.ngx-datex-overlay.ngx-datex-arrow-up:before{display:block;border-left:7px solid transparent;border-right:7px solid transparent;border-top:7px solid #e0e0e0;bottom:-7px}.ngx-datex-overlay.ngx-datex-arrow-up:after{display:block;border-left:6px solid transparent;border-right:6px solid transparent;border-top:6px solid #ffffff;bottom:-6px}.ngx-datex-overlay.ngx-datex-arrow-right:before{display:block;border-top:7px solid transparent;border-bottom:7px solid transparent;border-right:7px solid #e0e0e0;left:-7px}.ngx-datex-overlay.ngx-datex-arrow-right:after{display:block;border-top:6px solid transparent;border-bottom:6px solid transparent;border-right:6px solid #ffffff;left:-6px}.ngx-datex-overlay.ngx-datex-arrow-left:before{display:block;border-top:7px solid transparent;border-bottom:7px solid transparent;border-left:7px solid #e0e0e0;right:-7px}.ngx-datex-overlay.ngx-datex-arrow-left:after{display:block;border-top:6px solid transparent;border-bottom:6px solid transparent;border-left:6px solid #ffffff;right:-6px}.ngx-datex-overlay.ngx-datex-arrow-up.ngx-datex-arrow-align-start:before,.ngx-datex-overlay.ngx-datex-arrow-down.ngx-datex-arrow-align-start:before{left:20px}.ngx-datex-overlay.ngx-datex-arrow-up.ngx-datex-arrow-align-start:after,.ngx-datex-overlay.ngx-datex-arrow-down.ngx-datex-arrow-align-start:after{left:21px}.ngx-datex-overlay.ngx-datex-arrow-up.ngx-datex-arrow-align-center:before,.ngx-datex-overlay.ngx-datex-arrow-down.ngx-datex-arrow-align-center:before{left:50%;transform:translate(-50%)}.ngx-datex-overlay.ngx-datex-arrow-up.ngx-datex-arrow-align-center:after,.ngx-datex-overlay.ngx-datex-arrow-down.ngx-datex-arrow-align-center:after{left:50%;transform:translate(-50%)}.ngx-datex-overlay.ngx-datex-arrow-up.ngx-datex-arrow-align-end:before,.ngx-datex-overlay.ngx-datex-arrow-down.ngx-datex-arrow-align-end:before{right:20px}.ngx-datex-overlay.ngx-datex-arrow-up.ngx-datex-arrow-align-end:after,.ngx-datex-overlay.ngx-datex-arrow-down.ngx-datex-arrow-align-end:after{right:21px}.ngx-datex-overlay.ngx-datex-arrow-left.ngx-datex-arrow-align-start:before,.ngx-datex-overlay.ngx-datex-arrow-right.ngx-datex-arrow-align-start:before{top:20px}.ngx-datex-overlay.ngx-datex-arrow-left.ngx-datex-arrow-align-start:after,.ngx-datex-overlay.ngx-datex-arrow-right.ngx-datex-arrow-align-start:after{top:21px}.ngx-datex-overlay.ngx-datex-arrow-left.ngx-datex-arrow-align-center:before,.ngx-datex-overlay.ngx-datex-arrow-right.ngx-datex-arrow-align-center:before{top:50%;transform:translateY(-50%)}.ngx-datex-overlay.ngx-datex-arrow-left.ngx-datex-arrow-align-center:after,.ngx-datex-overlay.ngx-datex-arrow-right.ngx-datex-arrow-align-center:after{top:50%;transform:translateY(-50%)}.ngx-datex-overlay.ngx-datex-arrow-left.ngx-datex-arrow-align-end:before,.ngx-datex-overlay.ngx-datex-arrow-right.ngx-datex-arrow-align-end:before{bottom:20px}.ngx-datex-overlay.ngx-datex-arrow-left.ngx-datex-arrow-align-end:after,.ngx-datex-overlay.ngx-datex-arrow-right.ngx-datex-arrow-align-end:after{bottom:21px}.ngx-datex-overlay.ngx-datex-single{width:300px}.ngx-datex-overlay.ngx-datex-single .ngx-datex-ranges-sidebar{display:none}.ngx-datex-overlay.ngx-datex-overlay-mobile{width:100vw;max-width:100vw;min-width:100vw;border-radius:16px 16px 0 0;max-height:90vh;overflow-y:auto;box-shadow:0 -8px 32px #0003;border:none;margin:0;padding:0}.ngx-datex-overlay.ngx-datex-overlay-mobile:before,.ngx-datex-overlay.ngx-datex-overlay-mobile:after{display:none}.ngx-datex-overlay.ngx-datex-overlay-mobile .ngx-datex-content{min-height:auto;padding:0;margin:0;width:100%;display:flex;flex-direction:column}.ngx-datex-overlay.ngx-datex-overlay-mobile .ngx-datex-calendar-section{padding:0;margin:0;width:100%;flex:1}.ngx-datex-overlay.ngx-datex-overlay-mobile .ngx-datex-calendars{padding:16px;gap:20px;margin:0;width:100%;box-sizing:border-box}.ngx-datex-overlay.ngx-datex-overlay-mobile .ngx-datex-calendar{padding:0;margin:0;border-radius:0;background:transparent;border:none;box-shadow:none;width:100%}.ngx-datex-content{display:flex;min-height:350px}.ngx-datex-ranges-sidebar{width:140px;background:#fff;border-right:1px solid #e0e0e0;padding:0;border-radius:8px 0 0 8px;display:flex;flex-direction:column}.ngx-datex-range-item{background:none;border:none;padding:8px 12px;text-align:left;cursor:pointer;font-size:12px;color:#212121;transition:all .2s ease;border-radius:0;margin:2px 0}.ngx-datex-range-item:hover{background:#f5f5f5;color:#1976d2}.ngx-datex-range-item.ngx-datex-range-active{background:#1976d2;color:#fff;font-weight:500}.ngx-datex-ranges-chips{width:100%;background:#fff;border-bottom:1px solid #e0e0e0;padding:16px 0;overflow-x:auto;overflow-y:hidden;-webkit-overflow-scrolling:touch;scrollbar-width:none;-ms-overflow-style:none}.ngx-datex-ranges-chips::-webkit-scrollbar{display:none}.ngx-datex-ranges-chips-container{display:flex;gap:12px;padding:0 16px;min-width:max-content}.ngx-datex-range-chip{background:#f8f9fa;border:1px solid #dee2e6;border-radius:20px;padding:10px 16px;font-size:14px;font-weight:500;color:#495057;cursor:pointer;transition:all .3s ease;white-space:nowrap;flex-shrink:0;min-height:40px;display:flex;align-items:center;justify-content:center;min-width:80px;text-align:center}.ngx-datex-range-chip:hover{background:#e3f2fd;border-color:#2196f3;color:#1976d2;transform:translateY(-2px);box-shadow:0 4px 12px #2196f340}.ngx-datex-range-chip.ngx-datex-range-chip-active{background:linear-gradient(135deg,#2196f3,#1976d2);border-color:#1976d2;color:#fff;font-weight:600;box-shadow:0 4px 16px #1976d266;transform:translateY(-1px)}.ngx-datex-range-chip:active{transform:translateY(0)}.ngx-datex-calendar-section{flex:1;display:flex;flex-direction:column}@media(max-width:768px){.ngx-datex-ranges-chips+.ngx-datex-calendar-section{padding-top:0}.ngx-datex-calendar-section{padding:0;background:#fff;width:100%;margin:0}}.ngx-datex-calendars{display:flex;padding:0;gap:0;flex:1}@media(max-width:768px){.ngx-datex-calendars{flex-direction:column;gap:16px;padding:12px;width:100%;margin:0;box-sizing:border-box}}.ngx-datex-calendar{flex:1;padding:8px;min-width:0}.ngx-datex-calendar.ngx-datex-calendar-right{padding:8px}@media(max-width:768px){.ngx-datex-calendar{padding:0;background:transparent;border:none;box-shadow:none;width:100%;flex:none;min-width:unset;box-sizing:border-box;margin:0}.ngx-datex-calendar .ngx-datex-calendar-grid{width:100%;margin:0}.ngx-datex-calendar .ngx-datex-week{width:100%;display:table-row;margin:0}.ngx-datex-calendar .ngx-datex-day{display:table-cell;width:14.28%;margin:0;padding:0}.ngx-datex-calendar .ngx-datex-days-header{width:100%;margin:0 0 8px}.ngx-datex-calendar .ngx-datex-calendar-header{margin:0 0 16px;padding:0}}.ngx-datex-calendar.ngx-datex-calendar-single{padding:8px}.ngx-datex-calendar-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:8px;padding:0;height:28px}@media(max-width:768px){.ngx-datex-calendar-header{height:40px;margin-bottom:12px;padding:0 4px}}.ngx-datex-nav-button{background:none;border:none;cursor:pointer;padding:4px;border-radius:3px;display:flex;align-items:center;justify-content:center;transition:background-color .2s ease;color:#757575;width:32px;height:28px}@media(max-width:768px){.ngx-datex-nav-button{width:40px;height:40px;border-radius:6px;background:#f5f5f5}.ngx-datex-nav-button mat-icon{font-size:18px}}.ngx-datex-nav-button:hover:not(:disabled){background:#f5f5f5;color:#1976d2}.ngx-datex-nav-button:disabled{opacity:.5;cursor:not-allowed}.ngx-datex-nav-button mat-icon{font-size:18px;width:18px;height:18px;color:inherit}.ngx-datex-month-year{display:flex;align-items:center;gap:4px;font-weight:500;font-size:12px}@media(max-width:768px){.ngx-datex-month-year{font-size:16px;font-weight:600;gap:8px}}.ngx-datex-month-select,.ngx-datex-year-select{border:1px solid #e0e0e0;border-radius:3px;padding:1px 2px;font-size:12px;background:#fff;cursor:pointer;color:#212121;height:auto;margin:0;outline:none;appearance:auto;-webkit-appearance:menulist;-moz-appearance:menulist;min-height:20px}@media(max-width:768px){.ngx-datex-month-select,.ngx-datex-year-select{font-size:14px;padding:4px 8px;border-radius:6px;min-height:32px;border:2px solid #e0e0e0}}.ngx-datex-month-select:focus,.ngx-datex-year-select:focus{outline:none;border-color:#1976d2;box-shadow:0 0 0 2px #1976d233}.ngx-datex-month-select option,.ngx-datex-year-select option{background:#fff;color:#212121}.ngx-datex-month-select{margin-right:2%;width:56%;min-width:50px}.ngx-datex-year-select{width:40%;min-width:50px}.ngx-datex-days-header{display:table;width:100%;margin-bottom:0;border-collapse:collapse;border-spacing:0}@media(max-width:768px){.ngx-datex-days-header{width:100%;margin:0 0 8px;padding:0}}.ngx-datex-days-header-row{display:table-row}.ngx-datex-day-header{display:table-cell;text-align:center;font-size:12px;font-weight:500;width:14.28%;height:28px;line-height:28px;background:#fff;color:#212121;box-sizing:border-box;vertical-align:middle;padding:0}@media(max-width:768px){.ngx-datex-day-header{height:36px;line-height:36px;font-size:13px;font-weight:600;background:#f8f9fa;color:#495057;border-bottom:1px solid #e0e0e0}}.ngx-datex-calendar-grid{display:table;width:100%;margin:0;padding:0;border-collapse:collapse;border-spacing:0;table-layout:fixed}@media(max-width:768px){.ngx-datex-calendar-grid{border-radius:0;overflow:visible;width:100%;margin:0;padding:0}}.ngx-datex-week{display:table-row}.ngx-datex-day{display:table-cell;width:14.28%;height:28px;text-align:center;font-size:12px;padding:0;margin:0;box-sizing:border-box;border:1px solid transparent;background:#fff;cursor:pointer}@media(max-width:768px){.ngx-datex-day{height:40px;font-size:15px;font-weight:500;line-height:40px;vertical-align:middle;border:1px solid #f0f0f0}}.ngx-datex-day{transition:all .15s ease;position:relative;border-radius:4px;line-height:28px;white-space:nowrap;color:#212121;vertical-align:middle}.ngx-datex-day:hover:not(:disabled){background:#f5f5f5;color:#212121}.ngx-datex-day.ngx-datex-day-other-month{background-color:#fff!important;border:1px solid transparent!important;border-color:transparent!important;color:#999!important}.ngx-datex-day.ngx-datex-day-other-month.ngx-datex-day-in-range,.ngx-datex-day.ngx-datex-day-other-month.ngx-datex-day-range-start,.ngx-datex-day.ngx-datex-day-other-month.ngx-datex-day-range-end,.ngx-datex-day.ngx-datex-day-other-month.ngx-datex-day-selected,.ngx-datex-day.ngx-datex-day-other-month.ngx-datex-day-active,.ngx-datex-day.ngx-datex-day-other-month.ngx-datex-day-hover-range,.ngx-datex-day.ngx-datex-day-other-month.ngx-datex-day-hover-start,.ngx-datex-day.ngx-datex-day-other-month.ngx-datex-day-hover-end{background-color:#fff!important;border:1px solid transparent!important;border-color:transparent!important;color:#999!important;border-radius:4px!important;box-shadow:none!important;outline:none!important}.ngx-datex-day.ngx-datex-day-other-month:hover{background-color:#eee!important;border:1px solid transparent!important;border-color:transparent!important;color:inherit!important;box-shadow:none!important;outline:none!important}.ngx-datex-day.ngx-datex-day-today{font-weight:700;background:#1976d21a;color:#1976d2}.ngx-datex-day.ngx-datex-day-selected,.ngx-datex-day.ngx-datex-day-active{background-color:#1976d2;border-color:transparent;color:#fff}.ngx-datex-day.ngx-datex-day-selected:hover,.ngx-datex-day.ngx-datex-day-active:hover{background-color:#1976d2;opacity:.9}.ngx-datex-day.ngx-datex-day-in-range{background-color:#ebf4f8;border-color:transparent;color:#212121;border-radius:0}.ngx-datex-day.ngx-datex-day-range-start{background-color:#1976d2;border-color:transparent;color:#fff;border-radius:4px 0 0 4px}.ngx-datex-day.ngx-datex-day-range-end{background-color:#1976d2;border-color:transparent;color:#fff;border-radius:0 4px 4px 0}.ngx-datex-day.ngx-datex-day-range-start.ngx-datex-day-range-end{border-radius:4px}.ngx-datex-day.ngx-datex-day-hover-range{background-color:#ebf4f8;border-color:transparent;color:#212121;border-radius:0}.ngx-datex-day.ngx-datex-day-hover-start{background-color:#1976d2;border-color:transparent;color:#fff;border-radius:4px 0 0 4px}.ngx-datex-day.ngx-datex-day-hover-end{background-color:#1976d2;border-color:transparent;color:#fff;border-radius:0 4px 4px 0}.ngx-datex-day.ngx-datex-day-hover-start.ngx-datex-day-hover-end{border-radius:4px}.ngx-datex-day.ngx-datex-day-disabled{color:#bdbdbd;cursor:not-allowed;text-decoration:line-through}.ngx-datex-day.ngx-datex-day-disabled:hover{background:#fff;color:#bdbdbd}.ngx-datex-footer{padding:8px;border-top:1px solid #e0e0e0;background:#fff;border-radius:0 0 8px 8px;display:flex;justify-content:space-between;align-items:center;line-height:10px;vertical-align:middle;margin:0}.ngx-datex-selected-range{font-size:12px;color:#212121;font-weight:400;display:inline-block;padding-right:8px}.ngx-datex-footer-buttons{display:flex;gap:4px}.ngx-datex-cancel-button,.ngx-datex-apply-button{min-width:70px;font-weight:700;font-size:12px;padding:7px 8px;border-radius:3px;border:1px solid transparent;cursor:pointer;transition:all .15s ease-in-out;margin-left:4px}.ngx-datex-cancel-button{background-color:#dc3545;border-color:#dc3545;color:#fff}.ngx-datex-cancel-button:hover:not(:disabled){background-color:#dc3545;border-color:#dc3545;opacity:.8}.ngx-datex-apply-button{background-color:#22c55e;border-color:#22c55e;color:#fff}.ngx-datex-apply-button:disabled{background:#9ca3af;border-color:#9ca3af;opacity:.6;cursor:not-allowed}.ngx-datex-apply-button:hover:not(:disabled){background-color:#22c55e;border-color:#22c55e;opacity:.8}.ngx-datex-mobile .ngx-datex-overlay{position:fixed;inset:auto 0 0;width:100%;max-width:100vw;min-width:100vw;border-radius:12px 12px 0 0;margin:0;max-height:85vh;overflow-y:auto;z-index:999999;box-shadow:0 -4px 20px #00000026}.ngx-datex-mobile .ngx-datex-overlay:before,.ngx-datex-mobile .ngx-datex-overlay:after{display:none}.ngx-datex-mobile .ngx-datex-content{flex-direction:column}.ngx-datex-mobile .ngx-datex-ranges-sidebar{width:100%;border-right:none;border-bottom:1px solid #e0e0e0;border-radius:12px 12px 0 0;flex-direction:row;overflow-x:auto;padding:8px 0;-webkit-overflow-scrolling:touch;scrollbar-width:none;-ms-overflow-style:none}.ngx-datex-mobile .ngx-datex-ranges-sidebar::-webkit-scrollbar{display:none}.ngx-datex-mobile .ngx-datex-range-item{white-space:nowrap;padding:8px 12px;margin:0 4px;border-radius:20px;background-color:#f5f5f5;border:1px solid #e0e0e0;flex:0 0 auto;min-width:60px;text-align:center;user-select:none;-webkit-user-select:none;-webkit-tap-highlight-color:transparent}.ngx-datex-mobile .ngx-datex-range-item:hover{background-color:#1976d21a;transform:translateY(-1px);box-shadow:0 2px 4px #0000001a}.ngx-datex-mobile .ngx-datex-range-item.ngx-datex-range-active{background-color:#1976d2;color:#fff;border-color:#1976d2;font-weight:600}.ngx-datex-mobile .ngx-datex-calendars{flex-direction:column;padding:6px}.ngx-datex-mobile .ngx-datex-calendar{width:100%;padding:6px}.ngx-datex-mobile .ngx-datex-days-header{display:table;width:100%;border-collapse:collapse;border-spacing:0}.ngx-datex-mobile .ngx-datex-days-header-row{display:table-row}.ngx-datex-mobile .ngx-datex-calendar-grid{display:table;width:100%;border-collapse:collapse;border-spacing:0}.ngx-datex-mobile .ngx-datex-week{display:table-row}.ngx-datex-mobile .ngx-datex-week-number-header,.ngx-datex-mobile .ngx-datex-week-number{display:none}.ngx-datex-mobile .ngx-datex-day-header{display:table-cell;height:28px;line-height:28px;font-size:11px;font-weight:600;text-align:center;padding:0;margin:0;box-sizing:border-box;vertical-align:middle}.ngx-datex-mobile .ngx-datex-day{display:table-cell;height:40px;line-height:40px;font-size:15px;font-weight:500;border-radius:6px;-webkit-tap-highlight-color:transparent;touch-action:manipulation;text-align:center;vertical-align:middle}.ngx-datex-mobile .ngx-datex-footer{display:none}.ngx-datex-mobile-header{width:100%;background-color:#fff;color:#333;border-radius:16px 16px 0 0;box-sizing:border-box;position:relative;z-index:3;border-bottom:1px solid #e0e0e0;display:flex;flex-direction:column;gap:0;padding:12px 0 8px}.ngx-datex-mobile-header-content{padding:0 16px 8px;text-align:center;flex:1}.ngx-datex-mobile-header-buttons{display:flex;justify-content:space-between;align-items:center;padding:8px 16px 0;gap:12px;border-top:1px solid #f0f0f0;background-color:#fff}.ngx-datex-mobile-selected-range{display:block;font-size:16px;font-weight:600;line-height:1.3;color:#1a1a1a;margin-bottom:2px}.ngx-datex-mobile-range-label{display:block;font-size:13px;font-weight:500;color:#2196f3;margin-top:2px}.ngx-datex-mobile-cancel-button,.ngx-datex-mobile-apply-button{flex:1;padding:10px 16px;border-radius:8px;font-size:14px;font-weight:600;cursor:pointer;transition:all .3s ease;border:2px solid transparent;text-transform:none;letter-spacing:0;min-height:40px;display:flex;align-items:center;justify-content:center}.ngx-datex-mobile-cancel-button{background:#dc3545;border:2px solid #dc3545;color:#fff}.ngx-datex-mobile-cancel-button:hover{background:#c82333;border-color:#c82333;transform:translateY(-1px);box-shadow:0 4px 12px #dc35454d}.ngx-datex-mobile-cancel-button:active{transform:translateY(0);box-shadow:0 2px 4px #dc354533}.ngx-datex-mobile-apply-button{background:#28a745;border:2px solid #28a745;color:#fff}.ngx-datex-mobile-apply-button:hover{background:#218838;border-color:#218838;transform:translateY(-1px);box-shadow:0 4px 16px #28a7454d}.ngx-datex-mobile-apply-button:active{transform:translateY(0);box-shadow:0 2px 8px #28a74533}.ngx-datex-mobile-apply-button:disabled{background:#e0e0e0;border-color:#e0e0e0;color:#9e9e9e;cursor:not-allowed;transform:none;box-shadow:none}.ngx-datex-day:focus{outline:2px solid #1976d2;outline-offset:-2px;z-index:1}.ngx-datex-nav-button:focus{outline:2px solid #1976d2;outline-offset:2px}.ngx-datex-range-item:focus{outline:2px solid #1976d2;outline-offset:-2px}.ngx-datex-overlay{animation:fadeInScale .2s ease-out}.ngx-datex-overlay:before,.ngx-datex-overlay:after{animation:fadeInArrow .2s ease-out .1s both}.ngx-datex-overlay.ngx-datex-arrow-align-center.ngx-datex-arrow-up:before,.ngx-datex-overlay.ngx-datex-arrow-align-center.ngx-datex-arrow-up:after,.ngx-datex-overlay.ngx-datex-arrow-align-center.ngx-datex-arrow-down:before,.ngx-datex-overlay.ngx-datex-arrow-align-center.ngx-datex-arrow-down:after{animation:fadeInArrowCenterHorizontal .2s ease-out .1s both}.ngx-datex-overlay.ngx-datex-arrow-align-center.ngx-datex-arrow-left:before,.ngx-datex-overlay.ngx-datex-arrow-align-center.ngx-datex-arrow-left:after,.ngx-datex-overlay.ngx-datex-arrow-align-center.ngx-datex-arrow-right:before,.ngx-datex-overlay.ngx-datex-arrow-align-center.ngx-datex-arrow-right:after{animation:fadeInArrowCenterVertical .2s ease-out .1s both}@keyframes fadeInScale{0%{opacity:0;transform:scale(.95) translateY(-10px)}to{opacity:1;transform:scale(1) translateY(0)}}@keyframes fadeInArrow{0%{opacity:0;transform:scale(.8)}to{opacity:1;transform:scale(1)}}@keyframes fadeInArrowCenterHorizontal{0%{opacity:0;transform:translate(-50%) scale(.8)}to{opacity:1;transform:translate(-50%) scale(1)}}@keyframes fadeInArrowCenterVertical{0%{opacity:0;transform:translateY(-50%) scale(.8)}to{opacity:1;transform:translateY(-50%) scale(1)}}.ngx-datex-overlay.ngx-datex-single .ngx-datex-calendar.ngx-datex-calendar-right{display:none}.ngx-datex-overlay.ngx-datex-single .ngx-datex-calendar.ngx-datex-calendar-left{padding:8px}.ngx-datex-time-picker{border-top:1px solid #e0e0e0;padding:8px;background:#fff;margin-top:8px}.ngx-datex-time-controls{display:flex;justify-content:center;align-items:center;gap:4px;flex-wrap:nowrap}.ngx-datex-time-separator{font-size:14px;font-weight:700;color:#333;margin:0 2px;line-height:1}.ngx-datex-time-select{border:1px solid #ccc;border-radius:3px;padding:2px 4px;font-size:11px;background:#fff;color:#333;outline:none;cursor:pointer;appearance:auto;-webkit-appearance:menulist;-moz-appearance:menulist;min-height:22px;line-height:1}.ngx-datex-time-select:focus{border-color:#1976d2;box-shadow:0 0 0 1px #1976d233}.ngx-datex-time-select:disabled{background:#f5f5f5;color:#999;cursor:not-allowed;opacity:.6}.ngx-datex-time-select option{padding:2px 4px;font-size:11px}.ngx-datex-hour-select,.ngx-datex-minute-select{width:50px;text-align:center}.ngx-datex-ampm-select{width:45px;text-align:center}.ngx-datex-mobile .ngx-datex-time-picker{margin-top:12px;padding:12px}.ngx-datex-mobile .ngx-datex-time-controls{gap:6px}.ngx-datex-mobile .ngx-datex-time-separator{font-size:16px;margin:0 4px}.ngx-datex-mobile .ngx-datex-time-select{font-size:14px;padding:4px 6px;min-height:32px;border-radius:4px}.ngx-datex-mobile .ngx-datex-hour-select,.ngx-datex-mobile .ngx-datex-minute-select{width:60px}.ngx-datex-mobile .ngx-datex-ampm-select{width:55px}@media(min-width:564px)and (max-width:768px){.ngx-datex-overlay.ngx-datex-overlay-mobile{position:fixed;width:90%;max-width:500px;inset:auto auto 20px 50%;transform:translate(-50%);border-radius:8px}}@media(min-width:564px){.ngx-datex-mobile .ngx-datex-overlay{position:absolute;inset:100% auto auto 0;width:650px;max-width:90vw;border-radius:8px;margin-top:7px;max-height:none}.ngx-datex-mobile .ngx-datex-overlay:before,.ngx-datex-mobile .ngx-datex-overlay:after{display:inline-block}.ngx-datex-mobile .ngx-datex-footer{display:flex}.ngx-datex-mobile .ngx-datex-mobile-header{display:none}.ngx-datex-mobile .ngx-datex-ranges-sidebar{width:140px;flex-direction:column;border-right:1px solid #e0e0e0;border-bottom:none;border-radius:8px 0 0 8px;overflow-x:visible;padding:0}.ngx-datex-mobile .ngx-datex-range-item{white-space:normal;padding:8px 12px;margin:2px 0;border-radius:0;background:none;border:none;text-align:left;min-width:auto}.ngx-datex-mobile .ngx-datex-calendars{flex-direction:row;padding:0}.ngx-datex-mobile .ngx-datex-calendar{padding:8px 0 8px 8px}.ngx-datex-mobile .ngx-datex-calendar.ngx-datex-calendar-right{padding:8px 8px 8px 0}.ngx-datex-mobile .ngx-datex-days-header{display:table;width:100%;border-collapse:collapse;border-spacing:0}.ngx-datex-mobile .ngx-datex-days-header-row{display:table-row}.ngx-datex-mobile .ngx-datex-calendar-grid{display:table;width:100%;border-collapse:collapse;border-spacing:0}.ngx-datex-mobile .ngx-datex-week{display:table-row}.ngx-datex-mobile .ngx-datex-week-number-header,.ngx-datex-mobile .ngx-datex-week-number{display:none}.ngx-datex-mobile .ngx-datex-day-header{display:table-cell;width:14.28%;height:28px;line-height:28px;font-size:12px;box-sizing:border-box;text-align:center;vertical-align:middle}.ngx-datex-mobile .ngx-datex-day{display:table-cell;width:14.28%;height:28px;line-height:28px;font-size:12px;border-radius:4px;box-sizing:border-box;text-align:center;vertical-align:middle}}.ngx-datex-day.ngx-datex-day-other-month.ngx-datex-day-selected,.ngx-datex-day.ngx-datex-day-other-month.ngx-datex-day-active,.ngx-datex-day.ngx-datex-day-other-month.ngx-datex-day-in-range,.ngx-datex-day.ngx-datex-day-other-month.ngx-datex-day-range-start,.ngx-datex-day.ngx-datex-day-other-month.ngx-datex-day-range-end,.ngx-datex-day.ngx-datex-day-other-month.ngx-datex-day-hover-range,.ngx-datex-day.ngx-datex-day-other-month.ngx-datex-day-hover-start,.ngx-datex-day.ngx-datex-day-other-month.ngx-datex-day-hover-end{background-color:#fff!important;border:1px solid transparent!important;border-color:transparent!important;color:#999!important;border-radius:4px!important;box-shadow:none!important;outline:none!important}.ngx-datex-day.ngx-datex-day-other-month:hover,.ngx-datex-day.ngx-datex-day-other-month:focus{background-color:#eee!important;border:1px solid transparent!important;border-color:transparent!important;color:#999!important;box-shadow:none!important;outline:none!important}.ngx-datex-day.ngx-datex-day-other-month:focus{background-color:#eee!important;border:1px solid transparent!important;border-color:transparent!important;color:#999!important;box-shadow:none!important;outline:none!important}\n"] }]
|
|
3544
|
+
}], ctorParameters: () => [], propDecorators: { inputElement: [{
|
|
3545
|
+
type: ViewChild,
|
|
3546
|
+
args: ['inputElement']
|
|
3547
|
+
}], calendarTemplate: [{
|
|
3548
|
+
type: ViewChild,
|
|
3549
|
+
args: ['calendarTemplate']
|
|
3550
|
+
}], config: [{ type: i0.Input, args: [{ isSignal: true, alias: "config", required: false }] }], locale: [{ type: i0.Input, args: [{ isSignal: true, alias: "locale", required: false }] }], theme: [{ type: i0.Input, args: [{ isSignal: true, alias: "theme", required: false }] }], appearance: [{ type: i0.Input, args: [{ isSignal: true, alias: "appearance", required: false }] }], floatLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "floatLabel", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], calendarIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "calendarIcon", required: false }] }], showCalendarIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "showCalendarIcon", required: false }] }], calendarIconPosition: [{ type: i0.Input, args: [{ isSignal: true, alias: "calendarIconPosition", required: false }] }], showCheckbox: [{ type: i0.Input, args: [{ isSignal: true, alias: "showCheckbox", required: false }] }], checkboxPosition: [{ type: i0.Input, args: [{ isSignal: true, alias: "checkboxPosition", required: false }] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], showHeader: [{ type: i0.Input, args: [{ isSignal: true, alias: "showHeader", required: false }] }], showFooter: [{ type: i0.Input, args: [{ isSignal: true, alias: "showFooter", required: false }] }], singleDatePicker: [{ type: i0.Input, args: [{ isSignal: true, alias: "singleDatePicker", required: false }] }], autoApply: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoApply", required: false }] }], showDropdowns: [{ type: i0.Input, args: [{ isSignal: true, alias: "showDropdowns", required: false }] }], timePicker: [{ type: i0.Input, args: [{ isSignal: true, alias: "timePicker", required: false }] }], timePicker24Hour: [{ type: i0.Input, args: [{ isSignal: true, alias: "timePicker24Hour", required: false }] }], timePickerIncrement: [{ type: i0.Input, args: [{ isSignal: true, alias: "timePickerIncrement", required: false }] }], timePickerSeconds: [{ type: i0.Input, args: [{ isSignal: true, alias: "timePickerSeconds", required: false }] }], linkedCalendars: [{ type: i0.Input, args: [{ isSignal: true, alias: "linkedCalendars", required: false }] }], autoUpdateInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoUpdateInput", required: false }] }], alwaysShowCalendars: [{ type: i0.Input, args: [{ isSignal: true, alias: "alwaysShowCalendars", required: false }] }], showCustomRangeLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "showCustomRangeLabel", required: false }] }], startDate: [{ type: i0.Input, args: [{ isSignal: true, alias: "startDate", required: false }] }], endDate: [{ type: i0.Input, args: [{ isSignal: true, alias: "endDate", required: false }] }], minDate: [{ type: i0.Input, args: [{ isSignal: true, alias: "minDate", required: false }] }], maxDate: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxDate", required: false }] }], maxSpan: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxSpan", required: false }] }], showWeekNumbers: [{ type: i0.Input, args: [{ isSignal: true, alias: "showWeekNumbers", required: false }] }], showISOWeekNumbers: [{ type: i0.Input, args: [{ isSignal: true, alias: "showISOWeekNumbers", required: false }] }], buttonClasses: [{ type: i0.Input, args: [{ isSignal: true, alias: "buttonClasses", required: false }] }], applyButtonClasses: [{ type: i0.Input, args: [{ isSignal: true, alias: "applyButtonClasses", required: false }] }], cancelButtonClasses: [{ type: i0.Input, args: [{ isSignal: true, alias: "cancelButtonClasses", required: false }] }], isInvalidDate: [{ type: i0.Input, args: [{ isSignal: true, alias: "isInvalidDate", required: false }] }], isCustomDate: [{ type: i0.Input, args: [{ isSignal: true, alias: "isCustomDate", required: false }] }], minYear: [{ type: i0.Input, args: [{ isSignal: true, alias: "minYear", required: false }] }], maxYear: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxYear", required: false }] }], ranges: [{ type: i0.Input, args: [{ isSignal: true, alias: "ranges", required: false }] }], opens: [{ type: i0.Input, args: [{ isSignal: true, alias: "opens", required: false }] }], drops: [{ type: i0.Input, args: [{ isSignal: true, alias: "drops", required: false }] }], headerTemplate: [{ type: i0.Input, args: [{ isSignal: true, alias: "headerTemplate", required: false }] }], footerTemplate: [{ type: i0.Input, args: [{ isSignal: true, alias: "footerTemplate", required: false }] }], dayTemplate: [{ type: i0.Input, args: [{ isSignal: true, alias: "dayTemplate", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], ariaDescribedBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaDescribedBy", required: false }] }], dateChange: [{ type: i0.Output, args: ["dateChange"] }], rangeChange: [{ type: i0.Output, args: ["rangeChange"] }], openEvent: [{ type: i0.Output, args: ["openEvent"] }], closeEvent: [{ type: i0.Output, args: ["closeEvent"] }], monthChange: [{ type: i0.Output, args: ["monthChange"] }], yearChange: [{ type: i0.Output, args: ["yearChange"] }], dateHover: [{ type: i0.Output, args: ["dateHover"] }], validationError: [{ type: i0.Output, args: ["validationError"] }], checkboxChange: [{ type: i0.Output, args: ["checkboxChange"] }] } });
|
|
3551
|
+
|
|
3552
|
+
/*
|
|
3553
|
+
* Public API Surface of ngx-datex
|
|
3554
|
+
*/
|
|
3555
|
+
// Main component
|
|
3556
|
+
|
|
3557
|
+
/**
|
|
3558
|
+
* Generated bundle index. Do not edit.
|
|
3559
|
+
*/
|
|
3560
|
+
|
|
3561
|
+
export { DEFAULT_RANGES, MATERIAL_LIGHT_THEME, NgxDatex, NgxDatexCalendarService, NgxDatexOverlayService, NgxDatexService, NgxDatexTimePickerService, SPANISH_LOCALE, addDays, addMonths, endOfDay, formatDateValue, getStartOfMonth, getStartOfWeek, isBeforeDate, isMobileDevice, isSameDate, isSameDay, isSameMonth, isSameYear, isValidDate, parseDateValue, startOfDay, startOfMonth, startOfWeek };
|
|
3562
|
+
//# sourceMappingURL=ngx-datex.mjs.map
|