@3sln/dodo 0.0.3 → 0.0.5
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/README.md +6 -1
- package/index.js +128 -7
- package/package.json +6 -2
- package/src/html.js +119 -117
- package/src/scheduler.js +48 -48
- package/src/vdom.js +274 -161
- package/dodo.js +0 -698
package/README.md
CHANGED
|
@@ -25,7 +25,12 @@ reconcile(container, [myVdom]);
|
|
|
25
25
|
|
|
26
26
|
## Core Concepts
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
## Core Concepts
|
|
29
|
+
|
|
30
|
+
- **Factories for Customization:** Dodo is built around a factory pattern. While you can import and use a pre-configured default instance, you can also create custom instances for advanced use cases (like interoperating with ClojureScript). The library exports three factories:
|
|
31
|
+
- `dodo(settings)`: A unified factory that returns a complete API with VDOM functions, HTML helpers, and the scheduler.
|
|
32
|
+
- `vdom(settings)`: Creates just the core VDOM API (`h`, `alias`, `special`, `reconcile`, `settings`).
|
|
33
|
+
- `html({ h })`: Creates the HTML helper functions (`div`, `p`, etc.) bound to a specific `h` function.
|
|
29
34
|
- **HTML Helpers:** Simple functions like `div()`, `p()`, `span()` are exported for convenience.
|
|
30
35
|
- **`$`-Prefixed Props:** Special props that `dodo` intercepts are prefixed with a `$` to avoid conflicts with standard properties (e.g., `$classes`, `$styling`, `$attrs`, `$dataset`).
|
|
31
36
|
- **`.key()`:** Chain `.key('unique-id')` to any VNode in a list to enable efficient, keyed reconciliation.
|
package/index.js
CHANGED
|
@@ -1,10 +1,131 @@
|
|
|
1
|
-
import
|
|
1
|
+
import vdomFactory from './src/vdom.js';
|
|
2
|
+
import htmlFactory from './src/html.js';
|
|
3
|
+
import * as scheduler from './src/scheduler.js';
|
|
2
4
|
|
|
3
|
-
|
|
4
|
-
const
|
|
5
|
+
function dodoFactory(userSettings) {
|
|
6
|
+
const vdomInstance = vdomFactory(userSettings);
|
|
7
|
+
const htmlInstance = htmlFactory(vdomInstance);
|
|
8
|
+
return {
|
|
9
|
+
...vdomInstance,
|
|
10
|
+
...htmlInstance,
|
|
11
|
+
...scheduler,
|
|
12
|
+
};
|
|
13
|
+
}
|
|
5
14
|
|
|
6
|
-
|
|
15
|
+
const defaultDodo = dodoFactory();
|
|
7
16
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
export {
|
|
17
|
+
export {vdomFactory as vdom, htmlFactory as html, dodoFactory as dodo};
|
|
18
|
+
|
|
19
|
+
export const {
|
|
20
|
+
h,
|
|
21
|
+
alias,
|
|
22
|
+
special,
|
|
23
|
+
reconcile,
|
|
24
|
+
settings,
|
|
25
|
+
title,
|
|
26
|
+
meta,
|
|
27
|
+
link,
|
|
28
|
+
style,
|
|
29
|
+
script,
|
|
30
|
+
noscript,
|
|
31
|
+
address,
|
|
32
|
+
article,
|
|
33
|
+
aside,
|
|
34
|
+
footer,
|
|
35
|
+
header,
|
|
36
|
+
h1,
|
|
37
|
+
h2,
|
|
38
|
+
h3,
|
|
39
|
+
h4,
|
|
40
|
+
h5,
|
|
41
|
+
h6,
|
|
42
|
+
main,
|
|
43
|
+
nav,
|
|
44
|
+
section,
|
|
45
|
+
blockquote,
|
|
46
|
+
dd,
|
|
47
|
+
div,
|
|
48
|
+
dl,
|
|
49
|
+
dt,
|
|
50
|
+
figcaption,
|
|
51
|
+
figure,
|
|
52
|
+
hr,
|
|
53
|
+
li,
|
|
54
|
+
ol,
|
|
55
|
+
p,
|
|
56
|
+
pre,
|
|
57
|
+
ul,
|
|
58
|
+
a,
|
|
59
|
+
abbr,
|
|
60
|
+
b,
|
|
61
|
+
br,
|
|
62
|
+
cite,
|
|
63
|
+
code,
|
|
64
|
+
data,
|
|
65
|
+
del,
|
|
66
|
+
dfn,
|
|
67
|
+
em,
|
|
68
|
+
i,
|
|
69
|
+
ins,
|
|
70
|
+
kbd,
|
|
71
|
+
mark,
|
|
72
|
+
q,
|
|
73
|
+
s,
|
|
74
|
+
samp,
|
|
75
|
+
small,
|
|
76
|
+
span,
|
|
77
|
+
strong,
|
|
78
|
+
sub,
|
|
79
|
+
sup,
|
|
80
|
+
time,
|
|
81
|
+
u,
|
|
82
|
+
wbr,
|
|
83
|
+
area,
|
|
84
|
+
audio,
|
|
85
|
+
img,
|
|
86
|
+
map,
|
|
87
|
+
track,
|
|
88
|
+
video,
|
|
89
|
+
embed,
|
|
90
|
+
iframe,
|
|
91
|
+
object,
|
|
92
|
+
param,
|
|
93
|
+
picture,
|
|
94
|
+
portal,
|
|
95
|
+
source,
|
|
96
|
+
svg,
|
|
97
|
+
math,
|
|
98
|
+
canvas,
|
|
99
|
+
caption,
|
|
100
|
+
col,
|
|
101
|
+
colgroup,
|
|
102
|
+
table,
|
|
103
|
+
tbody,
|
|
104
|
+
td,
|
|
105
|
+
tfoot,
|
|
106
|
+
th,
|
|
107
|
+
thead,
|
|
108
|
+
tr,
|
|
109
|
+
button,
|
|
110
|
+
datalist,
|
|
111
|
+
fieldset,
|
|
112
|
+
form,
|
|
113
|
+
input,
|
|
114
|
+
label,
|
|
115
|
+
legend,
|
|
116
|
+
meter,
|
|
117
|
+
optgroup,
|
|
118
|
+
option,
|
|
119
|
+
output,
|
|
120
|
+
progress,
|
|
121
|
+
select,
|
|
122
|
+
textarea,
|
|
123
|
+
details,
|
|
124
|
+
dialog,
|
|
125
|
+
summary,
|
|
126
|
+
slot,
|
|
127
|
+
template,
|
|
128
|
+
schedule,
|
|
129
|
+
flush,
|
|
130
|
+
clear,
|
|
131
|
+
} = defaultDodo;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@3sln/dodo",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.5",
|
|
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,128 +1,130 @@
|
|
|
1
|
-
|
|
1
|
+
export default function htmlFactory({h}) {
|
|
2
|
+
return {
|
|
3
|
+
// Document metadata
|
|
4
|
+
title: (...args) => h('title', ...args),
|
|
5
|
+
meta: props => h('meta', props),
|
|
6
|
+
link: props => h('link', props),
|
|
7
|
+
style: (...args) => h('style', ...args),
|
|
8
|
+
script: (...args) => h('script', ...args),
|
|
9
|
+
noscript: (...args) => h('noscript', ...args),
|
|
2
10
|
|
|
3
|
-
//
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
11
|
+
// Content sectioning
|
|
12
|
+
address: (...args) => h('address', ...args),
|
|
13
|
+
article: (...args) => h('article', ...args),
|
|
14
|
+
aside: (...args) => h('aside', ...args),
|
|
15
|
+
footer: (...args) => h('footer', ...args),
|
|
16
|
+
header: (...args) => h('header', ...args),
|
|
17
|
+
h1: (...args) => h('h1', ...args),
|
|
18
|
+
h2: (...args) => h('h2', ...args),
|
|
19
|
+
h3: (...args) => h('h3', ...args),
|
|
20
|
+
h4: (...args) => h('h4', ...args),
|
|
21
|
+
h5: (...args) => h('h5', ...args),
|
|
22
|
+
h6: (...args) => h('h6', ...args),
|
|
23
|
+
main: (...args) => h('main', ...args),
|
|
24
|
+
nav: (...args) => h('nav', ...args),
|
|
25
|
+
section: (...args) => h('section', ...args),
|
|
10
26
|
|
|
11
|
-
//
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
export const section = (...args) => h('section', ...args);
|
|
27
|
+
// Text content
|
|
28
|
+
blockquote: (...args) => h('blockquote', ...args),
|
|
29
|
+
dd: (...args) => h('dd', ...args),
|
|
30
|
+
div: (...args) => h('div', ...args),
|
|
31
|
+
dl: (...args) => h('dl', ...args),
|
|
32
|
+
dt: (...args) => h('dt', ...args),
|
|
33
|
+
figcaption: (...args) => h('figcaption', ...args),
|
|
34
|
+
figure: (...args) => h('figure', ...args),
|
|
35
|
+
hr: () => h('hr'),
|
|
36
|
+
li: (...args) => h('li', ...args),
|
|
37
|
+
ol: (...args) => h('ol', ...args),
|
|
38
|
+
p: (...args) => h('p', ...args),
|
|
39
|
+
pre: (...args) => h('pre', ...args),
|
|
40
|
+
ul: (...args) => h('ul', ...args),
|
|
26
41
|
|
|
27
|
-
//
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
42
|
+
// Inline text semantics
|
|
43
|
+
a: (...args) => h('a', ...args),
|
|
44
|
+
abbr: (...args) => h('abbr', ...args),
|
|
45
|
+
b: (...args) => h('b', ...args),
|
|
46
|
+
br: () => h('br'),
|
|
47
|
+
cite: (...args) => h('cite', ...args),
|
|
48
|
+
code: (...args) => h('code', ...args),
|
|
49
|
+
data: (...args) => h('data', ...args),
|
|
50
|
+
del: (...args) => h('del', ...args),
|
|
51
|
+
dfn: (...args) => h('dfn', ...args),
|
|
52
|
+
em: (...args) => h('em', ...args),
|
|
53
|
+
i: (...args) => h('i', ...args),
|
|
54
|
+
ins: (...args) => h('ins', ...args),
|
|
55
|
+
kbd: (...args) => h('kbd', ...args),
|
|
56
|
+
mark: (...args) => h('mark', ...args),
|
|
57
|
+
q: (...args) => h('q', ...args),
|
|
58
|
+
s: (...args) => h('s', ...args),
|
|
59
|
+
samp: (...args) => h('samp', ...args),
|
|
60
|
+
small: (...args) => h('small', ...args),
|
|
61
|
+
span: (...args) => h('span', ...args),
|
|
62
|
+
strong: (...args) => h('strong', ...args),
|
|
63
|
+
sub: (...args) => h('sub', ...args),
|
|
64
|
+
sup: (...args) => h('sup', ...args),
|
|
65
|
+
time: (...args) => h('time', ...args),
|
|
66
|
+
u: (...args) => h('u', ...args),
|
|
67
|
+
wbr: () => h('wbr'),
|
|
41
68
|
|
|
42
|
-
//
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
export const data = (...args) => h('data', ...args);
|
|
50
|
-
export const del = (...args) => h('del', ...args);
|
|
51
|
-
export const dfn = (...args) => h('dfn', ...args);
|
|
52
|
-
export const em = (...args) => h('em', ...args);
|
|
53
|
-
export const i = (...args) => h('i', ...args);
|
|
54
|
-
export const ins = (...args) => h('ins', ...args);
|
|
55
|
-
export const kbd = (...args) => h('kbd', ...args);
|
|
56
|
-
export const mark = (...args) => h('mark', ...args);
|
|
57
|
-
export const q = (...args) => h('q', ...args);
|
|
58
|
-
export const s = (...args) => h('s', ...args);
|
|
59
|
-
export const samp = (...args) => h('samp', ...args);
|
|
60
|
-
export const small = (...args) => h('small', ...args);
|
|
61
|
-
export const span = (...args) => h('span', ...args);
|
|
62
|
-
export const strong = (...args) => h('strong', ...args);
|
|
63
|
-
export const sub = (...args) => h('sub', ...args);
|
|
64
|
-
export const sup = (...args) => h('sup', ...args);
|
|
65
|
-
export const time = (...args) => h('time', ...args);
|
|
66
|
-
export const u = (...args) => h('u', ...args);
|
|
67
|
-
export const wbr = () => h('wbr');
|
|
69
|
+
// Image and multimedia
|
|
70
|
+
area: props => h('area', props),
|
|
71
|
+
audio: (...args) => h('audio', ...args),
|
|
72
|
+
img: props => h('img', props),
|
|
73
|
+
map: (...args) => h('map', ...args),
|
|
74
|
+
track: props => h('track', props),
|
|
75
|
+
video: (...args) => h('video', ...args),
|
|
68
76
|
|
|
69
|
-
//
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
77
|
+
// Embedded content
|
|
78
|
+
embed: props => h('embed', props),
|
|
79
|
+
iframe: (...args) => h('iframe', ...args),
|
|
80
|
+
object: (...args) => h('object', ...args),
|
|
81
|
+
param: props => h('param', props),
|
|
82
|
+
picture: (...args) => h('picture', ...args),
|
|
83
|
+
portal: (...args) => h('portal', ...args),
|
|
84
|
+
source: props => h('source', props),
|
|
76
85
|
|
|
77
|
-
//
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
export const object = (...args) => h('object', ...args);
|
|
81
|
-
export const param = (props) => h('param', props);
|
|
82
|
-
export const picture = (...args) => h('picture', ...args);
|
|
83
|
-
export const portal = (...args) => h('portal', ...args);
|
|
84
|
-
export const source = (props) => h('source', props);
|
|
86
|
+
// SVG and MathML
|
|
87
|
+
svg: (...args) => h('svg', ...args),
|
|
88
|
+
math: (...args) => h('math', ...args),
|
|
85
89
|
|
|
86
|
-
//
|
|
87
|
-
|
|
88
|
-
export const math = (...args) => h('math', ...args);
|
|
90
|
+
// Scripting
|
|
91
|
+
canvas: (...args) => h('canvas', ...args),
|
|
89
92
|
|
|
90
|
-
//
|
|
91
|
-
|
|
93
|
+
// Table content
|
|
94
|
+
caption: (...args) => h('caption', ...args),
|
|
95
|
+
col: props => h('col', props),
|
|
96
|
+
colgroup: (...args) => h('colgroup', ...args),
|
|
97
|
+
table: (...args) => h('table', ...args),
|
|
98
|
+
tbody: (...args) => h('tbody', ...args),
|
|
99
|
+
td: (...args) => h('td', ...args),
|
|
100
|
+
tfoot: (...args) => h('tfoot', ...args),
|
|
101
|
+
th: (...args) => h('th', ...args),
|
|
102
|
+
thead: (...args) => h('thead', ...args),
|
|
103
|
+
tr: (...args) => h('tr', ...args),
|
|
92
104
|
|
|
93
|
-
//
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
105
|
+
// Forms
|
|
106
|
+
button: (...args) => h('button', ...args),
|
|
107
|
+
datalist: (...args) => h('datalist', ...args),
|
|
108
|
+
fieldset: (...args) => h('fieldset', ...args),
|
|
109
|
+
form: (...args) => h('form', ...args),
|
|
110
|
+
input: props => h('input', props),
|
|
111
|
+
label: (...args) => h('label', ...args),
|
|
112
|
+
legend: (...args) => h('legend', ...args),
|
|
113
|
+
meter: (...args) => h('meter', ...args),
|
|
114
|
+
optgroup: (...args) => h('optgroup', ...args),
|
|
115
|
+
option: (...args) => h('option', ...args),
|
|
116
|
+
output: (...args) => h('output', ...args),
|
|
117
|
+
progress: (...args) => h('progress', ...args),
|
|
118
|
+
select: (...args) => h('select', ...args),
|
|
119
|
+
textarea: (...args) => h('textarea', ...args),
|
|
104
120
|
|
|
105
|
-
//
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
export const form = (...args) => h('form', ...args);
|
|
110
|
-
export const input = (props) => h('input', props);
|
|
111
|
-
export const label = (...args) => h('label', ...args);
|
|
112
|
-
export const legend = (...args) => h('legend', ...args);
|
|
113
|
-
export const meter = (...args) => h('meter', ...args);
|
|
114
|
-
export const optgroup = (...args) => h('optgroup', ...args);
|
|
115
|
-
export const option = (...args) => h('option', ...args);
|
|
116
|
-
export const output = (...args) => h('output', ...args);
|
|
117
|
-
export const progress = (...args) => h('progress', ...args);
|
|
118
|
-
export const select = (...args) => h('select', ...args);
|
|
119
|
-
export const textarea = (...args) => h('textarea', ...args);
|
|
121
|
+
// Interactive elements
|
|
122
|
+
details: (...args) => h('details', ...args),
|
|
123
|
+
dialog: (...args) => h('dialog', ...args),
|
|
124
|
+
summary: (...args) => h('summary', ...args),
|
|
120
125
|
|
|
121
|
-
//
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
// Web Components
|
|
127
|
-
export const slot = (...args) => h('slot', ...args);
|
|
128
|
-
export const template = (...args) => h('template', ...args);
|
|
126
|
+
// Web Components
|
|
127
|
+
slot: (...args) => h('slot', ...args),
|
|
128
|
+
template: (...args) => h('template', ...args),
|
|
129
|
+
};
|
|
130
|
+
}
|
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
|
}
|