From 68ffacd8e10d5ea3ad4ab1dc1edcbc334f7f8f37 Mon Sep 17 00:00:00 2001
From: Balint Kleri <b@balintbrews.com>
Date: Thu, 1 May 2025 14:52:13 +0200
Subject: [PATCH 1/6] Create drupal-globals utility

---
 ui/src/features/code-editor/Preview.tsx | 16 ++++------------
 ui/src/utils/drupal-globals.ts          |  6 ++++++
 2 files changed, 10 insertions(+), 12 deletions(-)
 create mode 100644 ui/src/utils/drupal-globals.ts

diff --git a/ui/src/features/code-editor/Preview.tsx b/ui/src/features/code-editor/Preview.tsx
index 6de5083d35..4fe17c21a8 100644
--- a/ui/src/features/code-editor/Preview.tsx
+++ b/ui/src/features/code-editor/Preview.tsx
@@ -38,19 +38,11 @@ import {
   getSlotNamesForPreview,
 } from '@/features/code-editor/utils';
 import { useGetCodeComponentsQuery } from '@/services/componentAndLayout';
+import { getDrupal, getXbSettings, getBasePath } from '@/utils/drupal-globals';
 
-const { Drupal } = window as any;
-
-const XB_MODULE_UI_PATH = (() => {
-  const { drupalSettings } = window;
-  if (!drupalSettings) {
-    return '';
-  }
-  const { xbModulePath } = drupalSettings.xb;
-  const { baseUrl } = drupalSettings.path;
-  return `${baseUrl}${xbModulePath}/ui` as const;
-})();
-
+const Drupal = getDrupal();
+const XB_MODULE_UI_PATH =
+  `${getBasePath()}${getXbSettings().xbModulePath}/ui` as const;
 const PREVIEW_LIB_PATH = 'dist/assets/code-editor-preview.js' as const;
 
 const swcConfig: Options = {
diff --git a/ui/src/utils/drupal-globals.ts b/ui/src/utils/drupal-globals.ts
new file mode 100644
index 0000000000..6215e5aa1b
--- /dev/null
+++ b/ui/src/utils/drupal-globals.ts
@@ -0,0 +1,6 @@
+const { Drupal, drupalSettings } = window as any;
+
+export const getDrupal = () => Drupal;
+export const getDrupalSettings = () => drupalSettings;
+export const getXbSettings = () => drupalSettings.xb;
+export const getBasePath = () => drupalSettings.path.baseUrl;
-- 
GitLab


From 11620cf980def1f37a45c82834690af0ff08e006 Mon Sep 17 00:00:00 2001
From: Harumi Jang <harumi.jang@acquia.com>
Date: Mon, 12 May 2025 12:47:50 -0400
Subject: [PATCH 2/6] Change drupalSettings

---
 ui/src/components/ComponentPreview.tsx        |  3 ++-
 .../components/extensions/ExtensionsList.tsx  | 25 +++++++++++--------
 .../form/components/drupal/DrupalTextArea.tsx |  3 ++-
 ui/src/components/pageInfo/PageInfo.tsx       |  4 +--
 ui/src/components/topbar/Topbar.tsx           |  3 ++-
 ui/src/features/drupal/drupalUtil.ts          |  3 ++-
 ui/src/features/layout/preview/Preview.tsx    |  3 ++-
 ui/src/hooks/useComponentSelection.ts         |  3 ++-
 ui/src/hooks/useEditorNavigation.ts           |  4 +--
 ui/src/main.tsx                               |  3 ++-
 ui/src/services/addAjaxPageState.ts           |  4 ++-
 11 files changed, 35 insertions(+), 23 deletions(-)

diff --git a/ui/src/components/ComponentPreview.tsx b/ui/src/components/ComponentPreview.tsx
index fc0141c1f7..c768a0bb9e 100644
--- a/ui/src/components/ComponentPreview.tsx
+++ b/ui/src/components/ComponentPreview.tsx
@@ -5,12 +5,13 @@ import clsx from 'clsx';
 import Panel from '@/components/Panel';
 import type { XBComponent } from '@/types/Component';
 import type { Section } from '@/types/Section';
+import { getDrupalSettings } from '@/utils/drupal-globals';
 
 interface ComponentPreviewProps {
   componentListItem: XBComponent | Section;
 }
 
-const { drupalSettings } = window;
+const drupalSettings = getDrupalSettings();
 
 const ComponentPreview: React.FC<ComponentPreviewProps> = ({
   componentListItem,
diff --git a/ui/src/components/extensions/ExtensionsList.tsx b/ui/src/components/extensions/ExtensionsList.tsx
index 6ca53731e8..9dfe17031e 100644
--- a/ui/src/components/extensions/ExtensionsList.tsx
+++ b/ui/src/components/extensions/ExtensionsList.tsx
@@ -3,24 +3,27 @@ import { ExternalLinkIcon } from '@radix-ui/react-icons';
 import ExtensionButton from '@/components/extensions/ExtensionButton';
 import { handleNonWorkingBtn } from '@/utils/function-utils';
 import type React from 'react';
+import { getDrupalSettings } from '@/utils/drupal-globals';
 
 interface ExtensionsPopoverProps {}
 
-const { drupalSettings } = window;
+const drupalSettings = getDrupalSettings();
 
 const ExtensionsList: React.FC<ExtensionsPopoverProps> = () => {
   let extensionsList = [];
   if (drupalSettings && drupalSettings.xbExtension) {
-    extensionsList = Object.values(drupalSettings.xbExtension).map((value) => {
-      return {
-        ...value,
-        imgSrc:
-          value.imgSrc ||
-          `${drupalSettings.path.baseUrl}${drupalSettings.xb.xbModulePath}/ui/assets/icons/extension-default-abstract.svg`,
-        name: value.name,
-        description: value.description,
-      };
-    });
+    extensionsList = Object.values(drupalSettings.xbExtension).map(
+      (value: any) => {
+        return {
+          ...value,
+          imgSrc:
+            value.imgSrc ||
+            `${drupalSettings.path.baseUrl}${drupalSettings.xb.xbModulePath}/ui/assets/icons/extension-default-abstract.svg`,
+          name: value.name,
+          description: value.description,
+        };
+      },
+    );
   }
 
   return <ExtensionsListDisplay extensions={extensionsList || []} />;
diff --git a/ui/src/components/form/components/drupal/DrupalTextArea.tsx b/ui/src/components/form/components/drupal/DrupalTextArea.tsx
index 2a6b49cb19..7456269988 100644
--- a/ui/src/components/form/components/drupal/DrupalTextArea.tsx
+++ b/ui/src/components/form/components/drupal/DrupalTextArea.tsx
@@ -5,7 +5,8 @@ import { useRef, useState } from 'react';
 import { Flex } from '@radix-ui/themes';
 import type { Attributes } from '@/types/DrupalAttribute';
 import DrupalFormattedTextArea from './DrupalFormattedTextArea';
-const { drupalSettings } = window as any;
+import { getDrupalSettings } from '@/utils/drupal-globals';
+const drupalSettings = getDrupalSettings();
 
 const DrupalTextArea = ({
   attributes = {},
diff --git a/ui/src/components/pageInfo/PageInfo.tsx b/ui/src/components/pageInfo/PageInfo.tsx
index dbd92beef0..be023da0fd 100644
--- a/ui/src/components/pageInfo/PageInfo.tsx
+++ b/ui/src/components/pageInfo/PageInfo.tsx
@@ -40,7 +40,7 @@ import {
   selectEntityId,
   selectEntityType,
 } from '@/features/configuration/configurationSlice';
-import { getBaseUrl } from '@/utils/drupal-globals';
+import { getBaseUrl, getDrupalSettings } from '@/utils/drupal-globals';
 
 interface PageType {
   [key: string]: ReactElement;
@@ -53,7 +53,7 @@ const iconMap: PageType = {
   GlobalSectionName: <SectionIcon />,
 };
 
-const { drupalSettings } = window;
+const drupalSettings = getDrupalSettings();
 
 const PageInfo = () => {
   const { showBoundary } = useErrorBoundary();
diff --git a/ui/src/components/topbar/Topbar.tsx b/ui/src/components/topbar/Topbar.tsx
index 2978007940..4d07e9e351 100644
--- a/ui/src/components/topbar/Topbar.tsx
+++ b/ui/src/components/topbar/Topbar.tsx
@@ -21,10 +21,11 @@ import ExtensionsList from '@/components/extensions/ExtensionsList';
 import TopbarPopover from '@/components/topbar/menu/TopbarPopover';
 import topBarStyles from '@/components/topbar/Topbar.module.css';
 import DynamicComponents from '@/components/dynamicComponents/DynamicComponents';
+import { getDrupalSettings } from '@/utils/drupal-globals';
 
 const PREVIOUS_URL_STORAGE_KEY = 'XBPreviousURL';
 
-const { drupalSettings } = window;
+const drupalSettings = getDrupalSettings();
 
 const Topbar = () => {
   const navigate = useNavigate();
diff --git a/ui/src/features/drupal/drupalUtil.ts b/ui/src/features/drupal/drupalUtil.ts
index 7a66dd17f6..be3e7c9e3c 100644
--- a/ui/src/features/drupal/drupalUtil.ts
+++ b/ui/src/features/drupal/drupalUtil.ts
@@ -1,6 +1,7 @@
 import type { PropsValues } from '@/types/Form';
+import { getDrupalSettings } from '@/utils/drupal-globals';
 
-const { drupalSettings } = window;
+const drupalSettings = getDrupalSettings();
 export const setXbDrupalSetting = (
   property: 'layoutUtils' | 'navUtils',
   value: PropsValues,
diff --git a/ui/src/features/layout/preview/Preview.tsx b/ui/src/features/layout/preview/Preview.tsx
index 6d23b26ce2..e864a68905 100644
--- a/ui/src/features/layout/preview/Preview.tsx
+++ b/ui/src/features/layout/preview/Preview.tsx
@@ -18,6 +18,7 @@ import { selectPageData } from '@/features/pageData/pageDataSlice';
 import { selectPreviewHtml } from '@/features/pagePreview/previewSlice';
 import { contentApi } from '@/services/content';
 import { selectSelectedComponentUuid } from '@/features/ui/uiSlice';
+import { getDrupalSettings } from '@/utils/drupal-globals';
 
 interface PreviewProps {}
 
@@ -33,7 +34,7 @@ const previewSizes = {
     name: 'Mobile',
   },
 };
-const { drupalSettings } = window;
+const drupalSettings = getDrupalSettings();
 type PreviewSizeKey = keyof typeof previewSizes;
 const labelFormKey = `${drupalSettings.xb.entityTypeKeys.label}[0][value]`;
 
diff --git a/ui/src/hooks/useComponentSelection.ts b/ui/src/hooks/useComponentSelection.ts
index 2f0a20f2ca..5212b60c09 100644
--- a/ui/src/hooks/useComponentSelection.ts
+++ b/ui/src/hooks/useComponentSelection.ts
@@ -17,8 +17,9 @@ import {
 import type { RegionNode } from '@/features/layout/layoutModelSlice';
 import { selectLayout } from '@/features/layout/layoutModelSlice';
 import { selectDevMode } from '@/features/configuration/configurationSlice';
+import { getDrupalSettings } from '@/utils/drupal-globals';
 
-const { drupalSettings } = window;
+const drupalSettings = getDrupalSettings();
 
 /**
  * Filters out any components that are parents or children of components in the selection
diff --git a/ui/src/hooks/useEditorNavigation.ts b/ui/src/hooks/useEditorNavigation.ts
index db468a9365..c5ac7a62e6 100644
--- a/ui/src/hooks/useEditorNavigation.ts
+++ b/ui/src/hooks/useEditorNavigation.ts
@@ -1,9 +1,9 @@
 import { useCallback } from 'react';
 import { useNavigate } from 'react-router-dom';
 import { DEFAULT_REGION } from '@/features/ui/uiSlice';
-import { getBaseUrl } from '@/utils/drupal-globals';
+import { getBaseUrl, getDrupalSettings } from '@/utils/drupal-globals';
 
-const { drupalSettings } = window;
+const drupalSettings = getDrupalSettings();
 
 /**
  * Hook for editor navigation functions
diff --git a/ui/src/main.tsx b/ui/src/main.tsx
index ce94c11ff9..db2fc46830 100644
--- a/ui/src/main.tsx
+++ b/ui/src/main.tsx
@@ -20,6 +20,7 @@ import transforms from '@/utils/transforms';
 import '@/styles/radix-themes';
 import '@/styles/index.css';
 import { AJAX_UPDATE_FORM_STATE_EVENT } from '@/types/Ajax';
+import { getDrupalSettings } from '@/utils/drupal-globals';
 
 // Provide these dependencies as globals so extensions do not have redundant and
 // potentially conflicting dependencies.
@@ -32,7 +33,7 @@ interface ProviderComponentProps {
   store: EnhancedStore;
 }
 
-const { drupalSettings } = window;
+const drupalSettings = getDrupalSettings();
 const { Drupal } = window as any;
 
 const container = document.getElementById('experience-builder');
diff --git a/ui/src/services/addAjaxPageState.ts b/ui/src/services/addAjaxPageState.ts
index 4f0af37487..fd082b8a3c 100644
--- a/ui/src/services/addAjaxPageState.ts
+++ b/ui/src/services/addAjaxPageState.ts
@@ -1,4 +1,6 @@
-const { drupalSettings } = window as any;
+import { getDrupalSettings } from '@/utils/drupal-globals';
+
+const drupalSettings = getDrupalSettings();
 
 const addAjaxPageState = (query: string) => {
   // Drupal's AJAX API automatically adds ajaxPageState as a parameter, but
-- 
GitLab


From 17a5246b3b49eb5f27915dc18c59b74b073d78b4 Mon Sep 17 00:00:00 2001
From: Harumi Jang <harumi.jang@acquia.com>
Date: Mon, 12 May 2025 12:49:37 -0400
Subject: [PATCH 3/6] Change getDrupal

---
 ui/src/components/form/formUtil.ts       | 3 ++-
 ui/src/hooks/useDrupalBehaviors.ts       | 3 ++-
 ui/src/main.tsx                          | 4 ++--
 ui/src/services/processResponseAssets.ts | 3 ++-
 4 files changed, 8 insertions(+), 5 deletions(-)

diff --git a/ui/src/components/form/formUtil.ts b/ui/src/components/form/formUtil.ts
index fbc40cdd07..a1f6ce1958 100644
--- a/ui/src/components/form/formUtil.ts
+++ b/ui/src/components/form/formUtil.ts
@@ -14,6 +14,7 @@ import type { TransformConfig, Transforms } from '@/utils/transforms';
 import transforms from '@/utils/transforms';
 import qs from 'qs';
 import addDraft2019 from 'ajv-formats-draft2019';
+import { getDrupal } from '@/utils/drupal-globals';
 const ajv = new Ajv();
 addFormats(ajv);
 addDraft2019(ajv);
@@ -338,7 +339,7 @@ export function getPropsValues(
   // Iterate through every item in form state that corresponds to
   // a component input to create propsValues, which will ultimately be
   // used to update this component's model.
-  const { Drupal } = (window as any) || {
+  const Drupal = getDrupal() || {
     Drupal: { xbTransforms: transforms },
   };
   const transformsList: Transforms = Drupal?.xbTransforms || transforms;
diff --git a/ui/src/hooks/useDrupalBehaviors.ts b/ui/src/hooks/useDrupalBehaviors.ts
index 0b95f17b70..a245db9f59 100644
--- a/ui/src/hooks/useDrupalBehaviors.ts
+++ b/ui/src/hooks/useDrupalBehaviors.ts
@@ -1,7 +1,8 @@
 import { useEffect } from 'react';
 import type { RefObject } from 'react';
+import { getDrupal } from '@/utils/drupal-globals';
 
-const { Drupal } = window as any;
+const Drupal = getDrupal();
 
 export function useDrupalBehaviors(
   ref: RefObject<HTMLElement>,
diff --git a/ui/src/main.tsx b/ui/src/main.tsx
index db2fc46830..4fdcd80fce 100644
--- a/ui/src/main.tsx
+++ b/ui/src/main.tsx
@@ -20,7 +20,7 @@ import transforms from '@/utils/transforms';
 import '@/styles/radix-themes';
 import '@/styles/index.css';
 import { AJAX_UPDATE_FORM_STATE_EVENT } from '@/types/Ajax';
-import { getDrupalSettings } from '@/utils/drupal-globals';
+import { getDrupal, getDrupalSettings } from '@/utils/drupal-globals';
 
 // Provide these dependencies as globals so extensions do not have redundant and
 // potentially conflicting dependencies.
@@ -34,7 +34,7 @@ interface ProviderComponentProps {
 }
 
 const drupalSettings = getDrupalSettings();
-const { Drupal } = window as any;
+const Drupal = getDrupal();
 
 const container = document.getElementById('experience-builder');
 
diff --git a/ui/src/services/processResponseAssets.ts b/ui/src/services/processResponseAssets.ts
index 3c9f54e8c5..c8c88430aa 100644
--- a/ui/src/services/processResponseAssets.ts
+++ b/ui/src/services/processResponseAssets.ts
@@ -1,6 +1,7 @@
 import type { PropsValues } from '@/types/Form';
+import { getDrupal } from '@/utils/drupal-globals';
 
-const { Drupal } = window as any;
+const Drupal = getDrupal();
 
 /**
  * Takes a response rendered by XBTemplateRenderer, identifies any attached
-- 
GitLab


From b04726b24fcdd5a391e619765c7b22a5a892c500 Mon Sep 17 00:00:00 2001
From: Harumi Jang <harumi.jang@acquia.com>
Date: Mon, 12 May 2025 13:09:13 -0400
Subject: [PATCH 4/6] Use xbSettings

---
 ui/src/components/pageInfo/PageInfo.tsx    | 6 +++---
 ui/src/features/layout/preview/Preview.tsx | 6 +++---
 ui/src/hooks/useComponentSelection.ts      | 6 +++---
 ui/src/hooks/useEditorNavigation.ts        | 6 +++---
 ui/src/utils/drupal-globals.ts             | 1 -
 5 files changed, 12 insertions(+), 13 deletions(-)

diff --git a/ui/src/components/pageInfo/PageInfo.tsx b/ui/src/components/pageInfo/PageInfo.tsx
index be023da0fd..cf206b314f 100644
--- a/ui/src/components/pageInfo/PageInfo.tsx
+++ b/ui/src/components/pageInfo/PageInfo.tsx
@@ -40,7 +40,7 @@ import {
   selectEntityId,
   selectEntityType,
 } from '@/features/configuration/configurationSlice';
-import { getBaseUrl, getDrupalSettings } from '@/utils/drupal-globals';
+import { getBaseUrl, getXbSettings } from '@/utils/drupal-globals';
 
 interface PageType {
   [key: string]: ReactElement;
@@ -53,7 +53,7 @@ const iconMap: PageType = {
   GlobalSectionName: <SectionIcon />,
 };
 
-const drupalSettings = getDrupalSettings();
+const xbSettings = getXbSettings();
 
 const PageInfo = () => {
   const { showBoundary } = useErrorBoundary();
@@ -68,7 +68,7 @@ const PageInfo = () => {
   )?.name;
   const entity_form_fields = useAppSelector(selectPageData);
   const title =
-    entity_form_fields[`${drupalSettings.xb.entityTypeKeys.label}[0][value]`];
+    entity_form_fields[`${xbSettings.entityTypeKeys.label}[0][value]`];
 
   const {
     data: pageItems,
diff --git a/ui/src/features/layout/preview/Preview.tsx b/ui/src/features/layout/preview/Preview.tsx
index e864a68905..010011d9e4 100644
--- a/ui/src/features/layout/preview/Preview.tsx
+++ b/ui/src/features/layout/preview/Preview.tsx
@@ -18,7 +18,7 @@ import { selectPageData } from '@/features/pageData/pageDataSlice';
 import { selectPreviewHtml } from '@/features/pagePreview/previewSlice';
 import { contentApi } from '@/services/content';
 import { selectSelectedComponentUuid } from '@/features/ui/uiSlice';
-import { getDrupalSettings } from '@/utils/drupal-globals';
+import { getXbSettings } from '@/utils/drupal-globals';
 
 interface PreviewProps {}
 
@@ -34,9 +34,9 @@ const previewSizes = {
     name: 'Mobile',
   },
 };
-const drupalSettings = getDrupalSettings();
+const xbSettings = getXbSettings();
 type PreviewSizeKey = keyof typeof previewSizes;
-const labelFormKey = `${drupalSettings.xb.entityTypeKeys.label}[0][value]`;
+const labelFormKey = `${xbSettings.entityTypeKeys.label}[0][value]`;
 
 const Preview: React.FC<PreviewProps> = () => {
   const layout = useAppSelector(selectLayout);
diff --git a/ui/src/hooks/useComponentSelection.ts b/ui/src/hooks/useComponentSelection.ts
index 5212b60c09..c2c35d1a8c 100644
--- a/ui/src/hooks/useComponentSelection.ts
+++ b/ui/src/hooks/useComponentSelection.ts
@@ -17,9 +17,9 @@ import {
 import type { RegionNode } from '@/features/layout/layoutModelSlice';
 import { selectLayout } from '@/features/layout/layoutModelSlice';
 import { selectDevMode } from '@/features/configuration/configurationSlice';
-import { getDrupalSettings } from '@/utils/drupal-globals';
+import { getXbSettings } from '@/utils/drupal-globals';
 
-const drupalSettings = getDrupalSettings();
+const xbSettings = getXbSettings();
 
 /**
  * Filters out any components that are parents or children of components in the selection
@@ -264,7 +264,7 @@ export function useComponentSelection() {
   };
 
   // Add to Drupal settings for external access by extensions etc
-  drupalSettings.xb.componentSelectionUtils = componentSelectionUtils;
+  xbSettings.componentSelectionUtils = componentSelectionUtils;
 
   return componentSelectionUtils;
 }
diff --git a/ui/src/hooks/useEditorNavigation.ts b/ui/src/hooks/useEditorNavigation.ts
index c5ac7a62e6..e0ae3448af 100644
--- a/ui/src/hooks/useEditorNavigation.ts
+++ b/ui/src/hooks/useEditorNavigation.ts
@@ -1,9 +1,9 @@
 import { useCallback } from 'react';
 import { useNavigate } from 'react-router-dom';
 import { DEFAULT_REGION } from '@/features/ui/uiSlice';
-import { getBaseUrl, getDrupalSettings } from '@/utils/drupal-globals';
+import { getBaseUrl, getXbSettings } from '@/utils/drupal-globals';
 
-const drupalSettings = getDrupalSettings();
+const xbSettings = getXbSettings();
 
 /**
  * Hook for editor navigation functions
@@ -43,7 +43,7 @@ export function useEditorNavigation() {
     setEditorEntity,
   };
 
-  drupalSettings.xb.navUtils = editorNavUtils;
+  xbSettings.navUtils = editorNavUtils;
 
   return editorNavUtils;
 }
diff --git a/ui/src/utils/drupal-globals.ts b/ui/src/utils/drupal-globals.ts
index 574755467f..f1dac5a208 100644
--- a/ui/src/utils/drupal-globals.ts
+++ b/ui/src/utils/drupal-globals.ts
@@ -1,4 +1,3 @@
-// @todo Refactor codebase to use these methods in https://drupal.org/i/3521811.
 const { Drupal, drupalSettings } = window as any;
 
 export const getDrupal = () => Drupal;
-- 
GitLab


From e2b235147c0696e3c5b4ae6068eef869847f09f1 Mon Sep 17 00:00:00 2001
From: Harumi Jang <harumi.jang@acquia.com>
Date: Tue, 13 May 2025 18:00:07 -0400
Subject: [PATCH 5/6] Address feedback

---
 ui/global.d.ts                                | 29 +-------------
 ui/src/components/ComponentPreview.tsx        |  5 ++-
 .../components/extensions/ExtensionsList.tsx  | 30 +++++++-------
 .../form/components/drupal/DrupalTextArea.tsx | 15 +------
 ui/src/features/drupal/drupalUtil.ts          | 12 ------
 ui/src/features/layout/layoutModelSlice.ts    |  2 +-
 ui/src/features/layout/layoutUtils.ts         |  2 +-
 ui/src/main.tsx                               | 19 ++++-----
 ui/src/types/DrupalSettings.ts                | 39 +++++++++++++++++++
 ui/src/types/FormatType.ts                    | 13 +++++++
 ui/src/utils/drupal-globals.ts                | 14 ++++++-
 11 files changed, 99 insertions(+), 81 deletions(-)
 delete mode 100644 ui/src/features/drupal/drupalUtil.ts
 create mode 100644 ui/src/types/DrupalSettings.ts
 create mode 100644 ui/src/types/FormatType.ts

diff --git a/ui/global.d.ts b/ui/global.d.ts
index 331f91ce60..8f1746972f 100644
--- a/ui/global.d.ts
+++ b/ui/global.d.ts
@@ -1,36 +1,9 @@
-import type { PropsValues } from '@/types/Form';
 import type React from 'react';
 import type ReactDom from 'react-dom';
 // eslint-disable-next-line @typescript-eslint/no-restricted-imports
 import type * as ReactRedux from 'react-redux';
 import type * as ReduxToolkit from '@reduxjs/toolkit';
-
-interface DrupalSettings {
-  xb: {
-    base: string;
-    entityType: string;
-    entity: string;
-    entityTypeKeys: {
-      id: string;
-      label: string;
-    };
-    globalAssets: {
-      css: string;
-      jsHeader: string;
-      jsFooter: string;
-    };
-    layoutUtils: PropsValues;
-    componentSelectionUtils: PropsValues;
-    navUtils: PropsValues;
-    xbModulePath: string;
-    selectedComponent: string;
-    devMode: boolean;
-  };
-  xbExtension: object;
-  path: {
-    baseUrl: string;
-  };
-}
+import type { DrupalSettings } from '@/types/DrupalSettings';
 
 interface CKEditor5Types {
   editorClassic: {
diff --git a/ui/src/components/ComponentPreview.tsx b/ui/src/components/ComponentPreview.tsx
index e1f38f2e3c..21ffccbefd 100644
--- a/ui/src/components/ComponentPreview.tsx
+++ b/ui/src/components/ComponentPreview.tsx
@@ -4,13 +4,14 @@ import styles from './ComponentPreview.module.css';
 import clsx from 'clsx';
 import type { XBComponent } from '@/types/Component';
 import type { Section } from '@/types/Section';
-import { getDrupalSettings } from '@/utils/drupal-globals';
+import { getBaseUrl, getDrupalSettings } from '@/utils/drupal-globals';
 
 interface ComponentPreviewProps {
   componentListItem: XBComponent | Section;
 }
 
 const drupalSettings = getDrupalSettings();
+const baseUrl = getBaseUrl();
 
 const ComponentPreview: React.FC<ComponentPreviewProps> = ({
   componentListItem,
@@ -28,7 +29,7 @@ const ComponentPreview: React.FC<ComponentPreviewProps> = ({
     drupalSettings?.xb.globalAssets.jsHeader + component.js_header;
 
   const markup = component.default_markup;
-  const base_url = window.location.origin + drupalSettings?.path.baseUrl;
+  const base_url = window.location.origin + baseUrl;
 
   const html = `
 <html>
diff --git a/ui/src/components/extensions/ExtensionsList.tsx b/ui/src/components/extensions/ExtensionsList.tsx
index 9dfe17031e..6e665a763e 100644
--- a/ui/src/components/extensions/ExtensionsList.tsx
+++ b/ui/src/components/extensions/ExtensionsList.tsx
@@ -3,27 +3,31 @@ import { ExternalLinkIcon } from '@radix-ui/react-icons';
 import ExtensionButton from '@/components/extensions/ExtensionButton';
 import { handleNonWorkingBtn } from '@/utils/function-utils';
 import type React from 'react';
-import { getDrupalSettings } from '@/utils/drupal-globals';
+import {
+  getBaseUrl,
+  getDrupalSettings,
+  getXbSettings,
+} from '@/utils/drupal-globals';
 
 interface ExtensionsPopoverProps {}
 
 const drupalSettings = getDrupalSettings();
+const baseUrl = getBaseUrl();
+const xbSettings = getXbSettings();
 
 const ExtensionsList: React.FC<ExtensionsPopoverProps> = () => {
   let extensionsList = [];
   if (drupalSettings && drupalSettings.xbExtension) {
-    extensionsList = Object.values(drupalSettings.xbExtension).map(
-      (value: any) => {
-        return {
-          ...value,
-          imgSrc:
-            value.imgSrc ||
-            `${drupalSettings.path.baseUrl}${drupalSettings.xb.xbModulePath}/ui/assets/icons/extension-default-abstract.svg`,
-          name: value.name,
-          description: value.description,
-        };
-      },
-    );
+    extensionsList = Object.values(drupalSettings.xbExtension).map((value) => {
+      return {
+        ...value,
+        imgSrc:
+          value.imgSrc ||
+          `${baseUrl}${xbSettings.xbModulePath}/ui/assets/icons/extension-default-abstract.svg`,
+        name: value.name,
+        description: value.description,
+      };
+    });
   }
 
   return <ExtensionsListDisplay extensions={extensionsList || []} />;
diff --git a/ui/src/components/form/components/drupal/DrupalTextArea.tsx b/ui/src/components/form/components/drupal/DrupalTextArea.tsx
index 7456269988..9b02c61182 100644
--- a/ui/src/components/form/components/drupal/DrupalTextArea.tsx
+++ b/ui/src/components/form/components/drupal/DrupalTextArea.tsx
@@ -6,6 +6,7 @@ import { Flex } from '@radix-ui/themes';
 import type { Attributes } from '@/types/DrupalAttribute';
 import DrupalFormattedTextArea from './DrupalFormattedTextArea';
 import { getDrupalSettings } from '@/utils/drupal-globals';
+import type { FormatType } from '@/types/FormatType';
 const drupalSettings = getDrupalSettings();
 
 const DrupalTextArea = ({
@@ -68,20 +69,6 @@ const DrupalTextArea = ({
   );
 };
 
-interface FormatType {
-  format: string;
-  editor?: string;
-  editorSettings?: {
-    toolbar: any[];
-    plugins: string[];
-    config: {
-      [key: string]: any;
-    };
-    language: Record<string, any>;
-  };
-  [key: string]: any;
-}
-
 interface FormatSelectProps {
   attributes: Attributes;
   selectAttributes: Record<string, any>;
diff --git a/ui/src/features/drupal/drupalUtil.ts b/ui/src/features/drupal/drupalUtil.ts
deleted file mode 100644
index be3e7c9e3c..0000000000
--- a/ui/src/features/drupal/drupalUtil.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-import type { PropsValues } from '@/types/Form';
-import { getDrupalSettings } from '@/utils/drupal-globals';
-
-const drupalSettings = getDrupalSettings();
-export const setXbDrupalSetting = (
-  property: 'layoutUtils' | 'navUtils',
-  value: PropsValues,
-) => {
-  if (drupalSettings?.xb?.[property]) {
-    drupalSettings.xb[property] = { ...drupalSettings.xb[property], ...value };
-  }
-};
diff --git a/ui/src/features/layout/layoutModelSlice.ts b/ui/src/features/layout/layoutModelSlice.ts
index a2e01b7b1c..dee6177f45 100644
--- a/ui/src/features/layout/layoutModelSlice.ts
+++ b/ui/src/features/layout/layoutModelSlice.ts
@@ -8,7 +8,7 @@ import { createSelector } from '@reduxjs/toolkit';
 import { createSlice } from '@reduxjs/toolkit';
 import type { StateWithHistory } from 'redux-undo';
 import { v4 as uuidv4 } from 'uuid';
-import { setXbDrupalSetting } from '@/features/drupal/drupalUtil';
+import { setXbDrupalSetting } from '@/utils/drupal-globals';
 import {
   findComponentByUuid,
   findNodePathByUuid,
diff --git a/ui/src/features/layout/layoutUtils.ts b/ui/src/features/layout/layoutUtils.ts
index 8c4b3baf6e..fca9fd9e1c 100644
--- a/ui/src/features/layout/layoutUtils.ts
+++ b/ui/src/features/layout/layoutUtils.ts
@@ -8,7 +8,7 @@ import type {
 } from './layoutModelSlice';
 import { NodeType } from './layoutModelSlice';
 import { v4 as uuidv4 } from 'uuid';
-import { setXbDrupalSetting } from '@/features/drupal/drupalUtil';
+import { setXbDrupalSetting } from '@/utils/drupal-globals';
 import { isConsecutive } from '@/utils/function-utils';
 
 type NodeFunction = (
diff --git a/ui/src/main.tsx b/ui/src/main.tsx
index 4fdcd80fce..00d3fbc54e 100644
--- a/ui/src/main.tsx
+++ b/ui/src/main.tsx
@@ -20,7 +20,7 @@ import transforms from '@/utils/transforms';
 import '@/styles/radix-themes';
 import '@/styles/index.css';
 import { AJAX_UPDATE_FORM_STATE_EVENT } from '@/types/Ajax';
-import { getDrupal, getDrupalSettings } from '@/utils/drupal-globals';
+import { getBaseUrl, getDrupal, getXbSettings } from '@/utils/drupal-globals';
 
 // Provide these dependencies as globals so extensions do not have redundant and
 // potentially conflicting dependencies.
@@ -33,17 +33,18 @@ interface ProviderComponentProps {
   store: EnhancedStore;
 }
 
-const drupalSettings = getDrupalSettings();
 const Drupal = getDrupal();
+const xbSettings = getXbSettings();
+const baseUrl = getBaseUrl();
 
 const container = document.getElementById('experience-builder');
 
 const appConfiguration: AppConfiguration = {
   ...initialState,
-  baseUrl: drupalSettings?.path?.baseUrl || import.meta.env.BASE_URL,
-  entityType: drupalSettings?.xb?.entityType || 'node',
-  entity: drupalSettings?.xb?.entity || '1',
-  devMode: drupalSettings?.xb?.devMode || false,
+  baseUrl: baseUrl || import.meta.env.BASE_URL,
+  entityType: xbSettings.entityType || 'node',
+  entity: xbSettings.entity || '1',
+  devMode: xbSettings.devMode || false,
 };
 
 const isAjaxing = () =>
@@ -89,13 +90,13 @@ Drupal.attachBehaviorsAfterAjaxing = attachBehaviorsAfterAjaxing;
 if (container) {
   const root = createRoot(container);
   let routerRoot = appConfiguration.baseUrl;
-  if (drupalSettings?.xb?.base) {
-    routerRoot = `${routerRoot}${drupalSettings.xb.base}`;
+  if (xbSettings.base) {
+    routerRoot = `${routerRoot}${xbSettings.base}`;
   }
   const store = makeStore({ configuration: appConfiguration });
 
   // Make the store available to extensions.
-  (drupalSettings as any).xb.store = store;
+  xbSettings.store = store;
 
   root.render(
     <React.StrictMode>
diff --git a/ui/src/types/DrupalSettings.ts b/ui/src/types/DrupalSettings.ts
new file mode 100644
index 0000000000..f817332ecc
--- /dev/null
+++ b/ui/src/types/DrupalSettings.ts
@@ -0,0 +1,39 @@
+import type { PropsValues } from '@/types/Form';
+import type { FormatType } from '@/types/FormatType';
+
+export interface DrupalSettings {
+  xb: {
+    base: string;
+    entityType: string;
+    entity: string;
+    entityTypeKeys: {
+      id: string;
+      label: string;
+    };
+    globalAssets: {
+      css: string;
+      jsHeader: string;
+      jsFooter: string;
+    };
+    layoutUtils: PropsValues;
+    componentSelectionUtils: PropsValues;
+    navUtils: PropsValues;
+    xbModulePath: string;
+    selectedComponent: string;
+    devMode: boolean;
+  };
+  xbExtension: object;
+  path: {
+    baseUrl: string;
+  };
+  editor: {
+    formats: {
+      [key: string]: FormatType;
+    };
+  };
+  ajaxPageState: {
+    libraries: string;
+    theme: string;
+    theme_token: string;
+  };
+}
diff --git a/ui/src/types/FormatType.ts b/ui/src/types/FormatType.ts
new file mode 100644
index 0000000000..ab62c4c534
--- /dev/null
+++ b/ui/src/types/FormatType.ts
@@ -0,0 +1,13 @@
+export interface FormatType {
+  format: string;
+  editor?: string;
+  editorSettings?: {
+    toolbar: any[];
+    plugins: string[];
+    config: {
+      [key: string]: any;
+    };
+    language: Record<string, any>;
+  };
+  [key: string]: any;
+}
diff --git a/ui/src/utils/drupal-globals.ts b/ui/src/utils/drupal-globals.ts
index f1dac5a208..fecd4257e9 100644
--- a/ui/src/utils/drupal-globals.ts
+++ b/ui/src/utils/drupal-globals.ts
@@ -1,6 +1,18 @@
+import type { PropsValues } from '@/types/Form';
+import type { DrupalSettings } from '@/types/DrupalSettings';
+
 const { Drupal, drupalSettings } = window as any;
 
 export const getDrupal = () => Drupal;
-export const getDrupalSettings = () => drupalSettings;
+export const getDrupalSettings = (): DrupalSettings => drupalSettings;
 export const getXbSettings = () => drupalSettings.xb;
 export const getBaseUrl = () => drupalSettings.path.baseUrl;
+
+export const setXbDrupalSetting = (
+  property: 'layoutUtils' | 'navUtils',
+  value: PropsValues,
+) => {
+  if (drupalSettings?.xb?.[property]) {
+    drupalSettings.xb[property] = { ...drupalSettings.xb[property], ...value };
+  }
+};
-- 
GitLab


From 1114f705c276c983e8da89f220c40519e45ed86c Mon Sep 17 00:00:00 2001
From: Harumi Jang <harumi.jang@acquia.com>
Date: Wed, 14 May 2025 10:20:42 -0400
Subject: [PATCH 6/6] Update vite mock

---
 ui/tests/vitest/support/vitest.setup.js | 26 +++++++++++++++++--------
 1 file changed, 18 insertions(+), 8 deletions(-)

diff --git a/ui/tests/vitest/support/vitest.setup.js b/ui/tests/vitest/support/vitest.setup.js
index 08aae24d56..d9adef1345 100644
--- a/ui/tests/vitest/support/vitest.setup.js
+++ b/ui/tests/vitest/support/vitest.setup.js
@@ -1,5 +1,12 @@
 import { vi } from 'vitest';
 
+const mockDrupalSettings = {
+  path: {
+    baseUrl: '/',
+  },
+  xb: {},
+};
+
 vi.stubGlobal('URL', {
   createObjectURL: vi.fn().mockImplementation((blob) => {
     return `mock-object-url/${blob.name}`;
@@ -10,14 +17,17 @@ vi.mock('@/utils/drupal-globals', () => ({
   getDrupal: () => ({
     url: (path) => `http://mock-drupal-url/${path}`,
   }),
-  getDrupalSettings: () => ({
-    path: {
-      baseUrl: '/',
-    },
-    xb: {},
-  }),
-  getXbSettings: () => ({}),
-  getBasePath: () => '/',
+  getDrupalSettings: () => mockDrupalSettings,
+  getXbSettings: () => mockDrupalSettings.xb,
+  getBasePath: () => mockDrupalSettings.path.baseUrl,
+  setXbDrupalSetting: (property, value) => {
+    if (mockDrupalSettings?.xb?.[property]) {
+      mockDrupalSettings.xb[property] = {
+        ...mockDrupalSettings.xb[property],
+        ...value,
+      };
+    }
+  },
 }));
 
 vi.mock('@swc/wasm-web', () => ({
-- 
GitLab