@jxsuite/runtime 0.19.0 → 0.21.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/runtime.js +61 -19
- package/dist/runtime.js.map +6 -6
- package/package.json +3 -2
- package/src/runtime.js +198 -99
package/src/runtime.js
CHANGED
|
@@ -25,7 +25,7 @@ import { reactive, ref, computed, effect, isRef, onEffectCleanup } from "@vue/re
|
|
|
25
25
|
*
|
|
26
26
|
* @param {string | Record<string, any>} source - Path to .json file, URL, or raw document object
|
|
27
27
|
* @param {HTMLElement} [target] Default is `document.body`
|
|
28
|
-
* @param {
|
|
28
|
+
* @param {JxRenderOptions} [options]
|
|
29
29
|
* @returns {Promise<Record<string, any>>} Resolves with the live component scope (state reactive
|
|
30
30
|
* proxy)
|
|
31
31
|
*/
|
|
@@ -88,6 +88,13 @@ export function setSkipServerFunctions(/** @type {boolean} */ v) {
|
|
|
88
88
|
_serverFnConfig.skip = v;
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
+
/** @type {{ skip: boolean }} */
|
|
92
|
+
const _contentConfig = { skip: false };
|
|
93
|
+
/** Set to true to suppress ContentCollection/ContentEntry resolution (used by Studio edit mode). */
|
|
94
|
+
export function setSkipContentResolution(/** @type {boolean} */ v) {
|
|
95
|
+
_contentConfig.skip = v;
|
|
96
|
+
}
|
|
97
|
+
|
|
91
98
|
/**
|
|
92
99
|
* Build the reactive scope (state) from the document using the five-shape detection algorithm.
|
|
93
100
|
*
|
|
@@ -232,11 +239,12 @@ export { hasSchemaKeywords };
|
|
|
232
239
|
*
|
|
233
240
|
* @param {string} str
|
|
234
241
|
* @param {Record<string, any>} state
|
|
235
|
-
* @returns {
|
|
242
|
+
* @returns {string}
|
|
236
243
|
*/
|
|
237
244
|
function evaluateTemplate(str, state) {
|
|
238
|
-
const
|
|
239
|
-
|
|
245
|
+
const $map = state?.$map;
|
|
246
|
+
const fn = new Function("state", "$map", "item", "index", `return \`${str}\``);
|
|
247
|
+
return fn(state, $map, $map?.item, $map?.index);
|
|
240
248
|
}
|
|
241
249
|
|
|
242
250
|
// ─── Step 2b: Function resolution (Shape 4) ─────────────────────────────────
|
|
@@ -254,7 +262,7 @@ const _moduleCache = new Map();
|
|
|
254
262
|
* @param {Record<string, any>} state - Reactive scope proxy
|
|
255
263
|
* @param {string} key - Def key name
|
|
256
264
|
* @param {string} [base] - Base URL for resolving $src imports
|
|
257
|
-
* @returns {Promise<
|
|
265
|
+
* @returns {Promise<unknown>}
|
|
258
266
|
*/
|
|
259
267
|
async function resolveFunction(def, state, key, base) {
|
|
260
268
|
if (def.body && def.$src) {
|
|
@@ -385,7 +393,7 @@ export const RESERVED_KEYS = new Set([
|
|
|
385
393
|
*
|
|
386
394
|
* @param {Record<string, any>} def
|
|
387
395
|
* @param {Record<string, any>} state - Reactive scope proxy (or child scope via Object.create)
|
|
388
|
-
* @param {
|
|
396
|
+
* @param {JxRenderOptions} [options]
|
|
389
397
|
* @returns {HTMLElement | Text}
|
|
390
398
|
*/
|
|
391
399
|
export function renderNode(def, state, options) {
|
|
@@ -448,7 +456,7 @@ export function renderNode(def, state, options) {
|
|
|
448
456
|
/**
|
|
449
457
|
* Check if a value is a template string (contains ${}).
|
|
450
458
|
*
|
|
451
|
-
* @param {
|
|
459
|
+
* @param {unknown} val
|
|
452
460
|
* @returns {boolean}
|
|
453
461
|
*/
|
|
454
462
|
function isTemplateString(val) {
|
|
@@ -492,19 +500,22 @@ function applyProperties(el, def, state) {
|
|
|
492
500
|
}
|
|
493
501
|
|
|
494
502
|
/**
|
|
495
|
-
* @param {
|
|
503
|
+
* @param {HTMLElement} el
|
|
496
504
|
* @param {string} key
|
|
497
|
-
* @param {
|
|
505
|
+
* @param {unknown} val
|
|
498
506
|
* @param {Record<string, any>} state
|
|
499
507
|
*/
|
|
500
508
|
function bindProperty(el, key, val, state) {
|
|
509
|
+
const target = /** @type {Record<string, unknown>} */ (/** @type {unknown} */ (el));
|
|
501
510
|
if (isRefObj(val)) {
|
|
502
511
|
if (key === "id") {
|
|
503
|
-
|
|
512
|
+
target[key] = /** @type {string} */ (
|
|
513
|
+
resolveRef(/** @type {{ $ref: string }} */ (val).$ref, state)
|
|
514
|
+
);
|
|
504
515
|
return;
|
|
505
516
|
}
|
|
506
517
|
effect(() => {
|
|
507
|
-
|
|
518
|
+
target[key] = resolveRef(/** @type {{ $ref: string }} */ (val).$ref, state);
|
|
508
519
|
});
|
|
509
520
|
return;
|
|
510
521
|
}
|
|
@@ -512,12 +523,12 @@ function bindProperty(el, key, val, state) {
|
|
|
512
523
|
// Universal ${} reactivity — template strings in element properties
|
|
513
524
|
if (isTemplateString(val)) {
|
|
514
525
|
effect(() => {
|
|
515
|
-
|
|
526
|
+
target[key] = evaluateTemplate(/** @type {string} */ (val), state);
|
|
516
527
|
});
|
|
517
528
|
return;
|
|
518
529
|
}
|
|
519
530
|
|
|
520
|
-
|
|
531
|
+
target[key] = val;
|
|
521
532
|
}
|
|
522
533
|
|
|
523
534
|
/**
|
|
@@ -548,9 +559,10 @@ export function applyStyle(el, styleDef, mediaQueries = {}, state = {}) {
|
|
|
548
559
|
else el.style.setProperty(prop, val);
|
|
549
560
|
} else if (isTemplateString(val))
|
|
550
561
|
effect(() => {
|
|
551
|
-
/** @type {
|
|
562
|
+
/** @type {Record<string, string>} */ (/** @type {unknown} */ (el.style))[prop] =
|
|
563
|
+
evaluateTemplate(val, state);
|
|
552
564
|
});
|
|
553
|
-
else /** @type {
|
|
565
|
+
else /** @type {Record<string, string>} */ (/** @type {unknown} */ (el.style))[prop] = val;
|
|
554
566
|
}
|
|
555
567
|
|
|
556
568
|
const hasNested = Object.keys(nested).length > 0;
|
|
@@ -647,7 +659,7 @@ function applyAttributes(el, attrs, state) {
|
|
|
647
659
|
/**
|
|
648
660
|
* @param {Record<string, any>} def
|
|
649
661
|
* @param {Record<string, any>} state
|
|
650
|
-
* @param {
|
|
662
|
+
* @param {JxRenderOptions} [options]
|
|
651
663
|
* @returns {HTMLElement}
|
|
652
664
|
*/
|
|
653
665
|
function renderMappedArray(def, state, options) {
|
|
@@ -672,11 +684,13 @@ function renderMappedArray(def, state, options) {
|
|
|
672
684
|
if (!Array.isArray(items)) return;
|
|
673
685
|
if (filterRef) {
|
|
674
686
|
const fn = resolveRef(filterRef.$ref, state);
|
|
675
|
-
if (typeof fn === "function")
|
|
687
|
+
if (typeof fn === "function")
|
|
688
|
+
items = items.filter(/** @type {(v: unknown) => boolean} */ (fn));
|
|
676
689
|
}
|
|
677
690
|
if (sortRef) {
|
|
678
691
|
const fn = resolveRef(sortRef.$ref, state);
|
|
679
|
-
if (typeof fn === "function")
|
|
692
|
+
if (typeof fn === "function")
|
|
693
|
+
items = [...items].sort(/** @type {(a: unknown, b: unknown) => number} */ (fn));
|
|
680
694
|
}
|
|
681
695
|
|
|
682
696
|
items.forEach((item, index) => {
|
|
@@ -699,7 +713,7 @@ function renderMappedArray(def, state, options) {
|
|
|
699
713
|
/**
|
|
700
714
|
* @param {Record<string, any>} def
|
|
701
715
|
* @param {Record<string, any>} state
|
|
702
|
-
* @param {
|
|
716
|
+
* @param {JxRenderOptions} [options]
|
|
703
717
|
* @returns {HTMLElement}
|
|
704
718
|
*/
|
|
705
719
|
function renderSwitch(def, state, options) {
|
|
@@ -715,7 +729,7 @@ function renderSwitch(def, state, options) {
|
|
|
715
729
|
|
|
716
730
|
effect(() => {
|
|
717
731
|
container.innerHTML = "";
|
|
718
|
-
const key = resolveRef(def.$switch.$ref, state);
|
|
732
|
+
const key = /** @type {string} */ (resolveRef(def.$switch.$ref, state));
|
|
719
733
|
const caseDef = def.cases?.[key];
|
|
720
734
|
if (!caseDef) return;
|
|
721
735
|
|
|
@@ -732,7 +746,7 @@ function renderSwitch(def, state, options) {
|
|
|
732
746
|
const childOpts = options ? { ...options, _path: [...path, "cases", key] } : undefined;
|
|
733
747
|
container.appendChild(renderNode(doc, childScope, childOpts));
|
|
734
748
|
})
|
|
735
|
-
.catch((/** @type {
|
|
749
|
+
.catch((/** @type {unknown} */ e) =>
|
|
736
750
|
console.error("Jx $switch: failed to load external case", caseDef.$ref, e),
|
|
737
751
|
);
|
|
738
752
|
return;
|
|
@@ -757,7 +771,7 @@ function renderSwitch(def, state, options) {
|
|
|
757
771
|
* @param {Record<string, any>} state - Reactive scope proxy
|
|
758
772
|
* @param {string} key - Def key (for diagnostics)
|
|
759
773
|
* @param {string} [base] - Base URL for resolving $src imports
|
|
760
|
-
* @returns {Promise<
|
|
774
|
+
* @returns {Promise<unknown>}
|
|
761
775
|
*/
|
|
762
776
|
export async function resolvePrototype(def, state, key, base) {
|
|
763
777
|
// ── External class via $src ─────────────────────────────────────────────────
|
|
@@ -767,10 +781,10 @@ export async function resolvePrototype(def, state, key, base) {
|
|
|
767
781
|
|
|
768
782
|
switch (def.$prototype) {
|
|
769
783
|
case "Request": {
|
|
770
|
-
/** @type {import("@vue/reactivity").Ref<
|
|
784
|
+
/** @type {import("@vue/reactivity").Ref<unknown>} */
|
|
771
785
|
const s = ref(null);
|
|
772
786
|
const debounceMs = def.debounce ?? 0;
|
|
773
|
-
/** @type {
|
|
787
|
+
/** @type {ReturnType<typeof setTimeout> | null} */
|
|
774
788
|
let debounceTimer = null;
|
|
775
789
|
|
|
776
790
|
if (!def.manual) {
|
|
@@ -786,7 +800,7 @@ export async function resolvePrototype(def, state, key, base) {
|
|
|
786
800
|
const controller = new AbortController();
|
|
787
801
|
onEffectCleanup(() => {
|
|
788
802
|
controller.abort();
|
|
789
|
-
clearTimeout(debounceTimer);
|
|
803
|
+
if (debounceTimer !== null) clearTimeout(debounceTimer);
|
|
790
804
|
});
|
|
791
805
|
|
|
792
806
|
const doFetch = () =>
|
|
@@ -802,8 +816,8 @@ export async function resolvePrototype(def, state, key, base) {
|
|
|
802
816
|
.then((d) => {
|
|
803
817
|
s.value = d;
|
|
804
818
|
})
|
|
805
|
-
.catch((/** @type {
|
|
806
|
-
if (e.name !== "AbortError") s.value = { error: String(e) };
|
|
819
|
+
.catch((/** @type {unknown} */ e) => {
|
|
820
|
+
if (/** @type {Error} */ (e).name !== "AbortError") s.value = { error: String(e) };
|
|
807
821
|
});
|
|
808
822
|
|
|
809
823
|
if (debounceMs > 0) {
|
|
@@ -902,22 +916,22 @@ export async function resolvePrototype(def, state, key, base) {
|
|
|
902
916
|
} = def;
|
|
903
917
|
const req = indexedDB.open(database, version);
|
|
904
918
|
req.onupgradeneeded = (e) => {
|
|
905
|
-
/** @type {
|
|
906
|
-
const db = /** @type {
|
|
919
|
+
/** @type {IDBDatabase} */
|
|
920
|
+
const db = /** @type {IDBOpenDBRequest} */ (e.target).result;
|
|
907
921
|
if (!db.objectStoreNames.contains(store)) {
|
|
908
922
|
const os = db.createObjectStore(store, { keyPath, autoIncrement });
|
|
909
923
|
for (const i of indexes) os.createIndex(i.name, i.keyPath, { unique: i.unique ?? false });
|
|
910
924
|
}
|
|
911
925
|
};
|
|
912
926
|
req.onsuccess = (e) => {
|
|
913
|
-
/** @type {
|
|
914
|
-
const db = /** @type {
|
|
927
|
+
/** @type {IDBDatabase} */
|
|
928
|
+
const db = /** @type {IDBOpenDBRequest} */ (e.target).result;
|
|
915
929
|
idbState.value = {
|
|
916
930
|
database,
|
|
917
931
|
store,
|
|
918
932
|
version,
|
|
919
933
|
isReady: true,
|
|
920
|
-
getStore: (/** @type {
|
|
934
|
+
getStore: (/** @type {IDBTransactionMode} */ mode = "readwrite") =>
|
|
921
935
|
Promise.resolve(db.transaction(store, mode).objectStore(store)),
|
|
922
936
|
};
|
|
923
937
|
};
|
|
@@ -945,6 +959,42 @@ export async function resolvePrototype(def, state, key, base) {
|
|
|
945
959
|
case "ReadableStream":
|
|
946
960
|
return null;
|
|
947
961
|
|
|
962
|
+
case "ContentCollection":
|
|
963
|
+
case "ContentEntry": {
|
|
964
|
+
if (_contentConfig.skip) {
|
|
965
|
+
return ref(def.$prototype === "ContentCollection" ? [{ id: "", data: {} }] : null);
|
|
966
|
+
}
|
|
967
|
+
const s = ref(def.$prototype === "ContentCollection" ? [] : null);
|
|
968
|
+
effect(() => {
|
|
969
|
+
let id = def.id;
|
|
970
|
+
if (id && typeof id === "object" && id.$ref?.startsWith("#/$params/")) {
|
|
971
|
+
const paramName = id.$ref.replace("#/$params/", "");
|
|
972
|
+
id = state?.$params?.[paramName];
|
|
973
|
+
} else if (typeof id === "string" && id.includes("${")) {
|
|
974
|
+
id = evaluateTemplate(id, state);
|
|
975
|
+
}
|
|
976
|
+
if (def.$prototype === "ContentEntry" && !id) return;
|
|
977
|
+
fetch("/__studio/content", {
|
|
978
|
+
method: "POST",
|
|
979
|
+
headers: { "Content-Type": "application/json" },
|
|
980
|
+
body: JSON.stringify({
|
|
981
|
+
$prototype: def.$prototype,
|
|
982
|
+
contentType: def.contentType,
|
|
983
|
+
id,
|
|
984
|
+
filter: def.filter,
|
|
985
|
+
sort: def.sort,
|
|
986
|
+
limit: def.limit,
|
|
987
|
+
}),
|
|
988
|
+
})
|
|
989
|
+
.then((r) => (r.ok ? r.json() : null))
|
|
990
|
+
.then((d) => {
|
|
991
|
+
s.value = d;
|
|
992
|
+
})
|
|
993
|
+
.catch(() => {});
|
|
994
|
+
});
|
|
995
|
+
return s;
|
|
996
|
+
}
|
|
997
|
+
|
|
948
998
|
default:
|
|
949
999
|
console.warn(
|
|
950
1000
|
`Jx: unknown $prototype "${def.$prototype}" for "${key}". Did you mean to add '$src'?`,
|
|
@@ -976,7 +1026,7 @@ const EXTERNAL_RESERVED = new Set([
|
|
|
976
1026
|
* @param {Record<string, any>} state
|
|
977
1027
|
* @param {string} key
|
|
978
1028
|
* @param {string} [base]
|
|
979
|
-
* @returns {Promise<
|
|
1029
|
+
* @returns {Promise<unknown>}
|
|
980
1030
|
*/
|
|
981
1031
|
async function resolveExternalPrototype(def, state, key, base) {
|
|
982
1032
|
const src = def.$src;
|
|
@@ -1000,7 +1050,7 @@ async function resolveExternalPrototype(def, state, key, base) {
|
|
|
1000
1050
|
* @param {string} src - JS module URL to import
|
|
1001
1051
|
* @param {string} exportName - Export name to look up
|
|
1002
1052
|
* @param {string} [base] - Base URL for resolution
|
|
1003
|
-
* @returns {Promise<
|
|
1053
|
+
* @returns {Promise<unknown>}
|
|
1004
1054
|
*/
|
|
1005
1055
|
async function importAndInstantiate(def, src, exportName, base) {
|
|
1006
1056
|
let mod;
|
|
@@ -1049,7 +1099,7 @@ async function importAndInstantiate(def, src, exportName, base) {
|
|
|
1049
1099
|
/** @type {import("@vue/reactivity").Ref<any>} */
|
|
1050
1100
|
const s = ref(value);
|
|
1051
1101
|
if (typeof instance.subscribe === "function") {
|
|
1052
|
-
instance.subscribe((/** @type {
|
|
1102
|
+
instance.subscribe((/** @type {unknown} */ newVal) => {
|
|
1053
1103
|
s.value = newVal;
|
|
1054
1104
|
});
|
|
1055
1105
|
}
|
|
@@ -1064,7 +1114,7 @@ async function importAndInstantiate(def, src, exportName, base) {
|
|
|
1064
1114
|
* @param {Record<string, any>} state
|
|
1065
1115
|
* @param {string} key
|
|
1066
1116
|
* @param {string} [base]
|
|
1067
|
-
* @returns {Promise<
|
|
1117
|
+
* @returns {Promise<unknown>}
|
|
1068
1118
|
*/
|
|
1069
1119
|
async function resolveClassJson(def, state, key, base) {
|
|
1070
1120
|
const src = def.$src;
|
|
@@ -1116,7 +1166,7 @@ async function resolveClassJson(def, state, key, base) {
|
|
|
1116
1166
|
/** @type {import("@vue/reactivity").Ref<any>} */
|
|
1117
1167
|
const s = ref(value);
|
|
1118
1168
|
if (typeof instance.subscribe === "function") {
|
|
1119
|
-
instance.subscribe((/** @type {
|
|
1169
|
+
instance.subscribe((/** @type {unknown} */ newVal) => {
|
|
1120
1170
|
s.value = newVal;
|
|
1121
1171
|
});
|
|
1122
1172
|
}
|
|
@@ -1128,7 +1178,7 @@ async function resolveClassJson(def, state, key, base) {
|
|
|
1128
1178
|
* fields to _-prefixed public fields.
|
|
1129
1179
|
*
|
|
1130
1180
|
* @param {Record<string, any>} classDef
|
|
1131
|
-
* @returns {
|
|
1181
|
+
* @returns {DynamicClass}
|
|
1132
1182
|
*/
|
|
1133
1183
|
function classFromSchema(classDef) {
|
|
1134
1184
|
const fields = classDef.$defs?.fields ?? {};
|
|
@@ -1138,16 +1188,34 @@ function classFromSchema(classDef) {
|
|
|
1138
1188
|
class DynClass {
|
|
1139
1189
|
constructor(/** @type {Record<string, any>} */ config = {}) {
|
|
1140
1190
|
for (const [key, field] of Object.entries(fields)) {
|
|
1141
|
-
/**
|
|
1142
|
-
|
|
1191
|
+
/**
|
|
1192
|
+
* @type {{
|
|
1193
|
+
* identifier?: string;
|
|
1194
|
+
* access?: string;
|
|
1195
|
+
* initializer?: unknown;
|
|
1196
|
+
* default?: unknown;
|
|
1197
|
+
* }}
|
|
1198
|
+
*/
|
|
1199
|
+
const typedField =
|
|
1200
|
+
/**
|
|
1201
|
+
* @type {{
|
|
1202
|
+
* identifier?: string;
|
|
1203
|
+
* access?: string;
|
|
1204
|
+
* initializer?: unknown;
|
|
1205
|
+
* default?: unknown;
|
|
1206
|
+
* }}
|
|
1207
|
+
*/ (field);
|
|
1143
1208
|
const id = typedField.identifier ?? key;
|
|
1144
1209
|
const propName = typedField.access === "private" ? `_${id}` : id;
|
|
1145
|
-
if (config[id] !== undefined)
|
|
1210
|
+
if (config[id] !== undefined)
|
|
1211
|
+
/** @type {Record<string, unknown>} */ (this)[propName] = config[id];
|
|
1146
1212
|
else if (typedField.initializer !== undefined)
|
|
1147
|
-
/** @type {
|
|
1213
|
+
/** @type {Record<string, unknown>} */ (this)[propName] = typedField.initializer;
|
|
1148
1214
|
else if (typedField.default !== undefined)
|
|
1149
|
-
/** @type {
|
|
1150
|
-
|
|
1215
|
+
/** @type {Record<string, unknown>} */ (this)[propName] = structuredClone(
|
|
1216
|
+
typedField.default,
|
|
1217
|
+
);
|
|
1218
|
+
else /** @type {Record<string, unknown>} */ (this)[propName] = null;
|
|
1151
1219
|
}
|
|
1152
1220
|
if (ctor?.body) {
|
|
1153
1221
|
const bodyStr = Array.isArray(ctor.body) ? ctor.body.join("\n") : ctor.body;
|
|
@@ -1157,13 +1225,35 @@ function classFromSchema(classDef) {
|
|
|
1157
1225
|
}
|
|
1158
1226
|
|
|
1159
1227
|
for (const [key, method] of Object.entries(methods)) {
|
|
1160
|
-
/**
|
|
1161
|
-
|
|
1228
|
+
/**
|
|
1229
|
+
* @type {{
|
|
1230
|
+
* identifier?: string;
|
|
1231
|
+
* parameters?: Record<string, unknown>[];
|
|
1232
|
+
* body?: string | string[];
|
|
1233
|
+
* role?: string;
|
|
1234
|
+
* scope?: string;
|
|
1235
|
+
* getter?: { body: string };
|
|
1236
|
+
* setter?: { body: string; parameters?: Record<string, unknown>[] };
|
|
1237
|
+
* }}
|
|
1238
|
+
*/
|
|
1239
|
+
const typedMethod = /**
|
|
1240
|
+
* @type {{
|
|
1241
|
+
* identifier?: string;
|
|
1242
|
+
* parameters?: Record<string, unknown>[];
|
|
1243
|
+
* body?: string | string[];
|
|
1244
|
+
* role?: string;
|
|
1245
|
+
* scope?: string;
|
|
1246
|
+
* getter?: { body: string };
|
|
1247
|
+
* setter?: { body: string; parameters?: Record<string, unknown>[] };
|
|
1248
|
+
* }}
|
|
1249
|
+
*/ (method);
|
|
1162
1250
|
const name = typedMethod.identifier ?? key;
|
|
1163
|
-
const params =
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1251
|
+
const params = /** @type {string[]} */ (
|
|
1252
|
+
(typedMethod.parameters ?? []).map((/** @type {Record<string, unknown>} */ p) => {
|
|
1253
|
+
if (p.$ref) return /** @type {string} */ (/** @type {string} */ (p.$ref).split("/").pop());
|
|
1254
|
+
return /** @type {string} */ (p.identifier ?? p.name ?? "arg");
|
|
1255
|
+
})
|
|
1256
|
+
);
|
|
1167
1257
|
const bodyStr = Array.isArray(typedMethod.body)
|
|
1168
1258
|
? typedMethod.body.join("\n")
|
|
1169
1259
|
: (typedMethod.body ?? "");
|
|
@@ -1172,23 +1262,27 @@ function classFromSchema(classDef) {
|
|
|
1172
1262
|
/** @type {PropertyDescriptor} */
|
|
1173
1263
|
const descriptor = {};
|
|
1174
1264
|
if (typedMethod.getter)
|
|
1175
|
-
descriptor.get = /** @type {
|
|
1265
|
+
descriptor.get = /** @type {() => unknown} */ (new Function(typedMethod.getter.body));
|
|
1176
1266
|
if (typedMethod.setter) {
|
|
1177
1267
|
const sp = (typedMethod.setter.parameters ?? []).map(
|
|
1178
|
-
(/** @type {
|
|
1268
|
+
(/** @type {Record<string, unknown>} */ p) =>
|
|
1269
|
+
/** @type {string} */ (p.$ref)?.split("/").pop() ?? "v",
|
|
1270
|
+
);
|
|
1271
|
+
descriptor.set = /** @type {(v: unknown) => void} */ (
|
|
1272
|
+
new Function(...sp, typedMethod.setter.body)
|
|
1179
1273
|
);
|
|
1180
|
-
descriptor.set = /** @type {any} */ (new Function(...sp, typedMethod.setter.body));
|
|
1181
1274
|
}
|
|
1182
1275
|
Object.defineProperty(DynClass.prototype, name, { ...descriptor, configurable: true });
|
|
1183
1276
|
} else if (typedMethod.scope === "static") {
|
|
1184
|
-
/** @type {
|
|
1277
|
+
/** @type {DynamicClass} */ (DynClass)[name] = new Function(...params, bodyStr);
|
|
1185
1278
|
} else {
|
|
1186
|
-
/** @type {
|
|
1279
|
+
/** @type {DynamicClass} */ (DynClass).prototype[name] = new Function(...params, bodyStr);
|
|
1187
1280
|
}
|
|
1188
1281
|
}
|
|
1189
1282
|
|
|
1190
1283
|
Object.defineProperty(DynClass, "name", { value: classDef.title, configurable: true });
|
|
1191
|
-
|
|
1284
|
+
const dynCtor = /** @type {DynamicClass} */ (/** @type {unknown} */ (DynClass));
|
|
1285
|
+
return dynCtor;
|
|
1192
1286
|
}
|
|
1193
1287
|
|
|
1194
1288
|
/**
|
|
@@ -1200,7 +1294,7 @@ function classFromSchema(classDef) {
|
|
|
1200
1294
|
* @param {Record<string, any>} state
|
|
1201
1295
|
* @param {string} key
|
|
1202
1296
|
* @param {string} [base]
|
|
1203
|
-
* @returns {Promise<
|
|
1297
|
+
* @returns {Promise<unknown>}
|
|
1204
1298
|
*/
|
|
1205
1299
|
async function resolveViaDevProxy(def, state, key, base) {
|
|
1206
1300
|
/** @type {Record<string, any>} */
|
|
@@ -1209,7 +1303,9 @@ async function resolveViaDevProxy(def, state, key, base) {
|
|
|
1209
1303
|
if (!EXTERNAL_RESERVED.has(k)) config[k] = v;
|
|
1210
1304
|
}
|
|
1211
1305
|
|
|
1212
|
-
const hasTemplates = Object.values(config).some((/** @type {
|
|
1306
|
+
const hasTemplates = Object.values(config).some((/** @type {unknown} */ v) =>
|
|
1307
|
+
isTemplateString(v),
|
|
1308
|
+
);
|
|
1213
1309
|
|
|
1214
1310
|
/** @param {Record<string, any>} resolvedConfig */
|
|
1215
1311
|
const doResolve = (resolvedConfig) =>
|
|
@@ -1239,17 +1335,17 @@ async function resolveViaDevProxy(def, state, key, base) {
|
|
|
1239
1335
|
resolvedConfig[k] = isTemplateString(v) ? evaluateTemplate(v, state) : v;
|
|
1240
1336
|
}
|
|
1241
1337
|
doResolve(resolvedConfig)
|
|
1242
|
-
.then((/** @type {
|
|
1338
|
+
.then((/** @type {unknown} */ value) => {
|
|
1243
1339
|
s.value = value;
|
|
1244
1340
|
})
|
|
1245
|
-
.catch((/** @type {
|
|
1341
|
+
.catch((/** @type {unknown} */ e) => console.error("Jx dev proxy:", e));
|
|
1246
1342
|
});
|
|
1247
1343
|
} else {
|
|
1248
1344
|
doResolve(config)
|
|
1249
|
-
.then((/** @type {
|
|
1345
|
+
.then((/** @type {unknown} */ value) => {
|
|
1250
1346
|
s.value = value;
|
|
1251
1347
|
})
|
|
1252
|
-
.catch((/** @type {
|
|
1348
|
+
.catch((/** @type {unknown} */ e) => console.error("Jx dev proxy:", e));
|
|
1253
1349
|
}
|
|
1254
1350
|
return s;
|
|
1255
1351
|
}
|
|
@@ -1264,7 +1360,7 @@ async function resolveViaDevProxy(def, state, key, base) {
|
|
|
1264
1360
|
* @param {Record<string, any>} state
|
|
1265
1361
|
* @param {string} key
|
|
1266
1362
|
* @param {string} [base]
|
|
1267
|
-
* @returns {Promise<
|
|
1363
|
+
* @returns {Promise<unknown>}
|
|
1268
1364
|
*/
|
|
1269
1365
|
async function resolveServerFunction(def, state, key, base) {
|
|
1270
1366
|
const src = def.$src;
|
|
@@ -1298,12 +1394,12 @@ async function resolveServerFunction(def, state, key, base) {
|
|
|
1298
1394
|
throw new Error(`Jx: "${exportName}" from "${src}" is not a function`);
|
|
1299
1395
|
|
|
1300
1396
|
const rawArgs = def.arguments ?? {};
|
|
1301
|
-
const hasReactiveArg = Object.values(rawArgs).some((/** @type {
|
|
1397
|
+
const hasReactiveArg = Object.values(rawArgs).some((/** @type {unknown} */ v) => isRefObj(v));
|
|
1302
1398
|
const resolveArgs = () => {
|
|
1303
1399
|
/** @type {Record<string, any>} */
|
|
1304
1400
|
const args = {};
|
|
1305
1401
|
for (const [k, v] of Object.entries(rawArgs)) {
|
|
1306
|
-
args[k] = isRefObj(v) ? resolveRef(/** @type {
|
|
1402
|
+
args[k] = isRefObj(v) ? resolveRef(/** @type {{ $ref: string }} */ (v).$ref, state) : v;
|
|
1307
1403
|
}
|
|
1308
1404
|
return args;
|
|
1309
1405
|
};
|
|
@@ -1316,7 +1412,7 @@ async function resolveServerFunction(def, state, key, base) {
|
|
|
1316
1412
|
const args = resolveArgs();
|
|
1317
1413
|
onEffectCleanup(() => {});
|
|
1318
1414
|
fn(args)
|
|
1319
|
-
.then((/** @type {
|
|
1415
|
+
.then((/** @type {unknown} */ result) => {
|
|
1320
1416
|
s.value = result;
|
|
1321
1417
|
})
|
|
1322
1418
|
.catch(() => {});
|
|
@@ -1336,17 +1432,17 @@ async function resolveServerFunction(def, state, key, base) {
|
|
|
1336
1432
|
* @param {Record<string, any>} state
|
|
1337
1433
|
* @param {string} key
|
|
1338
1434
|
* @param {string} [base]
|
|
1339
|
-
* @returns {Promise<
|
|
1435
|
+
* @returns {Promise<unknown>}
|
|
1340
1436
|
*/
|
|
1341
1437
|
async function resolveServerFunctionViaProxy(def, state, key, base) {
|
|
1342
1438
|
const rawArgs = def.arguments ?? {};
|
|
1343
|
-
const hasReactiveArg = Object.values(rawArgs).some((/** @type {
|
|
1439
|
+
const hasReactiveArg = Object.values(rawArgs).some((/** @type {unknown} */ v) => isRefObj(v));
|
|
1344
1440
|
|
|
1345
1441
|
const resolveArgs = () => {
|
|
1346
1442
|
/** @type {Record<string, any>} */
|
|
1347
1443
|
const args = {};
|
|
1348
1444
|
for (const [k, v] of Object.entries(rawArgs)) {
|
|
1349
|
-
args[k] = isRefObj(v) ? resolveRef(/** @type {
|
|
1445
|
+
args[k] = isRefObj(v) ? resolveRef(/** @type {{ $ref: string }} */ (v).$ref, state) : v;
|
|
1350
1446
|
}
|
|
1351
1447
|
return args;
|
|
1352
1448
|
};
|
|
@@ -1375,17 +1471,17 @@ async function resolveServerFunctionViaProxy(def, state, key, base) {
|
|
|
1375
1471
|
const args = resolveArgs();
|
|
1376
1472
|
onEffectCleanup(() => {});
|
|
1377
1473
|
doResolve(args)
|
|
1378
|
-
.then((/** @type {
|
|
1474
|
+
.then((/** @type {unknown} */ result) => {
|
|
1379
1475
|
s.value = result;
|
|
1380
1476
|
})
|
|
1381
|
-
.catch((/** @type {
|
|
1477
|
+
.catch((/** @type {unknown} */ e) => console.error("Jx server proxy:", e));
|
|
1382
1478
|
});
|
|
1383
1479
|
} else {
|
|
1384
1480
|
doResolve(resolveArgs())
|
|
1385
|
-
.then((/** @type {
|
|
1481
|
+
.then((/** @type {unknown} */ result) => {
|
|
1386
1482
|
s.value = result;
|
|
1387
1483
|
})
|
|
1388
|
-
.catch((/** @type {
|
|
1484
|
+
.catch((/** @type {unknown} */ e) => console.error("Jx server proxy:", e));
|
|
1389
1485
|
}
|
|
1390
1486
|
return s;
|
|
1391
1487
|
}
|
|
@@ -1398,7 +1494,7 @@ async function resolveServerFunctionViaProxy(def, state, key, base) {
|
|
|
1398
1494
|
*
|
|
1399
1495
|
* @param {string} ref
|
|
1400
1496
|
* @param {Record<string, any>} state - Reactive scope proxy (or child scope)
|
|
1401
|
-
* @returns {
|
|
1497
|
+
* @returns {unknown}
|
|
1402
1498
|
*/
|
|
1403
1499
|
export function resolveRef(ref, state) {
|
|
1404
1500
|
if (typeof ref !== "string") return ref;
|
|
@@ -1426,7 +1522,7 @@ export function resolveRef(ref, state) {
|
|
|
1426
1522
|
/**
|
|
1427
1523
|
* Check if v is a Vue ref (including computed).
|
|
1428
1524
|
*
|
|
1429
|
-
* @param {
|
|
1525
|
+
* @param {unknown} v
|
|
1430
1526
|
* @returns {boolean}
|
|
1431
1527
|
*/
|
|
1432
1528
|
export function isSignal(v) {
|
|
@@ -1434,11 +1530,15 @@ export function isSignal(v) {
|
|
|
1434
1530
|
}
|
|
1435
1531
|
|
|
1436
1532
|
/**
|
|
1437
|
-
* @param {
|
|
1533
|
+
* @param {unknown} v
|
|
1438
1534
|
* @returns {boolean}
|
|
1439
1535
|
*/
|
|
1440
1536
|
function isRefObj(v) {
|
|
1441
|
-
return
|
|
1537
|
+
return (
|
|
1538
|
+
v !== null &&
|
|
1539
|
+
typeof v === "object" &&
|
|
1540
|
+
typeof (/** @type {Record<string, unknown>} */ (v).$ref) === "string"
|
|
1541
|
+
);
|
|
1442
1542
|
}
|
|
1443
1543
|
|
|
1444
1544
|
/**
|
|
@@ -1450,12 +1550,12 @@ function isNestedSelector(k) {
|
|
|
1450
1550
|
}
|
|
1451
1551
|
|
|
1452
1552
|
/**
|
|
1453
|
-
* @param {
|
|
1553
|
+
* @param {unknown} obj
|
|
1454
1554
|
* @param {string} path
|
|
1455
|
-
* @returns {
|
|
1555
|
+
* @returns {unknown}
|
|
1456
1556
|
*/
|
|
1457
1557
|
function getPath(obj, path) {
|
|
1458
|
-
return path.split(/[./]/).reduce((o, k) => o?.[k], obj);
|
|
1558
|
+
return path.split(/[./]/).reduce((o, k) => /** @type {Record<string, unknown>} */ (o)?.[k], obj);
|
|
1459
1559
|
}
|
|
1460
1560
|
|
|
1461
1561
|
/**
|
|
@@ -1484,7 +1584,7 @@ export function camelToKebab(s) {
|
|
|
1484
1584
|
/**
|
|
1485
1585
|
* Convert a style rules object to a CSS text string (skipping nested selectors).
|
|
1486
1586
|
*
|
|
1487
|
-
* @param {Record<string,
|
|
1587
|
+
* @param {Record<string, unknown> | object} rules
|
|
1488
1588
|
* @returns {string}
|
|
1489
1589
|
*/
|
|
1490
1590
|
export function toCSSText(rules) {
|
|
@@ -1630,8 +1730,8 @@ export async function defineElement(source, base) {
|
|
|
1630
1730
|
|
|
1631
1731
|
// Merge $props set as JS properties by parent before connection
|
|
1632
1732
|
for (const key of Object.keys(def.state ?? {})) {
|
|
1633
|
-
if (key in this && /** @type {
|
|
1634
|
-
state[key] = /** @type {
|
|
1733
|
+
if (key in this && /** @type {Record<string, unknown>} */ (this)[key] !== undefined) {
|
|
1734
|
+
state[key] = /** @type {Record<string, unknown>} */ (this)[key];
|
|
1635
1735
|
}
|
|
1636
1736
|
}
|
|
1637
1737
|
// Set up property getters/setters that forward into reactive state
|
|
@@ -1639,7 +1739,7 @@ export async function defineElement(source, base) {
|
|
|
1639
1739
|
if (!(key in HTMLElement.prototype)) {
|
|
1640
1740
|
Object.defineProperty(this, key, {
|
|
1641
1741
|
get: () => state[key],
|
|
1642
|
-
set: (/** @type {
|
|
1742
|
+
set: (/** @type {unknown} */ v) => {
|
|
1643
1743
|
state[key] = v;
|
|
1644
1744
|
},
|
|
1645
1745
|
configurable: true,
|
|
@@ -1647,7 +1747,7 @@ export async function defineElement(source, base) {
|
|
|
1647
1747
|
}
|
|
1648
1748
|
}
|
|
1649
1749
|
|
|
1650
|
-
/** @type {
|
|
1750
|
+
/** @type {Record<string, unknown>} */ (this)._state = state;
|
|
1651
1751
|
|
|
1652
1752
|
// Capture light DOM children (for slot distribution) before rendering
|
|
1653
1753
|
const slottedChildren = Array.from(this.childNodes);
|
|
@@ -1677,16 +1777,16 @@ export async function defineElement(source, base) {
|
|
|
1677
1777
|
}
|
|
1678
1778
|
|
|
1679
1779
|
disconnectedCallback() {
|
|
1680
|
-
/** @type {any} */
|
|
1681
|
-
const self = this;
|
|
1780
|
+
/** @type {{ _state?: Record<string, any> }} */
|
|
1781
|
+
const self = /** @type {{ _state?: Record<string, any> }} */ (/** @type {unknown} */ (this));
|
|
1682
1782
|
if (typeof self._state?.onUnmount === "function") {
|
|
1683
1783
|
self._state.onUnmount(self._state);
|
|
1684
1784
|
}
|
|
1685
1785
|
}
|
|
1686
1786
|
|
|
1687
1787
|
adoptedCallback() {
|
|
1688
|
-
/** @type {any} */
|
|
1689
|
-
const self = this;
|
|
1788
|
+
/** @type {{ _state?: Record<string, any> }} */
|
|
1789
|
+
const self = /** @type {{ _state?: Record<string, any> }} */ (/** @type {unknown} */ (this));
|
|
1690
1790
|
if (typeof self._state?.onAdopted === "function") {
|
|
1691
1791
|
self._state.onAdopted(self._state);
|
|
1692
1792
|
}
|
|
@@ -1697,8 +1797,8 @@ export async function defineElement(source, base) {
|
|
|
1697
1797
|
/** @type {string | null} */ oldVal,
|
|
1698
1798
|
/** @type {string | null} */ newVal,
|
|
1699
1799
|
) {
|
|
1700
|
-
/** @type {any} */
|
|
1701
|
-
const self = this;
|
|
1800
|
+
/** @type {{ _state?: Record<string, any> }} */
|
|
1801
|
+
const self = /** @type {{ _state?: Record<string, any> }} */ (/** @type {unknown} */ (this));
|
|
1702
1802
|
if (!self._state || oldVal === newVal) return;
|
|
1703
1803
|
const camelKey = name.replace(
|
|
1704
1804
|
/-([a-z])/g,
|
|
@@ -1720,31 +1820,30 @@ export async function defineElement(source, base) {
|
|
|
1720
1820
|
*
|
|
1721
1821
|
* @param {Record<string, any>} def
|
|
1722
1822
|
* @param {Record<string, any>} state
|
|
1723
|
-
* @param {
|
|
1724
|
-
* @param {
|
|
1823
|
+
* @param {JxRenderOptions} [options]
|
|
1824
|
+
* @param {JxPath} [path]
|
|
1725
1825
|
* @returns {HTMLElement}
|
|
1726
1826
|
*/
|
|
1727
1827
|
function renderCustomElementWithProps(def, state, options, path) {
|
|
1728
|
-
/** @type {any} */
|
|
1729
1828
|
const el = document.createElement(def.tagName);
|
|
1730
1829
|
|
|
1731
|
-
if (options?.onNodeCreated) options.onNodeCreated(el, path, def);
|
|
1830
|
+
if (options?.onNodeCreated) options.onNodeCreated(el, path ?? [], def);
|
|
1732
1831
|
|
|
1733
1832
|
// Set JS properties from $props (before connection)
|
|
1734
1833
|
for (const [key, val] of Object.entries(def.$props ?? {})) {
|
|
1735
1834
|
if (isRefObj(val)) {
|
|
1736
1835
|
const resolved = resolveRef(val.$ref, state);
|
|
1737
|
-
el[key] = resolved;
|
|
1836
|
+
/** @type {Record<string, unknown>} */ (el)[key] = resolved;
|
|
1738
1837
|
// Reactive forwarding: re-set the property when the source changes
|
|
1739
1838
|
effect(() => {
|
|
1740
|
-
el[key] = resolveRef(val.$ref, state);
|
|
1839
|
+
/** @type {Record<string, unknown>} */ (el)[key] = resolveRef(val.$ref, state);
|
|
1741
1840
|
});
|
|
1742
1841
|
} else if (isTemplateString(val)) {
|
|
1743
1842
|
effect(() => {
|
|
1744
|
-
el[key] = evaluateTemplate(val, state);
|
|
1843
|
+
/** @type {Record<string, unknown>} */ (el)[key] = evaluateTemplate(val, state);
|
|
1745
1844
|
});
|
|
1746
1845
|
} else {
|
|
1747
|
-
el[key] = val;
|
|
1846
|
+
/** @type {Record<string, unknown>} */ (el)[key] = val;
|
|
1748
1847
|
}
|
|
1749
1848
|
}
|
|
1750
1849
|
|