typograph-editor-documentation
    Preparing search index...

    Typograph — Which page?

    This document describes how a host asks which page it is dealing with: the page the user has selected, the page the work is on, and the page the pointer is over — and how a host shows a page itself.

    It exists because get_active_page() returns a spread, and a spread can hold two pages. A host attaching something to "the page the user picked" needs an answer one level finer than that.

    These are canvas methods on window.typograph, so they work in the viewer bundle as well as the editor.

    For page structure changes — add, delete, duplicate, reorder — see PageEvents.md, which also documents the hover event described below.

    Source of truth: get_selected_page() / get_focused_page() / show_spread() / show_page() in the canvas package at src/canvas.ts; the event name lives in src/typograph_events.ts.


    Every call here answers with the same pair:

    { container_index: 3, record_index: 1 }
    
    • container_index — which spread, indexing document.pages (what get_active_page() returns one of).
    • record_index — which page within that spread: 0 for a single page, 0 or 1 for a facing pair.

    This is the same addressing logical_pages(), move_logical_page() and delete_logical_page() use, so a pair from here can be handed straight to them:

    const doc = typograph.get_typograph_document();
    const flat = doc.logical_pages(); // [{container_index, record_index}, ...]
    const sel = typograph.get_selected_page();

    // the flat index the move/delete calls take
    const index = flat.findIndex(p => p.container_index === sel.container_index
    && p.record_index === sel.record_index);

    What the user picked in the page bar — the blue selection.

    const sel = typograph.get_selected_page();
    

    Three possible answers, and all three mean something different:

    Returns Meaning
    {container_index, record_index} one page of a spread is selected
    {container_index, record_index: null} the whole spread is selected
    null nothing is selected — or this embed has no page bar

    ⚠️ record_index: null is not "cannot tell". It says the user selected a spread rather than a page, and that is what the editor's own delete and duplicate act on: a selected page of a spread removes that one page, a selected spread removes the whole container. Treating null as page 0 will disagree with what the editor does next.

    A whole-spread selection is reached by shift-clicking the second half of a spread, and by an island or single-page row. null comes from clicking the page number badge, which activates a spread without selecting pages.

    The returned object is a copy — writing to it does not move the selection.


    Which page the work is on.

    const page = typograph.get_focused_page();   // { container_index: 3, record_index: 0 }
    

    The active spread, plus the page the current element selection sits on. When nothing is selected it keeps its last answer, and a click on an empty page area sets it.

    Unlike get_selected_page() this always answers while a document is open, including in a viewer where there is no page bar to click. That makes it the one to use for "apply this to the current page".

    get_selected_page() get_focused_page()
    Answers what the user picked in the page bar which page the work is on
    In a viewer (no page bar) null answers
    Can say "a whole spread" yes, record_index: null no, always a page
    Can say "nothing" yes, null only when no document is open

    Use the first to follow the user's explicit choice, the second when you need a page regardless.


    typograph.show_spread() and typograph.show_page() bring a spread, or one page of it, into view — what a click on its row in the page bar does.

    typograph.show_spread(2);                                      // spread 2, selected whole
    typograph.show_page({ container_index: 3, record_index: 1 }); // the second page of spread 3

    show_page() takes the same pair get_selected_page() returns, so a page a host stored can be shown again as it is; record_index: null is the whole spread. The page also becomes the focused one, so get_focused_page() answers it.

    What follows is exactly what a click does: the canvas shows the spread, the page bar's selection moves to it, and typograph_page_shown (Page Action Events) fires — once, and not at all when that spread was already the one shown. It works with the page bar hidden too, so a viewer without one can drive its own navigation from these calls.

    It is navigation, not an edit: nothing to undo, and is_changed() does not move.

    Both return true when the page is shown, and false — changing nothing — when:

    • the spread or page does not exist (an index past the end, a negative or fractional one, a record_index past the spread's last page);
    • the canvas is not set up yet;
    • a cover's spine is being edited (the spine is the only page there is until OK or Cancel);
    • the host limited the page bar with set_visible_page_indexes() and this spread is not in it.

    Call it from typograph_document_opened: by then the document is fully open, its first spread is shown and the page bar lists its pages.

    document.addEventListener('typograph_document_opened', () => {
    typograph.show_page({ container_index: 3, record_index: 1 });
    });

    To go by page number, logical_pages() lists every page in order, so page n is entry n − 1:

    const page = typograph.get_typograph_document().logical_pages()[n - 1];
    if (page) typograph.show_page(page);

    Before release 20261008 the event fired before the canvas went back to the first spread, so a page shown in the handler was replaced straight away; a setTimeout(…, 0) around the call worked around it and can now go.


    typograph.set_page_fill() gives one page a background colour of its own, or a whole spread one. The colour is a swatch uuid from the document's swatch library, with an optional tint from 0 to 1.

    typograph.set_page_fill({ container_index: 2, record_index: 1 }, swatch_uuid);          // that page
    typograph.set_page_fill({ container_index: 2, record_index: 1 }, swatch_uuid, 0.4); // at 40%
    typograph.set_page_fill({ container_index: 2, record_index: 1 }, null); // back to the spread's colour
    typograph.set_page_fill({ container_index: 2, record_index: null }, swatch_uuid); // the whole spread

    typograph.get_page_fill({ container_index: 2, record_index: 1 });
    // → { swatch_uuid: 'Cyan', tint: 1, own: true } (own: false when it is the spread's colour)

    The colour belongs to the page: it stays with it when pages are deleted, moved or re-paired, and it is saved and printed on that page only — up to the spine, with the bleed on the outer edges. Colouring a whole spread replaces the colours its pages had of their own.

    Each call is one undo step. It returns false, changing nothing, when the page or the swatch does not exist, the tint is outside 0–1, a single page is given a gradient (a page's own colour is a solid swatch; a gradient can colour a whole spread), or a spread is given null.

    ⚠️ Colouring a page through its spread object — get_active_page().set_fill_swatch_uuid(uuid) — sets the spread's colour only. It does not record an undo step or redraw, and a page that has a colour of its own keeps showing that. Use set_page_fill() instead.


    Fired when the page under the pointer changes, on the rendered spread in the canvas.

    document.addEventListener('typograph_page_hover', (e) => {
    const { container_index, record_index } = e.detail;
    if (record_index === null) clearLayoutPicker();
    else showLayoutPickerFor(container_index, record_index);
    });
    Property Type Description
    container_index number | null the spread under the pointer, null when over no page
    record_index number | null the page within it, null when over no page

    Both are null together. That is a real state, not a failure: the pointer can be over the canvas without being over paper — the grey around the spread, or the gutter between two pages.

    It fires once per boundary crossing, not continuously while the pointer stays on one page — the same mechanism as typograph_pageelement_hover, and like that event it is not throttled, because each event is a meaningful transition.

    Hover dispatch is off by default when the document was set up with interactive:false, and is otherwise independent of it:

    typograph.set_hover_events(true);    // both hover events on
    typograph.get_hover_events(); // → true

    That independence is deliberate, so a viewer host can drive a layout picker or a page highlight without making the document editable. Switching hover events off fires a final null for both the element and the page hover, so a highlight is never left stuck.

    ⚠️ Element hover and page hover are independent. A page can have no element on it, and an element can overhang the paper onto the grey — so do not infer one from the other.

    ⚠️ Today the canvas draws one spread at a time, so container_index is the spread being viewed and the new information is which page of it. Written as a pair anyway, so a host does not have to correlate two sources.


    function targetPage() {
    // The explicit choice first; fall back to the page being worked on — a viewer has no page bar,
    // and a user who selected a whole spread has not told us which half.
    const sel = typograph.get_selected_page();
    if (sel && sel.record_index !== null) return sel;
    return typograph.get_focused_page();
    }

    document.getElementById('apply-layout').addEventListener('click', () => {
    const page = targetPage();
    if (!page) return; // no document open
    applyLayout(page.container_index, page.record_index);
    });
    document.addEventListener('typograph_setup_ready', () => {
    typograph.set_hover_events(true); // a view-only embed starts with them off
    });

    document.addEventListener('typograph_page_hover', (e) => {
    const { container_index, record_index } = e.detail;
    document.querySelectorAll('.page-chip.hovered')
    .forEach(el => el.classList.remove('hovered'));
    if (record_index === null) return;
    document.querySelector(`.page-chip[data-spread="${container_index}"][data-page="${record_index}"]`)
    ?.classList.add('hovered');
    });

    Call / event Answers Can be null
    typograph.get_selected_page() the page bar's selection yes — nothing selected, or no page bar; record_index alone is null for a whole spread
    typograph.get_focused_page() the page the work is on only when no document is open
    typograph_page_hover the page under the pointer, on change both fields, when over no page
    typograph.set_hover_events(on) turns both hover events on or off —
    typograph.show_spread(container_index) shows that spread, selected whole — returns false when it cannot be shown
    typograph.show_page({container_index, record_index}) shows and selects that page — returns false when it cannot be shown
    typograph.set_page_fill({container_index, record_index}, swatch_uuid, tint?) colours a page, or a whole spread with record_index: null — returns false when it cannot
    typograph.get_page_fill({container_index, record_index}) {swatch_uuid, tint, own} yes — no such page