@3sln/dodo 0.0.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/src/vdom.js ADDED
@@ -0,0 +1,676 @@
1
+ const ELEMENT_NODE = Symbol('ELEMENT_NODE');
2
+ const ALIAS_NODE = Symbol('ALIAS_NODE');
3
+ const SPECIAL_NODE = Symbol('SPECIAL_NODE');
4
+ const OPAQUE_NODE = Symbol('OPAQUE_NODE');
5
+ const NODE_STATE = Symbol('NODE_STATE');
6
+ const EMPTY_OBJECT = Object.freeze({});
7
+
8
+ class VNode {
9
+ constructor(type, tag, args) {
10
+ this.type = type;
11
+ this.tag = tag;
12
+ this.args = args;
13
+ }
14
+
15
+ key(k) {
16
+ this.key = k;
17
+ return this;
18
+ }
19
+
20
+ on(hooks) {
21
+ this.hooks = hooks;
22
+ return this;
23
+ }
24
+
25
+ opaque() {
26
+ if (this.type !== ELEMENT_NODE) {
27
+ throw new Error('.opaque() can only be used on element nodes (h).');
28
+ }
29
+ this.type = OPAQUE_NODE;
30
+ return this;
31
+ }
32
+ }
33
+
34
+ function defaultShouldUpdate(a, b) {
35
+ if (a === b) return false;
36
+ if (a && b && typeof a === 'object' && typeof b === 'object') {
37
+ if (a.constructor !== b.constructor) return true;
38
+
39
+ if (Array.isArray(a)) {
40
+ if (a.length !== b.length) return true;
41
+ for (let j = 0; j < a.length; j++) {
42
+ if (a[j] !== b[j]) return true;
43
+ }
44
+ return false;
45
+ }
46
+
47
+ if (a.constructor === Object) {
48
+ const keysA = Object.keys(a);
49
+ if (keysA.length !== Object.keys(b).length) return true;
50
+ for (const key of keysA) {
51
+ if (!b.hasOwnProperty(key) || a[key] !== b[key]) return true;
52
+ }
53
+ return false;
54
+ }
55
+ }
56
+ return true;
57
+ }
58
+
59
+ function isIterable(x) {
60
+ return Array.isArray(x) || (x != null && typeof x[Symbol.iterator] === 'function' && typeof x !== 'string');
61
+ }
62
+
63
+ function alias(f) {
64
+ return (...args) => new VNode(ALIAS_NODE, f, args);
65
+ }
66
+
67
+ function special(o) {
68
+ return (...args) => new VNode(SPECIAL_NODE, o, args);
69
+ }
70
+
71
+ function newElementNamespace(parentNode, newNodeTag) {
72
+ if (parentNode.namespaceURI === 'http://www.w3.org/1999/xhtml') {
73
+ switch (newNodeTag) {
74
+ case 'svg': return 'http://www.w3.org/2000/svg';
75
+ case 'math': return 'http://www.w3.org/1998/Math/MathML';
76
+ }
77
+ }
78
+ return parentNode.namespaceURI ?? parentNode.host?.namespaceURI ?? 'http://www.w3.org/1999/xhtml';
79
+ }
80
+
81
+ function createElementNode(parentNode, tag, vdom) {
82
+ const el = parentNode.ownerDocument.createElementNS(newElementNamespace(parentNode, tag), tag);
83
+ el[NODE_STATE] = { originalProps: {}, newVdom: vdom };
84
+ return el;
85
+ }
86
+
87
+ const documentToFocusWithinSet = new WeakMap();
88
+
89
+ function getPathFromElement(element) {
90
+ const path = [];
91
+ let current = element;
92
+ while (current) {
93
+ path.push(current);
94
+ if (current.shadowRoot) {
95
+ current = current.shadowRoot.host;
96
+ } else {
97
+ current = current.parentNode;
98
+ }
99
+ }
100
+ return path;
101
+ }
102
+
103
+ function addPathToFocusWithinSet(set, path) {
104
+ for (const node of path) {
105
+ set.add(node);
106
+ }
107
+ }
108
+
109
+ function removePathFromFocusWithinSet(set, newPath) {
110
+ const newPathSet = new Set(newPath);
111
+ for (const node of [...set]) {
112
+ if (!newPathSet.has(node)) {
113
+ set.delete(node);
114
+ }
115
+ }
116
+ }
117
+
118
+ function installFocusTrackingForDocument(doc) {
119
+ const focusWithinSet = new Set();
120
+ doc.addEventListener('focusin', (event) => {
121
+ addPathToFocusWithinSet(focusWithinSet, event.composedPath());
122
+ });
123
+ doc.addEventListener('focusout', (event) => {
124
+ const newPath = event.relatedTarget ? event.composedPath() : [];
125
+ removePathFromFocusWithinSet(focusWithinSet, newPath);
126
+ });
127
+ addPathToFocusWithinSet(focusWithinSet, getPathFromElement(doc.activeElement));
128
+ documentToFocusWithinSet.set(doc, focusWithinSet);
129
+ return focusWithinSet;
130
+ }
131
+
132
+ export default (userSettings) => {
133
+ const shouldUpdate = userSettings?.shouldUpdate ?? defaultShouldUpdate;
134
+ const isMap = userSettings?.isMap ?? ((x) => x?.constructor === Object);
135
+ const mapIter = userSettings?.mapIter ?? ((m) => Object.entries(m));
136
+ const mapGet = userSettings?.mapGet ?? ((m, k) => m[k]);
137
+ const mapMerge = userSettings?.mapMerge ?? ((...maps) => Object.assign({}, ...maps));
138
+ const newMap = userSettings?.newMap ?? ((obj) => ({...obj}));
139
+ const mapPut = userSettings?.mapPut ?? ((m, k, v) => { m[k] = v; return m; });
140
+ const isSeq = userSettings?.isSeq ?? isIterable;
141
+ const seqIter = userSettings?.seqIter ?? ((s) => s);
142
+ const convertTagName = userSettings?.convertTagName ?? ((t) => t);
143
+ const convertPropName = userSettings?.convertPropName ?? ((p) => p);
144
+ const convertStyleName = userSettings?.convertStyleName ?? ((s) => s);
145
+ const convertDataName = userSettings?.convertDataName ?? ((d) => d);
146
+ const convertClassName = userSettings?.convertClassName ?? ((c) => c);
147
+ const listenerKey = userSettings?.listenerKey ?? 'listener';
148
+ const captureKey = userSettings?.captureKey ?? 'capture';
149
+ const passiveKey = userSettings?.passiveKey ?? 'passive';
150
+
151
+ function flattenSeqIntoArray(array, items, excludeFalsey) {
152
+ for (const item of seqIter(items)) {
153
+ if (excludeFalsey && (item == null || item == false)) {
154
+ continue;
155
+ }
156
+
157
+ if (!isSeq(item)) {
158
+ array.push(item);
159
+ } else {
160
+ flattenSeqIntoArray(array, item);
161
+ }
162
+ }
163
+ }
164
+
165
+ function defaultFlattenSeq(items, excludeFalsey) {
166
+ const array = [];
167
+ flattenSeqIntoArray(array, items, excludeFalsey);
168
+ return array;
169
+ }
170
+
171
+ const flattenSeq = userSettings?.flattenSeq ?? defaultFlattenSeq;
172
+
173
+ function flattenVNodeChildrenIntoArray(array, children) {
174
+ for (const item of children) {
175
+ if (item === null || item === undefined || item === false) continue;
176
+ if (!isSeq(item)) {
177
+ array.push(item);
178
+ } else {
179
+ flattenVNodeChildrenIntoArray(array, seqIter(item));
180
+ }
181
+ }
182
+ }
183
+ function flattenVNodeChildren(children) {
184
+ const array = [];
185
+ flattenVNodeChildrenIntoArray(array, children);
186
+ return array;
187
+ }
188
+
189
+ function h(tag, props, ...children) {
190
+ if (!isMap(props)) {
191
+ children.unshift(props);
192
+ props = EMPTY_OBJECT;
193
+ }
194
+ return new VNode(ELEMENT_NODE, convertTagName(tag), [props ?? EMPTY_OBJECT, ...children]);
195
+ }
196
+
197
+ function reconcileElementStyling(target, oldStyling, newStyling) {
198
+ const style = target.style;
199
+ for (const [name, value] of mapIter(newStyling)) {
200
+ style.setProperty(convertStyleName(name), value);
201
+ }
202
+ for (const [name] of mapIter(oldStyling)) {
203
+ if (mapGet(newStyling, name) !== undefined) continue;
204
+ style.removeProperty(convertStyleName(name));
205
+ }
206
+ }
207
+
208
+ function reconcileElementAttributes(target, oldAttrs, newAttrs) {
209
+ for (const [name, value] of mapIter(newAttrs)) {
210
+ target.setAttribute(convertPropName(name), value);
211
+ }
212
+ for (const [name] of mapIter(oldAttrs)) {
213
+ if (mapGet(newAttrs, name) !== undefined) continue;
214
+ target.removeAttribute(convertPropName(name));
215
+ }
216
+ }
217
+
218
+ function reconcileElementDataset(target, oldDataset, newDataset) {
219
+ for (const [name, value] of mapIter(newDataset)) {
220
+ target.dataset[convertDataName(name)] = value;
221
+ }
222
+ for (const [name] of mapIter(oldDataset)) {
223
+ if (mapGet(newDataset, name) !== undefined) continue;
224
+ delete target.dataset[convertDataName(name)];
225
+ }
226
+ }
227
+
228
+ function reconcileElementClasses(target, oldClasses, newClasses) {
229
+ const classesToRemove = new Set(flattenSeq(oldClasses, true));
230
+ for (const c of flattenSeq(newClasses, true)) {
231
+ const className = convertClassName(c);
232
+ classesToRemove.delete(className);
233
+ target.classList.add(className);
234
+ }
235
+ for (const c of classesToRemove) {
236
+ target.classList.remove(convertClassName(c));
237
+ }
238
+ }
239
+
240
+ function reconcileElementProps(target, props) {
241
+ const nodeState = target[NODE_STATE];
242
+ const isHtml = target.namespaceURI === 'http://www.w3.org/1999/xhtml';
243
+ const oldProps = nodeState.vdom?.args[0] ?? EMPTY_OBJECT;
244
+
245
+ // Handle new and changed props
246
+ for (const [name, newValue] of mapIter(props)) {
247
+ const propName = convertPropName(name);
248
+ const oldValue = mapGet(oldProps, name);
249
+
250
+ if (Object.is(newValue, oldValue)) continue;
251
+
252
+ switch (propName) {
253
+ case '$styling': {
254
+ if (!isMap(newValue)) throw new Error('invalid value for styling prop');
255
+ reconcileElementStyling(target, oldValue ?? EMPTY_OBJECT, newValue ?? EMPTY_OBJECT);
256
+ break;
257
+ }
258
+ case '$classes': {
259
+ if (!isSeq(newValue)) throw new Error('invalid value for classes prop');
260
+ reconcileElementClasses(target, oldValue ?? [], newValue ?? []);
261
+ break;
262
+ }
263
+ case '$attrs': {
264
+ if (!isMap(newValue)) throw new Error('invalid value for attrs prop');
265
+ reconcileElementAttributes(target, oldValue ?? EMPTY_OBJECT, newValue ?? EMPTY_OBJECT);
266
+ break;
267
+ }
268
+ case '$dataset': {
269
+ if (!isMap(newValue)) throw new Error('invalid value for dataset prop');
270
+ reconcileElementDataset(target, oldValue ?? EMPTY_OBJECT, newValue ?? EMPTY_OBJECT);
271
+ break;
272
+ }
273
+ default: {
274
+ if (isHtml) {
275
+ const originalProps = nodeState.originalProps;
276
+ if (!(propName in originalProps)) {
277
+ originalProps[propName] = target[propName];
278
+ }
279
+ if (newValue === undefined) {
280
+ target[propName] = originalProps[propName];
281
+ } else {
282
+ target[propName] = newValue;
283
+ }
284
+ } else {
285
+ if (newValue === undefined) {
286
+ target.removeAttribute(propName);
287
+ } else {
288
+ target.setAttribute(propName, newValue);
289
+ }
290
+ }
291
+ break;
292
+ }
293
+ }
294
+ }
295
+
296
+ // Handle removed props
297
+ for (const [name, oldValue] of mapIter(oldProps)) {
298
+ if (mapGet(props, name) !== undefined) continue; // it wasn't removed
299
+
300
+ const propName = convertPropName(name);
301
+ switch (propName) {
302
+ case '$styling':
303
+ reconcileElementStyling(target, oldValue ?? EMPTY_OBJECT, EMPTY_OBJECT);
304
+ break;
305
+ case '$classes':
306
+ reconcileElementClasses(target, oldValue ?? [], []);
307
+ break;
308
+ case '$attrs':
309
+ reconcileElementAttributes(target, oldValue ?? EMPTY_OBJECT, EMPTY_OBJECT);
310
+ break;
311
+ case '$dataset':
312
+ reconcileElementDataset(target, oldValue ?? EMPTY_OBJECT, EMPTY_OBJECT);
313
+ break;
314
+ default: {
315
+ if (isHtml) {
316
+ const originalProps = nodeState.originalProps;
317
+ if (propName in originalProps) {
318
+ target[propName] = originalProps[propName];
319
+ delete originalProps[propName];
320
+ }
321
+ } else {
322
+ target.removeAttribute(propName);
323
+ }
324
+ break;
325
+ }
326
+ }
327
+ }
328
+ }
329
+
330
+ function reconcileListeners(target, hooks) {
331
+ const state = target[NODE_STATE];
332
+ if (!state.vdom) {
333
+ for (const [name, listener] of mapIter(hooks)) {
334
+ if (name[0] === '$') continue;
335
+ if (typeof listener === 'function') {
336
+ target.addEventListener(name, listener);
337
+ } else if (listener != null) {
338
+ target.addEventListener(name, mapGet(listener, listenerKey), {
339
+ capture: !!mapGet(listener, captureKey),
340
+ passive: !!mapGet(listener, passiveKey)
341
+ });
342
+ }
343
+ }
344
+ } else {
345
+ const oldHooks = state.vdom.hooks ?? EMPTY_OBJECT;
346
+ for (const [name, listener] of mapIter(hooks)) {
347
+ if (name[0] === '$') continue;
348
+ const oldListener = mapGet(oldHooks, name);
349
+ if (listener === oldListener) continue;
350
+
351
+ if (typeof oldListener === 'function') {
352
+ target.removeEventListener(name, oldListener);
353
+ } else if (oldListener != null) {
354
+ target.removeEventListener(name, mapGet(oldListener, listenerKey), !!mapGet(oldListener, captureKey));
355
+ }
356
+
357
+ if (typeof listener === 'function') {
358
+ target.addEventListener(name, listener);
359
+ } else if (listener != null) {
360
+ target.addEventListener(name, mapGet(listener, listenerKey), {
361
+ capture: !!mapGet(listener, captureKey),
362
+ passive: !!mapGet(listener, passiveKey)
363
+ });
364
+ }
365
+ }
366
+ for (const [name] of mapIter(oldHooks)) {
367
+ if (name[0] === '$' || mapGet(hooks, name) !== undefined) continue;
368
+ const oldListener = mapGet(oldHooks, name);
369
+ if (typeof oldListener === 'function') {
370
+ target.removeEventListener(name, oldListener);
371
+ } else if (oldListener != null) {
372
+ target.removeEventListener(name, mapGet(oldListener, listenerKey), !!mapGet(oldListener, captureKey));
373
+ }
374
+ }
375
+ }
376
+ }
377
+
378
+ function reconcileNode(target) {
379
+ const state = target[NODE_STATE];
380
+ const newVdom = state.newVdom;
381
+ const oldVdom = state.vdom;
382
+ const args = newVdom.args;
383
+
384
+ switch (newVdom.type) {
385
+ case ELEMENT_NODE: {
386
+ reconcileElementProps(target, args[0]);
387
+ reconcileElementChildren(target, args.slice(1));
388
+ break;
389
+ }
390
+ case OPAQUE_NODE: {
391
+ reconcileElementProps(target, args[0]);
392
+ break;
393
+ }
394
+ case ALIAS_NODE: {
395
+ const innerVdom = newVdom.tag.apply(undefined, newVdom.args);
396
+ if (innerVdom === undefined || innerVdom === null) break;
397
+ if (isSeq(innerVdom)) {
398
+ reconcileElementChildren(target, innerVdom);
399
+ } else {
400
+ reconcileElementChildren(target, [innerVdom]);
401
+ }
402
+ break;
403
+ }
404
+ case SPECIAL_NODE: {
405
+ try {
406
+ newVdom.tag.update?.(target, newVdom.args, oldVdom?.args);
407
+ } catch (err) {
408
+ console.error(err);
409
+ }
410
+ break;
411
+ }
412
+ }
413
+
414
+ if (newVdom.hooks || oldVdom?.hooks) {
415
+ reconcileListeners(target, newVdom.hooks ?? EMPTY_OBJECT);
416
+ }
417
+ try {
418
+ newVdom.hooks?.$update?.(target, newVdom, oldVdom);
419
+ } catch (err) {
420
+ console.error(err);
421
+ }
422
+ state.vdom = newVdom;
423
+ delete state.newVdom;
424
+ }
425
+
426
+ function createNode(parentNode, vdom) {
427
+ if (typeof vdom !== 'object' || vdom === null) {
428
+ return parentNode.ownerDocument.createTextNode(vdom.toString());
429
+ }
430
+
431
+ let domNode;
432
+ switch (vdom.type) {
433
+ case ELEMENT_NODE:
434
+ case OPAQUE_NODE:
435
+ domNode = createElementNode(parentNode, vdom.tag, vdom);
436
+ break;
437
+ case ALIAS_NODE:
438
+ domNode = createElementNode(parentNode, 'udom-alias', vdom);
439
+ domNode.style.display = 'contents';
440
+ break;
441
+ case SPECIAL_NODE:
442
+ domNode = createElementNode(parentNode, 'udom-special', vdom);
443
+ domNode.style.display = 'contents';
444
+ break;
445
+ default:
446
+ throw new Error('Invalid VDOM node');
447
+ }
448
+ return domNode;
449
+ }
450
+
451
+ function cleanupTarget(target) {
452
+ if (target.children) {
453
+ for (const child of target.children) {
454
+ cleanupTarget(child);
455
+ }
456
+ }
457
+
458
+ const state = target[NODE_STATE];
459
+ if (state) {
460
+ switch (state.vdom.type) {
461
+ case ELEMENT_NODE:
462
+ case OPAQUE_NODE:
463
+ reconcileElementProps(target, {});
464
+ break;
465
+ case SPECIAL_NODE:
466
+ try {
467
+ state.vdom.tag.detach?.(target);
468
+ } catch (err) {
469
+ console.error(err);
470
+ }
471
+ break;
472
+ }
473
+ try {
474
+ state.vdom.hooks?.$detach?.(target);
475
+ } catch (err) {
476
+ console.error(err);
477
+ }
478
+ delete target[NODE_STATE];
479
+ }
480
+ }
481
+
482
+ function reconcileElementChildren(target, childrenIterable) {
483
+ const newChildren = flattenVNodeChildren(childrenIterable);
484
+ const oldNodesToRemove = new Set(target.childNodes);
485
+ const oldVNodeNodesPool = new Map();
486
+ const oldTextNodesPool = [];
487
+
488
+ for (const oldChild of target.childNodes) {
489
+ if (oldChild.nodeType === 3 /* TEXT_NODE */) {
490
+ oldTextNodesPool.push(oldChild);
491
+ continue;
492
+ }
493
+ const vdom = oldChild[NODE_STATE]?.vdom;
494
+ if (vdom === undefined) {
495
+ continue;
496
+ }
497
+
498
+ let oldNodesPoolForTag = oldVNodeNodesPool.get(vdom.tag);
499
+ if (!oldNodesPoolForTag) {
500
+ oldNodesPoolForTag = { nodesForKey: new Map(), nodesWithoutKey: [] };
501
+ oldVNodeNodesPool.set(vdom.tag, oldNodesPoolForTag);
502
+ }
503
+
504
+ if (vdom.key !== undefined) {
505
+ let oldNodesPoolForKey = oldNodesPoolForTag.nodesForKey.get(vdom.key);
506
+ if (oldNodesPoolForKey === undefined) {
507
+ oldNodesPoolForKey = [];
508
+ oldNodesPoolForTag.nodesForKey.set(vdom.key, oldNodesPoolForKey);
509
+ }
510
+ oldNodesPoolForKey.push(oldChild);
511
+ } else {
512
+ oldNodesPoolForTag.nodesWithoutKey.push(oldChild);
513
+ }
514
+ }
515
+
516
+ const newDomChildren = [];
517
+ for (const newVdom of newChildren) {
518
+ let newDomNode;
519
+ if (newVdom instanceof VNode) {
520
+ const oldNodesPoolForTag = oldVNodeNodesPool.get(newVdom.tag);
521
+ if (!oldNodesPoolForTag) {
522
+ newDomNode = createNode(target, newVdom);
523
+ } else {
524
+ const key = newVdom.key;
525
+ if (key !== undefined) {
526
+ const pool = oldNodesPoolForTag.nodesForKey.get(key);
527
+ if (pool && pool.length > 0) {
528
+ newDomNode = pool.shift();
529
+ const state = newDomNode[NODE_STATE];
530
+ if (shouldUpdate(state.vdom.args, newVdom.args)) {
531
+ state.newVdom = newVdom;
532
+ }
533
+ } else {
534
+ newDomNode = createNode(target, newVdom);
535
+ }
536
+ } else {
537
+ const unkeyedOldNode = oldNodesPoolForTag.nodesWithoutKey.shift();
538
+ if (unkeyedOldNode) {
539
+ newDomNode = unkeyedOldNode;
540
+ const state = newDomNode[NODE_STATE];
541
+ if (shouldUpdate(state.vdom.args, newVdom.args)) {
542
+ state.newVdom = newVdom;
543
+ }
544
+ } else {
545
+ newDomNode = createNode(target, newVdom);
546
+ }
547
+ }
548
+ }
549
+ } else {
550
+ if (oldTextNodesPool.length > 0) {
551
+ newDomNode = oldTextNodesPool.shift();
552
+ newDomNode.nodeValue = newVdom?.toString();
553
+ } else {
554
+ newDomNode = target.ownerDocument.createTextNode(newVdom?.toString());
555
+ }
556
+ }
557
+ oldNodesToRemove.delete(newDomNode);
558
+ newDomChildren.push(newDomNode);
559
+ }
560
+
561
+ for (const nodeToRemove of oldNodesToRemove) {
562
+ cleanupTarget(nodeToRemove);
563
+ target.removeChild(nodeToRemove);
564
+ }
565
+
566
+ const moveBefore = window.Element.prototype.moveBefore;
567
+ const insertBefore = window.Element.prototype.insertBefore;
568
+ if (target.isConnected && typeof moveBefore === 'function') {
569
+ for (let i = 0; i < newDomChildren.length; i++) {
570
+ const newChild = newDomChildren[i];
571
+ const existingChildAtPosition = target.childNodes[i];
572
+ if (newChild !== existingChildAtPosition) {
573
+ (newChild.isConnected ? moveBefore : insertBefore).call(target, newChild, existingChildAtPosition);
574
+ }
575
+ const state = newChild[NODE_STATE];
576
+ if (state?.newVdom) {
577
+ if (!state.vdom) {
578
+ try {
579
+ state.newVdom.hooks?.$attach?.(newChild);
580
+ if (state.newVdom.type === SPECIAL_NODE) {
581
+ state.newVdom.tag.attach?.(newChild);
582
+ }
583
+ } catch (err) {
584
+ console.error(err);
585
+ }
586
+ }
587
+ reconcileNode(newChild);
588
+ }
589
+ }
590
+ } else {
591
+ const doc = target.ownerDocument;
592
+ let focusWithin = documentToFocusWithinSet.get(doc);
593
+ if (!focusWithin) {
594
+ focusWithin = installFocusTrackingForDocument(doc);
595
+ }
596
+ for (let i = 0; i < newDomChildren.length; i++) {
597
+ const newChild = newDomChildren[i];
598
+ const existingChildAtPosition = target.childNodes[i];
599
+ if (newChild !== existingChildAtPosition) {
600
+ if (!focusWithin.has(newChild)) {
601
+ insertBefore.call(target, newChild, existingChildAtPosition);
602
+ }
603
+ }
604
+ const state = newChild[NODE_STATE];
605
+ if (state?.newVdom) {
606
+ if (!state.vdom) {
607
+ try {
608
+ state.newVdom.hooks?.$attach?.(newChild);
609
+ if (state.newVdom.type === SPECIAL_NODE) {
610
+ state.newVdom.tag.attach?.(newChild);
611
+ }
612
+ } catch (err) {
613
+ console.error(err);
614
+ }
615
+ }
616
+ reconcileNode(newChild);
617
+ }
618
+ }
619
+ }
620
+ }
621
+
622
+ function reconcile(target, vdom) {
623
+ if (vdom === null || vdom === undefined) {
624
+ cleanupTarget(target);
625
+ return;
626
+ }
627
+
628
+ if (isSeq(vdom)) {
629
+ reconcileElementChildren(target, seqIter(vdom));
630
+ return;
631
+ }
632
+
633
+ if (vdom instanceof VNode) {
634
+ let state = target[NODE_STATE];
635
+ if (state) {
636
+ if (state.vdom.type === vdom.type) {
637
+ if (shouldUpdate(state.vdom.args, vdom.args)) {
638
+ state.newVdom = vdom;
639
+ reconcileNode(target);
640
+ }
641
+ return;
642
+ }
643
+ cleanupTarget(target);
644
+ }
645
+
646
+ switch (vdom.type) {
647
+ case ELEMENT_NODE:
648
+ case OPAQUE_NODE:
649
+ if (0 !== target.nodeName.localeCompare(convertTagName(vdom.tag), undefined, { sensitivity: 'base' })) {
650
+ throw new Error('incompatible target for vdom');
651
+ }
652
+ break;
653
+ }
654
+
655
+ target[NODE_STATE] = state = { originalProps: {}, newVdom: vdom };
656
+ try {
657
+ vdom.hooks?.$attach?.(target);
658
+ if (vdom.type === SPECIAL_NODE) {
659
+ vdom.tag.attach?.(target);
660
+ }
661
+ } catch (err) {
662
+ console.error(err);
663
+ }
664
+ reconcileNode(target);
665
+ return;
666
+ }
667
+
668
+ throw new Error('invalid vdom');
669
+ }
670
+
671
+ return { h, alias, special, reconcile, settings: {
672
+ shouldUpdate, isMap, mapIter, mapGet, mapMerge, newMap, mapPut, isSeq, flattenSeq, seqIter,
673
+ convertTagName, convertPropName, convertStyleName, convertDataName, convertClassName,
674
+ listenerKey, captureKey, passiveKey
675
+ } };
676
+ }