@zywave/zui-slider 4.4.0-pre.4 → 4.4.0-pre.5
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/custom-elements.json +5 -40
- package/dist/zui-slider-css.js +1 -1
- package/dist/zui-slider-css.js.map +1 -1
- package/dist/zui-slider.js +28 -56
- package/dist/zui-slider.js.map +1 -1
- package/docs/demo.html +1 -1
- package/lab.html +5 -5
- package/package.json +2 -2
- package/src/zui-slider-css.js +1 -1
- package/src/zui-slider.scss +2 -0
- package/src/zui-slider.ts +22 -53
- package/test/zui-slider.test.ts +175 -237
package/test/zui-slider.test.ts
CHANGED
|
@@ -97,17 +97,14 @@ suite('zui-slider', () => {
|
|
|
97
97
|
assert.equal(element.value, '33.5');
|
|
98
98
|
});
|
|
99
99
|
|
|
100
|
-
test('float input position
|
|
100
|
+
test('float input position updates after drag', async () => {
|
|
101
101
|
await element.updateComplete;
|
|
102
102
|
const input = element.shadowRoot!.querySelector<HTMLInputElement>('input[type="range"]')!;
|
|
103
103
|
const thumbDiv = element.shadowRoot!.querySelector<HTMLElement>('.thumb-input')!;
|
|
104
|
-
const bgBefore = input.style.getPropertyValue('--zui-slider-track-bg');
|
|
105
104
|
input.value = '75';
|
|
106
105
|
input.dispatchEvent(new Event('input'));
|
|
107
106
|
await element.updateComplete;
|
|
108
107
|
assert.include(thumbDiv.style.left, '75%');
|
|
109
|
-
assert.notEqual(input.style.getPropertyValue('--zui-slider-track-bg'), bgBefore);
|
|
110
|
-
assert.include(input.style.getPropertyValue('--zui-slider-track-bg'), '75%');
|
|
111
108
|
});
|
|
112
109
|
|
|
113
110
|
test('floating input change fires component change event', async () => {
|
|
@@ -196,26 +193,38 @@ suite('zui-slider', () => {
|
|
|
196
193
|
assert.equal(new FormData(form).get(name), '50');
|
|
197
194
|
});
|
|
198
195
|
|
|
199
|
-
test('
|
|
196
|
+
test('element.value, native range input, and floating input stay in sync after drag', async () => {
|
|
200
197
|
await element.updateComplete;
|
|
201
|
-
const
|
|
202
|
-
const
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
198
|
+
const rangeInput = element.shadowRoot!.querySelector<HTMLInputElement>('input[type="range"]')!;
|
|
199
|
+
const floatInput = element.shadowRoot!.querySelector<HTMLInputElement>('.thumb-input input')!;
|
|
200
|
+
|
|
201
|
+
rangeInput.value = '75';
|
|
202
|
+
rangeInput.dispatchEvent(new Event('input'));
|
|
203
|
+
await element.updateComplete;
|
|
204
|
+
|
|
205
|
+
assert.equal(element.value, '75');
|
|
206
|
+
assert.equal(rangeInput.value, '75');
|
|
207
|
+
assert.equal(floatInput.value, '75');
|
|
206
208
|
});
|
|
207
209
|
|
|
208
|
-
test('
|
|
209
|
-
|
|
210
|
+
test('programmatic value reset after drag keeps native range input in sync', async () => {
|
|
211
|
+
// Reproduces a live() omission: user drags while a programmatic set is pending,
|
|
212
|
+
// causing Lit to skip the DOM write because its stored virtual still matches.
|
|
213
|
+
element.value = '50';
|
|
210
214
|
await element.updateComplete;
|
|
211
215
|
const input = element.shadowRoot!.querySelector<HTMLInputElement>('input[type="range"]')!;
|
|
212
|
-
assert.include(input.style.getPropertyValue('--zui-slider-track-bg'), 'var(--zui-gray)');
|
|
213
|
-
assert.notInclude(input.style.getPropertyValue('--zui-slider-track-bg'), 'var(--zui-blue)');
|
|
214
216
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
217
|
+
// Simulate drag: browser writes directly to DOM, then fires input event
|
|
218
|
+
input.value = '75';
|
|
219
|
+
input.dispatchEvent(new Event('input'));
|
|
220
|
+
|
|
221
|
+
// Before that render fires, programmatically revert — this is the live() failure case
|
|
222
|
+
element.value = '50'; // Lit virtual is still '50' from the last render
|
|
223
|
+
|
|
224
|
+
await element.updateComplete; // without live(), Lit sees virtual='50'==new='50' → skips DOM write
|
|
225
|
+
|
|
226
|
+
assert.equal(input.value, '50');
|
|
227
|
+
assert.equal(element.value, '50');
|
|
219
228
|
});
|
|
220
229
|
|
|
221
230
|
test('form reset hides visible floating input', async () => {
|
|
@@ -303,56 +312,6 @@ suite('zui-slider', () => {
|
|
|
303
312
|
document.body.removeChild(f);
|
|
304
313
|
});
|
|
305
314
|
|
|
306
|
-
test('floating input updates value after debounce delay but not immediately', async () => {
|
|
307
|
-
await element.updateComplete;
|
|
308
|
-
element.value = '50';
|
|
309
|
-
const floatInput = element.shadowRoot!.querySelector<HTMLInputElement>('.thumb-input input[type="number"]')!;
|
|
310
|
-
floatInput.value = '75';
|
|
311
|
-
floatInput.dispatchEvent(new Event('input'));
|
|
312
|
-
// Debounce is 500ms — value must not update synchronously
|
|
313
|
-
assert.equal(element.value, '50');
|
|
314
|
-
await new Promise<void>((r) => setTimeout(r, 600));
|
|
315
|
-
assert.equal(element.value, '75');
|
|
316
|
-
});
|
|
317
|
-
|
|
318
|
-
test('form reset cancels pending floating input debounce', async () => {
|
|
319
|
-
await element.updateComplete;
|
|
320
|
-
const floatInput = element.shadowRoot!.querySelector<HTMLInputElement>('.thumb-input input[type="number"]')!;
|
|
321
|
-
floatInput.value = '80';
|
|
322
|
-
floatInput.dispatchEvent(new Event('input'));
|
|
323
|
-
// Debounce is pending — reset before it fires
|
|
324
|
-
form.reset();
|
|
325
|
-
await new Promise<void>((r) => setTimeout(r, 600));
|
|
326
|
-
// Debounce timer must have been cancelled by formResetCallback
|
|
327
|
-
assert.equal(element.value, '50');
|
|
328
|
-
});
|
|
329
|
-
|
|
330
|
-
test('floating input empty-string input does not trigger value update', async () => {
|
|
331
|
-
await element.updateComplete;
|
|
332
|
-
element.value = '50';
|
|
333
|
-
const floatInput = element.shadowRoot!.querySelector<HTMLInputElement>('.thumb-input input[type="number"]')!;
|
|
334
|
-
floatInput.value = '';
|
|
335
|
-
floatInput.dispatchEvent(new Event('input'));
|
|
336
|
-
// Empty string is an in-progress edit — value must not be updated
|
|
337
|
-
assert.equal(element.value, '50');
|
|
338
|
-
});
|
|
339
|
-
|
|
340
|
-
test('clearing floating input cancels pending debounce so stale value is not committed', async () => {
|
|
341
|
-
await element.updateComplete;
|
|
342
|
-
element.value = '50';
|
|
343
|
-
const floatInput = element.shadowRoot!.querySelector<HTMLInputElement>('.thumb-input input[type="number"]')!;
|
|
344
|
-
// Start a debounce for '75'
|
|
345
|
-
floatInput.value = '75';
|
|
346
|
-
floatInput.dispatchEvent(new Event('input'));
|
|
347
|
-
assert.equal(element.value, '50'); // debounce not yet fired
|
|
348
|
-
// Then clear the field — should cancel the '75' debounce
|
|
349
|
-
floatInput.value = '';
|
|
350
|
-
floatInput.dispatchEvent(new Event('input'));
|
|
351
|
-
await new Promise<void>((r) => setTimeout(r, 350));
|
|
352
|
-
// The pending debounce was cancelled; value stays at '50'
|
|
353
|
-
assert.equal(element.value, '50');
|
|
354
|
-
});
|
|
355
|
-
|
|
356
315
|
test('committing empty floating input reverts display to current value', async () => {
|
|
357
316
|
await element.updateComplete;
|
|
358
317
|
element.value = '75';
|
|
@@ -363,48 +322,56 @@ suite('zui-slider', () => {
|
|
|
363
322
|
assert.equal(floatInput.value, '75');
|
|
364
323
|
});
|
|
365
324
|
|
|
366
|
-
test('floating input clamps out-of-bounds values to min/max
|
|
325
|
+
test('floating input clamps out-of-bounds values to min/max on commit', async () => {
|
|
367
326
|
await element.updateComplete;
|
|
368
327
|
const floatInput = element.shadowRoot!.querySelector<HTMLInputElement>('.thumb-input input[type="number"]')!;
|
|
369
328
|
floatInput.value = '150';
|
|
370
|
-
floatInput.dispatchEvent(new Event('
|
|
371
|
-
await new Promise<void>((r) => setTimeout(r, 600));
|
|
329
|
+
floatInput.dispatchEvent(new Event('change'));
|
|
372
330
|
assert.equal(element.value, '100');
|
|
373
331
|
|
|
374
332
|
element.min = 20;
|
|
375
333
|
floatInput.value = '5';
|
|
376
|
-
floatInput.dispatchEvent(new Event('
|
|
377
|
-
await new Promise<void>((r) => setTimeout(r, 600));
|
|
334
|
+
floatInput.dispatchEvent(new Event('change'));
|
|
378
335
|
assert.equal(element.value, '20');
|
|
379
336
|
});
|
|
380
337
|
|
|
381
|
-
test('floating input snaps typed value to nearest step
|
|
338
|
+
test('floating input snaps typed value to nearest step on commit', async () => {
|
|
382
339
|
await element.updateComplete;
|
|
383
340
|
element.step = 10;
|
|
384
341
|
const floatInput = element.shadowRoot!.querySelector<HTMLInputElement>('.thumb-input input[type="number"]')!;
|
|
385
342
|
floatInput.value = '47';
|
|
386
|
-
floatInput.dispatchEvent(new Event('
|
|
387
|
-
await new Promise<void>((r) => setTimeout(r, 600));
|
|
343
|
+
floatInput.dispatchEvent(new Event('change'));
|
|
388
344
|
assert.equal(element.value, '50');
|
|
389
345
|
});
|
|
390
346
|
|
|
391
|
-
test('floating input change
|
|
347
|
+
test('floating input change dispatches correct value and does not re-fire if value is unchanged', async () => {
|
|
392
348
|
await element.updateComplete;
|
|
349
|
+
let changeCount = 0;
|
|
393
350
|
let detail: string | undefined;
|
|
394
351
|
element.addEventListener('change', (e: Event) => {
|
|
352
|
+
changeCount++;
|
|
395
353
|
detail = (e as CustomEvent<string>).detail;
|
|
396
354
|
});
|
|
397
355
|
|
|
398
356
|
const floatInput = element.shadowRoot!.querySelector<HTMLInputElement>('.thumb-input input[type="number"]')!;
|
|
399
|
-
// Simulate typing then immediately committing before the 300ms debounce settles
|
|
400
357
|
floatInput.value = '75';
|
|
401
|
-
floatInput.dispatchEvent(new Event('
|
|
402
|
-
assert.equal(element.value, '50'); // debounce not yet fired
|
|
403
|
-
floatInput.dispatchEvent(new Event('change')); // commits immediately
|
|
358
|
+
floatInput.dispatchEvent(new Event('change'));
|
|
404
359
|
assert.equal(element.value, '75');
|
|
405
360
|
assert.equal(detail, '75');
|
|
406
|
-
|
|
407
|
-
|
|
361
|
+
assert.equal(changeCount, 1);
|
|
362
|
+
|
|
363
|
+
// Committing the same value again must not fire another change event
|
|
364
|
+
floatInput.value = '75';
|
|
365
|
+
floatInput.dispatchEvent(new Event('change'));
|
|
366
|
+
assert.equal(changeCount, 1);
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
test('pressing Enter in floating input commits value without waiting for blur', async () => {
|
|
370
|
+
await element.updateComplete;
|
|
371
|
+
element.value = '50';
|
|
372
|
+
const floatInput = element.shadowRoot!.querySelector<HTMLInputElement>('.thumb-input input[type="number"]')!;
|
|
373
|
+
floatInput.value = '75';
|
|
374
|
+
floatInput.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true }));
|
|
408
375
|
assert.equal(element.value, '75');
|
|
409
376
|
});
|
|
410
377
|
|
|
@@ -473,8 +440,7 @@ suite('zui-slider', () => {
|
|
|
473
440
|
input.dispatchEvent(new Event('input'));
|
|
474
441
|
assert.equal(element.value, '50');
|
|
475
442
|
floatInput.value = '75';
|
|
476
|
-
floatInput.dispatchEvent(new Event('
|
|
477
|
-
await new Promise<void>((r) => setTimeout(r, 350));
|
|
443
|
+
floatInput.dispatchEvent(new Event('change'));
|
|
478
444
|
assert.equal(element.value, '50');
|
|
479
445
|
});
|
|
480
446
|
|
|
@@ -892,62 +858,109 @@ suite('zui-slider range', () => {
|
|
|
892
858
|
assert.equal(element.valueStart, '33.5');
|
|
893
859
|
});
|
|
894
860
|
|
|
895
|
-
test('range start float input position
|
|
861
|
+
test('range start float input position updates after drag', async () => {
|
|
896
862
|
await element.updateComplete;
|
|
897
863
|
const startInput = element.shadowRoot!.querySelector<HTMLInputElement>('input.range-start')!;
|
|
898
864
|
const thumbInputDivs = element.shadowRoot!.querySelectorAll<HTMLElement>('.thumb-input');
|
|
899
|
-
const bgBefore = startInput.style.getPropertyValue('--zui-slider-track-bg');
|
|
900
865
|
startInput.value = '30';
|
|
901
866
|
startInput.dispatchEvent(new Event('input'));
|
|
902
867
|
await element.updateComplete;
|
|
903
868
|
assert.include(thumbInputDivs[0].style.left, '30%');
|
|
904
|
-
assert.notEqual(startInput.style.getPropertyValue('--zui-slider-track-bg'), bgBefore);
|
|
905
|
-
assert.include(startInput.style.getPropertyValue('--zui-slider-track-bg'), '30%');
|
|
906
869
|
});
|
|
907
870
|
|
|
908
|
-
test('
|
|
871
|
+
test('valueStart, range-start native input, and start floating input stay in sync after drag', async () => {
|
|
909
872
|
await element.updateComplete;
|
|
910
|
-
|
|
911
|
-
element.
|
|
912
|
-
detail = (e as CustomEvent).detail;
|
|
913
|
-
});
|
|
873
|
+
const startInput = element.shadowRoot!.querySelector<HTMLInputElement>('input.range-start')!;
|
|
874
|
+
const floatInputs = element.shadowRoot!.querySelectorAll<HTMLInputElement>('.thumb-input input');
|
|
914
875
|
|
|
915
|
-
|
|
916
|
-
|
|
876
|
+
startInput.value = '30';
|
|
877
|
+
startInput.dispatchEvent(new Event('input'));
|
|
878
|
+
await element.updateComplete;
|
|
879
|
+
|
|
880
|
+
assert.equal(element.valueStart, '30');
|
|
881
|
+
assert.equal(startInput.value, '30');
|
|
882
|
+
assert.equal(floatInputs[0].value, '30');
|
|
883
|
+
});
|
|
884
|
+
|
|
885
|
+
test('valueEnd, range-end native input, and end floating input stay in sync after drag', async () => {
|
|
886
|
+
await element.updateComplete;
|
|
887
|
+
const endInput = element.shadowRoot!.querySelector<HTMLInputElement>('input.range-end')!;
|
|
888
|
+
const floatInputs = element.shadowRoot!.querySelectorAll<HTMLInputElement>('.thumb-input input');
|
|
889
|
+
|
|
890
|
+
endInput.value = '60';
|
|
891
|
+
endInput.dispatchEvent(new Event('input'));
|
|
892
|
+
await element.updateComplete;
|
|
893
|
+
|
|
894
|
+
assert.equal(element.valueEnd, '60');
|
|
895
|
+
assert.equal(endInput.value, '60');
|
|
896
|
+
assert.equal(floatInputs[1].value, '60');
|
|
897
|
+
});
|
|
917
898
|
|
|
899
|
+
test('programmatic valueStart reset after drag keeps range-start native input in sync', async () => {
|
|
900
|
+
element.valueStart = '20';
|
|
901
|
+
await element.updateComplete;
|
|
918
902
|
const startInput = element.shadowRoot!.querySelector<HTMLInputElement>('input.range-start')!;
|
|
919
|
-
|
|
903
|
+
const floatInputs = element.shadowRoot!.querySelectorAll<HTMLInputElement>('.thumb-input input');
|
|
920
904
|
|
|
921
|
-
|
|
905
|
+
startInput.value = '40';
|
|
906
|
+
startInput.dispatchEvent(new Event('input'));
|
|
907
|
+
element.valueStart = '20'; // Lit virtual is still '20' from the last render
|
|
908
|
+
|
|
909
|
+
await element.updateComplete; // live() ensures '20' is written despite virtual==='20'
|
|
910
|
+
|
|
911
|
+
assert.equal(element.valueStart, '20');
|
|
912
|
+
assert.equal(startInput.value, '20');
|
|
913
|
+
assert.equal(floatInputs[0].value, '20');
|
|
922
914
|
});
|
|
923
915
|
|
|
924
|
-
test('range
|
|
916
|
+
test('programmatic valueEnd reset after drag keeps range-end native input in sync', async () => {
|
|
917
|
+
element.valueEnd = '80';
|
|
925
918
|
await element.updateComplete;
|
|
926
|
-
|
|
927
|
-
|
|
919
|
+
const endInput = element.shadowRoot!.querySelector<HTMLInputElement>('input.range-end')!;
|
|
920
|
+
const floatInputs = element.shadowRoot!.querySelectorAll<HTMLInputElement>('.thumb-input input');
|
|
928
921
|
|
|
929
|
-
|
|
930
|
-
|
|
922
|
+
endInput.value = '60';
|
|
923
|
+
endInput.dispatchEvent(new Event('input'));
|
|
924
|
+
element.valueEnd = '80'; // Lit virtual is still '80' from the last render
|
|
931
925
|
|
|
932
|
-
|
|
926
|
+
await element.updateComplete; // live() ensures '80' is written despite virtual==='80'
|
|
927
|
+
|
|
928
|
+
assert.equal(element.valueEnd, '80');
|
|
929
|
+
assert.equal(endInput.value, '80');
|
|
930
|
+
assert.equal(floatInputs[1].value, '80');
|
|
933
931
|
});
|
|
934
932
|
|
|
935
|
-
test('change event fires with valueStart and valueEnd detail
|
|
933
|
+
test('change event fires with valueStart and valueEnd detail from both thumbs', async () => {
|
|
936
934
|
await element.updateComplete;
|
|
937
935
|
let detail: { valueStart: string; valueEnd: string } | undefined;
|
|
938
936
|
element.addEventListener('change', (e: Event) => {
|
|
939
937
|
detail = (e as CustomEvent).detail;
|
|
940
938
|
});
|
|
941
939
|
|
|
940
|
+
element.valueStart = '10';
|
|
941
|
+
element.valueEnd = '40';
|
|
942
|
+
const startInput = element.shadowRoot!.querySelector<HTMLInputElement>('input.range-start')!;
|
|
943
|
+
startInput.dispatchEvent(new Event('change'));
|
|
944
|
+
assert.deepEqual(detail, { valueStart: '10', valueEnd: '40' });
|
|
945
|
+
|
|
942
946
|
element.valueStart = '15';
|
|
943
947
|
element.valueEnd = '55';
|
|
944
|
-
|
|
945
948
|
const endInput = element.shadowRoot!.querySelector<HTMLInputElement>('input.range-end')!;
|
|
946
949
|
endInput.dispatchEvent(new Event('change'));
|
|
947
|
-
|
|
948
950
|
assert.deepEqual(detail, { valueStart: '15', valueEnd: '55' });
|
|
949
951
|
});
|
|
950
952
|
|
|
953
|
+
test('range change event bubbles', async () => {
|
|
954
|
+
await element.updateComplete;
|
|
955
|
+
let bubbled = false;
|
|
956
|
+
document.body.addEventListener('change', () => (bubbled = true), { once: true });
|
|
957
|
+
|
|
958
|
+
const startInput = element.shadowRoot!.querySelector<HTMLInputElement>('input.range-start')!;
|
|
959
|
+
startInput.dispatchEvent(new Event('change'));
|
|
960
|
+
|
|
961
|
+
assert.isTrue(bubbled);
|
|
962
|
+
});
|
|
963
|
+
|
|
951
964
|
test('floating input change fires component range change event', async () => {
|
|
952
965
|
await element.updateComplete;
|
|
953
966
|
let detail: { valueStart: string; valueEnd: string } | undefined;
|
|
@@ -962,19 +975,6 @@ suite('zui-slider range', () => {
|
|
|
962
975
|
assert.deepEqual(detail, { valueStart: '20', valueEnd: '100' });
|
|
963
976
|
});
|
|
964
977
|
|
|
965
|
-
test('range-wrapper gradient uses gray when disabled and reverts to blue when re-enabled', async () => {
|
|
966
|
-
element.disabled = true;
|
|
967
|
-
await element.updateComplete;
|
|
968
|
-
const startInput = element.shadowRoot!.querySelector<HTMLInputElement>('input.range-start')!;
|
|
969
|
-
assert.include(startInput.style.getPropertyValue('--zui-slider-track-bg'), 'var(--zui-gray)');
|
|
970
|
-
assert.notInclude(startInput.style.getPropertyValue('--zui-slider-track-bg'), 'var(--zui-blue)');
|
|
971
|
-
|
|
972
|
-
element.disabled = false;
|
|
973
|
-
await element.updateComplete;
|
|
974
|
-
assert.include(startInput.style.getPropertyValue('--zui-slider-track-bg'), 'var(--zui-blue)');
|
|
975
|
-
assert.notInclude(startInput.style.getPropertyValue('--zui-slider-track-bg'), 'var(--zui-gray)');
|
|
976
|
-
});
|
|
977
|
-
|
|
978
978
|
test('form reset hides visible floating inputs in range mode', async () => {
|
|
979
979
|
await element.updateComplete;
|
|
980
980
|
const startInput = element.shadowRoot!.querySelector<HTMLInputElement>('input.range-start')!;
|
|
@@ -988,19 +988,6 @@ suite('zui-slider range', () => {
|
|
|
988
988
|
thumbInputs.forEach((ti) => assert.notInclude(ti.className, 'thumb-input--visible'));
|
|
989
989
|
});
|
|
990
990
|
|
|
991
|
-
test('range floating input empty-string input does not trigger value update', async () => {
|
|
992
|
-
await element.updateComplete;
|
|
993
|
-
element.valueStart = '20';
|
|
994
|
-
element.valueEnd = '80';
|
|
995
|
-
const floatInputs = element.shadowRoot!.querySelectorAll<HTMLInputElement>('.thumb-input input[type="number"]');
|
|
996
|
-
floatInputs[0].value = '';
|
|
997
|
-
floatInputs[0].dispatchEvent(new Event('input'));
|
|
998
|
-
floatInputs[1].value = '';
|
|
999
|
-
floatInputs[1].dispatchEvent(new Event('input'));
|
|
1000
|
-
assert.equal(element.valueStart, '20');
|
|
1001
|
-
assert.equal(element.valueEnd, '80');
|
|
1002
|
-
});
|
|
1003
|
-
|
|
1004
991
|
test('range floating inputs hide when disabled is set while visible', async () => {
|
|
1005
992
|
await element.updateComplete;
|
|
1006
993
|
const startInput = element.shadowRoot!.querySelector<HTMLInputElement>('input.range-start')!;
|
|
@@ -1014,14 +1001,6 @@ suite('zui-slider range', () => {
|
|
|
1014
1001
|
thumbInputs.forEach((ti) => assert.notInclude(ti.className, 'thumb-input--visible'));
|
|
1015
1002
|
});
|
|
1016
1003
|
|
|
1017
|
-
test('range-wrapper gradient insets are transparent outside thumb-size bounds', async () => {
|
|
1018
|
-
await element.updateComplete;
|
|
1019
|
-
const startInput = element.shadowRoot!.querySelector<HTMLInputElement>('input.range-start')!;
|
|
1020
|
-
const bg = startInput.style.getPropertyValue('--zui-slider-track-bg');
|
|
1021
|
-
assert.include(bg, 'transparent var(--zui-slider-thumb-size)');
|
|
1022
|
-
assert.include(bg, 'transparent calc(100% - var(--zui-slider-thumb-size))');
|
|
1023
|
-
});
|
|
1024
|
-
|
|
1025
1004
|
test('disconnectedCallback clears timers and resets thumb input visibility in range mode', async () => {
|
|
1026
1005
|
await element.updateComplete;
|
|
1027
1006
|
const startInput = element.shadowRoot!.querySelector<HTMLInputElement>('input.range-start')!;
|
|
@@ -1038,45 +1017,6 @@ suite('zui-slider range', () => {
|
|
|
1038
1017
|
thumbInputs.forEach((ti) => assert.notInclude(ti.className, 'thumb-input--visible'));
|
|
1039
1018
|
});
|
|
1040
1019
|
|
|
1041
|
-
test('range-wrapper gradient updates when valueStart and valueEnd change', async () => {
|
|
1042
|
-
element.valueStart = '25';
|
|
1043
|
-
element.valueEnd = '75';
|
|
1044
|
-
await element.updateComplete;
|
|
1045
|
-
const startInput = element.shadowRoot!.querySelector<HTMLInputElement>('input.range-start')!;
|
|
1046
|
-
const bg25 = startInput.style.getPropertyValue('--zui-slider-track-bg');
|
|
1047
|
-
|
|
1048
|
-
element.valueStart = '10';
|
|
1049
|
-
element.valueEnd = '90';
|
|
1050
|
-
await element.updateComplete;
|
|
1051
|
-
const bg10 = startInput.style.getPropertyValue('--zui-slider-track-bg');
|
|
1052
|
-
|
|
1053
|
-
assert.notEqual(bg25, bg10);
|
|
1054
|
-
});
|
|
1055
|
-
|
|
1056
|
-
test('range start floating input updates valueStart after debounce delay but not immediately', async () => {
|
|
1057
|
-
await element.updateComplete;
|
|
1058
|
-
element.valueStart = '20';
|
|
1059
|
-
const floatInputs = element.shadowRoot!.querySelectorAll<HTMLInputElement>('.thumb-input input[type="number"]');
|
|
1060
|
-
floatInputs[0].value = '40';
|
|
1061
|
-
floatInputs[0].dispatchEvent(new Event('input'));
|
|
1062
|
-
// Debounce is 500ms — valueStart must not update synchronously
|
|
1063
|
-
assert.equal(element.valueStart, '20');
|
|
1064
|
-
await new Promise<void>((r) => setTimeout(r, 600));
|
|
1065
|
-
assert.equal(element.valueStart, '40');
|
|
1066
|
-
});
|
|
1067
|
-
|
|
1068
|
-
test('range end floating input updates valueEnd after debounce delay but not immediately', async () => {
|
|
1069
|
-
await element.updateComplete;
|
|
1070
|
-
element.valueEnd = '80';
|
|
1071
|
-
const floatInputs = element.shadowRoot!.querySelectorAll<HTMLInputElement>('.thumb-input input[type="number"]');
|
|
1072
|
-
floatInputs[1].value = '60';
|
|
1073
|
-
floatInputs[1].dispatchEvent(new Event('input'));
|
|
1074
|
-
// Debounce is 500ms — valueEnd must not update synchronously
|
|
1075
|
-
assert.equal(element.valueEnd, '80');
|
|
1076
|
-
await new Promise<void>((r) => setTimeout(r, 600));
|
|
1077
|
-
assert.equal(element.valueEnd, '60');
|
|
1078
|
-
});
|
|
1079
|
-
|
|
1080
1020
|
test('focused range start floating input stays visible after pointerleave', async () => {
|
|
1081
1021
|
await element.updateComplete;
|
|
1082
1022
|
const startRangeInput = element.shadowRoot!.querySelector<HTMLInputElement>('input.range-start')!;
|
|
@@ -1094,32 +1034,29 @@ suite('zui-slider range', () => {
|
|
|
1094
1034
|
assert.isTrue(thumbInputDivs[0].classList.contains('thumb-input--visible'));
|
|
1095
1035
|
});
|
|
1096
1036
|
|
|
1097
|
-
test('range floating inputs clamp out-of-bounds values to min/max
|
|
1037
|
+
test('range floating inputs clamp out-of-bounds values to min/max on commit', async () => {
|
|
1098
1038
|
await element.updateComplete;
|
|
1099
1039
|
const floatInputs = element.shadowRoot!.querySelectorAll<HTMLInputElement>('.thumb-input input[type="number"]');
|
|
1100
1040
|
floatInputs[0].value = '150';
|
|
1101
|
-
floatInputs[0].dispatchEvent(new Event('
|
|
1102
|
-
await new Promise<void>((r) => setTimeout(r, 600));
|
|
1041
|
+
floatInputs[0].dispatchEvent(new Event('change'));
|
|
1103
1042
|
assert.equal(element.valueStart, '100');
|
|
1104
1043
|
|
|
1105
1044
|
element.min = 20;
|
|
1106
1045
|
floatInputs[1].value = '5';
|
|
1107
|
-
floatInputs[1].dispatchEvent(new Event('
|
|
1108
|
-
await new Promise<void>((r) => setTimeout(r, 600));
|
|
1046
|
+
floatInputs[1].dispatchEvent(new Event('change'));
|
|
1109
1047
|
assert.equal(element.valueEnd, '20');
|
|
1110
1048
|
});
|
|
1111
1049
|
|
|
1112
|
-
test('range floating input snaps typed valueStart to nearest step
|
|
1050
|
+
test('range floating input snaps typed valueStart to nearest step on commit', async () => {
|
|
1113
1051
|
await element.updateComplete;
|
|
1114
1052
|
element.step = 10;
|
|
1115
1053
|
const floatInputs = element.shadowRoot!.querySelectorAll<HTMLInputElement>('.thumb-input input[type="number"]');
|
|
1116
1054
|
floatInputs[0].value = '23';
|
|
1117
|
-
floatInputs[0].dispatchEvent(new Event('
|
|
1118
|
-
await new Promise<void>((r) => setTimeout(r, 600));
|
|
1055
|
+
floatInputs[0].dispatchEvent(new Event('change'));
|
|
1119
1056
|
assert.equal(element.valueStart, '20');
|
|
1120
1057
|
});
|
|
1121
1058
|
|
|
1122
|
-
test('range start floating input change
|
|
1059
|
+
test('range start floating input change dispatches correct value', async () => {
|
|
1123
1060
|
await element.updateComplete;
|
|
1124
1061
|
let detail: { valueStart: string; valueEnd: string } | undefined;
|
|
1125
1062
|
element.addEventListener('change', (e: Event) => {
|
|
@@ -1128,14 +1065,37 @@ suite('zui-slider range', () => {
|
|
|
1128
1065
|
|
|
1129
1066
|
const floatInputs = element.shadowRoot!.querySelectorAll<HTMLInputElement>('.thumb-input input[type="number"]');
|
|
1130
1067
|
floatInputs[0].value = '30';
|
|
1131
|
-
floatInputs[0].dispatchEvent(new Event('
|
|
1132
|
-
assert.equal(element.valueStart, '0'); // debounce not yet fired
|
|
1133
|
-
floatInputs[0].dispatchEvent(new Event('change')); // commits immediately
|
|
1068
|
+
floatInputs[0].dispatchEvent(new Event('change'));
|
|
1134
1069
|
assert.equal(element.valueStart, '30');
|
|
1135
1070
|
assert.deepEqual(detail, { valueStart: '30', valueEnd: '100' });
|
|
1136
|
-
|
|
1137
|
-
|
|
1071
|
+
});
|
|
1072
|
+
|
|
1073
|
+
test('pressing Enter in range floating inputs commits values', async () => {
|
|
1074
|
+
await element.updateComplete;
|
|
1075
|
+
const floatInputs = element.shadowRoot!.querySelectorAll<HTMLInputElement>('.thumb-input input[type="number"]');
|
|
1076
|
+
|
|
1077
|
+
floatInputs[0].value = '30';
|
|
1078
|
+
floatInputs[0].dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true }));
|
|
1138
1079
|
assert.equal(element.valueStart, '30');
|
|
1080
|
+
|
|
1081
|
+
floatInputs[1].value = '70';
|
|
1082
|
+
floatInputs[1].dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true }));
|
|
1083
|
+
assert.equal(element.valueEnd, '70');
|
|
1084
|
+
});
|
|
1085
|
+
|
|
1086
|
+
test('range floating input change does not dispatch change event when value is unchanged', async () => {
|
|
1087
|
+
await element.updateComplete;
|
|
1088
|
+
element.valueStart = '30';
|
|
1089
|
+
element.valueEnd = '70';
|
|
1090
|
+
let changeCount = 0;
|
|
1091
|
+
element.addEventListener('change', () => changeCount++);
|
|
1092
|
+
|
|
1093
|
+
const floatInputs = element.shadowRoot!.querySelectorAll<HTMLInputElement>('.thumb-input input[type="number"]');
|
|
1094
|
+
floatInputs[0].value = '30';
|
|
1095
|
+
floatInputs[0].dispatchEvent(new Event('change'));
|
|
1096
|
+
floatInputs[1].value = '70';
|
|
1097
|
+
floatInputs[1].dispatchEvent(new Event('change'));
|
|
1098
|
+
assert.equal(changeCount, 0);
|
|
1139
1099
|
});
|
|
1140
1100
|
});
|
|
1141
1101
|
|
|
@@ -1294,15 +1254,6 @@ suite('zui-slider step dots', () => {
|
|
|
1294
1254
|
assert.notExists(element.shadowRoot!.querySelector('.step-dots'));
|
|
1295
1255
|
});
|
|
1296
1256
|
|
|
1297
|
-
test('step dot positions are correctly offset by thumb size', async () => {
|
|
1298
|
-
await element.updateComplete;
|
|
1299
|
-
const dots = element.shadowRoot!.querySelectorAll<HTMLElement>('.step-dot');
|
|
1300
|
-
assert.equal(dots[0].style.left, 'var(--zui-slider-thumb-size)');
|
|
1301
|
-
assert.equal(dots[dots.length - 1].style.left, 'calc(100% - var(--zui-slider-thumb-size))');
|
|
1302
|
-
// pos=50: offset = 1.5 - (3*50)/100 = 0
|
|
1303
|
-
assert.equal(dots[2].style.left, 'calc(50% + var(--zui-slider-thumb-size) * 0)');
|
|
1304
|
-
});
|
|
1305
|
-
|
|
1306
1257
|
test('step dots toggle when step is set to 0 then back', async () => {
|
|
1307
1258
|
await element.updateComplete;
|
|
1308
1259
|
assert.exists(element.shadowRoot!.querySelector('.step-dots'));
|
|
@@ -1423,6 +1374,20 @@ suite('zui-slider steps', () => {
|
|
|
1423
1374
|
assert.equal(element.value, 'Large');
|
|
1424
1375
|
});
|
|
1425
1376
|
|
|
1377
|
+
test('steps floating input change does not dispatch change event when value is unchanged', async () => {
|
|
1378
|
+
element.steps = ['Small', 'Medium', 'Large'];
|
|
1379
|
+
element.value = 'Medium';
|
|
1380
|
+
await element.updateComplete;
|
|
1381
|
+
let changeCount = 0;
|
|
1382
|
+
element.addEventListener('change', () => changeCount++);
|
|
1383
|
+
|
|
1384
|
+
const floatInput = element.shadowRoot!.querySelector<HTMLInputElement>('.thumb-input input')!;
|
|
1385
|
+
floatInput.value = 'Medium';
|
|
1386
|
+
floatInput.dispatchEvent(new Event('change'));
|
|
1387
|
+
assert.equal(changeCount, 0);
|
|
1388
|
+
assert.equal(element.value, 'Medium');
|
|
1389
|
+
});
|
|
1390
|
+
|
|
1426
1391
|
test('floating input change to invalid label reverts input to current value', async () => {
|
|
1427
1392
|
element.steps = ['Small', 'Medium', 'Large'];
|
|
1428
1393
|
element.value = 'Medium';
|
|
@@ -1434,15 +1399,13 @@ suite('zui-slider steps', () => {
|
|
|
1434
1399
|
assert.equal(element.value, 'Medium');
|
|
1435
1400
|
});
|
|
1436
1401
|
|
|
1437
|
-
test('floating input
|
|
1402
|
+
test('floating input resolves typed numeric alias to nearest step on commit', async () => {
|
|
1438
1403
|
element.steps = [0, 25, 50, 75, 100];
|
|
1439
1404
|
await element.updateComplete;
|
|
1440
1405
|
assert.equal(element.value, '50'); // '50' is a valid label — not snapped on init
|
|
1441
1406
|
const floatInput = element.shadowRoot!.querySelector<HTMLInputElement>('.thumb-input input')!;
|
|
1442
1407
|
floatInput.value = '30';
|
|
1443
|
-
floatInput.dispatchEvent(new Event('
|
|
1444
|
-
assert.equal(element.value, '50'); // debounce not fired yet
|
|
1445
|
-
await new Promise<void>((r) => setTimeout(r, 600));
|
|
1408
|
+
floatInput.dispatchEvent(new Event('change'));
|
|
1446
1409
|
// |30-25|=5, |30-50|=20 → snaps to '25'
|
|
1447
1410
|
assert.equal(element.value, '25');
|
|
1448
1411
|
});
|
|
@@ -1531,21 +1494,13 @@ suite('zui-slider steps', () => {
|
|
|
1531
1494
|
assert.equal(element.shadowRoot!.querySelectorAll('.step-dot-label').length, 0);
|
|
1532
1495
|
});
|
|
1533
1496
|
|
|
1534
|
-
test('
|
|
1535
|
-
element.steps = [
|
|
1536
|
-
element.value = '50';
|
|
1497
|
+
test('pressing Enter in steps floating input commits the step label', async () => {
|
|
1498
|
+
element.steps = ['Small', 'Medium', 'Large'];
|
|
1537
1499
|
await element.updateComplete;
|
|
1538
1500
|
const floatInput = element.shadowRoot!.querySelector<HTMLInputElement>('.thumb-input input')!;
|
|
1539
|
-
|
|
1540
|
-
floatInput.
|
|
1541
|
-
|
|
1542
|
-
assert.equal(element.value, '50'); // debounce not yet fired
|
|
1543
|
-
// Clear the field before debounce fires
|
|
1544
|
-
floatInput.value = '';
|
|
1545
|
-
floatInput.dispatchEvent(new Event('input'));
|
|
1546
|
-
await new Promise<void>((r) => setTimeout(r, 600));
|
|
1547
|
-
// The debounce was cancelled; value must stay at '50'
|
|
1548
|
-
assert.equal(element.value, '50');
|
|
1501
|
+
floatInput.value = 'Large';
|
|
1502
|
+
floatInput.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true }));
|
|
1503
|
+
assert.equal(element.value, 'Large');
|
|
1549
1504
|
});
|
|
1550
1505
|
|
|
1551
1506
|
test('committing empty steps floating input reverts display to current step label', async () => {
|
|
@@ -1767,23 +1722,6 @@ suite('zui-slider stepParser', () => {
|
|
|
1767
1722
|
assert.equal(element.value, '2k');
|
|
1768
1723
|
});
|
|
1769
1724
|
|
|
1770
|
-
test('null return from debounced stepParser does not update value', async () => {
|
|
1771
|
-
element.steps = [
|
|
1772
|
-
{ value: 1000, label: '1k' },
|
|
1773
|
-
{ value: 2000, label: '2k' },
|
|
1774
|
-
{ value: 3000, label: '3k' },
|
|
1775
|
-
];
|
|
1776
|
-
element.stepParser = () => null;
|
|
1777
|
-
await element.updateComplete;
|
|
1778
|
-
// Default '50' snapped to '1k' on init
|
|
1779
|
-
const floatInput = element.shadowRoot!.querySelector<HTMLInputElement>('.thumb-input input')!;
|
|
1780
|
-
floatInput.value = 'anything';
|
|
1781
|
-
floatInput.dispatchEvent(new Event('input'));
|
|
1782
|
-
await new Promise<void>((r) => setTimeout(r, 600));
|
|
1783
|
-
// null → debounce does not call the setter → value stays at '1k'
|
|
1784
|
-
assert.equal(element.value, '1k');
|
|
1785
|
-
});
|
|
1786
|
-
|
|
1787
1725
|
test('string return from stepParser is used directly as a step label', async () => {
|
|
1788
1726
|
element.steps = [
|
|
1789
1727
|
{ value: 1000, label: '1k' },
|