Skip to content

Releases: argyleink/prop-for-that

v0.7.4

15 Jun 21:48

Choose a tag to compare

Added

  • keyboard plugin (global) — exposes the soft (on-screen) keyboard's
    geometry on :root: --live-keyboard-open (1/0), --live-keyboard-height,
    --live-keyboard-width, and --live-keyboard-x / -y / -right / -bottom
    (px, client coordinates), so layout can reflow around the keyboard with pure
    CSS — padding-block-end: calc(var(--live-keyboard-height) * 1px). Reads exact
    geometry from the VirtualKeyboard API on Chromium/Android (it sets
    navigator.virtualKeyboard.overlaysContent = true while bound — so the
    keyboard overlays content instead of resizing the viewport — and restores it on
    dispose), and falls back to inferring the height from visualViewport shrink on
    iOS/Safari (approximate: a pinch-zoom can read as a keyboard). Seeds zeros where
    neither API is available. (#6)
  • meta plugin (global) — writes every <meta name|property + content> on
    the page as a write-once --const-meta-<slug> (the slug lowercases the name and
    collapses runs of non-alphanumerics to a single dash): --const-meta-theme-color,
    --const-meta-og-image, --const-meta-color-scheme, etc. — so CSS can read page
    metadata directly (background: var(--const-meta-theme-color),
    background-image: url(var(--const-meta-og-image))). First match wins per name,
    and a media-conditional meta is skipped when its query doesn't currently match,
    so the active theme-color variant is the one written. Read once at bind on the
    const cadence, so meta tags swapped in by a framework after bind aren't
    tracked. (#7)

Both are tree-shakeable opt-in plugins — import from prop-for-that/plugins, or
let auto load them on demand via data-props-for.

v0.7.3

15 Jun 20:42

Choose a tag to compare

Changed

  • Event-driven element sources no longer use the off-screen viewport gate.
    range, field, field-state, form-state, select, color-input, and
    img now run ungated (gate: false). They only write in response to user
    interaction or an image load — neither of which can happen while the element
    is off screen — so gating them only added an IntersectionObserver
    subscription plus a tear-down / re-seed cycle on every scroll in and out, for
    no benefit. Ungated, they seed their values once at bind time (so a var()
    resolves immediately, even for an element below the fold) and never tear down.
    Continuously-sampling element sources (size, pointer-local, media,
    img-color, video-color) stay gated, where pausing off screen is a real
    saving. Additive: off-screen elements that previously had no props until they
    scrolled in now carry their seeded values from the start.

Fixed

  • field-state no longer loses its latched interaction history when its
    element scrolls off screen and back.
    Because it was viewport-gated,
    scrolling the field out of view tore the source down and scrolling back
    re-ran it — re-snapshotting each field's "initial" value from the current
    (already-edited) value and clearing the dirty / touched / changed latches, so
    a field would report pristine again. Now that it runs ungated (above), the
    snapshot and latches are taken once and persist for the binding's life.
  • The shared frame loop no longer keeps requestAnimationFrame scheduled when
    no flush is owed.
    Under a liveHz cadence cap a throttled frame rescheduled
    unconditionally; it now reschedules only while a continuous sampler is
    registered or a flush is still pending, so a one-shot write can't leave rAF
    spinning after its work is done. Any running rAF can demote compositor-driven
    CSS animations, so the loop is kept as quiet as possible — it idles the moment
    nothing needs it (regression test added).
  • writer.forget() no longer leaves an empty entry that makes a queued frame
    flush nothing.
    When a source's disposal raced a same-frame write, the
    emptied target lingered in the pending map past the flush fast-path's size
    check; it is now removed so the frame is a true no-op.

Performance

  • Removed a per-frame array allocation from the loop's sampler dispatch
    ([...frameFns] → direct Set iteration). Samplers only ever remove
    themselves, which Set iteration handles safely, so the defensive copy was
    unnecessary on the one path that runs every frame.
  • motion and orientation now attach through the shared, ref-counted
    onWindow helper instead of raw window.addEventListener, matching every
    other window-driven source — one real listener per event type for the page.

v0.7.2

14 Jun 16:27

Choose a tag to compare

Fixed

  • fps source no longer writes --live-fps every frame. The exponential
    moving average is still sampled per frame, but the value is now written at most
    every 250 ms (≤4 Hz). Writing per frame coupled the readout to the frame rate:
    each write invalidates --live-fps for the whole tree, and on a large reactive
    DOM that restyle is heavy enough to lower the frame rate, which changes the
    rounded value, which writes again — a feedback loop that spiralled FPS downward
    and accumulated restyle/GC work in Firefox until the tab hung and crashed (no
    user interaction required). The throttled write breaks the loop; an fps readout
    doesn't need to update faster than a few hertz anyway. Values are unchanged
    (rounded EMA), only the write cadence is bounded.

v0.7.1

14 Jun 13:32
134fee7

Choose a tag to compare

Fixed

  • visibility now tolerates near-1 intersection ratios. The shared
    IntersectionObserver requests callbacks just below full containment, and the
    visibility source confirms full visibility from the entry geometry instead of
    requiring an exact intersectionRatio === 1. This fixes scroll-triggered
    reveal examples that could get stuck on Firefox mobile when the browser reports
    a ratio fractionally below 1.

v0.7.0

13 Jun 16:43

Choose a tag to compare

Breaking release (0.x minor): leaner defaults, viewport-aware sources, and a
truly zero-config auto. Migration notes are under each item.

Changed

  • auto is now fully declarative and attaches nothing by default. It no
    longer auto-attaches viewport/pointer to :root. Declare every source —
    globals included — with data-props-for, e.g. <html data-props-for="viewport pointer">. Migration: add the globals you relied on to the root <html>.
  • auto loads plugins on demand. The first time a data-props-for key needs
    a plugin, its chunk is dynamically imported and registered, then the binding
    attaches — no more registerPlugins() / prop-for-that/plugins import when
    using auto. Because it lazy-loads via dynamic import(), load auto as a
    module: <script type="module" src=".../auto.js">. The classic
    auto.global.js drop-in is removed (it can't resolve lazy chunks from a CDN).
    Migration: switch the auto script tag to type="module" and drop any
    registerPlugins() calls that existed only to feed auto.
  • Element sources are viewport-gated. An element-scoped source now runs only
    while its element is in the viewport (via the shared IntersectionObserver);
    off screen its work is torn down and its last values freeze in place, resuming +
    re-seeding on re-entry. Global sources and :root bindings are never gated; a
    source can opt out with gate: false. No API change for consumers.
  • The frame loop freezes while the tab is hidden (document.hidden) and stays
    fully idle when nothing is changing — fewer stray requestAnimationFrames on
    pages that aren't actively updating.
  • Shared ResizeObserver/IntersectionObserver now support multiple subscribers
    per element (with last-entry replay to late subscribers), so an element can be
    watched by both a source and the gate at once.
  • auto no longer caches a failed plugin load permanently — a transient failure
    (offline, blocked, flaky CDN) is dropped so a later request for the same key retries.
  • img-color memoizes its extracted palette per rendered source, so the viewport
    gate re-running start() on scroll-in reuses the result instead of re-sampling the
    canvas when the image hasn't changed.
  • Documented the auto path's two limits (light-DOM only / no shadow roots; needs a
    CDN that serves the dist tree verbatim) and the gate contract for source authors
    (start() is re-invoked on every viewport re-entry, so keep it cheap + idempotent).

Added

  • gate?: boolean on Source — opt an element source out of viewport gating
    (defaults on for scope: 'element'). visibility sets it false.
  • isRegistered(key) exported from prop-for-that — whether a source key is
    currently registered.

Removed

  • pointer is no longer a core source — it writes on every pointermove, so
    it's now an opt-in plugin alongside pointer-local. Migration: under auto
    it just works (loaded on demand). Imperatively, register it first:
    import { pointer } from 'prop-for-that/plugins'; register(pointer); propsFor(['pointer']).
  • dist/auto.global.js (the classic, non-module auto drop-in) — see the
    auto note above.

v0.6.4

12 Jun 08:19

Choose a tag to compare

Added

  • nav-type plugin--const-nav-type: how the user arrived at the page,
    written once as a string (navigate / reload / back_forward / prerender)
    from performance.getEntriesByType('navigation')[0].type. Pairs with style
    queries — @container style(--const-nav-type: reload) { … }. Global scope,
    const cadence; typed mode registers it as a <custom-ident>. Import as
    navType from prop-for-that/plugins. (Rides the batched writer, so it lands
    the frame after bind — read the same value in a <head> script if you need it
    before first paint.)