Spaces:
Running
Running
File size: 5,456 Bytes
e8a57cb | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 | import { IS_NON_DIMENSIONAL, NULL, SVG_NAMESPACE } from '../constants';
import options from '../options';
function setStyle(style, key, value) {
if (key[0] == '-') {
style.setProperty(key, value == NULL ? '' : value);
} else if (value == NULL) {
style[key] = '';
} else if (typeof value != 'number' || IS_NON_DIMENSIONAL.test(key)) {
style[key] = value;
} else {
style[key] = value + 'px';
}
}
const CAPTURE_REGEX = /(PointerCapture)$|Capture$/i;
// A logical clock to solve issues like https://github.com/preactjs/preact/issues/3927.
// When the DOM performs an event it leaves micro-ticks in between bubbling up which means that
// an event can trigger on a newly reated DOM-node while the event bubbles up.
//
// Originally inspired by Vue
// (https://github.com/vuejs/core/blob/caeb8a68811a1b0f79/packages/runtime-dom/src/modules/events.ts#L90-L101),
// but modified to use a logical clock instead of Date.now() in case event handlers get attached
// and events get dispatched during the same millisecond.
//
// The clock is incremented after each new event dispatch. This allows 1 000 000 new events
// per second for over 280 years before the value reaches Number.MAX_SAFE_INTEGER (2**53 - 1).
let eventClock = 0;
/**
* Set a property value on a DOM node
* @param {import('../internal').PreactElement} dom The DOM node to modify
* @param {string} name The name of the property to set
* @param {*} value The value to set the property to
* @param {*} oldValue The old value the property had
* @param {string} namespace Whether or not this DOM node is an SVG node or not
*/
export function setProperty(dom, name, value, oldValue, namespace) {
let useCapture;
o: if (name == 'style') {
if (typeof value == 'string') {
dom.style.cssText = value;
} else {
if (typeof oldValue == 'string') {
dom.style.cssText = oldValue = '';
}
if (oldValue) {
for (name in oldValue) {
if (!(value && name in value)) {
setStyle(dom.style, name, '');
}
}
}
if (value) {
for (name in value) {
if (!oldValue || value[name] != oldValue[name]) {
setStyle(dom.style, name, value[name]);
}
}
}
}
}
// Benchmark for comparison: https://esbench.com/bench/574c954bdb965b9a00965ac6
else if (name[0] == 'o' && name[1] == 'n') {
useCapture = name != (name = name.replace(CAPTURE_REGEX, '$1'));
const lowerCaseName = name.toLowerCase();
// Infer correct casing for DOM built-in events:
if (lowerCaseName in dom || name == 'onFocusOut' || name == 'onFocusIn')
name = lowerCaseName.slice(2);
else name = name.slice(2);
if (!dom._listeners) dom._listeners = {};
dom._listeners[name + useCapture] = value;
if (value) {
if (!oldValue) {
value._attached = eventClock;
dom.addEventListener(
name,
useCapture ? eventProxyCapture : eventProxy,
useCapture
);
} else {
value._attached = oldValue._attached;
}
} else {
dom.removeEventListener(
name,
useCapture ? eventProxyCapture : eventProxy,
useCapture
);
}
} else {
if (namespace == SVG_NAMESPACE) {
// Normalize incorrect prop usage for SVG:
// - xlink:href / xlinkHref --> href (xlink:href was removed from SVG and isn't needed)
// - className --> class
name = name.replace(/xlink(H|:h)/, 'h').replace(/sName$/, 's');
} else if (
name != 'width' &&
name != 'height' &&
name != 'href' &&
name != 'list' &&
name != 'form' &&
// Default value in browsers is `-1` and an empty string is
// cast to `0` instead
name != 'tabIndex' &&
name != 'download' &&
name != 'rowSpan' &&
name != 'colSpan' &&
name != 'role' &&
name != 'popover' &&
name in dom
) {
try {
dom[name] = value == NULL ? '' : value;
// labelled break is 1b smaller here than a return statement (sorry)
break o;
} catch (e) {}
}
// aria- and data- attributes have no boolean representation.
// A `false` value is different from the attribute not being
// present, so we can't remove it. For non-boolean aria
// attributes we could treat false as a removal, but the
// amount of exceptions would cost too many bytes. On top of
// that other frameworks generally stringify `false`.
if (typeof value == 'function') {
// never serialize functions as attribute values
} else if (value != NULL && (value !== false || name[4] == '-')) {
dom.setAttribute(name, name == 'popover' && value == true ? '' : value);
} else {
dom.removeAttribute(name);
}
}
}
/**
* Create an event proxy function.
* @param {boolean} useCapture Is the event handler for the capture phase.
* @private
*/
function createEventProxy(useCapture) {
/**
* Proxy an event to hooked event handlers
* @param {import('../internal').PreactEvent} e The event object from the browser
* @private
*/
return function (e) {
if (this._listeners) {
const eventHandler = this._listeners[e.type + useCapture];
if (e._dispatched == NULL) {
e._dispatched = eventClock++;
// When `e._dispatched` is smaller than the time when the targeted event
// handler was attached we know we have bubbled up to an element that was added
// during patching the DOM.
} else if (e._dispatched < eventHandler._attached) {
return;
}
return eventHandler(options.event ? options.event(e) : e);
}
};
}
const eventProxy = createEventProxy(false);
const eventProxyCapture = createEventProxy(true);
|