roqa 0.0.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/CHANGELOG.md +21 -0
- package/LICENSE +21 -0
- package/README.md +33 -0
- package/package.json +77 -0
- package/src/compiler/codegen.js +1217 -0
- package/src/compiler/index.js +47 -0
- package/src/compiler/parser.js +197 -0
- package/src/compiler/transforms/bind-detector.js +264 -0
- package/src/compiler/transforms/events.js +246 -0
- package/src/compiler/transforms/for-transform.js +164 -0
- package/src/compiler/transforms/inline-get.js +1049 -0
- package/src/compiler/transforms/jsx-to-template.js +871 -0
- package/src/compiler/transforms/show-transform.js +78 -0
- package/src/compiler/transforms/validate.js +80 -0
- package/src/compiler/utils.js +69 -0
- package/src/jsx-runtime.d.ts +640 -0
- package/src/jsx-runtime.js +73 -0
- package/src/runtime/cell.js +37 -0
- package/src/runtime/component.js +241 -0
- package/src/runtime/events.js +156 -0
- package/src/runtime/for-block.js +374 -0
- package/src/runtime/index.js +17 -0
- package/src/runtime/show-block.js +115 -0
- package/src/runtime/template.js +32 -0
- package/types/compiler.d.ts +9 -0
- package/types/index.d.ts +433 -0
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
import { bind } from "./cell.js";
|
|
2
|
+
|
|
3
|
+
// Reconcile arrays using Longest Increasing Subsequence (LIS) algorithm
|
|
4
|
+
// Heavily based on Ripple's reconciliation algorithms: https://github.com/Ripple-TS/ripple/blob/main/packages/ripple/src/runtime/internal/client/for.js
|
|
5
|
+
|
|
6
|
+
// LIS algorithm state (reused across calls for performance)
|
|
7
|
+
let lisResult;
|
|
8
|
+
let lisP;
|
|
9
|
+
let lisMaxLen = 0;
|
|
10
|
+
|
|
11
|
+
function lisAlgorithm(arr) {
|
|
12
|
+
let arrI = 0,
|
|
13
|
+
i = 0,
|
|
14
|
+
j = 0,
|
|
15
|
+
k = 0,
|
|
16
|
+
u = 0,
|
|
17
|
+
v = 0,
|
|
18
|
+
c = 0;
|
|
19
|
+
const len = arr.length;
|
|
20
|
+
if (len > lisMaxLen) {
|
|
21
|
+
lisMaxLen = len;
|
|
22
|
+
lisResult = new Int32Array(len);
|
|
23
|
+
lisP = new Int32Array(len);
|
|
24
|
+
}
|
|
25
|
+
while (i < len) {
|
|
26
|
+
arrI = arr[i];
|
|
27
|
+
if (arrI !== 0) {
|
|
28
|
+
j = lisResult[k];
|
|
29
|
+
if (arr[j] < arrI) {
|
|
30
|
+
lisP[i] = j;
|
|
31
|
+
lisResult[++k] = i;
|
|
32
|
+
i++;
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
u = 0;
|
|
36
|
+
v = k;
|
|
37
|
+
while (u < v) {
|
|
38
|
+
c = (u + v) >> 1;
|
|
39
|
+
if (arr[lisResult[c]] < arrI) {
|
|
40
|
+
u = c + 1;
|
|
41
|
+
} else {
|
|
42
|
+
v = c;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
if (arrI < arr[lisResult[u]]) {
|
|
46
|
+
if (u > 0) lisP[i] = lisResult[u - 1];
|
|
47
|
+
lisResult[u] = i;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
i++;
|
|
51
|
+
}
|
|
52
|
+
u = k + 1;
|
|
53
|
+
const seq = new Int32Array(u);
|
|
54
|
+
v = lisResult[u - 1];
|
|
55
|
+
while (u-- > 0) {
|
|
56
|
+
seq[u] = v;
|
|
57
|
+
v = lisP[v];
|
|
58
|
+
lisResult[u] = 0;
|
|
59
|
+
}
|
|
60
|
+
return seq;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Create an item block (lightweight object representing a rendered item)
|
|
65
|
+
* @param {Node} anchor - Where to insert
|
|
66
|
+
* @param {*} value - The data item
|
|
67
|
+
* @param {number} index - Array index
|
|
68
|
+
* @param {Function} renderFn - (anchor, value, index) => { start, end } or just appends nodes
|
|
69
|
+
*/
|
|
70
|
+
function createItem(anchor, value, index, renderFn) {
|
|
71
|
+
// renderFn should return { start, end } nodes for the item
|
|
72
|
+
const item = renderFn(anchor, value, index);
|
|
73
|
+
return {
|
|
74
|
+
s: item, // state: { start, end } - the DOM range for this item
|
|
75
|
+
v: value,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Move a block's DOM nodes before an anchor
|
|
81
|
+
*/
|
|
82
|
+
function moveItem(item, anchor) {
|
|
83
|
+
const state = item.s;
|
|
84
|
+
let node = state.start;
|
|
85
|
+
const end = state.end;
|
|
86
|
+
|
|
87
|
+
if (node !== end) {
|
|
88
|
+
while (node !== null) {
|
|
89
|
+
const next_node = node.nextSibling;
|
|
90
|
+
anchor.before(node);
|
|
91
|
+
if (next_node === end) {
|
|
92
|
+
anchor.before(end);
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
node = next_node;
|
|
96
|
+
}
|
|
97
|
+
} else {
|
|
98
|
+
anchor.before(node);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Destroy an item's DOM nodes and run cleanup if present
|
|
104
|
+
*/
|
|
105
|
+
function destroyItem(item) {
|
|
106
|
+
const state = item.s;
|
|
107
|
+
let node = state.start;
|
|
108
|
+
const end = state.end;
|
|
109
|
+
|
|
110
|
+
// Run cleanup function if the render provided one
|
|
111
|
+
if (state.cleanup) state.cleanup();
|
|
112
|
+
|
|
113
|
+
while (node !== null) {
|
|
114
|
+
const next = node.nextSibling;
|
|
115
|
+
node.remove();
|
|
116
|
+
if (node === end) break;
|
|
117
|
+
node = next;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Fast path: clear all items when going from non-empty to empty
|
|
123
|
+
*/
|
|
124
|
+
function reconcileFastClear(anchor, forState, array) {
|
|
125
|
+
const parent_node = anchor.parentNode;
|
|
126
|
+
parent_node.textContent = "";
|
|
127
|
+
parent_node.append(anchor);
|
|
128
|
+
forState.array = array;
|
|
129
|
+
forState.items = [];
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Reconcile arrays by reference equality
|
|
134
|
+
*/
|
|
135
|
+
function reconcileByRef(anchor, forState, b, renderFn) {
|
|
136
|
+
let aStart = 0,
|
|
137
|
+
bStart = 0,
|
|
138
|
+
aLeft = 0,
|
|
139
|
+
bLeft = 0,
|
|
140
|
+
sources = new Int32Array(0),
|
|
141
|
+
moved = false,
|
|
142
|
+
pos = 0,
|
|
143
|
+
patched = 0,
|
|
144
|
+
i = 0,
|
|
145
|
+
j = 0;
|
|
146
|
+
|
|
147
|
+
const a = forState.array;
|
|
148
|
+
const aLen = a.length;
|
|
149
|
+
const bLen = b.length;
|
|
150
|
+
|
|
151
|
+
if (bLen !== 0) {
|
|
152
|
+
const bItems = Array(bLen);
|
|
153
|
+
|
|
154
|
+
// Empty -> non-empty: create all
|
|
155
|
+
if (aLen === 0) {
|
|
156
|
+
for (; j < bLen; j++) {
|
|
157
|
+
bItems[j] = createItem(anchor, b[j], j, renderFn);
|
|
158
|
+
}
|
|
159
|
+
forState.array = b;
|
|
160
|
+
forState.items = bItems;
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const aItems = forState.items;
|
|
165
|
+
let aVal = a[j];
|
|
166
|
+
let bVal = b[j];
|
|
167
|
+
let aEnd = aLen - 1;
|
|
168
|
+
let bEnd = bLen - 1;
|
|
169
|
+
|
|
170
|
+
// Skip common prefix
|
|
171
|
+
outer: {
|
|
172
|
+
while (aVal === bVal) {
|
|
173
|
+
a[j] = bVal;
|
|
174
|
+
bItems[j] = aItems[j];
|
|
175
|
+
if (++j > aEnd || j > bEnd) break outer;
|
|
176
|
+
aVal = a[j];
|
|
177
|
+
bVal = b[j];
|
|
178
|
+
}
|
|
179
|
+
// Skip common suffix
|
|
180
|
+
aVal = a[aEnd];
|
|
181
|
+
bVal = b[bEnd];
|
|
182
|
+
while (aVal === bVal) {
|
|
183
|
+
a[aEnd] = bVal;
|
|
184
|
+
bItems[bEnd] = aItems[aEnd];
|
|
185
|
+
bEnd--;
|
|
186
|
+
if (j > --aEnd || j > bEnd) break outer;
|
|
187
|
+
aVal = a[aEnd];
|
|
188
|
+
bVal = b[bEnd];
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
let fastPathRemoval = false;
|
|
193
|
+
let target;
|
|
194
|
+
|
|
195
|
+
if (j > aEnd) {
|
|
196
|
+
// Only additions
|
|
197
|
+
if (j <= bEnd) {
|
|
198
|
+
while (j <= bEnd) {
|
|
199
|
+
bVal = b[j];
|
|
200
|
+
target = j >= aLen ? anchor : aItems[j].s.start;
|
|
201
|
+
bItems[j] = createItem(target, bVal, j, renderFn);
|
|
202
|
+
j++;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
} else if (j > bEnd) {
|
|
206
|
+
// Only removals
|
|
207
|
+
while (j <= aEnd) {
|
|
208
|
+
destroyItem(aItems[j++]);
|
|
209
|
+
}
|
|
210
|
+
} else {
|
|
211
|
+
// General case: need full reconciliation
|
|
212
|
+
aStart = j;
|
|
213
|
+
bStart = j;
|
|
214
|
+
aLeft = aEnd - j + 1;
|
|
215
|
+
bLeft = bEnd - j + 1;
|
|
216
|
+
sources = new Int32Array(bLeft);
|
|
217
|
+
moved = false;
|
|
218
|
+
pos = 0;
|
|
219
|
+
patched = 0;
|
|
220
|
+
i = 0;
|
|
221
|
+
fastPathRemoval = aLeft === aLen;
|
|
222
|
+
|
|
223
|
+
if (bLen < 4 || (aLeft | bLeft) < 32) {
|
|
224
|
+
// Small arrays: use O(n*m) search
|
|
225
|
+
for (i = aStart; i <= aEnd; ++i) {
|
|
226
|
+
aVal = a[i];
|
|
227
|
+
if (patched < bLeft) {
|
|
228
|
+
for (j = bStart; j <= bEnd; j++) {
|
|
229
|
+
if (aVal === (bVal = b[j])) {
|
|
230
|
+
sources[j - bStart] = i + 1;
|
|
231
|
+
if (fastPathRemoval) {
|
|
232
|
+
fastPathRemoval = false;
|
|
233
|
+
while (aStart < i) destroyItem(aItems[aStart++]);
|
|
234
|
+
}
|
|
235
|
+
if (pos > j) moved = true;
|
|
236
|
+
else pos = j;
|
|
237
|
+
bItems[j] = aItems[i];
|
|
238
|
+
++patched;
|
|
239
|
+
break;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
if (!fastPathRemoval && j > bEnd) destroyItem(aItems[i]);
|
|
243
|
+
} else if (!fastPathRemoval) {
|
|
244
|
+
destroyItem(aItems[i]);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
} else {
|
|
248
|
+
// Larger arrays: use Map for O(n+m) lookup
|
|
249
|
+
const map = new Map();
|
|
250
|
+
for (i = bStart; i <= bEnd; ++i) map.set(b[i], i);
|
|
251
|
+
for (i = aStart; i <= aEnd; ++i) {
|
|
252
|
+
aVal = a[i];
|
|
253
|
+
if (patched < bLeft) {
|
|
254
|
+
j = map.get(aVal);
|
|
255
|
+
if (j !== undefined) {
|
|
256
|
+
if (fastPathRemoval) {
|
|
257
|
+
fastPathRemoval = false;
|
|
258
|
+
while (i > aStart) destroyItem(aItems[aStart++]);
|
|
259
|
+
}
|
|
260
|
+
sources[j - bStart] = i + 1;
|
|
261
|
+
if (pos > j) moved = true;
|
|
262
|
+
else pos = j;
|
|
263
|
+
bItems[j] = aItems[i];
|
|
264
|
+
++patched;
|
|
265
|
+
} else if (!fastPathRemoval) {
|
|
266
|
+
destroyItem(aItems[i]);
|
|
267
|
+
}
|
|
268
|
+
} else if (!fastPathRemoval) {
|
|
269
|
+
destroyItem(aItems[i]);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
if (fastPathRemoval) {
|
|
275
|
+
reconcileFastClear(anchor, forState, []);
|
|
276
|
+
reconcileByRef(anchor, forState, b, renderFn);
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (moved) {
|
|
281
|
+
let nextPos = 0;
|
|
282
|
+
const seq = lisAlgorithm(sources);
|
|
283
|
+
j = seq.length - 1;
|
|
284
|
+
for (i = bLeft - 1; i >= 0; i--) {
|
|
285
|
+
pos = i + bStart;
|
|
286
|
+
nextPos = pos + 1;
|
|
287
|
+
target = nextPos < bLen ? bItems[nextPos].s.start : anchor;
|
|
288
|
+
|
|
289
|
+
if (sources[i] === 0) {
|
|
290
|
+
bVal = b[pos];
|
|
291
|
+
bItems[pos] = createItem(target, bVal, pos, renderFn);
|
|
292
|
+
} else if (j < 0 || i !== seq[j]) {
|
|
293
|
+
moveItem(bItems[pos], target);
|
|
294
|
+
} else {
|
|
295
|
+
j--;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
} else if (patched !== bLeft) {
|
|
299
|
+
for (i = bLeft - 1; i >= 0; i--) {
|
|
300
|
+
if (sources[i] === 0) {
|
|
301
|
+
pos = i + bStart;
|
|
302
|
+
bVal = b[pos];
|
|
303
|
+
const nextPos = pos + 1;
|
|
304
|
+
target = nextPos < bLen ? bItems[nextPos].s.start : anchor;
|
|
305
|
+
bItems[pos] = createItem(target, bVal, pos, renderFn);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
forState.array = b;
|
|
312
|
+
forState.items = bItems;
|
|
313
|
+
} else if (aLen > 0) {
|
|
314
|
+
// Non-empty -> empty: clear all
|
|
315
|
+
reconcileFastClear(anchor, forState, b);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Create a forBlock for efficient list rendering
|
|
321
|
+
* @param {Element} container - The container element (e.g., tbody)
|
|
322
|
+
* @param {Object} sourceCell - A cell containing the array to render
|
|
323
|
+
* @param {Function} renderFn - (anchor, item, index) => { start, end, cleanup? }
|
|
324
|
+
* @returns {{ update: Function, state: Object, destroy: Function }}
|
|
325
|
+
*/
|
|
326
|
+
export function forBlock(container, sourceCell, renderFn) {
|
|
327
|
+
// Create anchor node at end of container
|
|
328
|
+
const anchor = document.createTextNode("");
|
|
329
|
+
container.appendChild(anchor);
|
|
330
|
+
|
|
331
|
+
// Initialize state
|
|
332
|
+
const forState = {
|
|
333
|
+
array: [],
|
|
334
|
+
items: [],
|
|
335
|
+
};
|
|
336
|
+
|
|
337
|
+
const doUpdate = () => {
|
|
338
|
+
const collection = sourceCell.v;
|
|
339
|
+
const array = Array.isArray(collection)
|
|
340
|
+
? collection
|
|
341
|
+
: collection == null
|
|
342
|
+
? []
|
|
343
|
+
: Array.from(collection);
|
|
344
|
+
reconcileByRef(anchor, forState, array, renderFn);
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
// Subscribe to cell changes
|
|
348
|
+
const unsubscribe = bind(sourceCell, doUpdate);
|
|
349
|
+
|
|
350
|
+
// Initial render
|
|
351
|
+
doUpdate();
|
|
352
|
+
|
|
353
|
+
// Destroy function for cleanup
|
|
354
|
+
const destroy = () => {
|
|
355
|
+
unsubscribe();
|
|
356
|
+
// Destroy all current items
|
|
357
|
+
const items = forState.items;
|
|
358
|
+
for (let i = 0; i < items.length; i++) {
|
|
359
|
+
destroyItem(items[i]);
|
|
360
|
+
}
|
|
361
|
+
forState.array = [];
|
|
362
|
+
forState.items = [];
|
|
363
|
+
anchor.remove();
|
|
364
|
+
};
|
|
365
|
+
|
|
366
|
+
// Return controller object
|
|
367
|
+
return {
|
|
368
|
+
update: doUpdate,
|
|
369
|
+
destroy,
|
|
370
|
+
get state() {
|
|
371
|
+
return forState;
|
|
372
|
+
},
|
|
373
|
+
};
|
|
374
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// Template
|
|
2
|
+
export { template, svgTemplate } from "./template.js";
|
|
3
|
+
|
|
4
|
+
// Reactive primitives
|
|
5
|
+
export { cell, get, put, bind, notify, set } from "./cell.js";
|
|
6
|
+
|
|
7
|
+
// Event delegation
|
|
8
|
+
export { delegate, handleRootEvents } from "./events.js";
|
|
9
|
+
|
|
10
|
+
// Component definition
|
|
11
|
+
export { defineComponent, setProp, getProps } from "./component.js";
|
|
12
|
+
|
|
13
|
+
// List rendering
|
|
14
|
+
export { forBlock } from "./for-block.js";
|
|
15
|
+
|
|
16
|
+
// Conditional rendering
|
|
17
|
+
export { showBlock } from "./show-block.js";
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { bind } from "./cell.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Create a showBlock for conditional rendering
|
|
5
|
+
* @param {Element} container - The container element
|
|
6
|
+
* @param {Object|Function} condition - A cell containing the condition, or a getter function returning boolean
|
|
7
|
+
* @param {Function} renderFn - (anchor) => { start, end, cleanup? }
|
|
8
|
+
* @param {Array} [deps] - Optional array of cells to subscribe to for reactive updates (for complex expressions)
|
|
9
|
+
* @returns {{ update: Function, destroy: Function }}
|
|
10
|
+
*/
|
|
11
|
+
export function showBlock(container, condition, renderFn, deps) {
|
|
12
|
+
// Create anchor node at end of container
|
|
13
|
+
const anchor = document.createTextNode("");
|
|
14
|
+
container.appendChild(anchor);
|
|
15
|
+
|
|
16
|
+
// Track current rendered state: { start, end, cleanup? } or null
|
|
17
|
+
let currentState = null;
|
|
18
|
+
|
|
19
|
+
// Determine if condition is a cell, a getter function, or a static value
|
|
20
|
+
const isCell = condition && typeof condition === "object" && "v" in condition;
|
|
21
|
+
const isGetter = typeof condition === "function";
|
|
22
|
+
|
|
23
|
+
const create = () => {
|
|
24
|
+
if (currentState) return; // Already showing
|
|
25
|
+
currentState = renderFn(anchor);
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const destroyCurrent = () => {
|
|
29
|
+
if (!currentState) return; // Nothing to destroy
|
|
30
|
+
|
|
31
|
+
// Run cleanup function if provided
|
|
32
|
+
if (currentState.cleanup) {
|
|
33
|
+
currentState.cleanup();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Remove DOM nodes
|
|
37
|
+
let node = currentState.start;
|
|
38
|
+
const end = currentState.end;
|
|
39
|
+
do {
|
|
40
|
+
const next = node.nextSibling;
|
|
41
|
+
node.remove();
|
|
42
|
+
if (node === end) break;
|
|
43
|
+
node = next;
|
|
44
|
+
} while (node);
|
|
45
|
+
|
|
46
|
+
currentState = null;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
// Optimized update functions based on condition type
|
|
50
|
+
const doUpdate = isCell
|
|
51
|
+
? () => {
|
|
52
|
+
if (condition.v) {
|
|
53
|
+
if (!currentState) create();
|
|
54
|
+
} else if (currentState) {
|
|
55
|
+
destroyCurrent();
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
: isGetter
|
|
59
|
+
? () => {
|
|
60
|
+
if (condition()) {
|
|
61
|
+
if (!currentState) create();
|
|
62
|
+
} else if (currentState) {
|
|
63
|
+
destroyCurrent();
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
: () => {
|
|
67
|
+
// Static value - only runs once
|
|
68
|
+
if (condition && !currentState) create();
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
// Subscribe to cell changes
|
|
72
|
+
// Optimize for common case: single subscription doesn't need array
|
|
73
|
+
let unsubscribe = null;
|
|
74
|
+
let unsubscribes = null;
|
|
75
|
+
|
|
76
|
+
const depsLen = deps ? deps.length : 0;
|
|
77
|
+
|
|
78
|
+
if (isCell) {
|
|
79
|
+
// Simple cell condition
|
|
80
|
+
unsubscribe = bind(condition, doUpdate);
|
|
81
|
+
} else if (depsLen === 1) {
|
|
82
|
+
// Single dependency - no array needed
|
|
83
|
+
unsubscribe = bind(deps[0], doUpdate);
|
|
84
|
+
} else if (depsLen > 1) {
|
|
85
|
+
// Multiple dependencies - use array
|
|
86
|
+
unsubscribes = [];
|
|
87
|
+
for (let i = 0; i < depsLen; i++) {
|
|
88
|
+
unsubscribes.push(bind(deps[i], doUpdate));
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Initial render
|
|
93
|
+
doUpdate();
|
|
94
|
+
|
|
95
|
+
// Destroy function for cleanup
|
|
96
|
+
const destroy = () => {
|
|
97
|
+
if (unsubscribe) {
|
|
98
|
+
unsubscribe();
|
|
99
|
+
} else if (unsubscribes) {
|
|
100
|
+
for (let i = 0; i < unsubscribes.length; i++) {
|
|
101
|
+
unsubscribes[i]();
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
destroyCurrent();
|
|
105
|
+
anchor.remove();
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
update: doUpdate,
|
|
110
|
+
destroy,
|
|
111
|
+
get isShowing() {
|
|
112
|
+
return currentState !== null;
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
const { cloneNode } = Node.prototype;
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Create a template from an HTML string and return a clone function
|
|
5
|
+
* @param {string} html - The HTML string to create a template from
|
|
6
|
+
* @returns {() => Node} - A function that returns a deep clone of the template content
|
|
7
|
+
*/
|
|
8
|
+
export const template = (html) => {
|
|
9
|
+
const t = document.createElement("template");
|
|
10
|
+
t.innerHTML = html;
|
|
11
|
+
return () => cloneNode.call(t.content, true);
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Create a template from an SVG string and return a clone function.
|
|
16
|
+
* SVG elements must be created in the SVG namespace to render correctly.
|
|
17
|
+
* @param {string} svg - The SVG string (can be a full <svg> or inner SVG content)
|
|
18
|
+
* @returns {() => Node} - A function that returns a deep clone of the SVG content
|
|
19
|
+
*/
|
|
20
|
+
export const svgTemplate = (svg) => {
|
|
21
|
+
// Wrap in an SVG element to ensure proper namespace parsing
|
|
22
|
+
const wrapper = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
23
|
+
wrapper.innerHTML = svg;
|
|
24
|
+
|
|
25
|
+
// Use a document fragment to match the behavior of the regular template function
|
|
26
|
+
// The traversal code expects to call .firstChild on the result
|
|
27
|
+
const fragment = document.createDocumentFragment();
|
|
28
|
+
while (wrapper.firstChild) {
|
|
29
|
+
fragment.appendChild(wrapper.firstChild);
|
|
30
|
+
}
|
|
31
|
+
return () => cloneNode.call(fragment, true);
|
|
32
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export function compile(code: string, filename: string): { code: string; map: any };
|
|
2
|
+
export function parse(code: string, filename: string): any;
|
|
3
|
+
export function validateNoCustomComponents(ast: any): void;
|
|
4
|
+
export function generateOutput(
|
|
5
|
+
code: string,
|
|
6
|
+
ast: any,
|
|
7
|
+
filename: string,
|
|
8
|
+
): { code: string; map: any };
|
|
9
|
+
export function inlineGetCalls(code: string, filename: string): { code: string; map: any };
|