Skip to content

Commit d576d90

Browse files
authored
Merge pull request #356 from contentstack/VE-5061
fix(VE-5061): add observer to observe cslp of selected element
2 parents a597a94 + abf08bf commit d576d90

3 files changed

Lines changed: 128 additions & 60 deletions

File tree

src/visualBuilder/generators/generateOverlay.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,13 @@ interface HideOverlayParams
211211
}
212212

213213
export function hideOverlay(params: HideOverlayParams): void {
214+
const focusElementObserver =
215+
VisualBuilder.VisualBuilderGlobalState.value.focusElementObserver;
216+
if (focusElementObserver) {
217+
focusElementObserver.disconnect();
218+
VisualBuilder.VisualBuilderGlobalState.value.focusElementObserver =
219+
null;
220+
}
214221
hideFocusOverlay({
215222
visualBuilderContainer: params.visualBuilderContainer,
216223
visualBuilderOverlayWrapper: params.visualBuilderOverlayWrapper,

src/visualBuilder/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ interface VisualBuilderGlobalStateImpl {
5252
audienceMode: boolean;
5353
locale: string;
5454
variant: string | null;
55+
focusElementObserver: MutationObserver | null;
5556
}
5657

5758
export class VisualBuilder {
@@ -69,6 +70,7 @@ export class VisualBuilder {
6970
audienceMode: false,
7071
locale: Config.get().stackDetails.masterLocale || "en-us",
7172
variant: null,
73+
focusElementObserver: null,
7274
});
7375

7476
private handlePositionChange(editableElement: HTMLElement) {
@@ -344,6 +346,7 @@ export class VisualBuilder {
344346
audienceMode: false,
345347
locale: "en-us",
346348
variant: null,
349+
focusElementObserver: null,
347350
};
348351

349352
// Remove DOM elements

src/visualBuilder/listeners/mouseClick.ts

Lines changed: 118 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import { VB_EmptyBlockParentClass } from "../..";
2626
type HandleBuilderInteractionParams = Omit<
2727
EventListenerHandlerParams,
2828
"eventDetails" | "customCursor"
29-
>;
29+
> & { reEvaluate?: boolean };
3030

3131
type AddFocusOverlayParams = Pick<
3232
EventListenerHandlerParams,
@@ -86,42 +86,24 @@ async function handleBuilderInteraction(
8686
}
8787

8888
const eventDetails = getCsDataOfElement(params.event);
89-
visualBuilderPostMessage
90-
?.send(VisualBuilderPostMessageEvents.MOUSE_CLICK, {
91-
cslpData: eventDetails?.cslpData,
92-
fieldMetadata: eventDetails?.fieldMetadata,
93-
})
94-
.catch((err) => {
95-
console.warn("Error while sending post message", err);
96-
});
89+
90+
// Send mouse click post message
91+
sendMouseClickPostMessage(eventDetails);
92+
9793
if (
9894
!eventDetails ||
9995
!params.overlayWrapper ||
10096
!params.visualBuilderContainer
10197
) {
10298
return;
10399
}
104-
const { editableElement, fieldMetadata } = eventDetails;
105100

106-
if (
107-
VisualBuilder.VisualBuilderGlobalState.value
108-
.previousSelectedEditableDOM &&
109-
VisualBuilder.VisualBuilderGlobalState.value
110-
.previousSelectedEditableDOM !== editableElement
111-
) {
112-
cleanIndividualFieldResidual({
113-
overlayWrapper: params.overlayWrapper,
114-
visualBuilderContainer: params.visualBuilderContainer,
115-
focusedToolbar: params.focusedToolbar,
116-
resizeObserver: params.resizeObserver,
117-
});
118-
}
101+
const { editableElement, fieldMetadata } = eventDetails;
102+
// Clean residuals if necessary
103+
cleanResidualsIfNeeded(params, editableElement);
119104

120-
// if the selected element is our empty block element, return
121-
if (
122-
editableElement.classList.contains(VB_EmptyBlockParentClass) ||
123-
editableElement.classList.contains("visual-builder__empty-block")
124-
) {
105+
// Return if the selected element is an empty block
106+
if (isEmptyBlockElement(editableElement)) {
125107
return;
126108
}
127109

@@ -133,15 +115,87 @@ async function handleBuilderInteraction(
133115
VisualBuilder.VisualBuilderGlobalState.value
134116
.previousSelectedEditableDOM;
135117
if (
136-
previousSelectedElement &&
137-
previousSelectedElement === editableElement
118+
isSameSelectedElement(previousSelectedElement, editableElement, params)
138119
) {
139120
return;
140121
}
141122

142123
VisualBuilder.VisualBuilderGlobalState.value.previousSelectedEditableDOM =
143124
editableElement;
144125

126+
// Add overlay and focused toolbar
127+
addOverlayAndToolbar(params, eventDetails, editableElement);
128+
129+
const { cslpValue } = fieldMetadata;
130+
131+
toggleHighlightedCommentIconDisplay(cslpValue, false);
132+
133+
// Handle field schema and individual fields
134+
await handleFieldSchemaAndIndividualFields(
135+
params,
136+
eventDetails,
137+
fieldMetadata,
138+
editableElement
139+
);
140+
141+
// Observe changes to the editable element
142+
observeEditableElementChanges(params, editableElement);
143+
}
144+
145+
function sendMouseClickPostMessage(eventDetails: any) {
146+
visualBuilderPostMessage
147+
?.send(VisualBuilderPostMessageEvents.MOUSE_CLICK, {
148+
cslpData: eventDetails?.cslpData,
149+
fieldMetadata: eventDetails?.fieldMetadata,
150+
})
151+
.catch((err) => {
152+
console.warn("Error while sending post message", err);
153+
});
154+
}
155+
function cleanResidualsIfNeeded(
156+
params: HandleBuilderInteractionParams,
157+
editableElement: Element
158+
) {
159+
const previousSelectedElement =
160+
VisualBuilder.VisualBuilderGlobalState.value
161+
.previousSelectedEditableDOM;
162+
if (
163+
(previousSelectedElement &&
164+
previousSelectedElement !== editableElement) ||
165+
params.reEvaluate
166+
) {
167+
cleanIndividualFieldResidual({
168+
overlayWrapper: params.overlayWrapper!,
169+
visualBuilderContainer: params.visualBuilderContainer,
170+
focusedToolbar: params.focusedToolbar,
171+
resizeObserver: params.resizeObserver,
172+
});
173+
}
174+
}
175+
function isEmptyBlockElement(editableElement: Element): boolean {
176+
return (
177+
editableElement.classList.contains(VB_EmptyBlockParentClass) ||
178+
editableElement.classList.contains("visual-builder__empty-block")
179+
);
180+
}
181+
182+
function isSameSelectedElement(
183+
previousSelectedElement: Element | null,
184+
editableElement: Element,
185+
params: HandleBuilderInteractionParams
186+
): boolean {
187+
return !!(
188+
previousSelectedElement &&
189+
previousSelectedElement === editableElement &&
190+
!params.reEvaluate
191+
);
192+
}
193+
194+
function addOverlayAndToolbar(
195+
params: HandleBuilderInteractionParams,
196+
eventDetails: any,
197+
editableElement: Element
198+
) {
145199
addOverlay({
146200
overlayWrapper: params.overlayWrapper,
147201
resizeObserver: params.resizeObserver,
@@ -160,18 +214,20 @@ async function handleBuilderInteraction(
160214
});
161215
},
162216
});
163-
164-
const { content_type_uid, fieldPath, cslpValue } = fieldMetadata;
165-
166-
toggleHighlightedCommentIconDisplay(cslpValue, false);
167-
217+
}
218+
async function handleFieldSchemaAndIndividualFields(
219+
params: HandleBuilderInteractionParams,
220+
eventDetails: any,
221+
fieldMetadata: any,
222+
editableElement: Element
223+
) {
224+
const { content_type_uid, fieldPath } = fieldMetadata;
168225
const fieldSchema = await FieldSchemaMap.getFieldSchema(
169226
content_type_uid,
170227
fieldPath
171228
);
172229

173230
if (fieldSchema) {
174-
// after field schema is available re-add disabled overlay
175231
const { isDisabled } = isFieldDisabled(fieldSchema, eventDetails);
176232
if (isDisabled) {
177233
addOverlay({
@@ -183,37 +239,39 @@ async function handleBuilderInteraction(
183239
}
184240
}
185241

186-
// This is most probably redundant code, as the handleIndividualFields function
187-
// takes care of this
188-
// TODO: Remove this
189-
// if (
190-
// fieldSchema.data_type === "block" ||
191-
// fieldSchema.multiple ||
192-
// (fieldSchema.data_type === "reference" &&
193-
// // @ts-ignore
194-
// fieldSchema.field_metadata.ref_multiple)
195-
// ) {
196-
// handleAddButtonsForMultiple(eventDetails, {
197-
// editableElement: editableElement,
198-
// visualBuilderContainer: params.visualBuilderContainer,
199-
// resizeObserver: params.resizeObserver,
200-
// });
201-
// } else {
202-
// removeAddInstanceButtons({
203-
// eventTarget: params.event.target,
204-
// visualBuilderContainer: params.visualBuilderContainer,
205-
// overlayWrapper: params.overlayWrapper,
206-
// });
207-
// }
208242
visualBuilderPostMessage?.send(VisualBuilderPostMessageEvents.FOCUS_FIELD, {
209243
DOMEditStack: getDOMEditStack(editableElement),
210244
});
211245

212246
await handleIndividualFields(eventDetails, {
213-
visualBuilderContainer: params.visualBuilderContainer,
247+
visualBuilderContainer: params.visualBuilderContainer!,
214248
resizeObserver: params.resizeObserver,
215-
lastEditedField: previousSelectedElement,
249+
lastEditedField:
250+
VisualBuilder.VisualBuilderGlobalState.value
251+
.previousSelectedEditableDOM,
216252
});
217253
}
254+
function observeEditableElementChanges(
255+
params: HandleBuilderInteractionParams,
256+
editableElement: Element
257+
) {
258+
const focusElementObserver = new MutationObserver((mutations) => {
259+
mutations.forEach((mutation) => {
260+
if (
261+
mutation.type === "attributes" &&
262+
mutation.attributeName === "data-cslp"
263+
) {
264+
focusElementObserver?.disconnect();
265+
VisualBuilder.VisualBuilderGlobalState.value.focusElementObserver =
266+
null;
267+
handleBuilderInteraction({ ...params, reEvaluate: true });
268+
}
269+
});
270+
});
271+
272+
VisualBuilder.VisualBuilderGlobalState.value.focusElementObserver =
273+
focusElementObserver;
274+
focusElementObserver.observe(editableElement, { attributes: true });
275+
}
218276

219277
export default handleBuilderInteraction;

0 commit comments

Comments
 (0)