/
githubmirror
/
angular
Обзор
Документация
Войти
/
githubmirror
/
angular
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
adev/src/app/features/references/api-reference-list/api-reference-list.component.ts
203 строки
6 KB
Matthieu Riegler
docs(docs-infra): remove explicit OnPush
25 мар 2026, 22:58
25 мар 2026, 22:58
1938054
Код
Авторство
О чём код?
/*! * @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 {CdkMenuModule} from '@angular/cdk/menu'; import {KeyValuePipe} from '@angular/common'; import { Component, EnvironmentInjector, afterNextRender, computed, effect, inject, input, linkedSignal, } from '@angular/core'; import {Select, SelectOption, TextField} from '@angular/docs'; import {FormField, form} from '@angular/forms/signals'; import {MatChipListbox, MatChipOption} from '@angular/material/chips'; import {Params, Router} from '@angular/router'; import ApiItemLabel from '../api-item-label/api-item-label.component'; import ApiItemsSection from '../api-items-section/api-items-section.component'; import {ApiItemType} from '../interfaces/api-item-type'; import {ApiItemsGroup} from '../interfaces/api-items-group'; import {ApiLabel} from '../pipes/api-label.pipe'; import {ApiReferenceManager} from './api-reference-manager.service'; export const ALL_TYPES_KEY = 'All'; export const ALL_PACKAGES = 'All'; export const STATUSES = { stable: 1, developerPreview: 2, experimental: 4, deprecated: 8, } as const; export const DEFAULT_STATUS = STATUSES.stable | STATUSES.developerPreview | STATUSES.experimental; @Component({ selector: 'adev-reference-list', imports: [ ApiItemsSection, ApiItemLabel, TextField, ApiLabel, CdkMenuModule, MatChipListbox, MatChipOption, KeyValuePipe, Select, FormField, ], templateUrl: './api-reference-list.component.html', styleUrls: ['./api-reference-list.component.scss'], }) export default class ApiReferenceList { // services private readonly apiReferenceManager = inject(ApiReferenceManager); private readonly router = inject(Router); private readonly injector = inject(EnvironmentInjector); // inputs readonly queryInput = input<string | undefined>('', {alias: 'query'}); readonly typeInput = input<string | undefined>(ALL_TYPES_KEY, {alias: 'type'}); readonly statusInput = input<number | undefined>(DEFAULT_STATUS, {alias: 'status'}); protected selectedPackageInput = input<string | undefined>(ALL_PACKAGES, {alias: 'package'}); // inputs are route binded, they can reset to undefined // also we want a writable state, so we use a linked signal public form = form( linkedSignal(() => ({ query: this.queryInput() ?? '', status: this.statusInput() ?? DEFAULT_STATUS, type: this.typeInput() ?? ALL_TYPES_KEY, selectedPackage: this.selectedPackageInput() ?? ALL_PACKAGES, })), ); protected packageOptions = computed<SelectOption[]>(() => [ {label: 'All Packages', value: ALL_PACKAGES}, ...this.apiReferenceManager.apiGroups().map((group) => ({ label: group.title, value: group.id, })), ]); // const state protected readonly itemTypes = Object.values(ApiItemType); protected readonly statuses = STATUSES; protected readonly statusLabels = { [STATUSES.stable]: 'Stable', [STATUSES.developerPreview]: 'Developer Preview', [STATUSES.experimental]: 'Experimental', [STATUSES.deprecated]: 'Deprecated', }; readonly filteredGroups = computed((): ApiItemsGroup[] => { const query = this.form.query().value().toLocaleLowerCase(); const status = this.form.status().value(); const type = this.form.type().value(); return this.apiReferenceManager .apiGroups() .map((group) => ({ title: group.title, id: group.id, items: group.items.filter((apiItem) => { return ( (query == '' ? true : apiItem.title.toLocaleLowerCase().includes(query) || group.title.toLocaleLowerCase().includes(query)) && (type === ALL_TYPES_KEY || apiItem.itemType === type) && ((status & STATUSES.stable && !apiItem.developerPreview && !apiItem.deprecated && !apiItem.experimental) || (status & STATUSES.deprecated && apiItem.deprecated) || (status & STATUSES.developerPreview && apiItem.developerPreview) || (status & STATUSES.experimental && apiItem.experimental)) ); }), })) .filter( (group) => group.items.length > 0 && (this.form.selectedPackage().value() === ALL_PACKAGES || group.id === this.form.selectedPackage().value()), ); }); constructor() { effect(() => { const filterInput = this.form.query(); afterNextRender( { write: () => { if (matchMedia('(hover: hover) and (pointer:fine)').matches) { scheduleOnIdle(() => filterInput.focusBoundControl()); } }, }, {injector: this.injector}, ); }); effect(() => { // We'll only set the params if we deviate from the default values const params: Params = { 'query': this.form.query().value() || null, 'type': this.form.type().value() === ALL_TYPES_KEY ? null : this.form.type().value(), 'status': this.form.status().value() === DEFAULT_STATUS ? null : this.form.status().value(), 'package': this.form.selectedPackage().value() === ALL_PACKAGES ? null : this.form.selectedPackage().value(), }; this.router.navigate([], { queryParams: params, replaceUrl: true, preserveFragment: true, info: { disableScrolling: true, }, }); }); } setItemType(itemType: ApiItemType): void { this.form.type().value.update((type) => (type === itemType ? ALL_TYPES_KEY : itemType)); } setStatus(status: number): void { this.form.status().value.update((previousStatus) => { if (this.isStatusSelected(status)) { return previousStatus & ~status; // Clear the bit } else { return previousStatus | status; // Set the bit } }); } isStatusSelected(status: number): boolean { return (this.form.status().value() & status) === status; } } /** * Schedules a function to be run in a new macrotask. * This is needed because the `requestIdleCallback` API is not available in all browsers. * @param fn */ function scheduleOnIdle(fn: () => void): void { if (typeof requestIdleCallback !== 'undefined') { requestIdleCallback(fn); } else { setTimeout(fn, 0); } }