@onerjs/shared-ui-components 8.42.2 → 8.42.3

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.
@@ -1,7 +1,7 @@
1
1
  import type { FunctionComponent } from "react";
2
+ import type { Vector3 } from "@onerjs/core/Maths/math.vector.js";
2
3
  import type { PrimitiveProps } from "../../primitives/primitive.js";
3
4
  import type { PropertyLineProps } from "./propertyLine.js";
4
- import type { Vector3 } from "@onerjs/core/Maths/math.vector.js";
5
5
  import { Quaternion, Vector2, Vector4 } from "@onerjs/core/Maths/math.vector.js";
6
6
  export type TensorPropertyLineProps<V extends Vector2 | Vector3 | Vector4 | Quaternion> = PropertyLineProps<V> & PrimitiveProps<V> & {
7
7
  /**
@@ -20,6 +20,8 @@ export type TensorPropertyLineProps<V extends Vector2 | Vector3 | Vector4 | Quat
20
20
  * Internal spinbutton's step
21
21
  */
22
22
  step?: number;
23
+ /** Optional fixed precision (number of decimal digits). Overrides the automatically computed display precision. */
24
+ precision?: number;
23
25
  /**
24
26
  * If passed, the UX will use the conversion functions to display/update values
25
27
  */
@@ -1,11 +1,12 @@
1
1
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useEffect, useState } from "react";
3
2
  import { Body1 } from "@fluentui/react-components";
4
- import { PropertyLine } from "./propertyLine.js";
5
- import { SyncedSliderPropertyLine } from "./syncedSliderPropertyLine.js";
3
+ import { useEffect, useState } from "react";
6
4
  import { Quaternion, Vector2, Vector4 } from "@onerjs/core/Maths/math.vector.js";
7
5
  import { Tools } from "@onerjs/core/Misc/tools.js";
8
6
  import { CalculatePrecision } from "../../primitives/utils.js";
7
+ import { NumberInputPropertyLine } from "./inputPropertyLine.js";
8
+ import { PropertyLine } from "./propertyLine.js";
9
+ import { TextPropertyLine } from "./textPropertyLine.js";
9
10
  const HasZ = (vector) => !(vector instanceof Vector2);
10
11
  const HasW = (vector) => vector instanceof Vector4 || vector instanceof Quaternion;
11
12
  /**
@@ -30,21 +31,21 @@ const TensorPropertyLine = (props) => {
30
31
  useEffect(() => {
31
32
  setVector(props.value);
32
33
  }, [props.value, props.expandedContent]);
33
- return (_jsx(PropertyLine, { ...props, expandedContent: vector ? _jsx(VectorSliders, { vector: vector, min: min, max: max, unit: props.unit, step: props.step, converted: converted, onChange: onChange }) : undefined, children: _jsx(Body1, { children: `[${formatted(props.value.x)}, ${formatted(props.value.y)}${HasZ(props.value) ? `, ${formatted(props.value.z)}` : ""}${HasW(props.value) ? `, ${formatted(props.value.w)}` : ""}]` }) }));
34
+ return (_jsx(PropertyLine, { ...props, expandedContent: _jsxs(_Fragment, { children: [props.expandedContent, vector ? (_jsx(VectorSliders, { vector: vector, min: min, max: max, unit: props.unit, step: props.step, precision: props.precision, converted: converted, onChange: onChange })) : undefined] }), children: _jsx(Body1, { children: `[${formatted(props.value.x)}, ${formatted(props.value.y)}${HasZ(props.value) ? `, ${formatted(props.value.z)}` : ""}${HasW(props.value) ? `, ${formatted(props.value.w)}` : ""}]` }) }));
34
35
  };
35
- const VectorSliders = ({ vector, min, max, unit, step, converted, onChange }) => (_jsxs(_Fragment, { children: [_jsx(SyncedSliderPropertyLine, { label: "X", value: converted(vector.x), min: min, max: max, onChange: (val) => onChange(val, "x"), unit: unit, step: step }), _jsx(SyncedSliderPropertyLine, { label: "Y", value: converted(vector.y), min: min, max: max, onChange: (val) => onChange(val, "y"), unit: unit, step: step }), HasZ(vector) && _jsx(SyncedSliderPropertyLine, { label: "Z", value: converted(vector.z), min: min, max: max, onChange: (val) => onChange(val, "z"), unit: unit, step: step }), HasW(vector) && _jsx(SyncedSliderPropertyLine, { label: "W", value: converted(vector.w), min: min, max: max, onChange: (val) => onChange(val, "w"), unit: unit, step: step })] }));
36
+ const VectorSliders = ({ vector, min, max, unit, step, precision, converted, onChange }) => (_jsxs(_Fragment, { children: [_jsx(NumberInputPropertyLine, { label: "X", value: converted(vector.x), min: min, max: max, onChange: (val) => onChange(val, "x"), unit: unit, step: step, precision: precision }), _jsx(NumberInputPropertyLine, { label: "Y", value: converted(vector.y), min: min, max: max, onChange: (val) => onChange(val, "y"), unit: unit, step: step, precision: precision }), HasZ(vector) && (_jsx(NumberInputPropertyLine, { label: "Z", value: converted(vector.z), min: min, max: max, onChange: (val) => onChange(val, "z"), unit: unit, step: step, precision: precision })), HasW(vector) && (_jsx(NumberInputPropertyLine, { label: "W", value: converted(vector.w), min: min, max: max, onChange: (val) => onChange(val, "w"), unit: unit, step: step, precision: precision }))] }));
36
37
  const ToDegreesConverter = { from: Tools.ToDegrees, to: Tools.ToRadians };
37
38
  export const RotationVectorPropertyLine = (props) => {
38
39
  RotationVectorPropertyLine.displayName = "RotationVectorPropertyLine";
39
- const min = props.useDegrees ? 0 : undefined;
40
- const max = props.useDegrees ? 360 : undefined;
41
- return (_jsx(Vector3PropertyLine, { ...props, unit: props.useDegrees ? "deg" : "rad", valueConverter: props.useDegrees ? ToDegreesConverter : undefined, min: min, max: max, step: 0.001 }));
40
+ const step = props.useDegrees ? 1 : 0.01;
41
+ const precision = props.useDegrees ? 1 : 2;
42
+ return (_jsx(Vector3PropertyLine, { ...props, unit: props.useDegrees ? "°" : "rad", valueConverter: props.useDegrees ? ToDegreesConverter : undefined, step: step, precision: precision }));
42
43
  };
43
44
  const QuaternionPropertyLineInternal = TensorPropertyLine;
44
45
  export const QuaternionPropertyLine = (props) => {
45
46
  QuaternionPropertyLine.displayName = "QuaternionPropertyLine";
46
- const min = props.useDegrees ? 0 : undefined;
47
- const max = props.useDegrees ? 360 : undefined;
47
+ const step = props.useDegrees ? 1 : 0.01;
48
+ const precision = props.useDegrees ? 1 : 2;
48
49
  const [quat, setQuat] = useState(props.value);
49
50
  useEffect(() => {
50
51
  setQuat(props.value);
@@ -59,7 +60,7 @@ export const QuaternionPropertyLine = (props) => {
59
60
  const quat = Quaternion.FromEulerAngles(val.x, val.y, val.z);
60
61
  onQuatChange(quat);
61
62
  };
62
- return useEuler ? (_jsx(Vector3PropertyLine, { ...restProps, nullable: false, ignoreNullable: false, value: quat.toEulerAngles(), valueConverter: ToDegreesConverter, min: min, max: max, onChange: onEulerChange, unit: props.useDegrees ? "deg" : "rad" })) : (_jsx(QuaternionPropertyLineInternal, { ...props, nullable: false, value: quat, min: min, max: max, onChange: onQuatChange, unit: props.useDegrees ? "deg" : "rad" }));
63
+ return useEuler ? (_jsx(Vector3PropertyLine, { ...restProps, nullable: false, ignoreNullable: false, value: quat.toEulerAngles(), valueConverter: ToDegreesConverter, onChange: onEulerChange, unit: props.useDegrees ? "°" : "rad", step: step, precision: precision, expandedContent: _jsx(TextPropertyLine, { label: "Quaternion", value: `[${quat.x.toFixed(4)}, ${quat.y.toFixed(4)}, ${quat.z.toFixed(4)}, ${quat.w.toFixed(4)}]` }) })) : (_jsx(QuaternionPropertyLineInternal, { ...props, nullable: false, value: quat, onChange: onQuatChange, unit: props.useDegrees ? "°" : "rad", step: step, precision: precision }));
63
64
  };
64
65
  export const Vector2PropertyLine = TensorPropertyLine;
65
66
  export const Vector3PropertyLine = TensorPropertyLine;
@@ -1 +1 @@
1
- {"version":3,"file":"vectorPropertyLine.js","sourceRoot":"","sources":["../../../../../../dev/sharedUiComponents/src/fluent/hoc/propertyLines/vectorPropertyLine.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useEffect, useState } from \"react\";\r\nimport type { FunctionComponent } from \"react\";\r\n\r\nimport { Body1 } from \"@fluentui/react-components\";\r\nimport { PropertyLine } from \"./propertyLine\";\r\nimport type { PrimitiveProps } from \"../../primitives/primitive\";\r\nimport type { PropertyLineProps } from \"./propertyLine\";\r\n\r\nimport { SyncedSliderPropertyLine } from \"./syncedSliderPropertyLine\";\r\nimport type { Vector3 } from \"core/Maths/math.vector\";\r\nimport { Quaternion, Vector2, Vector4 } from \"core/Maths/math.vector\";\r\nimport { Tools } from \"core/Misc/tools\";\r\nimport { CalculatePrecision } from \"../../primitives/utils\";\r\n\r\nexport type TensorPropertyLineProps<V extends Vector2 | Vector3 | Vector4 | Quaternion> = PropertyLineProps<V> &\r\n PrimitiveProps<V> & {\r\n /**\r\n * If passed, all sliders will use this for the min value\r\n */\r\n min?: number;\r\n /**\r\n * If passed, all sliders will use this for the max value\r\n */\r\n max?: number;\r\n /**\r\n * Will be displayed in the input UI to indicate the unit of measurement\r\n */\r\n unit?: string;\r\n\r\n /**\r\n * Internal spinbutton's step\r\n */\r\n step?: number;\r\n /**\r\n * If passed, the UX will use the conversion functions to display/update values\r\n */\r\n valueConverter?: {\r\n /**\r\n * Will call from(val) before displaying in the UX\r\n */\r\n from: (val: number) => number;\r\n /**\r\n * Will call to(val) before calling onChange\r\n */\r\n to: (val: number) => number;\r\n };\r\n };\r\n\r\nconst HasZ = (vector: Vector2 | Vector3 | Vector4 | Quaternion): vector is Vector3 => !(vector instanceof Vector2);\r\nconst HasW = (vector: Vector2 | Vector3 | Vector4 | Quaternion): vector is Vector4 => vector instanceof Vector4 || vector instanceof Quaternion;\r\n\r\n/**\r\n * Reusable component which renders a vector property line containing a label, vector value, and expandable XYZW values\r\n * The expanded section contains a slider/input box for each component of the vector (x, y, z, w)\r\n * @param props\r\n * @returns\r\n */\r\nconst TensorPropertyLine: FunctionComponent<TensorPropertyLineProps<Vector2 | Vector3 | Vector4 | Quaternion>> = (props) => {\r\n TensorPropertyLine.displayName = \"TensorPropertyLine\";\r\n const converted = (val: number) => (props.valueConverter ? props.valueConverter.from(val) : val);\r\n const formatted = (val: number) => converted(val).toFixed(props.step !== undefined ? Math.max(0, CalculatePrecision(props.step)) : 2);\r\n\r\n const [vector, setVector] = useState(props.value);\r\n const { min, max } = props;\r\n\r\n const onChange = (val: number, key: \"x\" | \"y\" | \"z\" | \"w\") => {\r\n const value = props.valueConverter ? props.valueConverter.to(val) : val;\r\n const newVector = vector.clone();\r\n (newVector as Vector4)[key] = value; // The syncedSlider for 'w' is only rendered when vector is a Vector4, so this is safe\r\n\r\n setVector(newVector);\r\n props.onChange(newVector);\r\n };\r\n\r\n useEffect(() => {\r\n setVector(props.value);\r\n }, [props.value, props.expandedContent]);\r\n\r\n return (\r\n <PropertyLine\r\n {...props}\r\n expandedContent={\r\n vector ? <VectorSliders vector={vector} min={min} max={max} unit={props.unit} step={props.step} converted={converted} onChange={onChange} /> : undefined\r\n }\r\n >\r\n <Body1>{`[${formatted(props.value.x)}, ${formatted(props.value.y)}${HasZ(props.value) ? `, ${formatted(props.value.z)}` : \"\"}${HasW(props.value) ? `, ${formatted(props.value.w)}` : \"\"}]`}</Body1>\r\n </PropertyLine>\r\n );\r\n};\r\n\r\ntype VectorSlidersProps<V extends Vector2 | Vector3 | Vector4 | Quaternion> = {\r\n vector: V;\r\n min?: number;\r\n max?: number;\r\n unit?: string;\r\n step?: number;\r\n converted: (val: number) => number;\r\n onChange: (val: number, key: \"x\" | \"y\" | \"z\" | \"w\") => void;\r\n};\r\n\r\nconst VectorSliders = <V extends Vector2 | Vector3 | Vector4 | Quaternion>({ vector, min, max, unit, step, converted, onChange }: VectorSlidersProps<V>) => (\r\n <>\r\n <SyncedSliderPropertyLine label=\"X\" value={converted(vector.x)} min={min} max={max} onChange={(val) => onChange(val, \"x\")} unit={unit} step={step} />\r\n <SyncedSliderPropertyLine label=\"Y\" value={converted(vector.y)} min={min} max={max} onChange={(val) => onChange(val, \"y\")} unit={unit} step={step} />\r\n {HasZ(vector) && <SyncedSliderPropertyLine label=\"Z\" value={converted(vector.z)} min={min} max={max} onChange={(val) => onChange(val, \"z\")} unit={unit} step={step} />}\r\n {HasW(vector) && <SyncedSliderPropertyLine label=\"W\" value={converted(vector.w)} min={min} max={max} onChange={(val) => onChange(val, \"w\")} unit={unit} step={step} />}\r\n </>\r\n);\r\n\r\ntype RotationVectorPropertyLineProps = TensorPropertyLineProps<Vector3> & {\r\n /**\r\n * Display angles as degrees instead of radians\r\n */\r\n useDegrees?: boolean;\r\n};\r\n\r\nconst ToDegreesConverter = { from: Tools.ToDegrees, to: Tools.ToRadians };\r\nexport const RotationVectorPropertyLine: FunctionComponent<RotationVectorPropertyLineProps> = (props) => {\r\n RotationVectorPropertyLine.displayName = \"RotationVectorPropertyLine\";\r\n const min = props.useDegrees ? 0 : undefined;\r\n const max = props.useDegrees ? 360 : undefined;\r\n return (\r\n <Vector3PropertyLine\r\n {...props}\r\n unit={props.useDegrees ? \"deg\" : \"rad\"}\r\n valueConverter={props.useDegrees ? ToDegreesConverter : undefined}\r\n min={min}\r\n max={max}\r\n step={0.001}\r\n />\r\n );\r\n};\r\n\r\ntype QuaternionPropertyLineProps = TensorPropertyLineProps<Quaternion> & {\r\n /**\r\n * Display angles as degrees instead of radians\r\n */\r\n useDegrees?: boolean;\r\n /**\r\n * Display angles as Euler angles instead of quaternions\r\n */\r\n useEuler?: boolean;\r\n};\r\nconst QuaternionPropertyLineInternal = TensorPropertyLine as FunctionComponent<TensorPropertyLineProps<Quaternion>>;\r\nexport const QuaternionPropertyLine: FunctionComponent<QuaternionPropertyLineProps> = (props) => {\r\n QuaternionPropertyLine.displayName = \"QuaternionPropertyLine\";\r\n const min = props.useDegrees ? 0 : undefined;\r\n const max = props.useDegrees ? 360 : undefined;\r\n const [quat, setQuat] = useState(props.value);\r\n\r\n useEffect(() => {\r\n setQuat(props.value);\r\n }, [props.value]);\r\n\r\n // Extract only the properties that exist on QuaternionPropertyLineProps\r\n const { useEuler, ...restProps } = props;\r\n\r\n const onQuatChange = (val: Quaternion) => {\r\n setQuat(val);\r\n props.onChange(val);\r\n };\r\n\r\n const onEulerChange = (val: Vector3) => {\r\n const quat = Quaternion.FromEulerAngles(val.x, val.y, val.z);\r\n onQuatChange(quat);\r\n };\r\n\r\n return useEuler ? (\r\n <Vector3PropertyLine\r\n {...restProps}\r\n nullable={false}\r\n ignoreNullable={false}\r\n value={quat.toEulerAngles()}\r\n valueConverter={ToDegreesConverter}\r\n min={min}\r\n max={max}\r\n onChange={onEulerChange}\r\n unit={props.useDegrees ? \"deg\" : \"rad\"}\r\n />\r\n ) : (\r\n <QuaternionPropertyLineInternal {...props} nullable={false} value={quat} min={min} max={max} onChange={onQuatChange} unit={props.useDegrees ? \"deg\" : \"rad\"} />\r\n );\r\n};\r\nexport const Vector2PropertyLine = TensorPropertyLine as FunctionComponent<TensorPropertyLineProps<Vector2>>;\r\nexport const Vector3PropertyLine = TensorPropertyLine as FunctionComponent<TensorPropertyLineProps<Vector3>>;\r\nexport const Vector4PropertyLine = TensorPropertyLine as FunctionComponent<TensorPropertyLineProps<Vector4>>;\r\n"]}
1
+ {"version":3,"file":"vectorPropertyLine.js","sourceRoot":"","sources":["../../../../../../dev/sharedUiComponents/src/fluent/hoc/propertyLines/vectorPropertyLine.tsx"],"names":[],"mappings":"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","sourcesContent":["import type { FunctionComponent } from \"react\";\r\n\r\nimport type { Vector3 } from \"core/Maths/math.vector\";\r\nimport type { PrimitiveProps } from \"../../primitives/primitive\";\r\nimport type { PropertyLineProps } from \"./propertyLine\";\r\n\r\nimport { Body1 } from \"@fluentui/react-components\";\r\nimport { useEffect, useState } from \"react\";\r\n\r\nimport { Quaternion, Vector2, Vector4 } from \"core/Maths/math.vector\";\r\nimport { Tools } from \"core/Misc/tools\";\r\nimport { CalculatePrecision } from \"../../primitives/utils\";\r\nimport { NumberInputPropertyLine } from \"./inputPropertyLine\";\r\nimport { PropertyLine } from \"./propertyLine\";\r\nimport { TextPropertyLine } from \"./textPropertyLine\";\r\n\r\nexport type TensorPropertyLineProps<V extends Vector2 | Vector3 | Vector4 | Quaternion> = PropertyLineProps<V> &\r\n PrimitiveProps<V> & {\r\n /**\r\n * If passed, all sliders will use this for the min value\r\n */\r\n min?: number;\r\n /**\r\n * If passed, all sliders will use this for the max value\r\n */\r\n max?: number;\r\n /**\r\n * Will be displayed in the input UI to indicate the unit of measurement\r\n */\r\n unit?: string;\r\n /**\r\n * Internal spinbutton's step\r\n */\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 /**\r\n * If passed, the UX will use the conversion functions to display/update values\r\n */\r\n valueConverter?: {\r\n /**\r\n * Will call from(val) before displaying in the UX\r\n */\r\n from: (val: number) => number;\r\n /**\r\n * Will call to(val) before calling onChange\r\n */\r\n to: (val: number) => number;\r\n };\r\n };\r\n\r\nconst HasZ = (vector: Vector2 | Vector3 | Vector4 | Quaternion): vector is Vector3 => !(vector instanceof Vector2);\r\nconst HasW = (vector: Vector2 | Vector3 | Vector4 | Quaternion): vector is Vector4 => vector instanceof Vector4 || vector instanceof Quaternion;\r\n\r\n/**\r\n * Reusable component which renders a vector property line containing a label, vector value, and expandable XYZW values\r\n * The expanded section contains a slider/input box for each component of the vector (x, y, z, w)\r\n * @param props\r\n * @returns\r\n */\r\nconst TensorPropertyLine: FunctionComponent<TensorPropertyLineProps<Vector2 | Vector3 | Vector4 | Quaternion>> = (props) => {\r\n TensorPropertyLine.displayName = \"TensorPropertyLine\";\r\n const converted = (val: number) => (props.valueConverter ? props.valueConverter.from(val) : val);\r\n const formatted = (val: number) => converted(val).toFixed(props.step !== undefined ? Math.max(0, CalculatePrecision(props.step)) : 2);\r\n\r\n const [vector, setVector] = useState(props.value);\r\n const { min, max } = props;\r\n\r\n const onChange = (val: number, key: \"x\" | \"y\" | \"z\" | \"w\") => {\r\n const value = props.valueConverter ? props.valueConverter.to(val) : val;\r\n const newVector = vector.clone();\r\n (newVector as Vector4)[key] = value; // The syncedSlider for 'w' is only rendered when vector is a Vector4, so this is safe\r\n\r\n setVector(newVector);\r\n props.onChange(newVector);\r\n };\r\n\r\n useEffect(() => {\r\n setVector(props.value);\r\n }, [props.value, props.expandedContent]);\r\n\r\n return (\r\n <PropertyLine\r\n {...props}\r\n expandedContent={\r\n <>\r\n {props.expandedContent}\r\n {vector ? (\r\n <VectorSliders\r\n vector={vector}\r\n min={min}\r\n max={max}\r\n unit={props.unit}\r\n step={props.step}\r\n precision={props.precision}\r\n converted={converted}\r\n onChange={onChange}\r\n />\r\n ) : undefined}\r\n </>\r\n }\r\n >\r\n <Body1>{`[${formatted(props.value.x)}, ${formatted(props.value.y)}${HasZ(props.value) ? `, ${formatted(props.value.z)}` : \"\"}${HasW(props.value) ? `, ${formatted(props.value.w)}` : \"\"}]`}</Body1>\r\n </PropertyLine>\r\n );\r\n};\r\n\r\ntype VectorSlidersProps<V extends Vector2 | Vector3 | Vector4 | Quaternion> = {\r\n vector: V;\r\n min?: number;\r\n max?: number;\r\n unit?: string;\r\n step?: number;\r\n precision?: number;\r\n converted: (val: number) => number;\r\n onChange: (val: number, key: \"x\" | \"y\" | \"z\" | \"w\") => void;\r\n};\r\n\r\nconst VectorSliders = <V extends Vector2 | Vector3 | Vector4 | Quaternion>({ vector, min, max, unit, step, precision, converted, onChange }: VectorSlidersProps<V>) => (\r\n <>\r\n <NumberInputPropertyLine label=\"X\" value={converted(vector.x)} min={min} max={max} onChange={(val) => onChange(val, \"x\")} unit={unit} step={step} precision={precision} />\r\n <NumberInputPropertyLine label=\"Y\" value={converted(vector.y)} min={min} max={max} onChange={(val) => onChange(val, \"y\")} unit={unit} step={step} precision={precision} />\r\n {HasZ(vector) && (\r\n <NumberInputPropertyLine\r\n label=\"Z\"\r\n value={converted(vector.z)}\r\n min={min}\r\n max={max}\r\n onChange={(val) => onChange(val, \"z\")}\r\n unit={unit}\r\n step={step}\r\n precision={precision}\r\n />\r\n )}\r\n {HasW(vector) && (\r\n <NumberInputPropertyLine\r\n label=\"W\"\r\n value={converted(vector.w)}\r\n min={min}\r\n max={max}\r\n onChange={(val) => onChange(val, \"w\")}\r\n unit={unit}\r\n step={step}\r\n precision={precision}\r\n />\r\n )}\r\n </>\r\n);\r\n\r\ntype RotationVectorPropertyLineProps = TensorPropertyLineProps<Vector3> & {\r\n /**\r\n * Display angles as degrees instead of radians\r\n */\r\n useDegrees?: boolean;\r\n};\r\n\r\nconst ToDegreesConverter = { from: Tools.ToDegrees, to: Tools.ToRadians };\r\nexport const RotationVectorPropertyLine: FunctionComponent<RotationVectorPropertyLineProps> = (props) => {\r\n RotationVectorPropertyLine.displayName = \"RotationVectorPropertyLine\";\r\n const step = props.useDegrees ? 1 : 0.01;\r\n const precision = props.useDegrees ? 1 : 2;\r\n return (\r\n <Vector3PropertyLine\r\n {...props}\r\n unit={props.useDegrees ? \"°\" : \"rad\"}\r\n valueConverter={props.useDegrees ? ToDegreesConverter : undefined}\r\n step={step}\r\n precision={precision}\r\n />\r\n );\r\n};\r\n\r\ntype QuaternionPropertyLineProps = TensorPropertyLineProps<Quaternion> & {\r\n /**\r\n * Display angles as degrees instead of radians\r\n */\r\n useDegrees?: boolean;\r\n /**\r\n * Display angles as Euler angles instead of quaternions\r\n */\r\n useEuler?: boolean;\r\n};\r\nconst QuaternionPropertyLineInternal = TensorPropertyLine as FunctionComponent<TensorPropertyLineProps<Quaternion>>;\r\nexport const QuaternionPropertyLine: FunctionComponent<QuaternionPropertyLineProps> = (props) => {\r\n QuaternionPropertyLine.displayName = \"QuaternionPropertyLine\";\r\n const step = props.useDegrees ? 1 : 0.01;\r\n const precision = props.useDegrees ? 1 : 2;\r\n const [quat, setQuat] = useState(props.value);\r\n\r\n useEffect(() => {\r\n setQuat(props.value);\r\n }, [props.value]);\r\n\r\n // Extract only the properties that exist on QuaternionPropertyLineProps\r\n const { useEuler, ...restProps } = props;\r\n\r\n const onQuatChange = (val: Quaternion) => {\r\n setQuat(val);\r\n props.onChange(val);\r\n };\r\n\r\n const onEulerChange = (val: Vector3) => {\r\n const quat = Quaternion.FromEulerAngles(val.x, val.y, val.z);\r\n onQuatChange(quat);\r\n };\r\n\r\n return useEuler ? (\r\n <Vector3PropertyLine\r\n {...restProps}\r\n nullable={false}\r\n ignoreNullable={false}\r\n value={quat.toEulerAngles()}\r\n valueConverter={ToDegreesConverter}\r\n onChange={onEulerChange}\r\n unit={props.useDegrees ? \"°\" : \"rad\"}\r\n step={step}\r\n precision={precision}\r\n expandedContent={<TextPropertyLine label=\"Quaternion\" value={`[${quat.x.toFixed(4)}, ${quat.y.toFixed(4)}, ${quat.z.toFixed(4)}, ${quat.w.toFixed(4)}]`} />}\r\n />\r\n ) : (\r\n <QuaternionPropertyLineInternal {...props} nullable={false} value={quat} onChange={onQuatChange} unit={props.useDegrees ? \"°\" : \"rad\"} step={step} precision={precision} />\r\n );\r\n};\r\nexport const Vector2PropertyLine = TensorPropertyLine as FunctionComponent<TensorPropertyLineProps<Vector2>>;\r\nexport const Vector3PropertyLine = TensorPropertyLine as FunctionComponent<TensorPropertyLineProps<Vector3>>;\r\nexport const Vector4PropertyLine = TensorPropertyLine as FunctionComponent<TensorPropertyLineProps<Vector4>>;\r\n"]}
@@ -4,5 +4,8 @@ type KeyCallbacks = {
4
4
  onKeyUp?: (e: KeyboardEvent) => void;
5
5
  };
6
6
  export declare function useKeyListener(callbacks: KeyCallbacks, options?: WindowOptions): void;
7
- export declare function useKeyState(key: string, options?: WindowOptions): boolean;
7
+ type KeyStateOptions = WindowOptions & {
8
+ preventDefault?: boolean;
9
+ };
10
+ export declare function useKeyState(key: string, options?: KeyStateOptions): boolean;
8
11
  export {};
@@ -23,14 +23,20 @@ export function useKeyState(key, options) {
23
23
  useKeyListener({
24
24
  onKeyDown: useCallback((e) => {
25
25
  if (e.key === key) {
26
+ if (options?.preventDefault) {
27
+ e.preventDefault();
28
+ }
26
29
  setIsPressed(true);
27
30
  }
28
- }, [key]),
31
+ }, [key, options?.preventDefault]),
29
32
  onKeyUp: useCallback((e) => {
30
33
  if (e.key === key) {
34
+ if (options?.preventDefault) {
35
+ e.preventDefault();
36
+ }
31
37
  setIsPressed(false);
32
38
  }
33
- }, [key]),
39
+ }, [key, options?.preventDefault]),
34
40
  }, options);
35
41
  useEventListener("window", "blur", useCallback(() => setIsPressed(false), []), options); // Reset state on window blur to avoid stuck keys
36
42
  return isPressed;
@@ -1 +1 @@
1
- {"version":3,"file":"keyboardHooks.js","sourceRoot":"","sources":["../../../../../dev/sharedUiComponents/src/fluent/hooks/keyboardHooks.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAE9C,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAOhD,MAAM,UAAU,cAAc,CAAC,SAAuB,EAAE,OAAuB;IAC3E,MAAM,WAAW,GAAG,IAAI,GAAG,CAAgE;QACvF,CAAC,SAAS,EAAE,SAAS,CAAC,SAAS,CAAC;QAChC,CAAC,OAAO,EAAE,SAAS,CAAC,OAAO,CAAC;KAC/B,CAAC,CAAC;IAEH,KAAK,MAAM,SAAS,IAAI,CAAC,SAAS,EAAE,OAAO,CAAU,EAAE,CAAC;QACpD,MAAM,OAAO,GAAG,WAAW,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QAC3C,IAAI,OAAO,EAAE,CAAC;YACV,kDAAkD;YAClD,MAAM,cAAc,GAAG,CAAC,CAAgB,EAAE,EAAE;gBACxC,IAAI,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;oBACZ,OAAO,CAAC,CAAC,CAAC,CAAC;gBACf,CAAC;YACL,CAAC,CAAC;YAEF,gBAAgB,CAAC,UAAU,EAAE,SAAS,EAAE,cAAc,EAAE,OAAO,CAAC,CAAC;QACrE,CAAC;IACL,CAAC;AACL,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,GAAW,EAAE,OAAuB;IAC5D,MAAM,CAAC,SAAS,EAAE,YAAY,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IAElD,cAAc,CACV;QACI,SAAS,EAAE,WAAW,CAClB,CAAC,CAAgB,EAAE,EAAE;YACjB,IAAI,CAAC,CAAC,GAAG,KAAK,GAAG,EAAE,CAAC;gBAChB,YAAY,CAAC,IAAI,CAAC,CAAC;YACvB,CAAC;QACL,CAAC,EACD,CAAC,GAAG,CAAC,CACR;QACD,OAAO,EAAE,WAAW,CAChB,CAAC,CAAgB,EAAE,EAAE;YACjB,IAAI,CAAC,CAAC,GAAG,KAAK,GAAG,EAAE,CAAC;gBAChB,YAAY,CAAC,KAAK,CAAC,CAAC;YACxB,CAAC;QACL,CAAC,EACD,CAAC,GAAG,CAAC,CACR;KACJ,EACD,OAAO,CACV,CAAC;IAEF,gBAAgB,CACZ,QAAQ,EACR,MAAM,EACN,WAAW,CAAC,GAAG,EAAE,CAAC,YAAY,CAAC,KAAK,CAAC,EAAE,EAAE,CAAC,EAC1C,OAAO,CACV,CAAC,CAAC,iDAAiD;IAEpD,OAAO,SAAS,CAAC;AACrB,CAAC","sourcesContent":["import type { WindowOptions } from \"./eventHooks\";\r\n\r\nimport { useCallback, useState } from \"react\";\r\n\r\nimport { useEventListener } from \"./eventHooks\";\r\n\r\ntype KeyCallbacks = {\r\n onKeyDown?: (e: KeyboardEvent) => void;\r\n onKeyUp?: (e: KeyboardEvent) => void;\r\n};\r\n\r\nexport function useKeyListener(callbacks: KeyCallbacks, options?: WindowOptions) {\r\n const callbackMap = new Map<\"keydown\" | \"keyup\", ((e: KeyboardEvent) => void) | undefined>([\r\n [\"keydown\", callbacks.onKeyDown],\r\n [\"keyup\", callbacks.onKeyUp],\r\n ]);\r\n\r\n for (const eventType of [\"keydown\", \"keyup\"] as const) {\r\n const handler = callbackMap.get(eventType);\r\n if (handler) {\r\n // Ignore repeated events from holding down a key.\r\n const guardedHandler = (e: KeyboardEvent) => {\r\n if (!e.repeat) {\r\n handler(e);\r\n }\r\n };\r\n\r\n useEventListener(\"document\", eventType, guardedHandler, options);\r\n }\r\n }\r\n}\r\n\r\nexport function useKeyState(key: string, options?: WindowOptions): boolean {\r\n const [isPressed, setIsPressed] = useState(false);\r\n\r\n useKeyListener(\r\n {\r\n onKeyDown: useCallback(\r\n (e: KeyboardEvent) => {\r\n if (e.key === key) {\r\n setIsPressed(true);\r\n }\r\n },\r\n [key]\r\n ),\r\n onKeyUp: useCallback(\r\n (e: KeyboardEvent) => {\r\n if (e.key === key) {\r\n setIsPressed(false);\r\n }\r\n },\r\n [key]\r\n ),\r\n },\r\n options\r\n );\r\n\r\n useEventListener(\r\n \"window\",\r\n \"blur\",\r\n useCallback(() => setIsPressed(false), []),\r\n options\r\n ); // Reset state on window blur to avoid stuck keys\r\n\r\n return isPressed;\r\n}\r\n"]}
1
+ {"version":3,"file":"keyboardHooks.js","sourceRoot":"","sources":["../../../../../dev/sharedUiComponents/src/fluent/hooks/keyboardHooks.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAE9C,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAOhD,MAAM,UAAU,cAAc,CAAC,SAAuB,EAAE,OAAuB;IAC3E,MAAM,WAAW,GAAG,IAAI,GAAG,CAAgE;QACvF,CAAC,SAAS,EAAE,SAAS,CAAC,SAAS,CAAC;QAChC,CAAC,OAAO,EAAE,SAAS,CAAC,OAAO,CAAC;KAC/B,CAAC,CAAC;IAEH,KAAK,MAAM,SAAS,IAAI,CAAC,SAAS,EAAE,OAAO,CAAU,EAAE,CAAC;QACpD,MAAM,OAAO,GAAG,WAAW,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QAC3C,IAAI,OAAO,EAAE,CAAC;YACV,kDAAkD;YAClD,MAAM,cAAc,GAAG,CAAC,CAAgB,EAAE,EAAE;gBACxC,IAAI,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;oBACZ,OAAO,CAAC,CAAC,CAAC,CAAC;gBACf,CAAC;YACL,CAAC,CAAC;YAEF,gBAAgB,CAAC,UAAU,EAAE,SAAS,EAAE,cAAc,EAAE,OAAO,CAAC,CAAC;QACrE,CAAC;IACL,CAAC;AACL,CAAC;AAMD,MAAM,UAAU,WAAW,CAAC,GAAW,EAAE,OAAyB;IAC9D,MAAM,CAAC,SAAS,EAAE,YAAY,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IAElD,cAAc,CACV;QACI,SAAS,EAAE,WAAW,CAClB,CAAC,CAAgB,EAAE,EAAE;YACjB,IAAI,CAAC,CAAC,GAAG,KAAK,GAAG,EAAE,CAAC;gBAChB,IAAI,OAAO,EAAE,cAAc,EAAE,CAAC;oBAC1B,CAAC,CAAC,cAAc,EAAE,CAAC;gBACvB,CAAC;gBACD,YAAY,CAAC,IAAI,CAAC,CAAC;YACvB,CAAC;QACL,CAAC,EACD,CAAC,GAAG,EAAE,OAAO,EAAE,cAAc,CAAC,CACjC;QACD,OAAO,EAAE,WAAW,CAChB,CAAC,CAAgB,EAAE,EAAE;YACjB,IAAI,CAAC,CAAC,GAAG,KAAK,GAAG,EAAE,CAAC;gBAChB,IAAI,OAAO,EAAE,cAAc,EAAE,CAAC;oBAC1B,CAAC,CAAC,cAAc,EAAE,CAAC;gBACvB,CAAC;gBACD,YAAY,CAAC,KAAK,CAAC,CAAC;YACxB,CAAC;QACL,CAAC,EACD,CAAC,GAAG,EAAE,OAAO,EAAE,cAAc,CAAC,CACjC;KACJ,EACD,OAAO,CACV,CAAC;IAEF,gBAAgB,CACZ,QAAQ,EACR,MAAM,EACN,WAAW,CAAC,GAAG,EAAE,CAAC,YAAY,CAAC,KAAK,CAAC,EAAE,EAAE,CAAC,EAC1C,OAAO,CACV,CAAC,CAAC,iDAAiD;IAEpD,OAAO,SAAS,CAAC;AACrB,CAAC","sourcesContent":["import type { WindowOptions } from \"./eventHooks\";\r\n\r\nimport { useCallback, useState } from \"react\";\r\n\r\nimport { useEventListener } from \"./eventHooks\";\r\n\r\ntype KeyCallbacks = {\r\n onKeyDown?: (e: KeyboardEvent) => void;\r\n onKeyUp?: (e: KeyboardEvent) => void;\r\n};\r\n\r\nexport function useKeyListener(callbacks: KeyCallbacks, options?: WindowOptions) {\r\n const callbackMap = new Map<\"keydown\" | \"keyup\", ((e: KeyboardEvent) => void) | undefined>([\r\n [\"keydown\", callbacks.onKeyDown],\r\n [\"keyup\", callbacks.onKeyUp],\r\n ]);\r\n\r\n for (const eventType of [\"keydown\", \"keyup\"] as const) {\r\n const handler = callbackMap.get(eventType);\r\n if (handler) {\r\n // Ignore repeated events from holding down a key.\r\n const guardedHandler = (e: KeyboardEvent) => {\r\n if (!e.repeat) {\r\n handler(e);\r\n }\r\n };\r\n\r\n useEventListener(\"document\", eventType, guardedHandler, options);\r\n }\r\n }\r\n}\r\n\r\ntype KeyStateOptions = WindowOptions & {\r\n preventDefault?: boolean;\r\n};\r\n\r\nexport function useKeyState(key: string, options?: KeyStateOptions): boolean {\r\n const [isPressed, setIsPressed] = useState(false);\r\n\r\n useKeyListener(\r\n {\r\n onKeyDown: useCallback(\r\n (e: KeyboardEvent) => {\r\n if (e.key === key) {\r\n if (options?.preventDefault) {\r\n e.preventDefault();\r\n }\r\n setIsPressed(true);\r\n }\r\n },\r\n [key, options?.preventDefault]\r\n ),\r\n onKeyUp: useCallback(\r\n (e: KeyboardEvent) => {\r\n if (e.key === key) {\r\n if (options?.preventDefault) {\r\n e.preventDefault();\r\n }\r\n setIsPressed(false);\r\n }\r\n },\r\n [key, options?.preventDefault]\r\n ),\r\n },\r\n options\r\n );\r\n\r\n useEventListener(\r\n \"window\",\r\n \"blur\",\r\n useCallback(() => setIsPressed(false), []),\r\n options\r\n ); // Reset state on window blur to avoid stuck keys\r\n\r\n return isPressed;\r\n}\r\n"]}
@@ -49,7 +49,7 @@ const Gradient = (props) => {
49
49
  // Only use compact mode when there are numeric values (spinbuttons) taking up space
50
50
  const hasNumericValues = !(gradient.value1 instanceof Color3 || gradient.value1 instanceof Color4) ||
51
51
  (gradient.value2 !== undefined && !(gradient.value2 instanceof Color3 || gradient.value2 instanceof Color4));
52
- return (_jsxs("div", { id: "gradientContainer", className: classes.container, children: [_jsx("div", { className: gradient.value1 instanceof Color3 || gradient.value1 instanceof Color4 ? classes.colorWrapper : classes.valueWrapper, children: gradient.value1 instanceof Color3 || gradient.value1 instanceof Color4 ? (_jsx(ColorPickerPopup, { value: gradient.value1, onChange: (color) => gradientChange({ ...gradient, value1: color }) })) : (_jsx(SyncedSliderInput, { step: 0.01, value: gradient.value1, onChange: (val) => gradientChange({ ...gradient, value1: val }), compact: true })) }), gradient.value2 !== undefined && (_jsx("div", { className: gradient.value2 instanceof Color3 || gradient.value2 instanceof Color4 ? classes.colorWrapper : classes.valueWrapper, children: gradient.value2 instanceof Color3 || gradient.value2 instanceof Color4 ? (_jsx(ColorPickerPopup, { value: gradient.value2, onChange: (color) => gradientChange({ ...gradient, value2: color }) })) : (_jsx(SyncedSliderInput, { step: 0.01, value: gradient.value2, onChange: (val) => gradientChange({ ...gradient, value2: val }), compact: true })) })), _jsx("div", { className: classes.stepSliderWrapper, children: _jsx(SyncedSliderInput, { notifyOnlyOnRelease: true, min: 0, max: 1, step: 0.01, value: gradient.step, onChange: (val) => gradientChange({ ...gradient, step: val }), compact: hasNumericValues, growSlider: !hasNumericValues }) })] }));
52
+ return (_jsxs("div", { id: "gradientContainer", className: classes.container, children: [_jsx("div", { className: gradient.value1 instanceof Color3 || gradient.value1 instanceof Color4 ? classes.colorWrapper : classes.valueWrapper, children: gradient.value1 instanceof Color3 || gradient.value1 instanceof Color4 ? (_jsx(ColorPickerPopup, { value: gradient.value1, onChange: (color) => gradientChange({ ...gradient, value1: color }) })) : (_jsx(SyncedSliderInput, { step: 0.01, precision: 2, value: gradient.value1, onChange: (val) => gradientChange({ ...gradient, value1: val }), compact: true })) }), gradient.value2 !== undefined && (_jsx("div", { className: gradient.value2 instanceof Color3 || gradient.value2 instanceof Color4 ? classes.colorWrapper : classes.valueWrapper, children: gradient.value2 instanceof Color3 || gradient.value2 instanceof Color4 ? (_jsx(ColorPickerPopup, { value: gradient.value2, onChange: (color) => gradientChange({ ...gradient, value2: color }) })) : (_jsx(SyncedSliderInput, { step: 0.01, precision: 2, value: gradient.value2, onChange: (val) => gradientChange({ ...gradient, value2: val }), compact: true })) })), _jsx("div", { className: classes.stepSliderWrapper, children: _jsx(SyncedSliderInput, { notifyOnlyOnRelease: true, min: 0, max: 1, step: 0.01, precision: 2, value: gradient.step, onChange: (val) => gradientChange({ ...gradient, step: val }), compact: hasNumericValues, growSlider: !hasNumericValues }) })] }));
53
53
  };
54
54
  const FactorGradientCast = Gradient;
55
55
  const Color3GradientCast = Gradient;
@@ -1 +1 @@
1
- {"version":3,"file":"gradient.js","sourceRoot":"","sources":["../../../../../dev/sharedUiComponents/src/fluent/primitives/gradient.tsx"],"names":[],"mappings":"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","sourcesContent":["import type { FunctionComponent } from \"react\";\r\nimport type { PrimitiveProps } from \"./primitive\";\r\nimport { useEffect, useState } from \"react\";\r\nimport { makeStyles, tokens } from \"@fluentui/react-components\";\r\n\r\nimport { SyncedSliderInput } from \"./syncedSlider\";\r\nimport { Color3, Color4 } from \"core/Maths/math.color\";\r\nimport { ColorPickerPopup } from \"./colorPicker\";\r\nimport { Color3Gradient, ColorGradient as Color4Gradient, FactorGradient } from \"core/Misc/gradients\";\r\nimport { GradientBlockColorStep } from \"core/Materials/Node/Blocks/gradientBlock\";\r\n\r\nconst useGradientStyles = makeStyles({\r\n container: {\r\n display: \"flex\",\r\n alignItems: \"center\",\r\n gap: tokens.spacingHorizontalXS, // Reduced gap\r\n width: \"100%\",\r\n minWidth: 0,\r\n overflow: \"hidden\",\r\n },\r\n // Wrapper for factor spin buttons - fixed width, doesn't grow\r\n valueWrapper: {\r\n flex: \"0 0 auto\", // Fixed size, no grow, no shrink\r\n },\r\n // Wrapper for color pickers - fixed size since they're just swatches\r\n colorWrapper: {\r\n flex: \"0 0 auto\",\r\n alignContent: \"center\",\r\n },\r\n // Wrapper for the step slider - takes remaining space and can shrink\r\n stepSliderWrapper: {\r\n flex: \"1 1 0\", // Grow to fill available space\r\n minWidth: \"100px\", // Minimum to fit slider + spinbutton\r\n },\r\n});\r\n\r\ntype GradientProps<T extends number | Color3 | Color4> = {\r\n value1: T;\r\n value2?: T;\r\n step: number;\r\n};\r\n\r\n/**\r\n * Gradient component that displays 1 or 2 color or number inputs next to a slider\r\n * @param props - Component props containing gradient value and change handlers\r\n * @returns A React component\r\n */\r\nconst Gradient: FunctionComponent<PrimitiveProps<GradientProps<number | Color3 | Color4>>> = (props) => {\r\n Gradient.displayName = \"Gradient\";\r\n const [gradient, setGradient] = useState(props.value);\r\n const classes = useGradientStyles();\r\n\r\n useEffect(() => {\r\n setGradient(props.value); // Re-render if props.value changes\r\n }, [props.value]);\r\n\r\n const gradientChange = (newGradient: GradientProps<number | Color3 | Color4>) => {\r\n setGradient(newGradient);\r\n props.onChange(newGradient);\r\n };\r\n // Only use compact mode when there are numeric values (spinbuttons) taking up space\r\n const hasNumericValues =\r\n !(gradient.value1 instanceof Color3 || gradient.value1 instanceof Color4) ||\r\n (gradient.value2 !== undefined && !(gradient.value2 instanceof Color3 || gradient.value2 instanceof Color4));\r\n\r\n return (\r\n <div id=\"gradientContainer\" className={classes.container}>\r\n <div className={gradient.value1 instanceof Color3 || gradient.value1 instanceof Color4 ? classes.colorWrapper : classes.valueWrapper}>\r\n {gradient.value1 instanceof Color3 || gradient.value1 instanceof Color4 ? (\r\n <ColorPickerPopup value={gradient.value1} onChange={(color) => gradientChange({ ...gradient, value1: color })} />\r\n ) : (\r\n <SyncedSliderInput step={0.01} value={gradient.value1} onChange={(val) => gradientChange({ ...gradient, value1: val })} compact />\r\n )}\r\n </div>\r\n {gradient.value2 !== undefined && (\r\n <div className={gradient.value2 instanceof Color3 || gradient.value2 instanceof Color4 ? classes.colorWrapper : classes.valueWrapper}>\r\n {gradient.value2 instanceof Color3 || gradient.value2 instanceof Color4 ? (\r\n <ColorPickerPopup value={gradient.value2} onChange={(color) => gradientChange({ ...gradient, value2: color })} />\r\n ) : (\r\n <SyncedSliderInput step={0.01} value={gradient.value2} onChange={(val) => gradientChange({ ...gradient, value2: val })} compact />\r\n )}\r\n </div>\r\n )}\r\n\r\n <div className={classes.stepSliderWrapper}>\r\n <SyncedSliderInput\r\n notifyOnlyOnRelease={true}\r\n min={0}\r\n max={1}\r\n step={0.01}\r\n value={gradient.step}\r\n onChange={(val) => gradientChange({ ...gradient, step: val })}\r\n compact={hasNumericValues}\r\n growSlider={!hasNumericValues}\r\n />\r\n </div>\r\n </div>\r\n );\r\n};\r\n\r\nconst FactorGradientCast = Gradient as FunctionComponent<PrimitiveProps<GradientProps<number>>>;\r\nconst Color3GradientCast = Gradient as FunctionComponent<PrimitiveProps<GradientProps<Color3>>>;\r\nconst Color4GradientCast = Gradient as FunctionComponent<PrimitiveProps<GradientProps<Color4>>>;\r\n\r\n/**\r\n * Component wrapper for FactorGradient that provides slider inputs for factor1, factor2, and gradient step\r\n * @param props - Component props containing FactorGradient value and change handler\r\n * @returns A React component\r\n */\r\nexport const FactorGradientComponent: FunctionComponent<PrimitiveProps<FactorGradient>> = (props) => {\r\n return (\r\n <FactorGradientCast\r\n {...props}\r\n value={{ value1: props.value.factor1, value2: props.value.factor2, step: props.value.gradient }}\r\n onChange={(gradient) => props.onChange(new FactorGradient(gradient.step, gradient.value1, gradient.value2))}\r\n />\r\n );\r\n};\r\n/**\r\n * Component wrapper for Color3Gradient that provides color picker and gradient step slider\r\n * @param props - Component props containing Color3Gradient value and change handler\r\n * @returns A React component\r\n */\r\nexport const Color3GradientComponent: FunctionComponent<PrimitiveProps<Color3Gradient>> = (props) => {\r\n return (\r\n <Color3GradientCast\r\n {...props}\r\n value={{ value1: props.value.color, step: props.value.gradient }}\r\n onChange={(gradient) => props.onChange(new Color3Gradient(gradient.step, gradient.value1))}\r\n />\r\n );\r\n};\r\n/**\r\n * Component wrapper for Color4Gradient that provides color pickers for color1, color2, and gradient step slider\r\n * @param props - Component props containing Color4Gradient value and change handler\r\n * @returns A React component\r\n */\r\nexport const Color4GradientComponent: FunctionComponent<PrimitiveProps<Color4Gradient>> = (props) => {\r\n return (\r\n <Color4GradientCast\r\n {...props}\r\n value={{ value1: props.value.color1, value2: props.value.color2, step: props.value.gradient }}\r\n onChange={(gradient) => props.onChange(new Color4Gradient(gradient.step, gradient.value1, gradient.value2))}\r\n />\r\n );\r\n};\r\n/**\r\n * Component wrapper for GradientBlockColorStep that provides color picker and step slider\r\n * @param props - Component props containing GradientBlockColorStep value and change handler\r\n * @returns A React component\r\n */\r\nexport const ColorStepGradientComponent: FunctionComponent<PrimitiveProps<GradientBlockColorStep>> = (props) => {\r\n return (\r\n <Color3GradientCast\r\n {...props}\r\n value={{ value1: props.value.color, step: props.value.step }}\r\n onChange={(gradient) => props.onChange(new GradientBlockColorStep(gradient.step, gradient.value1))}\r\n />\r\n );\r\n};\r\n"]}
1
+ {"version":3,"file":"gradient.js","sourceRoot":"","sources":["../../../../../dev/sharedUiComponents/src/fluent/primitives/gradient.tsx"],"names":[],"mappings":"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","sourcesContent":["import type { FunctionComponent } from \"react\";\r\nimport type { PrimitiveProps } from \"./primitive\";\r\nimport { useEffect, useState } from \"react\";\r\nimport { makeStyles, tokens } from \"@fluentui/react-components\";\r\n\r\nimport { SyncedSliderInput } from \"./syncedSlider\";\r\nimport { Color3, Color4 } from \"core/Maths/math.color\";\r\nimport { ColorPickerPopup } from \"./colorPicker\";\r\nimport { Color3Gradient, ColorGradient as Color4Gradient, FactorGradient } from \"core/Misc/gradients\";\r\nimport { GradientBlockColorStep } from \"core/Materials/Node/Blocks/gradientBlock\";\r\n\r\nconst useGradientStyles = makeStyles({\r\n container: {\r\n display: \"flex\",\r\n alignItems: \"center\",\r\n gap: tokens.spacingHorizontalXS, // Reduced gap\r\n width: \"100%\",\r\n minWidth: 0,\r\n overflow: \"hidden\",\r\n },\r\n // Wrapper for factor spin buttons - fixed width, doesn't grow\r\n valueWrapper: {\r\n flex: \"0 0 auto\", // Fixed size, no grow, no shrink\r\n },\r\n // Wrapper for color pickers - fixed size since they're just swatches\r\n colorWrapper: {\r\n flex: \"0 0 auto\",\r\n alignContent: \"center\",\r\n },\r\n // Wrapper for the step slider - takes remaining space and can shrink\r\n stepSliderWrapper: {\r\n flex: \"1 1 0\", // Grow to fill available space\r\n minWidth: \"100px\", // Minimum to fit slider + spinbutton\r\n },\r\n});\r\n\r\ntype GradientProps<T extends number | Color3 | Color4> = {\r\n value1: T;\r\n value2?: T;\r\n step: number;\r\n};\r\n\r\n/**\r\n * Gradient component that displays 1 or 2 color or number inputs next to a slider\r\n * @param props - Component props containing gradient value and change handlers\r\n * @returns A React component\r\n */\r\nconst Gradient: FunctionComponent<PrimitiveProps<GradientProps<number | Color3 | Color4>>> = (props) => {\r\n Gradient.displayName = \"Gradient\";\r\n const [gradient, setGradient] = useState(props.value);\r\n const classes = useGradientStyles();\r\n\r\n useEffect(() => {\r\n setGradient(props.value); // Re-render if props.value changes\r\n }, [props.value]);\r\n\r\n const gradientChange = (newGradient: GradientProps<number | Color3 | Color4>) => {\r\n setGradient(newGradient);\r\n props.onChange(newGradient);\r\n };\r\n // Only use compact mode when there are numeric values (spinbuttons) taking up space\r\n const hasNumericValues =\r\n !(gradient.value1 instanceof Color3 || gradient.value1 instanceof Color4) ||\r\n (gradient.value2 !== undefined && !(gradient.value2 instanceof Color3 || gradient.value2 instanceof Color4));\r\n\r\n return (\r\n <div id=\"gradientContainer\" className={classes.container}>\r\n <div className={gradient.value1 instanceof Color3 || gradient.value1 instanceof Color4 ? classes.colorWrapper : classes.valueWrapper}>\r\n {gradient.value1 instanceof Color3 || gradient.value1 instanceof Color4 ? (\r\n <ColorPickerPopup value={gradient.value1} onChange={(color) => gradientChange({ ...gradient, value1: color })} />\r\n ) : (\r\n <SyncedSliderInput step={0.01} precision={2} value={gradient.value1} onChange={(val) => gradientChange({ ...gradient, value1: val })} compact />\r\n )}\r\n </div>\r\n {gradient.value2 !== undefined && (\r\n <div className={gradient.value2 instanceof Color3 || gradient.value2 instanceof Color4 ? classes.colorWrapper : classes.valueWrapper}>\r\n {gradient.value2 instanceof Color3 || gradient.value2 instanceof Color4 ? (\r\n <ColorPickerPopup value={gradient.value2} onChange={(color) => gradientChange({ ...gradient, value2: color })} />\r\n ) : (\r\n <SyncedSliderInput step={0.01} precision={2} value={gradient.value2} onChange={(val) => gradientChange({ ...gradient, value2: val })} compact />\r\n )}\r\n </div>\r\n )}\r\n\r\n <div className={classes.stepSliderWrapper}>\r\n <SyncedSliderInput\r\n notifyOnlyOnRelease={true}\r\n min={0}\r\n max={1}\r\n step={0.01}\r\n precision={2}\r\n value={gradient.step}\r\n onChange={(val) => gradientChange({ ...gradient, step: val })}\r\n compact={hasNumericValues}\r\n growSlider={!hasNumericValues}\r\n />\r\n </div>\r\n </div>\r\n );\r\n};\r\n\r\nconst FactorGradientCast = Gradient as FunctionComponent<PrimitiveProps<GradientProps<number>>>;\r\nconst Color3GradientCast = Gradient as FunctionComponent<PrimitiveProps<GradientProps<Color3>>>;\r\nconst Color4GradientCast = Gradient as FunctionComponent<PrimitiveProps<GradientProps<Color4>>>;\r\n\r\n/**\r\n * Component wrapper for FactorGradient that provides slider inputs for factor1, factor2, and gradient step\r\n * @param props - Component props containing FactorGradient value and change handler\r\n * @returns A React component\r\n */\r\nexport const FactorGradientComponent: FunctionComponent<PrimitiveProps<FactorGradient>> = (props) => {\r\n return (\r\n <FactorGradientCast\r\n {...props}\r\n value={{ value1: props.value.factor1, value2: props.value.factor2, step: props.value.gradient }}\r\n onChange={(gradient) => props.onChange(new FactorGradient(gradient.step, gradient.value1, gradient.value2))}\r\n />\r\n );\r\n};\r\n/**\r\n * Component wrapper for Color3Gradient that provides color picker and gradient step slider\r\n * @param props - Component props containing Color3Gradient value and change handler\r\n * @returns A React component\r\n */\r\nexport const Color3GradientComponent: FunctionComponent<PrimitiveProps<Color3Gradient>> = (props) => {\r\n return (\r\n <Color3GradientCast\r\n {...props}\r\n value={{ value1: props.value.color, step: props.value.gradient }}\r\n onChange={(gradient) => props.onChange(new Color3Gradient(gradient.step, gradient.value1))}\r\n />\r\n );\r\n};\r\n/**\r\n * Component wrapper for Color4Gradient that provides color pickers for color1, color2, and gradient step slider\r\n * @param props - Component props containing Color4Gradient value and change handler\r\n * @returns A React component\r\n */\r\nexport const Color4GradientComponent: FunctionComponent<PrimitiveProps<Color4Gradient>> = (props) => {\r\n return (\r\n <Color4GradientCast\r\n {...props}\r\n value={{ value1: props.value.color1, value2: props.value.color2, step: props.value.gradient }}\r\n onChange={(gradient) => props.onChange(new Color4Gradient(gradient.step, gradient.value1, gradient.value2))}\r\n />\r\n );\r\n};\r\n/**\r\n * Component wrapper for GradientBlockColorStep that provides color picker and step slider\r\n * @param props - Component props containing GradientBlockColorStep value and change handler\r\n * @returns A React component\r\n */\r\nexport const ColorStepGradientComponent: FunctionComponent<PrimitiveProps<GradientBlockColorStep>> = (props) => {\r\n return (\r\n <Color3GradientCast\r\n {...props}\r\n value={{ value1: props.value.color, step: props.value.step }}\r\n onChange={(gradient) => props.onChange(new GradientBlockColorStep(gradient.step, gradient.value1))}\r\n />\r\n );\r\n};\r\n"]}
@@ -0,0 +1,23 @@
1
+ import type { FunctionComponent } from "react";
2
+ import type { PrimitiveProps } from "./primitive.js";
3
+ export type SliderProps = PrimitiveProps<number> & {
4
+ /** Minimum value for the slider */
5
+ min?: number;
6
+ /** Maximum value for the slider */
7
+ max?: number;
8
+ /** Step size for the slider */
9
+ step?: number;
10
+ /** When true, onChange is only called when the user releases the slider, not during drag */
11
+ notifyOnlyOnRelease?: boolean;
12
+ /** Optional pointer down handler */
13
+ onPointerDown?: () => void;
14
+ /** Optional pointer up handler */
15
+ onPointerUp?: () => void;
16
+ };
17
+ /**
18
+ * A slider primitive that wraps the Fluent UI Slider with step scaling, drag tracking, and optional notify-on-release behavior.
19
+ * Follows the same pattern as other primitives (e.g. Switch) — no wrapper divs, just the Fluent component with logic.
20
+ * @param props
21
+ * @returns Slider component
22
+ */
23
+ export declare const Slider: FunctionComponent<SliderProps>;
@@ -0,0 +1,56 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Slider as FluentSlider } from "@fluentui/react-components";
3
+ import { useEffect, useState, useRef, useContext } from "react";
4
+ import { ToolContext } from "../hoc/fluentToolWrapper.js";
5
+ /**
6
+ * A slider primitive that wraps the Fluent UI Slider with step scaling, drag tracking, and optional notify-on-release behavior.
7
+ * Follows the same pattern as other primitives (e.g. Switch) — no wrapper divs, just the Fluent component with logic.
8
+ * @param props
9
+ * @returns Slider component
10
+ */
11
+ export const Slider = (props) => {
12
+ Slider.displayName = "Slider";
13
+ const { size } = useContext(ToolContext);
14
+ const [value, setValue] = useState(props.value ?? 0);
15
+ const pendingValueRef = useRef(undefined);
16
+ const isDraggingRef = useRef(false);
17
+ // NOTE: The Fluent slider will add tick marks if the step prop is anything other than undefined.
18
+ // To avoid this, we scale the min/max based on the step so we can always make step undefined.
19
+ // The actual step size in the Fluent slider is 1 when it is set to undefined.
20
+ const min = props.min ?? 0;
21
+ const max = props.max ?? 100;
22
+ const step = props.step ?? 1;
23
+ useEffect(() => {
24
+ !isDraggingRef.current && setValue(props.value ?? 0); // Update local state when props.value changes as long as user is not actively dragging
25
+ }, [props.value]);
26
+ const handleSliderChange = (_, data) => {
27
+ const newValue = data.value * step;
28
+ setValue(newValue);
29
+ if (props.notifyOnlyOnRelease) {
30
+ // Store the value but don't notify parent yet
31
+ pendingValueRef.current = newValue;
32
+ }
33
+ else {
34
+ // Notify parent as slider changes
35
+ props.onChange(newValue);
36
+ }
37
+ };
38
+ const handleSliderPointerDown = () => {
39
+ isDraggingRef.current = true;
40
+ };
41
+ const handleSliderPointerUp = () => {
42
+ if (props.notifyOnlyOnRelease && isDraggingRef.current && pendingValueRef.current !== undefined) {
43
+ props.onChange(pendingValueRef.current);
44
+ pendingValueRef.current = undefined;
45
+ }
46
+ isDraggingRef.current = false;
47
+ };
48
+ return (_jsx(FluentSlider, { className: props.className, size: size, min: min / step, max: max / step, step: undefined, value: value / step, disabled: props.disabled, onChange: handleSliderChange, onPointerDown: () => {
49
+ handleSliderPointerDown();
50
+ props.onPointerDown?.();
51
+ }, onPointerUp: () => {
52
+ handleSliderPointerUp();
53
+ props.onPointerUp?.();
54
+ } }));
55
+ };
56
+ //# sourceMappingURL=slider.js.map
@@ -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 { SpinButton as FluentSpinButton, mergeClasses, useId } from "@fluentui/react-components";
3
- import { forwardRef, useEffect, useState, useRef, useContext } from "react";
4
- import { InfoLabel } from "./infoLabel.js";
5
- import { CalculatePrecision, HandleKeyDown, HandleOnBlur, useInputStyles } from "./utils.js";
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
- function CoerceStepValue(step, isAltKeyPressed, isShiftKeyPressed) {
9
- // When the alt key is pressed, decrease step by a factor of 10.
10
- if (isAltKeyPressed) {
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 shift key is pressed, increase step by a factor of 10.
14
- if (isShiftKeyPressed) {
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 = "SpinButton";
21
- const classes = useInputStyles();
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 [value, setValue] = useState(props.value);
25
- const lastCommittedValue = useRef(props.value);
26
- // When the input does not have keyboard focus
27
- const isUnfocusedAltKeyPressed = useKeyState("Alt");
28
- const isUnfocusedShiftKeyPressed = useKeyState("Shift");
29
- // When the input does have keyboard focus
30
- const [isFocusedAltKeyPressed, setIsFocusedAltKeyPressed] = useState(false);
31
- const [isFocusedShiftKeyPressed, setIsFocusedShiftKeyPressed] = useState(false);
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
- const displayPrecision = Math.min(4, Math.max(stepPrecision, valuePrecision));
38
- // Set to large const to prevent Fluent from rounding user-entered values on commit
39
- // We control display formatting ourselves via displayValue, so this only affects internal rounding. The value stored internally will still have max precision
40
- const fluentPrecision = 20;
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); // Update local state when props.value changes
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
- const tryCommitValue = (currVal) => {
55
- // Only commit if valid and different from last committed value
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 handleChange = (event, data) => {
62
- event.stopPropagation(); // Prevent event propagation
63
- if (data.value != null && !Number.isNaN(data.value)) {
64
- setValue(data.value);
65
- tryCommitValue(data.value);
66
- }
67
- };
68
- // Strip the unit suffix (e.g. "deg" or " deg") from the raw input value before evaluating expressions.
69
- const stripUnit = (val) => {
70
- if (!props.unit) {
71
- return val;
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
- const regex = new RegExp("\\s*" + props.unit + "$");
74
- const match = val.match(regex);
75
- if (match) {
76
- return val.slice(0, -match[0].length);
77
- }
78
- return val;
79
- };
80
- // Allow arbitrary expressions, primarily for math operations (e.g. 10*60 for 10 minutes in seconds).
81
- // Use Function constructor to safely evaluate the expression without allowing access to scope.
82
- // If the expression is invalid, fallback to NaN which will be caught by validateValue and prevent committing.
83
- const evaluateExpression = (rawValue) => {
84
- const val = stripUnit(rawValue).trim();
85
- try {
86
- return Number(Function(`"use strict";return (${val})`)());
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
- const handleKeyDown = (event) => {
93
- if (event.key === "Alt") {
94
- setIsFocusedAltKeyPressed(true);
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
- else if (event.key === "Shift") {
97
- setIsFocusedShiftKeyPressed(true);
144
+ }, [step]);
145
+ const handleIconPointerMove = useCallback((e) => {
146
+ if (!isDragging) {
147
+ return;
98
148
  }
99
- // Evaluate on Enter in keyDown (before Fluent's internal commit clears the raw text
100
- // and re-renders with the truncated displayValue).
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 currVal = evaluateExpression(event.target.value);
103
- if (!isNaN(currVal)) {
104
- setValue(currVal);
105
- tryCommitValue(currVal);
169
+ const committed = commitEditText(event.currentTarget.value);
170
+ if (committed !== undefined) {
171
+ inputRef.current?.blur();
106
172
  }
107
173
  }
108
- HandleKeyDown(event);
109
- };
110
- const handleKeyUp = (event) => {
111
- event.stopPropagation(); // Prevent event propagation
112
- if (event.key === "Alt") {
113
- setIsFocusedAltKeyPressed(false);
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
- else if (event.key === "Shift") {
116
- setIsFocusedShiftKeyPressed(false);
117
- }
118
- // Skip Enter it's handled in keyDown before Fluent's internal commit
119
- // clears the raw text and replaces it with the truncated displayValue.
120
- if (event.key === "Enter") {
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
- const currVal = evaluateExpression(event.target.value);
124
- if (!isNaN(currVal)) {
125
- setValue(currVal);
126
- tryCommitValue(currVal);
127
- }
128
- };
129
- const id = useId("spin-button");
130
- const mergedClassName = mergeClasses(classes.input, !validateValue(value) ? classes.invalid : "", props.className);
131
- // Build input slot from inputClassName
132
- const inputSlot = {
133
- className: mergeClasses(classes.inputSlot, props.inputClassName),
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, Slider } from "@fluentui/react-components";
2
+ import { makeStyles } from "@fluentui/react-components";
3
3
  import { SpinButton } from "./spinButton.js";
4
- import { useEffect, useState, useRef, useContext } from "react";
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 = (_, data) => {
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, { ...passthroughProps, className: getSliderClassName(), size: size, min: min / step, max: max / step, step: undefined, value: value / step, onChange: handleSliderChange, 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 })] })] }));
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":"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","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"]}
@@ -32,12 +32,10 @@
32
32
  grid-column: 1;
33
33
  position: relative;
34
34
  border: 4px solid black;
35
- border-top-right-radius: 7px;
36
- border-top-left-radius: 7px;
35
+ border-top-right-radius: 8px;
36
+ border-top-left-radius: 8px;
37
37
  background: black;
38
38
  color: white;
39
- transform: scaleX(1.01) translateY(-0.5px);
40
- transform-origin: center;
41
39
  display: grid;
42
40
  grid-template-columns: auto 1fr auto;
43
41
  grid-template-rows: 100%;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onerjs/shared-ui-components",
3
- "version": "8.42.2",
3
+ "version": "8.42.3",
4
4
  "main": "index.js",
5
5
  "module": "index.js",
6
6
  "types": "index.d.ts",