watchAttributes
ts
watchAttributes(el: Element, callback: (change: AttributeChange) => void): () => voidObserves every attribute of one element, through a second, unfiltered observer.
ts
interface AttributeChange {
name: string;
value: string | null;
previousValue: string | null;
}Why it exists
attributeFilter takes exact names and the DOM has no wildcard, so the engine cannot see an attribute the framework cannot name. This is the opt-in, and the page pays for the elements that ask.
Usage
js
import { Base, watchAttributes } from '@studiometa/js-toolkit';
class Bindings extends Base {
static config = { name: 'Bindings' };
mounted() {
return watchAttributes(this.$el, ({ name, value, previousValue }) => {
if (!name.startsWith('data-my-')) return;
// …
});
}
}The caller owns it. The helper returns one idempotent cleanup and knows nothing about Base: a component calls it from mounted() and returns the cleanup.
The contract
- The records join the shared queue. They are drained wherever the engine drains its own,
whenDOMSettled()included, and they are reported from the same background task — as the last step of the batch. - A callback runs after the framework work of the batch. So a component that stops its watcher during the same batch in which its own declaration is withdrawn hears nothing about the attribute change.
- Changes are coalesced, with the rule of
option<Name>Changed(): several writes in one batch give one change, from the value before the first write to the value at the end, and a write that ends where it started is not a change. - The payload covers the element's whole attribute set, framework names included, with raw strings and
nullfor an absent attribute. A caller narrows by prefix. - A failing callback is reported as
callback.attribute-watcher-failed, so one watcher cannot stop another.
What to reach for instead
| Want | Use |
|---|---|
| a declared option | option<Name>Changed() |
| a whole namespace, with keyed bindings | watchAttributeNamespace() |
| a subtree, character data, or a foreign node | useMutation() |
A declared option is already in the one page-wide filter, and costs no second observer.