Luxar Viewer API Documentation - v2026.9.22
    Preparing search index...

    Narrow port interfaces — the handler only reads the methods it needs from each collaborator, so tests can stub with plain vi.fn()s.

    interface PickResultHandlerPorts {
        labelLoader?: {
            getLabel: (path: string, idx: number) => Promise<string | null>;
        };
        keyLoader?: {
            getLabel: (path: string, idx: number) => Promise<string | null>;
        };
        imageLabelLoader?: {
            getImageUrl: (path: string, idx: number) => Promise<string | null>;
        };
        overlayManager?: {
            updateHoverContent: (
                r:
                    | {
                        label?: string
                        | null;
                        key?: string | null;
                        imageUrl?: string | null;
                        nodeName: string;
                        elementIndex: number;
                    }
                    | null,
            ) => void;
        };
        onSelection?: (
            sel:
                | { nodeName: string; elementIndex: number; hitNodeName: string }
                | null,
        ) => void;
        onPicked?: (
            pick:
                | {
                    mainNode: Object3D;
                    nodeName: string;
                    hitNodeName: string;
                    elementIndex: number;
                    label: string
                    | null;
                    key: string | null;
                    screenX: number;
                    screenY: number;
                }
                | null,
        ) => void;
    }
    Index
    labelLoader?: {
        getLabel: (path: string, idx: number) => Promise<string | null>;
    }
    keyLoader?: { getLabel: (path: string, idx: number) => Promise<string | null> }

    Reads the per-element keys CSR (issue #1917). Same shape as labelLoader — it IS a LabelLoader, constructed on the 'keys' channel — so the key is fetched on exactly the same terms as the label: lazily, once per node, coalesced.

    imageLabelLoader?: {
        getImageUrl: (path: string, idx: number) => Promise<string | null>;
    }
    overlayManager?: {
        updateHoverContent: (
            r:
                | {
                    label?: string
                    | null;
                    key?: string | null;
                    imageUrl?: string | null;
                    nodeName: string;
                    elementIndex: number;
                }
                | null,
        ) => void;
    }
    onSelection?: (
        sel:
            | { nodeName: string; elementIndex: number; hitNodeName: string }
            | null,
    ) => void

    Optional sink for the public selection embedder event. Fires with the picked element on every (non-superseded) hover-pick, and null when the hover clears. Independent of whether a string/image tooltip exists — reports what is currently picked. Inline shape (not the embedder type) to keep this handler decoupled from the public event module — keep it in sync with SelectionPayload in core/app/embedder/events.ts.

    onPicked?: (
        pick:
            | {
                mainNode: Object3D;
                nodeName: string;
                hitNodeName: string;
                elementIndex: number;
                label: string
                | null;
                key: string | null;
                screenX: number;
                screenY: number;
            }
            | null,
    ) => void

    Optional sink retaining the settled pick so a click can act on it (issue #1917). Written on every non-superseded pick and cleared when the hover clears, mirroring onSelection exactly.

    Independent of whether a tooltip has content: an element with no label can still carry a link built from {hover_index}, and a right-click on it should still offer something. Inline shape (not the cache type) to keep this handler decoupled — see core/app/interaction/picked-element-cache.ts.