@react-aria/slider
Advanced tools
Comparing version 3.0.0-nightly.2508 to 3.0.0-nightly-641446f65-240905
430
dist/main.js
@@ -1,423 +0,25 @@ | ||
var { | ||
useFocusable | ||
} = require("@react-aria/focus"); | ||
var $481f97d830e3ede6$exports = require("./useSlider.main.js"); | ||
var $5eb806b626475377$exports = require("./useSliderThumb.main.js"); | ||
var { | ||
useLocale | ||
} = require("@react-aria/i18n"); | ||
var { | ||
useLabel | ||
} = require("@react-aria/label"); | ||
var { | ||
setInteractionModality, | ||
useMove | ||
} = require("@react-aria/interactions"); | ||
var { | ||
useRef, | ||
useCallback, | ||
useEffect | ||
} = require("react"); | ||
var { | ||
clamp, | ||
mergeProps, | ||
useGlobalListeners, | ||
focusWithoutScrolling | ||
} = require("@react-aria/utils"); | ||
var _babelRuntimeHelpersExtends = $parcel$interopDefault(require("@babel/runtime/helpers/extends")); | ||
function $parcel$interopDefault(a) { | ||
return a && a.__esModule ? a.default : a; | ||
function $parcel$export(e, n, v, s) { | ||
Object.defineProperty(e, n, {get: v, set: s, enumerable: true, configurable: true}); | ||
} | ||
const $dec1906781d9c7cb69245fc4d5344b$export$sliderIds = new WeakMap(); | ||
function $dec1906781d9c7cb69245fc4d5344b$export$getSliderThumbId(state, index) { | ||
let id = $dec1906781d9c7cb69245fc4d5344b$export$sliderIds.get(state); | ||
if (!id) { | ||
throw new Error('Unknown slider state'); | ||
} | ||
return id + "-" + index; | ||
} | ||
/** | ||
* Provides the behavior and accessibility implementation for a slider component representing one or more values. | ||
$parcel$export(module.exports, "useSlider", () => $481f97d830e3ede6$exports.useSlider); | ||
$parcel$export(module.exports, "useSliderThumb", () => $5eb806b626475377$exports.useSliderThumb); | ||
/* | ||
* Copyright 2020 Adobe. All rights reserved. | ||
* This file is licensed to you under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. You may obtain a copy | ||
* of the License at http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* @param props Props for the slider. | ||
* @param state State for the slider, as returned by `useSliderState`. | ||
* @param trackRef Ref for the "track" element. The width of this element provides the "length" | ||
* of the track -- the span of one dimensional space that the slider thumb can be. It also | ||
* accepts click and drag motions, so that the closest thumb will follow clicks and drags on | ||
* the track. | ||
*/ | ||
function useSlider(props, state, trackRef) { | ||
var _labelProps$id; | ||
* Unless required by applicable law or agreed to in writing, software distributed under | ||
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS | ||
* OF ANY KIND, either express or implied. See the License for the specific language | ||
* governing permissions and limitations under the License. | ||
*/ | ||
let { | ||
labelProps, | ||
fieldProps | ||
} = useLabel(props); | ||
let isVertical = props.orientation === 'vertical'; // 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); | ||
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/onTouchDown; see trackProps below. | ||
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, | ||
deltaY | ||
} = _ref; | ||
let size = isVertical ? trackRef.current.offsetHeight : trackRef.current.offsetWidth; | ||
if (currentPosition.current == null) { | ||
currentPosition.current = stateRef.current.getThumbPercent(realTimeTrackDraggingIndex.current) * size; | ||
} | ||
let delta = isVertical ? deltaY : deltaX; | ||
if (isVertical || reverseX) { | ||
delta = -delta; | ||
} | ||
currentPosition.current += delta; | ||
if (realTimeTrackDraggingIndex.current != null && trackRef.current) { | ||
const percent = clamp(currentPosition.current / size, 0, 1); | ||
stateRef.current.setThumbPercent(realTimeTrackDraggingIndex.current, percent); | ||
} | ||
}, | ||
onMoveEnd() { | ||
if (realTimeTrackDraggingIndex.current != null) { | ||
stateRef.current.setThumbDragging(realTimeTrackDraggingIndex.current, false); | ||
realTimeTrackDraggingIndex.current = null; | ||
} | ||
} | ||
}); | ||
let currentPointer = useRef(undefined); | ||
let onDownTrack = (e, id, clientX, clientY) => { | ||
// 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))) { | ||
let size = isVertical ? trackRef.current.offsetHeight : trackRef.current.offsetWidth; // Find the closest thumb | ||
const trackPosition = trackRef.current.getBoundingClientRect()[isVertical ? 'top' : 'left']; | ||
const clickPosition = isVertical ? clientY : clientX; | ||
const offset = clickPosition - trackPosition; | ||
let percent = offset / size; | ||
if (direction === 'rtl' || isVertical) { | ||
percent = 1 - percent; | ||
} | ||
let value = state.getPercentValue(percent); // to find the closet thumb we split the array based on the first thumb position to the "right/end" of the click. | ||
let closestThumb; | ||
let split = state.values.findIndex(v => value - v < 0); | ||
if (split === 0) { | ||
// If the index is zero then the closetThumb is the first one | ||
closestThumb = split; | ||
} else if (split === -1) { | ||
// If no index is found they've clicked past all the thumbs | ||
closestThumb = state.values.length - 1; | ||
} else { | ||
let lastLeft = state.values[split - 1]; | ||
let firstRight = state.values[split]; // Pick the last left/start thumb, unless they are stacked on top of each other, then pick the right/end one | ||
if (Math.abs(lastLeft - value) < Math.abs(firstRight - value)) { | ||
closestThumb = split - 1; | ||
} else { | ||
closestThumb = split; | ||
} | ||
} // Confirm that the found closest thumb is editable, not disabled, and move it | ||
if (closestThumb >= 0 && state.isThumbEditable(closestThumb)) { | ||
// Don't unfocus anything | ||
e.preventDefault(); | ||
realTimeTrackDraggingIndex.current = closestThumb; | ||
state.setFocusedThumb(closestThumb); | ||
currentPointer.current = id; | ||
state.setThumbDragging(realTimeTrackDraggingIndex.current, true); | ||
state.setThumbValue(closestThumb, value); | ||
addGlobalListener(window, 'mouseup', onUpTrack, false); | ||
addGlobalListener(window, 'touchend', onUpTrack, false); | ||
addGlobalListener(window, 'pointerup', onUpTrack, false); | ||
} else { | ||
realTimeTrackDraggingIndex.current = null; | ||
} | ||
} | ||
}; | ||
let onUpTrack = e => { | ||
var _e$pointerId, _e$changedTouches; | ||
let id = (_e$pointerId = e.pointerId) != null ? _e$pointerId : (_e$changedTouches = e.changedTouches) == null ? void 0 : _e$changedTouches[0].identifier; | ||
if (id === currentPointer.current) { | ||
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); | ||
} | ||
}; | ||
if (labelProps.htmlFor) { | ||
// Ideally the `for` attribute should point to the first thumb, but VoiceOver on iOS | ||
// causes this to override the `aria-labelledby` on the thumb. This causes the first | ||
// thumb to only be announced as the slider label rather than its individual name as well. | ||
// See https://bugs.webkit.org/show_bug.cgi?id=172464. | ||
delete labelProps.htmlFor; | ||
labelProps.onClick = () => { | ||
var _document$getElementB; | ||
// Safari does not focus <input type="range"> elements when clicking on an associated <label>, | ||
// so do it manually. In addition, make sure we show the focus ring. | ||
(_document$getElementB = document.getElementById($dec1906781d9c7cb69245fc4d5344b$export$getSliderThumbId(state, 0))) == null ? void 0 : _document$getElementB.focus(); | ||
setInteractionModality('keyboard'); | ||
}; | ||
} | ||
return { | ||
labelProps, | ||
// The root element of the Slider will have role="group" to group together | ||
// all the thumb inputs in the Slider. The label of the Slider will | ||
// be used to label the group. | ||
groupProps: _babelRuntimeHelpersExtends({ | ||
role: 'group' | ||
}, fieldProps), | ||
trackProps: mergeProps({ | ||
onMouseDown(e) { | ||
if (e.button !== 0 || e.altKey || e.ctrlKey || e.metaKey) { | ||
return; | ||
} | ||
onDownTrack(e, undefined, e.clientX, e.clientY); | ||
}, | ||
onPointerDown(e) { | ||
if (e.pointerType === 'mouse' && (e.button !== 0 || e.altKey || e.ctrlKey || e.metaKey)) { | ||
return; | ||
} | ||
onDownTrack(e, e.pointerId, e.clientX, e.clientY); | ||
}, | ||
onTouchStart(e) { | ||
onDownTrack(e, e.changedTouches[0].identifier, e.changedTouches[0].clientX, e.changedTouches[0].clientY); | ||
} | ||
}, moveProps), | ||
outputProps: { | ||
htmlFor: state.values.map((_, index) => $dec1906781d9c7cb69245fc4d5344b$export$getSliderThumbId(state, index)).join(' '), | ||
'aria-live': 'off' | ||
} | ||
}; | ||
} | ||
exports.useSlider = useSlider; | ||
/** | ||
* Provides behavior and accessibility for a thumb of a slider component. | ||
* | ||
* @param opts Options for this Slider thumb. | ||
* @param state Slider state, created via `useSliderState`. | ||
*/ | ||
function useSliderThumb(opts, state) { | ||
var _opts$ariaLabelledby; | ||
let { | ||
index, | ||
isRequired, | ||
isDisabled, | ||
validationState, | ||
trackRef, | ||
inputRef | ||
} = opts; | ||
let isVertical = opts.orientation === 'vertical'; | ||
let { | ||
direction | ||
} = useLocale(); | ||
let { | ||
addGlobalListener, | ||
removeGlobalListener | ||
} = useGlobalListeners(); | ||
let labelId = $dec1906781d9c7cb69245fc4d5344b$export$sliderIds.get(state); | ||
const { | ||
labelProps, | ||
fieldProps | ||
} = useLabel(_babelRuntimeHelpersExtends({}, opts, { | ||
id: $dec1906781d9c7cb69245fc4d5344b$export$getSliderThumbId(state, index), | ||
'aria-labelledby': (labelId + " " + ((_opts$ariaLabelledby = opts['aria-labelledby']) != null ? _opts$ariaLabelledby : '')).trim() | ||
})); | ||
const value = state.values[index]; | ||
const focusInput = useCallback(() => { | ||
if (inputRef.current) { | ||
focusWithoutScrolling(inputRef.current); | ||
} | ||
}, [inputRef]); | ||
const isFocused = state.focusedThumb === index; | ||
useEffect(() => { | ||
if (isFocused) { | ||
focusInput(); | ||
} | ||
}, [isFocused, 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); | ||
}, | ||
onMove(_ref) { | ||
let { | ||
deltaX, | ||
deltaY, | ||
pointerType | ||
} = _ref; | ||
let size = isVertical ? trackRef.current.offsetHeight : trackRef.current.offsetWidth; | ||
if (currentPosition.current == null) { | ||
currentPosition.current = stateRef.current.getThumbPercent(index) * size; | ||
} | ||
if (pointerType === 'keyboard') { | ||
// (invert left/right according to language direction) + (according to vertical) | ||
let delta = ((reverseX ? -deltaX : deltaX) + (isVertical ? -deltaY : -deltaY)) * stateRef.current.step; | ||
currentPosition.current += delta * size; | ||
stateRef.current.setThumbValue(index, stateRef.current.getThumbValue(index) + delta); | ||
} else { | ||
let delta = isVertical ? deltaY : deltaX; | ||
if (isVertical || reverseX) { | ||
delta = -delta; | ||
} | ||
currentPosition.current += delta; | ||
stateRef.current.setThumbPercent(index, clamp(currentPosition.current / size, 0, 1)); | ||
} | ||
}, | ||
onMoveEnd() { | ||
state.setThumbDragging(index, false); | ||
} | ||
}); // Immediately register editability with the state | ||
state.setThumbEditable(index, !isDisabled); | ||
const { | ||
focusableProps | ||
} = useFocusable(mergeProps(opts, { | ||
onFocus: () => state.setFocusedThumb(index), | ||
onBlur: () => state.setFocusedThumb(undefined) | ||
}), inputRef); | ||
let currentPointer = useRef(undefined); | ||
let onDown = id => { | ||
focusInput(); | ||
currentPointer.current = id; | ||
state.setThumbDragging(index, true); | ||
addGlobalListener(window, 'mouseup', onUp, false); | ||
addGlobalListener(window, 'touchend', onUp, false); | ||
addGlobalListener(window, 'pointerup', onUp, false); | ||
}; | ||
let onUp = e => { | ||
var _e$pointerId, _e$changedTouches; | ||
let id = (_e$pointerId = e.pointerId) != null ? _e$pointerId : (_e$changedTouches = e.changedTouches) == null ? void 0 : _e$changedTouches[0].identifier; | ||
if (id === currentPointer.current) { | ||
focusInput(); | ||
state.setThumbDragging(index, false); | ||
removeGlobalListener(window, 'mouseup', onUp, false); | ||
removeGlobalListener(window, 'touchend', onUp, false); | ||
removeGlobalListener(window, 'pointerup', onUp, false); | ||
} | ||
}; // not the key handler for moving the thumb with the slider. Instead, | ||
// we focus the range input, and let the browser handle the keyboard | ||
// interactions; we then listen to input's onChange to update state. | ||
return { | ||
inputProps: mergeProps(focusableProps, fieldProps, { | ||
type: 'range', | ||
tabIndex: !isDisabled ? 0 : undefined, | ||
min: state.getThumbMinValue(index), | ||
max: state.getThumbMaxValue(index), | ||
step: state.step, | ||
value: value, | ||
disabled: isDisabled, | ||
'aria-orientation': opts.orientation, | ||
'aria-valuetext': state.getThumbValueLabel(index), | ||
'aria-required': isRequired || undefined, | ||
'aria-invalid': validationState === 'invalid' || undefined, | ||
'aria-errormessage': opts['aria-errormessage'], | ||
onChange: e => { | ||
state.setThumbValue(index, parseFloat(e.target.value)); | ||
} | ||
}), | ||
thumbProps: !isDisabled ? mergeProps(moveProps, { | ||
onMouseDown: e => { | ||
if (e.button !== 0 || e.altKey || e.ctrlKey || e.metaKey) { | ||
return; | ||
} | ||
onDown(null); | ||
}, | ||
onPointerDown: e => { | ||
if (e.button !== 0 || e.altKey || e.ctrlKey || e.metaKey) { | ||
return; | ||
} | ||
onDown(e.pointerId); | ||
}, | ||
onTouchStart: e => { | ||
onDown(e.changedTouches[0].identifier); | ||
} | ||
}) : {}, | ||
labelProps | ||
}; | ||
} | ||
exports.useSliderThumb = useSliderThumb; | ||
//# sourceMappingURL=main.js.map |
@@ -1,390 +0,19 @@ | ||
import { useFocusable } from "@react-aria/focus"; | ||
import { useLocale } from "@react-aria/i18n"; | ||
import { useLabel } from "@react-aria/label"; | ||
import { setInteractionModality, useMove } from "@react-aria/interactions"; | ||
import { useRef, useCallback, useEffect } from "react"; | ||
import { clamp, mergeProps, useGlobalListeners, focusWithoutScrolling } from "@react-aria/utils"; | ||
import _babelRuntimeHelpersEsmExtends from "@babel/runtime/helpers/esm/extends"; | ||
const $d20491ae7743da17cfa841ff6d87$export$sliderIds = new WeakMap(); | ||
import {useSlider as $bcca50147b47f54d$export$56b2c08e277f365} from "./useSlider.module.js"; | ||
import {useSliderThumb as $47b897dc8cdb026b$export$8d15029008292ae} from "./useSliderThumb.module.js"; | ||
function $d20491ae7743da17cfa841ff6d87$export$getSliderThumbId(state, index) { | ||
let id = $d20491ae7743da17cfa841ff6d87$export$sliderIds.get(state); | ||
if (!id) { | ||
throw new Error('Unknown slider state'); | ||
} | ||
return id + "-" + index; | ||
} | ||
/** | ||
* Provides the behavior and accessibility implementation for a slider component representing one or more values. | ||
/* | ||
* Copyright 2020 Adobe. All rights reserved. | ||
* This file is licensed to you under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. You may obtain a copy | ||
* of the License at http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* @param props Props for the slider. | ||
* @param state State for the slider, as returned by `useSliderState`. | ||
* @param trackRef Ref for the "track" element. The width of this element provides the "length" | ||
* of the track -- the span of one dimensional space that the slider thumb can be. It also | ||
* accepts click and drag motions, so that the closest thumb will follow clicks and drags on | ||
* the track. | ||
*/ | ||
export function useSlider(props, state, trackRef) { | ||
var _labelProps$id; | ||
* Unless required by applicable law or agreed to in writing, software distributed under | ||
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS | ||
* OF ANY KIND, either express or implied. See the License for the specific language | ||
* governing permissions and limitations under the License. | ||
*/ | ||
let { | ||
labelProps, | ||
fieldProps | ||
} = useLabel(props); | ||
let isVertical = props.orientation === 'vertical'; // 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); | ||
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/onTouchDown; see trackProps below. | ||
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, | ||
deltaY | ||
} = _ref; | ||
let size = isVertical ? trackRef.current.offsetHeight : trackRef.current.offsetWidth; | ||
if (currentPosition.current == null) { | ||
currentPosition.current = stateRef.current.getThumbPercent(realTimeTrackDraggingIndex.current) * size; | ||
} | ||
let delta = isVertical ? deltaY : deltaX; | ||
if (isVertical || reverseX) { | ||
delta = -delta; | ||
} | ||
currentPosition.current += delta; | ||
if (realTimeTrackDraggingIndex.current != null && trackRef.current) { | ||
const percent = clamp(currentPosition.current / size, 0, 1); | ||
stateRef.current.setThumbPercent(realTimeTrackDraggingIndex.current, percent); | ||
} | ||
}, | ||
onMoveEnd() { | ||
if (realTimeTrackDraggingIndex.current != null) { | ||
stateRef.current.setThumbDragging(realTimeTrackDraggingIndex.current, false); | ||
realTimeTrackDraggingIndex.current = null; | ||
} | ||
} | ||
}); | ||
let currentPointer = useRef(undefined); | ||
let onDownTrack = (e, id, clientX, clientY) => { | ||
// 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))) { | ||
let size = isVertical ? trackRef.current.offsetHeight : trackRef.current.offsetWidth; // Find the closest thumb | ||
const trackPosition = trackRef.current.getBoundingClientRect()[isVertical ? 'top' : 'left']; | ||
const clickPosition = isVertical ? clientY : clientX; | ||
const offset = clickPosition - trackPosition; | ||
let percent = offset / size; | ||
if (direction === 'rtl' || isVertical) { | ||
percent = 1 - percent; | ||
} | ||
let value = state.getPercentValue(percent); // to find the closet thumb we split the array based on the first thumb position to the "right/end" of the click. | ||
let closestThumb; | ||
let split = state.values.findIndex(v => value - v < 0); | ||
if (split === 0) { | ||
// If the index is zero then the closetThumb is the first one | ||
closestThumb = split; | ||
} else if (split === -1) { | ||
// If no index is found they've clicked past all the thumbs | ||
closestThumb = state.values.length - 1; | ||
} else { | ||
let lastLeft = state.values[split - 1]; | ||
let firstRight = state.values[split]; // Pick the last left/start thumb, unless they are stacked on top of each other, then pick the right/end one | ||
if (Math.abs(lastLeft - value) < Math.abs(firstRight - value)) { | ||
closestThumb = split - 1; | ||
} else { | ||
closestThumb = split; | ||
} | ||
} // Confirm that the found closest thumb is editable, not disabled, and move it | ||
if (closestThumb >= 0 && state.isThumbEditable(closestThumb)) { | ||
// Don't unfocus anything | ||
e.preventDefault(); | ||
realTimeTrackDraggingIndex.current = closestThumb; | ||
state.setFocusedThumb(closestThumb); | ||
currentPointer.current = id; | ||
state.setThumbDragging(realTimeTrackDraggingIndex.current, true); | ||
state.setThumbValue(closestThumb, value); | ||
addGlobalListener(window, 'mouseup', onUpTrack, false); | ||
addGlobalListener(window, 'touchend', onUpTrack, false); | ||
addGlobalListener(window, 'pointerup', onUpTrack, false); | ||
} else { | ||
realTimeTrackDraggingIndex.current = null; | ||
} | ||
} | ||
}; | ||
let onUpTrack = e => { | ||
var _e$pointerId, _e$changedTouches; | ||
let id = (_e$pointerId = e.pointerId) != null ? _e$pointerId : (_e$changedTouches = e.changedTouches) == null ? void 0 : _e$changedTouches[0].identifier; | ||
if (id === currentPointer.current) { | ||
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); | ||
} | ||
}; | ||
if (labelProps.htmlFor) { | ||
// Ideally the `for` attribute should point to the first thumb, but VoiceOver on iOS | ||
// causes this to override the `aria-labelledby` on the thumb. This causes the first | ||
// thumb to only be announced as the slider label rather than its individual name as well. | ||
// See https://bugs.webkit.org/show_bug.cgi?id=172464. | ||
delete labelProps.htmlFor; | ||
labelProps.onClick = () => { | ||
var _document$getElementB; | ||
// Safari does not focus <input type="range"> elements when clicking on an associated <label>, | ||
// so do it manually. In addition, make sure we show the focus ring. | ||
(_document$getElementB = document.getElementById($d20491ae7743da17cfa841ff6d87$export$getSliderThumbId(state, 0))) == null ? void 0 : _document$getElementB.focus(); | ||
setInteractionModality('keyboard'); | ||
}; | ||
} | ||
return { | ||
labelProps, | ||
// The root element of the Slider will have role="group" to group together | ||
// all the thumb inputs in the Slider. The label of the Slider will | ||
// be used to label the group. | ||
groupProps: _babelRuntimeHelpersEsmExtends({ | ||
role: 'group' | ||
}, fieldProps), | ||
trackProps: mergeProps({ | ||
onMouseDown(e) { | ||
if (e.button !== 0 || e.altKey || e.ctrlKey || e.metaKey) { | ||
return; | ||
} | ||
onDownTrack(e, undefined, e.clientX, e.clientY); | ||
}, | ||
onPointerDown(e) { | ||
if (e.pointerType === 'mouse' && (e.button !== 0 || e.altKey || e.ctrlKey || e.metaKey)) { | ||
return; | ||
} | ||
onDownTrack(e, e.pointerId, e.clientX, e.clientY); | ||
}, | ||
onTouchStart(e) { | ||
onDownTrack(e, e.changedTouches[0].identifier, e.changedTouches[0].clientX, e.changedTouches[0].clientY); | ||
} | ||
}, moveProps), | ||
outputProps: { | ||
htmlFor: state.values.map((_, index) => $d20491ae7743da17cfa841ff6d87$export$getSliderThumbId(state, index)).join(' '), | ||
'aria-live': 'off' | ||
} | ||
}; | ||
} | ||
/** | ||
* Provides behavior and accessibility for a thumb of a slider component. | ||
* | ||
* @param opts Options for this Slider thumb. | ||
* @param state Slider state, created via `useSliderState`. | ||
*/ | ||
export function useSliderThumb(opts, state) { | ||
var _opts$ariaLabelledby; | ||
let { | ||
index, | ||
isRequired, | ||
isDisabled, | ||
validationState, | ||
trackRef, | ||
inputRef | ||
} = opts; | ||
let isVertical = opts.orientation === 'vertical'; | ||
let { | ||
direction | ||
} = useLocale(); | ||
let { | ||
addGlobalListener, | ||
removeGlobalListener | ||
} = useGlobalListeners(); | ||
let labelId = $d20491ae7743da17cfa841ff6d87$export$sliderIds.get(state); | ||
const { | ||
labelProps, | ||
fieldProps | ||
} = useLabel(_babelRuntimeHelpersEsmExtends({}, opts, { | ||
id: $d20491ae7743da17cfa841ff6d87$export$getSliderThumbId(state, index), | ||
'aria-labelledby': (labelId + " " + ((_opts$ariaLabelledby = opts['aria-labelledby']) != null ? _opts$ariaLabelledby : '')).trim() | ||
})); | ||
const value = state.values[index]; | ||
const focusInput = useCallback(() => { | ||
if (inputRef.current) { | ||
focusWithoutScrolling(inputRef.current); | ||
} | ||
}, [inputRef]); | ||
const isFocused = state.focusedThumb === index; | ||
useEffect(() => { | ||
if (isFocused) { | ||
focusInput(); | ||
} | ||
}, [isFocused, 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); | ||
}, | ||
onMove(_ref) { | ||
let { | ||
deltaX, | ||
deltaY, | ||
pointerType | ||
} = _ref; | ||
let size = isVertical ? trackRef.current.offsetHeight : trackRef.current.offsetWidth; | ||
if (currentPosition.current == null) { | ||
currentPosition.current = stateRef.current.getThumbPercent(index) * size; | ||
} | ||
if (pointerType === 'keyboard') { | ||
// (invert left/right according to language direction) + (according to vertical) | ||
let delta = ((reverseX ? -deltaX : deltaX) + (isVertical ? -deltaY : -deltaY)) * stateRef.current.step; | ||
currentPosition.current += delta * size; | ||
stateRef.current.setThumbValue(index, stateRef.current.getThumbValue(index) + delta); | ||
} else { | ||
let delta = isVertical ? deltaY : deltaX; | ||
if (isVertical || reverseX) { | ||
delta = -delta; | ||
} | ||
currentPosition.current += delta; | ||
stateRef.current.setThumbPercent(index, clamp(currentPosition.current / size, 0, 1)); | ||
} | ||
}, | ||
onMoveEnd() { | ||
state.setThumbDragging(index, false); | ||
} | ||
}); // Immediately register editability with the state | ||
state.setThumbEditable(index, !isDisabled); | ||
const { | ||
focusableProps | ||
} = useFocusable(mergeProps(opts, { | ||
onFocus: () => state.setFocusedThumb(index), | ||
onBlur: () => state.setFocusedThumb(undefined) | ||
}), inputRef); | ||
let currentPointer = useRef(undefined); | ||
let onDown = id => { | ||
focusInput(); | ||
currentPointer.current = id; | ||
state.setThumbDragging(index, true); | ||
addGlobalListener(window, 'mouseup', onUp, false); | ||
addGlobalListener(window, 'touchend', onUp, false); | ||
addGlobalListener(window, 'pointerup', onUp, false); | ||
}; | ||
let onUp = e => { | ||
var _e$pointerId, _e$changedTouches; | ||
let id = (_e$pointerId = e.pointerId) != null ? _e$pointerId : (_e$changedTouches = e.changedTouches) == null ? void 0 : _e$changedTouches[0].identifier; | ||
if (id === currentPointer.current) { | ||
focusInput(); | ||
state.setThumbDragging(index, false); | ||
removeGlobalListener(window, 'mouseup', onUp, false); | ||
removeGlobalListener(window, 'touchend', onUp, false); | ||
removeGlobalListener(window, 'pointerup', onUp, false); | ||
} | ||
}; // not the key handler for moving the thumb with the slider. Instead, | ||
// we focus the range input, and let the browser handle the keyboard | ||
// interactions; we then listen to input's onChange to update state. | ||
return { | ||
inputProps: mergeProps(focusableProps, fieldProps, { | ||
type: 'range', | ||
tabIndex: !isDisabled ? 0 : undefined, | ||
min: state.getThumbMinValue(index), | ||
max: state.getThumbMaxValue(index), | ||
step: state.step, | ||
value: value, | ||
disabled: isDisabled, | ||
'aria-orientation': opts.orientation, | ||
'aria-valuetext': state.getThumbValueLabel(index), | ||
'aria-required': isRequired || undefined, | ||
'aria-invalid': validationState === 'invalid' || undefined, | ||
'aria-errormessage': opts['aria-errormessage'], | ||
onChange: e => { | ||
state.setThumbValue(index, parseFloat(e.target.value)); | ||
} | ||
}), | ||
thumbProps: !isDisabled ? mergeProps(moveProps, { | ||
onMouseDown: e => { | ||
if (e.button !== 0 || e.altKey || e.ctrlKey || e.metaKey) { | ||
return; | ||
} | ||
onDown(null); | ||
}, | ||
onPointerDown: e => { | ||
if (e.button !== 0 || e.altKey || e.ctrlKey || e.metaKey) { | ||
return; | ||
} | ||
onDown(e.pointerId); | ||
}, | ||
onTouchStart: e => { | ||
onDown(e.changedTouches[0].identifier); | ||
} | ||
}) : {}, | ||
labelProps | ||
}; | ||
} | ||
export {$bcca50147b47f54d$export$56b2c08e277f365 as useSlider, $47b897dc8cdb026b$export$8d15029008292ae as useSliderThumb}; | ||
//# sourceMappingURL=module.js.map |
import { AriaSliderProps, AriaSliderThumbProps } from "@react-types/slider"; | ||
import { HTMLAttributes, LabelHTMLAttributes, OutputHTMLAttributes, RefObject, InputHTMLAttributes } from "react"; | ||
import { DOMAttributes, RefObject } from "@react-types/shared"; | ||
import { LabelHTMLAttributes, OutputHTMLAttributes, InputHTMLAttributes } from "react"; | ||
import { SliderState } from "@react-stately/slider"; | ||
interface SliderAria { | ||
export interface SliderAria { | ||
/** Props for the label element. */ | ||
labelProps: LabelHTMLAttributes<HTMLLabelElement>; | ||
/** Props for the root element of the slider component; groups slider inputs. */ | ||
groupProps: HTMLAttributes<HTMLElement>; | ||
groupProps: DOMAttributes; | ||
/** Props for the track element. */ | ||
trackProps: HTMLAttributes<HTMLElement>; | ||
trackProps: DOMAttributes; | ||
/** Props for the output element, displaying the value of the slider thumbs. */ | ||
@@ -24,16 +25,22 @@ outputProps: OutputHTMLAttributes<HTMLOutputElement>; | ||
*/ | ||
export function useSlider(props: AriaSliderProps, state: SliderState, trackRef: RefObject<HTMLElement>): SliderAria; | ||
interface SliderThumbAria { | ||
export function useSlider<T extends number | number[]>(props: AriaSliderProps<T>, state: SliderState, trackRef: RefObject<Element | null>): SliderAria; | ||
export interface SliderThumbAria { | ||
/** Props for the root thumb element; handles the dragging motion. */ | ||
thumbProps: HTMLAttributes<HTMLElement>; | ||
/** Props for the range input. */ | ||
thumbProps: DOMAttributes; | ||
/** Props for the visually hidden range input element. */ | ||
inputProps: InputHTMLAttributes<HTMLInputElement>; | ||
/** Props for the label element for this thumb. */ | ||
/** Props for the label element for this thumb (optional). */ | ||
labelProps: LabelHTMLAttributes<HTMLLabelElement>; | ||
/** Whether this thumb is currently being dragged. */ | ||
isDragging: boolean; | ||
/** Whether the thumb is currently focused. */ | ||
isFocused: boolean; | ||
/** Whether the thumb is disabled. */ | ||
isDisabled: boolean; | ||
} | ||
interface SliderThumbOptions extends AriaSliderThumbProps { | ||
export interface AriaSliderThumbOptions extends AriaSliderThumbProps { | ||
/** A ref to the track element. */ | ||
trackRef: RefObject<HTMLElement>; | ||
trackRef: RefObject<Element | null>; | ||
/** A ref to the thumb input element. */ | ||
inputRef: RefObject<HTMLInputElement>; | ||
inputRef: RefObject<HTMLInputElement | null>; | ||
} | ||
@@ -46,4 +53,7 @@ /** | ||
*/ | ||
export function useSliderThumb(opts: SliderThumbOptions, state: SliderState): SliderThumbAria; | ||
export function useSliderThumb(opts: AriaSliderThumbOptions, state: SliderState): SliderThumbAria; | ||
export type { AriaSliderProps } from '@react-types/slider'; | ||
export type { AriaSliderThumbProps } from '@react-types/slider'; | ||
export type { Orientation } from '@react-types/shared'; | ||
//# sourceMappingURL=types.d.ts.map |
{ | ||
"name": "@react-aria/slider", | ||
"version": "3.0.0-nightly.2508+78347729", | ||
"version": "3.0.0-nightly-641446f65-240905", | ||
"description": "Slider", | ||
@@ -8,2 +8,7 @@ "license": "Apache-2.0", | ||
"module": "dist/module.js", | ||
"exports": { | ||
"types": "./dist/types.d.ts", | ||
"import": "./dist/import.mjs", | ||
"require": "./dist/main.js" | ||
}, | ||
"types": "dist/types.d.ts", | ||
@@ -21,15 +26,14 @@ "source": "src/index.ts", | ||
"dependencies": { | ||
"@babel/runtime": "^7.6.2", | ||
"@react-aria/focus": "3.0.0-nightly.830+78347729", | ||
"@react-aria/i18n": "3.0.0-nightly.830+78347729", | ||
"@react-aria/interactions": "3.0.0-nightly.830+78347729", | ||
"@react-aria/label": "3.0.0-nightly.830+78347729", | ||
"@react-aria/utils": "3.0.0-nightly.830+78347729", | ||
"@react-stately/radio": "3.0.0-nightly.830+78347729", | ||
"@react-stately/slider": "3.0.0-nightly.2508+78347729", | ||
"@react-types/radio": "3.0.0-nightly.830+78347729", | ||
"@react-types/slider": "3.0.0-nightly.2508+78347729" | ||
"@react-aria/focus": "^3.0.0-nightly-641446f65-240905", | ||
"@react-aria/i18n": "^3.0.0-nightly-641446f65-240905", | ||
"@react-aria/interactions": "^3.0.0-nightly-641446f65-240905", | ||
"@react-aria/label": "^3.0.0-nightly-641446f65-240905", | ||
"@react-aria/utils": "^3.0.0-nightly-641446f65-240905", | ||
"@react-stately/slider": "^3.0.0-nightly-641446f65-240905", | ||
"@react-types/shared": "^3.0.0-nightly-641446f65-240905", | ||
"@react-types/slider": "^3.0.0-nightly-641446f65-240905", | ||
"@swc/helpers": "^0.5.0" | ||
}, | ||
"peerDependencies": { | ||
"react": "^16.8.0 || ^17.0.0-rc.1" | ||
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0" | ||
}, | ||
@@ -39,3 +43,3 @@ "publishConfig": { | ||
}, | ||
"gitHead": "78347729a1af5d16e7fa90120a258841d4cb55a7" | ||
} | ||
"stableVersion": "3.7.11" | ||
} |
@@ -12,4 +12,8 @@ /* | ||
*/ | ||
export * from './useSlider'; | ||
export * from './useSliderThumb'; | ||
export {useSlider} from './useSlider'; | ||
export {useSliderThumb} from './useSliderThumb'; | ||
export type {AriaSliderProps} from '@react-types/slider'; | ||
export type {SliderAria} from './useSlider'; | ||
export type {AriaSliderThumbOptions, SliderThumbAria} from './useSliderThumb'; | ||
export type {AriaSliderThumbProps} from '@react-types/slider'; | ||
export type {Orientation} from '@react-types/shared'; |
@@ -15,4 +15,5 @@ /* | ||
import {clamp, mergeProps, useGlobalListeners} from '@react-aria/utils'; | ||
import {getSliderThumbId, sliderIds} from './utils'; | ||
import React, {HTMLAttributes, LabelHTMLAttributes, OutputHTMLAttributes, RefObject, useRef} from 'react'; | ||
import {DOMAttributes, RefObject} from '@react-types/shared'; | ||
import {getSliderThumbId, sliderData} from './utils'; | ||
import React, {LabelHTMLAttributes, OutputHTMLAttributes, useRef} from 'react'; | ||
import {setInteractionModality, useMove} from '@react-aria/interactions'; | ||
@@ -23,3 +24,3 @@ import {SliderState} from '@react-stately/slider'; | ||
interface SliderAria { | ||
export interface SliderAria { | ||
/** Props for the label element. */ | ||
@@ -29,6 +30,6 @@ labelProps: LabelHTMLAttributes<HTMLLabelElement>, | ||
/** Props for the root element of the slider component; groups slider inputs. */ | ||
groupProps: HTMLAttributes<HTMLElement>, | ||
groupProps: DOMAttributes, | ||
/** Props for the track element. */ | ||
trackProps: HTMLAttributes<HTMLElement>, | ||
trackProps: DOMAttributes, | ||
@@ -49,6 +50,6 @@ /** Props for the output element, displaying the value of the slider thumbs. */ | ||
*/ | ||
export function useSlider( | ||
props: AriaSliderProps, | ||
export function useSlider<T extends number | number[]>( | ||
props: AriaSliderProps<T>, | ||
state: SliderState, | ||
trackRef: RefObject<HTMLElement> | ||
trackRef: RefObject<Element | null> | ||
): SliderAria { | ||
@@ -60,3 +61,7 @@ let {labelProps, fieldProps} = useLabel(props); | ||
// Attach id of the label to the state so it can be accessed by useSliderThumb. | ||
sliderIds.set(state, labelProps.id ?? fieldProps.id); | ||
sliderData.set(state, { | ||
id: labelProps.id ?? fieldProps.id, | ||
'aria-describedby': props['aria-describedby'], | ||
'aria-details': props['aria-details'] | ||
}); | ||
@@ -73,4 +78,2 @@ let {direction} = useLocale(); | ||
const stateRef = useRef<SliderState>(null); | ||
stateRef.current = state; | ||
const reverseX = direction === 'rtl'; | ||
@@ -83,6 +86,7 @@ const currentPosition = useRef<number>(null); | ||
onMove({deltaX, deltaY}) { | ||
let size = isVertical ? trackRef.current.offsetHeight : trackRef.current.offsetWidth; | ||
let {height, width} = trackRef.current.getBoundingClientRect(); | ||
let size = isVertical ? height : width; | ||
if (currentPosition.current == null) { | ||
currentPosition.current = stateRef.current.getThumbPercent(realTimeTrackDraggingIndex.current) * size; | ||
currentPosition.current = state.getThumbPercent(realTimeTrackDraggingIndex.current) * size; | ||
} | ||
@@ -99,3 +103,3 @@ | ||
const percent = clamp(currentPosition.current / size, 0, 1); | ||
stateRef.current.setThumbPercent(realTimeTrackDraggingIndex.current, percent); | ||
state.setThumbPercent(realTimeTrackDraggingIndex.current, percent); | ||
} | ||
@@ -105,3 +109,3 @@ }, | ||
if (realTimeTrackDraggingIndex.current != null) { | ||
stateRef.current.setThumbDragging(realTimeTrackDraggingIndex.current, false); | ||
state.setThumbDragging(realTimeTrackDraggingIndex.current, false); | ||
realTimeTrackDraggingIndex.current = null; | ||
@@ -116,5 +120,6 @@ } | ||
if (trackRef.current && !props.isDisabled && state.values.every((_, i) => !state.isThumbDragging(i))) { | ||
let size = isVertical ? trackRef.current.offsetHeight : trackRef.current.offsetWidth; | ||
let {height, width, top, left} = trackRef.current.getBoundingClientRect(); | ||
let size = isVertical ? height : width; | ||
// Find the closest thumb | ||
const trackPosition = trackRef.current.getBoundingClientRect()[isVertical ? 'top' : 'left']; | ||
const trackPosition = isVertical ? top : left; | ||
const clickPosition = isVertical ? clientY : clientX; | ||
@@ -181,3 +186,3 @@ const offset = clickPosition - trackPosition; | ||
if (labelProps.htmlFor) { | ||
if ('htmlFor' in labelProps && labelProps.htmlFor) { | ||
// Ideally the `for` attribute should point to the first thumb, but VoiceOver on iOS | ||
@@ -206,3 +211,3 @@ // causes this to override the `aria-labelledby` on the thumb. This causes the first | ||
trackProps: mergeProps({ | ||
onMouseDown(e: React.MouseEvent<HTMLElement>) { | ||
onMouseDown(e: React.MouseEvent) { | ||
if (e.button !== 0 || e.altKey || e.ctrlKey || e.metaKey) { | ||
@@ -213,3 +218,3 @@ return; | ||
}, | ||
onPointerDown(e: React.PointerEvent<HTMLElement>) { | ||
onPointerDown(e: React.PointerEvent) { | ||
if (e.pointerType === 'mouse' && (e.button !== 0 || e.altKey || e.ctrlKey || e.metaKey)) { | ||
@@ -220,3 +225,7 @@ return; | ||
}, | ||
onTouchStart(e: React.TouchEvent<HTMLElement>) { onDownTrack(e, e.changedTouches[0].identifier, e.changedTouches[0].clientX, e.changedTouches[0].clientY); } | ||
onTouchStart(e: React.TouchEvent) { onDownTrack(e, e.changedTouches[0].identifier, e.changedTouches[0].clientX, e.changedTouches[0].clientY); }, | ||
style: { | ||
position: 'relative', | ||
touchAction: 'none' | ||
} | ||
}, moveProps), | ||
@@ -223,0 +232,0 @@ outputProps: { |
import {AriaSliderThumbProps} from '@react-types/slider'; | ||
import {clamp, focusWithoutScrolling, mergeProps, useGlobalListeners} from '@react-aria/utils'; | ||
import {getSliderThumbId, sliderIds} from './utils'; | ||
import React, {ChangeEvent, HTMLAttributes, InputHTMLAttributes, LabelHTMLAttributes, RefObject, useCallback, useEffect, useRef} from 'react'; | ||
import {clamp, focusWithoutScrolling, mergeProps, useFormReset, useGlobalListeners} from '@react-aria/utils'; | ||
import {DOMAttributes, RefObject} from '@react-types/shared'; | ||
import {getSliderThumbId, sliderData} from './utils'; | ||
import React, {ChangeEvent, InputHTMLAttributes, LabelHTMLAttributes, useCallback, useEffect, useRef} from 'react'; | ||
import {SliderState} from '@react-stately/slider'; | ||
import {useFocusable} from '@react-aria/focus'; | ||
import {useKeyboard, useMove} from '@react-aria/interactions'; | ||
import {useLabel} from '@react-aria/label'; | ||
import {useLocale} from '@react-aria/i18n'; | ||
import {useMove} from '@react-aria/interactions'; | ||
interface SliderThumbAria { | ||
export interface SliderThumbAria { | ||
/** Props for the root thumb element; handles the dragging motion. */ | ||
thumbProps: HTMLAttributes<HTMLElement>, | ||
thumbProps: DOMAttributes, | ||
/** Props for the range input. */ | ||
/** Props for the visually hidden range input element. */ | ||
inputProps: InputHTMLAttributes<HTMLInputElement>, | ||
/** Props for the label element for this thumb. */ | ||
labelProps: LabelHTMLAttributes<HTMLLabelElement> | ||
/** Props for the label element for this thumb (optional). */ | ||
labelProps: LabelHTMLAttributes<HTMLLabelElement>, | ||
/** Whether this thumb is currently being dragged. */ | ||
isDragging: boolean, | ||
/** Whether the thumb is currently focused. */ | ||
isFocused: boolean, | ||
/** Whether the thumb is disabled. */ | ||
isDisabled: boolean | ||
} | ||
interface SliderThumbOptions extends AriaSliderThumbProps { | ||
export interface AriaSliderThumbOptions extends AriaSliderThumbProps { | ||
/** A ref to the track element. */ | ||
trackRef: RefObject<HTMLElement>, | ||
trackRef: RefObject<Element | null>, | ||
/** A ref to the thumb input element. */ | ||
inputRef: RefObject<HTMLInputElement> | ||
inputRef: RefObject<HTMLInputElement | null> | ||
} | ||
@@ -36,15 +44,18 @@ | ||
export function useSliderThumb( | ||
opts: SliderThumbOptions, | ||
opts: AriaSliderThumbOptions, | ||
state: SliderState | ||
): SliderThumbAria { | ||
let { | ||
index, | ||
index = 0, | ||
isRequired, | ||
isDisabled, | ||
validationState, | ||
isInvalid, | ||
trackRef, | ||
inputRef | ||
inputRef, | ||
orientation = state.orientation, | ||
name | ||
} = opts; | ||
let isVertical = opts.orientation === 'vertical'; | ||
let isDisabled = opts.isDisabled || state.isDisabled; | ||
let isVertical = orientation === 'vertical'; | ||
@@ -54,7 +65,7 @@ let {direction} = useLocale(); | ||
let labelId = sliderIds.get(state); | ||
let data = sliderData.get(state); | ||
const {labelProps, fieldProps} = useLabel({ | ||
...opts, | ||
id: getSliderThumbId(state, index), | ||
'aria-labelledby': `${labelId} ${opts['aria-labelledby'] ?? ''}`.trim() | ||
'aria-labelledby': `${data.id} ${opts['aria-labelledby'] ?? ''}`.trim() | ||
}); | ||
@@ -78,6 +89,43 @@ | ||
const stateRef = useRef<SliderState>(null); | ||
stateRef.current = state; | ||
let reverseX = direction === 'rtl'; | ||
let currentPosition = useRef<number>(null); | ||
let {keyboardProps} = useKeyboard({ | ||
onKeyDown(e) { | ||
let { | ||
getThumbMaxValue, | ||
getThumbMinValue, | ||
decrementThumb, | ||
incrementThumb, | ||
setThumbValue, | ||
setThumbDragging, | ||
pageSize | ||
} = state; | ||
// these are the cases that useMove or useSlider don't handle | ||
if (!/^(PageUp|PageDown|Home|End)$/.test(e.key)) { | ||
e.continuePropagation(); | ||
return; | ||
} | ||
// same handling as useMove, stopPropagation to prevent useSlider from handling the event as well. | ||
e.preventDefault(); | ||
// remember to set this so that onChangeEnd is fired | ||
setThumbDragging(index, true); | ||
switch (e.key) { | ||
case 'PageUp': | ||
incrementThumb(index, pageSize); | ||
break; | ||
case 'PageDown': | ||
decrementThumb(index, pageSize); | ||
break; | ||
case 'Home': | ||
setThumbValue(index, getThumbMinValue(index)); | ||
break; | ||
case 'End': | ||
setThumbValue(index, getThumbMaxValue(index)); | ||
break; | ||
} | ||
setThumbDragging(index, false); | ||
} | ||
}); | ||
let {moveProps} = useMove({ | ||
@@ -88,13 +136,23 @@ onMoveStart() { | ||
}, | ||
onMove({deltaX, deltaY, pointerType}) { | ||
let size = isVertical ? trackRef.current.offsetHeight : trackRef.current.offsetWidth; | ||
onMove({deltaX, deltaY, pointerType, shiftKey}) { | ||
const { | ||
getThumbPercent, | ||
setThumbPercent, | ||
decrementThumb, | ||
incrementThumb, | ||
step, | ||
pageSize | ||
} = state; | ||
let {width, height} = trackRef.current.getBoundingClientRect(); | ||
let size = isVertical ? height : width; | ||
if (currentPosition.current == null) { | ||
currentPosition.current = stateRef.current.getThumbPercent(index) * size; | ||
currentPosition.current = getThumbPercent(index) * size; | ||
} | ||
if (pointerType === 'keyboard') { | ||
// (invert left/right according to language direction) + (according to vertical) | ||
let delta = ((reverseX ? -deltaX : deltaX) + (isVertical ? -deltaY : -deltaY)) * stateRef.current.step; | ||
currentPosition.current += delta * size; | ||
stateRef.current.setThumbValue(index, stateRef.current.getThumbValue(index) + delta); | ||
if ((deltaX > 0 && reverseX) || (deltaX < 0 && !reverseX) || deltaY > 0) { | ||
decrementThumb(index, shiftKey ? pageSize : step); | ||
} else { | ||
incrementThumb(index, shiftKey ? pageSize : step); | ||
} | ||
} else { | ||
@@ -107,3 +165,3 @@ let delta = isVertical ? deltaY : deltaX; | ||
currentPosition.current += delta; | ||
stateRef.current.setThumbPercent(index, clamp(currentPosition.current / size, 0, 1)); | ||
setThumbPercent(index, clamp(currentPosition.current / size, 0, 1)); | ||
} | ||
@@ -127,4 +185,4 @@ }, | ||
let currentPointer = useRef<number | null | undefined>(undefined); | ||
let onDown = (id: number | null) => { | ||
let currentPointer = useRef<number | undefined>(undefined); | ||
let onDown = (id?: number) => { | ||
focusInput(); | ||
@@ -151,2 +209,31 @@ currentPointer.current = id; | ||
let thumbPosition = state.getThumbPercent(index); | ||
if (isVertical || direction === 'rtl') { | ||
thumbPosition = 1 - thumbPosition; | ||
} | ||
let interactions = !isDisabled ? mergeProps( | ||
keyboardProps, | ||
moveProps, | ||
{ | ||
onMouseDown: (e: React.MouseEvent) => { | ||
if (e.button !== 0 || e.altKey || e.ctrlKey || e.metaKey) { | ||
return; | ||
} | ||
onDown(); | ||
}, | ||
onPointerDown: (e: React.PointerEvent) => { | ||
if (e.button !== 0 || e.altKey || e.ctrlKey || e.metaKey) { | ||
return; | ||
} | ||
onDown(e.pointerId); | ||
}, | ||
onTouchStart: (e: React.TouchEvent) => {onDown(e.changedTouches[0].identifier);} | ||
} | ||
) : {}; | ||
useFormReset(inputRef, value, (v) => { | ||
state.setThumbValue(index, v); | ||
}); | ||
// We install mouse handlers for the drag motion on the thumb div, but | ||
@@ -164,8 +251,11 @@ // not the key handler for moving the thumb with the slider. Instead, | ||
value: value, | ||
name, | ||
disabled: isDisabled, | ||
'aria-orientation': opts.orientation, | ||
'aria-orientation': orientation, | ||
'aria-valuetext': state.getThumbValueLabel(index), | ||
'aria-required': isRequired || undefined, | ||
'aria-invalid': validationState === 'invalid' || undefined, | ||
'aria-invalid': isInvalid || validationState === 'invalid' || undefined, | ||
'aria-errormessage': opts['aria-errormessage'], | ||
'aria-describedby': [data['aria-describedby'], opts['aria-describedby']].filter(Boolean).join(' '), | ||
'aria-details': [data['aria-details'], opts['aria-details']].filter(Boolean).join(' '), | ||
onChange: (e: ChangeEvent<HTMLInputElement>) => { | ||
@@ -175,22 +265,16 @@ state.setThumbValue(index, parseFloat(e.target.value)); | ||
}), | ||
thumbProps: !isDisabled ? mergeProps( | ||
moveProps, | ||
{ | ||
onMouseDown: (e: React.MouseEvent<HTMLElement>) => { | ||
if (e.button !== 0 || e.altKey || e.ctrlKey || e.metaKey) { | ||
return; | ||
} | ||
onDown(null); | ||
}, | ||
onPointerDown: (e: React.PointerEvent<HTMLElement>) => { | ||
if (e.button !== 0 || e.altKey || e.ctrlKey || e.metaKey) { | ||
return; | ||
} | ||
onDown(e.pointerId); | ||
}, | ||
onTouchStart: (e: React.TouchEvent<HTMLElement>) => {onDown(e.changedTouches[0].identifier);} | ||
thumbProps: { | ||
...interactions, | ||
style: { | ||
position: 'absolute', | ||
[isVertical ? 'top' : 'left']: `${thumbPosition * 100}%`, | ||
transform: 'translate(-50%, -50%)', | ||
touchAction: 'none' | ||
} | ||
) : {}, | ||
labelProps | ||
}, | ||
labelProps, | ||
isDragging: state.isThumbDragging(index), | ||
isDisabled, | ||
isFocused | ||
}; | ||
} |
import {SliderState} from '@react-stately/slider'; | ||
export const sliderIds = new WeakMap<SliderState, string>(); | ||
interface SliderData { | ||
id: string, | ||
'aria-describedby'?: string, | ||
'aria-details'?: string | ||
} | ||
export const sliderData = new WeakMap<SliderState, SliderData>(); | ||
export function getSliderThumbId(state: SliderState, index: number) { | ||
let id = sliderIds.get(state); | ||
if (!id) { | ||
let data = sliderData.get(state); | ||
if (!data) { | ||
throw new Error('Unknown slider state'); | ||
} | ||
return `${id}-${index}`; | ||
return `${data.id}-${index}`; | ||
} |
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
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Unidentified License
License(Experimental) Something that seems like a license was found, but its contents could not be matched with a known license.
Found 2 instances 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
142867
10
28
1637
2
80
+ Added@swc/helpers@^0.5.0
+ Added@formatjs/ecma402-abstract@2.2.3(transitive)
+ Added@formatjs/fast-memoize@2.2.3(transitive)
+ Added@formatjs/icu-messageformat-parser@2.9.3(transitive)
+ Added@formatjs/icu-skeleton-parser@1.8.7(transitive)
+ Added@formatjs/intl-localematcher@0.5.7(transitive)
+ Added@internationalized/date@3.5.6(transitive)
+ Added@internationalized/message@3.1.5(transitive)
+ Added@internationalized/number@3.5.4(transitive)
+ Added@internationalized/string@3.2.4(transitive)
+ Added@react-aria/focus@3.18.4(transitive)
+ Added@react-aria/i18n@3.12.3(transitive)
+ Added@react-aria/interactions@3.22.4(transitive)
+ Added@react-aria/label@3.7.12(transitive)
+ Added@react-aria/ssr@3.9.6(transitive)
+ Added@react-aria/utils@3.25.3(transitive)
+ Added@react-stately/slider@3.5.8(transitive)
+ Added@react-stately/utils@3.10.4(transitive)
+ Added@react-types/shared@3.25.0(transitive)
+ Added@react-types/slider@3.7.6(transitive)
+ Added@swc/helpers@0.5.15(transitive)
+ Addedclsx@2.1.1(transitive)
+ Addedintl-messageformat@10.7.6(transitive)
+ Addedreact@18.3.1(transitive)
+ Addedtslib@2.8.1(transitive)
- Removed@babel/runtime@^7.6.2
- Removed@babel/runtime@7.26.0(transitive)
- Removedobject-assign@4.1.1(transitive)
- Removedreact@17.0.2(transitive)
- Removedregenerator-runtime@0.14.1(transitive)
Updated@react-aria/interactions@^3.0.0-nightly-641446f65-240905
Updated@react-stately/slider@^3.0.0-nightly-641446f65-240905