@3sln/dodo 0.0.2 → 0.0.4
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 +39 -21
- package/index.js +3 -3
- package/package.json +6 -2
- package/src/html.js +11 -11
- package/src/scheduler.js +48 -48
- package/src/vdom.js +234 -154
package/dodo.js
CHANGED
|
@@ -39,22 +39,25 @@ function defaultFlattenSeq(items) {
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
function isIterable(x) {
|
|
42
|
-
return
|
|
42
|
+
return (
|
|
43
|
+
Array.isArray(x) ||
|
|
44
|
+
(x != null && typeof x[Symbol.iterator] === 'function' && typeof x !== 'string')
|
|
45
|
+
);
|
|
43
46
|
}
|
|
44
47
|
|
|
45
48
|
export const settings = {
|
|
46
49
|
shouldUpdate: defaultShouldUpdate,
|
|
47
|
-
isMap:
|
|
48
|
-
mapIter:
|
|
50
|
+
isMap: x => x?.constructor === Object,
|
|
51
|
+
mapIter: m => Object.entries(m),
|
|
49
52
|
mapGet: (m, k) => m[k],
|
|
50
53
|
isSeq: isIterable,
|
|
51
54
|
flattenSeq: defaultFlattenSeq,
|
|
52
|
-
seqIter:
|
|
53
|
-
convertTagName:
|
|
54
|
-
convertPropName:
|
|
55
|
-
convertStyleName:
|
|
56
|
-
convertDataName:
|
|
57
|
-
convertClassName:
|
|
55
|
+
seqIter: s => s,
|
|
56
|
+
convertTagName: t => t,
|
|
57
|
+
convertPropName: p => p,
|
|
58
|
+
convertStyleName: s => s,
|
|
59
|
+
convertDataName: d => d,
|
|
60
|
+
convertClassName: c => c,
|
|
58
61
|
};
|
|
59
62
|
|
|
60
63
|
function flattenVNodeChildrenIntoArray(array, children) {
|
|
@@ -107,7 +110,10 @@ export function h(tag, props, ...children) {
|
|
|
107
110
|
children.unshift(props);
|
|
108
111
|
props = EMPTY_OBJECT;
|
|
109
112
|
}
|
|
110
|
-
return new VNode(ELEMENT_NODE, settings.convertTagName(tag), [
|
|
113
|
+
return new VNode(ELEMENT_NODE, settings.convertTagName(tag), [
|
|
114
|
+
props ?? EMPTY_OBJECT,
|
|
115
|
+
...children,
|
|
116
|
+
]);
|
|
111
117
|
}
|
|
112
118
|
|
|
113
119
|
export function o(tag, props) {
|
|
@@ -293,7 +299,10 @@ function reconcileListeners(target, hooks) {
|
|
|
293
299
|
target.addEventListener(name, listener);
|
|
294
300
|
break;
|
|
295
301
|
case 'object':
|
|
296
|
-
target.addEventListener(name, listener.listener, {
|
|
302
|
+
target.addEventListener(name, listener.listener, {
|
|
303
|
+
capture: listener.capture,
|
|
304
|
+
passive: listener.passive,
|
|
305
|
+
});
|
|
297
306
|
break;
|
|
298
307
|
}
|
|
299
308
|
}
|
|
@@ -321,7 +330,10 @@ function reconcileListeners(target, hooks) {
|
|
|
321
330
|
target.addEventListener(name, listener);
|
|
322
331
|
break;
|
|
323
332
|
case 'object':
|
|
324
|
-
target.addEventListener(name, listener.listener, {
|
|
333
|
+
target.addEventListener(name, listener.listener, {
|
|
334
|
+
capture: listener.capture,
|
|
335
|
+
passive: listener.passive,
|
|
336
|
+
});
|
|
325
337
|
break;
|
|
326
338
|
}
|
|
327
339
|
}
|
|
@@ -345,8 +357,10 @@ function reconcileListeners(target, hooks) {
|
|
|
345
357
|
function newElementNamespace(parentNode, newNodeTag) {
|
|
346
358
|
if (parentNode.namespaceURI === 'http://www.w3.org/1999/xhtml') {
|
|
347
359
|
switch (newNodeTag) {
|
|
348
|
-
case 'svg':
|
|
349
|
-
|
|
360
|
+
case 'svg':
|
|
361
|
+
return 'http://www.w3.org/2000/svg';
|
|
362
|
+
case 'math':
|
|
363
|
+
return 'http://www.w3.org/1998/Math/MathML';
|
|
350
364
|
}
|
|
351
365
|
}
|
|
352
366
|
return parentNode.namespaceURI ?? parentNode.host?.namespaceURI ?? 'http://www.w3.org/1999/xhtml';
|
|
@@ -404,7 +418,7 @@ function reconcileNode(target) {
|
|
|
404
418
|
|
|
405
419
|
function createElementNode(parentNode, tag, vdom) {
|
|
406
420
|
const el = parentNode.ownerDocument.createElementNS(newElementNamespace(parentNode, tag), tag);
|
|
407
|
-
el[NODE_STATE] = {
|
|
421
|
+
el[NODE_STATE] = {originalProps: {}, newVdom: vdom};
|
|
408
422
|
return el;
|
|
409
423
|
}
|
|
410
424
|
|
|
@@ -466,10 +480,10 @@ function removePathFromFocusWithinSet(set, newPath) {
|
|
|
466
480
|
|
|
467
481
|
function installFocusTrackingForDocument(doc) {
|
|
468
482
|
const focusWithinSet = new Set();
|
|
469
|
-
doc.addEventListener('focusin',
|
|
483
|
+
doc.addEventListener('focusin', event => {
|
|
470
484
|
addPathToFocusWithinSet(focusWithinSet, event.composedPath());
|
|
471
485
|
});
|
|
472
|
-
doc.addEventListener('focusout',
|
|
486
|
+
doc.addEventListener('focusout', event => {
|
|
473
487
|
const newPath = event.relatedTarget ? event.composedPath() : [];
|
|
474
488
|
removePathFromFocusWithinSet(focusWithinSet, newPath);
|
|
475
489
|
});
|
|
@@ -524,7 +538,7 @@ function reconcileElementChildren(target, childrenIterable) {
|
|
|
524
538
|
|
|
525
539
|
let oldNodesPoolForTag = oldVNodeNodesPool.get(vdom.tag);
|
|
526
540
|
if (!oldNodesPoolForTag) {
|
|
527
|
-
oldNodesPoolForTag = {
|
|
541
|
+
oldNodesPoolForTag = {nodesForKey: new Map(), nodesWithoutKey: []};
|
|
528
542
|
oldVNodeNodesPool.set(vdom.tag, oldNodesPoolForTag);
|
|
529
543
|
}
|
|
530
544
|
|
|
@@ -597,7 +611,11 @@ function reconcileElementChildren(target, childrenIterable) {
|
|
|
597
611
|
const newChild = newDomChildren[i];
|
|
598
612
|
const existingChildAtPosition = target.childNodes[i];
|
|
599
613
|
if (newChild !== existingChildAtPosition) {
|
|
600
|
-
(newChild.isConnected ? moveBefore : insertBefore).call(
|
|
614
|
+
(newChild.isConnected ? moveBefore : insertBefore).call(
|
|
615
|
+
target,
|
|
616
|
+
newChild,
|
|
617
|
+
existingChildAtPosition,
|
|
618
|
+
);
|
|
601
619
|
}
|
|
602
620
|
const state = newChild[NODE_STATE];
|
|
603
621
|
if (state?.newVdom) {
|
|
@@ -673,13 +691,13 @@ export function reconcile(target, vdom) {
|
|
|
673
691
|
switch (vdom.type) {
|
|
674
692
|
case ELEMENT_NODE:
|
|
675
693
|
case OPAQUE_NODE:
|
|
676
|
-
if (0 !== target.nodeName.localeCompare(vdom.tag, undefined, {
|
|
694
|
+
if (0 !== target.nodeName.localeCompare(vdom.tag, undefined, {sensitivity: 'base'})) {
|
|
677
695
|
throw new Error('incompatible target for vdom');
|
|
678
696
|
}
|
|
679
697
|
break;
|
|
680
698
|
}
|
|
681
699
|
|
|
682
|
-
target[NODE_STATE] = state = {
|
|
700
|
+
target[NODE_STATE] = state = {originalProps: {}, newVdom: vdom};
|
|
683
701
|
try {
|
|
684
702
|
vdom.hooks?.$attach?.(target);
|
|
685
703
|
if (vdom.type === SPECIAL_NODE) {
|
package/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import vdom from './src/vdom.js';
|
|
2
2
|
|
|
3
3
|
// Create a default API instance by calling the factory with no settings.
|
|
4
|
-
const {
|
|
4
|
+
const {h, alias, special, reconcile, settings} = vdom();
|
|
5
5
|
|
|
6
|
-
export {
|
|
6
|
+
export {vdom, h, alias, special, reconcile, settings};
|
|
7
7
|
|
|
8
8
|
// Export all the HTML helpers.
|
|
9
9
|
export * from './src/html.js';
|
|
10
|
-
export {
|
|
10
|
+
export {schedule, flush, clear} from './src/scheduler.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@3sln/dodo",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.4",
|
|
4
4
|
"description": "A minimal, configurable virtual DOM library.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -23,9 +23,13 @@
|
|
|
23
23
|
],
|
|
24
24
|
"devDependencies": {
|
|
25
25
|
"@types/bun": "latest",
|
|
26
|
-
"happy-dom": "^18.0.1"
|
|
26
|
+
"happy-dom": "^18.0.1",
|
|
27
|
+
"prettier": "^3.6.2"
|
|
27
28
|
},
|
|
28
29
|
"publishConfig": {
|
|
29
30
|
"access": "public"
|
|
31
|
+
},
|
|
32
|
+
"scripts": {
|
|
33
|
+
"format": "prettier -w \"**/*.js\""
|
|
30
34
|
}
|
|
31
35
|
}
|
package/src/html.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {h} from '../index.js';
|
|
2
2
|
|
|
3
3
|
// Document metadata
|
|
4
4
|
export const title = (...args) => h('title', ...args);
|
|
5
|
-
export const meta =
|
|
6
|
-
export const link =
|
|
5
|
+
export const meta = props => h('meta', props);
|
|
6
|
+
export const link = props => h('link', props);
|
|
7
7
|
export const style = (...args) => h('style', ...args);
|
|
8
8
|
export const script = (...args) => h('script', ...args);
|
|
9
9
|
export const noscript = (...args) => h('noscript', ...args);
|
|
@@ -67,21 +67,21 @@ export const u = (...args) => h('u', ...args);
|
|
|
67
67
|
export const wbr = () => h('wbr');
|
|
68
68
|
|
|
69
69
|
// Image and multimedia
|
|
70
|
-
export const area =
|
|
70
|
+
export const area = props => h('area', props);
|
|
71
71
|
export const audio = (...args) => h('audio', ...args);
|
|
72
|
-
export const img =
|
|
72
|
+
export const img = props => h('img', props);
|
|
73
73
|
export const map = (...args) => h('map', ...args);
|
|
74
|
-
export const track =
|
|
74
|
+
export const track = props => h('track', props);
|
|
75
75
|
export const video = (...args) => h('video', ...args);
|
|
76
76
|
|
|
77
77
|
// Embedded content
|
|
78
|
-
export const embed =
|
|
78
|
+
export const embed = props => h('embed', props);
|
|
79
79
|
export const iframe = (...args) => h('iframe', ...args);
|
|
80
80
|
export const object = (...args) => h('object', ...args);
|
|
81
|
-
export const param =
|
|
81
|
+
export const param = props => h('param', props);
|
|
82
82
|
export const picture = (...args) => h('picture', ...args);
|
|
83
83
|
export const portal = (...args) => h('portal', ...args);
|
|
84
|
-
export const source =
|
|
84
|
+
export const source = props => h('source', props);
|
|
85
85
|
|
|
86
86
|
// SVG and MathML
|
|
87
87
|
export const svg = (...args) => h('svg', ...args);
|
|
@@ -92,7 +92,7 @@ export const canvas = (...args) => h('canvas', ...args);
|
|
|
92
92
|
|
|
93
93
|
// Table content
|
|
94
94
|
export const caption = (...args) => h('caption', ...args);
|
|
95
|
-
export const col =
|
|
95
|
+
export const col = props => h('col', props);
|
|
96
96
|
export const colgroup = (...args) => h('colgroup', ...args);
|
|
97
97
|
export const table = (...args) => h('table', ...args);
|
|
98
98
|
export const tbody = (...args) => h('tbody', ...args);
|
|
@@ -107,7 +107,7 @@ export const button = (...args) => h('button', ...args);
|
|
|
107
107
|
export const datalist = (...args) => h('datalist', ...args);
|
|
108
108
|
export const fieldset = (...args) => h('fieldset', ...args);
|
|
109
109
|
export const form = (...args) => h('form', ...args);
|
|
110
|
-
export const input =
|
|
110
|
+
export const input = props => h('input', props);
|
|
111
111
|
export const label = (...args) => h('label', ...args);
|
|
112
112
|
export const legend = (...args) => h('legend', ...args);
|
|
113
113
|
export const meter = (...args) => h('meter', ...args);
|
package/src/scheduler.js
CHANGED
|
@@ -6,74 +6,74 @@ const FRAME_BUDGET = 10; // ms
|
|
|
6
6
|
const CHUNK_SIZE = 100;
|
|
7
7
|
|
|
8
8
|
function _runTasks(tasks) {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
}
|
|
9
|
+
for (const f of tasks) {
|
|
10
|
+
try {
|
|
11
|
+
f();
|
|
12
|
+
} catch (err) {
|
|
13
|
+
console.error('Error in scheduled function:', err);
|
|
15
14
|
}
|
|
15
|
+
}
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
function runQueue() {
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
const startTime = performance.now();
|
|
20
|
+
frameId = 0;
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
while (queue.length > 0) {
|
|
23
|
+
const chunk = queue.splice(0, CHUNK_SIZE);
|
|
24
|
+
_runTasks(chunk);
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
}
|
|
26
|
+
if (performance.now() - startTime > FRAME_BUDGET && queue.length > 0) {
|
|
27
|
+
frameId = requestAnimationFrame(runQueue);
|
|
28
|
+
return;
|
|
30
29
|
}
|
|
30
|
+
}
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
scheduled = false;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
// Schedules a function to be executed on the next animation frame.
|
|
36
|
-
export function schedule(f, {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
36
|
+
export function schedule(f, {signal} = {}) {
|
|
37
|
+
if (signal?.aborted) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
40
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
41
|
+
let task = f;
|
|
42
|
+
// If a signal is provided, wrap the task to check for abortion before execution.
|
|
43
|
+
if (signal) {
|
|
44
|
+
task = () => {
|
|
45
|
+
if (!signal.aborted) {
|
|
46
|
+
f();
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
50
|
|
|
51
|
-
|
|
51
|
+
queue.push(task);
|
|
52
52
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
53
|
+
if (!scheduled) {
|
|
54
|
+
scheduled = true;
|
|
55
|
+
frameId = requestAnimationFrame(runQueue);
|
|
56
|
+
}
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
// Immediately runs all queued tasks synchronously.
|
|
60
60
|
export function flush() {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
61
|
+
if (frameId) {
|
|
62
|
+
cancelAnimationFrame(frameId);
|
|
63
|
+
}
|
|
64
|
+
const toRun = queue;
|
|
65
|
+
queue = [];
|
|
66
|
+
_runTasks(toRun);
|
|
67
|
+
scheduled = false;
|
|
68
|
+
frameId = 0;
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
// Clears all pending tasks.
|
|
72
72
|
export function clear() {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
73
|
+
if (frameId) {
|
|
74
|
+
cancelAnimationFrame(frameId);
|
|
75
|
+
}
|
|
76
|
+
queue = [];
|
|
77
|
+
scheduled = false;
|
|
78
|
+
frameId = 0;
|
|
79
79
|
}
|
package/src/vdom.js
CHANGED
|
@@ -32,32 +32,35 @@ class VNode {
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
function defaultShouldUpdate(a, b) {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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
46
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
}
|
|
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;
|
|
55
54
|
}
|
|
56
|
-
|
|
55
|
+
}
|
|
56
|
+
return true;
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
function isIterable(x) {
|
|
60
|
-
return
|
|
60
|
+
return (
|
|
61
|
+
Array.isArray(x) ||
|
|
62
|
+
(x != null && typeof x[Symbol.iterator] === 'function' && typeof x !== 'string')
|
|
63
|
+
);
|
|
61
64
|
}
|
|
62
65
|
|
|
63
66
|
function alias(f) {
|
|
@@ -71,8 +74,10 @@ function special(o) {
|
|
|
71
74
|
function newElementNamespace(parentNode, newNodeTag) {
|
|
72
75
|
if (parentNode.namespaceURI === 'http://www.w3.org/1999/xhtml') {
|
|
73
76
|
switch (newNodeTag) {
|
|
74
|
-
case 'svg':
|
|
75
|
-
|
|
77
|
+
case 'svg':
|
|
78
|
+
return 'http://www.w3.org/2000/svg';
|
|
79
|
+
case 'math':
|
|
80
|
+
return 'http://www.w3.org/1998/Math/MathML';
|
|
76
81
|
}
|
|
77
82
|
}
|
|
78
83
|
return parentNode.namespaceURI ?? parentNode.host?.namespaceURI ?? 'http://www.w3.org/1999/xhtml';
|
|
@@ -80,7 +85,7 @@ function newElementNamespace(parentNode, newNodeTag) {
|
|
|
80
85
|
|
|
81
86
|
function createElementNode(parentNode, tag, vdom) {
|
|
82
87
|
const el = parentNode.ownerDocument.createElementNS(newElementNamespace(parentNode, tag), tag);
|
|
83
|
-
el[NODE_STATE] = {
|
|
88
|
+
el[NODE_STATE] = {originalProps: {}, newVdom: vdom};
|
|
84
89
|
return el;
|
|
85
90
|
}
|
|
86
91
|
|
|
@@ -91,10 +96,10 @@ function getPathFromElement(element) {
|
|
|
91
96
|
let current = element;
|
|
92
97
|
while (current) {
|
|
93
98
|
path.push(current);
|
|
94
|
-
if (current.
|
|
95
|
-
current = current.
|
|
99
|
+
if (current.parentElement) {
|
|
100
|
+
current = current.parentElement;
|
|
96
101
|
} else {
|
|
97
|
-
current = current.
|
|
102
|
+
current = current.getRootNode()?.host;
|
|
98
103
|
}
|
|
99
104
|
}
|
|
100
105
|
return path;
|
|
@@ -117,10 +122,10 @@ function removePathFromFocusWithinSet(set, newPath) {
|
|
|
117
122
|
|
|
118
123
|
function installFocusTrackingForDocument(doc) {
|
|
119
124
|
const focusWithinSet = new Set();
|
|
120
|
-
doc.addEventListener('focusin',
|
|
125
|
+
doc.addEventListener('focusin', event => {
|
|
121
126
|
addPathToFocusWithinSet(focusWithinSet, event.composedPath());
|
|
122
127
|
});
|
|
123
|
-
doc.addEventListener('focusout',
|
|
128
|
+
doc.addEventListener('focusout', event => {
|
|
124
129
|
const newPath = event.relatedTarget ? event.composedPath() : [];
|
|
125
130
|
removePathFromFocusWithinSet(focusWithinSet, newPath);
|
|
126
131
|
});
|
|
@@ -129,21 +134,26 @@ function installFocusTrackingForDocument(doc) {
|
|
|
129
134
|
return focusWithinSet;
|
|
130
135
|
}
|
|
131
136
|
|
|
132
|
-
export default
|
|
137
|
+
export default userSettings => {
|
|
133
138
|
const shouldUpdate = userSettings?.shouldUpdate ?? defaultShouldUpdate;
|
|
134
|
-
const isMap = userSettings?.isMap ?? (
|
|
135
|
-
const mapIter = userSettings?.mapIter ?? (
|
|
139
|
+
const isMap = userSettings?.isMap ?? (x => x?.constructor === Object);
|
|
140
|
+
const mapIter = userSettings?.mapIter ?? (m => Object.entries(m));
|
|
136
141
|
const mapGet = userSettings?.mapGet ?? ((m, k) => m[k]);
|
|
137
142
|
const mapMerge = userSettings?.mapMerge ?? ((...maps) => Object.assign({}, ...maps));
|
|
138
|
-
const newMap = userSettings?.newMap ?? (
|
|
139
|
-
const mapPut =
|
|
143
|
+
const newMap = userSettings?.newMap ?? (obj => ({...obj}));
|
|
144
|
+
const mapPut =
|
|
145
|
+
userSettings?.mapPut ??
|
|
146
|
+
((m, k, v) => {
|
|
147
|
+
m[k] = v;
|
|
148
|
+
return m;
|
|
149
|
+
});
|
|
140
150
|
const isSeq = userSettings?.isSeq ?? isIterable;
|
|
141
|
-
const seqIter = userSettings?.seqIter ?? (
|
|
142
|
-
const convertTagName = userSettings?.convertTagName ?? (
|
|
143
|
-
const convertPropName = userSettings?.convertPropName ?? (
|
|
144
|
-
const convertStyleName = userSettings?.convertStyleName ?? (
|
|
145
|
-
const convertDataName = userSettings?.convertDataName ?? (
|
|
146
|
-
const convertClassName = userSettings?.convertClassName ?? (
|
|
151
|
+
const seqIter = userSettings?.seqIter ?? (s => s);
|
|
152
|
+
const convertTagName = userSettings?.convertTagName ?? (t => t);
|
|
153
|
+
const convertPropName = userSettings?.convertPropName ?? (p => p);
|
|
154
|
+
const convertStyleName = userSettings?.convertStyleName ?? (s => s);
|
|
155
|
+
const convertDataName = userSettings?.convertDataName ?? (d => d);
|
|
156
|
+
const convertClassName = userSettings?.convertClassName ?? (c => c);
|
|
147
157
|
const listenerKey = userSettings?.listenerKey ?? 'listener';
|
|
148
158
|
const captureKey = userSettings?.captureKey ?? 'capture';
|
|
149
159
|
const passiveKey = userSettings?.passiveKey ?? 'passive';
|
|
@@ -244,86 +254,86 @@ export default (userSettings) => {
|
|
|
244
254
|
|
|
245
255
|
// Handle new and changed props
|
|
246
256
|
for (const [name, newValue] of mapIter(props)) {
|
|
247
|
-
|
|
248
|
-
|
|
257
|
+
const propName = convertPropName(name);
|
|
258
|
+
const oldValue = mapGet(oldProps, name);
|
|
249
259
|
|
|
250
|
-
|
|
260
|
+
if (Object.is(newValue, oldValue)) continue;
|
|
251
261
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
262
|
+
switch (propName) {
|
|
263
|
+
case '$styling': {
|
|
264
|
+
if (!isMap(newValue)) throw new Error('invalid value for styling prop');
|
|
265
|
+
reconcileElementStyling(target, oldValue ?? EMPTY_OBJECT, newValue ?? EMPTY_OBJECT);
|
|
266
|
+
break;
|
|
267
|
+
}
|
|
268
|
+
case '$classes': {
|
|
269
|
+
if (!isSeq(newValue)) throw new Error('invalid value for classes prop');
|
|
270
|
+
reconcileElementClasses(target, oldValue ?? [], newValue ?? []);
|
|
271
|
+
break;
|
|
272
|
+
}
|
|
273
|
+
case '$attrs': {
|
|
274
|
+
if (!isMap(newValue)) throw new Error('invalid value for attrs prop');
|
|
275
|
+
reconcileElementAttributes(target, oldValue ?? EMPTY_OBJECT, newValue ?? EMPTY_OBJECT);
|
|
276
|
+
break;
|
|
277
|
+
}
|
|
278
|
+
case '$dataset': {
|
|
279
|
+
if (!isMap(newValue)) throw new Error('invalid value for dataset prop');
|
|
280
|
+
reconcileElementDataset(target, oldValue ?? EMPTY_OBJECT, newValue ?? EMPTY_OBJECT);
|
|
281
|
+
break;
|
|
282
|
+
}
|
|
283
|
+
default: {
|
|
284
|
+
if (isHtml) {
|
|
285
|
+
const originalProps = nodeState.originalProps;
|
|
286
|
+
if (!(propName in originalProps)) {
|
|
287
|
+
originalProps[propName] = target[propName];
|
|
267
288
|
}
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
289
|
+
if (newValue === undefined) {
|
|
290
|
+
target[propName] = originalProps[propName];
|
|
291
|
+
} else {
|
|
292
|
+
target[propName] = newValue;
|
|
272
293
|
}
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
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;
|
|
294
|
+
} else {
|
|
295
|
+
if (newValue === undefined) {
|
|
296
|
+
target.removeAttribute(propName);
|
|
297
|
+
} else {
|
|
298
|
+
target.setAttribute(propName, newValue);
|
|
292
299
|
}
|
|
300
|
+
}
|
|
301
|
+
break;
|
|
293
302
|
}
|
|
303
|
+
}
|
|
294
304
|
}
|
|
295
305
|
|
|
296
306
|
// Handle removed props
|
|
297
307
|
for (const [name, oldValue] of mapIter(oldProps)) {
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
}
|
|
321
|
-
} else {
|
|
322
|
-
target.removeAttribute(propName);
|
|
323
|
-
}
|
|
324
|
-
break;
|
|
308
|
+
if (mapGet(props, name) !== undefined) continue; // it wasn't removed
|
|
309
|
+
|
|
310
|
+
const propName = convertPropName(name);
|
|
311
|
+
switch (propName) {
|
|
312
|
+
case '$styling':
|
|
313
|
+
reconcileElementStyling(target, oldValue ?? EMPTY_OBJECT, EMPTY_OBJECT);
|
|
314
|
+
break;
|
|
315
|
+
case '$classes':
|
|
316
|
+
reconcileElementClasses(target, oldValue ?? [], []);
|
|
317
|
+
break;
|
|
318
|
+
case '$attrs':
|
|
319
|
+
reconcileElementAttributes(target, oldValue ?? EMPTY_OBJECT, EMPTY_OBJECT);
|
|
320
|
+
break;
|
|
321
|
+
case '$dataset':
|
|
322
|
+
reconcileElementDataset(target, oldValue ?? EMPTY_OBJECT, EMPTY_OBJECT);
|
|
323
|
+
break;
|
|
324
|
+
default: {
|
|
325
|
+
if (isHtml) {
|
|
326
|
+
const originalProps = nodeState.originalProps;
|
|
327
|
+
if (propName in originalProps) {
|
|
328
|
+
target[propName] = originalProps[propName];
|
|
329
|
+
delete originalProps[propName];
|
|
325
330
|
}
|
|
331
|
+
} else {
|
|
332
|
+
target.removeAttribute(propName);
|
|
333
|
+
}
|
|
334
|
+
break;
|
|
326
335
|
}
|
|
336
|
+
}
|
|
327
337
|
}
|
|
328
338
|
}
|
|
329
339
|
|
|
@@ -335,9 +345,9 @@ export default (userSettings) => {
|
|
|
335
345
|
if (typeof listener === 'function') {
|
|
336
346
|
target.addEventListener(name, listener);
|
|
337
347
|
} else if (listener != null) {
|
|
338
|
-
target.addEventListener(name, mapGet(listener, listenerKey), {
|
|
339
|
-
capture: !!mapGet(listener, captureKey),
|
|
340
|
-
passive: !!mapGet(listener, passiveKey)
|
|
348
|
+
target.addEventListener(name, mapGet(listener, listenerKey), {
|
|
349
|
+
capture: !!mapGet(listener, captureKey),
|
|
350
|
+
passive: !!mapGet(listener, passiveKey),
|
|
341
351
|
});
|
|
342
352
|
}
|
|
343
353
|
}
|
|
@@ -351,15 +361,19 @@ export default (userSettings) => {
|
|
|
351
361
|
if (typeof oldListener === 'function') {
|
|
352
362
|
target.removeEventListener(name, oldListener);
|
|
353
363
|
} else if (oldListener != null) {
|
|
354
|
-
target.removeEventListener(
|
|
364
|
+
target.removeEventListener(
|
|
365
|
+
name,
|
|
366
|
+
mapGet(oldListener, listenerKey),
|
|
367
|
+
!!mapGet(oldListener, captureKey),
|
|
368
|
+
);
|
|
355
369
|
}
|
|
356
|
-
|
|
370
|
+
|
|
357
371
|
if (typeof listener === 'function') {
|
|
358
372
|
target.addEventListener(name, listener);
|
|
359
373
|
} else if (listener != null) {
|
|
360
|
-
target.addEventListener(name, mapGet(listener, listenerKey), {
|
|
361
|
-
capture: !!mapGet(listener, captureKey),
|
|
362
|
-
passive: !!mapGet(listener, passiveKey)
|
|
374
|
+
target.addEventListener(name, mapGet(listener, listenerKey), {
|
|
375
|
+
capture: !!mapGet(listener, captureKey),
|
|
376
|
+
passive: !!mapGet(listener, passiveKey),
|
|
363
377
|
});
|
|
364
378
|
}
|
|
365
379
|
}
|
|
@@ -369,7 +383,11 @@ export default (userSettings) => {
|
|
|
369
383
|
if (typeof oldListener === 'function') {
|
|
370
384
|
target.removeEventListener(name, oldListener);
|
|
371
385
|
} else if (oldListener != null) {
|
|
372
|
-
target.removeEventListener(
|
|
386
|
+
target.removeEventListener(
|
|
387
|
+
name,
|
|
388
|
+
mapGet(oldListener, listenerKey),
|
|
389
|
+
!!mapGet(oldListener, captureKey),
|
|
390
|
+
);
|
|
373
391
|
}
|
|
374
392
|
}
|
|
375
393
|
}
|
|
@@ -459,7 +477,6 @@ export default (userSettings) => {
|
|
|
459
477
|
function cleanupTarget(target) {
|
|
460
478
|
const state = target[NODE_STATE];
|
|
461
479
|
if (state) {
|
|
462
|
-
|
|
463
480
|
switch (state.vdom.type) {
|
|
464
481
|
case ELEMENT_NODE:
|
|
465
482
|
reconcileElementProps(target, {});
|
|
@@ -509,7 +526,7 @@ export default (userSettings) => {
|
|
|
509
526
|
|
|
510
527
|
let oldNodesPoolForTag = oldVNodeNodesPool.get(vdom.tag);
|
|
511
528
|
if (!oldNodesPoolForTag) {
|
|
512
|
-
oldNodesPoolForTag = {
|
|
529
|
+
oldNodesPoolForTag = {nodesForKey: new Map(), nodesWithoutKey: []};
|
|
513
530
|
oldVNodeNodesPool.set(vdom.tag, oldNodesPoolForTag);
|
|
514
531
|
}
|
|
515
532
|
|
|
@@ -539,7 +556,10 @@ export default (userSettings) => {
|
|
|
539
556
|
if (pool && pool.length > 0) {
|
|
540
557
|
newDomNode = pool.shift();
|
|
541
558
|
const state = newDomNode[NODE_STATE];
|
|
542
|
-
if (
|
|
559
|
+
if (
|
|
560
|
+
shouldUpdate(state.vdom.args, newVdom.args) ||
|
|
561
|
+
shouldUpdate(state.vdom.hooks, newVdom.hooks)
|
|
562
|
+
) {
|
|
543
563
|
state.newVdom = newVdom;
|
|
544
564
|
}
|
|
545
565
|
} else {
|
|
@@ -550,7 +570,10 @@ export default (userSettings) => {
|
|
|
550
570
|
if (unkeyedOldNode) {
|
|
551
571
|
newDomNode = unkeyedOldNode;
|
|
552
572
|
const state = newDomNode[NODE_STATE];
|
|
553
|
-
if (
|
|
573
|
+
if (
|
|
574
|
+
shouldUpdate(state.vdom.args, newVdom.args) ||
|
|
575
|
+
shouldUpdate(state.vdom.hooks, newVdom.hooks)
|
|
576
|
+
) {
|
|
554
577
|
state.newVdom = newVdom;
|
|
555
578
|
}
|
|
556
579
|
} else {
|
|
@@ -577,41 +600,69 @@ export default (userSettings) => {
|
|
|
577
600
|
|
|
578
601
|
const moveBefore = window.Element.prototype.moveBefore;
|
|
579
602
|
const insertBefore = window.Element.prototype.insertBefore;
|
|
580
|
-
if (target.isConnected
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
(newChild
|
|
603
|
+
if (target.isConnected) {
|
|
604
|
+
if (typeof moveBefore === 'function') {
|
|
605
|
+
for (let i = 0; i < newDomChildren.length; i++) {
|
|
606
|
+
const newChild = newDomChildren[i];
|
|
607
|
+
const existingChildAtPosition = target.childNodes[i];
|
|
608
|
+
if (newChild !== existingChildAtPosition) {
|
|
609
|
+
(newChild.isConnected ? moveBefore : insertBefore).call(
|
|
610
|
+
target,
|
|
611
|
+
newChild,
|
|
612
|
+
existingChildAtPosition,
|
|
613
|
+
);
|
|
614
|
+
}
|
|
615
|
+
const state = newChild[NODE_STATE];
|
|
616
|
+
if (state?.newVdom) {
|
|
617
|
+
if (!state.vdom) {
|
|
618
|
+
try {
|
|
619
|
+
state.newVdom.hooks?.$attach?.(newChild);
|
|
620
|
+
if (state.newVdom.type === SPECIAL_NODE) {
|
|
621
|
+
state.newVdom.tag.attach?.(newChild);
|
|
622
|
+
}
|
|
623
|
+
} catch (err) {
|
|
624
|
+
console.error(err);
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
reconcileNode(newChild);
|
|
628
|
+
}
|
|
586
629
|
}
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
630
|
+
} else {
|
|
631
|
+
const doc = target.ownerDocument;
|
|
632
|
+
let focusWithin = documentToFocusWithinSet.get(doc);
|
|
633
|
+
if (!focusWithin) {
|
|
634
|
+
focusWithin = installFocusTrackingForDocument(doc);
|
|
635
|
+
}
|
|
636
|
+
for (let i = 0; i < newDomChildren.length; i++) {
|
|
637
|
+
const newChild = newDomChildren[i];
|
|
638
|
+
const existingChildAtPosition = target.childNodes[i];
|
|
639
|
+
if (newChild !== existingChildAtPosition) {
|
|
640
|
+
if (!focusWithin.has(newChild)) {
|
|
641
|
+
insertBefore.call(target, newChild, existingChildAtPosition);
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
const state = newChild[NODE_STATE];
|
|
645
|
+
if (state?.newVdom) {
|
|
646
|
+
if (!state.vdom) {
|
|
647
|
+
try {
|
|
648
|
+
state.newVdom.hooks?.$attach?.(newChild);
|
|
649
|
+
if (state.newVdom.type === SPECIAL_NODE) {
|
|
650
|
+
state.newVdom.tag.attach?.(newChild);
|
|
651
|
+
}
|
|
652
|
+
} catch (err) {
|
|
653
|
+
console.error(err);
|
|
594
654
|
}
|
|
595
|
-
} catch (err) {
|
|
596
|
-
console.error(err);
|
|
597
655
|
}
|
|
656
|
+
reconcileNode(newChild);
|
|
598
657
|
}
|
|
599
|
-
reconcileNode(newChild);
|
|
600
658
|
}
|
|
601
659
|
}
|
|
602
660
|
} else {
|
|
603
|
-
const doc = target.ownerDocument;
|
|
604
|
-
let focusWithin = documentToFocusWithinSet.get(doc);
|
|
605
|
-
if (!focusWithin) {
|
|
606
|
-
focusWithin = installFocusTrackingForDocument(doc);
|
|
607
|
-
}
|
|
608
661
|
for (let i = 0; i < newDomChildren.length; i++) {
|
|
609
662
|
const newChild = newDomChildren[i];
|
|
610
663
|
const existingChildAtPosition = target.childNodes[i];
|
|
611
664
|
if (newChild !== existingChildAtPosition) {
|
|
612
|
-
|
|
613
|
-
insertBefore.call(target, newChild, existingChildAtPosition);
|
|
614
|
-
}
|
|
665
|
+
insertBefore.call(target, newChild, existingChildAtPosition);
|
|
615
666
|
}
|
|
616
667
|
const state = newChild[NODE_STATE];
|
|
617
668
|
if (state?.newVdom) {
|
|
@@ -651,7 +702,10 @@ export default (userSettings) => {
|
|
|
651
702
|
if (vdom instanceof VNode) {
|
|
652
703
|
if (state) {
|
|
653
704
|
if (state.vdom.type === vdom.type) {
|
|
654
|
-
if (
|
|
705
|
+
if (
|
|
706
|
+
shouldUpdate(state.vdom.args, vdom.args) ||
|
|
707
|
+
shouldUpdate(state.vdom.hooks, vdom.hooks)
|
|
708
|
+
) {
|
|
655
709
|
state.newVdom = vdom;
|
|
656
710
|
reconcileNode(target);
|
|
657
711
|
}
|
|
@@ -663,13 +717,18 @@ export default (userSettings) => {
|
|
|
663
717
|
switch (vdom.type) {
|
|
664
718
|
case ELEMENT_NODE:
|
|
665
719
|
case OPAQUE_NODE:
|
|
666
|
-
if (
|
|
720
|
+
if (
|
|
721
|
+
0 !==
|
|
722
|
+
target.nodeName.localeCompare(convertTagName(vdom.tag), undefined, {
|
|
723
|
+
sensitivity: 'base',
|
|
724
|
+
})
|
|
725
|
+
) {
|
|
667
726
|
throw new Error('incompatible target for vdom');
|
|
668
727
|
}
|
|
669
728
|
break;
|
|
670
729
|
}
|
|
671
730
|
|
|
672
|
-
target[NODE_STATE] = {
|
|
731
|
+
target[NODE_STATE] = {originalProps: {}, newVdom: vdom};
|
|
673
732
|
try {
|
|
674
733
|
vdom.hooks?.$attach?.(target);
|
|
675
734
|
if (vdom.type === SPECIAL_NODE) {
|
|
@@ -685,9 +744,30 @@ export default (userSettings) => {
|
|
|
685
744
|
throw new Error('invalid vdom');
|
|
686
745
|
}
|
|
687
746
|
|
|
688
|
-
return {
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
747
|
+
return {
|
|
748
|
+
h,
|
|
749
|
+
alias,
|
|
750
|
+
special,
|
|
751
|
+
reconcile,
|
|
752
|
+
settings: {
|
|
753
|
+
shouldUpdate,
|
|
754
|
+
isMap,
|
|
755
|
+
mapIter,
|
|
756
|
+
mapGet,
|
|
757
|
+
mapMerge,
|
|
758
|
+
newMap,
|
|
759
|
+
mapPut,
|
|
760
|
+
isSeq,
|
|
761
|
+
flattenSeq,
|
|
762
|
+
seqIter,
|
|
763
|
+
convertTagName,
|
|
764
|
+
convertPropName,
|
|
765
|
+
convertStyleName,
|
|
766
|
+
convertDataName,
|
|
767
|
+
convertClassName,
|
|
768
|
+
listenerKey,
|
|
769
|
+
captureKey,
|
|
770
|
+
passiveKey,
|
|
771
|
+
},
|
|
772
|
+
};
|
|
773
|
+
};
|