aberdeen 1.0.7 → 1.0.10
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 +1 -1
- package/dist/aberdeen.d.ts +2 -2
- package/dist/aberdeen.js +35 -12
- package/dist/aberdeen.js.map +4 -4
- package/dist/helpers/reverseSortedSet.d.ts +8 -3
- package/dist-min/aberdeen.js +5 -5
- package/dist-min/aberdeen.js.map +4 -4
- package/html-to-aberdeen +354 -0
- package/package.json +1 -1
- package/src/aberdeen.ts +63 -19
- package/src/helpers/reverseSortedSet.ts +34 -31
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
package/src/aberdeen.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ReverseSortedSet } from "./helpers/reverseSortedSet.js";
|
|
2
|
+
import type { ReverseSortedSetPointer } from "./helpers/reverseSortedSet.js";
|
|
2
3
|
|
|
3
4
|
/*
|
|
4
5
|
* QueueRunner
|
|
@@ -9,9 +10,11 @@ import { ReverseSortedSet } from "./helpers/reverseSortedSet.js";
|
|
|
9
10
|
interface QueueRunner {
|
|
10
11
|
prio: number; // Higher values have higher priority
|
|
11
12
|
queueRun(): void;
|
|
13
|
+
|
|
14
|
+
[ptr: ReverseSortedSetPointer]: QueueRunner;
|
|
12
15
|
}
|
|
13
16
|
|
|
14
|
-
let sortedQueue: ReverseSortedSet<QueueRunner> | undefined; // When set, a runQueue is scheduled or currently running.
|
|
17
|
+
let sortedQueue: ReverseSortedSet<QueueRunner, "prio"> | undefined; // When set, a runQueue is scheduled or currently running.
|
|
15
18
|
let runQueueDepth = 0; // Incremented when a queue event causes another queue event to be added. Reset when queue is empty. Throw when >= 42 to break (infinite) recursion.
|
|
16
19
|
let topRedrawScope: Scope | undefined; // The scope that triggered the current redraw. Elements drawn at this scope level may trigger 'create' animations.
|
|
17
20
|
|
|
@@ -28,7 +31,7 @@ export type DatumType =
|
|
|
28
31
|
|
|
29
32
|
function queue(runner: QueueRunner) {
|
|
30
33
|
if (!sortedQueue) {
|
|
31
|
-
sortedQueue = new ReverseSortedSet<QueueRunner>("prio");
|
|
34
|
+
sortedQueue = new ReverseSortedSet<QueueRunner, "prio">("prio");
|
|
32
35
|
setTimeout(runQueue, 0);
|
|
33
36
|
} else if (!(runQueueDepth & 1)) {
|
|
34
37
|
runQueueDepth++; // Make it uneven
|
|
@@ -111,9 +114,9 @@ function partToStr(part: number | string): string {
|
|
|
111
114
|
* 1 - separator between string array items
|
|
112
115
|
* 65535 - for compatibility
|
|
113
116
|
*/
|
|
114
|
-
result
|
|
117
|
+
result = String.fromCharCode(
|
|
115
118
|
negative ? 65534 - (num % 65533) : 2 + (num % 65533),
|
|
116
|
-
);
|
|
119
|
+
) + result;
|
|
117
120
|
num = Math.floor(num / 65533);
|
|
118
121
|
}
|
|
119
122
|
// Prefix the number of digits, counting down from 128 for negative and up for positive
|
|
@@ -168,6 +171,8 @@ abstract class Scope implements QueueRunner {
|
|
|
168
171
|
// order of the source code.
|
|
169
172
|
prio: number = --lastPrio;
|
|
170
173
|
|
|
174
|
+
[ptr: ReverseSortedSetPointer]: this;
|
|
175
|
+
|
|
171
176
|
abstract onChange(index: any, newData: DatumType, oldData: DatumType): void;
|
|
172
177
|
abstract queueRun(): void;
|
|
173
178
|
|
|
@@ -198,6 +203,9 @@ abstract class ContentScope extends Scope {
|
|
|
198
203
|
// be for child scopes, subscriptions as well as `clean(..)` hooks.
|
|
199
204
|
cleaners: Array<{ delete: (scope: Scope) => void } | (() => void)>;
|
|
200
205
|
|
|
206
|
+
// Whether this scope is within an SVG namespace context
|
|
207
|
+
inSvgNamespace: boolean = false;
|
|
208
|
+
|
|
201
209
|
constructor(
|
|
202
210
|
cleaners: Array<{ delete: (scope: Scope) => void } | (() => void)> = [],
|
|
203
211
|
) {
|
|
@@ -264,6 +272,10 @@ class ChainedScope extends ContentScope {
|
|
|
264
272
|
useParentCleaners = false,
|
|
265
273
|
) {
|
|
266
274
|
super(useParentCleaners ? currentScope.cleaners : []);
|
|
275
|
+
|
|
276
|
+
// Inherit SVG namespace state from current scope
|
|
277
|
+
this.inSvgNamespace = currentScope.inSvgNamespace;
|
|
278
|
+
|
|
267
279
|
if (parentElement === currentScope.parentElement) {
|
|
268
280
|
// If `currentScope` is not actually a ChainedScope, prevSibling will be undefined, as intended
|
|
269
281
|
this.prevSibling = currentScope.getChildPrevSibling();
|
|
@@ -334,6 +346,10 @@ class MountScope extends ContentScope {
|
|
|
334
346
|
public renderer: () => any,
|
|
335
347
|
) {
|
|
336
348
|
super();
|
|
349
|
+
|
|
350
|
+
// Inherit SVG namespace state from current scope
|
|
351
|
+
this.inSvgNamespace = currentScope.inSvgNamespace;
|
|
352
|
+
|
|
337
353
|
this.redraw();
|
|
338
354
|
currentScope.cleaners.push(this);
|
|
339
355
|
}
|
|
@@ -442,7 +458,9 @@ class SetArgScope extends ChainedScope {
|
|
|
442
458
|
}
|
|
443
459
|
}
|
|
444
460
|
|
|
445
|
-
let immediateQueue: ReverseSortedSet<Scope> = new ReverseSortedSet(
|
|
461
|
+
let immediateQueue: ReverseSortedSet<Scope, "prio"> = new ReverseSortedSet(
|
|
462
|
+
"prio",
|
|
463
|
+
);
|
|
446
464
|
|
|
447
465
|
class ImmediateScope extends RegularScope {
|
|
448
466
|
onChange(index: any, newData: DatumType, oldData: DatumType) {
|
|
@@ -491,9 +509,8 @@ class OnEachScope extends Scope {
|
|
|
491
509
|
byIndex: Map<any, OnEachItemScope> = new Map();
|
|
492
510
|
|
|
493
511
|
/** The reverse-ordered list of item scopes, not including those for which makeSortKey returned undefined. */
|
|
494
|
-
sortedSet: ReverseSortedSet<OnEachItemScope> =
|
|
495
|
-
"sortKey"
|
|
496
|
-
);
|
|
512
|
+
sortedSet: ReverseSortedSet<OnEachItemScope, "sortKey"> =
|
|
513
|
+
new ReverseSortedSet("sortKey");
|
|
497
514
|
|
|
498
515
|
/** Indexes that have been created/removed and need to be handled in the next `queueRun`. */
|
|
499
516
|
changedIndexes: Set<any> = new Set();
|
|
@@ -594,6 +611,9 @@ class OnEachItemScope extends ContentScope {
|
|
|
594
611
|
) {
|
|
595
612
|
super();
|
|
596
613
|
this.parentElement = parent.parentElement;
|
|
614
|
+
|
|
615
|
+
// Inherit SVG namespace state from current scope
|
|
616
|
+
this.inSvgNamespace = currentScope.inSvgNamespace;
|
|
597
617
|
|
|
598
618
|
this.parent.byIndex.set(this.itemIndex, this);
|
|
599
619
|
|
|
@@ -803,7 +823,7 @@ function subscribe(
|
|
|
803
823
|
}
|
|
804
824
|
|
|
805
825
|
export function onEach<T>(
|
|
806
|
-
target:
|
|
826
|
+
target: ReadonlyArray<undefined | T>,
|
|
807
827
|
render: (value: T, index: number) => void,
|
|
808
828
|
makeKey?: (value: T, key: any) => SortKeyType,
|
|
809
829
|
): void;
|
|
@@ -1564,9 +1584,9 @@ const SPECIAL_PROPS: { [key: string]: (value: any) => void } = {
|
|
|
1564
1584
|
addNode(document.createTextNode(value));
|
|
1565
1585
|
},
|
|
1566
1586
|
element: (value: any) => {
|
|
1567
|
-
|
|
1568
|
-
throw new Error(`Unexpected element-argument: ${JSON.parse(value)}`);
|
|
1587
|
+
console.log("Aberdeen: $({element: myElement}) is deprecated, use $(myElement) instead");
|
|
1569
1588
|
addNode(value);
|
|
1589
|
+
SPECIAL_PROPS.element = addNode; // Avoid the console.log next time
|
|
1570
1590
|
},
|
|
1571
1591
|
};
|
|
1572
1592
|
|
|
@@ -1603,7 +1623,7 @@ const SPECIAL_PROPS: { [key: string]: (value: any) => void } = {
|
|
|
1603
1623
|
* This is often used together with {@link ref}, in order to use properties other than `.value`.
|
|
1604
1624
|
* - `{text: string|number}`: Add the value as a `TextNode` to the *current* element.
|
|
1605
1625
|
* - `{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.
|
|
1606
|
-
|
|
1626
|
+
- `Node`: If a DOM Node (Element or TextNode) is passed in, it is added as a child to the *current* element. If the Node is an Element, it becomes the new *current* element for the rest of this `$` function execution.
|
|
1607
1627
|
*
|
|
1608
1628
|
* @returns The most inner DOM element that was created (not counting text nodes nor elements created by content functions),
|
|
1609
1629
|
* or undefined if no elements were created.
|
|
@@ -1699,7 +1719,14 @@ export function $(
|
|
|
1699
1719
|
err = `Tag '${arg}' cannot contain space`;
|
|
1700
1720
|
break;
|
|
1701
1721
|
} else {
|
|
1702
|
-
|
|
1722
|
+
// Determine which namespace to use for element creation
|
|
1723
|
+
const useNamespace = currentScope.inSvgNamespace || arg === 'svg';
|
|
1724
|
+
if (useNamespace) {
|
|
1725
|
+
result = document.createElementNS('http://www.w3.org/2000/svg', arg);
|
|
1726
|
+
} else {
|
|
1727
|
+
result = document.createElement(arg);
|
|
1728
|
+
}
|
|
1729
|
+
|
|
1703
1730
|
if (classes) result.className = classes.replaceAll(".", " ");
|
|
1704
1731
|
if (text) result.textContent = text;
|
|
1705
1732
|
addNode(result);
|
|
@@ -1707,18 +1734,35 @@ export function $(
|
|
|
1707
1734
|
savedCurrentScope = currentScope;
|
|
1708
1735
|
}
|
|
1709
1736
|
const newScope = new ChainedScope(result, true);
|
|
1737
|
+
|
|
1738
|
+
// If we're creating an SVG element, set the SVG namespace flag for child scopes
|
|
1739
|
+
if (arg === 'svg') {
|
|
1740
|
+
newScope.inSvgNamespace = true;
|
|
1741
|
+
}
|
|
1742
|
+
|
|
1710
1743
|
newScope.lastChild = result.lastChild || undefined;
|
|
1711
1744
|
if (topRedrawScope === currentScope) topRedrawScope = newScope;
|
|
1712
1745
|
currentScope = newScope;
|
|
1713
1746
|
}
|
|
1714
1747
|
} else if (typeof arg === "object") {
|
|
1715
1748
|
if (arg.constructor !== Object) {
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1749
|
+
if (arg instanceof Node) {
|
|
1750
|
+
addNode(arg);
|
|
1751
|
+
if (arg instanceof Element) {
|
|
1752
|
+
// If it's an Element, it may contain children, so we make it the current scope
|
|
1753
|
+
if (!savedCurrentScope) savedCurrentScope = currentScope;
|
|
1754
|
+
currentScope = new ChainedScope(arg, true);
|
|
1755
|
+
currentScope.lastChild = arg.lastChild || undefined;
|
|
1756
|
+
}
|
|
1757
|
+
} else {
|
|
1758
|
+
err = `Unexpected argument: ${arg}`;
|
|
1759
|
+
break;
|
|
1760
|
+
}
|
|
1761
|
+
} else {
|
|
1762
|
+
for (const key in arg) {
|
|
1763
|
+
const val = arg[key];
|
|
1764
|
+
applyArg(key, val);
|
|
1765
|
+
}
|
|
1722
1766
|
}
|
|
1723
1767
|
} else if (typeof arg === "function") {
|
|
1724
1768
|
new RegularScope(currentScope.parentElement, arg);
|