@jxsuite/runtime 2.0.5 → 3.0.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/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  ## Overview
6
6
 
7
- The runtime mounts Jx documents into the browser DOM. It walks the JSON tree, constructs DOM nodes, and wires reactive effects using [`@vue/reactivity`](https://github.com/vuejs/core/tree/main/packages/reactivity). State is tracked automatically — no virtual DOM, no diffing.
7
+ The runtime mounts Jx documents into the browser DOM. It walks the JSON tree, constructs DOM nodes, and wires reactive effects using [`@vue/reactivity`](https://github.com/vuejs/core/tree/main/packages/reactivity). State is tracked automatically: no virtual DOM, no diffing.
8
8
 
9
9
  ## Installation
10
10
 
@@ -55,7 +55,7 @@ Each document goes through four steps:
55
55
  | Map item | `{ "$ref": "$map/item" }` | Current item in `Array` iteration |
56
56
  | Parent | `{ "$ref": "parent#/color" }` | Prop passed via `$props` |
57
57
  | Window | `{ "$ref": "window#/config" }` | Window global |
58
- | External | `{ "$ref": "./card.json" }` | Component doc — via `$switch`/`$elements`, not a bare child |
58
+ | External | `{ "$ref": "./card.json" }` | Component doc (via `$switch`/`$elements`, not a bare child) |
59
59
 
60
60
  ## Custom element support
61
61
 
package/dist/runtime.js CHANGED
@@ -2032,6 +2032,47 @@ function isPrivateStateKey(key) {
2032
2032
  return key.startsWith("#");
2033
2033
  }
2034
2034
 
2035
+ // ../schema/src/asset-paths.ts
2036
+ var URL_TERMINATOR = String.raw`"'\s<>()\\`;
2037
+ function parseSrcset(value) {
2038
+ const out = [];
2039
+ let i = 0;
2040
+ while (i < value.length) {
2041
+ while (i < value.length && (/\s/.test(value[i]) || value[i] === ",")) {
2042
+ i += 1;
2043
+ }
2044
+ if (i >= value.length) {
2045
+ break;
2046
+ }
2047
+ const start = i;
2048
+ while (i < value.length && !/\s/.test(value[i])) {
2049
+ i += 1;
2050
+ }
2051
+ let url = value.slice(start, i);
2052
+ if (url.endsWith(",")) {
2053
+ out.push({ descriptor: "", url: url.replace(/,+$/, "") });
2054
+ continue;
2055
+ }
2056
+ while (i < value.length && /\s/.test(value[i])) {
2057
+ i += 1;
2058
+ }
2059
+ const descStart = i;
2060
+ while (i < value.length && value[i] !== ",") {
2061
+ i += 1;
2062
+ }
2063
+ const descriptor = value.slice(descStart, i).trim();
2064
+ i += 1;
2065
+ url = url.replace(/,+$/, "");
2066
+ if (url !== "") {
2067
+ out.push({ descriptor, url });
2068
+ }
2069
+ }
2070
+ return out;
2071
+ }
2072
+ function formatSrcset(candidates) {
2073
+ return candidates.map(({ url, descriptor }) => descriptor ? `${url} ${descriptor}` : url).join(", ");
2074
+ }
2075
+
2035
2076
  // src/statements.ts
2036
2077
  function statementKind(statement) {
2037
2078
  if ("operator" in statement) {
@@ -2153,7 +2194,7 @@ function checkSchemaVersion($schema) {
2153
2194
  }
2154
2195
  }
2155
2196
  async function Jx(source, target = document.body, options) {
2156
- const base = typeof source === "string" ? new URL(source, location.href).href : location.href;
2197
+ const base = options?.base ? new URL(options.base, location.href).href : typeof source === "string" ? new URL(source, location.href).href : location.href;
2157
2198
  const doc = await resolve(source);
2158
2199
  checkSchemaVersion(doc.$schema);
2159
2200
  if (doc.$elements) {
@@ -2268,6 +2309,52 @@ function canvasAttrName(el, key) {
2268
2309
  }
2269
2310
  return key;
2270
2311
  }
2312
+ var _canvasAssetResolver = null;
2313
+ function setCanvasAssetResolver(fn) {
2314
+ _canvasAssetResolver = fn;
2315
+ }
2316
+ var ASSET_KEYS = new Set(["src", "poster", "data"]);
2317
+ var SRCSET_KEYS = new Set(["srcset", "imagesrcset"]);
2318
+ var HREF_ASSET_TAGS = new Set(["LINK"]);
2319
+ function isAssetKey(tagName, key) {
2320
+ return ASSET_KEYS.has(key) || key === "href" && HREF_ASSET_TAGS.has(tagName);
2321
+ }
2322
+ function canvasAssetValue(tagName, key, value) {
2323
+ if (!_canvasAssetResolver || value === "") {
2324
+ return value;
2325
+ }
2326
+ const name = key.toLowerCase();
2327
+ if (SRCSET_KEYS.has(name)) {
2328
+ const candidates = parseSrcset(value);
2329
+ let changed = false;
2330
+ const next = candidates.map((candidate) => {
2331
+ const resolved = _canvasAssetResolver?.(candidate.url, name) ?? null;
2332
+ if (resolved === null || resolved === candidate.url) {
2333
+ return candidate;
2334
+ }
2335
+ changed = true;
2336
+ return { descriptor: candidate.descriptor, url: resolved };
2337
+ });
2338
+ return changed ? formatSrcset(next) : value;
2339
+ }
2340
+ if (!isAssetKey(tagName, name)) {
2341
+ return value;
2342
+ }
2343
+ return _canvasAssetResolver(value, name) ?? value;
2344
+ }
2345
+ var CSS_URL_RE = /\burl\(\s*(?<q>["']?)(?<u>[^"')]*)\k<q>\s*\)/g;
2346
+ function canvasStyleUrls(value) {
2347
+ if (!_canvasAssetResolver || !value.includes("url(")) {
2348
+ return value;
2349
+ }
2350
+ return value.replaceAll(CSS_URL_RE, (whole, quote, url) => {
2351
+ const resolved = url === "" ? null : _canvasAssetResolver?.(url, "url()") ?? null;
2352
+ return resolved === null ? whole : `url(${quote}${resolved}${quote})`;
2353
+ });
2354
+ }
2355
+ function canvasStyleValue(value) {
2356
+ return transposeCanvasUnits(canvasStyleUrls(value));
2357
+ }
2271
2358
  var _stampPropBindings = false;
2272
2359
  function setStampPropBindings(on) {
2273
2360
  _stampPropBindings = on;
@@ -2441,6 +2528,32 @@ function evaluateTemplate(str, state) {
2441
2528
  const fn = new Function("state", "$map", "item", "index", `return \`${str}\``);
2442
2529
  return fn(state, $map, $map?.item, $map?.index);
2443
2530
  }
2531
+ function isSingleExpression(str) {
2532
+ if (!str.startsWith("${") || !str.endsWith("}")) {
2533
+ return false;
2534
+ }
2535
+ let depth = 0;
2536
+ for (let i = 1;i < str.length; i += 1) {
2537
+ const c = str[i];
2538
+ if (c === "{") {
2539
+ depth += 1;
2540
+ } else if (c === "}") {
2541
+ depth -= 1;
2542
+ if (depth === 0) {
2543
+ return i === str.length - 1;
2544
+ }
2545
+ }
2546
+ }
2547
+ return false;
2548
+ }
2549
+ function evaluateAttrTemplate(str, state) {
2550
+ if (!isSingleExpression(str)) {
2551
+ return evaluateTemplate(str, state);
2552
+ }
2553
+ const $map = state?.$map;
2554
+ const fn = new Function("state", "$map", "item", "index", `return (${str.slice(2, -1)})`);
2555
+ return fn(state, $map, $map?.item, $map?.index);
2556
+ }
2444
2557
  var _moduleCache = new Map;
2445
2558
  async function resolveFunction(def2, state, key, base) {
2446
2559
  if (def2.body && def2.$src) {
@@ -2544,6 +2657,17 @@ var RESERVED_KEYS = new Set([
2544
2657
  "observedAttributes"
2545
2658
  ]);
2546
2659
  var warnedRefChildren = new Set;
2660
+ var SVG_NS = "http://www.w3.org/2000/svg";
2661
+ var MATHML_NS = "http://www.w3.org/1998/Math/MathML";
2662
+ function elementNamespace(tagName, inherited) {
2663
+ if (tagName === "svg") {
2664
+ return SVG_NS;
2665
+ }
2666
+ if (tagName === "math") {
2667
+ return MATHML_NS;
2668
+ }
2669
+ return inherited ?? null;
2670
+ }
2547
2671
  function renderNode(def2, state, options) {
2548
2672
  const path = options?._path ?? [];
2549
2673
  if (typeof def2 === "string" || typeof def2 === "number" || typeof def2 === "boolean") {
@@ -2586,7 +2710,9 @@ function renderNode(def2, state, options) {
2586
2710
  if (def2.$switch) {
2587
2711
  return renderSwitch(def2, localState, options);
2588
2712
  }
2589
- const el = document.createElement(tagName);
2713
+ const ns = elementNamespace(tagName, options?._ns);
2714
+ const childNs = tagName === "foreignObject" ? null : ns;
2715
+ const el = ns ? document.createElementNS(ns, tagName) : document.createElement(tagName);
2590
2716
  if (options?.onNodeCreated) {
2591
2717
  options.onNodeCreated(el, path, def2, localState);
2592
2718
  }
@@ -2595,12 +2721,12 @@ function renderNode(def2, state, options) {
2595
2721
  applyAttributes(el, def2.attributes ?? {}, localState);
2596
2722
  const kids = def2.children;
2597
2723
  if (isMappedArray(kids)) {
2598
- const arrOpts = options ? { ...options, _path: [...path, "children"] } : undefined;
2724
+ const arrOpts = options || childNs ? { ...options, _ns: childNs, _path: [...path, "children"] } : undefined;
2599
2725
  renderMappedArrayInto(el, kids, localState, arrOpts);
2600
2726
  } else if (Array.isArray(kids)) {
2601
2727
  for (let i = 0;i < kids.length; i++) {
2602
2728
  const child = kids[i];
2603
- const childOpts = options ? { ...options, _path: [...path, "children", i] } : undefined;
2729
+ const childOpts = options || childNs ? { ...options, _ns: childNs, _path: [...path, "children", i] } : undefined;
2604
2730
  if (isMappedArray(child)) {
2605
2731
  renderMappedArrayInto(el, child, localState, childOpts);
2606
2732
  } else {
@@ -2659,6 +2785,15 @@ function bindProperty(el, key, val, state) {
2659
2785
  if (boundProp) {
2660
2786
  el.dataset.jxBoundProp = boundProp;
2661
2787
  }
2788
+ const asAsset = (resolved) => typeof resolved === "string" ? canvasAssetValue(el.tagName, key, resolved) : resolved;
2789
+ const write = (resolved) => {
2790
+ const node = el;
2791
+ if (key === "className" && !(node instanceof HTMLElement)) {
2792
+ node.setAttribute("class", resolved == null ? "" : String(resolved));
2793
+ return;
2794
+ }
2795
+ target[key] = resolved;
2796
+ };
2662
2797
  if (isRefObj(val)) {
2663
2798
  const refVal = val;
2664
2799
  if (key === "id") {
@@ -2666,17 +2801,17 @@ function bindProperty(el, key, val, state) {
2666
2801
  return;
2667
2802
  }
2668
2803
  effect(() => {
2669
- target[key] = resolveRef(refVal.$ref, state);
2804
+ write(asAsset(resolveRef(refVal.$ref, state)));
2670
2805
  });
2671
2806
  return;
2672
2807
  }
2673
2808
  if (isTemplateString(val)) {
2674
2809
  effect(() => {
2675
- target[key] = evaluateTemplate(val, state);
2810
+ write(asAsset(evaluateTemplate(val, state)));
2676
2811
  });
2677
2812
  return;
2678
2813
  }
2679
- target[key] = val;
2814
+ write(asAsset(val));
2680
2815
  }
2681
2816
  function applyStyle(el, styleDef, mediaQueries = {}, state = {}) {
2682
2817
  const nested = {};
@@ -2711,19 +2846,19 @@ function applyStyle(el, styleDef, mediaQueries = {}, state = {}) {
2711
2846
  if (prop.startsWith("--")) {
2712
2847
  if (isTemplateString(val)) {
2713
2848
  effect(() => {
2714
- el.style.setProperty(prop, transposeCanvasUnits(evaluateTemplate(val, state)));
2849
+ el.style.setProperty(prop, canvasStyleValue(evaluateTemplate(val, state)));
2715
2850
  });
2716
2851
  } else {
2717
- el.style.setProperty(prop, transposeCanvasUnits(scalar));
2852
+ el.style.setProperty(prop, canvasStyleValue(scalar));
2718
2853
  }
2719
2854
  } else if (isTemplateString(val)) {
2720
2855
  effect(() => {
2721
- el.style[prop] = transposeCanvasUnits(evaluateTemplate(val, state));
2856
+ el.style[prop] = canvasStyleValue(evaluateTemplate(val, state));
2722
2857
  });
2723
2858
  } else if (mediaOverriddenProps.has(prop)) {
2724
2859
  baseDecls[prop] = scalar;
2725
2860
  } else {
2726
- el.style[prop] = transposeCanvasUnits(scalar);
2861
+ el.style[prop] = canvasStyleValue(scalar);
2727
2862
  }
2728
2863
  }
2729
2864
  const hasNested = Object.keys(nested).length > 0;
@@ -2782,7 +2917,7 @@ function applyStyle(el, styleDef, mediaQueries = {}, state = {}) {
2782
2917
  if (key === "@--") {
2783
2918
  continue;
2784
2919
  }
2785
- const query = key.startsWith("@--") ? mediaQueries[key.slice(1)] ?? key.slice(1) : key.startsWith("@(") ? key.slice(1) : null;
2920
+ const query = resolveAtQuery(key, mediaQueries);
2786
2921
  const atRule = query === null ? key : `@media ${query}`;
2787
2922
  const scope = `[data-jx="${uid}"]`;
2788
2923
  const scheme = query === null ? null : pureSchemeOf(query);
@@ -2815,15 +2950,35 @@ function reapplyStyle(el, styleDef, mediaQueries = {}, state = {}) {
2815
2950
  delete el.dataset.jx;
2816
2951
  applyStyle(el, styleDef ?? {}, mediaQueries, state);
2817
2952
  }
2953
+ var ENUMERATED_ATTRS = new Set(["contenteditable", "draggable", "spellcheck"]);
2954
+ function booleanAttrValue(name, value) {
2955
+ const lower = name.toLowerCase();
2956
+ if (lower.startsWith("aria-") || ENUMERATED_ATTRS.has(lower)) {
2957
+ return String(value);
2958
+ }
2959
+ return value ? "" : null;
2960
+ }
2818
2961
  function applyAttributes(el, attrs, state) {
2819
2962
  for (const [k, v] of Object.entries(attrs)) {
2820
2963
  const attr = canvasAttrName(el, k);
2964
+ const write = (resolved) => {
2965
+ if (typeof resolved === "boolean") {
2966
+ const text = booleanAttrValue(attr, resolved);
2967
+ if (text === null) {
2968
+ el.removeAttribute(attr);
2969
+ } else {
2970
+ el.setAttribute(attr, text);
2971
+ }
2972
+ return;
2973
+ }
2974
+ el.setAttribute(attr, canvasAssetValue(el.tagName, k, String(resolved ?? "")));
2975
+ };
2821
2976
  if (isRefObj(v)) {
2822
- effect(() => el.setAttribute(attr, String(resolveRef(v.$ref, state) ?? "")));
2977
+ effect(() => write(resolveRef(v.$ref, state)));
2823
2978
  } else if (isTemplateString(v)) {
2824
- effect(() => el.setAttribute(attr, String(evaluateTemplate(v, state))));
2979
+ effect(() => write(evaluateAttrTemplate(v, state)));
2825
2980
  } else {
2826
- el.setAttribute(attr, String(v));
2981
+ write(v);
2827
2982
  }
2828
2983
  }
2829
2984
  }
@@ -3502,7 +3657,18 @@ function camelToKebab(s) {
3502
3657
  return s.replaceAll(/[A-Z]/g, (c) => `-${c.toLowerCase()}`);
3503
3658
  }
3504
3659
  function toCSSText(rules) {
3505
- return Object.entries(rules).filter(([k, v]) => !isNestedSelector(k) && (v === null || typeof v !== "object")).map(([p, v]) => `${camelToKebab(p)}: ${transposeCanvasUnits(String(v))}`).join("; ");
3660
+ return Object.entries(rules).filter(([k, v]) => !isNestedSelector(k) && (v === null || typeof v !== "object")).map(([p, v]) => `${camelToKebab(p)}: ${canvasStyleValue(String(v))}`).join("; ");
3661
+ }
3662
+ var MEDIA_TYPES = new Set(["all", "print", "screen", "speech"]);
3663
+ function resolveAtQuery(atKey, mediaQueries) {
3664
+ if (atKey.startsWith("@--")) {
3665
+ return mediaQueries[atKey.slice(1)] ?? atKey.slice(1);
3666
+ }
3667
+ if (atKey.startsWith("@(")) {
3668
+ const inner = atKey.slice(2, -1).trim();
3669
+ return MEDIA_TYPES.has(inner.toLowerCase()) ? inner : atKey.slice(1);
3670
+ }
3671
+ return null;
3506
3672
  }
3507
3673
  var COLOR_SCHEME_ATTR = "data-color-scheme";
3508
3674
  var COLOR_SCHEME_STORAGE_KEY = "jx-color-scheme";
@@ -3564,9 +3730,14 @@ function injectHead(entries, _base) {
3564
3730
  const attrs = { ...entry.attributes };
3565
3731
  for (const key of ["href", "src"]) {
3566
3732
  const v = attrs[key];
3567
- if (typeof v === "string" && v && !v.startsWith("/") && !v.startsWith(".") && !v.startsWith("http")) {
3733
+ if (typeof v !== "string" || v === "") {
3734
+ continue;
3735
+ }
3736
+ if (!v.startsWith("/") && !v.startsWith(".") && !v.startsWith("http")) {
3568
3737
  attrs[key] = `/node_modules/${v}`;
3738
+ continue;
3569
3739
  }
3740
+ attrs[key] = canvasAssetValue(tag.toUpperCase(), key, v);
3570
3741
  }
3571
3742
  const selector = `${tag}${attrs.href ? `[href="${attrs.href}"]` : ""}${attrs.src ? `[src="${attrs.src}"]` : ""}`;
3572
3743
  if (selector !== tag && document.head.querySelector(selector)) {
@@ -3681,6 +3852,9 @@ async function defineElement(source, baseUrl) {
3681
3852
  }
3682
3853
  applyStyle(this, def2.style ?? {}, state["$media"] ?? {}, state);
3683
3854
  applyAttributes(this, def2.attributes ?? {}, state);
3855
+ if (!Array.isArray(def2.children) && def2.textContent !== undefined) {
3856
+ bindProperty(this, "textContent", def2.textContent, state);
3857
+ }
3684
3858
  const children = Array.isArray(def2.children) ? def2.children : [];
3685
3859
  for (const childDef of children) {
3686
3860
  this.append(renderNode(childDef, state));
@@ -3727,19 +3901,21 @@ function renderCustomElementWithProps(def2, state, options, path) {
3727
3901
  if (refusePrivateProp(key, "$props")) {
3728
3902
  continue;
3729
3903
  }
3904
+ const write = (resolved) => {
3905
+ el[key] = typeof resolved === "string" ? canvasAssetValue(el.tagName, key, resolved) : resolved;
3906
+ };
3730
3907
  if (isRefObj(val)) {
3731
3908
  const refVal = val;
3732
- const resolved = resolveRef(refVal.$ref, state);
3733
- el[key] = resolved;
3909
+ write(resolveRef(refVal.$ref, state));
3734
3910
  effect(() => {
3735
- el[key] = resolveRef(refVal.$ref, state);
3911
+ write(resolveRef(refVal.$ref, state));
3736
3912
  });
3737
3913
  } else if (isTemplateString(val)) {
3738
3914
  effect(() => {
3739
- el[key] = evaluateTemplate(val, state);
3915
+ write(evaluateTemplate(val, state));
3740
3916
  });
3741
3917
  } else {
3742
- el[key] = val;
3918
+ write(val);
3743
3919
  }
3744
3920
  }
3745
3921
  applyStyle(el, def2.style ?? {}, state["$media"] ?? {}, state);
@@ -3791,22 +3967,27 @@ export {
3791
3967
  SUPPORTED_SCHEMA_VERSION,
3792
3968
  _resetPrivatePropWarnings,
3793
3969
  applyStyle,
3970
+ booleanAttrValue,
3794
3971
  buildScope,
3795
3972
  camelToKebab,
3973
+ canvasStyleValue,
3796
3974
  defineElement,
3797
3975
  elementStyleTags,
3798
3976
  hasSchemaKeywords,
3799
3977
  isSignal,
3978
+ isSingleExpression,
3800
3979
  observeScope,
3801
3980
  pureSchemeOf,
3802
3981
  reapplyStyle,
3803
3982
  renderNode,
3804
3983
  resolve,
3984
+ resolveAtQuery,
3805
3985
  resolvePrototype,
3806
3986
  resolveRef,
3807
3987
  resolveTagName,
3808
3988
  runScoped,
3809
3989
  schemeSelectors,
3990
+ setCanvasAssetResolver,
3810
3991
  setCanvasDelinkAnchors,
3811
3992
  setCanvasViewportTranspose,
3812
3993
  setResolveToken,
@@ -3819,5 +4000,5 @@ export {
3819
4000
  transposeCanvasUnits
3820
4001
  };
3821
4002
 
3822
- //# debugId=619B22D180EBAE7164756E2164756E21
4003
+ //# debugId=33CA9AA2BD53687064756E2164756E21
3823
4004
  //# sourceMappingURL=runtime.js.map