@signality/core 0.1.3 → 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 (36) hide show
  1. package/browser/device-pixel-ratio/index.d.ts +30 -0
  2. package/browser/index.d.ts +1 -0
  3. package/browser/listener/index.d.ts +1 -0
  4. package/browser/picture-in-picture/index.d.ts +1 -5
  5. package/browser/web-notification/index.d.ts +3 -3
  6. package/fesm2022/signality-core-browser-clipboard.mjs +11 -29
  7. package/fesm2022/signality-core-browser-clipboard.mjs.map +1 -1
  8. package/fesm2022/signality-core-browser-device-pixel-ratio.mjs +45 -0
  9. package/fesm2022/signality-core-browser-device-pixel-ratio.mjs.map +1 -0
  10. package/fesm2022/signality-core-browser-eye-dropper.mjs +2 -3
  11. package/fesm2022/signality-core-browser-eye-dropper.mjs.map +1 -1
  12. package/fesm2022/signality-core-browser-fullscreen.mjs +7 -19
  13. package/fesm2022/signality-core-browser-fullscreen.mjs.map +1 -1
  14. package/fesm2022/signality-core-browser-gamepad.mjs +2 -10
  15. package/fesm2022/signality-core-browser-gamepad.mjs.map +1 -1
  16. package/fesm2022/signality-core-browser-listener.mjs.map +1 -1
  17. package/fesm2022/signality-core-browser-picture-in-picture.mjs +8 -12
  18. package/fesm2022/signality-core-browser-picture-in-picture.mjs.map +1 -1
  19. package/fesm2022/signality-core-browser-storage.mjs +19 -44
  20. package/fesm2022/signality-core-browser-storage.mjs.map +1 -1
  21. package/fesm2022/signality-core-browser-vibration.mjs +14 -29
  22. package/fesm2022/signality-core-browser-vibration.mjs.map +1 -1
  23. package/fesm2022/signality-core-browser-web-notification.mjs +32 -53
  24. package/fesm2022/signality-core-browser-web-notification.mjs.map +1 -1
  25. package/fesm2022/signality-core-browser-web-share.mjs +3 -11
  26. package/fesm2022/signality-core-browser-web-share.mjs.map +1 -1
  27. package/fesm2022/signality-core-browser.mjs +1 -0
  28. package/fesm2022/signality-core-browser.mjs.map +1 -1
  29. package/fesm2022/signality-core-forms-cva.mjs +13 -3
  30. package/fesm2022/signality-core-forms-cva.mjs.map +1 -1
  31. package/fesm2022/signality-core-internal.mjs +34 -2
  32. package/fesm2022/signality-core-internal.mjs.map +1 -1
  33. package/forms/cva/index.d.ts +10 -5
  34. package/internal/utils/index.d.ts +1 -0
  35. package/internal/utils/wait-for-value.d.ts +6 -0
  36. package/package.json +5 -1
@@ -0,0 +1,30 @@
1
+ import { type CreateSignalOptions, type Signal } from '@angular/core';
2
+ import type { WithInjector } from '@signality/core/types';
3
+ export interface DevicePixelRatioOptions extends CreateSignalOptions<number>, WithInjector {
4
+ /**
5
+ * Initial value for SSR.
6
+ * @default 1
7
+ */
8
+ readonly initialValue?: number;
9
+ }
10
+ /**
11
+ * Signal-based wrapper around the [Window.devicePixelRatio](https://developer.mozilla.org/en-US/docs/Web/API/Window/devicePixelRatio) property.
12
+ * Tracks changes to the device pixel ratio, which occurs when zooming or moving the window to a display with different pixel density.
13
+ *
14
+ * @param options - Optional configuration including initialValue for SSR
15
+ * @returns A signal containing the current device pixel ratio
16
+ *
17
+ * @example
18
+ * ```typescript
19
+ * @Component({
20
+ * template: `
21
+ * <p>Device Pixel Ratio: {{ pixelRatio() }}</p>
22
+ * `
23
+ * })
24
+ * export class PixelRatioDemo {
25
+ * readonly pixelRatio = devicePixelRatio();
26
+ * }
27
+ * ```
28
+ */
29
+ export declare function devicePixelRatio(options?: DevicePixelRatioOptions): Signal<number>;
30
+ export declare const DEVICE_PIXEL_RATIO: import("@angular/core").ProviderToken<Signal<number>>;
@@ -4,6 +4,7 @@ export * from '@signality/core/browser/breakpoints';
4
4
  export * from '@signality/core/browser/broadcast-channel';
5
5
  export * from '@signality/core/browser/browser-language';
6
6
  export * from '@signality/core/browser/clipboard';
7
+ export * from '@signality/core/browser/device-pixel-ratio';
7
8
  export * from '@signality/core/browser/device-posture';
8
9
  export * from '@signality/core/browser/display-media';
9
10
  export * from '@signality/core/browser/eye-dropper';
@@ -10,6 +10,7 @@ export interface ListenerFunction {
10
10
  <T extends HTMLElement, E extends keyof HTMLElementEventMap>(target: MaybeElementSignal<T>, event: MaybeSignal<E>, handler: (this: T, e: HTMLElementEventMap[E]) => any, options?: ListenerOptions): ListenerRef;
11
11
  <T extends SVGElement, E extends keyof SVGElementEventMap>(target: MaybeElementSignal<T>, event: MaybeSignal<E>, handler: (this: T, e: SVGElementEventMap[E]) => any, options?: ListenerOptions): ListenerRef;
12
12
  <Names extends string>(target: MaybeSignal<InferEventTarget<Names>>, event: MaybeSignal<Names>, handler: (e: Event) => void, options?: ListenerOptions): ListenerRef;
13
+ <E extends string>(target: MaybeSignal<MediaQueryList>, event: MaybeSignal<E>, handler: (this: MediaQueryList, e: MediaQueryListEvent) => any, options?: ListenerOptions): ListenerRef;
13
14
  <EventType = Event>(target: MaybeSignal<EventTarget> | MaybeElementSignal<Element>, event: MaybeSignal<string>, handler: GeneralEventListener<EventType>, options?: ListenerOptions): ListenerRef;
14
15
  readonly capture: ListenerFunction;
15
16
  readonly passive: ListenerFunction;
@@ -17,10 +17,6 @@ export interface PictureInPictureRef {
17
17
  /**
18
18
  * Enter Picture-in-Picture mode for the target video element.
19
19
  *
20
- * @throws {DOMException} `'NotAllowedError'` — the document is not allowed to use PiP
21
- * @throws {DOMException} `'InvalidStateError'` — the video element has `disablePictureInPicture` attribute
22
- * @throws {DOMException} `'NotSupportedError'` — Picture-in-Picture is not supported
23
- *
24
20
  * @see [HTMLVideoElement: requestPictureInPicture() on MDN](https://developer.mozilla.org/en-US/docs/Web/API/HTMLVideoElement/requestPictureInPicture)
25
21
  */
26
22
  readonly enter: () => Promise<void>;
@@ -56,7 +52,7 @@ export interface PictureInPictureRef {
56
52
  * `
57
53
  * })
58
54
  * export class PiPDemo {
59
- * readonly video = viewChild<HTMLVideoElement>('video');
55
+ * readonly video = viewChild<ElementRef>('video');
60
56
  * readonly pip = pictureInPicture(this.video);
61
57
  * }
62
58
  * ```
@@ -32,13 +32,13 @@ export interface WebNotificationRef {
32
32
  *
33
33
  * @see [Notification: requestPermission() on MDN](https://developer.mozilla.org/en-US/docs/Web/API/Notification/requestPermission_static)
34
34
  */
35
- readonly requestPermission: () => Promise<NotificationPermission>;
35
+ readonly requestPermission: () => Promise<void>;
36
36
  /**
37
- * Show a notification. Automatically closes the previous one. Per-call options override constructor defaults.
37
+ * Show a notification.
38
38
  *
39
39
  * @see [Notification() constructor on MDN](https://developer.mozilla.org/en-US/docs/Web/API/Notification/Notification)
40
40
  */
41
- readonly show: (title: string, options?: NotificationOptions) => Notification | undefined;
41
+ readonly show: (title: string, options?: NotificationOptions) => void;
42
42
  /**
43
43
  * Close the currently active notification.
44
44
  *
@@ -46,39 +46,21 @@ function clipboard(options) {
46
46
  const copied = signal(false, ...(ngDevMode ? [{ debugName: "copied" }] : []));
47
47
  let copiedTimeout;
48
48
  const copy = async (value) => {
49
- try {
50
- await navigator.clipboard.writeText(value);
51
- text.set(value);
52
- copied.set(true);
53
- if (copiedTimeout) {
54
- clearTimeout(copiedTimeout);
55
- }
56
- copiedTimeout = setTimeout(() => {
57
- copied.set(false);
58
- copiedTimeout = undefined;
59
- }, toValue.untracked(copiedDuration));
49
+ await navigator.clipboard.writeText(value);
50
+ text.set(value);
51
+ copied.set(true);
52
+ if (copiedTimeout) {
53
+ clearTimeout(copiedTimeout);
60
54
  }
61
- catch (error) {
55
+ copiedTimeout = setTimeout(() => {
62
56
  copied.set(false);
63
- if (ngDevMode) {
64
- console.warn(`[clipboard] Failed to copy text to clipboard. ` +
65
- `This may be due to permission denied or clipboard access failed.`, error);
66
- }
67
- }
57
+ copiedTimeout = undefined;
58
+ }, toValue.untracked(copiedDuration));
68
59
  };
69
60
  const paste = async () => {
70
- try {
71
- const value = await navigator.clipboard.readText();
72
- text.set(value);
73
- return value;
74
- }
75
- catch (error) {
76
- if (ngDevMode) {
77
- console.warn(`[clipboard] Failed to read text from clipboard. ` +
78
- `This may be due to permission denied or clipboard access failed.`, error);
79
- }
80
- return '';
81
- }
61
+ const value = await navigator.clipboard.readText();
62
+ text.set(value);
63
+ return value;
82
64
  };
83
65
  onCleanup(() => {
84
66
  if (copiedTimeout) {
@@ -1 +1 @@
1
- {"version":3,"file":"signality-core-browser-clipboard.mjs","sources":["../../../projects/core/browser/clipboard/index.ts","../../../projects/core/browser/clipboard/signality-core-browser-clipboard.ts"],"sourcesContent":["import { type Signal, signal } from '@angular/core';\nimport { constSignal, NOOP_ASYNC_FN, setupContext, type Timer } from '@signality/core/internal';\nimport { toValue } from '@signality/core/utilities';\nimport type { MaybeSignal, WithInjector } from '@signality/core/types';\n\nexport interface ClipboardOptions extends WithInjector {\n /**\n * How long `copied` stays `true` after copy (ms).\n * @default 1500\n */\n readonly copiedDuration?: MaybeSignal<number>;\n}\n\nexport interface ClipboardRef {\n /**\n * Whether the Clipboard API is supported in the current browser.\n *\n * @see [Clipboard API browser compatibility on MDN](https://developer.mozilla.org/en-US/docs/Web/API/Clipboard_API#browser_compatibility)\n */\n readonly isSupported: Signal<boolean>;\n\n /**\n * The most recently copied or pasted text.\n */\n readonly text: Signal<string>;\n\n /**\n * Whether the text was recently copied. Resets to `false` after `copiedDuration` ms.\n */\n readonly copied: Signal<boolean>;\n\n /**\n * Write text to the clipboard.\n *\n * @see [Clipboard: writeText() on MDN](https://developer.mozilla.org/en-US/docs/Web/API/Clipboard/writeText)\n */\n readonly copy: (text: string) => Promise<void>;\n\n /**\n * Read text from the clipboard.\n *\n * @see [Clipboard: readText() on MDN](https://developer.mozilla.org/en-US/docs/Web/API/Clipboard/readText)\n */\n readonly paste: () => Promise<string>;\n}\n\n/**\n * Signal-based wrapper around the [Clipboard API](https://developer.mozilla.org/en-US/docs/Web/API/Clipboard_API).\n *\n * @param options - Optional configuration\n * @returns A ClipboardRef with text, copied, isSupported signals and copy/paste methods\n *\n * @example\n * ```typescript\n * @Component({\n * template: `\n * <input #input value=\"Hello World!\" />\n * <button (click)=\"copyText(input.value)\">Copy</button>\n * @if (cb.copied()) {\n * <span>Copied!</span>\n * }\n * `\n * })\n * export class ClipboardDemo {\n * readonly cb = clipboard();\n *\n * async copyText(text: string) {\n * await this.cb.copy(text);\n * }\n * }\n * ```\n */\nexport function clipboard(options?: ClipboardOptions): ClipboardRef {\n const { runInContext } = setupContext(options?.injector, clipboard);\n\n return runInContext(({ isBrowser, onCleanup }) => {\n const isSupported = constSignal(\n isBrowser && 'clipboard' in navigator && typeof navigator.clipboard?.writeText === 'function'\n );\n\n if (!isSupported()) {\n return {\n isSupported,\n text: constSignal(''),\n copied: constSignal(false),\n copy: NOOP_ASYNC_FN,\n paste: () => Promise.resolve(''),\n };\n }\n\n const copiedDuration = options?.copiedDuration ?? 1500;\n\n const text = signal('');\n const copied = signal(false);\n\n let copiedTimeout: Timer;\n\n const copy = async (value: string): Promise<void> => {\n try {\n await navigator.clipboard.writeText(value);\n\n text.set(value);\n copied.set(true);\n\n if (copiedTimeout) {\n clearTimeout(copiedTimeout);\n }\n\n copiedTimeout = setTimeout(() => {\n copied.set(false);\n copiedTimeout = undefined;\n }, toValue.untracked(copiedDuration));\n } catch (error) {\n copied.set(false);\n if (ngDevMode) {\n console.warn(\n `[clipboard] Failed to copy text to clipboard. ` +\n `This may be due to permission denied or clipboard access failed.`,\n error\n );\n }\n }\n };\n\n const paste = async (): Promise<string> => {\n try {\n const value = await navigator.clipboard.readText();\n text.set(value);\n return value;\n } catch (error) {\n if (ngDevMode) {\n console.warn(\n `[clipboard] Failed to read text from clipboard. ` +\n `This may be due to permission denied or clipboard access failed.`,\n error\n );\n }\n return '';\n }\n };\n\n onCleanup(() => {\n if (copiedTimeout) {\n clearTimeout(copiedTimeout);\n }\n });\n\n return {\n isSupported,\n text: text.asReadonly(),\n copied: copied.asReadonly(),\n copy,\n paste,\n };\n });\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;;AA8CA;;;;;;;;;;;;;;;;;;;;;;;;;AAyBG;AACG,SAAU,SAAS,CAAC,OAA0B,EAAA;AAClD,IAAA,MAAM,EAAE,YAAY,EAAE,GAAG,YAAY,CAAC,OAAO,EAAE,QAAQ,EAAE,SAAS,CAAC;IAEnE,OAAO,YAAY,CAAC,CAAC,EAAE,SAAS,EAAE,SAAS,EAAE,KAAI;AAC/C,QAAA,MAAM,WAAW,GAAG,WAAW,CAC7B,SAAS,IAAI,WAAW,IAAI,SAAS,IAAI,OAAO,SAAS,CAAC,SAAS,EAAE,SAAS,KAAK,UAAU,CAC9F;AAED,QAAA,IAAI,CAAC,WAAW,EAAE,EAAE;YAClB,OAAO;gBACL,WAAW;AACX,gBAAA,IAAI,EAAE,WAAW,CAAC,EAAE,CAAC;AACrB,gBAAA,MAAM,EAAE,WAAW,CAAC,KAAK,CAAC;AAC1B,gBAAA,IAAI,EAAE,aAAa;gBACnB,KAAK,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;aACjC;QACH;AAEA,QAAA,MAAM,cAAc,GAAG,OAAO,EAAE,cAAc,IAAI,IAAI;AAEtD,QAAA,MAAM,IAAI,GAAG,MAAM,CAAC,EAAE,gDAAC;AACvB,QAAA,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,kDAAC;AAE5B,QAAA,IAAI,aAAoB;AAExB,QAAA,MAAM,IAAI,GAAG,OAAO,KAAa,KAAmB;AAClD,YAAA,IAAI;gBACF,MAAM,SAAS,CAAC,SAAS,CAAC,SAAS,CAAC,KAAK,CAAC;AAE1C,gBAAA,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC;AACf,gBAAA,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC;gBAEhB,IAAI,aAAa,EAAE;oBACjB,YAAY,CAAC,aAAa,CAAC;gBAC7B;AAEA,gBAAA,aAAa,GAAG,UAAU,CAAC,MAAK;AAC9B,oBAAA,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC;oBACjB,aAAa,GAAG,SAAS;gBAC3B,CAAC,EAAE,OAAO,CAAC,SAAS,CAAC,cAAc,CAAC,CAAC;YACvC;YAAE,OAAO,KAAK,EAAE;AACd,gBAAA,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC;gBACjB,IAAI,SAAS,EAAE;oBACb,OAAO,CAAC,IAAI,CACV,CAAA,8CAAA,CAAgD;wBAC9C,CAAA,gEAAA,CAAkE,EACpE,KAAK,CACN;gBACH;YACF;AACF,QAAA,CAAC;AAED,QAAA,MAAM,KAAK,GAAG,YAA4B;AACxC,YAAA,IAAI;gBACF,MAAM,KAAK,GAAG,MAAM,SAAS,CAAC,SAAS,CAAC,QAAQ,EAAE;AAClD,gBAAA,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC;AACf,gBAAA,OAAO,KAAK;YACd;YAAE,OAAO,KAAK,EAAE;gBACd,IAAI,SAAS,EAAE;oBACb,OAAO,CAAC,IAAI,CACV,CAAA,gDAAA,CAAkD;wBAChD,CAAA,gEAAA,CAAkE,EACpE,KAAK,CACN;gBACH;AACA,gBAAA,OAAO,EAAE;YACX;AACF,QAAA,CAAC;QAED,SAAS,CAAC,MAAK;YACb,IAAI,aAAa,EAAE;gBACjB,YAAY,CAAC,aAAa,CAAC;YAC7B;AACF,QAAA,CAAC,CAAC;QAEF,OAAO;YACL,WAAW;AACX,YAAA,IAAI,EAAE,IAAI,CAAC,UAAU,EAAE;AACvB,YAAA,MAAM,EAAE,MAAM,CAAC,UAAU,EAAE;YAC3B,IAAI;YACJ,KAAK;SACN;AACH,IAAA,CAAC,CAAC;AACJ;;AC3JA;;AAEG;;;;"}
1
+ {"version":3,"file":"signality-core-browser-clipboard.mjs","sources":["../../../projects/core/browser/clipboard/index.ts","../../../projects/core/browser/clipboard/signality-core-browser-clipboard.ts"],"sourcesContent":["import { type Signal, signal } from '@angular/core';\nimport { constSignal, NOOP_ASYNC_FN, setupContext, type Timer } from '@signality/core/internal';\nimport { toValue } from '@signality/core/utilities';\nimport type { MaybeSignal, WithInjector } from '@signality/core/types';\n\nexport interface ClipboardOptions extends WithInjector {\n /**\n * How long `copied` stays `true` after copy (ms).\n * @default 1500\n */\n readonly copiedDuration?: MaybeSignal<number>;\n}\n\nexport interface ClipboardRef {\n /**\n * Whether the Clipboard API is supported in the current browser.\n *\n * @see [Clipboard API browser compatibility on MDN](https://developer.mozilla.org/en-US/docs/Web/API/Clipboard_API#browser_compatibility)\n */\n readonly isSupported: Signal<boolean>;\n\n /**\n * The most recently copied or pasted text.\n */\n readonly text: Signal<string>;\n\n /**\n * Whether the text was recently copied. Resets to `false` after `copiedDuration` ms.\n */\n readonly copied: Signal<boolean>;\n\n /**\n * Write text to the clipboard.\n *\n * @see [Clipboard: writeText() on MDN](https://developer.mozilla.org/en-US/docs/Web/API/Clipboard/writeText)\n */\n readonly copy: (text: string) => Promise<void>;\n\n /**\n * Read text from the clipboard.\n *\n * @see [Clipboard: readText() on MDN](https://developer.mozilla.org/en-US/docs/Web/API/Clipboard/readText)\n */\n readonly paste: () => Promise<string>;\n}\n\n/**\n * Signal-based wrapper around the [Clipboard API](https://developer.mozilla.org/en-US/docs/Web/API/Clipboard_API).\n *\n * @param options - Optional configuration\n * @returns A ClipboardRef with text, copied, isSupported signals and copy/paste methods\n *\n * @example\n * ```typescript\n * @Component({\n * template: `\n * <input #input value=\"Hello World!\" />\n * <button (click)=\"copyText(input.value)\">Copy</button>\n * @if (cb.copied()) {\n * <span>Copied!</span>\n * }\n * `\n * })\n * export class ClipboardDemo {\n * readonly cb = clipboard();\n *\n * async copyText(text: string) {\n * await this.cb.copy(text);\n * }\n * }\n * ```\n */\nexport function clipboard(options?: ClipboardOptions): ClipboardRef {\n const { runInContext } = setupContext(options?.injector, clipboard);\n\n return runInContext(({ isBrowser, onCleanup }) => {\n const isSupported = constSignal(\n isBrowser && 'clipboard' in navigator && typeof navigator.clipboard?.writeText === 'function'\n );\n\n if (!isSupported()) {\n return {\n isSupported,\n text: constSignal(''),\n copied: constSignal(false),\n copy: NOOP_ASYNC_FN,\n paste: () => Promise.resolve(''),\n };\n }\n\n const copiedDuration = options?.copiedDuration ?? 1500;\n\n const text = signal('');\n const copied = signal(false);\n\n let copiedTimeout: Timer;\n\n const copy = async (value: string): Promise<void> => {\n await navigator.clipboard.writeText(value);\n\n text.set(value);\n copied.set(true);\n\n if (copiedTimeout) {\n clearTimeout(copiedTimeout);\n }\n\n copiedTimeout = setTimeout(() => {\n copied.set(false);\n copiedTimeout = undefined;\n }, toValue.untracked(copiedDuration));\n };\n\n const paste = async (): Promise<string> => {\n const value = await navigator.clipboard.readText();\n text.set(value);\n return value;\n };\n\n onCleanup(() => {\n if (copiedTimeout) {\n clearTimeout(copiedTimeout);\n }\n });\n\n return {\n isSupported,\n text: text.asReadonly(),\n copied: copied.asReadonly(),\n copy,\n paste,\n };\n });\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;;AA8CA;;;;;;;;;;;;;;;;;;;;;;;;;AAyBG;AACG,SAAU,SAAS,CAAC,OAA0B,EAAA;AAClD,IAAA,MAAM,EAAE,YAAY,EAAE,GAAG,YAAY,CAAC,OAAO,EAAE,QAAQ,EAAE,SAAS,CAAC;IAEnE,OAAO,YAAY,CAAC,CAAC,EAAE,SAAS,EAAE,SAAS,EAAE,KAAI;AAC/C,QAAA,MAAM,WAAW,GAAG,WAAW,CAC7B,SAAS,IAAI,WAAW,IAAI,SAAS,IAAI,OAAO,SAAS,CAAC,SAAS,EAAE,SAAS,KAAK,UAAU,CAC9F;AAED,QAAA,IAAI,CAAC,WAAW,EAAE,EAAE;YAClB,OAAO;gBACL,WAAW;AACX,gBAAA,IAAI,EAAE,WAAW,CAAC,EAAE,CAAC;AACrB,gBAAA,MAAM,EAAE,WAAW,CAAC,KAAK,CAAC;AAC1B,gBAAA,IAAI,EAAE,aAAa;gBACnB,KAAK,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;aACjC;QACH;AAEA,QAAA,MAAM,cAAc,GAAG,OAAO,EAAE,cAAc,IAAI,IAAI;AAEtD,QAAA,MAAM,IAAI,GAAG,MAAM,CAAC,EAAE,gDAAC;AACvB,QAAA,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,kDAAC;AAE5B,QAAA,IAAI,aAAoB;AAExB,QAAA,MAAM,IAAI,GAAG,OAAO,KAAa,KAAmB;YAClD,MAAM,SAAS,CAAC,SAAS,CAAC,SAAS,CAAC,KAAK,CAAC;AAE1C,YAAA,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC;AACf,YAAA,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC;YAEhB,IAAI,aAAa,EAAE;gBACjB,YAAY,CAAC,aAAa,CAAC;YAC7B;AAEA,YAAA,aAAa,GAAG,UAAU,CAAC,MAAK;AAC9B,gBAAA,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC;gBACjB,aAAa,GAAG,SAAS;YAC3B,CAAC,EAAE,OAAO,CAAC,SAAS,CAAC,cAAc,CAAC,CAAC;AACvC,QAAA,CAAC;AAED,QAAA,MAAM,KAAK,GAAG,YAA4B;YACxC,MAAM,KAAK,GAAG,MAAM,SAAS,CAAC,SAAS,CAAC,QAAQ,EAAE;AAClD,YAAA,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC;AACf,YAAA,OAAO,KAAK;AACd,QAAA,CAAC;QAED,SAAS,CAAC,MAAK;YACb,IAAI,aAAa,EAAE;gBACjB,YAAY,CAAC,aAAa,CAAC;YAC7B;AACF,QAAA,CAAC,CAAC;QAEF,OAAO;YACL,WAAW;AACX,YAAA,IAAI,EAAE,IAAI,CAAC,UAAU,EAAE;AACvB,YAAA,MAAM,EAAE,MAAM,CAAC,UAAU,EAAE;YAC3B,IAAI;YACJ,KAAK;SACN;AACH,IAAA,CAAC,CAAC;AACJ;;ACrIA;;AAEG;;;;"}
@@ -0,0 +1,45 @@
1
+ import { signal, computed } from '@angular/core';
2
+ import { setupContext, constSignal, createToken } from '@signality/core/internal';
3
+ import { watcher } from '@signality/core/reactivity/watcher';
4
+ import { mediaQuery } from '@signality/core/browser/media-query';
5
+
6
+ /**
7
+ * Signal-based wrapper around the [Window.devicePixelRatio](https://developer.mozilla.org/en-US/docs/Web/API/Window/devicePixelRatio) property.
8
+ * Tracks changes to the device pixel ratio, which occurs when zooming or moving the window to a display with different pixel density.
9
+ *
10
+ * @param options - Optional configuration including initialValue for SSR
11
+ * @returns A signal containing the current device pixel ratio
12
+ *
13
+ * @example
14
+ * ```typescript
15
+ * @Component({
16
+ * template: `
17
+ * <p>Device Pixel Ratio: {{ pixelRatio() }}</p>
18
+ * `
19
+ * })
20
+ * export class PixelRatioDemo {
21
+ * readonly pixelRatio = devicePixelRatio();
22
+ * }
23
+ * ```
24
+ */
25
+ function devicePixelRatio(options) {
26
+ const { runInContext } = setupContext(options?.injector, devicePixelRatio);
27
+ return runInContext(({ isServer }) => {
28
+ if (isServer) {
29
+ return constSignal(options?.initialValue ?? 1);
30
+ }
31
+ const pixelRatio = signal(window.devicePixelRatio, options);
32
+ watcher(mediaQuery(computed(() => `(resolution: ${pixelRatio()}dppx)`)), () => {
33
+ pixelRatio.set(window.devicePixelRatio);
34
+ });
35
+ return pixelRatio.asReadonly();
36
+ });
37
+ }
38
+ const DEVICE_PIXEL_RATIO = /* @__PURE__ */ createToken(devicePixelRatio);
39
+
40
+ /**
41
+ * Generated bundle index. Do not edit.
42
+ */
43
+
44
+ export { DEVICE_PIXEL_RATIO, devicePixelRatio };
45
+ //# sourceMappingURL=signality-core-browser-device-pixel-ratio.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"signality-core-browser-device-pixel-ratio.mjs","sources":["../../../projects/core/browser/device-pixel-ratio/index.ts","../../../projects/core/browser/device-pixel-ratio/signality-core-browser-device-pixel-ratio.ts"],"sourcesContent":["import { computed, type CreateSignalOptions, type Signal, signal } from '@angular/core';\nimport { constSignal, createToken, setupContext } from '@signality/core/internal';\nimport type { WithInjector } from '@signality/core/types';\nimport { watcher } from '@signality/core/reactivity/watcher';\nimport { mediaQuery } from '@signality/core/browser/media-query';\n\nexport interface DevicePixelRatioOptions extends CreateSignalOptions<number>, WithInjector {\n /**\n * Initial value for SSR.\n * @default 1\n */\n readonly initialValue?: number;\n}\n\n/**\n * Signal-based wrapper around the [Window.devicePixelRatio](https://developer.mozilla.org/en-US/docs/Web/API/Window/devicePixelRatio) property.\n * Tracks changes to the device pixel ratio, which occurs when zooming or moving the window to a display with different pixel density.\n *\n * @param options - Optional configuration including initialValue for SSR\n * @returns A signal containing the current device pixel ratio\n *\n * @example\n * ```typescript\n * @Component({\n * template: `\n * <p>Device Pixel Ratio: {{ pixelRatio() }}</p>\n * `\n * })\n * export class PixelRatioDemo {\n * readonly pixelRatio = devicePixelRatio();\n * }\n * ```\n */\nexport function devicePixelRatio(options?: DevicePixelRatioOptions): Signal<number> {\n const { runInContext } = setupContext(options?.injector, devicePixelRatio);\n\n return runInContext(({ isServer }) => {\n if (isServer) {\n return constSignal(options?.initialValue ?? 1);\n }\n\n const pixelRatio = signal(window.devicePixelRatio, options);\n\n watcher(mediaQuery(computed(() => `(resolution: ${pixelRatio()}dppx)`)), () => {\n pixelRatio.set(window.devicePixelRatio);\n });\n\n return pixelRatio.asReadonly();\n });\n}\n\nexport const DEVICE_PIXEL_RATIO = /* @__PURE__ */ createToken(devicePixelRatio);\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;;;AAcA;;;;;;;;;;;;;;;;;;AAkBG;AACG,SAAU,gBAAgB,CAAC,OAAiC,EAAA;AAChE,IAAA,MAAM,EAAE,YAAY,EAAE,GAAG,YAAY,CAAC,OAAO,EAAE,QAAQ,EAAE,gBAAgB,CAAC;AAE1E,IAAA,OAAO,YAAY,CAAC,CAAC,EAAE,QAAQ,EAAE,KAAI;QACnC,IAAI,QAAQ,EAAE;YACZ,OAAO,WAAW,CAAC,OAAO,EAAE,YAAY,IAAI,CAAC,CAAC;QAChD;QAEA,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,CAAC,gBAAgB,EAAE,OAAO,CAAC;AAE3D,QAAA,OAAO,CAAC,UAAU,CAAC,QAAQ,CAAC,MAAM,CAAA,aAAA,EAAgB,UAAU,EAAE,CAAA,KAAA,CAAO,CAAC,CAAC,EAAE,MAAK;AAC5E,YAAA,UAAU,CAAC,GAAG,CAAC,MAAM,CAAC,gBAAgB,CAAC;AACzC,QAAA,CAAC,CAAC;AAEF,QAAA,OAAO,UAAU,CAAC,UAAU,EAAE;AAChC,IAAA,CAAC,CAAC;AACJ;AAEO,MAAM,kBAAkB,mBAAmB,WAAW,CAAC,gBAAgB;;ACnD9E;;AAEG;;;;"}
@@ -51,9 +51,8 @@ function eyeDropper(options) {
51
51
  sRGBHex.set(result.sRGBHex);
52
52
  }
53
53
  catch (error) {
54
- if (ngDevMode) {
55
- console.warn(`[eyeDropper] Failed to open eyedropper. ` +
56
- `This may be due to user cancellation or an error occurred.`, error);
54
+ if (error.name !== 'AbortError') {
55
+ throw error;
57
56
  }
58
57
  }
59
58
  finally {
@@ -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 /**\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;;;;"}
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 ((error as Error).name !== 'AbortError') {\n throw error;\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;AACd,gBAAA,IAAK,KAAe,CAAC,IAAI,KAAK,YAAY,EAAE;AAC1C,oBAAA,MAAM,KAAK;gBACb;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;;ACxHA;;AAEG;;;;"}
@@ -1,5 +1,5 @@
1
1
  import { signal, untracked } from '@angular/core';
2
- import { setupContext, constSignal, NOOP_ASYNC_FN } from '@signality/core/internal';
2
+ import { setupContext, constSignal, NOOP_ASYNC_FN, assertElement } from '@signality/core/internal';
3
3
  import { toElement } from '@signality/core/utilities';
4
4
  import { setupSync, listener } from '@signality/core/browser/listener';
5
5
 
@@ -58,28 +58,16 @@ function fullscreen(options) {
58
58
  const isActive = signal(false, ...(ngDevMode ? [{ debugName: "isActive" }] : []));
59
59
  const enter = async () => {
60
60
  const el = toElement.untracked(target);
61
- if (el && document.fullscreenElement !== el) {
62
- try {
63
- await el.requestFullscreen();
64
- }
65
- catch (error) {
66
- if (ngDevMode) {
67
- console.warn(`[fullscreen] Failed to enter fullscreen mode.`, error);
68
- }
69
- }
61
+ ngDevMode && assertElement(el, 'fullscreen');
62
+ if (document.fullscreenElement !== el) {
63
+ await el?.requestFullscreen();
70
64
  }
71
65
  };
72
66
  const exit = async () => {
73
67
  const el = toElement.untracked(target);
74
- if (el && document.fullscreenElement === el) {
75
- try {
76
- await document.exitFullscreen();
77
- }
78
- catch (error) {
79
- if (ngDevMode) {
80
- console.warn(`[fullscreen] Failed to exit fullscreen mode.`, error);
81
- }
82
- }
68
+ ngDevMode && assertElement(el, 'fullscreen');
69
+ if (document.fullscreenElement === el) {
70
+ await document.exitFullscreen();
83
71
  }
84
72
  };
85
73
  const toggle = async () => {
@@ -1 +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 } from '@signality/core/internal';\nimport { toElement } from '@signality/core/utilities';\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":";;;;;AAiDA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;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;;AChKA;;AAEG;;;;"}
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 { assertElement, constSignal, NOOP_ASYNC_FN, setupContext } from '@signality/core/internal';\nimport { toElement } from '@signality/core/utilities';\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 ngDevMode && assertElement(el, 'fullscreen');\n if (document.fullscreenElement !== el) {\n await el?.requestFullscreen();\n }\n };\n\n const exit = async (): Promise<void> => {\n const el = toElement.untracked(target);\n ngDevMode && assertElement(el, 'fullscreen');\n if (document.fullscreenElement === el) {\n await document.exitFullscreen();\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":";;;;;AAiDA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;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;AACtC,YAAA,SAAS,IAAI,aAAa,CAAC,EAAE,EAAE,YAAY,CAAC;AAC5C,YAAA,IAAI,QAAQ,CAAC,iBAAiB,KAAK,EAAE,EAAE;AACrC,gBAAA,MAAM,EAAE,EAAE,iBAAiB,EAAE;YAC/B;AACF,QAAA,CAAC;AAED,QAAA,MAAM,IAAI,GAAG,YAA0B;YACrC,MAAM,EAAE,GAAG,SAAS,CAAC,SAAS,CAAC,MAAM,CAAC;AACtC,YAAA,SAAS,IAAI,aAAa,CAAC,EAAE,EAAE,YAAY,CAAC;AAC5C,YAAA,IAAI,QAAQ,CAAC,iBAAiB,KAAK,EAAE,EAAE;AACrC,gBAAA,MAAM,QAAQ,CAAC,cAAc,EAAE;YACjC;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;;ACpJA;;AAEG;;;;"}
@@ -39,16 +39,8 @@ function gamepad(options) {
39
39
  };
40
40
  }
41
41
  const getGamepads = () => {
42
- try {
43
- const pads = navigator.getGamepads();
44
- return [...pads];
45
- }
46
- catch (error) {
47
- if (ngDevMode) {
48
- console.warn(`[gamepad] Failed to get gamepads.`, error);
49
- }
50
- return [];
51
- }
42
+ const pads = navigator.getGamepads();
43
+ return [...pads];
52
44
  };
53
45
  const gamepads = signal(getGamepads(), ...(ngDevMode ? [{ debugName: "gamepads" }] : []));
54
46
  const activeGamepad = computed(() => gamepads().find(gp => gp !== null) ?? undefined, ...(ngDevMode ? [{ debugName: "activeGamepad" }] : []));
@@ -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 /**\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":";;;;AA0CA;;;;;;;;;;;;;;;;;;;;;;AAsBG;AACG,SAAU,OAAO,CAAC,OAAsB,EAAA;AAC5C,IAAA,MAAM,EAAE,YAAY,EAAE,GAAG,YAAY,CAAC,OAAO,EAAE,QAAQ,EAAE,OAAO,CAAC;IAEjE,OAAO,YAAY,CAAC,CAAC,EAAE,SAAS,EAAE,SAAS,EAAE,KAAI;QAC/C,MAAM,WAAW,GAAG,WAAW,CAAC,SAAS,IAAI,aAAa,IAAI,SAAS,CAAC;AAExE,QAAA,IAAI,CAAC,WAAW,EAAE,EAAE;YAClB,OAAO;gBACL,WAAW;AACX,gBAAA,QAAQ,EAAE,WAAW,CAAC,EAAE,CAAC;AACzB,gBAAA,aAAa,EAAE,WAAW,CAAC,SAAS,CAAC;AACrC,gBAAA,IAAI,EAAE,WAAW,CAAC,EAAE,CAAC;AACrB,gBAAA,OAAO,EAAE,WAAW,CAAC,EAAE,CAAC;aACzB;QACH;QAEA,MAAM,WAAW,GAAG,MAAK;AACvB,YAAA,IAAI;AACF,gBAAA,MAAM,IAAI,GAAG,SAAS,CAAC,WAAW,EAAE;AACpC,gBAAA,OAAO,CAAC,GAAG,IAAI,CAAC;YAClB;YAAE,OAAO,KAAK,EAAE;gBACd,IAAI,SAAS,EAAE;AACb,oBAAA,OAAO,CAAC,IAAI,CAAC,mCAAmC,EAAE,KAAK,CAAC;gBAC1D;AACA,gBAAA,OAAO,EAAE;YACX;AACF,QAAA,CAAC;AAED,QAAA,MAAM,QAAQ,GAAG,MAAM,CAAqB,WAAW,EAAE,oDAAC;QAC1D,MAAM,aAAa,GAAG,QAAQ,CAAC,MAAM,QAAQ,EAAE,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,KAAK,IAAI,CAAC,IAAI,SAAS,EAAA,IAAA,SAAA,GAAA,CAAA,EAAA,SAAA,EAAA,eAAA,EAAA,CAAA,GAAA,EAAA,CAAA,CAAC;AACrF,QAAA,MAAM,IAAI,GAAG,QAAQ,CAAC,MAAM,aAAa,EAAE,EAAE,IAAI,IAAI,EAAE,gDAAC;AACxD,QAAA,MAAM,OAAO,GAAG,QAAQ,CAAC,MAAM,aAAa,EAAE,EAAE,OAAO,IAAI,EAAE,mDAAC;QAE9D,IAAI,gBAAgB,GAAkB,IAAI;QAE1C,MAAM,YAAY,GAAG,MAAK;AACxB,YAAA,QAAQ,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC;AAC3B,YAAA,gBAAgB,GAAG,qBAAqB,CAAC,YAAY,CAAC;AACxD,QAAA,CAAC;QAED,MAAM,kBAAkB,GAAG,MAAK;AAC9B,YAAA,QAAQ,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC;AAE3B,YAAA,IAAI,gBAAgB,KAAK,IAAI,EAAE;AAC7B,gBAAA,YAAY,EAAE;YAChB;AACF,QAAA,CAAC;QAED,MAAM,qBAAqB,GAAG,MAAK;AACjC,YAAA,QAAQ,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC;AAE3B,YAAA,MAAM,WAAW,GAAG,QAAQ,EAAE,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,KAAK,IAAI,CAAC;AAEtD,YAAA,IAAI,CAAC,WAAW,IAAI,gBAAgB,KAAK,IAAI,EAAE;gBAC7C,oBAAoB,CAAC,gBAAgB,CAAC;gBACtC,gBAAgB,GAAG,IAAI;YACzB;AACF,QAAA,CAAC;QAED,SAAS,CAAC,MAAK;YACb,QAAQ,CAAC,OAAO,CAAC,MAAM,EAAE,kBAAkB,EAAE,kBAAkB,CAAC;YAChE,QAAQ,CAAC,OAAO,CAAC,MAAM,EAAE,qBAAqB,EAAE,qBAAqB,CAAC;AACxE,QAAA,CAAC,CAAC;AAEF,QAAA,IAAI,QAAQ,EAAE,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,KAAK,IAAI,CAAC,EAAE;AACtC,YAAA,YAAY,EAAE;QAChB;QAEA,SAAS,CAAC,MAAK;AACb,YAAA,IAAI,gBAAgB,KAAK,IAAI,EAAE;gBAC7B,oBAAoB,CAAC,gBAAgB,CAAC;YACxC;AACF,QAAA,CAAC,CAAC;QAEF,OAAO;YACL,WAAW;AACX,YAAA,QAAQ,EAAE,QAAQ,CAAC,UAAU,EAAE;YAC/B,aAAa;YACb,IAAI;YACJ,OAAO;SACR;AACH,IAAA,CAAC,CAAC;AACJ;;ACnJA;;AAEG;;;;"}
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 const pads = navigator.getGamepads();\n return [...pads];\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 +1 @@
1
- {"version":3,"file":"signality-core-browser-listener.mjs","sources":["../../../projects/core/browser/listener/index.ts","../../../projects/core/browser/listener/signality-core-browser-listener.ts"],"sourcesContent":["import {\n afterRenderEffect,\n type CreateEffectOptions,\n effect,\n type EffectCleanupRegisterFn,\n type EffectRef,\n untracked,\n} from '@angular/core';\nimport { type BaseEffectNode, SIGNAL } from '@angular/core/primitives/signals';\nimport {\n assertEventTarget,\n NOOP_EFFECT_REF,\n setupContext,\n unrefElement,\n} from '@signality/core/internal';\nimport { toValue } from '@signality/core/utilities';\nimport type { MaybeElementSignal, MaybeSignal, WithInjector } from '@signality/core/types';\n\nexport type ListenerOptions = WithInjector;\n\nexport interface ListenerRef {\n readonly destroy: () => void;\n}\n\nexport interface ListenerFunction {\n <E extends keyof WindowEventMap>(\n target: Window,\n event: MaybeSignal<E>,\n handler: (this: Window, e: WindowEventMap[E]) => any,\n options?: ListenerOptions\n ): ListenerRef;\n\n <E extends keyof DocumentEventMap>(\n target: Document,\n event: MaybeSignal<E>,\n handler: (this: Document, e: DocumentEventMap[E]) => any,\n options?: ListenerOptions\n ): ListenerRef;\n\n <E extends keyof ShadowRootEventMap>(\n target: MaybeSignal<ShadowRoot>,\n event: MaybeSignal<E>,\n handler: (this: ShadowRoot, e: ShadowRootEventMap[E]) => any,\n options?: ListenerOptions\n ): ListenerRef;\n\n <T extends HTMLElement, E extends keyof HTMLElementEventMap>(\n target: MaybeElementSignal<T>,\n event: MaybeSignal<E>,\n handler: (this: T, e: HTMLElementEventMap[E]) => any,\n options?: ListenerOptions\n ): ListenerRef;\n\n <T extends SVGElement, E extends keyof SVGElementEventMap>(\n target: MaybeElementSignal<T>,\n event: MaybeSignal<E>,\n handler: (this: T, e: SVGElementEventMap[E]) => any,\n options?: ListenerOptions\n ): ListenerRef;\n\n <Names extends string>(\n target: MaybeSignal<InferEventTarget<Names>>,\n event: MaybeSignal<Names>,\n handler: (e: Event) => void,\n options?: ListenerOptions\n ): ListenerRef;\n\n <EventType = Event>(\n target: MaybeSignal<EventTarget> | MaybeElementSignal<Element>,\n event: MaybeSignal<string>,\n handler: GeneralEventListener<EventType>,\n options?: ListenerOptions\n ): ListenerRef;\n\n readonly capture: ListenerFunction;\n readonly passive: ListenerFunction;\n readonly once: ListenerFunction;\n readonly stop: ListenerFunction;\n readonly prevent: ListenerFunction;\n readonly self: ListenerFunction;\n}\n\n/**\n * Signal-based wrapper around the [addEventListener](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener) method.\n *\n * @param target - Event target\n * @param event - Event type name\n * @param handler - Event handler function\n * @param options - Optional listener configuration\n * @returns A ListenerRef that can be used to destroy the listener\n *\n * @example\n * ```typescript\n * @Component({\n * template: `<button #btn>Click me</button>`,\n * })\n * export class ListenerDemo {\n * readonly btn = viewChild<ElementRef>('btn');\n *\n * constructor() {\n * listener.capture.prevent(this.btn, 'click', event => {\n * console.log('Button clicked!', event);\n * });\n * }\n * }\n * ```\n */\nexport const listener: ListenerFunction = createModifier({});\n\nlet isSyncSetupRequired = false;\n\n/**\n * By default, `listener()` registers event listeners after the render cycle completes\n * to ensure DOM elements exist. However, global targets (window, document, navigator.*, etc.)\n * are not tied to the render cycle. Use `setupSync()` to wrap listener calls when you need to prevent\n * race conditions where a global event is dispatched before Angular completes its scheduled rendering tasks.\n */\nexport function setupSync<T>(listenerFactoryExecFn: () => T): T {\n isSyncSetupRequired = true;\n\n try {\n return listenerFactoryExecFn();\n } finally {\n isSyncSetupRequired = false;\n }\n}\n\nfunction listenerImpl(applied: InternalListenerOptions, ...args: any[]): ListenerRef {\n const options = args[3] as ListenerOptions | undefined;\n\n const { runInContext } = setupContext(options?.injector, listenerImpl);\n\n return runInContext(({ isServer }) => {\n if (isServer) {\n return NOOP_EFFECT_REF;\n }\n\n const [maybeReactiveTarget, maybeReactiveEvent, rawHandler] = args;\n const { stop, prevent, self, ...nativeOptions } = applied;\n const hasModifiers = stop || prevent || self;\n\n const handler = hasModifiers\n ? function (this: any, event: Event) {\n if (self && event.target !== event.currentTarget) return;\n if (prevent) event.preventDefault();\n if (stop) event.stopPropagation();\n rawHandler.call(this, event);\n }\n : rawHandler;\n\n // Isolate event handler from signal dependency tracking.\n // Angular's template compiler does this via setActiveConsumer(null) in executeListenerWithErrorHandling.\n // Without this, events that fire during change detection (e.g. window 'blur' when the tab loses focus)\n // can trigger NG0600 if the handler writes to a signal while a reactive consumer is active.\n // See: https://github.com/angular/angular/issues/60143\n const untrackedHandler = function (this: any, event: Event) {\n untracked(() => handler.call(this, event));\n };\n\n const setupListener = (onCleanup: EffectCleanupRegisterFn) => {\n const raw = toValue(maybeReactiveTarget);\n const target = unrefElement(raw);\n const event = toValue(maybeReactiveEvent);\n\n if (!target) {\n return;\n }\n\n if (ngDevMode) {\n assertEventTarget(target, 'listener');\n }\n\n target.addEventListener(event, untrackedHandler, nativeOptions);\n\n onCleanup(() => {\n target.removeEventListener(event, untrackedHandler, nativeOptions);\n });\n };\n\n let effectRef: EffectRef;\n\n if (isSyncSetupRequired) {\n effectRef = syncEffect(setupListener);\n } else {\n effectRef = afterRenderEffect({ read: setupListener });\n }\n\n return { destroy: () => effectRef.destroy() };\n });\n}\n\nconst MODIFIERS = new Set<keyof InternalListenerOptions>([\n 'capture',\n 'passive',\n 'once',\n 'stop',\n 'prevent',\n 'self',\n]);\n\nfunction createModifier(applied: InternalListenerOptions): ListenerFunction {\n const modifierFn = ((...args: any[]) => {\n return listenerImpl(applied, ...args);\n }) as ListenerFunction;\n\n return new Proxy(modifierFn, {\n get(target, prop) {\n if (typeof prop !== 'string' || !MODIFIERS.has(prop as any)) {\n return target[prop as keyof typeof target];\n }\n\n if (applied[prop as keyof InternalListenerOptions]) {\n return target;\n }\n\n return createModifier({ ...applied, [prop]: true });\n },\n });\n}\n\nfunction syncEffect(\n effectFn: (onCleanup: EffectCleanupRegisterFn) => void,\n options?: CreateEffectOptions\n): EffectRef {\n const effectRef = effect(effectFn, options);\n const effectNode: BaseEffectNode = (effectRef as any)[SIGNAL];\n try {\n effectNode.run();\n } catch (error) {\n if (ngDevMode) {\n console.warn('[syncEffect] Failed to run effectFn synchronously', error);\n }\n }\n return effectRef;\n}\n\ninterface InternalListenerOptions {\n readonly capture?: boolean;\n readonly passive?: boolean;\n readonly once?: boolean;\n readonly stop?: boolean;\n readonly prevent?: boolean;\n readonly self?: boolean;\n}\n\ninterface InferEventTarget<Events> {\n readonly addEventListener: (event: Events, fn?: any, options?: any) => any;\n readonly removeEventListener: (event: Events, fn?: any, options?: any) => any;\n}\n\ninterface GeneralEventListener<E = Event> {\n (e: E): 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-listener.mjs","sources":["../../../projects/core/browser/listener/index.ts","../../../projects/core/browser/listener/signality-core-browser-listener.ts"],"sourcesContent":["import {\n afterRenderEffect,\n type CreateEffectOptions,\n effect,\n type EffectCleanupRegisterFn,\n type EffectRef,\n untracked,\n} from '@angular/core';\nimport { type BaseEffectNode, SIGNAL } from '@angular/core/primitives/signals';\nimport {\n assertEventTarget,\n NOOP_EFFECT_REF,\n setupContext,\n unrefElement,\n} from '@signality/core/internal';\nimport { toValue } from '@signality/core/utilities';\nimport type { MaybeElementSignal, MaybeSignal, WithInjector } from '@signality/core/types';\n\nexport type ListenerOptions = WithInjector;\n\nexport interface ListenerRef {\n readonly destroy: () => void;\n}\n\nexport interface ListenerFunction {\n <E extends keyof WindowEventMap>(\n target: Window,\n event: MaybeSignal<E>,\n handler: (this: Window, e: WindowEventMap[E]) => any,\n options?: ListenerOptions\n ): ListenerRef;\n\n <E extends keyof DocumentEventMap>(\n target: Document,\n event: MaybeSignal<E>,\n handler: (this: Document, e: DocumentEventMap[E]) => any,\n options?: ListenerOptions\n ): ListenerRef;\n\n <E extends keyof ShadowRootEventMap>(\n target: MaybeSignal<ShadowRoot>,\n event: MaybeSignal<E>,\n handler: (this: ShadowRoot, e: ShadowRootEventMap[E]) => any,\n options?: ListenerOptions\n ): ListenerRef;\n\n <T extends HTMLElement, E extends keyof HTMLElementEventMap>(\n target: MaybeElementSignal<T>,\n event: MaybeSignal<E>,\n handler: (this: T, e: HTMLElementEventMap[E]) => any,\n options?: ListenerOptions\n ): ListenerRef;\n\n <T extends SVGElement, E extends keyof SVGElementEventMap>(\n target: MaybeElementSignal<T>,\n event: MaybeSignal<E>,\n handler: (this: T, e: SVGElementEventMap[E]) => any,\n options?: ListenerOptions\n ): ListenerRef;\n\n <Names extends string>(\n target: MaybeSignal<InferEventTarget<Names>>,\n event: MaybeSignal<Names>,\n handler: (e: Event) => void,\n options?: ListenerOptions\n ): ListenerRef;\n\n <E extends string>(\n target: MaybeSignal<MediaQueryList>,\n event: MaybeSignal<E>,\n handler: (this: MediaQueryList, e: MediaQueryListEvent) => any,\n options?: ListenerOptions\n ): ListenerRef;\n\n <EventType = Event>(\n target: MaybeSignal<EventTarget> | MaybeElementSignal<Element>,\n event: MaybeSignal<string>,\n handler: GeneralEventListener<EventType>,\n options?: ListenerOptions\n ): ListenerRef;\n\n readonly capture: ListenerFunction;\n readonly passive: ListenerFunction;\n readonly once: ListenerFunction;\n readonly stop: ListenerFunction;\n readonly prevent: ListenerFunction;\n readonly self: ListenerFunction;\n}\n\n/**\n * Signal-based wrapper around the [addEventListener](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener) method.\n *\n * @param target - Event target\n * @param event - Event type name\n * @param handler - Event handler function\n * @param options - Optional listener configuration\n * @returns A ListenerRef that can be used to destroy the listener\n *\n * @example\n * ```typescript\n * @Component({\n * template: `<button #btn>Click me</button>`,\n * })\n * export class ListenerDemo {\n * readonly btn = viewChild<ElementRef>('btn');\n *\n * constructor() {\n * listener.capture.prevent(this.btn, 'click', event => {\n * console.log('Button clicked!', event);\n * });\n * }\n * }\n * ```\n */\nexport const listener: ListenerFunction = createModifier({});\n\nlet isSyncSetupRequired = false;\n\n/**\n * By default, `listener()` registers event listeners after the render cycle completes\n * to ensure DOM elements exist. However, global targets (window, document, navigator.*, etc.)\n * are not tied to the render cycle. Use `setupSync()` to wrap listener calls when you need to prevent\n * race conditions where a global event is dispatched before Angular completes its scheduled rendering tasks.\n */\nexport function setupSync<T>(listenerFactoryExecFn: () => T): T {\n isSyncSetupRequired = true;\n\n try {\n return listenerFactoryExecFn();\n } finally {\n isSyncSetupRequired = false;\n }\n}\n\nfunction listenerImpl(applied: InternalListenerOptions, ...args: any[]): ListenerRef {\n const options = args[3] as ListenerOptions | undefined;\n\n const { runInContext } = setupContext(options?.injector, listenerImpl);\n\n return runInContext(({ isServer }) => {\n if (isServer) {\n return NOOP_EFFECT_REF;\n }\n\n const [maybeReactiveTarget, maybeReactiveEvent, rawHandler] = args;\n const { stop, prevent, self, ...nativeOptions } = applied;\n const hasModifiers = stop || prevent || self;\n\n const handler = hasModifiers\n ? function (this: any, event: Event) {\n if (self && event.target !== event.currentTarget) return;\n if (prevent) event.preventDefault();\n if (stop) event.stopPropagation();\n rawHandler.call(this, event);\n }\n : rawHandler;\n\n // Isolate event handler from signal dependency tracking.\n // Angular's template compiler does this via setActiveConsumer(null) in executeListenerWithErrorHandling.\n // Without this, events that fire during change detection (e.g. window 'blur' when the tab loses focus)\n // can trigger NG0600 if the handler writes to a signal while a reactive consumer is active.\n // See: https://github.com/angular/angular/issues/60143\n const untrackedHandler = function (this: any, event: Event) {\n untracked(() => handler.call(this, event));\n };\n\n const setupListener = (onCleanup: EffectCleanupRegisterFn) => {\n const raw = toValue(maybeReactiveTarget);\n const target = unrefElement(raw);\n const event = toValue(maybeReactiveEvent);\n\n if (!target) {\n return;\n }\n\n if (ngDevMode) {\n assertEventTarget(target, 'listener');\n }\n\n target.addEventListener(event, untrackedHandler, nativeOptions);\n\n onCleanup(() => {\n target.removeEventListener(event, untrackedHandler, nativeOptions);\n });\n };\n\n let effectRef: EffectRef;\n\n if (isSyncSetupRequired) {\n effectRef = syncEffect(setupListener);\n } else {\n effectRef = afterRenderEffect({ read: setupListener });\n }\n\n return { destroy: () => effectRef.destroy() };\n });\n}\n\nconst MODIFIERS = new Set<keyof InternalListenerOptions>([\n 'capture',\n 'passive',\n 'once',\n 'stop',\n 'prevent',\n 'self',\n]);\n\nfunction createModifier(applied: InternalListenerOptions): ListenerFunction {\n const modifierFn = ((...args: any[]) => {\n return listenerImpl(applied, ...args);\n }) as ListenerFunction;\n\n return new Proxy(modifierFn, {\n get(target, prop) {\n if (typeof prop !== 'string' || !MODIFIERS.has(prop as any)) {\n return target[prop as keyof typeof target];\n }\n\n if (applied[prop as keyof InternalListenerOptions]) {\n return target;\n }\n\n return createModifier({ ...applied, [prop]: true });\n },\n });\n}\n\nfunction syncEffect(\n effectFn: (onCleanup: EffectCleanupRegisterFn) => void,\n options?: CreateEffectOptions\n): EffectRef {\n const effectRef = effect(effectFn, options);\n const effectNode: BaseEffectNode = (effectRef as any)[SIGNAL];\n try {\n effectNode.run();\n } catch (error) {\n if (ngDevMode) {\n console.warn('[syncEffect] Failed to run effectFn synchronously', error);\n }\n }\n return effectRef;\n}\n\ninterface InternalListenerOptions {\n readonly capture?: boolean;\n readonly passive?: boolean;\n readonly once?: boolean;\n readonly stop?: boolean;\n readonly prevent?: boolean;\n readonly self?: boolean;\n}\n\ninterface InferEventTarget<Events> {\n readonly addEventListener: (event: Events, fn?: any, options?: any) => any;\n readonly removeEventListener: (event: Events, fn?: any, options?: any) => any;\n}\n\ninterface GeneralEventListener<E = Event> {\n (e: E): void;\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
@@ -25,7 +25,7 @@ import { onDisconnect } from '@signality/core/elements/on-disconnect';
25
25
  * `
26
26
  * })
27
27
  * export class PiPDemo {
28
- * readonly video = viewChild<HTMLVideoElement>('video');
28
+ * readonly video = viewChild<ElementRef>('video');
29
29
  * readonly pip = pictureInPicture(this.video);
30
30
  * }
31
31
  * ```
@@ -46,21 +46,15 @@ function pictureInPicture(target, options) {
46
46
  const isActive = signal(false, ...(ngDevMode ? [{ debugName: "isActive" }] : []));
47
47
  const enter = async () => {
48
48
  const targetEl = toElement.untracked(target);
49
- assertElement(targetEl, 'pictureInPicture');
50
- await targetEl.requestPictureInPicture();
49
+ ngDevMode && assertElement(targetEl, 'pictureInPicture');
50
+ await targetEl?.requestPictureInPicture();
51
51
  };
52
52
  const exit = async () => {
53
53
  const targetEl = toElement.untracked(target);
54
+ ngDevMode && assertElement(targetEl, 'pictureInPicture');
54
55
  const pipEl = getPipElement(document);
55
56
  if (targetEl === pipEl) {
56
- try {
57
- await document.exitPictureInPicture();
58
- }
59
- catch (error) {
60
- if (ngDevMode) {
61
- console.warn(`[pictureInPicture] Failed to exit Picture-in-Picture mode.`, error);
62
- }
63
- }
57
+ await document.exitPictureInPicture();
64
58
  }
65
59
  };
66
60
  const toggle = async () => {
@@ -84,7 +78,9 @@ function pictureInPicture(target, options) {
84
78
  console.warn(`[pictureInPicture] Failed to exit Picture-in-Picture mode on disconnect.`, error);
85
79
  }
86
80
  }
87
- isActive.set(false);
81
+ finally {
82
+ isActive.set(false);
83
+ }
88
84
  }
89
85
  });
90
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} 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 * @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;;;;"}