├── .cspell.json ├── .editorconfig ├── .github ├── CODEOWNERS ├── FUNDING.yml ├── renovate.json └── workflows │ ├── assign-author.yml │ ├── auto-merge.yml │ ├── ci.yml │ ├── deploy-preview.yml │ ├── deploy.yml │ ├── ready-to-merge.yml │ └── release.yml ├── .gitignore ├── .husky ├── .gitignore ├── commit-msg └── pre-commit ├── .idea └── icon.svg ├── .release-it.js ├── .vscode └── settings.json ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── apps └── demo │ ├── generate-demo-routes-file.ts │ ├── ngsw-config.json │ ├── package.json │ ├── project.json │ ├── server.ts │ ├── src │ ├── app │ │ ├── app.component.css │ │ ├── app.component.html │ │ ├── app.component.ts │ │ ├── app.config.ts │ │ ├── app.routes.ts │ │ ├── constants │ │ │ ├── demo-path.ts │ │ │ └── index.ts │ │ └── pages │ │ │ ├── audio │ │ │ ├── audio-page.component.css │ │ │ ├── audio-page.component.html │ │ │ └── audio-page.component.ts │ │ │ ├── canvas │ │ │ ├── canvas-page.component.css │ │ │ ├── canvas-page.component.html │ │ │ └── canvas-page.component.ts │ │ │ ├── common │ │ │ ├── common-page.component.html │ │ │ └── common-page.component.ts │ │ │ ├── geolocation │ │ │ ├── geolocation-page.component.html │ │ │ ├── geolocation-page.component.less │ │ │ ├── geolocation-page.component.ts │ │ │ └── samples │ │ │ │ ├── sample-async.ts │ │ │ │ └── sample.ts │ │ │ ├── home │ │ │ ├── home-page.component.css │ │ │ ├── home-page.component.html │ │ │ └── home-page.component.ts │ │ │ ├── intersection-observer │ │ │ ├── intersection-observer-page.component.html │ │ │ ├── intersection-observer-page.component.less │ │ │ └── intersection-observer-page.component.ts │ │ │ ├── midi │ │ │ ├── adsr.pipe.ts │ │ │ ├── demo │ │ │ │ ├── demo.component.html │ │ │ │ ├── demo.component.less │ │ │ │ ├── demo.component.ts │ │ │ │ └── response.ts │ │ │ ├── midi-page.component.html │ │ │ ├── midi-page.component.less │ │ │ └── midi-page.component.ts │ │ │ ├── mutation-observer │ │ │ ├── mutation-observer-page.component.html │ │ │ ├── mutation-observer-page.component.less │ │ │ └── mutation-observer-page.component.ts │ │ │ ├── notification │ │ │ ├── examples │ │ │ │ ├── 01-getting-permission │ │ │ │ │ ├── index.html │ │ │ │ │ └── index.ts │ │ │ │ ├── 02-create-notification │ │ │ │ │ ├── index.html │ │ │ │ │ └── index.ts │ │ │ │ ├── 03-close-notification │ │ │ │ │ ├── index.html │ │ │ │ │ └── index.ts │ │ │ │ └── 04-listen-notification-events │ │ │ │ │ ├── index.html │ │ │ │ │ └── index.ts │ │ │ ├── notification-page.component.ts │ │ │ ├── notification-page.style.less │ │ │ └── notification-page.template.html │ │ │ ├── payment-request │ │ │ ├── payment-request-page.component.html │ │ │ ├── payment-request-page.component.less │ │ │ ├── payment-request-page.component.ts │ │ │ ├── samples │ │ │ │ ├── directives.sample.ts │ │ │ │ └── service.sample.ts │ │ │ └── shop │ │ │ │ ├── shop.component.html │ │ │ │ ├── shop.component.less │ │ │ │ └── shop.component.ts │ │ │ ├── permissions │ │ │ ├── permissions-page.component.css │ │ │ ├── permissions-page.component.html │ │ │ ├── permissions-page.component.ts │ │ │ └── samples │ │ │ │ └── basic.ts │ │ │ ├── platform │ │ │ ├── index.html │ │ │ └── index.ts │ │ │ ├── resize-observer │ │ │ ├── resize-observer-page.component.html │ │ │ ├── resize-observer-page.component.less │ │ │ └── resize-observer-page.component.ts │ │ │ ├── screen-orientation │ │ │ ├── samples │ │ │ │ └── sample.ts │ │ │ ├── screen-orientation-page.component.html │ │ │ ├── screen-orientation-page.component.less │ │ │ └── screen-orientation-page.component.ts │ │ │ ├── speech │ │ │ ├── speech-page.component.html │ │ │ ├── speech-page.component.less │ │ │ └── speech-page.component.ts │ │ │ ├── storage │ │ │ ├── example │ │ │ │ ├── example.component.ts │ │ │ │ └── example.template.html │ │ │ ├── storage-page.component.html │ │ │ ├── storage-page.component.less │ │ │ └── storage-page.component.ts │ │ │ ├── universal │ │ │ ├── universal-page.component.html │ │ │ └── universal-page.component.ts │ │ │ ├── view-transition │ │ │ ├── view-transition-page.component.html │ │ │ ├── view-transition-page.component.less │ │ │ └── view-transition-page.component.ts │ │ │ └── workers │ │ │ ├── clock.component.ts │ │ │ ├── workers-page.component.html │ │ │ ├── workers-page.component.less │ │ │ └── workers-page.component.ts │ ├── assets │ │ ├── .gitkeep │ │ ├── android-chrome-192x192.png │ │ ├── android-chrome-512x512.png │ │ ├── apple-touch-icon.png │ │ ├── browserconfig.xml │ │ ├── change-permissions-instructions.jpg │ │ ├── demo.mp3 │ │ ├── favicon-16x16.png │ │ ├── favicon-32x32.png │ │ ├── favicon.ico │ │ ├── images │ │ │ └── web-api.svg │ │ ├── mstile-144x144.png │ │ ├── mstile-150x150.png │ │ ├── mstile-310x150.png │ │ ├── mstile-310x310.png │ │ ├── mstile-70x70.png │ │ ├── response.m4a │ │ └── safari-pinned-tab.svg │ ├── environments │ │ ├── environment.prod.ts │ │ └── environment.ts │ ├── favicon.ico │ ├── index.html │ ├── main.server.ts │ ├── main.ts │ ├── manifest.webmanifest │ ├── polyfills.ts │ ├── styles.css │ └── typings.d.ts │ ├── tsconfig.app.json │ ├── tsconfig.server.json │ └── webpack.config.ts ├── codecov.yml ├── firebase.json ├── libs ├── audio │ ├── LICENSE │ ├── README.md │ ├── demo.mp3 │ ├── envelope.png │ ├── karma.conf.js │ ├── logo.svg │ ├── mocks.js │ ├── ng-package.json │ ├── package.json │ ├── project.json │ ├── src │ │ ├── constants │ │ │ ├── fallback.ts │ │ │ └── polling-time.ts │ │ ├── decorators │ │ │ └── audio-param.ts │ │ ├── directives │ │ │ ├── audio-context.ts │ │ │ ├── channel.ts │ │ │ ├── destination.ts │ │ │ ├── listener.ts │ │ │ ├── offline-audio-context.ts │ │ │ ├── output.ts │ │ │ └── stream-destination.ts │ │ ├── index.ts │ │ ├── module.ts │ │ ├── nodes │ │ │ ├── analyser.ts │ │ │ ├── biquad-filter.ts │ │ │ ├── channel-merger.ts │ │ │ ├── channel-splitter.ts │ │ │ ├── convolver.ts │ │ │ ├── delay.ts │ │ │ ├── dynamics-compressor.ts │ │ │ ├── gain.ts │ │ │ ├── iir-filter.ts │ │ │ ├── panner.ts │ │ │ ├── script-processor.ts │ │ │ ├── stereo-panner.ts │ │ │ ├── wave-shaper.ts │ │ │ └── worklet.ts │ │ ├── pipes │ │ │ ├── audio-param.pipe.ts │ │ │ └── periodic-wave.pipe.ts │ │ ├── polyfill.js │ │ ├── services │ │ │ └── audio-buffer.service.ts │ │ ├── sources │ │ │ ├── buffer-source.ts │ │ │ ├── constant-source.ts │ │ │ ├── media-source.ts │ │ │ ├── media-stream-source.ts │ │ │ └── oscillator.ts │ │ ├── test.js │ │ ├── tokens │ │ │ ├── audio-context.ts │ │ │ ├── audio-node.ts │ │ │ ├── audio-worklet-processors-ready.ts │ │ │ ├── audio-worklet-processors.ts │ │ │ ├── audio-worklet-support.ts │ │ │ ├── constructor-support.ts │ │ │ ├── feedback-coefficients.ts │ │ │ ├── feedforward-coefficients.ts │ │ │ ├── media-stream.ts │ │ │ └── support.ts │ │ ├── types │ │ │ ├── audio-node-with-params.ts │ │ │ ├── audio-param-automation-mode.ts │ │ │ ├── audio-param-automation.ts │ │ │ ├── audio-param-curve.ts │ │ │ ├── audio-param-decorator.ts │ │ │ └── audio-param-input.ts │ │ └── utils │ │ │ ├── connect.ts │ │ │ ├── fallback-audio-param.ts │ │ │ ├── latency-hint-factory.ts │ │ │ ├── parse.ts │ │ │ └── process-audio-param.ts │ ├── test.ts │ ├── tests │ │ ├── analyser.spec.ts │ │ ├── audio-buffer.service.spec.ts │ │ ├── audio-context.spec.ts │ │ ├── audio-param.pipe.spec.ts │ │ ├── audio-param.spec.ts │ │ ├── audio-worklet-processors-ready.spec.ts │ │ ├── biquad-filter.spec.ts │ │ ├── buffer-source.spec.ts │ │ ├── channel-merger.spec.ts │ │ ├── channel-splitter.spec.ts │ │ ├── channel.spec.ts │ │ ├── constant-source.spec.ts │ │ ├── convolver.spec.ts │ │ ├── delay.spec.ts │ │ ├── destination.spec.ts │ │ ├── dynamics-compressor.spec.ts │ │ ├── fallback-audio-param.spec.ts │ │ ├── gain.spec.ts │ │ ├── iir-filter.spec.ts │ │ ├── listener.spec.ts │ │ ├── media-source.spec.ts │ │ ├── media-stream-source.spec.ts │ │ ├── offline-audio-context.spec.ts │ │ ├── oscillator.spec.ts │ │ ├── output.spec.ts │ │ ├── panner.spec.ts │ │ ├── periodic-wave.pipe.spec.ts │ │ ├── script-processor.spec.ts │ │ ├── stereo-panner.spec.ts │ │ ├── stream-destination.spec.ts │ │ ├── wave-shaper.spec.ts │ │ └── worklet.spec.ts │ └── tsconfig.spec.json ├── canvas │ ├── README.md │ ├── karma.conf.js │ ├── logo.svg │ ├── ng-package.json │ ├── package.json │ ├── project.json │ ├── src │ │ ├── contexts │ │ │ └── canvas-2d.ts │ │ ├── index.ts │ │ ├── interfaces │ │ │ └── canvas-method.ts │ │ ├── methods │ │ │ ├── clip-path.ts │ │ │ ├── draw-image.ts │ │ │ ├── path-2d.ts │ │ │ ├── path.ts │ │ │ └── text.ts │ │ ├── module.ts │ │ ├── path │ │ │ ├── arc-to.ts │ │ │ ├── arc.ts │ │ │ ├── bezier-curve-to.ts │ │ │ ├── ellipse.ts │ │ │ ├── line-to.ts │ │ │ ├── move-to.ts │ │ │ ├── quadratic-curve-to.ts │ │ │ └── rect.ts │ │ ├── pipes │ │ │ ├── gradient.pipe.ts │ │ │ ├── path.pipe.ts │ │ │ ├── pattern.pipe.ts │ │ │ ├── rad.pipe.ts │ │ │ └── transform.pipe.ts │ │ ├── properties │ │ │ ├── clip.ts │ │ │ ├── compositing.ts │ │ │ ├── fill-stroke-styles.ts │ │ │ ├── filter.ts │ │ │ ├── image-smoothing.ts │ │ │ ├── path-drawing-styles.ts │ │ │ ├── shadow-styles.ts │ │ │ ├── text-drawing-styles.ts │ │ │ └── transform.ts │ │ ├── services │ │ │ └── draw.service.ts │ │ ├── tokens │ │ │ ├── canvas-2d-context.ts │ │ │ ├── canvas-method.ts │ │ │ └── canvas-properties.ts │ │ └── types │ │ │ └── context-processor.ts │ ├── test.ts │ ├── tests │ │ ├── canvas-2d.spec.ts │ │ ├── canvas-properties.spec.ts │ │ ├── draw.service.spec.ts │ │ ├── methods.spec.ts │ │ ├── path.spec.ts │ │ ├── pipes.spec.ts │ │ └── properties.spec.ts │ └── tsconfig.spec.json ├── common │ ├── README.md │ ├── karma.conf.js │ ├── logo.svg │ ├── ng-package.json │ ├── package.json │ ├── project.json │ ├── src │ │ ├── index.ts │ │ └── tokens │ │ │ ├── animation-frame.ts │ │ │ ├── caches.ts │ │ │ ├── crypto.ts │ │ │ ├── css.ts │ │ │ ├── history.ts │ │ │ ├── local-storage.ts │ │ │ ├── location.ts │ │ │ ├── media-devices.ts │ │ │ ├── navigator.ts │ │ │ ├── network-information.ts │ │ │ ├── page-visibility.ts │ │ │ ├── performance.ts │ │ │ ├── screen.ts │ │ │ ├── session-storage.ts │ │ │ ├── speech-recognition.ts │ │ │ ├── speech-synthesis.ts │ │ │ ├── user-agent.ts │ │ │ └── window.ts │ ├── test.ts │ ├── tests │ │ ├── animation-frame.spec.ts │ │ ├── caches.spec.ts │ │ ├── crypto.spec.ts │ │ ├── css.spec.ts │ │ ├── history.spec.ts │ │ ├── local-storage.spec.ts │ │ ├── location.spec.ts │ │ ├── media-devices.spec.ts │ │ ├── navigator.spec.ts │ │ ├── network-information.spec.ts │ │ ├── page-visibility.spec.ts │ │ ├── performance.spec.ts │ │ ├── screen.spec.ts │ │ ├── session-storage.spec.ts │ │ ├── speech-recognition.spec.ts │ │ ├── speech-synthesis.spec.ts │ │ ├── user-agent.spec.ts │ │ └── window.spec.ts │ └── tsconfig.spec.json ├── geolocation │ ├── README.md │ ├── karma.conf.js │ ├── logo.svg │ ├── ng-package.json │ ├── package.json │ ├── project.json │ ├── src │ │ ├── index.ts │ │ ├── services │ │ │ └── geolocation.service.ts │ │ └── tokens │ │ │ ├── geolocation-options.ts │ │ │ ├── geolocation-support.ts │ │ │ └── geolocation.ts │ ├── test.ts │ └── tests │ │ ├── geolocation.service.spec.ts │ │ └── geolocation.spec.ts ├── intersection-observer │ ├── README.md │ ├── karma.conf.js │ ├── logo.svg │ ├── ng-package.json │ ├── package.json │ ├── project.json │ ├── src │ │ ├── classes │ │ │ └── safe-observer.ts │ │ ├── directives │ │ │ ├── intersection-observee.directive.ts │ │ │ ├── intersection-observer.directive.ts │ │ │ └── intersection-root.directive.ts │ │ ├── index.ts │ │ ├── module.ts │ │ ├── services │ │ │ ├── intersection-observee.service.ts │ │ │ └── intersection-observer.service.ts │ │ ├── tokens │ │ │ ├── intersection-root-margin.ts │ │ │ ├── intersection-root.ts │ │ │ ├── intersection-threshold.ts │ │ │ └── support.ts │ │ └── utils │ │ │ ├── root-margin-factory.ts │ │ │ └── threshold-factory.ts │ ├── test.ts │ ├── tests │ │ ├── intersection-observee.spec.ts │ │ ├── intersection-observer.service.spec.ts │ │ └── support.spec.ts │ └── tsconfig.spec.json ├── midi │ ├── README.md │ ├── karma.conf.js │ ├── logo.svg │ ├── ng-package.json │ ├── package.json │ ├── project.json │ ├── src │ │ ├── index.ts │ │ ├── monotype-operators │ │ │ ├── aftertouch.ts │ │ │ ├── filter-by-channel.ts │ │ │ ├── filter-by-id.ts │ │ │ ├── filter-by-name.ts │ │ │ ├── main-volume.ts │ │ │ ├── modulation-wheel.ts │ │ │ ├── notes.ts │ │ │ ├── pan.ts │ │ │ ├── pitch-bend.ts │ │ │ ├── polyphonic-aftertouch.ts │ │ │ ├── program-change.ts │ │ │ └── sustain-pedal.ts │ │ ├── operators │ │ │ ├── to-data-byte.ts │ │ │ ├── to-data.ts │ │ │ ├── to-status-byte.ts │ │ │ ├── to-time-stamp.ts │ │ │ └── to-value-byte.ts │ │ ├── pipes │ │ │ └── frequency │ │ │ │ ├── frequency.pipe.ts │ │ │ │ └── frequency.spec.ts │ │ ├── tokens │ │ │ ├── midi-access.ts │ │ │ ├── midi-input-query.ts │ │ │ ├── midi-input.ts │ │ │ ├── midi-inputs.ts │ │ │ ├── midi-messages.ts │ │ │ ├── midi-output-query.ts │ │ │ ├── midi-output.ts │ │ │ ├── midi-outputs.ts │ │ │ ├── midi-support.ts │ │ │ └── sysex.ts │ │ ├── types │ │ │ └── midi-channel.ts │ │ └── utils │ │ │ ├── between.ts │ │ │ ├── get-ports-stream.ts │ │ │ ├── input-by-id.ts │ │ │ ├── input-by-name.ts │ │ │ ├── output-by-id.ts │ │ │ ├── output-by-name.ts │ │ │ ├── to-frequency.ts │ │ │ └── to-note.ts │ ├── test.ts │ ├── tests │ │ ├── aftertouch.spec.ts │ │ ├── filter-by-channel.spec.ts │ │ ├── main-volume.spec.ts │ │ ├── midi-access.spec.ts │ │ ├── modulation-wheel.spec.ts │ │ ├── notes.spec.ts │ │ ├── pan.spec.ts │ │ ├── pitch-bend.spec.ts │ │ ├── polyphonic-aftertouch.spec.ts │ │ ├── program-change.spec.ts │ │ ├── providers.spec.ts │ │ ├── sustain-pedal.spec.ts │ │ ├── to-data-byte.spec.ts │ │ ├── to-data.spec.ts │ │ ├── to-status-byte.spec.ts │ │ ├── to-time-stamp.spec.ts │ │ ├── to-value-byte.spec.ts │ │ └── utils.spec.ts │ └── tsconfig.spec.json ├── mutation-observer │ ├── README.md │ ├── karma.conf.js │ ├── logo.svg │ ├── ng-package.json │ ├── package.json │ ├── project.json │ ├── src │ │ ├── classes │ │ │ └── safe-observer.ts │ │ ├── directives │ │ │ └── mutation-observer.directive.ts │ │ ├── index.ts │ │ ├── services │ │ │ └── mutation-observer.service.ts │ │ ├── tokens │ │ │ └── mutation-observer-init.ts │ │ └── utils │ │ │ ├── boolean-attribute.ts │ │ │ └── mutation-observer-init-factory.ts │ ├── test.ts │ ├── tests │ │ ├── mutation-observer.directive.spec.ts │ │ └── mutation-observer.service.spec.ts │ └── tsconfig.spec.json ├── notification │ ├── README.md │ ├── karma.conf.js │ ├── logo.svg │ ├── ng-package.json │ ├── package.json │ ├── project.json │ ├── src │ │ ├── index.ts │ │ ├── services │ │ │ └── notification.service.ts │ │ └── tokens │ │ │ └── support.ts │ ├── test.ts │ ├── tests │ │ └── notification.spec.ts │ └── tsconfig.spec.json ├── payment-request │ ├── README.md │ ├── karma.conf.js │ ├── logo.svg │ ├── ng-package.json │ ├── package.json │ ├── project.json │ ├── src │ │ ├── directives │ │ │ ├── payment-item │ │ │ │ └── payment-item.directive.ts │ │ │ ├── payment-submit │ │ │ │ └── payment-submit.directive.ts │ │ │ └── payment │ │ │ │ └── payment.directive.ts │ │ ├── index.ts │ │ ├── module.ts │ │ ├── services │ │ │ └── payment-request.service.ts │ │ ├── tokens │ │ │ ├── payment-methods.ts │ │ │ ├── payment-options.ts │ │ │ └── payment-request-support.ts │ │ └── utils │ │ │ └── is-error.ts │ ├── test.ts │ ├── tests │ │ └── payment-request.service.spec.ts │ └── tsconfig.spec.json ├── permissions │ ├── README.md │ ├── karma.conf.js │ ├── logo.svg │ ├── ng-package.json │ ├── package.json │ ├── project.json │ ├── src │ │ ├── index.ts │ │ ├── mocks │ │ │ ├── fake-permission-status.ts │ │ │ └── fake-permissions.ts │ │ ├── services │ │ │ └── permissions.service.ts │ │ ├── tokens │ │ │ ├── permissions-support.ts │ │ │ └── permissions.ts │ │ └── utils │ │ │ └── permissions-predicates.ts │ ├── test.ts │ ├── tests │ │ └── permissions.service.spec.ts │ └── tsconfig.spec.json ├── platform │ ├── README.md │ ├── karma.conf.js │ ├── logo.svg │ ├── ng-package.json │ ├── package.json │ ├── project.json │ ├── src │ │ ├── constants.ts │ │ ├── index.ts │ │ ├── is-android.ts │ │ ├── is-apple.ts │ │ ├── is-edge.ts │ │ ├── is-firefox.ts │ │ ├── is-ios.ts │ │ ├── is-mobile.ts │ │ ├── is-safari.ts │ │ ├── is-touch.ts │ │ └── is-webkit.ts │ ├── test.ts │ ├── tests │ │ ├── browsers.spec.ts │ │ ├── is-ios.spec.ts │ │ └── is-mobile.spec.ts │ ├── tsconfig.lib.json │ └── tsconfig.spec.json ├── resize-observer │ ├── LICENSE │ ├── README.md │ ├── karma.conf.js │ ├── logo.svg │ ├── ng-package.json │ ├── package.json │ ├── project.json │ ├── src │ │ ├── classes │ │ │ └── safe-observer.ts │ │ ├── directives │ │ │ └── resize-observer.directive.ts │ │ ├── index.ts │ │ ├── services │ │ │ └── resize-observer.service.ts │ │ └── tokens │ │ │ ├── resize-option-box.ts │ │ │ └── support.ts │ ├── test.ts │ ├── tests │ │ ├── resize-observer.service.spec.ts │ │ ├── resize-observer.spec.ts │ │ └── support.spec.ts │ └── tsconfig.spec.json ├── screen-orientation │ ├── README.md │ ├── logo.svg │ ├── ng-package.json │ ├── package.json │ ├── project.json │ ├── src │ │ ├── index.ts │ │ ├── screen.service.ts │ │ └── viewport.service.ts │ └── tsconfig.spec.json ├── speech │ ├── README.md │ ├── karma.conf.js │ ├── logo.svg │ ├── ng-package.json │ ├── package.json │ ├── project.json │ ├── src │ │ ├── index.ts │ │ ├── interfaces │ │ │ └── speech-synthesis-utterance-options.ts │ │ ├── modules │ │ │ └── speech-synthesis │ │ │ │ ├── text-to-speech.directive.ts │ │ │ │ └── utterance.pipe.ts │ │ ├── operators │ │ │ ├── confidence-above.ts │ │ │ ├── continuous.ts │ │ │ ├── final.ts │ │ │ ├── first-alternative.ts │ │ │ ├── skip-until-said.ts │ │ │ └── take-until-said.ts │ │ ├── services │ │ │ └── speech-recognition.service.ts │ │ ├── tokens │ │ │ ├── speech-recognition-max-alternatives.ts │ │ │ ├── speech-recognition-support.ts │ │ │ ├── speech-synthesis-support.ts │ │ │ └── speech-synthesis-voices.ts │ │ └── utils │ │ │ └── is-said.ts │ ├── test.ts │ ├── tests │ │ └── speech-synthesis.spec.ts │ └── tsconfig.spec.json ├── storage │ ├── README.md │ ├── karma.conf.js │ ├── logo.svg │ ├── ng-package.json │ ├── package.json │ ├── project.json │ ├── src │ │ ├── index.ts │ │ ├── services │ │ │ └── storage.service.ts │ │ ├── tokens │ │ │ └── storage-event.ts │ │ └── utils │ │ │ ├── filter-by-key.ts │ │ │ └── to-value.ts │ ├── test.ts │ ├── tests │ │ └── storage.service.spec.ts │ └── tsconfig.spec.json ├── universal │ ├── README.md │ ├── karma.conf.js │ ├── logo.svg │ ├── ng-package.json │ ├── package.json │ ├── project.json │ ├── src │ │ ├── classes │ │ │ ├── blob-mock.ts │ │ │ ├── dom-string-list-mock.ts │ │ │ ├── location-mock.ts │ │ │ └── storage-mock.ts │ │ ├── constants │ │ │ ├── universal-animation-frame.ts │ │ │ ├── universal-caches.ts │ │ │ ├── universal-crypto.ts │ │ │ ├── universal-history.ts │ │ │ ├── universal-local-storage.ts │ │ │ ├── universal-location.ts │ │ │ ├── universal-media-devices.ts │ │ │ ├── universal-navigator.ts │ │ │ ├── universal-performance.ts │ │ │ ├── universal-providers.ts │ │ │ ├── universal-session-storage.ts │ │ │ ├── universal-speech-synthesis.ts │ │ │ ├── universal-user-agent.ts │ │ │ └── universal-window.ts │ │ ├── index.ts │ │ ├── tokens │ │ │ ├── ssr-location.ts │ │ │ └── ssr-user-agent.ts │ │ └── utils │ │ │ ├── event-target.ts │ │ │ ├── functions.ts │ │ │ ├── provide-location.ts │ │ │ └── provide-user-agent.ts │ ├── test.ts │ ├── tests │ │ ├── functions.spec.ts │ │ ├── provide-location.spec.ts │ │ ├── provide-user-agent.spec.ts │ │ ├── universal-local-storage.spec.ts │ │ └── universal-navigator.spec.ts │ └── tsconfig.spec.json ├── view-transition │ ├── README.md │ ├── karma.conf.js │ ├── logo.svg │ ├── ng-package.json │ ├── package.json │ ├── project.json │ ├── src │ │ ├── index.ts │ │ └── services │ │ │ └── view-transition.service.ts │ ├── test.ts │ └── tests │ │ └── view-transition.service.spec.ts └── workers │ ├── README.md │ ├── karma.conf.js │ ├── logo.svg │ ├── ng-package.json │ ├── package.json │ ├── project.json │ ├── src │ ├── index.ts │ └── worker │ │ ├── classes │ │ └── web-worker.ts │ │ ├── consts │ │ └── worker-fn-template.ts │ │ ├── operators │ │ └── to-data.ts │ │ ├── pipes │ │ └── worker.pipe.ts │ │ └── types │ │ ├── typed-message-event.ts │ │ └── worker-function.ts │ ├── test.ts │ ├── tests │ ├── web-worker.spec.ts │ └── worker.pipe.spec.ts │ └── tsconfig.spec.json ├── nx.json ├── package-lock.json ├── package.json ├── tsconfig.build.json ├── tsconfig.eslint.json ├── tsconfig.json └── tsconfig.spec.json /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 4 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | 2 | # ================================================================================== 3 | # ================================================================================== 4 | # @taiga-family/ng-web-apis codeowners 5 | # ================================================================================== 6 | # ================================================================================== 7 | # 8 | # Configuration of code ownership and review approvals for the @taiga-family/ng-web-apis repo. 9 | # 10 | # More info: https://help.github.com/articles/about-codeowners/ 11 | # 12 | 13 | * @waterplea @MarsiBarsi 14 | # will be requested for review when someone opens a pull request 15 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | open_collective: ng-web-apis 4 | issuehunt: ng-web-apis 5 | 6 | tidelift: # npm/@ng-web-apis/common 7 | 8 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 9 | patreon: # Replace with a single Patreon username 10 | ko_fi: # Replace with a single Ko-fi username 11 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 12 | liberapay: # Replace with a single Liberapay username 13 | otechie: # Replace with a single Otechie username 14 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 15 | -------------------------------------------------------------------------------- /.github/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": ["github>taiga-family/renovate-config"] 4 | } 5 | -------------------------------------------------------------------------------- /.github/workflows/assign-author.yml: -------------------------------------------------------------------------------- 1 | name: 🤖 PR author as an assignee 2 | on: 3 | pull_request: 4 | types: [opened] 5 | 6 | jobs: 7 | assign: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v4.2.2 11 | - uses: taiga-family/ci/actions/setup/variables@v1.127.0 12 | - uses: toshimaru/auto-author-assign@v2.1.1 13 | continue-on-error: true 14 | 15 | concurrency: 16 | group: ${{ github.workflow }}-${{ github.ref }} 17 | cancel-in-progress: true 18 | -------------------------------------------------------------------------------- /.github/workflows/deploy-preview.yml: -------------------------------------------------------------------------------- 1 | name: 🚀 Deploy / preview 2 | on: pull_request 3 | 4 | jobs: 5 | build_and_preview: 6 | name: Firebase 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v4.2.2 10 | - uses: taiga-family/ci/actions/setup/variables@v1.127.0 11 | - uses: taiga-family/ci/actions/setup/node@v1.127.0 12 | - run: npx nx build demo 13 | 14 | - uses: FirebaseExtended/action-hosting-deploy@v0 15 | continue-on-error: true 16 | if: ${{ env.SUPPORT_AUTO_PUSH == 'true' }} 17 | with: 18 | repoToken: ${{ secrets.GITHUB_TOKEN }} 19 | firebaseServiceAccount: ${{ secrets.FIREBASE_SERVICE_ACCOUNT_NG_WEB_APIS_COMMON }} 20 | projectId: ng-web-apis-common 21 | expires: 1d 22 | 23 | concurrency: 24 | group: ${{ github.workflow }}-${{ github.ref }} 25 | cancel-in-progress: true 26 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: 🚀 Deploy 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | 7 | jobs: 8 | deploy: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v4.2.2 12 | - uses: taiga-family/ci/actions/setup/node@v1.127.0 13 | - run: npx nx build-gh-pages demo 14 | - name: Deploy 15 | uses: JamesIves/github-pages-deploy-action@v4.7.3 16 | with: 17 | token: ${{ secrets.TAIGA_FAMILY_BOT_PAT }} 18 | branch: gh-pages 19 | folder: dist/demo 20 | silent: false 21 | clean: true 22 | 23 | concurrency: 24 | group: ${{ github.workflow }}-${{ github.ref }} 25 | cancel-in-progress: true 26 | -------------------------------------------------------------------------------- /.github/workflows/ready-to-merge.yml: -------------------------------------------------------------------------------- 1 | name: 🤖 PR is ready to merge 2 | on: 3 | pull_request_review: 4 | types: [submitted] 5 | 6 | jobs: 7 | label-when-approved: 8 | name: Label when approved 9 | runs-on: ubuntu-latest 10 | if: github.event.review.state == 'approved' 11 | steps: 12 | - uses: actions/checkout@v4.2.2 13 | - uses: taiga-family/ci/actions/setup/variables@v1.127.0 14 | - uses: taiga-family/ci/actions/auto/label-when-approved@v1.127.0 15 | with: 16 | approvals: 1 17 | token: ${{ secrets.GITHUB_TOKEN }} 18 | 19 | concurrency: 20 | group: ${{ github.workflow }}-${{ github.ref }} 21 | cancel-in-progress: true 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # compiled output 2 | /dist 3 | /tmp 4 | /out-tsc 5 | # Only exists if Bazel was run 6 | /bazel-out 7 | 8 | # dependencies 9 | **/node_modules/** 10 | 11 | # profiling files 12 | chrome-profiler-events.json 13 | speed-measure-plugin.json 14 | 15 | # IDEs and editors 16 | /.idea 17 | .project 18 | .classpath 19 | .c9/ 20 | *.launch 21 | .settings/ 22 | *.sublime-workspace 23 | 24 | # IDE - VSCode 25 | .vscode/* 26 | !.vscode/settings.json 27 | !.vscode/tasks.json 28 | !.vscode/launch.json 29 | !.vscode/extensions.json 30 | .history/* 31 | 32 | # misc 33 | /.sass-cache 34 | /connect.lock 35 | /coverage 36 | /libpeerconnection.log 37 | npm-debug.log 38 | yarn-error.log 39 | /typings 40 | 41 | # System Files 42 | .DS_Store 43 | Thumbs.db 44 | 45 | migrations.json 46 | apps/demo/routesFile.txt 47 | .ssl 48 | .angular 49 | .nx 50 | *.iml 51 | -------------------------------------------------------------------------------- /.husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ 2 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # shellcheck disable=SC1090 3 | . "$(dirname "$0")/_/husky.sh" 4 | 5 | npx commitlint --edit $1 6 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # shellcheck disable=SC1090 3 | . "$(dirname "$0")/_/husky.sh" 4 | 5 | npx lint-staged 6 | npm run typecheck 7 | -------------------------------------------------------------------------------- /.release-it.js: -------------------------------------------------------------------------------- 1 | module.exports = require('@taiga-ui/release-it-config'); 2 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "node_modules/typescript/lib" 3 | } 4 | -------------------------------------------------------------------------------- /apps/demo/ngsw-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../node_modules/@angular/service-worker/config/schema.json", 3 | "index": "/index.html", 4 | "assetGroups": [ 5 | { 6 | "name": "app", 7 | "installMode": "prefetch", 8 | "resources": { 9 | "files": ["/favicon.ico", "/index.html", "/manifest.webmanifest", "/*.css", "/*.js"] 10 | } 11 | }, 12 | { 13 | "name": "assets", 14 | "installMode": "lazy", 15 | "updateMode": "prefetch", 16 | "resources": { 17 | "files": ["/assets/**", "/*.(eot|svg|cur|jpg|png|webp|gif|otf|ttf|woff|woff2|ani)"] 18 | } 19 | } 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /apps/demo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ng-web-apis-demo", 3 | "private": true 4 | } 5 | -------------------------------------------------------------------------------- /apps/demo/src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import {ChangeDetectionStrategy, Component} from '@angular/core'; 2 | import {RouterLink, RouterLinkActive, RouterOutlet} from '@angular/router'; 3 | import {TuiIcon, TuiLink, TuiRoot} from '@taiga-ui/core'; 4 | 5 | @Component({ 6 | standalone: true, 7 | selector: 'app', 8 | imports: [RouterLink, RouterLinkActive, RouterOutlet, TuiIcon, TuiLink, TuiRoot], 9 | templateUrl: './app.component.html', 10 | styleUrls: ['./app.component.css'], 11 | changeDetection: ChangeDetectionStrategy.OnPush, 12 | }) 13 | export class App {} 14 | -------------------------------------------------------------------------------- /apps/demo/src/app/constants/demo-path.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line no-restricted-syntax 2 | export enum DemoPath { 3 | HomePage = 'home', 4 | CommonPage = 'common', 5 | PlatformPage = 'platform', 6 | UniversalPage = 'universal', 7 | AudioPage = 'audio', 8 | ResizeObserverPage = 'resize-observer', 9 | CanvasPage = 'canvas', 10 | SpeechPage = 'speech', 11 | GeolocationPage = 'geolocation', 12 | ScreenOrientation = 'screen-orientation', 13 | IntersectionObserverPage = 'intersection-observer', 14 | MutationObserverPage = 'mutation-observer', 15 | PaymentRequestPage = 'payment-request', 16 | MidiPage = 'midi', 17 | PermissionsPage = 'permissions', 18 | StoragePage = 'storage', 19 | WorkersPage = 'workers', 20 | ViewTransitionPage = 'view-transition', 21 | Notification = 'notification', 22 | } 23 | -------------------------------------------------------------------------------- /apps/demo/src/app/constants/index.ts: -------------------------------------------------------------------------------- 1 | export * from './demo-path'; 2 | -------------------------------------------------------------------------------- /apps/demo/src/app/pages/canvas/canvas-page.component.css: -------------------------------------------------------------------------------- 1 | :host { 2 | display: block; 3 | max-inline-size: 50rem; 4 | margin: 0 auto; 5 | } 6 | 7 | .row { 8 | display: flex; 9 | min-inline-size: 100%; 10 | gap: 0.75rem; 11 | } 12 | 13 | .child { 14 | flex: 1; 15 | min-inline-size: 6.25rem; 16 | } 17 | 18 | .controls { 19 | display: flex; 20 | flex-direction: column; 21 | gap: 0.75rem; 22 | } 23 | 24 | input { 25 | margin: 0; 26 | } 27 | 28 | fieldset { 29 | display: flex; 30 | flex-wrap: wrap; 31 | border: none; 32 | background: #f5f5f5; 33 | padding: 0.75rem; 34 | margin: 0.75rem 0; 35 | gap: 0.75rem; 36 | border-radius: 1.25rem; 37 | } 38 | 39 | canvas { 40 | display: block; 41 | inline-size: 18.75rem; 42 | block-size: 9.375rem; 43 | margin: 0 auto 2.5rem; 44 | border-radius: 0.375rem; 45 | box-shadow: 0 0.75rem 2.25rem rgba(0, 0, 0, 0.2); 46 | } 47 | -------------------------------------------------------------------------------- /apps/demo/src/app/pages/common/common-page.component.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /apps/demo/src/app/pages/common/common-page.component.ts: -------------------------------------------------------------------------------- 1 | import {CommonModule} from '@angular/common'; 2 | import {ChangeDetectionStrategy, Component} from '@angular/core'; 3 | import {RouterLink} from '@angular/router'; 4 | import {MarkdownModule} from 'ngx-markdown'; 5 | 6 | @Component({ 7 | standalone: true, 8 | selector: 'common-page', 9 | imports: [CommonModule, MarkdownModule, RouterLink], 10 | templateUrl: './common-page.component.html', 11 | changeDetection: ChangeDetectionStrategy.OnPush, 12 | }) 13 | export default class CommonPage { 14 | protected readonly readme = import( 15 | '../../../../../../libs/common/README.md?raw' 16 | ).then((a) => a.default.replace('![logo](logo.svg) ', '')) as any as Promise; 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 | 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 | 24 | 25 | -------------------------------------------------------------------------------- /apps/demo/src/app/pages/notification/examples/02-create-notification/index.html: -------------------------------------------------------------------------------- 1 | 9 | -------------------------------------------------------------------------------- /apps/demo/src/app/pages/notification/examples/03-close-notification/index.html: -------------------------------------------------------------------------------- 1 | 9 | -------------------------------------------------------------------------------- /apps/demo/src/app/pages/notification/examples/04-listen-notification-events/index.html: -------------------------------------------------------------------------------- 1 | 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 | 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('![logo](logo.svg) ', '')) as any as Promise; 20 | } 21 | -------------------------------------------------------------------------------- /apps/demo/src/app/pages/resize-observer/resize-observer-page.component.html: -------------------------------------------------------------------------------- 1 |
5 |

12 | Resizable box 13 |

14 | 25 |
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('![logo](logo.svg) ', '')) 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 | # ![ng-web-apis logo](https://raw.githubusercontent.com/taiga-family/ng-web-apis/main/libs/platform/logo.svg) 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 | # ![ng-web-apis logo](https://raw.githubusercontent.com/taiga-family/ng-web-apis/main/libs/screen-orientation/logo.svg) 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 | --------------------------------------------------------------------------------