what-core 0.5.6 → 0.6.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.
package/dist/render.js CHANGED
@@ -1,118 +1,1236 @@
1
- // What Framework - Fine-Grained Rendering Primitives
2
- // Solid-style rendering: components run once, signals create individual DOM effects.
3
- // No VDOM diffing — direct DOM manipulation with surgical signal-driven updates.
4
-
5
- import { effect, untrack, createRoot, signal } from './reactive.js';
6
-
7
- // --- template(html) ---
8
- // Pre-parse HTML string into a <template> element. Returns a factory function
9
- // that clones the DOM tree via cloneNode(true) — 2-5x faster than createElement chains.
10
-
11
- export function template(html) {
12
- const t = document.createElement('template');
13
- t.innerHTML = html.trim();
14
- return () => t.content.firstChild.cloneNode(true);
1
+ // packages/core/src/reactive.js
2
+ var __DEV__ = typeof process !== "undefined" ? true : true;
3
+ var __devtools = null;
4
+ var currentEffect = null;
5
+ var currentRoot = null;
6
+ var currentOwner = null;
7
+ var insideComputed = false;
8
+ var batchDepth = 0;
9
+ var pendingEffects = [];
10
+ var pendingNeedSort = false;
11
+ var subSetOwner = /* @__PURE__ */ new WeakMap();
12
+ var NEEDS_UPSTREAM = /* @__PURE__ */ Symbol("needs_upstream");
13
+ function signal(initial, debugName) {
14
+ let value = initial;
15
+ const subs = /* @__PURE__ */ new Set();
16
+ function sig(...args) {
17
+ if (args.length === 0) {
18
+ if (currentEffect) {
19
+ subs.add(currentEffect);
20
+ currentEffect.deps.push(subs);
21
+ }
22
+ return value;
23
+ }
24
+ if (__DEV__ && insideComputed) {
25
+ console.warn(
26
+ "[what] Signal.set() called inside a computed function. This may cause infinite loops. Use effect() instead." + (debugName ? ` (signal: ${debugName})` : "")
27
+ );
28
+ }
29
+ const nextVal = typeof args[0] === "function" ? args[0](value) : args[0];
30
+ if (Object.is(value, nextVal)) return;
31
+ value = nextVal;
32
+ if (__DEV__ && __devtools) __devtools.onSignalUpdate(sig);
33
+ if (subs.size > 0) notify(subs);
34
+ }
35
+ sig.set = (next) => {
36
+ if (__DEV__ && insideComputed) {
37
+ console.warn(
38
+ "[what] Signal.set() called inside a computed function. This may cause infinite loops. Use effect() instead." + (debugName ? ` (signal: ${debugName})` : "")
39
+ );
40
+ }
41
+ const nextVal = typeof next === "function" ? next(value) : next;
42
+ if (Object.is(value, nextVal)) return;
43
+ value = nextVal;
44
+ if (__DEV__ && __devtools) __devtools.onSignalUpdate(sig);
45
+ if (subs.size > 0) notify(subs);
46
+ };
47
+ sig.peek = () => value;
48
+ sig.subscribe = (fn) => {
49
+ return effect(() => fn(sig()));
50
+ };
51
+ sig._signal = true;
52
+ if (__DEV__) {
53
+ sig._subs = subs;
54
+ if (debugName) sig._debugName = debugName;
55
+ }
56
+ if (__DEV__ && __devtools) __devtools.onSignalCreate(sig);
57
+ return sig;
58
+ }
59
+ function _updateLevel(e) {
60
+ let maxDepLevel = 0;
61
+ const deps = e.deps;
62
+ for (let i = 0; i < deps.length; i++) {
63
+ const owner = subSetOwner.get(deps[i]);
64
+ if (owner) {
65
+ const depLevel = owner._level;
66
+ if (depLevel > maxDepLevel) maxDepLevel = depLevel;
67
+ }
68
+ }
69
+ e._level = maxDepLevel + 1;
70
+ }
71
+ function effect(fn, opts) {
72
+ const e = _createEffect(fn);
73
+ e._level = 1;
74
+ const prev = currentEffect;
75
+ currentEffect = e;
76
+ try {
77
+ const result = e.fn();
78
+ if (typeof result === "function") e._cleanup = result;
79
+ } finally {
80
+ currentEffect = prev;
81
+ }
82
+ _updateLevel(e);
83
+ if (opts?.stable) e._stable = true;
84
+ const dispose = () => _disposeEffect(e);
85
+ if (currentRoot) {
86
+ currentRoot.disposals.push(dispose);
87
+ }
88
+ return dispose;
89
+ }
90
+ function _createEffect(fn, lazy) {
91
+ const e = {
92
+ fn,
93
+ deps: [],
94
+ // array of subscriber sets (cheaper than Set for typical 1-3 deps)
95
+ lazy: lazy || false,
96
+ _onNotify: null,
97
+ disposed: false,
98
+ _pending: false,
99
+ _stable: false,
100
+ // stable effects skip cleanup/re-subscribe on re-run
101
+ _level: 0,
102
+ // topological depth: signals=0, computed/effects=max(deps)+1
103
+ _computed: false,
104
+ // true for computed inner effects
105
+ _computedSubs: null,
106
+ // reference to the computed's subscriber set
107
+ _isDirty: null,
108
+ // function to check if computed is dirty (set by computed())
109
+ _markDirty: null
110
+ // function to mark computed dirty (set by computed())
111
+ };
112
+ if (__DEV__ && __devtools) __devtools.onEffectCreate(e);
113
+ return e;
114
+ }
115
+ function _runEffect(e) {
116
+ if (e.disposed) return;
117
+ if (e._stable) {
118
+ if (e._cleanup) {
119
+ try {
120
+ e._cleanup();
121
+ } catch (err) {
122
+ if (__DEV__) console.warn("[what] Error in effect cleanup:", err);
123
+ }
124
+ e._cleanup = null;
125
+ }
126
+ const prev2 = currentEffect;
127
+ currentEffect = null;
128
+ try {
129
+ const result = e.fn();
130
+ if (typeof result === "function") e._cleanup = result;
131
+ } catch (err) {
132
+ if (__devtools?.onError) __devtools.onError(err, { type: "effect", effect: e });
133
+ if (__DEV__) console.warn("[what] Error in stable effect:", err);
134
+ } finally {
135
+ currentEffect = prev2;
136
+ }
137
+ if (__DEV__ && __devtools?.onEffectRun) __devtools.onEffectRun(e);
138
+ return;
139
+ }
140
+ cleanup(e);
141
+ if (e._cleanup) {
142
+ try {
143
+ e._cleanup();
144
+ } catch (err) {
145
+ if (__devtools?.onError) __devtools.onError(err, { type: "effect-cleanup", effect: e });
146
+ if (__DEV__) console.warn("[what] Error in effect cleanup:", err);
147
+ }
148
+ e._cleanup = null;
149
+ }
150
+ const prev = currentEffect;
151
+ currentEffect = e;
152
+ try {
153
+ const result = e.fn();
154
+ if (typeof result === "function") {
155
+ e._cleanup = result;
156
+ }
157
+ } catch (err) {
158
+ if (err === NEEDS_UPSTREAM) throw err;
159
+ if (__devtools?.onError) __devtools.onError(err, { type: "effect", effect: e });
160
+ throw err;
161
+ } finally {
162
+ currentEffect = prev;
163
+ }
164
+ if (__DEV__ && __devtools?.onEffectRun) __devtools.onEffectRun(e);
165
+ }
166
+ function _disposeEffect(e) {
167
+ e.disposed = true;
168
+ if (__DEV__ && __devtools) __devtools.onEffectDispose(e);
169
+ cleanup(e);
170
+ if (e._cleanup) {
171
+ try {
172
+ e._cleanup();
173
+ } catch (err) {
174
+ if (__DEV__) console.warn("[what] Error in effect cleanup on dispose:", err);
175
+ }
176
+ e._cleanup = null;
177
+ }
178
+ }
179
+ function cleanup(e) {
180
+ const deps = e.deps;
181
+ for (let i = 0; i < deps.length; i++) deps[i].delete(e);
182
+ deps.length = 0;
183
+ }
184
+ var notifyDepth = 0;
185
+ var notifyQueue = null;
186
+ var notifyQueueLen = 0;
187
+ function notify(subs) {
188
+ if (notifyDepth === 0) {
189
+ notifyDepth = 1;
190
+ try {
191
+ for (const e of subs) {
192
+ if (e.disposed) continue;
193
+ if (e._onNotify) {
194
+ e._onNotify();
195
+ } else if (batchDepth === 0 && e._stable) {
196
+ const prev = currentEffect;
197
+ currentEffect = null;
198
+ try {
199
+ const result = e.fn();
200
+ if (typeof result === "function") {
201
+ if (e._cleanup) try {
202
+ e._cleanup();
203
+ } catch (err) {
204
+ }
205
+ e._cleanup = result;
206
+ }
207
+ } catch (err) {
208
+ if (__devtools?.onError) __devtools.onError(err, { type: "effect", effect: e });
209
+ if (__DEV__) console.warn("[what] Error in stable effect:", err);
210
+ } finally {
211
+ currentEffect = prev;
212
+ }
213
+ } else if (!e._pending) {
214
+ e._pending = true;
215
+ const level = e._level;
216
+ const len = pendingEffects.length;
217
+ if (len > 0 && pendingEffects[len - 1]._level > level) {
218
+ pendingNeedSort = true;
219
+ }
220
+ pendingEffects.push(e);
221
+ }
222
+ }
223
+ if (notifyQueueLen > 0) {
224
+ let qi = 0;
225
+ while (qi < notifyQueueLen) {
226
+ const queuedSubs = notifyQueue[qi];
227
+ notifyQueue[qi] = null;
228
+ qi++;
229
+ for (const e of queuedSubs) {
230
+ if (e.disposed) continue;
231
+ if (e._onNotify) {
232
+ e._onNotify();
233
+ } else if (batchDepth === 0 && e._stable) {
234
+ const prev = currentEffect;
235
+ currentEffect = null;
236
+ try {
237
+ const result = e.fn();
238
+ if (typeof result === "function") {
239
+ if (e._cleanup) try {
240
+ e._cleanup();
241
+ } catch (err) {
242
+ }
243
+ e._cleanup = result;
244
+ }
245
+ } catch (err) {
246
+ if (__devtools?.onError) __devtools.onError(err, { type: "effect", effect: e });
247
+ if (__DEV__) console.warn("[what] Error in stable effect:", err);
248
+ } finally {
249
+ currentEffect = prev;
250
+ }
251
+ } else if (!e._pending) {
252
+ e._pending = true;
253
+ const level = e._level;
254
+ const len = pendingEffects.length;
255
+ if (len > 0 && pendingEffects[len - 1]._level > level) {
256
+ pendingNeedSort = true;
257
+ }
258
+ pendingEffects.push(e);
259
+ }
260
+ }
261
+ }
262
+ notifyQueueLen = 0;
263
+ }
264
+ } finally {
265
+ notifyDepth = 0;
266
+ }
267
+ if (batchDepth === 0 && pendingEffects.length > 0) scheduleMicrotask();
268
+ } else {
269
+ if (notifyQueue === null) notifyQueue = [];
270
+ if (notifyQueueLen >= notifyQueue.length) {
271
+ notifyQueue.push(subs);
272
+ } else {
273
+ notifyQueue[notifyQueueLen] = subs;
274
+ }
275
+ notifyQueueLen++;
276
+ }
277
+ }
278
+ var microtaskScheduled = false;
279
+ function scheduleMicrotask() {
280
+ if (!microtaskScheduled) {
281
+ microtaskScheduled = true;
282
+ queueMicrotask(() => {
283
+ microtaskScheduled = false;
284
+ flush();
285
+ });
286
+ }
287
+ }
288
+ var isFlushing = false;
289
+ function flush() {
290
+ if (isFlushing) return;
291
+ isFlushing = true;
292
+ try {
293
+ let iterations = 0;
294
+ while (pendingEffects.length > 0 && iterations < 25) {
295
+ const batch2 = pendingEffects;
296
+ pendingEffects = [];
297
+ if (batch2.length > 1 && pendingNeedSort) {
298
+ batch2.sort((a, b) => a._level - b._level);
299
+ }
300
+ pendingNeedSort = false;
301
+ for (let i = 0; i < batch2.length; i++) {
302
+ const e = batch2[i];
303
+ e._pending = false;
304
+ if (!e.disposed && !e._onNotify) {
305
+ const prevDepsLen = e.deps.length;
306
+ _runEffect(e);
307
+ if (!e._computed && e.deps.length !== prevDepsLen) {
308
+ _updateLevel(e);
309
+ }
310
+ }
311
+ }
312
+ iterations++;
313
+ }
314
+ if (iterations >= 25) {
315
+ for (let i = 0; i < pendingEffects.length; i++) pendingEffects[i]._pending = false;
316
+ pendingEffects.length = 0;
317
+ if (__DEV__) {
318
+ const remaining = pendingEffects.slice(0, 3);
319
+ const effectNames = remaining.map((e) => e.fn?.name || e.fn?.toString().slice(0, 60) || "(anonymous)");
320
+ console.warn(
321
+ `[what] Possible infinite effect loop detected (25 iterations). Likely cause: an effect writes to a signal it also reads, creating a cycle. Use untrack() to read signals without subscribing. Looping effects: ${effectNames.join(", ")}`
322
+ );
323
+ } else {
324
+ console.warn("[what] Possible infinite effect loop detected");
325
+ }
326
+ }
327
+ } finally {
328
+ isFlushing = false;
329
+ }
330
+ }
331
+ function untrack(fn) {
332
+ const prev = currentEffect;
333
+ currentEffect = null;
334
+ try {
335
+ return fn();
336
+ } finally {
337
+ currentEffect = prev;
338
+ }
339
+ }
340
+ function createRoot(fn) {
341
+ const prevRoot = currentRoot;
342
+ const prevOwner = currentOwner;
343
+ const root = {
344
+ disposals: [],
345
+ owner: currentOwner,
346
+ // parent owner for ownership tree
347
+ children: [],
348
+ // child roots (ownership tree)
349
+ _disposed: false
350
+ };
351
+ if (currentOwner) {
352
+ currentOwner.children.push(root);
353
+ }
354
+ currentRoot = root;
355
+ currentOwner = root;
356
+ try {
357
+ const dispose = () => {
358
+ if (root._disposed) return;
359
+ root._disposed = true;
360
+ for (let i = root.children.length - 1; i >= 0; i--) {
361
+ _disposeRoot(root.children[i]);
362
+ }
363
+ root.children.length = 0;
364
+ for (let i = root.disposals.length - 1; i >= 0; i--) {
365
+ root.disposals[i]();
366
+ }
367
+ root.disposals.length = 0;
368
+ if (root.owner) {
369
+ const idx = root.owner.children.indexOf(root);
370
+ if (idx >= 0) root.owner.children.splice(idx, 1);
371
+ }
372
+ };
373
+ return fn(dispose);
374
+ } finally {
375
+ currentRoot = prevRoot;
376
+ currentOwner = prevOwner;
377
+ }
378
+ }
379
+ function _disposeRoot(root) {
380
+ if (root._disposed) return;
381
+ root._disposed = true;
382
+ for (let i = root.children.length - 1; i >= 0; i--) {
383
+ _disposeRoot(root.children[i]);
384
+ }
385
+ root.children.length = 0;
386
+ for (let i = root.disposals.length - 1; i >= 0; i--) {
387
+ root.disposals[i]();
388
+ }
389
+ root.disposals.length = 0;
15
390
  }
16
391
 
17
- // --- insert(parent, child, marker?) ---
18
- // Reactive child insertion. Handles all child types:
19
- // - string/number → text node
20
- // - function → effect that updates text node reactively
21
- // - DOM node → append directly
22
- // - array → insert each element
23
-
24
- export function insert(parent, child, marker) {
25
- if (child == null || typeof child === 'boolean') return;
26
-
27
- if (typeof child === 'string' || typeof child === 'number') {
28
- const textNode = document.createTextNode(String(child));
29
- parent.insertBefore(textNode, marker || null);
30
- return textNode;
392
+ // packages/core/src/components.js
393
+ var _getCurrentComponent = null;
394
+ function _injectGetCurrentComponent(fn) {
395
+ _getCurrentComponent = fn;
396
+ }
397
+ function reportError(error, startCtx) {
398
+ let ctx = startCtx || _getCurrentComponent?.();
399
+ while (ctx) {
400
+ if (ctx._errorBoundary) {
401
+ ctx._errorBoundary(error);
402
+ return true;
403
+ }
404
+ ctx = ctx._parentCtx;
31
405
  }
406
+ return false;
407
+ }
32
408
 
33
- if (typeof child === 'function') {
34
- // Reactive expression — create micro-effect
35
- let currentNode = document.createTextNode('');
36
- parent.insertBefore(currentNode, marker || null);
409
+ // packages/core/src/helpers.js
410
+ var _getCurrentComponentRef = null;
411
+ function _setComponentRef(fn) {
412
+ _getCurrentComponentRef = fn;
413
+ }
37
414
 
38
- effect(() => {
39
- const value = child();
40
- if (value instanceof Node) {
41
- // Function returned a DOM node — replace text node with it
42
- if (currentNode !== value) {
43
- parent.replaceChild(value, currentNode);
44
- currentNode = value;
415
+ // packages/core/src/dom.js
416
+ var SVG_ELEMENTS = /* @__PURE__ */ new Set([
417
+ "svg",
418
+ "path",
419
+ "circle",
420
+ "rect",
421
+ "line",
422
+ "polyline",
423
+ "polygon",
424
+ "ellipse",
425
+ "g",
426
+ "defs",
427
+ "use",
428
+ "symbol",
429
+ "clipPath",
430
+ "mask",
431
+ "pattern",
432
+ "image",
433
+ "text",
434
+ "tspan",
435
+ "textPath",
436
+ "foreignObject",
437
+ "linearGradient",
438
+ "radialGradient",
439
+ "stop",
440
+ "marker",
441
+ "animate",
442
+ "animateTransform",
443
+ "animateMotion",
444
+ "set",
445
+ "filter",
446
+ "feBlend",
447
+ "feColorMatrix",
448
+ "feComponentTransfer",
449
+ "feComposite",
450
+ "feConvolveMatrix",
451
+ "feDiffuseLighting",
452
+ "feDisplacementMap",
453
+ "feFlood",
454
+ "feGaussianBlur",
455
+ "feImage",
456
+ "feMerge",
457
+ "feMergeNode",
458
+ "feMorphology",
459
+ "feOffset",
460
+ "feSpecularLighting",
461
+ "feTile",
462
+ "feTurbulence"
463
+ ]);
464
+ var SVG_NS = "http://www.w3.org/2000/svg";
465
+ var mountedComponents = /* @__PURE__ */ new Set();
466
+ var _commentCtxMap = /* @__PURE__ */ new WeakMap();
467
+ function isDomNode(value) {
468
+ if (!value || typeof value !== "object") return false;
469
+ if (typeof Node !== "undefined" && value instanceof Node) return true;
470
+ return typeof value.nodeType === "number" && typeof value.nodeName === "string";
471
+ }
472
+ function isVNode(value) {
473
+ return !!value && typeof value === "object" && (value._vnode === true || "tag" in value);
474
+ }
475
+ function disposeComponent(ctx) {
476
+ if (ctx.disposed) return;
477
+ ctx.disposed = true;
478
+ if (ctx.cleanups) {
479
+ for (const cleanup2 of ctx.cleanups) {
480
+ try {
481
+ cleanup2();
482
+ } catch (e) {
483
+ console.error("[what] cleanup error:", e);
484
+ }
485
+ }
486
+ }
487
+ if (ctx.effects) {
488
+ for (const dispose of ctx.effects) {
489
+ try {
490
+ dispose();
491
+ } catch (e) {
492
+ }
493
+ }
494
+ }
495
+ if (ctx.hooks) {
496
+ for (const hook of ctx.hooks) {
497
+ if (hook && typeof hook.cleanup === "function") {
498
+ try {
499
+ hook.cleanup();
500
+ } catch (e) {
501
+ console.error("[what] hook cleanup error:", e);
45
502
  }
46
- } else if (Array.isArray(value)) {
47
- // Function returned array — handle dynamic lists
48
- _insertArray(parent, value, currentNode, marker);
49
- } else {
50
- // Primitive update text content
51
- const text = value == null || typeof value === 'boolean' ? '' : String(value);
52
- if (currentNode.nodeType === 3) {
53
- if (currentNode.textContent !== text) currentNode.textContent = text;
503
+ }
504
+ }
505
+ }
506
+ if (ctx._cleanupCallbacks) {
507
+ for (const fn of ctx._cleanupCallbacks) {
508
+ try {
509
+ fn();
510
+ } catch (e) {
511
+ console.error("[what] onCleanup error:", e);
512
+ }
513
+ }
514
+ }
515
+ if (__DEV__ && __devtools?.onComponentUnmount) __devtools.onComponentUnmount(ctx);
516
+ mountedComponents.delete(ctx);
517
+ }
518
+ function disposeTree(node) {
519
+ if (!node) return;
520
+ if (node._componentCtx) {
521
+ disposeComponent(node._componentCtx);
522
+ }
523
+ const commentCtx = _commentCtxMap.get(node);
524
+ if (commentCtx) {
525
+ disposeComponent(commentCtx);
526
+ }
527
+ if (node._dispose) {
528
+ try {
529
+ node._dispose();
530
+ } catch (e) {
531
+ }
532
+ }
533
+ if (node._propEffects) {
534
+ for (const key in node._propEffects) {
535
+ try {
536
+ node._propEffects[key]();
537
+ } catch (e) {
538
+ }
539
+ }
540
+ }
541
+ if (node.childNodes) {
542
+ for (const child of node.childNodes) {
543
+ disposeTree(child);
544
+ }
545
+ }
546
+ }
547
+ function createDOM(vnode, parent, isSvg) {
548
+ if (vnode == null || vnode === false || vnode === true) {
549
+ return document.createComment("");
550
+ }
551
+ if (typeof vnode === "string" || typeof vnode === "number") {
552
+ return document.createTextNode(String(vnode));
553
+ }
554
+ if (isDomNode(vnode)) {
555
+ return vnode;
556
+ }
557
+ if (typeof vnode === "function") {
558
+ const startMarker = document.createComment("fn");
559
+ const endMarker = document.createComment("/fn");
560
+ let currentNodes = [];
561
+ const frag = document.createDocumentFragment();
562
+ frag.appendChild(startMarker);
563
+ frag.appendChild(endMarker);
564
+ const dispose = effect(() => {
565
+ const val = vnode();
566
+ const vnodes = val == null || val === false || val === true ? [] : Array.isArray(val) ? val : [val];
567
+ const realParent = endMarker.parentNode;
568
+ if (!realParent) return;
569
+ for (const old of currentNodes) {
570
+ disposeTree(old);
571
+ if (old.parentNode === realParent) realParent.removeChild(old);
572
+ }
573
+ currentNodes = [];
574
+ for (const v of vnodes) {
575
+ const node = createDOM(v, realParent, parent?._isSvg);
576
+ if (node) {
577
+ if (node.nodeType === 11) {
578
+ const children = Array.from(node.childNodes);
579
+ realParent.insertBefore(node, endMarker);
580
+ for (const child of children) currentNodes.push(child);
581
+ } else {
582
+ realParent.insertBefore(node, endMarker);
583
+ currentNodes.push(node);
584
+ }
585
+ }
586
+ }
587
+ });
588
+ startMarker._dispose = dispose;
589
+ endMarker._dispose = dispose;
590
+ return frag;
591
+ }
592
+ if (Array.isArray(vnode)) {
593
+ const frag = document.createDocumentFragment();
594
+ for (const child of vnode) {
595
+ const node = createDOM(child, parent, isSvg);
596
+ if (node) frag.appendChild(node);
597
+ }
598
+ return frag;
599
+ }
600
+ if (isVNode(vnode) && typeof vnode.tag === "function") {
601
+ return createComponent(vnode, parent, isSvg);
602
+ }
603
+ if (isVNode(vnode)) {
604
+ return createElementFromVNode(vnode, parent, isSvg);
605
+ }
606
+ return document.createTextNode(String(vnode));
607
+ }
608
+ var componentStack = [];
609
+ function getCurrentComponent() {
610
+ return componentStack[componentStack.length - 1];
611
+ }
612
+ _injectGetCurrentComponent(getCurrentComponent);
613
+ _setComponentRef(getCurrentComponent);
614
+ function getComponentStack() {
615
+ return componentStack;
616
+ }
617
+ function createComponent(vnode, parent, isSvg) {
618
+ let { tag: Component, props, children } = vnode;
619
+ if (typeof Component === "function" && (Component.prototype?.isReactComponent || Component.prototype?.render)) {
620
+ const ClassComp = Component;
621
+ Component = function ClassComponentBridge(props2) {
622
+ const instance = new ClassComp(props2);
623
+ return instance.render();
624
+ };
625
+ Component.displayName = ClassComp.displayName || ClassComp.name || "ClassComponent";
626
+ }
627
+ if (Component === "__errorBoundary" || vnode.tag === "__errorBoundary") {
628
+ return createErrorBoundary(vnode, parent);
629
+ }
630
+ if (Component === "__suspense" || vnode.tag === "__suspense") {
631
+ return createSuspenseBoundary(vnode, parent);
632
+ }
633
+ if (Component === "__portal" || vnode.tag === "__portal") {
634
+ return createPortalDOM(vnode, parent);
635
+ }
636
+ const ctx = {
637
+ hooks: [],
638
+ hookIndex: 0,
639
+ effects: [],
640
+ cleanups: [],
641
+ mounted: false,
642
+ disposed: false,
643
+ Component,
644
+ _parentCtx: componentStack[componentStack.length - 1] || null,
645
+ _errorBoundary: (() => {
646
+ let p = componentStack[componentStack.length - 1];
647
+ while (p) {
648
+ if (p._errorBoundary) return p._errorBoundary;
649
+ p = p._parentCtx;
650
+ }
651
+ return null;
652
+ })()
653
+ };
654
+ const startComment = document.createComment("c:start");
655
+ const endComment = document.createComment("c:end");
656
+ _commentCtxMap.set(startComment, ctx);
657
+ ctx._startComment = startComment;
658
+ ctx._endComment = endComment;
659
+ const container = document.createDocumentFragment();
660
+ container._componentCtx = ctx;
661
+ ctx._wrapper = startComment;
662
+ mountedComponents.add(ctx);
663
+ if (__DEV__ && __devtools?.onComponentMount) __devtools.onComponentMount(ctx);
664
+ const propsChildren = children.length === 0 ? void 0 : children.length === 1 ? children[0] : children;
665
+ const propsSignal = signal({ ...props, children: propsChildren });
666
+ ctx._propsSignal = propsSignal;
667
+ const reactiveProps = new Proxy({}, {
668
+ get(_, key) {
669
+ const current = propsSignal();
670
+ return current[key];
671
+ },
672
+ has(_, key) {
673
+ const current = propsSignal();
674
+ return key in current;
675
+ },
676
+ ownKeys() {
677
+ const current = propsSignal();
678
+ return Reflect.ownKeys(current);
679
+ },
680
+ getOwnPropertyDescriptor(_, key) {
681
+ const current = propsSignal();
682
+ if (key in current) {
683
+ return { value: current[key], writable: false, enumerable: true, configurable: true };
684
+ }
685
+ return void 0;
686
+ }
687
+ });
688
+ componentStack.push(ctx);
689
+ let result;
690
+ try {
691
+ result = Component(reactiveProps);
692
+ } catch (error) {
693
+ componentStack.pop();
694
+ if (!reportError(error, ctx)) {
695
+ console.error("[what] Uncaught error in component:", Component.name || "Anonymous", error);
696
+ throw error;
697
+ }
698
+ container.appendChild(startComment);
699
+ container.appendChild(endComment);
700
+ return container;
701
+ }
702
+ componentStack.pop();
703
+ ctx.mounted = true;
704
+ if (ctx._mountCallbacks) {
705
+ queueMicrotask(() => {
706
+ if (ctx.disposed) return;
707
+ for (const fn of ctx._mountCallbacks) {
708
+ try {
709
+ fn();
710
+ } catch (e) {
711
+ console.error("[what] onMount error:", e);
712
+ }
713
+ }
714
+ });
715
+ }
716
+ container.appendChild(startComment);
717
+ const vnodes = Array.isArray(result) ? result : [result];
718
+ for (const v of vnodes) {
719
+ const node = createDOM(v, container, isSvg);
720
+ if (node) container.appendChild(node);
721
+ }
722
+ container.appendChild(endComment);
723
+ return container;
724
+ }
725
+ function createErrorBoundary(vnode, parent) {
726
+ const { errorState, handleError, fallback, reset } = vnode.props;
727
+ const children = vnode.children;
728
+ const startComment = document.createComment("eb:start");
729
+ const endComment = document.createComment("eb:end");
730
+ const boundaryCtx = {
731
+ hooks: [],
732
+ hookIndex: 0,
733
+ effects: [],
734
+ cleanups: [],
735
+ mounted: false,
736
+ disposed: false,
737
+ _parentCtx: componentStack[componentStack.length - 1] || null,
738
+ _errorBoundary: handleError,
739
+ _startComment: startComment,
740
+ _endComment: endComment
741
+ };
742
+ _commentCtxMap.set(startComment, boundaryCtx);
743
+ const container = document.createDocumentFragment();
744
+ container._componentCtx = boundaryCtx;
745
+ container.appendChild(startComment);
746
+ container.appendChild(endComment);
747
+ const dispose = effect(() => {
748
+ const error = errorState();
749
+ componentStack.push(boundaryCtx);
750
+ if (startComment.parentNode) {
751
+ while (startComment.nextSibling && startComment.nextSibling !== endComment) {
752
+ const old = startComment.nextSibling;
753
+ disposeTree(old);
754
+ old.parentNode.removeChild(old);
755
+ }
756
+ }
757
+ let vnodes;
758
+ if (error) {
759
+ vnodes = typeof fallback === "function" ? [fallback({ error, reset })] : [fallback];
760
+ } else {
761
+ vnodes = children;
762
+ }
763
+ vnodes = Array.isArray(vnodes) ? vnodes : [vnodes];
764
+ for (const v of vnodes) {
765
+ const node = createDOM(v, parent);
766
+ if (node) {
767
+ if (endComment.parentNode) {
768
+ endComment.parentNode.insertBefore(node, endComment);
769
+ } else {
770
+ container.insertBefore(node, endComment);
771
+ }
772
+ }
773
+ }
774
+ componentStack.pop();
775
+ });
776
+ boundaryCtx.effects.push(dispose);
777
+ return container;
778
+ }
779
+ function createSuspenseBoundary(vnode, parent) {
780
+ const { boundary, fallback, loading } = vnode.props;
781
+ const children = vnode.children;
782
+ const startComment = document.createComment("sb:start");
783
+ const endComment = document.createComment("sb:end");
784
+ const boundaryCtx = {
785
+ hooks: [],
786
+ hookIndex: 0,
787
+ effects: [],
788
+ cleanups: [],
789
+ mounted: false,
790
+ disposed: false,
791
+ _parentCtx: componentStack[componentStack.length - 1] || null,
792
+ _startComment: startComment,
793
+ _endComment: endComment
794
+ };
795
+ _commentCtxMap.set(startComment, boundaryCtx);
796
+ const container = document.createDocumentFragment();
797
+ container._componentCtx = boundaryCtx;
798
+ container.appendChild(startComment);
799
+ container.appendChild(endComment);
800
+ const dispose = effect(() => {
801
+ const isLoading = loading();
802
+ const vnodes = isLoading ? [fallback] : children;
803
+ const normalized = Array.isArray(vnodes) ? vnodes : [vnodes];
804
+ componentStack.push(boundaryCtx);
805
+ if (startComment.parentNode) {
806
+ while (startComment.nextSibling && startComment.nextSibling !== endComment) {
807
+ const old = startComment.nextSibling;
808
+ disposeTree(old);
809
+ old.parentNode.removeChild(old);
810
+ }
811
+ }
812
+ for (const v of normalized) {
813
+ const node = createDOM(v, parent);
814
+ if (node) {
815
+ if (endComment.parentNode) {
816
+ endComment.parentNode.insertBefore(node, endComment);
54
817
  } else {
55
- const textNode = document.createTextNode(text);
56
- parent.replaceChild(textNode, currentNode);
57
- currentNode = textNode;
818
+ container.insertBefore(node, endComment);
58
819
  }
59
820
  }
821
+ }
822
+ componentStack.pop();
823
+ });
824
+ boundaryCtx.effects.push(dispose);
825
+ return container;
826
+ }
827
+ function createPortalDOM(vnode, parent) {
828
+ const { container } = vnode.props;
829
+ const children = vnode.children;
830
+ if (!container) {
831
+ console.warn("[what] Portal: target container not found");
832
+ return document.createComment("portal:empty");
833
+ }
834
+ const portalCtx = {
835
+ hooks: [],
836
+ hookIndex: 0,
837
+ effects: [],
838
+ cleanups: [],
839
+ mounted: false,
840
+ disposed: false,
841
+ _parentCtx: componentStack[componentStack.length - 1] || null
842
+ };
843
+ const placeholder = document.createComment("portal");
844
+ placeholder._componentCtx = portalCtx;
845
+ const portalNodes = [];
846
+ for (const child of children) {
847
+ const node = createDOM(child, container);
848
+ if (node) {
849
+ container.appendChild(node);
850
+ portalNodes.push(node);
851
+ }
852
+ }
853
+ portalCtx._cleanupCallbacks = [() => {
854
+ for (const node of portalNodes) {
855
+ disposeTree(node);
856
+ if (node.parentNode) node.parentNode.removeChild(node);
857
+ }
858
+ }];
859
+ return placeholder;
860
+ }
861
+ function createElementFromVNode(vnode, parent, isSvg) {
862
+ const { tag, props, children } = vnode;
863
+ const svgContext = isSvg || SVG_ELEMENTS.has(tag);
864
+ const el = svgContext ? document.createElementNS(SVG_NS, tag) : document.createElement(tag);
865
+ if (props) {
866
+ applyProps(el, props, {}, svgContext);
867
+ }
868
+ for (const child of children) {
869
+ const node = createDOM(child, el, svgContext && tag !== "foreignObject");
870
+ if (node) el.appendChild(node);
871
+ }
872
+ el._vnode = vnode;
873
+ return el;
874
+ }
875
+ function applyProps(el, newProps, oldProps, isSvg) {
876
+ newProps = newProps || {};
877
+ oldProps = oldProps || {};
878
+ for (const key in newProps) {
879
+ if (key === "key" || key === "children") continue;
880
+ if (key === "ref") {
881
+ if (typeof newProps.ref === "function") newProps.ref(el);
882
+ else if (newProps.ref) newProps.ref.current = el;
883
+ continue;
884
+ }
885
+ setProp(el, key, newProps[key], isSvg);
886
+ }
887
+ }
888
+ function setProp(el, key, value, isSvg) {
889
+ if (typeof value === "function" && !(key.startsWith("on") && key.length > 2) && key !== "ref") {
890
+ if (!el._propEffects) el._propEffects = {};
891
+ if (el._propEffects[key]) {
892
+ try {
893
+ el._propEffects[key]();
894
+ } catch (e) {
895
+ }
896
+ }
897
+ el._propEffects[key] = effect(() => {
898
+ const resolved = value();
899
+ setProp(el, key, resolved, isSvg);
60
900
  });
61
-
62
- return currentNode;
901
+ return;
63
902
  }
64
-
65
- if (child instanceof Node) {
66
- parent.insertBefore(child, marker || null);
67
- return child;
903
+ if (key.startsWith("on") && key.length > 2) {
904
+ let eventName = key.slice(2);
905
+ let useCapture = false;
906
+ if (eventName.endsWith("Capture")) {
907
+ eventName = eventName.slice(0, -7);
908
+ useCapture = true;
909
+ }
910
+ const event = eventName.toLowerCase();
911
+ const storageKey = useCapture ? event + "_capture" : event;
912
+ const old = el._events?.[storageKey];
913
+ if (old && old._original === value) return;
914
+ if (old) el.removeEventListener(event, old, useCapture);
915
+ if (value == null) return;
916
+ if (!el._events) el._events = {};
917
+ const wrappedHandler = (e) => {
918
+ if (!e.nativeEvent) e.nativeEvent = e;
919
+ return untrack(() => value(e));
920
+ };
921
+ wrappedHandler._original = value;
922
+ el._events[storageKey] = wrappedHandler;
923
+ const eventOpts = value._eventOpts;
924
+ el.addEventListener(event, wrappedHandler, eventOpts || useCapture || void 0);
925
+ return;
68
926
  }
69
-
70
- if (Array.isArray(child)) {
71
- const nodes = [];
72
- for (let i = 0; i < child.length; i++) {
73
- const node = insert(parent, child[i], marker);
74
- if (node) nodes.push(node);
927
+ if (key === "className" || key === "class") {
928
+ if (isSvg) {
929
+ el.setAttribute("class", value || "");
930
+ } else {
931
+ el.className = value || "";
932
+ }
933
+ return;
934
+ }
935
+ if (key === "style") {
936
+ if (typeof value === "string") {
937
+ el.style.cssText = value;
938
+ el._prevStyle = null;
939
+ } else if (typeof value === "object") {
940
+ const oldStyle = el._prevStyle || {};
941
+ for (const prop in oldStyle) {
942
+ if (!(prop in value)) el.style[prop] = "";
943
+ }
944
+ for (const prop in value) {
945
+ el.style[prop] = value[prop] ?? "";
946
+ }
947
+ el._prevStyle = { ...value };
948
+ }
949
+ return;
950
+ }
951
+ if (key === "dangerouslySetInnerHTML") {
952
+ el.innerHTML = value?.__html ?? "";
953
+ return;
954
+ }
955
+ if (key === "innerHTML") {
956
+ if (value == null) return;
957
+ if (value && typeof value === "object" && "__html" in value) {
958
+ el.innerHTML = value.__html ?? "";
959
+ } else {
960
+ if (__DEV__) {
961
+ console.warn(
962
+ "[what] innerHTML received a raw string. This is a security risk (XSS). Use innerHTML={{ __html: trustedString }} or dangerouslySetInnerHTML={{ __html: trustedString }} instead."
963
+ );
964
+ }
965
+ return;
966
+ }
967
+ return;
968
+ }
969
+ if (typeof value === "boolean") {
970
+ if (value) el.setAttribute(key, "");
971
+ else el.removeAttribute(key);
972
+ return;
973
+ }
974
+ if (key.startsWith("data-") || key.startsWith("aria-")) {
975
+ el.setAttribute(key, value);
976
+ return;
977
+ }
978
+ if (isSvg) {
979
+ if (value === false || value == null) {
980
+ el.removeAttribute(key);
981
+ } else {
982
+ el.setAttribute(key, value === true ? "" : String(value));
75
983
  }
76
- return nodes;
984
+ return;
985
+ }
986
+ if (key in el) {
987
+ el[key] = value;
988
+ } else {
989
+ el.setAttribute(key, value);
77
990
  }
78
991
  }
79
992
 
80
- function _insertArray(parent, arr, currentNode, marker) {
81
- // Simple case: replace placeholder with array nodes
82
- const frag = document.createDocumentFragment();
83
- for (let i = 0; i < arr.length; i++) {
84
- if (arr[i] instanceof Node) {
85
- frag.appendChild(arr[i]);
86
- } else if (arr[i] != null && typeof arr[i] !== 'boolean') {
87
- frag.appendChild(document.createTextNode(String(arr[i])));
993
+ // packages/core/src/render.js
994
+ function _$createComponent(Component, props, children) {
995
+ if (children && children.length > 0) {
996
+ const mergedChildren = children.length === 1 ? children[0] : children;
997
+ props = props ? { ...props, children: mergedChildren } : { children: mergedChildren };
998
+ }
999
+ return createDOM({ tag: Component, props: props || {}, children: children || [], key: null, _vnode: true });
1000
+ }
1001
+ var URL_ATTRS = /* @__PURE__ */ new Set(["href", "src", "action", "formaction", "formAction"]);
1002
+ function isSafeUrl(url) {
1003
+ if (typeof url !== "string") return true;
1004
+ const normalized = url.trim().replace(/[\s\x00-\x1f]/g, "").toLowerCase();
1005
+ if (normalized.startsWith("javascript:")) return false;
1006
+ if (normalized.startsWith("data:")) return false;
1007
+ if (normalized.startsWith("vbscript:")) return false;
1008
+ return true;
1009
+ }
1010
+ var TABLE_WRAPPERS = {
1011
+ tr: { depth: 2, wrap: "<table><tbody>", unwrap: "</tbody></table>" },
1012
+ td: { depth: 3, wrap: "<table><tbody><tr>", unwrap: "</tr></tbody></table>" },
1013
+ th: { depth: 3, wrap: "<table><tbody><tr>", unwrap: "</tr></tbody></table>" },
1014
+ thead: { depth: 1, wrap: "<table>", unwrap: "</table>" },
1015
+ tbody: { depth: 1, wrap: "<table>", unwrap: "</table>" },
1016
+ tfoot: { depth: 1, wrap: "<table>", unwrap: "</table>" },
1017
+ colgroup: { depth: 1, wrap: "<table>", unwrap: "</table>" },
1018
+ col: { depth: 1, wrap: "<table>", unwrap: "</table>" },
1019
+ caption: { depth: 1, wrap: "<table>", unwrap: "</table>" }
1020
+ };
1021
+ var SVG_ELEMENTS2 = /* @__PURE__ */ new Set([
1022
+ "svg",
1023
+ "path",
1024
+ "circle",
1025
+ "rect",
1026
+ "line",
1027
+ "polyline",
1028
+ "polygon",
1029
+ "ellipse",
1030
+ "g",
1031
+ "defs",
1032
+ "use",
1033
+ "text",
1034
+ "tspan",
1035
+ "foreignObject",
1036
+ "clipPath",
1037
+ "mask",
1038
+ "pattern",
1039
+ "linearGradient",
1040
+ "radialGradient",
1041
+ "stop",
1042
+ "marker",
1043
+ "symbol",
1044
+ "image",
1045
+ "animate",
1046
+ "animateTransform",
1047
+ "animateMotion",
1048
+ "set",
1049
+ "filter",
1050
+ "feGaussianBlur",
1051
+ "feOffset",
1052
+ "feMerge",
1053
+ "feMergeNode",
1054
+ "feBlend",
1055
+ "feColorMatrix",
1056
+ "feComponentTransfer",
1057
+ "feComposite",
1058
+ "feConvolveMatrix",
1059
+ "feDiffuseLighting",
1060
+ "feDisplacementMap",
1061
+ "feFlood",
1062
+ "feImage",
1063
+ "feMorphology",
1064
+ "feSpecularLighting",
1065
+ "feTile",
1066
+ "feTurbulence",
1067
+ "feDistantLight",
1068
+ "fePointLight",
1069
+ "feSpotLight"
1070
+ ]);
1071
+ function getLeadingTag(html) {
1072
+ const m = html.match(/^<([a-zA-Z][a-zA-Z0-9]*)/);
1073
+ return m ? m[1] : "";
1074
+ }
1075
+ function _$templateImpl(html) {
1076
+ const trimmed = html.trim();
1077
+ const tag = getLeadingTag(trimmed);
1078
+ if (SVG_ELEMENTS2.has(tag)) {
1079
+ return svgTemplate(trimmed);
1080
+ }
1081
+ const tableInfo = TABLE_WRAPPERS[tag];
1082
+ if (tableInfo) {
1083
+ const t2 = document.createElement("template");
1084
+ t2.innerHTML = tableInfo.wrap + trimmed + tableInfo.unwrap;
1085
+ return () => {
1086
+ let node = t2.content.firstChild;
1087
+ for (let i = 0; i < tableInfo.depth; i++) {
1088
+ node = node.firstChild;
1089
+ }
1090
+ return node.cloneNode(true);
1091
+ };
1092
+ }
1093
+ const t = document.createElement("template");
1094
+ t.innerHTML = trimmed;
1095
+ return () => t.content.firstChild.cloneNode(true);
1096
+ }
1097
+ var _templateWarned = false;
1098
+ function template(html) {
1099
+ if (__DEV__ && !_templateWarned) {
1100
+ _templateWarned = true;
1101
+ console.warn(
1102
+ "[what] template() is a compiler internal. Use JSX instead. Direct calls with user input can lead to XSS vulnerabilities."
1103
+ );
1104
+ }
1105
+ return _$templateImpl(html);
1106
+ }
1107
+ function svgTemplate(html) {
1108
+ const trimmed = html.trim();
1109
+ const tag = getLeadingTag(trimmed);
1110
+ if (tag === "svg") {
1111
+ const t2 = document.createElement("template");
1112
+ t2.innerHTML = trimmed;
1113
+ return () => t2.content.firstChild.cloneNode(true);
1114
+ }
1115
+ const t = document.createElement("template");
1116
+ t.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg">${trimmed}</svg>`;
1117
+ return () => t.content.firstChild.firstChild.cloneNode(true);
1118
+ }
1119
+ function insert(parent, child, marker) {
1120
+ if (typeof child === "function") {
1121
+ let current = null;
1122
+ effect(() => {
1123
+ current = reconcileInsert(parent, child(), current, marker || null);
1124
+ });
1125
+ return current;
1126
+ }
1127
+ return reconcileInsert(parent, child, null, marker || null);
1128
+ }
1129
+ function isDomNode2(value) {
1130
+ if (!value || typeof value !== "object") return false;
1131
+ if (typeof Node !== "undefined" && value instanceof Node) return true;
1132
+ return typeof value.nodeType === "number" && typeof value.nodeName === "string";
1133
+ }
1134
+ function isVNode2(value) {
1135
+ return !!value && typeof value === "object" && (value._vnode === true || "tag" in value);
1136
+ }
1137
+ function isSvgParent(parent) {
1138
+ return typeof SVGElement !== "undefined" && parent instanceof SVGElement && parent.tagName.toLowerCase() !== "foreignobject";
1139
+ }
1140
+ function asNodeArray(value) {
1141
+ if (value == null) return [];
1142
+ return Array.isArray(value) ? value : [value];
1143
+ }
1144
+ function valuesToNodes(value, parent, out) {
1145
+ if (value == null || typeof value === "boolean") return out;
1146
+ if (Array.isArray(value)) {
1147
+ for (let i = 0; i < value.length; i++) {
1148
+ valuesToNodes(value[i], parent, out);
88
1149
  }
1150
+ return out;
1151
+ }
1152
+ if (typeof value === "string" || typeof value === "number") {
1153
+ out.push(document.createTextNode(String(value)));
1154
+ return out;
1155
+ }
1156
+ if (isDomNode2(value)) {
1157
+ out.push(value);
1158
+ return out;
1159
+ }
1160
+ if (isVNode2(value)) {
1161
+ out.push(createDOM(value, parent, isSvgParent(parent)));
1162
+ return out;
89
1163
  }
90
- parent.replaceChild(frag, currentNode);
1164
+ out.push(document.createTextNode(String(value)));
1165
+ return out;
91
1166
  }
92
-
93
- // --- mapArray(source, mapFn, options?) ---
94
- // Reactive list rendering with per-item scopes.
95
- // Unkeyed: tracks items by reference. Keyed: tracks by key function.
96
- // With key + raw: mapFn receives (item, index) — raw item value. Items identified by key for
97
- // efficient DOM reuse/moves. Use when items have per-field signals (no wrapper needed).
98
- // With key (no raw): mapFn receives (itemAccessor, index) — accessor is a signal getter.
99
- // When item reference changes but key persists, the signal updates in place.
100
- // Without key: mapFn receives (item, index) raw item value. New reference = new row.
101
-
102
- export function mapArray(source, mapFn, options) {
1167
+ function sameNodeArray(a, b) {
1168
+ if (a.length !== b.length) return false;
1169
+ for (let i = 0; i < a.length; i++) {
1170
+ if (a[i] !== b[i]) return false;
1171
+ }
1172
+ return true;
1173
+ }
1174
+ function reconcileInsert(parent, value, current, marker) {
1175
+ if (!parent || typeof parent.insertBefore !== "function") {
1176
+ if (__DEV__) {
1177
+ console.warn("[what] reconcileInsert called with invalid parent:", parent);
1178
+ }
1179
+ return current;
1180
+ }
1181
+ const targetMarker = marker || null;
1182
+ if (value == null || typeof value === "boolean") {
1183
+ const oldNodes2 = asNodeArray(current);
1184
+ for (let i = 0; i < oldNodes2.length; i++) {
1185
+ const oldNode = oldNodes2[i];
1186
+ if (oldNode.parentNode === parent) {
1187
+ disposeTree(oldNode);
1188
+ parent.removeChild(oldNode);
1189
+ }
1190
+ }
1191
+ return null;
1192
+ }
1193
+ if ((typeof value === "string" || typeof value === "number") && current && !Array.isArray(current) && current.nodeType === 3) {
1194
+ const text = String(value);
1195
+ if (current.textContent !== text) current.textContent = text;
1196
+ return current;
1197
+ }
1198
+ const newNodes = valuesToNodes(value, parent, []);
1199
+ const oldNodes = asNodeArray(current);
1200
+ if (sameNodeArray(oldNodes, newNodes)) {
1201
+ return current;
1202
+ }
1203
+ const keep = new Set(newNodes);
1204
+ for (let i = 0; i < oldNodes.length; i++) {
1205
+ const oldNode = oldNodes[i];
1206
+ if (!keep.has(oldNode) && oldNode.parentNode === parent) {
1207
+ disposeTree(oldNode);
1208
+ parent.removeChild(oldNode);
1209
+ }
1210
+ }
1211
+ let ref = targetMarker;
1212
+ for (let i = newNodes.length - 1; i >= 0; i--) {
1213
+ const node = newNodes[i];
1214
+ if (node.parentNode !== parent || node.nextSibling !== ref) {
1215
+ if (ref && ref.parentNode !== parent) ref = null;
1216
+ if (ref) parent.insertBefore(node, ref);
1217
+ else parent.appendChild(node);
1218
+ }
1219
+ ref = node;
1220
+ }
1221
+ if (newNodes.length === 0) return null;
1222
+ return newNodes.length === 1 ? newNodes[0] : newNodes;
1223
+ }
1224
+ function mapArray(source, mapFn, options) {
103
1225
  const keyFn = options?.key;
104
1226
  const raw = options?.raw || false;
105
-
106
1227
  return (parent, marker) => {
107
1228
  let items = [];
108
1229
  let mappedNodes = [];
109
1230
  let disposeFns = [];
110
- // Keyed mode state: key { itemSignal }. Null for raw/unkeyed modes.
111
- let keyedState = keyFn && !raw ? new Map() : null;
112
-
113
- const endMarker = document.createComment('/list');
1231
+ let keyedState = keyFn && !raw ? /* @__PURE__ */ new Map() : null;
1232
+ const endMarker = document.createComment("/list");
114
1233
  parent.insertBefore(endMarker, marker || null);
115
-
116
1234
  effect(() => {
117
1235
  const newItems = source() || [];
118
1236
  if (keyFn) {
@@ -122,33 +1240,31 @@ export function mapArray(source, mapFn, options) {
122
1240
  }
123
1241
  items = newItems.slice();
124
1242
  });
125
-
126
1243
  return endMarker;
127
1244
  };
128
1245
  }
129
-
130
1246
  function reconcileList(parent, endMarker, oldItems, newItems, mappedNodes, disposeFns, mapFn) {
131
1247
  const newLen = newItems.length;
132
1248
  const oldLen = oldItems.length;
133
-
134
1249
  if (newLen === 0) {
135
- // Fast path: clear all
136
1250
  if (oldLen > 0) {
137
- for (let i = 0; i < oldLen; i++) disposeFns[i]?.();
138
- parent.textContent = '';
139
- parent.appendChild(endMarker);
1251
+ for (let i = 0; i < oldLen; i++) {
1252
+ disposeFns[i]?.();
1253
+ if (mappedNodes[i]?.parentNode === parent) {
1254
+ disposeTree(mappedNodes[i]);
1255
+ parent.removeChild(mappedNodes[i]);
1256
+ }
1257
+ }
140
1258
  mappedNodes.length = 0;
141
1259
  disposeFns.length = 0;
142
1260
  }
143
1261
  return;
144
1262
  }
145
-
146
1263
  if (oldLen === 0) {
147
- // Fast path: all new
148
1264
  const frag = document.createDocumentFragment();
149
1265
  for (let i = 0; i < newLen; i++) {
150
1266
  const item = newItems[i];
151
- const node = createRoot(dispose => {
1267
+ const node = createRoot((dispose) => {
152
1268
  disposeFns[i] = dispose;
153
1269
  return mapFn(item, i);
154
1270
  });
@@ -158,23 +1274,16 @@ function reconcileList(parent, endMarker, oldItems, newItems, mappedNodes, dispo
158
1274
  parent.insertBefore(frag, endMarker);
159
1275
  return;
160
1276
  }
161
-
162
- // --- Common prefix/suffix skip ---
163
1277
  let start = 0;
164
1278
  const minLen = Math.min(oldLen, newLen);
165
1279
  while (start < minLen && oldItems[start] === newItems[start]) start++;
166
-
167
- // If everything matches and same length, nothing changed
168
1280
  if (start === oldLen && start === newLen) return;
169
-
170
1281
  let oldEnd = oldLen - 1;
171
1282
  let newEnd = newLen - 1;
172
1283
  while (oldEnd >= start && newEnd >= start && oldItems[oldEnd] === newItems[newEnd]) {
173
1284
  oldEnd--;
174
1285
  newEnd--;
175
1286
  }
176
-
177
- // Copy prefix/suffix into output arrays
178
1287
  const newMapped = new Array(newLen);
179
1288
  const newDispose = new Array(newLen);
180
1289
  for (let i = 0; i < start; i++) {
@@ -182,30 +1291,24 @@ function reconcileList(parent, endMarker, oldItems, newItems, mappedNodes, dispo
182
1291
  newDispose[i] = disposeFns[i];
183
1292
  }
184
1293
  for (let i = newEnd + 1; i < newLen; i++) {
185
- // Suffix items: same item, possibly different index offset
186
1294
  const oldI = oldEnd + 1 + (i - newEnd - 1);
187
1295
  newMapped[i] = mappedNodes[oldI];
188
1296
  newDispose[i] = disposeFns[oldI];
189
1297
  }
190
-
191
- // Only reconcile the middle section: start..newEnd (new) vs start..oldEnd (old)
192
1298
  const midNewLen = newEnd - start + 1;
193
1299
  const midOldLen = oldEnd - start + 1;
194
-
195
1300
  if (midNewLen === 0) {
196
- // Only removals in the middle
197
1301
  for (let i = start; i <= oldEnd; i++) {
198
1302
  disposeFns[i]?.();
199
1303
  if (mappedNodes[i]?.parentNode) mappedNodes[i].parentNode.removeChild(mappedNodes[i]);
200
1304
  }
201
1305
  } else if (midOldLen === 0) {
202
- // Only insertions in the middle
203
1306
  const marker = start < newLen && newMapped[newEnd + 1] ? newMapped[newEnd + 1] : endMarker;
204
1307
  const frag = document.createDocumentFragment();
205
1308
  for (let i = start; i <= newEnd; i++) {
206
1309
  const item = newItems[i];
207
1310
  const idx = i;
208
- newMapped[i] = createRoot(dispose => {
1311
+ newMapped[i] = createRoot((dispose) => {
209
1312
  newDispose[idx] = dispose;
210
1313
  return mapFn(item, idx);
211
1314
  });
@@ -213,12 +1316,21 @@ function reconcileList(parent, endMarker, oldItems, newItems, mappedNodes, dispo
213
1316
  }
214
1317
  parent.insertBefore(frag, marker);
215
1318
  } else {
216
- // General case: reconcile middle section with LIS
217
- _reconcileMiddle(parent, endMarker, oldItems, newItems, mappedNodes, disposeFns,
218
- mapFn, start, oldEnd, newEnd, newMapped, newDispose);
1319
+ _reconcileMiddle(
1320
+ parent,
1321
+ endMarker,
1322
+ oldItems,
1323
+ newItems,
1324
+ mappedNodes,
1325
+ disposeFns,
1326
+ mapFn,
1327
+ start,
1328
+ oldEnd,
1329
+ newEnd,
1330
+ newMapped,
1331
+ newDispose
1332
+ );
219
1333
  }
220
-
221
- // Update arrays in place
222
1334
  mappedNodes.length = newLen;
223
1335
  disposeFns.length = newLen;
224
1336
  for (let i = 0; i < newLen; i++) {
@@ -226,46 +1338,32 @@ function reconcileList(parent, endMarker, oldItems, newItems, mappedNodes, dispo
226
1338
  disposeFns[i] = newDispose[i];
227
1339
  }
228
1340
  }
229
-
230
- function _reconcileMiddle(parent, endMarker, oldItems, newItems, mappedNodes, disposeFns,
231
- mapFn, start, oldEnd, newEnd, newMapped, newDispose) {
232
- // Build index map only for the middle section
233
- const oldIdxMap = new Map();
1341
+ function _reconcileMiddle(parent, endMarker, oldItems, newItems, mappedNodes, disposeFns, mapFn, start, oldEnd, newEnd, newMapped, newDispose) {
1342
+ const oldIdxMap = /* @__PURE__ */ new Map();
234
1343
  for (let i = start; i <= oldEnd; i++) {
235
1344
  oldIdxMap.set(oldItems[i], i);
236
1345
  }
237
-
238
- // Match old items to new positions, collect old indices for LIS
239
1346
  const midLen = newEnd - start + 1;
240
- const oldIndices = new Int32Array(midLen); // -1 = new item
1347
+ const oldIndices = new Int32Array(midLen);
241
1348
  oldIndices.fill(-1);
242
-
243
1349
  for (let i = start; i <= newEnd; i++) {
244
1350
  const oldIdx = oldIdxMap.get(newItems[i]);
245
- if (oldIdx !== undefined) {
1351
+ if (oldIdx !== void 0) {
246
1352
  oldIdxMap.delete(newItems[i]);
247
1353
  newMapped[i] = mappedNodes[oldIdx];
248
1354
  newDispose[i] = disposeFns[oldIdx];
249
1355
  oldIndices[i - start] = oldIdx;
250
1356
  }
251
1357
  }
252
-
253
- // Dispose removed items
254
1358
  for (const [, oldIdx] of oldIdxMap) {
255
1359
  disposeFns[oldIdx]?.();
256
1360
  if (mappedNodes[oldIdx]?.parentNode) mappedNodes[oldIdx].parentNode.removeChild(mappedNodes[oldIdx]);
257
1361
  }
258
-
259
- // Compute LIS on old indices of reused items
260
- // Build the sequence of old indices for reused items only
261
1362
  const reusedCount = midLen - _countNeg1(oldIndices, midLen);
262
-
263
- // Use a bitfield (via Uint8Array) to mark LIS positions — avoids Set overhead
264
1363
  const inLIS = new Uint8Array(midLen);
265
-
266
1364
  if (reusedCount > 1) {
267
1365
  const seq = new Int32Array(reusedCount);
268
- const seqToMid = new Int32Array(reusedCount); // maps seq index → mid index
1366
+ const seqToMid = new Int32Array(reusedCount);
269
1367
  let k = 0;
270
1368
  for (let i = 0; i < midLen; i++) {
271
1369
  if (oldIndices[i] !== -1) {
@@ -279,56 +1377,46 @@ function _reconcileMiddle(parent, endMarker, oldItems, newItems, mappedNodes, di
279
1377
  inLIS[seqToMid[lisResult[i]]] = 1;
280
1378
  }
281
1379
  } else if (reusedCount === 1) {
282
- // Single reused item is trivially in LIS
283
1380
  for (let i = 0; i < midLen; i++) {
284
- if (oldIndices[i] !== -1) { inLIS[i] = 1; break; }
1381
+ if (oldIndices[i] !== -1) {
1382
+ inLIS[i] = 1;
1383
+ break;
1384
+ }
285
1385
  }
286
1386
  }
287
-
288
- // Create new items
289
1387
  for (let i = start; i <= newEnd; i++) {
290
1388
  if (!newMapped[i]) {
291
1389
  const item = newItems[i];
292
1390
  const idx = i;
293
- newMapped[i] = createRoot(dispose => {
1391
+ newMapped[i] = createRoot((dispose) => {
294
1392
  newDispose[idx] = dispose;
295
1393
  return mapFn(item, idx);
296
1394
  });
297
1395
  }
298
1396
  }
299
-
300
- // Position: work backwards from the item after newEnd (suffix start or endMarker)
301
- let nextSibling = newEnd + 1 < newMapped.length && newMapped[newEnd + 1]
302
- ? newMapped[newEnd + 1] : endMarker;
303
-
1397
+ let nextSibling = newEnd + 1 < newMapped.length && newMapped[newEnd + 1] ? newMapped[newEnd + 1] : endMarker;
304
1398
  for (let i = newEnd; i >= start; i--) {
305
1399
  const mi = i - start;
306
1400
  if (oldIndices[mi] === -1 || !inLIS[mi]) {
307
- // New item or moved item insert
1401
+ if (nextSibling && nextSibling.parentNode !== parent) nextSibling = endMarker;
308
1402
  parent.insertBefore(newMapped[i], nextSibling);
309
1403
  }
310
1404
  nextSibling = newMapped[i];
311
1405
  }
312
1406
  }
313
-
314
1407
  function _countNeg1(arr, len) {
315
1408
  let c = 0;
316
1409
  for (let i = 0; i < len; i++) if (arr[i] === -1) c++;
317
1410
  return c;
318
1411
  }
319
-
320
- // Longest Increasing Subsequence — returns indices into the input array.
321
- // O(n log n) using patience sorting. Uses typed arrays for performance.
322
1412
  function _lis(arr, len) {
323
1413
  if (len === 0) return [];
324
1414
  if (len === 1) return [0];
325
-
326
- const tails = new Int32Array(len); // indices into arr
1415
+ const tails = new Int32Array(len);
327
1416
  const predecessors = new Int32Array(len);
328
1417
  let tailLen = 1;
329
1418
  tails[0] = 0;
330
1419
  predecessors[0] = -1;
331
-
332
1420
  for (let i = 1; i < len; i++) {
333
1421
  if (arr[i] > arr[tails[tailLen - 1]]) {
334
1422
  predecessors[i] = tails[tailLen - 1];
@@ -336,7 +1424,7 @@ function _lis(arr, len) {
336
1424
  } else {
337
1425
  let lo = 0, hi = tailLen - 1;
338
1426
  while (lo < hi) {
339
- const mid = (lo + hi) >> 1;
1427
+ const mid = lo + hi >> 1;
340
1428
  if (arr[tails[mid]] < arr[i]) lo = mid + 1;
341
1429
  else hi = mid;
342
1430
  }
@@ -344,7 +1432,6 @@ function _lis(arr, len) {
344
1432
  predecessors[i] = lo > 0 ? tails[lo - 1] : -1;
345
1433
  }
346
1434
  }
347
-
348
1435
  const result = new Array(tailLen);
349
1436
  let k = tails[tailLen - 1];
350
1437
  for (let i = tailLen - 1; i >= 0; i--) {
@@ -353,33 +1440,24 @@ function _lis(arr, len) {
353
1440
  }
354
1441
  return result;
355
1442
  }
356
-
357
- // --- reconcileKeyed ---
358
- // Keyed reconciliation: tracks items by key function, not by reference.
359
- // When a key persists but its item reference changes, the item signal updates
360
- // in place — no DOM node destruction/creation. Only effects reading the
361
- // item accessor re-run (e.g., textContent update for changed label).
362
-
363
1443
  function reconcileKeyed(parent, endMarker, oldItems, newItems, mappedNodes, disposeFns, mapFn, keyFn, keyedState) {
364
1444
  const newLen = newItems.length;
365
1445
  const oldLen = oldItems.length;
366
-
367
- // --- Fast path: clear all ---
368
1446
  if (newLen === 0) {
369
1447
  if (oldLen > 0) {
370
- // Skip individual disposal: per-row effects only subscribe to their item signal,
371
- // which is also being discarded. Both become unreachable → GC collects them.
372
- // Bulk DOM removal: clear parent, re-add marker.
373
- parent.textContent = '';
374
- parent.appendChild(endMarker);
1448
+ for (let i = 0; i < oldLen; i++) {
1449
+ disposeFns[i]?.();
1450
+ if (mappedNodes[i]?.parentNode === parent) {
1451
+ disposeTree(mappedNodes[i]);
1452
+ parent.removeChild(mappedNodes[i]);
1453
+ }
1454
+ }
375
1455
  mappedNodes.length = 0;
376
1456
  disposeFns.length = 0;
377
1457
  if (keyedState) keyedState.clear();
378
1458
  }
379
1459
  return;
380
1460
  }
381
-
382
- // --- Fast path: all new ---
383
1461
  if (oldLen === 0) {
384
1462
  const frag = document.createDocumentFragment();
385
1463
  for (let i = 0; i < newLen; i++) {
@@ -392,9 +1470,9 @@ function reconcileKeyed(parent, endMarker, oldItems, newItems, mappedNodes, disp
392
1470
  accessor = itemSig;
393
1471
  keyedState.set(key, { itemSig });
394
1472
  } else {
395
- accessor = item; // raw mode: pass item directly
1473
+ accessor = item;
396
1474
  }
397
- const node = createRoot(dispose => {
1475
+ const node = createRoot((dispose) => {
398
1476
  disposeFns[idx] = dispose;
399
1477
  return mapFn(accessor, idx);
400
1478
  });
@@ -404,26 +1482,27 @@ function reconcileKeyed(parent, endMarker, oldItems, newItems, mappedNodes, disp
404
1482
  parent.insertBefore(frag, endMarker);
405
1483
  return;
406
1484
  }
407
-
408
- // --- Common prefix: skip matching keys at the start ---
409
1485
  let start = 0;
410
1486
  const minLen = Math.min(oldLen, newLen);
411
1487
  while (start < minLen) {
412
- // Fast path: same reference → same key, no update needed
413
- if (oldItems[start] === newItems[start]) { start++; continue; }
1488
+ if (oldItems[start] === newItems[start]) {
1489
+ start++;
1490
+ continue;
1491
+ }
414
1492
  const oldKey = keyFn(oldItems[start]);
415
1493
  const newKey = keyFn(newItems[start]);
416
1494
  if (oldKey !== newKey) break;
417
- // Key matches but reference changed — update signal (non-raw mode only)
418
1495
  if (keyedState) keyedState.get(oldKey).itemSig.set(newItems[start]);
419
1496
  start++;
420
1497
  }
421
-
422
- // --- Common suffix: skip matching keys at the end ---
423
1498
  let oldEnd = oldLen - 1;
424
1499
  let newEnd = newLen - 1;
425
1500
  while (oldEnd >= start && newEnd >= start) {
426
- if (oldItems[oldEnd] === newItems[newEnd]) { oldEnd--; newEnd--; continue; }
1501
+ if (oldItems[oldEnd] === newItems[newEnd]) {
1502
+ oldEnd--;
1503
+ newEnd--;
1504
+ continue;
1505
+ }
427
1506
  const oldKey = keyFn(oldItems[oldEnd]);
428
1507
  const newKey = keyFn(newItems[newEnd]);
429
1508
  if (oldKey !== newKey) break;
@@ -431,14 +1510,9 @@ function reconcileKeyed(parent, endMarker, oldItems, newItems, mappedNodes, disp
431
1510
  oldEnd--;
432
1511
  newEnd--;
433
1512
  }
434
-
435
- // If everything matched, nothing to do
436
1513
  if (start > oldEnd && start > newEnd) {
437
- // Just copy existing mappings to output
438
1514
  return;
439
1515
  }
440
-
441
- // Copy prefix/suffix into output arrays
442
1516
  const newMapped = new Array(newLen);
443
1517
  const newDispose = new Array(newLen);
444
1518
  for (let i = 0; i < start; i++) {
@@ -450,11 +1524,8 @@ function reconcileKeyed(parent, endMarker, oldItems, newItems, mappedNodes, disp
450
1524
  newMapped[i] = mappedNodes[oldI];
451
1525
  newDispose[i] = disposeFns[oldI];
452
1526
  }
453
-
454
1527
  const midNewLen = newEnd - start + 1;
455
1528
  const midOldLen = oldEnd - start + 1;
456
-
457
- // --- Only additions in middle ---
458
1529
  if (midOldLen === 0) {
459
1530
  const marker = newEnd + 1 < newLen && newMapped[newEnd + 1] ? newMapped[newEnd + 1] : endMarker;
460
1531
  const frag = document.createDocumentFragment();
@@ -470,7 +1541,7 @@ function reconcileKeyed(parent, endMarker, oldItems, newItems, mappedNodes, disp
470
1541
  } else {
471
1542
  accessor = item;
472
1543
  }
473
- newMapped[i] = createRoot(dispose => {
1544
+ newMapped[i] = createRoot((dispose) => {
474
1545
  newDispose[idx] = dispose;
475
1546
  return mapFn(accessor, idx);
476
1547
  });
@@ -480,8 +1551,6 @@ function reconcileKeyed(parent, endMarker, oldItems, newItems, mappedNodes, disp
480
1551
  _copyBack(mappedNodes, disposeFns, newMapped, newDispose, newLen);
481
1552
  return;
482
1553
  }
483
-
484
- // --- Only removals in middle ---
485
1554
  if (midNewLen === 0) {
486
1555
  for (let i = start; i <= oldEnd; i++) {
487
1556
  disposeFns[i]?.();
@@ -491,41 +1560,30 @@ function reconcileKeyed(parent, endMarker, oldItems, newItems, mappedNodes, disp
491
1560
  _copyBack(mappedNodes, disposeFns, newMapped, newDispose, newLen);
492
1561
  return;
493
1562
  }
494
-
495
- // --- General case: reconcile middle section ---
496
- // Build old key → old index map for middle section only
497
- const oldKeyMap = new Map();
1563
+ const oldKeyMap = /* @__PURE__ */ new Map();
498
1564
  for (let i = start; i <= oldEnd; i++) {
499
1565
  oldKeyMap.set(keyFn(oldItems[i]), i);
500
1566
  }
501
-
502
1567
  const oldIndices = new Int32Array(midNewLen);
503
1568
  oldIndices.fill(-1);
504
-
505
- // Match by key
506
1569
  for (let i = start; i <= newEnd; i++) {
507
1570
  const key = keyFn(newItems[i]);
508
1571
  const oldIdx = oldKeyMap.get(key);
509
- if (oldIdx !== undefined) {
1572
+ if (oldIdx !== void 0) {
510
1573
  oldKeyMap.delete(key);
511
1574
  newMapped[i] = mappedNodes[oldIdx];
512
1575
  newDispose[i] = disposeFns[oldIdx];
513
1576
  oldIndices[i - start] = oldIdx;
514
- // Update item signal if reference changed (non-raw mode only)
515
1577
  if (keyedState && newItems[i] !== oldItems[oldIdx]) {
516
1578
  keyedState.get(key).itemSig.set(newItems[i]);
517
1579
  }
518
1580
  }
519
1581
  }
520
-
521
- // Dispose removed items
522
1582
  for (const [key, oldIdx] of oldKeyMap) {
523
1583
  disposeFns[oldIdx]?.();
524
1584
  if (mappedNodes[oldIdx]?.parentNode) parent.removeChild(mappedNodes[oldIdx]);
525
1585
  if (keyedState) keyedState.delete(key);
526
1586
  }
527
-
528
- // Create new items
529
1587
  for (let i = start; i <= newEnd; i++) {
530
1588
  if (!newMapped[i]) {
531
1589
  const item = newItems[i];
@@ -539,15 +1597,12 @@ function reconcileKeyed(parent, endMarker, oldItems, newItems, mappedNodes, disp
539
1597
  } else {
540
1598
  accessor = item;
541
1599
  }
542
- newMapped[i] = createRoot(dispose => {
1600
+ newMapped[i] = createRoot((dispose) => {
543
1601
  newDispose[idx] = dispose;
544
1602
  return mapFn(accessor, idx);
545
1603
  });
546
1604
  }
547
1605
  }
548
-
549
- // Position using LIS
550
- // First check: are reused items already in order? (common for update-in-place)
551
1606
  let reusedCount = 0;
552
1607
  let alreadySorted = true;
553
1608
  let lastOldIdx = -1;
@@ -558,11 +1613,8 @@ function reconcileKeyed(parent, endMarker, oldItems, newItems, mappedNodes, disp
558
1613
  lastOldIdx = oldIndices[i];
559
1614
  }
560
1615
  }
561
-
562
1616
  const inLIS = new Uint8Array(midNewLen);
563
-
564
1617
  if (alreadySorted) {
565
- // All reused items are in order — mark all as in LIS (no moves needed)
566
1618
  for (let i = 0; i < midNewLen; i++) {
567
1619
  if (oldIndices[i] !== -1) inLIS[i] = 1;
568
1620
  }
@@ -583,25 +1635,23 @@ function reconcileKeyed(parent, endMarker, oldItems, newItems, mappedNodes, disp
583
1635
  }
584
1636
  } else if (reusedCount === 1) {
585
1637
  for (let i = 0; i < midNewLen; i++) {
586
- if (oldIndices[i] !== -1) { inLIS[i] = 1; break; }
1638
+ if (oldIndices[i] !== -1) {
1639
+ inLIS[i] = 1;
1640
+ break;
1641
+ }
587
1642
  }
588
1643
  }
589
-
590
- // Position: work backwards, insert items not in LIS
591
- let nextSibling = newEnd + 1 < newMapped.length && newMapped[newEnd + 1]
592
- ? newMapped[newEnd + 1] : endMarker;
593
-
1644
+ let nextSibling = newEnd + 1 < newMapped.length && newMapped[newEnd + 1] ? newMapped[newEnd + 1] : endMarker;
594
1645
  for (let i = newEnd; i >= start; i--) {
595
1646
  const mi = i - start;
596
1647
  if (oldIndices[mi] === -1 || !inLIS[mi]) {
1648
+ if (nextSibling && nextSibling.parentNode !== parent) nextSibling = endMarker;
597
1649
  parent.insertBefore(newMapped[i], nextSibling);
598
1650
  }
599
1651
  nextSibling = newMapped[i];
600
1652
  }
601
-
602
1653
  _copyBack(mappedNodes, disposeFns, newMapped, newDispose, newLen);
603
1654
  }
604
-
605
1655
  function _copyBack(mappedNodes, disposeFns, newMapped, newDispose, newLen) {
606
1656
  mappedNodes.length = newLen;
607
1657
  disposeFns.length = newLen;
@@ -610,58 +1660,76 @@ function _copyBack(mappedNodes, disposeFns, newMapped, newDispose, newLen) {
610
1660
  disposeFns[i] = newDispose[i];
611
1661
  }
612
1662
  }
613
-
614
- // --- spread(el, props) ---
615
- // Fine-grained prop effects. Function props create individual effects.
616
- // Event props use direct assignment.
617
-
618
- export function spread(el, props) {
1663
+ function spread(el, props) {
619
1664
  for (const key in props) {
620
1665
  const value = props[key];
621
-
622
- if (key.startsWith('on') && key.length > 2) {
623
- // Event handler — direct assignment. Use $$name for delegated events.
1666
+ if (key.startsWith("on") && key.length > 2) {
624
1667
  const event = key.slice(2).toLowerCase();
625
1668
  el.addEventListener(event, value);
626
1669
  continue;
627
1670
  }
628
-
629
- if (typeof value === 'function' && !key.startsWith('on')) {
630
- // Reactive prop — create micro-effect
631
- if (key === 'class' || key === 'className') {
632
- effect(() => { el.className = value() || ''; });
633
- } else if (key === 'style' && typeof value() === 'object') {
1671
+ if (typeof value === "function" && !key.startsWith("on")) {
1672
+ if (key === "class" || key === "className") {
1673
+ effect(() => {
1674
+ el.className = value() || "";
1675
+ });
1676
+ } else if (key === "style" && typeof value() === "object") {
634
1677
  effect(() => {
635
1678
  const styles = value();
636
1679
  for (const prop in styles) {
637
- el.style[prop] = styles[prop] ?? '';
1680
+ el.style[prop] = styles[prop] ?? "";
638
1681
  }
639
1682
  });
640
1683
  } else {
641
- effect(() => { setPropDirect(el, key, value()); });
1684
+ effect(() => {
1685
+ setProp2(el, key, value());
1686
+ });
642
1687
  }
643
1688
  } else {
644
- // Static prop
645
- setPropDirect(el, key, value);
1689
+ setProp2(el, key, value);
646
1690
  }
647
1691
  }
648
1692
  }
649
-
650
- function setPropDirect(el, key, value) {
651
- if (key === 'class' || key === 'className') {
652
- el.className = value || '';
653
- } else if (key === 'style') {
654
- if (typeof value === 'string') {
1693
+ function setProp2(el, key, value) {
1694
+ if (key === "ref") {
1695
+ if (typeof value === "function") value(el);
1696
+ else if (value && typeof value === "object") value.current = el;
1697
+ return;
1698
+ }
1699
+ if (URL_ATTRS.has(key) || URL_ATTRS.has(key.toLowerCase())) {
1700
+ if (!isSafeUrl(value)) {
1701
+ if (typeof console !== "undefined") {
1702
+ console.warn(`[what] Blocked unsafe URL in "${key}" attribute: ${value}`);
1703
+ }
1704
+ return;
1705
+ }
1706
+ }
1707
+ if (key === "class" || key === "className") {
1708
+ el.className = value || "";
1709
+ } else if (key === "dangerouslySetInnerHTML") {
1710
+ el.innerHTML = value?.__html ?? "";
1711
+ } else if (key === "innerHTML") {
1712
+ if (value && typeof value === "object" && "__html" in value) {
1713
+ el.innerHTML = value.__html ?? "";
1714
+ } else {
1715
+ if (typeof console !== "undefined" && value != null && value !== "") {
1716
+ console.warn(
1717
+ '[what] Plain string innerHTML is not allowed. Use { __html: "..." } or dangerouslySetInnerHTML={{ __html: "..." }} instead.'
1718
+ );
1719
+ }
1720
+ }
1721
+ } else if (key === "style") {
1722
+ if (typeof value === "string") {
655
1723
  el.style.cssText = value;
656
- } else if (typeof value === 'object') {
1724
+ } else if (typeof value === "object") {
657
1725
  for (const prop in value) {
658
- el.style[prop] = value[prop] ?? '';
1726
+ el.style[prop] = value[prop] ?? "";
659
1727
  }
660
1728
  }
661
- } else if (key.startsWith('data-') || key.startsWith('aria-')) {
1729
+ } else if (key.startsWith("data-") || key.startsWith("aria-")) {
662
1730
  el.setAttribute(key, value);
663
- } else if (typeof value === 'boolean') {
664
- if (value) el.setAttribute(key, '');
1731
+ } else if (typeof value === "boolean") {
1732
+ if (value) el.setAttribute(key, "");
665
1733
  else el.removeAttribute(key);
666
1734
  } else if (key in el) {
667
1735
  el[key] = value;
@@ -669,24 +1737,14 @@ function setPropDirect(el, key, value) {
669
1737
  el.setAttribute(key, value);
670
1738
  }
671
1739
  }
672
-
673
- // --- delegateEvents(eventNames) ---
674
- // Event delegation: common events handled at document level.
675
- // Handlers stored as el.$$click, el.$$input, etc.
676
- // Single listener per event type on document — reduces listener count from N to 1.
677
-
678
- const delegatedEvents = new Set();
679
-
680
- export function delegateEvents(eventNames) {
1740
+ var delegatedEvents = /* @__PURE__ */ new Set();
1741
+ function delegateEvents(eventNames) {
681
1742
  for (const name of eventNames) {
682
1743
  if (delegatedEvents.has(name)) continue;
683
1744
  delegatedEvents.add(name);
684
-
685
1745
  document.addEventListener(name, (e) => {
686
1746
  let node = e.target;
687
- const key = '$$' + name;
688
-
689
- // Walk up the DOM tree looking for handlers
1747
+ const key = "$$" + name;
690
1748
  while (node) {
691
1749
  const handler = node[key];
692
1750
  if (handler) {
@@ -698,19 +1756,238 @@ export function delegateEvents(eventNames) {
698
1756
  });
699
1757
  }
700
1758
  }
701
-
702
- // --- addEventListener helper for non-delegated events ---
703
- export function on(el, event, handler) {
1759
+ function on(el, event, handler) {
704
1760
  el.addEventListener(event, handler);
705
1761
  return () => el.removeEventListener(event, handler);
706
1762
  }
707
-
708
- // --- className helper for conditional classes ---
709
- export function classList(el, classes) {
1763
+ function classList(el, classes) {
710
1764
  effect(() => {
711
1765
  for (const name in classes) {
712
- const value = typeof classes[name] === 'function' ? classes[name]() : classes[name];
1766
+ const value = typeof classes[name] === "function" ? classes[name]() : classes[name];
713
1767
  el.classList.toggle(name, !!value);
714
1768
  }
715
1769
  });
716
1770
  }
1771
+ var _isHydrating = false;
1772
+ var _hydrationCursor = null;
1773
+ function isHydrating() {
1774
+ return _isHydrating;
1775
+ }
1776
+ function hydrate(vnode, container) {
1777
+ _isHydrating = true;
1778
+ _hydrationCursor = { parent: container, index: 0 };
1779
+ try {
1780
+ const result = hydrateNode(vnode, container);
1781
+ return result;
1782
+ } finally {
1783
+ _isHydrating = false;
1784
+ _hydrationCursor = null;
1785
+ }
1786
+ }
1787
+ function claimNode(parent) {
1788
+ const children = parent.childNodes;
1789
+ while (_hydrationCursor.index < children.length) {
1790
+ const node = children[_hydrationCursor.index];
1791
+ if (node.nodeType === 8) {
1792
+ const text = node.textContent;
1793
+ if (text === "$" || text === "/$" || text === "[]" || text === "/[]") {
1794
+ _hydrationCursor.index++;
1795
+ continue;
1796
+ }
1797
+ }
1798
+ _hydrationCursor.index++;
1799
+ return node;
1800
+ }
1801
+ return null;
1802
+ }
1803
+ function isDevMode() {
1804
+ return typeof process !== "undefined" && true;
1805
+ }
1806
+ function hydrateNode(vnode, parent) {
1807
+ if (vnode == null || typeof vnode === "boolean") {
1808
+ return null;
1809
+ }
1810
+ if (typeof vnode === "string" || typeof vnode === "number") {
1811
+ const existing = claimNode(parent);
1812
+ const text = String(vnode);
1813
+ if (existing && existing.nodeType === 3) {
1814
+ if (isDevMode() && existing.textContent !== text) {
1815
+ console.warn(
1816
+ `[what] Hydration mismatch: expected text "${text}", got "${existing.textContent}"`
1817
+ );
1818
+ existing.textContent = text;
1819
+ }
1820
+ return existing;
1821
+ }
1822
+ if (isDevMode()) {
1823
+ console.warn(
1824
+ `[what] Hydration mismatch: expected text node "${text}", got ${existing ? existing.nodeName : "nothing"}. Falling back to client render.`
1825
+ );
1826
+ }
1827
+ const textNode2 = document.createTextNode(text);
1828
+ if (existing) {
1829
+ parent.replaceChild(textNode2, existing);
1830
+ } else {
1831
+ parent.appendChild(textNode2);
1832
+ }
1833
+ return textNode2;
1834
+ }
1835
+ if (typeof vnode === "function") {
1836
+ const initialValue = vnode();
1837
+ let current = hydrateNode(initialValue, parent);
1838
+ effect(() => {
1839
+ const value = vnode();
1840
+ if (!_isHydrating) {
1841
+ current = reconcileInsert(parent, value, current, null);
1842
+ }
1843
+ });
1844
+ return current;
1845
+ }
1846
+ if (Array.isArray(vnode)) {
1847
+ const nodes = [];
1848
+ for (const child of vnode) {
1849
+ const node = hydrateNode(child, parent);
1850
+ if (node) nodes.push(node);
1851
+ }
1852
+ return nodes.length === 1 ? nodes[0] : nodes;
1853
+ }
1854
+ if (typeof vnode === "object" && vnode._vnode) {
1855
+ if (typeof vnode.tag === "function") {
1856
+ const componentStack2 = getComponentStack();
1857
+ const Component = vnode.tag;
1858
+ const props = vnode.props || {};
1859
+ const children = vnode.children || [];
1860
+ const ctx = {
1861
+ hooks: [],
1862
+ hookIndex: 0,
1863
+ effects: [],
1864
+ cleanups: [],
1865
+ mounted: false,
1866
+ disposed: false,
1867
+ Component,
1868
+ _parentCtx: componentStack2[componentStack2.length - 1] || null,
1869
+ _errorBoundary: null
1870
+ };
1871
+ componentStack2.push(ctx);
1872
+ let result;
1873
+ try {
1874
+ const propsChildren = children.length === 0 ? void 0 : children.length === 1 ? children[0] : children;
1875
+ result = Component({ ...props, children: propsChildren });
1876
+ } catch (error) {
1877
+ componentStack2.pop();
1878
+ console.error("[what] Error in component during hydration:", Component.name || "Anonymous", error);
1879
+ return null;
1880
+ }
1881
+ componentStack2.pop();
1882
+ ctx.mounted = true;
1883
+ if (ctx._mountCallbacks) {
1884
+ queueMicrotask(() => {
1885
+ if (ctx.disposed) return;
1886
+ for (const fn of ctx._mountCallbacks) {
1887
+ try {
1888
+ fn();
1889
+ } catch (e) {
1890
+ console.error("[what] onMount error:", e);
1891
+ }
1892
+ }
1893
+ });
1894
+ }
1895
+ return hydrateNode(result, parent);
1896
+ }
1897
+ const existing = claimNode(parent);
1898
+ const expectedTag = vnode.tag.toUpperCase();
1899
+ if (existing && existing.nodeType === 1 && existing.nodeName === expectedTag) {
1900
+ hydrateElementProps(existing, vnode.props || {});
1901
+ const savedCursor = _hydrationCursor;
1902
+ _hydrationCursor = { parent: existing, index: 0 };
1903
+ const rawInner = vnode.props?.dangerouslySetInnerHTML?.__html;
1904
+ if (rawInner == null) {
1905
+ for (const child of vnode.children) {
1906
+ hydrateNode(child, existing);
1907
+ }
1908
+ }
1909
+ _hydrationCursor = savedCursor;
1910
+ return existing;
1911
+ }
1912
+ if (isDevMode()) {
1913
+ console.warn(
1914
+ `[what] Hydration mismatch: expected <${vnode.tag}>, got ${existing ? existing.nodeName : "nothing"}. Falling back to client render.`
1915
+ );
1916
+ }
1917
+ const newEl = document.createElement(vnode.tag);
1918
+ for (const key in vnode.props || {}) {
1919
+ if (key === "children" || key === "key") continue;
1920
+ setProp2(newEl, key, vnode.props[key]);
1921
+ }
1922
+ for (const child of vnode.children) {
1923
+ reconcileInsert(newEl, child, null, null);
1924
+ }
1925
+ if (existing) {
1926
+ parent.replaceChild(newEl, existing);
1927
+ } else {
1928
+ parent.appendChild(newEl);
1929
+ }
1930
+ return newEl;
1931
+ }
1932
+ if (isDomNode2(vnode)) {
1933
+ return vnode;
1934
+ }
1935
+ const textNode = document.createTextNode(String(vnode));
1936
+ parent.appendChild(textNode);
1937
+ return textNode;
1938
+ }
1939
+ function hydrateElementProps(el, props) {
1940
+ for (const key in props) {
1941
+ if (key === "children" || key === "key" || key === "ref") continue;
1942
+ if (key === "dangerouslySetInnerHTML" || key === "innerHTML") continue;
1943
+ const value = props[key];
1944
+ if (key.startsWith("on") && key.length > 2) {
1945
+ const event = key.slice(2).toLowerCase();
1946
+ el.addEventListener(event, value);
1947
+ continue;
1948
+ }
1949
+ if (key.startsWith("$$")) {
1950
+ el[key] = value;
1951
+ continue;
1952
+ }
1953
+ if (typeof value === "function" && !key.startsWith("on")) {
1954
+ if (key === "class" || key === "className") {
1955
+ effect(() => {
1956
+ el.className = value() || "";
1957
+ });
1958
+ } else if (key === "style" && typeof value() === "object") {
1959
+ effect(() => {
1960
+ const styles = value();
1961
+ for (const prop in styles) {
1962
+ el.style[prop] = styles[prop] ?? "";
1963
+ }
1964
+ });
1965
+ } else {
1966
+ effect(() => {
1967
+ setProp2(el, key, value());
1968
+ });
1969
+ }
1970
+ continue;
1971
+ }
1972
+ if (key === "data-hk") continue;
1973
+ }
1974
+ }
1975
+ export {
1976
+ _$createComponent,
1977
+ _$templateImpl as _$template,
1978
+ template as _template,
1979
+ classList,
1980
+ delegateEvents,
1981
+ effect,
1982
+ hydrate,
1983
+ insert,
1984
+ isHydrating,
1985
+ mapArray,
1986
+ on,
1987
+ setProp2 as setProp,
1988
+ spread,
1989
+ svgTemplate,
1990
+ template,
1991
+ untrack
1992
+ };
1993
+ //# sourceMappingURL=render.js.map