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.
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.
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: {
docx: { revisions: 'markup' },
xlsx: { formulaBar: false, headers: true },
pptx: { notes: true },
}