@signality/core 0.1.1 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/browser/file-dialog/index.d.ts +5 -3
- package/browser/storage/index.d.ts +1 -1
- package/browser/text-selection/index.d.ts +9 -2
- package/elements/element-focus/index.d.ts +12 -8
- package/fesm2022/signality-core-browser-clipboard.mjs +2 -1
- package/fesm2022/signality-core-browser-clipboard.mjs.map +1 -1
- package/fesm2022/signality-core-browser-favicon.mjs +1 -1
- package/fesm2022/signality-core-browser-favicon.mjs.map +1 -1
- package/fesm2022/signality-core-browser-file-dialog.mjs +3 -2
- package/fesm2022/signality-core-browser-file-dialog.mjs.map +1 -1
- package/fesm2022/signality-core-browser-fullscreen.mjs +2 -1
- package/fesm2022/signality-core-browser-fullscreen.mjs.map +1 -1
- package/fesm2022/signality-core-browser-listener.mjs +2 -1
- package/fesm2022/signality-core-browser-listener.mjs.map +1 -1
- package/fesm2022/signality-core-browser-media-query.mjs +2 -1
- package/fesm2022/signality-core-browser-media-query.mjs.map +1 -1
- package/fesm2022/signality-core-browser-picture-in-picture.mjs +2 -1
- package/fesm2022/signality-core-browser-picture-in-picture.mjs.map +1 -1
- package/fesm2022/signality-core-browser-speech-recognition.mjs +2 -1
- package/fesm2022/signality-core-browser-speech-recognition.mjs.map +1 -1
- package/fesm2022/signality-core-browser-speech-synthesis.mjs +2 -1
- package/fesm2022/signality-core-browser-speech-synthesis.mjs.map +1 -1
- package/fesm2022/signality-core-browser-storage.mjs +40 -45
- package/fesm2022/signality-core-browser-storage.mjs.map +1 -1
- package/fesm2022/signality-core-browser-text-direction.mjs +2 -1
- package/fesm2022/signality-core-browser-text-direction.mjs.map +1 -1
- package/fesm2022/signality-core-browser-text-selection.mjs +36 -4
- package/fesm2022/signality-core-browser-text-selection.mjs.map +1 -1
- package/fesm2022/signality-core-browser-vibration.mjs +2 -1
- package/fesm2022/signality-core-browser-vibration.mjs.map +1 -1
- package/fesm2022/signality-core-browser-web-notification.mjs +2 -1
- package/fesm2022/signality-core-browser-web-notification.mjs.map +1 -1
- package/fesm2022/signality-core-browser-web-worker.mjs +2 -1
- package/fesm2022/signality-core-browser-web-worker.mjs.map +1 -1
- package/fesm2022/signality-core-elements-dropzone.mjs +2 -1
- package/fesm2022/signality-core-elements-dropzone.mjs.map +1 -1
- package/fesm2022/signality-core-elements-element-focus-within.mjs +2 -1
- package/fesm2022/signality-core-elements-element-focus-within.mjs.map +1 -1
- package/fesm2022/signality-core-elements-element-focus.mjs +24 -10
- package/fesm2022/signality-core-elements-element-focus.mjs.map +1 -1
- package/fesm2022/signality-core-elements-element-size.mjs +3 -2
- package/fesm2022/signality-core-elements-element-size.mjs.map +1 -1
- package/fesm2022/signality-core-elements-element-visibility.mjs +2 -6
- package/fesm2022/signality-core-elements-element-visibility.mjs.map +1 -1
- package/fesm2022/signality-core-elements-on-click-outside.mjs +2 -1
- package/fesm2022/signality-core-elements-on-click-outside.mjs.map +1 -1
- package/fesm2022/signality-core-elements-on-disconnect.mjs +2 -1
- package/fesm2022/signality-core-elements-on-disconnect.mjs.map +1 -1
- package/fesm2022/signality-core-elements-on-long-press.mjs +2 -1
- package/fesm2022/signality-core-elements-on-long-press.mjs.map +1 -1
- package/fesm2022/signality-core-elements-scroll-position.mjs +2 -1
- package/fesm2022/signality-core-elements-scroll-position.mjs.map +1 -1
- package/fesm2022/signality-core-internal.mjs +2 -26
- package/fesm2022/signality-core-internal.mjs.map +1 -1
- package/fesm2022/signality-core-observers-intersection-observer.mjs +2 -1
- package/fesm2022/signality-core-observers-intersection-observer.mjs.map +1 -1
- package/fesm2022/signality-core-observers-mutation-observer.mjs +2 -1
- package/fesm2022/signality-core-observers-mutation-observer.mjs.map +1 -1
- package/fesm2022/signality-core-observers-resize-observer.mjs +2 -1
- package/fesm2022/signality-core-observers-resize-observer.mjs.map +1 -1
- package/fesm2022/signality-core-reactivity-debounced.mjs +2 -1
- package/fesm2022/signality-core-reactivity-debounced.mjs.map +1 -1
- package/fesm2022/signality-core-reactivity-throttled.mjs +2 -1
- package/fesm2022/signality-core-reactivity-throttled.mjs.map +1 -1
- package/fesm2022/signality-core-scheduling-debounce-callback.mjs +2 -1
- package/fesm2022/signality-core-scheduling-debounce-callback.mjs.map +1 -1
- package/fesm2022/signality-core-scheduling-interval.mjs +2 -1
- package/fesm2022/signality-core-scheduling-interval.mjs.map +1 -1
- package/fesm2022/signality-core-scheduling-throttle-callback.mjs +2 -1
- package/fesm2022/signality-core-scheduling-throttle-callback.mjs.map +1 -1
- package/fesm2022/signality-core-utilities.mjs +64 -0
- package/fesm2022/signality-core-utilities.mjs.map +1 -0
- package/fesm2022/signality-core.mjs +1 -0
- package/fesm2022/signality-core.mjs.map +1 -1
- package/index.d.ts +1 -0
- package/internal/utils/index.d.ts +0 -2
- package/package.json +9 -5
- package/utilities/generate-id.d.ts +29 -0
- package/utilities/index.d.ts +3 -0
- /package/{internal/utils → utilities}/to-element.d.ts +0 -0
- /package/{internal/utils → utilities}/to-value.d.ts +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"signality-core-browser-speech-synthesis.mjs","sources":["../../../projects/core/browser/speech-synthesis/index.ts","../../../projects/core/browser/speech-synthesis/signality-core-browser-speech-synthesis.ts"],"sourcesContent":["import { type Signal, signal } from '@angular/core';\nimport { constSignal, NOOP_FN, setupContext, toValue } from '@signality/core/internal';\nimport type { MaybeSignal, WithInjector } from '@signality/core/types';\n\nexport interface SpeechSynthesisOptions extends WithInjector {\n /**\n * BCP 47 language tag (e.g. `'en-US'`). Supports reactive values.\n *\n * @see [SpeechSynthesisUtterance: lang on MDN](https://developer.mozilla.org/en-US/docs/Web/API/SpeechSynthesisUtterance/lang)\n */\n readonly lang?: MaybeSignal<string>;\n\n /**\n * Speech rate from `0.1` to `10`. Supports reactive values.\n *\n * @default 1\n * @see [SpeechSynthesisUtterance: rate on MDN](https://developer.mozilla.org/en-US/docs/Web/API/SpeechSynthesisUtterance/rate)\n */\n readonly rate?: MaybeSignal<number>;\n\n /**\n * Speech pitch from `0` to `2`. Supports reactive values.\n *\n * @default 1\n * @see [SpeechSynthesisUtterance: pitch on MDN](https://developer.mozilla.org/en-US/docs/Web/API/SpeechSynthesisUtterance/pitch)\n */\n readonly pitch?: MaybeSignal<number>;\n\n /**\n * Speech volume from `0` to `1`. Supports reactive values.\n *\n * @default 1\n * @see [SpeechSynthesisUtterance: volume on MDN](https://developer.mozilla.org/en-US/docs/Web/API/SpeechSynthesisUtterance/volume)\n */\n readonly volume?: MaybeSignal<number>;\n\n /**\n * Voice to use for synthesis. Supports reactive values.\n *\n * @see [SpeechSynthesisUtterance: voice on MDN](https://developer.mozilla.org/en-US/docs/Web/API/SpeechSynthesisUtterance/voice)\n */\n readonly voice?: MaybeSignal<SpeechSynthesisVoice>;\n}\n\nexport interface SpeechSynthesisRef {\n /**\n * Whether the Speech Synthesis API is supported in the current browser.\n *\n * @see [SpeechSynthesis browser compatibility on MDN](https://developer.mozilla.org/en-US/docs/Web/API/SpeechSynthesis#browser_compatibility)\n */\n readonly isSupported: Signal<boolean>;\n\n /**\n * Whether speech is currently being synthesized.\n *\n * @see [SpeechSynthesis: speaking on MDN](https://developer.mozilla.org/en-US/docs/Web/API/SpeechSynthesis/speaking)\n */\n readonly isSpeaking: Signal<boolean>;\n\n /**\n * Whether speech synthesis is currently paused.\n *\n * @see [SpeechSynthesis: paused on MDN](https://developer.mozilla.org/en-US/docs/Web/API/SpeechSynthesis/paused)\n */\n readonly isPaused: Signal<boolean>;\n\n /**\n * List of available synthesis voices for the current device and browser.\n *\n * @see [SpeechSynthesis: getVoices() on MDN](https://developer.mozilla.org/en-US/docs/Web/API/SpeechSynthesis/getVoices)\n */\n readonly voices: Signal<SpeechSynthesisVoice[]>;\n\n /**\n * The text currently being spoken, or `''` if idle.\n */\n readonly currentText: Signal<string>;\n\n /**\n * Speak the given text, cancelling any ongoing utterance.\n * Reads current reactive option values (`lang`, `rate`, `pitch`, `volume`, `voice`) at call time.\n *\n * @see [SpeechSynthesis: speak() on MDN](https://developer.mozilla.org/en-US/docs/Web/API/SpeechSynthesis/speak)\n */\n readonly speak: (text: string) => void;\n\n /**\n * Cancel and stop the current utterance.\n *\n * @see [SpeechSynthesis: cancel() on MDN](https://developer.mozilla.org/en-US/docs/Web/API/SpeechSynthesis/cancel)\n */\n readonly stop: () => void;\n\n /**\n * Pause the current utterance.\n *\n * @see [SpeechSynthesis: pause() on MDN](https://developer.mozilla.org/en-US/docs/Web/API/SpeechSynthesis/pause)\n */\n readonly pause: () => void;\n\n /**\n * Resume a paused utterance.\n *\n * @see [SpeechSynthesis: resume() on MDN](https://developer.mozilla.org/en-US/docs/Web/API/SpeechSynthesis/resume)\n */\n readonly resume: () => void;\n}\n\n/**\n * Signal-based wrapper around the [Speech Synthesis API](https://developer.mozilla.org/en-US/docs/Web/API/SpeechSynthesis).\n *\n * @param options - Optional configuration\n * @returns A SpeechSynthesisRef with isSupported, isSpeaking, isPaused, voices signals and control methods\n *\n * @example\n * ```typescript\n * @Component({\n * template: `\n * @if (synthesis.isSupported()) {\n * <button (click)=\"synthesis.speak('Hello, world!')\" [disabled]=\"synthesis.isSpeaking()\">\n * {{ synthesis.isSpeaking() ? 'Speaking...' : 'Speak' }}\n * </button>\n * <button (click)=\"synthesis.stop()\">Stop</button>\n * }\n * `\n * })\n * export class TextToSpeechDemo {\n * readonly synthesis = speechSynthesis({ rate: 1.5 });\n * }\n * ```\n */\nexport function speechSynthesis(options?: SpeechSynthesisOptions): SpeechSynthesisRef {\n const { runInContext } = setupContext(options?.injector, speechSynthesis);\n\n return runInContext(({ onCleanup, isBrowser }) => {\n const isSupported = constSignal(\n isBrowser && 'speechSynthesis' in window && typeof window.speechSynthesis !== 'undefined'\n );\n\n if (!isSupported()) {\n return {\n isSupported,\n isSpeaking: constSignal(false),\n isPaused: constSignal(false),\n voices: constSignal([]),\n currentText: constSignal(''),\n speak: NOOP_FN,\n stop: NOOP_FN,\n pause: NOOP_FN,\n resume: NOOP_FN,\n };\n }\n\n const { speechSynthesis } = window;\n\n const isSpeaking = signal(false);\n const isPaused = signal(false);\n const voices = signal<SpeechSynthesisVoice[]>(speechSynthesis.getVoices());\n const currentText = signal('');\n\n const loadVoices = () => {\n const availableVoices = speechSynthesis.getVoices();\n voices.set(availableVoices);\n };\n\n const updateSpeakingState = () => {\n isSpeaking.set(speechSynthesis.speaking);\n isPaused.set(speechSynthesis.paused);\n };\n\n const handleStart = () => {\n isSpeaking.set(true);\n isPaused.set(false);\n };\n\n const handleEnd = () => {\n isSpeaking.set(false);\n isPaused.set(false);\n currentText.set('');\n };\n\n const handleError = () => {\n isSpeaking.set(false);\n isPaused.set(false);\n currentText.set('');\n };\n\n const handlePause = () => {\n isPaused.set(true);\n };\n\n const handleResume = () => {\n isPaused.set(false);\n };\n\n const speak = (text: string) => {\n if (!text) {\n return;\n }\n\n speechSynthesis.cancel();\n\n const utterance = new SpeechSynthesisUtterance(text);\n\n const lang = toValue.untracked(options?.lang);\n const voice = toValue.untracked(options?.voice);\n\n if (lang) {\n utterance.lang = lang;\n }\n\n if (voice) {\n utterance.voice = voice;\n }\n\n utterance.rate = toValue.untracked(options?.rate) ?? 1;\n utterance.pitch = toValue.untracked(options?.pitch) ?? 1;\n utterance.volume = toValue.untracked(options?.volume) ?? 1;\n\n utterance.onstart = handleStart;\n utterance.onend = handleEnd;\n utterance.onerror = handleError;\n utterance.onpause = handlePause;\n utterance.onresume = handleResume;\n\n currentText.set(text);\n speechSynthesis.speak(utterance);\n\n updateSpeakingState();\n };\n\n const stop = () => {\n speechSynthesis.cancel();\n isSpeaking.set(false);\n isPaused.set(false);\n currentText.set('');\n };\n\n const pause = () => {\n if (speechSynthesis.speaking && !speechSynthesis.paused) {\n speechSynthesis.pause();\n isPaused.set(true);\n }\n };\n\n const resume = () => {\n if (speechSynthesis.paused) {\n speechSynthesis.resume();\n isPaused.set(false);\n }\n };\n\n onCleanup(stop);\n\n if (speechSynthesis.onvoiceschanged !== undefined) {\n speechSynthesis.onvoiceschanged = loadVoices;\n }\n\n return {\n isSupported,\n isSpeaking: isSpeaking.asReadonly(),\n isPaused: isPaused.asReadonly(),\n voices: voices.asReadonly(),\n currentText: currentText.asReadonly(),\n speak,\n stop,\n pause,\n resume,\n };\n });\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"signality-core-browser-speech-synthesis.mjs","sources":["../../../projects/core/browser/speech-synthesis/index.ts","../../../projects/core/browser/speech-synthesis/signality-core-browser-speech-synthesis.ts"],"sourcesContent":["import { type Signal, signal } from '@angular/core';\nimport { constSignal, NOOP_FN, setupContext } from '@signality/core/internal';\nimport { toValue } from '@signality/core/utilities';\nimport type { MaybeSignal, WithInjector } from '@signality/core/types';\n\nexport interface SpeechSynthesisOptions extends WithInjector {\n /**\n * BCP 47 language tag (e.g. `'en-US'`). Supports reactive values.\n *\n * @see [SpeechSynthesisUtterance: lang on MDN](https://developer.mozilla.org/en-US/docs/Web/API/SpeechSynthesisUtterance/lang)\n */\n readonly lang?: MaybeSignal<string>;\n\n /**\n * Speech rate from `0.1` to `10`. Supports reactive values.\n *\n * @default 1\n * @see [SpeechSynthesisUtterance: rate on MDN](https://developer.mozilla.org/en-US/docs/Web/API/SpeechSynthesisUtterance/rate)\n */\n readonly rate?: MaybeSignal<number>;\n\n /**\n * Speech pitch from `0` to `2`. Supports reactive values.\n *\n * @default 1\n * @see [SpeechSynthesisUtterance: pitch on MDN](https://developer.mozilla.org/en-US/docs/Web/API/SpeechSynthesisUtterance/pitch)\n */\n readonly pitch?: MaybeSignal<number>;\n\n /**\n * Speech volume from `0` to `1`. Supports reactive values.\n *\n * @default 1\n * @see [SpeechSynthesisUtterance: volume on MDN](https://developer.mozilla.org/en-US/docs/Web/API/SpeechSynthesisUtterance/volume)\n */\n readonly volume?: MaybeSignal<number>;\n\n /**\n * Voice to use for synthesis. Supports reactive values.\n *\n * @see [SpeechSynthesisUtterance: voice on MDN](https://developer.mozilla.org/en-US/docs/Web/API/SpeechSynthesisUtterance/voice)\n */\n readonly voice?: MaybeSignal<SpeechSynthesisVoice>;\n}\n\nexport interface SpeechSynthesisRef {\n /**\n * Whether the Speech Synthesis API is supported in the current browser.\n *\n * @see [SpeechSynthesis browser compatibility on MDN](https://developer.mozilla.org/en-US/docs/Web/API/SpeechSynthesis#browser_compatibility)\n */\n readonly isSupported: Signal<boolean>;\n\n /**\n * Whether speech is currently being synthesized.\n *\n * @see [SpeechSynthesis: speaking on MDN](https://developer.mozilla.org/en-US/docs/Web/API/SpeechSynthesis/speaking)\n */\n readonly isSpeaking: Signal<boolean>;\n\n /**\n * Whether speech synthesis is currently paused.\n *\n * @see [SpeechSynthesis: paused on MDN](https://developer.mozilla.org/en-US/docs/Web/API/SpeechSynthesis/paused)\n */\n readonly isPaused: Signal<boolean>;\n\n /**\n * List of available synthesis voices for the current device and browser.\n *\n * @see [SpeechSynthesis: getVoices() on MDN](https://developer.mozilla.org/en-US/docs/Web/API/SpeechSynthesis/getVoices)\n */\n readonly voices: Signal<SpeechSynthesisVoice[]>;\n\n /**\n * The text currently being spoken, or `''` if idle.\n */\n readonly currentText: Signal<string>;\n\n /**\n * Speak the given text, cancelling any ongoing utterance.\n * Reads current reactive option values (`lang`, `rate`, `pitch`, `volume`, `voice`) at call time.\n *\n * @see [SpeechSynthesis: speak() on MDN](https://developer.mozilla.org/en-US/docs/Web/API/SpeechSynthesis/speak)\n */\n readonly speak: (text: string) => void;\n\n /**\n * Cancel and stop the current utterance.\n *\n * @see [SpeechSynthesis: cancel() on MDN](https://developer.mozilla.org/en-US/docs/Web/API/SpeechSynthesis/cancel)\n */\n readonly stop: () => void;\n\n /**\n * Pause the current utterance.\n *\n * @see [SpeechSynthesis: pause() on MDN](https://developer.mozilla.org/en-US/docs/Web/API/SpeechSynthesis/pause)\n */\n readonly pause: () => void;\n\n /**\n * Resume a paused utterance.\n *\n * @see [SpeechSynthesis: resume() on MDN](https://developer.mozilla.org/en-US/docs/Web/API/SpeechSynthesis/resume)\n */\n readonly resume: () => void;\n}\n\n/**\n * Signal-based wrapper around the [Speech Synthesis API](https://developer.mozilla.org/en-US/docs/Web/API/SpeechSynthesis).\n *\n * @param options - Optional configuration\n * @returns A SpeechSynthesisRef with isSupported, isSpeaking, isPaused, voices signals and control methods\n *\n * @example\n * ```typescript\n * @Component({\n * template: `\n * @if (synthesis.isSupported()) {\n * <button (click)=\"synthesis.speak('Hello, world!')\" [disabled]=\"synthesis.isSpeaking()\">\n * {{ synthesis.isSpeaking() ? 'Speaking...' : 'Speak' }}\n * </button>\n * <button (click)=\"synthesis.stop()\">Stop</button>\n * }\n * `\n * })\n * export class TextToSpeechDemo {\n * readonly synthesis = speechSynthesis({ rate: 1.5 });\n * }\n * ```\n */\nexport function speechSynthesis(options?: SpeechSynthesisOptions): SpeechSynthesisRef {\n const { runInContext } = setupContext(options?.injector, speechSynthesis);\n\n return runInContext(({ onCleanup, isBrowser }) => {\n const isSupported = constSignal(\n isBrowser && 'speechSynthesis' in window && typeof window.speechSynthesis !== 'undefined'\n );\n\n if (!isSupported()) {\n return {\n isSupported,\n isSpeaking: constSignal(false),\n isPaused: constSignal(false),\n voices: constSignal([]),\n currentText: constSignal(''),\n speak: NOOP_FN,\n stop: NOOP_FN,\n pause: NOOP_FN,\n resume: NOOP_FN,\n };\n }\n\n const { speechSynthesis } = window;\n\n const isSpeaking = signal(false);\n const isPaused = signal(false);\n const voices = signal<SpeechSynthesisVoice[]>(speechSynthesis.getVoices());\n const currentText = signal('');\n\n const loadVoices = () => {\n const availableVoices = speechSynthesis.getVoices();\n voices.set(availableVoices);\n };\n\n const updateSpeakingState = () => {\n isSpeaking.set(speechSynthesis.speaking);\n isPaused.set(speechSynthesis.paused);\n };\n\n const handleStart = () => {\n isSpeaking.set(true);\n isPaused.set(false);\n };\n\n const handleEnd = () => {\n isSpeaking.set(false);\n isPaused.set(false);\n currentText.set('');\n };\n\n const handleError = () => {\n isSpeaking.set(false);\n isPaused.set(false);\n currentText.set('');\n };\n\n const handlePause = () => {\n isPaused.set(true);\n };\n\n const handleResume = () => {\n isPaused.set(false);\n };\n\n const speak = (text: string) => {\n if (!text) {\n return;\n }\n\n speechSynthesis.cancel();\n\n const utterance = new SpeechSynthesisUtterance(text);\n\n const lang = toValue.untracked(options?.lang);\n const voice = toValue.untracked(options?.voice);\n\n if (lang) {\n utterance.lang = lang;\n }\n\n if (voice) {\n utterance.voice = voice;\n }\n\n utterance.rate = toValue.untracked(options?.rate) ?? 1;\n utterance.pitch = toValue.untracked(options?.pitch) ?? 1;\n utterance.volume = toValue.untracked(options?.volume) ?? 1;\n\n utterance.onstart = handleStart;\n utterance.onend = handleEnd;\n utterance.onerror = handleError;\n utterance.onpause = handlePause;\n utterance.onresume = handleResume;\n\n currentText.set(text);\n speechSynthesis.speak(utterance);\n\n updateSpeakingState();\n };\n\n const stop = () => {\n speechSynthesis.cancel();\n isSpeaking.set(false);\n isPaused.set(false);\n currentText.set('');\n };\n\n const pause = () => {\n if (speechSynthesis.speaking && !speechSynthesis.paused) {\n speechSynthesis.pause();\n isPaused.set(true);\n }\n };\n\n const resume = () => {\n if (speechSynthesis.paused) {\n speechSynthesis.resume();\n isPaused.set(false);\n }\n };\n\n onCleanup(stop);\n\n if (speechSynthesis.onvoiceschanged !== undefined) {\n speechSynthesis.onvoiceschanged = loadVoices;\n }\n\n return {\n isSupported,\n isSpeaking: isSpeaking.asReadonly(),\n isPaused: isPaused.asReadonly(),\n voices: voices.asReadonly(),\n currentText: currentText.asReadonly(),\n speak,\n stop,\n pause,\n resume,\n };\n });\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { signal, isSignal } from '@angular/core';
|
|
2
|
-
import { setupContext, isPlainObject,
|
|
2
|
+
import { setupContext, isPlainObject, proxySignal } from '@signality/core/internal';
|
|
3
|
+
import { toValue } from '@signality/core/utilities';
|
|
3
4
|
import { setupSync, listener } from '@signality/core/browser/listener';
|
|
4
5
|
import { watcher } from '@signality/core/reactivity/watcher';
|
|
5
6
|
|
|
@@ -40,22 +41,13 @@ import { watcher } from '@signality/core/reactivity/watcher';
|
|
|
40
41
|
function storage(key, initialValue, options) {
|
|
41
42
|
const { runInContext } = setupContext(options?.injector, storage);
|
|
42
43
|
return runInContext(({ isServer }) => {
|
|
43
|
-
|
|
44
|
+
const type = options?.type ?? 'local';
|
|
45
|
+
if (isServer || !storageAvailable(type)) {
|
|
44
46
|
return signal(initialValue, options);
|
|
45
47
|
}
|
|
46
|
-
const
|
|
48
|
+
const targetStorage = type === 'local' ? window.localStorage : window.sessionStorage;
|
|
47
49
|
const serializer = resolveSerializer(initialValue, options);
|
|
48
|
-
const
|
|
49
|
-
const type = storageType === 'local' ? 'localStorage' : 'sessionStorage';
|
|
50
|
-
if (!storageAvailable(type)) {
|
|
51
|
-
if (ngDevMode) {
|
|
52
|
-
console.warn(`[storage] ${type} is not available or accessible`);
|
|
53
|
-
}
|
|
54
|
-
return null;
|
|
55
|
-
}
|
|
56
|
-
return window[type];
|
|
57
|
-
};
|
|
58
|
-
const mergeWithInitial = (storedValue) => {
|
|
50
|
+
const processValue = (storedValue) => {
|
|
59
51
|
if (options?.mergeResolver) {
|
|
60
52
|
return options.mergeResolver(storedValue, initialValue);
|
|
61
53
|
}
|
|
@@ -65,20 +57,15 @@ function storage(key, initialValue, options) {
|
|
|
65
57
|
return storedValue;
|
|
66
58
|
};
|
|
67
59
|
const readValue = (storageKey) => {
|
|
68
|
-
const storage = getStorage();
|
|
69
|
-
if (storage === null) {
|
|
70
|
-
return initialValue;
|
|
71
|
-
}
|
|
72
60
|
try {
|
|
73
|
-
const raw =
|
|
61
|
+
const raw = targetStorage.getItem(storageKey);
|
|
74
62
|
if (raw === null) {
|
|
75
63
|
if (initialValue != null) {
|
|
76
64
|
writeValue(initialValue);
|
|
77
65
|
}
|
|
78
66
|
return initialValue;
|
|
79
67
|
}
|
|
80
|
-
|
|
81
|
-
return mergeWithInitial(parsed);
|
|
68
|
+
return processValue(serializer.read(raw));
|
|
82
69
|
}
|
|
83
70
|
catch (error) {
|
|
84
71
|
if (ngDevMode) {
|
|
@@ -87,19 +74,29 @@ function storage(key, initialValue, options) {
|
|
|
87
74
|
return initialValue;
|
|
88
75
|
}
|
|
89
76
|
};
|
|
77
|
+
const dispatchStorageEvent = (key, oldValue, newValue) => {
|
|
78
|
+
window.dispatchEvent(new StorageEvent('storage', {
|
|
79
|
+
key,
|
|
80
|
+
oldValue,
|
|
81
|
+
newValue,
|
|
82
|
+
storageArea: targetStorage,
|
|
83
|
+
url: window.location.href,
|
|
84
|
+
}));
|
|
85
|
+
};
|
|
90
86
|
const writeValue = (value) => {
|
|
91
|
-
const storage = getStorage();
|
|
92
87
|
const storageKey = toValue(key);
|
|
93
|
-
if (storage === null) {
|
|
94
|
-
return;
|
|
95
|
-
}
|
|
96
88
|
try {
|
|
89
|
+
const oldValue = targetStorage.getItem(storageKey);
|
|
97
90
|
if (value == null) {
|
|
98
|
-
|
|
91
|
+
targetStorage.removeItem(storageKey);
|
|
92
|
+
dispatchStorageEvent(storageKey, oldValue, null);
|
|
99
93
|
}
|
|
100
94
|
else {
|
|
101
95
|
const serialized = serializer.write(value);
|
|
102
|
-
|
|
96
|
+
if (oldValue !== serialized) {
|
|
97
|
+
targetStorage.setItem(storageKey, serialized);
|
|
98
|
+
dispatchStorageEvent(storageKey, oldValue, serialized);
|
|
99
|
+
}
|
|
103
100
|
}
|
|
104
101
|
}
|
|
105
102
|
catch (error) {
|
|
@@ -110,26 +107,24 @@ function storage(key, initialValue, options) {
|
|
|
110
107
|
}
|
|
111
108
|
};
|
|
112
109
|
const state = signal(readValue(toValue(key)), options);
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
console.warn(`[storage] Failed to sync value from other tab for key "${event.key}"`, error);
|
|
127
|
-
}
|
|
110
|
+
setupSync(() => {
|
|
111
|
+
listener(window, 'storage', event => {
|
|
112
|
+
const currentKey = toValue(key);
|
|
113
|
+
if (event.key === currentKey && event.storageArea === targetStorage) {
|
|
114
|
+
try {
|
|
115
|
+
const newValue = event.newValue === null
|
|
116
|
+
? initialValue
|
|
117
|
+
: processValue(serializer.read(event.newValue));
|
|
118
|
+
state.set(newValue);
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
if (ngDevMode) {
|
|
122
|
+
console.warn(`[storage] Failed to sync value from other tab for key "${event.key}"`, error);
|
|
128
123
|
}
|
|
129
124
|
}
|
|
130
|
-
}
|
|
125
|
+
}
|
|
131
126
|
});
|
|
132
|
-
}
|
|
127
|
+
});
|
|
133
128
|
if (isSignal(key)) {
|
|
134
129
|
watcher(key, newKey => state.set(readValue(newKey)));
|
|
135
130
|
}
|
|
@@ -239,7 +234,7 @@ function inferSerializerType(value) {
|
|
|
239
234
|
function storageAvailable(type) {
|
|
240
235
|
let storage;
|
|
241
236
|
try {
|
|
242
|
-
storage = window[type];
|
|
237
|
+
storage = window[`${type}Storage`];
|
|
243
238
|
const testKey = '__storage_test__';
|
|
244
239
|
storage.setItem(testKey, testKey);
|
|
245
240
|
storage.removeItem(testKey);
|
|
@@ -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 * 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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"}
|
|
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 } from '@signality/core/internal';\nimport { toValue } from '@signality/core/utilities';\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(initial, stored),\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 const type = options?.type ?? 'local';\n\n if (isServer || !storageAvailable(type)) {\n return signal(initialValue, options);\n }\n\n const targetStorage = type === 'local' ? window.localStorage : window.sessionStorage;\n const serializer = resolveSerializer(initialValue, options);\n\n const processValue = (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 try {\n const raw = targetStorage.getItem(storageKey);\n\n if (raw === null) {\n if (initialValue != null) {\n writeValue(initialValue);\n }\n return initialValue;\n }\n\n return processValue(serializer.read(raw));\n } catch (error) {\n if (ngDevMode) {\n console.warn(`[storage] Failed to deserialize value for key \"${key}\"`, error);\n }\n return initialValue;\n }\n };\n\n const dispatchStorageEvent = (\n key: string,\n oldValue: string | null,\n newValue: string | null\n ) => {\n window.dispatchEvent(\n new StorageEvent('storage', {\n key,\n oldValue,\n newValue,\n storageArea: targetStorage,\n url: window.location.href,\n })\n );\n };\n\n const writeValue = (value: T): void => {\n const storageKey = toValue(key);\n\n try {\n const oldValue = targetStorage.getItem(storageKey);\n\n if (value == null) {\n targetStorage.removeItem(storageKey);\n dispatchStorageEvent(storageKey, oldValue, null);\n } else {\n const serialized = serializer.write(value);\n if (oldValue !== serialized) {\n targetStorage.setItem(storageKey, serialized);\n dispatchStorageEvent(storageKey, oldValue, serialized);\n }\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 setupSync(() => {\n listener(window, 'storage', event => {\n const currentKey = toValue(key);\n\n if (event.key === currentKey && event.storageArea === targetStorage) {\n try {\n const newValue =\n event.newValue === null\n ? initialValue\n : processValue(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 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: 'local' | 'session'): boolean {\n let storage: Storage | undefined;\n\n try {\n storage = window[`${type}Storage`];\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,5 +1,6 @@
|
|
|
1
1
|
import { signal } from '@angular/core';
|
|
2
|
-
import { setupContext,
|
|
2
|
+
import { setupContext, proxySignal, createToken } from '@signality/core/internal';
|
|
3
|
+
import { toElement } from '@signality/core/utilities';
|
|
3
4
|
import { mutationObserver } from '@signality/core/observers/mutation-observer';
|
|
4
5
|
|
|
5
6
|
/**
|
|
@@ -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
|
|
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 } from '@signality/core/internal';\nimport { toElement } from '@signality/core/utilities';\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":";;;;;AAwBA;;;;;;;;;;;;;;;;;;;;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;;AC9EvE;;AAEG;;;;"}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { signal, computed } from '@angular/core';
|
|
2
|
-
import { setupContext, NOOP_FN, constSignal, ALWAYS_FALSE_FN, createToken } from '@signality/core/internal';
|
|
2
|
+
import { setupContext, NOOP_FN, constSignal, ALWAYS_FALSE_FN, createToken, isNodeWithin } from '@signality/core/internal';
|
|
3
|
+
import { toElement } from '@signality/core/utilities';
|
|
3
4
|
import { setupSync, listener } from '@signality/core/browser/listener';
|
|
5
|
+
import { onDisconnect } from '@signality/core/elements/on-disconnect';
|
|
4
6
|
|
|
5
7
|
/**
|
|
6
8
|
* Signal-based wrapper around the [Selection API](https://developer.mozilla.org/en-US/docs/Web/API/Selection_API).
|
|
@@ -36,17 +38,40 @@ function textSelection(options) {
|
|
|
36
38
|
clear: NOOP_FN,
|
|
37
39
|
};
|
|
38
40
|
}
|
|
39
|
-
const
|
|
41
|
+
const root = options?.root;
|
|
42
|
+
const getSelection = () => {
|
|
43
|
+
const sel = window.getSelection();
|
|
44
|
+
if (!root || !sel) {
|
|
45
|
+
return sel;
|
|
46
|
+
}
|
|
47
|
+
const rootEl = toElement(root);
|
|
48
|
+
if (rootEl && isSelectionWithinRoot(sel, rootEl)) {
|
|
49
|
+
return sel;
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
const selection = signal(getSelection(), ...(ngDevMode ? [{ debugName: "selection", equal: ALWAYS_FALSE_FN }] : [{
|
|
56
|
+
equal: ALWAYS_FALSE_FN,
|
|
57
|
+
}]));
|
|
40
58
|
const text = computed(() => selection()?.toString() ?? '', ...(ngDevMode ? [{ debugName: "text" }] : []));
|
|
41
59
|
const ranges = computed(() => {
|
|
42
60
|
const sel = selection();
|
|
43
61
|
return sel ? getRangesFromSelection(sel) : [];
|
|
44
62
|
}, ...(ngDevMode ? [{ debugName: "ranges" }] : []));
|
|
45
63
|
const rects = computed(() => ranges().map(range => range.getBoundingClientRect()), ...(ngDevMode ? [{ debugName: "rects" }] : []));
|
|
46
|
-
const clear = () =>
|
|
64
|
+
const clear = () => {
|
|
65
|
+
window.getSelection()?.removeAllRanges();
|
|
66
|
+
};
|
|
47
67
|
setupSync(() => {
|
|
48
|
-
listener(document, 'selectionchange', () =>
|
|
68
|
+
listener(document, 'selectionchange', () => {
|
|
69
|
+
selection.set(getSelection());
|
|
70
|
+
});
|
|
49
71
|
});
|
|
72
|
+
if (root) {
|
|
73
|
+
onDisconnect(root, () => selection.set(null));
|
|
74
|
+
}
|
|
50
75
|
return {
|
|
51
76
|
selection: selection.asReadonly(),
|
|
52
77
|
text,
|
|
@@ -61,6 +86,13 @@ function getRangesFromSelection(selection) {
|
|
|
61
86
|
const rangeCount = selection.rangeCount ?? 0;
|
|
62
87
|
return Array.from({ length: rangeCount }, (_, i) => selection.getRangeAt(i));
|
|
63
88
|
}
|
|
89
|
+
function isSelectionWithinRoot(selection, rootEl) {
|
|
90
|
+
if (selection.rangeCount === 0) {
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
const { startContainer, endContainer } = selection.getRangeAt(0);
|
|
94
|
+
return isNodeWithin(startContainer, rootEl) && isNodeWithin(endContainer, rootEl);
|
|
95
|
+
}
|
|
64
96
|
|
|
65
97
|
/**
|
|
66
98
|
* Generated bundle index. Do not edit.
|
|
@@ -1 +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
|
|
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 isNodeWithin,\n NOOP_FN,\n setupContext,\n} from '@signality/core/internal';\nimport type { MaybeElementSignal, WithInjector } from '@signality/core/types';\nimport { toElement } from '@signality/core/utilities';\nimport { listener, setupSync } from '@signality/core/browser/listener';\nimport { onDisconnect } from '@signality/core/elements/on-disconnect';\n\nexport interface TextSelectionOptions extends WithInjector {\n /**\n * Element to track selection within.\n * When provided, only selections entirely contained within this element are tracked.\n * @default document (all selections)\n */\n readonly root?: MaybeElementSignal<Element>;\n}\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 root = options?.root;\n\n const getSelection = (): Selection | null => {\n const sel = window.getSelection();\n if (!root || !sel) {\n return sel;\n }\n\n const rootEl = toElement(root);\n if (rootEl && isSelectionWithinRoot(sel, rootEl)) {\n return sel;\n } else {\n return null;\n }\n };\n\n const selection = signal<Selection | null>(getSelection(), {\n equal: ALWAYS_FALSE_FN,\n });\n\n const text = computed(() => selection()?.toString() ?? '');\n\n const ranges = computed<Range[]>(() => {\n const sel = selection();\n return sel ? getRangesFromSelection(sel) : [];\n });\n\n const rects = computed(() => ranges().map(range => range.getBoundingClientRect()));\n\n const clear = () => {\n window.getSelection()?.removeAllRanges();\n };\n\n setupSync(() => {\n listener(document, 'selectionchange', () => {\n selection.set(getSelection());\n });\n });\n\n if (root) {\n onDisconnect(root, () => selection.set(null));\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\nfunction isSelectionWithinRoot(selection: Selection, rootEl: Element): boolean {\n if (selection.rangeCount === 0) {\n return false;\n }\n const { startContainer, endContainer } = selection.getRangeAt(0);\n return isNodeWithin(startContainer, rootEl) && isNodeWithin(endContainer, rootEl);\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { signal } from '@angular/core';
|
|
2
|
-
import { setupContext, constSignal, NOOP_FN
|
|
2
|
+
import { setupContext, constSignal, NOOP_FN } from '@signality/core/internal';
|
|
3
|
+
import { toValue } from '@signality/core/utilities';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Signal-based wrapper around the [Vibration API](https://developer.mozilla.org/en-US/docs/Web/API/Vibration_API).
|
|
@@ -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
|
|
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 } from '@signality/core/internal';\nimport { toValue } from '@signality/core/utilities';\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":";;;;AA2CA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;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;;ACnJA;;AAEG;;;;"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { signal, untracked } from '@angular/core';
|
|
2
|
-
import { setupContext, constSignal, NOOP_FN
|
|
2
|
+
import { setupContext, constSignal, NOOP_FN } from '@signality/core/internal';
|
|
3
|
+
import { toValue } from '@signality/core/utilities';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Signal-based wrapper around the [Notifications API](https://developer.mozilla.org/en-US/docs/Web/API/Notifications_API).
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"signality-core-browser-web-notification.mjs","sources":["../../../projects/core/browser/web-notification/index.ts","../../../projects/core/browser/web-notification/signality-core-browser-web-notification.ts"],"sourcesContent":["import { type Signal, signal, untracked } from '@angular/core';\nimport { constSignal, NOOP_FN, setupContext, type Timer
|
|
1
|
+
{"version":3,"file":"signality-core-browser-web-notification.mjs","sources":["../../../projects/core/browser/web-notification/index.ts","../../../projects/core/browser/web-notification/signality-core-browser-web-notification.ts"],"sourcesContent":["import { type Signal, signal, untracked } from '@angular/core';\nimport { constSignal, NOOP_FN, setupContext, type Timer } from '@signality/core/internal';\nimport { toValue } from '@signality/core/utilities';\nimport type { MaybeSignal, WithInjector } from '@signality/core/types';\n\nexport interface WebNotificationOptions extends NotificationOptions, WithInjector {\n /**\n * Auto-close the notification after the specified number of milliseconds.\n *\n * @see [Notification: close() on MDN](https://developer.mozilla.org/en-US/docs/Web/API/Notification/close)\n */\n readonly autoClose?: MaybeSignal<number>;\n}\n\nexport interface WebNotificationRef {\n /**\n * Whether the Notifications API is supported in the current browser.\n *\n * @see [Notifications API browser compatibility on MDN](https://developer.mozilla.org/en-US/docs/Web/API/Notifications_API#browser_compatibility)\n */\n readonly isSupported: Signal<boolean>;\n\n /**\n * Current notification permission state: `'granted'`, `'denied'`, or `'default'`.\n *\n * @see [Notification: permission static property on MDN](https://developer.mozilla.org/en-US/docs/Web/API/Notification/permission_static)\n */\n readonly permission: Signal<NotificationPermission>;\n\n /**\n * The currently active notification instance, or `null` if none is shown.\n *\n * @see [Notification on MDN](https://developer.mozilla.org/en-US/docs/Web/API/Notification)\n */\n readonly notification: Signal<Notification | null>;\n\n /**\n * Request permission to show notifications.\n *\n * @see [Notification: requestPermission() on MDN](https://developer.mozilla.org/en-US/docs/Web/API/Notification/requestPermission_static)\n */\n readonly requestPermission: () => Promise<NotificationPermission>;\n\n /**\n * Show a notification. Automatically closes the previous one. Per-call options override constructor defaults.\n *\n * @see [Notification() constructor on MDN](https://developer.mozilla.org/en-US/docs/Web/API/Notification/Notification)\n */\n readonly show: (title: string, options?: NotificationOptions) => Notification | undefined;\n\n /**\n * Close the currently active notification.\n *\n * @see [Notification: close() on MDN](https://developer.mozilla.org/en-US/docs/Web/API/Notification/close)\n */\n readonly close: () => void;\n}\n\n/**\n * Signal-based wrapper around the [Notifications API](https://developer.mozilla.org/en-US/docs/Web/API/Notifications_API).\n *\n * Tracks the current notification as a signal. Calling `show()` auto-closes\n * the previous notification. Calling `close()` closes the current one.\n *\n * @param options - Optional configuration and default notification options\n * @returns A WebNotificationRef with notification state and control methods\n *\n * @example\n * ```typescript\n * @Component({\n * template: `\n * <button (click)=\"requestPermission()\">Request Permission</button>\n * <button (click)=\"showNotification()\" [disabled]=\"notif.permission() !== 'granted'\">\n * Show Notification\n * </button>\n * @if (notif.notification()) {\n * <button (click)=\"notif.close()\">Close</button>\n * }\n * `\n * })\n * export class NotificationDemo {\n * readonly notif = webNotification({ icon: '/icon.png' });\n *\n * async requestPermission() {\n * await this.notif.requestPermission();\n * }\n *\n * showNotification() {\n * this.notif.show('Hello!', { body: 'This is a notification' });\n * }\n * }\n * ```\n */\nexport function webNotification(options?: WebNotificationOptions): WebNotificationRef {\n const { runInContext } = setupContext(options?.injector, webNotification);\n\n return runInContext(({ isBrowser, onCleanup }) => {\n const isSupported = constSignal(isBrowser && 'Notification' in window);\n\n if (!isSupported()) {\n return {\n isSupported,\n permission: constSignal('denied'),\n notification: constSignal(null),\n requestPermission: async () => 'denied',\n show: () => undefined,\n close: NOOP_FN,\n };\n }\n\n const permission = signal(Notification.permission);\n const notification = signal<Notification | null>(null);\n\n let autoCloseTimeout: Timer;\n\n const clearAutoClose = () => {\n clearTimeout(autoCloseTimeout);\n autoCloseTimeout = undefined;\n };\n\n const requestPermission = async (): Promise<NotificationPermission> => {\n try {\n const result = await Notification.requestPermission();\n permission.set(result);\n return result;\n } catch (error) {\n if (ngDevMode) {\n console.warn(`[webNotification] Failed to request notification permission.`, error);\n }\n return permission();\n }\n };\n\n const close = (): void => {\n clearAutoClose();\n\n const current = notification();\n\n if (current) {\n try {\n current.onclose = null;\n current.close();\n } catch (error) {\n if (ngDevMode) {\n console.warn(`[webNotification] Failed to close notification.`, error);\n }\n }\n notification.set(null);\n }\n };\n\n const show = (title: string, overrides?: NotificationOptions): Notification | undefined => {\n return untracked(() => {\n if (permission() !== 'granted') {\n return undefined;\n }\n\n close();\n\n try {\n const { autoClose, ...defaults } = options ?? {};\n const mergedOptions = { ...defaults, ...overrides };\n\n const instance = new Notification(title, mergedOptions);\n\n instance.onclose = () => {\n clearAutoClose();\n notification.set(null);\n };\n\n notification.set(instance);\n\n const autoCloseMs = toValue(autoClose);\n if (autoCloseMs && autoCloseMs > 0) {\n autoCloseTimeout = setTimeout(close, autoCloseMs);\n }\n\n return instance;\n } catch (error) {\n if (ngDevMode) {\n console.warn(`[webNotification] Failed to show notification.`, error);\n }\n return undefined;\n }\n });\n };\n\n onCleanup(close);\n\n return {\n isSupported,\n permission: permission.asReadonly(),\n notification: notification.asReadonly(),\n requestPermission,\n show,\n close,\n };\n });\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;;AA0DA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkCG;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;QAC/C,MAAM,WAAW,GAAG,WAAW,CAAC,SAAS,IAAI,cAAc,IAAI,MAAM,CAAC;AAEtE,QAAA,IAAI,CAAC,WAAW,EAAE,EAAE;YAClB,OAAO;gBACL,WAAW;AACX,gBAAA,UAAU,EAAE,WAAW,CAAC,QAAQ,CAAC;AACjC,gBAAA,YAAY,EAAE,WAAW,CAAC,IAAI,CAAC;AAC/B,gBAAA,iBAAiB,EAAE,YAAY,QAAQ;AACvC,gBAAA,IAAI,EAAE,MAAM,SAAS;AACrB,gBAAA,KAAK,EAAE,OAAO;aACf;QACH;QAEA,MAAM,UAAU,GAAG,MAAM,CAAC,YAAY,CAAC,UAAU,sDAAC;AAClD,QAAA,MAAM,YAAY,GAAG,MAAM,CAAsB,IAAI,wDAAC;AAEtD,QAAA,IAAI,gBAAuB;QAE3B,MAAM,cAAc,GAAG,MAAK;YAC1B,YAAY,CAAC,gBAAgB,CAAC;YAC9B,gBAAgB,GAAG,SAAS;AAC9B,QAAA,CAAC;AAED,QAAA,MAAM,iBAAiB,GAAG,YAA4C;AACpE,YAAA,IAAI;AACF,gBAAA,MAAM,MAAM,GAAG,MAAM,YAAY,CAAC,iBAAiB,EAAE;AACrD,gBAAA,UAAU,CAAC,GAAG,CAAC,MAAM,CAAC;AACtB,gBAAA,OAAO,MAAM;YACf;YAAE,OAAO,KAAK,EAAE;gBACd,IAAI,SAAS,EAAE;AACb,oBAAA,OAAO,CAAC,IAAI,CAAC,8DAA8D,EAAE,KAAK,CAAC;gBACrF;gBACA,OAAO,UAAU,EAAE;YACrB;AACF,QAAA,CAAC;QAED,MAAM,KAAK,GAAG,MAAW;AACvB,YAAA,cAAc,EAAE;AAEhB,YAAA,MAAM,OAAO,GAAG,YAAY,EAAE;YAE9B,IAAI,OAAO,EAAE;AACX,gBAAA,IAAI;AACF,oBAAA,OAAO,CAAC,OAAO,GAAG,IAAI;oBACtB,OAAO,CAAC,KAAK,EAAE;gBACjB;gBAAE,OAAO,KAAK,EAAE;oBACd,IAAI,SAAS,EAAE;AACb,wBAAA,OAAO,CAAC,IAAI,CAAC,iDAAiD,EAAE,KAAK,CAAC;oBACxE;gBACF;AACA,gBAAA,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC;YACxB;AACF,QAAA,CAAC;AAED,QAAA,MAAM,IAAI,GAAG,CAAC,KAAa,EAAE,SAA+B,KAA8B;YACxF,OAAO,SAAS,CAAC,MAAK;AACpB,gBAAA,IAAI,UAAU,EAAE,KAAK,SAAS,EAAE;AAC9B,oBAAA,OAAO,SAAS;gBAClB;AAEA,gBAAA,KAAK,EAAE;AAEP,gBAAA,IAAI;oBACF,MAAM,EAAE,SAAS,EAAE,GAAG,QAAQ,EAAE,GAAG,OAAO,IAAI,EAAE;oBAChD,MAAM,aAAa,GAAG,EAAE,GAAG,QAAQ,EAAE,GAAG,SAAS,EAAE;oBAEnD,MAAM,QAAQ,GAAG,IAAI,YAAY,CAAC,KAAK,EAAE,aAAa,CAAC;AAEvD,oBAAA,QAAQ,CAAC,OAAO,GAAG,MAAK;AACtB,wBAAA,cAAc,EAAE;AAChB,wBAAA,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC;AACxB,oBAAA,CAAC;AAED,oBAAA,YAAY,CAAC,GAAG,CAAC,QAAQ,CAAC;AAE1B,oBAAA,MAAM,WAAW,GAAG,OAAO,CAAC,SAAS,CAAC;AACtC,oBAAA,IAAI,WAAW,IAAI,WAAW,GAAG,CAAC,EAAE;AAClC,wBAAA,gBAAgB,GAAG,UAAU,CAAC,KAAK,EAAE,WAAW,CAAC;oBACnD;AAEA,oBAAA,OAAO,QAAQ;gBACjB;gBAAE,OAAO,KAAK,EAAE;oBACd,IAAI,SAAS,EAAE;AACb,wBAAA,OAAO,CAAC,IAAI,CAAC,gDAAgD,EAAE,KAAK,CAAC;oBACvE;AACA,oBAAA,OAAO,SAAS;gBAClB;AACF,YAAA,CAAC,CAAC;AACJ,QAAA,CAAC;QAED,SAAS,CAAC,KAAK,CAAC;QAEhB,OAAO;YACL,WAAW;AACX,YAAA,UAAU,EAAE,UAAU,CAAC,UAAU,EAAE;AACnC,YAAA,YAAY,EAAE,YAAY,CAAC,UAAU,EAAE;YACvC,iBAAiB;YACjB,IAAI;YACJ,KAAK;SACN;AACH,IAAA,CAAC,CAAC;AACJ;;ACtMA;;AAEG;;;;"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { signal, untracked, isSignal } from '@angular/core';
|
|
2
|
-
import { setupContext, NOOP_FN, constSignal
|
|
2
|
+
import { setupContext, NOOP_FN, constSignal } from '@signality/core/internal';
|
|
3
|
+
import { toValue } from '@signality/core/utilities';
|
|
3
4
|
import { watcher } from '@signality/core/reactivity/watcher';
|
|
4
5
|
|
|
5
6
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"signality-core-browser-web-worker.mjs","sources":["../../../projects/core/browser/web-worker/index.ts","../../../projects/core/browser/web-worker/signality-core-browser-web-worker.ts"],"sourcesContent":["import { isSignal, type Signal, signal, untracked } from '@angular/core';\nimport { constSignal, NOOP_FN, setupContext
|
|
1
|
+
{"version":3,"file":"signality-core-browser-web-worker.mjs","sources":["../../../projects/core/browser/web-worker/index.ts","../../../projects/core/browser/web-worker/signality-core-browser-web-worker.ts"],"sourcesContent":["import { isSignal, type Signal, signal, untracked } from '@angular/core';\nimport { constSignal, NOOP_FN, setupContext } from '@signality/core/internal';\nimport { toValue } from '@signality/core/utilities';\nimport type { MaybeSignal, WithInjector } from '@signality/core/types';\nimport { watcher } from '@signality/core/reactivity/watcher';\n\nexport interface WebWorkerOptions extends WithInjector {\n /**\n * Worker script type.\n *\n * @default 'classic'\n * @see [Worker: Worker() constructor — type on MDN](https://developer.mozilla.org/en-US/docs/Web/API/Worker/Worker#type)\n */\n readonly type?: 'classic' | 'module';\n\n /**\n * Credentials mode used when fetching the worker script.\n *\n * @see [Worker: Worker() constructor — credentials on MDN](https://developer.mozilla.org/en-US/docs/Web/API/Worker/Worker#credentials)\n */\n readonly credentials?: RequestCredentials;\n\n /**\n * Identifying name for the worker, useful for debugging in DevTools.\n *\n * @see [Worker: Worker() constructor — name on MDN](https://developer.mozilla.org/en-US/docs/Web/API/Worker/Worker#name)\n */\n readonly name?: string;\n}\n\nexport interface WebWorkerRef<I, O> {\n /**\n * The last message received from the worker via `postMessage`.\n * `undefined` until the first message is received.\n */\n readonly data: Signal<O | undefined>;\n\n /**\n * Whether the worker has been successfully created and is ready to receive messages.\n */\n readonly isReady: Signal<boolean>;\n\n /**\n * The last error emitted by the worker, or `null` if no error has occurred.\n */\n readonly error: Signal<Error | null>;\n\n /**\n * Send a message to the worker.\n *\n * @see [Worker.postMessage() on MDN](https://developer.mozilla.org/en-US/docs/Web/API/Worker/postMessage)\n */\n readonly post: (data: I) => void;\n\n /**\n * Terminate the worker immediately, discarding any pending messages.\n *\n * @see [Worker.terminate() on MDN](https://developer.mozilla.org/en-US/docs/Web/API/Worker/terminate)\n */\n readonly terminate: () => void;\n}\n\n/**\n * Signal-based wrapper around the [Web Workers API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API).\n * Run scripts in background threads without blocking the main thread.\n *\n * When `url` is a signal, the worker is automatically recreated whenever the URL changes.\n *\n * @param url - Worker script URL (static or reactive signal)\n * @param options - Optional configuration\n * @returns A WebWorkerRef with data signal and control methods\n *\n * @example\n * ```typescript\n * @Component({\n * template: `\n * <button (click)=\"worker.post(42)\">Calculate</button>\n * @if (worker.data()) {\n * <p>Result: {{ worker.data() }}</p>\n * }\n * `\n * })\n * export class WorkerDemo {\n * readonly worker = webWorker<number, number>('/workers/calc.js');\n * }\n * ```\n */\nexport function webWorker<I, O>(\n url: MaybeSignal<string | URL>,\n options?: WebWorkerOptions\n): WebWorkerRef<I, O> {\n const { runInContext } = setupContext(options?.injector, webWorker);\n\n return runInContext(({ isServer, onCleanup }) => {\n if (isServer) {\n return {\n data: constSignal(undefined),\n isReady: constSignal(false),\n error: constSignal(null),\n post: NOOP_FN,\n terminate: NOOP_FN,\n };\n }\n\n const type = options?.type ?? 'classic';\n const credentials = options?.credentials;\n const name = options?.name;\n\n const data = signal<O | undefined>(undefined);\n const isReady = signal(false);\n const error = signal<Error | null>(null);\n\n let worker: Worker | null = null;\n\n const createWorker = (workerUrl: string | URL) => {\n if (worker) {\n worker.terminate();\n }\n\n try {\n worker = new Worker(workerUrl, { type, credentials, name });\n\n worker.onmessage = (event: MessageEvent<O>) => {\n data.set(event.data);\n };\n\n worker.onerror = (event: ErrorEvent) => {\n error.set(new Error(event.message));\n };\n\n worker.onmessageerror = () => {\n error.set(new Error('Message deserialization error'));\n };\n\n isReady.set(true);\n } catch (e) {\n error.set(e as Error);\n isReady.set(false);\n }\n };\n\n const post = (message: I) => {\n if (worker && untracked(isReady)) {\n try {\n worker.postMessage(message);\n } catch (e) {\n error.set(e as Error);\n }\n }\n };\n\n const terminate = () => {\n if (worker) {\n worker.terminate();\n worker = null;\n isReady.set(false);\n }\n };\n\n if (isSignal(url)) {\n watcher(url, createWorker);\n }\n\n onCleanup(terminate);\n\n createWorker(toValue(url));\n\n return {\n data: data.asReadonly(),\n isReady: isReady.asReadonly(),\n error: error.asReadonly(),\n post,\n terminate,\n };\n });\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { signal, isSignal } from '@angular/core';
|
|
2
|
-
import { setupContext, constSignal,
|
|
2
|
+
import { setupContext, constSignal, isAcceptedFile, isNodeWithin } from '@signality/core/internal';
|
|
3
|
+
import { toValue, toElement } from '@signality/core/utilities';
|
|
3
4
|
import { listener } from '@signality/core/browser/listener';
|
|
4
5
|
import { onDisconnect } from '@signality/core/elements/on-disconnect';
|
|
5
6
|
import { watcher } from '@signality/core/reactivity/watcher';
|