/** * HeaderFooterRegion — the DocxEditor's header/footer editing surface. * * 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 presents them two * ways, both composed PER STORY PARAGRAPH via the editor's block renderer, which resolves * `hdr`/`ftr` anchors natively: * * - **Continuous view** — two bands, one docked above the body and one below, drawn as the * top and bottom margin of the sheet (a dashed rule and a small "Header"/"Footer" tag, the * look Word has while a header is being edited). * - **Page view** — no bands. The paginator clones each story onto every page as inert * presentation; clicking a page's header or footer area swaps THAT page's clone for the * real, editable story paragraphs. A commit re-renders the paragraph and re-clones the story * onto every other page that shows it, so all pages update live, and page-number fields on * each clone are substituted per page just as the paginator does. * * 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 story blocks with the same `wireBlock` it * uses for the body, which is why the whole ribbon works inside a story with no new command code. * * "Different first page" and "Different odd & even pages" are Word's two checkboxes, backed by * `w:titlePg` (per section) and `w:evenAndOddHeaders` (document-global). Enabling one seeds BOTH * the header and the footer story of that kind, exactly as Word does — page 1 stops using the * default stories the moment `w:titlePg` is set, so seeding only one side would silently leave * page 1 with no footer. */ import type { HeaderFooterKind, NumberFormat } from "./types.js"; /** Which of the two stories 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; ListAnchors?: (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; /** Turn `w:titlePg` / `w:evenAndOddHeaders` on or off (optional: older bundles can only enable). */ SetHeaderFooterKindEnabled?: (handle: number, anchor: string, kind: string, enabled: boolean) => string; SetPageNumbering: (handle: number, anchor: string, opJson: string) => string; ClearPageNumbering: (handle: number, anchor: string) => string; } export interface HeaderFooterRegionCallbacks { /** Wire a freshly rendered story paragraph as an editable block (DocxEditor.wireBlock). */ wireBlock: (el: HTMLElement) => void; /** Render one story paragraph by full anchor id, with the editor's own render profile. */ renderBlock: (anchorId: string) => HTMLElement | null; /** Rebuild the editor's unid → full-anchor map (a seed adds a part, changing the projection). */ refreshAnchorMap: () => void; /** Full anchor id of a rendered BODY block's unid, or undefined. */ bodyAnchorIdOf: (unid: string) => string | undefined; /** Re-render the whole document (page view: a story changed height, or a kind flag flipped). */ remount: () => void; /** The story host that is active changed (null = the caret left every story). */ onActiveChange?: (host: HTMLElement | null, which: BandWhich | null) => void; } export declare class HeaderFooterRegion { readonly headerBand: HTMLElement; readonly footerBand: HTMLElement; private readonly bridge; private readonly handle; private readonly callbacks; /** The body anchor whose section the region currently describes (needed to seed a story). */ private bodyAnchorId; private sectionInfo; /** Memoized body anchor → governing sectionUnid, so repeat focus costs no bridge call. */ private readonly sectionOfAnchor; /** Which story kind each continuous band shows. */ private readonly kinds; /** Page view: the page stack being edited in place, and the hosts currently holding live blocks. */ private pageRoot; private readonly pageHosts; private activeHost; private readonly pageListeners; constructor(bridge: HeaderFooterBridge, handle: number, callbacks: HeaderFooterRegionCallbacks); /** * Point the region 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; /** Re-read the section and repaint one story (band, or the active page host and its clones). */ refresh(which: BandWhich): void; /** Repaint both stories from the live session (after a remount, undo/redo, or section change). */ refreshAll(): void; /** Select (and, if absent, create) the story kind a continuous band shows. */ setKind(which: BandWhich, kind: HeaderFooterKind): void; /** The kind a band (or the active page host) is currently editing. */ kindOf(which: BandWhich): HeaderFooterKind; /** Word's checkbox state: whether `w:titlePg` (first) / `w:evenAndOddHeaders` (even) is set. */ kindEnabled(kind: "first" | "even"): boolean; /** * Word's "Different first page" / "Different odd & even pages". Enabling seeds BOTH stories of * that kind when absent (page 1 uses its own header AND footer once `w:titlePg` is set, so a * missing first-page footer would leave page 1 footer-less) and sets the flag; disabling clears * the flag and leaves the parts in place, as Word does. Returns false when the bundle predates * the disable op and `enabled` is false. */ setKindEnabled(kind: "first" | "even", enabled: boolean): boolean; /** Human label for the story a band (or the active host) shows — "First Page Header". */ storyLabel(which: BandWhich): string; /** The kinds a band could show right now (those with a story or an enabled flag). */ availableKinds(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}. */ insertPageNumber(which: BandWhich, anchorId: string, field: "currentPage" | "totalPages" | "pageOfTotal"): boolean; /** Insert a page number into a story's own target paragraph (focused, else last). Seeds the * story first if the selected kind has none, so the command is never a silent no-op. */ insertPageNumberInBand(which: BandWhich, field: "currentPage" | "totalPages" | "pageOfTotal"): boolean; /** Set this section's page numbering (`w:pgNumType`) — Word's *Format Page Numbers…*. */ setPageNumbering(op: { start?: number; format?: NumberFormat; }): boolean; /** This section's page numbering as the live document states it (fields absent, not defaulted). */ pageNumbering(): { start?: number; format?: NumberFormat; }; /** Remove this section's page-numbering start/format. */ clearPageNumbering(): boolean; /** The story host (continuous band or active page area) containing `node`, or null. */ bandOf(node: Node | null): HTMLElement | null; /** The block-list root (a story's container) owning `node`, or null. */ blockRootOf(node: Node | null): HTMLElement | null; /** True when `node` is inside a story host (band or page area). */ contains(node: Node | null): boolean; /** `"header"` / `"footer"` for a story host produced by this region. */ whichOf(band: HTMLElement): BandWhich; /** The story host currently being edited (page view), or null. */ get active(): HTMLElement | null; /** True while the caret is in a story (either view) — drives the contextual ribbon tab. */ isStoryActive(): boolean; /** * Track focus: the editor calls this whenever any block takes focus. A body block leaving a * page story deactivates it (and re-paginates if the story grew past its band); a story block * activates its host. */ noteFocus(el: HTMLElement | null): void; /** Leave story editing: the caret goes back to the body (Word's "Close Header and Footer"). */ close(): void; /** * Move the caret into a story (Word's "Go to Header / Go to Footer"). Page view activates the * area on the page the caret is on (else the first page); continuous view focuses the band. */ focusStory(which: BandWhich, near?: HTMLElement | null): boolean; /** After the editor swapped/split/merged a story block in place: show this page's own numbers * in the fresh render, then mirror it to the page clones. */ afterStoryEdit(el: HTMLElement): void; /** * Adopt a page stack: every page's header/footer area becomes click-to-edit. The paginator's * clones stay as they are until a click swaps one for the live story. */ attachPages(pageRoot: HTMLElement): void; /** Release a page stack (before a remount replaces it). */ detachPages(): void; /** True when the region is presenting stories inside page boxes rather than as bands. */ get inPageMode(): boolean; /** * Swap a page's cloned story for the live, editable one and put the caret in it. The page * advertises which story it shows (`data-hf-type`, stamped by the paginator) and which * section it belongs to (`data-section-index`); the section's body anchor comes from the * first anchored block on the page (or an earlier page of the same section). */ private editInPage; /** The full body anchor of the first anchored block on `page`, walking back to earlier pages. */ private bodyAnchorForPage; /** Render the live story into a page host (replacing the paginator's clone). */ private renderHostStory; /** Repaint the page story `which` (active host first, then every clone of the same story). */ private repaintPageStory; /** Mirror a live host's story onto every other page area showing the same story. */ private propagate; /** Substitute PAGE / NUMPAGES markers in a page area from the page's own numbering. */ private substituteFields; private deactivateHost; private readSectionInfo; private reloadSection; private refsFor; private refFor; private partUriFor; /** Full anchor ids of the story paragraphs held in `partUri`, in document order. */ private storyAnchors; /** Create an empty story for `kind` so there is a paragraph to click into. */ private seedStory; private buildBand; /** Which story paragraph a page-number insert targets: the focused one, else the last. */ private pageNumberTarget; private renderBand; /** Render the story's paragraphs into `body` (a placeholder when there is no story yet). */ private fillStoryBody; /** * Mark a story paragraph as belonging to this region. The full anchor id is stamped alongside * `data-anchor` (which carries only the bare unid) so chrome can address the block without * going through the editor's unid map. Called on first render AND after an incremental swap. */ adoptBlock(el: HTMLElement, anchorId: string): void; private placeholder; } //# sourceMappingURL=editor-headerfooter.d.ts.map