@imfusion/sdk-react 0.7.0-dev.31.g0ba92924

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.
Files changed (42) hide show
  1. package/README.md +51 -0
  2. package/dist/ImFusionCanvas.d.ts +26 -0
  3. package/dist/ImFusionCanvas.js +51 -0
  4. package/dist/ImFusionError.d.ts +37 -0
  5. package/dist/ImFusionError.js +41 -0
  6. package/dist/ImFusionLoading.d.ts +25 -0
  7. package/dist/ImFusionLoading.js +30 -0
  8. package/dist/ImFusionProvider.d.ts +56 -0
  9. package/dist/ImFusionProvider.js +88 -0
  10. package/dist/ImFusionReady.d.ts +25 -0
  11. package/dist/ImFusionReady.js +30 -0
  12. package/dist/context.d.ts +14 -0
  13. package/dist/context.js +3 -0
  14. package/dist/index.d.ts +17 -0
  15. package/dist/index.js +17 -0
  16. package/dist/useAnnotations.d.ts +33 -0
  17. package/dist/useAnnotations.js +67 -0
  18. package/dist/useContextMenu.d.ts +21 -0
  19. package/dist/useContextMenu.js +30 -0
  20. package/dist/useData.d.ts +26 -0
  21. package/dist/useData.js +40 -0
  22. package/dist/useDataLoader.d.ts +50 -0
  23. package/dist/useDataLoader.js +70 -0
  24. package/dist/useDisplayOptions.d.ts +59 -0
  25. package/dist/useDisplayOptions.js +87 -0
  26. package/dist/useFrameSelection.d.ts +32 -0
  27. package/dist/useFrameSelection.js +43 -0
  28. package/dist/useImFusion.d.ts +22 -0
  29. package/dist/useImFusion.js +32 -0
  30. package/dist/useImFusionError.d.ts +21 -0
  31. package/dist/useImFusionError.js +32 -0
  32. package/dist/useImFusionState.d.ts +31 -0
  33. package/dist/useImFusionState.js +32 -0
  34. package/dist/useLayoutMode.d.ts +29 -0
  35. package/dist/useLayoutMode.js +38 -0
  36. package/dist/useStableHandle.d.ts +15 -0
  37. package/dist/useStableHandle.js +12 -0
  38. package/dist/useViewVisibility.d.ts +30 -0
  39. package/dist/useViewVisibility.js +45 -0
  40. package/dist/useVisibleData.d.ts +21 -0
  41. package/dist/useVisibleData.js +32 -0
  42. package/package.json +51 -0
package/README.md ADDED
@@ -0,0 +1,51 @@
1
+ # ImFusion Web SDK React Bindings
2
+ React components and hooks for integrating the ImFusion Web SDK into React applications.
3
+
4
+ ## Getting Started
5
+
6
+ Wrap your app with `ImFusionProvider`, add a canvas, and use the lifecycle components:
7
+
8
+ ```tsx
9
+ import {
10
+ ImFusionProvider,
11
+ ImFusionCanvas,
12
+ ImFusionReady,
13
+ ImFusionLoading,
14
+ ImFusionError,
15
+ } from '@imfusion/sdk-react';
16
+
17
+ function App() {
18
+ return (
19
+ <ImFusionProvider>
20
+ <ImFusionCanvas style={{ width: '100%', height: '100%' }} />
21
+ <ImFusionLoading>Initializing SDK...</ImFusionLoading>
22
+ <ImFusionError>{error => <p>Failed: {error.message}</p>}</ImFusionError>
23
+ <ImFusionReady>
24
+ <Viewer />
25
+ </ImFusionReady>
26
+ </ImFusionProvider>
27
+ );
28
+ }
29
+ ```
30
+
31
+ Inside `ImFusionReady`, use hooks to interact with the SDK:
32
+
33
+ ```tsx
34
+ import { useImFusion, useDataLoader, useData } from '@imfusion/sdk-react';
35
+
36
+ function Viewer() {
37
+ const imf = useImFusion();
38
+ const { loadUrl, isLoading, progress } = useDataLoader();
39
+ const datasets = useData();
40
+
41
+ return (
42
+ <div>
43
+ <button onClick={() => loadUrl('/data/scan.nii.gz')} disabled={isLoading}>
44
+ {isLoading ? 'Loading...' : 'Load'}
45
+ </button>
46
+ <p>{datasets.length} dataset(s) loaded</p>
47
+ </div>
48
+ );
49
+ }
50
+ ```
51
+
@@ -0,0 +1,26 @@
1
+ import { type CanvasHTMLAttributes } from 'react';
2
+ /**
3
+ * Renders a canvas element and registers it with the {@link ImFusionProvider}.
4
+ *
5
+ * When this component mounts, it triggers SDK initialization by registering
6
+ * the canvas element with the parent provider. All standard HTML canvas
7
+ * attributes are passed through to the underlying `<canvas>` element.
8
+ *
9
+ * @example
10
+ * ```tsx
11
+ * <ImFusionProvider>
12
+ * <ImFusionCanvas
13
+ * style={{ width: '100%', height: '100vh' }}
14
+ * className="viewer-canvas"
15
+ * />
16
+ * </ImFusionProvider>
17
+ * ```
18
+ *
19
+ * @example
20
+ * ```tsx
21
+ * // Accessing the canvas element via ref
22
+ * const canvasRef = useRef<HTMLCanvasElement>(null);
23
+ * <ImFusionCanvas ref={canvasRef} width={800} height={600} />
24
+ * ```
25
+ */
26
+ export declare const ImFusionCanvas: import("react").ForwardRefExoticComponent<CanvasHTMLAttributes<HTMLCanvasElement> & import("react").RefAttributes<HTMLCanvasElement>>;
@@ -0,0 +1,51 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useContext, useEffect, useRef, forwardRef } from 'react';
3
+ import { ImFusionContext } from './context.js';
4
+ /**
5
+ * Renders a canvas element and registers it with the {@link ImFusionProvider}.
6
+ *
7
+ * When this component mounts, it triggers SDK initialization by registering
8
+ * the canvas element with the parent provider. All standard HTML canvas
9
+ * attributes are passed through to the underlying `<canvas>` element.
10
+ *
11
+ * @example
12
+ * ```tsx
13
+ * <ImFusionProvider>
14
+ * <ImFusionCanvas
15
+ * style={{ width: '100%', height: '100vh' }}
16
+ * className="viewer-canvas"
17
+ * />
18
+ * </ImFusionProvider>
19
+ * ```
20
+ *
21
+ * @example
22
+ * ```tsx
23
+ * // Accessing the canvas element via ref
24
+ * const canvasRef = useRef<HTMLCanvasElement>(null);
25
+ * <ImFusionCanvas ref={canvasRef} width={800} height={600} />
26
+ * ```
27
+ */
28
+ export const ImFusionCanvas = forwardRef(function ImFusionCanvas(props, forwardedRef) {
29
+ const context = useContext(ImFusionContext);
30
+ const internalRef = useRef(null);
31
+ if (!context) {
32
+ throw new Error('ImFusionCanvas must be used within an ImFusionProvider');
33
+ }
34
+ useEffect(() => {
35
+ const canvas = internalRef.current;
36
+ if (canvas) {
37
+ context.registerCanvas(canvas);
38
+ }
39
+ }, [context.registerCanvas]);
40
+ // Merge the internal ref with the forwarded ref
41
+ const setRefs = (element) => {
42
+ internalRef.current = element;
43
+ if (typeof forwardedRef === 'function') {
44
+ forwardedRef(element);
45
+ }
46
+ else if (forwardedRef) {
47
+ forwardedRef.current = element;
48
+ }
49
+ };
50
+ return _jsx("canvas", { ref: setRefs, ...props });
51
+ });
@@ -0,0 +1,37 @@
1
+ import { type ReactNode } from 'react';
2
+ export interface ImFusionErrorProps {
3
+ /** Content to render when an initialization error has occurred. */
4
+ children: ReactNode;
5
+ }
6
+ /**
7
+ * Renders children when SDK initialization has failed.
8
+ *
9
+ * Use {@link useImFusionError} inside children to access the error object.
10
+ *
11
+ * @example
12
+ * ```tsx
13
+ * <ImFusionProvider>
14
+ * <ImFusionCanvas />
15
+ * <ImFusionLoading>
16
+ * <Spinner />
17
+ * </ImFusionLoading>
18
+ * <ImFusionError>
19
+ * <ErrorDisplay />
20
+ * </ImFusionError>
21
+ * <ImFusionReady>
22
+ * <App />
23
+ * </ImFusionReady>
24
+ * </ImFusionProvider>
25
+ * ```
26
+ *
27
+ * ```tsx
28
+ * function ErrorDisplay() {
29
+ * const error = useImFusionError();
30
+ * return <p>Failed to initialize: {error.message}</p>;
31
+ * }
32
+ * ```
33
+ *
34
+ * @see {@link useImFusionError}
35
+ * @see {@link useImFusionState}
36
+ */
37
+ export declare function ImFusionError({ children }: ImFusionErrorProps): import("react/jsx-runtime").JSX.Element | null;
@@ -0,0 +1,41 @@
1
+ import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
2
+ import { useContext } from 'react';
3
+ import { ImFusionContext } from './context.js';
4
+ /**
5
+ * Renders children when SDK initialization has failed.
6
+ *
7
+ * Use {@link useImFusionError} inside children to access the error object.
8
+ *
9
+ * @example
10
+ * ```tsx
11
+ * <ImFusionProvider>
12
+ * <ImFusionCanvas />
13
+ * <ImFusionLoading>
14
+ * <Spinner />
15
+ * </ImFusionLoading>
16
+ * <ImFusionError>
17
+ * <ErrorDisplay />
18
+ * </ImFusionError>
19
+ * <ImFusionReady>
20
+ * <App />
21
+ * </ImFusionReady>
22
+ * </ImFusionProvider>
23
+ * ```
24
+ *
25
+ * ```tsx
26
+ * function ErrorDisplay() {
27
+ * const error = useImFusionError();
28
+ * return <p>Failed to initialize: {error.message}</p>;
29
+ * }
30
+ * ```
31
+ *
32
+ * @see {@link useImFusionError}
33
+ * @see {@link useImFusionState}
34
+ */
35
+ export function ImFusionError({ children }) {
36
+ const context = useContext(ImFusionContext);
37
+ if (!context) {
38
+ throw new Error('ImFusionError must be used within an ImFusionProvider');
39
+ }
40
+ return context.error ? _jsx(_Fragment, { children: children }) : null;
41
+ }
@@ -0,0 +1,25 @@
1
+ import { type ReactNode } from 'react';
2
+ export interface ImFusionLoadingProps {
3
+ children: ReactNode;
4
+ }
5
+ /**
6
+ * Renders children while the SDK is initializing.
7
+ *
8
+ * Automatically hides when the SDK becomes ready or when an error occurs.
9
+ *
10
+ * @example
11
+ * ```tsx
12
+ * <ImFusionProvider>
13
+ * <ImFusionCanvas />
14
+ * <ImFusionLoading>
15
+ * <div className="spinner">Loading SDK...</div>
16
+ * </ImFusionLoading>
17
+ * <ImFusionReady>
18
+ * <App />
19
+ * </ImFusionReady>
20
+ * </ImFusionProvider>
21
+ * ```
22
+ *
23
+ * @see {@link useImFusionState}
24
+ */
25
+ export declare function ImFusionLoading({ children }: ImFusionLoadingProps): import("react/jsx-runtime").JSX.Element | null;
@@ -0,0 +1,30 @@
1
+ import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
2
+ import { useContext } from 'react';
3
+ import { ImFusionContext } from './context.js';
4
+ /**
5
+ * Renders children while the SDK is initializing.
6
+ *
7
+ * Automatically hides when the SDK becomes ready or when an error occurs.
8
+ *
9
+ * @example
10
+ * ```tsx
11
+ * <ImFusionProvider>
12
+ * <ImFusionCanvas />
13
+ * <ImFusionLoading>
14
+ * <div className="spinner">Loading SDK...</div>
15
+ * </ImFusionLoading>
16
+ * <ImFusionReady>
17
+ * <App />
18
+ * </ImFusionReady>
19
+ * </ImFusionProvider>
20
+ * ```
21
+ *
22
+ * @see {@link useImFusionState}
23
+ */
24
+ export function ImFusionLoading({ children }) {
25
+ const context = useContext(ImFusionContext);
26
+ if (!context) {
27
+ throw new Error('ImFusionLoading must be used within an ImFusionProvider');
28
+ }
29
+ return !context.imf && !context.error ? _jsx(_Fragment, { children: children }) : null;
30
+ }
@@ -0,0 +1,56 @@
1
+ import { type ReactNode } from 'react';
2
+ import { type ImFusion, type InitOptions } from '@imfusion/sdk';
3
+ export interface ImFusionProviderProps {
4
+ /**
5
+ * Options passed to {@link @imfusion/sdk!init | init} when the SDK is initialized.
6
+ *
7
+ * @see {@link @imfusion/sdk!InitOptions | InitOptions}
8
+ */
9
+ options?: InitOptions;
10
+ /**
11
+ * Called after the SDK has been initialized successfully.
12
+ */
13
+ onReady?: (imf: ImFusion) => void;
14
+ /**
15
+ * Called if SDK initialization fails.
16
+ */
17
+ onError?: (error: Error) => void;
18
+ /**
19
+ * Content that should have access to the ImFusion SDK.
20
+ *
21
+ * Render an {@link ImFusionCanvas} somewhere inside this provider so the SDK
22
+ * has a canvas to initialize against.
23
+ */
24
+ children: ReactNode;
25
+ }
26
+ /**
27
+ * Initializes the ImFusion SDK and provides it to descendant components.
28
+ *
29
+ * Place an {@link ImFusionCanvas} inside this provider to trigger initialization.
30
+ * Children are rendered immediately; use the state components to show different
31
+ * UI for each initialization phase.
32
+ *
33
+ * @example
34
+ * ```tsx
35
+ * <ImFusionProvider options={{ autoResize: true }}>
36
+ * <ImFusionCanvas style={{ width: '100%', height: '100vh' }} />
37
+ *
38
+ * <ImFusionLoading>
39
+ * <Spinner />
40
+ * </ImFusionLoading>
41
+ *
42
+ * <ImFusionError>
43
+ * <ErrorMessage />
44
+ * </ImFusionError>
45
+ *
46
+ * <ImFusionReady>
47
+ * <MyApp />
48
+ * </ImFusionReady>
49
+ * </ImFusionProvider>
50
+ * ```
51
+ *
52
+ * @remarks
53
+ * The SDK is initialized once per provider instance. Changes to `options` do
54
+ * not reconfigure an already initialized SDK.
55
+ */
56
+ export declare function ImFusionProvider({ options, onReady, onError, children }: ImFusionProviderProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,88 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useState, useEffect, useRef, useCallback, useMemo } from 'react';
3
+ import { init } from '@imfusion/sdk';
4
+ import { ImFusionContext } from './context.js';
5
+ /**
6
+ * Initializes the ImFusion SDK and provides it to descendant components.
7
+ *
8
+ * Place an {@link ImFusionCanvas} inside this provider to trigger initialization.
9
+ * Children are rendered immediately; use the state components to show different
10
+ * UI for each initialization phase.
11
+ *
12
+ * @example
13
+ * ```tsx
14
+ * <ImFusionProvider options={{ autoResize: true }}>
15
+ * <ImFusionCanvas style={{ width: '100%', height: '100vh' }} />
16
+ *
17
+ * <ImFusionLoading>
18
+ * <Spinner />
19
+ * </ImFusionLoading>
20
+ *
21
+ * <ImFusionError>
22
+ * <ErrorMessage />
23
+ * </ImFusionError>
24
+ *
25
+ * <ImFusionReady>
26
+ * <MyApp />
27
+ * </ImFusionReady>
28
+ * </ImFusionProvider>
29
+ * ```
30
+ *
31
+ * @remarks
32
+ * The SDK is initialized once per provider instance. Changes to `options` do
33
+ * not reconfigure an already initialized SDK.
34
+ */
35
+ export function ImFusionProvider({ options, onReady, onError, children }) {
36
+ const [canvas, setCanvas] = useState(null);
37
+ const [imf, setImf] = useState(null);
38
+ const [error, setError] = useState(null);
39
+ // Capture init promise to prevent double WASM creation in StrictMode
40
+ const initPromiseRef = useRef(null);
41
+ // Capture the first canvas; warn if a different one tries to register
42
+ const initialCanvasRef = useRef(null);
43
+ // Capture initial options only (never updated)
44
+ const optionsRef = useRef(options);
45
+ const onReadyRef = useRef(onReady);
46
+ const onErrorRef = useRef(onError);
47
+ onReadyRef.current = onReady;
48
+ onErrorRef.current = onError;
49
+ const registerCanvas = useCallback((canvasElement) => {
50
+ if (initialCanvasRef.current) {
51
+ if (initialCanvasRef.current !== canvasElement) {
52
+ console.warn('ImFusionProvider: A different canvas element was registered after SDK initialization. ' +
53
+ 'The SDK remains bound to the original canvas.');
54
+ }
55
+ return;
56
+ }
57
+ initialCanvasRef.current = canvasElement;
58
+ setCanvas(canvasElement);
59
+ }, []);
60
+ useEffect(() => {
61
+ if (!canvas)
62
+ return;
63
+ let cancelled = false;
64
+ if (!initPromiseRef.current) {
65
+ initPromiseRef.current = init(canvas, optionsRef.current);
66
+ }
67
+ initPromiseRef.current
68
+ .then((instance) => {
69
+ if (!cancelled) {
70
+ setImf(instance);
71
+ onReadyRef.current?.(instance);
72
+ }
73
+ })
74
+ .catch((err) => {
75
+ if (!cancelled) {
76
+ const error = err instanceof Error ? err : new Error(String(err));
77
+ setError(error);
78
+ onErrorRef.current?.(error);
79
+ }
80
+ });
81
+ return () => {
82
+ cancelled = true;
83
+ initPromiseRef.current?.then((instance) => instance.destroy());
84
+ };
85
+ }, [canvas]);
86
+ const contextValue = useMemo(() => ({ imf, error, registerCanvas }), [imf, error, registerCanvas]);
87
+ return _jsx(ImFusionContext.Provider, { value: contextValue, children: children });
88
+ }
@@ -0,0 +1,25 @@
1
+ import { type ReactNode } from 'react';
2
+ export interface ImFusionReadyProps {
3
+ children: ReactNode;
4
+ }
5
+ /**
6
+ * Renders children only after the SDK has successfully initialized.
7
+ *
8
+ * Components inside can safely call {@link useImFusion}. Multiple instances
9
+ * can be placed independently throughout the tree.
10
+ *
11
+ * @example
12
+ * ```tsx
13
+ * <ImFusionProvider>
14
+ * <ImFusionCanvas />
15
+ * <ImFusionReady>
16
+ * <Sidebar />
17
+ * <Toolbar />
18
+ * </ImFusionReady>
19
+ * </ImFusionProvider>
20
+ * ```
21
+ *
22
+ * @see {@link useImFusion}
23
+ * @see {@link useImFusionState}
24
+ */
25
+ export declare function ImFusionReady({ children }: ImFusionReadyProps): import("react/jsx-runtime").JSX.Element | null;
@@ -0,0 +1,30 @@
1
+ import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
2
+ import { useContext } from 'react';
3
+ import { ImFusionContext } from './context.js';
4
+ /**
5
+ * Renders children only after the SDK has successfully initialized.
6
+ *
7
+ * Components inside can safely call {@link useImFusion}. Multiple instances
8
+ * can be placed independently throughout the tree.
9
+ *
10
+ * @example
11
+ * ```tsx
12
+ * <ImFusionProvider>
13
+ * <ImFusionCanvas />
14
+ * <ImFusionReady>
15
+ * <Sidebar />
16
+ * <Toolbar />
17
+ * </ImFusionReady>
18
+ * </ImFusionProvider>
19
+ * ```
20
+ *
21
+ * @see {@link useImFusion}
22
+ * @see {@link useImFusionState}
23
+ */
24
+ export function ImFusionReady({ children }) {
25
+ const context = useContext(ImFusionContext);
26
+ if (!context) {
27
+ throw new Error('ImFusionReady must be used within an ImFusionProvider');
28
+ }
29
+ return context.imf ? _jsx(_Fragment, { children: children }) : null;
30
+ }
@@ -0,0 +1,14 @@
1
+ import type { ImFusion } from '@imfusion/sdk';
2
+ /**
3
+ * Internal context value shared between provider, canvas, and hooks.
4
+ *
5
+ * @internal Use {@link useImFusion},
6
+ * {@link useImFusionError}, or {@link useImFusionState} instead.
7
+ */
8
+ export interface ImFusionContextValue {
9
+ imf: ImFusion | null;
10
+ error: Error | null;
11
+ registerCanvas: (canvas: HTMLCanvasElement) => void;
12
+ }
13
+ /** @internal */
14
+ export declare const ImFusionContext: import("react").Context<ImFusionContextValue | null>;
@@ -0,0 +1,3 @@
1
+ import { createContext } from 'react';
2
+ /** @internal */
3
+ export const ImFusionContext = createContext(null);
@@ -0,0 +1,17 @@
1
+ export { ImFusionProvider, type ImFusionProviderProps } from './ImFusionProvider.js';
2
+ export { ImFusionCanvas } from './ImFusionCanvas.js';
3
+ export { ImFusionReady, type ImFusionReadyProps } from './ImFusionReady.js';
4
+ export { ImFusionLoading, type ImFusionLoadingProps } from './ImFusionLoading.js';
5
+ export { ImFusionError, type ImFusionErrorProps } from './ImFusionError.js';
6
+ export { useImFusion } from './useImFusion.js';
7
+ export { useImFusionError } from './useImFusionError.js';
8
+ export { useImFusionState, type ImFusionState } from './useImFusionState.js';
9
+ export { useData } from './useData.js';
10
+ export { useVisibleData } from './useVisibleData.js';
11
+ export { useDataLoader, type DataLoaderProgress } from './useDataLoader.js';
12
+ export { useAnnotations } from './useAnnotations.js';
13
+ export { useLayoutMode } from './useLayoutMode.js';
14
+ export { useViewVisibility } from './useViewVisibility.js';
15
+ export { useDisplayOptions2d, useDisplayOptions3d } from './useDisplayOptions.js';
16
+ export { useFrameSelection } from './useFrameSelection.js';
17
+ export { useContextMenu } from './useContextMenu.js';
package/dist/index.js ADDED
@@ -0,0 +1,17 @@
1
+ export { ImFusionProvider } from './ImFusionProvider.js';
2
+ export { ImFusionCanvas } from './ImFusionCanvas.js';
3
+ export { ImFusionReady } from './ImFusionReady.js';
4
+ export { ImFusionLoading } from './ImFusionLoading.js';
5
+ export { ImFusionError } from './ImFusionError.js';
6
+ export { useImFusion } from './useImFusion.js';
7
+ export { useImFusionError } from './useImFusionError.js';
8
+ export { useImFusionState } from './useImFusionState.js';
9
+ export { useData } from './useData.js';
10
+ export { useVisibleData } from './useVisibleData.js';
11
+ export { useDataLoader } from './useDataLoader.js';
12
+ export { useAnnotations } from './useAnnotations.js';
13
+ export { useLayoutMode } from './useLayoutMode.js';
14
+ export { useViewVisibility } from './useViewVisibility.js';
15
+ export { useDisplayOptions2d, useDisplayOptions3d } from './useDisplayOptions.js';
16
+ export { useFrameSelection } from './useFrameSelection.js';
17
+ export { useContextMenu } from './useContextMenu.js';
@@ -0,0 +1,33 @@
1
+ import type { Annotation, Data, HandleArray } from '@imfusion/sdk';
2
+ /**
3
+ * Returns a reactive list of annotations, optionally filtered by dataset.
4
+ *
5
+ * Subscribes to {@link @imfusion/sdk!AnnotationModel.onAnnotationAdded | AnnotationModel.onAnnotationAdded},
6
+ * {@link @imfusion/sdk!AnnotationModel.onAnnotationAboutToBeRemoved | AnnotationModel.onAnnotationAboutToBeRemoved}, and
7
+ * {@link @imfusion/sdk!AnnotationModel.onAnnotationEvent | AnnotationModel.onAnnotationEvent}.
8
+ *
9
+ * When `data` is provided, only annotations belonging to that dataset are
10
+ * returned.
11
+ *
12
+ * @param data - Optional dataset to filter annotations by
13
+ * @returns A {@link @imfusion/sdk!HandleArray | HandleArray} of annotations
14
+ * @throws If called outside of an {@link ImFusionProvider}
15
+ * @throws If called before the SDK has finished initializing
16
+ *
17
+ * @example
18
+ * ```tsx
19
+ * function AnnotationList() {
20
+ * const annotations = useAnnotations();
21
+ * return <p>Total annotations: {annotations.length}</p>;
22
+ * }
23
+ * ```
24
+ *
25
+ * @example
26
+ * ```tsx
27
+ * function DataAnnotations({ data }: { data: Data }) {
28
+ * const annotations = useAnnotations(data);
29
+ * return <p>Annotations on {data.name}: {annotations.length}</p>;
30
+ * }
31
+ * ```
32
+ */
33
+ export declare function useAnnotations(data?: Data): HandleArray<Annotation>;
@@ -0,0 +1,67 @@
1
+ import { useState, useEffect, useCallback } from 'react';
2
+ import { useImFusion } from './useImFusion.js';
3
+ import { useStableHandle } from './useStableHandle.js';
4
+ /**
5
+ * Returns a reactive list of annotations, optionally filtered by dataset.
6
+ *
7
+ * Subscribes to {@link @imfusion/sdk!AnnotationModel.onAnnotationAdded | AnnotationModel.onAnnotationAdded},
8
+ * {@link @imfusion/sdk!AnnotationModel.onAnnotationAboutToBeRemoved | AnnotationModel.onAnnotationAboutToBeRemoved}, and
9
+ * {@link @imfusion/sdk!AnnotationModel.onAnnotationEvent | AnnotationModel.onAnnotationEvent}.
10
+ *
11
+ * When `data` is provided, only annotations belonging to that dataset are
12
+ * returned.
13
+ *
14
+ * @param data - Optional dataset to filter annotations by
15
+ * @returns A {@link @imfusion/sdk!HandleArray | HandleArray} of annotations
16
+ * @throws If called outside of an {@link ImFusionProvider}
17
+ * @throws If called before the SDK has finished initializing
18
+ *
19
+ * @example
20
+ * ```tsx
21
+ * function AnnotationList() {
22
+ * const annotations = useAnnotations();
23
+ * return <p>Total annotations: {annotations.length}</p>;
24
+ * }
25
+ * ```
26
+ *
27
+ * @example
28
+ * ```tsx
29
+ * function DataAnnotations({ data }: { data: Data }) {
30
+ * const annotations = useAnnotations(data);
31
+ * return <p>Annotations on {data.name}: {annotations.length}</p>;
32
+ * }
33
+ * ```
34
+ */
35
+ export function useAnnotations(data) {
36
+ const imf = useImFusion();
37
+ const stableData = useStableHandle(data);
38
+ const readAnnotations = useCallback(() => {
39
+ return stableData ? imf.annotationModel.dataAnnotations(stableData) : imf.annotationModel.annotations();
40
+ }, [imf, stableData]);
41
+ const [annotations, setAnnotations] = useState(readAnnotations);
42
+ useEffect(() => {
43
+ setAnnotations(readAnnotations());
44
+ const refresh = () => {
45
+ setAnnotations((prev) => {
46
+ const next = readAnnotations();
47
+ if (prev.length !== next.length)
48
+ return next;
49
+ for (let i = 0; i < prev.length; i++) {
50
+ if (!prev[i].isAliasOf(next[i]))
51
+ return next;
52
+ }
53
+ return prev;
54
+ });
55
+ };
56
+ const deferredRefresh = () => queueMicrotask(refresh);
57
+ const offAdded = imf.annotationModel.onAnnotationAdded(refresh);
58
+ const offRemoved = imf.annotationModel.onAnnotationAboutToBeRemoved(deferredRefresh);
59
+ const offEvent = imf.annotationModel.onAnnotationEvent(refresh);
60
+ return () => {
61
+ offAdded();
62
+ offRemoved();
63
+ offEvent();
64
+ };
65
+ }, [imf, readAnnotations]);
66
+ return annotations;
67
+ }
@@ -0,0 +1,21 @@
1
+ import type { Menu } from '@imfusion/sdk';
2
+ /**
3
+ * Registers a context menu handler that fires when the SDK produces a
4
+ * context menu from an input event.
5
+ *
6
+ * @param callback - Receives the {@link @imfusion/sdk!Menu | Menu} and the DOM {@link Event} that triggered it
7
+ * @throws If called outside of an {@link ImFusionProvider}
8
+ * @throws If called before the SDK has finished initializing
9
+ *
10
+ * @example
11
+ * ```tsx
12
+ * function ContextMenuHandler() {
13
+ * useContextMenu((menu, event) => {
14
+ * event.preventDefault();
15
+ * // Build a custom menu UI from menu.items()
16
+ * });
17
+ * return null;
18
+ * }
19
+ * ```
20
+ */
21
+ export declare function useContextMenu(callback: (menu: Menu, event: Event) => void): void;