@keenthemes/ktui 1.1.2 → 1.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/dist/ktui.js +506 -49
  2. package/dist/ktui.min.js +1 -1
  3. package/dist/ktui.min.js.map +1 -1
  4. package/dist/styles.css +12 -0
  5. package/lib/cjs/components/component.js +22 -0
  6. package/lib/cjs/components/component.js.map +1 -1
  7. package/lib/cjs/components/datatable/datatable.js +7 -1
  8. package/lib/cjs/components/datatable/datatable.js.map +1 -1
  9. package/lib/cjs/components/drawer/drawer.js +255 -9
  10. package/lib/cjs/components/drawer/drawer.js.map +1 -1
  11. package/lib/cjs/components/dropdown/dropdown.js +55 -8
  12. package/lib/cjs/components/dropdown/dropdown.js.map +1 -1
  13. package/lib/cjs/components/select/search.js +17 -7
  14. package/lib/cjs/components/select/search.js.map +1 -1
  15. package/lib/cjs/components/select/select.js +92 -14
  16. package/lib/cjs/components/select/select.js.map +1 -1
  17. package/lib/cjs/components/sticky/sticky.js +44 -5
  18. package/lib/cjs/components/sticky/sticky.js.map +1 -1
  19. package/lib/cjs/helpers/data.js +8 -0
  20. package/lib/cjs/helpers/data.js.map +1 -1
  21. package/lib/cjs/helpers/event-handler.js +6 -5
  22. package/lib/cjs/helpers/event-handler.js.map +1 -1
  23. package/lib/cjs/index.js.map +1 -1
  24. package/lib/esm/components/component.js +22 -0
  25. package/lib/esm/components/component.js.map +1 -1
  26. package/lib/esm/components/datatable/datatable.js +7 -1
  27. package/lib/esm/components/datatable/datatable.js.map +1 -1
  28. package/lib/esm/components/drawer/drawer.js +255 -9
  29. package/lib/esm/components/drawer/drawer.js.map +1 -1
  30. package/lib/esm/components/dropdown/dropdown.js +55 -8
  31. package/lib/esm/components/dropdown/dropdown.js.map +1 -1
  32. package/lib/esm/components/select/search.js +17 -7
  33. package/lib/esm/components/select/search.js.map +1 -1
  34. package/lib/esm/components/select/select.js +92 -14
  35. package/lib/esm/components/select/select.js.map +1 -1
  36. package/lib/esm/components/sticky/sticky.js +44 -5
  37. package/lib/esm/components/sticky/sticky.js.map +1 -1
  38. package/lib/esm/helpers/data.js +8 -0
  39. package/lib/esm/helpers/data.js.map +1 -1
  40. package/lib/esm/helpers/event-handler.js +6 -5
  41. package/lib/esm/helpers/event-handler.js.map +1 -1
  42. package/lib/esm/index.js.map +1 -1
  43. package/package.json +4 -2
  44. package/src/components/component.ts +26 -0
  45. package/src/components/datatable/__tests__/race-conditions.test.ts +2 -2
  46. package/src/components/datatable/datatable.ts +8 -1
  47. package/src/components/drawer/drawer.ts +266 -10
  48. package/src/components/dropdown/dropdown.ts +63 -8
  49. package/src/components/select/__tests__/ux-behaviors.test.ts +382 -4
  50. package/src/components/select/search.ts +16 -7
  51. package/src/components/select/select.css +5 -1
  52. package/src/components/select/select.ts +105 -16
  53. package/src/components/sticky/sticky.ts +55 -5
  54. package/src/helpers/data.ts +10 -0
  55. package/src/helpers/event-handler.ts +7 -6
  56. package/src/index.ts +2 -0
@@ -43,13 +43,16 @@ export class KTDrawer extends KTComponent implements KTDrawerInterface {
43
43
  constructor(element: HTMLElement, config?: KTDrawerConfigInterface) {
44
44
  super();
45
45
 
46
- if (KTData.has(element as HTMLElement, this._name)) return;
46
+ if (KTData.has(element as HTMLElement, this._name)) {
47
+ return;
48
+ }
47
49
 
48
50
  this._init(element);
49
51
  this._buildConfig(config);
50
52
  this._handleClose();
51
53
  this._update();
52
54
  this._handleContainer();
55
+
53
56
  }
54
57
 
55
58
  protected _handleClose(): void {
@@ -88,6 +91,28 @@ export class KTDrawer extends KTComponent implements KTDrawerInterface {
88
91
 
89
92
  KTDrawer.hide();
90
93
 
94
+ // If drawer needs to be in front of backdrop, ensure it's in body (for proper z-index stacking)
95
+ // This ensures the drawer and backdrop are in the same stacking context
96
+ if (this._getOption('container') === 'body' && this._element.parentElement !== document.body) {
97
+ // Store original parent for restoration when hiding
98
+ if (!this._element.hasAttribute('data-kt-drawer-original-parent-id')) {
99
+ const originalParent = this._element.parentElement;
100
+ if (originalParent && originalParent !== document.body) {
101
+ this._element.setAttribute('data-kt-drawer-original-parent-id', originalParent.id || '');
102
+ // Store a reference to find the parent later (using closest to find Livewire component or header)
103
+ const livewireComponent = originalParent.closest('[wire\\:id]');
104
+ const header = originalParent.closest('header#header');
105
+ if (livewireComponent) {
106
+ this._element.setAttribute('data-kt-drawer-original-wire-id', (livewireComponent as HTMLElement).getAttribute('wire:id') || '');
107
+ }
108
+ if (header) {
109
+ this._element.setAttribute('data-kt-drawer-original-in-header', 'true');
110
+ }
111
+ }
112
+ }
113
+ document.body.appendChild(this._element);
114
+ }
115
+
91
116
  if (this._getOption('backdrop') === true) this._createBackdrop();
92
117
 
93
118
  if (relatedTarget) this._relatedTarget = relatedTarget;
@@ -162,6 +187,10 @@ export class KTDrawer extends KTComponent implements KTDrawerInterface {
162
187
  this._element.classList.remove(this._getOption('shownClass') as string);
163
188
  this._element.style.zIndex = '';
164
189
 
190
+ // Note: We don't move drawer back to original location here
191
+ // Livewire will handle DOM structure on next navigation, and drawer will be reinitialized
192
+ // in its original location from the persisted component HTML
193
+
165
194
  this._fireEvent('hidden');
166
195
  this._dispatchEvent('hidden');
167
196
  });
@@ -180,7 +209,25 @@ export class KTDrawer extends KTComponent implements KTDrawerInterface {
180
209
  protected _handleContainer(): void {
181
210
  if (this._getOption('container')) {
182
211
  if (this._getOption('container') === 'body') {
183
- document.body.appendChild(this._element);
212
+ // Check if drawer is in a persisted Livewire component (like header with @persist)
213
+ // If so, don't move it to body - keep it in place so Livewire can preserve it
214
+ // This follows the same pattern as dropdowns/menus which work with wire:navigate
215
+ const originalParent = this._element.parentNode;
216
+ const isInPersistedComponent = originalParent &&
217
+ ((originalParent as HTMLElement).closest('[wire\\:id]') !== null ||
218
+ (originalParent as HTMLElement).closest('header#header') !== null);
219
+
220
+ if (isInPersistedComponent) {
221
+ // Don't move to body - keep in original location for Livewire persistence
222
+ // Use fixed positioning to achieve the same visual effect
223
+ // Ensure drawer has fixed positioning to work from its current location
224
+ if (!this._element.style.position || this._element.style.position === 'static') {
225
+ this._element.style.position = 'fixed';
226
+ }
227
+ } else {
228
+ // Not in persisted component - safe to move to body (follows original behavior)
229
+ document.body.appendChild(this._element);
230
+ }
184
231
  } else {
185
232
  document
186
233
  .querySelector(this._getOption('container') as string)
@@ -265,15 +312,128 @@ export class KTDrawer extends KTComponent implements KTDrawerInterface {
265
312
  return this._isEnabled();
266
313
  }
267
314
 
315
+ public static getElement(reference: HTMLElement): HTMLElement {
316
+ if (reference && reference.hasAttribute('data-kt-drawer-initialized'))
317
+ return reference;
318
+
319
+ const findElement =
320
+ reference &&
321
+ (reference.closest('[data-kt-drawer-initialized]') as HTMLElement);
322
+ if (findElement) return findElement;
323
+
324
+ // Fallback: look for parent with data-kt-drawer attribute
325
+ if (reference) {
326
+ const drawerContainer = reference.closest('[data-kt-drawer]') as HTMLElement;
327
+ if (drawerContainer) return drawerContainer;
328
+ }
329
+
330
+ // If reference is a toggle button with a selector, find the drawer by selector
331
+ // This handles cases where the toggle button is not a child of the drawer
332
+ if (reference && reference.hasAttribute('data-kt-drawer-toggle')) {
333
+ const selector = reference.getAttribute('data-kt-drawer-toggle');
334
+ if (selector) {
335
+ // Check both document and body (drawers with container="body" are moved to body)
336
+ const drawerElInDoc = document.querySelector(selector);
337
+ const drawerElInBody = document.body.querySelector(selector);
338
+ const drawerEl = drawerElInDoc || drawerElInBody;
339
+ if (drawerEl) return drawerEl as HTMLElement;
340
+ }
341
+ }
342
+
343
+ return null;
344
+ }
345
+
346
+ /**
347
+ * Wait for an element to appear in the DOM using polling with MutationObserver fallback
348
+ * Useful for persisted Livewire components that may not be in DOM immediately
349
+ */
350
+ public static waitForElement(selector: string, timeout: number = 2000): Promise<HTMLElement | null> {
351
+ return new Promise((resolve) => {
352
+ let resolved = false;
353
+
354
+ const doResolve = (element: HTMLElement | null) => {
355
+ if (!resolved) {
356
+ resolved = true;
357
+ resolve(element);
358
+ }
359
+ };
360
+
361
+ // Check if element already exists
362
+ const existing = document.querySelector(selector) || document.body.querySelector(selector);
363
+ if (existing) {
364
+ doResolve(existing as HTMLElement);
365
+ return;
366
+ }
367
+
368
+ // Use polling for faster detection (check every 50ms)
369
+ let attempts = 0;
370
+ const maxAttempts = timeout / 50;
371
+ const pollInterval = setInterval(() => {
372
+ if (resolved) {
373
+ clearInterval(pollInterval);
374
+ return;
375
+ }
376
+ attempts++;
377
+ const element = document.querySelector(selector) || document.body.querySelector(selector);
378
+ if (element) {
379
+ clearInterval(pollInterval);
380
+ doResolve(element as HTMLElement);
381
+ return;
382
+ }
383
+ if (attempts >= maxAttempts) {
384
+ clearInterval(pollInterval);
385
+ doResolve(null);
386
+ }
387
+ }, 50);
388
+
389
+ // Also use MutationObserver as backup for immediate detection
390
+ const observer = new MutationObserver(() => {
391
+ if (resolved) {
392
+ observer.disconnect();
393
+ return;
394
+ }
395
+ const element = document.querySelector(selector) || document.body.querySelector(selector);
396
+ if (element) {
397
+ clearInterval(pollInterval);
398
+ observer.disconnect();
399
+ doResolve(element as HTMLElement);
400
+ }
401
+ });
402
+
403
+ observer.observe(document.body, {
404
+ childList: true,
405
+ subtree: true,
406
+ });
407
+
408
+ // Cleanup on timeout
409
+ setTimeout(() => {
410
+ if (!resolved) {
411
+ clearInterval(pollInterval);
412
+ observer.disconnect();
413
+ doResolve(null);
414
+ }
415
+ }, timeout);
416
+ });
417
+ }
418
+
268
419
  public static getInstance(element: HTMLElement): KTDrawer {
269
420
  if (!element) return null;
270
421
 
271
- if (KTData.has(element, 'drawer')) {
272
- return KTData.get(element, 'drawer') as KTDrawer;
422
+ const drawerElement = KTDrawer.getElement(element);
423
+ if (!drawerElement) {
424
+ // If element is a toggle button and drawer element wasn't found, return null
425
+ // The handleToggle() will handle waiting for the element to appear
426
+ if (element.hasAttribute('data-kt-drawer-toggle')) {
427
+ }
428
+ return null;
429
+ }
430
+
431
+ if (KTData.has(drawerElement, 'drawer')) {
432
+ return KTData.get(drawerElement, 'drawer') as KTDrawer;
273
433
  }
274
434
 
275
- if (element.getAttribute('data-kt-drawer-initialized') === 'true') {
276
- return new KTDrawer(element);
435
+ if (drawerElement.getAttribute('data-kt-drawer-initialized') === 'true') {
436
+ return new KTDrawer(drawerElement);
277
437
  }
278
438
 
279
439
  return null;
@@ -322,6 +482,14 @@ export class KTDrawer extends KTComponent implements KTDrawerInterface {
322
482
  }
323
483
 
324
484
  public static handleToggle(): void {
485
+
486
+ // Add raw click listener to document.body to track all clicks
487
+ document.body.addEventListener('click', (rawEvent: MouseEvent) => {
488
+ const target = rawEvent.target as HTMLElement;
489
+ if (target && target.hasAttribute('data-kt-drawer-toggle')) {
490
+ }
491
+ }, true); // Use capture phase to catch before any stopPropagation
492
+
325
493
  KTEventHandler.on(
326
494
  document.body,
327
495
  '[data-kt-drawer-toggle]',
@@ -332,10 +500,49 @@ export class KTDrawer extends KTComponent implements KTDrawerInterface {
332
500
  const selector = target.getAttribute('data-kt-drawer-toggle');
333
501
  if (!selector) return;
334
502
 
335
- const drawerEl = document.querySelector(selector);
336
- const drawer = KTDrawer.getInstance(drawerEl as HTMLElement);
503
+ // Try to get instance immediately
504
+ const drawer = KTDrawer.getInstance(target);
505
+
337
506
  if (drawer) {
338
507
  drawer.toggle();
508
+ } else {
509
+ // Drawer element not found - wait for it to appear (handles persisted Livewire components)
510
+ // Check if drawer exists in persisted components (might be in header that's persisted)
511
+ const persistedHeader = document.querySelector('[wire\\:id]')?.closest('[wire\\:id]') || document.querySelector('header#header');
512
+ const drawerInPersisted = persistedHeader ? persistedHeader.querySelector(selector) : null;
513
+
514
+ // Wait longer for persisted components that may take time to render
515
+ // Also check if drawer exists in persisted header component
516
+ KTDrawer.waitForElement(selector, 5000).then((drawerElement) => {
517
+ if (drawerElement) {
518
+ // Initialize the drawer if not already initialized
519
+ if (!KTData.has(drawerElement, 'drawer')) {
520
+ new KTDrawer(drawerElement);
521
+ }
522
+ // Get instance and toggle
523
+ const drawerInstance = KTDrawer.getInstance(drawerElement);
524
+ if (drawerInstance) {
525
+ drawerInstance.toggle();
526
+ }
527
+ } else {
528
+ // Drawer never appeared - trigger a reinit to see if it helps
529
+ // This handles cases where drawers are in persisted components that haven't rendered yet
530
+ setTimeout(() => {
531
+ KTDrawer.reinit();
532
+ // Try one more time after reinit
533
+ const drawerAfterReinit = document.querySelector(selector) || document.body.querySelector(selector);
534
+ if (drawerAfterReinit) {
535
+ if (!KTData.has(drawerAfterReinit as HTMLElement, 'drawer')) {
536
+ new KTDrawer(drawerAfterReinit as HTMLElement);
537
+ }
538
+ const drawerInstance = KTDrawer.getInstance(drawerAfterReinit as HTMLElement);
539
+ if (drawerInstance) {
540
+ drawerInstance.toggle();
541
+ }
542
+ }
543
+ }, 500);
544
+ }
545
+ });
339
546
  }
340
547
  },
341
548
  );
@@ -411,8 +618,12 @@ export class KTDrawer extends KTComponent implements KTDrawerInterface {
411
618
  }
412
619
 
413
620
  public static createInstances(): void {
414
- const elements = document.querySelectorAll('[data-kt-drawer]');
415
-
621
+ // Find all drawer elements - check both document and body (drawers with container="body" are moved there)
622
+ const elementsInDoc = document.querySelectorAll('[data-kt-drawer]');
623
+ const elementsInBody = document.body.querySelectorAll('[data-kt-drawer]');
624
+ // Combine and deduplicate
625
+ const allElements = new Set([...Array.from(elementsInDoc), ...Array.from(elementsInBody)]);
626
+ const elements = Array.from(allElements);
416
627
  elements.forEach((element) => {
417
628
  new KTDrawer(element as HTMLElement);
418
629
  });
@@ -430,6 +641,51 @@ export class KTDrawer extends KTComponent implements KTDrawerInterface {
430
641
  window.KT_DRAWER_INITIALIZED = true;
431
642
  }
432
643
  }
644
+
645
+ /**
646
+ * Force reinitialization of drawers by clearing KTData entries.
647
+ * Useful for Livewire wire:navigate where persisted elements need reinitialization.
648
+ */
649
+ public static reinit(): void {
650
+ // Follow the same simple pattern as KTDropdown.reinit()
651
+ // Find all drawer elements - check both document and body (some may be moved to body)
652
+ const elementsInDoc = document.querySelectorAll('[data-kt-drawer]');
653
+ const elementsInBody = document.body.querySelectorAll('[data-kt-drawer]');
654
+ // Combine and deduplicate
655
+ const allElements = new Set([...Array.from(elementsInDoc), ...Array.from(elementsInBody)]);
656
+ const elements = Array.from(allElements);
657
+
658
+
659
+ // Clean up existing instances
660
+ elements.forEach((element) => {
661
+ try {
662
+ // Get existing instance to clean up
663
+ const instance = KTDrawer.getInstance(element as HTMLElement);
664
+ if (instance && typeof instance.hide === 'function') {
665
+ instance.hide(); // This will clean up backdrop and state
666
+ }
667
+ // Clear KTData entries
668
+ const hadDrawer = KTData.has(element as HTMLElement, 'drawer');
669
+ KTData.remove(element as HTMLElement, 'drawer');
670
+ // Remove initialization attribute to allow fresh initialization
671
+ element.removeAttribute('data-kt-drawer-initialized');
672
+ } catch (e) {
673
+ // Ignore errors for individual elements
674
+ }
675
+ });
676
+
677
+ // Now create fresh instances
678
+ KTDrawer.createInstances();
679
+
680
+ // Always ensure handlers are set up (similar to KTMenu.init() behavior)
681
+ // Event handlers use delegation so they persist, but we ensure they're attached
682
+ KTDrawer.handleToggle();
683
+ KTDrawer.handleDismiss();
684
+ KTDrawer.handleResize();
685
+ KTDrawer.handleClickAway();
686
+ KTDrawer.handleKeyword();
687
+
688
+ }
433
689
  }
434
690
 
435
691
  if (typeof window !== 'undefined') {
@@ -49,7 +49,9 @@ export class KTDropdown extends KTComponent implements KTDropdownInterface {
49
49
  constructor(element: HTMLElement, config?: KTDropdownConfigInterface) {
50
50
  super();
51
51
 
52
- if (KTData.has(element as HTMLElement, this._name)) return;
52
+ if (KTData.has(element as HTMLElement, this._name)) {
53
+ return;
54
+ }
53
55
 
54
56
  this._init(element);
55
57
  this._buildConfig(config);
@@ -57,11 +59,15 @@ export class KTDropdown extends KTComponent implements KTDropdownInterface {
57
59
  this._toggleElement = this._element.querySelector(
58
60
  '[data-kt-dropdown-toggle]',
59
61
  ) as HTMLElement;
60
- if (!this._toggleElement) return;
62
+ if (!this._toggleElement) {
63
+ return;
64
+ }
61
65
  this._menuElement = this._element.querySelector(
62
66
  '[data-kt-dropdown-menu]',
63
67
  ) as HTMLElement;
64
- if (!this._menuElement) return;
68
+ if (!this._menuElement) {
69
+ return;
70
+ }
65
71
 
66
72
  KTData.set(this._menuElement, 'dropdownElement', this._element);
67
73
  this._setupNestedDropdowns();
@@ -101,9 +107,13 @@ export class KTDropdown extends KTComponent implements KTDropdownInterface {
101
107
  event.preventDefault();
102
108
  event.stopPropagation();
103
109
 
104
- if (this._disabled) return;
110
+ if (this._disabled) {
111
+ return;
112
+ }
105
113
 
106
- if (this._getOption('trigger') !== 'click') return;
114
+ if (this._getOption('trigger') !== 'click') {
115
+ return;
116
+ }
107
117
 
108
118
  this._toggle();
109
119
  }
@@ -154,7 +164,9 @@ export class KTDropdown extends KTComponent implements KTDropdownInterface {
154
164
  }
155
165
 
156
166
  protected _show(): void {
157
- if (this._isOpen || this._isTransitioning) return;
167
+ if (this._isOpen || this._isTransitioning) {
168
+ return;
169
+ }
158
170
 
159
171
  const payload = { cancel: false };
160
172
  this._fireEvent('show', payload);
@@ -372,6 +384,12 @@ export class KTDropdown extends KTComponent implements KTDropdownInterface {
372
384
  (reference.closest('[data-kt-dropdown-initialized]') as HTMLElement);
373
385
  if (findElement) return findElement;
374
386
 
387
+ // Fallback: look for parent with data-kt-dropdown attribute
388
+ if (reference) {
389
+ const dropdownContainer = reference.closest('[data-kt-dropdown]') as HTMLElement;
390
+ if (dropdownContainer) return dropdownContainer;
391
+ }
392
+
375
393
  if (
376
394
  reference &&
377
395
  reference.hasAttribute('data-kt-dropdown-menu') &&
@@ -386,10 +404,13 @@ export class KTDropdown extends KTComponent implements KTDropdownInterface {
386
404
  public static getInstance(element: HTMLElement): KTDropdown {
387
405
  element = this.getElement(element);
388
406
 
389
- if (!element) return null;
407
+ if (!element) {
408
+ return null;
409
+ }
390
410
 
391
411
  if (KTData.has(element, 'dropdown')) {
392
- return KTData.get(element, 'dropdown') as KTDropdown;
412
+ const instance = KTData.get(element, 'dropdown') as KTDropdown;
413
+ return instance;
393
414
  }
394
415
 
395
416
  if (element.getAttribute('data-kt-dropdown-initialized') === 'true') {
@@ -556,6 +577,40 @@ export class KTDropdown extends KTComponent implements KTDropdownInterface {
556
577
  window.KT_DROPDOWN_INITIALIZED = true;
557
578
  }
558
579
  }
580
+
581
+ /**
582
+ * Force reinitialization of dropdowns by clearing KTData entries.
583
+ * Useful for Livewire wire:navigate where persisted elements need reinitialization.
584
+ */
585
+ public static reinit(): void {
586
+ const elements = document.querySelectorAll('[data-kt-dropdown]');
587
+ elements.forEach((element) => {
588
+ try {
589
+ // Get existing instance to clean up Popper
590
+ const instance = KTDropdown.getInstance(element as HTMLElement);
591
+ if (instance && typeof instance.hide === 'function') {
592
+ instance.hide(); // This will destroy Popper
593
+ }
594
+ // Clear KTData entries
595
+ KTData.remove(element as HTMLElement, 'dropdown');
596
+ KTData.remove(element as HTMLElement, 'popper');
597
+ // Remove initialization attribute to allow fresh initialization
598
+ element.removeAttribute('data-kt-dropdown-initialized');
599
+ const menu = element.querySelector('[data-kt-dropdown-menu]');
600
+ if (menu) {
601
+ KTData.remove(menu as HTMLElement, 'dropdownElement');
602
+ }
603
+ } catch (e) {
604
+ // Ignore errors for individual elements
605
+ }
606
+ });
607
+ // Now create fresh instances
608
+ KTDropdown.createInstances();
609
+
610
+ // Always ensure handlers are set up (similar to KTMenu.init() behavior)
611
+ // Event handlers use delegation so they persist, but we ensure they're attached
612
+ KTDropdown.initHandlers();
613
+ }
559
614
  }
560
615
 
561
616
  if (typeof window !== 'undefined') {