@signality/core 0.1.2 → 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 +1 -2
- package/browser/text-selection/index.d.ts +9 -2
- package/fesm2022/signality-core-browser-clipboard.mjs +2 -1
- package/fesm2022/signality-core-browser-clipboard.mjs.map +1 -1
- package/fesm2022/signality-core-browser-file-dialog.mjs +2 -1
- 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 +2 -1
- package/fesm2022/signality-core-elements-element-focus.mjs.map +1 -1
- package/fesm2022/signality-core-elements-element-size.mjs +2 -1
- package/fesm2022/signality-core-elements-element-size.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
|
@@ -35,8 +35,7 @@ export interface FileDialogOptions extends WithInjector {
|
|
|
35
35
|
* Custom validation predicate called for each selected file.
|
|
36
36
|
* Return `true` to keep the file, `false` to reject it.
|
|
37
37
|
*
|
|
38
|
-
* When provided,
|
|
39
|
-
* takes full responsibility for deciding which files are valid.
|
|
38
|
+
* When provided, `accept` only affects native browser filtering, not per-file validation.
|
|
40
39
|
*
|
|
41
40
|
* @example
|
|
42
41
|
* ```typescript
|
|
@@ -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,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;;;;"}
|
|
@@ -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
|
/**
|
|
@@ -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).
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"signality-core-browser-speech-synthesis.mjs","sources":["../../../projects/core/browser/speech-synthesis/index.ts","../../../projects/core/browser/speech-synthesis/signality-core-browser-speech-synthesis.ts"],"sourcesContent":["import { type Signal, signal } from '@angular/core';\nimport { constSignal, NOOP_FN, setupContext, toValue } from '@signality/core/internal';\nimport type { MaybeSignal, WithInjector } from '@signality/core/types';\n\nexport interface SpeechSynthesisOptions extends WithInjector {\n /**\n * BCP 47 language tag (e.g. `'en-US'`). Supports reactive values.\n *\n * @see [SpeechSynthesisUtterance: lang on MDN](https://developer.mozilla.org/en-US/docs/Web/API/SpeechSynthesisUtterance/lang)\n */\n readonly lang?: MaybeSignal<string>;\n\n /**\n * Speech rate from `0.1` to `10`. Supports reactive values.\n *\n * @default 1\n * @see [SpeechSynthesisUtterance: rate on MDN](https://developer.mozilla.org/en-US/docs/Web/API/SpeechSynthesisUtterance/rate)\n */\n readonly rate?: MaybeSignal<number>;\n\n /**\n * Speech pitch from `0` to `2`. Supports reactive values.\n *\n * @default 1\n * @see [SpeechSynthesisUtterance: pitch on MDN](https://developer.mozilla.org/en-US/docs/Web/API/SpeechSynthesisUtterance/pitch)\n */\n readonly pitch?: MaybeSignal<number>;\n\n /**\n * Speech volume from `0` to `1`. Supports reactive values.\n *\n * @default 1\n * @see [SpeechSynthesisUtterance: volume on MDN](https://developer.mozilla.org/en-US/docs/Web/API/SpeechSynthesisUtterance/volume)\n */\n readonly volume?: MaybeSignal<number>;\n\n /**\n * Voice to use for synthesis. Supports reactive values.\n *\n * @see [SpeechSynthesisUtterance: voice on MDN](https://developer.mozilla.org/en-US/docs/Web/API/SpeechSynthesisUtterance/voice)\n */\n readonly voice?: MaybeSignal<SpeechSynthesisVoice>;\n}\n\nexport interface SpeechSynthesisRef {\n /**\n * Whether the Speech Synthesis API is supported in the current browser.\n *\n * @see [SpeechSynthesis browser compatibility on MDN](https://developer.mozilla.org/en-US/docs/Web/API/SpeechSynthesis#browser_compatibility)\n */\n readonly isSupported: Signal<boolean>;\n\n /**\n * Whether speech is currently being synthesized.\n *\n * @see [SpeechSynthesis: speaking on MDN](https://developer.mozilla.org/en-US/docs/Web/API/SpeechSynthesis/speaking)\n */\n readonly isSpeaking: Signal<boolean>;\n\n /**\n * Whether speech synthesis is currently paused.\n *\n * @see [SpeechSynthesis: paused on MDN](https://developer.mozilla.org/en-US/docs/Web/API/SpeechSynthesis/paused)\n */\n readonly isPaused: Signal<boolean>;\n\n /**\n * List of available synthesis voices for the current device and browser.\n *\n * @see [SpeechSynthesis: getVoices() on MDN](https://developer.mozilla.org/en-US/docs/Web/API/SpeechSynthesis/getVoices)\n */\n readonly voices: Signal<SpeechSynthesisVoice[]>;\n\n /**\n * The text currently being spoken, or `''` if idle.\n */\n readonly currentText: Signal<string>;\n\n /**\n * Speak the given text, cancelling any ongoing utterance.\n * Reads current reactive option values (`lang`, `rate`, `pitch`, `volume`, `voice`) at call time.\n *\n * @see [SpeechSynthesis: speak() on MDN](https://developer.mozilla.org/en-US/docs/Web/API/SpeechSynthesis/speak)\n */\n readonly speak: (text: string) => void;\n\n /**\n * Cancel and stop the current utterance.\n *\n * @see [SpeechSynthesis: cancel() on MDN](https://developer.mozilla.org/en-US/docs/Web/API/SpeechSynthesis/cancel)\n */\n readonly stop: () => void;\n\n /**\n * Pause the current utterance.\n *\n * @see [SpeechSynthesis: pause() on MDN](https://developer.mozilla.org/en-US/docs/Web/API/SpeechSynthesis/pause)\n */\n readonly pause: () => void;\n\n /**\n * Resume a paused utterance.\n *\n * @see [SpeechSynthesis: resume() on MDN](https://developer.mozilla.org/en-US/docs/Web/API/SpeechSynthesis/resume)\n */\n readonly resume: () => void;\n}\n\n/**\n * Signal-based wrapper around the [Speech Synthesis API](https://developer.mozilla.org/en-US/docs/Web/API/SpeechSynthesis).\n *\n * @param options - Optional configuration\n * @returns A SpeechSynthesisRef with isSupported, isSpeaking, isPaused, voices signals and control methods\n *\n * @example\n * ```typescript\n * @Component({\n * template: `\n * @if (synthesis.isSupported()) {\n * <button (click)=\"synthesis.speak('Hello, world!')\" [disabled]=\"synthesis.isSpeaking()\">\n * {{ synthesis.isSpeaking() ? 'Speaking...' : 'Speak' }}\n * </button>\n * <button (click)=\"synthesis.stop()\">Stop</button>\n * }\n * `\n * })\n * export class TextToSpeechDemo {\n * readonly synthesis = speechSynthesis({ rate: 1.5 });\n * }\n * ```\n */\nexport function speechSynthesis(options?: SpeechSynthesisOptions): SpeechSynthesisRef {\n const { runInContext } = setupContext(options?.injector, speechSynthesis);\n\n return runInContext(({ onCleanup, isBrowser }) => {\n const isSupported = constSignal(\n isBrowser && 'speechSynthesis' in window && typeof window.speechSynthesis !== 'undefined'\n );\n\n if (!isSupported()) {\n return {\n isSupported,\n isSpeaking: constSignal(false),\n isPaused: constSignal(false),\n voices: constSignal([]),\n currentText: constSignal(''),\n speak: NOOP_FN,\n stop: NOOP_FN,\n pause: NOOP_FN,\n resume: NOOP_FN,\n };\n }\n\n const { speechSynthesis } = window;\n\n const isSpeaking = signal(false);\n const isPaused = signal(false);\n const voices = signal<SpeechSynthesisVoice[]>(speechSynthesis.getVoices());\n const currentText = signal('');\n\n const loadVoices = () => {\n const availableVoices = speechSynthesis.getVoices();\n voices.set(availableVoices);\n };\n\n const updateSpeakingState = () => {\n isSpeaking.set(speechSynthesis.speaking);\n isPaused.set(speechSynthesis.paused);\n };\n\n const handleStart = () => {\n isSpeaking.set(true);\n isPaused.set(false);\n };\n\n const handleEnd = () => {\n isSpeaking.set(false);\n isPaused.set(false);\n currentText.set('');\n };\n\n const handleError = () => {\n isSpeaking.set(false);\n isPaused.set(false);\n currentText.set('');\n };\n\n const handlePause = () => {\n isPaused.set(true);\n };\n\n const handleResume = () => {\n isPaused.set(false);\n };\n\n const speak = (text: string) => {\n if (!text) {\n return;\n }\n\n speechSynthesis.cancel();\n\n const utterance = new SpeechSynthesisUtterance(text);\n\n const lang = toValue.untracked(options?.lang);\n const voice = toValue.untracked(options?.voice);\n\n if (lang) {\n utterance.lang = lang;\n }\n\n if (voice) {\n utterance.voice = voice;\n }\n\n utterance.rate = toValue.untracked(options?.rate) ?? 1;\n utterance.pitch = toValue.untracked(options?.pitch) ?? 1;\n utterance.volume = toValue.untracked(options?.volume) ?? 1;\n\n utterance.onstart = handleStart;\n utterance.onend = handleEnd;\n utterance.onerror = handleError;\n utterance.onpause = handlePause;\n utterance.onresume = handleResume;\n\n currentText.set(text);\n speechSynthesis.speak(utterance);\n\n updateSpeakingState();\n };\n\n const stop = () => {\n speechSynthesis.cancel();\n isSpeaking.set(false);\n isPaused.set(false);\n currentText.set('');\n };\n\n const pause = () => {\n if (speechSynthesis.speaking && !speechSynthesis.paused) {\n speechSynthesis.pause();\n isPaused.set(true);\n }\n };\n\n const resume = () => {\n if (speechSynthesis.paused) {\n speechSynthesis.resume();\n isPaused.set(false);\n }\n };\n\n onCleanup(stop);\n\n if (speechSynthesis.onvoiceschanged !== undefined) {\n speechSynthesis.onvoiceschanged = loadVoices;\n }\n\n return {\n isSupported,\n isSpeaking: isSpeaking.asReadonly(),\n isPaused: isPaused.asReadonly(),\n voices: voices.asReadonly(),\n currentText: currentText.asReadonly(),\n speak,\n stop,\n pause,\n resume,\n };\n });\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-synthesis.mjs","sources":["../../../projects/core/browser/speech-synthesis/index.ts","../../../projects/core/browser/speech-synthesis/signality-core-browser-speech-synthesis.ts"],"sourcesContent":["import { type Signal, signal } 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';\n\nexport interface SpeechSynthesisOptions extends WithInjector {\n /**\n * BCP 47 language tag (e.g. `'en-US'`). Supports reactive values.\n *\n * @see [SpeechSynthesisUtterance: lang on MDN](https://developer.mozilla.org/en-US/docs/Web/API/SpeechSynthesisUtterance/lang)\n */\n readonly lang?: MaybeSignal<string>;\n\n /**\n * Speech rate from `0.1` to `10`. Supports reactive values.\n *\n * @default 1\n * @see [SpeechSynthesisUtterance: rate on MDN](https://developer.mozilla.org/en-US/docs/Web/API/SpeechSynthesisUtterance/rate)\n */\n readonly rate?: MaybeSignal<number>;\n\n /**\n * Speech pitch from `0` to `2`. Supports reactive values.\n *\n * @default 1\n * @see [SpeechSynthesisUtterance: pitch on MDN](https://developer.mozilla.org/en-US/docs/Web/API/SpeechSynthesisUtterance/pitch)\n */\n readonly pitch?: MaybeSignal<number>;\n\n /**\n * Speech volume from `0` to `1`. Supports reactive values.\n *\n * @default 1\n * @see [SpeechSynthesisUtterance: volume on MDN](https://developer.mozilla.org/en-US/docs/Web/API/SpeechSynthesisUtterance/volume)\n */\n readonly volume?: MaybeSignal<number>;\n\n /**\n * Voice to use for synthesis. Supports reactive values.\n *\n * @see [SpeechSynthesisUtterance: voice on MDN](https://developer.mozilla.org/en-US/docs/Web/API/SpeechSynthesisUtterance/voice)\n */\n readonly voice?: MaybeSignal<SpeechSynthesisVoice>;\n}\n\nexport interface SpeechSynthesisRef {\n /**\n * Whether the Speech Synthesis API is supported in the current browser.\n *\n * @see [SpeechSynthesis browser compatibility on MDN](https://developer.mozilla.org/en-US/docs/Web/API/SpeechSynthesis#browser_compatibility)\n */\n readonly isSupported: Signal<boolean>;\n\n /**\n * Whether speech is currently being synthesized.\n *\n * @see [SpeechSynthesis: speaking on MDN](https://developer.mozilla.org/en-US/docs/Web/API/SpeechSynthesis/speaking)\n */\n readonly isSpeaking: Signal<boolean>;\n\n /**\n * Whether speech synthesis is currently paused.\n *\n * @see [SpeechSynthesis: paused on MDN](https://developer.mozilla.org/en-US/docs/Web/API/SpeechSynthesis/paused)\n */\n readonly isPaused: Signal<boolean>;\n\n /**\n * List of available synthesis voices for the current device and browser.\n *\n * @see [SpeechSynthesis: getVoices() on MDN](https://developer.mozilla.org/en-US/docs/Web/API/SpeechSynthesis/getVoices)\n */\n readonly voices: Signal<SpeechSynthesisVoice[]>;\n\n /**\n * The text currently being spoken, or `''` if idle.\n */\n readonly currentText: Signal<string>;\n\n /**\n * Speak the given text, cancelling any ongoing utterance.\n * Reads current reactive option values (`lang`, `rate`, `pitch`, `volume`, `voice`) at call time.\n *\n * @see [SpeechSynthesis: speak() on MDN](https://developer.mozilla.org/en-US/docs/Web/API/SpeechSynthesis/speak)\n */\n readonly speak: (text: string) => void;\n\n /**\n * Cancel and stop the current utterance.\n *\n * @see [SpeechSynthesis: cancel() on MDN](https://developer.mozilla.org/en-US/docs/Web/API/SpeechSynthesis/cancel)\n */\n readonly stop: () => void;\n\n /**\n * Pause the current utterance.\n *\n * @see [SpeechSynthesis: pause() on MDN](https://developer.mozilla.org/en-US/docs/Web/API/SpeechSynthesis/pause)\n */\n readonly pause: () => void;\n\n /**\n * Resume a paused utterance.\n *\n * @see [SpeechSynthesis: resume() on MDN](https://developer.mozilla.org/en-US/docs/Web/API/SpeechSynthesis/resume)\n */\n readonly resume: () => void;\n}\n\n/**\n * Signal-based wrapper around the [Speech Synthesis API](https://developer.mozilla.org/en-US/docs/Web/API/SpeechSynthesis).\n *\n * @param options - Optional configuration\n * @returns A SpeechSynthesisRef with isSupported, isSpeaking, isPaused, voices signals and control methods\n *\n * @example\n * ```typescript\n * @Component({\n * template: `\n * @if (synthesis.isSupported()) {\n * <button (click)=\"synthesis.speak('Hello, world!')\" [disabled]=\"synthesis.isSpeaking()\">\n * {{ synthesis.isSpeaking() ? 'Speaking...' : 'Speak' }}\n * </button>\n * <button (click)=\"synthesis.stop()\">Stop</button>\n * }\n * `\n * })\n * export class TextToSpeechDemo {\n * readonly synthesis = speechSynthesis({ rate: 1.5 });\n * }\n * ```\n */\nexport function speechSynthesis(options?: SpeechSynthesisOptions): SpeechSynthesisRef {\n const { runInContext } = setupContext(options?.injector, speechSynthesis);\n\n return runInContext(({ onCleanup, isBrowser }) => {\n const isSupported = constSignal(\n isBrowser && 'speechSynthesis' in window && typeof window.speechSynthesis !== 'undefined'\n );\n\n if (!isSupported()) {\n return {\n isSupported,\n isSpeaking: constSignal(false),\n isPaused: constSignal(false),\n voices: constSignal([]),\n currentText: constSignal(''),\n speak: NOOP_FN,\n stop: NOOP_FN,\n pause: NOOP_FN,\n resume: NOOP_FN,\n };\n }\n\n const { speechSynthesis } = window;\n\n const isSpeaking = signal(false);\n const isPaused = signal(false);\n const voices = signal<SpeechSynthesisVoice[]>(speechSynthesis.getVoices());\n const currentText = signal('');\n\n const loadVoices = () => {\n const availableVoices = speechSynthesis.getVoices();\n voices.set(availableVoices);\n };\n\n const updateSpeakingState = () => {\n isSpeaking.set(speechSynthesis.speaking);\n isPaused.set(speechSynthesis.paused);\n };\n\n const handleStart = () => {\n isSpeaking.set(true);\n isPaused.set(false);\n };\n\n const handleEnd = () => {\n isSpeaking.set(false);\n isPaused.set(false);\n currentText.set('');\n };\n\n const handleError = () => {\n isSpeaking.set(false);\n isPaused.set(false);\n currentText.set('');\n };\n\n const handlePause = () => {\n isPaused.set(true);\n };\n\n const handleResume = () => {\n isPaused.set(false);\n };\n\n const speak = (text: string) => {\n if (!text) {\n return;\n }\n\n speechSynthesis.cancel();\n\n const utterance = new SpeechSynthesisUtterance(text);\n\n const lang = toValue.untracked(options?.lang);\n const voice = toValue.untracked(options?.voice);\n\n if (lang) {\n utterance.lang = lang;\n }\n\n if (voice) {\n utterance.voice = voice;\n }\n\n utterance.rate = toValue.untracked(options?.rate) ?? 1;\n utterance.pitch = toValue.untracked(options?.pitch) ?? 1;\n utterance.volume = toValue.untracked(options?.volume) ?? 1;\n\n utterance.onstart = handleStart;\n utterance.onend = handleEnd;\n utterance.onerror = handleError;\n utterance.onpause = handlePause;\n utterance.onresume = handleResume;\n\n currentText.set(text);\n speechSynthesis.speak(utterance);\n\n updateSpeakingState();\n };\n\n const stop = () => {\n speechSynthesis.cancel();\n isSpeaking.set(false);\n isPaused.set(false);\n currentText.set('');\n };\n\n const pause = () => {\n if (speechSynthesis.speaking && !speechSynthesis.paused) {\n speechSynthesis.pause();\n isPaused.set(true);\n }\n };\n\n const resume = () => {\n if (speechSynthesis.paused) {\n speechSynthesis.resume();\n isPaused.set(false);\n }\n };\n\n onCleanup(stop);\n\n if (speechSynthesis.onvoiceschanged !== undefined) {\n speechSynthesis.onvoiceschanged = loadVoices;\n }\n\n return {\n isSupported,\n isSpeaking: isSpeaking.asReadonly(),\n isPaused: isPaused.asReadonly(),\n voices: voices.asReadonly(),\n currentText: currentText.asReadonly(),\n speak,\n stop,\n pause,\n resume,\n };\n });\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|