UNPKG

react-native-a11y-slider

Version:

An accessible range slider that supports assistive devices like screen readers

322 lines (321 loc) 13.6 kB
var __rest = (this && this.__rest) || function (s, e) { var t = {}; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; } return t; }; import React, { useState, useCallback, useEffect, useMemo, } from "react"; import { View, StyleSheet, } from "react-native"; import { MarkerType, SliderType, } from "./types"; import GestureContainer from "./GestureContainer"; import Marker from "./Marker"; export default function Slider(_a) { var min = _a.min, max = _a.max, values = _a.values, sliderValues = _a.sliderValues, _b = _a.markerColor, markerColor = _b === void 0 ? "#333" : _b, _c = _a.showLabel, showLabel = _c === void 0 ? true : _c, style = _a.style, trackStyle = _a.trackStyle, selectedTrackStyle = _a.selectedTrackStyle, labelStyle = _a.labelStyle, labelTextStyle = _a.labelTextStyle, _d = _a.increment, increment = _d === void 0 ? 1 : _d, hitSlop = _a.hitSlop, onChange = _a.onChange, onSlidingStart = _a.onSlidingStart, onSlidingComplete = _a.onSlidingComplete, labelComponent = _a.labelComponent, setA11yMarkerProps = _a.setA11yMarkerProps, _e = _a.markerComponent, markerComponent = _e === void 0 ? Marker : _e, accessibilityProps = __rest(_a, ["min", "max", "values", "sliderValues", "markerColor", "showLabel", "style", "trackStyle", "selectedTrackStyle", "labelStyle", "labelTextStyle", "increment", "hitSlop", "onChange", "onSlidingStart", "onSlidingComplete", "labelComponent", "setA11yMarkerProps", "markerComponent"]); var _f = useState(0), sliderWidth = _f[0], setSliderWidth = _f[1]; var _g = useState(0), lowerIndex = _g[0], setLowerIndexState = _g[1]; var _h = useState(), upperIndex = _h[0], setUpperIndexState = _h[1]; var _j = useState([]), stops = _j[0], setStops = _j[1]; var markerCount = values.length; var hasUpperIndex = typeof upperIndex === "number"; var sliderType = values.length > 1 ? SliderType.RANGE : SliderType.SINGLE; /** * Set the absolute upper and lower boundaries for each marker thumb so they cannot get * dragged past one another. */ var _k = useMemo(function () { if (!stops.length || typeof lowerIndex === "undefined") { return []; } var maxIdx = stops.length - 1; return [ // Lower boundaries { min: stops[0].px, max: stops[hasUpperIndex ? upperIndex - 1 : maxIdx].px, }, // Upper boundaries { min: stops[hasUpperIndex ? lowerIndex + 1 : 0].pxInverse, max: stops[maxIdx].pxInverse, }, ]; }, [stops, lowerIndex, hasUpperIndex, upperIndex]), lowerPanBoundaries = _k[0], upperPanBoundaries = _k[1]; /** * Adjust the track to be centered on the markers */ var trackPlacement = useMemo(function () { var margin = 15; if (typeof (markerComponent === null || markerComponent === void 0 ? void 0 : markerComponent.size) !== "undefined") { margin = (markerComponent === null || markerComponent === void 0 ? void 0 : markerComponent.size) / 2; } return { bottom: margin, left: margin, right: margin, }; }, [markerComponent]); /** * Get the coordinates for the selected part of the track. * This is the track that is highlighted between the lower and upper marker thumb. */ var selectedTrackCoordinates = useMemo(function () { var coords = { left: 0, right: 0, }; if (!(stops === null || stops === void 0 ? void 0 : stops.length) || typeof lowerIndex === "undefined") { return {}; } var lowerPosition = stops[lowerIndex]; var upperPosition = hasUpperIndex ? stops[upperIndex] : null; if (upperPosition) { coords.left = lowerPosition.px; coords.right = sliderWidth - upperPosition.px; } else if (lowerPosition) { coords.right = sliderWidth - lowerPosition.px; } else { return {}; } return coords; }, [stops, lowerIndex, hasUpperIndex, upperIndex, sliderWidth]); /** * Fire the onChange handler */ var fireChange = useCallback(function (lowerIdx, upperIdx) { var _a; if (typeof onChange !== "function") { return; } var changedValues = [stops[lowerIdx].value]; if (hasUpperIndex && typeof upperIdx !== "undefined") { changedValues.push((_a = stops[upperIdx]) === null || _a === void 0 ? void 0 : _a.value); } onChange(changedValues); }, [onChange, stops, hasUpperIndex]); /** * Get the slider width and calculate the slider stops */ var defineSliderScale = useCallback(function (event) { var width = event.nativeEvent.layout.width; var hasMinMax = typeof min === "number" && typeof max === "number"; var stopValues = []; var valuePositionMap = {}; // Calculate stop count and width-per-step var stopCount = 0; if (sliderValues === null || sliderValues === void 0 ? void 0 : sliderValues.length) { stopCount = (sliderValues === null || sliderValues === void 0 ? void 0 : sliderValues.length) - 1; } else if (hasMinMax) { stopCount = (max - min) / increment; } var widthPerStep = width / stopCount; var calcPx = function (index) { var px = Math.round(index * widthPerStep); if (px < 0) { px = 0; } else if (px > width) { px = width; } return px; }; // If the stop values were passed in if (sliderValues === null || sliderValues === void 0 ? void 0 : sliderValues.length) { for (var i = 0; i < sliderValues.length; i++) { var value = sliderValues[i]; var px = calcPx(i); var pxInverse = px - width; stopValues[i] = { value: value, index: i, px: px, pxInverse: pxInverse, }; valuePositionMap[value] = stopValues[i]; } } // Calculate from a min/max else if (hasMinMax) { for (var i = 0, value = min; value <= max; i++, value += increment) { var px = calcPx(i); var pxInverse = px - width; stopValues[i] = { value: value, index: i, px: px, pxInverse: pxInverse, }; valuePositionMap[value] = stopValues[i]; } } setStops(stopValues); setSliderWidth(width); }, [sliderValues, max, min, increment]); /** * Set lower index and be sure it is within the bounds. * Push the upper index, if necessary */ var onSetLowerIndex = useCallback(function (idx, pushUpper) { if (pushUpper === void 0) { pushUpper = false; } var newUpperIndex = upperIndex || 0; // Push upper value, if necessary if (pushUpper && hasUpperIndex && idx >= upperIndex) { if (upperIndex < stops.length - 1) { newUpperIndex++; setUpperIndexState(newUpperIndex); } else { return; // cannot push if upper is already at the end } } // Cannot go above upper value if (hasUpperIndex && newUpperIndex && idx >= newUpperIndex) { return; } var maxIdx = stops.length - 1; if (idx <= 0) { idx = 0; } else if (idx >= maxIdx) { idx = maxIdx; } setLowerIndexState(idx); fireChange(idx, newUpperIndex); }, [upperIndex, hasUpperIndex, stops === null || stops === void 0 ? void 0 : stops.length, fireChange]); /** * Set upper value */ var onSetUpperIndex = useCallback(function (idx, pushLower) { if (pushLower === void 0) { pushLower = false; } var newLowerIndex = lowerIndex; // Push lower value, if necessary if (pushLower && idx <= lowerIndex && idx >= 0) { if (lowerIndex > 0) { newLowerIndex--; setLowerIndexState(newLowerIndex); } else { return; // cannot push if lower index is already at the start } } // Cannot go below lower value if (idx <= newLowerIndex) { return; } var maxIdx = stops.length - 1; if (idx >= maxIdx) { idx = maxIdx; } else if (idx < 1) { idx = 1; } setUpperIndexState(idx); fireChange(newLowerIndex, idx); }, [lowerIndex, stops === null || stops === void 0 ? void 0 : stops.length, fireChange]); /** * Set the stop values from props */ useEffect(function () { if (!(values === null || values === void 0 ? void 0 : values.length) || !stops.length) { return; } var lower = values[0], upper = values[1]; var hasUpperValue = typeof upper !== "undefined"; var lowerIdx = null; var upperIdx = null; // Find the position index for each value for (var i = 0; i < stops.length; i++) { var position = stops[i]; if (lowerIdx === null) { if (position.value === lower) { lowerIdx = i; } } else if (hasUpperValue) { if (position.value === upper) { upperIdx = i; } } else { break; } } if (lowerIdx === null) { lowerIdx = 0; } setLowerIndexState(lowerIdx); if (hasUpperValue) { if (upperIdx === null) { upperIdx = stops.length - 1; } setUpperIndexState(upperIdx); } }, [values, stops]); /** * Style memoizing */ var wrapperStyles = useMemo(function () { return [styles.wrapper, style]; }, [styles.wrapper, style]); var trackWrapperStyles = useMemo(function () { return [styles.trackContainer, trackPlacement]; }, [styles.trackContainer, trackPlacement]); var trackStyles = useMemo(function () { return [styles.track, trackStyle]; }, [styles.track, trackStyle]); var trackSelectedStyles = useMemo(function () { return [styles.selectedTrack, selectedTrackCoordinates, selectedTrackStyle]; }, [styles.selectedTrack, selectedTrackCoordinates, selectedTrackStyle]); return (<View style={wrapperStyles}> <View style={styles.container}> {/* Thumb markers */} <View style={styles.markerContainer}> {typeof lowerIndex === "number" && stops && stops[lowerIndex] && (<GestureContainer markerCount={markerCount} type={MarkerType.LOWER} sliderType={sliderType} minValue={min} maxValue={max} position={stops[lowerIndex]} stops={stops} panBoundaries={lowerPanBoundaries} showLabel={showLabel} labelComponent={labelComponent} markerComponent={markerComponent} markerColor={markerColor} setIndex={onSetLowerIndex} setA11yMarkerProps={setA11yMarkerProps} onSlidingStart={onSlidingStart} onSlidingComplete={onSlidingComplete} hitSlop={hitSlop} labelStyle={labelStyle} labelTextStyle={labelTextStyle} {...accessibilityProps}/>)} {hasUpperIndex && stops && stops[upperIndex] && (<GestureContainer markerCount={markerCount} type={MarkerType.UPPER} sliderType={sliderType} minValue={min} maxValue={max} position={stops[upperIndex]} stops={stops} panBoundaries={upperPanBoundaries} showLabel={showLabel} labelComponent={labelComponent} markerComponent={markerComponent} markerColor={markerColor} setIndex={onSetUpperIndex} setA11yMarkerProps={setA11yMarkerProps} onSlidingStart={onSlidingStart} onSlidingComplete={onSlidingComplete} hitSlop={hitSlop} labelStyle={labelStyle} labelTextStyle={labelTextStyle} {...accessibilityProps}/>)} </View> <View style={trackWrapperStyles}> {/* Full track */} <View style={trackStyles} onLayout={defineSliderScale}/> {/* Selected track */} <View style={trackSelectedStyles}/> </View> </View> </View>); } var styles = StyleSheet.create({ wrapper: { flex: 0, width: "100%", }, container: { position: "relative", }, trackContainer: { position: "absolute", bottom: 0, left: 0, right: 0, zIndex: 0, }, track: { height: 1, width: "100%", borderBottomWidth: 2, borderColor: "#999", }, selectedTrack: { flex: 1, height: 1, position: "absolute", left: 0, bottom: 0, zIndex: 0, borderBottomWidth: 2, borderColor: "#333", }, markerContainer: { flexDirection: "row", zIndex: 1, justifyContent: "space-between", }, }); //# sourceMappingURL=Slider.js.map