diff --git a/src/dom.js b/src/dom.js index 03270db..5f873be 100644 --- a/src/dom.js +++ b/src/dom.js @@ -69,42 +69,54 @@ export { createElement as el }; import { prop_process } from './dom-common.js'; const { setDeleteAttr }= prop_process; +const assign_context= new WeakMap(); export function assign(element, ...attributes){ - const _this= this; - const s= signals(this); if(!attributes.length) return element; + assign_context.set(element, assignContext(element, this)); + + for(const [ key, value ] of Object.entries(Object.assign({}, ...attributes))) + assignAttribute.call(this, element, key, value); + assign_context.delete(element); + return element; +} +export function assignAttribute(element, key, value){ + /* jshint maxcomplexity:14 */ + const { setRemoveAttr, s }= assignContext(element, this); + const _this= this; + + value= s.processReactiveAttribute(element, key, value, + (key, value)=> assignAttribute.call(_this, element, key, value)); + const [ k ]= key; + if("="===k) return setRemoveAttr(key.slice(1), value); + if("."===k) return setDelete(element, key.slice(1), value); + if(/(aria|data)([A-Z])/.test(key)){//TODO: temporal as aria* exists in Element for some browsers + key= key.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase(); + return setRemoveAttr(key, value); + } + if("className"===key) key= "class";//just optimalization, `isPropSetter` returns false immediately + switch(key){ + case "xlink:href": + return setRemoveAttr(key, value, "http://www.w3.org/1999/xlink"); + case "textContent": //just optimalization, its part of Node ⇒ deep for `isPropSetter` + return setDeleteAttr(element, key, value); + case "style": + if(typeof value!=="object") break; + /* falls through */ + case "dataset": + return forEachEntries(s, value, setDelete.bind(null, element[key])); + case "ariaset": + return forEachEntries(s, value, (key, val)=> setRemoveAttr("aria-"+key, val)); + case "classList": + return classListDeclarative.call(_this, element, value); + } + return isPropSetter(element, key) ? setDeleteAttr(element, key, value) : setRemoveAttr(key, value); +} +function assignContext(element, _this){ + if(assign_context.has(element)) return assign_context.get(element); const is_svg= element instanceof SVGElement; const setRemoveAttr= (is_svg ? setRemoveNS : setRemove).bind(null, element, "Attribute"); - - /* jshint maxcomplexity:13 */ - Object.entries(Object.assign({}, ...attributes)).forEach(function assignNth([ key, attr ]){ - attr= s.processReactiveAttribute(element, key, attr, assignNth); - const [ k ]= key; - if("="===k) return setRemoveAttr(key.slice(1), attr); - if("."===k) return setDelete(element, key.slice(1), attr); - if(/(aria|data)([A-Z])/.test(key)){//TODO: temporal as aria* exists in Element for some browsers - key= key.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase(); - return setRemoveAttr(key, attr); - } - if("className"===key) key= "class";//just optimalization, `isPropSetter` returns false immediately - switch(key){ - case "xlink:href": - return setRemoveAttr(key, attr, "http://www.w3.org/1999/xlink"); - case "textContent": //just optimalization, its part of Node ⇒ deep for `isPropSetter` - return setDeleteAttr(element, key, attr); - case "style": - if(typeof attr!=="object") break; - /* falls through */ - case "dataset": - return forEachEntries(s, attr, setDelete.bind(null, element[key])); - case "ariaset": - return forEachEntries(s, attr, (key, val)=> setRemoveAttr("aria-"+key, val)); - case "classList": - return classListDeclarative.call(_this, element, attr); - } - return isPropSetter(element, key) ? setDeleteAttr(element, key, attr) : setRemoveAttr(key, attr); - }); - return element; + const s= signals(_this); + return { setRemoveAttr, s }; } export function classListDeclarative(element, toggle){ const s= signals(this); @@ -137,7 +149,7 @@ function forEachEntries(s, obj, cb){ if(typeof obj !== "object" || obj===null) return; return Object.entries(obj).forEach(function process([ key, val ]){ if(!key) return; - val= s.processReactiveAttribute(obj, key, val, a=> cb(...a)); + val= s.processReactiveAttribute(obj, key, val, cb); cb(key, val); }); } diff --git a/src/signals-common.js b/src/signals-common.js index d0b42c5..abe281a 100644 --- a/src/signals-common.js +++ b/src/signals-common.js @@ -1,6 +1,6 @@ export const signals_global= { isSignal(attributes){ return false; }, - processReactiveAttribute(obj, key, attr, assignNth){ return attr; }, + processReactiveAttribute(obj, key, attr, set){ return attr; }, }; export function registerReactivity(def, global= true){ if(global) return Object.assign(signals_global, def); diff --git a/src/signals-lib.js b/src/signals-lib.js index 6950dc2..f4c55dc 100644 --- a/src/signals-lib.js +++ b/src/signals-lib.js @@ -107,9 +107,9 @@ S.el= function(signal, map){ import { typeOf } from './helpers.js'; export const signals_config= { isSignal, - processReactiveAttribute(_, key, attrs, assignNth){ + processReactiveAttribute(_, key, attrs, set){ if(!isSignal(attrs)) return attrs; - const l= attr=> assignNth([ key, attr ]); + const l= attr=> set(key, attr); addSignalListener(attrs, l); removeSignalsFromElements(attrs, l, _, key); return attrs();