Merge pull request #127 from theatre-js/dev/cl-2022-04/refactors-docs

Dev/cl 2022 04/refactors docs
This commit is contained in:
Cole Lawrence 2022-04-20 12:18:42 -04:00 committed by GitHub
commit 9f46a0b78f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 76 additions and 49 deletions

View file

@ -1,6 +1,8 @@
<!DOCTYPE html>
<html> <html>
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Playground Theatre.js</title> <title>Playground Theatre.js</title>
<style> <style>
body { body {

View file

@ -14,8 +14,7 @@ import type {
IValidCompoundProps, IValidCompoundProps,
ShorthandCompoundPropsToLonghandCompoundProps, ShorthandCompoundPropsToLonghandCompoundProps,
} from './internals' } from './internals'
import {sanitizeCompoundProps} from './internals' import {propTypeSymbol, sanitizeCompoundProps} from './internals'
import {propTypeSymbol} from './internals'
// eslint-disable-next-line unused-imports/no-unused-imports // eslint-disable-next-line unused-imports/no-unused-imports
import type SheetObject from '@theatre/core/sheetObjects/SheetObject' import type SheetObject from '@theatre/core/sheetObjects/SheetObject'
@ -91,9 +90,7 @@ const validateCommonOpts = (fnCallSignature: string, opts?: CommonOpts) => {
*/ */
export const compound = <Props extends IShorthandCompoundProps>( export const compound = <Props extends IShorthandCompoundProps>(
props: Props, props: Props,
opts: { opts: CommonOpts = {},
label?: string
} = {},
): PropTypeConfig_Compound< ): PropTypeConfig_Compound<
ShorthandCompoundPropsToLonghandCompoundProps<Props> ShorthandCompoundPropsToLonghandCompoundProps<Props>
> => { > => {
@ -287,9 +284,7 @@ const _interpolateNumber = (
export const rgba = ( export const rgba = (
defaultValue: Rgba = {r: 0, g: 0, b: 0, a: 1}, defaultValue: Rgba = {r: 0, g: 0, b: 0, a: 1},
opts: { opts: CommonOpts = {},
label?: string
} = {},
): PropTypeConfig_Rgba => { ): PropTypeConfig_Rgba => {
if (process.env.NODE_ENV !== 'production') { if (process.env.NODE_ENV !== 'production') {
validateCommonOpts('t.rgba(defaultValue, opts)', opts) validateCommonOpts('t.rgba(defaultValue, opts)', opts)
@ -337,6 +332,7 @@ export const rgba = (
} }
const _sanitizeRgba = (val: unknown): Rgba | undefined => { const _sanitizeRgba = (val: unknown): Rgba | undefined => {
if (!val) return undefined
let valid = true let valid = true
for (const c of ['r', 'g', 'b', 'a']) { for (const c of ['r', 'g', 'b', 'a']) {
if ( if (
@ -555,8 +551,6 @@ export function stringLiteral<
} }
} }
export type Sanitizer<T> = (value: unknown) => T | undefined
/** /**
* A linear interpolator for a certain value type. * A linear interpolator for a certain value type.
* *
@ -669,7 +663,7 @@ const defaultNumberNudgeFn: NumberNudgeFn = ({
export interface PropTypeConfig_Boolean export interface PropTypeConfig_Boolean
extends ISimplePropType<'boolean', boolean> {} extends ISimplePropType<'boolean', boolean> {}
interface CommonOpts { type CommonOpts = {
/** /**
* Each prop type may be given a custom label instead of the name of the sub-prop * Each prop type may be given a custom label instead of the name of the sub-prop
* it is in. * it is in.

View file

@ -17,6 +17,16 @@ export type IValidCompoundProps = {
[K in string]: PropTypeConfig [K in string]: PropTypeConfig
} }
/**
*
* This does not include Rgba since Rgba does not have a predictable
* object shape. We prefer to infer that compound props are described as
* `Record<string, IShorthandProp>` for now.
*
* In the future, it might be reasonable to wrap these types up into something
* which would allow us to differentiate between values at runtime
* (e.g. `val.type = "Rgba"` vs `val.type = "Compound"` etc)
*/
type IShorthandProp = type IShorthandProp =
| string | string
| number | number

View file

@ -23,7 +23,7 @@ const BooleanPropEditor: React.FC<{
const onChange = useCallback( const onChange = useCallback(
(el: React.ChangeEvent<HTMLInputElement>) => { (el: React.ChangeEvent<HTMLInputElement>) => {
stuff.permenantlySetValue(Boolean(el.target.checked)) stuff.permanentlySetValue(Boolean(el.target.checked))
}, },
[propConfig, pointerToProp, obj], [propConfig, pointerToProp, obj],
) )

View file

@ -29,7 +29,7 @@ const NumberPropEditor: React.FC<{
value={stuff.value} value={stuff.value}
temporarilySetValue={stuff.temporarilySetValue} temporarilySetValue={stuff.temporarilySetValue}
discardTemporaryValue={stuff.discardTemporaryValue} discardTemporaryValue={stuff.discardTemporaryValue}
permenantlySetValue={stuff.permenantlySetValue} permanentlySetValue={stuff.permanentlySetValue}
range={propConfig.range} range={propConfig.range}
nudge={nudge} nudge={nudge}
/> />

View file

@ -76,7 +76,7 @@ const RgbaPropEditor: React.FC<{
const onChange = useCallback( const onChange = useCallback(
(color: string) => { (color: string) => {
const rgba = decorateRgba(parseRgbaFromHex(color)) const rgba = decorateRgba(parseRgbaFromHex(color))
stuff.permenantlySetValue(rgba) stuff.permanentlySetValue(rgba)
}, },
[stuff], [stuff],
) )
@ -98,7 +98,7 @@ const RgbaPropEditor: React.FC<{
permanentlySetValue={(color) => { permanentlySetValue={(color) => {
// console.log('perm') // console.log('perm')
const rgba = decorateRgba(color) const rgba = decorateRgba(color)
stuff.permenantlySetValue(rgba) stuff.permanentlySetValue(rgba)
}} }}
discardTemporaryValue={stuff.discardTemporaryValue} discardTemporaryValue={stuff.discardTemporaryValue}
/> />
@ -120,7 +120,7 @@ const RgbaPropEditor: React.FC<{
value={rgba2hex(stuff.value)} value={rgba2hex(stuff.value)}
temporarilySetValue={noop} temporarilySetValue={noop}
discardTemporaryValue={noop} discardTemporaryValue={noop}
permenantlySetValue={onChange} permanentlySetValue={onChange}
isValid={(v) => !!v.match(/^#?([0-9a-f]{8})$/i)} isValid={(v) => !!v.match(/^#?([0-9a-f]{8})$/i)}
/> />
</RowContainer> </RowContainer>

View file

@ -20,7 +20,7 @@ const StringLiteralPropEditor: React.FC<{
const onChange = useCallback( const onChange = useCallback(
(val: string) => { (val: string) => {
stuff.permenantlySetValue(val) stuff.permanentlySetValue(val)
}, },
[propConfig, pointerToProp, obj], [propConfig, pointerToProp, obj],
) )

View file

@ -22,7 +22,7 @@ const StringPropEditor: React.FC<{
value={stuff.value} value={stuff.value}
temporarilySetValue={stuff.temporarilySetValue} temporarilySetValue={stuff.temporarilySetValue}
discardTemporaryValue={stuff.discardTemporaryValue} discardTemporaryValue={stuff.discardTemporaryValue}
permenantlySetValue={stuff.permenantlySetValue} permanentlySetValue={stuff.permanentlySetValue}
/> />
</SingleRowPropEditor> </SingleRowPropEditor>
) )

View file

@ -23,7 +23,7 @@ interface CommonStuff<T> {
temporarilySetValue(v: T): void temporarilySetValue(v: T): void
discardTemporaryValue(): void discardTemporaryValue(): void
permenantlySetValue(v: T): void permanentlySetValue(v: T): void
} }
interface Default<T> extends CommonStuff<T> { interface Default<T> extends CommonStuff<T> {
@ -76,7 +76,7 @@ export function useEditingToolsForPrimitiveProp<
currentScrub = null currentScrub = null
} }
}, },
permenantlySetValue(v: T): void { permanentlySetValue(v: T): void {
if (currentScrub) { if (currentScrub) {
currentScrub.capture((api) => { currentScrub.capture((api) => {
api.set(pointerToProp, v) api.set(pointerToProp, v)

View file

@ -192,7 +192,7 @@ const CurveEditorPopover: React.FC<
tempTransaction = undefined tempTransaction = undefined
} }
}, },
permenantlySetValue(newCurve: string): void { permanentlySetValue(newCurve: string): void {
if (tempTransaction) { if (tempTransaction) {
tempTransaction.discard() tempTransaction.discard()
tempTransaction = undefined tempTransaction = undefined
@ -282,7 +282,7 @@ const CurveEditorPopover: React.FC<
props.onRequestClose() props.onRequestClose()
} }
if (e.key === 'Enter') { if (e.key === 'Enter') {
fns.permenantlySetValue(filter) fns.permanentlySetValue(filter)
props.onRequestClose() props.onRequestClose()
} }
}} }}
@ -302,7 +302,7 @@ const CurveEditorPopover: React.FC<
if (e.key === 'Escape') { if (e.key === 'Escape') {
props.onRequestClose() props.onRequestClose()
} else if (e.key === 'Enter') { } else if (e.key === 'Enter') {
fns.permenantlySetValue(preset.value) fns.permanentlySetValue(preset.value)
props.onRequestClose() props.onRequestClose()
} }
if (e.key === 'ArrowRight') { if (e.key === 'ArrowRight') {
@ -358,7 +358,7 @@ const CurveEditorPopover: React.FC<
ref={optionsRef.current[preset.label]} ref={optionsRef.current[preset.label]}
key={preset.label} key={preset.label}
onClick={() => { onClick={() => {
fns.permenantlySetValue(preset.value) fns.permanentlySetValue(preset.value)
props.onRequestClose() props.onRequestClose()
}} }}
// Temporarily apply on hover // Temporarily apply on hover

View file

@ -14,7 +14,7 @@ import styled from 'styled-components'
import type KeyframeEditor from './KeyframeEditor' import type KeyframeEditor from './KeyframeEditor'
import {useLockFrameStampPosition} from '@theatre/studio/panels/SequenceEditorPanel/FrameStampPositionProvider' import {useLockFrameStampPosition} from '@theatre/studio/panels/SequenceEditorPanel/FrameStampPositionProvider'
import {attributeNameThatLocksFramestamp} from '@theatre/studio/panels/SequenceEditorPanel/FrameStampPositionProvider' import {attributeNameThatLocksFramestamp} from '@theatre/studio/panels/SequenceEditorPanel/FrameStampPositionProvider'
import {useCursorLock} from '@theatre/studio/uiComponents/PointerEventsHandler' import {useCssCursorLock} from '@theatre/studio/uiComponents/PointerEventsHandler'
import SnapCursor from './SnapCursor.svg' import SnapCursor from './SnapCursor.svg'
import selectedKeyframeIdsIfInSingleTrack from '@theatre/studio/panels/SequenceEditorPanel/DopeSheet/Right/BasicKeyframedTrack/selectedKeyframeIdsIfInSingleTrack' import selectedKeyframeIdsIfInSingleTrack from '@theatre/studio/panels/SequenceEditorPanel/DopeSheet/Right/BasicKeyframedTrack/selectedKeyframeIdsIfInSingleTrack'
@ -237,7 +237,7 @@ function useDragKeyframe(
useDrag(node, gestureHandlers) useDrag(node, gestureHandlers)
useCursorLock(isDragging, 'draggingPositionInSequenceEditor', 'ew-resize') useCssCursorLock(isDragging, 'draggingPositionInSequenceEditor', 'ew-resize')
return [isDragging] return [isDragging]
} }

View file

@ -10,7 +10,7 @@ import React, {useLayoutEffect, useMemo} from 'react'
import styled from 'styled-components' import styled from 'styled-components'
import {useReceiveVerticalWheelEvent} from '@theatre/studio/panels/SequenceEditorPanel/VerticalScrollContainer' import {useReceiveVerticalWheelEvent} from '@theatre/studio/panels/SequenceEditorPanel/VerticalScrollContainer'
import {pointerEventsAutoInNormalMode} from '@theatre/studio/css' import {pointerEventsAutoInNormalMode} from '@theatre/studio/css'
import {useCursorLock} from '@theatre/studio/uiComponents/PointerEventsHandler' import {useCssCursorLock} from '@theatre/studio/uiComponents/PointerEventsHandler'
import type {IRange} from '@theatre/shared/utils/types' import type {IRange} from '@theatre/shared/utils/types'
const Container = styled.div` const Container = styled.div`
@ -80,7 +80,7 @@ function useDragHandlers(
const setIsSeeking = val(layoutP.seeker.setIsSeeking) const setIsSeeking = val(layoutP.seeker.setIsSeeking)
return { return {
onDrag(dx: number, _, event) { onDrag(dx, _, event) {
const deltaPos = scaledSpaceToUnitSpace(dx) const deltaPos = scaledSpaceToUnitSpace(dx)
const unsnappedPos = clamp(posBeforeSeek + deltaPos, 0, sequence.length) const unsnappedPos = clamp(posBeforeSeek + deltaPos, 0, sequence.length)
@ -104,8 +104,12 @@ function useDragHandlers(
sequence.position = newPosition sequence.position = newPosition
}, },
onDragStart(event) { onDragStart(event) {
if (event.target instanceof HTMLInputElement) return false if (event.target instanceof HTMLInputElement) {
// editing some value
return false
}
if (event.shiftKey || event.altKey || event.ctrlKey || event.metaKey) { if (event.shiftKey || event.altKey || event.ctrlKey || event.metaKey) {
// e.g. marquee selection has shiftKey
return false return false
} }
if ( if (
@ -113,6 +117,9 @@ function useDragHandlers(
.composedPath() .composedPath()
.some((el) => el instanceof HTMLElement && el.draggable === true) .some((el) => el instanceof HTMLElement && el.draggable === true)
) { ) {
// Question: I think to check if we want another descendent element
// to be able to take control of this drag event.
// Question: e.g. for `useDragKeyframe`?
return false return false
} }
@ -139,9 +146,9 @@ function useDragHandlers(
} }
}, [layoutP, containerEl]) }, [layoutP, containerEl])
const [isDragigng] = useDrag(containerEl, handlers) const [isDragging] = useDrag(containerEl, handlers)
useCursorLock(isDragigng, 'draggingPositionInSequenceEditor', 'ew-resize') useCssCursorLock(isDragging, 'draggingPositionInSequenceEditor', 'ew-resize')
} }
function useHandlePanAndZoom( function useHandlePanAndZoom(

View file

@ -57,7 +57,7 @@ const LengthEditorPopover: React.FC<{
tempTransaction = undefined tempTransaction = undefined
} }
}, },
permenantlySetValue(newLength: number): void { permanentlySetValue(newLength: number): void {
if (tempTransaction) { if (tempTransaction) {
tempTransaction.discard() tempTransaction.discard()
tempTransaction = undefined tempTransaction = undefined

View file

@ -13,7 +13,7 @@ import type {Keyframe} from '@theatre/core/projects/store/types/SheetState_Histo
import {useLockFrameStampPosition} from '@theatre/studio/panels/SequenceEditorPanel/FrameStampPositionProvider' import {useLockFrameStampPosition} from '@theatre/studio/panels/SequenceEditorPanel/FrameStampPositionProvider'
import {attributeNameThatLocksFramestamp} from '@theatre/studio/panels/SequenceEditorPanel/FrameStampPositionProvider' import {attributeNameThatLocksFramestamp} from '@theatre/studio/panels/SequenceEditorPanel/FrameStampPositionProvider'
import {pointerEventsAutoInNormalMode} from '@theatre/studio/css' import {pointerEventsAutoInNormalMode} from '@theatre/studio/css'
import {useCursorLock} from '@theatre/studio/uiComponents/PointerEventsHandler' import {useCssCursorLock} from '@theatre/studio/uiComponents/PointerEventsHandler'
export const dotSize = 6 export const dotSize = 6
@ -170,7 +170,7 @@ function useDragKeyframe(
}, []) }, [])
useDrag(node, gestureHandlers) useDrag(node, gestureHandlers)
useCursorLock(isDragging, 'draggingPositionInSequenceEditor', 'ew-resize') useCssCursorLock(isDragging, 'draggingPositionInSequenceEditor', 'ew-resize')
return isDragging return isDragging
} }

View file

@ -13,7 +13,7 @@ import type {Keyframe} from '@theatre/core/projects/store/types/SheetState_Histo
import {useLockFrameStampPosition} from '@theatre/studio/panels/SequenceEditorPanel/FrameStampPositionProvider' import {useLockFrameStampPosition} from '@theatre/studio/panels/SequenceEditorPanel/FrameStampPositionProvider'
import {attributeNameThatLocksFramestamp} from '@theatre/studio/panels/SequenceEditorPanel/FrameStampPositionProvider' import {attributeNameThatLocksFramestamp} from '@theatre/studio/panels/SequenceEditorPanel/FrameStampPositionProvider'
import {pointerEventsAutoInNormalMode} from '@theatre/studio/css' import {pointerEventsAutoInNormalMode} from '@theatre/studio/css'
import {useCursorLock} from '@theatre/studio/uiComponents/PointerEventsHandler' import {useCssCursorLock} from '@theatre/studio/uiComponents/PointerEventsHandler'
export const dotSize = 6 export const dotSize = 6
@ -224,7 +224,7 @@ function useDragKeyframe(
}, []) }, [])
useDrag(node, gestureHandlers) useDrag(node, gestureHandlers)
useCursorLock(isDragging, 'draggingPositionInSequenceEditor', 'move') useCssCursorLock(isDragging, 'draggingPositionInSequenceEditor', 'move')
return isDragging return isDragging
} }

View file

@ -7,7 +7,7 @@ import getStudio from '@theatre/studio/getStudio'
import type {SequenceEditorPanelLayout} from '@theatre/studio/panels/SequenceEditorPanel/layout/layout' import type {SequenceEditorPanelLayout} from '@theatre/studio/panels/SequenceEditorPanel/layout/layout'
import {topStripHeight} from '@theatre/studio/panels/SequenceEditorPanel/RightOverlay/TopStrip' import {topStripHeight} from '@theatre/studio/panels/SequenceEditorPanel/RightOverlay/TopStrip'
import type {CommitOrDiscard} from '@theatre/studio/StudioStore/StudioStore' import type {CommitOrDiscard} from '@theatre/studio/StudioStore/StudioStore'
import {useCursorLock} from '@theatre/studio/uiComponents/PointerEventsHandler' import {useCssCursorLock} from '@theatre/studio/uiComponents/PointerEventsHandler'
import useDrag from '@theatre/studio/uiComponents/useDrag' import useDrag from '@theatre/studio/uiComponents/useDrag'
import useRefAndState from '@theatre/studio/utils/useRefAndState' import useRefAndState from '@theatre/studio/utils/useRefAndState'
import React, {useMemo, useRef, useState} from 'react' import React, {useMemo, useRef, useState} from 'react'
@ -211,7 +211,7 @@ const FocusRangeThumb: React.FC<{
useDrag(hitZoneNode, gestureHandlers) useDrag(hitZoneNode, gestureHandlers)
useCursorLock(isDragging, 'draggingPositionInSequenceEditor', 'ew-resize') useCssCursorLock(isDragging, 'draggingPositionInSequenceEditor', 'ew-resize')
return usePrism(() => { return usePrism(() => {
const existingRange = existingRangeD.getValue() const existingRange = existingRangeD.getValue()

View file

@ -133,6 +133,8 @@ const Playhead: React.FC<{layoutP: Pointer<SequenceEditorPanelLayout>}> = ({
const scaledSpaceToUnitSpace = val(layoutP.scaledSpace.toUnitSpace) const scaledSpaceToUnitSpace = val(layoutP.scaledSpace.toUnitSpace)
// This may not currently snap correctly like it does when grabbing the "Rod".
// See https://www.notion.so/theatrejs/dragging-from-playhead-does-not-snap-dadac4fa755149cebbcb70a655c3a0d5
const gestureHandlers = useMemo((): Parameters<typeof useDrag>[1] => { const gestureHandlers = useMemo((): Parameters<typeof useDrag>[1] => {
const setIsSeeking = val(layoutP.seeker.setIsSeeking) const setIsSeeking = val(layoutP.seeker.setIsSeeking)

View file

@ -55,7 +55,7 @@ const PlayheadPositionPopover: React.FC<{
sequence.position = originalPosition sequence.position = originalPosition
} }
}, },
permenantlySetValue(newPosition: number): void { permanentlySetValue(newPosition: number): void {
if (tempPosition) { if (tempPosition) {
tempPosition = undefined tempPosition = undefined
} }

View file

@ -1,2 +0,0 @@
// export const
export {}

View file

@ -68,9 +68,23 @@ const PointerEventsHandler: React.FC<{
) )
} }
export const useCursorLock = ( /**
* A "locking" mechanism for managing style.cursor values.
*
* Putting this behind a lock is important so we can properly manage
* multiple features all coordinating to style the cursor.
*
* This will also track a stack of different cursor styles so that
* adding a style to be the "foremost" cursor can override a previous style,
* but then "unlocking" that style will again reveal the existing styles.
*
* It behaves a bit like a stack.
*/
export const useCssCursorLock = (
/** Whether to enable the provided cursor style */
enabled: boolean, enabled: boolean,
className: string, className: string,
/** e.g. `"ew"`, `"help"`, `"pointer"`, `"text"`, etc */
cursor: string, cursor: string,
) => { ) => {
const ctx = useContext(context) const ctx = useContext(context)

View file

@ -105,7 +105,7 @@ const BasicNumberInput: React.FC<{
value: number value: number
temporarilySetValue: (v: number) => void temporarilySetValue: (v: number) => void
discardTemporaryValue: () => void discardTemporaryValue: () => void
permenantlySetValue: (v: number) => void permanentlySetValue: (v: number) => void
className?: string className?: string
range?: [min: number, max: number] range?: [min: number, max: number]
isValid?: (v: number) => boolean isValid?: (v: number) => boolean
@ -167,7 +167,7 @@ const BasicNumberInput: React.FC<{
if (curState.valueBeforeEditing === value) { if (curState.valueBeforeEditing === value) {
propsRef.current.discardTemporaryValue() propsRef.current.discardTemporaryValue()
} else { } else {
propsRef.current.permenantlySetValue(value) propsRef.current.permanentlySetValue(value)
} }
} }
} }
@ -243,7 +243,7 @@ const BasicNumberInput: React.FC<{
if (curState.valueBeforeDragging === value) { if (curState.valueBeforeDragging === value) {
propsRef.current.discardTemporaryValue() propsRef.current.discardTemporaryValue()
} else { } else {
propsRef.current.permenantlySetValue(value) propsRef.current.permanentlySetValue(value)
} }
stateRef.current = {mode: 'noFocus'} stateRef.current = {mode: 'noFocus'}
} }

View file

@ -55,7 +55,7 @@ const BasicStringInput: React.FC<{
value: string value: string
temporarilySetValue: (v: string) => void temporarilySetValue: (v: string) => void
discardTemporaryValue: () => void discardTemporaryValue: () => void
permenantlySetValue: (v: string) => void permanentlySetValue: (v: string) => void
className?: string className?: string
isValid?: (v: string) => boolean isValid?: (v: string) => boolean
inputRef?: MutableRefObject<HTMLInputElement | null> inputRef?: MutableRefObject<HTMLInputElement | null>
@ -112,7 +112,7 @@ const BasicStringInput: React.FC<{
if (curState.valueBeforeEditing === value) { if (curState.valueBeforeEditing === value) {
propsRef.current.discardTemporaryValue() propsRef.current.discardTemporaryValue()
} else { } else {
propsRef.current.permenantlySetValue(value) propsRef.current.permanentlySetValue(value)
} }
} }
} }

View file

@ -1,7 +1,7 @@
import type {$FixMe} from '@theatre/shared/utils/types' import type {$FixMe} from '@theatre/shared/utils/types'
import {useLayoutEffect, useRef} from 'react' import {useLayoutEffect, useRef} from 'react'
import useRefAndState from '@theatre/studio/utils/useRefAndState' import useRefAndState from '@theatre/studio/utils/useRefAndState'
import {useCursorLock} from './PointerEventsHandler' import {useCssCursorLock} from './PointerEventsHandler'
export type UseDragOpts = { export type UseDragOpts = {
/** /**
@ -52,7 +52,7 @@ export default function useDrag(
'dragStartCalled' | 'dragging' | 'notDragging' 'dragStartCalled' | 'dragging' | 'notDragging'
>('notDragging') >('notDragging')
useCursorLock( useCssCursorLock(
mode === 'dragging' && typeof opts.lockCursorTo === 'string', mode === 'dragging' && typeof opts.lockCursorTo === 'string',
'dragging', 'dragging',
opts.lockCursorTo!, opts.lockCursorTo!,