@constela/runtime 0.12.2 → 0.14.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 +29 -2
- package/dist/index.js +599 -39
- package/package.json +4 -4
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CompiledExpression, CompiledAction, CompiledNode, CompiledProgram } from '@constela/compiler';
|
|
1
|
+
import { CompiledExpression, CompiledAction, CompiledNode, CompiledLocalAction, CompiledProgram } from '@constela/compiler';
|
|
2
2
|
|
|
3
3
|
interface Signal<T> {
|
|
4
4
|
get(): T;
|
|
@@ -254,6 +254,20 @@ declare function createConnectionManager(): ConnectionManager;
|
|
|
254
254
|
* - navigate: Page navigation
|
|
255
255
|
*/
|
|
256
256
|
|
|
257
|
+
/**
|
|
258
|
+
* Local state store interface for component-level state
|
|
259
|
+
*/
|
|
260
|
+
interface LocalStateStore$1 {
|
|
261
|
+
get(name: string): unknown;
|
|
262
|
+
set(name: string, value: unknown): void;
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Extended action type with local action metadata
|
|
266
|
+
*/
|
|
267
|
+
interface ExtendedAction extends CompiledAction {
|
|
268
|
+
_isLocalAction?: boolean;
|
|
269
|
+
_localStore?: LocalStateStore$1;
|
|
270
|
+
}
|
|
257
271
|
interface ActionContext {
|
|
258
272
|
state: StateStore;
|
|
259
273
|
actions: Record<string, CompiledAction>;
|
|
@@ -261,6 +275,7 @@ interface ActionContext {
|
|
|
261
275
|
eventPayload?: unknown;
|
|
262
276
|
refs?: Record<string, Element>;
|
|
263
277
|
subscriptions?: (() => void)[];
|
|
278
|
+
cleanups?: (() => void)[];
|
|
264
279
|
route?: {
|
|
265
280
|
params: Record<string, string>;
|
|
266
281
|
query: Record<string, string>;
|
|
@@ -269,7 +284,7 @@ interface ActionContext {
|
|
|
269
284
|
imports?: Record<string, unknown>;
|
|
270
285
|
connections?: ConnectionManager;
|
|
271
286
|
}
|
|
272
|
-
declare function executeAction(action: CompiledAction, ctx: ActionContext): Promise<void>;
|
|
287
|
+
declare function executeAction(action: CompiledAction | ExtendedAction, ctx: ActionContext): Promise<void>;
|
|
273
288
|
|
|
274
289
|
/**
|
|
275
290
|
* Renderer - DOM rendering for compiled view nodes
|
|
@@ -281,6 +296,14 @@ declare function executeAction(action: CompiledAction, ctx: ActionContext): Prom
|
|
|
281
296
|
* - each: List rendering with reactive updates
|
|
282
297
|
*/
|
|
283
298
|
|
|
299
|
+
/**
|
|
300
|
+
* Local state store interface for component-level state
|
|
301
|
+
*/
|
|
302
|
+
interface LocalStateStore {
|
|
303
|
+
get(name: string): unknown;
|
|
304
|
+
set(name: string, value: unknown): void;
|
|
305
|
+
signals: Record<string, Signal<unknown>>;
|
|
306
|
+
}
|
|
284
307
|
interface RenderContext {
|
|
285
308
|
state: StateStore;
|
|
286
309
|
actions: Record<string, CompiledAction>;
|
|
@@ -289,6 +312,10 @@ interface RenderContext {
|
|
|
289
312
|
cleanups?: (() => void)[];
|
|
290
313
|
refs?: Record<string, Element>;
|
|
291
314
|
inSvg?: boolean;
|
|
315
|
+
localState?: {
|
|
316
|
+
store: LocalStateStore;
|
|
317
|
+
actions: Record<string, CompiledLocalAction>;
|
|
318
|
+
};
|
|
292
319
|
}
|
|
293
320
|
declare function render(node: CompiledNode, ctx: RenderContext): Node;
|
|
294
321
|
|
package/dist/index.js
CHANGED
|
@@ -428,6 +428,18 @@ function evaluate(expr, ctx) {
|
|
|
428
428
|
return val == null ? "" : String(val);
|
|
429
429
|
}).join("");
|
|
430
430
|
}
|
|
431
|
+
case "validity": {
|
|
432
|
+
const element2 = ctx.refs?.[expr.ref];
|
|
433
|
+
if (!element2) return null;
|
|
434
|
+
const formElement = element2;
|
|
435
|
+
if (!formElement.validity) return null;
|
|
436
|
+
const validity = formElement.validity;
|
|
437
|
+
const property = expr.property || "valid";
|
|
438
|
+
if (property === "message") {
|
|
439
|
+
return formElement.validationMessage || "";
|
|
440
|
+
}
|
|
441
|
+
return validity[property] ?? null;
|
|
442
|
+
}
|
|
431
443
|
default: {
|
|
432
444
|
const _exhaustiveCheck = expr;
|
|
433
445
|
throw new Error(`Unknown expression type: ${JSON.stringify(_exhaustiveCheck)}`);
|
|
@@ -590,38 +602,58 @@ function createEvalContext(ctx) {
|
|
|
590
602
|
};
|
|
591
603
|
}
|
|
592
604
|
async function executeAction(action, ctx) {
|
|
605
|
+
const extAction = action;
|
|
606
|
+
const isLocal = extAction._isLocalAction && extAction._localStore;
|
|
607
|
+
const localStore = extAction._localStore;
|
|
608
|
+
const delayPromises = [];
|
|
593
609
|
for (const step of action.steps) {
|
|
594
610
|
if (step.do === "set" || step.do === "update" || step.do === "setPath") {
|
|
595
|
-
executeStepSync(step, ctx);
|
|
611
|
+
executeStepSync(step, ctx, isLocal ? localStore : void 0);
|
|
596
612
|
} else if (step.do === "if") {
|
|
597
|
-
await executeIfStep(step, ctx);
|
|
613
|
+
await executeIfStep(step, ctx, isLocal ? localStore : void 0);
|
|
614
|
+
} else if (step.do === "delay") {
|
|
615
|
+
const delayPromise = executeDelayStep(step, ctx);
|
|
616
|
+
delayPromises.push(delayPromise);
|
|
617
|
+
} else if (step.do === "interval") {
|
|
618
|
+
await executeIntervalStep(step, ctx);
|
|
598
619
|
} else {
|
|
599
620
|
await executeStep(step, ctx);
|
|
600
621
|
}
|
|
601
622
|
}
|
|
623
|
+
if (delayPromises.length > 0) {
|
|
624
|
+
await Promise.all(delayPromises);
|
|
625
|
+
}
|
|
602
626
|
}
|
|
603
|
-
function executeStepSync(step, ctx) {
|
|
627
|
+
function executeStepSync(step, ctx, localStore) {
|
|
604
628
|
switch (step.do) {
|
|
605
629
|
case "set":
|
|
606
|
-
|
|
630
|
+
if (localStore) {
|
|
631
|
+
executeLocalSetStepSync(step.target, step.value, ctx, localStore);
|
|
632
|
+
} else {
|
|
633
|
+
executeSetStepSync(step.target, step.value, ctx);
|
|
634
|
+
}
|
|
607
635
|
break;
|
|
608
636
|
case "update":
|
|
609
|
-
|
|
637
|
+
if (localStore) {
|
|
638
|
+
executeLocalUpdateStepSync(step, ctx, localStore);
|
|
639
|
+
} else {
|
|
640
|
+
executeUpdateStepSync(step, ctx);
|
|
641
|
+
}
|
|
610
642
|
break;
|
|
611
643
|
case "setPath":
|
|
612
644
|
executeSetPathStepSync(step, ctx);
|
|
613
645
|
break;
|
|
614
646
|
}
|
|
615
647
|
}
|
|
616
|
-
async function executeIfStep(step, ctx) {
|
|
648
|
+
async function executeIfStep(step, ctx, localStore) {
|
|
617
649
|
const evalCtx = createEvalContext(ctx);
|
|
618
650
|
const condition = evaluate(step.condition, evalCtx);
|
|
619
651
|
const stepsToExecute = condition ? step.then : step.else || [];
|
|
620
652
|
for (const nestedStep of stepsToExecute) {
|
|
621
653
|
if (nestedStep.do === "set" || nestedStep.do === "update" || nestedStep.do === "setPath") {
|
|
622
|
-
executeStepSync(nestedStep, ctx);
|
|
654
|
+
executeStepSync(nestedStep, ctx, localStore);
|
|
623
655
|
} else if (nestedStep.do === "if") {
|
|
624
|
-
await executeIfStep(nestedStep, ctx);
|
|
656
|
+
await executeIfStep(nestedStep, ctx, localStore);
|
|
625
657
|
} else {
|
|
626
658
|
await executeStep(nestedStep, ctx);
|
|
627
659
|
}
|
|
@@ -718,6 +750,97 @@ function executeUpdateStepSync(step, ctx) {
|
|
|
718
750
|
}
|
|
719
751
|
}
|
|
720
752
|
}
|
|
753
|
+
function executeLocalSetStepSync(target, value, ctx, localStore) {
|
|
754
|
+
const evalCtx = createEvalContext(ctx);
|
|
755
|
+
const newValue = evaluate(value, evalCtx);
|
|
756
|
+
localStore.set(target, newValue);
|
|
757
|
+
}
|
|
758
|
+
function executeLocalUpdateStepSync(step, ctx, localStore) {
|
|
759
|
+
const { target, operation, value } = step;
|
|
760
|
+
const evalCtx = createEvalContext(ctx);
|
|
761
|
+
const currentValue = localStore.get(target);
|
|
762
|
+
switch (operation) {
|
|
763
|
+
case "toggle": {
|
|
764
|
+
const current = typeof currentValue === "boolean" ? currentValue : false;
|
|
765
|
+
localStore.set(target, !current);
|
|
766
|
+
break;
|
|
767
|
+
}
|
|
768
|
+
case "increment": {
|
|
769
|
+
const evalResult = value ? evaluate(value, evalCtx) : 1;
|
|
770
|
+
const amount = typeof evalResult === "number" ? evalResult : 1;
|
|
771
|
+
const current = typeof currentValue === "number" ? currentValue : 0;
|
|
772
|
+
localStore.set(target, current + amount);
|
|
773
|
+
break;
|
|
774
|
+
}
|
|
775
|
+
case "decrement": {
|
|
776
|
+
const evalResult = value ? evaluate(value, evalCtx) : 1;
|
|
777
|
+
const amount = typeof evalResult === "number" ? evalResult : 1;
|
|
778
|
+
const current = typeof currentValue === "number" ? currentValue : 0;
|
|
779
|
+
localStore.set(target, current - amount);
|
|
780
|
+
break;
|
|
781
|
+
}
|
|
782
|
+
case "push": {
|
|
783
|
+
const item = value ? evaluate(value, evalCtx) : void 0;
|
|
784
|
+
const arr = Array.isArray(currentValue) ? currentValue : [];
|
|
785
|
+
localStore.set(target, [...arr, item]);
|
|
786
|
+
break;
|
|
787
|
+
}
|
|
788
|
+
case "pop": {
|
|
789
|
+
const arr = Array.isArray(currentValue) ? currentValue : [];
|
|
790
|
+
localStore.set(target, arr.slice(0, -1));
|
|
791
|
+
break;
|
|
792
|
+
}
|
|
793
|
+
case "remove": {
|
|
794
|
+
const removeValue = value ? evaluate(value, evalCtx) : void 0;
|
|
795
|
+
const arr = Array.isArray(currentValue) ? currentValue : [];
|
|
796
|
+
if (typeof removeValue === "number") {
|
|
797
|
+
localStore.set(target, arr.filter((_2, i) => i !== removeValue));
|
|
798
|
+
} else {
|
|
799
|
+
localStore.set(target, arr.filter((x2) => x2 !== removeValue));
|
|
800
|
+
}
|
|
801
|
+
break;
|
|
802
|
+
}
|
|
803
|
+
case "merge": {
|
|
804
|
+
const evalResult = value ? evaluate(value, evalCtx) : {};
|
|
805
|
+
const mergeValue = typeof evalResult === "object" && evalResult !== null ? evalResult : {};
|
|
806
|
+
const current = typeof currentValue === "object" && currentValue !== null ? currentValue : {};
|
|
807
|
+
localStore.set(target, { ...current, ...mergeValue });
|
|
808
|
+
break;
|
|
809
|
+
}
|
|
810
|
+
case "replaceAt": {
|
|
811
|
+
const idx = step.index ? evaluate(step.index, evalCtx) : 0;
|
|
812
|
+
const newValue = value ? evaluate(value, evalCtx) : void 0;
|
|
813
|
+
const arr = Array.isArray(currentValue) ? [...currentValue] : [];
|
|
814
|
+
if (typeof idx === "number" && idx >= 0 && idx < arr.length) {
|
|
815
|
+
arr[idx] = newValue;
|
|
816
|
+
}
|
|
817
|
+
localStore.set(target, arr);
|
|
818
|
+
break;
|
|
819
|
+
}
|
|
820
|
+
case "insertAt": {
|
|
821
|
+
const idx = step.index ? evaluate(step.index, evalCtx) : 0;
|
|
822
|
+
const newValue = value ? evaluate(value, evalCtx) : void 0;
|
|
823
|
+
const arr = Array.isArray(currentValue) ? [...currentValue] : [];
|
|
824
|
+
if (typeof idx === "number" && idx >= 0) {
|
|
825
|
+
arr.splice(idx, 0, newValue);
|
|
826
|
+
}
|
|
827
|
+
localStore.set(target, arr);
|
|
828
|
+
break;
|
|
829
|
+
}
|
|
830
|
+
case "splice": {
|
|
831
|
+
const idx = step.index ? evaluate(step.index, evalCtx) : 0;
|
|
832
|
+
const delCount = step.deleteCount ? evaluate(step.deleteCount, evalCtx) : 0;
|
|
833
|
+
const items = value ? evaluate(value, evalCtx) : [];
|
|
834
|
+
const arr = Array.isArray(currentValue) ? [...currentValue] : [];
|
|
835
|
+
if (typeof idx === "number" && typeof delCount === "number") {
|
|
836
|
+
const insertItems = Array.isArray(items) ? items : [];
|
|
837
|
+
arr.splice(idx, delCount, ...insertItems);
|
|
838
|
+
}
|
|
839
|
+
localStore.set(target, arr);
|
|
840
|
+
break;
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
}
|
|
721
844
|
function executeSetPathStepSync(step, ctx) {
|
|
722
845
|
const evalCtx = createEvalContext(ctx);
|
|
723
846
|
const pathValue = evaluate(step.path, evalCtx);
|
|
@@ -790,6 +913,18 @@ async function executeStep(step, ctx) {
|
|
|
790
913
|
case "close":
|
|
791
914
|
await executeCloseStep(step, ctx);
|
|
792
915
|
break;
|
|
916
|
+
case "delay":
|
|
917
|
+
await executeDelayStep(step, ctx);
|
|
918
|
+
break;
|
|
919
|
+
case "interval":
|
|
920
|
+
await executeIntervalStep(step, ctx);
|
|
921
|
+
break;
|
|
922
|
+
case "clearTimer":
|
|
923
|
+
await executeClearTimerStep(step, ctx);
|
|
924
|
+
break;
|
|
925
|
+
case "focus":
|
|
926
|
+
await executeFocusStep(step, ctx);
|
|
927
|
+
break;
|
|
793
928
|
}
|
|
794
929
|
}
|
|
795
930
|
async function executeSetStep(target, value, ctx) {
|
|
@@ -1156,6 +1291,137 @@ async function executeCloseStep(step, ctx) {
|
|
|
1156
1291
|
if (!ctx.connections) return;
|
|
1157
1292
|
ctx.connections.close(step.connection);
|
|
1158
1293
|
}
|
|
1294
|
+
async function executeDelayStep(step, ctx) {
|
|
1295
|
+
const evalCtx = createEvalContext(ctx);
|
|
1296
|
+
const msValue = evaluate(step.ms, evalCtx);
|
|
1297
|
+
const ms = typeof msValue === "number" ? Math.max(0, msValue) : 0;
|
|
1298
|
+
return new Promise((resolve) => {
|
|
1299
|
+
let resolved = false;
|
|
1300
|
+
const timeoutId = setTimeout(async () => {
|
|
1301
|
+
if (resolved) return;
|
|
1302
|
+
resolved = true;
|
|
1303
|
+
for (const thenStep of step.then) {
|
|
1304
|
+
if (thenStep.do === "set" || thenStep.do === "update" || thenStep.do === "setPath") {
|
|
1305
|
+
executeStepSync(thenStep, ctx);
|
|
1306
|
+
} else if (thenStep.do === "if") {
|
|
1307
|
+
await executeIfStep(thenStep, ctx);
|
|
1308
|
+
} else {
|
|
1309
|
+
await executeStep(thenStep, ctx);
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1312
|
+
resolve();
|
|
1313
|
+
}, ms);
|
|
1314
|
+
const numericId = typeof timeoutId === "number" ? timeoutId : Number(timeoutId);
|
|
1315
|
+
if (step.result) {
|
|
1316
|
+
ctx.locals[step.result] = numericId;
|
|
1317
|
+
}
|
|
1318
|
+
if (!ctx.locals["_timerResolvers"]) {
|
|
1319
|
+
ctx.locals["_timerResolvers"] = /* @__PURE__ */ new Map();
|
|
1320
|
+
}
|
|
1321
|
+
ctx.locals["_timerResolvers"].set(numericId, () => {
|
|
1322
|
+
if (!resolved) {
|
|
1323
|
+
resolved = true;
|
|
1324
|
+
clearTimeout(timeoutId);
|
|
1325
|
+
resolve();
|
|
1326
|
+
}
|
|
1327
|
+
});
|
|
1328
|
+
if (ctx.cleanups) {
|
|
1329
|
+
ctx.cleanups.push(() => {
|
|
1330
|
+
if (!resolved) {
|
|
1331
|
+
resolved = true;
|
|
1332
|
+
clearTimeout(timeoutId);
|
|
1333
|
+
resolve();
|
|
1334
|
+
}
|
|
1335
|
+
});
|
|
1336
|
+
}
|
|
1337
|
+
});
|
|
1338
|
+
}
|
|
1339
|
+
async function executeIntervalStep(step, ctx) {
|
|
1340
|
+
const evalCtx = createEvalContext(ctx);
|
|
1341
|
+
const msValue = evaluate(step.ms, evalCtx);
|
|
1342
|
+
const ms = typeof msValue === "number" ? Math.max(0, msValue) : 0;
|
|
1343
|
+
const intervalId = setInterval(async () => {
|
|
1344
|
+
const action = ctx.actions[step.action];
|
|
1345
|
+
if (action) {
|
|
1346
|
+
await executeAction(action, ctx);
|
|
1347
|
+
}
|
|
1348
|
+
}, ms);
|
|
1349
|
+
const numericId = typeof intervalId === "number" ? intervalId : Number(intervalId);
|
|
1350
|
+
if (step.result) {
|
|
1351
|
+
ctx.locals[step.result] = numericId;
|
|
1352
|
+
}
|
|
1353
|
+
if (ctx.cleanups) {
|
|
1354
|
+
ctx.cleanups.push(() => clearInterval(intervalId));
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
async function executeClearTimerStep(step, ctx) {
|
|
1358
|
+
const evalCtx = createEvalContext(ctx);
|
|
1359
|
+
const timerId = evaluate(step.target, evalCtx);
|
|
1360
|
+
if (timerId == null) {
|
|
1361
|
+
return;
|
|
1362
|
+
}
|
|
1363
|
+
const numericId = typeof timerId === "number" ? timerId : Number(timerId);
|
|
1364
|
+
const timerResolvers = ctx.locals["_timerResolvers"];
|
|
1365
|
+
if (timerResolvers?.has(numericId)) {
|
|
1366
|
+
const resolver = timerResolvers.get(numericId);
|
|
1367
|
+
if (resolver) {
|
|
1368
|
+
resolver();
|
|
1369
|
+
}
|
|
1370
|
+
timerResolvers.delete(numericId);
|
|
1371
|
+
}
|
|
1372
|
+
clearTimeout(timerId);
|
|
1373
|
+
clearInterval(timerId);
|
|
1374
|
+
}
|
|
1375
|
+
async function executeFocusStep(step, ctx) {
|
|
1376
|
+
const evalCtx = createEvalContext(ctx);
|
|
1377
|
+
const targetValue = evaluate(step.target, evalCtx);
|
|
1378
|
+
let element2;
|
|
1379
|
+
if (targetValue instanceof Element) {
|
|
1380
|
+
element2 = targetValue;
|
|
1381
|
+
} else if (typeof targetValue === "string") {
|
|
1382
|
+
element2 = ctx.refs?.[targetValue];
|
|
1383
|
+
}
|
|
1384
|
+
try {
|
|
1385
|
+
if (!element2) {
|
|
1386
|
+
const refName = typeof targetValue === "string" ? targetValue : "unknown";
|
|
1387
|
+
throw new Error(`Ref "${refName}" not found`);
|
|
1388
|
+
}
|
|
1389
|
+
switch (step.operation) {
|
|
1390
|
+
case "focus":
|
|
1391
|
+
if (typeof element2.focus === "function") {
|
|
1392
|
+
element2.focus();
|
|
1393
|
+
}
|
|
1394
|
+
break;
|
|
1395
|
+
case "blur":
|
|
1396
|
+
if (typeof element2.blur === "function") {
|
|
1397
|
+
element2.blur();
|
|
1398
|
+
}
|
|
1399
|
+
break;
|
|
1400
|
+
case "select":
|
|
1401
|
+
if (typeof element2.select === "function") {
|
|
1402
|
+
element2.select();
|
|
1403
|
+
} else {
|
|
1404
|
+
throw new Error(`Element does not support select operation`);
|
|
1405
|
+
}
|
|
1406
|
+
break;
|
|
1407
|
+
}
|
|
1408
|
+
if (step.onSuccess) {
|
|
1409
|
+
for (const successStep of step.onSuccess) {
|
|
1410
|
+
await executeStep(successStep, ctx);
|
|
1411
|
+
}
|
|
1412
|
+
}
|
|
1413
|
+
} catch (err) {
|
|
1414
|
+
ctx.locals["error"] = {
|
|
1415
|
+
message: err instanceof Error ? err.message : String(err),
|
|
1416
|
+
name: err instanceof Error ? err.name : "Error"
|
|
1417
|
+
};
|
|
1418
|
+
if (step.onError) {
|
|
1419
|
+
for (const errorStep of step.onError) {
|
|
1420
|
+
await executeStep(errorStep, ctx);
|
|
1421
|
+
}
|
|
1422
|
+
}
|
|
1423
|
+
}
|
|
1424
|
+
}
|
|
1159
1425
|
|
|
1160
1426
|
// ../../node_modules/.pnpm/marked@17.0.1/node_modules/marked/lib/marked.esm.js
|
|
1161
1427
|
function L() {
|
|
@@ -2589,14 +2855,14 @@ function createDOMPurify() {
|
|
|
2589
2855
|
DocumentFragment,
|
|
2590
2856
|
HTMLTemplateElement,
|
|
2591
2857
|
Node: Node2,
|
|
2592
|
-
Element,
|
|
2858
|
+
Element: Element2,
|
|
2593
2859
|
NodeFilter,
|
|
2594
2860
|
NamedNodeMap = window2.NamedNodeMap || window2.MozNamedAttrMap,
|
|
2595
2861
|
HTMLFormElement,
|
|
2596
2862
|
DOMParser,
|
|
2597
2863
|
trustedTypes
|
|
2598
2864
|
} = window2;
|
|
2599
|
-
const ElementPrototype =
|
|
2865
|
+
const ElementPrototype = Element2.prototype;
|
|
2600
2866
|
const cloneNode = lookupGetter(ElementPrototype, "cloneNode");
|
|
2601
2867
|
const remove = lookupGetter(ElementPrototype, "remove");
|
|
2602
2868
|
const getNextSibling = lookupGetter(ElementPrototype, "nextSibling");
|
|
@@ -3050,7 +3316,7 @@ function createDOMPurify() {
|
|
|
3050
3316
|
_forceRemove(currentNode);
|
|
3051
3317
|
return true;
|
|
3052
3318
|
}
|
|
3053
|
-
if (currentNode instanceof
|
|
3319
|
+
if (currentNode instanceof Element2 && !_checkValidNamespace(currentNode)) {
|
|
3054
3320
|
_forceRemove(currentNode);
|
|
3055
3321
|
return true;
|
|
3056
3322
|
}
|
|
@@ -13146,6 +13412,188 @@ function isSvgTag(tag) {
|
|
|
13146
13412
|
function isEventHandler(value) {
|
|
13147
13413
|
return typeof value === "object" && value !== null && "event" in value && "action" in value;
|
|
13148
13414
|
}
|
|
13415
|
+
function debounce(fn, wait, ctx) {
|
|
13416
|
+
let timeoutId = null;
|
|
13417
|
+
const debouncedFn = (event) => {
|
|
13418
|
+
if (timeoutId !== null) {
|
|
13419
|
+
clearTimeout(timeoutId);
|
|
13420
|
+
}
|
|
13421
|
+
timeoutId = setTimeout(() => {
|
|
13422
|
+
timeoutId = null;
|
|
13423
|
+
fn(event);
|
|
13424
|
+
}, wait);
|
|
13425
|
+
};
|
|
13426
|
+
ctx.cleanups?.push(() => {
|
|
13427
|
+
if (timeoutId !== null) {
|
|
13428
|
+
clearTimeout(timeoutId);
|
|
13429
|
+
timeoutId = null;
|
|
13430
|
+
}
|
|
13431
|
+
});
|
|
13432
|
+
return debouncedFn;
|
|
13433
|
+
}
|
|
13434
|
+
function throttle(fn, wait, ctx) {
|
|
13435
|
+
let lastTime = 0;
|
|
13436
|
+
let timeoutId = null;
|
|
13437
|
+
let lastEvent = null;
|
|
13438
|
+
const throttledFn = (event) => {
|
|
13439
|
+
const now = Date.now();
|
|
13440
|
+
const remaining = wait - (now - lastTime);
|
|
13441
|
+
if (remaining <= 0) {
|
|
13442
|
+
if (timeoutId !== null) {
|
|
13443
|
+
clearTimeout(timeoutId);
|
|
13444
|
+
timeoutId = null;
|
|
13445
|
+
}
|
|
13446
|
+
lastTime = now;
|
|
13447
|
+
fn(event);
|
|
13448
|
+
} else {
|
|
13449
|
+
lastEvent = event;
|
|
13450
|
+
if (timeoutId === null) {
|
|
13451
|
+
timeoutId = setTimeout(() => {
|
|
13452
|
+
timeoutId = null;
|
|
13453
|
+
lastTime = Date.now();
|
|
13454
|
+
if (lastEvent) {
|
|
13455
|
+
fn(lastEvent);
|
|
13456
|
+
lastEvent = null;
|
|
13457
|
+
}
|
|
13458
|
+
}, remaining);
|
|
13459
|
+
}
|
|
13460
|
+
}
|
|
13461
|
+
};
|
|
13462
|
+
ctx.cleanups?.push(() => {
|
|
13463
|
+
if (timeoutId !== null) {
|
|
13464
|
+
clearTimeout(timeoutId);
|
|
13465
|
+
timeoutId = null;
|
|
13466
|
+
}
|
|
13467
|
+
});
|
|
13468
|
+
return throttledFn;
|
|
13469
|
+
}
|
|
13470
|
+
function createEventCallback(handler, ctx) {
|
|
13471
|
+
return async (event) => {
|
|
13472
|
+
const action = ctx.actions[handler.action];
|
|
13473
|
+
if (!action) return;
|
|
13474
|
+
const eventLocals = {};
|
|
13475
|
+
const target = event.target;
|
|
13476
|
+
if (target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement || target instanceof HTMLSelectElement) {
|
|
13477
|
+
eventLocals["value"] = target.value;
|
|
13478
|
+
if (target instanceof HTMLInputElement && target.type === "checkbox") {
|
|
13479
|
+
eventLocals["checked"] = target.checked;
|
|
13480
|
+
}
|
|
13481
|
+
if (target instanceof HTMLInputElement && target.type === "file") {
|
|
13482
|
+
eventLocals["files"] = Array.from(target.files || []).map((f) => ({
|
|
13483
|
+
name: f.name,
|
|
13484
|
+
size: f.size,
|
|
13485
|
+
type: f.type,
|
|
13486
|
+
_file: f
|
|
13487
|
+
}));
|
|
13488
|
+
}
|
|
13489
|
+
}
|
|
13490
|
+
if (event instanceof KeyboardEvent) {
|
|
13491
|
+
eventLocals["key"] = event.key;
|
|
13492
|
+
eventLocals["code"] = event.code;
|
|
13493
|
+
eventLocals["ctrlKey"] = event.ctrlKey;
|
|
13494
|
+
eventLocals["shiftKey"] = event.shiftKey;
|
|
13495
|
+
eventLocals["altKey"] = event.altKey;
|
|
13496
|
+
eventLocals["metaKey"] = event.metaKey;
|
|
13497
|
+
}
|
|
13498
|
+
if (event instanceof MouseEvent) {
|
|
13499
|
+
eventLocals["clientX"] = event.clientX;
|
|
13500
|
+
eventLocals["clientY"] = event.clientY;
|
|
13501
|
+
eventLocals["pageX"] = event.pageX;
|
|
13502
|
+
eventLocals["pageY"] = event.pageY;
|
|
13503
|
+
eventLocals["button"] = event.button;
|
|
13504
|
+
}
|
|
13505
|
+
const touchEvent = event;
|
|
13506
|
+
if (touchEvent.touches && touchEvent.changedTouches) {
|
|
13507
|
+
eventLocals["touches"] = Array.from(touchEvent.touches).map((t) => ({
|
|
13508
|
+
clientX: t.clientX,
|
|
13509
|
+
clientY: t.clientY,
|
|
13510
|
+
pageX: t.pageX,
|
|
13511
|
+
pageY: t.pageY
|
|
13512
|
+
}));
|
|
13513
|
+
eventLocals["changedTouches"] = Array.from(touchEvent.changedTouches).map((t) => ({
|
|
13514
|
+
clientX: t.clientX,
|
|
13515
|
+
clientY: t.clientY,
|
|
13516
|
+
pageX: t.pageX,
|
|
13517
|
+
pageY: t.pageY
|
|
13518
|
+
}));
|
|
13519
|
+
}
|
|
13520
|
+
if (handler.event === "scroll" && event.target instanceof Element) {
|
|
13521
|
+
eventLocals["scrollTop"] = event.target.scrollTop;
|
|
13522
|
+
eventLocals["scrollLeft"] = event.target.scrollLeft;
|
|
13523
|
+
}
|
|
13524
|
+
let payload = void 0;
|
|
13525
|
+
if (handler.payload) {
|
|
13526
|
+
payload = evaluatePayload(handler.payload, {
|
|
13527
|
+
state: ctx.state,
|
|
13528
|
+
locals: { ...ctx.locals, ...eventLocals },
|
|
13529
|
+
...ctx.imports && { imports: ctx.imports }
|
|
13530
|
+
});
|
|
13531
|
+
}
|
|
13532
|
+
const actionCtx = {
|
|
13533
|
+
state: ctx.state,
|
|
13534
|
+
actions: ctx.actions,
|
|
13535
|
+
locals: { ...ctx.locals, ...eventLocals, payload },
|
|
13536
|
+
eventPayload: payload
|
|
13537
|
+
};
|
|
13538
|
+
await executeAction(action, actionCtx);
|
|
13539
|
+
};
|
|
13540
|
+
}
|
|
13541
|
+
function wrapWithDebounceThrottle(callback, handler, ctx) {
|
|
13542
|
+
if (handler.debounce !== void 0 && handler.debounce >= 0) {
|
|
13543
|
+
return debounce(callback, handler.debounce, ctx);
|
|
13544
|
+
}
|
|
13545
|
+
if (handler.throttle !== void 0 && handler.throttle >= 0) {
|
|
13546
|
+
return throttle(callback, handler.throttle, ctx);
|
|
13547
|
+
}
|
|
13548
|
+
return callback;
|
|
13549
|
+
}
|
|
13550
|
+
function setupIntersectionObserver(el, handler, ctx) {
|
|
13551
|
+
const options = {};
|
|
13552
|
+
if (handler.options?.threshold !== void 0) {
|
|
13553
|
+
options.threshold = handler.options.threshold;
|
|
13554
|
+
}
|
|
13555
|
+
if (handler.options?.rootMargin !== void 0) {
|
|
13556
|
+
options.rootMargin = handler.options.rootMargin;
|
|
13557
|
+
}
|
|
13558
|
+
let hasTriggered = false;
|
|
13559
|
+
const observer = new IntersectionObserver((entries2) => {
|
|
13560
|
+
for (const entry of entries2) {
|
|
13561
|
+
if (entry.target !== el) continue;
|
|
13562
|
+
if (handler.options?.once && hasTriggered) {
|
|
13563
|
+
continue;
|
|
13564
|
+
}
|
|
13565
|
+
const action = ctx.actions[handler.action];
|
|
13566
|
+
if (!action) continue;
|
|
13567
|
+
const intersectLocals = {
|
|
13568
|
+
isIntersecting: entry.isIntersecting,
|
|
13569
|
+
intersectionRatio: entry.intersectionRatio
|
|
13570
|
+
};
|
|
13571
|
+
let payload = void 0;
|
|
13572
|
+
if (handler.payload) {
|
|
13573
|
+
payload = evaluatePayload(handler.payload, {
|
|
13574
|
+
state: ctx.state,
|
|
13575
|
+
locals: { ...ctx.locals, ...intersectLocals },
|
|
13576
|
+
...ctx.imports && { imports: ctx.imports }
|
|
13577
|
+
});
|
|
13578
|
+
}
|
|
13579
|
+
const actionCtx = {
|
|
13580
|
+
state: ctx.state,
|
|
13581
|
+
actions: ctx.actions,
|
|
13582
|
+
locals: { ...ctx.locals, ...intersectLocals, payload },
|
|
13583
|
+
eventPayload: payload
|
|
13584
|
+
};
|
|
13585
|
+
executeAction(action, actionCtx);
|
|
13586
|
+
if (handler.options?.once) {
|
|
13587
|
+
hasTriggered = true;
|
|
13588
|
+
observer.unobserve(el);
|
|
13589
|
+
}
|
|
13590
|
+
}
|
|
13591
|
+
}, options);
|
|
13592
|
+
observer.observe(el);
|
|
13593
|
+
ctx.cleanups?.push(() => {
|
|
13594
|
+
observer.disconnect();
|
|
13595
|
+
});
|
|
13596
|
+
}
|
|
13149
13597
|
function render(node, ctx) {
|
|
13150
13598
|
switch (node.kind) {
|
|
13151
13599
|
case "element":
|
|
@@ -13160,6 +13608,10 @@ function render(node, ctx) {
|
|
|
13160
13608
|
return renderMarkdown(node, ctx);
|
|
13161
13609
|
case "code":
|
|
13162
13610
|
return renderCode(node, ctx);
|
|
13611
|
+
case "portal":
|
|
13612
|
+
return renderPortal(node, ctx);
|
|
13613
|
+
case "localState":
|
|
13614
|
+
return renderLocalState(node, ctx);
|
|
13163
13615
|
default:
|
|
13164
13616
|
throw new Error("Unknown node kind");
|
|
13165
13617
|
}
|
|
@@ -13180,34 +13632,13 @@ function renderElement(node, ctx) {
|
|
|
13180
13632
|
if (isEventHandler(propValue)) {
|
|
13181
13633
|
const handler = propValue;
|
|
13182
13634
|
const eventName = handler.event;
|
|
13183
|
-
|
|
13184
|
-
|
|
13185
|
-
|
|
13186
|
-
|
|
13187
|
-
|
|
13188
|
-
|
|
13189
|
-
|
|
13190
|
-
if (target instanceof HTMLInputElement && target.type === "checkbox") {
|
|
13191
|
-
eventLocals["checked"] = target.checked;
|
|
13192
|
-
}
|
|
13193
|
-
}
|
|
13194
|
-
let payload = void 0;
|
|
13195
|
-
if (handler.payload) {
|
|
13196
|
-
payload = evaluatePayload(handler.payload, {
|
|
13197
|
-
state: ctx.state,
|
|
13198
|
-
locals: { ...ctx.locals, ...eventLocals },
|
|
13199
|
-
...ctx.imports && { imports: ctx.imports }
|
|
13200
|
-
});
|
|
13201
|
-
}
|
|
13202
|
-
const actionCtx = {
|
|
13203
|
-
state: ctx.state,
|
|
13204
|
-
actions: ctx.actions,
|
|
13205
|
-
locals: { ...ctx.locals, ...eventLocals, payload },
|
|
13206
|
-
eventPayload: payload
|
|
13207
|
-
};
|
|
13208
|
-
await executeAction(action, actionCtx);
|
|
13209
|
-
}
|
|
13210
|
-
});
|
|
13635
|
+
if (eventName === "intersect") {
|
|
13636
|
+
setupIntersectionObserver(el, handler, ctx);
|
|
13637
|
+
} else {
|
|
13638
|
+
const eventCallback = createEventCallback(handler, ctx);
|
|
13639
|
+
const wrappedCallback = wrapWithDebounceThrottle(eventCallback, handler, ctx);
|
|
13640
|
+
el.addEventListener(eventName, wrappedCallback);
|
|
13641
|
+
}
|
|
13211
13642
|
} else {
|
|
13212
13643
|
const cleanup = createEffect(() => {
|
|
13213
13644
|
const value = evaluate(propValue, { state: ctx.state, locals: ctx.locals, ...ctx.imports && { imports: ctx.imports } });
|
|
@@ -13547,6 +13978,135 @@ function renderCode(node, ctx) {
|
|
|
13547
13978
|
ctx.cleanups?.push(cleanup);
|
|
13548
13979
|
return container;
|
|
13549
13980
|
}
|
|
13981
|
+
function renderPortal(node, ctx) {
|
|
13982
|
+
let targetElement = null;
|
|
13983
|
+
if (node.target === "body") {
|
|
13984
|
+
targetElement = document.body;
|
|
13985
|
+
} else if (node.target === "head") {
|
|
13986
|
+
targetElement = document.head;
|
|
13987
|
+
} else {
|
|
13988
|
+
targetElement = document.querySelector(node.target);
|
|
13989
|
+
}
|
|
13990
|
+
if (!targetElement) {
|
|
13991
|
+
return document.createComment("portal:target-not-found");
|
|
13992
|
+
}
|
|
13993
|
+
const portalContainer = document.createElement("div");
|
|
13994
|
+
portalContainer.setAttribute("data-portal", "true");
|
|
13995
|
+
portalContainer.style.display = "contents";
|
|
13996
|
+
const portalCleanups = [];
|
|
13997
|
+
const portalCtx = {
|
|
13998
|
+
...ctx,
|
|
13999
|
+
cleanups: portalCleanups
|
|
14000
|
+
};
|
|
14001
|
+
for (const child of node.children) {
|
|
14002
|
+
const childNode = render(child, portalCtx);
|
|
14003
|
+
portalContainer.appendChild(childNode);
|
|
14004
|
+
}
|
|
14005
|
+
targetElement.appendChild(portalContainer);
|
|
14006
|
+
ctx.cleanups?.push(() => {
|
|
14007
|
+
for (const cleanup of portalCleanups) {
|
|
14008
|
+
cleanup();
|
|
14009
|
+
}
|
|
14010
|
+
if (portalContainer.parentNode) {
|
|
14011
|
+
portalContainer.parentNode.removeChild(portalContainer);
|
|
14012
|
+
}
|
|
14013
|
+
});
|
|
14014
|
+
return document.createComment("portal");
|
|
14015
|
+
}
|
|
14016
|
+
function createLocalStateStore(stateDefs) {
|
|
14017
|
+
const signals = {};
|
|
14018
|
+
for (const [name, def] of Object.entries(stateDefs)) {
|
|
14019
|
+
signals[name] = createSignal(def.initial);
|
|
14020
|
+
}
|
|
14021
|
+
return {
|
|
14022
|
+
get(name) {
|
|
14023
|
+
return signals[name]?.get();
|
|
14024
|
+
},
|
|
14025
|
+
set(name, value) {
|
|
14026
|
+
signals[name]?.set(value);
|
|
14027
|
+
},
|
|
14028
|
+
signals
|
|
14029
|
+
};
|
|
14030
|
+
}
|
|
14031
|
+
function createLocalsWithLocalState(baseLocals, localStore) {
|
|
14032
|
+
return new Proxy(baseLocals, {
|
|
14033
|
+
get(target, prop) {
|
|
14034
|
+
if (prop in localStore.signals) {
|
|
14035
|
+
return localStore.get(prop);
|
|
14036
|
+
}
|
|
14037
|
+
return target[prop];
|
|
14038
|
+
},
|
|
14039
|
+
has(target, prop) {
|
|
14040
|
+
if (prop in localStore.signals) return true;
|
|
14041
|
+
return prop in target;
|
|
14042
|
+
},
|
|
14043
|
+
ownKeys(target) {
|
|
14044
|
+
const keys = Reflect.ownKeys(target);
|
|
14045
|
+
for (const key2 of Object.keys(localStore.signals)) {
|
|
14046
|
+
if (!keys.includes(key2)) keys.push(key2);
|
|
14047
|
+
}
|
|
14048
|
+
return keys;
|
|
14049
|
+
},
|
|
14050
|
+
getOwnPropertyDescriptor(target, prop) {
|
|
14051
|
+
if (prop in localStore.signals) {
|
|
14052
|
+
return { enumerable: true, configurable: true };
|
|
14053
|
+
}
|
|
14054
|
+
return Reflect.getOwnPropertyDescriptor(target, prop);
|
|
14055
|
+
}
|
|
14056
|
+
});
|
|
14057
|
+
}
|
|
14058
|
+
function createStateWithLocalState(globalState, localStore) {
|
|
14059
|
+
return {
|
|
14060
|
+
get(name) {
|
|
14061
|
+
if (name in localStore.signals) {
|
|
14062
|
+
return localStore.get(name);
|
|
14063
|
+
}
|
|
14064
|
+
return globalState.get(name);
|
|
14065
|
+
},
|
|
14066
|
+
set(name, value) {
|
|
14067
|
+
globalState.set(name, value);
|
|
14068
|
+
},
|
|
14069
|
+
setPath(name, path, value) {
|
|
14070
|
+
globalState.setPath(name, path, value);
|
|
14071
|
+
},
|
|
14072
|
+
subscribe(name, fn) {
|
|
14073
|
+
if (name in localStore.signals) {
|
|
14074
|
+
return localStore.signals[name].subscribe(fn);
|
|
14075
|
+
}
|
|
14076
|
+
return globalState.subscribe(name, fn);
|
|
14077
|
+
},
|
|
14078
|
+
getPath(name, path) {
|
|
14079
|
+
return globalState.getPath(name, path);
|
|
14080
|
+
},
|
|
14081
|
+
subscribeToPath(name, path, fn) {
|
|
14082
|
+
return globalState.subscribeToPath(name, path, fn);
|
|
14083
|
+
}
|
|
14084
|
+
};
|
|
14085
|
+
}
|
|
14086
|
+
function renderLocalState(node, ctx) {
|
|
14087
|
+
const localStore = createLocalStateStore(node.state);
|
|
14088
|
+
const mergedLocals = createLocalsWithLocalState(ctx.locals, localStore);
|
|
14089
|
+
const mergedState = createStateWithLocalState(ctx.state, localStore);
|
|
14090
|
+
const mergedActions = { ...ctx.actions };
|
|
14091
|
+
for (const [name, action] of Object.entries(node.actions)) {
|
|
14092
|
+
mergedActions[name] = {
|
|
14093
|
+
...action,
|
|
14094
|
+
_isLocalAction: true,
|
|
14095
|
+
_localStore: localStore
|
|
14096
|
+
};
|
|
14097
|
+
}
|
|
14098
|
+
const childCtx = {
|
|
14099
|
+
...ctx,
|
|
14100
|
+
state: mergedState,
|
|
14101
|
+
locals: mergedLocals,
|
|
14102
|
+
actions: mergedActions,
|
|
14103
|
+
localState: {
|
|
14104
|
+
store: localStore,
|
|
14105
|
+
actions: node.actions
|
|
14106
|
+
}
|
|
14107
|
+
};
|
|
14108
|
+
return render(node.child, childCtx);
|
|
14109
|
+
}
|
|
13550
14110
|
|
|
13551
14111
|
// src/app.ts
|
|
13552
14112
|
function createApp(program, mount) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@constela/runtime",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.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.0",
|
|
22
|
+
"@constela/core": "0.11.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": "7.0.0"
|
|
33
33
|
},
|
|
34
34
|
"engines": {
|
|
35
35
|
"node": ">=20.0.0"
|