@signality/core 0.1.1 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/browser/file-dialog/index.d.ts +5 -3
- package/browser/storage/index.d.ts +1 -1
- package/browser/text-selection/index.d.ts +9 -2
- package/elements/element-focus/index.d.ts +12 -8
- package/fesm2022/signality-core-browser-clipboard.mjs +2 -1
- package/fesm2022/signality-core-browser-clipboard.mjs.map +1 -1
- package/fesm2022/signality-core-browser-favicon.mjs +1 -1
- package/fesm2022/signality-core-browser-favicon.mjs.map +1 -1
- package/fesm2022/signality-core-browser-file-dialog.mjs +3 -2
- package/fesm2022/signality-core-browser-file-dialog.mjs.map +1 -1
- package/fesm2022/signality-core-browser-fullscreen.mjs +2 -1
- package/fesm2022/signality-core-browser-fullscreen.mjs.map +1 -1
- package/fesm2022/signality-core-browser-listener.mjs +2 -1
- package/fesm2022/signality-core-browser-listener.mjs.map +1 -1
- package/fesm2022/signality-core-browser-media-query.mjs +2 -1
- package/fesm2022/signality-core-browser-media-query.mjs.map +1 -1
- package/fesm2022/signality-core-browser-picture-in-picture.mjs +2 -1
- package/fesm2022/signality-core-browser-picture-in-picture.mjs.map +1 -1
- package/fesm2022/signality-core-browser-speech-recognition.mjs +2 -1
- package/fesm2022/signality-core-browser-speech-recognition.mjs.map +1 -1
- package/fesm2022/signality-core-browser-speech-synthesis.mjs +2 -1
- package/fesm2022/signality-core-browser-speech-synthesis.mjs.map +1 -1
- package/fesm2022/signality-core-browser-storage.mjs +40 -45
- package/fesm2022/signality-core-browser-storage.mjs.map +1 -1
- package/fesm2022/signality-core-browser-text-direction.mjs +2 -1
- package/fesm2022/signality-core-browser-text-direction.mjs.map +1 -1
- package/fesm2022/signality-core-browser-text-selection.mjs +36 -4
- package/fesm2022/signality-core-browser-text-selection.mjs.map +1 -1
- package/fesm2022/signality-core-browser-vibration.mjs +2 -1
- package/fesm2022/signality-core-browser-vibration.mjs.map +1 -1
- package/fesm2022/signality-core-browser-web-notification.mjs +2 -1
- package/fesm2022/signality-core-browser-web-notification.mjs.map +1 -1
- package/fesm2022/signality-core-browser-web-worker.mjs +2 -1
- package/fesm2022/signality-core-browser-web-worker.mjs.map +1 -1
- package/fesm2022/signality-core-elements-dropzone.mjs +2 -1
- package/fesm2022/signality-core-elements-dropzone.mjs.map +1 -1
- package/fesm2022/signality-core-elements-element-focus-within.mjs +2 -1
- package/fesm2022/signality-core-elements-element-focus-within.mjs.map +1 -1
- package/fesm2022/signality-core-elements-element-focus.mjs +24 -10
- package/fesm2022/signality-core-elements-element-focus.mjs.map +1 -1
- package/fesm2022/signality-core-elements-element-size.mjs +3 -2
- package/fesm2022/signality-core-elements-element-size.mjs.map +1 -1
- package/fesm2022/signality-core-elements-element-visibility.mjs +2 -6
- package/fesm2022/signality-core-elements-element-visibility.mjs.map +1 -1
- package/fesm2022/signality-core-elements-on-click-outside.mjs +2 -1
- package/fesm2022/signality-core-elements-on-click-outside.mjs.map +1 -1
- package/fesm2022/signality-core-elements-on-disconnect.mjs +2 -1
- package/fesm2022/signality-core-elements-on-disconnect.mjs.map +1 -1
- package/fesm2022/signality-core-elements-on-long-press.mjs +2 -1
- package/fesm2022/signality-core-elements-on-long-press.mjs.map +1 -1
- package/fesm2022/signality-core-elements-scroll-position.mjs +2 -1
- package/fesm2022/signality-core-elements-scroll-position.mjs.map +1 -1
- package/fesm2022/signality-core-internal.mjs +2 -26
- package/fesm2022/signality-core-internal.mjs.map +1 -1
- package/fesm2022/signality-core-observers-intersection-observer.mjs +2 -1
- package/fesm2022/signality-core-observers-intersection-observer.mjs.map +1 -1
- package/fesm2022/signality-core-observers-mutation-observer.mjs +2 -1
- package/fesm2022/signality-core-observers-mutation-observer.mjs.map +1 -1
- package/fesm2022/signality-core-observers-resize-observer.mjs +2 -1
- package/fesm2022/signality-core-observers-resize-observer.mjs.map +1 -1
- package/fesm2022/signality-core-reactivity-debounced.mjs +2 -1
- package/fesm2022/signality-core-reactivity-debounced.mjs.map +1 -1
- package/fesm2022/signality-core-reactivity-throttled.mjs +2 -1
- package/fesm2022/signality-core-reactivity-throttled.mjs.map +1 -1
- package/fesm2022/signality-core-scheduling-debounce-callback.mjs +2 -1
- package/fesm2022/signality-core-scheduling-debounce-callback.mjs.map +1 -1
- package/fesm2022/signality-core-scheduling-interval.mjs +2 -1
- package/fesm2022/signality-core-scheduling-interval.mjs.map +1 -1
- package/fesm2022/signality-core-scheduling-throttle-callback.mjs +2 -1
- package/fesm2022/signality-core-scheduling-throttle-callback.mjs.map +1 -1
- package/fesm2022/signality-core-utilities.mjs +64 -0
- package/fesm2022/signality-core-utilities.mjs.map +1 -0
- package/fesm2022/signality-core.mjs +1 -0
- package/fesm2022/signality-core.mjs.map +1 -1
- package/index.d.ts +1 -0
- package/internal/utils/index.d.ts +0 -2
- package/package.json +9 -5
- package/utilities/generate-id.d.ts +29 -0
- package/utilities/index.d.ts +3 -0
- /package/{internal/utils → utilities}/to-element.d.ts +0 -0
- /package/{internal/utils → utilities}/to-value.d.ts +0 -0
|
@@ -24,7 +24,10 @@ export interface FileDialogOptions extends WithInjector {
|
|
|
24
24
|
readonly capture?: MaybeSignal<string>;
|
|
25
25
|
/**
|
|
26
26
|
* Whether to select directories instead of files.
|
|
27
|
-
*
|
|
27
|
+
*
|
|
28
|
+
* Uses the `webkitdirectory` attribute, widely supported across all modern browsers.
|
|
29
|
+
*
|
|
30
|
+
* @see [webkitdirectory on MDN](https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/webkitdirectory)
|
|
28
31
|
* @default false
|
|
29
32
|
*/
|
|
30
33
|
readonly directory?: MaybeSignal<boolean>;
|
|
@@ -32,8 +35,7 @@ export interface FileDialogOptions extends WithInjector {
|
|
|
32
35
|
* Custom validation predicate called for each selected file.
|
|
33
36
|
* Return `true` to keep the file, `false` to reject it.
|
|
34
37
|
*
|
|
35
|
-
* When provided,
|
|
36
|
-
* takes full responsibility for deciding which files are valid.
|
|
38
|
+
* When provided, `accept` only affects native browser filtering, not per-file validation.
|
|
37
39
|
*
|
|
38
40
|
* @example
|
|
39
41
|
* ```typescript
|
|
@@ -54,7 +54,7 @@ export interface StorageOptions<T> extends CreateSignalOptions<T>, WithInjector
|
|
|
54
54
|
*
|
|
55
55
|
* // Or with custom merge
|
|
56
56
|
* const settings = storage('settings', defaultSettings, {
|
|
57
|
-
* mergeResolver: (stored, initial) => deepMerge(
|
|
57
|
+
* mergeResolver: (stored, initial) => deepMerge(initial, stored),
|
|
58
58
|
* });
|
|
59
59
|
* ```
|
|
60
60
|
*/
|
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import { type Signal } from '@angular/core';
|
|
2
|
-
import type { WithInjector } from '@signality/core/types';
|
|
3
|
-
export
|
|
2
|
+
import type { MaybeElementSignal, WithInjector } from '@signality/core/types';
|
|
3
|
+
export interface TextSelectionOptions extends WithInjector {
|
|
4
|
+
/**
|
|
5
|
+
* Element to track selection within.
|
|
6
|
+
* When provided, only selections entirely contained within this element are tracked.
|
|
7
|
+
* @default document (all selections)
|
|
8
|
+
*/
|
|
9
|
+
readonly root?: MaybeElementSignal<Element>;
|
|
10
|
+
}
|
|
4
11
|
export interface TextSelectionRef {
|
|
5
12
|
/** The selected text content */
|
|
6
13
|
readonly text: Signal<string>;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type CreateSignalOptions, type
|
|
1
|
+
import { type CreateSignalOptions, type WritableSignal } from '@angular/core';
|
|
2
2
|
import type { MaybeElementSignal, WithInjector } from '@signality/core/types';
|
|
3
3
|
export interface ElementFocusOptions extends CreateSignalOptions<boolean>, WithInjector {
|
|
4
4
|
/**
|
|
@@ -10,23 +10,27 @@ export interface ElementFocusOptions extends CreateSignalOptions<boolean>, WithI
|
|
|
10
10
|
* @default false
|
|
11
11
|
*/
|
|
12
12
|
readonly focusVisible?: boolean;
|
|
13
|
+
/**
|
|
14
|
+
* Prevent scrolling to the element when it is focused.
|
|
15
|
+
* @default false
|
|
16
|
+
*/
|
|
17
|
+
readonly preventScroll?: boolean;
|
|
13
18
|
}
|
|
14
19
|
/**
|
|
15
20
|
* Reactive tracking of focus state on an element.
|
|
16
|
-
* Detects when an element gains or loses focus.
|
|
21
|
+
* Detects when an element gains or loses focus, and allows programmatically setting focus.
|
|
17
22
|
*
|
|
18
23
|
* @param target - The element to track focus state on
|
|
19
|
-
* @param options - Optional configuration including focusVisible
|
|
20
|
-
* @returns A signal that is `true` when the element has focus
|
|
24
|
+
* @param options - Optional configuration including focusVisible, preventScroll and injector
|
|
25
|
+
* @returns A writable signal that is `true` when the element has focus
|
|
21
26
|
*
|
|
22
27
|
* @example
|
|
23
28
|
* ```typescript
|
|
24
29
|
* @Component({
|
|
25
30
|
* template: `
|
|
26
31
|
* <input #input [class.focused]="isFocused()" />
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
* }
|
|
32
|
+
* <button (click)="isFocused.set(true)">Focus Input</button>
|
|
33
|
+
* <button (click)="isFocused.set(false)">Blur Input</button>
|
|
30
34
|
* `
|
|
31
35
|
* })
|
|
32
36
|
* export class FocusDemo {
|
|
@@ -35,4 +39,4 @@ export interface ElementFocusOptions extends CreateSignalOptions<boolean>, WithI
|
|
|
35
39
|
* }
|
|
36
40
|
* ```
|
|
37
41
|
*/
|
|
38
|
-
export declare function elementFocus(target: MaybeElementSignal<HTMLElement>, options?: ElementFocusOptions):
|
|
42
|
+
export declare function elementFocus(target: MaybeElementSignal<HTMLElement>, options?: ElementFocusOptions): WritableSignal<boolean>;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { signal } from '@angular/core';
|
|
2
|
-
import { setupContext, constSignal, NOOP_ASYNC_FN
|
|
2
|
+
import { setupContext, constSignal, NOOP_ASYNC_FN } from '@signality/core/internal';
|
|
3
|
+
import { toValue } from '@signality/core/utilities';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Signal-based wrapper around the [Clipboard API](https://developer.mozilla.org/en-US/docs/Web/API/Clipboard_API).
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"signality-core-browser-clipboard.mjs","sources":["../../../projects/core/browser/clipboard/index.ts","../../../projects/core/browser/clipboard/signality-core-browser-clipboard.ts"],"sourcesContent":["import { type Signal, signal } from '@angular/core';\nimport {
|
|
1
|
+
{"version":3,"file":"signality-core-browser-clipboard.mjs","sources":["../../../projects/core/browser/clipboard/index.ts","../../../projects/core/browser/clipboard/signality-core-browser-clipboard.ts"],"sourcesContent":["import { type Signal, signal } from '@angular/core';\nimport { constSignal, NOOP_ASYNC_FN, setupContext, type Timer } from '@signality/core/internal';\nimport { toValue } from '@signality/core/utilities';\nimport type { MaybeSignal, WithInjector } from '@signality/core/types';\n\nexport interface ClipboardOptions extends WithInjector {\n /**\n * How long `copied` stays `true` after copy (ms).\n * @default 1500\n */\n readonly copiedDuration?: MaybeSignal<number>;\n}\n\nexport interface ClipboardRef {\n /**\n * Whether the Clipboard API is supported in the current browser.\n *\n * @see [Clipboard API browser compatibility on MDN](https://developer.mozilla.org/en-US/docs/Web/API/Clipboard_API#browser_compatibility)\n */\n readonly isSupported: Signal<boolean>;\n\n /**\n * The most recently copied or pasted text.\n */\n readonly text: Signal<string>;\n\n /**\n * Whether the text was recently copied. Resets to `false` after `copiedDuration` ms.\n */\n readonly copied: Signal<boolean>;\n\n /**\n * Write text to the clipboard.\n *\n * @see [Clipboard: writeText() on MDN](https://developer.mozilla.org/en-US/docs/Web/API/Clipboard/writeText)\n */\n readonly copy: (text: string) => Promise<void>;\n\n /**\n * Read text from the clipboard.\n *\n * @see [Clipboard: readText() on MDN](https://developer.mozilla.org/en-US/docs/Web/API/Clipboard/readText)\n */\n readonly paste: () => Promise<string>;\n}\n\n/**\n * Signal-based wrapper around the [Clipboard API](https://developer.mozilla.org/en-US/docs/Web/API/Clipboard_API).\n *\n * @param options - Optional configuration\n * @returns A ClipboardRef with text, copied, isSupported signals and copy/paste methods\n *\n * @example\n * ```typescript\n * @Component({\n * template: `\n * <input #input value=\"Hello World!\" />\n * <button (click)=\"copyText(input.value)\">Copy</button>\n * @if (cb.copied()) {\n * <span>Copied!</span>\n * }\n * `\n * })\n * export class ClipboardDemo {\n * readonly cb = clipboard();\n *\n * async copyText(text: string) {\n * await this.cb.copy(text);\n * }\n * }\n * ```\n */\nexport function clipboard(options?: ClipboardOptions): ClipboardRef {\n const { runInContext } = setupContext(options?.injector, clipboard);\n\n return runInContext(({ isBrowser, onCleanup }) => {\n const isSupported = constSignal(\n isBrowser && 'clipboard' in navigator && typeof navigator.clipboard?.writeText === 'function'\n );\n\n if (!isSupported()) {\n return {\n isSupported,\n text: constSignal(''),\n copied: constSignal(false),\n copy: NOOP_ASYNC_FN,\n paste: () => Promise.resolve(''),\n };\n }\n\n const copiedDuration = options?.copiedDuration ?? 1500;\n\n const text = signal('');\n const copied = signal(false);\n\n let copiedTimeout: Timer;\n\n const copy = async (value: string): Promise<void> => {\n try {\n await navigator.clipboard.writeText(value);\n\n text.set(value);\n copied.set(true);\n\n if (copiedTimeout) {\n clearTimeout(copiedTimeout);\n }\n\n copiedTimeout = setTimeout(() => {\n copied.set(false);\n copiedTimeout = undefined;\n }, toValue.untracked(copiedDuration));\n } catch (error) {\n copied.set(false);\n if (ngDevMode) {\n console.warn(\n `[clipboard] Failed to copy text to clipboard. ` +\n `This may be due to permission denied or clipboard access failed.`,\n error\n );\n }\n }\n };\n\n const paste = async (): Promise<string> => {\n try {\n const value = await navigator.clipboard.readText();\n text.set(value);\n return value;\n } catch (error) {\n if (ngDevMode) {\n console.warn(\n `[clipboard] Failed to read text from clipboard. ` +\n `This may be due to permission denied or clipboard access failed.`,\n error\n );\n }\n return '';\n }\n };\n\n onCleanup(() => {\n if (copiedTimeout) {\n clearTimeout(copiedTimeout);\n }\n });\n\n return {\n isSupported,\n text: text.asReadonly(),\n copied: copied.asReadonly(),\n copy,\n paste,\n };\n });\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;;AA8CA;;;;;;;;;;;;;;;;;;;;;;;;;AAyBG;AACG,SAAU,SAAS,CAAC,OAA0B,EAAA;AAClD,IAAA,MAAM,EAAE,YAAY,EAAE,GAAG,YAAY,CAAC,OAAO,EAAE,QAAQ,EAAE,SAAS,CAAC;IAEnE,OAAO,YAAY,CAAC,CAAC,EAAE,SAAS,EAAE,SAAS,EAAE,KAAI;AAC/C,QAAA,MAAM,WAAW,GAAG,WAAW,CAC7B,SAAS,IAAI,WAAW,IAAI,SAAS,IAAI,OAAO,SAAS,CAAC,SAAS,EAAE,SAAS,KAAK,UAAU,CAC9F;AAED,QAAA,IAAI,CAAC,WAAW,EAAE,EAAE;YAClB,OAAO;gBACL,WAAW;AACX,gBAAA,IAAI,EAAE,WAAW,CAAC,EAAE,CAAC;AACrB,gBAAA,MAAM,EAAE,WAAW,CAAC,KAAK,CAAC;AAC1B,gBAAA,IAAI,EAAE,aAAa;gBACnB,KAAK,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;aACjC;QACH;AAEA,QAAA,MAAM,cAAc,GAAG,OAAO,EAAE,cAAc,IAAI,IAAI;AAEtD,QAAA,MAAM,IAAI,GAAG,MAAM,CAAC,EAAE,gDAAC;AACvB,QAAA,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,kDAAC;AAE5B,QAAA,IAAI,aAAoB;AAExB,QAAA,MAAM,IAAI,GAAG,OAAO,KAAa,KAAmB;AAClD,YAAA,IAAI;gBACF,MAAM,SAAS,CAAC,SAAS,CAAC,SAAS,CAAC,KAAK,CAAC;AAE1C,gBAAA,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC;AACf,gBAAA,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC;gBAEhB,IAAI,aAAa,EAAE;oBACjB,YAAY,CAAC,aAAa,CAAC;gBAC7B;AAEA,gBAAA,aAAa,GAAG,UAAU,CAAC,MAAK;AAC9B,oBAAA,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC;oBACjB,aAAa,GAAG,SAAS;gBAC3B,CAAC,EAAE,OAAO,CAAC,SAAS,CAAC,cAAc,CAAC,CAAC;YACvC;YAAE,OAAO,KAAK,EAAE;AACd,gBAAA,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC;gBACjB,IAAI,SAAS,EAAE;oBACb,OAAO,CAAC,IAAI,CACV,CAAA,8CAAA,CAAgD;wBAC9C,CAAA,gEAAA,CAAkE,EACpE,KAAK,CACN;gBACH;YACF;AACF,QAAA,CAAC;AAED,QAAA,MAAM,KAAK,GAAG,YAA4B;AACxC,YAAA,IAAI;gBACF,MAAM,KAAK,GAAG,MAAM,SAAS,CAAC,SAAS,CAAC,QAAQ,EAAE;AAClD,gBAAA,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC;AACf,gBAAA,OAAO,KAAK;YACd;YAAE,OAAO,KAAK,EAAE;gBACd,IAAI,SAAS,EAAE;oBACb,OAAO,CAAC,IAAI,CACV,CAAA,gDAAA,CAAkD;wBAChD,CAAA,gEAAA,CAAkE,EACpE,KAAK,CACN;gBACH;AACA,gBAAA,OAAO,EAAE;YACX;AACF,QAAA,CAAC;QAED,SAAS,CAAC,MAAK;YACb,IAAI,aAAa,EAAE;gBACjB,YAAY,CAAC,aAAa,CAAC;YAC7B;AACF,QAAA,CAAC,CAAC;QAEF,OAAO;YACL,WAAW;AACX,YAAA,IAAI,EAAE,IAAI,CAAC,UAAU,EAAE;AACvB,YAAA,MAAM,EAAE,MAAM,CAAC,UAAU,EAAE;YAC3B,IAAI;YACJ,KAAK;SACN;AACH,IAAA,CAAC,CAAC;AACJ;;AC3JA;;AAEG;;;;"}
|
|
@@ -42,7 +42,7 @@ function favicon(options) {
|
|
|
42
42
|
const appBaseHref = inject(APP_BASE_HREF, { optional: true });
|
|
43
43
|
const baseUrl = options?.baseUrl ?? appBaseHref ?? '';
|
|
44
44
|
const getLinkElement = () => {
|
|
45
|
-
let link = document.querySelector('link[rel*="icon"]');
|
|
45
|
+
let link = document.querySelector('link[rel*="icon"]:not([rel*="apple-touch-icon"])');
|
|
46
46
|
if (!link) {
|
|
47
47
|
link = document.createElement('link');
|
|
48
48
|
link.rel = 'icon';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"signality-core-browser-favicon.mjs","sources":["../../../projects/core/browser/favicon/index.ts","../../../projects/core/browser/favicon/signality-core-browser-favicon.ts"],"sourcesContent":["import { inject, type Signal, signal, untracked } from '@angular/core';\nimport { APP_BASE_HREF } from '@angular/common';\nimport { constSignal, createToken, NOOP_FN, setupContext } from '@signality/core/internal';\nimport type { WithInjector } from '@signality/core/types';\n\nexport interface FaviconOptions extends WithInjector {\n /**\n * Base URL prepended to all favicon paths passed to `set()`.\n *\n * Resolution priority:\n * 1. Explicit `baseUrl` value\n * 2. [`APP_BASE_HREF`](https://angular.dev/api/common/APP_BASE_HREF) token value (if configured)\n * 3. Empty string `''`\n */\n readonly baseUrl?: string;\n}\n\nexport interface FaviconRef {\n /**\n * URL of the currently active favicon.\n */\n readonly current: Signal<string>;\n\n /**\n * URL of the favicon at the time the utility was initialized.\n */\n readonly original: Signal<string>;\n\n /**\n * Set the favicon to the given URL.\n *\n * @see [HTMLLinkElement on MDN](https://developer.mozilla.org/en-US/docs/Web/API/HTMLLinkElement)\n */\n readonly set: (url: string) => void;\n\n /**\n * Render an emoji onto a canvas and use it as the favicon.\n *\n * @see [Canvas API on MDN](https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API)\n */\n readonly setEmoji: (emoji: string) => void;\n\n /**\n * Reset the favicon to the original URL captured on initialization.\n */\n readonly reset: () => void;\n}\n\n/**\n * Reactive favicon manipulation using the [HTMLLinkElement](https://developer.mozilla.org/en-US/docs/Web/API/HTMLLinkElement).\n * Dynamically change the page favicon based on application state.\n *\n * @param options - Optional configuration\n * @returns A FaviconRef with favicon control methods\n *\n * @example\n * ```typescript\n * @Component({\n * template: `\n * <button (click)=\"setNotification()\">Set Notification</button>\n * <button (click)=\"fav.reset()\">Reset Favicon</button>\n * <p>Current: {{ fav.current() }}</p>\n * `\n * })\n * export class FaviconDemo {\n * readonly fav = favicon();\n *\n * setNotification() {\n * this.fav.setEmoji('🔴');\n * }\n * }\n * ```\n */\nexport function favicon(options?: FaviconOptions): FaviconRef {\n const { runInContext } = setupContext(options?.injector, favicon);\n\n return runInContext(({ isServer }) => {\n if (isServer) {\n return {\n current: constSignal(''),\n original: constSignal(''),\n set: NOOP_FN,\n setEmoji: NOOP_FN,\n reset: NOOP_FN,\n };\n }\n\n const appBaseHref = inject(APP_BASE_HREF, { optional: true });\n const baseUrl = options?.baseUrl ?? appBaseHref ?? '';\n\n const getLinkElement = (): HTMLLinkElement => {\n let link = document.querySelector<HTMLLinkElement>('link[rel*=\"icon\"]');\n\n if (!link) {\n link = document.createElement('link');\n link.rel = 'icon';\n document.head.appendChild(link);\n }\n\n return link;\n };\n\n const { href = '' } = getLinkElement();\n const current = signal(href);\n const original = signal(href);\n\n const set = (url: string) => {\n const fullUrl = baseUrl + url;\n const linkEl = getLinkElement();\n linkEl.href = fullUrl;\n current.set(fullUrl);\n };\n\n const setEmoji = (emoji: string) => {\n const canvas = document.createElement('canvas');\n canvas.width = 32;\n canvas.height = 32;\n\n const ctx = canvas.getContext('2d');\n if (!ctx) return;\n\n ctx.font = '28px serif';\n ctx.textAlign = 'center';\n ctx.textBaseline = 'middle';\n ctx.fillText(emoji, 16, 18);\n\n const dataUrl = canvas.toDataURL('image/png');\n const linkEl = getLinkElement();\n linkEl.href = dataUrl;\n current.set(dataUrl);\n };\n\n const reset = () => {\n const linkEl = getLinkElement();\n const originalHref = untracked(original);\n linkEl.href = originalHref;\n current.set(originalHref);\n };\n\n return {\n current: current.asReadonly(),\n original: original.asReadonly(),\n set,\n setEmoji,\n reset,\n };\n });\n}\n\nexport const FAVICON = /* @__PURE__ */ createToken(favicon);\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;;AAgDA;;;;;;;;;;;;;;;;;;;;;;;;AAwBG;AACG,SAAU,OAAO,CAAC,OAAwB,EAAA;AAC9C,IAAA,MAAM,EAAE,YAAY,EAAE,GAAG,YAAY,CAAC,OAAO,EAAE,QAAQ,EAAE,OAAO,CAAC;AAEjE,IAAA,OAAO,YAAY,CAAC,CAAC,EAAE,QAAQ,EAAE,KAAI;QACnC,IAAI,QAAQ,EAAE;YACZ,OAAO;AACL,gBAAA,OAAO,EAAE,WAAW,CAAC,EAAE,CAAC;AACxB,gBAAA,QAAQ,EAAE,WAAW,CAAC,EAAE,CAAC;AACzB,gBAAA,GAAG,EAAE,OAAO;AACZ,gBAAA,QAAQ,EAAE,OAAO;AACjB,gBAAA,KAAK,EAAE,OAAO;aACf;QACH;AAEA,QAAA,MAAM,WAAW,GAAG,MAAM,CAAC,aAAa,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;QAC7D,MAAM,OAAO,GAAG,OAAO,EAAE,OAAO,IAAI,WAAW,IAAI,EAAE;QAErD,MAAM,cAAc,GAAG,MAAsB;YAC3C,IAAI,IAAI,GAAG,QAAQ,CAAC,aAAa,
|
|
1
|
+
{"version":3,"file":"signality-core-browser-favicon.mjs","sources":["../../../projects/core/browser/favicon/index.ts","../../../projects/core/browser/favicon/signality-core-browser-favicon.ts"],"sourcesContent":["import { inject, type Signal, signal, untracked } from '@angular/core';\nimport { APP_BASE_HREF } from '@angular/common';\nimport { constSignal, createToken, NOOP_FN, setupContext } from '@signality/core/internal';\nimport type { WithInjector } from '@signality/core/types';\n\nexport interface FaviconOptions extends WithInjector {\n /**\n * Base URL prepended to all favicon paths passed to `set()`.\n *\n * Resolution priority:\n * 1. Explicit `baseUrl` value\n * 2. [`APP_BASE_HREF`](https://angular.dev/api/common/APP_BASE_HREF) token value (if configured)\n * 3. Empty string `''`\n */\n readonly baseUrl?: string;\n}\n\nexport interface FaviconRef {\n /**\n * URL of the currently active favicon.\n */\n readonly current: Signal<string>;\n\n /**\n * URL of the favicon at the time the utility was initialized.\n */\n readonly original: Signal<string>;\n\n /**\n * Set the favicon to the given URL.\n *\n * @see [HTMLLinkElement on MDN](https://developer.mozilla.org/en-US/docs/Web/API/HTMLLinkElement)\n */\n readonly set: (url: string) => void;\n\n /**\n * Render an emoji onto a canvas and use it as the favicon.\n *\n * @see [Canvas API on MDN](https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API)\n */\n readonly setEmoji: (emoji: string) => void;\n\n /**\n * Reset the favicon to the original URL captured on initialization.\n */\n readonly reset: () => void;\n}\n\n/**\n * Reactive favicon manipulation using the [HTMLLinkElement](https://developer.mozilla.org/en-US/docs/Web/API/HTMLLinkElement).\n * Dynamically change the page favicon based on application state.\n *\n * @param options - Optional configuration\n * @returns A FaviconRef with favicon control methods\n *\n * @example\n * ```typescript\n * @Component({\n * template: `\n * <button (click)=\"setNotification()\">Set Notification</button>\n * <button (click)=\"fav.reset()\">Reset Favicon</button>\n * <p>Current: {{ fav.current() }}</p>\n * `\n * })\n * export class FaviconDemo {\n * readonly fav = favicon();\n *\n * setNotification() {\n * this.fav.setEmoji('🔴');\n * }\n * }\n * ```\n */\nexport function favicon(options?: FaviconOptions): FaviconRef {\n const { runInContext } = setupContext(options?.injector, favicon);\n\n return runInContext(({ isServer }) => {\n if (isServer) {\n return {\n current: constSignal(''),\n original: constSignal(''),\n set: NOOP_FN,\n setEmoji: NOOP_FN,\n reset: NOOP_FN,\n };\n }\n\n const appBaseHref = inject(APP_BASE_HREF, { optional: true });\n const baseUrl = options?.baseUrl ?? appBaseHref ?? '';\n\n const getLinkElement = (): HTMLLinkElement => {\n let link = document.querySelector<HTMLLinkElement>(\n 'link[rel*=\"icon\"]:not([rel*=\"apple-touch-icon\"])'\n );\n\n if (!link) {\n link = document.createElement('link');\n link.rel = 'icon';\n document.head.appendChild(link);\n }\n\n return link;\n };\n\n const { href = '' } = getLinkElement();\n const current = signal(href);\n const original = signal(href);\n\n const set = (url: string) => {\n const fullUrl = baseUrl + url;\n const linkEl = getLinkElement();\n linkEl.href = fullUrl;\n current.set(fullUrl);\n };\n\n const setEmoji = (emoji: string) => {\n const canvas = document.createElement('canvas');\n canvas.width = 32;\n canvas.height = 32;\n\n const ctx = canvas.getContext('2d');\n if (!ctx) return;\n\n ctx.font = '28px serif';\n ctx.textAlign = 'center';\n ctx.textBaseline = 'middle';\n ctx.fillText(emoji, 16, 18);\n\n const dataUrl = canvas.toDataURL('image/png');\n const linkEl = getLinkElement();\n linkEl.href = dataUrl;\n current.set(dataUrl);\n };\n\n const reset = () => {\n const linkEl = getLinkElement();\n const originalHref = untracked(original);\n linkEl.href = originalHref;\n current.set(originalHref);\n };\n\n return {\n current: current.asReadonly(),\n original: original.asReadonly(),\n set,\n setEmoji,\n reset,\n };\n });\n}\n\nexport const FAVICON = /* @__PURE__ */ createToken(favicon);\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { signal, untracked, isSignal } from '@angular/core';
|
|
2
|
-
import { setupContext, NOOP_FN,
|
|
2
|
+
import { setupContext, NOOP_FN, isAcceptedFile } from '@signality/core/internal';
|
|
3
|
+
import { toValue } from '@signality/core/utilities';
|
|
3
4
|
import { watcher } from '@signality/core/reactivity/watcher';
|
|
4
5
|
|
|
5
6
|
/**
|
|
@@ -90,7 +91,7 @@ function fileDialog(options) {
|
|
|
90
91
|
inputEl.click();
|
|
91
92
|
});
|
|
92
93
|
};
|
|
93
|
-
const filters = [accept, multiple
|
|
94
|
+
const filters = [accept, multiple].filter(isSignal);
|
|
94
95
|
if (filters.length) {
|
|
95
96
|
watcher(filters, () => processFiles(files()));
|
|
96
97
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"signality-core-browser-file-dialog.mjs","sources":["../../../projects/core/browser/file-dialog/index.ts","../../../projects/core/browser/file-dialog/signality-core-browser-file-dialog.ts"],"sourcesContent":["import { isSignal, type Signal, signal, untracked, type WritableSignal } from '@angular/core';\nimport { isAcceptedFile, NOOP_FN, setupContext
|
|
1
|
+
{"version":3,"file":"signality-core-browser-file-dialog.mjs","sources":["../../../projects/core/browser/file-dialog/index.ts","../../../projects/core/browser/file-dialog/signality-core-browser-file-dialog.ts"],"sourcesContent":["import { isSignal, type Signal, signal, untracked, type WritableSignal } from '@angular/core';\nimport { isAcceptedFile, NOOP_FN, setupContext } from '@signality/core/internal';\nimport { toValue } from '@signality/core/utilities';\nimport type { MaybeSignal, WithInjector } from '@signality/core/types';\nimport { watcher } from '@signality/core/reactivity/watcher';\n\nexport interface FileDialogOptions extends WithInjector {\n /**\n * Whether to allow selecting multiple files.\n * When changed reactively, the current file list is re-filtered.\n * @default true\n */\n readonly multiple?: MaybeSignal<boolean>;\n\n /**\n * Comma-separated list of accepted file types, matching the native HTML\n * [`accept`](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Attributes/accept) attribute format.\n * Supports MIME types (`'image/png'`), wildcards (`'image/*'`), and file extensions (`'.pdf'`).\n * When changed reactively, the current file list is re-filtered.\n *\n * @default '*'\n * @see [accept attribute on MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Attributes/accept)\n */\n readonly accept?: MaybeSignal<string>;\n\n /**\n * Capture source for mobile devices: `'user'` (front camera) or `'environment'` (rear camera).\n * @see [capture attribute on MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/capture)\n */\n readonly capture?: MaybeSignal<string>;\n\n /**\n * Whether to select directories instead of files.\n *\n * Uses the `webkitdirectory` attribute, widely supported across all modern browsers.\n *\n * @see [webkitdirectory on MDN](https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/webkitdirectory)\n * @default false\n */\n readonly directory?: MaybeSignal<boolean>;\n\n /**\n * Custom validation predicate called for each selected file.\n * Return `true` to keep the file, `false` to reject it.\n *\n * When provided, `accept` only affects native browser filtering, not per-file validation.\n *\n * @example\n * ```typescript\n * fileDialog({\n * validator: (file) => file.size <= 5 * 1024 * 1024, // max 5 MB\n * });\n * ```\n */\n readonly validator?: (file: File) => boolean;\n\n /**\n * Callback invoked with files that were rejected during selection.\n * Useful for showing toast notifications or validation errors.\n *\n * @example\n * ```typescript\n * fileDialog({\n * accept: 'image/*',\n * onReject: (rejected) => {\n * rejected.forEach(f => toast.error(`${f.name} is not valid`));\n * },\n * });\n * ```\n */\n readonly onReject?: (files: File[]) => void;\n}\n\nexport interface FileDialogRef {\n /**\n * List of files selected via the file dialog.\n * A `WritableSignal` — can be reset externally (e.g. `files.set([])`).\n * Re-filtered automatically when `accept` or `multiple` options change reactively.\n *\n * @see [File API on MDN](https://developer.mozilla.org/en-US/docs/Web/API/File)\n */\n readonly files: WritableSignal<File[]>;\n\n /**\n * Open the native file picker dialog.\n */\n readonly open: () => void;\n}\n\n/**\n * Signal-based utility for programmatically opening the native file picker dialog.\n *\n * @param options - Optional configuration\n * @returns A {@link FileDialogRef} with `files` signal and `open` method\n *\n * @example\n * ```typescript\n * @Component({\n * template: `\n * <button (click)=\"fd.open()\">Select Files</button>\n * @for (file of fd.files(); track file.name) {\n * <p>{{ file.name }} ({{ file.size }} bytes)</p>\n * }\n * `\n * })\n * export class FileUpload {\n * readonly fd = fileDialog({ accept: 'image/*' });\n * }\n * ```\n */\nexport function fileDialog(options?: FileDialogOptions): FileDialogRef {\n const { runInContext } = setupContext(options?.injector, fileDialog);\n\n return runInContext(({ isBrowser }) => {\n if (!isBrowser) {\n return {\n files: signal<File[]>([]),\n open: NOOP_FN,\n };\n }\n\n const accept = options?.accept ?? '*';\n const multiple = options?.multiple ?? true;\n const capture = options?.capture ?? '';\n const directory = options?.directory ?? false;\n const validatorFn = options?.validator;\n const onReject = options?.onReject;\n\n const files = signal<File[]>([]);\n\n let inputEl: HTMLInputElement | null = null;\n\n const processFiles = (raw: File[]) => {\n const accepted: File[] = [];\n const rejected: File[] = [];\n const acceptValue = toValue(accept);\n const multipleValue = toValue(multiple);\n\n const isAccepted = validatorFn\n ? validatorFn\n : (file: File) => isAcceptedFile(file, acceptValue);\n\n for (const file of raw) {\n if (isAccepted(file)) {\n accepted.push(file);\n if (!multipleValue) {\n break;\n }\n } else {\n rejected.push(file);\n }\n }\n\n if (onReject && rejected.length > 0) {\n onReject(rejected);\n }\n\n files.set(accepted);\n };\n\n const createInput = (): HTMLInputElement => {\n const el = document.createElement('input');\n el.type = 'file';\n el.onchange = e => {\n const fileList = (e.currentTarget as HTMLInputElement).files;\n processFiles(fileList ? Array.from(fileList) : []);\n };\n return el;\n };\n\n const open = (): void => {\n untracked(() => {\n inputEl ??= createInput();\n inputEl.value = '';\n inputEl.multiple = toValue(multiple);\n inputEl.accept = toValue(accept);\n\n const directoriesOnly = toValue(directory);\n if (directoriesOnly) {\n inputEl.webkitdirectory = true;\n }\n\n const captureValue = toValue(capture);\n if (captureValue) {\n inputEl.capture = captureValue;\n }\n\n inputEl.click();\n });\n };\n\n const filters = [accept, multiple].filter(isSignal) as Signal<any>[];\n\n if (filters.length) {\n watcher(filters, () => processFiles(files()));\n }\n\n return {\n files,\n open,\n };\n });\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { signal, untracked } from '@angular/core';
|
|
2
|
-
import { setupContext, constSignal, NOOP_ASYNC_FN
|
|
2
|
+
import { setupContext, constSignal, NOOP_ASYNC_FN } from '@signality/core/internal';
|
|
3
|
+
import { toElement } from '@signality/core/utilities';
|
|
3
4
|
import { setupSync, listener } from '@signality/core/browser/listener';
|
|
4
5
|
|
|
5
6
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"signality-core-browser-fullscreen.mjs","sources":["../../../projects/core/browser/fullscreen/index.ts","../../../projects/core/browser/fullscreen/signality-core-browser-fullscreen.ts"],"sourcesContent":["import { signal, type Signal, untracked } from '@angular/core';\nimport { constSignal, NOOP_ASYNC_FN, setupContext
|
|
1
|
+
{"version":3,"file":"signality-core-browser-fullscreen.mjs","sources":["../../../projects/core/browser/fullscreen/index.ts","../../../projects/core/browser/fullscreen/signality-core-browser-fullscreen.ts"],"sourcesContent":["import { signal, type Signal, untracked } from '@angular/core';\nimport { constSignal, NOOP_ASYNC_FN, setupContext } from '@signality/core/internal';\nimport { toElement } from '@signality/core/utilities';\nimport type { MaybeElementSignal, WithInjector } from '@signality/core/types';\nimport { listener, setupSync } from '@signality/core/browser/listener';\n\nexport interface FullscreenOptions extends WithInjector {\n /**\n * Element to make fullscreen.\n * @default document.documentElement\n */\n readonly target?: MaybeElementSignal<Element>;\n}\n\nexport interface FullscreenRef {\n /**\n * Whether the Fullscreen API is supported in the current browser.\n *\n * @see [Fullscreen API browser compatibility on MDN](https://developer.mozilla.org/en-US/docs/Web/API/Fullscreen_API#browser_compatibility)\n */\n readonly isSupported: Signal<boolean>;\n\n /**\n * Whether the target element is currently displayed in fullscreen mode.\n *\n * @see [Document: fullscreenElement on MDN](https://developer.mozilla.org/en-US/docs/Web/API/Document/fullscreenElement)\n */\n readonly isActive: Signal<boolean>;\n\n /**\n * Enter fullscreen mode for the target element.\n *\n * @see [Element: requestFullscreen() on MDN](https://developer.mozilla.org/en-US/docs/Web/API/Element/requestFullscreen)\n */\n readonly enter: () => Promise<void>;\n\n /**\n * Exit fullscreen mode.\n *\n * @see [Document: exitFullscreen() on MDN](https://developer.mozilla.org/en-US/docs/Web/API/Document/exitFullscreen)\n */\n readonly exit: () => Promise<void>;\n\n /**\n * Toggle fullscreen mode — enters if inactive, exits if active.\n */\n readonly toggle: () => Promise<void>;\n}\n\n/**\n * Signal-based wrapper around the [Fullscreen API](https://developer.mozilla.org/en-US/docs/Web/API/Fullscreen_API).\n *\n * @param options - Optional configuration including target element and injector\n * @returns A {@link FullscreenRef} with `isSupported`, `isActive` signals and `enter`/`exit`/`toggle` methods\n *\n * @example\n * ```typescript\n * @Component({\n * template: `\n * @if (fs.isSupported()) {\n * <button (click)=\"fs.toggle()\">Toggle Fullscreen</button>\n * <p>Active: {{ fs.isActive() }}</p>\n * }\n * `\n * })\n * export class FullscreenDemo {\n * readonly fs = fullscreen();\n * }\n * ```\n *\n * @example\n * ```typescript\n * // Fullscreen a specific element\n * @Component({\n * template: `\n * <div #container>\n * <p>This content can go fullscreen</p>\n * <button (click)=\"fs.toggle()\">Toggle</button>\n * </div>\n * `\n * })\n * export class ElementFullscreen {\n * readonly container = viewChild<ElementRef>('container');\n * readonly fs = fullscreen({ target: this.container });\n * }\n * ```\n */\nexport function fullscreen(options?: FullscreenOptions): FullscreenRef {\n const { runInContext } = setupContext(options?.injector, fullscreen);\n\n return runInContext(({ isBrowser }) => {\n const isSupported = constSignal(\n isBrowser && 'fullscreenEnabled' in document && document.fullscreenEnabled\n );\n\n if (!isSupported()) {\n return {\n isSupported,\n isActive: constSignal(false),\n enter: NOOP_ASYNC_FN,\n exit: NOOP_ASYNC_FN,\n toggle: NOOP_ASYNC_FN,\n };\n }\n\n const target = options?.target ?? document.documentElement;\n\n const isActive = signal(false);\n\n const enter = async (): Promise<void> => {\n const el = toElement.untracked(target);\n\n if (el && document.fullscreenElement !== el) {\n try {\n await el.requestFullscreen();\n } catch (error) {\n if (ngDevMode) {\n console.warn(`[fullscreen] Failed to enter fullscreen mode.`, error);\n }\n }\n }\n };\n\n const exit = async (): Promise<void> => {\n const el = toElement.untracked(target);\n\n if (el && document.fullscreenElement === el) {\n try {\n await document.exitFullscreen();\n } catch (error) {\n if (ngDevMode) {\n console.warn(`[fullscreen] Failed to exit fullscreen mode.`, error);\n }\n }\n }\n };\n\n const toggle = async (): Promise<void> => {\n if (untracked(isActive)) {\n await exit();\n } else {\n await enter();\n }\n };\n\n setupSync(() => {\n listener(document, 'fullscreenchange', () => {\n const el = toElement.untracked(target);\n isActive.set(document.fullscreenElement != null && document.fullscreenElement === el);\n });\n });\n\n return {\n isSupported,\n isActive: isActive.asReadonly(),\n enter,\n exit,\n toggle,\n };\n });\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;;;AAiDA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqCG;AACG,SAAU,UAAU,CAAC,OAA2B,EAAA;AACpD,IAAA,MAAM,EAAE,YAAY,EAAE,GAAG,YAAY,CAAC,OAAO,EAAE,QAAQ,EAAE,UAAU,CAAC;AAEpE,IAAA,OAAO,YAAY,CAAC,CAAC,EAAE,SAAS,EAAE,KAAI;AACpC,QAAA,MAAM,WAAW,GAAG,WAAW,CAC7B,SAAS,IAAI,mBAAmB,IAAI,QAAQ,IAAI,QAAQ,CAAC,iBAAiB,CAC3E;AAED,QAAA,IAAI,CAAC,WAAW,EAAE,EAAE;YAClB,OAAO;gBACL,WAAW;AACX,gBAAA,QAAQ,EAAE,WAAW,CAAC,KAAK,CAAC;AAC5B,gBAAA,KAAK,EAAE,aAAa;AACpB,gBAAA,IAAI,EAAE,aAAa;AACnB,gBAAA,MAAM,EAAE,aAAa;aACtB;QACH;QAEA,MAAM,MAAM,GAAG,OAAO,EAAE,MAAM,IAAI,QAAQ,CAAC,eAAe;AAE1D,QAAA,MAAM,QAAQ,GAAG,MAAM,CAAC,KAAK,oDAAC;AAE9B,QAAA,MAAM,KAAK,GAAG,YAA0B;YACtC,MAAM,EAAE,GAAG,SAAS,CAAC,SAAS,CAAC,MAAM,CAAC;YAEtC,IAAI,EAAE,IAAI,QAAQ,CAAC,iBAAiB,KAAK,EAAE,EAAE;AAC3C,gBAAA,IAAI;AACF,oBAAA,MAAM,EAAE,CAAC,iBAAiB,EAAE;gBAC9B;gBAAE,OAAO,KAAK,EAAE;oBACd,IAAI,SAAS,EAAE;AACb,wBAAA,OAAO,CAAC,IAAI,CAAC,+CAA+C,EAAE,KAAK,CAAC;oBACtE;gBACF;YACF;AACF,QAAA,CAAC;AAED,QAAA,MAAM,IAAI,GAAG,YAA0B;YACrC,MAAM,EAAE,GAAG,SAAS,CAAC,SAAS,CAAC,MAAM,CAAC;YAEtC,IAAI,EAAE,IAAI,QAAQ,CAAC,iBAAiB,KAAK,EAAE,EAAE;AAC3C,gBAAA,IAAI;AACF,oBAAA,MAAM,QAAQ,CAAC,cAAc,EAAE;gBACjC;gBAAE,OAAO,KAAK,EAAE;oBACd,IAAI,SAAS,EAAE;AACb,wBAAA,OAAO,CAAC,IAAI,CAAC,8CAA8C,EAAE,KAAK,CAAC;oBACrE;gBACF;YACF;AACF,QAAA,CAAC;AAED,QAAA,MAAM,MAAM,GAAG,YAA0B;AACvC,YAAA,IAAI,SAAS,CAAC,QAAQ,CAAC,EAAE;gBACvB,MAAM,IAAI,EAAE;YACd;iBAAO;gBACL,MAAM,KAAK,EAAE;YACf;AACF,QAAA,CAAC;QAED,SAAS,CAAC,MAAK;AACb,YAAA,QAAQ,CAAC,QAAQ,EAAE,kBAAkB,EAAE,MAAK;gBAC1C,MAAM,EAAE,GAAG,SAAS,CAAC,SAAS,CAAC,MAAM,CAAC;AACtC,gBAAA,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,iBAAiB,IAAI,IAAI,IAAI,QAAQ,CAAC,iBAAiB,KAAK,EAAE,CAAC;AACvF,YAAA,CAAC,CAAC;AACJ,QAAA,CAAC,CAAC;QAEF,OAAO;YACL,WAAW;AACX,YAAA,QAAQ,EAAE,QAAQ,CAAC,UAAU,EAAE;YAC/B,KAAK;YACL,IAAI;YACJ,MAAM;SACP;AACH,IAAA,CAAC,CAAC;AACJ;;AChKA;;AAEG;;;;"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { untracked, afterRenderEffect, effect } from '@angular/core';
|
|
2
2
|
import { SIGNAL } from '@angular/core/primitives/signals';
|
|
3
|
-
import { setupContext, NOOP_EFFECT_REF,
|
|
3
|
+
import { setupContext, NOOP_EFFECT_REF, unrefElement, assertEventTarget } from '@signality/core/internal';
|
|
4
|
+
import { toValue } from '@signality/core/utilities';
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Signal-based wrapper around the [addEventListener](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener) method.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"signality-core-browser-listener.mjs","sources":["../../../projects/core/browser/listener/index.ts","../../../projects/core/browser/listener/signality-core-browser-listener.ts"],"sourcesContent":["import {\n afterRenderEffect,\n type CreateEffectOptions,\n effect,\n type EffectCleanupRegisterFn,\n type EffectRef,\n untracked,\n} from '@angular/core';\nimport { type BaseEffectNode, SIGNAL } from '@angular/core/primitives/signals';\nimport {\n assertEventTarget,\n NOOP_EFFECT_REF,\n setupContext,\n toValue,\n unrefElement,\n} from '@signality/core/internal';\nimport type { MaybeElementSignal, MaybeSignal, WithInjector } from '@signality/core/types';\n\nexport type ListenerOptions = WithInjector;\n\nexport interface ListenerRef {\n readonly destroy: () => void;\n}\n\nexport interface ListenerFunction {\n <E extends keyof WindowEventMap>(\n target: Window,\n event: MaybeSignal<E>,\n handler: (this: Window, e: WindowEventMap[E]) => any,\n options?: ListenerOptions\n ): ListenerRef;\n\n <E extends keyof DocumentEventMap>(\n target: Document,\n event: MaybeSignal<E>,\n handler: (this: Document, e: DocumentEventMap[E]) => any,\n options?: ListenerOptions\n ): ListenerRef;\n\n <E extends keyof ShadowRootEventMap>(\n target: MaybeSignal<ShadowRoot>,\n event: MaybeSignal<E>,\n handler: (this: ShadowRoot, e: ShadowRootEventMap[E]) => any,\n options?: ListenerOptions\n ): ListenerRef;\n\n <T extends HTMLElement, E extends keyof HTMLElementEventMap>(\n target: MaybeElementSignal<T>,\n event: MaybeSignal<E>,\n handler: (this: T, e: HTMLElementEventMap[E]) => any,\n options?: ListenerOptions\n ): ListenerRef;\n\n <T extends SVGElement, E extends keyof SVGElementEventMap>(\n target: MaybeElementSignal<T>,\n event: MaybeSignal<E>,\n handler: (this: T, e: SVGElementEventMap[E]) => any,\n options?: ListenerOptions\n ): ListenerRef;\n\n <Names extends string>(\n target: MaybeSignal<InferEventTarget<Names>>,\n event: MaybeSignal<Names>,\n handler: (e: Event) => void,\n options?: ListenerOptions\n ): ListenerRef;\n\n <EventType = Event>(\n target: MaybeSignal<EventTarget> | MaybeElementSignal<Element>,\n event: MaybeSignal<string>,\n handler: GeneralEventListener<EventType>,\n options?: ListenerOptions\n ): ListenerRef;\n\n readonly capture: ListenerFunction;\n readonly passive: ListenerFunction;\n readonly once: ListenerFunction;\n readonly stop: ListenerFunction;\n readonly prevent: ListenerFunction;\n readonly self: ListenerFunction;\n}\n\n/**\n * Signal-based wrapper around the [addEventListener](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener) method.\n *\n * @param target - Event target\n * @param event - Event type name\n * @param handler - Event handler function\n * @param options - Optional listener configuration\n * @returns A ListenerRef that can be used to destroy the listener\n *\n * @example\n * ```typescript\n * @Component({\n * template: `<button #btn>Click me</button>`,\n * })\n * export class ListenerDemo {\n * readonly btn = viewChild<ElementRef>('btn');\n *\n * constructor() {\n * listener.capture.prevent(this.btn, 'click', event => {\n * console.log('Button clicked!', event);\n * });\n * }\n * }\n * ```\n */\nexport const listener: ListenerFunction = createModifier({});\n\nlet isSyncSetupRequired = false;\n\n/**\n * By default, `listener()` registers event listeners after the render cycle completes\n * to ensure DOM elements exist. However, global targets (window, document, navigator.*, etc.)\n * are not tied to the render cycle. Use `setupSync()` to wrap listener calls when you need to prevent\n * race conditions where a global event is dispatched before Angular completes its scheduled rendering tasks.\n */\nexport function setupSync<T>(listenerFactoryExecFn: () => T): T {\n isSyncSetupRequired = true;\n\n try {\n return listenerFactoryExecFn();\n } finally {\n isSyncSetupRequired = false;\n }\n}\n\nfunction listenerImpl(applied: InternalListenerOptions, ...args: any[]): ListenerRef {\n const options = args[3] as ListenerOptions | undefined;\n\n const { runInContext } = setupContext(options?.injector, listenerImpl);\n\n return runInContext(({ isServer }) => {\n if (isServer) {\n return NOOP_EFFECT_REF;\n }\n\n const [maybeReactiveTarget, maybeReactiveEvent, rawHandler] = args;\n const { stop, prevent, self, ...nativeOptions } = applied;\n const hasModifiers = stop || prevent || self;\n\n const handler = hasModifiers\n ? function (this: any, event: Event) {\n if (self && event.target !== event.currentTarget) return;\n if (prevent) event.preventDefault();\n if (stop) event.stopPropagation();\n rawHandler.call(this, event);\n }\n : rawHandler;\n\n // Isolate event handler from signal dependency tracking.\n // Angular's template compiler does this via setActiveConsumer(null) in executeListenerWithErrorHandling.\n // Without this, events that fire during change detection (e.g. window 'blur' when the tab loses focus)\n // can trigger NG0600 if the handler writes to a signal while a reactive consumer is active.\n // See: https://github.com/angular/angular/issues/60143\n const untrackedHandler = function (this: any, event: Event) {\n untracked(() => handler.call(this, event));\n };\n\n const setupListener = (onCleanup: EffectCleanupRegisterFn) => {\n const raw = toValue(maybeReactiveTarget);\n const target = unrefElement(raw);\n const event = toValue(maybeReactiveEvent);\n\n if (!target) {\n return;\n }\n\n if (ngDevMode) {\n assertEventTarget(target, 'listener');\n }\n\n target.addEventListener(event, untrackedHandler, nativeOptions);\n\n onCleanup(() => {\n target.removeEventListener(event, untrackedHandler, nativeOptions);\n });\n };\n\n let effectRef: EffectRef;\n\n if (isSyncSetupRequired) {\n effectRef = syncEffect(setupListener);\n } else {\n effectRef = afterRenderEffect({ read: setupListener });\n }\n\n return { destroy: () => effectRef.destroy() };\n });\n}\n\nconst MODIFIERS = new Set<keyof InternalListenerOptions>([\n 'capture',\n 'passive',\n 'once',\n 'stop',\n 'prevent',\n 'self',\n]);\n\nfunction createModifier(applied: InternalListenerOptions): ListenerFunction {\n const modifierFn = ((...args: any[]) => {\n return listenerImpl(applied, ...args);\n }) as ListenerFunction;\n\n return new Proxy(modifierFn, {\n get(target, prop) {\n if (typeof prop !== 'string' || !MODIFIERS.has(prop as any)) {\n return target[prop as keyof typeof target];\n }\n\n if (applied[prop as keyof InternalListenerOptions]) {\n return target;\n }\n\n return createModifier({ ...applied, [prop]: true });\n },\n });\n}\n\nfunction syncEffect(\n effectFn: (onCleanup: EffectCleanupRegisterFn) => void,\n options?: CreateEffectOptions\n): EffectRef {\n const effectRef = effect(effectFn, options);\n const effectNode: BaseEffectNode = (effectRef as any)[SIGNAL];\n try {\n effectNode.run();\n } catch (error) {\n if (ngDevMode) {\n console.warn('[syncEffect] Failed to run effectFn synchronously', error);\n }\n }\n return effectRef;\n}\n\ninterface InternalListenerOptions {\n readonly capture?: boolean;\n readonly passive?: boolean;\n readonly once?: boolean;\n readonly stop?: boolean;\n readonly prevent?: boolean;\n readonly self?: boolean;\n}\n\ninterface InferEventTarget<Events> {\n readonly addEventListener: (event: Events, fn?: any, options?: any) => any;\n readonly removeEventListener: (event: Events, fn?: any, options?: any) => any;\n}\n\ninterface GeneralEventListener<E = Event> {\n (e: E): void;\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"signality-core-browser-listener.mjs","sources":["../../../projects/core/browser/listener/index.ts","../../../projects/core/browser/listener/signality-core-browser-listener.ts"],"sourcesContent":["import {\n afterRenderEffect,\n type CreateEffectOptions,\n effect,\n type EffectCleanupRegisterFn,\n type EffectRef,\n untracked,\n} from '@angular/core';\nimport { type BaseEffectNode, SIGNAL } from '@angular/core/primitives/signals';\nimport {\n assertEventTarget,\n NOOP_EFFECT_REF,\n setupContext,\n unrefElement,\n} from '@signality/core/internal';\nimport { toValue } from '@signality/core/utilities';\nimport type { MaybeElementSignal, MaybeSignal, WithInjector } from '@signality/core/types';\n\nexport type ListenerOptions = WithInjector;\n\nexport interface ListenerRef {\n readonly destroy: () => void;\n}\n\nexport interface ListenerFunction {\n <E extends keyof WindowEventMap>(\n target: Window,\n event: MaybeSignal<E>,\n handler: (this: Window, e: WindowEventMap[E]) => any,\n options?: ListenerOptions\n ): ListenerRef;\n\n <E extends keyof DocumentEventMap>(\n target: Document,\n event: MaybeSignal<E>,\n handler: (this: Document, e: DocumentEventMap[E]) => any,\n options?: ListenerOptions\n ): ListenerRef;\n\n <E extends keyof ShadowRootEventMap>(\n target: MaybeSignal<ShadowRoot>,\n event: MaybeSignal<E>,\n handler: (this: ShadowRoot, e: ShadowRootEventMap[E]) => any,\n options?: ListenerOptions\n ): ListenerRef;\n\n <T extends HTMLElement, E extends keyof HTMLElementEventMap>(\n target: MaybeElementSignal<T>,\n event: MaybeSignal<E>,\n handler: (this: T, e: HTMLElementEventMap[E]) => any,\n options?: ListenerOptions\n ): ListenerRef;\n\n <T extends SVGElement, E extends keyof SVGElementEventMap>(\n target: MaybeElementSignal<T>,\n event: MaybeSignal<E>,\n handler: (this: T, e: SVGElementEventMap[E]) => any,\n options?: ListenerOptions\n ): ListenerRef;\n\n <Names extends string>(\n target: MaybeSignal<InferEventTarget<Names>>,\n event: MaybeSignal<Names>,\n handler: (e: Event) => void,\n options?: ListenerOptions\n ): ListenerRef;\n\n <EventType = Event>(\n target: MaybeSignal<EventTarget> | MaybeElementSignal<Element>,\n event: MaybeSignal<string>,\n handler: GeneralEventListener<EventType>,\n options?: ListenerOptions\n ): ListenerRef;\n\n readonly capture: ListenerFunction;\n readonly passive: ListenerFunction;\n readonly once: ListenerFunction;\n readonly stop: ListenerFunction;\n readonly prevent: ListenerFunction;\n readonly self: ListenerFunction;\n}\n\n/**\n * Signal-based wrapper around the [addEventListener](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener) method.\n *\n * @param target - Event target\n * @param event - Event type name\n * @param handler - Event handler function\n * @param options - Optional listener configuration\n * @returns A ListenerRef that can be used to destroy the listener\n *\n * @example\n * ```typescript\n * @Component({\n * template: `<button #btn>Click me</button>`,\n * })\n * export class ListenerDemo {\n * readonly btn = viewChild<ElementRef>('btn');\n *\n * constructor() {\n * listener.capture.prevent(this.btn, 'click', event => {\n * console.log('Button clicked!', event);\n * });\n * }\n * }\n * ```\n */\nexport const listener: ListenerFunction = createModifier({});\n\nlet isSyncSetupRequired = false;\n\n/**\n * By default, `listener()` registers event listeners after the render cycle completes\n * to ensure DOM elements exist. However, global targets (window, document, navigator.*, etc.)\n * are not tied to the render cycle. Use `setupSync()` to wrap listener calls when you need to prevent\n * race conditions where a global event is dispatched before Angular completes its scheduled rendering tasks.\n */\nexport function setupSync<T>(listenerFactoryExecFn: () => T): T {\n isSyncSetupRequired = true;\n\n try {\n return listenerFactoryExecFn();\n } finally {\n isSyncSetupRequired = false;\n }\n}\n\nfunction listenerImpl(applied: InternalListenerOptions, ...args: any[]): ListenerRef {\n const options = args[3] as ListenerOptions | undefined;\n\n const { runInContext } = setupContext(options?.injector, listenerImpl);\n\n return runInContext(({ isServer }) => {\n if (isServer) {\n return NOOP_EFFECT_REF;\n }\n\n const [maybeReactiveTarget, maybeReactiveEvent, rawHandler] = args;\n const { stop, prevent, self, ...nativeOptions } = applied;\n const hasModifiers = stop || prevent || self;\n\n const handler = hasModifiers\n ? function (this: any, event: Event) {\n if (self && event.target !== event.currentTarget) return;\n if (prevent) event.preventDefault();\n if (stop) event.stopPropagation();\n rawHandler.call(this, event);\n }\n : rawHandler;\n\n // Isolate event handler from signal dependency tracking.\n // Angular's template compiler does this via setActiveConsumer(null) in executeListenerWithErrorHandling.\n // Without this, events that fire during change detection (e.g. window 'blur' when the tab loses focus)\n // can trigger NG0600 if the handler writes to a signal while a reactive consumer is active.\n // See: https://github.com/angular/angular/issues/60143\n const untrackedHandler = function (this: any, event: Event) {\n untracked(() => handler.call(this, event));\n };\n\n const setupListener = (onCleanup: EffectCleanupRegisterFn) => {\n const raw = toValue(maybeReactiveTarget);\n const target = unrefElement(raw);\n const event = toValue(maybeReactiveEvent);\n\n if (!target) {\n return;\n }\n\n if (ngDevMode) {\n assertEventTarget(target, 'listener');\n }\n\n target.addEventListener(event, untrackedHandler, nativeOptions);\n\n onCleanup(() => {\n target.removeEventListener(event, untrackedHandler, nativeOptions);\n });\n };\n\n let effectRef: EffectRef;\n\n if (isSyncSetupRequired) {\n effectRef = syncEffect(setupListener);\n } else {\n effectRef = afterRenderEffect({ read: setupListener });\n }\n\n return { destroy: () => effectRef.destroy() };\n });\n}\n\nconst MODIFIERS = new Set<keyof InternalListenerOptions>([\n 'capture',\n 'passive',\n 'once',\n 'stop',\n 'prevent',\n 'self',\n]);\n\nfunction createModifier(applied: InternalListenerOptions): ListenerFunction {\n const modifierFn = ((...args: any[]) => {\n return listenerImpl(applied, ...args);\n }) as ListenerFunction;\n\n return new Proxy(modifierFn, {\n get(target, prop) {\n if (typeof prop !== 'string' || !MODIFIERS.has(prop as any)) {\n return target[prop as keyof typeof target];\n }\n\n if (applied[prop as keyof InternalListenerOptions]) {\n return target;\n }\n\n return createModifier({ ...applied, [prop]: true });\n },\n });\n}\n\nfunction syncEffect(\n effectFn: (onCleanup: EffectCleanupRegisterFn) => void,\n options?: CreateEffectOptions\n): EffectRef {\n const effectRef = effect(effectFn, options);\n const effectNode: BaseEffectNode = (effectRef as any)[SIGNAL];\n try {\n effectNode.run();\n } catch (error) {\n if (ngDevMode) {\n console.warn('[syncEffect] Failed to run effectFn synchronously', error);\n }\n }\n return effectRef;\n}\n\ninterface InternalListenerOptions {\n readonly capture?: boolean;\n readonly passive?: boolean;\n readonly once?: boolean;\n readonly stop?: boolean;\n readonly prevent?: boolean;\n readonly self?: boolean;\n}\n\ninterface InferEventTarget<Events> {\n readonly addEventListener: (event: Events, fn?: any, options?: any) => any;\n readonly removeEventListener: (event: Events, fn?: any, options?: any) => any;\n}\n\ninterface GeneralEventListener<E = Event> {\n (e: E): void;\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { signal, effect } from '@angular/core';
|
|
2
|
-
import { setupContext, constSignal
|
|
2
|
+
import { setupContext, constSignal } from '@signality/core/internal';
|
|
3
|
+
import { toValue } from '@signality/core/utilities';
|
|
3
4
|
import { listener } from '@signality/core/browser/listener';
|
|
4
5
|
|
|
5
6
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"signality-core-browser-media-query.mjs","sources":["../../../projects/core/browser/media-query/index.ts","../../../projects/core/browser/media-query/signality-core-browser-media-query.ts"],"sourcesContent":["import { type CreateSignalOptions, effect, type Signal, signal } from '@angular/core';\nimport { constSignal, setupContext,
|
|
1
|
+
{"version":3,"file":"signality-core-browser-media-query.mjs","sources":["../../../projects/core/browser/media-query/index.ts","../../../projects/core/browser/media-query/signality-core-browser-media-query.ts"],"sourcesContent":["import { type CreateSignalOptions, effect, type Signal, signal } from '@angular/core';\nimport { constSignal, setupContext, type Union } from '@signality/core/internal';\nimport { toValue } from '@signality/core/utilities';\nimport type { MaybeSignal, WithInjector } from '@signality/core/types';\nimport { listener } from '@signality/core/browser/listener';\n\nexport interface MediaQueryOptions extends CreateSignalOptions<boolean>, WithInjector {\n /**\n * Initial value for SSR.\n * @default false\n */\n readonly initialValue?: boolean;\n}\n\n/**\n * Reactive wrapper around the [Window.matchMedia](https://developer.mozilla.org/en-US/docs/Web/API/Window/matchMedia) method.\n *\n * @param query - CSS media query string (can be a signal)\n * @param options - Optional configuration\n * @returns A signal that is true when the media query matches\n *\n * @example\n * ```typescript\n * @Component({\n * template: `\n * @if (prefersDark()) {\n * <p>Dark mode is preferred</p>\n * } @else {\n * <p>Light mode is preferred</p>\n * }\n * `\n * })\n * export class ThemeDemo {\n * readonly prefersDark = mediaQuery('(prefers-color-scheme: dark)');\n * }\n * ```\n */\nexport function mediaQuery(\n query: MaybeSignal<Union<MediaQueryFeature, string>>,\n options?: MediaQueryOptions\n): Signal<boolean> {\n const { runInContext } = setupContext(options?.injector, mediaQuery);\n\n return runInContext(({ isServer, injector }) => {\n if (isServer) {\n return constSignal(!!options?.initialValue);\n }\n\n const matches = signal(!!options?.initialValue, options);\n\n effect(onCleanup => {\n const queryString = toValue(query);\n\n if (!queryString) {\n matches.set(false);\n return;\n }\n\n const mediaQueryList = window.matchMedia(queryString);\n\n matches.set(mediaQueryList.matches);\n\n const changeListener = listener(\n mediaQueryList,\n 'change',\n (e: MediaQueryListEvent) => matches.set(e.matches),\n { injector }\n );\n\n onCleanup(changeListener.destroy);\n });\n\n return matches.asReadonly();\n });\n}\n\ntype MediaQueryFeature =\n | `(any-hover: ${'none' | 'hover'})`\n | `(any-pointer: ${'none' | 'coarse' | 'fine'})`\n | `(aspect-ratio: ${string})`\n | `(color: ${string})`\n | `(color-gamut: ${'srgb' | 'p3' | 'rec2020'})`\n | `(color-index: ${string})`\n | `(device-aspect-ratio: ${string})`\n | `(device-height: ${string})`\n | `(device-posture: ${'flat' | 'folded' | 'continuous'})`\n | `(device-width: ${string})`\n | `(display-mode: ${\n | 'fullscreen'\n | 'standalone'\n | 'minimal-ui'\n | 'browser'\n | 'picture-in-picture'\n | 'window-controls-overlay'})`\n | `(dynamic-range: ${'standard' | 'high'})`\n | `(forced-colors: ${'none' | 'active'})`\n | `(grid: ${'0' | '1'})`\n | `(height: ${string})`\n | `(hover: ${'none' | 'hover'})`\n | `(inverted-colors: ${'none' | 'inverted'})`\n | `(monochrome: ${string})`\n | `(orientation: ${'portrait' | 'landscape'})`\n | `(overflow-block: ${'none' | 'scroll' | 'optional-paged' | 'paged'})`\n | `(overflow-inline: ${'none' | 'scroll'})`\n | `(pointer: ${'none' | 'coarse' | 'fine'})`\n | `(prefers-color-scheme: ${'light' | 'dark' | 'no-preference'})`\n | `(prefers-contrast: ${'no-preference' | 'more' | 'less' | 'custom'})`\n | `(prefers-reduced-data: ${'no-preference' | 'reduce'})`\n | `(prefers-reduced-motion: ${'no-preference' | 'reduce'})`\n | `(prefers-reduced-transparency: ${'no-preference' | 'reduce'})`\n | `(resolution: ${string})`\n | `(scan: ${'progressive' | 'interlace'})`\n | `(scripting: ${'none' | 'initial-only' | 'enabled'})`\n | `(shape: ${'rect' | 'round'})`\n | `(update: ${'none' | 'slow' | 'fast'})`\n | `(video-dynamic-range: ${'standard' | 'high'})`\n | `(width: ${string})`\n | `(min-width: ${string})`\n | `(max-width: ${string})`\n | `(min-height: ${string})`\n | `(max-height: ${string})`\n | `(min-aspect-ratio: ${string})`\n | `(max-aspect-ratio: ${string})`\n | `(min-resolution: ${string})`\n | `(max-resolution: ${string})`\n | `(min-color: ${string})`\n | `(max-color: ${string})`\n | `(min-color-index: ${string})`\n | `(max-color-index: ${string})`\n | `(min-monochrome: ${string})`\n | `(max-monochrome: ${string})`;\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;;;AAcA;;;;;;;;;;;;;;;;;;;;;;AAsBG;AACG,SAAU,UAAU,CACxB,KAAoD,EACpD,OAA2B,EAAA;AAE3B,IAAA,MAAM,EAAE,YAAY,EAAE,GAAG,YAAY,CAAC,OAAO,EAAE,QAAQ,EAAE,UAAU,CAAC;IAEpE,OAAO,YAAY,CAAC,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,KAAI;QAC7C,IAAI,QAAQ,EAAE;YACZ,OAAO,WAAW,CAAC,CAAC,CAAC,OAAO,EAAE,YAAY,CAAC;QAC7C;AAEA,QAAA,MAAM,OAAO,GAAG,MAAM,CAAC,CAAC,CAAC,OAAO,EAAE,YAAY,EAAE,OAAO,CAAC;QAExD,MAAM,CAAC,SAAS,IAAG;AACjB,YAAA,MAAM,WAAW,GAAG,OAAO,CAAC,KAAK,CAAC;YAElC,IAAI,CAAC,WAAW,EAAE;AAChB,gBAAA,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC;gBAClB;YACF;YAEA,MAAM,cAAc,GAAG,MAAM,CAAC,UAAU,CAAC,WAAW,CAAC;AAErD,YAAA,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,OAAO,CAAC;YAEnC,MAAM,cAAc,GAAG,QAAQ,CAC7B,cAAc,EACd,QAAQ,EACR,CAAC,CAAsB,KAAK,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,EAClD,EAAE,QAAQ,EAAE,CACb;AAED,YAAA,SAAS,CAAC,cAAc,CAAC,OAAO,CAAC;AACnC,QAAA,CAAC,CAAC;AAEF,QAAA,OAAO,OAAO,CAAC,UAAU,EAAE;AAC7B,IAAA,CAAC,CAAC;AACJ;;AC1EA;;AAEG;;;;"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { signal, untracked } from '@angular/core';
|
|
2
|
-
import { setupContext, constSignal, NOOP_ASYNC_FN,
|
|
2
|
+
import { setupContext, constSignal, NOOP_ASYNC_FN, assertElement, getPipElement } from '@signality/core/internal';
|
|
3
|
+
import { toElement } from '@signality/core/utilities';
|
|
3
4
|
import { listener } from '@signality/core/browser/listener';
|
|
4
5
|
import { onDisconnect } from '@signality/core/elements/on-disconnect';
|
|
5
6
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"signality-core-browser-picture-in-picture.mjs","sources":["../../../projects/core/browser/picture-in-picture/index.ts","../../../projects/core/browser/picture-in-picture/signality-core-browser-picture-in-picture.ts"],"sourcesContent":["import { signal, type Signal, untracked } from '@angular/core';\nimport {\n assertElement,\n constSignal,\n getPipElement,\n NOOP_ASYNC_FN,\n setupContext,\n
|
|
1
|
+
{"version":3,"file":"signality-core-browser-picture-in-picture.mjs","sources":["../../../projects/core/browser/picture-in-picture/index.ts","../../../projects/core/browser/picture-in-picture/signality-core-browser-picture-in-picture.ts"],"sourcesContent":["import { signal, type Signal, untracked } from '@angular/core';\nimport {\n assertElement,\n constSignal,\n getPipElement,\n NOOP_ASYNC_FN,\n setupContext,\n} from '@signality/core/internal';\nimport { toElement } from '@signality/core/utilities';\nimport type { MaybeElementSignal, WithInjector } from '@signality/core/types';\nimport { listener } from '@signality/core/browser/listener';\nimport { onDisconnect } from '@signality/core/elements/on-disconnect';\n\nexport type PictureInPictureOptions = WithInjector;\n\nexport interface PictureInPictureRef {\n /**\n * Whether the Picture-in-Picture API is supported in the current browser.\n *\n * @see [Picture-in-Picture API browser compatibility on MDN](https://developer.mozilla.org/en-US/docs/Web/API/Picture-in-Picture_API#browser_compatibility)\n */\n readonly isSupported: Signal<boolean>;\n\n /**\n * Whether the target video element is currently displayed in Picture-in-Picture mode.\n *\n * @see [Document: pictureInPictureElement on MDN](https://developer.mozilla.org/en-US/docs/Web/API/Document/pictureInPictureElement)\n */\n readonly isActive: Signal<boolean>;\n\n /**\n * Enter Picture-in-Picture mode for the target video element.\n *\n * @throws {DOMException} `'NotAllowedError'` — the document is not allowed to use PiP\n * @throws {DOMException} `'InvalidStateError'` — the video element has `disablePictureInPicture` attribute\n * @throws {DOMException} `'NotSupportedError'` — Picture-in-Picture is not supported\n *\n * @see [HTMLVideoElement: requestPictureInPicture() on MDN](https://developer.mozilla.org/en-US/docs/Web/API/HTMLVideoElement/requestPictureInPicture)\n */\n readonly enter: () => Promise<void>;\n\n /**\n * Exit Picture-in-Picture mode.\n *\n * @see [Document: exitPictureInPicture() on MDN](https://developer.mozilla.org/en-US/docs/Web/API/Document/exitPictureInPicture)\n */\n readonly exit: () => Promise<void>;\n\n /**\n * Toggle Picture-in-Picture mode — enters if inactive, exits if active.\n */\n readonly toggle: () => Promise<void>;\n}\n\n/**\n * Signal-based wrapper around the [Picture-in-Picture API](https://developer.mozilla.org/en-US/docs/Web/API/Picture-in-Picture_API).\n *\n * Automatically exits Picture-in-Picture when the target element is disconnected from the DOM.\n *\n * @param target - Video element\n * @param options - Optional configuration\n * @returns A {@link PictureInPictureRef} with `isSupported`, `isActive` signals and `enter`/`exit`/`toggle` methods\n *\n * @example\n * ```typescript\n * @Component({\n * template: `\n * @if (pip.isSupported()) {\n * <video #video src=\"video.mp4\"></video>\n * <button (click)=\"pip.toggle()\">Toggle PiP</button>\n * <p>Active: {{ pip.isActive() }}</p>\n * }\n * `\n * })\n * export class PiPDemo {\n * readonly video = viewChild<HTMLVideoElement>('video');\n * readonly pip = pictureInPicture(this.video);\n * }\n * ```\n */\nexport function pictureInPicture(\n target: MaybeElementSignal<HTMLVideoElement>,\n options?: PictureInPictureOptions\n): PictureInPictureRef {\n const { runInContext } = setupContext(options?.injector, pictureInPicture);\n\n return runInContext(({ isBrowser }) => {\n const isSupported = constSignal(\n isBrowser && 'pictureInPictureEnabled' in document && document.pictureInPictureEnabled\n );\n\n if (!isSupported()) {\n return {\n isSupported,\n isActive: constSignal(false),\n enter: NOOP_ASYNC_FN,\n exit: NOOP_ASYNC_FN,\n toggle: NOOP_ASYNC_FN,\n };\n }\n\n const isActive = signal(false);\n\n const enter = async (): Promise<void> => {\n const targetEl = toElement.untracked(target);\n assertElement(targetEl, 'pictureInPicture');\n await targetEl.requestPictureInPicture();\n };\n\n const exit = async (): Promise<void> => {\n const targetEl = toElement.untracked(target);\n const pipEl = getPipElement(document);\n\n if (targetEl === pipEl) {\n try {\n await document.exitPictureInPicture();\n } catch (error) {\n if (ngDevMode) {\n console.warn(`[pictureInPicture] Failed to exit Picture-in-Picture mode.`, error);\n }\n }\n }\n };\n\n const toggle = async (): Promise<void> => {\n if (untracked(isActive)) {\n await exit();\n } else {\n await enter();\n }\n };\n\n listener(target, 'enterpictureinpicture', () => isActive.set(true));\n listener(target, 'leavepictureinpicture', () => isActive.set(false));\n\n onDisconnect(target, async targetEl => {\n const pipEl = getPipElement(document);\n\n if (pipEl && targetEl === pipEl) {\n try {\n await document.exitPictureInPicture();\n } catch (error) {\n if (ngDevMode) {\n console.warn(\n `[pictureInPicture] Failed to exit Picture-in-Picture mode on disconnect.`,\n error\n );\n }\n }\n isActive.set(false);\n }\n });\n\n return {\n isSupported,\n isActive,\n enter,\n exit,\n toggle,\n };\n });\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;;;;AAsDA;;;;;;;;;;;;;;;;;;;;;;;;;AAyBG;AACG,SAAU,gBAAgB,CAC9B,MAA4C,EAC5C,OAAiC,EAAA;AAEjC,IAAA,MAAM,EAAE,YAAY,EAAE,GAAG,YAAY,CAAC,OAAO,EAAE,QAAQ,EAAE,gBAAgB,CAAC;AAE1E,IAAA,OAAO,YAAY,CAAC,CAAC,EAAE,SAAS,EAAE,KAAI;AACpC,QAAA,MAAM,WAAW,GAAG,WAAW,CAC7B,SAAS,IAAI,yBAAyB,IAAI,QAAQ,IAAI,QAAQ,CAAC,uBAAuB,CACvF;AAED,QAAA,IAAI,CAAC,WAAW,EAAE,EAAE;YAClB,OAAO;gBACL,WAAW;AACX,gBAAA,QAAQ,EAAE,WAAW,CAAC,KAAK,CAAC;AAC5B,gBAAA,KAAK,EAAE,aAAa;AACpB,gBAAA,IAAI,EAAE,aAAa;AACnB,gBAAA,MAAM,EAAE,aAAa;aACtB;QACH;AAEA,QAAA,MAAM,QAAQ,GAAG,MAAM,CAAC,KAAK,oDAAC;AAE9B,QAAA,MAAM,KAAK,GAAG,YAA0B;YACtC,MAAM,QAAQ,GAAG,SAAS,CAAC,SAAS,CAAC,MAAM,CAAC;AAC5C,YAAA,aAAa,CAAC,QAAQ,EAAE,kBAAkB,CAAC;AAC3C,YAAA,MAAM,QAAQ,CAAC,uBAAuB,EAAE;AAC1C,QAAA,CAAC;AAED,QAAA,MAAM,IAAI,GAAG,YAA0B;YACrC,MAAM,QAAQ,GAAG,SAAS,CAAC,SAAS,CAAC,MAAM,CAAC;AAC5C,YAAA,MAAM,KAAK,GAAG,aAAa,CAAC,QAAQ,CAAC;AAErC,YAAA,IAAI,QAAQ,KAAK,KAAK,EAAE;AACtB,gBAAA,IAAI;AACF,oBAAA,MAAM,QAAQ,CAAC,oBAAoB,EAAE;gBACvC;gBAAE,OAAO,KAAK,EAAE;oBACd,IAAI,SAAS,EAAE;AACb,wBAAA,OAAO,CAAC,IAAI,CAAC,4DAA4D,EAAE,KAAK,CAAC;oBACnF;gBACF;YACF;AACF,QAAA,CAAC;AAED,QAAA,MAAM,MAAM,GAAG,YAA0B;AACvC,YAAA,IAAI,SAAS,CAAC,QAAQ,CAAC,EAAE;gBACvB,MAAM,IAAI,EAAE;YACd;iBAAO;gBACL,MAAM,KAAK,EAAE;YACf;AACF,QAAA,CAAC;AAED,QAAA,QAAQ,CAAC,MAAM,EAAE,uBAAuB,EAAE,MAAM,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;AACnE,QAAA,QAAQ,CAAC,MAAM,EAAE,uBAAuB,EAAE,MAAM,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;AAEpE,QAAA,YAAY,CAAC,MAAM,EAAE,OAAM,QAAQ,KAAG;AACpC,YAAA,MAAM,KAAK,GAAG,aAAa,CAAC,QAAQ,CAAC;AAErC,YAAA,IAAI,KAAK,IAAI,QAAQ,KAAK,KAAK,EAAE;AAC/B,gBAAA,IAAI;AACF,oBAAA,MAAM,QAAQ,CAAC,oBAAoB,EAAE;gBACvC;gBAAE,OAAO,KAAK,EAAE;oBACd,IAAI,SAAS,EAAE;AACb,wBAAA,OAAO,CAAC,IAAI,CACV,0EAA0E,EAC1E,KAAK,CACN;oBACH;gBACF;AACA,gBAAA,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC;YACrB;AACF,QAAA,CAAC,CAAC;QAEF,OAAO;YACL,WAAW;YACX,QAAQ;YACR,KAAK;YACL,IAAI;YACJ,MAAM;SACP;AACH,IAAA,CAAC,CAAC;AACJ;;ACjKA;;AAEG;;;;"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { signal, untracked, isSignal } from '@angular/core';
|
|
2
|
-
import { setupContext, constSignal, NOOP_FN
|
|
2
|
+
import { setupContext, constSignal, NOOP_FN } from '@signality/core/internal';
|
|
3
|
+
import { toValue } from '@signality/core/utilities';
|
|
3
4
|
import { watcher } from '@signality/core/reactivity/watcher';
|
|
4
5
|
import { permissionState } from '@signality/core/browser/permission-state';
|
|
5
6
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"signality-core-browser-speech-recognition.mjs","sources":["../../../projects/core/browser/speech-recognition/index.ts","../../../projects/core/browser/speech-recognition/signality-core-browser-speech-recognition.ts"],"sourcesContent":["import { isSignal, type Signal, signal, untracked } from '@angular/core';\nimport { constSignal, NOOP_FN, setupContext, toValue } from '@signality/core/internal';\nimport type { MaybeSignal, WithInjector } from '@signality/core/types';\nimport { watcher } from '@signality/core/reactivity/watcher';\nimport { permissionState } from '@signality/core/browser/permission-state';\n\nexport interface SpeechRecognitionOptions extends WithInjector {\n /**\n * BCP 47 language tag for recognition (e.g. `'en-US'`).\n *\n * @default 'en-US'\n * @see [SpeechRecognition: lang on MDN](https://developer.mozilla.org/en-US/docs/Web/API/SpeechRecognition/lang)\n */\n readonly lang?: MaybeSignal<string>;\n\n /**\n * Whether to return interim (in-progress) results alongside final ones.\n *\n * @default false\n * @see [SpeechRecognition: interimResults on MDN](https://developer.mozilla.org/en-US/docs/Web/API/SpeechRecognition/interimResults)\n */\n readonly interimResults?: boolean;\n\n /**\n * Whether recognition continues after the user stops speaking.\n *\n * @default false\n * @see [SpeechRecognition: continuous on MDN](https://developer.mozilla.org/en-US/docs/Web/API/SpeechRecognition/continuous)\n */\n readonly continuous?: boolean;\n\n /**\n * Maximum number of alternative recognition results per utterance.\n *\n * @default 1\n * @see [SpeechRecognition: maxAlternatives on MDN](https://developer.mozilla.org/en-US/docs/Web/API/SpeechRecognition/maxAlternatives)\n */\n readonly maxAlternatives?: number;\n}\n\nexport interface SpeechRecognitionRef {\n /**\n * Whether the Speech Recognition API is supported in the current browser.\n *\n * @see [SpeechRecognition browser compatibility on MDN](https://developer.mozilla.org/en-US/docs/Web/API/SpeechRecognition#browser_compatibility)\n */\n readonly isSupported: Signal<boolean>;\n\n /**\n * Whether speech recognition is currently active and listening.\n */\n readonly isListening: Signal<boolean>;\n\n /**\n * Accumulated final transcript text.\n *\n * @see [SpeechRecognitionResult on MDN](https://developer.mozilla.org/en-US/docs/Web/API/SpeechRecognitionResult)\n */\n readonly text: Signal<string>;\n\n /**\n * In-progress interim transcript. Only populated when `interimResults` is `true`.\n *\n * @see [SpeechRecognition: interimResults on MDN](https://developer.mozilla.org/en-US/docs/Web/API/SpeechRecognition/interimResults)\n */\n readonly interimText: Signal<string>;\n\n /**\n * The last recognition error, or `null` if no error occurred.\n *\n * @see [SpeechRecognitionErrorEvent on MDN](https://developer.mozilla.org/en-US/docs/Web/API/SpeechRecognitionErrorEvent)\n */\n readonly error: Signal<SpeechRecognitionErrorEvent | Error | null>;\n\n /**\n * Start listening for speech input.\n *\n * @see [SpeechRecognition: start() on MDN](https://developer.mozilla.org/en-US/docs/Web/API/SpeechRecognition/start)\n */\n readonly start: () => void;\n\n /**\n * Stop listening and return any remaining results.\n *\n * @see [SpeechRecognition: stop() on MDN](https://developer.mozilla.org/en-US/docs/Web/API/SpeechRecognition/stop)\n */\n readonly stop: () => void;\n\n /**\n * Abort recognition immediately without returning results.\n *\n * @see [SpeechRecognition: abort() on MDN](https://developer.mozilla.org/en-US/docs/Web/API/SpeechRecognition/abort)\n */\n readonly abort: () => void;\n}\n\n/**\n * Signal-based wrapper around the [Speech Recognition API](https://developer.mozilla.org/en-US/docs/Web/API/SpeechRecognition).\n *\n * @param options - Optional configuration\n * @returns A SpeechRecognitionRef with isSupported, isListening, text, interimText, error signals and control methods\n *\n * @example\n * ```typescript\n * @Component({\n * template: `\n * @if (recognition.isSupported()) {\n * <button (click)=\"toggleRecognition()\">\n * {{ recognition.isListening() ? 'Stop' : 'Start' }} Recording\n * </button>\n * <p>{{ recognition.text() }}</p>\n * @if (recognition.interimText()) {\n * <p><em>{{ recognition.interimText() }}</em></p>\n * }\n * }\n * `\n * })\n * export class SpeechComponent {\n * readonly recognition = speechRecognition();\n *\n * toggleRecognition() {\n * if (this.recognition.isListening()) {\n * this.recognition.stop();\n * } else {\n * this.recognition.start();\n * }\n * }\n * }\n * ```\n */\nexport function speechRecognition(options?: SpeechRecognitionOptions): SpeechRecognitionRef {\n const { runInContext } = setupContext(options?.injector, speechRecognition);\n\n return runInContext(({ isBrowser, onCleanup }) => {\n const isSupported = constSignal(\n isBrowser && ('SpeechRecognition' in window || 'webkitSpeechRecognition' in window)\n );\n\n if (!isSupported()) {\n return {\n isSupported,\n isListening: constSignal(false),\n text: constSignal(''),\n interimText: constSignal(''),\n error: constSignal(null),\n start: NOOP_FN,\n stop: NOOP_FN,\n abort: NOOP_FN,\n };\n }\n\n const SpeechRecognitionClass =\n (window as any).SpeechRecognition || (window as any).webkitSpeechRecognition;\n\n const recognition: SpeechRecognition = new SpeechRecognitionClass();\n\n const {\n lang = 'en-US',\n interimResults = false,\n continuous = false,\n maxAlternatives = 1,\n } = options ?? {};\n\n recognition.lang = toValue(lang);\n recognition.continuous = continuous;\n recognition.interimResults = interimResults;\n recognition.maxAlternatives = maxAlternatives;\n\n const isListening = signal(false);\n const text = signal('');\n const interimText = signal('');\n const error = signal<SpeechRecognitionErrorEvent | Error | null>(null);\n\n const handleResult = (event: SpeechRecognitionEvent) => {\n let finalTranscript = '';\n let interimTranscript = '';\n\n for (let i = event.resultIndex; i < event.results.length; i++) {\n const result = event.results[i];\n const transcript = result[0]?.transcript || result.item(0)?.transcript || '';\n\n if (result.isFinal) {\n finalTranscript += transcript;\n } else {\n interimTranscript += transcript;\n }\n }\n\n if (finalTranscript) {\n text.update(t => (t ? t + ' ' : '') + finalTranscript);\n interimText.set('');\n } else if (interimTranscript) {\n interimText.set(interimTranscript);\n }\n };\n\n const handleError = (event: SpeechRecognitionErrorEvent) => {\n error.set(event);\n isListening.set(false);\n };\n\n const handleStart = () => {\n isListening.set(true);\n error.set(null);\n\n if (!continuous) {\n text.set('');\n interimText.set('');\n }\n };\n\n const handleEnd = () => {\n isListening.set(false);\n recognition.lang = toValue(lang);\n };\n\n recognition.onstart = handleStart;\n recognition.onend = handleEnd;\n recognition.onerror = handleError;\n recognition.onresult = handleResult;\n\n const start = () => {\n try {\n if (!untracked(isListening)) {\n recognition.start();\n }\n } catch (err) {\n error.set(err as Error);\n }\n };\n\n const stop = () => {\n if (untracked(isListening)) {\n recognition.stop();\n }\n };\n\n const abort = () => {\n if (untracked(isListening)) {\n recognition.abort();\n }\n };\n\n onCleanup(abort);\n\n if (isSignal(lang)) {\n watcher(lang, newLang => {\n if (!isListening()) {\n recognition.lang = newLang;\n }\n });\n }\n\n watcher(permissionState('microphone'), state => {\n if (state === 'denied') {\n abort();\n }\n });\n\n return {\n isSupported,\n isListening: isListening.asReadonly(),\n text: text.asReadonly(),\n interimText: interimText.asReadonly(),\n error: error.asReadonly(),\n start,\n stop,\n abort,\n };\n });\n}\n\n/**\n * Local type definitions for Web Speech API (Speech Recognition).\n *\n * @remarks\n * External `@types/dom-speech-recognition` package may conflict with user's other libraries\n * or become outdated. For better DX, we define minimal required interfaces locally\n * without polluting the global namespace with experimental APIs.\n */\ntype SpeechRecognitionErrorCode =\n | 'aborted'\n | 'audio-capture'\n | 'bad-grammar'\n | 'language-not-supported'\n | 'network'\n | 'no-speech'\n | 'not-allowed'\n | 'service-not-allowed';\n\ninterface SpeechRecognitionAlternative {\n readonly transcript: string;\n readonly confidence: number;\n}\n\ninterface SpeechRecognitionResult {\n readonly isFinal: boolean;\n readonly length: number;\n item(index: number): SpeechRecognitionAlternative;\n [index: number]: SpeechRecognitionAlternative;\n}\n\ninterface SpeechRecognitionResultList {\n readonly length: number;\n item(index: number): SpeechRecognitionResult;\n [index: number]: SpeechRecognitionResult;\n}\n\ninterface SpeechGrammar {\n src: string;\n weight: number;\n}\n\ninterface SpeechGrammarList {\n readonly length: number;\n addFromString(string: string, weight?: number): void;\n addFromURI(src: string, weight?: number): void;\n item(index: number): SpeechGrammar;\n [index: number]: SpeechGrammar;\n}\n\ninterface SpeechRecognitionEvent extends Event {\n readonly resultIndex: number;\n readonly results: SpeechRecognitionResultList;\n}\n\ninterface SpeechRecognitionErrorEvent extends Event {\n readonly error: SpeechRecognitionErrorCode;\n readonly message: string;\n}\n\ninterface SpeechRecognition extends EventTarget {\n continuous: boolean;\n grammars: SpeechGrammarList;\n interimResults: boolean;\n lang: string;\n maxAlternatives: number;\n onaudioend: ((this: SpeechRecognition, ev: Event) => void) | null;\n onaudiostart: ((this: SpeechRecognition, ev: Event) => void) | null;\n onend: ((this: SpeechRecognition, ev: Event) => void) | null;\n onerror: ((this: SpeechRecognition, ev: SpeechRecognitionErrorEvent) => void) | null;\n onnomatch: ((this: SpeechRecognition, ev: SpeechRecognitionEvent) => void) | null;\n onresult: ((this: SpeechRecognition, ev: SpeechRecognitionEvent) => void) | null;\n onsoundend: ((this: SpeechRecognition, ev: Event) => void) | null;\n onsoundstart: ((this: SpeechRecognition, ev: Event) => void) | null;\n onspeechend: ((this: SpeechRecognition, ev: Event) => void) | null;\n onspeechstart: ((this: SpeechRecognition, ev: Event) => void) | null;\n onstart: ((this: SpeechRecognition, ev: Event) => void) | null;\n abort(): void;\n start(audioTrack?: MediaStreamTrack): void;\n stop(): void;\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"signality-core-browser-speech-recognition.mjs","sources":["../../../projects/core/browser/speech-recognition/index.ts","../../../projects/core/browser/speech-recognition/signality-core-browser-speech-recognition.ts"],"sourcesContent":["import { isSignal, type Signal, signal, untracked } from '@angular/core';\nimport { constSignal, NOOP_FN, setupContext } from '@signality/core/internal';\nimport { toValue } from '@signality/core/utilities';\nimport type { MaybeSignal, WithInjector } from '@signality/core/types';\nimport { watcher } from '@signality/core/reactivity/watcher';\nimport { permissionState } from '@signality/core/browser/permission-state';\n\nexport interface SpeechRecognitionOptions extends WithInjector {\n /**\n * BCP 47 language tag for recognition (e.g. `'en-US'`).\n *\n * @default 'en-US'\n * @see [SpeechRecognition: lang on MDN](https://developer.mozilla.org/en-US/docs/Web/API/SpeechRecognition/lang)\n */\n readonly lang?: MaybeSignal<string>;\n\n /**\n * Whether to return interim (in-progress) results alongside final ones.\n *\n * @default false\n * @see [SpeechRecognition: interimResults on MDN](https://developer.mozilla.org/en-US/docs/Web/API/SpeechRecognition/interimResults)\n */\n readonly interimResults?: boolean;\n\n /**\n * Whether recognition continues after the user stops speaking.\n *\n * @default false\n * @see [SpeechRecognition: continuous on MDN](https://developer.mozilla.org/en-US/docs/Web/API/SpeechRecognition/continuous)\n */\n readonly continuous?: boolean;\n\n /**\n * Maximum number of alternative recognition results per utterance.\n *\n * @default 1\n * @see [SpeechRecognition: maxAlternatives on MDN](https://developer.mozilla.org/en-US/docs/Web/API/SpeechRecognition/maxAlternatives)\n */\n readonly maxAlternatives?: number;\n}\n\nexport interface SpeechRecognitionRef {\n /**\n * Whether the Speech Recognition API is supported in the current browser.\n *\n * @see [SpeechRecognition browser compatibility on MDN](https://developer.mozilla.org/en-US/docs/Web/API/SpeechRecognition#browser_compatibility)\n */\n readonly isSupported: Signal<boolean>;\n\n /**\n * Whether speech recognition is currently active and listening.\n */\n readonly isListening: Signal<boolean>;\n\n /**\n * Accumulated final transcript text.\n *\n * @see [SpeechRecognitionResult on MDN](https://developer.mozilla.org/en-US/docs/Web/API/SpeechRecognitionResult)\n */\n readonly text: Signal<string>;\n\n /**\n * In-progress interim transcript. Only populated when `interimResults` is `true`.\n *\n * @see [SpeechRecognition: interimResults on MDN](https://developer.mozilla.org/en-US/docs/Web/API/SpeechRecognition/interimResults)\n */\n readonly interimText: Signal<string>;\n\n /**\n * The last recognition error, or `null` if no error occurred.\n *\n * @see [SpeechRecognitionErrorEvent on MDN](https://developer.mozilla.org/en-US/docs/Web/API/SpeechRecognitionErrorEvent)\n */\n readonly error: Signal<SpeechRecognitionErrorEvent | Error | null>;\n\n /**\n * Start listening for speech input.\n *\n * @see [SpeechRecognition: start() on MDN](https://developer.mozilla.org/en-US/docs/Web/API/SpeechRecognition/start)\n */\n readonly start: () => void;\n\n /**\n * Stop listening and return any remaining results.\n *\n * @see [SpeechRecognition: stop() on MDN](https://developer.mozilla.org/en-US/docs/Web/API/SpeechRecognition/stop)\n */\n readonly stop: () => void;\n\n /**\n * Abort recognition immediately without returning results.\n *\n * @see [SpeechRecognition: abort() on MDN](https://developer.mozilla.org/en-US/docs/Web/API/SpeechRecognition/abort)\n */\n readonly abort: () => void;\n}\n\n/**\n * Signal-based wrapper around the [Speech Recognition API](https://developer.mozilla.org/en-US/docs/Web/API/SpeechRecognition).\n *\n * @param options - Optional configuration\n * @returns A SpeechRecognitionRef with isSupported, isListening, text, interimText, error signals and control methods\n *\n * @example\n * ```typescript\n * @Component({\n * template: `\n * @if (recognition.isSupported()) {\n * <button (click)=\"toggleRecognition()\">\n * {{ recognition.isListening() ? 'Stop' : 'Start' }} Recording\n * </button>\n * <p>{{ recognition.text() }}</p>\n * @if (recognition.interimText()) {\n * <p><em>{{ recognition.interimText() }}</em></p>\n * }\n * }\n * `\n * })\n * export class SpeechComponent {\n * readonly recognition = speechRecognition();\n *\n * toggleRecognition() {\n * if (this.recognition.isListening()) {\n * this.recognition.stop();\n * } else {\n * this.recognition.start();\n * }\n * }\n * }\n * ```\n */\nexport function speechRecognition(options?: SpeechRecognitionOptions): SpeechRecognitionRef {\n const { runInContext } = setupContext(options?.injector, speechRecognition);\n\n return runInContext(({ isBrowser, onCleanup }) => {\n const isSupported = constSignal(\n isBrowser && ('SpeechRecognition' in window || 'webkitSpeechRecognition' in window)\n );\n\n if (!isSupported()) {\n return {\n isSupported,\n isListening: constSignal(false),\n text: constSignal(''),\n interimText: constSignal(''),\n error: constSignal(null),\n start: NOOP_FN,\n stop: NOOP_FN,\n abort: NOOP_FN,\n };\n }\n\n const SpeechRecognitionClass =\n (window as any).SpeechRecognition || (window as any).webkitSpeechRecognition;\n\n const recognition: SpeechRecognition = new SpeechRecognitionClass();\n\n const {\n lang = 'en-US',\n interimResults = false,\n continuous = false,\n maxAlternatives = 1,\n } = options ?? {};\n\n recognition.lang = toValue(lang);\n recognition.continuous = continuous;\n recognition.interimResults = interimResults;\n recognition.maxAlternatives = maxAlternatives;\n\n const isListening = signal(false);\n const text = signal('');\n const interimText = signal('');\n const error = signal<SpeechRecognitionErrorEvent | Error | null>(null);\n\n const handleResult = (event: SpeechRecognitionEvent) => {\n let finalTranscript = '';\n let interimTranscript = '';\n\n for (let i = event.resultIndex; i < event.results.length; i++) {\n const result = event.results[i];\n const transcript = result[0]?.transcript || result.item(0)?.transcript || '';\n\n if (result.isFinal) {\n finalTranscript += transcript;\n } else {\n interimTranscript += transcript;\n }\n }\n\n if (finalTranscript) {\n text.update(t => (t ? t + ' ' : '') + finalTranscript);\n interimText.set('');\n } else if (interimTranscript) {\n interimText.set(interimTranscript);\n }\n };\n\n const handleError = (event: SpeechRecognitionErrorEvent) => {\n error.set(event);\n isListening.set(false);\n };\n\n const handleStart = () => {\n isListening.set(true);\n error.set(null);\n\n if (!continuous) {\n text.set('');\n interimText.set('');\n }\n };\n\n const handleEnd = () => {\n isListening.set(false);\n recognition.lang = toValue(lang);\n };\n\n recognition.onstart = handleStart;\n recognition.onend = handleEnd;\n recognition.onerror = handleError;\n recognition.onresult = handleResult;\n\n const start = () => {\n try {\n if (!untracked(isListening)) {\n recognition.start();\n }\n } catch (err) {\n error.set(err as Error);\n }\n };\n\n const stop = () => {\n if (untracked(isListening)) {\n recognition.stop();\n }\n };\n\n const abort = () => {\n if (untracked(isListening)) {\n recognition.abort();\n }\n };\n\n onCleanup(abort);\n\n if (isSignal(lang)) {\n watcher(lang, newLang => {\n if (!isListening()) {\n recognition.lang = newLang;\n }\n });\n }\n\n watcher(permissionState('microphone'), state => {\n if (state === 'denied') {\n abort();\n }\n });\n\n return {\n isSupported,\n isListening: isListening.asReadonly(),\n text: text.asReadonly(),\n interimText: interimText.asReadonly(),\n error: error.asReadonly(),\n start,\n stop,\n abort,\n };\n });\n}\n\n/**\n * Local type definitions for Web Speech API (Speech Recognition).\n *\n * @remarks\n * External `@types/dom-speech-recognition` package may conflict with user's other libraries\n * or become outdated. For better DX, we define minimal required interfaces locally\n * without polluting the global namespace with experimental APIs.\n */\ntype SpeechRecognitionErrorCode =\n | 'aborted'\n | 'audio-capture'\n | 'bad-grammar'\n | 'language-not-supported'\n | 'network'\n | 'no-speech'\n | 'not-allowed'\n | 'service-not-allowed';\n\ninterface SpeechRecognitionAlternative {\n readonly transcript: string;\n readonly confidence: number;\n}\n\ninterface SpeechRecognitionResult {\n readonly isFinal: boolean;\n readonly length: number;\n item(index: number): SpeechRecognitionAlternative;\n [index: number]: SpeechRecognitionAlternative;\n}\n\ninterface SpeechRecognitionResultList {\n readonly length: number;\n item(index: number): SpeechRecognitionResult;\n [index: number]: SpeechRecognitionResult;\n}\n\ninterface SpeechGrammar {\n src: string;\n weight: number;\n}\n\ninterface SpeechGrammarList {\n readonly length: number;\n addFromString(string: string, weight?: number): void;\n addFromURI(src: string, weight?: number): void;\n item(index: number): SpeechGrammar;\n [index: number]: SpeechGrammar;\n}\n\ninterface SpeechRecognitionEvent extends Event {\n readonly resultIndex: number;\n readonly results: SpeechRecognitionResultList;\n}\n\ninterface SpeechRecognitionErrorEvent extends Event {\n readonly error: SpeechRecognitionErrorCode;\n readonly message: string;\n}\n\ninterface SpeechRecognition extends EventTarget {\n continuous: boolean;\n grammars: SpeechGrammarList;\n interimResults: boolean;\n lang: string;\n maxAlternatives: number;\n onaudioend: ((this: SpeechRecognition, ev: Event) => void) | null;\n onaudiostart: ((this: SpeechRecognition, ev: Event) => void) | null;\n onend: ((this: SpeechRecognition, ev: Event) => void) | null;\n onerror: ((this: SpeechRecognition, ev: SpeechRecognitionErrorEvent) => void) | null;\n onnomatch: ((this: SpeechRecognition, ev: SpeechRecognitionEvent) => void) | null;\n onresult: ((this: SpeechRecognition, ev: SpeechRecognitionEvent) => void) | null;\n onsoundend: ((this: SpeechRecognition, ev: Event) => void) | null;\n onsoundstart: ((this: SpeechRecognition, ev: Event) => void) | null;\n onspeechend: ((this: SpeechRecognition, ev: Event) => void) | null;\n onspeechstart: ((this: SpeechRecognition, ev: Event) => void) | null;\n onstart: ((this: SpeechRecognition, ev: Event) => void) | null;\n abort(): void;\n start(audioTrack?: MediaStreamTrack): void;\n stop(): void;\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { signal } from '@angular/core';
|
|
2
|
-
import { setupContext, constSignal, NOOP_FN
|
|
2
|
+
import { setupContext, constSignal, NOOP_FN } from '@signality/core/internal';
|
|
3
|
+
import { toValue } from '@signality/core/utilities';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Signal-based wrapper around the [Speech Synthesis API](https://developer.mozilla.org/en-US/docs/Web/API/SpeechSynthesis).
|