From bab5a47953979af9cc5acc4fc05cbe6623caeb55 Mon Sep 17 00:00:00 2001 From: SahilCs15 Date: Sun, 29 Jun 2025 21:08:47 +0530 Subject: [PATCH 01/13] chore: fieldlocation fetch completed --- src/index.ts | 1 + .../components/FieldLocationAppList.tsx | 101 ++++++++++++++ src/visualBuilder/components/FieldToolbar.tsx | 131 +++++++++++++++--- .../components/icons/EmptyAppIcon.tsx | 40 ++++++ src/visualBuilder/components/icons/index.tsx | 33 +++-- .../utils/types/postMessage.types.ts | 2 + src/visualBuilder/visualBuilder.style.ts | 8 ++ 7 files changed, 282 insertions(+), 34 deletions(-) create mode 100644 src/visualBuilder/components/FieldLocationAppList.tsx create mode 100644 src/visualBuilder/components/icons/EmptyAppIcon.tsx diff --git a/src/index.ts b/src/index.ts index 45074abc..a4a5b462 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,6 +5,7 @@ import LightLivePreviewHoC from "./light-sdk"; export type IStackSdk = ExternalStackSdkType; +console.log('initialised') const ContentstackLivePreview = typeof process !== "undefined" && diff --git a/src/visualBuilder/components/FieldLocationAppList.tsx b/src/visualBuilder/components/FieldLocationAppList.tsx new file mode 100644 index 00000000..69463865 --- /dev/null +++ b/src/visualBuilder/components/FieldLocationAppList.tsx @@ -0,0 +1,101 @@ +import React from "react"; +import { EmptyAppIcon } from "./icons/EmptyAppIcon"; + +interface App { + uid: string; + title: string; + name: string; + icon?: string; + visible: boolean; +} + +interface FieldLocationAppListProps { + apps: App[]; +} + +export const FieldLocationAppList: React.FC = ({ apps }) => { + if (!apps || apps.length === 0) { + return null; + } + + return ( +
+ {apps.map((app) => ( +
{ + console.log("App clicked:", app); + }} + > +
+ {app.icon ? ( + {app.title} + ) : ( + + )} +
+
+
+ {app.title} +
+
+ {app.name} +
+
+ +
+ ))} +
+ ); +}; \ No newline at end of file diff --git a/src/visualBuilder/components/FieldToolbar.tsx b/src/visualBuilder/components/FieldToolbar.tsx index c2ab9088..64ea688f 100644 --- a/src/visualBuilder/components/FieldToolbar.tsx +++ b/src/visualBuilder/components/FieldToolbar.tsx @@ -19,12 +19,13 @@ import { MoveLeftIcon, MoveRightIcon, ReplaceAssetIcon, + MoreIcon, } from "./icons"; import { fieldIcons } from "./icons/fields"; import classNames from "classnames"; import { visualBuilderStyles } from "../visualBuilder.style"; import CommentIcon from "./CommentIcon"; -import React, { useEffect, useState } from "preact/compat"; +import React, { useEffect, useState, useRef } from "preact/compat"; import { FieldSchemaMap } from "../utils/fieldSchemaMap"; import { isFieldDisabled } from "../utils/isFieldDisabled"; import { IReferenceContentTypeSchema } from "../../cms/types/contentTypeSchema.types"; @@ -40,6 +41,9 @@ import { } from "./FieldRevert/FieldRevertComponent"; import { LoadingIcon } from "./icons/loading"; import { EntryPermissions } from "../utils/getEntryPermissions"; +import { EmptyAppIcon } from "./icons/EmptyAppIcon"; +import { FieldLocationAppList } from "./FieldLocationAppList.tsx"; + export type FieldDetails = Pick< VisualBuilderCslpEventDetails, @@ -56,7 +60,6 @@ interface MultipleFieldToolbarProps { } function handleReplaceAsset(fieldMetadata: CslpData) { - // TODO avoid sending whole fieldMetadata visualBuilderPostMessage?.send( VisualBuilderPostMessageEvents.OPEN_ASSET_MODAL, { @@ -115,6 +118,9 @@ function FieldToolbarComponent( } = props; const { fieldMetadata, editableElement: targetElement } = eventDetails; const [isFormLoading, setIsFormLoading] = useState(false); + const [fieldLocationData, setFieldLocationData] = useState(null); + const [displayAllApps, setDisplayAllApps] = useState(false); + const moreButtonRef = useRef(null); const parentPath = fieldMetadata?.multipleFieldMetadata?.parentDetails?.parentCslpValue || @@ -158,13 +164,6 @@ function FieldToolbarComponent( isMultiple = (fieldSchema as IReferenceContentTypeSchema) .field_metadata.ref_multiple; - // field is multiple but an instance is not selected - // instead the whole field (all instances) is selected. - // Currently, when whole featured_blogs is selected in canvas, - // the fieldPathWithIndex and instance.fieldPathWithIndex are the same - // cannot rely on -1 index, as the non-negative index then refers to the index of - // the featured_blogs block in page_components - // It is not needed except taxanomy. isWholeMultipleField = isMultiple && (fieldMetadata.fieldPathWithIndex === @@ -173,18 +172,15 @@ function FieldToolbarComponent( isReplaceAllowed = ALLOWED_REPLACE_FIELDS.includes(fieldType) && !isWholeMultipleField; - // if ( - // DEFAULT_MULTIPLE_FIELDS.includes(fieldType) && - // isWholeMultipleField && - // !isVariant - // ) { - // return null; - // } } const invertTooltipPosition = targetElement.getBoundingClientRect().top <= TOOLTIP_TOP_EDGE_BUFFER; + const handleMoreIconClick = () => { + setDisplayAllApps(!displayAllApps); + }; + const editButton = Icon ? ( ); - // TODO sibling count is incorrect for this purpose + + + + + const totalElementCount = targetElement?.parentNode?.childElementCount ?? 1; const indexOfElement = fieldMetadata?.multipleFieldMetadata?.index; - const disableMoveLeft = indexOfElement === 0; // first element - const disableMoveRight = indexOfElement === totalElementCount - 1; // last element + const disableMoveLeft = indexOfElement === 0; + const disableMoveRight = indexOfElement === totalElementCount - 1; useEffect(() => { async function fetchFieldSchema() { @@ -353,6 +351,25 @@ function FieldToolbarComponent( }; }, []); + useEffect(() => { + const event = visualBuilderPostMessage?.on( + VisualBuilderPostMessageEvents.FIELD_LOCATION_DATA, + (data: { data: any }) => { + console.log('fieldLocationData received:', data.data); + setFieldLocationData(data.data.fieldLocationData || data.data); + } + ); + return () => { + event?.unregister(); + }; + }, [fieldMetadata]); + + // Debug logging + useEffect(() => { + console.log('fieldLocationData state:', fieldLocationData); + console.log('apps from fieldLocationData:', fieldLocationData?.apps); + }, [fieldLocationData, fieldMetadata]); + const multipleFieldToolbarButtonClasses = classNames( "visual-builder__button visual-builder__button--secondary", visualBuilderStyles()["visual-builder__button"], @@ -365,6 +382,9 @@ function FieldToolbarComponent( } ); + + + return (
)} + +
+ {fieldLocationData?.apps && + fieldLocationData.apps.length > 0 && ( +
+ + + +
+ )} +
+ {displayAllApps && ( + + )} ); } diff --git a/src/visualBuilder/components/icons/EmptyAppIcon.tsx b/src/visualBuilder/components/icons/EmptyAppIcon.tsx new file mode 100644 index 00000000..f0ba2d2b --- /dev/null +++ b/src/visualBuilder/components/icons/EmptyAppIcon.tsx @@ -0,0 +1,40 @@ +export const EmptyAppIcon = ({ + id = "", + ...props +}: React.SVGProps & { id?: string }) => { + return ( + + + + + ); +}; +export const sumOfAscii = (str: string): number => + [...str].reduce((a, b) => a + b.charCodeAt(0), 0); + +export const getColorFromString = (str: string = ""): string => { + const colorList: string[] = [ + "#99D8CE", + "#6BC3FE", + "#5060C1", + "#835EC3", + "#B16DBD", + "#FF85BC", + "#FF7E83", + "#A2D959", + "#59BA5E", + ]; + return colorList[sumOfAscii(str) % colorList.length]; +}; diff --git a/src/visualBuilder/components/icons/index.tsx b/src/visualBuilder/components/icons/index.tsx index d209df86..bb80aead 100644 --- a/src/visualBuilder/components/icons/index.tsx +++ b/src/visualBuilder/components/icons/index.tsx @@ -285,27 +285,18 @@ export function AddCommentIcon(): JSX.Element { export function WarningOctagonIcon(): JSX.Element { return ( - - ); @@ -341,4 +332,20 @@ export function CaretRightIcon(): JSX.Element { ) +} +export function MoreIcon(): JSX.Element { + return ( + + + + + + ); } \ No newline at end of file diff --git a/src/visualBuilder/utils/types/postMessage.types.ts b/src/visualBuilder/utils/types/postMessage.types.ts index e24bd4a5..050868b0 100644 --- a/src/visualBuilder/utils/types/postMessage.types.ts +++ b/src/visualBuilder/utils/types/postMessage.types.ts @@ -13,6 +13,7 @@ export enum VisualBuilderPostMessageEvents { GET_FIELD_DISPLAY_NAMES = "get-field-display-names", MOUSE_CLICK = "mouse-click", FOCUS_FIELD = "focus-field", + GET_FIELD_LOCATION_DETAILS = "get-field-location-details", OPEN_FIELD_EDIT_MODAL = "open-field-edit-modal", DELETE_INSTANCE = "delete-instance", MOVE_INSTANCE = "move-instance", @@ -26,6 +27,7 @@ export enum VisualBuilderPostMessageEvents { COLLAB_RESOLVE_THREAD = "collab-resolve-thread", COLLAB_DELETE_THREAD = "collab-delete-thread", COLLAB_MISSING_THREADS = "collab-missing-threads", + FIELD_LOCATION_DATA = "field-location-data", // FROM visual builder GET_ALL_ENTRIES_IN_CURRENT_PAGE = "get-entries-in-current-page", diff --git a/src/visualBuilder/visualBuilder.style.ts b/src/visualBuilder/visualBuilder.style.ts index dc4ea46a..118ac5d4 100644 --- a/src/visualBuilder/visualBuilder.style.ts +++ b/src/visualBuilder/visualBuilder.style.ts @@ -739,6 +739,14 @@ export function visualBuilderStyles() { fill: #475161; } `, + "visual-builder__field-location-icons-container": css` + display: flex; + gap: 0.25rem; + align-items: center; + justify-content: center; + margin-left: 0.25rem; + + `, }; } From 0c490390011da7a4c7b8a4b64e4e53cdab0cc9a3 Mon Sep 17 00:00:00 2001 From: SahilCs15 Date: Mon, 30 Jun 2025 21:02:00 +0530 Subject: [PATCH 02/13] chore: rendered the fieldmodifier apps added an event listner to send the selected app --- .../components/FieldLocationAppList.tsx | 190 +++++++++++------- .../components/FieldLocationIcon.tsx | 98 +++++++++ src/visualBuilder/components/FieldToolbar.tsx | 106 +++------- src/visualBuilder/components/icons/index.tsx | 36 ++-- .../utils/types/postMessage.types.ts | 2 +- src/visualBuilder/visualBuilder.style.ts | 95 +++++++++ 6 files changed, 360 insertions(+), 167 deletions(-) create mode 100644 src/visualBuilder/components/FieldLocationIcon.tsx diff --git a/src/visualBuilder/components/FieldLocationAppList.tsx b/src/visualBuilder/components/FieldLocationAppList.tsx index 69463865..4723884a 100644 --- a/src/visualBuilder/components/FieldLocationAppList.tsx +++ b/src/visualBuilder/components/FieldLocationAppList.tsx @@ -1,101 +1,135 @@ -import React from "react"; +import React, { useState, useEffect, useRef } from "preact/compat"; import { EmptyAppIcon } from "./icons/EmptyAppIcon"; +import { VisualBuilderPostMessageEvents } from "../utils/types/postMessage.types"; +import visualBuilderPostMessage from "../utils/visualBuilderPostMessage"; +import { visualBuilderStyles } from "../visualBuilder.style"; +import classNames from "classnames"; interface App { - uid: string; - title: string; - name: string; + app_installation_uid: string; + app_uid: string; + contentTypeUid: string; + entryUid: string; + fieldDataType: string; + fieldDisplayName: string; + fieldPath: string; icon?: string; - visible: boolean; + locale: string; + manifest: { + uid: string; + name: string; + description: string; + icon: string; + visibility: string; + }; + title: string; + uid: string; } + interface FieldLocationAppListProps { apps: App[]; + position: "left" | "right"; } -export const FieldLocationAppList: React.FC = ({ apps }) => { - if (!apps || apps.length === 0) { - return null; +export const FieldLocationAppList = ({ apps, position }: FieldLocationAppListProps) => { + const [search, setSearch] = useState(""); + const [filteredApps, setFilteredApps] = useState(apps); + const appRefs = useRef<{ [key: string]: HTMLDivElement | null }>({}); + + useEffect(() => { + const filtered = apps.filter(app => + app.title.toLowerCase().includes(search.toLowerCase()) + ); + setFilteredApps(filtered); + }, [search, apps]); + + const handleAppClick = (app: App) => { + const appElement = appRefs.current[app.uid]; + const positionData = appElement?.getBoundingClientRect(); + visualBuilderPostMessage?.send(VisualBuilderPostMessageEvents.FIELD_LOCATION_SELECTED_APP, { + app: app, + position: positionData, + }); } return (
- {apps.map((app) => ( -
{ - console.log("App clicked:", app); - }} +
+ -
- {app.icon ? ( - {app.title} - ) : ( - - )} + + + + setSearch((e.target as HTMLInputElement).value) + } + placeholder="Search for Apps" + className={visualBuilderStyles()["visual-builder__field-location-app-list__search-input"]} + /> +
+
+ {filteredApps.length === 0 && ( +
+ + No matching results found! +
-
+ )} + {filteredApps + .filter((_, index) => index !== 0) + .map((app) => (
(appRefs.current[app.uid] = el)} + className={visualBuilderStyles()["visual-builder__field-location-app-list__item"]} + onClick={() => handleAppClick(app)} > - {app.title} +
+ {app.icon ? ( + {app.title} + ) : ( + + )} +
+ + {app.title} +
-
- {app.name} -
-
- -
- ))} + ))} +
); }; \ No newline at end of file diff --git a/src/visualBuilder/components/FieldLocationIcon.tsx b/src/visualBuilder/components/FieldLocationIcon.tsx new file mode 100644 index 00000000..c6a744d3 --- /dev/null +++ b/src/visualBuilder/components/FieldLocationIcon.tsx @@ -0,0 +1,98 @@ +import classNames from "classnames"; +import { visualBuilderStyles } from "../visualBuilder.style"; +import { EmptyAppIcon } from "./icons/EmptyAppIcon"; +import { MoreIcon } from "./icons"; +import React, { useRef } from "preact/compat"; +import { LoadingIcon } from "./icons/loading"; +import { VisualBuilderPostMessageEvents } from "../utils/types/postMessage.types"; +import visualBuilderPostMessage from "../utils/visualBuilderPostMessage"; + +export const FieldLocationIcon = ({ + fieldLocationData, + multipleFieldToolbarButtonClasses, + handleMoreIconClick, + moreButtonRef, + isLoading, + toolbarRef, +}: { + fieldLocationData: any; + multipleFieldToolbarButtonClasses: any; + handleMoreIconClick: () => void; + moreButtonRef: any; + isLoading: boolean; + toolbarRef: any; +}) => { + // if (isLoading) { + // return ( + //
+ // + //
+ // ); + // } + + const currentAppRef = useRef(null); + + if (!fieldLocationData?.apps || fieldLocationData.apps.length === 0) { + return null; + } + + return ( +
+
+ + + +
+ ); +}; diff --git a/src/visualBuilder/components/FieldToolbar.tsx b/src/visualBuilder/components/FieldToolbar.tsx index 64ea688f..b5e47051 100644 --- a/src/visualBuilder/components/FieldToolbar.tsx +++ b/src/visualBuilder/components/FieldToolbar.tsx @@ -42,7 +42,8 @@ import { import { LoadingIcon } from "./icons/loading"; import { EntryPermissions } from "../utils/getEntryPermissions"; import { EmptyAppIcon } from "./icons/EmptyAppIcon"; -import { FieldLocationAppList } from "./FieldLocationAppList.tsx"; +import { FieldLocationAppList } from "./FieldLocationAppList"; +import { FieldLocationIcon } from "./FieldLocationIcon"; export type FieldDetails = Pick< @@ -121,6 +122,9 @@ function FieldToolbarComponent( const [fieldLocationData, setFieldLocationData] = useState(null); const [displayAllApps, setDisplayAllApps] = useState(false); const moreButtonRef = useRef(null); + const toolbarRef = useRef(null); + const [isFieldLocationLoading, setIsFieldLocationLoading] = useState(true); + const [appListPosition, setAppListPosition] = useState<"left" | "right">("right"); const parentPath = fieldMetadata?.multipleFieldMetadata?.parentDetails?.parentCslpValue || @@ -141,6 +145,7 @@ function FieldToolbarComponent( let Icon = null; let fieldType = null; let isWholeMultipleField = false; + const APP_LIST_MIN_WIDTH = 230; let disableFieldActions = false; if (fieldSchema) { @@ -178,6 +183,25 @@ function FieldToolbarComponent( targetElement.getBoundingClientRect().top <= TOOLTIP_TOP_EDGE_BUFFER; const handleMoreIconClick = () => { + + + if(toolbarRef.current){ + const rect=toolbarRef.current.getBoundingClientRect(); + const spaceRight=window.innerWidth-rect.right; + const spaceLeft=rect.left; + let position=''; + + if(spaceRightAPP_LIST_MIN_WIDTH){ + position="right"; + }else { + position=spaceRight>spaceLeft ? "right" : "left"; + } + setAppListPosition(position as "left" | "right"); + + } + setDisplayAllApps(!displayAllApps); }; @@ -352,11 +376,12 @@ function FieldToolbarComponent( }, []); useEffect(() => { + setIsFieldLocationLoading(true); const event = visualBuilderPostMessage?.on( VisualBuilderPostMessageEvents.FIELD_LOCATION_DATA, (data: { data: any }) => { - console.log('fieldLocationData received:', data.data); - setFieldLocationData(data.data.fieldLocationData || data.data); + setFieldLocationData(data.data.fieldLocationData); + setIsFieldLocationLoading(false); } ); return () => { @@ -364,11 +389,7 @@ function FieldToolbarComponent( }; }, [fieldMetadata]); - // Debug logging - useEffect(() => { - console.log('fieldLocationData state:', fieldLocationData); - console.log('apps from fieldLocationData:', fieldLocationData?.apps); - }, [fieldLocationData, fieldMetadata]); + const multipleFieldToolbarButtonClasses = classNames( "visual-builder__button visual-builder__button--secondary", @@ -535,76 +556,13 @@ function FieldToolbarComponent( )} -
- {fieldLocationData?.apps && - fieldLocationData.apps.length > 0 && ( -
- - - -
- )} -
+ +
{displayAllApps && ( - + )} ); diff --git a/src/visualBuilder/components/icons/index.tsx b/src/visualBuilder/components/icons/index.tsx index bb80aead..1793d6fc 100644 --- a/src/visualBuilder/components/icons/index.tsx +++ b/src/visualBuilder/components/icons/index.tsx @@ -161,8 +161,8 @@ export function EditIcon(): JSX.Element { return ( + + ); @@ -336,16 +345,15 @@ export function CaretRightIcon(): JSX.Element { export function MoreIcon(): JSX.Element { return ( - - - + + + ); } \ No newline at end of file diff --git a/src/visualBuilder/utils/types/postMessage.types.ts b/src/visualBuilder/utils/types/postMessage.types.ts index 050868b0..1c1e7dc5 100644 --- a/src/visualBuilder/utils/types/postMessage.types.ts +++ b/src/visualBuilder/utils/types/postMessage.types.ts @@ -13,7 +13,6 @@ export enum VisualBuilderPostMessageEvents { GET_FIELD_DISPLAY_NAMES = "get-field-display-names", MOUSE_CLICK = "mouse-click", FOCUS_FIELD = "focus-field", - GET_FIELD_LOCATION_DETAILS = "get-field-location-details", OPEN_FIELD_EDIT_MODAL = "open-field-edit-modal", DELETE_INSTANCE = "delete-instance", MOVE_INSTANCE = "move-instance", @@ -28,6 +27,7 @@ export enum VisualBuilderPostMessageEvents { COLLAB_DELETE_THREAD = "collab-delete-thread", COLLAB_MISSING_THREADS = "collab-missing-threads", FIELD_LOCATION_DATA = "field-location-data", + FIELD_LOCATION_SELECTED_APP = "field-location-selected-app", // FROM visual builder GET_ALL_ENTRIES_IN_CURRENT_PAGE = "get-entries-in-current-page", diff --git a/src/visualBuilder/visualBuilder.style.ts b/src/visualBuilder/visualBuilder.style.ts index 118ac5d4..8b9a8673 100644 --- a/src/visualBuilder/visualBuilder.style.ts +++ b/src/visualBuilder/visualBuilder.style.ts @@ -727,6 +727,7 @@ export function visualBuilderStyles() { display: flex; flex-direction: column-reverse; z-index: 2147483647 !important; + position: relative; `, "visual-builder__variant-button": css` display: flex; @@ -747,6 +748,100 @@ export function visualBuilderStyles() { margin-left: 0.25rem; `, + "visual-builder__field-location-icons-container__divider": css` + height: 32px !important; + width: 1px; + border-radius: 2px; + background-color: #8a8f99; + `, + "visual-builder__field-location-icons-container__app-icon": css` + width: 16px; + height: 16px; + object-fit: cover; + `, + "visual-builder__field-location-app-list": css` + position: absolute; + top: 0; + background: #fff; + border: 1px solid #e0e0e0; + border-radius: 8px; + box-shadow: 0 2px 8px rgba(0,0,0,0.15); + z-index: 1000; + min-width: 230px; + max-height: 250px; + min-height: 250px; + overflow-y: auto; + display: flex; + flex-direction: column; + `, + "visual-builder__field-location-app-list--left": css` + right: 100%; + margin-right: 8px; + `, + "visual-builder__field-location-app-list--right": css` + left: 100%; + margin-left: 8px; + `, + "visual-builder__field-location-app-list__search-container": css` + display: flex; + align-items: center; + padding: 10px 16px 0px 16px; + border: none; + border-bottom: 1px solid #f0f0f0; + `, + "visual-builder__field-location-app-list__search-input": css` + width: 100%; + padding: 10px 12px; + font-size: 14px; + outline: none; + box-sizing: border-box; + border: none; + `, + "visual-builder__field-location-app-list__search-icon": css` + width: 14px; + height: 14px; + `, + "visual-builder__field-location-app-list__content": css` + flex: 1; + overflow-y: auto; + `, + "visual-builder__field-location-app-list__no-results": css` + display: flex; + align-items: center; + justify-content: center; + height: 100%; + width: 100%; + text-align: center; + `, + "visual-builder__field-location-app-list__no-results-text": css` + color: #373b40; + font-weight: 400; + `, + "visual-builder__field-location-app-list__item": css` + display: flex; + align-items: center; + padding: 10px 16px; + cursor: pointer; + font-size: 14px; + `, + "visual-builder__field-location-app-list__item-icon-container": css` + width: 24px; + height: 24px; + margin-right: 12px; + display: flex; + align-items: center; + justify-content: center; + `, + "visual-builder__field-location-app-list__item-icon": css` + width: 24px; + height: 24px; + border-radius: 50%; + object-fit: cover; + `, + "visual-builder__field-location-app-list__item-title": css` + color: #373b40; + font-weight: 400; + `, }; } From 6337a6de2c41684d6cc70b62f600a4036264577b Mon Sep 17 00:00:00 2001 From: SahilCs15 Date: Tue, 1 Jul 2025 15:19:56 +0530 Subject: [PATCH 03/13] chore: added test cases for the field location data --- src/index.ts | 2 - .../components/FieldLocationAppList.tsx | 178 ++++++++++----- .../components/FieldLocationIcon.tsx | 39 ++-- src/visualBuilder/components/FieldToolbar.tsx | 12 +- .../__test__/FieldLocationAppList.test.tsx | 216 ++++++++++++++++++ .../__test__/FieldLocationIcon.test.tsx | 41 ++++ src/visualBuilder/components/icons/index.tsx | 4 +- 7 files changed, 399 insertions(+), 93 deletions(-) create mode 100644 src/visualBuilder/components/__test__/FieldLocationAppList.test.tsx create mode 100644 src/visualBuilder/components/__test__/FieldLocationIcon.test.tsx diff --git a/src/index.ts b/src/index.ts index a4a5b462..11164ffd 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,8 +5,6 @@ import LightLivePreviewHoC from "./light-sdk"; export type IStackSdk = ExternalStackSdkType; -console.log('initialised') - const ContentstackLivePreview = typeof process !== "undefined" && (process?.env?.PURGE_PREVIEW_SDK === "true" || diff --git a/src/visualBuilder/components/FieldLocationAppList.tsx b/src/visualBuilder/components/FieldLocationAppList.tsx index 4723884a..b5bdba63 100644 --- a/src/visualBuilder/components/FieldLocationAppList.tsx +++ b/src/visualBuilder/components/FieldLocationAppList.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect, useRef } from "preact/compat"; +import React, { useState, useEffect, useMemo } from "preact/compat"; import { EmptyAppIcon } from "./icons/EmptyAppIcon"; import { VisualBuilderPostMessageEvents } from "../utils/types/postMessage.types"; import visualBuilderPostMessage from "../utils/visualBuilderPostMessage"; @@ -26,44 +26,70 @@ interface App { uid: string; } - interface FieldLocationAppListProps { apps: App[]; position: "left" | "right"; + toolbarRef: React.RefObject; } -export const FieldLocationAppList = ({ apps, position }: FieldLocationAppListProps) => { +const normalize = (text: string) => + text + .toLowerCase() + .replace(/[^a-z0-9 ]/gi, "") + .trim(); + +export const FieldLocationAppList = ({ + apps, + position, + toolbarRef, +}: FieldLocationAppListProps) => { + const remainingApps = apps.filter((app, index) => index !== 0); const [search, setSearch] = useState(""); - const [filteredApps, setFilteredApps] = useState(apps); - const appRefs = useRef<{ [key: string]: HTMLDivElement | null }>({}); - useEffect(() => { - const filtered = apps.filter(app => - app.title.toLowerCase().includes(search.toLowerCase()) - ); - setFilteredApps(filtered); - }, [search, apps]); + const filteredApps = useMemo(() => { + if (!search.trim()) return remainingApps; - const handleAppClick = (app: App) => { - const appElement = appRefs.current[app.uid]; - const positionData = appElement?.getBoundingClientRect(); - visualBuilderPostMessage?.send(VisualBuilderPostMessageEvents.FIELD_LOCATION_SELECTED_APP, { - app: app, - position: positionData, + const normalizedSearch = normalize(search); + return remainingApps.filter((app) => { + return ( + normalize(app.title).includes(normalizedSearch) + ); }); - } + }, [search, remainingApps]); + + const handleAppClick = (app: App) => { + visualBuilderPostMessage?.send( + VisualBuilderPostMessageEvents.FIELD_LOCATION_SELECTED_APP, + { + app: app, + position: toolbarRef.current?.getBoundingClientRect(), + } + ); + }; return (
-
+
-
+ +
{filteredApps.length === 0 && ( -
- +
+ No matching results found!
)} - {filteredApps - .filter((_, index) => index !== 0) - .map((app) => ( + {filteredApps.map((app) => ( +
handleAppClick(app)} + >
(appRefs.current[app.uid] = el)} - className={visualBuilderStyles()["visual-builder__field-location-app-list__item"]} - onClick={() => handleAppClick(app)} + className={ + visualBuilderStyles()[ + "visual-builder__field-location-app-list__item-icon-container" + ] + } > -
- {app.icon ? ( - {app.title} - ) : ( - - )} -
- - {app.title} - + {app.icon ? ( + {app.title} + ) : ( + + )}
- ))} + + {app.title} + +
+ ))}
); -}; \ No newline at end of file +}; diff --git a/src/visualBuilder/components/FieldLocationIcon.tsx b/src/visualBuilder/components/FieldLocationIcon.tsx index c6a744d3..9f0081cf 100644 --- a/src/visualBuilder/components/FieldLocationIcon.tsx +++ b/src/visualBuilder/components/FieldLocationIcon.tsx @@ -12,38 +12,29 @@ export const FieldLocationIcon = ({ multipleFieldToolbarButtonClasses, handleMoreIconClick, moreButtonRef, - isLoading, toolbarRef, }: { fieldLocationData: any; multipleFieldToolbarButtonClasses: any; handleMoreIconClick: () => void; moreButtonRef: any; - isLoading: boolean; toolbarRef: any; }) => { - // if (isLoading) { - // return ( - //
- // - //
- // ); - // } - const currentAppRef = useRef(null); + if (!fieldLocationData?.apps || fieldLocationData.apps.length === 0) { return null; } + const handleAppClick = (app: any) => { + if(!toolbarRef.current) return + visualBuilderPostMessage?.send(VisualBuilderPostMessageEvents.FIELD_LOCATION_SELECTED_APP, { + app, + position: toolbarRef.current?.getBoundingClientRect(), + }); + }; + return (
{ - e.preventDefault(); - e.stopPropagation(); - const positionData = currentAppRef.current?.getBoundingClientRect(); - visualBuilderPostMessage?.send(VisualBuilderPostMessageEvents.FIELD_LOCATION_SELECTED_APP, { - app: fieldLocationData.apps[0], - position: positionData, - }); + e.preventDefault(); + e.stopPropagation(); + handleAppClick(fieldLocationData.apps[0]); }} + data-testid="field-location-icon" > {fieldLocationData.apps[0].icon ? ( (null); const toolbarRef = useRef(null); - const [isFieldLocationLoading, setIsFieldLocationLoading] = useState(true); const [appListPosition, setAppListPosition] = useState<"left" | "right">("right"); const parentPath = @@ -370,24 +369,23 @@ function FieldToolbarComponent( } } ); + return () => { event?.unregister(); }; }, []); useEffect(() => { - setIsFieldLocationLoading(true); const event = visualBuilderPostMessage?.on( VisualBuilderPostMessageEvents.FIELD_LOCATION_DATA, (data: { data: any }) => { - setFieldLocationData(data.data.fieldLocationData); - setIsFieldLocationLoading(false); + setFieldLocationData(data.data.fieldLocationData ); } ); return () => { event?.unregister(); }; - }, [fieldMetadata]); + }, []); @@ -556,13 +554,13 @@ function FieldToolbarComponent( )} - +
{displayAllApps && ( - + )}
); diff --git a/src/visualBuilder/components/__test__/FieldLocationAppList.test.tsx b/src/visualBuilder/components/__test__/FieldLocationAppList.test.tsx new file mode 100644 index 00000000..c2da1ef0 --- /dev/null +++ b/src/visualBuilder/components/__test__/FieldLocationAppList.test.tsx @@ -0,0 +1,216 @@ +import React from "preact/compat"; +import { render, fireEvent, screen } from "@testing-library/preact"; +import { FieldLocationAppList } from "../FieldLocationAppList"; +import visualBuilderPostMessage from "../../utils/visualBuilderPostMessage"; +import { vi } from "vitest"; + +vi.mock("../../utils/visualBuilderPostMessage", () => ({ + default: { + send: vi.fn(), + }, +})); + +vi.mock("../../visualBuilder.style", () => ({ + visualBuilderStyles: () => ({ + "visual-builder__field-location-app-list": "visual-builder__field-location-app-list", + "visual-builder__field-location-app-list--left": "visual-builder__field-location-app-list--left", + "visual-builder__field-location-app-list--right": "visual-builder__field-location-app-list--right", + "visual-builder__field-location-app-list__search-container": "visual-builder__field-location-app-list__search-container", + "visual-builder__field-location-app-list__search-input": "visual-builder__field-location-app-list__search-input", + "visual-builder__field-location-app-list__search-icon": "visual-builder__field-location-app-list__search-icon", + "visual-builder__field-location-app-list__content": "visual-builder__field-location-app-list__content", + "visual-builder__field-location-app-list__no-results": "visual-builder__field-location-app-list__no-results", + "visual-builder__field-location-app-list__no-results-text": "visual-builder__field-location-app-list__no-results-text", + "visual-builder__field-location-app-list__item": "visual-builder__field-location-app-list__item", + "visual-builder__field-location-app-list__item-icon-container": "visual-builder__field-location-app-list__item-icon-container", + "visual-builder__field-location-app-list__item-icon": "visual-builder__field-location-app-list__item-icon", + "visual-builder__field-location-app-list__item-title": "visual-builder__field-location-app-list__item-title", + }), +})); + +vi.mock("classnames", () => ({ + default: (...args: any[]) => args.filter(Boolean).join(" "), +})); + +vi.mock("../icons/EmptyAppIcon", () => ({ + EmptyAppIcon: ({ id }: { id: string }) =>
Empty Icon
, +})); + +const mockApps = [ + { + uid: "1", + title: "First App", + app_installation_uid: "install_1", + app_uid: "app_1", + contentTypeUid: "content_1", + entryUid: "entry_1", + fieldDataType: "text", + fieldDisplayName: "Title", + fieldPath: "title", + locale: "en-us", + manifest: { + uid: "manifest_1", + name: "First App", + description: "First app description", + icon: "icon1.png", + visibility: "public", + }, + }, + { + uid: "2", + title: "Second App", + app_installation_uid: "install_2", + app_uid: "app_2", + contentTypeUid: "content_2", + entryUid: "entry_2", + fieldDataType: "text", + fieldDisplayName: "Description", + fieldPath: "description", + locale: "en-us", + manifest: { + uid: "manifest_2", + name: "Second App", + description: "Second app description", + icon: "icon2.png", + visibility: "public", + }, + }, + { + uid: "3", + title: "Third App", + app_installation_uid: "install_3", + app_uid: "app_3", + contentTypeUid: "content_3", + entryUid: "entry_3", + fieldDataType: "text", + fieldDisplayName: "Content", + fieldPath: "content", + locale: "en-us", + manifest: { + uid: "manifest_3", + name: "Third App", + description: "Third app description", + icon: "icon3.png", + visibility: "public", + }, + }, +]; + +describe("FieldLocationAppList", () => { + const mockToolbarRef = { + current: { + getBoundingClientRect: () => ({ top: 0, left: 0, right: 100, bottom: 50, width: 100, height: 50, x: 0, y: 0 }), + } as HTMLDivElement, + }; + + beforeEach(() => { + vi.clearAllMocks(); + }); + + it("should render the app list with search input", () => { + render(); + + expect(screen.getByPlaceholderText("Search for Apps")).toBeInTheDocument(); + expect(screen.getByText("Second App")).toBeInTheDocument(); + expect(screen.getByText("Third App")).toBeInTheDocument(); + }); + + it("should not render the first app (index 0)", () => { + render(); + + expect(screen.queryByText("First App")).not.toBeInTheDocument(); + expect(screen.getByText("Second App")).toBeInTheDocument(); + expect(screen.getByText("Third App")).toBeInTheDocument(); + }); + + it("should filter apps when searching", () => { + render(); + + const searchInput = screen.getByPlaceholderText("Search for Apps"); + fireEvent.input(searchInput, { target: { value: "Second" } }); + + expect(screen.getByText("Second App")).toBeInTheDocument(); + expect(screen.queryByText("Third App")).not.toBeInTheDocument(); + }); + + it("should show no results message when search has no matches", () => { + render(); + + const searchInput = screen.getByPlaceholderText("Search for Apps"); + fireEvent.input(searchInput, { target: { value: "NonExistent" } }); + + expect(screen.getByText("No matching results found!")).toBeInTheDocument(); + expect(screen.queryByText("Second App")).not.toBeInTheDocument(); + expect(screen.queryByText("Third App")).not.toBeInTheDocument(); + }); + + it("should send event when app is clicked", () => { + render(); + + const secondApp = screen.getByText("Second App"); + fireEvent.click(secondApp); + + expect(visualBuilderPostMessage?.send).toHaveBeenCalledWith("field-location-selected-app", { + app: mockApps[1], + position: { top: 0, left: 0, right: 100, bottom: 50, width: 100, height: 50, x: 0, y: 0 }, + }); + }); + + + + + + it("should apply correct CSS classes for right position", () => { + const { container } = render(); + + const appList = container.firstChild as HTMLElement; + expect(appList).toHaveClass("visual-builder__field-location-app-list"); + }); + + it("should apply correct CSS classes for left position", () => { + const { container } = render(); + + const appList = container.firstChild as HTMLElement; + expect(appList).toHaveClass("visual-builder__field-location-app-list"); + }); + + it("should handle empty apps array", () => { + render(); + + expect(screen.getByText("No matching results found!")).toBeInTheDocument(); + }); + + it("should handle single app (which gets filtered out)", () => { + const singleApp = [mockApps[0]]; + render(); + + expect(screen.getByText("No matching results found!")).toBeInTheDocument(); + expect(screen.queryByText("First App")).not.toBeInTheDocument(); + }); + + it("should handle case-insensitive search", () => { + render(); + + const searchInput = screen.getByPlaceholderText("Search for Apps"); + fireEvent.input(searchInput, { target: { value: "second" } }); + + expect(screen.getByText("Second App")).toBeInTheDocument(); + expect(screen.queryByText("Third App")).not.toBeInTheDocument(); + }); + + it("should clear search results when search input is cleared", () => { + render(); + + const searchInput = screen.getByPlaceholderText("Search for Apps"); + + fireEvent.input(searchInput, { target: { value: "Second" } }); + expect(screen.getByText("Second App")).toBeInTheDocument(); + expect(screen.queryByText("Third App")).not.toBeInTheDocument(); + + fireEvent.input(searchInput, { target: { value: "" } }); + expect(screen.getByText("Second App")).toBeInTheDocument(); + expect(screen.getByText("Third App")).toBeInTheDocument(); + }); + + +}); diff --git a/src/visualBuilder/components/__test__/FieldLocationIcon.test.tsx b/src/visualBuilder/components/__test__/FieldLocationIcon.test.tsx new file mode 100644 index 00000000..b9fa0314 --- /dev/null +++ b/src/visualBuilder/components/__test__/FieldLocationIcon.test.tsx @@ -0,0 +1,41 @@ +import React from "preact/compat"; +import { render, fireEvent } from "@testing-library/preact"; +import { FieldLocationIcon } from "../FieldLocationIcon"; +import visualBuilderPostMessage from "../../utils/visualBuilderPostMessage"; +import { vi } from "vitest"; + + + +describe("FieldLocationIcon", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it("should render fieldlocation icon when we have data in fieldLocationData", () => { + const { getByTestId } = render( + {}} + moreButtonRef={null} + toolbarRef={null} + /> + ); + expect(getByTestId("field-location-icon")).toBeInTheDocument(); + }); + + it("should not render fieldlocation icon when we don't have data in fieldLocationData", () => { + const { queryByTestId } = render( + {}} + moreButtonRef={null} + toolbarRef={null} + /> + ); + expect(queryByTestId("field-location-icon")).not.toBeInTheDocument(); + }); + + +}); \ No newline at end of file diff --git a/src/visualBuilder/components/icons/index.tsx b/src/visualBuilder/components/icons/index.tsx index 1793d6fc..9e4901d1 100644 --- a/src/visualBuilder/components/icons/index.tsx +++ b/src/visualBuilder/components/icons/index.tsx @@ -161,8 +161,8 @@ export function EditIcon(): JSX.Element { return ( Date: Tue, 1 Jul 2025 17:35:11 +0530 Subject: [PATCH 04/13] fix: undo unneccsary changes --- src/index.ts | 1 + src/visualBuilder/components/FieldToolbar.tsx | 83 ++++++++++++------- 2 files changed, 52 insertions(+), 32 deletions(-) diff --git a/src/index.ts b/src/index.ts index 11164ffd..45074abc 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,6 +5,7 @@ import LightLivePreviewHoC from "./light-sdk"; export type IStackSdk = ExternalStackSdkType; + const ContentstackLivePreview = typeof process !== "undefined" && (process?.env?.PURGE_PREVIEW_SDK === "true" || diff --git a/src/visualBuilder/components/FieldToolbar.tsx b/src/visualBuilder/components/FieldToolbar.tsx index 13cddad0..1ce33de6 100644 --- a/src/visualBuilder/components/FieldToolbar.tsx +++ b/src/visualBuilder/components/FieldToolbar.tsx @@ -61,6 +61,7 @@ interface MultipleFieldToolbarProps { } function handleReplaceAsset(fieldMetadata: CslpData) { + // TODO avoid sending whole fieldMetadata visualBuilderPostMessage?.send( VisualBuilderPostMessageEvents.OPEN_ASSET_MODAL, { @@ -123,7 +124,9 @@ function FieldToolbarComponent( const [displayAllApps, setDisplayAllApps] = useState(false); const moreButtonRef = useRef(null); const toolbarRef = useRef(null); - const [appListPosition, setAppListPosition] = useState<"left" | "right">("right"); + const [appListPosition, setAppListPosition] = useState<"left" | "right">( + "right" + ); const parentPath = fieldMetadata?.multipleFieldMetadata?.parentDetails?.parentCslpValue || @@ -168,6 +171,13 @@ function FieldToolbarComponent( isMultiple = (fieldSchema as IReferenceContentTypeSchema) .field_metadata.ref_multiple; + // field is multiple but an instance is not selected + // instead the whole field (all instances) is selected. + // Currently, when whole featured_blogs is selected in canvas, + // the fieldPathWithIndex and instance.fieldPathWithIndex are the same + // cannot rely on -1 index, as the non-negative index then refers to the index of + // the featured_blogs block in page_components + // It is not needed except taxanomy. isWholeMultipleField = isMultiple && (fieldMetadata.fieldPathWithIndex === @@ -176,29 +186,33 @@ function FieldToolbarComponent( isReplaceAllowed = ALLOWED_REPLACE_FIELDS.includes(fieldType) && !isWholeMultipleField; + // if ( + // DEFAULT_MULTIPLE_FIELDS.includes(fieldType) && + // isWholeMultipleField && + // !isVariant + // ) { + // return null; + // } } const invertTooltipPosition = targetElement.getBoundingClientRect().top <= TOOLTIP_TOP_EDGE_BUFFER; const handleMoreIconClick = () => { - - - if(toolbarRef.current){ - const rect=toolbarRef.current.getBoundingClientRect(); - const spaceRight=window.innerWidth-rect.right; - const spaceLeft=rect.left; - let position=''; - - if(spaceRightAPP_LIST_MIN_WIDTH){ - position="right"; - }else { - position=spaceRight>spaceLeft ? "right" : "left"; + if (toolbarRef.current) { + const rect = toolbarRef.current.getBoundingClientRect(); + const spaceRight = window.innerWidth - rect.right; + const spaceLeft = rect.left; + let position = ""; + + if (spaceRight < APP_LIST_MIN_WIDTH) { + position = "left"; + } else if (spaceRight > APP_LIST_MIN_WIDTH) { + position = "right"; + } else { + position = spaceRight > spaceLeft ? "right" : "left"; } setAppListPosition(position as "left" | "right"); - } setDisplayAllApps(!displayAllApps); @@ -221,6 +235,8 @@ function FieldToolbarComponent( )} data-tooltip={"Edit"} onClick={(e) => { + // TODO the listener for field path is attached to the common parent requiring + // propagation to be stopped, should ideally only attach onClick to fieldpath dropdown e.preventDefault(); e.stopPropagation(); handleEdit(fieldMetadata); @@ -331,16 +347,13 @@ function FieldToolbarComponent( ); - - - - + // TODO sibling count is incorrect for this purpose const totalElementCount = targetElement?.parentNode?.childElementCount ?? 1; const indexOfElement = fieldMetadata?.multipleFieldMetadata?.index; - const disableMoveLeft = indexOfElement === 0; - const disableMoveRight = indexOfElement === totalElementCount - 1; + const disableMoveLeft = indexOfElement === 0; // first element + const disableMoveRight = indexOfElement === totalElementCount - 1; // last element useEffect(() => { async function fetchFieldSchema() { @@ -377,9 +390,9 @@ function FieldToolbarComponent( useEffect(() => { const event = visualBuilderPostMessage?.on( - VisualBuilderPostMessageEvents.FIELD_LOCATION_DATA, + VisualBuilderPostMessageEvents.FIELD_LOCATION_DATA, (data: { data: any }) => { - setFieldLocationData(data.data.fieldLocationData ); + setFieldLocationData(data.data.fieldLocationData); } ); return () => { @@ -387,8 +400,6 @@ function FieldToolbarComponent( }; }, []); - - const multipleFieldToolbarButtonClasses = classNames( "visual-builder__button visual-builder__button--secondary", visualBuilderStyles()["visual-builder__button"], @@ -401,9 +412,6 @@ function FieldToolbarComponent( } ); - - - return (
)} - - +
{displayAllApps && ( - + )} ); From 3987e1c92daf17658c0b814e52f2ce8950334d5b Mon Sep 17 00:00:00 2001 From: SahilCs15 Date: Thu, 3 Jul 2025 18:07:42 +0530 Subject: [PATCH 05/13] fix: resolved the issues --- .../components/FieldLocationIcon.tsx | 22 +++-- .../components/__test__/fieldToolbar.test.tsx | 86 ++++++++++++++++++- src/visualBuilder/visualBuilder.style.ts | 4 +- 3 files changed, 101 insertions(+), 11 deletions(-) diff --git a/src/visualBuilder/components/FieldLocationIcon.tsx b/src/visualBuilder/components/FieldLocationIcon.tsx index 9f0081cf..c8719102 100644 --- a/src/visualBuilder/components/FieldLocationIcon.tsx +++ b/src/visualBuilder/components/FieldLocationIcon.tsx @@ -72,14 +72,20 @@ export const FieldLocationIcon = ({ )} - + + { + fieldLocationData.apps.length > 1 && ( + + ) + } ); }; diff --git a/src/visualBuilder/components/__test__/fieldToolbar.test.tsx b/src/visualBuilder/components/__test__/fieldToolbar.test.tsx index d31bf592..44d52537 100644 --- a/src/visualBuilder/components/__test__/fieldToolbar.test.tsx +++ b/src/visualBuilder/components/__test__/fieldToolbar.test.tsx @@ -12,6 +12,9 @@ import { asyncRender } from "../../../__test__/utils"; import { VisualBuilderCslpEventDetails } from "../../types/visualBuilder.types"; import { isFieldDisabled } from "../../utils/isFieldDisabled"; import React from "preact/compat"; +import visualBuilderPostMessage from "../../utils/visualBuilderPostMessage"; +import { FieldLocationIcon } from "../FieldLocationIcon"; +import { VisualBuilderPostMessageEvents } from "../../utils/types/postMessage.types"; vi.mock("../../utils/instanceHandlers", () => ({ handleMoveInstance: vi.fn(), @@ -182,7 +185,7 @@ describe("FieldToolbarComponent", () => { test("display variant icon instead of dropdown", async () => { mockEventDetails.fieldMetadata.variant = "variant"; const { findByTestId } = await asyncRender( - + ); const variantIcon = await findByTestId( @@ -301,4 +304,85 @@ describe("FieldToolbarComponent", () => { expect(replaceButton).toBeDisabled(); } }); + + describe("Field Location Dropdown", () => { + + + + test("FieldLocationIcon shows dropdown icon when multiple apps are available", async () => { + const mockFieldLocationData = { + apps: [ + { + uid: "app1", + title: "First App", + icon: "icon1.png", + app_installation_uid: "install1" + }, + { + uid: "app2", + title: "Second App", + icon: "icon2.png", + app_installation_uid: "install2" + } + ] + }; + + const mockHandleMoreIconClick = vi.fn(); + const mockButtonRef = { current: null }; + const mockToolbarRef = { current: null }; + + const { container } = await asyncRender( + + ); + + const appIcon = container.querySelector('[data-testid="field-location-icon"]'); + expect(appIcon).toBeInTheDocument(); + + const moreButton = container.querySelector('[data-testid="field-location-more-button"]'); + expect(moreButton).toBeInTheDocument(); + + fireEvent.click(moreButton!); + expect(mockHandleMoreIconClick).toHaveBeenCalledTimes(1); + }); + + test("FieldLocationIcon does not show dropdown icon when only one app is available", async () => { + const mockFieldLocationData = { + apps: [ + { + uid: "app1", + title: "First App", + icon: "icon1.png", + app_installation_uid: "install1" + } + ] + }; + + const mockHandleMoreIconClick = vi.fn(); + const mockButtonRef = { current: null }; + const mockToolbarRef = { current: null }; + + const { container } = await asyncRender( + + ); + + const appIcon = container.querySelector('[data-testid="field-location-icon"]'); + expect(appIcon).toBeInTheDocument(); + + const moreButton = container.querySelector('[data-testid="field-location-more-button"]'); + expect(moreButton).not.toBeInTheDocument(); + }); + + }); }); diff --git a/src/visualBuilder/visualBuilder.style.ts b/src/visualBuilder/visualBuilder.style.ts index 8b9a8673..c4a1f4f0 100644 --- a/src/visualBuilder/visualBuilder.style.ts +++ b/src/visualBuilder/visualBuilder.style.ts @@ -755,8 +755,8 @@ export function visualBuilderStyles() { background-color: #8a8f99; `, "visual-builder__field-location-icons-container__app-icon": css` - width: 16px; - height: 16px; + width: 24px; + height: 24px; object-fit: cover; `, "visual-builder__field-location-app-list": css` From bd5095a556930aeef55afc9de7f64d26f5e268d6 Mon Sep 17 00:00:00 2001 From: SahilCs15 Date: Wed, 9 Jul 2025 12:30:03 +0530 Subject: [PATCH 06/13] redner the app on dom --- .../components/FieldLocationAppList.tsx | 8 ++++++++ src/visualBuilder/components/FieldLocationIcon.tsx | 1 + src/visualBuilder/components/FieldToolbar.tsx | 3 +++ .../__test__/FieldLocationAppList.test.tsx | 14 +++++++++++++- .../__test__/multipleElementAddButton.test.ts | 12 ++++++++---- 5 files changed, 33 insertions(+), 5 deletions(-) diff --git a/src/visualBuilder/components/FieldLocationAppList.tsx b/src/visualBuilder/components/FieldLocationAppList.tsx index b5bdba63..c9630717 100644 --- a/src/visualBuilder/components/FieldLocationAppList.tsx +++ b/src/visualBuilder/components/FieldLocationAppList.tsx @@ -4,6 +4,7 @@ import { VisualBuilderPostMessageEvents } from "../utils/types/postMessage.types import visualBuilderPostMessage from "../utils/visualBuilderPostMessage"; import { visualBuilderStyles } from "../visualBuilder.style"; import classNames from "classnames"; +import { CslpData } from "../../utils/cslpdata"; interface App { app_installation_uid: string; @@ -30,6 +31,9 @@ interface FieldLocationAppListProps { apps: App[]; position: "left" | "right"; toolbarRef: React.RefObject; + domEditStack:CslpData[] + setDisplayAllApps: (displayAllApps: boolean) => void; + displayAllApps: boolean; } const normalize = (text: string) => @@ -42,6 +46,8 @@ export const FieldLocationAppList = ({ apps, position, toolbarRef, + domEditStack, + setDisplayAllApps, }: FieldLocationAppListProps) => { const remainingApps = apps.filter((app, index) => index !== 0); const [search, setSearch] = useState(""); @@ -63,8 +69,10 @@ export const FieldLocationAppList = ({ { app: app, position: toolbarRef.current?.getBoundingClientRect(), + DomEditStack:domEditStack } ); + setDisplayAllApps(false); }; return ( diff --git a/src/visualBuilder/components/FieldLocationIcon.tsx b/src/visualBuilder/components/FieldLocationIcon.tsx index c8719102..0c8b9fee 100644 --- a/src/visualBuilder/components/FieldLocationIcon.tsx +++ b/src/visualBuilder/components/FieldLocationIcon.tsx @@ -32,6 +32,7 @@ export const FieldLocationIcon = ({ visualBuilderPostMessage?.send(VisualBuilderPostMessageEvents.FIELD_LOCATION_SELECTED_APP, { app, position: toolbarRef.current?.getBoundingClientRect(), + DomEditStack:fieldLocationData.DomEditStack }); }; diff --git a/src/visualBuilder/components/FieldToolbar.tsx b/src/visualBuilder/components/FieldToolbar.tsx index 1ce33de6..b477320f 100644 --- a/src/visualBuilder/components/FieldToolbar.tsx +++ b/src/visualBuilder/components/FieldToolbar.tsx @@ -579,6 +579,9 @@ function FieldToolbarComponent( toolbarRef={toolbarRef} apps={fieldLocationData?.apps || ([] as any[])} position={appListPosition} + domEditStack={fieldLocationData.DomEditStack} + setDisplayAllApps={setDisplayAllApps} + displayAllApps={displayAllApps} /> )} diff --git a/src/visualBuilder/components/__test__/FieldLocationAppList.test.tsx b/src/visualBuilder/components/__test__/FieldLocationAppList.test.tsx index c2da1ef0..4c158351 100644 --- a/src/visualBuilder/components/__test__/FieldLocationAppList.test.tsx +++ b/src/visualBuilder/components/__test__/FieldLocationAppList.test.tsx @@ -145,7 +145,8 @@ describe("FieldLocationAppList", () => { }); it("should send event when app is clicked", () => { - render(); + const mockSetDisplayAllApps = vi.fn(); + render(); const secondApp = screen.getByText("Second App"); fireEvent.click(secondApp); @@ -153,9 +154,20 @@ describe("FieldLocationAppList", () => { expect(visualBuilderPostMessage?.send).toHaveBeenCalledWith("field-location-selected-app", { app: mockApps[1], position: { top: 0, left: 0, right: 100, bottom: 50, width: 100, height: 50, x: 0, y: 0 }, + DomEditStack: [] }); }); + it("should close the app list when an app is selected", () => { + const mockSetDisplayAllApps = vi.fn(); + render(); + + const secondApp = screen.getByText("Second App"); + fireEvent.click(secondApp); + + expect(mockSetDisplayAllApps).toHaveBeenCalledWith(false); + }); + diff --git a/src/visualBuilder/utils/__test__/multipleElementAddButton.test.ts b/src/visualBuilder/utils/__test__/multipleElementAddButton.test.ts index fd3e44ae..d4ec564a 100644 --- a/src/visualBuilder/utils/__test__/multipleElementAddButton.test.ts +++ b/src/visualBuilder/utils/__test__/multipleElementAddButton.test.ts @@ -92,7 +92,8 @@ describe("getChildrenDirection", () => { }); afterEach(() => { - document.getElementsByTagName("body")[0].innerHTML = ""; + vi.clearAllTimers(); + document.body.innerHTML = ''; vi.clearAllMocks(); }); @@ -232,7 +233,8 @@ describe("handleAddButtonsForMultiple", () => { }); afterEach(() => { - document.getElementsByTagName("body")[0].innerHTML = ""; + vi.clearAllTimers(); + document.body.innerHTML = ''; vi.clearAllMocks(); }); @@ -466,7 +468,8 @@ describe("handleAddButtonsForMultiple", () => { }); afterEach(() => { - document.getElementsByTagName("body")[0].innerHTML = ""; + vi.clearAllTimers(); + document.body.innerHTML = ''; vi.clearAllMocks(); }); @@ -590,7 +593,8 @@ describe("removeAddInstanceButtons", () => { }); afterEach(() => { - document.getElementsByTagName("body")[0].innerHTML = ""; + vi.clearAllTimers(); + document.body.innerHTML = ''; vi.clearAllMocks(); }); From 2cb9824dde552548a66d5a1ae79f1b98b089a535 Mon Sep 17 00:00:00 2001 From: SahilCs15 Date: Wed, 9 Jul 2025 15:58:43 +0530 Subject: [PATCH 07/13] fix: disable scroll when field modifer is active --- src/visualBuilder/index.ts | 16 ++++++++++++++++ .../utils/types/postMessage.types.ts | 3 ++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/visualBuilder/index.ts b/src/visualBuilder/index.ts index 6b6d005a..117f505f 100644 --- a/src/visualBuilder/index.ts +++ b/src/visualBuilder/index.ts @@ -371,6 +371,22 @@ export class VisualBuilder { VisualBuilderPostMessageEvents.SEND_VARIANT_AND_LOCALE ); + visualBuilderPostMessage?.on<{ + scroll: boolean + }>( + VisualBuilderPostMessageEvents.TOGGLE_SCROLL, + (event) => { + if (!event.data.scroll) { + document.body.style.overflow = 'hidden' + } else { + document.body.style.overflow = 'auto' + } + } + ); + + + + useHideFocusOverlayPostMessageEvent({ overlayWrapper: this.overlayWrapper, visualBuilderContainer: this.visualBuilderContainer, diff --git a/src/visualBuilder/utils/types/postMessage.types.ts b/src/visualBuilder/utils/types/postMessage.types.ts index 1c1e7dc5..ec960e3e 100644 --- a/src/visualBuilder/utils/types/postMessage.types.ts +++ b/src/visualBuilder/utils/types/postMessage.types.ts @@ -52,4 +52,5 @@ export enum VisualBuilderPostMessageEvents { COLLAB_THREADS_REMOVE = "collab-threads-remove", COLLAB_THREAD_REOPEN = "collab-thread-reopen", COLLAB_THREAD_HIGHLIGHT = "collab-thread-highlight", -} \ No newline at end of file + TOGGLE_SCROLL = "toggle-scroll", +} From a0803ad8aeab52fd298ef71a1dab754ae116aa27 Mon Sep 17 00:00:00 2001 From: SahilCs15 Date: Fri, 11 Jul 2025 16:15:04 +0530 Subject: [PATCH 08/13] fix:removed uncessary changes --- .../utils/__test__/multipleElementAddButton.test.ts | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/visualBuilder/utils/__test__/multipleElementAddButton.test.ts b/src/visualBuilder/utils/__test__/multipleElementAddButton.test.ts index d4ec564a..fd3e44ae 100644 --- a/src/visualBuilder/utils/__test__/multipleElementAddButton.test.ts +++ b/src/visualBuilder/utils/__test__/multipleElementAddButton.test.ts @@ -92,8 +92,7 @@ describe("getChildrenDirection", () => { }); afterEach(() => { - vi.clearAllTimers(); - document.body.innerHTML = ''; + document.getElementsByTagName("body")[0].innerHTML = ""; vi.clearAllMocks(); }); @@ -233,8 +232,7 @@ describe("handleAddButtonsForMultiple", () => { }); afterEach(() => { - vi.clearAllTimers(); - document.body.innerHTML = ''; + document.getElementsByTagName("body")[0].innerHTML = ""; vi.clearAllMocks(); }); @@ -468,8 +466,7 @@ describe("handleAddButtonsForMultiple", () => { }); afterEach(() => { - vi.clearAllTimers(); - document.body.innerHTML = ''; + document.getElementsByTagName("body")[0].innerHTML = ""; vi.clearAllMocks(); }); @@ -593,8 +590,7 @@ describe("removeAddInstanceButtons", () => { }); afterEach(() => { - vi.clearAllTimers(); - document.body.innerHTML = ''; + document.getElementsByTagName("body")[0].innerHTML = ""; vi.clearAllMocks(); }); From 27ec2f3442f1fdc1b327e77fda1b8cb1b71b1822 Mon Sep 17 00:00:00 2001 From: SahilCs15 Date: Mon, 14 Jul 2025 17:55:08 +0530 Subject: [PATCH 09/13] fix: addresed the requested cahnges --- src/visualBuilder/components/FieldLocationIcon.tsx | 5 ++++- src/visualBuilder/components/FieldToolbar.tsx | 7 ++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/visualBuilder/components/FieldLocationIcon.tsx b/src/visualBuilder/components/FieldLocationIcon.tsx index 0c8b9fee..e9b9cf8c 100644 --- a/src/visualBuilder/components/FieldLocationIcon.tsx +++ b/src/visualBuilder/components/FieldLocationIcon.tsx @@ -6,6 +6,7 @@ import React, { useRef } from "preact/compat"; import { LoadingIcon } from "./icons/loading"; import { VisualBuilderPostMessageEvents } from "../utils/types/postMessage.types"; import visualBuilderPostMessage from "../utils/visualBuilderPostMessage"; +import { CslpData } from "../../utils/cslpdata"; export const FieldLocationIcon = ({ fieldLocationData, @@ -13,12 +14,14 @@ export const FieldLocationIcon = ({ handleMoreIconClick, moreButtonRef, toolbarRef, + domEditStack }: { fieldLocationData: any; multipleFieldToolbarButtonClasses: any; handleMoreIconClick: () => void; moreButtonRef: any; toolbarRef: any; + domEditStack:CslpData[] }) => { @@ -32,7 +35,7 @@ export const FieldLocationIcon = ({ visualBuilderPostMessage?.send(VisualBuilderPostMessageEvents.FIELD_LOCATION_SELECTED_APP, { app, position: toolbarRef.current?.getBoundingClientRect(), - DomEditStack:fieldLocationData.DomEditStack + DomEditStack:domEditStack }); }; diff --git a/src/visualBuilder/components/FieldToolbar.tsx b/src/visualBuilder/components/FieldToolbar.tsx index b477320f..c542c83a 100644 --- a/src/visualBuilder/components/FieldToolbar.tsx +++ b/src/visualBuilder/components/FieldToolbar.tsx @@ -1,4 +1,5 @@ import { CslpData } from "../../cslp/types/cslp.types"; +import { CslpData as CslpDataUtil } from "../../utils/cslpdata"; import getChildrenDirection from "../utils/getChildrenDirection"; import { ALLOWED_MODAL_EDITABLE_FIELD, @@ -195,6 +196,9 @@ function FieldToolbarComponent( // } } + const domEditStack=getDOMEditStack(eventDetails.editableElement) as CslpDataUtil[] + + const invertTooltipPosition = targetElement.getBoundingClientRect().top <= TOOLTIP_TOP_EDGE_BUFFER; @@ -570,6 +574,7 @@ function FieldToolbarComponent( handleMoreIconClick={handleMoreIconClick} moreButtonRef={moreButtonRef} toolbarRef={toolbarRef} + domEditStack={domEditStack} /> @@ -579,7 +584,7 @@ function FieldToolbarComponent( toolbarRef={toolbarRef} apps={fieldLocationData?.apps || ([] as any[])} position={appListPosition} - domEditStack={fieldLocationData.DomEditStack} + domEditStack={domEditStack} setDisplayAllApps={setDisplayAllApps} displayAllApps={displayAllApps} /> From 17423d5e34094d91c0840a49dbee913be3b4bc37 Mon Sep 17 00:00:00 2001 From: SahilCs15 Date: Wed, 16 Jul 2025 12:07:59 +0530 Subject: [PATCH 10/13] fix: fixed conficts --- src/visualBuilder/components/icons/index.tsx | 31 ++++++++++--------- .../generators/generateToolbar.tsx | 1 + 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/src/visualBuilder/components/icons/index.tsx b/src/visualBuilder/components/icons/index.tsx index 9e4901d1..0b51a27d 100644 --- a/src/visualBuilder/components/icons/index.tsx +++ b/src/visualBuilder/components/icons/index.tsx @@ -311,6 +311,22 @@ export function WarningOctagonIcon(): JSX.Element { ); } +export function MoreIcon(): JSX.Element { + return ( + + + + + + ); +} + export function ContentTypeIcon(): JSX.Element { return (
- - - - - ); -} \ No newline at end of file diff --git a/src/visualBuilder/generators/generateToolbar.tsx b/src/visualBuilder/generators/generateToolbar.tsx index 725b2c98..b15aa768 100644 --- a/src/visualBuilder/generators/generateToolbar.tsx +++ b/src/visualBuilder/generators/generateToolbar.tsx @@ -178,6 +178,7 @@ export function removeFieldToolbar(toolbar: Element) { const toolbarEvents = [ VisualBuilderPostMessageEvents.DELETE_INSTANCE, VisualBuilderPostMessageEvents.UPDATE_DISCUSSION_ID, + VisualBuilderPostMessageEvents.FIELD_LOCATION_DATA ]; toolbarEvents.forEach((event) => { //@ts-expect-error - We are accessing private method here, but it is necessary to clean up the event listeners. From 53c6e77c2d49718c748895d3bdecb7676461fe8d Mon Sep 17 00:00:00 2001 From: SahilCs15 Date: Wed, 16 Jul 2025 14:28:13 +0530 Subject: [PATCH 11/13] fix: fixed the required changes --- .../components/FieldLocationIcon.tsx | 2 +- .../__test__/FieldLocationAppList.test.tsx | 64 +++++++++++---- .../__test__/FieldLocationIcon.test.tsx | 82 +++++++++++++++++++ .../components/__test__/fieldToolbar.test.tsx | 81 ------------------ 4 files changed, 129 insertions(+), 100 deletions(-) diff --git a/src/visualBuilder/components/FieldLocationIcon.tsx b/src/visualBuilder/components/FieldLocationIcon.tsx index e9b9cf8c..a9396de9 100644 --- a/src/visualBuilder/components/FieldLocationIcon.tsx +++ b/src/visualBuilder/components/FieldLocationIcon.tsx @@ -26,7 +26,7 @@ export const FieldLocationIcon = ({ - if (!fieldLocationData?.apps || fieldLocationData.apps.length === 0) { + if (!fieldLocationData?.apps || fieldLocationData?.apps?.length === 0) { return null; } diff --git a/src/visualBuilder/components/__test__/FieldLocationAppList.test.tsx b/src/visualBuilder/components/__test__/FieldLocationAppList.test.tsx index 4c158351..ca4873c6 100644 --- a/src/visualBuilder/components/__test__/FieldLocationAppList.test.tsx +++ b/src/visualBuilder/components/__test__/FieldLocationAppList.test.tsx @@ -29,7 +29,21 @@ vi.mock("../../visualBuilder.style", () => ({ })); vi.mock("classnames", () => ({ - default: (...args: any[]) => args.filter(Boolean).join(" "), + default: (...args: any[]) => { + const classes: string[] = []; + args.forEach(arg => { + if (typeof arg === 'string') { + classes.push(arg); + } else if (typeof arg === 'object' && arg !== null) { + Object.entries(arg).forEach(([className, condition]) => { + if (condition) { + classes.push(className); + } + }); + } + }); + return classes.join(' '); + }, })); vi.mock("../icons/EmptyAppIcon", () => ({ @@ -108,7 +122,7 @@ describe("FieldLocationAppList", () => { }); it("should render the app list with search input", () => { - render(); + render( {}} displayAllApps={true} />); expect(screen.getByPlaceholderText("Search for Apps")).toBeInTheDocument(); expect(screen.getByText("Second App")).toBeInTheDocument(); @@ -116,7 +130,7 @@ describe("FieldLocationAppList", () => { }); it("should not render the first app (index 0)", () => { - render(); + render( {}} displayAllApps={true} />); expect(screen.queryByText("First App")).not.toBeInTheDocument(); expect(screen.getByText("Second App")).toBeInTheDocument(); @@ -124,7 +138,7 @@ describe("FieldLocationAppList", () => { }); it("should filter apps when searching", () => { - render(); + render( {}} displayAllApps={true} />); const searchInput = screen.getByPlaceholderText("Search for Apps"); fireEvent.input(searchInput, { target: { value: "Second" } }); @@ -134,7 +148,7 @@ describe("FieldLocationAppList", () => { }); it("should show no results message when search has no matches", () => { - render(); + render( {}} displayAllApps={true} />); const searchInput = screen.getByPlaceholderText("Search for Apps"); fireEvent.input(searchInput, { target: { value: "NonExistent" } }); @@ -168,40 +182,54 @@ describe("FieldLocationAppList", () => { expect(mockSetDisplayAllApps).toHaveBeenCalledWith(false); }); - - it("should apply correct CSS classes for right position", () => { - const { container } = render(); - + const { container } = render( + {}} + displayAllApps={true} + /> + ); const appList = container.firstChild as HTMLElement; - expect(appList).toHaveClass("visual-builder__field-location-app-list"); + expect(appList).toHaveClass("visual-builder__field-location-app-list--right"); }); - it("should apply correct CSS classes for left position", () => { - const { container } = render(); - + it("should apply correct CSS classes and left position style for left position", () => { + const { container } = render( + {}} + displayAllApps={true} + /> + ); const appList = container.firstChild as HTMLElement; - expect(appList).toHaveClass("visual-builder__field-location-app-list"); + expect(appList).toHaveClass("visual-builder__field-location-app-list--left"); }); it("should handle empty apps array", () => { - render(); + render( {}} displayAllApps={true} />); expect(screen.getByText("No matching results found!")).toBeInTheDocument(); }); it("should handle single app (which gets filtered out)", () => { const singleApp = [mockApps[0]]; - render(); + render( {}} displayAllApps={true} />); expect(screen.getByText("No matching results found!")).toBeInTheDocument(); expect(screen.queryByText("First App")).not.toBeInTheDocument(); }); it("should handle case-insensitive search", () => { - render(); + render( {}} displayAllApps={true} />); const searchInput = screen.getByPlaceholderText("Search for Apps"); fireEvent.input(searchInput, { target: { value: "second" } }); @@ -211,7 +239,7 @@ describe("FieldLocationAppList", () => { }); it("should clear search results when search input is cleared", () => { - render(); + render( {}} displayAllApps={true} />); const searchInput = screen.getByPlaceholderText("Search for Apps"); diff --git a/src/visualBuilder/components/__test__/FieldLocationIcon.test.tsx b/src/visualBuilder/components/__test__/FieldLocationIcon.test.tsx index b9fa0314..a2311068 100644 --- a/src/visualBuilder/components/__test__/FieldLocationIcon.test.tsx +++ b/src/visualBuilder/components/__test__/FieldLocationIcon.test.tsx @@ -3,6 +3,7 @@ import { render, fireEvent } from "@testing-library/preact"; import { FieldLocationIcon } from "../FieldLocationIcon"; import visualBuilderPostMessage from "../../utils/visualBuilderPostMessage"; import { vi } from "vitest"; +import { asyncRender } from "../../../__test__/utils"; @@ -19,6 +20,7 @@ describe("FieldLocationIcon", () => { handleMoreIconClick={() => {}} moreButtonRef={null} toolbarRef={null} + /> ); expect(getByTestId("field-location-icon")).toBeInTheDocument(); @@ -37,5 +39,85 @@ describe("FieldLocationIcon", () => { expect(queryByTestId("field-location-icon")).not.toBeInTheDocument(); }); + describe("Field Location Dropdown", () => { + + + + test("FieldLocationIcon shows dropdown icon when multiple apps are available", async () => { + const mockFieldLocationData = { + apps: [ + { + uid: "app1", + title: "First App", + icon: "icon1.png", + app_installation_uid: "install1" + }, + { + uid: "app2", + title: "Second App", + icon: "icon2.png", + app_installation_uid: "install2" + } + ] + }; + + const mockHandleMoreIconClick = vi.fn(); + const mockButtonRef = { current: null }; + const mockToolbarRef = { current: null }; + + const { container } = await asyncRender( + + ); + + const appIcon = container.querySelector('[data-testid="field-location-icon"]'); + expect(appIcon).toBeInTheDocument(); + + const moreButton = container.querySelector('[data-testid="field-location-more-button"]'); + expect(moreButton).toBeInTheDocument(); + + fireEvent.click(moreButton!); + expect(mockHandleMoreIconClick).toHaveBeenCalledTimes(1); + }); + + test("FieldLocationIcon does not show dropdown icon when only one app is available", async () => { + const mockFieldLocationData = { + apps: [ + { + uid: "app1", + title: "First App", + icon: "icon1.png", + app_installation_uid: "install1" + } + ] + }; + + const mockHandleMoreIconClick = vi.fn(); + const mockButtonRef = { current: null }; + const mockToolbarRef = { current: null }; + + const { container } = await asyncRender( + + ); + + const appIcon = container.querySelector('[data-testid="field-location-icon"]'); + expect(appIcon).toBeInTheDocument(); + + const moreButton = container.querySelector('[data-testid="field-location-more-button"]'); + expect(moreButton).not.toBeInTheDocument(); + }); + + }); }); \ No newline at end of file diff --git a/src/visualBuilder/components/__test__/fieldToolbar.test.tsx b/src/visualBuilder/components/__test__/fieldToolbar.test.tsx index 44d52537..59e43282 100644 --- a/src/visualBuilder/components/__test__/fieldToolbar.test.tsx +++ b/src/visualBuilder/components/__test__/fieldToolbar.test.tsx @@ -304,85 +304,4 @@ describe("FieldToolbarComponent", () => { expect(replaceButton).toBeDisabled(); } }); - - describe("Field Location Dropdown", () => { - - - - test("FieldLocationIcon shows dropdown icon when multiple apps are available", async () => { - const mockFieldLocationData = { - apps: [ - { - uid: "app1", - title: "First App", - icon: "icon1.png", - app_installation_uid: "install1" - }, - { - uid: "app2", - title: "Second App", - icon: "icon2.png", - app_installation_uid: "install2" - } - ] - }; - - const mockHandleMoreIconClick = vi.fn(); - const mockButtonRef = { current: null }; - const mockToolbarRef = { current: null }; - - const { container } = await asyncRender( - - ); - - const appIcon = container.querySelector('[data-testid="field-location-icon"]'); - expect(appIcon).toBeInTheDocument(); - - const moreButton = container.querySelector('[data-testid="field-location-more-button"]'); - expect(moreButton).toBeInTheDocument(); - - fireEvent.click(moreButton!); - expect(mockHandleMoreIconClick).toHaveBeenCalledTimes(1); - }); - - test("FieldLocationIcon does not show dropdown icon when only one app is available", async () => { - const mockFieldLocationData = { - apps: [ - { - uid: "app1", - title: "First App", - icon: "icon1.png", - app_installation_uid: "install1" - } - ] - }; - - const mockHandleMoreIconClick = vi.fn(); - const mockButtonRef = { current: null }; - const mockToolbarRef = { current: null }; - - const { container } = await asyncRender( - - ); - - const appIcon = container.querySelector('[data-testid="field-location-icon"]'); - expect(appIcon).toBeInTheDocument(); - - const moreButton = container.querySelector('[data-testid="field-location-more-button"]'); - expect(moreButton).not.toBeInTheDocument(); - }); - - }); }); From 6837e62f73fbb6cc46adf6d4454157181d428043 Mon Sep 17 00:00:00 2001 From: SahilCs15 Date: Wed, 16 Jul 2025 14:37:33 +0530 Subject: [PATCH 12/13] fix: removed not requied changes --- .../__test__/FieldLocationAppList.test.tsx | 25 +------------------ 1 file changed, 1 insertion(+), 24 deletions(-) diff --git a/src/visualBuilder/components/__test__/FieldLocationAppList.test.tsx b/src/visualBuilder/components/__test__/FieldLocationAppList.test.tsx index ca4873c6..59f76e8a 100644 --- a/src/visualBuilder/components/__test__/FieldLocationAppList.test.tsx +++ b/src/visualBuilder/components/__test__/FieldLocationAppList.test.tsx @@ -28,24 +28,6 @@ vi.mock("../../visualBuilder.style", () => ({ }), })); -vi.mock("classnames", () => ({ - default: (...args: any[]) => { - const classes: string[] = []; - args.forEach(arg => { - if (typeof arg === 'string') { - classes.push(arg); - } else if (typeof arg === 'object' && arg !== null) { - Object.entries(arg).forEach(([className, condition]) => { - if (condition) { - classes.push(className); - } - }); - } - }); - return classes.join(' '); - }, -})); - vi.mock("../icons/EmptyAppIcon", () => ({ EmptyAppIcon: ({ id }: { id: string }) =>
Empty Icon
, })); @@ -214,12 +196,7 @@ describe("FieldLocationAppList", () => { expect(appList).toHaveClass("visual-builder__field-location-app-list--left"); }); - it("should handle empty apps array", () => { - render( {}} displayAllApps={true} />); - - expect(screen.getByText("No matching results found!")).toBeInTheDocument(); - }); - + it("should handle single app (which gets filtered out)", () => { const singleApp = [mockApps[0]]; render( {}} displayAllApps={true} />); From b7bba121cd3093916a0ca891bdc16c53b825c34a Mon Sep 17 00:00:00 2001 From: Sahil Chalke Date: Fri, 18 Jul 2025 18:35:48 +0530 Subject: [PATCH 13/13] fix: modified the way we were handling fieldlocation data fetch (#462) --- src/visualBuilder/components/FieldToolbar.tsx | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/visualBuilder/components/FieldToolbar.tsx b/src/visualBuilder/components/FieldToolbar.tsx index c542c83a..29967560 100644 --- a/src/visualBuilder/components/FieldToolbar.tsx +++ b/src/visualBuilder/components/FieldToolbar.tsx @@ -392,17 +392,23 @@ function FieldToolbarComponent( }; }, []); + + useEffect(() => { - const event = visualBuilderPostMessage?.on( - VisualBuilderPostMessageEvents.FIELD_LOCATION_DATA, - (data: { data: any }) => { - setFieldLocationData(data.data.fieldLocationData); + const fetchFieldLocationData = async () => { + try { + const event = await visualBuilderPostMessage?.send(VisualBuilderPostMessageEvents.FIELD_LOCATION_DATA, { + domEditStack: getDOMEditStack(eventDetails.editableElement) + }); + + setFieldLocationData(event) + } catch (error) { + console.error('Error fetching field location data:', error); } - ); - return () => { - event?.unregister(); }; - }, []); + + fetchFieldLocationData(); + }, [eventDetails.editableElement]); const multipleFieldToolbarButtonClasses = classNames( "visual-builder__button visual-builder__button--secondary",