el[key] = '';
el.removeAttribute(key);
return;
} else if (type === 'number') {
// e.g.
el[key] = 0;
el.removeAttribute(key);
return;
}
} // some properties perform value validation and throw
try {
el[key] = value;
} catch (e) {
}
} // Async edge case fix requires storing an event listener's attach timestamp.
let _getNow = Date.now;
let skipTimestampCheck = false;
if (typeof window !== 'undefined') {
// Determine what event timestamp the browser is using. Annoyingly, the
// timestamp can either be hi-res (relative to page load) or low-res
// (relative to UNIX epoch), so in order to compare time we have to use the
// same timestamp type when saving the flush timestamp.
if (_getNow() > document.createEvent('Event').timeStamp) {
// if the low-res timestamp which is bigger than the event timestamp
// (which is evaluated AFTER) it means the event is using a hi-res timestamp,
// and we need to use the hi-res version for event listeners as well.
_getNow = () => performance.now();
} // #3485: Firefox <= 53 has incorrect Event.timeStamp implementation
// and does not fire microtasks in between event propagation, so safe to exclude.
const ffMatch = navigator.userAgent.match(/firefox\/(\d+)/i);
skipTimestampCheck = !!(ffMatch && Number(ffMatch[1]) <= 53);
} // To avoid the overhead of repeatedly calling performance.now(), we cache
// and use the same timestamp for all event listeners attached in the same tick.
let cachedNow = 0;
const p = Promise.resolve();
const reset = () => {
cachedNow = 0;
};
const getNow = () => cachedNow || (p.then(reset), cachedNow = _getNow());
function addEventListener(el, event, handler, options) {
el.addEventListener(event, handler, options);
}
function removeEventListener(el, event, handler, options) {
el.removeEventListener(event, handler, options);
}
function patchEvent(el, rawName, prevValue, nextValue, instance = null) {
// vei = vue event invokers
const invokers = el._vei || (el._vei = {});
const existingInvoker = invokers[rawName];
if (nextValue && existingInvoker) {
// patch
existingInvoker.value = nextValue;
} else {
const [name, options] = parseName(rawName);
if (nextValue) {
// add
const invoker = invokers[rawName] = createInvoker(nextValue, instance);
addEventListener(el, name, invoker, options);
} else if (existingInvoker) {
// remove
removeEventListener(el, name, existingInvoker, options);
invokers[rawName] = undefined;
}
}
}
const optionsModifierRE = /(?:Once|Passive|Capture)$/;
function parseName(name) {
let options;
if (optionsModifierRE.test(name)) {
options = {};
let m;
while (m = name.match(optionsModifierRE)) {
name = name.slice(0, name.length - m[0].length);
options[m[0].toLowerCase()] = true;
}
}
return [hyphenate(name.slice(2)), options];
}
function createInvoker(initialValue, instance) {
const invoker = e => {
// async edge case #6566: inner click event triggers patch, event handler
// attached to outer element during patch, and triggered again. This
// happens because browsers fire microtask ticks between event propagation.
// the solution is simple: we save the timestamp when a handler is attached,
// and the handler would only fire if the event passed to it was fired
// AFTER it was attached.
const timeStamp = e.timeStamp || _getNow();
if (skipTimestampCheck || timeStamp >= invoker.attached - 1) {
callWithAsyncErrorHandling(patchStopImmediatePropagation(e, invoker.value), instance, 5
/* NATIVE_EVENT_HANDLER */
, [e]);
}
};
invoker.value = initialValue;
invoker.attached = getNow();
return invoker;
}
function patchStopImmediatePropagation(e, value) {
if (isArray(value)) {
const originalStop = e.stopImmediatePropagation;
e.stopImmediatePropagation = () => {
originalStop.call(e);
e._stopped = true;
};
return value.map(fn => e => !e._stopped && fn(e));
} else {
return value;
}
}
const nativeOnRE = /^on[a-z]/;
const forcePatchProp = (_, key) => key === 'value';
const patchProp = (el, key, prevValue, nextValue, isSVG = false, prevChildren, parentComponent, parentSuspense, unmountChildren) => {
switch (key) {
// special
case 'class':
patchClass(el, nextValue, isSVG);
break;
case 'style':
patchStyle(el, prevValue, nextValue);
break;
default:
if (isOn(key)) {
// ignore v-model listeners
if (!isModelListener(key)) {
patchEvent(el, key, prevValue, nextValue, parentComponent);
}
} else if (shouldSetAsProp(el, key, nextValue, isSVG)) {
patchDOMProp(el, key, nextValue, prevChildren, parentComponent, parentSuspense, unmountChildren);
} else {
// special case for
with
// :true-value & :false-value
// store value as dom properties since non-string values will be
// stringified.
if (key === 'true-value') {
el._trueValue = nextValue;
} else if (key === 'false-value') {
el._falseValue = nextValue;
}
patchAttr(el, key, nextValue, isSVG);
}
break;
}
};
function shouldSetAsProp(el, key, value, isSVG) {
if (isSVG) {
// most keys must be set as attribute on svg elements to work
// ...except innerHTML
if (key === 'innerHTML') {
return true;
} // or native onclick with function values
if (key in el && nativeOnRE.test(key) && isFunction(value)) {
return true;
}
return false;
} // spellcheck and draggable are numerated attrs, however their
// corresponding DOM properties are actually booleans - this leads to
// setting it with a string "false" value leading it to be coerced to
// `true`, so we need to always treat them as attributes.
// Note that `contentEditable` doesn't have this problem: its DOM
// property is also enumerated string values.
if (key === 'spellcheck' || key === 'draggable') {
return false;
} // #1787, #2840 form property on form elements is readonly and must be set as
// attribute.
if (key === 'form') {
return false;
} // #1526
must be set as attribute
if (key === 'list' && el.tagName === 'INPUT') {
return false;
} // #2766