@angular-helpers/browser-web-apis 1.41.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.es.md +826 -0
  2. package/README.md +918 -0
  3. package/package.json +52 -0
package/README.es.md ADDED
@@ -0,0 +1,826 @@
1
+ # @angular-helpers/browser-web-apis
2
+
3
+ Paquete de servicios Angular para acceder de forma estructurada y segura a Browser Web APIs.
4
+
5
+ 🌐 **Documentación y Demo**: https://gaspar1992.github.io/angular-helpers/
6
+
7
+ [Read in English](./README.md)
8
+
9
+ ## Caracteristicas
10
+
11
+ - Acceso unificado a APIs del navegador mediante servicios tipados
12
+ - **Tree-shaking real** β€” cada `provideX()` vive en su propio modulo e importa solo lo que necesita
13
+ - `provideBrowserWebApis()` para configuracion rapida cuando el tamano del bundle no es critico
14
+ - APIs reactivas con signals y observables
15
+ - Integracion segura con el ciclo de vida via `DestroyRef` (cleanup automatico)
16
+ - Logging y manejo de errores centralizado en `BrowserApiBaseService`
17
+ - Validacion de contexto seguro y deteccion de soporte del navegador incorporados
18
+
19
+ ## Servicios disponibles
20
+
21
+ ### APIs de medios y dispositivo
22
+
23
+ - `CameraService` - Acceso a camara con gestion de permisos
24
+ - `MediaDevicesService` - Enumeracion y gestion de dispositivos multimedia
25
+ - `GeolocationService` - Acceso a la API de geolocalizacion
26
+ - `NotificationService` - API de notificaciones del navegador
27
+ - `MediaRecorderService` - Grabar audio/video desde MediaStream
28
+
29
+ ### APIs de observadores
30
+
31
+ - `IntersectionObserverService` - Detectar cuando elementos entran/salen del viewport
32
+ - `ResizeObserverService` - Observar cambios de tamano de elementos
33
+ - `MutationObserverService` - Observar mutaciones en el DOM
34
+ - `PerformanceObserverService` - Monitorear entradas de rendimiento (LCP, CLS, etc.)
35
+
36
+ ### APIs de sistema
37
+
38
+ - `BatteryService` - Monitorear estado de bateria y carga
39
+ - `PageVisibilityService` - Rastrear cambios de visibilidad del documento
40
+ - `ScreenWakeLockService` - Evitar que la pantalla se atenue o bloquee
41
+ - `ScreenOrientationService` - Leer y bloquear orientacion de pantalla
42
+ - `FullscreenService` - Alternar modo pantalla completa para elementos
43
+ - `VibrationService` - Activar patrones de retroalimentacion tactil
44
+ - `SpeechSynthesisService` - Texto a voz con seleccion de voz
45
+ - `IdleDetectorService` - Detectar estado idle del usuario y bloqueo de pantalla
46
+ - `GamepadService` - Polling de entrada de controladores de juego
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
51
+
52
+ ### APIs de red
53
+
54
+ - `WebSocketService` - Gestion de conexiones WebSocket
55
+ - `ServerSentEventsService` - Cliente de Server-Sent Events
56
+ - `BroadcastChannelService` - Comunicacion entre pestanas
57
+ - `NetworkInformationService` - Informacion de conexion y estado online
58
+
59
+ ### APIs de almacenamiento y E/S
60
+
61
+ - `WebStorageService` - Helpers para LocalStorage y SessionStorage
62
+ - `WebShareService` - Soporte para Web Share API nativa
63
+ - `ClipboardService` - Acceso al portapapeles del sistema
64
+ - `FileSystemAccessService` - Abrir/guardar archivos via selector nativo
65
+
66
+ ### Web APIs
67
+
68
+ - `WebWorkerService` - Gestion de Web Workers
69
+
70
+ ### Seguridad y capacidades
71
+
72
+ - `PermissionsService` - Gestion centralizada de permisos del navegador
73
+ - `BrowserCapabilityService` - Deteccion de soporte de APIs del navegador
74
+
75
+ ### Utilidades
76
+
77
+ - `BrowserApiBaseService` - Clase base compartida para servicios de Browser APIs
78
+
79
+ ## Instalacion
80
+
81
+ ```bash
82
+ npm install @angular-helpers/browser-web-apis
83
+ ```
84
+
85
+ ## Configuracion rapida
86
+
87
+ ### Todo en uno (sin preocupacion por el bundle)
88
+
89
+ ```typescript
90
+ import { provideBrowserWebApis } from '@angular-helpers/browser-web-apis';
91
+
92
+ bootstrapApplication(AppComponent, {
93
+ providers: [
94
+ provideBrowserWebApis({
95
+ enableCamera: true,
96
+ enableGeolocation: true,
97
+ enableNotifications: true,
98
+ }),
99
+ ],
100
+ });
101
+ ```
102
+
103
+ ### Configuracion granular (recomendada para produccion)
104
+
105
+ Cada `provideX()` vive en su propio modulo e importa solo su servicio. Los bundlers (webpack, Rollup, Vite) eliminan todo lo que no uses.
106
+
107
+ ```typescript
108
+ import {
109
+ provideCamera,
110
+ provideGeolocation,
111
+ provideWebStorage,
112
+ } from '@angular-helpers/browser-web-apis';
113
+
114
+ bootstrapApplication(AppComponent, {
115
+ providers: [
116
+ provideCamera(), // β†’ solo CameraService + PermissionsService
117
+ provideGeolocation(), // β†’ solo GeolocationService + PermissionsService
118
+ provideWebStorage(), // β†’ solo WebStorageService
119
+ ],
120
+ });
121
+ ```
122
+
123
+ Cada servicio tiene su propio `provideX()`:
124
+
125
+ | Funcion | Servicios incluidos |
126
+ | ------------------------------- | ------------------------------------------- |
127
+ | `provideCamera()` | `PermissionsService`, `CameraService` |
128
+ | `provideGeolocation()` | `PermissionsService`, `GeolocationService` |
129
+ | `provideNotifications()` | `PermissionsService`, `NotificationService` |
130
+ | `provideClipboard()` | `PermissionsService`, `ClipboardService` |
131
+ | `provideMediaDevices()` | `PermissionsService`, `MediaDevicesService` |
132
+ | `provideWebStorage()` | `WebStorageService` |
133
+ | `provideWebSocket()` | `WebSocketService` |
134
+ | `provideWebWorker()` | `WebWorkerService` |
135
+ | `provideBattery()` | `BatteryService` |
136
+ | `provideIntersectionObserver()` | `IntersectionObserverService` |
137
+ | `provideResizeObserver()` | `ResizeObserverService` |
138
+ | `provideMutationObserver()` | `MutationObserverService` |
139
+ | `providePerformanceObserver()` | `PerformanceObserverService` |
140
+ | `providePageVisibility()` | `PageVisibilityService` |
141
+ | `provideNetworkInformation()` | `NetworkInformationService` |
142
+ | …y 22 mas | Ver `src/providers/` |
143
+
144
+ ### Providers combinados
145
+
146
+ Funciones de conveniencia que agrupan servicios relacionados:
147
+
148
+ ```typescript
149
+ import { provideMediaApis, provideStorageApis } from '@angular-helpers/browser-web-apis';
150
+
151
+ bootstrapApplication(AppComponent, {
152
+ providers: [
153
+ provideMediaApis(), // Camera + MediaDevices + Permissions
154
+ provideStorageApis(), // Clipboard + WebStorage + Permissions
155
+ ],
156
+ });
157
+ ```
158
+
159
+ ## Uso por servicio
160
+
161
+ ### CameraService
162
+
163
+ ```typescript
164
+ import { CameraService } from '@angular-helpers/browser-web-apis';
165
+
166
+ export class PhotoComponent {
167
+ private cameraService = inject(CameraService);
168
+
169
+ async takePhoto() {
170
+ try {
171
+ const stream = await this.cameraService.startCamera({
172
+ video: true,
173
+ audio: false,
174
+ });
175
+
176
+ // Usa el stream para captura de foto o video
177
+ } catch (error) {
178
+ console.error('Error accessing camera:', error);
179
+ }
180
+ }
181
+
182
+ async stopCamera() {
183
+ await this.cameraService.stopCamera();
184
+ }
185
+ }
186
+ ```
187
+
188
+ ### BrowserCapabilityService
189
+
190
+ ```typescript
191
+ import { BrowserCapabilityService } from '@angular-helpers/browser-web-apis';
192
+
193
+ export class MyComponent {
194
+ private capability = inject(BrowserCapabilityService);
195
+
196
+ ngOnInit() {
197
+ if (this.capability.isSupported('geolocation')) {
198
+ console.log('La geolocalizacion esta disponible');
199
+ }
200
+ }
201
+ }
202
+ ```
203
+
204
+ > Para prevenciΓ³n de ReDoS, usa el paquete `@angular-helpers/security`.
205
+
206
+ ### GeolocationService
207
+
208
+ ```typescript
209
+ import { GeolocationService } from '@angular-helpers/browser-web-apis';
210
+
211
+ export class LocationComponent {
212
+ private geolocation = inject(GeolocationService);
213
+
214
+ async getCurrentLocation() {
215
+ try {
216
+ const position = await this.geolocation.getCurrentPosition({
217
+ enableHighAccuracy: true,
218
+ timeout: 10000,
219
+ maximumAge: 60000,
220
+ });
221
+
222
+ console.log('Position:', position.coords);
223
+ } catch (error) {
224
+ console.error('Error getting location:', error);
225
+ }
226
+ }
227
+ }
228
+ ```
229
+
230
+ ### IntersectionObserverService
231
+
232
+ ```typescript
233
+ import { IntersectionObserverService } from '@angular-helpers/browser-web-apis';
234
+
235
+ export class LazyImageComponent {
236
+ private intersectionService = inject(IntersectionObserverService);
237
+ private elementRef = inject(ElementRef);
238
+
239
+ isVisible = signal(false);
240
+
241
+ ngAfterViewInit() {
242
+ this.intersectionService
243
+ .observeVisibility(this.elementRef.nativeElement, { threshold: 0.5 })
244
+ .subscribe((visible) => this.isVisible.set(visible));
245
+ }
246
+ }
247
+ ```
248
+
249
+ ### ResizeObserverService
250
+
251
+ ```typescript
252
+ import { ResizeObserverService } from '@angular-helpers/browser-web-apis';
253
+
254
+ export class ResponsiveComponent {
255
+ private resizeService = inject(ResizeObserverService);
256
+ private elementRef = inject(ElementRef);
257
+
258
+ elementSize = signal<ElementSize | null>(null);
259
+
260
+ ngAfterViewInit() {
261
+ this.resizeService
262
+ .observeSize(this.elementRef.nativeElement)
263
+ .subscribe((size) => this.elementSize.set(size));
264
+ }
265
+ }
266
+ ```
267
+
268
+ ### PageVisibilityService
269
+
270
+ ```typescript
271
+ import { PageVisibilityService } from '@angular-helpers/browser-web-apis';
272
+
273
+ export class AnalyticsComponent {
274
+ private visibilityService = inject(PageVisibilityService);
275
+
276
+ ngOnInit() {
277
+ this.visibilityService.watch().subscribe((state) => {
278
+ console.log('La pagina ahora esta:', state);
279
+ });
280
+ }
281
+ }
282
+ ```
283
+
284
+ ### FullscreenService
285
+
286
+ ```typescript
287
+ import { FullscreenService } from '@angular-helpers/browser-web-apis';
288
+
289
+ export class VideoPlayerComponent {
290
+ private fullscreenService = inject(FullscreenService);
291
+
292
+ async toggleFullscreen() {
293
+ await this.fullscreenService.toggle();
294
+ }
295
+ }
296
+ ```
297
+
298
+ ### ScreenWakeLockService
299
+
300
+ ```typescript
301
+ import { ScreenWakeLockService } from '@angular-helpers/browser-web-apis';
302
+
303
+ export class PresentationComponent {
304
+ private wakeLockService = inject(ScreenWakeLockService);
305
+
306
+ async keepScreenOn() {
307
+ await this.wakeLockService.request();
308
+ }
309
+
310
+ async releaseScreen() {
311
+ await this.wakeLockService.release();
312
+ }
313
+ }
314
+ ```
315
+
316
+ ### BroadcastChannelService
317
+
318
+ ```typescript
319
+ import { BroadcastChannelService } from '@angular-helpers/browser-web-apis';
320
+
321
+ export class SyncComponent {
322
+ private broadcastService = inject(BroadcastChannelService);
323
+
324
+ ngOnInit() {
325
+ // Escuchar mensajes de otras pestanas
326
+ this.broadcastService.open<string>('app-sync').subscribe((msg) => {
327
+ console.log('Recibido:', msg);
328
+ });
329
+ }
330
+
331
+ sendMessage(data: string) {
332
+ this.broadcastService.post('app-sync', data);
333
+ }
334
+ }
335
+ ```
336
+
337
+ ### ServerSentEventsService
338
+
339
+ ```typescript
340
+ import { ServerSentEventsService } from '@angular-helpers/browser-web-apis';
341
+
342
+ export class LiveFeedComponent {
343
+ private sseService = inject(ServerSentEventsService);
344
+
345
+ connectToEvents() {
346
+ this.sseService.connect('https://api.example.com/events').subscribe({
347
+ next: (message) => console.log('Evento:', message),
348
+ error: (err) => console.error('Error SSE:', err),
349
+ });
350
+ }
351
+ }
352
+ ```
353
+
354
+ ### VibrationService
355
+
356
+ ```typescript
357
+ import { VibrationService } from '@angular-helpers/browser-web-apis';
358
+
359
+ export class FeedbackComponent {
360
+ private vibrationService = inject(VibrationService);
361
+
362
+ onSuccess() {
363
+ this.vibrationService.success();
364
+ }
365
+
366
+ onError() {
367
+ this.vibrationService.error();
368
+ }
369
+ }
370
+ ```
371
+
372
+ ### SpeechSynthesisService
373
+
374
+ ```typescript
375
+ import { SpeechSynthesisService } from '@angular-helpers/browser-web-apis';
376
+
377
+ export class VoiceComponent {
378
+ private speechService = inject(SpeechSynthesisService);
379
+
380
+ speakText(text: string) {
381
+ this.speechService.speak(text).subscribe((state) => {
382
+ console.log('Estado de voz:', state);
383
+ });
384
+ }
385
+ }
386
+ ```
387
+
388
+ ### ScreenOrientationService
389
+
390
+ ```typescript
391
+ import { ScreenOrientationService } from '@angular-helpers/browser-web-apis';
392
+
393
+ export class MobileComponent {
394
+ private orientationService = inject(ScreenOrientationService);
395
+
396
+ ngOnInit() {
397
+ // Observar cambios de orientacion
398
+ this.orientationService.watch().subscribe((orientation) => {
399
+ console.log('Orientacion:', orientation.type, 'Angulo:', orientation.angle);
400
+ });
401
+ }
402
+
403
+ async lockPortrait() {
404
+ try {
405
+ await this.orientationService.lock('portrait');
406
+ } catch (error) {
407
+ console.error('No se pudo bloquear la orientacion:', error);
408
+ }
409
+ }
410
+
411
+ unlockOrientation() {
412
+ this.orientationService.unlock();
413
+ }
414
+ }
415
+ ```
416
+
417
+ ### BatteryService
418
+
419
+ ```typescript
420
+ import { BatteryService } from '@angular-helpers/browser-web-apis';
421
+
422
+ export class PowerComponent {
423
+ private batteryService = inject(BatteryService);
424
+
425
+ async ngOnInit() {
426
+ try {
427
+ // Inicializar y obtener informacion inicial de la bateria
428
+ const batteryInfo = await this.batteryService.initialize();
429
+ console.log('Nivel de bateria:', batteryInfo.level);
430
+ console.log('Esta cargando:', batteryInfo.charging);
431
+
432
+ // Observar cambios de bateria
433
+ this.batteryService.watchBatteryInfo().subscribe((info) => {
434
+ console.log('Bateria actualizada:', info);
435
+ });
436
+ } catch (error) {
437
+ console.error('Battery API no soportada:', error);
438
+ }
439
+ }
440
+ }
441
+ ```
442
+
443
+ ### ClipboardService
444
+
445
+ ```typescript
446
+ import { ClipboardService } from '@angular-helpers/browser-web-apis';
447
+
448
+ export class CopyComponent {
449
+ private clipboardService = inject(ClipboardService);
450
+
451
+ async copyToClipboard(text: string) {
452
+ try {
453
+ await this.clipboardService.writeText(text);
454
+ console.log('Copiado exitosamente');
455
+ } catch (error) {
456
+ console.error('Error al copiar:', error);
457
+ }
458
+ }
459
+
460
+ async pasteFromClipboard(): Promise<string> {
461
+ try {
462
+ const text = await this.clipboardService.readText();
463
+ return text;
464
+ } catch (error) {
465
+ console.error('Error al leer el portapapeles:', error);
466
+ return '';
467
+ }
468
+ }
469
+ }
470
+ ```
471
+
472
+ ### FileSystemAccessService
473
+
474
+ ```typescript
475
+ import { FileSystemAccessService } from '@angular-helpers/browser-web-apis';
476
+
477
+ export class FileManagerComponent {
478
+ private fileService = inject(FileSystemAccessService);
479
+
480
+ async openFiles() {
481
+ try {
482
+ const files = await this.fileService.openFile({
483
+ multiple: true,
484
+ types: [
485
+ {
486
+ description: 'Archivos de texto',
487
+ accept: { 'text/plain': ['.txt'] },
488
+ },
489
+ ],
490
+ });
491
+ console.log('Archivos seleccionados:', files);
492
+ } catch (error) {
493
+ console.error('Error al abrir archivos:', error);
494
+ }
495
+ }
496
+
497
+ async saveContent(content: string) {
498
+ try {
499
+ await this.fileService.saveFile(content, {
500
+ suggestedName: 'documento.txt',
501
+ });
502
+ } catch (error) {
503
+ console.error('Error al guardar archivo:', error);
504
+ }
505
+ }
506
+ }
507
+ ```
508
+
509
+ ### MediaRecorderService
510
+
511
+ ```typescript
512
+ import { MediaRecorderService } from '@angular-helpers/browser-web-apis';
513
+
514
+ export class RecorderComponent {
515
+ private recorderService = inject(MediaRecorderService);
516
+ private stream: MediaStream | null = null;
517
+
518
+ async startRecording() {
519
+ try {
520
+ this.stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
521
+ await this.recorderService.start(this.stream, { mimeType: 'video/webm' });
522
+ } catch (error) {
523
+ console.error('Error al iniciar la grabacion:', error);
524
+ }
525
+ }
526
+
527
+ stopRecording() {
528
+ const result = this.recorderService.stop();
529
+ if (result) {
530
+ console.log('Grabacion guardada, URL del blob:', result.url);
531
+ }
532
+ }
533
+ }
534
+ ```
535
+
536
+ ### WebSocketService
537
+
538
+ ```typescript
539
+ import { WebSocketService } from '@angular-helpers/browser-web-apis';
540
+
541
+ export class LiveComponent {
542
+ private wsService = inject(WebSocketService);
543
+
544
+ connect() {
545
+ this.wsService
546
+ .connect({
547
+ url: 'wss://example.com/socket',
548
+ reconnectInterval: 3000,
549
+ maxReconnectAttempts: 5,
550
+ })
551
+ .subscribe((status) => {
552
+ console.log('Estado de conexion:', status);
553
+ });
554
+
555
+ this.wsService.getMessages().subscribe((message) => {
556
+ console.log('Recibido:', message);
557
+ });
558
+ }
559
+
560
+ sendMessage(data: unknown) {
561
+ this.wsService.send({ type: 'message', data });
562
+ }
563
+ }
564
+ ```
565
+
566
+ ### WebStorageService
567
+
568
+ ```typescript
569
+ import { WebStorageService } from '@angular-helpers/browser-web-apis';
570
+
571
+ export class SettingsComponent {
572
+ private storageService = inject(WebStorageService);
573
+
574
+ saveSetting(key: string, value: unknown) {
575
+ this.storageService.setLocalStorage(key, value);
576
+ }
577
+
578
+ getSetting<T>(key: string): T | null {
579
+ return this.storageService.getLocalStorage<T>(key);
580
+ }
581
+
582
+ watchSetting<T>(key: string) {
583
+ return this.storageService.watchLocalStorage<T>(key).subscribe((value) => {
584
+ console.log('Configuracion cambiada:', value);
585
+ });
586
+ }
587
+ }
588
+ ```
589
+
590
+ ### WebWorkerService
591
+
592
+ ```typescript
593
+ import { WebWorkerService } from '@angular-helpers/browser-web-apis';
594
+
595
+ export class WorkerComponent {
596
+ private workerService = inject(WebWorkerService);
597
+
598
+ async createWorker() {
599
+ this.workerService
600
+ .createWorker('calc-worker', '/assets/workers/calc.worker.js')
601
+ .subscribe((status) => {
602
+ console.log('Estado del worker:', status);
603
+ });
604
+
605
+ this.workerService.getMessages('calc-worker').subscribe((message) => {
606
+ console.log('Respuesta del worker:', message);
607
+ });
608
+ }
609
+
610
+ sendTask(data: unknown) {
611
+ this.workerService.postMessage('calc-worker', {
612
+ id: 'task-1',
613
+ type: 'CALCULATE',
614
+ data,
615
+ });
616
+ }
617
+ }
618
+ ```
619
+
620
+ ## Primitivas Signal Fn
621
+
622
+ Alternativas reactivas sin boilerplate a los servicios basados en RxJS. Cada funcion `inject*` retorna un objeto ref con signals de solo lectura y maneja la limpieza automaticamente via `DestroyRef`.
623
+
624
+ ### injectPageVisibility
625
+
626
+ ```typescript
627
+ import { injectPageVisibility } from '@angular-helpers/browser-web-apis';
628
+
629
+ @Component({...})
630
+ export class MyComponent {
631
+ readonly visibility = injectPageVisibility();
632
+
633
+ // visibility.state() β†’ 'visible' | 'hidden'
634
+ // visibility.isVisible() β†’ boolean
635
+ // visibility.isHidden() β†’ boolean
636
+ }
637
+ ```
638
+
639
+ ### injectResizeObserver
640
+
641
+ Acepta `Element`, `ElementRef`, o un **`Signal<ElementRef | undefined>`** (ej. de `viewChild`). Cuando se pasa un signal, el observer inicia automaticamente cuando el elemento esta disponible.
642
+
643
+ ```typescript
644
+ import { injectResizeObserver } from '@angular-helpers/browser-web-apis';
645
+
646
+ @Component({...})
647
+ export class MyComponent {
648
+ readonly boxRef = viewChild<ElementRef>('box');
649
+ readonly resize = injectResizeObserver(this.boxRef);
650
+
651
+ // resize.width() β†’ number
652
+ // resize.height() β†’ number
653
+ // resize.inlineSize() β†’ number (logico)
654
+ // resize.blockSize() β†’ number (logico)
655
+ // resize.size() β†’ ElementSize | null
656
+ }
657
+ ```
658
+
659
+ ### injectIntersectionObserver
660
+
661
+ Misma flexibilidad con `ElementInput` β€” funciona con signals de `viewChild` directamente.
662
+
663
+ ```typescript
664
+ import { injectIntersectionObserver } from '@angular-helpers/browser-web-apis';
665
+
666
+ @Component({...})
667
+ export class MyComponent {
668
+ readonly targetRef = viewChild<ElementRef>('target');
669
+ readonly inView = injectIntersectionObserver(this.targetRef, { threshold: 0.25 });
670
+
671
+ // inView.isIntersecting() β†’ boolean
672
+ // inView.isVisible() β†’ boolean
673
+ }
674
+ ```
675
+
676
+ ### injectNetworkInformation
677
+
678
+ ```typescript
679
+ import { injectNetworkInformation } from '@angular-helpers/browser-web-apis';
680
+
681
+ @Component({...})
682
+ export class MyComponent {
683
+ readonly net = injectNetworkInformation();
684
+
685
+ // net.online() β†’ boolean
686
+ // net.effectiveType() β†’ '4g' | '3g' | '2g' | 'slow-2g' | undefined
687
+ // net.downlink() β†’ number | undefined (Mbps)
688
+ // net.rtt() β†’ number | undefined (ms)
689
+ // net.type() β†’ ConnectionType | undefined
690
+ // net.saveData() β†’ boolean | undefined
691
+ }
692
+ ```
693
+
694
+ ### injectScreenOrientation
695
+
696
+ ```typescript
697
+ import { injectScreenOrientation } from '@angular-helpers/browser-web-apis';
698
+
699
+ @Component({...})
700
+ export class MyComponent {
701
+ readonly orientation = injectScreenOrientation();
702
+
703
+ // orientation.type() β†’ OrientationType
704
+ // orientation.angle() β†’ number
705
+ // orientation.isPortrait() β†’ boolean
706
+ // orientation.isLandscape() β†’ boolean
707
+ // orientation.lock('landscape') β†’ Promise<void>
708
+ // orientation.unlock()
709
+ }
710
+ ```
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
+
794
+ ### Tipo ElementInput
795
+
796
+ Tanto `injectResizeObserver` como `injectIntersectionObserver` aceptan el tipo `ElementInput`:
797
+
798
+ ```typescript
799
+ type ElementInput =
800
+ | Element
801
+ | ElementRef<Element>
802
+ | Signal<Element | ElementRef<Element> | undefined>;
803
+ ```
804
+
805
+ Esto permite pasar un signal de `viewChild` directamente β€” la funcion usa internamente un `effect` para comenzar a observar cuando el elemento se renderiza, con limpieza automatica.
806
+
807
+ ## Soporte de navegadores
808
+
809
+ Los servicios validan automaticamente el soporte del navegador y el manejo de rutas no soportadas:
810
+
811
+ - Chrome: soporte completo
812
+ - Firefox: soporte completo
813
+ - Safari: soporte parcial
814
+ - Edge: soporte completo
815
+ - Navegadores moviles: depende de plataforma y API
816
+
817
+ ## Notas
818
+
819
+ - Varias APIs requieren contexto seguro (HTTPS)
820
+ - Algunas APIs requieren interaccion explicita del usuario
821
+ - El comportamiento de permisos varia segun el navegador
822
+ - Implementa siempre manejo de errores y fallback
823
+
824
+ ## Licencia
825
+
826
+ MIT