@constela/runtime 0.13.0 → 0.15.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/dist/index.d.ts +1 -0
- package/dist/index.js +426 -32
- package/package.json +4 -4
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -177,6 +177,7 @@ function createComputed(getter) {
|
|
|
177
177
|
}
|
|
178
178
|
|
|
179
179
|
// src/state/store.ts
|
|
180
|
+
import { isCookieInitialExpr } from "@constela/core";
|
|
180
181
|
function normalizePath(path) {
|
|
181
182
|
if (typeof path === "string") {
|
|
182
183
|
return path.split(".").map((segment) => {
|
|
@@ -213,10 +214,33 @@ function setValueAtPath(obj, path, value) {
|
|
|
213
214
|
);
|
|
214
215
|
return clone3;
|
|
215
216
|
}
|
|
217
|
+
function getCookieValue(key2) {
|
|
218
|
+
if (typeof document === "undefined") return void 0;
|
|
219
|
+
for (const cookie of document.cookie.split(";")) {
|
|
220
|
+
const eqIndex = cookie.indexOf("=");
|
|
221
|
+
if (eqIndex === -1) continue;
|
|
222
|
+
const name = cookie.slice(0, eqIndex).trim();
|
|
223
|
+
if (name === key2) {
|
|
224
|
+
const value = cookie.slice(eqIndex + 1);
|
|
225
|
+
try {
|
|
226
|
+
return decodeURIComponent(value);
|
|
227
|
+
} catch {
|
|
228
|
+
return value;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
return void 0;
|
|
233
|
+
}
|
|
216
234
|
function createStateStore(definitions) {
|
|
217
235
|
const signals = /* @__PURE__ */ new Map();
|
|
218
236
|
for (const [name, def] of Object.entries(definitions)) {
|
|
219
|
-
let initialValue
|
|
237
|
+
let initialValue;
|
|
238
|
+
if (isCookieInitialExpr(def.initial)) {
|
|
239
|
+
const cookieValue = getCookieValue(def.initial.key);
|
|
240
|
+
initialValue = cookieValue !== void 0 ? cookieValue : def.initial.default;
|
|
241
|
+
} else {
|
|
242
|
+
initialValue = def.initial;
|
|
243
|
+
}
|
|
220
244
|
if (name === "theme" && typeof window !== "undefined") {
|
|
221
245
|
try {
|
|
222
246
|
const stored = localStorage.getItem("theme");
|
|
@@ -245,6 +269,14 @@ function createStateStore(definitions) {
|
|
|
245
269
|
if (!signal) {
|
|
246
270
|
throw new Error(`State field "${name}" does not exist`);
|
|
247
271
|
}
|
|
272
|
+
if (name === "theme" && typeof document !== "undefined") {
|
|
273
|
+
try {
|
|
274
|
+
const valueStr = typeof value === "string" ? value : JSON.stringify(value);
|
|
275
|
+
const oneYear = 365 * 24 * 60 * 60;
|
|
276
|
+
document.cookie = `theme=${encodeURIComponent(valueStr)}; path=/; max-age=${oneYear}; SameSite=Lax`;
|
|
277
|
+
} catch {
|
|
278
|
+
}
|
|
279
|
+
}
|
|
248
280
|
signal.set(value);
|
|
249
281
|
},
|
|
250
282
|
subscribe(name, fn) {
|
|
@@ -428,6 +460,18 @@ function evaluate(expr, ctx) {
|
|
|
428
460
|
return val == null ? "" : String(val);
|
|
429
461
|
}).join("");
|
|
430
462
|
}
|
|
463
|
+
case "validity": {
|
|
464
|
+
const element2 = ctx.refs?.[expr.ref];
|
|
465
|
+
if (!element2) return null;
|
|
466
|
+
const formElement = element2;
|
|
467
|
+
if (!formElement.validity) return null;
|
|
468
|
+
const validity = formElement.validity;
|
|
469
|
+
const property = expr.property || "valid";
|
|
470
|
+
if (property === "message") {
|
|
471
|
+
return formElement.validationMessage || "";
|
|
472
|
+
}
|
|
473
|
+
return validity[property] ?? null;
|
|
474
|
+
}
|
|
431
475
|
default: {
|
|
432
476
|
const _exhaustiveCheck = expr;
|
|
433
477
|
throw new Error(`Unknown expression type: ${JSON.stringify(_exhaustiveCheck)}`);
|
|
@@ -593,15 +637,24 @@ async function executeAction(action, ctx) {
|
|
|
593
637
|
const extAction = action;
|
|
594
638
|
const isLocal = extAction._isLocalAction && extAction._localStore;
|
|
595
639
|
const localStore = extAction._localStore;
|
|
640
|
+
const delayPromises = [];
|
|
596
641
|
for (const step of action.steps) {
|
|
597
642
|
if (step.do === "set" || step.do === "update" || step.do === "setPath") {
|
|
598
643
|
executeStepSync(step, ctx, isLocal ? localStore : void 0);
|
|
599
644
|
} else if (step.do === "if") {
|
|
600
645
|
await executeIfStep(step, ctx, isLocal ? localStore : void 0);
|
|
646
|
+
} else if (step.do === "delay") {
|
|
647
|
+
const delayPromise = executeDelayStep(step, ctx);
|
|
648
|
+
delayPromises.push(delayPromise);
|
|
649
|
+
} else if (step.do === "interval") {
|
|
650
|
+
await executeIntervalStep(step, ctx);
|
|
601
651
|
} else {
|
|
602
652
|
await executeStep(step, ctx);
|
|
603
653
|
}
|
|
604
654
|
}
|
|
655
|
+
if (delayPromises.length > 0) {
|
|
656
|
+
await Promise.all(delayPromises);
|
|
657
|
+
}
|
|
605
658
|
}
|
|
606
659
|
function executeStepSync(step, ctx, localStore) {
|
|
607
660
|
switch (step.do) {
|
|
@@ -892,6 +945,18 @@ async function executeStep(step, ctx) {
|
|
|
892
945
|
case "close":
|
|
893
946
|
await executeCloseStep(step, ctx);
|
|
894
947
|
break;
|
|
948
|
+
case "delay":
|
|
949
|
+
await executeDelayStep(step, ctx);
|
|
950
|
+
break;
|
|
951
|
+
case "interval":
|
|
952
|
+
await executeIntervalStep(step, ctx);
|
|
953
|
+
break;
|
|
954
|
+
case "clearTimer":
|
|
955
|
+
await executeClearTimerStep(step, ctx);
|
|
956
|
+
break;
|
|
957
|
+
case "focus":
|
|
958
|
+
await executeFocusStep(step, ctx);
|
|
959
|
+
break;
|
|
895
960
|
}
|
|
896
961
|
}
|
|
897
962
|
async function executeSetStep(target, value, ctx) {
|
|
@@ -1258,6 +1323,137 @@ async function executeCloseStep(step, ctx) {
|
|
|
1258
1323
|
if (!ctx.connections) return;
|
|
1259
1324
|
ctx.connections.close(step.connection);
|
|
1260
1325
|
}
|
|
1326
|
+
async function executeDelayStep(step, ctx) {
|
|
1327
|
+
const evalCtx = createEvalContext(ctx);
|
|
1328
|
+
const msValue = evaluate(step.ms, evalCtx);
|
|
1329
|
+
const ms = typeof msValue === "number" ? Math.max(0, msValue) : 0;
|
|
1330
|
+
return new Promise((resolve) => {
|
|
1331
|
+
let resolved = false;
|
|
1332
|
+
const timeoutId = setTimeout(async () => {
|
|
1333
|
+
if (resolved) return;
|
|
1334
|
+
resolved = true;
|
|
1335
|
+
for (const thenStep of step.then) {
|
|
1336
|
+
if (thenStep.do === "set" || thenStep.do === "update" || thenStep.do === "setPath") {
|
|
1337
|
+
executeStepSync(thenStep, ctx);
|
|
1338
|
+
} else if (thenStep.do === "if") {
|
|
1339
|
+
await executeIfStep(thenStep, ctx);
|
|
1340
|
+
} else {
|
|
1341
|
+
await executeStep(thenStep, ctx);
|
|
1342
|
+
}
|
|
1343
|
+
}
|
|
1344
|
+
resolve();
|
|
1345
|
+
}, ms);
|
|
1346
|
+
const numericId = typeof timeoutId === "number" ? timeoutId : Number(timeoutId);
|
|
1347
|
+
if (step.result) {
|
|
1348
|
+
ctx.locals[step.result] = numericId;
|
|
1349
|
+
}
|
|
1350
|
+
if (!ctx.locals["_timerResolvers"]) {
|
|
1351
|
+
ctx.locals["_timerResolvers"] = /* @__PURE__ */ new Map();
|
|
1352
|
+
}
|
|
1353
|
+
ctx.locals["_timerResolvers"].set(numericId, () => {
|
|
1354
|
+
if (!resolved) {
|
|
1355
|
+
resolved = true;
|
|
1356
|
+
clearTimeout(timeoutId);
|
|
1357
|
+
resolve();
|
|
1358
|
+
}
|
|
1359
|
+
});
|
|
1360
|
+
if (ctx.cleanups) {
|
|
1361
|
+
ctx.cleanups.push(() => {
|
|
1362
|
+
if (!resolved) {
|
|
1363
|
+
resolved = true;
|
|
1364
|
+
clearTimeout(timeoutId);
|
|
1365
|
+
resolve();
|
|
1366
|
+
}
|
|
1367
|
+
});
|
|
1368
|
+
}
|
|
1369
|
+
});
|
|
1370
|
+
}
|
|
1371
|
+
async function executeIntervalStep(step, ctx) {
|
|
1372
|
+
const evalCtx = createEvalContext(ctx);
|
|
1373
|
+
const msValue = evaluate(step.ms, evalCtx);
|
|
1374
|
+
const ms = typeof msValue === "number" ? Math.max(0, msValue) : 0;
|
|
1375
|
+
const intervalId = setInterval(async () => {
|
|
1376
|
+
const action = ctx.actions[step.action];
|
|
1377
|
+
if (action) {
|
|
1378
|
+
await executeAction(action, ctx);
|
|
1379
|
+
}
|
|
1380
|
+
}, ms);
|
|
1381
|
+
const numericId = typeof intervalId === "number" ? intervalId : Number(intervalId);
|
|
1382
|
+
if (step.result) {
|
|
1383
|
+
ctx.locals[step.result] = numericId;
|
|
1384
|
+
}
|
|
1385
|
+
if (ctx.cleanups) {
|
|
1386
|
+
ctx.cleanups.push(() => clearInterval(intervalId));
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1389
|
+
async function executeClearTimerStep(step, ctx) {
|
|
1390
|
+
const evalCtx = createEvalContext(ctx);
|
|
1391
|
+
const timerId = evaluate(step.target, evalCtx);
|
|
1392
|
+
if (timerId == null) {
|
|
1393
|
+
return;
|
|
1394
|
+
}
|
|
1395
|
+
const numericId = typeof timerId === "number" ? timerId : Number(timerId);
|
|
1396
|
+
const timerResolvers = ctx.locals["_timerResolvers"];
|
|
1397
|
+
if (timerResolvers?.has(numericId)) {
|
|
1398
|
+
const resolver = timerResolvers.get(numericId);
|
|
1399
|
+
if (resolver) {
|
|
1400
|
+
resolver();
|
|
1401
|
+
}
|
|
1402
|
+
timerResolvers.delete(numericId);
|
|
1403
|
+
}
|
|
1404
|
+
clearTimeout(timerId);
|
|
1405
|
+
clearInterval(timerId);
|
|
1406
|
+
}
|
|
1407
|
+
async function executeFocusStep(step, ctx) {
|
|
1408
|
+
const evalCtx = createEvalContext(ctx);
|
|
1409
|
+
const targetValue = evaluate(step.target, evalCtx);
|
|
1410
|
+
let element2;
|
|
1411
|
+
if (targetValue instanceof Element) {
|
|
1412
|
+
element2 = targetValue;
|
|
1413
|
+
} else if (typeof targetValue === "string") {
|
|
1414
|
+
element2 = ctx.refs?.[targetValue];
|
|
1415
|
+
}
|
|
1416
|
+
try {
|
|
1417
|
+
if (!element2) {
|
|
1418
|
+
const refName = typeof targetValue === "string" ? targetValue : "unknown";
|
|
1419
|
+
throw new Error(`Ref "${refName}" not found`);
|
|
1420
|
+
}
|
|
1421
|
+
switch (step.operation) {
|
|
1422
|
+
case "focus":
|
|
1423
|
+
if (typeof element2.focus === "function") {
|
|
1424
|
+
element2.focus();
|
|
1425
|
+
}
|
|
1426
|
+
break;
|
|
1427
|
+
case "blur":
|
|
1428
|
+
if (typeof element2.blur === "function") {
|
|
1429
|
+
element2.blur();
|
|
1430
|
+
}
|
|
1431
|
+
break;
|
|
1432
|
+
case "select":
|
|
1433
|
+
if (typeof element2.select === "function") {
|
|
1434
|
+
element2.select();
|
|
1435
|
+
} else {
|
|
1436
|
+
throw new Error(`Element does not support select operation`);
|
|
1437
|
+
}
|
|
1438
|
+
break;
|
|
1439
|
+
}
|
|
1440
|
+
if (step.onSuccess) {
|
|
1441
|
+
for (const successStep of step.onSuccess) {
|
|
1442
|
+
await executeStep(successStep, ctx);
|
|
1443
|
+
}
|
|
1444
|
+
}
|
|
1445
|
+
} catch (err) {
|
|
1446
|
+
ctx.locals["error"] = {
|
|
1447
|
+
message: err instanceof Error ? err.message : String(err),
|
|
1448
|
+
name: err instanceof Error ? err.name : "Error"
|
|
1449
|
+
};
|
|
1450
|
+
if (step.onError) {
|
|
1451
|
+
for (const errorStep of step.onError) {
|
|
1452
|
+
await executeStep(errorStep, ctx);
|
|
1453
|
+
}
|
|
1454
|
+
}
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
1261
1457
|
|
|
1262
1458
|
// ../../node_modules/.pnpm/marked@17.0.1/node_modules/marked/lib/marked.esm.js
|
|
1263
1459
|
function L() {
|
|
@@ -2691,14 +2887,14 @@ function createDOMPurify() {
|
|
|
2691
2887
|
DocumentFragment,
|
|
2692
2888
|
HTMLTemplateElement,
|
|
2693
2889
|
Node: Node2,
|
|
2694
|
-
Element,
|
|
2890
|
+
Element: Element2,
|
|
2695
2891
|
NodeFilter,
|
|
2696
2892
|
NamedNodeMap = window2.NamedNodeMap || window2.MozNamedAttrMap,
|
|
2697
2893
|
HTMLFormElement,
|
|
2698
2894
|
DOMParser,
|
|
2699
2895
|
trustedTypes
|
|
2700
2896
|
} = window2;
|
|
2701
|
-
const ElementPrototype =
|
|
2897
|
+
const ElementPrototype = Element2.prototype;
|
|
2702
2898
|
const cloneNode = lookupGetter(ElementPrototype, "cloneNode");
|
|
2703
2899
|
const remove = lookupGetter(ElementPrototype, "remove");
|
|
2704
2900
|
const getNextSibling = lookupGetter(ElementPrototype, "nextSibling");
|
|
@@ -3152,7 +3348,7 @@ function createDOMPurify() {
|
|
|
3152
3348
|
_forceRemove(currentNode);
|
|
3153
3349
|
return true;
|
|
3154
3350
|
}
|
|
3155
|
-
if (currentNode instanceof
|
|
3351
|
+
if (currentNode instanceof Element2 && !_checkValidNamespace(currentNode)) {
|
|
3156
3352
|
_forceRemove(currentNode);
|
|
3157
3353
|
return true;
|
|
3158
3354
|
}
|
|
@@ -13248,6 +13444,188 @@ function isSvgTag(tag) {
|
|
|
13248
13444
|
function isEventHandler(value) {
|
|
13249
13445
|
return typeof value === "object" && value !== null && "event" in value && "action" in value;
|
|
13250
13446
|
}
|
|
13447
|
+
function debounce(fn, wait, ctx) {
|
|
13448
|
+
let timeoutId = null;
|
|
13449
|
+
const debouncedFn = (event) => {
|
|
13450
|
+
if (timeoutId !== null) {
|
|
13451
|
+
clearTimeout(timeoutId);
|
|
13452
|
+
}
|
|
13453
|
+
timeoutId = setTimeout(() => {
|
|
13454
|
+
timeoutId = null;
|
|
13455
|
+
fn(event);
|
|
13456
|
+
}, wait);
|
|
13457
|
+
};
|
|
13458
|
+
ctx.cleanups?.push(() => {
|
|
13459
|
+
if (timeoutId !== null) {
|
|
13460
|
+
clearTimeout(timeoutId);
|
|
13461
|
+
timeoutId = null;
|
|
13462
|
+
}
|
|
13463
|
+
});
|
|
13464
|
+
return debouncedFn;
|
|
13465
|
+
}
|
|
13466
|
+
function throttle(fn, wait, ctx) {
|
|
13467
|
+
let lastTime = 0;
|
|
13468
|
+
let timeoutId = null;
|
|
13469
|
+
let lastEvent = null;
|
|
13470
|
+
const throttledFn = (event) => {
|
|
13471
|
+
const now = Date.now();
|
|
13472
|
+
const remaining = wait - (now - lastTime);
|
|
13473
|
+
if (remaining <= 0) {
|
|
13474
|
+
if (timeoutId !== null) {
|
|
13475
|
+
clearTimeout(timeoutId);
|
|
13476
|
+
timeoutId = null;
|
|
13477
|
+
}
|
|
13478
|
+
lastTime = now;
|
|
13479
|
+
fn(event);
|
|
13480
|
+
} else {
|
|
13481
|
+
lastEvent = event;
|
|
13482
|
+
if (timeoutId === null) {
|
|
13483
|
+
timeoutId = setTimeout(() => {
|
|
13484
|
+
timeoutId = null;
|
|
13485
|
+
lastTime = Date.now();
|
|
13486
|
+
if (lastEvent) {
|
|
13487
|
+
fn(lastEvent);
|
|
13488
|
+
lastEvent = null;
|
|
13489
|
+
}
|
|
13490
|
+
}, remaining);
|
|
13491
|
+
}
|
|
13492
|
+
}
|
|
13493
|
+
};
|
|
13494
|
+
ctx.cleanups?.push(() => {
|
|
13495
|
+
if (timeoutId !== null) {
|
|
13496
|
+
clearTimeout(timeoutId);
|
|
13497
|
+
timeoutId = null;
|
|
13498
|
+
}
|
|
13499
|
+
});
|
|
13500
|
+
return throttledFn;
|
|
13501
|
+
}
|
|
13502
|
+
function createEventCallback(handler, ctx) {
|
|
13503
|
+
return async (event) => {
|
|
13504
|
+
const action = ctx.actions[handler.action];
|
|
13505
|
+
if (!action) return;
|
|
13506
|
+
const eventLocals = {};
|
|
13507
|
+
const target = event.target;
|
|
13508
|
+
if (target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement || target instanceof HTMLSelectElement) {
|
|
13509
|
+
eventLocals["value"] = target.value;
|
|
13510
|
+
if (target instanceof HTMLInputElement && target.type === "checkbox") {
|
|
13511
|
+
eventLocals["checked"] = target.checked;
|
|
13512
|
+
}
|
|
13513
|
+
if (target instanceof HTMLInputElement && target.type === "file") {
|
|
13514
|
+
eventLocals["files"] = Array.from(target.files || []).map((f) => ({
|
|
13515
|
+
name: f.name,
|
|
13516
|
+
size: f.size,
|
|
13517
|
+
type: f.type,
|
|
13518
|
+
_file: f
|
|
13519
|
+
}));
|
|
13520
|
+
}
|
|
13521
|
+
}
|
|
13522
|
+
if (event instanceof KeyboardEvent) {
|
|
13523
|
+
eventLocals["key"] = event.key;
|
|
13524
|
+
eventLocals["code"] = event.code;
|
|
13525
|
+
eventLocals["ctrlKey"] = event.ctrlKey;
|
|
13526
|
+
eventLocals["shiftKey"] = event.shiftKey;
|
|
13527
|
+
eventLocals["altKey"] = event.altKey;
|
|
13528
|
+
eventLocals["metaKey"] = event.metaKey;
|
|
13529
|
+
}
|
|
13530
|
+
if (event instanceof MouseEvent) {
|
|
13531
|
+
eventLocals["clientX"] = event.clientX;
|
|
13532
|
+
eventLocals["clientY"] = event.clientY;
|
|
13533
|
+
eventLocals["pageX"] = event.pageX;
|
|
13534
|
+
eventLocals["pageY"] = event.pageY;
|
|
13535
|
+
eventLocals["button"] = event.button;
|
|
13536
|
+
}
|
|
13537
|
+
const touchEvent = event;
|
|
13538
|
+
if (touchEvent.touches && touchEvent.changedTouches) {
|
|
13539
|
+
eventLocals["touches"] = Array.from(touchEvent.touches).map((t) => ({
|
|
13540
|
+
clientX: t.clientX,
|
|
13541
|
+
clientY: t.clientY,
|
|
13542
|
+
pageX: t.pageX,
|
|
13543
|
+
pageY: t.pageY
|
|
13544
|
+
}));
|
|
13545
|
+
eventLocals["changedTouches"] = Array.from(touchEvent.changedTouches).map((t) => ({
|
|
13546
|
+
clientX: t.clientX,
|
|
13547
|
+
clientY: t.clientY,
|
|
13548
|
+
pageX: t.pageX,
|
|
13549
|
+
pageY: t.pageY
|
|
13550
|
+
}));
|
|
13551
|
+
}
|
|
13552
|
+
if (handler.event === "scroll" && event.target instanceof Element) {
|
|
13553
|
+
eventLocals["scrollTop"] = event.target.scrollTop;
|
|
13554
|
+
eventLocals["scrollLeft"] = event.target.scrollLeft;
|
|
13555
|
+
}
|
|
13556
|
+
let payload = void 0;
|
|
13557
|
+
if (handler.payload) {
|
|
13558
|
+
payload = evaluatePayload(handler.payload, {
|
|
13559
|
+
state: ctx.state,
|
|
13560
|
+
locals: { ...ctx.locals, ...eventLocals },
|
|
13561
|
+
...ctx.imports && { imports: ctx.imports }
|
|
13562
|
+
});
|
|
13563
|
+
}
|
|
13564
|
+
const actionCtx = {
|
|
13565
|
+
state: ctx.state,
|
|
13566
|
+
actions: ctx.actions,
|
|
13567
|
+
locals: { ...ctx.locals, ...eventLocals, payload },
|
|
13568
|
+
eventPayload: payload
|
|
13569
|
+
};
|
|
13570
|
+
await executeAction(action, actionCtx);
|
|
13571
|
+
};
|
|
13572
|
+
}
|
|
13573
|
+
function wrapWithDebounceThrottle(callback, handler, ctx) {
|
|
13574
|
+
if (handler.debounce !== void 0 && handler.debounce >= 0) {
|
|
13575
|
+
return debounce(callback, handler.debounce, ctx);
|
|
13576
|
+
}
|
|
13577
|
+
if (handler.throttle !== void 0 && handler.throttle >= 0) {
|
|
13578
|
+
return throttle(callback, handler.throttle, ctx);
|
|
13579
|
+
}
|
|
13580
|
+
return callback;
|
|
13581
|
+
}
|
|
13582
|
+
function setupIntersectionObserver(el, handler, ctx) {
|
|
13583
|
+
const options = {};
|
|
13584
|
+
if (handler.options?.threshold !== void 0) {
|
|
13585
|
+
options.threshold = handler.options.threshold;
|
|
13586
|
+
}
|
|
13587
|
+
if (handler.options?.rootMargin !== void 0) {
|
|
13588
|
+
options.rootMargin = handler.options.rootMargin;
|
|
13589
|
+
}
|
|
13590
|
+
let hasTriggered = false;
|
|
13591
|
+
const observer = new IntersectionObserver((entries2) => {
|
|
13592
|
+
for (const entry of entries2) {
|
|
13593
|
+
if (entry.target !== el) continue;
|
|
13594
|
+
if (handler.options?.once && hasTriggered) {
|
|
13595
|
+
continue;
|
|
13596
|
+
}
|
|
13597
|
+
const action = ctx.actions[handler.action];
|
|
13598
|
+
if (!action) continue;
|
|
13599
|
+
const intersectLocals = {
|
|
13600
|
+
isIntersecting: entry.isIntersecting,
|
|
13601
|
+
intersectionRatio: entry.intersectionRatio
|
|
13602
|
+
};
|
|
13603
|
+
let payload = void 0;
|
|
13604
|
+
if (handler.payload) {
|
|
13605
|
+
payload = evaluatePayload(handler.payload, {
|
|
13606
|
+
state: ctx.state,
|
|
13607
|
+
locals: { ...ctx.locals, ...intersectLocals },
|
|
13608
|
+
...ctx.imports && { imports: ctx.imports }
|
|
13609
|
+
});
|
|
13610
|
+
}
|
|
13611
|
+
const actionCtx = {
|
|
13612
|
+
state: ctx.state,
|
|
13613
|
+
actions: ctx.actions,
|
|
13614
|
+
locals: { ...ctx.locals, ...intersectLocals, payload },
|
|
13615
|
+
eventPayload: payload
|
|
13616
|
+
};
|
|
13617
|
+
executeAction(action, actionCtx);
|
|
13618
|
+
if (handler.options?.once) {
|
|
13619
|
+
hasTriggered = true;
|
|
13620
|
+
observer.unobserve(el);
|
|
13621
|
+
}
|
|
13622
|
+
}
|
|
13623
|
+
}, options);
|
|
13624
|
+
observer.observe(el);
|
|
13625
|
+
ctx.cleanups?.push(() => {
|
|
13626
|
+
observer.disconnect();
|
|
13627
|
+
});
|
|
13628
|
+
}
|
|
13251
13629
|
function render(node, ctx) {
|
|
13252
13630
|
switch (node.kind) {
|
|
13253
13631
|
case "element":
|
|
@@ -13262,6 +13640,8 @@ function render(node, ctx) {
|
|
|
13262
13640
|
return renderMarkdown(node, ctx);
|
|
13263
13641
|
case "code":
|
|
13264
13642
|
return renderCode(node, ctx);
|
|
13643
|
+
case "portal":
|
|
13644
|
+
return renderPortal(node, ctx);
|
|
13265
13645
|
case "localState":
|
|
13266
13646
|
return renderLocalState(node, ctx);
|
|
13267
13647
|
default:
|
|
@@ -13284,34 +13664,13 @@ function renderElement(node, ctx) {
|
|
|
13284
13664
|
if (isEventHandler(propValue)) {
|
|
13285
13665
|
const handler = propValue;
|
|
13286
13666
|
const eventName = handler.event;
|
|
13287
|
-
|
|
13288
|
-
|
|
13289
|
-
|
|
13290
|
-
|
|
13291
|
-
|
|
13292
|
-
|
|
13293
|
-
|
|
13294
|
-
if (target instanceof HTMLInputElement && target.type === "checkbox") {
|
|
13295
|
-
eventLocals["checked"] = target.checked;
|
|
13296
|
-
}
|
|
13297
|
-
}
|
|
13298
|
-
let payload = void 0;
|
|
13299
|
-
if (handler.payload) {
|
|
13300
|
-
payload = evaluatePayload(handler.payload, {
|
|
13301
|
-
state: ctx.state,
|
|
13302
|
-
locals: { ...ctx.locals, ...eventLocals },
|
|
13303
|
-
...ctx.imports && { imports: ctx.imports }
|
|
13304
|
-
});
|
|
13305
|
-
}
|
|
13306
|
-
const actionCtx = {
|
|
13307
|
-
state: ctx.state,
|
|
13308
|
-
actions: ctx.actions,
|
|
13309
|
-
locals: { ...ctx.locals, ...eventLocals, payload },
|
|
13310
|
-
eventPayload: payload
|
|
13311
|
-
};
|
|
13312
|
-
await executeAction(action, actionCtx);
|
|
13313
|
-
}
|
|
13314
|
-
});
|
|
13667
|
+
if (eventName === "intersect") {
|
|
13668
|
+
setupIntersectionObserver(el, handler, ctx);
|
|
13669
|
+
} else {
|
|
13670
|
+
const eventCallback = createEventCallback(handler, ctx);
|
|
13671
|
+
const wrappedCallback = wrapWithDebounceThrottle(eventCallback, handler, ctx);
|
|
13672
|
+
el.addEventListener(eventName, wrappedCallback);
|
|
13673
|
+
}
|
|
13315
13674
|
} else {
|
|
13316
13675
|
const cleanup = createEffect(() => {
|
|
13317
13676
|
const value = evaluate(propValue, { state: ctx.state, locals: ctx.locals, ...ctx.imports && { imports: ctx.imports } });
|
|
@@ -13651,6 +14010,41 @@ function renderCode(node, ctx) {
|
|
|
13651
14010
|
ctx.cleanups?.push(cleanup);
|
|
13652
14011
|
return container;
|
|
13653
14012
|
}
|
|
14013
|
+
function renderPortal(node, ctx) {
|
|
14014
|
+
let targetElement = null;
|
|
14015
|
+
if (node.target === "body") {
|
|
14016
|
+
targetElement = document.body;
|
|
14017
|
+
} else if (node.target === "head") {
|
|
14018
|
+
targetElement = document.head;
|
|
14019
|
+
} else {
|
|
14020
|
+
targetElement = document.querySelector(node.target);
|
|
14021
|
+
}
|
|
14022
|
+
if (!targetElement) {
|
|
14023
|
+
return document.createComment("portal:target-not-found");
|
|
14024
|
+
}
|
|
14025
|
+
const portalContainer = document.createElement("div");
|
|
14026
|
+
portalContainer.setAttribute("data-portal", "true");
|
|
14027
|
+
portalContainer.style.display = "contents";
|
|
14028
|
+
const portalCleanups = [];
|
|
14029
|
+
const portalCtx = {
|
|
14030
|
+
...ctx,
|
|
14031
|
+
cleanups: portalCleanups
|
|
14032
|
+
};
|
|
14033
|
+
for (const child of node.children) {
|
|
14034
|
+
const childNode = render(child, portalCtx);
|
|
14035
|
+
portalContainer.appendChild(childNode);
|
|
14036
|
+
}
|
|
14037
|
+
targetElement.appendChild(portalContainer);
|
|
14038
|
+
ctx.cleanups?.push(() => {
|
|
14039
|
+
for (const cleanup of portalCleanups) {
|
|
14040
|
+
cleanup();
|
|
14041
|
+
}
|
|
14042
|
+
if (portalContainer.parentNode) {
|
|
14043
|
+
portalContainer.parentNode.removeChild(portalContainer);
|
|
14044
|
+
}
|
|
14045
|
+
});
|
|
14046
|
+
return document.createComment("portal");
|
|
14047
|
+
}
|
|
13654
14048
|
function createLocalStateStore(stateDefs) {
|
|
13655
14049
|
const signals = {};
|
|
13656
14050
|
for (const [name, def] of Object.entries(stateDefs)) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@constela/runtime",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.0",
|
|
4
4
|
"description": "Runtime DOM renderer for Constela UI framework",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -18,8 +18,8 @@
|
|
|
18
18
|
"dompurify": "^3.3.1",
|
|
19
19
|
"marked": "^17.0.1",
|
|
20
20
|
"shiki": "^3.20.0",
|
|
21
|
-
"@constela/compiler": "0.
|
|
22
|
-
"@constela/core": "0.
|
|
21
|
+
"@constela/compiler": "0.11.1",
|
|
22
|
+
"@constela/core": "0.12.0"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
25
|
"@types/dompurify": "^3.2.0",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"tsup": "^8.0.0",
|
|
30
30
|
"typescript": "^5.3.0",
|
|
31
31
|
"vitest": "^2.0.0",
|
|
32
|
-
"@constela/server": "
|
|
32
|
+
"@constela/server": "8.0.0"
|
|
33
33
|
},
|
|
34
34
|
"engines": {
|
|
35
35
|
"node": ">=20.0.0"
|