@signality/core 0.1.2 → 0.2.0

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.
Files changed (94) hide show
  1. package/browser/device-pixel-ratio/index.d.ts +30 -0
  2. package/browser/file-dialog/index.d.ts +1 -2
  3. package/browser/index.d.ts +1 -0
  4. package/browser/listener/index.d.ts +1 -0
  5. package/browser/picture-in-picture/index.d.ts +1 -5
  6. package/browser/text-selection/index.d.ts +9 -2
  7. package/browser/web-notification/index.d.ts +3 -3
  8. package/fesm2022/signality-core-browser-clipboard.mjs +13 -30
  9. package/fesm2022/signality-core-browser-clipboard.mjs.map +1 -1
  10. package/fesm2022/signality-core-browser-device-pixel-ratio.mjs +45 -0
  11. package/fesm2022/signality-core-browser-device-pixel-ratio.mjs.map +1 -0
  12. package/fesm2022/signality-core-browser-eye-dropper.mjs +2 -3
  13. package/fesm2022/signality-core-browser-eye-dropper.mjs.map +1 -1
  14. package/fesm2022/signality-core-browser-file-dialog.mjs +2 -1
  15. package/fesm2022/signality-core-browser-file-dialog.mjs.map +1 -1
  16. package/fesm2022/signality-core-browser-fullscreen.mjs +8 -19
  17. package/fesm2022/signality-core-browser-fullscreen.mjs.map +1 -1
  18. package/fesm2022/signality-core-browser-gamepad.mjs +2 -10
  19. package/fesm2022/signality-core-browser-gamepad.mjs.map +1 -1
  20. package/fesm2022/signality-core-browser-listener.mjs +2 -1
  21. package/fesm2022/signality-core-browser-listener.mjs.map +1 -1
  22. package/fesm2022/signality-core-browser-media-query.mjs +2 -1
  23. package/fesm2022/signality-core-browser-media-query.mjs.map +1 -1
  24. package/fesm2022/signality-core-browser-picture-in-picture.mjs +10 -13
  25. package/fesm2022/signality-core-browser-picture-in-picture.mjs.map +1 -1
  26. package/fesm2022/signality-core-browser-speech-recognition.mjs +2 -1
  27. package/fesm2022/signality-core-browser-speech-recognition.mjs.map +1 -1
  28. package/fesm2022/signality-core-browser-speech-synthesis.mjs +2 -1
  29. package/fesm2022/signality-core-browser-speech-synthesis.mjs.map +1 -1
  30. package/fesm2022/signality-core-browser-storage.mjs +38 -68
  31. package/fesm2022/signality-core-browser-storage.mjs.map +1 -1
  32. package/fesm2022/signality-core-browser-text-direction.mjs +2 -1
  33. package/fesm2022/signality-core-browser-text-direction.mjs.map +1 -1
  34. package/fesm2022/signality-core-browser-text-selection.mjs +36 -4
  35. package/fesm2022/signality-core-browser-text-selection.mjs.map +1 -1
  36. package/fesm2022/signality-core-browser-vibration.mjs +16 -30
  37. package/fesm2022/signality-core-browser-vibration.mjs.map +1 -1
  38. package/fesm2022/signality-core-browser-web-notification.mjs +33 -53
  39. package/fesm2022/signality-core-browser-web-notification.mjs.map +1 -1
  40. package/fesm2022/signality-core-browser-web-share.mjs +3 -11
  41. package/fesm2022/signality-core-browser-web-share.mjs.map +1 -1
  42. package/fesm2022/signality-core-browser-web-worker.mjs +2 -1
  43. package/fesm2022/signality-core-browser-web-worker.mjs.map +1 -1
  44. package/fesm2022/signality-core-browser.mjs +1 -0
  45. package/fesm2022/signality-core-browser.mjs.map +1 -1
  46. package/fesm2022/signality-core-elements-dropzone.mjs +2 -1
  47. package/fesm2022/signality-core-elements-dropzone.mjs.map +1 -1
  48. package/fesm2022/signality-core-elements-element-focus-within.mjs +2 -1
  49. package/fesm2022/signality-core-elements-element-focus-within.mjs.map +1 -1
  50. package/fesm2022/signality-core-elements-element-focus.mjs +2 -1
  51. package/fesm2022/signality-core-elements-element-focus.mjs.map +1 -1
  52. package/fesm2022/signality-core-elements-element-size.mjs +2 -1
  53. package/fesm2022/signality-core-elements-element-size.mjs.map +1 -1
  54. package/fesm2022/signality-core-elements-on-click-outside.mjs +2 -1
  55. package/fesm2022/signality-core-elements-on-click-outside.mjs.map +1 -1
  56. package/fesm2022/signality-core-elements-on-disconnect.mjs +2 -1
  57. package/fesm2022/signality-core-elements-on-disconnect.mjs.map +1 -1
  58. package/fesm2022/signality-core-elements-on-long-press.mjs +2 -1
  59. package/fesm2022/signality-core-elements-on-long-press.mjs.map +1 -1
  60. package/fesm2022/signality-core-elements-scroll-position.mjs +2 -1
  61. package/fesm2022/signality-core-elements-scroll-position.mjs.map +1 -1
  62. package/fesm2022/signality-core-forms-cva.mjs +13 -3
  63. package/fesm2022/signality-core-forms-cva.mjs.map +1 -1
  64. package/fesm2022/signality-core-internal.mjs +35 -27
  65. package/fesm2022/signality-core-internal.mjs.map +1 -1
  66. package/fesm2022/signality-core-observers-intersection-observer.mjs +2 -1
  67. package/fesm2022/signality-core-observers-intersection-observer.mjs.map +1 -1
  68. package/fesm2022/signality-core-observers-mutation-observer.mjs +2 -1
  69. package/fesm2022/signality-core-observers-mutation-observer.mjs.map +1 -1
  70. package/fesm2022/signality-core-observers-resize-observer.mjs +2 -1
  71. package/fesm2022/signality-core-observers-resize-observer.mjs.map +1 -1
  72. package/fesm2022/signality-core-reactivity-debounced.mjs +2 -1
  73. package/fesm2022/signality-core-reactivity-debounced.mjs.map +1 -1
  74. package/fesm2022/signality-core-reactivity-throttled.mjs +2 -1
  75. package/fesm2022/signality-core-reactivity-throttled.mjs.map +1 -1
  76. package/fesm2022/signality-core-scheduling-debounce-callback.mjs +2 -1
  77. package/fesm2022/signality-core-scheduling-debounce-callback.mjs.map +1 -1
  78. package/fesm2022/signality-core-scheduling-interval.mjs +2 -1
  79. package/fesm2022/signality-core-scheduling-interval.mjs.map +1 -1
  80. package/fesm2022/signality-core-scheduling-throttle-callback.mjs +2 -1
  81. package/fesm2022/signality-core-scheduling-throttle-callback.mjs.map +1 -1
  82. package/fesm2022/signality-core-utilities.mjs +64 -0
  83. package/fesm2022/signality-core-utilities.mjs.map +1 -0
  84. package/fesm2022/signality-core.mjs +1 -0
  85. package/fesm2022/signality-core.mjs.map +1 -1
  86. package/forms/cva/index.d.ts +10 -5
  87. package/index.d.ts +1 -0
  88. package/internal/utils/index.d.ts +1 -2
  89. package/internal/utils/wait-for-value.d.ts +6 -0
  90. package/package.json +13 -5
  91. package/utilities/generate-id.d.ts +29 -0
  92. package/utilities/index.d.ts +3 -0
  93. /package/{internal/utils → utilities}/to-element.d.ts +0 -0
  94. /package/{internal/utils → utilities}/to-value.d.ts +0 -0
@@ -1 +1 @@
1
- {"version":3,"file":"signality-core-browser-media-query.mjs","sources":["../../../projects/core/browser/media-query/index.ts","../../../projects/core/browser/media-query/signality-core-browser-media-query.ts"],"sourcesContent":["import { type CreateSignalOptions, effect, type Signal, signal } from '@angular/core';\nimport { constSignal, setupContext, toValue, type Union } from '@signality/core/internal';\nimport type { MaybeSignal, WithInjector } from '@signality/core/types';\nimport { listener } from '@signality/core/browser/listener';\n\nexport interface MediaQueryOptions extends CreateSignalOptions<boolean>, WithInjector {\n /**\n * Initial value for SSR.\n * @default false\n */\n readonly initialValue?: boolean;\n}\n\n/**\n * Reactive wrapper around the [Window.matchMedia](https://developer.mozilla.org/en-US/docs/Web/API/Window/matchMedia) method.\n *\n * @param query - CSS media query string (can be a signal)\n * @param options - Optional configuration\n * @returns A signal that is true when the media query matches\n *\n * @example\n * ```typescript\n * @Component({\n * template: `\n * @if (prefersDark()) {\n * <p>Dark mode is preferred</p>\n * } @else {\n * <p>Light mode is preferred</p>\n * }\n * `\n * })\n * export class ThemeDemo {\n * readonly prefersDark = mediaQuery('(prefers-color-scheme: dark)');\n * }\n * ```\n */\nexport function mediaQuery(\n query: MaybeSignal<Union<MediaQueryFeature, string>>,\n options?: MediaQueryOptions\n): Signal<boolean> {\n const { runInContext } = setupContext(options?.injector, mediaQuery);\n\n return runInContext(({ isServer, injector }) => {\n if (isServer) {\n return constSignal(!!options?.initialValue);\n }\n\n const matches = signal(!!options?.initialValue, options);\n\n effect(onCleanup => {\n const queryString = toValue(query);\n\n if (!queryString) {\n matches.set(false);\n return;\n }\n\n const mediaQueryList = window.matchMedia(queryString);\n\n matches.set(mediaQueryList.matches);\n\n const changeListener = listener(\n mediaQueryList,\n 'change',\n (e: MediaQueryListEvent) => matches.set(e.matches),\n { injector }\n );\n\n onCleanup(changeListener.destroy);\n });\n\n return matches.asReadonly();\n });\n}\n\ntype MediaQueryFeature =\n | `(any-hover: ${'none' | 'hover'})`\n | `(any-pointer: ${'none' | 'coarse' | 'fine'})`\n | `(aspect-ratio: ${string})`\n | `(color: ${string})`\n | `(color-gamut: ${'srgb' | 'p3' | 'rec2020'})`\n | `(color-index: ${string})`\n | `(device-aspect-ratio: ${string})`\n | `(device-height: ${string})`\n | `(device-posture: ${'flat' | 'folded' | 'continuous'})`\n | `(device-width: ${string})`\n | `(display-mode: ${\n | 'fullscreen'\n | 'standalone'\n | 'minimal-ui'\n | 'browser'\n | 'picture-in-picture'\n | 'window-controls-overlay'})`\n | `(dynamic-range: ${'standard' | 'high'})`\n | `(forced-colors: ${'none' | 'active'})`\n | `(grid: ${'0' | '1'})`\n | `(height: ${string})`\n | `(hover: ${'none' | 'hover'})`\n | `(inverted-colors: ${'none' | 'inverted'})`\n | `(monochrome: ${string})`\n | `(orientation: ${'portrait' | 'landscape'})`\n | `(overflow-block: ${'none' | 'scroll' | 'optional-paged' | 'paged'})`\n | `(overflow-inline: ${'none' | 'scroll'})`\n | `(pointer: ${'none' | 'coarse' | 'fine'})`\n | `(prefers-color-scheme: ${'light' | 'dark' | 'no-preference'})`\n | `(prefers-contrast: ${'no-preference' | 'more' | 'less' | 'custom'})`\n | `(prefers-reduced-data: ${'no-preference' | 'reduce'})`\n | `(prefers-reduced-motion: ${'no-preference' | 'reduce'})`\n | `(prefers-reduced-transparency: ${'no-preference' | 'reduce'})`\n | `(resolution: ${string})`\n | `(scan: ${'progressive' | 'interlace'})`\n | `(scripting: ${'none' | 'initial-only' | 'enabled'})`\n | `(shape: ${'rect' | 'round'})`\n | `(update: ${'none' | 'slow' | 'fast'})`\n | `(video-dynamic-range: ${'standard' | 'high'})`\n | `(width: ${string})`\n | `(min-width: ${string})`\n | `(max-width: ${string})`\n | `(min-height: ${string})`\n | `(max-height: ${string})`\n | `(min-aspect-ratio: ${string})`\n | `(max-aspect-ratio: ${string})`\n | `(min-resolution: ${string})`\n | `(max-resolution: ${string})`\n | `(min-color: ${string})`\n | `(max-color: ${string})`\n | `(min-color-index: ${string})`\n | `(max-color-index: ${string})`\n | `(min-monochrome: ${string})`\n | `(max-monochrome: ${string})`;\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;;AAaA;;;;;;;;;;;;;;;;;;;;;;AAsBG;AACG,SAAU,UAAU,CACxB,KAAoD,EACpD,OAA2B,EAAA;AAE3B,IAAA,MAAM,EAAE,YAAY,EAAE,GAAG,YAAY,CAAC,OAAO,EAAE,QAAQ,EAAE,UAAU,CAAC;IAEpE,OAAO,YAAY,CAAC,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,KAAI;QAC7C,IAAI,QAAQ,EAAE;YACZ,OAAO,WAAW,CAAC,CAAC,CAAC,OAAO,EAAE,YAAY,CAAC;QAC7C;AAEA,QAAA,MAAM,OAAO,GAAG,MAAM,CAAC,CAAC,CAAC,OAAO,EAAE,YAAY,EAAE,OAAO,CAAC;QAExD,MAAM,CAAC,SAAS,IAAG;AACjB,YAAA,MAAM,WAAW,GAAG,OAAO,CAAC,KAAK,CAAC;YAElC,IAAI,CAAC,WAAW,EAAE;AAChB,gBAAA,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC;gBAClB;YACF;YAEA,MAAM,cAAc,GAAG,MAAM,CAAC,UAAU,CAAC,WAAW,CAAC;AAErD,YAAA,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,OAAO,CAAC;YAEnC,MAAM,cAAc,GAAG,QAAQ,CAC7B,cAAc,EACd,QAAQ,EACR,CAAC,CAAsB,KAAK,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,EAClD,EAAE,QAAQ,EAAE,CACb;AAED,YAAA,SAAS,CAAC,cAAc,CAAC,OAAO,CAAC;AACnC,QAAA,CAAC,CAAC;AAEF,QAAA,OAAO,OAAO,CAAC,UAAU,EAAE;AAC7B,IAAA,CAAC,CAAC;AACJ;;ACzEA;;AAEG;;;;"}
1
+ {"version":3,"file":"signality-core-browser-media-query.mjs","sources":["../../../projects/core/browser/media-query/index.ts","../../../projects/core/browser/media-query/signality-core-browser-media-query.ts"],"sourcesContent":["import { type CreateSignalOptions, effect, type Signal, signal } from '@angular/core';\nimport { constSignal, setupContext, type Union } from '@signality/core/internal';\nimport { toValue } from '@signality/core/utilities';\nimport type { MaybeSignal, WithInjector } from '@signality/core/types';\nimport { listener } from '@signality/core/browser/listener';\n\nexport interface MediaQueryOptions extends CreateSignalOptions<boolean>, WithInjector {\n /**\n * Initial value for SSR.\n * @default false\n */\n readonly initialValue?: boolean;\n}\n\n/**\n * Reactive wrapper around the [Window.matchMedia](https://developer.mozilla.org/en-US/docs/Web/API/Window/matchMedia) method.\n *\n * @param query - CSS media query string (can be a signal)\n * @param options - Optional configuration\n * @returns A signal that is true when the media query matches\n *\n * @example\n * ```typescript\n * @Component({\n * template: `\n * @if (prefersDark()) {\n * <p>Dark mode is preferred</p>\n * } @else {\n * <p>Light mode is preferred</p>\n * }\n * `\n * })\n * export class ThemeDemo {\n * readonly prefersDark = mediaQuery('(prefers-color-scheme: dark)');\n * }\n * ```\n */\nexport function mediaQuery(\n query: MaybeSignal<Union<MediaQueryFeature, string>>,\n options?: MediaQueryOptions\n): Signal<boolean> {\n const { runInContext } = setupContext(options?.injector, mediaQuery);\n\n return runInContext(({ isServer, injector }) => {\n if (isServer) {\n return constSignal(!!options?.initialValue);\n }\n\n const matches = signal(!!options?.initialValue, options);\n\n effect(onCleanup => {\n const queryString = toValue(query);\n\n if (!queryString) {\n matches.set(false);\n return;\n }\n\n const mediaQueryList = window.matchMedia(queryString);\n\n matches.set(mediaQueryList.matches);\n\n const changeListener = listener(\n mediaQueryList,\n 'change',\n (e: MediaQueryListEvent) => matches.set(e.matches),\n { injector }\n );\n\n onCleanup(changeListener.destroy);\n });\n\n return matches.asReadonly();\n });\n}\n\ntype MediaQueryFeature =\n | `(any-hover: ${'none' | 'hover'})`\n | `(any-pointer: ${'none' | 'coarse' | 'fine'})`\n | `(aspect-ratio: ${string})`\n | `(color: ${string})`\n | `(color-gamut: ${'srgb' | 'p3' | 'rec2020'})`\n | `(color-index: ${string})`\n | `(device-aspect-ratio: ${string})`\n | `(device-height: ${string})`\n | `(device-posture: ${'flat' | 'folded' | 'continuous'})`\n | `(device-width: ${string})`\n | `(display-mode: ${\n | 'fullscreen'\n | 'standalone'\n | 'minimal-ui'\n | 'browser'\n | 'picture-in-picture'\n | 'window-controls-overlay'})`\n | `(dynamic-range: ${'standard' | 'high'})`\n | `(forced-colors: ${'none' | 'active'})`\n | `(grid: ${'0' | '1'})`\n | `(height: ${string})`\n | `(hover: ${'none' | 'hover'})`\n | `(inverted-colors: ${'none' | 'inverted'})`\n | `(monochrome: ${string})`\n | `(orientation: ${'portrait' | 'landscape'})`\n | `(overflow-block: ${'none' | 'scroll' | 'optional-paged' | 'paged'})`\n | `(overflow-inline: ${'none' | 'scroll'})`\n | `(pointer: ${'none' | 'coarse' | 'fine'})`\n | `(prefers-color-scheme: ${'light' | 'dark' | 'no-preference'})`\n | `(prefers-contrast: ${'no-preference' | 'more' | 'less' | 'custom'})`\n | `(prefers-reduced-data: ${'no-preference' | 'reduce'})`\n | `(prefers-reduced-motion: ${'no-preference' | 'reduce'})`\n | `(prefers-reduced-transparency: ${'no-preference' | 'reduce'})`\n | `(resolution: ${string})`\n | `(scan: ${'progressive' | 'interlace'})`\n | `(scripting: ${'none' | 'initial-only' | 'enabled'})`\n | `(shape: ${'rect' | 'round'})`\n | `(update: ${'none' | 'slow' | 'fast'})`\n | `(video-dynamic-range: ${'standard' | 'high'})`\n | `(width: ${string})`\n | `(min-width: ${string})`\n | `(max-width: ${string})`\n | `(min-height: ${string})`\n | `(max-height: ${string})`\n | `(min-aspect-ratio: ${string})`\n | `(max-aspect-ratio: ${string})`\n | `(min-resolution: ${string})`\n | `(max-resolution: ${string})`\n | `(min-color: ${string})`\n | `(max-color: ${string})`\n | `(min-color-index: ${string})`\n | `(max-color-index: ${string})`\n | `(min-monochrome: ${string})`\n | `(max-monochrome: ${string})`;\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;;;AAcA;;;;;;;;;;;;;;;;;;;;;;AAsBG;AACG,SAAU,UAAU,CACxB,KAAoD,EACpD,OAA2B,EAAA;AAE3B,IAAA,MAAM,EAAE,YAAY,EAAE,GAAG,YAAY,CAAC,OAAO,EAAE,QAAQ,EAAE,UAAU,CAAC;IAEpE,OAAO,YAAY,CAAC,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,KAAI;QAC7C,IAAI,QAAQ,EAAE;YACZ,OAAO,WAAW,CAAC,CAAC,CAAC,OAAO,EAAE,YAAY,CAAC;QAC7C;AAEA,QAAA,MAAM,OAAO,GAAG,MAAM,CAAC,CAAC,CAAC,OAAO,EAAE,YAAY,EAAE,OAAO,CAAC;QAExD,MAAM,CAAC,SAAS,IAAG;AACjB,YAAA,MAAM,WAAW,GAAG,OAAO,CAAC,KAAK,CAAC;YAElC,IAAI,CAAC,WAAW,EAAE;AAChB,gBAAA,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC;gBAClB;YACF;YAEA,MAAM,cAAc,GAAG,MAAM,CAAC,UAAU,CAAC,WAAW,CAAC;AAErD,YAAA,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,OAAO,CAAC;YAEnC,MAAM,cAAc,GAAG,QAAQ,CAC7B,cAAc,EACd,QAAQ,EACR,CAAC,CAAsB,KAAK,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,EAClD,EAAE,QAAQ,EAAE,CACb;AAED,YAAA,SAAS,CAAC,cAAc,CAAC,OAAO,CAAC;AACnC,QAAA,CAAC,CAAC;AAEF,QAAA,OAAO,OAAO,CAAC,UAAU,EAAE;AAC7B,IAAA,CAAC,CAAC;AACJ;;AC1EA;;AAEG;;;;"}
@@ -1,5 +1,6 @@
1
1
  import { signal, untracked } from '@angular/core';
2
- import { setupContext, constSignal, NOOP_ASYNC_FN, toElement, assertElement, getPipElement } from '@signality/core/internal';
2
+ import { setupContext, constSignal, NOOP_ASYNC_FN, assertElement, getPipElement } from '@signality/core/internal';
3
+ import { toElement } from '@signality/core/utilities';
3
4
  import { listener } from '@signality/core/browser/listener';
4
5
  import { onDisconnect } from '@signality/core/elements/on-disconnect';
5
6
 
@@ -24,7 +25,7 @@ import { onDisconnect } from '@signality/core/elements/on-disconnect';
24
25
  * `
25
26
  * })
26
27
  * export class PiPDemo {
27
- * readonly video = viewChild<HTMLVideoElement>('video');
28
+ * readonly video = viewChild<ElementRef>('video');
28
29
  * readonly pip = pictureInPicture(this.video);
29
30
  * }
30
31
  * ```
@@ -45,21 +46,15 @@ function pictureInPicture(target, options) {
45
46
  const isActive = signal(false, ...(ngDevMode ? [{ debugName: "isActive" }] : []));
46
47
  const enter = async () => {
47
48
  const targetEl = toElement.untracked(target);
48
- assertElement(targetEl, 'pictureInPicture');
49
- await targetEl.requestPictureInPicture();
49
+ ngDevMode && assertElement(targetEl, 'pictureInPicture');
50
+ await targetEl?.requestPictureInPicture();
50
51
  };
51
52
  const exit = async () => {
52
53
  const targetEl = toElement.untracked(target);
54
+ ngDevMode && assertElement(targetEl, 'pictureInPicture');
53
55
  const pipEl = getPipElement(document);
54
56
  if (targetEl === pipEl) {
55
- try {
56
- await document.exitPictureInPicture();
57
- }
58
- catch (error) {
59
- if (ngDevMode) {
60
- console.warn(`[pictureInPicture] Failed to exit Picture-in-Picture mode.`, error);
61
- }
62
- }
57
+ await document.exitPictureInPicture();
63
58
  }
64
59
  };
65
60
  const toggle = async () => {
@@ -83,7 +78,9 @@ function pictureInPicture(target, options) {
83
78
  console.warn(`[pictureInPicture] Failed to exit Picture-in-Picture mode on disconnect.`, error);
84
79
  }
85
80
  }
86
- isActive.set(false);
81
+ finally {
82
+ isActive.set(false);
83
+ }
87
84
  }
88
85
  });
89
86
  return {
@@ -1 +1 @@
1
- {"version":3,"file":"signality-core-browser-picture-in-picture.mjs","sources":["../../../projects/core/browser/picture-in-picture/index.ts","../../../projects/core/browser/picture-in-picture/signality-core-browser-picture-in-picture.ts"],"sourcesContent":["import { signal, type Signal, untracked } from '@angular/core';\nimport {\n assertElement,\n constSignal,\n getPipElement,\n NOOP_ASYNC_FN,\n setupContext,\n toElement,\n} from '@signality/core/internal';\nimport type { MaybeElementSignal, WithInjector } from '@signality/core/types';\nimport { listener } from '@signality/core/browser/listener';\nimport { onDisconnect } from '@signality/core/elements/on-disconnect';\n\nexport type PictureInPictureOptions = WithInjector;\n\nexport interface PictureInPictureRef {\n /**\n * Whether the Picture-in-Picture API is supported in the current browser.\n *\n * @see [Picture-in-Picture API browser compatibility on MDN](https://developer.mozilla.org/en-US/docs/Web/API/Picture-in-Picture_API#browser_compatibility)\n */\n readonly isSupported: Signal<boolean>;\n\n /**\n * Whether the target video element is currently displayed in Picture-in-Picture mode.\n *\n * @see [Document: pictureInPictureElement on MDN](https://developer.mozilla.org/en-US/docs/Web/API/Document/pictureInPictureElement)\n */\n readonly isActive: Signal<boolean>;\n\n /**\n * Enter Picture-in-Picture mode for the target video element.\n *\n * @throws {DOMException} `'NotAllowedError'` — the document is not allowed to use PiP\n * @throws {DOMException} `'InvalidStateError'` — the video element has `disablePictureInPicture` attribute\n * @throws {DOMException} `'NotSupportedError'` — Picture-in-Picture is not supported\n *\n * @see [HTMLVideoElement: requestPictureInPicture() on MDN](https://developer.mozilla.org/en-US/docs/Web/API/HTMLVideoElement/requestPictureInPicture)\n */\n readonly enter: () => Promise<void>;\n\n /**\n * Exit Picture-in-Picture mode.\n *\n * @see [Document: exitPictureInPicture() on MDN](https://developer.mozilla.org/en-US/docs/Web/API/Document/exitPictureInPicture)\n */\n readonly exit: () => Promise<void>;\n\n /**\n * Toggle Picture-in-Picture mode — enters if inactive, exits if active.\n */\n readonly toggle: () => Promise<void>;\n}\n\n/**\n * Signal-based wrapper around the [Picture-in-Picture API](https://developer.mozilla.org/en-US/docs/Web/API/Picture-in-Picture_API).\n *\n * Automatically exits Picture-in-Picture when the target element is disconnected from the DOM.\n *\n * @param target - Video element\n * @param options - Optional configuration\n * @returns A {@link PictureInPictureRef} with `isSupported`, `isActive` signals and `enter`/`exit`/`toggle` methods\n *\n * @example\n * ```typescript\n * @Component({\n * template: `\n * @if (pip.isSupported()) {\n * <video #video src=\"video.mp4\"></video>\n * <button (click)=\"pip.toggle()\">Toggle PiP</button>\n * <p>Active: {{ pip.isActive() }}</p>\n * }\n * `\n * })\n * export class PiPDemo {\n * readonly video = viewChild<HTMLVideoElement>('video');\n * readonly pip = pictureInPicture(this.video);\n * }\n * ```\n */\nexport function pictureInPicture(\n target: MaybeElementSignal<HTMLVideoElement>,\n options?: PictureInPictureOptions\n): PictureInPictureRef {\n const { runInContext } = setupContext(options?.injector, pictureInPicture);\n\n return runInContext(({ isBrowser }) => {\n const isSupported = constSignal(\n isBrowser && 'pictureInPictureEnabled' in document && document.pictureInPictureEnabled\n );\n\n if (!isSupported()) {\n return {\n isSupported,\n isActive: constSignal(false),\n enter: NOOP_ASYNC_FN,\n exit: NOOP_ASYNC_FN,\n toggle: NOOP_ASYNC_FN,\n };\n }\n\n const isActive = signal(false);\n\n const enter = async (): Promise<void> => {\n const targetEl = toElement.untracked(target);\n assertElement(targetEl, 'pictureInPicture');\n await targetEl.requestPictureInPicture();\n };\n\n const exit = async (): Promise<void> => {\n const targetEl = toElement.untracked(target);\n const pipEl = getPipElement(document);\n\n if (targetEl === pipEl) {\n try {\n await document.exitPictureInPicture();\n } catch (error) {\n if (ngDevMode) {\n console.warn(`[pictureInPicture] Failed to exit Picture-in-Picture mode.`, error);\n }\n }\n }\n };\n\n const toggle = async (): Promise<void> => {\n if (untracked(isActive)) {\n await exit();\n } else {\n await enter();\n }\n };\n\n listener(target, 'enterpictureinpicture', () => isActive.set(true));\n listener(target, 'leavepictureinpicture', () => isActive.set(false));\n\n onDisconnect(target, async targetEl => {\n const pipEl = getPipElement(document);\n\n if (pipEl && targetEl === pipEl) {\n try {\n await document.exitPictureInPicture();\n } catch (error) {\n if (ngDevMode) {\n console.warn(\n `[pictureInPicture] Failed to exit Picture-in-Picture mode on disconnect.`,\n error\n );\n }\n }\n isActive.set(false);\n }\n });\n\n return {\n isSupported,\n isActive,\n enter,\n exit,\n toggle,\n };\n });\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;;;AAsDA;;;;;;;;;;;;;;;;;;;;;;;;;AAyBG;AACG,SAAU,gBAAgB,CAC9B,MAA4C,EAC5C,OAAiC,EAAA;AAEjC,IAAA,MAAM,EAAE,YAAY,EAAE,GAAG,YAAY,CAAC,OAAO,EAAE,QAAQ,EAAE,gBAAgB,CAAC;AAE1E,IAAA,OAAO,YAAY,CAAC,CAAC,EAAE,SAAS,EAAE,KAAI;AACpC,QAAA,MAAM,WAAW,GAAG,WAAW,CAC7B,SAAS,IAAI,yBAAyB,IAAI,QAAQ,IAAI,QAAQ,CAAC,uBAAuB,CACvF;AAED,QAAA,IAAI,CAAC,WAAW,EAAE,EAAE;YAClB,OAAO;gBACL,WAAW;AACX,gBAAA,QAAQ,EAAE,WAAW,CAAC,KAAK,CAAC;AAC5B,gBAAA,KAAK,EAAE,aAAa;AACpB,gBAAA,IAAI,EAAE,aAAa;AACnB,gBAAA,MAAM,EAAE,aAAa;aACtB;QACH;AAEA,QAAA,MAAM,QAAQ,GAAG,MAAM,CAAC,KAAK,oDAAC;AAE9B,QAAA,MAAM,KAAK,GAAG,YAA0B;YACtC,MAAM,QAAQ,GAAG,SAAS,CAAC,SAAS,CAAC,MAAM,CAAC;AAC5C,YAAA,aAAa,CAAC,QAAQ,EAAE,kBAAkB,CAAC;AAC3C,YAAA,MAAM,QAAQ,CAAC,uBAAuB,EAAE;AAC1C,QAAA,CAAC;AAED,QAAA,MAAM,IAAI,GAAG,YAA0B;YACrC,MAAM,QAAQ,GAAG,SAAS,CAAC,SAAS,CAAC,MAAM,CAAC;AAC5C,YAAA,MAAM,KAAK,GAAG,aAAa,CAAC,QAAQ,CAAC;AAErC,YAAA,IAAI,QAAQ,KAAK,KAAK,EAAE;AACtB,gBAAA,IAAI;AACF,oBAAA,MAAM,QAAQ,CAAC,oBAAoB,EAAE;gBACvC;gBAAE,OAAO,KAAK,EAAE;oBACd,IAAI,SAAS,EAAE;AACb,wBAAA,OAAO,CAAC,IAAI,CAAC,4DAA4D,EAAE,KAAK,CAAC;oBACnF;gBACF;YACF;AACF,QAAA,CAAC;AAED,QAAA,MAAM,MAAM,GAAG,YAA0B;AACvC,YAAA,IAAI,SAAS,CAAC,QAAQ,CAAC,EAAE;gBACvB,MAAM,IAAI,EAAE;YACd;iBAAO;gBACL,MAAM,KAAK,EAAE;YACf;AACF,QAAA,CAAC;AAED,QAAA,QAAQ,CAAC,MAAM,EAAE,uBAAuB,EAAE,MAAM,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;AACnE,QAAA,QAAQ,CAAC,MAAM,EAAE,uBAAuB,EAAE,MAAM,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;AAEpE,QAAA,YAAY,CAAC,MAAM,EAAE,OAAM,QAAQ,KAAG;AACpC,YAAA,MAAM,KAAK,GAAG,aAAa,CAAC,QAAQ,CAAC;AAErC,YAAA,IAAI,KAAK,IAAI,QAAQ,KAAK,KAAK,EAAE;AAC/B,gBAAA,IAAI;AACF,oBAAA,MAAM,QAAQ,CAAC,oBAAoB,EAAE;gBACvC;gBAAE,OAAO,KAAK,EAAE;oBACd,IAAI,SAAS,EAAE;AACb,wBAAA,OAAO,CAAC,IAAI,CACV,0EAA0E,EAC1E,KAAK,CACN;oBACH;gBACF;AACA,gBAAA,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC;YACrB;AACF,QAAA,CAAC,CAAC;QAEF,OAAO;YACL,WAAW;YACX,QAAQ;YACR,KAAK;YACL,IAAI;YACJ,MAAM;SACP;AACH,IAAA,CAAC,CAAC;AACJ;;ACjKA;;AAEG;;;;"}
1
+ {"version":3,"file":"signality-core-browser-picture-in-picture.mjs","sources":["../../../projects/core/browser/picture-in-picture/index.ts","../../../projects/core/browser/picture-in-picture/signality-core-browser-picture-in-picture.ts"],"sourcesContent":["import { signal, type Signal, untracked } from '@angular/core';\nimport {\n assertElement,\n constSignal,\n getPipElement,\n NOOP_ASYNC_FN,\n setupContext,\n} from '@signality/core/internal';\nimport { toElement } from '@signality/core/utilities';\nimport type { MaybeElementSignal, WithInjector } from '@signality/core/types';\nimport { listener } from '@signality/core/browser/listener';\nimport { onDisconnect } from '@signality/core/elements/on-disconnect';\n\nexport type PictureInPictureOptions = WithInjector;\n\nexport interface PictureInPictureRef {\n /**\n * Whether the Picture-in-Picture API is supported in the current browser.\n *\n * @see [Picture-in-Picture API browser compatibility on MDN](https://developer.mozilla.org/en-US/docs/Web/API/Picture-in-Picture_API#browser_compatibility)\n */\n readonly isSupported: Signal<boolean>;\n\n /**\n * Whether the target video element is currently displayed in Picture-in-Picture mode.\n *\n * @see [Document: pictureInPictureElement on MDN](https://developer.mozilla.org/en-US/docs/Web/API/Document/pictureInPictureElement)\n */\n readonly isActive: Signal<boolean>;\n\n /**\n * Enter Picture-in-Picture mode for the target video element.\n *\n * @see [HTMLVideoElement: requestPictureInPicture() on MDN](https://developer.mozilla.org/en-US/docs/Web/API/HTMLVideoElement/requestPictureInPicture)\n */\n readonly enter: () => Promise<void>;\n\n /**\n * Exit Picture-in-Picture mode.\n *\n * @see [Document: exitPictureInPicture() on MDN](https://developer.mozilla.org/en-US/docs/Web/API/Document/exitPictureInPicture)\n */\n readonly exit: () => Promise<void>;\n\n /**\n * Toggle Picture-in-Picture mode — enters if inactive, exits if active.\n */\n readonly toggle: () => Promise<void>;\n}\n\n/**\n * Signal-based wrapper around the [Picture-in-Picture API](https://developer.mozilla.org/en-US/docs/Web/API/Picture-in-Picture_API).\n *\n * Automatically exits Picture-in-Picture when the target element is disconnected from the DOM.\n *\n * @param target - Video element\n * @param options - Optional configuration\n * @returns A {@link PictureInPictureRef} with `isSupported`, `isActive` signals and `enter`/`exit`/`toggle` methods\n *\n * @example\n * ```typescript\n * @Component({\n * template: `\n * @if (pip.isSupported()) {\n * <video #video src=\"video.mp4\"></video>\n * <button (click)=\"pip.toggle()\">Toggle PiP</button>\n * <p>Active: {{ pip.isActive() }}</p>\n * }\n * `\n * })\n * export class PiPDemo {\n * readonly video = viewChild<ElementRef>('video');\n * readonly pip = pictureInPicture(this.video);\n * }\n * ```\n */\nexport function pictureInPicture(\n target: MaybeElementSignal<HTMLVideoElement>,\n options?: PictureInPictureOptions\n): PictureInPictureRef {\n const { runInContext } = setupContext(options?.injector, pictureInPicture);\n\n return runInContext(({ isBrowser }) => {\n const isSupported = constSignal(\n isBrowser && 'pictureInPictureEnabled' in document && document.pictureInPictureEnabled\n );\n\n if (!isSupported()) {\n return {\n isSupported,\n isActive: constSignal(false),\n enter: NOOP_ASYNC_FN,\n exit: NOOP_ASYNC_FN,\n toggle: NOOP_ASYNC_FN,\n };\n }\n\n const isActive = signal(false);\n\n const enter = async (): Promise<void> => {\n const targetEl = toElement.untracked(target);\n ngDevMode && assertElement(targetEl, 'pictureInPicture');\n await targetEl?.requestPictureInPicture();\n };\n\n const exit = async (): Promise<void> => {\n const targetEl = toElement.untracked(target);\n ngDevMode && assertElement(targetEl, 'pictureInPicture');\n const pipEl = getPipElement(document);\n if (targetEl === pipEl) {\n await document.exitPictureInPicture();\n }\n };\n\n const toggle = async (): Promise<void> => {\n if (untracked(isActive)) {\n await exit();\n } else {\n await enter();\n }\n };\n\n listener(target, 'enterpictureinpicture', () => isActive.set(true));\n listener(target, 'leavepictureinpicture', () => isActive.set(false));\n\n onDisconnect(target, async targetEl => {\n const pipEl = getPipElement(document);\n\n if (pipEl && targetEl === pipEl) {\n try {\n await document.exitPictureInPicture();\n } catch (error) {\n if (ngDevMode) {\n console.warn(\n `[pictureInPicture] Failed to exit Picture-in-Picture mode on disconnect.`,\n error\n );\n }\n } finally {\n isActive.set(false);\n }\n }\n });\n\n return {\n isSupported,\n isActive,\n enter,\n exit,\n toggle,\n };\n });\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;;;;AAkDA;;;;;;;;;;;;;;;;;;;;;;;;;AAyBG;AACG,SAAU,gBAAgB,CAC9B,MAA4C,EAC5C,OAAiC,EAAA;AAEjC,IAAA,MAAM,EAAE,YAAY,EAAE,GAAG,YAAY,CAAC,OAAO,EAAE,QAAQ,EAAE,gBAAgB,CAAC;AAE1E,IAAA,OAAO,YAAY,CAAC,CAAC,EAAE,SAAS,EAAE,KAAI;AACpC,QAAA,MAAM,WAAW,GAAG,WAAW,CAC7B,SAAS,IAAI,yBAAyB,IAAI,QAAQ,IAAI,QAAQ,CAAC,uBAAuB,CACvF;AAED,QAAA,IAAI,CAAC,WAAW,EAAE,EAAE;YAClB,OAAO;gBACL,WAAW;AACX,gBAAA,QAAQ,EAAE,WAAW,CAAC,KAAK,CAAC;AAC5B,gBAAA,KAAK,EAAE,aAAa;AACpB,gBAAA,IAAI,EAAE,aAAa;AACnB,gBAAA,MAAM,EAAE,aAAa;aACtB;QACH;AAEA,QAAA,MAAM,QAAQ,GAAG,MAAM,CAAC,KAAK,oDAAC;AAE9B,QAAA,MAAM,KAAK,GAAG,YAA0B;YACtC,MAAM,QAAQ,GAAG,SAAS,CAAC,SAAS,CAAC,MAAM,CAAC;AAC5C,YAAA,SAAS,IAAI,aAAa,CAAC,QAAQ,EAAE,kBAAkB,CAAC;AACxD,YAAA,MAAM,QAAQ,EAAE,uBAAuB,EAAE;AAC3C,QAAA,CAAC;AAED,QAAA,MAAM,IAAI,GAAG,YAA0B;YACrC,MAAM,QAAQ,GAAG,SAAS,CAAC,SAAS,CAAC,MAAM,CAAC;AAC5C,YAAA,SAAS,IAAI,aAAa,CAAC,QAAQ,EAAE,kBAAkB,CAAC;AACxD,YAAA,MAAM,KAAK,GAAG,aAAa,CAAC,QAAQ,CAAC;AACrC,YAAA,IAAI,QAAQ,KAAK,KAAK,EAAE;AACtB,gBAAA,MAAM,QAAQ,CAAC,oBAAoB,EAAE;YACvC;AACF,QAAA,CAAC;AAED,QAAA,MAAM,MAAM,GAAG,YAA0B;AACvC,YAAA,IAAI,SAAS,CAAC,QAAQ,CAAC,EAAE;gBACvB,MAAM,IAAI,EAAE;YACd;iBAAO;gBACL,MAAM,KAAK,EAAE;YACf;AACF,QAAA,CAAC;AAED,QAAA,QAAQ,CAAC,MAAM,EAAE,uBAAuB,EAAE,MAAM,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;AACnE,QAAA,QAAQ,CAAC,MAAM,EAAE,uBAAuB,EAAE,MAAM,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;AAEpE,QAAA,YAAY,CAAC,MAAM,EAAE,OAAM,QAAQ,KAAG;AACpC,YAAA,MAAM,KAAK,GAAG,aAAa,CAAC,QAAQ,CAAC;AAErC,YAAA,IAAI,KAAK,IAAI,QAAQ,KAAK,KAAK,EAAE;AAC/B,gBAAA,IAAI;AACF,oBAAA,MAAM,QAAQ,CAAC,oBAAoB,EAAE;gBACvC;gBAAE,OAAO,KAAK,EAAE;oBACd,IAAI,SAAS,EAAE;AACb,wBAAA,OAAO,CAAC,IAAI,CACV,0EAA0E,EAC1E,KAAK,CACN;oBACH;gBACF;wBAAU;AACR,oBAAA,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC;gBACrB;YACF;AACF,QAAA,CAAC,CAAC;QAEF,OAAO;YACL,WAAW;YACX,QAAQ;YACR,KAAK;YACL,IAAI;YACJ,MAAM;SACP;AACH,IAAA,CAAC,CAAC;AACJ;;ACxJA;;AAEG;;;;"}
@@ -1,5 +1,6 @@
1
1
  import { signal, untracked, isSignal } from '@angular/core';
2
- import { setupContext, constSignal, NOOP_FN, toValue } from '@signality/core/internal';
2
+ import { setupContext, constSignal, NOOP_FN } from '@signality/core/internal';
3
+ import { toValue } from '@signality/core/utilities';
3
4
  import { watcher } from '@signality/core/reactivity/watcher';
4
5
  import { permissionState } from '@signality/core/browser/permission-state';
5
6
 
@@ -1 +1 @@
1
- {"version":3,"file":"signality-core-browser-speech-recognition.mjs","sources":["../../../projects/core/browser/speech-recognition/index.ts","../../../projects/core/browser/speech-recognition/signality-core-browser-speech-recognition.ts"],"sourcesContent":["import { isSignal, type Signal, signal, untracked } from '@angular/core';\nimport { constSignal, NOOP_FN, setupContext, toValue } from '@signality/core/internal';\nimport type { MaybeSignal, WithInjector } from '@signality/core/types';\nimport { watcher } from '@signality/core/reactivity/watcher';\nimport { permissionState } from '@signality/core/browser/permission-state';\n\nexport interface SpeechRecognitionOptions extends WithInjector {\n /**\n * BCP 47 language tag for recognition (e.g. `'en-US'`).\n *\n * @default 'en-US'\n * @see [SpeechRecognition: lang on MDN](https://developer.mozilla.org/en-US/docs/Web/API/SpeechRecognition/lang)\n */\n readonly lang?: MaybeSignal<string>;\n\n /**\n * Whether to return interim (in-progress) results alongside final ones.\n *\n * @default false\n * @see [SpeechRecognition: interimResults on MDN](https://developer.mozilla.org/en-US/docs/Web/API/SpeechRecognition/interimResults)\n */\n readonly interimResults?: boolean;\n\n /**\n * Whether recognition continues after the user stops speaking.\n *\n * @default false\n * @see [SpeechRecognition: continuous on MDN](https://developer.mozilla.org/en-US/docs/Web/API/SpeechRecognition/continuous)\n */\n readonly continuous?: boolean;\n\n /**\n * Maximum number of alternative recognition results per utterance.\n *\n * @default 1\n * @see [SpeechRecognition: maxAlternatives on MDN](https://developer.mozilla.org/en-US/docs/Web/API/SpeechRecognition/maxAlternatives)\n */\n readonly maxAlternatives?: number;\n}\n\nexport interface SpeechRecognitionRef {\n /**\n * Whether the Speech Recognition API is supported in the current browser.\n *\n * @see [SpeechRecognition browser compatibility on MDN](https://developer.mozilla.org/en-US/docs/Web/API/SpeechRecognition#browser_compatibility)\n */\n readonly isSupported: Signal<boolean>;\n\n /**\n * Whether speech recognition is currently active and listening.\n */\n readonly isListening: Signal<boolean>;\n\n /**\n * Accumulated final transcript text.\n *\n * @see [SpeechRecognitionResult on MDN](https://developer.mozilla.org/en-US/docs/Web/API/SpeechRecognitionResult)\n */\n readonly text: Signal<string>;\n\n /**\n * In-progress interim transcript. Only populated when `interimResults` is `true`.\n *\n * @see [SpeechRecognition: interimResults on MDN](https://developer.mozilla.org/en-US/docs/Web/API/SpeechRecognition/interimResults)\n */\n readonly interimText: Signal<string>;\n\n /**\n * The last recognition error, or `null` if no error occurred.\n *\n * @see [SpeechRecognitionErrorEvent on MDN](https://developer.mozilla.org/en-US/docs/Web/API/SpeechRecognitionErrorEvent)\n */\n readonly error: Signal<SpeechRecognitionErrorEvent | Error | null>;\n\n /**\n * Start listening for speech input.\n *\n * @see [SpeechRecognition: start() on MDN](https://developer.mozilla.org/en-US/docs/Web/API/SpeechRecognition/start)\n */\n readonly start: () => void;\n\n /**\n * Stop listening and return any remaining results.\n *\n * @see [SpeechRecognition: stop() on MDN](https://developer.mozilla.org/en-US/docs/Web/API/SpeechRecognition/stop)\n */\n readonly stop: () => void;\n\n /**\n * Abort recognition immediately without returning results.\n *\n * @see [SpeechRecognition: abort() on MDN](https://developer.mozilla.org/en-US/docs/Web/API/SpeechRecognition/abort)\n */\n readonly abort: () => void;\n}\n\n/**\n * Signal-based wrapper around the [Speech Recognition API](https://developer.mozilla.org/en-US/docs/Web/API/SpeechRecognition).\n *\n * @param options - Optional configuration\n * @returns A SpeechRecognitionRef with isSupported, isListening, text, interimText, error signals and control methods\n *\n * @example\n * ```typescript\n * @Component({\n * template: `\n * @if (recognition.isSupported()) {\n * <button (click)=\"toggleRecognition()\">\n * {{ recognition.isListening() ? 'Stop' : 'Start' }} Recording\n * </button>\n * <p>{{ recognition.text() }}</p>\n * @if (recognition.interimText()) {\n * <p><em>{{ recognition.interimText() }}</em></p>\n * }\n * }\n * `\n * })\n * export class SpeechComponent {\n * readonly recognition = speechRecognition();\n *\n * toggleRecognition() {\n * if (this.recognition.isListening()) {\n * this.recognition.stop();\n * } else {\n * this.recognition.start();\n * }\n * }\n * }\n * ```\n */\nexport function speechRecognition(options?: SpeechRecognitionOptions): SpeechRecognitionRef {\n const { runInContext } = setupContext(options?.injector, speechRecognition);\n\n return runInContext(({ isBrowser, onCleanup }) => {\n const isSupported = constSignal(\n isBrowser && ('SpeechRecognition' in window || 'webkitSpeechRecognition' in window)\n );\n\n if (!isSupported()) {\n return {\n isSupported,\n isListening: constSignal(false),\n text: constSignal(''),\n interimText: constSignal(''),\n error: constSignal(null),\n start: NOOP_FN,\n stop: NOOP_FN,\n abort: NOOP_FN,\n };\n }\n\n const SpeechRecognitionClass =\n (window as any).SpeechRecognition || (window as any).webkitSpeechRecognition;\n\n const recognition: SpeechRecognition = new SpeechRecognitionClass();\n\n const {\n lang = 'en-US',\n interimResults = false,\n continuous = false,\n maxAlternatives = 1,\n } = options ?? {};\n\n recognition.lang = toValue(lang);\n recognition.continuous = continuous;\n recognition.interimResults = interimResults;\n recognition.maxAlternatives = maxAlternatives;\n\n const isListening = signal(false);\n const text = signal('');\n const interimText = signal('');\n const error = signal<SpeechRecognitionErrorEvent | Error | null>(null);\n\n const handleResult = (event: SpeechRecognitionEvent) => {\n let finalTranscript = '';\n let interimTranscript = '';\n\n for (let i = event.resultIndex; i < event.results.length; i++) {\n const result = event.results[i];\n const transcript = result[0]?.transcript || result.item(0)?.transcript || '';\n\n if (result.isFinal) {\n finalTranscript += transcript;\n } else {\n interimTranscript += transcript;\n }\n }\n\n if (finalTranscript) {\n text.update(t => (t ? t + ' ' : '') + finalTranscript);\n interimText.set('');\n } else if (interimTranscript) {\n interimText.set(interimTranscript);\n }\n };\n\n const handleError = (event: SpeechRecognitionErrorEvent) => {\n error.set(event);\n isListening.set(false);\n };\n\n const handleStart = () => {\n isListening.set(true);\n error.set(null);\n\n if (!continuous) {\n text.set('');\n interimText.set('');\n }\n };\n\n const handleEnd = () => {\n isListening.set(false);\n recognition.lang = toValue(lang);\n };\n\n recognition.onstart = handleStart;\n recognition.onend = handleEnd;\n recognition.onerror = handleError;\n recognition.onresult = handleResult;\n\n const start = () => {\n try {\n if (!untracked(isListening)) {\n recognition.start();\n }\n } catch (err) {\n error.set(err as Error);\n }\n };\n\n const stop = () => {\n if (untracked(isListening)) {\n recognition.stop();\n }\n };\n\n const abort = () => {\n if (untracked(isListening)) {\n recognition.abort();\n }\n };\n\n onCleanup(abort);\n\n if (isSignal(lang)) {\n watcher(lang, newLang => {\n if (!isListening()) {\n recognition.lang = newLang;\n }\n });\n }\n\n watcher(permissionState('microphone'), state => {\n if (state === 'denied') {\n abort();\n }\n });\n\n return {\n isSupported,\n isListening: isListening.asReadonly(),\n text: text.asReadonly(),\n interimText: interimText.asReadonly(),\n error: error.asReadonly(),\n start,\n stop,\n abort,\n };\n });\n}\n\n/**\n * Local type definitions for Web Speech API (Speech Recognition).\n *\n * @remarks\n * External `@types/dom-speech-recognition` package may conflict with user's other libraries\n * or become outdated. For better DX, we define minimal required interfaces locally\n * without polluting the global namespace with experimental APIs.\n */\ntype SpeechRecognitionErrorCode =\n | 'aborted'\n | 'audio-capture'\n | 'bad-grammar'\n | 'language-not-supported'\n | 'network'\n | 'no-speech'\n | 'not-allowed'\n | 'service-not-allowed';\n\ninterface SpeechRecognitionAlternative {\n readonly transcript: string;\n readonly confidence: number;\n}\n\ninterface SpeechRecognitionResult {\n readonly isFinal: boolean;\n readonly length: number;\n item(index: number): SpeechRecognitionAlternative;\n [index: number]: SpeechRecognitionAlternative;\n}\n\ninterface SpeechRecognitionResultList {\n readonly length: number;\n item(index: number): SpeechRecognitionResult;\n [index: number]: SpeechRecognitionResult;\n}\n\ninterface SpeechGrammar {\n src: string;\n weight: number;\n}\n\ninterface SpeechGrammarList {\n readonly length: number;\n addFromString(string: string, weight?: number): void;\n addFromURI(src: string, weight?: number): void;\n item(index: number): SpeechGrammar;\n [index: number]: SpeechGrammar;\n}\n\ninterface SpeechRecognitionEvent extends Event {\n readonly resultIndex: number;\n readonly results: SpeechRecognitionResultList;\n}\n\ninterface SpeechRecognitionErrorEvent extends Event {\n readonly error: SpeechRecognitionErrorCode;\n readonly message: string;\n}\n\ninterface SpeechRecognition extends EventTarget {\n continuous: boolean;\n grammars: SpeechGrammarList;\n interimResults: boolean;\n lang: string;\n maxAlternatives: number;\n onaudioend: ((this: SpeechRecognition, ev: Event) => void) | null;\n onaudiostart: ((this: SpeechRecognition, ev: Event) => void) | null;\n onend: ((this: SpeechRecognition, ev: Event) => void) | null;\n onerror: ((this: SpeechRecognition, ev: SpeechRecognitionErrorEvent) => void) | null;\n onnomatch: ((this: SpeechRecognition, ev: SpeechRecognitionEvent) => void) | null;\n onresult: ((this: SpeechRecognition, ev: SpeechRecognitionEvent) => void) | null;\n onsoundend: ((this: SpeechRecognition, ev: Event) => void) | null;\n onsoundstart: ((this: SpeechRecognition, ev: Event) => void) | null;\n onspeechend: ((this: SpeechRecognition, ev: Event) => void) | null;\n onspeechstart: ((this: SpeechRecognition, ev: Event) => void) | null;\n onstart: ((this: SpeechRecognition, ev: Event) => void) | null;\n abort(): void;\n start(audioTrack?: MediaStreamTrack): void;\n stop(): void;\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"signality-core-browser-speech-recognition.mjs","sources":["../../../projects/core/browser/speech-recognition/index.ts","../../../projects/core/browser/speech-recognition/signality-core-browser-speech-recognition.ts"],"sourcesContent":["import { isSignal, type Signal, signal, untracked } from '@angular/core';\nimport { constSignal, NOOP_FN, setupContext } from '@signality/core/internal';\nimport { toValue } from '@signality/core/utilities';\nimport type { MaybeSignal, WithInjector } from '@signality/core/types';\nimport { watcher } from '@signality/core/reactivity/watcher';\nimport { permissionState } from '@signality/core/browser/permission-state';\n\nexport interface SpeechRecognitionOptions extends WithInjector {\n /**\n * BCP 47 language tag for recognition (e.g. `'en-US'`).\n *\n * @default 'en-US'\n * @see [SpeechRecognition: lang on MDN](https://developer.mozilla.org/en-US/docs/Web/API/SpeechRecognition/lang)\n */\n readonly lang?: MaybeSignal<string>;\n\n /**\n * Whether to return interim (in-progress) results alongside final ones.\n *\n * @default false\n * @see [SpeechRecognition: interimResults on MDN](https://developer.mozilla.org/en-US/docs/Web/API/SpeechRecognition/interimResults)\n */\n readonly interimResults?: boolean;\n\n /**\n * Whether recognition continues after the user stops speaking.\n *\n * @default false\n * @see [SpeechRecognition: continuous on MDN](https://developer.mozilla.org/en-US/docs/Web/API/SpeechRecognition/continuous)\n */\n readonly continuous?: boolean;\n\n /**\n * Maximum number of alternative recognition results per utterance.\n *\n * @default 1\n * @see [SpeechRecognition: maxAlternatives on MDN](https://developer.mozilla.org/en-US/docs/Web/API/SpeechRecognition/maxAlternatives)\n */\n readonly maxAlternatives?: number;\n}\n\nexport interface SpeechRecognitionRef {\n /**\n * Whether the Speech Recognition API is supported in the current browser.\n *\n * @see [SpeechRecognition browser compatibility on MDN](https://developer.mozilla.org/en-US/docs/Web/API/SpeechRecognition#browser_compatibility)\n */\n readonly isSupported: Signal<boolean>;\n\n /**\n * Whether speech recognition is currently active and listening.\n */\n readonly isListening: Signal<boolean>;\n\n /**\n * Accumulated final transcript text.\n *\n * @see [SpeechRecognitionResult on MDN](https://developer.mozilla.org/en-US/docs/Web/API/SpeechRecognitionResult)\n */\n readonly text: Signal<string>;\n\n /**\n * In-progress interim transcript. Only populated when `interimResults` is `true`.\n *\n * @see [SpeechRecognition: interimResults on MDN](https://developer.mozilla.org/en-US/docs/Web/API/SpeechRecognition/interimResults)\n */\n readonly interimText: Signal<string>;\n\n /**\n * The last recognition error, or `null` if no error occurred.\n *\n * @see [SpeechRecognitionErrorEvent on MDN](https://developer.mozilla.org/en-US/docs/Web/API/SpeechRecognitionErrorEvent)\n */\n readonly error: Signal<SpeechRecognitionErrorEvent | Error | null>;\n\n /**\n * Start listening for speech input.\n *\n * @see [SpeechRecognition: start() on MDN](https://developer.mozilla.org/en-US/docs/Web/API/SpeechRecognition/start)\n */\n readonly start: () => void;\n\n /**\n * Stop listening and return any remaining results.\n *\n * @see [SpeechRecognition: stop() on MDN](https://developer.mozilla.org/en-US/docs/Web/API/SpeechRecognition/stop)\n */\n readonly stop: () => void;\n\n /**\n * Abort recognition immediately without returning results.\n *\n * @see [SpeechRecognition: abort() on MDN](https://developer.mozilla.org/en-US/docs/Web/API/SpeechRecognition/abort)\n */\n readonly abort: () => void;\n}\n\n/**\n * Signal-based wrapper around the [Speech Recognition API](https://developer.mozilla.org/en-US/docs/Web/API/SpeechRecognition).\n *\n * @param options - Optional configuration\n * @returns A SpeechRecognitionRef with isSupported, isListening, text, interimText, error signals and control methods\n *\n * @example\n * ```typescript\n * @Component({\n * template: `\n * @if (recognition.isSupported()) {\n * <button (click)=\"toggleRecognition()\">\n * {{ recognition.isListening() ? 'Stop' : 'Start' }} Recording\n * </button>\n * <p>{{ recognition.text() }}</p>\n * @if (recognition.interimText()) {\n * <p><em>{{ recognition.interimText() }}</em></p>\n * }\n * }\n * `\n * })\n * export class SpeechComponent {\n * readonly recognition = speechRecognition();\n *\n * toggleRecognition() {\n * if (this.recognition.isListening()) {\n * this.recognition.stop();\n * } else {\n * this.recognition.start();\n * }\n * }\n * }\n * ```\n */\nexport function speechRecognition(options?: SpeechRecognitionOptions): SpeechRecognitionRef {\n const { runInContext } = setupContext(options?.injector, speechRecognition);\n\n return runInContext(({ isBrowser, onCleanup }) => {\n const isSupported = constSignal(\n isBrowser && ('SpeechRecognition' in window || 'webkitSpeechRecognition' in window)\n );\n\n if (!isSupported()) {\n return {\n isSupported,\n isListening: constSignal(false),\n text: constSignal(''),\n interimText: constSignal(''),\n error: constSignal(null),\n start: NOOP_FN,\n stop: NOOP_FN,\n abort: NOOP_FN,\n };\n }\n\n const SpeechRecognitionClass =\n (window as any).SpeechRecognition || (window as any).webkitSpeechRecognition;\n\n const recognition: SpeechRecognition = new SpeechRecognitionClass();\n\n const {\n lang = 'en-US',\n interimResults = false,\n continuous = false,\n maxAlternatives = 1,\n } = options ?? {};\n\n recognition.lang = toValue(lang);\n recognition.continuous = continuous;\n recognition.interimResults = interimResults;\n recognition.maxAlternatives = maxAlternatives;\n\n const isListening = signal(false);\n const text = signal('');\n const interimText = signal('');\n const error = signal<SpeechRecognitionErrorEvent | Error | null>(null);\n\n const handleResult = (event: SpeechRecognitionEvent) => {\n let finalTranscript = '';\n let interimTranscript = '';\n\n for (let i = event.resultIndex; i < event.results.length; i++) {\n const result = event.results[i];\n const transcript = result[0]?.transcript || result.item(0)?.transcript || '';\n\n if (result.isFinal) {\n finalTranscript += transcript;\n } else {\n interimTranscript += transcript;\n }\n }\n\n if (finalTranscript) {\n text.update(t => (t ? t + ' ' : '') + finalTranscript);\n interimText.set('');\n } else if (interimTranscript) {\n interimText.set(interimTranscript);\n }\n };\n\n const handleError = (event: SpeechRecognitionErrorEvent) => {\n error.set(event);\n isListening.set(false);\n };\n\n const handleStart = () => {\n isListening.set(true);\n error.set(null);\n\n if (!continuous) {\n text.set('');\n interimText.set('');\n }\n };\n\n const handleEnd = () => {\n isListening.set(false);\n recognition.lang = toValue(lang);\n };\n\n recognition.onstart = handleStart;\n recognition.onend = handleEnd;\n recognition.onerror = handleError;\n recognition.onresult = handleResult;\n\n const start = () => {\n try {\n if (!untracked(isListening)) {\n recognition.start();\n }\n } catch (err) {\n error.set(err as Error);\n }\n };\n\n const stop = () => {\n if (untracked(isListening)) {\n recognition.stop();\n }\n };\n\n const abort = () => {\n if (untracked(isListening)) {\n recognition.abort();\n }\n };\n\n onCleanup(abort);\n\n if (isSignal(lang)) {\n watcher(lang, newLang => {\n if (!isListening()) {\n recognition.lang = newLang;\n }\n });\n }\n\n watcher(permissionState('microphone'), state => {\n if (state === 'denied') {\n abort();\n }\n });\n\n return {\n isSupported,\n isListening: isListening.asReadonly(),\n text: text.asReadonly(),\n interimText: interimText.asReadonly(),\n error: error.asReadonly(),\n start,\n stop,\n abort,\n };\n });\n}\n\n/**\n * Local type definitions for Web Speech API (Speech Recognition).\n *\n * @remarks\n * External `@types/dom-speech-recognition` package may conflict with user's other libraries\n * or become outdated. For better DX, we define minimal required interfaces locally\n * without polluting the global namespace with experimental APIs.\n */\ntype SpeechRecognitionErrorCode =\n | 'aborted'\n | 'audio-capture'\n | 'bad-grammar'\n | 'language-not-supported'\n | 'network'\n | 'no-speech'\n | 'not-allowed'\n | 'service-not-allowed';\n\ninterface SpeechRecognitionAlternative {\n readonly transcript: string;\n readonly confidence: number;\n}\n\ninterface SpeechRecognitionResult {\n readonly isFinal: boolean;\n readonly length: number;\n item(index: number): SpeechRecognitionAlternative;\n [index: number]: SpeechRecognitionAlternative;\n}\n\ninterface SpeechRecognitionResultList {\n readonly length: number;\n item(index: number): SpeechRecognitionResult;\n [index: number]: SpeechRecognitionResult;\n}\n\ninterface SpeechGrammar {\n src: string;\n weight: number;\n}\n\ninterface SpeechGrammarList {\n readonly length: number;\n addFromString(string: string, weight?: number): void;\n addFromURI(src: string, weight?: number): void;\n item(index: number): SpeechGrammar;\n [index: number]: SpeechGrammar;\n}\n\ninterface SpeechRecognitionEvent extends Event {\n readonly resultIndex: number;\n readonly results: SpeechRecognitionResultList;\n}\n\ninterface SpeechRecognitionErrorEvent extends Event {\n readonly error: SpeechRecognitionErrorCode;\n readonly message: string;\n}\n\ninterface SpeechRecognition extends EventTarget {\n continuous: boolean;\n grammars: SpeechGrammarList;\n interimResults: boolean;\n lang: string;\n maxAlternatives: number;\n onaudioend: ((this: SpeechRecognition, ev: Event) => void) | null;\n onaudiostart: ((this: SpeechRecognition, ev: Event) => void) | null;\n onend: ((this: SpeechRecognition, ev: Event) => void) | null;\n onerror: ((this: SpeechRecognition, ev: SpeechRecognitionErrorEvent) => void) | null;\n onnomatch: ((this: SpeechRecognition, ev: SpeechRecognitionEvent) => void) | null;\n onresult: ((this: SpeechRecognition, ev: SpeechRecognitionEvent) => void) | null;\n onsoundend: ((this: SpeechRecognition, ev: Event) => void) | null;\n onsoundstart: ((this: SpeechRecognition, ev: Event) => void) | null;\n onspeechend: ((this: SpeechRecognition, ev: Event) => void) | null;\n onspeechstart: ((this: SpeechRecognition, ev: Event) => void) | null;\n onstart: ((this: SpeechRecognition, ev: Event) => void) | null;\n abort(): void;\n start(audioTrack?: MediaStreamTrack): void;\n stop(): void;\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
@@ -1,5 +1,6 @@
1
1
  import { signal } from '@angular/core';
2
- import { setupContext, constSignal, NOOP_FN, toValue } from '@signality/core/internal';
2
+ import { setupContext, constSignal, NOOP_FN } from '@signality/core/internal';
3
+ import { toValue } from '@signality/core/utilities';
3
4
 
4
5
  /**
5
6
  * Signal-based wrapper around the [Speech Synthesis API](https://developer.mozilla.org/en-US/docs/Web/API/SpeechSynthesis).
@@ -1 +1 @@
1
- {"version":3,"file":"signality-core-browser-speech-synthesis.mjs","sources":["../../../projects/core/browser/speech-synthesis/index.ts","../../../projects/core/browser/speech-synthesis/signality-core-browser-speech-synthesis.ts"],"sourcesContent":["import { type Signal, signal } from '@angular/core';\nimport { constSignal, NOOP_FN, setupContext, toValue } from '@signality/core/internal';\nimport type { MaybeSignal, WithInjector } from '@signality/core/types';\n\nexport interface SpeechSynthesisOptions extends WithInjector {\n /**\n * BCP 47 language tag (e.g. `'en-US'`). Supports reactive values.\n *\n * @see [SpeechSynthesisUtterance: lang on MDN](https://developer.mozilla.org/en-US/docs/Web/API/SpeechSynthesisUtterance/lang)\n */\n readonly lang?: MaybeSignal<string>;\n\n /**\n * Speech rate from `0.1` to `10`. Supports reactive values.\n *\n * @default 1\n * @see [SpeechSynthesisUtterance: rate on MDN](https://developer.mozilla.org/en-US/docs/Web/API/SpeechSynthesisUtterance/rate)\n */\n readonly rate?: MaybeSignal<number>;\n\n /**\n * Speech pitch from `0` to `2`. Supports reactive values.\n *\n * @default 1\n * @see [SpeechSynthesisUtterance: pitch on MDN](https://developer.mozilla.org/en-US/docs/Web/API/SpeechSynthesisUtterance/pitch)\n */\n readonly pitch?: MaybeSignal<number>;\n\n /**\n * Speech volume from `0` to `1`. Supports reactive values.\n *\n * @default 1\n * @see [SpeechSynthesisUtterance: volume on MDN](https://developer.mozilla.org/en-US/docs/Web/API/SpeechSynthesisUtterance/volume)\n */\n readonly volume?: MaybeSignal<number>;\n\n /**\n * Voice to use for synthesis. Supports reactive values.\n *\n * @see [SpeechSynthesisUtterance: voice on MDN](https://developer.mozilla.org/en-US/docs/Web/API/SpeechSynthesisUtterance/voice)\n */\n readonly voice?: MaybeSignal<SpeechSynthesisVoice>;\n}\n\nexport interface SpeechSynthesisRef {\n /**\n * Whether the Speech Synthesis API is supported in the current browser.\n *\n * @see [SpeechSynthesis browser compatibility on MDN](https://developer.mozilla.org/en-US/docs/Web/API/SpeechSynthesis#browser_compatibility)\n */\n readonly isSupported: Signal<boolean>;\n\n /**\n * Whether speech is currently being synthesized.\n *\n * @see [SpeechSynthesis: speaking on MDN](https://developer.mozilla.org/en-US/docs/Web/API/SpeechSynthesis/speaking)\n */\n readonly isSpeaking: Signal<boolean>;\n\n /**\n * Whether speech synthesis is currently paused.\n *\n * @see [SpeechSynthesis: paused on MDN](https://developer.mozilla.org/en-US/docs/Web/API/SpeechSynthesis/paused)\n */\n readonly isPaused: Signal<boolean>;\n\n /**\n * List of available synthesis voices for the current device and browser.\n *\n * @see [SpeechSynthesis: getVoices() on MDN](https://developer.mozilla.org/en-US/docs/Web/API/SpeechSynthesis/getVoices)\n */\n readonly voices: Signal<SpeechSynthesisVoice[]>;\n\n /**\n * The text currently being spoken, or `''` if idle.\n */\n readonly currentText: Signal<string>;\n\n /**\n * Speak the given text, cancelling any ongoing utterance.\n * Reads current reactive option values (`lang`, `rate`, `pitch`, `volume`, `voice`) at call time.\n *\n * @see [SpeechSynthesis: speak() on MDN](https://developer.mozilla.org/en-US/docs/Web/API/SpeechSynthesis/speak)\n */\n readonly speak: (text: string) => void;\n\n /**\n * Cancel and stop the current utterance.\n *\n * @see [SpeechSynthesis: cancel() on MDN](https://developer.mozilla.org/en-US/docs/Web/API/SpeechSynthesis/cancel)\n */\n readonly stop: () => void;\n\n /**\n * Pause the current utterance.\n *\n * @see [SpeechSynthesis: pause() on MDN](https://developer.mozilla.org/en-US/docs/Web/API/SpeechSynthesis/pause)\n */\n readonly pause: () => void;\n\n /**\n * Resume a paused utterance.\n *\n * @see [SpeechSynthesis: resume() on MDN](https://developer.mozilla.org/en-US/docs/Web/API/SpeechSynthesis/resume)\n */\n readonly resume: () => void;\n}\n\n/**\n * Signal-based wrapper around the [Speech Synthesis API](https://developer.mozilla.org/en-US/docs/Web/API/SpeechSynthesis).\n *\n * @param options - Optional configuration\n * @returns A SpeechSynthesisRef with isSupported, isSpeaking, isPaused, voices signals and control methods\n *\n * @example\n * ```typescript\n * @Component({\n * template: `\n * @if (synthesis.isSupported()) {\n * <button (click)=\"synthesis.speak('Hello, world!')\" [disabled]=\"synthesis.isSpeaking()\">\n * {{ synthesis.isSpeaking() ? 'Speaking...' : 'Speak' }}\n * </button>\n * <button (click)=\"synthesis.stop()\">Stop</button>\n * }\n * `\n * })\n * export class TextToSpeechDemo {\n * readonly synthesis = speechSynthesis({ rate: 1.5 });\n * }\n * ```\n */\nexport function speechSynthesis(options?: SpeechSynthesisOptions): SpeechSynthesisRef {\n const { runInContext } = setupContext(options?.injector, speechSynthesis);\n\n return runInContext(({ onCleanup, isBrowser }) => {\n const isSupported = constSignal(\n isBrowser && 'speechSynthesis' in window && typeof window.speechSynthesis !== 'undefined'\n );\n\n if (!isSupported()) {\n return {\n isSupported,\n isSpeaking: constSignal(false),\n isPaused: constSignal(false),\n voices: constSignal([]),\n currentText: constSignal(''),\n speak: NOOP_FN,\n stop: NOOP_FN,\n pause: NOOP_FN,\n resume: NOOP_FN,\n };\n }\n\n const { speechSynthesis } = window;\n\n const isSpeaking = signal(false);\n const isPaused = signal(false);\n const voices = signal<SpeechSynthesisVoice[]>(speechSynthesis.getVoices());\n const currentText = signal('');\n\n const loadVoices = () => {\n const availableVoices = speechSynthesis.getVoices();\n voices.set(availableVoices);\n };\n\n const updateSpeakingState = () => {\n isSpeaking.set(speechSynthesis.speaking);\n isPaused.set(speechSynthesis.paused);\n };\n\n const handleStart = () => {\n isSpeaking.set(true);\n isPaused.set(false);\n };\n\n const handleEnd = () => {\n isSpeaking.set(false);\n isPaused.set(false);\n currentText.set('');\n };\n\n const handleError = () => {\n isSpeaking.set(false);\n isPaused.set(false);\n currentText.set('');\n };\n\n const handlePause = () => {\n isPaused.set(true);\n };\n\n const handleResume = () => {\n isPaused.set(false);\n };\n\n const speak = (text: string) => {\n if (!text) {\n return;\n }\n\n speechSynthesis.cancel();\n\n const utterance = new SpeechSynthesisUtterance(text);\n\n const lang = toValue.untracked(options?.lang);\n const voice = toValue.untracked(options?.voice);\n\n if (lang) {\n utterance.lang = lang;\n }\n\n if (voice) {\n utterance.voice = voice;\n }\n\n utterance.rate = toValue.untracked(options?.rate) ?? 1;\n utterance.pitch = toValue.untracked(options?.pitch) ?? 1;\n utterance.volume = toValue.untracked(options?.volume) ?? 1;\n\n utterance.onstart = handleStart;\n utterance.onend = handleEnd;\n utterance.onerror = handleError;\n utterance.onpause = handlePause;\n utterance.onresume = handleResume;\n\n currentText.set(text);\n speechSynthesis.speak(utterance);\n\n updateSpeakingState();\n };\n\n const stop = () => {\n speechSynthesis.cancel();\n isSpeaking.set(false);\n isPaused.set(false);\n currentText.set('');\n };\n\n const pause = () => {\n if (speechSynthesis.speaking && !speechSynthesis.paused) {\n speechSynthesis.pause();\n isPaused.set(true);\n }\n };\n\n const resume = () => {\n if (speechSynthesis.paused) {\n speechSynthesis.resume();\n isPaused.set(false);\n }\n };\n\n onCleanup(stop);\n\n if (speechSynthesis.onvoiceschanged !== undefined) {\n speechSynthesis.onvoiceschanged = loadVoices;\n }\n\n return {\n isSupported,\n isSpeaking: isSpeaking.asReadonly(),\n isPaused: isPaused.asReadonly(),\n voices: voices.asReadonly(),\n currentText: currentText.asReadonly(),\n speak,\n stop,\n pause,\n resume,\n };\n });\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;AA4GA;;;;;;;;;;;;;;;;;;;;;;AAsBG;AACG,SAAU,eAAe,CAAC,OAAgC,EAAA;AAC9D,IAAA,MAAM,EAAE,YAAY,EAAE,GAAG,YAAY,CAAC,OAAO,EAAE,QAAQ,EAAE,eAAe,CAAC;IAEzE,OAAO,YAAY,CAAC,CAAC,EAAE,SAAS,EAAE,SAAS,EAAE,KAAI;AAC/C,QAAA,MAAM,WAAW,GAAG,WAAW,CAC7B,SAAS,IAAI,iBAAiB,IAAI,MAAM,IAAI,OAAO,MAAM,CAAC,eAAe,KAAK,WAAW,CAC1F;AAED,QAAA,IAAI,CAAC,WAAW,EAAE,EAAE;YAClB,OAAO;gBACL,WAAW;AACX,gBAAA,UAAU,EAAE,WAAW,CAAC,KAAK,CAAC;AAC9B,gBAAA,QAAQ,EAAE,WAAW,CAAC,KAAK,CAAC;AAC5B,gBAAA,MAAM,EAAE,WAAW,CAAC,EAAE,CAAC;AACvB,gBAAA,WAAW,EAAE,WAAW,CAAC,EAAE,CAAC;AAC5B,gBAAA,KAAK,EAAE,OAAO;AACd,gBAAA,IAAI,EAAE,OAAO;AACb,gBAAA,KAAK,EAAE,OAAO;AACd,gBAAA,MAAM,EAAE,OAAO;aAChB;QACH;AAEA,QAAA,MAAM,EAAE,eAAe,EAAE,GAAG,MAAM;AAElC,QAAA,MAAM,UAAU,GAAG,MAAM,CAAC,KAAK,sDAAC;AAChC,QAAA,MAAM,QAAQ,GAAG,MAAM,CAAC,KAAK,oDAAC;QAC9B,MAAM,MAAM,GAAG,MAAM,CAAyB,eAAe,CAAC,SAAS,EAAE,EAAA,IAAA,SAAA,GAAA,CAAA,EAAA,SAAA,EAAA,QAAA,EAAA,CAAA,GAAA,EAAA,CAAA,CAAC;AAC1E,QAAA,MAAM,WAAW,GAAG,MAAM,CAAC,EAAE,uDAAC;QAE9B,MAAM,UAAU,GAAG,MAAK;AACtB,YAAA,MAAM,eAAe,GAAG,eAAe,CAAC,SAAS,EAAE;AACnD,YAAA,MAAM,CAAC,GAAG,CAAC,eAAe,CAAC;AAC7B,QAAA,CAAC;QAED,MAAM,mBAAmB,GAAG,MAAK;AAC/B,YAAA,UAAU,CAAC,GAAG,CAAC,eAAe,CAAC,QAAQ,CAAC;AACxC,YAAA,QAAQ,CAAC,GAAG,CAAC,eAAe,CAAC,MAAM,CAAC;AACtC,QAAA,CAAC;QAED,MAAM,WAAW,GAAG,MAAK;AACvB,YAAA,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC;AACpB,YAAA,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC;AACrB,QAAA,CAAC;QAED,MAAM,SAAS,GAAG,MAAK;AACrB,YAAA,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC;AACrB,YAAA,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC;AACnB,YAAA,WAAW,CAAC,GAAG,CAAC,EAAE,CAAC;AACrB,QAAA,CAAC;QAED,MAAM,WAAW,GAAG,MAAK;AACvB,YAAA,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC;AACrB,YAAA,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC;AACnB,YAAA,WAAW,CAAC,GAAG,CAAC,EAAE,CAAC;AACrB,QAAA,CAAC;QAED,MAAM,WAAW,GAAG,MAAK;AACvB,YAAA,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC;AACpB,QAAA,CAAC;QAED,MAAM,YAAY,GAAG,MAAK;AACxB,YAAA,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC;AACrB,QAAA,CAAC;AAED,QAAA,MAAM,KAAK,GAAG,CAAC,IAAY,KAAI;YAC7B,IAAI,CAAC,IAAI,EAAE;gBACT;YACF;YAEA,eAAe,CAAC,MAAM,EAAE;AAExB,YAAA,MAAM,SAAS,GAAG,IAAI,wBAAwB,CAAC,IAAI,CAAC;YAEpD,MAAM,IAAI,GAAG,OAAO,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,CAAC;YAC7C,MAAM,KAAK,GAAG,OAAO,CAAC,SAAS,CAAC,OAAO,EAAE,KAAK,CAAC;YAE/C,IAAI,IAAI,EAAE;AACR,gBAAA,SAAS,CAAC,IAAI,GAAG,IAAI;YACvB;YAEA,IAAI,KAAK,EAAE;AACT,gBAAA,SAAS,CAAC,KAAK,GAAG,KAAK;YACzB;AAEA,YAAA,SAAS,CAAC,IAAI,GAAG,OAAO,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,CAAC,IAAI,CAAC;AACtD,YAAA,SAAS,CAAC,KAAK,GAAG,OAAO,CAAC,SAAS,CAAC,OAAO,EAAE,KAAK,CAAC,IAAI,CAAC;AACxD,YAAA,SAAS,CAAC,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC,OAAO,EAAE,MAAM,CAAC,IAAI,CAAC;AAE1D,YAAA,SAAS,CAAC,OAAO,GAAG,WAAW;AAC/B,YAAA,SAAS,CAAC,KAAK,GAAG,SAAS;AAC3B,YAAA,SAAS,CAAC,OAAO,GAAG,WAAW;AAC/B,YAAA,SAAS,CAAC,OAAO,GAAG,WAAW;AAC/B,YAAA,SAAS,CAAC,QAAQ,GAAG,YAAY;AAEjC,YAAA,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC;AACrB,YAAA,eAAe,CAAC,KAAK,CAAC,SAAS,CAAC;AAEhC,YAAA,mBAAmB,EAAE;AACvB,QAAA,CAAC;QAED,MAAM,IAAI,GAAG,MAAK;YAChB,eAAe,CAAC,MAAM,EAAE;AACxB,YAAA,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC;AACrB,YAAA,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC;AACnB,YAAA,WAAW,CAAC,GAAG,CAAC,EAAE,CAAC;AACrB,QAAA,CAAC;QAED,MAAM,KAAK,GAAG,MAAK;YACjB,IAAI,eAAe,CAAC,QAAQ,IAAI,CAAC,eAAe,CAAC,MAAM,EAAE;gBACvD,eAAe,CAAC,KAAK,EAAE;AACvB,gBAAA,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC;YACpB;AACF,QAAA,CAAC;QAED,MAAM,MAAM,GAAG,MAAK;AAClB,YAAA,IAAI,eAAe,CAAC,MAAM,EAAE;gBAC1B,eAAe,CAAC,MAAM,EAAE;AACxB,gBAAA,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC;YACrB;AACF,QAAA,CAAC;QAED,SAAS,CAAC,IAAI,CAAC;AAEf,QAAA,IAAI,eAAe,CAAC,eAAe,KAAK,SAAS,EAAE;AACjD,YAAA,eAAe,CAAC,eAAe,GAAG,UAAU;QAC9C;QAEA,OAAO;YACL,WAAW;AACX,YAAA,UAAU,EAAE,UAAU,CAAC,UAAU,EAAE;AACnC,YAAA,QAAQ,EAAE,QAAQ,CAAC,UAAU,EAAE;AAC/B,YAAA,MAAM,EAAE,MAAM,CAAC,UAAU,EAAE;AAC3B,YAAA,WAAW,EAAE,WAAW,CAAC,UAAU,EAAE;YACrC,KAAK;YACL,IAAI;YACJ,KAAK;YACL,MAAM;SACP;AACH,IAAA,CAAC,CAAC;AACJ;;AC9QA;;AAEG;;;;"}
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":";;;;AA6GA;;;;;;;;;;;;;;;;;;;;;;AAsBG;AACG,SAAU,eAAe,CAAC,OAAgC,EAAA;AAC9D,IAAA,MAAM,EAAE,YAAY,EAAE,GAAG,YAAY,CAAC,OAAO,EAAE,QAAQ,EAAE,eAAe,CAAC;IAEzE,OAAO,YAAY,CAAC,CAAC,EAAE,SAAS,EAAE,SAAS,EAAE,KAAI;AAC/C,QAAA,MAAM,WAAW,GAAG,WAAW,CAC7B,SAAS,IAAI,iBAAiB,IAAI,MAAM,IAAI,OAAO,MAAM,CAAC,eAAe,KAAK,WAAW,CAC1F;AAED,QAAA,IAAI,CAAC,WAAW,EAAE,EAAE;YAClB,OAAO;gBACL,WAAW;AACX,gBAAA,UAAU,EAAE,WAAW,CAAC,KAAK,CAAC;AAC9B,gBAAA,QAAQ,EAAE,WAAW,CAAC,KAAK,CAAC;AAC5B,gBAAA,MAAM,EAAE,WAAW,CAAC,EAAE,CAAC;AACvB,gBAAA,WAAW,EAAE,WAAW,CAAC,EAAE,CAAC;AAC5B,gBAAA,KAAK,EAAE,OAAO;AACd,gBAAA,IAAI,EAAE,OAAO;AACb,gBAAA,KAAK,EAAE,OAAO;AACd,gBAAA,MAAM,EAAE,OAAO;aAChB;QACH;AAEA,QAAA,MAAM,EAAE,eAAe,EAAE,GAAG,MAAM;AAElC,QAAA,MAAM,UAAU,GAAG,MAAM,CAAC,KAAK,sDAAC;AAChC,QAAA,MAAM,QAAQ,GAAG,MAAM,CAAC,KAAK,oDAAC;QAC9B,MAAM,MAAM,GAAG,MAAM,CAAyB,eAAe,CAAC,SAAS,EAAE,EAAA,IAAA,SAAA,GAAA,CAAA,EAAA,SAAA,EAAA,QAAA,EAAA,CAAA,GAAA,EAAA,CAAA,CAAC;AAC1E,QAAA,MAAM,WAAW,GAAG,MAAM,CAAC,EAAE,uDAAC;QAE9B,MAAM,UAAU,GAAG,MAAK;AACtB,YAAA,MAAM,eAAe,GAAG,eAAe,CAAC,SAAS,EAAE;AACnD,YAAA,MAAM,CAAC,GAAG,CAAC,eAAe,CAAC;AAC7B,QAAA,CAAC;QAED,MAAM,mBAAmB,GAAG,MAAK;AAC/B,YAAA,UAAU,CAAC,GAAG,CAAC,eAAe,CAAC,QAAQ,CAAC;AACxC,YAAA,QAAQ,CAAC,GAAG,CAAC,eAAe,CAAC,MAAM,CAAC;AACtC,QAAA,CAAC;QAED,MAAM,WAAW,GAAG,MAAK;AACvB,YAAA,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC;AACpB,YAAA,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC;AACrB,QAAA,CAAC;QAED,MAAM,SAAS,GAAG,MAAK;AACrB,YAAA,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC;AACrB,YAAA,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC;AACnB,YAAA,WAAW,CAAC,GAAG,CAAC,EAAE,CAAC;AACrB,QAAA,CAAC;QAED,MAAM,WAAW,GAAG,MAAK;AACvB,YAAA,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC;AACrB,YAAA,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC;AACnB,YAAA,WAAW,CAAC,GAAG,CAAC,EAAE,CAAC;AACrB,QAAA,CAAC;QAED,MAAM,WAAW,GAAG,MAAK;AACvB,YAAA,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC;AACpB,QAAA,CAAC;QAED,MAAM,YAAY,GAAG,MAAK;AACxB,YAAA,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC;AACrB,QAAA,CAAC;AAED,QAAA,MAAM,KAAK,GAAG,CAAC,IAAY,KAAI;YAC7B,IAAI,CAAC,IAAI,EAAE;gBACT;YACF;YAEA,eAAe,CAAC,MAAM,EAAE;AAExB,YAAA,MAAM,SAAS,GAAG,IAAI,wBAAwB,CAAC,IAAI,CAAC;YAEpD,MAAM,IAAI,GAAG,OAAO,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,CAAC;YAC7C,MAAM,KAAK,GAAG,OAAO,CAAC,SAAS,CAAC,OAAO,EAAE,KAAK,CAAC;YAE/C,IAAI,IAAI,EAAE;AACR,gBAAA,SAAS,CAAC,IAAI,GAAG,IAAI;YACvB;YAEA,IAAI,KAAK,EAAE;AACT,gBAAA,SAAS,CAAC,KAAK,GAAG,KAAK;YACzB;AAEA,YAAA,SAAS,CAAC,IAAI,GAAG,OAAO,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,CAAC,IAAI,CAAC;AACtD,YAAA,SAAS,CAAC,KAAK,GAAG,OAAO,CAAC,SAAS,CAAC,OAAO,EAAE,KAAK,CAAC,IAAI,CAAC;AACxD,YAAA,SAAS,CAAC,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC,OAAO,EAAE,MAAM,CAAC,IAAI,CAAC;AAE1D,YAAA,SAAS,CAAC,OAAO,GAAG,WAAW;AAC/B,YAAA,SAAS,CAAC,KAAK,GAAG,SAAS;AAC3B,YAAA,SAAS,CAAC,OAAO,GAAG,WAAW;AAC/B,YAAA,SAAS,CAAC,OAAO,GAAG,WAAW;AAC/B,YAAA,SAAS,CAAC,QAAQ,GAAG,YAAY;AAEjC,YAAA,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC;AACrB,YAAA,eAAe,CAAC,KAAK,CAAC,SAAS,CAAC;AAEhC,YAAA,mBAAmB,EAAE;AACvB,QAAA,CAAC;QAED,MAAM,IAAI,GAAG,MAAK;YAChB,eAAe,CAAC,MAAM,EAAE;AACxB,YAAA,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC;AACrB,YAAA,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC;AACnB,YAAA,WAAW,CAAC,GAAG,CAAC,EAAE,CAAC;AACrB,QAAA,CAAC;QAED,MAAM,KAAK,GAAG,MAAK;YACjB,IAAI,eAAe,CAAC,QAAQ,IAAI,CAAC,eAAe,CAAC,MAAM,EAAE;gBACvD,eAAe,CAAC,KAAK,EAAE;AACvB,gBAAA,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC;YACpB;AACF,QAAA,CAAC;QAED,MAAM,MAAM,GAAG,MAAK;AAClB,YAAA,IAAI,eAAe,CAAC,MAAM,EAAE;gBAC1B,eAAe,CAAC,MAAM,EAAE;AACxB,gBAAA,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC;YACrB;AACF,QAAA,CAAC;QAED,SAAS,CAAC,IAAI,CAAC;AAEf,QAAA,IAAI,eAAe,CAAC,eAAe,KAAK,SAAS,EAAE;AACjD,YAAA,eAAe,CAAC,eAAe,GAAG,UAAU;QAC9C;QAEA,OAAO;YACL,WAAW;AACX,YAAA,UAAU,EAAE,UAAU,CAAC,UAAU,EAAE;AACnC,YAAA,QAAQ,EAAE,QAAQ,CAAC,UAAU,EAAE;AAC/B,YAAA,MAAM,EAAE,MAAM,CAAC,UAAU,EAAE;AAC3B,YAAA,WAAW,EAAE,WAAW,CAAC,UAAU,EAAE;YACrC,KAAK;YACL,IAAI;YACJ,KAAK;YACL,MAAM;SACP;AACH,IAAA,CAAC,CAAC;AACJ;;AC/QA;;AAEG;;;;"}
@@ -1,5 +1,6 @@
1
1
  import { signal, isSignal } from '@angular/core';
2
- import { setupContext, isPlainObject, toValue, proxySignal } from '@signality/core/internal';
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
- if (isServer) {
44
+ const type = options?.type ?? 'local';
45
+ if (isServer || !storageAvailable(type)) {
44
46
  return signal(initialValue, options);
45
47
  }
46
- const storageType = options?.type ?? 'local';
48
+ const targetStorage = type === 'local' ? window.localStorage : window.sessionStorage;
47
49
  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) => {
50
+ const processValue = (storedValue) => {
59
51
  if (options?.mergeResolver) {
60
52
  return options.mergeResolver(storedValue, initialValue);
61
53
  }
@@ -65,71 +57,49 @@ 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
- 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);
60
+ const raw = targetStorage.getItem(storageKey);
61
+ if (raw === null) {
62
+ if (initialValue != null) {
63
+ writeValue(initialValue);
86
64
  }
87
65
  return initialValue;
88
66
  }
67
+ return processValue(serializer.read(raw));
68
+ };
69
+ const dispatchStorageEvent = (key, oldValue, newValue) => {
70
+ window.dispatchEvent(new StorageEvent('storage', {
71
+ key,
72
+ oldValue,
73
+ newValue,
74
+ storageArea: targetStorage,
75
+ url: window.location.href,
76
+ }));
89
77
  };
90
78
  const writeValue = (value) => {
91
- const storage = getStorage();
92
79
  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
- }
80
+ const oldValue = targetStorage.getItem(storageKey);
81
+ if (value == null) {
82
+ targetStorage.removeItem(storageKey);
83
+ dispatchStorageEvent(storageKey, oldValue, null);
104
84
  }
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);
85
+ else {
86
+ const serialized = serializer.write(value);
87
+ if (oldValue !== serialized) {
88
+ targetStorage.setItem(storageKey, serialized);
89
+ dispatchStorageEvent(storageKey, oldValue, serialized);
109
90
  }
110
91
  }
111
92
  };
112
93
  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
- });
94
+ setupSync(() => {
95
+ listener(window, 'storage', e => {
96
+ const currKey = toValue(key);
97
+ if (e.key === currKey && e.storageArea === targetStorage) {
98
+ const newValue = e.newValue === null ? initialValue : processValue(serializer.read(e.newValue));
99
+ state.set(newValue);
100
+ }
131
101
  });
132
- }
102
+ });
133
103
  if (isSignal(key)) {
134
104
  watcher(key, newKey => state.set(readValue(newKey)));
135
105
  }
@@ -239,7 +209,7 @@ function inferSerializerType(value) {
239
209
  function storageAvailable(type) {
240
210
  let storage;
241
211
  try {
242
- storage = window[type];
212
+ storage = window[`${type}Storage`];
243
213
  const testKey = '__storage_test__';
244
214
  storage.setItem(testKey, testKey);
245
215
  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(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 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 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 };\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 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 };\n\n const state = signal<T>(readValue(toValue(key)), options);\n\n setupSync(() => {\n listener(window, 'storage', e => {\n const currKey = toValue(key);\n\n if (e.key === currKey && e.storageArea === targetStorage) {\n const newValue =\n e.newValue === null ? initialValue : processValue(serializer.read(e.newValue));\n\n state.set(newValue);\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, toElement, proxySignal, createToken } from '@signality/core/internal';
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, toElement } from '@signality/core/internal';\nimport type { MaybeElementSignal, WithInjector } from '@signality/core/types';\nimport { mutationObserver } from '@signality/core/observers/mutation-observer';\n\n/**\n * Possible text direction values matching the HTML `dir` attribute.\n */\nexport type TextDirection = 'ltr' | 'rtl' | 'auto';\n\nexport interface TextDirectionOptions extends CreateSignalOptions<TextDirection>, WithInjector {\n /**\n * Element to observe. Defaults to `document.documentElement` (`<html>`).\n */\n readonly target?: MaybeElementSignal<HTMLElement>;\n\n /**\n * Initial value for SSR.\n * @default 'ltr'\n */\n readonly initialValue?: TextDirection;\n}\n\n/**\n * Reactive read/write wrapper around an element's `dir` attribute for detecting and controlling\n * [text directionality](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/dir).\n *\n * @param options - Optional configuration including target element, initial value and injector\n * @returns A writable signal of the current text direction\n *\n * @example\n * ```typescript\n * @Component({\n * template: `\n * <p>Current direction: {{ dir() }}</p>\n * <button (click)=\"dir.set('rtl')\">Set RTL</button>\n * <button (click)=\"dir.set('ltr')\">Set LTR</button>\n * `\n * })\n * export class TextDirectionDemo {\n * readonly dir = textDirection();\n * }\n * ```\n */\nexport function textDirection(options?: TextDirectionOptions): WritableSignal<TextDirection> {\n const { runInContext } = setupContext(options?.injector, textDirection);\n const initialValue = options?.initialValue ?? 'ltr';\n\n return runInContext(({ isServer }) => {\n if (isServer) {\n return signal(initialValue, options);\n }\n\n const target = options?.target ?? document.documentElement;\n\n const readDir = (): TextDirection => {\n const el = toElement(target);\n return (el?.getAttribute('dir') as TextDirection) || initialValue;\n };\n\n const dir = signal<TextDirection>(readDir());\n\n mutationObserver(target, () => dir.set(readDir()), {\n attributes: true,\n attributeFilter: ['dir'],\n });\n\n return proxySignal(dir, {\n set: value => {\n const el = toElement(target);\n el?.setAttribute('dir', value);\n dir.set(value);\n },\n });\n });\n}\n\nexport const TEXT_DIRECTION = /* @__PURE__ */ createToken(textDirection);\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;;AAuBA;;;;;;;;;;;;;;;;;;;;AAoBG;AACG,SAAU,aAAa,CAAC,OAA8B,EAAA;AAC1D,IAAA,MAAM,EAAE,YAAY,EAAE,GAAG,YAAY,CAAC,OAAO,EAAE,QAAQ,EAAE,aAAa,CAAC;AACvE,IAAA,MAAM,YAAY,GAAG,OAAO,EAAE,YAAY,IAAI,KAAK;AAEnD,IAAA,OAAO,YAAY,CAAC,CAAC,EAAE,QAAQ,EAAE,KAAI;QACnC,IAAI,QAAQ,EAAE;AACZ,YAAA,OAAO,MAAM,CAAC,YAAY,EAAE,OAAO,CAAC;QACtC;QAEA,MAAM,MAAM,GAAG,OAAO,EAAE,MAAM,IAAI,QAAQ,CAAC,eAAe;QAE1D,MAAM,OAAO,GAAG,MAAoB;AAClC,YAAA,MAAM,EAAE,GAAG,SAAS,CAAC,MAAM,CAAC;YAC5B,OAAQ,EAAE,EAAE,YAAY,CAAC,KAAK,CAAmB,IAAI,YAAY;AACnE,QAAA,CAAC;AAED,QAAA,MAAM,GAAG,GAAG,MAAM,CAAgB,OAAO,EAAE,+CAAC;AAE5C,QAAA,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC,EAAE;AACjD,YAAA,UAAU,EAAE,IAAI;YAChB,eAAe,EAAE,CAAC,KAAK,CAAC;AACzB,SAAA,CAAC;QAEF,OAAO,WAAW,CAAC,GAAG,EAAE;YACtB,GAAG,EAAE,KAAK,IAAG;AACX,gBAAA,MAAM,EAAE,GAAG,SAAS,CAAC,MAAM,CAAC;AAC5B,gBAAA,EAAE,EAAE,YAAY,CAAC,KAAK,EAAE,KAAK,CAAC;AAC9B,gBAAA,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC;YAChB,CAAC;AACF,SAAA,CAAC;AACJ,IAAA,CAAC,CAAC;AACJ;AAEO,MAAM,cAAc,mBAAmB,WAAW,CAAC,aAAa;;AC7EvE;;AAEG;;;;"}
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;;;;"}