@salesforce/webapp-experimental 1.47.0 → 1.48.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/dist/design/design-mode-interactions.js +649 -0
- package/dist/design/index.d.ts +12 -0
- package/dist/design/index.d.ts.map +1 -0
- package/dist/design/index.js +21 -0
- package/dist/design/interactions/communicationManager.d.ts +25 -0
- package/dist/design/interactions/communicationManager.d.ts.map +1 -0
- package/dist/design/interactions/communicationManager.js +112 -0
- package/dist/design/interactions/componentMatcher.d.ts +37 -0
- package/dist/design/interactions/componentMatcher.d.ts.map +1 -0
- package/dist/design/interactions/componentMatcher.js +68 -0
- package/dist/design/interactions/editableManager.d.ts +51 -0
- package/dist/design/interactions/editableManager.d.ts.map +1 -0
- package/dist/design/interactions/editableManager.js +90 -0
- package/dist/design/interactions/eventHandlers.d.ts +66 -0
- package/dist/design/interactions/eventHandlers.d.ts.map +1 -0
- package/dist/design/interactions/eventHandlers.js +136 -0
- package/dist/design/interactions/index.d.ts +7 -0
- package/dist/design/interactions/index.d.ts.map +1 -0
- package/dist/design/interactions/index.js +45 -0
- package/dist/design/interactions/interactionsController.d.ts +38 -0
- package/dist/design/interactions/interactionsController.d.ts.map +1 -0
- package/dist/design/interactions/interactionsController.js +86 -0
- package/dist/design/interactions/styleManager.d.ts +49 -0
- package/dist/design/interactions/styleManager.d.ts.map +1 -0
- package/dist/design/interactions/styleManager.js +89 -0
- package/dist/design/interactions/utils/cssUtils.d.ts +22 -0
- package/dist/design/interactions/utils/cssUtils.d.ts.map +1 -0
- package/dist/design/interactions/utils/cssUtils.js +46 -0
- package/dist/design/interactions/utils/sourceUtils.d.ts +23 -0
- package/dist/design/interactions/utils/sourceUtils.d.ts.map +1 -0
- package/dist/design/interactions/utils/sourceUtils.js +64 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/package.json +10 -4
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026, Salesforce, Inc.,
|
|
3
|
+
* All rights reserved.
|
|
4
|
+
* For full license text, see the LICENSE.txt file
|
|
5
|
+
*/
|
|
6
|
+
export declare class CommunicationManager {
|
|
7
|
+
constructor();
|
|
8
|
+
/**
|
|
9
|
+
* Notify the extension about a selected component
|
|
10
|
+
* @param element - The selected element
|
|
11
|
+
*/
|
|
12
|
+
notifyComponentSelected(element: HTMLElement): void;
|
|
13
|
+
/**
|
|
14
|
+
* Notify the extension about a text change
|
|
15
|
+
* @param element - The element that changed
|
|
16
|
+
* @param originalText - The original text
|
|
17
|
+
* @param newText - The new text
|
|
18
|
+
*/
|
|
19
|
+
notifyTextChange(element: HTMLElement, originalText: string, newText: string): void;
|
|
20
|
+
/**
|
|
21
|
+
* Notify the parent window that interactions initialization is complete
|
|
22
|
+
*/
|
|
23
|
+
notifyInitializationComplete(): void;
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=communicationManager.d.ts.map
|
|
@@ -0,0 +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"}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026, Salesforce, Inc.,
|
|
3
|
+
* All rights reserved.
|
|
4
|
+
* For full license text, see the LICENSE.txt file
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Communication Manager Module
|
|
8
|
+
* Handles communication with the parent window (VS Code extension)
|
|
9
|
+
*/
|
|
10
|
+
import { getElementStyles } from "./utils/cssUtils.js";
|
|
11
|
+
import { getLabelFromSource, getSourceFromDataAttributes } from "./utils/sourceUtils.js";
|
|
12
|
+
export class CommunicationManager {
|
|
13
|
+
constructor() {
|
|
14
|
+
// CommunicationManager has no required initialization
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Notify the extension about a selected component
|
|
18
|
+
* @param element - The selected element
|
|
19
|
+
*/
|
|
20
|
+
notifyComponentSelected(element) {
|
|
21
|
+
const label = getLabelFromSource(element);
|
|
22
|
+
// Temporarily remove design-mode-selected class to get original styles
|
|
23
|
+
const wasSelected = element.classList.contains("design-mode-selected");
|
|
24
|
+
if (wasSelected) {
|
|
25
|
+
element.classList.remove("design-mode-selected");
|
|
26
|
+
}
|
|
27
|
+
const styles = getElementStyles(element);
|
|
28
|
+
// Restore design-mode-selected class if it was present
|
|
29
|
+
if (wasSelected) {
|
|
30
|
+
element.classList.add("design-mode-selected");
|
|
31
|
+
}
|
|
32
|
+
// Source location metadata injected at compile time (babel-plugin-enhanced-locator)
|
|
33
|
+
const debugSource = getSourceFromDataAttributes(element);
|
|
34
|
+
// Send message to parent window
|
|
35
|
+
try {
|
|
36
|
+
if (window.parent !== window) {
|
|
37
|
+
window.parent.postMessage({
|
|
38
|
+
type: "component-selected",
|
|
39
|
+
component: {
|
|
40
|
+
name: label,
|
|
41
|
+
element: {
|
|
42
|
+
tagName: element.tagName,
|
|
43
|
+
classList: Array.from(element.classList),
|
|
44
|
+
id: element.id || "",
|
|
45
|
+
},
|
|
46
|
+
styles: {
|
|
47
|
+
...styles,
|
|
48
|
+
},
|
|
49
|
+
debugSource: debugSource,
|
|
50
|
+
},
|
|
51
|
+
}, "*");
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
console.log("Could not notify extension:", error);
|
|
56
|
+
}
|
|
57
|
+
// Store in global for polling fallback
|
|
58
|
+
window.selectedComponentInfo =
|
|
59
|
+
{
|
|
60
|
+
name: label,
|
|
61
|
+
element: element,
|
|
62
|
+
classList: Array.from(element.classList),
|
|
63
|
+
id: element.id || "no-id",
|
|
64
|
+
tagName: element.tagName,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Notify the extension about a text change
|
|
69
|
+
* @param element - The element that changed
|
|
70
|
+
* @param originalText - The original text
|
|
71
|
+
* @param newText - The new text
|
|
72
|
+
*/
|
|
73
|
+
notifyTextChange(element, originalText, newText) {
|
|
74
|
+
const label = getLabelFromSource(element);
|
|
75
|
+
try {
|
|
76
|
+
if (window.parent !== window) {
|
|
77
|
+
window.parent.postMessage({
|
|
78
|
+
type: "text-changed",
|
|
79
|
+
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
|
+
},
|
|
88
|
+
},
|
|
89
|
+
}, "*");
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
console.log("Could not notify extension about text change:", error);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Notify the parent window that interactions initialization is complete
|
|
98
|
+
*/
|
|
99
|
+
notifyInitializationComplete() {
|
|
100
|
+
try {
|
|
101
|
+
if (typeof window !== "undefined" && window.parent && window.parent !== window) {
|
|
102
|
+
window.parent.postMessage({
|
|
103
|
+
type: "interactions-initialized",
|
|
104
|
+
}, "*");
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
catch (error) {
|
|
108
|
+
const err = error;
|
|
109
|
+
console.warn("Could not send initialization message to parent:", err.message);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026, Salesforce, Inc.,
|
|
3
|
+
* All rights reserved.
|
|
4
|
+
* For full license text, see the LICENSE.txt file
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Component Matcher Module
|
|
8
|
+
* Handles mapping DOM elements to source-locatable elements (data-source-*).
|
|
9
|
+
*/
|
|
10
|
+
export declare class ComponentMatcher {
|
|
11
|
+
/**
|
|
12
|
+
* Check whether an element contains compile-time source metadata attributes.
|
|
13
|
+
* @param element - The element to check
|
|
14
|
+
* @returns True if the data-source-file attribute is present
|
|
15
|
+
*/
|
|
16
|
+
hasSourceMetadata(element: HTMLElement | null | undefined): boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Checks if a label represents a component name (not a fallback like tag name, ID, or text content)
|
|
19
|
+
* @param label - The label to check
|
|
20
|
+
* @param tagName - The element's tag name (lowercase)
|
|
21
|
+
* @returns True if the label is a component name
|
|
22
|
+
*/
|
|
23
|
+
isComponentNameLabel(label: string, tagName: string): boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Check if an element is highlightable.
|
|
26
|
+
* @param element - The element to check
|
|
27
|
+
* @returns True if the element should be highlighted
|
|
28
|
+
*/
|
|
29
|
+
isHighlightableElement(element: HTMLElement | null | undefined): boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Find the nearest highlightable element by walking up the DOM tree
|
|
32
|
+
* @param target - The target element
|
|
33
|
+
* @returns The highlightable element or null
|
|
34
|
+
*/
|
|
35
|
+
findHighlightableElement(target: HTMLElement | null | undefined): HTMLElement | null;
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=componentMatcher.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"componentMatcher.d.ts","sourceRoot":"","sources":["../../../src/design/interactions/componentMatcher.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;;GAGG;AAEH,qBAAa,gBAAgB;IAC5B;;;;OAIG;IACH,iBAAiB,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO;IAOnE;;;;;OAKG;IACH,oBAAoB,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO;IAM7D;;;;OAIG;IACH,sBAAsB,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO;IAOxE;;;;OAIG;IACH,wBAAwB,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI,GAAG,SAAS,GAAG,WAAW,GAAG,IAAI;CA0BpF"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026, Salesforce, Inc.,
|
|
3
|
+
* All rights reserved.
|
|
4
|
+
* For full license text, see the LICENSE.txt file
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Component Matcher Module
|
|
8
|
+
* Handles mapping DOM elements to source-locatable elements (data-source-*).
|
|
9
|
+
*/
|
|
10
|
+
export class ComponentMatcher {
|
|
11
|
+
/**
|
|
12
|
+
* Check whether an element contains compile-time source metadata attributes.
|
|
13
|
+
* @param element - The element to check
|
|
14
|
+
* @returns True if the data-source-file attribute is present
|
|
15
|
+
*/
|
|
16
|
+
hasSourceMetadata(element) {
|
|
17
|
+
if (!element) {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
return element.hasAttribute("data-source-file");
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Checks if a label represents a component name (not a fallback like tag name, ID, or text content)
|
|
24
|
+
* @param label - The label to check
|
|
25
|
+
* @param tagName - The element's tag name (lowercase)
|
|
26
|
+
* @returns True if the label is a component name
|
|
27
|
+
*/
|
|
28
|
+
isComponentNameLabel(label, tagName) {
|
|
29
|
+
return (label !== tagName && !label.includes("#") && !label.includes("(") && !label.includes('"'));
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Check if an element is highlightable.
|
|
33
|
+
* @param element - The element to check
|
|
34
|
+
* @returns True if the element should be highlighted
|
|
35
|
+
*/
|
|
36
|
+
isHighlightableElement(element) {
|
|
37
|
+
if (!element || element === document.body || element === document.documentElement) {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
return this.hasSourceMetadata(element);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Find the nearest highlightable element by walking up the DOM tree
|
|
44
|
+
* @param target - The target element
|
|
45
|
+
* @returns The highlightable element or null
|
|
46
|
+
*/
|
|
47
|
+
findHighlightableElement(target) {
|
|
48
|
+
if (!target || target === document.body || target === document.documentElement) {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
// Fast path: use closest() if supported.
|
|
52
|
+
const closest = typeof target.closest === "function"
|
|
53
|
+
? target.closest("[data-source-file]")
|
|
54
|
+
: null;
|
|
55
|
+
if (closest && closest !== document.body && closest !== document.documentElement) {
|
|
56
|
+
return closest;
|
|
57
|
+
}
|
|
58
|
+
// Fallback: manual walk.
|
|
59
|
+
let current = target;
|
|
60
|
+
while (current && current !== document.body && current !== document.documentElement) {
|
|
61
|
+
if (this.isHighlightableElement(current)) {
|
|
62
|
+
return current;
|
|
63
|
+
}
|
|
64
|
+
current = current.parentElement;
|
|
65
|
+
}
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026, Salesforce, Inc.,
|
|
3
|
+
* All rights reserved.
|
|
4
|
+
* For full license text, see the LICENSE.txt file
|
|
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
|
+
interface CommunicationManagerLike {
|
|
12
|
+
notifyTextChange: (element: HTMLElement, originalText: string, newText: string) => void;
|
|
13
|
+
}
|
|
14
|
+
export declare class EditableManager {
|
|
15
|
+
private communicationManager?;
|
|
16
|
+
private textTags;
|
|
17
|
+
private boundHandleBlur;
|
|
18
|
+
private boundHandleKeydown;
|
|
19
|
+
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
|
+
makeEditableIfText(element: HTMLElement): void;
|
|
25
|
+
/**
|
|
26
|
+
* Remove editable state from an element
|
|
27
|
+
* @param element - The element to make non-editable
|
|
28
|
+
*/
|
|
29
|
+
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
|
+
private _isTextElement;
|
|
37
|
+
/**
|
|
38
|
+
* Handle blur event on editable element
|
|
39
|
+
* @private
|
|
40
|
+
* @param e - The blur event
|
|
41
|
+
*/
|
|
42
|
+
private _handleBlur;
|
|
43
|
+
/**
|
|
44
|
+
* Handle keydown event on editable element
|
|
45
|
+
* @private
|
|
46
|
+
* @param e - The keydown event
|
|
47
|
+
*/
|
|
48
|
+
private _handleKeydown;
|
|
49
|
+
}
|
|
50
|
+
export {};
|
|
51
|
+
//# sourceMappingURL=editableManager.d.ts.map
|
|
@@ -0,0 +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"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026, Salesforce, Inc.,
|
|
3
|
+
* All rights reserved.
|
|
4
|
+
* For full license text, see the LICENSE.txt file
|
|
5
|
+
*/
|
|
6
|
+
export class EditableManager {
|
|
7
|
+
communicationManager;
|
|
8
|
+
textTags;
|
|
9
|
+
boundHandleBlur;
|
|
10
|
+
boundHandleKeydown;
|
|
11
|
+
constructor(communicationManager) {
|
|
12
|
+
this.communicationManager = communicationManager;
|
|
13
|
+
this.textTags = ["H1", "H2", "H3", "H4", "H5", "H6", "P", "SPAN", "A", "BUTTON", "LABEL"];
|
|
14
|
+
this.boundHandleBlur = this._handleBlur.bind(this);
|
|
15
|
+
this.boundHandleKeydown = this._handleKeydown.bind(this);
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Make an element editable if it's a text element
|
|
19
|
+
* @param element - The element to make editable
|
|
20
|
+
*/
|
|
21
|
+
makeEditableIfText(element) {
|
|
22
|
+
if (!this._isTextElement(element)) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
element.contentEditable = "true";
|
|
26
|
+
element.dataset.originalText = element.textContent ?? "";
|
|
27
|
+
element.addEventListener("blur", this.boundHandleBlur);
|
|
28
|
+
element.addEventListener("keydown", this.boundHandleKeydown);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Remove editable state from an element
|
|
32
|
+
* @param element - The element to make non-editable
|
|
33
|
+
*/
|
|
34
|
+
removeEditable(element) {
|
|
35
|
+
if (element.contentEditable === "true") {
|
|
36
|
+
element.contentEditable = "false";
|
|
37
|
+
}
|
|
38
|
+
delete element.dataset.originalText;
|
|
39
|
+
element.removeEventListener("blur", this.boundHandleBlur);
|
|
40
|
+
element.removeEventListener("keydown", this.boundHandleKeydown);
|
|
41
|
+
}
|
|
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
|
+
_isTextElement(element) {
|
|
49
|
+
return (this.textTags.includes(element.tagName) &&
|
|
50
|
+
(element.textContent ?? "").trim().length > 0 &&
|
|
51
|
+
element.dataset.textType === "static");
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Handle blur event on editable element
|
|
55
|
+
* @private
|
|
56
|
+
* @param e - The blur event
|
|
57
|
+
*/
|
|
58
|
+
_handleBlur(e) {
|
|
59
|
+
const element = e.target;
|
|
60
|
+
const newText = element.textContent ?? "";
|
|
61
|
+
const originalText = element.dataset.originalText ?? "";
|
|
62
|
+
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)
|
|
66
|
+
if (this.communicationManager) {
|
|
67
|
+
this.communicationManager.notifyTextChange(element, originalText, newText);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
this.removeEditable(element);
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Handle keydown event on editable element
|
|
74
|
+
* @private
|
|
75
|
+
* @param e - The keydown event
|
|
76
|
+
*/
|
|
77
|
+
_handleKeydown(e) {
|
|
78
|
+
const element = e.target;
|
|
79
|
+
if (e.key === "Enter" && !e.shiftKey) {
|
|
80
|
+
e.preventDefault();
|
|
81
|
+
element.blur();
|
|
82
|
+
}
|
|
83
|
+
if (e.key === "Escape") {
|
|
84
|
+
if (element.dataset.originalText) {
|
|
85
|
+
element.textContent = element.dataset.originalText;
|
|
86
|
+
}
|
|
87
|
+
element.blur();
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026, Salesforce, Inc.,
|
|
3
|
+
* All rights reserved.
|
|
4
|
+
* For full license text, see the LICENSE.txt file
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Event Handlers Module
|
|
8
|
+
* Handles mouse and click events for highlighting
|
|
9
|
+
*/
|
|
10
|
+
interface ComponentMatcherLike {
|
|
11
|
+
findHighlightableElement: (target: HTMLElement) => HTMLElement | null;
|
|
12
|
+
}
|
|
13
|
+
interface StyleManagerLike {
|
|
14
|
+
highlightElement: (element: HTMLElement) => void;
|
|
15
|
+
unhighlightElement: (element: HTMLElement) => void;
|
|
16
|
+
selectElement: (element: HTMLElement) => void;
|
|
17
|
+
deselectElement: (element: HTMLElement) => void;
|
|
18
|
+
}
|
|
19
|
+
interface EditableManagerLike {
|
|
20
|
+
makeEditableIfText: (element: HTMLElement) => void;
|
|
21
|
+
removeEditable: (element: HTMLElement) => void;
|
|
22
|
+
}
|
|
23
|
+
interface CommunicationManagerLike {
|
|
24
|
+
notifyComponentSelected: (element: HTMLElement) => void;
|
|
25
|
+
}
|
|
26
|
+
export declare class EventHandlers {
|
|
27
|
+
private isInteractionsActive;
|
|
28
|
+
private componentMatcher;
|
|
29
|
+
private styleManager;
|
|
30
|
+
private editableManager;
|
|
31
|
+
private communicationManager;
|
|
32
|
+
private currentHighlighted;
|
|
33
|
+
private selectedElement;
|
|
34
|
+
constructor(isInteractionsActive: () => boolean, componentMatcher: ComponentMatcherLike, styleManager: StyleManagerLike, editableManager: EditableManagerLike, communicationManager: CommunicationManagerLike);
|
|
35
|
+
/**
|
|
36
|
+
* Find the nearest highlightable element by walking up the DOM tree
|
|
37
|
+
* @param target - The target element
|
|
38
|
+
* @returns The highlightable element or null
|
|
39
|
+
*/
|
|
40
|
+
private _findHighlightableElement;
|
|
41
|
+
/**
|
|
42
|
+
* Handle mouseover event
|
|
43
|
+
* @param e - The mouseover event
|
|
44
|
+
*/
|
|
45
|
+
handleMouseOver(e: MouseEvent): void;
|
|
46
|
+
/**
|
|
47
|
+
* Handle mouseleave event
|
|
48
|
+
*/
|
|
49
|
+
handleMouseLeave(): void;
|
|
50
|
+
/**
|
|
51
|
+
* Handle click event
|
|
52
|
+
* @param e - The click event
|
|
53
|
+
*/
|
|
54
|
+
handleClick(e: MouseEvent): void;
|
|
55
|
+
/**
|
|
56
|
+
* Clear all highlights and selections
|
|
57
|
+
*/
|
|
58
|
+
clearAll(): void;
|
|
59
|
+
/**
|
|
60
|
+
* Get the currently selected element
|
|
61
|
+
* @returns The selected element
|
|
62
|
+
*/
|
|
63
|
+
getSelectedElement(): HTMLElement | null;
|
|
64
|
+
}
|
|
65
|
+
export {};
|
|
66
|
+
//# sourceMappingURL=eventHandlers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"eventHandlers.d.ts","sourceRoot":"","sources":["../../../src/design/interactions/eventHandlers.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;;GAGG;AAEH,UAAU,oBAAoB;IAC7B,wBAAwB,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,WAAW,GAAG,IAAI,CAAC;CACtE;AAED,UAAU,gBAAgB;IACzB,gBAAgB,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,IAAI,CAAC;IACjD,kBAAkB,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,IAAI,CAAC;IACnD,aAAa,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,IAAI,CAAC;IAC9C,eAAe,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,IAAI,CAAC;CAChD;AAED,UAAU,mBAAmB;IAC5B,kBAAkB,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,IAAI,CAAC;IACnD,cAAc,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,IAAI,CAAC;CAC/C;AAED,UAAU,wBAAwB;IACjC,uBAAuB,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,IAAI,CAAC;CACxD;AAED,qBAAa,aAAa;IACzB,OAAO,CAAC,oBAAoB,CAAgB;IAC5C,OAAO,CAAC,gBAAgB,CAAuB;IAC/C,OAAO,CAAC,YAAY,CAAmB;IACvC,OAAO,CAAC,eAAe,CAAsB;IAC7C,OAAO,CAAC,oBAAoB,CAA2B;IAEvD,OAAO,CAAC,kBAAkB,CAAqB;IAC/C,OAAO,CAAC,eAAe,CAAqB;gBAG3C,oBAAoB,EAAE,MAAM,OAAO,EACnC,gBAAgB,EAAE,oBAAoB,EACtC,YAAY,EAAE,gBAAgB,EAC9B,eAAe,EAAE,mBAAmB,EACpC,oBAAoB,EAAE,wBAAwB;IAiB/C;;;;OAIG;IACH,OAAO,CAAC,yBAAyB;IAIjC;;;OAGG;IACH,eAAe,CAAC,CAAC,EAAE,UAAU,GAAG,IAAI;IAmCpC;;OAEG;IACH,gBAAgB,IAAI,IAAI;IAcxB;;;OAGG;IACH,WAAW,CAAC,CAAC,EAAE,UAAU,GAAG,IAAI;IA6ChC;;OAEG;IACH,QAAQ,IAAI,IAAI;IAahB;;;OAGG;IACH,kBAAkB,IAAI,WAAW,GAAG,IAAI;CAGxC"}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026, Salesforce, Inc.,
|
|
3
|
+
* All rights reserved.
|
|
4
|
+
* For full license text, see the LICENSE.txt file
|
|
5
|
+
*/
|
|
6
|
+
export class EventHandlers {
|
|
7
|
+
isInteractionsActive;
|
|
8
|
+
componentMatcher;
|
|
9
|
+
styleManager;
|
|
10
|
+
editableManager;
|
|
11
|
+
communicationManager;
|
|
12
|
+
currentHighlighted;
|
|
13
|
+
selectedElement;
|
|
14
|
+
constructor(isInteractionsActive, componentMatcher, styleManager, editableManager, communicationManager) {
|
|
15
|
+
this.isInteractionsActive = isInteractionsActive;
|
|
16
|
+
this.componentMatcher = componentMatcher;
|
|
17
|
+
this.styleManager = styleManager;
|
|
18
|
+
this.editableManager = editableManager;
|
|
19
|
+
this.communicationManager = communicationManager;
|
|
20
|
+
this.currentHighlighted = null;
|
|
21
|
+
this.selectedElement = null;
|
|
22
|
+
// Bind methods to preserve 'this' context
|
|
23
|
+
this.handleMouseOver = this.handleMouseOver.bind(this);
|
|
24
|
+
this.handleMouseLeave = this.handleMouseLeave.bind(this);
|
|
25
|
+
this.handleClick = this.handleClick.bind(this);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Find the nearest highlightable element by walking up the DOM tree
|
|
29
|
+
* @param target - The target element
|
|
30
|
+
* @returns The highlightable element or null
|
|
31
|
+
*/
|
|
32
|
+
_findHighlightableElement(target) {
|
|
33
|
+
return this.componentMatcher.findHighlightableElement(target);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Handle mouseover event
|
|
37
|
+
* @param e - The mouseover event
|
|
38
|
+
*/
|
|
39
|
+
handleMouseOver(e) {
|
|
40
|
+
if (!this.isInteractionsActive()) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
e.stopPropagation();
|
|
44
|
+
const target = e.target;
|
|
45
|
+
if (target.nodeType !== 1 || target.tagName === "HTML" || target.tagName === "BODY") {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
const element = this._findHighlightableElement(target);
|
|
49
|
+
if (!element) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
if (this.currentHighlighted === element ||
|
|
53
|
+
(this.selectedElement && this.selectedElement === element)) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
if (this.currentHighlighted &&
|
|
57
|
+
!this.currentHighlighted.classList.contains("design-mode-selected")) {
|
|
58
|
+
this.styleManager.unhighlightElement(this.currentHighlighted);
|
|
59
|
+
}
|
|
60
|
+
this.styleManager.highlightElement(element);
|
|
61
|
+
this.currentHighlighted = element;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Handle mouseleave event
|
|
65
|
+
*/
|
|
66
|
+
handleMouseLeave() {
|
|
67
|
+
if (!this.isInteractionsActive()) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
if (this.currentHighlighted &&
|
|
71
|
+
!this.currentHighlighted.classList.contains("design-mode-selected")) {
|
|
72
|
+
this.styleManager.unhighlightElement(this.currentHighlighted);
|
|
73
|
+
this.currentHighlighted = null;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Handle click event
|
|
78
|
+
* @param e - The click event
|
|
79
|
+
*/
|
|
80
|
+
handleClick(e) {
|
|
81
|
+
if (!this.isInteractionsActive()) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
e.preventDefault();
|
|
85
|
+
e.stopPropagation();
|
|
86
|
+
const target = e.target;
|
|
87
|
+
if (target.nodeType !== 1 || target.tagName === "HTML" || target.tagName === "BODY") {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
const element = this._findHighlightableElement(target);
|
|
91
|
+
if (!element) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
if (this.selectedElement && this.selectedElement === element) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
// Deselect previous element
|
|
98
|
+
if (this.selectedElement) {
|
|
99
|
+
this.styleManager.deselectElement(this.selectedElement);
|
|
100
|
+
this.editableManager.removeEditable(this.selectedElement);
|
|
101
|
+
}
|
|
102
|
+
// Remove highlight from current highlighted
|
|
103
|
+
if (this.currentHighlighted) {
|
|
104
|
+
this.styleManager.unhighlightElement(this.currentHighlighted);
|
|
105
|
+
this.currentHighlighted = null;
|
|
106
|
+
}
|
|
107
|
+
// Select new element
|
|
108
|
+
this.selectedElement = element;
|
|
109
|
+
this.styleManager.selectElement(element);
|
|
110
|
+
// Make text elements editable
|
|
111
|
+
this.editableManager.makeEditableIfText(element);
|
|
112
|
+
// Notify extension
|
|
113
|
+
this.communicationManager.notifyComponentSelected(element);
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Clear all highlights and selections
|
|
117
|
+
*/
|
|
118
|
+
clearAll() {
|
|
119
|
+
if (this.currentHighlighted) {
|
|
120
|
+
this.styleManager.unhighlightElement(this.currentHighlighted);
|
|
121
|
+
this.currentHighlighted = null;
|
|
122
|
+
}
|
|
123
|
+
if (this.selectedElement) {
|
|
124
|
+
this.styleManager.deselectElement(this.selectedElement);
|
|
125
|
+
this.editableManager.removeEditable(this.selectedElement);
|
|
126
|
+
this.selectedElement = null;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Get the currently selected element
|
|
131
|
+
* @returns The selected element
|
|
132
|
+
*/
|
|
133
|
+
getSelectedElement() {
|
|
134
|
+
return this.selectedElement;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/design/interactions/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026, Salesforce, Inc.,
|
|
3
|
+
* All rights reserved.
|
|
4
|
+
* For full license text, see the LICENSE.txt file
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Entry point for bundling the design mode interactions
|
|
8
|
+
* This file is used by esbuild to create the bundled version
|
|
9
|
+
*/
|
|
10
|
+
import { InteractionsController } from "./interactionsController.js";
|
|
11
|
+
const interactions = new InteractionsController(true);
|
|
12
|
+
if (typeof document !== "undefined") {
|
|
13
|
+
if (document.readyState === "loading") {
|
|
14
|
+
document.addEventListener("DOMContentLoaded", () => {
|
|
15
|
+
interactions.initialize();
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
interactions.initialize();
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
// Expose global functions
|
|
23
|
+
if (typeof window !== "undefined") {
|
|
24
|
+
window.enableInteractions = function () {
|
|
25
|
+
interactions.enable();
|
|
26
|
+
};
|
|
27
|
+
window.disableInteractions = function () {
|
|
28
|
+
interactions.disable();
|
|
29
|
+
};
|
|
30
|
+
window.addEventListener("message", function (event) {
|
|
31
|
+
const data = event.data;
|
|
32
|
+
const typed = data && typeof data === "object"
|
|
33
|
+
? data
|
|
34
|
+
: null;
|
|
35
|
+
if (typed && typed.type === "style-change") {
|
|
36
|
+
interactions.applyStyleChange(String(typed.property ?? ""), String(typed.value ?? ""));
|
|
37
|
+
}
|
|
38
|
+
if (typed && typed.type === "enable-interactions") {
|
|
39
|
+
window.enableInteractions?.();
|
|
40
|
+
}
|
|
41
|
+
if (typed && typed.type === "disable-interactions") {
|
|
42
|
+
window.disableInteractions?.();
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
}
|