@zywave/zui-slider 4.4.0-pre.3 → 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 +183 -37
- package/dist/zui-slider-css.js +1 -1
- package/dist/zui-slider-css.js.map +1 -1
- package/dist/zui-slider.d.ts +16 -21
- package/dist/zui-slider.js +368 -115
- package/dist/zui-slider.js.map +1 -1
- package/docs/demo.html +99 -78
- package/lab.html +415 -15
- package/package.json +2 -2
- package/src/zui-slider-css.js +1 -1
- package/src/zui-slider.scss +34 -7
- package/src/zui-slider.ts +373 -113
- package/test/zui-slider.test.ts +659 -291
package/test/zui-slider.test.ts
CHANGED
|
@@ -30,7 +30,6 @@ suite('zui-slider', () => {
|
|
|
30
30
|
assert.equal(element.max, 100);
|
|
31
31
|
assert.equal(element.step, 0);
|
|
32
32
|
assert.equal(element.disabled, false);
|
|
33
|
-
assert.isFalse(element.showMinMax);
|
|
34
33
|
assert.isFalse(element.range);
|
|
35
34
|
});
|
|
36
35
|
|
|
@@ -98,17 +97,14 @@ suite('zui-slider', () => {
|
|
|
98
97
|
assert.equal(element.value, '33.5');
|
|
99
98
|
});
|
|
100
99
|
|
|
101
|
-
test('float input position
|
|
100
|
+
test('float input position updates after drag', async () => {
|
|
102
101
|
await element.updateComplete;
|
|
103
102
|
const input = element.shadowRoot!.querySelector<HTMLInputElement>('input[type="range"]')!;
|
|
104
103
|
const thumbDiv = element.shadowRoot!.querySelector<HTMLElement>('.thumb-input')!;
|
|
105
|
-
const bgBefore = input.style.getPropertyValue('--zui-slider-track-bg');
|
|
106
104
|
input.value = '75';
|
|
107
105
|
input.dispatchEvent(new Event('input'));
|
|
108
106
|
await element.updateComplete;
|
|
109
107
|
assert.include(thumbDiv.style.left, '75%');
|
|
110
|
-
assert.notEqual(input.style.getPropertyValue('--zui-slider-track-bg'), bgBefore);
|
|
111
|
-
assert.include(input.style.getPropertyValue('--zui-slider-track-bg'), '75%');
|
|
112
108
|
});
|
|
113
109
|
|
|
114
110
|
test('floating input change fires component change event', async () => {
|
|
@@ -197,81 +193,38 @@ suite('zui-slider', () => {
|
|
|
197
193
|
assert.equal(new FormData(form).get(name), '50');
|
|
198
194
|
});
|
|
199
195
|
|
|
200
|
-
test('native
|
|
201
|
-
for (const s of [0, -5]) {
|
|
202
|
-
element.step = s;
|
|
203
|
-
await element.updateComplete;
|
|
204
|
-
const input = element.shadowRoot!.querySelector<HTMLInputElement>('input[type="range"]')!;
|
|
205
|
-
const floatInput = element.shadowRoot!.querySelector<HTMLInputElement>('.thumb-input input[type="number"]')!;
|
|
206
|
-
assert.equal(input.step, '1');
|
|
207
|
-
assert.equal(floatInput.step, '1');
|
|
208
|
-
}
|
|
209
|
-
});
|
|
210
|
-
|
|
211
|
-
test('native inputs have correct step attribute when step is set', async () => {
|
|
212
|
-
element.step = 25;
|
|
196
|
+
test('element.value, native range input, and floating input stay in sync after drag', async () => {
|
|
213
197
|
await element.updateComplete;
|
|
214
|
-
const
|
|
215
|
-
const floatInput = element.shadowRoot!.querySelector<HTMLInputElement>('.thumb-input input
|
|
216
|
-
assert.equal(input.step, '25');
|
|
217
|
-
assert.equal(floatInput.step, '25');
|
|
218
|
-
});
|
|
198
|
+
const rangeInput = element.shadowRoot!.querySelector<HTMLInputElement>('input[type="range"]')!;
|
|
199
|
+
const floatInput = element.shadowRoot!.querySelector<HTMLInputElement>('.thumb-input input')!;
|
|
219
200
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
element.max = 90;
|
|
201
|
+
rangeInput.value = '75';
|
|
202
|
+
rangeInput.dispatchEvent(new Event('input'));
|
|
223
203
|
await element.updateComplete;
|
|
224
|
-
const input = element.shadowRoot!.querySelector<HTMLInputElement>('input[type="range"]')!;
|
|
225
|
-
const floatInput = element.shadowRoot!.querySelector<HTMLInputElement>('.thumb-input input[type="number"]')!;
|
|
226
|
-
assert.equal(input.min, '10');
|
|
227
|
-
assert.equal(input.max, '90');
|
|
228
|
-
assert.equal(floatInput.min, '10');
|
|
229
|
-
assert.equal(floatInput.max, '90');
|
|
230
|
-
});
|
|
231
204
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
const bg = input.style.getPropertyValue('--zui-slider-track-bg');
|
|
236
|
-
assert.include(bg, 'linear-gradient');
|
|
237
|
-
assert.include(bg, 'transparent var(--zui-slider-thumb-size)');
|
|
238
|
-
assert.include(bg, 'transparent calc(100% - var(--zui-slider-thumb-size))');
|
|
205
|
+
assert.equal(element.value, '75');
|
|
206
|
+
assert.equal(rangeInput.value, '75');
|
|
207
|
+
assert.equal(floatInput.value, '75');
|
|
239
208
|
});
|
|
240
209
|
|
|
241
|
-
test('
|
|
242
|
-
|
|
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';
|
|
243
214
|
await element.updateComplete;
|
|
244
215
|
const input = element.shadowRoot!.querySelector<HTMLInputElement>('input[type="range"]')!;
|
|
245
|
-
assert.include(input.style.getPropertyValue('--zui-slider-track-bg'), 'var(--zui-gray)');
|
|
246
|
-
assert.notInclude(input.style.getPropertyValue('--zui-slider-track-bg'), 'var(--zui-blue)');
|
|
247
216
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
assert.notInclude(input.style.getPropertyValue('--zui-slider-track-bg'), 'var(--zui-gray)');
|
|
252
|
-
});
|
|
217
|
+
// Simulate drag: browser writes directly to DOM, then fires input event
|
|
218
|
+
input.value = '75';
|
|
219
|
+
input.dispatchEvent(new Event('input'));
|
|
253
220
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
element.value = '50';
|
|
257
|
-
await element.updateComplete;
|
|
258
|
-
const input = element.shadowRoot!.querySelector<HTMLInputElement>('input[type="range"]')!;
|
|
259
|
-
assert.include(
|
|
260
|
-
input.style.getPropertyValue('--zui-slider-track-bg'),
|
|
261
|
-
'calc(50% + var(--zui-slider-thumb-size) * 0)'
|
|
262
|
-
);
|
|
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
|
|
263
223
|
|
|
264
|
-
//
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
assert.
|
|
268
|
-
input.style.getPropertyValue('--zui-slider-track-bg'),
|
|
269
|
-
'calc(75% + var(--zui-slider-thumb-size) * -0.75)'
|
|
270
|
-
);
|
|
271
|
-
assert.notInclude(
|
|
272
|
-
input.style.getPropertyValue('--zui-slider-track-bg'),
|
|
273
|
-
'calc(50% + var(--zui-slider-thumb-size) * 0)'
|
|
274
|
-
);
|
|
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');
|
|
275
228
|
});
|
|
276
229
|
|
|
277
230
|
test('form reset hides visible floating input', async () => {
|
|
@@ -359,82 +312,66 @@ suite('zui-slider', () => {
|
|
|
359
312
|
document.body.removeChild(f);
|
|
360
313
|
});
|
|
361
314
|
|
|
362
|
-
test('floating input
|
|
363
|
-
await element.updateComplete;
|
|
364
|
-
element.value = '50';
|
|
365
|
-
const floatInput = element.shadowRoot!.querySelector<HTMLInputElement>('.thumb-input input[type="number"]')!;
|
|
366
|
-
floatInput.value = '75';
|
|
367
|
-
floatInput.dispatchEvent(new Event('input'));
|
|
368
|
-
// Debounce is 300ms — value must not update synchronously
|
|
369
|
-
assert.equal(element.value, '50');
|
|
370
|
-
await new Promise<void>((r) => setTimeout(r, 350));
|
|
371
|
-
assert.equal(element.value, '75');
|
|
372
|
-
});
|
|
373
|
-
|
|
374
|
-
test('form reset cancels pending floating input debounce', async () => {
|
|
315
|
+
test('committing empty floating input reverts display to current value', async () => {
|
|
375
316
|
await element.updateComplete;
|
|
376
|
-
|
|
377
|
-
floatInput.value = '80';
|
|
378
|
-
floatInput.dispatchEvent(new Event('input'));
|
|
379
|
-
// Debounce is pending — reset before it fires
|
|
380
|
-
form.reset();
|
|
381
|
-
await new Promise<void>((r) => setTimeout(r, 350));
|
|
382
|
-
// Debounce timer must have been cancelled by formResetCallback
|
|
383
|
-
assert.equal(element.value, '50');
|
|
384
|
-
});
|
|
385
|
-
|
|
386
|
-
test('floating input empty-string input does not trigger value update', async () => {
|
|
387
|
-
await element.updateComplete;
|
|
388
|
-
element.value = '50';
|
|
317
|
+
element.value = '75';
|
|
389
318
|
const floatInput = element.shadowRoot!.querySelector<HTMLInputElement>('.thumb-input input[type="number"]')!;
|
|
390
319
|
floatInput.value = '';
|
|
391
|
-
floatInput.dispatchEvent(new Event('
|
|
392
|
-
|
|
393
|
-
assert.equal(
|
|
320
|
+
floatInput.dispatchEvent(new Event('change'));
|
|
321
|
+
assert.equal(element.value, '75');
|
|
322
|
+
assert.equal(floatInput.value, '75');
|
|
394
323
|
});
|
|
395
324
|
|
|
396
|
-
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 () => {
|
|
397
326
|
await element.updateComplete;
|
|
398
327
|
const floatInput = element.shadowRoot!.querySelector<HTMLInputElement>('.thumb-input input[type="number"]')!;
|
|
399
328
|
floatInput.value = '150';
|
|
400
|
-
floatInput.dispatchEvent(new Event('
|
|
401
|
-
await new Promise<void>((r) => setTimeout(r, 350));
|
|
329
|
+
floatInput.dispatchEvent(new Event('change'));
|
|
402
330
|
assert.equal(element.value, '100');
|
|
403
331
|
|
|
404
332
|
element.min = 20;
|
|
405
333
|
floatInput.value = '5';
|
|
406
|
-
floatInput.dispatchEvent(new Event('
|
|
407
|
-
await new Promise<void>((r) => setTimeout(r, 350));
|
|
334
|
+
floatInput.dispatchEvent(new Event('change'));
|
|
408
335
|
assert.equal(element.value, '20');
|
|
409
336
|
});
|
|
410
337
|
|
|
411
|
-
test('floating input snaps typed value to nearest step
|
|
338
|
+
test('floating input snaps typed value to nearest step on commit', async () => {
|
|
412
339
|
await element.updateComplete;
|
|
413
340
|
element.step = 10;
|
|
414
341
|
const floatInput = element.shadowRoot!.querySelector<HTMLInputElement>('.thumb-input input[type="number"]')!;
|
|
415
342
|
floatInput.value = '47';
|
|
416
|
-
floatInput.dispatchEvent(new Event('
|
|
417
|
-
await new Promise<void>((r) => setTimeout(r, 350));
|
|
343
|
+
floatInput.dispatchEvent(new Event('change'));
|
|
418
344
|
assert.equal(element.value, '50');
|
|
419
345
|
});
|
|
420
346
|
|
|
421
|
-
test('floating input change
|
|
347
|
+
test('floating input change dispatches correct value and does not re-fire if value is unchanged', async () => {
|
|
422
348
|
await element.updateComplete;
|
|
349
|
+
let changeCount = 0;
|
|
423
350
|
let detail: string | undefined;
|
|
424
351
|
element.addEventListener('change', (e: Event) => {
|
|
352
|
+
changeCount++;
|
|
425
353
|
detail = (e as CustomEvent<string>).detail;
|
|
426
354
|
});
|
|
427
355
|
|
|
428
356
|
const floatInput = element.shadowRoot!.querySelector<HTMLInputElement>('.thumb-input input[type="number"]')!;
|
|
429
|
-
// Simulate typing then immediately committing before the 300ms debounce settles
|
|
430
357
|
floatInput.value = '75';
|
|
431
|
-
floatInput.dispatchEvent(new Event('
|
|
432
|
-
assert.equal(element.value, '50'); // debounce not yet fired
|
|
433
|
-
floatInput.dispatchEvent(new Event('change')); // commits immediately
|
|
358
|
+
floatInput.dispatchEvent(new Event('change'));
|
|
434
359
|
assert.equal(element.value, '75');
|
|
435
360
|
assert.equal(detail, '75');
|
|
436
|
-
|
|
437
|
-
|
|
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 }));
|
|
438
375
|
assert.equal(element.value, '75');
|
|
439
376
|
});
|
|
440
377
|
|
|
@@ -503,8 +440,7 @@ suite('zui-slider', () => {
|
|
|
503
440
|
input.dispatchEvent(new Event('input'));
|
|
504
441
|
assert.equal(element.value, '50');
|
|
505
442
|
floatInput.value = '75';
|
|
506
|
-
floatInput.dispatchEvent(new Event('
|
|
507
|
-
await new Promise<void>((r) => setTimeout(r, 350));
|
|
443
|
+
floatInput.dispatchEvent(new Event('change'));
|
|
508
444
|
assert.equal(element.value, '50');
|
|
509
445
|
});
|
|
510
446
|
|
|
@@ -552,7 +488,7 @@ suite('zui-slider', () => {
|
|
|
552
488
|
});
|
|
553
489
|
});
|
|
554
490
|
|
|
555
|
-
suite('zui-slider
|
|
491
|
+
suite('zui-slider min-max labels', () => {
|
|
556
492
|
let element: ZuiSlider;
|
|
557
493
|
|
|
558
494
|
setup(() => {
|
|
@@ -564,26 +500,30 @@ suite('zui-slider showMinMax', () => {
|
|
|
564
500
|
document.body.removeChild(element);
|
|
565
501
|
});
|
|
566
502
|
|
|
567
|
-
test('min-max labels
|
|
503
|
+
test('min-max labels are always rendered when showStepLabels is false', async () => {
|
|
568
504
|
await element.updateComplete;
|
|
569
|
-
assert.
|
|
505
|
+
assert.exists(element.shadowRoot!.querySelector('.min-max-labels'));
|
|
506
|
+
});
|
|
570
507
|
|
|
508
|
+
test('min-max labels are hidden when showStepLabels is true', async () => {
|
|
509
|
+
element.steps = ['Small', 'Medium', 'Large'];
|
|
510
|
+
element.showStepLabels = true;
|
|
511
|
+
await element.updateComplete;
|
|
512
|
+
const labels = element.shadowRoot!.querySelector<HTMLElement>('.min-max-labels');
|
|
513
|
+
assert.exists(labels);
|
|
514
|
+
assert.equal(labels!.style.visibility, 'hidden');
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
test('min-max labels reflect min and max values', async () => {
|
|
571
518
|
element.min = 10;
|
|
572
519
|
element.max = 90;
|
|
573
|
-
element.showMinMax = true;
|
|
574
520
|
await element.updateComplete;
|
|
575
521
|
const labels = element.shadowRoot!.querySelectorAll('.min-max-label');
|
|
576
|
-
assert.exists(element.shadowRoot!.querySelector('.min-max-labels'));
|
|
577
522
|
assert.equal(labels[0].textContent!.trim(), '10');
|
|
578
523
|
assert.equal(labels[1].textContent!.trim(), '90');
|
|
579
|
-
|
|
580
|
-
element.showMinMax = false;
|
|
581
|
-
await element.updateComplete;
|
|
582
|
-
assert.notExists(element.shadowRoot!.querySelector('.min-max-labels'));
|
|
583
524
|
});
|
|
584
525
|
|
|
585
|
-
test('min-max labels update when min and max change
|
|
586
|
-
element.showMinMax = true;
|
|
526
|
+
test('min-max labels update when min and max change', async () => {
|
|
587
527
|
element.min = 10;
|
|
588
528
|
element.max = 90;
|
|
589
529
|
await element.updateComplete;
|
|
@@ -595,17 +535,10 @@ suite('zui-slider showMinMax', () => {
|
|
|
595
535
|
assert.equal(labels[1].textContent!.trim(), '95');
|
|
596
536
|
});
|
|
597
537
|
|
|
598
|
-
test('show-min-max HTML attribute enables min-max labels', async () => {
|
|
599
|
-
element.setAttribute('show-min-max', '');
|
|
600
|
-
await element.updateComplete;
|
|
601
|
-
assert.exists(element.shadowRoot!.querySelector('.min-max-labels'));
|
|
602
|
-
});
|
|
603
|
-
|
|
604
538
|
test('min-max labels render in range mode', async () => {
|
|
605
539
|
element.setAttribute('range', '');
|
|
606
540
|
element.min = 5;
|
|
607
541
|
element.max = 95;
|
|
608
|
-
element.showMinMax = true;
|
|
609
542
|
await element.updateComplete;
|
|
610
543
|
assert.exists(element.shadowRoot!.querySelector('.min-max-labels'));
|
|
611
544
|
const labels = element.shadowRoot!.querySelectorAll('.min-max-label');
|
|
@@ -613,6 +546,15 @@ suite('zui-slider showMinMax', () => {
|
|
|
613
546
|
assert.equal(labels[0].textContent!.trim(), '5');
|
|
614
547
|
assert.equal(labels[1].textContent!.trim(), '95');
|
|
615
548
|
});
|
|
549
|
+
|
|
550
|
+
test('show-step-labels attribute hides min-max labels', async () => {
|
|
551
|
+
element.steps = ['Small', 'Medium', 'Large'];
|
|
552
|
+
element.setAttribute('show-step-labels', '');
|
|
553
|
+
await element.updateComplete;
|
|
554
|
+
const labels = element.shadowRoot!.querySelector<HTMLElement>('.min-max-labels');
|
|
555
|
+
assert.exists(labels);
|
|
556
|
+
assert.equal(labels!.style.visibility, 'hidden');
|
|
557
|
+
});
|
|
616
558
|
});
|
|
617
559
|
|
|
618
560
|
faceTests<ZuiSlider>('zui-slider', {
|
|
@@ -649,7 +591,6 @@ suite('zui-slider range', () => {
|
|
|
649
591
|
assert.equal(element.max, 100);
|
|
650
592
|
assert.equal(element.step, 0);
|
|
651
593
|
assert.isFalse(element.disabled);
|
|
652
|
-
assert.isFalse(element.showMinMax);
|
|
653
594
|
});
|
|
654
595
|
|
|
655
596
|
test('accepts value-start and value-end attributes', () => {
|
|
@@ -917,62 +858,109 @@ suite('zui-slider range', () => {
|
|
|
917
858
|
assert.equal(element.valueStart, '33.5');
|
|
918
859
|
});
|
|
919
860
|
|
|
920
|
-
test('range start float input position
|
|
861
|
+
test('range start float input position updates after drag', async () => {
|
|
921
862
|
await element.updateComplete;
|
|
922
863
|
const startInput = element.shadowRoot!.querySelector<HTMLInputElement>('input.range-start')!;
|
|
923
864
|
const thumbInputDivs = element.shadowRoot!.querySelectorAll<HTMLElement>('.thumb-input');
|
|
924
|
-
const bgBefore = startInput.style.getPropertyValue('--zui-slider-track-bg');
|
|
925
865
|
startInput.value = '30';
|
|
926
866
|
startInput.dispatchEvent(new Event('input'));
|
|
927
867
|
await element.updateComplete;
|
|
928
868
|
assert.include(thumbInputDivs[0].style.left, '30%');
|
|
929
|
-
assert.notEqual(startInput.style.getPropertyValue('--zui-slider-track-bg'), bgBefore);
|
|
930
|
-
assert.include(startInput.style.getPropertyValue('--zui-slider-track-bg'), '30%');
|
|
931
869
|
});
|
|
932
870
|
|
|
933
|
-
test('
|
|
871
|
+
test('valueStart, range-start native input, and start floating input stay in sync after drag', async () => {
|
|
934
872
|
await element.updateComplete;
|
|
935
|
-
|
|
936
|
-
element.
|
|
937
|
-
detail = (e as CustomEvent).detail;
|
|
938
|
-
});
|
|
873
|
+
const startInput = element.shadowRoot!.querySelector<HTMLInputElement>('input.range-start')!;
|
|
874
|
+
const floatInputs = element.shadowRoot!.querySelectorAll<HTMLInputElement>('.thumb-input input');
|
|
939
875
|
|
|
940
|
-
|
|
941
|
-
|
|
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
|
+
});
|
|
942
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
|
+
});
|
|
898
|
+
|
|
899
|
+
test('programmatic valueStart reset after drag keeps range-start native input in sync', async () => {
|
|
900
|
+
element.valueStart = '20';
|
|
901
|
+
await element.updateComplete;
|
|
943
902
|
const startInput = element.shadowRoot!.querySelector<HTMLInputElement>('input.range-start')!;
|
|
944
|
-
|
|
903
|
+
const floatInputs = element.shadowRoot!.querySelectorAll<HTMLInputElement>('.thumb-input input');
|
|
945
904
|
|
|
946
|
-
|
|
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');
|
|
947
914
|
});
|
|
948
915
|
|
|
949
|
-
test('range
|
|
916
|
+
test('programmatic valueEnd reset after drag keeps range-end native input in sync', async () => {
|
|
917
|
+
element.valueEnd = '80';
|
|
950
918
|
await element.updateComplete;
|
|
951
|
-
|
|
952
|
-
|
|
919
|
+
const endInput = element.shadowRoot!.querySelector<HTMLInputElement>('input.range-end')!;
|
|
920
|
+
const floatInputs = element.shadowRoot!.querySelectorAll<HTMLInputElement>('.thumb-input input');
|
|
953
921
|
|
|
954
|
-
|
|
955
|
-
|
|
922
|
+
endInput.value = '60';
|
|
923
|
+
endInput.dispatchEvent(new Event('input'));
|
|
924
|
+
element.valueEnd = '80'; // Lit virtual is still '80' from the last render
|
|
956
925
|
|
|
957
|
-
|
|
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');
|
|
958
931
|
});
|
|
959
932
|
|
|
960
|
-
test('change event fires with valueStart and valueEnd detail
|
|
933
|
+
test('change event fires with valueStart and valueEnd detail from both thumbs', async () => {
|
|
961
934
|
await element.updateComplete;
|
|
962
935
|
let detail: { valueStart: string; valueEnd: string } | undefined;
|
|
963
936
|
element.addEventListener('change', (e: Event) => {
|
|
964
937
|
detail = (e as CustomEvent).detail;
|
|
965
938
|
});
|
|
966
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
|
+
|
|
967
946
|
element.valueStart = '15';
|
|
968
947
|
element.valueEnd = '55';
|
|
969
|
-
|
|
970
948
|
const endInput = element.shadowRoot!.querySelector<HTMLInputElement>('input.range-end')!;
|
|
971
949
|
endInput.dispatchEvent(new Event('change'));
|
|
972
|
-
|
|
973
950
|
assert.deepEqual(detail, { valueStart: '15', valueEnd: '55' });
|
|
974
951
|
});
|
|
975
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
|
+
|
|
976
964
|
test('floating input change fires component range change event', async () => {
|
|
977
965
|
await element.updateComplete;
|
|
978
966
|
let detail: { valueStart: string; valueEnd: string } | undefined;
|
|
@@ -987,58 +975,6 @@ suite('zui-slider range', () => {
|
|
|
987
975
|
assert.deepEqual(detail, { valueStart: '20', valueEnd: '100' });
|
|
988
976
|
});
|
|
989
977
|
|
|
990
|
-
test('range inputs reflect min and max properties in DOM', async () => {
|
|
991
|
-
element.min = 10;
|
|
992
|
-
element.max = 90;
|
|
993
|
-
await element.updateComplete;
|
|
994
|
-
const startInput = element.shadowRoot!.querySelector<HTMLInputElement>('input.range-start')!;
|
|
995
|
-
const endInput = element.shadowRoot!.querySelector<HTMLInputElement>('input.range-end')!;
|
|
996
|
-
assert.equal(startInput.min, '10');
|
|
997
|
-
assert.equal(startInput.max, '90');
|
|
998
|
-
assert.equal(endInput.min, '10');
|
|
999
|
-
assert.equal(endInput.max, '90');
|
|
1000
|
-
const floatInputs = element.shadowRoot!.querySelectorAll<HTMLInputElement>('.thumb-input input[type="number"]');
|
|
1001
|
-
floatInputs.forEach((fi) => {
|
|
1002
|
-
assert.equal(fi.min, '10');
|
|
1003
|
-
assert.equal(fi.max, '90');
|
|
1004
|
-
});
|
|
1005
|
-
});
|
|
1006
|
-
|
|
1007
|
-
test('all range inputs use step="1" when component step is 0', async () => {
|
|
1008
|
-
element.step = 0;
|
|
1009
|
-
await element.updateComplete;
|
|
1010
|
-
const startInput = element.shadowRoot!.querySelector<HTMLInputElement>('input.range-start')!;
|
|
1011
|
-
const endInput = element.shadowRoot!.querySelector<HTMLInputElement>('input.range-end')!;
|
|
1012
|
-
const floatInputs = element.shadowRoot!.querySelectorAll<HTMLInputElement>('.thumb-input input[type="number"]');
|
|
1013
|
-
assert.equal(startInput.step, '1');
|
|
1014
|
-
assert.equal(endInput.step, '1');
|
|
1015
|
-
floatInputs.forEach((fi) => assert.equal(fi.step, '1'));
|
|
1016
|
-
});
|
|
1017
|
-
|
|
1018
|
-
test('all range inputs have correct step attribute when step is set', async () => {
|
|
1019
|
-
element.step = 10;
|
|
1020
|
-
await element.updateComplete;
|
|
1021
|
-
const startInput = element.shadowRoot!.querySelector<HTMLInputElement>('input.range-start')!;
|
|
1022
|
-
const endInput = element.shadowRoot!.querySelector<HTMLInputElement>('input.range-end')!;
|
|
1023
|
-
const floatInputs = element.shadowRoot!.querySelectorAll<HTMLInputElement>('.thumb-input input[type="number"]');
|
|
1024
|
-
assert.equal(startInput.step, '10');
|
|
1025
|
-
assert.equal(endInput.step, '10');
|
|
1026
|
-
floatInputs.forEach((fi) => assert.equal(fi.step, '10'));
|
|
1027
|
-
});
|
|
1028
|
-
|
|
1029
|
-
test('range-wrapper gradient uses gray when disabled and reverts to blue when re-enabled', async () => {
|
|
1030
|
-
element.disabled = true;
|
|
1031
|
-
await element.updateComplete;
|
|
1032
|
-
const startInput = element.shadowRoot!.querySelector<HTMLInputElement>('input.range-start')!;
|
|
1033
|
-
assert.include(startInput.style.getPropertyValue('--zui-slider-track-bg'), 'var(--zui-gray)');
|
|
1034
|
-
assert.notInclude(startInput.style.getPropertyValue('--zui-slider-track-bg'), 'var(--zui-blue)');
|
|
1035
|
-
|
|
1036
|
-
element.disabled = false;
|
|
1037
|
-
await element.updateComplete;
|
|
1038
|
-
assert.include(startInput.style.getPropertyValue('--zui-slider-track-bg'), 'var(--zui-blue)');
|
|
1039
|
-
assert.notInclude(startInput.style.getPropertyValue('--zui-slider-track-bg'), 'var(--zui-gray)');
|
|
1040
|
-
});
|
|
1041
|
-
|
|
1042
978
|
test('form reset hides visible floating inputs in range mode', async () => {
|
|
1043
979
|
await element.updateComplete;
|
|
1044
980
|
const startInput = element.shadowRoot!.querySelector<HTMLInputElement>('input.range-start')!;
|
|
@@ -1052,19 +988,6 @@ suite('zui-slider range', () => {
|
|
|
1052
988
|
thumbInputs.forEach((ti) => assert.notInclude(ti.className, 'thumb-input--visible'));
|
|
1053
989
|
});
|
|
1054
990
|
|
|
1055
|
-
test('range floating input empty-string input does not trigger value update', async () => {
|
|
1056
|
-
await element.updateComplete;
|
|
1057
|
-
element.valueStart = '20';
|
|
1058
|
-
element.valueEnd = '80';
|
|
1059
|
-
const floatInputs = element.shadowRoot!.querySelectorAll<HTMLInputElement>('.thumb-input input[type="number"]');
|
|
1060
|
-
floatInputs[0].value = '';
|
|
1061
|
-
floatInputs[0].dispatchEvent(new Event('input'));
|
|
1062
|
-
floatInputs[1].value = '';
|
|
1063
|
-
floatInputs[1].dispatchEvent(new Event('input'));
|
|
1064
|
-
assert.equal(element.valueStart, '20');
|
|
1065
|
-
assert.equal(element.valueEnd, '80');
|
|
1066
|
-
});
|
|
1067
|
-
|
|
1068
991
|
test('range floating inputs hide when disabled is set while visible', async () => {
|
|
1069
992
|
await element.updateComplete;
|
|
1070
993
|
const startInput = element.shadowRoot!.querySelector<HTMLInputElement>('input.range-start')!;
|
|
@@ -1078,14 +1001,6 @@ suite('zui-slider range', () => {
|
|
|
1078
1001
|
thumbInputs.forEach((ti) => assert.notInclude(ti.className, 'thumb-input--visible'));
|
|
1079
1002
|
});
|
|
1080
1003
|
|
|
1081
|
-
test('range-wrapper gradient insets are transparent outside thumb-size bounds', async () => {
|
|
1082
|
-
await element.updateComplete;
|
|
1083
|
-
const startInput = element.shadowRoot!.querySelector<HTMLInputElement>('input.range-start')!;
|
|
1084
|
-
const bg = startInput.style.getPropertyValue('--zui-slider-track-bg');
|
|
1085
|
-
assert.include(bg, 'transparent var(--zui-slider-thumb-size)');
|
|
1086
|
-
assert.include(bg, 'transparent calc(100% - var(--zui-slider-thumb-size))');
|
|
1087
|
-
});
|
|
1088
|
-
|
|
1089
1004
|
test('disconnectedCallback clears timers and resets thumb input visibility in range mode', async () => {
|
|
1090
1005
|
await element.updateComplete;
|
|
1091
1006
|
const startInput = element.shadowRoot!.querySelector<HTMLInputElement>('input.range-start')!;
|
|
@@ -1102,45 +1017,6 @@ suite('zui-slider range', () => {
|
|
|
1102
1017
|
thumbInputs.forEach((ti) => assert.notInclude(ti.className, 'thumb-input--visible'));
|
|
1103
1018
|
});
|
|
1104
1019
|
|
|
1105
|
-
test('range-wrapper gradient updates when valueStart and valueEnd change', async () => {
|
|
1106
|
-
element.valueStart = '25';
|
|
1107
|
-
element.valueEnd = '75';
|
|
1108
|
-
await element.updateComplete;
|
|
1109
|
-
const startInput = element.shadowRoot!.querySelector<HTMLInputElement>('input.range-start')!;
|
|
1110
|
-
const bg25 = startInput.style.getPropertyValue('--zui-slider-track-bg');
|
|
1111
|
-
|
|
1112
|
-
element.valueStart = '10';
|
|
1113
|
-
element.valueEnd = '90';
|
|
1114
|
-
await element.updateComplete;
|
|
1115
|
-
const bg10 = startInput.style.getPropertyValue('--zui-slider-track-bg');
|
|
1116
|
-
|
|
1117
|
-
assert.notEqual(bg25, bg10);
|
|
1118
|
-
});
|
|
1119
|
-
|
|
1120
|
-
test('range start floating input updates valueStart after debounce delay but not immediately', async () => {
|
|
1121
|
-
await element.updateComplete;
|
|
1122
|
-
element.valueStart = '20';
|
|
1123
|
-
const floatInputs = element.shadowRoot!.querySelectorAll<HTMLInputElement>('.thumb-input input[type="number"]');
|
|
1124
|
-
floatInputs[0].value = '40';
|
|
1125
|
-
floatInputs[0].dispatchEvent(new Event('input'));
|
|
1126
|
-
// Debounce is 300ms — valueStart must not update synchronously
|
|
1127
|
-
assert.equal(element.valueStart, '20');
|
|
1128
|
-
await new Promise<void>((r) => setTimeout(r, 350));
|
|
1129
|
-
assert.equal(element.valueStart, '40');
|
|
1130
|
-
});
|
|
1131
|
-
|
|
1132
|
-
test('range end floating input updates valueEnd after debounce delay but not immediately', async () => {
|
|
1133
|
-
await element.updateComplete;
|
|
1134
|
-
element.valueEnd = '80';
|
|
1135
|
-
const floatInputs = element.shadowRoot!.querySelectorAll<HTMLInputElement>('.thumb-input input[type="number"]');
|
|
1136
|
-
floatInputs[1].value = '60';
|
|
1137
|
-
floatInputs[1].dispatchEvent(new Event('input'));
|
|
1138
|
-
// Debounce is 300ms — valueEnd must not update synchronously
|
|
1139
|
-
assert.equal(element.valueEnd, '80');
|
|
1140
|
-
await new Promise<void>((r) => setTimeout(r, 350));
|
|
1141
|
-
assert.equal(element.valueEnd, '60');
|
|
1142
|
-
});
|
|
1143
|
-
|
|
1144
1020
|
test('focused range start floating input stays visible after pointerleave', async () => {
|
|
1145
1021
|
await element.updateComplete;
|
|
1146
1022
|
const startRangeInput = element.shadowRoot!.querySelector<HTMLInputElement>('input.range-start')!;
|
|
@@ -1158,32 +1034,29 @@ suite('zui-slider range', () => {
|
|
|
1158
1034
|
assert.isTrue(thumbInputDivs[0].classList.contains('thumb-input--visible'));
|
|
1159
1035
|
});
|
|
1160
1036
|
|
|
1161
|
-
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 () => {
|
|
1162
1038
|
await element.updateComplete;
|
|
1163
1039
|
const floatInputs = element.shadowRoot!.querySelectorAll<HTMLInputElement>('.thumb-input input[type="number"]');
|
|
1164
1040
|
floatInputs[0].value = '150';
|
|
1165
|
-
floatInputs[0].dispatchEvent(new Event('
|
|
1166
|
-
await new Promise<void>((r) => setTimeout(r, 350));
|
|
1041
|
+
floatInputs[0].dispatchEvent(new Event('change'));
|
|
1167
1042
|
assert.equal(element.valueStart, '100');
|
|
1168
1043
|
|
|
1169
1044
|
element.min = 20;
|
|
1170
1045
|
floatInputs[1].value = '5';
|
|
1171
|
-
floatInputs[1].dispatchEvent(new Event('
|
|
1172
|
-
await new Promise<void>((r) => setTimeout(r, 350));
|
|
1046
|
+
floatInputs[1].dispatchEvent(new Event('change'));
|
|
1173
1047
|
assert.equal(element.valueEnd, '20');
|
|
1174
1048
|
});
|
|
1175
1049
|
|
|
1176
|
-
test('range floating input snaps typed valueStart to nearest step
|
|
1050
|
+
test('range floating input snaps typed valueStart to nearest step on commit', async () => {
|
|
1177
1051
|
await element.updateComplete;
|
|
1178
1052
|
element.step = 10;
|
|
1179
1053
|
const floatInputs = element.shadowRoot!.querySelectorAll<HTMLInputElement>('.thumb-input input[type="number"]');
|
|
1180
1054
|
floatInputs[0].value = '23';
|
|
1181
|
-
floatInputs[0].dispatchEvent(new Event('
|
|
1182
|
-
await new Promise<void>((r) => setTimeout(r, 350));
|
|
1055
|
+
floatInputs[0].dispatchEvent(new Event('change'));
|
|
1183
1056
|
assert.equal(element.valueStart, '20');
|
|
1184
1057
|
});
|
|
1185
1058
|
|
|
1186
|
-
test('range start floating input change
|
|
1059
|
+
test('range start floating input change dispatches correct value', async () => {
|
|
1187
1060
|
await element.updateComplete;
|
|
1188
1061
|
let detail: { valueStart: string; valueEnd: string } | undefined;
|
|
1189
1062
|
element.addEventListener('change', (e: Event) => {
|
|
@@ -1192,14 +1065,37 @@ suite('zui-slider range', () => {
|
|
|
1192
1065
|
|
|
1193
1066
|
const floatInputs = element.shadowRoot!.querySelectorAll<HTMLInputElement>('.thumb-input input[type="number"]');
|
|
1194
1067
|
floatInputs[0].value = '30';
|
|
1195
|
-
floatInputs[0].dispatchEvent(new Event('
|
|
1196
|
-
assert.equal(element.valueStart, '0'); // debounce not yet fired
|
|
1197
|
-
floatInputs[0].dispatchEvent(new Event('change')); // commits immediately
|
|
1068
|
+
floatInputs[0].dispatchEvent(new Event('change'));
|
|
1198
1069
|
assert.equal(element.valueStart, '30');
|
|
1199
1070
|
assert.deepEqual(detail, { valueStart: '30', valueEnd: '100' });
|
|
1200
|
-
|
|
1201
|
-
|
|
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 }));
|
|
1202
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);
|
|
1203
1099
|
});
|
|
1204
1100
|
});
|
|
1205
1101
|
|
|
@@ -1358,15 +1254,6 @@ suite('zui-slider step dots', () => {
|
|
|
1358
1254
|
assert.notExists(element.shadowRoot!.querySelector('.step-dots'));
|
|
1359
1255
|
});
|
|
1360
1256
|
|
|
1361
|
-
test('step dot positions are correctly offset by thumb size', async () => {
|
|
1362
|
-
await element.updateComplete;
|
|
1363
|
-
const dots = element.shadowRoot!.querySelectorAll<HTMLElement>('.step-dot');
|
|
1364
|
-
assert.equal(dots[0].style.left, 'var(--zui-slider-thumb-size)');
|
|
1365
|
-
assert.equal(dots[dots.length - 1].style.left, 'calc(100% - var(--zui-slider-thumb-size))');
|
|
1366
|
-
// pos=50: offset = 1.5 - (3*50)/100 = 0
|
|
1367
|
-
assert.equal(dots[2].style.left, 'calc(50% + var(--zui-slider-thumb-size) * 0)');
|
|
1368
|
-
});
|
|
1369
|
-
|
|
1370
1257
|
test('step dots toggle when step is set to 0 then back', async () => {
|
|
1371
1258
|
await element.updateComplete;
|
|
1372
1259
|
assert.exists(element.shadowRoot!.querySelector('.step-dots'));
|
|
@@ -1407,3 +1294,484 @@ suite('zui-slider step dots', () => {
|
|
|
1407
1294
|
assert.equal(wrapper.querySelectorAll('.step-dot').length, 5); // 0, 25, 50, 75, 100
|
|
1408
1295
|
});
|
|
1409
1296
|
});
|
|
1297
|
+
|
|
1298
|
+
suite('zui-slider steps', () => {
|
|
1299
|
+
let element: ZuiSlider;
|
|
1300
|
+
let form: HTMLFormElement;
|
|
1301
|
+
|
|
1302
|
+
setup(() => {
|
|
1303
|
+
element = document.createElement('zui-slider') as ZuiSlider;
|
|
1304
|
+
form = buildForm({ enableSubmit: false, appendChildren: [element] });
|
|
1305
|
+
});
|
|
1306
|
+
|
|
1307
|
+
teardown(() => {
|
|
1308
|
+
document.body.removeChild(form);
|
|
1309
|
+
});
|
|
1310
|
+
|
|
1311
|
+
test('initializes value to first step when current value is not a step label', async () => {
|
|
1312
|
+
element.steps = ['Small', 'Medium', 'Large'];
|
|
1313
|
+
await element.updateComplete;
|
|
1314
|
+
assert.equal(element.value, 'Small');
|
|
1315
|
+
});
|
|
1316
|
+
|
|
1317
|
+
test('value set to a valid step label is preserved', async () => {
|
|
1318
|
+
element.steps = ['Small', 'Medium', 'Large'];
|
|
1319
|
+
element.value = 'Medium';
|
|
1320
|
+
await element.updateComplete;
|
|
1321
|
+
assert.equal(element.value, 'Medium');
|
|
1322
|
+
});
|
|
1323
|
+
|
|
1324
|
+
test('native range input has index-based min, max, and step attributes', async () => {
|
|
1325
|
+
element.steps = ['Small', 'Medium', 'Large'];
|
|
1326
|
+
await element.updateComplete;
|
|
1327
|
+
const rangeInput = element.shadowRoot!.querySelector<HTMLInputElement>('input[type="range"]')!;
|
|
1328
|
+
assert.equal(rangeInput.min, '0');
|
|
1329
|
+
assert.equal(rangeInput.max, '2');
|
|
1330
|
+
assert.equal(rangeInput.step, '1');
|
|
1331
|
+
});
|
|
1332
|
+
|
|
1333
|
+
test('native range input value reflects current step index', async () => {
|
|
1334
|
+
element.steps = ['Small', 'Medium', 'Large'];
|
|
1335
|
+
element.value = 'Medium';
|
|
1336
|
+
await element.updateComplete;
|
|
1337
|
+
const rangeInput = element.shadowRoot!.querySelector<HTMLInputElement>('input[type="range"]')!;
|
|
1338
|
+
assert.equal(rangeInput.value, '1');
|
|
1339
|
+
});
|
|
1340
|
+
|
|
1341
|
+
test('dragging native range to index sets value to corresponding step label', async () => {
|
|
1342
|
+
element.steps = ['Small', 'Medium', 'Large'];
|
|
1343
|
+
await element.updateComplete;
|
|
1344
|
+
const rangeInput = element.shadowRoot!.querySelector<HTMLInputElement>('input[type="range"]')!;
|
|
1345
|
+
rangeInput.value = '2';
|
|
1346
|
+
rangeInput.dispatchEvent(new Event('input'));
|
|
1347
|
+
assert.equal(element.value, 'Large');
|
|
1348
|
+
});
|
|
1349
|
+
|
|
1350
|
+
test('progress is computed by step index, not by numeric value', async () => {
|
|
1351
|
+
element.steps = ['Small', 'Medium', 'Large'];
|
|
1352
|
+
element.value = 'Small';
|
|
1353
|
+
await element.updateComplete;
|
|
1354
|
+
assert.equal(element.progress, 0);
|
|
1355
|
+
element.value = 'Medium';
|
|
1356
|
+
assert.equal(element.progress, 50);
|
|
1357
|
+
element.value = 'Large';
|
|
1358
|
+
assert.equal(element.progress, 100);
|
|
1359
|
+
});
|
|
1360
|
+
|
|
1361
|
+
test('floating input is type="text" in steps mode', async () => {
|
|
1362
|
+
element.steps = ['Small', 'Medium', 'Large'];
|
|
1363
|
+
await element.updateComplete;
|
|
1364
|
+
const floatInput = element.shadowRoot!.querySelector<HTMLInputElement>('.thumb-input input')!;
|
|
1365
|
+
assert.equal(floatInput.type, 'text');
|
|
1366
|
+
});
|
|
1367
|
+
|
|
1368
|
+
test('floating input change to valid step label updates value immediately', async () => {
|
|
1369
|
+
element.steps = ['Small', 'Medium', 'Large'];
|
|
1370
|
+
await element.updateComplete;
|
|
1371
|
+
const floatInput = element.shadowRoot!.querySelector<HTMLInputElement>('.thumb-input input')!;
|
|
1372
|
+
floatInput.value = 'Large';
|
|
1373
|
+
floatInput.dispatchEvent(new Event('change'));
|
|
1374
|
+
assert.equal(element.value, 'Large');
|
|
1375
|
+
});
|
|
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
|
+
|
|
1391
|
+
test('floating input change to invalid label reverts input to current value', async () => {
|
|
1392
|
+
element.steps = ['Small', 'Medium', 'Large'];
|
|
1393
|
+
element.value = 'Medium';
|
|
1394
|
+
await element.updateComplete;
|
|
1395
|
+
const floatInput = element.shadowRoot!.querySelector<HTMLInputElement>('.thumb-input input')!;
|
|
1396
|
+
floatInput.value = 'ExtraLarge';
|
|
1397
|
+
floatInput.dispatchEvent(new Event('change'));
|
|
1398
|
+
assert.equal(floatInput.value, 'Medium');
|
|
1399
|
+
assert.equal(element.value, 'Medium');
|
|
1400
|
+
});
|
|
1401
|
+
|
|
1402
|
+
test('floating input resolves typed numeric alias to nearest step on commit', async () => {
|
|
1403
|
+
element.steps = [0, 25, 50, 75, 100];
|
|
1404
|
+
await element.updateComplete;
|
|
1405
|
+
assert.equal(element.value, '50'); // '50' is a valid label — not snapped on init
|
|
1406
|
+
const floatInput = element.shadowRoot!.querySelector<HTMLInputElement>('.thumb-input input')!;
|
|
1407
|
+
floatInput.value = '30';
|
|
1408
|
+
floatInput.dispatchEvent(new Event('change'));
|
|
1409
|
+
// |30-25|=5, |30-50|=20 → snaps to '25'
|
|
1410
|
+
assert.equal(element.value, '25');
|
|
1411
|
+
});
|
|
1412
|
+
|
|
1413
|
+
test('overflow step is preferred when value exceeds last finite step', async () => {
|
|
1414
|
+
element.steps = [
|
|
1415
|
+
{ value: 0, label: '0' },
|
|
1416
|
+
{ value: 100, label: '100' },
|
|
1417
|
+
{ value: Infinity, label: '100+' },
|
|
1418
|
+
];
|
|
1419
|
+
await element.updateComplete;
|
|
1420
|
+
// 150 > lastFiniteValue=100 → overflow step wins
|
|
1421
|
+
element.value = '150';
|
|
1422
|
+
assert.equal(element.value, '100+');
|
|
1423
|
+
});
|
|
1424
|
+
|
|
1425
|
+
test('step dots count equals steps.length', async () => {
|
|
1426
|
+
element.steps = ['Small', 'Medium', 'Large'];
|
|
1427
|
+
await element.updateComplete;
|
|
1428
|
+
assert.equal(element.shadowRoot!.querySelectorAll('.step-dot').length, 3);
|
|
1429
|
+
});
|
|
1430
|
+
|
|
1431
|
+
test('min-max labels show first and last step labels', async () => {
|
|
1432
|
+
element.steps = ['Small', 'Medium', 'Large'];
|
|
1433
|
+
await element.updateComplete;
|
|
1434
|
+
const labels = element.shadowRoot!.querySelectorAll('.min-max-label');
|
|
1435
|
+
assert.equal(labels[0].textContent!.trim(), 'Small');
|
|
1436
|
+
assert.equal(labels[1].textContent!.trim(), 'Large');
|
|
1437
|
+
});
|
|
1438
|
+
|
|
1439
|
+
test('updating steps snaps current value to first step when no longer a valid label', async () => {
|
|
1440
|
+
element.steps = ['A', 'B', 'C'];
|
|
1441
|
+
element.value = 'B';
|
|
1442
|
+
await element.updateComplete;
|
|
1443
|
+
element.steps = ['X', 'Y', 'Z'];
|
|
1444
|
+
await element.updateComplete;
|
|
1445
|
+
// 'B' not in new steps → snaps to first step
|
|
1446
|
+
assert.equal(element.value, 'X');
|
|
1447
|
+
});
|
|
1448
|
+
|
|
1449
|
+
test('value is included in form submission as the step label', async () => {
|
|
1450
|
+
const name = randString();
|
|
1451
|
+
element.setAttribute('name', name);
|
|
1452
|
+
element.steps = ['Small', 'Medium', 'Large'];
|
|
1453
|
+
element.value = 'Large';
|
|
1454
|
+
await element.updateComplete;
|
|
1455
|
+
assert.equal(new FormData(form).get(name), 'Large');
|
|
1456
|
+
});
|
|
1457
|
+
|
|
1458
|
+
test('object form steps expose label as public value and use numeric value for snapping', async () => {
|
|
1459
|
+
element.steps = [
|
|
1460
|
+
{ value: 0, label: 'None' },
|
|
1461
|
+
{ value: 500, label: 'Half' },
|
|
1462
|
+
{ value: 1000, label: 'Full' },
|
|
1463
|
+
];
|
|
1464
|
+
// |400-0|=400, |400-500|=100, |400-1000|=600 → snaps to 'Half'
|
|
1465
|
+
element.value = '400';
|
|
1466
|
+
await element.updateComplete;
|
|
1467
|
+
assert.equal(element.value, 'Half');
|
|
1468
|
+
});
|
|
1469
|
+
|
|
1470
|
+
test('showStepLabels renders a label element for each step', async () => {
|
|
1471
|
+
element.steps = ['Small', 'Medium', 'Large'];
|
|
1472
|
+
element.showStepLabels = true;
|
|
1473
|
+
await element.updateComplete;
|
|
1474
|
+
const labels = element.shadowRoot!.querySelectorAll('.step-dot-label');
|
|
1475
|
+
assert.equal(labels.length, 3);
|
|
1476
|
+
assert.equal(labels[0].textContent!.trim(), 'Small');
|
|
1477
|
+
assert.equal(labels[1].textContent!.trim(), 'Medium');
|
|
1478
|
+
assert.equal(labels[2].textContent!.trim(), 'Large');
|
|
1479
|
+
});
|
|
1480
|
+
|
|
1481
|
+
test('showStepLabels uses label when provided, value otherwise', async () => {
|
|
1482
|
+
element.steps = [{ value: 0, label: 'None' }, { value: 500 }, { value: 1000, label: 'Full' }];
|
|
1483
|
+
element.showStepLabels = true;
|
|
1484
|
+
await element.updateComplete;
|
|
1485
|
+
const labels = element.shadowRoot!.querySelectorAll('.step-dot-label');
|
|
1486
|
+
assert.equal(labels[0].textContent!.trim(), 'None');
|
|
1487
|
+
assert.equal(labels[1].textContent!.trim(), '500');
|
|
1488
|
+
assert.equal(labels[2].textContent!.trim(), 'Full');
|
|
1489
|
+
});
|
|
1490
|
+
|
|
1491
|
+
test('showStepLabels false renders no label elements', async () => {
|
|
1492
|
+
element.steps = ['Small', 'Medium', 'Large'];
|
|
1493
|
+
await element.updateComplete;
|
|
1494
|
+
assert.equal(element.shadowRoot!.querySelectorAll('.step-dot-label').length, 0);
|
|
1495
|
+
});
|
|
1496
|
+
|
|
1497
|
+
test('pressing Enter in steps floating input commits the step label', async () => {
|
|
1498
|
+
element.steps = ['Small', 'Medium', 'Large'];
|
|
1499
|
+
await element.updateComplete;
|
|
1500
|
+
const floatInput = element.shadowRoot!.querySelector<HTMLInputElement>('.thumb-input input')!;
|
|
1501
|
+
floatInput.value = 'Large';
|
|
1502
|
+
floatInput.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true }));
|
|
1503
|
+
assert.equal(element.value, 'Large');
|
|
1504
|
+
});
|
|
1505
|
+
|
|
1506
|
+
test('committing empty steps floating input reverts display to current step label', async () => {
|
|
1507
|
+
element.steps = ['Small', 'Medium', 'Large'];
|
|
1508
|
+
element.value = 'Medium';
|
|
1509
|
+
await element.updateComplete;
|
|
1510
|
+
const floatInput = element.shadowRoot!.querySelector<HTMLInputElement>('.thumb-input input')!;
|
|
1511
|
+
floatInput.value = '';
|
|
1512
|
+
floatInput.dispatchEvent(new Event('change'));
|
|
1513
|
+
assert.equal(element.value, 'Medium');
|
|
1514
|
+
assert.equal(floatInput.value, 'Medium');
|
|
1515
|
+
});
|
|
1516
|
+
|
|
1517
|
+
test('steps attribute is parsed from comma-separated string', async () => {
|
|
1518
|
+
element.setAttribute('steps', 'Small,Medium,Large');
|
|
1519
|
+
await element.updateComplete;
|
|
1520
|
+
assert.equal(element.value, 'Small');
|
|
1521
|
+
assert.equal(element.shadowRoot!.querySelectorAll('.step-dot').length, 3);
|
|
1522
|
+
});
|
|
1523
|
+
|
|
1524
|
+
test('form reset restores initial step label', async () => {
|
|
1525
|
+
const el = document.createElement('zui-slider') as ZuiSlider;
|
|
1526
|
+
const name = randString();
|
|
1527
|
+
el.setAttribute('name', name);
|
|
1528
|
+
el.steps = ['Small', 'Medium', 'Large'];
|
|
1529
|
+
el.value = 'Medium';
|
|
1530
|
+
const f = buildForm({ enableSubmit: false, appendChildren: [el] });
|
|
1531
|
+
await el.updateComplete;
|
|
1532
|
+
|
|
1533
|
+
el.value = 'Large';
|
|
1534
|
+
f.reset();
|
|
1535
|
+
|
|
1536
|
+
assert.equal(el.value, 'Medium');
|
|
1537
|
+
assert.equal(new FormData(f).get(name), 'Medium');
|
|
1538
|
+
document.body.removeChild(f);
|
|
1539
|
+
});
|
|
1540
|
+
});
|
|
1541
|
+
|
|
1542
|
+
suite('zui-slider steps range', () => {
|
|
1543
|
+
let element: ZuiSlider;
|
|
1544
|
+
let form: HTMLFormElement;
|
|
1545
|
+
|
|
1546
|
+
setup(() => {
|
|
1547
|
+
element = document.createElement('zui-slider') as ZuiSlider;
|
|
1548
|
+
element.setAttribute('range', '');
|
|
1549
|
+
form = buildForm({ enableSubmit: false, appendChildren: [element] });
|
|
1550
|
+
});
|
|
1551
|
+
|
|
1552
|
+
teardown(() => {
|
|
1553
|
+
document.body.removeChild(form);
|
|
1554
|
+
});
|
|
1555
|
+
|
|
1556
|
+
test('invalid default valueStart and valueEnd snap to first and last step', async () => {
|
|
1557
|
+
element.steps = ['A', 'B', 'C', 'D', 'E'];
|
|
1558
|
+
await element.updateComplete;
|
|
1559
|
+
assert.equal(element.valueStart, 'A');
|
|
1560
|
+
assert.equal(element.valueEnd, 'E');
|
|
1561
|
+
});
|
|
1562
|
+
|
|
1563
|
+
test('valid step labels for valueStart and valueEnd are preserved', async () => {
|
|
1564
|
+
element.steps = ['A', 'B', 'C', 'D', 'E'];
|
|
1565
|
+
element.valueStart = 'B';
|
|
1566
|
+
element.valueEnd = 'D';
|
|
1567
|
+
await element.updateComplete;
|
|
1568
|
+
assert.equal(element.valueStart, 'B');
|
|
1569
|
+
assert.equal(element.valueEnd, 'D');
|
|
1570
|
+
});
|
|
1571
|
+
|
|
1572
|
+
test('dragging start thumb to index equal to endIdx is rejected', async () => {
|
|
1573
|
+
element.steps = ['A', 'B', 'C', 'D', 'E'];
|
|
1574
|
+
await element.updateComplete;
|
|
1575
|
+
// valueStart='A'(idx=0), valueEnd='E'(idx=4)
|
|
1576
|
+
const startInput = element.shadowRoot!.querySelector<HTMLInputElement>('input.range-start')!;
|
|
1577
|
+
startInput.value = '4'; // equal to endIdx → rejected
|
|
1578
|
+
startInput.dispatchEvent(new Event('input'));
|
|
1579
|
+
assert.equal(element.valueStart, 'A');
|
|
1580
|
+
assert.equal(startInput.value, '0');
|
|
1581
|
+
});
|
|
1582
|
+
|
|
1583
|
+
test('dragging start thumb to valid index advances valueStart', async () => {
|
|
1584
|
+
element.steps = ['A', 'B', 'C', 'D', 'E'];
|
|
1585
|
+
await element.updateComplete;
|
|
1586
|
+
const startInput = element.shadowRoot!.querySelector<HTMLInputElement>('input.range-start')!;
|
|
1587
|
+
startInput.value = '2'; // index of 'C', less than endIdx=4 → accepted
|
|
1588
|
+
startInput.dispatchEvent(new Event('input'));
|
|
1589
|
+
assert.equal(element.valueStart, 'C');
|
|
1590
|
+
});
|
|
1591
|
+
|
|
1592
|
+
test('progressStart and progressEnd are computed by step index', async () => {
|
|
1593
|
+
element.steps = ['A', 'B', 'C', 'D', 'E'];
|
|
1594
|
+
await element.updateComplete;
|
|
1595
|
+
assert.equal(element.progressStart, 0); // 'A' at idx 0/4 = 0%
|
|
1596
|
+
assert.equal(element.progressEnd, 100); // 'E' at idx 4/4 = 100%
|
|
1597
|
+
element.valueStart = 'B'; // idx 1/4 = 25%
|
|
1598
|
+
element.valueEnd = 'D'; // idx 3/4 = 75%
|
|
1599
|
+
assert.equal(element.progressStart, 25);
|
|
1600
|
+
assert.equal(element.progressEnd, 75);
|
|
1601
|
+
});
|
|
1602
|
+
|
|
1603
|
+
test('form value is step labels joined as "start,end"', async () => {
|
|
1604
|
+
const name = randString();
|
|
1605
|
+
element.setAttribute('name', name);
|
|
1606
|
+
element.steps = ['A', 'B', 'C', 'D', 'E'];
|
|
1607
|
+
await element.updateComplete;
|
|
1608
|
+
assert.equal(new FormData(form).get(name), 'A,E');
|
|
1609
|
+
});
|
|
1610
|
+
|
|
1611
|
+
test('dynamic steps change snaps both invalid values to first and last step', async () => {
|
|
1612
|
+
element.steps = ['A', 'B', 'C'];
|
|
1613
|
+
element.valueStart = 'A';
|
|
1614
|
+
element.valueEnd = 'C';
|
|
1615
|
+
await element.updateComplete;
|
|
1616
|
+
element.steps = ['X', 'Y', 'Z'];
|
|
1617
|
+
await element.updateComplete;
|
|
1618
|
+
// 'A' and 'C' not in new steps → snap to first and last
|
|
1619
|
+
assert.equal(element.valueStart, 'X');
|
|
1620
|
+
assert.equal(element.valueEnd, 'Z');
|
|
1621
|
+
});
|
|
1622
|
+
|
|
1623
|
+
test('form reset restores initial step labels in range mode', async () => {
|
|
1624
|
+
const el = document.createElement('zui-slider') as ZuiSlider;
|
|
1625
|
+
el.setAttribute('range', '');
|
|
1626
|
+
const name = randString();
|
|
1627
|
+
el.setAttribute('name', name);
|
|
1628
|
+
el.steps = ['A', 'B', 'C', 'D', 'E'];
|
|
1629
|
+
el.valueStart = 'B';
|
|
1630
|
+
el.valueEnd = 'D';
|
|
1631
|
+
const f = buildForm({ enableSubmit: false, appendChildren: [el] });
|
|
1632
|
+
await el.updateComplete;
|
|
1633
|
+
|
|
1634
|
+
el.valueStart = 'A';
|
|
1635
|
+
el.valueEnd = 'E';
|
|
1636
|
+
f.reset();
|
|
1637
|
+
|
|
1638
|
+
assert.equal(el.valueStart, 'B');
|
|
1639
|
+
assert.equal(el.valueEnd, 'D');
|
|
1640
|
+
assert.equal(new FormData(f).get(name), 'B,D');
|
|
1641
|
+
document.body.removeChild(f);
|
|
1642
|
+
});
|
|
1643
|
+
|
|
1644
|
+
test('track click moves nearer thumb in steps mode', async () => {
|
|
1645
|
+
// 5 steps → indices 0-4 (total=4); fraction 0.25 → idx 1 ('B'), fraction 0.75 → idx 3 ('D')
|
|
1646
|
+
element.steps = ['A', 'B', 'C', 'D', 'E'];
|
|
1647
|
+
await element.updateComplete;
|
|
1648
|
+
|
|
1649
|
+
clickAtFraction(element, 0.25); // closer to start (idx=0 'A') than end (idx=4 'E') → moves start
|
|
1650
|
+
assert.equal(element.valueStart, 'B');
|
|
1651
|
+
assert.equal(element.valueEnd, 'E');
|
|
1652
|
+
|
|
1653
|
+
clickAtFraction(element, 0.75); // closer to end (idx=4 'E') than start (idx=1 'B') → moves end
|
|
1654
|
+
assert.equal(element.valueStart, 'B');
|
|
1655
|
+
assert.equal(element.valueEnd, 'D');
|
|
1656
|
+
});
|
|
1657
|
+
});
|
|
1658
|
+
|
|
1659
|
+
suite('zui-slider stepParser', () => {
|
|
1660
|
+
let element: ZuiSlider;
|
|
1661
|
+
let form: HTMLFormElement;
|
|
1662
|
+
|
|
1663
|
+
setup(() => {
|
|
1664
|
+
element = document.createElement('zui-slider') as ZuiSlider;
|
|
1665
|
+
form = buildForm({ enableSubmit: false, appendChildren: [element] });
|
|
1666
|
+
});
|
|
1667
|
+
|
|
1668
|
+
teardown(() => {
|
|
1669
|
+
document.body.removeChild(form);
|
|
1670
|
+
});
|
|
1671
|
+
|
|
1672
|
+
test('exact step label match does not invoke stepParser', async () => {
|
|
1673
|
+
let callCount = 0;
|
|
1674
|
+
element.steps = [
|
|
1675
|
+
{ value: 1000, label: '1k' },
|
|
1676
|
+
{ value: 2000, label: '2k' },
|
|
1677
|
+
{ value: 3000, label: '3k' },
|
|
1678
|
+
];
|
|
1679
|
+
element.stepParser = (input) => {
|
|
1680
|
+
callCount++;
|
|
1681
|
+
return parseFloat(input) * 1000;
|
|
1682
|
+
};
|
|
1683
|
+
await element.updateComplete;
|
|
1684
|
+
const floatInput = element.shadowRoot!.querySelector<HTMLInputElement>('.thumb-input input')!;
|
|
1685
|
+
floatInput.value = '2k'; // exact step label — resolved before stepParser is consulted
|
|
1686
|
+
floatInput.dispatchEvent(new Event('change'));
|
|
1687
|
+
assert.equal(element.value, '2k');
|
|
1688
|
+
assert.equal(callCount, 0);
|
|
1689
|
+
});
|
|
1690
|
+
|
|
1691
|
+
test('number return from stepParser snaps to nearest step by numeric value', async () => {
|
|
1692
|
+
element.steps = [
|
|
1693
|
+
{ value: 1000, label: '1k' },
|
|
1694
|
+
{ value: 2000, label: '2k' },
|
|
1695
|
+
{ value: 3000, label: '3k' },
|
|
1696
|
+
];
|
|
1697
|
+
element.stepParser = (input) => {
|
|
1698
|
+
const m = input.match(/^([\d.]+)k$/);
|
|
1699
|
+
return m ? parseFloat(m[1]) * 1000 : null;
|
|
1700
|
+
};
|
|
1701
|
+
await element.updateComplete;
|
|
1702
|
+
const floatInput = element.shadowRoot!.querySelector<HTMLInputElement>('.thumb-input input')!;
|
|
1703
|
+
floatInput.value = '1.5k'; // stepParser returns 1500; |1500-1000|=|1500-2000|=500 → tie → first ('1k') wins
|
|
1704
|
+
floatInput.dispatchEvent(new Event('change'));
|
|
1705
|
+
assert.equal(element.value, '1k');
|
|
1706
|
+
});
|
|
1707
|
+
|
|
1708
|
+
test('null return from stepParser reverts floating input to current value', async () => {
|
|
1709
|
+
element.steps = [
|
|
1710
|
+
{ value: 1000, label: '1k' },
|
|
1711
|
+
{ value: 2000, label: '2k' },
|
|
1712
|
+
{ value: 3000, label: '3k' },
|
|
1713
|
+
];
|
|
1714
|
+
element.stepParser = (input) => (input === 'bad' ? null : parseFloat(input) * 1000);
|
|
1715
|
+
element.value = '2k';
|
|
1716
|
+
await element.updateComplete;
|
|
1717
|
+
const floatInput = element.shadowRoot!.querySelector<HTMLInputElement>('.thumb-input input')!;
|
|
1718
|
+
floatInput.value = 'bad';
|
|
1719
|
+
floatInput.dispatchEvent(new Event('change'));
|
|
1720
|
+
// null → rejected; floating input reverts to the current step value
|
|
1721
|
+
assert.equal(floatInput.value, '2k');
|
|
1722
|
+
assert.equal(element.value, '2k');
|
|
1723
|
+
});
|
|
1724
|
+
|
|
1725
|
+
test('string return from stepParser is used directly as a step label', async () => {
|
|
1726
|
+
element.steps = [
|
|
1727
|
+
{ value: 1000, label: '1k' },
|
|
1728
|
+
{ value: 2000, label: '2k' },
|
|
1729
|
+
{ value: 3000, label: '3k' },
|
|
1730
|
+
];
|
|
1731
|
+
element.stepParser = (input) => (input.startsWith('3') ? '3k' : null);
|
|
1732
|
+
await element.updateComplete;
|
|
1733
|
+
const floatInput = element.shadowRoot!.querySelector<HTMLInputElement>('.thumb-input input')!;
|
|
1734
|
+
floatInput.value = '3000'; // stepParser returns '3k', an exact step label
|
|
1735
|
+
floatInput.dispatchEvent(new Event('change'));
|
|
1736
|
+
assert.equal(element.value, '3k');
|
|
1737
|
+
});
|
|
1738
|
+
|
|
1739
|
+
test('programmatic value set uses stepParser for non-label strings via #clampToRange', async () => {
|
|
1740
|
+
element.steps = [
|
|
1741
|
+
{ value: 1000, label: '1k' },
|
|
1742
|
+
{ value: 2000, label: '2k' },
|
|
1743
|
+
{ value: 3000, label: '3k' },
|
|
1744
|
+
];
|
|
1745
|
+
element.stepParser = (input) => {
|
|
1746
|
+
const m = input.match(/^([\d.]+)k$/);
|
|
1747
|
+
return m ? parseFloat(m[1]) * 1000 : null;
|
|
1748
|
+
};
|
|
1749
|
+
await element.updateComplete;
|
|
1750
|
+
// '2.5k' → stepParser returns 2500; |2500-2000|=|2500-3000|=500 → tie → '2k' wins
|
|
1751
|
+
element.value = '2.5k';
|
|
1752
|
+
assert.equal(element.value, '2k');
|
|
1753
|
+
});
|
|
1754
|
+
|
|
1755
|
+
test('stepParser resolves typed aliases in range mode floating inputs', async () => {
|
|
1756
|
+
element.setAttribute('range', '');
|
|
1757
|
+
element.steps = [
|
|
1758
|
+
{ value: 1000, label: '1k' },
|
|
1759
|
+
{ value: 2000, label: '2k' },
|
|
1760
|
+
{ value: 3000, label: '3k' },
|
|
1761
|
+
];
|
|
1762
|
+
element.stepParser = (input) => {
|
|
1763
|
+
const m = input.match(/^([\d.]+)k$/);
|
|
1764
|
+
return m ? parseFloat(m[1]) * 1000 : null;
|
|
1765
|
+
};
|
|
1766
|
+
await element.updateComplete;
|
|
1767
|
+
const floatInputs = element.shadowRoot!.querySelectorAll<HTMLInputElement>('.thumb-input input');
|
|
1768
|
+
// '1.5k' → stepParser returns 1500; tie at 500 from both '1k' and '2k' → first ('1k') wins
|
|
1769
|
+
floatInputs[0].value = '1.5k';
|
|
1770
|
+
floatInputs[0].dispatchEvent(new Event('change'));
|
|
1771
|
+
assert.equal(element.valueStart, '1k');
|
|
1772
|
+
// '2.5k' → stepParser returns 2500; tie → first ('2k') wins
|
|
1773
|
+
floatInputs[1].value = '2.5k';
|
|
1774
|
+
floatInputs[1].dispatchEvent(new Event('change'));
|
|
1775
|
+
assert.equal(element.valueEnd, '2k');
|
|
1776
|
+
});
|
|
1777
|
+
});
|