Page Menu
Home
c4science
Search
Configure Global Search
Log In
Files
F99459337
useSlider.js
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Subscribers
None
File Metadata
Details
File Info
Storage
Attached
Created
Fri, Jan 24, 18:33
Size
18 KB
Mime Type
text/x-java
Expires
Sun, Jan 26, 18:33 (1 d, 18 h)
Engine
blob
Format
Raw Data
Handle
23797666
Attached To
rOACCT Open Access Compliance Check Tool (OACCT)
useSlider.js
View Options
import
_extends
from
"@babel/runtime/helpers/esm/extends"
;
import
*
as
React
from
'react'
;
import
{
unstable_useIsFocusVisible
as
useIsFocusVisible
,
unstable_useEnhancedEffect
as
useEnhancedEffect
,
unstable_ownerDocument
as
ownerDocument
,
unstable_useEventCallback
as
useEventCallback
,
unstable_useForkRef
as
useForkRef
,
unstable_useControlled
as
useControlled
,
visuallyHidden
}
from
'@mui/utils'
;
const
INTENTIONAL_DRAG_COUNT_THRESHOLD
=
2
;
function
asc
(
a
,
b
)
{
return
a
-
b
;
}
function
clamp
(
value
,
min
,
max
)
{
if
(
value
==
null
)
{
return
min
;
}
return
Math
.
min
(
Math
.
max
(
min
,
value
),
max
);
}
function
findClosest
(
values
,
currentValue
)
{
var
_values$reduce
;
const
{
index
:
closestIndex
}
=
(
_values$reduce
=
values
.
reduce
((
acc
,
value
,
index
)
=>
{
const
distance
=
Math
.
abs
(
currentValue
-
value
);
if
(
acc
===
null
||
distance
<
acc
.
distance
||
distance
===
acc
.
distance
)
{
return
{
distance
,
index
};
}
return
acc
;
},
null
))
!=
null
?
_values$reduce
:
{};
return
closestIndex
;
}
function
trackFinger
(
event
,
touchId
)
{
// The event is TouchEvent
if
(
touchId
.
current
!==
undefined
&&
event
.
changedTouches
)
{
const
touchEvent
=
event
;
for
(
let
i
=
0
;
i
<
touchEvent
.
changedTouches
.
length
;
i
+=
1
)
{
const
touch
=
touchEvent
.
changedTouches
[
i
];
if
(
touch
.
identifier
===
touchId
.
current
)
{
return
{
x
:
touch
.
clientX
,
y
:
touch
.
clientY
};
}
}
return
false
;
}
// The event is MouseEvent
return
{
x
:
event
.
clientX
,
y
:
event
.
clientY
};
}
export
function
valueToPercent
(
value
,
min
,
max
)
{
return
(
value
-
min
)
*
100
/
(
max
-
min
);
}
function
percentToValue
(
percent
,
min
,
max
)
{
return
(
max
-
min
)
*
percent
+
min
;
}
function
getDecimalPrecision
(
num
)
{
// This handles the case when num is very small (0.00000001), js will turn this into 1e-8.
// When num is bigger than 1 or less than -1 it won't get converted to this notation so it's fine.
if
(
Math
.
abs
(
num
)
<
1
)
{
const
parts
=
num
.
toExponential
().
split
(
'e-'
);
const
matissaDecimalPart
=
parts
[
0
].
split
(
'.'
)[
1
];
return
(
matissaDecimalPart
?
matissaDecimalPart
.
length
:
0
)
+
parseInt
(
parts
[
1
],
10
);
}
const
decimalPart
=
num
.
toString
().
split
(
'.'
)[
1
];
return
decimalPart
?
decimalPart
.
length
:
0
;
}
function
roundValueToStep
(
value
,
step
,
min
)
{
const
nearest
=
Math
.
round
((
value
-
min
)
/
step
)
*
step
+
min
;
return
Number
(
nearest
.
toFixed
(
getDecimalPrecision
(
step
)));
}
function
setValueIndex
({
values
,
newValue
,
index
})
{
const
output
=
values
.
slice
();
output
[
index
]
=
newValue
;
return
output
.
sort
(
asc
);
}
function
focusThumb
({
sliderRef
,
activeIndex
,
setActive
})
{
var
_sliderRef$current
,
_doc$activeElement
;
const
doc
=
ownerDocument
(
sliderRef
.
current
);
if
(
!
((
_sliderRef$current
=
sliderRef
.
current
)
!=
null
&&
_sliderRef$current
.
contains
(
doc
.
activeElement
))
||
Number
(
doc
==
null
?
void
0
:
(
_doc$activeElement
=
doc
.
activeElement
)
==
null
?
void
0
:
_doc$activeElement
.
getAttribute
(
'data-index'
))
!==
activeIndex
)
{
var
_sliderRef$current2
;
(
_sliderRef$current2
=
sliderRef
.
current
)
==
null
?
void
0
:
_sliderRef$current2
.
querySelector
(
`
[
type
=
"range"
][
data
-
index
=
"${activeIndex}"
]
`
).
focus
();
}
if
(
setActive
)
{
setActive
(
activeIndex
);
}
}
const
axisProps
=
{
horizontal
:
{
offset
:
percent
=>
({
left
:
`
$
{
percent
}
%
`
}),
leap
:
percent
=>
({
width
:
`
$
{
percent
}
%
`
})
},
'horizontal-reverse'
:
{
offset
:
percent
=>
({
right
:
`
$
{
percent
}
%
`
}),
leap
:
percent
=>
({
width
:
`
$
{
percent
}
%
`
})
},
vertical
:
{
offset
:
percent
=>
({
bottom
:
`
$
{
percent
}
%
`
}),
leap
:
percent
=>
({
height
:
`
$
{
percent
}
%
`
})
}
};
export
const
Identity
=
x
=>
x
;
// TODO: remove support for Safari < 13.
// https://caniuse.com/#search=touch-action
//
// Safari, on iOS, supports touch action since v13.
// Over 80% of the iOS phones are compatible
// in August 2020.
// Utilizing the CSS.supports method to check if touch-action is supported.
// Since CSS.supports is supported on all but Edge@12 and IE and touch-action
// is supported on both Edge@12 and IE if CSS.supports is not available that means that
// touch-action will be supported
let
cachedSupportsTouchActionNone
;
function
doesSupportTouchActionNone
()
{
if
(
cachedSupportsTouchActionNone
===
undefined
)
{
if
(
typeof
CSS
!==
'undefined'
&&
typeof
CSS
.
supports
===
'function'
)
{
cachedSupportsTouchActionNone
=
CSS
.
supports
(
'touch-action'
,
'none'
);
}
else
{
cachedSupportsTouchActionNone
=
true
;
}
}
return
cachedSupportsTouchActionNone
;
}
export
default
function
useSlider
(
parameters
)
{
const
{
'aria-labelledby'
:
ariaLabelledby
,
defaultValue
,
disabled
=
false
,
disableSwap
=
false
,
isRtl
=
false
,
marks
:
marksProp
=
false
,
max
=
100
,
min
=
0
,
name
,
onChange
,
onChangeCommitted
,
orientation
=
'horizontal'
,
ref
,
scale
=
Identity
,
step
=
1
,
tabIndex
,
value
:
valueProp
}
=
parameters
;
const
touchId
=
React
.
useRef
();
// We can't use the :active browser pseudo-classes.
// - The active state isn't triggered when clicking on the rail.
// - The active state isn't transferred when inversing a range slider.
const
[
active
,
setActive
]
=
React
.
useState
(
-
1
);
const
[
open
,
setOpen
]
=
React
.
useState
(
-
1
);
const
[
dragging
,
setDragging
]
=
React
.
useState
(
false
);
const
moveCount
=
React
.
useRef
(
0
);
const
[
valueDerived
,
setValueState
]
=
useControlled
({
controlled
:
valueProp
,
default
:
defaultValue
!=
null
?
defaultValue
:
min
,
name
:
'Slider'
});
const
handleChange
=
onChange
&&
((
event
,
value
,
thumbIndex
)
=>
{
// Redefine target to allow name and value to be read.
// This allows seamless integration with the most popular form libraries.
// https://github.com/mui/material-ui/issues/13485#issuecomment-676048492
// Clone the event to not override `target` of the original event.
const
nativeEvent
=
event
.
nativeEvent
||
event
;
// @ts-ignore The nativeEvent is function, not object
const
clonedEvent
=
new
nativeEvent
.
constructor
(
nativeEvent
.
type
,
nativeEvent
);
Object
.
defineProperty
(
clonedEvent
,
'target'
,
{
writable
:
true
,
value
:
{
value
,
name
}
});
onChange
(
clonedEvent
,
value
,
thumbIndex
);
});
const
range
=
Array
.
isArray
(
valueDerived
);
let
values
=
range
?
valueDerived
.
slice
().
sort
(
asc
)
:
[
valueDerived
];
values
=
values
.
map
(
value
=>
clamp
(
value
,
min
,
max
));
const
marks
=
marksProp
===
true
&&
step
!==
null
?
[...
Array
(
Math
.
floor
((
max
-
min
)
/
step
)
+
1
)].
map
((
_
,
index
)
=>
({
value
:
min
+
step
*
index
}))
:
marksProp
||
[];
const
marksValues
=
marks
.
map
(
mark
=>
mark
.
value
);
const
{
isFocusVisibleRef
,
onBlur
:
handleBlurVisible
,
onFocus
:
handleFocusVisible
,
ref
:
focusVisibleRef
}
=
useIsFocusVisible
();
const
[
focusedThumbIndex
,
setFocusedThumbIndex
]
=
React
.
useState
(
-
1
);
const
sliderRef
=
React
.
useRef
();
const
handleFocusRef
=
useForkRef
(
focusVisibleRef
,
sliderRef
);
const
handleRef
=
useForkRef
(
ref
,
handleFocusRef
);
const
createHandleHiddenInputFocus
=
otherHandlers
=>
event
=>
{
var
_otherHandlers$onFocu
;
const
index
=
Number
(
event
.
currentTarget
.
getAttribute
(
'data-index'
));
handleFocusVisible
(
event
);
if
(
isFocusVisibleRef
.
current
===
true
)
{
setFocusedThumbIndex
(
index
);
}
setOpen
(
index
);
otherHandlers
==
null
?
void
0
:
(
_otherHandlers$onFocu
=
otherHandlers
.
onFocus
)
==
null
?
void
0
:
_otherHandlers$onFocu
.
call
(
otherHandlers
,
event
);
};
const
createHandleHiddenInputBlur
=
otherHandlers
=>
event
=>
{
var
_otherHandlers$onBlur
;
handleBlurVisible
(
event
);
if
(
isFocusVisibleRef
.
current
===
false
)
{
setFocusedThumbIndex
(
-
1
);
}
setOpen
(
-
1
);
otherHandlers
==
null
?
void
0
:
(
_otherHandlers$onBlur
=
otherHandlers
.
onBlur
)
==
null
?
void
0
:
_otherHandlers$onBlur
.
call
(
otherHandlers
,
event
);
};
useEnhancedEffect
(()
=>
{
if
(
disabled
&&
sliderRef
.
current
.
contains
(
document
.
activeElement
))
{
var
_document$activeEleme
;
// This is necessary because Firefox and Safari will keep focus
// on a disabled element:
// https://codesandbox.io/s/mui-pr-22247-forked-h151h?file=/src/App.js
// @ts-ignore
(
_document$activeEleme
=
document
.
activeElement
)
==
null
?
void
0
:
_document$activeEleme
.
blur
();
}
},
[
disabled
]);
if
(
disabled
&&
active
!==
-
1
)
{
setActive
(
-
1
);
}
if
(
disabled
&&
focusedThumbIndex
!==
-
1
)
{
setFocusedThumbIndex
(
-
1
);
}
const
createHandleHiddenInputChange
=
otherHandlers
=>
event
=>
{
var
_otherHandlers$onChan
;
(
_otherHandlers$onChan
=
otherHandlers
.
onChange
)
==
null
?
void
0
:
_otherHandlers$onChan
.
call
(
otherHandlers
,
event
);
const
index
=
Number
(
event
.
currentTarget
.
getAttribute
(
'data-index'
));
const
value
=
values
[
index
];
const
marksIndex
=
marksValues
.
indexOf
(
value
);
// @ts-ignore
let
newValue
=
event
.
target
.
valueAsNumber
;
if
(
marks
&&
step
==
null
)
{
newValue
=
newValue
<
value
?
marksValues
[
marksIndex
-
1
]
:
marksValues
[
marksIndex
+
1
];
}
newValue
=
clamp
(
newValue
,
min
,
max
);
if
(
marks
&&
step
==
null
)
{
const
currentMarkIndex
=
marksValues
.
indexOf
(
values
[
index
]);
newValue
=
newValue
<
values
[
index
]
?
marksValues
[
currentMarkIndex
-
1
]
:
marksValues
[
currentMarkIndex
+
1
];
}
if
(
range
)
{
// Bound the new value to the thumb's neighbours.
if
(
disableSwap
)
{
newValue
=
clamp
(
newValue
,
values
[
index
-
1
]
||
-
Infinity
,
values
[
index
+
1
]
||
Infinity
);
}
const
previousValue
=
newValue
;
newValue
=
setValueIndex
({
values
,
newValue
,
index
});
let
activeIndex
=
index
;
// Potentially swap the index if needed.
if
(
!
disableSwap
)
{
activeIndex
=
newValue
.
indexOf
(
previousValue
);
}
focusThumb
({
sliderRef
,
activeIndex
});
}
setValueState
(
newValue
);
setFocusedThumbIndex
(
index
);
if
(
handleChange
)
{
handleChange
(
event
,
newValue
,
index
);
}
if
(
onChangeCommitted
)
{
onChangeCommitted
(
event
,
newValue
);
}
};
const
previousIndex
=
React
.
useRef
();
let
axis
=
orientation
;
if
(
isRtl
&&
orientation
===
'horizontal'
)
{
axis
+=
'-reverse'
;
}
const
getFingerNewValue
=
({
finger
,
move
=
false
})
=>
{
const
{
current
:
slider
}
=
sliderRef
;
const
{
width
,
height
,
bottom
,
left
}
=
slider
.
getBoundingClientRect
();
let
percent
;
if
(
axis
.
indexOf
(
'vertical'
)
===
0
)
{
percent
=
(
bottom
-
finger
.
y
)
/
height
;
}
else
{
percent
=
(
finger
.
x
-
left
)
/
width
;
}
if
(
axis
.
indexOf
(
'-reverse'
)
!==
-
1
)
{
percent
=
1
-
percent
;
}
let
newValue
;
newValue
=
percentToValue
(
percent
,
min
,
max
);
if
(
step
)
{
newValue
=
roundValueToStep
(
newValue
,
step
,
min
);
}
else
{
const
closestIndex
=
findClosest
(
marksValues
,
newValue
);
newValue
=
marksValues
[
closestIndex
];
}
newValue
=
clamp
(
newValue
,
min
,
max
);
let
activeIndex
=
0
;
if
(
range
)
{
if
(
!
move
)
{
activeIndex
=
findClosest
(
values
,
newValue
);
}
else
{
activeIndex
=
previousIndex
.
current
;
}
// Bound the new value to the thumb's neighbours.
if
(
disableSwap
)
{
newValue
=
clamp
(
newValue
,
values
[
activeIndex
-
1
]
||
-
Infinity
,
values
[
activeIndex
+
1
]
||
Infinity
);
}
const
previousValue
=
newValue
;
newValue
=
setValueIndex
({
values
,
newValue
,
index
:
activeIndex
});
// Potentially swap the index if needed.
if
(
!
(
disableSwap
&&
move
))
{
activeIndex
=
newValue
.
indexOf
(
previousValue
);
previousIndex
.
current
=
activeIndex
;
}
}
return
{
newValue
,
activeIndex
};
};
const
handleTouchMove
=
useEventCallback
(
nativeEvent
=>
{
const
finger
=
trackFinger
(
nativeEvent
,
touchId
);
if
(
!
finger
)
{
return
;
}
moveCount
.
current
+=
1
;
// Cancel move in case some other element consumed a mouseup event and it was not fired.
// @ts-ignore buttons doesn't not exists on touch event
if
(
nativeEvent
.
type
===
'mousemove'
&&
nativeEvent
.
buttons
===
0
)
{
// eslint-disable-next-line @typescript-eslint/no-use-before-define
handleTouchEnd
(
nativeEvent
);
return
;
}
const
{
newValue
,
activeIndex
}
=
getFingerNewValue
({
finger
,
move
:
true
});
focusThumb
({
sliderRef
,
activeIndex
,
setActive
});
setValueState
(
newValue
);
if
(
!
dragging
&&
moveCount
.
current
>
INTENTIONAL_DRAG_COUNT_THRESHOLD
)
{
setDragging
(
true
);
}
if
(
handleChange
&&
newValue
!==
valueDerived
)
{
handleChange
(
nativeEvent
,
newValue
,
activeIndex
);
}
});
const
handleTouchEnd
=
useEventCallback
(
nativeEvent
=>
{
const
finger
=
trackFinger
(
nativeEvent
,
touchId
);
setDragging
(
false
);
if
(
!
finger
)
{
return
;
}
const
{
newValue
}
=
getFingerNewValue
({
finger
,
move
:
true
});
setActive
(
-
1
);
if
(
nativeEvent
.
type
===
'touchend'
)
{
setOpen
(
-
1
);
}
if
(
onChangeCommitted
)
{
onChangeCommitted
(
nativeEvent
,
newValue
);
}
touchId
.
current
=
undefined
;
// eslint-disable-next-line @typescript-eslint/no-use-before-define
stopListening
();
});
const
handleTouchStart
=
useEventCallback
(
nativeEvent
=>
{
if
(
disabled
)
{
return
;
}
// If touch-action: none; is not supported we need to prevent the scroll manually.
if
(
!
doesSupportTouchActionNone
())
{
nativeEvent
.
preventDefault
();
}
const
touch
=
nativeEvent
.
changedTouches
[
0
];
if
(
touch
!=
null
)
{
// A number that uniquely identifies the current finger in the touch session.
touchId
.
current
=
touch
.
identifier
;
}
const
finger
=
trackFinger
(
nativeEvent
,
touchId
);
if
(
finger
!==
false
)
{
const
{
newValue
,
activeIndex
}
=
getFingerNewValue
({
finger
});
focusThumb
({
sliderRef
,
activeIndex
,
setActive
});
setValueState
(
newValue
);
if
(
handleChange
)
{
handleChange
(
nativeEvent
,
newValue
,
activeIndex
);
}
}
moveCount
.
current
=
0
;
const
doc
=
ownerDocument
(
sliderRef
.
current
);
doc
.
addEventListener
(
'touchmove'
,
handleTouchMove
);
doc
.
addEventListener
(
'touchend'
,
handleTouchEnd
);
});
const
stopListening
=
React
.
useCallback
(()
=>
{
const
doc
=
ownerDocument
(
sliderRef
.
current
);
doc
.
removeEventListener
(
'mousemove'
,
handleTouchMove
);
doc
.
removeEventListener
(
'mouseup'
,
handleTouchEnd
);
doc
.
removeEventListener
(
'touchmove'
,
handleTouchMove
);
doc
.
removeEventListener
(
'touchend'
,
handleTouchEnd
);
},
[
handleTouchEnd
,
handleTouchMove
]);
React
.
useEffect
(()
=>
{
const
{
current
:
slider
}
=
sliderRef
;
slider
.
addEventListener
(
'touchstart'
,
handleTouchStart
,
{
passive
:
doesSupportTouchActionNone
()
});
return
()
=>
{
// @ts-ignore
slider
.
removeEventListener
(
'touchstart'
,
handleTouchStart
,
{
passive
:
doesSupportTouchActionNone
()
});
stopListening
();
};
},
[
stopListening
,
handleTouchStart
]);
React
.
useEffect
(()
=>
{
if
(
disabled
)
{
stopListening
();
}
},
[
disabled
,
stopListening
]);
const
createHandleMouseDown
=
otherHandlers
=>
event
=>
{
var
_otherHandlers$onMous
;
(
_otherHandlers$onMous
=
otherHandlers
.
onMouseDown
)
==
null
?
void
0
:
_otherHandlers$onMous
.
call
(
otherHandlers
,
event
);
if
(
disabled
)
{
return
;
}
if
(
event
.
defaultPrevented
)
{
return
;
}
// Only handle left clicks
if
(
event
.
button
!==
0
)
{
return
;
}
// Avoid text selection
event
.
preventDefault
();
const
finger
=
trackFinger
(
event
,
touchId
);
if
(
finger
!==
false
)
{
const
{
newValue
,
activeIndex
}
=
getFingerNewValue
({
finger
});
focusThumb
({
sliderRef
,
activeIndex
,
setActive
});
setValueState
(
newValue
);
if
(
handleChange
)
{
handleChange
(
event
,
newValue
,
activeIndex
);
}
}
moveCount
.
current
=
0
;
const
doc
=
ownerDocument
(
sliderRef
.
current
);
doc
.
addEventListener
(
'mousemove'
,
handleTouchMove
);
doc
.
addEventListener
(
'mouseup'
,
handleTouchEnd
);
};
const
trackOffset
=
valueToPercent
(
range
?
values
[
0
]
:
min
,
min
,
max
);
const
trackLeap
=
valueToPercent
(
values
[
values
.
length
-
1
],
min
,
max
)
-
trackOffset
;
const
getRootProps
=
(
otherHandlers
=
{})
=>
{
const
ownEventHandlers
=
{
onMouseDown
:
createHandleMouseDown
(
otherHandlers
||
{})
};
const
mergedEventHandlers
=
_extends
({},
otherHandlers
,
ownEventHandlers
);
return
_extends
({
ref
:
handleRef
},
mergedEventHandlers
);
};
const
createHandleMouseOver
=
otherHandlers
=>
event
=>
{
var
_otherHandlers$onMous2
;
(
_otherHandlers$onMous2
=
otherHandlers
.
onMouseOver
)
==
null
?
void
0
:
_otherHandlers$onMous2
.
call
(
otherHandlers
,
event
);
const
index
=
Number
(
event
.
currentTarget
.
getAttribute
(
'data-index'
));
setOpen
(
index
);
};
const
createHandleMouseLeave
=
otherHandlers
=>
event
=>
{
var
_otherHandlers$onMous3
;
(
_otherHandlers$onMous3
=
otherHandlers
.
onMouseLeave
)
==
null
?
void
0
:
_otherHandlers$onMous3
.
call
(
otherHandlers
,
event
);
setOpen
(
-
1
);
};
const
getThumbProps
=
(
otherHandlers
=
{})
=>
{
const
ownEventHandlers
=
{
onMouseOver
:
createHandleMouseOver
(
otherHandlers
||
{}),
onMouseLeave
:
createHandleMouseLeave
(
otherHandlers
||
{})
};
return
_extends
({},
otherHandlers
,
ownEventHandlers
);
};
const
getHiddenInputProps
=
(
otherHandlers
=
{})
=>
{
var
_parameters$step
;
const
ownEventHandlers
=
{
onChange
:
createHandleHiddenInputChange
(
otherHandlers
||
{}),
onFocus
:
createHandleHiddenInputFocus
(
otherHandlers
||
{}),
onBlur
:
createHandleHiddenInputBlur
(
otherHandlers
||
{})
};
const
mergedEventHandlers
=
_extends
({},
otherHandlers
,
ownEventHandlers
);
return
_extends
({
tabIndex
,
'aria-labelledby'
:
ariaLabelledby
,
'aria-orientation'
:
orientation
,
'aria-valuemax'
:
scale
(
max
),
'aria-valuemin'
:
scale
(
min
),
name
,
type
:
'range'
,
min
:
parameters
.
min
,
max
:
parameters
.
max
,
step
:
(
_parameters$step
=
parameters
.
step
)
!=
null
?
_parameters$step
:
undefined
,
disabled
},
mergedEventHandlers
,
{
style
:
_extends
({},
visuallyHidden
,
{
direction
:
isRtl
?
'rtl'
:
'ltr'
,
// So that VoiceOver's focus indicator matches the thumb's dimensions
width
:
'100%'
,
height
:
'100%'
})
});
};
return
{
active
,
axis
:
axis
,
axisProps
,
dragging
,
focusedThumbIndex
,
getHiddenInputProps
,
getRootProps
,
getThumbProps
,
marks
:
marks
,
open
,
range
,
trackLeap
,
trackOffset
,
values
};
}
Event Timeline
Log In to Comment