|
13 | 13 | export let vertical = false; |
14 | 14 | export let float = false; |
15 | 15 | export let hover = true; |
| 16 | + export let disabled = false; |
16 | 17 |
|
17 | 18 | // range pips / values props |
18 | 19 | export let pips = false; |
|
50 | 51 |
|
51 | 52 | // copy the initial values in to a spring function which |
52 | 53 | // will update every time the values array is modified |
53 | | - let springPositions = spring( |
54 | | - values.map((v) => |
55 | | - parseFloat((((v - min) / (max - min)) * 100).toFixed(precision)) |
56 | | - ), |
57 | | - springValues |
58 | | - ); |
59 | | -
|
60 | | - // check the values array, and trim it if needed (range) |
61 | | - // and clamp the values to the steps and boundaries set up in the slider |
62 | | - $: values = trimRange(values).map((v) => alignValueToStep(v)); |
63 | | -
|
64 | | - // update the spring function so that movement can happen in the UI |
| 54 | + let springPositions; |
| 55 | +
|
65 | 56 | $: { |
66 | | - springPositions.set(values.map((v) => percentOf(v))); |
67 | | - } |
| 57 | + // check that "values" is an array, or set it as array |
| 58 | + // to prevent any errors in springs, or range trimming |
| 59 | + if ( !Array.isArray( values ) ) { |
| 60 | + values = [(max + min) / 2]; |
| 61 | + console.error( "'values' prop should be an Array (https://github.com/simeydotme/svelte-range-slider-pips#slider-props)" ); |
| 62 | + } |
| 63 | + // trim the range as needed |
| 64 | + values = trimRange(values); |
| 65 | + // clamp the values to the steps and boundaries set up in the slider |
| 66 | + values = values.map((v) => alignValueToStep(v)); |
| 67 | + // update the spring function so that movement can happen in the UI |
| 68 | + if ( springPositions ) { |
| 69 | + springPositions.set(values.map((v) => percentOf(v))); |
| 70 | + } else { |
| 71 | + springPositions = spring( values.map((v) => percentOf(v)), springValues ); |
| 72 | + } |
| 73 | + }; |
68 | 74 |
|
69 | 75 | /** |
70 | 76 | * take in a value, and then calculate that value's percentage |
|
167 | 173 |
|
168 | 174 | /** |
169 | 175 | * trim the values array based on whether the property |
170 | | - * for 'range' is 'min', 'max', or truthy. |
| 176 | + * for 'range' is 'min', 'max', or truthy. This is because we |
| 177 | + * do not want more than one handle for a min/max range, and we do |
| 178 | + * not want more than two handles for a true range. |
171 | 179 | * @param {array} values the input values for the rangeSlider |
172 | 180 | * @return {array} the range array for creating a rangeSlider |
173 | 181 | **/ |
|
352 | 360 | * @param {event} e the event from browser |
353 | 361 | **/ |
354 | 362 | function sliderFocusHandle(e) { |
355 | | - activeHandle = index(e.target); |
356 | | - focus = true; |
| 363 | + if ( !disabled ) { |
| 364 | + activeHandle = index(e.target); |
| 365 | + focus = true; |
| 366 | + } |
357 | 367 | } |
358 | 368 |
|
359 | 369 | /** |
|
362 | 372 | * @param {event} e the event from browser |
363 | 373 | **/ |
364 | 374 | function sliderKeydown(e) { |
365 | | - const handle = index(e.target); |
366 | | - let jump = e.ctrlKey || e.metaKey || e.shiftKey ? step * 10 : step; |
367 | | - let prevent = false; |
368 | | -
|
369 | | - switch (e.key) { |
370 | | - case "PageDown": |
371 | | - jump *= 10; |
372 | | - case "ArrowRight": |
373 | | - case "ArrowUp": |
374 | | - moveHandle(handle, values[handle] + jump); |
375 | | - prevent = true; |
376 | | - break; |
377 | | - case "PageUp": |
378 | | - jump *= 10; |
379 | | - case "ArrowLeft": |
380 | | - case "ArrowDown": |
381 | | - moveHandle(handle, values[handle] - jump); |
382 | | - prevent = true; |
383 | | - break; |
384 | | - case "Home": |
385 | | - moveHandle(handle, min); |
386 | | - prevent = true; |
387 | | - break; |
388 | | - case "End": |
389 | | - moveHandle(handle, max); |
390 | | - prevent = true; |
391 | | - break; |
392 | | - } |
393 | | - if (prevent) { |
394 | | - e.preventDefault(); |
395 | | - e.stopPropagation(); |
| 375 | + if ( !disabled ) { |
| 376 | + const handle = index(e.target); |
| 377 | + let jump = e.ctrlKey || e.metaKey || e.shiftKey ? step * 10 : step; |
| 378 | + let prevent = false; |
| 379 | +
|
| 380 | + switch (e.key) { |
| 381 | + case "PageDown": |
| 382 | + jump *= 10; |
| 383 | + case "ArrowRight": |
| 384 | + case "ArrowUp": |
| 385 | + moveHandle(handle, values[handle] + jump); |
| 386 | + prevent = true; |
| 387 | + break; |
| 388 | + case "PageUp": |
| 389 | + jump *= 10; |
| 390 | + case "ArrowLeft": |
| 391 | + case "ArrowDown": |
| 392 | + moveHandle(handle, values[handle] - jump); |
| 393 | + prevent = true; |
| 394 | + break; |
| 395 | + case "Home": |
| 396 | + moveHandle(handle, min); |
| 397 | + prevent = true; |
| 398 | + break; |
| 399 | + case "End": |
| 400 | + moveHandle(handle, max); |
| 401 | + prevent = true; |
| 402 | + break; |
| 403 | + } |
| 404 | + if (prevent) { |
| 405 | + e.preventDefault(); |
| 406 | + e.stopPropagation(); |
| 407 | + } |
396 | 408 | } |
397 | 409 | } |
398 | 410 |
|
|
402 | 414 | * @param {event} e the event from browser |
403 | 415 | **/ |
404 | 416 | function sliderInteractStart(e) { |
405 | | - const clientPos = normalisedClient(e); |
406 | | - // set the closest handle as active |
407 | | - focus = true; |
408 | | - handleActivated = true; |
409 | | - handlePressed = true; |
410 | | - activeHandle = getClosestHandle(clientPos); |
411 | | -
|
412 | | - // fire the start event |
413 | | - startValue = previousValue = alignValueToStep(values[activeHandle]); |
414 | | - eStart(); |
415 | | -
|
416 | | - // for touch devices we want the handle to instantly |
417 | | - // move to the position touched for more responsive feeling |
418 | | - if (e.type === "touchstart") { |
419 | | - handleInteract(clientPos); |
| 417 | + if ( !disabled ) { |
| 418 | + const clientPos = normalisedClient(e); |
| 419 | + // set the closest handle as active |
| 420 | + focus = true; |
| 421 | + handleActivated = true; |
| 422 | + handlePressed = true; |
| 423 | + activeHandle = getClosestHandle(clientPos); |
| 424 | +
|
| 425 | + // fire the start event |
| 426 | + startValue = previousValue = alignValueToStep(values[activeHandle]); |
| 427 | + eStart(); |
| 428 | +
|
| 429 | + // for touch devices we want the handle to instantly |
| 430 | + // move to the position touched for more responsive feeling |
| 431 | + if (e.type === "touchstart") { |
| 432 | + handleInteract(clientPos); |
| 433 | + } |
420 | 434 | } |
421 | 435 | } |
422 | 436 |
|
|
451 | 465 | * @param {event} e the event from browser |
452 | 466 | **/ |
453 | 467 | function bodyInteract(e) { |
454 | | - if (handleActivated) { |
455 | | - handleInteract(normalisedClient(e)); |
| 468 | + if ( !disabled ) { |
| 469 | + if (handleActivated) { |
| 470 | + handleInteract(normalisedClient(e)); |
| 471 | + } |
456 | 472 | } |
457 | 473 | } |
458 | 474 |
|
|
463 | 479 | * @param {event} e the event from browser |
464 | 480 | **/ |
465 | 481 | function bodyMouseUp(e) { |
466 | | - const el = e.target; |
467 | | - // this only works if a handle is active, which can |
468 | | - // only happen if there was sliderInteractStart triggered |
469 | | - // on the slider, already |
470 | | - if (handleActivated) { |
471 | | - if (el === slider || slider.contains(el)) { |
472 | | - focus = true; |
473 | | - if (!targetIsHandle(el)) { |
474 | | - handleInteract(normalisedClient(e)); |
| 482 | + if ( !disabled ) { |
| 483 | + const el = e.target; |
| 484 | + // this only works if a handle is active, which can |
| 485 | + // only happen if there was sliderInteractStart triggered |
| 486 | + // on the slider, already |
| 487 | + if (handleActivated) { |
| 488 | + if (el === slider || slider.contains(el)) { |
| 489 | + focus = true; |
| 490 | + if (!targetIsHandle(el)) { |
| 491 | + handleInteract(normalisedClient(e)); |
| 492 | + } |
475 | 493 | } |
| 494 | + // fire the stop event for mouse device |
| 495 | + // when the body is triggered with an active handle |
| 496 | + eStop(); |
476 | 497 | } |
477 | | - // fire the stop event for mouse device |
478 | | - // when the body is triggered with an active handle |
479 | | - eStop(); |
480 | 498 | } |
481 | 499 | handleActivated = false; |
482 | 500 | handlePressed = false; |
|
493 | 511 | } |
494 | 512 |
|
495 | 513 | function bodyKeyDown(e) { |
496 | | - if (e.target === slider || slider.contains(e.target)) { |
497 | | - keyboardActive = true; |
| 514 | + if ( !disabled ) { |
| 515 | + if (e.target === slider || slider.contains(e.target)) { |
| 516 | + keyboardActive = true; |
| 517 | + } |
498 | 518 | } |
499 | 519 | } |
500 | 520 |
|
501 | 521 | function eStart() { |
502 | | - dispatch("start", { |
| 522 | + !disabled && dispatch("start", { |
503 | 523 | activeHandle, |
504 | 524 | value: startValue, |
505 | 525 | values: values.map((v) => alignValueToStep(v)), |
506 | 526 | }); |
507 | 527 | } |
508 | 528 |
|
509 | 529 | function eStop() { |
510 | | - dispatch("stop", { |
| 530 | + !disabled && dispatch("stop", { |
511 | 531 | activeHandle, |
512 | 532 | startValue: startValue, |
513 | 533 | value: values[activeHandle], |
|
516 | 536 | } |
517 | 537 |
|
518 | 538 | function eChange() { |
519 | | - dispatch("change", { |
| 539 | + !disabled && dispatch("change", { |
520 | 540 | activeHandle, |
521 | 541 | startValue: startValue, |
522 | 542 | previousValue: |
|
545 | 565 | border-radius: 100px; |
546 | 566 | height: 0.5em; |
547 | 567 | margin: 1em; |
| 568 | + transition: opacity 0.2s ease; |
548 | 569 | } |
549 | 570 | :global(.rangeSlider, .rangeSlider *) { |
550 | 571 | user-select: none; |
|
695 | 716 | background-color: #4a40d4; |
696 | 717 | background-color: var(--float); |
697 | 718 | } |
| 719 | + :global(.rangeSlider.disabled ) { |
| 720 | + opacity: 0.5; |
| 721 | + } |
| 722 | + :global(.rangeSlider.disabled .rangeNub) { |
| 723 | + background-color: #d7dada; |
| 724 | + background-color: var(--slider); |
| 725 | + } |
698 | 726 | </style> |
699 | 727 |
|
700 | 728 | <div |
701 | 729 | {id} |
702 | 730 | bind:this={slider} |
703 | 731 | class="rangeSlider" |
704 | 732 | class:range |
| 733 | + class:disabled |
705 | 734 | class:vertical |
706 | 735 | class:focus |
707 | 736 | class:min={range === 'min'} |
|
716 | 745 | {#each values as value, index} |
717 | 746 | <span |
718 | 747 | role="slider" |
719 | | - tabindex="0" |
720 | 748 | class="rangeHandle" |
721 | | - class:hoverable={hover} |
| 749 | + class:hoverable={hover && !disabled} |
722 | 750 | class:active={focus && activeHandle === index} |
723 | 751 | class:press={handlePressed && activeHandle === index} |
724 | 752 | on:blur={sliderBlurHandle} |
|
729 | 757 | aria-valuemax={range === true && index === 0 ? values[1] : max} |
730 | 758 | aria-valuenow={value} |
731 | 759 | aria-valuetext="{prefix}{handleFormatter(value)}{suffix}" |
732 | | - aria-orientation={vertical ? 'vertical' : 'horizontal'}> |
| 760 | + aria-orientation={vertical ? 'vertical' : 'horizontal'} |
| 761 | + aria-disabled={disabled} |
| 762 | + {disabled} |
| 763 | + tabindex="{ disabled ? -1 : 0 }" |
| 764 | + > |
733 | 765 | <span class="rangeNub" /> |
734 | 766 | {#if float} |
735 | 767 | <span class="rangeFloat">{prefix}{handleFormatter(value)}{suffix}</span> |
|
759 | 791 | {suffix} |
760 | 792 | {formatter} |
761 | 793 | {focus} |
| 794 | + {disabled} |
762 | 795 | {percentOf} /> |
763 | 796 | {/if} |
764 | 797 | </div> |
|
0 commit comments