Skip to content

fix: register destinations only once for wrapped text#3309

Open
XiaoTianFan wants to merge 1 commit intodiegomura:masterfrom
XiaoTianFan:fix-wrapped-text-destinations
Open

fix: register destinations only once for wrapped text#3309
XiaoTianFan wants to merge 1 commit intodiegomura:masterfrom
XiaoTianFan:fix-wrapped-text-destinations

Conversation

@XiaoTianFan
Copy link
Copy Markdown

Problem

When text with an id prop wraps across multiple pages, the layout engine creates multiple fragments that all inherit the same id. Each fragment calls addNamedDestination, causing later fragments to overwrite earlier ones. This breaks reverse links (e.g., endnote → superscript) because the destination points to the wrong page.

Solution

Implement "first occurrence wins" semantics:

  • Track registered destinations using a Set in RenderOptions
  • Only register the FIRST occurrence of each ID
  • Subsequent occurrences (from wrapping) are silently skipped

This ensures destinations always point to the first page where the content appears, enabling correct bidirectional links in academic documents with endnotes/footnotes.

Changes

Core Implementation (4 files)

  1. packages/render/src/types.ts

    • Added registeredDestinations: Set<string> to RenderOptions
  2. packages/render/src/index.ts

    • Initialize registeredDestinations: new Set<string>()
  3. packages/render/src/operations/setDestination.ts

    • Check if ID already registered before calling addNamedDestination
    • Only register first occurrence
  4. packages/render/src/primitives/renderNode.ts

    • Pass options parameter to setDestination

Tests (1 file)

  1. packages/render/tests/operations/setDestination.test.ts
    • Added test: "should only register the first occurrence of duplicate IDs"
    • Added test: "should allow different IDs to be registered"
    • Updated existing tests with new signature

Example (2 files)

  1. packages/examples/vite/src/examples/endnote/index.tsx

    • Demonstrates bidirectional endnote links
    • Shows correct component structure for inline links
  2. packages/examples/vite/src/examples/index.ts

    • Registered new example

Example Structure Requirements

Important: For inline destinations to work correctly:

  • id must be on block-level elements (View, Image), not inline Views
  • Link must wrap content directly for proper clickable areas

✅ Correct:

<View id="fn-src-1">
  <Text>
    text <Link href="#dest">¹</Link> text
  </Text>
</View>

❌ Incorrect:

<Text>
  text <View id="fn-src-1" style={{display:'inline'}}>
    <Link>¹</Link>
  </View> text
</Text>

Breaking Changes

None. This is a pure bug fix with no API changes.

Related Issues

Fixes #2110 (Destinations Undocumented but Functional)
Fixes #2377 (Destinations handling for multiple pages)
Related to #2700 (Bookmarks all link to last page)
Related to PR #746 (Go-to functionality)

When text with an `id` wraps across multiple pages, the layout engine
creates multiple fragments that all inherit the same `id` prop.
Previously, each fragment would call `addNamedDestination`,
causing later fragments to overwrite earlier ones. This broke
reverse links (endnote → superscript) because
the destination would point to the wrong page.

Solution:
- Track registered destinations using a Set in RenderOptions
- Only register the FIRST occurrence of each ID
- Subsequent occurrences (from wrapping) are silently skipped

This ensures destinations always point to the first page
where the content appears, enabling correct bidirectional
links in academic documents with endnotes/footnotes.

Fixes diegomura#2110, diegomura#2377, diegomura#2700

Co-Authored-By: Claude <noreply@anthropic.com>
@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Mar 16, 2026

⚠️ No Changeset found

Latest commit: 5def8de

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Destinations (id) API handling for multiple pages Destinations: Undocumented but Fully Functional Feature

1 participant