@vielzeug/sentinel 2.0.2 → 3.0.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}if(t.signal){if(t.signal.aborted){this.dispose();return}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
+ function e(e,n){return new t(e,n)}var t=class{disposalSignal;abortController=new AbortController;currentValue;listeners=new Set;cleanup;disposedValue=!1;externalSignal;externalAbortListener;constructor(e,t){if(this.currentValue=e.initialValue,this.disposalSignal=this.abortController.signal,e.signal?.aborted){this.disposedValue=!0,this.abortController.abort();return}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.abortController.abort()}};notify(){for(let e of[...this.listeners])try{e()}catch(e){queueMicrotask(()=>{throw e})}}[Symbol.dispose](){this.dispose()}};exports.createSentinel=e;
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 signalFactory = options.runtime?.signal ?? createSignal;\n this.state = signalFactory(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 if (options.signal.aborted) {\n this.dispose();\n return;\n }\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,EAAgB,EAAQ,SAAS,QAAU,EAAA,OAIjD,GAHA,KAAK,MAAQ,EAAc,EAAQ,YAAY,EAC/C,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,KAAQ,OAEb,IAAI,EAAQ,OAAO,QAAS,CAC1B,KAAK,QAAQ,EACb,MACF,CAEA,KAAK,eAAiB,EAAQ,OAC9B,KAAK,0BAA8B,KAAK,QAAQ,EAChD,KAAK,eAAe,iBAAiB,QAAS,KAAK,sBAAuB,CAAE,KAAM,EAAK,CAAC,CAJxF,CAKF,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 { Sentinel, 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\n constructor(options: CreateSentinelOptions<T>, setup: (update: (value: T) => void) => () => void) {\n this.currentValue = 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) {\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.abortController.abort();\n }\n };\n\n private notify(): void {\n for (const listener of [...this.listeners]) {\n try {\n listener();\n } catch (error) {\n queueMicrotask(() => {\n throw error;\n });\n }\n }\n }\n\n [Symbol.dispose](): void {\n this.dispose();\n }\n}\n"],"mappings":"AAMA,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,sBAEA,YAAY,EAAmC,EAAmD,CAIhG,GAHA,KAAK,aAAe,EAAQ,aAC5B,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,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,gBAAgB,MAAM,CAC7B,CACF,EAEA,QAAuB,CACrB,IAAK,IAAM,IAAY,CAAC,GAAG,KAAK,SAAS,EACvC,GAAI,CACF,EAAS,CACX,OAAS,EAAO,CACd,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":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,eAAe,EAAe,MAAM,YAAY,CAAC;AAEzE,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}if(t.signal){if(t.signal.aborted){this.dispose();return}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
+ function e(e,n){return new t(e,n)}var t=class{disposalSignal;abortController=new AbortController;currentValue;listeners=new Set;cleanup;disposedValue=!1;externalSignal;externalAbortListener;constructor(e,t){if(this.currentValue=e.initialValue,this.disposalSignal=this.abortController.signal,e.signal?.aborted){this.disposedValue=!0,this.abortController.abort();return}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.abortController.abort()}};notify(){for(let e of[...this.listeners])try{e()}catch(e){queueMicrotask(()=>{throw e})}}[Symbol.dispose](){this.dispose()}};export{e 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 signalFactory = options.runtime?.signal ?? createSignal;\n this.state = signalFactory(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 if (options.signal.aborted) {\n this.dispose();\n return;\n }\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,EAAgB,EAAQ,SAAS,QAAU,EAIjD,GAHA,KAAK,MAAQ,EAAc,EAAQ,YAAY,EAC/C,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,KAAQ,OAEb,IAAI,EAAQ,OAAO,QAAS,CAC1B,KAAK,QAAQ,EACb,MACF,CAEA,KAAK,eAAiB,EAAQ,OAC9B,KAAK,0BAA8B,KAAK,QAAQ,EAChD,KAAK,eAAe,iBAAiB,QAAS,KAAK,sBAAuB,CAAE,KAAM,EAAK,CAAC,CAJxF,CAKF,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 { Sentinel, 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\n constructor(options: CreateSentinelOptions<T>, setup: (update: (value: T) => void) => () => void) {\n this.currentValue = 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) {\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.abortController.abort();\n }\n };\n\n private notify(): void {\n for (const listener of [...this.listeners]) {\n try {\n listener();\n } catch (error) {\n queueMicrotask(() => {\n throw error;\n });\n }\n }\n }\n\n [Symbol.dispose](): void {\n this.dispose();\n }\n}\n"],"mappings":"AAMA,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,sBAEA,YAAY,EAAmC,EAAmD,CAIhG,GAHA,KAAK,aAAe,EAAQ,aAC5B,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,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,gBAAgB,MAAM,CAC7B,CACF,EAEA,QAAuB,CACrB,IAAK,IAAM,IAAY,CAAC,GAAG,KAAK,SAAS,EACvC,GAAI,CACF,EAAS,CACX,OAAS,EAAO,CACd,mBAAqB,CACnB,MAAM,CACR,CAAC,CACH,CAEJ,CAEA,CAAC,OAAO,UAAiB,CACvB,KAAK,QAAQ,CACf,CACF"}
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}if(t.signal){if(t.signal.aborted){this.dispose();return}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`});var e=class extends Error{constructor(e,t){super(e,t),this.name=new.target.name,Object.setPrototypeOf(this,new.target.prototype)}},t=class extends e{};function n(e){if(e)return e;if(typeof window>`u`)throw new t(`Window is not available in this environment.`);return window}function r(e){let n=e.ownerDocument.defaultView;if(!n)throw new t(`The element is not connected to a Window.`);return n}function i(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 a(e,t){return new o(e,t)}var o=class{disposalSignal;abortController=new AbortController;currentValue;listeners=new Set;cleanup;disposedValue=!1;externalSignal;externalAbortListener;constructor(e,t){if(this.currentValue=e.initialValue,this.disposalSignal=this.abortController.signal,e.signal?.aborted){this.disposedValue=!0,this.abortController.abort();return}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.abortController.abort()}};notify(){for(let e of[...this.listeners])try{e()}catch(e){queueMicrotask(()=>{throw e})}}[Symbol.dispose](){this.dispose()}};function s(e,n){let i=r(e).ResizeObserver;if(typeof i!=`function`)throw new t(`ResizeObserver is not available in this environment.`);return a({initialValue:null,...n},t=>{let n=new i(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 c(e,n){let i=r(e).IntersectionObserver;if(typeof i!=`function`)throw new t(`IntersectionObserver is not available in this environment.`);return a({initialValue:null,...n},t=>{let r=new i(e=>{let n=e.at(-1);n&&t({intersectionRatio:n.intersectionRatio,isIntersecting:n.isIntersecting})},{root:n?.root,rootMargin:n?.rootMargin,scrollMargin:n?.scrollMargin,threshold:n?.threshold});try{r.observe(e)}catch(e){throw r.disconnect(),e}return()=>r.disconnect()})}function l(e,r){let o=n(r?.target);if(typeof o.matchMedia!=`function`)throw new t(`matchMedia is not available for query "${e}".`);let s=o.matchMedia(e),c=()=>({matches:s.matches});return a({initialValue:c(),...r},e=>i(s,()=>e(c())))}function u(e){return e?{downlink:e.downlink,effectiveType:e.effectiveType,rtt:e.rtt,saveData:e.saveData}:null}function d(e){let t=n(e?.target),r=t.navigator,i=()=>({connection:u(r.connection),online:r.onLine});return a({initialValue:i(),...e},e=>{let n=()=>e(i()),a=r.connection;return t.addEventListener(`online`,n),t.addEventListener(`offline`,n),a?.addEventListener(`change`,n),()=>{t.removeEventListener(`online`,n),t.removeEventListener(`offline`,n),a?.removeEventListener(`change`,n)}})}function f(e){let t=n(e?.target),r=()=>({dpr:t.devicePixelRatio,height:t.innerHeight,width:t.innerWidth});return a({initialValue:r(),...e},e=>{let n=()=>e(r()),a,o=()=>{a?.(),a=void 0,typeof t.matchMedia==`function`&&(a=i(t.matchMedia(`(resolution: ${t.devicePixelRatio}dppx)`),()=>{e(r()),o()}))};return t.addEventListener(`resize`,n),o(),()=>{t.removeEventListener(`resize`,n),a?.()}})}exports.SentinelError=e,exports.SentinelUnavailableError=t,exports.createElementSize=s,exports.createIntersection=c,exports.createMediaQuery=l,exports.createNetwork=d,exports.createViewport=f;
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 signalFactory = options.runtime?.signal ?? createSignal;\n this.state = signalFactory(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 if (options.signal.aborted) {\n this.dispose();\n return;\n }\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":"qGAAA,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,EAAgB,EAAQ,SAAS,QAAU,EAAA,OAIjD,GAHA,KAAK,MAAQ,EAAc,EAAQ,YAAY,EAC/C,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,KAAQ,OAEb,IAAI,EAAQ,OAAO,QAAS,CAC1B,KAAK,QAAQ,EACb,MACF,CAEA,KAAK,eAAiB,EAAQ,OAC9B,KAAK,0BAA8B,KAAK,QAAQ,EAChD,KAAK,eAAe,iBAAiB,QAAS,KAAK,sBAAuB,CAAE,KAAM,EAAK,CAAC,CAJxF,CAKF,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,EC3FA,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.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 { Sentinel, 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\n constructor(options: CreateSentinelOptions<T>, setup: (update: (value: T) => void) => () => void) {\n this.currentValue = 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) {\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.abortController.abort();\n }\n };\n\n private notify(): void {\n for (const listener of [...this.listeners]) {\n try {\n listener();\n } catch (error) {\n queueMicrotask(() => {\n throw error;\n });\n }\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,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}if(e.signal){if(e.signal.aborted){this.dispose();return}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){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});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;currentValue;listeners=new Set;cleanup;disposedValue=!1;externalSignal;externalAbortListener;constructor(e,t){if(this.currentValue=e.initialValue,this.disposalSignal=this.abortController.signal,e.signal?.aborted){this.disposedValue=!0,this.abortController.abort();return}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.abortController.abort()}};notify(){for(let e of[...this.listeners])try{e()}catch(e){queueMicrotask(()=>{throw e})}}[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?.()}})}return e.SentinelError=t,e.SentinelUnavailableError=n,e.createElementSize=c,e.createIntersection=l,e.createMediaQuery=u,e.createNetwork=f,e.createViewport=p,e})({});
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 signalFactory = options.runtime?.signal ?? createSignal;\n this.state = signalFactory(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 if (options.signal.aborted) {\n this.dispose();\n return;\n }\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.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 { Sentinel, 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\n constructor(options: CreateSentinelOptions<T>, setup: (update: (value: T) => void) => () => void) {\n this.currentValue = 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) {\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.abortController.abort();\n }\n };\n\n private notify(): void {\n for (const listener of [...this.listeners]) {\n try {\n listener();\n } catch (error) {\n queueMicrotask(() => {\n throw error;\n });\n }\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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"}
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}if(t.signal){if(t.signal.aborted){this.dispose();return}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
+ var e=class extends Error{constructor(e,t){super(e,t),this.name=new.target.name,Object.setPrototypeOf(this,new.target.prototype)}},t=class extends e{};function n(e){if(e)return e;if(typeof window>`u`)throw new t(`Window is not available in this environment.`);return window}function r(e){let n=e.ownerDocument.defaultView;if(!n)throw new t(`The element is not connected to a Window.`);return n}function i(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 a(e,t){return new o(e,t)}var o=class{disposalSignal;abortController=new AbortController;currentValue;listeners=new Set;cleanup;disposedValue=!1;externalSignal;externalAbortListener;constructor(e,t){if(this.currentValue=e.initialValue,this.disposalSignal=this.abortController.signal,e.signal?.aborted){this.disposedValue=!0,this.abortController.abort();return}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.abortController.abort()}};notify(){for(let e of[...this.listeners])try{e()}catch(e){queueMicrotask(()=>{throw e})}}[Symbol.dispose](){this.dispose()}};function s(e,n){let i=r(e).ResizeObserver;if(typeof i!=`function`)throw new t(`ResizeObserver is not available in this environment.`);return a({initialValue:null,...n},t=>{let n=new i(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 c(e,n){let i=r(e).IntersectionObserver;if(typeof i!=`function`)throw new t(`IntersectionObserver is not available in this environment.`);return a({initialValue:null,...n},t=>{let r=new i(e=>{let n=e.at(-1);n&&t({intersectionRatio:n.intersectionRatio,isIntersecting:n.isIntersecting})},{root:n?.root,rootMargin:n?.rootMargin,scrollMargin:n?.scrollMargin,threshold:n?.threshold});try{r.observe(e)}catch(e){throw r.disconnect(),e}return()=>r.disconnect()})}function l(e,r){let o=n(r?.target);if(typeof o.matchMedia!=`function`)throw new t(`matchMedia is not available for query "${e}".`);let s=o.matchMedia(e),c=()=>({matches:s.matches});return a({initialValue:c(),...r},e=>i(s,()=>e(c())))}function u(e){return e?{downlink:e.downlink,effectiveType:e.effectiveType,rtt:e.rtt,saveData:e.saveData}:null}function d(e){let t=n(e?.target),r=t.navigator,i=()=>({connection:u(r.connection),online:r.onLine});return a({initialValue:i(),...e},e=>{let n=()=>e(i()),a=r.connection;return t.addEventListener(`online`,n),t.addEventListener(`offline`,n),a?.addEventListener(`change`,n),()=>{t.removeEventListener(`online`,n),t.removeEventListener(`offline`,n),a?.removeEventListener(`change`,n)}})}function f(e){let t=n(e?.target),r=()=>({dpr:t.devicePixelRatio,height:t.innerHeight,width:t.innerWidth});return a({initialValue:r(),...e},e=>{let n=()=>e(r()),a,o=()=>{a?.(),a=void 0,typeof t.matchMedia==`function`&&(a=i(t.matchMedia(`(resolution: ${t.devicePixelRatio}dppx)`),()=>{e(r()),o()}))};return t.addEventListener(`resize`,n),o(),()=>{t.removeEventListener(`resize`,n),a?.()}})}export{e as SentinelError,t as SentinelUnavailableError,s as createElementSize,c as createIntersection,l as createMediaQuery,d as createNetwork,f as createViewport};
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 signalFactory = options.runtime?.signal ?? createSignal;\n this.state = signalFactory(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 if (options.signal.aborted) {\n this.dispose();\n return;\n }\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.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 { Sentinel, 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\n constructor(options: CreateSentinelOptions<T>, setup: (update: (value: T) => void) => () => void) {\n this.currentValue = 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) {\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.abortController.abort();\n }\n };\n\n private notify(): void {\n for (const listener of [...this.listeners]) {\n try {\n listener();\n } catch (error) {\n queueMicrotask(() => {\n throw error;\n });\n }\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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"}
package/dist/types.d.ts CHANGED
@@ -1,12 +1,17 @@
1
- import type { Readable, Ripple } from '@vielzeug/ripple';
2
- export interface Sentinel<T> extends Readable<T> {
1
+ export type Unsubscribe = () => void;
2
+ export interface Readable<T> {
3
+ getSnapshot(): T;
4
+ subscribe(listener: () => void): Unsubscribe;
5
+ }
6
+ export interface Disposable {
3
7
  readonly disposalSignal: AbortSignal;
4
8
  dispose(): void;
5
9
  readonly disposed: boolean;
6
10
  [Symbol.dispose](): void;
7
11
  }
12
+ export interface Sentinel<T> extends Readable<T>, Disposable {
13
+ }
8
14
  export interface SentinelOptions {
9
- readonly runtime?: Pick<Ripple, 'signal'>;
10
15
  readonly signal?: AbortSignal;
11
16
  }
12
17
  export interface WindowSentinelOptions extends SentinelOptions {
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAEzD,MAAM,WAAW,QAAQ,CAAC,CAAC,CAAE,SAAQ,QAAQ,CAAC,CAAC,CAAC;IAC9C,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,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,MAAM,WAAW,QAAQ,CAAC,CAAC;IACzB,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,QAAQ,CAAC,CAAC,CAAC,EAAE,UAAU;CAAG;AAE/D,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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@vielzeug/sentinel",
3
- "version": "2.0.2",
4
- "description": "Reactive observables for external browser environment state",
3
+ "version": "3.0.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,20 @@
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"
41
- },
42
- "peerDependencies": {
43
- "@vielzeug/ripple": "^2.2.4"
40
+ "test:e2e": "pnpm --silent run build && playwright test"
44
41
  },
45
42
  "devDependencies": {
46
- "@playwright/test": "^1.62.1",
47
- "@types/node": "^26.2.0",
48
- "@vielzeug/ripple": "2.2.4",
43
+ "@playwright/test": "^1.63.0",
44
+ "@types/node": "^26.4.1",
49
45
  "typescript": "^6.0.3",
50
- "vite": "^8.2.1",
51
- "vitest": "^4.1.10"
46
+ "vite": "^8.2.2",
47
+ "vitest": "^5.0.0"
52
48
  }
53
49
  }