kiru 1.0.1 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/appHandle.js +2 -2
- package/dist/appHandle.js.map +1 -1
- package/dist/components/lazy.d.ts.map +1 -1
- package/dist/components/lazy.js +2 -2
- package/dist/components/lazy.js.map +1 -1
- package/dist/components/transition.js +1 -5
- package/dist/components/transition.js.map +1 -1
- package/dist/devtools.d.ts.map +1 -1
- package/dist/devtools.js +6 -2
- package/dist/devtools.js.map +1 -1
- package/dist/dom/commit.d.ts +5 -0
- package/dist/dom/commit.d.ts.map +1 -0
- package/dist/dom/commit.js +94 -0
- package/dist/dom/commit.js.map +1 -0
- package/dist/dom/focus.d.ts +4 -0
- package/dist/dom/focus.d.ts.map +1 -0
- package/dist/dom/focus.js +32 -0
- package/dist/dom/focus.js.map +1 -0
- package/dist/dom/index.d.ts +4 -0
- package/dist/dom/index.d.ts.map +1 -0
- package/dist/dom/index.js +4 -0
- package/dist/dom/index.js.map +1 -0
- package/dist/dom/nodes.d.ts +12 -0
- package/dist/dom/nodes.d.ts.map +1 -0
- package/dist/dom/nodes.js +165 -0
- package/dist/dom/nodes.js.map +1 -0
- package/dist/dom/props.d.ts +8 -0
- package/dist/dom/props.d.ts.map +1 -0
- package/dist/dom/props.js +675 -0
- package/dist/dom/props.js.map +1 -0
- package/dist/env.d.ts +2 -0
- package/dist/env.d.ts.map +1 -1
- package/dist/env.js +2 -0
- package/dist/env.js.map +1 -1
- package/dist/globalContext.d.ts +3 -8
- package/dist/globalContext.d.ts.map +1 -1
- package/dist/globalContext.js +4 -16
- package/dist/globalContext.js.map +1 -1
- package/dist/globals.d.ts +21 -1
- package/dist/globals.d.ts.map +1 -1
- package/dist/globals.js +22 -2
- package/dist/globals.js.map +1 -1
- package/dist/hmr.d.ts +17 -2
- package/dist/hmr.d.ts.map +1 -1
- package/dist/hmr.js +31 -5
- package/dist/hmr.js.map +1 -1
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +1 -0
- package/dist/hooks/index.js.map +1 -1
- package/dist/hooks/onBeforeMount.d.ts +1 -1
- package/dist/hooks/onBeforeMount.d.ts.map +1 -1
- package/dist/hooks/onBeforeMount.js +10 -3
- package/dist/hooks/onBeforeMount.js.map +1 -1
- package/dist/hooks/onCleanup.d.ts +1 -1
- package/dist/hooks/onCleanup.d.ts.map +1 -1
- package/dist/hooks/onCleanup.js +7 -4
- package/dist/hooks/onCleanup.js.map +1 -1
- package/dist/hooks/onMount.d.ts +2 -2
- package/dist/hooks/onMount.d.ts.map +1 -1
- package/dist/hooks/onMount.js +11 -4
- package/dist/hooks/onMount.js.map +1 -1
- package/dist/hooks/setup.d.ts +13 -0
- package/dist/hooks/setup.d.ts.map +1 -0
- package/dist/hooks/setup.js +54 -0
- package/dist/hooks/setup.js.map +1 -0
- package/dist/hooks/utils.d.ts +2 -3
- package/dist/hooks/utils.d.ts.map +1 -1
- package/dist/hooks/utils.js +9 -14
- package/dist/hooks/utils.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -3
- package/dist/index.js.map +1 -1
- package/dist/reconciler.js +3 -3
- package/dist/reconciler.js.map +1 -1
- package/dist/router/head.js +2 -2
- package/dist/router/head.js.map +1 -1
- package/dist/router/pageConfig.js +2 -2
- package/dist/router/pageConfig.js.map +1 -1
- package/dist/scheduler.js +62 -57
- package/dist/scheduler.js.map +1 -1
- package/dist/signals/base.js +3 -3
- package/dist/signals/base.js.map +1 -1
- package/dist/signals/effect.d.ts.map +1 -1
- package/dist/signals/effect.js +6 -6
- package/dist/signals/effect.js.map +1 -1
- package/dist/signals/tracking.d.ts +3 -2
- package/dist/signals/tracking.d.ts.map +1 -1
- package/dist/signals/tracking.js.map +1 -1
- package/dist/statefulPromise.js +2 -2
- package/dist/statefulPromise.js.map +1 -1
- package/dist/types.d.ts +5 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.dom.d.ts +1 -1
- package/dist/types.dom.d.ts.map +1 -1
- package/dist/utils/format.d.ts.map +1 -1
- package/dist/utils/format.js +4 -1
- package/dist/utils/format.js.map +1 -1
- package/dist/utils/vdom.d.ts +2 -2
- package/dist/utils/vdom.d.ts.map +1 -1
- package/dist/utils/vdom.js +2 -2
- package/dist/utils/vdom.js.map +1 -1
- package/dist/viewTransitions.d.ts.map +1 -1
- package/dist/viewTransitions.js +2 -1
- package/dist/viewTransitions.js.map +1 -1
- package/package.json +1 -1
- package/src/appHandle.ts +2 -2
- package/src/components/lazy.ts +5 -6
- package/src/components/transition.ts +2 -6
- package/src/devtools.ts +4 -2
- package/src/dom/commit.ts +133 -0
- package/src/dom/focus.ts +34 -0
- package/src/dom/index.ts +3 -0
- package/src/dom/nodes.ts +204 -0
- package/src/dom/props.ts +818 -0
- package/src/env.ts +3 -0
- package/src/globalContext.ts +7 -24
- package/src/globals.ts +25 -2
- package/src/hmr.ts +32 -5
- package/src/hooks/index.ts +1 -0
- package/src/hooks/onBeforeMount.ts +9 -3
- package/src/hooks/onCleanup.ts +10 -4
- package/src/hooks/onMount.ts +10 -4
- package/src/hooks/setup.ts +70 -0
- package/src/hooks/utils.ts +14 -19
- package/src/index.ts +4 -2
- package/src/reconciler.ts +3 -3
- package/src/router/head.ts +2 -2
- package/src/router/pageConfig.ts +2 -2
- package/src/scheduler.ts +79 -64
- package/src/signals/base.ts +3 -3
- package/src/signals/effect.ts +5 -7
- package/src/signals/tracking.ts +3 -2
- package/src/statefulPromise.ts +2 -2
- package/src/types.dom.ts +2 -2
- package/src/types.ts +7 -1
- package/src/utils/format.ts +3 -1
- package/src/utils/vdom.ts +2 -2
- package/src/viewTransitions.ts +2 -1
- package/dist/dom.d.ts +0 -10
- package/dist/dom.d.ts.map +0 -1
- package/dist/dom.js +0 -601
- package/dist/dom.js.map +0 -1
- package/src/dom.ts +0 -775
|
@@ -0,0 +1,675 @@
|
|
|
1
|
+
import { propToHtmlAttr, getVNodeApp, setRef, registerVNodeCleanup, } from "../utils/index.js";
|
|
2
|
+
import { Signal } from "../signals/base.js";
|
|
3
|
+
import { unwrap } from "../signals/utils.js";
|
|
4
|
+
import { booleanAttributes, EVENT_PREFIX_REGEX } from "../constants.js";
|
|
5
|
+
import { __DEV__, isBrowser } from "../env.js";
|
|
6
|
+
import { wrapFocusEventHandler } from "./focus.js";
|
|
7
|
+
export { updateDomProps, unmountDomProps, setSignalProp };
|
|
8
|
+
const eventListenerObjects = new WeakMap();
|
|
9
|
+
const skippedProps = new Set(["children", "ref", "key"]);
|
|
10
|
+
// Reusable buckets for maybeOrderPropKeys — avoids per-call allocation.
|
|
11
|
+
// Safe because JS is single-threaded.
|
|
12
|
+
const _buckets = [[], [], [], [], [], [], []];
|
|
13
|
+
const bindAttrToEventMap = {
|
|
14
|
+
value: "input",
|
|
15
|
+
checked: "change",
|
|
16
|
+
open: "toggle",
|
|
17
|
+
volume: "volumechange",
|
|
18
|
+
playbackRate: "ratechange",
|
|
19
|
+
currentTime: "timeupdate",
|
|
20
|
+
};
|
|
21
|
+
const numericValueInputTypes = new Set(["progress", "meter", "number", "range"]);
|
|
22
|
+
// Reuse a Set for explicit-value element tag lookup (faster than indexOf on array)
|
|
23
|
+
const explicitValueElementTags = new Set(["INPUT", "TEXTAREA"]);
|
|
24
|
+
function updateDomProps(vNode) {
|
|
25
|
+
const { dom, prev, props, cleanups } = vNode;
|
|
26
|
+
const prevProps = prev?.props ?? {};
|
|
27
|
+
const nextProps = props ?? {};
|
|
28
|
+
if (isTextNode(dom)) {
|
|
29
|
+
const nextVal = nextProps.nodeValue;
|
|
30
|
+
if (!Signal.isSignal(nextVal) && dom.nodeValue !== nextVal) {
|
|
31
|
+
dom.nodeValue = nextVal;
|
|
32
|
+
}
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
// Fast-path for first commit: no previous props, so we can skip diffing and
|
|
36
|
+
// just apply all props (with ordering) directly.
|
|
37
|
+
if (!prev) {
|
|
38
|
+
mountDomProps(vNode, dom, nextProps, cleanups);
|
|
39
|
+
const nextRef = nextProps.ref;
|
|
40
|
+
if (nextRef)
|
|
41
|
+
setRef(nextRef, dom);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
// Use a Set to deduplicate keys that appear in both prevProps and nextProps.
|
|
45
|
+
const execKeySet = new Set();
|
|
46
|
+
let styleKeyToSignal;
|
|
47
|
+
let events;
|
|
48
|
+
// Handle prevProps keys that may have been removed or changed.
|
|
49
|
+
for (const key in prevProps) {
|
|
50
|
+
const prevVal = prevProps[key];
|
|
51
|
+
const nextVal = nextProps[key];
|
|
52
|
+
if (prevVal === nextVal)
|
|
53
|
+
continue; // unchanged
|
|
54
|
+
// Event removal
|
|
55
|
+
if (key.length >= 2 &&
|
|
56
|
+
key.charCodeAt(0) === 111 &&
|
|
57
|
+
key.charCodeAt(1) === 110) {
|
|
58
|
+
// "on"
|
|
59
|
+
if (!nextVal) {
|
|
60
|
+
events ?? (events = eventListenerObjects.get(vNode) ?? {});
|
|
61
|
+
eventListenerObjects.set(vNode, events);
|
|
62
|
+
const evtName = key.replace(EVENT_PREFIX_REGEX, "");
|
|
63
|
+
const evtObj = events[evtName];
|
|
64
|
+
if (evtObj) {
|
|
65
|
+
dom.removeEventListener(evtName, evtObj);
|
|
66
|
+
delete events[evtName];
|
|
67
|
+
}
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
// Cleanup previous signals
|
|
72
|
+
if (Signal.isSignal(prevVal) && cleanups?.[key]) {
|
|
73
|
+
cleanups[key]();
|
|
74
|
+
delete cleanups[key];
|
|
75
|
+
}
|
|
76
|
+
execKeySet.add(key);
|
|
77
|
+
}
|
|
78
|
+
// Handle nextProps keys that are new or changed.
|
|
79
|
+
for (const key in nextProps) {
|
|
80
|
+
if (!(key in prevProps) || prevProps[key] !== nextProps[key]) {
|
|
81
|
+
execKeySet.add(key);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
const execKeys = Array.from(execKeySet);
|
|
85
|
+
// Analyze for constraint/value hazards only if multiple keys changed.
|
|
86
|
+
if (isElementNode(dom) && execKeys.length > 1) {
|
|
87
|
+
const changedSet = execKeySet; // reuse the same set
|
|
88
|
+
let seenConstraint = false;
|
|
89
|
+
let constraintChanged = false;
|
|
90
|
+
const valueLikeKeys = [];
|
|
91
|
+
let hasEvents = false;
|
|
92
|
+
let hasNonEvent = false;
|
|
93
|
+
for (const key in nextProps) {
|
|
94
|
+
const isEvent = key.length >= 2 &&
|
|
95
|
+
key.charCodeAt(0) === 111 &&
|
|
96
|
+
key.charCodeAt(1) === 110; // "on"
|
|
97
|
+
if (isEvent)
|
|
98
|
+
hasEvents = true;
|
|
99
|
+
else
|
|
100
|
+
hasNonEvent = true;
|
|
101
|
+
const baseKey = !isEvent && key.startsWith("bind:") ? key.slice(5) : key;
|
|
102
|
+
const priority = getBasePropPriority(baseKey, isEvent);
|
|
103
|
+
if (priority === 1) {
|
|
104
|
+
seenConstraint = true;
|
|
105
|
+
if (changedSet.has(key))
|
|
106
|
+
constraintChanged = true;
|
|
107
|
+
}
|
|
108
|
+
else if (priority === 5) {
|
|
109
|
+
valueLikeKeys.push(key);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
if (seenConstraint && constraintChanged) {
|
|
113
|
+
for (const vk of valueLikeKeys) {
|
|
114
|
+
if (!changedSet.has(vk) && vk in nextProps) {
|
|
115
|
+
execKeys.push(vk);
|
|
116
|
+
changedSet.add(vk);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
const needsOrdering = (seenConstraint && constraintChanged) || (hasEvents && hasNonEvent);
|
|
121
|
+
if (needsOrdering && execKeys.length > 1)
|
|
122
|
+
maybeOrderPropKeys(execKeys);
|
|
123
|
+
}
|
|
124
|
+
// Apply updates
|
|
125
|
+
for (let i = 0; i < execKeys.length; i++) {
|
|
126
|
+
const key = execKeys[i];
|
|
127
|
+
const prevVal = prevProps[key];
|
|
128
|
+
const nextVal = nextProps[key];
|
|
129
|
+
// Skip structural props early
|
|
130
|
+
if (skippedProps.has(key))
|
|
131
|
+
continue;
|
|
132
|
+
// Events — charcode check is faster than startsWith for hot path
|
|
133
|
+
if (key.length >= 2 &&
|
|
134
|
+
key.charCodeAt(0) === 111 &&
|
|
135
|
+
key.charCodeAt(1) === 110) {
|
|
136
|
+
// "on"
|
|
137
|
+
events ?? (events = eventListenerObjects.get(vNode) ?? {});
|
|
138
|
+
eventListenerObjects.set(vNode, events);
|
|
139
|
+
const evtName = key.replace(EVENT_PREFIX_REGEX, "");
|
|
140
|
+
const evtObj = events[evtName];
|
|
141
|
+
if (!nextVal) {
|
|
142
|
+
if (evtObj) {
|
|
143
|
+
dom.removeEventListener(evtName, evtObj);
|
|
144
|
+
delete events[evtName];
|
|
145
|
+
}
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
let handleEvent = nextVal.bind(void 0);
|
|
149
|
+
if (evtName === "focus" || evtName === "blur")
|
|
150
|
+
handleEvent = wrapFocusEventHandler(handleEvent);
|
|
151
|
+
if (evtObj) {
|
|
152
|
+
evtObj.handleEvent = handleEvent;
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
dom.addEventListener(evtName, (events[evtName] = { handleEvent }));
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
// Signal
|
|
159
|
+
if (Signal.isSignal(nextVal)) {
|
|
160
|
+
setSignalProp(vNode, dom, key, nextVal, prevVal);
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
// Style
|
|
164
|
+
if (key === "style" && typeof nextVal === "object" && nextVal !== null) {
|
|
165
|
+
if (cleanups?.style) {
|
|
166
|
+
cleanups.style();
|
|
167
|
+
delete cleanups.style;
|
|
168
|
+
}
|
|
169
|
+
if (!styleKeyToSignal) {
|
|
170
|
+
styleKeyToSignal = new Map();
|
|
171
|
+
}
|
|
172
|
+
setStyleProp(dom, nextVal, prevVal, true);
|
|
173
|
+
if (styleKeyToSignal.size > 0) {
|
|
174
|
+
const unsubs = [];
|
|
175
|
+
for (const [k, sig] of styleKeyToSignal.entries()) {
|
|
176
|
+
unsubs.push(sig.subscribe(k.startsWith("--")
|
|
177
|
+
? (v) => setCustomCSSStyleDecValue(dom, k, v)
|
|
178
|
+
: (v) => setCSSStyleDecValue(dom, k, v)));
|
|
179
|
+
}
|
|
180
|
+
styleKeyToSignal.clear();
|
|
181
|
+
registerVNodeCleanup(vNode, "style", () => unsubs.forEach((u) => u()));
|
|
182
|
+
}
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
setProp(dom, key, nextVal, prevVal);
|
|
186
|
+
}
|
|
187
|
+
// Ref
|
|
188
|
+
const prevRef = prevProps.ref;
|
|
189
|
+
const nextRef = nextProps.ref;
|
|
190
|
+
if (prevRef !== nextRef) {
|
|
191
|
+
if (prevRef)
|
|
192
|
+
setRef(prevRef, null);
|
|
193
|
+
if (nextRef)
|
|
194
|
+
setRef(nextRef, dom);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
function unmountDomProps(vNode, dom, prevProps, cleanups) {
|
|
198
|
+
let events;
|
|
199
|
+
for (const key in prevProps) {
|
|
200
|
+
const prevVal = prevProps[key];
|
|
201
|
+
// Skip structural props early
|
|
202
|
+
if (skippedProps.has(key))
|
|
203
|
+
continue;
|
|
204
|
+
// Events
|
|
205
|
+
if (key.length >= 2 &&
|
|
206
|
+
key.charCodeAt(0) === 111 &&
|
|
207
|
+
key.charCodeAt(1) === 110) {
|
|
208
|
+
// "on"
|
|
209
|
+
events ?? (events = eventListenerObjects.get(vNode) ?? {});
|
|
210
|
+
eventListenerObjects.set(vNode, events);
|
|
211
|
+
const evtName = key.replace(EVENT_PREFIX_REGEX, "");
|
|
212
|
+
const evtObj = events[evtName];
|
|
213
|
+
if (evtObj) {
|
|
214
|
+
dom.removeEventListener(evtName, evtObj);
|
|
215
|
+
delete events[evtName];
|
|
216
|
+
}
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
// Signals (including bind: props) – invoke their registered cleanups.
|
|
220
|
+
if (Signal.isSignal(prevVal) && cleanups?.[key]) {
|
|
221
|
+
cleanups[key]();
|
|
222
|
+
delete cleanups[key];
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
// Style object: clear any style listeners and remove the style attribute.
|
|
226
|
+
if (key === "style") {
|
|
227
|
+
if (cleanups?.style) {
|
|
228
|
+
cleanups.style();
|
|
229
|
+
delete cleanups.style;
|
|
230
|
+
}
|
|
231
|
+
if (isElementNode(dom)) {
|
|
232
|
+
setStyleProp(dom, undefined, prevVal);
|
|
233
|
+
}
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
// Other props: remove/reset attributes based on previous value.
|
|
237
|
+
if (isElementNode(dom)) {
|
|
238
|
+
setProp(dom, key, undefined, prevVal);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
// Clear previous ref
|
|
242
|
+
const prevRef = prevProps.ref;
|
|
243
|
+
if (prevRef)
|
|
244
|
+
setRef(prevRef, null);
|
|
245
|
+
}
|
|
246
|
+
const styleKeyToSignal = new Map();
|
|
247
|
+
function mountDomProps(vNode, dom, props, cleanups) {
|
|
248
|
+
const keys = Object.keys(props);
|
|
249
|
+
if (isElementNode(dom) && keys.length > 1) {
|
|
250
|
+
maybeOrderPropKeys(keys);
|
|
251
|
+
}
|
|
252
|
+
let events;
|
|
253
|
+
for (let i = 0; i < keys.length; i++) {
|
|
254
|
+
const key = keys[i];
|
|
255
|
+
const value = props[key];
|
|
256
|
+
if (skippedProps.has(key))
|
|
257
|
+
continue;
|
|
258
|
+
// Events
|
|
259
|
+
if (key.length >= 2 &&
|
|
260
|
+
key.charCodeAt(0) === 111 &&
|
|
261
|
+
key.charCodeAt(1) === 110) {
|
|
262
|
+
// "on"
|
|
263
|
+
if (!value)
|
|
264
|
+
continue;
|
|
265
|
+
events ?? (events = eventListenerObjects.get(vNode) ?? {});
|
|
266
|
+
eventListenerObjects.set(vNode, events);
|
|
267
|
+
const evtName = key.replace(EVENT_PREFIX_REGEX, "");
|
|
268
|
+
const evtObj = events[evtName];
|
|
269
|
+
let handleEvent = value.bind(void 0);
|
|
270
|
+
if (evtName === "focus" || evtName === "blur") {
|
|
271
|
+
handleEvent = wrapFocusEventHandler(handleEvent);
|
|
272
|
+
}
|
|
273
|
+
if (evtObj) {
|
|
274
|
+
evtObj.handleEvent = handleEvent;
|
|
275
|
+
}
|
|
276
|
+
else {
|
|
277
|
+
dom.addEventListener(evtName, (events[evtName] = { handleEvent }));
|
|
278
|
+
}
|
|
279
|
+
continue;
|
|
280
|
+
}
|
|
281
|
+
// Signals
|
|
282
|
+
if (Signal.isSignal(value)) {
|
|
283
|
+
setSignalProp(vNode, dom, key, value, undefined);
|
|
284
|
+
continue;
|
|
285
|
+
}
|
|
286
|
+
// Style
|
|
287
|
+
if (key === "style" && typeof value === "object" && value !== null) {
|
|
288
|
+
setStyleProp(dom, value, undefined, true);
|
|
289
|
+
if (styleKeyToSignal.size > 0) {
|
|
290
|
+
cleanups ?? (cleanups = {});
|
|
291
|
+
for (const [k, sig] of styleKeyToSignal.entries()) {
|
|
292
|
+
const cleanupKey = `style-${k}`;
|
|
293
|
+
cleanups[cleanupKey]?.();
|
|
294
|
+
cleanups[cleanupKey] = sig.subscribe(k.startsWith("--")
|
|
295
|
+
? (v) => setCustomCSSStyleDecValue(dom, k, v)
|
|
296
|
+
: (v) => setCSSStyleDecValue(dom, k, v));
|
|
297
|
+
}
|
|
298
|
+
styleKeyToSignal.clear();
|
|
299
|
+
}
|
|
300
|
+
continue;
|
|
301
|
+
}
|
|
302
|
+
setProp(dom, key, value, undefined);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
function maybeOrderPropKeys(keys) {
|
|
306
|
+
if (keys.length <= 1)
|
|
307
|
+
return;
|
|
308
|
+
// Clear reusable buckets
|
|
309
|
+
for (let b = 0; b < 7; b++)
|
|
310
|
+
_buckets[b].length = 0;
|
|
311
|
+
for (let i = 0; i < keys.length; i++) {
|
|
312
|
+
const key = keys[i];
|
|
313
|
+
const isEvent = key.length >= 2 && key.charCodeAt(0) === 111 && key.charCodeAt(1) === 110; // "on"
|
|
314
|
+
let baseKey = key;
|
|
315
|
+
if (!isEvent && key.length > 5 && key.charCodeAt(4) === 58) {
|
|
316
|
+
// "bind:"
|
|
317
|
+
baseKey = key.slice(5);
|
|
318
|
+
}
|
|
319
|
+
const priority = getBasePropPriority(baseKey, isEvent);
|
|
320
|
+
let bucketIdx;
|
|
321
|
+
if (priority <= 0)
|
|
322
|
+
bucketIdx = 0;
|
|
323
|
+
else if (priority === 1)
|
|
324
|
+
bucketIdx = 1;
|
|
325
|
+
else if (priority === 2)
|
|
326
|
+
bucketIdx = 2;
|
|
327
|
+
else if (priority === 3)
|
|
328
|
+
bucketIdx = 3;
|
|
329
|
+
else if (priority === 5)
|
|
330
|
+
bucketIdx = 4;
|
|
331
|
+
else if (priority >= 9)
|
|
332
|
+
bucketIdx = 6;
|
|
333
|
+
else
|
|
334
|
+
bucketIdx = 5;
|
|
335
|
+
_buckets[bucketIdx].push(key);
|
|
336
|
+
}
|
|
337
|
+
let outIdx = 0;
|
|
338
|
+
for (let b = 0; b < 7; b++) {
|
|
339
|
+
const bucket = _buckets[b];
|
|
340
|
+
for (let i = 0; i < bucket.length; i++) {
|
|
341
|
+
keys[outIdx++] = bucket[i];
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
function getBasePropPriority(baseKey, isEvent) {
|
|
346
|
+
if (isEvent)
|
|
347
|
+
return 9;
|
|
348
|
+
// Use first char to narrow comparisons
|
|
349
|
+
switch (baseKey) {
|
|
350
|
+
case "innerHTML":
|
|
351
|
+
case "type":
|
|
352
|
+
case "muted":
|
|
353
|
+
case "autoplay":
|
|
354
|
+
case "loop":
|
|
355
|
+
return 0;
|
|
356
|
+
case "min":
|
|
357
|
+
case "max":
|
|
358
|
+
case "step":
|
|
359
|
+
case "pattern":
|
|
360
|
+
case "accept":
|
|
361
|
+
case "multiple":
|
|
362
|
+
case "preload":
|
|
363
|
+
case "minLength":
|
|
364
|
+
case "maxLength":
|
|
365
|
+
case "crossOrigin":
|
|
366
|
+
case "decoding":
|
|
367
|
+
case "loading":
|
|
368
|
+
case "referrerPolicy":
|
|
369
|
+
return 1;
|
|
370
|
+
case "style":
|
|
371
|
+
case "className":
|
|
372
|
+
return 3;
|
|
373
|
+
case "value":
|
|
374
|
+
case "checked":
|
|
375
|
+
case "selected":
|
|
376
|
+
case "open":
|
|
377
|
+
case "src":
|
|
378
|
+
return 5;
|
|
379
|
+
default:
|
|
380
|
+
return 4;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
function setSelectElementValue(dom, value) {
|
|
384
|
+
if (!dom.multiple || value === undefined || value === null || value === "") {
|
|
385
|
+
dom.value = value;
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
const options = dom.options;
|
|
389
|
+
const len = options.length;
|
|
390
|
+
for (let i = 0; i < len; i++) {
|
|
391
|
+
const option = options[i];
|
|
392
|
+
option.selected = value.indexOf(option.value) > -1;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
function setSignalProp(vNode, dom, key, signal, prevValue) {
|
|
396
|
+
const colonIdx = key.indexOf(":");
|
|
397
|
+
const modifier = colonIdx === -1 ? key : key.slice(0, colonIdx);
|
|
398
|
+
const attr = colonIdx === -1 ? undefined : key.slice(colonIdx + 1);
|
|
399
|
+
if (modifier === "bind") {
|
|
400
|
+
const evtName = bindAttrToEventMap[attr];
|
|
401
|
+
if (!evtName) {
|
|
402
|
+
if (__DEV__) {
|
|
403
|
+
console.error(`[kiru]: ${attr} is not a valid element binding attribute.`);
|
|
404
|
+
}
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
const value = signal.peek();
|
|
408
|
+
const cleanup = bindElementProp(vNode, dom, attr, evtName, signal, value);
|
|
409
|
+
registerVNodeCleanup(vNode, key, cleanup);
|
|
410
|
+
}
|
|
411
|
+
else {
|
|
412
|
+
const unsub = signal.subscribe((value, prev) => {
|
|
413
|
+
if (value === prev)
|
|
414
|
+
return;
|
|
415
|
+
setProp(dom, key, value, prev);
|
|
416
|
+
if (__DEV__) {
|
|
417
|
+
emitSignalAttrUpdate(vNode);
|
|
418
|
+
}
|
|
419
|
+
});
|
|
420
|
+
registerVNodeCleanup(vNode, key, unsub);
|
|
421
|
+
}
|
|
422
|
+
const value = signal.peek();
|
|
423
|
+
const prev = unwrap(prevValue);
|
|
424
|
+
if (modifier !== "bind" && value !== prev) {
|
|
425
|
+
setProp(dom, attr ?? modifier, value, prev);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
function bindElementProp(vNode, dom, attr, evtName, signal, initialValue) {
|
|
429
|
+
const writeToSignal = (val) => {
|
|
430
|
+
signal.sneak(val);
|
|
431
|
+
signal.notify((sub) => sub !== updateFromSignal);
|
|
432
|
+
};
|
|
433
|
+
const writeToElement = dom.nodeName === "SELECT" && attr === "value"
|
|
434
|
+
? (value) => setSelectElementValue(dom, value)
|
|
435
|
+
: (value) => (dom[attr] = value);
|
|
436
|
+
const updateFromSignal = (value) => {
|
|
437
|
+
writeToElement(value);
|
|
438
|
+
if (__DEV__) {
|
|
439
|
+
emitSignalAttrUpdate(vNode);
|
|
440
|
+
}
|
|
441
|
+
};
|
|
442
|
+
let readValue;
|
|
443
|
+
let evtHandler;
|
|
444
|
+
if (attr === "value") {
|
|
445
|
+
readValue = createElementValueReader(dom);
|
|
446
|
+
evtHandler = () => writeToSignal(readValue());
|
|
447
|
+
}
|
|
448
|
+
else {
|
|
449
|
+
evtHandler = () => {
|
|
450
|
+
const val = dom[attr];
|
|
451
|
+
if (attr === "currentTime" && signal.peek() === val)
|
|
452
|
+
return;
|
|
453
|
+
writeToSignal(val);
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
if (initialValue !== undefined) {
|
|
457
|
+
updateFromSignal(initialValue);
|
|
458
|
+
}
|
|
459
|
+
// After binding is established, always reconcile the signal with the
|
|
460
|
+
// element's current value. This ensures that any browser coercion
|
|
461
|
+
// (clamping, defaulting, etc.) is reflected back into the signal,
|
|
462
|
+
// even when the initial signal value was undefined.
|
|
463
|
+
let domVal;
|
|
464
|
+
if (attr === "value" && readValue) {
|
|
465
|
+
domVal = readValue();
|
|
466
|
+
}
|
|
467
|
+
else {
|
|
468
|
+
domVal = dom[attr];
|
|
469
|
+
}
|
|
470
|
+
const current = signal.peek();
|
|
471
|
+
if (domVal !== current) {
|
|
472
|
+
writeToSignal(domVal);
|
|
473
|
+
}
|
|
474
|
+
dom.addEventListener(evtName, evtHandler);
|
|
475
|
+
const unsub = signal.subscribe(updateFromSignal);
|
|
476
|
+
return () => {
|
|
477
|
+
dom.removeEventListener(evtName, evtHandler);
|
|
478
|
+
unsub();
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
function setProp(element, key, value, prev) {
|
|
482
|
+
switch (key) {
|
|
483
|
+
case "style":
|
|
484
|
+
return setStyleProp(element, value, prev);
|
|
485
|
+
case "className":
|
|
486
|
+
return setClassName(element, value);
|
|
487
|
+
case "innerHTML":
|
|
488
|
+
return setInnerHTML(element, value);
|
|
489
|
+
case "muted":
|
|
490
|
+
;
|
|
491
|
+
element.muted = Boolean(value);
|
|
492
|
+
return;
|
|
493
|
+
case "value":
|
|
494
|
+
if (element.nodeName === "SELECT") {
|
|
495
|
+
return setSelectElementValue(element, value);
|
|
496
|
+
}
|
|
497
|
+
const strVal = value === undefined || value === null ? "" : String(value);
|
|
498
|
+
if (explicitValueElementTags.has(element.nodeName)) {
|
|
499
|
+
;
|
|
500
|
+
element.value = strVal;
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
element.setAttribute("value", strVal);
|
|
504
|
+
return;
|
|
505
|
+
case "checked":
|
|
506
|
+
if (element.nodeName === "INPUT") {
|
|
507
|
+
;
|
|
508
|
+
element.checked = Boolean(value);
|
|
509
|
+
return;
|
|
510
|
+
}
|
|
511
|
+
element.setAttribute("checked", String(value));
|
|
512
|
+
return;
|
|
513
|
+
default:
|
|
514
|
+
return setDomAttribute(element, propToHtmlAttr(key), value);
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
function setDomAttribute(element, key, value) {
|
|
518
|
+
const isBoolAttr = booleanAttributes.has(key);
|
|
519
|
+
if (handleAttributeRemoval(element, key, value, isBoolAttr))
|
|
520
|
+
return;
|
|
521
|
+
element.setAttribute(key, isBoolAttr && typeof value === "boolean" ? "" : String(value));
|
|
522
|
+
}
|
|
523
|
+
function setInnerHTML(element, value) {
|
|
524
|
+
if (value === null || value === undefined || typeof value === "boolean") {
|
|
525
|
+
element.innerHTML = "";
|
|
526
|
+
return;
|
|
527
|
+
}
|
|
528
|
+
element.innerHTML = String(value);
|
|
529
|
+
}
|
|
530
|
+
function setClassName(element, value) {
|
|
531
|
+
const val = unwrap(value);
|
|
532
|
+
if (!val) {
|
|
533
|
+
return element.removeAttribute("class");
|
|
534
|
+
}
|
|
535
|
+
element.setAttribute("class", val);
|
|
536
|
+
}
|
|
537
|
+
function setCustomCSSStyleDecValue(element, key, value) {
|
|
538
|
+
if (value === undefined || value === null) {
|
|
539
|
+
element.style.removeProperty(key);
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
element.style.setProperty(key, String(value));
|
|
543
|
+
}
|
|
544
|
+
function setCSSStyleDecValue(element, key, value) {
|
|
545
|
+
element.style[key] =
|
|
546
|
+
value !== undefined && value !== null ? String(value) : "";
|
|
547
|
+
}
|
|
548
|
+
function setStyleProp(element, value, prev, trackSignals = false) {
|
|
549
|
+
if (handleAttributeRemoval(element, "style", value))
|
|
550
|
+
return;
|
|
551
|
+
const raw = unwrap(value);
|
|
552
|
+
if (raw === null || raw === undefined) {
|
|
553
|
+
element.removeAttribute("style");
|
|
554
|
+
return;
|
|
555
|
+
}
|
|
556
|
+
if (typeof raw === "string") {
|
|
557
|
+
element.setAttribute("style", raw);
|
|
558
|
+
return;
|
|
559
|
+
}
|
|
560
|
+
let prevStyle = {};
|
|
561
|
+
const rawPrev = unwrap(prev);
|
|
562
|
+
if (typeof rawPrev === "string") {
|
|
563
|
+
element.setAttribute("style", "");
|
|
564
|
+
}
|
|
565
|
+
else if (typeof rawPrev === "object" && rawPrev !== null) {
|
|
566
|
+
prevStyle = rawPrev;
|
|
567
|
+
}
|
|
568
|
+
const nextStyle = raw;
|
|
569
|
+
const prevKeys = Object.keys(prevStyle);
|
|
570
|
+
const nextKeys = Object.keys(nextStyle);
|
|
571
|
+
// Avoid Set allocation for the common case where prevStyle is empty
|
|
572
|
+
if (prevKeys.length === 0) {
|
|
573
|
+
for (let i = 0; i < nextKeys.length; i++) {
|
|
574
|
+
const k = nextKeys[i];
|
|
575
|
+
const rawNext = nextStyle[k];
|
|
576
|
+
const nextVal = unwrap(rawNext);
|
|
577
|
+
if (trackSignals && Signal.isSignal(rawNext)) {
|
|
578
|
+
styleKeyToSignal.set(k, rawNext);
|
|
579
|
+
}
|
|
580
|
+
if (k.startsWith("--")) {
|
|
581
|
+
setCustomCSSStyleDecValue(element, k, nextVal);
|
|
582
|
+
}
|
|
583
|
+
else {
|
|
584
|
+
setCSSStyleDecValue(element, k, nextVal);
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
return;
|
|
588
|
+
}
|
|
589
|
+
// Full merge path: iterate prevKeys for removals, nextKeys for additions/changes
|
|
590
|
+
const nextStyleKeys = new Set(nextKeys);
|
|
591
|
+
for (let i = 0; i < prevKeys.length; i++) {
|
|
592
|
+
const k = prevKeys[i];
|
|
593
|
+
if (!nextStyleKeys.has(k)) {
|
|
594
|
+
// Property was removed
|
|
595
|
+
if (k.startsWith("--")) {
|
|
596
|
+
setCustomCSSStyleDecValue(element, k, undefined);
|
|
597
|
+
}
|
|
598
|
+
else {
|
|
599
|
+
setCSSStyleDecValue(element, k, undefined);
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
for (let i = 0; i < nextKeys.length; i++) {
|
|
604
|
+
const k = nextKeys[i];
|
|
605
|
+
const rawNext = nextStyle[k];
|
|
606
|
+
const prevVal = unwrap(prevStyle[k]);
|
|
607
|
+
const nextVal = unwrap(rawNext);
|
|
608
|
+
if (trackSignals && Signal.isSignal(rawNext)) {
|
|
609
|
+
styleKeyToSignal.set(k, rawNext);
|
|
610
|
+
}
|
|
611
|
+
if (prevVal === nextVal)
|
|
612
|
+
continue;
|
|
613
|
+
if (k.startsWith("--")) {
|
|
614
|
+
setCustomCSSStyleDecValue(element, k, nextVal);
|
|
615
|
+
}
|
|
616
|
+
else {
|
|
617
|
+
setCSSStyleDecValue(element, k, nextVal);
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
function handleAttributeRemoval(element, key, value, isBoolAttr = false) {
|
|
622
|
+
if (value === null) {
|
|
623
|
+
element.removeAttribute(key);
|
|
624
|
+
return true;
|
|
625
|
+
}
|
|
626
|
+
switch (typeof value) {
|
|
627
|
+
case "undefined":
|
|
628
|
+
case "function":
|
|
629
|
+
case "symbol": {
|
|
630
|
+
element.removeAttribute(key);
|
|
631
|
+
return true;
|
|
632
|
+
}
|
|
633
|
+
case "boolean": {
|
|
634
|
+
if (isBoolAttr && !value) {
|
|
635
|
+
element.removeAttribute(key);
|
|
636
|
+
return true;
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
return false;
|
|
641
|
+
}
|
|
642
|
+
function createElementValueReader(dom) {
|
|
643
|
+
if (dom.nodeName === "INPUT") {
|
|
644
|
+
return createInputValueReader(dom);
|
|
645
|
+
}
|
|
646
|
+
if (dom.nodeName === "SELECT") {
|
|
647
|
+
return () => getSelectElementValue(dom);
|
|
648
|
+
}
|
|
649
|
+
return () => dom.value;
|
|
650
|
+
}
|
|
651
|
+
function createInputValueReader(dom) {
|
|
652
|
+
const t = dom.type;
|
|
653
|
+
if (numericValueInputTypes.has(t)) {
|
|
654
|
+
return () => dom.valueAsNumber;
|
|
655
|
+
}
|
|
656
|
+
return () => dom.value;
|
|
657
|
+
}
|
|
658
|
+
function getSelectElementValue(dom) {
|
|
659
|
+
if (dom.multiple) {
|
|
660
|
+
return Array.from(dom.selectedOptions).map((option) => option.value);
|
|
661
|
+
}
|
|
662
|
+
return dom.value;
|
|
663
|
+
}
|
|
664
|
+
function emitSignalAttrUpdate(vNode) {
|
|
665
|
+
if (!isBrowser)
|
|
666
|
+
return;
|
|
667
|
+
window.__kiru?.profilingContext?.emit("signalAttrUpdate", getVNodeApp(vNode));
|
|
668
|
+
}
|
|
669
|
+
function isElementNode(dom) {
|
|
670
|
+
return dom.nodeType === 1;
|
|
671
|
+
}
|
|
672
|
+
function isTextNode(dom) {
|
|
673
|
+
return dom.nodeType === 3;
|
|
674
|
+
}
|
|
675
|
+
//# sourceMappingURL=props.js.map
|