@onerjs/shared-ui-components 8.42.2 → 8.42.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fluent/hoc/fileUploadLine.js +1 -1
- package/fluent/hoc/fileUploadLine.js.map +1 -1
- package/fluent/hoc/propertyLines/vectorPropertyLine.d.ts +3 -1
- package/fluent/hoc/propertyLines/vectorPropertyLine.js +12 -11
- package/fluent/hoc/propertyLines/vectorPropertyLine.js.map +1 -1
- package/fluent/hooks/keyboardHooks.d.ts +4 -1
- package/fluent/hooks/keyboardHooks.js +8 -2
- package/fluent/hooks/keyboardHooks.js.map +1 -1
- package/fluent/primitives/accordion.js +5 -3
- package/fluent/primitives/accordion.js.map +1 -1
- package/fluent/primitives/gradient.js +1 -1
- package/fluent/primitives/gradient.js.map +1 -1
- package/fluent/primitives/slider.d.ts +23 -0
- package/fluent/primitives/slider.js +56 -0
- package/fluent/primitives/slider.js.map +1 -0
- package/fluent/primitives/spinButton.d.ts +12 -0
- package/fluent/primitives/spinButton.js +175 -100
- package/fluent/primitives/spinButton.js.map +1 -1
- package/fluent/primitives/syncedSlider.d.ts +2 -0
- package/fluent/primitives/syncedSlider.js +5 -13
- package/fluent/primitives/syncedSlider.js.map +1 -1
- package/lines/optionsLineComponent.js +3 -0
- package/lines/optionsLineComponent.js.map +1 -1
- package/nodeGraphSystem/graphFrame.js +3 -0
- package/nodeGraphSystem/graphFrame.js.map +1 -1
- package/nodeGraphSystem/graphNode.module.scss +2 -4
- package/package.json +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"slider.js","sourceRoot":"","sources":["../../../../../dev/sharedUiComponents/src/fluent/primitives/slider.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,MAAM,IAAI,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAEpE,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC;AAEhE,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAiBvD;;;;;GAKG;AACH,MAAM,CAAC,MAAM,MAAM,GAAmC,CAAC,KAAK,EAAE,EAAE;IAC5D,MAAM,CAAC,WAAW,GAAG,QAAQ,CAAC;IAC9B,MAAM,EAAE,IAAI,EAAE,GAAG,UAAU,CAAC,WAAW,CAAC,CAAC;IACzC,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAS,KAAK,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC;IAC7D,MAAM,eAAe,GAAG,MAAM,CAAS,SAAS,CAAC,CAAC;IAClD,MAAM,aAAa,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IAEpC,iGAAiG;IACjG,8FAA8F;IAC9F,8EAA8E;IAC9E,MAAM,GAAG,GAAG,KAAK,CAAC,GAAG,IAAI,CAAC,CAAC;IAC3B,MAAM,GAAG,GAAG,KAAK,CAAC,GAAG,IAAI,GAAG,CAAC;IAC7B,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,IAAI,CAAC,CAAC;IAE7B,SAAS,CAAC,GAAG,EAAE;QACX,CAAC,aAAa,CAAC,OAAO,IAAI,QAAQ,CAAC,KAAK,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC,CAAC,uFAAuF;IACjJ,CAAC,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC;IAElB,MAAM,kBAAkB,GAAG,CAAC,CAAgC,EAAE,IAAwB,EAAE,EAAE;QACtF,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;QACnC,QAAQ,CAAC,QAAQ,CAAC,CAAC;QAEnB,IAAI,KAAK,CAAC,mBAAmB,EAAE,CAAC;YAC5B,8CAA8C;YAC9C,eAAe,CAAC,OAAO,GAAG,QAAQ,CAAC;QACvC,CAAC;aAAM,CAAC;YACJ,kCAAkC;YAClC,KAAK,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;QAC7B,CAAC;IACL,CAAC,CAAC;IAEF,MAAM,uBAAuB,GAAG,GAAG,EAAE;QACjC,aAAa,CAAC,OAAO,GAAG,IAAI,CAAC;IACjC,CAAC,CAAC;IAEF,MAAM,qBAAqB,GAAG,GAAG,EAAE;QAC/B,IAAI,KAAK,CAAC,mBAAmB,IAAI,aAAa,CAAC,OAAO,IAAI,eAAe,CAAC,OAAO,KAAK,SAAS,EAAE,CAAC;YAC9F,KAAK,CAAC,QAAQ,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC;YACxC,eAAe,CAAC,OAAO,GAAG,SAAS,CAAC;QACxC,CAAC;QACD,aAAa,CAAC,OAAO,GAAG,KAAK,CAAC;IAClC,CAAC,CAAC;IAEF,OAAO,CACH,KAAC,YAAY,IACT,SAAS,EAAE,KAAK,CAAC,SAAS,EAC1B,IAAI,EAAE,IAAI,EACV,GAAG,EAAE,GAAG,GAAG,IAAI,EACf,GAAG,EAAE,GAAG,GAAG,IAAI,EACf,IAAI,EAAE,SAAS,EACf,KAAK,EAAE,KAAK,GAAG,IAAI,EACnB,QAAQ,EAAE,KAAK,CAAC,QAAQ,EACxB,QAAQ,EAAE,kBAAkB,EAC5B,aAAa,EAAE,GAAG,EAAE;YAChB,uBAAuB,EAAE,CAAC;YAC1B,KAAK,CAAC,aAAa,EAAE,EAAE,CAAC;QAC5B,CAAC,EACD,WAAW,EAAE,GAAG,EAAE;YACd,qBAAqB,EAAE,CAAC;YACxB,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;QAC1B,CAAC,GACH,CACL,CAAC;AACN,CAAC,CAAC","sourcesContent":["import type { SliderOnChangeData } from \"@fluentui/react-components\";\r\nimport { Slider as FluentSlider } from \"@fluentui/react-components\";\r\nimport type { ChangeEvent, FunctionComponent } from \"react\";\r\nimport { useEffect, useState, useRef, useContext } from \"react\";\r\nimport type { PrimitiveProps } from \"./primitive\";\r\nimport { ToolContext } from \"../hoc/fluentToolWrapper\";\r\n\r\nexport type SliderProps = PrimitiveProps<number> & {\r\n /** Minimum value for the slider */\r\n min?: number;\r\n /** Maximum value for the slider */\r\n max?: number;\r\n /** Step size for the slider */\r\n step?: number;\r\n /** When true, onChange is only called when the user releases the slider, not during drag */\r\n notifyOnlyOnRelease?: boolean;\r\n /** Optional pointer down handler */\r\n onPointerDown?: () => void;\r\n /** Optional pointer up handler */\r\n onPointerUp?: () => void;\r\n};\r\n\r\n/**\r\n * A slider primitive that wraps the Fluent UI Slider with step scaling, drag tracking, and optional notify-on-release behavior.\r\n * Follows the same pattern as other primitives (e.g. Switch) — no wrapper divs, just the Fluent component with logic.\r\n * @param props\r\n * @returns Slider component\r\n */\r\nexport const Slider: FunctionComponent<SliderProps> = (props) => {\r\n Slider.displayName = \"Slider\";\r\n const { size } = useContext(ToolContext);\r\n const [value, setValue] = useState<number>(props.value ?? 0);\r\n const pendingValueRef = useRef<number>(undefined);\r\n const isDraggingRef = useRef(false);\r\n\r\n // NOTE: The Fluent slider will add tick marks if the step prop is anything other than undefined.\r\n // To avoid this, we scale the min/max based on the step so we can always make step undefined.\r\n // The actual step size in the Fluent slider is 1 when it is set to undefined.\r\n const min = props.min ?? 0;\r\n const max = props.max ?? 100;\r\n const step = props.step ?? 1;\r\n\r\n useEffect(() => {\r\n !isDraggingRef.current && setValue(props.value ?? 0); // Update local state when props.value changes as long as user is not actively dragging\r\n }, [props.value]);\r\n\r\n const handleSliderChange = (_: ChangeEvent<HTMLInputElement>, data: SliderOnChangeData) => {\r\n const newValue = data.value * step;\r\n setValue(newValue);\r\n\r\n if (props.notifyOnlyOnRelease) {\r\n // Store the value but don't notify parent yet\r\n pendingValueRef.current = newValue;\r\n } else {\r\n // Notify parent as slider changes\r\n props.onChange(newValue);\r\n }\r\n };\r\n\r\n const handleSliderPointerDown = () => {\r\n isDraggingRef.current = true;\r\n };\r\n\r\n const handleSliderPointerUp = () => {\r\n if (props.notifyOnlyOnRelease && isDraggingRef.current && pendingValueRef.current !== undefined) {\r\n props.onChange(pendingValueRef.current);\r\n pendingValueRef.current = undefined;\r\n }\r\n isDraggingRef.current = false;\r\n };\r\n\r\n return (\r\n <FluentSlider\r\n className={props.className}\r\n size={size}\r\n min={min / step}\r\n max={max / step}\r\n step={undefined}\r\n value={value / step}\r\n disabled={props.disabled}\r\n onChange={handleSliderChange}\r\n onPointerDown={() => {\r\n handleSliderPointerDown();\r\n props.onPointerDown?.();\r\n }}\r\n onPointerUp={() => {\r\n handleSliderPointerUp();\r\n props.onPointerUp?.();\r\n }}\r\n />\r\n );\r\n};\r\n"]}
|
|
@@ -8,9 +8,17 @@ export type SpinButtonProps = PrimitiveProps<number> & {
|
|
|
8
8
|
unit?: string;
|
|
9
9
|
forceInt?: boolean;
|
|
10
10
|
validator?: (value: number) => boolean;
|
|
11
|
+
/** Optional fixed precision (number of decimal digits). Overrides the automatically computed display precision. */
|
|
12
|
+
precision?: number;
|
|
11
13
|
/** Optional className for the input element */
|
|
12
14
|
inputClassName?: string;
|
|
15
|
+
/** When true, hides the drag-to-scrub button */
|
|
16
|
+
disableDragButton?: boolean;
|
|
13
17
|
};
|
|
18
|
+
/**
|
|
19
|
+
* A numeric input with a vertical drag-to-scrub icon (ArrowsBidirectionalRegular rotated 90°).
|
|
20
|
+
* Click-and-drag up/down on the icon to increment/decrement the value.
|
|
21
|
+
*/
|
|
14
22
|
export declare const SpinButton: import("react").ForwardRefExoticComponent<import("./primitive.js").BasePrimitiveProps & {
|
|
15
23
|
value: number;
|
|
16
24
|
infoLabel?: import("./infoLabel.js").InfoLabelParentProps;
|
|
@@ -25,6 +33,10 @@ export declare const SpinButton: import("react").ForwardRefExoticComponent<impor
|
|
|
25
33
|
unit?: string;
|
|
26
34
|
forceInt?: boolean;
|
|
27
35
|
validator?: (value: number) => boolean;
|
|
36
|
+
/** Optional fixed precision (number of decimal digits). Overrides the automatically computed display precision. */
|
|
37
|
+
precision?: number;
|
|
28
38
|
/** Optional className for the input element */
|
|
29
39
|
inputClassName?: string;
|
|
40
|
+
/** When true, hides the drag-to-scrub button */
|
|
41
|
+
disableDragButton?: boolean;
|
|
30
42
|
} & import("react").RefAttributes<HTMLInputElement>>;
|
|
@@ -1,138 +1,213 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
2
|
+
import { Input, makeStyles, mergeClasses, tokens, useId, useMergedRefs } from "@fluentui/react-components";
|
|
3
|
+
import { ArrowBidirectionalUpDownFilled } from "@fluentui/react-icons";
|
|
4
|
+
import { Clamp } from "@onerjs/core/Maths/math.scalar.functions.js";
|
|
5
|
+
import { forwardRef, useCallback, useContext, useEffect, useRef, useState } from "react";
|
|
6
6
|
import { ToolContext } from "../hoc/fluentToolWrapper.js";
|
|
7
7
|
import { useKeyState } from "../hooks/keyboardHooks.js";
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
import { InfoLabel } from "./infoLabel.js";
|
|
9
|
+
import { CalculatePrecision, HandleKeyDown, HandleOnBlur, useInputStyles } from "./utils.js";
|
|
10
|
+
function CoerceStepValue(step, isFineKeyPressed, isCourseKeyPressed) {
|
|
11
|
+
// When the fine key is pressed, decrease step by a factor of 10.
|
|
12
|
+
if (isFineKeyPressed) {
|
|
11
13
|
return step * 0.1;
|
|
12
14
|
}
|
|
13
|
-
// When the
|
|
14
|
-
if (
|
|
15
|
+
// When the course key is pressed, increase step by a factor of 10.
|
|
16
|
+
if (isCourseKeyPressed) {
|
|
15
17
|
return step * 10;
|
|
16
18
|
}
|
|
17
19
|
return step;
|
|
18
20
|
}
|
|
21
|
+
// Allow arbitrary expressions, primarily for math operations (e.g. 10*60 for 10 minutes in seconds).
|
|
22
|
+
// Use Function constructor to safely evaluate the expression without allowing access to scope.
|
|
23
|
+
// If the expression is invalid, fallback to NaN which will be caught by validateValue and prevent committing.
|
|
24
|
+
function EvaluateExpression(rawValue) {
|
|
25
|
+
const val = rawValue.trim();
|
|
26
|
+
try {
|
|
27
|
+
return Number(Function(`"use strict";return (${val})`)());
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return NaN;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
const useStyles = makeStyles({
|
|
34
|
+
icon: {
|
|
35
|
+
"&:hover": {
|
|
36
|
+
color: tokens.colorBrandForeground1,
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
/**
|
|
41
|
+
* A numeric input with a vertical drag-to-scrub icon (ArrowsBidirectionalRegular rotated 90°).
|
|
42
|
+
* Click-and-drag up/down on the icon to increment/decrement the value.
|
|
43
|
+
*/
|
|
19
44
|
export const SpinButton = forwardRef((props, ref) => {
|
|
20
|
-
SpinButton.displayName = "
|
|
21
|
-
const
|
|
45
|
+
SpinButton.displayName = "SpinButton2";
|
|
46
|
+
const inputClasses = useInputStyles();
|
|
47
|
+
const classes = useStyles();
|
|
22
48
|
const { size } = useContext(ToolContext);
|
|
23
49
|
const { min, max } = props;
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
const
|
|
31
|
-
const
|
|
32
|
-
// step and forceInt are not mutually exclusive since there could be cases where you want to forceInt but have spinButton jump >1 int per spin
|
|
33
|
-
const step = CoerceStepValue(props.step ?? 1, isUnfocusedAltKeyPressed || isFocusedAltKeyPressed, isUnfocusedShiftKeyPressed || isFocusedShiftKeyPressed);
|
|
50
|
+
const baseStep = props.step ?? 1;
|
|
51
|
+
// Local ref for the input element so we can blur it programmatically (e.g. when a drag starts while editing).
|
|
52
|
+
const inputRef = useRef(null);
|
|
53
|
+
const mergedRef = useMergedRefs(ref, inputRef);
|
|
54
|
+
// Modifier keys for step coercion.
|
|
55
|
+
const isAltKeyPressed = useKeyState("Alt", { preventDefault: true });
|
|
56
|
+
const isShiftKeyPressed = useKeyState("Shift");
|
|
57
|
+
const step = CoerceStepValue(baseStep, isAltKeyPressed, isShiftKeyPressed);
|
|
34
58
|
const stepPrecision = Math.max(0, CalculatePrecision(step));
|
|
59
|
+
const [value, setValue] = useState(props.value ?? 0);
|
|
60
|
+
const lastCommittedValue = useRef(props.value);
|
|
61
|
+
const [isDragging, setIsDragging] = useState(false);
|
|
62
|
+
const scrubStartYRef = useRef(0);
|
|
63
|
+
const scrubStartValueRef = useRef(0);
|
|
64
|
+
const lastPointerYRef = useRef(0);
|
|
65
|
+
const [isHovered, setIsHovered] = useState(false);
|
|
66
|
+
// Editing state: when the user is typing, we show their raw text rather than the formatted value.
|
|
67
|
+
const [isEditing, setIsEditing] = useState(false);
|
|
68
|
+
const [editText, setEditText] = useState("");
|
|
35
69
|
const valuePrecision = Math.max(0, CalculatePrecision(value));
|
|
36
|
-
// Display precision: controls how many decimals are shown in the formatted displayValue. Cap at 4 to avoid wild numbers
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
//
|
|
40
|
-
const
|
|
70
|
+
// Display precision: controls how many decimals are shown in the formatted displayValue. Cap at 4 to avoid wild numbers.
|
|
71
|
+
// If a fixed precision prop is provided, use it instead.
|
|
72
|
+
const displayPrecision = props.precision ?? Math.min(4, Math.max(stepPrecision, valuePrecision));
|
|
73
|
+
// Format a number for display: toFixed, then trim trailing zeros and period unless a fixed precision is specified.
|
|
74
|
+
const formatValue = useCallback((v) => {
|
|
75
|
+
const fixed = v.toFixed(displayPrecision);
|
|
76
|
+
if (props.precision !== undefined) {
|
|
77
|
+
return fixed;
|
|
78
|
+
}
|
|
79
|
+
return fixed.replace(/(\.\d*?)0+$/, "$1").replace(/\.$/, "");
|
|
80
|
+
}, [displayPrecision, props.precision]);
|
|
41
81
|
useEffect(() => {
|
|
42
|
-
if (props.value !== lastCommittedValue.current) {
|
|
82
|
+
if (!isDragging && props.value !== lastCommittedValue.current) {
|
|
43
83
|
lastCommittedValue.current = props.value;
|
|
44
|
-
setValue(props.value);
|
|
84
|
+
setValue(props.value ?? 0);
|
|
45
85
|
}
|
|
46
|
-
}, [props.value]);
|
|
47
|
-
const validateValue = (numericValue) => {
|
|
86
|
+
}, [props.value, isDragging]);
|
|
87
|
+
const validateValue = useCallback((numericValue) => {
|
|
48
88
|
const outOfBounds = (min !== undefined && numericValue < min) || (max !== undefined && numericValue > max);
|
|
49
89
|
const failsValidator = props.validator && !props.validator(numericValue);
|
|
50
90
|
const failsIntCheck = props.forceInt ? !Number.isInteger(numericValue) : false;
|
|
51
91
|
const invalid = !!outOfBounds || !!failsValidator || isNaN(numericValue) || !!failsIntCheck;
|
|
52
92
|
return !invalid;
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
|
|
93
|
+
}, [min, max, props.validator, props.forceInt]);
|
|
94
|
+
// Constrain a value to the valid range by clamping to [min, max].
|
|
95
|
+
const constrainValue = useCallback((v) => Clamp(v, min ?? -Infinity, max ?? Infinity), [min, max]);
|
|
96
|
+
const tryCommitValue = useCallback((currVal) => {
|
|
56
97
|
if (validateValue(currVal) && currVal !== lastCommittedValue.current) {
|
|
57
98
|
lastCommittedValue.current = currVal;
|
|
58
99
|
props.onChange(currVal);
|
|
59
100
|
}
|
|
60
|
-
};
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
101
|
+
}, [validateValue, props.onChange]);
|
|
102
|
+
const handleInputChange = useCallback((_, data) => {
|
|
103
|
+
// Just update the raw text — no evaluation or commit until Enter/blur.
|
|
104
|
+
setEditText(data.value);
|
|
105
|
+
}, []);
|
|
106
|
+
// Evaluate the current edit text and commit the value. Returns the clamped value if valid, or undefined.
|
|
107
|
+
const commitEditText = useCallback((text) => {
|
|
108
|
+
const numericValue = EvaluateExpression(text);
|
|
109
|
+
if (!isNaN(numericValue) && validateValue(numericValue)) {
|
|
110
|
+
const constrained = constrainValue(numericValue);
|
|
111
|
+
setValue(constrained);
|
|
112
|
+
tryCommitValue(constrained);
|
|
113
|
+
return constrained;
|
|
72
114
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
}
|
|
88
|
-
catch {
|
|
89
|
-
return NaN;
|
|
115
|
+
return undefined;
|
|
116
|
+
}, [validateValue, constrainValue, tryCommitValue]);
|
|
117
|
+
const handleIconPointerDown = useCallback((e) => {
|
|
118
|
+
e.preventDefault();
|
|
119
|
+
e.stopPropagation();
|
|
120
|
+
// If the input was being edited, commit the current text and blur the input
|
|
121
|
+
// so the focus state stays consistent after the drag ends.
|
|
122
|
+
let startValue = value;
|
|
123
|
+
if (isEditing) {
|
|
124
|
+
const committed = commitEditText(editText);
|
|
125
|
+
if (committed !== undefined) {
|
|
126
|
+
startValue = committed;
|
|
127
|
+
}
|
|
128
|
+
setIsEditing(false);
|
|
90
129
|
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
130
|
+
// Blur the active element to ensure we can observe document level modifier keys.
|
|
131
|
+
inputRef.current?.ownerDocument.activeElement?.blur?.();
|
|
132
|
+
setIsDragging(true);
|
|
133
|
+
scrubStartYRef.current = e.clientY;
|
|
134
|
+
scrubStartValueRef.current = startValue;
|
|
135
|
+
e.currentTarget.setPointerCapture(e.pointerId);
|
|
136
|
+
}, [value, isEditing, editText, commitEditText]);
|
|
137
|
+
// When the step size changes during a drag (e.g. Shift/Alt pressed or released), reset the scrub reference point
|
|
138
|
+
// to the current value and pointer position so only future movement uses the new step.
|
|
139
|
+
useEffect(() => {
|
|
140
|
+
if (isDragging) {
|
|
141
|
+
scrubStartValueRef.current = value;
|
|
142
|
+
scrubStartYRef.current = lastPointerYRef.current;
|
|
95
143
|
}
|
|
96
|
-
|
|
97
|
-
|
|
144
|
+
}, [step]);
|
|
145
|
+
const handleIconPointerMove = useCallback((e) => {
|
|
146
|
+
if (!isDragging) {
|
|
147
|
+
return;
|
|
98
148
|
}
|
|
99
|
-
|
|
100
|
-
//
|
|
149
|
+
lastPointerYRef.current = e.clientY;
|
|
150
|
+
// Dragging up (negative dy) should increment, dragging down should decrement.
|
|
151
|
+
// Scale delta by step but round to display precision (not step) for smooth fine-grained control.
|
|
152
|
+
const dy = scrubStartYRef.current - e.clientY;
|
|
153
|
+
// 5 is just a number that "feels right" for the drag sensitivity — it determines how far the user needs to drag to change the value by 1 step.
|
|
154
|
+
const delta = (dy * step) / 5;
|
|
155
|
+
const raw = scrubStartValueRef.current + delta;
|
|
156
|
+
const precisionFactor = Math.pow(10, displayPrecision);
|
|
157
|
+
const rounded = Math.round(raw * precisionFactor) / precisionFactor;
|
|
158
|
+
const constrained = constrainValue(rounded);
|
|
159
|
+
setValue(constrained);
|
|
160
|
+
tryCommitValue(constrained);
|
|
161
|
+
}, [isDragging, step, displayPrecision, constrainValue, tryCommitValue]);
|
|
162
|
+
const handleIconPointerUp = useCallback((e) => {
|
|
163
|
+
setIsDragging(false);
|
|
164
|
+
e.currentTarget.releasePointerCapture(e.pointerId);
|
|
165
|
+
}, []);
|
|
166
|
+
const handleKeyDown = useCallback((event) => {
|
|
167
|
+
// Commit on Enter and blur the input if the value is valid.
|
|
101
168
|
if (event.key === "Enter") {
|
|
102
|
-
const
|
|
103
|
-
if (
|
|
104
|
-
|
|
105
|
-
tryCommitValue(currVal);
|
|
169
|
+
const committed = commitEditText(event.currentTarget.value);
|
|
170
|
+
if (committed !== undefined) {
|
|
171
|
+
inputRef.current?.blur();
|
|
106
172
|
}
|
|
107
173
|
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
174
|
+
if (event.key === "ArrowUp" || event.key === "ArrowDown") {
|
|
175
|
+
event.preventDefault();
|
|
176
|
+
const direction = event.key === "ArrowUp" ? 1 : -1;
|
|
177
|
+
const newValue = constrainValue(Math.round((value + direction * step) / step) * step);
|
|
178
|
+
setValue(newValue);
|
|
179
|
+
tryCommitValue(newValue);
|
|
180
|
+
// Update edit text to reflect the new value so the user sees the change
|
|
181
|
+
setEditText(formatValue(newValue));
|
|
114
182
|
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
183
|
+
HandleKeyDown(event);
|
|
184
|
+
}, [value, step, constrainValue, tryCommitValue, commitEditText, formatValue]);
|
|
185
|
+
const id = useId("spin-button2");
|
|
186
|
+
// Real-time validation: when editing, validate the expression; otherwise validate the committed value.
|
|
187
|
+
// (validateValue already handles NaN, so no separate isNaN check needed.)
|
|
188
|
+
const isInputInvalid = !validateValue(isEditing ? EvaluateExpression(editText) : value);
|
|
189
|
+
const mergedClassName = mergeClasses(inputClasses.input, isInputInvalid ? inputClasses.invalid : "", props.className);
|
|
190
|
+
const inputSlotClassName = mergeClasses(inputClasses.inputSlot, props.inputClassName);
|
|
191
|
+
const formattedValue = formatValue(value);
|
|
192
|
+
const handleFocus = useCallback(() => {
|
|
193
|
+
setIsEditing(true);
|
|
194
|
+
setEditText(formattedValue);
|
|
195
|
+
}, [formattedValue]);
|
|
196
|
+
const handleBlur = useCallback((event) => {
|
|
197
|
+
// Skip blur handling if a drag just started (icon pointerDown already committed).
|
|
198
|
+
if (isDragging) {
|
|
121
199
|
return;
|
|
122
200
|
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
};
|
|
135
|
-
const spinButton = (_jsx(FluentSpinButton, { ref: ref, ...props, appearance: "outline", input: inputSlot, step: step, id: id, size: size, precision: fluentPrecision, displayValue: `${value.toFixed(displayPrecision)}${props.unit ? " " + props.unit : ""}`, value: value, onChange: handleChange, onKeyDown: handleKeyDown, onKeyUp: handleKeyUp, onBlur: HandleOnBlur, className: mergedClassName }));
|
|
136
|
-
return props.infoLabel ? (_jsxs("div", { className: classes.container, children: [_jsx(InfoLabel, { ...props.infoLabel, htmlFor: id }), spinButton] })) : (spinButton);
|
|
201
|
+
commitEditText(event.target.value);
|
|
202
|
+
setIsEditing(false);
|
|
203
|
+
HandleOnBlur(event);
|
|
204
|
+
}, [commitEditText, isDragging]);
|
|
205
|
+
const contentBefore = !props.disableDragButton && (isHovered || isDragging) && !isInputInvalid ? (_jsx(ArrowBidirectionalUpDownFilled, { className: classes.icon, style: { cursor: isDragging ? "ns-resize" : "pointer" }, onPointerDown: handleIconPointerDown, onPointerMove: handleIconPointerMove, onPointerUp: handleIconPointerUp })) : undefined;
|
|
206
|
+
const input = (_jsx("div", { onMouseEnter: () => setIsHovered(true), onMouseLeave: () => {
|
|
207
|
+
if (!isDragging) {
|
|
208
|
+
setIsHovered(false);
|
|
209
|
+
}
|
|
210
|
+
}, children: _jsx(Input, { ref: mergedRef, id: id, appearance: "outline", size: size, className: mergedClassName, input: { className: inputSlotClassName }, value: isEditing ? editText : formattedValue, onChange: handleInputChange, onFocus: handleFocus, onKeyDown: handleKeyDown, onBlur: handleBlur, contentBefore: contentBefore, contentAfter: props.unit }) }));
|
|
211
|
+
return props.infoLabel ? (_jsxs("div", { className: inputClasses.container, children: [_jsx(InfoLabel, { ...props.infoLabel, htmlFor: id }), input] })) : (input);
|
|
137
212
|
});
|
|
138
213
|
//# sourceMappingURL=spinButton.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"spinButton.js","sourceRoot":"","sources":["../../../../../dev/sharedUiComponents/src/fluent/primitives/spinButton.tsx"],"names":[],"mappings":"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","sourcesContent":["import { SpinButton as FluentSpinButton, mergeClasses, useId } from \"@fluentui/react-components\";\r\nimport type { SpinButtonOnChangeData, SpinButtonChangeEvent } from \"@fluentui/react-components\";\r\nimport type { KeyboardEvent } from \"react\";\r\nimport { forwardRef, useEffect, useState, useRef, useContext } from \"react\";\r\nimport type { PrimitiveProps } from \"./primitive\";\r\nimport { InfoLabel } from \"./infoLabel\";\r\nimport { CalculatePrecision, HandleKeyDown, HandleOnBlur, useInputStyles } from \"./utils\";\r\nimport { ToolContext } from \"../hoc/fluentToolWrapper\";\r\nimport { useKeyState } from \"../hooks/keyboardHooks\";\r\n\r\nfunction CoerceStepValue(step: number, isAltKeyPressed: boolean, isShiftKeyPressed: boolean): number {\r\n // When the alt key is pressed, decrease step by a factor of 10.\r\n if (isAltKeyPressed) {\r\n return step * 0.1;\r\n }\r\n\r\n // When the shift key is pressed, increase step by a factor of 10.\r\n if (isShiftKeyPressed) {\r\n return step * 10;\r\n }\r\n\r\n return step;\r\n}\r\n\r\nexport type SpinButtonProps = PrimitiveProps<number> & {\r\n min?: number;\r\n max?: number;\r\n /** Determines how much the spinbutton increments with the arrow keys. Note this also determines the precision value (# of decimals in display value)\r\n * i.e. if step = 1, precision = 0. step = 0.0089, precision = 4. step = 300, precision = 2. step = 23.00, precision = 2. */\r\n step?: number;\r\n unit?: string;\r\n forceInt?: boolean;\r\n validator?: (value: number) => boolean;\r\n /** Optional className for the input element */\r\n inputClassName?: string;\r\n};\r\n\r\nexport const SpinButton = forwardRef<HTMLInputElement, SpinButtonProps>((props, ref) => {\r\n SpinButton.displayName = \"SpinButton\";\r\n const classes = useInputStyles();\r\n const { size } = useContext(ToolContext);\r\n\r\n const { min, max } = props;\r\n\r\n const [value, setValue] = useState(props.value);\r\n const lastCommittedValue = useRef(props.value);\r\n\r\n // When the input does not have keyboard focus\r\n const isUnfocusedAltKeyPressed = useKeyState(\"Alt\");\r\n const isUnfocusedShiftKeyPressed = useKeyState(\"Shift\");\r\n\r\n // When the input does have keyboard focus\r\n const [isFocusedAltKeyPressed, setIsFocusedAltKeyPressed] = useState(false);\r\n const [isFocusedShiftKeyPressed, setIsFocusedShiftKeyPressed] = useState(false);\r\n\r\n // step and forceInt are not mutually exclusive since there could be cases where you want to forceInt but have spinButton jump >1 int per spin\r\n const step = CoerceStepValue(props.step ?? 1, isUnfocusedAltKeyPressed || isFocusedAltKeyPressed, isUnfocusedShiftKeyPressed || isFocusedShiftKeyPressed);\r\n const stepPrecision = Math.max(0, CalculatePrecision(step));\r\n const valuePrecision = Math.max(0, CalculatePrecision(value));\r\n // Display precision: controls how many decimals are shown in the formatted displayValue. Cap at 4 to avoid wild numbers\r\n const displayPrecision = Math.min(4, Math.max(stepPrecision, valuePrecision));\r\n // Set to large const to prevent Fluent from rounding user-entered values on commit\r\n // We control display formatting ourselves via displayValue, so this only affects internal rounding. The value stored internally will still have max precision\r\n const fluentPrecision = 20;\r\n\r\n useEffect(() => {\r\n if (props.value !== lastCommittedValue.current) {\r\n lastCommittedValue.current = props.value;\r\n setValue(props.value); // Update local state when props.value changes\r\n }\r\n }, [props.value]);\r\n\r\n const validateValue = (numericValue: number): boolean => {\r\n const outOfBounds = (min !== undefined && numericValue < min) || (max !== undefined && numericValue > max);\r\n const failsValidator = props.validator && !props.validator(numericValue);\r\n const failsIntCheck = props.forceInt ? !Number.isInteger(numericValue) : false;\r\n const invalid = !!outOfBounds || !!failsValidator || isNaN(numericValue) || !!failsIntCheck;\r\n return !invalid;\r\n };\r\n\r\n const tryCommitValue = (currVal: number) => {\r\n // Only commit if valid and different from last committed value\r\n if (validateValue(currVal) && currVal !== lastCommittedValue.current) {\r\n lastCommittedValue.current = currVal;\r\n props.onChange(currVal);\r\n }\r\n };\r\n\r\n const handleChange = (event: SpinButtonChangeEvent, data: SpinButtonOnChangeData) => {\r\n event.stopPropagation(); // Prevent event propagation\r\n if (data.value != null && !Number.isNaN(data.value)) {\r\n setValue(data.value);\r\n tryCommitValue(data.value);\r\n }\r\n };\r\n\r\n // Strip the unit suffix (e.g. \"deg\" or \" deg\") from the raw input value before evaluating expressions.\r\n const stripUnit = (val: string): string => {\r\n if (!props.unit) {\r\n return val;\r\n }\r\n\r\n const regex = new RegExp(\"\\\\s*\" + props.unit + \"$\");\r\n const match = val.match(regex);\r\n\r\n if (match) {\r\n return val.slice(0, -match[0].length);\r\n }\r\n return val;\r\n };\r\n\r\n // Allow arbitrary expressions, primarily for math operations (e.g. 10*60 for 10 minutes in seconds).\r\n // Use Function constructor to safely evaluate the expression without allowing access to scope.\r\n // If the expression is invalid, fallback to NaN which will be caught by validateValue and prevent committing.\r\n const evaluateExpression = (rawValue: string): number => {\r\n const val = stripUnit(rawValue).trim();\r\n try {\r\n return Number(Function(`\"use strict\";return (${val})`)());\r\n } catch {\r\n return NaN;\r\n }\r\n };\r\n\r\n const handleKeyDown = (event: KeyboardEvent<HTMLInputElement>) => {\r\n if (event.key === \"Alt\") {\r\n setIsFocusedAltKeyPressed(true);\r\n } else if (event.key === \"Shift\") {\r\n setIsFocusedShiftKeyPressed(true);\r\n }\r\n\r\n // Evaluate on Enter in keyDown (before Fluent's internal commit clears the raw text\r\n // and re-renders with the truncated displayValue).\r\n if (event.key === \"Enter\") {\r\n const currVal = evaluateExpression((event.target as HTMLInputElement).value);\r\n if (!isNaN(currVal)) {\r\n setValue(currVal);\r\n tryCommitValue(currVal);\r\n }\r\n }\r\n\r\n HandleKeyDown(event);\r\n };\r\n\r\n const handleKeyUp = (event: KeyboardEvent<HTMLInputElement>) => {\r\n event.stopPropagation(); // Prevent event propagation\r\n\r\n if (event.key === \"Alt\") {\r\n setIsFocusedAltKeyPressed(false);\r\n } else if (event.key === \"Shift\") {\r\n setIsFocusedShiftKeyPressed(false);\r\n }\r\n\r\n // Skip Enter — it's handled in keyDown before Fluent's internal commit\r\n // clears the raw text and replaces it with the truncated displayValue.\r\n if (event.key === \"Enter\") {\r\n return;\r\n }\r\n\r\n const currVal = evaluateExpression((event.target as any).value);\r\n\r\n if (!isNaN(currVal)) {\r\n setValue(currVal);\r\n tryCommitValue(currVal);\r\n }\r\n };\r\n\r\n const id = useId(\"spin-button\");\r\n const mergedClassName = mergeClasses(classes.input, !validateValue(value) ? classes.invalid : \"\", props.className);\r\n\r\n // Build input slot from inputClassName\r\n const inputSlot = {\r\n className: mergeClasses(classes.inputSlot, props.inputClassName),\r\n };\r\n\r\n const spinButton = (\r\n <FluentSpinButton\r\n ref={ref}\r\n {...props}\r\n appearance=\"outline\"\r\n input={inputSlot}\r\n step={step}\r\n id={id}\r\n size={size}\r\n precision={fluentPrecision}\r\n displayValue={`${value.toFixed(displayPrecision)}${props.unit ? \" \" + props.unit : \"\"}`}\r\n value={value}\r\n onChange={handleChange}\r\n onKeyDown={handleKeyDown}\r\n onKeyUp={handleKeyUp}\r\n onBlur={HandleOnBlur}\r\n className={mergedClassName}\r\n />\r\n );\r\n\r\n return props.infoLabel ? (\r\n <div className={classes.container}>\r\n <InfoLabel {...props.infoLabel} htmlFor={id} />\r\n {spinButton}\r\n </div>\r\n ) : (\r\n spinButton\r\n );\r\n});\r\n"]}
|
|
1
|
+
{"version":3,"file":"spinButton.js","sourceRoot":"","sources":["../../../../../dev/sharedUiComponents/src/fluent/primitives/spinButton.tsx"],"names":[],"mappings":"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","sourcesContent":["import type { ChangeEvent, FocusEvent, KeyboardEvent, PointerEvent } from \"react\";\r\n\r\nimport type { PrimitiveProps } from \"./primitive\";\r\n\r\nimport { Input, makeStyles, mergeClasses, tokens, useId, useMergedRefs } from \"@fluentui/react-components\";\r\nimport { ArrowBidirectionalUpDownFilled } from \"@fluentui/react-icons\";\r\n\r\nimport { Clamp } from \"core/Maths/math.scalar.functions\";\r\nimport { forwardRef, useCallback, useContext, useEffect, useRef, useState } from \"react\";\r\nimport { ToolContext } from \"../hoc/fluentToolWrapper\";\r\nimport { useKeyState } from \"../hooks/keyboardHooks\";\r\nimport { InfoLabel } from \"./infoLabel\";\r\nimport { CalculatePrecision, HandleKeyDown, HandleOnBlur, useInputStyles } from \"./utils\";\r\n\r\nfunction CoerceStepValue(step: number, isFineKeyPressed: boolean, isCourseKeyPressed: boolean): number {\r\n // When the fine key is pressed, decrease step by a factor of 10.\r\n if (isFineKeyPressed) {\r\n return step * 0.1;\r\n }\r\n\r\n // When the course key is pressed, increase step by a factor of 10.\r\n if (isCourseKeyPressed) {\r\n return step * 10;\r\n }\r\n\r\n return step;\r\n}\r\n\r\n// Allow arbitrary expressions, primarily for math operations (e.g. 10*60 for 10 minutes in seconds).\r\n// Use Function constructor to safely evaluate the expression without allowing access to scope.\r\n// If the expression is invalid, fallback to NaN which will be caught by validateValue and prevent committing.\r\nfunction EvaluateExpression(rawValue: string): number {\r\n const val = rawValue.trim();\r\n try {\r\n return Number(Function(`\"use strict\";return (${val})`)());\r\n } catch {\r\n return NaN;\r\n }\r\n}\r\n\r\nexport type SpinButtonProps = PrimitiveProps<number> & {\r\n min?: number;\r\n max?: number;\r\n /** Determines how much the spinbutton increments with the arrow keys. Note this also determines the precision value (# of decimals in display value)\r\n * i.e. if step = 1, precision = 0. step = 0.0089, precision = 4. step = 300, precision = 2. step = 23.00, precision = 2. */\r\n step?: number;\r\n unit?: string;\r\n forceInt?: boolean;\r\n validator?: (value: number) => boolean;\r\n /** Optional fixed precision (number of decimal digits). Overrides the automatically computed display precision. */\r\n precision?: number;\r\n /** Optional className for the input element */\r\n inputClassName?: string;\r\n /** When true, hides the drag-to-scrub button */\r\n disableDragButton?: boolean;\r\n};\r\n\r\nconst useStyles = makeStyles({\r\n icon: {\r\n \"&:hover\": {\r\n color: tokens.colorBrandForeground1,\r\n },\r\n },\r\n});\r\n\r\n/**\r\n * A numeric input with a vertical drag-to-scrub icon (ArrowsBidirectionalRegular rotated 90°).\r\n * Click-and-drag up/down on the icon to increment/decrement the value.\r\n */\r\nexport const SpinButton = forwardRef<HTMLInputElement, SpinButtonProps>((props, ref) => {\r\n SpinButton.displayName = \"SpinButton2\";\r\n const inputClasses = useInputStyles();\r\n const classes = useStyles();\r\n const { size } = useContext(ToolContext);\r\n\r\n const { min, max } = props;\r\n const baseStep = props.step ?? 1;\r\n\r\n // Local ref for the input element so we can blur it programmatically (e.g. when a drag starts while editing).\r\n const inputRef = useRef<HTMLInputElement | null>(null);\r\n const mergedRef = useMergedRefs(ref, inputRef);\r\n\r\n // Modifier keys for step coercion.\r\n const isAltKeyPressed = useKeyState(\"Alt\", { preventDefault: true });\r\n const isShiftKeyPressed = useKeyState(\"Shift\");\r\n\r\n const step = CoerceStepValue(baseStep, isAltKeyPressed, isShiftKeyPressed);\r\n const stepPrecision = Math.max(0, CalculatePrecision(step));\r\n\r\n const [value, setValue] = useState<number>(props.value ?? 0);\r\n const lastCommittedValue = useRef(props.value);\r\n const [isDragging, setIsDragging] = useState(false);\r\n const scrubStartYRef = useRef(0);\r\n const scrubStartValueRef = useRef(0);\r\n const lastPointerYRef = useRef(0);\r\n const [isHovered, setIsHovered] = useState(false);\r\n\r\n // Editing state: when the user is typing, we show their raw text rather than the formatted value.\r\n const [isEditing, setIsEditing] = useState(false);\r\n const [editText, setEditText] = useState(\"\");\r\n\r\n const valuePrecision = Math.max(0, CalculatePrecision(value));\r\n // Display precision: controls how many decimals are shown in the formatted displayValue. Cap at 4 to avoid wild numbers.\r\n // If a fixed precision prop is provided, use it instead.\r\n const displayPrecision = props.precision ?? Math.min(4, Math.max(stepPrecision, valuePrecision));\r\n\r\n // Format a number for display: toFixed, then trim trailing zeros and period unless a fixed precision is specified.\r\n const formatValue = useCallback(\r\n (v: number) => {\r\n const fixed = v.toFixed(displayPrecision);\r\n if (props.precision !== undefined) {\r\n return fixed;\r\n }\r\n return fixed.replace(/(\\.\\d*?)0+$/, \"$1\").replace(/\\.$/, \"\");\r\n },\r\n [displayPrecision, props.precision]\r\n );\r\n\r\n useEffect(() => {\r\n if (!isDragging && props.value !== lastCommittedValue.current) {\r\n lastCommittedValue.current = props.value;\r\n setValue(props.value ?? 0);\r\n }\r\n }, [props.value, isDragging]);\r\n\r\n const validateValue = useCallback(\r\n (numericValue: number): boolean => {\r\n const outOfBounds = (min !== undefined && numericValue < min) || (max !== undefined && numericValue > max);\r\n const failsValidator = props.validator && !props.validator(numericValue);\r\n const failsIntCheck = props.forceInt ? !Number.isInteger(numericValue) : false;\r\n const invalid = !!outOfBounds || !!failsValidator || isNaN(numericValue) || !!failsIntCheck;\r\n return !invalid;\r\n },\r\n [min, max, props.validator, props.forceInt]\r\n );\r\n\r\n // Constrain a value to the valid range by clamping to [min, max].\r\n const constrainValue = useCallback((v: number) => Clamp(v, min ?? -Infinity, max ?? Infinity), [min, max]);\r\n\r\n const tryCommitValue = useCallback(\r\n (currVal: number) => {\r\n if (validateValue(currVal) && currVal !== lastCommittedValue.current) {\r\n lastCommittedValue.current = currVal;\r\n props.onChange(currVal);\r\n }\r\n },\r\n [validateValue, props.onChange]\r\n );\r\n\r\n const handleInputChange = useCallback((_: ChangeEvent, data: { value: string }) => {\r\n // Just update the raw text — no evaluation or commit until Enter/blur.\r\n setEditText(data.value);\r\n }, []);\r\n\r\n // Evaluate the current edit text and commit the value. Returns the clamped value if valid, or undefined.\r\n const commitEditText = useCallback(\r\n (text: string): number | undefined => {\r\n const numericValue = EvaluateExpression(text);\r\n if (!isNaN(numericValue) && validateValue(numericValue)) {\r\n const constrained = constrainValue(numericValue);\r\n setValue(constrained);\r\n tryCommitValue(constrained);\r\n return constrained;\r\n }\r\n return undefined;\r\n },\r\n [validateValue, constrainValue, tryCommitValue]\r\n );\r\n\r\n const handleIconPointerDown = useCallback(\r\n (e: PointerEvent<Element>) => {\r\n e.preventDefault();\r\n e.stopPropagation();\r\n // If the input was being edited, commit the current text and blur the input\r\n // so the focus state stays consistent after the drag ends.\r\n let startValue = value;\r\n if (isEditing) {\r\n const committed = commitEditText(editText);\r\n if (committed !== undefined) {\r\n startValue = committed;\r\n }\r\n setIsEditing(false);\r\n }\r\n // Blur the active element to ensure we can observe document level modifier keys.\r\n (inputRef.current?.ownerDocument.activeElement as Partial<HTMLElement>)?.blur?.();\r\n setIsDragging(true);\r\n scrubStartYRef.current = e.clientY;\r\n scrubStartValueRef.current = startValue;\r\n e.currentTarget.setPointerCapture(e.pointerId);\r\n },\r\n [value, isEditing, editText, commitEditText]\r\n );\r\n\r\n // When the step size changes during a drag (e.g. Shift/Alt pressed or released), reset the scrub reference point\r\n // to the current value and pointer position so only future movement uses the new step.\r\n useEffect(() => {\r\n if (isDragging) {\r\n scrubStartValueRef.current = value;\r\n scrubStartYRef.current = lastPointerYRef.current;\r\n }\r\n }, [step]);\r\n\r\n const handleIconPointerMove = useCallback(\r\n (e: PointerEvent) => {\r\n if (!isDragging) {\r\n return;\r\n }\r\n lastPointerYRef.current = e.clientY;\r\n // Dragging up (negative dy) should increment, dragging down should decrement.\r\n // Scale delta by step but round to display precision (not step) for smooth fine-grained control.\r\n const dy = scrubStartYRef.current - e.clientY;\r\n // 5 is just a number that \"feels right\" for the drag sensitivity — it determines how far the user needs to drag to change the value by 1 step.\r\n const delta = (dy * step) / 5;\r\n const raw = scrubStartValueRef.current + delta;\r\n const precisionFactor = Math.pow(10, displayPrecision);\r\n const rounded = Math.round(raw * precisionFactor) / precisionFactor;\r\n const constrained = constrainValue(rounded);\r\n setValue(constrained);\r\n tryCommitValue(constrained);\r\n },\r\n [isDragging, step, displayPrecision, constrainValue, tryCommitValue]\r\n );\r\n\r\n const handleIconPointerUp = useCallback((e: PointerEvent<Element>) => {\r\n setIsDragging(false);\r\n e.currentTarget.releasePointerCapture(e.pointerId);\r\n }, []);\r\n\r\n const handleKeyDown = useCallback(\r\n (event: KeyboardEvent<HTMLInputElement>) => {\r\n // Commit on Enter and blur the input if the value is valid.\r\n if (event.key === \"Enter\") {\r\n const committed = commitEditText(event.currentTarget.value);\r\n if (committed !== undefined) {\r\n inputRef.current?.blur();\r\n }\r\n }\r\n\r\n if (event.key === \"ArrowUp\" || event.key === \"ArrowDown\") {\r\n event.preventDefault();\r\n const direction = event.key === \"ArrowUp\" ? 1 : -1;\r\n const newValue = constrainValue(Math.round((value + direction * step) / step) * step);\r\n setValue(newValue);\r\n tryCommitValue(newValue);\r\n // Update edit text to reflect the new value so the user sees the change\r\n setEditText(formatValue(newValue));\r\n }\r\n\r\n HandleKeyDown(event);\r\n },\r\n [value, step, constrainValue, tryCommitValue, commitEditText, formatValue]\r\n );\r\n\r\n const id = useId(\"spin-button2\");\r\n\r\n // Real-time validation: when editing, validate the expression; otherwise validate the committed value.\r\n // (validateValue already handles NaN, so no separate isNaN check needed.)\r\n const isInputInvalid = !validateValue(isEditing ? EvaluateExpression(editText) : value);\r\n\r\n const mergedClassName = mergeClasses(inputClasses.input, isInputInvalid ? inputClasses.invalid : \"\", props.className);\r\n const inputSlotClassName = mergeClasses(inputClasses.inputSlot, props.inputClassName);\r\n\r\n const formattedValue = formatValue(value);\r\n\r\n const handleFocus = useCallback(() => {\r\n setIsEditing(true);\r\n setEditText(formattedValue);\r\n }, [formattedValue]);\r\n\r\n const handleBlur = useCallback(\r\n (event: FocusEvent<HTMLInputElement>) => {\r\n // Skip blur handling if a drag just started (icon pointerDown already committed).\r\n if (isDragging) {\r\n return;\r\n }\r\n commitEditText(event.target.value);\r\n setIsEditing(false);\r\n HandleOnBlur(event);\r\n },\r\n [commitEditText, isDragging]\r\n );\r\n\r\n const contentBefore =\r\n !props.disableDragButton && (isHovered || isDragging) && !isInputInvalid ? (\r\n <ArrowBidirectionalUpDownFilled\r\n className={classes.icon}\r\n style={{ cursor: isDragging ? \"ns-resize\" : \"pointer\" }}\r\n onPointerDown={handleIconPointerDown}\r\n onPointerMove={handleIconPointerMove}\r\n onPointerUp={handleIconPointerUp}\r\n />\r\n ) : undefined;\r\n\r\n const input = (\r\n <div\r\n onMouseEnter={() => setIsHovered(true)}\r\n onMouseLeave={() => {\r\n if (!isDragging) {\r\n setIsHovered(false);\r\n }\r\n }}\r\n >\r\n <Input\r\n ref={mergedRef}\r\n id={id}\r\n appearance=\"outline\"\r\n size={size}\r\n className={mergedClassName}\r\n input={{ className: inputSlotClassName }}\r\n value={isEditing ? editText : formattedValue}\r\n onChange={handleInputChange}\r\n onFocus={handleFocus}\r\n onKeyDown={handleKeyDown}\r\n onBlur={handleBlur}\r\n contentBefore={contentBefore}\r\n contentAfter={props.unit}\r\n />\r\n </div>\r\n );\r\n\r\n return props.infoLabel ? (\r\n <div className={inputClasses.container}>\r\n <InfoLabel {...props.infoLabel} htmlFor={id} />\r\n {input}\r\n </div>\r\n ) : (\r\n input\r\n );\r\n});\r\n"]}
|
|
@@ -7,6 +7,8 @@ export type SyncedSliderProps = PrimitiveProps<number> & {
|
|
|
7
7
|
max?: number;
|
|
8
8
|
/** Step size for the slider */
|
|
9
9
|
step?: number;
|
|
10
|
+
/** Optional fixed precision (number of decimal digits). Overrides the automatically computed display precision. */
|
|
11
|
+
precision?: number;
|
|
10
12
|
/** Displayed in the ux to indicate unit of measurement */
|
|
11
13
|
unit?: string;
|
|
12
14
|
/** When true, onChange is only called when the user releases the slider, not during drag */
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { makeStyles
|
|
2
|
+
import { makeStyles } from "@fluentui/react-components";
|
|
3
3
|
import { SpinButton } from "./spinButton.js";
|
|
4
|
-
import {
|
|
4
|
+
import { Slider } from "./slider.js";
|
|
5
|
+
import { useEffect, useState, useRef } from "react";
|
|
5
6
|
import { InfoLabel } from "./infoLabel.js";
|
|
6
|
-
import { ToolContext } from "../hoc/fluentToolWrapper.js";
|
|
7
7
|
const useSyncedSliderStyles = makeStyles({
|
|
8
8
|
container: { display: "flex", minWidth: 0 },
|
|
9
9
|
syncedSlider: {
|
|
@@ -46,21 +46,13 @@ export const SyncedSliderInput = (props) => {
|
|
|
46
46
|
SyncedSliderInput.displayName = "SyncedSliderInput";
|
|
47
47
|
const { infoLabel, ...passthroughProps } = props;
|
|
48
48
|
const classes = useSyncedSliderStyles();
|
|
49
|
-
const { size } = useContext(ToolContext);
|
|
50
49
|
const [value, setValue] = useState(props.value ?? 0);
|
|
51
50
|
const pendingValueRef = useRef(undefined);
|
|
52
51
|
const isDraggingRef = useRef(false);
|
|
53
|
-
// NOTE: The Fluent slider will add tick marks if the step prop is anything other than undefined.
|
|
54
|
-
// To avoid this, we scale the min/max based on the step so we can always make step undefined.
|
|
55
|
-
// The actual step size in the Fluent slider is 1 when it is ste to undefined.
|
|
56
|
-
const min = props.min ?? 0;
|
|
57
|
-
const max = props.max ?? 100;
|
|
58
|
-
const step = props.step ?? 1;
|
|
59
52
|
useEffect(() => {
|
|
60
53
|
!isDraggingRef.current && setValue(props.value ?? 0); // Update local state when props.value changes as long as user is not actively dragging
|
|
61
54
|
}, [props.value]);
|
|
62
|
-
const handleSliderChange = (
|
|
63
|
-
const newValue = data.value * step;
|
|
55
|
+
const handleSliderChange = (newValue) => {
|
|
64
56
|
setValue(newValue);
|
|
65
57
|
if (props.notifyOnlyOnRelease) {
|
|
66
58
|
// Store the value but don't notify parent yet
|
|
@@ -96,6 +88,6 @@ export const SyncedSliderInput = (props) => {
|
|
|
96
88
|
}
|
|
97
89
|
return classes.slider;
|
|
98
90
|
};
|
|
99
|
-
return (_jsxs("div", { className: classes.container, children: [infoLabel && _jsx(InfoLabel, { ...infoLabel, htmlFor: "syncedSlider" }), _jsxs("div", { id: "syncedSlider", className: classes.syncedSlider, children: [hasSlider && (_jsx(Slider, {
|
|
91
|
+
return (_jsxs("div", { className: classes.container, children: [infoLabel && _jsx(InfoLabel, { ...infoLabel, htmlFor: "syncedSlider" }), _jsxs("div", { id: "syncedSlider", className: classes.syncedSlider, children: [hasSlider && (_jsx(Slider, { className: getSliderClassName(), value: value, onChange: handleSliderChange, min: props.min, max: props.max, step: props.step, disabled: props.disabled, onPointerDown: handleSliderPointerDown, onPointerUp: handleSliderPointerUp })), _jsx(SpinButton, { ...passthroughProps, className: hasSlider || props.compact ? classes.compactSpinButton : undefined, inputClassName: hasSlider || props.compact ? classes.compactSpinButtonInput : undefined, value: value, onChange: handleInputChange, step: props.step, disableDragButton: true })] })] }));
|
|
100
92
|
};
|
|
101
93
|
//# sourceMappingURL=syncedSlider.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"syncedSlider.js","sourceRoot":"","sources":["../../../../../dev/sharedUiComponents/src/fluent/primitives/syncedSlider.tsx"],"names":[],"mappings":"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","sourcesContent":["import type { SliderOnChangeData } from \"@fluentui/react-components\";\r\nimport { makeStyles, Slider } from \"@fluentui/react-components\";\r\nimport { SpinButton } from \"./spinButton\";\r\nimport type { ChangeEvent, FunctionComponent } from \"react\";\r\nimport { useEffect, useState, useRef, useContext } from \"react\";\r\nimport type { PrimitiveProps } from \"./primitive\";\r\nimport { InfoLabel } from \"./infoLabel\";\r\nimport { ToolContext } from \"../hoc/fluentToolWrapper\";\r\n\r\nconst useSyncedSliderStyles = makeStyles({\r\n container: { display: \"flex\", minWidth: 0 },\r\n syncedSlider: {\r\n flex: \"1 1 0\",\r\n flexDirection: \"row\",\r\n display: \"flex\",\r\n alignItems: \"center\",\r\n minWidth: 0,\r\n },\r\n slider: {\r\n flex: \"1 1 auto\",\r\n minWidth: \"75px\",\r\n maxWidth: \"75px\",\r\n },\r\n compactSlider: {\r\n flex: \"1 1 auto\",\r\n minWidth: \"50px\", // Allow shrinking for compact mode\r\n maxWidth: \"75px\",\r\n },\r\n growSlider: {\r\n flex: \"1 1 auto\",\r\n minWidth: \"50px\",\r\n // No maxWidth - slider grows to fill available space\r\n },\r\n compactSpinButton: {\r\n width: \"65px\",\r\n minWidth: \"65px\",\r\n maxWidth: \"65px\",\r\n },\r\n compactSpinButtonInput: {\r\n minWidth: \"0\",\r\n },\r\n});\r\n\r\nexport type SyncedSliderProps = PrimitiveProps<number> & {\r\n /** Minimum value for the slider */\r\n min?: number;\r\n /** Maximum value for the slider */\r\n max?: number;\r\n /** Step size for the slider */\r\n step?: number;\r\n /** Displayed in the ux to indicate unit of measurement */\r\n unit?: string;\r\n /** When true, onChange is only called when the user releases the slider, not during drag */\r\n notifyOnlyOnRelease?: boolean;\r\n /** When true, slider grows to fill space and SpinButton is fixed at 65px */\r\n compact?: boolean;\r\n /** When true, slider grows to fill all available space (no maxWidth constraint) */\r\n growSlider?: boolean;\r\n};\r\n\r\n/**\r\n * Component which synchronizes a slider and an input field, allowing the user to change the value using either control\r\n * @param props\r\n * @returns SyncedSlider component\r\n */\r\nexport const SyncedSliderInput: FunctionComponent<SyncedSliderProps> = (props) => {\r\n SyncedSliderInput.displayName = \"SyncedSliderInput\";\r\n const { infoLabel, ...passthroughProps } = props;\r\n const classes = useSyncedSliderStyles();\r\n const { size } = useContext(ToolContext);\r\n const [value, setValue] = useState<number>(props.value ?? 0);\r\n const pendingValueRef = useRef<number>(undefined);\r\n const isDraggingRef = useRef(false);\r\n\r\n // NOTE: The Fluent slider will add tick marks if the step prop is anything other than undefined.\r\n // To avoid this, we scale the min/max based on the step so we can always make step undefined.\r\n // The actual step size in the Fluent slider is 1 when it is ste to undefined.\r\n const min = props.min ?? 0;\r\n const max = props.max ?? 100;\r\n const step = props.step ?? 1;\r\n\r\n useEffect(() => {\r\n !isDraggingRef.current && setValue(props.value ?? 0); // Update local state when props.value changes as long as user is not actively dragging\r\n }, [props.value]);\r\n\r\n const handleSliderChange = (_: ChangeEvent<HTMLInputElement>, data: SliderOnChangeData) => {\r\n const newValue = data.value * step;\r\n setValue(newValue);\r\n\r\n if (props.notifyOnlyOnRelease) {\r\n // Store the value but don't notify parent yet\r\n pendingValueRef.current = newValue;\r\n } else {\r\n // Notify parent as slider changes\r\n props.onChange(newValue);\r\n }\r\n };\r\n\r\n const handleSliderPointerDown = () => {\r\n isDraggingRef.current = true;\r\n };\r\n\r\n const handleSliderPointerUp = () => {\r\n if (props.notifyOnlyOnRelease && isDraggingRef.current && pendingValueRef.current !== undefined) {\r\n props.onChange(pendingValueRef.current);\r\n pendingValueRef.current = undefined;\r\n }\r\n isDraggingRef.current = false;\r\n };\r\n\r\n const handleInputChange = (value: number) => {\r\n setValue(value);\r\n props.onChange(value); // Input always updates immediately\r\n };\r\n\r\n const hasSlider = props.min !== undefined && props.max !== undefined;\r\n\r\n // Determine Slider className based on props\r\n const getSliderClassName = () => {\r\n if (props.growSlider) {\r\n return classes.growSlider;\r\n }\r\n if (props.compact) {\r\n return classes.compactSlider;\r\n }\r\n return classes.slider;\r\n };\r\n\r\n return (\r\n <div className={classes.container}>\r\n {infoLabel && <InfoLabel {...infoLabel} htmlFor={\"syncedSlider\"} />}\r\n <div id=\"syncedSlider\" className={classes.syncedSlider}>\r\n {hasSlider && (\r\n <Slider\r\n {...passthroughProps}\r\n className={getSliderClassName()}\r\n size={size}\r\n min={min / step}\r\n max={max / step}\r\n step={undefined}\r\n value={value / step}\r\n onChange={handleSliderChange}\r\n onPointerDown={handleSliderPointerDown}\r\n onPointerUp={handleSliderPointerUp}\r\n />\r\n )}\r\n <SpinButton\r\n {...passthroughProps}\r\n className={hasSlider || props.compact ? classes.compactSpinButton : undefined}\r\n inputClassName={hasSlider || props.compact ? classes.compactSpinButtonInput : undefined}\r\n value={value}\r\n onChange={handleInputChange}\r\n step={props.step}\r\n />\r\n </div>\r\n </div>\r\n );\r\n};\r\n"]}
|
|
1
|
+
{"version":3,"file":"syncedSlider.js","sourceRoot":"","sources":["../../../../../dev/sharedUiComponents/src/fluent/primitives/syncedSlider.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AACxD,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC;AAEpD,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAExC,MAAM,qBAAqB,GAAG,UAAU,CAAC;IACrC,SAAS,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,EAAE;IAC3C,YAAY,EAAE;QACV,IAAI,EAAE,OAAO;QACb,aAAa,EAAE,KAAK;QACpB,OAAO,EAAE,MAAM;QACf,UAAU,EAAE,QAAQ;QACpB,QAAQ,EAAE,CAAC;KACd;IACD,MAAM,EAAE;QACJ,IAAI,EAAE,UAAU;QAChB,QAAQ,EAAE,MAAM;QAChB,QAAQ,EAAE,MAAM;KACnB;IACD,aAAa,EAAE;QACX,IAAI,EAAE,UAAU;QAChB,QAAQ,EAAE,MAAM,EAAE,mCAAmC;QACrD,QAAQ,EAAE,MAAM;KACnB;IACD,UAAU,EAAE;QACR,IAAI,EAAE,UAAU;QAChB,QAAQ,EAAE,MAAM;QAChB,qDAAqD;KACxD;IACD,iBAAiB,EAAE;QACf,KAAK,EAAE,MAAM;QACb,QAAQ,EAAE,MAAM;QAChB,QAAQ,EAAE,MAAM;KACnB;IACD,sBAAsB,EAAE;QACpB,QAAQ,EAAE,GAAG;KAChB;CACJ,CAAC,CAAC;AAqBH;;;;GAIG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAyC,CAAC,KAAK,EAAE,EAAE;IAC7E,iBAAiB,CAAC,WAAW,GAAG,mBAAmB,CAAC;IACpD,MAAM,EAAE,SAAS,EAAE,GAAG,gBAAgB,EAAE,GAAG,KAAK,CAAC;IACjD,MAAM,OAAO,GAAG,qBAAqB,EAAE,CAAC;IACxC,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAS,KAAK,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC;IAC7D,MAAM,eAAe,GAAG,MAAM,CAAS,SAAS,CAAC,CAAC;IAClD,MAAM,aAAa,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IAEpC,SAAS,CAAC,GAAG,EAAE;QACX,CAAC,aAAa,CAAC,OAAO,IAAI,QAAQ,CAAC,KAAK,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC,CAAC,uFAAuF;IACjJ,CAAC,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC;IAElB,MAAM,kBAAkB,GAAG,CAAC,QAAgB,EAAE,EAAE;QAC5C,QAAQ,CAAC,QAAQ,CAAC,CAAC;QAEnB,IAAI,KAAK,CAAC,mBAAmB,EAAE,CAAC;YAC5B,8CAA8C;YAC9C,eAAe,CAAC,OAAO,GAAG,QAAQ,CAAC;QACvC,CAAC;aAAM,CAAC;YACJ,kCAAkC;YAClC,KAAK,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;QAC7B,CAAC;IACL,CAAC,CAAC;IAEF,MAAM,uBAAuB,GAAG,GAAG,EAAE;QACjC,aAAa,CAAC,OAAO,GAAG,IAAI,CAAC;IACjC,CAAC,CAAC;IAEF,MAAM,qBAAqB,GAAG,GAAG,EAAE;QAC/B,IAAI,KAAK,CAAC,mBAAmB,IAAI,aAAa,CAAC,OAAO,IAAI,eAAe,CAAC,OAAO,KAAK,SAAS,EAAE,CAAC;YAC9F,KAAK,CAAC,QAAQ,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC;YACxC,eAAe,CAAC,OAAO,GAAG,SAAS,CAAC;QACxC,CAAC;QACD,aAAa,CAAC,OAAO,GAAG,KAAK,CAAC;IAClC,CAAC,CAAC;IAEF,MAAM,iBAAiB,GAAG,CAAC,KAAa,EAAE,EAAE;QACxC,QAAQ,CAAC,KAAK,CAAC,CAAC;QAChB,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,mCAAmC;IAC9D,CAAC,CAAC;IAEF,MAAM,SAAS,GAAG,KAAK,CAAC,GAAG,KAAK,SAAS,IAAI,KAAK,CAAC,GAAG,KAAK,SAAS,CAAC;IAErE,4CAA4C;IAC5C,MAAM,kBAAkB,GAAG,GAAG,EAAE;QAC5B,IAAI,KAAK,CAAC,UAAU,EAAE,CAAC;YACnB,OAAO,OAAO,CAAC,UAAU,CAAC;QAC9B,CAAC;QACD,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC;YAChB,OAAO,OAAO,CAAC,aAAa,CAAC;QACjC,CAAC;QACD,OAAO,OAAO,CAAC,MAAM,CAAC;IAC1B,CAAC,CAAC;IAEF,OAAO,CACH,eAAK,SAAS,EAAE,OAAO,CAAC,SAAS,aAC5B,SAAS,IAAI,KAAC,SAAS,OAAK,SAAS,EAAE,OAAO,EAAE,cAAc,GAAI,EACnE,eAAK,EAAE,EAAC,cAAc,EAAC,SAAS,EAAE,OAAO,CAAC,YAAY,aACjD,SAAS,IAAI,CACV,KAAC,MAAM,IACH,SAAS,EAAE,kBAAkB,EAAE,EAC/B,KAAK,EAAE,KAAK,EACZ,QAAQ,EAAE,kBAAkB,EAC5B,GAAG,EAAE,KAAK,CAAC,GAAG,EACd,GAAG,EAAE,KAAK,CAAC,GAAG,EACd,IAAI,EAAE,KAAK,CAAC,IAAI,EAChB,QAAQ,EAAE,KAAK,CAAC,QAAQ,EACxB,aAAa,EAAE,uBAAuB,EACtC,WAAW,EAAE,qBAAqB,GACpC,CACL,EACD,KAAC,UAAU,OACH,gBAAgB,EACpB,SAAS,EAAE,SAAS,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,iBAAiB,CAAC,CAAC,CAAC,SAAS,EAC7E,cAAc,EAAE,SAAS,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,sBAAsB,CAAC,CAAC,CAAC,SAAS,EACvF,KAAK,EAAE,KAAK,EACZ,QAAQ,EAAE,iBAAiB,EAC3B,IAAI,EAAE,KAAK,CAAC,IAAI,EAChB,iBAAiB,SACnB,IACA,IACJ,CACT,CAAC;AACN,CAAC,CAAC","sourcesContent":["import type { FunctionComponent } from \"react\";\r\nimport { makeStyles } from \"@fluentui/react-components\";\r\nimport { SpinButton } from \"./spinButton\";\r\nimport { Slider } from \"./slider\";\r\nimport { useEffect, useState, useRef } from \"react\";\r\nimport type { PrimitiveProps } from \"./primitive\";\r\nimport { InfoLabel } from \"./infoLabel\";\r\n\r\nconst useSyncedSliderStyles = makeStyles({\r\n container: { display: \"flex\", minWidth: 0 },\r\n syncedSlider: {\r\n flex: \"1 1 0\",\r\n flexDirection: \"row\",\r\n display: \"flex\",\r\n alignItems: \"center\",\r\n minWidth: 0,\r\n },\r\n slider: {\r\n flex: \"1 1 auto\",\r\n minWidth: \"75px\",\r\n maxWidth: \"75px\",\r\n },\r\n compactSlider: {\r\n flex: \"1 1 auto\",\r\n minWidth: \"50px\", // Allow shrinking for compact mode\r\n maxWidth: \"75px\",\r\n },\r\n growSlider: {\r\n flex: \"1 1 auto\",\r\n minWidth: \"50px\",\r\n // No maxWidth - slider grows to fill available space\r\n },\r\n compactSpinButton: {\r\n width: \"65px\",\r\n minWidth: \"65px\",\r\n maxWidth: \"65px\",\r\n },\r\n compactSpinButtonInput: {\r\n minWidth: \"0\",\r\n },\r\n});\r\n\r\nexport type SyncedSliderProps = PrimitiveProps<number> & {\r\n /** Minimum value for the slider */\r\n min?: number;\r\n /** Maximum value for the slider */\r\n max?: number;\r\n /** Step size for the slider */\r\n step?: number;\r\n /** Optional fixed precision (number of decimal digits). Overrides the automatically computed display precision. */\r\n precision?: number;\r\n /** Displayed in the ux to indicate unit of measurement */\r\n unit?: string;\r\n /** When true, onChange is only called when the user releases the slider, not during drag */\r\n notifyOnlyOnRelease?: boolean;\r\n /** When true, slider grows to fill space and SpinButton is fixed at 65px */\r\n compact?: boolean;\r\n /** When true, slider grows to fill all available space (no maxWidth constraint) */\r\n growSlider?: boolean;\r\n};\r\n\r\n/**\r\n * Component which synchronizes a slider and an input field, allowing the user to change the value using either control\r\n * @param props\r\n * @returns SyncedSlider component\r\n */\r\nexport const SyncedSliderInput: FunctionComponent<SyncedSliderProps> = (props) => {\r\n SyncedSliderInput.displayName = \"SyncedSliderInput\";\r\n const { infoLabel, ...passthroughProps } = props;\r\n const classes = useSyncedSliderStyles();\r\n const [value, setValue] = useState<number>(props.value ?? 0);\r\n const pendingValueRef = useRef<number>(undefined);\r\n const isDraggingRef = useRef(false);\r\n\r\n useEffect(() => {\r\n !isDraggingRef.current && setValue(props.value ?? 0); // Update local state when props.value changes as long as user is not actively dragging\r\n }, [props.value]);\r\n\r\n const handleSliderChange = (newValue: number) => {\r\n setValue(newValue);\r\n\r\n if (props.notifyOnlyOnRelease) {\r\n // Store the value but don't notify parent yet\r\n pendingValueRef.current = newValue;\r\n } else {\r\n // Notify parent as slider changes\r\n props.onChange(newValue);\r\n }\r\n };\r\n\r\n const handleSliderPointerDown = () => {\r\n isDraggingRef.current = true;\r\n };\r\n\r\n const handleSliderPointerUp = () => {\r\n if (props.notifyOnlyOnRelease && isDraggingRef.current && pendingValueRef.current !== undefined) {\r\n props.onChange(pendingValueRef.current);\r\n pendingValueRef.current = undefined;\r\n }\r\n isDraggingRef.current = false;\r\n };\r\n\r\n const handleInputChange = (value: number) => {\r\n setValue(value);\r\n props.onChange(value); // Input always updates immediately\r\n };\r\n\r\n const hasSlider = props.min !== undefined && props.max !== undefined;\r\n\r\n // Determine Slider className based on props\r\n const getSliderClassName = () => {\r\n if (props.growSlider) {\r\n return classes.growSlider;\r\n }\r\n if (props.compact) {\r\n return classes.compactSlider;\r\n }\r\n return classes.slider;\r\n };\r\n\r\n return (\r\n <div className={classes.container}>\r\n {infoLabel && <InfoLabel {...infoLabel} htmlFor={\"syncedSlider\"} />}\r\n <div id=\"syncedSlider\" className={classes.syncedSlider}>\r\n {hasSlider && (\r\n <Slider\r\n className={getSliderClassName()}\r\n value={value}\r\n onChange={handleSliderChange}\r\n min={props.min}\r\n max={props.max}\r\n step={props.step}\r\n disabled={props.disabled}\r\n onPointerDown={handleSliderPointerDown}\r\n onPointerUp={handleSliderPointerUp}\r\n />\r\n )}\r\n <SpinButton\r\n {...passthroughProps}\r\n className={hasSlider || props.compact ? classes.compactSpinButton : undefined}\r\n inputClassName={hasSlider || props.compact ? classes.compactSpinButtonInput : undefined}\r\n value={value}\r\n onChange={handleInputChange}\r\n step={props.step}\r\n disableDragButton\r\n />\r\n </div>\r\n </div>\r\n );\r\n};\r\n"]}
|
|
@@ -30,6 +30,9 @@ export class OptionsLine extends React.Component {
|
|
|
30
30
|
this._localChange = false;
|
|
31
31
|
return true;
|
|
32
32
|
}
|
|
33
|
+
if (nextProps.options !== this.props.options) {
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
33
36
|
const newValue = this._remapValueIn(nextProps.extractValue ? nextProps.extractValue(this.props.target) : nextProps.target[nextProps.propertyName]);
|
|
34
37
|
if (newValue != null && newValue !== nextState.value) {
|
|
35
38
|
nextState.value = newValue;
|