-
Notifications
You must be signed in to change notification settings - Fork 36
fix(components/docs-tools): links in if statements are correctly registered in table of contents #4339
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
fix(components/docs-tools): links in if statements are correctly registered in table of contents #4339
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -11,43 +11,60 @@ import { SkyDocsHeadingAnchorComponent } from './heading-anchor.component'; | |
| @Injectable() | ||
| export class SkyDocsHeadingAnchorService implements OnDestroy { | ||
| #anchors: SkyDocsHeadingAnchorComponent[] = []; | ||
| #anchorElements = new Map<SkyDocsHeadingAnchorComponent, Element>(); | ||
| readonly #anchorsChange = new BehaviorSubject<SkyDocsHeadingAnchorLink[]>([]); | ||
|
|
||
| public readonly anchorsChange = this.#anchorsChange.asObservable(); | ||
|
|
||
| public ngOnDestroy(): void { | ||
| this.#anchors = []; | ||
| this.#anchorElements.clear(); | ||
| this.#anchorsChange.complete(); | ||
| } | ||
|
|
||
| public register(anchor: SkyDocsHeadingAnchorComponent): void { | ||
| public register( | ||
| anchor: SkyDocsHeadingAnchorComponent, | ||
| element: Element, | ||
| ): void { | ||
| if (!this.#anchors.includes(anchor)) { | ||
| this.#anchors.push(anchor); | ||
| this.#anchorElements.set(anchor, element); | ||
| this.#anchorsChange.next(this.#getLinks()); | ||
| } | ||
| } | ||
|
|
||
| public unregister(anchor: SkyDocsHeadingAnchorComponent): void { | ||
| if (this.#anchors.includes(anchor)) { | ||
| this.#anchors.splice(this.#anchors.indexOf(anchor)); | ||
| this.#anchors.splice(this.#anchors.indexOf(anchor), 1); | ||
| this.#anchorElements.delete(anchor); | ||
| this.#anchorsChange.next(this.#getLinks()); | ||
| } | ||
| } | ||
|
|
||
| #getLinks(): SkyDocsHeadingAnchorLink[] { | ||
| const els = document.querySelectorAll('sky-docs-heading-anchor'); | ||
| const anchorsSorted: SkyDocsHeadingAnchorComponent[] = []; | ||
|
|
||
| // Since heading anchors can be registered at any point in the lifecycle | ||
| // of the app, we need to sort the links by their location in the DOM, | ||
| // rather than the order at which they were registered. | ||
| for (const el of els) { | ||
| this.#anchors.forEach((anchor) => { | ||
| if (anchor.equals(el)) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This public method is no longer used on the anchor component; can you remove it? |
||
| anchorsSorted.push(anchor); | ||
| } | ||
| }); | ||
| } | ||
| const anchorsSorted = [...this.#anchors].sort((a, b) => { | ||
| const elA = this.#anchorElements.get(a); | ||
| const elB = this.#anchorElements.get(b); | ||
|
|
||
| if (!elA || !elB) { | ||
| return 0; | ||
| } | ||
|
|
||
| const position = elA.compareDocumentPosition(elB); | ||
|
|
||
| if (position & Node.DOCUMENT_POSITION_FOLLOWING) { | ||
| return -1; | ||
| } | ||
|
|
||
| if (position & Node.DOCUMENT_POSITION_PRECEDING) { | ||
| return 1; | ||
| } | ||
|
|
||
| return 0; | ||
|
Comment on lines
+56
to
+66
|
||
| }); | ||
|
|
||
| return anchorsSorted.map((a) => { | ||
| return { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
register()emitsanchorsChangesynchronously. In the reported@iftiming scenario the host element may still be disconnected fromdocument, so#getLinks()may compute an implementation-specific order (and never re-emit once the element is attached). Consider deferring the emission (e.g., queueing a microtask / next tick) or otherwise scheduling a second recomputation after the element is connected so the TOC order reliably reflects actual DOM order.