;
17 | }
18 |
--------------------------------------------------------------------------------
/apps/demo/src/app/pages/geolocation/geolocation-page.component.less:
--------------------------------------------------------------------------------
1 | :host {
2 | display: block;
3 | max-inline-size: 37.5rem;
4 | margin: 0 auto;
5 | line-height: 1.5;
6 | font-size: 1.1em;
7 | }
8 |
9 | button {
10 | display: block;
11 | margin: 0 auto;
12 | }
13 |
14 | iframe {
15 | inline-size: 100%;
16 | block-size: 20rem;
17 | }
18 |
--------------------------------------------------------------------------------
/apps/demo/src/app/pages/geolocation/samples/sample-async.ts:
--------------------------------------------------------------------------------
1 | export const SAMPLE_ASYNC = ` `;
4 |
--------------------------------------------------------------------------------
/apps/demo/src/app/pages/geolocation/samples/sample.ts:
--------------------------------------------------------------------------------
1 | export const SAMPLE = `import {GeolocationService} from '@ng-web-apis/geolocation';
2 |
3 | // ...
4 |
5 | constructor(private readonly geolocation$: GeolocationService) {}
6 |
7 | getPosition() {
8 | geolocation$.subscribe((position) => {
9 | doSomethingWithPosition(position);
10 | });
11 | }`;
12 |
--------------------------------------------------------------------------------
/apps/demo/src/app/pages/home/home-page.component.css:
--------------------------------------------------------------------------------
1 | :host {
2 | display: flex;
3 | flex-wrap: wrap;
4 | place-content: flex-start center;
5 | }
6 |
7 | .link {
8 | display: flex;
9 | max-inline-size: 23.75rem;
10 | color: #444;
11 | border-radius: 4px;
12 | border: 1px solid #dcdcdc;
13 | font-size: 0.875rem;
14 | padding: 0 16px 16px;
15 | margin: 0.625rem;
16 | box-sizing: border-box;
17 | transition: box-shadow 0.3s;
18 | }
19 |
20 | .link:hover {
21 | box-shadow: 0 0.75rem 2.25rem rgba(0, 0, 0, 0.2);
22 | }
23 |
24 | .not-supported {
25 | opacity: 0.5;
26 | }
27 |
28 | .not-supported h2::after {
29 | content: 'Not supported by your browser';
30 | display: block;
31 | font-size: 0.6em;
32 | color: var(--tui-status-negative);
33 | }
34 |
35 | .icon {
36 | flex-shrink: 0;
37 | margin: 1.5rem 0.375rem 0 1.25rem;
38 | }
39 |
--------------------------------------------------------------------------------
/apps/demo/src/app/pages/intersection-observer/intersection-observer-page.component.html:
--------------------------------------------------------------------------------
1 |
8 |
13 | I'm being observed
14 |
15 |
16 | Your browser does not support Intersection Observer API
17 |
--------------------------------------------------------------------------------
/apps/demo/src/app/pages/midi/midi-page.component.html:
--------------------------------------------------------------------------------
1 | Web MIDI API is not supported by your browser
2 |
3 |
4 |
10 | Start AudioContext
11 |
12 |
13 |
--------------------------------------------------------------------------------
/apps/demo/src/app/pages/midi/midi-page.component.less:
--------------------------------------------------------------------------------
1 | :host {
2 | position: absolute;
3 | left: 0;
4 | inline-size: 100vw;
5 | perspective: 150vw;
6 | user-select: none;
7 | flex-direction: column;
8 | align-items: center;
9 |
10 | @media (max-width: 600px) {
11 | & {
12 | perspective: 250vw;
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/apps/demo/src/app/pages/midi/midi-page.component.ts:
--------------------------------------------------------------------------------
1 | import {CommonModule} from '@angular/common';
2 | import {ChangeDetectionStrategy, Component, inject} from '@angular/core';
3 | import {FormsModule} from '@angular/forms';
4 | import {WaWebAudio} from '@ng-web-apis/audio';
5 | import {MIDI_SUPPORT} from '@ng-web-apis/midi';
6 | import {TuiButton} from '@taiga-ui/core';
7 |
8 | import {Demo} from './demo/demo.component';
9 |
10 | @Component({
11 | standalone: true,
12 | selector: 'midi-page',
13 | imports: [CommonModule, Demo, FormsModule, TuiButton, WaWebAudio],
14 | templateUrl: './midi-page.component.html',
15 | styleUrls: ['./midi-page.component.less'],
16 | changeDetection: ChangeDetectionStrategy.OnPush,
17 | })
18 | export default class MidiPage {
19 | protected readonly supported = inject(MIDI_SUPPORT);
20 |
21 | protected started = false;
22 |
23 | protected start(): void {
24 | this.started = true;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/apps/demo/src/app/pages/mutation-observer/mutation-observer-page.component.less:
--------------------------------------------------------------------------------
1 | :host {
2 | display: block;
3 | max-inline-size: 37.5rem;
4 | margin: 0 auto;
5 | }
6 |
7 | .observer {
8 | background: #87ceeb;
9 | border-radius: 16px;
10 | padding: 2.5rem;
11 | }
12 |
--------------------------------------------------------------------------------
/apps/demo/src/app/pages/notification/examples/01-getting-permission/index.html:
--------------------------------------------------------------------------------
1 |
2 |
6 | Permission is granted
7 |
8 |
9 |
13 | Permission is denied
14 |
15 |
16 |
22 | Request permission
23 |
24 |
25 |
--------------------------------------------------------------------------------
/apps/demo/src/app/pages/notification/examples/02-create-notification/index.html:
--------------------------------------------------------------------------------
1 |
7 | Send notification
8 |
9 |
--------------------------------------------------------------------------------
/apps/demo/src/app/pages/notification/examples/03-close-notification/index.html:
--------------------------------------------------------------------------------
1 |
7 | Send notification
8 |
9 |
--------------------------------------------------------------------------------
/apps/demo/src/app/pages/notification/examples/04-listen-notification-events/index.html:
--------------------------------------------------------------------------------
1 |
7 | Send notification
8 |
9 |
--------------------------------------------------------------------------------
/apps/demo/src/app/pages/notification/notification-page.style.less:
--------------------------------------------------------------------------------
1 | :host {
2 | display: block;
3 | max-inline-size: 56.25rem;
4 | margin: 0 auto;
5 | font: var(--tui-font-text-m);
6 | }
7 |
8 | tui-notification {
9 | margin-bottom: 1rem;
10 | }
11 |
12 | .header {
13 | display: flex;
14 | font: var(--tui-font-heading-4);
15 | align-items: center;
16 | gap: 1rem;
17 | }
18 |
19 | .description {
20 | margin-bottom: 2rem;
21 | }
22 |
--------------------------------------------------------------------------------
/apps/demo/src/app/pages/payment-request/payment-request-page.component.less:
--------------------------------------------------------------------------------
1 | :host {
2 | display: block;
3 | max-inline-size: 37.5rem;
4 | margin: 0 auto;
5 | }
6 |
7 | .wrapper {
8 | padding: 0 0.625rem;
9 | }
10 |
11 | .how-it-works {
12 | position: relative;
13 | display: flex;
14 | flex-wrap: wrap;
15 | }
16 |
17 | .how-it-works-point {
18 | margin-top: 1.625rem;
19 | margin-bottom: 1.625rem;
20 | }
21 |
22 | .how-it-works-text {
23 | flex: 1;
24 | min-inline-size: 18.75rem;
25 | margin: 0.5rem;
26 | }
27 |
28 | .directive-sample {
29 | display: block;
30 | flex: 1;
31 | margin: 0.5rem;
32 | inline-size: 20.625rem;
33 | }
34 |
35 | .directive-sample-arrow {
36 | position: absolute;
37 | right: 5.9375rem;
38 | top: -2.625rem;
39 | }
40 |
--------------------------------------------------------------------------------
/apps/demo/src/app/pages/payment-request/samples/directives.sample.ts:
--------------------------------------------------------------------------------
1 | export const DIRECTIVES_SAMPLE = `
5 |
10 | {{label}} ({{amount}})
11 |
12 |
16 | Buy
17 |
18 |
`;
19 |
--------------------------------------------------------------------------------
/apps/demo/src/app/pages/payment-request/samples/service.sample.ts:
--------------------------------------------------------------------------------
1 | export const SERVICE_SAMPLE = `import {PaymentRequestService} from '@ng-web-apis/payment-request';
2 |
3 | // ...
4 |
5 | constructor(private readonly paymentRequest: PaymentRequestService) {}
6 |
7 | pay(details: PaymentDetailsInit) {
8 | this.paymentRequest.request(details).then(
9 | response => {
10 | response.complete();
11 | },
12 | error => {},
13 | );
14 | }`;
15 |
--------------------------------------------------------------------------------
/apps/demo/src/app/pages/permissions/permissions-page.component.css:
--------------------------------------------------------------------------------
1 | :host {
2 | display: block;
3 | max-inline-size: 37.5rem;
4 | margin: 0 auto;
5 | }
6 |
--------------------------------------------------------------------------------
/apps/demo/src/app/pages/permissions/samples/basic.ts:
--------------------------------------------------------------------------------
1 | export const SAMPLE = `import { PermissionsService } from '@ng-web-apis/permissions';
2 |
3 | @Component({
4 | selector: 'main',
5 | template: \`
6 | Geolocation state: {{ geolocationState$ | async }}
7 | \`,
8 | changeDetection: ChangeDetectionStrategy.OnPush,
9 | })
10 | export class App {
11 | geolocationState$ = this.permissionsService.state('geolocation');
12 |
13 | constructor(
14 | private readonly permissionsService: PermissionsService,
15 | ) {}
16 | }
17 | `;
18 |
--------------------------------------------------------------------------------
/apps/demo/src/app/pages/platform/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | isIos:
5 | {{ isIos }}
6 |
7 |
--------------------------------------------------------------------------------
/apps/demo/src/app/pages/platform/index.ts:
--------------------------------------------------------------------------------
1 | import {CommonModule} from '@angular/common';
2 | import {ChangeDetectionStrategy, Component, inject} from '@angular/core';
3 | import {WA_IS_IOS} from '@ng-web-apis/platform';
4 | import {MarkdownModule} from 'ngx-markdown';
5 |
6 | @Component({
7 | standalone: true,
8 | selector: 'platform-page',
9 | imports: [CommonModule, MarkdownModule],
10 | templateUrl: './index.html',
11 | styles: ['.example {max-inline-size: 50rem; margin: 2rem auto }'],
12 | changeDetection: ChangeDetectionStrategy.OnPush,
13 | })
14 | export default class CommonPage {
15 | protected readonly isIos = inject(WA_IS_IOS);
16 |
17 | protected readonly readme = import(
18 | '../../../../../../libs/platform/README.md?raw'
19 | ).then((a) => a.default.replace(' ', '')) as any as Promise;
20 | }
21 |
--------------------------------------------------------------------------------
/apps/demo/src/app/pages/resize-observer/resize-observer-page.component.html:
--------------------------------------------------------------------------------
1 |
26 | Your browser does not support Resize Observer API
27 |
--------------------------------------------------------------------------------
/apps/demo/src/app/pages/resize-observer/resize-observer-page.component.ts:
--------------------------------------------------------------------------------
1 | import {CommonModule} from '@angular/common';
2 | import {ChangeDetectionStrategy, Component, inject} from '@angular/core';
3 | import {FormsModule} from '@angular/forms';
4 | import {RESIZE_OBSERVER_SUPPORT, WaResizeObserver} from '@ng-web-apis/resize-observer';
5 |
6 | @Component({
7 | standalone: true,
8 | selector: 'resize-observer-page',
9 | imports: [CommonModule, FormsModule, WaResizeObserver],
10 | templateUrl: './resize-observer-page.component.html',
11 | styleUrls: ['./resize-observer-page.component.less'],
12 | changeDetection: ChangeDetectionStrategy.OnPush,
13 | })
14 | export default class ResizeObserverPage {
15 | protected readonly support = inject(RESIZE_OBSERVER_SUPPORT);
16 |
17 | protected ratio = 0;
18 | protected widthPercent = 50;
19 |
20 | protected onResize(entry: readonly ResizeObserverEntry[]): void {
21 | this.ratio = Math.round((entry[0]?.contentRect.width ?? 0) / 110);
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/apps/demo/src/app/pages/screen-orientation/samples/sample.ts:
--------------------------------------------------------------------------------
1 | export const SAMPLE_TS = `import {ScreenOrientationService} from '@ng-web-apis/screen-orientation';
2 |
3 | // ...
4 | export class Example {
5 | constructor(readonly orientation$: ScreenOrientationService) {}
6 | }`;
7 |
8 | export const SAMPLE_HTML = '{{ orientation$ | async }}
';
9 |
--------------------------------------------------------------------------------
/apps/demo/src/app/pages/screen-orientation/screen-orientation-page.component.less:
--------------------------------------------------------------------------------
1 | :host {
2 | display: block;
3 | max-inline-size: 43.75rem;
4 | margin: 0 auto;
5 | line-height: 1.5;
6 | font-size: 1.1em;
7 | }
8 |
9 | button {
10 | display: block;
11 | margin: 0 auto;
12 | }
13 |
14 | iframe {
15 | inline-size: 100%;
16 | block-size: 20rem;
17 | }
18 |
--------------------------------------------------------------------------------
/apps/demo/src/app/pages/screen-orientation/screen-orientation-page.component.ts:
--------------------------------------------------------------------------------
1 | import {CommonModule} from '@angular/common';
2 | import {ChangeDetectionStrategy, Component, inject} from '@angular/core';
3 | import {ScreenOrientationService} from '@ng-web-apis/screen-orientation';
4 | import {HighlightModule} from 'ngx-highlightjs';
5 |
6 | import {SAMPLE_HTML, SAMPLE_TS} from './samples/sample';
7 |
8 | @Component({
9 | standalone: true,
10 | selector: 'screen-orientation-page',
11 | imports: [CommonModule, HighlightModule],
12 | templateUrl: './screen-orientation-page.component.html',
13 | styleUrls: ['./screen-orientation-page.component.less'],
14 | changeDetection: ChangeDetectionStrategy.OnPush,
15 | })
16 | export default class ScreenOrientationPage {
17 | protected readonly orientation$ = inject(ScreenOrientationService);
18 | protected readonly sample = SAMPLE_TS;
19 | protected readonly sampleHtml = SAMPLE_HTML;
20 | }
21 |
--------------------------------------------------------------------------------
/apps/demo/src/app/pages/storage/example/example.template.html:
--------------------------------------------------------------------------------
1 |
2 | Value from
3 | STORAGE_EVENT
4 | : {{ value$ | async }}
5 |
6 |
7 |
11 | Native update
12 |
13 |
17 | With service
18 |
19 |
20 |
21 |
22 | Native event is only triggered with update happens in another tab. Try opening this page in another tab and type
23 | into the first input. Use
24 | StorageService
25 | if you need to know about changes in the same tab too.
26 |
27 |
28 |
--------------------------------------------------------------------------------
/apps/demo/src/app/pages/storage/storage-page.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/apps/demo/src/app/pages/storage/storage-page.component.less:
--------------------------------------------------------------------------------
1 | :host {
2 | display: block;
3 | max-inline-size: 37.5rem;
4 | margin: 0 auto;
5 | }
6 |
7 | tui-doc-example {
8 | padding: 0;
9 | }
10 |
--------------------------------------------------------------------------------
/apps/demo/src/app/pages/universal/universal-page.component.html:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/apps/demo/src/app/pages/universal/universal-page.component.ts:
--------------------------------------------------------------------------------
1 | import {CommonModule} from '@angular/common';
2 | import {ChangeDetectionStrategy, Component} from '@angular/core';
3 | import {MarkdownModule} from 'ngx-markdown';
4 |
5 | @Component({
6 | standalone: true,
7 | selector: 'universal-page',
8 | imports: [CommonModule, MarkdownModule],
9 | templateUrl: './universal-page.component.html',
10 | changeDetection: ChangeDetectionStrategy.OnPush,
11 | })
12 | export default class UniversalPage {
13 | protected readonly readme = import(
14 | '../../../../../../libs/universal/README.md?raw'
15 | ).then((a) => a.default.replace(' ', '')) as any as Promise;
16 | }
17 |
--------------------------------------------------------------------------------
/apps/demo/src/app/pages/workers/clock.component.ts:
--------------------------------------------------------------------------------
1 | import {CommonModule} from '@angular/common';
2 | import {ChangeDetectionStrategy, Component} from '@angular/core';
3 | import type {Observable} from 'rxjs';
4 | import {map, timer} from 'rxjs';
5 |
6 | @Component({
7 | standalone: true,
8 | selector: 'app-clock',
9 | imports: [CommonModule],
10 | template: `
11 | {{ date$ | async | date: 'mediumTime' }}
12 | `,
13 | changeDetection: ChangeDetectionStrategy.OnPush,
14 | })
15 | export class Clock {
16 | protected readonly date$: Observable = timer(0, 1000).pipe(
17 | map(() => Date.now()),
18 | );
19 | }
20 |
--------------------------------------------------------------------------------
/apps/demo/src/app/pages/workers/workers-page.component.less:
--------------------------------------------------------------------------------
1 | :host {
2 | display: block;
3 | max-inline-size: 37.5rem;
4 | margin: 0 auto;
5 | }
6 |
7 | .example {
8 | min-inline-size: 22.5rem;
9 | border-top: 1px solid #dcdcdc;
10 | margin-top: 16px;
11 | padding-top: 16px;
12 | }
13 |
--------------------------------------------------------------------------------
/apps/demo/src/assets/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/taiga-family/ng-web-apis/d19ac5bb22eb4791452861ba6c79d22a37502756/apps/demo/src/assets/.gitkeep
--------------------------------------------------------------------------------
/apps/demo/src/assets/android-chrome-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/taiga-family/ng-web-apis/d19ac5bb22eb4791452861ba6c79d22a37502756/apps/demo/src/assets/android-chrome-192x192.png
--------------------------------------------------------------------------------
/apps/demo/src/assets/android-chrome-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/taiga-family/ng-web-apis/d19ac5bb22eb4791452861ba6c79d22a37502756/apps/demo/src/assets/android-chrome-512x512.png
--------------------------------------------------------------------------------
/apps/demo/src/assets/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/taiga-family/ng-web-apis/d19ac5bb22eb4791452861ba6c79d22a37502756/apps/demo/src/assets/apple-touch-icon.png
--------------------------------------------------------------------------------
/apps/demo/src/assets/browserconfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | #00aba9
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/apps/demo/src/assets/change-permissions-instructions.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/taiga-family/ng-web-apis/d19ac5bb22eb4791452861ba6c79d22a37502756/apps/demo/src/assets/change-permissions-instructions.jpg
--------------------------------------------------------------------------------
/apps/demo/src/assets/demo.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/taiga-family/ng-web-apis/d19ac5bb22eb4791452861ba6c79d22a37502756/apps/demo/src/assets/demo.mp3
--------------------------------------------------------------------------------
/apps/demo/src/assets/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/taiga-family/ng-web-apis/d19ac5bb22eb4791452861ba6c79d22a37502756/apps/demo/src/assets/favicon-16x16.png
--------------------------------------------------------------------------------
/apps/demo/src/assets/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/taiga-family/ng-web-apis/d19ac5bb22eb4791452861ba6c79d22a37502756/apps/demo/src/assets/favicon-32x32.png
--------------------------------------------------------------------------------
/apps/demo/src/assets/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/taiga-family/ng-web-apis/d19ac5bb22eb4791452861ba6c79d22a37502756/apps/demo/src/assets/favicon.ico
--------------------------------------------------------------------------------
/apps/demo/src/assets/mstile-144x144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/taiga-family/ng-web-apis/d19ac5bb22eb4791452861ba6c79d22a37502756/apps/demo/src/assets/mstile-144x144.png
--------------------------------------------------------------------------------
/apps/demo/src/assets/mstile-150x150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/taiga-family/ng-web-apis/d19ac5bb22eb4791452861ba6c79d22a37502756/apps/demo/src/assets/mstile-150x150.png
--------------------------------------------------------------------------------
/apps/demo/src/assets/mstile-310x150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/taiga-family/ng-web-apis/d19ac5bb22eb4791452861ba6c79d22a37502756/apps/demo/src/assets/mstile-310x150.png
--------------------------------------------------------------------------------
/apps/demo/src/assets/mstile-310x310.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/taiga-family/ng-web-apis/d19ac5bb22eb4791452861ba6c79d22a37502756/apps/demo/src/assets/mstile-310x310.png
--------------------------------------------------------------------------------
/apps/demo/src/assets/mstile-70x70.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/taiga-family/ng-web-apis/d19ac5bb22eb4791452861ba6c79d22a37502756/apps/demo/src/assets/mstile-70x70.png
--------------------------------------------------------------------------------
/apps/demo/src/assets/response.m4a:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/taiga-family/ng-web-apis/d19ac5bb22eb4791452861ba6c79d22a37502756/apps/demo/src/assets/response.m4a
--------------------------------------------------------------------------------
/apps/demo/src/environments/environment.prod.ts:
--------------------------------------------------------------------------------
1 | export const environment = {
2 | production: true,
3 | };
4 |
--------------------------------------------------------------------------------
/apps/demo/src/environments/environment.ts:
--------------------------------------------------------------------------------
1 | export const environment = {
2 | production: false,
3 | };
4 |
--------------------------------------------------------------------------------
/apps/demo/src/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/taiga-family/ng-web-apis/d19ac5bb22eb4791452861ba6c79d22a37502756/apps/demo/src/favicon.ico
--------------------------------------------------------------------------------
/apps/demo/src/main.server.ts:
--------------------------------------------------------------------------------
1 | import type {ApplicationRef} from '@angular/core';
2 | import {importProvidersFrom, mergeApplicationConfig} from '@angular/core';
3 | import {bootstrapApplication} from '@angular/platform-browser';
4 | import {provideServerRendering, ServerModule} from '@angular/platform-server';
5 | import {UNIVERSAL_PROVIDERS} from '@ng-web-apis/universal';
6 |
7 | import {App} from './app/app.component';
8 | import {config} from './app/app.config';
9 |
10 | const serverConfig = mergeApplicationConfig(config, {
11 | providers: [
12 | importProvidersFrom(ServerModule),
13 | provideServerRendering(),
14 | UNIVERSAL_PROVIDERS,
15 | ],
16 | });
17 |
18 | export default async (): Promise =>
19 | bootstrapApplication(App, serverConfig);
20 |
--------------------------------------------------------------------------------
/apps/demo/src/main.ts:
--------------------------------------------------------------------------------
1 | import {bootstrapApplication} from '@angular/platform-browser';
2 |
3 | import {App} from './app/app.component';
4 | import {config} from './app/app.config';
5 |
6 | bootstrapApplication(App, config).catch((err: unknown) => console.error(err));
7 |
--------------------------------------------------------------------------------
/apps/demo/src/manifest.webmanifest:
--------------------------------------------------------------------------------
1 | {
2 | "name": "NG Web APIs",
3 | "short_name": "NG Web APIs",
4 | "theme_color": "#ffffff",
5 | "background_color": "#ffffff",
6 | "display": "standalone",
7 | "scope": "./",
8 | "start_url": "./",
9 | "icons": [
10 | {
11 | "src": "assets/android-chrome-192x192.png",
12 | "sizes": "192x192",
13 | "type": "image/png",
14 | "purpose": "maskable any"
15 | },
16 | {
17 | "src": "assets/android-chrome-512x512.png",
18 | "sizes": "512x512",
19 | "type": "image/png",
20 | "purpose": "maskable any"
21 | }
22 | ]
23 | }
24 |
--------------------------------------------------------------------------------
/apps/demo/src/polyfills.ts:
--------------------------------------------------------------------------------
1 | import '@ng-web-apis/audio/polyfill';
2 | import 'zone.js';
3 |
--------------------------------------------------------------------------------
/apps/demo/src/styles.css:
--------------------------------------------------------------------------------
1 | @import '~highlight.js/styles/github.css';
2 |
3 | body,
4 | html {
5 | block-size: 100%;
6 | font-family: Roboto, 'Helvetica Neue Light', 'Helvetica Neue', Helvetica, Arial, 'Lucida Grande', sans-serif;
7 | }
8 |
9 | a {
10 | color: #1976d2;
11 | text-decoration: none;
12 | }
13 |
14 | h1 {
15 | line-height: 1;
16 | }
17 |
18 | markdown {
19 | display: block;
20 | max-inline-size: 50rem;
21 | margin: 0 auto;
22 | }
23 |
24 | code:not(pre code) {
25 | background: var(--tui-background-base-alt);
26 | vertical-align: middle;
27 | box-shadow: inset 0 -2px var(--tui-background-neutral-1);
28 | padding: 0.375rem 0.5rem;
29 | font-size: 0.875rem;
30 | border-radius: 0.5rem;
31 | }
32 |
--------------------------------------------------------------------------------
/apps/demo/src/typings.d.ts:
--------------------------------------------------------------------------------
1 | /* Import file's content as string.
2 | To understand how it works, see `apps/demo/webpack.config.ts`.
3 | */
4 | declare module '*?raw' {
5 | const result: string;
6 |
7 | export default result;
8 | }
9 |
--------------------------------------------------------------------------------
/apps/demo/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "files": ["src/main.ts", "src/polyfills.ts"],
4 | "compilerOptions": {
5 | "outDir": "./out-tsc/app"
6 | },
7 | "include": ["**/*.d.ts"],
8 | "angularCompilerOptions": {
9 | "strictMetadataEmit": false,
10 | "compilationMode": "full"
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/apps/demo/tsconfig.server.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "./dist/out-tsc-server",
5 | "target": "es2016",
6 | "types": ["node", "webmidi", "dom-view-transitions", "dom-speech-recognition"]
7 | },
8 | "files": ["src/typings.d.ts", "src/main.server.ts", "server.ts"],
9 | "angularCompilerOptions": {
10 | "entryModule": "./src/app/app.server.module#AppServerModule",
11 | "compilationMode": "full"
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/codecov.yml:
--------------------------------------------------------------------------------
1 | codecov:
2 | branch: main
3 | notify:
4 | require_ci_to_pass: no
5 |
6 | coverage:
7 | # This value is used to customize the visible color range in Codecov.
8 | # The first number represents the red, and the second represents green.
9 | # You can change the range of colors by adjusting this configuration.
10 | range: 50..100 # by default 70..100
11 | round: down
12 | precision: 2
13 |
14 | # Disable codecov/patch check
15 | status:
16 | project:
17 | default:
18 | enabled: false
19 | patch:
20 | default:
21 | enabled: false
22 |
--------------------------------------------------------------------------------
/firebase.json:
--------------------------------------------------------------------------------
1 | {
2 | "projects": {
3 | "default": "ng-web-apis-common"
4 | },
5 | "hosting": {
6 | "public": "dist/demo",
7 | "ignore": ["firebase.json", "**/.*", "**/node_modules/**"],
8 | "rewrites": [
9 | {
10 | "source": "**",
11 | "destination": "/index.html"
12 | }
13 | ]
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/libs/audio/demo.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/taiga-family/ng-web-apis/d19ac5bb22eb4791452861ba6c79d22a37502756/libs/audio/demo.mp3
--------------------------------------------------------------------------------
/libs/audio/envelope.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/taiga-family/ng-web-apis/d19ac5bb22eb4791452861ba6c79d22a37502756/libs/audio/envelope.png
--------------------------------------------------------------------------------
/libs/audio/ng-package.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
3 | "assets": [
4 | "logo.svg",
5 | "README.md"
6 | ],
7 | "dest": "../../dist/audio",
8 | "lib": {
9 | "entryFile": "src/index.ts"
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/libs/audio/src/constants/fallback.ts:
--------------------------------------------------------------------------------
1 | import type {Provider} from '@angular/core';
2 |
3 | import {CONSTRUCTOR_SUPPORT} from '../tokens/constructor-support';
4 |
5 | /**
6 | * Just for unit tests
7 | */
8 | export const providers: Provider[] = [
9 | {
10 | provide: CONSTRUCTOR_SUPPORT,
11 | useValue: false,
12 | },
13 | ];
14 |
--------------------------------------------------------------------------------
/libs/audio/src/constants/polling-time.ts:
--------------------------------------------------------------------------------
1 | export const POLLING_TIME = 100;
2 |
--------------------------------------------------------------------------------
/libs/audio/src/directives/channel.ts:
--------------------------------------------------------------------------------
1 | import type {OnDestroy} from '@angular/core';
2 | import {Directive, inject} from '@angular/core';
3 |
4 | import {AUDIO_CONTEXT} from '../tokens/audio-context';
5 | import {CONSTRUCTOR_SUPPORT} from '../tokens/constructor-support';
6 |
7 | @Directive({
8 | standalone: true,
9 | selector: '[waChannel]',
10 | exportAs: 'AudioNode',
11 | })
12 | export class WebAudioChannel extends GainNode implements OnDestroy {
13 | constructor() {
14 | const context = inject(AUDIO_CONTEXT);
15 | const modern = inject(CONSTRUCTOR_SUPPORT);
16 |
17 | if (modern) {
18 | super(context);
19 | } else {
20 | const result = context.createGain();
21 |
22 | Object.setPrototypeOf(result, WebAudioChannel.prototype);
23 |
24 | return result as WebAudioChannel;
25 | }
26 | }
27 |
28 | public ngOnDestroy(): void {
29 | this.disconnect();
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/libs/audio/src/test.js:
--------------------------------------------------------------------------------
1 | class TestProcessor extends AudioWorkletProcessor {
2 | process() {
3 | return true;
4 | }
5 | }
6 |
7 | registerProcessor('test', TestProcessor);
8 |
--------------------------------------------------------------------------------
/libs/audio/src/tokens/audio-context.ts:
--------------------------------------------------------------------------------
1 | import {InjectionToken} from '@angular/core';
2 |
3 | export const WA_AUDIO_CONTEXT = new InjectionToken(
4 | '[WA_AUDIO_CONTEXT]',
5 | {
6 | providedIn: 'root',
7 | factory: () => new AudioContext(),
8 | },
9 | );
10 |
11 | /**
12 | * @deprecated: drop in v5.0, use {@link WA_AUDIO_CONTEXT}
13 | */
14 | export const AUDIO_CONTEXT = WA_AUDIO_CONTEXT;
15 |
--------------------------------------------------------------------------------
/libs/audio/src/tokens/audio-node.ts:
--------------------------------------------------------------------------------
1 | import type {ExistingProvider, Type} from '@angular/core';
2 | import {InjectionToken} from '@angular/core';
3 |
4 | export const WA_AUDIO_NODE = new InjectionToken('[WA_AUDIO_NODE]', {
5 | factory: () => null,
6 | });
7 |
8 | export function asAudioNode(useExisting: Type): ExistingProvider {
9 | return {
10 | provide: WA_AUDIO_NODE,
11 | useExisting,
12 | };
13 | }
14 |
15 | /**
16 | * @deprecated: drop in v5.0, use {@link WA_AUDIO_NODE}
17 | */
18 | export const AUDIO_NODE = WA_AUDIO_NODE;
19 |
--------------------------------------------------------------------------------
/libs/audio/src/tokens/audio-worklet-processors.ts:
--------------------------------------------------------------------------------
1 | import {InjectionToken} from '@angular/core';
2 |
3 | export const WA_AUDIO_WORKLET_PROCESSORS = new InjectionToken(
4 | '[WA_AUDIO_WORKLET_PROCESSORS]',
5 | {
6 | providedIn: 'root',
7 | factory: () => [],
8 | },
9 | );
10 |
11 | /**
12 | * @deprecated: drop in v5.0, use {@link WA_AUDIO_WORKLET_PROCESSORS}
13 | */
14 | export const AUDIO_WORKLET_PROCESSORS = WA_AUDIO_WORKLET_PROCESSORS;
15 |
--------------------------------------------------------------------------------
/libs/audio/src/tokens/audio-worklet-support.ts:
--------------------------------------------------------------------------------
1 | import {inject, InjectionToken} from '@angular/core';
2 |
3 | import {WA_AUDIO_CONTEXT} from './audio-context';
4 |
5 | export const WA_AUDIO_WORKLET_SUPPORT = new InjectionToken(
6 | '[WA_AUDIO_WORKLET_SUPPORT]',
7 | {
8 | factory: () => !!inject(WA_AUDIO_CONTEXT).audioWorklet,
9 | },
10 | );
11 |
12 | /**
13 | * @deprecated: drop in v5.0, use {@link WA_AUDIO_WORKLET_SUPPORT}
14 | */
15 | export const AUDIO_WORKLET_SUPPORT = WA_AUDIO_WORKLET_SUPPORT;
16 |
--------------------------------------------------------------------------------
/libs/audio/src/tokens/constructor-support.ts:
--------------------------------------------------------------------------------
1 | import {inject, InjectionToken} from '@angular/core';
2 |
3 | import {WA_AUDIO_CONTEXT} from './audio-context';
4 |
5 | /**
6 | * This is mostly for internal use only
7 | */
8 | export const WA_CONSTRUCTOR_SUPPORT = new InjectionToken(
9 | '[WA_CONSTRUCTOR_SUPPORT]',
10 | {
11 | providedIn: 'root',
12 | factory: () => {
13 | try {
14 | return !!new GainNode(inject(WA_AUDIO_CONTEXT));
15 | } catch {
16 | return false;
17 | }
18 | },
19 | },
20 | );
21 |
22 | /**
23 | * @deprecated: drop in v5.0, use {@link WA_CONSTRUCTOR_SUPPORT}
24 | */
25 | export const CONSTRUCTOR_SUPPORT = WA_CONSTRUCTOR_SUPPORT;
26 |
--------------------------------------------------------------------------------
/libs/audio/src/tokens/feedback-coefficients.ts:
--------------------------------------------------------------------------------
1 | import {InjectionToken} from '@angular/core';
2 |
3 | export const WA_FEEDBACK_COEFFICIENTS = new InjectionToken(
4 | '[WA_FEEDBACK_COEFFICIENTS]',
5 | );
6 |
7 | /**
8 | * @deprecated: drop in v5.0, use {@link WA_FEEDBACK_COEFFICIENTS}
9 | */
10 | export const FEEDBACK_COEFFICIENTS = WA_FEEDBACK_COEFFICIENTS;
11 |
--------------------------------------------------------------------------------
/libs/audio/src/tokens/feedforward-coefficients.ts:
--------------------------------------------------------------------------------
1 | import {InjectionToken} from '@angular/core';
2 |
3 | export const WA_FEEDFORWARD_COEFFICIENTS = new InjectionToken(
4 | '[WA_FEEDFORWARD_COEFFICIENTS]',
5 | );
6 |
7 | /**
8 | * @deprecated: drop in v5.0, use {@link WA_FEEDFORWARD_COEFFICIENTS}
9 | */
10 | export const FEEDFORWARD_COEFFICIENTS = WA_FEEDFORWARD_COEFFICIENTS;
11 |
--------------------------------------------------------------------------------
/libs/audio/src/tokens/media-stream.ts:
--------------------------------------------------------------------------------
1 | import {InjectionToken} from '@angular/core';
2 |
3 | export const WA_MEDIA_STREAM = new InjectionToken('[WA_MEDIA_STREAM]');
4 |
5 | /**
6 | * @deprecated: drop in v5.0, use {@link WA_MEDIA_STREAM}
7 | */
8 | export const MEDIA_STREAM = WA_MEDIA_STREAM;
9 |
--------------------------------------------------------------------------------
/libs/audio/src/tokens/support.ts:
--------------------------------------------------------------------------------
1 | import {InjectionToken} from '@angular/core';
2 |
3 | export const WA_WEB_AUDIO_SUPPORT = new InjectionToken(
4 | '[WA_WEB_AUDIO_SUPPORT]',
5 | {
6 | providedIn: 'root',
7 | factory: () => !!AudioContext,
8 | },
9 | );
10 |
11 | /**
12 | * @deprecated: drop in v5.0, use {@link WA_WEB_AUDIO_SUPPORT}
13 | */
14 | export const WEB_AUDIO_SUPPORT = WA_WEB_AUDIO_SUPPORT;
15 |
--------------------------------------------------------------------------------
/libs/audio/src/types/audio-node-with-params.ts:
--------------------------------------------------------------------------------
1 | export type AudioNodeWithParams = AudioNode & Record;
2 |
--------------------------------------------------------------------------------
/libs/audio/src/types/audio-param-automation-mode.ts:
--------------------------------------------------------------------------------
1 | export type AudioParamAutomationMode = 'exponential' | 'instant' | 'linear';
2 |
--------------------------------------------------------------------------------
/libs/audio/src/types/audio-param-automation.ts:
--------------------------------------------------------------------------------
1 | import type {AudioParamAutomationMode} from './audio-param-automation-mode';
2 |
3 | export type AudioParamAutomation = Readonly<{
4 | value: number;
5 | duration: number;
6 | mode: AudioParamAutomationMode;
7 | }>;
8 |
--------------------------------------------------------------------------------
/libs/audio/src/types/audio-param-curve.ts:
--------------------------------------------------------------------------------
1 | export type AudioParamCurve = Readonly<{
2 | value: number[];
3 | duration: number;
4 | }>;
5 |
--------------------------------------------------------------------------------
/libs/audio/src/types/audio-param-decorator.ts:
--------------------------------------------------------------------------------
1 | import type {AudioNodeWithParams} from './audio-node-with-params';
2 |
3 | export type AudioParamDecorator = (
4 | target: AudioNodeWithParams,
5 | propertyKey: string,
6 | ) => void;
7 |
8 | export type AudioParamWorkletDecorator = (
9 | target: AudioWorkletNode,
10 | propertyKey: string,
11 | ) => void;
12 |
--------------------------------------------------------------------------------
/libs/audio/src/types/audio-param-input.ts:
--------------------------------------------------------------------------------
1 | import type {AudioParamAutomation} from './audio-param-automation';
2 | import type {AudioParamCurve} from './audio-param-curve';
3 |
4 | export type AudioParamInput =
5 | | Array
6 | | AudioParamAutomation
7 | | AudioParamCurve
8 | | number;
9 |
--------------------------------------------------------------------------------
/libs/audio/src/utils/connect.ts:
--------------------------------------------------------------------------------
1 | export function connect(
2 | source?: AudioNode | null,
3 | destination?: AudioNode | AudioParam | null,
4 | ): void {
5 | if (source && destination) {
6 | // @ts-ignore TS does not have a union override for connect method
7 | source.connect(destination);
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/libs/audio/src/utils/fallback-audio-param.ts:
--------------------------------------------------------------------------------
1 | import type {AudioParamInput} from '../types/audio-param-input';
2 |
3 | export function fallbackAudioParam(value?: AudioParamInput): number {
4 | if (!value) {
5 | return 0;
6 | }
7 |
8 | if (typeof value === 'number') {
9 | return value;
10 | }
11 |
12 | if (value instanceof Array) {
13 | const last = value[value.length - 1]?.value;
14 |
15 | return typeof last === 'number' ? last : (last?.[last.length - 1] ?? 0);
16 | }
17 |
18 | if (value.value instanceof Array) {
19 | return value.value?.[value.value.length - 1] ?? 0;
20 | }
21 |
22 | return value.value;
23 | }
24 |
--------------------------------------------------------------------------------
/libs/audio/src/utils/latency-hint-factory.ts:
--------------------------------------------------------------------------------
1 | export function latencyHintFactory(
2 | latencyHint: AudioContextLatencyCategory | null,
3 | ): AudioContextLatencyCategory | number | undefined {
4 | return latencyHint === null
5 | ? undefined
6 | : Number.parseFloat(latencyHint) || latencyHint;
7 | }
8 |
--------------------------------------------------------------------------------
/libs/audio/src/utils/parse.ts:
--------------------------------------------------------------------------------
1 | import type {AudioParamInput} from '../types/audio-param-input';
2 |
3 | export function parse(value: AudioParamInput | string | null, fallback: number): number {
4 | const parsed = parseFloat((value as string) || '');
5 |
6 | return Number.isNaN(parsed) ? fallback : parsed;
7 | }
8 |
--------------------------------------------------------------------------------
/libs/audio/test.ts:
--------------------------------------------------------------------------------
1 | import 'zone.js';
2 | import 'zone.js/testing';
3 |
4 | import {getTestBed} from '@angular/core/testing';
5 | import {
6 | BrowserDynamicTestingModule,
7 | platformBrowserDynamicTesting,
8 | } from '@angular/platform-browser-dynamic/testing';
9 |
10 | getTestBed().initTestEnvironment(
11 | BrowserDynamicTestingModule,
12 | platformBrowserDynamicTesting(),
13 | );
14 |
--------------------------------------------------------------------------------
/libs/audio/tests/audio-buffer.service.spec.ts:
--------------------------------------------------------------------------------
1 | import {TestBed} from '@angular/core/testing';
2 | import {AudioBufferService} from '@ng-web-apis/audio';
3 |
4 | window.onbeforeunload = jasmine.createSpy();
5 |
6 | describe('AudioBufferService', () => {
7 | let service: AudioBufferService;
8 |
9 | beforeEach(() => {
10 | TestBed.configureTestingModule({});
11 | service = TestBed.inject(AudioBufferService);
12 | });
13 |
14 | it('turns audio file to AudioBuffer', (done) => {
15 | void service.fetch('/base/demo.mp3').then((buffer) => {
16 | expect(buffer instanceof AudioBuffer).toBe(true);
17 |
18 | done();
19 | });
20 | });
21 |
22 | it('caches AudioBuffer', (done) => {
23 | void service.fetch('/base/demo.mp3').then((buffer1) => {
24 | void service.fetch('/base/demo.mp3').then((buffer2) => {
25 | expect(buffer1).toBe(buffer2);
26 |
27 | done();
28 | });
29 | });
30 | });
31 | });
32 |
--------------------------------------------------------------------------------
/libs/audio/tests/audio-param.pipe.spec.ts:
--------------------------------------------------------------------------------
1 | import {WebAudioParamPipe} from '@ng-web-apis/audio';
2 |
3 | window.onbeforeunload = jasmine.createSpy();
4 |
5 | describe('WebAudioParamPipe', () => {
6 | const pipe = new WebAudioParamPipe();
7 |
8 | it('uses exponential mode by default', () => {
9 | expect(pipe.transform(10, 1)).toEqual({
10 | value: 10,
11 | duration: 1,
12 | mode: 'exponential',
13 | });
14 | });
15 |
16 | it('uses given mode', () => {
17 | expect(pipe.transform(10, 1, 'linear')).toEqual({
18 | value: 10,
19 | duration: 1,
20 | mode: 'linear',
21 | });
22 | });
23 |
24 | it('handles array', () => {
25 | expect(pipe.transform([10, 0, 10], 1, 'linear')).toEqual({
26 | value: [10, 0, 10],
27 | duration: 1,
28 | });
29 | });
30 | });
31 |
--------------------------------------------------------------------------------
/libs/audio/tests/audio-param.spec.ts:
--------------------------------------------------------------------------------
1 | import {audioParam} from '@ng-web-apis/audio';
2 |
3 | window.onbeforeunload = jasmine.createSpy();
4 |
5 | describe('audioParam decorator', () => {
6 | it('falls back to plain property when not used on proper AudioParam', () => {
7 | const context = new AudioContext();
8 | const gain: any = new GainNode(context);
9 |
10 | audioParam()(gain, 'prop');
11 | gain.prop = 237;
12 |
13 | expect(gain.prop).toBe(237);
14 | });
15 | });
16 |
--------------------------------------------------------------------------------
/libs/audio/tests/periodic-wave.pipe.spec.ts:
--------------------------------------------------------------------------------
1 | import {TestBed} from '@angular/core/testing';
2 | import {AUDIO_CONTEXT, WebAudioPeriodicWavePipe} from '@ng-web-apis/audio';
3 |
4 | window.onbeforeunload = jasmine.createSpy();
5 |
6 | describe('waPeriodicWave', () => {
7 | // TODO: need investigate why
8 | // Error: Failed to execute 'createPeriodicWave' on 'BaseAudioContext':
9 | // The length of the real array provided (1) is less than the minimum bound (2)
10 | xit('creates PeriodicWave', () => {
11 | TestBed.overrideProvider(AUDIO_CONTEXT, {
12 | useValue: new AudioContext(),
13 | }).runInInjectionContext(() => {
14 | const pipe = new WebAudioPeriodicWavePipe();
15 |
16 | expect(pipe.transform([10]) instanceof PeriodicWave).toBe(true);
17 | });
18 | });
19 | });
20 |
--------------------------------------------------------------------------------
/libs/audio/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.spec.json",
3 | "include": ["**/*.spec.ts", "./test.ts", "**/*.d.ts"],
4 | "files": ["./test.ts"]
5 | }
6 |
--------------------------------------------------------------------------------
/libs/canvas/ng-package.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
3 | "assets": [
4 | "logo.svg",
5 | "README.md"
6 | ],
7 | "dest": "../../dist/canvas",
8 | "lib": {
9 | "entryFile": "src/index.ts"
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/libs/canvas/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@ng-web-apis/canvas",
3 | "version": "4.12.0",
4 | "description": "A library for declarative use of Canvas API with Angular",
5 | "keywords": [
6 | "angular",
7 | "ng",
8 | "canvas",
9 | "svg",
10 | "2d",
11 | "3d",
12 | "webgl",
13 | "graphic"
14 | ],
15 | "homepage": "https://github.com/taiga-family/ng-web-apis/blob/main/libs/canvas/README.md",
16 | "bugs": "https://github.com/taiga-family/ng-web-apis/issues",
17 | "repository": "https://github.com/taiga-family/ng-web-apis",
18 | "license": "Apache-2.0",
19 | "author": {
20 | "name": "Alexander Inkin",
21 | "email": "alexander@inkin.ru"
22 | },
23 | "contributors": [
24 | "Roman Sedov <79601794011@ya.ru>"
25 | ],
26 | "peerDependencies": {
27 | "@angular/core": ">=16.0.0",
28 | "@ng-web-apis/common": ">=4.12.0"
29 | },
30 | "publishConfig": {
31 | "access": "public"
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/libs/canvas/src/interfaces/canvas-method.ts:
--------------------------------------------------------------------------------
1 | import type {Context2dProcessor} from '../types/context-processor';
2 |
3 | export interface CanvasMethod {
4 | call: Context2dProcessor;
5 | }
6 |
--------------------------------------------------------------------------------
/libs/canvas/src/methods/clip-path.ts:
--------------------------------------------------------------------------------
1 | import {
2 | ChangeDetectionStrategy,
3 | Component,
4 | ContentChildren,
5 | QueryList,
6 | } from '@angular/core';
7 |
8 | import type {CanvasMethod} from '../interfaces/canvas-method';
9 | import {CANVAS_METHOD} from '../tokens/canvas-method';
10 |
11 | @Component({
12 | standalone: true,
13 | selector: 'canvas-clip-path',
14 | template: `
15 |
16 | `,
17 | changeDetection: ChangeDetectionStrategy.OnPush,
18 | })
19 | export class WaCanvasClipPath {
20 | @ContentChildren(CANVAS_METHOD)
21 | public readonly pathSteps = new QueryList();
22 | }
23 |
24 | /**
25 | * @deprecated: use {@link WaCanvasClipPath}
26 | */
27 | export const ClipPathComponent = WaCanvasClipPath;
28 |
--------------------------------------------------------------------------------
/libs/canvas/src/methods/path-2d.ts:
--------------------------------------------------------------------------------
1 | import {Directive, inject, Input} from '@angular/core';
2 |
3 | import {WaDrawService} from '../services/draw.service';
4 |
5 | @Directive({
6 | standalone: true,
7 | selector: 'canvas-path[path]',
8 | providers: [WaDrawService],
9 | })
10 | export class WaCanvasPath2d {
11 | private readonly method = inject(WaDrawService);
12 |
13 | @Input()
14 | public path = new Path2D();
15 |
16 | @Input()
17 | public fillRule?: CanvasFillRule;
18 |
19 | constructor() {
20 | this.method.call = (context) => {
21 | context.fill(this.path, this.fillRule);
22 | context.stroke(this.path);
23 | };
24 | }
25 | }
26 |
27 | /**
28 | * @deprecated: use {@link WaCanvasPath2d}
29 | */
30 | export const Path2dDirective = WaCanvasPath2d;
31 |
--------------------------------------------------------------------------------
/libs/canvas/src/methods/text.ts:
--------------------------------------------------------------------------------
1 | import {Directive, inject, Input} from '@angular/core';
2 |
3 | import {WaDrawService} from '../services/draw.service';
4 |
5 | @Directive({
6 | standalone: true,
7 | selector: 'canvas-text',
8 | providers: [WaDrawService],
9 | })
10 | export class WaCanvasText {
11 | private readonly method = inject(WaDrawService);
12 |
13 | @Input()
14 | public text = '';
15 |
16 | @Input()
17 | public x = 0;
18 |
19 | @Input()
20 | public y = 0;
21 |
22 | @Input()
23 | public maxWidth?: number;
24 |
25 | constructor() {
26 | this.method.call = (context) => {
27 | context.fillText(this.text, this.x, this.y, this.maxWidth);
28 | context.strokeText(this.text, this.x, this.y, this.maxWidth);
29 | };
30 | }
31 | }
32 |
33 | /**
34 | * @deprecated: use {@link WaCanvasText}
35 | */
36 | export const TextDirective = WaCanvasText;
37 |
--------------------------------------------------------------------------------
/libs/canvas/src/path/arc-to.ts:
--------------------------------------------------------------------------------
1 | import {Directive, Input} from '@angular/core';
2 |
3 | import type {CanvasMethod} from '../interfaces/canvas-method';
4 | import {asCanvasMethod} from '../tokens/canvas-method';
5 |
6 | @Directive({
7 | standalone: true,
8 | selector: 'canvas-arc-to',
9 | providers: [asCanvasMethod(WaCanvasArcTo)],
10 | })
11 | export class WaCanvasArcTo implements CanvasMethod {
12 | @Input()
13 | public x1 = 0;
14 |
15 | @Input()
16 | public y1 = 0;
17 |
18 | @Input()
19 | public x2 = 0;
20 |
21 | @Input()
22 | public y2 = 0;
23 |
24 | @Input()
25 | public radius = 0;
26 |
27 | public call(context: CanvasRenderingContext2D): void {
28 | context.arcTo(this.x1, this.y1, this.x2, this.y2, this.radius);
29 | }
30 | }
31 |
32 | /**
33 | * @deprecated: use {@link WaCanvasArcTo}
34 | */
35 | export const ArcToDirective = WaCanvasArcTo;
36 |
--------------------------------------------------------------------------------
/libs/canvas/src/path/bezier-curve-to.ts:
--------------------------------------------------------------------------------
1 | import {Directive, Input} from '@angular/core';
2 |
3 | import type {CanvasMethod} from '../interfaces/canvas-method';
4 | import {asCanvasMethod} from '../tokens/canvas-method';
5 |
6 | @Directive({
7 | standalone: true,
8 | selector: 'canvas-bezier-curve-to',
9 | providers: [asCanvasMethod(WaCanvasBezierCurveTo)],
10 | })
11 | export class WaCanvasBezierCurveTo implements CanvasMethod {
12 | @Input()
13 | public cp1x = 0;
14 |
15 | @Input()
16 | public cp1y = 0;
17 |
18 | @Input()
19 | public cp2x = 0;
20 |
21 | @Input()
22 | public cp2y = 0;
23 |
24 | @Input()
25 | public x = 0;
26 |
27 | @Input()
28 | public y = 0;
29 |
30 | public call(context: CanvasRenderingContext2D): void {
31 | context.bezierCurveTo(this.cp1x, this.cp1y, this.cp2x, this.cp2y, this.x, this.y);
32 | }
33 | }
34 |
35 | /**
36 | * @deprecated use {@link WaCanvasBezierCurveTo}
37 | */
38 | export const BezierCurveToDirective = WaCanvasBezierCurveTo;
39 |
--------------------------------------------------------------------------------
/libs/canvas/src/path/line-to.ts:
--------------------------------------------------------------------------------
1 | import {Directive, Input} from '@angular/core';
2 |
3 | import type {CanvasMethod} from '../interfaces/canvas-method';
4 | import {asCanvasMethod} from '../tokens/canvas-method';
5 |
6 | @Directive({
7 | standalone: true,
8 | selector: 'canvas-line-to',
9 | providers: [asCanvasMethod(WaCanvasLineTo)],
10 | })
11 | export class WaCanvasLineTo implements CanvasMethod {
12 | @Input()
13 | public x = 0;
14 |
15 | @Input()
16 | public y = 0;
17 |
18 | public call(context: CanvasRenderingContext2D): void {
19 | context.lineTo(this.x, this.y);
20 | }
21 | }
22 |
23 | /**
24 | * @deprecated: use {@link WaCanvasLineTo}
25 | */
26 | export const LineToDirective = WaCanvasLineTo;
27 |
--------------------------------------------------------------------------------
/libs/canvas/src/path/move-to.ts:
--------------------------------------------------------------------------------
1 | import {Directive, Input} from '@angular/core';
2 |
3 | import type {CanvasMethod} from '../interfaces/canvas-method';
4 | import {asCanvasMethod} from '../tokens/canvas-method';
5 |
6 | @Directive({
7 | standalone: true,
8 | selector: 'canvas-move-to',
9 | providers: [asCanvasMethod(WaCanvasMoveTo)],
10 | })
11 | export class WaCanvasMoveTo implements CanvasMethod {
12 | @Input()
13 | public x = 0;
14 |
15 | @Input()
16 | public y = 0;
17 |
18 | public call(context: CanvasRenderingContext2D): void {
19 | context.moveTo(this.x, this.y);
20 | }
21 | }
22 |
23 | /**
24 | * @deprecated: use {@link WaCanvasMoveTo}
25 | */
26 | export const MoveToDirective = WaCanvasMoveTo;
27 |
--------------------------------------------------------------------------------
/libs/canvas/src/path/quadratic-curve-to.ts:
--------------------------------------------------------------------------------
1 | import {Directive, Input} from '@angular/core';
2 |
3 | import type {CanvasMethod} from '../interfaces/canvas-method';
4 | import {asCanvasMethod} from '../tokens/canvas-method';
5 |
6 | @Directive({
7 | standalone: true,
8 | selector: 'canvas-quadratic-curve-to',
9 | providers: [asCanvasMethod(WaCanvasQuadraticCurveTo)],
10 | })
11 | export class WaCanvasQuadraticCurveTo implements CanvasMethod {
12 | @Input()
13 | public cpx = 0;
14 |
15 | @Input()
16 | public cpy = 0;
17 |
18 | @Input()
19 | public x = 0;
20 |
21 | @Input()
22 | public y = 0;
23 |
24 | public call(context: CanvasRenderingContext2D): void {
25 | context.quadraticCurveTo(this.cpx, this.cpy, this.x, this.y);
26 | }
27 | }
28 |
29 | /**
30 | * @deprecated: use {@link WaCanvasQuadraticCurveTo}
31 | */
32 | export const QuadraticCurveToDirective = WaCanvasQuadraticCurveTo;
33 |
--------------------------------------------------------------------------------
/libs/canvas/src/path/rect.ts:
--------------------------------------------------------------------------------
1 | import {Directive, Input} from '@angular/core';
2 |
3 | import type {CanvasMethod} from '../interfaces/canvas-method';
4 | import {asCanvasMethod} from '../tokens/canvas-method';
5 |
6 | @Directive({
7 | standalone: true,
8 | selector: 'canvas-rect',
9 | providers: [asCanvasMethod(WaCanvasRect)],
10 | })
11 | export class WaCanvasRect implements CanvasMethod {
12 | @Input()
13 | public x = 0;
14 |
15 | @Input()
16 | public y = 0;
17 |
18 | @Input()
19 | public width = 0;
20 |
21 | @Input()
22 | public height = 0;
23 |
24 | public call(context: CanvasRenderingContext2D): void {
25 | context.rect(this.x, this.y, this.width, this.height);
26 | }
27 | }
28 |
29 | /**
30 | * @deprecated: use {@link WaCanvasRect}
31 | */
32 | export const RectDirective = WaCanvasRect;
33 |
--------------------------------------------------------------------------------
/libs/canvas/src/pipes/path.pipe.ts:
--------------------------------------------------------------------------------
1 | import type {PipeTransform} from '@angular/core';
2 | import {Pipe} from '@angular/core';
3 |
4 | @Pipe({
5 | standalone: true,
6 | name: 'path',
7 | })
8 | export class WaCanvasPathPipe implements PipeTransform {
9 | public transform(path: string): Path2D {
10 | return new Path2D(path);
11 | }
12 | }
13 |
14 | /**
15 | * @deprecated: use {@link WaCanvasPathPipe}
16 | */
17 | export const PathPipe = WaCanvasPathPipe;
18 |
--------------------------------------------------------------------------------
/libs/canvas/src/pipes/pattern.pipe.ts:
--------------------------------------------------------------------------------
1 | import type {PipeTransform} from '@angular/core';
2 | import {inject, Pipe} from '@angular/core';
3 |
4 | import {CANVAS_2D_CONTEXT} from '../tokens/canvas-2d-context';
5 |
6 | @Pipe({
7 | standalone: true,
8 | name: 'pattern',
9 | })
10 | export class WaCanvasPatternPipe implements PipeTransform {
11 | private readonly context = inject(CANVAS_2D_CONTEXT);
12 |
13 | public transform(image: CanvasImageSource, repetition = 'repeat'): CanvasPattern {
14 | return this.context.createPattern(image, repetition)!;
15 | }
16 | }
17 |
18 | /**
19 | * @deprecated: use {@link WaCanvasPatternPipe}
20 | */
21 | export const PatternPipe = WaCanvasPatternPipe;
22 |
--------------------------------------------------------------------------------
/libs/canvas/src/pipes/rad.pipe.ts:
--------------------------------------------------------------------------------
1 | import type {PipeTransform} from '@angular/core';
2 | import {Pipe} from '@angular/core';
3 |
4 | @Pipe({
5 | standalone: true,
6 | name: 'rad',
7 | })
8 | export class WaCanvasRadPipe implements PipeTransform {
9 | public transform(input: number): number {
10 | return (input * Math.PI) / 180;
11 | }
12 | }
13 |
14 | /**
15 | * @deprecated: use {@link WaCanvasRadPipe}
16 | */
17 | export const RadPipe = WaCanvasRadPipe;
18 |
--------------------------------------------------------------------------------
/libs/canvas/src/pipes/transform.pipe.ts:
--------------------------------------------------------------------------------
1 | import type {PipeTransform} from '@angular/core';
2 | import {Pipe} from '@angular/core';
3 |
4 | @Pipe({
5 | standalone: true,
6 | name: 'transform',
7 | })
8 | export class WaCanvasTransformPipe implements PipeTransform {
9 | public transform(value: string): DOMMatrix {
10 | return new DOMMatrix(value);
11 | }
12 | }
13 |
14 | /**
15 | * @deprecated: use {@link WaCanvasTransformPipe}
16 | */
17 | export const TransformPipe = WaCanvasTransformPipe;
18 |
--------------------------------------------------------------------------------
/libs/canvas/src/properties/filter.ts:
--------------------------------------------------------------------------------
1 | import {Directive, Input} from '@angular/core';
2 |
3 | import type {CanvasMethod} from '../interfaces/canvas-method';
4 | import {asCanvasProperty} from '../tokens/canvas-properties';
5 |
6 | @Directive({
7 | standalone: true,
8 | selector: 'canvas-draw-image[filter],canvas-path[filter],canvas-text[filter]',
9 | providers: [asCanvasProperty(WaCanvasFilter)],
10 | })
11 | export class WaCanvasFilter implements CanvasMethod, CanvasFilters {
12 | @Input()
13 | public filter = 'none';
14 |
15 | public call(context: CanvasRenderingContext2D): void {
16 | context.filter = this.filter;
17 | }
18 | }
19 |
20 | /**
21 | * @deprecated: use {@link WaCanvasFilter}
22 | */
23 | export const FilterDirective = WaCanvasFilter;
24 |
--------------------------------------------------------------------------------
/libs/canvas/src/tokens/canvas-2d-context.ts:
--------------------------------------------------------------------------------
1 | import {InjectionToken} from '@angular/core';
2 |
3 | export const WA_CANVAS_2D_CONTEXT = new InjectionToken(
4 | '[WA_CANVAS_2D_CONTEXT]',
5 | );
6 |
7 | /**
8 | * @deprecated: drop in v5.0, use {@link WA_CANVAS_2D_CONTEXT}
9 | */
10 | export const CANVAS_2D_CONTEXT = WA_CANVAS_2D_CONTEXT;
11 |
--------------------------------------------------------------------------------
/libs/canvas/src/tokens/canvas-method.ts:
--------------------------------------------------------------------------------
1 | import type {ExistingProvider, Type} from '@angular/core';
2 | import {InjectionToken} from '@angular/core';
3 |
4 | import type {CanvasMethod} from '../interfaces/canvas-method';
5 |
6 | export const WA_CANVAS_METHOD = new InjectionToken('[WA_CANVAS_METHOD]');
7 |
8 | export function asCanvasMethod(useExisting: Type): ExistingProvider {
9 | return {
10 | provide: WA_CANVAS_METHOD,
11 | useExisting,
12 | };
13 | }
14 |
15 | /**
16 | * @deprecated: drop in v5.0, use {@link WA_CANVAS_METHOD}
17 | */
18 | export const CANVAS_METHOD = WA_CANVAS_METHOD;
19 |
--------------------------------------------------------------------------------
/libs/canvas/src/tokens/canvas-properties.ts:
--------------------------------------------------------------------------------
1 | import type {ExistingProvider, Type} from '@angular/core';
2 | import {InjectionToken} from '@angular/core';
3 |
4 | import type {CanvasMethod} from '../interfaces/canvas-method';
5 |
6 | export const WA_CANVAS_PROPERTIES = new InjectionToken(
7 | '[WA_CANVAS_PROPERTIES]',
8 | {
9 | factory: () => [],
10 | },
11 | );
12 |
13 | export function asCanvasProperty(useExisting: Type): ExistingProvider {
14 | return {
15 | provide: WA_CANVAS_PROPERTIES,
16 | multi: true,
17 | useExisting,
18 | };
19 | }
20 |
21 | /**
22 | * @deprecated: drop in v5.0, use {@link WA_CANVAS_PROPERTIES}
23 | */
24 | export const CANVAS_PROPERTIES = WA_CANVAS_PROPERTIES;
25 |
--------------------------------------------------------------------------------
/libs/canvas/src/types/context-processor.ts:
--------------------------------------------------------------------------------
1 | export type Context2dProcessor = (context: CanvasRenderingContext2D) => void;
2 |
--------------------------------------------------------------------------------
/libs/canvas/test.ts:
--------------------------------------------------------------------------------
1 | import 'zone.js';
2 | import 'zone.js/testing';
3 |
4 | import {getTestBed} from '@angular/core/testing';
5 | import {
6 | BrowserDynamicTestingModule,
7 | platformBrowserDynamicTesting,
8 | } from '@angular/platform-browser-dynamic/testing';
9 |
10 | getTestBed().initTestEnvironment(
11 | BrowserDynamicTestingModule,
12 | platformBrowserDynamicTesting(),
13 | );
14 |
--------------------------------------------------------------------------------
/libs/canvas/tests/canvas-properties.spec.ts:
--------------------------------------------------------------------------------
1 | import {TestBed} from '@angular/core/testing';
2 | import {CANVAS_PROPERTIES} from '@ng-web-apis/canvas';
3 |
4 | window.onbeforeunload = jasmine.createSpy();
5 |
6 | describe('CANVAS_PROPERTIES', () => {
7 | it('is empty by default', () => {
8 | expect(TestBed.inject(CANVAS_PROPERTIES)).toEqual([]);
9 | });
10 | });
11 |
--------------------------------------------------------------------------------
/libs/canvas/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.spec.json",
3 | "include": ["**/*.spec.ts", "./test.ts", "**/*.d.ts"],
4 | "files": ["./test.ts"]
5 | }
6 |
--------------------------------------------------------------------------------
/libs/common/ng-package.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
3 | "assets": [
4 | "logo.svg",
5 | "README.md"
6 | ],
7 | "dest": "../../dist/common",
8 | "lib": {
9 | "entryFile": "src/index.ts"
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/libs/common/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from './tokens/animation-frame';
2 | export * from './tokens/caches';
3 | export * from './tokens/crypto';
4 | export * from './tokens/css';
5 | export * from './tokens/history';
6 | export * from './tokens/local-storage';
7 | export * from './tokens/location';
8 | export * from './tokens/media-devices';
9 | export * from './tokens/navigator';
10 | export * from './tokens/network-information';
11 | export * from './tokens/page-visibility';
12 | export * from './tokens/performance';
13 | export * from './tokens/screen';
14 | export * from './tokens/session-storage';
15 | export * from './tokens/speech-recognition';
16 | export * from './tokens/speech-synthesis';
17 | export * from './tokens/user-agent';
18 | export * from './tokens/window';
19 |
--------------------------------------------------------------------------------
/libs/common/src/tokens/caches.ts:
--------------------------------------------------------------------------------
1 | import {inject, InjectionToken} from '@angular/core';
2 |
3 | import {WINDOW} from './window';
4 |
5 | export const WA_CACHES = new InjectionToken('[WA_CACHES]', {
6 | factory: () => inject(WINDOW).caches,
7 | });
8 |
9 | /**
10 | * @deprecated: drop in v5.0, use {@link WA_CACHES}
11 | */
12 | export const CACHES = WA_CACHES;
13 |
--------------------------------------------------------------------------------
/libs/common/src/tokens/crypto.ts:
--------------------------------------------------------------------------------
1 | import {inject, InjectionToken} from '@angular/core';
2 |
3 | import {WINDOW} from './window';
4 |
5 | export const WA_CRYPTO = new InjectionToken('[WA_CRYPTO]', {
6 | factory: () => inject(WINDOW).crypto,
7 | });
8 |
9 | /**
10 | * @deprecated: drop in v5.0, use {@link WA_CRYPTO}
11 | */
12 | export const CRYPTO = WA_CRYPTO;
13 |
--------------------------------------------------------------------------------
/libs/common/src/tokens/css.ts:
--------------------------------------------------------------------------------
1 | import {inject, InjectionToken} from '@angular/core';
2 |
3 | import {WINDOW} from './window';
4 |
5 | declare global {
6 | interface Window {
7 | CSS: typeof CSS;
8 | }
9 | }
10 |
11 | export const WA_CSS = new InjectionToken('[WA_CSS]', {
12 | factory: () =>
13 | inject(WINDOW).CSS ??
14 | ({
15 | escape: (v) => v,
16 | // eslint-disable-next-line no-restricted-syntax
17 | supports: () => false,
18 | } satisfies typeof CSS),
19 | });
20 |
21 | /**
22 | * @deprecated: drop in v5.0, use {@link WA_CSS}
23 | */
24 | export const TOKEN_CSS = WA_CSS;
25 |
26 | export {TOKEN_CSS as CSS};
27 |
--------------------------------------------------------------------------------
/libs/common/src/tokens/history.ts:
--------------------------------------------------------------------------------
1 | import {inject, InjectionToken} from '@angular/core';
2 |
3 | import {WINDOW} from './window';
4 |
5 | export const WA_HISTORY = new InjectionToken('[WA_HISTORY]', {
6 | factory: () => inject(WINDOW).history,
7 | });
8 |
9 | /**
10 | * @deprecated: drop in v5.0, use {@link WA_HISTORY}
11 | */
12 | export const HISTORY = WA_HISTORY;
13 |
--------------------------------------------------------------------------------
/libs/common/src/tokens/local-storage.ts:
--------------------------------------------------------------------------------
1 | import {inject, InjectionToken} from '@angular/core';
2 |
3 | import {WINDOW} from './window';
4 |
5 | export const WA_LOCAL_STORAGE = new InjectionToken('[WA_LOCAL_STORAGE]', {
6 | factory: () => inject(WINDOW).localStorage,
7 | });
8 |
9 | /**
10 | * @deprecated: drop in v5.0, use {@link WA_LOCAL_STORAGE}
11 | */
12 | export const LOCAL_STORAGE = WA_LOCAL_STORAGE;
13 |
--------------------------------------------------------------------------------
/libs/common/src/tokens/location.ts:
--------------------------------------------------------------------------------
1 | import {inject, InjectionToken} from '@angular/core';
2 |
3 | import {WINDOW} from './window';
4 |
5 | export const WA_LOCATION = new InjectionToken('[WA_LOCATION]', {
6 | factory: () => inject(WINDOW).location,
7 | });
8 |
9 | /**
10 | * @deprecated: drop in v5.0, use {@link WA_LOCATION}
11 | */
12 | export const LOCATION = WA_LOCATION;
13 |
--------------------------------------------------------------------------------
/libs/common/src/tokens/media-devices.ts:
--------------------------------------------------------------------------------
1 | import {inject, InjectionToken} from '@angular/core';
2 |
3 | import {NAVIGATOR} from './navigator';
4 |
5 | export const WA_MEDIA_DEVICES = new InjectionToken('[WA_MEDIA_DEVICES]', {
6 | factory: () => inject(NAVIGATOR).mediaDevices,
7 | });
8 |
9 | /**
10 | * @deprecated: drop in v5.0, use {@link WA_MEDIA_DEVICES}
11 | */
12 | export const MEDIA_DEVICES = WA_MEDIA_DEVICES;
13 |
--------------------------------------------------------------------------------
/libs/common/src/tokens/navigator.ts:
--------------------------------------------------------------------------------
1 | import {inject, InjectionToken} from '@angular/core';
2 |
3 | import {WINDOW} from './window';
4 |
5 | export const WA_NAVIGATOR = new InjectionToken('[WA_NAVIGATOR]', {
6 | factory: () => inject(WINDOW).navigator,
7 | });
8 |
9 | /**
10 | * @deprecated: drop in v5.0, use {@link WA_NAVIGATOR}
11 | */
12 | export const NAVIGATOR = WA_NAVIGATOR;
13 |
--------------------------------------------------------------------------------
/libs/common/src/tokens/network-information.ts:
--------------------------------------------------------------------------------
1 | import {inject, InjectionToken} from '@angular/core';
2 |
3 | import {WA_NAVIGATOR} from './navigator';
4 |
5 | export const WA_NETWORK_INFORMATION = new InjectionToken<
6 | // @ts-ignore
7 | (typeof navigator)['connection'] | null
8 | >('[WA_NETWORK_INFORMATION]', {
9 | // @ts-ignore
10 | factory: () => inject(WA_NAVIGATOR).connection || null,
11 | });
12 |
13 | /**
14 | * @deprecated: drop in v5.0, use {@link WA_NETWORK_INFORMATION}
15 | */
16 | export const NETWORK_INFORMATION = WA_NETWORK_INFORMATION;
17 |
--------------------------------------------------------------------------------
/libs/common/src/tokens/page-visibility.ts:
--------------------------------------------------------------------------------
1 | import {DOCUMENT} from '@angular/common';
2 | import {inject, InjectionToken} from '@angular/core';
3 | import type {Observable} from 'rxjs';
4 | import {distinctUntilChanged, fromEvent, map, shareReplay, startWith} from 'rxjs';
5 |
6 | export const WA_PAGE_VISIBILITY = new InjectionToken>(
7 | '[WA_PAGE_VISIBILITY]',
8 | {
9 | factory: () => {
10 | const documentRef = inject(DOCUMENT);
11 |
12 | return fromEvent(documentRef, 'visibilitychange').pipe(
13 | startWith(0),
14 | map(() => documentRef.visibilityState !== 'hidden'),
15 | distinctUntilChanged(),
16 | shareReplay({refCount: false, bufferSize: 1}),
17 | );
18 | },
19 | },
20 | );
21 |
22 | /**
23 | * @deprecated: drop in v5.0, use {@link WA_PAGE_VISIBILITY}
24 | */
25 | export const PAGE_VISIBILITY = WA_PAGE_VISIBILITY;
26 |
--------------------------------------------------------------------------------
/libs/common/src/tokens/performance.ts:
--------------------------------------------------------------------------------
1 | import {inject, InjectionToken} from '@angular/core';
2 |
3 | import {WINDOW} from './window';
4 |
5 | export const WA_PERFORMANCE = new InjectionToken('[WA_PERFORMANCE]', {
6 | factory: () => inject(WINDOW).performance,
7 | });
8 |
9 | /**
10 | * @deprecated: drop in v5.0, use {@link WA_PERFORMANCE}
11 | */
12 | export const PERFORMANCE = WA_PERFORMANCE;
13 |
--------------------------------------------------------------------------------
/libs/common/src/tokens/screen.ts:
--------------------------------------------------------------------------------
1 | import {inject, InjectionToken} from '@angular/core';
2 |
3 | import {WINDOW} from './window';
4 |
5 | export const WA_SCREEN = new InjectionToken('[WA_SCREEN]', {
6 | factory: () => inject(WINDOW).screen,
7 | });
8 |
9 | /**
10 | * @deprecated: drop in v5.0, use {@link WA_SCREEN}
11 | */
12 | export const SCREEN = WA_SCREEN;
13 |
--------------------------------------------------------------------------------
/libs/common/src/tokens/session-storage.ts:
--------------------------------------------------------------------------------
1 | import {inject, InjectionToken} from '@angular/core';
2 |
3 | import {WINDOW} from './window';
4 |
5 | export const WA_SESSION_STORAGE = new InjectionToken('[WA_SESSION_STORAGE]', {
6 | factory: () => inject(WINDOW).sessionStorage,
7 | });
8 |
9 | /**
10 | * @deprecated: drop in v5.0, use {@link WA_SESSION_STORAGE}
11 | */
12 | export const SESSION_STORAGE = WA_SESSION_STORAGE;
13 |
--------------------------------------------------------------------------------
/libs/common/src/tokens/speech-recognition.ts:
--------------------------------------------------------------------------------
1 | import {inject, InjectionToken} from '@angular/core';
2 |
3 | import {WINDOW} from './window';
4 |
5 | export const WA_SPEECH_RECOGNITION = new InjectionToken<
6 | // @ts-ignore
7 | (typeof window)['speechRecognition'] | null
8 | >('[WA_SPEECH_RECOGNITION]: [SPEECH_RECOGNITION]', {
9 | factory: () => {
10 | const windowRef: any = inject(WINDOW);
11 |
12 | return windowRef.speechRecognition || windowRef.webkitSpeechRecognition || null;
13 | },
14 | });
15 |
16 | /**
17 | * @deprecated: drop in v5.0, use {@link WA_SPEECH_RECOGNITION}
18 | */
19 | export const SPEECH_RECOGNITION = WA_SPEECH_RECOGNITION;
20 |
--------------------------------------------------------------------------------
/libs/common/src/tokens/speech-synthesis.ts:
--------------------------------------------------------------------------------
1 | import {inject, InjectionToken} from '@angular/core';
2 |
3 | import {WINDOW} from './window';
4 |
5 | export const WA_SPEECH_SYNTHESIS = new InjectionToken(
6 | '[WA_SPEECH_SYNTHESIS]',
7 | {
8 | factory: () => inject(WINDOW).speechSynthesis,
9 | },
10 | );
11 |
12 | /**
13 | * @deprecated: drop in v5.0, use {@link WA_SPEECH_SYNTHESIS}
14 | */
15 | export const SPEECH_SYNTHESIS = WA_SPEECH_SYNTHESIS;
16 |
--------------------------------------------------------------------------------
/libs/common/src/tokens/user-agent.ts:
--------------------------------------------------------------------------------
1 | import {inject, InjectionToken} from '@angular/core';
2 |
3 | import {NAVIGATOR} from './navigator';
4 |
5 | export const WA_USER_AGENT = new InjectionToken('[WA_USER_AGENT]', {
6 | factory: () => inject(NAVIGATOR).userAgent,
7 | });
8 |
9 | /**
10 | * @deprecated: drop in v5.0, use {@link WA_USER_AGENT}
11 | */
12 | export const USER_AGENT = WA_USER_AGENT;
13 |
--------------------------------------------------------------------------------
/libs/common/src/tokens/window.ts:
--------------------------------------------------------------------------------
1 | import {DOCUMENT} from '@angular/common';
2 | import {inject, InjectionToken} from '@angular/core';
3 |
4 | export const WA_WINDOW = new InjectionToken('[WA_WINDOW]', {
5 | factory: () => {
6 | const {defaultView} = inject(DOCUMENT);
7 |
8 | if (!defaultView) {
9 | throw new Error('Window is not available');
10 | }
11 |
12 | return defaultView;
13 | },
14 | });
15 |
16 | /**
17 | * @deprecated: drop in v5.0, use {@link WA_WINDOW}
18 | */
19 | export const WINDOW = WA_WINDOW;
20 |
--------------------------------------------------------------------------------
/libs/common/test.ts:
--------------------------------------------------------------------------------
1 | import 'zone.js';
2 | import 'zone.js/testing';
3 |
4 | import {getTestBed} from '@angular/core/testing';
5 | import {
6 | BrowserDynamicTestingModule,
7 | platformBrowserDynamicTesting,
8 | } from '@angular/platform-browser-dynamic/testing';
9 |
10 | getTestBed().initTestEnvironment(
11 | BrowserDynamicTestingModule,
12 | platformBrowserDynamicTesting(),
13 | );
14 |
--------------------------------------------------------------------------------
/libs/common/tests/animation-frame.spec.ts:
--------------------------------------------------------------------------------
1 | import {TestBed} from '@angular/core/testing';
2 | import {WA_ANIMATION_FRAME} from '@ng-web-apis/common';
3 | import type {Observable} from 'rxjs';
4 | import {first} from 'rxjs';
5 |
6 | window.onbeforeunload = jasmine.createSpy();
7 |
8 | describe('ANIMATION_FRAME', () => {
9 | it('passes DOMHighResTimeStamp to the subscriber', (done) => {
10 | TestBed.configureTestingModule({});
11 |
12 | const animationFrame$: Observable =
13 | TestBed.inject(WA_ANIMATION_FRAME);
14 |
15 | animationFrame$.pipe(first()).subscribe((timestamp) => {
16 | expect(typeof timestamp).toBe('number');
17 |
18 | done();
19 | });
20 | });
21 | });
22 |
--------------------------------------------------------------------------------
/libs/common/tests/caches.spec.ts:
--------------------------------------------------------------------------------
1 | import {TestBed} from '@angular/core/testing';
2 | import {CACHES} from '@ng-web-apis/common';
3 |
4 | window.onbeforeunload = jasmine.createSpy();
5 |
6 | describe('CACHES', () => {
7 | it('injects window.caches object', () => {
8 | TestBed.configureTestingModule({});
9 |
10 | expect(TestBed.inject(CACHES)).toBe(window.caches);
11 | });
12 | });
13 |
--------------------------------------------------------------------------------
/libs/common/tests/crypto.spec.ts:
--------------------------------------------------------------------------------
1 | import {TestBed} from '@angular/core/testing';
2 | import {CRYPTO} from '@ng-web-apis/common';
3 |
4 | window.onbeforeunload = jasmine.createSpy();
5 |
6 | describe('CRYPTO', () => {
7 | it('injects window.crypto object', () => {
8 | TestBed.configureTestingModule({});
9 |
10 | expect(TestBed.inject(CRYPTO)).toBe(window.crypto);
11 | });
12 | });
13 |
--------------------------------------------------------------------------------
/libs/common/tests/css.spec.ts:
--------------------------------------------------------------------------------
1 | import {TestBed} from '@angular/core/testing';
2 | import {CSS, WINDOW} from '@ng-web-apis/common';
3 |
4 | window.onbeforeunload = jasmine.createSpy();
5 |
6 | describe('CSS', () => {
7 | it('injects window.CSS object', () => {
8 | TestBed.configureTestingModule({});
9 |
10 | expect(TestBed.inject(CSS)).toBe(window.CSS);
11 | });
12 |
13 | it('injects mock when CSS is not available', () => {
14 | TestBed.configureTestingModule({
15 | providers: [{provide: WINDOW, useValue: {}}],
16 | });
17 |
18 | const css = TestBed.inject(CSS);
19 |
20 | expect(css.supports('display', 'block')).toBe(false);
21 | expect(css.escape('<&>hapica$')).toBe('<&>hapica$');
22 | });
23 | });
24 |
--------------------------------------------------------------------------------
/libs/common/tests/history.spec.ts:
--------------------------------------------------------------------------------
1 | import {TestBed} from '@angular/core/testing';
2 | import {HISTORY} from '@ng-web-apis/common';
3 |
4 | window.onbeforeunload = jasmine.createSpy();
5 |
6 | describe('HISTORY', () => {
7 | it('injects window.history object', () => {
8 | TestBed.configureTestingModule({});
9 |
10 | expect(TestBed.inject(HISTORY)).toBe(window.history);
11 | });
12 | });
13 |
--------------------------------------------------------------------------------
/libs/common/tests/local-storage.spec.ts:
--------------------------------------------------------------------------------
1 | import {TestBed} from '@angular/core/testing';
2 | import {LOCAL_STORAGE} from '@ng-web-apis/common';
3 |
4 | window.onbeforeunload = jasmine.createSpy();
5 |
6 | describe('LOCAL_STORAGE', () => {
7 | it('injects window.localStorage object', () => {
8 | TestBed.configureTestingModule({});
9 |
10 | expect(TestBed.inject(LOCAL_STORAGE)).toBe(window.localStorage);
11 | });
12 | });
13 |
--------------------------------------------------------------------------------
/libs/common/tests/location.spec.ts:
--------------------------------------------------------------------------------
1 | import {TestBed} from '@angular/core/testing';
2 | import {LOCATION} from '@ng-web-apis/common';
3 |
4 | window.onbeforeunload = jasmine.createSpy();
5 |
6 | describe('LOCATION', () => {
7 | it('injects window.location object', () => {
8 | TestBed.configureTestingModule({});
9 |
10 | expect(TestBed.inject(LOCATION)).toBe(window.location);
11 | });
12 | });
13 |
--------------------------------------------------------------------------------
/libs/common/tests/media-devices.spec.ts:
--------------------------------------------------------------------------------
1 | import {TestBed} from '@angular/core/testing';
2 | import {MEDIA_DEVICES} from '@ng-web-apis/common';
3 |
4 | window.onbeforeunload = jasmine.createSpy();
5 |
6 | describe('MEDIA_DEVICES', () => {
7 | it('injects window.navigator object', () => {
8 | TestBed.configureTestingModule({});
9 |
10 | expect(TestBed.inject(MEDIA_DEVICES)).toBe(window.navigator.mediaDevices);
11 | });
12 | });
13 |
--------------------------------------------------------------------------------
/libs/common/tests/navigator.spec.ts:
--------------------------------------------------------------------------------
1 | import {TestBed} from '@angular/core/testing';
2 | import {NAVIGATOR} from '@ng-web-apis/common';
3 |
4 | window.onbeforeunload = jasmine.createSpy();
5 |
6 | describe('WINDOW', () => {
7 | it('injects window.navigator object', () => {
8 | TestBed.configureTestingModule({});
9 |
10 | expect(TestBed.inject(NAVIGATOR)).toBe(window.navigator);
11 | });
12 | });
13 |
--------------------------------------------------------------------------------
/libs/common/tests/network-information.spec.ts:
--------------------------------------------------------------------------------
1 | import {TestBed} from '@angular/core/testing';
2 | import {NAVIGATOR, NETWORK_INFORMATION} from '@ng-web-apis/common';
3 |
4 | window.onbeforeunload = jasmine.createSpy();
5 |
6 | describe('NETWORK_INFORMATION', () => {
7 | it('injects window.navigator.connection object', () => {
8 | TestBed.configureTestingModule({});
9 |
10 | expect(TestBed.inject(NETWORK_INFORMATION)).toBe(
11 | (window.navigator as any).connection,
12 | );
13 | });
14 |
15 | it('injects null in unsupported browsers', () => {
16 | TestBed.configureTestingModule({
17 | providers: [{provide: NAVIGATOR, useValue: {}}],
18 | });
19 |
20 | expect(TestBed.inject(NETWORK_INFORMATION)).toBeNull();
21 | });
22 | });
23 |
--------------------------------------------------------------------------------
/libs/common/tests/page-visibility.spec.ts:
--------------------------------------------------------------------------------
1 | import {TestBed} from '@angular/core/testing';
2 | import {WA_PAGE_VISIBILITY} from '@ng-web-apis/common';
3 | import type {Observable} from 'rxjs';
4 | import {first} from 'rxjs';
5 |
6 | window.onbeforeunload = jasmine.createSpy();
7 |
8 | describe('PAGE_VISIBILITY', () => {
9 | it('watching for page visibility state', (done) => {
10 | TestBed.configureTestingModule({});
11 |
12 | const pageVisibility$: Observable = TestBed.inject(WA_PAGE_VISIBILITY);
13 |
14 | pageVisibility$.pipe(first()).subscribe((state) => {
15 | expect(typeof state).toBe('boolean');
16 | expect(state).toBe(true);
17 |
18 | done();
19 | });
20 | });
21 | });
22 |
--------------------------------------------------------------------------------
/libs/common/tests/performance.spec.ts:
--------------------------------------------------------------------------------
1 | import {TestBed} from '@angular/core/testing';
2 | import {PERFORMANCE} from '@ng-web-apis/common';
3 |
4 | window.onbeforeunload = jasmine.createSpy();
5 |
6 | describe('PERFORMANCE', () => {
7 | it('injects window.performance object', () => {
8 | TestBed.configureTestingModule({});
9 |
10 | expect(TestBed.inject(PERFORMANCE)).toBe(window.performance);
11 | });
12 | });
13 |
--------------------------------------------------------------------------------
/libs/common/tests/screen.spec.ts:
--------------------------------------------------------------------------------
1 | import {TestBed} from '@angular/core/testing';
2 | import {SCREEN} from '@ng-web-apis/common';
3 |
4 | window.onbeforeunload = jasmine.createSpy();
5 |
6 | describe('SCREEN', () => {
7 | it('injects window.screen object', () => {
8 | TestBed.configureTestingModule({});
9 |
10 | expect(TestBed.inject(SCREEN)).toBe(window.screen);
11 | });
12 | });
13 |
--------------------------------------------------------------------------------
/libs/common/tests/session-storage.spec.ts:
--------------------------------------------------------------------------------
1 | import {TestBed} from '@angular/core/testing';
2 | import {SESSION_STORAGE} from '@ng-web-apis/common';
3 |
4 | window.onbeforeunload = jasmine.createSpy();
5 |
6 | describe('SESSION_STORAGE', () => {
7 | it('injects window.sessionStorage object', () => {
8 | TestBed.configureTestingModule({});
9 |
10 | expect(TestBed.inject(SESSION_STORAGE)).toBe(window.sessionStorage);
11 | });
12 | });
13 |
--------------------------------------------------------------------------------
/libs/common/tests/speech-recognition.spec.ts:
--------------------------------------------------------------------------------
1 | import {TestBed} from '@angular/core/testing';
2 | import {SPEECH_RECOGNITION} from '@ng-web-apis/common';
3 |
4 | window.onbeforeunload = jasmine.createSpy();
5 |
6 | describe('SPEECH_RECOGNITION', () => {
7 | it('injects webkitSpeechRecognition class', () => {
8 | TestBed.configureTestingModule({});
9 |
10 | expect(TestBed.inject(SPEECH_RECOGNITION)).toBe(
11 | (globalThis as any).webkitSpeechRecognition,
12 | );
13 | });
14 |
15 | it('injects null when browser does not support SpeechRecognition', () => {
16 | TestBed.configureTestingModule({});
17 |
18 | const speechRecognition = (globalThis as any).webkitSpeechRecognition;
19 |
20 | (globalThis as any).webkitSpeechRecognition = undefined;
21 |
22 | expect(TestBed.inject(SPEECH_RECOGNITION)).toBeNull();
23 |
24 | (globalThis as any).webkitSpeechRecognition = speechRecognition;
25 | });
26 | });
27 |
--------------------------------------------------------------------------------
/libs/common/tests/speech-synthesis.spec.ts:
--------------------------------------------------------------------------------
1 | import {TestBed} from '@angular/core/testing';
2 | import {SPEECH_SYNTHESIS} from '@ng-web-apis/common';
3 |
4 | window.onbeforeunload = jasmine.createSpy();
5 |
6 | describe('SPEECH_SYNTHESIS', () => {
7 | it('injects window.speechSynthesis object', () => {
8 | TestBed.configureTestingModule({});
9 |
10 | expect(TestBed.inject(SPEECH_SYNTHESIS)).toBe(window.speechSynthesis);
11 | });
12 | });
13 |
--------------------------------------------------------------------------------
/libs/common/tests/user-agent.spec.ts:
--------------------------------------------------------------------------------
1 | import {TestBed} from '@angular/core/testing';
2 | import {USER_AGENT} from '@ng-web-apis/common';
3 |
4 | window.onbeforeunload = jasmine.createSpy();
5 |
6 | describe('USER_AGENT', () => {
7 | it('injects window.navigator.userAgent string', () => {
8 | TestBed.configureTestingModule({});
9 |
10 | expect(TestBed.inject(USER_AGENT)).toBe(window.navigator.userAgent);
11 | });
12 | });
13 |
--------------------------------------------------------------------------------
/libs/common/tests/window.spec.ts:
--------------------------------------------------------------------------------
1 | import {DOCUMENT} from '@angular/common';
2 | import {TestBed} from '@angular/core/testing';
3 | import {WINDOW} from '@ng-web-apis/common';
4 |
5 | window.onbeforeunload = jasmine.createSpy();
6 |
7 | describe('WINDOW', () => {
8 | it('injects global object', () => {
9 | TestBed.configureTestingModule({});
10 |
11 | expect(TestBed.inject(WINDOW)).toBe(window);
12 | });
13 |
14 | it('throws error if global object not available', () => {
15 | TestBed.configureTestingModule({
16 | providers: [
17 | {
18 | provide: DOCUMENT,
19 | useValue: {
20 | querySelectorAll: () => [],
21 | },
22 | },
23 | ],
24 | });
25 |
26 | expect(() => TestBed.inject(WINDOW)).toThrow();
27 | });
28 | });
29 |
--------------------------------------------------------------------------------
/libs/common/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.spec.json",
3 | "include": ["**/*.spec.ts", "./test.ts", "**/*.d.ts"],
4 | "files": ["./test.ts"]
5 | }
6 |
--------------------------------------------------------------------------------
/libs/geolocation/ng-package.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
3 | "assets": [
4 | "logo.svg",
5 | "README.md"
6 | ],
7 | "dest": "../../dist/geolocation",
8 | "lib": {
9 | "entryFile": "src/index.ts"
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/libs/geolocation/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from './services/geolocation.service';
2 | export * from './tokens/geolocation';
3 | export * from './tokens/geolocation-options';
4 | export * from './tokens/geolocation-support';
5 |
--------------------------------------------------------------------------------
/libs/geolocation/src/tokens/geolocation-options.ts:
--------------------------------------------------------------------------------
1 | import {InjectionToken} from '@angular/core';
2 |
3 | export const WA_POSITION_OPTIONS = new InjectionToken(
4 | '[WA_POSITION_OPTIONS]',
5 | {factory: () => ({})},
6 | );
7 |
8 | /**
9 | * @deprecated: drop in v5.0, use {@link WA_POSITION_OPTIONS}
10 | */
11 | export const POSITION_OPTIONS = WA_POSITION_OPTIONS;
12 |
--------------------------------------------------------------------------------
/libs/geolocation/src/tokens/geolocation-support.ts:
--------------------------------------------------------------------------------
1 | import {inject, InjectionToken} from '@angular/core';
2 |
3 | import {GEOLOCATION} from './geolocation';
4 |
5 | export const WA_GEOLOCATION_SUPPORT = new InjectionToken(
6 | '[WA_GEOLOCATION_SUPPORT]',
7 | {
8 | factory: () => !!inject(GEOLOCATION),
9 | },
10 | );
11 |
12 | /**
13 | * @deprecated: drop in v5.0, use {@link WA_GEOLOCATION_SUPPORT}
14 | */
15 | export const GEOLOCATION_SUPPORT = WA_GEOLOCATION_SUPPORT;
16 |
--------------------------------------------------------------------------------
/libs/geolocation/src/tokens/geolocation.ts:
--------------------------------------------------------------------------------
1 | import {inject, InjectionToken} from '@angular/core';
2 | import {WA_NAVIGATOR} from '@ng-web-apis/common';
3 |
4 | export const WA_GEOLOCATION = new InjectionToken('[WA_GEOLOCATION]', {
5 | factory: () => inject(WA_NAVIGATOR).geolocation,
6 | });
7 |
8 | /**
9 | * @deprecated: drop in v5.0, use {@link WA_GEOLOCATION}
10 | */
11 | export const GEOLOCATION = WA_GEOLOCATION;
12 |
--------------------------------------------------------------------------------
/libs/geolocation/test.ts:
--------------------------------------------------------------------------------
1 | import 'zone.js';
2 | import 'zone.js/testing';
3 |
4 | import {getTestBed} from '@angular/core/testing';
5 | import {
6 | BrowserDynamicTestingModule,
7 | platformBrowserDynamicTesting,
8 | } from '@angular/platform-browser-dynamic/testing';
9 |
10 | getTestBed().initTestEnvironment(
11 | BrowserDynamicTestingModule,
12 | platformBrowserDynamicTesting(),
13 | );
14 |
--------------------------------------------------------------------------------
/libs/geolocation/tests/geolocation.spec.ts:
--------------------------------------------------------------------------------
1 | import {TestBed} from '@angular/core/testing';
2 | import {GeolocationService} from '@ng-web-apis/geolocation';
3 | import {catchError} from 'rxjs';
4 |
5 | window.onbeforeunload = jasmine.createSpy();
6 |
7 | describe('Geolocation token', () => {
8 | let service: any;
9 |
10 | beforeEach(() => {
11 | TestBed.configureTestingModule({
12 | providers: [GeolocationService],
13 | });
14 |
15 | service = TestBed.inject(GeolocationService).pipe(
16 | catchError((_err, caught) => caught),
17 | );
18 | });
19 |
20 | it('defined', () => {
21 | expect(service).toBeDefined();
22 | });
23 | });
24 |
--------------------------------------------------------------------------------
/libs/intersection-observer/ng-package.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
3 | "assets": [
4 | "logo.svg",
5 | "README.md"
6 | ],
7 | "dest": "../../dist/intersection-observer",
8 | "lib": {
9 | "entryFile": "src/index.ts"
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/libs/intersection-observer/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@ng-web-apis/intersection-observer",
3 | "version": "4.12.0",
4 | "description": "A library for declarative use of Intersection Observer API with Angular",
5 | "keywords": [
6 | "angular",
7 | "ng",
8 | "intersection",
9 | "observer"
10 | ],
11 | "homepage": "https://github.com/taiga-family/ng-web-apis/blob/main/libs/intersection-observer/README.md",
12 | "bugs": "https://github.com/taiga-family/ng-web-apis/issues",
13 | "repository": "https://github.com/taiga-family/ng-web-apis",
14 | "license": "Apache-2.0",
15 | "author": {
16 | "name": "Alexander Inkin",
17 | "email": "alexander@inkin.ru"
18 | },
19 | "contributors": [
20 | "Roman Sedov <79601794011@ya.ru>"
21 | ],
22 | "peerDependencies": {
23 | "@angular/core": ">=16.0.0",
24 | "@ng-web-apis/common": ">=4.12.0"
25 | },
26 | "publishConfig": {
27 | "access": "public"
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/libs/intersection-observer/src/classes/safe-observer.ts:
--------------------------------------------------------------------------------
1 | export const SafeObserver =
2 | typeof IntersectionObserver !== 'undefined'
3 | ? IntersectionObserver
4 | : class implements IntersectionObserver {
5 | public readonly root = null;
6 | public readonly rootMargin = '';
7 | public readonly thresholds = [];
8 | public observe(): void {}
9 | public unobserve(): void {}
10 | public disconnect(): void {}
11 | public takeRecords(): IntersectionObserverEntry[] {
12 | return [];
13 | }
14 | };
15 |
--------------------------------------------------------------------------------
/libs/intersection-observer/src/directives/intersection-observee.directive.ts:
--------------------------------------------------------------------------------
1 | import {Directive, inject} from '@angular/core';
2 |
3 | import {IntersectionObserveeService} from '../services/intersection-observee.service';
4 |
5 | @Directive({
6 | standalone: true,
7 | selector: '[waIntersectionObservee]',
8 | outputs: ['waIntersectionObservee'],
9 | providers: [IntersectionObserveeService],
10 | })
11 | export class WaIntersectionObservee {
12 | protected readonly waIntersectionObservee = inject(IntersectionObserveeService);
13 | }
14 |
15 | /**
16 | * @deprecated: use {@link WaIntersectionObservee}
17 | */
18 | export const IntersectionObserveeDirective = WaIntersectionObservee;
19 |
20 | /**
21 | * @deprecated: use {@link WaIntersectionObservee}
22 | */
23 | export const WaObservee = WaIntersectionObservee;
24 |
--------------------------------------------------------------------------------
/libs/intersection-observer/src/directives/intersection-root.directive.ts:
--------------------------------------------------------------------------------
1 | import {Directive, ElementRef} from '@angular/core';
2 |
3 | import {INTERSECTION_ROOT} from '../tokens/intersection-root';
4 |
5 | @Directive({
6 | standalone: true,
7 | selector: '[waIntersectionRoot]',
8 | providers: [
9 | {
10 | provide: INTERSECTION_ROOT,
11 | useExisting: ElementRef,
12 | },
13 | ],
14 | })
15 | export class WaIntersectionRoot {}
16 |
17 | /**
18 | * @deprecated: use {@link WaIntersectionRoot}
19 | */
20 | export const IntersectionRootDirective = WaIntersectionRoot;
21 |
--------------------------------------------------------------------------------
/libs/intersection-observer/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from './directives/intersection-observee.directive';
2 | export * from './directives/intersection-observer.directive';
3 | export * from './directives/intersection-root.directive';
4 | export * from './module';
5 | export * from './services/intersection-observee.service';
6 | export * from './services/intersection-observer.service';
7 | export * from './tokens/intersection-root';
8 | export * from './tokens/intersection-root-margin';
9 | export * from './tokens/intersection-threshold';
10 | export * from './tokens/support';
11 |
--------------------------------------------------------------------------------
/libs/intersection-observer/src/module.ts:
--------------------------------------------------------------------------------
1 | import {NgModule} from '@angular/core';
2 |
3 | import {WaIntersectionObservee} from './directives/intersection-observee.directive';
4 | import {WaIntersectionObserverDirective} from './directives/intersection-observer.directive';
5 | import {WaIntersectionRoot} from './directives/intersection-root.directive';
6 |
7 | export const WaIntersectionObserver = [
8 | WaIntersectionObserverDirective,
9 | WaIntersectionObservee,
10 | WaIntersectionRoot,
11 | ] as const;
12 |
13 | /**
14 | * @deprecated: use {@link WaIntersectionObserver}
15 | */
16 | @NgModule({
17 | imports: [
18 | WaIntersectionObserverDirective,
19 | WaIntersectionObservee,
20 | WaIntersectionRoot,
21 | ],
22 | exports: [
23 | WaIntersectionObserverDirective,
24 | WaIntersectionObservee,
25 | WaIntersectionRoot,
26 | ],
27 | })
28 | export class IntersectionObserverModule {}
29 |
--------------------------------------------------------------------------------
/libs/intersection-observer/src/services/intersection-observee.service.ts:
--------------------------------------------------------------------------------
1 | import {ElementRef, inject, Injectable} from '@angular/core';
2 | import {Observable, share} from 'rxjs';
3 |
4 | import {WaIntersectionObserverDirective} from '../directives/intersection-observer.directive';
5 |
6 | @Injectable()
7 | export class IntersectionObserveeService extends Observable {
8 | constructor() {
9 | const nativeElement: Element = inject(ElementRef).nativeElement;
10 | const observer = inject(WaIntersectionObserverDirective);
11 |
12 | super((subscriber) => {
13 | observer.observe(nativeElement, (entries) => {
14 | subscriber.next(entries);
15 | });
16 |
17 | return () => {
18 | observer.unobserve(nativeElement);
19 | };
20 | });
21 |
22 | return this.pipe(share());
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/libs/intersection-observer/src/tokens/intersection-root-margin.ts:
--------------------------------------------------------------------------------
1 | import {InjectionToken} from '@angular/core';
2 |
3 | export const WA_INTERSECTION_ROOT_MARGIN_DEFAULT = '0px 0px 0px 0px';
4 |
5 | /**
6 | * @deprecated: drop in v5.0, use {@link WA_INTERSECTION_ROOT_MARGIN_DEFAULT}
7 | */
8 | export const INTERSECTION_ROOT_MARGIN_DEFAULT = WA_INTERSECTION_ROOT_MARGIN_DEFAULT;
9 |
10 | export const WA_INTERSECTION_ROOT_MARGIN = new InjectionToken(
11 | '[WA_INTERSECTION_ROOT_MARGIN]',
12 | {
13 | providedIn: 'root',
14 | factory: () => INTERSECTION_ROOT_MARGIN_DEFAULT,
15 | },
16 | );
17 |
18 | /**
19 | * @deprecated: drop in v5.0, use {@link WA_INTERSECTION_ROOT_MARGIN}
20 | */
21 | export const INTERSECTION_ROOT_MARGIN = WA_INTERSECTION_ROOT_MARGIN;
22 |
--------------------------------------------------------------------------------
/libs/intersection-observer/src/tokens/intersection-root.ts:
--------------------------------------------------------------------------------
1 | import type {ElementRef} from '@angular/core';
2 | import {InjectionToken} from '@angular/core';
3 |
4 | export const WA_INTERSECTION_ROOT = new InjectionToken>(
5 | '[WA_INTERSECTION_ROOT]',
6 | );
7 |
8 | /**
9 | * @deprecated: drop in v5.0, use {@link WA_INTERSECTION_ROOT}
10 | */
11 | export const INTERSECTION_ROOT = WA_INTERSECTION_ROOT;
12 |
--------------------------------------------------------------------------------
/libs/intersection-observer/src/tokens/intersection-threshold.ts:
--------------------------------------------------------------------------------
1 | import {InjectionToken} from '@angular/core';
2 |
3 | export const WA_INTERSECTION_THRESHOLD_DEFAULT = 0;
4 |
5 | /**
6 | * @deprecated: drop in v5.0, use {@link WA_INTERSECTION_THRESHOLD_DEFAULT}
7 | */
8 | export const INTERSECTION_THRESHOLD_DEFAULT = WA_INTERSECTION_THRESHOLD_DEFAULT;
9 |
10 | export const WA_INTERSECTION_THRESHOLD = new InjectionToken(
11 | '[WA_INTERSECTION_THRESHOLD]',
12 | {
13 | providedIn: 'root',
14 | factory: () => INTERSECTION_THRESHOLD_DEFAULT,
15 | },
16 | );
17 |
18 | /**
19 | * @deprecated: drop in v5.0, use {@link WA_INTERSECTION_THRESHOLD}
20 | */
21 | export const INTERSECTION_THRESHOLD = WA_INTERSECTION_THRESHOLD;
22 |
--------------------------------------------------------------------------------
/libs/intersection-observer/src/tokens/support.ts:
--------------------------------------------------------------------------------
1 | import {inject, InjectionToken} from '@angular/core';
2 | import {WA_WINDOW} from '@ng-web-apis/common';
3 |
4 | export const WA_INTERSECTION_OBSERVER_SUPPORT = new InjectionToken(
5 | '[WA_INTERSECTION_OBSERVER_SUPPORT]: [INTERSECTION_OBSERVER_SUPPORT]',
6 | {
7 | providedIn: 'root',
8 | factory: () => !!(inject(WA_WINDOW) as any).IntersectionObserver,
9 | },
10 | );
11 |
12 | /**
13 | * @deprecated: drop in v5.0, use {@link WA_INTERSECTION_OBSERVER_SUPPORT}
14 | */
15 | export const INTERSECTION_OBSERVER_SUPPORT = WA_INTERSECTION_OBSERVER_SUPPORT;
16 |
--------------------------------------------------------------------------------
/libs/intersection-observer/src/utils/root-margin-factory.ts:
--------------------------------------------------------------------------------
1 | import {ElementRef, inject} from '@angular/core';
2 |
3 | import {INTERSECTION_ROOT_MARGIN_DEFAULT} from '../tokens/intersection-root-margin';
4 |
5 | export function rootMarginFactory(): string {
6 | return (
7 | inject(ElementRef).nativeElement.getAttribute('waIntersectionRootMargin') ||
8 | INTERSECTION_ROOT_MARGIN_DEFAULT
9 | );
10 | }
11 |
--------------------------------------------------------------------------------
/libs/intersection-observer/src/utils/threshold-factory.ts:
--------------------------------------------------------------------------------
1 | import {ElementRef, inject} from '@angular/core';
2 |
3 | import {INTERSECTION_THRESHOLD_DEFAULT} from '../tokens/intersection-threshold';
4 |
5 | export function thresholdFactory(): number[] | number {
6 | return (
7 | inject(ElementRef)
8 | .nativeElement.getAttribute('waIntersectionThreshold')
9 | ?.split(',')
10 | .map(parseFloat) || INTERSECTION_THRESHOLD_DEFAULT
11 | );
12 | }
13 |
--------------------------------------------------------------------------------
/libs/intersection-observer/test.ts:
--------------------------------------------------------------------------------
1 | import 'zone.js';
2 | import 'zone.js/testing';
3 |
4 | import {getTestBed} from '@angular/core/testing';
5 | import {
6 | BrowserDynamicTestingModule,
7 | platformBrowserDynamicTesting,
8 | } from '@angular/platform-browser-dynamic/testing';
9 |
10 | getTestBed().initTestEnvironment(
11 | BrowserDynamicTestingModule,
12 | platformBrowserDynamicTesting(),
13 | );
14 |
--------------------------------------------------------------------------------
/libs/intersection-observer/tests/support.spec.ts:
--------------------------------------------------------------------------------
1 | import {TestBed} from '@angular/core/testing';
2 | import {INTERSECTION_OBSERVER_SUPPORT} from '@ng-web-apis/intersection-observer';
3 |
4 | window.onbeforeunload = jasmine.createSpy();
5 |
6 | describe('INTERSECTION_OBSERVER_SUPPORT', () => {
7 | it('true in modern browsers', () => {
8 | TestBed.configureTestingModule({});
9 |
10 | expect(TestBed.inject(INTERSECTION_OBSERVER_SUPPORT)).toBe(true);
11 | });
12 | });
13 |
--------------------------------------------------------------------------------
/libs/intersection-observer/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.spec.json",
3 | "include": ["**/*.spec.ts", "./test.ts", "**/*.d.ts"],
4 | "files": ["./test.ts"]
5 | }
6 |
--------------------------------------------------------------------------------
/libs/midi/ng-package.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
3 | "assets": [
4 | "logo.svg",
5 | "README.md"
6 | ],
7 | "dest": "../../dist/midi",
8 | "lib": {
9 | "entryFile": "src/index.ts"
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/libs/midi/src/monotype-operators/aftertouch.ts:
--------------------------------------------------------------------------------
1 | import type {MonoTypeOperatorFunction} from 'rxjs';
2 | import {filter} from 'rxjs';
3 |
4 | import {between} from '../utils/between';
5 |
6 | /**
7 | * Filter MIDI messages to aftertouch changes only
8 | */
9 | export function aftertouch(): MonoTypeOperatorFunction {
10 | return (source) => source.pipe(filter(({data}) => between(data[0] ?? 0, 208, 223)));
11 | }
12 |
--------------------------------------------------------------------------------
/libs/midi/src/monotype-operators/filter-by-channel.ts:
--------------------------------------------------------------------------------
1 | import type {MonoTypeOperatorFunction} from 'rxjs';
2 | import {filter} from 'rxjs';
3 |
4 | import type {MidiChannel} from '../types/midi-channel';
5 |
6 | /**
7 | * Filter MIDI messages by channel
8 | *
9 | * @param channel number from 0 to 15
10 | */
11 | export function filterByChannel(
12 | channel: MidiChannel,
13 | ): MonoTypeOperatorFunction {
14 | return (source) => source.pipe(filter(({data}) => (data[0] ?? 0) % 16 === channel));
15 | }
16 |
--------------------------------------------------------------------------------
/libs/midi/src/monotype-operators/filter-by-id.ts:
--------------------------------------------------------------------------------
1 | import type {MonoTypeOperatorFunction} from 'rxjs';
2 | import {filter} from 'rxjs';
3 |
4 | /**
5 | * Filter MIDI messages by MIDIInput id
6 | *
7 | * @param id
8 | */
9 | export function filterById(id: string): MonoTypeOperatorFunction {
10 | return (source) => source.pipe(filter(({target}) => (target as MIDIPort).id === id));
11 | }
12 |
--------------------------------------------------------------------------------
/libs/midi/src/monotype-operators/filter-by-name.ts:
--------------------------------------------------------------------------------
1 | import type {MonoTypeOperatorFunction} from 'rxjs';
2 | import {filter} from 'rxjs';
3 |
4 | /**
5 | * Filter MIDI messages by MIDIInput name
6 | *
7 | * @param name
8 | */
9 | export function filterByName(name: string): MonoTypeOperatorFunction {
10 | return (source) =>
11 | source.pipe(filter(({target}) => (target as MIDIPort).name === name));
12 | }
13 |
--------------------------------------------------------------------------------
/libs/midi/src/monotype-operators/main-volume.ts:
--------------------------------------------------------------------------------
1 | import type {MonoTypeOperatorFunction} from 'rxjs';
2 | import {filter} from 'rxjs';
3 |
4 | import {between} from '../utils/between';
5 |
6 | /**
7 | * Filter MIDI messages to main volume changes only
8 | */
9 | export function mainVolume(): MonoTypeOperatorFunction {
10 | return (source) =>
11 | source.pipe(filter(({data}) => between(data[0] ?? 0, 176, 191) && data[1] === 7));
12 | }
13 |
--------------------------------------------------------------------------------
/libs/midi/src/monotype-operators/modulation-wheel.ts:
--------------------------------------------------------------------------------
1 | import type {MonoTypeOperatorFunction} from 'rxjs';
2 | import {filter} from 'rxjs';
3 |
4 | import {between} from '../utils/between';
5 |
6 | /**
7 | * Filter MIDI messages to modulation wheel changes only
8 | */
9 | export function modulationWheel(): MonoTypeOperatorFunction {
10 | return (source) =>
11 | source.pipe(filter(({data}) => between(data[0] ?? 0, 176, 191) && data[1] === 1));
12 | }
13 |
--------------------------------------------------------------------------------
/libs/midi/src/monotype-operators/notes.ts:
--------------------------------------------------------------------------------
1 | import type {MonoTypeOperatorFunction} from 'rxjs';
2 | import {filter, map} from 'rxjs';
3 |
4 | import {between} from '../utils/between';
5 |
6 | /**
7 | * Filter MIDI messages to notes only
8 | *
9 | * IMPORTANT: It normalizes noteOff events to noteOn with 0 velocity
10 | */
11 | export function notes(): MonoTypeOperatorFunction {
12 | return (source) =>
13 | source.pipe(
14 | filter(({data}) => between(data[0] ?? 0, 128, 159)),
15 | map((event) => {
16 | if (between(event.data[0] ?? 0, 128, 143)) {
17 | event.data[0] += 16;
18 | event.data[2] = 0;
19 | }
20 |
21 | return event;
22 | }),
23 | );
24 | }
25 |
--------------------------------------------------------------------------------
/libs/midi/src/monotype-operators/pan.ts:
--------------------------------------------------------------------------------
1 | import type {MonoTypeOperatorFunction} from 'rxjs';
2 | import {filter} from 'rxjs';
3 |
4 | import {between} from '../utils/between';
5 |
6 | /**
7 | * Filter MIDI messages to pan changes only
8 | */
9 | export function pan(): MonoTypeOperatorFunction {
10 | return (source) =>
11 | source.pipe(
12 | filter(({data}) => between(data[0] ?? 0, 176, 191) && data[1] === 10),
13 | );
14 | }
15 |
--------------------------------------------------------------------------------
/libs/midi/src/monotype-operators/pitch-bend.ts:
--------------------------------------------------------------------------------
1 | import type {MonoTypeOperatorFunction} from 'rxjs';
2 | import {filter} from 'rxjs';
3 |
4 | import {between} from '../utils/between';
5 |
6 | /**
7 | * Filter MIDI messages to pitch bend changes only
8 | */
9 | export function pitchBend(): MonoTypeOperatorFunction {
10 | return (source) => source.pipe(filter(({data}) => between(data[0] ?? 0, 224, 239)));
11 | }
12 |
--------------------------------------------------------------------------------
/libs/midi/src/monotype-operators/polyphonic-aftertouch.ts:
--------------------------------------------------------------------------------
1 | import type {MonoTypeOperatorFunction} from 'rxjs';
2 | import {filter} from 'rxjs';
3 |
4 | import {between} from '../utils/between';
5 |
6 | /**
7 | * Filter MIDI messages to polyphonic aftertouch changes only
8 | */
9 | export function polyphonicAftertouch(): MonoTypeOperatorFunction {
10 | return (source) => source.pipe(filter(({data}) => between(data[0] ?? 0, 160, 175)));
11 | }
12 |
--------------------------------------------------------------------------------
/libs/midi/src/monotype-operators/program-change.ts:
--------------------------------------------------------------------------------
1 | import type {MonoTypeOperatorFunction} from 'rxjs';
2 | import {filter} from 'rxjs';
3 |
4 | import {between} from '../utils/between';
5 |
6 | /**
7 | * Filter MIDI messages to program changes only
8 | */
9 | export function programChange(): MonoTypeOperatorFunction {
10 | return (source) => source.pipe(filter(({data}) => between(data[0] ?? 0, 208, 223)));
11 | }
12 |
--------------------------------------------------------------------------------
/libs/midi/src/monotype-operators/sustain-pedal.ts:
--------------------------------------------------------------------------------
1 | import type {MonoTypeOperatorFunction} from 'rxjs';
2 | import {filter} from 'rxjs';
3 |
4 | import {between} from '../utils/between';
5 |
6 | /**
7 | * Filter MIDI messages to sustain pedal changes only
8 | */
9 | export function sustainPedal(): MonoTypeOperatorFunction {
10 | return (source) =>
11 | source.pipe(
12 | filter(({data}) => between(data[0] ?? 0, 176, 191) && data[1] === 64),
13 | );
14 | }
15 |
--------------------------------------------------------------------------------
/libs/midi/src/operators/to-data-byte.ts:
--------------------------------------------------------------------------------
1 | import type {OperatorFunction} from 'rxjs';
2 | import {map} from 'rxjs';
3 |
4 | /**
5 | * Extract data byte (2nd) from MIDI message
6 | *
7 | * NOTE: Some status messages do not have 2nd byte, use it when you're certain
8 | */
9 | export function toDataByte(): OperatorFunction {
10 | return (source) => source.pipe(map(({data}) => data[1] ?? 0));
11 | }
12 |
--------------------------------------------------------------------------------
/libs/midi/src/operators/to-data.ts:
--------------------------------------------------------------------------------
1 | import type {OperatorFunction} from 'rxjs';
2 | import {map} from 'rxjs';
3 |
4 | /**
5 | * Extract MIDI data from event
6 | */
7 | export function toData(): OperatorFunction {
8 | return (source) => source.pipe(map(({data}) => data));
9 | }
10 |
--------------------------------------------------------------------------------
/libs/midi/src/operators/to-status-byte.ts:
--------------------------------------------------------------------------------
1 | import type {OperatorFunction} from 'rxjs';
2 | import {map} from 'rxjs';
3 |
4 | /**
5 | * Extract status byte (1st) from MIDI message
6 | */
7 | export function toStatusByte(): OperatorFunction {
8 | return (source) => source.pipe(map(({data}) => data[0] ?? 0));
9 | }
10 |
--------------------------------------------------------------------------------
/libs/midi/src/operators/to-time-stamp.ts:
--------------------------------------------------------------------------------
1 | import type {OperatorFunction} from 'rxjs';
2 | import {map} from 'rxjs';
3 |
4 | /**
5 | * Extract received time from MIDI event
6 | */
7 | export function toTimeStamp(): OperatorFunction {
8 | return (source) => source.pipe(map(({timeStamp}) => timeStamp));
9 | }
10 |
--------------------------------------------------------------------------------
/libs/midi/src/operators/to-value-byte.ts:
--------------------------------------------------------------------------------
1 | import type {OperatorFunction} from 'rxjs';
2 | import {map} from 'rxjs';
3 |
4 | /**
5 | * Extract value byte (3rd) from MIDI message
6 | *
7 | * NOTE: Some status messages do not have 3rd byte, use it when you're certain
8 | */
9 | export function toValueByte(): OperatorFunction {
10 | return (source) => source.pipe(map(({data}) => data[2] ?? 0));
11 | }
12 |
--------------------------------------------------------------------------------
/libs/midi/src/pipes/frequency/frequency.pipe.ts:
--------------------------------------------------------------------------------
1 | import type {PipeTransform} from '@angular/core';
2 | import {Pipe} from '@angular/core';
3 |
4 | import {toFrequency} from '../../utils/to-frequency';
5 |
6 | @Pipe({
7 | standalone: true,
8 | name: 'frequency',
9 | })
10 | export class FrequencyPipe implements PipeTransform {
11 | public transform(note: number, tuning?: number): number {
12 | return toFrequency(note, tuning);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/libs/midi/src/pipes/frequency/frequency.spec.ts:
--------------------------------------------------------------------------------
1 | import {FrequencyPipe} from '@ng-web-apis/midi';
2 |
3 | window.onbeforeunload = jasmine.createSpy();
4 |
5 | describe('FrequencyPipe', () => {
6 | const pipe = new FrequencyPipe();
7 |
8 | it('default tuning', () => {
9 | expect(pipe.transform(69)).toBe(440);
10 | });
11 |
12 | it('altered tuning', () => {
13 | expect(Math.round(pipe.transform(71, 392))).toBe(440);
14 | });
15 | });
16 |
--------------------------------------------------------------------------------
/libs/midi/src/tokens/midi-access.ts:
--------------------------------------------------------------------------------
1 | import {inject, InjectionToken} from '@angular/core';
2 | import {WA_NAVIGATOR} from '@ng-web-apis/common';
3 |
4 | import {SYSEX} from './sysex';
5 |
6 | export const WA_MIDI_ACCESS = new InjectionToken>(
7 | '[WA_MIDI_ACCESS]',
8 | {
9 | providedIn: 'root',
10 | factory: async () => {
11 | const navigatorRef = inject(WA_NAVIGATOR);
12 | const sysex = inject(SYSEX);
13 |
14 | return navigatorRef.requestMIDIAccess
15 | ? navigatorRef.requestMIDIAccess({sysex})
16 | : Promise.reject(new Error('Web MIDI API is not supported'));
17 | },
18 | },
19 | );
20 |
21 | /**
22 | * @deprecated: drop in v5.0, use {@link WA_MIDI_ACCESS}
23 | */
24 | export const MIDI_ACCESS = WA_MIDI_ACCESS;
25 |
--------------------------------------------------------------------------------
/libs/midi/src/tokens/midi-input-query.ts:
--------------------------------------------------------------------------------
1 | import {InjectionToken} from '@angular/core';
2 |
3 | export const WA_MIDI_INPUT_QUERY = new InjectionToken('[WA_MIDI_INPUT_QUERY]');
4 |
5 | /**
6 | * @deprecated: drop in v5.0, use {@link WA_MIDI_INPUT_QUERY}
7 | */
8 | export const MIDI_INPUT_QUERY = WA_MIDI_INPUT_QUERY;
9 |
--------------------------------------------------------------------------------
/libs/midi/src/tokens/midi-input.ts:
--------------------------------------------------------------------------------
1 | import {InjectionToken} from '@angular/core';
2 |
3 | export const WA_MIDI_INPUT = new InjectionToken>(
4 | '[WA_MIDI_INPUT]',
5 | );
6 |
7 | /**
8 | * @deprecated: drop in v5.0, use {@link WA_MIDI_INPUT}
9 | */
10 | export const MIDI_INPUT = WA_MIDI_INPUT;
11 |
--------------------------------------------------------------------------------
/libs/midi/src/tokens/midi-inputs.ts:
--------------------------------------------------------------------------------
1 | import {InjectionToken} from '@angular/core';
2 | import type {Observable} from 'rxjs';
3 |
4 | import {getPortsStream} from '../utils/get-ports-stream';
5 |
6 | export const WA_MIDI_INPUTS = new InjectionToken>(
7 | '[WA_MIDI_INPUTS]',
8 | {
9 | factory: () => getPortsStream('inputs'),
10 | },
11 | );
12 |
13 | /**
14 | * @deprecated: drop in v5.0, use {@link WA_MIDI_INPUTS}
15 | */
16 | export const MIDI_INPUTS = WA_MIDI_INPUTS;
17 |
--------------------------------------------------------------------------------
/libs/midi/src/tokens/midi-output-query.ts:
--------------------------------------------------------------------------------
1 | import {InjectionToken} from '@angular/core';
2 |
3 | export const WA_MIDI_OUTPUT_QUERY = new InjectionToken('[WA_MIDI_OUTPUT_QUERY]');
4 |
5 | /**
6 | * @deprecated: drop in v5.0, use {@link WA_MIDI_OUTPUT_QUERY}
7 | */
8 | export const MIDI_OUTPUT_QUERY = WA_MIDI_OUTPUT_QUERY;
9 |
--------------------------------------------------------------------------------
/libs/midi/src/tokens/midi-output.ts:
--------------------------------------------------------------------------------
1 | import {InjectionToken} from '@angular/core';
2 |
3 | export const WA_MIDI_OUTPUT = new InjectionToken>(
4 | '[WA_MIDI_OUTPUT]',
5 | );
6 |
7 | /**
8 | * @deprecated: drop in v5.0, use {@link WA_MIDI_OUTPUT}
9 | */
10 | export const MIDI_OUTPUT = WA_MIDI_OUTPUT;
11 |
--------------------------------------------------------------------------------
/libs/midi/src/tokens/midi-outputs.ts:
--------------------------------------------------------------------------------
1 | import {InjectionToken} from '@angular/core';
2 | import type {Observable} from 'rxjs';
3 |
4 | import {getPortsStream} from '../utils/get-ports-stream';
5 |
6 | export const WA_MIDI_OUTPUTS = new InjectionToken>(
7 | '[WA_MIDI_OUTPUTS]',
8 | {
9 | factory: () => getPortsStream('outputs'),
10 | },
11 | );
12 |
13 | /**
14 | * @deprecated: drop in v5.0, use {@link WA_MIDI_OUTPUTS}
15 | */
16 | export const MIDI_OUTPUTS = WA_MIDI_OUTPUTS;
17 |
--------------------------------------------------------------------------------
/libs/midi/src/tokens/midi-support.ts:
--------------------------------------------------------------------------------
1 | import {inject, InjectionToken} from '@angular/core';
2 | import {WA_NAVIGATOR} from '@ng-web-apis/common';
3 |
4 | export const WA_MIDI_SUPPORT = new InjectionToken('[WA_MIDI_SUPPORT]', {
5 | factory: () => !!inject(WA_NAVIGATOR).requestMIDIAccess,
6 | });
7 |
8 | /**
9 | * @deprecated: drop in v5.0, use {@link WA_MIDI_SUPPORT}
10 | */
11 | export const MIDI_SUPPORT = WA_MIDI_SUPPORT;
12 |
--------------------------------------------------------------------------------
/libs/midi/src/tokens/sysex.ts:
--------------------------------------------------------------------------------
1 | import {InjectionToken} from '@angular/core';
2 |
3 | export const WA_SYSEX = new InjectionToken('[WA_SYSEX]', {
4 | providedIn: 'root',
5 | // eslint-disable-next-line no-restricted-syntax
6 | factory: () => false,
7 | });
8 |
9 | /**
10 | * @deprecated: drop in v5.0, use {@link WA_SYSEX}
11 | */
12 | export const SYSEX = WA_SYSEX;
13 |
--------------------------------------------------------------------------------
/libs/midi/src/types/midi-channel.ts:
--------------------------------------------------------------------------------
1 | export type MidiChannel =
2 | | 0
3 | | 1
4 | | 2
5 | | 3
6 | | 4
7 | | 5
8 | | 6
9 | | 7
10 | | 8
11 | | 9
12 | | 10
13 | | 11
14 | | 12
15 | | 13
16 | | 14
17 | | 15;
18 |
--------------------------------------------------------------------------------
/libs/midi/src/utils/between.ts:
--------------------------------------------------------------------------------
1 | export function between(value: number, min: number, max: number): boolean {
2 | return value >= min && value <= max;
3 | }
4 |
--------------------------------------------------------------------------------
/libs/midi/src/utils/to-frequency.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Convert MIDI notes to frequencies
3 | *
4 | * @param note MIDI note
5 | * @param tuning tuning for middle A (440 by default)
6 | */
7 | export function toFrequency(note: number, tuning = 440): number {
8 | return 2 ** ((note - 69) / 12) * tuning;
9 | }
10 |
--------------------------------------------------------------------------------
/libs/midi/src/utils/to-note.ts:
--------------------------------------------------------------------------------
1 | const COEFFICIENT = 2 ** (1 / 12);
2 |
3 | /**
4 | * Convert frequencies to MIDI notes
5 | *
6 | * @param frequency
7 | * @param tuning tuning for middle A (440 by default)
8 | */
9 | export function toNote(frequency: number, tuning = 440): number {
10 | return Math.round(Math.log(frequency / tuning) / Math.log(COEFFICIENT)) + 69;
11 | }
12 |
--------------------------------------------------------------------------------
/libs/midi/test.ts:
--------------------------------------------------------------------------------
1 | import 'zone.js';
2 | import 'zone.js/testing';
3 |
4 | import {getTestBed} from '@angular/core/testing';
5 | import {
6 | BrowserDynamicTestingModule,
7 | platformBrowserDynamicTesting,
8 | } from '@angular/platform-browser-dynamic/testing';
9 |
10 | getTestBed().initTestEnvironment(
11 | BrowserDynamicTestingModule,
12 | platformBrowserDynamicTesting(),
13 | );
14 |
--------------------------------------------------------------------------------
/libs/midi/tests/aftertouch.spec.ts:
--------------------------------------------------------------------------------
1 | import {aftertouch} from '@ng-web-apis/midi';
2 | import {from} from 'rxjs';
3 |
4 | window.onbeforeunload = jasmine.createSpy();
5 |
6 | describe('aftertouch', () => {
7 | it('lets aftertouch events through', () => {
8 | const events: WebMidi.MIDIMessageEvent[] = Array.from({length: 3}, (_, i) => {
9 | const data = new Uint8Array([i + 207, 2, 3]);
10 | const receivedTime = 1.234;
11 |
12 | return new MIDIMessageEvent('midimessage', {data, receivedTime} as any);
13 | }) as WebMidi.MIDIMessageEvent[];
14 |
15 | const processed: any[] = [];
16 |
17 | from(events)
18 | .pipe(aftertouch())
19 | .subscribe((result) => {
20 | processed.push(result);
21 | });
22 |
23 | expect(processed[0]).toBe(events[1]);
24 | expect(processed[1]).toBe(events[2]);
25 | });
26 | });
27 |
--------------------------------------------------------------------------------
/libs/midi/tests/filter-by-channel.spec.ts:
--------------------------------------------------------------------------------
1 | import {filterByChannel} from '@ng-web-apis/midi';
2 | import {from} from 'rxjs';
3 |
4 | window.onbeforeunload = jasmine.createSpy();
5 |
6 | describe('aftertouch', () => {
7 | it('filters events by channel', () => {
8 | const events: WebMidi.MIDIMessageEvent[] = Array.from({length: 3}, (_, i) => {
9 | const data = new Uint8Array([i, 2, 3]);
10 | const receivedTime = 1.234;
11 |
12 | return new MIDIMessageEvent('midimessage', {data, receivedTime} as any);
13 | }) as WebMidi.MIDIMessageEvent[];
14 |
15 | const processed: any[] = [];
16 |
17 | from(events)
18 | .pipe(filterByChannel(1))
19 | .subscribe((result) => {
20 | processed.push(result);
21 | });
22 |
23 | expect(processed[0]).toBe(events[1]);
24 | expect(processed.length).toBe(1);
25 | });
26 | });
27 |
--------------------------------------------------------------------------------
/libs/midi/tests/main-volume.spec.ts:
--------------------------------------------------------------------------------
1 | import {mainVolume} from '@ng-web-apis/midi';
2 | import {from} from 'rxjs';
3 |
4 | window.onbeforeunload = jasmine.createSpy();
5 |
6 | describe('mainVolume', () => {
7 | it('lets main volume events through', () => {
8 | const events: WebMidi.MIDIMessageEvent[] = Array.from({length: 3}, (_, i) => {
9 | const data = new Uint8Array([i + 175, 7, 3]);
10 | const receivedTime = 1.234;
11 |
12 | return new MIDIMessageEvent('midimessage', {data, receivedTime} as any);
13 | }) as WebMidi.MIDIMessageEvent[];
14 |
15 | const processed: any[] = [];
16 |
17 | from(events)
18 | .pipe(mainVolume())
19 | .subscribe((result) => {
20 | processed.push(result);
21 | });
22 |
23 | expect(processed[0]).toBe(events[1]);
24 | expect(processed[1]).toBe(events[2]);
25 | });
26 | });
27 |
--------------------------------------------------------------------------------
/libs/midi/tests/modulation-wheel.spec.ts:
--------------------------------------------------------------------------------
1 | import {modulationWheel} from '@ng-web-apis/midi';
2 | import {from} from 'rxjs';
3 |
4 | window.onbeforeunload = jasmine.createSpy();
5 |
6 | describe('modulationWheel', () => {
7 | it('lets main volume events through', () => {
8 | const events: WebMidi.MIDIMessageEvent[] = Array.from({length: 3}, (_, i) => {
9 | const data = new Uint8Array([i + 175, 1, 3]);
10 | const receivedTime = 1.234;
11 |
12 | return new MIDIMessageEvent('midimessage', {data, receivedTime} as any);
13 | }) as WebMidi.MIDIMessageEvent[];
14 |
15 | const processed: any[] = [];
16 |
17 | from(events)
18 | .pipe(modulationWheel())
19 | .subscribe((result) => {
20 | processed.push(result);
21 | });
22 |
23 | expect(processed[0]).toBe(events[1]);
24 | expect(processed[1]).toBe(events[2]);
25 | });
26 | });
27 |
--------------------------------------------------------------------------------
/libs/midi/tests/pan.spec.ts:
--------------------------------------------------------------------------------
1 | import {pan} from '@ng-web-apis/midi';
2 | import {from} from 'rxjs';
3 |
4 | window.onbeforeunload = jasmine.createSpy();
5 |
6 | describe('pan', () => {
7 | it('lets main volume events through', () => {
8 | const events: WebMidi.MIDIMessageEvent[] = Array.from({length: 3}, (_, i) => {
9 | const data = new Uint8Array([i + 175, 10, 3]);
10 | const receivedTime = 1.234;
11 |
12 | return new MIDIMessageEvent('midimessage', {data, receivedTime} as any);
13 | }) as WebMidi.MIDIMessageEvent[];
14 |
15 | const processed: any[] = [];
16 |
17 | from(events)
18 | .pipe(pan())
19 | .subscribe((result) => {
20 | processed.push(result);
21 | });
22 |
23 | expect(processed[0]).toBe(events[1]);
24 | expect(processed[1]).toBe(events[2]);
25 | });
26 | });
27 |
--------------------------------------------------------------------------------
/libs/midi/tests/pitch-bend.spec.ts:
--------------------------------------------------------------------------------
1 | import {pitchBend} from '@ng-web-apis/midi';
2 | import {from} from 'rxjs';
3 |
4 | window.onbeforeunload = jasmine.createSpy();
5 |
6 | describe('pitchBend', () => {
7 | it('lets pitch bend events through', () => {
8 | const events: WebMidi.MIDIMessageEvent[] = Array.from({length: 3}, (_, i) => {
9 | const data = new Uint8Array([i + 223, 2, 3]);
10 | const receivedTime = 1.234;
11 |
12 | return new MIDIMessageEvent('midimessage', {data, receivedTime} as any);
13 | }) as WebMidi.MIDIMessageEvent[];
14 |
15 | const processed: any[] = [];
16 |
17 | from(events)
18 | .pipe(pitchBend())
19 | .subscribe((result) => {
20 | processed.push(result);
21 | });
22 |
23 | expect(processed[0]).toBe(events[1]);
24 | expect(processed[1]).toBe(events[2]);
25 | });
26 | });
27 |
--------------------------------------------------------------------------------
/libs/midi/tests/polyphonic-aftertouch.spec.ts:
--------------------------------------------------------------------------------
1 | import {polyphonicAftertouch} from '@ng-web-apis/midi';
2 | import {from} from 'rxjs';
3 |
4 | window.onbeforeunload = jasmine.createSpy();
5 |
6 | describe('polyphonicAftertouch', () => {
7 | it('lets polyphonic aftertouch events through', () => {
8 | const events: WebMidi.MIDIMessageEvent[] = Array.from({length: 3}, (_, i) => {
9 | const data = new Uint8Array([i + 159, 2, 3]);
10 | const receivedTime = 1.234;
11 |
12 | return new MIDIMessageEvent('midimessage', {data, receivedTime} as any);
13 | }) as WebMidi.MIDIMessageEvent[];
14 |
15 | const processed: any[] = [];
16 |
17 | from(events)
18 | .pipe(polyphonicAftertouch())
19 | .subscribe((result) => {
20 | processed.push(result);
21 | });
22 |
23 | expect(processed[0]).toBe(events[1]);
24 | expect(processed[1]).toBe(events[2]);
25 | });
26 | });
27 |
--------------------------------------------------------------------------------
/libs/midi/tests/program-change.spec.ts:
--------------------------------------------------------------------------------
1 | import {programChange} from '@ng-web-apis/midi';
2 | import {from} from 'rxjs';
3 |
4 | window.onbeforeunload = jasmine.createSpy();
5 |
6 | describe('programChange', () => {
7 | it('lets program change events through', () => {
8 | const events: WebMidi.MIDIMessageEvent[] = Array.from({length: 3}, (_, i) => {
9 | const data = new Uint8Array([i + 207, 2, 3]);
10 | const receivedTime = 1.234;
11 |
12 | return new MIDIMessageEvent('midimessage', {data, receivedTime} as any);
13 | }) as WebMidi.MIDIMessageEvent[];
14 |
15 | const processed: any[] = [];
16 |
17 | from(events)
18 | .pipe(programChange())
19 | .subscribe((result) => {
20 | processed.push(result);
21 | });
22 |
23 | expect(processed[0]).toBe(events[1]);
24 | expect(processed[1]).toBe(events[2]);
25 | });
26 | });
27 |
--------------------------------------------------------------------------------
/libs/midi/tests/sustain-pedal.spec.ts:
--------------------------------------------------------------------------------
1 | import {sustainPedal} from '@ng-web-apis/midi';
2 | import {from} from 'rxjs';
3 |
4 | window.onbeforeunload = jasmine.createSpy();
5 |
6 | describe('sustainPedal', () => {
7 | it('lets sustain pedal events through', () => {
8 | const events: WebMidi.MIDIMessageEvent[] = Array.from({length: 3}, (_, i) => {
9 | const data = new Uint8Array([i + 175, 64, 3]);
10 | const receivedTime = 1.234;
11 |
12 | return new MIDIMessageEvent('midimessage', {data, receivedTime} as any);
13 | }) as WebMidi.MIDIMessageEvent[];
14 |
15 | const processed: any[] = [];
16 |
17 | from(events)
18 | .pipe(sustainPedal())
19 | .subscribe((result) => {
20 | processed.push(result);
21 | });
22 |
23 | expect(processed[0]).toBe(events[1]);
24 | expect(processed[1]).toBe(events[2]);
25 | });
26 | });
27 |
--------------------------------------------------------------------------------
/libs/midi/tests/to-data-byte.spec.ts:
--------------------------------------------------------------------------------
1 | import {toDataByte} from '@ng-web-apis/midi';
2 | import {of} from 'rxjs';
3 |
4 | window.onbeforeunload = jasmine.createSpy();
5 |
6 | describe('toDataByte', () => {
7 | it('extracts data byte', () => {
8 | const event = new MIDIMessageEvent('midimessage', {
9 | data: new Uint8Array([1, 2, 3]),
10 | }) as WebMidi.MIDIMessageEvent;
11 |
12 | of(event)
13 | .pipe(toDataByte())
14 | .subscribe((result) => {
15 | expect(result).toBe(2);
16 | });
17 | });
18 | });
19 |
--------------------------------------------------------------------------------
/libs/midi/tests/to-data.spec.ts:
--------------------------------------------------------------------------------
1 | import {toData} from '@ng-web-apis/midi';
2 | import {of} from 'rxjs';
3 |
4 | window.onbeforeunload = jasmine.createSpy();
5 |
6 | describe('toData', () => {
7 | it('extracts data array', () => {
8 | const event = new MIDIMessageEvent('midimessage', {
9 | data: new Uint8Array([1, 2, 3]),
10 | }) as WebMidi.MIDIMessageEvent;
11 |
12 | of(event)
13 | .pipe(toData())
14 | .subscribe((result) => {
15 | expect(result).toBe(event.data);
16 | });
17 | });
18 | });
19 |
--------------------------------------------------------------------------------
/libs/midi/tests/to-status-byte.spec.ts:
--------------------------------------------------------------------------------
1 | import {toStatusByte} from '@ng-web-apis/midi';
2 | import {of} from 'rxjs';
3 |
4 | window.onbeforeunload = jasmine.createSpy();
5 |
6 | describe('toStatusByte', () => {
7 | it('extracts status byte', () => {
8 | const event = new MIDIMessageEvent('midimessage', {
9 | data: new Uint8Array([1, 2, 3]),
10 | }) as WebMidi.MIDIMessageEvent;
11 |
12 | of(event)
13 | .pipe(toStatusByte())
14 | .subscribe((result) => {
15 | expect(result).toBe(1);
16 | });
17 | });
18 | });
19 |
--------------------------------------------------------------------------------
/libs/midi/tests/to-time-stamp.spec.ts:
--------------------------------------------------------------------------------
1 | import {toTimeStamp} from '@ng-web-apis/midi';
2 | import {of} from 'rxjs';
3 |
4 | window.onbeforeunload = jasmine.createSpy();
5 |
6 | describe('toTime', () => {
7 | it('extracts receivedTime timestamp', () => {
8 | const event = new MIDIMessageEvent('midimessage', {
9 | data: new Uint8Array([1, 2, 3]),
10 | }) as WebMidi.MIDIMessageEvent;
11 |
12 | of(event)
13 | .pipe(toTimeStamp())
14 | .subscribe((result) => {
15 | expect(result).toBe(event.timeStamp);
16 | });
17 | });
18 | });
19 |
--------------------------------------------------------------------------------
/libs/midi/tests/to-value-byte.spec.ts:
--------------------------------------------------------------------------------
1 | import {toValueByte} from '@ng-web-apis/midi';
2 | import {of} from 'rxjs';
3 |
4 | window.onbeforeunload = jasmine.createSpy();
5 |
6 | describe('toValueByte', () => {
7 | it('extracts value byte', () => {
8 | const event = new MIDIMessageEvent('midimessage', {
9 | data: new Uint8Array([1, 2, 3]),
10 | }) as WebMidi.MIDIMessageEvent;
11 |
12 | of(event)
13 | .pipe(toValueByte())
14 | .subscribe((result) => {
15 | expect(result).toBe(3);
16 | });
17 | });
18 | });
19 |
--------------------------------------------------------------------------------
/libs/midi/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.spec.json",
3 | "include": ["**/*.spec.ts", "./test.ts", "**/*.d.ts"],
4 | "files": ["./test.ts"]
5 | }
6 |
--------------------------------------------------------------------------------
/libs/mutation-observer/ng-package.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
3 | "assets": [
4 | "logo.svg",
5 | "README.md"
6 | ],
7 | "dest": "../../dist/mutation-observer",
8 | "lib": {
9 | "entryFile": "src/index.ts"
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/libs/mutation-observer/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@ng-web-apis/mutation-observer",
3 | "version": "4.12.0",
4 | "description": "A library for declarative use of Mutation Observer API with Angular",
5 | "keywords": [
6 | "angular",
7 | "ng",
8 | "mutation",
9 | "observer"
10 | ],
11 | "homepage": "https://github.com/taiga-family/ng-web-apis/blob/main/libs/mutation-observer/README.md",
12 | "bugs": "https://github.com/taiga-family/ng-web-apis/issues",
13 | "repository": "https://github.com/taiga-family/ng-web-apis",
14 | "license": "Apache-2.0",
15 | "author": {
16 | "name": "Alexander Inkin",
17 | "email": "alexander@inkin.ru"
18 | },
19 | "contributors": [
20 | "Roman Sedov <79601794011@ya.ru>"
21 | ],
22 | "peerDependencies": {
23 | "@angular/core": ">=16.0.0",
24 | "@ng-web-apis/common": ">=4.12.0"
25 | },
26 | "publishConfig": {
27 | "access": "public"
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/libs/mutation-observer/src/classes/safe-observer.ts:
--------------------------------------------------------------------------------
1 | export const SafeObserver =
2 | typeof MutationObserver !== 'undefined'
3 | ? MutationObserver
4 | : class implements MutationObserver {
5 | public observe(): void {}
6 | public disconnect(): void {}
7 | public takeRecords(): MutationRecord[] {
8 | return [];
9 | }
10 | };
11 |
--------------------------------------------------------------------------------
/libs/mutation-observer/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from './directives/mutation-observer.directive';
2 | export * from './services/mutation-observer.service';
3 | export * from './tokens/mutation-observer-init';
4 |
--------------------------------------------------------------------------------
/libs/mutation-observer/src/services/mutation-observer.service.ts:
--------------------------------------------------------------------------------
1 | import {ElementRef, inject, Injectable} from '@angular/core';
2 | import {Observable} from 'rxjs';
3 |
4 | import {SafeObserver} from '../classes/safe-observer';
5 | import {MUTATION_OBSERVER_INIT} from '../tokens/mutation-observer-init';
6 |
7 | @Injectable()
8 | export class MutationObserverService extends Observable {
9 | constructor() {
10 | const nativeElement: Node = inject(ElementRef).nativeElement;
11 | const config = inject(MUTATION_OBSERVER_INIT);
12 |
13 | super((subscriber) => {
14 | const observer = new SafeObserver((records) => {
15 | subscriber.next(records);
16 | });
17 |
18 | observer.observe(nativeElement, config);
19 |
20 | return () => {
21 | observer.disconnect();
22 | };
23 | });
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/libs/mutation-observer/src/tokens/mutation-observer-init.ts:
--------------------------------------------------------------------------------
1 | import type {Provider} from '@angular/core';
2 | import {InjectionToken} from '@angular/core';
3 |
4 | export const WA_MUTATION_OBSERVER_INIT = new InjectionToken(
5 | '[WA_MUTATION_OBSERVER_INIT]',
6 | );
7 |
8 | /**
9 | * @deprecated: drop in v5.0, use {@link WA_MUTATION_OBSERVER_INIT}
10 | */
11 | export const MUTATION_OBSERVER_INIT = WA_MUTATION_OBSERVER_INIT;
12 |
13 | export function provideMutationObserverInit(useValue: MutationObserverInit): Provider {
14 | return {
15 | provide: WA_MUTATION_OBSERVER_INIT,
16 | useValue,
17 | };
18 | }
19 |
--------------------------------------------------------------------------------
/libs/mutation-observer/src/utils/boolean-attribute.ts:
--------------------------------------------------------------------------------
1 | export function booleanAttribute(element: Element, attribute: string): true | undefined {
2 | return element.getAttribute(attribute) !== null || undefined;
3 | }
4 |
--------------------------------------------------------------------------------
/libs/mutation-observer/src/utils/mutation-observer-init-factory.ts:
--------------------------------------------------------------------------------
1 | import {ElementRef, inject} from '@angular/core';
2 |
3 | import {booleanAttribute} from './boolean-attribute';
4 |
5 | export function mutationObserverInitFactory(): MutationObserverInit {
6 | const {nativeElement} = inject(ElementRef);
7 | const attributeFilter: string | null = nativeElement.getAttribute('attributeFilter');
8 |
9 | return {
10 | attributeFilter: attributeFilter?.split(',').map((attr) => attr.trim()),
11 | attributeOldValue: booleanAttribute(nativeElement, 'attributeOldValue'),
12 | attributes: booleanAttribute(nativeElement, 'attributes'),
13 | characterData: booleanAttribute(nativeElement, 'characterData'),
14 | characterDataOldValue: booleanAttribute(nativeElement, 'characterDataOldValue'),
15 | childList: booleanAttribute(nativeElement, 'childList'),
16 | subtree: booleanAttribute(nativeElement, 'subtree'),
17 | };
18 | }
19 |
--------------------------------------------------------------------------------
/libs/mutation-observer/test.ts:
--------------------------------------------------------------------------------
1 | import 'zone.js';
2 | import 'zone.js/testing';
3 |
4 | import {getTestBed} from '@angular/core/testing';
5 | import {
6 | BrowserDynamicTestingModule,
7 | platformBrowserDynamicTesting,
8 | } from '@angular/platform-browser-dynamic/testing';
9 |
10 | getTestBed().initTestEnvironment(
11 | BrowserDynamicTestingModule,
12 | platformBrowserDynamicTesting(),
13 | );
14 |
--------------------------------------------------------------------------------
/libs/mutation-observer/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.spec.json",
3 | "include": ["**/*.spec.ts", "./test.ts", "**/*.d.ts"],
4 | "files": ["./test.ts"]
5 | }
6 |
--------------------------------------------------------------------------------
/libs/notification/ng-package.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
3 | "assets": [
4 | "logo.svg",
5 | "README.md"
6 | ],
7 | "dest": "../../dist/notification",
8 | "lib": {
9 | "entryFile": "src/index.ts"
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/libs/notification/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@ng-web-apis/notification",
3 | "version": "4.12.0",
4 | "description": "A library for declarative use of Notification API with Angular",
5 | "keywords": [
6 | "angular",
7 | "ng",
8 | "notification"
9 | ],
10 | "homepage": "https://github.com/taiga-family/ng-web-apis/blob/main/libs/notification/README.md",
11 | "bugs": "https://github.com/taiga-family/ng-web-apis/issues",
12 | "repository": "https://github.com/taiga-family/ng-web-apis",
13 | "license": "Apache-2.0",
14 | "author": {
15 | "name": "Nikita Barsukov",
16 | "email": "nikita.s.barsukov@gmail.com"
17 | },
18 | "contributors": [
19 | "Nikita Barsukov "
20 | ],
21 | "peerDependencies": {
22 | "@angular/core": ">=16.0.0",
23 | "@ng-web-apis/common": ">=4.12.0",
24 | "rxjs": ">=7.0.0"
25 | },
26 | "publishConfig": {
27 | "access": "public"
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/libs/notification/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from './services/notification.service';
2 | export * from './tokens/support';
3 |
--------------------------------------------------------------------------------
/libs/notification/src/tokens/support.ts:
--------------------------------------------------------------------------------
1 | import {inject, InjectionToken} from '@angular/core';
2 | import {WA_WINDOW} from '@ng-web-apis/common';
3 |
4 | export const WA_NOTIFICATION_SUPPORT = new InjectionToken(
5 | '[WA_NOTIFICATION_SUPPORT]',
6 | {
7 | factory: () => 'Notification' in inject(WA_WINDOW),
8 | },
9 | );
10 |
11 | /**
12 | * @deprecated: drop in v5.0, use {@link WA_NOTIFICATION_SUPPORT}
13 | */
14 | export const NOTIFICATION_SUPPORT = WA_NOTIFICATION_SUPPORT;
15 |
--------------------------------------------------------------------------------
/libs/notification/test.ts:
--------------------------------------------------------------------------------
1 | import 'zone.js';
2 | import 'zone.js/testing';
3 |
4 | import {getTestBed} from '@angular/core/testing';
5 | import {
6 | BrowserDynamicTestingModule,
7 | platformBrowserDynamicTesting,
8 | } from '@angular/platform-browser-dynamic/testing';
9 |
10 | getTestBed().initTestEnvironment(
11 | BrowserDynamicTestingModule,
12 | platformBrowserDynamicTesting(),
13 | );
14 |
--------------------------------------------------------------------------------
/libs/notification/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.spec.json",
3 | "include": ["**/*.spec.ts", "./test.ts", "**/*.d.ts"],
4 | "files": ["./test.ts"]
5 | }
6 |
--------------------------------------------------------------------------------
/libs/payment-request/ng-package.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
3 | "assets": [
4 | "logo.svg",
5 | "README.md"
6 | ],
7 | "dest": "../../dist/payment-request",
8 | "lib": {
9 | "entryFile": "src/index.ts"
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/libs/payment-request/src/directives/payment-item/payment-item.directive.ts:
--------------------------------------------------------------------------------
1 | import {Directive, Input} from '@angular/core';
2 |
3 | @Directive({
4 | standalone: true,
5 | selector: '[waPaymentItem][paymentAmount][paymentLabel]',
6 | })
7 | export class WaPaymentItem implements PaymentItem {
8 | @Input('paymentAmount')
9 | public amount!: PaymentCurrencyAmount;
10 |
11 | @Input('paymentLabel')
12 | public label!: string;
13 |
14 | @Input('paymentPending')
15 | public pending?: boolean;
16 | }
17 |
18 | /**
19 | * @deprecated: use {@link WaPaymentItem}
20 | */
21 | export const PaymentItemDirective = WaPaymentItem;
22 |
--------------------------------------------------------------------------------
/libs/payment-request/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from './directives/payment/payment.directive';
2 | export * from './directives/payment-item/payment-item.directive';
3 | export * from './directives/payment-submit/payment-submit.directive';
4 | export * from './module';
5 | export * from './services/payment-request.service';
6 | export * from './tokens/payment-methods';
7 | export * from './tokens/payment-options';
8 | export * from './tokens/payment-request-support';
9 |
--------------------------------------------------------------------------------
/libs/payment-request/src/module.ts:
--------------------------------------------------------------------------------
1 | import {WaPayment} from './directives/payment/payment.directive';
2 | import {WaPaymentItem} from './directives/payment-item/payment-item.directive';
3 | import {WaPaymentSubmit} from './directives/payment-submit/payment-submit.directive';
4 |
5 | export const WaPaymentRequest = [WaPayment, WaPaymentItem, WaPaymentSubmit];
6 |
7 | /**
8 | * @deprecated: use {@link WaPaymentRequest}
9 | */
10 | export const PaymentRequestModule = WaPaymentRequest;
11 |
--------------------------------------------------------------------------------
/libs/payment-request/src/tokens/payment-methods.ts:
--------------------------------------------------------------------------------
1 | import {InjectionToken} from '@angular/core';
2 |
3 | export const WA_PAYMENT_METHODS = new InjectionToken(
4 | '[WA_PAYMENT_METHODS]',
5 | {
6 | factory: () => [{supportedMethods: 'basic-card'}],
7 | },
8 | );
9 |
10 | /**
11 | * @deprecated: drop in v5.0, use {@link WA_PAYMENT_METHODS}
12 | */
13 | export const PAYMENT_METHODS = WA_PAYMENT_METHODS;
14 |
--------------------------------------------------------------------------------
/libs/payment-request/src/tokens/payment-request-support.ts:
--------------------------------------------------------------------------------
1 | import {inject, InjectionToken} from '@angular/core';
2 | import {WA_WINDOW} from '@ng-web-apis/common';
3 |
4 | declare global {
5 | interface Window {
6 | PaymentRequest: PaymentRequest;
7 | }
8 | }
9 |
10 | export const WA_PAYMENT_REQUEST_SUPPORT = new InjectionToken(
11 | '[WA_PAYMENT_REQUEST_SUPPORT]',
12 | {
13 | factory: () => !!inject(WA_WINDOW).PaymentRequest,
14 | },
15 | );
16 |
17 | /**
18 | * @deprecated: drop in v5.0, use {@link WA_PAYMENT_REQUEST_SUPPORT}
19 | */
20 | export const PAYMENT_REQUEST_SUPPORT = WA_PAYMENT_REQUEST_SUPPORT;
21 |
--------------------------------------------------------------------------------
/libs/payment-request/src/utils/is-error.ts:
--------------------------------------------------------------------------------
1 | export function isError(item: unknown): item is DOMException | Error {
2 | return item instanceof Error || item instanceof DOMException;
3 | }
4 |
--------------------------------------------------------------------------------
/libs/payment-request/test.ts:
--------------------------------------------------------------------------------
1 | import 'zone.js';
2 | import 'zone.js/testing';
3 |
4 | import {getTestBed} from '@angular/core/testing';
5 | import {
6 | BrowserDynamicTestingModule,
7 | platformBrowserDynamicTesting,
8 | } from '@angular/platform-browser-dynamic/testing';
9 |
10 | getTestBed().initTestEnvironment(
11 | BrowserDynamicTestingModule,
12 | platformBrowserDynamicTesting(),
13 | );
14 |
--------------------------------------------------------------------------------
/libs/payment-request/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.spec.json",
3 | "include": ["**/*.spec.ts", "./test.ts", "**/*.d.ts"],
4 | "files": ["./test.ts"]
5 | }
6 |
--------------------------------------------------------------------------------
/libs/permissions/ng-package.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
3 | "assets": [
4 | "logo.svg",
5 | "README.md"
6 | ],
7 | "dest": "../../dist/permissions",
8 | "lib": {
9 | "entryFile": "src/index.ts"
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/libs/permissions/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from './services/permissions.service';
2 | export * from './tokens/permissions';
3 | export * from './tokens/permissions-support';
4 | export * from './utils/permissions-predicates';
5 |
--------------------------------------------------------------------------------
/libs/permissions/src/mocks/fake-permissions.ts:
--------------------------------------------------------------------------------
1 | export class FakePermissions implements Permissions {
2 | // eslint-disable-next-line @typescript-eslint/require-await
3 | public async query(_permissionDesc: PermissionDescriptor): Promise {
4 | throw new Error('Method not implemented.');
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/libs/permissions/src/tokens/permissions-support.ts:
--------------------------------------------------------------------------------
1 | import {inject, InjectionToken} from '@angular/core';
2 |
3 | import {PERMISSIONS} from './permissions';
4 |
5 | export const WA_PERMISSIONS_SUPPORT = new InjectionToken(
6 | '[WA_PERMISSIONS_SUPPORT]',
7 | {
8 | factory: () => !!inject(PERMISSIONS),
9 | },
10 | );
11 |
12 | /**
13 | * @deprecated: drop in v5.0, use {@link WA_PERMISSIONS_SUPPORT}
14 | */
15 | export const PERMISSIONS_SUPPORT = WA_PERMISSIONS_SUPPORT;
16 |
--------------------------------------------------------------------------------
/libs/permissions/src/tokens/permissions.ts:
--------------------------------------------------------------------------------
1 | import {inject, InjectionToken} from '@angular/core';
2 | import {WA_NAVIGATOR} from '@ng-web-apis/common';
3 |
4 | export const WA_PERMISSIONS = new InjectionToken('[WA_PERMISSIONS]', {
5 | factory: () => inject(WA_NAVIGATOR).permissions,
6 | });
7 |
8 | /**
9 | * @deprecated: drop in v5.0, use {@link WA_PERMISSIONS}
10 | */
11 | export const PERMISSIONS = WA_PERMISSIONS;
12 |
--------------------------------------------------------------------------------
/libs/permissions/src/utils/permissions-predicates.ts:
--------------------------------------------------------------------------------
1 | export function isGranted(
2 | state: NotificationPermission | PermissionState,
3 | ): state is 'granted' {
4 | return state === 'granted';
5 | }
6 |
7 | export function isDenied(
8 | state: NotificationPermission | PermissionState,
9 | ): state is 'denied' {
10 | return state === 'denied';
11 | }
12 |
13 | export function isPrompt(s: NotificationPermission): s is 'default';
14 | export function isPrompt(s: PermissionState): s is 'prompt';
15 | export function isPrompt(
16 | state: NotificationPermission | PermissionState,
17 | ): state is 'default' | 'prompt' {
18 | return state === 'prompt' || state === 'default';
19 | }
20 |
--------------------------------------------------------------------------------
/libs/permissions/test.ts:
--------------------------------------------------------------------------------
1 | import 'zone.js';
2 | import 'zone.js/testing';
3 |
4 | import {getTestBed} from '@angular/core/testing';
5 | import {
6 | BrowserDynamicTestingModule,
7 | platformBrowserDynamicTesting,
8 | } from '@angular/platform-browser-dynamic/testing';
9 |
10 | getTestBed().initTestEnvironment(
11 | BrowserDynamicTestingModule,
12 | platformBrowserDynamicTesting(),
13 | );
14 |
--------------------------------------------------------------------------------
/libs/permissions/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.spec.json",
3 | "include": ["**/*.spec.ts", "./test.ts", "**/*.d.ts"],
4 | "files": ["./test.ts"]
5 | }
6 |
--------------------------------------------------------------------------------
/libs/platform/README.md:
--------------------------------------------------------------------------------
1 | #  Platform
2 |
3 | ## Install
4 |
5 | ```bash
6 | npm i @ng-web-apis/platform
7 | ```
8 |
9 | ## Tokens
10 |
11 | - `WA_IS_MOBILE`
12 | - `WA_IS_IOS`
13 | - `WA_IS_ANDROID`
14 | - `WA_IS_WEBKIT`
15 |
--------------------------------------------------------------------------------
/libs/platform/ng-package.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
3 | "assets": [
4 | "logo.svg",
5 | "README.md"
6 | ],
7 | "dest": "../../dist/platform",
8 | "lib": {
9 | "entryFile": "src/index.ts"
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/libs/platform/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@ng-web-apis/platform",
3 | "version": "4.12.0",
4 | "description": "A basic library for web apis",
5 | "keywords": [
6 | "angular",
7 | "ng",
8 | "web",
9 | "platform"
10 | ],
11 | "homepage": "https://github.com/taiga-family/ng-web-apis/blob/main/libs/platform/README.md",
12 | "bugs": "https://github.com/taiga-family/ng-web-apis/issues",
13 | "repository": "https://github.com/taiga-family/ng-web-apis",
14 | "license": "Apache-2.0",
15 | "publishConfig": {
16 | "access": "public"
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/libs/platform/src/constants.ts:
--------------------------------------------------------------------------------
1 | export const WA_SAFARI_REG_EXP = /^((?!chrome|android).)*safari/i;
2 | export const WA_IOS_REG_EXP = /ipad|iphone|ipod/i;
3 |
--------------------------------------------------------------------------------
/libs/platform/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from './constants';
2 | export * from './is-android';
3 | export * from './is-apple';
4 | export * from './is-edge';
5 | export * from './is-firefox';
6 | export * from './is-ios';
7 | export * from './is-mobile';
8 | export * from './is-safari';
9 | export * from './is-touch';
10 | export * from './is-webkit';
11 |
--------------------------------------------------------------------------------
/libs/platform/src/is-android.ts:
--------------------------------------------------------------------------------
1 | import {inject, InjectionToken} from '@angular/core';
2 |
3 | import {WA_IS_IOS} from './is-ios';
4 | import {WA_IS_MOBILE} from './is-mobile';
5 |
6 | export const WA_IS_ANDROID = new InjectionToken('', {
7 | factory: () => inject(WA_IS_MOBILE) && !inject(WA_IS_IOS),
8 | });
9 |
--------------------------------------------------------------------------------
/libs/platform/src/is-apple.ts:
--------------------------------------------------------------------------------
1 | import {WA_SAFARI_REG_EXP} from './constants';
2 | import {isIos} from './is-ios';
3 |
4 | export function isApple(navigator: Navigator): boolean {
5 | return isIos(navigator) || WA_SAFARI_REG_EXP.test(navigator.userAgent);
6 | }
7 |
--------------------------------------------------------------------------------
/libs/platform/src/is-edge.ts:
--------------------------------------------------------------------------------
1 | export function isEdge(userAgent: string): boolean {
2 | return userAgent.toLowerCase().includes('edge');
3 | }
4 |
--------------------------------------------------------------------------------
/libs/platform/src/is-firefox.ts:
--------------------------------------------------------------------------------
1 | export function isFirefox(userAgent: string): boolean {
2 | return userAgent.toLowerCase().includes('firefox');
3 | }
4 |
--------------------------------------------------------------------------------
/libs/platform/src/is-ios.ts:
--------------------------------------------------------------------------------
1 | import {inject, InjectionToken} from '@angular/core';
2 | import {WA_NAVIGATOR} from '@ng-web-apis/common';
3 |
4 | import {WA_IOS_REG_EXP as ios, WA_SAFARI_REG_EXP as safari} from './constants';
5 |
6 | export function isIos({userAgent, maxTouchPoints}: Navigator): boolean {
7 | return ios.test(userAgent) || (safari.test(userAgent) && maxTouchPoints > 1);
8 | }
9 |
10 | export const WA_IS_IOS = new InjectionToken('', {
11 | factory: () => isIos(inject(WA_NAVIGATOR)),
12 | });
13 |
--------------------------------------------------------------------------------
/libs/platform/src/is-safari.ts:
--------------------------------------------------------------------------------
1 | // TODO: Drop change to Document in v5
2 | export function isSafari({ownerDocument: doc}: Element): boolean {
3 | const win = doc?.defaultView as unknown as Window & {safari?: any};
4 |
5 | const isMacOsSafari =
6 | win.safari !== undefined &&
7 | win.safari?.pushNotification?.toString() === '[object SafariRemoteNotification]';
8 |
9 | const isIosSafari =
10 | !!win.navigator?.vendor?.includes('Apple') &&
11 | !win.navigator?.userAgent?.includes('CriOS') &&
12 | !win.navigator?.userAgent?.includes('FxiOS');
13 |
14 | return isMacOsSafari || isIosSafari;
15 | }
16 |
--------------------------------------------------------------------------------
/libs/platform/src/is-touch.ts:
--------------------------------------------------------------------------------
1 | import type {Signal} from '@angular/core';
2 | import {inject, InjectionToken} from '@angular/core';
3 | import {toSignal} from '@angular/core/rxjs-interop';
4 | import {WA_WINDOW} from '@ng-web-apis/common';
5 | import {fromEvent, map} from 'rxjs';
6 |
7 | export const WA_IS_TOUCH = new InjectionToken>('', {
8 | factory: () => {
9 | const media = inject(WA_WINDOW).matchMedia('(pointer: coarse)');
10 |
11 | return toSignal(fromEvent(media, 'change').pipe(map(() => media.matches)), {
12 | initialValue: media.matches,
13 | });
14 | },
15 | });
16 |
--------------------------------------------------------------------------------
/libs/platform/src/is-webkit.ts:
--------------------------------------------------------------------------------
1 | import {inject, InjectionToken} from '@angular/core';
2 | import {WA_WINDOW} from '@ng-web-apis/common';
3 |
4 | export const WA_IS_WEBKIT = new InjectionToken('', {
5 | factory: () => !!inject(WA_WINDOW)?.webkitConvertPointFromNodeToPage,
6 | });
7 |
--------------------------------------------------------------------------------
/libs/platform/test.ts:
--------------------------------------------------------------------------------
1 | import 'zone.js';
2 | import 'zone.js/testing';
3 |
4 | import {getTestBed} from '@angular/core/testing';
5 | import {
6 | BrowserDynamicTestingModule,
7 | platformBrowserDynamicTesting,
8 | } from '@angular/platform-browser-dynamic/testing';
9 |
10 | getTestBed().initTestEnvironment(
11 | BrowserDynamicTestingModule,
12 | platformBrowserDynamicTesting(),
13 | );
14 |
--------------------------------------------------------------------------------
/libs/platform/tsconfig.lib.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.build.json",
3 | "files": ["src/index.ts"],
4 | "include": ["src/*"]
5 | }
6 |
--------------------------------------------------------------------------------
/libs/platform/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.spec.json",
3 | "include": ["**/*.spec.ts", "./test.ts", "**/*.d.ts"],
4 | "files": ["./test.ts"]
5 | }
6 |
--------------------------------------------------------------------------------
/libs/resize-observer/ng-package.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
3 | "assets": [
4 | "logo.svg",
5 | "README.md"
6 | ],
7 | "dest": "../../dist/resize-observer",
8 | "lib": {
9 | "entryFile": "src/index.ts"
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/libs/resize-observer/src/classes/safe-observer.ts:
--------------------------------------------------------------------------------
1 | export const SafeObserver =
2 | typeof ResizeObserver !== 'undefined'
3 | ? ResizeObserver
4 | : class implements ResizeObserver {
5 | public observe(): void {}
6 | public unobserve(): void {}
7 | public disconnect(): void {}
8 | };
9 |
--------------------------------------------------------------------------------
/libs/resize-observer/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from './directives/resize-observer.directive';
2 | export * from './services/resize-observer.service';
3 | export * from './tokens/resize-option-box';
4 | export * from './tokens/support';
5 |
--------------------------------------------------------------------------------
/libs/resize-observer/src/services/resize-observer.service.ts:
--------------------------------------------------------------------------------
1 | import {ElementRef, inject, Injectable} from '@angular/core';
2 | import {Observable} from 'rxjs';
3 |
4 | import {SafeObserver} from '../classes/safe-observer';
5 | import {RESIZE_OPTION_BOX} from '../tokens/resize-option-box';
6 |
7 | @Injectable()
8 | export class ResizeObserverService extends Observable {
9 | constructor() {
10 | const nativeElement: HTMLElement = inject(ElementRef).nativeElement;
11 | const box = inject(RESIZE_OPTION_BOX);
12 |
13 | super((subscriber) => {
14 | const observer = new SafeObserver((entries) => subscriber.next(entries));
15 |
16 | observer.observe(nativeElement, {box});
17 |
18 | return () => {
19 | observer.disconnect();
20 | };
21 | });
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/libs/resize-observer/src/tokens/resize-option-box.ts:
--------------------------------------------------------------------------------
1 | import {InjectionToken} from '@angular/core';
2 |
3 | export const WA_RESIZE_OPTION_BOX_DEFAULT = 'content-box';
4 |
5 | /**
6 | * @deprecated: drop in v5.0, use {@link WA_RESIZE_OPTION_BOX_DEFAULT}
7 | */
8 | export const RESIZE_OPTION_BOX_DEFAULT = WA_RESIZE_OPTION_BOX_DEFAULT;
9 |
10 | export const WA_RESIZE_OPTION_BOX = new InjectionToken(
11 | '[WA_RESIZE_OPTION_BOX]',
12 | {
13 | providedIn: 'root',
14 | factory: () => RESIZE_OPTION_BOX_DEFAULT,
15 | },
16 | );
17 |
18 | /**
19 | * @deprecated: drop in v5.0, use {@link WA_RESIZE_OPTION_BOX}
20 | */
21 | export const RESIZE_OPTION_BOX = WA_RESIZE_OPTION_BOX;
22 |
--------------------------------------------------------------------------------
/libs/resize-observer/src/tokens/support.ts:
--------------------------------------------------------------------------------
1 | import {inject, InjectionToken} from '@angular/core';
2 | import {WA_WINDOW} from '@ng-web-apis/common';
3 |
4 | export const WA_RESIZE_OBSERVER_SUPPORT = new InjectionToken(
5 | '[WA_RESIZE_OBSERVER_SUPPORT]',
6 | {
7 | providedIn: 'root',
8 | factory: () => !!(inject(WA_WINDOW) as any).ResizeObserver,
9 | },
10 | );
11 |
12 | /**
13 | * @deprecated: drop in v5.0, use {@link WA_RESIZE_OBSERVER_SUPPORT}
14 | */
15 | export const RESIZE_OBSERVER_SUPPORT = WA_RESIZE_OBSERVER_SUPPORT;
16 |
--------------------------------------------------------------------------------
/libs/resize-observer/test.ts:
--------------------------------------------------------------------------------
1 | import 'zone.js';
2 | import 'zone.js/testing';
3 |
4 | import {getTestBed} from '@angular/core/testing';
5 | import {
6 | BrowserDynamicTestingModule,
7 | platformBrowserDynamicTesting,
8 | } from '@angular/platform-browser-dynamic/testing';
9 |
10 | getTestBed().initTestEnvironment(
11 | BrowserDynamicTestingModule,
12 | platformBrowserDynamicTesting(),
13 | );
14 |
--------------------------------------------------------------------------------
/libs/resize-observer/tests/support.spec.ts:
--------------------------------------------------------------------------------
1 | import {TestBed} from '@angular/core/testing';
2 | import {RESIZE_OBSERVER_SUPPORT} from '@ng-web-apis/resize-observer';
3 |
4 | window.onbeforeunload = jasmine.createSpy();
5 |
6 | describe('RESIZE_OBSERVER_SUPPORT', () => {
7 | it('true in modern browsers', () => {
8 | TestBed.configureTestingModule({});
9 |
10 | expect(TestBed.inject(RESIZE_OBSERVER_SUPPORT)).toBe(true);
11 | });
12 | });
13 |
--------------------------------------------------------------------------------
/libs/resize-observer/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.spec.json",
3 | "include": ["**/*.spec.ts", "./test.ts", "**/*.d.ts"],
4 | "files": ["./test.ts"]
5 | }
6 |
--------------------------------------------------------------------------------
/libs/screen-orientation/README.md:
--------------------------------------------------------------------------------
1 | #  Screen Orientation API for Angular
2 |
3 | ## Install
4 |
5 | ```bash
6 | npm i @ng-web-apis/screen-orientation
7 | ```
8 |
9 | ## Examples
10 |
11 | ```ts
12 | import {ScreenOrientationService} from '@ng-web-apis/screen-orientation';
13 |
14 | // ...
15 | export class Example {
16 | constructor(readonly orientation$: ScreenOrientationService) {}
17 | }
18 | ```
19 |
20 | ```html
21 | {{ orientation$ | async }}
22 | ```
23 |
--------------------------------------------------------------------------------
/libs/screen-orientation/ng-package.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
3 | "assets": [
4 | "logo.svg",
5 | "README.md"
6 | ],
7 | "dest": "../../dist/screen-orientation",
8 | "lib": {
9 | "entryFile": "src/index.ts"
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/libs/screen-orientation/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@ng-web-apis/screen-orientation",
3 | "version": "4.12.0",
4 | "description": "A library for declarative use of screen orientation with Angular",
5 | "keywords": [
6 | "angular",
7 | "ng",
8 | "screen",
9 | "orientation"
10 | ],
11 | "homepage": "https://github.com/taiga-family/ng-web-apis/blob/main/libs/screen-orientation/README.md",
12 | "bugs": "https://github.com/taiga-family/ng-web-apis/issues",
13 | "repository": "https://github.com/taiga-family/ng-web-apis",
14 | "license": "Apache-2.0",
15 | "author": {
16 | "name": "Maksim Ivanov",
17 | "email": "splincodewd@yandex.ru"
18 | },
19 | "peerDependencies": {
20 | "@angular/core": ">=16.0.0",
21 | "@ng-web-apis/common": ">=4.12.0",
22 | "rxjs": ">=7.0.0"
23 | },
24 | "publishConfig": {
25 | "access": "public"
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/libs/screen-orientation/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from './screen.service';
2 | export * from './viewport.service';
3 |
--------------------------------------------------------------------------------
/libs/screen-orientation/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.spec.json",
3 | "include": ["**/*.spec.ts", "./test.ts", "**/*.d.ts"]
4 | }
5 |
--------------------------------------------------------------------------------
/libs/speech/ng-package.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
3 | "assets": [
4 | "logo.svg",
5 | "README.md"
6 | ],
7 | "dest": "../../dist/speech",
8 | "lib": {
9 | "entryFile": "src/index.ts"
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/libs/speech/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from './interfaces/speech-synthesis-utterance-options';
2 | export * from './modules/speech-synthesis/text-to-speech.directive';
3 | export * from './modules/speech-synthesis/utterance.pipe';
4 | export * from './operators/confidence-above';
5 | export * from './operators/continuous';
6 | export * from './operators/final';
7 | export * from './operators/first-alternative';
8 | export * from './operators/skip-until-said';
9 | export * from './operators/take-until-said';
10 | export * from './services/speech-recognition.service';
11 | export * from './tokens/speech-recognition-max-alternatives';
12 | export * from './tokens/speech-recognition-support';
13 | export * from './tokens/speech-synthesis-support';
14 | export * from './tokens/speech-synthesis-voices';
15 | export * from './utils/is-said';
16 |
--------------------------------------------------------------------------------
/libs/speech/src/interfaces/speech-synthesis-utterance-options.ts:
--------------------------------------------------------------------------------
1 | export interface SpeechSynthesisUtteranceOptions {
2 | readonly lang?: string;
3 | readonly pitch?: number;
4 | readonly rate?: number;
5 | readonly voice?: SpeechSynthesisVoice | null;
6 | readonly volume?: number;
7 | }
8 |
--------------------------------------------------------------------------------
/libs/speech/src/operators/confidence-above.ts:
--------------------------------------------------------------------------------
1 | import type {MonoTypeOperatorFunction} from 'rxjs';
2 | import {filter} from 'rxjs';
3 |
4 | export function confidenceAbove(
5 | threshold: number,
6 | ): MonoTypeOperatorFunction {
7 | return filter(({confidence}) => confidence > threshold);
8 | }
9 |
--------------------------------------------------------------------------------
/libs/speech/src/operators/continuous.ts:
--------------------------------------------------------------------------------
1 | import type {MonoTypeOperatorFunction} from 'rxjs';
2 | import {scan} from 'rxjs';
3 |
4 | export function continuous(): MonoTypeOperatorFunction {
5 | return scan(
6 | (result: SpeechRecognitionResult[], current) => [
7 | ...result.filter(({isFinal}) => isFinal),
8 | ...current,
9 | ],
10 | [],
11 | );
12 | }
13 |
--------------------------------------------------------------------------------
/libs/speech/src/operators/final.ts:
--------------------------------------------------------------------------------
1 | import type {MonoTypeOperatorFunction} from 'rxjs';
2 | import {map} from 'rxjs';
3 |
4 | export function final(): MonoTypeOperatorFunction {
5 | return map((results) => results.filter(({isFinal}) => isFinal));
6 | }
7 |
--------------------------------------------------------------------------------
/libs/speech/src/operators/first-alternative.ts:
--------------------------------------------------------------------------------
1 | import type {OperatorFunction} from 'rxjs';
2 | import {map} from 'rxjs';
3 |
4 | export function firstAlternative(): OperatorFunction<
5 | SpeechRecognitionResult[],
6 | SpeechRecognitionAlternative
7 | > {
8 | // eslint-disable-next-line @typescript-eslint/no-non-null-asserted-optional-chain
9 | return map((result) => result[0]?.[0]!);
10 | }
11 |
--------------------------------------------------------------------------------
/libs/speech/src/operators/skip-until-said.ts:
--------------------------------------------------------------------------------
1 | import type {MonoTypeOperatorFunction} from 'rxjs';
2 | import {map, pipe, skipWhile} from 'rxjs';
3 |
4 | import {isSaid} from '../utils/is-said';
5 |
6 | export function skipUntilSaid(
7 | text: string,
8 | ): MonoTypeOperatorFunction {
9 | const predicate = isSaid(text);
10 |
11 | return pipe(
12 | skipWhile((results) => !predicate(results)),
13 | map((value, index) => (index ? value : [])),
14 | );
15 | }
16 |
--------------------------------------------------------------------------------
/libs/speech/src/operators/take-until-said.ts:
--------------------------------------------------------------------------------
1 | import type {MonoTypeOperatorFunction} from 'rxjs';
2 | import {takeWhile} from 'rxjs';
3 |
4 | import {isSaid} from '../utils/is-said';
5 |
6 | export function takeUntilSaid(
7 | text: string,
8 | ): MonoTypeOperatorFunction {
9 | const predicate = isSaid(text);
10 |
11 | return takeWhile((results) => !predicate(results));
12 | }
13 |
--------------------------------------------------------------------------------
/libs/speech/src/tokens/speech-recognition-max-alternatives.ts:
--------------------------------------------------------------------------------
1 | import {InjectionToken} from '@angular/core';
2 |
3 | export const WA_SPEECH_RECOGNITION_MAX_ALTERNATIVES = new InjectionToken(
4 | '[WA_SPEECH_RECOGNITION_MAX_ALTERNATIVES]',
5 | {
6 | factory: () => 1,
7 | },
8 | );
9 |
10 | /**
11 | * @deprecated: drop in v5.0, use {@link WA_SPEECH_RECOGNITION_MAX_ALTERNATIVES}
12 | */
13 | export const SPEECH_RECOGNITION_MAX_ALTERNATIVES = WA_SPEECH_RECOGNITION_MAX_ALTERNATIVES;
14 |
--------------------------------------------------------------------------------
/libs/speech/src/tokens/speech-recognition-support.ts:
--------------------------------------------------------------------------------
1 | import {inject, InjectionToken} from '@angular/core';
2 | import {SPEECH_RECOGNITION} from '@ng-web-apis/common';
3 |
4 | export const WA_SPEECH_RECOGNITION_SUPPORT = new InjectionToken(
5 | '[WA_SPEECH_RECOGNITION_SUPPORT]',
6 | {
7 | factory: () => !!inject(SPEECH_RECOGNITION),
8 | },
9 | );
10 |
11 | /**
12 | * @deprecated: drop in v5.0, use {@link WA_SPEECH_RECOGNITION_SUPPORT}
13 | */
14 | export const SPEECH_RECOGNITION_SUPPORT = WA_SPEECH_RECOGNITION_SUPPORT;
15 |
--------------------------------------------------------------------------------
/libs/speech/src/tokens/speech-synthesis-support.ts:
--------------------------------------------------------------------------------
1 | import {inject, InjectionToken} from '@angular/core';
2 | import {SPEECH_SYNTHESIS} from '@ng-web-apis/common';
3 |
4 | export const WA_SPEECH_SYNTHESIS_SUPPORT = new InjectionToken(
5 | '[WA_SPEECH_SYNTHESIS_SUPPORT]',
6 | {
7 | factory: () => !!inject(SPEECH_SYNTHESIS),
8 | },
9 | );
10 |
11 | /**
12 | * @deprecated: drop in v5.0, use {@link WA_SPEECH_SYNTHESIS_SUPPORT}
13 | */
14 | export const SPEECH_SYNTHESIS_SUPPORT = WA_SPEECH_SYNTHESIS_SUPPORT;
15 |
--------------------------------------------------------------------------------
/libs/speech/src/tokens/speech-synthesis-voices.ts:
--------------------------------------------------------------------------------
1 | import {inject, InjectionToken} from '@angular/core';
2 | import {SPEECH_SYNTHESIS} from '@ng-web-apis/common';
3 | import type {Observable} from 'rxjs';
4 | import {fromEvent, map, startWith} from 'rxjs';
5 |
6 | export const WA_SPEECH_SYNTHESIS_VOICES = new InjectionToken<
7 | Observable
8 | >('[WA_SPEECH_SYNTHESIS_VOICES]', {
9 | factory: () => {
10 | const speechSynthesisRef = inject(SPEECH_SYNTHESIS);
11 |
12 | return fromEvent(speechSynthesisRef, 'voiceschanged').pipe(
13 | startWith(null),
14 | map(() => speechSynthesisRef.getVoices()),
15 | );
16 | },
17 | });
18 |
19 | /**
20 | * @deprecated: drop in v5.0, use {@link WA_SPEECH_SYNTHESIS_VOICES}
21 | */
22 | export const SPEECH_SYNTHESIS_VOICES = WA_SPEECH_SYNTHESIS_VOICES;
23 |
--------------------------------------------------------------------------------
/libs/speech/src/utils/is-said.ts:
--------------------------------------------------------------------------------
1 | export function isSaid(phrase: string): (results: SpeechRecognitionResult[]) => boolean {
2 | const lowercased = phrase.toLowerCase().trim();
3 |
4 | return (results) =>
5 | !!results.find(
6 | (result) =>
7 | result.isFinal &&
8 | result[0]?.transcript.toLowerCase().trim() === lowercased,
9 | );
10 | }
11 |
--------------------------------------------------------------------------------
/libs/speech/test.ts:
--------------------------------------------------------------------------------
1 | import 'zone.js';
2 | import 'zone.js/testing';
3 |
4 | import {getTestBed} from '@angular/core/testing';
5 | import {
6 | BrowserDynamicTestingModule,
7 | platformBrowserDynamicTesting,
8 | } from '@angular/platform-browser-dynamic/testing';
9 |
10 | getTestBed().initTestEnvironment(
11 | BrowserDynamicTestingModule,
12 | platformBrowserDynamicTesting(),
13 | );
14 |
--------------------------------------------------------------------------------
/libs/speech/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.spec.json",
3 | "include": ["**/*.spec.ts", "./test.ts", "**/*.d.ts"],
4 | "files": ["./test.ts"]
5 | }
6 |
--------------------------------------------------------------------------------
/libs/storage/ng-package.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
3 | "assets": [
4 | "logo.svg",
5 | "README.md"
6 | ],
7 | "dest": "../../dist/storage",
8 | "lib": {
9 | "entryFile": "src/index.ts"
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/libs/storage/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from './services/storage.service';
2 | export * from './tokens/storage-event';
3 | export * from './utils/filter-by-key';
4 | export * from './utils/to-value';
5 |
--------------------------------------------------------------------------------
/libs/storage/src/tokens/storage-event.ts:
--------------------------------------------------------------------------------
1 | import {inject, InjectionToken} from '@angular/core';
2 | import {WA_WINDOW} from '@ng-web-apis/common';
3 | import type {Observable} from 'rxjs';
4 | import {fromEvent} from 'rxjs';
5 |
6 | export const WA_STORAGE_EVENT = new InjectionToken>(
7 | '[WA_STORAGE_EVENT]',
8 | {
9 | factory: () => fromEvent(inject(WA_WINDOW), 'storage'),
10 | },
11 | );
12 |
13 | /**
14 | * @deprecated: drop in v5.0, use {@link WA_STORAGE_EVENT}
15 | */
16 | export const STORAGE_EVENT = WA_STORAGE_EVENT;
17 |
--------------------------------------------------------------------------------
/libs/storage/src/utils/filter-by-key.ts:
--------------------------------------------------------------------------------
1 | import type {MonoTypeOperatorFunction} from 'rxjs';
2 | import {filter} from 'rxjs';
3 |
4 | export function filterByKey(target: string): MonoTypeOperatorFunction {
5 | return filter(({key}) => key === null || key === target);
6 | }
7 |
--------------------------------------------------------------------------------
/libs/storage/src/utils/to-value.ts:
--------------------------------------------------------------------------------
1 | import type {OperatorFunction} from 'rxjs';
2 | import {map} from 'rxjs';
3 |
4 | export function toValue(): OperatorFunction {
5 | return map(({newValue}) => newValue);
6 | }
7 |
--------------------------------------------------------------------------------
/libs/storage/test.ts:
--------------------------------------------------------------------------------
1 | import 'zone.js';
2 | import 'zone.js/testing';
3 |
4 | import {getTestBed} from '@angular/core/testing';
5 | import {
6 | BrowserDynamicTestingModule,
7 | platformBrowserDynamicTesting,
8 | } from '@angular/platform-browser-dynamic/testing';
9 |
10 | getTestBed().initTestEnvironment(
11 | BrowserDynamicTestingModule,
12 | platformBrowserDynamicTesting(),
13 | );
14 |
--------------------------------------------------------------------------------
/libs/storage/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.spec.json",
3 | "include": ["**/*.spec.ts", "./test.ts", "**/*.d.ts"],
4 | "files": ["./test.ts"]
5 | }
6 |
--------------------------------------------------------------------------------
/libs/universal/ng-package.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
3 | "assets": [
4 | "mocks.js",
5 | "logo.svg",
6 | "README.md"
7 | ],
8 | "dest": "../../dist/universal",
9 | "lib": {
10 | "entryFile": "src/index.ts"
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/libs/universal/src/classes/blob-mock.ts:
--------------------------------------------------------------------------------
1 | import {alwaysRejected} from '../utils/functions';
2 |
3 | export class BlobMock implements Blob {
4 | public size = 0;
5 | public type = '';
6 | public arrayBuffer = async (): Promise => alwaysRejected();
7 | public stream = (): ReadableStream => new ReadableStream();
8 | public text = async (): Promise => alwaysRejected();
9 | public slice = (): this => this;
10 | }
11 |
--------------------------------------------------------------------------------
/libs/universal/src/classes/dom-string-list-mock.ts:
--------------------------------------------------------------------------------
1 | export class DOMStringListMock extends Array implements DOMStringList {
2 | public contains(): boolean {
3 | return false;
4 | }
5 |
6 | public item(): null {
7 | return null;
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/libs/universal/src/classes/location-mock.ts:
--------------------------------------------------------------------------------
1 | import {emptyFunction} from '../utils/functions';
2 | import {DOMStringListMock} from './dom-string-list-mock';
3 |
4 | export class LocationMock implements Location {
5 | public readonly ancestorOrigins = new DOMStringListMock();
6 | public hash = '';
7 | public host = '';
8 | public hostname = '';
9 | public href = '';
10 | public readonly origin = '';
11 | public pathname = '';
12 | public port = '';
13 | public protocol = '';
14 | public search = '';
15 |
16 | public assign = emptyFunction;
17 |
18 | public reload = emptyFunction;
19 |
20 | public replace = emptyFunction;
21 | }
22 |
--------------------------------------------------------------------------------
/libs/universal/src/classes/storage-mock.ts:
--------------------------------------------------------------------------------
1 | export class StorageMock implements Storage {
2 | private readonly storage = new Map();
3 |
4 | public get length(): number {
5 | return this.storage.size;
6 | }
7 |
8 | public getItem(key: string): string | null {
9 | return this.storage.has(key) ? this.storage.get(key)! : null;
10 | }
11 |
12 | public setItem(key: string, value: string): void {
13 | this.storage.set(key, value);
14 | }
15 |
16 | public clear(): void {
17 | this.storage.clear();
18 | }
19 |
20 | public key(index: number): string | null {
21 | return index < this.storage.size
22 | ? ([...this.storage.keys()]?.[index] ?? null)
23 | : null;
24 | }
25 |
26 | public removeItem(key: string): void {
27 | this.storage.delete(key);
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/libs/universal/src/constants/universal-animation-frame.ts:
--------------------------------------------------------------------------------
1 | import type {ValueProvider} from '@angular/core';
2 | import {WA_ANIMATION_FRAME} from '@ng-web-apis/common';
3 | import {NEVER} from 'rxjs';
4 |
5 | export const UNIVERSAL_ANIMATION_FRAME: ValueProvider = {
6 | provide: WA_ANIMATION_FRAME,
7 | useValue: NEVER,
8 | };
9 |
--------------------------------------------------------------------------------
/libs/universal/src/constants/universal-caches.ts:
--------------------------------------------------------------------------------
1 | import type {ValueProvider} from '@angular/core';
2 | import {CACHES} from '@ng-web-apis/common';
3 |
4 | import {alwaysRejected} from '../utils/functions';
5 |
6 | export const CACHES_MOCK = {
7 | delete: async () => Promise.resolve(false),
8 | has: async () => Promise.resolve(false),
9 | keys: async () => Promise.resolve([]),
10 | match: alwaysRejected,
11 | open: alwaysRejected,
12 | };
13 |
14 | export const UNIVERSAL_CACHES: ValueProvider = {
15 | provide: CACHES,
16 | useValue: CACHES_MOCK,
17 | };
18 |
--------------------------------------------------------------------------------
/libs/universal/src/constants/universal-crypto.ts:
--------------------------------------------------------------------------------
1 | import type {ValueProvider} from '@angular/core';
2 | import {CRYPTO} from '@ng-web-apis/common';
3 |
4 | import {alwaysRejected, identity} from '../utils/functions';
5 |
6 | export const CRYPTO_MOCK = {
7 | subtle: new Proxy(
8 | {},
9 | {
10 | get: () => () => alwaysRejected,
11 | },
12 | ),
13 | getRandomValues: identity,
14 | };
15 |
16 | export const UNIVERSAL_CRYPTO: ValueProvider = {
17 | provide: CRYPTO,
18 | useValue: CRYPTO_MOCK,
19 | };
20 |
--------------------------------------------------------------------------------
/libs/universal/src/constants/universal-history.ts:
--------------------------------------------------------------------------------
1 | import type {ValueProvider} from '@angular/core';
2 | import {HISTORY} from '@ng-web-apis/common';
3 |
4 | import {emptyFunction} from '../utils/functions';
5 |
6 | export const HISTORY_MOCK = {
7 | length: 0,
8 | scrollRestoration: 'auto',
9 | state: {},
10 | back: emptyFunction,
11 | forward: emptyFunction,
12 | go: emptyFunction,
13 | pushState: emptyFunction,
14 | replaceState: emptyFunction,
15 | };
16 |
17 | export const UNIVERSAL_HISTORY: ValueProvider = {
18 | provide: HISTORY,
19 | useValue: HISTORY_MOCK,
20 | };
21 |
--------------------------------------------------------------------------------
/libs/universal/src/constants/universal-local-storage.ts:
--------------------------------------------------------------------------------
1 | import type {ClassProvider} from '@angular/core';
2 | import {WA_LOCAL_STORAGE} from '@ng-web-apis/common';
3 |
4 | import {StorageMock} from '../classes/storage-mock';
5 |
6 | export const UNIVERSAL_LOCAL_STORAGE: ClassProvider = {
7 | provide: WA_LOCAL_STORAGE,
8 | useClass: StorageMock,
9 | };
10 |
--------------------------------------------------------------------------------
/libs/universal/src/constants/universal-location.ts:
--------------------------------------------------------------------------------
1 | import type {FactoryProvider} from '@angular/core';
2 | import {Optional} from '@angular/core';
3 | import {WA_LOCATION} from '@ng-web-apis/common';
4 |
5 | import {LocationMock} from '../classes/location-mock';
6 | import {SSR_LOCATION} from '../tokens/ssr-location';
7 |
8 | export const UNIVERSAL_LOCATION: FactoryProvider = {
9 | provide: WA_LOCATION,
10 | deps: [[new Optional(), SSR_LOCATION]],
11 | useFactory: (location: Location | null) => location || new LocationMock(),
12 | };
13 |
--------------------------------------------------------------------------------
/libs/universal/src/constants/universal-media-devices.ts:
--------------------------------------------------------------------------------
1 | import type {ValueProvider} from '@angular/core';
2 | import {MEDIA_DEVICES} from '@ng-web-apis/common';
3 |
4 | import {NAVIGATOR_MOCK} from './universal-navigator';
5 |
6 | export const UNIVERSAL_MEDIA_DEVICES: ValueProvider = {
7 | provide: MEDIA_DEVICES,
8 | useValue: NAVIGATOR_MOCK.mediaDevices,
9 | };
10 |
--------------------------------------------------------------------------------
/libs/universal/src/constants/universal-performance.ts:
--------------------------------------------------------------------------------
1 | import type {FactoryProvider} from '@angular/core';
2 | import {WA_PERFORMANCE} from '@ng-web-apis/common';
3 |
4 | export function performanceFactory(): Performance {
5 | return (
6 | safeRequire<{performance: Performance}>('node:perf_hooks')?.performance ||
7 | safeRequire<{performance: Performance}>('perf_hooks')?.performance ||
8 | globalThis.performance
9 | );
10 | }
11 |
12 | export const UNIVERSAL_PERFORMANCE: FactoryProvider = {
13 | provide: WA_PERFORMANCE,
14 | deps: [],
15 | useFactory: performanceFactory,
16 | };
17 |
18 | function safeRequire(modulePath: string): T | undefined {
19 | try {
20 | return require(modulePath);
21 | } catch {
22 | return undefined;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/libs/universal/src/constants/universal-session-storage.ts:
--------------------------------------------------------------------------------
1 | import type {ClassProvider} from '@angular/core';
2 | import {WA_SESSION_STORAGE} from '@ng-web-apis/common';
3 |
4 | import {StorageMock} from '../classes/storage-mock';
5 |
6 | export const UNIVERSAL_SESSION_STORAGE: ClassProvider = {
7 | provide: WA_SESSION_STORAGE,
8 | useClass: StorageMock,
9 | };
10 |
--------------------------------------------------------------------------------
/libs/universal/src/constants/universal-speech-synthesis.ts:
--------------------------------------------------------------------------------
1 | import type {ValueProvider} from '@angular/core';
2 | import {SPEECH_SYNTHESIS} from '@ng-web-apis/common';
3 |
4 | import {alwaysFalse, emptyArray, emptyFunction} from '../utils/functions';
5 |
6 | export const SPEECH_SYNTHESIS_MOCK: SpeechSynthesis = {
7 | paused: false,
8 | pending: false,
9 | speaking: false,
10 | onvoiceschanged: emptyFunction,
11 | addEventListener: emptyFunction,
12 | removeEventListener: emptyFunction,
13 | dispatchEvent: alwaysFalse,
14 | cancel: emptyFunction,
15 | pause: emptyFunction,
16 | resume: emptyFunction,
17 | speak: emptyFunction,
18 | getVoices: emptyArray,
19 | };
20 |
21 | export const UNIVERSAL_SPEECH_SYNTHESIS: ValueProvider = {
22 | provide: SPEECH_SYNTHESIS,
23 | useValue: SPEECH_SYNTHESIS_MOCK,
24 | };
25 |
--------------------------------------------------------------------------------
/libs/universal/src/constants/universal-user-agent.ts:
--------------------------------------------------------------------------------
1 | import type {FactoryProvider} from '@angular/core';
2 | import {Optional} from '@angular/core';
3 | import {WA_USER_AGENT} from '@ng-web-apis/common';
4 |
5 | import {SSR_USER_AGENT} from '../tokens/ssr-user-agent';
6 |
7 | export const UNIVERSAL_USER_AGENT: FactoryProvider = {
8 | provide: WA_USER_AGENT,
9 | deps: [[new Optional(), SSR_USER_AGENT]],
10 | useFactory: (userAgent: string | null) => userAgent ?? '',
11 | };
12 |
--------------------------------------------------------------------------------
/libs/universal/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from './constants/universal-animation-frame';
2 | export * from './constants/universal-caches';
3 | export * from './constants/universal-crypto';
4 | export * from './constants/universal-history';
5 | export * from './constants/universal-local-storage';
6 | export * from './constants/universal-location';
7 | export * from './constants/universal-media-devices';
8 | export * from './constants/universal-navigator';
9 | export * from './constants/universal-performance';
10 | export * from './constants/universal-providers';
11 | export * from './constants/universal-session-storage';
12 | export * from './constants/universal-speech-synthesis';
13 | export * from './constants/universal-user-agent';
14 | export * from './constants/universal-window';
15 | export * from './tokens/ssr-location';
16 | export * from './tokens/ssr-user-agent';
17 | export * from './utils/event-target';
18 | export * from './utils/functions';
19 | export * from './utils/provide-location';
20 | export * from './utils/provide-user-agent';
21 |
--------------------------------------------------------------------------------
/libs/universal/src/tokens/ssr-location.ts:
--------------------------------------------------------------------------------
1 | import {InjectionToken} from '@angular/core';
2 |
3 | export const WA_SSR_LOCATION = new InjectionToken('[WA_SSR_LOCATION]');
4 |
5 | /**
6 | * @deprecated: drop in v5.0, use {@link WA_SSR_LOCATION}
7 | */
8 | export const SSR_LOCATION = WA_SSR_LOCATION;
9 |
--------------------------------------------------------------------------------
/libs/universal/src/tokens/ssr-user-agent.ts:
--------------------------------------------------------------------------------
1 | import {InjectionToken} from '@angular/core';
2 |
3 | export const WA_SSR_USER_AGENT = new InjectionToken('[WA_SSR_USER_AGENT]');
4 |
5 | /**
6 | * @deprecated: drop in v5.0, use {@link WA_SSR_USER_AGENT}
7 | */
8 | export const SSR_USER_AGENT = WA_SSR_USER_AGENT;
9 |
--------------------------------------------------------------------------------
/libs/universal/src/utils/event-target.ts:
--------------------------------------------------------------------------------
1 | import {alwaysFalse, emptyFunction} from './functions';
2 |
3 | export const EVENT_TARGET: EventTarget = {
4 | addEventListener: emptyFunction,
5 | dispatchEvent: alwaysFalse,
6 | removeEventListener: emptyFunction,
7 | };
8 |
--------------------------------------------------------------------------------
/libs/universal/src/utils/functions.ts:
--------------------------------------------------------------------------------
1 | export function identity(v: T): T {
2 | return v;
3 | }
4 |
5 | // eslint-disable-next-line @typescript-eslint/no-empty-function
6 | export function emptyFunction(): void {}
7 |
8 | export function emptyArray(): any[] {
9 | return [];
10 | }
11 |
12 | export function emptyObject(): object {
13 | return {};
14 | }
15 |
16 | export function alwaysFalse(): boolean {
17 | return false;
18 | }
19 |
20 | export function alwaysNull(): null {
21 | return null;
22 | }
23 |
24 | export function alwaysZero(): number {
25 | return 0;
26 | }
27 |
28 | export async function alwaysRejected(): Promise {
29 | return Promise.reject().catch(emptyFunction);
30 | }
31 |
--------------------------------------------------------------------------------
/libs/universal/src/utils/provide-location.ts:
--------------------------------------------------------------------------------
1 | import type {IncomingMessage} from 'node:http';
2 |
3 | import type {ValueProvider} from '@angular/core';
4 |
5 | import {DOMStringListMock} from '../classes/dom-string-list-mock';
6 | import {SSR_LOCATION} from '../tokens/ssr-location';
7 | import {emptyFunction} from './functions';
8 |
9 | export function provideLocation(req: IncomingMessage): ValueProvider {
10 | const protocol = 'encrypted' in req.socket ? 'https' : 'http';
11 | const url: any = new URL(`${protocol}://${req.headers.host}${req.url}`);
12 |
13 | url.assign = emptyFunction;
14 | url.reload = emptyFunction;
15 | url.replace = emptyFunction;
16 | url.ancestorOrigins = new DOMStringListMock();
17 |
18 | return {
19 | provide: SSR_LOCATION,
20 | useValue: url,
21 | };
22 | }
23 |
--------------------------------------------------------------------------------
/libs/universal/src/utils/provide-user-agent.ts:
--------------------------------------------------------------------------------
1 | import type {IncomingHttpHeaders} from 'node:http';
2 |
3 | import type {ValueProvider} from '@angular/core';
4 |
5 | import {SSR_USER_AGENT} from '../tokens/ssr-user-agent';
6 |
7 | export function provideUserAgent(req: {headers: IncomingHttpHeaders}): ValueProvider {
8 | return {
9 | provide: SSR_USER_AGENT,
10 | useValue: req.headers['user-agent'],
11 | };
12 | }
13 |
--------------------------------------------------------------------------------
/libs/universal/test.ts:
--------------------------------------------------------------------------------
1 | import 'zone.js';
2 | import 'zone.js/testing';
3 |
4 | import {getTestBed} from '@angular/core/testing';
5 | import {
6 | BrowserDynamicTestingModule,
7 | platformBrowserDynamicTesting,
8 | } from '@angular/platform-browser-dynamic/testing';
9 |
10 | getTestBed().initTestEnvironment(
11 | BrowserDynamicTestingModule,
12 | platformBrowserDynamicTesting(),
13 | );
14 |
--------------------------------------------------------------------------------
/libs/universal/tests/provide-user-agent.spec.ts:
--------------------------------------------------------------------------------
1 | import {TestBed} from '@angular/core/testing';
2 | import {provideUserAgent, SSR_USER_AGENT} from '@ng-web-apis/universal';
3 |
4 | window.onbeforeunload = jasmine.createSpy();
5 |
6 | describe('provideUserAgent', () => {
7 | const req = {
8 | headers: {
9 | 'user-agent': 'Chrome',
10 | },
11 | };
12 |
13 | it('parses request', () => {
14 | TestBed.configureTestingModule({
15 | providers: [provideUserAgent(req)],
16 | });
17 |
18 | expect(String(TestBed.inject(SSR_USER_AGENT))).toBe('Chrome');
19 | });
20 | });
21 |
--------------------------------------------------------------------------------
/libs/universal/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.spec.json",
3 | "include": ["**/*.spec.ts", "./test.ts", "**/*.d.ts"],
4 | "files": ["./test.ts"]
5 | }
6 |
--------------------------------------------------------------------------------
/libs/view-transition/ng-package.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
3 | "assets": [
4 | "logo.svg",
5 | "README.md"
6 | ],
7 | "dest": "../../dist/view-transition",
8 | "lib": {
9 | "entryFile": "src/index.ts"
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/libs/view-transition/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from './services/view-transition.service';
2 |
--------------------------------------------------------------------------------
/libs/view-transition/test.ts:
--------------------------------------------------------------------------------
1 | import 'zone.js';
2 | import 'zone.js/testing';
3 |
4 | import {getTestBed} from '@angular/core/testing';
5 | import {
6 | BrowserDynamicTestingModule,
7 | platformBrowserDynamicTesting,
8 | } from '@angular/platform-browser-dynamic/testing';
9 |
10 | getTestBed().initTestEnvironment(
11 | BrowserDynamicTestingModule,
12 | platformBrowserDynamicTesting(),
13 | );
14 |
--------------------------------------------------------------------------------
/libs/workers/ng-package.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
3 | "assets": [
4 | "logo.svg",
5 | "README.md"
6 | ],
7 | "dest": "../../dist/workers",
8 | "lib": {
9 | "entryFile": "src/index.ts"
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/libs/workers/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from './worker/classes/web-worker';
2 | export * from './worker/operators/to-data';
3 | export * from './worker/pipes/worker.pipe';
4 | export * from './worker/types/typed-message-event';
5 | export * from './worker/types/worker-function';
6 |
--------------------------------------------------------------------------------
/libs/workers/src/worker/consts/worker-fn-template.ts:
--------------------------------------------------------------------------------
1 | // throw an error using the `setTimeout` function
2 | // because web worker doesn't emit ErrorEvent from promises
3 | export const WORKER_BLANK_FN = `
4 | function(fn){
5 | function isFunction(type){
6 | return type === 'function';
7 | }
8 |
9 | self.addEventListener('message', function(e) {
10 | var result = fn.call(null, e.data);
11 | if (result && [typeof result.then, typeof result.catch].every(isFunction)){
12 | result
13 | .then(postMessage)
14 | .catch(function(error) {
15 | setTimeout(function(){throw error}, 0)
16 | })
17 | } else {
18 | postMessage(result);
19 | }
20 | })
21 | }
22 | `;
23 |
--------------------------------------------------------------------------------
/libs/workers/src/worker/operators/to-data.ts:
--------------------------------------------------------------------------------
1 | import type {OperatorFunction} from 'rxjs';
2 | import {map} from 'rxjs';
3 |
4 | import type {TypedMessageEvent} from '../types/typed-message-event';
5 |
6 | export function toData(): OperatorFunction, T> {
7 | return map, T>(({data}) => data);
8 | }
9 |
--------------------------------------------------------------------------------
/libs/workers/src/worker/types/typed-message-event.ts:
--------------------------------------------------------------------------------
1 | export interface TypedMessageEvent extends MessageEvent {
2 | data: T;
3 | }
4 |
--------------------------------------------------------------------------------
/libs/workers/src/worker/types/worker-function.ts:
--------------------------------------------------------------------------------
1 | export type WorkerFunction = (data: T) => Promise | R;
2 |
--------------------------------------------------------------------------------
/libs/workers/test.ts:
--------------------------------------------------------------------------------
1 | import 'zone.js';
2 | import 'zone.js/testing';
3 |
4 | import {getTestBed} from '@angular/core/testing';
5 | import {
6 | BrowserDynamicTestingModule,
7 | platformBrowserDynamicTesting,
8 | } from '@angular/platform-browser-dynamic/testing';
9 |
10 | getTestBed().initTestEnvironment(
11 | BrowserDynamicTestingModule,
12 | platformBrowserDynamicTesting(),
13 | );
14 |
--------------------------------------------------------------------------------
/libs/workers/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.spec.json",
3 | "include": ["**/*.spec.ts", "./test.ts", "**/*.d.ts"],
4 | "files": ["./test.ts"]
5 | }
6 |
--------------------------------------------------------------------------------
/nx.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "./node_modules/nx/schemas/nx-schema.json",
3 | "workspaceLayout": {
4 | "libsDir": "libs",
5 | "appsDir": "apps"
6 | },
7 | "targetDefaults": {
8 | "build": {
9 | "inputs": ["production", "^production"],
10 | "cache": true
11 | },
12 | "test": {
13 | "cache": true
14 | }
15 | },
16 | "namedInputs": {
17 | "default": ["{projectRoot}/**/*", "sharedGlobals"],
18 | "sharedGlobals": [
19 | "{workspaceRoot}/package-lock.json",
20 | "{workspaceRoot}/nx.json",
21 | "{workspaceRoot}/tsconfig.*.json",
22 | "{workspaceRoot}/tsconfig.json",
23 | "{workspaceRoot}/*.yml",
24 | "{workspaceRoot}/*.md"
25 | ],
26 | "production": ["default"]
27 | },
28 | "defaultBase": "origin/main",
29 | "useLegacyCache": true
30 | }
31 |
--------------------------------------------------------------------------------
/tsconfig.eslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "rootDir": ".",
5 | "baseUrl": ".",
6 | "strict": true,
7 | "incremental": true
8 | },
9 | "include": ["**/*.ts", "**/*.js"],
10 | "exclude": ["**/node_modules"]
11 | }
12 |
--------------------------------------------------------------------------------
/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "exclude": [],
4 | "compilerOptions": {
5 | "types": ["jasmine", "node", "webmidi", "dom-view-transitions", "dom-speech-recognition"]
6 | }
7 | }
8 |
--------------------------------------------------------------------------------