Skip to content
Genom
Concepts

Extending the viewer

Two levels, and the cheap one first.

Decorators

A decorator runs *after* the renderer produced an element. That is what makes it a stable contract: replacing a renderer breaks whenever its internals move, while adding a class or an attribute to a finished element does not.

Keys are format:kind. The prefix is stripped before the renderer sees them, so a renderer never has to know its own name, and an unprefixed key applies to whatever format is showing.

decorate.ts
createViewer(element, {
  formats: all,
  decorate: {
    'xlsx:cell': (el, cell) => {
      if (typeof cell.value === 'number' && cell.value < 0) el.classList.add('negative');
    },
    'docx:hyperlink': (el, link, { document }) => {
      el.dataset.external = String(!link.href.startsWith('#'));
    },
  },
});

Plugins

When a decorator is not enough — a toolbar, an annotation layer, telemetry, your own keyboard shortcuts — a plugin gets the viewer itself and returns its own teardown.

plugin.ts
import type { ViewerPlugin } from '@genomdev/core/viewer';

const analytics: ViewerPlugin = {
  name: 'analytics',
  setup(viewer) {
    const off = viewer.on('document:open', ({ document }) => {
      track('document_opened', { format: document.format });
    });
    return off;
  },
};

createViewer(element, { formats: all, plugins: [analytics] });

Format options

Renderer settings are addressed by format id rather than thrown into one bag, so two formats can want the same word without colliding, and a renderer never sees another format’s settings.

options
options: {
  docx: { revisions: 'markup' },
  xlsx: { formulaBar: false, headers: true },
  pptx: { notes: true },
}