react-native-a11y-slider
Version:
An accessible range slider that supports assistive devices like screen readers
322 lines (321 loc) • 13.6 kB
JavaScript
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