@onerjs/shared-ui-components 8.41.6 → 8.41.7

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.
@@ -31,7 +31,13 @@ export const SpinButton = forwardRef((props, ref) => {
31
31
  const [isFocusedShiftKeyPressed, setIsFocusedShiftKeyPressed] = useState(false);
32
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
33
  const step = CoerceStepValue(props.step ?? 1, isUnfocusedAltKeyPressed || isFocusedAltKeyPressed, isUnfocusedShiftKeyPressed || isFocusedShiftKeyPressed);
34
- const precision = Math.min(4, Math.max(0, CalculatePrecision(step))); // Cap precision at 4 to avoid wild numbers
34
+ const stepPrecision = Math.max(0, CalculatePrecision(step));
35
+ 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;
35
41
  useEffect(() => {
36
42
  if (props.value !== lastCommittedValue.current) {
37
43
  lastCommittedValue.current = props.value;
@@ -59,6 +65,30 @@ export const SpinButton = forwardRef((props, ref) => {
59
65
  tryCommitValue(data.value);
60
66
  }
61
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;
72
+ }
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;
90
+ }
91
+ };
62
92
  const handleKeyDown = (event) => {
63
93
  if (event.key === "Alt") {
64
94
  setIsFocusedAltKeyPressed(true);
@@ -66,28 +96,32 @@ export const SpinButton = forwardRef((props, ref) => {
66
96
  else if (event.key === "Shift") {
67
97
  setIsFocusedShiftKeyPressed(true);
68
98
  }
99
+ // Evaluate on Enter in keyDown (before Fluent's internal commit clears the raw text
100
+ // and re-renders with the truncated displayValue).
101
+ if (event.key === "Enter") {
102
+ const currVal = evaluateExpression(event.target.value);
103
+ if (!isNaN(currVal)) {
104
+ setValue(currVal);
105
+ tryCommitValue(currVal);
106
+ }
107
+ }
69
108
  HandleKeyDown(event);
70
109
  };
71
110
  const handleKeyUp = (event) => {
72
111
  event.stopPropagation(); // Prevent event propagation
73
- if (event.key !== "Enter") {
74
- if (event.key === "Alt") {
75
- setIsFocusedAltKeyPressed(false);
76
- }
77
- else if (event.key === "Shift") {
78
- setIsFocusedShiftKeyPressed(false);
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 currVal = ((val) => {
84
- try {
85
- return Number(Function(`"use strict";return (${val})`)());
86
- }
87
- catch {
88
- return NaN;
89
- }
90
- })(event.target.value);
112
+ if (event.key === "Alt") {
113
+ setIsFocusedAltKeyPressed(false);
114
+ }
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") {
121
+ return;
122
+ }
123
+ const currVal = evaluateExpression(event.target.value);
124
+ if (!isNaN(currVal)) {
91
125
  setValue(currVal);
92
126
  tryCommitValue(currVal);
93
127
  }
@@ -98,7 +132,7 @@ export const SpinButton = forwardRef((props, ref) => {
98
132
  const inputSlot = {
99
133
  className: mergeClasses(classes.inputSlot, props.inputClassName),
100
134
  };
101
- const spinButton = (_jsx(FluentSpinButton, { ref: ref, ...props, appearance: "outline", input: inputSlot, step: step, id: id, size: size, precision: precision, displayValue: `${value.toFixed(precision)}${props.unit ? " " + props.unit : ""}`, value: value, onChange: handleChange, onKeyDown: handleKeyDown, onKeyUp: handleKeyUp, onBlur: HandleOnBlur, className: mergedClassName }));
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 }));
102
136
  return props.infoLabel ? (_jsxs("div", { className: classes.container, children: [_jsx(InfoLabel, { ...props.infoLabel, htmlFor: id }), spinButton] })) : (spinButton);
103
137
  });
104
138
  //# 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 precision = Math.min(4, Math.max(0, CalculatePrecision(step))); // Cap precision at 4 to avoid wild numbers\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 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 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 !== \"Enter\") {\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 // 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 currVal = ((val: string): number => {\r\n try {\r\n return Number(Function(`\"use strict\";return (${val})`)());\r\n } catch {\r\n return NaN;\r\n }\r\n })((event.target as any).value);\r\n\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={precision}\r\n displayValue={`${value.toFixed(precision)}${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 { 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"]}
@@ -9,7 +9,7 @@ export const ToastProvider = ({ children }) => {
9
9
  const showToast = useCallback((message) => {
10
10
  dispatchToast(_jsx(Toast, { children: _jsx(ToastTitle, { children: message }) }), { intent: "info", timeout: 2000 });
11
11
  }, [dispatchToast]);
12
- return (_jsxs(ToastContext.Provider, { value: { showToast }, children: [children, _jsx(FluentProvider, { applyStylesToPortals: true, targetDocument: targetDocument, children: _jsx(Toaster, { toasterId: toasterId, position: "bottom-end" }) })] }));
12
+ return (_jsxs(ToastContext.Provider, { value: { showToast }, children: [children, _jsx(FluentProvider, { applyStylesToPortals: true, targetDocument: targetDocument, children: _jsx(Toaster, { toasterId: toasterId, position: "bottom" }) })] }));
13
13
  };
14
14
  /**
15
15
  * Hook to show toast notifications.
@@ -1 +1 @@
1
- {"version":3,"file":"toast.js","sourceRoot":"","sources":["../../../../../dev/sharedUiComponents/src/fluent/primitives/toast.tsx"],"names":[],"mappings":";AAEA,OAAO,EAAE,cAAc,EAAE,KAAK,EAAE,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,KAAK,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AAC9H,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC;AAM/D,MAAM,YAAY,GAAG,aAAa,CAAmB,EAAE,SAAS,EAAE,GAAG,EAAE,GAAE,CAAC,EAAE,CAAC,CAAC;AAE9E,MAAM,CAAC,MAAM,aAAa,GAAyC,CAAC,EAAE,QAAQ,EAAE,EAAE,EAAE;IAChF,MAAM,SAAS,GAAG,KAAK,CAAC,SAAS,CAAC,CAAC;IACnC,MAAM,EAAE,aAAa,EAAE,GAAG,kBAAkB,CAAC,SAAS,CAAC,CAAC;IACxD,MAAM,EAAE,cAAc,EAAE,GAAG,SAAS,EAAE,CAAC;IAEvC,MAAM,SAAS,GAAG,WAAW,CACzB,CAAC,OAAe,EAAE,EAAE;QAChB,aAAa,CACT,KAAC,KAAK,cACF,KAAC,UAAU,cAAE,OAAO,GAAc,GAC9B,EACR,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,CACpC,CAAC;IACN,CAAC,EACD,CAAC,aAAa,CAAC,CAClB,CAAC;IAEF,OAAO,CACH,MAAC,YAAY,CAAC,QAAQ,IAAC,KAAK,EAAE,EAAE,SAAS,EAAE,aACtC,QAAQ,EACT,KAAC,cAAc,IAAC,oBAAoB,QAAC,cAAc,EAAE,cAAc,YAC/D,KAAC,OAAO,IAAC,SAAS,EAAE,SAAS,EAAE,QAAQ,EAAC,YAAY,GAAG,GAC1C,IACG,CAC3B,CAAC;AACN,CAAC,CAAC;AAEF;;;GAGG;AACH,MAAM,UAAU,QAAQ;IACpB,OAAO,UAAU,CAAC,YAAY,CAAC,CAAC;AACpC,CAAC","sourcesContent":["import type { FunctionComponent, PropsWithChildren } from \"react\";\r\n\r\nimport { FluentProvider, Toast, Toaster, ToastTitle, useFluent, useId, useToastController } from \"@fluentui/react-components\";\r\nimport { createContext, useCallback, useContext } from \"react\";\r\n\r\ntype ToastContextType = {\r\n showToast: (message: string) => void;\r\n};\r\n\r\nconst ToastContext = createContext<ToastContextType>({ showToast: () => {} });\r\n\r\nexport const ToastProvider: FunctionComponent<PropsWithChildren> = ({ children }) => {\r\n const toasterId = useId(\"toaster\");\r\n const { dispatchToast } = useToastController(toasterId);\r\n const { targetDocument } = useFluent();\r\n\r\n const showToast = useCallback(\r\n (message: string) => {\r\n dispatchToast(\r\n <Toast>\r\n <ToastTitle>{message}</ToastTitle>\r\n </Toast>,\r\n { intent: \"info\", timeout: 2000 }\r\n );\r\n },\r\n [dispatchToast]\r\n );\r\n\r\n return (\r\n <ToastContext.Provider value={{ showToast }}>\r\n {children}\r\n <FluentProvider applyStylesToPortals targetDocument={targetDocument}>\r\n <Toaster toasterId={toasterId} position=\"bottom-end\" />\r\n </FluentProvider>\r\n </ToastContext.Provider>\r\n );\r\n};\r\n\r\n/**\r\n * Hook to show toast notifications.\r\n * @returns Object with showToast function that accepts a message string\r\n */\r\nexport function useToast() {\r\n return useContext(ToastContext);\r\n}\r\n"]}
1
+ {"version":3,"file":"toast.js","sourceRoot":"","sources":["../../../../../dev/sharedUiComponents/src/fluent/primitives/toast.tsx"],"names":[],"mappings":";AAEA,OAAO,EAAE,cAAc,EAAE,KAAK,EAAE,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,KAAK,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AAC9H,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC;AAM/D,MAAM,YAAY,GAAG,aAAa,CAAmB,EAAE,SAAS,EAAE,GAAG,EAAE,GAAE,CAAC,EAAE,CAAC,CAAC;AAE9E,MAAM,CAAC,MAAM,aAAa,GAAyC,CAAC,EAAE,QAAQ,EAAE,EAAE,EAAE;IAChF,MAAM,SAAS,GAAG,KAAK,CAAC,SAAS,CAAC,CAAC;IACnC,MAAM,EAAE,aAAa,EAAE,GAAG,kBAAkB,CAAC,SAAS,CAAC,CAAC;IACxD,MAAM,EAAE,cAAc,EAAE,GAAG,SAAS,EAAE,CAAC;IAEvC,MAAM,SAAS,GAAG,WAAW,CACzB,CAAC,OAAe,EAAE,EAAE;QAChB,aAAa,CACT,KAAC,KAAK,cACF,KAAC,UAAU,cAAE,OAAO,GAAc,GAC9B,EACR,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,CACpC,CAAC;IACN,CAAC,EACD,CAAC,aAAa,CAAC,CAClB,CAAC;IAEF,OAAO,CACH,MAAC,YAAY,CAAC,QAAQ,IAAC,KAAK,EAAE,EAAE,SAAS,EAAE,aACtC,QAAQ,EACT,KAAC,cAAc,IAAC,oBAAoB,QAAC,cAAc,EAAE,cAAc,YAC/D,KAAC,OAAO,IAAC,SAAS,EAAE,SAAS,EAAE,QAAQ,EAAC,QAAQ,GAAG,GACtC,IACG,CAC3B,CAAC;AACN,CAAC,CAAC;AAEF;;;GAGG;AACH,MAAM,UAAU,QAAQ;IACpB,OAAO,UAAU,CAAC,YAAY,CAAC,CAAC;AACpC,CAAC","sourcesContent":["import type { FunctionComponent, PropsWithChildren } from \"react\";\r\n\r\nimport { FluentProvider, Toast, Toaster, ToastTitle, useFluent, useId, useToastController } from \"@fluentui/react-components\";\r\nimport { createContext, useCallback, useContext } from \"react\";\r\n\r\ntype ToastContextType = {\r\n showToast: (message: string) => void;\r\n};\r\n\r\nconst ToastContext = createContext<ToastContextType>({ showToast: () => {} });\r\n\r\nexport const ToastProvider: FunctionComponent<PropsWithChildren> = ({ children }) => {\r\n const toasterId = useId(\"toaster\");\r\n const { dispatchToast } = useToastController(toasterId);\r\n const { targetDocument } = useFluent();\r\n\r\n const showToast = useCallback(\r\n (message: string) => {\r\n dispatchToast(\r\n <Toast>\r\n <ToastTitle>{message}</ToastTitle>\r\n </Toast>,\r\n { intent: \"info\", timeout: 2000 }\r\n );\r\n },\r\n [dispatchToast]\r\n );\r\n\r\n return (\r\n <ToastContext.Provider value={{ showToast }}>\r\n {children}\r\n <FluentProvider applyStylesToPortals targetDocument={targetDocument}>\r\n <Toaster toasterId={toasterId} position=\"bottom\" />\r\n </FluentProvider>\r\n </ToastContext.Provider>\r\n );\r\n};\r\n\r\n/**\r\n * Hook to show toast notifications.\r\n * @returns Object with showToast function that accepts a message string\r\n */\r\nexport function useToast() {\r\n return useContext(ToastContext);\r\n}\r\n"]}
@@ -24,12 +24,12 @@ export const ToggleButton = (props) => {
24
24
  const classes = useStyles();
25
25
  const [checked, setChecked] = useState(value);
26
26
  const toggle = useCallback(() => {
27
- setChecked((prev) => {
28
- const enabled = !prev;
27
+ setChecked((prevChecked) => {
28
+ const enabled = !prevChecked;
29
29
  onChange(enabled);
30
30
  return enabled;
31
31
  });
32
- }, [setChecked]);
32
+ }, [onChange]);
33
33
  useEffect(() => {
34
34
  setChecked(props.value);
35
35
  }, [props.value]);
@@ -1 +1 @@
1
- {"version":3,"file":"toggleButton.js","sourceRoot":"","sources":["../../../../../dev/sharedUiComponents/src/fluent/primitives/toggleButton.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,YAAY,IAAI,kBAAkB,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AAE5F,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAGrE,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AACvD,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,MAAM,SAAS,GAAG,UAAU,CAAC;IACzB,MAAM,EAAE;QACJ,OAAO,EAAE,MAAM;QACf,UAAU,EAAE,QAAQ;QACpB,cAAc,EAAE,QAAQ;KAC3B;CACJ,CAAC,CAAC;AASH;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,YAAY,GAAyC,CAAC,KAAK,EAAE,EAAE;IACxE,YAAY,CAAC,WAAW,GAAG,cAAc,CAAC;IAC1C,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,UAAU,GAAG,QAAQ,EAAE,GAAG,KAAK,CAAC;IAChE,MAAM,EAAE,IAAI,EAAE,GAAG,UAAU,CAAC,WAAW,CAAC,CAAC;IACzC,MAAM,OAAO,GAAG,SAAS,EAAE,CAAC;IAC5B,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IAC9C,MAAM,MAAM,GAAG,WAAW,CAAC,GAAG,EAAE;QAC5B,UAAU,CAAC,CAAC,IAAI,EAAE,EAAE;YAChB,MAAM,OAAO,GAAG,CAAC,IAAI,CAAC;YACtB,QAAQ,CAAC,OAAO,CAAC,CAAC;YAClB,OAAO,OAAO,CAAC;QACnB,CAAC,CAAC,CAAC;IACP,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC;IAEjB,SAAS,CAAC,GAAG,EAAE;QACX,UAAU,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IAC5B,CAAC,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC;IAElB,OAAO,CACH,KAAC,OAAO,IAAC,OAAO,EAAE,KAAK,IAAI,EAAE,YACzB,KAAC,kBAAkB,IACf,SAAS,EAAE,OAAO,CAAC,MAAM,EACzB,IAAI,EAAE,IAAI,EACV,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,KAAC,KAAK,CAAC,WAAW,KAAG,CAAC,CAAC,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC,CAAC,KAAC,KAAK,CAAC,aAAa,KAAG,CAAC,CAAC,CAAC,KAAC,KAAK,CAAC,WAAW,KAAG,EAC7G,UAAU,EAAE,UAAU,EACtB,OAAO,EAAE,OAAO,EAChB,OAAO,EAAE,MAAM,GACjB,GACI,CACb,CAAC;AACN,CAAC,CAAC","sourcesContent":["import { ToggleButton as FluentToggleButton, makeStyles } from \"@fluentui/react-components\";\r\nimport type { ButtonProps } from \"./button\";\r\nimport { useCallback, useContext, useEffect, useState } from \"react\";\r\nimport type { FunctionComponent } from \"react\";\r\nimport type { FluentIcon } from \"@fluentui/react-icons\";\r\nimport { ToolContext } from \"../hoc/fluentToolWrapper\";\r\nimport { Tooltip } from \"./tooltip\";\r\n\r\nconst useStyles = makeStyles({\r\n button: {\r\n display: \"flex\",\r\n alignItems: \"center\",\r\n justifyContent: \"center\",\r\n },\r\n});\r\n\r\ntype ToggleButtonProps = Omit<ButtonProps, \"icon\" | \"onClick\"> & {\r\n value: boolean;\r\n checkedIcon: FluentIcon;\r\n uncheckedIcon?: FluentIcon;\r\n onChange: (checked: boolean) => void;\r\n};\r\n\r\n/**\r\n * Toggles between two states using a button with icons.\r\n * If no disabledIcon is provided, the button will toggle between visual enabled/disabled states without an icon change\r\n *\r\n * @param props\r\n * @returns\r\n */\r\nexport const ToggleButton: FunctionComponent<ToggleButtonProps> = (props) => {\r\n ToggleButton.displayName = \"ToggleButton\";\r\n const { value, onChange, title, appearance = \"subtle\" } = props;\r\n const { size } = useContext(ToolContext);\r\n const classes = useStyles();\r\n const [checked, setChecked] = useState(value);\r\n const toggle = useCallback(() => {\r\n setChecked((prev) => {\r\n const enabled = !prev;\r\n onChange(enabled);\r\n return enabled;\r\n });\r\n }, [setChecked]);\r\n\r\n useEffect(() => {\r\n setChecked(props.value);\r\n }, [props.value]);\r\n\r\n return (\r\n <Tooltip content={title ?? \"\"}>\r\n <FluentToggleButton\r\n className={classes.button}\r\n size={size}\r\n icon={checked ? <props.checkedIcon /> : props.uncheckedIcon ? <props.uncheckedIcon /> : <props.checkedIcon />}\r\n appearance={appearance}\r\n checked={checked}\r\n onClick={toggle}\r\n />\r\n </Tooltip>\r\n );\r\n};\r\n"]}
1
+ {"version":3,"file":"toggleButton.js","sourceRoot":"","sources":["../../../../../dev/sharedUiComponents/src/fluent/primitives/toggleButton.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,YAAY,IAAI,kBAAkB,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AAE5F,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAGrE,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AACvD,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,MAAM,SAAS,GAAG,UAAU,CAAC;IACzB,MAAM,EAAE;QACJ,OAAO,EAAE,MAAM;QACf,UAAU,EAAE,QAAQ;QACpB,cAAc,EAAE,QAAQ;KAC3B;CACJ,CAAC,CAAC;AASH;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,YAAY,GAAyC,CAAC,KAAK,EAAE,EAAE;IACxE,YAAY,CAAC,WAAW,GAAG,cAAc,CAAC;IAC1C,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,UAAU,GAAG,QAAQ,EAAE,GAAG,KAAK,CAAC;IAChE,MAAM,EAAE,IAAI,EAAE,GAAG,UAAU,CAAC,WAAW,CAAC,CAAC;IACzC,MAAM,OAAO,GAAG,SAAS,EAAE,CAAC;IAC5B,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IAC9C,MAAM,MAAM,GAAG,WAAW,CAAC,GAAG,EAAE;QAC5B,UAAU,CAAC,CAAC,WAAW,EAAE,EAAE;YACvB,MAAM,OAAO,GAAG,CAAC,WAAW,CAAC;YAC7B,QAAQ,CAAC,OAAO,CAAC,CAAC;YAClB,OAAO,OAAO,CAAC;QACnB,CAAC,CAAC,CAAC;IACP,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC;IAEf,SAAS,CAAC,GAAG,EAAE;QACX,UAAU,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IAC5B,CAAC,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC;IAElB,OAAO,CACH,KAAC,OAAO,IAAC,OAAO,EAAE,KAAK,IAAI,EAAE,YACzB,KAAC,kBAAkB,IACf,SAAS,EAAE,OAAO,CAAC,MAAM,EACzB,IAAI,EAAE,IAAI,EACV,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,KAAC,KAAK,CAAC,WAAW,KAAG,CAAC,CAAC,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC,CAAC,KAAC,KAAK,CAAC,aAAa,KAAG,CAAC,CAAC,CAAC,KAAC,KAAK,CAAC,WAAW,KAAG,EAC7G,UAAU,EAAE,UAAU,EACtB,OAAO,EAAE,OAAO,EAChB,OAAO,EAAE,MAAM,GACjB,GACI,CACb,CAAC;AACN,CAAC,CAAC","sourcesContent":["import { ToggleButton as FluentToggleButton, makeStyles } from \"@fluentui/react-components\";\r\nimport type { ButtonProps } from \"./button\";\r\nimport { useCallback, useContext, useEffect, useState } from \"react\";\r\nimport type { FunctionComponent } from \"react\";\r\nimport type { FluentIcon } from \"@fluentui/react-icons\";\r\nimport { ToolContext } from \"../hoc/fluentToolWrapper\";\r\nimport { Tooltip } from \"./tooltip\";\r\n\r\nconst useStyles = makeStyles({\r\n button: {\r\n display: \"flex\",\r\n alignItems: \"center\",\r\n justifyContent: \"center\",\r\n },\r\n});\r\n\r\ntype ToggleButtonProps = Omit<ButtonProps, \"icon\" | \"onClick\"> & {\r\n value: boolean;\r\n checkedIcon: FluentIcon;\r\n uncheckedIcon?: FluentIcon;\r\n onChange: (checked: boolean) => void;\r\n};\r\n\r\n/**\r\n * Toggles between two states using a button with icons.\r\n * If no disabledIcon is provided, the button will toggle between visual enabled/disabled states without an icon change\r\n *\r\n * @param props\r\n * @returns\r\n */\r\nexport const ToggleButton: FunctionComponent<ToggleButtonProps> = (props) => {\r\n ToggleButton.displayName = \"ToggleButton\";\r\n const { value, onChange, title, appearance = \"subtle\" } = props;\r\n const { size } = useContext(ToolContext);\r\n const classes = useStyles();\r\n const [checked, setChecked] = useState(value);\r\n const toggle = useCallback(() => {\r\n setChecked((prevChecked) => {\r\n const enabled = !prevChecked;\r\n onChange(enabled);\r\n return enabled;\r\n });\r\n }, [onChange]);\r\n\r\n useEffect(() => {\r\n setChecked(props.value);\r\n }, [props.value]);\r\n\r\n return (\r\n <Tooltip content={title ?? \"\"}>\r\n <FluentToggleButton\r\n className={classes.button}\r\n size={size}\r\n icon={checked ? <props.checkedIcon /> : props.uncheckedIcon ? <props.uncheckedIcon /> : <props.checkedIcon />}\r\n appearance={appearance}\r\n checked={checked}\r\n onClick={toggle}\r\n />\r\n </Tooltip>\r\n );\r\n};\r\n"]}
@@ -28,7 +28,6 @@ export declare class GraphFrame {
28
28
  private _headerTextElement;
29
29
  private _headerCollapseElement;
30
30
  private _headerCloseElement;
31
- private _headerFocusElement;
32
31
  private _commentsElement;
33
32
  private _portContainer;
34
33
  private _outputPortContainer;
@@ -58,7 +57,6 @@ export declare class GraphFrame {
58
57
  private readonly _closeSVG;
59
58
  private readonly _expandSVG;
60
59
  private readonly _collapseSVG;
61
- private readonly _focusSVG;
62
60
  get id(): number;
63
61
  get isCollapsed(): boolean;
64
62
  private _createInputPort;
@@ -271,6 +271,10 @@ export class GraphFrame {
271
271
  this._ownerCanvas._frameIsMoving = true;
272
272
  // Need to delegate the outside ports to the frame
273
273
  if (value) {
274
+ // Exit focus mode when collapsing
275
+ if (this._isFocused) {
276
+ this.switchFocusMode();
277
+ }
274
278
  this.element.classList.add(styles.collapsed);
275
279
  this.element.classList.remove(styles.expanded);
276
280
  this._headerElement.classList.add(styles.collapsedHeader);
@@ -308,7 +312,7 @@ export class GraphFrame {
308
312
  // UI
309
313
  if (this._isCollapsed) {
310
314
  this._headerCollapseElement.innerHTML = this._expandSVG;
311
- this._headerCollapseElement.title = "Expand";
315
+ this._headerCollapseElement.title = "Expand (Shift+click for focus mode)";
312
316
  }
313
317
  else {
314
318
  this._headerCollapseElement.innerHTML = this._collapseSVG;
@@ -428,7 +432,6 @@ export class GraphFrame {
428
432
  this._closeSVG = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 30"><g id="Layer_2" data-name="Layer 2"><path d="M16,15l5.85,5.84-1,1L15,15.93,9.15,21.78l-1-1L14,15,8.19,9.12l1-1L15,14l5.84-5.84,1,1Z"/></g></svg>`;
429
433
  this._expandSVG = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 30"><g id="Layer_2" data-name="Layer 2"><path d="M22.31,7.69V22.31H7.69V7.69ZM21.19,8.81H8.81V21.19H21.19Zm-6.75,6.75H11.06V14.44h3.38V11.06h1.12v3.38h3.38v1.12H15.56v3.38H14.44Z"/></g></svg>`;
430
434
  this._collapseSVG = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 30"><g id="Layer_2" data-name="Layer 2"><path d="M22.31,7.69V22.31H7.69V7.69ZM21.19,8.81H8.81V21.19H21.19Zm-2.25,6.75H11.06V14.44h7.88Z"/></g></svg>`;
431
- this._focusSVG = `<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M6.24992 4.5C5.28344 4.5 4.49996 5.2835 4.49996 6.25V17.75C4.49996 18.7165 5.28344 19.5 6.24992 19.5H17.7496C18.7161 19.5 19.4996 18.7165 19.4996 17.75V13.75C19.4996 13.3358 19.8354 13 20.2496 13C20.6638 13 20.9995 13.3358 20.9995 13.75V17.75C20.9995 19.5449 19.5445 21 17.7496 21H6.24992C4.45504 21 3 19.5449 3 17.75V6.25C3 4.45507 4.45504 3 6.24992 3H10.2498C10.664 3 10.9998 3.33579 10.9998 3.75C10.9998 4.16421 10.664 4.5 10.2498 4.5H6.24992ZM12.9997 3.75C12.9997 3.33579 13.3355 3 13.7497 3H20.25C20.6642 3 21 3.33579 21 3.75V10.25C21 10.6642 20.6642 11 20.25 11C19.8358 11 19.5 10.6642 19.5 10.25V5.56074L14.28 10.7804C13.9871 11.0732 13.5123 11.0732 13.2194 10.7803C12.9265 10.4874 12.9265 10.0125 13.2194 9.71964L18.4395 4.5H13.7497C13.3355 4.5 12.9997 4.16421 12.9997 3.75Z" /></svg>`;
432
435
  this._isFocused = false;
433
436
  this._initResizing = (evt) => {
434
437
  evt.stopPropagation();
@@ -870,21 +873,6 @@ export class GraphFrame {
870
873
  this._headerTextElement = root.ownerDocument.createElement("div");
871
874
  this._headerTextElement.classList.add(styles["frame-box-header-title"]);
872
875
  this._headerElement.appendChild(this._headerTextElement);
873
- // Focus
874
- this._headerFocusElement = root.ownerDocument.createElement("div");
875
- this._headerFocusElement.classList.add(styles["frame-box-header-focus"]);
876
- this._headerFocusElement.classList.add(styles["frame-box-header-button"]);
877
- this._headerFocusElement.title = "Switch focus mode";
878
- this._headerFocusElement.ondragstart = () => false;
879
- this._headerFocusElement.addEventListener("pointerdown", (evt) => {
880
- evt.stopPropagation();
881
- });
882
- this._headerFocusElement.addEventListener("pointerup", (evt) => {
883
- evt.stopPropagation();
884
- this.switchFocusMode();
885
- });
886
- this._headerFocusElement.innerHTML = this._focusSVG;
887
- this._headerElement.appendChild(this._headerFocusElement);
888
876
  // Collapse
889
877
  this._headerCollapseElement = root.ownerDocument.createElement("div");
890
878
  this._headerCollapseElement.classList.add(styles["frame-box-header-collapse"]);
@@ -898,7 +886,16 @@ export class GraphFrame {
898
886
  this._headerCollapseElement.addEventListener("pointerup", (evt) => {
899
887
  evt.stopPropagation();
900
888
  this._headerCollapseElement.classList.remove("down");
901
- this.isCollapsed = !this.isCollapsed;
889
+ if (evt.shiftKey) {
890
+ // Shift+click toggles focus mode without changing collapse state
891
+ if (this._isCollapsed) {
892
+ this.isCollapsed = false;
893
+ }
894
+ this.switchFocusMode();
895
+ }
896
+ else {
897
+ this.isCollapsed = !this.isCollapsed;
898
+ }
902
899
  });
903
900
  this._headerCollapseElement.innerHTML = this._collapseSVG;
904
901
  this._headerElement.appendChild(this._headerCollapseElement);