|
132 | 132 | }; |
133 | 133 |
|
134 | 134 | const checkValueIsNumber = () => { |
135 | | - if (typeof value !== 'number') { |
| 135 | + if (!isFiniteNumber(value)) { |
136 | 136 | value = (max + min) / 2; |
137 | 137 | console.error("'value' prop should be a Number"); |
138 | 138 | } |
|
142 | 142 | if (!Array.isArray(values)) { |
143 | 143 | values = [value]; |
144 | 144 | console.error("'values' prop should be an Array"); |
| 145 | + } else if (values.some((v) => !isFiniteNumber(v))) { |
| 146 | + values = values.map((v) => (isFiniteNumber(v) ? v : (max + min) / 2)); |
| 147 | + console.error("'values' prop should be an Array of Numbers"); |
145 | 148 | } |
146 | 149 | }; |
147 | 150 |
|
|
211 | 214 | $: hasRange = |
212 | 215 | (range === true && values.length === 2) || ((range === 'min' || range === 'max') && values.length === 1); |
213 | 216 |
|
214 | | - $: { |
| 217 | + $: ((uValues, uValue) => { |
215 | 218 | // if a range, then trim so it remains as a min/max (only 2 handles) |
216 | | - const trimmedValues = trimRange(values, range); |
| 219 | + const trimmedValues = trimRange(uValues, range); |
217 | 220 | // and also align the handles to the steps/limits |
218 | 221 | const trimmedAlignedValues = trimmedValues.map((v) => constrainAndAlignValue(v, min, max, step, precision, limits)); |
219 | 222 | // update the values if they needed to be fixed |
220 | 223 | if ( |
221 | | - !(values.length === trimmedAlignedValues.length) || |
222 | | - !values.every((item, i) => coerceFloat(item, precision) === trimmedAlignedValues[i]) |
| 224 | + !(uValues.length === trimmedAlignedValues.length) || |
| 225 | + !uValues.every((item, i) => coerceFloat(item, precision) === trimmedAlignedValues[i]) |
223 | 226 | ) { |
224 | | - values = trimmedAlignedValues; |
| 227 | + uValues = trimmedAlignedValues; |
225 | 228 | } |
226 | 229 |
|
227 | | - // check if the valueLength (length of values[]) has changed, |
228 | | - // because if so we need to re-seed the spring function with the |
229 | | - // new values array. |
230 | | - if (valueLength !== values.length) { |
231 | | - // set the initial spring values when the slider initialises, |
232 | | - // or when values array length has changed |
233 | | - springPositions = springStore( |
234 | | - values.map((v) => valueAsPercent(v, min, max)), |
235 | | - springValues |
236 | | - ); |
237 | | - } else { |
238 | | - // update the value of the spring function for animated handles |
239 | | - // whenever the values has updated |
240 | | - if (slider) { |
241 | | - requestAnimationFrame(() => { |
242 | | - springPositions.set( |
243 | | - values.map((v) => valueAsPercent(v, min, max)), |
244 | | - { hard: !spring } |
245 | | - ); |
246 | | - }); |
247 | | - } |
| 230 | + // When the values array length changes, we must recreate the spring function |
| 231 | + // because the spring store is bound to a specific array length. Attempting to |
| 232 | + // update a spring with a different array size would cause errors or unexpected |
| 233 | + // behavior. For existing arrays, we update the spring values for smooth animations. |
| 234 | + if (valueLength !== uValues.length) { |
| 235 | + // create spring if there's no spring yet, or length changed |
| 236 | + createSpring(uValues); |
| 237 | + } else if (slider) { |
| 238 | + // only update the spring values if the slider is mounted |
| 239 | + updateSpring(uValues); |
248 | 240 | } |
| 241 | +
|
| 242 | + // update the external values |
| 243 | + values = uValues; |
| 244 | +
|
249 | 245 | // set the valueLength for the next check |
250 | | - valueLength = values.length; |
251 | | - } |
| 246 | + valueLength = uValues.length; |
| 247 | + })(values, value); |
| 248 | +
|
| 249 | + /** |
| 250 | + * create a spring function to animate the handles |
| 251 | + * @param values the values to animate |
| 252 | + */ |
| 253 | + const createSpring = (values: number[]) => { |
| 254 | + springPositions = springStore( |
| 255 | + values.map((v) => valueAsPercent(v, min, max)), |
| 256 | + springValues |
| 257 | + ); |
| 258 | + }; |
| 259 | +
|
| 260 | + /** |
| 261 | + * update the spring function to animate the handles |
| 262 | + * @param values the values to animate |
| 263 | + */ |
| 264 | + const updateSpring = (values: number[]) => { |
| 265 | + requestAnimationFrame(() => { |
| 266 | + springPositions.set( |
| 267 | + values.map((v) => valueAsPercent(v, min, max)), |
| 268 | + { hard: !spring } |
| 269 | + ); |
| 270 | + }); |
| 271 | + }; |
252 | 272 |
|
253 | 273 | /** |
254 | 274 | * the orientation of the handles/pips based on the |
|
792 | 812 | class:rsFocus={focus} |
793 | 813 | class:rsPips={pips} |
794 | 814 | class:rsPipLabels={all === 'label' || first === 'label' || last === 'label' || rest === 'label'} |
795 | | - style:--slider-length={sliderSize} |
796 | | - {style} |
| 815 | + style={`--slider-length: ${sliderSize};${style ?? ''}`} |
797 | 816 | on:mousedown={sliderInteractStart} |
798 | 817 | on:mouseup={sliderInteractEnd} |
799 | 818 | on:touchstart|preventDefault={sliderInteractStart} |
800 | 819 | on:touchend|preventDefault={sliderInteractEnd} |
801 | 820 | > |
802 | 821 | {#each values as value, index} |
803 | | - {@const zindex = `${focus && activeHandle === index ? 3 : ''}`} |
| 822 | + {@const zindex = focus && activeHandle === index ? `z-index: 3; ` : ``} |
| 823 | + {@const mountOpacity = isMounted ? `` : `opacity: 0; `} |
804 | 824 | <span |
805 | 825 | role="slider" |
806 | 826 | class="rangeHandle" |
|
810 | 830 | on:blur={sliderBlurHandle} |
811 | 831 | on:focus={sliderFocusHandle} |
812 | 832 | on:keydown={sliderKeydown} |
813 | | - style:--handle-pos={$springPositions[index]} |
814 | | - style="z-index: {zindex}; {isMounted ? '' : 'opacity: 0;'}" |
| 833 | + style={`--handle-pos: ${$springPositions[index]};${zindex}${mountOpacity}`} |
815 | 834 | aria-label={ariaLabels[index]} |
816 | 835 | aria-valuemin={range === true && index === 1 ? values[0] : min} |
817 | 836 | aria-valuemax={range === true && index === 0 ? values[1] : max} |
|
841 | 860 | ></span> |
842 | 861 | {/if} |
843 | 862 | {#if hasRange} |
| 863 | + {@const rangeStart = rangeStartPercent($springPositions)} |
| 864 | + {@const rangeEnd = rangeEndPercent($springPositions)} |
| 865 | + {@const rangeSize = rangeEnd - rangeStart} |
| 866 | + {@const mountOpacity = isMounted ? `` : `opacity: 0; `} |
844 | 867 | <span |
845 | 868 | class="rangeBar" |
846 | 869 | class:rsPress={rangePressed} |
847 | | - style:--range-start={rangeStartPercent($springPositions)} |
848 | | - style:--range-end={rangeEndPercent($springPositions)} |
849 | | - style:--range-size={rangeEndPercent($springPositions) - rangeStartPercent($springPositions)} |
850 | | - style={isMounted ? '' : 'opacity: 0;'} |
| 870 | + style={`--range-start:${rangeStart};--range-end:${rangeEnd};--range-size:${rangeSize};${mountOpacity};`} |
851 | 871 | > |
852 | 872 | {#if rangeFloat} |
853 | 873 | <span class="rangeFloat"> |
|
0 commit comments