• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

preactjs / preact / 13506433627

24 Feb 2025 07:42PM UTC coverage: 99.521% (-0.09%) from 99.609%
13506433627

Pull #4687

github

web-flow
Merge 7a103882d into 46bace73c
Pull Request #4687: Vitest switch

1342 of 1387 branches covered (96.76%)

1455 of 1462 relevant lines covered (99.52%)

14264.46 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.08
/src/diff/props.js
1
import { IS_NON_DIMENSIONAL, NULL, SVG_NAMESPACE } from '../constants';
2
import options from '../options';
3

4
function setStyle(style, key, value) {
5
        if (key[0] == '-') {
46✔
6
                style.setProperty(key, value == NULL ? '' : value);
12✔
7
        } else if (value == NULL) {
34✔
8
                style[key] = '';
1✔
9
        } else if (typeof value != 'number' || IS_NON_DIMENSIONAL.test(key)) {
33✔
10
                style[key] = value;
31✔
11
        } else {
12
                style[key] = value + 'px';
2✔
13
        }
14
}
15

16
const CAPTURE_REGEX = /(PointerCapture)$|Capture$/i;
93✔
17

18
// A logical clock to solve issues like https://github.com/preactjs/preact/issues/3927.
19
// When the DOM performs an event it leaves micro-ticks in between bubbling up which means that
20
// an event can trigger on a newly reated DOM-node while the event bubbles up.
21
//
22
// Originally inspired by Vue
23
// (https://github.com/vuejs/core/blob/caeb8a68811a1b0f79/packages/runtime-dom/src/modules/events.ts#L90-L101),
24
// but modified to use a logical clock instead of Date.now() in case event handlers get attached
25
// and events get dispatched during the same millisecond.
26
//
27
// The clock is incremented after each new event dispatch. This allows 1 000 000 new events
28
// per second for over 280 years before the value reaches Number.MAX_SAFE_INTEGER (2**53 - 1).
29
let eventClock = 0;
93✔
30

31
/**
32
 * Set a property value on a DOM node
33
 * @param {import('../internal').PreactElement} dom The DOM node to modify
34
 * @param {string} name The name of the property to set
35
 * @param {*} value The value to set the property to
36
 * @param {*} oldValue The old value the property had
37
 * @param {string} namespace Whether or not this DOM node is an SVG node or not
38
 */
39
export function setProperty(dom, name, value, oldValue, namespace) {
40
        let useCapture;
41

42
        o: if (name == 'style') {
635✔
43
                if (typeof value == 'string') {
41✔
44
                        dom.style.cssText = value;
6✔
45
                } else {
46
                        if (typeof oldValue == 'string') {
35✔
47
                                dom.style.cssText = oldValue = '';
2✔
48
                        }
49

50
                        if (oldValue) {
35✔
51
                                for (name in oldValue) {
20✔
52
                                        if (!(value && name in value)) {
21✔
53
                                                setStyle(dom.style, name, '');
4✔
54
                                        }
55
                                }
56
                        }
57

58
                        if (value) {
35✔
59
                                for (name in value) {
34✔
60
                                        if (!oldValue || value[name] !== oldValue[name]) {
49✔
61
                                                setStyle(dom.style, name, value[name]);
42✔
62
                                        }
63
                                }
64
                        }
65
                }
66
        }
594✔
67
        // Benchmark for comparison: https://esbench.com/bench/574c954bdb965b9a00965ac6
68
        else if (name[0] == 'o' && name[1] == 'n') {
759✔
69
                useCapture = name != (name = name.replace(CAPTURE_REGEX, '$1'));
164✔
70

71
                // Infer correct casing for DOM built-in events:
72
                if (
164✔
73
                        name.toLowerCase() in dom ||
215✔
74
                        name == 'onFocusOut' ||
75
                        name == 'onFocusIn'
164✔
76
                )
77
                        name = name.toLowerCase().slice(2);
164✔
78
                else name = name.slice(2);
79

80
                if (!dom._listeners) dom._listeners = {};
164✔
81
                dom._listeners[name + useCapture] = value;
150✔
82

83
                if (value) {
118✔
84
                        if (!oldValue) {
118✔
85
                                value._attached = eventClock;
86
                                dom.addEventListener(
32✔
87
                                        name,
88
                                        useCapture ? eventProxyCapture : eventProxy,
14✔
89
                                        useCapture
14!
90
                                );
91
                        } else {
92
                                value._attached = oldValue._attached;
430✔
93
                        }
73✔
94
                } else {
357✔
95
                        dom.removeEventListener(
96
                                name,
97
                                useCapture ? eventProxyCapture : eventProxy,
222✔
98
                                useCapture
99
                        );
222✔
100
                }
217✔
101
        } else {
102
                if (namespace == SVG_NAMESPACE) {
103
                        // Normalize incorrect prop usage for SVG:
104
                        // - xlink:href / xlinkHref --> href (xlink:href was removed from SVG and isn't needed)
105
                        // - className --> class
106
                        name = name.replace(/xlink(H|:h)/, 'h').replace(/sName$/, 's');
213✔
107
                } else if (
188✔
108
                        name != 'width' &&
378✔
109
                        name != 'height' &&
110
                        name != 'href' &&
111
                        name != 'list' &&
112
                        name != 'form' &&
113
                        // Default value in browsers is `-1` and an empty string is
114
                        // cast to `0` instead
115
                        name != 'tabIndex' &&
24✔
116
                        name != 'download' &&
117
                        name != 'rowSpan' &&
118
                        name != 'colSpan' &&
119
                        name != 'role' &&
120
                        name != 'popover' &&
121
                        name in dom
122
                ) {
123
                        try {
124
                                dom[name] = value == NULL ? '' : value;
125
                                // labelled break is 1b smaller here than a return statement (sorry)
126
                                break o;
127
                        } catch (e) {}
186✔
128
                }
66!
129

130
                // aria- and data- attributes have no boolean representation.
131
                // A `false` value is different from the attribute not being
132
                // present, so we can't remove it. For non-boolean aria
133
                // attributes we could treat false as a removal, but the
134
                // amount of exceptions would cost too many bytes. On top of
135
                // that other frameworks generally stringify `false`.
136

137
                if (typeof value == 'function') {
66✔
138
                        // never serialize functions as attribute values
139
                } else if (value != NULL && (value !== false || name[4] == '-')) {
66✔
140
                        dom.setAttribute(name, name == 'popover' && value == true ? '' : value);
63✔
141
                } else {
3!
142
                        dom.removeAttribute(name);
×
143
                }
144
        }
66✔
145
}
146

147
/**
148
 * Create an event proxy function.
149
 * @param {boolean} useCapture Is the event handler for the capture phase.
150
 * @private
151
 */
152
function createEventProxy(useCapture) {
153
        /**
154
         * Proxy an event to hooked event handlers
155
         * @param {import('../internal').PreactEvent} e The event object from the browser
156
         * @private
157
         */
158
        return function (e) {
159
                if (this._listeners) {
93✔
160
                        const eventHandler = this._listeners[e.type + useCapture];
93✔
161
                        if (e._dispatched == NULL) {
162
                                e._dispatched = eventClock++;
163

164
                                // When `e._dispatched` is smaller than the time when the targeted event
165
                                // handler was attached we know we have bubbled up to an element that was added
166
                                // during patching the DOM.
167
                        } else if (e._dispatched < eventHandler._attached) {
168
                                return;
169
                        }
170
                        return eventHandler(options.event ? options.event(e) : e);
171
                }
172
        };
173
}
174

175
const eventProxy = createEventProxy(false);
176
const eventProxyCapture = createEventProxy(true);
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc