@keenthemes/ktui 1.1.2 → 1.1.4

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 +532 -59
  2. package/dist/ktui.min.js +1 -1
  3. package/dist/ktui.min.js.map +1 -1
  4. package/dist/styles.css +51 -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 +26 -7
  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 +26 -7
  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 +32 -7
  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 +7 -2
  52. package/src/components/select/select.ts +112 -20
  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
@@ -149,23 +149,31 @@ export class KTSelect extends KTComponent {
149
149
  const dispatchGlobalEvents =
150
150
  this._config.dispatchGlobalEvents !== false; // Default to true
151
151
  if (dispatchGlobalEvents) {
152
- // Create namespaced event name for document dispatch
153
- const namespacedEventType = `kt-select:${eventType}`;
154
-
155
- // Create event with same detail structure
156
- const globalEvent = new CustomEvent(namespacedEventType, {
157
- detail: {
158
- payload,
159
- instance: this, // Include component instance reference
160
- element: this._element, // Include element reference
161
- },
152
+ // Create event detail structure
153
+ const eventDetail = {
154
+ payload,
155
+ instance: this, // Include component instance reference
156
+ element: this._element, // Include element reference
157
+ };
158
+
159
+ // Dispatch non-namespaced event on document (for jQuery compatibility: $(document).on('show', ...))
160
+ const nonNamespacedEvent = new CustomEvent(eventType, {
161
+ detail: eventDetail,
162
162
  bubbles: true,
163
163
  cancelable: true,
164
164
  composed: true, // Allow event to cross shadow DOM boundaries
165
165
  });
166
+ document.dispatchEvent(nonNamespacedEvent);
166
167
 
167
- // Dispatch on document
168
- document.dispatchEvent(globalEvent);
168
+ // Also dispatch namespaced event on document (for namespaced listeners: $(document).on('kt-select:show', ...))
169
+ const namespacedEventType = `kt-select:${eventType}`;
170
+ const namespacedEvent = new CustomEvent(namespacedEventType, {
171
+ detail: eventDetail,
172
+ bubbles: true,
173
+ cancelable: true,
174
+ composed: true, // Allow event to cross shadow DOM boundaries
175
+ });
176
+ document.dispatchEvent(namespacedEvent);
169
177
  }
170
178
  }
171
179
 
@@ -1113,7 +1121,24 @@ export class KTSelect extends KTComponent {
1113
1121
  this.updateSelectAllButtonState();
1114
1122
 
1115
1123
  // Focus the first selected option or first option if nothing selected
1116
- this._focusSelectedOption();
1124
+ // BUT: Skip this if search autofocus is enabled, as we want search input to get focus
1125
+ if (!(this._config.enableSearch && this._config.searchAutofocus)) {
1126
+ this._focusSelectedOption();
1127
+ }
1128
+
1129
+ // Dispatch dropdown.show event on the wrapper element for search module
1130
+ // Use requestAnimationFrame to ensure dropdown is visible and transition has started
1131
+ requestAnimationFrame(() => {
1132
+ requestAnimationFrame(() => {
1133
+ if (this._wrapperElement) {
1134
+ const dropdownShowEvent = new CustomEvent('dropdown.show', {
1135
+ bubbles: true,
1136
+ cancelable: true,
1137
+ });
1138
+ this._wrapperElement.dispatchEvent(dropdownShowEvent);
1139
+ }
1140
+ });
1141
+ });
1117
1142
  }
1118
1143
 
1119
1144
  /**
@@ -1150,7 +1175,7 @@ export class KTSelect extends KTComponent {
1150
1175
  this._focusManager.resetFocus();
1151
1176
  }
1152
1177
 
1153
- // Dispatch custom events
1178
+ // Dispatch custom events on the select element
1154
1179
  this._dispatchEvent('close');
1155
1180
  this._fireEvent('close');
1156
1181
 
@@ -1315,15 +1340,18 @@ export class KTSelect extends KTComponent {
1315
1340
  return; // Exit early to prevent any text generation
1316
1341
  } else {
1317
1342
  // Tags are not enabled AND options are selected: render normal text display.
1318
- let content = '';
1343
+ // Wrap content in .kt-select-option-text so long text truncates in single-select (see Asana #1212821478465094).
1344
+ const wrapper = document.createElement('div');
1345
+ wrapper.className = 'kt-select-option-text';
1346
+ wrapper.setAttribute('data-kt-text-container', 'true');
1319
1347
  if (this._config.displayTemplate) {
1320
- content = this.renderDisplayTemplateForSelected(
1348
+ wrapper.innerHTML = this.renderDisplayTemplateForSelected(
1321
1349
  this.getSelectedOptions(),
1322
1350
  );
1323
1351
  } else {
1324
- content = this.getSelectedOptionsText();
1352
+ wrapper.textContent = this.getSelectedOptionsText();
1325
1353
  }
1326
- valueDisplayEl.innerHTML = content;
1354
+ valueDisplayEl.replaceChildren(wrapper);
1327
1355
  }
1328
1356
  }
1329
1357
  }
@@ -1409,6 +1437,65 @@ export class KTSelect extends KTComponent {
1409
1437
  this._fireEvent('change');
1410
1438
  }
1411
1439
 
1440
+ /**
1441
+ * Deselect a specific option by value
1442
+ * @param value The value of the option to deselect
1443
+ * @public
1444
+ */
1445
+ public deselectOption(value: string): void {
1446
+ // Check if the option is currently selected
1447
+ if (!this._state.isSelected(value)) {
1448
+ return; // Already deselected
1449
+ }
1450
+
1451
+ // For single-select mode, check if clearing is allowed
1452
+ if (!this._config.multiple && !this._config.allowClear) {
1453
+ return; // Cannot deselect in single-select mode unless allowClear is true
1454
+ }
1455
+
1456
+ // Remove from selected options
1457
+ if (this._config.multiple) {
1458
+ // For multiple select, just toggle it off
1459
+ this._state.toggleSelectedOptions(value);
1460
+ } else {
1461
+ // For single select, clear all selections
1462
+ this._state.setSelectedOptions([]);
1463
+ }
1464
+
1465
+ // Update the native select element
1466
+ const optionEl = Array.from(this._element.querySelectorAll('option')).find(
1467
+ (opt) => opt.value === value,
1468
+ ) as HTMLOptionElement;
1469
+
1470
+ if (optionEl) {
1471
+ optionEl.selected = false;
1472
+ }
1473
+
1474
+ // For single select, clear the native select value
1475
+ if (!this._config.multiple) {
1476
+ (this._element as HTMLSelectElement).value = '';
1477
+ }
1478
+
1479
+ // Update the display
1480
+ this.updateSelectedOptionDisplay();
1481
+ this._updateSelectedOptionClass();
1482
+
1483
+ // Update select all button state
1484
+ this.updateSelectAllButtonState();
1485
+
1486
+ // Dispatch change event
1487
+ this._dispatchEvent('change', {
1488
+ value: value,
1489
+ selected: false,
1490
+ selectedOptions: this.getSelectedOptions(),
1491
+ });
1492
+ this._fireEvent('change', {
1493
+ value: value,
1494
+ selected: false,
1495
+ selectedOptions: this.getSelectedOptions(),
1496
+ });
1497
+ }
1498
+
1412
1499
  /**
1413
1500
  * Set selected options programmatically
1414
1501
  */
@@ -1657,9 +1744,14 @@ export class KTSelect extends KTComponent {
1657
1744
  // Get current selection state
1658
1745
  const isSelected = this._state.isSelected(value);
1659
1746
 
1660
- // If already selected in single select mode, do nothing (can't deselect in single select)
1747
+ // If already selected in single select mode, allow deselecting only if allowClear is true
1661
1748
  if (isSelected && !this._config.multiple) {
1662
- return;
1749
+ if (this._config.allowClear) {
1750
+ // Use the deselectOption method to handle clearing
1751
+ this.deselectOption(value);
1752
+ return;
1753
+ }
1754
+ return; // Can't deselect in single select mode when allowClear is false
1663
1755
  }
1664
1756
 
1665
1757
  // Ensure any search input is cleared when selection changes
@@ -44,6 +44,8 @@ export class KTSticky extends KTComponent implements KTStickyInterface {
44
44
  protected _releaseElement: HTMLElement;
45
45
  protected _activateElement: HTMLElement;
46
46
  protected _wrapperElement: HTMLElement;
47
+ private _resizeHandler: (() => void) | null = null;
48
+ private _scrollHandler: (() => void) | null = null;
47
49
 
48
50
  constructor(
49
51
  element: HTMLElement,
@@ -51,7 +53,10 @@ export class KTSticky extends KTComponent implements KTStickyInterface {
51
53
  ) {
52
54
  super();
53
55
 
54
- if (KTData.has(element as HTMLElement, this._name)) return;
56
+ // Check if element already has an instance and is still connected
57
+ if (this._shouldSkipInit(element)) {
58
+ return;
59
+ }
55
60
 
56
61
  this._init(element);
57
62
  this._buildConfig(config);
@@ -88,7 +93,8 @@ export class KTSticky extends KTComponent implements KTStickyInterface {
88
93
  }
89
94
 
90
95
  protected _handlers(): void {
91
- window.addEventListener('resize', () => {
96
+ // Store resize handler reference for cleanup
97
+ this._resizeHandler = () => {
92
98
  let timer;
93
99
 
94
100
  KTUtils.throttle(
@@ -98,11 +104,25 @@ export class KTSticky extends KTComponent implements KTStickyInterface {
98
104
  },
99
105
  200,
100
106
  );
101
- });
107
+ };
102
108
 
103
- this._targetElement.addEventListener('scroll', () => {
109
+ window.addEventListener('resize', this._resizeHandler);
110
+
111
+ // Store scroll handler reference for cleanup
112
+ this._scrollHandler = () => {
104
113
  this._process();
105
- });
114
+ };
115
+
116
+ if (this._targetElement) {
117
+ if (this._targetElement === document) {
118
+ window.addEventListener('scroll', this._scrollHandler);
119
+ } else {
120
+ (this._targetElement as HTMLElement).addEventListener(
121
+ 'scroll',
122
+ this._scrollHandler,
123
+ );
124
+ }
125
+ }
106
126
  }
107
127
 
108
128
  protected _process(): void {
@@ -373,6 +393,36 @@ export class KTSticky extends KTComponent implements KTStickyInterface {
373
393
  return this._isActive();
374
394
  }
375
395
 
396
+ public override dispose(): void {
397
+ // Remove resize event listener
398
+ if (this._resizeHandler) {
399
+ window.removeEventListener('resize', this._resizeHandler);
400
+ this._resizeHandler = null;
401
+ }
402
+
403
+ // Remove scroll event listener
404
+ if (this._scrollHandler) {
405
+ if (this._targetElement === document) {
406
+ window.removeEventListener('scroll', this._scrollHandler);
407
+ } else if (this._targetElement) {
408
+ (this._targetElement as HTMLElement).removeEventListener(
409
+ 'scroll',
410
+ this._scrollHandler,
411
+ );
412
+ }
413
+ this._scrollHandler = null;
414
+ }
415
+
416
+ // Clean up state
417
+ this._disable();
418
+ if (this._attributeRoot && document.body.hasAttribute(this._attributeRoot)) {
419
+ document.body.removeAttribute(this._attributeRoot);
420
+ }
421
+
422
+ // Call parent dispose to clean up data attributes and KTData
423
+ super.dispose();
424
+ }
425
+
376
426
  public static getInstance(element: HTMLElement): KTSticky {
377
427
  if (!element) return null;
378
428
 
@@ -41,6 +41,16 @@ const KTData = {
41
41
  KTElementMap.delete(element);
42
42
  }
43
43
  },
44
+
45
+ // Clear all data for a specific element (useful for reinitialization)
46
+ clear(element: HTMLElement): void {
47
+ KTElementMap.delete(element);
48
+ },
44
49
  };
45
50
 
51
+ // Expose KTData on window for external access (useful for Livewire wire:navigate)
52
+ if (typeof window !== 'undefined') {
53
+ window.KTData = KTData;
54
+ }
55
+
46
56
  export default KTData;
@@ -28,14 +28,15 @@ const KTEventHandler = {
28
28
  KTDelegatedEventHandlers[eventId] = (
29
29
  event: Event & { target: HTMLElement },
30
30
  ) => {
31
- const targets = element.querySelectorAll(selector);
32
- let target = event.target;
31
+ // Fix: Check selector dynamically instead of pre-computing targets
32
+ // This allows event delegation to work with dynamically added elements
33
+ let target = event.target as HTMLElement;
33
34
 
34
35
  while (target && target !== element) {
35
- for (let i = 0, j = targets.length; i < j; i++) {
36
- if (target === targets[i]) {
37
- handler.call(this, event, target);
38
- }
36
+ // Check if current target matches the selector
37
+ if (target.matches && target.matches(selector)) {
38
+ handler.call(this, event, target);
39
+ return; // Stop bubbling once we've handled it
39
40
  }
40
41
 
41
42
  target = target.parentNode as HTMLElement;
package/src/index.ts CHANGED
@@ -6,6 +6,7 @@
6
6
  import KTDom from './helpers/dom';
7
7
  import KTUtils from './helpers/utils';
8
8
  import KTEventHandler from './helpers/event-handler';
9
+ import KTData from './helpers/data';
9
10
  import { KTDropdown } from './components/dropdown';
10
11
  import { KTModal } from './components/modal';
11
12
  import { KTDrawer } from './components/drawer';
@@ -81,6 +82,7 @@ declare global {
81
82
  KTUtils: typeof KTUtils;
82
83
  KTDom: typeof KTDom;
83
84
  KTEventHandler: typeof KTEventHandler;
85
+ KTData: typeof KTData;
84
86
  KTDropdown: typeof KTDropdown;
85
87
  KTModal: typeof KTModal;
86
88
  KTDrawer: typeof KTDrawer;