Skip to content

Inserting a table of contents leaves the page numbers with no tab stop: TOC1..9 are never materialised #3988

Description

@Nathaniel-260

What happened?

Insert a table of contents and each row comes out as title + tab character + PAGEREF, with no tab stop anywhere for that tab character to land on. The page number therefore sits one default tab (½ inch) after the title text instead of at the far edge of the text area, and there is no dot leader between them. It reads as one run-on line rather than a table of contents.

Three layers were checked, and the stop is in none of them:

  1. The rows themselves — the inserted TOC paragraphs have no w:tabs in their pPr.

  2. The styles they point at — the rows carry w:pStyle TOC1/TOC2/TOC3, but TOC1TOC9 are not defined in styles.xml. shared/common/data/blank.docx has 33 style definitions and none of them is a TOC*. What it does have is the latent declarations:

    <w:lsdException w:name="toc 1" w:semiHidden="1" w:uiPriority="39" w:unhideWhenUsed="1"/>
    …
    <w:lsdException w:name="toc 9" w:semiHidden="1" w:uiPriority="39" w:unhideWhenUsed="1"/>

    In Word those latent entries are the promise of a definition: inserting a TOC materialises TOC1TOC9 from Word's built-in definitions — each with a right-aligned tab stop at the section's text width carrying the requested leader, and a per-level w:ind — and unhides them. SuperDoc writes the field and the rows but never creates the styles, so the w:pStyle reference resolves to nothing.

  3. The document default — with no stop from either layer the tab falls back to w:defaultTabStop, which the shipped template sets to 720. Hence the half-inch jump, unrelated to the page width, and no leader.

The configure surface looks like it should cover this and does not: TocConfigurePatch offers tabLeader and rightAlignPageNumbers (packages/document-api/src/toc/toc.types.ts) —

/** Tab leader style between entry text and page number. */
tabLeader?: 'none' | 'dot' | 'hyphen' | 'underscore' | 'middleDot';
/** Whether TOC entry page numbers use right-aligned tab stops. Stored as a PM node attribute (not a field switch). */
rightAlignPageNumbers?: boolean;

— but there is no tab stop for either of them to describe, and setting them does not create one. toc.configure with { tabLeader: 'dot' } or { rightAlignPageNumbers: true } returns success with no warnings, the instruction is unchanged, the value is not echoed back in displayConfig on the next toc.list(), and no w:tabs appears. { outlineLevels: { from: 1, to: 3 } } in the same patch does take effect (the instruction becomes TOC \o "1-3" \h), so the call itself is reaching the field — those two keys are being dropped. A following toc.update does not add a stop either.

Steps to reproduce

  1. New document. Type a few paragraphs and give three of them Heading 1 / Heading 2 / Heading 3. (On a blank document only Heading 1 is reachable from the gallery — Heading 2-9 never reach the styles gallery: w:unhideWhenUsed is not implemented #3987 — so apply 2 and 3 by ID: ui.commands.executeAsync('linked-style', { style: 'Heading2' }).)
  2. Put the caret in a body paragraph and insert a table of contents.
  3. Look at the result on screen: page number a fixed short gap after the title, no leader dots.
  4. Save the .docx and inspect it:
    • the TOC1/TOC2/TOC3 paragraphs have no w:tabs;
    • word/styles.xml still has no TOC* style;
    • word/settings.xml still has <w:defaultTabStop w:val="720"/>.
  5. toc.configure with { tabLeader: 'dot', rightAlignPageNumbers: true } — reports success, changes nothing. Add outlineLevels: { from: 1, to: 3 } to the same patch and the instruction does change, so the patch is being applied; those two keys are dropped.
  6. toc.update — the rows are rebuilt, still with no stop.

Word, same document: the numbers form a right-aligned column at the edge of the text area with a dotted leader running to them, and styles.xml has gained TOC1TOC3.

Suggested fix

On toc.insert (and on toc.update for rows that are new), materialise the TOC1TOC9 definitions the field's level range needs, the way Word does:

  • w:tabs<w:tab w:val="end" w:pos="<section text width>" w:leader="<tabLeader>"/>
  • per-level w:ind (Word steps 220 twips per level)
  • clear w:semiHidden/w:unhideWhenUsed on the materialised style, since it is now used

That also gives tabLeader and rightAlignPageNumbers something real to write to, and it makes the saved file open in Word looking the way it looked in SuperDoc.

One smaller thing noticed on the way, possibly related to #2580: the instruction the insert produces is TOC \h — no \o range switch at all, where Word writes TOC \o "1-3" \h \z \u.

Workaround

For anyone who lands here: the rows can be fixed up from the Document API after inserting, per row —

await doc.format.paragraph.clearAllTabStops({ target });
await doc.format.paragraph.setTabStop({ target, position: textWidthTwips, alignment: 'right', leader: 'dot' });

(with target = { kind: 'block', nodeType: 'paragraph', nodeId } for each TOC row; the block for the TOC node itself reports nodeType: 'tableOfContents' in blocks.list but is accepted as 'paragraph' here). In a right-to-left document two further corrections are needed, which is the third issue in this set.

SuperDoc version

2.13.0 (@superdoc/docx-engine 0.12.0). Chrome, Windows 10.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    status: queuedEngineering work is queued; no delivery date is committed.

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions