@salesforce/webapp-experimental 1.73.1 → 1.75.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.
@@ -12,6 +12,154 @@
12
12
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
13
13
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
14
14
 
15
+ // src/design/interactions/utils/sourceUtils.ts
16
+ function parseOptionalInt(value) {
17
+ if (value === null || value === void 0 || value === "") {
18
+ return null;
19
+ }
20
+ const parsed = Number.parseInt(String(value), 10);
21
+ return Number.isFinite(parsed) ? parsed : null;
22
+ }
23
+ function parseSourceFileAttribute(value) {
24
+ const match = /^(.*):(\d+):(\d+)$/.exec(value);
25
+ if (!match) {
26
+ return { fileName: value, lineNumber: null, columnNumber: null };
27
+ }
28
+ return {
29
+ fileName: match[1] ?? value,
30
+ lineNumber: parseOptionalInt(match[2]),
31
+ columnNumber: parseOptionalInt(match[3])
32
+ };
33
+ }
34
+ function getSourceFromDataAttributes(element) {
35
+ if (!element) {
36
+ return null;
37
+ }
38
+ const source = element.getAttribute("data-source-file") || null;
39
+ if (!source) {
40
+ return null;
41
+ }
42
+ return parseSourceFileAttribute(source);
43
+ }
44
+ function findElementsBySourceLocation(location) {
45
+ const results = [];
46
+ const elements = document.querySelectorAll("[data-source-file]");
47
+ for (const el of elements) {
48
+ const elSource = getSourceFromDataAttributes(el);
49
+ if (elSource && elSource.fileName === location.fileName && elSource.lineNumber === location.lineNumber && elSource.columnNumber === location.columnNumber) {
50
+ results.push(el);
51
+ }
52
+ }
53
+ return results;
54
+ }
55
+ function parseSourceLocation(sl) {
56
+ if (!sl || typeof sl !== "object") return void 0;
57
+ const obj = sl;
58
+ return {
59
+ fileName: String(obj.sourceFile ?? ""),
60
+ lineNumber: typeof obj.lineNumber === "number" ? obj.lineNumber : null,
61
+ columnNumber: typeof obj.columnNumber === "number" ? obj.columnNumber : null
62
+ };
63
+ }
64
+ function getLabelFromSource(element) {
65
+ if (!element) {
66
+ return "";
67
+ }
68
+ const source = element.getAttribute("data-source-file");
69
+ if (!source) {
70
+ return element.tagName ? element.tagName.toLowerCase() : "";
71
+ }
72
+ const { fileName } = parseSourceFileAttribute(source);
73
+ const parts = fileName.split(/[/\\]/);
74
+ const baseName = parts[parts.length - 1] || fileName;
75
+ console.log("baseName", baseName);
76
+ return baseName;
77
+ }
78
+
79
+ // src/design/interactions/editableManager.ts
80
+ var TEXT_TAGS = ["H1", "H2", "H3", "H4", "H5", "H6", "P", "SPAN", "A", "BUTTON", "LABEL"];
81
+ var EditableManager = class {
82
+ constructor(communicationManager) {
83
+ __publicField(this, "communicationManager");
84
+ __publicField(this, "boundHandleBlur");
85
+ __publicField(this, "boundHandleKeydown");
86
+ __publicField(this, "boundHandleInput");
87
+ __publicField(this, "editableGroup");
88
+ this.communicationManager = communicationManager;
89
+ this.boundHandleBlur = this._handleBlur.bind(this);
90
+ this.boundHandleKeydown = this._handleKeydown.bind(this);
91
+ this.boundHandleInput = this._handleInput.bind(this);
92
+ this.editableGroup = [];
93
+ }
94
+ makeEditableIfText(element) {
95
+ if (!this._isTextElement(element)) {
96
+ return;
97
+ }
98
+ const source = getSourceFromDataAttributes(element);
99
+ const siblings = source ? findElementsBySourceLocation(source) : [];
100
+ const others = siblings.filter((el) => el !== element);
101
+ this.editableGroup = [element, ...others];
102
+ for (const el of this.editableGroup) {
103
+ el.dataset.originalText = el.textContent ?? "";
104
+ }
105
+ element.contentEditable = "true";
106
+ element.addEventListener("blur", this.boundHandleBlur);
107
+ element.addEventListener("keydown", this.boundHandleKeydown);
108
+ element.addEventListener("input", this.boundHandleInput);
109
+ }
110
+ removeEditable(element) {
111
+ if (element.contentEditable === "true") {
112
+ element.contentEditable = "false";
113
+ }
114
+ element.removeEventListener("blur", this.boundHandleBlur);
115
+ element.removeEventListener("keydown", this.boundHandleKeydown);
116
+ element.removeEventListener("input", this.boundHandleInput);
117
+ for (const el of this.editableGroup) {
118
+ delete el.dataset.originalText;
119
+ }
120
+ this.editableGroup = [];
121
+ }
122
+ _isTextElement(element) {
123
+ return TEXT_TAGS.includes(element.tagName) && (element.textContent ?? "").trim().length > 0 && element.dataset.textType === "static";
124
+ }
125
+ _handleInput(e) {
126
+ const primary = e.target;
127
+ const text = primary.textContent ?? "";
128
+ for (let i = 1; i < this.editableGroup.length; i++) {
129
+ this.editableGroup[i].textContent = text;
130
+ }
131
+ }
132
+ _handleBlur(e) {
133
+ const element = e.target;
134
+ const newText = element.textContent ?? "";
135
+ const originalText = element.dataset.originalText ?? "";
136
+ if (newText !== originalText) {
137
+ for (const el of this.editableGroup) {
138
+ el.dataset.originalText = newText;
139
+ }
140
+ if (this.communicationManager) {
141
+ this.communicationManager.notifyTextChange(element, originalText, newText);
142
+ }
143
+ }
144
+ this.removeEditable(element);
145
+ }
146
+ _handleKeydown(e) {
147
+ const element = e.target;
148
+ if (e.key === "Enter" && !e.shiftKey) {
149
+ e.preventDefault();
150
+ element.blur();
151
+ }
152
+ if (e.key === "Escape") {
153
+ for (const el of this.editableGroup) {
154
+ if (el.dataset.originalText) {
155
+ el.textContent = el.dataset.originalText;
156
+ }
157
+ }
158
+ element.blur();
159
+ }
160
+ }
161
+ };
162
+
15
163
  // src/design/interactions/utils/cssUtils.ts
16
164
  function getElementStyles(element) {
17
165
  if (!element) return {};
@@ -74,61 +222,6 @@
74
222
  };
75
223
  }
76
224
 
77
- // src/design/interactions/utils/sourceUtils.ts
78
- function parseOptionalInt(value) {
79
- if (value === null || value === void 0 || value === "") {
80
- return null;
81
- }
82
- const parsed = Number.parseInt(String(value), 10);
83
- return Number.isFinite(parsed) ? parsed : null;
84
- }
85
- function parseSourceFileAttribute(value) {
86
- const match = /^(.*):(\d+):(\d+)$/.exec(value);
87
- if (!match) {
88
- return { fileName: value, lineNumber: null, columnNumber: null };
89
- }
90
- return {
91
- fileName: match[1] ?? value,
92
- lineNumber: parseOptionalInt(match[2]),
93
- columnNumber: parseOptionalInt(match[3])
94
- };
95
- }
96
- function getSourceFromDataAttributes(element) {
97
- if (!element) {
98
- return null;
99
- }
100
- const source = element.getAttribute("data-source-file") || null;
101
- if (!source) {
102
- return null;
103
- }
104
- return parseSourceFileAttribute(source);
105
- }
106
- function findElementsBySourceLocation(location) {
107
- const results = [];
108
- const elements = document.querySelectorAll("[data-source-file]");
109
- for (const el of elements) {
110
- const elSource = getSourceFromDataAttributes(el);
111
- if (elSource && elSource.fileName === location.fileName && elSource.lineNumber === location.lineNumber && elSource.columnNumber === location.columnNumber) {
112
- results.push(el);
113
- }
114
- }
115
- return results;
116
- }
117
- function getLabelFromSource(element) {
118
- if (!element) {
119
- return "";
120
- }
121
- const source = element.getAttribute("data-source-file");
122
- if (!source) {
123
- return element.tagName ? element.tagName.toLowerCase() : "";
124
- }
125
- const { fileName } = parseSourceFileAttribute(source);
126
- const parts = fileName.split(/[/\\]/);
127
- const baseName = parts[parts.length - 1] || fileName;
128
- console.log("baseName", baseName);
129
- return baseName;
130
- }
131
-
132
225
  // src/design/interactions/communicationManager.ts
133
226
  var CommunicationManager = class {
134
227
  constructor() {
@@ -148,6 +241,8 @@
148
241
  element.classList.add("design-mode-selected");
149
242
  }
150
243
  const debugSource = getSourceFromDataAttributes(element);
244
+ const textType = element.dataset?.textType ?? "none";
245
+ const hasNonEditableText = TEXT_TAGS.includes(element.tagName) && (textType === "dynamic" || textType === "mixed");
151
246
  try {
152
247
  if (window.parent !== window) {
153
248
  window.parent.postMessage(
@@ -163,7 +258,8 @@
163
258
  styles: {
164
259
  ...styles
165
260
  },
166
- debugSource
261
+ debugSource,
262
+ hasNonEditableText
167
263
  }
168
264
  },
169
265
  "*"
@@ -188,20 +284,18 @@
188
284
  */
189
285
  notifyTextChange(element, originalText, newText) {
190
286
  const label = getLabelFromSource(element);
287
+ const debugSource = getSourceFromDataAttributes(element);
191
288
  try {
192
289
  if (window.parent !== window) {
193
290
  window.parent.postMessage(
194
291
  {
195
292
  type: "text-changed",
196
293
  change: {
197
- component: { name: label },
198
- element: {
199
- tagName: element.tagName,
200
- classList: Array.from(element.classList),
201
- id: element.id || "",
202
- originalText,
203
- newText
204
- }
294
+ componentName: label,
295
+ tagName: element.tagName,
296
+ originalText,
297
+ newText,
298
+ debugSource
205
299
  }
206
300
  },
207
301
  "*"
@@ -301,89 +395,6 @@
301
395
  }
302
396
  };
303
397
 
304
- // src/design/interactions/editableManager.ts
305
- var EditableManager = class {
306
- constructor(communicationManager) {
307
- __publicField(this, "communicationManager");
308
- __publicField(this, "textTags");
309
- __publicField(this, "boundHandleBlur");
310
- __publicField(this, "boundHandleKeydown");
311
- this.communicationManager = communicationManager;
312
- this.textTags = ["H1", "H2", "H3", "H4", "H5", "H6", "P", "SPAN", "A", "BUTTON", "LABEL"];
313
- this.boundHandleBlur = this._handleBlur.bind(this);
314
- this.boundHandleKeydown = this._handleKeydown.bind(this);
315
- }
316
- /**
317
- * Make an element editable if it's a text element
318
- * @param element - The element to make editable
319
- */
320
- makeEditableIfText(element) {
321
- if (!this._isTextElement(element)) {
322
- return;
323
- }
324
- element.contentEditable = "true";
325
- element.dataset.originalText = element.textContent ?? "";
326
- element.addEventListener("blur", this.boundHandleBlur);
327
- element.addEventListener("keydown", this.boundHandleKeydown);
328
- }
329
- /**
330
- * Remove editable state from an element
331
- * @param element - The element to make non-editable
332
- */
333
- removeEditable(element) {
334
- if (element.contentEditable === "true") {
335
- element.contentEditable = "false";
336
- }
337
- delete element.dataset.originalText;
338
- element.removeEventListener("blur", this.boundHandleBlur);
339
- element.removeEventListener("keydown", this.boundHandleKeydown);
340
- }
341
- /**
342
- * Check if an element is a text element that can be made editable
343
- * @private
344
- * @param element - The element to check
345
- * @returns True if the element can be made editable
346
- */
347
- _isTextElement(element) {
348
- return this.textTags.includes(element.tagName) && (element.textContent ?? "").trim().length > 0 && element.dataset.textType === "static";
349
- }
350
- /**
351
- * Handle blur event on editable element
352
- * @private
353
- * @param e - The blur event
354
- */
355
- _handleBlur(e) {
356
- const element = e.target;
357
- const newText = element.textContent ?? "";
358
- const originalText = element.dataset.originalText ?? "";
359
- if (newText !== originalText) {
360
- element.dataset.originalText = newText;
361
- if (this.communicationManager) {
362
- this.communicationManager.notifyTextChange(element, originalText, newText);
363
- }
364
- }
365
- this.removeEditable(element);
366
- }
367
- /**
368
- * Handle keydown event on editable element
369
- * @private
370
- * @param e - The keydown event
371
- */
372
- _handleKeydown(e) {
373
- const element = e.target;
374
- if (e.key === "Enter" && !e.shiftKey) {
375
- e.preventDefault();
376
- element.blur();
377
- }
378
- if (e.key === "Escape") {
379
- if (element.dataset.originalText) {
380
- element.textContent = element.dataset.originalText;
381
- }
382
- element.blur();
383
- }
384
- }
385
- };
386
-
387
398
  // src/design/interactions/eventHandlers.ts
388
399
  var EventHandlers = class {
389
400
  constructor(isInteractionsActive, componentMatcher, styleManager, editableManager, communicationManager) {
@@ -673,23 +684,29 @@
673
684
  this.eventHandlers.clearAll();
674
685
  console.log("Design Mode Interactions disabled");
675
686
  }
687
+ resolveTargets(sourceLocation) {
688
+ let location = sourceLocation ?? null;
689
+ if (!location?.fileName) {
690
+ const selectedElement = this.eventHandlers.getSelectedElement();
691
+ location = getSourceFromDataAttributes(selectedElement);
692
+ }
693
+ return location ? findElementsBySourceLocation(location) : [];
694
+ }
676
695
  /**
677
696
  * Apply a style change to all elements at the given source location.
678
697
  * When sourceLocation is provided (undo/redo), it is used directly.
679
698
  * Otherwise the source location is read from the currently selected element.
680
699
  */
681
700
  applyStyleChange(property, value, sourceLocation) {
682
- let location = sourceLocation ?? null;
683
- if (!location?.fileName) {
684
- const selectedElement = this.eventHandlers.getSelectedElement();
685
- location = getSourceFromDataAttributes(selectedElement);
686
- }
687
- if (!location) return;
688
- const targets = findElementsBySourceLocation(location);
689
- for (const el of targets) {
701
+ for (const el of this.resolveTargets(sourceLocation)) {
690
702
  el.style[property] = value;
691
703
  }
692
704
  }
705
+ applyTextChange(text, sourceLocation) {
706
+ for (const el of this.resolveTargets(sourceLocation)) {
707
+ el.textContent = text;
708
+ }
709
+ }
693
710
  /**
694
711
  * Cleanup and remove event listeners
695
712
  */
@@ -724,16 +741,16 @@
724
741
  const data = event.data;
725
742
  const typed = data && typeof data === "object" ? data : null;
726
743
  if (typed && typed.type === "style-change") {
727
- const sl = typed.sourceLocation;
728
- const sourceLocation = sl && typeof sl === "object" ? {
729
- fileName: String(sl.sourceFile ?? ""),
730
- lineNumber: typeof sl.lineNumber === "number" ? sl.lineNumber : null,
731
- columnNumber: typeof sl.columnNumber === "number" ? sl.columnNumber : null
732
- } : void 0;
733
744
  interactions.applyStyleChange(
734
745
  String(typed.property ?? ""),
735
746
  String(typed.value ?? ""),
736
- sourceLocation
747
+ parseSourceLocation(typed.sourceLocation)
748
+ );
749
+ }
750
+ if (typed && typed.type === "text-change") {
751
+ interactions.applyTextChange(
752
+ String(typed.text ?? ""),
753
+ parseSourceLocation(typed.sourceLocation)
737
754
  );
738
755
  }
739
756
  if (typed && typed.type === "enable-interactions") {
@@ -1 +1 @@
1
- {"version":3,"file":"communicationManager.d.ts","sourceRoot":"","sources":["../../../src/design/interactions/communicationManager.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAkBH,qBAAa,oBAAoB;;IAKhC;;;OAGG;IACH,uBAAuB,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI;IAwDnD;;;;;OAKG;IACH,gBAAgB,CAAC,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI;IA2BnF;;OAEG;IACH,4BAA4B,IAAI,IAAI;CAepC"}
1
+ {"version":3,"file":"communicationManager.d.ts","sourceRoot":"","sources":["../../../src/design/interactions/communicationManager.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAmBH,qBAAa,oBAAoB;;IAKhC;;;OAGG;IACH,uBAAuB,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI;IA6DnD;;;;;OAKG;IACH,gBAAgB,CAAC,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI;IAyBnF;;OAEG;IACH,4BAA4B,IAAI,IAAI;CAepC"}
@@ -7,6 +7,7 @@
7
7
  * Communication Manager Module
8
8
  * Handles communication with the parent window (VS Code extension)
9
9
  */
10
+ import { TEXT_TAGS } from "./editableManager.js";
10
11
  import { getElementStyles } from "./utils/cssUtils.js";
11
12
  import { getLabelFromSource, getSourceFromDataAttributes } from "./utils/sourceUtils.js";
12
13
  export class CommunicationManager {
@@ -31,6 +32,8 @@ export class CommunicationManager {
31
32
  }
32
33
  // Source location metadata injected at compile time (babel-plugin-enhanced-locator)
33
34
  const debugSource = getSourceFromDataAttributes(element);
35
+ const textType = element.dataset?.textType ?? "none";
36
+ const hasNonEditableText = TEXT_TAGS.includes(element.tagName) && (textType === "dynamic" || textType === "mixed");
34
37
  // Send message to parent window
35
38
  try {
36
39
  if (window.parent !== window) {
@@ -47,6 +50,7 @@ export class CommunicationManager {
47
50
  ...styles,
48
51
  },
49
52
  debugSource: debugSource,
53
+ hasNonEditableText,
50
54
  },
51
55
  }, "*");
52
56
  }
@@ -72,19 +76,17 @@ export class CommunicationManager {
72
76
  */
73
77
  notifyTextChange(element, originalText, newText) {
74
78
  const label = getLabelFromSource(element);
79
+ const debugSource = getSourceFromDataAttributes(element);
75
80
  try {
76
81
  if (window.parent !== window) {
77
82
  window.parent.postMessage({
78
83
  type: "text-changed",
79
84
  change: {
80
- component: { name: label },
81
- element: {
82
- tagName: element.tagName,
83
- classList: Array.from(element.classList),
84
- id: element.id || "",
85
- originalText: originalText,
86
- newText: newText,
87
- },
85
+ componentName: label,
86
+ tagName: element.tagName,
87
+ originalText,
88
+ newText,
89
+ debugSource,
88
90
  },
89
91
  }, "*");
90
92
  }
@@ -3,48 +3,22 @@
3
3
  * All rights reserved.
4
4
  * For full license text, see the LICENSE.txt file
5
5
  */
6
- /**
7
- * Editable Manager Module
8
- * Handles making elements editable and notifying the extension of text changes.
9
- * History/undo for style changes is owned by the design property panel (host); this module does not use history.
10
- */
11
6
  interface CommunicationManagerLike {
12
7
  notifyTextChange: (element: HTMLElement, originalText: string, newText: string) => void;
13
8
  }
9
+ export declare const TEXT_TAGS: string[];
14
10
  export declare class EditableManager {
15
11
  private communicationManager?;
16
- private textTags;
17
12
  private boundHandleBlur;
18
13
  private boundHandleKeydown;
14
+ private boundHandleInput;
15
+ private editableGroup;
19
16
  constructor(communicationManager?: CommunicationManagerLike);
20
- /**
21
- * Make an element editable if it's a text element
22
- * @param element - The element to make editable
23
- */
24
17
  makeEditableIfText(element: HTMLElement): void;
25
- /**
26
- * Remove editable state from an element
27
- * @param element - The element to make non-editable
28
- */
29
18
  removeEditable(element: HTMLElement): void;
30
- /**
31
- * Check if an element is a text element that can be made editable
32
- * @private
33
- * @param element - The element to check
34
- * @returns True if the element can be made editable
35
- */
36
19
  private _isTextElement;
37
- /**
38
- * Handle blur event on editable element
39
- * @private
40
- * @param e - The blur event
41
- */
20
+ private _handleInput;
42
21
  private _handleBlur;
43
- /**
44
- * Handle keydown event on editable element
45
- * @private
46
- * @param e - The keydown event
47
- */
48
22
  private _handleKeydown;
49
23
  }
50
24
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"editableManager.d.ts","sourceRoot":"","sources":["../../../src/design/interactions/editableManager.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;;;GAIG;AAEH,UAAU,wBAAwB;IACjC,gBAAgB,EAAE,CAAC,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACxF;AAED,qBAAa,eAAe;IAC3B,OAAO,CAAC,oBAAoB,CAAC,CAA2B;IACxD,OAAO,CAAC,QAAQ,CAAW;IAC3B,OAAO,CAAC,eAAe,CAAqB;IAC5C,OAAO,CAAC,kBAAkB,CAA6B;gBAE3C,oBAAoB,CAAC,EAAE,wBAAwB;IAO3D;;;OAGG;IACH,kBAAkB,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI;IAY9C;;;OAGG;IACH,cAAc,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI;IAS1C;;;;;OAKG;IACH,OAAO,CAAC,cAAc;IAQtB;;;;OAIG;IACH,OAAO,CAAC,WAAW;IAkBnB;;;;OAIG;IACH,OAAO,CAAC,cAAc;CAetB"}
1
+ {"version":3,"file":"editableManager.d.ts","sourceRoot":"","sources":["../../../src/design/interactions/editableManager.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAUH,UAAU,wBAAwB;IACjC,gBAAgB,EAAE,CAAC,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACxF;AAED,eAAO,MAAM,SAAS,UAA4E,CAAC;AAEnG,qBAAa,eAAe;IAC3B,OAAO,CAAC,oBAAoB,CAAC,CAA2B;IACxD,OAAO,CAAC,eAAe,CAAqB;IAC5C,OAAO,CAAC,kBAAkB,CAA6B;IACvD,OAAO,CAAC,gBAAgB,CAAqB;IAC7C,OAAO,CAAC,aAAa,CAAgB;gBAEzB,oBAAoB,CAAC,EAAE,wBAAwB;IAQ3D,kBAAkB,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI;IAoB9C,cAAc,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI;IAe1C,OAAO,CAAC,cAAc;IAQtB,OAAO,CAAC,YAAY;IAQpB,OAAO,CAAC,WAAW;IAkBnB,OAAO,CAAC,cAAc;CAiBtB"}
@@ -3,77 +3,80 @@
3
3
  * All rights reserved.
4
4
  * For full license text, see the LICENSE.txt file
5
5
  */
6
+ /**
7
+ * Editable Manager Module
8
+ * Handles making elements editable and notifying the extension of text changes.
9
+ * History/undo for style changes is owned by the design property panel (host); this module does not use history.
10
+ */
11
+ import { findElementsBySourceLocation, getSourceFromDataAttributes } from "./utils/sourceUtils.js";
12
+ export const TEXT_TAGS = ["H1", "H2", "H3", "H4", "H5", "H6", "P", "SPAN", "A", "BUTTON", "LABEL"];
6
13
  export class EditableManager {
7
14
  communicationManager;
8
- textTags;
9
15
  boundHandleBlur;
10
16
  boundHandleKeydown;
17
+ boundHandleInput;
18
+ editableGroup;
11
19
  constructor(communicationManager) {
12
20
  this.communicationManager = communicationManager;
13
- this.textTags = ["H1", "H2", "H3", "H4", "H5", "H6", "P", "SPAN", "A", "BUTTON", "LABEL"];
14
21
  this.boundHandleBlur = this._handleBlur.bind(this);
15
22
  this.boundHandleKeydown = this._handleKeydown.bind(this);
23
+ this.boundHandleInput = this._handleInput.bind(this);
24
+ this.editableGroup = [];
16
25
  }
17
- /**
18
- * Make an element editable if it's a text element
19
- * @param element - The element to make editable
20
- */
21
26
  makeEditableIfText(element) {
22
27
  if (!this._isTextElement(element)) {
23
28
  return;
24
29
  }
30
+ const source = getSourceFromDataAttributes(element);
31
+ const siblings = source ? findElementsBySourceLocation(source) : [];
32
+ const others = siblings.filter((el) => el !== element);
33
+ this.editableGroup = [element, ...others];
34
+ for (const el of this.editableGroup) {
35
+ el.dataset.originalText = el.textContent ?? "";
36
+ }
25
37
  element.contentEditable = "true";
26
- element.dataset.originalText = element.textContent ?? "";
27
38
  element.addEventListener("blur", this.boundHandleBlur);
28
39
  element.addEventListener("keydown", this.boundHandleKeydown);
40
+ element.addEventListener("input", this.boundHandleInput);
29
41
  }
30
- /**
31
- * Remove editable state from an element
32
- * @param element - The element to make non-editable
33
- */
34
42
  removeEditable(element) {
35
43
  if (element.contentEditable === "true") {
36
44
  element.contentEditable = "false";
37
45
  }
38
- delete element.dataset.originalText;
39
46
  element.removeEventListener("blur", this.boundHandleBlur);
40
47
  element.removeEventListener("keydown", this.boundHandleKeydown);
48
+ element.removeEventListener("input", this.boundHandleInput);
49
+ for (const el of this.editableGroup) {
50
+ delete el.dataset.originalText;
51
+ }
52
+ this.editableGroup = [];
41
53
  }
42
- /**
43
- * Check if an element is a text element that can be made editable
44
- * @private
45
- * @param element - The element to check
46
- * @returns True if the element can be made editable
47
- */
48
54
  _isTextElement(element) {
49
- return (this.textTags.includes(element.tagName) &&
55
+ return (TEXT_TAGS.includes(element.tagName) &&
50
56
  (element.textContent ?? "").trim().length > 0 &&
51
57
  element.dataset.textType === "static");
52
58
  }
53
- /**
54
- * Handle blur event on editable element
55
- * @private
56
- * @param e - The blur event
57
- */
59
+ _handleInput(e) {
60
+ const primary = e.target;
61
+ const text = primary.textContent ?? "";
62
+ for (let i = 1; i < this.editableGroup.length; i++) {
63
+ this.editableGroup[i].textContent = text;
64
+ }
65
+ }
58
66
  _handleBlur(e) {
59
67
  const element = e.target;
60
68
  const newText = element.textContent ?? "";
61
69
  const originalText = element.dataset.originalText ?? "";
62
70
  if (newText !== originalText) {
63
- // Mirror JS behavior: update stored originalText after a commit
64
- element.dataset.originalText = newText;
65
- // Notify extension about text change (if available)
71
+ for (const el of this.editableGroup) {
72
+ el.dataset.originalText = newText;
73
+ }
66
74
  if (this.communicationManager) {
67
75
  this.communicationManager.notifyTextChange(element, originalText, newText);
68
76
  }
69
77
  }
70
78
  this.removeEditable(element);
71
79
  }
72
- /**
73
- * Handle keydown event on editable element
74
- * @private
75
- * @param e - The keydown event
76
- */
77
80
  _handleKeydown(e) {
78
81
  const element = e.target;
79
82
  if (e.key === "Enter" && !e.shiftKey) {
@@ -81,8 +84,10 @@ export class EditableManager {
81
84
  element.blur();
82
85
  }
83
86
  if (e.key === "Escape") {
84
- if (element.dataset.originalText) {
85
- element.textContent = element.dataset.originalText;
87
+ for (const el of this.editableGroup) {
88
+ if (el.dataset.originalText) {
89
+ el.textContent = el.dataset.originalText;
90
+ }
86
91
  }
87
92
  element.blur();
88
93
  }
@@ -8,6 +8,7 @@
8
8
  * This file is used by esbuild to create the bundled version
9
9
  */
10
10
  import { InteractionsController } from "./interactionsController.js";
11
+ import { parseSourceLocation } from "./utils/sourceUtils.js";
11
12
  const interactions = new InteractionsController(true);
12
13
  if (typeof document !== "undefined") {
13
14
  if (document.readyState === "loading") {
@@ -29,23 +30,12 @@ if (typeof window !== "undefined") {
29
30
  };
30
31
  window.addEventListener("message", function (event) {
31
32
  const data = event.data;
32
- const typed = data && typeof data === "object"
33
- ? data
34
- : null;
33
+ const typed = data && typeof data === "object" ? data : null;
35
34
  if (typed && typed.type === "style-change") {
36
- const sl = typed.sourceLocation;
37
- const sourceLocation = sl && typeof sl === "object"
38
- ? {
39
- fileName: String(sl.sourceFile ?? ""),
40
- lineNumber: typeof sl.lineNumber === "number"
41
- ? sl.lineNumber
42
- : null,
43
- columnNumber: typeof sl.columnNumber === "number"
44
- ? sl.columnNumber
45
- : null,
46
- }
47
- : undefined;
48
- interactions.applyStyleChange(String(typed.property ?? ""), String(typed.value ?? ""), sourceLocation);
35
+ interactions.applyStyleChange(String(typed.property ?? ""), String(typed.value ?? ""), parseSourceLocation(typed.sourceLocation));
36
+ }
37
+ if (typed && typed.type === "text-change") {
38
+ interactions.applyTextChange(String(typed.text ?? ""), parseSourceLocation(typed.sourceLocation));
49
39
  }
50
40
  if (typed && typed.type === "enable-interactions") {
51
41
  window.enableInteractions?.();
@@ -25,12 +25,14 @@ export declare class InteractionsController {
25
25
  * Disable the design mode interactions
26
26
  */
27
27
  disable(): void;
28
+ private resolveTargets;
28
29
  /**
29
30
  * Apply a style change to all elements at the given source location.
30
31
  * When sourceLocation is provided (undo/redo), it is used directly.
31
32
  * Otherwise the source location is read from the currently selected element.
32
33
  */
33
34
  applyStyleChange(property: string, value: string, sourceLocation?: SourceLocation): void;
35
+ applyTextChange(text: string, sourceLocation?: SourceLocation): void;
34
36
  /**
35
37
  * Cleanup and remove event listeners
36
38
  */
@@ -1 +1 @@
1
- {"version":3,"file":"interactionsController.d.ts","sourceRoot":"","sources":["../../../src/design/interactions/interactionsController.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAYH,OAAO,EAGN,KAAK,cAAc,EACnB,MAAM,wBAAwB,CAAC;AAEhC,qBAAa,sBAAsB;IAClC,OAAO,CAAC,OAAO,CAAU;IACzB,OAAO,CAAC,QAAQ,CAAU;IAE1B,OAAO,CAAC,gBAAgB,CAAmB;IAC3C,OAAO,CAAC,YAAY,CAAe;IACnC,OAAO,CAAC,oBAAoB,CAAuB;IACnD,OAAO,CAAC,eAAe,CAAkB;IACzC,OAAO,CAAC,aAAa,CAAgB;gBAEzB,OAAO,UAAO;IAwD1B;;OAEG;IACH,UAAU,IAAI,IAAI;IAiBlB;;OAEG;IACH,MAAM,IAAI,IAAI;IAKd;;OAEG;IACH,OAAO,IAAI,IAAI;IAMf;;;;OAIG;IACH,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,cAAc,CAAC,EAAE,cAAc,GAAG,IAAI;IAgBxF;;OAEG;IACH,OAAO,IAAI,IAAI;CAOf"}
1
+ {"version":3,"file":"interactionsController.d.ts","sourceRoot":"","sources":["../../../src/design/interactions/interactionsController.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAYH,OAAO,EAGN,KAAK,cAAc,EACnB,MAAM,wBAAwB,CAAC;AAEhC,qBAAa,sBAAsB;IAClC,OAAO,CAAC,OAAO,CAAU;IACzB,OAAO,CAAC,QAAQ,CAAU;IAE1B,OAAO,CAAC,gBAAgB,CAAmB;IAC3C,OAAO,CAAC,YAAY,CAAe;IACnC,OAAO,CAAC,oBAAoB,CAAuB;IACnD,OAAO,CAAC,eAAe,CAAkB;IACzC,OAAO,CAAC,aAAa,CAAgB;gBAEzB,OAAO,UAAO;IAwD1B;;OAEG;IACH,UAAU,IAAI,IAAI;IAiBlB;;OAEG;IACH,MAAM,IAAI,IAAI;IAKd;;OAEG;IACH,OAAO,IAAI,IAAI;IAMf,OAAO,CAAC,cAAc;IAWtB;;;;OAIG;IACH,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,cAAc,CAAC,EAAE,cAAc,GAAG,IAAI;IAMxF,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,cAAc,CAAC,EAAE,cAAc,GAAG,IAAI;IAMpE;;OAEG;IACH,OAAO,IAAI,IAAI;CAOf"}
@@ -99,24 +99,29 @@ export class InteractionsController {
99
99
  this.eventHandlers.clearAll();
100
100
  console.log("Design Mode Interactions disabled");
101
101
  }
102
+ resolveTargets(sourceLocation) {
103
+ let location = sourceLocation ?? null;
104
+ if (!location?.fileName) {
105
+ const selectedElement = this.eventHandlers.getSelectedElement();
106
+ location = getSourceFromDataAttributes(selectedElement);
107
+ }
108
+ return location ? findElementsBySourceLocation(location) : [];
109
+ }
102
110
  /**
103
111
  * Apply a style change to all elements at the given source location.
104
112
  * When sourceLocation is provided (undo/redo), it is used directly.
105
113
  * Otherwise the source location is read from the currently selected element.
106
114
  */
107
115
  applyStyleChange(property, value, sourceLocation) {
108
- let location = sourceLocation ?? null;
109
- if (!location?.fileName) {
110
- const selectedElement = this.eventHandlers.getSelectedElement();
111
- location = getSourceFromDataAttributes(selectedElement);
112
- }
113
- if (!location)
114
- return;
115
- const targets = findElementsBySourceLocation(location);
116
- for (const el of targets) {
116
+ for (const el of this.resolveTargets(sourceLocation)) {
117
117
  el.style[property] = value;
118
118
  }
119
119
  }
120
+ applyTextChange(text, sourceLocation) {
121
+ for (const el of this.resolveTargets(sourceLocation)) {
122
+ el.textContent = text;
123
+ }
124
+ }
120
125
  /**
121
126
  * Cleanup and remove event listeners
122
127
  */
@@ -25,5 +25,12 @@ export declare function findElementsBySourceLocation(location: SourceLocation):
25
25
  * @param element - The DOM element
26
26
  * @returns A label suitable for UI display
27
27
  */
28
+ /**
29
+ * Parse an untyped message payload object into a SourceLocation.
30
+ * Used by the entry point to convert raw postMessage data into typed values.
31
+ * @param sl - The raw source location object from a message payload
32
+ * @returns A SourceLocation, or undefined if the input is not a valid object
33
+ */
34
+ export declare function parseSourceLocation(sl: unknown): SourceLocation | undefined;
28
35
  export declare function getLabelFromSource(element: HTMLElement | null | undefined): string;
29
36
  //# sourceMappingURL=sourceUtils.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"sourceUtils.d.ts","sourceRoot":"","sources":["../../../../src/design/interactions/utils/sourceUtils.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAkCH,MAAM,WAAW,cAAc;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B;AAED;;;;GAIG;AACH,wBAAgB,2BAA2B,CAC1C,OAAO,EAAE,WAAW,GAAG,IAAI,GAAG,SAAS,GACrC,cAAc,GAAG,IAAI,CAWvB;AAED;;;;GAIG;AACH,wBAAgB,4BAA4B,CAAC,QAAQ,EAAE,cAAc,GAAG,WAAW,EAAE,CAepF;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAiBlF"}
1
+ {"version":3,"file":"sourceUtils.d.ts","sourceRoot":"","sources":["../../../../src/design/interactions/utils/sourceUtils.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAkCH,MAAM,WAAW,cAAc;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B;AAED;;;;GAIG;AACH,wBAAgB,2BAA2B,CAC1C,OAAO,EAAE,WAAW,GAAG,IAAI,GAAG,SAAS,GACrC,cAAc,GAAG,IAAI,CAWvB;AAED;;;;GAIG;AACH,wBAAgB,4BAA4B,CAAC,QAAQ,EAAE,cAAc,GAAG,WAAW,EAAE,CAepF;AAED;;;;GAIG;AACH;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,EAAE,EAAE,OAAO,GAAG,cAAc,GAAG,SAAS,CAQ3E;AAED,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAiBlF"}
@@ -66,6 +66,22 @@ export function findElementsBySourceLocation(location) {
66
66
  * @param element - The DOM element
67
67
  * @returns A label suitable for UI display
68
68
  */
69
+ /**
70
+ * Parse an untyped message payload object into a SourceLocation.
71
+ * Used by the entry point to convert raw postMessage data into typed values.
72
+ * @param sl - The raw source location object from a message payload
73
+ * @returns A SourceLocation, or undefined if the input is not a valid object
74
+ */
75
+ export function parseSourceLocation(sl) {
76
+ if (!sl || typeof sl !== "object")
77
+ return undefined;
78
+ const obj = sl;
79
+ return {
80
+ fileName: String(obj.sourceFile ?? ""),
81
+ lineNumber: typeof obj.lineNumber === "number" ? obj.lineNumber : null,
82
+ columnNumber: typeof obj.columnNumber === "number" ? obj.columnNumber : null,
83
+ };
84
+ }
69
85
  export function getLabelFromSource(element) {
70
86
  if (!element) {
71
87
  return "";
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@salesforce/webapp-experimental",
3
3
  "description": "[experimental] Core package for Salesforce Web Applications",
4
- "version": "1.73.1",
4
+ "version": "1.75.0",
5
5
  "license": "SEE LICENSE IN LICENSE.txt",
6
6
  "type": "module",
7
7
  "main": "./dist/index.js",
@@ -45,7 +45,7 @@
45
45
  },
46
46
  "dependencies": {
47
47
  "@salesforce/core": "^8.23.4",
48
- "@salesforce/sdk-data": "^1.73.1",
48
+ "@salesforce/sdk-data": "^1.75.0",
49
49
  "axios": "^1.7.7",
50
50
  "micromatch": "^4.0.8",
51
51
  "path-to-regexp": "^8.3.0"