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