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

    Module core/app/interaction/element-actions

    Resolve a picked element's authored interaction templates into a URL and a copy string (issue #1917).

    Pure: takes a scene node and the pick's values, returns strings. All DOM work — opening the link, the context menu, the clipboard — lives in canvas-actions.ts, so everything security-relevant here is unit-testable without a browser.

    A .luxar.zarr can be served from anywhere, and .zattrs is JSON the viewer did not author. BOTH halves of a link are therefore hostile input: the template (author-controlled) and the values substituted into it (data-controlled, one per element). The Python writer runs an equivalent check at authoring time, but that only covers stores written by Luxar — nothing stops a hand-edited .zattrs, so the viewer re-validates rather than trusting the producer.

    The guarantees, in the order they are established:

    1. Substituted values are encodeURIComponent-escaped, so a label can contribute content to a URL but never structure — no injected query, fragment, path segment or authority. (utils/hover-template.ts.)
    2. The result is parsed with new URL(built) and no base, so a relative template throws instead of resolving against the viewer's own origin. A third-party store must not be able to aim a click at an embedder's site.
    3. The scheme must be http: or https: — an ALLOWLIST. This is deliberately stricter than the denylist OverlayManager.sanitizeHtml uses for rendered markup: that decides what to display, this decides where to navigate, and the safe set for navigation is small and known.
    4. Length is capped, so a hostile store cannot push an unbounded string at the browser after per-element substitution.

    One bounded gap is accepted knowingly, because closing it costs more than it buys: . and .. are unreserved characters, so encodeURIComponent leaves them intact and the URL parser then normalizes them away — a label of exactly ".." turns https://site/entry/{hover_label} into https://site/. That is a wrong destination, not a boundary crossing: the origin is fixed by the template, a label is a single path segment (its slashes ARE encoded), so the worst reachable outcome is the origin root. Escaping is therefore about structure, not about pinning the exact path.

    InteractionTemplates
    ResolvedElementActions
    MAX_COPY_CHARS
    readInteractionTemplates
    buildElementUrl
    explainLinkRejection
    resolveLinkTarget
    buildElementCopyText
    resolveElementActions