@jxsuite/studio 0.24.0 → 0.25.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jxsuite/studio",
3
- "version": "0.24.0",
3
+ "version": "0.25.1",
4
4
  "description": "Jx Studio — visual builder for Jx documents",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -32,9 +32,9 @@
32
32
  "dependencies": {
33
33
  "@atlaskit/pragmatic-drag-and-drop": "^1.8.1",
34
34
  "@atlaskit/pragmatic-drag-and-drop-hitbox": "^1.1.0",
35
- "@jxsuite/parser": "^0.24.0",
36
- "@jxsuite/runtime": "^0.24.0",
37
- "@jxsuite/schema": "^0.24.0",
35
+ "@jxsuite/parser": "^0.25.1",
36
+ "@jxsuite/runtime": "^0.25.1",
37
+ "@jxsuite/schema": "^0.25.1",
38
38
  "@spectrum-web-components/accordion": "^1.12.1",
39
39
  "@spectrum-web-components/action-bar": "1.12.1",
40
40
  "@spectrum-web-components/action-button": "^1.12.1",
@@ -66,7 +66,7 @@ export function defaultDef(tag: string) {
66
66
  else if (tag === "blockquote") def.textContent = "Quote";
67
67
  else if (tag === "pre") def.textContent = "Preformatted text";
68
68
  else if (tag === "input") def.attributes = { type: "text", placeholder: "Enter text..." };
69
- else if (tag === "img") def.attributes = { src: "", alt: "Image" };
69
+ else if (tag === "img") def.attributes = { alt: "Image" };
70
70
  else if (tag === "iframe") def.attributes = { src: "" };
71
71
  else if (tag === "select") def.children = [{ tagName: "option", textContent: "Option 1" }];
72
72
  else if (tag === "ul" || tag === "ol") def.children = [{ tagName: "li", textContent: "Item" }];
@@ -7,6 +7,10 @@
7
7
 
8
8
  import type { JxMutableNode } from "@jxsuite/schema/types";
9
9
 
10
+ const mediaTags = new Set(["img", "video", "source", "iframe", "audio"]);
11
+ const TRANSPARENT_PX =
12
+ "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
13
+
10
14
  /**
11
15
  * Convert a template string to a displayable expression for edit mode. Replaces ${expr} with ❮ expr
12
16
  * ❯ so the runtime renders it as literal text.
@@ -48,9 +52,42 @@ export function prepareForEditMode(node: JxMutableNode): JxMutableNode {
48
52
  const /** @type {Record<string, unknown>} */ obj = node as Record<string, unknown>;
49
53
 
50
54
  const out: Record<string, unknown> = {};
55
+ let needsMediaPlaceholder = false;
56
+ const isMediaElement = mediaTags.has((obj.tagName as string) || "");
57
+
58
+ // Check if this media element lacks a resolvable src (top-level or in attributes)
59
+ if (isMediaElement) {
60
+ const attrs = obj.attributes as Record<string, unknown> | undefined;
61
+ const topSrc = obj.src;
62
+ const attrSrc = attrs?.src;
63
+ const topPoster = obj.poster;
64
+ const attrPoster = attrs?.poster;
65
+ const hasSrc = (topSrc && topSrc !== "") || (attrSrc && attrSrc !== "");
66
+ const hasPoster = (topPoster && topPoster !== "") || (attrPoster && attrPoster !== "");
67
+ if (!hasSrc && !hasPoster) {
68
+ needsMediaPlaceholder = true;
69
+ }
70
+ }
71
+
51
72
  for (const [k, v] of Object.entries(obj)) {
52
- if (k === "state" || k === "$media" || k === "$props" || k === "$elements") {
73
+ if (k === "state" || k === "$media" || k === "$elements") {
53
74
  out[k] = v; // preserve as-is for runtime resolution
75
+ } else if (k === "$props" && v && typeof v === "object") {
76
+ // Process $props values: convert template strings to display format
77
+ const propsOut: Record<string, unknown> = {};
78
+ for (const [pk, pv] of Object.entries(v)) {
79
+ if (typeof pv === "string" && pv.includes("${")) {
80
+ const isUrlAttr = pk === "src" || pk === "href" || pk === "poster" || pk === "action";
81
+ propsOut[pk] = isUrlAttr ? "" : templateToEditDisplay(pv);
82
+ } else if (pv && typeof pv === "object" && (pv as Record<string, unknown>).$ref) {
83
+ const ref = (pv as Record<string, unknown>).$ref as string;
84
+ const label = ref.startsWith("#/state/") ? ref.slice(8) : ref;
85
+ propsOut[pk] = `{${label}}`;
86
+ } else {
87
+ propsOut[pk] = pv;
88
+ }
89
+ }
90
+ out[k] = propsOut;
54
91
  } else if (k === "children") {
55
92
  if (Array.isArray(v)) {
56
93
  out.children = v.map(prepareForEditMode);
@@ -106,6 +143,30 @@ export function prepareForEditMode(node: JxMutableNode): JxMutableNode {
106
143
  ];
107
144
  }
108
145
  }
146
+ } else if (k === "attributes" && isMediaElement && v && typeof v === "object") {
147
+ // Process attributes for media elements: replace src/poster with transparent pixel
148
+ const attrs = v as Record<string, unknown>;
149
+ const processed: Record<string, unknown> = {};
150
+ for (const [ak, av] of Object.entries(attrs)) {
151
+ if (ak === "src" || ak === "poster") {
152
+ if (typeof av === "string" && av !== "" && !av.includes("${")) {
153
+ processed[ak] = av;
154
+ } else {
155
+ needsMediaPlaceholder = true;
156
+ processed[ak] = TRANSPARENT_PX;
157
+ }
158
+ } else if (typeof av === "string" && av.includes("${")) {
159
+ const isUrlAttr = ak === "href" || ak === "action";
160
+ processed[ak] = isUrlAttr ? "" : templateToEditDisplay(av);
161
+ } else if (av && typeof av === "object" && (av as Record<string, unknown>).$ref) {
162
+ const ref = (av as Record<string, unknown>).$ref as string;
163
+ const label = ref.startsWith("#/state/") ? ref.slice(8) : ref;
164
+ processed[ak] = `{${label}}`;
165
+ } else {
166
+ processed[ak] = av;
167
+ }
168
+ }
169
+ out.attributes = processed;
109
170
  } else if (k === "style") {
110
171
  // Replace template strings in style values with empty strings
111
172
  if (v && typeof v === "object") {
@@ -119,16 +180,39 @@ export function prepareForEditMode(node: JxMutableNode): JxMutableNode {
119
180
  }
120
181
  } else if (typeof v === "string" && v.includes("${")) {
121
182
  // Template string in a display property → show raw expression
122
- // For URL-bearing attributes, use empty string to avoid triggering network requests
123
- const isUrlAttr = k === "src" || k === "href" || k === "poster" || k === "action";
124
- out[k] = isUrlAttr ? "" : templateToEditDisplay(v);
183
+ const isMediaSrc =
184
+ (k === "src" || k === "poster") && mediaTags.has((obj.tagName as string) || "");
185
+ if (isMediaSrc) {
186
+ needsMediaPlaceholder = true;
187
+ out[k] = TRANSPARENT_PX;
188
+ } else {
189
+ const isUrlAttr = k === "src" || k === "href" || k === "poster" || k === "action";
190
+ out[k] = isUrlAttr ? "" : templateToEditDisplay(v);
191
+ }
125
192
  } else if (v && typeof v === "object" && (v as Record<string, unknown>).$ref) {
126
193
  // $ref binding → show ref path as literal text
127
194
  const ref = (v as Record<string, unknown>).$ref as string;
128
195
  const label = ref.startsWith("#/state/") ? ref.slice(8) : ref;
129
- out[k] = `{${label}}`;
196
+ const isMediaSrc =
197
+ (k === "src" || k === "poster") && mediaTags.has((obj.tagName as string) || "");
198
+ if (isMediaSrc) {
199
+ needsMediaPlaceholder = true;
200
+ out[k] = TRANSPARENT_PX;
201
+ } else {
202
+ out[k] = `{${label}}`;
203
+ }
130
204
  } else {
131
- out[k] = prepareForEditMode(v as JxMutableNode);
205
+ // Empty src/poster on media elements → use transparent pixel placeholder
206
+ if (
207
+ (k === "src" || k === "poster") &&
208
+ v === "" &&
209
+ mediaTags.has((obj.tagName as string) || "")
210
+ ) {
211
+ needsMediaPlaceholder = true;
212
+ out[k] = TRANSPARENT_PX;
213
+ } else {
214
+ out[k] = prepareForEditMode(v as JxMutableNode);
215
+ }
132
216
  }
133
217
  }
134
218
 
@@ -202,5 +286,13 @@ export function prepareForEditMode(node: JxMutableNode): JxMutableNode {
202
286
  }
203
287
  }
204
288
 
289
+ // Media elements with missing/dynamic src get a placeholder class
290
+ if (needsMediaPlaceholder) {
291
+ const cls = (out.className as string) || "";
292
+ if (!cls.includes("empty-media-placeholder")) {
293
+ out.className = cls ? cls + " empty-media-placeholder" : "empty-media-placeholder";
294
+ }
295
+ }
296
+
205
297
  return out;
206
298
  }