@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.
- package/dist/ktui.js +532 -59
- package/dist/ktui.min.js +1 -1
- package/dist/ktui.min.js.map +1 -1
- package/dist/styles.css +51 -0
- package/lib/cjs/components/component.js +22 -0
- package/lib/cjs/components/component.js.map +1 -1
- package/lib/cjs/components/datatable/datatable.js +26 -7
- package/lib/cjs/components/datatable/datatable.js.map +1 -1
- package/lib/cjs/components/drawer/drawer.js +255 -9
- package/lib/cjs/components/drawer/drawer.js.map +1 -1
- package/lib/cjs/components/dropdown/dropdown.js +55 -8
- package/lib/cjs/components/dropdown/dropdown.js.map +1 -1
- package/lib/cjs/components/select/search.js +17 -7
- package/lib/cjs/components/select/search.js.map +1 -1
- package/lib/cjs/components/select/select.js +92 -14
- package/lib/cjs/components/select/select.js.map +1 -1
- package/lib/cjs/components/sticky/sticky.js +44 -5
- package/lib/cjs/components/sticky/sticky.js.map +1 -1
- package/lib/cjs/helpers/data.js +8 -0
- package/lib/cjs/helpers/data.js.map +1 -1
- package/lib/cjs/helpers/event-handler.js +6 -5
- package/lib/cjs/helpers/event-handler.js.map +1 -1
- package/lib/cjs/index.js.map +1 -1
- package/lib/esm/components/component.js +22 -0
- package/lib/esm/components/component.js.map +1 -1
- package/lib/esm/components/datatable/datatable.js +26 -7
- package/lib/esm/components/datatable/datatable.js.map +1 -1
- package/lib/esm/components/drawer/drawer.js +255 -9
- package/lib/esm/components/drawer/drawer.js.map +1 -1
- package/lib/esm/components/dropdown/dropdown.js +55 -8
- package/lib/esm/components/dropdown/dropdown.js.map +1 -1
- package/lib/esm/components/select/search.js +17 -7
- package/lib/esm/components/select/search.js.map +1 -1
- package/lib/esm/components/select/select.js +92 -14
- package/lib/esm/components/select/select.js.map +1 -1
- package/lib/esm/components/sticky/sticky.js +44 -5
- package/lib/esm/components/sticky/sticky.js.map +1 -1
- package/lib/esm/helpers/data.js +8 -0
- package/lib/esm/helpers/data.js.map +1 -1
- package/lib/esm/helpers/event-handler.js +6 -5
- package/lib/esm/helpers/event-handler.js.map +1 -1
- package/lib/esm/index.js.map +1 -1
- package/package.json +4 -2
- package/src/components/component.ts +26 -0
- package/src/components/datatable/__tests__/race-conditions.test.ts +2 -2
- package/src/components/datatable/datatable.ts +32 -7
- package/src/components/drawer/drawer.ts +266 -10
- package/src/components/dropdown/dropdown.ts +63 -8
- package/src/components/select/__tests__/ux-behaviors.test.ts +382 -4
- package/src/components/select/search.ts +16 -7
- package/src/components/select/select.css +7 -2
- package/src/components/select/select.ts +112 -20
- package/src/components/sticky/sticky.ts +55 -5
- package/src/helpers/data.ts +10 -0
- package/src/helpers/event-handler.ts +7 -6
- 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
|
|
153
|
-
const
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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
|
-
//
|
|
168
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
1348
|
+
wrapper.innerHTML = this.renderDisplayTemplateForSelected(
|
|
1321
1349
|
this.getSelectedOptions(),
|
|
1322
1350
|
);
|
|
1323
1351
|
} else {
|
|
1324
|
-
|
|
1352
|
+
wrapper.textContent = this.getSelectedOptionsText();
|
|
1325
1353
|
}
|
|
1326
|
-
valueDisplayEl.
|
|
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,
|
|
1747
|
+
// If already selected in single select mode, allow deselecting only if allowClear is true
|
|
1661
1748
|
if (isSelected && !this._config.multiple) {
|
|
1662
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
package/src/helpers/data.ts
CHANGED
|
@@ -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
|
-
|
|
32
|
-
|
|
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
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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;
|