Skip to content

@grida/svg-editor: define host resource resolution for external image hrefs #960

Description

@softmarshmallow

Is your feature request related to a problem? Please describe.

@grida/svg-editor currently accepts SVG source as in-memory text and renders/edit it inside the host app DOM. That works for self-contained SVGs, but it leaves a contract gap for valid SVGs that contain external resources such as:

<image href="assets/photo.png" />

When the SVG came from a file bundle, workspace, archive, or any other host-owned document root, the relative href no longer has a natural browser base URL once the editor receives only SVG text. In the inline editor DOM, the browser resolves assets/photo.png against the app route, not against the SVG document's original location. In thumbnails/export/presentation paths that encode the SVG into a data: URL, the same resource has no useful bundle-relative base either.

So SDK users have to rediscover and solve the same problem themselves: before rendering, map portable document hrefs to browser-renderable URLs; before saving, avoid persisting host-specific URLs back into the SVG.

Describe the solution you'd like

Define this as a first-party @grida/svg-editor host contract and provide an official mechanism for it.

At minimum, document the rule clearly:

  • The editor does not fetch local/bundle resources by itself.
  • Hosts that pass SVG text containing external resources must provide renderable URLs for the edit surface.
  • Saved SVG should preserve the authored, portable hrefs where possible, not persist temporary blob:, data:, file:, or app-specific URLs.

Preferably, expose a first-party resource mapping API so hosts do not have to hand-roll SVG rewriting around the editor. The exact API can be designed, but the contract should support a render-time projection such as:

type SvgEditorResourceResolver = (
  href: string,
  context: {
    kind: "image" | "feImage" | "css-url" | "unknown";
    documentUrl?: string;
  }
) => string | null | undefined;

The important behavior is:

  • The editor document model keeps the original authored href.
  • The rendered DOM receives the resolved browser URL.
  • Serialization/save emits the original href unless the user intentionally changes it.
  • Already absolute or self-contained hrefs (data:, blob:, http:, https:, fragment-only #id) are handled predictably.
  • Failed or missing resolutions degrade to the original href/broken image instead of corrupting the document.

Because some hosts may need async work to turn a bundle-local resource into a renderable URL, this may need two layers:

  • a synchronous render hook for already-available URLs, and/or
  • a package helper that materializes external resource hrefs before mounting and reverses them before commit.

The API should make the intended ownership explicit: the host owns resource authorization, root mapping, and URL lifetime; @grida/svg-editor owns applying the mapping consistently to the edit/render projection without changing the saved SVG by accident.

Describe alternatives you've considered

  • Host-side inlining: rewrite relative images to data: URLs before passing SVG to the editor, then reverse the mapping before save. This works, but it duplicates fragile parsing logic in every host, increases memory usage for large images, and makes thumbnails/presentation/export paths repeat the same solution.
  • Host-side URL rewrite to blob: or custom protocol URLs. This is better than base64 for large files, but still requires each host to know which SVG attributes/CSS forms need rewriting and how to keep serialization clean.
  • Load the SVG as a real document URL or inside an iframe. This can be enough for a read-only viewer because the browser has a document URL/base URL. It does not directly solve the current editor architecture, where the package receives SVG text and renders editable inline DOM that must participate in selection, overlays, mutation, and serialization.
  • Require SVG authors/agents to always embed images as data: URLs. This avoids resolution, but makes otherwise portable file-bundle SVGs heavier and less maintainable.

Additional context

This is primarily an SDK/editor contract issue, not a one-off rendering bug. It affects any host that edits SVG documents whose images live beside the SVG in a file bundle, workspace, archive, or other document root.

Likely first scope:

  • <image href="...">
  • <image xlink:href="...">
  • <feImage href="...">

Likely later/explicit scope:

  • CSS url(...)
  • external <use href="...">
  • fonts and other fetched resources

Useful tests for the package:

  • relative <image href> renders through the resolver but serialization preserves the original href
  • xlink:href is handled without dropping namespace compatibility
  • data:, blob:, http:, https:, and #fragment hrefs are not incorrectly rewritten
  • resolver failure does not crash or mutate the saved SVG
  • the same resource mapping path is usable by edit surface previews/thumbnails/export helpers, or the docs explain which layer owns each one

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions