what-core 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/a11y.js +425 -0
- package/dist/animation.js +531 -0
- package/dist/components.js +272 -115
- package/dist/data.js +434 -0
- package/dist/dom.js +635 -424
- package/dist/form.js +441 -0
- package/dist/h.js +191 -138
- package/dist/head.js +59 -42
- package/dist/helpers.js +125 -83
- package/dist/hooks.js +224 -134
- package/dist/index.js +2 -2
- package/dist/reactive.js +150 -107
- package/dist/scheduler.js +241 -0
- package/dist/skeleton.js +363 -0
- package/dist/store.js +113 -55
- package/dist/testing.js +367 -0
- package/dist/what.js +2 -2
- package/index.d.ts +15 -0
- package/package.json +1 -1
- package/src/components.js +93 -0
- package/src/dom.js +47 -12
- package/src/index.js +2 -2
- package/src/store.js +23 -5
package/dist/h.js
CHANGED
|
@@ -1,150 +1,203 @@
|
|
|
1
|
+
// What Framework - Hyperscript / VDOM
|
|
2
|
+
// Minimal virtual DOM nodes. No classes, no fibers, just plain objects.
|
|
3
|
+
|
|
4
|
+
// h(tag, props, ...children) -> VNode
|
|
5
|
+
// h(Component, props, ...children) -> VNode
|
|
6
|
+
// VNode = { tag, props, children, key }
|
|
7
|
+
|
|
1
8
|
const EMPTY_OBJ = {};
|
|
2
9
|
const EMPTY_ARR = [];
|
|
10
|
+
|
|
3
11
|
export function h(tag, props, ...children) {
|
|
4
|
-
props = props || EMPTY_OBJ;
|
|
5
|
-
const flat = flattenChildren(children);
|
|
6
|
-
const key = props.key ?? null;
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
}
|
|
12
|
+
props = props || EMPTY_OBJ;
|
|
13
|
+
const flat = flattenChildren(children);
|
|
14
|
+
const key = props.key ?? null;
|
|
15
|
+
|
|
16
|
+
// Strip key from props passed to component/element
|
|
17
|
+
if (props.key !== undefined) {
|
|
18
|
+
props = { ...props };
|
|
19
|
+
delete props.key;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return { tag, props, children: flat, key, _vnode: true };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Fragment — just returns children
|
|
13
26
|
export function Fragment({ children }) {
|
|
14
|
-
return children;
|
|
27
|
+
return children;
|
|
15
28
|
}
|
|
29
|
+
|
|
16
30
|
function flattenChildren(children) {
|
|
17
|
-
const out = [];
|
|
18
|
-
for (let i = 0; i < children.length; i++) {
|
|
19
|
-
const child = children[i];
|
|
20
|
-
if (child == null || child === false || child === true) continue;
|
|
21
|
-
if (Array.isArray(child)) {
|
|
22
|
-
out.push(...flattenChildren(child));
|
|
23
|
-
} else if (typeof child === 'object' && child._vnode) {
|
|
24
|
-
out.push(child);
|
|
25
|
-
} else {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
+
const out = [];
|
|
32
|
+
for (let i = 0; i < children.length; i++) {
|
|
33
|
+
const child = children[i];
|
|
34
|
+
if (child == null || child === false || child === true) continue;
|
|
35
|
+
if (Array.isArray(child)) {
|
|
36
|
+
out.push(...flattenChildren(child));
|
|
37
|
+
} else if (typeof child === 'object' && child._vnode) {
|
|
38
|
+
out.push(child);
|
|
39
|
+
} else {
|
|
40
|
+
// Text node
|
|
41
|
+
out.push(String(child));
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return out;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// JSX-like tagged template alternative (no build step needed)
|
|
48
|
+
// html`<div class="foo">${content}</div>`
|
|
49
|
+
// Uses a simple parser, not full HTML — good enough for most cases.
|
|
50
|
+
|
|
31
51
|
export function html(strings, ...values) {
|
|
32
|
-
const src = strings.reduce((acc, str, i) =>
|
|
33
|
-
acc + str + (i < values.length ? `\x00${i}\x00` : ''), '');
|
|
34
|
-
return parseTemplate(src, values);
|
|
52
|
+
const src = strings.reduce((acc, str, i) =>
|
|
53
|
+
acc + str + (i < values.length ? `\x00${i}\x00` : ''), '');
|
|
54
|
+
return parseTemplate(src, values);
|
|
35
55
|
}
|
|
56
|
+
|
|
36
57
|
function parseTemplate(src, values) {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
58
|
+
// Lightweight tagged template parser
|
|
59
|
+
// Supports: <tag attr="val">, <tag ...${spread}>, ${children}, self-closing
|
|
60
|
+
src = src.trim();
|
|
61
|
+
const nodes = [];
|
|
62
|
+
let i = 0;
|
|
63
|
+
|
|
64
|
+
while (i < src.length) {
|
|
65
|
+
if (src[i] === '<') {
|
|
66
|
+
const result = parseElement(src, i, values);
|
|
67
|
+
if (result) {
|
|
68
|
+
nodes.push(result.node);
|
|
69
|
+
i = result.end;
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
// Text or interpolation
|
|
74
|
+
const result = parseText(src, i, values);
|
|
75
|
+
if (result.text) nodes.push(result.text);
|
|
76
|
+
i = result.end;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return nodes.length === 1 ? nodes[0] : nodes;
|
|
80
|
+
}
|
|
81
|
+
|
|
55
82
|
function parseElement(src, start, values) {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
i++;
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
if (src.slice(i, i +
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
i
|
|
75
|
-
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
if (
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
i
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
props[attrName] =
|
|
106
|
-
|
|
107
|
-
} else {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
const
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
i +=
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
83
|
+
// Opening tag
|
|
84
|
+
const openMatch = src.slice(start).match(/^<([a-zA-Z][a-zA-Z0-9-]*|[A-Z]\w*)/);
|
|
85
|
+
if (!openMatch) return null;
|
|
86
|
+
|
|
87
|
+
const tag = openMatch[1];
|
|
88
|
+
let i = start + openMatch[0].length;
|
|
89
|
+
const props = {};
|
|
90
|
+
|
|
91
|
+
// Parse attributes
|
|
92
|
+
while (i < src.length) {
|
|
93
|
+
// Skip whitespace
|
|
94
|
+
while (i < src.length && /\s/.test(src[i])) i++;
|
|
95
|
+
|
|
96
|
+
// Self-closing or end of opening tag
|
|
97
|
+
if (src.slice(i, i + 2) === '/>') {
|
|
98
|
+
return { node: h(tag, Object.keys(props).length ? props : null), end: i + 2 };
|
|
99
|
+
}
|
|
100
|
+
if (src[i] === '>') {
|
|
101
|
+
i++;
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Spread: ...${obj}
|
|
106
|
+
if (src.slice(i, i + 3) === '...') {
|
|
107
|
+
const placeholder = src.slice(i + 3).match(/^\x00(\d+)\x00/);
|
|
108
|
+
if (placeholder) {
|
|
109
|
+
Object.assign(props, values[Number(placeholder[1])]);
|
|
110
|
+
i += 3 + placeholder[0].length;
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Attribute: name=${val} or name="val" or name
|
|
116
|
+
const attrMatch = src.slice(i).match(/^([a-zA-Z_@:][a-zA-Z0-9_:.-]*)/);
|
|
117
|
+
if (!attrMatch) break;
|
|
118
|
+
|
|
119
|
+
const attrName = attrMatch[1];
|
|
120
|
+
i += attrMatch[0].length;
|
|
121
|
+
|
|
122
|
+
// Skip whitespace around =
|
|
123
|
+
while (i < src.length && /\s/.test(src[i])) i++;
|
|
124
|
+
|
|
125
|
+
if (src[i] === '=') {
|
|
126
|
+
i++;
|
|
127
|
+
while (i < src.length && /\s/.test(src[i])) i++;
|
|
128
|
+
|
|
129
|
+
// Value is a placeholder
|
|
130
|
+
const ph = src.slice(i).match(/^\x00(\d+)\x00/);
|
|
131
|
+
if (ph) {
|
|
132
|
+
props[attrName] = values[Number(ph[1])];
|
|
133
|
+
i += ph[0].length;
|
|
134
|
+
} else if (src[i] === '"' || src[i] === "'") {
|
|
135
|
+
const q = src[i];
|
|
136
|
+
i++;
|
|
137
|
+
let val = '';
|
|
138
|
+
while (i < src.length && src[i] !== q) {
|
|
139
|
+
const tph = src.slice(i).match(/^\x00(\d+)\x00/);
|
|
140
|
+
if (tph) {
|
|
141
|
+
val += String(values[Number(tph[1])]);
|
|
142
|
+
i += tph[0].length;
|
|
143
|
+
} else {
|
|
144
|
+
val += src[i];
|
|
145
|
+
i++;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
i++; // closing quote
|
|
149
|
+
props[attrName] = val;
|
|
150
|
+
}
|
|
151
|
+
} else {
|
|
152
|
+
props[attrName] = true;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Parse children until closing tag
|
|
157
|
+
const children = [];
|
|
158
|
+
const closeTag = `</${tag}>`;
|
|
159
|
+
|
|
160
|
+
while (i < src.length) {
|
|
161
|
+
if (src.slice(i, i + closeTag.length) === closeTag) {
|
|
162
|
+
i += closeTag.length;
|
|
163
|
+
break;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (src[i] === '<') {
|
|
167
|
+
const child = parseElement(src, i, values);
|
|
168
|
+
if (child) {
|
|
169
|
+
children.push(child.node);
|
|
170
|
+
i = child.end;
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const text = parseText(src, i, values);
|
|
176
|
+
if (text.text != null) children.push(text.text);
|
|
177
|
+
i = text.end;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return {
|
|
181
|
+
node: h(tag, Object.keys(props).length ? props : null, ...children),
|
|
182
|
+
end: i,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
135
186
|
function parseText(src, start, values) {
|
|
136
|
-
let i = start;
|
|
137
|
-
let text = '';
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
if (
|
|
142
|
-
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
i
|
|
187
|
+
let i = start;
|
|
188
|
+
let text = '';
|
|
189
|
+
|
|
190
|
+
while (i < src.length && src[i] !== '<') {
|
|
191
|
+
const ph = src.slice(i).match(/^\x00(\d+)\x00/);
|
|
192
|
+
if (ph) {
|
|
193
|
+
if (text.trim()) {
|
|
194
|
+
return { text: text.trim(), end: i };
|
|
195
|
+
}
|
|
196
|
+
return { text: values[Number(ph[1])], end: i + ph[0].length };
|
|
197
|
+
}
|
|
198
|
+
text += src[i];
|
|
199
|
+
i++;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return { text: text.trim() || null, end: i };
|
|
148
203
|
}
|
|
149
|
-
return { text: text.trim() || null, end: i };
|
|
150
|
-
}
|
package/dist/head.js
CHANGED
|
@@ -1,51 +1,68 @@
|
|
|
1
|
+
// What Framework - Head Management
|
|
2
|
+
// Declarative <head> updates from any component.
|
|
3
|
+
// Supports title, meta, link, script tags. Auto-deduplicates by key.
|
|
4
|
+
|
|
1
5
|
const headState = {
|
|
2
|
-
title: null,
|
|
3
|
-
metas: new Map(),
|
|
4
|
-
links: new Map(),
|
|
6
|
+
title: null,
|
|
7
|
+
metas: new Map(),
|
|
8
|
+
links: new Map(),
|
|
5
9
|
};
|
|
10
|
+
|
|
11
|
+
// --- Head component ---
|
|
12
|
+
// Use in any component to set head tags. Last one wins for title/meta.
|
|
13
|
+
|
|
6
14
|
export function Head({ title, meta, link, children }) {
|
|
7
|
-
if (typeof document === 'undefined') return null;
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
15
|
+
if (typeof document === 'undefined') return null;
|
|
16
|
+
|
|
17
|
+
if (title) {
|
|
18
|
+
document.title = title;
|
|
19
|
+
headState.title = title;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (meta) {
|
|
23
|
+
for (const attrs of (Array.isArray(meta) ? meta : [meta])) {
|
|
24
|
+
const key = attrs.name || attrs.property || attrs.httpEquiv || JSON.stringify(attrs);
|
|
25
|
+
setHeadTag('meta', key, attrs);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (link) {
|
|
30
|
+
for (const attrs of (Array.isArray(link) ? link : [link])) {
|
|
31
|
+
const key = attrs.rel + (attrs.href || '');
|
|
32
|
+
setHeadTag('link', key, attrs);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return children || null;
|
|
25
37
|
}
|
|
38
|
+
|
|
26
39
|
function setHeadTag(tag, key, attrs) {
|
|
27
|
-
const existing = document.head.querySelector(`[data-what-head="${key}"]`);
|
|
28
|
-
if (existing) {
|
|
29
|
-
updateElement(existing, attrs);
|
|
30
|
-
return;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
el.
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
40
|
+
const existing = document.head.querySelector(`[data-what-head="${key}"]`);
|
|
41
|
+
if (existing) {
|
|
42
|
+
updateElement(existing, attrs);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const el = document.createElement(tag);
|
|
47
|
+
el.setAttribute('data-what-head', key);
|
|
48
|
+
for (const [k, v] of Object.entries(attrs)) {
|
|
49
|
+
el.setAttribute(k, v);
|
|
50
|
+
}
|
|
51
|
+
document.head.appendChild(el);
|
|
38
52
|
}
|
|
53
|
+
|
|
39
54
|
function updateElement(el, attrs) {
|
|
40
|
-
for (const [k, v] of Object.entries(attrs)) {
|
|
41
|
-
if (el.getAttribute(k) !== v) {
|
|
42
|
-
el.setAttribute(k, v);
|
|
43
|
-
}
|
|
44
|
-
}
|
|
55
|
+
for (const [k, v] of Object.entries(attrs)) {
|
|
56
|
+
if (el.getAttribute(k) !== v) {
|
|
57
|
+
el.setAttribute(k, v);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
45
60
|
}
|
|
61
|
+
|
|
62
|
+
// --- Cleanup: remove head tags added by What ---
|
|
46
63
|
export function clearHead() {
|
|
47
|
-
const tags = document.head.querySelectorAll('[data-what-head]');
|
|
48
|
-
for (const tag of tags) tag.remove();
|
|
49
|
-
headState.metas.clear();
|
|
50
|
-
headState.links.clear();
|
|
51
|
-
}
|
|
64
|
+
const tags = document.head.querySelectorAll('[data-what-head]');
|
|
65
|
+
for (const tag of tags) tag.remove();
|
|
66
|
+
headState.metas.clear();
|
|
67
|
+
headState.links.clear();
|
|
68
|
+
}
|