adminator-admin-dashboard 2.7.0 → 2.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +403 -0
- package/README.md +113 -68
- package/dist/main.js +2368 -1492
- package/dist/main.js.map +1 -1
- package/package.json +15 -4
- package/src/assets/scripts/app.js +8 -7
- package/src/assets/scripts/app.ts +757 -0
- package/src/assets/scripts/components/Chart.ts +1350 -0
- package/src/assets/scripts/components/Sidebar.ts +388 -0
- package/src/assets/scripts/datatable/index.ts +707 -0
- package/src/assets/scripts/datepicker/index.ts +699 -0
- package/src/assets/scripts/ui/index.ts +740 -0
- package/src/assets/scripts/utils/date.ts +363 -0
- package/src/assets/scripts/utils/dom.ts +513 -0
- package/src/assets/scripts/utils/theme.ts +313 -0
- package/src/assets/scripts/vectorMaps/index.ts +542 -0
- package/src/types/index.ts +236 -0
- package/RELEASE_NOTES.md +0 -92
|
@@ -0,0 +1,699 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Enhanced HTML5 DatePicker with TypeScript
|
|
3
|
+
* Modern date picker implementation using native HTML5 input[type="date"]
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import DateUtils from '../utils/date';
|
|
7
|
+
import type { ComponentInterface } from '../../types';
|
|
8
|
+
|
|
9
|
+
// Type definitions for DatePicker
|
|
10
|
+
export interface DatePickerOptions {
|
|
11
|
+
format?: string;
|
|
12
|
+
autoclose?: boolean;
|
|
13
|
+
todayHighlight?: boolean;
|
|
14
|
+
minDate?: string;
|
|
15
|
+
maxDate?: string;
|
|
16
|
+
startDate?: string;
|
|
17
|
+
endDate?: string;
|
|
18
|
+
daysOfWeekDisabled?: number[];
|
|
19
|
+
datesDisabled?: string[];
|
|
20
|
+
weekStart?: number;
|
|
21
|
+
language?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface DatePickerEvent {
|
|
25
|
+
date: string;
|
|
26
|
+
formattedDate: string;
|
|
27
|
+
dateObject: Date;
|
|
28
|
+
isValid: boolean;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface DatePickerValidation {
|
|
32
|
+
isValid: boolean;
|
|
33
|
+
errors: string[];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
declare global {
|
|
37
|
+
interface HTMLInputElement {
|
|
38
|
+
vanillaDatePicker?: VanillaDatePicker;
|
|
39
|
+
showPicker?: () => void;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Enhanced HTML5 date picker with vanilla JS
|
|
44
|
+
export class VanillaDatePicker implements ComponentInterface {
|
|
45
|
+
public name: string = 'VanillaDatePicker';
|
|
46
|
+
public element: HTMLInputElement;
|
|
47
|
+
public options: DatePickerOptions;
|
|
48
|
+
public isInitialized: boolean = false;
|
|
49
|
+
|
|
50
|
+
private wrapper: HTMLElement | null = null;
|
|
51
|
+
private todayIndicator: HTMLElement | null = null;
|
|
52
|
+
private validationErrors: string[] = [];
|
|
53
|
+
|
|
54
|
+
constructor(element: HTMLInputElement, options: DatePickerOptions = {}) {
|
|
55
|
+
this.element = element;
|
|
56
|
+
this.options = {
|
|
57
|
+
format: 'yyyy-mm-dd',
|
|
58
|
+
autoclose: true,
|
|
59
|
+
todayHighlight: true,
|
|
60
|
+
weekStart: 0,
|
|
61
|
+
language: 'en',
|
|
62
|
+
...options,
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
this.init();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
public init(): void {
|
|
69
|
+
this.convertToHTML5();
|
|
70
|
+
this.enhanceInput();
|
|
71
|
+
this.applyStyles();
|
|
72
|
+
this.bindEvents();
|
|
73
|
+
this.validateConstraints();
|
|
74
|
+
this.addTodayHighlight();
|
|
75
|
+
this.isInitialized = true;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
public destroy(): void {
|
|
79
|
+
if (this.wrapper && this.wrapper.parentNode) {
|
|
80
|
+
this.wrapper.parentNode.replaceChild(this.element, this.wrapper);
|
|
81
|
+
}
|
|
82
|
+
this.isInitialized = false;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
private convertToHTML5(): void {
|
|
86
|
+
// Convert input to HTML5 date type
|
|
87
|
+
this.element.type = 'date';
|
|
88
|
+
this.element.classList.add('form-control', 'vanilla-datepicker');
|
|
89
|
+
|
|
90
|
+
// Remove placeholder since HTML5 date inputs don't need it
|
|
91
|
+
this.element.removeAttribute('placeholder');
|
|
92
|
+
|
|
93
|
+
// Set constraints
|
|
94
|
+
if (this.options.minDate) {
|
|
95
|
+
this.element.min = this.options.minDate;
|
|
96
|
+
}
|
|
97
|
+
if (this.options.maxDate) {
|
|
98
|
+
this.element.max = this.options.maxDate;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Set default value if no value is set
|
|
102
|
+
if (!this.element.value) {
|
|
103
|
+
if (this.options.startDate) {
|
|
104
|
+
this.element.value = this.options.startDate;
|
|
105
|
+
} else if (this.options.todayHighlight) {
|
|
106
|
+
this.element.value = DateUtils.formatters.inputDate(DateUtils.now());
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Ensure proper styling
|
|
111
|
+
this.element.style.minHeight = '38px';
|
|
112
|
+
this.element.style.lineHeight = '1.5';
|
|
113
|
+
this.element.style.cursor = 'pointer';
|
|
114
|
+
|
|
115
|
+
// Add ARIA attributes
|
|
116
|
+
this.element.setAttribute('aria-label', 'Select date');
|
|
117
|
+
this.element.setAttribute('role', 'textbox');
|
|
118
|
+
this.element.setAttribute('aria-expanded', 'false');
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
private enhanceInput(): void {
|
|
122
|
+
// Create wrapper for enhanced functionality
|
|
123
|
+
const wrapper = document.createElement('div');
|
|
124
|
+
wrapper.className = 'vanilla-datepicker-wrapper';
|
|
125
|
+
wrapper.style.position = 'relative';
|
|
126
|
+
|
|
127
|
+
// Wrap the input
|
|
128
|
+
const parent = this.element.parentNode;
|
|
129
|
+
if (parent) {
|
|
130
|
+
parent.insertBefore(wrapper, this.element);
|
|
131
|
+
wrapper.appendChild(this.element);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Add calendar icon if input is in an input group
|
|
135
|
+
const inputGroup = this.element.closest('.input-group');
|
|
136
|
+
if (inputGroup) {
|
|
137
|
+
const calendarIcon = inputGroup.querySelector<HTMLElement>('.input-group-text i.ti-calendar');
|
|
138
|
+
if (calendarIcon) {
|
|
139
|
+
calendarIcon.addEventListener('click', this.handleIconClick.bind(this));
|
|
140
|
+
calendarIcon.style.cursor = 'pointer';
|
|
141
|
+
calendarIcon.setAttribute('tabindex', '0');
|
|
142
|
+
calendarIcon.setAttribute('role', 'button');
|
|
143
|
+
calendarIcon.setAttribute('aria-label', 'Open calendar');
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
this.wrapper = wrapper;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
private applyStyles(): void {
|
|
151
|
+
const styleId = 'vanilla-datepicker-styles';
|
|
152
|
+
if (document.getElementById(styleId)) return;
|
|
153
|
+
|
|
154
|
+
const style = document.createElement('style');
|
|
155
|
+
style.id = styleId;
|
|
156
|
+
style.textContent = `
|
|
157
|
+
.vanilla-datepicker-wrapper {
|
|
158
|
+
position: relative;
|
|
159
|
+
display: inline-block;
|
|
160
|
+
width: 100%;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
.vanilla-datepicker {
|
|
164
|
+
width: 100%;
|
|
165
|
+
padding: 8px 12px;
|
|
166
|
+
border: 1px solid var(--c-border, #ced4da);
|
|
167
|
+
border-radius: 4px;
|
|
168
|
+
background-color: var(--c-bkg-card, #fff);
|
|
169
|
+
color: var(--c-text-base, #495057);
|
|
170
|
+
font-size: 14px;
|
|
171
|
+
font-family: inherit;
|
|
172
|
+
transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
.vanilla-datepicker:focus {
|
|
176
|
+
outline: none;
|
|
177
|
+
border-color: var(--c-primary, #007bff);
|
|
178
|
+
box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
.vanilla-datepicker:invalid {
|
|
182
|
+
border-color: var(--c-danger, #dc3545);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
.vanilla-datepicker:invalid:focus {
|
|
186
|
+
border-color: var(--c-danger, #dc3545);
|
|
187
|
+
box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
.vanilla-datepicker::-webkit-calendar-picker-indicator {
|
|
191
|
+
cursor: pointer;
|
|
192
|
+
border-radius: 4px;
|
|
193
|
+
margin-right: 2px;
|
|
194
|
+
opacity: 0.6;
|
|
195
|
+
transition: opacity 0.15s ease-in-out;
|
|
196
|
+
filter: var(--datepicker-icon-filter, none);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
.vanilla-datepicker::-webkit-calendar-picker-indicator:hover {
|
|
200
|
+
opacity: 1;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
.vanilla-datepicker::-webkit-datetime-edit-fields-wrapper {
|
|
204
|
+
padding: 0;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
.vanilla-datepicker::-webkit-datetime-edit-month-field,
|
|
208
|
+
.vanilla-datepicker::-webkit-datetime-edit-day-field,
|
|
209
|
+
.vanilla-datepicker::-webkit-datetime-edit-year-field {
|
|
210
|
+
color: var(--c-text-base, #495057);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
.vanilla-datepicker::-webkit-datetime-edit-text {
|
|
214
|
+
color: var(--c-text-muted, #6c757d);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/* Dark mode support */
|
|
218
|
+
[data-theme="dark"] .vanilla-datepicker {
|
|
219
|
+
background-color: var(--c-bkg-card, #1f2937);
|
|
220
|
+
color: var(--c-text-base, #f9fafb);
|
|
221
|
+
border-color: var(--c-border, #374151);
|
|
222
|
+
--datepicker-icon-filter: invert(1);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
.datepicker-today-indicator {
|
|
226
|
+
position: absolute;
|
|
227
|
+
top: 4px;
|
|
228
|
+
right: 12px;
|
|
229
|
+
width: 6px;
|
|
230
|
+
height: 6px;
|
|
231
|
+
background-color: var(--c-primary, #007bff);
|
|
232
|
+
border-radius: 50%;
|
|
233
|
+
opacity: 0.8;
|
|
234
|
+
pointer-events: none;
|
|
235
|
+
z-index: 1;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
.datepicker-animation {
|
|
239
|
+
animation: datepicker-highlight 0.3s ease-in-out;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
@keyframes datepicker-highlight {
|
|
243
|
+
0% { transform: scale(1); }
|
|
244
|
+
50% { transform: scale(1.02); }
|
|
245
|
+
100% { transform: scale(1); }
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
.datepicker-error {
|
|
249
|
+
border-color: var(--c-danger, #dc3545) !important;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
.datepicker-error:focus {
|
|
253
|
+
border-color: var(--c-danger, #dc3545) !important;
|
|
254
|
+
box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25) !important;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
.datepicker-validation-feedback {
|
|
258
|
+
display: block;
|
|
259
|
+
width: 100%;
|
|
260
|
+
margin-top: 0.25rem;
|
|
261
|
+
font-size: 0.875rem;
|
|
262
|
+
color: var(--c-danger, #dc3545);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/* Responsive design */
|
|
266
|
+
@media (max-width: 768px) {
|
|
267
|
+
.vanilla-datepicker {
|
|
268
|
+
padding: 10px 12px;
|
|
269
|
+
font-size: 16px; /* Prevent zoom on iOS */
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
.vanilla-datepicker::-webkit-calendar-picker-indicator {
|
|
273
|
+
width: 20px;
|
|
274
|
+
height: 20px;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
`;
|
|
278
|
+
|
|
279
|
+
document.head.appendChild(style);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
private bindEvents(): void {
|
|
283
|
+
// Handle click events
|
|
284
|
+
this.element.addEventListener('click', this.handleClick.bind(this));
|
|
285
|
+
|
|
286
|
+
// Handle keyboard events
|
|
287
|
+
this.element.addEventListener('keydown', this.handleKeydown.bind(this));
|
|
288
|
+
|
|
289
|
+
// Handle change events
|
|
290
|
+
this.element.addEventListener('change', this.handleChange.bind(this));
|
|
291
|
+
|
|
292
|
+
// Handle focus events
|
|
293
|
+
this.element.addEventListener('focus', this.handleFocus.bind(this));
|
|
294
|
+
|
|
295
|
+
// Handle blur events
|
|
296
|
+
this.element.addEventListener('blur', this.handleBlur.bind(this));
|
|
297
|
+
|
|
298
|
+
// Handle input events for real-time validation
|
|
299
|
+
this.element.addEventListener('input', this.handleInput.bind(this));
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
private handleClick(): void {
|
|
303
|
+
this.openPicker();
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
private handleKeydown(e: KeyboardEvent): void {
|
|
307
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
308
|
+
e.preventDefault();
|
|
309
|
+
this.openPicker();
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
private handleChange(e: Event): void {
|
|
314
|
+
const target = e.target as HTMLInputElement;
|
|
315
|
+
this.handleDateChange(target.value);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
private handleFocus(): void {
|
|
319
|
+
this.element.classList.add('datepicker-animation');
|
|
320
|
+
this.element.setAttribute('aria-expanded', 'true');
|
|
321
|
+
setTimeout(() => {
|
|
322
|
+
this.element.classList.remove('datepicker-animation');
|
|
323
|
+
}, 300);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
private handleBlur(): void {
|
|
327
|
+
this.element.setAttribute('aria-expanded', 'false');
|
|
328
|
+
this.validateInput();
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
private handleInput(): void {
|
|
332
|
+
this.validateInput();
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
private handleIconClick(e: Event): void {
|
|
336
|
+
e.preventDefault();
|
|
337
|
+
e.stopPropagation();
|
|
338
|
+
this.openPicker();
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
private openPicker(): void {
|
|
342
|
+
this.element.focus();
|
|
343
|
+
|
|
344
|
+
// Try to open the native date picker
|
|
345
|
+
if (this.element.showPicker && typeof this.element.showPicker === 'function') {
|
|
346
|
+
try {
|
|
347
|
+
this.element.showPicker();
|
|
348
|
+
} catch (error) {
|
|
349
|
+
console.warn('DatePicker: showPicker not supported', error);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
private handleDateChange(selectedDate: string): void {
|
|
355
|
+
if (selectedDate) {
|
|
356
|
+
// Add visual feedback
|
|
357
|
+
this.element.classList.add('datepicker-animation');
|
|
358
|
+
setTimeout(() => {
|
|
359
|
+
this.element.classList.remove('datepicker-animation');
|
|
360
|
+
}, 300);
|
|
361
|
+
|
|
362
|
+
// Validate the date
|
|
363
|
+
const validation = this.validateDate(selectedDate);
|
|
364
|
+
|
|
365
|
+
// Create event data
|
|
366
|
+
const eventData: DatePickerEvent = {
|
|
367
|
+
date: selectedDate,
|
|
368
|
+
formattedDate: this.formatDate(selectedDate),
|
|
369
|
+
dateObject: new Date(selectedDate),
|
|
370
|
+
isValid: validation.isValid,
|
|
371
|
+
};
|
|
372
|
+
|
|
373
|
+
// Trigger custom event
|
|
374
|
+
const changeEvent = new CustomEvent('datepicker:change', {
|
|
375
|
+
detail: eventData,
|
|
376
|
+
bubbles: true,
|
|
377
|
+
});
|
|
378
|
+
this.element.dispatchEvent(changeEvent);
|
|
379
|
+
|
|
380
|
+
// Update validation state
|
|
381
|
+
this.updateValidationState(validation);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
private validateConstraints(): void {
|
|
386
|
+
// Set up date constraints based on options
|
|
387
|
+
if (this.options.datesDisabled && this.options.datesDisabled.length > 0) {
|
|
388
|
+
this.element.addEventListener('input', (e) => {
|
|
389
|
+
const target = e.target as HTMLInputElement;
|
|
390
|
+
if (this.options.datesDisabled!.includes(target.value)) {
|
|
391
|
+
this.addValidationError('This date is disabled');
|
|
392
|
+
}
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
if (this.options.daysOfWeekDisabled && this.options.daysOfWeekDisabled.length > 0) {
|
|
397
|
+
this.element.addEventListener('input', (e) => {
|
|
398
|
+
const target = e.target as HTMLInputElement;
|
|
399
|
+
const date = new Date(target.value);
|
|
400
|
+
const dayOfWeek = date.getDay();
|
|
401
|
+
if (this.options.daysOfWeekDisabled!.includes(dayOfWeek)) {
|
|
402
|
+
this.addValidationError('This day of the week is disabled');
|
|
403
|
+
}
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
private validateDate(dateString: string): DatePickerValidation {
|
|
409
|
+
const errors: string[] = [];
|
|
410
|
+
const date = new Date(dateString);
|
|
411
|
+
|
|
412
|
+
// Check if date is valid
|
|
413
|
+
if (isNaN(date.getTime())) {
|
|
414
|
+
errors.push('Invalid date format');
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// Check min/max constraints
|
|
418
|
+
if (this.options.minDate) {
|
|
419
|
+
const minDate = new Date(this.options.minDate);
|
|
420
|
+
if (date < minDate) {
|
|
421
|
+
errors.push(`Date must be after ${this.formatDate(this.options.minDate)}`);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
if (this.options.maxDate) {
|
|
426
|
+
const maxDate = new Date(this.options.maxDate);
|
|
427
|
+
if (date > maxDate) {
|
|
428
|
+
errors.push(`Date must be before ${this.formatDate(this.options.maxDate)}`);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// Check disabled dates
|
|
433
|
+
if (this.options.datesDisabled && this.options.datesDisabled.includes(dateString)) {
|
|
434
|
+
errors.push('This date is disabled');
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// Check disabled days of week
|
|
438
|
+
if (this.options.daysOfWeekDisabled && this.options.daysOfWeekDisabled.includes(date.getDay())) {
|
|
439
|
+
errors.push('This day of the week is disabled');
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
return {
|
|
443
|
+
isValid: errors.length === 0,
|
|
444
|
+
errors,
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
private validateInput(): void {
|
|
449
|
+
const value = this.element.value;
|
|
450
|
+
if (value) {
|
|
451
|
+
const validation = this.validateDate(value);
|
|
452
|
+
this.updateValidationState(validation);
|
|
453
|
+
} else {
|
|
454
|
+
this.clearValidationState();
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
private updateValidationState(validation: DatePickerValidation): void {
|
|
459
|
+
this.validationErrors = validation.errors;
|
|
460
|
+
|
|
461
|
+
// Remove existing validation feedback
|
|
462
|
+
this.clearValidationFeedback();
|
|
463
|
+
|
|
464
|
+
if (!validation.isValid) {
|
|
465
|
+
// Add error class
|
|
466
|
+
this.element.classList.add('datepicker-error');
|
|
467
|
+
|
|
468
|
+
// Add validation feedback
|
|
469
|
+
const feedback = document.createElement('div');
|
|
470
|
+
feedback.className = 'datepicker-validation-feedback';
|
|
471
|
+
feedback.textContent = validation.errors.join(', ');
|
|
472
|
+
|
|
473
|
+
if (this.wrapper) {
|
|
474
|
+
this.wrapper.appendChild(feedback);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// Set ARIA attributes
|
|
478
|
+
this.element.setAttribute('aria-invalid', 'true');
|
|
479
|
+
this.element.setAttribute('aria-describedby', 'datepicker-error');
|
|
480
|
+
feedback.id = 'datepicker-error';
|
|
481
|
+
} else {
|
|
482
|
+
this.clearValidationState();
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
private clearValidationState(): void {
|
|
487
|
+
this.element.classList.remove('datepicker-error');
|
|
488
|
+
this.element.setAttribute('aria-invalid', 'false');
|
|
489
|
+
this.element.removeAttribute('aria-describedby');
|
|
490
|
+
this.validationErrors = [];
|
|
491
|
+
this.clearValidationFeedback();
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
private clearValidationFeedback(): void {
|
|
495
|
+
if (this.wrapper) {
|
|
496
|
+
const existingFeedback = this.wrapper.querySelector('.datepicker-validation-feedback');
|
|
497
|
+
if (existingFeedback) {
|
|
498
|
+
existingFeedback.remove();
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
private addValidationError(error: string): void {
|
|
504
|
+
if (!this.validationErrors.includes(error)) {
|
|
505
|
+
this.validationErrors.push(error);
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
private addTodayHighlight(): void {
|
|
510
|
+
if (this.options.todayHighlight) {
|
|
511
|
+
const today = DateUtils.formatters.inputDate(DateUtils.now());
|
|
512
|
+
if (this.element.value === today) {
|
|
513
|
+
this.todayIndicator = document.createElement('div');
|
|
514
|
+
this.todayIndicator.className = 'datepicker-today-indicator';
|
|
515
|
+
this.todayIndicator.title = 'Today';
|
|
516
|
+
|
|
517
|
+
if (this.wrapper) {
|
|
518
|
+
this.wrapper.appendChild(this.todayIndicator);
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
private formatDate(dateString: string): string {
|
|
525
|
+
try {
|
|
526
|
+
const date = new Date(dateString);
|
|
527
|
+
return DateUtils.format(date, this.options.format || 'yyyy-mm-dd');
|
|
528
|
+
} catch (error) {
|
|
529
|
+
return dateString;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
// Public API methods
|
|
534
|
+
public setDate(dateString: string): void {
|
|
535
|
+
this.element.value = dateString;
|
|
536
|
+
this.handleDateChange(dateString);
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
public getDate(): string {
|
|
540
|
+
return this.element.value;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
public getFormattedDate(): string {
|
|
544
|
+
return this.formatDate(this.element.value);
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
public getDateObject(): Date | null {
|
|
548
|
+
return this.element.value ? new Date(this.element.value) : null;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
public isValid(): boolean {
|
|
552
|
+
return this.validationErrors.length === 0;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
public getValidationErrors(): string[] {
|
|
556
|
+
return [...this.validationErrors];
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
public setMinDate(dateString: string): void {
|
|
560
|
+
this.options.minDate = dateString;
|
|
561
|
+
this.element.min = dateString;
|
|
562
|
+
this.validateInput();
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
public setMaxDate(dateString: string): void {
|
|
566
|
+
this.options.maxDate = dateString;
|
|
567
|
+
this.element.max = dateString;
|
|
568
|
+
this.validateInput();
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
public reset(): void {
|
|
572
|
+
this.element.value = '';
|
|
573
|
+
this.clearValidationState();
|
|
574
|
+
if (this.todayIndicator) {
|
|
575
|
+
this.todayIndicator.remove();
|
|
576
|
+
this.todayIndicator = null;
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
public enable(): void {
|
|
581
|
+
this.element.disabled = false;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
public disable(): void {
|
|
585
|
+
this.element.disabled = true;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
public updateOptions(newOptions: Partial<DatePickerOptions>): void {
|
|
589
|
+
this.options = { ...this.options, ...newOptions };
|
|
590
|
+
this.validateConstraints();
|
|
591
|
+
this.validateInput();
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
// DatePicker Manager
|
|
596
|
+
export class DatePickerManager {
|
|
597
|
+
private instances: Map<string, VanillaDatePicker> = new Map();
|
|
598
|
+
|
|
599
|
+
public initialize(selector: string, options: DatePickerOptions = {}): VanillaDatePicker[] {
|
|
600
|
+
const elements = document.querySelectorAll<HTMLInputElement>(selector);
|
|
601
|
+
const instances: VanillaDatePicker[] = [];
|
|
602
|
+
|
|
603
|
+
elements.forEach((element, index) => {
|
|
604
|
+
// Clean up existing instance
|
|
605
|
+
if (element.vanillaDatePicker) {
|
|
606
|
+
element.vanillaDatePicker.destroy();
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
// Create new instance
|
|
610
|
+
const datePicker = new VanillaDatePicker(element, options);
|
|
611
|
+
element.vanillaDatePicker = datePicker;
|
|
612
|
+
|
|
613
|
+
// Store in manager
|
|
614
|
+
const key = `${selector}-${index}`;
|
|
615
|
+
this.instances.set(key, datePicker);
|
|
616
|
+
instances.push(datePicker);
|
|
617
|
+
});
|
|
618
|
+
|
|
619
|
+
return instances;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
public getInstances(selector: string): VanillaDatePicker[] {
|
|
623
|
+
const instances: VanillaDatePicker[] = [];
|
|
624
|
+
this.instances.forEach((instance, key) => {
|
|
625
|
+
if (key.startsWith(selector)) {
|
|
626
|
+
instances.push(instance);
|
|
627
|
+
}
|
|
628
|
+
});
|
|
629
|
+
return instances;
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
public destroyInstances(selector: string): void {
|
|
633
|
+
const keysToDelete: string[] = [];
|
|
634
|
+
this.instances.forEach((instance, key) => {
|
|
635
|
+
if (key.startsWith(selector)) {
|
|
636
|
+
instance.destroy();
|
|
637
|
+
keysToDelete.push(key);
|
|
638
|
+
}
|
|
639
|
+
});
|
|
640
|
+
keysToDelete.forEach(key => this.instances.delete(key));
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
public destroyAll(): void {
|
|
644
|
+
this.instances.forEach(instance => instance.destroy());
|
|
645
|
+
this.instances.clear();
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
// Create singleton manager
|
|
650
|
+
const datePickerManager = new DatePickerManager();
|
|
651
|
+
|
|
652
|
+
// Initialize date pickers
|
|
653
|
+
const initializeDatePickers = (): void => {
|
|
654
|
+
// Start date pickers
|
|
655
|
+
datePickerManager.initialize('.start-date', {
|
|
656
|
+
format: 'yyyy-mm-dd',
|
|
657
|
+
autoclose: true,
|
|
658
|
+
todayHighlight: true,
|
|
659
|
+
});
|
|
660
|
+
|
|
661
|
+
// End date pickers
|
|
662
|
+
datePickerManager.initialize('.end-date', {
|
|
663
|
+
format: 'yyyy-mm-dd',
|
|
664
|
+
autoclose: true,
|
|
665
|
+
todayHighlight: true,
|
|
666
|
+
});
|
|
667
|
+
|
|
668
|
+
// Generic date pickers
|
|
669
|
+
datePickerManager.initialize('input[type="date"]:not(.start-date):not(.end-date)', {
|
|
670
|
+
format: 'yyyy-mm-dd',
|
|
671
|
+
autoclose: true,
|
|
672
|
+
todayHighlight: true,
|
|
673
|
+
});
|
|
674
|
+
};
|
|
675
|
+
|
|
676
|
+
// Initialize on load
|
|
677
|
+
if (document.readyState === 'loading') {
|
|
678
|
+
document.addEventListener('DOMContentLoaded', initializeDatePickers);
|
|
679
|
+
} else {
|
|
680
|
+
initializeDatePickers();
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
// Reinitialize on theme change
|
|
684
|
+
window.addEventListener('adminator:themeChanged', () => {
|
|
685
|
+
setTimeout(initializeDatePickers, 100);
|
|
686
|
+
});
|
|
687
|
+
|
|
688
|
+
// Cleanup on page unload
|
|
689
|
+
window.addEventListener('beforeunload', () => {
|
|
690
|
+
datePickerManager.destroyAll();
|
|
691
|
+
});
|
|
692
|
+
|
|
693
|
+
// Export default for compatibility
|
|
694
|
+
export default {
|
|
695
|
+
init: initializeDatePickers,
|
|
696
|
+
manager: datePickerManager,
|
|
697
|
+
VanillaDatePicker,
|
|
698
|
+
DatePickerManager,
|
|
699
|
+
};
|