|
|
@@ -68,10 +68,16 @@
|
|
|
/** Evaluation stack for cycle detection: {key, name}, innermost last. */
|
|
|
var evalStack = [];
|
|
|
|
|
|
- /** Elements whose property listener is attached. @type {WeakSet} */
|
|
|
- var propBound = new WeakSet();
|
|
|
- /** Elements with a live MutationObserver. @type {WeakSet} */
|
|
|
- var observed = new WeakSet();
|
|
|
+ /**
|
|
|
+ * Per-(element, source) plumbing registries: which property listeners /
|
|
|
+ * attribute observers are already attached, so several bindings may share
|
|
|
+ * one element without one starving the other (review finding V1).
|
|
|
+ * @type {WeakMap} element -> { source: true }
|
|
|
+ */
|
|
|
+ var propBound = new WeakMap();
|
|
|
+ var observed = new WeakMap();
|
|
|
+ /** Variables bound to each (element, property): element -> { name: prop }. */
|
|
|
+ var propNames = new WeakMap();
|
|
|
/** Live property values: element -> { varName: value }. @type {WeakMap} */
|
|
|
var liveProps = new WeakMap();
|
|
|
/** Stable per-element identity for cache keys. @type {WeakMap} */
|
|
|
@@ -138,33 +144,62 @@
|
|
|
return tag === 'INPUT' || tag === 'SELECT' || tag === 'TEXTAREA';
|
|
|
}
|
|
|
|
|
|
- /** Attach the redraw plumbing for one -from- binding (idempotent). */
|
|
|
+ /**
|
|
|
+ * Attach the redraw plumbing for one -from- binding. The listener/observer
|
|
|
+ * is attached ONCE per (element, source); the VARIABLE registration
|
|
|
+ * (propNames + seed) happens for every binding — several variables may
|
|
|
+ * bind the same source, and each must see updates (review finding V1).
|
|
|
+ */
|
|
|
function bindSource(el, binding) {
|
|
|
- if (isPropSource(el, binding.source)) {
|
|
|
- if (propBound.has(el)) return;
|
|
|
- propBound.add(el);
|
|
|
- var store = liveProps.get(el) || {};
|
|
|
- store[binding.name] = el[binding.source];
|
|
|
- liveProps.set(el, store);
|
|
|
+ var propKey = binding.source;
|
|
|
+ if (isPropSource(el, propKey)) {
|
|
|
+ // Register this variable against the property (always).
|
|
|
+ var names = propNames.get(el) || {};
|
|
|
+ names[binding.name] = propKey;
|
|
|
+ propNames.set(el, names);
|
|
|
+ var seed = liveProps.get(el) || {};
|
|
|
+ if (!Object.prototype.hasOwnProperty.call(seed, binding.name)) {
|
|
|
+ seed[binding.name] = el[propKey];
|
|
|
+ liveProps.set(el, seed);
|
|
|
+ }
|
|
|
+ // Attach the shared listener once per (element, property).
|
|
|
+ var bound = propBound.get(el);
|
|
|
+ if (bound && bound[propKey]) return;
|
|
|
+ if (!bound) bound = {};
|
|
|
+ bound[propKey] = true;
|
|
|
+ propBound.set(el, bound);
|
|
|
var onChange = function () {
|
|
|
- var s = liveProps.get(el);
|
|
|
- if (!s || s[binding.name] === el[binding.source]) return; // no change
|
|
|
- s[binding.name] = el[binding.source];
|
|
|
- renderAll();
|
|
|
+ var n = propNames.get(el);
|
|
|
+ if (!n) return;
|
|
|
+ var changed = false;
|
|
|
+ var store = liveProps.get(el) || {};
|
|
|
+ for (var name in n) {
|
|
|
+ var key = n[name];
|
|
|
+ if (store[name] !== el[key]) {
|
|
|
+ store[name] = el[key];
|
|
|
+ changed = true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (changed) liveProps.set(el, store);
|
|
|
+ if (changed) renderAll();
|
|
|
};
|
|
|
el.addEventListener('input', onChange);
|
|
|
el.addEventListener('change', onChange);
|
|
|
return;
|
|
|
}
|
|
|
- if (observed.has(el) || typeof MutationObserver !== 'function') return;
|
|
|
- observed.add(el);
|
|
|
- var last = el.getAttribute(binding.source);
|
|
|
+ var seen = observed.get(el);
|
|
|
+ if (seen && seen[propKey]) return;
|
|
|
+ if (typeof MutationObserver !== 'function') return;
|
|
|
+ if (!seen) seen = {};
|
|
|
+ seen[propKey] = true;
|
|
|
+ observed.set(el, seen);
|
|
|
+ var last = el.getAttribute(propKey);
|
|
|
new MutationObserver(function () {
|
|
|
- var now = el.getAttribute(binding.source);
|
|
|
+ var now = el.getAttribute(propKey);
|
|
|
if (now === last) return; // same-value write: never redraw
|
|
|
last = now;
|
|
|
renderAll();
|
|
|
- }).observe(el, { attributes: true, attributeFilter: [binding.source] });
|
|
|
+ }).observe(el, { attributes: true, attributeFilter: [propKey] });
|
|
|
}
|
|
|
|
|
|
/**
|