@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,659 @@
1
+ /**
2
+ * Tests for KTRangeSlider component
3
+ */
4
+
5
+ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
6
+ import { KTRangeSlider } from '../range-slider';
7
+
8
+ describe('KTRangeSlider', () => {
9
+ let container: HTMLElement;
10
+
11
+ beforeEach(() => {
12
+ document.body.innerHTML = '';
13
+ container = document.createElement('div');
14
+ container.id = 'test-container';
15
+ document.body.appendChild(container);
16
+ });
17
+
18
+ afterEach(() => {
19
+ document.body.innerHTML = '';
20
+ });
21
+
22
+ describe('initialization', () => {
23
+ it('initializes on wrapper with nested range input', () => {
24
+ const wrap = document.createElement('div');
25
+ wrap.setAttribute('data-kt-range-slider', 'true');
26
+ const input = document.createElement('input');
27
+ input.type = 'range';
28
+ input.min = '0';
29
+ input.max = '100';
30
+ input.value = '40';
31
+ wrap.appendChild(input);
32
+ container.appendChild(wrap);
33
+
34
+ const instance = new KTRangeSlider(wrap);
35
+ expect(instance.getElement()).toBe(wrap);
36
+ expect(instance.getRangeInput()).toBe(input);
37
+ expect(instance.getValue()).toBe(40);
38
+ instance.dispose();
39
+ });
40
+
41
+ it('initializes on the range input root', () => {
42
+ const input = document.createElement('input');
43
+ input.type = 'range';
44
+ input.setAttribute('data-kt-range-slider', 'true');
45
+ input.min = '0';
46
+ input.max = '50';
47
+ input.value = '25';
48
+ container.appendChild(input);
49
+
50
+ const instance = new KTRangeSlider(input);
51
+ expect(instance.getElement()).toBe(input);
52
+ expect(instance.getRangeInput()).toBe(input);
53
+ instance.dispose();
54
+ });
55
+
56
+ it('binds to the first range input when multiple exist', () => {
57
+ const wrap = document.createElement('div');
58
+ wrap.setAttribute('data-kt-range-slider', 'true');
59
+ const a = document.createElement('input');
60
+ a.type = 'range';
61
+ a.value = '10';
62
+ const b = document.createElement('input');
63
+ b.type = 'range';
64
+ b.value = '20';
65
+ wrap.appendChild(a);
66
+ wrap.appendChild(b);
67
+ container.appendChild(wrap);
68
+
69
+ const instance = new KTRangeSlider(wrap);
70
+ expect(instance.getRangeInput()).toBe(a);
71
+ instance.dispose();
72
+ });
73
+
74
+ it('does not initialize without a range input', () => {
75
+ const wrap = document.createElement('div');
76
+ wrap.setAttribute('data-kt-range-slider', 'true');
77
+ container.appendChild(wrap);
78
+ const instance = new KTRangeSlider(wrap);
79
+ expect(instance.getElement()).toBeNull();
80
+ expect(instance.getRangeInput()).toBeNull();
81
+ });
82
+ });
83
+
84
+ describe('output binding', () => {
85
+ it('updates output element text on input', () => {
86
+ const wrap = document.createElement('div');
87
+ wrap.setAttribute('data-kt-range-slider', 'true');
88
+ wrap.setAttribute('data-kt-range-slider-output', '.rs-out');
89
+ const out = document.createElement('span');
90
+ out.className = 'rs-out';
91
+ wrap.appendChild(out);
92
+ const input = document.createElement('input');
93
+ input.type = 'range';
94
+ input.min = '0';
95
+ input.max = '100';
96
+ input.value = '30';
97
+ wrap.appendChild(input);
98
+ container.appendChild(wrap);
99
+
100
+ const instance = new KTRangeSlider(wrap);
101
+ expect(out.textContent).toBe('30');
102
+
103
+ input.value = '72';
104
+ input.dispatchEvent(new Event('input', { bubbles: true }));
105
+ expect(out.textContent).toBe('72');
106
+ instance.dispose();
107
+ });
108
+
109
+ it('still updates output if the native range input is replaced', () => {
110
+ const wrap = document.createElement('div');
111
+ wrap.setAttribute('data-kt-range-slider', 'true');
112
+ wrap.setAttribute('data-kt-range-slider-output', '.rs-out');
113
+
114
+ const out = document.createElement('span');
115
+ out.className = 'rs-out';
116
+ wrap.appendChild(out);
117
+
118
+ const input1 = document.createElement('input');
119
+ input1.type = 'range';
120
+ input1.min = '0';
121
+ input1.max = '100';
122
+ input1.value = '30';
123
+ wrap.appendChild(input1);
124
+ container.appendChild(wrap);
125
+
126
+ const instance = new KTRangeSlider(wrap);
127
+ expect(out.textContent).toBe('30');
128
+
129
+ // Simulate a preview re-render replacing the native input.
130
+ wrap.removeChild(input1);
131
+ const input2 = document.createElement('input');
132
+ input2.type = 'range';
133
+ input2.min = '0';
134
+ input2.max = '100';
135
+ input2.value = '45';
136
+ wrap.appendChild(input2);
137
+
138
+ input2.value = '55';
139
+ input2.dispatchEvent(new Event('input', { bubbles: true }));
140
+ expect(out.textContent).toBe('55');
141
+
142
+ instance.dispose();
143
+ });
144
+
145
+ it('trims and binds output selector within the root', () => {
146
+ const wrap = document.createElement('div');
147
+ wrap.setAttribute('data-kt-range-slider', 'true');
148
+ wrap.setAttribute('data-kt-range-slider-output', ' .rs-out ');
149
+
150
+ const out = document.createElement('span');
151
+ out.className = 'rs-out';
152
+ wrap.appendChild(out);
153
+
154
+ const input = document.createElement('input');
155
+ input.type = 'range';
156
+ input.min = '0';
157
+ input.max = '100';
158
+ input.value = '20';
159
+ wrap.appendChild(input);
160
+ container.appendChild(wrap);
161
+
162
+ const instance = new KTRangeSlider(wrap);
163
+ expect(out.textContent).toBe('20');
164
+
165
+ input.value = '33';
166
+ input.dispatchEvent(new Event('input', { bubbles: true }));
167
+ expect(out.textContent).toBe('33');
168
+
169
+ instance.dispose();
170
+ });
171
+
172
+ it('falls back to document query for the output selector', () => {
173
+ const wrap = document.createElement('div');
174
+ wrap.setAttribute('data-kt-range-slider', 'true');
175
+ wrap.setAttribute('data-kt-range-slider-output', '.rs-doc-out');
176
+
177
+ // Not inside the wrapper; should be resolved via document.querySelector.
178
+ const out = document.createElement('span');
179
+ out.className = 'rs-doc-out';
180
+ document.body.appendChild(out);
181
+
182
+ const input = document.createElement('input');
183
+ input.type = 'range';
184
+ input.min = '0';
185
+ input.max = '100';
186
+ input.value = '30';
187
+ wrap.appendChild(input);
188
+ container.appendChild(wrap);
189
+
190
+ const instance = new KTRangeSlider(wrap);
191
+ expect(out.textContent).toBe('30');
192
+
193
+ input.value = '90';
194
+ input.dispatchEvent(new Event('input', { bubbles: true }));
195
+ expect(out.textContent).toBe('90');
196
+
197
+ instance.dispose();
198
+ });
199
+
200
+ it('still updates track fill even when output selector does not resolve', () => {
201
+ const wrap = document.createElement('div');
202
+ wrap.setAttribute('data-kt-range-slider', 'true');
203
+ wrap.setAttribute('data-kt-range-slider-output', '.does-not-exist');
204
+
205
+ const input = document.createElement('input');
206
+ input.type = 'range';
207
+ input.min = '0';
208
+ input.max = '100';
209
+ input.value = '40';
210
+ wrap.appendChild(input);
211
+ container.appendChild(wrap);
212
+
213
+ const instance = new KTRangeSlider(wrap);
214
+ expect(wrap.style.getPropertyValue('--kt-range-fill').trim()).toBe('0.4');
215
+
216
+ input.value = '100';
217
+ input.dispatchEvent(new Event('input', { bubbles: true }));
218
+ expect(wrap.style.getPropertyValue('--kt-range-fill').trim()).toBe('1');
219
+
220
+ instance.dispose();
221
+ });
222
+
223
+ it('ignores bubbled input events coming from non-range inputs', () => {
224
+ const wrap = document.createElement('div');
225
+ wrap.setAttribute('data-kt-range-slider', 'true');
226
+ wrap.setAttribute('data-kt-range-slider-output', '.rs-out');
227
+
228
+ const out = document.createElement('span');
229
+ out.className = 'rs-out';
230
+ wrap.appendChild(out);
231
+
232
+ const range = document.createElement('input');
233
+ range.type = 'range';
234
+ range.min = '0';
235
+ range.max = '100';
236
+ range.value = '25';
237
+ wrap.appendChild(range);
238
+
239
+ const text = document.createElement('input');
240
+ text.type = 'text';
241
+ text.value = 'hello';
242
+ wrap.appendChild(text);
243
+
244
+ container.appendChild(wrap);
245
+
246
+ const instance = new KTRangeSlider(wrap);
247
+ expect(out.textContent).toBe('25');
248
+
249
+ // Event bubbles to wrapper, but should be ignored by handler because target isn't range.
250
+ text.dispatchEvent(new Event('input', { bubbles: true }));
251
+ expect(out.textContent).toBe('25');
252
+
253
+ instance.dispose();
254
+ });
255
+ });
256
+
257
+ describe('events', () => {
258
+ it('includes native step in kt.range-slider.input detail when step is numeric', () => {
259
+ const wrap = document.createElement('div');
260
+ wrap.setAttribute('data-kt-range-slider', 'true');
261
+ const input = document.createElement('input');
262
+ input.type = 'range';
263
+ input.min = '0';
264
+ input.max = '100';
265
+ input.step = '5';
266
+ input.value = '40';
267
+ wrap.appendChild(input);
268
+ container.appendChild(wrap);
269
+
270
+ const instance = new KTRangeSlider(wrap);
271
+ const spy = vi.fn();
272
+ wrap.addEventListener('kt.range-slider.input', spy);
273
+
274
+ input.dispatchEvent(new Event('input', { bubbles: true }));
275
+ expect(spy).toHaveBeenCalledTimes(1);
276
+ expect(spy.mock.calls[0][0].detail.payload).toMatchObject({
277
+ value: 40,
278
+ min: 0,
279
+ max: 100,
280
+ step: 5,
281
+ });
282
+
283
+ instance.dispose();
284
+ });
285
+
286
+ it('omits step in kt.range-slider.input payload when step="any"', () => {
287
+ const wrap = document.createElement('div');
288
+ wrap.setAttribute('data-kt-range-slider', 'true');
289
+
290
+ const input = document.createElement('input');
291
+ input.type = 'range';
292
+ input.min = '0';
293
+ input.max = '100';
294
+ input.step = 'any';
295
+ input.value = '40';
296
+ wrap.appendChild(input);
297
+ container.appendChild(wrap);
298
+
299
+ new KTRangeSlider(wrap);
300
+ const spy = vi.fn();
301
+ wrap.addEventListener('kt.range-slider.input', spy);
302
+
303
+ input.dispatchEvent(new Event('input', { bubbles: true }));
304
+ const payload = spy.mock.calls[0][0].detail.payload as Record<
305
+ string,
306
+ unknown
307
+ >;
308
+
309
+ expect(payload).toMatchObject({ value: 40, min: 0, max: 100 });
310
+ expect(payload).not.toHaveProperty('step');
311
+ });
312
+
313
+ it('defaults step to 1 when step attribute is empty', () => {
314
+ const wrap = document.createElement('div');
315
+ wrap.setAttribute('data-kt-range-slider', 'true');
316
+
317
+ const input = document.createElement('input');
318
+ input.type = 'range';
319
+ input.min = '0';
320
+ input.max = '100';
321
+ input.setAttribute('step', '');
322
+ input.value = '40';
323
+ wrap.appendChild(input);
324
+ container.appendChild(wrap);
325
+
326
+ new KTRangeSlider(wrap);
327
+ const spy = vi.fn();
328
+ wrap.addEventListener('kt.range-slider.input', spy);
329
+
330
+ input.dispatchEvent(new Event('input', { bubbles: true }));
331
+ expect(spy.mock.calls[0][0].detail.payload).toMatchObject({
332
+ value: 40,
333
+ min: 0,
334
+ max: 100,
335
+ step: 1,
336
+ });
337
+ });
338
+
339
+ it('defaults step to 1 when step attribute is invalid', () => {
340
+ const wrap = document.createElement('div');
341
+ wrap.setAttribute('data-kt-range-slider', 'true');
342
+
343
+ const input = document.createElement('input');
344
+ input.type = 'range';
345
+ input.min = '0';
346
+ input.max = '100';
347
+ input.step = '-2';
348
+ input.value = '40';
349
+ wrap.appendChild(input);
350
+ container.appendChild(wrap);
351
+
352
+ new KTRangeSlider(wrap);
353
+ const spy = vi.fn();
354
+ wrap.addEventListener('kt.range-slider.input', spy);
355
+
356
+ input.dispatchEvent(new Event('input', { bubbles: true }));
357
+ expect(spy.mock.calls[0][0].detail.payload).toMatchObject({
358
+ step: 1,
359
+ });
360
+ });
361
+
362
+ it('dispatches kt.range-slider.change on native change', () => {
363
+ const wrap = document.createElement('div');
364
+ wrap.setAttribute('data-kt-range-slider', 'true');
365
+
366
+ const input = document.createElement('input');
367
+ input.type = 'range';
368
+ input.min = '0';
369
+ input.max = '100';
370
+ input.value = '10';
371
+ wrap.appendChild(input);
372
+ container.appendChild(wrap);
373
+
374
+ new KTRangeSlider(wrap);
375
+ const spy = vi.fn();
376
+ wrap.addEventListener('kt.range-slider.change', spy);
377
+
378
+ input.value = '55';
379
+ input.dispatchEvent(new Event('change', { bubbles: true }));
380
+ expect(spy).toHaveBeenCalledTimes(1);
381
+ expect(spy.mock.calls[0][0].detail.payload).toMatchObject({
382
+ value: 55,
383
+ });
384
+ });
385
+ });
386
+
387
+ describe('fill CSS variable', () => {
388
+ it('sets --kt-range-fill on the root from value', () => {
389
+ const wrap = document.createElement('div');
390
+ wrap.setAttribute('data-kt-range-slider', 'true');
391
+ const input = document.createElement('input');
392
+ input.type = 'range';
393
+ input.min = '0';
394
+ input.max = '100';
395
+ input.value = '25';
396
+ wrap.appendChild(input);
397
+ container.appendChild(wrap);
398
+
399
+ const instance = new KTRangeSlider(wrap);
400
+ expect(wrap.style.getPropertyValue('--kt-range-fill').trim()).toBe(
401
+ '0.25',
402
+ );
403
+
404
+ input.value = '100';
405
+ input.dispatchEvent(new Event('input', { bubbles: true }));
406
+ expect(wrap.style.getPropertyValue('--kt-range-fill').trim()).toBe('1');
407
+ instance.dispose();
408
+ });
409
+
410
+ it('defaults numeric min/max when attributes are invalid', () => {
411
+ const wrap = document.createElement('div');
412
+ wrap.setAttribute('data-kt-range-slider', 'true');
413
+
414
+ const input = document.createElement('input');
415
+ input.type = 'range';
416
+ input.min = 'not-a-number';
417
+ input.max = 'also-not-a-number';
418
+ input.value = '30';
419
+ wrap.appendChild(input);
420
+ container.appendChild(wrap);
421
+
422
+ new KTRangeSlider(wrap);
423
+ // ratio = (30 - 0) / (100 - 0) = 0.3
424
+ expect(wrap.style.getPropertyValue('--kt-range-fill').trim()).toBe('0.3');
425
+ });
426
+
427
+ it('sets fill ratio to 0 when max equals min', () => {
428
+ const wrap = document.createElement('div');
429
+ wrap.setAttribute('data-kt-range-slider', 'true');
430
+
431
+ const input = document.createElement('input');
432
+ input.type = 'range';
433
+ input.min = '50';
434
+ input.max = '50';
435
+ input.value = '50';
436
+ wrap.appendChild(input);
437
+ container.appendChild(wrap);
438
+
439
+ new KTRangeSlider(wrap);
440
+ expect(wrap.style.getPropertyValue('--kt-range-fill').trim()).toBe('0');
441
+ });
442
+
443
+ it('handles max<min path via _clamp branch', () => {
444
+ const wrap = document.createElement('div');
445
+ wrap.setAttribute('data-kt-range-slider', 'true');
446
+
447
+ const input = document.createElement('input');
448
+ input.type = 'range';
449
+ input.min = '10';
450
+ input.max = '0';
451
+ input.value = '5';
452
+ wrap.appendChild(input);
453
+ container.appendChild(wrap);
454
+
455
+ const instance = new KTRangeSlider(wrap);
456
+
457
+ const min = parseFloat(input.min);
458
+ const max = parseFloat(input.max);
459
+ const value = instance.getValue();
460
+
461
+ const clamped = max < min ? value : Math.min(max, Math.max(min, value));
462
+ const ratio = max === min ? 0 : (clamped - min) / (max - min);
463
+
464
+ expect(wrap.style.getPropertyValue('--kt-range-fill').trim()).toBe(
465
+ String(ratio),
466
+ );
467
+ });
468
+ });
469
+
470
+ describe('dispose', () => {
471
+ it('removes listeners and clears data', () => {
472
+ const wrap = document.createElement('div');
473
+ wrap.setAttribute('data-kt-range-slider', 'true');
474
+ const input = document.createElement('input');
475
+ input.type = 'range';
476
+ input.min = '0';
477
+ input.max = '100';
478
+ input.value = '50';
479
+ wrap.appendChild(input);
480
+ container.appendChild(wrap);
481
+
482
+ const instance = new KTRangeSlider(wrap);
483
+ const spy = vi.fn();
484
+ wrap.addEventListener('kt.range-slider.input', spy);
485
+
486
+ instance.dispose();
487
+ expect(KTRangeSlider.getInstance(wrap)).toBeNull();
488
+
489
+ input.value = '80';
490
+ input.dispatchEvent(new Event('input', { bubbles: true }));
491
+ expect(spy).not.toHaveBeenCalled();
492
+ });
493
+ });
494
+
495
+ describe('createInstances', () => {
496
+ it('respects data-kt-range-slider-lazy', () => {
497
+ const wrap = document.createElement('div');
498
+ wrap.setAttribute('data-kt-range-slider', 'true');
499
+ wrap.setAttribute('data-kt-range-slider-lazy', 'true');
500
+
501
+ const input = document.createElement('input');
502
+ input.type = 'range';
503
+ input.min = '0';
504
+ input.max = '100';
505
+ input.value = '20';
506
+ wrap.appendChild(input);
507
+ container.appendChild(wrap);
508
+
509
+ KTRangeSlider.createInstances();
510
+ expect(KTRangeSlider.getInstance(wrap)).toBeNull();
511
+ });
512
+
513
+ it('is idempotent for the same root', () => {
514
+ const wrap = document.createElement('div');
515
+ wrap.setAttribute('data-kt-range-slider', 'true');
516
+ const input = document.createElement('input');
517
+ input.type = 'range';
518
+ wrap.appendChild(input);
519
+ container.appendChild(wrap);
520
+
521
+ KTRangeSlider.createInstances();
522
+ const a = KTRangeSlider.getInstance(wrap);
523
+ KTRangeSlider.createInstances();
524
+ const b = KTRangeSlider.getInstance(wrap);
525
+ expect(a).toBe(b);
526
+ a?.dispose();
527
+ });
528
+
529
+ it('constructor does not re-init an already-connected instance', () => {
530
+ const wrap = document.createElement('div');
531
+ wrap.setAttribute('data-kt-range-slider', 'true');
532
+
533
+ const input = document.createElement('input');
534
+ input.type = 'range';
535
+ input.min = '0';
536
+ input.max = '100';
537
+ input.value = '20';
538
+ wrap.appendChild(input);
539
+ container.appendChild(wrap);
540
+
541
+ const instance1 = new KTRangeSlider(wrap);
542
+ const a = KTRangeSlider.getInstance(wrap);
543
+ // Second constructor call should not create a second instance.
544
+ new KTRangeSlider(wrap);
545
+ const b = KTRangeSlider.getInstance(wrap);
546
+
547
+ expect(a).toBe(b);
548
+ expect(a).not.toBeNull();
549
+
550
+ instance1?.dispose?.();
551
+ });
552
+ });
553
+
554
+ describe('static helpers', () => {
555
+ it('getInstance returns null for empty element input', () => {
556
+ expect(
557
+ KTRangeSlider.getInstance(null as unknown as HTMLElement),
558
+ ).toBeNull();
559
+ });
560
+
561
+ it('getOrCreateInstance returns null when no range input exists', () => {
562
+ const wrap = document.createElement('div');
563
+ wrap.setAttribute('data-kt-range-slider', 'true');
564
+ container.appendChild(wrap);
565
+
566
+ expect(KTRangeSlider.getOrCreateInstance(wrap)).toBeNull();
567
+ });
568
+
569
+ it('getOrCreateInstance creates when range input exists and no instance yet', () => {
570
+ const wrap = document.createElement('div');
571
+ wrap.setAttribute('data-kt-range-slider', 'true');
572
+ container.appendChild(wrap);
573
+
574
+ const input = document.createElement('input');
575
+ input.type = 'range';
576
+ input.min = '0';
577
+ input.max = '100';
578
+ input.value = '20';
579
+ wrap.appendChild(input);
580
+
581
+ const instance = KTRangeSlider.getOrCreateInstance(wrap);
582
+ expect(instance).not.toBeNull();
583
+ expect(KTRangeSlider.getInstance(wrap)).toBe(instance);
584
+
585
+ instance?.dispose();
586
+ });
587
+
588
+ it('getOrCreateInstance returns existing instance when already initialized', () => {
589
+ const wrap = document.createElement('div');
590
+ wrap.setAttribute('data-kt-range-slider', 'true');
591
+ container.appendChild(wrap);
592
+
593
+ const input = document.createElement('input');
594
+ input.type = 'range';
595
+ wrap.appendChild(input);
596
+
597
+ const first = new KTRangeSlider(wrap);
598
+ const second = KTRangeSlider.getOrCreateInstance(wrap);
599
+ expect(second).toBe(first);
600
+ first.dispose();
601
+ });
602
+ });
603
+
604
+ describe('config + init', () => {
605
+ it('supports output selector from config object', () => {
606
+ const wrap = document.createElement('div');
607
+ const out = document.createElement('span');
608
+ out.className = 'cfg-out';
609
+ wrap.appendChild(out);
610
+
611
+ const input = document.createElement('input');
612
+ input.type = 'range';
613
+ input.min = '0';
614
+ input.max = '100';
615
+ input.value = '25';
616
+ wrap.appendChild(input);
617
+ container.appendChild(wrap);
618
+
619
+ const instance = new KTRangeSlider(wrap, { output: '.cfg-out' });
620
+ expect(out.textContent).toBe('25');
621
+
622
+ input.value = '60';
623
+ input.dispatchEvent(new Event('input', { bubbles: true }));
624
+ expect(out.textContent).toBe('60');
625
+ instance.dispose();
626
+ });
627
+
628
+ it('init() initializes eligible slider roots', () => {
629
+ const wrap = document.createElement('div');
630
+ wrap.setAttribute('data-kt-range-slider', 'true');
631
+ const input = document.createElement('input');
632
+ input.type = 'range';
633
+ wrap.appendChild(input);
634
+ container.appendChild(wrap);
635
+
636
+ KTRangeSlider.init();
637
+ expect(KTRangeSlider.getInstance(wrap)).not.toBeNull();
638
+ });
639
+
640
+ it('reinitializes after element is disconnected then reattached', () => {
641
+ const wrap = document.createElement('div');
642
+ wrap.setAttribute('data-kt-range-slider', 'true');
643
+ const input = document.createElement('input');
644
+ input.type = 'range';
645
+ wrap.appendChild(input);
646
+ container.appendChild(wrap);
647
+
648
+ const first = new KTRangeSlider(wrap);
649
+ container.removeChild(wrap);
650
+ // disconnected element with previous instance should not be skipped
651
+ const second = new KTRangeSlider(wrap);
652
+ container.appendChild(wrap);
653
+
654
+ expect(second.getElement()).toBe(wrap);
655
+ expect(second).not.toBe(first);
656
+ second.dispose();
657
+ });
658
+ });
659
+ });
@@ -0,0 +1,11 @@
1
+ /**
2
+ * KTUI - Free & Open-Source Tailwind UI Components by Keenthemes
3
+ * Copyright 2025 by Keenthemes Inc
4
+ */
5
+
6
+ export { KTRangeSlider } from './range-slider';
7
+ export type {
8
+ KTRangeSliderConfigInterface,
9
+ KTRangeSliderEventPayloadInterface,
10
+ KTRangeSliderInterface,
11
+ } from './types';