@angular-helpers/browser-web-apis 21.10.0 → 21.12.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.
package/README.es.md CHANGED
@@ -45,6 +45,9 @@ Paquete de servicios Angular para acceder de forma estructurada y segura a Brows
45
45
  - `IdleDetectorService` - Detectar estado idle del usuario y bloqueo de pantalla
46
46
  - `GamepadService` - Polling de entrada de controladores de juego
47
47
  - `WebAudioService` - Contexto de audio, osciladores y analizadores
48
+ - `WebLocksService` - Coordinacion de locks entre pestanas
49
+ - `StorageManagerService` - Cuotas y persistencia de almacenamiento
50
+ - `CompressionService` - Streams de compresion gzip/deflate
48
51
 
49
52
  ### APIs de red
50
53
 
@@ -706,6 +709,88 @@ export class MyComponent {
706
709
  }
707
710
  ```
708
711
 
712
+ ### injectClipboard
713
+
714
+ ```typescript
715
+ import { injectClipboard } from '@angular-helpers/browser-web-apis';
716
+
717
+ @Component({...})
718
+ export class MyComponent {
719
+ readonly cb = injectClipboard();
720
+
721
+ // cb.text() → string | null (ultimo texto copiado)
722
+ // cb.error() → string | null
723
+ // cb.busy() → boolean
724
+ // cb.isSupported() → boolean
725
+
726
+ async copy(text: string) {
727
+ await this.cb.writeText(text);
728
+ }
729
+ }
730
+ ```
731
+
732
+ ### injectGeolocation
733
+
734
+ ```typescript
735
+ import { injectGeolocation } from '@angular-helpers/browser-web-apis';
736
+
737
+ @Component({...})
738
+ export class MyComponent {
739
+ readonly geo = injectGeolocation({ watch: true });
740
+
741
+ // geo.position() → GeolocationPosition | null
742
+ // geo.error() → GeolocationPositionError | null
743
+ // geo.watching() → boolean
744
+ // geo.isSupported() → boolean
745
+
746
+ stopWatching() {
747
+ this.geo.stop();
748
+ }
749
+ }
750
+ ```
751
+
752
+ ### injectBattery
753
+
754
+ ```typescript
755
+ import { injectBattery } from '@angular-helpers/browser-web-apis';
756
+
757
+ @Component({...})
758
+ export class MyComponent {
759
+ readonly battery = injectBattery();
760
+
761
+ // battery.info() → BatteryInfo | null (nivel, carga, tiempos)
762
+ // battery.error() → string | null
763
+ // battery.isSupported() → boolean
764
+
765
+ async refresh() {
766
+ await this.battery.refresh();
767
+ }
768
+ }
769
+ ```
770
+
771
+ ### injectWakeLock
772
+
773
+ ```typescript
774
+ import { injectWakeLock } from '@angular-helpers/browser-web-apis';
775
+
776
+ @Component({...})
777
+ export class MyComponent {
778
+ readonly wakeLock = injectWakeLock();
779
+
780
+ // wakeLock.active() → boolean
781
+ // wakeLock.error() → string | null
782
+ // wakeLock.isSupported() → boolean
783
+
784
+ async toggle() {
785
+ if (this.wakeLock.active()) {
786
+ await this.wakeLock.release();
787
+ } else {
788
+ await this.wakeLock.request();
789
+ }
790
+ }
791
+ }
792
+ ```
793
+
709
794
  ### Tipo ElementInput
710
795
 
711
796
  Tanto `injectResizeObserver` como `injectIntersectionObserver` aceptan el tipo `ElementInput`:
package/README.md CHANGED
@@ -45,6 +45,9 @@ Angular services package for a structured and secure access layer over browser W
45
45
  - `IdleDetectorService` - Detect user idle state and screen lock
46
46
  - `GamepadService` - Game controller input polling
47
47
  - `WebAudioService` - Audio context, oscillators, and analysers
48
+ - `WebLocksService` - Cross-tab resource locking coordination
49
+ - `StorageManagerService` - Storage quotas and persistence
50
+ - `CompressionService` - Gzip/deflate compression streams
48
51
 
49
52
  ### Network APIs
50
53
 
@@ -101,13 +104,21 @@ npm install @angular-helpers/browser-web-apis
101
104
 
102
105
  ```typescript
103
106
  import { provideBrowserWebApis } from '@angular-helpers/browser-web-apis';
107
+ import {
108
+ CameraService,
109
+ GeolocationService,
110
+ NotificationService,
111
+ } from '@angular-helpers/browser-web-apis';
104
112
 
105
113
  bootstrapApplication(AppComponent, {
106
114
  providers: [
107
115
  provideBrowserWebApis({
108
- enableCamera: true,
109
- enableGeolocation: true,
110
- enableNotifications: true,
116
+ services: [
117
+ CameraService,
118
+ GeolocationService,
119
+ NotificationService,
120
+ // Add more services as needed
121
+ ],
111
122
  }),
112
123
  ],
113
124
  });
@@ -152,6 +163,9 @@ Every service has a matching `provideX()` function:
152
163
  | `providePerformanceObserver()` | `PerformanceObserverService` |
153
164
  | `providePageVisibility()` | `PageVisibilityService` |
154
165
  | `provideNetworkInformation()` | `NetworkInformationService` |
166
+ | `provideWebLocks()` | `WebLocksService` |
167
+ | `provideStorageManager()` | `StorageManagerService` |
168
+ | `provideCompression()` | `CompressionService` |
155
169
  | …and 22 more | See `src/providers/` |
156
170
 
157
171
  ### Combo providers
@@ -755,6 +769,88 @@ export class MyComponent {
755
769
  }
756
770
  ```
757
771
 
772
+ ### injectClipboard
773
+
774
+ ```typescript
775
+ import { injectClipboard } from '@angular-helpers/browser-web-apis';
776
+
777
+ @Component({...})
778
+ export class MyComponent {
779
+ readonly cb = injectClipboard();
780
+
781
+ // cb.text() → string | null (last copied text)
782
+ // cb.error() → string | null
783
+ // cb.busy() → boolean
784
+ // cb.isSupported() → boolean
785
+
786
+ async copy(text: string) {
787
+ await this.cb.writeText(text);
788
+ }
789
+ }
790
+ ```
791
+
792
+ ### injectGeolocation
793
+
794
+ ```typescript
795
+ import { injectGeolocation } from '@angular-helpers/browser-web-apis';
796
+
797
+ @Component({...})
798
+ export class MyComponent {
799
+ readonly geo = injectGeolocation({ watch: true });
800
+
801
+ // geo.position() → GeolocationPosition | null
802
+ // geo.error() → GeolocationPositionError | null
803
+ // geo.watching() → boolean
804
+ // geo.isSupported() → boolean
805
+
806
+ stopWatching() {
807
+ this.geo.stop();
808
+ }
809
+ }
810
+ ```
811
+
812
+ ### injectBattery
813
+
814
+ ```typescript
815
+ import { injectBattery } from '@angular-helpers/browser-web-apis';
816
+
817
+ @Component({...})
818
+ export class MyComponent {
819
+ readonly battery = injectBattery();
820
+
821
+ // battery.info() → BatteryInfo | null (level, charging, times)
822
+ // battery.error() → string | null
823
+ // battery.isSupported() → boolean
824
+
825
+ async refresh() {
826
+ await this.battery.refresh();
827
+ }
828
+ }
829
+ ```
830
+
831
+ ### injectWakeLock
832
+
833
+ ```typescript
834
+ import { injectWakeLock } from '@angular-helpers/browser-web-apis';
835
+
836
+ @Component({...})
837
+ export class MyComponent {
838
+ readonly wakeLock = injectWakeLock();
839
+
840
+ // wakeLock.active() → boolean
841
+ // wakeLock.error() → string | null
842
+ // wakeLock.isSupported() → boolean
843
+
844
+ async toggle() {
845
+ if (this.wakeLock.active()) {
846
+ await this.wakeLock.release();
847
+ } else {
848
+ await this.wakeLock.request();
849
+ }
850
+ }
851
+ }
852
+ ```
853
+
758
854
  ### ElementInput type
759
855
 
760
856
  Both `injectResizeObserver` and `injectIntersectionObserver` accept the `ElementInput` type:
@@ -1,22 +1,66 @@
1
1
  import * as i0 from '@angular/core';
2
- import { InjectionToken, Injectable, inject, DestroyRef, PLATFORM_ID, signal, computed, isSignal, effect, ElementRef, makeEnvironmentProviders } from '@angular/core';
2
+ import { InjectionToken, isDevMode, inject, Injectable, DestroyRef, PLATFORM_ID, signal, computed, isSignal, effect, ElementRef, makeEnvironmentProviders } from '@angular/core';
3
3
  import { isPlatformBrowser, isPlatformServer } from '@angular/common';
4
4
  import { Observable, fromEvent, Subject, of, map as map$1 } from 'rxjs';
5
5
  import { toObservable, takeUntilDestroyed } from '@angular/core/rxjs-interop';
6
6
  import { filter, map, distinctUntilChanged } from 'rxjs/operators';
7
7
  import { Router } from '@angular/router';
8
8
 
9
+ const LEVEL_RANK = {
10
+ debug: 10,
11
+ info: 20,
12
+ warn: 30,
13
+ error: 40,
14
+ silent: 100,
15
+ };
16
+ /**
17
+ * Global log level for `BROWSER_API_LOGGER`. Defaults to `'debug'` in dev mode and
18
+ * `'warn'` in production. Override via `provideBrowserApiLogLevel('silent')` etc.
19
+ */
20
+ const BROWSER_API_LOG_LEVEL = new InjectionToken('BROWSER_API_LOG_LEVEL', {
21
+ providedIn: 'root',
22
+ factory: () => (isDevMode() ? 'debug' : 'warn'),
23
+ });
9
24
  const BROWSER_API_LOGGER = new InjectionToken('BROWSER_API_LOGGER', {
10
25
  providedIn: 'root',
11
- factory: () => ({
12
- // oxlint-disable-next-line no-console
13
- info: (message) => console.info(message),
14
- // oxlint-disable-next-line no-console
15
- warn: (message) => console.warn(message),
16
- // oxlint-disable-next-line no-console
17
- error: (message, error) => console.error(message, error),
18
- }),
26
+ factory: () => {
27
+ const level = inject(BROWSER_API_LOG_LEVEL);
28
+ return createLevelGatedLogger(level);
29
+ },
19
30
  });
31
+ function createLevelGatedLogger(level) {
32
+ const min = LEVEL_RANK[level];
33
+ return {
34
+ debug: (message) => {
35
+ if (LEVEL_RANK.debug < min)
36
+ return;
37
+ // oxlint-disable-next-line no-console
38
+ console.debug(message);
39
+ },
40
+ info: (message) => {
41
+ if (LEVEL_RANK.info < min)
42
+ return;
43
+ // oxlint-disable-next-line no-console
44
+ console.info(message);
45
+ },
46
+ warn: (message) => {
47
+ if (LEVEL_RANK.warn < min)
48
+ return;
49
+ // oxlint-disable-next-line no-console
50
+ console.warn(message);
51
+ },
52
+ error: (message, error) => {
53
+ if (LEVEL_RANK.error < min)
54
+ return;
55
+ // oxlint-disable-next-line no-console
56
+ console.error(message, error);
57
+ },
58
+ };
59
+ }
60
+ /** Log level helper provider. Overrides the default `BROWSER_API_LOG_LEVEL`. */
61
+ function provideBrowserApiLogLevel(level) {
62
+ return { provide: BROWSER_API_LOG_LEVEL, useValue: level };
63
+ }
20
64
 
21
65
  const BROWSER_CAPABILITIES = [
22
66
  { id: 'permissions', label: 'Permissions API', requiresSecureContext: false },
@@ -267,6 +311,9 @@ class BrowserApiBaseService {
267
311
  logInfo(message) {
268
312
  this.logger.info(`[${this.getApiName()}] ${message}`);
269
313
  }
314
+ logDebug(message) {
315
+ this.logger.debug?.(`[${this.getApiName()}] ${message}`);
316
+ }
270
317
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: BrowserApiBaseService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
271
318
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: BrowserApiBaseService });
272
319
  }
@@ -1004,19 +1051,22 @@ let legacyDeprecationLogged$1 = false;
1004
1051
  class WebStorageService extends BrowserApiBaseService {
1005
1052
  storageLogger = inject(BROWSER_API_LOGGER);
1006
1053
  storageEvents = signal(null, ...(ngDevMode ? [{ debugName: "storageEvents" }] : /* istanbul ignore next */ []));
1007
- eventBus = {
1008
- emit: (event) => this.storageEvents.set(event),
1009
- events$: toObservable(this.storageEvents).pipe(filter((event) => event !== null), distinctUntilChanged((a, b) => a.key === b.key &&
1010
- a.newValue === b.newValue &&
1011
- a.oldValue === b.oldValue &&
1012
- a.storageArea === b.storageArea)),
1013
- };
1054
+ eventBus;
1014
1055
  /** Local storage namespace. */
1015
- local = new StorageNamespaceImpl('localStorage', this.eventBus, this.storageLogger);
1056
+ local;
1016
1057
  /** Session storage namespace. */
1017
- session = new StorageNamespaceImpl('sessionStorage', this.eventBus, this.storageLogger);
1058
+ session;
1018
1059
  constructor() {
1019
1060
  super();
1061
+ this.eventBus = {
1062
+ emit: (event) => this.storageEvents.set(event),
1063
+ events$: toObservable(this.storageEvents).pipe(filter((event) => event !== null), distinctUntilChanged((a, b) => a.key === b.key &&
1064
+ a.newValue === b.newValue &&
1065
+ a.oldValue === b.oldValue &&
1066
+ a.storageArea === b.storageArea)),
1067
+ };
1068
+ this.local = new StorageNamespaceImpl('localStorage', this.eventBus, this.storageLogger);
1069
+ this.session = new StorageNamespaceImpl('sessionStorage', this.eventBus, this.storageLogger);
1020
1070
  this.setupCrossTabListener();
1021
1071
  }
1022
1072
  getApiName() {
@@ -1660,6 +1710,8 @@ class WebWorkerService extends BrowserApiBaseService {
1660
1710
  this.entries.delete(name);
1661
1711
  }
1662
1712
  terminateAllWorkers() {
1713
+ // Snapshot keys before mutating the map inside terminateWorker.
1714
+ // oxlint-disable-next-line unicorn/no-useless-spread
1663
1715
  for (const name of [...this.entries.keys()]) {
1664
1716
  this.terminateWorker(name);
1665
1717
  }
@@ -2069,17 +2121,17 @@ class BroadcastChannelService extends ConnectionRegistryBaseService {
2069
2121
  if (!channel) {
2070
2122
  channel = new BroadcastChannel(name);
2071
2123
  this.connections.set(name, channel);
2124
+ // Register cleanup once per channel, not per subscription.
2125
+ this.destroyRef.onDestroy(() => this.close(name));
2072
2126
  }
2073
2127
  const handler = (event) => observer.next(event.data);
2074
2128
  const errorHandler = () => observer.error(new Error(`BroadcastChannel "${name}" error`));
2075
2129
  channel.addEventListener('message', handler);
2076
2130
  channel.addEventListener('messageerror', errorHandler);
2077
- const cleanup = () => {
2131
+ return () => {
2078
2132
  channel.removeEventListener('message', handler);
2079
2133
  channel.removeEventListener('messageerror', errorHandler);
2080
2134
  };
2081
- this.destroyRef.onDestroy(() => this.close(name));
2082
- return cleanup;
2083
2135
  });
2084
2136
  }
2085
2137
  post(name, data) {
@@ -2088,6 +2140,7 @@ class BroadcastChannelService extends ConnectionRegistryBaseService {
2088
2140
  if (!channel) {
2089
2141
  channel = new BroadcastChannel(name);
2090
2142
  this.connections.set(name, channel);
2143
+ // Register cleanup once per channel creation.
2091
2144
  this.destroyRef.onDestroy(() => this.close(name));
2092
2145
  }
2093
2146
  channel.postMessage(data);
@@ -2178,6 +2231,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImpor
2178
2231
 
2179
2232
  class ScreenWakeLockService extends BrowserApiBaseService {
2180
2233
  sentinel = null;
2234
+ releaseRegistered = false;
2181
2235
  getApiName() {
2182
2236
  return 'screen-wake-lock';
2183
2237
  }
@@ -2199,7 +2253,11 @@ class ScreenWakeLockService extends BrowserApiBaseService {
2199
2253
  this.sentinel.addEventListener('release', () => {
2200
2254
  this.sentinel = null;
2201
2255
  });
2202
- this.destroyRef.onDestroy(() => this.release());
2256
+ // Register the destroy cleanup only once across multiple request() calls.
2257
+ if (!this.releaseRegistered) {
2258
+ this.releaseRegistered = true;
2259
+ this.destroyRef.onDestroy(() => this.release());
2260
+ }
2203
2261
  return { active: true, type, released: false };
2204
2262
  }
2205
2263
  catch (error) {
@@ -2236,9 +2294,7 @@ class ScreenWakeLockService extends BrowserApiBaseService {
2236
2294
  };
2237
2295
  document.addEventListener('visibilitychange', handleVisibilityChange);
2238
2296
  emit();
2239
- const cleanup = () => document.removeEventListener('visibilitychange', handleVisibilityChange);
2240
- this.destroyRef.onDestroy(cleanup);
2241
- return cleanup;
2297
+ return () => document.removeEventListener('visibilitychange', handleVisibilityChange);
2242
2298
  });
2243
2299
  }
2244
2300
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: ScreenWakeLockService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
@@ -2398,12 +2454,10 @@ class FullscreenService extends BrowserApiBaseService {
2398
2454
  document.addEventListener('fullscreenchange', handler);
2399
2455
  document.addEventListener('webkitfullscreenchange', handler);
2400
2456
  observer.next(this.isFullscreen);
2401
- const cleanup = () => {
2457
+ return () => {
2402
2458
  document.removeEventListener('fullscreenchange', handler);
2403
2459
  document.removeEventListener('webkitfullscreenchange', handler);
2404
2460
  };
2405
- this.destroyRef.onDestroy(cleanup);
2406
- return cleanup;
2407
2461
  });
2408
2462
  }
2409
2463
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: FullscreenService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
@@ -2639,11 +2693,9 @@ class ServerSentEventsService extends ConnectionRegistryBaseService {
2639
2693
  source.addEventListener(type, messageHandler);
2640
2694
  }
2641
2695
  }
2642
- const cleanup = () => {
2696
+ return () => {
2643
2697
  this.disconnect(url);
2644
2698
  };
2645
- this.destroyRef.onDestroy(cleanup);
2646
- return cleanup;
2647
2699
  });
2648
2700
  }
2649
2701
  disconnect(url) {
@@ -2746,9 +2798,7 @@ class SpeechSynthesisService extends BrowserApiBaseService {
2746
2798
  const emit = () => observer.next(speechSynthesis.getVoices());
2747
2799
  speechSynthesis.addEventListener('voiceschanged', emit);
2748
2800
  emit();
2749
- const cleanup = () => speechSynthesis.removeEventListener('voiceschanged', emit);
2750
- this.destroyRef.onDestroy(cleanup);
2751
- return cleanup;
2801
+ return () => speechSynthesis.removeEventListener('voiceschanged', emit);
2752
2802
  });
2753
2803
  }
2754
2804
  speak(text, options = {}) {
@@ -2777,12 +2827,19 @@ class SpeechSynthesisService extends BrowserApiBaseService {
2777
2827
  };
2778
2828
  observer.next('speaking');
2779
2829
  speechSynthesis.speak(utterance);
2780
- const cleanup = () => {
2830
+ let completed = false;
2831
+ const origOnEnd = utterance.onend;
2832
+ utterance.onend = (ev) => {
2833
+ completed = true;
2834
+ origOnEnd?.call(utterance, ev);
2835
+ };
2836
+ return () => {
2781
2837
  speechSynthesis.cancel();
2782
- observer.next('idle');
2838
+ // Only emit idle if the Observable hasn't already completed via onend/onerror.
2839
+ if (!completed) {
2840
+ observer.next('idle');
2841
+ }
2783
2842
  };
2784
- this.destroyRef.onDestroy(cleanup);
2785
- return cleanup;
2786
2843
  });
2787
2844
  }
2788
2845
  pause() {
@@ -2894,6 +2951,7 @@ class WebAudioService extends BrowserApiBaseService {
2894
2951
  return 'web-audio';
2895
2952
  }
2896
2953
  context = null;
2954
+ destroyRegistered = false;
2897
2955
  getCapabilityId() {
2898
2956
  return 'webAudio';
2899
2957
  }
@@ -2903,7 +2961,10 @@ class WebAudioService extends BrowserApiBaseService {
2903
2961
  }
2904
2962
  if (!this.context || this.context.state === 'closed') {
2905
2963
  this.context = new AudioContext();
2906
- this.destroyRef.onDestroy(() => this.close());
2964
+ if (!this.destroyRegistered) {
2965
+ this.destroyRegistered = true;
2966
+ this.destroyRef.onDestroy(() => this.close());
2967
+ }
2907
2968
  }
2908
2969
  return this.context;
2909
2970
  }
@@ -3098,6 +3159,121 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImpor
3098
3159
  type: Injectable
3099
3160
  }] });
3100
3161
 
3162
+ class EyeDropperService extends BrowserApiBaseService {
3163
+ getApiName() {
3164
+ return 'eye-dropper';
3165
+ }
3166
+ getCapabilityId() {
3167
+ return 'eyeDropper';
3168
+ }
3169
+ /** Override to also assert secure context (required by the spec). */
3170
+ isSupported() {
3171
+ return (super.isSupported() &&
3172
+ typeof window !== 'undefined' &&
3173
+ 'EyeDropper' in window &&
3174
+ window.isSecureContext);
3175
+ }
3176
+ ensureSupported() {
3177
+ super.ensureSupported();
3178
+ if (!window.isSecureContext) {
3179
+ throw new Error('EyeDropper API requires a secure context (HTTPS)');
3180
+ }
3181
+ }
3182
+ /**
3183
+ * Opens the system eye dropper tool and returns the selected color.
3184
+ *
3185
+ * @param options Optional configuration including an AbortSignal to cancel the dropper.
3186
+ * @returns A promise that resolves with the selected color in sRGB Hex format (e.g., "#000000").
3187
+ * @throws DOMException if the user cancels the selection (AbortError).
3188
+ */
3189
+ async open(options) {
3190
+ this.ensureSupported();
3191
+ const EyeDropperClass = window.EyeDropper;
3192
+ if (!EyeDropperClass) {
3193
+ throw new Error('EyeDropper is not supported in this browser.');
3194
+ }
3195
+ const eyeDropper = new EyeDropperClass();
3196
+ return eyeDropper.open(options);
3197
+ }
3198
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: EyeDropperService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
3199
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: EyeDropperService });
3200
+ }
3201
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: EyeDropperService, decorators: [{
3202
+ type: Injectable
3203
+ }] });
3204
+
3205
+ class IdleDetectorService extends BrowserApiBaseService {
3206
+ getApiName() {
3207
+ return 'idle-detector';
3208
+ }
3209
+ getCapabilityId() {
3210
+ return 'idleDetector';
3211
+ }
3212
+ isSupported() {
3213
+ return (super.isSupported() &&
3214
+ typeof window !== 'undefined' &&
3215
+ 'IdleDetector' in window &&
3216
+ window.isSecureContext);
3217
+ }
3218
+ ensureSupported() {
3219
+ super.ensureSupported();
3220
+ if (!window.isSecureContext) {
3221
+ throw new Error('IdleDetector API requires a secure context (HTTPS)');
3222
+ }
3223
+ }
3224
+ async requestPermission() {
3225
+ this.ensureSupported();
3226
+ const IdleDetectorClass = window.IdleDetector;
3227
+ return IdleDetectorClass.requestPermission();
3228
+ }
3229
+ /**
3230
+ * Starts tracking idle state. Emits the current state and subsequent changes.
3231
+ * Note: You must call requestPermission() and be granted access before starting.
3232
+ *
3233
+ * @param options Configuration for the idle detector, including the threshold (minimum 60000ms).
3234
+ * @returns An Observable of the IdleState.
3235
+ */
3236
+ watch(options) {
3237
+ return new Observable((observer) => {
3238
+ this.ensureSupported();
3239
+ const IdleDetectorClass = window.IdleDetector;
3240
+ const detector = new IdleDetectorClass();
3241
+ const emit = () => {
3242
+ observer.next({
3243
+ userState: detector.userState,
3244
+ screenState: detector.screenState,
3245
+ });
3246
+ };
3247
+ detector.addEventListener('change', emit);
3248
+ // We use a custom AbortController if one wasn't provided,
3249
+ // so we can abort the detector when the observable is unsubscribed.
3250
+ const abortController = new AbortController();
3251
+ const signal = options.signal || abortController.signal;
3252
+ const combinedOptions = { ...options, signal };
3253
+ // Stop tracking if an external signal aborts
3254
+ if (options.signal) {
3255
+ options.signal.addEventListener('abort', () => {
3256
+ observer.complete();
3257
+ }, { once: true });
3258
+ }
3259
+ detector.start(combinedOptions).catch((err) => {
3260
+ observer.error(err);
3261
+ });
3262
+ return () => {
3263
+ detector.removeEventListener('change', emit);
3264
+ if (!signal.aborted) {
3265
+ abortController.abort();
3266
+ }
3267
+ };
3268
+ });
3269
+ }
3270
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: IdleDetectorService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
3271
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: IdleDetectorService });
3272
+ }
3273
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: IdleDetectorService, decorators: [{
3274
+ type: Injectable
3275
+ }] });
3276
+
3101
3277
  /**
3102
3278
  * Service wrapping `navigator.locks` (Web Locks API). Coordinates exclusive or
3103
3279
  * shared access to a named resource across tabs and workers.
@@ -3285,6 +3461,35 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImpor
3285
3461
  type: Injectable
3286
3462
  }] });
3287
3463
 
3464
+ /**
3465
+ * When set to `true`, suppresses the one-time runtime warning that experimental
3466
+ * services emit on first use. Default is `false` (warnings emitted).
3467
+ */
3468
+ const BROWSER_API_EXPERIMENTAL_SILENT = new InjectionToken('BROWSER_API_EXPERIMENTAL_SILENT', {
3469
+ providedIn: 'root',
3470
+ factory: () => false,
3471
+ });
3472
+ const warned = new Set();
3473
+ /**
3474
+ * Emit a one-time runtime warning explaining that the API is experimental and
3475
+ * subject to breaking changes. No-op after the first call for a given key, or if
3476
+ * the consumer has provided `BROWSER_API_EXPERIMENTAL_SILENT` as `true`.
3477
+ */
3478
+ function warnExperimental(key, context) {
3479
+ if (context.silent)
3480
+ return;
3481
+ if (warned.has(key))
3482
+ return;
3483
+ warned.add(key);
3484
+ context.logger.warn(`[browser-web-apis:experimental] "${key}" is part of the experimental surface ` +
3485
+ 'and may change without a major bump. Provide BROWSER_API_EXPERIMENTAL_SILENT=true ' +
3486
+ 'to suppress this warning.');
3487
+ }
3488
+ /** Test-only: reset the in-memory dedup set. */
3489
+ function resetExperimentalWarnings() {
3490
+ warned.clear();
3491
+ }
3492
+
3288
3493
  // Common types for browser APIs
3289
3494
 
3290
3495
  function injectPageVisibility() {
@@ -3662,6 +3867,9 @@ function injectWakeLock() {
3662
3867
  let sentinel = null;
3663
3868
  let disposed = false;
3664
3869
  const onRelease = () => {
3870
+ if (sentinel) {
3871
+ sentinel.removeEventListener('release', onRelease);
3872
+ }
3665
3873
  if (!disposed)
3666
3874
  active.set(false);
3667
3875
  sentinel = null;
@@ -3701,8 +3909,11 @@ function injectWakeLock() {
3701
3909
  };
3702
3910
  destroyRef.onDestroy(() => {
3703
3911
  disposed = true;
3704
- if (sentinel && !sentinel.released) {
3705
- void sentinel.release();
3912
+ if (sentinel) {
3913
+ sentinel.removeEventListener('release', onRelease);
3914
+ if (!sentinel.released) {
3915
+ void sentinel.release();
3916
+ }
3706
3917
  }
3707
3918
  sentinel = null;
3708
3919
  });
@@ -3715,6 +3926,153 @@ function injectWakeLock() {
3715
3926
  };
3716
3927
  }
3717
3928
 
3929
+ function injectEyeDropper() {
3930
+ const platformId = inject(PLATFORM_ID);
3931
+ const isBrowser = isPlatformBrowser(platformId);
3932
+ const hasDropper = isBrowser && typeof window !== 'undefined' && 'EyeDropper' in window;
3933
+ const supported = signal(hasDropper && window.isSecureContext, ...(ngDevMode ? [{ debugName: "supported" }] : /* istanbul ignore next */ []));
3934
+ const color = signal(null, ...(ngDevMode ? [{ debugName: "color" }] : /* istanbul ignore next */ []));
3935
+ const error = signal(null, ...(ngDevMode ? [{ debugName: "error" }] : /* istanbul ignore next */ []));
3936
+ const isOpening = signal(false, ...(ngDevMode ? [{ debugName: "isOpening" }] : /* istanbul ignore next */ []));
3937
+ const open = async (options) => {
3938
+ if (!supported()) {
3939
+ error.set(new Error('EyeDropper API is not supported in this environment'));
3940
+ return null;
3941
+ }
3942
+ const EyeDropperClass = window.EyeDropper;
3943
+ const dropper = new EyeDropperClass();
3944
+ isOpening.set(true);
3945
+ error.set(null);
3946
+ try {
3947
+ const result = await dropper.open(options);
3948
+ color.set(result.sRGBHex);
3949
+ return result;
3950
+ }
3951
+ catch (e) {
3952
+ if (e instanceof DOMException && e.name === 'AbortError') {
3953
+ // User canceled, not an error
3954
+ return null;
3955
+ }
3956
+ const err = e instanceof Error ? e : new Error(String(e));
3957
+ error.set(err);
3958
+ return null;
3959
+ }
3960
+ finally {
3961
+ isOpening.set(false);
3962
+ }
3963
+ };
3964
+ return {
3965
+ isSupported: supported.asReadonly(),
3966
+ color: color.asReadonly(),
3967
+ error: error.asReadonly(),
3968
+ isOpening: isOpening.asReadonly(),
3969
+ open,
3970
+ };
3971
+ }
3972
+
3973
+ function injectIdleDetector() {
3974
+ const platformId = inject(PLATFORM_ID);
3975
+ const destroyRef = inject(DestroyRef);
3976
+ const isBrowser = isPlatformBrowser(platformId);
3977
+ const hasDetector = isBrowser && typeof window !== 'undefined' && 'IdleDetector' in window;
3978
+ const supported = signal(hasDetector && window.isSecureContext, ...(ngDevMode ? [{ debugName: "supported" }] : /* istanbul ignore next */ []));
3979
+ const state = signal(null, ...(ngDevMode ? [{ debugName: "state" }] : /* istanbul ignore next */ []));
3980
+ const error = signal(null, ...(ngDevMode ? [{ debugName: "error" }] : /* istanbul ignore next */ []));
3981
+ const isTracking = signal(false, ...(ngDevMode ? [{ debugName: "isTracking" }] : /* istanbul ignore next */ []));
3982
+ let detector = null;
3983
+ let abortController = null;
3984
+ let disposed = false;
3985
+ const onStateChange = () => {
3986
+ if (disposed || !detector)
3987
+ return;
3988
+ state.set({
3989
+ userState: detector.userState,
3990
+ screenState: detector.screenState,
3991
+ });
3992
+ };
3993
+ const stop = () => {
3994
+ if (detector) {
3995
+ detector.removeEventListener('change', onStateChange);
3996
+ }
3997
+ if (abortController) {
3998
+ abortController.abort();
3999
+ abortController = null;
4000
+ }
4001
+ detector = null;
4002
+ if (!disposed) {
4003
+ isTracking.set(false);
4004
+ }
4005
+ };
4006
+ destroyRef.onDestroy(() => {
4007
+ disposed = true;
4008
+ stop();
4009
+ });
4010
+ const requestPermission = async () => {
4011
+ if (!supported())
4012
+ return 'denied';
4013
+ const IdleDetectorClass = window.IdleDetector;
4014
+ return IdleDetectorClass.requestPermission();
4015
+ };
4016
+ const start = async (options) => {
4017
+ if (!supported() || disposed) {
4018
+ error.set(new Error('IdleDetector API is not supported in this environment'));
4019
+ return;
4020
+ }
4021
+ stop();
4022
+ error.set(null);
4023
+ const IdleDetectorClass = window.IdleDetector;
4024
+ detector = new IdleDetectorClass();
4025
+ abortController = new AbortController();
4026
+ const combinedSignal = options.signal
4027
+ ? abortSignalAny([options.signal, abortController.signal])
4028
+ : abortController.signal;
4029
+ detector.addEventListener('change', onStateChange);
4030
+ try {
4031
+ await detector.start({ ...options, signal: combinedSignal });
4032
+ if (!disposed) {
4033
+ isTracking.set(true);
4034
+ // Initial state emit
4035
+ onStateChange();
4036
+ }
4037
+ }
4038
+ catch (e) {
4039
+ stop();
4040
+ if (!disposed) {
4041
+ if (e instanceof DOMException && e.name === 'AbortError') {
4042
+ // Expected when stopped manually
4043
+ return;
4044
+ }
4045
+ error.set(e instanceof Error ? e : new Error(String(e)));
4046
+ }
4047
+ }
4048
+ };
4049
+ // Helper to combine abort signals since AbortSignal.any is relatively new
4050
+ function abortSignalAny(signals) {
4051
+ if (typeof AbortSignal !== 'undefined' && 'any' in AbortSignal) {
4052
+ return AbortSignal.any(signals);
4053
+ }
4054
+ const controller = new AbortController();
4055
+ for (const s of signals) {
4056
+ if (s.aborted) {
4057
+ controller.abort(s.reason);
4058
+ return controller.signal;
4059
+ }
4060
+ s.addEventListener('abort', () => controller.abort(s.reason), { once: true });
4061
+ }
4062
+ return controller.signal;
4063
+ }
4064
+ return {
4065
+ isSupported: supported.asReadonly(),
4066
+ state: state.asReadonly(),
4067
+ error: error.asReadonly(),
4068
+ isTracking: isTracking.asReadonly(),
4069
+ isIdle: computed(() => state()?.userState === 'idle'),
4070
+ start,
4071
+ stop,
4072
+ requestPermission,
4073
+ };
4074
+ }
4075
+
3718
4076
  class BrowserSupportUtil {
3719
4077
  static isSupported(feature) {
3720
4078
  if (typeof window === 'undefined' || typeof navigator === 'undefined') {
@@ -3820,6 +4178,7 @@ const permissionGuard = (permission) => {
3820
4178
  return true;
3821
4179
  }
3822
4180
  catch (error) {
4181
+ // oxlint-disable-next-line no-console -- guard errors must surface to DevTools; no injectable logger here
3823
4182
  console.error('Permission guard error:', error);
3824
4183
  router.navigate(['/permission-denied'], {
3825
4184
  queryParams: { permission },
@@ -3953,6 +4312,14 @@ function provideCompression() {
3953
4312
  return makeEnvironmentProviders([CompressionService]);
3954
4313
  }
3955
4314
 
4315
+ function provideEyeDropper() {
4316
+ return makeEnvironmentProviders([EyeDropperService, PermissionsService]);
4317
+ }
4318
+
4319
+ function provideIdleDetector() {
4320
+ return makeEnvironmentProviders([IdleDetectorService, PermissionsService]);
4321
+ }
4322
+
3956
4323
  function provideMediaApis() {
3957
4324
  return makeEnvironmentProviders([PermissionsService, CameraService, MediaDevicesService]);
3958
4325
  }
@@ -3999,10 +4366,36 @@ const defaultBrowserWebApisConfig = {
3999
4366
  enablePerformanceObserver: false,
4000
4367
  enableWebAudio: false,
4001
4368
  enableGamepad: false,
4369
+ enableEyeDropper: false,
4370
+ enableIdleDetector: false,
4002
4371
  };
4372
+ let legacyFlagsDeprecationLogged = false;
4003
4373
  function provideBrowserWebApis(config = {}) {
4004
- const mergedConfig = { ...defaultBrowserWebApisConfig, ...config };
4374
+ const { services, ...flagConfig } = config;
4375
+ // Composition-first path: only `services` provided, no flags.
4376
+ if (services && Object.keys(flagConfig).length === 0) {
4377
+ return makeEnvironmentProviders([
4378
+ PermissionsService,
4379
+ ...services.flatMap((env) => env.ɵproviders ?? []),
4380
+ ]);
4381
+ }
4382
+ // Legacy flag-bag path. Log deprecation once if any enableX is set.
4383
+ if (Object.keys(flagConfig).length > 0 && !legacyFlagsDeprecationLogged) {
4384
+ legacyFlagsDeprecationLogged = true;
4385
+ // oxlint-disable-next-line no-console
4386
+ console.warn('[browser-web-apis] provideBrowserWebApis(enableX flags) is deprecated. ' +
4387
+ 'Pass `{ services: [provideCamera(), ...] }` instead. The flag-bag form will be ' +
4388
+ 'removed in v22.');
4389
+ }
4390
+ const mergedConfig = { ...defaultBrowserWebApisConfig, ...flagConfig };
4005
4391
  const providers = [PermissionsService];
4392
+ if (services) {
4393
+ for (const env of services) {
4394
+ const inner = env.ɵproviders;
4395
+ if (inner)
4396
+ providers.push(...inner);
4397
+ }
4398
+ }
4006
4399
  const conditionalProviders = [
4007
4400
  [mergedConfig.enableCamera, CameraService],
4008
4401
  [mergedConfig.enableGeolocation, GeolocationService],
@@ -4031,6 +4424,8 @@ function provideBrowserWebApis(config = {}) {
4031
4424
  [mergedConfig.enablePerformanceObserver, PerformanceObserverService],
4032
4425
  [mergedConfig.enableWebAudio, WebAudioService],
4033
4426
  [mergedConfig.enableGamepad, GamepadService],
4427
+ [mergedConfig.enableEyeDropper, EyeDropperService],
4428
+ [mergedConfig.enableIdleDetector, IdleDetectorService],
4034
4429
  ];
4035
4430
  for (const [enabled, provider] of conditionalProviders) {
4036
4431
  if (enabled) {
@@ -4048,4 +4443,4 @@ const version = '0.1.0';
4048
4443
  * Generated bundle index. Do not edit.
4049
4444
  */
4050
4445
 
4051
- export { BROWSER_API_LOGGER, BatteryService, BroadcastChannelService, BrowserApiBaseService, BrowserCapabilityService, BrowserSupportUtil, CameraService, ClipboardService, CompressionService, ConnectionRegistryBaseService, FileSystemAccessService, FullscreenService, GamepadService, GeolocationService, IntersectionObserverService, MediaDevicesService, MediaRecorderService, MutationObserverService, NetworkInformationService, NotificationService, PageVisibilityService, PerformanceObserverService, PermissionsService, ResizeObserverService, ScreenOrientationService, ScreenWakeLockService, ServerSentEventsService, SpeechSynthesisService, StorageManagerService, VibrationService, WebAudioService, WebLocksService, WebShareService, WebSocketClient, WebSocketService, WebStorageService, WebWorkerService, permissionGuard as createPermissionGuard, defaultBrowserWebApisConfig, injectBattery, injectClipboard, injectGamepad, injectGeolocation, injectIntersectionObserver, injectMutationObserver, injectNetworkInformation, injectPageVisibility, injectPerformanceObserver, injectResizeObserver, injectScreenOrientation, injectWakeLock, permissionGuard, provideBattery, provideBroadcastChannel, provideBrowserWebApis, provideCamera, provideClipboard, provideCommunicationApis, provideCompression, provideFileSystemAccess, provideFullscreen, provideGamepad, provideGeolocation, provideIntersectionObserver, provideLocationApis, provideMediaApis, provideMediaDevices, provideMediaRecorder, provideMutationObserver, provideNetworkInformation, provideNotifications, providePageVisibility, providePerformanceObserver, providePermissions, provideResizeObserver, provideScreenOrientation, provideScreenWakeLock, provideServerSentEvents, provideSpeechSynthesis, provideStorageApis, provideStorageManager, provideVibration, provideWebAudio, provideWebLocks, provideWebShare, provideWebSocket, provideWebStorage, provideWebWorker, version };
4446
+ export { BROWSER_API_EXPERIMENTAL_SILENT, BROWSER_API_LOGGER, BROWSER_API_LOG_LEVEL, BatteryService, BroadcastChannelService, BrowserApiBaseService, BrowserCapabilityService, BrowserSupportUtil, CameraService, ClipboardService, CompressionService, ConnectionRegistryBaseService, EyeDropperService, FileSystemAccessService, FullscreenService, GamepadService, GeolocationService, IdleDetectorService, IntersectionObserverService, MediaDevicesService, MediaRecorderService, MutationObserverService, NetworkInformationService, NotificationService, PageVisibilityService, PerformanceObserverService, PermissionsService, ResizeObserverService, ScreenOrientationService, ScreenWakeLockService, ServerSentEventsService, SpeechSynthesisService, StorageManagerService, VibrationService, WebAudioService, WebLocksService, WebShareService, WebSocketClient, WebSocketService, WebStorageService, WebWorkerService, permissionGuard as createPermissionGuard, defaultBrowserWebApisConfig, injectBattery, injectClipboard, injectEyeDropper, injectGamepad, injectGeolocation, injectIdleDetector, injectIntersectionObserver, injectMutationObserver, injectNetworkInformation, injectPageVisibility, injectPerformanceObserver, injectResizeObserver, injectScreenOrientation, injectWakeLock, permissionGuard, provideBattery, provideBroadcastChannel, provideBrowserApiLogLevel, provideBrowserWebApis, provideCamera, provideClipboard, provideCommunicationApis, provideCompression, provideEyeDropper, provideFileSystemAccess, provideFullscreen, provideGamepad, provideGeolocation, provideIdleDetector, provideIntersectionObserver, provideLocationApis, provideMediaApis, provideMediaDevices, provideMediaRecorder, provideMutationObserver, provideNetworkInformation, provideNotifications, providePageVisibility, providePerformanceObserver, providePermissions, provideResizeObserver, provideScreenOrientation, provideScreenWakeLock, provideServerSentEvents, provideSpeechSynthesis, provideStorageApis, provideStorageManager, provideVibration, provideWebAudio, provideWebLocks, provideWebShare, provideWebSocket, provideWebStorage, provideWebWorker, version, warnExperimental };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@angular-helpers/browser-web-apis",
3
- "version": "21.10.0",
3
+ "version": "21.12.0",
4
4
  "description": "Sistema de servicios Angular para acceso formalizado a Browser Web APIs (cámara, permisos, geolocalización, etc.)",
5
5
  "homepage": "https://gaspar1992.github.io/angular-helpers/docs/browser-web-apis",
6
6
  "repository": {
@@ -49,6 +49,7 @@
49
49
  "default": "./fesm2022/angular-helpers-browser-web-apis-experimental.mjs"
50
50
  }
51
51
  },
52
+ "type": "module",
52
53
  "dependencies": {
53
54
  "tslib": "^2.3.0"
54
55
  }
@@ -230,6 +230,7 @@ declare abstract class BrowserApiBaseService {
230
230
  protected logError(message: string, error?: unknown): void;
231
231
  protected logWarn(message: string): void;
232
232
  protected logInfo(message: string): void;
233
+ protected logDebug(message: string): void;
233
234
  static ɵfac: i0.ɵɵFactoryDeclaration<BrowserApiBaseService, never>;
234
235
  static ɵprov: i0.ɵɵInjectableDeclaration<BrowserApiBaseService>;
235
236
  }
@@ -392,12 +393,24 @@ type ErrorCallback = (error: BrowserError) => void;
392
393
  */
393
394
  type ElementInput = Element | ElementRef<Element> | Signal<Element | ElementRef<Element> | undefined>;
394
395
 
396
+ type BrowserApiLogLevel = 'debug' | 'info' | 'warn' | 'error' | 'silent';
395
397
  interface BrowserApiLogger {
398
+ debug?(message: string): void;
396
399
  info(message: string): void;
397
400
  warn(message: string): void;
398
401
  error(message: string, error?: unknown): void;
399
402
  }
403
+ /**
404
+ * Global log level for `BROWSER_API_LOGGER`. Defaults to `'debug'` in dev mode and
405
+ * `'warn'` in production. Override via `provideBrowserApiLogLevel('silent')` etc.
406
+ */
407
+ declare const BROWSER_API_LOG_LEVEL: InjectionToken<BrowserApiLogLevel>;
400
408
  declare const BROWSER_API_LOGGER: InjectionToken<BrowserApiLogger>;
409
+ /** Log level helper provider. Overrides the default `BROWSER_API_LOG_LEVEL`. */
410
+ declare function provideBrowserApiLogLevel(level: BrowserApiLogLevel): {
411
+ provide: InjectionToken<BrowserApiLogLevel>;
412
+ useValue: BrowserApiLogLevel;
413
+ };
401
414
 
402
415
  interface StorageOptions {
403
416
  /** Optional prefix used to namespace keys (e.g. `app:` -> `app:userId`). */
@@ -877,6 +890,7 @@ interface WakeLockStatus {
877
890
  }
878
891
  declare class ScreenWakeLockService extends BrowserApiBaseService {
879
892
  private sentinel;
893
+ private releaseRegistered;
880
894
  protected getApiName(): string;
881
895
  protected getCapabilityId(): BrowserCapabilityId;
882
896
  get isActive(): boolean;
@@ -1105,6 +1119,7 @@ interface AudioAnalyserData {
1105
1119
  declare class WebAudioService extends BrowserApiBaseService {
1106
1120
  protected getApiName(): string;
1107
1121
  private context;
1122
+ private destroyRegistered;
1108
1123
  protected getCapabilityId(): BrowserCapabilityId;
1109
1124
  getContext(): AudioContext;
1110
1125
  resume(): Promise<void>;
@@ -1146,6 +1161,59 @@ declare class GamepadService extends BrowserApiBaseService {
1146
1161
  static ɵprov: i0.ɵɵInjectableDeclaration<GamepadService>;
1147
1162
  }
1148
1163
 
1164
+ interface EyeDropperResult {
1165
+ sRGBHex: string;
1166
+ }
1167
+ declare class EyeDropperService extends BrowserApiBaseService {
1168
+ protected getApiName(): string;
1169
+ protected getCapabilityId(): BrowserCapabilityId;
1170
+ /** Override to also assert secure context (required by the spec). */
1171
+ isSupported(): boolean;
1172
+ protected ensureSupported(): void;
1173
+ /**
1174
+ * Opens the system eye dropper tool and returns the selected color.
1175
+ *
1176
+ * @param options Optional configuration including an AbortSignal to cancel the dropper.
1177
+ * @returns A promise that resolves with the selected color in sRGB Hex format (e.g., "#000000").
1178
+ * @throws DOMException if the user cancels the selection (AbortError).
1179
+ */
1180
+ open(options?: {
1181
+ signal?: AbortSignal;
1182
+ }): Promise<EyeDropperResult>;
1183
+ static ɵfac: i0.ɵɵFactoryDeclaration<EyeDropperService, never>;
1184
+ static ɵprov: i0.ɵɵInjectableDeclaration<EyeDropperService>;
1185
+ }
1186
+
1187
+ type UserIdleState = 'active' | 'idle';
1188
+ type ScreenIdleState = 'locked' | 'unlocked';
1189
+ interface IdleState {
1190
+ userState: UserIdleState | null;
1191
+ screenState: ScreenIdleState | null;
1192
+ }
1193
+ interface IdleDetectorOptions {
1194
+ /** The minimum number of milliseconds of inactivity before the user is considered idle. Must be at least 60000. */
1195
+ threshold: number;
1196
+ /** An AbortSignal to abort the idle detection. */
1197
+ signal?: AbortSignal;
1198
+ }
1199
+ declare class IdleDetectorService extends BrowserApiBaseService {
1200
+ protected getApiName(): string;
1201
+ protected getCapabilityId(): BrowserCapabilityId;
1202
+ isSupported(): boolean;
1203
+ protected ensureSupported(): void;
1204
+ requestPermission(): Promise<PermissionState>;
1205
+ /**
1206
+ * Starts tracking idle state. Emits the current state and subsequent changes.
1207
+ * Note: You must call requestPermission() and be granted access before starting.
1208
+ *
1209
+ * @param options Configuration for the idle detector, including the threshold (minimum 60000ms).
1210
+ * @returns An Observable of the IdleState.
1211
+ */
1212
+ watch(options: IdleDetectorOptions): Observable<IdleState>;
1213
+ static ɵfac: i0.ɵɵFactoryDeclaration<IdleDetectorService, never>;
1214
+ static ɵprov: i0.ɵɵInjectableDeclaration<IdleDetectorService>;
1215
+ }
1216
+
1149
1217
  interface LockOptionsLike {
1150
1218
  mode?: 'exclusive' | 'shared';
1151
1219
  ifAvailable?: boolean;
@@ -1242,6 +1310,24 @@ declare class CompressionService extends BrowserApiBaseService {
1242
1310
  static ɵprov: i0.ɵɵInjectableDeclaration<CompressionService>;
1243
1311
  }
1244
1312
 
1313
+ /**
1314
+ * When set to `true`, suppresses the one-time runtime warning that experimental
1315
+ * services emit on first use. Default is `false` (warnings emitted).
1316
+ */
1317
+ declare const BROWSER_API_EXPERIMENTAL_SILENT: InjectionToken<boolean>;
1318
+ interface ExperimentalWarnContext {
1319
+ silent: boolean;
1320
+ logger: {
1321
+ warn: (message: string) => void;
1322
+ };
1323
+ }
1324
+ /**
1325
+ * Emit a one-time runtime warning explaining that the API is experimental and
1326
+ * subject to breaking changes. No-op after the first call for a given key, or if
1327
+ * the consumer has provided `BROWSER_API_EXPERIMENTAL_SILENT` as `true`.
1328
+ */
1329
+ declare function warnExperimental(key: string, context: ExperimentalWarnContext): void;
1330
+
1245
1331
  interface MediaDevice {
1246
1332
  deviceId: string;
1247
1333
  groupId: string;
@@ -1455,6 +1541,29 @@ interface WakeLockRef {
1455
1541
  }
1456
1542
  declare function injectWakeLock(): WakeLockRef;
1457
1543
 
1544
+ interface EyeDropperRef {
1545
+ readonly isSupported: Signal<boolean>;
1546
+ readonly color: Signal<string | null>;
1547
+ readonly error: Signal<Error | null>;
1548
+ readonly isOpening: Signal<boolean>;
1549
+ open(options?: {
1550
+ signal?: AbortSignal;
1551
+ }): Promise<EyeDropperResult | null>;
1552
+ }
1553
+ declare function injectEyeDropper(): EyeDropperRef;
1554
+
1555
+ interface IdleDetectorRef {
1556
+ readonly isSupported: Signal<boolean>;
1557
+ readonly state: Signal<IdleState | null>;
1558
+ readonly error: Signal<Error | null>;
1559
+ readonly isTracking: Signal<boolean>;
1560
+ readonly isIdle: Signal<boolean>;
1561
+ start(options: IdleDetectorOptions): Promise<void>;
1562
+ stop(): void;
1563
+ requestPermission(): Promise<PermissionState>;
1564
+ }
1565
+ declare function injectIdleDetector(): IdleDetectorRef;
1566
+
1458
1567
  declare class BrowserSupportUtil {
1459
1568
  static isSupported(feature: string): boolean;
1460
1569
  static getUnsupportedFeatures(): string[];
@@ -1542,12 +1651,29 @@ declare function provideStorageManager(): EnvironmentProviders;
1542
1651
 
1543
1652
  declare function provideCompression(): EnvironmentProviders;
1544
1653
 
1654
+ declare function provideEyeDropper(): EnvironmentProviders;
1655
+
1656
+ declare function provideIdleDetector(): EnvironmentProviders;
1657
+
1545
1658
  declare function provideMediaApis(): EnvironmentProviders;
1546
1659
  declare function provideLocationApis(): EnvironmentProviders;
1547
1660
  declare function provideStorageApis(): EnvironmentProviders;
1548
1661
  declare function provideCommunicationApis(): EnvironmentProviders;
1549
1662
 
1550
- interface BrowserWebApisConfig {
1663
+ /**
1664
+ * Composition-first config: pass an array of `provide*()` calls and we merge them
1665
+ * into a single `EnvironmentProviders`. Preferred over the `enableX` flag bag.
1666
+ *
1667
+ * ```ts
1668
+ * provideBrowserWebApis({
1669
+ * services: [provideCamera(), provideClipboard(), provideMediaApis()],
1670
+ * })
1671
+ * ```
1672
+ */
1673
+ interface BrowserWebApisCompositionConfig {
1674
+ services?: EnvironmentProviders[];
1675
+ }
1676
+ interface BrowserWebApisConfig extends BrowserWebApisCompositionConfig {
1551
1677
  enableCamera?: boolean;
1552
1678
  enableGeolocation?: boolean;
1553
1679
  enableNotifications?: boolean;
@@ -1575,11 +1701,13 @@ interface BrowserWebApisConfig {
1575
1701
  enablePerformanceObserver?: boolean;
1576
1702
  enableWebAudio?: boolean;
1577
1703
  enableGamepad?: boolean;
1704
+ enableEyeDropper?: boolean;
1705
+ enableIdleDetector?: boolean;
1578
1706
  }
1579
1707
  declare const defaultBrowserWebApisConfig: BrowserWebApisConfig;
1580
1708
  declare function provideBrowserWebApis(config?: BrowserWebApisConfig): EnvironmentProviders;
1581
1709
 
1582
1710
  declare const version = "0.1.0";
1583
1711
 
1584
- export { BROWSER_API_LOGGER, BatteryService, BroadcastChannelService, BrowserApiBaseService, BrowserCapabilityService, BrowserSupportUtil, CameraService, ClipboardService, CompressionService, ConnectionRegistryBaseService, FileSystemAccessService, FullscreenService, GamepadService, GeolocationService, IntersectionObserverService, MediaDevicesService, MediaRecorderService, MutationObserverService, NetworkInformationService, NotificationService, PageVisibilityService, PerformanceObserverService, PermissionsService, ResizeObserverService, ScreenOrientationService, ScreenWakeLockService, ServerSentEventsService, SpeechSynthesisService, StorageManagerService, VibrationService, WebAudioService, WebLocksService, WebShareService, WebSocketClient, WebSocketService, WebStorageService, WebWorkerService, permissionGuard as createPermissionGuard, defaultBrowserWebApisConfig, injectBattery, injectClipboard, injectGamepad, injectGeolocation, injectIntersectionObserver, injectMutationObserver, injectNetworkInformation, injectPageVisibility, injectPerformanceObserver, injectResizeObserver, injectScreenOrientation, injectWakeLock, permissionGuard, provideBattery, provideBroadcastChannel, provideBrowserWebApis, provideCamera, provideClipboard, provideCommunicationApis, provideCompression, provideFileSystemAccess, provideFullscreen, provideGamepad, provideGeolocation, provideIntersectionObserver, provideLocationApis, provideMediaApis, provideMediaDevices, provideMediaRecorder, provideMutationObserver, provideNetworkInformation, provideNotifications, providePageVisibility, providePerformanceObserver, providePermissions, provideResizeObserver, provideScreenOrientation, provideScreenWakeLock, provideServerSentEvents, provideSpeechSynthesis, provideStorageApis, provideStorageManager, provideVibration, provideWebAudio, provideWebLocks, provideWebShare, provideWebSocket, provideWebStorage, provideWebWorker, version };
1585
- export type { AudioAnalyserData, AudioContextState, BatteryInfo, BatteryManager, BatteryRef, BrowserApiLogger, BrowserCapabilityId, BrowserError, BrowserPermissions, BrowserWebApisConfig, CameraCapabilities, CameraInfo, ClipboardRef, CompressionFormat, ConnectionType, EffectiveConnectionType, ElementInput, ElementSize, ErrorCallback, EventHandler, FileOpenOptions, FileSaveOptions, GamepadRef, GamepadState, GeolocationCoordinates, GeolocationError, GeolocationOptions, GeolocationPosition$1 as GeolocationPosition, GeolocationRef, GeolocationWatchOptions, IntersectionObserverOptions, IntersectionRef, MediaDevice, MediaDeviceKind, MediaDevicesInfo, MediaStreamConstraints$1 as MediaStreamConstraints, MediaTrackConstraints$1 as MediaTrackConstraints, MutationObserverOptions, MutationRef, NetworkInformation, NetworkInformationRef, OrientationInfo, OrientationLockType, OrientationType, PageVisibilityRef, PerformanceEntryType, PerformanceObserverConfig, PerformanceObserverRef, PermissionNameExt, PermissionRequest, RecordingOptions, RecordingResult, RecordingState, ResizeObserverOptions, ResizeRef, SSEConfig, SSEConnectionState, SSEMessage, ScreenOrientationRef, SpeechOptions, SpeechState, StorageEvent, StorageNamespace, StorageOptions, StorageQuotaEstimate, StorageValue, VibrationPattern, VibrationPreset, VisibilityState, WakeLockRef, WakeLockStatus, WakeLockType, WebSocketClientConfig, WebSocketConfig, WebSocketMessage, WebSocketRequestOptions, WebSocketState, WebSocketStatus, WebSocketStatusV2, WorkerMessage, WorkerRequestOptions, WorkerStatus, WorkerTask };
1712
+ export { BROWSER_API_EXPERIMENTAL_SILENT, BROWSER_API_LOGGER, BROWSER_API_LOG_LEVEL, BatteryService, BroadcastChannelService, BrowserApiBaseService, BrowserCapabilityService, BrowserSupportUtil, CameraService, ClipboardService, CompressionService, ConnectionRegistryBaseService, EyeDropperService, FileSystemAccessService, FullscreenService, GamepadService, GeolocationService, IdleDetectorService, IntersectionObserverService, MediaDevicesService, MediaRecorderService, MutationObserverService, NetworkInformationService, NotificationService, PageVisibilityService, PerformanceObserverService, PermissionsService, ResizeObserverService, ScreenOrientationService, ScreenWakeLockService, ServerSentEventsService, SpeechSynthesisService, StorageManagerService, VibrationService, WebAudioService, WebLocksService, WebShareService, WebSocketClient, WebSocketService, WebStorageService, WebWorkerService, permissionGuard as createPermissionGuard, defaultBrowserWebApisConfig, injectBattery, injectClipboard, injectEyeDropper, injectGamepad, injectGeolocation, injectIdleDetector, injectIntersectionObserver, injectMutationObserver, injectNetworkInformation, injectPageVisibility, injectPerformanceObserver, injectResizeObserver, injectScreenOrientation, injectWakeLock, permissionGuard, provideBattery, provideBroadcastChannel, provideBrowserApiLogLevel, provideBrowserWebApis, provideCamera, provideClipboard, provideCommunicationApis, provideCompression, provideEyeDropper, provideFileSystemAccess, provideFullscreen, provideGamepad, provideGeolocation, provideIdleDetector, provideIntersectionObserver, provideLocationApis, provideMediaApis, provideMediaDevices, provideMediaRecorder, provideMutationObserver, provideNetworkInformation, provideNotifications, providePageVisibility, providePerformanceObserver, providePermissions, provideResizeObserver, provideScreenOrientation, provideScreenWakeLock, provideServerSentEvents, provideSpeechSynthesis, provideStorageApis, provideStorageManager, provideVibration, provideWebAudio, provideWebLocks, provideWebShare, provideWebSocket, provideWebStorage, provideWebWorker, version, warnExperimental };
1713
+ export type { AudioAnalyserData, AudioContextState, BatteryInfo, BatteryManager, BatteryRef, BrowserApiLogLevel, BrowserApiLogger, BrowserCapabilityId, BrowserError, BrowserPermissions, BrowserWebApisCompositionConfig, BrowserWebApisConfig, CameraCapabilities, CameraInfo, ClipboardRef, CompressionFormat, ConnectionType, EffectiveConnectionType, ElementInput, ElementSize, ErrorCallback, EventHandler, ExperimentalWarnContext, EyeDropperRef, EyeDropperResult, FileOpenOptions, FileSaveOptions, GamepadRef, GamepadState, GeolocationCoordinates, GeolocationError, GeolocationOptions, GeolocationPosition$1 as GeolocationPosition, GeolocationRef, GeolocationWatchOptions, IdleDetectorOptions, IdleDetectorRef, IdleState, IntersectionObserverOptions, IntersectionRef, MediaDevice, MediaDeviceKind, MediaDevicesInfo, MediaStreamConstraints$1 as MediaStreamConstraints, MediaTrackConstraints$1 as MediaTrackConstraints, MutationObserverOptions, MutationRef, NetworkInformation, NetworkInformationRef, OrientationInfo, OrientationLockType, OrientationType, PageVisibilityRef, PerformanceEntryType, PerformanceObserverConfig, PerformanceObserverRef, PermissionNameExt, PermissionRequest, RecordingOptions, RecordingResult, RecordingState, ResizeObserverOptions, ResizeRef, SSEConfig, SSEConnectionState, SSEMessage, ScreenIdleState, ScreenOrientationRef, SpeechOptions, SpeechState, StorageEvent, StorageNamespace, StorageOptions, StorageQuotaEstimate, StorageValue, UserIdleState, VibrationPattern, VibrationPreset, VisibilityState, WakeLockRef, WakeLockStatus, WakeLockType, WebSocketClientConfig, WebSocketConfig, WebSocketMessage, WebSocketRequestOptions, WebSocketState, WebSocketStatus, WebSocketStatusV2, WorkerMessage, WorkerRequestOptions, WorkerStatus, WorkerTask };