@signality/core 0.0.1-alpha.2 → 0.0.1-alpha.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (219) hide show
  1. package/browser/battery/index.d.ts +27 -2
  2. package/browser/bluetooth/index.d.ts +40 -13
  3. package/browser/breakpoints/index.d.ts +22 -9
  4. package/browser/broadcast-channel/index.d.ts +1 -1
  5. package/browser/browser-language/index.d.ts +1 -1
  6. package/browser/clipboard/index.d.ts +22 -6
  7. package/browser/device-posture/index.d.ts +23 -2
  8. package/browser/display-media/index.d.ts +34 -22
  9. package/browser/eye-dropper/index.d.ts +22 -6
  10. package/browser/favicon/index.d.ts +29 -2
  11. package/browser/file-dialog/index.d.ts +97 -0
  12. package/browser/fps/index.d.ts +1 -1
  13. package/browser/fullscreen/index.d.ts +78 -0
  14. package/browser/gamepad/index.d.ts +39 -9
  15. package/browser/geolocation/index.d.ts +44 -13
  16. package/browser/index.d.ts +8 -1
  17. package/browser/input-modality/index.d.ts +1 -1
  18. package/browser/listener/index.d.ts +1 -1
  19. package/browser/media-query/index.d.ts +1 -1
  20. package/browser/network/index.d.ts +37 -9
  21. package/browser/online/index.d.ts +1 -1
  22. package/browser/page-visibility/index.d.ts +1 -1
  23. package/browser/permission-state/index.d.ts +23 -0
  24. package/browser/picture-in-picture/index.d.ts +24 -6
  25. package/browser/screen-orientation/index.d.ts +1 -1
  26. package/browser/speech-recognition/index.d.ts +51 -13
  27. package/browser/speech-synthesis/index.d.ts +82 -42
  28. package/browser/storage/index.d.ts +1 -1
  29. package/browser/text-direction/index.d.ts +2 -5
  30. package/{elements → browser}/text-selection/index.d.ts +1 -1
  31. package/browser/vibration/index.d.ts +38 -9
  32. package/browser/wake-lock/index.d.ts +51 -12
  33. package/browser/web-notification/index.d.ts +35 -9
  34. package/browser/web-share/index.d.ts +19 -5
  35. package/browser/web-worker/index.d.ts +35 -11
  36. package/browser/window-focus/index.d.ts +27 -0
  37. package/{elements → browser}/window-size/index.d.ts +6 -7
  38. package/elements/active-element/index.d.ts +1 -1
  39. package/elements/dropzone/index.d.ts +60 -10
  40. package/elements/element-focus/index.d.ts +1 -1
  41. package/elements/element-focus-within/index.d.ts +1 -1
  42. package/elements/element-hover/index.d.ts +1 -1
  43. package/elements/element-size/index.d.ts +8 -5
  44. package/elements/element-visibility/index.d.ts +25 -7
  45. package/elements/index.d.ts +0 -2
  46. package/elements/mouse-position/index.d.ts +27 -7
  47. package/elements/on-click-outside/index.d.ts +1 -1
  48. package/elements/on-disconnect/index.d.ts +1 -1
  49. package/elements/on-long-press/index.d.ts +1 -1
  50. package/elements/pointer-swipe/index.d.ts +1 -1
  51. package/elements/scroll-position/index.d.ts +2 -2
  52. package/elements/swipe/index.d.ts +1 -1
  53. package/fesm2022/signality-core-browser-battery.mjs +1 -1
  54. package/fesm2022/signality-core-browser-battery.mjs.map +1 -1
  55. package/fesm2022/signality-core-browser-bluetooth.mjs +28 -27
  56. package/fesm2022/signality-core-browser-bluetooth.mjs.map +1 -1
  57. package/fesm2022/signality-core-browser-breakpoints.mjs +19 -10
  58. package/fesm2022/signality-core-browser-breakpoints.mjs.map +1 -1
  59. package/fesm2022/signality-core-browser-broadcast-channel.mjs +1 -1
  60. package/fesm2022/signality-core-browser-broadcast-channel.mjs.map +1 -1
  61. package/fesm2022/signality-core-browser-browser-language.mjs +1 -1
  62. package/fesm2022/signality-core-browser-browser-language.mjs.map +1 -1
  63. package/fesm2022/signality-core-browser-clipboard.mjs +1 -1
  64. package/fesm2022/signality-core-browser-clipboard.mjs.map +1 -1
  65. package/fesm2022/signality-core-browser-device-posture.mjs +13 -0
  66. package/fesm2022/signality-core-browser-device-posture.mjs.map +1 -1
  67. package/fesm2022/signality-core-browser-display-media.mjs +4 -17
  68. package/fesm2022/signality-core-browser-display-media.mjs.map +1 -1
  69. package/fesm2022/signality-core-browser-eye-dropper.mjs +1 -1
  70. package/fesm2022/signality-core-browser-eye-dropper.mjs.map +1 -1
  71. package/fesm2022/signality-core-browser-favicon.mjs +2 -2
  72. package/fesm2022/signality-core-browser-favicon.mjs.map +1 -1
  73. package/fesm2022/signality-core-browser-file-dialog.mjs +109 -0
  74. package/fesm2022/signality-core-browser-file-dialog.mjs.map +1 -0
  75. package/fesm2022/signality-core-browser-fps.mjs +1 -1
  76. package/fesm2022/signality-core-browser-fps.mjs.map +1 -1
  77. package/fesm2022/signality-core-browser-fullscreen.mjs +113 -0
  78. package/fesm2022/signality-core-browser-fullscreen.mjs.map +1 -0
  79. package/fesm2022/signality-core-browser-gamepad.mjs +14 -4
  80. package/fesm2022/signality-core-browser-gamepad.mjs.map +1 -1
  81. package/fesm2022/signality-core-browser-geolocation.mjs +8 -19
  82. package/fesm2022/signality-core-browser-geolocation.mjs.map +1 -1
  83. package/fesm2022/signality-core-browser-input-modality.mjs +1 -1
  84. package/fesm2022/signality-core-browser-input-modality.mjs.map +1 -1
  85. package/fesm2022/signality-core-browser-listener.mjs +18 -6
  86. package/fesm2022/signality-core-browser-listener.mjs.map +1 -1
  87. package/fesm2022/signality-core-browser-media-query.mjs +1 -1
  88. package/fesm2022/signality-core-browser-media-query.mjs.map +1 -1
  89. package/fesm2022/signality-core-browser-network.mjs +2 -2
  90. package/fesm2022/signality-core-browser-network.mjs.map +1 -1
  91. package/fesm2022/signality-core-browser-online.mjs +1 -1
  92. package/fesm2022/signality-core-browser-online.mjs.map +1 -1
  93. package/fesm2022/signality-core-browser-page-visibility.mjs +1 -1
  94. package/fesm2022/signality-core-browser-page-visibility.mjs.map +1 -1
  95. package/fesm2022/signality-core-browser-permission-state.mjs +57 -0
  96. package/fesm2022/signality-core-browser-permission-state.mjs.map +1 -0
  97. package/fesm2022/signality-core-browser-picture-in-picture.mjs +30 -13
  98. package/fesm2022/signality-core-browser-picture-in-picture.mjs.map +1 -1
  99. package/fesm2022/signality-core-browser-screen-orientation.mjs +1 -1
  100. package/fesm2022/signality-core-browser-screen-orientation.mjs.map +1 -1
  101. package/fesm2022/signality-core-browser-speech-recognition.mjs +7 -19
  102. package/fesm2022/signality-core-browser-speech-recognition.mjs.map +1 -1
  103. package/fesm2022/signality-core-browser-speech-synthesis.mjs +14 -16
  104. package/fesm2022/signality-core-browser-speech-synthesis.mjs.map +1 -1
  105. package/fesm2022/signality-core-browser-storage.mjs +1 -1
  106. package/fesm2022/signality-core-browser-storage.mjs.map +1 -1
  107. package/fesm2022/signality-core-browser-text-direction.mjs +1 -4
  108. package/fesm2022/signality-core-browser-text-direction.mjs.map +1 -1
  109. package/fesm2022/{signality-core-elements-text-selection.mjs → signality-core-browser-text-selection.mjs} +2 -2
  110. package/fesm2022/signality-core-browser-text-selection.mjs.map +1 -0
  111. package/fesm2022/signality-core-browser-vibration.mjs +14 -5
  112. package/fesm2022/signality-core-browser-vibration.mjs.map +1 -1
  113. package/fesm2022/signality-core-browser-wake-lock.mjs +33 -16
  114. package/fesm2022/signality-core-browser-wake-lock.mjs.map +1 -1
  115. package/fesm2022/signality-core-browser-web-notification.mjs +5 -7
  116. package/fesm2022/signality-core-browser-web-notification.mjs.map +1 -1
  117. package/fesm2022/signality-core-browser-web-share.mjs +3 -5
  118. package/fesm2022/signality-core-browser-web-share.mjs.map +1 -1
  119. package/fesm2022/signality-core-browser-web-worker.mjs +6 -3
  120. package/fesm2022/signality-core-browser-web-worker.mjs.map +1 -1
  121. package/fesm2022/signality-core-browser-window-focus.mjs +48 -0
  122. package/fesm2022/signality-core-browser-window-focus.mjs.map +1 -0
  123. package/fesm2022/{signality-core-elements-window-size.mjs → signality-core-browser-window-size.mjs} +4 -24
  124. package/fesm2022/signality-core-browser-window-size.mjs.map +1 -0
  125. package/fesm2022/signality-core-browser.mjs +8 -1
  126. package/fesm2022/signality-core-browser.mjs.map +1 -1
  127. package/fesm2022/signality-core-elements-active-element.mjs +1 -1
  128. package/fesm2022/signality-core-elements-active-element.mjs.map +1 -1
  129. package/fesm2022/signality-core-elements-dropzone.mjs +28 -29
  130. package/fesm2022/signality-core-elements-dropzone.mjs.map +1 -1
  131. package/fesm2022/signality-core-elements-element-focus-within.mjs +1 -1
  132. package/fesm2022/signality-core-elements-element-focus-within.mjs.map +1 -1
  133. package/fesm2022/signality-core-elements-element-focus.mjs +1 -1
  134. package/fesm2022/signality-core-elements-element-focus.mjs.map +1 -1
  135. package/fesm2022/signality-core-elements-element-hover.mjs +1 -1
  136. package/fesm2022/signality-core-elements-element-hover.mjs.map +1 -1
  137. package/fesm2022/signality-core-elements-element-size.mjs +19 -24
  138. package/fesm2022/signality-core-elements-element-size.mjs.map +1 -1
  139. package/fesm2022/signality-core-elements-element-visibility.mjs +2 -2
  140. package/fesm2022/signality-core-elements-element-visibility.mjs.map +1 -1
  141. package/fesm2022/signality-core-elements-mouse-position.mjs +3 -3
  142. package/fesm2022/signality-core-elements-mouse-position.mjs.map +1 -1
  143. package/fesm2022/signality-core-elements-on-click-outside.mjs +1 -1
  144. package/fesm2022/signality-core-elements-on-click-outside.mjs.map +1 -1
  145. package/fesm2022/signality-core-elements-on-disconnect.mjs +1 -1
  146. package/fesm2022/signality-core-elements-on-disconnect.mjs.map +1 -1
  147. package/fesm2022/signality-core-elements-on-long-press.mjs +1 -1
  148. package/fesm2022/signality-core-elements-on-long-press.mjs.map +1 -1
  149. package/fesm2022/signality-core-elements-pointer-swipe.mjs +1 -1
  150. package/fesm2022/signality-core-elements-pointer-swipe.mjs.map +1 -1
  151. package/fesm2022/signality-core-elements-scroll-position.mjs +2 -2
  152. package/fesm2022/signality-core-elements-scroll-position.mjs.map +1 -1
  153. package/fesm2022/signality-core-elements-swipe.mjs +1 -1
  154. package/fesm2022/signality-core-elements-swipe.mjs.map +1 -1
  155. package/fesm2022/signality-core-elements.mjs +0 -2
  156. package/fesm2022/signality-core-elements.mjs.map +1 -1
  157. package/fesm2022/signality-core-internal.mjs +54 -7
  158. package/fesm2022/signality-core-internal.mjs.map +1 -1
  159. package/fesm2022/signality-core-observers-intersection-observer.mjs +3 -2
  160. package/fesm2022/signality-core-observers-intersection-observer.mjs.map +1 -1
  161. package/fesm2022/signality-core-observers-mutation-observer.mjs +3 -2
  162. package/fesm2022/signality-core-observers-mutation-observer.mjs.map +1 -1
  163. package/fesm2022/signality-core-observers-resize-observer.mjs +3 -2
  164. package/fesm2022/signality-core-observers-resize-observer.mjs.map +1 -1
  165. package/fesm2022/signality-core-observers.mjs +0 -1
  166. package/fesm2022/signality-core-observers.mjs.map +1 -1
  167. package/fesm2022/signality-core-reactivity-debounced.mjs.map +1 -1
  168. package/fesm2022/signality-core-reactivity-throttled.mjs.map +1 -1
  169. package/fesm2022/signality-core-reactivity-watcher.mjs.map +1 -1
  170. package/fesm2022/signality-core-router-fragment.mjs +1 -1
  171. package/fesm2022/signality-core-router-fragment.mjs.map +1 -1
  172. package/fesm2022/signality-core-router-params.mjs +1 -1
  173. package/fesm2022/signality-core-router-params.mjs.map +1 -1
  174. package/fesm2022/signality-core-router-query-params.mjs.map +1 -1
  175. package/fesm2022/signality-core-router-route-data.mjs +1 -1
  176. package/fesm2022/signality-core-router-route-data.mjs.map +1 -1
  177. package/fesm2022/signality-core-router-router-listener.mjs.map +1 -1
  178. package/fesm2022/signality-core-router-title.mjs +1 -1
  179. package/fesm2022/signality-core-router-title.mjs.map +1 -1
  180. package/fesm2022/signality-core-router-url.mjs +1 -1
  181. package/fesm2022/signality-core-router-url.mjs.map +1 -1
  182. package/fesm2022/signality-core-scheduling-debounce-callback.mjs +1 -1
  183. package/fesm2022/signality-core-scheduling-debounce-callback.mjs.map +1 -1
  184. package/fesm2022/signality-core-scheduling-interval.mjs +27 -72
  185. package/fesm2022/signality-core-scheduling-interval.mjs.map +1 -1
  186. package/internal/utils/assert.d.ts +2 -0
  187. package/internal/utils/dom/index.d.ts +1 -0
  188. package/internal/utils/dom/is-element.d.ts +1 -1
  189. package/internal/utils/dom/is-event-target.d.ts +1 -0
  190. package/internal/utils/files/index.d.ts +1 -0
  191. package/internal/utils/files/is-accepted-file.d.ts +11 -0
  192. package/internal/utils/index.d.ts +3 -0
  193. package/internal/utils/to-element.d.ts +1 -1
  194. package/internal/utils/unref-element.d.ts +2 -0
  195. package/observers/index.d.ts +0 -1
  196. package/observers/intersection-observer/index.d.ts +22 -1
  197. package/observers/mutation-observer/index.d.ts +43 -1
  198. package/observers/resize-observer/index.d.ts +13 -1
  199. package/package.json +25 -17
  200. package/reactivity/debounced/index.d.ts +2 -2
  201. package/reactivity/throttled/index.d.ts +2 -2
  202. package/reactivity/watcher/index.d.ts +2 -2
  203. package/router/fragment/index.d.ts +1 -1
  204. package/router/params/index.d.ts +1 -1
  205. package/router/query-params/index.d.ts +5 -3
  206. package/router/route-data/index.d.ts +1 -1
  207. package/router/router-listener/index.d.ts +1 -1
  208. package/router/title/index.d.ts +1 -1
  209. package/router/url/index.d.ts +1 -1
  210. package/scheduling/debounce-callback/index.d.ts +1 -1
  211. package/scheduling/interval/index.d.ts +19 -27
  212. package/browser/pointer-lock-element/index.d.ts +0 -22
  213. package/fesm2022/signality-core-browser-pointer-lock-element.mjs +0 -43
  214. package/fesm2022/signality-core-browser-pointer-lock-element.mjs.map +0 -1
  215. package/fesm2022/signality-core-elements-text-selection.mjs.map +0 -1
  216. package/fesm2022/signality-core-elements-window-size.mjs.map +0 -1
  217. package/fesm2022/signality-core-observers-performance-observer.mjs +0 -84
  218. package/fesm2022/signality-core-observers-performance-observer.mjs.map +0 -1
  219. package/observers/performance-observer/index.d.ts +0 -58
@@ -1 +1 @@
1
- {"version":3,"file":"signality-core-browser-device-posture.mjs","sources":["../../../projects/core/browser/device-posture/index.ts","../../../projects/core/browser/device-posture/signality-core-browser-device-posture.ts"],"sourcesContent":["import { type Signal, signal } from '@angular/core';\nimport { constSignal, setupContext } from '@signality/core/internal';\nimport type { WithInjector } from '@signality/core/types';\nimport { listener, setupSync } from '@signality/core/browser/listener';\n\nexport type DevicePostureType = 'continuous' | 'folded';\n\nexport interface DevicePostureRef {\n /** Whether Device Posture API is supported */\n readonly isSupported: Signal<boolean>;\n\n /** Current device posture */\n readonly type: Signal<DevicePostureType>;\n}\n\n/**\n * Signal-based wrapper around the [Device Posture API](https://developer.mozilla.org/en-US/docs/Web/API/Device_Posture_API).\n * Track device posture state for foldable devices.\n *\n * @param options - Optional configuration including injector\n * @returns A DevicePostureRef with type signal\n *\n */\nexport function devicePosture(options?: WithInjector): DevicePostureRef {\n const { runInContext } = setupContext(options?.injector, devicePosture);\n\n return runInContext(({ isBrowser }) => {\n const isSupported = constSignal(\n isBrowser && 'devicePosture' in navigator && !!navigator.devicePosture\n );\n\n if (!isSupported()) {\n return {\n isSupported,\n type: constSignal('continuous'),\n };\n }\n\n const { devicePosture } = navigator as NavigatorWithDevicePosture;\n\n const type = signal<DevicePostureType>(devicePosture.type);\n\n setupSync(() => {\n listener(devicePosture, 'change', () => type.set(devicePosture.type));\n });\n\n return {\n isSupported,\n type: type.asReadonly(),\n };\n });\n}\n\ninterface DevicePosture extends EventTarget {\n readonly type: DevicePostureType;\n}\n\ninterface NavigatorWithDevicePosture extends Navigator {\n readonly devicePosture: DevicePosture;\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;;AAeA;;;;;;;AAOG;AACG,SAAU,aAAa,CAAC,OAAsB,EAAA;AAClD,IAAA,MAAM,EAAE,YAAY,EAAE,GAAG,YAAY,CAAC,OAAO,EAAE,QAAQ,EAAE,aAAa,CAAC;AAEvE,IAAA,OAAO,YAAY,CAAC,CAAC,EAAE,SAAS,EAAE,KAAI;AACpC,QAAA,MAAM,WAAW,GAAG,WAAW,CAC7B,SAAS,IAAI,eAAe,IAAI,SAAS,IAAI,CAAC,CAAC,SAAS,CAAC,aAAa,CACvE;AAED,QAAA,IAAI,CAAC,WAAW,EAAE,EAAE;YAClB,OAAO;gBACL,WAAW;AACX,gBAAA,IAAI,EAAE,WAAW,CAAC,YAAY,CAAC;aAChC;QACH;AAEA,QAAA,MAAM,EAAE,aAAa,EAAE,GAAG,SAAuC;QAEjE,MAAM,IAAI,GAAG,MAAM,CAAoB,aAAa,CAAC,IAAI,gDAAC;QAE1D,SAAS,CAAC,MAAK;AACb,YAAA,QAAQ,CAAC,aAAa,EAAE,QAAQ,EAAE,MAAM,IAAI,CAAC,GAAG,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC;AACvE,QAAA,CAAC,CAAC;QAEF,OAAO;YACL,WAAW;AACX,YAAA,IAAI,EAAE,IAAI,CAAC,UAAU,EAAE;SACxB;AACH,IAAA,CAAC,CAAC;AACJ;;ACnDA;;AAEG;;;;"}
1
+ {"version":3,"file":"signality-core-browser-device-posture.mjs","sources":["../../../projects/core/browser/device-posture/index.ts","../../../projects/core/browser/device-posture/signality-core-browser-device-posture.ts"],"sourcesContent":["import { type Signal, signal } from '@angular/core';\nimport { constSignal, setupContext } from '@signality/core/internal';\nimport type { WithInjector } from '@signality/core/types';\nimport { listener, setupSync } from '@signality/core/browser/listener';\n\nexport type DevicePostureType = 'continuous' | 'folded';\n\nexport interface DevicePostureRef {\n /**\n * Whether the Device Posture API is supported in the current browser.\n *\n * @see [Device Posture API browser compatibility on MDN](https://developer.mozilla.org/en-US/docs/Web/API/Device_Posture_API#browser_compatibility)\n */\n readonly isSupported: Signal<boolean>;\n\n /**\n * Current posture of the device. `'continuous'` for flat screens, `'folded'` for foldable devices.\n *\n * @see [DevicePosture: type on MDN](https://developer.mozilla.org/en-US/docs/Web/API/DevicePosture/type)\n */\n readonly type: Signal<DevicePostureType>;\n}\n\n/**\n * Signal-based wrapper around the [Device Posture API](https://developer.mozilla.org/en-US/docs/Web/API/Device_Posture_API).\n * Track device posture state for foldable devices.\n *\n * @param options - Optional configuration including injector\n * @returns A DevicePostureRef with type signal\n *\n * @example\n * ```typescript\n * @Component({\n * template: `\n * @if (posture.isSupported()) {\n * <p>Device posture: {{ posture.type() }}</p>\n * }\n * `\n * })\n * export class PostureDemo {\n * readonly posture = devicePosture();\n * }\n * ```\n */\nexport function devicePosture(options?: WithInjector): DevicePostureRef {\n const { runInContext } = setupContext(options?.injector, devicePosture);\n\n return runInContext(({ isBrowser }) => {\n const isSupported = constSignal(\n isBrowser && 'devicePosture' in navigator && !!navigator.devicePosture\n );\n\n if (!isSupported()) {\n return {\n isSupported,\n type: constSignal('continuous'),\n };\n }\n\n const { devicePosture } = navigator as NavigatorWithDevicePosture;\n\n const type = signal<DevicePostureType>(devicePosture.type);\n\n setupSync(() => {\n listener(devicePosture, 'change', () => type.set(devicePosture.type));\n });\n\n return {\n isSupported,\n type: type.asReadonly(),\n };\n });\n}\n\ninterface DevicePosture extends EventTarget {\n readonly type: DevicePostureType;\n}\n\ninterface NavigatorWithDevicePosture extends Navigator {\n readonly devicePosture: DevicePosture;\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;;AAuBA;;;;;;;;;;;;;;;;;;;;AAoBG;AACG,SAAU,aAAa,CAAC,OAAsB,EAAA;AAClD,IAAA,MAAM,EAAE,YAAY,EAAE,GAAG,YAAY,CAAC,OAAO,EAAE,QAAQ,EAAE,aAAa,CAAC;AAEvE,IAAA,OAAO,YAAY,CAAC,CAAC,EAAE,SAAS,EAAE,KAAI;AACpC,QAAA,MAAM,WAAW,GAAG,WAAW,CAC7B,SAAS,IAAI,eAAe,IAAI,SAAS,IAAI,CAAC,CAAC,SAAS,CAAC,aAAa,CACvE;AAED,QAAA,IAAI,CAAC,WAAW,EAAE,EAAE;YAClB,OAAO;gBACL,WAAW;AACX,gBAAA,IAAI,EAAE,WAAW,CAAC,YAAY,CAAC;aAChC;QACH;AAEA,QAAA,MAAM,EAAE,aAAa,EAAE,GAAG,SAAuC;QAEjE,MAAM,IAAI,GAAG,MAAM,CAAoB,aAAa,CAAC,IAAI,gDAAC;QAE1D,SAAS,CAAC,MAAK;AACb,YAAA,QAAQ,CAAC,aAAa,EAAE,QAAQ,EAAE,MAAM,IAAI,CAAC,GAAG,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC;AACvE,QAAA,CAAC,CAAC;QAEF,OAAO;YACL,WAAW;AACX,YAAA,IAAI,EAAE,IAAI,CAAC,UAAU,EAAE;SACxB;AACH,IAAA,CAAC,CAAC;AACJ;;ACxEA;;AAEG;;;;"}
@@ -28,7 +28,7 @@ import { listener } from '@signality/core/browser/listener';
28
28
  * }
29
29
  * `
30
30
  * })
31
- * class ScreenCaptureComponent {
31
+ * export class ScreenCaptureDemo {
32
32
  * readonly screen = displayMedia();
33
33
  *
34
34
  * async toggleCapture() {
@@ -40,18 +40,6 @@ import { listener } from '@signality/core/browser/listener';
40
40
  * }
41
41
  * }
42
42
  * ```
43
- *
44
- * @example
45
- * ```typescript
46
- * // With custom constraints
47
- * const screen = displayMedia({
48
- * video: {
49
- * width: { ideal: 1920 },
50
- * height: { ideal: 1080 },
51
- * },
52
- * audio: true,
53
- * });
54
- * ```
55
43
  */
56
44
  function displayMedia(options) {
57
45
  const { runInContext } = setupContext(options?.injector, displayMedia);
@@ -69,18 +57,17 @@ function displayMedia(options) {
69
57
  stop: NOOP_FN,
70
58
  };
71
59
  }
72
- const defaults = {
60
+ const constraints = {
73
61
  video: options?.video ?? true,
74
62
  audio: options?.audio ?? false,
75
63
  };
76
64
  const stream = signal(null, ...(ngDevMode ? [{ debugName: "stream" }] : []));
77
65
  const error = signal(null, ...(ngDevMode ? [{ debugName: "error" }] : []));
78
66
  const isActive = computed(() => stream() !== null, ...(ngDevMode ? [{ debugName: "isActive" }] : []));
79
- const start = async (overrides) => {
67
+ const start = async () => {
80
68
  try {
81
69
  stop();
82
- const mergedOptions = { ...defaults, ...overrides };
83
- const mediaStream = await navigator.mediaDevices.getDisplayMedia(mergedOptions);
70
+ const mediaStream = await navigator.mediaDevices.getDisplayMedia(constraints);
84
71
  stream.set(mediaStream);
85
72
  error.set(null);
86
73
  mediaStream
@@ -1 +1 @@
1
- {"version":3,"file":"signality-core-browser-display-media.mjs","sources":["../../../projects/core/browser/display-media/index.ts","../../../projects/core/browser/display-media/signality-core-browser-display-media.ts"],"sourcesContent":["import { computed, type Signal, signal, untracked } from '@angular/core';\nimport { constSignal, NOOP_FN, setupContext } from '@signality/core/internal';\nimport type { WithInjector } from '@signality/core/types';\nimport { listener } from '@signality/core/browser/listener';\n\nexport interface DisplayMediaOptions extends WithInjector {\n /**\n * Video constraints.\n * @default true\n */\n readonly video?: boolean | MediaTrackConstraints;\n\n /**\n * Audio constraints.\n * @default false\n */\n readonly audio?: boolean | MediaTrackConstraints;\n}\n\nexport interface DisplayMediaRef {\n /** Whether Screen Capture API is supported */\n readonly isSupported: Signal<boolean>;\n\n /** Whether currently capturing */\n readonly isActive: Signal<boolean>;\n\n /** Current media stream */\n readonly stream: Signal<MediaStream | null>;\n\n /** Last error */\n readonly error: Signal<Error | null>;\n\n /** Start screen capture */\n readonly start: (options?: DisplayMediaOptions) => Promise<MediaStream | null>;\n\n /** Stop screen capture */\n readonly stop: () => void;\n}\n\n/**\n * Signal-based wrapper around the [Screen Capture API](https://developer.mozilla.org/en-US/docs/Web/API/Screen_Capture_API).\n * Capture screen content with Angular signals.\n *\n * @param options - Optional default configuration\n * @returns A DisplayMediaRef with isSupported, stream, error signals and control methods\n *\n * @example\n * ```typescript\n * @Component({\n * template: `\n * @if (screen.isSupported()) {\n * <button (click)=\"toggleCapture()\">\n * {{ screen.isActive() ? 'Stop' : 'Start' }} Screen Capture\n * </button>\n * @if (screen.stream(); as stream) {\n * <video [srcObject]=\"stream\" autoplay></video>\n * }\n * @if (screen.error(); as error) {\n * <p class=\"error\">{{ error.message }}</p>\n * }\n * } @else {\n * <p>Screen Capture API not supported</p>\n * }\n * `\n * })\n * class ScreenCaptureComponent {\n * readonly screen = displayMedia();\n *\n * async toggleCapture() {\n * if (this.screen.isActive()) {\n * this.screen.stop();\n * } else {\n * await this.screen.start();\n * }\n * }\n * }\n * ```\n *\n * @example\n * ```typescript\n * // With custom constraints\n * const screen = displayMedia({\n * video: {\n * width: { ideal: 1920 },\n * height: { ideal: 1080 },\n * },\n * audio: true,\n * });\n * ```\n */\nexport function displayMedia(options?: DisplayMediaOptions): DisplayMediaRef {\n const { runInContext } = setupContext(options?.injector, displayMedia);\n\n return runInContext(({ isBrowser, injector, onCleanup }) => {\n const isSupported = constSignal(\n isBrowser &&\n 'mediaDevices' in navigator &&\n typeof navigator.mediaDevices?.getDisplayMedia === 'function'\n );\n\n if (!isSupported()) {\n return {\n isSupported,\n isActive: constSignal(false),\n stream: constSignal(null),\n error: constSignal(null),\n start: () => Promise.resolve(null),\n stop: NOOP_FN,\n };\n }\n\n const defaults: DisplayMediaOptions = {\n video: options?.video ?? true,\n audio: options?.audio ?? false,\n };\n\n const stream = signal<MediaStream | null>(null);\n const error = signal<Error | null>(null);\n const isActive = computed(() => stream() !== null);\n\n const start = async (overrides?: DisplayMediaOptions): Promise<MediaStream | null> => {\n try {\n stop();\n\n const mergedOptions = { ...defaults, ...overrides };\n const mediaStream = await navigator.mediaDevices.getDisplayMedia(mergedOptions);\n\n stream.set(mediaStream);\n error.set(null);\n\n mediaStream\n .getTracks()\n .forEach(track => listener(track, 'ended', () => stop(), { injector }));\n\n return mediaStream;\n } catch (err) {\n error.set(err as DOMException | TypeError);\n return null;\n }\n };\n\n const stop = () => {\n const currStream = untracked(stream);\n if (currStream) {\n currStream.getTracks().forEach(track => track.stop());\n stream.set(null);\n }\n error.set(null);\n };\n\n onCleanup(stop);\n\n return {\n isSupported,\n isActive: isActive,\n stream: stream.asReadonly(),\n error: error.asReadonly(),\n start,\n stop,\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-display-media.mjs","sources":["../../../projects/core/browser/display-media/index.ts","../../../projects/core/browser/display-media/signality-core-browser-display-media.ts"],"sourcesContent":["import { computed, type Signal, signal, untracked } from '@angular/core';\nimport { constSignal, NOOP_FN, setupContext } from '@signality/core/internal';\nimport type { WithInjector } from '@signality/core/types';\nimport { listener } from '@signality/core/browser/listener';\n\nexport interface DisplayMediaOptions extends WithInjector {\n /**\n * Video track constraints for the captured stream.\n *\n * @default true\n * @see [MediaTrackConstraints on MDN](https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints)\n */\n readonly video?: boolean | MediaTrackConstraints;\n\n /**\n * Audio track constraints for the captured stream.\n *\n * @default false\n * @see [MediaTrackConstraints on MDN](https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints)\n */\n readonly audio?: boolean | MediaTrackConstraints;\n}\n\nexport interface DisplayMediaRef {\n /**\n * Whether the Screen Capture API is supported in the current browser.\n *\n * @see [Screen Capture API browser compatibility on MDN](https://developer.mozilla.org/en-US/docs/Web/API/Screen_Capture_API#browser_compatibility)\n */\n readonly isSupported: Signal<boolean>;\n\n /**\n * Whether screen capture is currently active.\n */\n readonly isActive: Signal<boolean>;\n\n /**\n * The active media stream, or `null` if capture is not active.\n *\n * @see [MediaStream on MDN](https://developer.mozilla.org/en-US/docs/Web/API/MediaStream)\n */\n readonly stream: Signal<MediaStream | null>;\n\n /**\n * The last error that occurred, or `null` if no error.\n */\n readonly error: Signal<Error | null>;\n\n /**\n * Start screen capture. Opens the browser's display picker.\n *\n * @see [MediaDevices: getDisplayMedia() on MDN](https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getDisplayMedia)\n */\n readonly start: () => Promise<MediaStream | null>;\n\n /**\n * Stop all active capture tracks and clear the stream.\n *\n * @see [MediaStreamTrack: stop() on MDN](https://developer.mozilla.org/en-US/docs/Web/API/MediaStreamTrack/stop)\n */\n readonly stop: () => void;\n}\n\n/**\n * Signal-based wrapper around the [Screen Capture API](https://developer.mozilla.org/en-US/docs/Web/API/Screen_Capture_API).\n * Capture screen content with Angular signals.\n *\n * @param options - Optional default configuration\n * @returns A DisplayMediaRef with isSupported, stream, error signals and control methods\n *\n * @example\n * ```typescript\n * @Component({\n * template: `\n * @if (screen.isSupported()) {\n * <button (click)=\"toggleCapture()\">\n * {{ screen.isActive() ? 'Stop' : 'Start' }} Screen Capture\n * </button>\n * @if (screen.stream(); as stream) {\n * <video [srcObject]=\"stream\" autoplay></video>\n * }\n * @if (screen.error(); as error) {\n * <p class=\"error\">{{ error.message }}</p>\n * }\n * } @else {\n * <p>Screen Capture API not supported</p>\n * }\n * `\n * })\n * export class ScreenCaptureDemo {\n * readonly screen = displayMedia();\n *\n * async toggleCapture() {\n * if (this.screen.isActive()) {\n * this.screen.stop();\n * } else {\n * await this.screen.start();\n * }\n * }\n * }\n * ```\n */\nexport function displayMedia(options?: DisplayMediaOptions): DisplayMediaRef {\n const { runInContext } = setupContext(options?.injector, displayMedia);\n\n return runInContext(({ isBrowser, injector, onCleanup }) => {\n const isSupported = constSignal(\n isBrowser &&\n 'mediaDevices' in navigator &&\n typeof navigator.mediaDevices?.getDisplayMedia === 'function'\n );\n\n if (!isSupported()) {\n return {\n isSupported,\n isActive: constSignal(false),\n stream: constSignal(null),\n error: constSignal(null),\n start: () => Promise.resolve(null),\n stop: NOOP_FN,\n };\n }\n\n const constraints: DisplayMediaStreamOptions = {\n video: options?.video ?? true,\n audio: options?.audio ?? false,\n };\n\n const stream = signal<MediaStream | null>(null);\n const error = signal<Error | null>(null);\n const isActive = computed(() => stream() !== null);\n\n const start = async (): Promise<MediaStream | null> => {\n try {\n stop();\n\n const mediaStream = await navigator.mediaDevices.getDisplayMedia(constraints);\n\n stream.set(mediaStream);\n error.set(null);\n\n mediaStream\n .getTracks()\n .forEach(track => listener(track, 'ended', () => stop(), { injector }));\n\n return mediaStream;\n } catch (err) {\n error.set(err as DOMException | TypeError);\n return null;\n }\n };\n\n const stop = () => {\n const currStream = untracked(stream);\n if (currStream) {\n currStream.getTracks().forEach(track => track.stop());\n stream.set(null);\n }\n error.set(null);\n };\n\n onCleanup(stop);\n\n return {\n isSupported,\n isActive: isActive,\n stream: stream.asReadonly(),\n error: error.asReadonly(),\n start,\n stop,\n };\n });\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;;AA+DA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsCG;AACG,SAAU,YAAY,CAAC,OAA6B,EAAA;AACxD,IAAA,MAAM,EAAE,YAAY,EAAE,GAAG,YAAY,CAAC,OAAO,EAAE,QAAQ,EAAE,YAAY,CAAC;IAEtE,OAAO,YAAY,CAAC,CAAC,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,EAAE,KAAI;AACzD,QAAA,MAAM,WAAW,GAAG,WAAW,CAC7B,SAAS;AACP,YAAA,cAAc,IAAI,SAAS;YAC3B,OAAO,SAAS,CAAC,YAAY,EAAE,eAAe,KAAK,UAAU,CAChE;AAED,QAAA,IAAI,CAAC,WAAW,EAAE,EAAE;YAClB,OAAO;gBACL,WAAW;AACX,gBAAA,QAAQ,EAAE,WAAW,CAAC,KAAK,CAAC;AAC5B,gBAAA,MAAM,EAAE,WAAW,CAAC,IAAI,CAAC;AACzB,gBAAA,KAAK,EAAE,WAAW,CAAC,IAAI,CAAC;gBACxB,KAAK,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC;AAClC,gBAAA,IAAI,EAAE,OAAO;aACd;QACH;AAEA,QAAA,MAAM,WAAW,GAA8B;AAC7C,YAAA,KAAK,EAAE,OAAO,EAAE,KAAK,IAAI,IAAI;AAC7B,YAAA,KAAK,EAAE,OAAO,EAAE,KAAK,IAAI,KAAK;SAC/B;AAED,QAAA,MAAM,MAAM,GAAG,MAAM,CAAqB,IAAI,kDAAC;AAC/C,QAAA,MAAM,KAAK,GAAG,MAAM,CAAe,IAAI,iDAAC;AACxC,QAAA,MAAM,QAAQ,GAAG,QAAQ,CAAC,MAAM,MAAM,EAAE,KAAK,IAAI,EAAA,IAAA,SAAA,GAAA,CAAA,EAAA,SAAA,EAAA,UAAA,EAAA,CAAA,GAAA,EAAA,CAAA,CAAC;AAElD,QAAA,MAAM,KAAK,GAAG,YAAwC;AACpD,YAAA,IAAI;AACF,gBAAA,IAAI,EAAE;gBAEN,MAAM,WAAW,GAAG,MAAM,SAAS,CAAC,YAAY,CAAC,eAAe,CAAC,WAAW,CAAC;AAE7E,gBAAA,MAAM,CAAC,GAAG,CAAC,WAAW,CAAC;AACvB,gBAAA,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC;gBAEf;AACG,qBAAA,SAAS;qBACT,OAAO,CAAC,KAAK,IAAI,QAAQ,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,IAAI,EAAE,EAAE,EAAE,QAAQ,EAAE,CAAC,CAAC;AAEzE,gBAAA,OAAO,WAAW;YACpB;YAAE,OAAO,GAAG,EAAE;AACZ,gBAAA,KAAK,CAAC,GAAG,CAAC,GAA+B,CAAC;AAC1C,gBAAA,OAAO,IAAI;YACb;AACF,QAAA,CAAC;QAED,MAAM,IAAI,GAAG,MAAK;AAChB,YAAA,MAAM,UAAU,GAAG,SAAS,CAAC,MAAM,CAAC;YACpC,IAAI,UAAU,EAAE;AACd,gBAAA,UAAU,CAAC,SAAS,EAAE,CAAC,OAAO,CAAC,KAAK,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC;AACrD,gBAAA,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC;YAClB;AACA,YAAA,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC;AACjB,QAAA,CAAC;QAED,SAAS,CAAC,IAAI,CAAC;QAEf,OAAO;YACL,WAAW;AACX,YAAA,QAAQ,EAAE,QAAQ;AAClB,YAAA,MAAM,EAAE,MAAM,CAAC,UAAU,EAAE;AAC3B,YAAA,KAAK,EAAE,KAAK,CAAC,UAAU,EAAE;YACzB,KAAK;YACL,IAAI;SACL;AACH,IAAA,CAAC,CAAC;AACJ;;AC5KA;;AAEG;;;;"}
@@ -19,7 +19,7 @@ import { setupContext, constSignal, NOOP_FN, NOOP_ASYNC_FN } from '@signality/co
19
19
  * }
20
20
  * `
21
21
  * })
22
- * class ColorPickerComponent {
22
+ * export class ColorPicker {
23
23
  * readonly eyeDropper = eyeDropper();
24
24
  *
25
25
  * async pickColor() {
@@ -1 +1 @@
1
- {"version":3,"file":"signality-core-browser-eye-dropper.mjs","sources":["../../../projects/core/browser/eye-dropper/index.ts","../../../projects/core/browser/eye-dropper/signality-core-browser-eye-dropper.ts"],"sourcesContent":["import { type Signal, signal } from '@angular/core';\nimport { constSignal, NOOP_ASYNC_FN, NOOP_FN, setupContext } from '@signality/core/internal';\nimport type { WithInjector } from '@signality/core/types';\n\nexport interface EyeDropperOptions extends WithInjector {\n /**\n * Initial color value in sRGB hex format.\n * @default ''\n */\n readonly initialValue?: string;\n}\n\nexport interface EyeDropperRef {\n /** Current selected color in sRGB hex format */\n readonly sRGBHex: Signal<string>;\n\n /** Whether EyeDropper API is supported */\n readonly isSupported: Signal<boolean>;\n\n /** Open the eyedropper tool to select a color */\n readonly open: () => Promise<void>;\n\n /** Cancel the active eyedropper operation */\n readonly close: () => void;\n}\n\n/**\n * Signal-based wrapper around the [EyeDropper API](https://developer.mozilla.org/en-US/docs/Web/API/EyeDropper_API).\n *\n * @param options - Optional configuration\n * @returns An {@link EyeDropperRef} with `isSupported`, `sRGBHex` signals and `open`/`close` methods\n *\n * @example\n * ```typescript\n * @Component({\n * template: `\n * @if (eyeDropper.isSupported()) {\n * <button (click)=\"pickColor()\">Pick Color</button>\n * <div [style.background-color]=\"eyeDropper.sRGBHex()\">\n * Selected: {{ eyeDropper.sRGBHex() }}\n * </div>\n * }\n * `\n * })\n * class ColorPickerComponent {\n * readonly eyeDropper = eyeDropper();\n *\n * async pickColor() {\n * await this.eyeDropper.open();\n * }\n * }\n * ```\n */\nexport function eyeDropper(options?: EyeDropperOptions): EyeDropperRef {\n const { runInContext } = setupContext(options?.injector, eyeDropper);\n\n return runInContext(({ isBrowser, onCleanup }) => {\n const isSupported = constSignal(isBrowser && 'EyeDropper' in window);\n\n if (!isSupported()) {\n return {\n isSupported,\n sRGBHex: constSignal(''),\n open: NOOP_ASYNC_FN,\n close: NOOP_FN,\n };\n }\n\n const sRGBHex = signal(options?.initialValue ?? '');\n\n let abortController: AbortController | null = null;\n\n const open = async (): Promise<void> => {\n close();\n\n abortController = new AbortController();\n const eyeDropper: EyeDropper = new (window as any).EyeDropper();\n\n try {\n const result = await eyeDropper.open({ signal: abortController.signal });\n sRGBHex.set(result.sRGBHex);\n } catch (error) {\n if (ngDevMode) {\n console.warn(\n `[eyeDropper] Failed to open eyedropper. ` +\n `This may be due to user cancellation or an error occurred.`,\n error\n );\n }\n } finally {\n abortController = null;\n }\n };\n\n const close = () => {\n abortController?.abort();\n abortController = null;\n };\n\n onCleanup(close);\n\n return {\n isSupported,\n sRGBHex: sRGBHex.asReadonly(),\n open,\n close,\n };\n });\n}\n\ninterface EyeDropper {\n // eslint-disable-next-line @typescript-eslint/no-misused-new\n new (): EyeDropper;\n readonly open: (options?: EyeDropperOpenOptions) => Promise<{ sRGBHex: string }>;\n [Symbol.toStringTag]: 'EyeDropper';\n}\n\ninterface EyeDropperOpenOptions {\n /**\n * AbortSignal to cancel the eyedropper operation.\n * @see https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal\n */\n readonly signal?: AbortSignal;\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;AA0BA;;;;;;;;;;;;;;;;;;;;;;;;;;AA0BG;AACG,SAAU,UAAU,CAAC,OAA2B,EAAA;AACpD,IAAA,MAAM,EAAE,YAAY,EAAE,GAAG,YAAY,CAAC,OAAO,EAAE,QAAQ,EAAE,UAAU,CAAC;IAEpE,OAAO,YAAY,CAAC,CAAC,EAAE,SAAS,EAAE,SAAS,EAAE,KAAI;QAC/C,MAAM,WAAW,GAAG,WAAW,CAAC,SAAS,IAAI,YAAY,IAAI,MAAM,CAAC;AAEpE,QAAA,IAAI,CAAC,WAAW,EAAE,EAAE;YAClB,OAAO;gBACL,WAAW;AACX,gBAAA,OAAO,EAAE,WAAW,CAAC,EAAE,CAAC;AACxB,gBAAA,IAAI,EAAE,aAAa;AACnB,gBAAA,KAAK,EAAE,OAAO;aACf;QACH;QAEA,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,EAAE,YAAY,IAAI,EAAE,EAAA,IAAA,SAAA,GAAA,CAAA,EAAA,SAAA,EAAA,SAAA,EAAA,CAAA,GAAA,EAAA,CAAA,CAAC;QAEnD,IAAI,eAAe,GAA2B,IAAI;AAElD,QAAA,MAAM,IAAI,GAAG,YAA0B;AACrC,YAAA,KAAK,EAAE;AAEP,YAAA,eAAe,GAAG,IAAI,eAAe,EAAE;AACvC,YAAA,MAAM,UAAU,GAAe,IAAK,MAAc,CAAC,UAAU,EAAE;AAE/D,YAAA,IAAI;AACF,gBAAA,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,eAAe,CAAC,MAAM,EAAE,CAAC;AACxE,gBAAA,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC;YAC7B;YAAE,OAAO,KAAK,EAAE;gBACd,IAAI,SAAS,EAAE;oBACb,OAAO,CAAC,IAAI,CACV,CAAA,wCAAA,CAA0C;wBACxC,CAAA,0DAAA,CAA4D,EAC9D,KAAK,CACN;gBACH;YACF;oBAAU;gBACR,eAAe,GAAG,IAAI;YACxB;AACF,QAAA,CAAC;QAED,MAAM,KAAK,GAAG,MAAK;YACjB,eAAe,EAAE,KAAK,EAAE;YACxB,eAAe,GAAG,IAAI;AACxB,QAAA,CAAC;QAED,SAAS,CAAC,KAAK,CAAC;QAEhB,OAAO;YACL,WAAW;AACX,YAAA,OAAO,EAAE,OAAO,CAAC,UAAU,EAAE;YAC7B,IAAI;YACJ,KAAK;SACN;AACH,IAAA,CAAC,CAAC;AACJ;;AC5GA;;AAEG;;;;"}
1
+ {"version":3,"file":"signality-core-browser-eye-dropper.mjs","sources":["../../../projects/core/browser/eye-dropper/index.ts","../../../projects/core/browser/eye-dropper/signality-core-browser-eye-dropper.ts"],"sourcesContent":["import { type Signal, signal } from '@angular/core';\nimport { constSignal, NOOP_ASYNC_FN, NOOP_FN, setupContext } from '@signality/core/internal';\nimport type { WithInjector } from '@signality/core/types';\n\nexport interface EyeDropperOptions extends WithInjector {\n /**\n * Initial color value in sRGB hex format.\n * @default ''\n */\n readonly initialValue?: string;\n}\n\nexport interface EyeDropperRef {\n /**\n * Whether the EyeDropper API is supported in the current browser.\n *\n * @see [EyeDropper browser compatibility on MDN](https://developer.mozilla.org/en-US/docs/Web/API/EyeDropper#browser_compatibility)\n */\n readonly isSupported: Signal<boolean>;\n\n /**\n * The most recently selected color in sRGB hex format (e.g. `#ff0000`).\n *\n * @see [EyeDropper: open() return value on MDN](https://developer.mozilla.org/en-US/docs/Web/API/EyeDropper/open#return_value)\n */\n readonly sRGBHex: Signal<string>;\n\n /**\n * Open the eyedropper tool and wait for the user to select a color.\n *\n * @see [EyeDropper: open() on MDN](https://developer.mozilla.org/en-US/docs/Web/API/EyeDropper/open)\n */\n readonly open: () => Promise<void>;\n\n /**\n * Cancel the active eyedropper operation via `AbortController`.\n *\n * @see [AbortController: abort() on MDN](https://developer.mozilla.org/en-US/docs/Web/API/AbortController/abort)\n */\n readonly close: () => void;\n}\n\n/**\n * Signal-based wrapper around the [EyeDropper API](https://developer.mozilla.org/en-US/docs/Web/API/EyeDropper_API).\n *\n * @param options - Optional configuration\n * @returns An {@link EyeDropperRef} with `isSupported`, `sRGBHex` signals and `open`/`close` methods\n *\n * @example\n * ```typescript\n * @Component({\n * template: `\n * @if (eyeDropper.isSupported()) {\n * <button (click)=\"pickColor()\">Pick Color</button>\n * <div [style.background-color]=\"eyeDropper.sRGBHex()\">\n * Selected: {{ eyeDropper.sRGBHex() }}\n * </div>\n * }\n * `\n * })\n * export class ColorPicker {\n * readonly eyeDropper = eyeDropper();\n *\n * async pickColor() {\n * await this.eyeDropper.open();\n * }\n * }\n * ```\n */\nexport function eyeDropper(options?: EyeDropperOptions): EyeDropperRef {\n const { runInContext } = setupContext(options?.injector, eyeDropper);\n\n return runInContext(({ isBrowser, onCleanup }) => {\n const isSupported = constSignal(isBrowser && 'EyeDropper' in window);\n\n if (!isSupported()) {\n return {\n isSupported,\n sRGBHex: constSignal(''),\n open: NOOP_ASYNC_FN,\n close: NOOP_FN,\n };\n }\n\n const sRGBHex = signal(options?.initialValue ?? '');\n\n let abortController: AbortController | null = null;\n\n const open = async (): Promise<void> => {\n close();\n\n abortController = new AbortController();\n const eyeDropper: EyeDropper = new (window as any).EyeDropper();\n\n try {\n const result = await eyeDropper.open({ signal: abortController.signal });\n sRGBHex.set(result.sRGBHex);\n } catch (error) {\n if (ngDevMode) {\n console.warn(\n `[eyeDropper] Failed to open eyedropper. ` +\n `This may be due to user cancellation or an error occurred.`,\n error\n );\n }\n } finally {\n abortController = null;\n }\n };\n\n const close = () => {\n abortController?.abort();\n abortController = null;\n };\n\n onCleanup(close);\n\n return {\n isSupported,\n sRGBHex: sRGBHex.asReadonly(),\n open,\n close,\n };\n });\n}\n\ninterface EyeDropper {\n // eslint-disable-next-line @typescript-eslint/no-misused-new\n new (): EyeDropper;\n readonly open: (options?: EyeDropperOpenOptions) => Promise<{ sRGBHex: string }>;\n [Symbol.toStringTag]: 'EyeDropper';\n}\n\ninterface EyeDropperOpenOptions {\n /**\n * AbortSignal to cancel the eyedropper operation.\n *\n * @see [AbortSignal on MDN](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal)\n */\n readonly signal?: AbortSignal;\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;AA0CA;;;;;;;;;;;;;;;;;;;;;;;;;;AA0BG;AACG,SAAU,UAAU,CAAC,OAA2B,EAAA;AACpD,IAAA,MAAM,EAAE,YAAY,EAAE,GAAG,YAAY,CAAC,OAAO,EAAE,QAAQ,EAAE,UAAU,CAAC;IAEpE,OAAO,YAAY,CAAC,CAAC,EAAE,SAAS,EAAE,SAAS,EAAE,KAAI;QAC/C,MAAM,WAAW,GAAG,WAAW,CAAC,SAAS,IAAI,YAAY,IAAI,MAAM,CAAC;AAEpE,QAAA,IAAI,CAAC,WAAW,EAAE,EAAE;YAClB,OAAO;gBACL,WAAW;AACX,gBAAA,OAAO,EAAE,WAAW,CAAC,EAAE,CAAC;AACxB,gBAAA,IAAI,EAAE,aAAa;AACnB,gBAAA,KAAK,EAAE,OAAO;aACf;QACH;QAEA,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,EAAE,YAAY,IAAI,EAAE,EAAA,IAAA,SAAA,GAAA,CAAA,EAAA,SAAA,EAAA,SAAA,EAAA,CAAA,GAAA,EAAA,CAAA,CAAC;QAEnD,IAAI,eAAe,GAA2B,IAAI;AAElD,QAAA,MAAM,IAAI,GAAG,YAA0B;AACrC,YAAA,KAAK,EAAE;AAEP,YAAA,eAAe,GAAG,IAAI,eAAe,EAAE;AACvC,YAAA,MAAM,UAAU,GAAe,IAAK,MAAc,CAAC,UAAU,EAAE;AAE/D,YAAA,IAAI;AACF,gBAAA,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,eAAe,CAAC,MAAM,EAAE,CAAC;AACxE,gBAAA,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC;YAC7B;YAAE,OAAO,KAAK,EAAE;gBACd,IAAI,SAAS,EAAE;oBACb,OAAO,CAAC,IAAI,CACV,CAAA,wCAAA,CAA0C;wBACxC,CAAA,0DAAA,CAA4D,EAC9D,KAAK,CACN;gBACH;YACF;oBAAU;gBACR,eAAe,GAAG,IAAI;YACxB;AACF,QAAA,CAAC;QAED,MAAM,KAAK,GAAG,MAAK;YACjB,eAAe,EAAE,KAAK,EAAE;YACxB,eAAe,GAAG,IAAI;AACxB,QAAA,CAAC;QAED,SAAS,CAAC,KAAK,CAAC;QAEhB,OAAO;YACL,WAAW;AACX,YAAA,OAAO,EAAE,OAAO,CAAC,UAAU,EAAE;YAC7B,IAAI;YACJ,KAAK;SACN;AACH,IAAA,CAAC,CAAC;AACJ;;AC5HA;;AAEG;;;;"}
@@ -3,7 +3,7 @@ import { APP_BASE_HREF } from '@angular/common';
3
3
  import { setupContext, NOOP_FN, constSignal, createToken } from '@signality/core/internal';
4
4
 
5
5
  /**
6
- * Reactive favicon manipulation.
6
+ * Reactive favicon manipulation using the [HTMLLinkElement](https://developer.mozilla.org/en-US/docs/Web/API/HTMLLinkElement).
7
7
  * Dynamically change the page favicon based on application state.
8
8
  *
9
9
  * @param options - Optional configuration
@@ -18,7 +18,7 @@ import { setupContext, NOOP_FN, constSignal, createToken } from '@signality/core
18
18
  * <p>Current: {{ fav.current() }}</p>
19
19
  * `
20
20
  * })
21
- * class FaviconComponent {
21
+ * export class FaviconDemo {
22
22
  * readonly fav = favicon();
23
23
  *
24
24
  * setNotification() {
@@ -1 +1 @@
1
- {"version":3,"file":"signality-core-browser-favicon.mjs","sources":["../../../projects/core/browser/favicon/index.ts","../../../projects/core/browser/favicon/signality-core-browser-favicon.ts"],"sourcesContent":["import { inject, type Signal, signal, untracked } from '@angular/core';\nimport { APP_BASE_HREF } from '@angular/common';\nimport { constSignal, createToken, NOOP_FN, setupContext } from '@signality/core/internal';\nimport type { WithInjector } from '@signality/core/types';\n\nexport interface FaviconOptions extends WithInjector {\n readonly baseUrl?: string;\n}\n\nexport interface FaviconRef {\n readonly current: Signal<string>;\n readonly original: Signal<string>;\n readonly set: (url: string) => void;\n readonly setEmoji: (emoji: string) => void;\n readonly reset: () => void;\n}\n\n/**\n * Reactive favicon manipulation.\n * Dynamically change the page favicon based on application state.\n *\n * @param options - Optional configuration\n * @returns A FaviconRef with favicon control methods\n *\n * @example\n * ```typescript\n * @Component({\n * template: `\n * <button (click)=\"setNotification()\">Set Notification</button>\n * <button (click)=\"fav.reset()\">Reset Favicon</button>\n * <p>Current: {{ fav.current() }}</p>\n * `\n * })\n * class FaviconComponent {\n * readonly fav = favicon();\n *\n * setNotification() {\n * this.fav.setEmoji('🔴');\n * }\n * }\n * ```\n */\nexport function favicon(options?: FaviconOptions): FaviconRef {\n const { runInContext } = setupContext(options?.injector, favicon);\n\n return runInContext(({ isServer }) => {\n if (isServer) {\n return {\n current: constSignal(''),\n original: constSignal(''),\n set: NOOP_FN,\n setEmoji: NOOP_FN,\n reset: NOOP_FN,\n };\n }\n\n const appBaseHref = inject(APP_BASE_HREF, { optional: true });\n const baseUrl = options?.baseUrl ?? appBaseHref ?? '';\n\n const getLinkElement = (): HTMLLinkElement => {\n let link = document.querySelector<HTMLLinkElement>('link[rel*=\"icon\"]');\n\n if (!link) {\n link = document.createElement('link');\n link.rel = 'icon';\n document.head.appendChild(link);\n }\n\n return link;\n };\n\n const { href = '' } = getLinkElement();\n const current = signal(href);\n const original = signal(href);\n\n const set = (url: string) => {\n const fullUrl = baseUrl + url;\n const linkEl = getLinkElement();\n linkEl.href = fullUrl;\n current.set(fullUrl);\n };\n\n const setEmoji = (emoji: string) => {\n const canvas = document.createElement('canvas');\n canvas.width = 32;\n canvas.height = 32;\n\n const ctx = canvas.getContext('2d');\n if (!ctx) return;\n\n ctx.font = '28px serif';\n ctx.textAlign = 'center';\n ctx.textBaseline = 'middle';\n ctx.fillText(emoji, 16, 18);\n\n const dataUrl = canvas.toDataURL('image/png');\n const linkEl = getLinkElement();\n linkEl.href = dataUrl;\n current.set(dataUrl);\n };\n\n const reset = () => {\n const linkEl = getLinkElement();\n const originalHref = untracked(original);\n linkEl.href = originalHref;\n current.set(originalHref);\n };\n\n return {\n current: current.asReadonly(),\n original: original.asReadonly(),\n set,\n setEmoji,\n reset,\n };\n });\n}\n\nexport const FAVICON = /* @__PURE__ */ createToken(favicon);\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-favicon.mjs","sources":["../../../projects/core/browser/favicon/index.ts","../../../projects/core/browser/favicon/signality-core-browser-favicon.ts"],"sourcesContent":["import { inject, type Signal, signal, untracked } from '@angular/core';\nimport { APP_BASE_HREF } from '@angular/common';\nimport { constSignal, createToken, NOOP_FN, setupContext } from '@signality/core/internal';\nimport type { WithInjector } from '@signality/core/types';\n\nexport interface FaviconOptions extends WithInjector {\n /**\n * Base URL prepended to all favicon paths passed to `set()`.\n *\n * Resolution priority:\n * 1. Explicit `baseUrl` value\n * 2. [`APP_BASE_HREF`](https://angular.dev/api/common/APP_BASE_HREF) token value (if configured)\n * 3. Empty string `''`\n */\n readonly baseUrl?: string;\n}\n\nexport interface FaviconRef {\n /**\n * URL of the currently active favicon.\n */\n readonly current: Signal<string>;\n\n /**\n * URL of the favicon at the time the utility was initialized.\n */\n readonly original: Signal<string>;\n\n /**\n * Set the favicon to the given URL.\n *\n * @see [HTMLLinkElement on MDN](https://developer.mozilla.org/en-US/docs/Web/API/HTMLLinkElement)\n */\n readonly set: (url: string) => void;\n\n /**\n * Render an emoji onto a canvas and use it as the favicon.\n *\n * @see [Canvas API on MDN](https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API)\n */\n readonly setEmoji: (emoji: string) => void;\n\n /**\n * Reset the favicon to the original URL captured on initialization.\n */\n readonly reset: () => void;\n}\n\n/**\n * Reactive favicon manipulation using the [HTMLLinkElement](https://developer.mozilla.org/en-US/docs/Web/API/HTMLLinkElement).\n * Dynamically change the page favicon based on application state.\n *\n * @param options - Optional configuration\n * @returns A FaviconRef with favicon control methods\n *\n * @example\n * ```typescript\n * @Component({\n * template: `\n * <button (click)=\"setNotification()\">Set Notification</button>\n * <button (click)=\"fav.reset()\">Reset Favicon</button>\n * <p>Current: {{ fav.current() }}</p>\n * `\n * })\n * export class FaviconDemo {\n * readonly fav = favicon();\n *\n * setNotification() {\n * this.fav.setEmoji('🔴');\n * }\n * }\n * ```\n */\nexport function favicon(options?: FaviconOptions): FaviconRef {\n const { runInContext } = setupContext(options?.injector, favicon);\n\n return runInContext(({ isServer }) => {\n if (isServer) {\n return {\n current: constSignal(''),\n original: constSignal(''),\n set: NOOP_FN,\n setEmoji: NOOP_FN,\n reset: NOOP_FN,\n };\n }\n\n const appBaseHref = inject(APP_BASE_HREF, { optional: true });\n const baseUrl = options?.baseUrl ?? appBaseHref ?? '';\n\n const getLinkElement = (): HTMLLinkElement => {\n let link = document.querySelector<HTMLLinkElement>('link[rel*=\"icon\"]');\n\n if (!link) {\n link = document.createElement('link');\n link.rel = 'icon';\n document.head.appendChild(link);\n }\n\n return link;\n };\n\n const { href = '' } = getLinkElement();\n const current = signal(href);\n const original = signal(href);\n\n const set = (url: string) => {\n const fullUrl = baseUrl + url;\n const linkEl = getLinkElement();\n linkEl.href = fullUrl;\n current.set(fullUrl);\n };\n\n const setEmoji = (emoji: string) => {\n const canvas = document.createElement('canvas');\n canvas.width = 32;\n canvas.height = 32;\n\n const ctx = canvas.getContext('2d');\n if (!ctx) return;\n\n ctx.font = '28px serif';\n ctx.textAlign = 'center';\n ctx.textBaseline = 'middle';\n ctx.fillText(emoji, 16, 18);\n\n const dataUrl = canvas.toDataURL('image/png');\n const linkEl = getLinkElement();\n linkEl.href = dataUrl;\n current.set(dataUrl);\n };\n\n const reset = () => {\n const linkEl = getLinkElement();\n const originalHref = untracked(original);\n linkEl.href = originalHref;\n current.set(originalHref);\n };\n\n return {\n current: current.asReadonly(),\n original: original.asReadonly(),\n set,\n setEmoji,\n reset,\n };\n });\n}\n\nexport const FAVICON = /* @__PURE__ */ createToken(favicon);\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,109 @@
1
+ import { signal, untracked, isSignal } from '@angular/core';
2
+ import { setupContext, NOOP_FN, toValue, isAcceptedFile } from '@signality/core/internal';
3
+ import { watcher } from '@signality/core/reactivity/watcher';
4
+
5
+ /**
6
+ * Signal-based utility for programmatically opening the native file picker dialog.
7
+ *
8
+ * @param options - Optional configuration
9
+ * @returns A {@link FileDialogRef} with `files` signal and `open` method
10
+ *
11
+ * @example
12
+ * ```typescript
13
+ * @Component({
14
+ * template: `
15
+ * <button (click)="fd.open()">Select Files</button>
16
+ * @for (file of fd.files(); track file.name) {
17
+ * <p>{{ file.name }} ({{ file.size }} bytes)</p>
18
+ * }
19
+ * `
20
+ * })
21
+ * export class FileUpload {
22
+ * readonly fd = fileDialog({ accept: 'image/*' });
23
+ * }
24
+ * ```
25
+ */
26
+ function fileDialog(options) {
27
+ const { runInContext } = setupContext(options?.injector, fileDialog);
28
+ return runInContext(({ isBrowser }) => {
29
+ if (!isBrowser) {
30
+ return {
31
+ files: signal([]),
32
+ open: NOOP_FN,
33
+ };
34
+ }
35
+ const accept = options?.accept ?? '*';
36
+ const multiple = options?.multiple ?? true;
37
+ const capture = options?.capture ?? '';
38
+ const directory = options?.directory ?? false;
39
+ const validatorFn = options?.validator;
40
+ const onReject = options?.onReject;
41
+ const files = signal([], ...(ngDevMode ? [{ debugName: "files" }] : []));
42
+ let inputEl = null;
43
+ const processFiles = (raw) => {
44
+ const accepted = [];
45
+ const rejected = [];
46
+ const acceptValue = toValue(accept);
47
+ const multipleValue = toValue(multiple);
48
+ const isAccepted = validatorFn
49
+ ? validatorFn
50
+ : (file) => isAcceptedFile(file, acceptValue);
51
+ for (const file of raw) {
52
+ if (isAccepted(file)) {
53
+ accepted.push(file);
54
+ if (!multipleValue) {
55
+ break;
56
+ }
57
+ }
58
+ else {
59
+ rejected.push(file);
60
+ }
61
+ }
62
+ if (onReject && rejected.length > 0) {
63
+ onReject(rejected);
64
+ }
65
+ files.set(accepted);
66
+ };
67
+ const createInput = () => {
68
+ const el = document.createElement('input');
69
+ el.type = 'file';
70
+ el.onchange = e => {
71
+ const fileList = e.currentTarget.files;
72
+ processFiles(fileList ? Array.from(fileList) : []);
73
+ };
74
+ return el;
75
+ };
76
+ const open = () => {
77
+ untracked(() => {
78
+ inputEl ??= createInput();
79
+ inputEl.value = '';
80
+ inputEl.multiple = toValue(multiple);
81
+ inputEl.accept = toValue(accept);
82
+ const directoriesOnly = toValue(directory);
83
+ if (directoriesOnly) {
84
+ inputEl.webkitdirectory = true;
85
+ }
86
+ const captureValue = toValue(capture);
87
+ if (captureValue) {
88
+ inputEl.capture = captureValue;
89
+ }
90
+ inputEl.click();
91
+ });
92
+ };
93
+ const filters = [accept, multiple, validatorFn].filter(isSignal);
94
+ if (filters.length) {
95
+ watcher(filters, () => processFiles(files()));
96
+ }
97
+ return {
98
+ files,
99
+ open,
100
+ };
101
+ });
102
+ }
103
+
104
+ /**
105
+ * Generated bundle index. Do not edit.
106
+ */
107
+
108
+ export { fileDialog };
109
+ //# sourceMappingURL=signality-core-browser-file-dialog.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"signality-core-browser-file-dialog.mjs","sources":["../../../projects/core/browser/file-dialog/index.ts","../../../projects/core/browser/file-dialog/signality-core-browser-file-dialog.ts"],"sourcesContent":["import { isSignal, type Signal, signal, untracked, type WritableSignal } from '@angular/core';\nimport { isAcceptedFile, NOOP_FN, setupContext, toValue } from '@signality/core/internal';\nimport type { MaybeSignal, WithInjector } from '@signality/core/types';\nimport { watcher } from '@signality/core/reactivity/watcher';\n\nexport interface FileDialogOptions extends WithInjector {\n /**\n * Whether to allow selecting multiple files.\n * When changed reactively, the current file list is re-filtered.\n * @default true\n */\n readonly multiple?: MaybeSignal<boolean>;\n\n /**\n * Comma-separated list of accepted file types, matching the native HTML\n * [`accept`](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Attributes/accept) attribute format.\n * Supports MIME types (`'image/png'`), wildcards (`'image/*'`), and file extensions (`'.pdf'`).\n * When changed reactively, the current file list is re-filtered.\n *\n * @default '*'\n * @see [accept attribute on MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Attributes/accept)\n */\n readonly accept?: MaybeSignal<string>;\n\n /**\n * Capture source for mobile devices: `'user'` (front camera) or `'environment'` (rear camera).\n * @see [capture attribute on MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/capture)\n */\n readonly capture?: MaybeSignal<string>;\n\n /**\n * Whether to select directories instead of files.\n * Uses the non-standard `webkitdirectory` attribute.\n * @default false\n */\n readonly directory?: MaybeSignal<boolean>;\n\n /**\n * Custom validation predicate called for each selected file.\n * Return `true` to keep the file, `false` to reject it.\n *\n * When provided, the `accept` option is ignored — the validator\n * takes full responsibility for deciding which files are valid.\n *\n * @example\n * ```typescript\n * fileDialog({\n * validator: (file) => file.size <= 5 * 1024 * 1024, // max 5 MB\n * });\n * ```\n */\n readonly validator?: (file: File) => boolean;\n\n /**\n * Callback invoked with files that were rejected during selection.\n * Useful for showing toast notifications or validation errors.\n *\n * @example\n * ```typescript\n * fileDialog({\n * accept: 'image/*',\n * onReject: (rejected) => {\n * rejected.forEach(f => toast.error(`${f.name} is not valid`));\n * },\n * });\n * ```\n */\n readonly onReject?: (files: File[]) => void;\n}\n\nexport interface FileDialogRef {\n /**\n * List of files selected via the file dialog.\n * A `WritableSignal` — can be reset externally (e.g. `files.set([])`).\n * Re-filtered automatically when `accept` or `multiple` options change reactively.\n *\n * @see [File API on MDN](https://developer.mozilla.org/en-US/docs/Web/API/File)\n */\n readonly files: WritableSignal<File[]>;\n\n /**\n * Open the native file picker dialog.\n */\n readonly open: () => void;\n}\n\n/**\n * Signal-based utility for programmatically opening the native file picker dialog.\n *\n * @param options - Optional configuration\n * @returns A {@link FileDialogRef} with `files` signal and `open` method\n *\n * @example\n * ```typescript\n * @Component({\n * template: `\n * <button (click)=\"fd.open()\">Select Files</button>\n * @for (file of fd.files(); track file.name) {\n * <p>{{ file.name }} ({{ file.size }} bytes)</p>\n * }\n * `\n * })\n * export class FileUpload {\n * readonly fd = fileDialog({ accept: 'image/*' });\n * }\n * ```\n */\nexport function fileDialog(options?: FileDialogOptions): FileDialogRef {\n const { runInContext } = setupContext(options?.injector, fileDialog);\n\n return runInContext(({ isBrowser }) => {\n if (!isBrowser) {\n return {\n files: signal<File[]>([]),\n open: NOOP_FN,\n };\n }\n\n const accept = options?.accept ?? '*';\n const multiple = options?.multiple ?? true;\n const capture = options?.capture ?? '';\n const directory = options?.directory ?? false;\n const validatorFn = options?.validator;\n const onReject = options?.onReject;\n\n const files = signal<File[]>([]);\n\n let inputEl: HTMLInputElement | null = null;\n\n const processFiles = (raw: File[]) => {\n const accepted: File[] = [];\n const rejected: File[] = [];\n const acceptValue = toValue(accept);\n const multipleValue = toValue(multiple);\n\n const isAccepted = validatorFn\n ? validatorFn\n : (file: File) => isAcceptedFile(file, acceptValue);\n\n for (const file of raw) {\n if (isAccepted(file)) {\n accepted.push(file);\n if (!multipleValue) {\n break;\n }\n } else {\n rejected.push(file);\n }\n }\n\n if (onReject && rejected.length > 0) {\n onReject(rejected);\n }\n\n files.set(accepted);\n };\n\n const createInput = (): HTMLInputElement => {\n const el = document.createElement('input');\n el.type = 'file';\n el.onchange = e => {\n const fileList = (e.currentTarget as HTMLInputElement).files;\n processFiles(fileList ? Array.from(fileList) : []);\n };\n return el;\n };\n\n const open = (): void => {\n untracked(() => {\n inputEl ??= createInput();\n\n inputEl.value = '';\n inputEl.multiple = toValue(multiple);\n inputEl.accept = toValue(accept);\n\n const directoriesOnly = toValue(directory);\n if (directoriesOnly) {\n inputEl.webkitdirectory = true;\n }\n\n const captureValue = toValue(capture);\n if (captureValue) {\n inputEl.capture = captureValue;\n }\n\n inputEl.click();\n });\n };\n\n const filters = [accept, multiple, validatorFn].filter(isSignal) as Signal<any>[];\n\n if (filters.length) {\n watcher(filters, () => processFiles(files()));\n }\n\n return {\n files,\n open,\n };\n });\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
@@ -16,7 +16,7 @@ import { setupContext, NOOP_FN, constSignal, createToken } from '@signality/core
16
16
  * <button (click)="fpsMonitor.start()">Start</button>
17
17
  * `
18
18
  * })
19
- * class FpsComponent {
19
+ * export class FpsMonitor {
20
20
  * readonly fpsMonitor = fps();
21
21
  * }
22
22
  * ```
@@ -1 +1 @@
1
- {"version":3,"file":"signality-core-browser-fps.mjs","sources":["../../../projects/core/browser/fps/index.ts","../../../projects/core/browser/fps/signality-core-browser-fps.ts"],"sourcesContent":["import { type Signal, signal, untracked } from '@angular/core';\nimport { constSignal, createToken, NOOP_FN, setupContext } from '@signality/core/internal';\nimport type { WithInjector } from '@signality/core/types';\n\nexport interface FpsOptions extends WithInjector {\n /**\n * Start monitoring immediately.\n * @default true\n */\n readonly immediate?: boolean;\n\n /**\n * Number of frames to average.\n * @default 60\n */\n readonly sampleSize?: number;\n}\n\nexport interface FpsRef {\n /** Current frames per second */\n readonly fps: Signal<number>;\n\n /** Whether monitoring is active */\n readonly isRunning: Signal<boolean>;\n\n /** Start FPS monitoring */\n readonly start: () => void;\n\n /** Stop FPS monitoring */\n readonly stop: () => void;\n}\n\n/**\n * Reactive FPS monitor using requestAnimationFrame.\n *\n * @param options - Configuration options\n * @returns An FpsRef with fps signal and control methods\n *\n * @example\n * ```typescript\n * @Component({\n * template: `\n * <p>FPS: {{ fpsMonitor.fps() }}</p>\n * <button (click)=\"fpsMonitor.stop()\">Stop</button>\n * <button (click)=\"fpsMonitor.start()\">Start</button>\n * `\n * })\n * class FpsComponent {\n * readonly fpsMonitor = fps();\n * }\n * ```\n */\nexport function fps(options?: FpsOptions): FpsRef {\n const { runInContext } = setupContext(options?.injector, fps);\n\n return runInContext(({ isServer, onCleanup }) => {\n if (isServer) {\n return {\n fps: constSignal(0),\n isRunning: constSignal(false),\n start: NOOP_FN,\n stop: NOOP_FN,\n };\n }\n\n const immediate = options?.immediate ?? true;\n const sampleSize = options?.sampleSize ?? 60;\n\n const fpsValue = signal(0);\n const isRunning = signal(false);\n\n let frameId: number | undefined;\n let lastTime = performance.now();\n let frameCount = 0;\n let frameTimes: number[] = [];\n\n const loop = (currentTime: number) => {\n const delta = currentTime - lastTime;\n lastTime = currentTime;\n\n if (delta > 0) {\n frameTimes.push(1000 / delta);\n\n if (frameTimes.length > sampleSize) {\n frameTimes.shift();\n }\n\n frameCount++;\n\n if (frameCount >= 10) {\n const avgFps = frameTimes.reduce((a, b) => a + b, 0) / frameTimes.length;\n fpsValue.set(Math.round(avgFps));\n frameCount = 0;\n }\n }\n\n if (untracked(isRunning)) {\n frameId = requestAnimationFrame(loop);\n }\n };\n\n const start = () => {\n const running = untracked(isRunning);\n\n if (running) {\n return;\n }\n\n isRunning.set(true);\n lastTime = performance.now();\n frameCount = 0;\n frameTimes = [];\n frameId = requestAnimationFrame(loop);\n };\n\n const stop = () => {\n const running = untracked(isRunning);\n\n if (!running) {\n return;\n }\n\n isRunning.set(false);\n\n if (frameId !== undefined) {\n cancelAnimationFrame(frameId);\n frameId = undefined;\n }\n };\n\n if (immediate) {\n start();\n }\n\n onCleanup(stop);\n\n return {\n fps: fpsValue.asReadonly(),\n isRunning: isRunning.asReadonly(),\n start,\n stop,\n };\n });\n}\n\nexport const FPS = /* @__PURE__ */ createToken(fps);\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-fps.mjs","sources":["../../../projects/core/browser/fps/index.ts","../../../projects/core/browser/fps/signality-core-browser-fps.ts"],"sourcesContent":["import { type Signal, signal, untracked } from '@angular/core';\nimport { constSignal, createToken, NOOP_FN, setupContext } from '@signality/core/internal';\nimport type { WithInjector } from '@signality/core/types';\n\nexport interface FpsOptions extends WithInjector {\n /**\n * Start monitoring immediately.\n * @default true\n */\n readonly immediate?: boolean;\n\n /**\n * Number of frames to average.\n * @default 60\n */\n readonly sampleSize?: number;\n}\n\nexport interface FpsRef {\n /** Current frames per second */\n readonly fps: Signal<number>;\n\n /** Whether monitoring is active */\n readonly isRunning: Signal<boolean>;\n\n /** Start FPS monitoring */\n readonly start: () => void;\n\n /** Stop FPS monitoring */\n readonly stop: () => void;\n}\n\n/**\n * Reactive FPS monitor using requestAnimationFrame.\n *\n * @param options - Configuration options\n * @returns An FpsRef with fps signal and control methods\n *\n * @example\n * ```typescript\n * @Component({\n * template: `\n * <p>FPS: {{ fpsMonitor.fps() }}</p>\n * <button (click)=\"fpsMonitor.stop()\">Stop</button>\n * <button (click)=\"fpsMonitor.start()\">Start</button>\n * `\n * })\n * export class FpsMonitor {\n * readonly fpsMonitor = fps();\n * }\n * ```\n */\nexport function fps(options?: FpsOptions): FpsRef {\n const { runInContext } = setupContext(options?.injector, fps);\n\n return runInContext(({ isServer, onCleanup }) => {\n if (isServer) {\n return {\n fps: constSignal(0),\n isRunning: constSignal(false),\n start: NOOP_FN,\n stop: NOOP_FN,\n };\n }\n\n const immediate = options?.immediate ?? true;\n const sampleSize = options?.sampleSize ?? 60;\n\n const fpsValue = signal(0);\n const isRunning = signal(false);\n\n let frameId: number | undefined;\n let lastTime = performance.now();\n let frameCount = 0;\n let frameTimes: number[] = [];\n\n const loop = (currentTime: number) => {\n const delta = currentTime - lastTime;\n lastTime = currentTime;\n\n if (delta > 0) {\n frameTimes.push(1000 / delta);\n\n if (frameTimes.length > sampleSize) {\n frameTimes.shift();\n }\n\n frameCount++;\n\n if (frameCount >= 10) {\n const avgFps = frameTimes.reduce((a, b) => a + b, 0) / frameTimes.length;\n fpsValue.set(Math.round(avgFps));\n frameCount = 0;\n }\n }\n\n if (untracked(isRunning)) {\n frameId = requestAnimationFrame(loop);\n }\n };\n\n const start = () => {\n const running = untracked(isRunning);\n\n if (running) {\n return;\n }\n\n isRunning.set(true);\n lastTime = performance.now();\n frameCount = 0;\n frameTimes = [];\n frameId = requestAnimationFrame(loop);\n };\n\n const stop = () => {\n const running = untracked(isRunning);\n\n if (!running) {\n return;\n }\n\n isRunning.set(false);\n\n if (frameId !== undefined) {\n cancelAnimationFrame(frameId);\n frameId = undefined;\n }\n };\n\n if (immediate) {\n start();\n }\n\n onCleanup(stop);\n\n return {\n fps: fpsValue.asReadonly(),\n isRunning: isRunning.asReadonly(),\n start,\n stop,\n };\n });\n}\n\nexport const FPS = /* @__PURE__ */ createToken(fps);\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;AAgCA;;;;;;;;;;;;;;;;;;;AAmBG;AACG,SAAU,GAAG,CAAC,OAAoB,EAAA;AACtC,IAAA,MAAM,EAAE,YAAY,EAAE,GAAG,YAAY,CAAC,OAAO,EAAE,QAAQ,EAAE,GAAG,CAAC;IAE7D,OAAO,YAAY,CAAC,CAAC,EAAE,QAAQ,EAAE,SAAS,EAAE,KAAI;QAC9C,IAAI,QAAQ,EAAE;YACZ,OAAO;AACL,gBAAA,GAAG,EAAE,WAAW,CAAC,CAAC,CAAC;AACnB,gBAAA,SAAS,EAAE,WAAW,CAAC,KAAK,CAAC;AAC7B,gBAAA,KAAK,EAAE,OAAO;AACd,gBAAA,IAAI,EAAE,OAAO;aACd;QACH;AAEA,QAAA,MAAM,SAAS,GAAG,OAAO,EAAE,SAAS,IAAI,IAAI;AAC5C,QAAA,MAAM,UAAU,GAAG,OAAO,EAAE,UAAU,IAAI,EAAE;AAE5C,QAAA,MAAM,QAAQ,GAAG,MAAM,CAAC,CAAC,oDAAC;AAC1B,QAAA,MAAM,SAAS,GAAG,MAAM,CAAC,KAAK,qDAAC;AAE/B,QAAA,IAAI,OAA2B;AAC/B,QAAA,IAAI,QAAQ,GAAG,WAAW,CAAC,GAAG,EAAE;QAChC,IAAI,UAAU,GAAG,CAAC;QAClB,IAAI,UAAU,GAAa,EAAE;AAE7B,QAAA,MAAM,IAAI,GAAG,CAAC,WAAmB,KAAI;AACnC,YAAA,MAAM,KAAK,GAAG,WAAW,GAAG,QAAQ;YACpC,QAAQ,GAAG,WAAW;AAEtB,YAAA,IAAI,KAAK,GAAG,CAAC,EAAE;AACb,gBAAA,UAAU,CAAC,IAAI,CAAC,IAAI,GAAG,KAAK,CAAC;AAE7B,gBAAA,IAAI,UAAU,CAAC,MAAM,GAAG,UAAU,EAAE;oBAClC,UAAU,CAAC,KAAK,EAAE;gBACpB;AAEA,gBAAA,UAAU,EAAE;AAEZ,gBAAA,IAAI,UAAU,IAAI,EAAE,EAAE;oBACpB,MAAM,MAAM,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,GAAG,UAAU,CAAC,MAAM;oBACxE,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;oBAChC,UAAU,GAAG,CAAC;gBAChB;YACF;AAEA,YAAA,IAAI,SAAS,CAAC,SAAS,CAAC,EAAE;AACxB,gBAAA,OAAO,GAAG,qBAAqB,CAAC,IAAI,CAAC;YACvC;AACF,QAAA,CAAC;QAED,MAAM,KAAK,GAAG,MAAK;AACjB,YAAA,MAAM,OAAO,GAAG,SAAS,CAAC,SAAS,CAAC;YAEpC,IAAI,OAAO,EAAE;gBACX;YACF;AAEA,YAAA,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC;AACnB,YAAA,QAAQ,GAAG,WAAW,CAAC,GAAG,EAAE;YAC5B,UAAU,GAAG,CAAC;YACd,UAAU,GAAG,EAAE;AACf,YAAA,OAAO,GAAG,qBAAqB,CAAC,IAAI,CAAC;AACvC,QAAA,CAAC;QAED,MAAM,IAAI,GAAG,MAAK;AAChB,YAAA,MAAM,OAAO,GAAG,SAAS,CAAC,SAAS,CAAC;YAEpC,IAAI,CAAC,OAAO,EAAE;gBACZ;YACF;AAEA,YAAA,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC;AAEpB,YAAA,IAAI,OAAO,KAAK,SAAS,EAAE;gBACzB,oBAAoB,CAAC,OAAO,CAAC;gBAC7B,OAAO,GAAG,SAAS;YACrB;AACF,QAAA,CAAC;QAED,IAAI,SAAS,EAAE;AACb,YAAA,KAAK,EAAE;QACT;QAEA,SAAS,CAAC,IAAI,CAAC;QAEf,OAAO;AACL,YAAA,GAAG,EAAE,QAAQ,CAAC,UAAU,EAAE;AAC1B,YAAA,SAAS,EAAE,SAAS,CAAC,UAAU,EAAE;YACjC,KAAK;YACL,IAAI;SACL;AACH,IAAA,CAAC,CAAC;AACJ;AAEO,MAAM,GAAG,mBAAmB,WAAW,CAAC,GAAG;;ACjJlD;;AAEG;;;;"}
@@ -0,0 +1,113 @@
1
+ import { signal, untracked } from '@angular/core';
2
+ import { setupContext, constSignal, NOOP_ASYNC_FN, toElement } from '@signality/core/internal';
3
+ import { setupSync, listener } from '@signality/core/browser/listener';
4
+
5
+ /**
6
+ * Signal-based wrapper around the [Fullscreen API](https://developer.mozilla.org/en-US/docs/Web/API/Fullscreen_API).
7
+ *
8
+ * @param options - Optional configuration including target element and injector
9
+ * @returns A {@link FullscreenRef} with `isSupported`, `isActive` signals and `enter`/`exit`/`toggle` methods
10
+ *
11
+ * @example
12
+ * ```typescript
13
+ * @Component({
14
+ * template: `
15
+ * @if (fs.isSupported()) {
16
+ * <button (click)="fs.toggle()">Toggle Fullscreen</button>
17
+ * <p>Active: {{ fs.isActive() }}</p>
18
+ * }
19
+ * `
20
+ * })
21
+ * export class FullscreenDemo {
22
+ * readonly fs = fullscreen();
23
+ * }
24
+ * ```
25
+ *
26
+ * @example
27
+ * ```typescript
28
+ * // Fullscreen a specific element
29
+ * @Component({
30
+ * template: `
31
+ * <div #container>
32
+ * <p>This content can go fullscreen</p>
33
+ * <button (click)="fs.toggle()">Toggle</button>
34
+ * </div>
35
+ * `
36
+ * })
37
+ * export class ElementFullscreen {
38
+ * readonly container = viewChild<ElementRef>('container');
39
+ * readonly fs = fullscreen({ target: this.container });
40
+ * }
41
+ * ```
42
+ */
43
+ function fullscreen(options) {
44
+ const { runInContext } = setupContext(options?.injector, fullscreen);
45
+ return runInContext(({ isBrowser }) => {
46
+ const isSupported = constSignal(isBrowser && 'fullscreenEnabled' in document && document.fullscreenEnabled);
47
+ if (!isSupported()) {
48
+ return {
49
+ isSupported,
50
+ isActive: constSignal(false),
51
+ enter: NOOP_ASYNC_FN,
52
+ exit: NOOP_ASYNC_FN,
53
+ toggle: NOOP_ASYNC_FN,
54
+ };
55
+ }
56
+ const target = options?.target ?? document.documentElement;
57
+ const isActive = signal(false, ...(ngDevMode ? [{ debugName: "isActive" }] : []));
58
+ const enter = async () => {
59
+ const el = toElement.untracked(target);
60
+ if (el && document.fullscreenElement !== el) {
61
+ try {
62
+ await el.requestFullscreen();
63
+ }
64
+ catch (error) {
65
+ if (ngDevMode) {
66
+ console.warn(`[fullscreen] Failed to enter fullscreen mode.`, error);
67
+ }
68
+ }
69
+ }
70
+ };
71
+ const exit = async () => {
72
+ const el = toElement.untracked(target);
73
+ if (el && document.fullscreenElement === el) {
74
+ try {
75
+ await document.exitFullscreen();
76
+ }
77
+ catch (error) {
78
+ if (ngDevMode) {
79
+ console.warn(`[fullscreen] Failed to exit fullscreen mode.`, error);
80
+ }
81
+ }
82
+ }
83
+ };
84
+ const toggle = async () => {
85
+ if (untracked(isActive)) {
86
+ await exit();
87
+ }
88
+ else {
89
+ await enter();
90
+ }
91
+ };
92
+ setupSync(() => {
93
+ listener(document, 'fullscreenchange', () => {
94
+ const el = toElement.untracked(target);
95
+ isActive.set(document.fullscreenElement != null && document.fullscreenElement === el);
96
+ });
97
+ });
98
+ return {
99
+ isSupported,
100
+ isActive: isActive.asReadonly(),
101
+ enter,
102
+ exit,
103
+ toggle,
104
+ };
105
+ });
106
+ }
107
+
108
+ /**
109
+ * Generated bundle index. Do not edit.
110
+ */
111
+
112
+ export { fullscreen };
113
+ //# sourceMappingURL=signality-core-browser-fullscreen.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"signality-core-browser-fullscreen.mjs","sources":["../../../projects/core/browser/fullscreen/index.ts","../../../projects/core/browser/fullscreen/signality-core-browser-fullscreen.ts"],"sourcesContent":["import { signal, type Signal, untracked } from '@angular/core';\nimport { constSignal, NOOP_ASYNC_FN, setupContext, toElement } from '@signality/core/internal';\nimport type { MaybeElementSignal, WithInjector } from '@signality/core/types';\nimport { listener, setupSync } from '@signality/core/browser/listener';\n\nexport interface FullscreenOptions extends WithInjector {\n /**\n * Element to make fullscreen.\n * @default document.documentElement\n */\n readonly target?: MaybeElementSignal<Element>;\n}\n\nexport interface FullscreenRef {\n /**\n * Whether the Fullscreen API is supported in the current browser.\n *\n * @see [Fullscreen API browser compatibility on MDN](https://developer.mozilla.org/en-US/docs/Web/API/Fullscreen_API#browser_compatibility)\n */\n readonly isSupported: Signal<boolean>;\n\n /**\n * Whether the target element is currently displayed in fullscreen mode.\n *\n * @see [Document: fullscreenElement on MDN](https://developer.mozilla.org/en-US/docs/Web/API/Document/fullscreenElement)\n */\n readonly isActive: Signal<boolean>;\n\n /**\n * Enter fullscreen mode for the target element.\n *\n * @see [Element: requestFullscreen() on MDN](https://developer.mozilla.org/en-US/docs/Web/API/Element/requestFullscreen)\n */\n readonly enter: () => Promise<void>;\n\n /**\n * Exit fullscreen mode.\n *\n * @see [Document: exitFullscreen() on MDN](https://developer.mozilla.org/en-US/docs/Web/API/Document/exitFullscreen)\n */\n readonly exit: () => Promise<void>;\n\n /**\n * Toggle fullscreen mode — enters if inactive, exits if active.\n */\n readonly toggle: () => Promise<void>;\n}\n\n/**\n * Signal-based wrapper around the [Fullscreen API](https://developer.mozilla.org/en-US/docs/Web/API/Fullscreen_API).\n *\n * @param options - Optional configuration including target element and injector\n * @returns A {@link FullscreenRef} with `isSupported`, `isActive` signals and `enter`/`exit`/`toggle` methods\n *\n * @example\n * ```typescript\n * @Component({\n * template: `\n * @if (fs.isSupported()) {\n * <button (click)=\"fs.toggle()\">Toggle Fullscreen</button>\n * <p>Active: {{ fs.isActive() }}</p>\n * }\n * `\n * })\n * export class FullscreenDemo {\n * readonly fs = fullscreen();\n * }\n * ```\n *\n * @example\n * ```typescript\n * // Fullscreen a specific element\n * @Component({\n * template: `\n * <div #container>\n * <p>This content can go fullscreen</p>\n * <button (click)=\"fs.toggle()\">Toggle</button>\n * </div>\n * `\n * })\n * export class ElementFullscreen {\n * readonly container = viewChild<ElementRef>('container');\n * readonly fs = fullscreen({ target: this.container });\n * }\n * ```\n */\nexport function fullscreen(options?: FullscreenOptions): FullscreenRef {\n const { runInContext } = setupContext(options?.injector, fullscreen);\n\n return runInContext(({ isBrowser }) => {\n const isSupported = constSignal(\n isBrowser && 'fullscreenEnabled' in document && document.fullscreenEnabled\n );\n\n if (!isSupported()) {\n return {\n isSupported,\n isActive: constSignal(false),\n enter: NOOP_ASYNC_FN,\n exit: NOOP_ASYNC_FN,\n toggle: NOOP_ASYNC_FN,\n };\n }\n\n const target = options?.target ?? document.documentElement;\n\n const isActive = signal(false);\n\n const enter = async (): Promise<void> => {\n const el = toElement.untracked(target);\n\n if (el && document.fullscreenElement !== el) {\n try {\n await el.requestFullscreen();\n } catch (error) {\n if (ngDevMode) {\n console.warn(`[fullscreen] Failed to enter fullscreen mode.`, error);\n }\n }\n }\n };\n\n const exit = async (): Promise<void> => {\n const el = toElement.untracked(target);\n\n if (el && document.fullscreenElement === el) {\n try {\n await document.exitFullscreen();\n } catch (error) {\n if (ngDevMode) {\n console.warn(`[fullscreen] Failed to exit fullscreen mode.`, error);\n }\n }\n }\n };\n\n const toggle = async (): Promise<void> => {\n if (untracked(isActive)) {\n await exit();\n } else {\n await enter();\n }\n };\n\n setupSync(() => {\n listener(document, 'fullscreenchange', () => {\n const el = toElement.untracked(target);\n isActive.set(document.fullscreenElement != null && document.fullscreenElement === el);\n });\n });\n\n return {\n isSupported,\n isActive: isActive.asReadonly(),\n enter,\n exit,\n toggle,\n };\n });\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;;AAgDA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqCG;AACG,SAAU,UAAU,CAAC,OAA2B,EAAA;AACpD,IAAA,MAAM,EAAE,YAAY,EAAE,GAAG,YAAY,CAAC,OAAO,EAAE,QAAQ,EAAE,UAAU,CAAC;AAEpE,IAAA,OAAO,YAAY,CAAC,CAAC,EAAE,SAAS,EAAE,KAAI;AACpC,QAAA,MAAM,WAAW,GAAG,WAAW,CAC7B,SAAS,IAAI,mBAAmB,IAAI,QAAQ,IAAI,QAAQ,CAAC,iBAAiB,CAC3E;AAED,QAAA,IAAI,CAAC,WAAW,EAAE,EAAE;YAClB,OAAO;gBACL,WAAW;AACX,gBAAA,QAAQ,EAAE,WAAW,CAAC,KAAK,CAAC;AAC5B,gBAAA,KAAK,EAAE,aAAa;AACpB,gBAAA,IAAI,EAAE,aAAa;AACnB,gBAAA,MAAM,EAAE,aAAa;aACtB;QACH;QAEA,MAAM,MAAM,GAAG,OAAO,EAAE,MAAM,IAAI,QAAQ,CAAC,eAAe;AAE1D,QAAA,MAAM,QAAQ,GAAG,MAAM,CAAC,KAAK,oDAAC;AAE9B,QAAA,MAAM,KAAK,GAAG,YAA0B;YACtC,MAAM,EAAE,GAAG,SAAS,CAAC,SAAS,CAAC,MAAM,CAAC;YAEtC,IAAI,EAAE,IAAI,QAAQ,CAAC,iBAAiB,KAAK,EAAE,EAAE;AAC3C,gBAAA,IAAI;AACF,oBAAA,MAAM,EAAE,CAAC,iBAAiB,EAAE;gBAC9B;gBAAE,OAAO,KAAK,EAAE;oBACd,IAAI,SAAS,EAAE;AACb,wBAAA,OAAO,CAAC,IAAI,CAAC,+CAA+C,EAAE,KAAK,CAAC;oBACtE;gBACF;YACF;AACF,QAAA,CAAC;AAED,QAAA,MAAM,IAAI,GAAG,YAA0B;YACrC,MAAM,EAAE,GAAG,SAAS,CAAC,SAAS,CAAC,MAAM,CAAC;YAEtC,IAAI,EAAE,IAAI,QAAQ,CAAC,iBAAiB,KAAK,EAAE,EAAE;AAC3C,gBAAA,IAAI;AACF,oBAAA,MAAM,QAAQ,CAAC,cAAc,EAAE;gBACjC;gBAAE,OAAO,KAAK,EAAE;oBACd,IAAI,SAAS,EAAE;AACb,wBAAA,OAAO,CAAC,IAAI,CAAC,8CAA8C,EAAE,KAAK,CAAC;oBACrE;gBACF;YACF;AACF,QAAA,CAAC;AAED,QAAA,MAAM,MAAM,GAAG,YAA0B;AACvC,YAAA,IAAI,SAAS,CAAC,QAAQ,CAAC,EAAE;gBACvB,MAAM,IAAI,EAAE;YACd;iBAAO;gBACL,MAAM,KAAK,EAAE;YACf;AACF,QAAA,CAAC;QAED,SAAS,CAAC,MAAK;AACb,YAAA,QAAQ,CAAC,QAAQ,EAAE,kBAAkB,EAAE,MAAK;gBAC1C,MAAM,EAAE,GAAG,SAAS,CAAC,SAAS,CAAC,MAAM,CAAC;AACtC,gBAAA,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,iBAAiB,IAAI,IAAI,IAAI,QAAQ,CAAC,iBAAiB,KAAK,EAAE,CAAC;AACvF,YAAA,CAAC,CAAC;AACJ,QAAA,CAAC,CAAC;QAEF,OAAO;YACL,WAAW;AACX,YAAA,QAAQ,EAAE,QAAQ,CAAC,UAAU,EAAE;YAC/B,KAAK;YACL,IAAI;YACJ,MAAM;SACP;AACH,IAAA,CAAC,CAAC;AACJ;;AC/JA;;AAEG;;;;"}
@@ -3,16 +3,26 @@ import { setupContext, constSignal } from '@signality/core/internal';
3
3
  import { setupSync, listener } from '@signality/core/browser/listener';
4
4
 
5
5
  /**
6
- * Signal-based wrapper around the Gamepad API.
6
+ * Signal-based wrapper around the [Gamepad API](https://developer.mozilla.org/en-US/docs/Web/API/Gamepad_API).
7
7
  *
8
8
  * @param options - Optional injector for DI context
9
9
  * @returns A GamepadRef with gamepad state signals
10
10
  *
11
11
  * @example
12
12
  * ```typescript
13
- * const gp = gamepad();
14
- *
15
- * // In template: @for (pad of gp.gamepads(); track pad?.index) { ... }
13
+ * @Component({
14
+ * template: `
15
+ * @if (gp.isSupported()) {
16
+ * @for (pad of gp.gamepads(); track pad?.index) {
17
+ * <p>{{ pad?.id }}</p>
18
+ * }
19
+ * <p>Axes: {{ gp.axes() }}</p>
20
+ * }
21
+ * `
22
+ * })
23
+ * export class GamepadDemo {
24
+ * readonly gp = gamepad();
25
+ * }
16
26
  * ```
17
27
  */
18
28
  function gamepad(options) {
@@ -1 +1 @@
1
- {"version":3,"file":"signality-core-browser-gamepad.mjs","sources":["../../../projects/core/browser/gamepad/index.ts","../../../projects/core/browser/gamepad/signality-core-browser-gamepad.ts"],"sourcesContent":["import { computed, signal, type Signal } from '@angular/core';\nimport { constSignal, setupContext } from '@signality/core/internal';\nimport type { WithInjector } from '@signality/core/types';\nimport { listener, setupSync } from '@signality/core/browser/listener';\n\nexport interface GamepadRef {\n /** Whether Gamepad API is supported */\n readonly isSupported: Signal<boolean>;\n\n /** Array of connected gamepads */\n readonly gamepads: Signal<(Gamepad | null)[]>;\n\n /** First connected gamepad */\n readonly activeGamepad: Signal<Gamepad | undefined>;\n\n /** Axes values of active gamepad */\n readonly axes: Signal<readonly number[]>;\n\n /** Button states of active gamepad */\n readonly buttons: Signal<readonly GamepadButton[]>;\n}\n\n/**\n * Signal-based wrapper around the Gamepad API.\n *\n * @param options - Optional injector for DI context\n * @returns A GamepadRef with gamepad state signals\n *\n * @example\n * ```typescript\n * const gp = gamepad();\n *\n * // In template: @for (pad of gp.gamepads(); track pad?.index) { ... }\n * ```\n */\nexport function gamepad(options?: WithInjector): GamepadRef {\n const { runInContext } = setupContext(options?.injector, gamepad);\n\n return runInContext(({ onCleanup, isBrowser }) => {\n const isSupported = constSignal(isBrowser && 'getGamepads' in navigator);\n\n if (!isSupported()) {\n return {\n isSupported,\n gamepads: constSignal([]),\n activeGamepad: constSignal(undefined),\n axes: constSignal([]),\n buttons: constSignal([]),\n };\n }\n\n const getGamepads = () => {\n try {\n const pads = navigator.getGamepads();\n return [...pads];\n } catch (error) {\n if (ngDevMode) {\n console.warn(`[gamepad] Failed to get gamepads.`, error);\n }\n return [];\n }\n };\n\n const gamepads = signal<(Gamepad | null)[]>(getGamepads());\n const activeGamepad = computed(() => gamepads().find(gp => gp !== null) ?? undefined);\n const axes = computed(() => activeGamepad()?.axes ?? []);\n const buttons = computed(() => activeGamepad()?.buttons ?? []);\n\n let animationFrameId: number | null = null;\n\n const pollGamepads = () => {\n gamepads.set(getGamepads());\n animationFrameId = requestAnimationFrame(pollGamepads);\n };\n\n const onGamepadConnected = () => {\n gamepads.set(getGamepads());\n\n if (animationFrameId === null) {\n pollGamepads();\n }\n };\n\n const onGamepadDisconnected = () => {\n gamepads.set(getGamepads());\n\n const hasGamepads = gamepads().some(gp => gp !== null);\n\n if (!hasGamepads && animationFrameId !== null) {\n cancelAnimationFrame(animationFrameId);\n animationFrameId = null;\n }\n };\n\n setupSync(() => {\n listener.passive(window, 'gamepadconnected', onGamepadConnected);\n listener.passive(window, 'gamepaddisconnected', onGamepadDisconnected);\n });\n\n if (gamepads().some(gp => gp !== null)) {\n pollGamepads();\n }\n\n onCleanup(() => {\n if (animationFrameId !== null) {\n cancelAnimationFrame(animationFrameId);\n }\n });\n\n return {\n isSupported,\n gamepads: gamepads.asReadonly(),\n activeGamepad,\n axes,\n buttons,\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-gamepad.mjs","sources":["../../../projects/core/browser/gamepad/index.ts","../../../projects/core/browser/gamepad/signality-core-browser-gamepad.ts"],"sourcesContent":["import { computed, signal, type Signal } from '@angular/core';\nimport { constSignal, setupContext } from '@signality/core/internal';\nimport type { WithInjector } from '@signality/core/types';\nimport { listener, setupSync } from '@signality/core/browser/listener';\n\nexport interface GamepadRef {\n /**\n * Whether the Gamepad API is supported in the current browser.\n *\n * @see [Gamepad API browser compatibility on MDN](https://developer.mozilla.org/en-US/docs/Web/API/Gamepad_API#browser_compatibility)\n */\n readonly isSupported: Signal<boolean>;\n\n /**\n * Array of all connected gamepads. Indices match the gamepad's `index` property. May contain `null` for disconnected slots.\n *\n * @see [Navigator: getGamepads() on MDN](https://developer.mozilla.org/en-US/docs/Web/API/Navigator/getGamepads)\n */\n readonly gamepads: Signal<(Gamepad | null)[]>;\n\n /**\n * The first connected gamepad, or `undefined` if none are connected.\n *\n * @see [Gamepad on MDN](https://developer.mozilla.org/en-US/docs/Web/API/Gamepad)\n */\n readonly activeGamepad: Signal<Gamepad | undefined>;\n\n /**\n * Axes values of the active gamepad. Each value is in the range `[-1, 1]`.\n *\n * @see [Gamepad: axes on MDN](https://developer.mozilla.org/en-US/docs/Web/API/Gamepad/axes)\n */\n readonly axes: Signal<readonly number[]>;\n\n /**\n * Button states of the active gamepad.\n *\n * @see [Gamepad: buttons on MDN](https://developer.mozilla.org/en-US/docs/Web/API/Gamepad/buttons)\n */\n readonly buttons: Signal<readonly GamepadButton[]>;\n}\n\n/**\n * Signal-based wrapper around the [Gamepad API](https://developer.mozilla.org/en-US/docs/Web/API/Gamepad_API).\n *\n * @param options - Optional injector for DI context\n * @returns A GamepadRef with gamepad state signals\n *\n * @example\n * ```typescript\n * @Component({\n * template: `\n * @if (gp.isSupported()) {\n * @for (pad of gp.gamepads(); track pad?.index) {\n * <p>{{ pad?.id }}</p>\n * }\n * <p>Axes: {{ gp.axes() }}</p>\n * }\n * `\n * })\n * export class GamepadDemo {\n * readonly gp = gamepad();\n * }\n * ```\n */\nexport function gamepad(options?: WithInjector): GamepadRef {\n const { runInContext } = setupContext(options?.injector, gamepad);\n\n return runInContext(({ onCleanup, isBrowser }) => {\n const isSupported = constSignal(isBrowser && 'getGamepads' in navigator);\n\n if (!isSupported()) {\n return {\n isSupported,\n gamepads: constSignal([]),\n activeGamepad: constSignal(undefined),\n axes: constSignal([]),\n buttons: constSignal([]),\n };\n }\n\n const getGamepads = () => {\n try {\n const pads = navigator.getGamepads();\n return [...pads];\n } catch (error) {\n if (ngDevMode) {\n console.warn(`[gamepad] Failed to get gamepads.`, error);\n }\n return [];\n }\n };\n\n const gamepads = signal<(Gamepad | null)[]>(getGamepads());\n const activeGamepad = computed(() => gamepads().find(gp => gp !== null) ?? undefined);\n const axes = computed(() => activeGamepad()?.axes ?? []);\n const buttons = computed(() => activeGamepad()?.buttons ?? []);\n\n let animationFrameId: number | null = null;\n\n const pollGamepads = () => {\n gamepads.set(getGamepads());\n animationFrameId = requestAnimationFrame(pollGamepads);\n };\n\n const onGamepadConnected = () => {\n gamepads.set(getGamepads());\n\n if (animationFrameId === null) {\n pollGamepads();\n }\n };\n\n const onGamepadDisconnected = () => {\n gamepads.set(getGamepads());\n\n const hasGamepads = gamepads().some(gp => gp !== null);\n\n if (!hasGamepads && animationFrameId !== null) {\n cancelAnimationFrame(animationFrameId);\n animationFrameId = null;\n }\n };\n\n setupSync(() => {\n listener.passive(window, 'gamepadconnected', onGamepadConnected);\n listener.passive(window, 'gamepaddisconnected', onGamepadDisconnected);\n });\n\n if (gamepads().some(gp => gp !== null)) {\n pollGamepads();\n }\n\n onCleanup(() => {\n if (animationFrameId !== null) {\n cancelAnimationFrame(animationFrameId);\n }\n });\n\n return {\n isSupported,\n gamepads: gamepads.asReadonly(),\n activeGamepad,\n axes,\n buttons,\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,7 @@
1
1
  import { signal, untracked, computed } from '@angular/core';
2
2
  import { setupContext, constSignal, NOOP_FN } from '@signality/core/internal';
3
+ import { watcher } from '@signality/core/reactivity/watcher';
4
+ import { permissionState } from '@signality/core/browser/permission-state';
3
5
 
4
6
  /**
5
7
  * Signal-based wrapper around the [Geolocation API](https://developer.mozilla.org/en-US/docs/Web/API/Geolocation_API).
@@ -19,7 +21,7 @@ import { setupContext, constSignal, NOOP_FN } from '@signality/core/internal';
19
21
  * <button (click)="geo.stop()">Stop</button>
20
22
  * <button (click)="geo.start()">Start</button>
21
23
  * })
22
- * class LocationComponent {
24
+ * export class LocationDemo {
23
25
  * readonly geo = geolocation();
24
26
  * }
25
27
  * ```
@@ -39,7 +41,7 @@ function geolocation(options) {
39
41
  stop: NOOP_FN,
40
42
  };
41
43
  }
42
- const immediate = options?.immediate ?? true;
44
+ const immediate = options?.immediate ?? false;
43
45
  const positionOptions = {
44
46
  enableHighAccuracy: options?.enableHighAccuracy ?? true,
45
47
  maximumAge: options?.maximumAge ?? 0,
@@ -78,24 +80,11 @@ function geolocation(options) {
78
80
  isLoading.set(false);
79
81
  });
80
82
  };
81
- const abortController = new AbortController();
82
- onCleanup(() => {
83
- abortController.abort();
84
- stop();
85
- });
86
- navigator.permissions.query({ name: 'geolocation' }).then(status => {
87
- if (abortController.signal.aborted) {
88
- return;
83
+ onCleanup(stop);
84
+ watcher(permissionState('geolocation'), state => {
85
+ if (state === 'denied') {
86
+ stop();
89
87
  }
90
- const check = () => {
91
- if (status.state === 'denied') {
92
- stop();
93
- }
94
- };
95
- check();
96
- status.addEventListener('change', check, {
97
- signal: abortController.signal,
98
- });
99
88
  });
100
89
  if (immediate) {
101
90
  start();