Skip to content

Commit b223988

Browse files
authored
Merge pull request #497 from contentstack/VB-248-dev
VB-248 fixed the cursor moving to the start with a explicitly adding pseudo element on second time a field is visited
2 parents 14b1f37 + 4e8d5e8 commit b223988

File tree

5 files changed

+195
-2
lines changed

5 files changed

+195
-2
lines changed

.talismanrc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ fileignoreconfig:
1010
checksum: d0ef271ee5381d9feab06bda6e7e89bd0a882fee87495627bd811c1f0a5459c7
1111
- filename: package-lock.json
1212
checksum: fd06363871d0ee16ebfb5d9d0cc479e0922a615bb76584b80bb6933ee6c3e237
13+
- filename: src/visualBuilder/utils/__test__/handleFieldMouseDown.test.ts
14+
checksum: dc20802eab76834de7aadb797b14076f1f1a9c0662b32493563fe68fd5cd6e16
1315
- filename: CHANGELOG.md
1416
checksum: 873106e25dafe0355c55724936cfe0ecc9d0192a2a82c98eddaf5648f23d5ee7
1517
version: "1.0"

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

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ vi.mock("../../generators/generatePseudoEditableField", () => ({
4040
generatePseudoEditableElement: vi.fn(() => {
4141
const pseudoElement = document.createElement("div");
4242
pseudoElement.setAttribute("data-testid", "pseudo-element");
43+
pseudoElement.className = "visual-builder__pseudo-editable-element";
4344
return pseudoElement;
4445
}),
4546
isEllipsisActive: vi.fn(() => false),
@@ -276,4 +277,116 @@ describe("enableInlineEditing", () => {
276277
VisualBuilder.VisualBuilderGlobalState.value.focusFieldValue
277278
).toBe("Test content");
278279
});
280+
281+
it("should create pseudo element when field is last edited", () => {
282+
document.body.appendChild(editableElement);
283+
editableElement.setAttribute("data-cs-last-edited", "true");
284+
285+
enableInlineEditing({
286+
expectedFieldData: "Test content",
287+
editableElement,
288+
fieldType: FieldDataType.SINGLELINE,
289+
elements: {
290+
visualBuilderContainer,
291+
resizeObserver,
292+
lastEditedField: null,
293+
},
294+
});
295+
296+
expect(generatePseudoEditableElement).toHaveBeenCalled();
297+
expect(editableElement.style.visibility).toBe("hidden");
298+
expect(resizeObserver.observe).toHaveBeenCalled();
299+
300+
document.body.removeChild(editableElement);
301+
});
302+
303+
it("should set data-cs-last-edited attribute on editable element", () => {
304+
enableInlineEditing({
305+
expectedFieldData: "Test content",
306+
editableElement,
307+
fieldType: FieldDataType.SINGLELINE,
308+
elements: {
309+
visualBuilderContainer,
310+
resizeObserver,
311+
lastEditedField: null,
312+
},
313+
});
314+
315+
expect(editableElement.getAttribute("data-cs-last-edited")).toBe("true");
316+
});
317+
318+
it("should create pseudo element when field is last edited even with same content", () => {
319+
document.body.appendChild(editableElement);
320+
editableElement.setAttribute("data-cs-last-edited", "true");
321+
editableElement.textContent = "Test content";
322+
editableElement.innerText = "Test content";
323+
324+
enableInlineEditing({
325+
expectedFieldData: "Test content",
326+
editableElement,
327+
fieldType: FieldDataType.SINGLELINE,
328+
elements: {
329+
visualBuilderContainer,
330+
resizeObserver,
331+
lastEditedField: null,
332+
},
333+
});
334+
335+
expect(generatePseudoEditableElement).toHaveBeenCalled();
336+
expect(editableElement.style.visibility).toBe("hidden");
337+
338+
document.body.removeChild(editableElement);
339+
});
340+
341+
it("should not create pseudo element when field is not last edited and content matches", () => {
342+
const otherElement = document.createElement("div");
343+
otherElement.setAttribute("data-cs-last-edited", "true");
344+
document.body.appendChild(otherElement);
345+
346+
editableElement.textContent = "Test content";
347+
editableElement.innerText = "Test content";
348+
349+
enableInlineEditing({
350+
expectedFieldData: "Test content",
351+
editableElement,
352+
fieldType: FieldDataType.SINGLELINE,
353+
elements: {
354+
visualBuilderContainer,
355+
resizeObserver,
356+
lastEditedField: null,
357+
},
358+
});
359+
360+
expect(generatePseudoEditableElement).not.toHaveBeenCalled();
361+
expect(editableElement.style.visibility).toBe("");
362+
expect(editableElement.getAttribute("data-cs-last-edited")).toBe("true");
363+
364+
document.body.removeChild(otherElement);
365+
});
366+
367+
it("should not create nested pseudo elements when pseudo element is clicked", () => {
368+
document.body.appendChild(editableElement);
369+
editableElement.setAttribute("data-cs-last-edited", "true");
370+
371+
enableInlineEditing({
372+
expectedFieldData: "Test content",
373+
editableElement,
374+
fieldType: FieldDataType.SINGLELINE,
375+
elements: {
376+
visualBuilderContainer,
377+
resizeObserver,
378+
lastEditedField: null,
379+
},
380+
});
381+
382+
expect(generatePseudoEditableElement).toHaveBeenCalled();
383+
expect(editableElement.style.visibility).toBe("hidden");
384+
385+
const pseudoElement = visualBuilderContainer.querySelector(".visual-builder__pseudo-editable-element");
386+
expect(pseudoElement).toBeTruthy();
387+
expect(pseudoElement?.getAttribute("data-cslp")).toBeNull();
388+
expect(editableElement.getAttribute("data-cs-last-edited")).toBe("true");
389+
390+
document.body.removeChild(editableElement);
391+
});
279392
});

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

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -328,4 +328,69 @@ describe("`handleFieldInput`", () => {
328328
VisualBuilder.VisualBuilderGlobalState.value.focusFieldReceivedInput
329329
).toBe(true);
330330
});
331+
332+
test("should manage data-cs-last-edited attribute when field receives input", () => {
333+
const previousField = document.createElement("div");
334+
previousField.setAttribute("data-cs-last-edited", "true");
335+
document.body.appendChild(previousField);
336+
337+
const inputEvent = new InputEvent("input", {
338+
bubbles: true,
339+
});
340+
h1.dispatchEvent(inputEvent);
341+
342+
expect(previousField.getAttribute("data-cs-last-edited")).toBeNull();
343+
expect(h1.getAttribute("data-cs-last-edited")).toBe("true");
344+
345+
document.body.removeChild(previousField);
346+
});
347+
348+
test("should not change data-cs-last-edited attribute when same field receives input", () => {
349+
h1.setAttribute("data-cs-last-edited", "true");
350+
351+
const inputEvent = new InputEvent("input", {
352+
bubbles: true,
353+
});
354+
h1.dispatchEvent(inputEvent);
355+
356+
expect(h1.getAttribute("data-cs-last-edited")).toBe("true");
357+
});
358+
359+
test("should set data-cs-last-edited attribute on new field when no previous field exists", () => {
360+
const inputEvent = new InputEvent("input", {
361+
bubbles: true,
362+
});
363+
h1.dispatchEvent(inputEvent);
364+
365+
expect(h1.getAttribute("data-cs-last-edited")).toBe("true");
366+
});
367+
368+
test("should transfer data-cs-last-edited attribute between multiple fields", () => {
369+
const field1 = document.createElement("div");
370+
const field2 = document.createElement("div");
371+
field1.setAttribute(VISUAL_BUILDER_FIELD_TYPE_ATTRIBUTE_KEY, "number");
372+
field2.setAttribute(VISUAL_BUILDER_FIELD_TYPE_ATTRIBUTE_KEY, "number");
373+
field1.setAttribute("data-cs-last-edited", "true");
374+
375+
field1.addEventListener("input", handleFieldInput);
376+
field2.addEventListener("input", handleFieldInput);
377+
378+
document.body.appendChild(field1);
379+
document.body.appendChild(field2);
380+
381+
const inputEvent1 = new InputEvent("input", { bubbles: true });
382+
field1.dispatchEvent(inputEvent1);
383+
384+
expect(field1.getAttribute("data-cs-last-edited")).toBe("true");
385+
expect(field2.getAttribute("data-cs-last-edited")).toBeNull();
386+
387+
const inputEvent2 = new InputEvent("input", { bubbles: true });
388+
field2.dispatchEvent(inputEvent2);
389+
390+
expect(field1.getAttribute("data-cs-last-edited")).toBeNull();
391+
expect(field2.getAttribute("data-cs-last-edited")).toBe("true");
392+
393+
document.body.removeChild(field1);
394+
document.body.removeChild(field2);
395+
});
331396
});

src/visualBuilder/utils/enableInlineEditing.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ export function enableInlineEditing({
3131
const elementComputedDisplay =
3232
window.getComputedStyle(actualEditableField).display;
3333

34+
3435
let textContent =
3536
(editableElement as HTMLElement).innerText ||
3637
editableElement.textContent ||
@@ -41,14 +42,17 @@ export function enableInlineEditing({
4142
actualEditableField.addEventListener("paste", pasteAsPlainText);
4243
}
4344
const expectedTextContent = expectedFieldData;
45+
46+
const isFieldLastEdited = document.querySelector("[data-cs-last-edited]") === editableElement;
4447
if (
4548
(expectedTextContent && textContent !== expectedTextContent) ||
46-
isEllipsisActive(editableElement as HTMLElement)
49+
isEllipsisActive(editableElement as HTMLElement) ||
50+
isFieldLastEdited
4751
) {
4852
// TODO: Testing will be done in the E2E.
4953
const pseudoEditableField = generatePseudoEditableElement(
5054
{ editableElement: editableElement as HTMLElement },
51-
{ textContent: expectedFieldData }
55+
{ textContent: expectedFieldData }
5256
);
5357

5458
(editableElement as HTMLElement).style.visibility = "hidden";
@@ -90,6 +94,9 @@ export function enableInlineEditing({
9094
actualEditableField.setAttribute("contenteditable", "true");
9195
actualEditableField.addEventListener("input", handleFieldInput);
9296
actualEditableField.addEventListener("keydown", handleFieldKeyDown);
97+
98+
editableElement.setAttribute("data-cs-last-edited", "true");
99+
93100
// focus on the contenteditable element to start accepting input
94101
actualEditableField.focus();
95102

src/visualBuilder/utils/handleFieldMouseDown.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,12 @@ export function handleFieldInput(e: Event): void {
1616
const fieldType = targetElement.getAttribute(
1717
VISUAL_BUILDER_FIELD_TYPE_ATTRIBUTE_KEY
1818
) as FieldDataType | null;
19+
20+
const previousLastEditedElement = document.querySelector("[data-cs-last-edited]");
21+
if (previousLastEditedElement !== targetElement) {
22+
previousLastEditedElement?.removeAttribute("data-cs-last-edited");
23+
targetElement.setAttribute("data-cs-last-edited", "true");
24+
}
1925
if (
2026
event.type === "input" &&
2127
ALLOWED_INLINE_EDITABLE_FIELD.includes(fieldType as FieldDataType)

0 commit comments

Comments
 (0)