@babylonjs/shared-ui-components 8.51.2 → 8.52.0

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.
@@ -107,22 +107,9 @@ export const ChildWindow = (props) => {
107
107
  body.style.padding = "0";
108
108
  body.style.display = "flex";
109
109
  body.style.overflow = "hidden";
110
- const applyWindowState = () => {
111
- // Setup the window state, including creating a Fluent/Griffel "renderer" for managing runtime styles/classes in the child window.
112
- setWindowState({ mountNode: body, renderer: createDOMRenderer(childWindow.document) });
113
- onOpenChange?.(true);
114
- };
115
- // Once the child window document is ready, setup the window state which will trigger another effect that renders into the child window.
116
- if (childWindow.document.readyState === "complete") {
117
- applyWindowState();
118
- }
119
- else {
120
- const onChildWindowLoad = () => {
121
- applyWindowState();
122
- };
123
- childWindow.addEventListener("load", onChildWindowLoad, { once: true });
124
- disposeActions.push(() => childWindow.removeEventListener("load", onChildWindowLoad));
125
- }
110
+ // Setup the window state, including creating a Fluent/Griffel "renderer" for managing runtime styles/classes in the child window.
111
+ setWindowState({ mountNode: body, renderer: createDOMRenderer(childWindow.document) });
112
+ onOpenChange?.(true);
126
113
  // When the child window is closed for any reason, transition back to a closed state.
127
114
  const onChildWindowUnload = () => {
128
115
  setWindowState(undefined);
@@ -1 +1 @@
1
- {"version":3,"file":"childWindow.js","sourceRoot":"","sources":["../../../../../dev/sharedUiComponents/src/fluent/hoc/childWindow.tsx"],"names":[],"mappings":"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","sourcesContent":["import type { GriffelRenderer } from \"@fluentui/react-components\";\r\nimport type { FunctionComponent, PropsWithChildren, Ref } from \"react\";\r\n\r\nimport { createDOMRenderer, FluentProvider, Portal, RendererProvider } from \"@fluentui/react-components\";\r\nimport { useCallback, useEffect, useImperativeHandle, useState } from \"react\";\r\n\r\nimport { Logger } from \"core/Misc/logger\";\r\nimport { ToastProvider } from \"../primitives/toast\";\r\n\r\nfunction ToFeaturesString(options: ChildWindowOptions) {\r\n const { defaultWidth, defaultHeight, defaultLeft, defaultTop } = options;\r\n\r\n const features: { key: string; value: string }[] = [];\r\n\r\n if (defaultWidth !== undefined) {\r\n features.push({ key: \"width\", value: defaultWidth.toString() });\r\n }\r\n if (defaultHeight !== undefined) {\r\n features.push({ key: \"height\", value: defaultHeight.toString() });\r\n }\r\n if (defaultLeft !== undefined) {\r\n features.push({ key: \"left\", value: defaultLeft.toString() });\r\n }\r\n if (defaultTop !== undefined) {\r\n features.push({ key: \"top\", value: defaultTop.toString() });\r\n }\r\n features.push({ key: \"location\", value: \"no\" });\r\n\r\n return features.map((feature) => `${feature.key}=${feature.value}`).join(\",\");\r\n}\r\n\r\nexport type ChildWindowOptions = {\r\n /**\r\n * The default width of the child window in pixels.\r\n * @remarks Ignored if the ChildWindow was passed an id and previous bounds were saved.\r\n */\r\n defaultWidth?: number;\r\n\r\n /**\r\n * The default height of the child window in pixels.\r\n * @remarks Ignored if the ChildWindow was passed an id and previous bounds were saved.\r\n */\r\n defaultHeight?: number;\r\n\r\n /**\r\n * The default left position of the child window in pixels.\r\n * @remarks Ignored if the ChildWindow was passed an id and previous bounds were saved.\r\n */\r\n defaultLeft?: number;\r\n\r\n /**\r\n * The default top position of the child window in pixels.\r\n * @remarks Ignored if the ChildWindow was passed an id and previous bounds were saved.\r\n */\r\n defaultTop?: number;\r\n\r\n /**\r\n * The title of the child window.\r\n * @remarks If not provided, the id will be used instead (if any).\r\n */\r\n title?: string;\r\n};\r\n\r\nexport type ChildWindow = {\r\n /**\r\n * Opens the child window.\r\n * @param options Options for opening the child window.\r\n */\r\n open: (options?: ChildWindowOptions) => void;\r\n\r\n /**\r\n * Closes the child window.\r\n */\r\n close: () => void;\r\n};\r\n\r\nexport type ChildWindowProps = {\r\n /**\r\n * An optional unique identity for the child window.\r\n * @remarks If provided, the child window's bounds will be saved/restored using this identity.\r\n */\r\n id?: string;\r\n\r\n /**\r\n * Called when the open state of the child window changes.\r\n * @param isOpen Whether the child window is open.\r\n */\r\n onOpenChange?: (isOpen: boolean) => void;\r\n\r\n /**\r\n * A ref that exposes the ChildWindow imperative API.\r\n */\r\n imperativeRef?: Ref<ChildWindow>;\r\n};\r\n\r\n/**\r\n * Allows displaying a child window that can contain child components.\r\n * @param props Props for the child window.\r\n * @returns The child window component.\r\n */\r\nexport const ChildWindow: FunctionComponent<PropsWithChildren<ChildWindowProps>> = (props) => {\r\n const { id, children, onOpenChange, imperativeRef: imperativeRef } = props;\r\n\r\n const [windowState, setWindowState] = useState<{ mountNode: HTMLElement; renderer: GriffelRenderer }>();\r\n const [childWindow, setChildWindow] = useState<Window>();\r\n\r\n const storageKey = id ? `Babylon/Settings/ChildWindow/${id}/Bounds` : null;\r\n\r\n // This function is just for creating the child window itself. It is a function because\r\n // it must be called synchronously in response to a user interaction (e.g. button click),\r\n // otherwise the browser will block it as a scripted popup.\r\n const createWindow = useCallback(\r\n (options: ChildWindowOptions = {}) => {\r\n if (storageKey) {\r\n // If we are persisting window bounds, but the window is already open, just use the existing bounds.\r\n // Otherwise, try to load bounds from storage.\r\n if (childWindow) {\r\n options.defaultLeft = childWindow.screenX;\r\n options.defaultTop = childWindow.screenY;\r\n options.defaultWidth = childWindow.innerWidth;\r\n options.defaultHeight = childWindow.innerHeight;\r\n } else {\r\n const savedBounds = localStorage.getItem(storageKey);\r\n if (savedBounds) {\r\n try {\r\n const bounds = JSON.parse(savedBounds);\r\n options.defaultLeft = bounds.left;\r\n options.defaultTop = bounds.top;\r\n options.defaultWidth = bounds.width;\r\n options.defaultHeight = bounds.height;\r\n } catch {\r\n Logger.Warn(`Could not parse saved bounds for child window with key ${storageKey}`);\r\n }\r\n }\r\n }\r\n }\r\n\r\n // Half width by default.\r\n if (!options.defaultWidth) {\r\n options.defaultWidth = window.innerWidth * (2 / 3);\r\n }\r\n // Half height by default.\r\n if (!options.defaultHeight) {\r\n options.defaultHeight = window.innerHeight * (2 / 3);\r\n }\r\n // Horizontally centered by default.\r\n if (!options.defaultLeft) {\r\n options.defaultLeft = window.screenX + (window.innerWidth - options.defaultWidth) * (2 / 3);\r\n }\r\n // Vertically centered by default.\r\n if (!options.defaultTop) {\r\n options.defaultTop = window.screenY + (window.innerHeight - options.defaultHeight) * (2 / 3);\r\n }\r\n\r\n // Try to create the child window (can be null if popups are blocked).\r\n const newChildWindow = window.open(\"\", \"\", ToFeaturesString(options));\r\n if (newChildWindow) {\r\n // Set the title if provided.\r\n newChildWindow.document.title = options.title ?? id ?? \"\";\r\n\r\n // Set the child window state.\r\n setChildWindow((current) => {\r\n // But first close any existing child window.\r\n current?.close();\r\n return newChildWindow;\r\n });\r\n }\r\n },\r\n [childWindow, storageKey]\r\n );\r\n\r\n useImperativeHandle(imperativeRef, () => {\r\n return {\r\n open: createWindow,\r\n close: () => setChildWindow(undefined),\r\n };\r\n }, [createWindow]);\r\n\r\n // This side effect runs any time the child window instance changes. It does the rest of the child window\r\n // setup work, including creating resources and state needed to properly render the content of the child window.\r\n useEffect(() => {\r\n const disposeActions: (() => void)[] = [];\r\n\r\n if (childWindow) {\r\n const body = childWindow.document.body;\r\n body.style.width = \"100%\";\r\n body.style.height = \"100%\";\r\n body.style.margin = \"0\";\r\n body.style.padding = \"0\";\r\n body.style.display = \"flex\";\r\n body.style.overflow = \"hidden\";\r\n\r\n const applyWindowState = () => {\r\n // Setup the window state, including creating a Fluent/Griffel \"renderer\" for managing runtime styles/classes in the child window.\r\n setWindowState({ mountNode: body, renderer: createDOMRenderer(childWindow.document) });\r\n onOpenChange?.(true);\r\n };\r\n\r\n // Once the child window document is ready, setup the window state which will trigger another effect that renders into the child window.\r\n if (childWindow.document.readyState === \"complete\") {\r\n applyWindowState();\r\n } else {\r\n const onChildWindowLoad = () => {\r\n applyWindowState();\r\n };\r\n childWindow.addEventListener(\"load\", onChildWindowLoad, { once: true });\r\n disposeActions.push(() => childWindow.removeEventListener(\"load\", onChildWindowLoad));\r\n }\r\n\r\n // When the child window is closed for any reason, transition back to a closed state.\r\n const onChildWindowUnload = () => {\r\n setWindowState(undefined);\r\n setChildWindow(undefined);\r\n onOpenChange?.(false);\r\n };\r\n childWindow.addEventListener(\"unload\", onChildWindowUnload, { once: true });\r\n disposeActions.push(() => childWindow.removeEventListener(\"unload\", onChildWindowUnload));\r\n\r\n // If the main window closes, close any open child windows as well (don't leave them orphaned).\r\n const onParentWindowUnload = () => {\r\n childWindow.close();\r\n };\r\n window.addEventListener(\"unload\", onParentWindowUnload, { once: true });\r\n disposeActions.push(() => window.removeEventListener(\"unload\", onParentWindowUnload));\r\n\r\n // On dispose, close the child window.\r\n disposeActions.push(() => childWindow.close());\r\n\r\n // On dispose, save the window bounds.\r\n disposeActions.push(() => {\r\n if (storageKey) {\r\n localStorage.setItem(\r\n storageKey,\r\n JSON.stringify({\r\n left: childWindow.screenX,\r\n top: childWindow.screenY,\r\n width: childWindow.innerWidth,\r\n height: childWindow.innerHeight,\r\n })\r\n );\r\n }\r\n });\r\n }\r\n\r\n return () => {\r\n disposeActions.reverse().forEach((dispose) => dispose());\r\n };\r\n }, [childWindow]);\r\n\r\n if (!windowState) {\r\n return null;\r\n }\r\n\r\n const { mountNode, renderer } = windowState;\r\n\r\n return (\r\n // Portal targets the body of the child window.\r\n <Portal mountNode={mountNode}>\r\n {/* RenderProvider manages Fluent style/class state. */}\r\n <RendererProvider renderer={renderer} targetDocument={mountNode.ownerDocument}>\r\n {/* Fluent Provider is needed for managing other Fluent state and applying the current theme mode. */}\r\n <FluentProvider\r\n style={{\r\n display: \"flex\",\r\n flexGrow: 1,\r\n flexDirection: \"column\",\r\n overflow: \"hidden\",\r\n }}\r\n applyStylesToPortals={false}\r\n targetDocument={mountNode.ownerDocument}\r\n >\r\n <ToastProvider>{children}</ToastProvider>\r\n </FluentProvider>\r\n </RendererProvider>\r\n </Portal>\r\n );\r\n};\r\n"]}
1
+ {"version":3,"file":"childWindow.js","sourceRoot":"","sources":["../../../../../dev/sharedUiComponents/src/fluent/hoc/childWindow.tsx"],"names":[],"mappings":"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","sourcesContent":["import type { GriffelRenderer } from \"@fluentui/react-components\";\r\nimport type { FunctionComponent, PropsWithChildren, Ref } from \"react\";\r\n\r\nimport { createDOMRenderer, FluentProvider, Portal, RendererProvider } from \"@fluentui/react-components\";\r\nimport { useCallback, useEffect, useImperativeHandle, useState } from \"react\";\r\n\r\nimport { Logger } from \"core/Misc/logger\";\r\nimport { ToastProvider } from \"../primitives/toast\";\r\n\r\nfunction ToFeaturesString(options: ChildWindowOptions) {\r\n const { defaultWidth, defaultHeight, defaultLeft, defaultTop } = options;\r\n\r\n const features: { key: string; value: string }[] = [];\r\n\r\n if (defaultWidth !== undefined) {\r\n features.push({ key: \"width\", value: defaultWidth.toString() });\r\n }\r\n if (defaultHeight !== undefined) {\r\n features.push({ key: \"height\", value: defaultHeight.toString() });\r\n }\r\n if (defaultLeft !== undefined) {\r\n features.push({ key: \"left\", value: defaultLeft.toString() });\r\n }\r\n if (defaultTop !== undefined) {\r\n features.push({ key: \"top\", value: defaultTop.toString() });\r\n }\r\n features.push({ key: \"location\", value: \"no\" });\r\n\r\n return features.map((feature) => `${feature.key}=${feature.value}`).join(\",\");\r\n}\r\n\r\nexport type ChildWindowOptions = {\r\n /**\r\n * The default width of the child window in pixels.\r\n * @remarks Ignored if the ChildWindow was passed an id and previous bounds were saved.\r\n */\r\n defaultWidth?: number;\r\n\r\n /**\r\n * The default height of the child window in pixels.\r\n * @remarks Ignored if the ChildWindow was passed an id and previous bounds were saved.\r\n */\r\n defaultHeight?: number;\r\n\r\n /**\r\n * The default left position of the child window in pixels.\r\n * @remarks Ignored if the ChildWindow was passed an id and previous bounds were saved.\r\n */\r\n defaultLeft?: number;\r\n\r\n /**\r\n * The default top position of the child window in pixels.\r\n * @remarks Ignored if the ChildWindow was passed an id and previous bounds were saved.\r\n */\r\n defaultTop?: number;\r\n\r\n /**\r\n * The title of the child window.\r\n * @remarks If not provided, the id will be used instead (if any).\r\n */\r\n title?: string;\r\n};\r\n\r\nexport type ChildWindow = {\r\n /**\r\n * Opens the child window.\r\n * @param options Options for opening the child window.\r\n */\r\n open: (options?: ChildWindowOptions) => void;\r\n\r\n /**\r\n * Closes the child window.\r\n */\r\n close: () => void;\r\n};\r\n\r\nexport type ChildWindowProps = {\r\n /**\r\n * An optional unique identity for the child window.\r\n * @remarks If provided, the child window's bounds will be saved/restored using this identity.\r\n */\r\n id?: string;\r\n\r\n /**\r\n * Called when the open state of the child window changes.\r\n * @param isOpen Whether the child window is open.\r\n */\r\n onOpenChange?: (isOpen: boolean) => void;\r\n\r\n /**\r\n * A ref that exposes the ChildWindow imperative API.\r\n */\r\n imperativeRef?: Ref<ChildWindow>;\r\n};\r\n\r\n/**\r\n * Allows displaying a child window that can contain child components.\r\n * @param props Props for the child window.\r\n * @returns The child window component.\r\n */\r\nexport const ChildWindow: FunctionComponent<PropsWithChildren<ChildWindowProps>> = (props) => {\r\n const { id, children, onOpenChange, imperativeRef: imperativeRef } = props;\r\n\r\n const [windowState, setWindowState] = useState<{ mountNode: HTMLElement; renderer: GriffelRenderer }>();\r\n const [childWindow, setChildWindow] = useState<Window>();\r\n\r\n const storageKey = id ? `Babylon/Settings/ChildWindow/${id}/Bounds` : null;\r\n\r\n // This function is just for creating the child window itself. It is a function because\r\n // it must be called synchronously in response to a user interaction (e.g. button click),\r\n // otherwise the browser will block it as a scripted popup.\r\n const createWindow = useCallback(\r\n (options: ChildWindowOptions = {}) => {\r\n if (storageKey) {\r\n // If we are persisting window bounds, but the window is already open, just use the existing bounds.\r\n // Otherwise, try to load bounds from storage.\r\n if (childWindow) {\r\n options.defaultLeft = childWindow.screenX;\r\n options.defaultTop = childWindow.screenY;\r\n options.defaultWidth = childWindow.innerWidth;\r\n options.defaultHeight = childWindow.innerHeight;\r\n } else {\r\n const savedBounds = localStorage.getItem(storageKey);\r\n if (savedBounds) {\r\n try {\r\n const bounds = JSON.parse(savedBounds);\r\n options.defaultLeft = bounds.left;\r\n options.defaultTop = bounds.top;\r\n options.defaultWidth = bounds.width;\r\n options.defaultHeight = bounds.height;\r\n } catch {\r\n Logger.Warn(`Could not parse saved bounds for child window with key ${storageKey}`);\r\n }\r\n }\r\n }\r\n }\r\n\r\n // Half width by default.\r\n if (!options.defaultWidth) {\r\n options.defaultWidth = window.innerWidth * (2 / 3);\r\n }\r\n // Half height by default.\r\n if (!options.defaultHeight) {\r\n options.defaultHeight = window.innerHeight * (2 / 3);\r\n }\r\n // Horizontally centered by default.\r\n if (!options.defaultLeft) {\r\n options.defaultLeft = window.screenX + (window.innerWidth - options.defaultWidth) * (2 / 3);\r\n }\r\n // Vertically centered by default.\r\n if (!options.defaultTop) {\r\n options.defaultTop = window.screenY + (window.innerHeight - options.defaultHeight) * (2 / 3);\r\n }\r\n\r\n // Try to create the child window (can be null if popups are blocked).\r\n const newChildWindow = window.open(\"\", \"\", ToFeaturesString(options));\r\n if (newChildWindow) {\r\n // Set the title if provided.\r\n newChildWindow.document.title = options.title ?? id ?? \"\";\r\n\r\n // Set the child window state.\r\n setChildWindow((current) => {\r\n // But first close any existing child window.\r\n current?.close();\r\n return newChildWindow;\r\n });\r\n }\r\n },\r\n [childWindow, storageKey]\r\n );\r\n\r\n useImperativeHandle(imperativeRef, () => {\r\n return {\r\n open: createWindow,\r\n close: () => setChildWindow(undefined),\r\n };\r\n }, [createWindow]);\r\n\r\n // This side effect runs any time the child window instance changes. It does the rest of the child window\r\n // setup work, including creating resources and state needed to properly render the content of the child window.\r\n useEffect(() => {\r\n const disposeActions: (() => void)[] = [];\r\n\r\n if (childWindow) {\r\n const body = childWindow.document.body;\r\n body.style.width = \"100%\";\r\n body.style.height = \"100%\";\r\n body.style.margin = \"0\";\r\n body.style.padding = \"0\";\r\n body.style.display = \"flex\";\r\n body.style.overflow = \"hidden\";\r\n\r\n // Setup the window state, including creating a Fluent/Griffel \"renderer\" for managing runtime styles/classes in the child window.\r\n setWindowState({ mountNode: body, renderer: createDOMRenderer(childWindow.document) });\r\n onOpenChange?.(true);\r\n\r\n // When the child window is closed for any reason, transition back to a closed state.\r\n const onChildWindowUnload = () => {\r\n setWindowState(undefined);\r\n setChildWindow(undefined);\r\n onOpenChange?.(false);\r\n };\r\n childWindow.addEventListener(\"unload\", onChildWindowUnload, { once: true });\r\n disposeActions.push(() => childWindow.removeEventListener(\"unload\", onChildWindowUnload));\r\n\r\n // If the main window closes, close any open child windows as well (don't leave them orphaned).\r\n const onParentWindowUnload = () => {\r\n childWindow.close();\r\n };\r\n window.addEventListener(\"unload\", onParentWindowUnload, { once: true });\r\n disposeActions.push(() => window.removeEventListener(\"unload\", onParentWindowUnload));\r\n\r\n // On dispose, close the child window.\r\n disposeActions.push(() => childWindow.close());\r\n\r\n // On dispose, save the window bounds.\r\n disposeActions.push(() => {\r\n if (storageKey) {\r\n localStorage.setItem(\r\n storageKey,\r\n JSON.stringify({\r\n left: childWindow.screenX,\r\n top: childWindow.screenY,\r\n width: childWindow.innerWidth,\r\n height: childWindow.innerHeight,\r\n })\r\n );\r\n }\r\n });\r\n }\r\n\r\n return () => {\r\n disposeActions.reverse().forEach((dispose) => dispose());\r\n };\r\n }, [childWindow]);\r\n\r\n if (!windowState) {\r\n return null;\r\n }\r\n\r\n const { mountNode, renderer } = windowState;\r\n\r\n return (\r\n // Portal targets the body of the child window.\r\n <Portal mountNode={mountNode}>\r\n {/* RenderProvider manages Fluent style/class state. */}\r\n <RendererProvider renderer={renderer} targetDocument={mountNode.ownerDocument}>\r\n {/* Fluent Provider is needed for managing other Fluent state and applying the current theme mode. */}\r\n <FluentProvider\r\n style={{\r\n display: \"flex\",\r\n flexGrow: 1,\r\n flexDirection: \"column\",\r\n overflow: \"hidden\",\r\n }}\r\n applyStylesToPortals={false}\r\n targetDocument={mountNode.ownerDocument}\r\n >\r\n <ToastProvider>{children}</ToastProvider>\r\n </FluentProvider>\r\n </RendererProvider>\r\n </Portal>\r\n );\r\n};\r\n"]}
@@ -178,9 +178,9 @@ export function useAccordionSectionItemState(props) {
178
178
  }
179
179
  prevItemIdRef.current = itemId;
180
180
  }, [itemId, sectionCtx?.sectionId]);
181
- // Register item and detect duplicates
181
+ // Register item and detect duplicates (skip nested items, as children of other AccordionSectionItem should not participate in pin/hide/search).
182
182
  useEffect(() => {
183
- if (!accordionCtx || !itemUniqueId) {
183
+ if (!accordionCtx || !itemUniqueId || isNested) {
184
184
  return;
185
185
  }
186
186
  const { registeredItemIds } = accordionCtx;
@@ -192,7 +192,7 @@ export function useAccordionSectionItemState(props) {
192
192
  return () => {
193
193
  registeredItemIds.delete(itemUniqueId);
194
194
  };
195
- }, [accordionCtx, itemUniqueId, itemId, itemLabel, sectionCtx?.sectionId]);
195
+ }, [accordionCtx, itemUniqueId, itemId, itemLabel, sectionCtx?.sectionId, isNested]);
196
196
  // If no context, static item, or nested, return undefined
197
197
  if (!accordionCtx || staticItem) {
198
198
  return undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"accordion.contexts.js","sourceRoot":"","sources":["../../../../../dev/sharedUiComponents/src/fluent/primitives/accordion.contexts.tsx"],"names":[],"mappings":"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","sourcesContent":["import type { RefObject } from \"react\";\nimport type { AccordionProps, AccordionSectionBlockProps, AccordionSectionItemProps } from \"./accordion\";\nimport { createContext, useContext, useEffect, useMemo, useReducer, useRef } from \"react\";\nimport { DataStorage } from \"core/Misc/dataStorage\";\nimport { Logger } from \"core/Misc/logger\";\n\n// ============================================================================\n// Storage Helpers\n// ============================================================================\n\nconst STORAGE_KEY_ROOT = \"Babylon/Accordion\";\n\nconst ReadFromStorage = <T,>(path: string, initial: T): T => {\n try {\n const stored = DataStorage.ReadString(`${STORAGE_KEY_ROOT}/${path}`, \"\");\n return stored ? JSON.parse(stored) : initial;\n } catch {\n return initial;\n }\n};\n\nconst WriteToStorage = (path: string, data: unknown): void => {\n DataStorage.WriteString(`${STORAGE_KEY_ROOT}/${path}`, JSON.stringify(data));\n};\n\n// ============================================================================\n// State Types\n// ============================================================================\n\n/**\n * Immutable state for the Accordion.\n */\nexport type AccordionState = {\n /** IDs of pinned items (persisted to localStorage). */\n pinnedIds: string[];\n /** IDs of hidden items (persisted to localStorage). */\n hiddenIds: string[];\n /** Current search/filter term. */\n searchTerm: string;\n /** Whether edit mode is active (shows pin/hide controls). */\n editMode: boolean;\n};\n\n/**\n * Actions that can be dispatched to update accordion state.\n */\nexport type AccordionAction =\n | { type: \"SET_SEARCH_TERM\"; term: string }\n | { type: \"SET_EDIT_MODE\"; enabled: boolean }\n | { type: \"TOGGLE_PINNED\"; itemId: string }\n | { type: \"TOGGLE_HIDDEN\"; itemId: string }\n | { type: \"MOVE_PINNED_UP\"; itemId: string }\n | { type: \"REMOVE_STALE_IDS\"; activeIds: Set<string> }\n | { type: \"SHOW_ALL\" }\n | { type: \"HIDE_ALL_VISIBLE\"; visibleItemIds: string[] };\n\n/**\n * Feature flags for the Accordion (immutable after initialization).\n */\nexport type AccordionFeatures = {\n /** Whether pinning is enabled. */\n pinning: boolean;\n /** Whether hiding is enabled. */\n hiding: boolean;\n /** Whether search is enabled. */\n search: boolean;\n};\n\n// ============================================================================\n// Reducer\n// ============================================================================\n\nconst AccordionReducer = (state: AccordionState, action: AccordionAction): AccordionState => {\n switch (action.type) {\n case \"SET_SEARCH_TERM\":\n return { ...state, searchTerm: action.term };\n\n case \"SET_EDIT_MODE\":\n return { ...state, editMode: action.enabled };\n\n case \"TOGGLE_PINNED\": {\n const isPinned = state.pinnedIds.includes(action.itemId);\n return {\n ...state,\n pinnedIds: isPinned ? state.pinnedIds.filter((id) => id !== action.itemId) : [...state.pinnedIds, action.itemId],\n };\n }\n\n case \"TOGGLE_HIDDEN\": {\n const isHidden = state.hiddenIds.includes(action.itemId);\n return {\n ...state,\n hiddenIds: isHidden ? state.hiddenIds.filter((id) => id !== action.itemId) : [...state.hiddenIds, action.itemId],\n };\n }\n\n case \"MOVE_PINNED_UP\": {\n const index = state.pinnedIds.indexOf(action.itemId);\n if (index <= 0) {\n return state;\n }\n const newPinnedIds = [...state.pinnedIds];\n [newPinnedIds[index - 1], newPinnedIds[index]] = [newPinnedIds[index], newPinnedIds[index - 1]];\n return { ...state, pinnedIds: newPinnedIds };\n }\n\n case \"REMOVE_STALE_IDS\": {\n const pinnedIds = state.pinnedIds.filter((id) => action.activeIds.has(id));\n const hiddenIds = state.hiddenIds.filter((id) => action.activeIds.has(id));\n if (pinnedIds.length === state.pinnedIds.length && hiddenIds.length === state.hiddenIds.length) {\n return state;\n }\n return { ...state, pinnedIds, hiddenIds };\n }\n\n case \"SHOW_ALL\":\n return { ...state, hiddenIds: [] };\n\n case \"HIDE_ALL_VISIBLE\":\n return {\n ...state,\n hiddenIds: [...new Set([...state.hiddenIds, ...action.visibleItemIds])],\n };\n\n default:\n return state;\n }\n};\n\n// ============================================================================\n// Accordion Context\n// ============================================================================\n\n/**\n * Context value for the Accordion component.\n */\nexport type AccordionContextValue = {\n /** The unique ID of the Accordion instance. */\n accordionId: string;\n /** State for the Accordion, managed via dispatch function. */\n state: AccordionState;\n /** Dispatch function to update state. */\n dispatch: React.Dispatch<AccordionAction>;\n /** Feature flags. */\n features: AccordionFeatures;\n /** Ref for the pinned items portal container. */\n pinnedContainerRef: RefObject<HTMLDivElement>;\n /** Map of registered item IDs to labels (for duplicate detection and section empty checks). */\n registeredItemIds: Map<string, string>;\n};\n\nexport const AccordionContext = createContext<AccordionContextValue | undefined>(undefined);\n\n/**\n * Hook to create and manage the AccordionContext value.\n *\n * @param props - AccordionProps\n * @returns AccordionContextValue, or undefined if no features are enabled or no uniqueId is provided.\n */\nexport function useAccordionContext(props: AccordionProps): AccordionContextValue | undefined {\n const { uniqueId: accordionId, enablePinnedItems, enableHiddenItems, enableSearchItems } = props;\n\n const features: AccordionFeatures = useMemo(\n () => ({\n pinning: enablePinnedItems ?? false,\n hiding: enableHiddenItems ?? false,\n search: enableSearchItems ?? false,\n }),\n [enablePinnedItems, enableHiddenItems, enableSearchItems]\n );\n\n const hasFeatures = features.pinning || features.hiding || features.search;\n\n // Initialize state from localStorage\n const initialState = useMemo((): AccordionState => {\n if (!accordionId || !hasFeatures) {\n return { pinnedIds: [], hiddenIds: [], searchTerm: \"\", editMode: false };\n }\n return {\n pinnedIds: features.pinning ? ReadFromStorage<string[]>(`Pinned/${accordionId}`, []) : [],\n hiddenIds: features.hiding ? ReadFromStorage<string[]>(`Hidden/${accordionId}`, []) : [],\n searchTerm: \"\",\n editMode: false,\n };\n }, [accordionId, hasFeatures, features.pinning, features.hiding]);\n\n const [state, dispatch] = useReducer(AccordionReducer, initialState);\n\n const pinnedContainerRef = useRef<HTMLDivElement>(null);\n const registeredItemIds = useRef<Map<string, string>>(new Map());\n\n // Persist pinnedIds to localStorage when they change\n useEffect(() => {\n if (accordionId && features.pinning) {\n WriteToStorage(`Pinned/${accordionId}`, state.pinnedIds);\n }\n }, [accordionId, features.pinning, state.pinnedIds]);\n\n // Persist hiddenIds to localStorage when they change\n useEffect(() => {\n if (accordionId && features.hiding) {\n WriteToStorage(`Hidden/${accordionId}`, state.hiddenIds);\n }\n }, [accordionId, features.hiding, state.hiddenIds]);\n\n // Clean stale IDs from localStorage on mount. Parent effects run after\n // children's, so all items will have registered by the time this fires.\n useEffect(() => {\n if (accordionId && hasFeatures) {\n dispatch({ type: \"REMOVE_STALE_IDS\", activeIds: new Set(registeredItemIds.current.keys()) });\n }\n }, [accordionId, hasFeatures]);\n\n // Return undefined if no accordionId or no features enabled\n if (!accordionId || !hasFeatures) {\n return undefined;\n }\n\n return {\n accordionId,\n state,\n dispatch,\n features,\n pinnedContainerRef,\n registeredItemIds: registeredItemIds.current,\n };\n}\n\n// ============================================================================\n// Section Context\n// ============================================================================\n\n/**\n * Context value for an AccordionSectionBlock.\n */\nexport type AccordionSectionBlockContextValue = {\n /** The section ID. */\n sectionId: string;\n};\n\nexport const AccordionSectionBlockContext = createContext<AccordionSectionBlockContextValue | undefined>(undefined);\n\n/**\n * Hook to create the AccordionSectionBlockContext value.\n *\n * @param props - AccordionSectionBlockProps\n * @returns AccordionSectionBlockContextValue and isEmpty state\n */\nexport function useAccordionSectionBlockContext(props: AccordionSectionBlockProps): {\n context: AccordionSectionBlockContextValue;\n isEmpty: boolean;\n} {\n const { sectionId } = props;\n const context = useMemo(() => ({ sectionId }), [sectionId]);\n const isEmpty = useIsSectionEmpty(sectionId);\n\n return { context, isEmpty };\n}\n\n// ============================================================================\n// Item Depth Context (to detect nested AccordionSectionItems)\n// ============================================================================\n\n/**\n * Context to track whether we're inside an AccordionSectionItem.\n * Used to prevent nested items from being individually manageable.\n */\nexport const AccordionItemDepthContext = createContext<boolean>(false);\n\n// ============================================================================\n// Item Context Hook\n// ============================================================================\n\n/**\n * Derived item state, computed from the accordion state during render.\n */\nexport type AccordionItemState = {\n /** The globally unique item ID. */\n itemUniqueId: string;\n /** Whether this item is nested inside another AccordionSectionItem. */\n isNested: boolean;\n /** Whether this item is pinned. */\n isPinned: boolean;\n /** Whether this item is hidden. */\n isHidden: boolean;\n /** Whether this item matches the current search term. */\n isMatch: boolean;\n /** The index of this item in the pinned list (for ordering). */\n pinnedIndex: number;\n /** Whether this pinned item can be moved up (is not first in the pinned list). */\n canMoveUp: boolean;\n /** Whether edit mode is active. */\n inEditMode: boolean;\n /** Callbacks to modify state. */\n actions: {\n togglePinned: () => void;\n toggleHidden: () => void;\n movePinnedUp: () => void;\n };\n};\n\n/**\n * Hook to compute item state from accordion context.\n *\n * @param props - AccordionSectionItemProps\n * @returns AccordionItemState, or undefined if no accordion context or nested item.\n */\nexport function useAccordionSectionItemState(props: AccordionSectionItemProps): AccordionItemState | undefined {\n const { uniqueId: itemId, label: itemLabel, staticItem } = props;\n\n const accordionCtx = useContext(AccordionContext);\n const sectionCtx = useContext(AccordionSectionBlockContext);\n const isNested = useContext(AccordionItemDepthContext);\n\n // Build the globally unique item ID\n const itemUniqueId = useMemo(() => {\n if (!accordionCtx || !sectionCtx) {\n return \"\";\n }\n return `${accordionCtx.accordionId}/${sectionCtx.sectionId}/${itemId}`;\n }, [accordionCtx?.accordionId, sectionCtx?.sectionId, itemId]);\n\n // Debug: warn if itemId changes (should be stable)\n const prevItemIdRef = useRef(itemId);\n useEffect(() => {\n if (prevItemIdRef.current !== itemId) {\n Logger.Warn(\n `Accordion: The uniqueId \"${itemId}\" in section \"${sectionCtx?.sectionId}\" has changed from \"${prevItemIdRef.current}\". ` +\n `Each item must have a unique, stable ID for pin/hide persistence to work correctly.`\n );\n }\n prevItemIdRef.current = itemId;\n }, [itemId, sectionCtx?.sectionId]);\n\n // Register item and detect duplicates\n useEffect(() => {\n if (!accordionCtx || !itemUniqueId) {\n return;\n }\n const { registeredItemIds } = accordionCtx;\n if (registeredItemIds.has(itemUniqueId)) {\n Logger.Warn(\n `Accordion: Duplicate uniqueId \"${itemId}\" detected in section \"${sectionCtx?.sectionId}\". ` +\n `Each item must have a unique ID within its section for pin/hide persistence to work correctly.`\n );\n }\n registeredItemIds.set(itemUniqueId, itemLabel ?? itemId);\n return () => {\n registeredItemIds.delete(itemUniqueId);\n };\n }, [accordionCtx, itemUniqueId, itemId, itemLabel, sectionCtx?.sectionId]);\n\n // If no context, static item, or nested, return undefined\n if (!accordionCtx || staticItem) {\n return undefined;\n }\n\n const { state, dispatch, features } = accordionCtx;\n const { pinnedIds, hiddenIds, searchTerm, editMode } = state;\n\n // Compute derived state\n const isPinned = features.pinning && pinnedIds.includes(itemUniqueId);\n const isHidden = features.hiding && hiddenIds.includes(itemUniqueId);\n const pinnedIndex = isPinned ? pinnedIds.indexOf(itemUniqueId) : -1;\n\n const canMoveUp = isPinned && pinnedIndex > 0;\n\n // Search matching\n const searchText = (itemLabel ?? itemId).toLowerCase();\n const isMatch = !features.search || !searchTerm || searchText.includes(searchTerm.toLowerCase());\n\n return {\n itemUniqueId,\n isNested,\n isPinned,\n isHidden,\n isMatch,\n pinnedIndex,\n canMoveUp,\n inEditMode: editMode,\n actions: {\n togglePinned: () => dispatch({ type: \"TOGGLE_PINNED\", itemId: itemUniqueId }),\n toggleHidden: () => dispatch({ type: \"TOGGLE_HIDDEN\", itemId: itemUniqueId }),\n movePinnedUp: () => dispatch({ type: \"MOVE_PINNED_UP\", itemId: itemUniqueId }),\n },\n };\n}\n/**\n * Hook to determine if a section is empty (has no visible items).\n * Derived from accordion state + globally registered items filtered by section prefix.\n *\n * @param sectionId - The section ID to check.\n * @returns Whether the section has no visible items.\n */\nfunction useIsSectionEmpty(sectionId: string): boolean {\n const accordionCtx = useContext(AccordionContext);\n\n if (!accordionCtx) {\n return false;\n }\n\n const { state, features, accordionId, registeredItemIds } = accordionCtx;\n const { pinnedIds, hiddenIds, searchTerm, editMode } = state;\n\n if (editMode) {\n return false;\n }\n\n const sectionPrefix = `${accordionId}/${sectionId}/`;\n let hasItems = false;\n\n for (const [itemId, itemLabel] of registeredItemIds) {\n if (!itemId.startsWith(sectionPrefix)) {\n continue;\n }\n hasItems = true;\n const isPinned = features.pinning && pinnedIds.includes(itemId);\n const isHidden = features.hiding && hiddenIds.includes(itemId);\n const searchText = (itemLabel || itemId).toLowerCase();\n const isMatch = !features.search || !searchTerm || searchText.includes(searchTerm.toLowerCase());\n if (!isPinned && !isHidden && isMatch) {\n return false;\n }\n }\n\n return hasItems;\n}\n"]}
1
+ {"version":3,"file":"accordion.contexts.js","sourceRoot":"","sources":["../../../../../dev/sharedUiComponents/src/fluent/primitives/accordion.contexts.tsx"],"names":[],"mappings":"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","sourcesContent":["import type { RefObject } from \"react\";\nimport type { AccordionProps, AccordionSectionBlockProps, AccordionSectionItemProps } from \"./accordion\";\nimport { createContext, useContext, useEffect, useMemo, useReducer, useRef } from \"react\";\nimport { DataStorage } from \"core/Misc/dataStorage\";\nimport { Logger } from \"core/Misc/logger\";\n\n// ============================================================================\n// Storage Helpers\n// ============================================================================\n\nconst STORAGE_KEY_ROOT = \"Babylon/Accordion\";\n\nconst ReadFromStorage = <T,>(path: string, initial: T): T => {\n try {\n const stored = DataStorage.ReadString(`${STORAGE_KEY_ROOT}/${path}`, \"\");\n return stored ? JSON.parse(stored) : initial;\n } catch {\n return initial;\n }\n};\n\nconst WriteToStorage = (path: string, data: unknown): void => {\n DataStorage.WriteString(`${STORAGE_KEY_ROOT}/${path}`, JSON.stringify(data));\n};\n\n// ============================================================================\n// State Types\n// ============================================================================\n\n/**\n * Immutable state for the Accordion.\n */\nexport type AccordionState = {\n /** IDs of pinned items (persisted to localStorage). */\n pinnedIds: string[];\n /** IDs of hidden items (persisted to localStorage). */\n hiddenIds: string[];\n /** Current search/filter term. */\n searchTerm: string;\n /** Whether edit mode is active (shows pin/hide controls). */\n editMode: boolean;\n};\n\n/**\n * Actions that can be dispatched to update accordion state.\n */\nexport type AccordionAction =\n | { type: \"SET_SEARCH_TERM\"; term: string }\n | { type: \"SET_EDIT_MODE\"; enabled: boolean }\n | { type: \"TOGGLE_PINNED\"; itemId: string }\n | { type: \"TOGGLE_HIDDEN\"; itemId: string }\n | { type: \"MOVE_PINNED_UP\"; itemId: string }\n | { type: \"REMOVE_STALE_IDS\"; activeIds: Set<string> }\n | { type: \"SHOW_ALL\" }\n | { type: \"HIDE_ALL_VISIBLE\"; visibleItemIds: string[] };\n\n/**\n * Feature flags for the Accordion (immutable after initialization).\n */\nexport type AccordionFeatures = {\n /** Whether pinning is enabled. */\n pinning: boolean;\n /** Whether hiding is enabled. */\n hiding: boolean;\n /** Whether search is enabled. */\n search: boolean;\n};\n\n// ============================================================================\n// Reducer\n// ============================================================================\n\nconst AccordionReducer = (state: AccordionState, action: AccordionAction): AccordionState => {\n switch (action.type) {\n case \"SET_SEARCH_TERM\":\n return { ...state, searchTerm: action.term };\n\n case \"SET_EDIT_MODE\":\n return { ...state, editMode: action.enabled };\n\n case \"TOGGLE_PINNED\": {\n const isPinned = state.pinnedIds.includes(action.itemId);\n return {\n ...state,\n pinnedIds: isPinned ? state.pinnedIds.filter((id) => id !== action.itemId) : [...state.pinnedIds, action.itemId],\n };\n }\n\n case \"TOGGLE_HIDDEN\": {\n const isHidden = state.hiddenIds.includes(action.itemId);\n return {\n ...state,\n hiddenIds: isHidden ? state.hiddenIds.filter((id) => id !== action.itemId) : [...state.hiddenIds, action.itemId],\n };\n }\n\n case \"MOVE_PINNED_UP\": {\n const index = state.pinnedIds.indexOf(action.itemId);\n if (index <= 0) {\n return state;\n }\n const newPinnedIds = [...state.pinnedIds];\n [newPinnedIds[index - 1], newPinnedIds[index]] = [newPinnedIds[index], newPinnedIds[index - 1]];\n return { ...state, pinnedIds: newPinnedIds };\n }\n\n case \"REMOVE_STALE_IDS\": {\n const pinnedIds = state.pinnedIds.filter((id) => action.activeIds.has(id));\n const hiddenIds = state.hiddenIds.filter((id) => action.activeIds.has(id));\n if (pinnedIds.length === state.pinnedIds.length && hiddenIds.length === state.hiddenIds.length) {\n return state;\n }\n return { ...state, pinnedIds, hiddenIds };\n }\n\n case \"SHOW_ALL\":\n return { ...state, hiddenIds: [] };\n\n case \"HIDE_ALL_VISIBLE\":\n return {\n ...state,\n hiddenIds: [...new Set([...state.hiddenIds, ...action.visibleItemIds])],\n };\n\n default:\n return state;\n }\n};\n\n// ============================================================================\n// Accordion Context\n// ============================================================================\n\n/**\n * Context value for the Accordion component.\n */\nexport type AccordionContextValue = {\n /** The unique ID of the Accordion instance. */\n accordionId: string;\n /** State for the Accordion, managed via dispatch function. */\n state: AccordionState;\n /** Dispatch function to update state. */\n dispatch: React.Dispatch<AccordionAction>;\n /** Feature flags. */\n features: AccordionFeatures;\n /** Ref for the pinned items portal container. */\n pinnedContainerRef: RefObject<HTMLDivElement>;\n /** Map of registered item IDs to labels (for duplicate detection and section empty checks). */\n registeredItemIds: Map<string, string>;\n};\n\nexport const AccordionContext = createContext<AccordionContextValue | undefined>(undefined);\n\n/**\n * Hook to create and manage the AccordionContext value.\n *\n * @param props - AccordionProps\n * @returns AccordionContextValue, or undefined if no features are enabled or no uniqueId is provided.\n */\nexport function useAccordionContext(props: AccordionProps): AccordionContextValue | undefined {\n const { uniqueId: accordionId, enablePinnedItems, enableHiddenItems, enableSearchItems } = props;\n\n const features: AccordionFeatures = useMemo(\n () => ({\n pinning: enablePinnedItems ?? false,\n hiding: enableHiddenItems ?? false,\n search: enableSearchItems ?? false,\n }),\n [enablePinnedItems, enableHiddenItems, enableSearchItems]\n );\n\n const hasFeatures = features.pinning || features.hiding || features.search;\n\n // Initialize state from localStorage\n const initialState = useMemo((): AccordionState => {\n if (!accordionId || !hasFeatures) {\n return { pinnedIds: [], hiddenIds: [], searchTerm: \"\", editMode: false };\n }\n return {\n pinnedIds: features.pinning ? ReadFromStorage<string[]>(`Pinned/${accordionId}`, []) : [],\n hiddenIds: features.hiding ? ReadFromStorage<string[]>(`Hidden/${accordionId}`, []) : [],\n searchTerm: \"\",\n editMode: false,\n };\n }, [accordionId, hasFeatures, features.pinning, features.hiding]);\n\n const [state, dispatch] = useReducer(AccordionReducer, initialState);\n\n const pinnedContainerRef = useRef<HTMLDivElement>(null);\n const registeredItemIds = useRef<Map<string, string>>(new Map());\n\n // Persist pinnedIds to localStorage when they change\n useEffect(() => {\n if (accordionId && features.pinning) {\n WriteToStorage(`Pinned/${accordionId}`, state.pinnedIds);\n }\n }, [accordionId, features.pinning, state.pinnedIds]);\n\n // Persist hiddenIds to localStorage when they change\n useEffect(() => {\n if (accordionId && features.hiding) {\n WriteToStorage(`Hidden/${accordionId}`, state.hiddenIds);\n }\n }, [accordionId, features.hiding, state.hiddenIds]);\n\n // Clean stale IDs from localStorage on mount. Parent effects run after\n // children's, so all items will have registered by the time this fires.\n useEffect(() => {\n if (accordionId && hasFeatures) {\n dispatch({ type: \"REMOVE_STALE_IDS\", activeIds: new Set(registeredItemIds.current.keys()) });\n }\n }, [accordionId, hasFeatures]);\n\n // Return undefined if no accordionId or no features enabled\n if (!accordionId || !hasFeatures) {\n return undefined;\n }\n\n return {\n accordionId,\n state,\n dispatch,\n features,\n pinnedContainerRef,\n registeredItemIds: registeredItemIds.current,\n };\n}\n\n// ============================================================================\n// Section Context\n// ============================================================================\n\n/**\n * Context value for an AccordionSectionBlock.\n */\nexport type AccordionSectionBlockContextValue = {\n /** The section ID. */\n sectionId: string;\n};\n\nexport const AccordionSectionBlockContext = createContext<AccordionSectionBlockContextValue | undefined>(undefined);\n\n/**\n * Hook to create the AccordionSectionBlockContext value.\n *\n * @param props - AccordionSectionBlockProps\n * @returns AccordionSectionBlockContextValue and isEmpty state\n */\nexport function useAccordionSectionBlockContext(props: AccordionSectionBlockProps): {\n context: AccordionSectionBlockContextValue;\n isEmpty: boolean;\n} {\n const { sectionId } = props;\n const context = useMemo(() => ({ sectionId }), [sectionId]);\n const isEmpty = useIsSectionEmpty(sectionId);\n\n return { context, isEmpty };\n}\n\n// ============================================================================\n// Item Depth Context (to detect nested AccordionSectionItems)\n// ============================================================================\n\n/**\n * Context to track whether we're inside an AccordionSectionItem.\n * Used to prevent nested items from being individually manageable.\n */\nexport const AccordionItemDepthContext = createContext<boolean>(false);\n\n// ============================================================================\n// Item Context Hook\n// ============================================================================\n\n/**\n * Derived item state, computed from the accordion state during render.\n */\nexport type AccordionItemState = {\n /** The globally unique item ID. */\n itemUniqueId: string;\n /** Whether this item is nested inside another AccordionSectionItem. */\n isNested: boolean;\n /** Whether this item is pinned. */\n isPinned: boolean;\n /** Whether this item is hidden. */\n isHidden: boolean;\n /** Whether this item matches the current search term. */\n isMatch: boolean;\n /** The index of this item in the pinned list (for ordering). */\n pinnedIndex: number;\n /** Whether this pinned item can be moved up (is not first in the pinned list). */\n canMoveUp: boolean;\n /** Whether edit mode is active. */\n inEditMode: boolean;\n /** Callbacks to modify state. */\n actions: {\n togglePinned: () => void;\n toggleHidden: () => void;\n movePinnedUp: () => void;\n };\n};\n\n/**\n * Hook to compute item state from accordion context.\n *\n * @param props - AccordionSectionItemProps\n * @returns AccordionItemState, or undefined if no accordion context or nested item.\n */\nexport function useAccordionSectionItemState(props: AccordionSectionItemProps): AccordionItemState | undefined {\n const { uniqueId: itemId, label: itemLabel, staticItem } = props;\n\n const accordionCtx = useContext(AccordionContext);\n const sectionCtx = useContext(AccordionSectionBlockContext);\n const isNested = useContext(AccordionItemDepthContext);\n\n // Build the globally unique item ID\n const itemUniqueId = useMemo(() => {\n if (!accordionCtx || !sectionCtx) {\n return \"\";\n }\n return `${accordionCtx.accordionId}/${sectionCtx.sectionId}/${itemId}`;\n }, [accordionCtx?.accordionId, sectionCtx?.sectionId, itemId]);\n\n // Debug: warn if itemId changes (should be stable)\n const prevItemIdRef = useRef(itemId);\n useEffect(() => {\n if (prevItemIdRef.current !== itemId) {\n Logger.Warn(\n `Accordion: The uniqueId \"${itemId}\" in section \"${sectionCtx?.sectionId}\" has changed from \"${prevItemIdRef.current}\". ` +\n `Each item must have a unique, stable ID for pin/hide persistence to work correctly.`\n );\n }\n prevItemIdRef.current = itemId;\n }, [itemId, sectionCtx?.sectionId]);\n\n // Register item and detect duplicates (skip nested items, as children of other AccordionSectionItem should not participate in pin/hide/search).\n useEffect(() => {\n if (!accordionCtx || !itemUniqueId || isNested) {\n return;\n }\n const { registeredItemIds } = accordionCtx;\n if (registeredItemIds.has(itemUniqueId)) {\n Logger.Warn(\n `Accordion: Duplicate uniqueId \"${itemId}\" detected in section \"${sectionCtx?.sectionId}\". ` +\n `Each item must have a unique ID within its section for pin/hide persistence to work correctly.`\n );\n }\n registeredItemIds.set(itemUniqueId, itemLabel ?? itemId);\n return () => {\n registeredItemIds.delete(itemUniqueId);\n };\n }, [accordionCtx, itemUniqueId, itemId, itemLabel, sectionCtx?.sectionId, isNested]);\n\n // If no context, static item, or nested, return undefined\n if (!accordionCtx || staticItem) {\n return undefined;\n }\n\n const { state, dispatch, features } = accordionCtx;\n const { pinnedIds, hiddenIds, searchTerm, editMode } = state;\n\n // Compute derived state\n const isPinned = features.pinning && pinnedIds.includes(itemUniqueId);\n const isHidden = features.hiding && hiddenIds.includes(itemUniqueId);\n const pinnedIndex = isPinned ? pinnedIds.indexOf(itemUniqueId) : -1;\n\n const canMoveUp = isPinned && pinnedIndex > 0;\n\n // Search matching\n const searchText = (itemLabel ?? itemId).toLowerCase();\n const isMatch = !features.search || !searchTerm || searchText.includes(searchTerm.toLowerCase());\n\n return {\n itemUniqueId,\n isNested,\n isPinned,\n isHidden,\n isMatch,\n pinnedIndex,\n canMoveUp,\n inEditMode: editMode,\n actions: {\n togglePinned: () => dispatch({ type: \"TOGGLE_PINNED\", itemId: itemUniqueId }),\n toggleHidden: () => dispatch({ type: \"TOGGLE_HIDDEN\", itemId: itemUniqueId }),\n movePinnedUp: () => dispatch({ type: \"MOVE_PINNED_UP\", itemId: itemUniqueId }),\n },\n };\n}\n/**\n * Hook to determine if a section is empty (has no visible items).\n * Derived from accordion state + globally registered items filtered by section prefix.\n *\n * @param sectionId - The section ID to check.\n * @returns Whether the section has no visible items.\n */\nfunction useIsSectionEmpty(sectionId: string): boolean {\n const accordionCtx = useContext(AccordionContext);\n\n if (!accordionCtx) {\n return false;\n }\n\n const { state, features, accordionId, registeredItemIds } = accordionCtx;\n const { pinnedIds, hiddenIds, searchTerm, editMode } = state;\n\n if (editMode) {\n return false;\n }\n\n const sectionPrefix = `${accordionId}/${sectionId}/`;\n let hasItems = false;\n\n for (const [itemId, itemLabel] of registeredItemIds) {\n if (!itemId.startsWith(sectionPrefix)) {\n continue;\n }\n hasItems = true;\n const isPinned = features.pinning && pinnedIds.includes(itemId);\n const isHidden = features.hiding && hiddenIds.includes(itemId);\n const searchText = (itemLabel || itemId).toLowerCase();\n const isMatch = !features.search || !searchTerm || searchText.includes(searchTerm.toLowerCase());\n if (!isPinned && !isHidden && isMatch) {\n return false;\n }\n }\n\n return hasItems;\n}\n"]}
@@ -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"]}
@@ -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);