what-core 0.4.2 → 0.5.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/dist/h.js CHANGED
@@ -1,203 +1,152 @@
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
-
8
1
  const EMPTY_OBJ = {};
9
2
  const EMPTY_ARR = [];
10
-
11
3
  export function h(tag, props, ...children) {
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
4
+ props = props || EMPTY_OBJ;
5
+ const flat = flattenChildren(children);
6
+ const key = props.key ?? null;
7
+ if (props.key !== undefined) {
8
+ props = { ...props };
9
+ delete props.key;
10
+ }
11
+ return { tag, props, children: flat, key, _vnode: true };
12
+ }
26
13
  export function Fragment({ children }) {
27
- return children;
14
+ return children;
28
15
  }
29
-
30
16
  function flattenChildren(children) {
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
-
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 if (typeof child === 'function') {
26
+ out.push(child);
27
+ } else {
28
+ out.push(String(child));
29
+ }
30
+ }
31
+ return out;
32
+ }
51
33
  export function html(strings, ...values) {
52
- const src = strings.reduce((acc, str, i) =>
53
- acc + str + (i < values.length ? `\x00${i}\x00` : ''), '');
54
- return parseTemplate(src, values);
34
+ const src = strings.reduce((acc, str, i) =>
35
+ acc + str + (i < values.length ? `\x00${i}\x00` : ''), '');
36
+ return parseTemplate(src, values);
55
37
  }
56
-
57
38
  function parseTemplate(src, values) {
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
-
39
+ src = src.trim();
40
+ const nodes = [];
41
+ let i = 0;
42
+ while (i < src.length) {
43
+ if (src[i] === '<') {
44
+ const result = parseElement(src, i, values);
45
+ if (result) {
46
+ nodes.push(result.node);
47
+ i = result.end;
48
+ continue;
49
+ }
50
+ }
51
+ const result = parseText(src, i, values);
52
+ if (result.text) nodes.push(result.text);
53
+ i = result.end;
54
+ }
55
+ return nodes.length === 1 ? nodes[0] : nodes;
56
+ }
82
57
  function parseElement(src, start, values) {
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
-
58
+ const openMatch = src.slice(start).match(/^<([a-zA-Z][a-zA-Z0-9-]*|[A-Z]\w*)/);
59
+ if (!openMatch) return null;
60
+ const tag = openMatch[1];
61
+ let i = start + openMatch[0].length;
62
+ const props = {};
63
+ while (i < src.length) {
64
+ while (i < src.length && /\s/.test(src[i])) i++;
65
+ if (src.slice(i, i + 2) === '/>') {
66
+ return { node: h(tag, Object.keys(props).length ? props : null), end: i + 2 };
67
+ }
68
+ if (src[i] === '>') {
69
+ i++;
70
+ break;
71
+ }
72
+ if (src.slice(i, i + 3) === '...') {
73
+ const placeholder = src.slice(i + 3).match(/^\x00(\d+)\x00/);
74
+ if (placeholder) {
75
+ Object.assign(props, values[Number(placeholder[1])]);
76
+ i += 3 + placeholder[0].length;
77
+ continue;
78
+ }
79
+ }
80
+ const attrMatch = src.slice(i).match(/^([a-zA-Z_@:][a-zA-Z0-9_:.-]*)/);
81
+ if (!attrMatch) break;
82
+ const attrName = attrMatch[1];
83
+ i += attrMatch[0].length;
84
+ while (i < src.length && /\s/.test(src[i])) i++;
85
+ if (src[i] === '=') {
86
+ i++;
87
+ while (i < src.length && /\s/.test(src[i])) i++;
88
+ const ph = src.slice(i).match(/^\x00(\d+)\x00/);
89
+ if (ph) {
90
+ props[attrName] = values[Number(ph[1])];
91
+ i += ph[0].length;
92
+ } else if (src[i] === '"' || src[i] === "'") {
93
+ const q = src[i];
94
+ i++;
95
+ let val = '';
96
+ while (i < src.length && src[i] !== q) {
97
+ const tph = src.slice(i).match(/^\x00(\d+)\x00/);
98
+ if (tph) {
99
+ val += String(values[Number(tph[1])]);
100
+ i += tph[0].length;
101
+ } else {
102
+ val += src[i];
103
+ i++;
104
+ }
105
+ }
106
+ i++;
107
+ props[attrName] = val;
108
+ }
109
+ } else {
110
+ props[attrName] = true;
111
+ }
112
+ }
113
+ const children = [];
114
+ const closeTag = `</${tag}>`;
115
+ while (i < src.length) {
116
+ if (src.slice(i, i + closeTag.length) === closeTag) {
117
+ i += closeTag.length;
118
+ break;
119
+ }
120
+ if (src[i] === '<') {
121
+ const child = parseElement(src, i, values);
122
+ if (child) {
123
+ children.push(child.node);
124
+ i = child.end;
125
+ continue;
126
+ }
127
+ }
128
+ const text = parseText(src, i, values);
129
+ if (text.text != null) children.push(text.text);
130
+ i = text.end;
131
+ }
132
+ return {
133
+ node: h(tag, Object.keys(props).length ? props : null, ...children),
134
+ end: i,
135
+ };
136
+ }
186
137
  function parseText(src, start, values) {
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 };
138
+ let i = start;
139
+ let text = '';
140
+ while (i < src.length && src[i] !== '<') {
141
+ const ph = src.slice(i).match(/^\x00(\d+)\x00/);
142
+ if (ph) {
143
+ if (text.trim()) {
144
+ return { text: text.trim(), end: i };
145
+ }
146
+ return { text: values[Number(ph[1])], end: i + ph[0].length };
147
+ }
148
+ text += src[i];
149
+ i++;
203
150
  }
151
+ return { text: text.trim() || null, end: i };
152
+ }
package/dist/head.js CHANGED
@@ -1,68 +1,51 @@
1
- // What Framework - Head Management
2
- // Declarative <head> updates from any component.
3
- // Supports title, meta, link, script tags. Auto-deduplicates by key.
4
-
5
1
  const headState = {
6
- title: null,
7
- metas: new Map(),
8
- links: new Map(),
2
+ title: null,
3
+ metas: new Map(),
4
+ links: new Map(),
9
5
  };
10
-
11
- // --- Head component ---
12
- // Use in any component to set head tags. Last one wins for title/meta.
13
-
14
6
  export function Head({ title, meta, link, children }) {
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;
7
+ if (typeof document === 'undefined') return null;
8
+ if (title) {
9
+ document.title = title;
10
+ headState.title = title;
11
+ }
12
+ if (meta) {
13
+ for (const attrs of (Array.isArray(meta) ? meta : [meta])) {
14
+ const key = attrs.name || attrs.property || attrs.httpEquiv || JSON.stringify(attrs);
15
+ setHeadTag('meta', key, attrs);
16
+ }
17
+ }
18
+ if (link) {
19
+ for (const attrs of (Array.isArray(link) ? link : [link])) {
20
+ const key = attrs.rel + (attrs.href || '');
21
+ setHeadTag('link', key, attrs);
22
+ }
23
+ }
24
+ return children || null;
37
25
  }
38
-
39
26
  function setHeadTag(tag, key, attrs) {
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);
27
+ const existing = document.head.querySelector(`[data-what-head="${key}"]`);
28
+ if (existing) {
29
+ updateElement(existing, attrs);
30
+ return;
31
+ }
32
+ const el = document.createElement(tag);
33
+ el.setAttribute('data-what-head', key);
34
+ for (const [k, v] of Object.entries(attrs)) {
35
+ el.setAttribute(k, v);
36
+ }
37
+ document.head.appendChild(el);
52
38
  }
53
-
54
39
  function updateElement(el, attrs) {
55
- for (const [k, v] of Object.entries(attrs)) {
56
- if (el.getAttribute(k) !== v) {
57
- el.setAttribute(k, v);
58
- }
59
- }
40
+ for (const [k, v] of Object.entries(attrs)) {
41
+ if (el.getAttribute(k) !== v) {
42
+ el.setAttribute(k, v);
60
43
  }
61
-
62
- // --- Cleanup: remove head tags added by What ---
63
- export function clearHead() {
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
44
  }
45
+ }
46
+ 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
+ }