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

preactjs / preact / 13852829444

14 Mar 2025 08:37AM UTC coverage: 99.456% (-0.2%) from 99.61%
13852829444

Pull #4549

github

web-flow
Merge ef04ef633 into 64242f424
Pull Request #4549: (major) - Tracking PR for v11

1081 of 1107 branches covered (97.65%)

25 of 28 new or added lines in 11 files covered. (89.29%)

1 existing line in 1 file now uncovered.

731 of 735 relevant lines covered (99.46%)

12179.49 hits per line

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

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

4
function setStyle(style, key, value) {
5
        if (key[0] == '-') {
190✔
6
                style.setProperty(key, value == NULL ? '' : value);
13✔
7
        } else if (value == NULL) {
8
                style[key] = '';
177✔
9
        } else {
10
                style[key] = value;
11
        }
12
}
13

14
const CAPTURE_REGEX = /(PointerCapture)$|Capture$/i;
15

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

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

40
        o: if (name == 'style') {
625✔
41
                if (typeof value == 'string') {
42✔
42
                        dom.style.cssText = value;
6✔
43
                } else {
44
                        if (typeof oldValue == 'string') {
38✔
45
                                dom.style.cssText = oldValue = '';
46
                        }
47

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

56
                        if (value) {
36✔
57
                                for (name in value) {
35✔
58
                                        if (!oldValue || value[name] != oldValue[name]) {
193✔
59
                                                setStyle(dom.style, name, value[name]);
60
                                        }
61
                                }
62
                        }
63
                }
64
        }
65
        // Benchmark for comparison: https://esbench.com/bench/574c954bdb965b9a00965ac6
66
        else if (name[0] == 'o' && name[1] == 'n') {
583✔
67
                useCapture = name != (name = name.replace(CAPTURE_REGEX, '$1'));
173✔
68

69
                // Infer correct casing for events:
70
                if (name[2].toLowerCase() != name[2]) name = name.toLowerCase().slice(2);
173✔
71
                else name = name.slice(2);
72

73
                if (!dom._listeners) dom._listeners = {};
291✔
74
                dom._listeners[name + useCapture] = value;
75

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

123
                // aria- and data- attributes have no boolean representation.
124
                // A `false` value is different from the attribute not being
125
                // present, so we can't remove it. For non-boolean aria
126
                // attributes we could treat false as a removal, but the
127
                // amount of exceptions would cost too many bytes. On top of
128
                // that other frameworks generally stringify `false`.
129

130
                if (typeof value == 'function') {
211✔
131
                        // never serialize functions as attribute values
132
                } else if (value != NULL && (value !== false || name[4] == '-')) {
198✔
133
                        dom.setAttribute(name, name == 'popover' && value == true ? '' : value);
374✔
134
                } else {
135
                        dom.removeAttribute(name);
210✔
136
                }
137
        }
138
}
139

140
/**
141
 * Create an event proxy function.
142
 * @param {boolean} useCapture Is the event handler for the capture phase.
143
 * @private
144
 */
145
function createEventProxy(useCapture) {
146
        /**
147
         * Proxy an event to hooked event handlers
148
         * @param {import('../internal').PreactEvent} e The event object from the browser
149
         * @private
150
         */
151
        return function (e) {
188✔
152
                if (this._listeners) {
72✔
153
                        const eventHandler = this._listeners[e.type + useCapture];
72✔
154
                        if (e._dispatched == NULL) {
72✔
155
                                e._dispatched = eventClock++;
69✔
156

157
                                // When `e._dispatched` is smaller than the time when the targeted event
158
                                // handler was attached we know we have bubbled up to an element that was added
159
                                // during patching the DOM.
160
                        } else if (e._dispatched < eventHandler._attached) {
3!
UNCOV
161
                                return;
×
162
                        }
163
                        return eventHandler(options.event ? options.event(e) : e);
72✔
164
                }
165
        };
166
}
167

168
const eventProxy = createEventProxy(false);
169
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