@vielzeug/sentinel 2.0.3 → 26.10.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.md CHANGED
@@ -1,16 +1,16 @@
1
1
  # @vielzeug/sentinel
2
2
 
3
- Reactive browser and DOM observations with explicit ownership.
3
+ > Subscribable snapshots for external browser environment state
4
4
 
5
- Sentinel exposes viewport, network, media query, element size, and intersection state as disposable Ripple readables.
6
-
7
- ## Install
5
+ ## Installation
8
6
 
9
7
  ```sh
10
- pnpm add @vielzeug/sentinel @vielzeug/ripple
8
+ pnpm add @vielzeug/sentinel
9
+ npm install @vielzeug/sentinel
10
+ yarn add @vielzeug/sentinel
11
11
  ```
12
12
 
13
- ## Usage
13
+ ## Quick Start
14
14
 
15
15
  ```ts
16
16
  import { createViewport } from '@vielzeug/sentinel';
@@ -18,7 +18,8 @@ import { createViewport } from '@vielzeug/sentinel';
18
18
  function observeViewport(): () => void {
19
19
  const viewport = createViewport();
20
20
  const render = () => {
21
- console.log(`${viewport.value.width}×${viewport.value.height} at ${viewport.value.dpr}dpr`);
21
+ const { dpr, height, width } = viewport.getSnapshot();
22
+ console.log(`${width}×${height} at ${dpr}dpr`);
22
23
  };
23
24
 
24
25
  render();
@@ -34,79 +35,14 @@ const stopObserving = observeViewport();
34
35
  // Call stopObserving() when the owning view unmounts.
35
36
  ```
36
37
 
37
- A Sentinel implements Ripple's `Readable<T>` contract:
38
-
39
- ```ts
40
- import { computed, watch } from '@vielzeug/ripple';
41
- import { createMediaQuery, createViewport } from '@vielzeug/sentinel';
42
-
43
- const viewport = createViewport();
44
- const mobileQuery = createMediaQuery('(max-width: 768px)');
45
- const compact = computed(() => mobileQuery.value.matches || viewport.value.width < 400);
46
- const watcher = watch(compact, console.log, { immediate: true });
47
-
48
- watcher.dispose();
49
- mobileQuery.dispose();
50
- viewport.dispose();
51
- ```
52
-
53
- Element observations start at `null` until the browser reports the first entry:
54
-
55
- ```ts
56
- import { createElementSize } from '@vielzeug/sentinel';
57
-
58
- const element = document.getElementById('panel');
59
- if (!element) throw new Error('Panel not found');
60
-
61
- const size = createElementSize(element);
62
- const unsubscribe = size.subscribe(() => {
63
- console.log(size.value?.width);
64
- });
65
-
66
- unsubscribe();
67
- size.dispose();
68
- ```
69
-
70
- ## Lifecycle
71
-
72
- Every Sentinel provides `dispose()`, `disposed`, `disposalSignal`, and `[Symbol.dispose]()`. Pass an external signal when several observers share one lifetime:
73
-
74
- ```ts
75
- const controller = new AbortController();
76
- const viewport = createViewport({ signal: controller.signal });
77
- const network = createNetwork({ signal: controller.signal });
78
-
79
- controller.abort();
80
- ```
81
-
82
- ## Isolated Ripple Runtime
83
-
84
- Pass an isolated Ripple runtime when Sentinel state must participate in that graph:
85
-
86
- ```ts
87
- import { createRipple } from '@vielzeug/ripple';
88
- import { createViewport } from '@vielzeug/sentinel';
89
-
90
- const ripple = createRipple();
91
- const viewport = createViewport({ runtime: ripple });
92
-
93
- viewport.dispose();
94
- ripple.dispose();
95
- ```
96
-
97
- Dispose Sentinels before disposing their injected Ripple runtime.
38
+ ## Documentation
98
39
 
99
- ## Unavailable APIs
40
+ - [Overview](https://vielzeug.dev/sentinel/)
41
+ - [Usage Guide](https://vielzeug.dev/sentinel/usage)
42
+ - [API Reference](https://vielzeug.dev/sentinel/api)
43
+ - [Examples](https://vielzeug.dev/sentinel/examples)
44
+ - [Migration Guide](https://vielzeug.dev/sentinel/migration)
100
45
 
101
- `createMediaQuery()`, `createElementSize()`, and `createIntersection()` throw `SentinelUnavailableError` when the required browser API is absent. Window-based factories throw the same error when no browser window is available.
46
+ ## License
102
47
 
103
- ```ts
104
- import { createMediaQuery, SentinelUnavailableError } from '@vielzeug/sentinel';
105
-
106
- try {
107
- const media = createMediaQuery('(prefers-reduced-motion: reduce)');
108
- media.dispose();
109
- } catch (error) {
110
- if (!(error instanceof SentinelUnavailableError)) throw error;
111
- }
112
- ```
48
+ MIT © [Helmuth Saatkamp](https://github.com/helmuthdu) — part of the [Vielzeug](https://github.com/helmuthdu/vielzeug) monorepo.
package/dist/core.cjs CHANGED
@@ -1,2 +1,2 @@
1
- let e=require("@vielzeug/ripple");function t(e,t){return new n(e,t)}var n=class{disposalSignal;abortController=new AbortController;state;cleanup;disposedValue=!1;externalSignal;externalAbortListener;constructor(t,n){let r=t.runtime?.signal??e.signal;if(this.state=r(t.initialValue),this.disposalSignal=this.abortController.signal,t.signal?.aborted){this.disposedValue=!0,this.abortController.abort();return}try{this.cleanup=n(e=>{this.disposedValue||(this.state.value=e)})}catch(e){throw this.disposedValue=!0,this.abortController.abort(),e}t.signal&&(this.externalSignal=t.signal,this.externalAbortListener=()=>this.dispose(),this.externalSignal.addEventListener(`abort`,this.externalAbortListener,{once:!0}))}get disposed(){return this.disposedValue}get value(){return this.state.value}peek(){return this.state.peek()}subscribe(e){return this.state.subscribe(e)}dispose(){if(this.disposedValue)return;this.disposedValue=!0,this.externalSignal&&this.externalAbortListener&&this.externalSignal.removeEventListener(`abort`,this.externalAbortListener);let e=this.cleanup;this.cleanup=void 0,this.externalSignal=void 0,this.externalAbortListener=void 0;try{e?.()}finally{this.abortController.abort()}}[Symbol.dispose](){this.dispose()}};exports.createSentinel=t;
1
+ const e=require("./errors.cjs");let t=require("@vielzeug/arsenal");function n(e,t){return new r(e,t)}var r=class{disposalSignal;abortController=new AbortController;currentValue;listeners=new Set;cleanup;disposedValue=!1;externalSignal;externalAbortListener;tapper=(0,t.tapper)();constructor(t,n){if(t.signal?.aborted)throw new e.SentinelError(`Sentinel signal is already aborted`);this.currentValue=t.initialValue,this.disposalSignal=this.abortController.signal;try{this.cleanup=n(e=>{this.disposedValue||(this.currentValue=e,this.notify())})}catch(e){throw this.disposedValue=!0,this.abortController.abort(),e}t.signal&&(this.externalSignal=t.signal,this.externalAbortListener=()=>this.dispose(),this.externalSignal.addEventListener(`abort`,this.externalAbortListener,{once:!0}))}get disposed(){return this.disposedValue}getSnapshot=()=>this.currentValue;subscribe=e=>this.disposedValue?()=>{}:(this.listeners.add(e),()=>this.listeners.delete(e));dispose=()=>{if(this.disposedValue)return;this.disposedValue=!0,this.externalSignal&&this.externalAbortListener&&this.externalSignal.removeEventListener(`abort`,this.externalAbortListener);let e=this.cleanup;this.cleanup=void 0,this.externalSignal=void 0,this.externalAbortListener=void 0,this.listeners.clear();try{e?.()}finally{this.tapper.emit({type:`dispose`}),this.tapper.clear(),this.abortController.abort()}};tap=(e,t)=>this.tapper.tap(e,t);notify(){for(let e of[...this.listeners])try{e()}catch(e){this.tapper.emit({error:e,type:`error`}),queueMicrotask(()=>{throw e})}}[Symbol.dispose](){this.dispose()}};exports.createSentinel=n;
2
2
  //# sourceMappingURL=core.cjs.map
package/dist/core.cjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"core.cjs","names":[],"sources":["../src/core.ts"],"sourcesContent":["import type { Signal, Unsubscribe } from '@vielzeug/ripple';\nimport { signal as createSignal } from '@vielzeug/ripple';\nimport type { Sentinel, SentinelOptions } from './types.ts';\n\nexport interface CreateSentinelOptions<T> extends SentinelOptions {\n readonly initialValue: T;\n}\n\nexport function createSentinel<T>(\n options: CreateSentinelOptions<T>,\n setup: (update: (value: T) => void) => () => void,\n): Sentinel<T> {\n return new SentinelHandle(options, setup);\n}\n\nclass SentinelHandle<T> implements Sentinel<T> {\n readonly disposalSignal: AbortSignal;\n private readonly abortController = new AbortController();\n private readonly state: Signal<T>;\n private cleanup: (() => void) | undefined;\n private disposedValue = false;\n private externalSignal: AbortSignal | undefined;\n private externalAbortListener: (() => void) | undefined;\n\n constructor(options: CreateSentinelOptions<T>, setup: (update: (value: T) => void) => () => void) {\n const toSignal = options.runtime?.signal ?? createSignal;\n this.state = toSignal(options.initialValue);\n this.disposalSignal = this.abortController.signal;\n\n if (options.signal?.aborted) {\n this.disposedValue = true;\n this.abortController.abort();\n return;\n }\n\n try {\n this.cleanup = setup((value) => {\n if (!this.disposedValue) this.state.value = value;\n });\n } catch (error) {\n this.disposedValue = true;\n this.abortController.abort();\n throw error;\n }\n\n if (!options.signal) return;\n\n this.externalSignal = options.signal;\n this.externalAbortListener = () => this.dispose();\n this.externalSignal.addEventListener('abort', this.externalAbortListener, { once: true });\n }\n\n get disposed(): boolean {\n return this.disposedValue;\n }\n\n get value(): T {\n return this.state.value;\n }\n\n peek(): T {\n return this.state.peek();\n }\n\n subscribe(listener: () => void): Unsubscribe {\n return this.state.subscribe(listener);\n }\n\n dispose(): void {\n if (this.disposedValue) return;\n this.disposedValue = true;\n\n if (this.externalSignal && this.externalAbortListener) {\n this.externalSignal.removeEventListener('abort', this.externalAbortListener);\n }\n\n const cleanup = this.cleanup;\n this.cleanup = undefined;\n this.externalSignal = undefined;\n this.externalAbortListener = undefined;\n\n try {\n cleanup?.();\n } finally {\n this.abortController.abort();\n }\n }\n\n [Symbol.dispose](): void {\n this.dispose();\n }\n}\n"],"mappings":"kCAQA,SAAgB,EACd,EACA,EACa,CACb,OAAO,IAAI,EAAe,EAAS,CAAK,CAC1C,CAEA,IAAM,EAAN,KAA+C,CAC7C,eACA,gBAAmC,IAAI,gBACvC,MACA,QACA,cAAwB,GACxB,eACA,sBAEA,YAAY,EAAmC,EAAmD,CAChG,IAAM,EAAW,EAAQ,SAAS,QAAU,EAAA,OAI5C,GAHA,KAAK,MAAQ,EAAS,EAAQ,YAAY,EAC1C,KAAK,eAAiB,KAAK,gBAAgB,OAEvC,EAAQ,QAAQ,QAAS,CAC3B,KAAK,cAAgB,GACrB,KAAK,gBAAgB,MAAM,EAC3B,MACF,CAEA,GAAI,CACF,KAAK,QAAU,EAAO,GAAU,CACzB,KAAK,gBAAe,KAAK,MAAM,MAAQ,EAC9C,CAAC,CACH,OAAS,EAAO,CAGd,KAFA,MAAK,cAAgB,GACrB,KAAK,gBAAgB,MAAM,EACrB,CACR,CAEK,EAAQ,SAEb,KAAK,eAAiB,EAAQ,OAC9B,KAAK,0BAA8B,KAAK,QAAQ,EAChD,KAAK,eAAe,iBAAiB,QAAS,KAAK,sBAAuB,CAAE,KAAM,EAAK,CAAC,EAC1F,CAEA,IAAI,UAAoB,CACtB,OAAO,KAAK,aACd,CAEA,IAAI,OAAW,CACb,OAAO,KAAK,MAAM,KACpB,CAEA,MAAU,CACR,OAAO,KAAK,MAAM,KAAK,CACzB,CAEA,UAAU,EAAmC,CAC3C,OAAO,KAAK,MAAM,UAAU,CAAQ,CACtC,CAEA,SAAgB,CACd,GAAI,KAAK,cAAe,OACxB,KAAK,cAAgB,GAEjB,KAAK,gBAAkB,KAAK,uBAC9B,KAAK,eAAe,oBAAoB,QAAS,KAAK,qBAAqB,EAG7E,IAAM,EAAU,KAAK,QACrB,KAAK,QAAU,IAAA,GACf,KAAK,eAAiB,IAAA,GACtB,KAAK,sBAAwB,IAAA,GAE7B,GAAI,CACF,IAAU,CACZ,QAAU,CACR,KAAK,gBAAgB,MAAM,CAC7B,CACF,CAEA,CAAC,OAAO,UAAiB,CACvB,KAAK,QAAQ,CACf,CACF"}
1
+ {"version":3,"file":"core.cjs","names":[],"sources":["../src/core.ts"],"sourcesContent":["import { type Tapper, tapper } from '@vielzeug/arsenal';\n\nimport { SentinelError } from './errors.ts';\nimport type { Sentinel, SentinelEvent, SentinelOptions, Unsubscribe } from './types.ts';\n\nexport interface CreateSentinelOptions<T> extends SentinelOptions {\n readonly initialValue: T;\n}\n\nexport function createSentinel<T>(\n options: CreateSentinelOptions<T>,\n setup: (update: (value: T) => void) => () => void,\n): Sentinel<T> {\n return new SentinelHandle(options, setup);\n}\n\nclass SentinelHandle<T> implements Sentinel<T> {\n readonly disposalSignal: AbortSignal;\n private readonly abortController = new AbortController();\n private currentValue: T;\n private readonly listeners = new Set<() => void>();\n private cleanup: (() => void) | undefined;\n private disposedValue = false;\n private externalSignal: AbortSignal | undefined;\n private externalAbortListener: (() => void) | undefined;\n private readonly tapper: Tapper<SentinelEvent> = tapper<SentinelEvent>();\n\n constructor(options: CreateSentinelOptions<T>, setup: (update: (value: T) => void) => () => void) {\n if (options.signal?.aborted) {\n throw new SentinelError('Sentinel signal is already aborted');\n }\n\n this.currentValue = options.initialValue;\n this.disposalSignal = this.abortController.signal;\n\n try {\n this.cleanup = setup((value) => {\n if (!this.disposedValue) {\n this.currentValue = value;\n this.notify();\n }\n });\n } catch (error) {\n this.disposedValue = true;\n this.abortController.abort();\n throw error;\n }\n\n if (!options.signal) return;\n\n this.externalSignal = options.signal;\n this.externalAbortListener = () => this.dispose();\n this.externalSignal.addEventListener('abort', this.externalAbortListener, { once: true });\n }\n\n get disposed(): boolean {\n return this.disposedValue;\n }\n\n readonly getSnapshot = (): T => this.currentValue;\n\n readonly subscribe = (listener: () => void): Unsubscribe => {\n if (this.disposedValue) return () => {};\n this.listeners.add(listener);\n return () => this.listeners.delete(listener);\n };\n\n readonly dispose = (): void => {\n if (this.disposedValue) return;\n this.disposedValue = true;\n\n if (this.externalSignal && this.externalAbortListener) {\n this.externalSignal.removeEventListener('abort', this.externalAbortListener);\n }\n\n const cleanup = this.cleanup;\n this.cleanup = undefined;\n this.externalSignal = undefined;\n this.externalAbortListener = undefined;\n this.listeners.clear();\n\n try {\n cleanup?.();\n } finally {\n this.tapper.emit({ type: 'dispose' });\n this.tapper.clear();\n this.abortController.abort();\n }\n };\n\n readonly tap = (handler: (event: SentinelEvent) => void, options?: { readonly signal?: AbortSignal }): Unsubscribe =>\n this.tapper.tap(handler, options);\n\n private notify(): void {\n for (const listener of [...this.listeners]) {\n try {\n listener();\n } catch (error) {\n this.tapper.emit({ error, type: 'error' });\n queueMicrotask(() => {\n throw error;\n });\n }\n }\n }\n\n [Symbol.dispose](): void {\n this.dispose();\n }\n}\n"],"mappings":"mEASA,SAAgB,EACd,EACA,EACa,CACb,OAAO,IAAI,EAAe,EAAS,CAAK,CAC1C,CAEA,IAAM,EAAN,KAA+C,CAC7C,eACA,gBAAmC,IAAI,gBACvC,aACA,UAA6B,IAAI,IACjC,QACA,cAAwB,GACxB,eACA,sBACA,QAAA,EAAiD,EAAA,OAAA,CAAsB,EAEvE,YAAY,EAAmC,EAAmD,CAChG,GAAI,EAAQ,QAAQ,QAClB,MAAM,IAAI,EAAA,cAAc,oCAAoC,EAG9D,KAAK,aAAe,EAAQ,aAC5B,KAAK,eAAiB,KAAK,gBAAgB,OAE3C,GAAI,CACF,KAAK,QAAU,EAAO,GAAU,CACzB,KAAK,gBACR,KAAK,aAAe,EACpB,KAAK,OAAO,EAEhB,CAAC,CACH,OAAS,EAAO,CAGd,KAFA,MAAK,cAAgB,GACrB,KAAK,gBAAgB,MAAM,EACrB,CACR,CAEK,EAAQ,SAEb,KAAK,eAAiB,EAAQ,OAC9B,KAAK,0BAA8B,KAAK,QAAQ,EAChD,KAAK,eAAe,iBAAiB,QAAS,KAAK,sBAAuB,CAAE,KAAM,EAAK,CAAC,EAC1F,CAEA,IAAI,UAAoB,CACtB,OAAO,KAAK,aACd,CAEA,gBAAgC,KAAK,aAErC,UAAsB,GAChB,KAAK,kBAA4B,CAAC,GACtC,KAAK,UAAU,IAAI,CAAQ,MACd,KAAK,UAAU,OAAO,CAAQ,GAG7C,YAA+B,CAC7B,GAAI,KAAK,cAAe,OACxB,KAAK,cAAgB,GAEjB,KAAK,gBAAkB,KAAK,uBAC9B,KAAK,eAAe,oBAAoB,QAAS,KAAK,qBAAqB,EAG7E,IAAM,EAAU,KAAK,QACrB,KAAK,QAAU,IAAA,GACf,KAAK,eAAiB,IAAA,GACtB,KAAK,sBAAwB,IAAA,GAC7B,KAAK,UAAU,MAAM,EAErB,GAAI,CACF,IAAU,CACZ,QAAU,CACR,KAAK,OAAO,KAAK,CAAE,KAAM,SAAU,CAAC,EACpC,KAAK,OAAO,MAAM,EAClB,KAAK,gBAAgB,MAAM,CAC7B,CACF,EAEA,KAAgB,EAAyC,IACvD,KAAK,OAAO,IAAI,EAAS,CAAO,EAElC,QAAuB,CACrB,IAAK,IAAM,IAAY,CAAC,GAAG,KAAK,SAAS,EACvC,GAAI,CACF,EAAS,CACX,OAAS,EAAO,CACd,KAAK,OAAO,KAAK,CAAE,QAAO,KAAM,OAAQ,CAAC,EACzC,mBAAqB,CACnB,MAAM,CACR,CAAC,CACH,CAEJ,CAEA,CAAC,OAAO,UAAiB,CACvB,KAAK,QAAQ,CACf,CACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"core.d.ts","sourceRoot":"","sources":["../src/core.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAE5D,MAAM,WAAW,qBAAqB,CAAC,CAAC,CAAE,SAAQ,eAAe;IAC/D,QAAQ,CAAC,YAAY,EAAE,CAAC,CAAC;CAC1B;AAED,wBAAgB,cAAc,CAAC,CAAC,EAC9B,OAAO,EAAE,qBAAqB,CAAC,CAAC,CAAC,EACjC,KAAK,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,KAAK,MAAM,IAAI,GAChD,QAAQ,CAAC,CAAC,CAAC,CAEb"}
1
+ {"version":3,"file":"core.d.ts","sourceRoot":"","sources":["../src/core.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,QAAQ,EAAiB,eAAe,EAAe,MAAM,YAAY,CAAC;AAExF,MAAM,WAAW,qBAAqB,CAAC,CAAC,CAAE,SAAQ,eAAe;IAC/D,QAAQ,CAAC,YAAY,EAAE,CAAC,CAAC;CAC1B;AAED,wBAAgB,cAAc,CAAC,CAAC,EAC9B,OAAO,EAAE,qBAAqB,CAAC,CAAC,CAAC,EACjC,KAAK,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,KAAK,MAAM,IAAI,GAChD,QAAQ,CAAC,CAAC,CAAC,CAEb"}
package/dist/core.js CHANGED
@@ -1,2 +1,2 @@
1
- import{signal as e}from"@vielzeug/ripple";function t(e,t){return new n(e,t)}var n=class{disposalSignal;abortController=new AbortController;state;cleanup;disposedValue=!1;externalSignal;externalAbortListener;constructor(t,n){let r=t.runtime?.signal??e;if(this.state=r(t.initialValue),this.disposalSignal=this.abortController.signal,t.signal?.aborted){this.disposedValue=!0,this.abortController.abort();return}try{this.cleanup=n(e=>{this.disposedValue||(this.state.value=e)})}catch(e){throw this.disposedValue=!0,this.abortController.abort(),e}t.signal&&(this.externalSignal=t.signal,this.externalAbortListener=()=>this.dispose(),this.externalSignal.addEventListener(`abort`,this.externalAbortListener,{once:!0}))}get disposed(){return this.disposedValue}get value(){return this.state.value}peek(){return this.state.peek()}subscribe(e){return this.state.subscribe(e)}dispose(){if(this.disposedValue)return;this.disposedValue=!0,this.externalSignal&&this.externalAbortListener&&this.externalSignal.removeEventListener(`abort`,this.externalAbortListener);let e=this.cleanup;this.cleanup=void 0,this.externalSignal=void 0,this.externalAbortListener=void 0;try{e?.()}finally{this.abortController.abort()}}[Symbol.dispose](){this.dispose()}};export{t as createSentinel};
1
+ import{SentinelError as e}from"./errors.js";import{tapper as t}from"@vielzeug/arsenal";function n(e,t){return new r(e,t)}var r=class{disposalSignal;abortController=new AbortController;currentValue;listeners=new Set;cleanup;disposedValue=!1;externalSignal;externalAbortListener;tapper=t();constructor(t,n){if(t.signal?.aborted)throw new e(`Sentinel signal is already aborted`);this.currentValue=t.initialValue,this.disposalSignal=this.abortController.signal;try{this.cleanup=n(e=>{this.disposedValue||(this.currentValue=e,this.notify())})}catch(e){throw this.disposedValue=!0,this.abortController.abort(),e}t.signal&&(this.externalSignal=t.signal,this.externalAbortListener=()=>this.dispose(),this.externalSignal.addEventListener(`abort`,this.externalAbortListener,{once:!0}))}get disposed(){return this.disposedValue}getSnapshot=()=>this.currentValue;subscribe=e=>this.disposedValue?()=>{}:(this.listeners.add(e),()=>this.listeners.delete(e));dispose=()=>{if(this.disposedValue)return;this.disposedValue=!0,this.externalSignal&&this.externalAbortListener&&this.externalSignal.removeEventListener(`abort`,this.externalAbortListener);let e=this.cleanup;this.cleanup=void 0,this.externalSignal=void 0,this.externalAbortListener=void 0,this.listeners.clear();try{e?.()}finally{this.tapper.emit({type:`dispose`}),this.tapper.clear(),this.abortController.abort()}};tap=(e,t)=>this.tapper.tap(e,t);notify(){for(let e of[...this.listeners])try{e()}catch(e){this.tapper.emit({error:e,type:`error`}),queueMicrotask(()=>{throw e})}}[Symbol.dispose](){this.dispose()}};export{n as createSentinel};
2
2
  //# sourceMappingURL=core.js.map
package/dist/core.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"core.js","names":[],"sources":["../src/core.ts"],"sourcesContent":["import type { Signal, Unsubscribe } from '@vielzeug/ripple';\nimport { signal as createSignal } from '@vielzeug/ripple';\nimport type { Sentinel, SentinelOptions } from './types.ts';\n\nexport interface CreateSentinelOptions<T> extends SentinelOptions {\n readonly initialValue: T;\n}\n\nexport function createSentinel<T>(\n options: CreateSentinelOptions<T>,\n setup: (update: (value: T) => void) => () => void,\n): Sentinel<T> {\n return new SentinelHandle(options, setup);\n}\n\nclass SentinelHandle<T> implements Sentinel<T> {\n readonly disposalSignal: AbortSignal;\n private readonly abortController = new AbortController();\n private readonly state: Signal<T>;\n private cleanup: (() => void) | undefined;\n private disposedValue = false;\n private externalSignal: AbortSignal | undefined;\n private externalAbortListener: (() => void) | undefined;\n\n constructor(options: CreateSentinelOptions<T>, setup: (update: (value: T) => void) => () => void) {\n const toSignal = options.runtime?.signal ?? createSignal;\n this.state = toSignal(options.initialValue);\n this.disposalSignal = this.abortController.signal;\n\n if (options.signal?.aborted) {\n this.disposedValue = true;\n this.abortController.abort();\n return;\n }\n\n try {\n this.cleanup = setup((value) => {\n if (!this.disposedValue) this.state.value = value;\n });\n } catch (error) {\n this.disposedValue = true;\n this.abortController.abort();\n throw error;\n }\n\n if (!options.signal) return;\n\n this.externalSignal = options.signal;\n this.externalAbortListener = () => this.dispose();\n this.externalSignal.addEventListener('abort', this.externalAbortListener, { once: true });\n }\n\n get disposed(): boolean {\n return this.disposedValue;\n }\n\n get value(): T {\n return this.state.value;\n }\n\n peek(): T {\n return this.state.peek();\n }\n\n subscribe(listener: () => void): Unsubscribe {\n return this.state.subscribe(listener);\n }\n\n dispose(): void {\n if (this.disposedValue) return;\n this.disposedValue = true;\n\n if (this.externalSignal && this.externalAbortListener) {\n this.externalSignal.removeEventListener('abort', this.externalAbortListener);\n }\n\n const cleanup = this.cleanup;\n this.cleanup = undefined;\n this.externalSignal = undefined;\n this.externalAbortListener = undefined;\n\n try {\n cleanup?.();\n } finally {\n this.abortController.abort();\n }\n }\n\n [Symbol.dispose](): void {\n this.dispose();\n }\n}\n"],"mappings":"0CAQA,SAAgB,EACd,EACA,EACa,CACb,OAAO,IAAI,EAAe,EAAS,CAAK,CAC1C,CAEA,IAAM,EAAN,KAA+C,CAC7C,eACA,gBAAmC,IAAI,gBACvC,MACA,QACA,cAAwB,GACxB,eACA,sBAEA,YAAY,EAAmC,EAAmD,CAChG,IAAM,EAAW,EAAQ,SAAS,QAAU,EAI5C,GAHA,KAAK,MAAQ,EAAS,EAAQ,YAAY,EAC1C,KAAK,eAAiB,KAAK,gBAAgB,OAEvC,EAAQ,QAAQ,QAAS,CAC3B,KAAK,cAAgB,GACrB,KAAK,gBAAgB,MAAM,EAC3B,MACF,CAEA,GAAI,CACF,KAAK,QAAU,EAAO,GAAU,CACzB,KAAK,gBAAe,KAAK,MAAM,MAAQ,EAC9C,CAAC,CACH,OAAS,EAAO,CAGd,KAFA,MAAK,cAAgB,GACrB,KAAK,gBAAgB,MAAM,EACrB,CACR,CAEK,EAAQ,SAEb,KAAK,eAAiB,EAAQ,OAC9B,KAAK,0BAA8B,KAAK,QAAQ,EAChD,KAAK,eAAe,iBAAiB,QAAS,KAAK,sBAAuB,CAAE,KAAM,EAAK,CAAC,EAC1F,CAEA,IAAI,UAAoB,CACtB,OAAO,KAAK,aACd,CAEA,IAAI,OAAW,CACb,OAAO,KAAK,MAAM,KACpB,CAEA,MAAU,CACR,OAAO,KAAK,MAAM,KAAK,CACzB,CAEA,UAAU,EAAmC,CAC3C,OAAO,KAAK,MAAM,UAAU,CAAQ,CACtC,CAEA,SAAgB,CACd,GAAI,KAAK,cAAe,OACxB,KAAK,cAAgB,GAEjB,KAAK,gBAAkB,KAAK,uBAC9B,KAAK,eAAe,oBAAoB,QAAS,KAAK,qBAAqB,EAG7E,IAAM,EAAU,KAAK,QACrB,KAAK,QAAU,IAAA,GACf,KAAK,eAAiB,IAAA,GACtB,KAAK,sBAAwB,IAAA,GAE7B,GAAI,CACF,IAAU,CACZ,QAAU,CACR,KAAK,gBAAgB,MAAM,CAC7B,CACF,CAEA,CAAC,OAAO,UAAiB,CACvB,KAAK,QAAQ,CACf,CACF"}
1
+ {"version":3,"file":"core.js","names":[],"sources":["../src/core.ts"],"sourcesContent":["import { type Tapper, tapper } from '@vielzeug/arsenal';\n\nimport { SentinelError } from './errors.ts';\nimport type { Sentinel, SentinelEvent, SentinelOptions, Unsubscribe } from './types.ts';\n\nexport interface CreateSentinelOptions<T> extends SentinelOptions {\n readonly initialValue: T;\n}\n\nexport function createSentinel<T>(\n options: CreateSentinelOptions<T>,\n setup: (update: (value: T) => void) => () => void,\n): Sentinel<T> {\n return new SentinelHandle(options, setup);\n}\n\nclass SentinelHandle<T> implements Sentinel<T> {\n readonly disposalSignal: AbortSignal;\n private readonly abortController = new AbortController();\n private currentValue: T;\n private readonly listeners = new Set<() => void>();\n private cleanup: (() => void) | undefined;\n private disposedValue = false;\n private externalSignal: AbortSignal | undefined;\n private externalAbortListener: (() => void) | undefined;\n private readonly tapper: Tapper<SentinelEvent> = tapper<SentinelEvent>();\n\n constructor(options: CreateSentinelOptions<T>, setup: (update: (value: T) => void) => () => void) {\n if (options.signal?.aborted) {\n throw new SentinelError('Sentinel signal is already aborted');\n }\n\n this.currentValue = options.initialValue;\n this.disposalSignal = this.abortController.signal;\n\n try {\n this.cleanup = setup((value) => {\n if (!this.disposedValue) {\n this.currentValue = value;\n this.notify();\n }\n });\n } catch (error) {\n this.disposedValue = true;\n this.abortController.abort();\n throw error;\n }\n\n if (!options.signal) return;\n\n this.externalSignal = options.signal;\n this.externalAbortListener = () => this.dispose();\n this.externalSignal.addEventListener('abort', this.externalAbortListener, { once: true });\n }\n\n get disposed(): boolean {\n return this.disposedValue;\n }\n\n readonly getSnapshot = (): T => this.currentValue;\n\n readonly subscribe = (listener: () => void): Unsubscribe => {\n if (this.disposedValue) return () => {};\n this.listeners.add(listener);\n return () => this.listeners.delete(listener);\n };\n\n readonly dispose = (): void => {\n if (this.disposedValue) return;\n this.disposedValue = true;\n\n if (this.externalSignal && this.externalAbortListener) {\n this.externalSignal.removeEventListener('abort', this.externalAbortListener);\n }\n\n const cleanup = this.cleanup;\n this.cleanup = undefined;\n this.externalSignal = undefined;\n this.externalAbortListener = undefined;\n this.listeners.clear();\n\n try {\n cleanup?.();\n } finally {\n this.tapper.emit({ type: 'dispose' });\n this.tapper.clear();\n this.abortController.abort();\n }\n };\n\n readonly tap = (handler: (event: SentinelEvent) => void, options?: { readonly signal?: AbortSignal }): Unsubscribe =>\n this.tapper.tap(handler, options);\n\n private notify(): void {\n for (const listener of [...this.listeners]) {\n try {\n listener();\n } catch (error) {\n this.tapper.emit({ error, type: 'error' });\n queueMicrotask(() => {\n throw error;\n });\n }\n }\n }\n\n [Symbol.dispose](): void {\n this.dispose();\n }\n}\n"],"mappings":"uFASA,SAAgB,EACd,EACA,EACa,CACb,OAAO,IAAI,EAAe,EAAS,CAAK,CAC1C,CAEA,IAAM,EAAN,KAA+C,CAC7C,eACA,gBAAmC,IAAI,gBACvC,aACA,UAA6B,IAAI,IACjC,QACA,cAAwB,GACxB,eACA,sBACA,OAAiD,EAAsB,EAEvE,YAAY,EAAmC,EAAmD,CAChG,GAAI,EAAQ,QAAQ,QAClB,MAAM,IAAI,EAAc,oCAAoC,EAG9D,KAAK,aAAe,EAAQ,aAC5B,KAAK,eAAiB,KAAK,gBAAgB,OAE3C,GAAI,CACF,KAAK,QAAU,EAAO,GAAU,CACzB,KAAK,gBACR,KAAK,aAAe,EACpB,KAAK,OAAO,EAEhB,CAAC,CACH,OAAS,EAAO,CAGd,KAFA,MAAK,cAAgB,GACrB,KAAK,gBAAgB,MAAM,EACrB,CACR,CAEK,EAAQ,SAEb,KAAK,eAAiB,EAAQ,OAC9B,KAAK,0BAA8B,KAAK,QAAQ,EAChD,KAAK,eAAe,iBAAiB,QAAS,KAAK,sBAAuB,CAAE,KAAM,EAAK,CAAC,EAC1F,CAEA,IAAI,UAAoB,CACtB,OAAO,KAAK,aACd,CAEA,gBAAgC,KAAK,aAErC,UAAsB,GAChB,KAAK,kBAA4B,CAAC,GACtC,KAAK,UAAU,IAAI,CAAQ,MACd,KAAK,UAAU,OAAO,CAAQ,GAG7C,YAA+B,CAC7B,GAAI,KAAK,cAAe,OACxB,KAAK,cAAgB,GAEjB,KAAK,gBAAkB,KAAK,uBAC9B,KAAK,eAAe,oBAAoB,QAAS,KAAK,qBAAqB,EAG7E,IAAM,EAAU,KAAK,QACrB,KAAK,QAAU,IAAA,GACf,KAAK,eAAiB,IAAA,GACtB,KAAK,sBAAwB,IAAA,GAC7B,KAAK,UAAU,MAAM,EAErB,GAAI,CACF,IAAU,CACZ,QAAU,CACR,KAAK,OAAO,KAAK,CAAE,KAAM,SAAU,CAAC,EACpC,KAAK,OAAO,MAAM,EAClB,KAAK,gBAAgB,MAAM,CAC7B,CACF,EAEA,KAAgB,EAAyC,IACvD,KAAK,OAAO,IAAI,EAAS,CAAO,EAElC,QAAuB,CACrB,IAAK,IAAM,IAAY,CAAC,GAAG,KAAK,SAAS,EACvC,GAAI,CACF,EAAS,CACX,OAAS,EAAO,CACd,KAAK,OAAO,KAAK,CAAE,QAAO,KAAM,OAAQ,CAAC,EACzC,mBAAqB,CACnB,MAAM,CACR,CAAC,CACH,CAEJ,CAEA,CAAC,OAAO,UAAiB,CACvB,KAAK,QAAQ,CACf,CACF"}
@@ -1,2 +1,2 @@
1
- const e=require("./errors.cjs"),t=require("./_platform.cjs"),n=require("./core.cjs");function r(r,i){let a=t.resolveElementWindow(r).ResizeObserver;if(typeof a!=`function`)throw new e.SentinelUnavailableError(`ResizeObserver is not available in this environment.`);return n.createSentinel({initialValue:null,...i},e=>{let t=new a(t=>{let n=t.at(-1);n&&e({height:n.contentRect.height,width:n.contentRect.width})});try{t.observe(r,{box:`content-box`})}catch(e){throw t.disconnect(),e}return()=>t.disconnect()})}exports.createElementSize=r;
1
+ const e=require("./errors.cjs"),t=require("./core.cjs"),n=require("./_platform.cjs");function r(r,i){let a=n.resolveElementWindow(r).ResizeObserver;if(typeof a!=`function`)throw new e.SentinelUnavailableError(`ResizeObserver is not available in this environment.`);return t.createSentinel({initialValue:null,...i},e=>{let t=new a(t=>{let n=t.at(-1);n&&e({height:n.contentRect.height,width:n.contentRect.width})});try{t.observe(r,{box:`content-box`})}catch(e){throw t.disconnect(),e}return()=>t.disconnect()})}exports.createElementSize=r;
2
2
  //# sourceMappingURL=element-size.cjs.map
@@ -1,2 +1,2 @@
1
- import{SentinelUnavailableError as e}from"./errors.js";import{resolveElementWindow as t}from"./_platform.js";import{createSentinel as n}from"./core.js";function r(r,i){let a=t(r).ResizeObserver;if(typeof a!=`function`)throw new e(`ResizeObserver is not available in this environment.`);return n({initialValue:null,...i},e=>{let t=new a(t=>{let n=t.at(-1);n&&e({height:n.contentRect.height,width:n.contentRect.width})});try{t.observe(r,{box:`content-box`})}catch(e){throw t.disconnect(),e}return()=>t.disconnect()})}export{r as createElementSize};
1
+ import{SentinelUnavailableError as e}from"./errors.js";import{createSentinel as t}from"./core.js";import{resolveElementWindow as n}from"./_platform.js";function r(r,i){let a=n(r).ResizeObserver;if(typeof a!=`function`)throw new e(`ResizeObserver is not available in this environment.`);return t({initialValue:null,...i},e=>{let t=new a(t=>{let n=t.at(-1);n&&e({height:n.contentRect.height,width:n.contentRect.width})});try{t.observe(r,{box:`content-box`})}catch(e){throw t.disconnect(),e}return()=>t.disconnect()})}export{r as createElementSize};
2
2
  //# sourceMappingURL=element-size.js.map
@@ -0,0 +1,2 @@
1
+ const e=require("./core.cjs"),t=require("./_platform.cjs");function n(n){let r=t.resolveWindow(n?.target).document,i=typeof r.documentElement?.requestFullscreen==`function`,a=()=>({active:r.fullscreenElement!==null,supported:i}),o=()=>{},s=e=>{!r.fullscreenElement&&i&&(e??r.documentElement).requestFullscreen().catch(()=>{})},c=()=>{r.fullscreenElement&&r.exitFullscreen().catch(()=>{})};return{...e.createSentinel({initialValue:a(),...n},e=>{o=e;let t=()=>o(a());return r.addEventListener(`fullscreenchange`,t),()=>{r.removeEventListener(`fullscreenchange`,t),r.fullscreenElement&&c()}}),exit:c,request:s,toggle(e){r.fullscreenElement?c():s(e)}}}exports.createFullscreen=n;
2
+ //# sourceMappingURL=fullscreen.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fullscreen.cjs","names":[],"sources":["../src/fullscreen.ts"],"sourcesContent":["import { resolveWindow } from './_platform.ts';\nimport { createSentinel } from './core.ts';\nimport type { FullscreenSentinel, FullscreenState, SentinelOptions } from './types.ts';\n\ntype FullscreenWindow = Window & { document: Document };\n\n/**\n * A sentinel-backed fullscreen view. `request()` enters fullscreen for an element (default:\n * the document element), `exit()` leaves it, and `toggle()` flips it — all degrade silently\n * when the Fullscreen API is missing or the browser refuses (user-activation requirements).\n * Disposing the sentinel exits fullscreen and stops tracking `fullscreenchange`.\n */\nexport function createFullscreen(\n options?: SentinelOptions & { readonly target?: FullscreenWindow },\n): FullscreenSentinel {\n const target = resolveWindow(options?.target);\n const doc = target.document;\n const supported = typeof doc.documentElement?.requestFullscreen === 'function';\n\n const read = (): FullscreenState => ({ active: doc.fullscreenElement !== null, supported });\n let updateState: (value: FullscreenState) => void = () => {};\n\n const request = (element?: Element): void => {\n if (doc.fullscreenElement || !supported) return;\n void (element ?? doc.documentElement).requestFullscreen().catch(() => {\n // Requires user activation in some browsers, or the element may refuse; degrade silently.\n });\n };\n\n const exit = (): void => {\n if (!doc.fullscreenElement) return;\n void doc.exitFullscreen().catch(() => {\n // Degrade silently.\n });\n };\n\n const sentinel = createSentinel<FullscreenState>({ initialValue: read(), ...options }, (update) => {\n updateState = update;\n\n const onFullscreenChange = (): void => updateState(read());\n\n doc.addEventListener('fullscreenchange', onFullscreenChange);\n\n return () => {\n doc.removeEventListener('fullscreenchange', onFullscreenChange);\n if (doc.fullscreenElement) exit();\n };\n });\n\n return {\n ...sentinel,\n exit,\n request,\n toggle(element?: Element): void {\n if (doc.fullscreenElement) exit();\n else request(element);\n },\n };\n}\n"],"mappings":"2DAYA,SAAgB,EACd,EACoB,CAEpB,IAAM,EADS,EAAA,cAAc,GAAS,MAC1B,CAAA,CAAO,SACb,EAAY,OAAO,EAAI,iBAAiB,mBAAsB,WAE9D,OAA+B,CAAE,OAAQ,EAAI,oBAAsB,KAAM,WAAU,GACrF,MAAsD,CAAC,EAErD,EAAW,GAA4B,CACvC,GAAI,mBAAsB,IACxB,GAAW,EAAI,gBAAA,CAAiB,kBAAkB,CAAC,CAAC,UAAY,CAEtE,CAAC,CACH,EAEM,MAAmB,CAClB,EAAI,mBACT,EAAS,eAAe,CAAC,CAAC,UAAY,CAEtC,CAAC,CACH,EAeA,MAAO,CACL,GAde,EAAA,eAAgC,CAAE,aAAc,EAAK,EAAG,GAAG,CAAQ,EAAI,GAAW,CACjG,EAAc,EAEd,IAAM,MAAiC,EAAY,EAAK,CAAC,EAIzD,OAFA,EAAI,iBAAiB,mBAAoB,CAAkB,MAE9C,CACX,EAAI,oBAAoB,mBAAoB,CAAkB,EAC1D,EAAI,mBAAmB,EAAK,CAClC,CACF,CAGK,EACH,OACA,UACA,OAAO,EAAyB,CAC1B,EAAI,kBAAmB,EAAK,EAC3B,EAAQ,CAAO,CACtB,CACF,CACF"}
@@ -0,0 +1,15 @@
1
+ import type { FullscreenSentinel, SentinelOptions } from './types.ts';
2
+ type FullscreenWindow = Window & {
3
+ document: Document;
4
+ };
5
+ /**
6
+ * A sentinel-backed fullscreen view. `request()` enters fullscreen for an element (default:
7
+ * the document element), `exit()` leaves it, and `toggle()` flips it — all degrade silently
8
+ * when the Fullscreen API is missing or the browser refuses (user-activation requirements).
9
+ * Disposing the sentinel exits fullscreen and stops tracking `fullscreenchange`.
10
+ */
11
+ export declare function createFullscreen(options?: SentinelOptions & {
12
+ readonly target?: FullscreenWindow;
13
+ }): FullscreenSentinel;
14
+ export {};
15
+ //# sourceMappingURL=fullscreen.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fullscreen.d.ts","sourceRoot":"","sources":["../src/fullscreen.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,kBAAkB,EAAmB,eAAe,EAAE,MAAM,YAAY,CAAC;AAEvF,KAAK,gBAAgB,GAAG,MAAM,GAAG;IAAE,QAAQ,EAAE,QAAQ,CAAA;CAAE,CAAC;AAExD;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,CAAC,EAAE,eAAe,GAAG;IAAE,QAAQ,CAAC,MAAM,CAAC,EAAE,gBAAgB,CAAA;CAAE,GACjE,kBAAkB,CA4CpB"}
@@ -0,0 +1,2 @@
1
+ import{createSentinel as e}from"./core.js";import{resolveWindow as t}from"./_platform.js";function n(n){let r=t(n?.target).document,i=typeof r.documentElement?.requestFullscreen==`function`,a=()=>({active:r.fullscreenElement!==null,supported:i}),o=()=>{},s=e=>{!r.fullscreenElement&&i&&(e??r.documentElement).requestFullscreen().catch(()=>{})},c=()=>{r.fullscreenElement&&r.exitFullscreen().catch(()=>{})};return{...e({initialValue:a(),...n},e=>{o=e;let t=()=>o(a());return r.addEventListener(`fullscreenchange`,t),()=>{r.removeEventListener(`fullscreenchange`,t),r.fullscreenElement&&c()}}),exit:c,request:s,toggle(e){r.fullscreenElement?c():s(e)}}}export{n as createFullscreen};
2
+ //# sourceMappingURL=fullscreen.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fullscreen.js","names":[],"sources":["../src/fullscreen.ts"],"sourcesContent":["import { resolveWindow } from './_platform.ts';\nimport { createSentinel } from './core.ts';\nimport type { FullscreenSentinel, FullscreenState, SentinelOptions } from './types.ts';\n\ntype FullscreenWindow = Window & { document: Document };\n\n/**\n * A sentinel-backed fullscreen view. `request()` enters fullscreen for an element (default:\n * the document element), `exit()` leaves it, and `toggle()` flips it — all degrade silently\n * when the Fullscreen API is missing or the browser refuses (user-activation requirements).\n * Disposing the sentinel exits fullscreen and stops tracking `fullscreenchange`.\n */\nexport function createFullscreen(\n options?: SentinelOptions & { readonly target?: FullscreenWindow },\n): FullscreenSentinel {\n const target = resolveWindow(options?.target);\n const doc = target.document;\n const supported = typeof doc.documentElement?.requestFullscreen === 'function';\n\n const read = (): FullscreenState => ({ active: doc.fullscreenElement !== null, supported });\n let updateState: (value: FullscreenState) => void = () => {};\n\n const request = (element?: Element): void => {\n if (doc.fullscreenElement || !supported) return;\n void (element ?? doc.documentElement).requestFullscreen().catch(() => {\n // Requires user activation in some browsers, or the element may refuse; degrade silently.\n });\n };\n\n const exit = (): void => {\n if (!doc.fullscreenElement) return;\n void doc.exitFullscreen().catch(() => {\n // Degrade silently.\n });\n };\n\n const sentinel = createSentinel<FullscreenState>({ initialValue: read(), ...options }, (update) => {\n updateState = update;\n\n const onFullscreenChange = (): void => updateState(read());\n\n doc.addEventListener('fullscreenchange', onFullscreenChange);\n\n return () => {\n doc.removeEventListener('fullscreenchange', onFullscreenChange);\n if (doc.fullscreenElement) exit();\n };\n });\n\n return {\n ...sentinel,\n exit,\n request,\n toggle(element?: Element): void {\n if (doc.fullscreenElement) exit();\n else request(element);\n },\n };\n}\n"],"mappings":"0FAYA,SAAgB,EACd,EACoB,CAEpB,IAAM,EADS,EAAc,GAAS,MAC1B,CAAA,CAAO,SACb,EAAY,OAAO,EAAI,iBAAiB,mBAAsB,WAE9D,OAA+B,CAAE,OAAQ,EAAI,oBAAsB,KAAM,WAAU,GACrF,MAAsD,CAAC,EAErD,EAAW,GAA4B,CACvC,GAAI,mBAAsB,IACxB,GAAW,EAAI,gBAAA,CAAiB,kBAAkB,CAAC,CAAC,UAAY,CAEtE,CAAC,CACH,EAEM,MAAmB,CAClB,EAAI,mBACT,EAAS,eAAe,CAAC,CAAC,UAAY,CAEtC,CAAC,CACH,EAeA,MAAO,CACL,GAde,EAAgC,CAAE,aAAc,EAAK,EAAG,GAAG,CAAQ,EAAI,GAAW,CACjG,EAAc,EAEd,IAAM,MAAiC,EAAY,EAAK,CAAC,EAIzD,OAFA,EAAI,iBAAiB,mBAAoB,CAAkB,MAE9C,CACX,EAAI,oBAAoB,mBAAoB,CAAkB,EAC1D,EAAI,mBAAmB,EAAK,CAClC,CACF,CAGK,EACH,OACA,UACA,OAAO,EAAyB,CAC1B,EAAI,kBAAmB,EAAK,EAC3B,EAAQ,CAAO,CACtB,CACF,CACF"}
package/dist/index.cjs CHANGED
@@ -1 +1 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./errors.cjs"),t=require("./element-size.cjs"),n=require("./intersection.cjs"),r=require("./media-query.cjs"),i=require("./network.cjs"),a=require("./viewport.cjs");exports.SentinelError=e.SentinelError,exports.SentinelUnavailableError=e.SentinelUnavailableError,exports.createElementSize=t.createElementSize,exports.createIntersection=n.createIntersection,exports.createMediaQuery=r.createMediaQuery,exports.createNetwork=i.createNetwork,exports.createViewport=a.createViewport;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./errors.cjs"),t=require("./core.cjs"),n=require("./element-size.cjs"),r=require("./fullscreen.cjs"),i=require("./intersection.cjs"),a=require("./media-query.cjs"),o=require("./network.cjs"),s=require("./viewport.cjs"),c=require("./wake-lock.cjs");exports.SentinelError=e.SentinelError,exports.SentinelUnavailableError=e.SentinelUnavailableError,exports.createElementSize=n.createElementSize,exports.createFullscreen=r.createFullscreen,exports.createIntersection=i.createIntersection,exports.createMediaQuery=a.createMediaQuery,exports.createNetwork=o.createNetwork,exports.createSentinel=t.createSentinel,exports.createViewport=s.createViewport,exports.createWakeLock=c.createWakeLock;
package/dist/index.d.ts CHANGED
@@ -1,9 +1,13 @@
1
+ export type { CreateSentinelOptions } from './core.ts';
2
+ export { createSentinel } from './core.ts';
1
3
  export { createElementSize } from './element-size.ts';
2
4
  export { SentinelError, SentinelUnavailableError } from './errors.ts';
5
+ export { createFullscreen } from './fullscreen.ts';
3
6
  export type { CreateIntersectionOptions } from './intersection.ts';
4
7
  export { createIntersection } from './intersection.ts';
5
8
  export { createMediaQuery } from './media-query.ts';
6
9
  export { createNetwork } from './network.ts';
7
- export type { ElementSizeState, IntersectionState, MediaQueryState, NetworkConnectionSnapshot, NetworkState, Sentinel, SentinelOptions, ViewportState, WindowSentinelOptions, } from './types.ts';
10
+ export type { ElementSizeState, FullscreenSentinel, FullscreenState, IntersectionState, MediaQueryState, NetworkConnectionSnapshot, NetworkState, Sentinel, SentinelEvent, SentinelOptions, Subscribable, Unsubscribe, ViewportState, WakeLockSentinel, WakeLockState, WindowSentinelOptions, } from './types.ts';
8
11
  export { createViewport } from './viewport.ts';
12
+ export { createWakeLock } from './wake-lock.ts';
9
13
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACtD,OAAO,EAAE,aAAa,EAAE,wBAAwB,EAAE,MAAM,aAAa,CAAC;AACtE,YAAY,EAAE,yBAAyB,EAAE,MAAM,mBAAmB,CAAC;AACnE,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,YAAY,EACV,gBAAgB,EAChB,iBAAiB,EACjB,eAAe,EACf,yBAAyB,EACzB,YAAY,EACZ,QAAQ,EACR,eAAe,EACf,aAAa,EACb,qBAAqB,GACtB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,qBAAqB,EAAE,MAAM,WAAW,CAAC;AACvD,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACtD,OAAO,EAAE,aAAa,EAAE,wBAAwB,EAAE,MAAM,aAAa,CAAC;AACtE,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACnD,YAAY,EAAE,yBAAyB,EAAE,MAAM,mBAAmB,CAAC;AACnE,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,YAAY,EACV,gBAAgB,EAChB,kBAAkB,EAClB,eAAe,EACf,iBAAiB,EACjB,eAAe,EACf,yBAAyB,EACzB,YAAY,EACZ,QAAQ,EACR,aAAa,EACb,eAAe,EACf,YAAY,EACZ,WAAW,EACX,aAAa,EACb,gBAAgB,EAChB,aAAa,EACb,qBAAqB,GACtB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC"}
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- import{SentinelError as e,SentinelUnavailableError as t}from"./errors.js";import{createElementSize as n}from"./element-size.js";import{createIntersection as r}from"./intersection.js";import{createMediaQuery as i}from"./media-query.js";import{createNetwork as a}from"./network.js";import{createViewport as o}from"./viewport.js";export{e as SentinelError,t as SentinelUnavailableError,n as createElementSize,r as createIntersection,i as createMediaQuery,a as createNetwork,o as createViewport};
1
+ import{SentinelError as e,SentinelUnavailableError as t}from"./errors.js";import{createSentinel as n}from"./core.js";import{createElementSize as r}from"./element-size.js";import{createFullscreen as i}from"./fullscreen.js";import{createIntersection as a}from"./intersection.js";import{createMediaQuery as o}from"./media-query.js";import{createNetwork as s}from"./network.js";import{createViewport as c}from"./viewport.js";import{createWakeLock as l}from"./wake-lock.js";export{e as SentinelError,t as SentinelUnavailableError,r as createElementSize,i as createFullscreen,a as createIntersection,o as createMediaQuery,s as createNetwork,n as createSentinel,c as createViewport,l as createWakeLock};
@@ -1,2 +1,2 @@
1
- const e=require("./errors.cjs"),t=require("./_platform.cjs"),n=require("./core.cjs");function r(r,i){let a=t.resolveElementWindow(r).IntersectionObserver;if(typeof a!=`function`)throw new e.SentinelUnavailableError(`IntersectionObserver is not available in this environment.`);return n.createSentinel({initialValue:null,...i},e=>{let t=new a(t=>{let n=t.at(-1);n&&e({intersectionRatio:n.intersectionRatio,isIntersecting:n.isIntersecting})},{root:i?.root,rootMargin:i?.rootMargin,scrollMargin:i?.scrollMargin,threshold:i?.threshold});try{t.observe(r)}catch(e){throw t.disconnect(),e}return()=>t.disconnect()})}exports.createIntersection=r;
1
+ const e=require("./errors.cjs"),t=require("./core.cjs"),n=require("./_platform.cjs");function r(r,i){let a=n.resolveElementWindow(r).IntersectionObserver;if(typeof a!=`function`)throw new e.SentinelUnavailableError(`IntersectionObserver is not available in this environment.`);return t.createSentinel({initialValue:null,...i},e=>{let t=new a(t=>{let n=t.at(-1);n&&e({intersectionRatio:n.intersectionRatio,isIntersecting:n.isIntersecting})},{root:i?.root,rootMargin:i?.rootMargin,scrollMargin:i?.scrollMargin,threshold:i?.threshold});try{t.observe(r)}catch(e){throw t.disconnect(),e}return()=>t.disconnect()})}exports.createIntersection=r;
2
2
  //# sourceMappingURL=intersection.cjs.map
@@ -1,2 +1,2 @@
1
- import{SentinelUnavailableError as e}from"./errors.js";import{resolveElementWindow as t}from"./_platform.js";import{createSentinel as n}from"./core.js";function r(r,i){let a=t(r).IntersectionObserver;if(typeof a!=`function`)throw new e(`IntersectionObserver is not available in this environment.`);return n({initialValue:null,...i},e=>{let t=new a(t=>{let n=t.at(-1);n&&e({intersectionRatio:n.intersectionRatio,isIntersecting:n.isIntersecting})},{root:i?.root,rootMargin:i?.rootMargin,scrollMargin:i?.scrollMargin,threshold:i?.threshold});try{t.observe(r)}catch(e){throw t.disconnect(),e}return()=>t.disconnect()})}export{r as createIntersection};
1
+ import{SentinelUnavailableError as e}from"./errors.js";import{createSentinel as t}from"./core.js";import{resolveElementWindow as n}from"./_platform.js";function r(r,i){let a=n(r).IntersectionObserver;if(typeof a!=`function`)throw new e(`IntersectionObserver is not available in this environment.`);return t({initialValue:null,...i},e=>{let t=new a(t=>{let n=t.at(-1);n&&e({intersectionRatio:n.intersectionRatio,isIntersecting:n.isIntersecting})},{root:i?.root,rootMargin:i?.rootMargin,scrollMargin:i?.scrollMargin,threshold:i?.threshold});try{t.observe(r)}catch(e){throw t.disconnect(),e}return()=>t.disconnect()})}export{r as createIntersection};
2
2
  //# sourceMappingURL=intersection.js.map
@@ -1,2 +1,2 @@
1
- const e=require("./errors.cjs"),t=require("./_platform.cjs"),n=require("./core.cjs");function r(r,i){let a=t.resolveWindow(i?.target);if(typeof a.matchMedia!=`function`)throw new e.SentinelUnavailableError(`matchMedia is not available for query "${r}".`);let o=a.matchMedia(r),s=()=>({matches:o.matches});return n.createSentinel({initialValue:s(),...i},e=>t.listenMediaQuery(o,()=>e(s())))}exports.createMediaQuery=r;
1
+ const e=require("./errors.cjs"),t=require("./core.cjs"),n=require("./_platform.cjs");function r(r,i){let a=n.resolveWindow(i?.target);if(typeof a.matchMedia!=`function`)throw new e.SentinelUnavailableError(`matchMedia is not available for query "${r}".`);let o=a.matchMedia(r),s=()=>({matches:o.matches});return t.createSentinel({initialValue:s(),...i},e=>n.listenMediaQuery(o,()=>e(s())))}exports.createMediaQuery=r;
2
2
  //# sourceMappingURL=media-query.cjs.map
@@ -1,2 +1,2 @@
1
- import{SentinelUnavailableError as e}from"./errors.js";import{listenMediaQuery as t,resolveWindow as n}from"./_platform.js";import{createSentinel as r}from"./core.js";function i(i,a){let o=n(a?.target);if(typeof o.matchMedia!=`function`)throw new e(`matchMedia is not available for query "${i}".`);let s=o.matchMedia(i),c=()=>({matches:s.matches});return r({initialValue:c(),...a},e=>t(s,()=>e(c())))}export{i as createMediaQuery};
1
+ import{SentinelUnavailableError as e}from"./errors.js";import{createSentinel as t}from"./core.js";import{listenMediaQuery as n,resolveWindow as r}from"./_platform.js";function i(i,a){let o=r(a?.target);if(typeof o.matchMedia!=`function`)throw new e(`matchMedia is not available for query "${i}".`);let s=o.matchMedia(i),c=()=>({matches:s.matches});return t({initialValue:c(),...a},e=>n(s,()=>e(c())))}export{i as createMediaQuery};
2
2
  //# sourceMappingURL=media-query.js.map
package/dist/network.cjs CHANGED
@@ -1,2 +1,2 @@
1
- const e=require("./_platform.cjs"),t=require("./core.cjs");function n(e){return e?{downlink:e.downlink,effectiveType:e.effectiveType,rtt:e.rtt,saveData:e.saveData}:null}function r(r){let i=e.resolveWindow(r?.target),a=i.navigator,o=()=>({connection:n(a.connection),online:a.onLine});return t.createSentinel({initialValue:o(),...r},e=>{let t=()=>e(o()),n=a.connection;return i.addEventListener(`online`,t),i.addEventListener(`offline`,t),n?.addEventListener(`change`,t),()=>{i.removeEventListener(`online`,t),i.removeEventListener(`offline`,t),n?.removeEventListener(`change`,t)}})}exports.createNetwork=r;
1
+ const e=require("./core.cjs"),t=require("./_platform.cjs");function n(e){return e?{downlink:e.downlink,effectiveType:e.effectiveType,rtt:e.rtt,saveData:e.saveData}:null}function r(r){let i=t.resolveWindow(r?.target),a=i.navigator,o=()=>({connection:n(a.connection),online:a.onLine});return e.createSentinel({initialValue:o(),...r},e=>{let t=()=>e(o()),n=a.connection;return i.addEventListener(`online`,t),i.addEventListener(`offline`,t),n?.addEventListener(`change`,t),()=>{i.removeEventListener(`online`,t),i.removeEventListener(`offline`,t),n?.removeEventListener(`change`,t)}})}exports.createNetwork=r;
2
2
  //# sourceMappingURL=network.cjs.map
package/dist/network.js CHANGED
@@ -1,2 +1,2 @@
1
- import{resolveWindow as e}from"./_platform.js";import{createSentinel as t}from"./core.js";function n(e){return e?{downlink:e.downlink,effectiveType:e.effectiveType,rtt:e.rtt,saveData:e.saveData}:null}function r(r){let i=e(r?.target),a=i.navigator,o=()=>({connection:n(a.connection),online:a.onLine});return t({initialValue:o(),...r},e=>{let t=()=>e(o()),n=a.connection;return i.addEventListener(`online`,t),i.addEventListener(`offline`,t),n?.addEventListener(`change`,t),()=>{i.removeEventListener(`online`,t),i.removeEventListener(`offline`,t),n?.removeEventListener(`change`,t)}})}export{r as createNetwork};
1
+ import{createSentinel as e}from"./core.js";import{resolveWindow as t}from"./_platform.js";function n(e){return e?{downlink:e.downlink,effectiveType:e.effectiveType,rtt:e.rtt,saveData:e.saveData}:null}function r(r){let i=t(r?.target),a=i.navigator,o=()=>({connection:n(a.connection),online:a.onLine});return e({initialValue:o(),...r},e=>{let t=()=>e(o()),n=a.connection;return i.addEventListener(`online`,t),i.addEventListener(`offline`,t),n?.addEventListener(`change`,t),()=>{i.removeEventListener(`online`,t),i.removeEventListener(`offline`,t),n?.removeEventListener(`change`,t)}})}export{r as createNetwork};
2
2
  //# sourceMappingURL=network.js.map
package/dist/sentinel.cjs CHANGED
@@ -1,2 +1,2 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require("@vielzeug/ripple");var t=class extends Error{constructor(e,t){super(e,t),this.name=new.target.name,Object.setPrototypeOf(this,new.target.prototype)}},n=class extends t{};function r(e){if(e)return e;if(typeof window>`u`)throw new n(`Window is not available in this environment.`);return window}function i(e){let t=e.ownerDocument.defaultView;if(!t)throw new n(`The element is not connected to a Window.`);return t}function a(e,t){let n=()=>t();if(typeof e.addEventListener==`function`)return e.addEventListener(`change`,n),()=>e.removeEventListener(`change`,n);let r=e;return typeof r.addListener==`function`?(r.addListener(n),()=>r.removeListener?.(n)):()=>{}}function o(e,t){return new s(e,t)}var s=class{disposalSignal;abortController=new AbortController;state;cleanup;disposedValue=!1;externalSignal;externalAbortListener;constructor(t,n){let r=t.runtime?.signal??e.signal;if(this.state=r(t.initialValue),this.disposalSignal=this.abortController.signal,t.signal?.aborted){this.disposedValue=!0,this.abortController.abort();return}try{this.cleanup=n(e=>{this.disposedValue||(this.state.value=e)})}catch(e){throw this.disposedValue=!0,this.abortController.abort(),e}t.signal&&(this.externalSignal=t.signal,this.externalAbortListener=()=>this.dispose(),this.externalSignal.addEventListener(`abort`,this.externalAbortListener,{once:!0}))}get disposed(){return this.disposedValue}get value(){return this.state.value}peek(){return this.state.peek()}subscribe(e){return this.state.subscribe(e)}dispose(){if(this.disposedValue)return;this.disposedValue=!0,this.externalSignal&&this.externalAbortListener&&this.externalSignal.removeEventListener(`abort`,this.externalAbortListener);let e=this.cleanup;this.cleanup=void 0,this.externalSignal=void 0,this.externalAbortListener=void 0;try{e?.()}finally{this.abortController.abort()}}[Symbol.dispose](){this.dispose()}};function c(e,t){let r=i(e).ResizeObserver;if(typeof r!=`function`)throw new n(`ResizeObserver is not available in this environment.`);return o({initialValue:null,...t},t=>{let n=new r(e=>{let n=e.at(-1);n&&t({height:n.contentRect.height,width:n.contentRect.width})});try{n.observe(e,{box:`content-box`})}catch(e){throw n.disconnect(),e}return()=>n.disconnect()})}function l(e,t){let r=i(e).IntersectionObserver;if(typeof r!=`function`)throw new n(`IntersectionObserver is not available in this environment.`);return o({initialValue:null,...t},n=>{let i=new r(e=>{let t=e.at(-1);t&&n({intersectionRatio:t.intersectionRatio,isIntersecting:t.isIntersecting})},{root:t?.root,rootMargin:t?.rootMargin,scrollMargin:t?.scrollMargin,threshold:t?.threshold});try{i.observe(e)}catch(e){throw i.disconnect(),e}return()=>i.disconnect()})}function u(e,t){let i=r(t?.target);if(typeof i.matchMedia!=`function`)throw new n(`matchMedia is not available for query "${e}".`);let s=i.matchMedia(e),c=()=>({matches:s.matches});return o({initialValue:c(),...t},e=>a(s,()=>e(c())))}function d(e){return e?{downlink:e.downlink,effectiveType:e.effectiveType,rtt:e.rtt,saveData:e.saveData}:null}function f(e){let t=r(e?.target),n=t.navigator,i=()=>({connection:d(n.connection),online:n.onLine});return o({initialValue:i(),...e},e=>{let r=()=>e(i()),a=n.connection;return t.addEventListener(`online`,r),t.addEventListener(`offline`,r),a?.addEventListener(`change`,r),()=>{t.removeEventListener(`online`,r),t.removeEventListener(`offline`,r),a?.removeEventListener(`change`,r)}})}function p(e){let t=r(e?.target),n=()=>({dpr:t.devicePixelRatio,height:t.innerHeight,width:t.innerWidth});return o({initialValue:n(),...e},e=>{let r=()=>e(n()),i,o=()=>{i?.(),i=void 0,typeof t.matchMedia==`function`&&(i=a(t.matchMedia(`(resolution: ${t.devicePixelRatio}dppx)`),()=>{e(n()),o()}))};return t.addEventListener(`resize`,r),o(),()=>{t.removeEventListener(`resize`,r),i?.()}})}exports.SentinelError=t,exports.SentinelUnavailableError=n,exports.createElementSize=c,exports.createIntersection=l,exports.createMediaQuery=u,exports.createNetwork=f,exports.createViewport=p;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require("@vielzeug/arsenal");var SentinelError=class extends Error{constructor(e,t){super(e,t),this.name=new.target.name,Object.setPrototypeOf(this,new.target.prototype)}},SentinelUnavailableError=class extends SentinelError{};function createSentinel(e,t){return new SentinelHandle(e,t)}var SentinelHandle=class{disposalSignal;abortController=new AbortController;currentValue;listeners=new Set;cleanup;disposedValue=!1;externalSignal;externalAbortListener;tapper=(0,e.tapper)();constructor(e,t){if(e.signal?.aborted)throw new SentinelError(`Sentinel signal is already aborted`);this.currentValue=e.initialValue,this.disposalSignal=this.abortController.signal;try{this.cleanup=t(e=>{this.disposedValue||(this.currentValue=e,this.notify())})}catch(e){throw this.disposedValue=!0,this.abortController.abort(),e}e.signal&&(this.externalSignal=e.signal,this.externalAbortListener=()=>this.dispose(),this.externalSignal.addEventListener(`abort`,this.externalAbortListener,{once:!0}))}get disposed(){return this.disposedValue}getSnapshot=()=>this.currentValue;subscribe=e=>this.disposedValue?()=>{}:(this.listeners.add(e),()=>this.listeners.delete(e));dispose=()=>{if(this.disposedValue)return;this.disposedValue=!0,this.externalSignal&&this.externalAbortListener&&this.externalSignal.removeEventListener(`abort`,this.externalAbortListener);let e=this.cleanup;this.cleanup=void 0,this.externalSignal=void 0,this.externalAbortListener=void 0,this.listeners.clear();try{e?.()}finally{this.tapper.emit({type:`dispose`}),this.tapper.clear(),this.abortController.abort()}};tap=(e,t)=>this.tapper.tap(e,t);notify(){for(let e of[...this.listeners])try{e()}catch(e){this.tapper.emit({error:e,type:`error`}),queueMicrotask(()=>{throw e})}}[Symbol.dispose](){this.dispose()}};function resolveWindow(e){if(e)return e;if(typeof window>`u`)throw new SentinelUnavailableError(`Window is not available in this environment.`);return window}function resolveElementWindow(e){let t=e.ownerDocument.defaultView;if(!t)throw new SentinelUnavailableError(`The element is not connected to a Window.`);return t}function listenMediaQuery(e,t){let onChange=()=>t();if(typeof e.addEventListener==`function`)return e.addEventListener(`change`,onChange),()=>e.removeEventListener(`change`,onChange);let n=e;return typeof n.addListener==`function`?(n.addListener(onChange),()=>n.removeListener?.(onChange)):()=>{}}function createElementSize(e,t){let n=resolveElementWindow(e).ResizeObserver;if(typeof n!=`function`)throw new SentinelUnavailableError(`ResizeObserver is not available in this environment.`);return createSentinel({initialValue:null,...t},t=>{let r=new n(e=>{let n=e.at(-1);n&&t({height:n.contentRect.height,width:n.contentRect.width})});try{r.observe(e,{box:`content-box`})}catch(e){throw r.disconnect(),e}return()=>r.disconnect()})}function createFullscreen(e){let t=resolveWindow(e?.target).document,n=typeof t.documentElement?.requestFullscreen==`function`,read=()=>({active:t.fullscreenElement!==null,supported:n}),updateState=()=>{},request=e=>{!t.fullscreenElement&&n&&(e??t.documentElement).requestFullscreen().catch(()=>{})},exit=()=>{t.fullscreenElement&&t.exitFullscreen().catch(()=>{})};return{...createSentinel({initialValue:read(),...e},e=>{updateState=e;let onFullscreenChange=()=>updateState(read());return t.addEventListener(`fullscreenchange`,onFullscreenChange),()=>{t.removeEventListener(`fullscreenchange`,onFullscreenChange),t.fullscreenElement&&exit()}}),exit,request,toggle(e){t.fullscreenElement?exit():request(e)}}}function createIntersection(e,t){let n=resolveElementWindow(e).IntersectionObserver;if(typeof n!=`function`)throw new SentinelUnavailableError(`IntersectionObserver is not available in this environment.`);return createSentinel({initialValue:null,...t},r=>{let i=new n(e=>{let t=e.at(-1);t&&r({intersectionRatio:t.intersectionRatio,isIntersecting:t.isIntersecting})},{root:t?.root,rootMargin:t?.rootMargin,scrollMargin:t?.scrollMargin,threshold:t?.threshold});try{i.observe(e)}catch(e){throw i.disconnect(),e}return()=>i.disconnect()})}function createMediaQuery(e,t){let n=resolveWindow(t?.target);if(typeof n.matchMedia!=`function`)throw new SentinelUnavailableError(`matchMedia is not available for query "${e}".`);let r=n.matchMedia(e),read=()=>({matches:r.matches});return createSentinel({initialValue:read(),...t},e=>listenMediaQuery(r,()=>e(read())))}function normalizeConnection(e){return e?{downlink:e.downlink,effectiveType:e.effectiveType,rtt:e.rtt,saveData:e.saveData}:null}function createNetwork(e){let t=resolveWindow(e?.target),n=t.navigator,read=()=>({connection:normalizeConnection(n.connection),online:n.onLine});return createSentinel({initialValue:read(),...e},e=>{let onChange=()=>e(read()),r=n.connection;return t.addEventListener(`online`,onChange),t.addEventListener(`offline`,onChange),r?.addEventListener(`change`,onChange),()=>{t.removeEventListener(`online`,onChange),t.removeEventListener(`offline`,onChange),r?.removeEventListener(`change`,onChange)}})}function createViewport(e){let t=resolveWindow(e?.target),read=()=>({dpr:t.devicePixelRatio,height:t.innerHeight,width:t.innerWidth});return createSentinel({initialValue:read(),...e},e=>{let onResize=()=>e(read()),n,observeDpr=()=>{n?.(),n=void 0,typeof t.matchMedia==`function`&&(n=listenMediaQuery(t.matchMedia(`(resolution: ${t.devicePixelRatio}dppx)`),()=>{e(read()),observeDpr()}))};return t.addEventListener(`resize`,onResize),observeDpr(),()=>{t.removeEventListener(`resize`,onResize),n?.()}})}function createWakeLock(e){let t=resolveWindow(e?.target),n=t.navigator,r=typeof n.wakeLock?.request==`function`,i=null,a=!1,o=!1,read=()=>({active:i!==null,supported:r}),updateState=()=>{};async function acquire(){if(r&&n.wakeLock){a=!0;try{let e=await n.wakeLock.request(`screen`);if(!o){e.release();return}i=e,i.addEventListener(`release`,()=>{i=null,updateState(read())}),updateState(read())}catch{}finally{a=!1}}}return{...createSentinel({initialValue:read(),...e},e=>{updateState=e;let onVisibilityChange=()=>{t.document.visibilityState===`visible`&&o&&!i&&!a&&acquire()};return t.document.addEventListener(`visibilitychange`,onVisibilityChange),()=>{t.document.removeEventListener(`visibilitychange`,onVisibilityChange),i&&i.release(),i=null,o=!1}}),release(){o=!1,i&&(i.release(),i=null,updateState(read()))},request(){i||a||!r||(o=!0,acquire())}}}exports.SentinelError=SentinelError,exports.SentinelUnavailableError=SentinelUnavailableError,exports.createElementSize=createElementSize,exports.createFullscreen=createFullscreen,exports.createIntersection=createIntersection,exports.createMediaQuery=createMediaQuery,exports.createNetwork=createNetwork,exports.createSentinel=createSentinel,exports.createViewport=createViewport,exports.createWakeLock=createWakeLock;
2
2
  //# sourceMappingURL=sentinel.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"sentinel.cjs","names":[],"sources":["../src/errors.ts","../src/_platform.ts","../src/core.ts","../src/element-size.ts","../src/intersection.ts","../src/media-query.ts","../src/network.ts","../src/viewport.ts"],"sourcesContent":["export class SentinelError extends Error {\n constructor(message: string, options?: ErrorOptions) {\n super(message, options);\n this.name = new.target.name;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\nexport class SentinelUnavailableError extends SentinelError {}\n","import { SentinelUnavailableError } from './errors.ts';\n\ntype LegacyMediaQueryList = MediaQueryList & {\n addListener?: (listener: (event: MediaQueryListEvent) => void) => void;\n removeListener?: (listener: (event: MediaQueryListEvent) => void) => void;\n};\n\nexport type ObserverWindow = Window & {\n readonly IntersectionObserver?: {\n new (callback: IntersectionObserverCallback, options?: IntersectionObserverInit): IntersectionObserver;\n };\n readonly ResizeObserver?: {\n new (callback: ResizeObserverCallback): ResizeObserver;\n };\n};\n\nexport function resolveWindow(target?: Window): Window {\n if (target) return target;\n if (typeof window === 'undefined') {\n throw new SentinelUnavailableError('Window is not available in this environment.');\n }\n\n return window;\n}\n\nexport function resolveElementWindow(element: Element): ObserverWindow {\n const ownerWindow = element.ownerDocument.defaultView;\n if (!ownerWindow) {\n throw new SentinelUnavailableError('The element is not connected to a Window.');\n }\n\n return ownerWindow;\n}\n\nexport function listenMediaQuery(mediaQuery: MediaQueryList, listener: () => void): () => void {\n const onChange = () => listener();\n\n if (typeof mediaQuery.addEventListener === 'function') {\n mediaQuery.addEventListener('change', onChange);\n return () => mediaQuery.removeEventListener('change', onChange);\n }\n\n const legacyMediaQuery = mediaQuery as LegacyMediaQueryList;\n if (typeof legacyMediaQuery.addListener === 'function') {\n legacyMediaQuery.addListener(onChange);\n return () => legacyMediaQuery.removeListener?.(onChange);\n }\n\n return () => {};\n}\n","import type { Signal, Unsubscribe } from '@vielzeug/ripple';\nimport { signal as createSignal } from '@vielzeug/ripple';\nimport type { Sentinel, SentinelOptions } from './types.ts';\n\nexport interface CreateSentinelOptions<T> extends SentinelOptions {\n readonly initialValue: T;\n}\n\nexport function createSentinel<T>(\n options: CreateSentinelOptions<T>,\n setup: (update: (value: T) => void) => () => void,\n): Sentinel<T> {\n return new SentinelHandle(options, setup);\n}\n\nclass SentinelHandle<T> implements Sentinel<T> {\n readonly disposalSignal: AbortSignal;\n private readonly abortController = new AbortController();\n private readonly state: Signal<T>;\n private cleanup: (() => void) | undefined;\n private disposedValue = false;\n private externalSignal: AbortSignal | undefined;\n private externalAbortListener: (() => void) | undefined;\n\n constructor(options: CreateSentinelOptions<T>, setup: (update: (value: T) => void) => () => void) {\n const toSignal = options.runtime?.signal ?? createSignal;\n this.state = toSignal(options.initialValue);\n this.disposalSignal = this.abortController.signal;\n\n if (options.signal?.aborted) {\n this.disposedValue = true;\n this.abortController.abort();\n return;\n }\n\n try {\n this.cleanup = setup((value) => {\n if (!this.disposedValue) this.state.value = value;\n });\n } catch (error) {\n this.disposedValue = true;\n this.abortController.abort();\n throw error;\n }\n\n if (!options.signal) return;\n\n this.externalSignal = options.signal;\n this.externalAbortListener = () => this.dispose();\n this.externalSignal.addEventListener('abort', this.externalAbortListener, { once: true });\n }\n\n get disposed(): boolean {\n return this.disposedValue;\n }\n\n get value(): T {\n return this.state.value;\n }\n\n peek(): T {\n return this.state.peek();\n }\n\n subscribe(listener: () => void): Unsubscribe {\n return this.state.subscribe(listener);\n }\n\n dispose(): void {\n if (this.disposedValue) return;\n this.disposedValue = true;\n\n if (this.externalSignal && this.externalAbortListener) {\n this.externalSignal.removeEventListener('abort', this.externalAbortListener);\n }\n\n const cleanup = this.cleanup;\n this.cleanup = undefined;\n this.externalSignal = undefined;\n this.externalAbortListener = undefined;\n\n try {\n cleanup?.();\n } finally {\n this.abortController.abort();\n }\n }\n\n [Symbol.dispose](): void {\n this.dispose();\n }\n}\n","import { resolveElementWindow } from './_platform.ts';\nimport { createSentinel } from './core.ts';\nimport { SentinelUnavailableError } from './errors.ts';\nimport type { ElementSizeState, Sentinel, SentinelOptions } from './types.ts';\n\nexport function createElementSize(element: Element, options?: SentinelOptions): Sentinel<ElementSizeState | null> {\n const target = resolveElementWindow(element);\n const ResizeObserverConstructor = target.ResizeObserver;\n if (typeof ResizeObserverConstructor !== 'function') {\n throw new SentinelUnavailableError('ResizeObserver is not available in this environment.');\n }\n\n return createSentinel<ElementSizeState | null>(\n {\n initialValue: null,\n ...options,\n },\n (update) => {\n const observer = new ResizeObserverConstructor((entries) => {\n const entry = entries.at(-1);\n if (!entry) return;\n\n update({\n height: entry.contentRect.height,\n width: entry.contentRect.width,\n });\n });\n\n try {\n observer.observe(element, { box: 'content-box' });\n } catch (error) {\n observer.disconnect();\n throw error;\n }\n\n return () => observer.disconnect();\n },\n );\n}\n","import { resolveElementWindow } from './_platform.ts';\nimport { createSentinel } from './core.ts';\nimport { SentinelUnavailableError } from './errors.ts';\nimport type { IntersectionState, Sentinel, SentinelOptions } from './types.ts';\n\nexport interface CreateIntersectionOptions extends SentinelOptions {\n readonly root?: Element | Document | null;\n readonly rootMargin?: string;\n readonly scrollMargin?: string;\n readonly threshold?: number | number[];\n}\n\nexport function createIntersection(\n element: Element,\n options?: CreateIntersectionOptions,\n): Sentinel<IntersectionState | null> {\n const target = resolveElementWindow(element);\n const IntersectionObserverConstructor = target.IntersectionObserver;\n if (typeof IntersectionObserverConstructor !== 'function') {\n throw new SentinelUnavailableError('IntersectionObserver is not available in this environment.');\n }\n\n return createSentinel<IntersectionState | null>(\n {\n initialValue: null,\n ...options,\n },\n (update) => {\n const observer = new IntersectionObserverConstructor(\n (entries) => {\n const entry = entries.at(-1);\n if (!entry) return;\n\n update({\n intersectionRatio: entry.intersectionRatio,\n isIntersecting: entry.isIntersecting,\n });\n },\n {\n root: options?.root,\n rootMargin: options?.rootMargin,\n scrollMargin: options?.scrollMargin,\n threshold: options?.threshold,\n },\n );\n\n try {\n observer.observe(element);\n } catch (error) {\n observer.disconnect();\n throw error;\n }\n\n return () => observer.disconnect();\n },\n );\n}\n","import { listenMediaQuery, resolveWindow } from './_platform.ts';\nimport { createSentinel } from './core.ts';\nimport { SentinelUnavailableError } from './errors.ts';\nimport type { MediaQueryState, Sentinel, WindowSentinelOptions } from './types.ts';\n\nexport function createMediaQuery(query: string, options?: WindowSentinelOptions): Sentinel<MediaQueryState> {\n const target = resolveWindow(options?.target);\n if (typeof target.matchMedia !== 'function') {\n throw new SentinelUnavailableError(`matchMedia is not available for query \"${query}\".`);\n }\n\n const mediaQuery = target.matchMedia(query);\n const read = (): MediaQueryState => ({ matches: mediaQuery.matches });\n\n return createSentinel(\n {\n initialValue: read(),\n ...options,\n },\n (update) => listenMediaQuery(mediaQuery, () => update(read())),\n );\n}\n","import { resolveWindow } from './_platform.ts';\nimport { createSentinel } from './core.ts';\nimport type { NetworkConnectionSnapshot, NetworkState, Sentinel, WindowSentinelOptions } from './types.ts';\n\ninterface NetworkInformationLike extends EventTarget {\n readonly downlink?: number;\n readonly effectiveType?: NetworkConnectionSnapshot['effectiveType'];\n readonly rtt?: number;\n readonly saveData?: boolean;\n}\n\ntype NavigatorWithConnection = Navigator & {\n readonly connection?: NetworkInformationLike;\n};\n\nfunction normalizeConnection(connection?: NetworkInformationLike): NetworkConnectionSnapshot | null {\n if (!connection) return null;\n\n return {\n downlink: connection.downlink,\n effectiveType: connection.effectiveType,\n rtt: connection.rtt,\n saveData: connection.saveData,\n };\n}\n\nexport function createNetwork(options?: WindowSentinelOptions): Sentinel<NetworkState> {\n const target = resolveWindow(options?.target);\n const navigator = target.navigator as NavigatorWithConnection;\n const read = (): NetworkState => ({\n connection: normalizeConnection(navigator.connection),\n online: navigator.onLine,\n });\n\n return createSentinel(\n {\n initialValue: read(),\n ...options,\n },\n (update) => {\n const onChange = () => update(read());\n const connection = navigator.connection;\n\n target.addEventListener('online', onChange);\n target.addEventListener('offline', onChange);\n connection?.addEventListener('change', onChange);\n\n return () => {\n target.removeEventListener('online', onChange);\n target.removeEventListener('offline', onChange);\n connection?.removeEventListener('change', onChange);\n };\n },\n );\n}\n","import { listenMediaQuery, resolveWindow } from './_platform.ts';\nimport { createSentinel } from './core.ts';\nimport type { Sentinel, ViewportState, WindowSentinelOptions } from './types.ts';\n\nexport function createViewport(options?: WindowSentinelOptions): Sentinel<ViewportState> {\n const target = resolveWindow(options?.target);\n const read = (): ViewportState => ({\n dpr: target.devicePixelRatio,\n height: target.innerHeight,\n width: target.innerWidth,\n });\n\n return createSentinel(\n {\n initialValue: read(),\n ...options,\n },\n (update) => {\n const onResize = () => update(read());\n let stopDprObservation: (() => void) | undefined;\n\n const observeDpr = () => {\n stopDprObservation?.();\n stopDprObservation = undefined;\n\n if (typeof target.matchMedia !== 'function') return;\n\n const mediaQuery = target.matchMedia(`(resolution: ${target.devicePixelRatio}dppx)`);\n stopDprObservation = listenMediaQuery(mediaQuery, () => {\n update(read());\n observeDpr();\n });\n };\n\n target.addEventListener('resize', onResize);\n observeDpr();\n\n return () => {\n target.removeEventListener('resize', onResize);\n stopDprObservation?.();\n };\n },\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"sentinel.cjs","names":[],"sources":["../src/errors.ts","../src/core.ts","../src/_platform.ts","../src/element-size.ts","../src/fullscreen.ts","../src/intersection.ts","../src/media-query.ts","../src/network.ts","../src/viewport.ts","../src/wake-lock.ts"],"sourcesContent":["export class SentinelError extends Error {\n constructor(message: string, options?: ErrorOptions) {\n super(message, options);\n this.name = new.target.name;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\nexport class SentinelUnavailableError extends SentinelError {}\n","import { type Tapper, tapper } from '@vielzeug/arsenal';\n\nimport { SentinelError } from './errors.ts';\nimport type { Sentinel, SentinelEvent, SentinelOptions, Unsubscribe } from './types.ts';\n\nexport interface CreateSentinelOptions<T> extends SentinelOptions {\n readonly initialValue: T;\n}\n\nexport function createSentinel<T>(\n options: CreateSentinelOptions<T>,\n setup: (update: (value: T) => void) => () => void,\n): Sentinel<T> {\n return new SentinelHandle(options, setup);\n}\n\nclass SentinelHandle<T> implements Sentinel<T> {\n readonly disposalSignal: AbortSignal;\n private readonly abortController = new AbortController();\n private currentValue: T;\n private readonly listeners = new Set<() => void>();\n private cleanup: (() => void) | undefined;\n private disposedValue = false;\n private externalSignal: AbortSignal | undefined;\n private externalAbortListener: (() => void) | undefined;\n private readonly tapper: Tapper<SentinelEvent> = tapper<SentinelEvent>();\n\n constructor(options: CreateSentinelOptions<T>, setup: (update: (value: T) => void) => () => void) {\n if (options.signal?.aborted) {\n throw new SentinelError('Sentinel signal is already aborted');\n }\n\n this.currentValue = options.initialValue;\n this.disposalSignal = this.abortController.signal;\n\n try {\n this.cleanup = setup((value) => {\n if (!this.disposedValue) {\n this.currentValue = value;\n this.notify();\n }\n });\n } catch (error) {\n this.disposedValue = true;\n this.abortController.abort();\n throw error;\n }\n\n if (!options.signal) return;\n\n this.externalSignal = options.signal;\n this.externalAbortListener = () => this.dispose();\n this.externalSignal.addEventListener('abort', this.externalAbortListener, { once: true });\n }\n\n get disposed(): boolean {\n return this.disposedValue;\n }\n\n readonly getSnapshot = (): T => this.currentValue;\n\n readonly subscribe = (listener: () => void): Unsubscribe => {\n if (this.disposedValue) return () => {};\n this.listeners.add(listener);\n return () => this.listeners.delete(listener);\n };\n\n readonly dispose = (): void => {\n if (this.disposedValue) return;\n this.disposedValue = true;\n\n if (this.externalSignal && this.externalAbortListener) {\n this.externalSignal.removeEventListener('abort', this.externalAbortListener);\n }\n\n const cleanup = this.cleanup;\n this.cleanup = undefined;\n this.externalSignal = undefined;\n this.externalAbortListener = undefined;\n this.listeners.clear();\n\n try {\n cleanup?.();\n } finally {\n this.tapper.emit({ type: 'dispose' });\n this.tapper.clear();\n this.abortController.abort();\n }\n };\n\n readonly tap = (handler: (event: SentinelEvent) => void, options?: { readonly signal?: AbortSignal }): Unsubscribe =>\n this.tapper.tap(handler, options);\n\n private notify(): void {\n for (const listener of [...this.listeners]) {\n try {\n listener();\n } catch (error) {\n this.tapper.emit({ error, type: 'error' });\n queueMicrotask(() => {\n throw error;\n });\n }\n }\n }\n\n [Symbol.dispose](): void {\n this.dispose();\n }\n}\n","import { SentinelUnavailableError } from './errors.ts';\n\ntype LegacyMediaQueryList = MediaQueryList & {\n addListener?: (listener: (event: MediaQueryListEvent) => void) => void;\n removeListener?: (listener: (event: MediaQueryListEvent) => void) => void;\n};\n\nexport type ObserverWindow = Window & {\n readonly IntersectionObserver?: {\n new (callback: IntersectionObserverCallback, options?: IntersectionObserverInit): IntersectionObserver;\n };\n readonly ResizeObserver?: {\n new (callback: ResizeObserverCallback): ResizeObserver;\n };\n};\n\nexport function resolveWindow(target?: Window): Window {\n if (target) return target;\n if (typeof window === 'undefined') {\n throw new SentinelUnavailableError('Window is not available in this environment.');\n }\n\n return window;\n}\n\nexport function resolveElementWindow(element: Element): ObserverWindow {\n const ownerWindow = element.ownerDocument.defaultView;\n if (!ownerWindow) {\n throw new SentinelUnavailableError('The element is not connected to a Window.');\n }\n\n return ownerWindow;\n}\n\nexport function listenMediaQuery(mediaQuery: MediaQueryList, listener: () => void): () => void {\n const onChange = () => listener();\n\n if (typeof mediaQuery.addEventListener === 'function') {\n mediaQuery.addEventListener('change', onChange);\n return () => mediaQuery.removeEventListener('change', onChange);\n }\n\n const legacyMediaQuery = mediaQuery as LegacyMediaQueryList;\n if (typeof legacyMediaQuery.addListener === 'function') {\n legacyMediaQuery.addListener(onChange);\n return () => legacyMediaQuery.removeListener?.(onChange);\n }\n\n return () => {};\n}\n","import { resolveElementWindow } from './_platform.ts';\nimport { createSentinel } from './core.ts';\nimport { SentinelUnavailableError } from './errors.ts';\nimport type { ElementSizeState, Sentinel, SentinelOptions } from './types.ts';\n\nexport function createElementSize(element: Element, options?: SentinelOptions): Sentinel<ElementSizeState | null> {\n const target = resolveElementWindow(element);\n const ResizeObserverConstructor = target.ResizeObserver;\n if (typeof ResizeObserverConstructor !== 'function') {\n throw new SentinelUnavailableError('ResizeObserver is not available in this environment.');\n }\n\n return createSentinel<ElementSizeState | null>(\n {\n initialValue: null,\n ...options,\n },\n (update) => {\n const observer = new ResizeObserverConstructor((entries) => {\n const entry = entries.at(-1);\n if (!entry) return;\n\n update({\n height: entry.contentRect.height,\n width: entry.contentRect.width,\n });\n });\n\n try {\n observer.observe(element, { box: 'content-box' });\n } catch (error) {\n observer.disconnect();\n throw error;\n }\n\n return () => observer.disconnect();\n },\n );\n}\n","import { resolveWindow } from './_platform.ts';\nimport { createSentinel } from './core.ts';\nimport type { FullscreenSentinel, FullscreenState, SentinelOptions } from './types.ts';\n\ntype FullscreenWindow = Window & { document: Document };\n\n/**\n * A sentinel-backed fullscreen view. `request()` enters fullscreen for an element (default:\n * the document element), `exit()` leaves it, and `toggle()` flips it — all degrade silently\n * when the Fullscreen API is missing or the browser refuses (user-activation requirements).\n * Disposing the sentinel exits fullscreen and stops tracking `fullscreenchange`.\n */\nexport function createFullscreen(\n options?: SentinelOptions & { readonly target?: FullscreenWindow },\n): FullscreenSentinel {\n const target = resolveWindow(options?.target);\n const doc = target.document;\n const supported = typeof doc.documentElement?.requestFullscreen === 'function';\n\n const read = (): FullscreenState => ({ active: doc.fullscreenElement !== null, supported });\n let updateState: (value: FullscreenState) => void = () => {};\n\n const request = (element?: Element): void => {\n if (doc.fullscreenElement || !supported) return;\n void (element ?? doc.documentElement).requestFullscreen().catch(() => {\n // Requires user activation in some browsers, or the element may refuse; degrade silently.\n });\n };\n\n const exit = (): void => {\n if (!doc.fullscreenElement) return;\n void doc.exitFullscreen().catch(() => {\n // Degrade silently.\n });\n };\n\n const sentinel = createSentinel<FullscreenState>({ initialValue: read(), ...options }, (update) => {\n updateState = update;\n\n const onFullscreenChange = (): void => updateState(read());\n\n doc.addEventListener('fullscreenchange', onFullscreenChange);\n\n return () => {\n doc.removeEventListener('fullscreenchange', onFullscreenChange);\n if (doc.fullscreenElement) exit();\n };\n });\n\n return {\n ...sentinel,\n exit,\n request,\n toggle(element?: Element): void {\n if (doc.fullscreenElement) exit();\n else request(element);\n },\n };\n}\n","import { resolveElementWindow } from './_platform.ts';\nimport { createSentinel } from './core.ts';\nimport { SentinelUnavailableError } from './errors.ts';\nimport type { IntersectionState, Sentinel, SentinelOptions } from './types.ts';\n\nexport interface CreateIntersectionOptions extends SentinelOptions {\n readonly root?: Element | Document | null;\n readonly rootMargin?: string;\n readonly scrollMargin?: string;\n readonly threshold?: number | number[];\n}\n\nexport function createIntersection(\n element: Element,\n options?: CreateIntersectionOptions,\n): Sentinel<IntersectionState | null> {\n const target = resolveElementWindow(element);\n const IntersectionObserverConstructor = target.IntersectionObserver;\n if (typeof IntersectionObserverConstructor !== 'function') {\n throw new SentinelUnavailableError('IntersectionObserver is not available in this environment.');\n }\n\n return createSentinel<IntersectionState | null>(\n {\n initialValue: null,\n ...options,\n },\n (update) => {\n const observer = new IntersectionObserverConstructor(\n (entries) => {\n const entry = entries.at(-1);\n if (!entry) return;\n\n update({\n intersectionRatio: entry.intersectionRatio,\n isIntersecting: entry.isIntersecting,\n });\n },\n {\n root: options?.root,\n rootMargin: options?.rootMargin,\n scrollMargin: options?.scrollMargin,\n threshold: options?.threshold,\n },\n );\n\n try {\n observer.observe(element);\n } catch (error) {\n observer.disconnect();\n throw error;\n }\n\n return () => observer.disconnect();\n },\n );\n}\n","import { listenMediaQuery, resolveWindow } from './_platform.ts';\nimport { createSentinel } from './core.ts';\nimport { SentinelUnavailableError } from './errors.ts';\nimport type { MediaQueryState, Sentinel, WindowSentinelOptions } from './types.ts';\n\nexport function createMediaQuery(query: string, options?: WindowSentinelOptions): Sentinel<MediaQueryState> {\n const target = resolveWindow(options?.target);\n if (typeof target.matchMedia !== 'function') {\n throw new SentinelUnavailableError(`matchMedia is not available for query \"${query}\".`);\n }\n\n const mediaQuery = target.matchMedia(query);\n const read = (): MediaQueryState => ({ matches: mediaQuery.matches });\n\n return createSentinel(\n {\n initialValue: read(),\n ...options,\n },\n (update) => listenMediaQuery(mediaQuery, () => update(read())),\n );\n}\n","import { resolveWindow } from './_platform.ts';\nimport { createSentinel } from './core.ts';\nimport type { NetworkConnectionSnapshot, NetworkState, Sentinel, WindowSentinelOptions } from './types.ts';\n\ninterface NetworkInformationLike extends EventTarget {\n readonly downlink?: number;\n readonly effectiveType?: NetworkConnectionSnapshot['effectiveType'];\n readonly rtt?: number;\n readonly saveData?: boolean;\n}\n\ntype NavigatorWithConnection = Navigator & {\n readonly connection?: NetworkInformationLike;\n};\n\nfunction normalizeConnection(connection?: NetworkInformationLike): NetworkConnectionSnapshot | null {\n if (!connection) return null;\n\n return {\n downlink: connection.downlink,\n effectiveType: connection.effectiveType,\n rtt: connection.rtt,\n saveData: connection.saveData,\n };\n}\n\nexport function createNetwork(options?: WindowSentinelOptions): Sentinel<NetworkState> {\n const target = resolveWindow(options?.target);\n const navigator = target.navigator as NavigatorWithConnection;\n const read = (): NetworkState => ({\n connection: normalizeConnection(navigator.connection),\n online: navigator.onLine,\n });\n\n return createSentinel(\n {\n initialValue: read(),\n ...options,\n },\n (update) => {\n const onChange = () => update(read());\n const connection = navigator.connection;\n\n target.addEventListener('online', onChange);\n target.addEventListener('offline', onChange);\n connection?.addEventListener('change', onChange);\n\n return () => {\n target.removeEventListener('online', onChange);\n target.removeEventListener('offline', onChange);\n connection?.removeEventListener('change', onChange);\n };\n },\n );\n}\n","import { listenMediaQuery, resolveWindow } from './_platform.ts';\nimport { createSentinel } from './core.ts';\nimport type { Sentinel, ViewportState, WindowSentinelOptions } from './types.ts';\n\nexport function createViewport(options?: WindowSentinelOptions): Sentinel<ViewportState> {\n const target = resolveWindow(options?.target);\n const read = (): ViewportState => ({\n dpr: target.devicePixelRatio,\n height: target.innerHeight,\n width: target.innerWidth,\n });\n\n return createSentinel(\n {\n initialValue: read(),\n ...options,\n },\n (update) => {\n const onResize = () => update(read());\n let stopDprObservation: (() => void) | undefined;\n\n const observeDpr = () => {\n stopDprObservation?.();\n stopDprObservation = undefined;\n\n if (typeof target.matchMedia !== 'function') return;\n\n const mediaQuery = target.matchMedia(`(resolution: ${target.devicePixelRatio}dppx)`);\n stopDprObservation = listenMediaQuery(mediaQuery, () => {\n update(read());\n observeDpr();\n });\n };\n\n target.addEventListener('resize', onResize);\n observeDpr();\n\n return () => {\n target.removeEventListener('resize', onResize);\n stopDprObservation?.();\n };\n },\n );\n}\n","import { resolveWindow } from './_platform.ts';\nimport { createSentinel } from './core.ts';\nimport type { SentinelOptions, WakeLockSentinel, WakeLockState } from './types.ts';\n\ntype WakeLockWindow = Window & { document: Document };\n\nexport function createWakeLock(options?: SentinelOptions & { readonly target?: WakeLockWindow }): WakeLockSentinel {\n const target = resolveWindow(options?.target);\n const navigator = target.navigator;\n const supported = typeof navigator.wakeLock?.request === 'function';\n\n let handle: globalThis.WakeLockSentinel | null = null;\n let pending = false;\n let wantsLock = false;\n\n const read = (): WakeLockState => ({ active: handle !== null, supported });\n let updateState: (value: WakeLockState) => void = () => {};\n\n async function acquire(): Promise<void> {\n if (!supported || !navigator.wakeLock) return;\n pending = true;\n try {\n const requested = await navigator.wakeLock.request('screen');\n // release()/dispose() during the pending request must not leave the lock held.\n if (!wantsLock) {\n void requested.release();\n\n return;\n }\n handle = requested;\n handle.addEventListener('release', () => {\n handle = null;\n updateState(read());\n });\n updateState(read());\n } catch {\n // Not supported, denied, or not in a secure context — degrade silently.\n } finally {\n pending = false;\n }\n }\n\n const sentinel = createSentinel<WakeLockState>({ initialValue: read(), ...options }, (update) => {\n updateState = update;\n\n const onVisibilityChange = (): void => {\n if (target.document.visibilityState === 'visible' && wantsLock && !handle && !pending) {\n void acquire();\n }\n };\n\n target.document.addEventListener('visibilitychange', onVisibilityChange);\n\n return () => {\n target.document.removeEventListener('visibilitychange', onVisibilityChange);\n if (handle) void handle.release();\n handle = null;\n wantsLock = false;\n };\n });\n\n return {\n ...sentinel,\n release(): void {\n wantsLock = false;\n if (handle) {\n void handle.release();\n handle = null;\n updateState(read());\n }\n },\n request(): void {\n if (handle || pending || !supported) return;\n wantsLock = true;\n void acquire();\n },\n };\n}\n"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- var Sentinel=(function(e,t){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});var n=class extends Error{constructor(e,t){super(e,t),this.name=new.target.name,Object.setPrototypeOf(this,new.target.prototype)}},r=class extends n{};function i(e){if(e)return e;if(typeof window>`u`)throw new r(`Window is not available in this environment.`);return window}function a(e){let t=e.ownerDocument.defaultView;if(!t)throw new r(`The element is not connected to a Window.`);return t}function o(e,t){let n=()=>t();if(typeof e.addEventListener==`function`)return e.addEventListener(`change`,n),()=>e.removeEventListener(`change`,n);let r=e;return typeof r.addListener==`function`?(r.addListener(n),()=>r.removeListener?.(n)):()=>{}}function s(e,t){return new c(e,t)}var c=class{disposalSignal;abortController=new AbortController;state;cleanup;disposedValue=!1;externalSignal;externalAbortListener;constructor(e,n){let r=e.runtime?.signal??t.signal;if(this.state=r(e.initialValue),this.disposalSignal=this.abortController.signal,e.signal?.aborted){this.disposedValue=!0,this.abortController.abort();return}try{this.cleanup=n(e=>{this.disposedValue||(this.state.value=e)})}catch(e){throw this.disposedValue=!0,this.abortController.abort(),e}e.signal&&(this.externalSignal=e.signal,this.externalAbortListener=()=>this.dispose(),this.externalSignal.addEventListener(`abort`,this.externalAbortListener,{once:!0}))}get disposed(){return this.disposedValue}get value(){return this.state.value}peek(){return this.state.peek()}subscribe(e){return this.state.subscribe(e)}dispose(){if(this.disposedValue)return;this.disposedValue=!0,this.externalSignal&&this.externalAbortListener&&this.externalSignal.removeEventListener(`abort`,this.externalAbortListener);let e=this.cleanup;this.cleanup=void 0,this.externalSignal=void 0,this.externalAbortListener=void 0;try{e?.()}finally{this.abortController.abort()}}[Symbol.dispose](){this.dispose()}};function l(e,t){let n=a(e).ResizeObserver;if(typeof n!=`function`)throw new r(`ResizeObserver is not available in this environment.`);return s({initialValue:null,...t},t=>{let r=new n(e=>{let n=e.at(-1);n&&t({height:n.contentRect.height,width:n.contentRect.width})});try{r.observe(e,{box:`content-box`})}catch(e){throw r.disconnect(),e}return()=>r.disconnect()})}function u(e,t){let n=a(e).IntersectionObserver;if(typeof n!=`function`)throw new r(`IntersectionObserver is not available in this environment.`);return s({initialValue:null,...t},r=>{let i=new n(e=>{let t=e.at(-1);t&&r({intersectionRatio:t.intersectionRatio,isIntersecting:t.isIntersecting})},{root:t?.root,rootMargin:t?.rootMargin,scrollMargin:t?.scrollMargin,threshold:t?.threshold});try{i.observe(e)}catch(e){throw i.disconnect(),e}return()=>i.disconnect()})}function d(e,t){let n=i(t?.target);if(typeof n.matchMedia!=`function`)throw new r(`matchMedia is not available for query "${e}".`);let a=n.matchMedia(e),c=()=>({matches:a.matches});return s({initialValue:c(),...t},e=>o(a,()=>e(c())))}function f(e){return e?{downlink:e.downlink,effectiveType:e.effectiveType,rtt:e.rtt,saveData:e.saveData}:null}function p(e){let t=i(e?.target),n=t.navigator,r=()=>({connection:f(n.connection),online:n.onLine});return s({initialValue:r(),...e},e=>{let i=()=>e(r()),a=n.connection;return t.addEventListener(`online`,i),t.addEventListener(`offline`,i),a?.addEventListener(`change`,i),()=>{t.removeEventListener(`online`,i),t.removeEventListener(`offline`,i),a?.removeEventListener(`change`,i)}})}function m(e){let t=i(e?.target),n=()=>({dpr:t.devicePixelRatio,height:t.innerHeight,width:t.innerWidth});return s({initialValue:n(),...e},e=>{let r=()=>e(n()),i,a=()=>{i?.(),i=void 0,typeof t.matchMedia==`function`&&(i=o(t.matchMedia(`(resolution: ${t.devicePixelRatio}dppx)`),()=>{e(n()),a()}))};return t.addEventListener(`resize`,r),a(),()=>{t.removeEventListener(`resize`,r),i?.()}})}return e.SentinelError=n,e.SentinelUnavailableError=r,e.createElementSize=l,e.createIntersection=u,e.createMediaQuery=d,e.createNetwork=p,e.createViewport=m,e})({},Ripple);
1
+ var Sentinel=(function(e,t){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});var SentinelError=class extends Error{constructor(e,t){super(e,t),this.name=new.target.name,Object.setPrototypeOf(this,new.target.prototype)}},SentinelUnavailableError=class extends SentinelError{};function createSentinel(e,t){return new SentinelHandle(e,t)}var SentinelHandle=class{disposalSignal;abortController=new AbortController;currentValue;listeners=new Set;cleanup;disposedValue=!1;externalSignal;externalAbortListener;tapper=(0,t.tapper)();constructor(e,t){if(e.signal?.aborted)throw new SentinelError(`Sentinel signal is already aborted`);this.currentValue=e.initialValue,this.disposalSignal=this.abortController.signal;try{this.cleanup=t(e=>{this.disposedValue||(this.currentValue=e,this.notify())})}catch(e){throw this.disposedValue=!0,this.abortController.abort(),e}e.signal&&(this.externalSignal=e.signal,this.externalAbortListener=()=>this.dispose(),this.externalSignal.addEventListener(`abort`,this.externalAbortListener,{once:!0}))}get disposed(){return this.disposedValue}getSnapshot=()=>this.currentValue;subscribe=e=>this.disposedValue?()=>{}:(this.listeners.add(e),()=>this.listeners.delete(e));dispose=()=>{if(this.disposedValue)return;this.disposedValue=!0,this.externalSignal&&this.externalAbortListener&&this.externalSignal.removeEventListener(`abort`,this.externalAbortListener);let e=this.cleanup;this.cleanup=void 0,this.externalSignal=void 0,this.externalAbortListener=void 0,this.listeners.clear();try{e?.()}finally{this.tapper.emit({type:`dispose`}),this.tapper.clear(),this.abortController.abort()}};tap=(e,t)=>this.tapper.tap(e,t);notify(){for(let e of[...this.listeners])try{e()}catch(e){this.tapper.emit({error:e,type:`error`}),queueMicrotask(()=>{throw e})}}[Symbol.dispose](){this.dispose()}};function resolveWindow(e){if(e)return e;if(typeof window>`u`)throw new SentinelUnavailableError(`Window is not available in this environment.`);return window}function resolveElementWindow(e){let t=e.ownerDocument.defaultView;if(!t)throw new SentinelUnavailableError(`The element is not connected to a Window.`);return t}function listenMediaQuery(e,t){let onChange=()=>t();if(typeof e.addEventListener==`function`)return e.addEventListener(`change`,onChange),()=>e.removeEventListener(`change`,onChange);let n=e;return typeof n.addListener==`function`?(n.addListener(onChange),()=>n.removeListener?.(onChange)):()=>{}}function createElementSize(e,t){let n=resolveElementWindow(e).ResizeObserver;if(typeof n!=`function`)throw new SentinelUnavailableError(`ResizeObserver is not available in this environment.`);return createSentinel({initialValue:null,...t},t=>{let r=new n(e=>{let n=e.at(-1);n&&t({height:n.contentRect.height,width:n.contentRect.width})});try{r.observe(e,{box:`content-box`})}catch(e){throw r.disconnect(),e}return()=>r.disconnect()})}function createFullscreen(e){let t=resolveWindow(e?.target).document,n=typeof t.documentElement?.requestFullscreen==`function`,read=()=>({active:t.fullscreenElement!==null,supported:n}),updateState=()=>{},request=e=>{!t.fullscreenElement&&n&&(e??t.documentElement).requestFullscreen().catch(()=>{})},exit=()=>{t.fullscreenElement&&t.exitFullscreen().catch(()=>{})};return{...createSentinel({initialValue:read(),...e},e=>{updateState=e;let onFullscreenChange=()=>updateState(read());return t.addEventListener(`fullscreenchange`,onFullscreenChange),()=>{t.removeEventListener(`fullscreenchange`,onFullscreenChange),t.fullscreenElement&&exit()}}),exit,request,toggle(e){t.fullscreenElement?exit():request(e)}}}function createIntersection(e,t){let n=resolveElementWindow(e).IntersectionObserver;if(typeof n!=`function`)throw new SentinelUnavailableError(`IntersectionObserver is not available in this environment.`);return createSentinel({initialValue:null,...t},r=>{let i=new n(e=>{let t=e.at(-1);t&&r({intersectionRatio:t.intersectionRatio,isIntersecting:t.isIntersecting})},{root:t?.root,rootMargin:t?.rootMargin,scrollMargin:t?.scrollMargin,threshold:t?.threshold});try{i.observe(e)}catch(e){throw i.disconnect(),e}return()=>i.disconnect()})}function createMediaQuery(e,t){let n=resolveWindow(t?.target);if(typeof n.matchMedia!=`function`)throw new SentinelUnavailableError(`matchMedia is not available for query "${e}".`);let r=n.matchMedia(e),read=()=>({matches:r.matches});return createSentinel({initialValue:read(),...t},e=>listenMediaQuery(r,()=>e(read())))}function normalizeConnection(e){return e?{downlink:e.downlink,effectiveType:e.effectiveType,rtt:e.rtt,saveData:e.saveData}:null}function createNetwork(e){let t=resolveWindow(e?.target),n=t.navigator,read=()=>({connection:normalizeConnection(n.connection),online:n.onLine});return createSentinel({initialValue:read(),...e},e=>{let onChange=()=>e(read()),r=n.connection;return t.addEventListener(`online`,onChange),t.addEventListener(`offline`,onChange),r?.addEventListener(`change`,onChange),()=>{t.removeEventListener(`online`,onChange),t.removeEventListener(`offline`,onChange),r?.removeEventListener(`change`,onChange)}})}function createViewport(e){let t=resolveWindow(e?.target),read=()=>({dpr:t.devicePixelRatio,height:t.innerHeight,width:t.innerWidth});return createSentinel({initialValue:read(),...e},e=>{let onResize=()=>e(read()),n,observeDpr=()=>{n?.(),n=void 0,typeof t.matchMedia==`function`&&(n=listenMediaQuery(t.matchMedia(`(resolution: ${t.devicePixelRatio}dppx)`),()=>{e(read()),observeDpr()}))};return t.addEventListener(`resize`,onResize),observeDpr(),()=>{t.removeEventListener(`resize`,onResize),n?.()}})}function createWakeLock(e){let t=resolveWindow(e?.target),n=t.navigator,r=typeof n.wakeLock?.request==`function`,i=null,a=!1,o=!1,read=()=>({active:i!==null,supported:r}),updateState=()=>{};async function acquire(){if(r&&n.wakeLock){a=!0;try{let e=await n.wakeLock.request(`screen`);if(!o){e.release();return}i=e,i.addEventListener(`release`,()=>{i=null,updateState(read())}),updateState(read())}catch{}finally{a=!1}}}return{...createSentinel({initialValue:read(),...e},e=>{updateState=e;let onVisibilityChange=()=>{t.document.visibilityState===`visible`&&o&&!i&&!a&&acquire()};return t.document.addEventListener(`visibilitychange`,onVisibilityChange),()=>{t.document.removeEventListener(`visibilitychange`,onVisibilityChange),i&&i.release(),i=null,o=!1}}),release(){o=!1,i&&(i.release(),i=null,updateState(read()))},request(){i||a||!r||(o=!0,acquire())}}}return e.SentinelError=SentinelError,e.SentinelUnavailableError=SentinelUnavailableError,e.createElementSize=createElementSize,e.createFullscreen=createFullscreen,e.createIntersection=createIntersection,e.createMediaQuery=createMediaQuery,e.createNetwork=createNetwork,e.createSentinel=createSentinel,e.createViewport=createViewport,e.createWakeLock=createWakeLock,e})({},Arsenal);
2
2
  //# sourceMappingURL=sentinel.iife.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"sentinel.iife.js","names":[],"sources":["../src/errors.ts","../src/_platform.ts","../src/core.ts","../src/element-size.ts","../src/intersection.ts","../src/media-query.ts","../src/network.ts","../src/viewport.ts"],"sourcesContent":["export class SentinelError extends Error {\n constructor(message: string, options?: ErrorOptions) {\n super(message, options);\n this.name = new.target.name;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\nexport class SentinelUnavailableError extends SentinelError {}\n","import { SentinelUnavailableError } from './errors.ts';\n\ntype LegacyMediaQueryList = MediaQueryList & {\n addListener?: (listener: (event: MediaQueryListEvent) => void) => void;\n removeListener?: (listener: (event: MediaQueryListEvent) => void) => void;\n};\n\nexport type ObserverWindow = Window & {\n readonly IntersectionObserver?: {\n new (callback: IntersectionObserverCallback, options?: IntersectionObserverInit): IntersectionObserver;\n };\n readonly ResizeObserver?: {\n new (callback: ResizeObserverCallback): ResizeObserver;\n };\n};\n\nexport function resolveWindow(target?: Window): Window {\n if (target) return target;\n if (typeof window === 'undefined') {\n throw new SentinelUnavailableError('Window is not available in this environment.');\n }\n\n return window;\n}\n\nexport function resolveElementWindow(element: Element): ObserverWindow {\n const ownerWindow = element.ownerDocument.defaultView;\n if (!ownerWindow) {\n throw new SentinelUnavailableError('The element is not connected to a Window.');\n }\n\n return ownerWindow;\n}\n\nexport function listenMediaQuery(mediaQuery: MediaQueryList, listener: () => void): () => void {\n const onChange = () => listener();\n\n if (typeof mediaQuery.addEventListener === 'function') {\n mediaQuery.addEventListener('change', onChange);\n return () => mediaQuery.removeEventListener('change', onChange);\n }\n\n const legacyMediaQuery = mediaQuery as LegacyMediaQueryList;\n if (typeof legacyMediaQuery.addListener === 'function') {\n legacyMediaQuery.addListener(onChange);\n return () => legacyMediaQuery.removeListener?.(onChange);\n }\n\n return () => {};\n}\n","import type { Signal, Unsubscribe } from '@vielzeug/ripple';\nimport { signal as createSignal } from '@vielzeug/ripple';\nimport type { Sentinel, SentinelOptions } from './types.ts';\n\nexport interface CreateSentinelOptions<T> extends SentinelOptions {\n readonly initialValue: T;\n}\n\nexport function createSentinel<T>(\n options: CreateSentinelOptions<T>,\n setup: (update: (value: T) => void) => () => void,\n): Sentinel<T> {\n return new SentinelHandle(options, setup);\n}\n\nclass SentinelHandle<T> implements Sentinel<T> {\n readonly disposalSignal: AbortSignal;\n private readonly abortController = new AbortController();\n private readonly state: Signal<T>;\n private cleanup: (() => void) | undefined;\n private disposedValue = false;\n private externalSignal: AbortSignal | undefined;\n private externalAbortListener: (() => void) | undefined;\n\n constructor(options: CreateSentinelOptions<T>, setup: (update: (value: T) => void) => () => void) {\n const toSignal = options.runtime?.signal ?? createSignal;\n this.state = toSignal(options.initialValue);\n this.disposalSignal = this.abortController.signal;\n\n if (options.signal?.aborted) {\n this.disposedValue = true;\n this.abortController.abort();\n return;\n }\n\n try {\n this.cleanup = setup((value) => {\n if (!this.disposedValue) this.state.value = value;\n });\n } catch (error) {\n this.disposedValue = true;\n this.abortController.abort();\n throw error;\n }\n\n if (!options.signal) return;\n\n this.externalSignal = options.signal;\n this.externalAbortListener = () => this.dispose();\n this.externalSignal.addEventListener('abort', this.externalAbortListener, { once: true });\n }\n\n get disposed(): boolean {\n return this.disposedValue;\n }\n\n get value(): T {\n return this.state.value;\n }\n\n peek(): T {\n return this.state.peek();\n }\n\n subscribe(listener: () => void): Unsubscribe {\n return this.state.subscribe(listener);\n }\n\n dispose(): void {\n if (this.disposedValue) return;\n this.disposedValue = true;\n\n if (this.externalSignal && this.externalAbortListener) {\n this.externalSignal.removeEventListener('abort', this.externalAbortListener);\n }\n\n const cleanup = this.cleanup;\n this.cleanup = undefined;\n this.externalSignal = undefined;\n this.externalAbortListener = undefined;\n\n try {\n cleanup?.();\n } finally {\n this.abortController.abort();\n }\n }\n\n [Symbol.dispose](): void {\n this.dispose();\n }\n}\n","import { resolveElementWindow } from './_platform.ts';\nimport { createSentinel } from './core.ts';\nimport { SentinelUnavailableError } from './errors.ts';\nimport type { ElementSizeState, Sentinel, SentinelOptions } from './types.ts';\n\nexport function createElementSize(element: Element, options?: SentinelOptions): Sentinel<ElementSizeState | null> {\n const target = resolveElementWindow(element);\n const ResizeObserverConstructor = target.ResizeObserver;\n if (typeof ResizeObserverConstructor !== 'function') {\n throw new SentinelUnavailableError('ResizeObserver is not available in this environment.');\n }\n\n return createSentinel<ElementSizeState | null>(\n {\n initialValue: null,\n ...options,\n },\n (update) => {\n const observer = new ResizeObserverConstructor((entries) => {\n const entry = entries.at(-1);\n if (!entry) return;\n\n update({\n height: entry.contentRect.height,\n width: entry.contentRect.width,\n });\n });\n\n try {\n observer.observe(element, { box: 'content-box' });\n } catch (error) {\n observer.disconnect();\n throw error;\n }\n\n return () => observer.disconnect();\n },\n );\n}\n","import { resolveElementWindow } from './_platform.ts';\nimport { createSentinel } from './core.ts';\nimport { SentinelUnavailableError } from './errors.ts';\nimport type { IntersectionState, Sentinel, SentinelOptions } from './types.ts';\n\nexport interface CreateIntersectionOptions extends SentinelOptions {\n readonly root?: Element | Document | null;\n readonly rootMargin?: string;\n readonly scrollMargin?: string;\n readonly threshold?: number | number[];\n}\n\nexport function createIntersection(\n element: Element,\n options?: CreateIntersectionOptions,\n): Sentinel<IntersectionState | null> {\n const target = resolveElementWindow(element);\n const IntersectionObserverConstructor = target.IntersectionObserver;\n if (typeof IntersectionObserverConstructor !== 'function') {\n throw new SentinelUnavailableError('IntersectionObserver is not available in this environment.');\n }\n\n return createSentinel<IntersectionState | null>(\n {\n initialValue: null,\n ...options,\n },\n (update) => {\n const observer = new IntersectionObserverConstructor(\n (entries) => {\n const entry = entries.at(-1);\n if (!entry) return;\n\n update({\n intersectionRatio: entry.intersectionRatio,\n isIntersecting: entry.isIntersecting,\n });\n },\n {\n root: options?.root,\n rootMargin: options?.rootMargin,\n scrollMargin: options?.scrollMargin,\n threshold: options?.threshold,\n },\n );\n\n try {\n observer.observe(element);\n } catch (error) {\n observer.disconnect();\n throw error;\n }\n\n return () => observer.disconnect();\n },\n );\n}\n","import { listenMediaQuery, resolveWindow } from './_platform.ts';\nimport { createSentinel } from './core.ts';\nimport { SentinelUnavailableError } from './errors.ts';\nimport type { MediaQueryState, Sentinel, WindowSentinelOptions } from './types.ts';\n\nexport function createMediaQuery(query: string, options?: WindowSentinelOptions): Sentinel<MediaQueryState> {\n const target = resolveWindow(options?.target);\n if (typeof target.matchMedia !== 'function') {\n throw new SentinelUnavailableError(`matchMedia is not available for query \"${query}\".`);\n }\n\n const mediaQuery = target.matchMedia(query);\n const read = (): MediaQueryState => ({ matches: mediaQuery.matches });\n\n return createSentinel(\n {\n initialValue: read(),\n ...options,\n },\n (update) => listenMediaQuery(mediaQuery, () => update(read())),\n );\n}\n","import { resolveWindow } from './_platform.ts';\nimport { createSentinel } from './core.ts';\nimport type { NetworkConnectionSnapshot, NetworkState, Sentinel, WindowSentinelOptions } from './types.ts';\n\ninterface NetworkInformationLike extends EventTarget {\n readonly downlink?: number;\n readonly effectiveType?: NetworkConnectionSnapshot['effectiveType'];\n readonly rtt?: number;\n readonly saveData?: boolean;\n}\n\ntype NavigatorWithConnection = Navigator & {\n readonly connection?: NetworkInformationLike;\n};\n\nfunction normalizeConnection(connection?: NetworkInformationLike): NetworkConnectionSnapshot | null {\n if (!connection) return null;\n\n return {\n downlink: connection.downlink,\n effectiveType: connection.effectiveType,\n rtt: connection.rtt,\n saveData: connection.saveData,\n };\n}\n\nexport function createNetwork(options?: WindowSentinelOptions): Sentinel<NetworkState> {\n const target = resolveWindow(options?.target);\n const navigator = target.navigator as NavigatorWithConnection;\n const read = (): NetworkState => ({\n connection: normalizeConnection(navigator.connection),\n online: navigator.onLine,\n });\n\n return createSentinel(\n {\n initialValue: read(),\n ...options,\n },\n (update) => {\n const onChange = () => update(read());\n const connection = navigator.connection;\n\n target.addEventListener('online', onChange);\n target.addEventListener('offline', onChange);\n connection?.addEventListener('change', onChange);\n\n return () => {\n target.removeEventListener('online', onChange);\n target.removeEventListener('offline', onChange);\n connection?.removeEventListener('change', onChange);\n };\n },\n );\n}\n","import { listenMediaQuery, resolveWindow } from './_platform.ts';\nimport { createSentinel } from './core.ts';\nimport type { Sentinel, ViewportState, WindowSentinelOptions } from './types.ts';\n\nexport function createViewport(options?: WindowSentinelOptions): Sentinel<ViewportState> {\n const target = resolveWindow(options?.target);\n const read = (): ViewportState => ({\n dpr: target.devicePixelRatio,\n height: target.innerHeight,\n width: target.innerWidth,\n });\n\n return createSentinel(\n {\n initialValue: read(),\n ...options,\n },\n (update) => {\n const onResize = () => update(read());\n let stopDprObservation: (() => void) | undefined;\n\n const observeDpr = () => {\n stopDprObservation?.();\n stopDprObservation = undefined;\n\n if (typeof target.matchMedia !== 'function') return;\n\n const mediaQuery = target.matchMedia(`(resolution: ${target.devicePixelRatio}dppx)`);\n stopDprObservation = listenMediaQuery(mediaQuery, () => {\n update(read());\n observeDpr();\n });\n };\n\n target.addEventListener('resize', onResize);\n observeDpr();\n\n return () => {\n target.removeEventListener('resize', onResize);\n stopDprObservation?.();\n };\n },\n );\n}\n"],"mappings":"yFAAA,IAAa,EAAb,cAAmC,KAAM,CACvC,YAAY,EAAiB,EAAwB,CACnD,MAAM,EAAS,CAAO,EACtB,KAAK,KAAO,WAAW,KACvB,OAAO,eAAe,KAAM,WAAW,SAAS,CAClD,CACF,EAEa,EAAb,cAA8C,CAAc,CAAC,ECQ7D,SAAgB,EAAc,EAAyB,CACrD,GAAI,EAAQ,OAAO,EACnB,GAAI,OAAO,OAAW,IACpB,MAAM,IAAI,EAAyB,8CAA8C,EAGnF,OAAO,MACT,CAEA,SAAgB,EAAqB,EAAkC,CACrE,IAAM,EAAc,EAAQ,cAAc,YAC1C,GAAI,CAAC,EACH,MAAM,IAAI,EAAyB,2CAA2C,EAGhF,OAAO,CACT,CAEA,SAAgB,EAAiB,EAA4B,EAAkC,CAC7F,IAAM,MAAiB,EAAS,EAEhC,GAAI,OAAO,EAAW,kBAAqB,WAEzC,OADA,EAAW,iBAAiB,SAAU,CAAQ,MACjC,EAAW,oBAAoB,SAAU,CAAQ,EAGhE,IAAM,EAAmB,EAMzB,OALI,OAAO,EAAiB,aAAgB,YAC1C,EAAiB,YAAY,CAAQ,MACxB,EAAiB,iBAAiB,CAAQ,OAG5C,CAAC,CAChB,CCzCA,SAAgB,EACd,EACA,EACa,CACb,OAAO,IAAI,EAAe,EAAS,CAAK,CAC1C,CAEA,IAAM,EAAN,KAA+C,CAC7C,eACA,gBAAmC,IAAI,gBACvC,MACA,QACA,cAAwB,GACxB,eACA,sBAEA,YAAY,EAAmC,EAAmD,CAChG,IAAM,EAAW,EAAQ,SAAS,QAAU,EAAA,OAI5C,GAHA,KAAK,MAAQ,EAAS,EAAQ,YAAY,EAC1C,KAAK,eAAiB,KAAK,gBAAgB,OAEvC,EAAQ,QAAQ,QAAS,CAC3B,KAAK,cAAgB,GACrB,KAAK,gBAAgB,MAAM,EAC3B,MACF,CAEA,GAAI,CACF,KAAK,QAAU,EAAO,GAAU,CACzB,KAAK,gBAAe,KAAK,MAAM,MAAQ,EAC9C,CAAC,CACH,OAAS,EAAO,CAGd,KAFA,MAAK,cAAgB,GACrB,KAAK,gBAAgB,MAAM,EACrB,CACR,CAEK,EAAQ,SAEb,KAAK,eAAiB,EAAQ,OAC9B,KAAK,0BAA8B,KAAK,QAAQ,EAChD,KAAK,eAAe,iBAAiB,QAAS,KAAK,sBAAuB,CAAE,KAAM,EAAK,CAAC,EAC1F,CAEA,IAAI,UAAoB,CACtB,OAAO,KAAK,aACd,CAEA,IAAI,OAAW,CACb,OAAO,KAAK,MAAM,KACpB,CAEA,MAAU,CACR,OAAO,KAAK,MAAM,KAAK,CACzB,CAEA,UAAU,EAAmC,CAC3C,OAAO,KAAK,MAAM,UAAU,CAAQ,CACtC,CAEA,SAAgB,CACd,GAAI,KAAK,cAAe,OACxB,KAAK,cAAgB,GAEjB,KAAK,gBAAkB,KAAK,uBAC9B,KAAK,eAAe,oBAAoB,QAAS,KAAK,qBAAqB,EAG7E,IAAM,EAAU,KAAK,QACrB,KAAK,QAAU,IAAA,GACf,KAAK,eAAiB,IAAA,GACtB,KAAK,sBAAwB,IAAA,GAE7B,GAAI,CACF,IAAU,CACZ,QAAU,CACR,KAAK,gBAAgB,MAAM,CAC7B,CACF,CAEA,CAAC,OAAO,UAAiB,CACvB,KAAK,QAAQ,CACf,CACF,ECtFA,SAAgB,EAAkB,EAAkB,EAA8D,CAEhH,IAAM,EADS,EAAqB,CACF,CAAA,CAAO,eACzC,GAAI,OAAO,GAA8B,WACvC,MAAM,IAAI,EAAyB,sDAAsD,EAG3F,OAAO,EACL,CACE,aAAc,KACd,GAAG,CACL,EACC,GAAW,CACV,IAAM,EAAW,IAAI,EAA2B,GAAY,CAC1D,IAAM,EAAQ,EAAQ,GAAG,EAAE,EACtB,GAEL,EAAO,CACL,OAAQ,EAAM,YAAY,OAC1B,MAAO,EAAM,YAAY,KAC3B,CAAC,CACH,CAAC,EAED,GAAI,CACF,EAAS,QAAQ,EAAS,CAAE,IAAK,aAAc,CAAC,CAClD,OAAS,EAAO,CAEd,MADA,EAAS,WAAW,EACd,CACR,CAEA,UAAa,EAAS,WAAW,CACnC,CACF,CACF,CC1BA,SAAgB,EACd,EACA,EACoC,CAEpC,IAAM,EADS,EAAqB,CACI,CAAA,CAAO,qBAC/C,GAAI,OAAO,GAAoC,WAC7C,MAAM,IAAI,EAAyB,4DAA4D,EAGjG,OAAO,EACL,CACE,aAAc,KACd,GAAG,CACL,EACC,GAAW,CACV,IAAM,EAAW,IAAI,EAClB,GAAY,CACX,IAAM,EAAQ,EAAQ,GAAG,EAAE,EACtB,GAEL,EAAO,CACL,kBAAmB,EAAM,kBACzB,eAAgB,EAAM,cACxB,CAAC,CACH,EACA,CACE,KAAM,GAAS,KACf,WAAY,GAAS,WACrB,aAAc,GAAS,aACvB,UAAW,GAAS,SACtB,CACF,EAEA,GAAI,CACF,EAAS,QAAQ,CAAO,CAC1B,OAAS,EAAO,CAEd,MADA,EAAS,WAAW,EACd,CACR,CAEA,UAAa,EAAS,WAAW,CACnC,CACF,CACF,CCnDA,SAAgB,EAAiB,EAAe,EAA4D,CAC1G,IAAM,EAAS,EAAc,GAAS,MAAM,EAC5C,GAAI,OAAO,EAAO,YAAe,WAC/B,MAAM,IAAI,EAAyB,0CAA0C,EAAM,GAAG,EAGxF,IAAM,EAAa,EAAO,WAAW,CAAK,EACpC,OAA+B,CAAE,QAAS,EAAW,OAAQ,GAEnE,OAAO,EACL,CACE,aAAc,EAAK,EACnB,GAAG,CACL,EACC,GAAW,EAAiB,MAAkB,EAAO,EAAK,CAAC,CAAC,CAC/D,CACF,CCNA,SAAS,EAAoB,EAAuE,CAGlG,OAFK,EAEE,CACL,SAAU,EAAW,SACrB,cAAe,EAAW,cAC1B,IAAK,EAAW,IAChB,SAAU,EAAW,QACvB,EAPwB,IAQ1B,CAEA,SAAgB,EAAc,EAAyD,CACrF,IAAM,EAAS,EAAc,GAAS,MAAM,EACtC,EAAY,EAAO,UACnB,OAA4B,CAChC,WAAY,EAAoB,EAAU,UAAU,EACpD,OAAQ,EAAU,MACpB,GAEA,OAAO,EACL,CACE,aAAc,EAAK,EACnB,GAAG,CACL,EACC,GAAW,CACV,IAAM,MAAiB,EAAO,EAAK,CAAC,EAC9B,EAAa,EAAU,WAM7B,OAJA,EAAO,iBAAiB,SAAU,CAAQ,EAC1C,EAAO,iBAAiB,UAAW,CAAQ,EAC3C,GAAY,iBAAiB,SAAU,CAAQ,MAElC,CACX,EAAO,oBAAoB,SAAU,CAAQ,EAC7C,EAAO,oBAAoB,UAAW,CAAQ,EAC9C,GAAY,oBAAoB,SAAU,CAAQ,CACpD,CACF,CACF,CACF,CClDA,SAAgB,EAAe,EAA0D,CACvF,IAAM,EAAS,EAAc,GAAS,MAAM,EACtC,OAA6B,CACjC,IAAK,EAAO,iBACZ,OAAQ,EAAO,YACf,MAAO,EAAO,UAChB,GAEA,OAAO,EACL,CACE,aAAc,EAAK,EACnB,GAAG,CACL,EACC,GAAW,CACV,IAAM,MAAiB,EAAO,EAAK,CAAC,EAChC,EAEE,MAAmB,CACvB,IAAqB,EACrB,EAAqB,IAAA,GAEjB,OAAO,EAAO,YAAe,aAGjC,EAAqB,EADF,EAAO,WAAW,gBAAgB,EAAO,iBAAiB,MACvC,MAAkB,CACtD,EAAO,EAAK,CAAC,EACb,EAAW,CACb,CAAC,EACH,EAKA,OAHA,EAAO,iBAAiB,SAAU,CAAQ,EAC1C,EAAW,MAEE,CACX,EAAO,oBAAoB,SAAU,CAAQ,EAC7C,IAAqB,CACvB,CACF,CACF,CACF"}
1
+ {"version":3,"file":"sentinel.iife.js","names":[],"sources":["../src/errors.ts","../src/core.ts","../src/_platform.ts","../src/element-size.ts","../src/fullscreen.ts","../src/intersection.ts","../src/media-query.ts","../src/network.ts","../src/viewport.ts","../src/wake-lock.ts"],"sourcesContent":["export class SentinelError extends Error {\n constructor(message: string, options?: ErrorOptions) {\n super(message, options);\n this.name = new.target.name;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\nexport class SentinelUnavailableError extends SentinelError {}\n","import { type Tapper, tapper } from '@vielzeug/arsenal';\n\nimport { SentinelError } from './errors.ts';\nimport type { Sentinel, SentinelEvent, SentinelOptions, Unsubscribe } from './types.ts';\n\nexport interface CreateSentinelOptions<T> extends SentinelOptions {\n readonly initialValue: T;\n}\n\nexport function createSentinel<T>(\n options: CreateSentinelOptions<T>,\n setup: (update: (value: T) => void) => () => void,\n): Sentinel<T> {\n return new SentinelHandle(options, setup);\n}\n\nclass SentinelHandle<T> implements Sentinel<T> {\n readonly disposalSignal: AbortSignal;\n private readonly abortController = new AbortController();\n private currentValue: T;\n private readonly listeners = new Set<() => void>();\n private cleanup: (() => void) | undefined;\n private disposedValue = false;\n private externalSignal: AbortSignal | undefined;\n private externalAbortListener: (() => void) | undefined;\n private readonly tapper: Tapper<SentinelEvent> = tapper<SentinelEvent>();\n\n constructor(options: CreateSentinelOptions<T>, setup: (update: (value: T) => void) => () => void) {\n if (options.signal?.aborted) {\n throw new SentinelError('Sentinel signal is already aborted');\n }\n\n this.currentValue = options.initialValue;\n this.disposalSignal = this.abortController.signal;\n\n try {\n this.cleanup = setup((value) => {\n if (!this.disposedValue) {\n this.currentValue = value;\n this.notify();\n }\n });\n } catch (error) {\n this.disposedValue = true;\n this.abortController.abort();\n throw error;\n }\n\n if (!options.signal) return;\n\n this.externalSignal = options.signal;\n this.externalAbortListener = () => this.dispose();\n this.externalSignal.addEventListener('abort', this.externalAbortListener, { once: true });\n }\n\n get disposed(): boolean {\n return this.disposedValue;\n }\n\n readonly getSnapshot = (): T => this.currentValue;\n\n readonly subscribe = (listener: () => void): Unsubscribe => {\n if (this.disposedValue) return () => {};\n this.listeners.add(listener);\n return () => this.listeners.delete(listener);\n };\n\n readonly dispose = (): void => {\n if (this.disposedValue) return;\n this.disposedValue = true;\n\n if (this.externalSignal && this.externalAbortListener) {\n this.externalSignal.removeEventListener('abort', this.externalAbortListener);\n }\n\n const cleanup = this.cleanup;\n this.cleanup = undefined;\n this.externalSignal = undefined;\n this.externalAbortListener = undefined;\n this.listeners.clear();\n\n try {\n cleanup?.();\n } finally {\n this.tapper.emit({ type: 'dispose' });\n this.tapper.clear();\n this.abortController.abort();\n }\n };\n\n readonly tap = (handler: (event: SentinelEvent) => void, options?: { readonly signal?: AbortSignal }): Unsubscribe =>\n this.tapper.tap(handler, options);\n\n private notify(): void {\n for (const listener of [...this.listeners]) {\n try {\n listener();\n } catch (error) {\n this.tapper.emit({ error, type: 'error' });\n queueMicrotask(() => {\n throw error;\n });\n }\n }\n }\n\n [Symbol.dispose](): void {\n this.dispose();\n }\n}\n","import { SentinelUnavailableError } from './errors.ts';\n\ntype LegacyMediaQueryList = MediaQueryList & {\n addListener?: (listener: (event: MediaQueryListEvent) => void) => void;\n removeListener?: (listener: (event: MediaQueryListEvent) => void) => void;\n};\n\nexport type ObserverWindow = Window & {\n readonly IntersectionObserver?: {\n new (callback: IntersectionObserverCallback, options?: IntersectionObserverInit): IntersectionObserver;\n };\n readonly ResizeObserver?: {\n new (callback: ResizeObserverCallback): ResizeObserver;\n };\n};\n\nexport function resolveWindow(target?: Window): Window {\n if (target) return target;\n if (typeof window === 'undefined') {\n throw new SentinelUnavailableError('Window is not available in this environment.');\n }\n\n return window;\n}\n\nexport function resolveElementWindow(element: Element): ObserverWindow {\n const ownerWindow = element.ownerDocument.defaultView;\n if (!ownerWindow) {\n throw new SentinelUnavailableError('The element is not connected to a Window.');\n }\n\n return ownerWindow;\n}\n\nexport function listenMediaQuery(mediaQuery: MediaQueryList, listener: () => void): () => void {\n const onChange = () => listener();\n\n if (typeof mediaQuery.addEventListener === 'function') {\n mediaQuery.addEventListener('change', onChange);\n return () => mediaQuery.removeEventListener('change', onChange);\n }\n\n const legacyMediaQuery = mediaQuery as LegacyMediaQueryList;\n if (typeof legacyMediaQuery.addListener === 'function') {\n legacyMediaQuery.addListener(onChange);\n return () => legacyMediaQuery.removeListener?.(onChange);\n }\n\n return () => {};\n}\n","import { resolveElementWindow } from './_platform.ts';\nimport { createSentinel } from './core.ts';\nimport { SentinelUnavailableError } from './errors.ts';\nimport type { ElementSizeState, Sentinel, SentinelOptions } from './types.ts';\n\nexport function createElementSize(element: Element, options?: SentinelOptions): Sentinel<ElementSizeState | null> {\n const target = resolveElementWindow(element);\n const ResizeObserverConstructor = target.ResizeObserver;\n if (typeof ResizeObserverConstructor !== 'function') {\n throw new SentinelUnavailableError('ResizeObserver is not available in this environment.');\n }\n\n return createSentinel<ElementSizeState | null>(\n {\n initialValue: null,\n ...options,\n },\n (update) => {\n const observer = new ResizeObserverConstructor((entries) => {\n const entry = entries.at(-1);\n if (!entry) return;\n\n update({\n height: entry.contentRect.height,\n width: entry.contentRect.width,\n });\n });\n\n try {\n observer.observe(element, { box: 'content-box' });\n } catch (error) {\n observer.disconnect();\n throw error;\n }\n\n return () => observer.disconnect();\n },\n );\n}\n","import { resolveWindow } from './_platform.ts';\nimport { createSentinel } from './core.ts';\nimport type { FullscreenSentinel, FullscreenState, SentinelOptions } from './types.ts';\n\ntype FullscreenWindow = Window & { document: Document };\n\n/**\n * A sentinel-backed fullscreen view. `request()` enters fullscreen for an element (default:\n * the document element), `exit()` leaves it, and `toggle()` flips it — all degrade silently\n * when the Fullscreen API is missing or the browser refuses (user-activation requirements).\n * Disposing the sentinel exits fullscreen and stops tracking `fullscreenchange`.\n */\nexport function createFullscreen(\n options?: SentinelOptions & { readonly target?: FullscreenWindow },\n): FullscreenSentinel {\n const target = resolveWindow(options?.target);\n const doc = target.document;\n const supported = typeof doc.documentElement?.requestFullscreen === 'function';\n\n const read = (): FullscreenState => ({ active: doc.fullscreenElement !== null, supported });\n let updateState: (value: FullscreenState) => void = () => {};\n\n const request = (element?: Element): void => {\n if (doc.fullscreenElement || !supported) return;\n void (element ?? doc.documentElement).requestFullscreen().catch(() => {\n // Requires user activation in some browsers, or the element may refuse; degrade silently.\n });\n };\n\n const exit = (): void => {\n if (!doc.fullscreenElement) return;\n void doc.exitFullscreen().catch(() => {\n // Degrade silently.\n });\n };\n\n const sentinel = createSentinel<FullscreenState>({ initialValue: read(), ...options }, (update) => {\n updateState = update;\n\n const onFullscreenChange = (): void => updateState(read());\n\n doc.addEventListener('fullscreenchange', onFullscreenChange);\n\n return () => {\n doc.removeEventListener('fullscreenchange', onFullscreenChange);\n if (doc.fullscreenElement) exit();\n };\n });\n\n return {\n ...sentinel,\n exit,\n request,\n toggle(element?: Element): void {\n if (doc.fullscreenElement) exit();\n else request(element);\n },\n };\n}\n","import { resolveElementWindow } from './_platform.ts';\nimport { createSentinel } from './core.ts';\nimport { SentinelUnavailableError } from './errors.ts';\nimport type { IntersectionState, Sentinel, SentinelOptions } from './types.ts';\n\nexport interface CreateIntersectionOptions extends SentinelOptions {\n readonly root?: Element | Document | null;\n readonly rootMargin?: string;\n readonly scrollMargin?: string;\n readonly threshold?: number | number[];\n}\n\nexport function createIntersection(\n element: Element,\n options?: CreateIntersectionOptions,\n): Sentinel<IntersectionState | null> {\n const target = resolveElementWindow(element);\n const IntersectionObserverConstructor = target.IntersectionObserver;\n if (typeof IntersectionObserverConstructor !== 'function') {\n throw new SentinelUnavailableError('IntersectionObserver is not available in this environment.');\n }\n\n return createSentinel<IntersectionState | null>(\n {\n initialValue: null,\n ...options,\n },\n (update) => {\n const observer = new IntersectionObserverConstructor(\n (entries) => {\n const entry = entries.at(-1);\n if (!entry) return;\n\n update({\n intersectionRatio: entry.intersectionRatio,\n isIntersecting: entry.isIntersecting,\n });\n },\n {\n root: options?.root,\n rootMargin: options?.rootMargin,\n scrollMargin: options?.scrollMargin,\n threshold: options?.threshold,\n },\n );\n\n try {\n observer.observe(element);\n } catch (error) {\n observer.disconnect();\n throw error;\n }\n\n return () => observer.disconnect();\n },\n );\n}\n","import { listenMediaQuery, resolveWindow } from './_platform.ts';\nimport { createSentinel } from './core.ts';\nimport { SentinelUnavailableError } from './errors.ts';\nimport type { MediaQueryState, Sentinel, WindowSentinelOptions } from './types.ts';\n\nexport function createMediaQuery(query: string, options?: WindowSentinelOptions): Sentinel<MediaQueryState> {\n const target = resolveWindow(options?.target);\n if (typeof target.matchMedia !== 'function') {\n throw new SentinelUnavailableError(`matchMedia is not available for query \"${query}\".`);\n }\n\n const mediaQuery = target.matchMedia(query);\n const read = (): MediaQueryState => ({ matches: mediaQuery.matches });\n\n return createSentinel(\n {\n initialValue: read(),\n ...options,\n },\n (update) => listenMediaQuery(mediaQuery, () => update(read())),\n );\n}\n","import { resolveWindow } from './_platform.ts';\nimport { createSentinel } from './core.ts';\nimport type { NetworkConnectionSnapshot, NetworkState, Sentinel, WindowSentinelOptions } from './types.ts';\n\ninterface NetworkInformationLike extends EventTarget {\n readonly downlink?: number;\n readonly effectiveType?: NetworkConnectionSnapshot['effectiveType'];\n readonly rtt?: number;\n readonly saveData?: boolean;\n}\n\ntype NavigatorWithConnection = Navigator & {\n readonly connection?: NetworkInformationLike;\n};\n\nfunction normalizeConnection(connection?: NetworkInformationLike): NetworkConnectionSnapshot | null {\n if (!connection) return null;\n\n return {\n downlink: connection.downlink,\n effectiveType: connection.effectiveType,\n rtt: connection.rtt,\n saveData: connection.saveData,\n };\n}\n\nexport function createNetwork(options?: WindowSentinelOptions): Sentinel<NetworkState> {\n const target = resolveWindow(options?.target);\n const navigator = target.navigator as NavigatorWithConnection;\n const read = (): NetworkState => ({\n connection: normalizeConnection(navigator.connection),\n online: navigator.onLine,\n });\n\n return createSentinel(\n {\n initialValue: read(),\n ...options,\n },\n (update) => {\n const onChange = () => update(read());\n const connection = navigator.connection;\n\n target.addEventListener('online', onChange);\n target.addEventListener('offline', onChange);\n connection?.addEventListener('change', onChange);\n\n return () => {\n target.removeEventListener('online', onChange);\n target.removeEventListener('offline', onChange);\n connection?.removeEventListener('change', onChange);\n };\n },\n );\n}\n","import { listenMediaQuery, resolveWindow } from './_platform.ts';\nimport { createSentinel } from './core.ts';\nimport type { Sentinel, ViewportState, WindowSentinelOptions } from './types.ts';\n\nexport function createViewport(options?: WindowSentinelOptions): Sentinel<ViewportState> {\n const target = resolveWindow(options?.target);\n const read = (): ViewportState => ({\n dpr: target.devicePixelRatio,\n height: target.innerHeight,\n width: target.innerWidth,\n });\n\n return createSentinel(\n {\n initialValue: read(),\n ...options,\n },\n (update) => {\n const onResize = () => update(read());\n let stopDprObservation: (() => void) | undefined;\n\n const observeDpr = () => {\n stopDprObservation?.();\n stopDprObservation = undefined;\n\n if (typeof target.matchMedia !== 'function') return;\n\n const mediaQuery = target.matchMedia(`(resolution: ${target.devicePixelRatio}dppx)`);\n stopDprObservation = listenMediaQuery(mediaQuery, () => {\n update(read());\n observeDpr();\n });\n };\n\n target.addEventListener('resize', onResize);\n observeDpr();\n\n return () => {\n target.removeEventListener('resize', onResize);\n stopDprObservation?.();\n };\n },\n );\n}\n","import { resolveWindow } from './_platform.ts';\nimport { createSentinel } from './core.ts';\nimport type { SentinelOptions, WakeLockSentinel, WakeLockState } from './types.ts';\n\ntype WakeLockWindow = Window & { document: Document };\n\nexport function createWakeLock(options?: SentinelOptions & { readonly target?: WakeLockWindow }): WakeLockSentinel {\n const target = resolveWindow(options?.target);\n const navigator = target.navigator;\n const supported = typeof navigator.wakeLock?.request === 'function';\n\n let handle: globalThis.WakeLockSentinel | null = null;\n let pending = false;\n let wantsLock = false;\n\n const read = (): WakeLockState => ({ active: handle !== null, supported });\n let updateState: (value: WakeLockState) => void = () => {};\n\n async function acquire(): Promise<void> {\n if (!supported || !navigator.wakeLock) return;\n pending = true;\n try {\n const requested = await navigator.wakeLock.request('screen');\n // release()/dispose() during the pending request must not leave the lock held.\n if (!wantsLock) {\n void requested.release();\n\n return;\n }\n handle = requested;\n handle.addEventListener('release', () => {\n handle = null;\n updateState(read());\n });\n updateState(read());\n } catch {\n // Not supported, denied, or not in a secure context — degrade silently.\n } finally {\n pending = false;\n }\n }\n\n const sentinel = createSentinel<WakeLockState>({ initialValue: read(), ...options }, (update) => {\n updateState = update;\n\n const onVisibilityChange = (): void => {\n if (target.document.visibilityState === 'visible' && wantsLock && !handle && !pending) {\n void acquire();\n }\n };\n\n target.document.addEventListener('visibilitychange', onVisibilityChange);\n\n return () => {\n target.document.removeEventListener('visibilitychange', onVisibilityChange);\n if (handle) void handle.release();\n handle = null;\n wantsLock = false;\n };\n });\n\n return {\n ...sentinel,\n release(): void {\n wantsLock = false;\n if (handle) {\n void handle.release();\n handle = null;\n updateState(read());\n }\n },\n request(): void {\n if (handle || pending || !supported) return;\n wantsLock = true;\n void acquire();\n },\n };\n}\n"],"mappings":"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"}
package/dist/sentinel.js CHANGED
@@ -1,2 +1,2 @@
1
- import{signal as e}from"@vielzeug/ripple";var t=class extends Error{constructor(e,t){super(e,t),this.name=new.target.name,Object.setPrototypeOf(this,new.target.prototype)}},n=class extends t{};function r(e){if(e)return e;if(typeof window>`u`)throw new n(`Window is not available in this environment.`);return window}function i(e){let t=e.ownerDocument.defaultView;if(!t)throw new n(`The element is not connected to a Window.`);return t}function a(e,t){let n=()=>t();if(typeof e.addEventListener==`function`)return e.addEventListener(`change`,n),()=>e.removeEventListener(`change`,n);let r=e;return typeof r.addListener==`function`?(r.addListener(n),()=>r.removeListener?.(n)):()=>{}}function o(e,t){return new s(e,t)}var s=class{disposalSignal;abortController=new AbortController;state;cleanup;disposedValue=!1;externalSignal;externalAbortListener;constructor(t,n){let r=t.runtime?.signal??e;if(this.state=r(t.initialValue),this.disposalSignal=this.abortController.signal,t.signal?.aborted){this.disposedValue=!0,this.abortController.abort();return}try{this.cleanup=n(e=>{this.disposedValue||(this.state.value=e)})}catch(e){throw this.disposedValue=!0,this.abortController.abort(),e}t.signal&&(this.externalSignal=t.signal,this.externalAbortListener=()=>this.dispose(),this.externalSignal.addEventListener(`abort`,this.externalAbortListener,{once:!0}))}get disposed(){return this.disposedValue}get value(){return this.state.value}peek(){return this.state.peek()}subscribe(e){return this.state.subscribe(e)}dispose(){if(this.disposedValue)return;this.disposedValue=!0,this.externalSignal&&this.externalAbortListener&&this.externalSignal.removeEventListener(`abort`,this.externalAbortListener);let e=this.cleanup;this.cleanup=void 0,this.externalSignal=void 0,this.externalAbortListener=void 0;try{e?.()}finally{this.abortController.abort()}}[Symbol.dispose](){this.dispose()}};function c(e,t){let r=i(e).ResizeObserver;if(typeof r!=`function`)throw new n(`ResizeObserver is not available in this environment.`);return o({initialValue:null,...t},t=>{let n=new r(e=>{let n=e.at(-1);n&&t({height:n.contentRect.height,width:n.contentRect.width})});try{n.observe(e,{box:`content-box`})}catch(e){throw n.disconnect(),e}return()=>n.disconnect()})}function l(e,t){let r=i(e).IntersectionObserver;if(typeof r!=`function`)throw new n(`IntersectionObserver is not available in this environment.`);return o({initialValue:null,...t},n=>{let i=new r(e=>{let t=e.at(-1);t&&n({intersectionRatio:t.intersectionRatio,isIntersecting:t.isIntersecting})},{root:t?.root,rootMargin:t?.rootMargin,scrollMargin:t?.scrollMargin,threshold:t?.threshold});try{i.observe(e)}catch(e){throw i.disconnect(),e}return()=>i.disconnect()})}function u(e,t){let i=r(t?.target);if(typeof i.matchMedia!=`function`)throw new n(`matchMedia is not available for query "${e}".`);let s=i.matchMedia(e),c=()=>({matches:s.matches});return o({initialValue:c(),...t},e=>a(s,()=>e(c())))}function d(e){return e?{downlink:e.downlink,effectiveType:e.effectiveType,rtt:e.rtt,saveData:e.saveData}:null}function f(e){let t=r(e?.target),n=t.navigator,i=()=>({connection:d(n.connection),online:n.onLine});return o({initialValue:i(),...e},e=>{let r=()=>e(i()),a=n.connection;return t.addEventListener(`online`,r),t.addEventListener(`offline`,r),a?.addEventListener(`change`,r),()=>{t.removeEventListener(`online`,r),t.removeEventListener(`offline`,r),a?.removeEventListener(`change`,r)}})}function p(e){let t=r(e?.target),n=()=>({dpr:t.devicePixelRatio,height:t.innerHeight,width:t.innerWidth});return o({initialValue:n(),...e},e=>{let r=()=>e(n()),i,o=()=>{i?.(),i=void 0,typeof t.matchMedia==`function`&&(i=a(t.matchMedia(`(resolution: ${t.devicePixelRatio}dppx)`),()=>{e(n()),o()}))};return t.addEventListener(`resize`,r),o(),()=>{t.removeEventListener(`resize`,r),i?.()}})}export{t as SentinelError,n as SentinelUnavailableError,c as createElementSize,l as createIntersection,u as createMediaQuery,f as createNetwork,p as createViewport};
1
+ import{tapper as e}from"@vielzeug/arsenal";var SentinelError=class extends Error{constructor(e,t){super(e,t),this.name=new.target.name,Object.setPrototypeOf(this,new.target.prototype)}},SentinelUnavailableError=class extends SentinelError{};function createSentinel(e,t){return new SentinelHandle(e,t)}var SentinelHandle=class{disposalSignal;abortController=new AbortController;currentValue;listeners=new Set;cleanup;disposedValue=!1;externalSignal;externalAbortListener;tapper=e();constructor(e,t){if(e.signal?.aborted)throw new SentinelError(`Sentinel signal is already aborted`);this.currentValue=e.initialValue,this.disposalSignal=this.abortController.signal;try{this.cleanup=t(e=>{this.disposedValue||(this.currentValue=e,this.notify())})}catch(e){throw this.disposedValue=!0,this.abortController.abort(),e}e.signal&&(this.externalSignal=e.signal,this.externalAbortListener=()=>this.dispose(),this.externalSignal.addEventListener(`abort`,this.externalAbortListener,{once:!0}))}get disposed(){return this.disposedValue}getSnapshot=()=>this.currentValue;subscribe=e=>this.disposedValue?()=>{}:(this.listeners.add(e),()=>this.listeners.delete(e));dispose=()=>{if(this.disposedValue)return;this.disposedValue=!0,this.externalSignal&&this.externalAbortListener&&this.externalSignal.removeEventListener(`abort`,this.externalAbortListener);let e=this.cleanup;this.cleanup=void 0,this.externalSignal=void 0,this.externalAbortListener=void 0,this.listeners.clear();try{e?.()}finally{this.tapper.emit({type:`dispose`}),this.tapper.clear(),this.abortController.abort()}};tap=(e,t)=>this.tapper.tap(e,t);notify(){for(let e of[...this.listeners])try{e()}catch(e){this.tapper.emit({error:e,type:`error`}),queueMicrotask(()=>{throw e})}}[Symbol.dispose](){this.dispose()}};function resolveWindow(e){if(e)return e;if(typeof window>`u`)throw new SentinelUnavailableError(`Window is not available in this environment.`);return window}function resolveElementWindow(e){let t=e.ownerDocument.defaultView;if(!t)throw new SentinelUnavailableError(`The element is not connected to a Window.`);return t}function listenMediaQuery(e,t){let onChange=()=>t();if(typeof e.addEventListener==`function`)return e.addEventListener(`change`,onChange),()=>e.removeEventListener(`change`,onChange);let n=e;return typeof n.addListener==`function`?(n.addListener(onChange),()=>n.removeListener?.(onChange)):()=>{}}function createElementSize(e,t){let n=resolveElementWindow(e).ResizeObserver;if(typeof n!=`function`)throw new SentinelUnavailableError(`ResizeObserver is not available in this environment.`);return createSentinel({initialValue:null,...t},t=>{let r=new n(e=>{let n=e.at(-1);n&&t({height:n.contentRect.height,width:n.contentRect.width})});try{r.observe(e,{box:`content-box`})}catch(e){throw r.disconnect(),e}return()=>r.disconnect()})}function createFullscreen(e){let t=resolveWindow(e?.target).document,n=typeof t.documentElement?.requestFullscreen==`function`,read=()=>({active:t.fullscreenElement!==null,supported:n}),updateState=()=>{},request=e=>{!t.fullscreenElement&&n&&(e??t.documentElement).requestFullscreen().catch(()=>{})},exit=()=>{t.fullscreenElement&&t.exitFullscreen().catch(()=>{})};return{...createSentinel({initialValue:read(),...e},e=>{updateState=e;let onFullscreenChange=()=>updateState(read());return t.addEventListener(`fullscreenchange`,onFullscreenChange),()=>{t.removeEventListener(`fullscreenchange`,onFullscreenChange),t.fullscreenElement&&exit()}}),exit,request,toggle(e){t.fullscreenElement?exit():request(e)}}}function createIntersection(e,t){let n=resolveElementWindow(e).IntersectionObserver;if(typeof n!=`function`)throw new SentinelUnavailableError(`IntersectionObserver is not available in this environment.`);return createSentinel({initialValue:null,...t},r=>{let i=new n(e=>{let t=e.at(-1);t&&r({intersectionRatio:t.intersectionRatio,isIntersecting:t.isIntersecting})},{root:t?.root,rootMargin:t?.rootMargin,scrollMargin:t?.scrollMargin,threshold:t?.threshold});try{i.observe(e)}catch(e){throw i.disconnect(),e}return()=>i.disconnect()})}function createMediaQuery(e,t){let n=resolveWindow(t?.target);if(typeof n.matchMedia!=`function`)throw new SentinelUnavailableError(`matchMedia is not available for query "${e}".`);let r=n.matchMedia(e),read=()=>({matches:r.matches});return createSentinel({initialValue:read(),...t},e=>listenMediaQuery(r,()=>e(read())))}function normalizeConnection(e){return e?{downlink:e.downlink,effectiveType:e.effectiveType,rtt:e.rtt,saveData:e.saveData}:null}function createNetwork(e){let t=resolveWindow(e?.target),n=t.navigator,read=()=>({connection:normalizeConnection(n.connection),online:n.onLine});return createSentinel({initialValue:read(),...e},e=>{let onChange=()=>e(read()),r=n.connection;return t.addEventListener(`online`,onChange),t.addEventListener(`offline`,onChange),r?.addEventListener(`change`,onChange),()=>{t.removeEventListener(`online`,onChange),t.removeEventListener(`offline`,onChange),r?.removeEventListener(`change`,onChange)}})}function createViewport(e){let t=resolveWindow(e?.target),read=()=>({dpr:t.devicePixelRatio,height:t.innerHeight,width:t.innerWidth});return createSentinel({initialValue:read(),...e},e=>{let onResize=()=>e(read()),n,observeDpr=()=>{n?.(),n=void 0,typeof t.matchMedia==`function`&&(n=listenMediaQuery(t.matchMedia(`(resolution: ${t.devicePixelRatio}dppx)`),()=>{e(read()),observeDpr()}))};return t.addEventListener(`resize`,onResize),observeDpr(),()=>{t.removeEventListener(`resize`,onResize),n?.()}})}function createWakeLock(e){let t=resolveWindow(e?.target),n=t.navigator,r=typeof n.wakeLock?.request==`function`,i=null,a=!1,o=!1,read=()=>({active:i!==null,supported:r}),updateState=()=>{};async function acquire(){if(r&&n.wakeLock){a=!0;try{let e=await n.wakeLock.request(`screen`);if(!o){e.release();return}i=e,i.addEventListener(`release`,()=>{i=null,updateState(read())}),updateState(read())}catch{}finally{a=!1}}}return{...createSentinel({initialValue:read(),...e},e=>{updateState=e;let onVisibilityChange=()=>{t.document.visibilityState===`visible`&&o&&!i&&!a&&acquire()};return t.document.addEventListener(`visibilitychange`,onVisibilityChange),()=>{t.document.removeEventListener(`visibilitychange`,onVisibilityChange),i&&i.release(),i=null,o=!1}}),release(){o=!1,i&&(i.release(),i=null,updateState(read()))},request(){i||a||!r||(o=!0,acquire())}}}export{SentinelError,SentinelUnavailableError,createElementSize,createFullscreen,createIntersection,createMediaQuery,createNetwork,createSentinel,createViewport,createWakeLock};
2
2
  //# sourceMappingURL=sentinel.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"sentinel.js","names":[],"sources":["../src/errors.ts","../src/_platform.ts","../src/core.ts","../src/element-size.ts","../src/intersection.ts","../src/media-query.ts","../src/network.ts","../src/viewport.ts"],"sourcesContent":["export class SentinelError extends Error {\n constructor(message: string, options?: ErrorOptions) {\n super(message, options);\n this.name = new.target.name;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\nexport class SentinelUnavailableError extends SentinelError {}\n","import { SentinelUnavailableError } from './errors.ts';\n\ntype LegacyMediaQueryList = MediaQueryList & {\n addListener?: (listener: (event: MediaQueryListEvent) => void) => void;\n removeListener?: (listener: (event: MediaQueryListEvent) => void) => void;\n};\n\nexport type ObserverWindow = Window & {\n readonly IntersectionObserver?: {\n new (callback: IntersectionObserverCallback, options?: IntersectionObserverInit): IntersectionObserver;\n };\n readonly ResizeObserver?: {\n new (callback: ResizeObserverCallback): ResizeObserver;\n };\n};\n\nexport function resolveWindow(target?: Window): Window {\n if (target) return target;\n if (typeof window === 'undefined') {\n throw new SentinelUnavailableError('Window is not available in this environment.');\n }\n\n return window;\n}\n\nexport function resolveElementWindow(element: Element): ObserverWindow {\n const ownerWindow = element.ownerDocument.defaultView;\n if (!ownerWindow) {\n throw new SentinelUnavailableError('The element is not connected to a Window.');\n }\n\n return ownerWindow;\n}\n\nexport function listenMediaQuery(mediaQuery: MediaQueryList, listener: () => void): () => void {\n const onChange = () => listener();\n\n if (typeof mediaQuery.addEventListener === 'function') {\n mediaQuery.addEventListener('change', onChange);\n return () => mediaQuery.removeEventListener('change', onChange);\n }\n\n const legacyMediaQuery = mediaQuery as LegacyMediaQueryList;\n if (typeof legacyMediaQuery.addListener === 'function') {\n legacyMediaQuery.addListener(onChange);\n return () => legacyMediaQuery.removeListener?.(onChange);\n }\n\n return () => {};\n}\n","import type { Signal, Unsubscribe } from '@vielzeug/ripple';\nimport { signal as createSignal } from '@vielzeug/ripple';\nimport type { Sentinel, SentinelOptions } from './types.ts';\n\nexport interface CreateSentinelOptions<T> extends SentinelOptions {\n readonly initialValue: T;\n}\n\nexport function createSentinel<T>(\n options: CreateSentinelOptions<T>,\n setup: (update: (value: T) => void) => () => void,\n): Sentinel<T> {\n return new SentinelHandle(options, setup);\n}\n\nclass SentinelHandle<T> implements Sentinel<T> {\n readonly disposalSignal: AbortSignal;\n private readonly abortController = new AbortController();\n private readonly state: Signal<T>;\n private cleanup: (() => void) | undefined;\n private disposedValue = false;\n private externalSignal: AbortSignal | undefined;\n private externalAbortListener: (() => void) | undefined;\n\n constructor(options: CreateSentinelOptions<T>, setup: (update: (value: T) => void) => () => void) {\n const toSignal = options.runtime?.signal ?? createSignal;\n this.state = toSignal(options.initialValue);\n this.disposalSignal = this.abortController.signal;\n\n if (options.signal?.aborted) {\n this.disposedValue = true;\n this.abortController.abort();\n return;\n }\n\n try {\n this.cleanup = setup((value) => {\n if (!this.disposedValue) this.state.value = value;\n });\n } catch (error) {\n this.disposedValue = true;\n this.abortController.abort();\n throw error;\n }\n\n if (!options.signal) return;\n\n this.externalSignal = options.signal;\n this.externalAbortListener = () => this.dispose();\n this.externalSignal.addEventListener('abort', this.externalAbortListener, { once: true });\n }\n\n get disposed(): boolean {\n return this.disposedValue;\n }\n\n get value(): T {\n return this.state.value;\n }\n\n peek(): T {\n return this.state.peek();\n }\n\n subscribe(listener: () => void): Unsubscribe {\n return this.state.subscribe(listener);\n }\n\n dispose(): void {\n if (this.disposedValue) return;\n this.disposedValue = true;\n\n if (this.externalSignal && this.externalAbortListener) {\n this.externalSignal.removeEventListener('abort', this.externalAbortListener);\n }\n\n const cleanup = this.cleanup;\n this.cleanup = undefined;\n this.externalSignal = undefined;\n this.externalAbortListener = undefined;\n\n try {\n cleanup?.();\n } finally {\n this.abortController.abort();\n }\n }\n\n [Symbol.dispose](): void {\n this.dispose();\n }\n}\n","import { resolveElementWindow } from './_platform.ts';\nimport { createSentinel } from './core.ts';\nimport { SentinelUnavailableError } from './errors.ts';\nimport type { ElementSizeState, Sentinel, SentinelOptions } from './types.ts';\n\nexport function createElementSize(element: Element, options?: SentinelOptions): Sentinel<ElementSizeState | null> {\n const target = resolveElementWindow(element);\n const ResizeObserverConstructor = target.ResizeObserver;\n if (typeof ResizeObserverConstructor !== 'function') {\n throw new SentinelUnavailableError('ResizeObserver is not available in this environment.');\n }\n\n return createSentinel<ElementSizeState | null>(\n {\n initialValue: null,\n ...options,\n },\n (update) => {\n const observer = new ResizeObserverConstructor((entries) => {\n const entry = entries.at(-1);\n if (!entry) return;\n\n update({\n height: entry.contentRect.height,\n width: entry.contentRect.width,\n });\n });\n\n try {\n observer.observe(element, { box: 'content-box' });\n } catch (error) {\n observer.disconnect();\n throw error;\n }\n\n return () => observer.disconnect();\n },\n );\n}\n","import { resolveElementWindow } from './_platform.ts';\nimport { createSentinel } from './core.ts';\nimport { SentinelUnavailableError } from './errors.ts';\nimport type { IntersectionState, Sentinel, SentinelOptions } from './types.ts';\n\nexport interface CreateIntersectionOptions extends SentinelOptions {\n readonly root?: Element | Document | null;\n readonly rootMargin?: string;\n readonly scrollMargin?: string;\n readonly threshold?: number | number[];\n}\n\nexport function createIntersection(\n element: Element,\n options?: CreateIntersectionOptions,\n): Sentinel<IntersectionState | null> {\n const target = resolveElementWindow(element);\n const IntersectionObserverConstructor = target.IntersectionObserver;\n if (typeof IntersectionObserverConstructor !== 'function') {\n throw new SentinelUnavailableError('IntersectionObserver is not available in this environment.');\n }\n\n return createSentinel<IntersectionState | null>(\n {\n initialValue: null,\n ...options,\n },\n (update) => {\n const observer = new IntersectionObserverConstructor(\n (entries) => {\n const entry = entries.at(-1);\n if (!entry) return;\n\n update({\n intersectionRatio: entry.intersectionRatio,\n isIntersecting: entry.isIntersecting,\n });\n },\n {\n root: options?.root,\n rootMargin: options?.rootMargin,\n scrollMargin: options?.scrollMargin,\n threshold: options?.threshold,\n },\n );\n\n try {\n observer.observe(element);\n } catch (error) {\n observer.disconnect();\n throw error;\n }\n\n return () => observer.disconnect();\n },\n );\n}\n","import { listenMediaQuery, resolveWindow } from './_platform.ts';\nimport { createSentinel } from './core.ts';\nimport { SentinelUnavailableError } from './errors.ts';\nimport type { MediaQueryState, Sentinel, WindowSentinelOptions } from './types.ts';\n\nexport function createMediaQuery(query: string, options?: WindowSentinelOptions): Sentinel<MediaQueryState> {\n const target = resolveWindow(options?.target);\n if (typeof target.matchMedia !== 'function') {\n throw new SentinelUnavailableError(`matchMedia is not available for query \"${query}\".`);\n }\n\n const mediaQuery = target.matchMedia(query);\n const read = (): MediaQueryState => ({ matches: mediaQuery.matches });\n\n return createSentinel(\n {\n initialValue: read(),\n ...options,\n },\n (update) => listenMediaQuery(mediaQuery, () => update(read())),\n );\n}\n","import { resolveWindow } from './_platform.ts';\nimport { createSentinel } from './core.ts';\nimport type { NetworkConnectionSnapshot, NetworkState, Sentinel, WindowSentinelOptions } from './types.ts';\n\ninterface NetworkInformationLike extends EventTarget {\n readonly downlink?: number;\n readonly effectiveType?: NetworkConnectionSnapshot['effectiveType'];\n readonly rtt?: number;\n readonly saveData?: boolean;\n}\n\ntype NavigatorWithConnection = Navigator & {\n readonly connection?: NetworkInformationLike;\n};\n\nfunction normalizeConnection(connection?: NetworkInformationLike): NetworkConnectionSnapshot | null {\n if (!connection) return null;\n\n return {\n downlink: connection.downlink,\n effectiveType: connection.effectiveType,\n rtt: connection.rtt,\n saveData: connection.saveData,\n };\n}\n\nexport function createNetwork(options?: WindowSentinelOptions): Sentinel<NetworkState> {\n const target = resolveWindow(options?.target);\n const navigator = target.navigator as NavigatorWithConnection;\n const read = (): NetworkState => ({\n connection: normalizeConnection(navigator.connection),\n online: navigator.onLine,\n });\n\n return createSentinel(\n {\n initialValue: read(),\n ...options,\n },\n (update) => {\n const onChange = () => update(read());\n const connection = navigator.connection;\n\n target.addEventListener('online', onChange);\n target.addEventListener('offline', onChange);\n connection?.addEventListener('change', onChange);\n\n return () => {\n target.removeEventListener('online', onChange);\n target.removeEventListener('offline', onChange);\n connection?.removeEventListener('change', onChange);\n };\n },\n );\n}\n","import { listenMediaQuery, resolveWindow } from './_platform.ts';\nimport { createSentinel } from './core.ts';\nimport type { Sentinel, ViewportState, WindowSentinelOptions } from './types.ts';\n\nexport function createViewport(options?: WindowSentinelOptions): Sentinel<ViewportState> {\n const target = resolveWindow(options?.target);\n const read = (): ViewportState => ({\n dpr: target.devicePixelRatio,\n height: target.innerHeight,\n width: target.innerWidth,\n });\n\n return createSentinel(\n {\n initialValue: read(),\n ...options,\n },\n (update) => {\n const onResize = () => update(read());\n let stopDprObservation: (() => void) | undefined;\n\n const observeDpr = () => {\n stopDprObservation?.();\n stopDprObservation = undefined;\n\n if (typeof target.matchMedia !== 'function') return;\n\n const mediaQuery = target.matchMedia(`(resolution: ${target.devicePixelRatio}dppx)`);\n stopDprObservation = listenMediaQuery(mediaQuery, () => {\n update(read());\n observeDpr();\n });\n };\n\n target.addEventListener('resize', onResize);\n observeDpr();\n\n return () => {\n target.removeEventListener('resize', onResize);\n stopDprObservation?.();\n };\n },\n );\n}\n"],"mappings":"0CAAA,IAAa,EAAb,cAAmC,KAAM,CACvC,YAAY,EAAiB,EAAwB,CACnD,MAAM,EAAS,CAAO,EACtB,KAAK,KAAO,WAAW,KACvB,OAAO,eAAe,KAAM,WAAW,SAAS,CAClD,CACF,EAEa,EAAb,cAA8C,CAAc,CAAC,ECQ7D,SAAgB,EAAc,EAAyB,CACrD,GAAI,EAAQ,OAAO,EACnB,GAAI,OAAO,OAAW,IACpB,MAAM,IAAI,EAAyB,8CAA8C,EAGnF,OAAO,MACT,CAEA,SAAgB,EAAqB,EAAkC,CACrE,IAAM,EAAc,EAAQ,cAAc,YAC1C,GAAI,CAAC,EACH,MAAM,IAAI,EAAyB,2CAA2C,EAGhF,OAAO,CACT,CAEA,SAAgB,EAAiB,EAA4B,EAAkC,CAC7F,IAAM,MAAiB,EAAS,EAEhC,GAAI,OAAO,EAAW,kBAAqB,WAEzC,OADA,EAAW,iBAAiB,SAAU,CAAQ,MACjC,EAAW,oBAAoB,SAAU,CAAQ,EAGhE,IAAM,EAAmB,EAMzB,OALI,OAAO,EAAiB,aAAgB,YAC1C,EAAiB,YAAY,CAAQ,MACxB,EAAiB,iBAAiB,CAAQ,OAG5C,CAAC,CAChB,CCzCA,SAAgB,EACd,EACA,EACa,CACb,OAAO,IAAI,EAAe,EAAS,CAAK,CAC1C,CAEA,IAAM,EAAN,KAA+C,CAC7C,eACA,gBAAmC,IAAI,gBACvC,MACA,QACA,cAAwB,GACxB,eACA,sBAEA,YAAY,EAAmC,EAAmD,CAChG,IAAM,EAAW,EAAQ,SAAS,QAAU,EAI5C,GAHA,KAAK,MAAQ,EAAS,EAAQ,YAAY,EAC1C,KAAK,eAAiB,KAAK,gBAAgB,OAEvC,EAAQ,QAAQ,QAAS,CAC3B,KAAK,cAAgB,GACrB,KAAK,gBAAgB,MAAM,EAC3B,MACF,CAEA,GAAI,CACF,KAAK,QAAU,EAAO,GAAU,CACzB,KAAK,gBAAe,KAAK,MAAM,MAAQ,EAC9C,CAAC,CACH,OAAS,EAAO,CAGd,KAFA,MAAK,cAAgB,GACrB,KAAK,gBAAgB,MAAM,EACrB,CACR,CAEK,EAAQ,SAEb,KAAK,eAAiB,EAAQ,OAC9B,KAAK,0BAA8B,KAAK,QAAQ,EAChD,KAAK,eAAe,iBAAiB,QAAS,KAAK,sBAAuB,CAAE,KAAM,EAAK,CAAC,EAC1F,CAEA,IAAI,UAAoB,CACtB,OAAO,KAAK,aACd,CAEA,IAAI,OAAW,CACb,OAAO,KAAK,MAAM,KACpB,CAEA,MAAU,CACR,OAAO,KAAK,MAAM,KAAK,CACzB,CAEA,UAAU,EAAmC,CAC3C,OAAO,KAAK,MAAM,UAAU,CAAQ,CACtC,CAEA,SAAgB,CACd,GAAI,KAAK,cAAe,OACxB,KAAK,cAAgB,GAEjB,KAAK,gBAAkB,KAAK,uBAC9B,KAAK,eAAe,oBAAoB,QAAS,KAAK,qBAAqB,EAG7E,IAAM,EAAU,KAAK,QACrB,KAAK,QAAU,IAAA,GACf,KAAK,eAAiB,IAAA,GACtB,KAAK,sBAAwB,IAAA,GAE7B,GAAI,CACF,IAAU,CACZ,QAAU,CACR,KAAK,gBAAgB,MAAM,CAC7B,CACF,CAEA,CAAC,OAAO,UAAiB,CACvB,KAAK,QAAQ,CACf,CACF,ECtFA,SAAgB,EAAkB,EAAkB,EAA8D,CAEhH,IAAM,EADS,EAAqB,CACF,CAAA,CAAO,eACzC,GAAI,OAAO,GAA8B,WACvC,MAAM,IAAI,EAAyB,sDAAsD,EAG3F,OAAO,EACL,CACE,aAAc,KACd,GAAG,CACL,EACC,GAAW,CACV,IAAM,EAAW,IAAI,EAA2B,GAAY,CAC1D,IAAM,EAAQ,EAAQ,GAAG,EAAE,EACtB,GAEL,EAAO,CACL,OAAQ,EAAM,YAAY,OAC1B,MAAO,EAAM,YAAY,KAC3B,CAAC,CACH,CAAC,EAED,GAAI,CACF,EAAS,QAAQ,EAAS,CAAE,IAAK,aAAc,CAAC,CAClD,OAAS,EAAO,CAEd,MADA,EAAS,WAAW,EACd,CACR,CAEA,UAAa,EAAS,WAAW,CACnC,CACF,CACF,CC1BA,SAAgB,EACd,EACA,EACoC,CAEpC,IAAM,EADS,EAAqB,CACI,CAAA,CAAO,qBAC/C,GAAI,OAAO,GAAoC,WAC7C,MAAM,IAAI,EAAyB,4DAA4D,EAGjG,OAAO,EACL,CACE,aAAc,KACd,GAAG,CACL,EACC,GAAW,CACV,IAAM,EAAW,IAAI,EAClB,GAAY,CACX,IAAM,EAAQ,EAAQ,GAAG,EAAE,EACtB,GAEL,EAAO,CACL,kBAAmB,EAAM,kBACzB,eAAgB,EAAM,cACxB,CAAC,CACH,EACA,CACE,KAAM,GAAS,KACf,WAAY,GAAS,WACrB,aAAc,GAAS,aACvB,UAAW,GAAS,SACtB,CACF,EAEA,GAAI,CACF,EAAS,QAAQ,CAAO,CAC1B,OAAS,EAAO,CAEd,MADA,EAAS,WAAW,EACd,CACR,CAEA,UAAa,EAAS,WAAW,CACnC,CACF,CACF,CCnDA,SAAgB,EAAiB,EAAe,EAA4D,CAC1G,IAAM,EAAS,EAAc,GAAS,MAAM,EAC5C,GAAI,OAAO,EAAO,YAAe,WAC/B,MAAM,IAAI,EAAyB,0CAA0C,EAAM,GAAG,EAGxF,IAAM,EAAa,EAAO,WAAW,CAAK,EACpC,OAA+B,CAAE,QAAS,EAAW,OAAQ,GAEnE,OAAO,EACL,CACE,aAAc,EAAK,EACnB,GAAG,CACL,EACC,GAAW,EAAiB,MAAkB,EAAO,EAAK,CAAC,CAAC,CAC/D,CACF,CCNA,SAAS,EAAoB,EAAuE,CAGlG,OAFK,EAEE,CACL,SAAU,EAAW,SACrB,cAAe,EAAW,cAC1B,IAAK,EAAW,IAChB,SAAU,EAAW,QACvB,EAPwB,IAQ1B,CAEA,SAAgB,EAAc,EAAyD,CACrF,IAAM,EAAS,EAAc,GAAS,MAAM,EACtC,EAAY,EAAO,UACnB,OAA4B,CAChC,WAAY,EAAoB,EAAU,UAAU,EACpD,OAAQ,EAAU,MACpB,GAEA,OAAO,EACL,CACE,aAAc,EAAK,EACnB,GAAG,CACL,EACC,GAAW,CACV,IAAM,MAAiB,EAAO,EAAK,CAAC,EAC9B,EAAa,EAAU,WAM7B,OAJA,EAAO,iBAAiB,SAAU,CAAQ,EAC1C,EAAO,iBAAiB,UAAW,CAAQ,EAC3C,GAAY,iBAAiB,SAAU,CAAQ,MAElC,CACX,EAAO,oBAAoB,SAAU,CAAQ,EAC7C,EAAO,oBAAoB,UAAW,CAAQ,EAC9C,GAAY,oBAAoB,SAAU,CAAQ,CACpD,CACF,CACF,CACF,CClDA,SAAgB,EAAe,EAA0D,CACvF,IAAM,EAAS,EAAc,GAAS,MAAM,EACtC,OAA6B,CACjC,IAAK,EAAO,iBACZ,OAAQ,EAAO,YACf,MAAO,EAAO,UAChB,GAEA,OAAO,EACL,CACE,aAAc,EAAK,EACnB,GAAG,CACL,EACC,GAAW,CACV,IAAM,MAAiB,EAAO,EAAK,CAAC,EAChC,EAEE,MAAmB,CACvB,IAAqB,EACrB,EAAqB,IAAA,GAEjB,OAAO,EAAO,YAAe,aAGjC,EAAqB,EADF,EAAO,WAAW,gBAAgB,EAAO,iBAAiB,MACvC,MAAkB,CACtD,EAAO,EAAK,CAAC,EACb,EAAW,CACb,CAAC,EACH,EAKA,OAHA,EAAO,iBAAiB,SAAU,CAAQ,EAC1C,EAAW,MAEE,CACX,EAAO,oBAAoB,SAAU,CAAQ,EAC7C,IAAqB,CACvB,CACF,CACF,CACF"}
1
+ {"version":3,"file":"sentinel.js","names":[],"sources":["../src/errors.ts","../src/core.ts","../src/_platform.ts","../src/element-size.ts","../src/fullscreen.ts","../src/intersection.ts","../src/media-query.ts","../src/network.ts","../src/viewport.ts","../src/wake-lock.ts"],"sourcesContent":["export class SentinelError extends Error {\n constructor(message: string, options?: ErrorOptions) {\n super(message, options);\n this.name = new.target.name;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\nexport class SentinelUnavailableError extends SentinelError {}\n","import { type Tapper, tapper } from '@vielzeug/arsenal';\n\nimport { SentinelError } from './errors.ts';\nimport type { Sentinel, SentinelEvent, SentinelOptions, Unsubscribe } from './types.ts';\n\nexport interface CreateSentinelOptions<T> extends SentinelOptions {\n readonly initialValue: T;\n}\n\nexport function createSentinel<T>(\n options: CreateSentinelOptions<T>,\n setup: (update: (value: T) => void) => () => void,\n): Sentinel<T> {\n return new SentinelHandle(options, setup);\n}\n\nclass SentinelHandle<T> implements Sentinel<T> {\n readonly disposalSignal: AbortSignal;\n private readonly abortController = new AbortController();\n private currentValue: T;\n private readonly listeners = new Set<() => void>();\n private cleanup: (() => void) | undefined;\n private disposedValue = false;\n private externalSignal: AbortSignal | undefined;\n private externalAbortListener: (() => void) | undefined;\n private readonly tapper: Tapper<SentinelEvent> = tapper<SentinelEvent>();\n\n constructor(options: CreateSentinelOptions<T>, setup: (update: (value: T) => void) => () => void) {\n if (options.signal?.aborted) {\n throw new SentinelError('Sentinel signal is already aborted');\n }\n\n this.currentValue = options.initialValue;\n this.disposalSignal = this.abortController.signal;\n\n try {\n this.cleanup = setup((value) => {\n if (!this.disposedValue) {\n this.currentValue = value;\n this.notify();\n }\n });\n } catch (error) {\n this.disposedValue = true;\n this.abortController.abort();\n throw error;\n }\n\n if (!options.signal) return;\n\n this.externalSignal = options.signal;\n this.externalAbortListener = () => this.dispose();\n this.externalSignal.addEventListener('abort', this.externalAbortListener, { once: true });\n }\n\n get disposed(): boolean {\n return this.disposedValue;\n }\n\n readonly getSnapshot = (): T => this.currentValue;\n\n readonly subscribe = (listener: () => void): Unsubscribe => {\n if (this.disposedValue) return () => {};\n this.listeners.add(listener);\n return () => this.listeners.delete(listener);\n };\n\n readonly dispose = (): void => {\n if (this.disposedValue) return;\n this.disposedValue = true;\n\n if (this.externalSignal && this.externalAbortListener) {\n this.externalSignal.removeEventListener('abort', this.externalAbortListener);\n }\n\n const cleanup = this.cleanup;\n this.cleanup = undefined;\n this.externalSignal = undefined;\n this.externalAbortListener = undefined;\n this.listeners.clear();\n\n try {\n cleanup?.();\n } finally {\n this.tapper.emit({ type: 'dispose' });\n this.tapper.clear();\n this.abortController.abort();\n }\n };\n\n readonly tap = (handler: (event: SentinelEvent) => void, options?: { readonly signal?: AbortSignal }): Unsubscribe =>\n this.tapper.tap(handler, options);\n\n private notify(): void {\n for (const listener of [...this.listeners]) {\n try {\n listener();\n } catch (error) {\n this.tapper.emit({ error, type: 'error' });\n queueMicrotask(() => {\n throw error;\n });\n }\n }\n }\n\n [Symbol.dispose](): void {\n this.dispose();\n }\n}\n","import { SentinelUnavailableError } from './errors.ts';\n\ntype LegacyMediaQueryList = MediaQueryList & {\n addListener?: (listener: (event: MediaQueryListEvent) => void) => void;\n removeListener?: (listener: (event: MediaQueryListEvent) => void) => void;\n};\n\nexport type ObserverWindow = Window & {\n readonly IntersectionObserver?: {\n new (callback: IntersectionObserverCallback, options?: IntersectionObserverInit): IntersectionObserver;\n };\n readonly ResizeObserver?: {\n new (callback: ResizeObserverCallback): ResizeObserver;\n };\n};\n\nexport function resolveWindow(target?: Window): Window {\n if (target) return target;\n if (typeof window === 'undefined') {\n throw new SentinelUnavailableError('Window is not available in this environment.');\n }\n\n return window;\n}\n\nexport function resolveElementWindow(element: Element): ObserverWindow {\n const ownerWindow = element.ownerDocument.defaultView;\n if (!ownerWindow) {\n throw new SentinelUnavailableError('The element is not connected to a Window.');\n }\n\n return ownerWindow;\n}\n\nexport function listenMediaQuery(mediaQuery: MediaQueryList, listener: () => void): () => void {\n const onChange = () => listener();\n\n if (typeof mediaQuery.addEventListener === 'function') {\n mediaQuery.addEventListener('change', onChange);\n return () => mediaQuery.removeEventListener('change', onChange);\n }\n\n const legacyMediaQuery = mediaQuery as LegacyMediaQueryList;\n if (typeof legacyMediaQuery.addListener === 'function') {\n legacyMediaQuery.addListener(onChange);\n return () => legacyMediaQuery.removeListener?.(onChange);\n }\n\n return () => {};\n}\n","import { resolveElementWindow } from './_platform.ts';\nimport { createSentinel } from './core.ts';\nimport { SentinelUnavailableError } from './errors.ts';\nimport type { ElementSizeState, Sentinel, SentinelOptions } from './types.ts';\n\nexport function createElementSize(element: Element, options?: SentinelOptions): Sentinel<ElementSizeState | null> {\n const target = resolveElementWindow(element);\n const ResizeObserverConstructor = target.ResizeObserver;\n if (typeof ResizeObserverConstructor !== 'function') {\n throw new SentinelUnavailableError('ResizeObserver is not available in this environment.');\n }\n\n return createSentinel<ElementSizeState | null>(\n {\n initialValue: null,\n ...options,\n },\n (update) => {\n const observer = new ResizeObserverConstructor((entries) => {\n const entry = entries.at(-1);\n if (!entry) return;\n\n update({\n height: entry.contentRect.height,\n width: entry.contentRect.width,\n });\n });\n\n try {\n observer.observe(element, { box: 'content-box' });\n } catch (error) {\n observer.disconnect();\n throw error;\n }\n\n return () => observer.disconnect();\n },\n );\n}\n","import { resolveWindow } from './_platform.ts';\nimport { createSentinel } from './core.ts';\nimport type { FullscreenSentinel, FullscreenState, SentinelOptions } from './types.ts';\n\ntype FullscreenWindow = Window & { document: Document };\n\n/**\n * A sentinel-backed fullscreen view. `request()` enters fullscreen for an element (default:\n * the document element), `exit()` leaves it, and `toggle()` flips it — all degrade silently\n * when the Fullscreen API is missing or the browser refuses (user-activation requirements).\n * Disposing the sentinel exits fullscreen and stops tracking `fullscreenchange`.\n */\nexport function createFullscreen(\n options?: SentinelOptions & { readonly target?: FullscreenWindow },\n): FullscreenSentinel {\n const target = resolveWindow(options?.target);\n const doc = target.document;\n const supported = typeof doc.documentElement?.requestFullscreen === 'function';\n\n const read = (): FullscreenState => ({ active: doc.fullscreenElement !== null, supported });\n let updateState: (value: FullscreenState) => void = () => {};\n\n const request = (element?: Element): void => {\n if (doc.fullscreenElement || !supported) return;\n void (element ?? doc.documentElement).requestFullscreen().catch(() => {\n // Requires user activation in some browsers, or the element may refuse; degrade silently.\n });\n };\n\n const exit = (): void => {\n if (!doc.fullscreenElement) return;\n void doc.exitFullscreen().catch(() => {\n // Degrade silently.\n });\n };\n\n const sentinel = createSentinel<FullscreenState>({ initialValue: read(), ...options }, (update) => {\n updateState = update;\n\n const onFullscreenChange = (): void => updateState(read());\n\n doc.addEventListener('fullscreenchange', onFullscreenChange);\n\n return () => {\n doc.removeEventListener('fullscreenchange', onFullscreenChange);\n if (doc.fullscreenElement) exit();\n };\n });\n\n return {\n ...sentinel,\n exit,\n request,\n toggle(element?: Element): void {\n if (doc.fullscreenElement) exit();\n else request(element);\n },\n };\n}\n","import { resolveElementWindow } from './_platform.ts';\nimport { createSentinel } from './core.ts';\nimport { SentinelUnavailableError } from './errors.ts';\nimport type { IntersectionState, Sentinel, SentinelOptions } from './types.ts';\n\nexport interface CreateIntersectionOptions extends SentinelOptions {\n readonly root?: Element | Document | null;\n readonly rootMargin?: string;\n readonly scrollMargin?: string;\n readonly threshold?: number | number[];\n}\n\nexport function createIntersection(\n element: Element,\n options?: CreateIntersectionOptions,\n): Sentinel<IntersectionState | null> {\n const target = resolveElementWindow(element);\n const IntersectionObserverConstructor = target.IntersectionObserver;\n if (typeof IntersectionObserverConstructor !== 'function') {\n throw new SentinelUnavailableError('IntersectionObserver is not available in this environment.');\n }\n\n return createSentinel<IntersectionState | null>(\n {\n initialValue: null,\n ...options,\n },\n (update) => {\n const observer = new IntersectionObserverConstructor(\n (entries) => {\n const entry = entries.at(-1);\n if (!entry) return;\n\n update({\n intersectionRatio: entry.intersectionRatio,\n isIntersecting: entry.isIntersecting,\n });\n },\n {\n root: options?.root,\n rootMargin: options?.rootMargin,\n scrollMargin: options?.scrollMargin,\n threshold: options?.threshold,\n },\n );\n\n try {\n observer.observe(element);\n } catch (error) {\n observer.disconnect();\n throw error;\n }\n\n return () => observer.disconnect();\n },\n );\n}\n","import { listenMediaQuery, resolveWindow } from './_platform.ts';\nimport { createSentinel } from './core.ts';\nimport { SentinelUnavailableError } from './errors.ts';\nimport type { MediaQueryState, Sentinel, WindowSentinelOptions } from './types.ts';\n\nexport function createMediaQuery(query: string, options?: WindowSentinelOptions): Sentinel<MediaQueryState> {\n const target = resolveWindow(options?.target);\n if (typeof target.matchMedia !== 'function') {\n throw new SentinelUnavailableError(`matchMedia is not available for query \"${query}\".`);\n }\n\n const mediaQuery = target.matchMedia(query);\n const read = (): MediaQueryState => ({ matches: mediaQuery.matches });\n\n return createSentinel(\n {\n initialValue: read(),\n ...options,\n },\n (update) => listenMediaQuery(mediaQuery, () => update(read())),\n );\n}\n","import { resolveWindow } from './_platform.ts';\nimport { createSentinel } from './core.ts';\nimport type { NetworkConnectionSnapshot, NetworkState, Sentinel, WindowSentinelOptions } from './types.ts';\n\ninterface NetworkInformationLike extends EventTarget {\n readonly downlink?: number;\n readonly effectiveType?: NetworkConnectionSnapshot['effectiveType'];\n readonly rtt?: number;\n readonly saveData?: boolean;\n}\n\ntype NavigatorWithConnection = Navigator & {\n readonly connection?: NetworkInformationLike;\n};\n\nfunction normalizeConnection(connection?: NetworkInformationLike): NetworkConnectionSnapshot | null {\n if (!connection) return null;\n\n return {\n downlink: connection.downlink,\n effectiveType: connection.effectiveType,\n rtt: connection.rtt,\n saveData: connection.saveData,\n };\n}\n\nexport function createNetwork(options?: WindowSentinelOptions): Sentinel<NetworkState> {\n const target = resolveWindow(options?.target);\n const navigator = target.navigator as NavigatorWithConnection;\n const read = (): NetworkState => ({\n connection: normalizeConnection(navigator.connection),\n online: navigator.onLine,\n });\n\n return createSentinel(\n {\n initialValue: read(),\n ...options,\n },\n (update) => {\n const onChange = () => update(read());\n const connection = navigator.connection;\n\n target.addEventListener('online', onChange);\n target.addEventListener('offline', onChange);\n connection?.addEventListener('change', onChange);\n\n return () => {\n target.removeEventListener('online', onChange);\n target.removeEventListener('offline', onChange);\n connection?.removeEventListener('change', onChange);\n };\n },\n );\n}\n","import { listenMediaQuery, resolveWindow } from './_platform.ts';\nimport { createSentinel } from './core.ts';\nimport type { Sentinel, ViewportState, WindowSentinelOptions } from './types.ts';\n\nexport function createViewport(options?: WindowSentinelOptions): Sentinel<ViewportState> {\n const target = resolveWindow(options?.target);\n const read = (): ViewportState => ({\n dpr: target.devicePixelRatio,\n height: target.innerHeight,\n width: target.innerWidth,\n });\n\n return createSentinel(\n {\n initialValue: read(),\n ...options,\n },\n (update) => {\n const onResize = () => update(read());\n let stopDprObservation: (() => void) | undefined;\n\n const observeDpr = () => {\n stopDprObservation?.();\n stopDprObservation = undefined;\n\n if (typeof target.matchMedia !== 'function') return;\n\n const mediaQuery = target.matchMedia(`(resolution: ${target.devicePixelRatio}dppx)`);\n stopDprObservation = listenMediaQuery(mediaQuery, () => {\n update(read());\n observeDpr();\n });\n };\n\n target.addEventListener('resize', onResize);\n observeDpr();\n\n return () => {\n target.removeEventListener('resize', onResize);\n stopDprObservation?.();\n };\n },\n );\n}\n","import { resolveWindow } from './_platform.ts';\nimport { createSentinel } from './core.ts';\nimport type { SentinelOptions, WakeLockSentinel, WakeLockState } from './types.ts';\n\ntype WakeLockWindow = Window & { document: Document };\n\nexport function createWakeLock(options?: SentinelOptions & { readonly target?: WakeLockWindow }): WakeLockSentinel {\n const target = resolveWindow(options?.target);\n const navigator = target.navigator;\n const supported = typeof navigator.wakeLock?.request === 'function';\n\n let handle: globalThis.WakeLockSentinel | null = null;\n let pending = false;\n let wantsLock = false;\n\n const read = (): WakeLockState => ({ active: handle !== null, supported });\n let updateState: (value: WakeLockState) => void = () => {};\n\n async function acquire(): Promise<void> {\n if (!supported || !navigator.wakeLock) return;\n pending = true;\n try {\n const requested = await navigator.wakeLock.request('screen');\n // release()/dispose() during the pending request must not leave the lock held.\n if (!wantsLock) {\n void requested.release();\n\n return;\n }\n handle = requested;\n handle.addEventListener('release', () => {\n handle = null;\n updateState(read());\n });\n updateState(read());\n } catch {\n // Not supported, denied, or not in a secure context — degrade silently.\n } finally {\n pending = false;\n }\n }\n\n const sentinel = createSentinel<WakeLockState>({ initialValue: read(), ...options }, (update) => {\n updateState = update;\n\n const onVisibilityChange = (): void => {\n if (target.document.visibilityState === 'visible' && wantsLock && !handle && !pending) {\n void acquire();\n }\n };\n\n target.document.addEventListener('visibilitychange', onVisibilityChange);\n\n return () => {\n target.document.removeEventListener('visibilitychange', onVisibilityChange);\n if (handle) void handle.release();\n handle = null;\n wantsLock = false;\n };\n });\n\n return {\n ...sentinel,\n release(): void {\n wantsLock = false;\n if (handle) {\n void handle.release();\n handle = null;\n updateState(read());\n }\n },\n request(): void {\n if (handle || pending || !supported) return;\n wantsLock = true;\n void acquire();\n },\n };\n}\n"],"mappings":"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"}
package/dist/types.d.ts CHANGED
@@ -1,8 +1,33 @@
1
- import type { Disposable, Readable, Ripple } from '@vielzeug/ripple';
2
- export interface Sentinel<T> extends Readable<T>, Disposable {
1
+ export type Unsubscribe = () => void;
2
+ /** Runtime events emitted by {@link Sentinel.tap}. Handler errors are swallowed. */
3
+ export type SentinelEvent = {
4
+ readonly error: unknown;
5
+ readonly type: 'error';
6
+ } | {
7
+ readonly type: 'dispose';
8
+ };
9
+ /**
10
+ * Read-only observable contract: `getSnapshot()` reads current state,
11
+ * `subscribe()` registers a change listener. Structural by design; matches
12
+ * `Subscribable` in `@vielzeug/arsenal`.
13
+ */
14
+ export interface Subscribable<T> {
15
+ getSnapshot(): T;
16
+ subscribe(listener: () => void): Unsubscribe;
17
+ }
18
+ export interface Disposable {
19
+ readonly disposalSignal: AbortSignal;
20
+ dispose(): void;
21
+ readonly disposed: boolean;
22
+ [Symbol.dispose](): void;
23
+ }
24
+ export interface Sentinel<T> extends Subscribable<T>, Disposable {
25
+ /** Observe runtime errors from listener dispatch. Returns an unsubscribe function. */
26
+ tap(handler: (event: SentinelEvent) => void, options?: {
27
+ readonly signal?: AbortSignal;
28
+ }): Unsubscribe;
3
29
  }
4
30
  export interface SentinelOptions {
5
- readonly runtime?: Pick<Ripple, 'signal'>;
6
31
  readonly signal?: AbortSignal;
7
32
  }
8
33
  export interface WindowSentinelOptions extends SentinelOptions {
@@ -34,4 +59,40 @@ export interface IntersectionState {
34
59
  readonly intersectionRatio: number;
35
60
  readonly isIntersecting: boolean;
36
61
  }
62
+ export interface WakeLockState {
63
+ /** Whether a wake lock is currently held. */
64
+ readonly active: boolean;
65
+ /** Whether the Screen Wake Lock API is available in this environment. */
66
+ readonly supported: boolean;
67
+ }
68
+ /**
69
+ * A sentinel-backed screen wake lock. `request()` acquires the lock; `release()` frees it.
70
+ * When the tab is hidden the browser auto-releases the lock; `request()` re-acquires it
71
+ * automatically on `visibilitychange` if the lock was active.
72
+ */
73
+ export interface WakeLockSentinel extends Sentinel<WakeLockState> {
74
+ /** Release the wake lock. No-op if not active. */
75
+ release(): void;
76
+ /** Acquire the screen wake lock. No-op if already active or unsupported. */
77
+ request(): void;
78
+ }
79
+ export interface FullscreenState {
80
+ /** Whether an element is currently shown fullscreen. */
81
+ readonly active: boolean;
82
+ /** Whether the Fullscreen API is available in this environment. */
83
+ readonly supported: boolean;
84
+ }
85
+ /**
86
+ * A sentinel-backed fullscreen view. `request()` enters fullscreen for an element (default:
87
+ * the document element), `exit()` leaves it, `toggle()` flips it. All degrade silently when
88
+ * the API is missing or the browser refuses. Disposing the sentinel exits fullscreen.
89
+ */
90
+ export interface FullscreenSentinel extends Sentinel<FullscreenState> {
91
+ /** Leave fullscreen. No-op when not active. */
92
+ exit(): void;
93
+ /** Enter fullscreen for `element` (default: the document element). No-op when already active or unsupported. */
94
+ request(element?: Element): void;
95
+ /** Flip fullscreen for `element` (default: the document element). */
96
+ toggle(element?: Element): void;
97
+ }
37
98
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAErE,MAAM,WAAW,QAAQ,CAAC,CAAC,CAAE,SAAQ,QAAQ,CAAC,CAAC,CAAC,EAAE,UAAU;CAAG;AAE/D,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,OAAO,CAAC,EAAE,IAAI,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IAC1C,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC;CAC/B;AAED,MAAM,WAAW,qBAAsB,SAAQ,eAAe;IAC5D,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,yBAAyB;IACxC,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,aAAa,CAAC,EAAE,SAAS,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;IACxD,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;CAC7B;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,UAAU,EAAE,yBAAyB,GAAG,IAAI,CAAC;IACtD,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;CAC1B;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;CAC3B;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;IACnC,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;CAClC"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,WAAW,GAAG,MAAM,IAAI,CAAC;AAErC,oFAAoF;AACpF,MAAM,MAAM,aAAa,GAAG;IAAE,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAA;CAAE,GAAG;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAA;CAAE,CAAC;AAE/G;;;;GAIG;AACH,MAAM,WAAW,YAAY,CAAC,CAAC;IAC7B,WAAW,IAAI,CAAC,CAAC;IACjB,SAAS,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,WAAW,CAAC;CAC9C;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,cAAc,EAAE,WAAW,CAAC;IACrC,OAAO,IAAI,IAAI,CAAC;IAChB,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC;CAC1B;AAED,MAAM,WAAW,QAAQ,CAAC,CAAC,CAAE,SAAQ,YAAY,CAAC,CAAC,CAAC,EAAE,UAAU;IAC9D,sFAAsF;IACtF,GAAG,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,EAAE,OAAO,CAAC,EAAE;QAAE,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAA;KAAE,GAAG,WAAW,CAAC;CACxG;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC;CAC/B;AAED,MAAM,WAAW,qBAAsB,SAAQ,eAAe;IAC5D,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,yBAAyB;IACxC,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,aAAa,CAAC,EAAE,SAAS,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;IACxD,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;CAC7B;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,UAAU,EAAE,yBAAyB,GAAG,IAAI,CAAC;IACtD,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;CAC1B;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;CAC3B;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;IACnC,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;CAClC;AAED,MAAM,WAAW,aAAa;IAC5B,6CAA6C;IAC7C,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,yEAAyE;IACzE,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;CAC7B;AAED;;;;GAIG;AACH,MAAM,WAAW,gBAAiB,SAAQ,QAAQ,CAAC,aAAa,CAAC;IAC/D,kDAAkD;IAClD,OAAO,IAAI,IAAI,CAAC;IAChB,4EAA4E;IAC5E,OAAO,IAAI,IAAI,CAAC;CACjB;AAED,MAAM,WAAW,eAAe;IAC9B,wDAAwD;IACxD,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,mEAAmE;IACnE,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;CAC7B;AAED;;;;GAIG;AACH,MAAM,WAAW,kBAAmB,SAAQ,QAAQ,CAAC,eAAe,CAAC;IACnE,+CAA+C;IAC/C,IAAI,IAAI,IAAI,CAAC;IACb,gHAAgH;IAChH,OAAO,CAAC,OAAO,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IACjC,qEAAqE;IACrE,MAAM,CAAC,OAAO,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;CACjC"}
package/dist/viewport.cjs CHANGED
@@ -1,2 +1,2 @@
1
- const e=require("./_platform.cjs"),t=require("./core.cjs");function n(n){let r=e.resolveWindow(n?.target),i=()=>({dpr:r.devicePixelRatio,height:r.innerHeight,width:r.innerWidth});return t.createSentinel({initialValue:i(),...n},t=>{let n=()=>t(i()),a,o=()=>{if(a?.(),a=void 0,typeof r.matchMedia!=`function`)return;let n=r.matchMedia(`(resolution: ${r.devicePixelRatio}dppx)`);a=e.listenMediaQuery(n,()=>{t(i()),o()})};return r.addEventListener(`resize`,n),o(),()=>{r.removeEventListener(`resize`,n),a?.()}})}exports.createViewport=n;
1
+ const e=require("./core.cjs"),t=require("./_platform.cjs");function n(n){let r=t.resolveWindow(n?.target),i=()=>({dpr:r.devicePixelRatio,height:r.innerHeight,width:r.innerWidth});return e.createSentinel({initialValue:i(),...n},e=>{let n=()=>e(i()),a,o=()=>{if(a?.(),a=void 0,typeof r.matchMedia!=`function`)return;let n=r.matchMedia(`(resolution: ${r.devicePixelRatio}dppx)`);a=t.listenMediaQuery(n,()=>{e(i()),o()})};return r.addEventListener(`resize`,n),o(),()=>{r.removeEventListener(`resize`,n),a?.()}})}exports.createViewport=n;
2
2
  //# sourceMappingURL=viewport.cjs.map
package/dist/viewport.js CHANGED
@@ -1,2 +1,2 @@
1
- import{listenMediaQuery as e,resolveWindow as t}from"./_platform.js";import{createSentinel as n}from"./core.js";function r(r){let i=t(r?.target),a=()=>({dpr:i.devicePixelRatio,height:i.innerHeight,width:i.innerWidth});return n({initialValue:a(),...r},t=>{let n=()=>t(a()),r,o=()=>{if(r?.(),r=void 0,typeof i.matchMedia!=`function`)return;let n=i.matchMedia(`(resolution: ${i.devicePixelRatio}dppx)`);r=e(n,()=>{t(a()),o()})};return i.addEventListener(`resize`,n),o(),()=>{i.removeEventListener(`resize`,n),r?.()}})}export{r as createViewport};
1
+ import{createSentinel as e}from"./core.js";import{listenMediaQuery as t,resolveWindow as n}from"./_platform.js";function r(r){let i=n(r?.target),a=()=>({dpr:i.devicePixelRatio,height:i.innerHeight,width:i.innerWidth});return e({initialValue:a(),...r},e=>{let n=()=>e(a()),r,o=()=>{if(r?.(),r=void 0,typeof i.matchMedia!=`function`)return;let n=i.matchMedia(`(resolution: ${i.devicePixelRatio}dppx)`);r=t(n,()=>{e(a()),o()})};return i.addEventListener(`resize`,n),o(),()=>{i.removeEventListener(`resize`,n),r?.()}})}export{r as createViewport};
2
2
  //# sourceMappingURL=viewport.js.map
@@ -0,0 +1,2 @@
1
+ const e=require("./core.cjs"),t=require("./_platform.cjs");function n(n){let r=t.resolveWindow(n?.target),i=r.navigator,a=typeof i.wakeLock?.request==`function`,o=null,s=!1,c=!1,l=()=>({active:o!==null,supported:a}),u=()=>{};async function d(){if(a&&i.wakeLock){s=!0;try{let e=await i.wakeLock.request(`screen`);if(!c){e.release();return}o=e,o.addEventListener(`release`,()=>{o=null,u(l())}),u(l())}catch{}finally{s=!1}}}return{...e.createSentinel({initialValue:l(),...n},e=>{u=e;let t=()=>{r.document.visibilityState===`visible`&&c&&!o&&!s&&d()};return r.document.addEventListener(`visibilitychange`,t),()=>{r.document.removeEventListener(`visibilitychange`,t),o&&o.release(),o=null,c=!1}}),release(){c=!1,o&&(o.release(),o=null,u(l()))},request(){o||s||!a||(c=!0,d())}}}exports.createWakeLock=n;
2
+ //# sourceMappingURL=wake-lock.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wake-lock.cjs","names":[],"sources":["../src/wake-lock.ts"],"sourcesContent":["import { resolveWindow } from './_platform.ts';\nimport { createSentinel } from './core.ts';\nimport type { SentinelOptions, WakeLockSentinel, WakeLockState } from './types.ts';\n\ntype WakeLockWindow = Window & { document: Document };\n\nexport function createWakeLock(options?: SentinelOptions & { readonly target?: WakeLockWindow }): WakeLockSentinel {\n const target = resolveWindow(options?.target);\n const navigator = target.navigator;\n const supported = typeof navigator.wakeLock?.request === 'function';\n\n let handle: globalThis.WakeLockSentinel | null = null;\n let pending = false;\n let wantsLock = false;\n\n const read = (): WakeLockState => ({ active: handle !== null, supported });\n let updateState: (value: WakeLockState) => void = () => {};\n\n async function acquire(): Promise<void> {\n if (!supported || !navigator.wakeLock) return;\n pending = true;\n try {\n const requested = await navigator.wakeLock.request('screen');\n // release()/dispose() during the pending request must not leave the lock held.\n if (!wantsLock) {\n void requested.release();\n\n return;\n }\n handle = requested;\n handle.addEventListener('release', () => {\n handle = null;\n updateState(read());\n });\n updateState(read());\n } catch {\n // Not supported, denied, or not in a secure context — degrade silently.\n } finally {\n pending = false;\n }\n }\n\n const sentinel = createSentinel<WakeLockState>({ initialValue: read(), ...options }, (update) => {\n updateState = update;\n\n const onVisibilityChange = (): void => {\n if (target.document.visibilityState === 'visible' && wantsLock && !handle && !pending) {\n void acquire();\n }\n };\n\n target.document.addEventListener('visibilitychange', onVisibilityChange);\n\n return () => {\n target.document.removeEventListener('visibilitychange', onVisibilityChange);\n if (handle) void handle.release();\n handle = null;\n wantsLock = false;\n };\n });\n\n return {\n ...sentinel,\n release(): void {\n wantsLock = false;\n if (handle) {\n void handle.release();\n handle = null;\n updateState(read());\n }\n },\n request(): void {\n if (handle || pending || !supported) return;\n wantsLock = true;\n void acquire();\n },\n };\n}\n"],"mappings":"2DAMA,SAAgB,EAAe,EAAoF,CACjH,IAAM,EAAS,EAAA,cAAc,GAAS,MAAM,EACtC,EAAY,EAAO,UACnB,EAAY,OAAO,EAAU,UAAU,SAAY,WAErD,EAA6C,KAC7C,EAAU,GACV,EAAY,GAEV,OAA6B,CAAE,OAAQ,IAAW,KAAM,WAAU,GACpE,MAAoD,CAAC,EAEzD,eAAe,GAAyB,CAClC,GAAC,GAAc,EAAU,SAC7B,GAAU,GACV,GAAI,CACF,IAAM,EAAY,MAAM,EAAU,SAAS,QAAQ,QAAQ,EAE3D,GAAI,CAAC,EAAW,CACd,EAAe,QAAQ,EAEvB,MACF,CACA,EAAS,EACT,EAAO,iBAAiB,cAAiB,CACvC,EAAS,KACT,EAAY,EAAK,CAAC,CACpB,CAAC,EACD,EAAY,EAAK,CAAC,CACpB,MAAQ,CAER,QAAU,CACR,EAAU,EACZ,CAnBU,CAoBZ,CAqBA,MAAO,CACL,GApBe,EAAA,eAA8B,CAAE,aAAc,EAAK,EAAG,GAAG,CAAQ,EAAI,GAAW,CAC/F,EAAc,EAEd,IAAM,MAAiC,CACjC,EAAO,SAAS,kBAAoB,WAAa,GAAa,CAAC,GAAU,CAAC,GAC5E,EAAa,CAEjB,EAIA,OAFA,EAAO,SAAS,iBAAiB,mBAAoB,CAAkB,MAE1D,CACX,EAAO,SAAS,oBAAoB,mBAAoB,CAAkB,EACtE,GAAQ,EAAY,QAAQ,EAChC,EAAS,KACT,EAAY,EACd,CACF,CAGK,EACH,SAAgB,CACd,EAAY,GACR,IACF,EAAY,QAAQ,EACpB,EAAS,KACT,EAAY,EAAK,CAAC,EAEtB,EACA,SAAgB,CACV,GAAU,GAAW,CAAC,IAC1B,EAAY,GACZ,EAAa,EACf,CACF,CACF"}
@@ -0,0 +1,9 @@
1
+ import type { SentinelOptions, WakeLockSentinel } from './types.ts';
2
+ type WakeLockWindow = Window & {
3
+ document: Document;
4
+ };
5
+ export declare function createWakeLock(options?: SentinelOptions & {
6
+ readonly target?: WakeLockWindow;
7
+ }): WakeLockSentinel;
8
+ export {};
9
+ //# sourceMappingURL=wake-lock.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wake-lock.d.ts","sourceRoot":"","sources":["../src/wake-lock.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,gBAAgB,EAAiB,MAAM,YAAY,CAAC;AAEnF,KAAK,cAAc,GAAG,MAAM,GAAG;IAAE,QAAQ,EAAE,QAAQ,CAAA;CAAE,CAAC;AAEtD,wBAAgB,cAAc,CAAC,OAAO,CAAC,EAAE,eAAe,GAAG;IAAE,QAAQ,CAAC,MAAM,CAAC,EAAE,cAAc,CAAA;CAAE,GAAG,gBAAgB,CAuEjH"}
@@ -0,0 +1,2 @@
1
+ import{createSentinel as e}from"./core.js";import{resolveWindow as t}from"./_platform.js";function n(n){let r=t(n?.target),i=r.navigator,a=typeof i.wakeLock?.request==`function`,o=null,s=!1,c=!1,l=()=>({active:o!==null,supported:a}),u=()=>{};async function d(){if(a&&i.wakeLock){s=!0;try{let e=await i.wakeLock.request(`screen`);if(!c){e.release();return}o=e,o.addEventListener(`release`,()=>{o=null,u(l())}),u(l())}catch{}finally{s=!1}}}return{...e({initialValue:l(),...n},e=>{u=e;let t=()=>{r.document.visibilityState===`visible`&&c&&!o&&!s&&d()};return r.document.addEventListener(`visibilitychange`,t),()=>{r.document.removeEventListener(`visibilitychange`,t),o&&o.release(),o=null,c=!1}}),release(){c=!1,o&&(o.release(),o=null,u(l()))},request(){o||s||!a||(c=!0,d())}}}export{n as createWakeLock};
2
+ //# sourceMappingURL=wake-lock.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wake-lock.js","names":[],"sources":["../src/wake-lock.ts"],"sourcesContent":["import { resolveWindow } from './_platform.ts';\nimport { createSentinel } from './core.ts';\nimport type { SentinelOptions, WakeLockSentinel, WakeLockState } from './types.ts';\n\ntype WakeLockWindow = Window & { document: Document };\n\nexport function createWakeLock(options?: SentinelOptions & { readonly target?: WakeLockWindow }): WakeLockSentinel {\n const target = resolveWindow(options?.target);\n const navigator = target.navigator;\n const supported = typeof navigator.wakeLock?.request === 'function';\n\n let handle: globalThis.WakeLockSentinel | null = null;\n let pending = false;\n let wantsLock = false;\n\n const read = (): WakeLockState => ({ active: handle !== null, supported });\n let updateState: (value: WakeLockState) => void = () => {};\n\n async function acquire(): Promise<void> {\n if (!supported || !navigator.wakeLock) return;\n pending = true;\n try {\n const requested = await navigator.wakeLock.request('screen');\n // release()/dispose() during the pending request must not leave the lock held.\n if (!wantsLock) {\n void requested.release();\n\n return;\n }\n handle = requested;\n handle.addEventListener('release', () => {\n handle = null;\n updateState(read());\n });\n updateState(read());\n } catch {\n // Not supported, denied, or not in a secure context — degrade silently.\n } finally {\n pending = false;\n }\n }\n\n const sentinel = createSentinel<WakeLockState>({ initialValue: read(), ...options }, (update) => {\n updateState = update;\n\n const onVisibilityChange = (): void => {\n if (target.document.visibilityState === 'visible' && wantsLock && !handle && !pending) {\n void acquire();\n }\n };\n\n target.document.addEventListener('visibilitychange', onVisibilityChange);\n\n return () => {\n target.document.removeEventListener('visibilitychange', onVisibilityChange);\n if (handle) void handle.release();\n handle = null;\n wantsLock = false;\n };\n });\n\n return {\n ...sentinel,\n release(): void {\n wantsLock = false;\n if (handle) {\n void handle.release();\n handle = null;\n updateState(read());\n }\n },\n request(): void {\n if (handle || pending || !supported) return;\n wantsLock = true;\n void acquire();\n },\n };\n}\n"],"mappings":"0FAMA,SAAgB,EAAe,EAAoF,CACjH,IAAM,EAAS,EAAc,GAAS,MAAM,EACtC,EAAY,EAAO,UACnB,EAAY,OAAO,EAAU,UAAU,SAAY,WAErD,EAA6C,KAC7C,EAAU,GACV,EAAY,GAEV,OAA6B,CAAE,OAAQ,IAAW,KAAM,WAAU,GACpE,MAAoD,CAAC,EAEzD,eAAe,GAAyB,CAClC,GAAC,GAAc,EAAU,SAC7B,GAAU,GACV,GAAI,CACF,IAAM,EAAY,MAAM,EAAU,SAAS,QAAQ,QAAQ,EAE3D,GAAI,CAAC,EAAW,CACd,EAAe,QAAQ,EAEvB,MACF,CACA,EAAS,EACT,EAAO,iBAAiB,cAAiB,CACvC,EAAS,KACT,EAAY,EAAK,CAAC,CACpB,CAAC,EACD,EAAY,EAAK,CAAC,CACpB,MAAQ,CAER,QAAU,CACR,EAAU,EACZ,CAnBU,CAoBZ,CAqBA,MAAO,CACL,GApBe,EAA8B,CAAE,aAAc,EAAK,EAAG,GAAG,CAAQ,EAAI,GAAW,CAC/F,EAAc,EAEd,IAAM,MAAiC,CACjC,EAAO,SAAS,kBAAoB,WAAa,GAAa,CAAC,GAAU,CAAC,GAC5E,EAAa,CAEjB,EAIA,OAFA,EAAO,SAAS,iBAAiB,mBAAoB,CAAkB,MAE1D,CACX,EAAO,SAAS,oBAAoB,mBAAoB,CAAkB,EACtE,GAAQ,EAAY,QAAQ,EAChC,EAAS,KACT,EAAY,EACd,CACF,CAGK,EACH,SAAgB,CACd,EAAY,GACR,IACF,EAAY,QAAQ,EACpB,EAAS,KACT,EAAY,EAAK,CAAC,EAEtB,EACA,SAAgB,CACV,GAAU,GAAW,CAAC,IAC1B,EAAY,GACZ,EAAa,EACf,CACF,CACF"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@vielzeug/sentinel",
3
- "version": "2.0.3",
4
- "description": "Reactive observables for external browser environment state",
3
+ "version": "26.10.0",
4
+ "description": "Subscribable snapshots for external browser environment state",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "https://github.com/helmuthdu/vielzeug",
@@ -30,24 +30,23 @@
30
30
  "registry": "https://registry.npmjs.org/"
31
31
  },
32
32
  "scripts": {
33
- "build": "vite build && pnpm run build:bundle && pnpm run build:types",
33
+ "build": "vite build && pnpm --silent run build:bundle && pnpm --silent run build:types",
34
34
  "build:bundle": "vite build --config vite.bundle.config.ts",
35
35
  "build:types": "tsc -p tsconfig.declarations.json",
36
36
  "fix": "biome check --write src",
37
37
  "lint": "biome ci src",
38
- "prepublishOnly": "pnpm run build",
38
+ "prepublishOnly": "pnpm --silent run build",
39
39
  "test": "vitest",
40
- "test:e2e": "pnpm --filter @vielzeug/ripple build && pnpm run build && playwright test"
40
+ "test:e2e": "pnpm --silent run build && playwright test"
41
41
  },
42
- "peerDependencies": {
43
- "@vielzeug/ripple": "^2.3.0"
42
+ "dependencies": {
43
+ "@vielzeug/arsenal": "26.10.0"
44
44
  },
45
45
  "devDependencies": {
46
- "@playwright/test": "^1.62.1",
47
- "@types/node": "^26.2.0",
48
- "@vielzeug/ripple": "2.3.0",
46
+ "@playwright/test": "^1.63.0",
47
+ "@types/node": "^26.6.2",
49
48
  "typescript": "^6.0.3",
50
- "vite": "^8.2.1",
51
- "vitest": "^4.1.10"
49
+ "vite": "^8.3.0",
50
+ "vitest": "^5.0.1"
52
51
  }
53
52
  }