Remove DraggableArea
in favor of useDrag
This commit is contained in:
parent
386a8fafac
commit
b547282d95
15 changed files with 179 additions and 338 deletions
|
@ -155,7 +155,7 @@ function useDragKeyframe(node: HTMLDivElement | null, props: IProps) {
|
|||
const gestureHandlers = useMemo<Parameters<typeof useDrag>[1]>(() => {
|
||||
return {
|
||||
debugName: 'useDragKeyframe',
|
||||
lockCursorTo: 'ew-resize',
|
||||
lockCSSCursorTo: 'ew-resize',
|
||||
onDragStart(event) {
|
||||
const props = propsRef.current
|
||||
let tempTransaction: CommitOrDiscard | undefined
|
||||
|
|
|
@ -253,7 +253,7 @@ function useKeyframeDrag(
|
|||
const handlers = useFreezableMemo<Parameters<typeof useDrag>[1]>(
|
||||
(setFrozen) => ({
|
||||
debugName: 'CurveSegmentEditor/useKeyframeDrag',
|
||||
lockCursorTo: 'move',
|
||||
lockCSSCursorTo: 'move',
|
||||
onDragStart() {
|
||||
setFrozen(true)
|
||||
return {
|
||||
|
|
|
@ -60,7 +60,7 @@ function useCaptureSelection(
|
|||
return {
|
||||
debugName: 'DopeSheetSelectionView/useCaptureSelection',
|
||||
dontBlockMouseDown: true,
|
||||
lockCursorTo: 'cell',
|
||||
lockCSSCursorTo: 'cell',
|
||||
onDragStart(event) {
|
||||
if (!event.shiftKey || event.target instanceof HTMLInputElement) {
|
||||
return false
|
||||
|
|
|
@ -265,7 +265,7 @@ function useHandlePanAndZoom(
|
|||
|
||||
debugName: 'HorizontallyScrollableArea Middle Button Drag',
|
||||
buttons: [1],
|
||||
lockCursorTo: 'grabbing',
|
||||
lockCSSCursorTo: 'grabbing',
|
||||
}
|
||||
}, [layoutP]),
|
||||
)
|
||||
|
|
|
@ -226,7 +226,7 @@ function useDragBulge(
|
|||
const gestureHandlers = useMemo<Parameters<typeof useDrag>[1]>(() => {
|
||||
return {
|
||||
debugName: 'LengthIndicator/useDragBulge',
|
||||
lockCursorTo: 'ew-resize',
|
||||
lockCSSCursorTo: 'ew-resize',
|
||||
onDragStart(event) {
|
||||
let tempTransaction: CommitOrDiscard | undefined
|
||||
|
||||
|
|
|
@ -125,7 +125,7 @@ function useOurDrags(node: SVGCircleElement | null, props: IProps): void {
|
|||
const handlers = useMemo<Parameters<typeof useDrag>[1]>(() => {
|
||||
return {
|
||||
debugName: 'CurveHandler/useOurDrags',
|
||||
lockCursorTo: 'move',
|
||||
lockCSSCursorTo: 'move',
|
||||
onDragStart() {
|
||||
let tempTransaction: CommitOrDiscard | undefined
|
||||
|
||||
|
|
|
@ -107,7 +107,7 @@ function useDragKeyframe(
|
|||
const gestureHandlers = useMemo<Parameters<typeof useDrag>[1]>(() => {
|
||||
return {
|
||||
debugName: 'GraphEditorDotNonScalar/useDragKeyframe',
|
||||
lockCursorTo: 'ew-resize',
|
||||
lockCSSCursorTo: 'ew-resize',
|
||||
onDragStart(event) {
|
||||
setIsDragging(true)
|
||||
const propsAtStartOfDrag = propsRef.current
|
||||
|
|
|
@ -108,7 +108,7 @@ function useDragKeyframe(
|
|||
const gestureHandlers = useMemo<Parameters<typeof useDrag>[1]>(() => {
|
||||
return {
|
||||
debugName: 'GraphEditorDotScalar/useDragKeyframe',
|
||||
lockCursorTo: 'move',
|
||||
lockCSSCursorTo: 'move',
|
||||
onDragStart(event) {
|
||||
setIsDragging(true)
|
||||
const keepSpeeds = !!event.altKey
|
||||
|
|
|
@ -246,7 +246,7 @@ const FocusRangeStrip: React.FC<{
|
|||
}
|
||||
},
|
||||
|
||||
lockCursorTo: 'grabbing',
|
||||
lockCSSCursorTo: 'grabbing',
|
||||
}
|
||||
}, [sheet, scaledSpaceToUnitSpace])
|
||||
|
||||
|
|
|
@ -169,7 +169,7 @@ function usePanelDragZoneGestureHandlers(
|
|||
}
|
||||
},
|
||||
|
||||
lockCursorTo: 'ew-resize',
|
||||
lockCSSCursorTo: 'ew-resize',
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -214,7 +214,7 @@ function usePanelDragZoneGestureHandlers(
|
|||
},
|
||||
}
|
||||
},
|
||||
lockCursorTo: 'move',
|
||||
lockCSSCursorTo: 'move',
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import type {SequenceEditorPanelLayout} from '@theatre/studio/panels/SequenceEditorPanel/layout/layout'
|
||||
import DraggableArea from '@theatre/studio/uiComponents/DraggableArea'
|
||||
import {useVal} from '@theatre/react'
|
||||
import type {IRange} from '@theatre/shared/utils/types'
|
||||
import type {Pointer} from '@theatre/dataverse'
|
||||
|
@ -11,6 +10,8 @@ import styled from 'styled-components'
|
|||
import {zIndexes} from '@theatre/studio/panels/SequenceEditorPanel/SequenceEditorPanel'
|
||||
import {includeLockFrameStampAttrs} from '@theatre/studio/panels/SequenceEditorPanel/FrameStampPositionProvider'
|
||||
import {pointerEventsAutoInNormalMode} from '@theatre/studio/css'
|
||||
import useRefAndState from '@theatre/studio/utils/useRefAndState'
|
||||
import useDrag from '@theatre/studio/uiComponents/useDrag'
|
||||
|
||||
const Container = styled.div`
|
||||
--threadHeight: 6px;
|
||||
|
@ -174,117 +175,143 @@ const HorizontalScrollbar: React.FC<{
|
|||
}
|
||||
|
||||
const self = {
|
||||
onDragStart() {
|
||||
onRangeDragStart() {
|
||||
noteValuesBeforeDrag()
|
||||
},
|
||||
onDragEnd() {
|
||||
setBeingDragged('nothing')
|
||||
},
|
||||
dragRange: (dx: number) => {
|
||||
setBeingDragged('both')
|
||||
const deltaPosInUnitSpace = deltaXToDeltaPos(dx)
|
||||
return {
|
||||
onDrag(dx: number) {
|
||||
setBeingDragged('both')
|
||||
const deltaPosInUnitSpace = deltaXToDeltaPos(dx)
|
||||
|
||||
const newRange: IRange = mapValues(
|
||||
valuesBeforeDrag.clippedSpaceRange,
|
||||
(p) => p + deltaPosInUnitSpace,
|
||||
)
|
||||
const newRange: IRange = mapValues(
|
||||
valuesBeforeDrag.clippedSpaceRange,
|
||||
(p) => p + deltaPosInUnitSpace,
|
||||
)
|
||||
|
||||
val(layoutP.clippedSpace.setRange)(newRange)
|
||||
val(layoutP.clippedSpace.setRange)(newRange)
|
||||
},
|
||||
onDragEnd() {
|
||||
setBeingDragged('nothing')
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
dragRangeStart: (dx: number) => {
|
||||
setBeingDragged('start')
|
||||
onRangeStartDragStart() {
|
||||
noteValuesBeforeDrag()
|
||||
return {
|
||||
onDrag(dx: number) {
|
||||
setBeingDragged('start')
|
||||
|
||||
const deltaPosInUnitSpace = deltaXToDeltaPos(dx)
|
||||
const deltaPosInUnitSpace = deltaXToDeltaPos(dx)
|
||||
|
||||
const newRange: IRange = {
|
||||
start: valuesBeforeDrag.clippedSpaceRange.start + deltaPosInUnitSpace,
|
||||
end: valuesBeforeDrag.clippedSpaceRange.end,
|
||||
const newRange: IRange = {
|
||||
start:
|
||||
valuesBeforeDrag.clippedSpaceRange.start + deltaPosInUnitSpace,
|
||||
end: valuesBeforeDrag.clippedSpaceRange.end,
|
||||
}
|
||||
|
||||
if (newRange.start > newRange.end - 1) {
|
||||
newRange.start = newRange.end - 1
|
||||
}
|
||||
|
||||
if (newRange.start <= 0) {
|
||||
newRange.start = 0
|
||||
}
|
||||
|
||||
val(layoutP.clippedSpace.setRange)(newRange)
|
||||
},
|
||||
onDragEnd() {
|
||||
setBeingDragged('nothing')
|
||||
},
|
||||
}
|
||||
|
||||
if (newRange.start > newRange.end - 1) {
|
||||
newRange.start = newRange.end - 1
|
||||
}
|
||||
|
||||
if (newRange.start <= 0) {
|
||||
newRange.start = 0
|
||||
}
|
||||
|
||||
val(layoutP.clippedSpace.setRange)(newRange)
|
||||
},
|
||||
|
||||
dragRangeEnd: (dx: number) => {
|
||||
setBeingDragged('end')
|
||||
onRangeEndDragStart() {
|
||||
noteValuesBeforeDrag()
|
||||
return {
|
||||
onDrag(dx: number) {
|
||||
setBeingDragged('end')
|
||||
|
||||
const deltaPosInUnitSpace = deltaXToDeltaPos(dx)
|
||||
const deltaPosInUnitSpace = deltaXToDeltaPos(dx)
|
||||
|
||||
const newRange: IRange = {
|
||||
start: valuesBeforeDrag.clippedSpaceRange.start,
|
||||
end: valuesBeforeDrag.clippedSpaceRange.end + deltaPosInUnitSpace,
|
||||
const newRange: IRange = {
|
||||
start: valuesBeforeDrag.clippedSpaceRange.start,
|
||||
end: valuesBeforeDrag.clippedSpaceRange.end + deltaPosInUnitSpace,
|
||||
}
|
||||
|
||||
if (newRange.end < newRange.start + 1) {
|
||||
newRange.end = newRange.start + 1
|
||||
}
|
||||
|
||||
if (newRange.end >= valuesBeforeDrag.assumedLengthOfSequence) {
|
||||
newRange.end = valuesBeforeDrag.assumedLengthOfSequence
|
||||
}
|
||||
|
||||
val(layoutP.clippedSpace.setRange)(newRange)
|
||||
},
|
||||
onDragEnd() {
|
||||
setBeingDragged('nothing')
|
||||
},
|
||||
}
|
||||
|
||||
if (newRange.end < newRange.start + 1) {
|
||||
newRange.end = newRange.start + 1
|
||||
}
|
||||
|
||||
if (newRange.end >= valuesBeforeDrag.assumedLengthOfSequence) {
|
||||
newRange.end = valuesBeforeDrag.assumedLengthOfSequence
|
||||
}
|
||||
|
||||
val(layoutP.clippedSpace.setRange)(newRange)
|
||||
},
|
||||
}
|
||||
|
||||
return self
|
||||
}, [layoutP, relevantValuesD])
|
||||
|
||||
const [refRangeDrag, nodeRangeDrag] = useRefAndState<HTMLDivElement | null>(
|
||||
null,
|
||||
)
|
||||
useDrag(nodeRangeDrag, {
|
||||
debugName: 'HorizontalScrollbar/onRangeDrag',
|
||||
onDragStart: handles.onRangeDragStart,
|
||||
lockCSSCursorTo: 'ew-resize',
|
||||
})
|
||||
|
||||
const [refRangeStartDrag, nodeRangeStartDrag] =
|
||||
useRefAndState<HTMLDivElement | null>(null)
|
||||
useDrag(nodeRangeStartDrag, {
|
||||
debugName: 'HorizontalScrollbar/onRangeStartDrag',
|
||||
onDragStart: handles.onRangeStartDragStart,
|
||||
lockCSSCursorTo: 'w-resize',
|
||||
})
|
||||
|
||||
const [refRangeEndDrag, nodeRangeEndDrag] =
|
||||
useRefAndState<HTMLDivElement | null>(null)
|
||||
useDrag(nodeRangeEndDrag, {
|
||||
debugName: 'HorizontalScrollbar/onRangeEndDrag',
|
||||
onDragStart: handles.onRangeEndDragStart,
|
||||
lockCSSCursorTo: 'e-resize',
|
||||
})
|
||||
|
||||
return (
|
||||
<Container
|
||||
style={{bottom: bottom + 8 + 'px'}}
|
||||
{...includeLockFrameStampAttrs('hide')}
|
||||
>
|
||||
<TimeThread>
|
||||
<DraggableArea
|
||||
onDragStart={handles.onDragStart}
|
||||
onDragEnd={handles.onDragEnd}
|
||||
onDrag={handles.dragRange}
|
||||
lockCursorTo="ew-resize"
|
||||
<RangeBar
|
||||
ref={refRangeDrag}
|
||||
style={{
|
||||
width: `${rangeEndX - rangeStartX}px`,
|
||||
transform: `translate3d(${rangeStartX}px, 0, 0)`,
|
||||
}}
|
||||
/>
|
||||
<RangeStartHandle
|
||||
ref={refRangeStartDrag}
|
||||
style={{transform: `translate3d(${rangeStartX}px, 0, 0)`}}
|
||||
>
|
||||
<RangeBar
|
||||
style={{
|
||||
width: `${rangeEndX - rangeStartX}px`,
|
||||
transform: `translate3d(${rangeStartX}px, 0, 0)`,
|
||||
}}
|
||||
/>
|
||||
</DraggableArea>
|
||||
<DraggableArea
|
||||
onDragStart={handles.onDragStart}
|
||||
onDrag={handles.dragRangeStart}
|
||||
lockCursorTo="w-resize"
|
||||
<Tooltip active={beingDragged === 'both' || beingDragged === 'start'}>
|
||||
{unitPosToHumanReadablePos(clippedSpaceRange.start)}
|
||||
</Tooltip>
|
||||
</RangeStartHandle>
|
||||
<RangeEndHandle
|
||||
ref={refRangeEndDrag}
|
||||
style={{transform: `translate3d(${rangeEndX}px, 0, 0)`}}
|
||||
>
|
||||
<RangeStartHandle
|
||||
style={{transform: `translate3d(${rangeStartX}px, 0, 0)`}}
|
||||
>
|
||||
<Tooltip
|
||||
active={beingDragged === 'both' || beingDragged === 'start'}
|
||||
>
|
||||
{unitPosToHumanReadablePos(clippedSpaceRange.start)}
|
||||
</Tooltip>
|
||||
</RangeStartHandle>
|
||||
</DraggableArea>
|
||||
<DraggableArea
|
||||
onDragStart={handles.onDragStart}
|
||||
onDrag={handles.dragRangeEnd}
|
||||
lockCursorTo="e-resize"
|
||||
>
|
||||
<RangeEndHandle
|
||||
style={{transform: `translate3d(${rangeEndX}px, 0, 0)`}}
|
||||
>
|
||||
<Tooltip active={beingDragged === 'both' || beingDragged === 'end'}>
|
||||
{unitPosToHumanReadablePos(clippedSpaceRange.end)}
|
||||
</Tooltip>
|
||||
</RangeEndHandle>
|
||||
</DraggableArea>
|
||||
<Tooltip active={beingDragged === 'both' || beingDragged === 'end'}>
|
||||
{unitPosToHumanReadablePos(clippedSpaceRange.end)}
|
||||
</Tooltip>
|
||||
</RangeEndHandle>
|
||||
</TimeThread>
|
||||
</Container>
|
||||
)
|
||||
|
|
|
@ -1,176 +0,0 @@
|
|||
import {voidFn} from '@theatre/shared/utils'
|
||||
import React from 'react'
|
||||
|
||||
function createCursorLock(cursor: string) {
|
||||
const el = document.createElement('div')
|
||||
el.style.cssText = `
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: 9999999;`
|
||||
|
||||
el.style.cursor = cursor
|
||||
document.body.appendChild(el)
|
||||
const relinquish = () => {
|
||||
document.body.removeChild(el)
|
||||
}
|
||||
|
||||
return relinquish
|
||||
}
|
||||
|
||||
type IProps = {
|
||||
children: React.ReactElement<HTMLElement | SVGElement>
|
||||
onDragStart?: (
|
||||
event: React.MouseEvent<HTMLElement | SVGElement>,
|
||||
) => void | false
|
||||
onDragEnd?: (dragHappened: boolean) => void
|
||||
onDrag: (dx: number, dy: number, event: MouseEvent) => void
|
||||
enabled?: boolean
|
||||
shouldReturnMovement?: boolean
|
||||
dontBlockMouseDown?: boolean
|
||||
lockCursorTo?: string
|
||||
}
|
||||
|
||||
type State = {
|
||||
dragHappened: boolean
|
||||
startPos: {
|
||||
x: number
|
||||
y: number
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO low-hanging-fruit replace all DraggableArea instances with `react-use-gesture`
|
||||
*/
|
||||
class DraggableArea extends React.PureComponent<IProps, {}> {
|
||||
s: State
|
||||
mode: 'dragStartCalled' | 'dragging' | 'notDragging' = 'notDragging'
|
||||
getDeltas: (e: MouseEvent) => [number, number]
|
||||
relinquishCursorLock: () => void = voidFn
|
||||
|
||||
constructor(props: IProps) {
|
||||
super(props)
|
||||
this.s = {
|
||||
dragHappened: false,
|
||||
startPos: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
},
|
||||
}
|
||||
if (props.shouldReturnMovement) {
|
||||
this.getDeltas = this.getMovements
|
||||
} else {
|
||||
this.getDeltas = this.getDistances
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const shouldRegisterEvents = this.props.enabled !== false
|
||||
|
||||
const children = shouldRegisterEvents
|
||||
? React.cloneElement(this.props.children, {
|
||||
onMouseDown: this.dragStartHandler,
|
||||
onClickCapture: this.disableUnwantedClick,
|
||||
})
|
||||
: this.props.children
|
||||
|
||||
return children
|
||||
}
|
||||
|
||||
addDragListeners() {
|
||||
document.addEventListener('mousemove', this.dragHandler)
|
||||
document.addEventListener('mouseup', this.dragEndHandler)
|
||||
}
|
||||
|
||||
removeDragListeners() {
|
||||
document.removeEventListener('mousemove', this.dragHandler)
|
||||
document.removeEventListener('mouseup', this.dragEndHandler)
|
||||
}
|
||||
|
||||
disableUnwantedClick = (event: MouseEvent) => {
|
||||
if (this.s.dragHappened) {
|
||||
if (!this.props.dontBlockMouseDown && this.mode !== 'notDragging') {
|
||||
event.stopPropagation()
|
||||
event.preventDefault()
|
||||
}
|
||||
this.s.dragHappened = false
|
||||
}
|
||||
}
|
||||
|
||||
UNSAFE_componentWillReceiveProps(newProps: IProps) {
|
||||
if (
|
||||
newProps.lockCursorTo !== this.props.lockCursorTo &&
|
||||
this.s.dragHappened
|
||||
) {
|
||||
this.relinquishCursorLock()
|
||||
this.relinquishCursorLock = voidFn
|
||||
if (newProps.lockCursorTo) {
|
||||
this.relinquishCursorLock = createCursorLock(newProps.lockCursorTo)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dragStartHandler = (event: React.MouseEvent<HTMLElement>) => {
|
||||
if (event.button !== 0) return
|
||||
const resultOfStart =
|
||||
this.props.onDragStart && this.props.onDragStart(event)
|
||||
|
||||
if (resultOfStart === false) return
|
||||
|
||||
if (!this.props.dontBlockMouseDown) {
|
||||
event.stopPropagation()
|
||||
event.preventDefault()
|
||||
}
|
||||
|
||||
this.mode = 'dragStartCalled'
|
||||
|
||||
const {screenX, screenY} = event
|
||||
this.s.startPos = {x: screenX, y: screenY}
|
||||
this.s.dragHappened = false
|
||||
|
||||
this.addDragListeners()
|
||||
}
|
||||
|
||||
dragEndHandler = () => {
|
||||
this.removeDragListeners()
|
||||
this.mode = 'notDragging'
|
||||
|
||||
this.props.onDragEnd && this.props.onDragEnd(this.s.dragHappened)
|
||||
this.relinquishCursorLock()
|
||||
this.relinquishCursorLock = voidFn
|
||||
}
|
||||
|
||||
dragHandler = (event: MouseEvent) => {
|
||||
if (!this.s.dragHappened && this.props.lockCursorTo) {
|
||||
this.relinquishCursorLock = createCursorLock(this.props.lockCursorTo)
|
||||
}
|
||||
if (!this.s.dragHappened) this.s.dragHappened = true
|
||||
this.mode = 'dragging'
|
||||
|
||||
const deltas = this.getDeltas(event)
|
||||
this.props.onDrag(deltas[0], deltas[1], event)
|
||||
}
|
||||
|
||||
getDistances(event: MouseEvent): [number, number] {
|
||||
const {startPos} = this.s
|
||||
return [event.screenX - startPos.x, event.screenY - startPos.y]
|
||||
}
|
||||
|
||||
getMovements(event: MouseEvent): [number, number] {
|
||||
return [event.movementX, event.movementY]
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this.mode !== 'notDragging') {
|
||||
this.relinquishCursorLock()
|
||||
this.relinquishCursorLock = voidFn
|
||||
this.removeDragListeners()
|
||||
this.props.onDragEnd && this.props.onDragEnd(this.mode === 'dragging')
|
||||
this.mode = 'notDragging'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default DraggableArea
|
|
@ -39,10 +39,10 @@ const CursorOverride = styled.div`
|
|||
`
|
||||
|
||||
type Context = {
|
||||
getLock: (className: string, cursor: string) => () => void
|
||||
getLock: (className: string, cursor?: string) => () => void
|
||||
}
|
||||
|
||||
type Lock = {className: string; cursor: string}
|
||||
type Lock = {className: string; cursor?: string}
|
||||
|
||||
const context = createContext<Context>({} as $IntentionalAny)
|
||||
|
||||
|
@ -51,7 +51,7 @@ const PointerEventsHandler: React.FC<{
|
|||
}> = (props) => {
|
||||
const [locks, setLocks] = useState<Lock[]>([])
|
||||
const contextValue = useMemo<Context>(() => {
|
||||
const getLock = (className: string, cursor: string) => {
|
||||
const getLock = (className: string, cursor?: string) => {
|
||||
const lock = {className, cursor}
|
||||
setLocks((s) => [...s, lock])
|
||||
const unlock = () => {
|
||||
|
@ -104,7 +104,7 @@ export const useCssCursorLock = (
|
|||
enabled: boolean,
|
||||
className: string,
|
||||
/** e.g. `"ew"`, `"help"`, `"pointer"`, `"text"`, etc */
|
||||
cursor: string,
|
||||
cursor?: string,
|
||||
) => {
|
||||
const ctx = useContext(context)
|
||||
useLayoutEffect(() => {
|
||||
|
|
|
@ -2,10 +2,10 @@ import {clamp, isInteger, round} from 'lodash-es'
|
|||
import type {MutableRefObject} from 'react'
|
||||
import React, {useMemo, useRef} from 'react'
|
||||
import styled from 'styled-components'
|
||||
import DraggableArea from '@theatre/studio/uiComponents/DraggableArea'
|
||||
import mergeRefs from 'react-merge-refs'
|
||||
import useRefAndState from '@theatre/studio/utils/useRefAndState'
|
||||
import useOnClickOutside from '@theatre/studio/uiComponents/useOnClickOutside'
|
||||
import useDrag from '@theatre/studio/uiComponents/useDrag'
|
||||
|
||||
const Container = styled.div`
|
||||
height: 100%;
|
||||
|
@ -71,9 +71,9 @@ const FillIndicator = styled.div`
|
|||
}
|
||||
`
|
||||
|
||||
function isFiniteFloat(s: string) {
|
||||
return isFinite(parseFloat(s))
|
||||
}
|
||||
const DragWrap = styled.div`
|
||||
display: contents;
|
||||
`
|
||||
|
||||
type IState_NoFocus = {
|
||||
mode: 'noFocus'
|
||||
|
@ -87,8 +87,6 @@ type IState_EditingViaKeyboard = {
|
|||
|
||||
type IState_Dragging = {
|
||||
mode: 'dragging'
|
||||
valueBeforeDragging: number
|
||||
currentDraggingValue: number
|
||||
}
|
||||
|
||||
type IState = IState_NoFocus | IState_EditingViaKeyboard | IState_Dragging
|
||||
|
@ -223,55 +221,50 @@ const BasicNumberInput: React.FC<{
|
|||
|
||||
stateRef.current = {
|
||||
mode: 'dragging',
|
||||
valueBeforeDragging: curValue,
|
||||
currentDraggingValue: curValue,
|
||||
}
|
||||
|
||||
let valueBeforeDragging = curValue
|
||||
let valueDuringDragging = curValue
|
||||
|
||||
bodyCursorBeforeDrag.current = document.body.style.cursor
|
||||
}
|
||||
|
||||
const onDragEnd = (happened: boolean) => {
|
||||
if (!happened) {
|
||||
propsRef.current.discardTemporaryValue()
|
||||
stateRef.current = {mode: 'noFocus'}
|
||||
return {
|
||||
// note: we use mx because we need to constrain the `valueDuringDragging`
|
||||
// and dx will keep accumulating past any constraints
|
||||
onDrag(_dx: number, _dy: number, _e: MouseEvent, mx: number) {
|
||||
const newValue =
|
||||
valueDuringDragging +
|
||||
propsA.nudge({
|
||||
deltaX: mx,
|
||||
deltaFraction: mx / inputWidth,
|
||||
magnitude: 1,
|
||||
})
|
||||
|
||||
inputRef.current!.focus()
|
||||
inputRef.current!.setSelectionRange(0, 100)
|
||||
} else {
|
||||
const curState = stateRef.current as IState_Dragging
|
||||
const value = curState.currentDraggingValue
|
||||
if (curState.valueBeforeDragging === value) {
|
||||
propsRef.current.discardTemporaryValue()
|
||||
} else {
|
||||
propsRef.current.permanentlySetValue(value)
|
||||
}
|
||||
stateRef.current = {mode: 'noFocus'}
|
||||
valueDuringDragging = propsA.range
|
||||
? clamp(newValue, propsA.range[0], propsA.range[1])
|
||||
: newValue
|
||||
|
||||
propsRef.current.temporarilySetValue(valueDuringDragging)
|
||||
},
|
||||
onDragEnd(happened: boolean) {
|
||||
if (!happened) {
|
||||
propsRef.current.discardTemporaryValue()
|
||||
stateRef.current = {mode: 'noFocus'}
|
||||
|
||||
inputRef.current!.focus()
|
||||
inputRef.current!.setSelectionRange(0, 100)
|
||||
} else {
|
||||
if (valueBeforeDragging === valueDuringDragging) {
|
||||
propsRef.current.discardTemporaryValue()
|
||||
} else {
|
||||
propsRef.current.permanentlySetValue(valueDuringDragging)
|
||||
}
|
||||
stateRef.current = {mode: 'noFocus'}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const onDrag = (deltaX: number, _dy: number) => {
|
||||
const curState = stateRef.current as IState_Dragging
|
||||
|
||||
let newValue =
|
||||
curState.valueBeforeDragging +
|
||||
propsA.nudge({
|
||||
deltaX,
|
||||
deltaFraction: deltaX / inputWidth,
|
||||
magnitude: 1,
|
||||
})
|
||||
|
||||
if (propsA.range) {
|
||||
newValue = clamp(newValue, propsA.range[0], propsA.range[1])
|
||||
}
|
||||
|
||||
stateRef.current = {
|
||||
...curState,
|
||||
currentDraggingValue: newValue,
|
||||
}
|
||||
|
||||
propsRef.current.temporarilySetValue(newValue)
|
||||
}
|
||||
|
||||
return {
|
||||
inputChange,
|
||||
onBlur,
|
||||
|
@ -279,8 +272,6 @@ const BasicNumberInput: React.FC<{
|
|||
onInputKeyDown,
|
||||
onClick,
|
||||
onFocus,
|
||||
onDragEnd,
|
||||
onDrag,
|
||||
}
|
||||
}, [])
|
||||
|
||||
|
@ -329,18 +320,17 @@ const BasicNumberInput: React.FC<{
|
|||
/>
|
||||
) : null
|
||||
|
||||
const [refDrag, nodeDrag] = useRefAndState<HTMLDivElement | null>(null)
|
||||
useDrag(nodeDrag, {
|
||||
debugName: 'form/BasicNumberInput',
|
||||
onDragStart: callbacks.transitionToDraggingMode,
|
||||
lockCSSCursorTo: 'ew-resize',
|
||||
disabled: stateRef.current.mode === 'editingViaKeyboard',
|
||||
})
|
||||
|
||||
return (
|
||||
<Container className={propsA.className + ' ' + stateRef.current.mode}>
|
||||
<DraggableArea
|
||||
key="draggableArea"
|
||||
onDragStart={callbacks.transitionToDraggingMode}
|
||||
onDragEnd={callbacks.onDragEnd}
|
||||
onDrag={callbacks.onDrag}
|
||||
enabled={stateRef.current.mode !== 'editingViaKeyboard'}
|
||||
lockCursorTo="ew-resize"
|
||||
>
|
||||
{theInput}
|
||||
</DraggableArea>
|
||||
<DragWrap ref={refDrag}>{theInput}</DragWrap>
|
||||
{fillIndicator}
|
||||
</Container>
|
||||
)
|
||||
|
|
|
@ -44,7 +44,7 @@ export type UseDragOpts = {
|
|||
/**
|
||||
* The css cursor property during the gesture will be locked to this value
|
||||
*/
|
||||
lockCursorTo?: string
|
||||
lockCSSCursorTo?: string
|
||||
/**
|
||||
* Called at the start of the gesture. Mind you, that this would be called, even
|
||||
* if the user is just clicking (and not dragging). However, if the gesture turns
|
||||
|
@ -88,9 +88,9 @@ export default function useDrag(
|
|||
>('notDragging')
|
||||
|
||||
useCssCursorLock(
|
||||
mode === 'dragging' && typeof opts.lockCursorTo === 'string',
|
||||
mode === 'dragging' && typeof opts.lockCSSCursorTo === 'string',
|
||||
'dragging',
|
||||
opts.lockCursorTo!,
|
||||
opts.lockCSSCursorTo,
|
||||
)
|
||||
|
||||
const {capturePointer} = usePointerCapturing(`useDrag for ${opts.debugName}`)
|
||||
|
|
Loading…
Reference in a new issue