aberdeen 1.0.0 → 1.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 +137 -141
- package/dist/aberdeen.d.ts +15 -5
- package/dist/aberdeen.js +15 -10
- package/dist/aberdeen.js.map +3 -3
- package/dist-min/aberdeen.js +3 -3
- package/dist-min/aberdeen.js.map +3 -3
- package/html-to-aberdeen +354 -0
- package/package.json +9 -6
- package/src/aberdeen.ts +29 -15
package/html-to-aberdeen
ADDED
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// WARNING: This script was created by Claude Sonnet 3.7, and hasn't
|
|
4
|
+
// received any human code review. It seems to do the job though!
|
|
5
|
+
|
|
6
|
+
export function parseHTML(html) {
|
|
7
|
+
const result = {
|
|
8
|
+
body: []
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
let currentPosition = 0;
|
|
12
|
+
let currentParent = result;
|
|
13
|
+
const stack = [];
|
|
14
|
+
|
|
15
|
+
while (currentPosition < html.length) {
|
|
16
|
+
// Skip whitespace
|
|
17
|
+
while (currentPosition < html.length && /\s/.test(html[currentPosition])) {
|
|
18
|
+
currentPosition++;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (currentPosition >= html.length) break;
|
|
22
|
+
|
|
23
|
+
// Check for comment
|
|
24
|
+
if (html.substring(currentPosition, currentPosition + 4) === '<!--') {
|
|
25
|
+
const endComment = html.indexOf('-->', currentPosition);
|
|
26
|
+
if (endComment === -1) break;
|
|
27
|
+
|
|
28
|
+
const commentContent = html.substring(currentPosition + 4, endComment);
|
|
29
|
+
currentParent.children = currentParent.children || [];
|
|
30
|
+
currentParent.children.push({
|
|
31
|
+
type: 'comment',
|
|
32
|
+
content: commentContent
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
currentPosition = endComment + 3;
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Check for tag
|
|
40
|
+
if (html[currentPosition] === '<') {
|
|
41
|
+
// Check if it's a closing tag
|
|
42
|
+
if (html[currentPosition + 1] === '/') {
|
|
43
|
+
const endTag = html.indexOf('>', currentPosition);
|
|
44
|
+
if (endTag === -1) break;
|
|
45
|
+
|
|
46
|
+
const tagName = html.substring(currentPosition + 2, endTag).trim().toLowerCase();
|
|
47
|
+
|
|
48
|
+
// Pop from stack
|
|
49
|
+
if (stack.length > 0) {
|
|
50
|
+
currentParent = stack.pop();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
currentPosition = endTag + 1;
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// It's an opening tag
|
|
58
|
+
const endTag = html.indexOf('>', currentPosition);
|
|
59
|
+
if (endTag === -1) break;
|
|
60
|
+
|
|
61
|
+
const selfClosing = html[endTag - 1] === '/';
|
|
62
|
+
const tagContent = html.substring(currentPosition + 1, selfClosing ? endTag - 1 : endTag).trim();
|
|
63
|
+
const spaceIndex = tagContent.search(/\s/);
|
|
64
|
+
|
|
65
|
+
let tagName, attributesStr;
|
|
66
|
+
if (spaceIndex === -1) {
|
|
67
|
+
tagName = tagContent;
|
|
68
|
+
attributesStr = '';
|
|
69
|
+
} else {
|
|
70
|
+
tagName = tagContent.substring(0, spaceIndex);
|
|
71
|
+
attributesStr = tagContent.substring(spaceIndex + 1);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
tagName = tagName.toLowerCase();
|
|
75
|
+
|
|
76
|
+
// Parse attributes
|
|
77
|
+
const attributes = [];
|
|
78
|
+
let attrMatch;
|
|
79
|
+
const attrRegex = /([\w-]+)(?:=(?:"([^"]*)"|'([^']*)'|(\S+)))?/g;
|
|
80
|
+
|
|
81
|
+
while ((attrMatch = attrRegex.exec(attributesStr)) !== null) {
|
|
82
|
+
const name = attrMatch[1];
|
|
83
|
+
const value = attrMatch[2] || attrMatch[3] || attrMatch[4] || '';
|
|
84
|
+
attributes.push({ name, value });
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const newElement = {
|
|
88
|
+
type: 'element',
|
|
89
|
+
tagName,
|
|
90
|
+
attributes,
|
|
91
|
+
children: []
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
// Add to current parent
|
|
95
|
+
if (currentParent === result) {
|
|
96
|
+
currentParent.body = currentParent.body || [];
|
|
97
|
+
currentParent.body.push(newElement);
|
|
98
|
+
} else {
|
|
99
|
+
currentParent.children = currentParent.children || [];
|
|
100
|
+
currentParent.children.push(newElement);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (!selfClosing && !['br', 'hr', 'img', 'input', 'link', 'meta'].includes(tagName)) {
|
|
104
|
+
stack.push(currentParent);
|
|
105
|
+
currentParent = newElement;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
currentPosition = endTag + 1;
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// It's text content
|
|
113
|
+
let endText = html.indexOf('<', currentPosition);
|
|
114
|
+
if (endText === -1) endText = html.length;
|
|
115
|
+
|
|
116
|
+
const textContent = html.substring(currentPosition, endText);
|
|
117
|
+
if (textContent.trim()) {
|
|
118
|
+
if (currentParent === result) {
|
|
119
|
+
currentParent.body = currentParent.body || [];
|
|
120
|
+
currentParent.body.push({
|
|
121
|
+
type: 'text',
|
|
122
|
+
content: textContent
|
|
123
|
+
});
|
|
124
|
+
} else {
|
|
125
|
+
currentParent.children = currentParent.children || [];
|
|
126
|
+
currentParent.children.push({
|
|
127
|
+
type: 'text',
|
|
128
|
+
content: textContent
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
currentPosition = endText;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return result;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Read from stdin
|
|
140
|
+
let html = '';
|
|
141
|
+
process.stdin.setEncoding('utf8');
|
|
142
|
+
process.stdin.on('data', (chunk) => {
|
|
143
|
+
html += chunk;
|
|
144
|
+
});
|
|
145
|
+
process.stdin.on('end', () => {
|
|
146
|
+
// Convert HTML to Aberdeen code
|
|
147
|
+
const aberdeenCode = convertHTMLToAberdeen(html);
|
|
148
|
+
|
|
149
|
+
// Output to stdout
|
|
150
|
+
process.stdout.write(aberdeenCode);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
// Main conversion function
|
|
154
|
+
function convertHTMLToAberdeen(html) {
|
|
155
|
+
// Parse HTML into a simple AST
|
|
156
|
+
const ast = parseHTML(html);
|
|
157
|
+
|
|
158
|
+
// Generate the Aberdeen code
|
|
159
|
+
let aberdeenCode = ``;
|
|
160
|
+
|
|
161
|
+
// Process the body's children
|
|
162
|
+
for (const node of ast.body) {
|
|
163
|
+
aberdeenCode += processNode(node);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return aberdeenCode;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Process a node and return Aberdeen code
|
|
170
|
+
function processNode(node, indentLevel = 0) {
|
|
171
|
+
const indent = ' '.repeat(indentLevel);
|
|
172
|
+
|
|
173
|
+
if (node.type === 'text') {
|
|
174
|
+
const text = node.content.trim();
|
|
175
|
+
if (text) {
|
|
176
|
+
return `${indent}$(':${escapeString(text)}');\n`;
|
|
177
|
+
}
|
|
178
|
+
return '';
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (node.type === 'comment') {
|
|
182
|
+
return `${indent}// ${node.content.trim()}\n`;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (node.type === 'element') {
|
|
186
|
+
// Get tag name
|
|
187
|
+
const tagName = node.tagName.toLowerCase();
|
|
188
|
+
|
|
189
|
+
// Get classes
|
|
190
|
+
const classAttr = node.attributes.find(attr => attr.name === 'class');
|
|
191
|
+
const classes = classAttr
|
|
192
|
+
? classAttr.value.split(/\s+/).filter(Boolean).join('.')
|
|
193
|
+
: '';
|
|
194
|
+
|
|
195
|
+
// Get other attributes
|
|
196
|
+
const attributes = {};
|
|
197
|
+
for (const attr of node.attributes) {
|
|
198
|
+
if (attr.name !== 'class') {
|
|
199
|
+
attributes[attr.name] = attr.value;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Check if node has only text content
|
|
204
|
+
const hasOnlyTextContent =
|
|
205
|
+
node.children.length === 1 &&
|
|
206
|
+
node.children[0].type === 'text' &&
|
|
207
|
+
node.children[0].content.trim();
|
|
208
|
+
|
|
209
|
+
// Build the tag string
|
|
210
|
+
let tagString = tagName;
|
|
211
|
+
if (classes) {
|
|
212
|
+
tagString += `.${classes}`;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (hasOnlyTextContent) {
|
|
216
|
+
tagString += `:${escapeString(node.children[0].content.trim())}`;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
let result = `${indent}$('${tagString}'`;
|
|
220
|
+
|
|
221
|
+
// Add attributes if any
|
|
222
|
+
if (Object.keys(attributes).length > 0) {
|
|
223
|
+
result += `, ${formatAttributes(attributes, indent)}`;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Process child nodes
|
|
227
|
+
const childNodes = node.children.filter(child =>
|
|
228
|
+
child.type === 'element' ||
|
|
229
|
+
(child.type === 'text' && child.content.trim())
|
|
230
|
+
);
|
|
231
|
+
|
|
232
|
+
if (childNodes.length > 0 && !hasOnlyTextContent) {
|
|
233
|
+
// Get all descendants in a single-child chain
|
|
234
|
+
const singleChildChain = getSingleChildChain(node);
|
|
235
|
+
|
|
236
|
+
if (singleChildChain.length > 1) {
|
|
237
|
+
// We have a chain of single children, add them all on the same line
|
|
238
|
+
for (let i = 1; i < singleChildChain.length; i++) {
|
|
239
|
+
const chainNode = singleChildChain[i];
|
|
240
|
+
|
|
241
|
+
// Get tag name
|
|
242
|
+
const chainTagName = chainNode.tagName.toLowerCase();
|
|
243
|
+
|
|
244
|
+
// Get classes
|
|
245
|
+
const chainClassAttr = chainNode.attributes.find(attr => attr.name === 'class');
|
|
246
|
+
const chainClasses = chainClassAttr
|
|
247
|
+
? chainClassAttr.value.split(/\s+/).filter(Boolean).join('.')
|
|
248
|
+
: '';
|
|
249
|
+
|
|
250
|
+
// Build the tag string
|
|
251
|
+
let chainTagString = chainTagName;
|
|
252
|
+
if (chainClasses) {
|
|
253
|
+
chainTagString += `.${chainClasses}`;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Check if node has only text content
|
|
257
|
+
const chainHasOnlyTextContent =
|
|
258
|
+
chainNode.children.length === 1 &&
|
|
259
|
+
chainNode.children[0].type === 'text' &&
|
|
260
|
+
chainNode.children[0].content.trim();
|
|
261
|
+
|
|
262
|
+
if (chainHasOnlyTextContent) {
|
|
263
|
+
chainTagString += `:${escapeString(chainNode.children[0].content.trim())}`;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
result += `, '${chainTagString}'`;
|
|
267
|
+
|
|
268
|
+
// Add attributes if any
|
|
269
|
+
const chainAttributes = {};
|
|
270
|
+
for (const attr of chainNode.attributes) {
|
|
271
|
+
if (attr.name !== 'class') {
|
|
272
|
+
chainAttributes[attr.name] = attr.value;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if (Object.keys(chainAttributes).length > 0) {
|
|
277
|
+
result += `, ${formatAttributes(chainAttributes, indent)}`;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Check if the last node in the chain has any non-text children
|
|
282
|
+
const lastNode = singleChildChain[singleChildChain.length - 1];
|
|
283
|
+
const lastNodeChildren = lastNode.children.filter(child =>
|
|
284
|
+
child.type === 'element' ||
|
|
285
|
+
(child.type === 'text' && child.content.trim() &&
|
|
286
|
+
!(lastNode.children.length === 1 && lastNode.children[0].type === 'text'))
|
|
287
|
+
);
|
|
288
|
+
|
|
289
|
+
if (lastNodeChildren.length > 0) {
|
|
290
|
+
result += `, () => {\n`;
|
|
291
|
+
for (const child of lastNodeChildren) {
|
|
292
|
+
result += processNode(child, indentLevel + 1);
|
|
293
|
+
}
|
|
294
|
+
result += `${indent}}`;
|
|
295
|
+
}
|
|
296
|
+
} else {
|
|
297
|
+
// Multiple children, use a content function
|
|
298
|
+
result += `, () => {\n`;
|
|
299
|
+
for (const child of childNodes) {
|
|
300
|
+
result += processNode(child, indentLevel + 1);
|
|
301
|
+
}
|
|
302
|
+
result += `${indent}}`;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
result += `);\n`;
|
|
307
|
+
return result;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
return '';
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// Get a chain of nodes where each node has exactly one element child
|
|
314
|
+
function getSingleChildChain(node) {
|
|
315
|
+
const chain = [node];
|
|
316
|
+
let current = node;
|
|
317
|
+
|
|
318
|
+
while (true) {
|
|
319
|
+
// Get element children
|
|
320
|
+
const elementChildren = current.children.filter(child => child.type === 'element');
|
|
321
|
+
|
|
322
|
+
// If there's exactly one element child, add it to the chain
|
|
323
|
+
if (elementChildren.length === 1) {
|
|
324
|
+
current = elementChildren[0];
|
|
325
|
+
chain.push(current);
|
|
326
|
+
} else {
|
|
327
|
+
break;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
return chain;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// Format attributes object with proper indentation
|
|
335
|
+
function formatAttributes(attributes, indent) {
|
|
336
|
+
const attrLines = JSON.stringify(attributes, null, 4).split('\n');
|
|
337
|
+
|
|
338
|
+
if (attrLines.length <= 1) {
|
|
339
|
+
return JSON.stringify(attributes);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
return attrLines.map((line, i) => {
|
|
343
|
+
if (i === 0) return line;
|
|
344
|
+
return indent + line;
|
|
345
|
+
}).join('\n');
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// Escape special characters in strings
|
|
349
|
+
function escapeString(str) {
|
|
350
|
+
return str
|
|
351
|
+
.replace(/\\/g, '\\\\')
|
|
352
|
+
.replace(/'/g, "\\'")
|
|
353
|
+
.replace(/\n/g, '\\n');
|
|
354
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aberdeen",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.5",
|
|
4
4
|
"author": "Frank van Viegen",
|
|
5
5
|
"main": "dist-min/aberdeen.js",
|
|
6
6
|
"devDependencies": {
|
|
@@ -12,31 +12,34 @@
|
|
|
12
12
|
".": {
|
|
13
13
|
"default": "./dist-min/aberdeen.js",
|
|
14
14
|
"development": "./dist/aberdeen.js",
|
|
15
|
-
"types
|
|
15
|
+
"types": "./dist/aberdeen.d.ts"
|
|
16
16
|
},
|
|
17
17
|
"./route": {
|
|
18
18
|
"default": "./dist-min/route.js",
|
|
19
19
|
"development": "./dist/route.js",
|
|
20
|
-
"types
|
|
20
|
+
"types": "./dist/route.d.ts"
|
|
21
21
|
},
|
|
22
22
|
"./transition": {
|
|
23
23
|
"default": "./dist-min/transition.js",
|
|
24
24
|
"development": "./dist/transition.js",
|
|
25
|
-
"types
|
|
25
|
+
"types": "./dist/transition.d.ts"
|
|
26
26
|
},
|
|
27
27
|
"./prediction": {
|
|
28
28
|
"default": "./dist-min/prediction.js",
|
|
29
29
|
"development": "./dist/prediction.js",
|
|
30
|
-
"types
|
|
30
|
+
"types": "./dist/prediction.d.ts"
|
|
31
31
|
},
|
|
32
32
|
"./package.json": "./package.json"
|
|
33
33
|
},
|
|
34
|
-
"description": "
|
|
34
|
+
"description": "Build fast reactive UIs in pure TypeScript/JavaScript without a virtual DOM.",
|
|
35
35
|
"files": [
|
|
36
36
|
"dist",
|
|
37
37
|
"dist-min",
|
|
38
38
|
"src"
|
|
39
39
|
],
|
|
40
|
+
"bin": {
|
|
41
|
+
"html-to-aberdeen": "./html-to-aberdeen"
|
|
42
|
+
},
|
|
40
43
|
"license": "MIT",
|
|
41
44
|
"scripts": {
|
|
42
45
|
"build": "bun run build:dist && typedoc && rm -rf dist-docs/assets/aberdeen ; cp -r dist dist-docs/assets/aberdeen",
|
package/src/aberdeen.ts
CHANGED
|
@@ -1374,8 +1374,7 @@ export function ref<T extends TargetType, K extends keyof T>(target: T, index: K
|
|
|
1374
1374
|
}
|
|
1375
1375
|
|
|
1376
1376
|
|
|
1377
|
-
function applyBind(
|
|
1378
|
-
const el = _el as HTMLInputElement;
|
|
1377
|
+
function applyBind(el: HTMLInputElement, target: any) {
|
|
1379
1378
|
let onProxyChange: () => void;
|
|
1380
1379
|
let onInputChange: () => void;
|
|
1381
1380
|
let type = el.getAttribute('type');
|
|
@@ -1393,7 +1392,10 @@ function applyBind(_el: Element, target: any) {
|
|
|
1393
1392
|
} else {
|
|
1394
1393
|
onInputChange = () => target.value = type==='number' || type==='range' ? (el.value==='' ? null : +el.value) : el.value;
|
|
1395
1394
|
if (value === undefined) onInputChange();
|
|
1396
|
-
onProxyChange = () =>
|
|
1395
|
+
onProxyChange = () => {
|
|
1396
|
+
el.value = target.value;
|
|
1397
|
+
if (el.tagName==='SELECT' && el.value != target.value) throw new Error(`SELECT has no '${target.value}' OPTION (yet)`);
|
|
1398
|
+
}
|
|
1397
1399
|
}
|
|
1398
1400
|
observe(onProxyChange);
|
|
1399
1401
|
el.addEventListener('input', onInputChange);
|
|
@@ -1472,22 +1474,32 @@ const SPECIAL_PROPS: {[key: string]: (value: any) => void} = {
|
|
|
1472
1474
|
* - `{html: string}`: Add the value as HTML to the *current* element. This should only be used in exceptional situations. And of course, beware of XSS.
|
|
1473
1475
|
* - `{element: Node}`: Add a pre-existing HTML `Node` to the *current* element.
|
|
1474
1476
|
*
|
|
1477
|
+
* @returns The most inner DOM element that was created (not counting text nodes nor elements created by content functions),
|
|
1478
|
+
* or undefined if no elements were created.
|
|
1475
1479
|
*
|
|
1476
1480
|
* @example Create Element
|
|
1477
1481
|
* ```typescript
|
|
1478
1482
|
* $('button.secondary.outline:Submit', {
|
|
1479
|
-
* disabled:
|
|
1483
|
+
* disabled: false,
|
|
1480
1484
|
* click: () => console.log('Clicked!'),
|
|
1481
1485
|
* $color: 'red'
|
|
1482
1486
|
* });
|
|
1483
1487
|
* ```
|
|
1488
|
+
*
|
|
1489
|
+
* @example Create Nested Elements
|
|
1490
|
+
* ```typescript
|
|
1491
|
+
* let inputElement: Element = $('label:Click me', 'input', {type: 'checkbox'});
|
|
1492
|
+
* // You should usually not touch raw DOM elements, unless when integrating
|
|
1493
|
+
* // with non-Aberdeen code.
|
|
1494
|
+
* console.log('DOM element:', inputElement);
|
|
1495
|
+
* ```
|
|
1484
1496
|
*
|
|
1485
|
-
* @example
|
|
1497
|
+
* @example Content Functions & Reactive Scope
|
|
1486
1498
|
* ```typescript
|
|
1487
1499
|
* const state = proxy({ count: 0 });
|
|
1488
1500
|
* $('div', () => { // Outer element
|
|
1489
1501
|
* // This scope re-renders when state.count changes
|
|
1490
|
-
* $(
|
|
1502
|
+
* $(`p:Count is ${state.count}`);
|
|
1491
1503
|
* $('button:Increment', { click: () => state.count++ });
|
|
1492
1504
|
* });
|
|
1493
1505
|
* ```
|
|
@@ -1514,9 +1526,10 @@ const SPECIAL_PROPS: {[key: string]: (value: any) => void} = {
|
|
|
1514
1526
|
*/
|
|
1515
1527
|
|
|
1516
1528
|
|
|
1517
|
-
export function $(...args: (string | null | undefined | false | (() => void) | Record<string,any>)[]): void {
|
|
1529
|
+
export function $(...args: (string | null | undefined | false | (() => void) | Record<string,any>)[]): void | Element {
|
|
1518
1530
|
let savedCurrentScope;
|
|
1519
1531
|
let err;
|
|
1532
|
+
let result;
|
|
1520
1533
|
|
|
1521
1534
|
for(let arg of args) {
|
|
1522
1535
|
if (arg == null || arg === false) continue;
|
|
@@ -1545,15 +1558,15 @@ export function $(...args: (string | null | undefined | false | (() => void) | R
|
|
|
1545
1558
|
err = `Tag '${arg}' cannot contain space`;
|
|
1546
1559
|
break;
|
|
1547
1560
|
} else {
|
|
1548
|
-
|
|
1549
|
-
if (classes)
|
|
1550
|
-
if (text)
|
|
1551
|
-
addNode(
|
|
1561
|
+
result = document.createElement(arg);
|
|
1562
|
+
if (classes) result.className = classes.replaceAll('.', ' ');
|
|
1563
|
+
if (text) result.textContent = text;
|
|
1564
|
+
addNode(result);
|
|
1552
1565
|
if (!savedCurrentScope) {
|
|
1553
1566
|
savedCurrentScope = currentScope;
|
|
1554
1567
|
}
|
|
1555
|
-
let newScope = new ChainedScope(
|
|
1556
|
-
newScope.lastChild =
|
|
1568
|
+
let newScope = new ChainedScope(result, true);
|
|
1569
|
+
newScope.lastChild = result.lastChild || undefined;
|
|
1557
1570
|
if (topRedrawScope === currentScope) topRedrawScope = newScope;
|
|
1558
1571
|
currentScope = newScope;
|
|
1559
1572
|
}
|
|
@@ -1578,6 +1591,7 @@ export function $(...args: (string | null | undefined | false | (() => void) | R
|
|
|
1578
1591
|
currentScope = savedCurrentScope;
|
|
1579
1592
|
}
|
|
1580
1593
|
if (err) throw new Error(err);
|
|
1594
|
+
return result;
|
|
1581
1595
|
}
|
|
1582
1596
|
|
|
1583
1597
|
let cssCount = 0;
|
|
@@ -1671,7 +1685,7 @@ function applyArg(key: string, value: any) {
|
|
|
1671
1685
|
const el = currentScope.parentElement;
|
|
1672
1686
|
if (typeof value === 'object' && value !== null && value[TARGET_SYMBOL]) { // Value is a proxy
|
|
1673
1687
|
if (key === 'bind') {
|
|
1674
|
-
applyBind(el, value)
|
|
1688
|
+
applyBind(el as HTMLInputElement, value)
|
|
1675
1689
|
} else {
|
|
1676
1690
|
new SetArgScope(el, key, value)
|
|
1677
1691
|
// SetArgScope will (repeatedly) call `applyArg` again with the actual value
|
|
@@ -1766,7 +1780,7 @@ export function setErrorHandler(handler?: (error: Error) => boolean | undefined)
|
|
|
1766
1780
|
* call or a {@link $} element's render function).
|
|
1767
1781
|
*
|
|
1768
1782
|
* **Note:** While this provides access to the DOM element, directly manipulating it outside
|
|
1769
|
-
* of Aberdeen's control is generally discouraged. Prefer
|
|
1783
|
+
* of Aberdeen's control is generally discouraged. Prefer reactive updates using {@link $}.
|
|
1770
1784
|
*
|
|
1771
1785
|
* @returns The current parent `Element` for DOM insertion.
|
|
1772
1786
|
*
|