@signality/core 0.0.1-alpha.2 → 0.0.1-alpha.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/battery/index.d.ts +27 -2
- package/browser/bluetooth/index.d.ts +40 -13
- package/browser/breakpoints/index.d.ts +22 -9
- package/browser/broadcast-channel/index.d.ts +1 -1
- package/browser/browser-language/index.d.ts +1 -1
- package/browser/clipboard/index.d.ts +22 -6
- package/browser/device-posture/index.d.ts +23 -2
- package/browser/display-media/index.d.ts +34 -22
- package/browser/eye-dropper/index.d.ts +22 -6
- package/browser/favicon/index.d.ts +29 -2
- package/browser/file-dialog/index.d.ts +97 -0
- package/browser/fps/index.d.ts +1 -1
- package/browser/fullscreen/index.d.ts +78 -0
- package/browser/gamepad/index.d.ts +39 -9
- package/browser/geolocation/index.d.ts +44 -13
- package/browser/index.d.ts +8 -1
- package/browser/input-modality/index.d.ts +1 -1
- package/browser/listener/index.d.ts +1 -1
- package/browser/media-query/index.d.ts +1 -1
- package/browser/network/index.d.ts +37 -9
- package/browser/online/index.d.ts +1 -1
- package/browser/page-visibility/index.d.ts +1 -1
- package/browser/permission-state/index.d.ts +23 -0
- package/browser/picture-in-picture/index.d.ts +24 -6
- package/browser/screen-orientation/index.d.ts +1 -1
- package/browser/speech-recognition/index.d.ts +51 -13
- package/browser/speech-synthesis/index.d.ts +82 -42
- package/browser/storage/index.d.ts +1 -1
- package/browser/text-direction/index.d.ts +2 -5
- package/{elements → browser}/text-selection/index.d.ts +1 -1
- package/browser/vibration/index.d.ts +38 -9
- package/browser/wake-lock/index.d.ts +51 -12
- package/browser/web-notification/index.d.ts +35 -9
- package/browser/web-share/index.d.ts +19 -5
- package/browser/web-worker/index.d.ts +35 -11
- package/browser/window-focus/index.d.ts +27 -0
- package/{elements → browser}/window-size/index.d.ts +6 -7
- package/elements/active-element/index.d.ts +1 -1
- package/elements/dropzone/index.d.ts +60 -10
- package/elements/element-focus/index.d.ts +1 -1
- package/elements/element-focus-within/index.d.ts +1 -1
- package/elements/element-hover/index.d.ts +1 -1
- package/elements/element-size/index.d.ts +8 -5
- package/elements/element-visibility/index.d.ts +25 -7
- package/elements/index.d.ts +0 -2
- package/elements/mouse-position/index.d.ts +27 -7
- package/elements/on-click-outside/index.d.ts +1 -1
- package/elements/on-disconnect/index.d.ts +1 -1
- package/elements/on-long-press/index.d.ts +1 -1
- package/elements/pointer-swipe/index.d.ts +1 -1
- package/elements/scroll-position/index.d.ts +2 -2
- package/elements/swipe/index.d.ts +1 -1
- package/fesm2022/signality-core-browser-battery.mjs +1 -1
- package/fesm2022/signality-core-browser-battery.mjs.map +1 -1
- package/fesm2022/signality-core-browser-bluetooth.mjs +28 -27
- package/fesm2022/signality-core-browser-bluetooth.mjs.map +1 -1
- package/fesm2022/signality-core-browser-breakpoints.mjs +19 -10
- package/fesm2022/signality-core-browser-breakpoints.mjs.map +1 -1
- package/fesm2022/signality-core-browser-broadcast-channel.mjs +1 -1
- package/fesm2022/signality-core-browser-broadcast-channel.mjs.map +1 -1
- package/fesm2022/signality-core-browser-browser-language.mjs +1 -1
- package/fesm2022/signality-core-browser-browser-language.mjs.map +1 -1
- package/fesm2022/signality-core-browser-clipboard.mjs +1 -1
- package/fesm2022/signality-core-browser-clipboard.mjs.map +1 -1
- package/fesm2022/signality-core-browser-device-posture.mjs +13 -0
- package/fesm2022/signality-core-browser-device-posture.mjs.map +1 -1
- package/fesm2022/signality-core-browser-display-media.mjs +4 -17
- package/fesm2022/signality-core-browser-display-media.mjs.map +1 -1
- package/fesm2022/signality-core-browser-eye-dropper.mjs +1 -1
- package/fesm2022/signality-core-browser-eye-dropper.mjs.map +1 -1
- package/fesm2022/signality-core-browser-favicon.mjs +2 -2
- package/fesm2022/signality-core-browser-favicon.mjs.map +1 -1
- package/fesm2022/signality-core-browser-file-dialog.mjs +109 -0
- package/fesm2022/signality-core-browser-file-dialog.mjs.map +1 -0
- package/fesm2022/signality-core-browser-fps.mjs +1 -1
- package/fesm2022/signality-core-browser-fps.mjs.map +1 -1
- package/fesm2022/signality-core-browser-fullscreen.mjs +113 -0
- package/fesm2022/signality-core-browser-fullscreen.mjs.map +1 -0
- package/fesm2022/signality-core-browser-gamepad.mjs +14 -4
- package/fesm2022/signality-core-browser-gamepad.mjs.map +1 -1
- package/fesm2022/signality-core-browser-geolocation.mjs +8 -19
- package/fesm2022/signality-core-browser-geolocation.mjs.map +1 -1
- package/fesm2022/signality-core-browser-input-modality.mjs +1 -1
- package/fesm2022/signality-core-browser-input-modality.mjs.map +1 -1
- package/fesm2022/signality-core-browser-listener.mjs +18 -6
- package/fesm2022/signality-core-browser-listener.mjs.map +1 -1
- package/fesm2022/signality-core-browser-media-query.mjs +1 -1
- package/fesm2022/signality-core-browser-media-query.mjs.map +1 -1
- package/fesm2022/signality-core-browser-network.mjs +2 -2
- package/fesm2022/signality-core-browser-network.mjs.map +1 -1
- package/fesm2022/signality-core-browser-online.mjs +1 -1
- package/fesm2022/signality-core-browser-online.mjs.map +1 -1
- package/fesm2022/signality-core-browser-page-visibility.mjs +1 -1
- package/fesm2022/signality-core-browser-page-visibility.mjs.map +1 -1
- package/fesm2022/signality-core-browser-permission-state.mjs +57 -0
- package/fesm2022/signality-core-browser-permission-state.mjs.map +1 -0
- package/fesm2022/signality-core-browser-picture-in-picture.mjs +30 -13
- package/fesm2022/signality-core-browser-picture-in-picture.mjs.map +1 -1
- package/fesm2022/signality-core-browser-screen-orientation.mjs +1 -1
- package/fesm2022/signality-core-browser-screen-orientation.mjs.map +1 -1
- package/fesm2022/signality-core-browser-speech-recognition.mjs +7 -19
- package/fesm2022/signality-core-browser-speech-recognition.mjs.map +1 -1
- package/fesm2022/signality-core-browser-speech-synthesis.mjs +14 -16
- package/fesm2022/signality-core-browser-speech-synthesis.mjs.map +1 -1
- package/fesm2022/signality-core-browser-storage.mjs +1 -1
- package/fesm2022/signality-core-browser-storage.mjs.map +1 -1
- package/fesm2022/signality-core-browser-text-direction.mjs +1 -4
- package/fesm2022/signality-core-browser-text-direction.mjs.map +1 -1
- package/fesm2022/{signality-core-elements-text-selection.mjs → signality-core-browser-text-selection.mjs} +2 -2
- package/fesm2022/signality-core-browser-text-selection.mjs.map +1 -0
- package/fesm2022/signality-core-browser-vibration.mjs +14 -5
- package/fesm2022/signality-core-browser-vibration.mjs.map +1 -1
- package/fesm2022/signality-core-browser-wake-lock.mjs +33 -16
- package/fesm2022/signality-core-browser-wake-lock.mjs.map +1 -1
- package/fesm2022/signality-core-browser-web-notification.mjs +5 -7
- package/fesm2022/signality-core-browser-web-notification.mjs.map +1 -1
- package/fesm2022/signality-core-browser-web-share.mjs +3 -5
- package/fesm2022/signality-core-browser-web-share.mjs.map +1 -1
- package/fesm2022/signality-core-browser-web-worker.mjs +6 -3
- package/fesm2022/signality-core-browser-web-worker.mjs.map +1 -1
- package/fesm2022/signality-core-browser-window-focus.mjs +48 -0
- package/fesm2022/signality-core-browser-window-focus.mjs.map +1 -0
- package/fesm2022/{signality-core-elements-window-size.mjs → signality-core-browser-window-size.mjs} +4 -24
- package/fesm2022/signality-core-browser-window-size.mjs.map +1 -0
- package/fesm2022/signality-core-browser.mjs +8 -1
- package/fesm2022/signality-core-browser.mjs.map +1 -1
- package/fesm2022/signality-core-elements-active-element.mjs +1 -1
- package/fesm2022/signality-core-elements-active-element.mjs.map +1 -1
- package/fesm2022/signality-core-elements-dropzone.mjs +28 -29
- package/fesm2022/signality-core-elements-dropzone.mjs.map +1 -1
- package/fesm2022/signality-core-elements-element-focus-within.mjs +1 -1
- package/fesm2022/signality-core-elements-element-focus-within.mjs.map +1 -1
- package/fesm2022/signality-core-elements-element-focus.mjs +1 -1
- package/fesm2022/signality-core-elements-element-focus.mjs.map +1 -1
- package/fesm2022/signality-core-elements-element-hover.mjs +1 -1
- package/fesm2022/signality-core-elements-element-hover.mjs.map +1 -1
- package/fesm2022/signality-core-elements-element-size.mjs +19 -24
- package/fesm2022/signality-core-elements-element-size.mjs.map +1 -1
- package/fesm2022/signality-core-elements-element-visibility.mjs +2 -2
- package/fesm2022/signality-core-elements-element-visibility.mjs.map +1 -1
- package/fesm2022/signality-core-elements-mouse-position.mjs +3 -3
- package/fesm2022/signality-core-elements-mouse-position.mjs.map +1 -1
- package/fesm2022/signality-core-elements-on-click-outside.mjs +1 -1
- package/fesm2022/signality-core-elements-on-click-outside.mjs.map +1 -1
- package/fesm2022/signality-core-elements-on-disconnect.mjs +1 -1
- package/fesm2022/signality-core-elements-on-disconnect.mjs.map +1 -1
- package/fesm2022/signality-core-elements-on-long-press.mjs +1 -1
- package/fesm2022/signality-core-elements-on-long-press.mjs.map +1 -1
- package/fesm2022/signality-core-elements-pointer-swipe.mjs +1 -1
- package/fesm2022/signality-core-elements-pointer-swipe.mjs.map +1 -1
- package/fesm2022/signality-core-elements-scroll-position.mjs +2 -2
- package/fesm2022/signality-core-elements-scroll-position.mjs.map +1 -1
- package/fesm2022/signality-core-elements-swipe.mjs +1 -1
- package/fesm2022/signality-core-elements-swipe.mjs.map +1 -1
- package/fesm2022/signality-core-elements.mjs +0 -2
- package/fesm2022/signality-core-elements.mjs.map +1 -1
- package/fesm2022/signality-core-internal.mjs +54 -7
- package/fesm2022/signality-core-internal.mjs.map +1 -1
- package/fesm2022/signality-core-observers-intersection-observer.mjs +3 -2
- package/fesm2022/signality-core-observers-intersection-observer.mjs.map +1 -1
- package/fesm2022/signality-core-observers-mutation-observer.mjs +3 -2
- package/fesm2022/signality-core-observers-mutation-observer.mjs.map +1 -1
- package/fesm2022/signality-core-observers-resize-observer.mjs +3 -2
- package/fesm2022/signality-core-observers-resize-observer.mjs.map +1 -1
- package/fesm2022/signality-core-observers.mjs +0 -1
- package/fesm2022/signality-core-observers.mjs.map +1 -1
- package/fesm2022/signality-core-reactivity-debounced.mjs.map +1 -1
- package/fesm2022/signality-core-reactivity-throttled.mjs.map +1 -1
- package/fesm2022/signality-core-reactivity-watcher.mjs.map +1 -1
- package/fesm2022/signality-core-router-fragment.mjs +1 -1
- package/fesm2022/signality-core-router-fragment.mjs.map +1 -1
- package/fesm2022/signality-core-router-params.mjs +1 -1
- package/fesm2022/signality-core-router-params.mjs.map +1 -1
- package/fesm2022/signality-core-router-query-params.mjs.map +1 -1
- package/fesm2022/signality-core-router-route-data.mjs +1 -1
- package/fesm2022/signality-core-router-route-data.mjs.map +1 -1
- package/fesm2022/signality-core-router-router-listener.mjs.map +1 -1
- package/fesm2022/signality-core-router-title.mjs +1 -1
- package/fesm2022/signality-core-router-title.mjs.map +1 -1
- package/fesm2022/signality-core-router-url.mjs +1 -1
- package/fesm2022/signality-core-router-url.mjs.map +1 -1
- package/fesm2022/signality-core-scheduling-debounce-callback.mjs +1 -1
- package/fesm2022/signality-core-scheduling-debounce-callback.mjs.map +1 -1
- package/fesm2022/signality-core-scheduling-interval.mjs +27 -72
- package/fesm2022/signality-core-scheduling-interval.mjs.map +1 -1
- package/internal/utils/assert.d.ts +2 -0
- package/internal/utils/dom/index.d.ts +1 -0
- package/internal/utils/dom/is-element.d.ts +1 -1
- package/internal/utils/dom/is-event-target.d.ts +1 -0
- package/internal/utils/files/index.d.ts +1 -0
- package/internal/utils/files/is-accepted-file.d.ts +11 -0
- package/internal/utils/index.d.ts +3 -0
- package/internal/utils/to-element.d.ts +1 -1
- package/internal/utils/unref-element.d.ts +2 -0
- package/observers/index.d.ts +0 -1
- package/observers/intersection-observer/index.d.ts +22 -1
- package/observers/mutation-observer/index.d.ts +43 -1
- package/observers/resize-observer/index.d.ts +13 -1
- package/package.json +25 -17
- package/reactivity/debounced/index.d.ts +2 -2
- package/reactivity/throttled/index.d.ts +2 -2
- package/reactivity/watcher/index.d.ts +2 -2
- package/router/fragment/index.d.ts +1 -1
- package/router/params/index.d.ts +1 -1
- package/router/query-params/index.d.ts +5 -3
- package/router/route-data/index.d.ts +1 -1
- package/router/router-listener/index.d.ts +1 -1
- package/router/title/index.d.ts +1 -1
- package/router/url/index.d.ts +1 -1
- package/scheduling/debounce-callback/index.d.ts +1 -1
- package/scheduling/interval/index.d.ts +19 -27
- package/browser/pointer-lock-element/index.d.ts +0 -22
- package/fesm2022/signality-core-browser-pointer-lock-element.mjs +0 -43
- package/fesm2022/signality-core-browser-pointer-lock-element.mjs.map +0 -1
- package/fesm2022/signality-core-elements-text-selection.mjs.map +0 -1
- package/fesm2022/signality-core-elements-window-size.mjs.map +0 -1
- package/fesm2022/signality-core-observers-performance-observer.mjs +0 -84
- package/fesm2022/signality-core-observers-performance-observer.mjs.map +0 -1
- package/observers/performance-observer/index.d.ts +0 -58
|
@@ -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';\n\nexport interface SpeechRecognitionOptions extends WithInjector {\n /**\n * Language for speech recognition.\n * @default 'en-US'\n */\n readonly lang?: MaybeSignal<string>;\n\n /**\n * Whether to return interim results.\n * @default false\n */\n readonly interimResults?: boolean;\n\n /**\n * Whether to continue recognition after speech ends.\n * @default false\n */\n readonly continuous?: boolean;\n\n /**\n * Maximum number of alternative transcripts.\n * @default 1\n */\n readonly maxAlternatives?: number;\n}\n\nexport interface SpeechRecognitionRef {\n /** Whether Speech Recognition API is supported */\n readonly isSupported: Signal<boolean>;\n\n /** Whether recognition is currently active */\n readonly isListening: Signal<boolean>;\n\n /** Final transcript text */\n readonly text: Signal<string>;\n\n /** Interim transcript text */\n readonly interimText: Signal<string>;\n\n /** Error if recognition failed */\n readonly error: Signal<SpeechRecognitionErrorEvent | Error | null>;\n\n /** Start speech recognition */\n readonly start: () => void;\n\n /** Stop speech recognition */\n readonly stop: () => void;\n\n /** Abort speech recognition */\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 * 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 = window.SpeechRecognition || window.webkitSpeechRecognition;\n\n const recognition = new SpeechRecognitionClass();\n const abortController = new AbortController();\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(() => {\n abortController.abort();\n abort();\n });\n\n if (isSignal(lang)) {\n watcher(lang, newLang => {\n if (!isListening()) {\n recognition.lang = newLang;\n }\n });\n }\n\n navigator.permissions.query({ name: 'microphone' }).then(result => {\n if (abortController.signal.aborted) {\n return;\n }\n\n const check = () => {\n if (result.state === 'denied') {\n abort();\n }\n };\n\n check();\n\n result.addEventListener('change', check, {\n signal: abortController.signal,\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 * 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, 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, injector, 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 = window.SpeechRecognition || window.webkitSpeechRecognition;\n\n const recognition = 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 * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { signal } from '@angular/core';
|
|
2
|
-
import { setupContext, constSignal, NOOP_FN } from '@signality/core/internal';
|
|
2
|
+
import { setupContext, constSignal, NOOP_FN, toValue } from '@signality/core/internal';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Signal-based wrapper around the [Speech Synthesis API](https://developer.mozilla.org/en-US/docs/Web/API/SpeechSynthesis).
|
|
@@ -12,19 +12,15 @@ import { setupContext, constSignal, NOOP_FN } from '@signality/core/internal';
|
|
|
12
12
|
* @Component({
|
|
13
13
|
* template: `
|
|
14
14
|
* @if (synthesis.isSupported()) {
|
|
15
|
-
* <button (click)="
|
|
15
|
+
* <button (click)="synthesis.speak('Hello, world!')" [disabled]="synthesis.isSpeaking()">
|
|
16
16
|
* {{ synthesis.isSpeaking() ? 'Speaking...' : 'Speak' }}
|
|
17
17
|
* </button>
|
|
18
18
|
* <button (click)="synthesis.stop()">Stop</button>
|
|
19
19
|
* }
|
|
20
20
|
* `
|
|
21
21
|
* })
|
|
22
|
-
* class
|
|
23
|
-
* readonly synthesis = speechSynthesis();
|
|
24
|
-
*
|
|
25
|
-
* speakText() {
|
|
26
|
-
* this.synthesis.speak('Hello, world!');
|
|
27
|
-
* }
|
|
22
|
+
* export class TextToSpeechDemo {
|
|
23
|
+
* readonly synthesis = speechSynthesis({ rate: 1.5 });
|
|
28
24
|
* }
|
|
29
25
|
* ```
|
|
30
26
|
*/
|
|
@@ -46,7 +42,6 @@ function speechSynthesis(options) {
|
|
|
46
42
|
};
|
|
47
43
|
}
|
|
48
44
|
const { speechSynthesis } = window;
|
|
49
|
-
const { lang: defaultLang, rate: defaultRate = 1, pitch: defaultPitch = 1, volume: defaultVolume = 1, voice: defaultVoice, } = options ?? {};
|
|
50
45
|
const isSpeaking = signal(false, ...(ngDevMode ? [{ debugName: "isSpeaking" }] : []));
|
|
51
46
|
const isPaused = signal(false, ...(ngDevMode ? [{ debugName: "isPaused" }] : []));
|
|
52
47
|
const voices = signal(speechSynthesis.getVoices(), ...(ngDevMode ? [{ debugName: "voices" }] : []));
|
|
@@ -79,20 +74,23 @@ function speechSynthesis(options) {
|
|
|
79
74
|
const handleResume = () => {
|
|
80
75
|
isPaused.set(false);
|
|
81
76
|
};
|
|
82
|
-
const speak = (text
|
|
77
|
+
const speak = (text) => {
|
|
83
78
|
if (!text) {
|
|
84
79
|
return;
|
|
85
80
|
}
|
|
86
81
|
speechSynthesis.cancel();
|
|
87
82
|
const utterance = new SpeechSynthesisUtterance(text);
|
|
88
|
-
const
|
|
89
|
-
|
|
83
|
+
const lang = toValue.untracked(options?.lang);
|
|
84
|
+
const voice = toValue.untracked(options?.voice);
|
|
85
|
+
if (lang) {
|
|
90
86
|
utterance.lang = lang;
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
utterance.volume = volume;
|
|
94
|
-
if (voice)
|
|
87
|
+
}
|
|
88
|
+
if (voice) {
|
|
95
89
|
utterance.voice = voice;
|
|
90
|
+
}
|
|
91
|
+
utterance.rate = toValue.untracked(options?.rate) ?? 1;
|
|
92
|
+
utterance.pitch = toValue.untracked(options?.pitch) ?? 1;
|
|
93
|
+
utterance.volume = toValue.untracked(options?.volume) ?? 1;
|
|
96
94
|
utterance.onstart = handleStart;
|
|
97
95
|
utterance.onend = handleEnd;
|
|
98
96
|
utterance.onerror = handleError;
|
|
@@ -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 } from '@signality/core/internal';\nimport type { WithInjector } from '@signality/core/types';\n\nexport interface SpeechSynthesisSpeakOptions {\n /** Text to speak */\n readonly text: string;\n\n /** Language code (e.g., 'en-US') */\n readonly lang?: string;\n\n /** Speech rate (0.1 to 10) */\n readonly rate?: number;\n\n /** Speech pitch (0 to 2) */\n readonly pitch?: number;\n\n /** Speech volume (0 to 1) */\n readonly volume?: number;\n\n /** Voice to use */\n readonly voice?: SpeechSynthesisVoice;\n}\n\nexport interface SpeechSynthesisOptions extends WithInjector {\n /** Default language */\n readonly lang?: string;\n\n /** Default speech rate */\n readonly rate?: number;\n\n /** Default pitch */\n readonly pitch?: number;\n\n /** Default volume */\n readonly volume?: number;\n\n /** Default voice */\n readonly voice?: SpeechSynthesisVoice;\n}\n\nexport interface SpeechSynthesisRef {\n /** Whether Speech Synthesis API is supported */\n readonly isSupported: Signal<boolean>;\n\n /** Whether speech is currently playing */\n readonly isSpeaking: Signal<boolean>;\n\n /** Whether speech is currently paused */\n readonly isPaused: Signal<boolean>;\n\n /** Available voices */\n readonly voices: Signal<SpeechSynthesisVoice[]>;\n\n /** Current speaking text */\n readonly currentText: Signal<string>;\n\n /** Speak text */\n readonly speak: (text: string, options?: Partial<SpeechSynthesisSpeakOptions>) => void;\n\n /** Stop speaking */\n readonly stop: () => void;\n\n /** Pause speaking */\n readonly pause: () => void;\n\n /** Resume speaking */\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)=\"speakText()\" [disabled]=\"synthesis.isSpeaking()\">\n * {{ synthesis.isSpeaking() ? 'Speaking...' : 'Speak' }}\n * </button>\n * <button (click)=\"synthesis.stop()\">Stop</button>\n * }\n * `\n * })\n * class TextToSpeechComponent {\n * readonly synthesis = speechSynthesis();\n *\n * speakText() {\n * this.synthesis.speak('Hello, world!');\n * }\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 {\n lang: defaultLang,\n rate: defaultRate = 1,\n pitch: defaultPitch = 1,\n volume: defaultVolume = 1,\n voice: defaultVoice,\n } = options ?? {};\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, speakOptions?: Partial<SpeechSynthesisSpeakOptions>) => {\n if (!text) {\n return;\n }\n\n speechSynthesis.cancel();\n\n const utterance = new SpeechSynthesisUtterance(text);\n\n const {\n lang = defaultLang,\n rate = defaultRate,\n pitch = defaultPitch,\n volume = defaultVolume,\n voice = defaultVoice,\n } = speakOptions ?? {};\n\n if (lang) utterance.lang = lang;\n\n utterance.rate = rate;\n utterance.pitch = pitch;\n utterance.volume = volume;\n\n if (voice) utterance.voice = voice;\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, 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":";;;AA4GA;;;;;;;;;;;;;;;;;;;;;;AAsBG;AACG,SAAU,eAAe,CAAC,OAAgC,EAAA;AAC9D,IAAA,MAAM,EAAE,YAAY,EAAE,GAAG,YAAY,CAAC,OAAO,EAAE,QAAQ,EAAE,eAAe,CAAC;IAEzE,OAAO,YAAY,CAAC,CAAC,EAAE,SAAS,EAAE,SAAS,EAAE,KAAI;AAC/C,QAAA,MAAM,WAAW,GAAG,WAAW,CAC7B,SAAS,IAAI,iBAAiB,IAAI,MAAM,IAAI,OAAO,MAAM,CAAC,eAAe,KAAK,WAAW,CAC1F;AAED,QAAA,IAAI,CAAC,WAAW,EAAE,EAAE;YAClB,OAAO;gBACL,WAAW;AACX,gBAAA,UAAU,EAAE,WAAW,CAAC,KAAK,CAAC;AAC9B,gBAAA,QAAQ,EAAE,WAAW,CAAC,KAAK,CAAC;AAC5B,gBAAA,MAAM,EAAE,WAAW,CAAC,EAAE,CAAC;AACvB,gBAAA,WAAW,EAAE,WAAW,CAAC,EAAE,CAAC;AAC5B,gBAAA,KAAK,EAAE,OAAO;AACd,gBAAA,IAAI,EAAE,OAAO;AACb,gBAAA,KAAK,EAAE,OAAO;AACd,gBAAA,MAAM,EAAE,OAAO;aAChB;QACH;AAEA,QAAA,MAAM,EAAE,eAAe,EAAE,GAAG,MAAM;AAElC,QAAA,MAAM,UAAU,GAAG,MAAM,CAAC,KAAK,sDAAC;AAChC,QAAA,MAAM,QAAQ,GAAG,MAAM,CAAC,KAAK,oDAAC;QAC9B,MAAM,MAAM,GAAG,MAAM,CAAyB,eAAe,CAAC,SAAS,EAAE,EAAA,IAAA,SAAA,GAAA,CAAA,EAAA,SAAA,EAAA,QAAA,EAAA,CAAA,GAAA,EAAA,CAAA,CAAC;AAC1E,QAAA,MAAM,WAAW,GAAG,MAAM,CAAC,EAAE,uDAAC;QAE9B,MAAM,UAAU,GAAG,MAAK;AACtB,YAAA,MAAM,eAAe,GAAG,eAAe,CAAC,SAAS,EAAE;AACnD,YAAA,MAAM,CAAC,GAAG,CAAC,eAAe,CAAC;AAC7B,QAAA,CAAC;QAED,MAAM,mBAAmB,GAAG,MAAK;AAC/B,YAAA,UAAU,CAAC,GAAG,CAAC,eAAe,CAAC,QAAQ,CAAC;AACxC,YAAA,QAAQ,CAAC,GAAG,CAAC,eAAe,CAAC,MAAM,CAAC;AACtC,QAAA,CAAC;QAED,MAAM,WAAW,GAAG,MAAK;AACvB,YAAA,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC;AACpB,YAAA,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC;AACrB,QAAA,CAAC;QAED,MAAM,SAAS,GAAG,MAAK;AACrB,YAAA,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC;AACrB,YAAA,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC;AACnB,YAAA,WAAW,CAAC,GAAG,CAAC,EAAE,CAAC;AACrB,QAAA,CAAC;QAED,MAAM,WAAW,GAAG,MAAK;AACvB,YAAA,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC;AACrB,YAAA,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC;AACnB,YAAA,WAAW,CAAC,GAAG,CAAC,EAAE,CAAC;AACrB,QAAA,CAAC;QAED,MAAM,WAAW,GAAG,MAAK;AACvB,YAAA,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC;AACpB,QAAA,CAAC;QAED,MAAM,YAAY,GAAG,MAAK;AACxB,YAAA,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC;AACrB,QAAA,CAAC;AAED,QAAA,MAAM,KAAK,GAAG,CAAC,IAAY,KAAI;YAC7B,IAAI,CAAC,IAAI,EAAE;gBACT;YACF;YAEA,eAAe,CAAC,MAAM,EAAE;AAExB,YAAA,MAAM,SAAS,GAAG,IAAI,wBAAwB,CAAC,IAAI,CAAC;YAEpD,MAAM,IAAI,GAAG,OAAO,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,CAAC;YAC7C,MAAM,KAAK,GAAG,OAAO,CAAC,SAAS,CAAC,OAAO,EAAE,KAAK,CAAC;YAE/C,IAAI,IAAI,EAAE;AACR,gBAAA,SAAS,CAAC,IAAI,GAAG,IAAI;YACvB;YAEA,IAAI,KAAK,EAAE;AACT,gBAAA,SAAS,CAAC,KAAK,GAAG,KAAK;YACzB;AAEA,YAAA,SAAS,CAAC,IAAI,GAAG,OAAO,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,CAAC,IAAI,CAAC;AACtD,YAAA,SAAS,CAAC,KAAK,GAAG,OAAO,CAAC,SAAS,CAAC,OAAO,EAAE,KAAK,CAAC,IAAI,CAAC;AACxD,YAAA,SAAS,CAAC,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC,OAAO,EAAE,MAAM,CAAC,IAAI,CAAC;AAE1D,YAAA,SAAS,CAAC,OAAO,GAAG,WAAW;AAC/B,YAAA,SAAS,CAAC,KAAK,GAAG,SAAS;AAC3B,YAAA,SAAS,CAAC,OAAO,GAAG,WAAW;AAC/B,YAAA,SAAS,CAAC,OAAO,GAAG,WAAW;AAC/B,YAAA,SAAS,CAAC,QAAQ,GAAG,YAAY;AAEjC,YAAA,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC;AACrB,YAAA,eAAe,CAAC,KAAK,CAAC,SAAS,CAAC;AAEhC,YAAA,mBAAmB,EAAE;AACvB,QAAA,CAAC;QAED,MAAM,IAAI,GAAG,MAAK;YAChB,eAAe,CAAC,MAAM,EAAE;AACxB,YAAA,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC;AACrB,YAAA,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC;AACnB,YAAA,WAAW,CAAC,GAAG,CAAC,EAAE,CAAC;AACrB,QAAA,CAAC;QAED,MAAM,KAAK,GAAG,MAAK;YACjB,IAAI,eAAe,CAAC,QAAQ,IAAI,CAAC,eAAe,CAAC,MAAM,EAAE;gBACvD,eAAe,CAAC,KAAK,EAAE;AACvB,gBAAA,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC;YACpB;AACF,QAAA,CAAC;QAED,MAAM,MAAM,GAAG,MAAK;AAClB,YAAA,IAAI,eAAe,CAAC,MAAM,EAAE;gBAC1B,eAAe,CAAC,MAAM,EAAE;AACxB,gBAAA,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC;YACrB;AACF,QAAA,CAAC;QAED,SAAS,CAAC,IAAI,CAAC;AAEf,QAAA,IAAI,eAAe,CAAC,eAAe,KAAK,SAAS,EAAE;AACjD,YAAA,eAAe,CAAC,eAAe,GAAG,UAAU;QAC9C;QAEA,OAAO;YACL,WAAW;AACX,YAAA,UAAU,EAAE,UAAU,CAAC,UAAU,EAAE;AACnC,YAAA,QAAQ,EAAE,QAAQ,CAAC,UAAU,EAAE;AAC/B,YAAA,MAAM,EAAE,MAAM,CAAC,UAAU,EAAE;AAC3B,YAAA,WAAW,EAAE,WAAW,CAAC,UAAU,EAAE;YACrC,KAAK;YACL,IAAI;YACJ,KAAK;YACL,MAAM;SACP;AACH,IAAA,CAAC,CAAC;AACJ;;AC9QA;;AAEG;;;;"}
|
|
@@ -21,7 +21,7 @@ import { watcher } from '@signality/core/reactivity/watcher';
|
|
|
21
21
|
* <button (click)="count.set(count() + 1)">Increment</button>
|
|
22
22
|
* '
|
|
23
23
|
* })
|
|
24
|
-
* class
|
|
24
|
+
* export class UserPreview {
|
|
25
25
|
* readonly username = storage('username', '');
|
|
26
26
|
* readonly count = storage('counter', 0); // number serialization inferred
|
|
27
27
|
* readonly lastVisit = storage('lastVisit', new Date()); // Date serialization inferred
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"signality-core-browser-storage.mjs","sources":["../../../projects/core/browser/storage/index.ts","../../../projects/core/browser/storage/signality-core-browser-storage.ts"],"sourcesContent":["import { type CreateSignalOptions, isSignal, signal, type WritableSignal } from '@angular/core';\nimport { isPlainObject, proxySignal, setupContext, toValue } from '@signality/core/internal';\nimport type { MaybeSignal, WithInjector } from '@signality/core/types';\nimport { listener, setupSync } from '@signality/core/browser/listener';\nimport { watcher } from '@signality/core/reactivity/watcher';\n\nexport interface StorageOptions<T> extends CreateSignalOptions<T>, WithInjector {\n /**\n * Storage type to use.\n * @default 'local'\n */\n readonly type?: 'local' | 'session';\n\n /**\n * Custom serializer for read/write operations.\n *\n * If not provided, the serializer is automatically inferred from the initial value type:\n * - `string` → pass-through (no transformation)\n * - `number` → handles Infinity, -Infinity, NaN\n * - `boolean` → strict true/false conversion\n * - `bigint` → string representation\n * - `Date` → ISO 8601 format\n * - `Map` → JSON array of entries\n * - `Set` → JSON array\n * - `object/array` → JSON serialization\n *\n * @example\n * ```typescript\n * // Use built-in serializers\n * import { Serializers } from '@signality/core';\n *\n * const counter = storage('count', 0, {\n * serializer: Serializers.number,\n * });\n *\n * // or create a custom serializer\n * const userSettings = storage('settings', defaultSettings, {\n * serializer: {\n * write: (v) => JSON.stringify(v),\n * read: (s) => ({ ...defaultSettings, ...JSON.parse(s) }),\n * },\n * });\n * ```\n */\n readonly serializer?: Serializer<T>;\n\n /**\n * Merge resolver function when reading from storage.\n *\n * Receives stored value and default value, returns the final value.\n * Default: shallow merge for objects ({ ...initialValue, ...stored })\n *\n * Useful for handling schema migrations when default has new properties.\n *\n * @example\n * ```typescript\n * const settings = storage('settings', { theme: 'dark', fontSize: 14 }, {\n * mergeResolver: (stored, initial) => ({ ...initial, ...stored }),\n * });\n *\n * // Or with custom merge\n * const settings = storage('settings', defaultSettings, {\n * mergeResolver: (stored, initial) => deepMerge(stored, initial),\n * });\n * ```\n */\n readonly mergeResolver?: (storedValue: T, initialValue: T) => T;\n}\n\n/**\n * Serializer interface for converting values to/from strings for storage.\n */\nexport interface Serializer<T> {\n readonly write: (value: T) => string;\n readonly read: (raw: string) => T;\n}\n\n/**\n * Signal-based wrapper around the [Web Storage API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API) (localStorage/sessionStorage).\n *\n * @param key - Storage key (can be a signal for dynamic keys)\n * @param initialValue - Default value if key doesn't exist\n * @param options - Configuration options\n * @returns A WritableSignal that automatically syncs with storage\n *\n * @example\n * Basic usage with automatic serialization:\n * ```typescript\n * @Component({\n * template: '\n * <input [(ngModel)]=\"username\" />\n * <p>Count: {{ count() }}</p>\n * <button (click)=\"count.set(count() + 1)\">Increment</button>\n * '\n * })\n * class UserComponent {\n * readonly username = storage('username', '');\n * readonly count = storage('counter', 0); // number serialization inferred\n * readonly lastVisit = storage('lastVisit', new Date()); // Date serialization inferred\n * }\n * ```\n *\n * @example\n * With options:\n * ```typescript\n * const preferences = storage('prefs', defaultPrefs, {\n * type: 'session',\n * mergeWithInitial: true,\n * });\n * ```\n */\nexport function storage<T>(\n key: MaybeSignal<string>,\n initialValue: T,\n options?: StorageOptions<T>\n): WritableSignal<T> {\n const { runInContext } = setupContext(options?.injector, storage);\n\n return runInContext(({ isServer }) => {\n if (isServer) {\n return signal(initialValue, options);\n }\n\n const storageType = options?.type ?? 'local';\n const serializer = resolveSerializer(initialValue, options);\n\n const getStorage = (): Storage | null => {\n const type = storageType === 'local' ? 'localStorage' : 'sessionStorage';\n\n if (!storageAvailable(type)) {\n if (ngDevMode) {\n console.warn(`[storage] ${type} is not available or accessible`);\n }\n return null;\n }\n\n return window[type];\n };\n\n const mergeWithInitial = (storedValue: T) => {\n if (options?.mergeResolver) {\n return options.mergeResolver(storedValue, initialValue);\n }\n\n if (isPlainObject(initialValue)) {\n return { ...initialValue, ...storedValue };\n }\n\n return storedValue;\n };\n\n const readValue = (storageKey: string): T => {\n const storage = getStorage();\n\n if (storage === null) {\n return initialValue;\n }\n\n try {\n const raw = storage.getItem(storageKey);\n\n if (raw === null) {\n if (initialValue != null) {\n writeValue(initialValue);\n }\n return initialValue;\n }\n\n const parsed = serializer.read(raw);\n return mergeWithInitial(parsed);\n } catch (error) {\n if (ngDevMode) {\n console.warn(`[storage] Failed to deserialize value for key \"${key}\"`, error);\n }\n\n return initialValue;\n }\n };\n\n const writeValue = (value: T): void => {\n const storage = getStorage();\n const storageKey = toValue(key);\n\n if (storage === null) {\n return;\n }\n\n try {\n if (value == null) {\n storage.removeItem(storageKey);\n } else {\n const serialized = serializer.write(value);\n storage.setItem(storageKey, serialized);\n }\n } catch (error) {\n if (ngDevMode) {\n console.warn(\n `[storage] Failed to write value for key \"${storageKey}\". ` +\n `This may be due to storage quota exceeded or serialization error.`,\n error\n );\n }\n }\n };\n\n const state = signal<T>(readValue(toValue(key)), options);\n\n if (storageType === 'local') {\n setupSync(() => {\n listener(window, 'storage', event => {\n const currentKey = toValue(key);\n\n if (event.key === currentKey && event.storageArea === window.localStorage) {\n try {\n const newValue =\n event.newValue === null\n ? initialValue\n : mergeWithInitial(serializer.read(event.newValue));\n\n state.set(newValue);\n } catch (error) {\n if (ngDevMode) {\n console.warn(\n `[storage] Failed to sync value from other tab for key \"${event.key}\"`,\n error\n );\n }\n }\n }\n });\n });\n }\n\n if (isSignal(key)) {\n watcher(key, newKey => state.set(readValue(newKey)));\n }\n\n return proxySignal(state, {\n set: (value: T) => {\n state.set(value);\n writeValue(value);\n },\n });\n });\n}\n\nexport const Serializers = {\n string: {\n read: (v: string): string => v,\n write: (v: string): string => v,\n } satisfies Serializer<string>,\n\n number: {\n read: (v: string): number => {\n if (v === 'Infinity') return Infinity;\n if (v === '-Infinity') return -Infinity;\n if (v === 'NaN') return NaN;\n return Number.parseFloat(v);\n },\n write: (v: number): string => {\n if (Number.isNaN(v)) return 'NaN';\n if (v === Infinity) return 'Infinity';\n if (v === -Infinity) return '-Infinity';\n return String(v);\n },\n } satisfies Serializer<number>,\n\n boolean: {\n read: (v: string): boolean => v === 'true',\n write: (v: boolean): string => (v ? 'true' : 'false'),\n } satisfies Serializer<boolean>,\n\n bigint: {\n read: (v: string): bigint => BigInt(v),\n write: (v: bigint): string => v.toString(),\n } satisfies Serializer<bigint>,\n\n /*\n * Date serializer - uses ISO 8601 format for maximum compatibility.\n */\n date: {\n read: (v: string): Date => new Date(v),\n write: (v: Date): string => v.toISOString(),\n } satisfies Serializer<Date>,\n\n object: {\n read: <T>(v: string): T => JSON.parse(v) as T,\n write: <T>(v: T): string => JSON.stringify(v),\n } satisfies Serializer<unknown>,\n\n map: {\n read: <K, V>(v: string): Map<K, V> => new Map(JSON.parse(v)),\n write: <K, V>(v: Map<K, V>): string => JSON.stringify([...v.entries()]),\n } satisfies Serializer<Map<unknown, unknown>>,\n\n set: {\n read: <T>(v: string): Set<T> => new Set(JSON.parse(v)),\n write: <T>(v: Set<T>): string => JSON.stringify([...v]),\n } satisfies Serializer<Set<unknown>>,\n\n /*\n * Any serializer - fallback that treats everything as string.\n */\n any: {\n read: <T>(v: string): T => v as T,\n write: (v: unknown): string => String(v),\n } satisfies Serializer<unknown>,\n} as const;\n\nfunction resolveSerializer<T>(initialValue: T, options?: StorageOptions<T>): Serializer<T> {\n if (options?.serializer) {\n return options.serializer;\n }\n const type = inferSerializerType(initialValue);\n return Serializers[type] as Serializer<T>;\n}\n\nfunction inferSerializerType<T>(value: T): keyof typeof Serializers {\n if (value === null || value === undefined) {\n return 'any';\n }\n\n if (value instanceof Map) {\n return 'map';\n }\n\n if (value instanceof Set) {\n return 'set';\n }\n\n if (value instanceof Date) {\n return 'date';\n }\n\n switch (typeof value) {\n case 'string':\n return 'string';\n case 'number':\n return 'number';\n case 'boolean':\n return 'boolean';\n case 'bigint':\n return 'bigint';\n case 'object':\n return 'object';\n default:\n return 'any';\n }\n}\n\nfunction storageAvailable(type: 'localStorage' | 'sessionStorage'): boolean {\n let storage: Storage | undefined;\n\n try {\n storage = window[type];\n const testKey = '__storage_test__';\n storage.setItem(testKey, testKey);\n storage.removeItem(testKey);\n return true;\n } catch (e) {\n return (\n e instanceof DOMException &&\n e.name === 'QuotaExceededError' &&\n storage !== undefined &&\n storage.length !== 0\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-storage.mjs","sources":["../../../projects/core/browser/storage/index.ts","../../../projects/core/browser/storage/signality-core-browser-storage.ts"],"sourcesContent":["import { type CreateSignalOptions, isSignal, signal, type WritableSignal } from '@angular/core';\nimport { isPlainObject, proxySignal, setupContext, toValue } from '@signality/core/internal';\nimport type { MaybeSignal, WithInjector } from '@signality/core/types';\nimport { listener, setupSync } from '@signality/core/browser/listener';\nimport { watcher } from '@signality/core/reactivity/watcher';\n\nexport interface StorageOptions<T> extends CreateSignalOptions<T>, WithInjector {\n /**\n * Storage type to use.\n * @default 'local'\n */\n readonly type?: 'local' | 'session';\n\n /**\n * Custom serializer for read/write operations.\n *\n * If not provided, the serializer is automatically inferred from the initial value type:\n * - `string` → pass-through (no transformation)\n * - `number` → handles Infinity, -Infinity, NaN\n * - `boolean` → strict true/false conversion\n * - `bigint` → string representation\n * - `Date` → ISO 8601 format\n * - `Map` → JSON array of entries\n * - `Set` → JSON array\n * - `object/array` → JSON serialization\n *\n * @example\n * ```typescript\n * // Use built-in serializers\n * import { Serializers } from '@signality/core';\n *\n * const counter = storage('count', 0, {\n * serializer: Serializers.number,\n * });\n *\n * // or create a custom serializer\n * const userSettings = storage('settings', defaultSettings, {\n * serializer: {\n * write: (v) => JSON.stringify(v),\n * read: (s) => ({ ...defaultSettings, ...JSON.parse(s) }),\n * },\n * });\n * ```\n */\n readonly serializer?: Serializer<T>;\n\n /**\n * Merge resolver function when reading from storage.\n *\n * Receives stored value and default value, returns the final value.\n * Default: shallow merge for objects ({ ...initialValue, ...stored })\n *\n * Useful for handling schema migrations when default has new properties.\n *\n * @example\n * ```typescript\n * const settings = storage('settings', { theme: 'dark', fontSize: 14 }, {\n * mergeResolver: (stored, initial) => ({ ...initial, ...stored }),\n * });\n *\n * // Or with custom merge\n * const settings = storage('settings', defaultSettings, {\n * mergeResolver: (stored, initial) => deepMerge(stored, initial),\n * });\n * ```\n */\n readonly mergeResolver?: (storedValue: T, initialValue: T) => T;\n}\n\n/**\n * Serializer interface for converting values to/from strings for storage.\n */\nexport interface Serializer<T> {\n readonly write: (value: T) => string;\n readonly read: (raw: string) => T;\n}\n\n/**\n * Signal-based wrapper around the [Web Storage API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API) (localStorage/sessionStorage).\n *\n * @param key - Storage key (can be a signal for dynamic keys)\n * @param initialValue - Default value if key doesn't exist\n * @param options - Configuration options\n * @returns A WritableSignal that automatically syncs with storage\n *\n * @example\n * Basic usage with automatic serialization:\n * ```typescript\n * @Component({\n * template: '\n * <input [(ngModel)]=\"username\" />\n * <p>Count: {{ count() }}</p>\n * <button (click)=\"count.set(count() + 1)\">Increment</button>\n * '\n * })\n * export class UserPreview {\n * readonly username = storage('username', '');\n * readonly count = storage('counter', 0); // number serialization inferred\n * readonly lastVisit = storage('lastVisit', new Date()); // Date serialization inferred\n * }\n * ```\n *\n * @example\n * With options:\n * ```typescript\n * const preferences = storage('prefs', defaultPrefs, {\n * type: 'session',\n * mergeWithInitial: true,\n * });\n * ```\n */\nexport function storage<T>(\n key: MaybeSignal<string>,\n initialValue: T,\n options?: StorageOptions<T>\n): WritableSignal<T> {\n const { runInContext } = setupContext(options?.injector, storage);\n\n return runInContext(({ isServer }) => {\n if (isServer) {\n return signal(initialValue, options);\n }\n\n const storageType = options?.type ?? 'local';\n const serializer = resolveSerializer(initialValue, options);\n\n const getStorage = (): Storage | null => {\n const type = storageType === 'local' ? 'localStorage' : 'sessionStorage';\n\n if (!storageAvailable(type)) {\n if (ngDevMode) {\n console.warn(`[storage] ${type} is not available or accessible`);\n }\n return null;\n }\n\n return window[type];\n };\n\n const mergeWithInitial = (storedValue: T) => {\n if (options?.mergeResolver) {\n return options.mergeResolver(storedValue, initialValue);\n }\n\n if (isPlainObject(initialValue)) {\n return { ...initialValue, ...storedValue };\n }\n\n return storedValue;\n };\n\n const readValue = (storageKey: string): T => {\n const storage = getStorage();\n\n if (storage === null) {\n return initialValue;\n }\n\n try {\n const raw = storage.getItem(storageKey);\n\n if (raw === null) {\n if (initialValue != null) {\n writeValue(initialValue);\n }\n return initialValue;\n }\n\n const parsed = serializer.read(raw);\n return mergeWithInitial(parsed);\n } catch (error) {\n if (ngDevMode) {\n console.warn(`[storage] Failed to deserialize value for key \"${key}\"`, error);\n }\n\n return initialValue;\n }\n };\n\n const writeValue = (value: T): void => {\n const storage = getStorage();\n const storageKey = toValue(key);\n\n if (storage === null) {\n return;\n }\n\n try {\n if (value == null) {\n storage.removeItem(storageKey);\n } else {\n const serialized = serializer.write(value);\n storage.setItem(storageKey, serialized);\n }\n } catch (error) {\n if (ngDevMode) {\n console.warn(\n `[storage] Failed to write value for key \"${storageKey}\". ` +\n `This may be due to storage quota exceeded or serialization error.`,\n error\n );\n }\n }\n };\n\n const state = signal<T>(readValue(toValue(key)), options);\n\n if (storageType === 'local') {\n setupSync(() => {\n listener(window, 'storage', event => {\n const currentKey = toValue(key);\n\n if (event.key === currentKey && event.storageArea === window.localStorage) {\n try {\n const newValue =\n event.newValue === null\n ? initialValue\n : mergeWithInitial(serializer.read(event.newValue));\n\n state.set(newValue);\n } catch (error) {\n if (ngDevMode) {\n console.warn(\n `[storage] Failed to sync value from other tab for key \"${event.key}\"`,\n error\n );\n }\n }\n }\n });\n });\n }\n\n if (isSignal(key)) {\n watcher(key, newKey => state.set(readValue(newKey)));\n }\n\n return proxySignal(state, {\n set: (value: T) => {\n state.set(value);\n writeValue(value);\n },\n });\n });\n}\n\nexport const Serializers = {\n string: {\n read: (v: string): string => v,\n write: (v: string): string => v,\n } satisfies Serializer<string>,\n\n number: {\n read: (v: string): number => {\n if (v === 'Infinity') return Infinity;\n if (v === '-Infinity') return -Infinity;\n if (v === 'NaN') return NaN;\n return Number.parseFloat(v);\n },\n write: (v: number): string => {\n if (Number.isNaN(v)) return 'NaN';\n if (v === Infinity) return 'Infinity';\n if (v === -Infinity) return '-Infinity';\n return String(v);\n },\n } satisfies Serializer<number>,\n\n boolean: {\n read: (v: string): boolean => v === 'true',\n write: (v: boolean): string => (v ? 'true' : 'false'),\n } satisfies Serializer<boolean>,\n\n bigint: {\n read: (v: string): bigint => BigInt(v),\n write: (v: bigint): string => v.toString(),\n } satisfies Serializer<bigint>,\n\n /*\n * Date serializer - uses ISO 8601 format for maximum compatibility.\n */\n date: {\n read: (v: string): Date => new Date(v),\n write: (v: Date): string => v.toISOString(),\n } satisfies Serializer<Date>,\n\n object: {\n read: <T>(v: string): T => JSON.parse(v) as T,\n write: <T>(v: T): string => JSON.stringify(v),\n } satisfies Serializer<unknown>,\n\n map: {\n read: <K, V>(v: string): Map<K, V> => new Map(JSON.parse(v)),\n write: <K, V>(v: Map<K, V>): string => JSON.stringify([...v.entries()]),\n } satisfies Serializer<Map<unknown, unknown>>,\n\n set: {\n read: <T>(v: string): Set<T> => new Set(JSON.parse(v)),\n write: <T>(v: Set<T>): string => JSON.stringify([...v]),\n } satisfies Serializer<Set<unknown>>,\n\n /*\n * Any serializer - fallback that treats everything as string.\n */\n any: {\n read: <T>(v: string): T => v as T,\n write: (v: unknown): string => String(v),\n } satisfies Serializer<unknown>,\n} as const;\n\nfunction resolveSerializer<T>(initialValue: T, options?: StorageOptions<T>): Serializer<T> {\n if (options?.serializer) {\n return options.serializer;\n }\n const type = inferSerializerType(initialValue);\n return Serializers[type] as Serializer<T>;\n}\n\nfunction inferSerializerType<T>(value: T): keyof typeof Serializers {\n if (value === null || value === undefined) {\n return 'any';\n }\n\n if (value instanceof Map) {\n return 'map';\n }\n\n if (value instanceof Set) {\n return 'set';\n }\n\n if (value instanceof Date) {\n return 'date';\n }\n\n switch (typeof value) {\n case 'string':\n return 'string';\n case 'number':\n return 'number';\n case 'boolean':\n return 'boolean';\n case 'bigint':\n return 'bigint';\n case 'object':\n return 'object';\n default:\n return 'any';\n }\n}\n\nfunction storageAvailable(type: 'localStorage' | 'sessionStorage'): boolean {\n let storage: Storage | undefined;\n\n try {\n storage = window[type];\n const testKey = '__storage_test__';\n storage.setItem(testKey, testKey);\n storage.removeItem(testKey);\n return true;\n } catch (e) {\n return (\n e instanceof DOMException &&\n e.name === 'QuotaExceededError' &&\n storage !== undefined &&\n storage.length !== 0\n );\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|
|
@@ -6,9 +6,6 @@ import { mutationObserver } from '@signality/core/observers/mutation-observer';
|
|
|
6
6
|
* Reactive read/write wrapper around an element's `dir` attribute for detecting and controlling
|
|
7
7
|
* [text directionality](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/dir).
|
|
8
8
|
*
|
|
9
|
-
* Returns a `WritableSignal` that reflects the current `dir` attribute value and updates the DOM
|
|
10
|
-
* when written to. Changes made to the attribute externally are picked up via `MutationObserver`.
|
|
11
|
-
*
|
|
12
9
|
* @param options - Optional configuration including target element, initial value and injector
|
|
13
10
|
* @returns A writable signal of the current text direction
|
|
14
11
|
*
|
|
@@ -21,7 +18,7 @@ import { mutationObserver } from '@signality/core/observers/mutation-observer';
|
|
|
21
18
|
* <button (click)="dir.set('ltr')">Set LTR</button>
|
|
22
19
|
* `
|
|
23
20
|
* })
|
|
24
|
-
* class
|
|
21
|
+
* export class TextDirectionDemo {
|
|
25
22
|
* readonly dir = textDirection();
|
|
26
23
|
* }
|
|
27
24
|
* ```
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"signality-core-browser-text-direction.mjs","sources":["../../../projects/core/browser/text-direction/index.ts","../../../projects/core/browser/text-direction/signality-core-browser-text-direction.ts"],"sourcesContent":["import { CreateSignalOptions, signal, WritableSignal } from '@angular/core';\nimport { createToken, proxySignal, setupContext, toElement } from '@signality/core/internal';\nimport type { MaybeElementSignal, WithInjector } from '@signality/core/types';\nimport { mutationObserver } from '@signality/core/observers/mutation-observer';\n\n/**\n * Possible text direction values matching the HTML `dir` attribute.\n */\nexport type TextDirection = 'ltr' | 'rtl' | 'auto';\n\nexport interface TextDirectionOptions extends CreateSignalOptions<TextDirection>, WithInjector {\n /**\n * Element to observe. Defaults to `document.documentElement` (`<html>`).\n */\n readonly target?: MaybeElementSignal<HTMLElement>;\n\n /**\n * Initial
|
|
1
|
+
{"version":3,"file":"signality-core-browser-text-direction.mjs","sources":["../../../projects/core/browser/text-direction/index.ts","../../../projects/core/browser/text-direction/signality-core-browser-text-direction.ts"],"sourcesContent":["import { CreateSignalOptions, signal, WritableSignal } from '@angular/core';\nimport { createToken, proxySignal, setupContext, toElement } from '@signality/core/internal';\nimport type { MaybeElementSignal, WithInjector } from '@signality/core/types';\nimport { mutationObserver } from '@signality/core/observers/mutation-observer';\n\n/**\n * Possible text direction values matching the HTML `dir` attribute.\n */\nexport type TextDirection = 'ltr' | 'rtl' | 'auto';\n\nexport interface TextDirectionOptions extends CreateSignalOptions<TextDirection>, WithInjector {\n /**\n * Element to observe. Defaults to `document.documentElement` (`<html>`).\n */\n readonly target?: MaybeElementSignal<HTMLElement>;\n\n /**\n * Initial value for SSR.\n * @default 'ltr'\n */\n readonly initialValue?: TextDirection;\n}\n\n/**\n * Reactive read/write wrapper around an element's `dir` attribute for detecting and controlling\n * [text directionality](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/dir).\n *\n * @param options - Optional configuration including target element, initial value and injector\n * @returns A writable signal of the current text direction\n *\n * @example\n * ```typescript\n * @Component({\n * template: `\n * <p>Current direction: {{ dir() }}</p>\n * <button (click)=\"dir.set('rtl')\">Set RTL</button>\n * <button (click)=\"dir.set('ltr')\">Set LTR</button>\n * `\n * })\n * export class TextDirectionDemo {\n * readonly dir = textDirection();\n * }\n * ```\n */\nexport function textDirection(options?: TextDirectionOptions): WritableSignal<TextDirection> {\n const { runInContext } = setupContext(options?.injector, textDirection);\n const initialValue = options?.initialValue ?? 'ltr';\n\n return runInContext(({ isServer }) => {\n if (isServer) {\n return signal(initialValue, options);\n }\n\n const target = options?.target ?? document.documentElement;\n\n const readDir = (): TextDirection => {\n const el = toElement(target);\n return (el?.getAttribute('dir') as TextDirection) || initialValue;\n };\n\n const dir = signal<TextDirection>(readDir());\n\n mutationObserver(target, () => dir.set(readDir()), {\n attributes: true,\n attributeFilter: ['dir'],\n });\n\n return proxySignal(dir, {\n set: value => {\n const el = toElement(target);\n el?.setAttribute('dir', value);\n dir.set(value);\n },\n });\n });\n}\n\nexport const TEXT_DIRECTION = /* @__PURE__ */ createToken(textDirection);\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;;AAuBA;;;;;;;;;;;;;;;;;;;;AAoBG;AACG,SAAU,aAAa,CAAC,OAA8B,EAAA;AAC1D,IAAA,MAAM,EAAE,YAAY,EAAE,GAAG,YAAY,CAAC,OAAO,EAAE,QAAQ,EAAE,aAAa,CAAC;AACvE,IAAA,MAAM,YAAY,GAAG,OAAO,EAAE,YAAY,IAAI,KAAK;AAEnD,IAAA,OAAO,YAAY,CAAC,CAAC,EAAE,QAAQ,EAAE,KAAI;QACnC,IAAI,QAAQ,EAAE;AACZ,YAAA,OAAO,MAAM,CAAC,YAAY,EAAE,OAAO,CAAC;QACtC;QAEA,MAAM,MAAM,GAAG,OAAO,EAAE,MAAM,IAAI,QAAQ,CAAC,eAAe;QAE1D,MAAM,OAAO,GAAG,MAAoB;AAClC,YAAA,MAAM,EAAE,GAAG,SAAS,CAAC,MAAM,CAAC;YAC5B,OAAQ,EAAE,EAAE,YAAY,CAAC,KAAK,CAAmB,IAAI,YAAY;AACnE,QAAA,CAAC;AAED,QAAA,MAAM,GAAG,GAAG,MAAM,CAAgB,OAAO,EAAE,+CAAC;AAE5C,QAAA,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC,EAAE;AACjD,YAAA,UAAU,EAAE,IAAI;YAChB,eAAe,EAAE,CAAC,KAAK,CAAC;AACzB,SAAA,CAAC;QAEF,OAAO,WAAW,CAAC,GAAG,EAAE;YACtB,GAAG,EAAE,KAAK,IAAG;AACX,gBAAA,MAAM,EAAE,GAAG,SAAS,CAAC,MAAM,CAAC;AAC5B,gBAAA,EAAE,EAAE,YAAY,CAAC,KAAK,EAAE,KAAK,CAAC;AAC9B,gBAAA,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC;YAChB,CAAC;AACF,SAAA,CAAC;AACJ,IAAA,CAAC,CAAC;AACJ;AAEO,MAAM,cAAc,mBAAmB,WAAW,CAAC,aAAa;;AC7EvE;;AAEG;;;;"}
|
|
@@ -19,7 +19,7 @@ import { setupSync, listener } from '@signality/core/browser/listener';
|
|
|
19
19
|
* }
|
|
20
20
|
* `
|
|
21
21
|
* })
|
|
22
|
-
* class
|
|
22
|
+
* export class TextSelectionDemo {
|
|
23
23
|
* readonly selection = textSelection();
|
|
24
24
|
* }
|
|
25
25
|
* ```
|
|
@@ -67,4 +67,4 @@ function getRangesFromSelection(selection) {
|
|
|
67
67
|
*/
|
|
68
68
|
|
|
69
69
|
export { TEXT_SELECTION, textSelection };
|
|
70
|
-
//# sourceMappingURL=signality-core-
|
|
70
|
+
//# sourceMappingURL=signality-core-browser-text-selection.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signality-core-browser-text-selection.mjs","sources":["../../../projects/core/browser/text-selection/index.ts","../../../projects/core/browser/text-selection/signality-core-browser-text-selection.ts"],"sourcesContent":["import { computed, type Signal, signal } from '@angular/core';\nimport {\n ALWAYS_FALSE_FN,\n constSignal,\n createToken,\n NOOP_FN,\n setupContext,\n} from '@signality/core/internal';\nimport type { WithInjector } from '@signality/core/types';\nimport { listener, setupSync } from '@signality/core/browser/listener';\n\nexport type TextSelectionOptions = WithInjector;\n\nexport interface TextSelectionRef {\n /** The selected text content */\n readonly text: Signal<string>;\n\n /** Array of Range objects */\n readonly ranges: Signal<Range[]>;\n\n /** Bounding rectangles of selection */\n readonly rects: Signal<DOMRect[]>;\n\n /** The raw Selection object */\n readonly selection: Signal<Selection | null>;\n\n /** Clear the current text selection */\n readonly clear: () => void;\n}\n\n/**\n * Signal-based wrapper around the [Selection API](https://developer.mozilla.org/en-US/docs/Web/API/Selection_API).\n *\n * @param options - Optional configuration including injector\n * @returns A TextSelectionRef with reactive signals for text, ranges, rects, and selection\n *\n * @example\n * ```typescript\n * @Component({\n * template: `\n * <p>Select some text below:</p>\n * <div>Lorem ipsum dolor sit amet...</div>\n * @if (selection.text(); as text) {\n * <p>Selected: \"{{ text }}\"</p>\n * }\n * `\n * })\n * export class TextSelectionDemo {\n * readonly selection = textSelection();\n * }\n * ```\n */\nexport function textSelection(options?: TextSelectionOptions): TextSelectionRef {\n const { runInContext } = setupContext(options?.injector, textSelection);\n\n return runInContext(({ isServer }) => {\n if (isServer) {\n return {\n selection: constSignal(null),\n text: constSignal(''),\n ranges: constSignal([]),\n rects: constSignal([]),\n clear: NOOP_FN,\n };\n }\n\n const selection = signal<Selection | null>(window.getSelection(), { equal: ALWAYS_FALSE_FN });\n const text = computed(() => selection()?.toString() ?? '');\n const ranges = computed<Range[]>(() => {\n const sel = selection();\n return sel ? getRangesFromSelection(sel) : [];\n });\n const rects = computed(() => ranges().map(range => range.getBoundingClientRect()));\n\n const clear = () => window.getSelection()?.removeAllRanges();\n\n setupSync(() => {\n listener(document, 'selectionchange', () => selection.set(window.getSelection()));\n });\n\n return {\n selection: selection.asReadonly(),\n text,\n ranges,\n rects,\n clear,\n };\n });\n}\n\nexport const TEXT_SELECTION = /* @__PURE__ */ createToken(textSelection);\n\nfunction getRangesFromSelection(selection: Selection): Range[] {\n const rangeCount = selection.rangeCount ?? 0;\n return Array.from({ length: rangeCount }, (_, i) => selection.getRangeAt(i));\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;;AA8BA;;;;;;;;;;;;;;;;;;;;;AAqBG;AACG,SAAU,aAAa,CAAC,OAA8B,EAAA;AAC1D,IAAA,MAAM,EAAE,YAAY,EAAE,GAAG,YAAY,CAAC,OAAO,EAAE,QAAQ,EAAE,aAAa,CAAC;AAEvE,IAAA,OAAO,YAAY,CAAC,CAAC,EAAE,QAAQ,EAAE,KAAI;QACnC,IAAI,QAAQ,EAAE;YACZ,OAAO;AACL,gBAAA,SAAS,EAAE,WAAW,CAAC,IAAI,CAAC;AAC5B,gBAAA,IAAI,EAAE,WAAW,CAAC,EAAE,CAAC;AACrB,gBAAA,MAAM,EAAE,WAAW,CAAC,EAAE,CAAC;AACvB,gBAAA,KAAK,EAAE,WAAW,CAAC,EAAE,CAAC;AACtB,gBAAA,KAAK,EAAE,OAAO;aACf;QACH;AAEA,QAAA,MAAM,SAAS,GAAG,MAAM,CAAmB,MAAM,CAAC,YAAY,EAAE,EAAA,IAAA,SAAA,GAAA,CAAA,EAAA,SAAA,EAAA,WAAA,EAAI,KAAK,EAAE,eAAe,OAAxB,EAAE,KAAK,EAAE,eAAe,EAAE,GAAC;AAC7F,QAAA,MAAM,IAAI,GAAG,QAAQ,CAAC,MAAM,SAAS,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,gDAAC;AAC1D,QAAA,MAAM,MAAM,GAAG,QAAQ,CAAU,MAAK;AACpC,YAAA,MAAM,GAAG,GAAG,SAAS,EAAE;AACvB,YAAA,OAAO,GAAG,GAAG,sBAAsB,CAAC,GAAG,CAAC,GAAG,EAAE;AAC/C,QAAA,CAAC,kDAAC;QACF,MAAM,KAAK,GAAG,QAAQ,CAAC,MAAM,MAAM,EAAE,CAAC,GAAG,CAAC,KAAK,IAAI,KAAK,CAAC,qBAAqB,EAAE,CAAC,EAAA,IAAA,SAAA,GAAA,CAAA,EAAA,SAAA,EAAA,OAAA,EAAA,CAAA,GAAA,EAAA,CAAA,CAAC;AAElF,QAAA,MAAM,KAAK,GAAG,MAAM,MAAM,CAAC,YAAY,EAAE,EAAE,eAAe,EAAE;QAE5D,SAAS,CAAC,MAAK;AACb,YAAA,QAAQ,CAAC,QAAQ,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC,YAAY,EAAE,CAAC,CAAC;AACnF,QAAA,CAAC,CAAC;QAEF,OAAO;AACL,YAAA,SAAS,EAAE,SAAS,CAAC,UAAU,EAAE;YACjC,IAAI;YACJ,MAAM;YACN,KAAK;YACL,KAAK;SACN;AACH,IAAA,CAAC,CAAC;AACJ;AAEO,MAAM,cAAc,mBAAmB,WAAW,CAAC,aAAa;AAEvE,SAAS,sBAAsB,CAAC,SAAoB,EAAA;AAClD,IAAA,MAAM,UAAU,GAAG,SAAS,CAAC,UAAU,IAAI,CAAC;IAC5C,OAAO,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,SAAS,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;AAC9E;;AC/FA;;AAEG;;;;"}
|
|
@@ -2,21 +2,30 @@ import { signal } from '@angular/core';
|
|
|
2
2
|
import { setupContext, constSignal, NOOP_FN, toValue } from '@signality/core/internal';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* Signal-based wrapper around the Vibration API.
|
|
5
|
+
* Signal-based wrapper around the [Vibration API](https://developer.mozilla.org/en-US/docs/Web/API/Vibration_API).
|
|
6
6
|
*
|
|
7
7
|
* @param options - Optional configuration including default pattern and injector
|
|
8
8
|
* @returns A VibrationRef with vibration control methods
|
|
9
9
|
*
|
|
10
10
|
* @example
|
|
11
11
|
* ```typescript
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* vib.
|
|
15
|
-
* vib.vibrate(
|
|
12
|
+
* @Component({
|
|
13
|
+
* template: `
|
|
14
|
+
* @if (vib.isSupported()) {
|
|
15
|
+
* <button (click)="vib.vibrate(100)">Vibrate 100ms</button>
|
|
16
|
+
* <button (click)="vib.vibrate([100, 50, 100])">Pattern</button>
|
|
17
|
+
* <button (click)="vib.stop()">Stop</button>
|
|
18
|
+
* }
|
|
19
|
+
* `
|
|
20
|
+
* })
|
|
21
|
+
* export class VibrationDemo {
|
|
22
|
+
* readonly vib = vibration();
|
|
23
|
+
* }
|
|
16
24
|
* ```
|
|
17
25
|
*
|
|
18
26
|
* @example
|
|
19
27
|
* ```typescript
|
|
28
|
+
* // With a default pattern
|
|
20
29
|
* const vib = vibration({ pattern: 200 });
|
|
21
30
|
*
|
|
22
31
|
* vib.vibrate(); // Uses default 200ms pattern
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"signality-core-browser-vibration.mjs","sources":["../../../projects/core/browser/vibration/index.ts","../../../projects/core/browser/vibration/signality-core-browser-vibration.ts"],"sourcesContent":["import { type Signal, signal } from '@angular/core';\nimport { constSignal, NOOP_FN, setupContext, type Timer, toValue } from '@signality/core/internal';\nimport type { MaybeSignal, WithInjector } from '@signality/core/types';\n\nexport interface VibrationOptions extends WithInjector {\n readonly pattern?: MaybeSignal<number | number[]>;\n}\n\nexport interface VibrationRef {\n
|
|
1
|
+
{"version":3,"file":"signality-core-browser-vibration.mjs","sources":["../../../projects/core/browser/vibration/index.ts","../../../projects/core/browser/vibration/signality-core-browser-vibration.ts"],"sourcesContent":["import { type Signal, signal } from '@angular/core';\nimport { constSignal, NOOP_FN, setupContext, type Timer, toValue } from '@signality/core/internal';\nimport type { MaybeSignal, WithInjector } from '@signality/core/types';\n\nexport interface VibrationOptions extends WithInjector {\n /**\n * Default vibration pattern in milliseconds. A single number vibrates for that duration;\n * an array alternates between vibration and pause durations.\n *\n * @see [Navigator: vibrate() pattern on MDN](https://developer.mozilla.org/en-US/docs/Web/API/Navigator/vibrate#pattern)\n */\n readonly pattern?: MaybeSignal<number | number[]>;\n}\n\nexport interface VibrationRef {\n /**\n * Whether the Vibration API is supported in the current browser.\n *\n * @see [Vibration API browser compatibility on MDN](https://developer.mozilla.org/en-US/docs/Web/API/Vibration_API#browser_compatibility)\n */\n readonly isSupported: Signal<boolean>;\n\n /**\n * Whether the device is currently vibrating.\n */\n readonly isVibrating: Signal<boolean>;\n\n /**\n * Trigger vibration with an optional pattern. Falls back to the default pattern from options, then `200ms`.\n *\n * @see [Navigator: vibrate() on MDN](https://developer.mozilla.org/en-US/docs/Web/API/Navigator/vibrate)\n */\n readonly vibrate: (pattern?: number | number[]) => void;\n\n /**\n * Stop any active vibration by calling `navigator.vibrate(0)`.\n *\n * @see [Navigator: vibrate() on MDN](https://developer.mozilla.org/en-US/docs/Web/API/Navigator/vibrate)\n */\n readonly stop: () => void;\n}\n\n/**\n * Signal-based wrapper around the [Vibration API](https://developer.mozilla.org/en-US/docs/Web/API/Vibration_API).\n *\n * @param options - Optional configuration including default pattern and injector\n * @returns A VibrationRef with vibration control methods\n *\n * @example\n * ```typescript\n * @Component({\n * template: `\n * @if (vib.isSupported()) {\n * <button (click)=\"vib.vibrate(100)\">Vibrate 100ms</button>\n * <button (click)=\"vib.vibrate([100, 50, 100])\">Pattern</button>\n * <button (click)=\"vib.stop()\">Stop</button>\n * }\n * `\n * })\n * export class VibrationDemo {\n * readonly vib = vibration();\n * }\n * ```\n *\n * @example\n * ```typescript\n * // With a default pattern\n * const vib = vibration({ pattern: 200 });\n *\n * vib.vibrate(); // Uses default 200ms pattern\n * ```\n */\nexport function vibration(options?: VibrationOptions): VibrationRef {\n const { runInContext } = setupContext(options?.injector, vibration);\n\n return runInContext(({ isBrowser, onCleanup }) => {\n const isSupported = constSignal(isBrowser && 'vibrate' in navigator);\n\n if (!isSupported()) {\n return {\n isSupported,\n isVibrating: constSignal(false),\n vibrate: NOOP_FN,\n stop: NOOP_FN,\n };\n }\n\n const isVibrating = signal(false);\n\n let vibrateTimeout: Timer;\n\n const vibrate = (vibratePattern?: number | number[]) => {\n const resolvedPattern = vibratePattern ?? toValue.untracked(options?.pattern) ?? 200;\n\n try {\n const result = navigator.vibrate(resolvedPattern);\n\n isVibrating.set(result);\n\n const duration = Array.isArray(resolvedPattern)\n ? resolvedPattern.reduce((a, b) => a + b, 0)\n : resolvedPattern;\n\n if (result && duration > 0) {\n if (vibrateTimeout) {\n clearTimeout(vibrateTimeout);\n }\n\n vibrateTimeout = setTimeout(() => {\n isVibrating.set(false);\n vibrateTimeout = undefined;\n }, duration);\n }\n } catch (error) {\n isVibrating.set(false);\n if (ngDevMode) {\n console.warn(`[vibration] Failed to vibrate device.`, error);\n }\n }\n };\n\n const stop = () => {\n try {\n navigator.vibrate(0);\n isVibrating.set(false);\n } catch (error) {\n if (ngDevMode) {\n console.warn(`[vibration] Failed to stop vibration.`, error);\n }\n }\n\n if (vibrateTimeout) {\n clearTimeout(vibrateTimeout);\n vibrateTimeout = undefined;\n }\n };\n\n onCleanup(stop);\n\n return {\n isSupported,\n isVibrating: isVibrating.asReadonly(),\n vibrate,\n stop,\n };\n });\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;AA0CA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6BG;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;QAC/C,MAAM,WAAW,GAAG,WAAW,CAAC,SAAS,IAAI,SAAS,IAAI,SAAS,CAAC;AAEpE,QAAA,IAAI,CAAC,WAAW,EAAE,EAAE;YAClB,OAAO;gBACL,WAAW;AACX,gBAAA,WAAW,EAAE,WAAW,CAAC,KAAK,CAAC;AAC/B,gBAAA,OAAO,EAAE,OAAO;AAChB,gBAAA,IAAI,EAAE,OAAO;aACd;QACH;AAEA,QAAA,MAAM,WAAW,GAAG,MAAM,CAAC,KAAK,uDAAC;AAEjC,QAAA,IAAI,cAAqB;AAEzB,QAAA,MAAM,OAAO,GAAG,CAAC,cAAkC,KAAI;AACrD,YAAA,MAAM,eAAe,GAAG,cAAc,IAAI,OAAO,CAAC,SAAS,CAAC,OAAO,EAAE,OAAO,CAAC,IAAI,GAAG;AAEpF,YAAA,IAAI;gBACF,MAAM,MAAM,GAAG,SAAS,CAAC,OAAO,CAAC,eAAe,CAAC;AAEjD,gBAAA,WAAW,CAAC,GAAG,CAAC,MAAM,CAAC;AAEvB,gBAAA,MAAM,QAAQ,GAAG,KAAK,CAAC,OAAO,CAAC,eAAe;AAC5C,sBAAE,eAAe,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;sBACzC,eAAe;AAEnB,gBAAA,IAAI,MAAM,IAAI,QAAQ,GAAG,CAAC,EAAE;oBAC1B,IAAI,cAAc,EAAE;wBAClB,YAAY,CAAC,cAAc,CAAC;oBAC9B;AAEA,oBAAA,cAAc,GAAG,UAAU,CAAC,MAAK;AAC/B,wBAAA,WAAW,CAAC,GAAG,CAAC,KAAK,CAAC;wBACtB,cAAc,GAAG,SAAS;oBAC5B,CAAC,EAAE,QAAQ,CAAC;gBACd;YACF;YAAE,OAAO,KAAK,EAAE;AACd,gBAAA,WAAW,CAAC,GAAG,CAAC,KAAK,CAAC;gBACtB,IAAI,SAAS,EAAE;AACb,oBAAA,OAAO,CAAC,IAAI,CAAC,uCAAuC,EAAE,KAAK,CAAC;gBAC9D;YACF;AACF,QAAA,CAAC;QAED,MAAM,IAAI,GAAG,MAAK;AAChB,YAAA,IAAI;AACF,gBAAA,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC;AACpB,gBAAA,WAAW,CAAC,GAAG,CAAC,KAAK,CAAC;YACxB;YAAE,OAAO,KAAK,EAAE;gBACd,IAAI,SAAS,EAAE;AACb,oBAAA,OAAO,CAAC,IAAI,CAAC,uCAAuC,EAAE,KAAK,CAAC;gBAC9D;YACF;YAEA,IAAI,cAAc,EAAE;gBAClB,YAAY,CAAC,cAAc,CAAC;gBAC5B,cAAc,GAAG,SAAS;YAC5B;AACF,QAAA,CAAC;QAED,SAAS,CAAC,IAAI,CAAC;QAEf,OAAO;YACL,WAAW;AACX,YAAA,WAAW,EAAE,WAAW,CAAC,UAAU,EAAE;YACrC,OAAO;YACP,IAAI;SACL;AACH,IAAA,CAAC,CAAC;AACJ;;AClJA;;AAEG;;;;"}
|
|
@@ -5,17 +5,24 @@ import { PAGE_VISIBILITY } from '@signality/core/browser/page-visibility';
|
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Signal-based wrapper around the [Screen Wake Lock API](https://developer.mozilla.org/en-US/docs/Web/API/Screen_Wake_Lock_API).
|
|
8
|
+
* Prevents the screen from turning off while the wake lock is active.
|
|
8
9
|
*
|
|
9
10
|
* @param options - Optional configuration
|
|
10
11
|
* @returns A WakeLockRef with isSupported, isActive, sentinel signals and control methods
|
|
11
12
|
*
|
|
12
13
|
* @example
|
|
13
14
|
* ```typescript
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
15
|
+
* @Component({
|
|
16
|
+
* template: `
|
|
17
|
+
* @if (wl.isSupported()) {
|
|
18
|
+
* <button (click)="wl.request()">Keep screen on</button>
|
|
19
|
+
* <button (click)="wl.release()">Release</button>
|
|
20
|
+
* <p>Active: {{ wl.isActive() }}</p>
|
|
21
|
+
* }
|
|
22
|
+
* `
|
|
23
|
+
* })
|
|
24
|
+
* export class WakeLockDemo {
|
|
25
|
+
* readonly wl = wakeLock();
|
|
19
26
|
* }
|
|
20
27
|
* ```
|
|
21
28
|
*/
|
|
@@ -23,7 +30,7 @@ function wakeLock(options) {
|
|
|
23
30
|
const { runInContext } = setupContext(options?.injector, wakeLock);
|
|
24
31
|
return runInContext(({ isBrowser, onCleanup, injector }) => {
|
|
25
32
|
const sentinel = signal(null, ...(ngDevMode ? [{ debugName: "sentinel" }] : []));
|
|
26
|
-
const
|
|
33
|
+
const pendingRequest = signal(false, ...(ngDevMode ? [{ debugName: "pendingRequest" }] : []));
|
|
27
34
|
const isSupported = constSignal(isBrowser &&
|
|
28
35
|
'wakeLock' in navigator &&
|
|
29
36
|
typeof navigator.wakeLock?.request === 'function');
|
|
@@ -44,7 +51,11 @@ function wakeLock(options) {
|
|
|
44
51
|
return !!s && !s.released && visibility() === 'visible';
|
|
45
52
|
}, ...(ngDevMode ? [{ debugName: "isActive" }] : []));
|
|
46
53
|
let listenerRef = null;
|
|
54
|
+
let isRequesting = false;
|
|
47
55
|
const forceRequest = async () => {
|
|
56
|
+
if (isRequesting)
|
|
57
|
+
return;
|
|
58
|
+
isRequesting = true;
|
|
48
59
|
try {
|
|
49
60
|
const currentSentinel = untracked(sentinel);
|
|
50
61
|
if (currentSentinel) {
|
|
@@ -60,7 +71,7 @@ function wakeLock(options) {
|
|
|
60
71
|
const newSentinel = await navigator.wakeLock.request('screen');
|
|
61
72
|
sentinel.set(newSentinel);
|
|
62
73
|
listenerRef = setupSync(() => listener(newSentinel, 'release', () => {
|
|
63
|
-
|
|
74
|
+
pendingRequest.set(false);
|
|
64
75
|
sentinel.set(null);
|
|
65
76
|
listenerRef = null;
|
|
66
77
|
}, { injector }));
|
|
@@ -68,11 +79,17 @@ function wakeLock(options) {
|
|
|
68
79
|
}
|
|
69
80
|
catch (err) {
|
|
70
81
|
sentinel.set(null);
|
|
71
|
-
|
|
82
|
+
pendingRequest.set(false);
|
|
72
83
|
if (listenerRef) {
|
|
84
|
+
listenerRef.destroy();
|
|
73
85
|
listenerRef = null;
|
|
74
86
|
}
|
|
75
|
-
|
|
87
|
+
if (ngDevMode) {
|
|
88
|
+
console.warn('[wakeLock] Failed to acquire wake lock.', err);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
finally {
|
|
92
|
+
isRequesting = false;
|
|
76
93
|
}
|
|
77
94
|
};
|
|
78
95
|
const request = async () => {
|
|
@@ -80,11 +97,11 @@ function wakeLock(options) {
|
|
|
80
97
|
await forceRequest();
|
|
81
98
|
}
|
|
82
99
|
else {
|
|
83
|
-
|
|
100
|
+
pendingRequest.set(true);
|
|
84
101
|
}
|
|
85
102
|
};
|
|
86
103
|
const release = async () => {
|
|
87
|
-
|
|
104
|
+
pendingRequest.set(false);
|
|
88
105
|
const currentSentinel = untracked(sentinel);
|
|
89
106
|
sentinel.set(null);
|
|
90
107
|
if (currentSentinel) {
|
|
@@ -107,17 +124,17 @@ function wakeLock(options) {
|
|
|
107
124
|
if (autoReacquire) {
|
|
108
125
|
effect(() => {
|
|
109
126
|
const isVisible = visibility() === 'visible';
|
|
110
|
-
const
|
|
111
|
-
if (isVisible &&
|
|
112
|
-
|
|
127
|
+
const isPending = pendingRequest();
|
|
128
|
+
if (isVisible && isPending) {
|
|
129
|
+
pendingRequest.set(false);
|
|
113
130
|
forceRequest().catch(() => {
|
|
114
|
-
/*
|
|
131
|
+
/* handled inside forceRequest */
|
|
115
132
|
});
|
|
116
133
|
}
|
|
117
134
|
});
|
|
118
135
|
}
|
|
119
136
|
onCleanup(() => {
|
|
120
|
-
const currentSentinel = sentinel
|
|
137
|
+
const currentSentinel = untracked(sentinel);
|
|
121
138
|
if (currentSentinel) {
|
|
122
139
|
if (listenerRef) {
|
|
123
140
|
listenerRef.destroy();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"signality-core-browser-wake-lock.mjs","sources":["../../../projects/core/browser/wake-lock/index.ts","../../../projects/core/browser/wake-lock/signality-core-browser-wake-lock.ts"],"sourcesContent":["import { computed, effect, inject, type Signal, signal, untracked } from '@angular/core';\nimport { constSignal, NOOP_ASYNC_FN, setupContext } from '@signality/core/internal';\nimport type { WithInjector } from '@signality/core/types';\nimport { listener, type ListenerRef, setupSync } from '@signality/core/browser/listener';\nimport { PAGE_VISIBILITY } from '@signality/core/browser/page-visibility';\n\nexport interface WakeLockOptions extends WithInjector {\n /** Whether to automatically reacquire wake lock when document becomes visible */\n readonly autoReacquire?: boolean;\n}\n\nexport interface WakeLockRef {\n /** Whether Screen Wake Lock API is supported */\n readonly isSupported: Signal<boolean>;\n\n /** Whether wake lock is currently active */\n readonly isActive: Signal<boolean>;\n\n /** Current wake lock sentinel */\n readonly sentinel: Signal<WakeLockSentinel | null>;\n\n /** Request a wake lock */\n readonly request: () => Promise<void>;\n\n /** Force request a wake lock (releases existing one first) */\n readonly forceRequest: () => Promise<void>;\n\n /** Release the wake lock */\n readonly release: () => Promise<void>;\n}\n\n/**\n * Signal-based wrapper around the [Screen Wake Lock API](https://developer.mozilla.org/en-US/docs/Web/API/Screen_Wake_Lock_API).\n *\n * @param options - Optional configuration\n * @returns A WakeLockRef with isSupported, isActive, sentinel signals and control methods\n *\n * @example\n * ```typescript\n * const wakeLock = wakeLock();\n *\n * if (wakeLock.isSupported()) {\n * await wakeLock.request();\n * // Screen will stay awake\n * }\n * ```\n */\nexport function wakeLock(options?: WakeLockOptions): WakeLockRef {\n const { runInContext } = setupContext(options?.injector, wakeLock);\n\n return runInContext(({ isBrowser, onCleanup, injector }) => {\n const sentinel = signal<WakeLockSentinel | null>(null);\n const requestedType = signal<'screen' | false>(false);\n\n const isSupported = constSignal(\n isBrowser &&\n 'wakeLock' in navigator &&\n typeof (navigator as NavigatorWithWakeLock).wakeLock?.request === 'function'\n );\n\n if (!isSupported()) {\n return {\n isSupported,\n isActive: constSignal(false),\n sentinel: sentinel.asReadonly(),\n request: NOOP_ASYNC_FN,\n forceRequest: NOOP_ASYNC_FN,\n release: NOOP_ASYNC_FN,\n };\n }\n\n const visibility = inject(PAGE_VISIBILITY);\n\n const { autoReacquire = true } = options ?? {};\n\n const isActive = computed(() => {\n const s = sentinel();\n return !!s && !s.released && visibility() === 'visible';\n });\n\n let listenerRef: ListenerRef | null = null;\n\n const forceRequest = async (): Promise<void> => {\n try {\n const currentSentinel = untracked(sentinel);\n\n if (currentSentinel) {\n if (listenerRef) {\n listenerRef.destroy();\n listenerRef = null;\n }\n\n if (!currentSentinel.released) {\n await currentSentinel.release();\n }\n }\n\n if (untracked(visibility) === 'visible') {\n const newSentinel = await (navigator as NavigatorWithWakeLock).wakeLock.request('screen');\n\n sentinel.set(newSentinel);\n\n listenerRef = setupSync(() =>\n listener(\n newSentinel,\n 'release',\n () => {\n requestedType.set(false);\n sentinel.set(null);\n listenerRef = null;\n },\n { injector }\n )\n );\n }\n } catch (err) {\n sentinel.set(null);\n requestedType.set(false);\n\n if (listenerRef) {\n listenerRef = null;\n }\n\n throw err;\n }\n };\n\n const request = async (): Promise<void> => {\n if (untracked(visibility) === 'visible') {\n await forceRequest();\n } else {\n requestedType.set('screen');\n }\n };\n\n const release = async (): Promise<void> => {\n requestedType.set(false);\n\n const currentSentinel = untracked(sentinel);\n\n sentinel.set(null);\n\n if (currentSentinel) {\n if (listenerRef) {\n listenerRef.destroy();\n listenerRef = null;\n }\n if (!currentSentinel.released) {\n try {\n await currentSentinel.release();\n } catch (err) {\n if (ngDevMode) {\n console.warn('[wakeLock] Failed to release wake lock.', err);\n }\n }\n }\n }\n };\n\n if (autoReacquire) {\n effect(() => {\n const isVisible = visibility() === 'visible';\n const pendingType = requestedType();\n\n if (isVisible && pendingType) {\n requestedType.set(false);\n forceRequest().catch(() => {\n /* ignore errors, will retry on next visibility change */\n });\n }\n });\n }\n\n onCleanup(() => {\n const currentSentinel = sentinel();\n if (currentSentinel) {\n if (listenerRef) {\n listenerRef.destroy();\n listenerRef = null;\n }\n if (!currentSentinel.released) {\n currentSentinel.release().catch(() => {\n /* ignore errors */\n });\n }\n }\n });\n\n return {\n isSupported,\n isActive,\n sentinel: sentinel.asReadonly(),\n request,\n forceRequest,\n release,\n };\n });\n}\n\ninterface NavigatorWithWakeLock extends Navigator {\n readonly wakeLock: {\n readonly request: (type: 'screen') => Promise<WakeLockSentinel>;\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-wake-lock.mjs","sources":["../../../projects/core/browser/wake-lock/index.ts","../../../projects/core/browser/wake-lock/signality-core-browser-wake-lock.ts"],"sourcesContent":["import { computed, effect, inject, type Signal, signal, untracked } from '@angular/core';\nimport { constSignal, NOOP_ASYNC_FN, setupContext } from '@signality/core/internal';\nimport type { WithInjector } from '@signality/core/types';\nimport { listener, type ListenerRef, setupSync } from '@signality/core/browser/listener';\nimport { PAGE_VISIBILITY } from '@signality/core/browser/page-visibility';\n\nexport interface WakeLockOptions extends WithInjector {\n /**\n * Whether to automatically reacquire the wake lock when the document becomes visible again\n * after being hidden (e.g. tab switch or screen lock).\n *\n * Defaults to `true`.\n *\n * @see [Page Visibility API on MDN](https://developer.mozilla.org/en-US/docs/Web/API/Page_Visibility_API)\n */\n readonly autoReacquire?: boolean;\n}\n\nexport interface WakeLockRef {\n /**\n * Whether the Screen Wake Lock API is supported in the current browser.\n *\n * @see [Screen Wake Lock API browser compatibility on MDN](https://developer.mozilla.org/en-US/docs/Web/API/Screen_Wake_Lock_API#browser_compatibility)\n */\n readonly isSupported: Signal<boolean>;\n\n /**\n * Whether the wake lock is currently active.\n * `false` when the sentinel is released or the document is not visible.\n */\n readonly isActive: Signal<boolean>;\n\n /**\n * The active `WakeLockSentinel` instance, or `null` when no wake lock is held.\n *\n * @see [WakeLockSentinel on MDN](https://developer.mozilla.org/en-US/docs/Web/API/WakeLockSentinel)\n */\n readonly sentinel: Signal<WakeLockSentinel | null>;\n\n /**\n * Request a wake lock. No-op if a lock is already active.\n * If the document is not visible, the request will be deferred until it becomes visible\n * (only when `autoReacquire` is `true`).\n *\n * @see [WakeLock.request() on MDN](https://developer.mozilla.org/en-US/docs/Web/API/WakeLock/request)\n */\n readonly request: () => Promise<void>;\n\n /**\n * Request a wake lock unconditionally, releasing the existing sentinel first if present.\n *\n * @see [WakeLockSentinel.release() on MDN](https://developer.mozilla.org/en-US/docs/Web/API/WakeLockSentinel/release)\n */\n readonly forceRequest: () => Promise<void>;\n\n /**\n * Release the active wake lock and clear the sentinel.\n *\n * @see [WakeLockSentinel.release() on MDN](https://developer.mozilla.org/en-US/docs/Web/API/WakeLockSentinel/release)\n */\n readonly release: () => Promise<void>;\n}\n\n/**\n * Signal-based wrapper around the [Screen Wake Lock API](https://developer.mozilla.org/en-US/docs/Web/API/Screen_Wake_Lock_API).\n * Prevents the screen from turning off while the wake lock is active.\n *\n * @param options - Optional configuration\n * @returns A WakeLockRef with isSupported, isActive, sentinel signals and control methods\n *\n * @example\n * ```typescript\n * @Component({\n * template: `\n * @if (wl.isSupported()) {\n * <button (click)=\"wl.request()\">Keep screen on</button>\n * <button (click)=\"wl.release()\">Release</button>\n * <p>Active: {{ wl.isActive() }}</p>\n * }\n * `\n * })\n * export class WakeLockDemo {\n * readonly wl = wakeLock();\n * }\n * ```\n */\nexport function wakeLock(options?: WakeLockOptions): WakeLockRef {\n const { runInContext } = setupContext(options?.injector, wakeLock);\n\n return runInContext(({ isBrowser, onCleanup, injector }) => {\n const sentinel = signal<WakeLockSentinel | null>(null);\n const pendingRequest = signal(false);\n\n const isSupported = constSignal(\n isBrowser &&\n 'wakeLock' in navigator &&\n typeof (navigator as NavigatorWithWakeLock).wakeLock?.request === 'function'\n );\n\n if (!isSupported()) {\n return {\n isSupported,\n isActive: constSignal(false),\n sentinel: sentinel.asReadonly(),\n request: NOOP_ASYNC_FN,\n forceRequest: NOOP_ASYNC_FN,\n release: NOOP_ASYNC_FN,\n };\n }\n\n const visibility = inject(PAGE_VISIBILITY);\n\n const { autoReacquire = true } = options ?? {};\n\n const isActive = computed(() => {\n const s = sentinel();\n return !!s && !s.released && visibility() === 'visible';\n });\n\n let listenerRef: ListenerRef | null = null;\n let isRequesting = false;\n\n const forceRequest = async (): Promise<void> => {\n if (isRequesting) return;\n isRequesting = true;\n\n try {\n const currentSentinel = untracked(sentinel);\n\n if (currentSentinel) {\n if (listenerRef) {\n listenerRef.destroy();\n listenerRef = null;\n }\n\n if (!currentSentinel.released) {\n await currentSentinel.release();\n }\n }\n\n if (untracked(visibility) === 'visible') {\n const newSentinel = await (navigator as NavigatorWithWakeLock).wakeLock.request('screen');\n\n sentinel.set(newSentinel);\n\n listenerRef = setupSync(() =>\n listener(\n newSentinel,\n 'release',\n () => {\n pendingRequest.set(false);\n sentinel.set(null);\n listenerRef = null;\n },\n { injector }\n )\n );\n }\n } catch (err) {\n sentinel.set(null);\n pendingRequest.set(false);\n\n if (listenerRef) {\n listenerRef.destroy();\n listenerRef = null;\n }\n\n if (ngDevMode) {\n console.warn('[wakeLock] Failed to acquire wake lock.', err);\n }\n } finally {\n isRequesting = false;\n }\n };\n\n const request = async (): Promise<void> => {\n if (untracked(visibility) === 'visible') {\n await forceRequest();\n } else {\n pendingRequest.set(true);\n }\n };\n\n const release = async (): Promise<void> => {\n pendingRequest.set(false);\n\n const currentSentinel = untracked(sentinel);\n\n sentinel.set(null);\n\n if (currentSentinel) {\n if (listenerRef) {\n listenerRef.destroy();\n listenerRef = null;\n }\n if (!currentSentinel.released) {\n try {\n await currentSentinel.release();\n } catch (err) {\n if (ngDevMode) {\n console.warn('[wakeLock] Failed to release wake lock.', err);\n }\n }\n }\n }\n };\n\n if (autoReacquire) {\n effect(() => {\n const isVisible = visibility() === 'visible';\n const isPending = pendingRequest();\n\n if (isVisible && isPending) {\n pendingRequest.set(false);\n forceRequest().catch(() => {\n /* handled inside forceRequest */\n });\n }\n });\n }\n\n onCleanup(() => {\n const currentSentinel = untracked(sentinel);\n if (currentSentinel) {\n if (listenerRef) {\n listenerRef.destroy();\n listenerRef = null;\n }\n if (!currentSentinel.released) {\n currentSentinel.release().catch(() => {\n /* ignore errors */\n });\n }\n }\n });\n\n return {\n isSupported,\n isActive,\n sentinel: sentinel.asReadonly(),\n request,\n forceRequest,\n release,\n };\n });\n}\n\ninterface NavigatorWithWakeLock extends Navigator {\n readonly wakeLock: {\n readonly request: (type: 'screen') => Promise<WakeLockSentinel>;\n };\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|