Skip to content

Commit 638637b

Browse files
Merge branch 'develop_v3' into VE-5851-no-update-permission-disable-fields
2 parents c4711b4 + 063f81a commit 638637b

6 files changed

Lines changed: 105 additions & 54 deletions

File tree

src/visualBuilder/__test__/index.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ vi.mock("../utils/visualBuilderPostMessage", async () => {
3535
Object.defineProperty(globalThis, "crypto", {
3636
value: {
3737
getRandomValues: (arr: Array<any>) => crypto.randomBytes(arr.length),
38+
randomUUID: () => crypto.randomUUID()
3839
},
3940
});
4041
// Increase the timeout for the test

src/visualBuilder/index.ts

Lines changed: 0 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -268,38 +268,6 @@ export class VisualBuilder {
268268
}, 1000)
269269
);
270270

271-
private dataCslpMutationObserver = new MutationObserver((mutations) => {
272-
let shouldCheck = false;
273-
274-
mutations.forEach((mutation) => {
275-
if (
276-
mutation.type === "childList" &&
277-
mutation.addedNodes.length > 0
278-
) {
279-
for (const node of mutation.addedNodes) {
280-
if (node.nodeType === Node.ELEMENT_NODE) {
281-
if ((node as Element).hasAttribute("data-cslp")) {
282-
shouldCheck = true;
283-
break;
284-
}
285-
}
286-
}
287-
}
288-
});
289-
290-
if (shouldCheck) {
291-
const dataCslpElements = document.querySelectorAll("[data-cslp]");
292-
if (dataCslpElements.length > 0) {
293-
dataCslpElements.forEach((element) => {
294-
if (!element.hasAttribute("data-cslp-unique-id")) {
295-
const uniqueId = `cslp-${window.crypto.randomUUID()}`;
296-
element.setAttribute("data-cslp-unique-id", uniqueId);
297-
}
298-
});
299-
}
300-
}
301-
});
302-
303271
constructor() {
304272
// Handles changes in element positions due to sidebar toggling or window resizing,
305273
// triggering a redraw of the visual builder
@@ -329,11 +297,6 @@ export class VisualBuilder {
329297
return;
330298
}
331299

332-
this.dataCslpMutationObserver.observe(document.body, {
333-
childList: true,
334-
subtree: true,
335-
});
336-
337300
visualBuilderPostMessage
338301
?.send<IVisualBuilderInitEvent>("init", {
339302
isSSR: config.ssr,
@@ -447,7 +410,6 @@ export class VisualBuilder {
447410
this.resizeObserver.disconnect();
448411
this.mutationObserver.disconnect();
449412
this.threadMutationObserver.disconnect();
450-
this.dataCslpMutationObserver.disconnect()
451413

452414
// Clear global state
453415
VisualBuilder.VisualBuilderGlobalState.value = {

src/visualBuilder/listeners/mouseClick.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import { generateThread } from "../generators/generateThread";
2929
import { isCollabThread } from "../generators/generateThread";
3030
import { toggleCollabPopup } from "../generators/generateThread";
3131
import { fixSvgXPath } from "../utils/collabUtils";
32+
import { v4 as uuidV4 } from "uuid";
3233
import { getEntryPermissionsCached } from "../utils/getEntryPermissionsCached";
3334

3435
type HandleBuilderInteractionParams = Omit<
@@ -80,6 +81,22 @@ async function handleBuilderInteraction(
8081
(eventTarget.hasAttribute("data-cslp") ||
8182
eventTarget.closest("[data-cslp]"));
8283

84+
// if multiple elements with the same cslp element are found,
85+
// assign a unique ID to each element which we can use to identify
86+
// them in updateFocussedState and other places where we
87+
// would have queried the element by data-cslp
88+
const duplicates = document.querySelectorAll(
89+
`[data-cslp="${eventTarget?.getAttribute("data-cslp")}"]`
90+
);
91+
if (duplicates.length > 1) {
92+
duplicates.forEach((ele) => {
93+
if (!ele.hasAttribute("data-cslp-unique-id")) {
94+
const uniqueId = `cslp-${uuidV4()}`;
95+
ele.setAttribute("data-cslp-unique-id", uniqueId);
96+
}
97+
});
98+
}
99+
83100
// if the target element is a studio-ui element, return
84101
// this is currently used for the "Edit in Studio" button
85102
if (eventTarget?.getAttribute("data-studio-ui") === "true") {

src/visualBuilder/utils/__test__/handleFieldMouseDown.test.ts

Lines changed: 59 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -128,26 +128,36 @@ describe("handle numeric field key down", () => {
128128
});
129129

130130
describe("handle keydown in button contenteditable", () => {
131-
let button: HTMLButtonElement | undefined;
132-
let spiedPreventDefault: MockInstance<(e: []) => void> | undefined;
133-
let spiedInsertSpaceAtCursor:
134-
| MockInstance<(typeof insertSpaceAtCursor)["insertSpaceAtCursor"]>
135-
| undefined;
131+
let spiedSendFieldEvent: MockInstance<() => void> | undefined;
132+
133+
beforeEach(() => {
134+
spiedSendFieldEvent = vi
135+
.spyOn(generateOverlay, "sendFieldEvent")
136+
.mockImplementation(() => {});
137+
const visualBuilderContainer = document.createElement("div");
138+
visualBuilderContainer.classList.add("visual-builder__container");
139+
document.body.appendChild(visualBuilderContainer);
140+
});
141+
142+
afterEach(() => {
143+
vi.clearAllMocks();
144+
});
136145

137146
test("should insert space in button content-editable", () => {
147+
let spiedPreventDefault: MockInstance<(e: []) => void> | undefined;
138148
vi.spyOn(window, "getSelection").mockReturnValue({
139-
// @ts-ignore
149+
// @ts-expect-error mocking only required properties
140150
getRangeAt: (n: number) => ({
141151
startOffset: 0,
142152
endOffset: 0,
143153
}),
144154
});
145-
spiedInsertSpaceAtCursor = vi.spyOn(
155+
const spiedInsertSpaceAtCursor = vi.spyOn(
146156
insertSpaceAtCursor,
147157
"insertSpaceAtCursor"
148158
);
149159

150-
button = document.createElement("button");
160+
const button = document.createElement("button");
151161
button.innerHTML = "Test";
152162
button.setAttribute("contenteditable", "true");
153163
button.setAttribute(
@@ -169,6 +179,47 @@ describe("handle keydown in button contenteditable", () => {
169179

170180
expect(spiedPreventDefault).toHaveBeenCalledTimes(1);
171181
expect(spiedInsertSpaceAtCursor).toHaveBeenCalledWith(button);
182+
expect(spiedSendFieldEvent).toHaveBeenCalled();
183+
});
184+
185+
test("should insert space in span content-editable inside button", () => {
186+
let spiedPreventDefault: MockInstance<(e: []) => void> | undefined;
187+
vi.spyOn(window, "getSelection").mockReturnValue({
188+
// @ts-expect-error mocking only required properties
189+
getRangeAt: (n: number) => ({
190+
startOffset: 0,
191+
endOffset: 0,
192+
}),
193+
});
194+
const spiedInsertSpaceAtCursor = vi.spyOn(
195+
insertSpaceAtCursor,
196+
"insertSpaceAtCursor"
197+
);
198+
199+
const button = document.createElement("button");
200+
const span = document.createElement("span");
201+
button.appendChild(span);
202+
span.setAttribute("contenteditable", "true");
203+
span.setAttribute(
204+
VISUAL_BUILDER_FIELD_TYPE_ATTRIBUTE_KEY,
205+
"single_line"
206+
);
207+
208+
span.addEventListener("keydown", (e) => {
209+
spiedPreventDefault = vi.spyOn(e, "preventDefault");
210+
handleFieldKeyDown(e);
211+
});
212+
213+
const keyDownEvent = new KeyboardEvent("keydown", {
214+
bubbles: true,
215+
key: "Space",
216+
code: "Space",
217+
});
218+
span.dispatchEvent(keyDownEvent);
219+
220+
expect(spiedPreventDefault).toHaveBeenCalledTimes(1);
221+
expect(spiedInsertSpaceAtCursor).toHaveBeenCalledWith(span);
222+
expect(spiedSendFieldEvent).toHaveBeenCalled();
172223
});
173224
});
174225

src/visualBuilder/utils/handleFieldMouseDown.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,15 @@ export function handleFieldKeyDown(e: Event): void {
4444
VISUAL_BUILDER_FIELD_TYPE_ATTRIBUTE_KEY
4545
) as FieldDataType | null;
4646

47-
if (targetElement.tagName === "BUTTON") {
47+
if (
48+
event
49+
.composedPath()
50+
.some(
51+
(element) =>
52+
element instanceof Element && element.tagName === "BUTTON"
53+
)
54+
) {
55+
// custom space handling when a button is involved
4856
handleKeyDownOnButton(event);
4957
}
5058
if (fieldType === FieldDataType.NUMBER) {
@@ -56,11 +64,12 @@ export function handleFieldKeyDown(e: Event): void {
5664

5765
// spaces do not work inside a button content-editable
5866
// this adds a space and moves the cursor ahead, the
59-
// button press event is also prevented
67+
// button press event is also prevented, finally syncs the field
6068
function handleKeyDownOnButton(e: KeyboardEvent) {
6169
if (e.code === "Space" && e.target) {
6270
e.preventDefault();
6371
insertSpaceAtCursor(e.target as HTMLElement);
72+
throttledFieldSync();
6473
}
6574
}
6675

src/visualBuilder/utils/updateFocussedState.ts

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -107,11 +107,18 @@ export async function updateFocussedState({
107107
return;
108108
}
109109

110+
// prefer data-cslp-unique-id when available else use data-cslp.
111+
// unique ID is added on click when multiple elements with same
112+
// data-cslp are found.
113+
const previousSelectedElementCslp =
114+
editableElement?.getAttribute("data-cslp") || "";
110115
const previousSelectedElementCslpUniqueId =
111116
previousSelectedEditableDOM?.getAttribute("data-cslp-unique-id");
112-
const newPreviousSelectedElement = document.querySelector(
113-
`[data-cslp-unique-id="${previousSelectedElementCslpUniqueId}"]`
114-
);
117+
const newPreviousSelectedElement =
118+
document.querySelector(
119+
`[data-cslp-unique-id="${previousSelectedElementCslpUniqueId}"]`
120+
) ||
121+
document.querySelector(`[data-cslp="${previousSelectedElementCslp}"]`);
115122
if (!newPreviousSelectedElement && resizeObserver) {
116123
hideFocusOverlay({
117124
visualBuilderOverlayWrapper: overlayWrapper,
@@ -246,10 +253,14 @@ export function updateFocussedStateOnMutation(
246253
.previousSelectedEditableDOM;
247254
if (!selectedElement) return;
248255

249-
const selectedElementCslpUniqueId = selectedElement?.getAttribute("data-cslp-unique-id");
250-
const newSelectedElement = document.querySelector(
251-
`[data-cslp-unique-id="${selectedElementCslpUniqueId}"]`
256+
const selectedElementCslp = selectedElement?.getAttribute("data-cslp");
257+
const selectedElementCslpUniqueId = selectedElement?.getAttribute(
258+
"data-cslp-unique-id"
252259
);
260+
const newSelectedElement =
261+
document.querySelector(
262+
`[data-cslp-unique-id="${selectedElementCslpUniqueId}"]`
263+
) || document.querySelector(`[data-cslp="${selectedElementCslp}"]`);
253264
if (!newSelectedElement && resizeObserver) {
254265
hideFocusOverlay({
255266
visualBuilderOverlayWrapper: focusOverlayWrapper,

0 commit comments

Comments
 (0)