/** * HeaderFooterRegion — the DocxEditor's docked header/footer editing bands. * * Header and footer stories live in their own OOXML parts (HeaderPart/FooterPart) *outside* * the body, so they cannot be another block in the body flow. This module renders them as two * docked bands — one above the body, one below — each composed PER STORY PARAGRAPH via the * session-attached `RenderBlockHtml`, which resolves `hdr`/`ftr` anchors natively. * * Using the same renderer for first paint and for the post-edit incremental swap means there is * no fidelity drift between them. It is also the only option that works in continuous mode at * all: the editor's full-document render only asks for headers/footers in paginated mode, and it * never stamps `data-anchor` inside header/footer parts (Unids are assigned to the main document * part only). In paginated mode the page boxes additionally clone one header node onto every * page, so page-margin nodes could never be uniquely addressable — bands keep exactly one DOM * node per story paragraph. * * Once rendered, a story paragraph is an ORDINARY editable block: DocxSession's anchor * resolution indexes every scope and routes by part, so ReplaceText/ApplyFormat/SetParagraphFormat * all accept a `p:hdr1:` anchor. The editor wires band blocks with the same `wireBlock` it * uses for the body, which is why the whole ribbon works inside a band with no new command code. */ import type { HeaderFooterKind, NumberFormat } from "./types.js"; /** Which of the two bands an operation targets. */ export type BandWhich = "header" | "footer"; /** The bridge slice the region needs. Structurally satisfied by `DocxEditorExports.DocxSessionBridge`; * declared locally so this module and `editor.ts` don't import each other. */ export interface HeaderFooterBridge { Project: (handle: number) => string; GetSectionInfo: (handle: number, anchorId: string) => string; SetHeaderText: (handle: number, anchor: string, kind: string, markdown: string) => string; SetFooterText: (handle: number, anchor: string, kind: string, markdown: string) => string; InsertPageNumberField: (handle: number, anchor: string, field: string, format: string) => string; EnsureHeaderFooterVisible: (handle: number, anchor: string, kind: string) => string; SetPageNumbering: (handle: number, anchor: string, opJson: string) => string; ClearPageNumbering: (handle: number, anchor: string) => string; RenderBlockHtml: (handle: number, anchorId: string, cssPrefix: string, fabricateClasses: boolean) => string; } export interface HeaderFooterRegionCallbacks { /** Wire a freshly rendered story paragraph as an editable block (DocxEditor.wireBlock). */ wireBlock: (el: HTMLElement) => void; /** Rebuild the editor's unid → full-anchor map (a seed adds a part, changing the projection). */ refreshAnchorMap: () => void; } export interface HeaderFooterRegionOptions { cssPrefix: string; fabricateClasses: boolean; } export declare class HeaderFooterRegion { readonly headerBand: HTMLElement; readonly footerBand: HTMLElement; private readonly bridge; private readonly handle; private readonly options; private readonly callbacks; /** The body anchor whose section the bands currently describe (needed to seed a story). */ private bodyAnchorId; private sectionInfo; /** Memoized body anchor → governing sectionUnid, so repeat focus costs no bridge call. */ private readonly sectionOfAnchor; private readonly kinds; constructor(bridge: HeaderFooterBridge, handle: number, options: HeaderFooterRegionOptions, callbacks: HeaderFooterRegionCallbacks); /** * Point the bands at the section governing `bodyAnchorId` and repaint if it changed. * Cheap to call on every focus change: the anchor → section lookup is memoized, and an * unchanged `sectionUnid` returns without touching the DOM (so it never clobbers the * user's kind selection). */ syncToBody(bodyAnchorId: string | null): void; /** Repaint one band from the live session. */ refresh(which: BandWhich): void; /** Repaint both bands from the live session (after a remount, undo/redo, or section change). */ refreshAll(): void; /** Select (and, if absent, create) the story kind a band edits, and make it render. */ setKind(which: BandWhich, kind: HeaderFooterKind): void; /** The kind a band is currently editing. */ kindOf(which: BandWhich): HeaderFooterKind; /** Append a PAGE / NUMPAGES field to the story paragraph addressed by `anchorId`. * Deliberately a PLAIN field (no `\*` switch), exactly as Word inserts one, so it follows the * section's page-number format — see {@link setPageNumbering}. Stamping the section's current * format as a switch here would silently WIN over any later format change. */ insertPageNumber(which: BandWhich, anchorId: string, field: "currentPage" | "totalPages"): boolean; /** * Set this section's page numbering (`w:pgNumType`) — Word's *Format Page Numbers…*. Omitted * fields are left alone, so the format and the start are independently settable. Both bands then * repaint, because the values belong to the section rather than to either story. * * The rendered page numbers in the editor do not change: a page-number field's cached result is * what the browser shows, and Word recomputes it on open. Paginated mode substitutes the real * per-page number, so it does reflect the format immediately. */ setPageNumbering(op: { start?: number; format?: NumberFormat; }): boolean; /** This section's page numbering as the live document states it. Fields are absent, not * defaulted — "continues the previous section" is not the same claim as "starts at 1". */ pageNumbering(): { start?: number; format?: NumberFormat; }; /** Remove this section's page-numbering start/format — it reverts to continuing the previous * section's numbering in Word's default `1, 2, 3`. */ clearPageNumbering(): boolean; /** * Re-read the section from the live document and repaint BOTH bands. * * `refreshAll` only repaints — it deliberately does not re-read, because its callers (remount, * undo/redo) already refreshed the section. A section-property write has no such caller, and * repainting from the stale snapshot would leave the chrome reporting the value the document had * before the edit. */ private reloadSection; /** Insert a page number into a band's own target paragraph (focused, else last). Seeds the * story first if the band's selected kind has none, so the command is never a silent no-op. */ insertPageNumberInBand(which: BandWhich, field: "currentPage" | "totalPages"): boolean; /** The band element containing `node`, or null. */ bandOf(node: Node | null): HTMLElement | null; /** The block-list root (a band's story container) owning `node`, or null. */ blockRootOf(node: Node | null): HTMLElement | null; /** True when `node` is inside either band (including its chrome). */ contains(node: Node | null): boolean; /** `"header"` / `"footer"` for a band element produced by this region. */ whichOf(band: HTMLElement): BandWhich; private readSectionInfo; private refsFor; /** The reference supplying `kind` for this band — possibly inherited from an earlier section. */ private refFor; /** URI of the part supplying `kind` for this band, or null when the story doesn't exist. */ private partUriFor; /** Full anchor ids of the story paragraphs held in `partUri`, in document order. */ private storyAnchors; /** * Create an empty story for `kind`. Seeding happens the moment a kind is selected rather than * lazily on first keystroke: an absent story renders no contenteditable element, so there * would be nothing to click into. */ private seedStory; private bandFor; private buildBand; /** * Which story paragraph a page-number insert targets: the last one the user focused in this * band if it is still attached, else the band's last paragraph (Word's convention — the page * number goes at the end of the footer line). */ private pageNumberTarget; private renderBand; /** * Mark a story paragraph as belonging to this region. The full anchor id is stamped alongside * `data-anchor` (which carries only the bare unid, matching the body's convention) so band * chrome can address the block without going through the editor's unid map. Called on first * render AND after an incremental swap, which replaces the DOM node. */ adoptBlock(el: HTMLElement, anchorId: string): void; private renderStoryBlock; private placeholder; /** * Surface the caveat that comes with the selected kind. Turning on first/even means those pages * stop using the Default stories entirely, which bites hardest on the OTHER band: enabling an * even header with no even footer leaves even pages footer-less, and enabling a first-page * header with an empty first-page footer leaves page 1 footer-less. The note is shown whenever * first/even is selected (the behavior change is real either way); the fix button appears only * when the counterpart story is missing entirely, which is what a user almost always wants next. */ private renderKindWarning; } //# sourceMappingURL=editor-headerfooter.d.ts.map