@signality/core 0.0.1-alpha.2
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/README.md +60 -0
- package/browser/battery/index.d.ts +34 -0
- package/browser/bluetooth/index.d.ts +56 -0
- package/browser/breakpoints/index.d.ts +32 -0
- package/browser/broadcast-channel/index.d.ts +42 -0
- package/browser/browser-language/index.d.ts +34 -0
- package/browser/clipboard/index.d.ts +48 -0
- package/browser/device-posture/index.d.ts +18 -0
- package/browser/display-media/index.d.ts +80 -0
- package/browser/eye-dropper/index.d.ts +47 -0
- package/browser/favicon/index.d.ts +39 -0
- package/browser/fps/index.d.ts +46 -0
- package/browser/gamepad/index.d.ts +28 -0
- package/browser/geolocation/index.d.ts +64 -0
- package/browser/index.d.ts +29 -0
- package/browser/input-modality/index.d.ts +26 -0
- package/browser/listener/index.d.ts +61 -0
- package/browser/media-query/index.d.ts +36 -0
- package/browser/network/index.d.ts +44 -0
- package/browser/online/index.d.ts +27 -0
- package/browser/page-visibility/index.d.ts +27 -0
- package/browser/picture-in-picture/index.d.ts +42 -0
- package/browser/pointer-lock-element/index.d.ts +22 -0
- package/browser/screen-orientation/index.d.ts +29 -0
- package/browser/speech-recognition/index.d.ts +77 -0
- package/browser/speech-synthesis/index.d.ts +76 -0
- package/browser/storage/index.d.ts +142 -0
- package/browser/text-direction/index.d.ts +43 -0
- package/browser/vibration/index.d.ts +37 -0
- package/browser/wake-lock/index.d.ts +37 -0
- package/browser/web-notification/index.d.ts +58 -0
- package/browser/web-share/index.d.ts +42 -0
- package/browser/web-worker/index.d.ts +52 -0
- package/elements/active-element/index.d.ts +27 -0
- package/elements/dropzone/index.d.ts +61 -0
- package/elements/element-focus/index.d.ts +38 -0
- package/elements/element-focus-within/index.d.ts +29 -0
- package/elements/element-hover/index.d.ts +27 -0
- package/elements/element-size/index.d.ts +40 -0
- package/elements/element-visibility/index.d.ts +53 -0
- package/elements/index.d.ts +16 -0
- package/elements/mouse-position/index.d.ts +64 -0
- package/elements/on-click-outside/index.d.ts +42 -0
- package/elements/on-disconnect/index.d.ts +45 -0
- package/elements/on-long-press/index.d.ts +44 -0
- package/elements/pointer-swipe/index.d.ts +58 -0
- package/elements/scroll-position/index.d.ts +96 -0
- package/elements/swipe/index.d.ts +49 -0
- package/elements/text-selection/index.d.ts +39 -0
- package/elements/window-size/index.d.ts +46 -0
- package/fesm2022/signality-core-browser-battery.mjs +80 -0
- package/fesm2022/signality-core-browser-battery.mjs.map +1 -0
- package/fesm2022/signality-core-browser-bluetooth.mjs +112 -0
- package/fesm2022/signality-core-browser-bluetooth.mjs.map +1 -0
- package/fesm2022/signality-core-browser-breakpoints.mjs +51 -0
- package/fesm2022/signality-core-browser-breakpoints.mjs.map +1 -0
- package/fesm2022/signality-core-browser-broadcast-channel.mjs +74 -0
- package/fesm2022/signality-core-browser-broadcast-channel.mjs.map +1 -0
- package/fesm2022/signality-core-browser-browser-language.mjs +48 -0
- package/fesm2022/signality-core-browser-browser-language.mjs.map +1 -0
- package/fesm2022/signality-core-browser-clipboard.mjs +102 -0
- package/fesm2022/signality-core-browser-clipboard.mjs.map +1 -0
- package/fesm2022/signality-core-browser-device-posture.mjs +40 -0
- package/fesm2022/signality-core-browser-device-posture.mjs.map +1 -0
- package/fesm2022/signality-core-browser-display-media.mjs +121 -0
- package/fesm2022/signality-core-browser-display-media.mjs.map +1 -0
- package/fesm2022/signality-core-browser-eye-dropper.mjs +82 -0
- package/fesm2022/signality-core-browser-eye-dropper.mjs.map +1 -0
- package/fesm2022/signality-core-browser-favicon.mjs +100 -0
- package/fesm2022/signality-core-browser-favicon.mjs.map +1 -0
- package/fesm2022/signality-core-browser-fps.mjs +103 -0
- package/fesm2022/signality-core-browser-fps.mjs.map +1 -0
- package/fesm2022/signality-core-browser-gamepad.mjs +93 -0
- package/fesm2022/signality-core-browser-gamepad.mjs.map +1 -0
- package/fesm2022/signality-core-browser-geolocation.mjs +120 -0
- package/fesm2022/signality-core-browser-geolocation.mjs.map +1 -0
- package/fesm2022/signality-core-browser-input-modality.mjs +64 -0
- package/fesm2022/signality-core-browser-input-modality.mjs.map +1 -0
- package/fesm2022/signality-core-browser-listener.mjs +132 -0
- package/fesm2022/signality-core-browser-listener.mjs.map +1 -0
- package/fesm2022/signality-core-browser-media-query.mjs +55 -0
- package/fesm2022/signality-core-browser-media-query.mjs.map +1 -0
- package/fesm2022/signality-core-browser-network.mjs +76 -0
- package/fesm2022/signality-core-browser-network.mjs.map +1 -0
- package/fesm2022/signality-core-browser-online.mjs +49 -0
- package/fesm2022/signality-core-browser-online.mjs.map +1 -0
- package/fesm2022/signality-core-browser-page-visibility.mjs +47 -0
- package/fesm2022/signality-core-browser-page-visibility.mjs.map +1 -0
- package/fesm2022/signality-core-browser-picture-in-picture.mjs +93 -0
- package/fesm2022/signality-core-browser-picture-in-picture.mjs.map +1 -0
- package/fesm2022/signality-core-browser-pointer-lock-element.mjs +43 -0
- package/fesm2022/signality-core-browser-pointer-lock-element.mjs.map +1 -0
- package/fesm2022/signality-core-browser-screen-orientation.mjs +43 -0
- package/fesm2022/signality-core-browser-screen-orientation.mjs.map +1 -0
- package/fesm2022/signality-core-browser-speech-recognition.mjs +171 -0
- package/fesm2022/signality-core-browser-speech-recognition.mjs.map +1 -0
- package/fesm2022/signality-core-browser-speech-synthesis.mjs +146 -0
- package/fesm2022/signality-core-browser-speech-synthesis.mjs.map +1 -0
- package/fesm2022/signality-core-browser-storage.mjs +261 -0
- package/fesm2022/signality-core-browser-storage.mjs.map +1 -0
- package/fesm2022/signality-core-browser-text-direction.mjs +62 -0
- package/fesm2022/signality-core-browser-text-direction.mjs.map +1 -0
- package/fesm2022/signality-core-browser-vibration.mjs +94 -0
- package/fesm2022/signality-core-browser-vibration.mjs.map +1 -0
- package/fesm2022/signality-core-browser-wake-lock.mjs +149 -0
- package/fesm2022/signality-core-browser-wake-lock.mjs.map +1 -0
- package/fesm2022/signality-core-browser-web-notification.mjs +137 -0
- package/fesm2022/signality-core-browser-web-notification.mjs.map +1 -0
- package/fesm2022/signality-core-browser-web-share.mjs +92 -0
- package/fesm2022/signality-core-browser-web-share.mjs.map +1 -0
- package/fesm2022/signality-core-browser-web-worker.mjs +105 -0
- package/fesm2022/signality-core-browser-web-worker.mjs.map +1 -0
- package/fesm2022/signality-core-browser.mjs +34 -0
- package/fesm2022/signality-core-browser.mjs.map +1 -0
- package/fesm2022/signality-core-elements-active-element.mjs +88 -0
- package/fesm2022/signality-core-elements-active-element.mjs.map +1 -0
- package/fesm2022/signality-core-elements-dropzone.mjs +158 -0
- package/fesm2022/signality-core-elements-dropzone.mjs.map +1 -0
- package/fesm2022/signality-core-elements-element-focus-within.mjs +56 -0
- package/fesm2022/signality-core-elements-element-focus-within.mjs.map +1 -0
- package/fesm2022/signality-core-elements-element-focus.mjs +54 -0
- package/fesm2022/signality-core-elements-element-focus.mjs.map +1 -0
- package/fesm2022/signality-core-elements-element-hover.mjs +48 -0
- package/fesm2022/signality-core-elements-element-hover.mjs.map +1 -0
- package/fesm2022/signality-core-elements-element-size.mjs +73 -0
- package/fesm2022/signality-core-elements-element-size.mjs.map +1 -0
- package/fesm2022/signality-core-elements-element-visibility.mjs +76 -0
- package/fesm2022/signality-core-elements-element-visibility.mjs.map +1 -0
- package/fesm2022/signality-core-elements-mouse-position.mjs +109 -0
- package/fesm2022/signality-core-elements-mouse-position.mjs.map +1 -0
- package/fesm2022/signality-core-elements-on-click-outside.mjs +97 -0
- package/fesm2022/signality-core-elements-on-click-outside.mjs.map +1 -0
- package/fesm2022/signality-core-elements-on-disconnect.mjs +99 -0
- package/fesm2022/signality-core-elements-on-disconnect.mjs.map +1 -0
- package/fesm2022/signality-core-elements-on-long-press.mjs +84 -0
- package/fesm2022/signality-core-elements-on-long-press.mjs.map +1 -0
- package/fesm2022/signality-core-elements-pointer-swipe.mjs +116 -0
- package/fesm2022/signality-core-elements-pointer-swipe.mjs.map +1 -0
- package/fesm2022/signality-core-elements-scroll-position.mjs +175 -0
- package/fesm2022/signality-core-elements-scroll-position.mjs.map +1 -0
- package/fesm2022/signality-core-elements-swipe.mjs +107 -0
- package/fesm2022/signality-core-elements-swipe.mjs.map +1 -0
- package/fesm2022/signality-core-elements-text-selection.mjs +70 -0
- package/fesm2022/signality-core-elements-text-selection.mjs.map +1 -0
- package/fesm2022/signality-core-elements-window-size.mjs +81 -0
- package/fesm2022/signality-core-elements-window-size.mjs.map +1 -0
- package/fesm2022/signality-core-elements.mjs +21 -0
- package/fesm2022/signality-core-elements.mjs.map +1 -0
- package/fesm2022/signality-core-forms-cva.mjs +140 -0
- package/fesm2022/signality-core-forms-cva.mjs.map +1 -0
- package/fesm2022/signality-core-forms.mjs +6 -0
- package/fesm2022/signality-core-forms.mjs.map +1 -0
- package/fesm2022/signality-core-internal.mjs +268 -0
- package/fesm2022/signality-core-internal.mjs.map +1 -0
- package/fesm2022/signality-core-observers-intersection-observer.mjs +70 -0
- package/fesm2022/signality-core-observers-intersection-observer.mjs.map +1 -0
- package/fesm2022/signality-core-observers-mutation-observer.mjs +77 -0
- package/fesm2022/signality-core-observers-mutation-observer.mjs.map +1 -0
- package/fesm2022/signality-core-observers-performance-observer.mjs +84 -0
- package/fesm2022/signality-core-observers-performance-observer.mjs.map +1 -0
- package/fesm2022/signality-core-observers-resize-observer.mjs +69 -0
- package/fesm2022/signality-core-observers-resize-observer.mjs.map +1 -0
- package/fesm2022/signality-core-observers.mjs +9 -0
- package/fesm2022/signality-core-observers.mjs.map +1 -0
- package/fesm2022/signality-core-reactivity-debounced.mjs +27 -0
- package/fesm2022/signality-core-reactivity-debounced.mjs.map +1 -0
- package/fesm2022/signality-core-reactivity-throttled.mjs +27 -0
- package/fesm2022/signality-core-reactivity-throttled.mjs.map +1 -0
- package/fesm2022/signality-core-reactivity-watcher.mjs +36 -0
- package/fesm2022/signality-core-reactivity-watcher.mjs.map +1 -0
- package/fesm2022/signality-core-reactivity.mjs +8 -0
- package/fesm2022/signality-core-reactivity.mjs.map +1 -0
- package/fesm2022/signality-core-router-fragment.mjs +41 -0
- package/fesm2022/signality-core-router-fragment.mjs.map +1 -0
- package/fesm2022/signality-core-router-params.mjs +45 -0
- package/fesm2022/signality-core-router-params.mjs.map +1 -0
- package/fesm2022/signality-core-router-query-params.mjs +67 -0
- package/fesm2022/signality-core-router-query-params.mjs.map +1 -0
- package/fesm2022/signality-core-router-route-data.mjs +46 -0
- package/fesm2022/signality-core-router-route-data.mjs.map +1 -0
- package/fesm2022/signality-core-router-router-listener.mjs +50 -0
- package/fesm2022/signality-core-router-router-listener.mjs.map +1 -0
- package/fesm2022/signality-core-router-title.mjs +54 -0
- package/fesm2022/signality-core-router-title.mjs.map +1 -0
- package/fesm2022/signality-core-router-url.mjs +53 -0
- package/fesm2022/signality-core-router-url.mjs.map +1 -0
- package/fesm2022/signality-core-router.mjs +12 -0
- package/fesm2022/signality-core-router.mjs.map +1 -0
- package/fesm2022/signality-core-scheduling-debounce-callback.mjs +59 -0
- package/fesm2022/signality-core-scheduling-debounce-callback.mjs.map +1 -0
- package/fesm2022/signality-core-scheduling-interval.mjs +110 -0
- package/fesm2022/signality-core-scheduling-interval.mjs.map +1 -0
- package/fesm2022/signality-core-scheduling-throttle-callback.mjs +66 -0
- package/fesm2022/signality-core-scheduling-throttle-callback.mjs.map +1 -0
- package/fesm2022/signality-core-scheduling.mjs +8 -0
- package/fesm2022/signality-core-scheduling.mjs.map +1 -0
- package/fesm2022/signality-core-types.mjs +4 -0
- package/fesm2022/signality-core-types.mjs.map +1 -0
- package/fesm2022/signality-core.mjs +13 -0
- package/fesm2022/signality-core.mjs.map +1 -0
- package/forms/cva/index.d.ts +60 -0
- package/forms/index.d.ts +1 -0
- package/index.d.ts +8 -0
- package/internal/constants/index.d.ts +2 -0
- package/internal/constants/mobile-regex.d.ts +1 -0
- package/internal/constants/stubs.d.ts +32 -0
- package/internal/index.d.ts +4 -0
- package/internal/providers/index.d.ts +3 -0
- package/internal/providers/is-browser.d.ts +2 -0
- package/internal/providers/is-mobile.d.ts +2 -0
- package/internal/providers/is-server.d.ts +2 -0
- package/internal/types/index.d.ts +2 -0
- package/internal/types/timer.d.ts +1 -0
- package/internal/types/union.d.ts +1 -0
- package/internal/utils/bom/index.d.ts +1 -0
- package/internal/utils/bom/is-window.d.ts +1 -0
- package/internal/utils/const-signal.d.ts +10 -0
- package/internal/utils/context.d.ts +18 -0
- package/internal/utils/create-token.d.ts +8 -0
- package/internal/utils/dom/get-active-element.d.ts +1 -0
- package/internal/utils/dom/get-event-target.d.ts +1 -0
- package/internal/utils/dom/get-pip-element.d.ts +1 -0
- package/internal/utils/dom/get-shadow-root.d.ts +1 -0
- package/internal/utils/dom/index.d.ts +6 -0
- package/internal/utils/dom/is-element.d.ts +1 -0
- package/internal/utils/dom/is-node-within.d.ts +1 -0
- package/internal/utils/index.d.ts +10 -0
- package/internal/utils/is-plain-object.d.ts +1 -0
- package/internal/utils/is-query-signal.d.ts +10 -0
- package/internal/utils/proxy-signal.d.ts +18 -0
- package/internal/utils/to-element.d.ts +12 -0
- package/internal/utils/to-value.d.ts +6 -0
- package/observers/index.d.ts +4 -0
- package/observers/intersection-observer/index.d.ts +42 -0
- package/observers/mutation-observer/index.d.ts +45 -0
- package/observers/performance-observer/index.d.ts +58 -0
- package/observers/resize-observer/index.d.ts +40 -0
- package/package.json +343 -0
- package/reactivity/debounced/index.d.ts +50 -0
- package/reactivity/index.d.ts +3 -0
- package/reactivity/throttled/index.d.ts +53 -0
- package/reactivity/watcher/index.d.ts +68 -0
- package/router/fragment/index.d.ts +26 -0
- package/router/index.d.ts +7 -0
- package/router/params/index.d.ts +28 -0
- package/router/query-params/index.d.ts +80 -0
- package/router/route-data/index.d.ts +28 -0
- package/router/router-listener/index.d.ts +83 -0
- package/router/title/index.d.ts +29 -0
- package/router/url/index.d.ts +32 -0
- package/scheduling/debounce-callback/index.d.ts +28 -0
- package/scheduling/index.d.ts +3 -0
- package/scheduling/interval/index.d.ts +51 -0
- package/scheduling/throttle-callback/index.d.ts +30 -0
- package/types/index.d.ts +6 -0
- package/types/maybe-element-signal.d.ts +2 -0
- package/types/maybe-signal.d.ts +2 -0
- package/types/signal-value.d.ts +2 -0
- package/types/signal-values.d.ts +5 -0
- package/types/unref-element.d.ts +2 -0
- package/types/with-injector.d.ts +8 -0
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { signal } from '@angular/core';
|
|
2
|
+
import { setupContext, constSignal, NOOP_FN } from '@signality/core/internal';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Signal-based wrapper around the [Speech Synthesis API](https://developer.mozilla.org/en-US/docs/Web/API/SpeechSynthesis).
|
|
6
|
+
*
|
|
7
|
+
* @param options - Optional configuration
|
|
8
|
+
* @returns A SpeechSynthesisRef with isSupported, isSpeaking, isPaused, voices signals and control methods
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```typescript
|
|
12
|
+
* @Component({
|
|
13
|
+
* template: `
|
|
14
|
+
* @if (synthesis.isSupported()) {
|
|
15
|
+
* <button (click)="speakText()" [disabled]="synthesis.isSpeaking()">
|
|
16
|
+
* {{ synthesis.isSpeaking() ? 'Speaking...' : 'Speak' }}
|
|
17
|
+
* </button>
|
|
18
|
+
* <button (click)="synthesis.stop()">Stop</button>
|
|
19
|
+
* }
|
|
20
|
+
* `
|
|
21
|
+
* })
|
|
22
|
+
* class TextToSpeechComponent {
|
|
23
|
+
* readonly synthesis = speechSynthesis();
|
|
24
|
+
*
|
|
25
|
+
* speakText() {
|
|
26
|
+
* this.synthesis.speak('Hello, world!');
|
|
27
|
+
* }
|
|
28
|
+
* }
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
function speechSynthesis(options) {
|
|
32
|
+
const { runInContext } = setupContext(options?.injector, speechSynthesis);
|
|
33
|
+
return runInContext(({ onCleanup, isBrowser }) => {
|
|
34
|
+
const isSupported = constSignal(isBrowser && 'speechSynthesis' in window && typeof window.speechSynthesis !== 'undefined');
|
|
35
|
+
if (!isSupported()) {
|
|
36
|
+
return {
|
|
37
|
+
isSupported,
|
|
38
|
+
isSpeaking: constSignal(false),
|
|
39
|
+
isPaused: constSignal(false),
|
|
40
|
+
voices: constSignal([]),
|
|
41
|
+
currentText: constSignal(''),
|
|
42
|
+
speak: NOOP_FN,
|
|
43
|
+
stop: NOOP_FN,
|
|
44
|
+
pause: NOOP_FN,
|
|
45
|
+
resume: NOOP_FN,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
const { speechSynthesis } = window;
|
|
49
|
+
const { lang: defaultLang, rate: defaultRate = 1, pitch: defaultPitch = 1, volume: defaultVolume = 1, voice: defaultVoice, } = options ?? {};
|
|
50
|
+
const isSpeaking = signal(false, ...(ngDevMode ? [{ debugName: "isSpeaking" }] : []));
|
|
51
|
+
const isPaused = signal(false, ...(ngDevMode ? [{ debugName: "isPaused" }] : []));
|
|
52
|
+
const voices = signal(speechSynthesis.getVoices(), ...(ngDevMode ? [{ debugName: "voices" }] : []));
|
|
53
|
+
const currentText = signal('', ...(ngDevMode ? [{ debugName: "currentText" }] : []));
|
|
54
|
+
const loadVoices = () => {
|
|
55
|
+
const availableVoices = speechSynthesis.getVoices();
|
|
56
|
+
voices.set(availableVoices);
|
|
57
|
+
};
|
|
58
|
+
const updateSpeakingState = () => {
|
|
59
|
+
isSpeaking.set(speechSynthesis.speaking);
|
|
60
|
+
isPaused.set(speechSynthesis.paused);
|
|
61
|
+
};
|
|
62
|
+
const handleStart = () => {
|
|
63
|
+
isSpeaking.set(true);
|
|
64
|
+
isPaused.set(false);
|
|
65
|
+
};
|
|
66
|
+
const handleEnd = () => {
|
|
67
|
+
isSpeaking.set(false);
|
|
68
|
+
isPaused.set(false);
|
|
69
|
+
currentText.set('');
|
|
70
|
+
};
|
|
71
|
+
const handleError = () => {
|
|
72
|
+
isSpeaking.set(false);
|
|
73
|
+
isPaused.set(false);
|
|
74
|
+
currentText.set('');
|
|
75
|
+
};
|
|
76
|
+
const handlePause = () => {
|
|
77
|
+
isPaused.set(true);
|
|
78
|
+
};
|
|
79
|
+
const handleResume = () => {
|
|
80
|
+
isPaused.set(false);
|
|
81
|
+
};
|
|
82
|
+
const speak = (text, speakOptions) => {
|
|
83
|
+
if (!text) {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
speechSynthesis.cancel();
|
|
87
|
+
const utterance = new SpeechSynthesisUtterance(text);
|
|
88
|
+
const { lang = defaultLang, rate = defaultRate, pitch = defaultPitch, volume = defaultVolume, voice = defaultVoice, } = speakOptions ?? {};
|
|
89
|
+
if (lang)
|
|
90
|
+
utterance.lang = lang;
|
|
91
|
+
utterance.rate = rate;
|
|
92
|
+
utterance.pitch = pitch;
|
|
93
|
+
utterance.volume = volume;
|
|
94
|
+
if (voice)
|
|
95
|
+
utterance.voice = voice;
|
|
96
|
+
utterance.onstart = handleStart;
|
|
97
|
+
utterance.onend = handleEnd;
|
|
98
|
+
utterance.onerror = handleError;
|
|
99
|
+
utterance.onpause = handlePause;
|
|
100
|
+
utterance.onresume = handleResume;
|
|
101
|
+
currentText.set(text);
|
|
102
|
+
speechSynthesis.speak(utterance);
|
|
103
|
+
updateSpeakingState();
|
|
104
|
+
};
|
|
105
|
+
const stop = () => {
|
|
106
|
+
speechSynthesis.cancel();
|
|
107
|
+
isSpeaking.set(false);
|
|
108
|
+
isPaused.set(false);
|
|
109
|
+
currentText.set('');
|
|
110
|
+
};
|
|
111
|
+
const pause = () => {
|
|
112
|
+
if (speechSynthesis.speaking && !speechSynthesis.paused) {
|
|
113
|
+
speechSynthesis.pause();
|
|
114
|
+
isPaused.set(true);
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
const resume = () => {
|
|
118
|
+
if (speechSynthesis.paused) {
|
|
119
|
+
speechSynthesis.resume();
|
|
120
|
+
isPaused.set(false);
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
onCleanup(stop);
|
|
124
|
+
if (speechSynthesis.onvoiceschanged !== undefined) {
|
|
125
|
+
speechSynthesis.onvoiceschanged = loadVoices;
|
|
126
|
+
}
|
|
127
|
+
return {
|
|
128
|
+
isSupported,
|
|
129
|
+
isSpeaking: isSpeaking.asReadonly(),
|
|
130
|
+
isPaused: isPaused.asReadonly(),
|
|
131
|
+
voices: voices.asReadonly(),
|
|
132
|
+
currentText: currentText.asReadonly(),
|
|
133
|
+
speak,
|
|
134
|
+
stop,
|
|
135
|
+
pause,
|
|
136
|
+
resume,
|
|
137
|
+
};
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Generated bundle index. Do not edit.
|
|
143
|
+
*/
|
|
144
|
+
|
|
145
|
+
export { speechSynthesis };
|
|
146
|
+
//# sourceMappingURL=signality-core-browser-speech-synthesis.mjs.map
|
|
@@ -0,0 +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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"}
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import { signal, isSignal } from '@angular/core';
|
|
2
|
+
import { setupContext, isPlainObject, toValue, proxySignal } from '@signality/core/internal';
|
|
3
|
+
import { setupSync, listener } from '@signality/core/browser/listener';
|
|
4
|
+
import { watcher } from '@signality/core/reactivity/watcher';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Signal-based wrapper around the [Web Storage API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API) (localStorage/sessionStorage).
|
|
8
|
+
*
|
|
9
|
+
* @param key - Storage key (can be a signal for dynamic keys)
|
|
10
|
+
* @param initialValue - Default value if key doesn't exist
|
|
11
|
+
* @param options - Configuration options
|
|
12
|
+
* @returns A WritableSignal that automatically syncs with storage
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* Basic usage with automatic serialization:
|
|
16
|
+
* ```typescript
|
|
17
|
+
* @Component({
|
|
18
|
+
* template: '
|
|
19
|
+
* <input [(ngModel)]="username" />
|
|
20
|
+
* <p>Count: {{ count() }}</p>
|
|
21
|
+
* <button (click)="count.set(count() + 1)">Increment</button>
|
|
22
|
+
* '
|
|
23
|
+
* })
|
|
24
|
+
* class UserComponent {
|
|
25
|
+
* readonly username = storage('username', '');
|
|
26
|
+
* readonly count = storage('counter', 0); // number serialization inferred
|
|
27
|
+
* readonly lastVisit = storage('lastVisit', new Date()); // Date serialization inferred
|
|
28
|
+
* }
|
|
29
|
+
* ```
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* With options:
|
|
33
|
+
* ```typescript
|
|
34
|
+
* const preferences = storage('prefs', defaultPrefs, {
|
|
35
|
+
* type: 'session',
|
|
36
|
+
* mergeWithInitial: true,
|
|
37
|
+
* });
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
function storage(key, initialValue, options) {
|
|
41
|
+
const { runInContext } = setupContext(options?.injector, storage);
|
|
42
|
+
return runInContext(({ isServer }) => {
|
|
43
|
+
if (isServer) {
|
|
44
|
+
return signal(initialValue, options);
|
|
45
|
+
}
|
|
46
|
+
const storageType = options?.type ?? 'local';
|
|
47
|
+
const serializer = resolveSerializer(initialValue, options);
|
|
48
|
+
const getStorage = () => {
|
|
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) => {
|
|
59
|
+
if (options?.mergeResolver) {
|
|
60
|
+
return options.mergeResolver(storedValue, initialValue);
|
|
61
|
+
}
|
|
62
|
+
if (isPlainObject(initialValue)) {
|
|
63
|
+
return { ...initialValue, ...storedValue };
|
|
64
|
+
}
|
|
65
|
+
return storedValue;
|
|
66
|
+
};
|
|
67
|
+
const readValue = (storageKey) => {
|
|
68
|
+
const storage = getStorage();
|
|
69
|
+
if (storage === null) {
|
|
70
|
+
return initialValue;
|
|
71
|
+
}
|
|
72
|
+
try {
|
|
73
|
+
const raw = storage.getItem(storageKey);
|
|
74
|
+
if (raw === null) {
|
|
75
|
+
if (initialValue != null) {
|
|
76
|
+
writeValue(initialValue);
|
|
77
|
+
}
|
|
78
|
+
return initialValue;
|
|
79
|
+
}
|
|
80
|
+
const parsed = serializer.read(raw);
|
|
81
|
+
return mergeWithInitial(parsed);
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
if (ngDevMode) {
|
|
85
|
+
console.warn(`[storage] Failed to deserialize value for key "${key}"`, error);
|
|
86
|
+
}
|
|
87
|
+
return initialValue;
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
const writeValue = (value) => {
|
|
91
|
+
const storage = getStorage();
|
|
92
|
+
const storageKey = toValue(key);
|
|
93
|
+
if (storage === null) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
try {
|
|
97
|
+
if (value == null) {
|
|
98
|
+
storage.removeItem(storageKey);
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
const serialized = serializer.write(value);
|
|
102
|
+
storage.setItem(storageKey, serialized);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
if (ngDevMode) {
|
|
107
|
+
console.warn(`[storage] Failed to write value for key "${storageKey}". ` +
|
|
108
|
+
`This may be due to storage quota exceeded or serialization error.`, error);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
const state = signal(readValue(toValue(key)), options);
|
|
113
|
+
if (storageType === 'local') {
|
|
114
|
+
setupSync(() => {
|
|
115
|
+
listener(window, 'storage', event => {
|
|
116
|
+
const currentKey = toValue(key);
|
|
117
|
+
if (event.key === currentKey && event.storageArea === window.localStorage) {
|
|
118
|
+
try {
|
|
119
|
+
const newValue = event.newValue === null
|
|
120
|
+
? initialValue
|
|
121
|
+
: mergeWithInitial(serializer.read(event.newValue));
|
|
122
|
+
state.set(newValue);
|
|
123
|
+
}
|
|
124
|
+
catch (error) {
|
|
125
|
+
if (ngDevMode) {
|
|
126
|
+
console.warn(`[storage] Failed to sync value from other tab for key "${event.key}"`, error);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
if (isSignal(key)) {
|
|
134
|
+
watcher(key, newKey => state.set(readValue(newKey)));
|
|
135
|
+
}
|
|
136
|
+
return proxySignal(state, {
|
|
137
|
+
set: (value) => {
|
|
138
|
+
state.set(value);
|
|
139
|
+
writeValue(value);
|
|
140
|
+
},
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
const Serializers = {
|
|
145
|
+
string: {
|
|
146
|
+
read: (v) => v,
|
|
147
|
+
write: (v) => v,
|
|
148
|
+
},
|
|
149
|
+
number: {
|
|
150
|
+
read: (v) => {
|
|
151
|
+
if (v === 'Infinity')
|
|
152
|
+
return Infinity;
|
|
153
|
+
if (v === '-Infinity')
|
|
154
|
+
return -Infinity;
|
|
155
|
+
if (v === 'NaN')
|
|
156
|
+
return NaN;
|
|
157
|
+
return Number.parseFloat(v);
|
|
158
|
+
},
|
|
159
|
+
write: (v) => {
|
|
160
|
+
if (Number.isNaN(v))
|
|
161
|
+
return 'NaN';
|
|
162
|
+
if (v === Infinity)
|
|
163
|
+
return 'Infinity';
|
|
164
|
+
if (v === -Infinity)
|
|
165
|
+
return '-Infinity';
|
|
166
|
+
return String(v);
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
boolean: {
|
|
170
|
+
read: (v) => v === 'true',
|
|
171
|
+
write: (v) => (v ? 'true' : 'false'),
|
|
172
|
+
},
|
|
173
|
+
bigint: {
|
|
174
|
+
read: (v) => BigInt(v),
|
|
175
|
+
write: (v) => v.toString(),
|
|
176
|
+
},
|
|
177
|
+
/*
|
|
178
|
+
* Date serializer - uses ISO 8601 format for maximum compatibility.
|
|
179
|
+
*/
|
|
180
|
+
date: {
|
|
181
|
+
read: (v) => new Date(v),
|
|
182
|
+
write: (v) => v.toISOString(),
|
|
183
|
+
},
|
|
184
|
+
object: {
|
|
185
|
+
read: (v) => JSON.parse(v),
|
|
186
|
+
write: (v) => JSON.stringify(v),
|
|
187
|
+
},
|
|
188
|
+
map: {
|
|
189
|
+
read: (v) => new Map(JSON.parse(v)),
|
|
190
|
+
write: (v) => JSON.stringify([...v.entries()]),
|
|
191
|
+
},
|
|
192
|
+
set: {
|
|
193
|
+
read: (v) => new Set(JSON.parse(v)),
|
|
194
|
+
write: (v) => JSON.stringify([...v]),
|
|
195
|
+
},
|
|
196
|
+
/*
|
|
197
|
+
* Any serializer - fallback that treats everything as string.
|
|
198
|
+
*/
|
|
199
|
+
any: {
|
|
200
|
+
read: (v) => v,
|
|
201
|
+
write: (v) => String(v),
|
|
202
|
+
},
|
|
203
|
+
};
|
|
204
|
+
function resolveSerializer(initialValue, options) {
|
|
205
|
+
if (options?.serializer) {
|
|
206
|
+
return options.serializer;
|
|
207
|
+
}
|
|
208
|
+
const type = inferSerializerType(initialValue);
|
|
209
|
+
return Serializers[type];
|
|
210
|
+
}
|
|
211
|
+
function inferSerializerType(value) {
|
|
212
|
+
if (value === null || value === undefined) {
|
|
213
|
+
return 'any';
|
|
214
|
+
}
|
|
215
|
+
if (value instanceof Map) {
|
|
216
|
+
return 'map';
|
|
217
|
+
}
|
|
218
|
+
if (value instanceof Set) {
|
|
219
|
+
return 'set';
|
|
220
|
+
}
|
|
221
|
+
if (value instanceof Date) {
|
|
222
|
+
return 'date';
|
|
223
|
+
}
|
|
224
|
+
switch (typeof value) {
|
|
225
|
+
case 'string':
|
|
226
|
+
return 'string';
|
|
227
|
+
case 'number':
|
|
228
|
+
return 'number';
|
|
229
|
+
case 'boolean':
|
|
230
|
+
return 'boolean';
|
|
231
|
+
case 'bigint':
|
|
232
|
+
return 'bigint';
|
|
233
|
+
case 'object':
|
|
234
|
+
return 'object';
|
|
235
|
+
default:
|
|
236
|
+
return 'any';
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
function storageAvailable(type) {
|
|
240
|
+
let storage;
|
|
241
|
+
try {
|
|
242
|
+
storage = window[type];
|
|
243
|
+
const testKey = '__storage_test__';
|
|
244
|
+
storage.setItem(testKey, testKey);
|
|
245
|
+
storage.removeItem(testKey);
|
|
246
|
+
return true;
|
|
247
|
+
}
|
|
248
|
+
catch (e) {
|
|
249
|
+
return (e instanceof DOMException &&
|
|
250
|
+
e.name === 'QuotaExceededError' &&
|
|
251
|
+
storage !== undefined &&
|
|
252
|
+
storage.length !== 0);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Generated bundle index. Do not edit.
|
|
258
|
+
*/
|
|
259
|
+
|
|
260
|
+
export { Serializers, storage };
|
|
261
|
+
//# sourceMappingURL=signality-core-browser-storage.mjs.map
|
|
@@ -0,0 +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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"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { signal } from '@angular/core';
|
|
2
|
+
import { setupContext, toElement, proxySignal, createToken } from '@signality/core/internal';
|
|
3
|
+
import { mutationObserver } from '@signality/core/observers/mutation-observer';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Reactive read/write wrapper around an element's `dir` attribute for detecting and controlling
|
|
7
|
+
* [text directionality](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/dir).
|
|
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
|
+
* @param options - Optional configuration including target element, initial value and injector
|
|
13
|
+
* @returns A writable signal of the current text direction
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```typescript
|
|
17
|
+
* @Component({
|
|
18
|
+
* template: `
|
|
19
|
+
* <p>Current direction: {{ dir() }}</p>
|
|
20
|
+
* <button (click)="dir.set('rtl')">Set RTL</button>
|
|
21
|
+
* <button (click)="dir.set('ltr')">Set LTR</button>
|
|
22
|
+
* `
|
|
23
|
+
* })
|
|
24
|
+
* class TextDirectionComponent {
|
|
25
|
+
* readonly dir = textDirection();
|
|
26
|
+
* }
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
function textDirection(options) {
|
|
30
|
+
const { runInContext } = setupContext(options?.injector, textDirection);
|
|
31
|
+
const initialValue = options?.initialValue ?? 'ltr';
|
|
32
|
+
return runInContext(({ isServer }) => {
|
|
33
|
+
if (isServer) {
|
|
34
|
+
return signal(initialValue, options);
|
|
35
|
+
}
|
|
36
|
+
const target = options?.target ?? document.documentElement;
|
|
37
|
+
const readDir = () => {
|
|
38
|
+
const el = toElement(target);
|
|
39
|
+
return el?.getAttribute('dir') || initialValue;
|
|
40
|
+
};
|
|
41
|
+
const dir = signal(readDir(), ...(ngDevMode ? [{ debugName: "dir" }] : []));
|
|
42
|
+
mutationObserver(target, () => dir.set(readDir()), {
|
|
43
|
+
attributes: true,
|
|
44
|
+
attributeFilter: ['dir'],
|
|
45
|
+
});
|
|
46
|
+
return proxySignal(dir, {
|
|
47
|
+
set: value => {
|
|
48
|
+
const el = toElement(target);
|
|
49
|
+
el?.setAttribute('dir', value);
|
|
50
|
+
dir.set(value);
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
const TEXT_DIRECTION = /* @__PURE__ */ createToken(textDirection);
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Generated bundle index. Do not edit.
|
|
59
|
+
*/
|
|
60
|
+
|
|
61
|
+
export { TEXT_DIRECTION, textDirection };
|
|
62
|
+
//# sourceMappingURL=signality-core-browser-text-direction.mjs.map
|
|
@@ -0,0 +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 direction value used during SSR or before the DOM is read.\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 * Returns a `WritableSignal` that reflects the current `dir` attribute value and updates the DOM\n * when written to. Changes made to the attribute externally are picked up via `MutationObserver`.\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 * class TextDirectionComponent {\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;;;;;;;;;;;;;;;;;;;;;;;AAuBG;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;;AChFvE;;AAEG;;;;"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { signal } from '@angular/core';
|
|
2
|
+
import { setupContext, constSignal, NOOP_FN, toValue } from '@signality/core/internal';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Signal-based wrapper around the Vibration API.
|
|
6
|
+
*
|
|
7
|
+
* @param options - Optional configuration including default pattern and injector
|
|
8
|
+
* @returns A VibrationRef with vibration control methods
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```typescript
|
|
12
|
+
* const vib = vibration();
|
|
13
|
+
*
|
|
14
|
+
* vib.vibrate(100); // Vibrate for 100ms
|
|
15
|
+
* vib.vibrate([100, 50, 100]); // Pattern: vibrate, pause, vibrate
|
|
16
|
+
* ```
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```typescript
|
|
20
|
+
* const vib = vibration({ pattern: 200 });
|
|
21
|
+
*
|
|
22
|
+
* vib.vibrate(); // Uses default 200ms pattern
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
function vibration(options) {
|
|
26
|
+
const { runInContext } = setupContext(options?.injector, vibration);
|
|
27
|
+
return runInContext(({ isBrowser, onCleanup }) => {
|
|
28
|
+
const isSupported = constSignal(isBrowser && 'vibrate' in navigator);
|
|
29
|
+
if (!isSupported()) {
|
|
30
|
+
return {
|
|
31
|
+
isSupported,
|
|
32
|
+
isVibrating: constSignal(false),
|
|
33
|
+
vibrate: NOOP_FN,
|
|
34
|
+
stop: NOOP_FN,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
const isVibrating = signal(false, ...(ngDevMode ? [{ debugName: "isVibrating" }] : []));
|
|
38
|
+
let vibrateTimeout;
|
|
39
|
+
const vibrate = (vibratePattern) => {
|
|
40
|
+
const resolvedPattern = vibratePattern ?? toValue.untracked(options?.pattern) ?? 200;
|
|
41
|
+
try {
|
|
42
|
+
const result = navigator.vibrate(resolvedPattern);
|
|
43
|
+
isVibrating.set(result);
|
|
44
|
+
const duration = Array.isArray(resolvedPattern)
|
|
45
|
+
? resolvedPattern.reduce((a, b) => a + b, 0)
|
|
46
|
+
: resolvedPattern;
|
|
47
|
+
if (result && duration > 0) {
|
|
48
|
+
if (vibrateTimeout) {
|
|
49
|
+
clearTimeout(vibrateTimeout);
|
|
50
|
+
}
|
|
51
|
+
vibrateTimeout = setTimeout(() => {
|
|
52
|
+
isVibrating.set(false);
|
|
53
|
+
vibrateTimeout = undefined;
|
|
54
|
+
}, duration);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
isVibrating.set(false);
|
|
59
|
+
if (ngDevMode) {
|
|
60
|
+
console.warn(`[vibration] Failed to vibrate device.`, error);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
const stop = () => {
|
|
65
|
+
try {
|
|
66
|
+
navigator.vibrate(0);
|
|
67
|
+
isVibrating.set(false);
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
if (ngDevMode) {
|
|
71
|
+
console.warn(`[vibration] Failed to stop vibration.`, error);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if (vibrateTimeout) {
|
|
75
|
+
clearTimeout(vibrateTimeout);
|
|
76
|
+
vibrateTimeout = undefined;
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
onCleanup(stop);
|
|
80
|
+
return {
|
|
81
|
+
isSupported,
|
|
82
|
+
isVibrating: isVibrating.asReadonly(),
|
|
83
|
+
vibrate,
|
|
84
|
+
stop,
|
|
85
|
+
};
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Generated bundle index. Do not edit.
|
|
91
|
+
*/
|
|
92
|
+
|
|
93
|
+
export { vibration };
|
|
94
|
+
//# sourceMappingURL=signality-core-browser-vibration.mjs.map
|
|
@@ -0,0 +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 /** Whether Vibration API is supported */\n readonly isSupported: Signal<boolean>;\n\n /** Whether currently vibrating */\n readonly isVibrating: Signal<boolean>;\n\n /** Start vibration */\n readonly vibrate: (pattern?: number | number[]) => void;\n\n /** Stop vibration */\n readonly stop: () => void;\n}\n\n/**\n * Signal-based wrapper around the 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 * const vib = vibration();\n *\n * vib.vibrate(100); // Vibrate for 100ms\n * vib.vibrate([100, 50, 100]); // Pattern: vibrate, pause, vibrate\n * ```\n *\n * @example\n * ```typescript\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":";;;AAsBA;;;;;;;;;;;;;;;;;;;;AAoBG;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;;ACrHA;;AAEG;;;;"}
|