@getforma/core 0.1.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/LICENSE +21 -0
- package/README.md +48 -0
- package/dist/chunk-27QSSN4E.cjs +1773 -0
- package/dist/chunk-27QSSN4E.cjs.map +1 -0
- package/dist/chunk-DB2ULMOM.js +1750 -0
- package/dist/chunk-DB2ULMOM.js.map +1 -0
- package/dist/chunk-FPSLC62A.cjs +31 -0
- package/dist/chunk-FPSLC62A.cjs.map +1 -0
- package/dist/chunk-KX5WRZH7.js +27 -0
- package/dist/chunk-KX5WRZH7.js.map +1 -0
- package/dist/formajs-runtime-hardened.global.js +2 -0
- package/dist/formajs-runtime-hardened.global.js.map +1 -0
- package/dist/formajs-runtime.global.js +2 -0
- package/dist/formajs-runtime.global.js.map +1 -0
- package/dist/formajs.global.js +2 -0
- package/dist/formajs.global.js.map +1 -0
- package/dist/index.cjs +1626 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1382 -0
- package/dist/index.d.ts +1382 -0
- package/dist/index.js +1482 -0
- package/dist/index.js.map +1 -0
- package/dist/runtime-hardened.cjs +2805 -0
- package/dist/runtime-hardened.cjs.map +1 -0
- package/dist/runtime-hardened.js +2786 -0
- package/dist/runtime-hardened.js.map +1 -0
- package/dist/runtime.cjs +2531 -0
- package/dist/runtime.cjs.map +1 -0
- package/dist/runtime.d.cts +86 -0
- package/dist/runtime.d.ts +86 -0
- package/dist/runtime.js +2512 -0
- package/dist/runtime.js.map +1 -0
- package/dist/signal-CfLDwMyg.d.cts +29 -0
- package/dist/signal-CfLDwMyg.d.ts +29 -0
- package/dist/ssr/index.cjs +381 -0
- package/dist/ssr/index.cjs.map +1 -0
- package/dist/ssr/index.d.cts +154 -0
- package/dist/ssr/index.d.ts +154 -0
- package/dist/ssr/index.js +371 -0
- package/dist/ssr/index.js.map +1 -0
- package/dist/tc39-compat.cjs +45 -0
- package/dist/tc39-compat.cjs.map +1 -0
- package/dist/tc39-compat.d.cts +50 -0
- package/dist/tc39-compat.d.ts +50 -0
- package/dist/tc39-compat.js +42 -0
- package/dist/tc39-compat.js.map +1 -0
- package/package.json +122 -0
|
@@ -0,0 +1,2805 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var alienSignals = require('alien-signals');
|
|
4
|
+
|
|
5
|
+
// src/reactive/signal.ts
|
|
6
|
+
function applySignalSet(s, v) {
|
|
7
|
+
if (typeof v !== "function") {
|
|
8
|
+
s(v);
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
alienSignals.pauseTracking();
|
|
12
|
+
const prev = s();
|
|
13
|
+
alienSignals.resumeTracking();
|
|
14
|
+
s(v(prev));
|
|
15
|
+
}
|
|
16
|
+
function createSignal(initialValue) {
|
|
17
|
+
const s = alienSignals.signal(initialValue);
|
|
18
|
+
const getter = s;
|
|
19
|
+
const setter = (v) => applySignalSet(s, v);
|
|
20
|
+
return [getter, setter];
|
|
21
|
+
}
|
|
22
|
+
var createValueSignal = createSignal;
|
|
23
|
+
function internalEffect(fn) {
|
|
24
|
+
const dispose = alienSignals.effect(fn);
|
|
25
|
+
return dispose;
|
|
26
|
+
}
|
|
27
|
+
function createComputed(fn) {
|
|
28
|
+
return alienSignals.computed(fn);
|
|
29
|
+
}
|
|
30
|
+
function batch(fn) {
|
|
31
|
+
alienSignals.startBatch();
|
|
32
|
+
try {
|
|
33
|
+
fn();
|
|
34
|
+
} finally {
|
|
35
|
+
alienSignals.endBatch();
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// src/dom/list.ts
|
|
40
|
+
function longestIncreasingSubsequence(arr) {
|
|
41
|
+
const n = arr.length;
|
|
42
|
+
if (n === 0) return [];
|
|
43
|
+
const tails = new Int32Array(n);
|
|
44
|
+
const tailIndices = new Int32Array(n);
|
|
45
|
+
const predecessor = new Int32Array(n).fill(-1);
|
|
46
|
+
let tailsLen = 0;
|
|
47
|
+
for (let i = 0; i < n; i++) {
|
|
48
|
+
const val = arr[i];
|
|
49
|
+
let lo = 0, hi = tailsLen;
|
|
50
|
+
while (lo < hi) {
|
|
51
|
+
const mid = lo + hi >> 1;
|
|
52
|
+
if (tails[mid] < val) lo = mid + 1;
|
|
53
|
+
else hi = mid;
|
|
54
|
+
}
|
|
55
|
+
tails[lo] = val;
|
|
56
|
+
tailIndices[lo] = i;
|
|
57
|
+
if (lo > 0) predecessor[i] = tailIndices[lo - 1];
|
|
58
|
+
if (lo >= tailsLen) tailsLen++;
|
|
59
|
+
}
|
|
60
|
+
const result = new Array(tailsLen);
|
|
61
|
+
let idx = tailIndices[tailsLen - 1];
|
|
62
|
+
for (let i = tailsLen - 1; i >= 0; i--) {
|
|
63
|
+
result[i] = idx;
|
|
64
|
+
idx = predecessor[idx];
|
|
65
|
+
}
|
|
66
|
+
return result;
|
|
67
|
+
}
|
|
68
|
+
var SMALL_LIST_THRESHOLD = 32;
|
|
69
|
+
function reconcileSmall(parent, oldItems, newItems, oldNodes, keyFn, createFn, updateFn, beforeNode, hooks) {
|
|
70
|
+
const oldLen = oldItems.length;
|
|
71
|
+
const newLen = newItems.length;
|
|
72
|
+
const oldKeys = new Array(oldLen);
|
|
73
|
+
for (let i = 0; i < oldLen; i++) {
|
|
74
|
+
oldKeys[i] = keyFn(oldItems[i]);
|
|
75
|
+
}
|
|
76
|
+
const oldIndices = new Array(newLen);
|
|
77
|
+
const oldUsed = new Uint8Array(oldLen);
|
|
78
|
+
for (let i = 0; i < newLen; i++) {
|
|
79
|
+
const key = keyFn(newItems[i]);
|
|
80
|
+
let found = -1;
|
|
81
|
+
for (let j = 0; j < oldLen; j++) {
|
|
82
|
+
if (!oldUsed[j] && oldKeys[j] === key) {
|
|
83
|
+
found = j;
|
|
84
|
+
oldUsed[j] = 1;
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
oldIndices[i] = found;
|
|
89
|
+
}
|
|
90
|
+
for (let i = 0; i < oldLen; i++) {
|
|
91
|
+
if (!oldUsed[i]) {
|
|
92
|
+
if (hooks?.onBeforeRemove) {
|
|
93
|
+
const node = oldNodes[i];
|
|
94
|
+
hooks.onBeforeRemove(node, () => {
|
|
95
|
+
if (node.parentNode) node.parentNode.removeChild(node);
|
|
96
|
+
});
|
|
97
|
+
} else {
|
|
98
|
+
parent.removeChild(oldNodes[i]);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
if (oldLen === newLen) {
|
|
103
|
+
let allSameOrder = true;
|
|
104
|
+
for (let i = 0; i < newLen; i++) {
|
|
105
|
+
if (oldIndices[i] !== i) {
|
|
106
|
+
allSameOrder = false;
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
if (allSameOrder) {
|
|
111
|
+
const nodes = new Array(newLen);
|
|
112
|
+
for (let i = 0; i < newLen; i++) {
|
|
113
|
+
const node = oldNodes[i];
|
|
114
|
+
updateFn(node, newItems[i]);
|
|
115
|
+
nodes[i] = node;
|
|
116
|
+
}
|
|
117
|
+
return { nodes, items: newItems };
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
const reusedIndices = [];
|
|
121
|
+
const reusedPositions = [];
|
|
122
|
+
for (let i = 0; i < newLen; i++) {
|
|
123
|
+
if (oldIndices[i] !== -1) {
|
|
124
|
+
reusedIndices.push(oldIndices[i]);
|
|
125
|
+
reusedPositions.push(i);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
const lisOfReused = longestIncreasingSubsequence(reusedIndices);
|
|
129
|
+
const lisFlags = new Uint8Array(newLen);
|
|
130
|
+
for (const li of lisOfReused) {
|
|
131
|
+
lisFlags[reusedPositions[li]] = 1;
|
|
132
|
+
}
|
|
133
|
+
const newNodes = new Array(newLen);
|
|
134
|
+
let nextSibling = null;
|
|
135
|
+
for (let i = newLen - 1; i >= 0; i--) {
|
|
136
|
+
let node;
|
|
137
|
+
let isNew = false;
|
|
138
|
+
if (oldIndices[i] === -1) {
|
|
139
|
+
node = createFn(newItems[i]);
|
|
140
|
+
isNew = true;
|
|
141
|
+
} else {
|
|
142
|
+
node = oldNodes[oldIndices[i]];
|
|
143
|
+
updateFn(node, newItems[i]);
|
|
144
|
+
if (lisFlags[i]) {
|
|
145
|
+
newNodes[i] = node;
|
|
146
|
+
nextSibling = node;
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
if (nextSibling) {
|
|
151
|
+
parent.insertBefore(node, nextSibling);
|
|
152
|
+
} else {
|
|
153
|
+
parent.appendChild(node);
|
|
154
|
+
}
|
|
155
|
+
if (isNew) hooks?.onInsert?.(node);
|
|
156
|
+
newNodes[i] = node;
|
|
157
|
+
nextSibling = node;
|
|
158
|
+
}
|
|
159
|
+
return { nodes: newNodes, items: newItems };
|
|
160
|
+
}
|
|
161
|
+
function reconcileList(parent, oldItems, newItems, oldNodes, keyFn, createFn, updateFn, beforeNode, hooks) {
|
|
162
|
+
const oldLen = oldItems.length;
|
|
163
|
+
const newLen = newItems.length;
|
|
164
|
+
if (newLen === 0) {
|
|
165
|
+
for (let i = 0; i < oldLen; i++) {
|
|
166
|
+
if (hooks?.onBeforeRemove) {
|
|
167
|
+
const node = oldNodes[i];
|
|
168
|
+
hooks.onBeforeRemove(node, () => {
|
|
169
|
+
if (node.parentNode) node.parentNode.removeChild(node);
|
|
170
|
+
});
|
|
171
|
+
} else {
|
|
172
|
+
parent.removeChild(oldNodes[i]);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return { nodes: [], items: [] };
|
|
176
|
+
}
|
|
177
|
+
if (oldLen === 0) {
|
|
178
|
+
const nodes = new Array(newLen);
|
|
179
|
+
for (let i = 0; i < newLen; i++) {
|
|
180
|
+
const node = createFn(newItems[i]);
|
|
181
|
+
{
|
|
182
|
+
parent.appendChild(node);
|
|
183
|
+
}
|
|
184
|
+
hooks?.onInsert?.(node);
|
|
185
|
+
nodes[i] = node;
|
|
186
|
+
}
|
|
187
|
+
return { nodes, items: newItems };
|
|
188
|
+
}
|
|
189
|
+
if (oldLen < SMALL_LIST_THRESHOLD) {
|
|
190
|
+
return reconcileSmall(parent, oldItems, newItems, oldNodes, keyFn, createFn, updateFn, beforeNode, hooks);
|
|
191
|
+
}
|
|
192
|
+
const oldKeyMap = /* @__PURE__ */ new Map();
|
|
193
|
+
for (let i = 0; i < oldLen; i++) {
|
|
194
|
+
oldKeyMap.set(keyFn(oldItems[i]), i);
|
|
195
|
+
}
|
|
196
|
+
const oldIndices = new Array(newLen);
|
|
197
|
+
const oldUsed = new Uint8Array(oldLen);
|
|
198
|
+
for (let i = 0; i < newLen; i++) {
|
|
199
|
+
const key = keyFn(newItems[i]);
|
|
200
|
+
const oldIdx = oldKeyMap.get(key);
|
|
201
|
+
if (oldIdx !== void 0) {
|
|
202
|
+
oldIndices[i] = oldIdx;
|
|
203
|
+
oldUsed[oldIdx] = 1;
|
|
204
|
+
} else {
|
|
205
|
+
oldIndices[i] = -1;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
for (let i = 0; i < oldLen; i++) {
|
|
209
|
+
if (!oldUsed[i]) {
|
|
210
|
+
if (hooks?.onBeforeRemove) {
|
|
211
|
+
const node = oldNodes[i];
|
|
212
|
+
hooks.onBeforeRemove(node, () => {
|
|
213
|
+
if (node.parentNode) node.parentNode.removeChild(node);
|
|
214
|
+
});
|
|
215
|
+
} else {
|
|
216
|
+
parent.removeChild(oldNodes[i]);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
if (oldLen === newLen) {
|
|
221
|
+
let allSameOrder = true;
|
|
222
|
+
for (let i = 0; i < newLen; i++) {
|
|
223
|
+
if (oldIndices[i] !== i) {
|
|
224
|
+
allSameOrder = false;
|
|
225
|
+
break;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
if (allSameOrder) {
|
|
229
|
+
const nodes = new Array(newLen);
|
|
230
|
+
for (let i = 0; i < newLen; i++) {
|
|
231
|
+
const node = oldNodes[i];
|
|
232
|
+
updateFn(node, newItems[i]);
|
|
233
|
+
nodes[i] = node;
|
|
234
|
+
}
|
|
235
|
+
return { nodes, items: newItems };
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
const reusedIndices = [];
|
|
239
|
+
const reusedPositions = [];
|
|
240
|
+
for (let i = 0; i < newLen; i++) {
|
|
241
|
+
if (oldIndices[i] !== -1) {
|
|
242
|
+
reusedIndices.push(oldIndices[i]);
|
|
243
|
+
reusedPositions.push(i);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
const lisOfReused = longestIncreasingSubsequence(reusedIndices);
|
|
247
|
+
const lisFlags = new Uint8Array(newLen);
|
|
248
|
+
for (const li of lisOfReused) {
|
|
249
|
+
lisFlags[reusedPositions[li]] = 1;
|
|
250
|
+
}
|
|
251
|
+
const newNodes = new Array(newLen);
|
|
252
|
+
let nextSibling = null;
|
|
253
|
+
for (let i = newLen - 1; i >= 0; i--) {
|
|
254
|
+
let node;
|
|
255
|
+
let isNew = false;
|
|
256
|
+
if (oldIndices[i] === -1) {
|
|
257
|
+
node = createFn(newItems[i]);
|
|
258
|
+
isNew = true;
|
|
259
|
+
} else {
|
|
260
|
+
node = oldNodes[oldIndices[i]];
|
|
261
|
+
updateFn(node, newItems[i]);
|
|
262
|
+
if (lisFlags[i]) {
|
|
263
|
+
newNodes[i] = node;
|
|
264
|
+
nextSibling = node;
|
|
265
|
+
continue;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
if (nextSibling) {
|
|
269
|
+
parent.insertBefore(node, nextSibling);
|
|
270
|
+
} else {
|
|
271
|
+
parent.appendChild(node);
|
|
272
|
+
}
|
|
273
|
+
if (isNew) hooks?.onInsert?.(node);
|
|
274
|
+
newNodes[i] = node;
|
|
275
|
+
nextSibling = node;
|
|
276
|
+
}
|
|
277
|
+
return { nodes: newNodes, items: newItems };
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// src/dom/reconcile.ts
|
|
281
|
+
function getBindTargets(el) {
|
|
282
|
+
const targets = /* @__PURE__ */ new Set();
|
|
283
|
+
const attrs = el.attributes;
|
|
284
|
+
for (let i = 0; i < attrs.length; i++) {
|
|
285
|
+
const name = attrs[i].name;
|
|
286
|
+
if (name.startsWith("data-bind:")) {
|
|
287
|
+
targets.add(name.slice(10));
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
return targets;
|
|
291
|
+
}
|
|
292
|
+
function ownsSubtree(el) {
|
|
293
|
+
return el.hasAttribute("data-list") || el.hasAttribute("data-if");
|
|
294
|
+
}
|
|
295
|
+
function getStateKeys(json) {
|
|
296
|
+
try {
|
|
297
|
+
const obj = JSON.parse(json);
|
|
298
|
+
return Object.keys(obj).sort();
|
|
299
|
+
} catch {
|
|
300
|
+
return [];
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
function sameShape(keysA, keysB) {
|
|
304
|
+
if (keysA.length !== keysB.length) return false;
|
|
305
|
+
for (let i = 0; i < keysA.length; i++) {
|
|
306
|
+
if (keysA[i] !== keysB[i]) return false;
|
|
307
|
+
}
|
|
308
|
+
return true;
|
|
309
|
+
}
|
|
310
|
+
function determineScopeMode(liveEl, newEl) {
|
|
311
|
+
const liveModule = liveEl.getAttribute("data-module");
|
|
312
|
+
const newModule = newEl.getAttribute("data-module");
|
|
313
|
+
if (liveModule !== newModule) return "REPLACE";
|
|
314
|
+
const liveInitialState = liveEl.__formaInitialState;
|
|
315
|
+
const liveStateJSON = liveInitialState ?? liveEl.getAttribute("data-forma-state") ?? "{}";
|
|
316
|
+
const newStateJSON = newEl.getAttribute("data-forma-state") ?? "{}";
|
|
317
|
+
const liveKeys = getStateKeys(liveStateJSON);
|
|
318
|
+
const newKeys = getStateKeys(newStateJSON);
|
|
319
|
+
if (sameShape(liveKeys, newKeys)) return "PRESERVE";
|
|
320
|
+
return "RESET";
|
|
321
|
+
}
|
|
322
|
+
var _parseTemplate = null;
|
|
323
|
+
function parseHTML(html) {
|
|
324
|
+
if (!_parseTemplate) _parseTemplate = document.createElement("template");
|
|
325
|
+
_parseTemplate.innerHTML = html;
|
|
326
|
+
return _parseTemplate.content;
|
|
327
|
+
}
|
|
328
|
+
function patchAttributes(liveEl, newEl) {
|
|
329
|
+
const bindTargets = getBindTargets(liveEl);
|
|
330
|
+
const hasDataShow = liveEl.hasAttribute("data-show");
|
|
331
|
+
const hasDataModel = liveEl.hasAttribute("data-model");
|
|
332
|
+
let liveHasClassDirectives = false;
|
|
333
|
+
const liveAttrs = liveEl.attributes;
|
|
334
|
+
for (let i = 0; i < liveAttrs.length; i++) {
|
|
335
|
+
if (liveAttrs[i].name.startsWith("data-class:")) {
|
|
336
|
+
liveHasClassDirectives = true;
|
|
337
|
+
break;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
const newAttrs = newEl.attributes;
|
|
341
|
+
for (let i = 0; i < newAttrs.length; i++) {
|
|
342
|
+
const attr = newAttrs[i];
|
|
343
|
+
if (attr.name === "style" && hasDataShow) continue;
|
|
344
|
+
if (attr.name === "class" && liveHasClassDirectives) continue;
|
|
345
|
+
if ((attr.name === "value" || attr.name === "checked") && hasDataModel) continue;
|
|
346
|
+
if (bindTargets.has(attr.name)) continue;
|
|
347
|
+
const liveVal = liveEl.getAttribute(attr.name);
|
|
348
|
+
if (liveVal !== attr.value) {
|
|
349
|
+
liveEl.setAttribute(attr.name, attr.value);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
for (let i = liveAttrs.length - 1; i >= 0; i--) {
|
|
353
|
+
const attr = liveAttrs[i];
|
|
354
|
+
if (!newEl.hasAttribute(attr.name)) {
|
|
355
|
+
if (attr.name === "style" && hasDataShow) continue;
|
|
356
|
+
if (attr.name === "class" && liveHasClassDirectives) continue;
|
|
357
|
+
if ((attr.name === "value" || attr.name === "checked") && hasDataModel) continue;
|
|
358
|
+
if (bindTargets.has(attr.name)) continue;
|
|
359
|
+
liveEl.removeAttribute(attr.name);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
function patchTextNodes(liveEl, newEl) {
|
|
364
|
+
if (liveEl.hasAttribute("data-text")) return;
|
|
365
|
+
const liveTexts = [];
|
|
366
|
+
const newTexts = [];
|
|
367
|
+
for (const child of Array.from(liveEl.childNodes)) {
|
|
368
|
+
if (child.nodeType === Node.TEXT_NODE) liveTexts.push(child);
|
|
369
|
+
}
|
|
370
|
+
for (let i = 0; i < newEl.childNodes.length; i++) {
|
|
371
|
+
const child = newEl.childNodes[i];
|
|
372
|
+
if (child.nodeType === Node.TEXT_NODE) newTexts.push({ node: child, index: i });
|
|
373
|
+
}
|
|
374
|
+
if (liveTexts.length === newTexts.length) {
|
|
375
|
+
for (let i = 0; i < liveTexts.length; i++) {
|
|
376
|
+
if (liveTexts[i].textContent !== newTexts[i].node.textContent) {
|
|
377
|
+
liveTexts[i].textContent = newTexts[i].node.textContent;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
const usedLive = /* @__PURE__ */ new Set();
|
|
383
|
+
let liveIdx = 0;
|
|
384
|
+
for (const { node: newText, index: newChildIdx } of newTexts) {
|
|
385
|
+
if (liveIdx < liveTexts.length) {
|
|
386
|
+
const liveText = liveTexts[liveIdx];
|
|
387
|
+
liveIdx++;
|
|
388
|
+
usedLive.add(liveText);
|
|
389
|
+
if (liveText.textContent !== newText.textContent) {
|
|
390
|
+
liveText.textContent = newText.textContent;
|
|
391
|
+
}
|
|
392
|
+
} else {
|
|
393
|
+
const ref = findTextInsertionRef(liveEl, newEl, newChildIdx);
|
|
394
|
+
liveEl.insertBefore(document.createTextNode(newText.textContent ?? ""), ref);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
for (const lt of liveTexts) {
|
|
398
|
+
if (!usedLive.has(lt) && lt.parentNode === liveEl) {
|
|
399
|
+
liveEl.removeChild(lt);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
function findTextInsertionRef(liveEl, newEl, newIdx) {
|
|
404
|
+
for (let j = newIdx + 1; j < newEl.childNodes.length; j++) {
|
|
405
|
+
const sibling = newEl.childNodes[j];
|
|
406
|
+
if (sibling.nodeType === Node.ELEMENT_NODE) {
|
|
407
|
+
const key = sibling.getAttribute("data-forma-id");
|
|
408
|
+
if (key) {
|
|
409
|
+
const match = liveEl.querySelector(`[data-forma-id="${CSS.escape(key)}"]`);
|
|
410
|
+
if (match && match.parentElement === liveEl) return match;
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
return null;
|
|
415
|
+
}
|
|
416
|
+
function diffChildren(liveParent, newParent, config) {
|
|
417
|
+
if (ownsSubtree(liveParent)) return;
|
|
418
|
+
patchTextNodes(liveParent, newParent);
|
|
419
|
+
const liveChildren = Array.from(liveParent.children);
|
|
420
|
+
const newChildren = Array.from(newParent.children);
|
|
421
|
+
const liveKeyed = /* @__PURE__ */ new Map();
|
|
422
|
+
const liveUnkeyed = [];
|
|
423
|
+
for (const child of liveChildren) {
|
|
424
|
+
if (child.hasAttribute("data-forma-leaving")) continue;
|
|
425
|
+
const key = child.getAttribute("data-forma-id");
|
|
426
|
+
if (key) {
|
|
427
|
+
liveKeyed.set(key, child);
|
|
428
|
+
} else {
|
|
429
|
+
liveUnkeyed.push(child);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
let unkeyedIdx = 0;
|
|
433
|
+
const usedLiveElements = /* @__PURE__ */ new Set();
|
|
434
|
+
for (const newChild of newChildren) {
|
|
435
|
+
const key = newChild.getAttribute("data-forma-id");
|
|
436
|
+
let liveMatch;
|
|
437
|
+
if (key) {
|
|
438
|
+
liveMatch = liveKeyed.get(key);
|
|
439
|
+
} else {
|
|
440
|
+
while (unkeyedIdx < liveUnkeyed.length) {
|
|
441
|
+
const candidate = liveUnkeyed[unkeyedIdx];
|
|
442
|
+
unkeyedIdx++;
|
|
443
|
+
if (candidate.tagName === newChild.tagName && !usedLiveElements.has(candidate)) {
|
|
444
|
+
liveMatch = candidate;
|
|
445
|
+
break;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
if (liveMatch) {
|
|
450
|
+
usedLiveElements.add(liveMatch);
|
|
451
|
+
if (liveMatch.hasAttribute("data-forma-state") && newChild.hasAttribute("data-forma-state")) {
|
|
452
|
+
const mode = determineScopeMode(liveMatch, newChild);
|
|
453
|
+
switch (mode) {
|
|
454
|
+
case "PRESERVE":
|
|
455
|
+
patchAttributes(liveMatch, newChild);
|
|
456
|
+
diffChildren(liveMatch, newChild, config);
|
|
457
|
+
break;
|
|
458
|
+
case "RESET":
|
|
459
|
+
config.unmountScope(liveMatch);
|
|
460
|
+
patchAttributes(liveMatch, newChild);
|
|
461
|
+
replaceInnerContent(liveMatch, newChild);
|
|
462
|
+
config.mountScope(liveMatch);
|
|
463
|
+
break;
|
|
464
|
+
case "REPLACE": {
|
|
465
|
+
config.unmountScope(liveMatch);
|
|
466
|
+
const replacement = newChild.cloneNode(true);
|
|
467
|
+
liveParent.replaceChild(replacement, liveMatch);
|
|
468
|
+
config.mountScope(replacement);
|
|
469
|
+
usedLiveElements.delete(liveMatch);
|
|
470
|
+
liveMatch = replacement;
|
|
471
|
+
usedLiveElements.add(replacement);
|
|
472
|
+
break;
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
} else {
|
|
476
|
+
patchAttributes(liveMatch, newChild);
|
|
477
|
+
diffChildren(liveMatch, newChild, config);
|
|
478
|
+
}
|
|
479
|
+
ensurePosition(liveParent, liveMatch, newChild, newChildren);
|
|
480
|
+
} else {
|
|
481
|
+
const clone = newChild.cloneNode(true);
|
|
482
|
+
const insertionRef = findInsertionPoint(liveParent, newChild, newChildren);
|
|
483
|
+
liveParent.insertBefore(clone, insertionRef);
|
|
484
|
+
usedLiveElements.add(clone);
|
|
485
|
+
if (clone.hasAttribute("data-forma-state")) {
|
|
486
|
+
config.mountScope(clone);
|
|
487
|
+
}
|
|
488
|
+
const nestedScopes = clone.querySelectorAll("[data-forma-state]");
|
|
489
|
+
for (const nested of Array.from(nestedScopes)) {
|
|
490
|
+
config.mountScope(nested);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
for (const child of liveChildren) {
|
|
495
|
+
if (!usedLiveElements.has(child)) {
|
|
496
|
+
if (child.parentElement !== liveParent) continue;
|
|
497
|
+
if (child.hasAttribute("data-forma-leaving")) continue;
|
|
498
|
+
if (child.hasAttribute("data-forma-state")) {
|
|
499
|
+
config.unmountScope(child);
|
|
500
|
+
}
|
|
501
|
+
const nestedScopes = child.querySelectorAll("[data-forma-state]");
|
|
502
|
+
for (const nested of Array.from(nestedScopes)) {
|
|
503
|
+
config.unmountScope(nested);
|
|
504
|
+
}
|
|
505
|
+
liveParent.removeChild(child);
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
function replaceInnerContent(liveEl, newEl) {
|
|
510
|
+
while (liveEl.firstChild) {
|
|
511
|
+
liveEl.removeChild(liveEl.firstChild);
|
|
512
|
+
}
|
|
513
|
+
for (const child of Array.from(newEl.childNodes)) {
|
|
514
|
+
liveEl.appendChild(child.cloneNode(true));
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
function ensurePosition(parent, liveEl, _newEl, newChildren) {
|
|
518
|
+
const newIdx = newChildren.indexOf(_newEl);
|
|
519
|
+
const liveChildElements = Array.from(parent.children);
|
|
520
|
+
const currentIdx = liveChildElements.indexOf(liveEl);
|
|
521
|
+
if (currentIdx !== newIdx) {
|
|
522
|
+
const nextNewChild = newChildren[newIdx + 1];
|
|
523
|
+
if (nextNewChild) {
|
|
524
|
+
const nextKey = nextNewChild.getAttribute("data-forma-id");
|
|
525
|
+
if (nextKey) {
|
|
526
|
+
const nextLive = parent.querySelector(`[data-forma-id="${CSS.escape(nextKey)}"]`);
|
|
527
|
+
if (nextLive && nextLive.parentElement === parent) {
|
|
528
|
+
parent.insertBefore(liveEl, nextLive);
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
parent.appendChild(liveEl);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
function findInsertionPoint(parent, newChild, newChildren) {
|
|
537
|
+
const newIdx = newChildren.indexOf(newChild);
|
|
538
|
+
for (let i = newIdx + 1; i < newChildren.length; i++) {
|
|
539
|
+
const key = newChildren[i].getAttribute("data-forma-id");
|
|
540
|
+
if (key) {
|
|
541
|
+
const existing = parent.querySelector(`[data-forma-id="${CSS.escape(key)}"]`);
|
|
542
|
+
if (existing && existing.parentElement === parent) {
|
|
543
|
+
return existing;
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
return null;
|
|
548
|
+
}
|
|
549
|
+
function createReconciler(config) {
|
|
550
|
+
let _lastHtml = "";
|
|
551
|
+
return function reconcile2(container, html) {
|
|
552
|
+
const trimmed = html.trim();
|
|
553
|
+
if (!trimmed) return;
|
|
554
|
+
if (trimmed === _lastHtml && container.hasChildNodes()) return;
|
|
555
|
+
_lastHtml = trimmed;
|
|
556
|
+
config.disconnectObserver();
|
|
557
|
+
try {
|
|
558
|
+
if (!container.hasChildNodes() || container.children.length === 0) {
|
|
559
|
+
container.innerHTML = trimmed;
|
|
560
|
+
config.batch(() => {
|
|
561
|
+
const scopes = container.querySelectorAll("[data-forma-state]");
|
|
562
|
+
for (const scope of Array.from(scopes)) {
|
|
563
|
+
config.mountScope(scope);
|
|
564
|
+
}
|
|
565
|
+
});
|
|
566
|
+
return;
|
|
567
|
+
}
|
|
568
|
+
const fragment = parseHTML(trimmed);
|
|
569
|
+
const templateContainer = document.createElement("div");
|
|
570
|
+
templateContainer.appendChild(fragment);
|
|
571
|
+
const liveKeys = /* @__PURE__ */ new Set();
|
|
572
|
+
for (const child of Array.from(container.children)) {
|
|
573
|
+
if (child.hasAttribute("data-forma-leaving")) continue;
|
|
574
|
+
const key = child.getAttribute("data-forma-id");
|
|
575
|
+
if (key) liveKeys.add(key);
|
|
576
|
+
}
|
|
577
|
+
let hasOverlap = false;
|
|
578
|
+
if (liveKeys.size > 0) {
|
|
579
|
+
for (const child of Array.from(templateContainer.children)) {
|
|
580
|
+
const key = child.getAttribute("data-forma-id");
|
|
581
|
+
if (key && liveKeys.has(key)) {
|
|
582
|
+
hasOverlap = true;
|
|
583
|
+
break;
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
if (liveKeys.size > 0 && !hasOverlap) {
|
|
588
|
+
config.batch(() => {
|
|
589
|
+
const liveScopes = container.querySelectorAll("[data-forma-state]");
|
|
590
|
+
for (const scope of Array.from(liveScopes)) {
|
|
591
|
+
config.unmountScope(scope);
|
|
592
|
+
}
|
|
593
|
+
container.innerHTML = trimmed;
|
|
594
|
+
const newScopes = container.querySelectorAll("[data-forma-state]");
|
|
595
|
+
for (const scope of Array.from(newScopes)) {
|
|
596
|
+
config.mountScope(scope);
|
|
597
|
+
}
|
|
598
|
+
});
|
|
599
|
+
return;
|
|
600
|
+
}
|
|
601
|
+
config.batch(() => {
|
|
602
|
+
diffChildren(container, templateContainer, config);
|
|
603
|
+
});
|
|
604
|
+
} finally {
|
|
605
|
+
config.reconnectObserver();
|
|
606
|
+
}
|
|
607
|
+
};
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
// src/runtime.ts
|
|
611
|
+
var _refetchRegistry = /* @__PURE__ */ new Map();
|
|
612
|
+
function $refetch(id) {
|
|
613
|
+
const fn = _refetchRegistry.get(id);
|
|
614
|
+
if (fn) {
|
|
615
|
+
fn();
|
|
616
|
+
} else if (_debug) {
|
|
617
|
+
dbg(`$refetch: no data-fetch with id "${id}" found`);
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
function createChildScope(parent, locals) {
|
|
621
|
+
const localGetters = /* @__PURE__ */ Object.create(null);
|
|
622
|
+
for (const key of Object.keys(locals)) {
|
|
623
|
+
localGetters[key] = () => locals[key];
|
|
624
|
+
}
|
|
625
|
+
return {
|
|
626
|
+
getters: new Proxy(parent.getters, {
|
|
627
|
+
get(target, prop) {
|
|
628
|
+
if (prop in localGetters) return localGetters[prop];
|
|
629
|
+
return target[prop];
|
|
630
|
+
},
|
|
631
|
+
has(target, prop) {
|
|
632
|
+
return prop in localGetters || prop in target;
|
|
633
|
+
}
|
|
634
|
+
}),
|
|
635
|
+
setters: parent.setters
|
|
636
|
+
};
|
|
637
|
+
}
|
|
638
|
+
var _debug = false;
|
|
639
|
+
var _unsafeEvalMode = "mutable";
|
|
640
|
+
var _allowUnsafeEval = false;
|
|
641
|
+
var _diagnosticsEnabled = true;
|
|
642
|
+
function dbg(...args) {
|
|
643
|
+
if (_debug || typeof window !== "undefined" && window.__FORMA_DEBUG) {
|
|
644
|
+
console.log("[FormaJS]", ...args);
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
var diagnostics = /* @__PURE__ */ new Map();
|
|
648
|
+
function parseBooleanFlag(raw) {
|
|
649
|
+
if (raw == null) return void 0;
|
|
650
|
+
const normalized = raw.trim().toLowerCase();
|
|
651
|
+
if (normalized === "1" || normalized === "true" || normalized === "on" || normalized === "yes") return true;
|
|
652
|
+
if (normalized === "0" || normalized === "false" || normalized === "off" || normalized === "no") return false;
|
|
653
|
+
return void 0;
|
|
654
|
+
}
|
|
655
|
+
function parseUnsafeEvalMode(raw) {
|
|
656
|
+
if (raw == null) return void 0;
|
|
657
|
+
const normalized = raw.trim().toLowerCase();
|
|
658
|
+
if (normalized === "mutable") return "mutable";
|
|
659
|
+
if (normalized === "locked-off" || normalized === "off" || normalized === "disabled") {
|
|
660
|
+
return "locked-off";
|
|
661
|
+
}
|
|
662
|
+
if (normalized === "locked-on" || normalized === "on" || normalized === "enabled") {
|
|
663
|
+
return "locked-on";
|
|
664
|
+
}
|
|
665
|
+
return void 0;
|
|
666
|
+
}
|
|
667
|
+
function readRuntimeConfig() {
|
|
668
|
+
const config = {};
|
|
669
|
+
if (typeof window !== "undefined") {
|
|
670
|
+
const globalConfig = window.__FORMA_RUNTIME_CONFIG;
|
|
671
|
+
if (globalConfig) {
|
|
672
|
+
if (typeof globalConfig.allowUnsafeEval === "boolean") {
|
|
673
|
+
config.allowUnsafeEval = globalConfig.allowUnsafeEval;
|
|
674
|
+
}
|
|
675
|
+
if (typeof globalConfig.unsafeEvalMode === "string") {
|
|
676
|
+
const parsed = parseUnsafeEvalMode(globalConfig.unsafeEvalMode);
|
|
677
|
+
if (parsed) config.unsafeEvalMode = parsed;
|
|
678
|
+
}
|
|
679
|
+
if (typeof globalConfig.lockUnsafeEval === "boolean") {
|
|
680
|
+
config.lockUnsafeEval = globalConfig.lockUnsafeEval;
|
|
681
|
+
}
|
|
682
|
+
if (typeof globalConfig.diagnostics === "boolean") {
|
|
683
|
+
config.diagnostics = globalConfig.diagnostics;
|
|
684
|
+
}
|
|
685
|
+
if (typeof globalConfig.autoContainment === "boolean") {
|
|
686
|
+
config.autoContainment = globalConfig.autoContainment;
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
if (typeof document !== "undefined") {
|
|
691
|
+
const script = document.currentScript;
|
|
692
|
+
if (script) {
|
|
693
|
+
const unsafeFromAttr = parseBooleanFlag(script.getAttribute("data-forma-unsafe-eval"));
|
|
694
|
+
if (unsafeFromAttr !== void 0) {
|
|
695
|
+
config.allowUnsafeEval = unsafeFromAttr;
|
|
696
|
+
}
|
|
697
|
+
const modeFromAttr = parseUnsafeEvalMode(
|
|
698
|
+
script.getAttribute("data-forma-unsafe-eval-mode")
|
|
699
|
+
);
|
|
700
|
+
if (modeFromAttr !== void 0) {
|
|
701
|
+
config.unsafeEvalMode = modeFromAttr;
|
|
702
|
+
}
|
|
703
|
+
const lockFromAttr = parseBooleanFlag(script.getAttribute("data-forma-lock-unsafe-eval"));
|
|
704
|
+
if (lockFromAttr !== void 0) {
|
|
705
|
+
config.lockUnsafeEval = lockFromAttr;
|
|
706
|
+
}
|
|
707
|
+
const diagnosticsFromAttr = parseBooleanFlag(script.getAttribute("data-forma-diagnostics"));
|
|
708
|
+
if (diagnosticsFromAttr !== void 0) {
|
|
709
|
+
config.diagnostics = diagnosticsFromAttr;
|
|
710
|
+
}
|
|
711
|
+
const containmentFromAttr = parseBooleanFlag(script.getAttribute("data-forma-auto-containment"));
|
|
712
|
+
if (containmentFromAttr !== void 0) {
|
|
713
|
+
config.autoContainment = containmentFromAttr;
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
return config;
|
|
718
|
+
}
|
|
719
|
+
function reportDiagnostic(kind, expr, reason) {
|
|
720
|
+
if (!_diagnosticsEnabled) return;
|
|
721
|
+
const key = `${kind}|${reason}|${expr}`;
|
|
722
|
+
const now = Date.now();
|
|
723
|
+
const existing = diagnostics.get(key);
|
|
724
|
+
if (existing) {
|
|
725
|
+
existing.count += 1;
|
|
726
|
+
existing.lastSeenAt = now;
|
|
727
|
+
} else {
|
|
728
|
+
diagnostics.set(key, {
|
|
729
|
+
kind,
|
|
730
|
+
expr,
|
|
731
|
+
reason,
|
|
732
|
+
count: 1,
|
|
733
|
+
firstSeenAt: now,
|
|
734
|
+
lastSeenAt: now
|
|
735
|
+
});
|
|
736
|
+
console.warn(`[FormaJS] ${reason}: ${expr}`);
|
|
737
|
+
}
|
|
738
|
+
try {
|
|
739
|
+
if (typeof window !== "undefined") {
|
|
740
|
+
const detail = {
|
|
741
|
+
kind,
|
|
742
|
+
expr,
|
|
743
|
+
reason,
|
|
744
|
+
count: diagnostics.get(key)?.count ?? 1
|
|
745
|
+
};
|
|
746
|
+
window.dispatchEvent(new CustomEvent("formajs:diagnostic", { detail }));
|
|
747
|
+
}
|
|
748
|
+
} catch {
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
var buildUnsafeEvalMode = parseUnsafeEvalMode(
|
|
752
|
+
"locked-off"
|
|
753
|
+
);
|
|
754
|
+
if (buildUnsafeEvalMode) {
|
|
755
|
+
_unsafeEvalMode = buildUnsafeEvalMode;
|
|
756
|
+
if (_unsafeEvalMode === "locked-off") _allowUnsafeEval = false;
|
|
757
|
+
if (_unsafeEvalMode === "locked-on") _allowUnsafeEval = true;
|
|
758
|
+
}
|
|
759
|
+
var runtimeConfig = readRuntimeConfig();
|
|
760
|
+
var configUnsafeMode = runtimeConfig.lockUnsafeEval ? "locked-off" : runtimeConfig.unsafeEvalMode;
|
|
761
|
+
if (configUnsafeMode) {
|
|
762
|
+
_unsafeEvalMode = configUnsafeMode;
|
|
763
|
+
if (_unsafeEvalMode === "locked-off") _allowUnsafeEval = false;
|
|
764
|
+
if (_unsafeEvalMode === "locked-on") _allowUnsafeEval = true;
|
|
765
|
+
}
|
|
766
|
+
if (_unsafeEvalMode === "mutable" && typeof runtimeConfig.allowUnsafeEval === "boolean") {
|
|
767
|
+
_allowUnsafeEval = runtimeConfig.allowUnsafeEval;
|
|
768
|
+
}
|
|
769
|
+
if (typeof runtimeConfig.diagnostics === "boolean") {
|
|
770
|
+
_diagnosticsEnabled = runtimeConfig.diagnostics;
|
|
771
|
+
}
|
|
772
|
+
var _autoContainment = runtimeConfig.autoContainment === true;
|
|
773
|
+
function getScheduler() {
|
|
774
|
+
const candidate = globalThis?.scheduler;
|
|
775
|
+
if (!candidate) return void 0;
|
|
776
|
+
if (typeof candidate.yield === "function" || typeof candidate.postTask === "function") {
|
|
777
|
+
return candidate;
|
|
778
|
+
}
|
|
779
|
+
return void 0;
|
|
780
|
+
}
|
|
781
|
+
async function yieldToMain() {
|
|
782
|
+
const scheduler = getScheduler();
|
|
783
|
+
if (scheduler?.yield) {
|
|
784
|
+
await scheduler.yield();
|
|
785
|
+
return;
|
|
786
|
+
}
|
|
787
|
+
if (scheduler?.postTask) {
|
|
788
|
+
await scheduler.postTask(() => {
|
|
789
|
+
}, { priority: "background" });
|
|
790
|
+
return;
|
|
791
|
+
}
|
|
792
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
793
|
+
}
|
|
794
|
+
function applyContainmentHints(root = document, options = {}) {
|
|
795
|
+
const selector = options.selector ?? "[data-forma-contain]";
|
|
796
|
+
if (!selector) return 0;
|
|
797
|
+
if (typeof root.querySelectorAll !== "function") return 0;
|
|
798
|
+
const nodes = root.querySelectorAll(selector);
|
|
799
|
+
let applied = 0;
|
|
800
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
801
|
+
const el = nodes[i];
|
|
802
|
+
if (!el?.style) continue;
|
|
803
|
+
const contain = el.getAttribute("data-forma-contain") ?? options.contain ?? "layout style paint";
|
|
804
|
+
const contentVisibility = el.getAttribute("data-forma-content-visibility") ?? options.contentVisibility ?? "auto";
|
|
805
|
+
const containIntrinsicSize = el.getAttribute("data-forma-contain-intrinsic-size") ?? options.containIntrinsicSize ?? "auto 800px";
|
|
806
|
+
const skipExisting = options.skipIfAlreadySet === true;
|
|
807
|
+
let changed = false;
|
|
808
|
+
const containCurrent = el.style.getPropertyValue("contain");
|
|
809
|
+
const contentVisCurrent = el.style.getPropertyValue("content-visibility");
|
|
810
|
+
const containSizeCurrent = el.style.getPropertyValue("contain-intrinsic-size");
|
|
811
|
+
if (contain !== "off" && (!skipExisting || !containCurrent)) {
|
|
812
|
+
el.style.setProperty("contain", contain);
|
|
813
|
+
changed = true;
|
|
814
|
+
}
|
|
815
|
+
if (contentVisibility !== "off" && (!skipExisting || !contentVisCurrent)) {
|
|
816
|
+
el.style.setProperty("content-visibility", contentVisibility);
|
|
817
|
+
changed = true;
|
|
818
|
+
}
|
|
819
|
+
if (containIntrinsicSize !== "off" && (!skipExisting || !containSizeCurrent)) {
|
|
820
|
+
el.style.setProperty("contain-intrinsic-size", containIntrinsicSize);
|
|
821
|
+
changed = true;
|
|
822
|
+
}
|
|
823
|
+
if (changed) applied++;
|
|
824
|
+
}
|
|
825
|
+
if (_debug && applied > 0) {
|
|
826
|
+
dbg("applyContainmentHints: applied to", applied, "element(s)");
|
|
827
|
+
}
|
|
828
|
+
return applied;
|
|
829
|
+
}
|
|
830
|
+
var RE_STRING_SINGLE = /^'[^']*'$/;
|
|
831
|
+
var RE_STRING_DOUBLE = /^"[^"]*"$/;
|
|
832
|
+
var RE_NUMBER = /^-?\d+(\.\d+)?$/;
|
|
833
|
+
var RE_IDENTIFIER = /^[a-zA-Z_$]\w*$/;
|
|
834
|
+
var RE_DOT_ACCESS = /^(\w+)\.(\w+)$/;
|
|
835
|
+
var RE_DEEP_DOT = /^(\w+)\.(\w+)\.(\w+)(?:\.(\w+))?$/;
|
|
836
|
+
var RE_BRACKET = /^(\w+)\[(\d+|'[^']*'|"[^"]*")\]$/;
|
|
837
|
+
var RE_TERNARY = /^(.+?)\s*\?\s*(.+?)\s*:\s*(.+)$/;
|
|
838
|
+
var RE_NULLISH = /^(.+?)\s*\?\?\s*(.+)$/;
|
|
839
|
+
var RE_AND = /^(.+?)\s*&&\s*(.+)$/;
|
|
840
|
+
var RE_OR = /^(.+?)\s*\|\|\s*(.+)$/;
|
|
841
|
+
var RE_COMPARISON = /^(.+?)\s*(===|!==|==|!=|>=|<=|>|<)\s*(.+)$/;
|
|
842
|
+
var RE_MUL = /^(.+?)\s*([*/%])\s*(.+)$/;
|
|
843
|
+
var RE_ADD = /^(.+?)\s*([+-])\s*(.+)$/;
|
|
844
|
+
var RE_TEMPLATE_LIT = /^`([^`]*)`$/;
|
|
845
|
+
var RE_TEMPLATE_INTERP = /\$\{([^}]+)\}/g;
|
|
846
|
+
var RE_METHOD_CALL = /^(\w+)\.(\w+)\((.*)\)$/;
|
|
847
|
+
var RE_GROUP_METHOD_CALL = /^\((.+)\)\.(\w+)\((.*)\)$/;
|
|
848
|
+
var RE_STRIP_BRACES = /^\{|\}$/g;
|
|
849
|
+
var RE_DIGIT_ONLY = /^\d+$/;
|
|
850
|
+
var RE_POST_INCR = /^(\w+)(\+\+|--)$/;
|
|
851
|
+
var RE_PRE_INCR = /^(\+\+|--)(\w+)$/;
|
|
852
|
+
var RE_TOGGLE = /^(\w+)\s*=\s*!(\w+)$/;
|
|
853
|
+
var RE_ASSIGN = /^(\w+)\s*=\s*(.+)$/;
|
|
854
|
+
var RE_COMPOUND = /^(\w+)\s*(\+=|-=|\*=|\/=)\s*(.+)$/;
|
|
855
|
+
var RE_IF_PREFIX = /^if\b/;
|
|
856
|
+
var RE_UNQUOTED_KEYS = /(\w+)\s*:/g;
|
|
857
|
+
var RE_COMPUTED = /^(\w+)\s*=\s*(.+)$/;
|
|
858
|
+
var RE_FETCH = /^(.+?)(?:→|->)\s*(\S+)(.*)$/;
|
|
859
|
+
var RE_FETCH_METHOD = /^(GET|POST|PUT|PATCH|DELETE)\s+(.+)$/i;
|
|
860
|
+
var RE_STRIP_ITEM_BRACES = /^\{item\.?|\}$/g;
|
|
861
|
+
var RE_REFETCH_CALL = /^\$refetch\(\s*['"]([^'"]+)['"]\s*\)$/;
|
|
862
|
+
var TRANSITION_STATE_SYM = /* @__PURE__ */ Symbol.for("forma-transition-state");
|
|
863
|
+
var EXPRESSION_CACHE_MAX = 2048;
|
|
864
|
+
var expressionCache = /* @__PURE__ */ new Map();
|
|
865
|
+
function cacheExpression(key, factory) {
|
|
866
|
+
if (expressionCache.size >= EXPRESSION_CACHE_MAX) {
|
|
867
|
+
const first = expressionCache.keys().next().value;
|
|
868
|
+
if (first !== void 0) expressionCache.delete(first);
|
|
869
|
+
}
|
|
870
|
+
expressionCache.set(key, factory);
|
|
871
|
+
}
|
|
872
|
+
var scopeExpressionCache = /* @__PURE__ */ new WeakMap();
|
|
873
|
+
var scopeHandlerCache = /* @__PURE__ */ new WeakMap();
|
|
874
|
+
var compiledTemplateCache = /* @__PURE__ */ new Map();
|
|
875
|
+
var UNSAFE_METHOD_NAMES = /* @__PURE__ */ new Set(["constructor", "__proto__", "prototype"]);
|
|
876
|
+
var TEXT_BINDING_SYM = /* @__PURE__ */ Symbol.for("forma-text-binding-cache");
|
|
877
|
+
function toTextValue(value2) {
|
|
878
|
+
if (value2 == null) return "";
|
|
879
|
+
if (typeof value2 === "string") return value2;
|
|
880
|
+
if (typeof value2 === "symbol") return value2.toString();
|
|
881
|
+
return String(value2);
|
|
882
|
+
}
|
|
883
|
+
function setElementTextFast(el, next) {
|
|
884
|
+
let cache = el[TEXT_BINDING_SYM];
|
|
885
|
+
if (!cache) {
|
|
886
|
+
cache = { initialized: false, last: "", node: null };
|
|
887
|
+
el[TEXT_BINDING_SYM] = cache;
|
|
888
|
+
}
|
|
889
|
+
if (cache.initialized && cache.last === next) return;
|
|
890
|
+
let node = cache.node;
|
|
891
|
+
if (!node || node.parentNode !== el || el.childNodes.length !== 1 || el.firstChild !== node) {
|
|
892
|
+
if (el.childNodes.length === 1 && el.firstChild?.nodeType === Node.TEXT_NODE) {
|
|
893
|
+
node = el.firstChild;
|
|
894
|
+
cache.node = node;
|
|
895
|
+
} else {
|
|
896
|
+
el.textContent = next;
|
|
897
|
+
const first = el.firstChild;
|
|
898
|
+
cache.node = first && first.nodeType === Node.TEXT_NODE && el.childNodes.length === 1 ? first : null;
|
|
899
|
+
cache.last = next;
|
|
900
|
+
cache.initialized = true;
|
|
901
|
+
return;
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
node.data = next;
|
|
905
|
+
cache.last = next;
|
|
906
|
+
cache.initialized = true;
|
|
907
|
+
}
|
|
908
|
+
function splitCallArgs(raw) {
|
|
909
|
+
const out = [];
|
|
910
|
+
if (raw.trim() === "") return out;
|
|
911
|
+
let depth = 0;
|
|
912
|
+
let inSingle = false;
|
|
913
|
+
let inDouble = false;
|
|
914
|
+
let escaped = false;
|
|
915
|
+
let start = 0;
|
|
916
|
+
for (let i = 0; i < raw.length; i++) {
|
|
917
|
+
const ch = raw[i];
|
|
918
|
+
if (escaped) {
|
|
919
|
+
escaped = false;
|
|
920
|
+
continue;
|
|
921
|
+
}
|
|
922
|
+
if (ch === "\\") {
|
|
923
|
+
escaped = true;
|
|
924
|
+
continue;
|
|
925
|
+
}
|
|
926
|
+
if (inSingle) {
|
|
927
|
+
if (ch === "'") inSingle = false;
|
|
928
|
+
continue;
|
|
929
|
+
}
|
|
930
|
+
if (inDouble) {
|
|
931
|
+
if (ch === '"') inDouble = false;
|
|
932
|
+
continue;
|
|
933
|
+
}
|
|
934
|
+
if (ch === "'") {
|
|
935
|
+
inSingle = true;
|
|
936
|
+
continue;
|
|
937
|
+
}
|
|
938
|
+
if (ch === '"') {
|
|
939
|
+
inDouble = true;
|
|
940
|
+
continue;
|
|
941
|
+
}
|
|
942
|
+
if (ch === "(") {
|
|
943
|
+
depth++;
|
|
944
|
+
continue;
|
|
945
|
+
}
|
|
946
|
+
if (ch === ")") {
|
|
947
|
+
if (depth > 0) depth--;
|
|
948
|
+
continue;
|
|
949
|
+
}
|
|
950
|
+
if (ch === "," && depth === 0) {
|
|
951
|
+
out.push(raw.slice(start, i).trim());
|
|
952
|
+
start = i + 1;
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
out.push(raw.slice(start).trim());
|
|
956
|
+
return out.filter(Boolean);
|
|
957
|
+
}
|
|
958
|
+
function readBalancedSegment(input, start, open, close) {
|
|
959
|
+
if (input[start] !== open) return null;
|
|
960
|
+
let depth = 0;
|
|
961
|
+
let inSingle = false;
|
|
962
|
+
let inDouble = false;
|
|
963
|
+
let inTemplate = false;
|
|
964
|
+
let escaped = false;
|
|
965
|
+
for (let i = start; i < input.length; i++) {
|
|
966
|
+
const ch = input[i];
|
|
967
|
+
if (escaped) {
|
|
968
|
+
escaped = false;
|
|
969
|
+
continue;
|
|
970
|
+
}
|
|
971
|
+
if (ch === "\\" && (inSingle || inDouble || inTemplate)) {
|
|
972
|
+
escaped = true;
|
|
973
|
+
continue;
|
|
974
|
+
}
|
|
975
|
+
if (inSingle) {
|
|
976
|
+
if (ch === "'") inSingle = false;
|
|
977
|
+
continue;
|
|
978
|
+
}
|
|
979
|
+
if (inDouble) {
|
|
980
|
+
if (ch === '"') inDouble = false;
|
|
981
|
+
continue;
|
|
982
|
+
}
|
|
983
|
+
if (inTemplate) {
|
|
984
|
+
if (ch === "`") inTemplate = false;
|
|
985
|
+
continue;
|
|
986
|
+
}
|
|
987
|
+
if (ch === "'") {
|
|
988
|
+
inSingle = true;
|
|
989
|
+
continue;
|
|
990
|
+
}
|
|
991
|
+
if (ch === '"') {
|
|
992
|
+
inDouble = true;
|
|
993
|
+
continue;
|
|
994
|
+
}
|
|
995
|
+
if (ch === "`") {
|
|
996
|
+
inTemplate = true;
|
|
997
|
+
continue;
|
|
998
|
+
}
|
|
999
|
+
if (ch === open) {
|
|
1000
|
+
depth++;
|
|
1001
|
+
continue;
|
|
1002
|
+
}
|
|
1003
|
+
if (ch === close) {
|
|
1004
|
+
depth--;
|
|
1005
|
+
if (depth === 0) {
|
|
1006
|
+
return {
|
|
1007
|
+
inner: input.slice(start + 1, i),
|
|
1008
|
+
end: i
|
|
1009
|
+
};
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
return null;
|
|
1014
|
+
}
|
|
1015
|
+
function splitTopLevelStatements(raw) {
|
|
1016
|
+
const input = raw.trim();
|
|
1017
|
+
if (!input) return [];
|
|
1018
|
+
const out = [];
|
|
1019
|
+
let depthParen = 0;
|
|
1020
|
+
let depthBrace = 0;
|
|
1021
|
+
let depthBracket = 0;
|
|
1022
|
+
let inSingle = false;
|
|
1023
|
+
let inDouble = false;
|
|
1024
|
+
let inTemplate = false;
|
|
1025
|
+
let escaped = false;
|
|
1026
|
+
let start = 0;
|
|
1027
|
+
for (let i = 0; i < input.length; i++) {
|
|
1028
|
+
const ch = input[i];
|
|
1029
|
+
if (escaped) {
|
|
1030
|
+
escaped = false;
|
|
1031
|
+
continue;
|
|
1032
|
+
}
|
|
1033
|
+
if (ch === "\\" && (inSingle || inDouble || inTemplate)) {
|
|
1034
|
+
escaped = true;
|
|
1035
|
+
continue;
|
|
1036
|
+
}
|
|
1037
|
+
if (inSingle) {
|
|
1038
|
+
if (ch === "'") inSingle = false;
|
|
1039
|
+
continue;
|
|
1040
|
+
}
|
|
1041
|
+
if (inDouble) {
|
|
1042
|
+
if (ch === '"') inDouble = false;
|
|
1043
|
+
continue;
|
|
1044
|
+
}
|
|
1045
|
+
if (inTemplate) {
|
|
1046
|
+
if (ch === "`") inTemplate = false;
|
|
1047
|
+
continue;
|
|
1048
|
+
}
|
|
1049
|
+
if (ch === "'") {
|
|
1050
|
+
inSingle = true;
|
|
1051
|
+
continue;
|
|
1052
|
+
}
|
|
1053
|
+
if (ch === '"') {
|
|
1054
|
+
inDouble = true;
|
|
1055
|
+
continue;
|
|
1056
|
+
}
|
|
1057
|
+
if (ch === "`") {
|
|
1058
|
+
inTemplate = true;
|
|
1059
|
+
continue;
|
|
1060
|
+
}
|
|
1061
|
+
if (ch === "(") depthParen++;
|
|
1062
|
+
else if (ch === ")" && depthParen > 0) depthParen--;
|
|
1063
|
+
else if (ch === "{") depthBrace++;
|
|
1064
|
+
else if (ch === "}" && depthBrace > 0) depthBrace--;
|
|
1065
|
+
else if (ch === "[") depthBracket++;
|
|
1066
|
+
else if (ch === "]" && depthBracket > 0) depthBracket--;
|
|
1067
|
+
if (ch === ";" && depthParen === 0 && depthBrace === 0 && depthBracket === 0) {
|
|
1068
|
+
const stmt = input.slice(start, i).trim();
|
|
1069
|
+
if (stmt) out.push(stmt);
|
|
1070
|
+
start = i + 1;
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
const tail = input.slice(start).trim();
|
|
1074
|
+
if (tail) out.push(tail);
|
|
1075
|
+
return out;
|
|
1076
|
+
}
|
|
1077
|
+
function consumeStatement(raw) {
|
|
1078
|
+
const input = raw.trim();
|
|
1079
|
+
if (!input) return null;
|
|
1080
|
+
if (input.startsWith("{")) {
|
|
1081
|
+
const block = readBalancedSegment(input, 0, "{", "}");
|
|
1082
|
+
if (!block) return null;
|
|
1083
|
+
const body = block.inner.trim();
|
|
1084
|
+
let rest = input.slice(block.end + 1).trim();
|
|
1085
|
+
if (rest.startsWith(";")) rest = rest.slice(1).trim();
|
|
1086
|
+
return { body, rest };
|
|
1087
|
+
}
|
|
1088
|
+
let depthParen = 0;
|
|
1089
|
+
let depthBrace = 0;
|
|
1090
|
+
let depthBracket = 0;
|
|
1091
|
+
let inSingle = false;
|
|
1092
|
+
let inDouble = false;
|
|
1093
|
+
let inTemplate = false;
|
|
1094
|
+
let escaped = false;
|
|
1095
|
+
for (let i = 0; i < input.length; i++) {
|
|
1096
|
+
const ch = input[i];
|
|
1097
|
+
if (escaped) {
|
|
1098
|
+
escaped = false;
|
|
1099
|
+
continue;
|
|
1100
|
+
}
|
|
1101
|
+
if (ch === "\\" && (inSingle || inDouble || inTemplate)) {
|
|
1102
|
+
escaped = true;
|
|
1103
|
+
continue;
|
|
1104
|
+
}
|
|
1105
|
+
if (inSingle) {
|
|
1106
|
+
if (ch === "'") inSingle = false;
|
|
1107
|
+
continue;
|
|
1108
|
+
}
|
|
1109
|
+
if (inDouble) {
|
|
1110
|
+
if (ch === '"') inDouble = false;
|
|
1111
|
+
continue;
|
|
1112
|
+
}
|
|
1113
|
+
if (inTemplate) {
|
|
1114
|
+
if (ch === "`") inTemplate = false;
|
|
1115
|
+
continue;
|
|
1116
|
+
}
|
|
1117
|
+
if (ch === "'") {
|
|
1118
|
+
inSingle = true;
|
|
1119
|
+
continue;
|
|
1120
|
+
}
|
|
1121
|
+
if (ch === '"') {
|
|
1122
|
+
inDouble = true;
|
|
1123
|
+
continue;
|
|
1124
|
+
}
|
|
1125
|
+
if (ch === "`") {
|
|
1126
|
+
inTemplate = true;
|
|
1127
|
+
continue;
|
|
1128
|
+
}
|
|
1129
|
+
if (ch === "(") depthParen++;
|
|
1130
|
+
else if (ch === ")" && depthParen > 0) depthParen--;
|
|
1131
|
+
else if (ch === "{") depthBrace++;
|
|
1132
|
+
else if (ch === "}" && depthBrace > 0) depthBrace--;
|
|
1133
|
+
else if (ch === "[") depthBracket++;
|
|
1134
|
+
else if (ch === "]" && depthBracket > 0) depthBracket--;
|
|
1135
|
+
if (ch === ";" && depthParen === 0 && depthBrace === 0 && depthBracket === 0) {
|
|
1136
|
+
return {
|
|
1137
|
+
body: input.slice(0, i).trim(),
|
|
1138
|
+
rest: input.slice(i + 1).trim()
|
|
1139
|
+
};
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
return {
|
|
1143
|
+
body: input,
|
|
1144
|
+
rest: ""
|
|
1145
|
+
};
|
|
1146
|
+
}
|
|
1147
|
+
function parseIfHandler(expr, scope) {
|
|
1148
|
+
const input = expr.trim();
|
|
1149
|
+
if (!RE_IF_PREFIX.test(input)) return null;
|
|
1150
|
+
let idx = 2;
|
|
1151
|
+
while (idx < input.length && /\s/.test(input[idx])) idx++;
|
|
1152
|
+
if (input[idx] !== "(") return null;
|
|
1153
|
+
const condSegment = readBalancedSegment(input, idx, "(", ")");
|
|
1154
|
+
if (!condSegment) return null;
|
|
1155
|
+
const condExpr = parseExpression(condSegment.inner.trim(), scope);
|
|
1156
|
+
if (!condExpr) return null;
|
|
1157
|
+
let rest = input.slice(condSegment.end + 1).trim();
|
|
1158
|
+
const thenStmt = consumeStatement(rest);
|
|
1159
|
+
if (!thenStmt || !thenStmt.body) return null;
|
|
1160
|
+
const thenHandler = parseHandler(thenStmt.body, scope);
|
|
1161
|
+
if (!thenHandler) return null;
|
|
1162
|
+
rest = thenStmt.rest.trim();
|
|
1163
|
+
let elseHandler = null;
|
|
1164
|
+
if (rest.startsWith("else")) {
|
|
1165
|
+
rest = rest.slice("else".length).trim();
|
|
1166
|
+
const elseStmt = consumeStatement(rest);
|
|
1167
|
+
if (!elseStmt || !elseStmt.body) return null;
|
|
1168
|
+
elseHandler = parseHandler(elseStmt.body, scope);
|
|
1169
|
+
if (!elseHandler) return null;
|
|
1170
|
+
rest = elseStmt.rest.trim();
|
|
1171
|
+
}
|
|
1172
|
+
if (rest.length > 0) return null;
|
|
1173
|
+
return (e) => {
|
|
1174
|
+
batch(() => {
|
|
1175
|
+
if (condExpr()) thenHandler(e);
|
|
1176
|
+
else elseHandler?.(e);
|
|
1177
|
+
});
|
|
1178
|
+
};
|
|
1179
|
+
}
|
|
1180
|
+
function unwrapOuterParens(raw) {
|
|
1181
|
+
let expr = raw.trim();
|
|
1182
|
+
while (expr.startsWith("(")) {
|
|
1183
|
+
const segment = readBalancedSegment(expr, 0, "(", ")");
|
|
1184
|
+
if (!segment || segment.end !== expr.length - 1) break;
|
|
1185
|
+
const inner = segment.inner.trim();
|
|
1186
|
+
if (!inner) break;
|
|
1187
|
+
expr = inner;
|
|
1188
|
+
}
|
|
1189
|
+
return expr;
|
|
1190
|
+
}
|
|
1191
|
+
function compileTemplate(text) {
|
|
1192
|
+
const cached = compiledTemplateCache.get(text);
|
|
1193
|
+
if (cached) return cached;
|
|
1194
|
+
const statics = [];
|
|
1195
|
+
const dynamics = [];
|
|
1196
|
+
let lastIndex = 0;
|
|
1197
|
+
const re = /\{item\.?(\w*)\}/g;
|
|
1198
|
+
let m;
|
|
1199
|
+
while ((m = re.exec(text)) !== null) {
|
|
1200
|
+
statics.push(text.slice(lastIndex, m.index));
|
|
1201
|
+
dynamics.push(m[1]);
|
|
1202
|
+
lastIndex = re.lastIndex;
|
|
1203
|
+
}
|
|
1204
|
+
statics.push(text.slice(lastIndex));
|
|
1205
|
+
const result = {
|
|
1206
|
+
statics,
|
|
1207
|
+
dynamics,
|
|
1208
|
+
hasItemRef: dynamics.length > 0
|
|
1209
|
+
};
|
|
1210
|
+
compiledTemplateCache.set(text, result);
|
|
1211
|
+
return result;
|
|
1212
|
+
}
|
|
1213
|
+
var templateTexts = /* @__PURE__ */ new WeakMap();
|
|
1214
|
+
function evaluateCompiledTemplate(compiled, item) {
|
|
1215
|
+
if (!compiled.hasItemRef) return compiled.statics[0];
|
|
1216
|
+
let result = compiled.statics[0];
|
|
1217
|
+
for (let i = 0; i < compiled.dynamics.length; i++) {
|
|
1218
|
+
const key = compiled.dynamics[i];
|
|
1219
|
+
if (!key) {
|
|
1220
|
+
result += typeof item === "object" ? JSON.stringify(item) : String(item ?? "");
|
|
1221
|
+
} else {
|
|
1222
|
+
result += String(item?.[key] ?? "");
|
|
1223
|
+
}
|
|
1224
|
+
result += compiled.statics[i + 1] ?? "";
|
|
1225
|
+
}
|
|
1226
|
+
return result;
|
|
1227
|
+
}
|
|
1228
|
+
function cloneWithTemplateData(template, item) {
|
|
1229
|
+
const clone = template.cloneNode(true);
|
|
1230
|
+
const walker = document.createTreeWalker(clone, NodeFilter.SHOW_TEXT);
|
|
1231
|
+
while (walker.nextNode()) {
|
|
1232
|
+
const node = walker.currentNode;
|
|
1233
|
+
const text = node.textContent ?? "";
|
|
1234
|
+
if (text.includes("{item")) {
|
|
1235
|
+
const compiled = compileTemplate(text);
|
|
1236
|
+
templateTexts.set(node, compiled);
|
|
1237
|
+
node.textContent = evaluateCompiledTemplate(compiled, item);
|
|
1238
|
+
}
|
|
1239
|
+
}
|
|
1240
|
+
cloneAttributeTemplates(clone, item);
|
|
1241
|
+
return clone;
|
|
1242
|
+
}
|
|
1243
|
+
function updateTemplateData(el, item) {
|
|
1244
|
+
const walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT);
|
|
1245
|
+
while (walker.nextNode()) {
|
|
1246
|
+
const node = walker.currentNode;
|
|
1247
|
+
const compiled = templateTexts.get(node);
|
|
1248
|
+
if (compiled) {
|
|
1249
|
+
node.textContent = evaluateCompiledTemplate(compiled, item);
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
var templateAttrs = /* @__PURE__ */ new WeakMap();
|
|
1254
|
+
var DIRECTIVE_ATTR_PREFIXES = [
|
|
1255
|
+
"data-list",
|
|
1256
|
+
"data-show",
|
|
1257
|
+
"data-text",
|
|
1258
|
+
"data-if",
|
|
1259
|
+
"data-model",
|
|
1260
|
+
"data-on:",
|
|
1261
|
+
"data-class:",
|
|
1262
|
+
"data-bind:",
|
|
1263
|
+
"data-computed",
|
|
1264
|
+
"data-persist",
|
|
1265
|
+
"data-fetch",
|
|
1266
|
+
"data-transition",
|
|
1267
|
+
"data-transition:"
|
|
1268
|
+
];
|
|
1269
|
+
function isDirectiveAttr(name) {
|
|
1270
|
+
for (const prefix of DIRECTIVE_ATTR_PREFIXES) {
|
|
1271
|
+
if (name === prefix || name.startsWith(prefix)) return true;
|
|
1272
|
+
}
|
|
1273
|
+
return false;
|
|
1274
|
+
}
|
|
1275
|
+
function splitClassTokens(raw) {
|
|
1276
|
+
if (!raw) return [];
|
|
1277
|
+
return raw.trim().split(/\s+/).map((t) => t.trim()).filter(Boolean);
|
|
1278
|
+
}
|
|
1279
|
+
function parseDurationTokenMs(token) {
|
|
1280
|
+
const t = token.trim().toLowerCase();
|
|
1281
|
+
if (t.endsWith("ms")) {
|
|
1282
|
+
const n = Number(t.slice(0, -2));
|
|
1283
|
+
return Number.isFinite(n) && n >= 0 ? n : null;
|
|
1284
|
+
}
|
|
1285
|
+
if (t.endsWith("s")) {
|
|
1286
|
+
const n = Number(t.slice(0, -1));
|
|
1287
|
+
return Number.isFinite(n) && n >= 0 ? n * 1e3 : null;
|
|
1288
|
+
}
|
|
1289
|
+
return null;
|
|
1290
|
+
}
|
|
1291
|
+
function parseClassTokensAndDuration(raw) {
|
|
1292
|
+
const classes = [];
|
|
1293
|
+
let durationMs;
|
|
1294
|
+
for (const token of splitClassTokens(raw)) {
|
|
1295
|
+
const parsed = parseDurationTokenMs(token);
|
|
1296
|
+
if (parsed != null) {
|
|
1297
|
+
durationMs = parsed;
|
|
1298
|
+
} else {
|
|
1299
|
+
classes.push(token);
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
1302
|
+
return { classes, durationMs };
|
|
1303
|
+
}
|
|
1304
|
+
function uniqueTokens(tokens) {
|
|
1305
|
+
return Array.from(new Set(tokens.filter(Boolean)));
|
|
1306
|
+
}
|
|
1307
|
+
function parseCssTimeListMs(raw) {
|
|
1308
|
+
if (!raw) return [];
|
|
1309
|
+
return raw.split(",").map((part) => parseDurationTokenMs(part.trim())).filter((ms) => ms != null);
|
|
1310
|
+
}
|
|
1311
|
+
function maxCombinedTimingsMs(durations, delays) {
|
|
1312
|
+
if (durations.length === 0 && delays.length === 0) return 0;
|
|
1313
|
+
if (durations.length === 0) return Math.max(...delays, 0);
|
|
1314
|
+
if (delays.length === 0) return Math.max(...durations, 0);
|
|
1315
|
+
const len = Math.max(durations.length, delays.length);
|
|
1316
|
+
let max = 0;
|
|
1317
|
+
for (let i = 0; i < len; i++) {
|
|
1318
|
+
const d = durations[i % durations.length] ?? 0;
|
|
1319
|
+
const delay = delays[i % delays.length] ?? 0;
|
|
1320
|
+
if (d + delay > max) max = d + delay;
|
|
1321
|
+
}
|
|
1322
|
+
return max;
|
|
1323
|
+
}
|
|
1324
|
+
function resolveTransitionDurationMs(el, explicitMs) {
|
|
1325
|
+
if (typeof explicitMs === "number") return explicitMs;
|
|
1326
|
+
const cs = window.getComputedStyle(el);
|
|
1327
|
+
const trans = maxCombinedTimingsMs(
|
|
1328
|
+
parseCssTimeListMs(cs.transitionDuration),
|
|
1329
|
+
parseCssTimeListMs(cs.transitionDelay)
|
|
1330
|
+
);
|
|
1331
|
+
const anim = maxCombinedTimingsMs(
|
|
1332
|
+
parseCssTimeListMs(cs.animationDuration),
|
|
1333
|
+
parseCssTimeListMs(cs.animationDelay)
|
|
1334
|
+
);
|
|
1335
|
+
return Math.max(trans, anim);
|
|
1336
|
+
}
|
|
1337
|
+
function getTransitionState(el) {
|
|
1338
|
+
const existing = el[TRANSITION_STATE_SYM];
|
|
1339
|
+
if (existing) return existing;
|
|
1340
|
+
const created = { token: 0, cancel: null };
|
|
1341
|
+
el[TRANSITION_STATE_SYM] = created;
|
|
1342
|
+
return created;
|
|
1343
|
+
}
|
|
1344
|
+
function clearTransitionState(el) {
|
|
1345
|
+
const state = el[TRANSITION_STATE_SYM];
|
|
1346
|
+
if (state?.cancel) {
|
|
1347
|
+
state.cancel();
|
|
1348
|
+
}
|
|
1349
|
+
delete el[TRANSITION_STATE_SYM];
|
|
1350
|
+
}
|
|
1351
|
+
function parseTransitionSpec(el) {
|
|
1352
|
+
const hasTransitionAttr = el.hasAttribute("data-transition") || Array.from(el.attributes).some((a) => a.name.startsWith("data-transition:"));
|
|
1353
|
+
if (!hasTransitionAttr) return null;
|
|
1354
|
+
const base = parseClassTokensAndDuration(el.getAttribute("data-transition")).classes;
|
|
1355
|
+
const enter = parseClassTokensAndDuration(el.getAttribute("data-transition:enter"));
|
|
1356
|
+
const leave = parseClassTokensAndDuration(el.getAttribute("data-transition:leave"));
|
|
1357
|
+
const enterFrom = splitClassTokens(
|
|
1358
|
+
el.getAttribute("data-transition:enter-from") ?? el.getAttribute("data-transition:enter-start")
|
|
1359
|
+
);
|
|
1360
|
+
const enterTo = splitClassTokens(
|
|
1361
|
+
el.getAttribute("data-transition:enter-to") ?? el.getAttribute("data-transition:enter-end")
|
|
1362
|
+
);
|
|
1363
|
+
const leaveFrom = splitClassTokens(
|
|
1364
|
+
el.getAttribute("data-transition:leave-from") ?? el.getAttribute("data-transition:leave-start")
|
|
1365
|
+
);
|
|
1366
|
+
const leaveTo = splitClassTokens(
|
|
1367
|
+
el.getAttribute("data-transition:leave-to") ?? el.getAttribute("data-transition:leave-end")
|
|
1368
|
+
);
|
|
1369
|
+
const durationBoth = parseDurationTokenMs(el.getAttribute("data-transition:duration") ?? "");
|
|
1370
|
+
const enterDuration = parseDurationTokenMs(el.getAttribute("data-transition:duration-enter") ?? "") ?? enter.durationMs ?? durationBoth ?? void 0;
|
|
1371
|
+
const leaveDuration = parseDurationTokenMs(el.getAttribute("data-transition:duration-leave") ?? "") ?? leave.durationMs ?? durationBoth ?? void 0;
|
|
1372
|
+
return {
|
|
1373
|
+
enter: uniqueTokens([...base, ...enter.classes]),
|
|
1374
|
+
enterFrom: uniqueTokens(enterFrom),
|
|
1375
|
+
enterTo: uniqueTokens(enterTo),
|
|
1376
|
+
leave: uniqueTokens([...base, ...leave.classes]),
|
|
1377
|
+
leaveFrom: uniqueTokens(leaveFrom),
|
|
1378
|
+
leaveTo: uniqueTokens(leaveTo),
|
|
1379
|
+
enterDurationMs: enterDuration,
|
|
1380
|
+
leaveDurationMs: leaveDuration
|
|
1381
|
+
};
|
|
1382
|
+
}
|
|
1383
|
+
function removeClasses(el, classes) {
|
|
1384
|
+
for (const cls of classes) {
|
|
1385
|
+
el.classList.remove(cls);
|
|
1386
|
+
}
|
|
1387
|
+
}
|
|
1388
|
+
function addClasses(el, classes) {
|
|
1389
|
+
for (const cls of classes) {
|
|
1390
|
+
el.classList.add(cls);
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1393
|
+
function runTransitionPhase(el, phaseClasses, onDone) {
|
|
1394
|
+
const cleanupClasses = uniqueTokens([
|
|
1395
|
+
...phaseClasses.base,
|
|
1396
|
+
...phaseClasses.from,
|
|
1397
|
+
...phaseClasses.to
|
|
1398
|
+
]);
|
|
1399
|
+
let done = false;
|
|
1400
|
+
let timeoutId = null;
|
|
1401
|
+
let raf1 = null;
|
|
1402
|
+
let raf2 = null;
|
|
1403
|
+
const finish = () => {
|
|
1404
|
+
if (done) return;
|
|
1405
|
+
done = true;
|
|
1406
|
+
if (timeoutId != null) window.clearTimeout(timeoutId);
|
|
1407
|
+
if (raf1 != null) cancelAnimationFrame(raf1);
|
|
1408
|
+
if (raf2 != null) cancelAnimationFrame(raf2);
|
|
1409
|
+
removeClasses(el, cleanupClasses);
|
|
1410
|
+
onDone();
|
|
1411
|
+
};
|
|
1412
|
+
addClasses(el, phaseClasses.base);
|
|
1413
|
+
addClasses(el, phaseClasses.from);
|
|
1414
|
+
removeClasses(el, phaseClasses.to);
|
|
1415
|
+
raf1 = requestAnimationFrame(() => {
|
|
1416
|
+
raf2 = requestAnimationFrame(() => {
|
|
1417
|
+
if (done) return;
|
|
1418
|
+
removeClasses(el, phaseClasses.from);
|
|
1419
|
+
addClasses(el, phaseClasses.to);
|
|
1420
|
+
const ms = resolveTransitionDurationMs(el, phaseClasses.durationMs);
|
|
1421
|
+
if (ms <= 0) {
|
|
1422
|
+
finish();
|
|
1423
|
+
return;
|
|
1424
|
+
}
|
|
1425
|
+
timeoutId = window.setTimeout(finish, ms + 25);
|
|
1426
|
+
});
|
|
1427
|
+
});
|
|
1428
|
+
return finish;
|
|
1429
|
+
}
|
|
1430
|
+
function transitionInsert(el, parent, ref, spec) {
|
|
1431
|
+
parent.insertBefore(el, ref);
|
|
1432
|
+
if (!spec) return;
|
|
1433
|
+
const state = getTransitionState(el);
|
|
1434
|
+
state.token += 1;
|
|
1435
|
+
const token = state.token;
|
|
1436
|
+
if (state.cancel) state.cancel();
|
|
1437
|
+
state.cancel = runTransitionPhase(
|
|
1438
|
+
el,
|
|
1439
|
+
{
|
|
1440
|
+
base: spec.enter,
|
|
1441
|
+
from: spec.enterFrom,
|
|
1442
|
+
to: spec.enterTo,
|
|
1443
|
+
durationMs: spec.enterDurationMs
|
|
1444
|
+
},
|
|
1445
|
+
() => {
|
|
1446
|
+
const current = getTransitionState(el);
|
|
1447
|
+
if (current.token === token) current.cancel = null;
|
|
1448
|
+
}
|
|
1449
|
+
);
|
|
1450
|
+
}
|
|
1451
|
+
function transitionRemove(el, spec, onDone) {
|
|
1452
|
+
if (el.hasAttribute("data-forma-leaving")) {
|
|
1453
|
+
onDone();
|
|
1454
|
+
return;
|
|
1455
|
+
}
|
|
1456
|
+
if (!spec) {
|
|
1457
|
+
onDone();
|
|
1458
|
+
return;
|
|
1459
|
+
}
|
|
1460
|
+
el.setAttribute("data-forma-leaving", "");
|
|
1461
|
+
const state = getTransitionState(el);
|
|
1462
|
+
state.token += 1;
|
|
1463
|
+
const token = state.token;
|
|
1464
|
+
if (state.cancel) state.cancel();
|
|
1465
|
+
state.cancel = runTransitionPhase(
|
|
1466
|
+
el,
|
|
1467
|
+
{
|
|
1468
|
+
base: spec.leave,
|
|
1469
|
+
from: spec.leaveFrom,
|
|
1470
|
+
to: spec.leaveTo,
|
|
1471
|
+
durationMs: spec.leaveDurationMs
|
|
1472
|
+
},
|
|
1473
|
+
() => {
|
|
1474
|
+
const current = getTransitionState(el);
|
|
1475
|
+
if (current.token === token) current.cancel = null;
|
|
1476
|
+
el.removeAttribute("data-forma-leaving");
|
|
1477
|
+
onDone();
|
|
1478
|
+
}
|
|
1479
|
+
);
|
|
1480
|
+
}
|
|
1481
|
+
function applyShowVisibility(el, visible, transition, initial) {
|
|
1482
|
+
if (!transition || initial) {
|
|
1483
|
+
el.style.display = visible ? "" : "none";
|
|
1484
|
+
if (transition) {
|
|
1485
|
+
removeClasses(el, uniqueTokens([
|
|
1486
|
+
...transition.enter,
|
|
1487
|
+
...transition.enterFrom,
|
|
1488
|
+
...transition.enterTo,
|
|
1489
|
+
...transition.leave,
|
|
1490
|
+
...transition.leaveFrom,
|
|
1491
|
+
...transition.leaveTo
|
|
1492
|
+
]));
|
|
1493
|
+
}
|
|
1494
|
+
return;
|
|
1495
|
+
}
|
|
1496
|
+
const state = getTransitionState(el);
|
|
1497
|
+
state.token += 1;
|
|
1498
|
+
const token = state.token;
|
|
1499
|
+
if (state.cancel) state.cancel();
|
|
1500
|
+
state.cancel = null;
|
|
1501
|
+
if (visible) {
|
|
1502
|
+
el.style.display = "";
|
|
1503
|
+
state.cancel = runTransitionPhase(
|
|
1504
|
+
el,
|
|
1505
|
+
{
|
|
1506
|
+
base: transition.enter,
|
|
1507
|
+
from: transition.enterFrom,
|
|
1508
|
+
to: transition.enterTo,
|
|
1509
|
+
durationMs: transition.enterDurationMs
|
|
1510
|
+
},
|
|
1511
|
+
() => {
|
|
1512
|
+
const current = getTransitionState(el);
|
|
1513
|
+
if (current.token === token) current.cancel = null;
|
|
1514
|
+
}
|
|
1515
|
+
);
|
|
1516
|
+
return;
|
|
1517
|
+
}
|
|
1518
|
+
state.cancel = runTransitionPhase(
|
|
1519
|
+
el,
|
|
1520
|
+
{
|
|
1521
|
+
base: transition.leave,
|
|
1522
|
+
from: transition.leaveFrom,
|
|
1523
|
+
to: transition.leaveTo,
|
|
1524
|
+
durationMs: transition.leaveDurationMs
|
|
1525
|
+
},
|
|
1526
|
+
() => {
|
|
1527
|
+
const current = getTransitionState(el);
|
|
1528
|
+
if (current.token !== token) return;
|
|
1529
|
+
el.style.display = "none";
|
|
1530
|
+
current.cancel = null;
|
|
1531
|
+
}
|
|
1532
|
+
);
|
|
1533
|
+
}
|
|
1534
|
+
function cloneAttributeTemplates(el, item) {
|
|
1535
|
+
const all = [el, ...Array.from(el.querySelectorAll("*"))];
|
|
1536
|
+
for (const node of all) {
|
|
1537
|
+
const entries = [];
|
|
1538
|
+
for (const attr of Array.from(node.attributes)) {
|
|
1539
|
+
if (isDirectiveAttr(attr.name)) continue;
|
|
1540
|
+
if (attr.value.includes("{item")) {
|
|
1541
|
+
const compiled = compileTemplate(attr.value);
|
|
1542
|
+
entries.push({ attr: attr.name, compiled });
|
|
1543
|
+
node.setAttribute(attr.name, evaluateCompiledTemplate(compiled, item));
|
|
1544
|
+
}
|
|
1545
|
+
}
|
|
1546
|
+
if (entries.length > 0) {
|
|
1547
|
+
templateAttrs.set(node, entries);
|
|
1548
|
+
}
|
|
1549
|
+
}
|
|
1550
|
+
}
|
|
1551
|
+
function parseExpression(expr, scope) {
|
|
1552
|
+
const cachedFactory = expressionCache.get(expr);
|
|
1553
|
+
if (cachedFactory) return cachedFactory(scope);
|
|
1554
|
+
const result = parseExpressionUncached(expr, scope);
|
|
1555
|
+
if (result !== null) {
|
|
1556
|
+
if (expr === "true" || expr === "false" || expr === "null" || expr === "undefined") {
|
|
1557
|
+
const val = expr === "true" ? true : expr === "false" ? false : expr === "null" ? null : void 0;
|
|
1558
|
+
cacheExpression(expr, () => () => val);
|
|
1559
|
+
} else if (RE_IDENTIFIER.test(expr)) {
|
|
1560
|
+
cacheExpression(expr, (s) => () => s.getters[expr]?.());
|
|
1561
|
+
} else if (RE_STRING_SINGLE.test(expr) || RE_STRING_DOUBLE.test(expr)) {
|
|
1562
|
+
const val = expr.slice(1, -1);
|
|
1563
|
+
cacheExpression(expr, () => () => val);
|
|
1564
|
+
} else if (RE_NUMBER.test(expr)) {
|
|
1565
|
+
const val = Number(expr);
|
|
1566
|
+
cacheExpression(expr, () => () => val);
|
|
1567
|
+
} else {
|
|
1568
|
+
const dotMatch = expr.match(RE_DOT_ACCESS);
|
|
1569
|
+
if (dotMatch) {
|
|
1570
|
+
const p1 = dotMatch[1], p2 = dotMatch[2];
|
|
1571
|
+
cacheExpression(expr, (s) => () => {
|
|
1572
|
+
const obj = s.getters[p1]?.();
|
|
1573
|
+
return obj?.[p2];
|
|
1574
|
+
});
|
|
1575
|
+
}
|
|
1576
|
+
}
|
|
1577
|
+
}
|
|
1578
|
+
return result;
|
|
1579
|
+
}
|
|
1580
|
+
function parseExpressionUncached(expr, scope) {
|
|
1581
|
+
expr = expr.trim();
|
|
1582
|
+
const unwrapped = unwrapOuterParens(expr);
|
|
1583
|
+
if (unwrapped !== expr) {
|
|
1584
|
+
return parseExpression(unwrapped, scope);
|
|
1585
|
+
}
|
|
1586
|
+
if (RE_STRING_SINGLE.test(expr) || RE_STRING_DOUBLE.test(expr)) {
|
|
1587
|
+
const val = expr.slice(1, -1);
|
|
1588
|
+
return () => val;
|
|
1589
|
+
}
|
|
1590
|
+
if (RE_NUMBER.test(expr)) {
|
|
1591
|
+
const val = Number(expr);
|
|
1592
|
+
return () => val;
|
|
1593
|
+
}
|
|
1594
|
+
if (expr === "true") return () => true;
|
|
1595
|
+
if (expr === "false") return () => false;
|
|
1596
|
+
if (expr === "null") return () => null;
|
|
1597
|
+
if (expr === "undefined") return () => void 0;
|
|
1598
|
+
if (RE_IDENTIFIER.test(expr)) {
|
|
1599
|
+
return () => scope.getters[expr]?.();
|
|
1600
|
+
}
|
|
1601
|
+
const dotMatch = expr.match(RE_DOT_ACCESS);
|
|
1602
|
+
if (dotMatch) {
|
|
1603
|
+
const p1 = dotMatch[1], p2 = dotMatch[2];
|
|
1604
|
+
return () => {
|
|
1605
|
+
const obj = scope.getters[p1]?.();
|
|
1606
|
+
return obj?.[p2];
|
|
1607
|
+
};
|
|
1608
|
+
}
|
|
1609
|
+
const deepDotMatch = expr.match(RE_DEEP_DOT);
|
|
1610
|
+
if (deepDotMatch) {
|
|
1611
|
+
const p1 = deepDotMatch[1], p2 = deepDotMatch[2], p3 = deepDotMatch[3], p4 = deepDotMatch[4];
|
|
1612
|
+
return () => {
|
|
1613
|
+
let val = scope.getters[p1]?.();
|
|
1614
|
+
val = val?.[p2];
|
|
1615
|
+
val = val?.[p3];
|
|
1616
|
+
if (p4) val = val?.[p4];
|
|
1617
|
+
return val;
|
|
1618
|
+
};
|
|
1619
|
+
}
|
|
1620
|
+
const groupedCallMatch = expr.match(RE_GROUP_METHOD_CALL);
|
|
1621
|
+
if (groupedCallMatch) {
|
|
1622
|
+
const baseRaw = groupedCallMatch[1].trim();
|
|
1623
|
+
const methodName = groupedCallMatch[2];
|
|
1624
|
+
const argsRaw = groupedCallMatch[3].trim();
|
|
1625
|
+
if (UNSAFE_METHOD_NAMES.has(methodName)) return () => void 0;
|
|
1626
|
+
const baseExpr = parseExpression(baseRaw, scope);
|
|
1627
|
+
if (!baseExpr) return null;
|
|
1628
|
+
const argFns = [];
|
|
1629
|
+
for (const arg of splitCallArgs(argsRaw)) {
|
|
1630
|
+
const parsed = parseExpression(arg, scope);
|
|
1631
|
+
if (!parsed) return null;
|
|
1632
|
+
argFns.push(parsed);
|
|
1633
|
+
}
|
|
1634
|
+
return () => {
|
|
1635
|
+
const base = baseExpr();
|
|
1636
|
+
const method = base?.[methodName];
|
|
1637
|
+
if (typeof method !== "function") return void 0;
|
|
1638
|
+
const args = argFns.map((fn) => fn());
|
|
1639
|
+
return method.apply(base, args);
|
|
1640
|
+
};
|
|
1641
|
+
}
|
|
1642
|
+
const callMatch = expr.match(RE_METHOD_CALL);
|
|
1643
|
+
if (callMatch) {
|
|
1644
|
+
const baseName = callMatch[1];
|
|
1645
|
+
const methodName = callMatch[2];
|
|
1646
|
+
const argsRaw = callMatch[3].trim();
|
|
1647
|
+
if (UNSAFE_METHOD_NAMES.has(methodName)) return () => void 0;
|
|
1648
|
+
const baseExpr = baseName === "Math" ? (() => Math) : parseExpression(baseName, scope);
|
|
1649
|
+
if (!baseExpr) return null;
|
|
1650
|
+
const argFns = [];
|
|
1651
|
+
for (const arg of splitCallArgs(argsRaw)) {
|
|
1652
|
+
const parsed = parseExpression(arg, scope);
|
|
1653
|
+
if (!parsed) return null;
|
|
1654
|
+
argFns.push(parsed);
|
|
1655
|
+
}
|
|
1656
|
+
return () => {
|
|
1657
|
+
const base = baseExpr();
|
|
1658
|
+
const method = base?.[methodName];
|
|
1659
|
+
if (typeof method !== "function") return void 0;
|
|
1660
|
+
const args = argFns.map((fn) => fn());
|
|
1661
|
+
return method.apply(base, args);
|
|
1662
|
+
};
|
|
1663
|
+
}
|
|
1664
|
+
if (expr.startsWith("!")) {
|
|
1665
|
+
const inner = parseExpression(expr.slice(1).trim(), scope);
|
|
1666
|
+
if (inner) return () => !inner();
|
|
1667
|
+
}
|
|
1668
|
+
const bracketMatch = expr.match(RE_BRACKET);
|
|
1669
|
+
if (bracketMatch) {
|
|
1670
|
+
const objExpr = parseExpression(bracketMatch[1], scope);
|
|
1671
|
+
let key;
|
|
1672
|
+
const rawKey = bracketMatch[2];
|
|
1673
|
+
if (RE_DIGIT_ONLY.test(rawKey)) {
|
|
1674
|
+
key = Number(rawKey);
|
|
1675
|
+
} else {
|
|
1676
|
+
key = rawKey.slice(1, -1);
|
|
1677
|
+
}
|
|
1678
|
+
if (objExpr) {
|
|
1679
|
+
return () => objExpr()?.[key];
|
|
1680
|
+
}
|
|
1681
|
+
}
|
|
1682
|
+
const ternaryMatch = expr.match(RE_TERNARY);
|
|
1683
|
+
if (ternaryMatch) {
|
|
1684
|
+
const cond = parseExpression(ternaryMatch[1].trim(), scope);
|
|
1685
|
+
const then = parseExpression(ternaryMatch[2].trim(), scope);
|
|
1686
|
+
const els = parseExpression(ternaryMatch[3].trim(), scope);
|
|
1687
|
+
if (cond && then && els) {
|
|
1688
|
+
return () => cond() ? then() : els();
|
|
1689
|
+
}
|
|
1690
|
+
}
|
|
1691
|
+
const nullishMatch = expr.match(RE_NULLISH);
|
|
1692
|
+
if (nullishMatch) {
|
|
1693
|
+
const left = parseExpression(nullishMatch[1].trim(), scope);
|
|
1694
|
+
const right = parseExpression(nullishMatch[2].trim(), scope);
|
|
1695
|
+
if (left && right) {
|
|
1696
|
+
return () => left() ?? right();
|
|
1697
|
+
}
|
|
1698
|
+
}
|
|
1699
|
+
const andMatch = expr.match(RE_AND);
|
|
1700
|
+
if (andMatch) {
|
|
1701
|
+
const left = parseExpression(andMatch[1].trim(), scope);
|
|
1702
|
+
const right = parseExpression(andMatch[2].trim(), scope);
|
|
1703
|
+
if (left && right) {
|
|
1704
|
+
return () => left() && right();
|
|
1705
|
+
}
|
|
1706
|
+
}
|
|
1707
|
+
const orMatch = expr.match(RE_OR);
|
|
1708
|
+
if (orMatch) {
|
|
1709
|
+
const left = parseExpression(orMatch[1].trim(), scope);
|
|
1710
|
+
const right = parseExpression(orMatch[2].trim(), scope);
|
|
1711
|
+
if (left && right) {
|
|
1712
|
+
return () => left() || right();
|
|
1713
|
+
}
|
|
1714
|
+
}
|
|
1715
|
+
const compMatch = expr.match(RE_COMPARISON);
|
|
1716
|
+
if (compMatch) {
|
|
1717
|
+
const left = parseExpression(compMatch[1].trim(), scope);
|
|
1718
|
+
const right = parseExpression(compMatch[3].trim(), scope);
|
|
1719
|
+
if (left && right) {
|
|
1720
|
+
const op = compMatch[2];
|
|
1721
|
+
return () => {
|
|
1722
|
+
const l = left(), r = right();
|
|
1723
|
+
switch (op) {
|
|
1724
|
+
case "===":
|
|
1725
|
+
return l === r;
|
|
1726
|
+
case "!==":
|
|
1727
|
+
return l !== r;
|
|
1728
|
+
case "==":
|
|
1729
|
+
return l == r;
|
|
1730
|
+
case "!=":
|
|
1731
|
+
return l != r;
|
|
1732
|
+
case ">":
|
|
1733
|
+
return l > r;
|
|
1734
|
+
case "<":
|
|
1735
|
+
return l < r;
|
|
1736
|
+
case ">=":
|
|
1737
|
+
return l >= r;
|
|
1738
|
+
case "<=":
|
|
1739
|
+
return l <= r;
|
|
1740
|
+
}
|
|
1741
|
+
};
|
|
1742
|
+
}
|
|
1743
|
+
}
|
|
1744
|
+
const mulMatch = expr.match(RE_MUL);
|
|
1745
|
+
if (mulMatch) {
|
|
1746
|
+
const left = parseExpression(mulMatch[1].trim(), scope);
|
|
1747
|
+
const right = parseExpression(mulMatch[3].trim(), scope);
|
|
1748
|
+
if (left && right) {
|
|
1749
|
+
const op = mulMatch[2];
|
|
1750
|
+
return () => {
|
|
1751
|
+
const l = left(), r = right();
|
|
1752
|
+
switch (op) {
|
|
1753
|
+
case "*":
|
|
1754
|
+
return l * r;
|
|
1755
|
+
case "/":
|
|
1756
|
+
return l / r;
|
|
1757
|
+
case "%":
|
|
1758
|
+
return l % r;
|
|
1759
|
+
}
|
|
1760
|
+
};
|
|
1761
|
+
}
|
|
1762
|
+
}
|
|
1763
|
+
const addMatch = expr.match(RE_ADD);
|
|
1764
|
+
if (addMatch) {
|
|
1765
|
+
const left = parseExpression(addMatch[1].trim(), scope);
|
|
1766
|
+
const right = parseExpression(addMatch[3].trim(), scope);
|
|
1767
|
+
if (left && right) {
|
|
1768
|
+
const op = addMatch[2];
|
|
1769
|
+
return () => {
|
|
1770
|
+
const l = left(), r = right();
|
|
1771
|
+
if (op === "+") return l + r;
|
|
1772
|
+
return l - r;
|
|
1773
|
+
};
|
|
1774
|
+
}
|
|
1775
|
+
}
|
|
1776
|
+
const tmplMatch = expr.match(RE_TEMPLATE_LIT);
|
|
1777
|
+
if (tmplMatch) {
|
|
1778
|
+
const raw = tmplMatch[1];
|
|
1779
|
+
const staticParts = [];
|
|
1780
|
+
const dynamicFns = [];
|
|
1781
|
+
let lastIndex = 0;
|
|
1782
|
+
const re = new RegExp(RE_TEMPLATE_INTERP.source, "g");
|
|
1783
|
+
let m;
|
|
1784
|
+
while ((m = re.exec(raw)) !== null) {
|
|
1785
|
+
staticParts.push(raw.slice(lastIndex, m.index));
|
|
1786
|
+
const inner = parseExpression(m[1].trim(), scope);
|
|
1787
|
+
if (!inner) return null;
|
|
1788
|
+
dynamicFns.push(inner);
|
|
1789
|
+
lastIndex = re.lastIndex;
|
|
1790
|
+
}
|
|
1791
|
+
staticParts.push(raw.slice(lastIndex));
|
|
1792
|
+
return () => {
|
|
1793
|
+
let result = staticParts[0];
|
|
1794
|
+
for (let i = 0; i < dynamicFns.length; i++) {
|
|
1795
|
+
result += String(dynamicFns[i]() ?? "");
|
|
1796
|
+
result += staticParts[i + 1] ?? "";
|
|
1797
|
+
}
|
|
1798
|
+
return result;
|
|
1799
|
+
};
|
|
1800
|
+
}
|
|
1801
|
+
return null;
|
|
1802
|
+
}
|
|
1803
|
+
function getScopeCache(cache, scope) {
|
|
1804
|
+
let scoped = cache.get(scope);
|
|
1805
|
+
if (!scoped) {
|
|
1806
|
+
scoped = /* @__PURE__ */ new Map();
|
|
1807
|
+
cache.set(scope, scoped);
|
|
1808
|
+
}
|
|
1809
|
+
return scoped;
|
|
1810
|
+
}
|
|
1811
|
+
function buildEvaluator(expr, scope) {
|
|
1812
|
+
const cleaned = expr.replace(RE_STRIP_BRACES, "").trim();
|
|
1813
|
+
const cache = getScopeCache(scopeExpressionCache, scope);
|
|
1814
|
+
const cached = cache.get(cleaned);
|
|
1815
|
+
if (cached) return cached;
|
|
1816
|
+
const cspFn = parseExpression(cleaned, scope);
|
|
1817
|
+
if (cspFn) {
|
|
1818
|
+
cache.set(cleaned, cspFn);
|
|
1819
|
+
return cspFn;
|
|
1820
|
+
}
|
|
1821
|
+
if (!_allowUnsafeEval) {
|
|
1822
|
+
dbg("buildEvaluator: blocked unsafe eval fallback for expression:", cleaned);
|
|
1823
|
+
reportDiagnostic("expression-unsupported", cleaned, "Unsupported expression in CSP-safe mode");
|
|
1824
|
+
const blocked = () => void 0;
|
|
1825
|
+
cache.set(cleaned, blocked);
|
|
1826
|
+
return blocked;
|
|
1827
|
+
}
|
|
1828
|
+
try {
|
|
1829
|
+
const fn = new Function("__scope", `with(__scope) { return (${cleaned}); }`);
|
|
1830
|
+
const proxy = new Proxy(/* @__PURE__ */ Object.create(null), {
|
|
1831
|
+
has(_, key) {
|
|
1832
|
+
return key in scope.getters;
|
|
1833
|
+
},
|
|
1834
|
+
get(_, key) {
|
|
1835
|
+
const g = scope.getters[key];
|
|
1836
|
+
return g ? g() : void 0;
|
|
1837
|
+
}
|
|
1838
|
+
});
|
|
1839
|
+
const unsafe = () => fn(proxy);
|
|
1840
|
+
cache.set(cleaned, unsafe);
|
|
1841
|
+
return unsafe;
|
|
1842
|
+
} catch {
|
|
1843
|
+
reportDiagnostic("expression-unsupported", cleaned, "Expression failed to compile");
|
|
1844
|
+
const failed = () => void 0;
|
|
1845
|
+
cache.set(cleaned, failed);
|
|
1846
|
+
return failed;
|
|
1847
|
+
}
|
|
1848
|
+
}
|
|
1849
|
+
function parseHandler(expr, scope) {
|
|
1850
|
+
const normalized = expr.trim().replace(/;+$/g, "").trim();
|
|
1851
|
+
if (!normalized) return null;
|
|
1852
|
+
const ifHandler = parseIfHandler(normalized, scope);
|
|
1853
|
+
if (ifHandler) return ifHandler;
|
|
1854
|
+
const stmts = splitTopLevelStatements(normalized);
|
|
1855
|
+
if (stmts.length > 1) {
|
|
1856
|
+
const handlers = stmts.map((s) => parseHandler(s, scope));
|
|
1857
|
+
if (handlers.every((h) => h !== null)) {
|
|
1858
|
+
return (e) => {
|
|
1859
|
+
batch(() => {
|
|
1860
|
+
for (const h of handlers) h(e);
|
|
1861
|
+
});
|
|
1862
|
+
};
|
|
1863
|
+
}
|
|
1864
|
+
return null;
|
|
1865
|
+
}
|
|
1866
|
+
const single = stmts[0] ?? normalized;
|
|
1867
|
+
const incrMatch = single.match(RE_POST_INCR);
|
|
1868
|
+
if (incrMatch) {
|
|
1869
|
+
const name = incrMatch[1];
|
|
1870
|
+
const op = incrMatch[2];
|
|
1871
|
+
return () => {
|
|
1872
|
+
batch(() => {
|
|
1873
|
+
const val = scope.getters[name]?.() ?? 0;
|
|
1874
|
+
scope.setters[name]?.(op === "++" ? val + 1 : val - 1);
|
|
1875
|
+
});
|
|
1876
|
+
};
|
|
1877
|
+
}
|
|
1878
|
+
const preIncrMatch = single.match(RE_PRE_INCR);
|
|
1879
|
+
if (preIncrMatch) {
|
|
1880
|
+
const op = preIncrMatch[1];
|
|
1881
|
+
const name = preIncrMatch[2];
|
|
1882
|
+
return () => {
|
|
1883
|
+
batch(() => {
|
|
1884
|
+
const val = scope.getters[name]?.() ?? 0;
|
|
1885
|
+
scope.setters[name]?.(op === "++" ? val + 1 : val - 1);
|
|
1886
|
+
});
|
|
1887
|
+
};
|
|
1888
|
+
}
|
|
1889
|
+
const toggleMatch = single.match(RE_TOGGLE);
|
|
1890
|
+
if (toggleMatch && toggleMatch[1] === toggleMatch[2]) {
|
|
1891
|
+
const name = toggleMatch[1];
|
|
1892
|
+
return () => {
|
|
1893
|
+
batch(() => {
|
|
1894
|
+
scope.setters[name]?.(!scope.getters[name]?.());
|
|
1895
|
+
});
|
|
1896
|
+
};
|
|
1897
|
+
}
|
|
1898
|
+
const assignMatch = single.match(RE_ASSIGN);
|
|
1899
|
+
if (assignMatch) {
|
|
1900
|
+
const name = assignMatch[1];
|
|
1901
|
+
const valExpr = parseExpression(assignMatch[2].trim(), scope);
|
|
1902
|
+
if (valExpr) {
|
|
1903
|
+
if (_debug) dbg(`parseHandler: assignment "${name} = ..." \u2014 setter exists:`, !!scope.setters[name], ", getter exists:", !!scope.getters[name]);
|
|
1904
|
+
return () => {
|
|
1905
|
+
batch(() => {
|
|
1906
|
+
const val = valExpr();
|
|
1907
|
+
if (_debug) dbg(`SETTER: ${name} = ${val} (was: ${scope.getters[name]?.()})`);
|
|
1908
|
+
scope.setters[name]?.(val);
|
|
1909
|
+
});
|
|
1910
|
+
};
|
|
1911
|
+
}
|
|
1912
|
+
}
|
|
1913
|
+
const compoundMatch = single.match(RE_COMPOUND);
|
|
1914
|
+
if (compoundMatch) {
|
|
1915
|
+
const name = compoundMatch[1];
|
|
1916
|
+
const op = compoundMatch[2];
|
|
1917
|
+
const valExpr = parseExpression(compoundMatch[3].trim(), scope);
|
|
1918
|
+
if (valExpr) {
|
|
1919
|
+
return () => {
|
|
1920
|
+
batch(() => {
|
|
1921
|
+
const current = scope.getters[name]?.() ?? 0;
|
|
1922
|
+
const val = valExpr();
|
|
1923
|
+
switch (op) {
|
|
1924
|
+
case "+=":
|
|
1925
|
+
scope.setters[name]?.(current + val);
|
|
1926
|
+
break;
|
|
1927
|
+
case "-=":
|
|
1928
|
+
scope.setters[name]?.(current - val);
|
|
1929
|
+
break;
|
|
1930
|
+
case "*=":
|
|
1931
|
+
scope.setters[name]?.(current * val);
|
|
1932
|
+
break;
|
|
1933
|
+
case "/=":
|
|
1934
|
+
scope.setters[name]?.(current / val);
|
|
1935
|
+
break;
|
|
1936
|
+
}
|
|
1937
|
+
});
|
|
1938
|
+
};
|
|
1939
|
+
}
|
|
1940
|
+
}
|
|
1941
|
+
const refetchMatch = single.match(RE_REFETCH_CALL);
|
|
1942
|
+
if (refetchMatch) {
|
|
1943
|
+
const fetchId = refetchMatch[1];
|
|
1944
|
+
return () => $refetch(fetchId);
|
|
1945
|
+
}
|
|
1946
|
+
return null;
|
|
1947
|
+
}
|
|
1948
|
+
function buildHandler(expr, scope) {
|
|
1949
|
+
const cleaned = expr.replace(RE_STRIP_BRACES, "").trim();
|
|
1950
|
+
const cache = getScopeCache(scopeHandlerCache, scope);
|
|
1951
|
+
const cached = cache.get(cleaned);
|
|
1952
|
+
if (cached) return cached;
|
|
1953
|
+
const cspFn = parseHandler(cleaned, scope);
|
|
1954
|
+
if (cspFn) {
|
|
1955
|
+
const result = { handler: cspFn, supported: true };
|
|
1956
|
+
cache.set(cleaned, result);
|
|
1957
|
+
return result;
|
|
1958
|
+
}
|
|
1959
|
+
if (!_allowUnsafeEval) {
|
|
1960
|
+
dbg("buildHandler: blocked unsafe eval fallback for expression:", cleaned);
|
|
1961
|
+
reportDiagnostic("handler-unsupported", cleaned, "Unsupported handler in CSP-safe mode");
|
|
1962
|
+
const result = {
|
|
1963
|
+
handler: () => {
|
|
1964
|
+
},
|
|
1965
|
+
supported: false
|
|
1966
|
+
};
|
|
1967
|
+
cache.set(cleaned, result);
|
|
1968
|
+
return result;
|
|
1969
|
+
}
|
|
1970
|
+
try {
|
|
1971
|
+
const fn = new Function("__scope", "$event", "event", `with(__scope) { ${cleaned} }`);
|
|
1972
|
+
const proxy = new Proxy(/* @__PURE__ */ Object.create(null), {
|
|
1973
|
+
has(_, key) {
|
|
1974
|
+
if (key === "$event" || key === "event") return false;
|
|
1975
|
+
return key in scope.getters || key in scope.setters;
|
|
1976
|
+
},
|
|
1977
|
+
get(_, key) {
|
|
1978
|
+
const g = scope.getters[key];
|
|
1979
|
+
return g ? g() : void 0;
|
|
1980
|
+
},
|
|
1981
|
+
set(_, key, value2) {
|
|
1982
|
+
const s = scope.setters[key];
|
|
1983
|
+
if (s) s(value2);
|
|
1984
|
+
return true;
|
|
1985
|
+
}
|
|
1986
|
+
});
|
|
1987
|
+
const unsafeHandler = (e) => {
|
|
1988
|
+
batch(() => fn(proxy, e, e));
|
|
1989
|
+
};
|
|
1990
|
+
const result = {
|
|
1991
|
+
handler: unsafeHandler,
|
|
1992
|
+
supported: true
|
|
1993
|
+
};
|
|
1994
|
+
cache.set(cleaned, result);
|
|
1995
|
+
return result;
|
|
1996
|
+
} catch {
|
|
1997
|
+
reportDiagnostic("handler-unsupported", cleaned, "Handler failed to compile");
|
|
1998
|
+
const result = {
|
|
1999
|
+
handler: () => {
|
|
2000
|
+
},
|
|
2001
|
+
supported: false
|
|
2002
|
+
};
|
|
2003
|
+
cache.set(cleaned, result);
|
|
2004
|
+
return result;
|
|
2005
|
+
}
|
|
2006
|
+
}
|
|
2007
|
+
function parseState(raw) {
|
|
2008
|
+
try {
|
|
2009
|
+
return JSON.parse(raw);
|
|
2010
|
+
} catch {
|
|
2011
|
+
try {
|
|
2012
|
+
return JSON.parse(raw.replace(RE_UNQUOTED_KEYS, '"$1":'));
|
|
2013
|
+
} catch {
|
|
2014
|
+
return {};
|
|
2015
|
+
}
|
|
2016
|
+
}
|
|
2017
|
+
}
|
|
2018
|
+
function initScope(stateEl) {
|
|
2019
|
+
const raw = stateEl.getAttribute("data-forma-state") ?? "{}";
|
|
2020
|
+
const state = parseState(raw);
|
|
2021
|
+
const keys = Object.keys(state);
|
|
2022
|
+
if (_debug) {
|
|
2023
|
+
dbg("initScope: parsed", keys.length, "keys:", keys.join(", "));
|
|
2024
|
+
if (keys.length === 0) {
|
|
2025
|
+
dbg("initScope: WARNING \u2014 empty state! Raw attribute:", raw.slice(0, 200));
|
|
2026
|
+
}
|
|
2027
|
+
}
|
|
2028
|
+
const getters = {};
|
|
2029
|
+
const setters = {};
|
|
2030
|
+
for (const [key, initial] of Object.entries(state)) {
|
|
2031
|
+
const [get, set] = createValueSignal(initial);
|
|
2032
|
+
getters[key] = get;
|
|
2033
|
+
setters[key] = set;
|
|
2034
|
+
}
|
|
2035
|
+
getters["$refetch"] = () => $refetch;
|
|
2036
|
+
return { getters, setters };
|
|
2037
|
+
}
|
|
2038
|
+
function bindElement(el, scope, disposers) {
|
|
2039
|
+
const known = getDirectives(el);
|
|
2040
|
+
const computedAttr = !known || known.has("data-computed") ? el.getAttribute("data-computed") : null;
|
|
2041
|
+
if (computedAttr) {
|
|
2042
|
+
const parts = computedAttr.split(/;\s*(?=\w+\s*=[^=])/);
|
|
2043
|
+
for (const part of parts) {
|
|
2044
|
+
const trimmed = part.trim();
|
|
2045
|
+
if (!trimmed) continue;
|
|
2046
|
+
const match = trimmed.match(RE_COMPUTED);
|
|
2047
|
+
if (match) {
|
|
2048
|
+
const name = match[1];
|
|
2049
|
+
const expr = match[2];
|
|
2050
|
+
const prevGetter = scope.getters[name];
|
|
2051
|
+
delete scope.getters[name];
|
|
2052
|
+
const evaluate = buildEvaluator(`{${expr}}`, scope);
|
|
2053
|
+
const getter = createComputed(evaluate);
|
|
2054
|
+
scope.getters[name] = getter;
|
|
2055
|
+
if (!prevGetter) {
|
|
2056
|
+
delete scope.setters[name];
|
|
2057
|
+
}
|
|
2058
|
+
}
|
|
2059
|
+
}
|
|
2060
|
+
}
|
|
2061
|
+
const textExpr = !known || known.has("data-text") ? el.getAttribute("data-text") : null;
|
|
2062
|
+
if (textExpr) {
|
|
2063
|
+
const evaluate = buildEvaluator(textExpr, scope);
|
|
2064
|
+
const dispose = internalEffect(() => {
|
|
2065
|
+
setElementTextFast(el, toTextValue(evaluate()));
|
|
2066
|
+
});
|
|
2067
|
+
disposers.push(dispose);
|
|
2068
|
+
}
|
|
2069
|
+
const showExpr = !known || known.has("data-show") ? el.getAttribute("data-show") : null;
|
|
2070
|
+
if (showExpr) {
|
|
2071
|
+
const evaluate = buildEvaluator(showExpr, scope);
|
|
2072
|
+
const transition = parseTransitionSpec(el);
|
|
2073
|
+
if (_debug) {
|
|
2074
|
+
const tag = el.tagName.toLowerCase();
|
|
2075
|
+
const cls = el.className ? `.${String(el.className).split(" ")[0]}` : "";
|
|
2076
|
+
dbg(`bindElement: data-show="${showExpr}" on <${tag}${cls}>`);
|
|
2077
|
+
}
|
|
2078
|
+
let initialized = false;
|
|
2079
|
+
const dispose = internalEffect(() => {
|
|
2080
|
+
const visible = !!evaluate();
|
|
2081
|
+
if (_debug) dbg(`data-show effect: "${showExpr}" \u2192 ${visible}`);
|
|
2082
|
+
applyShowVisibility(el, visible, transition, !initialized);
|
|
2083
|
+
initialized = true;
|
|
2084
|
+
});
|
|
2085
|
+
disposers.push(dispose);
|
|
2086
|
+
if (transition) {
|
|
2087
|
+
disposers.push(() => clearTransitionState(el));
|
|
2088
|
+
}
|
|
2089
|
+
}
|
|
2090
|
+
const ifExpr = !known || known.has("data-if") ? el.getAttribute("data-if") : null;
|
|
2091
|
+
if (ifExpr) {
|
|
2092
|
+
const evaluate = buildEvaluator(ifExpr, scope);
|
|
2093
|
+
const transition = parseTransitionSpec(el);
|
|
2094
|
+
const placeholder = document.createComment("forma-if");
|
|
2095
|
+
const parent = el.parentNode;
|
|
2096
|
+
let inserted = true;
|
|
2097
|
+
let initialized = false;
|
|
2098
|
+
const dispose = internalEffect(() => {
|
|
2099
|
+
const show = !!evaluate();
|
|
2100
|
+
if (show && !inserted) {
|
|
2101
|
+
clearTransitionState(el);
|
|
2102
|
+
el.removeAttribute("data-forma-leaving");
|
|
2103
|
+
if (initialized && transition) {
|
|
2104
|
+
transitionInsert(el, parent, placeholder, transition);
|
|
2105
|
+
} else {
|
|
2106
|
+
parent?.insertBefore(el, placeholder);
|
|
2107
|
+
}
|
|
2108
|
+
inserted = true;
|
|
2109
|
+
} else if (!show && inserted) {
|
|
2110
|
+
if (initialized && transition) {
|
|
2111
|
+
transitionRemove(el, transition, () => {
|
|
2112
|
+
if (el.parentNode) {
|
|
2113
|
+
parent?.insertBefore(placeholder, el);
|
|
2114
|
+
el.remove();
|
|
2115
|
+
}
|
|
2116
|
+
});
|
|
2117
|
+
} else {
|
|
2118
|
+
parent?.insertBefore(placeholder, el);
|
|
2119
|
+
el.remove();
|
|
2120
|
+
}
|
|
2121
|
+
inserted = false;
|
|
2122
|
+
}
|
|
2123
|
+
initialized = true;
|
|
2124
|
+
});
|
|
2125
|
+
disposers.push(dispose);
|
|
2126
|
+
if (transition) {
|
|
2127
|
+
disposers.push(() => clearTransitionState(el));
|
|
2128
|
+
}
|
|
2129
|
+
}
|
|
2130
|
+
const modelExpr = !known || known.has("data-model") ? el.getAttribute("data-model") : null;
|
|
2131
|
+
if (modelExpr) {
|
|
2132
|
+
const prop = modelExpr.replace(RE_STRIP_BRACES, "").trim();
|
|
2133
|
+
const getter = scope.getters[prop];
|
|
2134
|
+
const setter = scope.setters[prop];
|
|
2135
|
+
if (getter && setter) {
|
|
2136
|
+
const input = el;
|
|
2137
|
+
const dispose = internalEffect(() => {
|
|
2138
|
+
const val = getter();
|
|
2139
|
+
if (input.type === "checkbox") {
|
|
2140
|
+
input.checked = !!val;
|
|
2141
|
+
} else {
|
|
2142
|
+
input.value = String(val ?? "");
|
|
2143
|
+
}
|
|
2144
|
+
});
|
|
2145
|
+
disposers.push(dispose);
|
|
2146
|
+
const event = input.type === "checkbox" ? "change" : "input";
|
|
2147
|
+
const onModelInput = () => {
|
|
2148
|
+
if (input.type === "checkbox") {
|
|
2149
|
+
setter(input.checked);
|
|
2150
|
+
} else if (input.type === "number" || input.type === "range") {
|
|
2151
|
+
setter(Number(input.value));
|
|
2152
|
+
} else {
|
|
2153
|
+
setter(input.value);
|
|
2154
|
+
}
|
|
2155
|
+
};
|
|
2156
|
+
input.addEventListener(event, onModelInput);
|
|
2157
|
+
disposers.push(() => {
|
|
2158
|
+
input.removeEventListener(event, onModelInput);
|
|
2159
|
+
});
|
|
2160
|
+
}
|
|
2161
|
+
}
|
|
2162
|
+
const hasColonDirectives = !known || hasAnyPrefix(known, "data-on:", "data-class:", "data-bind:");
|
|
2163
|
+
const attrs = el.attributes;
|
|
2164
|
+
if (hasColonDirectives) for (let i = 0; i < attrs.length; i++) {
|
|
2165
|
+
const attr = attrs[i];
|
|
2166
|
+
const name = attr.name;
|
|
2167
|
+
if (name.startsWith("data-on:")) {
|
|
2168
|
+
const event = name.slice(8);
|
|
2169
|
+
const built = buildHandler(attr.value, scope);
|
|
2170
|
+
const handler = built.handler;
|
|
2171
|
+
if (_debug) {
|
|
2172
|
+
const tag = el.tagName.toLowerCase();
|
|
2173
|
+
const id = el.id ? `#${el.id}` : "";
|
|
2174
|
+
const cls = el.className ? `.${String(el.className).split(" ")[0]}` : "";
|
|
2175
|
+
dbg(`bindElement: data-on:${event}="${attr.value}" on <${tag}${id}${cls}>`);
|
|
2176
|
+
}
|
|
2177
|
+
if (!built.supported) {
|
|
2178
|
+
el.setAttribute("data-forma-handler-error", "unsupported");
|
|
2179
|
+
} else if (el.hasAttribute("data-forma-handler-error")) {
|
|
2180
|
+
el.removeAttribute("data-forma-handler-error");
|
|
2181
|
+
}
|
|
2182
|
+
if (_debug) {
|
|
2183
|
+
const attrVal = attr.value;
|
|
2184
|
+
const tracedHandler = (e) => {
|
|
2185
|
+
dbg(`HANDLER FIRED: data-on:${event}="${attrVal}"`, "isTrusted:", e.isTrusted);
|
|
2186
|
+
handler(e);
|
|
2187
|
+
};
|
|
2188
|
+
el.addEventListener(event, tracedHandler);
|
|
2189
|
+
disposers.push(() => {
|
|
2190
|
+
el.removeEventListener(event, tracedHandler);
|
|
2191
|
+
});
|
|
2192
|
+
} else {
|
|
2193
|
+
el.addEventListener(event, handler);
|
|
2194
|
+
disposers.push(() => {
|
|
2195
|
+
el.removeEventListener(event, handler);
|
|
2196
|
+
});
|
|
2197
|
+
}
|
|
2198
|
+
} else if (name.startsWith("data-class:")) {
|
|
2199
|
+
const cls = name.slice(11);
|
|
2200
|
+
const evaluate = buildEvaluator(attr.value, scope);
|
|
2201
|
+
const dispose = internalEffect(() => {
|
|
2202
|
+
el.classList.toggle(cls, !!evaluate());
|
|
2203
|
+
});
|
|
2204
|
+
disposers.push(dispose);
|
|
2205
|
+
} else if (name.startsWith("data-bind:")) {
|
|
2206
|
+
const attrName = name.slice(10);
|
|
2207
|
+
const evaluate = buildEvaluator(attr.value, scope);
|
|
2208
|
+
const dispose = internalEffect(() => {
|
|
2209
|
+
const val = evaluate();
|
|
2210
|
+
if (val == null || val === false) {
|
|
2211
|
+
el.removeAttribute(attrName);
|
|
2212
|
+
} else {
|
|
2213
|
+
el.setAttribute(attrName, String(val));
|
|
2214
|
+
}
|
|
2215
|
+
});
|
|
2216
|
+
disposers.push(dispose);
|
|
2217
|
+
}
|
|
2218
|
+
}
|
|
2219
|
+
const persistExpr = !known || known.has("data-persist") ? el.getAttribute("data-persist") : null;
|
|
2220
|
+
if (persistExpr) {
|
|
2221
|
+
const prop = persistExpr.replace(RE_STRIP_BRACES, "").trim();
|
|
2222
|
+
const getter = scope.getters[prop];
|
|
2223
|
+
const setter = scope.setters[prop];
|
|
2224
|
+
if (getter && setter) {
|
|
2225
|
+
const key = "forma:" + prop;
|
|
2226
|
+
try {
|
|
2227
|
+
const saved = localStorage.getItem(key);
|
|
2228
|
+
if (saved !== null) setter(JSON.parse(saved));
|
|
2229
|
+
} catch {
|
|
2230
|
+
}
|
|
2231
|
+
const dispose = internalEffect(() => {
|
|
2232
|
+
try {
|
|
2233
|
+
localStorage.setItem(key, JSON.stringify(getter()));
|
|
2234
|
+
} catch {
|
|
2235
|
+
}
|
|
2236
|
+
});
|
|
2237
|
+
disposers.push(dispose);
|
|
2238
|
+
}
|
|
2239
|
+
}
|
|
2240
|
+
const listExpr = !known || known.has("data-list") ? el.getAttribute("data-list") : null;
|
|
2241
|
+
if (listExpr) {
|
|
2242
|
+
const evaluate = buildEvaluator(listExpr, scope);
|
|
2243
|
+
const templateEl = el.children[0];
|
|
2244
|
+
if (templateEl) {
|
|
2245
|
+
let disposeCloneBindings2 = function(node) {
|
|
2246
|
+
const el2 = node;
|
|
2247
|
+
if (Array.isArray(el2.__formaDisposers)) {
|
|
2248
|
+
for (const d of el2.__formaDisposers) {
|
|
2249
|
+
try {
|
|
2250
|
+
d();
|
|
2251
|
+
} catch {
|
|
2252
|
+
}
|
|
2253
|
+
}
|
|
2254
|
+
delete el2.__formaDisposers;
|
|
2255
|
+
}
|
|
2256
|
+
}, createBoundClone2 = function(item, index) {
|
|
2257
|
+
const clone = cloneWithTemplateData(template, item);
|
|
2258
|
+
const childScope = createChildScope(scope, { item, index });
|
|
2259
|
+
const itemDisposers = [];
|
|
2260
|
+
bindElement(clone, childScope, itemDisposers);
|
|
2261
|
+
for (const desc of Array.from(clone.querySelectorAll("*"))) {
|
|
2262
|
+
bindElement(desc, childScope, itemDisposers);
|
|
2263
|
+
}
|
|
2264
|
+
clone.__formaDisposers = itemDisposers;
|
|
2265
|
+
return clone;
|
|
2266
|
+
}, updateBoundClone2 = function(node, item, index) {
|
|
2267
|
+
disposeCloneBindings2(node);
|
|
2268
|
+
updateTemplateData(node, item);
|
|
2269
|
+
const childScope = createChildScope(scope, { item, index });
|
|
2270
|
+
const itemDisposers = [];
|
|
2271
|
+
bindElement(node, childScope, itemDisposers);
|
|
2272
|
+
for (const desc of Array.from(node.querySelectorAll("*"))) {
|
|
2273
|
+
bindElement(desc, childScope, itemDisposers);
|
|
2274
|
+
}
|
|
2275
|
+
node.__formaDisposers = itemDisposers;
|
|
2276
|
+
};
|
|
2277
|
+
const template = templateEl.cloneNode(true);
|
|
2278
|
+
el.removeChild(templateEl);
|
|
2279
|
+
const keyAttr = template.getAttribute("data-key");
|
|
2280
|
+
const keyProp = keyAttr ? keyAttr.replace(RE_STRIP_ITEM_BRACES, "").trim() : null;
|
|
2281
|
+
const listTransition = parseTransitionSpec(el);
|
|
2282
|
+
let oldItems = [];
|
|
2283
|
+
let oldNodes = [];
|
|
2284
|
+
const listHooks = listTransition ? {
|
|
2285
|
+
onInsert: (node) => {
|
|
2286
|
+
const htmlEl = node;
|
|
2287
|
+
if (!htmlEl.setAttribute) return;
|
|
2288
|
+
const state = getTransitionState(htmlEl);
|
|
2289
|
+
state.token += 1;
|
|
2290
|
+
const token = state.token;
|
|
2291
|
+
if (state.cancel) state.cancel();
|
|
2292
|
+
state.cancel = runTransitionPhase(
|
|
2293
|
+
htmlEl,
|
|
2294
|
+
{
|
|
2295
|
+
base: listTransition.enter,
|
|
2296
|
+
from: listTransition.enterFrom,
|
|
2297
|
+
to: listTransition.enterTo,
|
|
2298
|
+
durationMs: listTransition.enterDurationMs
|
|
2299
|
+
},
|
|
2300
|
+
() => {
|
|
2301
|
+
const current = getTransitionState(htmlEl);
|
|
2302
|
+
if (current.token === token) current.cancel = null;
|
|
2303
|
+
}
|
|
2304
|
+
);
|
|
2305
|
+
},
|
|
2306
|
+
onBeforeRemove: (node, done) => {
|
|
2307
|
+
const htmlEl = node;
|
|
2308
|
+
if (!htmlEl.setAttribute) {
|
|
2309
|
+
done();
|
|
2310
|
+
return;
|
|
2311
|
+
}
|
|
2312
|
+
disposeCloneBindings2(node);
|
|
2313
|
+
transitionRemove(htmlEl, listTransition, () => {
|
|
2314
|
+
done();
|
|
2315
|
+
});
|
|
2316
|
+
}
|
|
2317
|
+
} : void 0;
|
|
2318
|
+
const dispose = internalEffect(() => {
|
|
2319
|
+
const rawItems = evaluate();
|
|
2320
|
+
if (!Array.isArray(rawItems)) {
|
|
2321
|
+
for (const n of oldNodes) {
|
|
2322
|
+
disposeCloneBindings2(n);
|
|
2323
|
+
el.removeChild(n);
|
|
2324
|
+
}
|
|
2325
|
+
oldItems = [];
|
|
2326
|
+
oldNodes = [];
|
|
2327
|
+
return;
|
|
2328
|
+
}
|
|
2329
|
+
if (listTransition) {
|
|
2330
|
+
const leavingNodes = el.querySelectorAll("[data-forma-leaving]");
|
|
2331
|
+
for (const ln of Array.from(leavingNodes)) {
|
|
2332
|
+
clearTransitionState(ln);
|
|
2333
|
+
ln.removeAttribute("data-forma-leaving");
|
|
2334
|
+
if (ln.parentNode) ln.parentNode.removeChild(ln);
|
|
2335
|
+
}
|
|
2336
|
+
}
|
|
2337
|
+
const prevNodes = new Set(oldNodes);
|
|
2338
|
+
if (keyProp) {
|
|
2339
|
+
const result = reconcileList(
|
|
2340
|
+
el,
|
|
2341
|
+
oldItems,
|
|
2342
|
+
rawItems,
|
|
2343
|
+
oldNodes,
|
|
2344
|
+
(item) => String(item?.[keyProp] ?? ""),
|
|
2345
|
+
(item) => {
|
|
2346
|
+
const idx = rawItems.indexOf(item);
|
|
2347
|
+
return createBoundClone2(item, idx);
|
|
2348
|
+
},
|
|
2349
|
+
(node, item) => {
|
|
2350
|
+
const idx = rawItems.indexOf(item);
|
|
2351
|
+
updateBoundClone2(node, item, idx);
|
|
2352
|
+
},
|
|
2353
|
+
void 0,
|
|
2354
|
+
// beforeNode
|
|
2355
|
+
listHooks
|
|
2356
|
+
);
|
|
2357
|
+
const nextNodes = new Set(result.nodes);
|
|
2358
|
+
for (const n of prevNodes) {
|
|
2359
|
+
if (!nextNodes.has(n)) {
|
|
2360
|
+
if (n.hasAttribute?.("data-forma-leaving")) continue;
|
|
2361
|
+
disposeCloneBindings2(n);
|
|
2362
|
+
}
|
|
2363
|
+
}
|
|
2364
|
+
oldItems = result.items;
|
|
2365
|
+
oldNodes = result.nodes;
|
|
2366
|
+
} else {
|
|
2367
|
+
const wrapped = rawItems.map((item, i) => ({ __idx: i, __item: item }));
|
|
2368
|
+
const oldWrapped = oldItems;
|
|
2369
|
+
const result = reconcileList(
|
|
2370
|
+
el,
|
|
2371
|
+
oldWrapped,
|
|
2372
|
+
wrapped,
|
|
2373
|
+
oldNodes,
|
|
2374
|
+
(w) => w.__idx,
|
|
2375
|
+
(w) => createBoundClone2(w.__item, w.__idx),
|
|
2376
|
+
(node, w) => updateBoundClone2(node, w.__item, w.__idx),
|
|
2377
|
+
void 0,
|
|
2378
|
+
// beforeNode
|
|
2379
|
+
listHooks
|
|
2380
|
+
);
|
|
2381
|
+
const nextNodes = new Set(result.nodes);
|
|
2382
|
+
for (const n of prevNodes) {
|
|
2383
|
+
if (!nextNodes.has(n)) {
|
|
2384
|
+
if (n.hasAttribute?.("data-forma-leaving")) continue;
|
|
2385
|
+
disposeCloneBindings2(n);
|
|
2386
|
+
}
|
|
2387
|
+
}
|
|
2388
|
+
oldItems = result.items;
|
|
2389
|
+
oldNodes = result.nodes;
|
|
2390
|
+
}
|
|
2391
|
+
});
|
|
2392
|
+
disposers.push(dispose);
|
|
2393
|
+
}
|
|
2394
|
+
}
|
|
2395
|
+
const fetchExpr = !known || known.has("data-fetch") ? el.getAttribute("data-fetch") : null;
|
|
2396
|
+
if (fetchExpr) {
|
|
2397
|
+
const arrowMatch = fetchExpr.match(RE_FETCH);
|
|
2398
|
+
if (arrowMatch) {
|
|
2399
|
+
const urlPart = arrowMatch[1].trim();
|
|
2400
|
+
const target = arrowMatch[2].trim();
|
|
2401
|
+
const modifiers = arrowMatch[3]?.trim() ?? "";
|
|
2402
|
+
let method = "GET";
|
|
2403
|
+
let url = urlPart;
|
|
2404
|
+
const methodMatch = urlPart.match(RE_FETCH_METHOD);
|
|
2405
|
+
if (methodMatch) {
|
|
2406
|
+
method = methodMatch[1].toUpperCase();
|
|
2407
|
+
url = methodMatch[2].trim();
|
|
2408
|
+
}
|
|
2409
|
+
let loadingTarget;
|
|
2410
|
+
let errorTarget;
|
|
2411
|
+
let interval;
|
|
2412
|
+
for (const mod of modifiers.split("|").filter(Boolean)) {
|
|
2413
|
+
const [k, v] = mod.split(":").map((s) => s.trim());
|
|
2414
|
+
if (k === "loading") loadingTarget = v;
|
|
2415
|
+
else if (k === "error") errorTarget = v;
|
|
2416
|
+
else if (k === "poll") interval = parseInt(v ?? "0", 10);
|
|
2417
|
+
}
|
|
2418
|
+
const [getTarget, setTarget] = createValueSignal(null);
|
|
2419
|
+
scope.getters[target] = getTarget;
|
|
2420
|
+
scope.setters[target] = setTarget;
|
|
2421
|
+
if (loadingTarget) {
|
|
2422
|
+
const [gl, sl] = createValueSignal(false);
|
|
2423
|
+
scope.getters[loadingTarget] = gl;
|
|
2424
|
+
scope.setters[loadingTarget] = sl;
|
|
2425
|
+
}
|
|
2426
|
+
if (errorTarget) {
|
|
2427
|
+
const [ge, se] = createValueSignal(null);
|
|
2428
|
+
scope.getters[errorTarget] = ge;
|
|
2429
|
+
scope.setters[errorTarget] = se;
|
|
2430
|
+
}
|
|
2431
|
+
const doFetch = () => {
|
|
2432
|
+
if (loadingTarget) scope.setters[loadingTarget](true);
|
|
2433
|
+
fetch(url, { method }).then((r) => r.json()).then((data) => {
|
|
2434
|
+
setTarget(data);
|
|
2435
|
+
if (loadingTarget) scope.setters[loadingTarget](false);
|
|
2436
|
+
}).catch((err) => {
|
|
2437
|
+
if (errorTarget) scope.setters[errorTarget](err.message);
|
|
2438
|
+
if (loadingTarget) scope.setters[loadingTarget](false);
|
|
2439
|
+
});
|
|
2440
|
+
};
|
|
2441
|
+
const fetchId = el.getAttribute("data-fetch-id");
|
|
2442
|
+
if (fetchId) {
|
|
2443
|
+
_refetchRegistry.set(fetchId, doFetch);
|
|
2444
|
+
disposers.push(() => _refetchRegistry.delete(fetchId));
|
|
2445
|
+
}
|
|
2446
|
+
doFetch();
|
|
2447
|
+
if (interval && interval > 0) {
|
|
2448
|
+
const id = setInterval(doFetch, interval);
|
|
2449
|
+
disposers.push(() => clearInterval(id));
|
|
2450
|
+
}
|
|
2451
|
+
}
|
|
2452
|
+
}
|
|
2453
|
+
}
|
|
2454
|
+
function hasDirective(el) {
|
|
2455
|
+
const attrs = el.attributes;
|
|
2456
|
+
for (let i = 0; i < attrs.length; i++) {
|
|
2457
|
+
const name = attrs[i].name;
|
|
2458
|
+
if (name.startsWith("data-text") || name.startsWith("data-show") || name.startsWith("data-if") || name.startsWith("data-model") || name.startsWith("data-computed") || name.startsWith("data-persist") || name.startsWith("data-list") || name.startsWith("data-fetch") || name.startsWith("data-on:") || name.startsWith("data-class:") || name.startsWith("data-bind:") || name.startsWith("data-transition")) {
|
|
2459
|
+
return true;
|
|
2460
|
+
}
|
|
2461
|
+
}
|
|
2462
|
+
return false;
|
|
2463
|
+
}
|
|
2464
|
+
var _directiveMap = null;
|
|
2465
|
+
function setDirectiveMap(map) {
|
|
2466
|
+
if (!map || Object.keys(map).length === 0) {
|
|
2467
|
+
_directiveMap = null;
|
|
2468
|
+
return;
|
|
2469
|
+
}
|
|
2470
|
+
_directiveMap = /* @__PURE__ */ new Map();
|
|
2471
|
+
for (const id in map) {
|
|
2472
|
+
_directiveMap.set(id, new Set(map[id]));
|
|
2473
|
+
}
|
|
2474
|
+
}
|
|
2475
|
+
function buildDirectiveSelector() {
|
|
2476
|
+
if (!_directiveMap || _directiveMap.size === 0) return null;
|
|
2477
|
+
if (_directiveMap.size > 200) return null;
|
|
2478
|
+
const parts = [];
|
|
2479
|
+
for (const id of _directiveMap.keys()) {
|
|
2480
|
+
parts.push(`[data-forma-id="${id}"]`);
|
|
2481
|
+
}
|
|
2482
|
+
return parts.join(",");
|
|
2483
|
+
}
|
|
2484
|
+
function getDirectives(el) {
|
|
2485
|
+
if (!_directiveMap) return null;
|
|
2486
|
+
const id = el.getAttribute("data-forma-id");
|
|
2487
|
+
if (!id) return null;
|
|
2488
|
+
return _directiveMap.get(id) ?? null;
|
|
2489
|
+
}
|
|
2490
|
+
function hasAnyPrefix(set, ...prefixes) {
|
|
2491
|
+
for (const entry of set) {
|
|
2492
|
+
for (const prefix of prefixes) {
|
|
2493
|
+
if (entry.startsWith(prefix)) return true;
|
|
2494
|
+
}
|
|
2495
|
+
}
|
|
2496
|
+
return false;
|
|
2497
|
+
}
|
|
2498
|
+
function mountScope(root) {
|
|
2499
|
+
if (root.__formaDisposers) {
|
|
2500
|
+
if (_debug) dbg("mountScope: SKIPPED (already mounted)");
|
|
2501
|
+
return;
|
|
2502
|
+
}
|
|
2503
|
+
const scope = initScope(root);
|
|
2504
|
+
const disposers = [];
|
|
2505
|
+
bindElement(root, scope, disposers);
|
|
2506
|
+
let boundCount = 0;
|
|
2507
|
+
const selector = buildDirectiveSelector();
|
|
2508
|
+
if (selector) {
|
|
2509
|
+
const targets = root.querySelectorAll(selector);
|
|
2510
|
+
for (let i = 0; i < targets.length; i++) {
|
|
2511
|
+
bindElement(targets[i], scope, disposers);
|
|
2512
|
+
boundCount++;
|
|
2513
|
+
}
|
|
2514
|
+
} else {
|
|
2515
|
+
const descendants = root.querySelectorAll("*");
|
|
2516
|
+
for (let i = 0; i < descendants.length; i++) {
|
|
2517
|
+
const el = descendants[i];
|
|
2518
|
+
if (hasDirective(el)) {
|
|
2519
|
+
bindElement(el, scope, disposers);
|
|
2520
|
+
boundCount++;
|
|
2521
|
+
}
|
|
2522
|
+
}
|
|
2523
|
+
}
|
|
2524
|
+
root.__formaDisposers = disposers;
|
|
2525
|
+
root.__formaScope = scope;
|
|
2526
|
+
root.__formaInitialState = root.getAttribute("data-forma-state") ?? "{}";
|
|
2527
|
+
if (_debug) dbg("mountScope: DONE \u2014", boundCount, "elements bound,", disposers.length, "disposers", selector ? "(targeted)" : "(full scan)");
|
|
2528
|
+
}
|
|
2529
|
+
function unmountScope(root) {
|
|
2530
|
+
const disposers = root.__formaDisposers;
|
|
2531
|
+
if (disposers) {
|
|
2532
|
+
for (const d of disposers) {
|
|
2533
|
+
try {
|
|
2534
|
+
d();
|
|
2535
|
+
} catch {
|
|
2536
|
+
}
|
|
2537
|
+
}
|
|
2538
|
+
delete root.__formaDisposers;
|
|
2539
|
+
delete root.__formaScope;
|
|
2540
|
+
delete root.__formaInitialState;
|
|
2541
|
+
}
|
|
2542
|
+
}
|
|
2543
|
+
var _observer = null;
|
|
2544
|
+
var ELEMENT_NODE = 1;
|
|
2545
|
+
var MUTATION_CHUNK_SIZE = 40;
|
|
2546
|
+
var _pendingMutations = [];
|
|
2547
|
+
var _drainingMutations = false;
|
|
2548
|
+
function processMutation(mutation) {
|
|
2549
|
+
for (let i = 0; i < mutation.removedNodes.length; i++) {
|
|
2550
|
+
const node = mutation.removedNodes[i];
|
|
2551
|
+
if (node.nodeType !== ELEMENT_NODE) continue;
|
|
2552
|
+
const el = node;
|
|
2553
|
+
if (el.hasAttribute("data-forma-state")) {
|
|
2554
|
+
if (_debug) dbg("MutationObserver: REMOVED scope");
|
|
2555
|
+
unmountScope(el);
|
|
2556
|
+
}
|
|
2557
|
+
const removed = el.querySelectorAll("[data-forma-state]");
|
|
2558
|
+
for (let j = 0; j < removed.length; j++) {
|
|
2559
|
+
unmountScope(removed[j]);
|
|
2560
|
+
}
|
|
2561
|
+
}
|
|
2562
|
+
for (let i = 0; i < mutation.addedNodes.length; i++) {
|
|
2563
|
+
const node = mutation.addedNodes[i];
|
|
2564
|
+
if (node.nodeType !== ELEMENT_NODE) continue;
|
|
2565
|
+
const el = node;
|
|
2566
|
+
if (el.closest("[data-forma-leaving]")) continue;
|
|
2567
|
+
if (el.hasAttribute("data-forma-state")) {
|
|
2568
|
+
if (_debug) dbg("MutationObserver: ADDED scope via mutation");
|
|
2569
|
+
mountScope(el);
|
|
2570
|
+
}
|
|
2571
|
+
const added = el.querySelectorAll("[data-forma-state]");
|
|
2572
|
+
if (_debug && added.length > 0) {
|
|
2573
|
+
dbg("MutationObserver: found", added.length, "nested scope(s) in added subtree");
|
|
2574
|
+
}
|
|
2575
|
+
for (let j = 0; j < added.length; j++) {
|
|
2576
|
+
const desc = added[j];
|
|
2577
|
+
if (desc.closest("[data-forma-leaving]")) continue;
|
|
2578
|
+
mountScope(desc);
|
|
2579
|
+
}
|
|
2580
|
+
}
|
|
2581
|
+
if (mutation.type === "attributes" && mutation.attributeName === "data-forma-state") {
|
|
2582
|
+
const target = mutation.target;
|
|
2583
|
+
unmountScope(target);
|
|
2584
|
+
if (target.hasAttribute("data-forma-state")) {
|
|
2585
|
+
mountScope(target);
|
|
2586
|
+
}
|
|
2587
|
+
}
|
|
2588
|
+
}
|
|
2589
|
+
async function drainMutationQueue() {
|
|
2590
|
+
try {
|
|
2591
|
+
while (_pendingMutations.length > 0) {
|
|
2592
|
+
const batch2 = _pendingMutations.splice(0, MUTATION_CHUNK_SIZE);
|
|
2593
|
+
for (let i = 0; i < batch2.length; i++) {
|
|
2594
|
+
processMutation(batch2[i]);
|
|
2595
|
+
}
|
|
2596
|
+
if (_pendingMutations.length > 0) {
|
|
2597
|
+
await yieldToMain();
|
|
2598
|
+
}
|
|
2599
|
+
}
|
|
2600
|
+
} finally {
|
|
2601
|
+
_drainingMutations = false;
|
|
2602
|
+
if (_pendingMutations.length > 0 && !_drainingMutations) {
|
|
2603
|
+
_drainingMutations = true;
|
|
2604
|
+
void drainMutationQueue();
|
|
2605
|
+
}
|
|
2606
|
+
}
|
|
2607
|
+
}
|
|
2608
|
+
function handleMutations(mutations) {
|
|
2609
|
+
if (_debug) dbg("MutationObserver: queued", mutations.length, "mutation(s)");
|
|
2610
|
+
_pendingMutations.push(...mutations);
|
|
2611
|
+
if (_drainingMutations) return;
|
|
2612
|
+
_drainingMutations = true;
|
|
2613
|
+
void drainMutationQueue();
|
|
2614
|
+
}
|
|
2615
|
+
function startObserver() {
|
|
2616
|
+
if (_observer) return;
|
|
2617
|
+
_observer = new MutationObserver(handleMutations);
|
|
2618
|
+
const target = document.body || document.documentElement;
|
|
2619
|
+
if (target) {
|
|
2620
|
+
_observer.observe(target, {
|
|
2621
|
+
childList: true,
|
|
2622
|
+
subtree: true,
|
|
2623
|
+
attributes: true,
|
|
2624
|
+
attributeFilter: ["data-forma-state"]
|
|
2625
|
+
});
|
|
2626
|
+
}
|
|
2627
|
+
}
|
|
2628
|
+
function stopObserver() {
|
|
2629
|
+
if (_observer) {
|
|
2630
|
+
_observer.disconnect();
|
|
2631
|
+
_observer = null;
|
|
2632
|
+
}
|
|
2633
|
+
}
|
|
2634
|
+
function initRuntime() {
|
|
2635
|
+
if (_autoContainment) {
|
|
2636
|
+
applyContainmentHints(document, { skipIfAlreadySet: true });
|
|
2637
|
+
}
|
|
2638
|
+
const stateRoots = document.querySelectorAll("[data-forma-state]");
|
|
2639
|
+
if (_debug) dbg("initRuntime: found", stateRoots.length, "scope(s)");
|
|
2640
|
+
for (const root of Array.from(stateRoots)) {
|
|
2641
|
+
mountScope(root);
|
|
2642
|
+
}
|
|
2643
|
+
startObserver();
|
|
2644
|
+
if (_debug) dbg("initRuntime: MutationObserver started");
|
|
2645
|
+
}
|
|
2646
|
+
function destroyRuntime() {
|
|
2647
|
+
stopObserver();
|
|
2648
|
+
const stateRoots = document.querySelectorAll("[data-forma-state]");
|
|
2649
|
+
for (const root of Array.from(stateRoots)) {
|
|
2650
|
+
unmountScope(root);
|
|
2651
|
+
}
|
|
2652
|
+
}
|
|
2653
|
+
function mount(el) {
|
|
2654
|
+
if (el.hasAttribute("data-forma-state")) {
|
|
2655
|
+
mountScope(el);
|
|
2656
|
+
}
|
|
2657
|
+
const descendants = el.querySelectorAll("[data-forma-state]");
|
|
2658
|
+
for (const desc of Array.from(descendants)) {
|
|
2659
|
+
mountScope(desc);
|
|
2660
|
+
}
|
|
2661
|
+
}
|
|
2662
|
+
function unmount(el) {
|
|
2663
|
+
if (el.hasAttribute("data-forma-state")) {
|
|
2664
|
+
unmountScope(el);
|
|
2665
|
+
}
|
|
2666
|
+
const descendants = el.querySelectorAll("[data-forma-state]");
|
|
2667
|
+
for (const desc of Array.from(descendants)) {
|
|
2668
|
+
unmountScope(desc);
|
|
2669
|
+
}
|
|
2670
|
+
}
|
|
2671
|
+
if (typeof document !== "undefined") {
|
|
2672
|
+
if (document.readyState === "loading") {
|
|
2673
|
+
document.addEventListener("DOMContentLoaded", initRuntime);
|
|
2674
|
+
} else {
|
|
2675
|
+
initRuntime();
|
|
2676
|
+
}
|
|
2677
|
+
}
|
|
2678
|
+
function setDebug(on) {
|
|
2679
|
+
_debug = on;
|
|
2680
|
+
}
|
|
2681
|
+
function setUnsafeEvalMode(mode) {
|
|
2682
|
+
if (_unsafeEvalMode === mode) return;
|
|
2683
|
+
_unsafeEvalMode = mode;
|
|
2684
|
+
if (mode === "locked-off") _allowUnsafeEval = false;
|
|
2685
|
+
if (mode === "locked-on") _allowUnsafeEval = true;
|
|
2686
|
+
scopeExpressionCache = /* @__PURE__ */ new WeakMap();
|
|
2687
|
+
scopeHandlerCache = /* @__PURE__ */ new WeakMap();
|
|
2688
|
+
}
|
|
2689
|
+
function setUnsafeEval(on) {
|
|
2690
|
+
if (_unsafeEvalMode !== "mutable") {
|
|
2691
|
+
dbg(
|
|
2692
|
+
`setUnsafeEval ignored (mode=${_unsafeEvalMode}); unsafe fallback is locked`
|
|
2693
|
+
);
|
|
2694
|
+
return;
|
|
2695
|
+
}
|
|
2696
|
+
if (_allowUnsafeEval === on) return;
|
|
2697
|
+
_allowUnsafeEval = on;
|
|
2698
|
+
scopeExpressionCache = /* @__PURE__ */ new WeakMap();
|
|
2699
|
+
scopeHandlerCache = /* @__PURE__ */ new WeakMap();
|
|
2700
|
+
}
|
|
2701
|
+
function getUnsafeEvalMode() {
|
|
2702
|
+
return _unsafeEvalMode;
|
|
2703
|
+
}
|
|
2704
|
+
function setDiagnostics(on) {
|
|
2705
|
+
_diagnosticsEnabled = on;
|
|
2706
|
+
}
|
|
2707
|
+
function getDiagnostics() {
|
|
2708
|
+
return Array.from(diagnostics.values()).map((d) => ({ ...d }));
|
|
2709
|
+
}
|
|
2710
|
+
function clearDiagnostics() {
|
|
2711
|
+
diagnostics.clear();
|
|
2712
|
+
}
|
|
2713
|
+
function getScopes() {
|
|
2714
|
+
const roots = document.querySelectorAll("[data-forma-state]");
|
|
2715
|
+
const result = [];
|
|
2716
|
+
for (const root of Array.from(roots)) {
|
|
2717
|
+
if (root.closest("[data-forma-leaving]")) continue;
|
|
2718
|
+
const scope = root.__formaScope;
|
|
2719
|
+
const initialJSON = root.__formaInitialState;
|
|
2720
|
+
if (!scope) continue;
|
|
2721
|
+
const values = {};
|
|
2722
|
+
for (const key of Object.keys(scope.getters)) {
|
|
2723
|
+
const val = scope.getters[key]();
|
|
2724
|
+
values[key] = { value: val, type: typeof val };
|
|
2725
|
+
}
|
|
2726
|
+
result.push({
|
|
2727
|
+
element: root,
|
|
2728
|
+
id: root.getAttribute("data-forma-id") || root.id || root.tagName.toLowerCase(),
|
|
2729
|
+
values,
|
|
2730
|
+
initialJSON: initialJSON ?? "{}"
|
|
2731
|
+
});
|
|
2732
|
+
}
|
|
2733
|
+
return result;
|
|
2734
|
+
}
|
|
2735
|
+
function setScopeValue(element, key, value2) {
|
|
2736
|
+
const scope = element.__formaScope;
|
|
2737
|
+
if (!scope?.setters[key]) return;
|
|
2738
|
+
batch(() => {
|
|
2739
|
+
scope.setters[key](value2);
|
|
2740
|
+
});
|
|
2741
|
+
}
|
|
2742
|
+
function resetScope(element) {
|
|
2743
|
+
const scope = element.__formaScope;
|
|
2744
|
+
const initialJSON = element.__formaInitialState;
|
|
2745
|
+
if (!scope || !initialJSON) return;
|
|
2746
|
+
const initial = parseState(initialJSON);
|
|
2747
|
+
batch(() => {
|
|
2748
|
+
for (const [key, val] of Object.entries(initial)) {
|
|
2749
|
+
scope.setters[key]?.(val);
|
|
2750
|
+
}
|
|
2751
|
+
});
|
|
2752
|
+
}
|
|
2753
|
+
var _reconciler = null;
|
|
2754
|
+
function getReconciler() {
|
|
2755
|
+
if (!_reconciler) {
|
|
2756
|
+
_reconciler = createReconciler({
|
|
2757
|
+
mountScope,
|
|
2758
|
+
unmountScope,
|
|
2759
|
+
disconnectObserver() {
|
|
2760
|
+
if (_observer) {
|
|
2761
|
+
_observer.disconnect();
|
|
2762
|
+
}
|
|
2763
|
+
},
|
|
2764
|
+
reconnectObserver() {
|
|
2765
|
+
if (_observer) {
|
|
2766
|
+
const target = document.body || document.documentElement;
|
|
2767
|
+
if (target) {
|
|
2768
|
+
_observer.observe(target, {
|
|
2769
|
+
childList: true,
|
|
2770
|
+
subtree: true,
|
|
2771
|
+
attributes: true,
|
|
2772
|
+
attributeFilter: ["data-forma-state"]
|
|
2773
|
+
});
|
|
2774
|
+
}
|
|
2775
|
+
}
|
|
2776
|
+
},
|
|
2777
|
+
batch
|
|
2778
|
+
});
|
|
2779
|
+
}
|
|
2780
|
+
return _reconciler;
|
|
2781
|
+
}
|
|
2782
|
+
function reconcile(container, html) {
|
|
2783
|
+
getReconciler()(container, html);
|
|
2784
|
+
}
|
|
2785
|
+
|
|
2786
|
+
exports.applyContainmentHints = applyContainmentHints;
|
|
2787
|
+
exports.clearDiagnostics = clearDiagnostics;
|
|
2788
|
+
exports.destroyRuntime = destroyRuntime;
|
|
2789
|
+
exports.getDiagnostics = getDiagnostics;
|
|
2790
|
+
exports.getScopes = getScopes;
|
|
2791
|
+
exports.getUnsafeEvalMode = getUnsafeEvalMode;
|
|
2792
|
+
exports.initRuntime = initRuntime;
|
|
2793
|
+
exports.mount = mount;
|
|
2794
|
+
exports.reconcile = reconcile;
|
|
2795
|
+
exports.resetScope = resetScope;
|
|
2796
|
+
exports.setDebug = setDebug;
|
|
2797
|
+
exports.setDiagnostics = setDiagnostics;
|
|
2798
|
+
exports.setDirectiveMap = setDirectiveMap;
|
|
2799
|
+
exports.setScopeValue = setScopeValue;
|
|
2800
|
+
exports.setUnsafeEval = setUnsafeEval;
|
|
2801
|
+
exports.setUnsafeEvalMode = setUnsafeEvalMode;
|
|
2802
|
+
exports.unmount = unmount;
|
|
2803
|
+
exports.yieldToMain = yieldToMain;
|
|
2804
|
+
//# sourceMappingURL=runtime-hardened.cjs.map
|
|
2805
|
+
//# sourceMappingURL=runtime-hardened.cjs.map
|