@react-aria/slider
Advanced tools
Comparing version 3.0.0-nightly.2414 to 3.0.0-nightly.2416
265
dist/main.js
@@ -6,2 +6,10 @@ var { | ||
var { | ||
useMove | ||
} = require("@react-aria/interactions"); | ||
var { | ||
useLocale | ||
} = require("@react-aria/i18n"); | ||
var { | ||
useLabel | ||
@@ -11,8 +19,2 @@ } = require("@react-aria/label"); | ||
var { | ||
mergeProps, | ||
useDrag1D, | ||
focusWithoutScrolling | ||
} = require("@react-aria/utils"); | ||
var { | ||
useRef, | ||
@@ -23,2 +25,9 @@ useCallback, | ||
var { | ||
clamp, | ||
mergeProps, | ||
useGlobalListeners, | ||
focusWithoutScrolling | ||
} = require("@react-aria/utils"); | ||
var _babelRuntimeHelpersExtends = $parcel$interopDefault(require("@babel/runtime/helpers/extends")); | ||
@@ -48,41 +57,97 @@ | ||
fieldProps | ||
} = useLabel(props); | ||
const isSliderEditable = !(props.isDisabled || props.isReadOnly); // Attach id of the label to the state so it can be accessed by useSliderThumb. | ||
} = useLabel(props); // Attach id of the label to the state so it can be accessed by useSliderThumb. | ||
$dec1906781d9c7cb69245fc4d5344b$export$sliderIds.set(state, (_labelProps$id = labelProps.id) != null ? _labelProps$id : fieldProps.id); // When the user clicks or drags the track, we want the motion to set and drag the | ||
// closest thumb. Hence we also need to install useDrag1D() on the track element. | ||
$dec1906781d9c7cb69245fc4d5344b$export$sliderIds.set(state, (_labelProps$id = labelProps.id) != null ? _labelProps$id : fieldProps.id); | ||
let { | ||
direction | ||
} = useLocale(); | ||
let { | ||
addGlobalListener, | ||
removeGlobalListener | ||
} = useGlobalListeners(); // When the user clicks or drags the track, we want the motion to set and drag the | ||
// closest thumb. Hence we also need to install useMove() on the track element. | ||
// Here, we keep track of which index is the "closest" to the drag start point. | ||
// It is set onMouseDown; see trackProps below. | ||
// It is set onMouseDown/onTouchDown; see trackProps below. | ||
const realTimeTrackDraggingIndex = useRef(undefined); | ||
const isTrackDragging = useRef(false); | ||
const { | ||
onMouseDown, | ||
onMouseEnter, | ||
onMouseOut | ||
} = useDrag1D({ | ||
containerRef: trackRef, | ||
reverse: false, | ||
orientation: 'horizontal', | ||
onDrag: dragging => { | ||
if (realTimeTrackDraggingIndex.current !== undefined) { | ||
state.setThumbDragging(realTimeTrackDraggingIndex.current, dragging); | ||
const realTimeTrackDraggingIndex = useRef(null); | ||
const stateRef = useRef(null); | ||
stateRef.current = state; | ||
const reverseX = direction === 'rtl'; | ||
const currentPosition = useRef(null); | ||
const moveProps = useMove({ | ||
onMoveStart() { | ||
currentPosition.current = null; | ||
}, | ||
onMove(_ref) { | ||
let { | ||
deltaX | ||
} = _ref; | ||
if (currentPosition.current == null) { | ||
currentPosition.current = stateRef.current.getThumbPercent(realTimeTrackDraggingIndex.current) * trackRef.current.offsetWidth; | ||
} | ||
isTrackDragging.current = dragging; | ||
currentPosition.current += reverseX ? -deltaX : deltaX; | ||
if (realTimeTrackDraggingIndex.current != null && trackRef.current) { | ||
const percent = clamp(currentPosition.current / trackRef.current.offsetWidth, 0, 1); | ||
stateRef.current.setThumbPercent(realTimeTrackDraggingIndex.current, percent); | ||
} | ||
}, | ||
onPositionChange: position => { | ||
if (realTimeTrackDraggingIndex.current !== undefined && trackRef.current) { | ||
const percent = position / trackRef.current.offsetWidth; | ||
state.setThumbPercent(realTimeTrackDraggingIndex.current, percent); // When track-dragging ends, onDrag is called before a final onPositionChange is | ||
// called, so we can't reset realTimeTrackDraggingIndex until onPositionChange, | ||
// as we still needed to update the thumb position one last time. Hence we | ||
// track whether we're dragging, and the actual dragged index, separately. | ||
if (!isTrackDragging.current) { | ||
realTimeTrackDraggingIndex.current = undefined; | ||
} | ||
onMoveEnd() { | ||
if (realTimeTrackDraggingIndex.current != null) { | ||
stateRef.current.setThumbDragging(realTimeTrackDraggingIndex.current, false); | ||
realTimeTrackDraggingIndex.current = null; | ||
} | ||
} | ||
}); | ||
let onDownTrack = (e, clientX) => { | ||
// We only trigger track-dragging if the user clicks on the track itself and nothing is currently being dragged. | ||
if (trackRef.current && !props.isDisabled && state.values.every((_, i) => !state.isThumbDragging(i))) { | ||
// Find the closest thumb | ||
const trackPosition = trackRef.current.getBoundingClientRect().left; | ||
const clickPosition = clientX; | ||
const offset = clickPosition - trackPosition; | ||
let percent = offset / trackRef.current.offsetWidth; | ||
if (direction === 'rtl') { | ||
percent = 1 - percent; | ||
} | ||
let value = state.getPercentValue(percent); // Only compute the diff for thumbs that are editable, as only they can be dragged | ||
const minDiff = Math.min(...state.values.map((v, index) => state.isThumbEditable(index) ? Math.abs(v - value) : Number.POSITIVE_INFINITY)); | ||
const index = state.values.findIndex(v => Math.abs(v - value) === minDiff); | ||
if (minDiff !== Number.POSITIVE_INFINITY && index >= 0) { | ||
// Don't unfocus anything | ||
e.preventDefault(); | ||
realTimeTrackDraggingIndex.current = index; | ||
state.setFocusedThumb(index); | ||
state.setThumbDragging(realTimeTrackDraggingIndex.current, true); | ||
state.setThumbValue(index, value); | ||
addGlobalListener(window, 'mouseup', onUpTrack, false); | ||
addGlobalListener(window, 'touchend', onUpTrack, false); | ||
addGlobalListener(window, 'pointerup', onUpTrack, false); | ||
} else { | ||
realTimeTrackDraggingIndex.current = null; | ||
} | ||
} | ||
}; | ||
let onUpTrack = () => { | ||
if (realTimeTrackDraggingIndex.current != null) { | ||
state.setThumbDragging(realTimeTrackDraggingIndex.current, false); | ||
realTimeTrackDraggingIndex.current = null; | ||
} | ||
removeGlobalListener(window, 'mouseup', onUpTrack, false); | ||
removeGlobalListener(window, 'touchend', onUpTrack, false); | ||
removeGlobalListener(window, 'pointerup', onUpTrack, false); | ||
}; | ||
return { | ||
@@ -97,38 +162,15 @@ labelProps, | ||
trackProps: mergeProps({ | ||
onMouseDown: e => { | ||
// We only trigger track-dragging if the user clicks on the track itself. | ||
if (trackRef.current && isSliderEditable) { | ||
// Find the closest thumb | ||
const trackPosition = trackRef.current.getBoundingClientRect().left; | ||
const clickPosition = e.clientX; | ||
const offset = clickPosition - trackPosition; | ||
const percent = offset / trackRef.current.offsetWidth; | ||
const value = state.getPercentValue(percent); // Only compute the diff for thumbs that are editable, as only they can be dragged | ||
onMouseDown(e) { | ||
onDownTrack(e, e.clientX); | ||
}, | ||
const minDiff = Math.min(...state.values.map((v, index) => state.isThumbEditable(index) ? Math.abs(v - value) : Number.POSITIVE_INFINITY)); | ||
const index = state.values.findIndex(v => Math.abs(v - value) === minDiff); | ||
onPointerDown(e) { | ||
onDownTrack(e, e.clientX); | ||
}, | ||
if (minDiff !== Number.POSITIVE_INFINITY && index >= 0) { | ||
// Don't unfocus anything | ||
e.preventDefault(); | ||
realTimeTrackDraggingIndex.current = index; | ||
state.setFocusedThumb(index); // We immediately toggle state to dragging and set the value on mouse down. | ||
// We set the value now, instead of waiting for onDrag, so that the thumb | ||
// is updated while you're still holding the mouse button down. And we | ||
// set dragging on now, so that onChangeEnd() won't fire yet when we set | ||
// the value. Dragging state will be reset to false in onDrag above, even | ||
// if no dragging actually occurs. | ||
onTouchStart(e) { | ||
onDownTrack(e, e.targetTouches[0].clientX); | ||
} | ||
state.setThumbDragging(realTimeTrackDraggingIndex.current, true); | ||
state.setThumbValue(index, value); | ||
} else { | ||
realTimeTrackDraggingIndex.current = undefined; | ||
} | ||
} | ||
} | ||
}, { | ||
onMouseDown, | ||
onMouseEnter, | ||
onMouseOut | ||
}) | ||
}, moveProps) | ||
}; | ||
@@ -152,3 +194,2 @@ } | ||
isDisabled, | ||
isReadOnly, | ||
validationState, | ||
@@ -158,2 +199,9 @@ trackRef, | ||
} = opts; | ||
let { | ||
direction | ||
} = useLocale(); | ||
let { | ||
addGlobalListener, | ||
removeGlobalListener | ||
} = useGlobalListeners(); | ||
let labelId = $dec1906781d9c7cb69245fc4d5344b$export$sliderIds.get(state); | ||
@@ -167,3 +215,2 @@ const { | ||
const value = state.values[index]; | ||
const isEditable = !(isDisabled || isReadOnly); | ||
const focusInput = useCallback(() => { | ||
@@ -180,17 +227,41 @@ if (inputRef.current) { | ||
}, [isFocused, focusInput]); | ||
const draggableProps = useDrag1D({ | ||
containerRef: trackRef, | ||
reverse: false, | ||
orientation: 'horizontal', | ||
onDrag: dragging => { | ||
state.setThumbDragging(index, dragging); | ||
focusInput(); | ||
const stateRef = useRef(null); | ||
stateRef.current = state; | ||
let reverseX = direction === 'rtl'; | ||
let currentPosition = useRef(null); | ||
let moveProps = useMove({ | ||
onMoveStart() { | ||
currentPosition.current = null; | ||
state.setThumbDragging(index, true); | ||
}, | ||
onPositionChange: position => { | ||
const percent = position / trackRef.current.offsetWidth; | ||
state.setThumbPercent(index, percent); | ||
onMove(_ref) { | ||
let { | ||
deltaX, | ||
deltaY, | ||
pointerType | ||
} = _ref; | ||
if (currentPosition.current == null) { | ||
currentPosition.current = stateRef.current.getThumbPercent(index) * trackRef.current.offsetWidth; | ||
} | ||
if (pointerType === 'keyboard') { | ||
// (invert left/right according to language direction) + (up should always increase) | ||
let delta = ((reverseX ? -deltaX : deltaX) + -deltaY) * stateRef.current.step; | ||
currentPosition.current += delta * trackRef.current.offsetWidth; | ||
stateRef.current.setThumbValue(index, stateRef.current.getThumbValue(index) + delta); | ||
} else { | ||
currentPosition.current += reverseX ? -deltaX : deltaX; | ||
stateRef.current.setThumbPercent(index, clamp(currentPosition.current / trackRef.current.offsetWidth, 0, 1)); | ||
} | ||
}, | ||
onMoveEnd() { | ||
state.setThumbDragging(index, false); | ||
} | ||
}); // Immediately register editability with the state | ||
state.setThumbEditable(index, isEditable); | ||
state.setThumbEditable(index, !isDisabled); | ||
const { | ||
@@ -201,3 +272,19 @@ focusableProps | ||
onBlur: () => state.setFocusedThumb(undefined) | ||
}), inputRef); // We install mouse handlers for the drag motion on the thumb div, but | ||
}), inputRef); | ||
let onDown = () => { | ||
focusInput(); | ||
state.setThumbDragging(index, true); | ||
addGlobalListener(window, 'mouseup', onUp, false); | ||
addGlobalListener(window, 'touchend', onUp, false); | ||
addGlobalListener(window, 'pointerup', onUp, false); | ||
}; | ||
let onUp = () => { | ||
focusInput(); | ||
state.setThumbDragging(index, false); | ||
removeGlobalListener(window, 'mouseup', onUp, false); | ||
removeGlobalListener(window, 'touchend', onUp, false); | ||
removeGlobalListener(window, 'pointerup', onUp, false); | ||
}; // We install mouse handlers for the drag motion on the thumb div, but | ||
// not the key handler for moving the thumb with the slider. Instead, | ||
@@ -207,6 +294,7 @@ // we focus the range input, and let the browser handle the keyboard | ||
return { | ||
inputProps: mergeProps(focusableProps, fieldProps, { | ||
type: 'range', | ||
tabIndex: isEditable ? 0 : undefined, | ||
tabIndex: !isDisabled ? 0 : undefined, | ||
min: state.getThumbMinValue(index), | ||
@@ -216,3 +304,2 @@ max: state.getThumbMaxValue(index), | ||
value: value, | ||
readOnly: isReadOnly, | ||
disabled: isDisabled, | ||
@@ -228,8 +315,6 @@ 'aria-orientation': 'horizontal', | ||
}), | ||
thumbProps: isEditable ? mergeProps({ | ||
onMouseDown: draggableProps.onMouseDown, | ||
onMouseEnter: draggableProps.onMouseEnter, | ||
onMouseOut: draggableProps.onMouseOut | ||
}, { | ||
onMouseDown: focusInput | ||
thumbProps: !isDisabled ? mergeProps(moveProps, { | ||
onPointerDown: onDown, | ||
onMouseDown: onDown, | ||
onTouchStart: onDown | ||
}) : {}, | ||
@@ -236,0 +321,0 @@ labelProps |
import { useFocusable } from "@react-aria/focus"; | ||
import { useMove } from "@react-aria/interactions"; | ||
import { useLocale } from "@react-aria/i18n"; | ||
import { useLabel } from "@react-aria/label"; | ||
import { mergeProps, useDrag1D, focusWithoutScrolling } from "@react-aria/utils"; | ||
import { useRef, useCallback, useEffect } from "react"; | ||
import { clamp, mergeProps, useGlobalListeners, focusWithoutScrolling } from "@react-aria/utils"; | ||
import _babelRuntimeHelpersEsmExtends from "@babel/runtime/helpers/esm/extends"; | ||
@@ -24,41 +26,97 @@ const $d20491ae7743da17cfa841ff6d87$export$sliderIds = new WeakMap(); | ||
fieldProps | ||
} = useLabel(props); | ||
const isSliderEditable = !(props.isDisabled || props.isReadOnly); // Attach id of the label to the state so it can be accessed by useSliderThumb. | ||
} = useLabel(props); // Attach id of the label to the state so it can be accessed by useSliderThumb. | ||
$d20491ae7743da17cfa841ff6d87$export$sliderIds.set(state, (_labelProps$id = labelProps.id) != null ? _labelProps$id : fieldProps.id); // When the user clicks or drags the track, we want the motion to set and drag the | ||
// closest thumb. Hence we also need to install useDrag1D() on the track element. | ||
$d20491ae7743da17cfa841ff6d87$export$sliderIds.set(state, (_labelProps$id = labelProps.id) != null ? _labelProps$id : fieldProps.id); | ||
let { | ||
direction | ||
} = useLocale(); | ||
let { | ||
addGlobalListener, | ||
removeGlobalListener | ||
} = useGlobalListeners(); // When the user clicks or drags the track, we want the motion to set and drag the | ||
// closest thumb. Hence we also need to install useMove() on the track element. | ||
// Here, we keep track of which index is the "closest" to the drag start point. | ||
// It is set onMouseDown; see trackProps below. | ||
// It is set onMouseDown/onTouchDown; see trackProps below. | ||
const realTimeTrackDraggingIndex = useRef(undefined); | ||
const isTrackDragging = useRef(false); | ||
const { | ||
onMouseDown, | ||
onMouseEnter, | ||
onMouseOut | ||
} = useDrag1D({ | ||
containerRef: trackRef, | ||
reverse: false, | ||
orientation: 'horizontal', | ||
onDrag: dragging => { | ||
if (realTimeTrackDraggingIndex.current !== undefined) { | ||
state.setThumbDragging(realTimeTrackDraggingIndex.current, dragging); | ||
const realTimeTrackDraggingIndex = useRef(null); | ||
const stateRef = useRef(null); | ||
stateRef.current = state; | ||
const reverseX = direction === 'rtl'; | ||
const currentPosition = useRef(null); | ||
const moveProps = useMove({ | ||
onMoveStart() { | ||
currentPosition.current = null; | ||
}, | ||
onMove(_ref) { | ||
let { | ||
deltaX | ||
} = _ref; | ||
if (currentPosition.current == null) { | ||
currentPosition.current = stateRef.current.getThumbPercent(realTimeTrackDraggingIndex.current) * trackRef.current.offsetWidth; | ||
} | ||
isTrackDragging.current = dragging; | ||
currentPosition.current += reverseX ? -deltaX : deltaX; | ||
if (realTimeTrackDraggingIndex.current != null && trackRef.current) { | ||
const percent = clamp(currentPosition.current / trackRef.current.offsetWidth, 0, 1); | ||
stateRef.current.setThumbPercent(realTimeTrackDraggingIndex.current, percent); | ||
} | ||
}, | ||
onPositionChange: position => { | ||
if (realTimeTrackDraggingIndex.current !== undefined && trackRef.current) { | ||
const percent = position / trackRef.current.offsetWidth; | ||
state.setThumbPercent(realTimeTrackDraggingIndex.current, percent); // When track-dragging ends, onDrag is called before a final onPositionChange is | ||
// called, so we can't reset realTimeTrackDraggingIndex until onPositionChange, | ||
// as we still needed to update the thumb position one last time. Hence we | ||
// track whether we're dragging, and the actual dragged index, separately. | ||
if (!isTrackDragging.current) { | ||
realTimeTrackDraggingIndex.current = undefined; | ||
} | ||
onMoveEnd() { | ||
if (realTimeTrackDraggingIndex.current != null) { | ||
stateRef.current.setThumbDragging(realTimeTrackDraggingIndex.current, false); | ||
realTimeTrackDraggingIndex.current = null; | ||
} | ||
} | ||
}); | ||
let onDownTrack = (e, clientX) => { | ||
// We only trigger track-dragging if the user clicks on the track itself and nothing is currently being dragged. | ||
if (trackRef.current && !props.isDisabled && state.values.every((_, i) => !state.isThumbDragging(i))) { | ||
// Find the closest thumb | ||
const trackPosition = trackRef.current.getBoundingClientRect().left; | ||
const clickPosition = clientX; | ||
const offset = clickPosition - trackPosition; | ||
let percent = offset / trackRef.current.offsetWidth; | ||
if (direction === 'rtl') { | ||
percent = 1 - percent; | ||
} | ||
let value = state.getPercentValue(percent); // Only compute the diff for thumbs that are editable, as only they can be dragged | ||
const minDiff = Math.min(...state.values.map((v, index) => state.isThumbEditable(index) ? Math.abs(v - value) : Number.POSITIVE_INFINITY)); | ||
const index = state.values.findIndex(v => Math.abs(v - value) === minDiff); | ||
if (minDiff !== Number.POSITIVE_INFINITY && index >= 0) { | ||
// Don't unfocus anything | ||
e.preventDefault(); | ||
realTimeTrackDraggingIndex.current = index; | ||
state.setFocusedThumb(index); | ||
state.setThumbDragging(realTimeTrackDraggingIndex.current, true); | ||
state.setThumbValue(index, value); | ||
addGlobalListener(window, 'mouseup', onUpTrack, false); | ||
addGlobalListener(window, 'touchend', onUpTrack, false); | ||
addGlobalListener(window, 'pointerup', onUpTrack, false); | ||
} else { | ||
realTimeTrackDraggingIndex.current = null; | ||
} | ||
} | ||
}; | ||
let onUpTrack = () => { | ||
if (realTimeTrackDraggingIndex.current != null) { | ||
state.setThumbDragging(realTimeTrackDraggingIndex.current, false); | ||
realTimeTrackDraggingIndex.current = null; | ||
} | ||
removeGlobalListener(window, 'mouseup', onUpTrack, false); | ||
removeGlobalListener(window, 'touchend', onUpTrack, false); | ||
removeGlobalListener(window, 'pointerup', onUpTrack, false); | ||
}; | ||
return { | ||
@@ -73,38 +131,15 @@ labelProps, | ||
trackProps: mergeProps({ | ||
onMouseDown: e => { | ||
// We only trigger track-dragging if the user clicks on the track itself. | ||
if (trackRef.current && isSliderEditable) { | ||
// Find the closest thumb | ||
const trackPosition = trackRef.current.getBoundingClientRect().left; | ||
const clickPosition = e.clientX; | ||
const offset = clickPosition - trackPosition; | ||
const percent = offset / trackRef.current.offsetWidth; | ||
const value = state.getPercentValue(percent); // Only compute the diff for thumbs that are editable, as only they can be dragged | ||
onMouseDown(e) { | ||
onDownTrack(e, e.clientX); | ||
}, | ||
const minDiff = Math.min(...state.values.map((v, index) => state.isThumbEditable(index) ? Math.abs(v - value) : Number.POSITIVE_INFINITY)); | ||
const index = state.values.findIndex(v => Math.abs(v - value) === minDiff); | ||
onPointerDown(e) { | ||
onDownTrack(e, e.clientX); | ||
}, | ||
if (minDiff !== Number.POSITIVE_INFINITY && index >= 0) { | ||
// Don't unfocus anything | ||
e.preventDefault(); | ||
realTimeTrackDraggingIndex.current = index; | ||
state.setFocusedThumb(index); // We immediately toggle state to dragging and set the value on mouse down. | ||
// We set the value now, instead of waiting for onDrag, so that the thumb | ||
// is updated while you're still holding the mouse button down. And we | ||
// set dragging on now, so that onChangeEnd() won't fire yet when we set | ||
// the value. Dragging state will be reset to false in onDrag above, even | ||
// if no dragging actually occurs. | ||
onTouchStart(e) { | ||
onDownTrack(e, e.targetTouches[0].clientX); | ||
} | ||
state.setThumbDragging(realTimeTrackDraggingIndex.current, true); | ||
state.setThumbValue(index, value); | ||
} else { | ||
realTimeTrackDraggingIndex.current = undefined; | ||
} | ||
} | ||
} | ||
}, { | ||
onMouseDown, | ||
onMouseEnter, | ||
onMouseOut | ||
}) | ||
}, moveProps) | ||
}; | ||
@@ -126,3 +161,2 @@ } | ||
isDisabled, | ||
isReadOnly, | ||
validationState, | ||
@@ -132,2 +166,9 @@ trackRef, | ||
} = opts; | ||
let { | ||
direction | ||
} = useLocale(); | ||
let { | ||
addGlobalListener, | ||
removeGlobalListener | ||
} = useGlobalListeners(); | ||
let labelId = $d20491ae7743da17cfa841ff6d87$export$sliderIds.get(state); | ||
@@ -141,3 +182,2 @@ const { | ||
const value = state.values[index]; | ||
const isEditable = !(isDisabled || isReadOnly); | ||
const focusInput = useCallback(() => { | ||
@@ -154,17 +194,41 @@ if (inputRef.current) { | ||
}, [isFocused, focusInput]); | ||
const draggableProps = useDrag1D({ | ||
containerRef: trackRef, | ||
reverse: false, | ||
orientation: 'horizontal', | ||
onDrag: dragging => { | ||
state.setThumbDragging(index, dragging); | ||
focusInput(); | ||
const stateRef = useRef(null); | ||
stateRef.current = state; | ||
let reverseX = direction === 'rtl'; | ||
let currentPosition = useRef(null); | ||
let moveProps = useMove({ | ||
onMoveStart() { | ||
currentPosition.current = null; | ||
state.setThumbDragging(index, true); | ||
}, | ||
onPositionChange: position => { | ||
const percent = position / trackRef.current.offsetWidth; | ||
state.setThumbPercent(index, percent); | ||
onMove(_ref) { | ||
let { | ||
deltaX, | ||
deltaY, | ||
pointerType | ||
} = _ref; | ||
if (currentPosition.current == null) { | ||
currentPosition.current = stateRef.current.getThumbPercent(index) * trackRef.current.offsetWidth; | ||
} | ||
if (pointerType === 'keyboard') { | ||
// (invert left/right according to language direction) + (up should always increase) | ||
let delta = ((reverseX ? -deltaX : deltaX) + -deltaY) * stateRef.current.step; | ||
currentPosition.current += delta * trackRef.current.offsetWidth; | ||
stateRef.current.setThumbValue(index, stateRef.current.getThumbValue(index) + delta); | ||
} else { | ||
currentPosition.current += reverseX ? -deltaX : deltaX; | ||
stateRef.current.setThumbPercent(index, clamp(currentPosition.current / trackRef.current.offsetWidth, 0, 1)); | ||
} | ||
}, | ||
onMoveEnd() { | ||
state.setThumbDragging(index, false); | ||
} | ||
}); // Immediately register editability with the state | ||
state.setThumbEditable(index, isEditable); | ||
state.setThumbEditable(index, !isDisabled); | ||
const { | ||
@@ -175,3 +239,19 @@ focusableProps | ||
onBlur: () => state.setFocusedThumb(undefined) | ||
}), inputRef); // We install mouse handlers for the drag motion on the thumb div, but | ||
}), inputRef); | ||
let onDown = () => { | ||
focusInput(); | ||
state.setThumbDragging(index, true); | ||
addGlobalListener(window, 'mouseup', onUp, false); | ||
addGlobalListener(window, 'touchend', onUp, false); | ||
addGlobalListener(window, 'pointerup', onUp, false); | ||
}; | ||
let onUp = () => { | ||
focusInput(); | ||
state.setThumbDragging(index, false); | ||
removeGlobalListener(window, 'mouseup', onUp, false); | ||
removeGlobalListener(window, 'touchend', onUp, false); | ||
removeGlobalListener(window, 'pointerup', onUp, false); | ||
}; // We install mouse handlers for the drag motion on the thumb div, but | ||
// not the key handler for moving the thumb with the slider. Instead, | ||
@@ -181,6 +261,7 @@ // we focus the range input, and let the browser handle the keyboard | ||
return { | ||
inputProps: mergeProps(focusableProps, fieldProps, { | ||
type: 'range', | ||
tabIndex: isEditable ? 0 : undefined, | ||
tabIndex: !isDisabled ? 0 : undefined, | ||
min: state.getThumbMinValue(index), | ||
@@ -190,3 +271,2 @@ max: state.getThumbMaxValue(index), | ||
value: value, | ||
readOnly: isReadOnly, | ||
disabled: isDisabled, | ||
@@ -202,8 +282,6 @@ 'aria-orientation': 'horizontal', | ||
}), | ||
thumbProps: isEditable ? mergeProps({ | ||
onMouseDown: draggableProps.onMouseDown, | ||
onMouseEnter: draggableProps.onMouseEnter, | ||
onMouseOut: draggableProps.onMouseOut | ||
}, { | ||
onMouseDown: focusInput | ||
thumbProps: !isDisabled ? mergeProps(moveProps, { | ||
onPointerDown: onDown, | ||
onMouseDown: onDown, | ||
onTouchStart: onDown | ||
}) : {}, | ||
@@ -210,0 +288,0 @@ labelProps |
{ | ||
"name": "@react-aria/slider", | ||
"version": "3.0.0-nightly.2414+0ac271ac", | ||
"version": "3.0.0-nightly.2416+dc6d7873", | ||
"description": "Slider", | ||
@@ -21,11 +21,11 @@ "license": "Apache-2.0", | ||
"@babel/runtime": "^7.6.2", | ||
"@react-aria/focus": "3.0.0-nightly.736+0ac271ac", | ||
"@react-aria/i18n": "3.0.0-nightly.736+0ac271ac", | ||
"@react-aria/interactions": "3.0.0-nightly.736+0ac271ac", | ||
"@react-aria/label": "3.0.0-nightly.736+0ac271ac", | ||
"@react-aria/utils": "3.0.0-nightly.736+0ac271ac", | ||
"@react-stately/radio": "3.0.0-nightly.736+0ac271ac", | ||
"@react-stately/slider": "3.0.0-nightly.2414+0ac271ac", | ||
"@react-types/radio": "3.0.0-nightly.736+0ac271ac", | ||
"@react-types/slider": "3.0.0-nightly.2414+0ac271ac" | ||
"@react-aria/focus": "3.0.0-nightly.738+dc6d7873", | ||
"@react-aria/i18n": "3.0.0-nightly.738+dc6d7873", | ||
"@react-aria/interactions": "3.0.0-nightly.738+dc6d7873", | ||
"@react-aria/label": "3.0.0-nightly.738+dc6d7873", | ||
"@react-aria/utils": "3.0.0-nightly.738+dc6d7873", | ||
"@react-stately/radio": "3.0.0-nightly.738+dc6d7873", | ||
"@react-stately/slider": "3.0.0-nightly.2416+dc6d7873", | ||
"@react-types/radio": "3.0.0-nightly.738+dc6d7873", | ||
"@react-types/slider": "3.0.0-nightly.2416+dc6d7873" | ||
}, | ||
@@ -38,3 +38,3 @@ "peerDependencies": { | ||
}, | ||
"gitHead": "0ac271ac5508fc6b03e0bd416142aba02546164a" | ||
"gitHead": "dc6d78733fb26fa02afeb1a36e11de7d71c84467" | ||
} |
@@ -13,4 +13,4 @@ /* | ||
import {clamp, mergeProps, useGlobalListeners} from '@react-aria/utils'; | ||
import {HTMLAttributes, useRef} from 'react'; | ||
import {mergeProps, useDrag1D} from '@react-aria/utils'; | ||
import {sliderIds} from './utils'; | ||
@@ -20,2 +20,4 @@ import {SliderProps} from '@react-types/slider'; | ||
import {useLabel} from '@react-aria/label'; | ||
import {useLocale} from '@react-aria/i18n'; | ||
import {useMove} from '@react-aria/interactions'; | ||
@@ -50,42 +52,90 @@ interface SliderAria { | ||
const isSliderEditable = !(props.isDisabled || props.isReadOnly); | ||
// Attach id of the label to the state so it can be accessed by useSliderThumb. | ||
sliderIds.set(state, labelProps.id ?? fieldProps.id); | ||
let {direction} = useLocale(); | ||
let {addGlobalListener, removeGlobalListener} = useGlobalListeners(); | ||
// When the user clicks or drags the track, we want the motion to set and drag the | ||
// closest thumb. Hence we also need to install useDrag1D() on the track element. | ||
// closest thumb. Hence we also need to install useMove() on the track element. | ||
// Here, we keep track of which index is the "closest" to the drag start point. | ||
// It is set onMouseDown; see trackProps below. | ||
const realTimeTrackDraggingIndex = useRef<number | undefined>(undefined); | ||
const isTrackDragging = useRef(false); | ||
const {onMouseDown, onMouseEnter, onMouseOut} = useDrag1D({ | ||
containerRef: trackRef as any, | ||
reverse: false, | ||
orientation: 'horizontal', | ||
onDrag: (dragging) => { | ||
if (realTimeTrackDraggingIndex.current !== undefined) { | ||
state.setThumbDragging(realTimeTrackDraggingIndex.current, dragging); | ||
// It is set onMouseDown/onTouchDown; see trackProps below. | ||
const realTimeTrackDraggingIndex = useRef<number | null>(null); | ||
const stateRef = useRef<SliderState>(null); | ||
stateRef.current = state; | ||
const reverseX = direction === 'rtl'; | ||
const currentPosition = useRef<number>(null); | ||
const moveProps = useMove({ | ||
onMoveStart() { | ||
currentPosition.current = null; | ||
}, | ||
onMove({deltaX}) { | ||
if (currentPosition.current == null) { | ||
currentPosition.current = stateRef.current.getThumbPercent(realTimeTrackDraggingIndex.current) * trackRef.current.offsetWidth; | ||
} | ||
isTrackDragging.current = dragging; | ||
}, | ||
onPositionChange: (position) => { | ||
if (realTimeTrackDraggingIndex.current !== undefined && trackRef.current) { | ||
const percent = position / trackRef.current.offsetWidth; | ||
state.setThumbPercent(realTimeTrackDraggingIndex.current, percent); | ||
currentPosition.current += reverseX ? -deltaX : deltaX; | ||
// When track-dragging ends, onDrag is called before a final onPositionChange is | ||
// called, so we can't reset realTimeTrackDraggingIndex until onPositionChange, | ||
// as we still needed to update the thumb position one last time. Hence we | ||
// track whether we're dragging, and the actual dragged index, separately. | ||
if (!isTrackDragging.current) { | ||
realTimeTrackDraggingIndex.current = undefined; | ||
} | ||
if (realTimeTrackDraggingIndex.current != null && trackRef.current) { | ||
const percent = clamp(currentPosition.current / trackRef.current.offsetWidth, 0, 1); | ||
stateRef.current.setThumbPercent(realTimeTrackDraggingIndex.current, percent); | ||
} | ||
}, | ||
onMoveEnd() { | ||
if (realTimeTrackDraggingIndex.current != null) { | ||
stateRef.current.setThumbDragging(realTimeTrackDraggingIndex.current, false); | ||
realTimeTrackDraggingIndex.current = null; | ||
} | ||
} | ||
}); | ||
let onDownTrack = (e: React.UIEvent, clientX: number) => { | ||
// We only trigger track-dragging if the user clicks on the track itself and nothing is currently being dragged. | ||
if (trackRef.current && !props.isDisabled && state.values.every((_, i) => !state.isThumbDragging(i))) { | ||
// Find the closest thumb | ||
const trackPosition = trackRef.current.getBoundingClientRect().left; | ||
const clickPosition = clientX; | ||
const offset = clickPosition - trackPosition; | ||
let percent = offset / trackRef.current.offsetWidth; | ||
if (direction === 'rtl') { | ||
percent = 1 - percent; | ||
} | ||
let value = state.getPercentValue(percent); | ||
// Only compute the diff for thumbs that are editable, as only they can be dragged | ||
const minDiff = Math.min(...state.values.map((v, index) => state.isThumbEditable(index) ? Math.abs(v - value) : Number.POSITIVE_INFINITY)); | ||
const index = state.values.findIndex(v => Math.abs(v - value) === minDiff); | ||
if (minDiff !== Number.POSITIVE_INFINITY && index >= 0) { | ||
// Don't unfocus anything | ||
e.preventDefault(); | ||
realTimeTrackDraggingIndex.current = index; | ||
state.setFocusedThumb(index); | ||
state.setThumbDragging(realTimeTrackDraggingIndex.current, true); | ||
state.setThumbValue(index, value); | ||
addGlobalListener(window, 'mouseup', onUpTrack, false); | ||
addGlobalListener(window, 'touchend', onUpTrack, false); | ||
addGlobalListener(window, 'pointerup', onUpTrack, false); | ||
} else { | ||
realTimeTrackDraggingIndex.current = null; | ||
} | ||
} | ||
}; | ||
let onUpTrack = () => { | ||
if (realTimeTrackDraggingIndex.current != null) { | ||
state.setThumbDragging(realTimeTrackDraggingIndex.current, false); | ||
realTimeTrackDraggingIndex.current = null; | ||
} | ||
removeGlobalListener(window, 'mouseup', onUpTrack, false); | ||
removeGlobalListener(window, 'touchend', onUpTrack, false); | ||
removeGlobalListener(window, 'pointerup', onUpTrack, false); | ||
}; | ||
return { | ||
labelProps, | ||
// The root element of the Slider will have role="group" to group together | ||
@@ -99,39 +149,7 @@ // all the thumb inputs in the Slider. The label of the Slider will | ||
trackProps: mergeProps({ | ||
onMouseDown: (e: React.MouseEvent<HTMLElement>) => { | ||
// We only trigger track-dragging if the user clicks on the track itself. | ||
if (trackRef.current && isSliderEditable) { | ||
// Find the closest thumb | ||
const trackPosition = trackRef.current.getBoundingClientRect().left; | ||
const clickPosition = e.clientX; | ||
const offset = clickPosition - trackPosition; | ||
const percent = offset / trackRef.current.offsetWidth; | ||
const value = state.getPercentValue(percent); | ||
// Only compute the diff for thumbs that are editable, as only they can be dragged | ||
const minDiff = Math.min(...state.values.map((v, index) => state.isThumbEditable(index) ? Math.abs(v - value) : Number.POSITIVE_INFINITY)); | ||
const index = state.values.findIndex(v => Math.abs(v - value) === minDiff); | ||
if (minDiff !== Number.POSITIVE_INFINITY && index >= 0) { | ||
// Don't unfocus anything | ||
e.preventDefault(); | ||
realTimeTrackDraggingIndex.current = index; | ||
state.setFocusedThumb(index); | ||
// We immediately toggle state to dragging and set the value on mouse down. | ||
// We set the value now, instead of waiting for onDrag, so that the thumb | ||
// is updated while you're still holding the mouse button down. And we | ||
// set dragging on now, so that onChangeEnd() won't fire yet when we set | ||
// the value. Dragging state will be reset to false in onDrag above, even | ||
// if no dragging actually occurs. | ||
state.setThumbDragging(realTimeTrackDraggingIndex.current, true); | ||
state.setThumbValue(index, value); | ||
} else { | ||
realTimeTrackDraggingIndex.current = undefined; | ||
} | ||
} | ||
} | ||
}, { | ||
onMouseDown, onMouseEnter, onMouseOut | ||
}) | ||
onMouseDown(e: React.MouseEvent<HTMLElement>) { onDownTrack(e, e.clientX); }, | ||
onPointerDown(e: React.PointerEvent<HTMLElement>) { onDownTrack(e, e.clientX); }, | ||
onTouchStart(e: React.TouchEvent<HTMLElement>) { onDownTrack(e, e.targetTouches[0].clientX); } | ||
}, moveProps) | ||
}; | ||
} |
@@ -1,3 +0,3 @@ | ||
import {ChangeEvent, HTMLAttributes, useCallback, useEffect} from 'react'; | ||
import {focusWithoutScrolling, mergeProps, useDrag1D} from '@react-aria/utils'; | ||
import {ChangeEvent, HTMLAttributes, useCallback, useEffect, useRef} from 'react'; | ||
import {clamp, focusWithoutScrolling, mergeProps, useGlobalListeners} from '@react-aria/utils'; | ||
import {sliderIds} from './utils'; | ||
@@ -8,2 +8,4 @@ import {SliderState} from '@react-stately/slider'; | ||
import {useLabel} from '@react-aria/label'; | ||
import {useLocale} from '@react-aria/i18n'; | ||
import {useMove} from '@react-aria/interactions'; | ||
@@ -34,3 +36,3 @@ interface SliderThumbAria { | ||
opts: SliderThumbOptions, | ||
state: SliderState, | ||
state: SliderState | ||
): SliderThumbAria { | ||
@@ -41,3 +43,2 @@ const { | ||
isDisabled, | ||
isReadOnly, | ||
validationState, | ||
@@ -48,2 +49,5 @@ trackRef, | ||
let {direction} = useLocale(); | ||
let {addGlobalListener, removeGlobalListener} = useGlobalListeners(); | ||
let labelId = sliderIds.get(state); | ||
@@ -56,3 +60,2 @@ const {labelProps, fieldProps} = useLabel({ | ||
const value = state.values[index]; | ||
const isEditable = !(isDisabled || isReadOnly); | ||
@@ -73,13 +76,27 @@ const focusInput = useCallback(() => { | ||
const draggableProps = useDrag1D({ | ||
containerRef: trackRef as any, | ||
reverse: false, | ||
orientation: 'horizontal', | ||
onDrag: (dragging) => { | ||
state.setThumbDragging(index, dragging); | ||
focusInput(); | ||
const stateRef = useRef<SliderState>(null); | ||
stateRef.current = state; | ||
let reverseX = direction === 'rtl'; | ||
let currentPosition = useRef<number>(null); | ||
let moveProps = useMove({ | ||
onMoveStart() { | ||
currentPosition.current = null; | ||
state.setThumbDragging(index, true); | ||
}, | ||
onPositionChange: (position) => { | ||
const percent = position / trackRef.current.offsetWidth; | ||
state.setThumbPercent(index, percent); | ||
onMove({deltaX, deltaY, pointerType}) { | ||
if (currentPosition.current == null) { | ||
currentPosition.current = stateRef.current.getThumbPercent(index) * trackRef.current.offsetWidth; | ||
} | ||
if (pointerType === 'keyboard') { | ||
// (invert left/right according to language direction) + (up should always increase) | ||
let delta = ((reverseX ? -deltaX : deltaX) + -deltaY) * stateRef.current.step; | ||
currentPosition.current += delta * trackRef.current.offsetWidth; | ||
stateRef.current.setThumbValue(index, stateRef.current.getThumbValue(index) + delta); | ||
} else { | ||
currentPosition.current += reverseX ? -deltaX : deltaX; | ||
stateRef.current.setThumbPercent(index, clamp(currentPosition.current / trackRef.current.offsetWidth, 0, 1)); | ||
} | ||
}, | ||
onMoveEnd() { | ||
state.setThumbDragging(index, false); | ||
} | ||
@@ -89,3 +106,3 @@ }); | ||
// Immediately register editability with the state | ||
state.setThumbEditable(index, isEditable); | ||
state.setThumbEditable(index, !isDisabled); | ||
@@ -100,2 +117,20 @@ const {focusableProps} = useFocusable( | ||
let onDown = () => { | ||
focusInput(); | ||
state.setThumbDragging(index, true); | ||
addGlobalListener(window, 'mouseup', onUp, false); | ||
addGlobalListener(window, 'touchend', onUp, false); | ||
addGlobalListener(window, 'pointerup', onUp, false); | ||
}; | ||
let onUp = () => { | ||
focusInput(); | ||
state.setThumbDragging(index, false); | ||
removeGlobalListener(window, 'mouseup', onUp, false); | ||
removeGlobalListener(window, 'touchend', onUp, false); | ||
removeGlobalListener(window, 'pointerup', onUp, false); | ||
}; | ||
// We install mouse handlers for the drag motion on the thumb div, but | ||
@@ -108,3 +143,3 @@ // not the key handler for moving the thumb with the slider. Instead, | ||
type: 'range', | ||
tabIndex: isEditable ? 0 : undefined, | ||
tabIndex: !isDisabled ? 0 : undefined, | ||
min: state.getThumbMinValue(index), | ||
@@ -114,3 +149,2 @@ max: state.getThumbMaxValue(index), | ||
value: value, | ||
readOnly: isReadOnly, | ||
disabled: isDisabled, | ||
@@ -126,11 +160,12 @@ 'aria-orientation': 'horizontal', | ||
}), | ||
thumbProps: isEditable ? mergeProps({ | ||
onMouseDown: draggableProps.onMouseDown, | ||
onMouseEnter: draggableProps.onMouseEnter, | ||
onMouseOut: draggableProps.onMouseOut | ||
}, { | ||
onMouseDown: focusInput | ||
}) : {}, | ||
thumbProps: !isDisabled ? mergeProps( | ||
moveProps, | ||
{ | ||
onPointerDown: onDown, | ||
onMouseDown: onDown, | ||
onTouchStart: onDown | ||
} | ||
) : {}, | ||
labelProps | ||
}; | ||
} |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Manifest confusion
Supply chain riskThis package has inconsistent metadata. This could be malicious or caused by an error when publishing the package.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Manifest confusion
Supply chain riskThis package has inconsistent metadata. This could be malicious or caused by an error when publishing the package.
Found 1 instance in 1 package
92308
848