@keenthemes/ktui 1.2.0 → 1.2.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.
Files changed (111) hide show
  1. package/dist/ktui.js +3349 -1550
  2. package/dist/ktui.min.js +1 -1
  3. package/dist/ktui.min.js.map +1 -1
  4. package/dist/styles.css +1 -1
  5. package/lib/cjs/components/clipboard/clipboard.d.ts +37 -0
  6. package/lib/cjs/components/clipboard/clipboard.d.ts.map +1 -0
  7. package/lib/cjs/components/clipboard/clipboard.js +402 -0
  8. package/lib/cjs/components/clipboard/clipboard.js.map +1 -0
  9. package/lib/cjs/components/clipboard/index.d.ts +3 -0
  10. package/lib/cjs/components/clipboard/index.d.ts.map +1 -0
  11. package/lib/cjs/components/clipboard/index.js +6 -0
  12. package/lib/cjs/components/clipboard/index.js.map +1 -0
  13. package/lib/cjs/components/clipboard/types.d.ts +44 -0
  14. package/lib/cjs/components/clipboard/types.d.ts.map +1 -0
  15. package/lib/cjs/components/clipboard/types.js +7 -0
  16. package/lib/cjs/components/clipboard/types.js.map +1 -0
  17. package/lib/cjs/components/datatable/datatable.d.ts.map +1 -1
  18. package/lib/cjs/components/datatable/datatable.js.map +1 -1
  19. package/lib/cjs/components/range-slider/index.d.ts +7 -0
  20. package/lib/cjs/components/range-slider/index.d.ts.map +1 -0
  21. package/lib/cjs/components/range-slider/index.js +10 -0
  22. package/lib/cjs/components/range-slider/index.js.map +1 -0
  23. package/lib/cjs/components/range-slider/range-slider.d.ts +42 -0
  24. package/lib/cjs/components/range-slider/range-slider.d.ts.map +1 -0
  25. package/lib/cjs/components/range-slider/range-slider.js +254 -0
  26. package/lib/cjs/components/range-slider/range-slider.js.map +1 -0
  27. package/lib/cjs/components/range-slider/types.d.ts +33 -0
  28. package/lib/cjs/components/range-slider/types.d.ts.map +1 -0
  29. package/lib/cjs/components/range-slider/types.js +7 -0
  30. package/lib/cjs/components/range-slider/types.js.map +1 -0
  31. package/lib/cjs/components/rating/rating.d.ts.map +1 -1
  32. package/lib/cjs/components/rating/rating.js +8 -3
  33. package/lib/cjs/components/rating/rating.js.map +1 -1
  34. package/lib/cjs/components/repeater/repeater.d.ts.map +1 -1
  35. package/lib/cjs/components/repeater/repeater.js +3 -2
  36. package/lib/cjs/components/repeater/repeater.js.map +1 -1
  37. package/lib/cjs/components/select/utils.d.ts.map +1 -1
  38. package/lib/cjs/components/select/utils.js +3 -1
  39. package/lib/cjs/components/select/utils.js.map +1 -1
  40. package/lib/cjs/components/sticky/sticky.d.ts.map +1 -1
  41. package/lib/cjs/components/sticky/sticky.js +3 -1
  42. package/lib/cjs/components/sticky/sticky.js.map +1 -1
  43. package/lib/cjs/index.d.ts +8 -0
  44. package/lib/cjs/index.d.ts.map +1 -1
  45. package/lib/cjs/index.js +9 -1
  46. package/lib/cjs/index.js.map +1 -1
  47. package/lib/esm/components/clipboard/clipboard.d.ts +37 -0
  48. package/lib/esm/components/clipboard/clipboard.d.ts.map +1 -0
  49. package/lib/esm/components/clipboard/clipboard.js +399 -0
  50. package/lib/esm/components/clipboard/clipboard.js.map +1 -0
  51. package/lib/esm/components/clipboard/index.d.ts +3 -0
  52. package/lib/esm/components/clipboard/index.d.ts.map +1 -0
  53. package/lib/esm/components/clipboard/index.js +2 -0
  54. package/lib/esm/components/clipboard/index.js.map +1 -0
  55. package/lib/esm/components/clipboard/types.d.ts +44 -0
  56. package/lib/esm/components/clipboard/types.d.ts.map +1 -0
  57. package/lib/esm/components/clipboard/types.js +6 -0
  58. package/lib/esm/components/clipboard/types.js.map +1 -0
  59. package/lib/esm/components/datatable/datatable.d.ts.map +1 -1
  60. package/lib/esm/components/datatable/datatable.js.map +1 -1
  61. package/lib/esm/components/range-slider/index.d.ts +7 -0
  62. package/lib/esm/components/range-slider/index.d.ts.map +1 -0
  63. package/lib/esm/components/range-slider/index.js +6 -0
  64. package/lib/esm/components/range-slider/index.js.map +1 -0
  65. package/lib/esm/components/range-slider/range-slider.d.ts +42 -0
  66. package/lib/esm/components/range-slider/range-slider.d.ts.map +1 -0
  67. package/lib/esm/components/range-slider/range-slider.js +251 -0
  68. package/lib/esm/components/range-slider/range-slider.js.map +1 -0
  69. package/lib/esm/components/range-slider/types.d.ts +33 -0
  70. package/lib/esm/components/range-slider/types.d.ts.map +1 -0
  71. package/lib/esm/components/range-slider/types.js +6 -0
  72. package/lib/esm/components/range-slider/types.js.map +1 -0
  73. package/lib/esm/components/rating/rating.d.ts.map +1 -1
  74. package/lib/esm/components/rating/rating.js +8 -3
  75. package/lib/esm/components/rating/rating.js.map +1 -1
  76. package/lib/esm/components/repeater/repeater.d.ts.map +1 -1
  77. package/lib/esm/components/repeater/repeater.js +3 -2
  78. package/lib/esm/components/repeater/repeater.js.map +1 -1
  79. package/lib/esm/components/select/utils.d.ts.map +1 -1
  80. package/lib/esm/components/select/utils.js +3 -1
  81. package/lib/esm/components/select/utils.js.map +1 -1
  82. package/lib/esm/components/sticky/sticky.d.ts.map +1 -1
  83. package/lib/esm/components/sticky/sticky.js +3 -1
  84. package/lib/esm/components/sticky/sticky.js.map +1 -1
  85. package/lib/esm/index.d.ts +8 -0
  86. package/lib/esm/index.d.ts.map +1 -1
  87. package/lib/esm/index.js +6 -0
  88. package/lib/esm/index.js.map +1 -1
  89. package/package.json +1 -2
  90. package/src/components/clipboard/__tests__/clipboard.test.ts +438 -0
  91. package/src/components/clipboard/clipboard.ts +416 -0
  92. package/src/components/clipboard/index.ts +2 -0
  93. package/src/components/clipboard/types.ts +51 -0
  94. package/src/components/datatable/__tests__/currency-sort.test.ts +2 -10
  95. package/src/components/datatable/__tests__/multi-row-headers.test.ts +2 -2
  96. package/src/components/datatable/__tests__/race-conditions.test.ts +11 -14
  97. package/src/components/datatable/datatable.ts +3 -5
  98. package/src/components/range-slider/__tests__/range-slider.test.ts +659 -0
  99. package/src/components/range-slider/index.ts +11 -0
  100. package/src/components/range-slider/range-slider.ts +276 -0
  101. package/src/components/range-slider/types.ts +36 -0
  102. package/src/components/rating/__tests__/rating.test.ts +11 -4
  103. package/src/components/rating/rating.ts +22 -11
  104. package/src/components/repeater/__tests__/repeater.test.ts +19 -6
  105. package/src/components/repeater/repeater.ts +5 -3
  106. package/src/components/select/__tests__/ux-behaviors.test.ts +21 -3
  107. package/src/components/select/utils.ts +5 -1
  108. package/src/components/sticky/__tests__/sticky.test.ts +10 -3
  109. package/src/components/sticky/sticky.ts +14 -24
  110. package/src/components/sticky/types.ts +3 -3
  111. package/src/index.ts +17 -0
@@ -0,0 +1,276 @@
1
+ /**
2
+ * KTUI - Free & Open-Source Tailwind UI Components by Keenthemes
3
+ * Copyright 2025 by Keenthemes Inc
4
+ */
5
+
6
+ import KTData from '../../helpers/data';
7
+ import KTComponent from '../component';
8
+ import {
9
+ KTRangeSliderConfigInterface,
10
+ KTRangeSliderEventPayloadInterface,
11
+ KTRangeSliderInterface,
12
+ } from './types';
13
+
14
+ declare global {
15
+ interface Window {
16
+ KTRangeSlider: typeof KTRangeSlider;
17
+ }
18
+ }
19
+
20
+ const FILL_VAR = '--kt-range-fill';
21
+
22
+ export class KTRangeSlider
23
+ extends KTComponent
24
+ implements KTRangeSliderInterface
25
+ {
26
+ protected override _name: string = 'range-slider';
27
+ protected override _defaultConfig: KTRangeSliderConfigInterface = {
28
+ output: '',
29
+ lazy: false,
30
+ };
31
+ protected override _config: KTRangeSliderConfigInterface =
32
+ this._defaultConfig;
33
+ protected _rangeInput: HTMLInputElement | null = null;
34
+ protected _onNativeInput: ((e: Event) => void) | null = null;
35
+ protected _onNativeChange: ((e: Event) => void) | null = null;
36
+
37
+ constructor(
38
+ element: HTMLElement,
39
+ config: KTRangeSliderConfigInterface | null = null,
40
+ ) {
41
+ super();
42
+
43
+ const input = KTRangeSlider.findRangeInput(element);
44
+ if (!input) {
45
+ return;
46
+ }
47
+
48
+ if (this._shouldSkipInit(element)) {
49
+ return;
50
+ }
51
+
52
+ this._rangeInput = input;
53
+ this._init(element);
54
+ this._buildConfig(config);
55
+
56
+ this._onNativeInput = this._handleNativeInput.bind(this);
57
+ this._onNativeChange = this._handleNativeChange.bind(this);
58
+ // Delegate events to the root so we keep working if the preview
59
+ // re-renders and replaces the native range input element.
60
+ this._element?.addEventListener('input', this._onNativeInput);
61
+ this._element?.addEventListener('change', this._onNativeChange);
62
+
63
+ this._syncFromInput();
64
+ }
65
+
66
+ private static findRangeInput(root: HTMLElement): HTMLInputElement | null {
67
+ if (root instanceof HTMLInputElement && root.type === 'range') {
68
+ return root;
69
+ }
70
+ return root.querySelector<HTMLInputElement>('input[type="range"]');
71
+ }
72
+
73
+ protected _getOutputSelector(): string {
74
+ const raw = this._getOption('output');
75
+ return typeof raw === 'string' ? raw.trim() : '';
76
+ }
77
+
78
+ protected _resolveOutputElement(): HTMLElement | null {
79
+ const selector = this._getOutputSelector();
80
+ if (!selector || !this._element) {
81
+ return null;
82
+ }
83
+ const fromRoot = this._element.querySelector<HTMLElement>(selector);
84
+ if (fromRoot) {
85
+ return fromRoot;
86
+ }
87
+ return document.querySelector<HTMLElement>(selector);
88
+ }
89
+
90
+ protected _getNumericMin(): number {
91
+ const input = this._rangeInput;
92
+ if (!input) return 0;
93
+ const n =
94
+ typeof input.min === 'string' && input.min !== ''
95
+ ? parseFloat(input.min)
96
+ : 0;
97
+ return Number.isFinite(n) ? n : 0;
98
+ }
99
+
100
+ protected _getNumericMax(): number {
101
+ const input = this._rangeInput;
102
+ if (!input) return 100;
103
+ if (typeof input.max === 'string' && input.max !== '') {
104
+ const n = parseFloat(input.max);
105
+ if (Number.isFinite(n)) return n;
106
+ }
107
+ return 100;
108
+ }
109
+
110
+ /** Reflects `step` for events; `undefined` when `step="any"`. */
111
+ protected _getStepForPayload(): number | undefined {
112
+ const input = this._rangeInput;
113
+ if (!input) return undefined;
114
+ const raw = input.getAttribute('step');
115
+ if (raw === 'any') return undefined;
116
+ if (raw === null || raw === '') return 1;
117
+ const n = parseFloat(raw);
118
+ return Number.isFinite(n) && n > 0 ? n : 1;
119
+ }
120
+
121
+ protected _getCurrentValue(): number {
122
+ const input = this._rangeInput;
123
+ if (!input) return 0;
124
+ const v =
125
+ typeof input.valueAsNumber === 'number' &&
126
+ !Number.isNaN(input.valueAsNumber)
127
+ ? input.valueAsNumber
128
+ : parseFloat(input.value);
129
+ return Number.isFinite(v) ? v : 0;
130
+ }
131
+
132
+ protected _clamp(value: number, min: number, max: number): number {
133
+ if (max < min) {
134
+ return value;
135
+ }
136
+ return Math.min(max, Math.max(min, value));
137
+ }
138
+
139
+ protected _fillRatio(value: number, min: number, max: number): number {
140
+ if (max === min) {
141
+ return 0;
142
+ }
143
+ return (this._clamp(value, min, max) - min) / (max - min);
144
+ }
145
+
146
+ protected _buildEventPayload(): KTRangeSliderEventPayloadInterface {
147
+ const min = this._getNumericMin();
148
+ const max = this._getNumericMax();
149
+ const value = this._getCurrentValue();
150
+ const step = this._getStepForPayload();
151
+ return {
152
+ value,
153
+ min,
154
+ max,
155
+ ...(step !== undefined ? { step } : {}),
156
+ };
157
+ }
158
+
159
+ protected _syncFromInput(): void {
160
+ if (!this._element || !this._rangeInput) {
161
+ return;
162
+ }
163
+
164
+ const min = this._getNumericMin();
165
+ const max = this._getNumericMax();
166
+ const value = this._getCurrentValue();
167
+ const ratio = this._fillRatio(value, min, max);
168
+
169
+ this._element.style.setProperty(FILL_VAR, String(ratio));
170
+
171
+ const out = this._resolveOutputElement();
172
+ if (out) {
173
+ out.textContent = String(this._rangeInput.value);
174
+ }
175
+ }
176
+
177
+ protected _handleNativeInput(_event: Event): void {
178
+ const event = _event as Event;
179
+ const target = event.target;
180
+ if (!(target instanceof HTMLInputElement) || target.type !== 'range') {
181
+ return;
182
+ }
183
+
184
+ this._rangeInput = target;
185
+ this._syncFromInput();
186
+
187
+ const payload = this._buildEventPayload();
188
+ this._fireEvent('input', payload);
189
+ this._dispatchEvent('kt.range-slider.input', payload);
190
+ }
191
+
192
+ protected _handleNativeChange(_event: Event): void {
193
+ const event = _event as Event;
194
+ const target = event.target;
195
+ if (!(target instanceof HTMLInputElement) || target.type !== 'range') {
196
+ return;
197
+ }
198
+
199
+ this._rangeInput = target;
200
+ this._syncFromInput();
201
+
202
+ const payload = this._buildEventPayload();
203
+ this._fireEvent('change', payload);
204
+ this._dispatchEvent('kt.range-slider.change', payload);
205
+ }
206
+
207
+ public getRangeInput(): HTMLInputElement | null {
208
+ return this._rangeInput;
209
+ }
210
+
211
+ public getValue(): number {
212
+ return this._getCurrentValue();
213
+ }
214
+
215
+ public override dispose(): void {
216
+ if (this._element) {
217
+ if (this._onNativeInput) {
218
+ this._element.removeEventListener('input', this._onNativeInput);
219
+ }
220
+ if (this._onNativeChange) {
221
+ this._element.removeEventListener('change', this._onNativeChange);
222
+ }
223
+ }
224
+ this._onNativeInput = null;
225
+ this._onNativeChange = null;
226
+ this._rangeInput = null;
227
+ if (this._element) {
228
+ this._element.style.removeProperty(FILL_VAR);
229
+ }
230
+ super.dispose();
231
+ }
232
+
233
+ public static getInstance(element: HTMLElement): KTRangeSlider | null {
234
+ if (!element) {
235
+ return null;
236
+ }
237
+ if (KTData.has(element, 'range-slider')) {
238
+ return KTData.get(element, 'range-slider') as KTRangeSlider;
239
+ }
240
+ return null;
241
+ }
242
+
243
+ public static getOrCreateInstance(
244
+ element: HTMLElement,
245
+ config?: KTRangeSliderConfigInterface,
246
+ ): KTRangeSlider | null {
247
+ const existing = this.getInstance(element);
248
+ if (existing) {
249
+ return existing;
250
+ }
251
+ if (!this.findRangeInput(element)) {
252
+ return null;
253
+ }
254
+ new KTRangeSlider(element, config ?? undefined);
255
+ return this.getInstance(element);
256
+ }
257
+
258
+ public static createInstances(): void {
259
+ document
260
+ .querySelectorAll<HTMLElement>('[data-kt-range-slider]')
261
+ .forEach((el) => {
262
+ if (el.getAttribute('data-kt-range-slider-lazy') === 'true') {
263
+ return;
264
+ }
265
+ new KTRangeSlider(el);
266
+ });
267
+ }
268
+
269
+ public static init(): void {
270
+ KTRangeSlider.createInstances();
271
+ }
272
+ }
273
+
274
+ if (typeof window !== 'undefined') {
275
+ window.KTRangeSlider = KTRangeSlider;
276
+ }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * KTUI - Free & Open-Source Tailwind UI Components by Keenthemes
3
+ * Copyright 2025 by Keenthemes Inc
4
+ */
5
+
6
+ /**
7
+ * Optional CSS selector (document or root-relative) for an element whose text
8
+ * updates with the current range value. Prefer `data-kt-range-slider-output` on the root.
9
+ */
10
+ export interface KTRangeSliderConfigInterface {
11
+ /** Selector for the output element; also read from `data-kt-range-slider-output`. */
12
+ output?: string;
13
+
14
+ /**
15
+ * When true, skip auto-init; use `getInstance` / `new KTRangeSlider(element)` manually.
16
+ */
17
+ lazy?: boolean;
18
+ }
19
+
20
+ export interface KTRangeSliderEventPayloadInterface {
21
+ value: number;
22
+ min: number;
23
+ max: number;
24
+ /** From the input `step` attribute; omitted when `step="any"`. */
25
+ step?: number;
26
+ }
27
+
28
+ export interface KTRangeSliderInterface {
29
+ getOption(name: string): unknown;
30
+ getElement(): HTMLElement;
31
+ getRangeInput(): HTMLInputElement | null;
32
+ getValue(): number;
33
+ on(eventType: string, callback: CallableFunction): string;
34
+ off(eventType: string, eventId: string): void;
35
+ dispose(): void;
36
+ }
@@ -104,7 +104,9 @@ describe('KTRating', () => {
104
104
  const instance = new KTRating(ratingEl);
105
105
  instance.setValue(3);
106
106
  expect(instance.getValue()).toBe(3);
107
- const checked = ratingEl.querySelector<HTMLInputElement>('input[type="radio"]:checked');
107
+ const checked = ratingEl.querySelector<HTMLInputElement>(
108
+ 'input[type="radio"]:checked',
109
+ );
108
110
  expect(checked?.value).toBe('3');
109
111
  instance.dispose();
110
112
  });
@@ -121,8 +123,11 @@ describe('KTRating', () => {
121
123
  it('dispatches kt.rating.change when user selects a value', () => {
122
124
  const instance = new KTRating(ratingEl);
123
125
  const events: CustomEvent[] = [];
124
- ratingEl.addEventListener('kt.rating.change', ((e: CustomEvent) => events.push(e)) as EventListener);
125
- const radio3 = ratingEl.querySelector<HTMLInputElement>('input[type="radio"][value="3"]');
126
+ ratingEl.addEventListener('kt.rating.change', ((e: CustomEvent) =>
127
+ events.push(e)) as EventListener);
128
+ const radio3 = ratingEl.querySelector<HTMLInputElement>(
129
+ 'input[type="radio"][value="3"]',
130
+ );
126
131
  expect(radio3).not.toBeNull();
127
132
  radio3!.checked = true;
128
133
  radio3!.dispatchEvent(new Event('change', { bubbles: true }));
@@ -226,7 +231,9 @@ describe('KTRating', () => {
226
231
  ratingEl.setAttribute('data-kt-rating-value', '2');
227
232
  const instance = new KTRating(ratingEl);
228
233
  expect(instance.getValue()).toBe(2);
229
- const checked = ratingEl.querySelector<HTMLInputElement>('input[type="radio"]:checked');
234
+ const checked = ratingEl.querySelector<HTMLInputElement>(
235
+ 'input[type="radio"]:checked',
236
+ );
230
237
  expect(checked?.value).toBe('2');
231
238
  instance.dispose();
232
239
  });
@@ -79,7 +79,8 @@ export class KTRating extends KTComponent implements KTRatingInterface {
79
79
  const svg = `<svg class="shrink-0 size-5" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">${path}</svg>`;
80
80
 
81
81
  const container = document.createElement('div');
82
- container.className = 'kt-rating flex flex-row-reverse justify-end items-center gap-0';
82
+ container.className =
83
+ 'kt-rating flex flex-row-reverse justify-end items-center gap-0';
83
84
  container.setAttribute('role', readonly ? 'img' : 'group');
84
85
  container.setAttribute('aria-label', `Rating: ${value} of ${max}`);
85
86
  if (readonly) {
@@ -141,13 +142,21 @@ export class KTRating extends KTComponent implements KTRatingInterface {
141
142
  'text-muted-foreground/50 dark:text-muted-foreground/50';
142
143
  const filledTokens = filledClass.split(' ');
143
144
  const unfilledTokens = unfilledClass.split(' ');
144
- this._container.querySelectorAll<HTMLElement>('.kt-rating-label').forEach((label) => {
145
- const v = parseInt(label.getAttribute('data-kt-rating-value') || '0', 10);
146
- const filled = v <= (val ?? 0);
147
- label.classList.remove(...filledTokens, ...unfilledTokens);
148
- label.classList.add(...(filled ? filledTokens : unfilledTokens));
149
- });
150
- this._container.setAttribute('aria-valuenow', val != null ? String(val) : '0');
145
+ this._container
146
+ .querySelectorAll<HTMLElement>('.kt-rating-label')
147
+ .forEach((label) => {
148
+ const v = parseInt(
149
+ label.getAttribute('data-kt-rating-value') || '0',
150
+ 10,
151
+ );
152
+ const filled = v <= (val ?? 0);
153
+ label.classList.remove(...filledTokens, ...unfilledTokens);
154
+ label.classList.add(...(filled ? filledTokens : unfilledTokens));
155
+ });
156
+ this._container.setAttribute(
157
+ 'aria-valuenow',
158
+ val != null ? String(val) : '0',
159
+ );
151
160
  this._container.setAttribute('aria-label', `Rating: ${val ?? 0} of ${max}`);
152
161
  }
153
162
 
@@ -189,9 +198,11 @@ export class KTRating extends KTComponent implements KTRatingInterface {
189
198
  this._updateInteractiveDisplay();
190
199
  }
191
200
  } else {
192
- this._container.querySelectorAll<HTMLInputElement>('input[type="radio"]').forEach((r) => {
193
- r.checked = false;
194
- });
201
+ this._container
202
+ .querySelectorAll<HTMLInputElement>('input[type="radio"]')
203
+ .forEach((r) => {
204
+ r.checked = false;
205
+ });
195
206
  this._updateInteractiveDisplay();
196
207
  }
197
208
  }
@@ -150,7 +150,8 @@ describe('KTRepeater', () => {
150
150
  const { trigger } = createFixture();
151
151
  const instance = new KTRepeater(trigger);
152
152
  const events: CustomEvent[] = [];
153
- trigger.addEventListener('add', ((e: Event) => events.push(e as CustomEvent)) as EventListener);
153
+ trigger.addEventListener('add', ((e: Event) =>
154
+ events.push(e as CustomEvent)) as EventListener);
154
155
  instance.add();
155
156
  expect(events.length).toBe(1);
156
157
  expect(events[0].detail?.payload?.element).toBeInstanceOf(HTMLElement);
@@ -182,7 +183,10 @@ describe('KTRepeater', () => {
182
183
  });
183
184
 
184
185
  it('re-enables trigger when a clone is removed (delete) and count drops below limit', () => {
185
- const { wrapper, trigger } = createFixture({ limit: 2, withDeleteInTarget: true });
186
+ const { wrapper, trigger } = createFixture({
187
+ limit: 2,
188
+ withDeleteInTarget: true,
189
+ });
186
190
  const instance = new KTRepeater(trigger);
187
191
  instance.add();
188
192
  expect(wrapper.children.length).toBe(2);
@@ -213,7 +217,9 @@ describe('KTRepeater', () => {
213
217
  instance.add();
214
218
  expect(wrapper.children.length).toBe(2);
215
219
  const firstClone = wrapper.children[1];
216
- const deleteBtn = firstClone.querySelector('[data-kt-repeater-delete]') as HTMLElement;
220
+ const deleteBtn = firstClone.querySelector(
221
+ '[data-kt-repeater-delete]',
222
+ ) as HTMLElement;
217
223
  deleteBtn.click();
218
224
  expect(wrapper.children.length).toBe(1);
219
225
  instance.dispose();
@@ -224,7 +230,9 @@ describe('KTRepeater', () => {
224
230
  const instance = new KTRepeater(trigger);
225
231
  expect(wrapper.children.length).toBe(1);
226
232
  const onlyRow = wrapper.children[0];
227
- const deleteBtn = onlyRow.querySelector('[data-kt-repeater-delete]') as HTMLElement;
233
+ const deleteBtn = onlyRow.querySelector(
234
+ '[data-kt-repeater-delete]',
235
+ ) as HTMLElement;
228
236
  deleteBtn.click();
229
237
  expect(wrapper.children.length).toBe(1);
230
238
  instance.dispose();
@@ -233,12 +241,17 @@ describe('KTRepeater', () => {
233
241
 
234
242
  describe('predefined (hidden) template', () => {
235
243
  it('clones from hidden template and appends to wrapper', () => {
236
- const { wrapper, trigger } = createFixture({ hiddenTemplate: true, withInitialChild: false });
244
+ const { wrapper, trigger } = createFixture({
245
+ hiddenTemplate: true,
246
+ withInitialChild: false,
247
+ });
237
248
  expect(wrapper.children.length).toBe(0);
238
249
  const instance = new KTRepeater(trigger);
239
250
  instance.add();
240
251
  expect(wrapper.children.length).toBe(1);
241
- expect(wrapper.querySelector('input[placeholder="Enter Name"]')).not.toBeNull();
252
+ expect(
253
+ wrapper.querySelector('input[placeholder="Enter Name"]'),
254
+ ).not.toBeNull();
242
255
  instance.dispose();
243
256
  });
244
257
  });
@@ -23,8 +23,8 @@ export class KTRepeater extends KTComponent implements KTRepeaterInterface {
23
23
  wrapper: '',
24
24
  limit: 0,
25
25
  };
26
- protected override _config: KTRepeaterConfigInterface =
27
- this._defaultConfig as KTRepeaterConfigInterface;
26
+ protected override _config: KTRepeaterConfigInterface = this
27
+ ._defaultConfig as KTRepeaterConfigInterface;
28
28
  protected _wrapperElement: HTMLElement | null = null;
29
29
  protected _deleteHandler: (e: Event) => void;
30
30
 
@@ -155,7 +155,9 @@ export class KTRepeater extends KTComponent implements KTRepeaterInterface {
155
155
  element: HTMLElement,
156
156
  config?: KTRepeaterConfigInterface,
157
157
  ): KTRepeater {
158
- return this.getInstance(element) || new KTRepeater(element, config ?? undefined);
158
+ return (
159
+ this.getInstance(element) || new KTRepeater(element, config ?? undefined)
160
+ );
159
161
  }
160
162
 
161
163
  public static createInstances(): void {
@@ -349,12 +349,30 @@ describe('KTSelect UX Behaviors', () => {
349
349
  await waitFor(50);
350
350
 
351
351
  // ArrowDown focuses first option; ArrowUp from first wraps to last (3). Enter selects focused.
352
- searchInput.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown', bubbles: true, cancelable: true }));
352
+ searchInput.dispatchEvent(
353
+ new KeyboardEvent('keydown', {
354
+ key: 'ArrowDown',
355
+ bubbles: true,
356
+ cancelable: true,
357
+ }),
358
+ );
353
359
  await waitFor(20);
354
- searchInput.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowUp', bubbles: true, cancelable: true }));
360
+ searchInput.dispatchEvent(
361
+ new KeyboardEvent('keydown', {
362
+ key: 'ArrowUp',
363
+ bubbles: true,
364
+ cancelable: true,
365
+ }),
366
+ );
355
367
  await waitFor(20);
356
368
 
357
- searchInput.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true, cancelable: true }));
369
+ searchInput.dispatchEvent(
370
+ new KeyboardEvent('keydown', {
371
+ key: 'Enter',
372
+ bubbles: true,
373
+ cancelable: true,
374
+ }),
375
+ );
358
376
  await waitFor(150);
359
377
 
360
378
  expect(select.getSelectedOptions()).toContain('3');
@@ -372,7 +372,11 @@ export class FocusManager {
372
372
  const focusedEl = this._element.querySelector(
373
373
  `${this._optionsSelector}.${this._focusClass}`,
374
374
  ) as HTMLElement | null;
375
- if (focusedEl && !focusedEl.classList.contains('hidden') && focusedEl.style.display !== 'none') {
375
+ if (
376
+ focusedEl &&
377
+ !focusedEl.classList.contains('hidden') &&
378
+ focusedEl.style.display !== 'none'
379
+ ) {
376
380
  const idx = options.indexOf(focusedEl);
377
381
  if (idx >= 0) {
378
382
  this._focusedOptionIndex = idx;
@@ -15,8 +15,13 @@ describe('KTSticky', () => {
15
15
  beforeEach(() => {
16
16
  document.body.innerHTML = '';
17
17
  scrollTop = 0;
18
- vi.spyOn(KTDomModule.default, 'getScrollTop').mockImplementation(() => scrollTop);
19
- vi.spyOn(KTDomModule.default, 'getViewPort').mockReturnValue({ height: 800, width: 1024 });
18
+ vi.spyOn(KTDomModule.default, 'getScrollTop').mockImplementation(
19
+ () => scrollTop,
20
+ );
21
+ vi.spyOn(KTDomModule.default, 'getViewPort').mockReturnValue({
22
+ height: 800,
23
+ width: 1024,
24
+ });
20
25
 
21
26
  wrapper = document.createElement('div');
22
27
  wrapper.setAttribute('data-kt-sticky-wrapper', 'true');
@@ -151,7 +156,9 @@ describe('KTSticky', () => {
151
156
  it('fires change event when sticky becomes active and when released', async () => {
152
157
  const instance = new KTSticky(stickyEl);
153
158
  const changes: { active: boolean }[] = [];
154
- instance.on('change', (payload: { active: boolean }) => changes.push(payload));
159
+ instance.on('change', (payload: { active: boolean }) =>
160
+ changes.push(payload),
161
+ );
155
162
 
156
163
  scrollTop = 150;
157
164
  window.dispatchEvent(new Event('scroll', { bubbles: true }));
@@ -119,42 +119,35 @@ export class KTSticky extends KTComponent implements KTStickyInterface {
119
119
 
120
120
  // Store scroll handler reference for cleanup
121
121
  this._scrollHandler = () => {
122
-
123
122
  this._isScrolling = true;
124
123
 
125
- if(this._isActive() === true){
126
-
124
+ if (this._isActive() === true) {
127
125
  this._debounceScroll(() => {
128
-
129
126
  this._isScrolling = false;
130
127
  this._process();
131
-
132
128
  }, 200);
133
-
134
- }else{
135
-
129
+ } else {
136
130
  this._isScrolling = false;
137
131
  this._process();
138
-
139
132
  }
140
-
141
133
  };
142
134
 
143
135
  if (this._targetElement) {
144
136
  if (this._targetElement === document) {
145
- window.addEventListener('scroll', this._scrollHandler, { passive: true });
137
+ window.addEventListener('scroll', this._scrollHandler, {
138
+ passive: true,
139
+ });
146
140
  } else {
147
141
  (this._targetElement as HTMLElement).addEventListener(
148
142
  'scroll',
149
143
  this._scrollHandler,
150
- { passive: true }
144
+ { passive: true },
151
145
  );
152
146
  }
153
147
  }
154
148
  }
155
149
 
156
150
  protected _debounceScroll(callback: () => void, delay: number = 200): void {
157
-
158
151
  if (this._scrollTimeout) {
159
152
  clearTimeout(this._scrollTimeout);
160
153
  }
@@ -162,7 +155,6 @@ export class KTSticky extends KTComponent implements KTStickyInterface {
162
155
  this._scrollTimeout = setTimeout(() => {
163
156
  callback();
164
157
  }, delay);
165
-
166
158
  }
167
159
 
168
160
  protected _process(): void {
@@ -352,7 +344,7 @@ export class KTSticky extends KTComponent implements KTStickyInterface {
352
344
  const activeClassList = this._getOption('activeClass') as string;
353
345
  if (activeClassList) {
354
346
  KTDom.addClass(this._element, activeClassList);
355
- }else{
347
+ } else {
356
348
  const classList = this._getOption('class') as string;
357
349
  if (classList) {
358
350
  KTDom.addClass(this._element, classList);
@@ -387,10 +379,10 @@ export class KTSticky extends KTComponent implements KTStickyInterface {
387
379
  const activeClassList = this._getOption('activeClass') as string;
388
380
  if (activeClassList) {
389
381
  KTDom.removeClass(this._element, activeClassList);
390
- }else{
382
+ } else {
391
383
  const classList = this._getOption('class') as string;
392
384
  if (classList) {
393
- KTDom.removeClass(this._element, classList);
385
+ KTDom.removeClass(this._element, classList);
394
386
  }
395
387
  }
396
388
 
@@ -399,12 +391,11 @@ export class KTSticky extends KTComponent implements KTStickyInterface {
399
391
  KTDom.addClass(this._element, releaseClassList);
400
392
  }
401
393
 
402
- if(this._eventTriggerState === false){
394
+ if (this._eventTriggerState === false) {
403
395
  const releaseDelay = this._getOption('releaseDelay') as number;
404
- if(releaseDelay && this._timeoutState === null){
396
+ if (releaseDelay && this._timeoutState === null) {
405
397
  this._timeoutState = setTimeout(() => {
406
-
407
- if (!this._element){
398
+ if (!this._element) {
408
399
  return;
409
400
  }
410
401
 
@@ -413,12 +404,11 @@ export class KTSticky extends KTComponent implements KTStickyInterface {
413
404
  }
414
405
 
415
406
  this._timeoutState = null;
416
-
417
407
  }, releaseDelay);
418
- }else{
408
+ } else {
419
409
  this._resetStyles();
420
410
  }
421
- }else{
411
+ } else {
422
412
  this._timeoutState = null;
423
413
  }
424
414
  }
@@ -19,9 +19,9 @@ export interface KTStickyConfigInterface {
19
19
  reverse: boolean;
20
20
  release: string;
21
21
  activate: string;
22
- releaseDelay: number,
23
- activeClass: string,
24
- releaseClass: string,
22
+ releaseDelay: number;
23
+ activeClass: string;
24
+ releaseClass: string;
25
25
  }
26
26
 
27
27
  export interface KTStickyInterface {