prop-for-that 0.6.4

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/CHANGELOG.md ADDED
@@ -0,0 +1,369 @@
1
+ # Changelog
2
+
3
+ All notable changes to **prop-for-that** are recorded here. Format follows
4
+ [Keep a Changelog](https://keepachangelog.com/). While the package is `0.x`, a
5
+ **minor** bump signals a breaking change and a **patch** bump signals a
6
+ backwards-compatible change (semver's `1.0.0`+ rules kick in at v1).
7
+
8
+ Only the published library (`dist/`) is versioned here; the demo and docs site
9
+ are repo-only and not part of the npm package.
10
+
11
+ ## [0.6.4]
12
+
13
+ ### Added
14
+ - **`nav-type` plugin** — `--const-nav-type`: how the user arrived at the page,
15
+ written once as a string (`navigate` / `reload` / `back_forward` / `prerender`)
16
+ from `performance.getEntriesByType('navigation')[0].type`. Pairs with style
17
+ queries — `@container style(--const-nav-type: reload) { … }`. Global scope,
18
+ `const` cadence; typed mode registers it as a `<custom-ident>`. Import as
19
+ `navType` from `prop-for-that/plugins`. (Rides the batched writer, so it lands
20
+ the frame after bind — read the same value in a `<head>` script if you need it
21
+ before first paint.)
22
+
23
+ ## [0.6.3]
24
+
25
+ ### Added
26
+ - **`page-visible` plugin** — `--live-page-visible` (1/0): whether the tab is in
27
+ the foreground vs hidden (backgrounded, minimized, switched away). The companion
28
+ to `page-focused` — a *visible* tab can still be *unfocused* — for pausing video,
29
+ polling, or expensive effects when the page is truly hidden. Global scope, seeded
30
+ from `document.visibilityState` and driven by `visibilitychange`. Import as
31
+ `pageVisible` from `prop-for-that/plugins`.
32
+ - Internal: `onDocument` — a shared, ref-counted passive `document`-listener helper
33
+ (the document-target twin of `onWindow`), so sources can subscribe to
34
+ `document`-only events (`visibilitychange`, Page Lifecycle `freeze`/`resume`)
35
+ without a raw `addEventListener`.
36
+
37
+ ## [0.6.2]
38
+
39
+ ### Added
40
+ - **`page-focused` plugin** — `--live-page-focused` (1/0): whether the document
41
+ has focus (tab frontmost *and* window focused). Goes `0` when the user switches
42
+ to another tab, window, or app, and `1` on return. Global scope, seeded from
43
+ `document.hasFocus()` and driven by window `focus`/`blur`. Import as
44
+ `pageFocused` from `prop-for-that/plugins`.
45
+
46
+ ## [0.6.1]
47
+
48
+ ### Added
49
+ - **`--const-mem` head constant** — `prop-for-that/head` now writes
50
+ `navigator.deviceMemory` (approximate device RAM in GiB) alongside the existing
51
+ scrollbar/DPR/core constants. Chromium-only and deliberately coarse (`0.25`–`8`,
52
+ capped for fingerprinting resistance); falls back to `0` where unsupported,
53
+ matching the `--const-cores` pattern.
54
+
55
+ ## [0.6.0]
56
+
57
+ ### Changed
58
+ - **`pointer-local` plugin — renamed its emitted properties (breaking).**
59
+ `--live-px` → `--live-local-pointer-x-ratio`, `--live-py` →
60
+ `--live-local-pointer-y-ratio`, `--live-pointer-inside` →
61
+ `--live-local-pointer-inside`. The element-scoped pointer now shares the
62
+ `pointer` namespace with a `local-` qualifier, so the two are distinguishable
63
+ at a glance, and the `-ratio` suffix keeps units honest against the viewport
64
+ `pointer` source (`--live-pointer-x` is pixels; `*-ratio` is 0–1). The global
65
+ `pointer` source's properties are unchanged. Update any CSS reading the old
66
+ names.
67
+
68
+ ## [0.5.2]
69
+
70
+ ### Added
71
+ - **`select` plugin** (`prop-for-that/plugins`) — a native `<select>`'s state as
72
+ numbers CSS can't reach: `--live-index` (`selectedIndex`, `-1` if none),
73
+ `--live-option-count`, `--live-index-pct` (0–1), `--live-value-num`
74
+ (`Number(value)`, only when the value is numeric), `--live-selected-count` and
75
+ `--live-selected-pct` (0–1) — the multi-select tally `:checked` can't count.
76
+ Turns a `<select>` into a discrete CSS state machine (slide a segmented
77
+ indicator off `--live-index`) or drives layout off the chosen value. Bind the
78
+ `<select>` or a container.
79
+ - **`color-input` plugin** (`prop-for-that/plugins`) — `--live-color`, the colour
80
+ chosen in an `<input type="color">` as one sRGB hex string (CSS otherwise can't
81
+ read it); extract channels with relative colour syntax / `color-mix()`. Typed
82
+ mode registers it `<color>` so a picker can live-theme + interpolate a region
83
+ with zero consumer JS. Bind the input or a container.
84
+ - **`field` plugin extended** — the per-reason `ValidityState` flags `:invalid`
85
+ can't distinguish (each 1/0): `--live-value-missing`, `--live-type-mismatch`,
86
+ `--live-pattern-mismatch`, `--live-too-long`, `--live-too-short`,
87
+ `--live-range-underflow`, `--live-range-overflow`, `--live-step-mismatch`,
88
+ `--live-bad-input`, `--live-custom-error`; plus a `maxlength` budget when one is
89
+ set: `--live-remaining` (chars left) and `--live-fill-pct` (0–1) for a pure-CSS
90
+ character counter. The budget props aren't written without a `maxlength`, so
91
+ keep a `var()` fallback.
92
+
93
+ ## [0.5.1]
94
+
95
+ ### Added
96
+ - **`data-props-typed` on the root `<html>`** — the markup equivalent of
97
+ `configure({ typed: true })` for the `auto` entry. Read once on load, it
98
+ registers every written `--live-*` as an interpolatable `@property`, so
99
+ HTML-only (`data-props-for`) pages can opt into typed values without touching
100
+ JS. It's a boolean: `@property` is registered per name for the whole document,
101
+ so typing is all-or-nothing and any attribute value is ignored (there's no
102
+ per-key subset, matching the JS API). For per-property initial values, use the
103
+ JS `configure({ defaults })`.
104
+
105
+ ## [0.5.0]
106
+
107
+ ### Changed
108
+ - **BREAKING: `imgColor` and `videoColor` now emit single `#rrggbb` colours, not
109
+ RGB channels.** The canvas pixels are sRGB, so a hex colour is sufficient — and
110
+ CSS already extracts whatever channels you need from a colour with relative
111
+ colour syntax (`oklch(from var(--live-img) l c h)`) or `color-mix()`, so sharing
112
+ separate channel props was redundant. This replaces the per-channel `r` / `g` /
113
+ `b` props and the `l` luminance prop:
114
+ - `imgColor`: `--live-img`, `--live-img-accent`, `--live-img-dark`,
115
+ `--live-img-light`, `--live-img-avg` (each was `…-r` / `-g` / `-b` / `-l`).
116
+ `--live-img-temp` is unchanged.
117
+ - `videoColor`: `--live-video`, `--live-video-accent` (each was `…-r/g/b/l`).
118
+ - To pick legible text, derive it from the colour's own lightness instead of a
119
+ luminance prop, e.g. `color: oklch(from var(--live-img) clamp(0, (0.6 - l) * 9, 1) 0 0)`.
120
+ - Both sources now declare `@property` specs (`<color>`), so the live colours
121
+ interpolate smoothly under `configure({ typed: true })`.
122
+
123
+ ## [0.4.6]
124
+
125
+ ### Added
126
+ - **`videoColor` now also writes a live accent colour** — `--live-video-accent-r`
127
+ / `-g` / `-b` / `-l`, the most vibrant colour of the current frame (reuses the
128
+ dominant when a frame is essentially grayscale). It reuses the same `palette()`
129
+ pass as `imgColor`, so the accent is free beyond a few ops per sample; the plugin
130
+ stays dominant + accent only (no full palette) since a six-swatch extraction
131
+ isn't worth running 4×/second for an ambient glow. The existing
132
+ `--live-video-r/g/b/l` dominant props are unchanged.
133
+
134
+ ### Changed
135
+ - Removed the now-unused internal `dominantColor()` from `_color.ts`; `videoColor`
136
+ reads the dominant from `palette()` instead. Internal only — no public API or
137
+ behaviour change.
138
+
139
+ ## [0.4.5]
140
+
141
+ ### Added
142
+ - **`imgColor` now extracts a small palette**, not just the dominant colour. Four
143
+ new swatches, each in the same `r` / `g` / `b` (`0`–`255`) + `l` (luminance
144
+ `0`–`1`) shape as the dominant: `--live-img-accent-*` (the most vibrant colour —
145
+ reuses the dominant for a grayscale image), `--live-img-dark-*` (darkest
146
+ non-black), `--live-img-light-*` (lightest non-white), and `--live-img-avg-*`
147
+ (the mean of every pixel, vs the dominant's mode). Plus a scalar
148
+ `--live-img-temp` (`−1` cool … `+1` warm) from the image's red-vs-blue balance.
149
+ All of it falls out of the **same single 16×16 bucketing pass** — no extra image
150
+ reads — so it's barely dearer than before. The existing `--live-img-r/g/b/l`
151
+ dominant props are unchanged.
152
+
153
+ ### Changed
154
+ - `_color.ts` gains a shared `buildBuckets()` helper and a `palette()` extractor;
155
+ `dominantColor()` (used by `videoColor`) is refactored onto it with no behaviour
156
+ change, so `videoColor` stays dominant-only and pays nothing for the new palette.
157
+
158
+ ## [0.4.4]
159
+
160
+ ### Added
161
+ - **`videoColor` plugin** (`prop-for-that/plugins`, key `video-color`, element).
162
+ The dominant colour of a playing `<video>` — `--live-video-r` / `-g` / `-b`
163
+ (`0`–`255`) plus `--live-video-l` (relative luminance `0`–`1`) — for an ambient
164
+ glow, scrim, or chrome that tracks the picture. Sampling rides
165
+ `requestVideoFrameCallback` (so it stops when the video is paused, offscreen, or
166
+ backgrounded) and is throttled to ~4 Hz on top; it seeds a paused/poster frame
167
+ on attach and falls back to the `timeupdate` event where
168
+ `requestVideoFrameCallback` is unavailable. The current frame is read 16×16 from
169
+ a canvas, reusing `img-color`'s dominant-colour path. Cross-origin video needs
170
+ `crossorigin="anonymous"` + permissive CORS headers, else the canvas is tainted
171
+ and the plugin writes nothing (`var()` fallbacks stay safe). Bind the `<video>`
172
+ or a container holding one.
173
+
174
+ ### Changed
175
+ - The shared pixel-sampling + dominant-colour logic now lives in
176
+ `src/plugins/_color.ts`, reused by both `imgColor` and `videoColor`. No
177
+ behaviour change to `imgColor`.
178
+
179
+ ## [0.4.3]
180
+
181
+ ### Added
182
+ - **`formState` plugin** (`prop-for-that/plugins`, key `form-state`, element).
183
+ Form-level **validity & completion** that CSS can't compute — `:invalid` matches
184
+ one control, but CSS can't count across a form or gate on "all valid." Bind it to
185
+ a `<form>` (or wrapper) for `--live-field-count` (controls subject to constraint
186
+ validation), `--live-valid-count` / `--live-invalid-count`, `--live-all-valid`
187
+ (`1`/`0` — the submit gate), and `--live-completion` (`0`–`1`: valid required
188
+ controls ÷ required controls). Recomputes on `input`/`change`, and on the frame
189
+ after a `reset`. Pairs with `field-state` (interaction history) and `field`
190
+ (per-field `--live-valid`).
191
+ - **`imgColor` plugin** (`prop-for-that/plugins`, key `img-color`, element). The
192
+ **dominant colour** of an `<img>` (CSS can't read pixels): `--live-img-r` /
193
+ `--live-img-g` / `--live-img-b` (`0`–`255`) plus `--live-img-l` (relative
194
+ luminance `0`–`1`, to pick legible text). Sampled 16×16 via `createImageBitmap`
195
+ + `OffscreenCanvas` (decode/downscale off the main thread, ~256 px read back),
196
+ with a `<canvas>` fallback; recomputed on load so a `src` swap updates it. Bind
197
+ the `<img>` or a container holding one. Cross-origin images need
198
+ `crossorigin="anonymous"` + CORS, else the canvas is tainted and the plugin
199
+ writes nothing — so keep a `var()` fallback. Kept separate from `img` so the
200
+ pixel-reading cost is opt-in.
201
+
202
+ ## [0.4.2]
203
+
204
+ ### Fixed
205
+ - **`fieldState`**: `--live-changed` now clears correctly after a form `reset`.
206
+ It's recomputed against each control's *default* value synchronously, instead
207
+ of re-read in a microtask — Chromium runs that microtask checkpoint *before* the
208
+ reset event reverts the control values, so `--live-changed` stayed stuck on after
209
+ a reset. `dirty` / `touched` / `submitted` already reset correctly; now `changed`
210
+ does too (handles text inputs, `<textarea>`, `<select>`, and checkbox/radio).
211
+
212
+ ## [0.4.1]
213
+
214
+ ### Added
215
+ - **`fieldState` plugin** (`prop-for-that/plugins`, key `field-state`, element).
216
+ The form interaction-history states libraries like Angular, Formik, and React
217
+ Hook Form track but CSS pseudo-classes can't express — all `1`/`0`:
218
+ `--live-dirty` / `--live-pristine` (has the user edited the field at all —
219
+ latches on first `input`/`change`), `--live-touched` / `--live-untouched`
220
+ (blurred at least once — latches on first `blur`), `--live-changed` (current
221
+ value differs from the value at mount — un-latches when typed back), and
222
+ `--live-submitted` (the owning `<form>` has been submitted — latches on
223
+ `submit`, clears on `reset`). Both latch pairs reset when the form is reset.
224
+ Bind a **single field** for that field's state, or a **`<form>` / wrapper** for
225
+ the *aggregate* over every field inside it (dirty/touched/changed if *any*
226
+ field is — like a framework form-group). Props land on the bound element, so a
227
+ label, hint, or submit button can reveal an error or enable only once
228
+ `--live-touched`/`--live-submitted`/`--live-dirty`. Validity stays in the
229
+ `field` plugin; focus stays in `:focus`.
230
+
231
+ ## [0.4.0]
232
+
233
+ ### Added
234
+ - **`--const-scrollbar-thin-w`** from the `head` entry: the scrollbar width when
235
+ `scrollbar-width: thin` is applied, measured alongside `--const-scrollbar-w`.
236
+ Falls back to the classic width where `scrollbar-width: thin` isn't supported.
237
+ - **`img` plugin** (`prop-for-that/plugins`, key `img`, element). For `<img>`:
238
+ `--live-natural-w` / `--live-natural-h` (intrinsic pixel size), `--live-loaded`
239
+ (`1`/`0`), `--live-broken` (`1`/`0`). Bind the image or a container holding one
240
+ (props land on the container, so a wrapper can show a skeleton while loading, a
241
+ fallback when broken, or set `aspect-ratio` from the natural size). Seeds from
242
+ `complete`/`naturalWidth` so already-cached images report correctly.
243
+ - **`cpuPressure` plugin** (`prop-for-that/plugins`, key `cpu-pressure`, global).
244
+ Exposes the CPU's [Compute Pressure](https://developer.mozilla.org/en-US/docs/Web/API/Compute_Pressure_API)
245
+ state as `--live-cpu-pressure`, an ordered tier (nominal=0, fair=1, serious=2,
246
+ critical=3) — use it to back off expensive CSS work as the CPU gets busy.
247
+ Chromium-only, secure-context, and gated by the `compute-pressure` Permissions
248
+ Policy; feature-detects and no-ops (seeding `0` where supported, writing nothing
249
+ where not) so `var(--live-cpu-pressure, 0)` is safe everywhere.
250
+
251
+ ### Changed (breaking)
252
+ - **The auto-mode attribute is renamed `data-prop` → `data-props-for`.** The
253
+ zero-config `prop-for-that/auto` entry now scans for, observes, and binds
254
+ `[data-props-for="key1 key2"]` (was `[data-prop]`). Rename the attribute on every
255
+ element: `<div data-prop="size">` → `<div data-props-for="size">`. The value
256
+ syntax is unchanged — one or more space-separated source keys. The imperative
257
+ `propsFor()` API is unaffected.
258
+ - **`visibility` is now full-element, and its latch moved off the `live`
259
+ cadence.** Both signals key off *entire-element* containment instead of
260
+ first-pixel overlap, and the write-once latch is renamed:
261
+ - `--live-visible` (1/0) now flips only while the element is **entirely**
262
+ within the viewport (previously: any part overlapping). Still reactive.
263
+ - `--live-has-entered` → **`--const-has-entered`**. It's written once (latches
264
+ to 1 the first time the element is entirely in view, never resets), so it now
265
+ lives on the `const` cadence. Update CSS to read `var(--const-has-entered)`.
266
+ - The shared `IntersectionObserver` gains `threshold: [0, 1]` to detect full
267
+ containment. An element larger than the viewport can never be entirely
268
+ visible, so neither signal ever turns on for it.
269
+
270
+ ## [0.3.0]
271
+
272
+ ### Changed (breaking)
273
+ - **Global `--live-*` writes go to an adopted stylesheet, not `<html>` inline
274
+ style.** Values written to the document root through the rAF writer now land in
275
+ a single constructable, adopted stylesheet rule (`:root {}`) instead of the
276
+ `<html>` element's inline `style`. This stops the per-frame churn from making
277
+ the DevTools Styles panel unusable and from flashing the Elements tree.
278
+ **Consumption is unchanged** — the rule targets `:root`, so `var()` / `calc()`
279
+ still inherit and resolve identically. What changes: reading
280
+ `document.documentElement.style.getPropertyValue('--live-*')` directly no longer
281
+ reflects these values (read computed style instead), and their cascade origin
282
+ moves from inline to an author rule (so author CSS / `!important` can now win).
283
+ Falls back to inline style where constructable stylesheets aren't supported
284
+ (older engines, SSR). **Element-scoped** writes are unchanged (still inline), and
285
+ the synchronous `head` entry still writes its `--const-*` constants inline by
286
+ design (first paint).
287
+
288
+ ### Added
289
+ - **`pause()` / `resume()`**: freeze the shared frame loop (no sampling, no
290
+ flushing) so live values hold steady — handy for inspecting them in DevTools
291
+ without churn, or halting work in a backgrounded tab — then pick back up.
292
+ Bindings stay attached; both are idempotent.
293
+ - **`configure({ liveHz })`**: optional cap (in Hz) on how often the loop samples
294
+ and flushes. Unset (default) runs every animation frame; e.g. `30` coalesces
295
+ writes to ≤30/sec for fewer style recalcs and a calmer DevTools panel, at the
296
+ cost of update smoothness. Throttles the whole loop, so per-frame samplers
297
+ (`fps`, `scroll-velocity`) measure at this rate too.
298
+
299
+ ## [0.2.0]
300
+
301
+ ### Changed (breaking)
302
+ - **Unified `global()` and `bind()` into a single `propsFor()`**. It is global
303
+ (writes to the configured root) unless the first argument is a `Node`,
304
+ `NodeList`, or array of elements, in which case it attaches per element. Its
305
+ disposer tears down exactly what that call started and removes the custom
306
+ properties it wrote. The old `global` and `bind` exports are **removed**.
307
+ - **The `config` object is no longer exported.** Read or change the prefixes and
308
+ root through `configure()`.
309
+ - **`visibility`** now writes binary, scroll-*triggered* state: `--live-visible`
310
+ (`1`/`0`) plus `--live-has-entered` (`1`/`0`, latches on first entry and never
311
+ resets). The continuous `--live-visible-ratio` is **removed**; use native
312
+ `animation-timeline: view()` for continuous scroll-driven effects.
313
+ - **`range`** is now container-aware: attach it to a wrapper and it finds the inner
314
+ `<input>`, writing `--live-value` / `--live-value-pct` on the **container** so
315
+ the input and sibling elements (gauges, readouts) can all read the value.
316
+
317
+ ### Removed (breaking)
318
+ - **Global `scroll` source** (`--live-scroll-x` / `-y` / `-progress`): superseded
319
+ by native CSS scroll-driven animations (`animation-timeline: scroll()` /
320
+ `view()`), which run on the compositor.
321
+ - **`el-scroll` plugin**: superseded by native `scroll(self)` / `scroll(nearest)`
322
+ scroll timelines.
323
+
324
+ ### Added
325
+ - **`propsFor()`**: one entry point for attaching sources, global or per element
326
+ (a single element, a `NodeList`, or an array). Returns a scoped disposer.
327
+ - **`reset()`**: tears down every active binding plus the shared observers and
328
+ listeners, for a clean slate (handy in tests and before hot reloads).
329
+ - **`unregister(key)`**: removes a source from the registry by key.
330
+ - **`SourceKey`** type export for the registry key.
331
+ - **Typed properties** via `configure({ typed: true })`: opt-in `@property`
332
+ registration of written `--live-*` values, so they interpolate (consumers add
333
+ `transition` / `@keyframes`) and resolve to a guaranteed `0` initial. It is
334
+ feature-detected, idempotent, and uses `inherits: true`; sources may declare
335
+ per-property `props` typings (default `<number>` / `0`), and consumers may set
336
+ initial values in the same call with `configure({ typed: true, defaults: {…} })`.
337
+ - `llms.txt`: a dense, agent-oriented reference, now included in the published package.
338
+ - **`field`** is now container-aware: attach it to a wrapper and it finds the inner
339
+ `input` / `textarea` / `select`, writing `--live-length` / `--live-empty` /
340
+ `--live-valid` on the **container** so sibling readers (a counter, a meter, a
341
+ status word) inherit them. Attaching directly to the field still works.
342
+ - **`media`** is now container-aware too: attach it to a wrapper and it finds the
343
+ inner `video` / `audio`, writing `--live-progress` / `--live-current-time` /
344
+ `--live-duration` / `--live-paused` / `--live-volume` on the **container** so
345
+ sibling readers (a progress ring, a scrubber) inherit them.
346
+
347
+ ### Fixed
348
+ - Single shared `requestAnimationFrame` loop now drives the writer and every
349
+ sampler, instead of each source running its own.
350
+ - `scroll-velocity` idles when the page isn't scrolling, instead of polling every
351
+ frame.
352
+ - `fps` now reports a smoothed average rather than a jittery per-frame value.
353
+ - `clock` no longer drifts: it's anchored to wall-clock time each tick.
354
+ - One shared `pointermove` listener now feeds both `pointer` and `pointer-local`.
355
+ - A failing source is isolated: it no longer takes down other sources or the loop.
356
+ - Written custom properties are removed on teardown.
357
+ - Disposers tear down only what their call started, leaving other bindings intact.
358
+
359
+ ## [0.1.0]
360
+
361
+ ### Added
362
+ - Initial release. Batched, diffed `requestAnimationFrame` writer.
363
+ - Core sources: `viewport`, `scroll`, `pointer`, `size`, `visibility`, `range`.
364
+ - Entries: `prop-for-that` (API), `prop-for-that/auto` (`[data-prop]` scanning),
365
+ `prop-for-that/head` (sync FOUC-safe `--const-*`).
366
+ - 14 opt-in plugins under `prop-for-that/plugins` (`scroll-velocity`, `online`,
367
+ `network`, `battery`, `clock`, `fps`, `visual-viewport`, `el-scroll`,
368
+ `pointer-local`, `media`, `field`, `orientation`, `motion`, `geo`).
369
+ - Build: ESM + CJS + `.d.ts` via tsup; tree-shakeable; zero runtime deps.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Adam Argyle
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,76 @@
1
+ <div align="center">
2
+
3
+ # prop-for-that
4
+
5
+ **Expose what JavaScript knows but CSS can't see — as live CSS custom properties.**
6
+
7
+ [![npm version](https://img.shields.io/npm/v/prop-for-that?logo=npm&color=cb3837)](https://www.npmjs.com/package/prop-for-that)
8
+ [![minzipped size](https://img.shields.io/bundlephobia/minzip/prop-for-that?color=22aa77)](https://bundlephobia.com/package/prop-for-that)
9
+ ![zero dependencies](https://img.shields.io/badge/dependencies-0-22aa77)
10
+ ![license MIT](https://img.shields.io/badge/license-MIT-blue)
11
+
12
+ [**Docs**](https://prop-for-that.netlify.app/docsite/) · [**Demos**](https://prop-for-that.netlify.app/) · [**Changelog**](./CHANGELOG.md) · [**llms.txt**](https://prop-for-that.netlify.app/llms.txt)
13
+
14
+ </div>
15
+
16
+ ---
17
+
18
+ Sliders, pointer position, element visibility, viewport size, battery, network, sensors — JavaScript can read all of it; CSS can't. **prop-for-that** writes that runtime state into `--live-*` and `--const-*` custom properties — batched and diffed down to one `setProperty` per frame — so your CSS can compose and react to it with plain `calc()` and `var()`.
19
+
20
+ Zero dependencies. TypeScript. ESM + CJS. SSR-safe.
21
+
22
+ ```bash
23
+ npm i prop-for-that
24
+ ```
25
+
26
+ ## Quick start
27
+
28
+ ```html
29
+ <script type="module">import 'prop-for-that/auto'</script>
30
+
31
+ <input type="range" data-props-for="range" />
32
+ ```
33
+
34
+ ```css
35
+ /* the slider paints itself from its own value — no event listeners, no render loop */
36
+ input {
37
+ background: hsl(calc(var(--live-value-pct) * 120) 80% 50%);
38
+ }
39
+ ```
40
+
41
+ Bind any element with `data-props-for="key …"` and read its `--live-*` properties in CSS. That's the whole idea.
42
+
43
+ ## Why
44
+
45
+ - **CSS does the work.** No per-element event handlers or render loops — bind once, compose in stylesheets.
46
+ - **Fast by design.** One `requestAnimationFrame` flush per frame, write-on-change diffing, and a single shared `ResizeObserver` / `IntersectionObserver` for the whole page.
47
+ - **Ship only what you use.** Five core sources are built in; everything else is an opt-in, tree-shakeable plugin.
48
+ - **Plays with the platform.** Opt into typed [`@property`](https://prop-for-that.netlify.app/docsite/concepts/typed-properties/) values for interpolation, or FOUC-safe constants written before first paint.
49
+ - **Tiny and dependency-free**, in every bundle format.
50
+
51
+ ## What it can read
52
+
53
+ **Core** (always on): viewport, pointer, element size, visibility, and `<input type="range">` values.
54
+
55
+ **Plugins** (opt-in): battery, network, online status, page focus & visibility, navigation type, FPS, clock, scroll velocity, device orientation / motion, geolocation, CPU pressure, media playback, form & field state, select & color-picker values, and dominant + accent colors extracted from images and video — 20+ in all.
56
+
57
+ → Every source, every property, and live demos are in the **[docs](https://prop-for-that.netlify.app/docsite/reference/plugins/)**.
58
+
59
+ ## Three entry points
60
+
61
+ | Import | What it does |
62
+ | --- | --- |
63
+ | `prop-for-that/auto` | Zero-config: attaches global state and binds every `data-props-for` element, kept in sync with the DOM. |
64
+ | `prop-for-that` | Imperative API — `propsFor()`, `register()`, `configure()` — for explicit control and teardown. |
65
+ | `prop-for-that/head` | Synchronous, FOUC-safe constants (scrollbar width, DPR, core count, device memory) before first paint. |
66
+ | `prop-for-that/plugins` | The opt-in plugin catalog. |
67
+
68
+ Full API and concepts: **[prop-for-that.netlify.app/docsite](https://prop-for-that.netlify.app/docsite/)**.
69
+
70
+ ## For LLMs / AI tools
71
+
72
+ A condensed, single-file reference — entry points, the full variable catalog, recipes, and gotchas — lives at **[llms.txt](./llms.txt)**, hosted at [prop-for-that.netlify.app/llms.txt](https://prop-for-that.netlify.app/llms.txt) and shipped in the npm package (`node_modules/prop-for-that/llms.txt`).
73
+
74
+ ## License
75
+
76
+ MIT © [Adam Argyle](https://github.com/argyleink)
package/dist/auto.cjs ADDED
@@ -0,0 +1 @@
1
+ 'use strict';var c={livePrefix:"--live-",constPrefix:"--const-",root:typeof document<"u"?document.documentElement:void 0,typed:false};var h=new Set,z=null,l=0,w=false,D=false,F=0,E=()=>typeof requestAnimationFrame=="function";function b(e){if(l=0,D);let t=c.liveHz;if(E()&&t&&t>0&&e-F<1e3/t){l=requestAnimationFrame(b);return}if(F=e,w=true,h.size)for(let r of [...h])r(e);z?.(),w=false,h.size&&E()&&A();}function A(){l||w||D||(E()?l=requestAnimationFrame(b):b(0));}function R(){A();}function O(e){z=e;}var f;function Q(){if(f!==void 0||(f=null,typeof document>"u"||typeof CSSStyleSheet!="function"||!("adoptedStyleSheets"in document)))return f;try{let e=new CSSStyleSheet;e.replaceSync(":root{}"),document.adoptedStyleSheets=[...document.adoptedStyleSheets,e],f=e.cssRules[0].style;}catch{f=null;}return f}function a(e){if(typeof document<"u"&&e===document.documentElement){let t=Q();if(t)return t}return e.style}var M=class{pending=new Map;last=new WeakMap;set(t,r,n){let o=this.pending.get(t);if(o?.has(r)){o.set(r,n);return}this.last.get(t)?.get(r)!==n&&(o?o.set(r,n):this.pending.set(t,new Map([[r,n]])),R());}forget(t,r){this.last.get(t)?.delete(r),this.pending.get(t)?.delete(r);}flush=()=>{if(this.pending.size!==0){for(let[t,r]of this.pending){let n=this.last.get(t);n||this.last.set(t,n=new Map);let o=a(t);for(let[i,s]of r)n.get(i)!==s&&(o.setProperty(i,s),n.set(i,s));}this.pending.clear();}}},m=new M;O(m.flush);var P=new Set;function q(e,t,r){if(P.has(e)||typeof CSS>"u"||typeof CSS.registerProperty!="function")return;P.add(e);let n=t?r?.[t]:void 0,o=t?c.defaults?.[t]:void 0;try{CSS.registerProperty({name:e,syntax:n?.syntax??"<number>",inherits:n?.inherits??!0,initialValue:o!=null?String(o):n?.initial??"0"});}catch{}}var L=new Map;function y(e,t){let r=L.get(e);if(!r){let n=new Set,o=i=>{for(let s of [...n])s(i);};r={handlers:n,dispatch:o},L.set(e,r),window.addEventListener(e,o,{passive:true});}return r.handlers.add(t),()=>{r.handlers.delete(t),r.handlers.size===0&&(window.removeEventListener(e,r.dispatch),L.delete(e));}}var I={key:"viewport",scope:"global",start(e){let t=()=>{e.write("vw",window.innerWidth),e.write("vh",window.innerHeight);};return t(),y("resize",t)}};function p(e){return Math.round(e*1e4)/1e4}var W={key:"pointer",scope:"global",start(e){return y("pointermove",r=>{let n=r;e.write("pointer-x",n.clientX),e.write("pointer-y",n.clientY),e.write("pointer-x-ratio",p(n.clientX/window.innerWidth)),e.write("pointer-y-ratio",p(n.clientY/window.innerHeight));})}};var g,T=new WeakMap;function K(e,t){return g||(g=new ResizeObserver(r=>{for(let n of r)T.get(n.target)?.(n);})),T.set(e,t),g.observe(e),()=>{g.unobserve(e),T.delete(e);}}var v,x=new WeakMap;function N(e,t){return v||(v=new IntersectionObserver(r=>{for(let n of r)x.get(n.target)?.(n);},{threshold:[0,1]})),x.set(e,t),v.observe(e),()=>{v.unobserve(e),x.delete(e);}}var B={key:"size",scope:"element",start(e){return K(e.target,t=>{let r=t.borderBoxSize?.[0],n=r?r.inlineSize:t.contentRect.width,o=r?r.blockSize:t.contentRect.height;e.write("w",p(n)),e.write("h",p(o)),e.write("aspect",o>0?p(n/o):0);})}};var X={key:"visibility",scope:"element",start(e){let t=false;return e.write("visible",0),e.write("has-entered",0,"const"),N(e.target,r=>{let n=r.intersectionRatio>=1;e.write("visible",n?1:0),n&&!t&&(t=true,e.write("has-entered",1,"const"));})}};function Y(e,t){return e.matches(t)?e:e.querySelector(t)}var $={key:"range",scope:"element",start(e){let t=Y(e.target,'input[type="range"], input[type="number"]');if(!t)return ()=>{};let r=()=>{let n=t.min===""?0:Number(t.min),o=t.max===""?100:Number(t.max),i=Number(t.value);e.write("value",i),e.write("value-pct",o>n?p((i-n)/(o-n)):0);};return r(),t.addEventListener("input",r,{passive:true}),()=>t.removeEventListener("input",r)}};var j={viewport:I,pointer:W,size:B,visibility:X,range:$};var Z=()=>{},u=new Map,ee={...j};function G(e){Object.assign(c,e);}function te(e,t,r){return {target:e,config:c,write(n,o,i="live"){let s=(i==="const"?c.constPrefix:c.livePrefix)+n;t.add(s),c.typed&&q(s,n,r),m.set(e,s,String(o));}}}function U(e,t,r){let n=r.get(t);if(!n)return;n.dispose();let o=a(e);for(let i of n.written)o.removeProperty(i),m.forget(e,i);r.delete(t);}function re(e,t){let r=u.get(e);r||u.set(e,r=new Map);let n=[];for(let o of t){if(r.has(o))continue;let i=ee[o];if(!i){console.warn(`[prop-for-that] unknown source "${o}"`);continue}let s=new Set,C;try{C=i.start(te(e,s,i.props));}catch(J){console.error(`[prop-for-that] source "${o}" failed to start`,J);continue}r.set(o,{dispose:C,written:s}),n.push(o);}return ()=>{let o=u.get(e);if(o){for(let i of n)U(e,i,o);o.size===0&&u.delete(e);}}}function ne(e){return e instanceof Element?[e]:typeof NodeList<"u"&&e instanceof NodeList||typeof HTMLCollection<"u"&&e instanceof HTMLCollection||Array.isArray(e)?Array.from(e).filter(t=>t instanceof Element):[]}function oe(e){return Array.isArray(e)&&(e.length===0||typeof e[0]=="string")}function H(e,t){let r,n;if(t===void 0&&oe(e)?(r=c.root?[c.root]:[],n=e):(r=ne(e),n=t??[]),!r.length||!n.length)return Z;let o=r.map(i=>re(i,n));return ()=>{for(let i of o)i();}}function k(e,t){let r=u.get(e);if(r){for(let n of t??[...r.keys()])U(e,n,r);r.size===0&&u.delete(e);}}var ie=["viewport","pointer"],d=new WeakMap;function se(e){return (e.dataset.propsFor??"").split(/\s+/).filter(Boolean)}function S(e){let t=se(e),r=d.get(e)??[],n=r.filter(i=>!t.includes(i)),o=t.filter(i=>!r.includes(i));n.length&&k(e,n),o.length&&H(e,o),t.length?d.set(e,t):d.delete(e);}function V(e){let t=d.get(e);t&&(k(e,t),d.delete(e));}function ce(e){e instanceof HTMLElement&&(e.hasAttribute("data-props-for")&&S(e),e.querySelectorAll("[data-props-for]").forEach(S));}function pe(e){e instanceof HTMLElement&&(V(e),e.querySelectorAll("[data-props-for]").forEach(V));}function _(){document.documentElement.hasAttribute("data-props-typed")&&G({typed:true}),H(ie),document.querySelectorAll("[data-props-for]").forEach(S),new MutationObserver(e=>{for(let t of e){if(t.type==="attributes"&&t.target instanceof HTMLElement){S(t.target);continue}t.removedNodes.forEach(pe),t.addedNodes.forEach(ce);}}).observe(document.documentElement,{subtree:true,childList:true,attributes:true,attributeFilter:["data-props-for"]});}typeof document<"u"&&(document.readyState==="loading"?document.addEventListener("DOMContentLoaded",_,{once:true}):_());
@@ -0,0 +1,2 @@
1
+
2
+ export { }
package/dist/auto.d.ts ADDED
@@ -0,0 +1,2 @@
1
+
2
+ export { }
@@ -0,0 +1 @@
1
+ (function(){'use strict';var c={livePrefix:"--live-",constPrefix:"--const-",root:typeof document<"u"?document.documentElement:void 0,typed:false};var h=new Set,z=null,l=0,w=false,D=false,F=0,E=()=>typeof requestAnimationFrame=="function";function b(e){if(l=0,D);let t=c.liveHz;if(E()&&t&&t>0&&e-F<1e3/t){l=requestAnimationFrame(b);return}if(F=e,w=true,h.size)for(let r of [...h])r(e);z?.(),w=false,h.size&&E()&&A();}function A(){l||w||D||(E()?l=requestAnimationFrame(b):b(0));}function R(){A();}function O(e){z=e;}var f;function Q(){if(f!==void 0||(f=null,typeof document>"u"||typeof CSSStyleSheet!="function"||!("adoptedStyleSheets"in document)))return f;try{let e=new CSSStyleSheet;e.replaceSync(":root{}"),document.adoptedStyleSheets=[...document.adoptedStyleSheets,e],f=e.cssRules[0].style;}catch{f=null;}return f}function a(e){if(typeof document<"u"&&e===document.documentElement){let t=Q();if(t)return t}return e.style}var M=class{pending=new Map;last=new WeakMap;set(t,r,n){let o=this.pending.get(t);if(o?.has(r)){o.set(r,n);return}this.last.get(t)?.get(r)!==n&&(o?o.set(r,n):this.pending.set(t,new Map([[r,n]])),R());}forget(t,r){this.last.get(t)?.delete(r),this.pending.get(t)?.delete(r);}flush=()=>{if(this.pending.size!==0){for(let[t,r]of this.pending){let n=this.last.get(t);n||this.last.set(t,n=new Map);let o=a(t);for(let[i,s]of r)n.get(i)!==s&&(o.setProperty(i,s),n.set(i,s));}this.pending.clear();}}},m=new M;O(m.flush);var P=new Set;function q(e,t,r){if(P.has(e)||typeof CSS>"u"||typeof CSS.registerProperty!="function")return;P.add(e);let n=t?r?.[t]:void 0,o=t?c.defaults?.[t]:void 0;try{CSS.registerProperty({name:e,syntax:n?.syntax??"<number>",inherits:n?.inherits??!0,initialValue:o!=null?String(o):n?.initial??"0"});}catch{}}var L=new Map;function y(e,t){let r=L.get(e);if(!r){let n=new Set,o=i=>{for(let s of [...n])s(i);};r={handlers:n,dispatch:o},L.set(e,r),window.addEventListener(e,o,{passive:true});}return r.handlers.add(t),()=>{r.handlers.delete(t),r.handlers.size===0&&(window.removeEventListener(e,r.dispatch),L.delete(e));}}var I={key:"viewport",scope:"global",start(e){let t=()=>{e.write("vw",window.innerWidth),e.write("vh",window.innerHeight);};return t(),y("resize",t)}};function p(e){return Math.round(e*1e4)/1e4}var W={key:"pointer",scope:"global",start(e){return y("pointermove",r=>{let n=r;e.write("pointer-x",n.clientX),e.write("pointer-y",n.clientY),e.write("pointer-x-ratio",p(n.clientX/window.innerWidth)),e.write("pointer-y-ratio",p(n.clientY/window.innerHeight));})}};var g,T=new WeakMap;function K(e,t){return g||(g=new ResizeObserver(r=>{for(let n of r)T.get(n.target)?.(n);})),T.set(e,t),g.observe(e),()=>{g.unobserve(e),T.delete(e);}}var v,x=new WeakMap;function N(e,t){return v||(v=new IntersectionObserver(r=>{for(let n of r)x.get(n.target)?.(n);},{threshold:[0,1]})),x.set(e,t),v.observe(e),()=>{v.unobserve(e),x.delete(e);}}var B={key:"size",scope:"element",start(e){return K(e.target,t=>{let r=t.borderBoxSize?.[0],n=r?r.inlineSize:t.contentRect.width,o=r?r.blockSize:t.contentRect.height;e.write("w",p(n)),e.write("h",p(o)),e.write("aspect",o>0?p(n/o):0);})}};var X={key:"visibility",scope:"element",start(e){let t=false;return e.write("visible",0),e.write("has-entered",0,"const"),N(e.target,r=>{let n=r.intersectionRatio>=1;e.write("visible",n?1:0),n&&!t&&(t=true,e.write("has-entered",1,"const"));})}};function Y(e,t){return e.matches(t)?e:e.querySelector(t)}var $={key:"range",scope:"element",start(e){let t=Y(e.target,'input[type="range"], input[type="number"]');if(!t)return ()=>{};let r=()=>{let n=t.min===""?0:Number(t.min),o=t.max===""?100:Number(t.max),i=Number(t.value);e.write("value",i),e.write("value-pct",o>n?p((i-n)/(o-n)):0);};return r(),t.addEventListener("input",r,{passive:true}),()=>t.removeEventListener("input",r)}};var j={viewport:I,pointer:W,size:B,visibility:X,range:$};var Z=()=>{},u=new Map,ee={...j};function G(e){Object.assign(c,e);}function te(e,t,r){return {target:e,config:c,write(n,o,i="live"){let s=(i==="const"?c.constPrefix:c.livePrefix)+n;t.add(s),c.typed&&q(s,n,r),m.set(e,s,String(o));}}}function U(e,t,r){let n=r.get(t);if(!n)return;n.dispose();let o=a(e);for(let i of n.written)o.removeProperty(i),m.forget(e,i);r.delete(t);}function re(e,t){let r=u.get(e);r||u.set(e,r=new Map);let n=[];for(let o of t){if(r.has(o))continue;let i=ee[o];if(!i){console.warn(`[prop-for-that] unknown source "${o}"`);continue}let s=new Set,C;try{C=i.start(te(e,s,i.props));}catch(J){console.error(`[prop-for-that] source "${o}" failed to start`,J);continue}r.set(o,{dispose:C,written:s}),n.push(o);}return ()=>{let o=u.get(e);if(o){for(let i of n)U(e,i,o);o.size===0&&u.delete(e);}}}function ne(e){return e instanceof Element?[e]:typeof NodeList<"u"&&e instanceof NodeList||typeof HTMLCollection<"u"&&e instanceof HTMLCollection||Array.isArray(e)?Array.from(e).filter(t=>t instanceof Element):[]}function oe(e){return Array.isArray(e)&&(e.length===0||typeof e[0]=="string")}function H(e,t){let r,n;if(t===void 0&&oe(e)?(r=c.root?[c.root]:[],n=e):(r=ne(e),n=t??[]),!r.length||!n.length)return Z;let o=r.map(i=>re(i,n));return ()=>{for(let i of o)i();}}function k(e,t){let r=u.get(e);if(r){for(let n of t??[...r.keys()])U(e,n,r);r.size===0&&u.delete(e);}}var ie=["viewport","pointer"],d=new WeakMap;function se(e){return (e.dataset.propsFor??"").split(/\s+/).filter(Boolean)}function S(e){let t=se(e),r=d.get(e)??[],n=r.filter(i=>!t.includes(i)),o=t.filter(i=>!r.includes(i));n.length&&k(e,n),o.length&&H(e,o),t.length?d.set(e,t):d.delete(e);}function V(e){let t=d.get(e);t&&(k(e,t),d.delete(e));}function ce(e){e instanceof HTMLElement&&(e.hasAttribute("data-props-for")&&S(e),e.querySelectorAll("[data-props-for]").forEach(S));}function pe(e){e instanceof HTMLElement&&(V(e),e.querySelectorAll("[data-props-for]").forEach(V));}function _(){document.documentElement.hasAttribute("data-props-typed")&&G({typed:true}),H(ie),document.querySelectorAll("[data-props-for]").forEach(S),new MutationObserver(e=>{for(let t of e){if(t.type==="attributes"&&t.target instanceof HTMLElement){S(t.target);continue}t.removedNodes.forEach(pe),t.addedNodes.forEach(ce);}}).observe(document.documentElement,{subtree:true,childList:true,attributes:true,attributeFilter:["data-props-for"]});}typeof document<"u"&&(document.readyState==="loading"?document.addEventListener("DOMContentLoaded",_,{once:true}):_());})();
package/dist/auto.js ADDED
@@ -0,0 +1 @@
1
+ import {i,j,k}from'./chunk-6DXNWAGS.js';import'./chunk-LWKH66VY.js';var p=["viewport","pointer"],n=new WeakMap;function m(e){return (e.dataset.propsFor??"").split(/\s+/).filter(Boolean)}function o(e){let t=m(e),d=n.get(e)??[],s=d.filter(r=>!t.includes(r)),a=t.filter(r=>!d.includes(r));s.length&&k(e,s),a.length&&j(e,a),t.length?n.set(e,t):n.delete(e);}function u(e){let t=n.get(e);t&&(k(e,t),n.delete(e));}function E(e){e instanceof HTMLElement&&(e.hasAttribute("data-props-for")&&o(e),e.querySelectorAll("[data-props-for]").forEach(o));}function L(e){e instanceof HTMLElement&&(u(e),e.querySelectorAll("[data-props-for]").forEach(u));}function l(){document.documentElement.hasAttribute("data-props-typed")&&i({typed:true}),j(p),document.querySelectorAll("[data-props-for]").forEach(o),new MutationObserver(e=>{for(let t of e){if(t.type==="attributes"&&t.target instanceof HTMLElement){o(t.target);continue}t.removedNodes.forEach(L),t.addedNodes.forEach(E);}}).observe(document.documentElement,{subtree:true,childList:true,attributes:true,attributeFilter:["data-props-for"]});}typeof document<"u"&&(document.readyState==="loading"?document.addEventListener("DOMContentLoaded",l,{once:true}):l());
@@ -0,0 +1 @@
1
+ import {a as a$1}from'./chunk-LWKH66VY.js';var a=new Set,z=null,f=0,h=false,d=false,F=0,E=()=>typeof requestAnimationFrame=="function";function b(e){if(f=0,d)return;let t=a$1.liveHz;if(E()&&t&&t>0&&e-F<1e3/t){f=requestAnimationFrame(b);return}if(F=e,h=true,a.size)for(let r of [...a])r(e);z?.(),h=false,a.size&&E()&&m();}function m(){f||h||d||(E()?f=requestAnimationFrame(b):b(0));}function D(){m();}function R(e){z=e;}function te(e){return a.add(e),m(),()=>{a.delete(e);}}function B(){d||(d=true,f&&typeof cancelAnimationFrame=="function"&&cancelAnimationFrame(f),f=0);}function V(){d&&(d=false,m());}var l;function G(){if(l!==void 0||(l=null,typeof document>"u"||typeof CSSStyleSheet!="function"||!("adoptedStyleSheets"in document)))return l;try{let e=new CSSStyleSheet;e.replaceSync(":root{}"),document.adoptedStyleSheets=[...document.adoptedStyleSheets,e],l=e.cssRules[0].style;}catch{l=null;}return l}function y(e){if(typeof document<"u"&&e===document.documentElement){let t=G();if(t)return t}return e.style}var M=class{pending=new Map;last=new WeakMap;set(t,r,n){let o=this.pending.get(t);if(o?.has(r)){o.set(r,n);return}this.last.get(t)?.get(r)!==n&&(o?o.set(r,n):this.pending.set(t,new Map([[r,n]])),D());}forget(t,r){this.last.get(t)?.delete(r),this.pending.get(t)?.delete(r);}flush=()=>{if(this.pending.size!==0){for(let[t,r]of this.pending){let n=this.last.get(t);n||this.last.set(t,n=new Map);let o=y(t);for(let[i,s]of r)n.get(i)!==s&&(o.setProperty(i,s),n.set(i,s));}this.pending.clear();}}},g=new M;R(g.flush);var P=new Set;function A(e,t,r){if(P.has(e)||typeof CSS>"u"||typeof CSS.registerProperty!="function")return;P.add(e);let n=t?r?.[t]:void 0,o=t?a$1.defaults?.[t]:void 0;try{CSS.registerProperty({name:e,syntax:n?.syntax??"<number>",inherits:n?.inherits??!0,initialValue:o!=null?String(o):n?.initial??"0"});}catch{}}var x=new Map;function v(e,t){let r=x.get(e);if(!r){let n=new Set,o=i=>{for(let s of [...n])s(i);};r={handlers:n,dispatch:o},x.set(e,r),window.addEventListener(e,o,{passive:true});}return r.handlers.add(t),()=>{r.handlers.delete(t),r.handlers.size===0&&(window.removeEventListener(e,r.dispatch),x.delete(e));}}var O={key:"viewport",scope:"global",start(e){let t=()=>{e.write("vw",window.innerWidth),e.write("vh",window.innerHeight);};return t(),v("resize",t)}};function c(e){return Math.round(e*1e4)/1e4}var I={key:"pointer",scope:"global",start(e){return v("pointermove",r=>{let n=r;e.write("pointer-x",n.clientX),e.write("pointer-y",n.clientY),e.write("pointer-x-ratio",c(n.clientX/window.innerWidth)),e.write("pointer-y-ratio",c(n.clientY/window.innerHeight));})}};var S,k=new WeakMap;function K(e,t){return S||(S=new ResizeObserver(r=>{for(let n of r)k.get(n.target)?.(n);})),k.set(e,t),S.observe(e),()=>{S.unobserve(e),k.delete(e);}}var w,T=new WeakMap;function W(e,t){return w||(w=new IntersectionObserver(r=>{for(let n of r)T.get(n.target)?.(n);},{threshold:[0,1]})),T.set(e,t),w.observe(e),()=>{w.unobserve(e),T.delete(e);}}var q={key:"size",scope:"element",start(e){return K(e.target,t=>{let r=t.borderBoxSize?.[0],n=r?r.inlineSize:t.contentRect.width,o=r?r.blockSize:t.contentRect.height;e.write("w",c(n)),e.write("h",c(o)),e.write("aspect",o>0?c(n/o):0);})}};var N={key:"visibility",scope:"element",start(e){let t=false;return e.write("visible",0),e.write("has-entered",0,"const"),W(e.target,r=>{let n=r.intersectionRatio>=1;e.write("visible",n?1:0),n&&!t&&(t=true,e.write("has-entered",1,"const"));})}};function X(e,t){return e.matches(t)?e:e.querySelector(t)}var Y={key:"range",scope:"element",start(e){let t=X(e.target,'input[type="range"], input[type="number"]');if(!t)return ()=>{};let r=()=>{let n=t.min===""?0:Number(t.min),o=t.max===""?100:Number(t.max),i=Number(t.value);e.write("value",i),e.write("value-pct",o>n?c((i-n)/(o-n)):0);};return r(),t.addEventListener("input",r,{passive:true}),()=>t.removeEventListener("input",r)}};var $={viewport:O,pointer:I,size:q,visibility:N,range:Y};var J=()=>{},u=new Map,L={...$};function Ke(e){L[e.key]=e;}function We(e){delete L[e];}function qe(e){Object.assign(a$1,e);}function Q(e,t,r){return {target:e,config:a$1,write(n,o,i="live"){let s=(i==="const"?a$1.constPrefix:a$1.livePrefix)+n;t.add(s),a$1.typed&&A(s,n,r),g.set(e,s,String(o));}}}function C(e,t,r){let n=r.get(t);if(!n)return;n.dispose();let o=y(e);for(let i of n.written)o.removeProperty(i),g.forget(e,i);r.delete(t);}function U(e,t){let r=u.get(e);r||u.set(e,r=new Map);let n=[];for(let o of t){if(r.has(o))continue;let i=L[o];if(!i){console.warn(`[prop-for-that] unknown source "${o}"`);continue}let s=new Set,H;try{H=i.start(Q(e,s,i.props));}catch(j){console.error(`[prop-for-that] source "${o}" failed to start`,j);continue}r.set(o,{dispose:H,written:s}),n.push(o);}return ()=>{let o=u.get(e);if(o){for(let i of n)C(e,i,o);o.size===0&&u.delete(e);}}}function Z(e){return e instanceof Element?[e]:typeof NodeList<"u"&&e instanceof NodeList||typeof HTMLCollection<"u"&&e instanceof HTMLCollection||Array.isArray(e)?Array.from(e).filter(t=>t instanceof Element):[]}function _(e){return Array.isArray(e)&&(e.length===0||typeof e[0]=="string")}function Ne(e,t){let r,n;if(t===void 0&&_(e)?(r=a$1.root?[a$1.root]:[],n=e):(r=Z(e),n=t??[]),!r.length||!n.length)return J;let o=r.map(i=>U(i,n));return ()=>{for(let i of o)i();}}function Xe(e,t){let r=u.get(e);if(r){for(let n of t??[...r.keys()])C(e,n,r);r.size===0&&u.delete(e);}}function Ye(){for(let[e,t]of u)for(let r of [...t.keys()])C(e,r,t);u.clear();}export{te as a,B as b,V as c,v as d,c as e,X as f,Ke as g,We as h,qe as i,Ne as j,Xe as k,Ye as l};
@@ -0,0 +1 @@
1
+ var e={livePrefix:"--live-",constPrefix:"--const-",root:typeof document<"u"?document.documentElement:void 0,typed:false};export{e as a};
package/dist/head.cjs ADDED
@@ -0,0 +1 @@
1
+ 'use strict';var o={constPrefix:"--const-",root:typeof document<"u"?document.documentElement:void 0};function s(){let i=o.root,t=(n,r)=>i.style.setProperty(o.constPrefix+n,String(r)),e=document.createElement("div");e.style.cssText="position:absolute;top:-9999px;width:100px;height:100px;overflow:scroll;visibility:hidden",(document.body??i).appendChild(e),t("scrollbar-w",e.offsetWidth-e.clientWidth),e.style.scrollbarWidth="thin",t("scrollbar-thin-w",e.offsetWidth-e.clientWidth),e.remove(),t("dpr",window.devicePixelRatio||1),t("cores",navigator.hardwareConcurrency||0),t("mem",navigator.deviceMemory||0);}typeof document<"u"&&s();
@@ -0,0 +1,2 @@
1
+
2
+ export { }
package/dist/head.d.ts ADDED
@@ -0,0 +1,2 @@
1
+
2
+ export { }
@@ -0,0 +1 @@
1
+ (function(){'use strict';var o={constPrefix:"--const-",root:typeof document<"u"?document.documentElement:void 0};function s(){let i=o.root,t=(n,r)=>i.style.setProperty(o.constPrefix+n,String(r)),e=document.createElement("div");e.style.cssText="position:absolute;top:-9999px;width:100px;height:100px;overflow:scroll;visibility:hidden",(document.body??i).appendChild(e),t("scrollbar-w",e.offsetWidth-e.clientWidth),e.style.scrollbarWidth="thin",t("scrollbar-thin-w",e.offsetWidth-e.clientWidth),e.remove(),t("dpr",window.devicePixelRatio||1),t("cores",navigator.hardwareConcurrency||0),t("mem",navigator.deviceMemory||0);}typeof document<"u"&&s();})();
package/dist/head.js ADDED
@@ -0,0 +1 @@
1
+ import {a}from'./chunk-LWKH66VY.js';function s(){let i=a.root,e=(r,n)=>i.style.setProperty(a.constPrefix+r,String(n)),t=document.createElement("div");t.style.cssText="position:absolute;top:-9999px;width:100px;height:100px;overflow:scroll;visibility:hidden",(document.body??i).appendChild(t),e("scrollbar-w",t.offsetWidth-t.clientWidth),t.style.scrollbarWidth="thin",e("scrollbar-thin-w",t.offsetWidth-t.clientWidth),t.remove(),e("dpr",window.devicePixelRatio||1),e("cores",navigator.hardwareConcurrency||0),e("mem",navigator.deviceMemory||0);}typeof document<"u"&&s();
package/dist/index.cjs ADDED
@@ -0,0 +1 @@
1
+ 'use strict';var p={livePrefix:"--live-",constPrefix:"--const-",root:typeof document<"u"?document.documentElement:void 0,typed:false};var S=new Set,z=null,f=0,w=false,d=false,F=0,h=()=>typeof requestAnimationFrame=="function";function E(e){if(f=0,d)return;let t=p.liveHz;if(h()&&t&&t>0&&e-F<1e3/t){f=requestAnimationFrame(E);return}if(F=e,w=true,S.size)for(let r of [...S])r(e);z?.(),w=false,S.size&&h()&&b();}function b(){f||w||d||(h()?f=requestAnimationFrame(E):E(0));}function D(){b();}function R(e){z=e;}function B(){d||(d=true,f&&typeof cancelAnimationFrame=="function"&&cancelAnimationFrame(f),f=0);}function V(){d&&(d=false,b());}var l;function G(){if(l!==void 0||(l=null,typeof document>"u"||typeof CSSStyleSheet!="function"||!("adoptedStyleSheets"in document)))return l;try{let e=new CSSStyleSheet;e.replaceSync(":root{}"),document.adoptedStyleSheets=[...document.adoptedStyleSheets,e],l=e.cssRules[0].style;}catch{l=null;}return l}function a(e){if(typeof document<"u"&&e===document.documentElement){let t=G();if(t)return t}return e.style}var x=class{pending=new Map;last=new WeakMap;set(t,r,n){let o=this.pending.get(t);if(o?.has(r)){o.set(r,n);return}this.last.get(t)?.get(r)!==n&&(o?o.set(r,n):this.pending.set(t,new Map([[r,n]])),D());}forget(t,r){this.last.get(t)?.delete(r),this.pending.get(t)?.delete(r);}flush=()=>{if(this.pending.size!==0){for(let[t,r]of this.pending){let n=this.last.get(t);n||this.last.set(t,n=new Map);let o=a(t);for(let[i,s]of r)n.get(i)!==s&&(o.setProperty(i,s),n.set(i,s));}this.pending.clear();}}},m=new x;R(m.flush);var P=new Set;function A(e,t,r){if(P.has(e)||typeof CSS>"u"||typeof CSS.registerProperty!="function")return;P.add(e);let n=t?r?.[t]:void 0,o=t?p.defaults?.[t]:void 0;try{CSS.registerProperty({name:e,syntax:n?.syntax??"<number>",inherits:n?.inherits??!0,initialValue:o!=null?String(o):n?.initial??"0"});}catch{}}var M=new Map;function y(e,t){let r=M.get(e);if(!r){let n=new Set,o=i=>{for(let s of [...n])s(i);};r={handlers:n,dispatch:o},M.set(e,r),window.addEventListener(e,o,{passive:true});}return r.handlers.add(t),()=>{r.handlers.delete(t),r.handlers.size===0&&(window.removeEventListener(e,r.dispatch),M.delete(e));}}var O={key:"viewport",scope:"global",start(e){let t=()=>{e.write("vw",window.innerWidth),e.write("vh",window.innerHeight);};return t(),y("resize",t)}};function c(e){return Math.round(e*1e4)/1e4}var I={key:"pointer",scope:"global",start(e){return y("pointermove",r=>{let n=r;e.write("pointer-x",n.clientX),e.write("pointer-y",n.clientY),e.write("pointer-x-ratio",c(n.clientX/window.innerWidth)),e.write("pointer-y-ratio",c(n.clientY/window.innerHeight));})}};var g,T=new WeakMap;function K(e,t){return g||(g=new ResizeObserver(r=>{for(let n of r)T.get(n.target)?.(n);})),T.set(e,t),g.observe(e),()=>{g.unobserve(e),T.delete(e);}}var v,k=new WeakMap;function W(e,t){return v||(v=new IntersectionObserver(r=>{for(let n of r)k.get(n.target)?.(n);},{threshold:[0,1]})),k.set(e,t),v.observe(e),()=>{v.unobserve(e),k.delete(e);}}var q={key:"size",scope:"element",start(e){return K(e.target,t=>{let r=t.borderBoxSize?.[0],n=r?r.inlineSize:t.contentRect.width,o=r?r.blockSize:t.contentRect.height;e.write("w",c(n)),e.write("h",c(o)),e.write("aspect",o>0?c(n/o):0);})}};var N={key:"visibility",scope:"element",start(e){let t=false;return e.write("visible",0),e.write("has-entered",0,"const"),W(e.target,r=>{let n=r.intersectionRatio>=1;e.write("visible",n?1:0),n&&!t&&(t=true,e.write("has-entered",1,"const"));})}};function X(e,t){return e.matches(t)?e:e.querySelector(t)}var Y={key:"range",scope:"element",start(e){let t=X(e.target,'input[type="range"], input[type="number"]');if(!t)return ()=>{};let r=()=>{let n=t.min===""?0:Number(t.min),o=t.max===""?100:Number(t.max),i=Number(t.value);e.write("value",i),e.write("value-pct",o>n?c((i-n)/(o-n)):0);};return r(),t.addEventListener("input",r,{passive:true}),()=>t.removeEventListener("input",r)}};var $={viewport:O,pointer:I,size:q,visibility:N,range:Y};var J=()=>{},u=new Map,C={...$};function Ke(e){C[e.key]=e;}function We(e){delete C[e];}function qe(e){Object.assign(p,e);}function Q(e,t,r){return {target:e,config:p,write(n,o,i="live"){let s=(i==="const"?p.constPrefix:p.livePrefix)+n;t.add(s),p.typed&&A(s,n,r),m.set(e,s,String(o));}}}function L(e,t,r){let n=r.get(t);if(!n)return;n.dispose();let o=a(e);for(let i of n.written)o.removeProperty(i),m.forget(e,i);r.delete(t);}function U(e,t){let r=u.get(e);r||u.set(e,r=new Map);let n=[];for(let o of t){if(r.has(o))continue;let i=C[o];if(!i){console.warn(`[prop-for-that] unknown source "${o}"`);continue}let s=new Set,H;try{H=i.start(Q(e,s,i.props));}catch(j){console.error(`[prop-for-that] source "${o}" failed to start`,j);continue}r.set(o,{dispose:H,written:s}),n.push(o);}return ()=>{let o=u.get(e);if(o){for(let i of n)L(e,i,o);o.size===0&&u.delete(e);}}}function Z(e){return e instanceof Element?[e]:typeof NodeList<"u"&&e instanceof NodeList||typeof HTMLCollection<"u"&&e instanceof HTMLCollection||Array.isArray(e)?Array.from(e).filter(t=>t instanceof Element):[]}function _(e){return Array.isArray(e)&&(e.length===0||typeof e[0]=="string")}function Ne(e,t){let r,n;if(t===void 0&&_(e)?(r=p.root?[p.root]:[],n=e):(r=Z(e),n=t??[]),!r.length||!n.length)return J;let o=r.map(i=>U(i,n));return ()=>{for(let i of o)i();}}function Xe(e,t){let r=u.get(e);if(r){for(let n of t??[...r.keys()])L(e,n,r);r.size===0&&u.delete(e);}}function Ye(){for(let[e,t]of u)for(let r of [...t.keys()])L(e,r,t);u.clear();}exports.configure=qe;exports.pause=B;exports.propsFor=Ne;exports.register=Ke;exports.reset=Ye;exports.resume=V;exports.unbind=Xe;exports.unregister=We;
@@ -0,0 +1,37 @@
1
+ import { a as Config, D as Disposer, S as Source } from './types-BYVnuPvd.cjs';
2
+ export { C as Cadence, P as PropSpec, b as SourceContext } from './types-BYVnuPvd.cjs';
3
+
4
+ /** Freeze the loop: no sampling, no flushing, until `resume()`. Idempotent. */
5
+ declare function pause(): void;
6
+ /** Unfreeze the loop and pick up sampling / queued writes again. Idempotent. */
7
+ declare function resume(): void;
8
+
9
+ type CoreKey = 'viewport' | 'pointer' | 'size' | 'visibility' | 'range';
10
+ /** Built-in keys autocomplete; any registered/plugin key (a string) is allowed. */
11
+ type SourceKey = CoreKey | (string & {});
12
+ /** Register a custom or plugin source so it can be used by key. */
13
+ declare function register(source: Source): void;
14
+ /** Remove a previously registered source key. */
15
+ declare function unregister(key: string): void;
16
+ /** Override prefixes or the global root target. Call before attaching sources. */
17
+ declare function configure(opts: Partial<Config>): void;
18
+ /**
19
+ * Attach sources by key. Global by default (writes to `:root`); pass a Node,
20
+ * NodeList, or array of elements as the first argument to attach to elements.
21
+ *
22
+ * propsFor(['pointer']) // → :root
23
+ * propsFor(el, ['size']) // → el
24
+ * propsFor(els, ['visibility']) // → each element in a NodeList / array
25
+ *
26
+ * Returns a disposer that tears down exactly what this call started and removes
27
+ * the custom properties it wrote.
28
+ */
29
+ declare function propsFor(keys: SourceKey[]): Disposer;
30
+ declare function propsFor(target: Element | NodeList | HTMLCollection | Element[], keys: SourceKey[]): Disposer;
31
+ /** Detach specific keys from an element, or all of them. Removes written props. */
32
+ declare function unbind(target: HTMLElement, keys?: string[]): void;
33
+ /** Tear down every active binding (and, via ref-counting, the shared observers
34
+ * and listeners they used). Useful for SPA route changes, HMR, and tests. */
35
+ declare function reset(): void;
36
+
37
+ export { Config, Disposer, Source, type SourceKey, configure, pause, propsFor, register, reset, resume, unbind, unregister };