diff --git a/adev/angular.json b/adev/angular.json
index 2b35f6f1c46..37dd4969837 100644
--- a/adev/angular.json
+++ b/adev/angular.json
@@ -105,36 +105,6 @@
}
}
}
- },
- "shared": {
- "projectType": "library",
- "root": "projects/shared",
- "sourceRoot": "projects/shared/src",
- "prefix": "lib",
- "architect": {
- "build": {
- "builder": "@angular-devkit/build-angular:ng-packagr",
- "options": {
- "project": "projects/shared/ng-package.json"
- },
- "configurations": {
- "production": {
- "tsConfig": "projects/shared/tsconfig.lib.prod.json"
- },
- "development": {
- "tsConfig": "projects/shared/tsconfig.lib.json"
- }
- },
- "defaultConfiguration": "production"
- },
- "test": {
- "builder": "@angular-devkit/build-angular:karma",
- "options": {
- "tsConfig": "projects/shared/tsconfig.spec.json",
- "polyfills": ["zone.js", "zone.js/testing"]
- }
- }
- }
}
},
"cli": {
diff --git a/adev/prerender/checks/check-links.ts b/adev/prerender/checks/check-links.ts
index bea0f00a0f9..426486ca98e 100644
--- a/adev/prerender/checks/check-links.ts
+++ b/adev/prerender/checks/check-links.ts
@@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.dev/license
*/
/* tslint:disable:no-console */
-import {NavigationItem} from '@angular/docs-shared';
+import {NavigationItem} from '@angular/docs';
import {readFileSync} from 'fs';
import {glob} from 'glob';
import {join} from 'path';
diff --git a/adev/prerender/tutorials/generate-tutorials-routes.ts b/adev/prerender/tutorials/generate-tutorials-routes.ts
index e9106d6aef8..6f896e69ed8 100644
--- a/adev/prerender/tutorials/generate-tutorials-routes.ts
+++ b/adev/prerender/tutorials/generate-tutorials-routes.ts
@@ -5,7 +5,7 @@
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
-import {NavigationItem} from '@angular/docs-shared';
+import {NavigationItem} from '@angular/docs';
import {PagePrefix} from '../../src/app/core/enums/pages';
diff --git a/adev/prerender/tutorials/tutorials-types.ts b/adev/prerender/tutorials/tutorials-types.ts
index 01a92b71747..81b82026f46 100644
--- a/adev/prerender/tutorials/tutorials-types.ts
+++ b/adev/prerender/tutorials/tutorials-types.ts
@@ -8,7 +8,7 @@
import type {FileSystemTree} from '@webcontainer/api';
import type {TutorialNavigationItemWithStep} from './generate-tutorials-routes';
-import {NavigationItem} from '@angular/docs-shared';
+import {NavigationItem} from '@angular/docs';
import {TutorialType} from './utils/web-constants';
/**
diff --git a/adev/shared/ng-package.json b/adev/shared/ng-package.json
deleted file mode 100644
index 1aed3740f57..00000000000
--- a/adev/shared/ng-package.json
+++ /dev/null
@@ -1,7 +0,0 @@
-{
- "$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
- "dest": "../../dist/shared",
- "lib": {
- "entryFile": "src/public-api.ts"
- }
-}
diff --git a/adev/shared/package.json b/adev/shared/package.json
deleted file mode 100644
index ae5206413fb..00000000000
--- a/adev/shared/package.json
+++ /dev/null
@@ -1,12 +0,0 @@
-{
- "name": "shared",
- "version": "0.0.1",
- "peerDependencies": {
- "@angular/common": "^16.0.1",
- "@angular/core": "^16.0.1"
- },
- "dependencies": {
- "tslib": "^2.3.0"
- },
- "sideEffects": false
-}
diff --git a/adev/shared/src/lib/components/algolia-icon/algolia-icon.component.html b/adev/shared/src/lib/components/algolia-icon/algolia-icon.component.html
deleted file mode 100644
index fc7b5fc15f0..00000000000
--- a/adev/shared/src/lib/components/algolia-icon/algolia-icon.component.html
+++ /dev/null
@@ -1,52 +0,0 @@
-
-
diff --git a/adev/shared/src/lib/components/algolia-icon/algolia-icon.component.ts b/adev/shared/src/lib/components/algolia-icon/algolia-icon.component.ts
deleted file mode 100644
index b0b469691ef..00000000000
--- a/adev/shared/src/lib/components/algolia-icon/algolia-icon.component.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-/*!
- * @license
- * Copyright Google LLC All Rights Reserved.
- *
- * Use of this source code is governed by an MIT-style license that can be
- * found in the LICENSE file at https://angular.dev/license
- */
-
-import {Component} from '@angular/core';
-
-@Component({
- selector: 'docs-algolia-icon',
- standalone: true,
- imports: [],
- templateUrl: './algolia-icon.component.html',
-})
-export class AlgoliaIcon {}
diff --git a/adev/shared/src/lib/components/breadcrumb/breadcrumb.component.html b/adev/shared/src/lib/components/breadcrumb/breadcrumb.component.html
deleted file mode 100644
index 5cf1378ed22..00000000000
--- a/adev/shared/src/lib/components/breadcrumb/breadcrumb.component.html
+++ /dev/null
@@ -1,13 +0,0 @@
-@for (breadcrumb of breadcrumbItems(); track breadcrumb) {
-
-}
diff --git a/adev/shared/src/lib/components/breadcrumb/breadcrumb.component.scss b/adev/shared/src/lib/components/breadcrumb/breadcrumb.component.scss
deleted file mode 100644
index fc5e6889d6b..00000000000
--- a/adev/shared/src/lib/components/breadcrumb/breadcrumb.component.scss
+++ /dev/null
@@ -1,25 +0,0 @@
-:host {
- display: flex;
- align-items: center;
- padding-block-end: 1.5rem;
-}
-
-.docs-breadcrumb {
- span {
- color: var(--quaternary-contrast);
- font-size: 0.875rem;
- display: flex;
- align-items: center;
- }
-
- &:not(:last-child) {
- span {
- &::after {
- content: 'chevron_right';
- font-family: var(--icons);
- margin-inline: 0.5rem;
- color: var(--quinary-contrast);
- }
- }
- }
-}
diff --git a/adev/shared/src/lib/components/breadcrumb/breadcrumb.component.spec.ts b/adev/shared/src/lib/components/breadcrumb/breadcrumb.component.spec.ts
deleted file mode 100644
index efbffe9177c..00000000000
--- a/adev/shared/src/lib/components/breadcrumb/breadcrumb.component.spec.ts
+++ /dev/null
@@ -1,29 +0,0 @@
-/*!
- * @license
- * Copyright Google LLC All Rights Reserved.
- *
- * Use of this source code is governed by an MIT-style license that can be
- * found in the LICENSE file at https://angular.dev/license
- */
-
-import {ComponentFixture, TestBed} from '@angular/core/testing';
-
-import {Breadcrumb} from './breadcrumb.component';
-
-describe('Breadcrumb', () => {
- let component: Breadcrumb;
- let fixture: ComponentFixture;
-
- beforeEach(() => {
- TestBed.configureTestingModule({
- imports: [Breadcrumb],
- });
- fixture = TestBed.createComponent(Breadcrumb);
- component = fixture.componentInstance;
- fixture.detectChanges();
- });
-
- it('should create', () => {
- expect(component).toBeTruthy();
- });
-});
diff --git a/adev/shared/src/lib/components/breadcrumb/breadcrumb.component.ts b/adev/shared/src/lib/components/breadcrumb/breadcrumb.component.ts
deleted file mode 100644
index 6e4272d7412..00000000000
--- a/adev/shared/src/lib/components/breadcrumb/breadcrumb.component.ts
+++ /dev/null
@@ -1,50 +0,0 @@
-/*!
- * @license
- * Copyright Google LLC All Rights Reserved.
- *
- * Use of this source code is governed by an MIT-style license that can be
- * found in the LICENSE file at https://angular.dev/license
- */
-
-import {ChangeDetectionStrategy, Component, OnInit, inject, signal} from '@angular/core';
-import {NavigationState} from '../../services';
-import {NavigationItem} from '../../interfaces';
-import {NgFor, NgIf} from '@angular/common';
-import {RouterLink} from '@angular/router';
-
-@Component({
- selector: 'docs-breadcrumb',
- standalone: true,
- imports: [NgIf, NgFor, RouterLink],
- templateUrl: './breadcrumb.component.html',
- styleUrls: ['./breadcrumb.component.scss'],
- changeDetection: ChangeDetectionStrategy.OnPush,
-})
-export class Breadcrumb implements OnInit {
- private readonly navigationState = inject(NavigationState);
-
- breadcrumbItems = signal([]);
-
- ngOnInit(): void {
- this.setBreadcrumbItemsBasedOnNavigationStructure();
- }
-
- private setBreadcrumbItemsBasedOnNavigationStructure(): void {
- let breadcrumbs: NavigationItem[] = [];
-
- const traverse = (node: NavigationItem | null) => {
- if (!node) {
- return;
- }
-
- if (node.parent) {
- breadcrumbs = [node.parent, ...breadcrumbs];
- traverse(node.parent);
- }
- };
-
- traverse(this.navigationState.activeNavigationItem());
-
- this.breadcrumbItems.set(breadcrumbs);
- }
-}
diff --git a/adev/shared/src/lib/components/cookie-popup/cookie-popup.component.html b/adev/shared/src/lib/components/cookie-popup/cookie-popup.component.html
deleted file mode 100644
index ca0af01fd6e..00000000000
--- a/adev/shared/src/lib/components/cookie-popup/cookie-popup.component.html
+++ /dev/null
@@ -1,22 +0,0 @@
-@if (!hasAccepted()) {
-
-}
diff --git a/adev/shared/src/lib/components/cookie-popup/cookie-popup.component.scss b/adev/shared/src/lib/components/cookie-popup/cookie-popup.component.scss
deleted file mode 100644
index 65fab67d4e7..00000000000
--- a/adev/shared/src/lib/components/cookie-popup/cookie-popup.component.scss
+++ /dev/null
@@ -1,40 +0,0 @@
-:host {
- position: fixed;
- bottom: 0.5rem;
- right: 0.5rem;
- z-index: var(--z-index-cookie-consent);
- opacity: 0;
- visibility: hidden;
- animation: 1s linear forwards 0.5s fadeIn;
-}
-
-.docs-cookies-popup {
- padding: 1rem;
- background-color: var(--page-background);
- border: 1px solid var(--senary-contrast);
- border-radius: 0.25rem;
- font-size: 0.875rem;
- max-width: 265px;
- transition: background-color 0.3s ease, border-color 0.3s ease, color 0.3s ease;
- box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.1);
-
- > div {
- display: flex;
- gap: 0.5rem;
- align-items: center;
- width: 100%;
- margin-block-start: 1rem;
- }
-
- p {
- margin-block: 0;
- color: var(--primary-contrast);
- }
-}
-
-@keyframes fadeIn {
- 100% {
- opacity: 100%;
- visibility: visible;
- }
-}
diff --git a/adev/shared/src/lib/components/cookie-popup/cookie-popup.component.spec.ts b/adev/shared/src/lib/components/cookie-popup/cookie-popup.component.spec.ts
deleted file mode 100644
index 70e78b1930b..00000000000
--- a/adev/shared/src/lib/components/cookie-popup/cookie-popup.component.spec.ts
+++ /dev/null
@@ -1,80 +0,0 @@
-/*!
- * @license
- * Copyright Google LLC All Rights Reserved.
- *
- * Use of this source code is governed by an MIT-style license that can be
- * found in the LICENSE file at https://angular.dev/license
- */
-
-import {ComponentFixture, TestBed} from '@angular/core/testing';
-
-import {CookiePopup, STORAGE_KEY} from './cookie-popup.component';
-import {LOCAL_STORAGE} from '../../providers';
-import {MockLocalStorage} from '../../utils/test.utils';
-
-describe('CookiePopup', () => {
- let fixture: ComponentFixture;
- let mockLocalStorage = new MockLocalStorage();
-
- beforeEach(() => {
- TestBed.configureTestingModule({
- imports: [CookiePopup],
- providers: [
- {
- provide: LOCAL_STORAGE,
- useValue: mockLocalStorage,
- },
- ],
- });
- });
-
- it('should make the popup visible by default', () => {
- initComponent(false);
-
- expect(getCookiesPopup()).not.toBeNull();
- });
-
- it('should hide the cookies popup if the user has already accepted cookies', () => {
- initComponent(true);
-
- expect(getCookiesPopup()).toBeNull();
- });
-
- it('should hide the cookies popup', () => {
- initComponent(false);
-
- accept();
-
- fixture.detectChanges();
-
- expect(getCookiesPopup()).toBeNull();
- });
-
- it('should store the user confirmation', () => {
- initComponent(false);
-
- expect(mockLocalStorage.getItem(STORAGE_KEY)).toBeNull();
-
- accept();
-
- expect(mockLocalStorage.getItem(STORAGE_KEY)).toBe('true');
- });
-
- // Helpers
- function getCookiesPopup() {
- return (fixture.nativeElement as HTMLElement).querySelector('.docs-cookies-popup');
- }
-
- function accept() {
- (fixture.nativeElement as HTMLElement)
- .querySelector('button[text="Ok, Got it"]')
- ?.click();
- }
-
- function initComponent(cookiesAccepted: boolean) {
- mockLocalStorage.setItem(STORAGE_KEY, cookiesAccepted ? 'true' : null);
- fixture = TestBed.createComponent(CookiePopup);
-
- fixture.detectChanges();
- }
-});
diff --git a/adev/shared/src/lib/components/cookie-popup/cookie-popup.component.ts b/adev/shared/src/lib/components/cookie-popup/cookie-popup.component.ts
deleted file mode 100644
index d83482c109d..00000000000
--- a/adev/shared/src/lib/components/cookie-popup/cookie-popup.component.ts
+++ /dev/null
@@ -1,47 +0,0 @@
-/*!
- * @license
- * Copyright Google LLC All Rights Reserved.
- *
- * Use of this source code is governed by an MIT-style license that can be
- * found in the LICENSE file at https://angular.dev/license
- */
-
-import {ChangeDetectionStrategy, Component, inject, signal} from '@angular/core';
-import {NgIf} from '@angular/common';
-import {LOCAL_STORAGE} from '../../providers';
-
-export const STORAGE_KEY = 'docs-accepts-cookies';
-
-@Component({
- selector: 'docs-cookie-popup',
- standalone: true,
- imports: [NgIf],
- templateUrl: './cookie-popup.component.html',
- styleUrls: ['./cookie-popup.component.scss'],
- changeDetection: ChangeDetectionStrategy.OnPush,
-})
-export class CookiePopup {
- private readonly localStorage = inject(LOCAL_STORAGE);
-
- /** Whether the user has accepted the cookie disclaimer. */
- hasAccepted = signal(false);
-
- constructor() {
- // Needs to be in a try/catch, because some browsers will
- // throw when using `localStorage` in private mode.
- try {
- this.hasAccepted.set(this.localStorage?.getItem(STORAGE_KEY) === 'true');
- } catch {
- this.hasAccepted.set(false);
- }
- }
-
- /** Accepts the cookie disclaimer. */
- protected accept(): void {
- try {
- this.localStorage?.setItem(STORAGE_KEY, 'true');
- } catch {}
-
- this.hasAccepted.set(true);
- }
-}
diff --git a/adev/shared/src/lib/components/copy-source-code-button/copy-source-code-button.component.html b/adev/shared/src/lib/components/copy-source-code-button/copy-source-code-button.component.html
deleted file mode 100644
index a0ef47cc928..00000000000
--- a/adev/shared/src/lib/components/copy-source-code-button/copy-source-code-button.component.html
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
-
-
-check
diff --git a/adev/shared/src/lib/components/copy-source-code-button/copy-source-code-button.component.spec.ts b/adev/shared/src/lib/components/copy-source-code-button/copy-source-code-button.component.spec.ts
deleted file mode 100644
index 425995f858c..00000000000
--- a/adev/shared/src/lib/components/copy-source-code-button/copy-source-code-button.component.spec.ts
+++ /dev/null
@@ -1,120 +0,0 @@
-/*!
- * @license
- * Copyright Google LLC All Rights Reserved.
- *
- * Use of this source code is governed by an MIT-style license that can be
- * found in the LICENSE file at https://angular.dev/license
- */
-
-import {ComponentFixture, TestBed, fakeAsync, tick} from '@angular/core/testing';
-
-import {
- CONFIRMATION_DISPLAY_TIME_MS,
- CopySourceCodeButton,
-} from './copy-source-code-button.component';
-import {Component, Input} from '@angular/core';
-import {By} from '@angular/platform-browser';
-import {Clipboard} from '@angular/cdk/clipboard';
-
-const SUCCESSFULLY_COPY_CLASS_NAME = 'docs-copy-source-code-button-success';
-const FAILED_COPY_CLASS_NAME = 'docs-copy-source-code-button-failed';
-
-describe('CopySourceCodeButton', () => {
- let component: CodeSnippetWrapper;
- let fixture: ComponentFixture;
- let copySpy: jasmine.Spy<(text: string) => boolean>;
-
- beforeEach(() => {
- TestBed.configureTestingModule({
- imports: [CodeSnippetWrapper],
- });
- fixture = TestBed.createComponent(CodeSnippetWrapper);
- component = fixture.componentInstance;
- fixture.detectChanges();
- });
-
- beforeEach(() => {
- const clipboardService = TestBed.inject(Clipboard);
- copySpy = spyOn(clipboardService, 'copy');
- });
-
- it('should call clipboard service when clicked on copy source code', () => {
- const expectedCodeToBeCopied = 'npm install -g @angular/cli';
- component.code = expectedCodeToBeCopied;
-
- fixture.detectChanges();
-
- const button = fixture.debugElement.query(By.directive(CopySourceCodeButton)).nativeElement;
- button.click();
-
- expect(copySpy.calls.argsFor(0)[0].trim()).toBe(expectedCodeToBeCopied);
- });
-
- it('should not copy lines marked as deleted when code snippet contains diff', () => {
- const codeInHtmlFormat = `
-
- <div *ngFor="let product of products">
- <div *ngFor="let product of products()">
-
- `;
- const expectedCodeToBeCopied = ``;
- component.code = codeInHtmlFormat;
-
- fixture.detectChanges();
-
- const button = fixture.debugElement.query(By.directive(CopySourceCodeButton)).nativeElement;
- button.click();
-
- expect(copySpy.calls.argsFor(0)[0].trim()).toBe(expectedCodeToBeCopied);
- });
-
- it(`should set ${SUCCESSFULLY_COPY_CLASS_NAME} for ${CONFIRMATION_DISPLAY_TIME_MS} ms when copy was executed properly`, fakeAsync(() => {
- component.code = 'example';
-
- fixture.detectChanges();
-
- const button = fixture.debugElement.query(By.directive(CopySourceCodeButton)).nativeElement;
- button.click();
- fixture.detectChanges();
-
- expect(button).toHaveClass(SUCCESSFULLY_COPY_CLASS_NAME);
-
- tick(CONFIRMATION_DISPLAY_TIME_MS);
- fixture.detectChanges();
-
- expect(button).not.toHaveClass(SUCCESSFULLY_COPY_CLASS_NAME);
- }));
-
- it(`should set ${FAILED_COPY_CLASS_NAME} for ${CONFIRMATION_DISPLAY_TIME_MS} ms when copy failed`, fakeAsync(() => {
- component.code = 'example';
- copySpy.and.throwError('Fake copy error');
-
- fixture.detectChanges();
-
- const button = fixture.debugElement.query(By.directive(CopySourceCodeButton)).nativeElement;
- button.click();
-
- fixture.detectChanges();
-
- expect(button).toHaveClass(FAILED_COPY_CLASS_NAME);
-
- tick(CONFIRMATION_DISPLAY_TIME_MS);
- fixture.detectChanges();
-
- expect(button).not.toHaveClass(FAILED_COPY_CLASS_NAME);
- }));
-});
-
-@Component({
- template: `
-
-
-
-
- `,
- imports: [CopySourceCodeButton],
- standalone: true,
-})
-class CodeSnippetWrapper {
- @Input({required: true}) code!: string;
-}
diff --git a/adev/shared/src/lib/components/copy-source-code-button/copy-source-code-button.component.ts b/adev/shared/src/lib/components/copy-source-code-button/copy-source-code-button.component.ts
deleted file mode 100644
index 402245d2fbb..00000000000
--- a/adev/shared/src/lib/components/copy-source-code-button/copy-source-code-button.component.ts
+++ /dev/null
@@ -1,89 +0,0 @@
-/*!
- * @license
- * Copyright Google LLC All Rights Reserved.
- *
- * Use of this source code is governed by an MIT-style license that can be
- * found in the LICENSE file at https://angular.dev/license
- */
-
-import {
- ChangeDetectionStrategy,
- ChangeDetectorRef,
- Component,
- ElementRef,
- WritableSignal,
- inject,
- signal,
-} from '@angular/core';
-import {CommonModule} from '@angular/common';
-import {Clipboard} from '@angular/cdk/clipboard';
-import {IconComponent} from '../icon/icon.component';
-
-export const REMOVED_LINE_CLASS_NAME = '.hljs-ln-line.remove';
-export const CONFIRMATION_DISPLAY_TIME_MS = 2000;
-
-@Component({
- selector: 'button[docs-copy-source-code]',
- standalone: true,
- imports: [CommonModule, IconComponent],
- templateUrl: './copy-source-code-button.component.html',
- host: {
- 'type': 'button',
- 'aria-label': 'Copy example source to clipboard',
- 'title': 'Copy example source',
- '(click)': 'copySourceCode()',
- '[class.docs-copy-source-code-button-success]': 'showCopySuccess()',
- '[class.docs-copy-source-code-button-failed]': 'showCopyFailure()',
- },
- changeDetection: ChangeDetectionStrategy.OnPush,
-})
-export class CopySourceCodeButton {
- private readonly changeDetector = inject(ChangeDetectorRef);
- private readonly clipboard = inject(Clipboard);
- private readonly elementRef = inject(ElementRef);
-
- protected readonly showCopySuccess = signal(false);
- protected readonly showCopyFailure = signal(false);
-
- copySourceCode(): void {
- try {
- const codeElement = this.elementRef.nativeElement.parentElement.querySelector(
- 'code',
- ) as HTMLElement;
- const sourceCode = this.getSourceCode(codeElement);
- this.clipboard.copy(sourceCode);
- this.showResult(this.showCopySuccess);
- } catch {
- this.showResult(this.showCopyFailure);
- }
- }
-
- private getSourceCode(codeElement: HTMLElement): string {
- this.showCopySuccess.set(false);
- this.showCopyFailure.set(false);
-
- const removedLines: NodeList = codeElement.querySelectorAll(REMOVED_LINE_CLASS_NAME);
-
- if (removedLines.length) {
- // Get only those lines which are not marked as removed
- const formattedText = Array.from(codeElement.querySelectorAll('.hljs-ln-line:not(.remove)'))
- .map((line) => (line as HTMLDivElement).innerText)
- .join('\n');
-
- return formattedText.trim();
- } else {
- const text: string = codeElement.innerText || '';
- return text.replaceAll(`\n\n\n`, ``).trim();
- }
- }
-
- private showResult(messageState: WritableSignal
) {
- messageState.set(true);
-
- setTimeout(() => {
- messageState.set(false);
- // It's required for code snippets embedded in the ExampleViewer.
- this.changeDetector.markForCheck();
- }, CONFIRMATION_DISPLAY_TIME_MS);
- }
-}
diff --git a/adev/shared/src/lib/components/docs-viewer/docs-viewer.component.scss b/adev/shared/src/lib/components/docs-viewer/docs-viewer.component.scss
deleted file mode 100644
index b5b034cc957..00000000000
--- a/adev/shared/src/lib/components/docs-viewer/docs-viewer.component.scss
+++ /dev/null
@@ -1,145 +0,0 @@
-@use '../../styles/links' as links;
-
-:host {
- --translate-y: clamp(5px, 0.25em, 7px);
-}
-
-.docs-viewer {
- display: flex;
- flex-direction: column;
- padding: var(--layout-padding);
- max-width: var(--page-width);
- width: 100%;
- box-sizing: border-box;
-
- @media only screen and (max-width: 1430px) {
- container: docs-content / inline-size;
- }
-
- // If rendered on the docs page, accommodate width for TOC
- adev-docs & {
- @media only screen and (min-width: 1430px) and (max-width: 1550px) {
- width: calc(100% - 195px - var(--layout-padding));
- max-width: var(--page-width);
- }
- }
-
- pre {
- margin-block: 0;
- padding-block: 0.75rem;
- }
-
- h1,
- h2,
- h3,
- h4,
- h5,
- h6 {
- .docs-anchor {
- margin-block-start: 2.5rem;
- display: inline-block;
- color: inherit;
-
- &::after {
- content: '\e157'; // codepoint for "link"
- font-family: 'Material Symbols Outlined';
-
- opacity: 0;
- margin-left: 8px;
- vertical-align: middle;
-
- color: var(--quaternary-contrast);
- font-size: clamp(18px, 1.25em, 30px);
- transition: opacity 0.3s ease;
- }
- &:hover {
- &::after {
- opacity: 1;
- }
- }
- }
- }
-
- h1 {
- font-size: 2.5rem;
- margin-block-end: 0;
- }
-
- h2 {
- font-size: 2rem;
- margin-block-end: 0.5rem;
- }
-
- h3 {
- font-size: 1.5rem;
- margin-block-end: 0.5rem;
- }
-
- h4 {
- font-size: 1.25rem;
- margin-block-end: 0.5rem;
- }
-
- h5 {
- font-size: 1rem;
- margin-block-end: 0;
- }
-
- h6 {
- font-size: 0.875rem;
- margin-block-end: 0;
- }
-
- > :last-child {
- margin-block-end: 0;
- }
-
- a:not(.docs-github-links):not(.docs-card):not(.docs-pill):not(.docs-example-github-link) {
- &[href^='http:'],
- &[href^='https:'] {
- @include links.external-link-with-icon();
- }
- }
-
- &-scroll-margin-large {
- h2,
- h3 {
- scroll-margin: 5em;
- }
- }
-}
-
-.docs-header {
- margin-block-end: 1rem;
- & > p:first-child {
- color: var(--quaternary-contrast);
- font-weight: 500;
- margin: 0;
- }
-}
-
-.docs-page-title {
- display: flex;
- justify-content: space-between;
-
- h1 {
- margin-block: 0;
- font-size: 2.25rem;
- }
-
- a {
- color: var(--primary-contrast);
- height: fit-content;
-
- docs-icon {
- color: var(--gray-400);
- transition: color 0.3s ease;
- }
-
- &:hover {
- docs-icon {
- color: var(--primary-contrast);
- }
- }
- }
-}
diff --git a/adev/shared/src/lib/components/docs-viewer/docs-viewer.component.spec.ts b/adev/shared/src/lib/components/docs-viewer/docs-viewer.component.spec.ts
deleted file mode 100644
index aca1b580b21..00000000000
--- a/adev/shared/src/lib/components/docs-viewer/docs-viewer.component.spec.ts
+++ /dev/null
@@ -1,104 +0,0 @@
-/*!
- * @license
- * Copyright Google LLC All Rights Reserved.
- *
- * Use of this source code is governed by an MIT-style license that can be
- * found in the LICENSE file at https://angular.dev/license
- */
-
-import {ComponentFixture, TestBed} from '@angular/core/testing';
-import {By} from '@angular/platform-browser';
-import {NoopAnimationsModule} from '@angular/platform-browser/animations';
-import {RouterTestingModule} from '@angular/router/testing';
-import {DocContent, ExampleViewerContentLoader} from '../../interfaces';
-import {EXAMPLE_VIEWER_CONTENT_LOADER} from '../../providers';
-import {CodeExampleViewMode, ExampleViewer} from '../example-viewer/example-viewer.component';
-import {DocViewer} from './docs-viewer.component';
-
-describe('DocViewer', () => {
- let fixture: ComponentFixture;
- let exampleContentSpy: jasmine.SpyObj;
-
- const sampleDocContentWithExampleViewerPlaceholders: DocContent = {
- id: 'id',
- contents: `
-
-
- * @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
import {ChangeDetectorRef, Component, inject, signal} from '@angular/core';
import {Component, signal} from '@angular/core';
import {CommonModule} from '@angular/common';
@Component({
selector: 'hello-world',
standalone: true,
imports: [CommonModule],
templateUrl: './hello-world.html',
styleUrls: ['./hello-world.css'],
})
export default class HelloWorldComponent {
world = 'World';
world = 'World!!!';
count = signal(0);
changeDetector = inject(ChangeDetectorRef);
increase(): void {
this.count.update((previous) => {
return previous + 1;
});
this.changeDetector.detectChanges();
}
}
-
-
`,
- };
-
- const sampleDocContentWithExpandedExampleViewerPlaceholders: DocContent = {
- id: 'id',
- contents: `
-
-
- * @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
import {ChangeDetectorRef, Component, inject, signal} from '@angular/core';
import {Component, signal} from '@angular/core';
import {CommonModule} from '@angular/common';
@Component({
selector: 'hello-world',
standalone: true,
imports: [CommonModule],
templateUrl: './hello-world.html',
styleUrls: ['./hello-world.css'],
})
export default class HelloWorldComponent {
world = 'World';
world = 'World!!!';
count = signal(0);
changeDetector = inject(ChangeDetectorRef);
increase(): void {
this.count.update((previous) => {
return previous + 1;
});
this.changeDetector.detectChanges();
}
}
-
-
-
-
- <h2>Hello {{ world }}</h2>
<button (click)="increase()">Increase</button>
<p>Counter: {{ count() }}</p>
-
-
-
`,
- };
-
- beforeEach(() => {
- exampleContentSpy = jasmine.createSpyObj('ExampleContentLoader', ['getCodeExampleData']);
- });
-
- beforeEach(async () => {
- await TestBed.configureTestingModule({
- imports: [DocViewer, NoopAnimationsModule, RouterTestingModule],
- providers: [{provide: EXAMPLE_VIEWER_CONTENT_LOADER, useValue: exampleContentSpy}],
- }).compileComponents();
-
- fixture = TestBed.createComponent(DocViewer);
- fixture.detectChanges();
- });
-
- it('should load doc into innerHTML', () => {
- const fixture = TestBed.createComponent(DocViewer);
- fixture.componentRef.setInput('docContent', 'hello world');
- fixture.detectChanges();
-
- expect(fixture.nativeElement.innerHTML).toBe('hello world');
- });
-
- it('should instantiate example viewer in snippet view mode', async () => {
- const fixture = TestBed.createComponent(DocViewer);
- fixture.componentRef.setInput(
- 'docContent',
- sampleDocContentWithExampleViewerPlaceholders.contents,
- );
- fixture.detectChanges();
- await fixture.whenStable();
-
- const exampleViewer = fixture.debugElement.query(By.directive(ExampleViewer));
-
- expect(exampleViewer).not.toBeNull();
- expect(exampleViewer.componentInstance.view()).toBe(CodeExampleViewMode.SNIPPET);
- });
-
- it('should display example viewer in multi file mode when user clicks expand', async () => {
- const fixture = TestBed.createComponent(DocViewer);
- fixture.componentRef.setInput(
- 'docContent',
- sampleDocContentWithExpandedExampleViewerPlaceholders.contents,
- );
- fixture.detectChanges();
- await fixture.whenStable();
-
- const exampleViewer = fixture.debugElement.query(By.directive(ExampleViewer));
- const expandButton = fixture.debugElement.query(
- By.css('button[aria-label="Expand code example"]'),
- );
- expandButton.nativeElement.click();
-
- expect(exampleViewer).not.toBeNull();
- expect(exampleViewer.componentInstance.view()).toBe(CodeExampleViewMode.MULTI_FILE);
- expect(exampleViewer.componentInstance.tabs().length).toBe(2);
- });
-});
diff --git a/adev/shared/src/lib/components/docs-viewer/docs-viewer.component.ts b/adev/shared/src/lib/components/docs-viewer/docs-viewer.component.ts
deleted file mode 100644
index 3d1c977bc6c..00000000000
--- a/adev/shared/src/lib/components/docs-viewer/docs-viewer.component.ts
+++ /dev/null
@@ -1,332 +0,0 @@
-/*!
- * @license
- * Copyright Google LLC All Rights Reserved.
- *
- * Use of this source code is governed by an MIT-style license that can be
- * found in the LICENSE file at https://angular.dev/license
- */
-
-import {CommonModule, DOCUMENT, isPlatformBrowser, Location} from '@angular/common';
-import {
- ApplicationRef,
- ChangeDetectionStrategy,
- Component,
- ComponentRef,
- createComponent,
- DestroyRef,
- ElementRef,
- EnvironmentInjector,
- inject,
- Injector,
- Input,
- OnChanges,
- PLATFORM_ID,
- SimpleChanges,
- Type,
- ViewContainerRef,
- ViewEncapsulation,
- ɵInitialRenderPendingTasks as PendingRenderTasks,
- EventEmitter,
- Output,
-} from '@angular/core';
-import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
-import {
- handleHrefClickEventWithRouter,
- IconComponent,
- NavigationState,
- Snippet,
- TableOfContents,
- TOC_SKIP_CONTENT_MARKER,
-} from '@angular/docs-shared';
-import {Router} from '@angular/router';
-import {fromEvent} from 'rxjs';
-
-import {Breadcrumb} from '../breadcrumb/breadcrumb.component';
-import {CopySourceCodeButton} from '../copy-source-code-button/copy-source-code-button.component';
-import {ExampleViewer} from '../example-viewer/example-viewer.component';
-
-///
-
-const TOC_HOST_ELEMENT_NAME = 'docs-table-of-contents';
-export const ASSETS_EXAMPLES_PATH = 'assets/content/examples';
-export const DOCS_VIEWER_SELECTOR = 'docs-viewer';
-export const DOCS_CODE_SELECTOR = '.docs-code';
-export const DOCS_CODE_MUTLIFILE_SELECTOR = '.docs-code-multifile';
-// TODO: Update the branch/sha
-export const GITHUB_CONTENT_URL =
- 'https://github.com/angular/angular/blob/main/adev/src/content/examples/';
-
-@Component({
- selector: DOCS_VIEWER_SELECTOR,
- standalone: true,
- imports: [CommonModule],
- template: '',
- styleUrls: ['docs-viewer.component.scss'],
- changeDetection: ChangeDetectionStrategy.OnPush,
- encapsulation: ViewEncapsulation.None,
- host: {
- '[class.docs-animate-content]': 'animateContent',
- },
-})
-export class DocViewer implements OnChanges {
- @Input() docContent?: string;
- @Input() hasToc = false;
- @Output() contentLoaded = new EventEmitter();
-
- private readonly destroyRef = inject(DestroyRef);
- private readonly document = inject(DOCUMENT);
- private readonly elementRef = inject(ElementRef);
- private readonly location = inject(Location);
- private readonly navigationState = inject(NavigationState);
- private readonly platformId = inject(PLATFORM_ID);
- private readonly router = inject(Router);
- private readonly viewContainer = inject(ViewContainerRef);
- private readonly environmentInjector = inject(EnvironmentInjector);
- private readonly injector = inject(Injector);
- private readonly appRef = inject(ApplicationRef);
-
- // tslint:disable-next-line:no-unused-variable
- private animateContent = false;
- private readonly pendingRenderTasks = inject(PendingRenderTasks);
-
- private countOfExamples = 0;
-
- async ngOnChanges(changes: SimpleChanges): Promise {
- const taskId = this.pendingRenderTasks.add();
- if ('docContent' in changes) {
- await this.renderContentsAndRunClientSetup(this.docContent!);
- }
- this.pendingRenderTasks.remove(taskId);
- }
-
- async renderContentsAndRunClientSetup(content?: string): Promise {
- const isBrowser = isPlatformBrowser(this.platformId);
- const contentContainer = this.elementRef.nativeElement;
-
- if (content) {
- if (isBrowser && !(this.document as any).startViewTransition) {
- // Apply a special class to the host node to trigger animation.
- // Note: when a page is hydrated, the `content` would be empty,
- // so we don't trigger an animation to avoid a content flickering
- // visual effect. In addition, if the browser supports view transitions (startViewTransition is present), the animation is handled by the native View Transition API so it does not need to be done here.
- this.animateContent = true;
- }
-
- contentContainer.innerHTML = content;
- }
-
- if (isBrowser) {
- // First we setup event listeners on the HTML we just loaded.
- // We want to do this before things like the example viewers are loaded.
- this.setupAnchorListeners(contentContainer);
- // Rewrite relative anchors (hrefs starting with `#`) because relative hrefs are relative to the base URL, which is '/'
- this.rewriteRelativeAnchors(contentContainer);
- // In case when content contains placeholders for executable examples, create ExampleViewer components.
- await this.loadExamples();
- // In case when content contains static code snippets, then create buttons
- // responsible for copy source code.
- this.loadCopySourceCodeButtons();
- }
-
- // Display Breadcrumb component if the `` element exists
- this.loadBreadcrumb(contentContainer);
-
- // Display Icon component if the `` element exists
- this.loadIcons(contentContainer);
-
- // Render ToC
- this.renderTableOfContents(contentContainer);
-
- this.contentLoaded.next();
- }
-
- /**
- * Load ExampleViewer component when:
- * - exists docs-code-multifile element with multiple files OR
- * - exists docs-code element with single file AND
- * - 'preview' attribute was provided OR
- * - 'visibleLines' attribute was provided
- */
- private async loadExamples(): Promise {
- const multifileCodeExamples = (
- Array.from(this.elementRef.nativeElement.querySelectorAll(DOCS_CODE_MUTLIFILE_SELECTOR))
- );
-
- for (let placeholder of multifileCodeExamples) {
- const path = placeholder.getAttribute('path')!;
- const snippets = this.getCodeSnippetsFromMultifileWrapper(placeholder);
- await this.renderExampleViewerComponents(placeholder, snippets, path);
- }
-
- const docsCodeElements = this.elementRef.nativeElement.querySelectorAll(DOCS_CODE_SELECTOR);
-
- for (const placeholder of docsCodeElements) {
- const snippet = this.getStandaloneCodeSnippet(placeholder);
- if (snippet) {
- await this.renderExampleViewerComponents(placeholder, [snippet], snippet.name);
- }
- }
- }
-
- private renderTableOfContents(element: HTMLElement): void {
- if (!this.hasToc) {
- return;
- }
-
- const firstHeading = element.querySelector('h2,h3[id]');
- if (!firstHeading) {
- return;
- }
-
- // Since the content of the main area is dynamically created and there is
- // no host element for a ToC component, we create it manually.
- let tocHostElement: HTMLElement | null = element.querySelector(TOC_HOST_ELEMENT_NAME);
- if (!tocHostElement) {
- tocHostElement = this.document.createElement(TOC_HOST_ELEMENT_NAME);
- tocHostElement.setAttribute(TOC_SKIP_CONTENT_MARKER, 'true');
- firstHeading?.parentNode?.insertBefore(tocHostElement, firstHeading);
- }
-
- this.renderComponent(TableOfContents, tocHostElement, {contentSourceElement: element});
- }
-
- private async renderExampleViewerComponents(
- placeholder: HTMLElement,
- snippets: Snippet[],
- path: string,
- ): Promise {
- const preview = Boolean(placeholder.getAttribute('preview'));
- const title = placeholder.getAttribute('header') ?? undefined;
- const firstCodeSnippetTitle =
- snippets.length > 0 ? snippets[0].title ?? snippets[0].name : undefined;
- const exampleRef = this.viewContainer.createComponent(ExampleViewer);
-
- this.countOfExamples++;
- exampleRef.instance.metadata = {
- title: title ?? firstCodeSnippetTitle,
- path,
- files: snippets,
- preview,
- id: this.countOfExamples,
- };
-
- exampleRef.instance.githubUrl = `${GITHUB_CONTENT_URL}/${snippets[0].name}`;
- exampleRef.instance.stackblitzUrl = `${ASSETS_EXAMPLES_PATH}/${snippets[0].name}.html`;
-
- placeholder.parentElement!.replaceChild(exampleRef.location.nativeElement, placeholder);
-
- await exampleRef.instance.renderExample();
- }
-
- private getCodeSnippetsFromMultifileWrapper(element: HTMLElement): Snippet[] {
- const tabs = Array.from(element.querySelectorAll(DOCS_CODE_SELECTOR));
-
- return tabs.map((tab) => ({
- name: tab.getAttribute('path') ?? tab.getAttribute('header') ?? '',
- content: tab.innerHTML,
- visibleLinesRange: tab.getAttribute('visibleLines') ?? undefined,
- }));
- }
-
- private getStandaloneCodeSnippet(element: HTMLElement): Snippet | null {
- const visibleLines = element.getAttribute('visibleLines') ?? undefined;
- const preview = element.getAttribute('preview');
-
- if (!visibleLines && !preview) {
- return null;
- }
-
- const content = element.querySelector('pre')!;
- const path = element.getAttribute('path')!;
- const title = element.getAttribute('header') ?? undefined;
-
- return {
- title,
- name: path,
- content: content?.outerHTML,
- visibleLinesRange: visibleLines,
- };
- }
-
- // If the content contains static code snippets, we should add buttons to copy
- // the code
- private loadCopySourceCodeButtons(): void {
- const staticCodeSnippets = (
- Array.from(this.elementRef.nativeElement.querySelectorAll('.docs-code:not([mermaid])'))
- );
-
- for (let codeSnippet of staticCodeSnippets) {
- const copySourceCodeButton = this.viewContainer.createComponent(CopySourceCodeButton);
- codeSnippet.appendChild(copySourceCodeButton.location.nativeElement);
- }
- }
-
- private loadBreadcrumb(element: HTMLElement): void {
- const breadcrumbPlaceholder = element.querySelector('docs-breadcrumb') as HTMLElement;
- const activeNavigationItem = this.navigationState.activeNavigationItem();
-
- if (breadcrumbPlaceholder && !!activeNavigationItem?.parent) {
- this.renderComponent(Breadcrumb, breadcrumbPlaceholder);
- }
- }
-
- private loadIcons(element: HTMLElement): void {
- element.querySelectorAll('docs-icon').forEach((iconsPlaceholder) => {
- this.renderComponent(IconComponent, iconsPlaceholder as HTMLElement);
- });
- }
-
- /**
- * Helper method to render a component dynamically in a context of this class.
- */
- private renderComponent(
- type: Type,
- hostElement: HTMLElement,
- inputs?: {[key: string]: unknown},
- ): ComponentRef {
- const componentRef = createComponent(type, {
- hostElement,
- elementInjector: this.injector,
- environmentInjector: this.environmentInjector,
- });
-
- if (inputs) {
- for (const [name, value] of Object.entries(inputs)) {
- componentRef.setInput(name, value);
- }
- }
-
- // Trigger change detection after setting inputs.
- componentRef.changeDetectorRef.detectChanges();
-
- // Attach a view to the ApplicationRef for change detection
- // purposes and for hydration serialization to pick it up
- // during SSG.
- this.appRef.attachView(componentRef.hostView);
-
- return componentRef;
- }
-
- private setupAnchorListeners(element: HTMLElement): void {
- element.querySelectorAll(`a[href]`).forEach((anchor) => {
- // Get the target element's ID from the href attribute
- const url = new URL((anchor as HTMLAnchorElement).href);
- const isExternalLink = url.origin !== this.document.location.origin;
- if (isExternalLink) {
- return;
- }
- fromEvent(anchor, 'click')
- .pipe(takeUntilDestroyed(this.destroyRef))
- .subscribe((e) => {
- handleHrefClickEventWithRouter(e, this.router);
- });
- });
- }
-
- private rewriteRelativeAnchors(element: HTMLElement) {
- for (const anchor of Array.from(element.querySelectorAll(`a[href^="#"]:not(a[download])`))) {
- const url = new URL((anchor as HTMLAnchorElement).href);
- (anchor as HTMLAnchorElement).href = this.location.path() + url.hash;
- }
- }
-}
diff --git a/adev/shared/src/lib/components/example-viewer/example-viewer.component.html b/adev/shared/src/lib/components/example-viewer/example-viewer.component.html
deleted file mode 100644
index be364bf503e..00000000000
--- a/adev/shared/src/lib/components/example-viewer/example-viewer.component.html
+++ /dev/null
@@ -1,155 +0,0 @@
-
-
- @if (view() === CodeExampleViewMode.SNIPPET) {
- {{ exampleMetadata()?.title }}
- }
-
- @if (view() === CodeExampleViewMode.MULTI_FILE) {
-
- @for (tab of tabs(); track tab) {
-
- }
-
- }
-
-
-
-
- @if (expandable()) {
-
- }
-
-
-
-
-
-
-
-
- @if (exampleComponent) {
-
-
-
- }
-
-
- @if (exampleComponent) {
- @if (githubUrl) {
-
-
-
-
-
- }
- @if (stackblitzUrl) {
-
-
-
-
-
- }
- }
-
-
diff --git a/adev/shared/src/lib/components/example-viewer/example-viewer.component.scss b/adev/shared/src/lib/components/example-viewer/example-viewer.component.scss
deleted file mode 100644
index 1df3ddbfab6..00000000000
--- a/adev/shared/src/lib/components/example-viewer/example-viewer.component.scss
+++ /dev/null
@@ -1,133 +0,0 @@
-:host {
- .docs-example-viewer-preview {
- .adev-dark-mode & {
- background: var(--gray-100);
- }
- @media screen and (prefers-color-scheme: dark) {
- background: var(--gray-100);
- }
- .adev-light-mode & {
- background: var(--page-background);
- }
- }
-}
-
-.docs-example-viewer {
- border: 1px solid var(--senary-contrast);
- border-radius: 0.25rem;
- overflow: hidden;
-}
-
-// Example viewer header
-.docs-example-viewer-actions {
- background: var(--subtle-purple);
- display: flex;
- justify-content: space-between;
- align-items: center;
- gap: 0.5rem;
- border-bottom: 1px solid var(--senary-contrast);
- transition: background 0.3s ease, border-color 0.3s ease;
- padding-inline-end: 0.65rem;
- font-family: var(--inter-tight-font);
-
- mat-tab-group {
- max-width: calc(100% - 140px);
- }
-
- span:first-of-type {
- background-image: var(--purple-to-blue-horizontal-gradient);
- background-clip: text;
- -webkit-background-clip: text;
- color: transparent;
-
- padding: 0.7rem 1.1rem;
- font-size: 0.875rem;
- font-style: normal;
- font-weight: 400;
- line-height: 1.4rem;
- letter-spacing: -0.00875rem;
- margin: 0;
- word-wrap: break-word;
- width: fit-content;
- }
-
- .docs-example-viewer-icons {
- display: flex;
- gap: 0.75rem;
-
- svg {
- fill: var(--gray-400);
- }
- }
-
- a,
- button {
- padding: 0;
- margin: 0;
- cursor: pointer;
- height: 24px;
- width: 24px;
- path {
- transition: fill 0.3s ease;
- }
-
- &:hover {
- svg {
- fill: var(--tertiary-contrast);
- }
- }
- }
-}
-
-// Example viewer code
-.docs-example-viewer-code-wrapper {
- position: relative;
- font-size: 0.875rem;
- // TODO: only show this if there is a preview
- // border-block-end: 1px solid var(--senary-contrast);
- transition: border-color 0.3s ease;
- container: viewerblock / inline-size;
- background-color: var(--octonary-contrast);
-
- button[docs-copy-source-code] {
- top: 0.31rem;
- @container viewerblock (min-width: 400px) {
- background-color: transparent;
- border: 1px solid transparent;
- }
- }
-}
-
-// stylelint-disable-next-line
-::ng-deep {
- .docs-example-viewer-preview {
- // stylelint-disable-next-line
- all: initial;
- display: block;
- padding: 1rem;
- border-block-start: 1px solid var(--senary-contrast);
-
- *,
- code::before,
- code,
- pre,
- a,
- i,
- p,
- h1,
- h2,
- h3,
- h4,
- h5,
- h6,
- ol,
- ul,
- li,
- hr,
- input,
- select,
- table {
- all: revert;
- }
- }
-}
diff --git a/adev/shared/src/lib/components/example-viewer/example-viewer.component.spec.ts b/adev/shared/src/lib/components/example-viewer/example-viewer.component.spec.ts
deleted file mode 100644
index fbe959414d8..00000000000
--- a/adev/shared/src/lib/components/example-viewer/example-viewer.component.spec.ts
+++ /dev/null
@@ -1,228 +0,0 @@
-/*!
- * @license
- * Copyright Google LLC All Rights Reserved.
- *
- * Use of this source code is governed by an MIT-style license that can be
- * found in the LICENSE file at https://angular.dev/license
- */
-
-import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
-import {ExampleViewer} from './example-viewer.component';
-import {DocsContentLoader, ExampleMetadata, ExampleViewerContentLoader} from '../../interfaces';
-import {DOCS_CONTENT_LOADER, EXAMPLE_VIEWER_CONTENT_LOADER} from '../../providers';
-import {Component} from '@angular/core';
-import {NoopAnimationsModule} from '@angular/platform-browser/animations';
-import {HarnessLoader} from '@angular/cdk/testing';
-import {TestbedHarnessEnvironment} from '@angular/cdk/testing/testbed';
-import {Clipboard} from '@angular/cdk/clipboard';
-import {By} from '@angular/platform-browser';
-import {MatTabGroupHarness} from '@angular/material/tabs/testing';
-import {CopySourceCodeButton} from '../copy-source-code-button/copy-source-code-button.component';
-import {ActivatedRoute} from '@angular/router';
-
-describe('ExampleViewer', () => {
- let component: ExampleViewer;
- let fixture: ComponentFixture;
- let loader: HarnessLoader;
- let exampleContentSpy: jasmine.SpyObj;
- let contentServiceSpy: jasmine.SpyObj;
-
- beforeEach(() => {
- exampleContentSpy = jasmine.createSpyObj('ExampleContentLoader', ['loadPreview']);
- contentServiceSpy = jasmine.createSpyObj('ContentLoader', ['getContent']);
- contentServiceSpy.getContent.and.returnValue(Promise.resolve(undefined));
- });
-
- beforeEach(async () => {
- await TestBed.configureTestingModule({
- imports: [ExampleViewer, NoopAnimationsModule],
- providers: [
- {provide: EXAMPLE_VIEWER_CONTENT_LOADER, useValue: exampleContentSpy},
- {provide: DOCS_CONTENT_LOADER, useValue: contentServiceSpy},
- {provide: ActivatedRoute, useValue: {snapshot: {fragment: 'fragment'}}},
- ],
- }).compileComponents();
- fixture = TestBed.createComponent(ExampleViewer);
- component = fixture.componentInstance;
- loader = TestbedHarnessEnvironment.loader(fixture);
- fixture.detectChanges();
- });
-
- it('should set file extensions as tab names when all files have different extension', waitForAsync(async () => {
- component.metadata = getMetadata({
- files: [
- {name: 'file.ts', content: ''},
- {name: 'file.html', content: ''},
- {name: 'file.css', content: ''},
- ],
- });
-
- await component.renderExample();
-
- expect(component.tabs()!.length).toBe(3);
- expect(component.tabs()![0].name).toBe('TS');
- expect(component.tabs()![1].name).toBe('HTML');
- expect(component.tabs()![2].name).toBe('CSS');
- }));
-
- it('should generate correct code content for multi file mode when it is expanded', waitForAsync(async () => {
- component.metadata = getMetadata({
- files: [
- {name: 'file.ts', content: 'typescript file'},
- {name: 'file.html', content: 'html file'},
- {name: 'file.css', content: 'css file'},
- ],
- });
-
- await component.renderExample();
-
- expect(component.tabs()!.length).toBe(3);
- expect(component.tabs()![0].code).toBe('typescript file');
- expect(component.tabs()![1].code).toBe('html file');
- expect(component.tabs()![2].code).toBe('css file');
- }));
-
- it('should set file names as tab names when there is at least one duplication', async () => {
- component.metadata = getMetadata({
- files: [
- {name: 'example.ts', content: 'typescript file'},
- {name: 'example.html', content: 'html file'},
- {name: 'another-example.ts', content: 'css file'},
- ],
- });
-
- await component.renderExample();
- expect(component.tabs()!.length).toBe(3);
- expect(component.tabs()![0].name).toBe('example.ts');
- expect(component.tabs()![1].name).toBe('example.html');
- expect(component.tabs()![2].name).toBe('another-example.ts');
- });
-
- it('should expandable be false when none of the example files have defined visibleLinesRange ', waitForAsync(async () => {
- component.metadata = getMetadata();
- await component.renderExample();
- expect(component.expandable()).toBeFalse();
- }));
-
- it('should expandable be true when at least one example file has defined visibleLinesRange ', waitForAsync(async () => {
- component.metadata = getMetadata({
- files: [
- {name: 'example.ts', content: 'typescript file'},
- {
- name: 'example.html',
- content: 'html file',
- visibleLinesRange: '[1, 2]',
- },
- {name: 'another-example.ts', content: 'css file'},
- ],
- });
- await component.renderExample();
- expect(component.expandable()).toBeTrue();
- }));
-
- it('should set exampleComponent when metadata contains path and preview is true', waitForAsync(async () => {
- exampleContentSpy.loadPreview.and.resolveTo(ExampleComponent);
- component.metadata = getMetadata({
- path: 'example.ts',
- preview: true,
- });
- await component.renderExample();
- expect(component.exampleComponent).toBe(ExampleComponent);
- }));
-
- it('should display GitHub button when githubUrl is provided and there is preview', waitForAsync(async () => {
- exampleContentSpy.loadPreview.and.resolveTo(ExampleComponent);
- component.metadata = getMetadata({
- path: 'example.ts',
- preview: true,
- });
- component.githubUrl = 'https://github.com/';
- await component.renderExample();
- const githubButton = fixture.debugElement.query(
- By.css('a[aria-label="Open example on GitHub"]'),
- );
- expect(githubButton).toBeTruthy();
- expect(githubButton.nativeElement.href).toBe(component.githubUrl);
- }));
-
- it('should display StackBlitz button when stackblitzUrl is provided and there is preview', waitForAsync(async () => {
- exampleContentSpy.loadPreview.and.resolveTo(ExampleComponent);
- component.metadata = getMetadata({
- path: 'example.ts',
- preview: true,
- });
- component.stackblitzUrl = 'https://stackblitz.com/';
- await component.renderExample();
- const stackblitzButton = fixture.debugElement.query(
- By.css('a[aria-label="Edit this example in StackBlitz"]'),
- );
- expect(stackblitzButton).toBeTruthy();
- expect(stackblitzButton.nativeElement.href).toBe(component.stackblitzUrl);
- }));
-
- it('should set expanded flag in metadata after toggleExampleVisibility', waitForAsync(async () => {
- component.metadata = getMetadata();
- await component.renderExample();
- component.toggleExampleVisibility();
- expect(component.expanded()).toBeTrue();
- const tabGroup = await loader.getHarness(MatTabGroupHarness);
- const tab = await tabGroup.getSelectedTab();
- expect(await tab.getLabel()).toBe('TS');
- component.toggleExampleVisibility();
- expect(component.expanded()).toBeFalse();
- }));
-
- it('should call clipboard service when clicked on copy source code', waitForAsync(async () => {
- const expectedCodeSnippetContent = 'typescript code';
- component.metadata = getMetadata({
- files: [
- {
- name: 'example.ts',
- content: `${expectedCodeSnippetContent}
`,
- },
- {name: 'example.css', content: ''},
- ],
- });
- const clipboardService = TestBed.inject(Clipboard);
- const spy = spyOn(clipboardService, 'copy');
-
- await component.renderExample();
- const button = fixture.debugElement.query(By.directive(CopySourceCodeButton)).nativeElement;
- button.click();
-
- expect(spy.calls.argsFor(0)[0].trim()).toBe(expectedCodeSnippetContent);
- }));
-
- it('should call clipboard service when clicked on copy example link', waitForAsync(async () => {
- component.metadata = getMetadata();
- component.expanded.set(true);
- fixture.detectChanges();
-
- const clipboardService = TestBed.inject(Clipboard);
- const spy = spyOn(clipboardService, 'copy');
- await component.renderExample();
- const button = fixture.debugElement.query(
- By.css('button.docs-example-copy-link'),
- ).nativeElement;
- button.click();
- expect(spy.calls.argsFor(0)[0].trim()).toBe(`http://localhost:9876/context.html#example-1`);
- }));
-});
-
-const getMetadata = (value: Partial = {}): ExampleMetadata => {
- return {
- id: 1,
- files: [
- {name: 'example.ts', content: ''},
- {name: 'example.css', content: ''},
- ],
- preview: false,
- ...value,
- };
-};
-
-@Component({
- template: '',
- standalone: true,
-})
-class ExampleComponent {}
diff --git a/adev/shared/src/lib/components/example-viewer/example-viewer.component.ts b/adev/shared/src/lib/components/example-viewer/example-viewer.component.ts
deleted file mode 100644
index 69a762cf5c0..00000000000
--- a/adev/shared/src/lib/components/example-viewer/example-viewer.component.ts
+++ /dev/null
@@ -1,237 +0,0 @@
-/*!
- * @license
- * Copyright Google LLC All Rights Reserved.
- *
- * Use of this source code is governed by an MIT-style license that can be
- * found in the LICENSE file at https://angular.dev/license
- */
-
-import {
- ChangeDetectionStrategy,
- Component,
- DestroyRef,
- Input,
- Type,
- computed,
- inject,
- ChangeDetectorRef,
- ViewChild,
- signal,
- ElementRef,
- forwardRef,
-} from '@angular/core';
-import {CommonModule, DOCUMENT} from '@angular/common';
-import {MatTabGroup, MatTabsModule} from '@angular/material/tabs';
-import {Clipboard} from '@angular/cdk/clipboard';
-import {CopySourceCodeButton} from '../copy-source-code-button/copy-source-code-button.component';
-import {ExampleMetadata, Snippet} from '../../interfaces';
-import {EXAMPLE_VIEWER_CONTENT_LOADER} from '../../providers';
-import {DocViewer} from '../docs-viewer/docs-viewer.component';
-import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
-
-export enum CodeExampleViewMode {
- SNIPPET = 'snippet',
- MULTI_FILE = 'multi',
-}
-
-export const CODE_LINE_NUMBER_CLASS_NAME = 'hljs-ln-number';
-export const CODE_LINE_CLASS_NAME = 'hljs-ln-line';
-export const GAP_CODE_LINE_CLASS_NAME = 'gap';
-export const HIDDEN_CLASS_NAME = 'hidden';
-
-@Component({
- selector: 'docs-example-viewer',
- standalone: true,
- imports: [CommonModule, forwardRef(() => DocViewer), CopySourceCodeButton, MatTabsModule],
- templateUrl: './example-viewer.component.html',
- styleUrls: ['./example-viewer.component.scss'],
- changeDetection: ChangeDetectionStrategy.OnPush,
-})
-export class ExampleViewer {
- // TODO: replace by signal-based input when it'll be available
- @Input({required: true}) set metadata(value: ExampleMetadata) {
- this.exampleMetadata.set(value);
- }
-
- @Input() githubUrl: string | null = null;
- @Input() stackblitzUrl: string | null = null;
- @ViewChild('codeTabs') matTabGroup?: MatTabGroup;
-
- private readonly changeDetector = inject(ChangeDetectorRef);
- private readonly clipboard = inject(Clipboard);
- private readonly destroyRef = inject(DestroyRef);
- private readonly document = inject(DOCUMENT);
- private readonly elementRef = inject(ElementRef);
- private readonly exampleViewerContentLoader = inject(EXAMPLE_VIEWER_CONTENT_LOADER);
-
- private readonly shouldDisplayFullName = computed(() => {
- const fileExtensions =
- this.exampleMetadata()?.files.map((file) => this.getFileExtension(file.name)) ?? [];
-
- // Display full file names only when exist files with the same extension
- return new Set(fileExtensions).size !== fileExtensions.length;
- });
-
- CodeExampleViewMode = CodeExampleViewMode;
- exampleComponent?: Type;
-
- expanded = signal(false);
- exampleMetadata = signal(null);
- snippetCode = signal(undefined);
- tabs = computed(() =>
- this.exampleMetadata()?.files.map((file) => ({
- name:
- file.title ?? (this.shouldDisplayFullName() ? file.name : this.getFileExtension(file.name)),
- code: file.content,
- })),
- );
- view = computed(() =>
- this.exampleMetadata()?.files.length === 1
- ? CodeExampleViewMode.SNIPPET
- : CodeExampleViewMode.MULTI_FILE,
- );
- expandable = computed(() =>
- this.exampleMetadata()?.files.some((file) => !!file.visibleLinesRange),
- );
-
- async renderExample(): Promise {
- // Lazy load live example component
- if (this.exampleMetadata()?.path && this.exampleMetadata()?.preview) {
- this.exampleComponent = await this.exampleViewerContentLoader.loadPreview(
- this.exampleMetadata()?.path!,
- );
- }
-
- this.snippetCode.set(this.exampleMetadata()?.files[0]);
-
- this.changeDetector.detectChanges();
-
- this.setCodeLinesVisibility();
-
- this.elementRef.nativeElement.setAttribute(
- 'id',
- `example-${this.exampleMetadata()?.id.toString()!}`,
- );
-
- this.matTabGroup?.realignInkBar();
-
- this.listenToMatTabIndexChange();
- }
-
- toggleExampleVisibility(): void {
- this.expanded.update((expanded) => !expanded);
-
- this.setCodeLinesVisibility();
- }
-
- copyLink(): void {
- // Reconstruct the URL using `origin + pathname` so we drop any pre-existing hash.
- const fullUrl = location.origin + location.pathname + '#example-' + this.exampleMetadata()?.id;
- this.clipboard.copy(fullUrl);
- }
-
- private listenToMatTabIndexChange(): void {
- this.matTabGroup?.realignInkBar();
- this.matTabGroup?.selectedIndexChange
- .pipe(takeUntilDestroyed(this.destroyRef))
- .subscribe((index) => {
- this.snippetCode.set(this.exampleMetadata()?.files[index]);
- this.setCodeLinesVisibility();
- });
- }
-
- private getFileExtension(name: string): string {
- const segments = name.split('.');
- return segments.length ? segments[segments.length - 1].toLocaleUpperCase() : '';
- }
-
- private setCodeLinesVisibility(): void {
- this.expanded()
- ? this.handleExpandedStateForCodeBlock()
- : this.handleCollapsedStateForCodeBlock();
- }
-
- private handleExpandedStateForCodeBlock(): void {
- const lines = (
- Array.from(
- this.elementRef.nativeElement.querySelectorAll(
- `.${CODE_LINE_CLASS_NAME}.${HIDDEN_CLASS_NAME}`,
- ),
- )
- );
-
- const lineNumbers = (
- Array.from(
- this.elementRef.nativeElement.querySelectorAll(
- `.${CODE_LINE_NUMBER_CLASS_NAME}.${HIDDEN_CLASS_NAME}`,
- ),
- )
- );
-
- const gapLines = (
- Array.from(
- this.elementRef.nativeElement.querySelectorAll(
- `.${CODE_LINE_CLASS_NAME}.${GAP_CODE_LINE_CLASS_NAME}`,
- ),
- )
- );
-
- for (const line of lines) {
- line.classList.remove(HIDDEN_CLASS_NAME);
- }
-
- for (const lineNumber of lineNumbers) {
- lineNumber.classList.remove(HIDDEN_CLASS_NAME);
- }
-
- for (const expandLine of gapLines) {
- expandLine.remove();
- }
- }
-
- private handleCollapsedStateForCodeBlock(): void {
- const visibleLinesRange = this.snippetCode()?.visibleLinesRange;
-
- if (!visibleLinesRange) {
- return;
- }
-
- const linesToDisplay = (visibleLinesRange?.split(',') ?? []).map((line) => Number(line));
- const lines = (
- Array.from(this.elementRef.nativeElement.querySelectorAll(`.${CODE_LINE_CLASS_NAME}`))
- );
- const lineNumbers = (
- Array.from(this.elementRef.nativeElement.querySelectorAll(`.${CODE_LINE_NUMBER_CLASS_NAME}`))
- );
- const appendGapBefore = [];
-
- for (const [index, line] of lines.entries()) {
- if (!linesToDisplay.includes(index)) {
- line.classList.add(HIDDEN_CLASS_NAME);
- } else if (!linesToDisplay.includes(index - 1)) {
- appendGapBefore.push(line);
- }
- }
-
- for (const [index, lineNumber] of lineNumbers.entries()) {
- if (!linesToDisplay.includes(index)) {
- lineNumber.classList.add(HIDDEN_CLASS_NAME);
- }
- }
-
- // Create gap line between visible ranges. For example we would like to display 10-16 and 20-29 lines.
- // We should display separator, gap between those two scopes.
- // TODO: we could replace div it with the component, and allow to expand code block after click.
- for (const [index, element] of appendGapBefore.entries()) {
- if (index === 0) {
- continue;
- }
-
- const separator = this.document.createElement('div');
- separator.textContent = `...`;
- separator.classList.add(CODE_LINE_CLASS_NAME);
- separator.classList.add(GAP_CODE_LINE_CLASS_NAME);
- element.parentNode?.insertBefore(separator, element);
- }
- }
-}
diff --git a/adev/shared/src/lib/components/icon/icon.component.html b/adev/shared/src/lib/components/icon/icon.component.html
deleted file mode 100644
index 6dbc7430638..00000000000
--- a/adev/shared/src/lib/components/icon/icon.component.html
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/adev/shared/src/lib/components/icon/icon.component.scss b/adev/shared/src/lib/components/icon/icon.component.scss
deleted file mode 100644
index a5337d3d774..00000000000
--- a/adev/shared/src/lib/components/icon/icon.component.scss
+++ /dev/null
@@ -1,3 +0,0 @@
-.docs-icon_high-contrast {
- color: var(--primary-contrast);
-}
diff --git a/adev/shared/src/lib/components/icon/icon.component.ts b/adev/shared/src/lib/components/icon/icon.component.ts
deleted file mode 100644
index ed38bf5c3f6..00000000000
--- a/adev/shared/src/lib/components/icon/icon.component.ts
+++ /dev/null
@@ -1,58 +0,0 @@
-/*!
- * @license
- * Copyright Google LLC All Rights Reserved.
- *
- * Use of this source code is governed by an MIT-style license that can be
- * found in the LICENSE file at https://angular.dev/license
- */
-
-import {DOCUMENT} from '@angular/common';
-import {
- ChangeDetectionStrategy,
- ChangeDetectorRef,
- Component,
- afterNextRender,
- inject,
-} from '@angular/core';
-
-@Component({
- selector: 'docs-icon',
- standalone: true,
- templateUrl: './icon.component.html',
- styleUrl: './icon.component.scss',
- host: {
- '[class]': 'MATERIAL_SYMBOLS_OUTLINED',
- '[style.font-size.px]': 'fontSize',
- 'aria-hidden': 'true',
- },
- changeDetection: ChangeDetectionStrategy.OnPush,
-})
-export class IconComponent {
- private readonly cdRef = inject(ChangeDetectorRef);
-
- get fontSize(): number | null {
- return IconComponent.isFontLoaded ? null : 0;
- }
-
- protected readonly MATERIAL_SYMBOLS_OUTLINED = 'material-symbols-outlined';
-
- private static isFontLoaded: boolean = false;
- /** Share the same promise across different instances of the component */
- private static whenFontLoad?: Promise | undefined;
-
- constructor() {
- if (IconComponent.isFontLoaded) {
- return;
- }
-
- const document = inject(DOCUMENT);
- afterNextRender(async () => {
- IconComponent.whenFontLoad ??= document.fonts.load('normal 1px "Material Symbols Outlined"');
- await IconComponent.whenFontLoad;
- IconComponent.isFontLoaded = true;
-
- // We need to ensure CD is triggered on the component when the font is loaded
- this.cdRef.markForCheck();
- });
- }
-}
diff --git a/adev/shared/src/lib/components/index.ts b/adev/shared/src/lib/components/index.ts
deleted file mode 100644
index 9503c1d86b2..00000000000
--- a/adev/shared/src/lib/components/index.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-/*!
- * @license
- * Copyright Google LLC All Rights Reserved.
- *
- * Use of this source code is governed by an MIT-style license that can be
- * found in the LICENSE file at https://angular.dev/license
- */
-
-export * from './cookie-popup/cookie-popup.component';
-export * from './docs-viewer/docs-viewer.component';
-export * from './navigation-list/navigation-list.component';
-export * from './select/select.component';
-export * from './slide-toggle/slide-toggle.component';
-export * from './table-of-contents/table-of-contents.component';
-export * from './text-field/text-field.component';
-export * from './icon/icon.component';
-export * from './search-dialog/search-dialog.component';
diff --git a/adev/shared/src/lib/components/navigation-list/navigation-list.component.html b/adev/shared/src/lib/components/navigation-list/navigation-list.component.html
deleted file mode 100644
index e54810698bf..00000000000
--- a/adev/shared/src/lib/components/navigation-list/navigation-list.component.html
+++ /dev/null
@@ -1,76 +0,0 @@
-
-
-
-
-
diff --git a/adev/shared/src/lib/components/navigation-list/navigation-list.component.scss b/adev/shared/src/lib/components/navigation-list/navigation-list.component.scss
deleted file mode 100644
index ab1805c521a..00000000000
--- a/adev/shared/src/lib/components/navigation-list/navigation-list.component.scss
+++ /dev/null
@@ -1,148 +0,0 @@
-@use '../../../../../shared/src/lib/styles/media-queries' as mq;
-
-:host {
- display: flex;
- min-width: var(--secondary-nav-width);
- list-style: none;
- overflow-y: auto;
- overflow-x: hidden;
- height: 100vh;
- padding: 0;
- margin: 0;
- padding-block: 1.5rem;
- font-size: 0.875rem;
- box-sizing: border-box;
-
- &::-webkit-scrollbar-track {
- background: rgba(0, 0, 0, 0);
- cursor: pointer;
- }
-
- &::-webkit-scrollbar {
- width: 6px;
- height: 6px;
- }
-
- &::-webkit-scrollbar-thumb {
- background-color: var(--septenary-contrast);
- @include mq.for-tablet-landscape-down {
- background-color: var(--quinary-contrast);
- }
-
- border-radius: 10px;
- transition: background-color 0.3s ease;
- }
-
- &::-webkit-scrollbar-thumb:hover {
- background-color: var(--quinary-contrast);
- }
-
- .adev-nav-secondary & {
- padding-block: 2rem;
- }
-
- > .adev-faceted-list {
- border: 0;
- }
-
- .docs-navigation-link-hidden {
- display: none;
- }
-
- .adev-nav-item-has-icon {
- &::after {
- // FIXME: for some reason this disappears when transformed
- content: 'chevron_right';
- font-size: 1.25rem;
- font-family: var(--icons);
- }
- }
-}
-
-.adev-secondary-nav-header {
- padding-block: 1.25rem;
- font-weight: 500;
-}
-
-.adev-secondary-nav-button {
- width: 15rem;
- display: flex;
- justify-content: space-between;
- align-items: center;
- border: none;
- padding-block: 1.25rem;
- padding-inline-start: 0;
- color: var(--primary-contrast);
- font-size: 0.875rem;
- font-family: var(--inter-font);
- line-height: 160%;
- letter-spacing: -0.00875rem;
- transition: color 0.3s ease, background 0.3s ease;
- text-align: left; // forces left alignment of text in button
-
- &.adev-secondary-nav-button-active {
- // font gradient
- background-image: var(--pink-to-purple-vertical-gradient);
- &::before {
- opacity: 1;
- transform: scaleY(1);
- background: var(--pink-to-purple-vertical-gradient);
- }
- &:hover {
- &::before {
- opacity: 1;
- transform: scaleY(1.1);
- }
- }
- }
-}
-
-.adev-expanded-button {
- justify-content: start;
- gap: 0.5rem;
-}
-
-a,
-.adev-not-expanded-button {
- display: flex;
- justify-content: space-between;
- align-items: center;
- font-weight: 500;
- line-height: 1.4rem;
- letter-spacing: -0.00875rem;
- padding: 0.5rem;
- padding-inline-start: 1rem;
- text-align: left;
-}
-
-// Add padding-bottom to last item in the list
-.docs-navigation-list {
- width: 100%;
-
- li:last-of-type {
- ul:last-of-type {
- li:last-of-type {
- padding-block-end: 1rem;
- }
- }
- }
- &:first-child {
- margin-inline-start: 1rem;
- }
-}
-
-.adev-external-link {
- display: flex;
- align-items: center;
- justify-content: space-between;
- width: 100%;
- gap: 0.5rem;
- &::after {
- content: 'open_in_new';
- font-family: var(--icons);
- font-size: 1.1rem;
- color: var(--quinary-contrast);
- transition: color 0.3s ease;
- margin-inline-end: 0.4rem;
- }
-}
diff --git a/adev/shared/src/lib/components/navigation-list/navigation-list.component.spec.ts b/adev/shared/src/lib/components/navigation-list/navigation-list.component.spec.ts
deleted file mode 100644
index 18e9f07b3f1..00000000000
--- a/adev/shared/src/lib/components/navigation-list/navigation-list.component.spec.ts
+++ /dev/null
@@ -1,58 +0,0 @@
-/*!
- * @license
- * Copyright Google LLC All Rights Reserved.
- *
- * Use of this source code is governed by an MIT-style license that can be
- * found in the LICENSE file at https://angular.dev/license
- */
-
-import {ComponentFixture, TestBed} from '@angular/core/testing';
-
-import {NavigationList} from './navigation-list.component';
-import {By} from '@angular/platform-browser';
-import {NavigationItem} from '../../interfaces';
-import {RouterTestingModule} from '@angular/router/testing';
-import {signal} from '@angular/core';
-import {NavigationState} from '@angular/docs-shared';
-
-const navigationItems: NavigationItem[] = [
- {
- label: 'Introduction',
- path: 'guide',
- },
- {
- label: 'Getting Started',
- children: [
- {label: 'What is Angular?', path: 'guide/what-is-angular'},
- {label: 'Setup', path: 'guide/setup'},
- ],
- },
-];
-
-describe('NavigationList', () => {
- let component: NavigationList;
- let fixture: ComponentFixture;
-
- beforeEach(async () => {
- await TestBed.configureTestingModule({
- imports: [NavigationList, RouterTestingModule],
- providers: [{provide: NavigationState, useClass: FakeNavigationListState}],
- }).compileComponents();
- fixture = TestBed.createComponent(NavigationList);
- component = fixture.componentInstance;
- });
-
- it('should display provided navigation structure', () => {
- component.navigationItems = [...navigationItems];
- fixture.detectChanges(true);
-
- const links = fixture.debugElement.queryAll(By.css('a'));
-
- expect(links.length).toBe(3);
- });
-});
-
-class FakeNavigationListState {
- isOpened = signal(true);
- activeNavigationItem = signal(navigationItems.at(1));
-}
diff --git a/adev/shared/src/lib/components/navigation-list/navigation-list.component.ts b/adev/shared/src/lib/components/navigation-list/navigation-list.component.ts
deleted file mode 100644
index 3f3657d9bd0..00000000000
--- a/adev/shared/src/lib/components/navigation-list/navigation-list.component.ts
+++ /dev/null
@@ -1,60 +0,0 @@
-/*!
- * @license
- * Copyright Google LLC All Rights Reserved.
- *
- * Use of this source code is governed by an MIT-style license that can be
- * found in the LICENSE file at https://angular.dev/license
- */
-
-import {
- ChangeDetectionStrategy,
- Component,
- EventEmitter,
- Input,
- Output,
- inject,
-} from '@angular/core';
-import {NavigationItem} from '../../interfaces';
-import {NavigationState} from '../../services';
-import {RouterLink, RouterLinkActive} from '@angular/router';
-import {CommonModule} from '@angular/common';
-import {IconComponent} from '../icon/icon.component';
-import {IsActiveNavigationItem} from '../../pipes/is-active-navigation-item.pipe';
-
-@Component({
- selector: 'docs-navigation-list',
- standalone: true,
- imports: [CommonModule, RouterLink, RouterLinkActive, IconComponent, IsActiveNavigationItem],
- templateUrl: './navigation-list.component.html',
- styleUrls: ['./navigation-list.component.scss'],
- changeDetection: ChangeDetectionStrategy.OnPush,
-})
-export class NavigationList {
- @Input({required: true}) navigationItems: NavigationItem[] = [];
- @Input() displayItemsToLevel: number = 2;
- @Input() collapsableLevel: number | undefined = undefined;
- @Input() expandableLevel: number = 2;
- @Input() isDropdownView = false;
-
- @Output() linkClicked = new EventEmitter();
-
- private readonly navigationState = inject(NavigationState);
-
- expandedItems = this.navigationState.expandedItems;
- activeItem = this.navigationState.activeNavigationItem;
-
- toggle(item: NavigationItem): void {
- if (
- item.level === 1 &&
- item.level !== this.expandableLevel &&
- item.level !== this.collapsableLevel
- ) {
- return;
- }
- this.navigationState.toggleItem(item);
- }
-
- emitClickOnLink(): void {
- this.linkClicked.emit();
- }
-}
diff --git a/adev/shared/src/lib/components/search-dialog/search-dialog.component.html b/adev/shared/src/lib/components/search-dialog/search-dialog.component.html
deleted file mode 100644
index 84025ad419d..00000000000
--- a/adev/shared/src/lib/components/search-dialog/search-dialog.component.html
+++ /dev/null
@@ -1,79 +0,0 @@
-
diff --git a/adev/shared/src/lib/components/search-dialog/search-dialog.component.scss b/adev/shared/src/lib/components/search-dialog/search-dialog.component.scss
deleted file mode 100644
index 9f5ac8827b8..00000000000
--- a/adev/shared/src/lib/components/search-dialog/search-dialog.component.scss
+++ /dev/null
@@ -1,144 +0,0 @@
-dialog {
- background-color: transparent;
- border: none;
- padding-block-end: 3rem;
-
- &::backdrop {
- backdrop-filter: blur(5px);
- }
-}
-
-.adev-search-container {
- width: 500px;
- max-width: 90vw;
- background-color: var(--page-background);
- border: 1px solid var(--senary-contrast);
- border-radius: 0.25rem;
- box-sizing: border-box;
-
- .adev-search-input {
- border-radius: 0.25rem 0.25rem 0 0;
- border: none;
- border-block-end: 1px solid var(--senary-contrast);
- height: 2.6875rem; // 43px;
- padding-inline-start: 1rem;
- position: relative;
-
- &::after {
- content: 'Esc';
- position: absolute;
- right: 1rem;
- color: var(--gray-400);
- font-size: 0.875rem;
- }
- }
-
- ul {
- max-height: 260px;
- overflow-y: auto;
- list-style-type: none;
- padding-inline: 0;
- padding-block-start: 1rem;
- margin: 0;
-
- li {
- border-inline-start: 2px solid var(--senary-contrast);
- margin-inline-start: 1rem;
- padding-inline-end: 1rem;
- padding-block: 0.25rem;
-
- a {
- color: var(--secondary-contrast);
- display: flex;
- justify-content: space-between;
- gap: 0.5rem;
-
- .adev-search-result-icon {
- i {
- display: flex;
- align-items: center;
- font-size: 1.2rem;
- }
- }
- }
-
- &.active {
- background-color: var(--septenary-contrast); // stylelint-disable-line
- }
-
- &:hover,
- &.active {
- background-color: var(--octonary-contrast); // stylelint-disable-line
- border-inline-start: 2px solid var(--primary-contrast);
- a {
- span:not(.adev-result-page-title),
- .adev-search-results__type {
- color: var(--primary-contrast);
- i {
- color: var(--primary-contrast);
- }
- }
- }
- }
- }
-
- .adev-search-result-icon,
- .adev-search-results__type,
- .adev-result-page-title {
- color: var(--quaternary-contrast);
- display: inline-block;
- font-size: 0.875rem;
- transition: color 0.3s ease;
- padding: 0.75rem;
- padding-inline-end: 0;
- }
-
- .adev-search-results__lvl2 {
- display: inline-block;
- margin-inline-start: 2rem;
- padding-block-start: 0;
- }
-
- .adev-search-results__lvl3 {
- margin-inline-start: 2rem;
- padding-block-start: 0;
- }
- }
-
- .adev-result-page-title {
- font-size: 0.875rem;
- font-weight: 400;
- }
-}
-
-.adev-search-results__no-results {
- padding: 0.75rem;
- color: var(--gray-400);
-}
-
-.adev-result-icon-and-type {
- display: flex;
-
- .adev-search-results__type {
- padding-inline-start: 0;
- }
-}
-
-.adev-algolia {
- display: flex;
- align-items: center;
- justify-content: end;
- color: var(--gray-400);
- padding: 1rem;
- font-size: 0.75rem;
- font-weight: 500;
- gap: 0.25rem;
- background-color: var(--page-background);
- border-radius: 0 0 0.25rem 0.25rem;
-
- docs-algolia-icon {
- margin-block-start: 0.12rem;
- margin-inline-start: 0.15rem;
- width: 4rem;
- }
-}
diff --git a/adev/shared/src/lib/components/search-dialog/search-dialog.component.spec.ts b/adev/shared/src/lib/components/search-dialog/search-dialog.component.spec.ts
deleted file mode 100644
index 7b9ad261f82..00000000000
--- a/adev/shared/src/lib/components/search-dialog/search-dialog.component.spec.ts
+++ /dev/null
@@ -1,51 +0,0 @@
-/*!
- * @license
- * Copyright Google LLC All Rights Reserved.
- *
- * Use of this source code is governed by an MIT-style license that can be
- * found in the LICENSE file at https://angular.dev/license
- */
-
-import {ComponentFixture, TestBed} from '@angular/core/testing';
-
-import {SearchDialog} from './search-dialog.component';
-import {WINDOW} from '../../providers';
-import {Search} from '../../services';
-import {signal} from '@angular/core';
-import {FakeEventTarget} from '../../utils/test.utils';
-
-describe('SearchDialog', () => {
- let component: SearchDialog;
- let fixture: ComponentFixture;
-
- const fakeSearch = {
- keyDown: signal(null),
- searchQuery: signal(''),
- searchResults: signal([]),
- };
- const fakeWindow = new FakeEventTarget();
-
- beforeEach(async () => {
- await TestBed.configureTestingModule({
- imports: [SearchDialog],
- providers: [
- {
- provide: Search,
- useValue: fakeSearch,
- },
- {
- provide: WINDOW,
- useValue: fakeWindow,
- },
- ],
- }).compileComponents();
-
- fixture = TestBed.createComponent(SearchDialog);
- component = fixture.componentInstance;
- fixture.detectChanges();
- });
-
- it('should create', () => {
- expect(component).toBeTruthy();
- });
-});
diff --git a/adev/shared/src/lib/components/search-dialog/search-dialog.component.ts b/adev/shared/src/lib/components/search-dialog/search-dialog.component.ts
deleted file mode 100644
index 5015422fc6d..00000000000
--- a/adev/shared/src/lib/components/search-dialog/search-dialog.component.ts
+++ /dev/null
@@ -1,139 +0,0 @@
-/*!
- * @license
- * Copyright Google LLC All Rights Reserved.
- *
- * Use of this source code is governed by an MIT-style license that can be
- * found in the LICENSE file at https://angular.dev/license
- */
-
-import {
- AfterViewInit,
- Component,
- DestroyRef,
- ElementRef,
- EventEmitter,
- NgZone,
- OnDestroy,
- OnInit,
- Output,
- QueryList,
- ViewChild,
- ViewChildren,
- inject,
-} from '@angular/core';
-
-import {ClickOutside, Search, TextField, WINDOW} from '@angular/docs-shared';
-import {FormsModule} from '@angular/forms';
-import {ActiveDescendantKeyManager} from '@angular/cdk/a11y';
-import {SearchItem} from '../../directives/search-item/search-item.directive';
-import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
-import {Router, RouterLink} from '@angular/router';
-import {filter, fromEvent} from 'rxjs';
-import {AlgoliaIcon} from '../algolia-icon/algolia-icon.component';
-import {RelativeLink} from '../../pipes/relative-link.pipe';
-
-@Component({
- selector: 'docs-search-dialog',
- standalone: true,
- imports: [
- ClickOutside,
- TextField,
- FormsModule,
- SearchItem,
- AlgoliaIcon,
- RelativeLink,
- RouterLink,
- ],
- templateUrl: './search-dialog.component.html',
- styleUrls: ['./search-dialog.component.scss'],
-})
-export class SearchDialog implements OnInit, AfterViewInit, OnDestroy {
- @Output() onClose = new EventEmitter();
- @ViewChild('searchDialog') dialog?: ElementRef;
- @ViewChildren(SearchItem) items?: QueryList;
-
- private readonly destroyRef = inject(DestroyRef);
- private readonly ngZone = inject(NgZone);
- private readonly search = inject(Search);
- private readonly relativeLink = new RelativeLink();
- private readonly router = inject(Router);
- private readonly window = inject(WINDOW);
-
- private keyManager?: ActiveDescendantKeyManager;
-
- searchQuery = this.search.searchQuery;
- searchResults = this.search.searchResults;
-
- ngOnInit(): void {
- this.ngZone.runOutsideAngular(() => {
- fromEvent(this.window, 'keydown')
- .pipe(
- filter((_) => !!this.keyManager),
- takeUntilDestroyed(this.destroyRef),
- )
- .subscribe((event) => {
- // When user presses Enter we can navigate to currently selected item in the search result list.
- if (event.key === 'Enter') {
- this.navigateToTheActiveItem();
- } else {
- this.ngZone.run(() => {
- this.keyManager?.onKeydown(event);
- });
- }
- });
- });
- }
-
- ngAfterViewInit() {
- this.dialog?.nativeElement.showModal();
-
- if (!this.items) {
- return;
- }
-
- this.keyManager = new ActiveDescendantKeyManager(this.items).withWrap();
- this.keyManager?.setFirstItemActive();
-
- this.updateActiveItemWhenResultsChanged();
- this.scrollToActiveItem();
- }
-
- ngOnDestroy(): void {
- this.keyManager?.destroy();
- }
-
- closeSearchDialog() {
- this.dialog?.nativeElement.close();
- this.onClose.next();
- }
-
- updateSearchQuery(query: string) {
- this.search.updateSearchQuery(query);
- }
-
- private updateActiveItemWhenResultsChanged(): void {
- this.items?.changes.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(() => {
- // Change detection should be run before execute `setFirstItemActive`.
- Promise.resolve().then(() => {
- this.keyManager?.setFirstItemActive();
- });
- });
- }
-
- private navigateToTheActiveItem(): void {
- const activeItemLink: string | undefined = this.keyManager?.activeItem?.item?.url;
-
- if (!activeItemLink) {
- return;
- }
-
- this.router.navigateByUrl(this.relativeLink.transform(activeItemLink));
- this.onClose.next();
- }
-
- private scrollToActiveItem(): void {
- this.keyManager?.change.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(() => {
- this.keyManager?.activeItem?.scrollIntoView();
- });
- }
-}
diff --git a/adev/shared/src/lib/components/select/select.component.html b/adev/shared/src/lib/components/select/select.component.html
deleted file mode 100644
index 872a145a3d5..00000000000
--- a/adev/shared/src/lib/components/select/select.component.html
+++ /dev/null
@@ -1,5 +0,0 @@
-
diff --git a/adev/shared/src/lib/components/select/select.component.scss b/adev/shared/src/lib/components/select/select.component.scss
deleted file mode 100644
index e69de29bb2d..00000000000
diff --git a/adev/shared/src/lib/components/select/select.component.spec.ts b/adev/shared/src/lib/components/select/select.component.spec.ts
deleted file mode 100644
index 1433caacef1..00000000000
--- a/adev/shared/src/lib/components/select/select.component.spec.ts
+++ /dev/null
@@ -1,29 +0,0 @@
-/*!
- * @license
- * Copyright Google LLC All Rights Reserved.
- *
- * Use of this source code is governed by an MIT-style license that can be
- * found in the LICENSE file at https://angular.dev/license
- */
-
-import {ComponentFixture, TestBed} from '@angular/core/testing';
-
-import {Select} from './select.component';
-
-describe('Select', () => {
- let component: Select;
- let fixture: ComponentFixture