Repository navigation
observe(): cues for elements appearing, leaving, and changing state - #8
Conversation
bind() covers what someone does. Nothing covered what the interface does on its own - a toast mounting, a dialog unmounting, a drawer flipping aria-expanded - so those still needed lifecycle hooks or effects, which is the plumbing the declarative attributes exist to avoid. One MutationObserver per root behind data-foley-enter / -exit / -change. Opt-in rather than folded into bind(), because it watches for the life of the page and most apps only want interaction sounds: callers who skip it pay nothing (typical bundle is 5924 bytes before and after this commit). Decisions worth keeping: - Only state attributes are watched (aria-expanded, aria-selected, aria-checked, aria-pressed, open, data-state) via attributeFilter. Class churns on every hover and would turn the page into a rattle. - Exits play centered. A removed node is detached, so getBoundingClientRect reads zeros and panFor would swing every exit hard left - found while writing this, now guarded. - Only changes after the call fire, so the initial render stays silent. - No throttle: 100 rows appearing fire one cue, because the 60ms per-cue cooldown already collapses them. Verified in Chrome: enter, nested-in-subtree, valueless-attr default, exit, aria-expanded, data-state, class-ignored, stop(), double-observe, the 100-row storm, and that a far-left enter pans to -0.533 while its exit creates no panner at all.
ad6d43c to
cb53762
Compare
The notification buttons called play() and then built a toast. Now the toast carries data-foley-enter and observe() sounds it - the page no longer knows which cue fires or when, which is the whole point of the feature. Deletes the imperative call rather than adding a demo on top of it. observe() starts next to bind(); it only reacts to changes after that, so the initial render stays silent. Quickstart sample shows both calls.
|
Demo wired (7153916). The three notification buttons used to call function toast(kind) {
const t = TOASTS[kind];
- play(t.cue);
const box = document.getElementById("toasts");
const el = document.createElement("div");
el.className = "toast";
+ el.setAttribute("data-foley-enter", t.cue);Verified in Chrome against the real page:
The page no longer knows which cue fires, or when. One thing I deliberately did not wire: the mobile menu. It sets Also skipped |
bind()covers what a user does. Nothing covered what the interface does on its own — a toast mounting, a dialog unmounting, a drawer flippingaria-expanded. Those still needed lifecycle hooks, effects, or a state watcher, which is exactly the plumbing the declarative attributes exist to remove.One
MutationObserverper root. Opt-in, not folded intobind()— it watches for the life of the page, and most apps only want click sounds.Callers who skip it pay nothing
bind+play+set(before this PR)bind+play+set(after)observeobservetree-shakes out completely when unused — byte-identical typical bundle. That's the argument for the opt-in export over starting it insidebind().Decisions worth reviewing
aria-expanded,aria-selected,aria-checked,aria-pressed,open,data-state, viaattributeFilter. Deliberately notclass: class churns on every hover and focus and would turn the page into a rattle. Components that signal state with a class should adddata-statealongside it.getBoundingClientRect()reads all zeros andpanForwould resolve to-localize— swinging every exit hard left. Found while writing this; there's a guard pinning the literal0.Verification
7 new guards (94 tests green). Behavior verified in Chrome, since node has no DOM:
observe()data-foley-enter=""bubblearia-expanded/data-statechangeclasschangestop()observe()twicelocalizeNo console errors.
node --checkon the page module,npm test,npm run buildall green.Scope note
This does not cover "enters the viewport" — that's
IntersectionObserver, a different primitive. Both examples that motivated this (toast appearing, drawer opening) are DOM mutations, soMutationObserveris the right tool; scroll-into-view cues would be a separate feature if they're ever wanted.The demo doesn't use
observe()yet. Worth adding so the page demonstrates it rather than just shipping it — happy to do it here or as a follow-up.Version
No bump on the branch, per the usual. This wants 2.9.0 (new export) with the size claim moved 7.2 → 7.6 kB.