diff --git a/editor/src/components/inspector/sections/layout-section/self-layout-subsection/frame-updating-layout-section.spec.browser2.tsx b/editor/src/components/inspector/sections/layout-section/self-layout-subsection/frame-updating-layout-section.spec.browser2.tsx
index 26a18ed7c5bc..eaf49c0d9058 100644
--- a/editor/src/components/inspector/sections/layout-section/self-layout-subsection/frame-updating-layout-section.spec.browser2.tsx
+++ b/editor/src/components/inspector/sections/layout-section/self-layout-subsection/frame-updating-layout-section.spec.browser2.tsx
@@ -18,7 +18,13 @@ import * as EP from '../../../../../core/shared/element-path'
import { selectComponentsForTest } from '../../../../../utils/utils.test-utils'
import { RegisteredCanvasStrategies } from '../../../../canvas/canvas-strategies/canvas-strategies'
import { act, fireEvent } from '@testing-library/react'
-import { mouseClickAtPoint } from '../../../../canvas/event-helpers.test-utils'
+import {
+ mouseClickAtPoint,
+ mouseDownAtPoint,
+ mouseDragFromPointToPoint,
+ mouseMoveToPoint,
+ pressKey,
+} from '../../../../canvas/event-helpers.test-utils'
import { getDomRectCenter } from '../../../../../core/shared/dom-utils'
import { getFixedHugDropdownId } from '../../../fill-hug-fixed-control'
@@ -144,6 +150,77 @@ describe('Frame updating layout section', () => {
}
describe('Left control', () => {
+ it(
+ 'scrubbing the left control label',
+ makeTestCase({
+ baseProject: `
+
+
`,
+ actionChange: async (renderResult) => {
+ // Select the rectangle.
+ await selectComponentsForTest(renderResult, [
+ EP.fromString(
+ `${BakedInStoryboardUID}/${TestSceneUID}/${TestAppUID}:root-div/rectangle-1`,
+ ),
+ ])
+
+ const scrubLabel = await renderResult.renderedDOM.findByTestId(
+ `frame-left-number-input-label-div`,
+ )
+ const scrubLabelBounds = scrubLabel.getBoundingClientRect()
+ const scrubLabelCenter = getDomRectCenter(scrubLabelBounds)
+ const scrubLabelEndPoint = { x: scrubLabelCenter.x + 100, y: scrubLabelCenter.y }
+ await mouseDragFromPointToPoint(scrubLabel, scrubLabelCenter, scrubLabelEndPoint)
+ },
+ expectedFrames: {
+ [`${BakedInStoryboardUID}/${TestSceneUID}/${TestAppUID}:root-div/rectangle-1`]:
+ localRectangle({
+ x: 140,
+ y: 100,
+ width: 200,
+ height: 300,
+ }),
+ },
+ expectedProject: `
+
+
`,
+ expectedFixedHugDropdownWidthValue: 'Fixed',
+ expectedFixedHugDropdownHeightValue: 'Fixed',
+ }),
+ )
+
it(
'with a single element selected when setting value directly',
makeTestCase({
diff --git a/editor/src/core/performance/__snapshots__/performance-regression-tests.spec.tsx.snap b/editor/src/core/performance/__snapshots__/performance-regression-tests.spec.tsx.snap
index 88978fea576f..7a36c09939f1 100644
--- a/editor/src/core/performance/__snapshots__/performance-regression-tests.spec.tsx.snap
+++ b/editor/src/core/performance/__snapshots__/performance-regression-tests.spec.tsx.snap
@@ -411,6 +411,8 @@ Array [
"//div//Symbol(react.memo)(NumberInput)",
"//div//Symbol(react.forward_ref)(Styled(div))",
"/div//Symbol(react.forward_ref)(Styled(div))/div",
+ "/Symbol(react.forward_ref)(Styled(div))/div/NumberInput/img",
+ "/Symbol(react.forward_ref)(Styled(div))/div/NumberInput/div",
"/Symbol(react.forward_ref)(Styled(div))/div/NumberInput/div:data-testid='radius-one-mouse-down-handler'",
"/Symbol(react.forward_ref)(Styled(div))/div/NumberInput/Symbol(react.forward_ref)(EmotionCssPropInternal)",
"/Symbol(react.forward_ref)(Styled(div))/div/NumberInput/Symbol(react.forward_ref)(EmotionCssPropInternal)",
@@ -438,6 +440,8 @@ Array [
"//div//Symbol(react.memo)(NumberInput)",
"//div//Symbol(react.forward_ref)(Styled(div))",
"/div//Symbol(react.forward_ref)(Styled(div))/div",
+ "/Symbol(react.forward_ref)(Styled(div))/div/NumberInput/img",
+ "/Symbol(react.forward_ref)(Styled(div))/div/NumberInput/div",
"/Symbol(react.forward_ref)(Styled(div))/div/NumberInput/div:data-testid='padding-V-mouse-down-handler'",
"/Symbol(react.forward_ref)(Styled(div))/div/NumberInput/Symbol(react.forward_ref)(EmotionCssPropInternal)",
"/Symbol(react.forward_ref)(Styled(div))/div/NumberInput/Symbol(react.forward_ref)(EmotionCssPropInternal)",
@@ -446,6 +450,8 @@ Array [
"/Symbol(react.forward_ref)(EmotionCssPropInternal)/div/Symbol(react.forward_ref)(EmotionCssPropInternal)/Symbol(react.memo)(Symbol(react.forward_ref)())",
"/Symbol(react.forward_ref)(EmotionCssPropInternal)/Symbol(react.memo)(Symbol(react.forward_ref)())/Symbol(react.forward_ref)()/Symbol(react.forward_ref)(Styled(input)):data-testid='padding-V'",
"/Symbol(react.memo)(Symbol(react.forward_ref)())/Symbol(react.forward_ref)()/Symbol(react.forward_ref)(Styled(input))/input:data-testid='padding-V'",
+ "/Symbol(react.forward_ref)(Styled(div))/div/NumberInput/img",
+ "/Symbol(react.forward_ref)(Styled(div))/div/NumberInput/div",
"/Symbol(react.forward_ref)(Styled(div))/div/NumberInput/div:data-testid='padding-H-mouse-down-handler'",
"/Symbol(react.forward_ref)(Styled(div))/div/NumberInput/Symbol(react.forward_ref)(EmotionCssPropInternal)",
"/Symbol(react.forward_ref)(Styled(div))/div/NumberInput/Symbol(react.forward_ref)(EmotionCssPropInternal)",
@@ -551,6 +557,8 @@ Array [
"/div/FrameUpdatingLayoutControl/UtopiaSpiedFunctionComponent(InspectorContextMenuWrapper)/Symbol(react.forward_ref)(EmotionCssPropInternal)",
"/FrameUpdatingLayoutControl/UtopiaSpiedFunctionComponent(InspectorContextMenuWrapper)/Symbol(react.forward_ref)(EmotionCssPropInternal)/div",
"/Symbol(react.forward_ref)(EmotionCssPropInternal)/div/UtopiaSpiedFunctionComponent(MenuProvider)/div",
+ "/UtopiaSpiedFunctionComponent(MenuProvider)/div/NumberInput/img",
+ "/UtopiaSpiedFunctionComponent(MenuProvider)/div/NumberInput/div",
"/UtopiaSpiedFunctionComponent(MenuProvider)/div/NumberInput/div:data-testid='frame-left-number-input-mouse-down-handler'",
"/UtopiaSpiedFunctionComponent(MenuProvider)/div/NumberInput/Symbol(react.forward_ref)(EmotionCssPropInternal)",
"/UtopiaSpiedFunctionComponent(MenuProvider)/div/NumberInput/Symbol(react.forward_ref)(EmotionCssPropInternal)",
@@ -572,6 +580,8 @@ Array [
"/div/FrameUpdatingLayoutControl/UtopiaSpiedFunctionComponent(InspectorContextMenuWrapper)/Symbol(react.forward_ref)(EmotionCssPropInternal)",
"/FrameUpdatingLayoutControl/UtopiaSpiedFunctionComponent(InspectorContextMenuWrapper)/Symbol(react.forward_ref)(EmotionCssPropInternal)/div",
"/Symbol(react.forward_ref)(EmotionCssPropInternal)/div/UtopiaSpiedFunctionComponent(MenuProvider)/div",
+ "/UtopiaSpiedFunctionComponent(MenuProvider)/div/NumberInput/img",
+ "/UtopiaSpiedFunctionComponent(MenuProvider)/div/NumberInput/div",
"/UtopiaSpiedFunctionComponent(MenuProvider)/div/NumberInput/div:data-testid='frame-top-number-input-mouse-down-handler'",
"/UtopiaSpiedFunctionComponent(MenuProvider)/div/NumberInput/Symbol(react.forward_ref)(EmotionCssPropInternal)",
"/UtopiaSpiedFunctionComponent(MenuProvider)/div/NumberInput/Symbol(react.forward_ref)(EmotionCssPropInternal)",
@@ -1033,6 +1043,8 @@ Array [
"//div//Symbol(react.memo)(NumberInput)",
"//div//Symbol(react.forward_ref)(Styled(div))",
"/div//Symbol(react.forward_ref)(Styled(div))/div",
+ "/Symbol(react.forward_ref)(Styled(div))/div/NumberInput/img",
+ "/Symbol(react.forward_ref)(Styled(div))/div/NumberInput/div",
"/Symbol(react.forward_ref)(Styled(div))/div/NumberInput/div:data-testid='radius-one-mouse-down-handler'",
"/Symbol(react.forward_ref)(Styled(div))/div/NumberInput/Symbol(react.forward_ref)(EmotionCssPropInternal)",
"/Symbol(react.forward_ref)(Styled(div))/div/NumberInput/Symbol(react.forward_ref)(EmotionCssPropInternal)",
@@ -1060,6 +1072,8 @@ Array [
"//div//Symbol(react.memo)(NumberInput)",
"//div//Symbol(react.forward_ref)(Styled(div))",
"/div//Symbol(react.forward_ref)(Styled(div))/div",
+ "/Symbol(react.forward_ref)(Styled(div))/div/NumberInput/img",
+ "/Symbol(react.forward_ref)(Styled(div))/div/NumberInput/div",
"/Symbol(react.forward_ref)(Styled(div))/div/NumberInput/div:data-testid='padding-V-mouse-down-handler'",
"/Symbol(react.forward_ref)(Styled(div))/div/NumberInput/Symbol(react.forward_ref)(EmotionCssPropInternal)",
"/Symbol(react.forward_ref)(Styled(div))/div/NumberInput/Symbol(react.forward_ref)(EmotionCssPropInternal)",
@@ -1068,6 +1082,8 @@ Array [
"/Symbol(react.forward_ref)(EmotionCssPropInternal)/div/Symbol(react.forward_ref)(EmotionCssPropInternal)/Symbol(react.memo)(Symbol(react.forward_ref)())",
"/Symbol(react.forward_ref)(EmotionCssPropInternal)/Symbol(react.memo)(Symbol(react.forward_ref)())/Symbol(react.forward_ref)()/Symbol(react.forward_ref)(Styled(input)):data-testid='padding-V'",
"/Symbol(react.memo)(Symbol(react.forward_ref)())/Symbol(react.forward_ref)()/Symbol(react.forward_ref)(Styled(input))/input:data-testid='padding-V'",
+ "/Symbol(react.forward_ref)(Styled(div))/div/NumberInput/img",
+ "/Symbol(react.forward_ref)(Styled(div))/div/NumberInput/div",
"/Symbol(react.forward_ref)(Styled(div))/div/NumberInput/div:data-testid='padding-H-mouse-down-handler'",
"/Symbol(react.forward_ref)(Styled(div))/div/NumberInput/Symbol(react.forward_ref)(EmotionCssPropInternal)",
"/Symbol(react.forward_ref)(Styled(div))/div/NumberInput/Symbol(react.forward_ref)(EmotionCssPropInternal)",
@@ -1091,6 +1107,8 @@ Array [
"/div/FrameUpdatingLayoutControl/UtopiaSpiedFunctionComponent(InspectorContextMenuWrapper)/Symbol(react.forward_ref)(EmotionCssPropInternal)",
"/FrameUpdatingLayoutControl/UtopiaSpiedFunctionComponent(InspectorContextMenuWrapper)/Symbol(react.forward_ref)(EmotionCssPropInternal)/div",
"/Symbol(react.forward_ref)(EmotionCssPropInternal)/div/UtopiaSpiedFunctionComponent(MenuProvider)/div",
+ "/UtopiaSpiedFunctionComponent(MenuProvider)/div/NumberInput/img",
+ "/UtopiaSpiedFunctionComponent(MenuProvider)/div/NumberInput/div",
"/UtopiaSpiedFunctionComponent(MenuProvider)/div/NumberInput/div:data-testid='frame-left-number-input-mouse-down-handler'",
"/UtopiaSpiedFunctionComponent(MenuProvider)/div/NumberInput/Symbol(react.forward_ref)(EmotionCssPropInternal)",
"/UtopiaSpiedFunctionComponent(MenuProvider)/div/NumberInput/Symbol(react.forward_ref)(EmotionCssPropInternal)",
@@ -1112,6 +1130,8 @@ Array [
"/div/FrameUpdatingLayoutControl/UtopiaSpiedFunctionComponent(InspectorContextMenuWrapper)/Symbol(react.forward_ref)(EmotionCssPropInternal)",
"/FrameUpdatingLayoutControl/UtopiaSpiedFunctionComponent(InspectorContextMenuWrapper)/Symbol(react.forward_ref)(EmotionCssPropInternal)/div",
"/Symbol(react.forward_ref)(EmotionCssPropInternal)/div/UtopiaSpiedFunctionComponent(MenuProvider)/div",
+ "/UtopiaSpiedFunctionComponent(MenuProvider)/div/NumberInput/img",
+ "/UtopiaSpiedFunctionComponent(MenuProvider)/div/NumberInput/div",
"/UtopiaSpiedFunctionComponent(MenuProvider)/div/NumberInput/div:data-testid='frame-top-number-input-mouse-down-handler'",
"/UtopiaSpiedFunctionComponent(MenuProvider)/div/NumberInput/Symbol(react.forward_ref)(EmotionCssPropInternal)",
"/UtopiaSpiedFunctionComponent(MenuProvider)/div/NumberInput/Symbol(react.forward_ref)(EmotionCssPropInternal)",
@@ -1411,6 +1431,8 @@ Array [
"/OpacityRow/UtopiaSpiedFunctionComponent(InspectorContextMenuWrapper)/Symbol(react.forward_ref)(EmotionCssPropInternal)/div",
"/Symbol(react.forward_ref)(EmotionCssPropInternal)/div/UtopiaSpiedFunctionComponent(MenuProvider)/div",
"/UtopiaSpiedFunctionComponent(MenuProvider)/div//Symbol(react.memo)(NumberInput)",
+ "/div//NumberInput/img",
+ "/div//NumberInput/div",
"/div//NumberInput/div:data-testid='opacity-mouse-down-handler'",
"/div//NumberInput/Symbol(react.forward_ref)(EmotionCssPropInternal)",
"/div//NumberInput/Symbol(react.forward_ref)(EmotionCssPropInternal)",
@@ -1982,6 +2004,8 @@ Array [
"/OpacityRow/UtopiaSpiedFunctionComponent(InspectorContextMenuWrapper)/Symbol(react.forward_ref)(EmotionCssPropInternal)/div",
"/Symbol(react.forward_ref)(EmotionCssPropInternal)/div/UtopiaSpiedFunctionComponent(MenuProvider)/div",
"/UtopiaSpiedFunctionComponent(MenuProvider)/div//Symbol(react.memo)(NumberInput)",
+ "/div//NumberInput/img",
+ "/div//NumberInput/div",
"/div//NumberInput/div:data-testid='opacity-mouse-down-handler'",
"/div//NumberInput/Symbol(react.forward_ref)(EmotionCssPropInternal)",
"/div//NumberInput/Symbol(react.forward_ref)(EmotionCssPropInternal)",
@@ -2505,6 +2529,8 @@ Array [
"/OpacityRow/UtopiaSpiedFunctionComponent(InspectorContextMenuWrapper)/Symbol(react.forward_ref)(EmotionCssPropInternal)/div",
"/Symbol(react.forward_ref)(EmotionCssPropInternal)/div/UtopiaSpiedFunctionComponent(MenuProvider)/div",
"/UtopiaSpiedFunctionComponent(MenuProvider)/div//Symbol(react.memo)(NumberInput)",
+ "/div//NumberInput/img",
+ "/div//NumberInput/div",
"/div//NumberInput/div:data-testid='opacity-mouse-down-handler'",
"/div//NumberInput/Symbol(react.forward_ref)(EmotionCssPropInternal)",
"/div//NumberInput/Symbol(react.forward_ref)(EmotionCssPropInternal)",
@@ -2906,6 +2932,8 @@ Array [
"/OpacityRow/UtopiaSpiedFunctionComponent(InspectorContextMenuWrapper)/Symbol(react.forward_ref)(EmotionCssPropInternal)/div",
"/Symbol(react.forward_ref)(EmotionCssPropInternal)/div/UtopiaSpiedFunctionComponent(MenuProvider)/div",
"/UtopiaSpiedFunctionComponent(MenuProvider)/div//Symbol(react.memo)(NumberInput)",
+ "/div//NumberInput/img",
+ "/div//NumberInput/div",
"/div//NumberInput/div:data-testid='opacity-mouse-down-handler'",
"/div//NumberInput/Symbol(react.forward_ref)(EmotionCssPropInternal)",
"/div//NumberInput/Symbol(react.forward_ref)(EmotionCssPropInternal)",
diff --git a/editor/src/core/performance/performance-regression-tests.spec.tsx b/editor/src/core/performance/performance-regression-tests.spec.tsx
index ef7f9349c484..a2bd8d40f165 100644
--- a/editor/src/core/performance/performance-regression-tests.spec.tsx
+++ b/editor/src/core/performance/performance-regression-tests.spec.tsx
@@ -65,7 +65,7 @@ describe('React Render Count Tests -', () => {
const renderCountAfter = renderResult.getNumberOfRenders()
// if this breaks, GREAT NEWS but update the test please :)
- expect(renderCountAfter - renderCountBefore).toMatchInlineSnapshot(`749`)
+ expect(renderCountAfter - renderCountBefore).toMatchInlineSnapshot(`753`)
expect(renderResult.getRenderInfo()).toMatchSnapshot()
})
@@ -127,7 +127,7 @@ describe('React Render Count Tests -', () => {
const renderCountAfter = renderResult.getNumberOfRenders()
// if this breaks, GREAT NEWS but update the test please :)
- expect(renderCountAfter - renderCountBefore).toMatchInlineSnapshot(`1097`)
+ expect(renderCountAfter - renderCountBefore).toMatchInlineSnapshot(`1101`)
expect(renderResult.getRenderInfo()).toMatchSnapshot()
})
@@ -183,7 +183,7 @@ describe('React Render Count Tests -', () => {
const renderCountAfter = renderResult.getNumberOfRenders()
// if this breaks, GREAT NEWS but update the test please :)
- expect(renderCountAfter - renderCountBefore).toMatchInlineSnapshot(`529`)
+ expect(renderCountAfter - renderCountBefore).toMatchInlineSnapshot(`539`)
expect(renderResult.getRenderInfo()).toMatchSnapshot()
})
@@ -249,7 +249,7 @@ describe('React Render Count Tests -', () => {
const renderCountAfter = renderResult.getNumberOfRenders()
// if this breaks, GREAT NEWS but update the test please :)
- expect(renderCountAfter - renderCountBefore).toMatchInlineSnapshot(`702`)
+ expect(renderCountAfter - renderCountBefore).toMatchInlineSnapshot(`712`)
expect(renderResult.getRenderInfo()).toMatchSnapshot()
})
})
diff --git a/editor/src/uuiui/inputs/number-input.tsx b/editor/src/uuiui/inputs/number-input.tsx
index 82d079451b53..597f801eecd0 100644
--- a/editor/src/uuiui/inputs/number-input.tsx
+++ b/editor/src/uuiui/inputs/number-input.tsx
@@ -35,7 +35,7 @@ import type {
} from '../../components/inspector/controls/control'
import type { Either } from '../../core/shared/either'
import { isLeft, mapEither } from '../../core/shared/either'
-import { clampValue } from '../../core/shared/math-utils'
+import { clampValue, point } from '../../core/shared/math-utils'
import { memoize } from '../../core/shared/memoize'
import type { ControlStyles } from '../../uuiui-deps'
import { getControlStyles, CSSCursor } from '../../uuiui-deps'
@@ -51,8 +51,7 @@ import {
} from './base-input'
import { usePropControlledStateV2 } from '../../components/inspector/common/inspector-utils'
import { useControlsDisabledInSubtree } from '../utilities/disable-subtree'
-
-export type LabelDragDirection = 'horizontal' | 'vertical'
+import { getPossiblyHashedURL } from '../../utils/hashed-assets'
function getDisplayValueNotMemoized(
value: CSSNumber | null,
@@ -88,36 +87,20 @@ function parseDisplayValueNotMemoized(
const parseDisplayValue = memoize(parseDisplayValueNotMemoized, { maxSize: 1000 })
-const dragDeltaSign = (start: number, end: number, directionAdjustment: 1 | -1): 1 | -1 => {
- const raw = (start - end) * directionAdjustment
- return raw >= 0 ? 1 : -1
+function dragDeltaSign(delta: number): 1 | -1 {
+ return delta >= 0 ? 1 : -1
}
-const calculateDragDirectionDelta = (
- start: number,
- end: number,
- scalingFactor: number,
- directionAdjustment: 1 | -1,
-): number => {
- const sign = dragDeltaSign(start, end, directionAdjustment)
- const rawAbsDelta = Math.abs(start - end)
+function calculateDragDirectionDelta(delta: number, scalingFactor: number): number {
+ const sign = dragDeltaSign(delta)
+ const rawAbsDelta = Math.abs(delta)
+ // Floor the value and then restore its sign so that it is rounded towards zero.
const scaledAbsDelta = Math.floor(rawAbsDelta / scalingFactor)
return sign * scaledAbsDelta
}
-const calculateDragDelta = (
- dragOriginX: number,
- dragOriginY: number,
- dragScreenX: number,
- dragScreenY: number,
- labelDragDirection: LabelDragDirection,
- scalingFactor: number = 2,
-) => {
- if (labelDragDirection === 'horizontal') {
- return calculateDragDirectionDelta(dragOriginX, dragScreenX, scalingFactor, 1)
- } else {
- return calculateDragDirectionDelta(dragOriginY, dragScreenY, scalingFactor, -1)
- }
+function calculateDragDelta(delta: number, scalingFactor: number = 2): number {
+ return calculateDragDirectionDelta(delta, scalingFactor)
}
let incrementTimeout: number | undefined = undefined
@@ -226,29 +209,15 @@ export const NumberInput = React.memo(
const [isFauxcused, setIsFauxcused] = React.useState(false)
const isFocused = isActuallyFocused || isFauxcused
- const [labelDragDirection, setLabelDragDirection] =
- React.useState('horizontal')
-
const [, setValueAtDragOriginState] = React.useState(0)
- const valueAtDragOrigin = React.useRef(0)
+ const valueAtDragOrigin = React.useRef(null)
const setValueAtDragOrigin = (n: number) => {
valueAtDragOrigin.current = n
setValueAtDragOriginState(n)
}
- const [, setDragOriginXState] = React.useState(-Infinity)
- const dragOriginX = React.useRef(-Infinity)
- const setDragOriginX = (n: number) => {
- dragOriginX.current = n
- setDragOriginXState(n)
- }
-
- const [, setDragOriginYState] = React.useState(-Infinity)
- const dragOriginY = React.useRef(-Infinity)
- const setDragOriginY = (n: number) => {
- dragOriginY.current = n
- setDragOriginYState(n)
- }
+ const [dragOriginX, setDragOriginX] = React.useState(null)
+ const [dragOriginY, setDragOriginY] = React.useState(null)
const [, setScrubThresholdPassedState] = React.useState(false)
const scrubThresholdPassed = React.useRef(false)
@@ -257,6 +226,15 @@ export const NumberInput = React.memo(
setScrubThresholdPassedState(b)
}
+ const simulatedPointerRef = React.useRef(null)
+ const pointerOriginRef = React.useRef(null)
+
+ const accumulatedMouseDeltaX = React.useRef(0)
+ // This is here to alleviate a circular reference issue that I stumbled into with the callbacks,
+ // it means that the cleanup callback isn't dependent on the event listeners, which result in
+ // a break in the circle.
+ const scrubbingCleanupCallbacks = React.useRef void>>([])
+
const [valueChangedSinceFocus, setValueChangedSinceFocus] = React.useState(false)
const scaleFactor = valueUnit === '%' ? 100 : 1
@@ -327,53 +305,41 @@ export const NumberInput = React.memo(
)
const setScrubValue = React.useCallback(
- (
- unit: CSSNumberUnit | null,
- screenX: number,
- screenY: number,
- scrubDragOriginX: number,
- scrubDragOriginY: number,
- transient: boolean,
- ) => {
- const primaryAxisDelta = calculateDragDelta(
- scrubDragOriginX,
- scrubDragOriginY,
- screenX,
- screenY,
- labelDragDirection,
- )
- const numericValue = clampValue(
- valueAtDragOrigin.current - stepSize * primaryAxisDelta,
- minimum,
- maximum,
- )
- const newValue = cssNumber(numericValue, unit)
-
- if (transient) {
- if (onTransientSubmitValue != null) {
- onTransientSubmitValue(newValue)
- } else if (onSubmitValue != null) {
- onSubmitValue(newValue)
- }
- } else {
- if (onForcedSubmitValue != null) {
- onForcedSubmitValue(newValue)
- } else if (onSubmitValue != null) {
- onSubmitValue(newValue)
+ (transient: boolean) => {
+ const dragDelta = calculateDragDelta(accumulatedMouseDeltaX.current)
+ if (valueAtDragOrigin.current != null) {
+ const numericValue = clampValue(
+ valueAtDragOrigin.current + stepSize * dragDelta,
+ minimum,
+ maximum,
+ )
+ const newValue = cssNumber(numericValue, valueUnit)
+
+ if (transient) {
+ if (onTransientSubmitValue != null) {
+ onTransientSubmitValue(newValue)
+ } else if (onSubmitValue != null) {
+ onSubmitValue(newValue)
+ }
+ } else {
+ if (onForcedSubmitValue != null) {
+ onForcedSubmitValue(newValue)
+ } else if (onSubmitValue != null) {
+ onSubmitValue(newValue)
+ }
}
+ updateValue(newValue)
}
- updateValue(newValue)
- return newValue
},
[
- labelDragDirection,
- maximum,
- minimum,
stepSize,
- onForcedSubmitValue,
- onSubmitValue,
- onTransientSubmitValue,
+ minimum,
+ maximum,
+ valueUnit,
updateValue,
+ onTransientSubmitValue,
+ onSubmitValue,
+ onForcedSubmitValue,
],
)
@@ -385,53 +351,79 @@ export const NumberInput = React.memo(
const onThresholdPassed = (e: MouseEvent, fn: () => void) => {
const thresholdPassed =
- scrubThresholdPassed.current || Math.abs(e.screenX - dragOriginX.current) >= ScrubThreshold
+ scrubThresholdPassed.current || Math.abs(accumulatedMouseDeltaX.current) >= ScrubThreshold
if (thresholdPassed) {
fn()
}
}
- const scrubOnMouseMove = React.useCallback(
+ const cancelPointerLock = React.useCallback(
+ (revertChanges: 'revert-nothing' | 'revert-changes') => {
+ if (document.pointerLockElement === pointerOriginRef.current) {
+ document.exitPointerLock()
+ }
+ if (
+ revertChanges === 'revert-changes' &&
+ onSubmitValue != null &&
+ valueAtDragOrigin.current != null
+ ) {
+ const oldValue = cssNumber(valueAtDragOrigin.current, valueUnit)
+ onSubmitValue(oldValue, false)
+ }
+
+ setIsFauxcused(false)
+ ref.current?.focus()
+
+ setScrubThresholdPassed(false)
+ setGlobalCursor?.(null)
+ },
+ [onSubmitValue, setGlobalCursor, valueUnit],
+ )
+
+ const checkPointerLockChange = React.useCallback(() => {
+ if (document.pointerLockElement !== pointerOriginRef.current) {
+ cancelPointerLock('revert-changes')
+ scrubbingCleanupCallbacks.current.forEach((fn) => fn())
+ }
+ }, [cancelPointerLock])
+
+ const scrubOnMouseUp = React.useCallback(
(e: MouseEvent) => {
+ scrubbingCleanupCallbacks.current.forEach((fn) => fn())
onThresholdPassed(e, () => {
- if (!scrubThresholdPassed.current) {
- setScrubThresholdPassed(true)
- }
- setScrubValue(
- valueUnit,
- e.screenX,
- e.screenY,
- dragOriginX.current,
- dragOriginY.current,
- true,
- )
+ setScrubValue(false)
})
+ cancelPointerLock('revert-nothing')
},
- [setScrubValue, valueUnit],
+ [cancelPointerLock, setScrubValue],
)
- const scrubOnMouseUp = React.useCallback(
+ const scrubOnMouseMove = React.useCallback(
(e: MouseEvent) => {
- window.removeEventListener('mouseup', scrubOnMouseUp)
- window.removeEventListener('mousemove', scrubOnMouseMove)
-
- setIsFauxcused(false)
- ref.current?.focus()
+ // Apply the movement to the accumulated delta, as the movement is
+ // relative to the last event.
+ accumulatedMouseDeltaX.current += e.movementX
onThresholdPassed(e, () => {
- setScrubValue(
- valueUnit,
- e.screenX,
- e.screenY,
- dragOriginX.current,
- dragOriginY.current,
- false,
- )
+ if (!scrubThresholdPassed.current) {
+ setScrubThresholdPassed(true)
+ if (pointerOriginRef.current != null) {
+ pointerOriginRef.current.requestPointerLock()
+ scrubbingCleanupCallbacks.current.push(() => {
+ window.removeEventListener('mouseup', scrubOnMouseUp)
+ })
+ scrubbingCleanupCallbacks.current.push(() => {
+ window.removeEventListener('mousemove', scrubOnMouseMove)
+ })
+ scrubbingCleanupCallbacks.current.push(() => {
+ document.removeEventListener('pointerlockchange', checkPointerLockChange, true)
+ })
+ }
+ }
+ setScrubValue(true)
})
- setScrubThresholdPassed(false)
- setGlobalCursor?.(null)
},
- [scrubOnMouseMove, setScrubValue, valueUnit, ref, setGlobalCursor],
+ [checkPointerLockChange, scrubOnMouseUp, setScrubValue],
)
const rc = roundCorners == null ? 'all' : roundCorners
@@ -461,7 +453,7 @@ export const NumberInput = React.memo(
ref.current?.blur()
}
},
- [incrementBy, stepSize, ref, updateValue],
+ [updateValue, incrementBy, stepSize],
)
const onKeyUp = React.useCallback(
@@ -634,14 +626,23 @@ export const NumberInput = React.memo(
setIsFauxcused(true)
window.addEventListener('mousemove', scrubOnMouseMove)
window.addEventListener('mouseup', scrubOnMouseUp)
- setLabelDragDirection('horizontal')
+ document.addEventListener('pointerlockchange', checkPointerLockChange, true)
setValueAtDragOrigin(value?.value ?? 0)
- setDragOriginX(e.screenX)
- setDragOriginY(e.screenY)
+ setDragOriginX(e.pageX)
+ setDragOriginY(e.pageY)
setGlobalCursor?.(CSSCursor.ResizeEW)
+ accumulatedMouseDeltaX.current = 0
}
},
- [scrubOnMouseMove, scrubOnMouseUp, setGlobalCursor, value, disabled, disableScrubbing],
+ [
+ disabled,
+ disableScrubbing,
+ scrubOnMouseMove,
+ scrubOnMouseUp,
+ checkPointerLockChange,
+ value?.value,
+ setGlobalCursor,
+ ],
)
const placeholder = getControlStylesAwarePlaceholder(controlStyles)
@@ -663,8 +664,47 @@ export const NumberInput = React.memo(
}
: undefined
+ let simulatedPointerTransformX: number | undefined = undefined
+ if (pointerOriginRef.current != null && scrubThresholdPassed.current && dragOriginX != null) {
+ const pointerOriginRect = pointerOriginRef.current.getBoundingClientRect()
+ const intendedPointerX =
+ (pointerOriginRect.left + accumulatedMouseDeltaX.current) % window.screen.width
+ simulatedPointerTransformX = intendedPointerX - pointerOriginRect.left
+ }
+
return (
-
+
+
+
+