cronixui 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.
@@ -0,0 +1,809 @@
1
+ /**
2
+ * CronixUI - Interactive Component Logic
3
+ * @version 1.0.0
4
+ */
5
+
6
+ (function(global) {
7
+ 'use strict';
8
+
9
+ // ========================================
10
+ // UTILITIES
11
+ // ========================================
12
+ function $(selector, context = document) {
13
+ return context.querySelector(selector);
14
+ }
15
+
16
+ function $$(selector, context = document) {
17
+ return Array.from(context.querySelectorAll(selector));
18
+ }
19
+
20
+ function createEl(tag, className, attrs = {}) {
21
+ const el = document.createElement(tag);
22
+ if (className) el.className = className;
23
+ Object.entries(attrs).forEach(([key, val]) => {
24
+ if (key === 'text') el.textContent = val;
25
+ else if (key === 'html') el.innerHTML = val;
26
+ else el.setAttribute(key, val);
27
+ });
28
+ return el;
29
+ }
30
+
31
+ // ========================================
32
+ // TOGGLE
33
+ // ========================================
34
+ class CronixToggle {
35
+ constructor(el) {
36
+ this.el = el;
37
+ this.input = el.querySelector('input');
38
+ this.box = el.querySelector('.cn-toggle-box');
39
+
40
+ if (this.el.tagName === 'DIV' && !this.input) {
41
+ el.addEventListener('click', () => this.toggle());
42
+ }
43
+
44
+ el._cnToggle = this;
45
+ }
46
+
47
+ toggle() {
48
+ this.el.classList.toggle('on');
49
+ this.el.dispatchEvent(new CustomEvent('change', {
50
+ detail: { value: this.isOn() }
51
+ }));
52
+ }
53
+
54
+ isOn() {
55
+ return this.el.classList.contains('on');
56
+ }
57
+
58
+ setOn(on) {
59
+ this.el.classList.toggle('on', on);
60
+ }
61
+ }
62
+
63
+ function initToggles() {
64
+ $$('.cn-toggle').forEach(el => {
65
+ if (!el._cnToggle) new CronixToggle(el);
66
+ });
67
+ }
68
+
69
+ // ========================================
70
+ // NAVIGATION
71
+ // ========================================
72
+ class CronixNav {
73
+ constructor(el) {
74
+ this.el = el;
75
+ this.items = $$('.cn-nav-item', el);
76
+ this.items.forEach(item => {
77
+ item.addEventListener('click', () => this.setActive(item));
78
+ });
79
+ el._cnNav = this;
80
+ }
81
+
82
+ setActive(activeItem) {
83
+ this.items.forEach(item => item.classList.remove('cn-nav-active'));
84
+ activeItem.classList.add('cn-nav-active');
85
+ this.el.dispatchEvent(new CustomEvent('change', {
86
+ detail: { item: activeItem }
87
+ }));
88
+ }
89
+ }
90
+
91
+ function initNavs() {
92
+ $$('.cn-nav').forEach(el => {
93
+ if (!el._cnNav) new CronixNav(el);
94
+ });
95
+ }
96
+
97
+ // ========================================
98
+ // TABS
99
+ // ========================================
100
+ class CronixTabs {
101
+ constructor(el) {
102
+ this.el = el;
103
+ this.tabs = $$('.cn-tab', el);
104
+ this.panels = $$('.cn-tab-panel', el.parentElement);
105
+
106
+ this.tabs.forEach((tab, i) => {
107
+ tab.addEventListener('click', () => this.setActive(i));
108
+ });
109
+
110
+ el._cnTabs = this;
111
+ }
112
+
113
+ setActive(index) {
114
+ this.tabs.forEach((tab, i) => {
115
+ tab.classList.toggle('cn-tab-active', i === index);
116
+ });
117
+ this.panels.forEach((panel, i) => {
118
+ panel.classList.toggle('cn-tab-panel-active', i === index);
119
+ });
120
+ this.el.dispatchEvent(new CustomEvent('change', {
121
+ detail: { index }
122
+ }));
123
+ }
124
+ }
125
+
126
+ function initTabs() {
127
+ $$('.cn-tabs').forEach(el => {
128
+ if (!el._cnTabs) new CronixTabs(el);
129
+ });
130
+ }
131
+
132
+ // ========================================
133
+ // ACCORDION
134
+ // ========================================
135
+ class CronixAccordion {
136
+ constructor(el) {
137
+ this.el = el;
138
+ this.items = $$('.cn-accordion-item', el);
139
+ this.multi = el.dataset.multi === 'true';
140
+
141
+ this.items.forEach(item => {
142
+ const header = $('.cn-accordion-header', item);
143
+ header.addEventListener('click', () => this.toggle(item));
144
+ });
145
+
146
+ el._cnAccordion = this;
147
+ }
148
+
149
+ toggle(item) {
150
+ const isOpen = item.classList.contains('cn-accordion-open');
151
+
152
+ if (!this.multi) {
153
+ this.items.forEach(i => i.classList.remove('cn-accordion-open'));
154
+ }
155
+
156
+ item.classList.toggle('cn-accordion-open', !isOpen);
157
+ }
158
+
159
+ openAll() {
160
+ this.items.forEach(item => item.classList.add('cn-accordion-open'));
161
+ }
162
+
163
+ closeAll() {
164
+ this.items.forEach(item => item.classList.remove('cn-accordion-open'));
165
+ }
166
+ }
167
+
168
+ function initAccordions() {
169
+ $$('.cn-accordion').forEach(el => {
170
+ if (!el._cnAccordion) new CronixAccordion(el);
171
+ });
172
+ }
173
+
174
+ // ========================================
175
+ // MODAL
176
+ // ========================================
177
+ class CronixModal {
178
+ constructor(el) {
179
+ this.el = el;
180
+ this.modal = $('.cn-modal', el);
181
+ this.closeBtn = $('.cn-modal-close', el);
182
+ this.onKeydown = this.onKeydown.bind(this);
183
+
184
+ if (this.closeBtn) {
185
+ this.closeBtn.addEventListener('click', () => this.close());
186
+ }
187
+
188
+ el.addEventListener('click', (e) => {
189
+ if (e.target === el) this.close();
190
+ });
191
+
192
+ el._cnModal = this;
193
+ }
194
+
195
+ open() {
196
+ this.el.classList.add('cn-modal-open');
197
+ document.body.style.overflow = 'hidden';
198
+ document.addEventListener('keydown', this.onKeydown);
199
+ this.el.dispatchEvent(new CustomEvent('open'));
200
+ }
201
+
202
+ close() {
203
+ this.el.classList.remove('cn-modal-open');
204
+ document.body.style.overflow = '';
205
+ document.removeEventListener('keydown', this.onKeydown);
206
+ this.el.dispatchEvent(new CustomEvent('close'));
207
+ }
208
+
209
+ onKeydown(e) {
210
+ if (e.key === 'Escape') this.close();
211
+ }
212
+ }
213
+
214
+ function initModals() {
215
+ $$('.cn-modal-backdrop').forEach(el => {
216
+ if (!el._cnModal) new CronixModal(el);
217
+ });
218
+ }
219
+
220
+ // ========================================
221
+ // DROPDOWN
222
+ // ========================================
223
+ class CronixDropdown {
224
+ constructor(el) {
225
+ this.el = el;
226
+ this.trigger = $('.cn-dropdown-trigger', el) || el.querySelector('[data-dropdown-trigger]') || el;
227
+ this.menu = $('.cn-dropdown-menu', el);
228
+ this.onDocClick = this.onDocClick.bind(this);
229
+
230
+ this.trigger.addEventListener('click', (e) => {
231
+ e.stopPropagation();
232
+ this.toggle();
233
+ });
234
+
235
+ this.menu.addEventListener('click', () => this.close());
236
+
237
+ el._cnDropdown = this;
238
+ }
239
+
240
+ open() {
241
+ this.el.classList.add('cn-dropdown-open');
242
+ document.addEventListener('click', this.onDocClick);
243
+ }
244
+
245
+ close() {
246
+ this.el.classList.remove('cn-dropdown-open');
247
+ document.removeEventListener('click', this.onDocClick);
248
+ }
249
+
250
+ toggle() {
251
+ if (this.el.classList.contains('cn-dropdown-open')) {
252
+ this.close();
253
+ } else {
254
+ this.open();
255
+ }
256
+ }
257
+
258
+ onDocClick(e) {
259
+ if (!this.el.contains(e.target)) {
260
+ this.close();
261
+ }
262
+ }
263
+ }
264
+
265
+ function initDropdowns() {
266
+ $$('.cn-dropdown').forEach(el => {
267
+ if (!el._cnDropdown) new CronixDropdown(el);
268
+ });
269
+ }
270
+
271
+ // ========================================
272
+ // TOAST
273
+ // ========================================
274
+ class CronixToast {
275
+ constructor(container) {
276
+ this.container = container || this.getOrCreateContainer();
277
+ }
278
+
279
+ getOrCreateContainer() {
280
+ let container = $('.cn-toast-container');
281
+ if (!container) {
282
+ container = createEl('div', 'cn-toast-container');
283
+ document.body.appendChild(container);
284
+ }
285
+ return container;
286
+ }
287
+
288
+ show(options) {
289
+ const {
290
+ title,
291
+ message,
292
+ type = 'info',
293
+ duration = 4000,
294
+ closable = true
295
+ } = options;
296
+
297
+ const toast = createEl('div', `cn-toast cn-toast-${type}`);
298
+ toast.innerHTML = `
299
+ <div class="cn-alert-icon">
300
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
301
+ ${this.getIcon(type)}
302
+ </svg>
303
+ </div>
304
+ <div class="cn-alert-content">
305
+ ${title ? `<div class="cn-alert-title">${title}</div>` : ''}
306
+ ${message ? `<div class="cn-alert-message">${message}</div>` : ''}
307
+ </div>
308
+ ${closable ? `
309
+ <button class="cn-alert-close">
310
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="16" height="16">
311
+ <line x1="18" y1="6" x2="6" y2="18"></line>
312
+ <line x1="6" y1="6" x2="18" y2="18"></line>
313
+ </svg>
314
+ </button>
315
+ ` : ''}
316
+ `;
317
+
318
+ if (closable) {
319
+ $('.cn-alert-close', toast).addEventListener('click', () => this.hide(toast));
320
+ }
321
+
322
+ this.container.appendChild(toast);
323
+
324
+ if (duration > 0) {
325
+ setTimeout(() => this.hide(toast), duration);
326
+ }
327
+
328
+ return toast;
329
+ }
330
+
331
+ hide(toast) {
332
+ toast.classList.add('cn-toast-leaving');
333
+ setTimeout(() => toast.remove(), 200);
334
+ }
335
+
336
+ getIcon(type) {
337
+ const icons = {
338
+ success: '<polyline points="20 6 9 17 4 12"></polyline>',
339
+ error: '<circle cx="12" cy="12" r="10"></circle><line x1="15" y1="9" x2="9" y2="15"></line><line x1="9" y1="9" x2="15" y2="15"></line>',
340
+ warning: '<path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"></path><line x1="12" y1="9" x2="12" y2="13"></line><line x1="12" y1="17" x2="12.01" y2="17"></line>',
341
+ info: '<circle cx="12" cy="12" r="10"></circle><line x1="12" y1="16" x2="12" y2="12"></line><line x1="12" y1="8" x2="12.01" y2="8"></line>'
342
+ };
343
+ return icons[type] || icons.info;
344
+ }
345
+
346
+ success(message, title) { return this.show({ message, title, type: 'success' }); }
347
+ error(message, title) { return this.show({ message, title, type: 'error' }); }
348
+ warning(message, title) { return this.show({ message, title, type: 'warning' }); }
349
+ info(message, title) { return this.show({ message, title, type: 'info' }); }
350
+ }
351
+
352
+ // ========================================
353
+ // COMMAND PALETTE
354
+ // ========================================
355
+ class CronixCommandPalette {
356
+ constructor(el) {
357
+ this.el = el;
358
+ this.input = $('.cn-command-palette-input', el);
359
+ this.results = $('.cn-command-palette-results', el);
360
+ this.items = [];
361
+ this.activeIndex = -1;
362
+ this.onKeydown = this.onKeydown.bind(this);
363
+
364
+ this.input.addEventListener('input', () => this.filter());
365
+ this.input.addEventListener('keydown', (e) => this.onKeydown(e));
366
+ el.addEventListener('click', (e) => {
367
+ if (e.target === el) this.close();
368
+ });
369
+
370
+ el._cnCommandPalette = this;
371
+ }
372
+
373
+ setItems(items) {
374
+ this.items = items;
375
+ this.render();
376
+ }
377
+
378
+ render() {
379
+ const query = this.input.value.toLowerCase();
380
+ const filtered = this.items.filter(item =>
381
+ item.title.toLowerCase().includes(query) ||
382
+ (item.subtitle && item.subtitle.toLowerCase().includes(query))
383
+ );
384
+
385
+ this.results.innerHTML = filtered.map((item, i) => `
386
+ <div class="cn-command-item${i === 0 ? ' cn-command-item-active' : ''}" data-index="${i}">
387
+ ${item.icon ? `<div class="cn-command-item-icon">${item.icon}</div>` : ''}
388
+ <div class="cn-command-item-content">
389
+ <div class="cn-command-item-title">${item.title}</div>
390
+ ${item.subtitle ? `<div class="cn-command-item-subtitle">${item.subtitle}</div>` : ''}
391
+ </div>
392
+ ${item.kbd ? `<div class="cn-command-item-kbd">${item.kbd}</div>` : ''}
393
+ </div>
394
+ `).join('');
395
+
396
+ $$('.cn-command-item', this.results).forEach((item, i) => {
397
+ item.addEventListener('click', () => this.select(filtered[i]));
398
+ item.addEventListener('mouseenter', () => this.setActive(i));
399
+ });
400
+
401
+ this.activeIndex = filtered.length > 0 ? 0 : -1;
402
+ }
403
+
404
+ filter() {
405
+ this.render();
406
+ }
407
+
408
+ onKeydown(e) {
409
+ const items = $$('.cn-command-item', this.results);
410
+
411
+ if (e.key === 'ArrowDown') {
412
+ e.preventDefault();
413
+ this.setActive(Math.min(this.activeIndex + 1, items.length - 1));
414
+ } else if (e.key === 'ArrowUp') {
415
+ e.preventDefault();
416
+ this.setActive(Math.max(this.activeIndex - 1, 0));
417
+ } else if (e.key === 'Enter' && this.activeIndex >= 0) {
418
+ e.preventDefault();
419
+ items[this.activeIndex].click();
420
+ }
421
+ }
422
+
423
+ setActive(index) {
424
+ const items = $$('.cn-command-item', this.results);
425
+ items.forEach((item, i) => item.classList.toggle('cn-command-item-active', i === index));
426
+ this.activeIndex = index;
427
+ }
428
+
429
+ select(item) {
430
+ if (item.action) item.action();
431
+ this.close();
432
+ this.el.dispatchEvent(new CustomEvent('select', { detail: item }));
433
+ }
434
+
435
+ open() {
436
+ this.el.classList.add('cn-command-palette-open');
437
+ this.input.value = '';
438
+ this.render();
439
+ setTimeout(() => this.input.focus(), 100);
440
+ document.body.style.overflow = 'hidden';
441
+ document.addEventListener('keydown', this.handleEscape);
442
+ }
443
+
444
+ close() {
445
+ this.el.classList.remove('cn-command-palette-open');
446
+ document.body.style.overflow = '';
447
+ document.removeEventListener('keydown', this.handleEscape);
448
+ }
449
+
450
+ handleEscape = (e) => {
451
+ if (e.key === 'Escape') this.close();
452
+ };
453
+ }
454
+
455
+ function initCommandPalettes() {
456
+ $$('.cn-command-palette').forEach(el => {
457
+ if (!el._cnCommandPalette) new CronixCommandPalette(el);
458
+ });
459
+ }
460
+
461
+ // ========================================
462
+ // SEARCH
463
+ // ========================================
464
+ class CronixSearch {
465
+ constructor(el) {
466
+ this.el = el;
467
+ this.input = $('.cn-search-input', el);
468
+ this.results = $('.cn-search-results', el);
469
+ this.onDocClick = this.onDocClick.bind(this);
470
+
471
+ this.input.addEventListener('focus', () => this.open());
472
+ this.input.addEventListener('input', () => this.filter());
473
+ el.addEventListener('click', (e) => e.stopPropagation());
474
+
475
+ el._cnSearch = this;
476
+ }
477
+
478
+ setItems(items) {
479
+ this.items = items;
480
+ }
481
+
482
+ filter() {
483
+ const query = this.input.value.toLowerCase();
484
+ if (!query) {
485
+ this.close();
486
+ return;
487
+ }
488
+
489
+ const filtered = this.items.filter(item =>
490
+ item.title.toLowerCase().includes(query) ||
491
+ (item.subtitle && item.subtitle.toLowerCase().includes(query))
492
+ );
493
+
494
+ this.results.innerHTML = filtered.map(item => `
495
+ <div class="cn-search-result">
496
+ <div class="cn-search-result-title">${item.title}</div>
497
+ ${item.subtitle ? `<div class="cn-search-result-subtitle">${item.subtitle}</div>` : ''}
498
+ </div>
499
+ `).join('');
500
+
501
+ $$('.cn-search-result', this.results).forEach((result, i) => {
502
+ result.addEventListener('click', () => {
503
+ this.select(filtered[i]);
504
+ });
505
+ });
506
+
507
+ this.open();
508
+ }
509
+
510
+ select(item) {
511
+ this.input.value = item.title;
512
+ this.close();
513
+ this.el.dispatchEvent(new CustomEvent('select', { detail: item }));
514
+ if (item.action) item.action();
515
+ }
516
+
517
+ open() {
518
+ this.el.classList.add('cn-search-open');
519
+ document.addEventListener('click', this.onDocClick);
520
+ }
521
+
522
+ close() {
523
+ this.el.classList.remove('cn-search-open');
524
+ document.removeEventListener('click', this.onDocClick);
525
+ }
526
+
527
+ onDocClick(e) {
528
+ if (!this.el.contains(e.target)) {
529
+ this.close();
530
+ }
531
+ }
532
+ }
533
+
534
+ function initSearch() {
535
+ $$('.cn-search').forEach(el => {
536
+ if (!el._cnSearch) new CronixSearch(el);
537
+ });
538
+ }
539
+
540
+ // ========================================
541
+ // ALERT
542
+ // ========================================
543
+ class CronixAlert {
544
+ constructor(el) {
545
+ this.el = el;
546
+ this.closeBtn = $('.cn-alert-close', el);
547
+
548
+ if (this.closeBtn) {
549
+ this.closeBtn.addEventListener('click', () => this.close());
550
+ }
551
+
552
+ el._cnAlert = this;
553
+ }
554
+
555
+ close() {
556
+ this.el.style.opacity = '0';
557
+ setTimeout(() => this.el.remove(), 200);
558
+ }
559
+ }
560
+
561
+ function initAlerts() {
562
+ $$('.cn-alert').forEach(el => {
563
+ if (!el._cnAlert) new CronixAlert(el);
564
+ });
565
+ }
566
+
567
+ // ========================================
568
+ // TABLE SORTING
569
+ // ========================================
570
+ class CronixTable {
571
+ constructor(el) {
572
+ this.el = el;
573
+ this.table = $('table', el) || el;
574
+ this.headers = $$('th[data-sort]', this.table);
575
+
576
+ this.headers.forEach(header => {
577
+ header.addEventListener('click', () => this.sort(header));
578
+ });
579
+
580
+ el._cnTable = this;
581
+ }
582
+
583
+ sort(header) {
584
+ const key = header.dataset.sort;
585
+ const tbody = $('tbody', this.table);
586
+ const rows = $$('tr', tbody);
587
+ const order = header.dataset.order === 'asc' ? 'desc' : 'asc';
588
+
589
+ this.headers.forEach(h => delete h.dataset.order);
590
+ header.dataset.order = order;
591
+
592
+ const index = Array.from(header.parentElement.children).indexOf(header);
593
+
594
+ rows.sort((a, b) => {
595
+ const aVal = a.children[index].textContent.trim();
596
+ const bVal = b.children[index].textContent.trim();
597
+ const aNum = parseFloat(aVal);
598
+ const bNum = parseFloat(bVal);
599
+
600
+ if (!isNaN(aNum) && !isNaN(bNum)) {
601
+ return order === 'asc' ? aNum - bNum : bNum - aNum;
602
+ }
603
+ return order === 'asc'
604
+ ? aVal.localeCompare(bVal)
605
+ : bVal.localeCompare(aVal);
606
+ });
607
+
608
+ rows.forEach(row => tbody.appendChild(row));
609
+ }
610
+ }
611
+
612
+ function initTables() {
613
+ $$('.cn-table-sortable').forEach(el => {
614
+ if (!el._cnTable) new CronixTable(el);
615
+ });
616
+ }
617
+
618
+ // ========================================
619
+ // PAGINATION
620
+ // ========================================
621
+ class CronixPagination {
622
+ constructor(el, options = {}) {
623
+ this.el = el;
624
+ this.total = options.total || 1;
625
+ this.current = options.current || 1;
626
+ this.onChange = options.onChange || (() => {});
627
+
628
+ this.render();
629
+ el._cnPagination = this;
630
+ }
631
+
632
+ render() {
633
+ const pages = this.getPages();
634
+
635
+ this.el.innerHTML = `
636
+ <button class="cn-pagination-item" data-page="prev" ${this.current === 1 ? 'disabled' : ''}>
637
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="16" height="16">
638
+ <polyline points="15 18 9 12 15 6"></polyline>
639
+ </svg>
640
+ </button>
641
+ ${pages.map(p => `
642
+ <button class="cn-pagination-item${p === this.current ? ' cn-pagination-active' : ''}" data-page="${p}">
643
+ ${p}
644
+ </button>
645
+ `).join('')}
646
+ <button class="cn-pagination-item" data-page="next" ${this.current === this.total ? 'disabled' : ''}>
647
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="16" height="16">
648
+ <polyline points="9 18 15 12 9 6"></polyline>
649
+ </svg>
650
+ </button>
651
+ `;
652
+
653
+ $$('.cn-pagination-item', this.el).forEach(btn => {
654
+ btn.addEventListener('click', () => {
655
+ const page = btn.dataset.page;
656
+ if (page === 'prev' && this.current > 1) {
657
+ this.goTo(this.current - 1);
658
+ } else if (page === 'next' && this.current < this.total) {
659
+ this.goTo(this.current + 1);
660
+ } else if (!isNaN(page)) {
661
+ this.goTo(parseInt(page));
662
+ }
663
+ });
664
+ });
665
+ }
666
+
667
+ getPages() {
668
+ const pages = [];
669
+ const maxVisible = 5;
670
+
671
+ if (this.total <= maxVisible) {
672
+ for (let i = 1; i <= this.total; i++) pages.push(i);
673
+ } else {
674
+ if (this.current <= 3) {
675
+ for (let i = 1; i <= 4; i++) pages.push(i);
676
+ pages.push('...');
677
+ pages.push(this.total);
678
+ } else if (this.current >= this.total - 2) {
679
+ pages.push(1);
680
+ pages.push('...');
681
+ for (let i = this.total - 3; i <= this.total; i++) pages.push(i);
682
+ } else {
683
+ pages.push(1);
684
+ pages.push('...');
685
+ for (let i = this.current - 1; i <= this.current + 1; i++) pages.push(i);
686
+ pages.push('...');
687
+ pages.push(this.total);
688
+ }
689
+ }
690
+
691
+ return pages;
692
+ }
693
+
694
+ goTo(page) {
695
+ if (page >= 1 && page <= this.total && page !== this.current) {
696
+ this.current = page;
697
+ this.render();
698
+ this.onChange(page);
699
+ this.el.dispatchEvent(new CustomEvent('change', { detail: { page } }));
700
+ }
701
+ }
702
+ }
703
+
704
+ // ========================================
705
+ // FILE INPUT
706
+ // ========================================
707
+ class CronixFileInput {
708
+ constructor(el) {
709
+ this.el = el;
710
+ this.input = $('input[type="file"]', el);
711
+ this.label = $('.cn-file-input-label', el);
712
+ this.textEl = $('.cn-file-input-text', el);
713
+ this.originalText = this.textEl ? this.textEl.innerHTML : '';
714
+
715
+ this.input.addEventListener('change', (e) => this.handleChange(e));
716
+
717
+ el._cnFileInput = this;
718
+ }
719
+
720
+ handleChange(e) {
721
+ const files = this.input.files;
722
+ if (files.length > 0) {
723
+ const names = Array.from(files).map(f => f.name).join(', ');
724
+ if (this.textEl) {
725
+ this.textEl.innerHTML = `<span>${names}</span>`;
726
+ }
727
+ this.el.classList.add('cn-file-input-has-file');
728
+ this.el.dispatchEvent(new CustomEvent('change', { detail: { files } }));
729
+ } else {
730
+ this.reset();
731
+ }
732
+ }
733
+
734
+ reset() {
735
+ this.input.value = '';
736
+ if (this.textEl) this.textEl.innerHTML = this.originalText;
737
+ this.el.classList.remove('cn-file-input-has-file');
738
+ }
739
+ }
740
+
741
+ function initFileInputs() {
742
+ $$('.cn-file-input').forEach(el => {
743
+ if (!el._cnFileInput) new CronixFileInput(el);
744
+ });
745
+ }
746
+
747
+ // ========================================
748
+ // INITIALIZE ALL
749
+ // ========================================
750
+ function init() {
751
+ initToggles();
752
+ initNavs();
753
+ initTabs();
754
+ initAccordions();
755
+ initModals();
756
+ initDropdowns();
757
+ initCommandPalettes();
758
+ initSearch();
759
+ initAlerts();
760
+ initTables();
761
+ initFileInputs();
762
+ }
763
+
764
+ // ========================================
765
+ // AUTO-INIT
766
+ // ========================================
767
+ if (document.readyState === 'loading') {
768
+ document.addEventListener('DOMContentLoaded', init);
769
+ } else {
770
+ init();
771
+ }
772
+
773
+ // ========================================
774
+ // EXPORT
775
+ // ========================================
776
+ const CronixUI = {
777
+ init,
778
+ Toggle: CronixToggle,
779
+ Nav: CronixNav,
780
+ Tabs: CronixTabs,
781
+ Accordion: CronixAccordion,
782
+ Modal: CronixModal,
783
+ Dropdown: CronixDropdown,
784
+ Toast: new CronixToast(),
785
+ CommandPalette: CronixCommandPalette,
786
+ Search: CronixSearch,
787
+ Alert: CronixAlert,
788
+ Table: CronixTable,
789
+ Pagination: CronixPagination,
790
+ FileInput: CronixFileInput,
791
+ $,
792
+ $$,
793
+ createEl
794
+ };
795
+
796
+ // AMD
797
+ if (typeof define === 'function' && define.amd) {
798
+ define(() => CronixUI);
799
+ }
800
+ // CommonJS
801
+ else if (typeof module !== 'undefined' && module.exports) {
802
+ module.exports = CronixUI;
803
+ }
804
+ // Browser global
805
+ else {
806
+ global.CronixUI = CronixUI;
807
+ }
808
+
809
+ })(typeof globalThis !== 'undefined' ? globalThis : window);