kiru 1.0.1 → 1.1.1

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.
Files changed (145) hide show
  1. package/dist/appHandle.js +2 -2
  2. package/dist/appHandle.js.map +1 -1
  3. package/dist/components/lazy.d.ts.map +1 -1
  4. package/dist/components/lazy.js +2 -2
  5. package/dist/components/lazy.js.map +1 -1
  6. package/dist/components/transition.js +1 -5
  7. package/dist/components/transition.js.map +1 -1
  8. package/dist/devtools.d.ts.map +1 -1
  9. package/dist/devtools.js +6 -2
  10. package/dist/devtools.js.map +1 -1
  11. package/dist/dom/commit.d.ts +5 -0
  12. package/dist/dom/commit.d.ts.map +1 -0
  13. package/dist/dom/commit.js +94 -0
  14. package/dist/dom/commit.js.map +1 -0
  15. package/dist/dom/focus.d.ts +4 -0
  16. package/dist/dom/focus.d.ts.map +1 -0
  17. package/dist/dom/focus.js +32 -0
  18. package/dist/dom/focus.js.map +1 -0
  19. package/dist/dom/index.d.ts +4 -0
  20. package/dist/dom/index.d.ts.map +1 -0
  21. package/dist/dom/index.js +4 -0
  22. package/dist/dom/index.js.map +1 -0
  23. package/dist/dom/nodes.d.ts +12 -0
  24. package/dist/dom/nodes.d.ts.map +1 -0
  25. package/dist/dom/nodes.js +166 -0
  26. package/dist/dom/nodes.js.map +1 -0
  27. package/dist/dom/props.d.ts +8 -0
  28. package/dist/dom/props.d.ts.map +1 -0
  29. package/dist/dom/props.js +675 -0
  30. package/dist/dom/props.js.map +1 -0
  31. package/dist/env.d.ts +2 -0
  32. package/dist/env.d.ts.map +1 -1
  33. package/dist/env.js +2 -0
  34. package/dist/env.js.map +1 -1
  35. package/dist/globalContext.d.ts +3 -8
  36. package/dist/globalContext.d.ts.map +1 -1
  37. package/dist/globalContext.js +4 -16
  38. package/dist/globalContext.js.map +1 -1
  39. package/dist/globals.d.ts +21 -1
  40. package/dist/globals.d.ts.map +1 -1
  41. package/dist/globals.js +22 -2
  42. package/dist/globals.js.map +1 -1
  43. package/dist/hmr.d.ts +17 -2
  44. package/dist/hmr.d.ts.map +1 -1
  45. package/dist/hmr.js +31 -5
  46. package/dist/hmr.js.map +1 -1
  47. package/dist/hooks/index.d.ts +1 -0
  48. package/dist/hooks/index.d.ts.map +1 -1
  49. package/dist/hooks/index.js +1 -0
  50. package/dist/hooks/index.js.map +1 -1
  51. package/dist/hooks/onBeforeMount.d.ts +1 -1
  52. package/dist/hooks/onBeforeMount.d.ts.map +1 -1
  53. package/dist/hooks/onBeforeMount.js +10 -3
  54. package/dist/hooks/onBeforeMount.js.map +1 -1
  55. package/dist/hooks/onCleanup.d.ts +1 -1
  56. package/dist/hooks/onCleanup.d.ts.map +1 -1
  57. package/dist/hooks/onCleanup.js +7 -4
  58. package/dist/hooks/onCleanup.js.map +1 -1
  59. package/dist/hooks/onMount.d.ts +2 -2
  60. package/dist/hooks/onMount.d.ts.map +1 -1
  61. package/dist/hooks/onMount.js +11 -4
  62. package/dist/hooks/onMount.js.map +1 -1
  63. package/dist/hooks/setup.d.ts +13 -0
  64. package/dist/hooks/setup.d.ts.map +1 -0
  65. package/dist/hooks/setup.js +54 -0
  66. package/dist/hooks/setup.js.map +1 -0
  67. package/dist/hooks/utils.d.ts +2 -3
  68. package/dist/hooks/utils.d.ts.map +1 -1
  69. package/dist/hooks/utils.js +9 -14
  70. package/dist/hooks/utils.js.map +1 -1
  71. package/dist/index.d.ts +1 -0
  72. package/dist/index.d.ts.map +1 -1
  73. package/dist/index.js +4 -3
  74. package/dist/index.js.map +1 -1
  75. package/dist/reconciler.js +3 -3
  76. package/dist/reconciler.js.map +1 -1
  77. package/dist/router/head.js +2 -2
  78. package/dist/router/head.js.map +1 -1
  79. package/dist/router/pageConfig.js +2 -2
  80. package/dist/router/pageConfig.js.map +1 -1
  81. package/dist/scheduler.js +62 -57
  82. package/dist/scheduler.js.map +1 -1
  83. package/dist/signals/base.js +3 -3
  84. package/dist/signals/base.js.map +1 -1
  85. package/dist/signals/effect.d.ts.map +1 -1
  86. package/dist/signals/effect.js +6 -6
  87. package/dist/signals/effect.js.map +1 -1
  88. package/dist/signals/tracking.d.ts +3 -2
  89. package/dist/signals/tracking.d.ts.map +1 -1
  90. package/dist/signals/tracking.js.map +1 -1
  91. package/dist/statefulPromise.js +2 -2
  92. package/dist/statefulPromise.js.map +1 -1
  93. package/dist/types.d.ts +5 -1
  94. package/dist/types.d.ts.map +1 -1
  95. package/dist/types.dom.d.ts +1 -1
  96. package/dist/types.dom.d.ts.map +1 -1
  97. package/dist/utils/format.d.ts.map +1 -1
  98. package/dist/utils/format.js +4 -1
  99. package/dist/utils/format.js.map +1 -1
  100. package/dist/utils/vdom.d.ts +2 -2
  101. package/dist/utils/vdom.d.ts.map +1 -1
  102. package/dist/utils/vdom.js +2 -2
  103. package/dist/utils/vdom.js.map +1 -1
  104. package/dist/viewTransitions.d.ts.map +1 -1
  105. package/dist/viewTransitions.js +2 -1
  106. package/dist/viewTransitions.js.map +1 -1
  107. package/package.json +1 -1
  108. package/src/appHandle.ts +2 -2
  109. package/src/components/lazy.ts +5 -6
  110. package/src/components/transition.ts +2 -6
  111. package/src/devtools.ts +4 -2
  112. package/src/dom/commit.ts +133 -0
  113. package/src/dom/focus.ts +34 -0
  114. package/src/dom/index.ts +3 -0
  115. package/src/dom/nodes.ts +205 -0
  116. package/src/dom/props.ts +818 -0
  117. package/src/env.ts +3 -0
  118. package/src/globalContext.ts +7 -24
  119. package/src/globals.ts +25 -2
  120. package/src/hmr.ts +32 -5
  121. package/src/hooks/index.ts +1 -0
  122. package/src/hooks/onBeforeMount.ts +9 -3
  123. package/src/hooks/onCleanup.ts +10 -4
  124. package/src/hooks/onMount.ts +10 -4
  125. package/src/hooks/setup.ts +70 -0
  126. package/src/hooks/utils.ts +14 -19
  127. package/src/index.ts +4 -2
  128. package/src/reconciler.ts +3 -3
  129. package/src/router/head.ts +2 -2
  130. package/src/router/pageConfig.ts +2 -2
  131. package/src/scheduler.ts +79 -64
  132. package/src/signals/base.ts +3 -3
  133. package/src/signals/effect.ts +5 -7
  134. package/src/signals/tracking.ts +3 -2
  135. package/src/statefulPromise.ts +2 -2
  136. package/src/types.dom.ts +2 -2
  137. package/src/types.ts +7 -1
  138. package/src/utils/format.ts +3 -1
  139. package/src/utils/vdom.ts +2 -2
  140. package/src/viewTransitions.ts +2 -1
  141. package/dist/dom.d.ts +0 -10
  142. package/dist/dom.d.ts.map +0 -1
  143. package/dist/dom.js +0 -601
  144. package/dist/dom.js.map +0 -1
  145. 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