diff --git a/adev/src/app/features/references/api-reference-details-page/api-reference-details-page.component.html b/adev/src/app/features/references/api-reference-details-page/api-reference-details-page.component.html index 5cc6a813f6c..d61fcf0aa8c 100644 --- a/adev/src/app/features/references/api-reference-details-page/api-reference-details-page.component.html +++ b/adev/src/app/features/references/api-reference-details-page/api-reference-details-page.component.html @@ -1,7 +1,13 @@ -
+
- + @for (tab of tabs(); track tab.url) {
@@ -12,12 +18,12 @@
-@if (isApiTabActive() && membersMarginTopInPx() > 0) { +@if (isApiTabActive()) { + (contentLoaded)="membersCardsLoaded()" + > } diff --git a/adev/src/app/features/references/api-reference-details-page/api-reference-details-page.component.scss b/adev/src/app/features/references/api-reference-details-page/api-reference-details-page.component.scss index a9e0e145ab9..7e922770a81 100644 --- a/adev/src/app/features/references/api-reference-details-page/api-reference-details-page.component.scss +++ b/adev/src/app/features/references/api-reference-details-page/api-reference-details-page.component.scss @@ -5,9 +5,7 @@ gap: 1rem; width: 100%; box-sizing: border-box; - @include mq.for-desktop-down { - flex-direction: column; - } + flex-direction: column; h1 { font-size: 1.5rem; @@ -34,32 +32,14 @@ .adev-header-and-tabs { padding: var(--layout-padding) 0 1rem var(--layout-padding); box-sizing: border-box; + width: 100%; + max-width: var(--page-width); - @include mq.for-desktop-up { - // this section should only be independently scrollable if - // the API tab is active & the screen is wide enough - &:has(.docs-reference-api-tab), - &.adev-cli-content { - position: sticky; - top: 0; - padding-inline-end: 1rem; - max-height: 100vh; - height: max-content; - overflow-y: scroll; - width: 50%; - } - &:has(.docs-reference-api-tab) { - width: 60%; - } - &:not(:has(.docs-reference-api-tab)) { - width: 100%; - max-width: var(--page-width); - } - } @include mq.for-desktop-down { padding: var(--layout-padding); - width: 100%; + max-width: none; } + &::-webkit-scrollbar-thumb { background-color: var(--septenary-contrast); border-radius: 10px; @@ -164,17 +144,15 @@ .docs-reference-members-container { width: 40%; - padding: 0 var(--layout-padding) 1rem 0; box-sizing: border-box; - max-width: 60ch; + width: 100%; + max-width: var(--page-width); + padding: 0 0 1rem var(--layout-padding); + @include mq.for-desktop-down { - width: 100%; padding: var(--layout-padding); + padding-top: 0; max-width: none; - // override javascript-applied margin-top: - // determined by how much space the sticky header takes up - // to align the selected card member with the code block - margin-block-start: 0 !important; } } @@ -301,7 +279,7 @@ h3 { display: inline-block; font-family: var(--code-font); - font-size: 1.25rem; + font-size: 1rem; letter-spacing: -0.025rem; margin: 0; max-width: 100%; diff --git a/adev/src/app/features/references/api-reference-details-page/api-reference-details-page.component.ts b/adev/src/app/features/references/api-reference-details-page/api-reference-details-page.component.ts index ca818e421a1..5593f197fba 100644 --- a/adev/src/app/features/references/api-reference-details-page/api-reference-details-page.component.ts +++ b/adev/src/app/features/references/api-reference-details-page/api-reference-details-page.component.ts @@ -6,14 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import { - ChangeDetectionStrategy, - Component, - afterNextRender, - inject, - input, - computed, -} from '@angular/core'; +import {ChangeDetectionStrategy, Component, inject, input, computed} from '@angular/core'; import {DOCUMENT} from '@angular/common'; import {MatTabsModule} from '@angular/material/tabs'; import {DocContent, DocViewer} from '@angular/docs'; @@ -26,7 +19,6 @@ import { API_REFERENCE_TAB_ATTRIBUTE, API_REFERENCE_TAB_API_LABEL, API_TAB_CLASS_NAME, - API_REFERENCE_TAB_BODY_CLASS_NAME, API_REFERENCE_TAB_URL_ATTRIBUTE, } from '../constants/api-reference-prerender.constants'; import {AppScroller} from '../../../app-scroller'; @@ -51,7 +43,6 @@ export default class ApiReferenceDetailsPage { // aliases ApiItemType = ApiItemType; - membersMarginTopInPx = this.scrollHandler.membersMarginTopInPx; // computed state parsedDocContent = computed(() => { @@ -107,11 +98,6 @@ export default class ApiReferenceDetailsPage { constructor() { this.appScroller.disableScrolling = true; - afterNextRender({ - write: () => { - this.scrollHandler.updateMembersMarginTop(API_REFERENCE_TAB_BODY_CLASS_NAME); - }, - }); } ngOnDestroy() { diff --git a/adev/src/app/features/references/cli-reference-details-page/cli-reference-details-page.component.scss b/adev/src/app/features/references/cli-reference-details-page/cli-reference-details-page.component.scss index a47eb078a0f..e1d1fcc38e8 100644 --- a/adev/src/app/features/references/cli-reference-details-page/cli-reference-details-page.component.scss +++ b/adev/src/app/features/references/cli-reference-details-page/cli-reference-details-page.component.scss @@ -15,26 +15,25 @@ .adev-header-and-tabs { &.adev-cli-content { - width: 50%; + width: 100%; + max-width: var(--page-width); + @include mq.for-desktop-down { - width: 100%; + max-width: none; } } } .adev-cli-members-container { - width: 50%; - padding: 0 var(--layout-padding) 1rem 0; - - margin-top: 8.3125rem; + padding: 0 0 var(--layout-padding) var(--layout-padding); padding-bottom: 1rem; box-sizing: border-box; - max-width: 60ch; + max-width: var(--page-width); + @include mq.for-desktop-down { width: 100%; padding: var(--layout-padding); - // override js applied margin top - margin-block-start: 0 !important; + padding-top: 0; max-width: none; } } @@ -103,7 +102,10 @@ background: transparent; border-radius: 0.25rem; position: relative; - transition: color 0.3s ease, background 0.3s ease, border 0.3s ease; + transition: + color 0.3s ease, + background 0.3s ease, + border 0.3s ease; &:hover { color: var(--primary-contrast); diff --git a/adev/src/app/features/references/cli-reference-details-page/cli-reference-details-page.component.ts b/adev/src/app/features/references/cli-reference-details-page/cli-reference-details-page.component.ts index 83c80078289..75414c36164 100644 --- a/adev/src/app/features/references/cli-reference-details-page/cli-reference-details-page.component.ts +++ b/adev/src/app/features/references/cli-reference-details-page/cli-reference-details-page.component.ts @@ -44,7 +44,6 @@ export default class CliReferenceDetailsPage implements OnInit { cardsInnerHtml = signal(''); mainContentInnerHtml = signal(''); - membersMarginTopInPx = this.scrollHandler.membersMarginTopInPx; ngOnInit(): void { this.setPageContent(); @@ -52,7 +51,6 @@ export default class CliReferenceDetailsPage implements OnInit { contentLoaded(): void { this.scrollHandler.setupListeners(CLI_TOC); - this.scrollHandler.updateMembersMarginTop(CLI_TOC); } // Fetch the content for CLI Reference page based on the active route. diff --git a/adev/src/app/features/references/constants/api-reference-prerender.constants.ts b/adev/src/app/features/references/constants/api-reference-prerender.constants.ts index 7ec2777028d..f0a9dd731e2 100644 --- a/adev/src/app/features/references/constants/api-reference-prerender.constants.ts +++ b/adev/src/app/features/references/constants/api-reference-prerender.constants.ts @@ -6,16 +6,10 @@ * found in the LICENSE file at https://angular.dev/license */ -export const API_REFERENCE_DETAILS_PAGE_CONTENT_CLASS_NAME = '.adev-reference-content'; export const API_REFERENCE_DETAILS_PAGE_HEADER_CLASS_NAME = '.docs-reference-header'; export const API_REFERENCE_DETAILS_PAGE_MEMBERS_CLASS_NAME = '.docs-reference-members-container'; -export const API_REFERENCE_TAB_BODY_CLASS_NAME = '.adev-reference-tab-body'; export const API_REFERENCE_TAB_ATTRIBUTE = 'data-tab'; export const API_REFERENCE_TAB_URL_ATTRIBUTE = 'data-tab-url'; export const API_REFERENCE_TAB_API_LABEL = 'API'; -export const API_REFERENCE_TAB_QUERY_PARAM = 'tab'; export const API_TAB_CLASS_NAME = '.docs-reference-api-tab'; -export const API_REFERENCE_MEMBER_CARD_CLASS_NAME = '.docs-reference-member-card'; -export const API_TAB_ACTIVE_CODE_LINE = 'shiki-ln-line-highlighted'; -export const HIGHLIGHT_JS_CODE_LINE_CLASS_NAME = 'shiki-ln-line'; export const MEMBER_ID_ATTRIBUTE = 'member-id'; diff --git a/adev/src/app/features/references/services/reference-scroll-handler.service.ts b/adev/src/app/features/references/services/reference-scroll-handler.service.ts index 6bad630401e..afd894e9caf 100644 --- a/adev/src/app/features/references/services/reference-scroll-handler.service.ts +++ b/adev/src/app/features/references/services/reference-scroll-handler.service.ts @@ -7,60 +7,32 @@ */ import {DOCUMENT, isPlatformBrowser} from '@angular/common'; -import { - DestroyRef, - EnvironmentInjector, - Injectable, - OnDestroy, - PLATFORM_ID, - afterNextRender, - inject, - signal, -} from '@angular/core'; +import {DestroyRef, Injectable, PLATFORM_ID, inject} from '@angular/core'; import {takeUntilDestroyed} from '@angular/core/rxjs-interop'; import {fromEvent} from 'rxjs'; -import {auditTime} from 'rxjs/operators'; -import { - API_REFERENCE_DETAILS_PAGE_MEMBERS_CLASS_NAME, - API_REFERENCE_MEMBER_CARD_CLASS_NAME, - API_TAB_ACTIVE_CODE_LINE, - MEMBER_ID_ATTRIBUTE, -} from '../constants/api-reference-prerender.constants'; +import {MEMBER_ID_ATTRIBUTE} from '../constants/api-reference-prerender.constants'; import {WINDOW} from '@angular/docs'; import {Router} from '@angular/router'; import {AppScroller} from '../../../app-scroller'; -export const SCROLL_EVENT_DELAY = 20; -export const SCROLL_THRESHOLD = 20; +// Adds some space/margin between the top of the target element and the top of viewport. +const SCROLL_MARGIN_TOP = 100; @Injectable() -export class ReferenceScrollHandler implements OnDestroy { +export class ReferenceScrollHandler { private readonly destroyRef = inject(DestroyRef); private readonly document = inject(DOCUMENT); - private readonly injector = inject(EnvironmentInjector); private readonly window = inject(WINDOW); private readonly router = inject(Router); private readonly appScroller = inject(AppScroller); private readonly isBrowser = isPlatformBrowser(inject(PLATFORM_ID)); - private readonly cardOffsetTop = new Map(); - private resizeObserver: ResizeObserver | null = null; - - membersMarginTopInPx = signal(0); - - ngOnDestroy(): void { - this.resizeObserver?.disconnect(); - } - setupListeners(tocSelector: string): void { if (!this.isBrowser) { return; } this.setupCodeToCListeners(tocSelector); - this.setupMemberCardListeners(); - this.setScrollEventHandlers(); - this.listenToResizeCardContainer(); this.setupFragmentChangeListener(); } @@ -76,22 +48,11 @@ export class ReferenceScrollHandler implements OnDestroy { } const card = this.document.getElementById(fragment) as HTMLDivElement | null; + card?.focus(); this.scrollToCard(card); }); } - updateMembersMarginTop(selectorOfTheElementToAlign: string): void { - if (!this.isBrowser) { - return; - } - - const elementToAlign = this.document.querySelector(selectorOfTheElementToAlign); - - if (elementToAlign) { - this.updateMarginTopWhenTabBodyIsResized(elementToAlign); - } - } - private setupCodeToCListeners(tocSelector: string): void { const tocContainer = this.document.querySelector(tocSelector); @@ -120,89 +81,6 @@ export class ReferenceScrollHandler implements OnDestroy { }); } - private setupMemberCardListeners(): void { - this.getAllMemberCards().forEach((card) => { - this.cardOffsetTop.set(card.id, card.offsetTop); - const header = card.querySelector('header'); - - if (!header) { - return; - } - fromEvent(header, 'click') - .pipe(takeUntilDestroyed(this.destroyRef)) - .subscribe((event) => { - const target = event.target as HTMLElement; - if (target instanceof HTMLAnchorElement) { - return; - } - - this.router.navigate([], {fragment: card.id, replaceUrl: true}); - }); - }); - } - - private setScrollEventHandlers(): void { - const scroll$ = fromEvent(this.document, 'scroll').pipe( - auditTime(SCROLL_EVENT_DELAY), - takeUntilDestroyed(this.destroyRef), - ); - - scroll$.subscribe(() => this.setActiveCodeLine()); - } - - private listenToResizeCardContainer(): void { - const membersCardContainer = this.document.querySelector( - API_REFERENCE_DETAILS_PAGE_MEMBERS_CLASS_NAME, - ); - if (membersCardContainer) { - afterNextRender( - () => { - const resizeObserver = new ResizeObserver(() => { - this.updateCardsOffsetTop(); - this.setActiveCodeLine(); - }); - resizeObserver.observe(membersCardContainer); - this.destroyRef.onDestroy(() => resizeObserver.disconnect()); - }, - {injector: this.injector}, - ); - } - } - - private setActiveCodeLine(): void { - const activeCard = Array.from(this.cardOffsetTop) - .filter(([_, offsetTop]) => { - return offsetTop < this.window.scrollY + this.membersMarginTopInPx() + SCROLL_THRESHOLD; - }) - .pop(); - - if (!activeCard) { - return; - } - - const activeLines = this.document.querySelectorAll( - `button.${API_TAB_ACTIVE_CODE_LINE}`, - ); - - const activeLine = activeLines.length > 0 ? activeLines.item(0) : null; - const previousActiveMemberId = this.getMemberId(activeLine); - const currentActiveMemberId = activeCard[0]; - - if (previousActiveMemberId && previousActiveMemberId !== currentActiveMemberId) { - for (const line of Array.from(activeLines)) { - line.classList.remove(API_TAB_ACTIVE_CODE_LINE); - } - } else { - const lines = this.document.querySelectorAll( - `button[${MEMBER_ID_ATTRIBUTE}="${currentActiveMemberId}"]`, - ); - for (const line of Array.from(lines)) { - line.classList.add(API_TAB_ACTIVE_CODE_LINE); - } - this.document.getElementById(`${currentActiveMemberId}`)?.focus({preventScroll: true}); - } - } - private scrollToCard(card: HTMLDivElement | null): void { if (!card) { return; @@ -213,23 +91,11 @@ export class ReferenceScrollHandler implements OnDestroy { } this.window.scrollTo({ - top: card!.offsetTop - this.membersMarginTopInPx(), + top: card!.offsetTop - SCROLL_MARGIN_TOP, behavior: 'smooth', }); } - private updateCardsOffsetTop(): void { - this.getAllMemberCards().forEach((card) => { - this.cardOffsetTop.set(card.id, card.offsetTop); - }); - } - - private getAllMemberCards(): NodeListOf { - return this.document.querySelectorAll( - `${API_REFERENCE_MEMBER_CARD_CLASS_NAME}`, - ); - } - private getMemberId(lineButton: HTMLButtonElement | null): string | undefined { if (!lineButton) { return undefined; @@ -237,19 +103,6 @@ export class ReferenceScrollHandler implements OnDestroy { return lineButton.attributes.getNamedItem(MEMBER_ID_ATTRIBUTE)?.value; } - private updateMarginTopWhenTabBodyIsResized(tabBody: HTMLElement): void { - this.resizeObserver?.disconnect(); - - this.resizeObserver = new ResizeObserver((_) => { - const offsetTop = tabBody.getBoundingClientRect().top; - if (offsetTop) { - this.membersMarginTopInPx.set(offsetTop); - } - }); - - this.resizeObserver.observe(tabBody); - } - private findButtonElement(element: HTMLElement) { let parent = element.parentElement;