@salesforce/webapp-experimental 1.80.1 → 1.82.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.
Files changed (52) hide show
  1. package/dist/api/clients.js +72 -77
  2. package/dist/api/index.d.ts +4 -4
  3. package/dist/api/index.d.ts.map +1 -1
  4. package/dist/api/index.js +13 -11
  5. package/dist/api/utils/accounts.js +16 -30
  6. package/dist/api/utils/records.js +21 -20
  7. package/dist/api/utils/user.js +20 -28
  8. package/dist/app/index.d.ts +4 -4
  9. package/dist/app/index.d.ts.map +1 -1
  10. package/dist/app/index.js +7 -7
  11. package/dist/app/manifest.js +23 -37
  12. package/dist/app/org.js +45 -58
  13. package/dist/design/index.js +12 -19
  14. package/dist/design/interactions/interactionsController.d.ts +1 -6
  15. package/dist/design/interactions/interactionsController.d.ts.map +1 -1
  16. package/dist/index.d.ts +4 -4
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.js +25 -10
  19. package/dist/package.json.js +4 -0
  20. package/dist/proxy/handler.d.ts +2 -7
  21. package/dist/proxy/handler.d.ts.map +1 -1
  22. package/dist/proxy/handler.js +462 -523
  23. package/dist/proxy/index.d.ts +2 -2
  24. package/dist/proxy/index.d.ts.map +1 -1
  25. package/dist/proxy/index.js +7 -6
  26. package/dist/proxy/livePreviewScript.js +11 -26
  27. package/dist/proxy/routing.d.ts +1 -6
  28. package/dist/proxy/routing.d.ts.map +1 -1
  29. package/dist/proxy/routing.js +73 -103
  30. package/package.json +7 -6
  31. package/dist/api/clients.test.d.ts +0 -7
  32. package/dist/api/clients.test.d.ts.map +0 -1
  33. package/dist/api/clients.test.js +0 -167
  34. package/dist/api/graphql-operations-types.js +0 -44
  35. package/dist/api/utils/records.test.d.ts +0 -7
  36. package/dist/api/utils/records.test.d.ts.map +0 -1
  37. package/dist/api/utils/records.test.js +0 -190
  38. package/dist/api/utils/user.test.d.ts +0 -7
  39. package/dist/api/utils/user.test.d.ts.map +0 -1
  40. package/dist/api/utils/user.test.js +0 -108
  41. package/dist/design/interactions/communicationManager.js +0 -108
  42. package/dist/design/interactions/componentMatcher.js +0 -80
  43. package/dist/design/interactions/editableManager.js +0 -95
  44. package/dist/design/interactions/eventHandlers.js +0 -125
  45. package/dist/design/interactions/index.js +0 -47
  46. package/dist/design/interactions/interactionsController.js +0 -135
  47. package/dist/design/interactions/styleManager.js +0 -97
  48. package/dist/design/interactions/utils/cssUtils.js +0 -72
  49. package/dist/design/interactions/utils/sourceUtils.js +0 -99
  50. package/dist/proxy/livePreviewScript.test.d.ts +0 -7
  51. package/dist/proxy/livePreviewScript.test.d.ts.map +0 -1
  52. package/dist/proxy/livePreviewScript.test.js +0 -96
@@ -1,190 +0,0 @@
1
- /**
2
- * Copyright (c) 2026, Salesforce, Inc.,
3
- * All rights reserved.
4
- * For full license text, see the LICENSE.txt file
5
- */
6
- import { describe, it, expect, vi, beforeEach } from "vitest";
7
- import { getRecord, createRecord, updateRecord, deleteRecord } from "./records.js";
8
- // Mock the uiApiClient
9
- vi.mock("../clients.js", () => {
10
- const mockGet = vi.fn();
11
- const mockPost = vi.fn();
12
- const mockPatch = vi.fn();
13
- const mockDelete = vi.fn();
14
- return {
15
- uiApiClient: {
16
- get: mockGet,
17
- post: mockPost,
18
- patch: mockPatch,
19
- delete: mockDelete,
20
- },
21
- };
22
- });
23
- // Get references to the mocked functions for assertions
24
- const { uiApiClient } = await import("../clients.js");
25
- const mockGet = vi.mocked(uiApiClient.get);
26
- const mockPost = vi.mocked(uiApiClient.post);
27
- const mockPatch = vi.mocked(uiApiClient.patch);
28
- const mockDelete = vi.mocked(uiApiClient.delete);
29
- describe("Records API Utils", () => {
30
- beforeEach(() => {
31
- vi.clearAllMocks();
32
- });
33
- describe("getRecord", () => {
34
- const data = {
35
- fields: {
36
- Name: { value: "Test Account" },
37
- Industry: { value: "Technology" },
38
- Phone: { value: "555-0123" },
39
- },
40
- };
41
- const mockRecordResponse = { json: vi.fn().mockResolvedValue(data) };
42
- it("fetches a record by ID with default params", async () => {
43
- mockGet.mockResolvedValue(mockRecordResponse);
44
- const result = await getRecord("001000000000001");
45
- expect(mockGet).toHaveBeenCalledWith("/records/001000000000001");
46
- expect(result).toEqual(data);
47
- });
48
- it("fetches a record by ID with custom params", async () => {
49
- mockGet.mockResolvedValue(mockRecordResponse);
50
- const params = { fields: "Name,Industry,Phone" };
51
- const result = await getRecord("001000000000001", params);
52
- expect(mockGet).toHaveBeenCalledWith("/records/001000000000001?fields=Name%2CIndustry%2CPhone");
53
- expect(result).toEqual(data);
54
- });
55
- it("handles API errors correctly", async () => {
56
- const apiError = new Error("Record not found");
57
- mockGet.mockRejectedValue(apiError);
58
- await expect(getRecord("001000000000001")).rejects.toThrow("Record not found");
59
- expect(mockGet).toHaveBeenCalledWith("/records/001000000000001");
60
- });
61
- });
62
- describe("createRecord", () => {
63
- const data = {
64
- id: "001000000000002",
65
- success: true,
66
- };
67
- const mockCreateResponse = { json: vi.fn().mockResolvedValue(data) };
68
- it("creates a record with correct API name and fields", async () => {
69
- mockPost.mockResolvedValue(mockCreateResponse);
70
- const apiName = "Account";
71
- const fields = {
72
- Name: "New Test Account",
73
- Industry: "Healthcare",
74
- Phone: "555-0456",
75
- };
76
- const result = await createRecord(apiName, fields);
77
- expect(mockPost).toHaveBeenCalledWith("/records", { apiName, fields });
78
- expect(result).toEqual(data);
79
- });
80
- it("handles empty fields object", async () => {
81
- mockPost.mockResolvedValue(mockCreateResponse);
82
- const result = await createRecord("Account", {});
83
- expect(mockPost).toHaveBeenCalledWith("/records", {
84
- apiName: "Account",
85
- fields: {},
86
- });
87
- expect(result).toEqual(data);
88
- });
89
- it("handles API errors during creation", async () => {
90
- const apiError = new Error("Invalid field value");
91
- mockPost.mockRejectedValue(apiError);
92
- const fields = { Name: "" }; // Invalid empty name
93
- await expect(createRecord("Account", fields)).rejects.toThrow("Invalid field value");
94
- expect(mockPost).toHaveBeenCalledWith("/records", {
95
- apiName: "Account",
96
- fields,
97
- });
98
- });
99
- });
100
- describe("updateRecord", () => {
101
- const data = { success: true };
102
- const mockUpdateResponse = { json: vi.fn().mockResolvedValue(data) };
103
- it("updates a record with correct ID and fields", async () => {
104
- mockPatch.mockResolvedValue(mockUpdateResponse);
105
- const recordId = "001000000000001";
106
- const fields = {
107
- Name: "Updated Account Name",
108
- Industry: "Finance",
109
- };
110
- const result = await updateRecord(recordId, fields);
111
- expect(mockPatch).toHaveBeenCalledWith(`/records/${recordId}`, {
112
- fields,
113
- });
114
- expect(result).toEqual(data);
115
- });
116
- it("handles partial field updates", async () => {
117
- mockPatch.mockResolvedValue(mockUpdateResponse);
118
- const recordId = "001000000000001";
119
- const fields = { Industry: "Manufacturing" };
120
- const result = await updateRecord(recordId, fields);
121
- expect(mockPatch).toHaveBeenCalledWith(`/records/${recordId}`, {
122
- fields,
123
- });
124
- expect(result).toEqual(data);
125
- });
126
- it("handles API errors during update", async () => {
127
- const apiError = new Error("Record is locked");
128
- mockPatch.mockRejectedValue(apiError);
129
- const recordId = "001000000000001";
130
- const fields = { Name: "New Name" };
131
- await expect(updateRecord(recordId, fields)).rejects.toThrow("Record is locked");
132
- expect(mockPatch).toHaveBeenCalledWith(`/records/${recordId}`, {
133
- fields,
134
- });
135
- });
136
- it("handles empty fields object for update", async () => {
137
- mockPatch.mockResolvedValue(mockUpdateResponse);
138
- const result = await updateRecord("001000000000001", {});
139
- expect(mockPatch).toHaveBeenCalledWith("/records/001000000000001", {
140
- fields: {},
141
- });
142
- expect(result).toEqual(data);
143
- });
144
- });
145
- describe("deleteRecord", () => {
146
- it("deletes a record by ID and returns true", async () => {
147
- mockDelete.mockResolvedValue({});
148
- const result = await deleteRecord("001000000000001");
149
- expect(mockDelete).toHaveBeenCalledWith("/records/001000000000001");
150
- expect(result).toBe(true);
151
- });
152
- it("handles API errors during deletion", async () => {
153
- const apiError = new Error("Cannot delete record");
154
- mockDelete.mockRejectedValue(apiError);
155
- await expect(deleteRecord("001000000000001")).rejects.toThrow("Cannot delete record");
156
- expect(mockDelete).toHaveBeenCalledWith("/records/001000000000001");
157
- });
158
- it("always returns true on successful deletion", async () => {
159
- mockDelete.mockResolvedValue({ status: 204 });
160
- const result = await deleteRecord("001000000000001");
161
- expect(result).toBe(true);
162
- });
163
- });
164
- describe("Error Handling", () => {
165
- it("propagates network errors from getRecord", async () => {
166
- const networkError = new Error("Network timeout");
167
- networkError.name = "NetworkError";
168
- mockGet.mockRejectedValue(networkError);
169
- await expect(getRecord("001000000000001")).rejects.toThrow("Network timeout");
170
- });
171
- it("propagates validation errors from createRecord", async () => {
172
- const validationError = new Error("Required field missing");
173
- validationError.name = "ValidationError";
174
- mockPost.mockRejectedValue(validationError);
175
- await expect(createRecord("Account", {})).rejects.toThrow("Required field missing");
176
- });
177
- it("propagates permission errors from updateRecord", async () => {
178
- const permissionError = new Error("Insufficient permissions");
179
- permissionError.name = "PermissionError";
180
- mockPatch.mockRejectedValue(permissionError);
181
- await expect(updateRecord("001000000000001", { Name: "Test" })).rejects.toThrow("Insufficient permissions");
182
- });
183
- it("propagates not found errors from deleteRecord", async () => {
184
- const notFoundError = new Error("Record not found");
185
- notFoundError.name = "NotFoundError";
186
- mockDelete.mockRejectedValue(notFoundError);
187
- await expect(deleteRecord("001000000000001")).rejects.toThrow("Record not found");
188
- });
189
- });
190
- });
@@ -1,7 +0,0 @@
1
- /**
2
- * Copyright (c) 2026, Salesforce, Inc.,
3
- * All rights reserved.
4
- * For full license text, see the LICENSE.txt file
5
- */
6
- export {};
7
- //# sourceMappingURL=user.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"user.test.d.ts","sourceRoot":"","sources":["../../../src/api/utils/user.test.ts"],"names":[],"mappings":"AAAA;;;;GAIG"}
@@ -1,108 +0,0 @@
1
- /**
2
- * Copyright (c) 2026, Salesforce, Inc.,
3
- * All rights reserved.
4
- * For full license text, see the LICENSE.txt file
5
- */
6
- import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
7
- import { getCurrentUser } from "./user.js";
8
- const mockFetch = vi.fn();
9
- vi.mock("@salesforce/sdk-data", () => ({
10
- getDataSDK: vi.fn(() => Promise.resolve({
11
- fetch: mockFetch,
12
- })),
13
- }));
14
- // Mock console.error to avoid noise in test output
15
- const mockConsoleError = vi.spyOn(console, "error").mockImplementation(() => {
16
- /* noop */
17
- });
18
- describe("User API Utils", () => {
19
- beforeEach(() => {
20
- vi.clearAllMocks();
21
- });
22
- afterEach(() => {
23
- mockConsoleError.mockClear();
24
- });
25
- describe("getCurrentUser", () => {
26
- const mockChatterUser = {
27
- id: "005000000000001",
28
- name: "John Doe",
29
- email: "john@example.com",
30
- username: "john@example.com",
31
- };
32
- it("successfully fetches current user", async () => {
33
- mockFetch.mockResolvedValue({
34
- ok: true,
35
- json: () => Promise.resolve(mockChatterUser),
36
- });
37
- const result = await getCurrentUser();
38
- expect(mockFetch).toHaveBeenCalledWith("/services/data/v99.0/chatter/users/me");
39
- expect(result).toEqual({
40
- id: "005000000000001",
41
- name: "John Doe",
42
- });
43
- });
44
- it('falls back to "User" when name is empty', async () => {
45
- mockFetch.mockResolvedValue({
46
- ok: true,
47
- json: () => Promise.resolve({
48
- id: "005000000000003",
49
- name: "",
50
- }),
51
- });
52
- const result = await getCurrentUser();
53
- expect(result).toEqual({
54
- id: "005000000000003",
55
- name: "User",
56
- });
57
- });
58
- it('falls back to "User" when name is null', async () => {
59
- mockFetch.mockResolvedValue({
60
- ok: true,
61
- json: () => Promise.resolve({
62
- id: "005000000000004",
63
- name: null,
64
- }),
65
- });
66
- const result = await getCurrentUser();
67
- expect(result).toEqual({
68
- id: "005000000000004",
69
- name: "User",
70
- });
71
- });
72
- it('falls back to "User" when name is undefined', async () => {
73
- mockFetch.mockResolvedValue({
74
- ok: true,
75
- json: () => Promise.resolve({
76
- id: "005000000000005",
77
- }),
78
- });
79
- const result = await getCurrentUser();
80
- expect(result).toEqual({
81
- id: "005000000000005",
82
- name: "User",
83
- });
84
- });
85
- it("throws error when request fails", async () => {
86
- mockFetch.mockResolvedValue({
87
- ok: false,
88
- status: 500,
89
- });
90
- await expect(getCurrentUser()).rejects.toThrow("HTTP 500");
91
- expect(mockConsoleError).toHaveBeenCalled();
92
- });
93
- it("throws error when no user data is found", async () => {
94
- mockFetch.mockResolvedValue({
95
- ok: true,
96
- json: () => Promise.resolve({}),
97
- });
98
- await expect(getCurrentUser()).rejects.toThrow("No user data found in Chatter API response");
99
- expect(mockConsoleError).toHaveBeenCalled();
100
- });
101
- it("throws error when fetch rejects", async () => {
102
- const apiError = new Error("Network error");
103
- mockFetch.mockRejectedValue(apiError);
104
- await expect(getCurrentUser()).rejects.toThrow("Network error");
105
- expect(mockConsoleError).toHaveBeenCalledWith("Error fetching user data:", apiError);
106
- });
107
- });
108
- });
@@ -1,108 +0,0 @@
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 { TEXT_TAGS } from "./editableManager.js";
11
- import { getElementStyles } from "./utils/cssUtils.js";
12
- import { getLabelFromSource, getSourceFromDataAttributes } from "./utils/sourceUtils.js";
13
- export class CommunicationManager {
14
- constructor() {
15
- // CommunicationManager has no required initialization
16
- }
17
- /**
18
- * Notify the extension about a selected component
19
- * @param element - The selected element
20
- */
21
- notifyComponentSelected(element) {
22
- const label = getLabelFromSource(element);
23
- // Temporarily remove design-mode-selected class to get original styles
24
- const wasSelected = element.classList.contains("design-mode-selected");
25
- if (wasSelected) {
26
- element.classList.remove("design-mode-selected");
27
- }
28
- const styles = getElementStyles(element);
29
- // Restore design-mode-selected class if it was present
30
- if (wasSelected) {
31
- element.classList.add("design-mode-selected");
32
- }
33
- // Source location metadata injected at compile time (babel-plugin-enhanced-locator)
34
- const debugSource = getSourceFromDataAttributes(element);
35
- const textType = element.dataset?.textType ?? "none";
36
- const hasNonEditableText = TEXT_TAGS.includes(element.tagName) && (textType === "dynamic" || textType === "mixed");
37
- // Send message to parent window
38
- try {
39
- if (window.parent !== window) {
40
- window.parent.postMessage({
41
- type: "component-selected",
42
- component: {
43
- name: label,
44
- tagName: element.tagName,
45
- styles: {
46
- ...styles,
47
- },
48
- debugSource: debugSource,
49
- hasNonEditableText,
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
- tagName: element.tagName,
63
- };
64
- }
65
- /**
66
- * Notify the extension about a text change
67
- * @param element - The element that changed
68
- * @param originalText - The original text
69
- * @param newText - The new text
70
- */
71
- notifyTextChange(element, originalText, newText) {
72
- const label = getLabelFromSource(element);
73
- const debugSource = getSourceFromDataAttributes(element);
74
- try {
75
- if (window.parent !== window) {
76
- window.parent.postMessage({
77
- type: "text-changed",
78
- change: {
79
- componentName: label,
80
- tagName: element.tagName,
81
- originalText,
82
- newText,
83
- debugSource,
84
- },
85
- }, "*");
86
- }
87
- }
88
- catch (error) {
89
- console.log("Could not notify extension about text change:", error);
90
- }
91
- }
92
- /**
93
- * Notify the parent window that interactions initialization is complete
94
- */
95
- notifyInitializationComplete() {
96
- try {
97
- if (typeof window !== "undefined" && window.parent && window.parent !== window) {
98
- window.parent.postMessage({
99
- type: "interactions-initialized",
100
- }, "*");
101
- }
102
- }
103
- catch (error) {
104
- const err = error;
105
- console.warn("Could not send initialization message to parent:", err.message);
106
- }
107
- }
108
- }
@@ -1,80 +0,0 @@
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 ComponentMatcher {
7
- allowlist;
8
- constructor(options = {}) {
9
- this.allowlist = options.allowlist ?? [];
10
- }
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
- matchesList(element, selectors) {
23
- return selectors.some((selector) => element.matches(selector));
24
- }
25
- /**
26
- * Checks if a label represents a component name (not a fallback like tag name, ID, or text content)
27
- * @param label - The label to check
28
- * @param tagName - The element's tag name (lowercase)
29
- * @returns True if the label is a component name
30
- */
31
- isComponentNameLabel(label, tagName) {
32
- return (label !== tagName && !label.includes("#") && !label.includes("(") && !label.includes('"'));
33
- }
34
- /**
35
- * Check if an element is highlightable.
36
- * @param element - The element to check
37
- * @returns True if the element should be highlighted
38
- */
39
- isHighlightableElement(element) {
40
- if (!element || element === document.body || element === document.documentElement) {
41
- return false;
42
- }
43
- if (!this.hasSourceMetadata(element)) {
44
- return false;
45
- }
46
- if (this.allowlist.length > 0) {
47
- return this.matchesList(element, this.allowlist);
48
- }
49
- return true;
50
- }
51
- /**
52
- * Find the nearest highlightable element by walking up the DOM tree
53
- * @param target - The target element
54
- * @returns The highlightable element or null
55
- */
56
- findHighlightableElement(target) {
57
- if (!target || target === document.body || target === document.documentElement) {
58
- return null;
59
- }
60
- // Fast path: use closest() if supported.
61
- const closest = typeof target.closest === "function"
62
- ? target.closest("[data-source-file]")
63
- : null;
64
- if (closest &&
65
- closest !== document.body &&
66
- closest !== document.documentElement &&
67
- this.isHighlightableElement(closest)) {
68
- return closest;
69
- }
70
- // Fallback: manual walk.
71
- let current = target;
72
- while (current && current !== document.body && current !== document.documentElement) {
73
- if (this.isHighlightableElement(current)) {
74
- return current;
75
- }
76
- current = current.parentElement;
77
- }
78
- return null;
79
- }
80
- }
@@ -1,95 +0,0 @@
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
- import { findElementsBySourceLocation, getSourceFromDataAttributes } from "./utils/sourceUtils.js";
12
- export const TEXT_TAGS = ["H1", "H2", "H3", "H4", "H5", "H6", "P", "SPAN", "A", "BUTTON", "LABEL"];
13
- export class EditableManager {
14
- communicationManager;
15
- boundHandleBlur;
16
- boundHandleKeydown;
17
- boundHandleInput;
18
- editableGroup;
19
- constructor(communicationManager) {
20
- this.communicationManager = communicationManager;
21
- this.boundHandleBlur = this._handleBlur.bind(this);
22
- this.boundHandleKeydown = this._handleKeydown.bind(this);
23
- this.boundHandleInput = this._handleInput.bind(this);
24
- this.editableGroup = [];
25
- }
26
- makeEditableIfText(element) {
27
- if (!this._isTextElement(element)) {
28
- return;
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
- }
37
- element.contentEditable = "true";
38
- element.addEventListener("blur", this.boundHandleBlur);
39
- element.addEventListener("keydown", this.boundHandleKeydown);
40
- element.addEventListener("input", this.boundHandleInput);
41
- }
42
- removeEditable(element) {
43
- if (element.contentEditable === "true") {
44
- element.contentEditable = "false";
45
- }
46
- element.removeEventListener("blur", this.boundHandleBlur);
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 = [];
53
- }
54
- _isTextElement(element) {
55
- return (TEXT_TAGS.includes(element.tagName) &&
56
- (element.textContent ?? "").trim().length > 0 &&
57
- element.dataset.textType === "static");
58
- }
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
- }
66
- _handleBlur(e) {
67
- const element = e.target;
68
- const newText = element.textContent ?? "";
69
- const originalText = element.dataset.originalText ?? "";
70
- if (newText !== originalText) {
71
- for (const el of this.editableGroup) {
72
- el.dataset.originalText = newText;
73
- }
74
- if (this.communicationManager) {
75
- this.communicationManager.notifyTextChange(element, originalText, newText);
76
- }
77
- }
78
- this.removeEditable(element);
79
- }
80
- _handleKeydown(e) {
81
- const element = e.target;
82
- if (e.key === "Enter" && !e.shiftKey) {
83
- e.preventDefault();
84
- element.blur();
85
- }
86
- if (e.key === "Escape") {
87
- for (const el of this.editableGroup) {
88
- if (el.dataset.originalText) {
89
- el.textContent = el.dataset.originalText;
90
- }
91
- }
92
- element.blur();
93
- }
94
- }
95
- }