@vielzeug/sentinel 26.10.0 → 26.10.5

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
@@ -45,4 +45,4 @@ const stopObserving = observeViewport();
45
45
 
46
46
  ## License
47
47
 
48
- MIT © [Helmuth Saatkamp](https://github.com/helmuthdu) — part of the [Vielzeug](https://github.com/helmuthdu/vielzeug) monorepo.
48
+ MIT © [Helmuth Saatkamp](https://github.com/helmuthdu): part of the [Vielzeug](https://github.com/helmuthdu/vielzeug) monorepo.
@@ -1 +1 @@
1
- {"version":3,"file":"fullscreen.cjs","names":[],"sources":["../src/fullscreen.ts"],"sourcesContent":["import { resolveWindow } from './_platform.ts';\nimport { createSentinel } from './core.ts';\nimport type { FullscreenSentinel, FullscreenState, SentinelOptions } from './types.ts';\n\ntype FullscreenWindow = Window & { document: Document };\n\n/**\n * A sentinel-backed fullscreen view. `request()` enters fullscreen for an element (default:\n * the document element), `exit()` leaves it, and `toggle()` flips it — all degrade silently\n * when the Fullscreen API is missing or the browser refuses (user-activation requirements).\n * Disposing the sentinel exits fullscreen and stops tracking `fullscreenchange`.\n */\nexport function createFullscreen(\n options?: SentinelOptions & { readonly target?: FullscreenWindow },\n): FullscreenSentinel {\n const target = resolveWindow(options?.target);\n const doc = target.document;\n const supported = typeof doc.documentElement?.requestFullscreen === 'function';\n\n const read = (): FullscreenState => ({ active: doc.fullscreenElement !== null, supported });\n let updateState: (value: FullscreenState) => void = () => {};\n\n const request = (element?: Element): void => {\n if (doc.fullscreenElement || !supported) return;\n void (element ?? doc.documentElement).requestFullscreen().catch(() => {\n // Requires user activation in some browsers, or the element may refuse; degrade silently.\n });\n };\n\n const exit = (): void => {\n if (!doc.fullscreenElement) return;\n void doc.exitFullscreen().catch(() => {\n // Degrade silently.\n });\n };\n\n const sentinel = createSentinel<FullscreenState>({ initialValue: read(), ...options }, (update) => {\n updateState = update;\n\n const onFullscreenChange = (): void => updateState(read());\n\n doc.addEventListener('fullscreenchange', onFullscreenChange);\n\n return () => {\n doc.removeEventListener('fullscreenchange', onFullscreenChange);\n if (doc.fullscreenElement) exit();\n };\n });\n\n return {\n ...sentinel,\n exit,\n request,\n toggle(element?: Element): void {\n if (doc.fullscreenElement) exit();\n else request(element);\n },\n };\n}\n"],"mappings":"2DAYA,SAAgB,EACd,EACoB,CAEpB,IAAM,EADS,EAAA,cAAc,GAAS,MAC1B,CAAA,CAAO,SACb,EAAY,OAAO,EAAI,iBAAiB,mBAAsB,WAE9D,OAA+B,CAAE,OAAQ,EAAI,oBAAsB,KAAM,WAAU,GACrF,MAAsD,CAAC,EAErD,EAAW,GAA4B,CACvC,GAAI,mBAAsB,IACxB,GAAW,EAAI,gBAAA,CAAiB,kBAAkB,CAAC,CAAC,UAAY,CAEtE,CAAC,CACH,EAEM,MAAmB,CAClB,EAAI,mBACT,EAAS,eAAe,CAAC,CAAC,UAAY,CAEtC,CAAC,CACH,EAeA,MAAO,CACL,GAde,EAAA,eAAgC,CAAE,aAAc,EAAK,EAAG,GAAG,CAAQ,EAAI,GAAW,CACjG,EAAc,EAEd,IAAM,MAAiC,EAAY,EAAK,CAAC,EAIzD,OAFA,EAAI,iBAAiB,mBAAoB,CAAkB,MAE9C,CACX,EAAI,oBAAoB,mBAAoB,CAAkB,EAC1D,EAAI,mBAAmB,EAAK,CAClC,CACF,CAGK,EACH,OACA,UACA,OAAO,EAAyB,CAC1B,EAAI,kBAAmB,EAAK,EAC3B,EAAQ,CAAO,CACtB,CACF,CACF"}
1
+ {"version":3,"file":"fullscreen.cjs","names":[],"sources":["../src/fullscreen.ts"],"sourcesContent":["import { resolveWindow } from './_platform.ts';\nimport { createSentinel } from './core.ts';\nimport type { FullscreenSentinel, FullscreenState, SentinelOptions } from './types.ts';\n\ntype FullscreenWindow = Window & { document: Document };\n\n/**\n * A sentinel-backed fullscreen view. `request()` enters fullscreen for an element (default:\n * the document element), `exit()` leaves it, and `toggle()` flips it: all degrade silently\n * when the Fullscreen API is missing or the browser refuses (user-activation requirements).\n * Disposing the sentinel exits fullscreen and stops tracking `fullscreenchange`.\n */\nexport function createFullscreen(\n options?: SentinelOptions & { readonly target?: FullscreenWindow },\n): FullscreenSentinel {\n const target = resolveWindow(options?.target);\n const doc = target.document;\n const supported = typeof doc.documentElement?.requestFullscreen === 'function';\n\n const read = (): FullscreenState => ({ active: doc.fullscreenElement !== null, supported });\n let updateState: (value: FullscreenState) => void = () => {};\n\n const request = (element?: Element): void => {\n if (doc.fullscreenElement || !supported) return;\n void (element ?? doc.documentElement).requestFullscreen().catch(() => {\n // Requires user activation in some browsers, or the element may refuse; degrade silently.\n });\n };\n\n const exit = (): void => {\n if (!doc.fullscreenElement) return;\n void doc.exitFullscreen().catch(() => {\n // Degrade silently.\n });\n };\n\n const sentinel = createSentinel<FullscreenState>({ initialValue: read(), ...options }, (update) => {\n updateState = update;\n\n const onFullscreenChange = (): void => updateState(read());\n\n doc.addEventListener('fullscreenchange', onFullscreenChange);\n\n return () => {\n doc.removeEventListener('fullscreenchange', onFullscreenChange);\n if (doc.fullscreenElement) exit();\n };\n });\n\n return {\n ...sentinel,\n exit,\n request,\n toggle(element?: Element): void {\n if (doc.fullscreenElement) exit();\n else request(element);\n },\n };\n}\n"],"mappings":"2DAYA,SAAgB,EACd,EACoB,CAEpB,IAAM,EADS,EAAA,cAAc,GAAS,MAC1B,CAAA,CAAO,SACb,EAAY,OAAO,EAAI,iBAAiB,mBAAsB,WAE9D,OAA+B,CAAE,OAAQ,EAAI,oBAAsB,KAAM,WAAU,GACrF,MAAsD,CAAC,EAErD,EAAW,GAA4B,CACvC,GAAI,mBAAsB,IACxB,GAAW,EAAI,gBAAA,CAAiB,kBAAkB,CAAC,CAAC,UAAY,CAEtE,CAAC,CACH,EAEM,MAAmB,CAClB,EAAI,mBACT,EAAS,eAAe,CAAC,CAAC,UAAY,CAEtC,CAAC,CACH,EAeA,MAAO,CACL,GAde,EAAA,eAAgC,CAAE,aAAc,EAAK,EAAG,GAAG,CAAQ,EAAI,GAAW,CACjG,EAAc,EAEd,IAAM,MAAiC,EAAY,EAAK,CAAC,EAIzD,OAFA,EAAI,iBAAiB,mBAAoB,CAAkB,MAE9C,CACX,EAAI,oBAAoB,mBAAoB,CAAkB,EAC1D,EAAI,mBAAmB,EAAK,CAClC,CACF,CAGK,EACH,OACA,UACA,OAAO,EAAyB,CAC1B,EAAI,kBAAmB,EAAK,EAC3B,EAAQ,CAAO,CACtB,CACF,CACF"}
@@ -4,7 +4,7 @@ type FullscreenWindow = Window & {
4
4
  };
5
5
  /**
6
6
  * A sentinel-backed fullscreen view. `request()` enters fullscreen for an element (default:
7
- * the document element), `exit()` leaves it, and `toggle()` flips it — all degrade silently
7
+ * the document element), `exit()` leaves it, and `toggle()` flips it: all degrade silently
8
8
  * when the Fullscreen API is missing or the browser refuses (user-activation requirements).
9
9
  * Disposing the sentinel exits fullscreen and stops tracking `fullscreenchange`.
10
10
  */
@@ -1 +1 @@
1
- {"version":3,"file":"fullscreen.js","names":[],"sources":["../src/fullscreen.ts"],"sourcesContent":["import { resolveWindow } from './_platform.ts';\nimport { createSentinel } from './core.ts';\nimport type { FullscreenSentinel, FullscreenState, SentinelOptions } from './types.ts';\n\ntype FullscreenWindow = Window & { document: Document };\n\n/**\n * A sentinel-backed fullscreen view. `request()` enters fullscreen for an element (default:\n * the document element), `exit()` leaves it, and `toggle()` flips it — all degrade silently\n * when the Fullscreen API is missing or the browser refuses (user-activation requirements).\n * Disposing the sentinel exits fullscreen and stops tracking `fullscreenchange`.\n */\nexport function createFullscreen(\n options?: SentinelOptions & { readonly target?: FullscreenWindow },\n): FullscreenSentinel {\n const target = resolveWindow(options?.target);\n const doc = target.document;\n const supported = typeof doc.documentElement?.requestFullscreen === 'function';\n\n const read = (): FullscreenState => ({ active: doc.fullscreenElement !== null, supported });\n let updateState: (value: FullscreenState) => void = () => {};\n\n const request = (element?: Element): void => {\n if (doc.fullscreenElement || !supported) return;\n void (element ?? doc.documentElement).requestFullscreen().catch(() => {\n // Requires user activation in some browsers, or the element may refuse; degrade silently.\n });\n };\n\n const exit = (): void => {\n if (!doc.fullscreenElement) return;\n void doc.exitFullscreen().catch(() => {\n // Degrade silently.\n });\n };\n\n const sentinel = createSentinel<FullscreenState>({ initialValue: read(), ...options }, (update) => {\n updateState = update;\n\n const onFullscreenChange = (): void => updateState(read());\n\n doc.addEventListener('fullscreenchange', onFullscreenChange);\n\n return () => {\n doc.removeEventListener('fullscreenchange', onFullscreenChange);\n if (doc.fullscreenElement) exit();\n };\n });\n\n return {\n ...sentinel,\n exit,\n request,\n toggle(element?: Element): void {\n if (doc.fullscreenElement) exit();\n else request(element);\n },\n };\n}\n"],"mappings":"0FAYA,SAAgB,EACd,EACoB,CAEpB,IAAM,EADS,EAAc,GAAS,MAC1B,CAAA,CAAO,SACb,EAAY,OAAO,EAAI,iBAAiB,mBAAsB,WAE9D,OAA+B,CAAE,OAAQ,EAAI,oBAAsB,KAAM,WAAU,GACrF,MAAsD,CAAC,EAErD,EAAW,GAA4B,CACvC,GAAI,mBAAsB,IACxB,GAAW,EAAI,gBAAA,CAAiB,kBAAkB,CAAC,CAAC,UAAY,CAEtE,CAAC,CACH,EAEM,MAAmB,CAClB,EAAI,mBACT,EAAS,eAAe,CAAC,CAAC,UAAY,CAEtC,CAAC,CACH,EAeA,MAAO,CACL,GAde,EAAgC,CAAE,aAAc,EAAK,EAAG,GAAG,CAAQ,EAAI,GAAW,CACjG,EAAc,EAEd,IAAM,MAAiC,EAAY,EAAK,CAAC,EAIzD,OAFA,EAAI,iBAAiB,mBAAoB,CAAkB,MAE9C,CACX,EAAI,oBAAoB,mBAAoB,CAAkB,EAC1D,EAAI,mBAAmB,EAAK,CAClC,CACF,CAGK,EACH,OACA,UACA,OAAO,EAAyB,CAC1B,EAAI,kBAAmB,EAAK,EAC3B,EAAQ,CAAO,CACtB,CACF,CACF"}
1
+ {"version":3,"file":"fullscreen.js","names":[],"sources":["../src/fullscreen.ts"],"sourcesContent":["import { resolveWindow } from './_platform.ts';\nimport { createSentinel } from './core.ts';\nimport type { FullscreenSentinel, FullscreenState, SentinelOptions } from './types.ts';\n\ntype FullscreenWindow = Window & { document: Document };\n\n/**\n * A sentinel-backed fullscreen view. `request()` enters fullscreen for an element (default:\n * the document element), `exit()` leaves it, and `toggle()` flips it: all degrade silently\n * when the Fullscreen API is missing or the browser refuses (user-activation requirements).\n * Disposing the sentinel exits fullscreen and stops tracking `fullscreenchange`.\n */\nexport function createFullscreen(\n options?: SentinelOptions & { readonly target?: FullscreenWindow },\n): FullscreenSentinel {\n const target = resolveWindow(options?.target);\n const doc = target.document;\n const supported = typeof doc.documentElement?.requestFullscreen === 'function';\n\n const read = (): FullscreenState => ({ active: doc.fullscreenElement !== null, supported });\n let updateState: (value: FullscreenState) => void = () => {};\n\n const request = (element?: Element): void => {\n if (doc.fullscreenElement || !supported) return;\n void (element ?? doc.documentElement).requestFullscreen().catch(() => {\n // Requires user activation in some browsers, or the element may refuse; degrade silently.\n });\n };\n\n const exit = (): void => {\n if (!doc.fullscreenElement) return;\n void doc.exitFullscreen().catch(() => {\n // Degrade silently.\n });\n };\n\n const sentinel = createSentinel<FullscreenState>({ initialValue: read(), ...options }, (update) => {\n updateState = update;\n\n const onFullscreenChange = (): void => updateState(read());\n\n doc.addEventListener('fullscreenchange', onFullscreenChange);\n\n return () => {\n doc.removeEventListener('fullscreenchange', onFullscreenChange);\n if (doc.fullscreenElement) exit();\n };\n });\n\n return {\n ...sentinel,\n exit,\n request,\n toggle(element?: Element): void {\n if (doc.fullscreenElement) exit();\n else request(element);\n },\n };\n}\n"],"mappings":"0FAYA,SAAgB,EACd,EACoB,CAEpB,IAAM,EADS,EAAc,GAAS,MAC1B,CAAA,CAAO,SACb,EAAY,OAAO,EAAI,iBAAiB,mBAAsB,WAE9D,OAA+B,CAAE,OAAQ,EAAI,oBAAsB,KAAM,WAAU,GACrF,MAAsD,CAAC,EAErD,EAAW,GAA4B,CACvC,GAAI,mBAAsB,IACxB,GAAW,EAAI,gBAAA,CAAiB,kBAAkB,CAAC,CAAC,UAAY,CAEtE,CAAC,CACH,EAEM,MAAmB,CAClB,EAAI,mBACT,EAAS,eAAe,CAAC,CAAC,UAAY,CAEtC,CAAC,CACH,EAeA,MAAO,CACL,GAde,EAAgC,CAAE,aAAc,EAAK,EAAG,GAAG,CAAQ,EAAI,GAAW,CACjG,EAAc,EAEd,IAAM,MAAiC,EAAY,EAAK,CAAC,EAIzD,OAFA,EAAI,iBAAiB,mBAAoB,CAAkB,MAE9C,CACX,EAAI,oBAAoB,mBAAoB,CAAkB,EAC1D,EAAI,mBAAmB,EAAK,CAClC,CACF,CAGK,EACH,OACA,UACA,OAAO,EAAyB,CAC1B,EAAI,kBAAmB,EAAK,EAC3B,EAAQ,CAAO,CACtB,CACF,CACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"sentinel.cjs","names":[],"sources":["../src/errors.ts","../src/core.ts","../src/_platform.ts","../src/element-size.ts","../src/fullscreen.ts","../src/intersection.ts","../src/media-query.ts","../src/network.ts","../src/viewport.ts","../src/wake-lock.ts"],"sourcesContent":["export class SentinelError extends Error {\n constructor(message: string, options?: ErrorOptions) {\n super(message, options);\n this.name = new.target.name;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\nexport class SentinelUnavailableError extends SentinelError {}\n","import { type Tapper, tapper } from '@vielzeug/arsenal';\n\nimport { SentinelError } from './errors.ts';\nimport type { Sentinel, SentinelEvent, SentinelOptions, Unsubscribe } from './types.ts';\n\nexport interface CreateSentinelOptions<T> extends SentinelOptions {\n readonly initialValue: T;\n}\n\nexport function createSentinel<T>(\n options: CreateSentinelOptions<T>,\n setup: (update: (value: T) => void) => () => void,\n): Sentinel<T> {\n return new SentinelHandle(options, setup);\n}\n\nclass SentinelHandle<T> implements Sentinel<T> {\n readonly disposalSignal: AbortSignal;\n private readonly abortController = new AbortController();\n private currentValue: T;\n private readonly listeners = new Set<() => void>();\n private cleanup: (() => void) | undefined;\n private disposedValue = false;\n private externalSignal: AbortSignal | undefined;\n private externalAbortListener: (() => void) | undefined;\n private readonly tapper: Tapper<SentinelEvent> = tapper<SentinelEvent>();\n\n constructor(options: CreateSentinelOptions<T>, setup: (update: (value: T) => void) => () => void) {\n if (options.signal?.aborted) {\n throw new SentinelError('Sentinel signal is already aborted');\n }\n\n this.currentValue = options.initialValue;\n this.disposalSignal = this.abortController.signal;\n\n try {\n this.cleanup = setup((value) => {\n if (!this.disposedValue) {\n this.currentValue = value;\n this.notify();\n }\n });\n } catch (error) {\n this.disposedValue = true;\n this.abortController.abort();\n throw error;\n }\n\n if (!options.signal) return;\n\n this.externalSignal = options.signal;\n this.externalAbortListener = () => this.dispose();\n this.externalSignal.addEventListener('abort', this.externalAbortListener, { once: true });\n }\n\n get disposed(): boolean {\n return this.disposedValue;\n }\n\n readonly getSnapshot = (): T => this.currentValue;\n\n readonly subscribe = (listener: () => void): Unsubscribe => {\n if (this.disposedValue) return () => {};\n this.listeners.add(listener);\n return () => this.listeners.delete(listener);\n };\n\n readonly dispose = (): void => {\n if (this.disposedValue) return;\n this.disposedValue = true;\n\n if (this.externalSignal && this.externalAbortListener) {\n this.externalSignal.removeEventListener('abort', this.externalAbortListener);\n }\n\n const cleanup = this.cleanup;\n this.cleanup = undefined;\n this.externalSignal = undefined;\n this.externalAbortListener = undefined;\n this.listeners.clear();\n\n try {\n cleanup?.();\n } finally {\n this.tapper.emit({ type: 'dispose' });\n this.tapper.clear();\n this.abortController.abort();\n }\n };\n\n readonly tap = (handler: (event: SentinelEvent) => void, options?: { readonly signal?: AbortSignal }): Unsubscribe =>\n this.tapper.tap(handler, options);\n\n private notify(): void {\n for (const listener of [...this.listeners]) {\n try {\n listener();\n } catch (error) {\n this.tapper.emit({ error, type: 'error' });\n queueMicrotask(() => {\n throw error;\n });\n }\n }\n }\n\n [Symbol.dispose](): void {\n this.dispose();\n }\n}\n","import { SentinelUnavailableError } from './errors.ts';\n\ntype LegacyMediaQueryList = MediaQueryList & {\n addListener?: (listener: (event: MediaQueryListEvent) => void) => void;\n removeListener?: (listener: (event: MediaQueryListEvent) => void) => void;\n};\n\nexport type ObserverWindow = Window & {\n readonly IntersectionObserver?: {\n new (callback: IntersectionObserverCallback, options?: IntersectionObserverInit): IntersectionObserver;\n };\n readonly ResizeObserver?: {\n new (callback: ResizeObserverCallback): ResizeObserver;\n };\n};\n\nexport function resolveWindow(target?: Window): Window {\n if (target) return target;\n if (typeof window === 'undefined') {\n throw new SentinelUnavailableError('Window is not available in this environment.');\n }\n\n return window;\n}\n\nexport function resolveElementWindow(element: Element): ObserverWindow {\n const ownerWindow = element.ownerDocument.defaultView;\n if (!ownerWindow) {\n throw new SentinelUnavailableError('The element is not connected to a Window.');\n }\n\n return ownerWindow;\n}\n\nexport function listenMediaQuery(mediaQuery: MediaQueryList, listener: () => void): () => void {\n const onChange = () => listener();\n\n if (typeof mediaQuery.addEventListener === 'function') {\n mediaQuery.addEventListener('change', onChange);\n return () => mediaQuery.removeEventListener('change', onChange);\n }\n\n const legacyMediaQuery = mediaQuery as LegacyMediaQueryList;\n if (typeof legacyMediaQuery.addListener === 'function') {\n legacyMediaQuery.addListener(onChange);\n return () => legacyMediaQuery.removeListener?.(onChange);\n }\n\n return () => {};\n}\n","import { resolveElementWindow } from './_platform.ts';\nimport { createSentinel } from './core.ts';\nimport { SentinelUnavailableError } from './errors.ts';\nimport type { ElementSizeState, Sentinel, SentinelOptions } from './types.ts';\n\nexport function createElementSize(element: Element, options?: SentinelOptions): Sentinel<ElementSizeState | null> {\n const target = resolveElementWindow(element);\n const ResizeObserverConstructor = target.ResizeObserver;\n if (typeof ResizeObserverConstructor !== 'function') {\n throw new SentinelUnavailableError('ResizeObserver is not available in this environment.');\n }\n\n return createSentinel<ElementSizeState | null>(\n {\n initialValue: null,\n ...options,\n },\n (update) => {\n const observer = new ResizeObserverConstructor((entries) => {\n const entry = entries.at(-1);\n if (!entry) return;\n\n update({\n height: entry.contentRect.height,\n width: entry.contentRect.width,\n });\n });\n\n try {\n observer.observe(element, { box: 'content-box' });\n } catch (error) {\n observer.disconnect();\n throw error;\n }\n\n return () => observer.disconnect();\n },\n );\n}\n","import { resolveWindow } from './_platform.ts';\nimport { createSentinel } from './core.ts';\nimport type { FullscreenSentinel, FullscreenState, SentinelOptions } from './types.ts';\n\ntype FullscreenWindow = Window & { document: Document };\n\n/**\n * A sentinel-backed fullscreen view. `request()` enters fullscreen for an element (default:\n * the document element), `exit()` leaves it, and `toggle()` flips it — all degrade silently\n * when the Fullscreen API is missing or the browser refuses (user-activation requirements).\n * Disposing the sentinel exits fullscreen and stops tracking `fullscreenchange`.\n */\nexport function createFullscreen(\n options?: SentinelOptions & { readonly target?: FullscreenWindow },\n): FullscreenSentinel {\n const target = resolveWindow(options?.target);\n const doc = target.document;\n const supported = typeof doc.documentElement?.requestFullscreen === 'function';\n\n const read = (): FullscreenState => ({ active: doc.fullscreenElement !== null, supported });\n let updateState: (value: FullscreenState) => void = () => {};\n\n const request = (element?: Element): void => {\n if (doc.fullscreenElement || !supported) return;\n void (element ?? doc.documentElement).requestFullscreen().catch(() => {\n // Requires user activation in some browsers, or the element may refuse; degrade silently.\n });\n };\n\n const exit = (): void => {\n if (!doc.fullscreenElement) return;\n void doc.exitFullscreen().catch(() => {\n // Degrade silently.\n });\n };\n\n const sentinel = createSentinel<FullscreenState>({ initialValue: read(), ...options }, (update) => {\n updateState = update;\n\n const onFullscreenChange = (): void => updateState(read());\n\n doc.addEventListener('fullscreenchange', onFullscreenChange);\n\n return () => {\n doc.removeEventListener('fullscreenchange', onFullscreenChange);\n if (doc.fullscreenElement) exit();\n };\n });\n\n return {\n ...sentinel,\n exit,\n request,\n toggle(element?: Element): void {\n if (doc.fullscreenElement) exit();\n else request(element);\n },\n };\n}\n","import { resolveElementWindow } from './_platform.ts';\nimport { createSentinel } from './core.ts';\nimport { SentinelUnavailableError } from './errors.ts';\nimport type { IntersectionState, Sentinel, SentinelOptions } from './types.ts';\n\nexport interface CreateIntersectionOptions extends SentinelOptions {\n readonly root?: Element | Document | null;\n readonly rootMargin?: string;\n readonly scrollMargin?: string;\n readonly threshold?: number | number[];\n}\n\nexport function createIntersection(\n element: Element,\n options?: CreateIntersectionOptions,\n): Sentinel<IntersectionState | null> {\n const target = resolveElementWindow(element);\n const IntersectionObserverConstructor = target.IntersectionObserver;\n if (typeof IntersectionObserverConstructor !== 'function') {\n throw new SentinelUnavailableError('IntersectionObserver is not available in this environment.');\n }\n\n return createSentinel<IntersectionState | null>(\n {\n initialValue: null,\n ...options,\n },\n (update) => {\n const observer = new IntersectionObserverConstructor(\n (entries) => {\n const entry = entries.at(-1);\n if (!entry) return;\n\n update({\n intersectionRatio: entry.intersectionRatio,\n isIntersecting: entry.isIntersecting,\n });\n },\n {\n root: options?.root,\n rootMargin: options?.rootMargin,\n scrollMargin: options?.scrollMargin,\n threshold: options?.threshold,\n },\n );\n\n try {\n observer.observe(element);\n } catch (error) {\n observer.disconnect();\n throw error;\n }\n\n return () => observer.disconnect();\n },\n );\n}\n","import { listenMediaQuery, resolveWindow } from './_platform.ts';\nimport { createSentinel } from './core.ts';\nimport { SentinelUnavailableError } from './errors.ts';\nimport type { MediaQueryState, Sentinel, WindowSentinelOptions } from './types.ts';\n\nexport function createMediaQuery(query: string, options?: WindowSentinelOptions): Sentinel<MediaQueryState> {\n const target = resolveWindow(options?.target);\n if (typeof target.matchMedia !== 'function') {\n throw new SentinelUnavailableError(`matchMedia is not available for query \"${query}\".`);\n }\n\n const mediaQuery = target.matchMedia(query);\n const read = (): MediaQueryState => ({ matches: mediaQuery.matches });\n\n return createSentinel(\n {\n initialValue: read(),\n ...options,\n },\n (update) => listenMediaQuery(mediaQuery, () => update(read())),\n );\n}\n","import { resolveWindow } from './_platform.ts';\nimport { createSentinel } from './core.ts';\nimport type { NetworkConnectionSnapshot, NetworkState, Sentinel, WindowSentinelOptions } from './types.ts';\n\ninterface NetworkInformationLike extends EventTarget {\n readonly downlink?: number;\n readonly effectiveType?: NetworkConnectionSnapshot['effectiveType'];\n readonly rtt?: number;\n readonly saveData?: boolean;\n}\n\ntype NavigatorWithConnection = Navigator & {\n readonly connection?: NetworkInformationLike;\n};\n\nfunction normalizeConnection(connection?: NetworkInformationLike): NetworkConnectionSnapshot | null {\n if (!connection) return null;\n\n return {\n downlink: connection.downlink,\n effectiveType: connection.effectiveType,\n rtt: connection.rtt,\n saveData: connection.saveData,\n };\n}\n\nexport function createNetwork(options?: WindowSentinelOptions): Sentinel<NetworkState> {\n const target = resolveWindow(options?.target);\n const navigator = target.navigator as NavigatorWithConnection;\n const read = (): NetworkState => ({\n connection: normalizeConnection(navigator.connection),\n online: navigator.onLine,\n });\n\n return createSentinel(\n {\n initialValue: read(),\n ...options,\n },\n (update) => {\n const onChange = () => update(read());\n const connection = navigator.connection;\n\n target.addEventListener('online', onChange);\n target.addEventListener('offline', onChange);\n connection?.addEventListener('change', onChange);\n\n return () => {\n target.removeEventListener('online', onChange);\n target.removeEventListener('offline', onChange);\n connection?.removeEventListener('change', onChange);\n };\n },\n );\n}\n","import { listenMediaQuery, resolveWindow } from './_platform.ts';\nimport { createSentinel } from './core.ts';\nimport type { Sentinel, ViewportState, WindowSentinelOptions } from './types.ts';\n\nexport function createViewport(options?: WindowSentinelOptions): Sentinel<ViewportState> {\n const target = resolveWindow(options?.target);\n const read = (): ViewportState => ({\n dpr: target.devicePixelRatio,\n height: target.innerHeight,\n width: target.innerWidth,\n });\n\n return createSentinel(\n {\n initialValue: read(),\n ...options,\n },\n (update) => {\n const onResize = () => update(read());\n let stopDprObservation: (() => void) | undefined;\n\n const observeDpr = () => {\n stopDprObservation?.();\n stopDprObservation = undefined;\n\n if (typeof target.matchMedia !== 'function') return;\n\n const mediaQuery = target.matchMedia(`(resolution: ${target.devicePixelRatio}dppx)`);\n stopDprObservation = listenMediaQuery(mediaQuery, () => {\n update(read());\n observeDpr();\n });\n };\n\n target.addEventListener('resize', onResize);\n observeDpr();\n\n return () => {\n target.removeEventListener('resize', onResize);\n stopDprObservation?.();\n };\n },\n );\n}\n","import { resolveWindow } from './_platform.ts';\nimport { createSentinel } from './core.ts';\nimport type { SentinelOptions, WakeLockSentinel, WakeLockState } from './types.ts';\n\ntype WakeLockWindow = Window & { document: Document };\n\nexport function createWakeLock(options?: SentinelOptions & { readonly target?: WakeLockWindow }): WakeLockSentinel {\n const target = resolveWindow(options?.target);\n const navigator = target.navigator;\n const supported = typeof navigator.wakeLock?.request === 'function';\n\n let handle: globalThis.WakeLockSentinel | null = null;\n let pending = false;\n let wantsLock = false;\n\n const read = (): WakeLockState => ({ active: handle !== null, supported });\n let updateState: (value: WakeLockState) => void = () => {};\n\n async function acquire(): Promise<void> {\n if (!supported || !navigator.wakeLock) return;\n pending = true;\n try {\n const requested = await navigator.wakeLock.request('screen');\n // release()/dispose() during the pending request must not leave the lock held.\n if (!wantsLock) {\n void requested.release();\n\n return;\n }\n handle = requested;\n handle.addEventListener('release', () => {\n handle = null;\n updateState(read());\n });\n updateState(read());\n } catch {\n // Not supported, denied, or not in a secure context — degrade silently.\n } finally {\n pending = false;\n }\n }\n\n const sentinel = createSentinel<WakeLockState>({ initialValue: read(), ...options }, (update) => {\n updateState = update;\n\n const onVisibilityChange = (): void => {\n if (target.document.visibilityState === 'visible' && wantsLock && !handle && !pending) {\n void acquire();\n }\n };\n\n target.document.addEventListener('visibilitychange', onVisibilityChange);\n\n return () => {\n target.document.removeEventListener('visibilitychange', onVisibilityChange);\n if (handle) void handle.release();\n handle = null;\n wantsLock = false;\n };\n });\n\n return {\n ...sentinel,\n release(): void {\n wantsLock = false;\n if (handle) {\n void handle.release();\n handle = null;\n updateState(read());\n }\n },\n request(): void {\n if (handle || pending || !supported) return;\n wantsLock = true;\n void acquire();\n },\n };\n}\n"],"mappings":"sGAAA,IAAa,cAAb,cAAmC,KAAM,CACvC,YAAY,EAAiB,EAAwB,CACnD,MAAM,EAAS,CAAO,EACtB,KAAK,KAAO,WAAW,KACvB,OAAO,eAAe,KAAM,WAAW,SAAS,CAClD,CACF,EAEa,yBAAb,cAA8C,aAAc,CAAC,ECC7D,SAAgB,eACd,EACA,EACa,CACb,OAAO,IAAI,eAAe,EAAS,CAAK,CAC1C,CAEA,IAAM,eAAN,KAA+C,CAC7C,eACA,gBAAmC,IAAI,gBACvC,aACA,UAA6B,IAAI,IACjC,QACA,cAAwB,GACxB,eACA,sBACA,QAAA,EAAiD,EAAA,OAAA,CAAsB,EAEvE,YAAY,EAAmC,EAAmD,CAChG,GAAI,EAAQ,QAAQ,QAClB,MAAM,IAAI,cAAc,oCAAoC,EAG9D,KAAK,aAAe,EAAQ,aAC5B,KAAK,eAAiB,KAAK,gBAAgB,OAE3C,GAAI,CACF,KAAK,QAAU,EAAO,GAAU,CACzB,KAAK,gBACR,KAAK,aAAe,EACpB,KAAK,OAAO,EAEhB,CAAC,CACH,OAAS,EAAO,CAGd,KAFA,MAAK,cAAgB,GACrB,KAAK,gBAAgB,MAAM,EACrB,CACR,CAEK,EAAQ,SAEb,KAAK,eAAiB,EAAQ,OAC9B,KAAK,0BAA8B,KAAK,QAAQ,EAChD,KAAK,eAAe,iBAAiB,QAAS,KAAK,sBAAuB,CAAE,KAAM,EAAK,CAAC,EAC1F,CAEA,IAAI,UAAoB,CACtB,OAAO,KAAK,aACd,CAEA,gBAAgC,KAAK,aAErC,UAAsB,GAChB,KAAK,kBAA4B,CAAC,GACtC,KAAK,UAAU,IAAI,CAAQ,MACd,KAAK,UAAU,OAAO,CAAQ,GAG7C,YAA+B,CAC7B,GAAI,KAAK,cAAe,OACxB,KAAK,cAAgB,GAEjB,KAAK,gBAAkB,KAAK,uBAC9B,KAAK,eAAe,oBAAoB,QAAS,KAAK,qBAAqB,EAG7E,IAAM,EAAU,KAAK,QACrB,KAAK,QAAU,IAAA,GACf,KAAK,eAAiB,IAAA,GACtB,KAAK,sBAAwB,IAAA,GAC7B,KAAK,UAAU,MAAM,EAErB,GAAI,CACF,IAAU,CACZ,QAAU,CACR,KAAK,OAAO,KAAK,CAAE,KAAM,SAAU,CAAC,EACpC,KAAK,OAAO,MAAM,EAClB,KAAK,gBAAgB,MAAM,CAC7B,CACF,EAEA,KAAgB,EAAyC,IACvD,KAAK,OAAO,IAAI,EAAS,CAAO,EAElC,QAAuB,CACrB,IAAK,IAAM,IAAY,CAAC,GAAG,KAAK,SAAS,EACvC,GAAI,CACF,EAAS,CACX,OAAS,EAAO,CACd,KAAK,OAAO,KAAK,CAAE,QAAO,KAAM,OAAQ,CAAC,EACzC,mBAAqB,CACnB,MAAM,CACR,CAAC,CACH,CAEJ,CAEA,CAAC,OAAO,UAAiB,CACvB,KAAK,QAAQ,CACf,CACF,EC7FA,SAAgB,cAAc,EAAyB,CACrD,GAAI,EAAQ,OAAO,EACnB,GAAI,OAAO,OAAW,IACpB,MAAM,IAAI,yBAAyB,8CAA8C,EAGnF,OAAO,MACT,CAEA,SAAgB,qBAAqB,EAAkC,CACrE,IAAM,EAAc,EAAQ,cAAc,YAC1C,GAAI,CAAC,EACH,MAAM,IAAI,yBAAyB,2CAA2C,EAGhF,OAAO,CACT,CAEA,SAAgB,iBAAiB,EAA4B,EAAkC,CAC7F,IAAM,aAAiB,EAAS,EAEhC,GAAI,OAAO,EAAW,kBAAqB,WAEzC,OADA,EAAW,iBAAiB,SAAU,QAAQ,MACjC,EAAW,oBAAoB,SAAU,QAAQ,EAGhE,IAAM,EAAmB,EAMzB,OALI,OAAO,EAAiB,aAAgB,YAC1C,EAAiB,YAAY,QAAQ,MACxB,EAAiB,iBAAiB,QAAQ,OAG5C,CAAC,CAChB,CC5CA,SAAgB,kBAAkB,EAAkB,EAA8D,CAEhH,IAAM,EADS,qBAAqB,CACF,CAAA,CAAO,eACzC,GAAI,OAAO,GAA8B,WACvC,MAAM,IAAI,yBAAyB,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,iBACd,EACoB,CAEpB,IAAM,EADS,cAAc,GAAS,MAC1B,CAAA,CAAO,SACb,EAAY,OAAO,EAAI,iBAAiB,mBAAsB,WAE9D,UAA+B,CAAE,OAAQ,EAAI,oBAAsB,KAAM,WAAU,GACrF,gBAAsD,CAAC,EAErD,QAAW,GAA4B,CACvC,GAAI,mBAAsB,IACxB,GAAW,EAAI,gBAAA,CAAiB,kBAAkB,CAAC,CAAC,UAAY,CAEtE,CAAC,CACH,EAEM,SAAmB,CAClB,EAAI,mBACT,EAAS,eAAe,CAAC,CAAC,UAAY,CAEtC,CAAC,CACH,EAeA,MAAO,CACL,GAde,eAAgC,CAAE,aAAc,KAAK,EAAG,GAAG,CAAQ,EAAI,GAAW,CACjG,YAAc,EAEd,IAAM,uBAAiC,YAAY,KAAK,CAAC,EAIzD,OAFA,EAAI,iBAAiB,mBAAoB,kBAAkB,MAE9C,CACX,EAAI,oBAAoB,mBAAoB,kBAAkB,EAC1D,EAAI,mBAAmB,KAAK,CAClC,CACF,CAGK,EACH,KACA,QACA,OAAO,EAAyB,CAC1B,EAAI,kBAAmB,KAAK,EAC3B,QAAQ,CAAO,CACtB,CACF,CACF,CC9CA,SAAgB,mBACd,EACA,EACoC,CAEpC,IAAM,EADS,qBAAqB,CACI,CAAA,CAAO,qBAC/C,GAAI,OAAO,GAAoC,WAC7C,MAAM,IAAI,yBAAyB,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,iBAAiB,EAAe,EAA4D,CAC1G,IAAM,EAAS,cAAc,GAAS,MAAM,EAC5C,GAAI,OAAO,EAAO,YAAe,WAC/B,MAAM,IAAI,yBAAyB,0CAA0C,EAAM,GAAG,EAGxF,IAAM,EAAa,EAAO,WAAW,CAAK,EACpC,UAA+B,CAAE,QAAS,EAAW,OAAQ,GAEnE,OAAO,eACL,CACE,aAAc,KAAK,EACnB,GAAG,CACL,EACC,GAAW,iBAAiB,MAAkB,EAAO,KAAK,CAAC,CAAC,CAC/D,CACF,CCNA,SAAS,oBAAoB,EAAuE,CAGlG,OAFK,EAEE,CACL,SAAU,EAAW,SACrB,cAAe,EAAW,cAC1B,IAAK,EAAW,IAChB,SAAU,EAAW,QACvB,EAPwB,IAQ1B,CAEA,SAAgB,cAAc,EAAyD,CACrF,IAAM,EAAS,cAAc,GAAS,MAAM,EACtC,EAAY,EAAO,UACnB,UAA4B,CAChC,WAAY,oBAAoB,EAAU,UAAU,EACpD,OAAQ,EAAU,MACpB,GAEA,OAAO,eACL,CACE,aAAc,KAAK,EACnB,GAAG,CACL,EACC,GAAW,CACV,IAAM,aAAiB,EAAO,KAAK,CAAC,EAC9B,EAAa,EAAU,WAM7B,OAJA,EAAO,iBAAiB,SAAU,QAAQ,EAC1C,EAAO,iBAAiB,UAAW,QAAQ,EAC3C,GAAY,iBAAiB,SAAU,QAAQ,MAElC,CACX,EAAO,oBAAoB,SAAU,QAAQ,EAC7C,EAAO,oBAAoB,UAAW,QAAQ,EAC9C,GAAY,oBAAoB,SAAU,QAAQ,CACpD,CACF,CACF,CACF,CClDA,SAAgB,eAAe,EAA0D,CACvF,IAAM,EAAS,cAAc,GAAS,MAAM,EACtC,UAA6B,CACjC,IAAK,EAAO,iBACZ,OAAQ,EAAO,YACf,MAAO,EAAO,UAChB,GAEA,OAAO,eACL,CACE,aAAc,KAAK,EACnB,GAAG,CACL,EACC,GAAW,CACV,IAAM,aAAiB,EAAO,KAAK,CAAC,EAChC,EAEE,eAAmB,CACvB,IAAqB,EACrB,EAAqB,IAAA,GAEjB,OAAO,EAAO,YAAe,aAGjC,EAAqB,iBADF,EAAO,WAAW,gBAAgB,EAAO,iBAAiB,MACvC,MAAkB,CACtD,EAAO,KAAK,CAAC,EACb,WAAW,CACb,CAAC,EACH,EAKA,OAHA,EAAO,iBAAiB,SAAU,QAAQ,EAC1C,WAAW,MAEE,CACX,EAAO,oBAAoB,SAAU,QAAQ,EAC7C,IAAqB,CACvB,CACF,CACF,CACF,CCrCA,SAAgB,eAAe,EAAoF,CACjH,IAAM,EAAS,cAAc,GAAS,MAAM,EACtC,EAAY,EAAO,UACnB,EAAY,OAAO,EAAU,UAAU,SAAY,WAErD,EAA6C,KAC7C,EAAU,GACV,EAAY,GAEV,UAA6B,CAAE,OAAQ,IAAW,KAAM,WAAU,GACpE,gBAAoD,CAAC,EAEzD,eAAe,SAAyB,CAClC,GAAC,GAAc,EAAU,SAC7B,GAAU,GACV,GAAI,CACF,IAAM,EAAY,MAAM,EAAU,SAAS,QAAQ,QAAQ,EAE3D,GAAI,CAAC,EAAW,CACd,EAAe,QAAQ,EAEvB,MACF,CACA,EAAS,EACT,EAAO,iBAAiB,cAAiB,CACvC,EAAS,KACT,YAAY,KAAK,CAAC,CACpB,CAAC,EACD,YAAY,KAAK,CAAC,CACpB,MAAQ,CAER,QAAU,CACR,EAAU,EACZ,CAnBU,CAoBZ,CAqBA,MAAO,CACL,GApBe,eAA8B,CAAE,aAAc,KAAK,EAAG,GAAG,CAAQ,EAAI,GAAW,CAC/F,YAAc,EAEd,IAAM,uBAAiC,CACjC,EAAO,SAAS,kBAAoB,WAAa,GAAa,CAAC,GAAU,CAAC,GAC5E,QAAa,CAEjB,EAIA,OAFA,EAAO,SAAS,iBAAiB,mBAAoB,kBAAkB,MAE1D,CACX,EAAO,SAAS,oBAAoB,mBAAoB,kBAAkB,EACtE,GAAQ,EAAY,QAAQ,EAChC,EAAS,KACT,EAAY,EACd,CACF,CAGK,EACH,SAAgB,CACd,EAAY,GACR,IACF,EAAY,QAAQ,EACpB,EAAS,KACT,YAAY,KAAK,CAAC,EAEtB,EACA,SAAgB,CACV,GAAU,GAAW,CAAC,IAC1B,EAAY,GACZ,QAAa,EACf,CACF,CACF"}
1
+ {"version":3,"file":"sentinel.cjs","names":[],"sources":["../src/errors.ts","../src/core.ts","../src/_platform.ts","../src/element-size.ts","../src/fullscreen.ts","../src/intersection.ts","../src/media-query.ts","../src/network.ts","../src/viewport.ts","../src/wake-lock.ts"],"sourcesContent":["export class SentinelError extends Error {\n constructor(message: string, options?: ErrorOptions) {\n super(message, options);\n this.name = new.target.name;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\nexport class SentinelUnavailableError extends SentinelError {}\n","import { type Tapper, tapper } from '@vielzeug/arsenal';\n\nimport { SentinelError } from './errors.ts';\nimport type { Sentinel, SentinelEvent, SentinelOptions, Unsubscribe } from './types.ts';\n\nexport interface CreateSentinelOptions<T> extends SentinelOptions {\n readonly initialValue: T;\n}\n\nexport function createSentinel<T>(\n options: CreateSentinelOptions<T>,\n setup: (update: (value: T) => void) => () => void,\n): Sentinel<T> {\n return new SentinelHandle(options, setup);\n}\n\nclass SentinelHandle<T> implements Sentinel<T> {\n readonly disposalSignal: AbortSignal;\n private readonly abortController = new AbortController();\n private currentValue: T;\n private readonly listeners = new Set<() => void>();\n private cleanup: (() => void) | undefined;\n private disposedValue = false;\n private externalSignal: AbortSignal | undefined;\n private externalAbortListener: (() => void) | undefined;\n private readonly tapper: Tapper<SentinelEvent> = tapper<SentinelEvent>();\n\n constructor(options: CreateSentinelOptions<T>, setup: (update: (value: T) => void) => () => void) {\n if (options.signal?.aborted) {\n throw new SentinelError('Sentinel signal is already aborted');\n }\n\n this.currentValue = options.initialValue;\n this.disposalSignal = this.abortController.signal;\n\n try {\n this.cleanup = setup((value) => {\n if (!this.disposedValue) {\n this.currentValue = value;\n this.notify();\n }\n });\n } catch (error) {\n this.disposedValue = true;\n this.abortController.abort();\n throw error;\n }\n\n if (!options.signal) return;\n\n this.externalSignal = options.signal;\n this.externalAbortListener = () => this.dispose();\n this.externalSignal.addEventListener('abort', this.externalAbortListener, { once: true });\n }\n\n get disposed(): boolean {\n return this.disposedValue;\n }\n\n readonly getSnapshot = (): T => this.currentValue;\n\n readonly subscribe = (listener: () => void): Unsubscribe => {\n if (this.disposedValue) return () => {};\n this.listeners.add(listener);\n return () => this.listeners.delete(listener);\n };\n\n readonly dispose = (): void => {\n if (this.disposedValue) return;\n this.disposedValue = true;\n\n if (this.externalSignal && this.externalAbortListener) {\n this.externalSignal.removeEventListener('abort', this.externalAbortListener);\n }\n\n const cleanup = this.cleanup;\n this.cleanup = undefined;\n this.externalSignal = undefined;\n this.externalAbortListener = undefined;\n this.listeners.clear();\n\n try {\n cleanup?.();\n } finally {\n this.tapper.emit({ type: 'dispose' });\n this.tapper.clear();\n this.abortController.abort();\n }\n };\n\n readonly tap = (handler: (event: SentinelEvent) => void, options?: { readonly signal?: AbortSignal }): Unsubscribe =>\n this.tapper.tap(handler, options);\n\n private notify(): void {\n for (const listener of [...this.listeners]) {\n try {\n listener();\n } catch (error) {\n this.tapper.emit({ error, type: 'error' });\n queueMicrotask(() => {\n throw error;\n });\n }\n }\n }\n\n [Symbol.dispose](): void {\n this.dispose();\n }\n}\n","import { SentinelUnavailableError } from './errors.ts';\n\ntype LegacyMediaQueryList = MediaQueryList & {\n addListener?: (listener: (event: MediaQueryListEvent) => void) => void;\n removeListener?: (listener: (event: MediaQueryListEvent) => void) => void;\n};\n\nexport type ObserverWindow = Window & {\n readonly IntersectionObserver?: {\n new (callback: IntersectionObserverCallback, options?: IntersectionObserverInit): IntersectionObserver;\n };\n readonly ResizeObserver?: {\n new (callback: ResizeObserverCallback): ResizeObserver;\n };\n};\n\nexport function resolveWindow(target?: Window): Window {\n if (target) return target;\n if (typeof window === 'undefined') {\n throw new SentinelUnavailableError('Window is not available in this environment.');\n }\n\n return window;\n}\n\nexport function resolveElementWindow(element: Element): ObserverWindow {\n const ownerWindow = element.ownerDocument.defaultView;\n if (!ownerWindow) {\n throw new SentinelUnavailableError('The element is not connected to a Window.');\n }\n\n return ownerWindow;\n}\n\nexport function listenMediaQuery(mediaQuery: MediaQueryList, listener: () => void): () => void {\n const onChange = () => listener();\n\n if (typeof mediaQuery.addEventListener === 'function') {\n mediaQuery.addEventListener('change', onChange);\n return () => mediaQuery.removeEventListener('change', onChange);\n }\n\n const legacyMediaQuery = mediaQuery as LegacyMediaQueryList;\n if (typeof legacyMediaQuery.addListener === 'function') {\n legacyMediaQuery.addListener(onChange);\n return () => legacyMediaQuery.removeListener?.(onChange);\n }\n\n return () => {};\n}\n","import { resolveElementWindow } from './_platform.ts';\nimport { createSentinel } from './core.ts';\nimport { SentinelUnavailableError } from './errors.ts';\nimport type { ElementSizeState, Sentinel, SentinelOptions } from './types.ts';\n\nexport function createElementSize(element: Element, options?: SentinelOptions): Sentinel<ElementSizeState | null> {\n const target = resolveElementWindow(element);\n const ResizeObserverConstructor = target.ResizeObserver;\n if (typeof ResizeObserverConstructor !== 'function') {\n throw new SentinelUnavailableError('ResizeObserver is not available in this environment.');\n }\n\n return createSentinel<ElementSizeState | null>(\n {\n initialValue: null,\n ...options,\n },\n (update) => {\n const observer = new ResizeObserverConstructor((entries) => {\n const entry = entries.at(-1);\n if (!entry) return;\n\n update({\n height: entry.contentRect.height,\n width: entry.contentRect.width,\n });\n });\n\n try {\n observer.observe(element, { box: 'content-box' });\n } catch (error) {\n observer.disconnect();\n throw error;\n }\n\n return () => observer.disconnect();\n },\n );\n}\n","import { resolveWindow } from './_platform.ts';\nimport { createSentinel } from './core.ts';\nimport type { FullscreenSentinel, FullscreenState, SentinelOptions } from './types.ts';\n\ntype FullscreenWindow = Window & { document: Document };\n\n/**\n * A sentinel-backed fullscreen view. `request()` enters fullscreen for an element (default:\n * the document element), `exit()` leaves it, and `toggle()` flips it: all degrade silently\n * when the Fullscreen API is missing or the browser refuses (user-activation requirements).\n * Disposing the sentinel exits fullscreen and stops tracking `fullscreenchange`.\n */\nexport function createFullscreen(\n options?: SentinelOptions & { readonly target?: FullscreenWindow },\n): FullscreenSentinel {\n const target = resolveWindow(options?.target);\n const doc = target.document;\n const supported = typeof doc.documentElement?.requestFullscreen === 'function';\n\n const read = (): FullscreenState => ({ active: doc.fullscreenElement !== null, supported });\n let updateState: (value: FullscreenState) => void = () => {};\n\n const request = (element?: Element): void => {\n if (doc.fullscreenElement || !supported) return;\n void (element ?? doc.documentElement).requestFullscreen().catch(() => {\n // Requires user activation in some browsers, or the element may refuse; degrade silently.\n });\n };\n\n const exit = (): void => {\n if (!doc.fullscreenElement) return;\n void doc.exitFullscreen().catch(() => {\n // Degrade silently.\n });\n };\n\n const sentinel = createSentinel<FullscreenState>({ initialValue: read(), ...options }, (update) => {\n updateState = update;\n\n const onFullscreenChange = (): void => updateState(read());\n\n doc.addEventListener('fullscreenchange', onFullscreenChange);\n\n return () => {\n doc.removeEventListener('fullscreenchange', onFullscreenChange);\n if (doc.fullscreenElement) exit();\n };\n });\n\n return {\n ...sentinel,\n exit,\n request,\n toggle(element?: Element): void {\n if (doc.fullscreenElement) exit();\n else request(element);\n },\n };\n}\n","import { resolveElementWindow } from './_platform.ts';\nimport { createSentinel } from './core.ts';\nimport { SentinelUnavailableError } from './errors.ts';\nimport type { IntersectionState, Sentinel, SentinelOptions } from './types.ts';\n\nexport interface CreateIntersectionOptions extends SentinelOptions {\n readonly root?: Element | Document | null;\n readonly rootMargin?: string;\n readonly scrollMargin?: string;\n readonly threshold?: number | number[];\n}\n\nexport function createIntersection(\n element: Element,\n options?: CreateIntersectionOptions,\n): Sentinel<IntersectionState | null> {\n const target = resolveElementWindow(element);\n const IntersectionObserverConstructor = target.IntersectionObserver;\n if (typeof IntersectionObserverConstructor !== 'function') {\n throw new SentinelUnavailableError('IntersectionObserver is not available in this environment.');\n }\n\n return createSentinel<IntersectionState | null>(\n {\n initialValue: null,\n ...options,\n },\n (update) => {\n const observer = new IntersectionObserverConstructor(\n (entries) => {\n const entry = entries.at(-1);\n if (!entry) return;\n\n update({\n intersectionRatio: entry.intersectionRatio,\n isIntersecting: entry.isIntersecting,\n });\n },\n {\n root: options?.root,\n rootMargin: options?.rootMargin,\n scrollMargin: options?.scrollMargin,\n threshold: options?.threshold,\n },\n );\n\n try {\n observer.observe(element);\n } catch (error) {\n observer.disconnect();\n throw error;\n }\n\n return () => observer.disconnect();\n },\n );\n}\n","import { listenMediaQuery, resolveWindow } from './_platform.ts';\nimport { createSentinel } from './core.ts';\nimport { SentinelUnavailableError } from './errors.ts';\nimport type { MediaQueryState, Sentinel, WindowSentinelOptions } from './types.ts';\n\nexport function createMediaQuery(query: string, options?: WindowSentinelOptions): Sentinel<MediaQueryState> {\n const target = resolveWindow(options?.target);\n if (typeof target.matchMedia !== 'function') {\n throw new SentinelUnavailableError(`matchMedia is not available for query \"${query}\".`);\n }\n\n const mediaQuery = target.matchMedia(query);\n const read = (): MediaQueryState => ({ matches: mediaQuery.matches });\n\n return createSentinel(\n {\n initialValue: read(),\n ...options,\n },\n (update) => listenMediaQuery(mediaQuery, () => update(read())),\n );\n}\n","import { resolveWindow } from './_platform.ts';\nimport { createSentinel } from './core.ts';\nimport type { NetworkConnectionSnapshot, NetworkState, Sentinel, WindowSentinelOptions } from './types.ts';\n\ninterface NetworkInformationLike extends EventTarget {\n readonly downlink?: number;\n readonly effectiveType?: NetworkConnectionSnapshot['effectiveType'];\n readonly rtt?: number;\n readonly saveData?: boolean;\n}\n\ntype NavigatorWithConnection = Navigator & {\n readonly connection?: NetworkInformationLike;\n};\n\nfunction normalizeConnection(connection?: NetworkInformationLike): NetworkConnectionSnapshot | null {\n if (!connection) return null;\n\n return {\n downlink: connection.downlink,\n effectiveType: connection.effectiveType,\n rtt: connection.rtt,\n saveData: connection.saveData,\n };\n}\n\nexport function createNetwork(options?: WindowSentinelOptions): Sentinel<NetworkState> {\n const target = resolveWindow(options?.target);\n const navigator = target.navigator as NavigatorWithConnection;\n const read = (): NetworkState => ({\n connection: normalizeConnection(navigator.connection),\n online: navigator.onLine,\n });\n\n return createSentinel(\n {\n initialValue: read(),\n ...options,\n },\n (update) => {\n const onChange = () => update(read());\n const connection = navigator.connection;\n\n target.addEventListener('online', onChange);\n target.addEventListener('offline', onChange);\n connection?.addEventListener('change', onChange);\n\n return () => {\n target.removeEventListener('online', onChange);\n target.removeEventListener('offline', onChange);\n connection?.removeEventListener('change', onChange);\n };\n },\n );\n}\n","import { listenMediaQuery, resolveWindow } from './_platform.ts';\nimport { createSentinel } from './core.ts';\nimport type { Sentinel, ViewportState, WindowSentinelOptions } from './types.ts';\n\nexport function createViewport(options?: WindowSentinelOptions): Sentinel<ViewportState> {\n const target = resolveWindow(options?.target);\n const read = (): ViewportState => ({\n dpr: target.devicePixelRatio,\n height: target.innerHeight,\n width: target.innerWidth,\n });\n\n return createSentinel(\n {\n initialValue: read(),\n ...options,\n },\n (update) => {\n const onResize = () => update(read());\n let stopDprObservation: (() => void) | undefined;\n\n const observeDpr = () => {\n stopDprObservation?.();\n stopDprObservation = undefined;\n\n if (typeof target.matchMedia !== 'function') return;\n\n const mediaQuery = target.matchMedia(`(resolution: ${target.devicePixelRatio}dppx)`);\n stopDprObservation = listenMediaQuery(mediaQuery, () => {\n update(read());\n observeDpr();\n });\n };\n\n target.addEventListener('resize', onResize);\n observeDpr();\n\n return () => {\n target.removeEventListener('resize', onResize);\n stopDprObservation?.();\n };\n },\n );\n}\n","import { resolveWindow } from './_platform.ts';\nimport { createSentinel } from './core.ts';\nimport type { SentinelOptions, WakeLockSentinel, WakeLockState } from './types.ts';\n\ntype WakeLockWindow = Window & { document: Document };\n\nexport function createWakeLock(options?: SentinelOptions & { readonly target?: WakeLockWindow }): WakeLockSentinel {\n const target = resolveWindow(options?.target);\n const navigator = target.navigator;\n const supported = typeof navigator.wakeLock?.request === 'function';\n\n let handle: globalThis.WakeLockSentinel | null = null;\n let pending = false;\n let wantsLock = false;\n\n const read = (): WakeLockState => ({ active: handle !== null, supported });\n let updateState: (value: WakeLockState) => void = () => {};\n\n async function acquire(): Promise<void> {\n if (!supported || !navigator.wakeLock) return;\n pending = true;\n try {\n const requested = await navigator.wakeLock.request('screen');\n // release()/dispose() during the pending request must not leave the lock held.\n if (!wantsLock) {\n void requested.release();\n\n return;\n }\n handle = requested;\n handle.addEventListener('release', () => {\n handle = null;\n updateState(read());\n });\n updateState(read());\n } catch {\n // Not supported, denied, or not in a secure context: degrade silently.\n } finally {\n pending = false;\n }\n }\n\n const sentinel = createSentinel<WakeLockState>({ initialValue: read(), ...options }, (update) => {\n updateState = update;\n\n const onVisibilityChange = (): void => {\n if (target.document.visibilityState === 'visible' && wantsLock && !handle && !pending) {\n void acquire();\n }\n };\n\n target.document.addEventListener('visibilitychange', onVisibilityChange);\n\n return () => {\n target.document.removeEventListener('visibilitychange', onVisibilityChange);\n if (handle) void handle.release();\n handle = null;\n wantsLock = false;\n };\n });\n\n return {\n ...sentinel,\n release(): void {\n wantsLock = false;\n if (handle) {\n void handle.release();\n handle = null;\n updateState(read());\n }\n },\n request(): void {\n if (handle || pending || !supported) return;\n wantsLock = true;\n void acquire();\n },\n };\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"sentinel.iife.js","names":[],"sources":["../src/errors.ts","../src/core.ts","../src/_platform.ts","../src/element-size.ts","../src/fullscreen.ts","../src/intersection.ts","../src/media-query.ts","../src/network.ts","../src/viewport.ts","../src/wake-lock.ts"],"sourcesContent":["export class SentinelError extends Error {\n constructor(message: string, options?: ErrorOptions) {\n super(message, options);\n this.name = new.target.name;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\nexport class SentinelUnavailableError extends SentinelError {}\n","import { type Tapper, tapper } from '@vielzeug/arsenal';\n\nimport { SentinelError } from './errors.ts';\nimport type { Sentinel, SentinelEvent, SentinelOptions, Unsubscribe } from './types.ts';\n\nexport interface CreateSentinelOptions<T> extends SentinelOptions {\n readonly initialValue: T;\n}\n\nexport function createSentinel<T>(\n options: CreateSentinelOptions<T>,\n setup: (update: (value: T) => void) => () => void,\n): Sentinel<T> {\n return new SentinelHandle(options, setup);\n}\n\nclass SentinelHandle<T> implements Sentinel<T> {\n readonly disposalSignal: AbortSignal;\n private readonly abortController = new AbortController();\n private currentValue: T;\n private readonly listeners = new Set<() => void>();\n private cleanup: (() => void) | undefined;\n private disposedValue = false;\n private externalSignal: AbortSignal | undefined;\n private externalAbortListener: (() => void) | undefined;\n private readonly tapper: Tapper<SentinelEvent> = tapper<SentinelEvent>();\n\n constructor(options: CreateSentinelOptions<T>, setup: (update: (value: T) => void) => () => void) {\n if (options.signal?.aborted) {\n throw new SentinelError('Sentinel signal is already aborted');\n }\n\n this.currentValue = options.initialValue;\n this.disposalSignal = this.abortController.signal;\n\n try {\n this.cleanup = setup((value) => {\n if (!this.disposedValue) {\n this.currentValue = value;\n this.notify();\n }\n });\n } catch (error) {\n this.disposedValue = true;\n this.abortController.abort();\n throw error;\n }\n\n if (!options.signal) return;\n\n this.externalSignal = options.signal;\n this.externalAbortListener = () => this.dispose();\n this.externalSignal.addEventListener('abort', this.externalAbortListener, { once: true });\n }\n\n get disposed(): boolean {\n return this.disposedValue;\n }\n\n readonly getSnapshot = (): T => this.currentValue;\n\n readonly subscribe = (listener: () => void): Unsubscribe => {\n if (this.disposedValue) return () => {};\n this.listeners.add(listener);\n return () => this.listeners.delete(listener);\n };\n\n readonly dispose = (): void => {\n if (this.disposedValue) return;\n this.disposedValue = true;\n\n if (this.externalSignal && this.externalAbortListener) {\n this.externalSignal.removeEventListener('abort', this.externalAbortListener);\n }\n\n const cleanup = this.cleanup;\n this.cleanup = undefined;\n this.externalSignal = undefined;\n this.externalAbortListener = undefined;\n this.listeners.clear();\n\n try {\n cleanup?.();\n } finally {\n this.tapper.emit({ type: 'dispose' });\n this.tapper.clear();\n this.abortController.abort();\n }\n };\n\n readonly tap = (handler: (event: SentinelEvent) => void, options?: { readonly signal?: AbortSignal }): Unsubscribe =>\n this.tapper.tap(handler, options);\n\n private notify(): void {\n for (const listener of [...this.listeners]) {\n try {\n listener();\n } catch (error) {\n this.tapper.emit({ error, type: 'error' });\n queueMicrotask(() => {\n throw error;\n });\n }\n }\n }\n\n [Symbol.dispose](): void {\n this.dispose();\n }\n}\n","import { SentinelUnavailableError } from './errors.ts';\n\ntype LegacyMediaQueryList = MediaQueryList & {\n addListener?: (listener: (event: MediaQueryListEvent) => void) => void;\n removeListener?: (listener: (event: MediaQueryListEvent) => void) => void;\n};\n\nexport type ObserverWindow = Window & {\n readonly IntersectionObserver?: {\n new (callback: IntersectionObserverCallback, options?: IntersectionObserverInit): IntersectionObserver;\n };\n readonly ResizeObserver?: {\n new (callback: ResizeObserverCallback): ResizeObserver;\n };\n};\n\nexport function resolveWindow(target?: Window): Window {\n if (target) return target;\n if (typeof window === 'undefined') {\n throw new SentinelUnavailableError('Window is not available in this environment.');\n }\n\n return window;\n}\n\nexport function resolveElementWindow(element: Element): ObserverWindow {\n const ownerWindow = element.ownerDocument.defaultView;\n if (!ownerWindow) {\n throw new SentinelUnavailableError('The element is not connected to a Window.');\n }\n\n return ownerWindow;\n}\n\nexport function listenMediaQuery(mediaQuery: MediaQueryList, listener: () => void): () => void {\n const onChange = () => listener();\n\n if (typeof mediaQuery.addEventListener === 'function') {\n mediaQuery.addEventListener('change', onChange);\n return () => mediaQuery.removeEventListener('change', onChange);\n }\n\n const legacyMediaQuery = mediaQuery as LegacyMediaQueryList;\n if (typeof legacyMediaQuery.addListener === 'function') {\n legacyMediaQuery.addListener(onChange);\n return () => legacyMediaQuery.removeListener?.(onChange);\n }\n\n return () => {};\n}\n","import { resolveElementWindow } from './_platform.ts';\nimport { createSentinel } from './core.ts';\nimport { SentinelUnavailableError } from './errors.ts';\nimport type { ElementSizeState, Sentinel, SentinelOptions } from './types.ts';\n\nexport function createElementSize(element: Element, options?: SentinelOptions): Sentinel<ElementSizeState | null> {\n const target = resolveElementWindow(element);\n const ResizeObserverConstructor = target.ResizeObserver;\n if (typeof ResizeObserverConstructor !== 'function') {\n throw new SentinelUnavailableError('ResizeObserver is not available in this environment.');\n }\n\n return createSentinel<ElementSizeState | null>(\n {\n initialValue: null,\n ...options,\n },\n (update) => {\n const observer = new ResizeObserverConstructor((entries) => {\n const entry = entries.at(-1);\n if (!entry) return;\n\n update({\n height: entry.contentRect.height,\n width: entry.contentRect.width,\n });\n });\n\n try {\n observer.observe(element, { box: 'content-box' });\n } catch (error) {\n observer.disconnect();\n throw error;\n }\n\n return () => observer.disconnect();\n },\n );\n}\n","import { resolveWindow } from './_platform.ts';\nimport { createSentinel } from './core.ts';\nimport type { FullscreenSentinel, FullscreenState, SentinelOptions } from './types.ts';\n\ntype FullscreenWindow = Window & { document: Document };\n\n/**\n * A sentinel-backed fullscreen view. `request()` enters fullscreen for an element (default:\n * the document element), `exit()` leaves it, and `toggle()` flips it — all degrade silently\n * when the Fullscreen API is missing or the browser refuses (user-activation requirements).\n * Disposing the sentinel exits fullscreen and stops tracking `fullscreenchange`.\n */\nexport function createFullscreen(\n options?: SentinelOptions & { readonly target?: FullscreenWindow },\n): FullscreenSentinel {\n const target = resolveWindow(options?.target);\n const doc = target.document;\n const supported = typeof doc.documentElement?.requestFullscreen === 'function';\n\n const read = (): FullscreenState => ({ active: doc.fullscreenElement !== null, supported });\n let updateState: (value: FullscreenState) => void = () => {};\n\n const request = (element?: Element): void => {\n if (doc.fullscreenElement || !supported) return;\n void (element ?? doc.documentElement).requestFullscreen().catch(() => {\n // Requires user activation in some browsers, or the element may refuse; degrade silently.\n });\n };\n\n const exit = (): void => {\n if (!doc.fullscreenElement) return;\n void doc.exitFullscreen().catch(() => {\n // Degrade silently.\n });\n };\n\n const sentinel = createSentinel<FullscreenState>({ initialValue: read(), ...options }, (update) => {\n updateState = update;\n\n const onFullscreenChange = (): void => updateState(read());\n\n doc.addEventListener('fullscreenchange', onFullscreenChange);\n\n return () => {\n doc.removeEventListener('fullscreenchange', onFullscreenChange);\n if (doc.fullscreenElement) exit();\n };\n });\n\n return {\n ...sentinel,\n exit,\n request,\n toggle(element?: Element): void {\n if (doc.fullscreenElement) exit();\n else request(element);\n },\n };\n}\n","import { resolveElementWindow } from './_platform.ts';\nimport { createSentinel } from './core.ts';\nimport { SentinelUnavailableError } from './errors.ts';\nimport type { IntersectionState, Sentinel, SentinelOptions } from './types.ts';\n\nexport interface CreateIntersectionOptions extends SentinelOptions {\n readonly root?: Element | Document | null;\n readonly rootMargin?: string;\n readonly scrollMargin?: string;\n readonly threshold?: number | number[];\n}\n\nexport function createIntersection(\n element: Element,\n options?: CreateIntersectionOptions,\n): Sentinel<IntersectionState | null> {\n const target = resolveElementWindow(element);\n const IntersectionObserverConstructor = target.IntersectionObserver;\n if (typeof IntersectionObserverConstructor !== 'function') {\n throw new SentinelUnavailableError('IntersectionObserver is not available in this environment.');\n }\n\n return createSentinel<IntersectionState | null>(\n {\n initialValue: null,\n ...options,\n },\n (update) => {\n const observer = new IntersectionObserverConstructor(\n (entries) => {\n const entry = entries.at(-1);\n if (!entry) return;\n\n update({\n intersectionRatio: entry.intersectionRatio,\n isIntersecting: entry.isIntersecting,\n });\n },\n {\n root: options?.root,\n rootMargin: options?.rootMargin,\n scrollMargin: options?.scrollMargin,\n threshold: options?.threshold,\n },\n );\n\n try {\n observer.observe(element);\n } catch (error) {\n observer.disconnect();\n throw error;\n }\n\n return () => observer.disconnect();\n },\n );\n}\n","import { listenMediaQuery, resolveWindow } from './_platform.ts';\nimport { createSentinel } from './core.ts';\nimport { SentinelUnavailableError } from './errors.ts';\nimport type { MediaQueryState, Sentinel, WindowSentinelOptions } from './types.ts';\n\nexport function createMediaQuery(query: string, options?: WindowSentinelOptions): Sentinel<MediaQueryState> {\n const target = resolveWindow(options?.target);\n if (typeof target.matchMedia !== 'function') {\n throw new SentinelUnavailableError(`matchMedia is not available for query \"${query}\".`);\n }\n\n const mediaQuery = target.matchMedia(query);\n const read = (): MediaQueryState => ({ matches: mediaQuery.matches });\n\n return createSentinel(\n {\n initialValue: read(),\n ...options,\n },\n (update) => listenMediaQuery(mediaQuery, () => update(read())),\n );\n}\n","import { resolveWindow } from './_platform.ts';\nimport { createSentinel } from './core.ts';\nimport type { NetworkConnectionSnapshot, NetworkState, Sentinel, WindowSentinelOptions } from './types.ts';\n\ninterface NetworkInformationLike extends EventTarget {\n readonly downlink?: number;\n readonly effectiveType?: NetworkConnectionSnapshot['effectiveType'];\n readonly rtt?: number;\n readonly saveData?: boolean;\n}\n\ntype NavigatorWithConnection = Navigator & {\n readonly connection?: NetworkInformationLike;\n};\n\nfunction normalizeConnection(connection?: NetworkInformationLike): NetworkConnectionSnapshot | null {\n if (!connection) return null;\n\n return {\n downlink: connection.downlink,\n effectiveType: connection.effectiveType,\n rtt: connection.rtt,\n saveData: connection.saveData,\n };\n}\n\nexport function createNetwork(options?: WindowSentinelOptions): Sentinel<NetworkState> {\n const target = resolveWindow(options?.target);\n const navigator = target.navigator as NavigatorWithConnection;\n const read = (): NetworkState => ({\n connection: normalizeConnection(navigator.connection),\n online: navigator.onLine,\n });\n\n return createSentinel(\n {\n initialValue: read(),\n ...options,\n },\n (update) => {\n const onChange = () => update(read());\n const connection = navigator.connection;\n\n target.addEventListener('online', onChange);\n target.addEventListener('offline', onChange);\n connection?.addEventListener('change', onChange);\n\n return () => {\n target.removeEventListener('online', onChange);\n target.removeEventListener('offline', onChange);\n connection?.removeEventListener('change', onChange);\n };\n },\n );\n}\n","import { listenMediaQuery, resolveWindow } from './_platform.ts';\nimport { createSentinel } from './core.ts';\nimport type { Sentinel, ViewportState, WindowSentinelOptions } from './types.ts';\n\nexport function createViewport(options?: WindowSentinelOptions): Sentinel<ViewportState> {\n const target = resolveWindow(options?.target);\n const read = (): ViewportState => ({\n dpr: target.devicePixelRatio,\n height: target.innerHeight,\n width: target.innerWidth,\n });\n\n return createSentinel(\n {\n initialValue: read(),\n ...options,\n },\n (update) => {\n const onResize = () => update(read());\n let stopDprObservation: (() => void) | undefined;\n\n const observeDpr = () => {\n stopDprObservation?.();\n stopDprObservation = undefined;\n\n if (typeof target.matchMedia !== 'function') return;\n\n const mediaQuery = target.matchMedia(`(resolution: ${target.devicePixelRatio}dppx)`);\n stopDprObservation = listenMediaQuery(mediaQuery, () => {\n update(read());\n observeDpr();\n });\n };\n\n target.addEventListener('resize', onResize);\n observeDpr();\n\n return () => {\n target.removeEventListener('resize', onResize);\n stopDprObservation?.();\n };\n },\n );\n}\n","import { resolveWindow } from './_platform.ts';\nimport { createSentinel } from './core.ts';\nimport type { SentinelOptions, WakeLockSentinel, WakeLockState } from './types.ts';\n\ntype WakeLockWindow = Window & { document: Document };\n\nexport function createWakeLock(options?: SentinelOptions & { readonly target?: WakeLockWindow }): WakeLockSentinel {\n const target = resolveWindow(options?.target);\n const navigator = target.navigator;\n const supported = typeof navigator.wakeLock?.request === 'function';\n\n let handle: globalThis.WakeLockSentinel | null = null;\n let pending = false;\n let wantsLock = false;\n\n const read = (): WakeLockState => ({ active: handle !== null, supported });\n let updateState: (value: WakeLockState) => void = () => {};\n\n async function acquire(): Promise<void> {\n if (!supported || !navigator.wakeLock) return;\n pending = true;\n try {\n const requested = await navigator.wakeLock.request('screen');\n // release()/dispose() during the pending request must not leave the lock held.\n if (!wantsLock) {\n void requested.release();\n\n return;\n }\n handle = requested;\n handle.addEventListener('release', () => {\n handle = null;\n updateState(read());\n });\n updateState(read());\n } catch {\n // Not supported, denied, or not in a secure context — degrade silently.\n } finally {\n pending = false;\n }\n }\n\n const sentinel = createSentinel<WakeLockState>({ initialValue: read(), ...options }, (update) => {\n updateState = update;\n\n const onVisibilityChange = (): void => {\n if (target.document.visibilityState === 'visible' && wantsLock && !handle && !pending) {\n void acquire();\n }\n };\n\n target.document.addEventListener('visibilitychange', onVisibilityChange);\n\n return () => {\n target.document.removeEventListener('visibilitychange', onVisibilityChange);\n if (handle) void handle.release();\n handle = null;\n wantsLock = false;\n };\n });\n\n return {\n ...sentinel,\n release(): void {\n wantsLock = false;\n if (handle) {\n void handle.release();\n handle = null;\n updateState(read());\n }\n },\n request(): void {\n if (handle || pending || !supported) return;\n wantsLock = true;\n void acquire();\n },\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"sentinel.iife.js","names":[],"sources":["../src/errors.ts","../src/core.ts","../src/_platform.ts","../src/element-size.ts","../src/fullscreen.ts","../src/intersection.ts","../src/media-query.ts","../src/network.ts","../src/viewport.ts","../src/wake-lock.ts"],"sourcesContent":["export class SentinelError extends Error {\n constructor(message: string, options?: ErrorOptions) {\n super(message, options);\n this.name = new.target.name;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\nexport class SentinelUnavailableError extends SentinelError {}\n","import { type Tapper, tapper } from '@vielzeug/arsenal';\n\nimport { SentinelError } from './errors.ts';\nimport type { Sentinel, SentinelEvent, SentinelOptions, Unsubscribe } from './types.ts';\n\nexport interface CreateSentinelOptions<T> extends SentinelOptions {\n readonly initialValue: T;\n}\n\nexport function createSentinel<T>(\n options: CreateSentinelOptions<T>,\n setup: (update: (value: T) => void) => () => void,\n): Sentinel<T> {\n return new SentinelHandle(options, setup);\n}\n\nclass SentinelHandle<T> implements Sentinel<T> {\n readonly disposalSignal: AbortSignal;\n private readonly abortController = new AbortController();\n private currentValue: T;\n private readonly listeners = new Set<() => void>();\n private cleanup: (() => void) | undefined;\n private disposedValue = false;\n private externalSignal: AbortSignal | undefined;\n private externalAbortListener: (() => void) | undefined;\n private readonly tapper: Tapper<SentinelEvent> = tapper<SentinelEvent>();\n\n constructor(options: CreateSentinelOptions<T>, setup: (update: (value: T) => void) => () => void) {\n if (options.signal?.aborted) {\n throw new SentinelError('Sentinel signal is already aborted');\n }\n\n this.currentValue = options.initialValue;\n this.disposalSignal = this.abortController.signal;\n\n try {\n this.cleanup = setup((value) => {\n if (!this.disposedValue) {\n this.currentValue = value;\n this.notify();\n }\n });\n } catch (error) {\n this.disposedValue = true;\n this.abortController.abort();\n throw error;\n }\n\n if (!options.signal) return;\n\n this.externalSignal = options.signal;\n this.externalAbortListener = () => this.dispose();\n this.externalSignal.addEventListener('abort', this.externalAbortListener, { once: true });\n }\n\n get disposed(): boolean {\n return this.disposedValue;\n }\n\n readonly getSnapshot = (): T => this.currentValue;\n\n readonly subscribe = (listener: () => void): Unsubscribe => {\n if (this.disposedValue) return () => {};\n this.listeners.add(listener);\n return () => this.listeners.delete(listener);\n };\n\n readonly dispose = (): void => {\n if (this.disposedValue) return;\n this.disposedValue = true;\n\n if (this.externalSignal && this.externalAbortListener) {\n this.externalSignal.removeEventListener('abort', this.externalAbortListener);\n }\n\n const cleanup = this.cleanup;\n this.cleanup = undefined;\n this.externalSignal = undefined;\n this.externalAbortListener = undefined;\n this.listeners.clear();\n\n try {\n cleanup?.();\n } finally {\n this.tapper.emit({ type: 'dispose' });\n this.tapper.clear();\n this.abortController.abort();\n }\n };\n\n readonly tap = (handler: (event: SentinelEvent) => void, options?: { readonly signal?: AbortSignal }): Unsubscribe =>\n this.tapper.tap(handler, options);\n\n private notify(): void {\n for (const listener of [...this.listeners]) {\n try {\n listener();\n } catch (error) {\n this.tapper.emit({ error, type: 'error' });\n queueMicrotask(() => {\n throw error;\n });\n }\n }\n }\n\n [Symbol.dispose](): void {\n this.dispose();\n }\n}\n","import { SentinelUnavailableError } from './errors.ts';\n\ntype LegacyMediaQueryList = MediaQueryList & {\n addListener?: (listener: (event: MediaQueryListEvent) => void) => void;\n removeListener?: (listener: (event: MediaQueryListEvent) => void) => void;\n};\n\nexport type ObserverWindow = Window & {\n readonly IntersectionObserver?: {\n new (callback: IntersectionObserverCallback, options?: IntersectionObserverInit): IntersectionObserver;\n };\n readonly ResizeObserver?: {\n new (callback: ResizeObserverCallback): ResizeObserver;\n };\n};\n\nexport function resolveWindow(target?: Window): Window {\n if (target) return target;\n if (typeof window === 'undefined') {\n throw new SentinelUnavailableError('Window is not available in this environment.');\n }\n\n return window;\n}\n\nexport function resolveElementWindow(element: Element): ObserverWindow {\n const ownerWindow = element.ownerDocument.defaultView;\n if (!ownerWindow) {\n throw new SentinelUnavailableError('The element is not connected to a Window.');\n }\n\n return ownerWindow;\n}\n\nexport function listenMediaQuery(mediaQuery: MediaQueryList, listener: () => void): () => void {\n const onChange = () => listener();\n\n if (typeof mediaQuery.addEventListener === 'function') {\n mediaQuery.addEventListener('change', onChange);\n return () => mediaQuery.removeEventListener('change', onChange);\n }\n\n const legacyMediaQuery = mediaQuery as LegacyMediaQueryList;\n if (typeof legacyMediaQuery.addListener === 'function') {\n legacyMediaQuery.addListener(onChange);\n return () => legacyMediaQuery.removeListener?.(onChange);\n }\n\n return () => {};\n}\n","import { resolveElementWindow } from './_platform.ts';\nimport { createSentinel } from './core.ts';\nimport { SentinelUnavailableError } from './errors.ts';\nimport type { ElementSizeState, Sentinel, SentinelOptions } from './types.ts';\n\nexport function createElementSize(element: Element, options?: SentinelOptions): Sentinel<ElementSizeState | null> {\n const target = resolveElementWindow(element);\n const ResizeObserverConstructor = target.ResizeObserver;\n if (typeof ResizeObserverConstructor !== 'function') {\n throw new SentinelUnavailableError('ResizeObserver is not available in this environment.');\n }\n\n return createSentinel<ElementSizeState | null>(\n {\n initialValue: null,\n ...options,\n },\n (update) => {\n const observer = new ResizeObserverConstructor((entries) => {\n const entry = entries.at(-1);\n if (!entry) return;\n\n update({\n height: entry.contentRect.height,\n width: entry.contentRect.width,\n });\n });\n\n try {\n observer.observe(element, { box: 'content-box' });\n } catch (error) {\n observer.disconnect();\n throw error;\n }\n\n return () => observer.disconnect();\n },\n );\n}\n","import { resolveWindow } from './_platform.ts';\nimport { createSentinel } from './core.ts';\nimport type { FullscreenSentinel, FullscreenState, SentinelOptions } from './types.ts';\n\ntype FullscreenWindow = Window & { document: Document };\n\n/**\n * A sentinel-backed fullscreen view. `request()` enters fullscreen for an element (default:\n * the document element), `exit()` leaves it, and `toggle()` flips it: all degrade silently\n * when the Fullscreen API is missing or the browser refuses (user-activation requirements).\n * Disposing the sentinel exits fullscreen and stops tracking `fullscreenchange`.\n */\nexport function createFullscreen(\n options?: SentinelOptions & { readonly target?: FullscreenWindow },\n): FullscreenSentinel {\n const target = resolveWindow(options?.target);\n const doc = target.document;\n const supported = typeof doc.documentElement?.requestFullscreen === 'function';\n\n const read = (): FullscreenState => ({ active: doc.fullscreenElement !== null, supported });\n let updateState: (value: FullscreenState) => void = () => {};\n\n const request = (element?: Element): void => {\n if (doc.fullscreenElement || !supported) return;\n void (element ?? doc.documentElement).requestFullscreen().catch(() => {\n // Requires user activation in some browsers, or the element may refuse; degrade silently.\n });\n };\n\n const exit = (): void => {\n if (!doc.fullscreenElement) return;\n void doc.exitFullscreen().catch(() => {\n // Degrade silently.\n });\n };\n\n const sentinel = createSentinel<FullscreenState>({ initialValue: read(), ...options }, (update) => {\n updateState = update;\n\n const onFullscreenChange = (): void => updateState(read());\n\n doc.addEventListener('fullscreenchange', onFullscreenChange);\n\n return () => {\n doc.removeEventListener('fullscreenchange', onFullscreenChange);\n if (doc.fullscreenElement) exit();\n };\n });\n\n return {\n ...sentinel,\n exit,\n request,\n toggle(element?: Element): void {\n if (doc.fullscreenElement) exit();\n else request(element);\n },\n };\n}\n","import { resolveElementWindow } from './_platform.ts';\nimport { createSentinel } from './core.ts';\nimport { SentinelUnavailableError } from './errors.ts';\nimport type { IntersectionState, Sentinel, SentinelOptions } from './types.ts';\n\nexport interface CreateIntersectionOptions extends SentinelOptions {\n readonly root?: Element | Document | null;\n readonly rootMargin?: string;\n readonly scrollMargin?: string;\n readonly threshold?: number | number[];\n}\n\nexport function createIntersection(\n element: Element,\n options?: CreateIntersectionOptions,\n): Sentinel<IntersectionState | null> {\n const target = resolveElementWindow(element);\n const IntersectionObserverConstructor = target.IntersectionObserver;\n if (typeof IntersectionObserverConstructor !== 'function') {\n throw new SentinelUnavailableError('IntersectionObserver is not available in this environment.');\n }\n\n return createSentinel<IntersectionState | null>(\n {\n initialValue: null,\n ...options,\n },\n (update) => {\n const observer = new IntersectionObserverConstructor(\n (entries) => {\n const entry = entries.at(-1);\n if (!entry) return;\n\n update({\n intersectionRatio: entry.intersectionRatio,\n isIntersecting: entry.isIntersecting,\n });\n },\n {\n root: options?.root,\n rootMargin: options?.rootMargin,\n scrollMargin: options?.scrollMargin,\n threshold: options?.threshold,\n },\n );\n\n try {\n observer.observe(element);\n } catch (error) {\n observer.disconnect();\n throw error;\n }\n\n return () => observer.disconnect();\n },\n );\n}\n","import { listenMediaQuery, resolveWindow } from './_platform.ts';\nimport { createSentinel } from './core.ts';\nimport { SentinelUnavailableError } from './errors.ts';\nimport type { MediaQueryState, Sentinel, WindowSentinelOptions } from './types.ts';\n\nexport function createMediaQuery(query: string, options?: WindowSentinelOptions): Sentinel<MediaQueryState> {\n const target = resolveWindow(options?.target);\n if (typeof target.matchMedia !== 'function') {\n throw new SentinelUnavailableError(`matchMedia is not available for query \"${query}\".`);\n }\n\n const mediaQuery = target.matchMedia(query);\n const read = (): MediaQueryState => ({ matches: mediaQuery.matches });\n\n return createSentinel(\n {\n initialValue: read(),\n ...options,\n },\n (update) => listenMediaQuery(mediaQuery, () => update(read())),\n );\n}\n","import { resolveWindow } from './_platform.ts';\nimport { createSentinel } from './core.ts';\nimport type { NetworkConnectionSnapshot, NetworkState, Sentinel, WindowSentinelOptions } from './types.ts';\n\ninterface NetworkInformationLike extends EventTarget {\n readonly downlink?: number;\n readonly effectiveType?: NetworkConnectionSnapshot['effectiveType'];\n readonly rtt?: number;\n readonly saveData?: boolean;\n}\n\ntype NavigatorWithConnection = Navigator & {\n readonly connection?: NetworkInformationLike;\n};\n\nfunction normalizeConnection(connection?: NetworkInformationLike): NetworkConnectionSnapshot | null {\n if (!connection) return null;\n\n return {\n downlink: connection.downlink,\n effectiveType: connection.effectiveType,\n rtt: connection.rtt,\n saveData: connection.saveData,\n };\n}\n\nexport function createNetwork(options?: WindowSentinelOptions): Sentinel<NetworkState> {\n const target = resolveWindow(options?.target);\n const navigator = target.navigator as NavigatorWithConnection;\n const read = (): NetworkState => ({\n connection: normalizeConnection(navigator.connection),\n online: navigator.onLine,\n });\n\n return createSentinel(\n {\n initialValue: read(),\n ...options,\n },\n (update) => {\n const onChange = () => update(read());\n const connection = navigator.connection;\n\n target.addEventListener('online', onChange);\n target.addEventListener('offline', onChange);\n connection?.addEventListener('change', onChange);\n\n return () => {\n target.removeEventListener('online', onChange);\n target.removeEventListener('offline', onChange);\n connection?.removeEventListener('change', onChange);\n };\n },\n );\n}\n","import { listenMediaQuery, resolveWindow } from './_platform.ts';\nimport { createSentinel } from './core.ts';\nimport type { Sentinel, ViewportState, WindowSentinelOptions } from './types.ts';\n\nexport function createViewport(options?: WindowSentinelOptions): Sentinel<ViewportState> {\n const target = resolveWindow(options?.target);\n const read = (): ViewportState => ({\n dpr: target.devicePixelRatio,\n height: target.innerHeight,\n width: target.innerWidth,\n });\n\n return createSentinel(\n {\n initialValue: read(),\n ...options,\n },\n (update) => {\n const onResize = () => update(read());\n let stopDprObservation: (() => void) | undefined;\n\n const observeDpr = () => {\n stopDprObservation?.();\n stopDprObservation = undefined;\n\n if (typeof target.matchMedia !== 'function') return;\n\n const mediaQuery = target.matchMedia(`(resolution: ${target.devicePixelRatio}dppx)`);\n stopDprObservation = listenMediaQuery(mediaQuery, () => {\n update(read());\n observeDpr();\n });\n };\n\n target.addEventListener('resize', onResize);\n observeDpr();\n\n return () => {\n target.removeEventListener('resize', onResize);\n stopDprObservation?.();\n };\n },\n );\n}\n","import { resolveWindow } from './_platform.ts';\nimport { createSentinel } from './core.ts';\nimport type { SentinelOptions, WakeLockSentinel, WakeLockState } from './types.ts';\n\ntype WakeLockWindow = Window & { document: Document };\n\nexport function createWakeLock(options?: SentinelOptions & { readonly target?: WakeLockWindow }): WakeLockSentinel {\n const target = resolveWindow(options?.target);\n const navigator = target.navigator;\n const supported = typeof navigator.wakeLock?.request === 'function';\n\n let handle: globalThis.WakeLockSentinel | null = null;\n let pending = false;\n let wantsLock = false;\n\n const read = (): WakeLockState => ({ active: handle !== null, supported });\n let updateState: (value: WakeLockState) => void = () => {};\n\n async function acquire(): Promise<void> {\n if (!supported || !navigator.wakeLock) return;\n pending = true;\n try {\n const requested = await navigator.wakeLock.request('screen');\n // release()/dispose() during the pending request must not leave the lock held.\n if (!wantsLock) {\n void requested.release();\n\n return;\n }\n handle = requested;\n handle.addEventListener('release', () => {\n handle = null;\n updateState(read());\n });\n updateState(read());\n } catch {\n // Not supported, denied, or not in a secure context: degrade silently.\n } finally {\n pending = false;\n }\n }\n\n const sentinel = createSentinel<WakeLockState>({ initialValue: read(), ...options }, (update) => {\n updateState = update;\n\n const onVisibilityChange = (): void => {\n if (target.document.visibilityState === 'visible' && wantsLock && !handle && !pending) {\n void acquire();\n }\n };\n\n target.document.addEventListener('visibilitychange', onVisibilityChange);\n\n return () => {\n target.document.removeEventListener('visibilitychange', onVisibilityChange);\n if (handle) void handle.release();\n handle = null;\n wantsLock = false;\n };\n });\n\n return {\n ...sentinel,\n release(): void {\n wantsLock = false;\n if (handle) {\n void handle.release();\n handle = null;\n updateState(read());\n }\n },\n request(): void {\n if (handle || pending || !supported) return;\n wantsLock = true;\n void acquire();\n },\n };\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"sentinel.js","names":[],"sources":["../src/errors.ts","../src/core.ts","../src/_platform.ts","../src/element-size.ts","../src/fullscreen.ts","../src/intersection.ts","../src/media-query.ts","../src/network.ts","../src/viewport.ts","../src/wake-lock.ts"],"sourcesContent":["export class SentinelError extends Error {\n constructor(message: string, options?: ErrorOptions) {\n super(message, options);\n this.name = new.target.name;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\nexport class SentinelUnavailableError extends SentinelError {}\n","import { type Tapper, tapper } from '@vielzeug/arsenal';\n\nimport { SentinelError } from './errors.ts';\nimport type { Sentinel, SentinelEvent, SentinelOptions, Unsubscribe } from './types.ts';\n\nexport interface CreateSentinelOptions<T> extends SentinelOptions {\n readonly initialValue: T;\n}\n\nexport function createSentinel<T>(\n options: CreateSentinelOptions<T>,\n setup: (update: (value: T) => void) => () => void,\n): Sentinel<T> {\n return new SentinelHandle(options, setup);\n}\n\nclass SentinelHandle<T> implements Sentinel<T> {\n readonly disposalSignal: AbortSignal;\n private readonly abortController = new AbortController();\n private currentValue: T;\n private readonly listeners = new Set<() => void>();\n private cleanup: (() => void) | undefined;\n private disposedValue = false;\n private externalSignal: AbortSignal | undefined;\n private externalAbortListener: (() => void) | undefined;\n private readonly tapper: Tapper<SentinelEvent> = tapper<SentinelEvent>();\n\n constructor(options: CreateSentinelOptions<T>, setup: (update: (value: T) => void) => () => void) {\n if (options.signal?.aborted) {\n throw new SentinelError('Sentinel signal is already aborted');\n }\n\n this.currentValue = options.initialValue;\n this.disposalSignal = this.abortController.signal;\n\n try {\n this.cleanup = setup((value) => {\n if (!this.disposedValue) {\n this.currentValue = value;\n this.notify();\n }\n });\n } catch (error) {\n this.disposedValue = true;\n this.abortController.abort();\n throw error;\n }\n\n if (!options.signal) return;\n\n this.externalSignal = options.signal;\n this.externalAbortListener = () => this.dispose();\n this.externalSignal.addEventListener('abort', this.externalAbortListener, { once: true });\n }\n\n get disposed(): boolean {\n return this.disposedValue;\n }\n\n readonly getSnapshot = (): T => this.currentValue;\n\n readonly subscribe = (listener: () => void): Unsubscribe => {\n if (this.disposedValue) return () => {};\n this.listeners.add(listener);\n return () => this.listeners.delete(listener);\n };\n\n readonly dispose = (): void => {\n if (this.disposedValue) return;\n this.disposedValue = true;\n\n if (this.externalSignal && this.externalAbortListener) {\n this.externalSignal.removeEventListener('abort', this.externalAbortListener);\n }\n\n const cleanup = this.cleanup;\n this.cleanup = undefined;\n this.externalSignal = undefined;\n this.externalAbortListener = undefined;\n this.listeners.clear();\n\n try {\n cleanup?.();\n } finally {\n this.tapper.emit({ type: 'dispose' });\n this.tapper.clear();\n this.abortController.abort();\n }\n };\n\n readonly tap = (handler: (event: SentinelEvent) => void, options?: { readonly signal?: AbortSignal }): Unsubscribe =>\n this.tapper.tap(handler, options);\n\n private notify(): void {\n for (const listener of [...this.listeners]) {\n try {\n listener();\n } catch (error) {\n this.tapper.emit({ error, type: 'error' });\n queueMicrotask(() => {\n throw error;\n });\n }\n }\n }\n\n [Symbol.dispose](): void {\n this.dispose();\n }\n}\n","import { SentinelUnavailableError } from './errors.ts';\n\ntype LegacyMediaQueryList = MediaQueryList & {\n addListener?: (listener: (event: MediaQueryListEvent) => void) => void;\n removeListener?: (listener: (event: MediaQueryListEvent) => void) => void;\n};\n\nexport type ObserverWindow = Window & {\n readonly IntersectionObserver?: {\n new (callback: IntersectionObserverCallback, options?: IntersectionObserverInit): IntersectionObserver;\n };\n readonly ResizeObserver?: {\n new (callback: ResizeObserverCallback): ResizeObserver;\n };\n};\n\nexport function resolveWindow(target?: Window): Window {\n if (target) return target;\n if (typeof window === 'undefined') {\n throw new SentinelUnavailableError('Window is not available in this environment.');\n }\n\n return window;\n}\n\nexport function resolveElementWindow(element: Element): ObserverWindow {\n const ownerWindow = element.ownerDocument.defaultView;\n if (!ownerWindow) {\n throw new SentinelUnavailableError('The element is not connected to a Window.');\n }\n\n return ownerWindow;\n}\n\nexport function listenMediaQuery(mediaQuery: MediaQueryList, listener: () => void): () => void {\n const onChange = () => listener();\n\n if (typeof mediaQuery.addEventListener === 'function') {\n mediaQuery.addEventListener('change', onChange);\n return () => mediaQuery.removeEventListener('change', onChange);\n }\n\n const legacyMediaQuery = mediaQuery as LegacyMediaQueryList;\n if (typeof legacyMediaQuery.addListener === 'function') {\n legacyMediaQuery.addListener(onChange);\n return () => legacyMediaQuery.removeListener?.(onChange);\n }\n\n return () => {};\n}\n","import { resolveElementWindow } from './_platform.ts';\nimport { createSentinel } from './core.ts';\nimport { SentinelUnavailableError } from './errors.ts';\nimport type { ElementSizeState, Sentinel, SentinelOptions } from './types.ts';\n\nexport function createElementSize(element: Element, options?: SentinelOptions): Sentinel<ElementSizeState | null> {\n const target = resolveElementWindow(element);\n const ResizeObserverConstructor = target.ResizeObserver;\n if (typeof ResizeObserverConstructor !== 'function') {\n throw new SentinelUnavailableError('ResizeObserver is not available in this environment.');\n }\n\n return createSentinel<ElementSizeState | null>(\n {\n initialValue: null,\n ...options,\n },\n (update) => {\n const observer = new ResizeObserverConstructor((entries) => {\n const entry = entries.at(-1);\n if (!entry) return;\n\n update({\n height: entry.contentRect.height,\n width: entry.contentRect.width,\n });\n });\n\n try {\n observer.observe(element, { box: 'content-box' });\n } catch (error) {\n observer.disconnect();\n throw error;\n }\n\n return () => observer.disconnect();\n },\n );\n}\n","import { resolveWindow } from './_platform.ts';\nimport { createSentinel } from './core.ts';\nimport type { FullscreenSentinel, FullscreenState, SentinelOptions } from './types.ts';\n\ntype FullscreenWindow = Window & { document: Document };\n\n/**\n * A sentinel-backed fullscreen view. `request()` enters fullscreen for an element (default:\n * the document element), `exit()` leaves it, and `toggle()` flips it — all degrade silently\n * when the Fullscreen API is missing or the browser refuses (user-activation requirements).\n * Disposing the sentinel exits fullscreen and stops tracking `fullscreenchange`.\n */\nexport function createFullscreen(\n options?: SentinelOptions & { readonly target?: FullscreenWindow },\n): FullscreenSentinel {\n const target = resolveWindow(options?.target);\n const doc = target.document;\n const supported = typeof doc.documentElement?.requestFullscreen === 'function';\n\n const read = (): FullscreenState => ({ active: doc.fullscreenElement !== null, supported });\n let updateState: (value: FullscreenState) => void = () => {};\n\n const request = (element?: Element): void => {\n if (doc.fullscreenElement || !supported) return;\n void (element ?? doc.documentElement).requestFullscreen().catch(() => {\n // Requires user activation in some browsers, or the element may refuse; degrade silently.\n });\n };\n\n const exit = (): void => {\n if (!doc.fullscreenElement) return;\n void doc.exitFullscreen().catch(() => {\n // Degrade silently.\n });\n };\n\n const sentinel = createSentinel<FullscreenState>({ initialValue: read(), ...options }, (update) => {\n updateState = update;\n\n const onFullscreenChange = (): void => updateState(read());\n\n doc.addEventListener('fullscreenchange', onFullscreenChange);\n\n return () => {\n doc.removeEventListener('fullscreenchange', onFullscreenChange);\n if (doc.fullscreenElement) exit();\n };\n });\n\n return {\n ...sentinel,\n exit,\n request,\n toggle(element?: Element): void {\n if (doc.fullscreenElement) exit();\n else request(element);\n },\n };\n}\n","import { resolveElementWindow } from './_platform.ts';\nimport { createSentinel } from './core.ts';\nimport { SentinelUnavailableError } from './errors.ts';\nimport type { IntersectionState, Sentinel, SentinelOptions } from './types.ts';\n\nexport interface CreateIntersectionOptions extends SentinelOptions {\n readonly root?: Element | Document | null;\n readonly rootMargin?: string;\n readonly scrollMargin?: string;\n readonly threshold?: number | number[];\n}\n\nexport function createIntersection(\n element: Element,\n options?: CreateIntersectionOptions,\n): Sentinel<IntersectionState | null> {\n const target = resolveElementWindow(element);\n const IntersectionObserverConstructor = target.IntersectionObserver;\n if (typeof IntersectionObserverConstructor !== 'function') {\n throw new SentinelUnavailableError('IntersectionObserver is not available in this environment.');\n }\n\n return createSentinel<IntersectionState | null>(\n {\n initialValue: null,\n ...options,\n },\n (update) => {\n const observer = new IntersectionObserverConstructor(\n (entries) => {\n const entry = entries.at(-1);\n if (!entry) return;\n\n update({\n intersectionRatio: entry.intersectionRatio,\n isIntersecting: entry.isIntersecting,\n });\n },\n {\n root: options?.root,\n rootMargin: options?.rootMargin,\n scrollMargin: options?.scrollMargin,\n threshold: options?.threshold,\n },\n );\n\n try {\n observer.observe(element);\n } catch (error) {\n observer.disconnect();\n throw error;\n }\n\n return () => observer.disconnect();\n },\n );\n}\n","import { listenMediaQuery, resolveWindow } from './_platform.ts';\nimport { createSentinel } from './core.ts';\nimport { SentinelUnavailableError } from './errors.ts';\nimport type { MediaQueryState, Sentinel, WindowSentinelOptions } from './types.ts';\n\nexport function createMediaQuery(query: string, options?: WindowSentinelOptions): Sentinel<MediaQueryState> {\n const target = resolveWindow(options?.target);\n if (typeof target.matchMedia !== 'function') {\n throw new SentinelUnavailableError(`matchMedia is not available for query \"${query}\".`);\n }\n\n const mediaQuery = target.matchMedia(query);\n const read = (): MediaQueryState => ({ matches: mediaQuery.matches });\n\n return createSentinel(\n {\n initialValue: read(),\n ...options,\n },\n (update) => listenMediaQuery(mediaQuery, () => update(read())),\n );\n}\n","import { resolveWindow } from './_platform.ts';\nimport { createSentinel } from './core.ts';\nimport type { NetworkConnectionSnapshot, NetworkState, Sentinel, WindowSentinelOptions } from './types.ts';\n\ninterface NetworkInformationLike extends EventTarget {\n readonly downlink?: number;\n readonly effectiveType?: NetworkConnectionSnapshot['effectiveType'];\n readonly rtt?: number;\n readonly saveData?: boolean;\n}\n\ntype NavigatorWithConnection = Navigator & {\n readonly connection?: NetworkInformationLike;\n};\n\nfunction normalizeConnection(connection?: NetworkInformationLike): NetworkConnectionSnapshot | null {\n if (!connection) return null;\n\n return {\n downlink: connection.downlink,\n effectiveType: connection.effectiveType,\n rtt: connection.rtt,\n saveData: connection.saveData,\n };\n}\n\nexport function createNetwork(options?: WindowSentinelOptions): Sentinel<NetworkState> {\n const target = resolveWindow(options?.target);\n const navigator = target.navigator as NavigatorWithConnection;\n const read = (): NetworkState => ({\n connection: normalizeConnection(navigator.connection),\n online: navigator.onLine,\n });\n\n return createSentinel(\n {\n initialValue: read(),\n ...options,\n },\n (update) => {\n const onChange = () => update(read());\n const connection = navigator.connection;\n\n target.addEventListener('online', onChange);\n target.addEventListener('offline', onChange);\n connection?.addEventListener('change', onChange);\n\n return () => {\n target.removeEventListener('online', onChange);\n target.removeEventListener('offline', onChange);\n connection?.removeEventListener('change', onChange);\n };\n },\n );\n}\n","import { listenMediaQuery, resolveWindow } from './_platform.ts';\nimport { createSentinel } from './core.ts';\nimport type { Sentinel, ViewportState, WindowSentinelOptions } from './types.ts';\n\nexport function createViewport(options?: WindowSentinelOptions): Sentinel<ViewportState> {\n const target = resolveWindow(options?.target);\n const read = (): ViewportState => ({\n dpr: target.devicePixelRatio,\n height: target.innerHeight,\n width: target.innerWidth,\n });\n\n return createSentinel(\n {\n initialValue: read(),\n ...options,\n },\n (update) => {\n const onResize = () => update(read());\n let stopDprObservation: (() => void) | undefined;\n\n const observeDpr = () => {\n stopDprObservation?.();\n stopDprObservation = undefined;\n\n if (typeof target.matchMedia !== 'function') return;\n\n const mediaQuery = target.matchMedia(`(resolution: ${target.devicePixelRatio}dppx)`);\n stopDprObservation = listenMediaQuery(mediaQuery, () => {\n update(read());\n observeDpr();\n });\n };\n\n target.addEventListener('resize', onResize);\n observeDpr();\n\n return () => {\n target.removeEventListener('resize', onResize);\n stopDprObservation?.();\n };\n },\n );\n}\n","import { resolveWindow } from './_platform.ts';\nimport { createSentinel } from './core.ts';\nimport type { SentinelOptions, WakeLockSentinel, WakeLockState } from './types.ts';\n\ntype WakeLockWindow = Window & { document: Document };\n\nexport function createWakeLock(options?: SentinelOptions & { readonly target?: WakeLockWindow }): WakeLockSentinel {\n const target = resolveWindow(options?.target);\n const navigator = target.navigator;\n const supported = typeof navigator.wakeLock?.request === 'function';\n\n let handle: globalThis.WakeLockSentinel | null = null;\n let pending = false;\n let wantsLock = false;\n\n const read = (): WakeLockState => ({ active: handle !== null, supported });\n let updateState: (value: WakeLockState) => void = () => {};\n\n async function acquire(): Promise<void> {\n if (!supported || !navigator.wakeLock) return;\n pending = true;\n try {\n const requested = await navigator.wakeLock.request('screen');\n // release()/dispose() during the pending request must not leave the lock held.\n if (!wantsLock) {\n void requested.release();\n\n return;\n }\n handle = requested;\n handle.addEventListener('release', () => {\n handle = null;\n updateState(read());\n });\n updateState(read());\n } catch {\n // Not supported, denied, or not in a secure context — degrade silently.\n } finally {\n pending = false;\n }\n }\n\n const sentinel = createSentinel<WakeLockState>({ initialValue: read(), ...options }, (update) => {\n updateState = update;\n\n const onVisibilityChange = (): void => {\n if (target.document.visibilityState === 'visible' && wantsLock && !handle && !pending) {\n void acquire();\n }\n };\n\n target.document.addEventListener('visibilitychange', onVisibilityChange);\n\n return () => {\n target.document.removeEventListener('visibilitychange', onVisibilityChange);\n if (handle) void handle.release();\n handle = null;\n wantsLock = false;\n };\n });\n\n return {\n ...sentinel,\n release(): void {\n wantsLock = false;\n if (handle) {\n void handle.release();\n handle = null;\n updateState(read());\n }\n },\n request(): void {\n if (handle || pending || !supported) return;\n wantsLock = true;\n void acquire();\n },\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"sentinel.js","names":[],"sources":["../src/errors.ts","../src/core.ts","../src/_platform.ts","../src/element-size.ts","../src/fullscreen.ts","../src/intersection.ts","../src/media-query.ts","../src/network.ts","../src/viewport.ts","../src/wake-lock.ts"],"sourcesContent":["export class SentinelError extends Error {\n constructor(message: string, options?: ErrorOptions) {\n super(message, options);\n this.name = new.target.name;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\nexport class SentinelUnavailableError extends SentinelError {}\n","import { type Tapper, tapper } from '@vielzeug/arsenal';\n\nimport { SentinelError } from './errors.ts';\nimport type { Sentinel, SentinelEvent, SentinelOptions, Unsubscribe } from './types.ts';\n\nexport interface CreateSentinelOptions<T> extends SentinelOptions {\n readonly initialValue: T;\n}\n\nexport function createSentinel<T>(\n options: CreateSentinelOptions<T>,\n setup: (update: (value: T) => void) => () => void,\n): Sentinel<T> {\n return new SentinelHandle(options, setup);\n}\n\nclass SentinelHandle<T> implements Sentinel<T> {\n readonly disposalSignal: AbortSignal;\n private readonly abortController = new AbortController();\n private currentValue: T;\n private readonly listeners = new Set<() => void>();\n private cleanup: (() => void) | undefined;\n private disposedValue = false;\n private externalSignal: AbortSignal | undefined;\n private externalAbortListener: (() => void) | undefined;\n private readonly tapper: Tapper<SentinelEvent> = tapper<SentinelEvent>();\n\n constructor(options: CreateSentinelOptions<T>, setup: (update: (value: T) => void) => () => void) {\n if (options.signal?.aborted) {\n throw new SentinelError('Sentinel signal is already aborted');\n }\n\n this.currentValue = options.initialValue;\n this.disposalSignal = this.abortController.signal;\n\n try {\n this.cleanup = setup((value) => {\n if (!this.disposedValue) {\n this.currentValue = value;\n this.notify();\n }\n });\n } catch (error) {\n this.disposedValue = true;\n this.abortController.abort();\n throw error;\n }\n\n if (!options.signal) return;\n\n this.externalSignal = options.signal;\n this.externalAbortListener = () => this.dispose();\n this.externalSignal.addEventListener('abort', this.externalAbortListener, { once: true });\n }\n\n get disposed(): boolean {\n return this.disposedValue;\n }\n\n readonly getSnapshot = (): T => this.currentValue;\n\n readonly subscribe = (listener: () => void): Unsubscribe => {\n if (this.disposedValue) return () => {};\n this.listeners.add(listener);\n return () => this.listeners.delete(listener);\n };\n\n readonly dispose = (): void => {\n if (this.disposedValue) return;\n this.disposedValue = true;\n\n if (this.externalSignal && this.externalAbortListener) {\n this.externalSignal.removeEventListener('abort', this.externalAbortListener);\n }\n\n const cleanup = this.cleanup;\n this.cleanup = undefined;\n this.externalSignal = undefined;\n this.externalAbortListener = undefined;\n this.listeners.clear();\n\n try {\n cleanup?.();\n } finally {\n this.tapper.emit({ type: 'dispose' });\n this.tapper.clear();\n this.abortController.abort();\n }\n };\n\n readonly tap = (handler: (event: SentinelEvent) => void, options?: { readonly signal?: AbortSignal }): Unsubscribe =>\n this.tapper.tap(handler, options);\n\n private notify(): void {\n for (const listener of [...this.listeners]) {\n try {\n listener();\n } catch (error) {\n this.tapper.emit({ error, type: 'error' });\n queueMicrotask(() => {\n throw error;\n });\n }\n }\n }\n\n [Symbol.dispose](): void {\n this.dispose();\n }\n}\n","import { SentinelUnavailableError } from './errors.ts';\n\ntype LegacyMediaQueryList = MediaQueryList & {\n addListener?: (listener: (event: MediaQueryListEvent) => void) => void;\n removeListener?: (listener: (event: MediaQueryListEvent) => void) => void;\n};\n\nexport type ObserverWindow = Window & {\n readonly IntersectionObserver?: {\n new (callback: IntersectionObserverCallback, options?: IntersectionObserverInit): IntersectionObserver;\n };\n readonly ResizeObserver?: {\n new (callback: ResizeObserverCallback): ResizeObserver;\n };\n};\n\nexport function resolveWindow(target?: Window): Window {\n if (target) return target;\n if (typeof window === 'undefined') {\n throw new SentinelUnavailableError('Window is not available in this environment.');\n }\n\n return window;\n}\n\nexport function resolveElementWindow(element: Element): ObserverWindow {\n const ownerWindow = element.ownerDocument.defaultView;\n if (!ownerWindow) {\n throw new SentinelUnavailableError('The element is not connected to a Window.');\n }\n\n return ownerWindow;\n}\n\nexport function listenMediaQuery(mediaQuery: MediaQueryList, listener: () => void): () => void {\n const onChange = () => listener();\n\n if (typeof mediaQuery.addEventListener === 'function') {\n mediaQuery.addEventListener('change', onChange);\n return () => mediaQuery.removeEventListener('change', onChange);\n }\n\n const legacyMediaQuery = mediaQuery as LegacyMediaQueryList;\n if (typeof legacyMediaQuery.addListener === 'function') {\n legacyMediaQuery.addListener(onChange);\n return () => legacyMediaQuery.removeListener?.(onChange);\n }\n\n return () => {};\n}\n","import { resolveElementWindow } from './_platform.ts';\nimport { createSentinel } from './core.ts';\nimport { SentinelUnavailableError } from './errors.ts';\nimport type { ElementSizeState, Sentinel, SentinelOptions } from './types.ts';\n\nexport function createElementSize(element: Element, options?: SentinelOptions): Sentinel<ElementSizeState | null> {\n const target = resolveElementWindow(element);\n const ResizeObserverConstructor = target.ResizeObserver;\n if (typeof ResizeObserverConstructor !== 'function') {\n throw new SentinelUnavailableError('ResizeObserver is not available in this environment.');\n }\n\n return createSentinel<ElementSizeState | null>(\n {\n initialValue: null,\n ...options,\n },\n (update) => {\n const observer = new ResizeObserverConstructor((entries) => {\n const entry = entries.at(-1);\n if (!entry) return;\n\n update({\n height: entry.contentRect.height,\n width: entry.contentRect.width,\n });\n });\n\n try {\n observer.observe(element, { box: 'content-box' });\n } catch (error) {\n observer.disconnect();\n throw error;\n }\n\n return () => observer.disconnect();\n },\n );\n}\n","import { resolveWindow } from './_platform.ts';\nimport { createSentinel } from './core.ts';\nimport type { FullscreenSentinel, FullscreenState, SentinelOptions } from './types.ts';\n\ntype FullscreenWindow = Window & { document: Document };\n\n/**\n * A sentinel-backed fullscreen view. `request()` enters fullscreen for an element (default:\n * the document element), `exit()` leaves it, and `toggle()` flips it: all degrade silently\n * when the Fullscreen API is missing or the browser refuses (user-activation requirements).\n * Disposing the sentinel exits fullscreen and stops tracking `fullscreenchange`.\n */\nexport function createFullscreen(\n options?: SentinelOptions & { readonly target?: FullscreenWindow },\n): FullscreenSentinel {\n const target = resolveWindow(options?.target);\n const doc = target.document;\n const supported = typeof doc.documentElement?.requestFullscreen === 'function';\n\n const read = (): FullscreenState => ({ active: doc.fullscreenElement !== null, supported });\n let updateState: (value: FullscreenState) => void = () => {};\n\n const request = (element?: Element): void => {\n if (doc.fullscreenElement || !supported) return;\n void (element ?? doc.documentElement).requestFullscreen().catch(() => {\n // Requires user activation in some browsers, or the element may refuse; degrade silently.\n });\n };\n\n const exit = (): void => {\n if (!doc.fullscreenElement) return;\n void doc.exitFullscreen().catch(() => {\n // Degrade silently.\n });\n };\n\n const sentinel = createSentinel<FullscreenState>({ initialValue: read(), ...options }, (update) => {\n updateState = update;\n\n const onFullscreenChange = (): void => updateState(read());\n\n doc.addEventListener('fullscreenchange', onFullscreenChange);\n\n return () => {\n doc.removeEventListener('fullscreenchange', onFullscreenChange);\n if (doc.fullscreenElement) exit();\n };\n });\n\n return {\n ...sentinel,\n exit,\n request,\n toggle(element?: Element): void {\n if (doc.fullscreenElement) exit();\n else request(element);\n },\n };\n}\n","import { resolveElementWindow } from './_platform.ts';\nimport { createSentinel } from './core.ts';\nimport { SentinelUnavailableError } from './errors.ts';\nimport type { IntersectionState, Sentinel, SentinelOptions } from './types.ts';\n\nexport interface CreateIntersectionOptions extends SentinelOptions {\n readonly root?: Element | Document | null;\n readonly rootMargin?: string;\n readonly scrollMargin?: string;\n readonly threshold?: number | number[];\n}\n\nexport function createIntersection(\n element: Element,\n options?: CreateIntersectionOptions,\n): Sentinel<IntersectionState | null> {\n const target = resolveElementWindow(element);\n const IntersectionObserverConstructor = target.IntersectionObserver;\n if (typeof IntersectionObserverConstructor !== 'function') {\n throw new SentinelUnavailableError('IntersectionObserver is not available in this environment.');\n }\n\n return createSentinel<IntersectionState | null>(\n {\n initialValue: null,\n ...options,\n },\n (update) => {\n const observer = new IntersectionObserverConstructor(\n (entries) => {\n const entry = entries.at(-1);\n if (!entry) return;\n\n update({\n intersectionRatio: entry.intersectionRatio,\n isIntersecting: entry.isIntersecting,\n });\n },\n {\n root: options?.root,\n rootMargin: options?.rootMargin,\n scrollMargin: options?.scrollMargin,\n threshold: options?.threshold,\n },\n );\n\n try {\n observer.observe(element);\n } catch (error) {\n observer.disconnect();\n throw error;\n }\n\n return () => observer.disconnect();\n },\n );\n}\n","import { listenMediaQuery, resolveWindow } from './_platform.ts';\nimport { createSentinel } from './core.ts';\nimport { SentinelUnavailableError } from './errors.ts';\nimport type { MediaQueryState, Sentinel, WindowSentinelOptions } from './types.ts';\n\nexport function createMediaQuery(query: string, options?: WindowSentinelOptions): Sentinel<MediaQueryState> {\n const target = resolveWindow(options?.target);\n if (typeof target.matchMedia !== 'function') {\n throw new SentinelUnavailableError(`matchMedia is not available for query \"${query}\".`);\n }\n\n const mediaQuery = target.matchMedia(query);\n const read = (): MediaQueryState => ({ matches: mediaQuery.matches });\n\n return createSentinel(\n {\n initialValue: read(),\n ...options,\n },\n (update) => listenMediaQuery(mediaQuery, () => update(read())),\n );\n}\n","import { resolveWindow } from './_platform.ts';\nimport { createSentinel } from './core.ts';\nimport type { NetworkConnectionSnapshot, NetworkState, Sentinel, WindowSentinelOptions } from './types.ts';\n\ninterface NetworkInformationLike extends EventTarget {\n readonly downlink?: number;\n readonly effectiveType?: NetworkConnectionSnapshot['effectiveType'];\n readonly rtt?: number;\n readonly saveData?: boolean;\n}\n\ntype NavigatorWithConnection = Navigator & {\n readonly connection?: NetworkInformationLike;\n};\n\nfunction normalizeConnection(connection?: NetworkInformationLike): NetworkConnectionSnapshot | null {\n if (!connection) return null;\n\n return {\n downlink: connection.downlink,\n effectiveType: connection.effectiveType,\n rtt: connection.rtt,\n saveData: connection.saveData,\n };\n}\n\nexport function createNetwork(options?: WindowSentinelOptions): Sentinel<NetworkState> {\n const target = resolveWindow(options?.target);\n const navigator = target.navigator as NavigatorWithConnection;\n const read = (): NetworkState => ({\n connection: normalizeConnection(navigator.connection),\n online: navigator.onLine,\n });\n\n return createSentinel(\n {\n initialValue: read(),\n ...options,\n },\n (update) => {\n const onChange = () => update(read());\n const connection = navigator.connection;\n\n target.addEventListener('online', onChange);\n target.addEventListener('offline', onChange);\n connection?.addEventListener('change', onChange);\n\n return () => {\n target.removeEventListener('online', onChange);\n target.removeEventListener('offline', onChange);\n connection?.removeEventListener('change', onChange);\n };\n },\n );\n}\n","import { listenMediaQuery, resolveWindow } from './_platform.ts';\nimport { createSentinel } from './core.ts';\nimport type { Sentinel, ViewportState, WindowSentinelOptions } from './types.ts';\n\nexport function createViewport(options?: WindowSentinelOptions): Sentinel<ViewportState> {\n const target = resolveWindow(options?.target);\n const read = (): ViewportState => ({\n dpr: target.devicePixelRatio,\n height: target.innerHeight,\n width: target.innerWidth,\n });\n\n return createSentinel(\n {\n initialValue: read(),\n ...options,\n },\n (update) => {\n const onResize = () => update(read());\n let stopDprObservation: (() => void) | undefined;\n\n const observeDpr = () => {\n stopDprObservation?.();\n stopDprObservation = undefined;\n\n if (typeof target.matchMedia !== 'function') return;\n\n const mediaQuery = target.matchMedia(`(resolution: ${target.devicePixelRatio}dppx)`);\n stopDprObservation = listenMediaQuery(mediaQuery, () => {\n update(read());\n observeDpr();\n });\n };\n\n target.addEventListener('resize', onResize);\n observeDpr();\n\n return () => {\n target.removeEventListener('resize', onResize);\n stopDprObservation?.();\n };\n },\n );\n}\n","import { resolveWindow } from './_platform.ts';\nimport { createSentinel } from './core.ts';\nimport type { SentinelOptions, WakeLockSentinel, WakeLockState } from './types.ts';\n\ntype WakeLockWindow = Window & { document: Document };\n\nexport function createWakeLock(options?: SentinelOptions & { readonly target?: WakeLockWindow }): WakeLockSentinel {\n const target = resolveWindow(options?.target);\n const navigator = target.navigator;\n const supported = typeof navigator.wakeLock?.request === 'function';\n\n let handle: globalThis.WakeLockSentinel | null = null;\n let pending = false;\n let wantsLock = false;\n\n const read = (): WakeLockState => ({ active: handle !== null, supported });\n let updateState: (value: WakeLockState) => void = () => {};\n\n async function acquire(): Promise<void> {\n if (!supported || !navigator.wakeLock) return;\n pending = true;\n try {\n const requested = await navigator.wakeLock.request('screen');\n // release()/dispose() during the pending request must not leave the lock held.\n if (!wantsLock) {\n void requested.release();\n\n return;\n }\n handle = requested;\n handle.addEventListener('release', () => {\n handle = null;\n updateState(read());\n });\n updateState(read());\n } catch {\n // Not supported, denied, or not in a secure context: degrade silently.\n } finally {\n pending = false;\n }\n }\n\n const sentinel = createSentinel<WakeLockState>({ initialValue: read(), ...options }, (update) => {\n updateState = update;\n\n const onVisibilityChange = (): void => {\n if (target.document.visibilityState === 'visible' && wantsLock && !handle && !pending) {\n void acquire();\n }\n };\n\n target.document.addEventListener('visibilitychange', onVisibilityChange);\n\n return () => {\n target.document.removeEventListener('visibilitychange', onVisibilityChange);\n if (handle) void handle.release();\n handle = null;\n wantsLock = false;\n };\n });\n\n return {\n ...sentinel,\n release(): void {\n wantsLock = false;\n if (handle) {\n void handle.release();\n handle = null;\n updateState(read());\n }\n },\n request(): void {\n if (handle || pending || !supported) return;\n wantsLock = true;\n void acquire();\n },\n };\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"wake-lock.cjs","names":[],"sources":["../src/wake-lock.ts"],"sourcesContent":["import { resolveWindow } from './_platform.ts';\nimport { createSentinel } from './core.ts';\nimport type { SentinelOptions, WakeLockSentinel, WakeLockState } from './types.ts';\n\ntype WakeLockWindow = Window & { document: Document };\n\nexport function createWakeLock(options?: SentinelOptions & { readonly target?: WakeLockWindow }): WakeLockSentinel {\n const target = resolveWindow(options?.target);\n const navigator = target.navigator;\n const supported = typeof navigator.wakeLock?.request === 'function';\n\n let handle: globalThis.WakeLockSentinel | null = null;\n let pending = false;\n let wantsLock = false;\n\n const read = (): WakeLockState => ({ active: handle !== null, supported });\n let updateState: (value: WakeLockState) => void = () => {};\n\n async function acquire(): Promise<void> {\n if (!supported || !navigator.wakeLock) return;\n pending = true;\n try {\n const requested = await navigator.wakeLock.request('screen');\n // release()/dispose() during the pending request must not leave the lock held.\n if (!wantsLock) {\n void requested.release();\n\n return;\n }\n handle = requested;\n handle.addEventListener('release', () => {\n handle = null;\n updateState(read());\n });\n updateState(read());\n } catch {\n // Not supported, denied, or not in a secure context — degrade silently.\n } finally {\n pending = false;\n }\n }\n\n const sentinel = createSentinel<WakeLockState>({ initialValue: read(), ...options }, (update) => {\n updateState = update;\n\n const onVisibilityChange = (): void => {\n if (target.document.visibilityState === 'visible' && wantsLock && !handle && !pending) {\n void acquire();\n }\n };\n\n target.document.addEventListener('visibilitychange', onVisibilityChange);\n\n return () => {\n target.document.removeEventListener('visibilitychange', onVisibilityChange);\n if (handle) void handle.release();\n handle = null;\n wantsLock = false;\n };\n });\n\n return {\n ...sentinel,\n release(): void {\n wantsLock = false;\n if (handle) {\n void handle.release();\n handle = null;\n updateState(read());\n }\n },\n request(): void {\n if (handle || pending || !supported) return;\n wantsLock = true;\n void acquire();\n },\n };\n}\n"],"mappings":"2DAMA,SAAgB,EAAe,EAAoF,CACjH,IAAM,EAAS,EAAA,cAAc,GAAS,MAAM,EACtC,EAAY,EAAO,UACnB,EAAY,OAAO,EAAU,UAAU,SAAY,WAErD,EAA6C,KAC7C,EAAU,GACV,EAAY,GAEV,OAA6B,CAAE,OAAQ,IAAW,KAAM,WAAU,GACpE,MAAoD,CAAC,EAEzD,eAAe,GAAyB,CAClC,GAAC,GAAc,EAAU,SAC7B,GAAU,GACV,GAAI,CACF,IAAM,EAAY,MAAM,EAAU,SAAS,QAAQ,QAAQ,EAE3D,GAAI,CAAC,EAAW,CACd,EAAe,QAAQ,EAEvB,MACF,CACA,EAAS,EACT,EAAO,iBAAiB,cAAiB,CACvC,EAAS,KACT,EAAY,EAAK,CAAC,CACpB,CAAC,EACD,EAAY,EAAK,CAAC,CACpB,MAAQ,CAER,QAAU,CACR,EAAU,EACZ,CAnBU,CAoBZ,CAqBA,MAAO,CACL,GApBe,EAAA,eAA8B,CAAE,aAAc,EAAK,EAAG,GAAG,CAAQ,EAAI,GAAW,CAC/F,EAAc,EAEd,IAAM,MAAiC,CACjC,EAAO,SAAS,kBAAoB,WAAa,GAAa,CAAC,GAAU,CAAC,GAC5E,EAAa,CAEjB,EAIA,OAFA,EAAO,SAAS,iBAAiB,mBAAoB,CAAkB,MAE1D,CACX,EAAO,SAAS,oBAAoB,mBAAoB,CAAkB,EACtE,GAAQ,EAAY,QAAQ,EAChC,EAAS,KACT,EAAY,EACd,CACF,CAGK,EACH,SAAgB,CACd,EAAY,GACR,IACF,EAAY,QAAQ,EACpB,EAAS,KACT,EAAY,EAAK,CAAC,EAEtB,EACA,SAAgB,CACV,GAAU,GAAW,CAAC,IAC1B,EAAY,GACZ,EAAa,EACf,CACF,CACF"}
1
+ {"version":3,"file":"wake-lock.cjs","names":[],"sources":["../src/wake-lock.ts"],"sourcesContent":["import { resolveWindow } from './_platform.ts';\nimport { createSentinel } from './core.ts';\nimport type { SentinelOptions, WakeLockSentinel, WakeLockState } from './types.ts';\n\ntype WakeLockWindow = Window & { document: Document };\n\nexport function createWakeLock(options?: SentinelOptions & { readonly target?: WakeLockWindow }): WakeLockSentinel {\n const target = resolveWindow(options?.target);\n const navigator = target.navigator;\n const supported = typeof navigator.wakeLock?.request === 'function';\n\n let handle: globalThis.WakeLockSentinel | null = null;\n let pending = false;\n let wantsLock = false;\n\n const read = (): WakeLockState => ({ active: handle !== null, supported });\n let updateState: (value: WakeLockState) => void = () => {};\n\n async function acquire(): Promise<void> {\n if (!supported || !navigator.wakeLock) return;\n pending = true;\n try {\n const requested = await navigator.wakeLock.request('screen');\n // release()/dispose() during the pending request must not leave the lock held.\n if (!wantsLock) {\n void requested.release();\n\n return;\n }\n handle = requested;\n handle.addEventListener('release', () => {\n handle = null;\n updateState(read());\n });\n updateState(read());\n } catch {\n // Not supported, denied, or not in a secure context: degrade silently.\n } finally {\n pending = false;\n }\n }\n\n const sentinel = createSentinel<WakeLockState>({ initialValue: read(), ...options }, (update) => {\n updateState = update;\n\n const onVisibilityChange = (): void => {\n if (target.document.visibilityState === 'visible' && wantsLock && !handle && !pending) {\n void acquire();\n }\n };\n\n target.document.addEventListener('visibilitychange', onVisibilityChange);\n\n return () => {\n target.document.removeEventListener('visibilitychange', onVisibilityChange);\n if (handle) void handle.release();\n handle = null;\n wantsLock = false;\n };\n });\n\n return {\n ...sentinel,\n release(): void {\n wantsLock = false;\n if (handle) {\n void handle.release();\n handle = null;\n updateState(read());\n }\n },\n request(): void {\n if (handle || pending || !supported) return;\n wantsLock = true;\n void acquire();\n },\n };\n}\n"],"mappings":"2DAMA,SAAgB,EAAe,EAAoF,CACjH,IAAM,EAAS,EAAA,cAAc,GAAS,MAAM,EACtC,EAAY,EAAO,UACnB,EAAY,OAAO,EAAU,UAAU,SAAY,WAErD,EAA6C,KAC7C,EAAU,GACV,EAAY,GAEV,OAA6B,CAAE,OAAQ,IAAW,KAAM,WAAU,GACpE,MAAoD,CAAC,EAEzD,eAAe,GAAyB,CAClC,GAAC,GAAc,EAAU,SAC7B,GAAU,GACV,GAAI,CACF,IAAM,EAAY,MAAM,EAAU,SAAS,QAAQ,QAAQ,EAE3D,GAAI,CAAC,EAAW,CACd,EAAe,QAAQ,EAEvB,MACF,CACA,EAAS,EACT,EAAO,iBAAiB,cAAiB,CACvC,EAAS,KACT,EAAY,EAAK,CAAC,CACpB,CAAC,EACD,EAAY,EAAK,CAAC,CACpB,MAAQ,CAER,QAAU,CACR,EAAU,EACZ,CAnBU,CAoBZ,CAqBA,MAAO,CACL,GApBe,EAAA,eAA8B,CAAE,aAAc,EAAK,EAAG,GAAG,CAAQ,EAAI,GAAW,CAC/F,EAAc,EAEd,IAAM,MAAiC,CACjC,EAAO,SAAS,kBAAoB,WAAa,GAAa,CAAC,GAAU,CAAC,GAC5E,EAAa,CAEjB,EAIA,OAFA,EAAO,SAAS,iBAAiB,mBAAoB,CAAkB,MAE1D,CACX,EAAO,SAAS,oBAAoB,mBAAoB,CAAkB,EACtE,GAAQ,EAAY,QAAQ,EAChC,EAAS,KACT,EAAY,EACd,CACF,CAGK,EACH,SAAgB,CACd,EAAY,GACR,IACF,EAAY,QAAQ,EACpB,EAAS,KACT,EAAY,EAAK,CAAC,EAEtB,EACA,SAAgB,CACV,GAAU,GAAW,CAAC,IAC1B,EAAY,GACZ,EAAa,EACf,CACF,CACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"wake-lock.js","names":[],"sources":["../src/wake-lock.ts"],"sourcesContent":["import { resolveWindow } from './_platform.ts';\nimport { createSentinel } from './core.ts';\nimport type { SentinelOptions, WakeLockSentinel, WakeLockState } from './types.ts';\n\ntype WakeLockWindow = Window & { document: Document };\n\nexport function createWakeLock(options?: SentinelOptions & { readonly target?: WakeLockWindow }): WakeLockSentinel {\n const target = resolveWindow(options?.target);\n const navigator = target.navigator;\n const supported = typeof navigator.wakeLock?.request === 'function';\n\n let handle: globalThis.WakeLockSentinel | null = null;\n let pending = false;\n let wantsLock = false;\n\n const read = (): WakeLockState => ({ active: handle !== null, supported });\n let updateState: (value: WakeLockState) => void = () => {};\n\n async function acquire(): Promise<void> {\n if (!supported || !navigator.wakeLock) return;\n pending = true;\n try {\n const requested = await navigator.wakeLock.request('screen');\n // release()/dispose() during the pending request must not leave the lock held.\n if (!wantsLock) {\n void requested.release();\n\n return;\n }\n handle = requested;\n handle.addEventListener('release', () => {\n handle = null;\n updateState(read());\n });\n updateState(read());\n } catch {\n // Not supported, denied, or not in a secure context — degrade silently.\n } finally {\n pending = false;\n }\n }\n\n const sentinel = createSentinel<WakeLockState>({ initialValue: read(), ...options }, (update) => {\n updateState = update;\n\n const onVisibilityChange = (): void => {\n if (target.document.visibilityState === 'visible' && wantsLock && !handle && !pending) {\n void acquire();\n }\n };\n\n target.document.addEventListener('visibilitychange', onVisibilityChange);\n\n return () => {\n target.document.removeEventListener('visibilitychange', onVisibilityChange);\n if (handle) void handle.release();\n handle = null;\n wantsLock = false;\n };\n });\n\n return {\n ...sentinel,\n release(): void {\n wantsLock = false;\n if (handle) {\n void handle.release();\n handle = null;\n updateState(read());\n }\n },\n request(): void {\n if (handle || pending || !supported) return;\n wantsLock = true;\n void acquire();\n },\n };\n}\n"],"mappings":"0FAMA,SAAgB,EAAe,EAAoF,CACjH,IAAM,EAAS,EAAc,GAAS,MAAM,EACtC,EAAY,EAAO,UACnB,EAAY,OAAO,EAAU,UAAU,SAAY,WAErD,EAA6C,KAC7C,EAAU,GACV,EAAY,GAEV,OAA6B,CAAE,OAAQ,IAAW,KAAM,WAAU,GACpE,MAAoD,CAAC,EAEzD,eAAe,GAAyB,CAClC,GAAC,GAAc,EAAU,SAC7B,GAAU,GACV,GAAI,CACF,IAAM,EAAY,MAAM,EAAU,SAAS,QAAQ,QAAQ,EAE3D,GAAI,CAAC,EAAW,CACd,EAAe,QAAQ,EAEvB,MACF,CACA,EAAS,EACT,EAAO,iBAAiB,cAAiB,CACvC,EAAS,KACT,EAAY,EAAK,CAAC,CACpB,CAAC,EACD,EAAY,EAAK,CAAC,CACpB,MAAQ,CAER,QAAU,CACR,EAAU,EACZ,CAnBU,CAoBZ,CAqBA,MAAO,CACL,GApBe,EAA8B,CAAE,aAAc,EAAK,EAAG,GAAG,CAAQ,EAAI,GAAW,CAC/F,EAAc,EAEd,IAAM,MAAiC,CACjC,EAAO,SAAS,kBAAoB,WAAa,GAAa,CAAC,GAAU,CAAC,GAC5E,EAAa,CAEjB,EAIA,OAFA,EAAO,SAAS,iBAAiB,mBAAoB,CAAkB,MAE1D,CACX,EAAO,SAAS,oBAAoB,mBAAoB,CAAkB,EACtE,GAAQ,EAAY,QAAQ,EAChC,EAAS,KACT,EAAY,EACd,CACF,CAGK,EACH,SAAgB,CACd,EAAY,GACR,IACF,EAAY,QAAQ,EACpB,EAAS,KACT,EAAY,EAAK,CAAC,EAEtB,EACA,SAAgB,CACV,GAAU,GAAW,CAAC,IAC1B,EAAY,GACZ,EAAa,EACf,CACF,CACF"}
1
+ {"version":3,"file":"wake-lock.js","names":[],"sources":["../src/wake-lock.ts"],"sourcesContent":["import { resolveWindow } from './_platform.ts';\nimport { createSentinel } from './core.ts';\nimport type { SentinelOptions, WakeLockSentinel, WakeLockState } from './types.ts';\n\ntype WakeLockWindow = Window & { document: Document };\n\nexport function createWakeLock(options?: SentinelOptions & { readonly target?: WakeLockWindow }): WakeLockSentinel {\n const target = resolveWindow(options?.target);\n const navigator = target.navigator;\n const supported = typeof navigator.wakeLock?.request === 'function';\n\n let handle: globalThis.WakeLockSentinel | null = null;\n let pending = false;\n let wantsLock = false;\n\n const read = (): WakeLockState => ({ active: handle !== null, supported });\n let updateState: (value: WakeLockState) => void = () => {};\n\n async function acquire(): Promise<void> {\n if (!supported || !navigator.wakeLock) return;\n pending = true;\n try {\n const requested = await navigator.wakeLock.request('screen');\n // release()/dispose() during the pending request must not leave the lock held.\n if (!wantsLock) {\n void requested.release();\n\n return;\n }\n handle = requested;\n handle.addEventListener('release', () => {\n handle = null;\n updateState(read());\n });\n updateState(read());\n } catch {\n // Not supported, denied, or not in a secure context: degrade silently.\n } finally {\n pending = false;\n }\n }\n\n const sentinel = createSentinel<WakeLockState>({ initialValue: read(), ...options }, (update) => {\n updateState = update;\n\n const onVisibilityChange = (): void => {\n if (target.document.visibilityState === 'visible' && wantsLock && !handle && !pending) {\n void acquire();\n }\n };\n\n target.document.addEventListener('visibilitychange', onVisibilityChange);\n\n return () => {\n target.document.removeEventListener('visibilitychange', onVisibilityChange);\n if (handle) void handle.release();\n handle = null;\n wantsLock = false;\n };\n });\n\n return {\n ...sentinel,\n release(): void {\n wantsLock = false;\n if (handle) {\n void handle.release();\n handle = null;\n updateState(read());\n }\n },\n request(): void {\n if (handle || pending || !supported) return;\n wantsLock = true;\n void acquire();\n },\n };\n}\n"],"mappings":"0FAMA,SAAgB,EAAe,EAAoF,CACjH,IAAM,EAAS,EAAc,GAAS,MAAM,EACtC,EAAY,EAAO,UACnB,EAAY,OAAO,EAAU,UAAU,SAAY,WAErD,EAA6C,KAC7C,EAAU,GACV,EAAY,GAEV,OAA6B,CAAE,OAAQ,IAAW,KAAM,WAAU,GACpE,MAAoD,CAAC,EAEzD,eAAe,GAAyB,CAClC,GAAC,GAAc,EAAU,SAC7B,GAAU,GACV,GAAI,CACF,IAAM,EAAY,MAAM,EAAU,SAAS,QAAQ,QAAQ,EAE3D,GAAI,CAAC,EAAW,CACd,EAAe,QAAQ,EAEvB,MACF,CACA,EAAS,EACT,EAAO,iBAAiB,cAAiB,CACvC,EAAS,KACT,EAAY,EAAK,CAAC,CACpB,CAAC,EACD,EAAY,EAAK,CAAC,CACpB,MAAQ,CAER,QAAU,CACR,EAAU,EACZ,CAnBU,CAoBZ,CAqBA,MAAO,CACL,GApBe,EAA8B,CAAE,aAAc,EAAK,EAAG,GAAG,CAAQ,EAAI,GAAW,CAC/F,EAAc,EAEd,IAAM,MAAiC,CACjC,EAAO,SAAS,kBAAoB,WAAa,GAAa,CAAC,GAAU,CAAC,GAC5E,EAAa,CAEjB,EAIA,OAFA,EAAO,SAAS,iBAAiB,mBAAoB,CAAkB,MAE1D,CACX,EAAO,SAAS,oBAAoB,mBAAoB,CAAkB,EACtE,GAAQ,EAAY,QAAQ,EAChC,EAAS,KACT,EAAY,EACd,CACF,CAGK,EACH,SAAgB,CACd,EAAY,GACR,IACF,EAAY,QAAQ,EACpB,EAAS,KACT,EAAY,EAAK,CAAC,EAEtB,EACA,SAAgB,CACV,GAAU,GAAW,CAAC,IAC1B,EAAY,GACZ,EAAa,EACf,CACF,CACF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vielzeug/sentinel",
3
- "version": "26.10.0",
3
+ "version": "26.10.5",
4
4
  "description": "Subscribable snapshots for external browser environment state",
5
5
  "repository": {
6
6
  "type": "git",
@@ -40,7 +40,7 @@
40
40
  "test:e2e": "pnpm --silent run build && playwright test"
41
41
  },
42
42
  "dependencies": {
43
- "@vielzeug/arsenal": "26.10.0"
43
+ "@vielzeug/arsenal": "26.10.5"
44
44
  },
45
45
  "devDependencies": {
46
46
  "@playwright/test": "^1.63.0",