adminator-admin-dashboard 2.7.1 → 2.8.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.
@@ -1,699 +0,0 @@
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
- };