function syncBooleanAttrProp(fromEl, toEl, name) { if (fromEl[name] !== toEl[name]) { fromEl[name] = toEl[name]; if (fromEl[name]) { fromEl.setAttribute(name, ''); } else { fromEl.removeAttribute(name); } } } export default { OPTION: function(fromEl, toEl) { var parentNode = fromEl.parentNode; if (parentNode) { var parentName = parentNode.nodeName.toUpperCase(); if (parentName === 'OPTGROUP') { parentNode = parentNode.parentNode; parentName = parentNode && parentNode.nodeName.toUpperCase(); } if (parentName === 'SELECT' && !parentNode.hasAttribute('multiple')) { if (fromEl.hasAttribute('selected') && !toEl.selected) { // Workaround for MS Edge bug where the 'selected' attribute can only be // removed if set to a non-empty value: // https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/12087679/ fromEl.setAttribute('selected', 'selected'); fromEl.removeAttribute('selected'); } // We have to reset select element's selectedIndex to -1, otherwise setting // fromEl.selected using the syncBooleanAttrProp below has no effect. // The correct selectedIndex will be set in the SELECT special handler below. parentNode.selectedIndex = -1; } } syncBooleanAttrProp(fromEl, toEl, 'selected'); }, /** * The "value" attribute is special for the element since it sets * the initial value. Changing the "value" attribute without changing the * "value" property will have no effect since it is only used to the set the * initial value. Similar for the "checked" attribute, and "disabled". */ INPUT: function(fromEl, toEl) { syncBooleanAttrProp(fromEl, toEl, 'checked'); syncBooleanAttrProp(fromEl, toEl, 'disabled'); if (fromEl.value !== toEl.value) { fromEl.value = toEl.value; } if (!toEl.hasAttribute('value')) { fromEl.removeAttribute('value'); } }, TEXTAREA: function(fromEl, toEl) { var newValue = toEl.value; if (fromEl.value !== newValue) { fromEl.value = newValue; } var firstChild = fromEl.firstChild; if (firstChild) { // Needed for IE. Apparently IE sets the placeholder as the // node value and vise versa. This ignores an empty update. var oldValue = firstChild.nodeValue; if (oldValue == newValue || (!newValue && oldValue == fromEl.placeholder)) { return; } firstChild.nodeValue = newValue; } }, SELECT: function(fromEl, toEl) { if (!toEl.hasAttribute('multiple')) { var selectedIndex = -1; var i = 0; // We have to loop through children of fromEl, not toEl since nodes can be moved // from toEl to fromEl directly when morphing. // At the time this special handler is invoked, all children have already been morphed // and appended to / removed from fromEl, so using fromEl here is safe and correct. var curChild = fromEl.firstChild; var optgroup; var nodeName; while(curChild) { nodeName = curChild.nodeName && curChild.nodeName.toUpperCase(); if (nodeName === 'OPTGROUP') { optgroup = curChild; curChild = optgroup.firstChild; } else { if (nodeName === 'OPTION') { if (curChild.hasAttribute('selected')) { selectedIndex = i; break; } i++; } curChild = curChild.nextSibling; if (!curChild && optgroup) { curChild = optgroup.nextSibling; optgroup = null; } } } fromEl.selectedIndex = selectedIndex; } } };