@myissue/vue-website-page-builder 3.3.97 → 3.3.99

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@myissue/vue-website-page-builder",
3
- "version": "3.3.97",
3
+ "version": "3.3.99",
4
4
  "description": "Vue 3 page builder component with drag & drop functionality.",
5
5
  "type": "module",
6
6
  "main": "./dist/vue-website-page-builder.umd.cjs",
@@ -52,12 +52,12 @@
52
52
  "license": "MIT",
53
53
  "repository": {
54
54
  "type": "git",
55
- "url": "https://github.com/qaiswardag/vue-website-page-builder.git"
55
+ "url": "git+ssh://git@github.com/myissue-org/vue-website-page-builder.git"
56
56
  },
57
57
  "bugs": {
58
- "url": "https://github.com/qaiswardag/vue-website-page-builder/issues"
58
+ "url": "https://github.com/myissue-org/vue-website-page-builder/issues"
59
59
  },
60
- "homepage": "https://mybuilder.dev",
60
+ "homepage": "https://www.mybuilder.dev",
61
61
  "engines": {
62
62
  "node": ">=18.0.0",
63
63
  "npm": ">=8.0.0"
@@ -18,7 +18,7 @@ const version = __APP_VERSION__
18
18
  </p>
19
19
  <p class="pbx-myPrimaryParagraph pbx-mt-3">
20
20
  <a
21
- href="https://github.com/qaiswardag/vue-website-page-builder"
21
+ href="https://github.com/myissue-org/vue-website-page-builder"
22
22
  target="_blank"
23
23
  class="pbx-myPrimaryLink pbx-text-myPrimaryDarkGrayColor"
24
24
  >
@@ -73,13 +73,68 @@ function onScroll() {
73
73
  }
74
74
  }
75
75
 
76
- // generate HTML
77
76
  const generateHTML = function (filename, HTML) {
77
+ // Extract existing styles from the page
78
+ const existingStyles = Array.from(document.querySelectorAll('style, link[rel="stylesheet"]'))
79
+ .map((style) => {
80
+ if (style.tagName === 'STYLE') {
81
+ return style.outerHTML // Inline styles
82
+ } else if (style.tagName === 'LINK') {
83
+ return `<link rel="stylesheet" href="${style.href}">` // External stylesheets
84
+ }
85
+ return ''
86
+ })
87
+ .join('\n')
88
+
89
+ // Add your custom CSS
90
+ const customCSS = `
91
+ <style>
92
+ #pagebuilder blockquote,
93
+ #pagebuilder dl,
94
+ #pagebuilder dd,
95
+ #pagebuilder pre,
96
+ #pagebuilder hr,
97
+ #pagebuilder figure,
98
+ #pagebuilder p,
99
+ #pagebuilder h1,
100
+ #pagebuilder h2,
101
+ #pagebuilder h3,
102
+ #pagebuilder h4,
103
+ #pagebuilder h5,
104
+ #pagebuilder h6,
105
+ #pagebuilder ul,
106
+ #pagebuilder ol,
107
+ #pagebuilder li {
108
+ margin: 0;
109
+ padding: 0; /* Often useful for ul/ol too */
110
+ }
111
+ </style>
112
+ `
113
+
114
+ // Combine existing styles and custom CSS
115
+ const css = `${existingStyles}\n${customCSS}`
116
+
117
+ // Generate the full HTML
118
+ const fullHTML = `
119
+ <!DOCTYPE html>
120
+ <html lang="en">
121
+ <head>
122
+ <meta charset="UTF-8">
123
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
124
+ <title>Downloaded HTML</title>
125
+ ${css}
126
+ </head>
127
+ <body>
128
+ <div id="pagebuilder" class="pbx-font-sans pbx-text-black">
129
+ ${HTML}
130
+ </div>
131
+ </body>
132
+ </html>
133
+ `
134
+
135
+ // Create and trigger the download
78
136
  const element = document.createElement('a')
79
- element.setAttribute(
80
- 'href',
81
- 'data:text/html;charset=utf-8,' + encodeURIComponent(fullHTMLContent(HTML)),
82
- )
137
+ element.setAttribute('href', 'data:text/html;charset=utf-8,' + encodeURIComponent(fullHTML))
83
138
  element.setAttribute('download', filename)
84
139
 
85
140
  element.style.display = 'none'
@@ -96,7 +151,21 @@ const handleDownloadHTML = function () {
96
151
  return
97
152
  }
98
153
 
99
- const html = extractCleanHTMLFromPageBuilder(pagebuilder)
154
+ // Extract clean HTML
155
+ let html = extractCleanHTMLFromPageBuilder(pagebuilder)
156
+
157
+ // Create a temporary DOM element to manipulate the HTML
158
+ const tempDiv = document.createElement('div')
159
+ tempDiv.innerHTML = html
160
+
161
+ // Remove 'hovered' and 'selected' attributes
162
+ tempDiv.querySelectorAll('[hovered], [selected]').forEach((el) => {
163
+ el.removeAttribute('hovered')
164
+ el.removeAttribute('selected')
165
+ })
166
+
167
+ // Get the cleaned HTML back
168
+ html = tempDiv.innerHTML
100
169
 
101
170
  generateHTML('downloaded_html.html', html)
102
171
  }
@@ -51,18 +51,20 @@ const handleRedo = async function () {
51
51
  class="pbx-flex-1 pbx-flex pbx-justify-center pbx-items-center pbx-py-2 pbx-w-full gap-1"
52
52
  >
53
53
  <!-- Undo Start -->
54
- <button @click="handleUndo" type="button" :disabled="!canUndo">
55
- <div
56
- class="pbx-h-10 pbx-w-10 pbx-rounded-full pbx-flex pbx-items-center pbx-border-none pbx-justify-center pbx-bg-gray-50 pbx-aspect-square pbx-text-black hover:pbx-text-white"
57
- :class="[
58
- canUndo
59
- ? 'pbx-cursor-pointer hover:pbx-bg-myPrimaryLinkColor focus-visible:pbx-ring-0'
60
- : 'pbx-cursor-not-allowed pbx-bg-opacity-20 hover:pbx-bg-gray-200',
61
- ]"
62
- >
63
- <span class="material-symbols-outlined"> undo </span>
64
- </div>
65
- </button>
54
+
55
+ <div
56
+ @click="handleUndo"
57
+ type="button"
58
+ class="pbx-h-10 pbx-w-10 pbx-rounded-full pbx-flex pbx-items-center pbx-border-none pbx-justify-center pbx-bg-gray-50 pbx-aspect-square pbx-text-black hover:pbx-text-white"
59
+ :class="[
60
+ canUndo
61
+ ? 'pbx-cursor-pointer hover:pbx-bg-myPrimaryLinkColor focus-visible:pbx-ring-0'
62
+ : 'pbx-cursor-not-allowed pbx-bg-opacity-20 hover:pbx-bg-gray-200',
63
+ ]"
64
+ >
65
+ <span class="material-symbols-outlined"> undo </span>
66
+ </div>
67
+
66
68
  <!-- Undo End -->
67
69
  <div
68
70
  class="pbx-text-xs pbx-text-gray-600 pbx-mx-2 pbx-py-3 pbx-px-2 pbx-border-solid pbx-border pbx-border-gray-200 pbx-rounded-full"
@@ -70,18 +72,19 @@ const handleRedo = async function () {
70
72
  {{ historyIndex + 1 }}/{{ historyLength }}
71
73
  </div>
72
74
  <!-- Redo Start -->
73
- <button @click="handleRedo" type="button" :disabled="!canRedo">
74
- <div
75
- class="pbx-h-10 pbx-w-10 pbx-rounded-full pbx-flex pbx-items-center pbx-border-none pbx-justify-center pbx-bg-gray-50 pbx-aspect-square pbx-text-black hover:pbx-text-white"
76
- :class="[
77
- canRedo
78
- ? 'pbx-cursor-pointer hover:pbx-bg-myPrimaryLinkColor focus-visible:pbx-ring-0'
79
- : 'pbx-cursor-not-allowed pbx-bg-opacity-20 hover:pbx-bg-gray-200',
80
- ]"
81
- >
82
- <span class="material-symbols-outlined"> redo </span>
83
- </div>
84
- </button>
75
+
76
+ <div
77
+ @click="handleRedo"
78
+ class="pbx-h-10 pbx-w-10 pbx-rounded-full pbx-flex pbx-items-center pbx-border-none pbx-justify-center pbx-bg-gray-50 pbx-aspect-square pbx-text-black hover:pbx-text-white"
79
+ :class="[
80
+ canRedo
81
+ ? 'pbx-cursor-pointer hover:pbx-bg-myPrimaryLinkColor focus-visible:pbx-ring-0'
82
+ : 'pbx-cursor-not-allowed pbx-bg-opacity-20 hover:pbx-bg-gray-200',
83
+ ]"
84
+ >
85
+ <span class="material-symbols-outlined"> redo </span>
86
+ </div>
87
+
85
88
  <!-- Redo End -->
86
89
  </div>
87
90
  </template>
@@ -879,11 +879,11 @@ onMounted(async () => {
879
879
 
880
880
  <div id="pagebuilder" class="pbx-text-black pbx-font-sans">
881
881
  <template v-for="component in getComponents" :key="component.id">
882
- <section
882
+ <div
883
883
  v-if="component.html_code"
884
884
  v-html="component.html_code"
885
885
  @mouseup="handleSelectComponent(component)"
886
- ></section>
886
+ ></div>
887
887
  </template>
888
888
  </div>
889
889
  </main>
package/src/css/style.css CHANGED
@@ -314,11 +314,8 @@
314
314
  @apply pbx-my-0 pbx-block pbx-text-sm pbx-font-normal pbx-text-myPrimaryDarkGrayColor pbx-text-left pbx-mb-2;
315
315
  }
316
316
 
317
- .pbx-myPrimaryFormFocus {
318
- @apply focus:pbx-ring-2 focus:pbx-ring-myPrimaryLinkColor focus:pbx-ring-offset-2 focus:pbx-border focus:pbx-border-gray-200;
319
- }
320
317
  .pbx-myPrimaryInput {
321
- @apply pbx-block pbx-pr-8 pbx-text-left pbx-bg-white pbx-w-auto sm:pbx-text-sm pbx-font-normal pbx-text-myPrimaryDarkGrayColor placeholder:pbx-font-normal placeholder:pbx-accent-gray-300 focus:pbx-bg-white pbx-rounded-md pbx-py-3 pbx-px-3 pbx-border pbx-border-gray-300 pbx-shadow-sm focus:pbx-outline-none pbx-myPrimaryFormFocus pbx-h-full pbx-border-solid;
318
+ @apply pbx-block pbx-pr-8 pbx-text-left pbx-bg-white pbx-w-auto sm:pbx-text-sm pbx-font-normal pbx-text-myPrimaryDarkGrayColor placeholder:pbx-font-normal placeholder:pbx-accent-gray-300 focus:pbx-bg-white pbx-rounded-md pbx-py-3 pbx-px-3 pbx-border pbx-border-gray-300 pbx-shadow-sm focus:pbx-outline-none pbx-h-full pbx-border-solid pbx-font-sans;
322
319
  }
323
320
  .pbx-myPrimaryInputNoBorder {
324
321
  @apply pbx-myPrimaryInput placeholder:pbx-accent-gray-300 focus:pbx-bg-none pbx-rounded-md pbx-py-3 pbx-px-3 pbx-border-none focus:pbx-outline-none focus:pbx-ring-0 focus:pbx-ring-offset-0 focus:pbx-border-none pbx-shadow-none;
@@ -358,20 +355,6 @@
358
355
  }
359
356
  }
360
357
 
361
- #page-builder-editor ol,
362
- #page-builder-editor-editable-area ol,
363
- #page-builder-editor ul,
364
- #page-builder-editor-editable-area ul {
365
- list-style: disc !important;
366
- padding: 0 0 0 1rem;
367
- }
368
-
369
- #page-builder-editor li,
370
- #page-builder-editor-editable-area li {
371
- line-height: 1.2;
372
- margin-left: 1em; /* Adjust this value as needed */
373
- }
374
-
375
358
  #pbxEditToolbar {
376
359
  opacity: 0;
377
360
  pointer-events: none;
@@ -471,36 +454,83 @@
471
454
  font-weight: 500;
472
455
  }
473
456
 
474
- #page-builder-editor ol,
475
- #pagebuilder ol,
476
- #page-builder-editor ul,
477
- #pagebuilder ul {
478
- list-style: disc !important;
479
- padding: 0rem;
480
- }
481
-
482
457
  .pbx-reorder-highlight {
483
458
  animation: pbx-reorder-flash 0.4s ease-in-out;
484
459
  }
485
460
 
486
461
  @keyframes pbx-reorder-flash {
487
462
  0% {
488
- box-shadow: 0 0 0 0px rgba(66, 153, 225, 0.7);
489
463
  transform: scale(1);
490
464
  opacity: 1;
491
465
  }
492
466
  50% {
493
- box-shadow: 0 0 0 10px rgba(66, 153, 225, 0);
494
- transform: scale(0.2); /* Even more shrink */
495
- opacity: 0.8;
467
+ transform: scale(0.8);
468
+ opacity: 0.6;
496
469
  }
497
470
  100% {
498
- box-shadow: 0 0 0 0px rgba(66, 153, 225, 0);
499
471
  transform: scale(1);
500
472
  opacity: 1;
501
473
  }
502
474
  }
503
475
 
504
476
  .pbx-sibling-highlight {
505
- border: 2px dashed #4299e1; /* Blue dashed border */
477
+ }
478
+
479
+ .pbx-myPrimaryInput,
480
+ .pbx-myPrimarySelect {
481
+ }
482
+
483
+ .pbx-myPrimaryInput:focus,
484
+ .pbx-myPrimarySelect:focus {
485
+ --tw-ring-inset: inset;
486
+ --tw-ring-opacity: 1;
487
+ /* Use the hex color with opacity variable */
488
+ --tw-ring-color: rgba(22, 163, 74, var(--tw-ring-opacity, 1));
489
+
490
+ --tw-ring-shadow: 0 0 0 4px var(--tw-ring-color);
491
+
492
+ --tw-ring-offset-width: 2px;
493
+ --tw-ring-offset-color: white;
494
+ --tw-ring-offset-shadow: 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
495
+
496
+ box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow);
497
+ border: 1px #dee6f0 solid;
498
+ }
499
+
500
+ #pagebuilder blockquote,
501
+ #pagebuilder dl,
502
+ #pagebuilder dd,
503
+ #pagebuilder pre,
504
+ #pagebuilder hr,
505
+ #pagebuilder figure,
506
+ #pagebuilder p,
507
+ #pagebuilder h1,
508
+ #pagebuilder h2,
509
+ #pagebuilder h3,
510
+ #pagebuilder h4,
511
+ #pagebuilder h5,
512
+ #pagebuilder h6,
513
+ #pagebuilder ul,
514
+ #pagebuilder ol,
515
+ #pagebuilder li {
516
+ margin: 0;
517
+ }
518
+
519
+ #page-builder-editor ol,
520
+ #pagebuilder ol,
521
+ #page-builder-editor ul,
522
+ #pagebuilder ul {
523
+ list-style: disc !important;
524
+ padding: 1rem 0 0 1rem;
525
+ margin-left: 1em;
526
+ line-height: 1.2;
527
+ }
528
+
529
+ #page-builder-editor ol,
530
+ #page-builder-editor-editable-area ol,
531
+ #page-builder-editor ul,
532
+ #page-builder-editor-editable-area ul {
533
+ list-style: disc !important;
534
+ padding: 1rem 0 0 1rem;
535
+ line-height: 1.2;
506
536
  }
@@ -1043,9 +1043,11 @@ export class PageBuilderService {
1043
1043
  /**
1044
1044
  * Manually saves the current page builder content to local storage.
1045
1045
  */
1046
- public handleManualSave = async () => {
1046
+ public handleManualSave = async (doNoClearHTML?: boolean) => {
1047
1047
  this.pageBuilderStateStore.setIsSaving(true)
1048
- this.clearHtmlSelection()
1048
+ if (!doNoClearHTML) {
1049
+ this.clearHtmlSelection()
1050
+ }
1049
1051
  this.startEditing()
1050
1052
  this.saveDomComponentsToLocalStorage()
1051
1053
  await delay(300)
@@ -1665,10 +1667,12 @@ export class PageBuilderService {
1665
1667
  await nextTick()
1666
1668
 
1667
1669
  // Scroll to the moved component
1668
- const pageBuilderWrapper = document.querySelector('#page-builder-wrapper')
1670
+ const pageBuilderWrapper = document.querySelector(
1671
+ '#page-builder-wrapper',
1672
+ ) as HTMLElement | null
1669
1673
  const movedComponentElement = pageBuilderWrapper?.querySelector(
1670
1674
  `section[data-componentid="${componentToMove.id}"]`,
1671
- )
1675
+ ) as HTMLElement
1672
1676
 
1673
1677
  if (movedComponentElement) {
1674
1678
  // Apply highlight to the moved element
@@ -1685,19 +1689,22 @@ export class PageBuilderService {
1685
1689
  nextSibling.classList.add('pbx-sibling-highlight')
1686
1690
  }
1687
1691
 
1688
- // Scroll to the moved component
1689
- movedComponentElement.scrollIntoView({ behavior: 'smooth', block: 'center' })
1690
-
1691
- // Remove highlights after a delay
1692
- setTimeout(() => {
1693
- movedComponentElement.classList.remove('pbx-reorder-highlight')
1694
- if (prevSibling && prevSibling.tagName === 'SECTION') {
1695
- prevSibling.classList.remove('pbx-sibling-highlight')
1696
- }
1697
- if (nextSibling && nextSibling.tagName === 'SECTION') {
1698
- nextSibling.classList.remove('pbx-sibling-highlight')
1699
- }
1700
- }, 1000) // Adjust delay as needed
1692
+ if (pageBuilderWrapper) {
1693
+ // Scroll to the moved component
1694
+ const topPos = movedComponentElement.offsetTop - pageBuilderWrapper.offsetTop
1695
+ pageBuilderWrapper.scrollTop = topPos - pageBuilderWrapper.clientHeight / 2
1696
+
1697
+ // Remove highlights after a delay
1698
+ setTimeout(() => {
1699
+ movedComponentElement.classList.remove('pbx-reorder-highlight')
1700
+ if (prevSibling && prevSibling.tagName === 'SECTION') {
1701
+ prevSibling.classList.remove('pbx-sibling-highlight')
1702
+ }
1703
+ if (nextSibling && nextSibling.tagName === 'SECTION') {
1704
+ nextSibling.classList.remove('pbx-sibling-highlight')
1705
+ }
1706
+ }, 200)
1707
+ }
1701
1708
  }
1702
1709
  }
1703
1710
 
@@ -1920,6 +1927,9 @@ export class PageBuilderService {
1920
1927
  pagebuilder.querySelectorAll('section[data-componentid]').forEach((section) => {
1921
1928
  const sanitizedSection = this.cloneAndRemoveSelectionAttributes(section as HTMLElement)
1922
1929
 
1930
+ // Remove the data-componentid attribute
1931
+ sanitizedSection.removeAttribute('data-componentid')
1932
+
1923
1933
  componentsToSave.push({
1924
1934
  html_code: sanitizedSection.outerHTML,
1925
1935
  title: sanitizedSection.getAttribute('data-component-title') || 'Untitled Component',
@@ -1938,7 +1948,29 @@ export class PageBuilderService {
1938
1948
  }
1939
1949
 
1940
1950
  const baseKey = this.getHistoryBaseKey()
1951
+
1941
1952
  if (baseKey) {
1953
+ const currentDataRaw = localStorage.getItem(baseKey)
1954
+ if (currentDataRaw) {
1955
+ const currentData = JSON.parse(currentDataRaw)
1956
+
1957
+ // Compare components
1958
+ const currentComponents = currentData.components || []
1959
+ const newComponents = dataToSave.components || []
1960
+
1961
+ const hasChanges = newComponents.some((newComponent, index) => {
1962
+ const currentComponent = currentComponents[index]
1963
+ return (
1964
+ !currentComponent || // New component added
1965
+ currentComponent.html_code !== newComponent.html_code // Component HTML changed
1966
+ )
1967
+ })
1968
+
1969
+ if (!hasChanges) {
1970
+ return
1971
+ }
1972
+ }
1973
+
1942
1974
  localStorage.setItem(baseKey, JSON.stringify(dataToSave))
1943
1975
  let history = LocalStorageManager.getHistory(baseKey)
1944
1976
 
@@ -2588,6 +2620,134 @@ export class PageBuilderService {
2588
2620
  }
2589
2621
  }
2590
2622
 
2623
+ /**
2624
+ * Applies modified components by mounting them to the DOM and attaching listeners.
2625
+ * @param htmlString - The HTML string to apply
2626
+ * @returns {Promise<string | null>} - Returns error message if failed, otherwise null
2627
+ */
2628
+ public async applyModifiedHTML(htmlString: string): Promise<string | null> {
2629
+ if (!htmlString || (typeof htmlString === 'string' && htmlString.length === 0)) {
2630
+ return 'No HTML content was provided. Please ensure a valid HTML string is passed.'
2631
+ }
2632
+
2633
+ // Check if the htmlString contains any <section> tags
2634
+ if (/<section[\s>]/i.test(htmlString)) {
2635
+ return 'Error: The <section> tag cannot be used as it is already included inside this component.'
2636
+ }
2637
+
2638
+ const tempDiv = document.createElement('div')
2639
+ tempDiv.innerHTML = htmlString.trim()
2640
+
2641
+ const parsedElement = tempDiv.firstElementChild as HTMLElement | null
2642
+
2643
+ if (!parsedElement) {
2644
+ return 'Could not parse element from HTML string.'
2645
+ }
2646
+
2647
+ // Replace the actual DOM element
2648
+ const oldElement = this.pageBuilderStateStore.getElement
2649
+
2650
+ if (oldElement && oldElement.parentElement) {
2651
+ oldElement.replaceWith(parsedElement)
2652
+
2653
+ // Update the element in the store (now referencing the new one)
2654
+ this.pageBuilderStateStore.setElement(parsedElement)
2655
+ }
2656
+
2657
+ await this.addListenersToEditableElements()
2658
+ await nextTick()
2659
+ return null
2660
+ }
2661
+
2662
+ private validateMountingHTML(
2663
+ htmlString: string,
2664
+ options?: { logError?: boolean },
2665
+ ): string | null {
2666
+ // Trim HTML string
2667
+ const trimmedData = htmlString.trim()
2668
+ const openingSectionMatches = htmlString.match(/<section\b[^>]*>/gi) || []
2669
+ const closingSectionMatches = htmlString.match(/<\/section>/gi) || []
2670
+
2671
+ if (!htmlString || htmlString.trim().length === 0) {
2672
+ const error = 'No HTML content was provided. Please ensure a valid HTML string is passed.'
2673
+ if (options && options.logError) {
2674
+ console.error(error)
2675
+ // Behavior
2676
+ return error
2677
+ }
2678
+ // default behavior
2679
+ return error
2680
+ }
2681
+
2682
+ if (openingSectionMatches.length !== closingSectionMatches.length) {
2683
+ const error =
2684
+ 'Uneven <section> tags detected in the provided HTML. Each component must be wrapped in its own properly paired <section>...</section>. ' +
2685
+ 'Ensure that all <section> tags have a matching closing </section> tag.'
2686
+
2687
+ if (options && options.logError) {
2688
+ console.error(error)
2689
+ return error
2690
+ }
2691
+
2692
+ return error
2693
+ }
2694
+
2695
+ const tempDiv = document.createElement('div')
2696
+ tempDiv.innerHTML = trimmedData
2697
+ const nestedSection = tempDiv.querySelector('section section')
2698
+ if (nestedSection) {
2699
+ const error =
2700
+ 'Nested <section> tags are not allowed. Please ensure that no <section> is placed inside another <section>.'
2701
+ if (options && options.logError) {
2702
+ console.error(error)
2703
+ return error
2704
+ }
2705
+ return error
2706
+ }
2707
+
2708
+ // Return error since JSON data has been passed to mount HTML to DOM
2709
+ if (trimmedData.startsWith('[') || trimmedData.startsWith('{')) {
2710
+ const error =
2711
+ 'Brackets [] or curly braces {} are not valid HTML. They are used for data formats like JSON.'
2712
+ if (options && options.logError) {
2713
+ console.error(error)
2714
+ return error
2715
+ }
2716
+
2717
+ return error
2718
+ }
2719
+
2720
+ return null
2721
+ }
2722
+
2723
+ /**
2724
+ * Applies modified components by mounting them to the DOM and attaching listeners.
2725
+ * @param htmlString - The HTML string to apply
2726
+ * @returns {Promise<string | null>} - Returns error message if failed, otherwise null
2727
+ */
2728
+ public async applyModifiedComponents(htmlString: string): Promise<string | null> {
2729
+ // Trim HTML string
2730
+ const trimmedData = htmlString.trim()
2731
+
2732
+ const openingSectionMatches = htmlString.match(/<section\b[^>]*>/gi) || []
2733
+
2734
+ if (openingSectionMatches.length === 0) {
2735
+ const error = 'No <section> tags found. Each component must be wrapped in a <section> tag.'
2736
+ if (error) {
2737
+ return error
2738
+ }
2739
+ }
2740
+
2741
+ const validationError = this.validateMountingHTML(trimmedData)
2742
+ if (validationError) return validationError
2743
+
2744
+ // also fixed to use `trimmedData`
2745
+ await this.mountComponentsToDOM(trimmedData)
2746
+ await this.addListenersToEditableElements()
2747
+ await nextTick()
2748
+ return null
2749
+ }
2750
+
2591
2751
  /**
2592
2752
  * Mounts builder components to the DOM from an HTML string.
2593
2753
  *
@@ -2606,13 +2766,11 @@ export class PageBuilderService {
2606
2766
  htmlString: string,
2607
2767
  usePassedPageSettings?: boolean,
2608
2768
  ): Promise<void> {
2769
+ // Trim HTML string
2609
2770
  const trimmedData = htmlString.trim()
2610
2771
 
2611
- // Return error since JSON data has been passed to mount HTML to DOM
2612
- if (trimmedData.startsWith('[') || trimmedData.startsWith('{')) {
2613
- console.error('Error: JSON data passed to mountComponentsToDOM for the Page Builder Package.')
2614
- return
2615
- }
2772
+ const validationError = this.validateMountingHTML(trimmedData, { logError: true })
2773
+ if (validationError) return
2616
2774
 
2617
2775
  // HTML string
2618
2776
  try {
@@ -2851,57 +3009,6 @@ export class PageBuilderService {
2851
3009
  }
2852
3010
  }
2853
3011
 
2854
- /**
2855
- * Applies modified components by mounting them to the DOM and attaching listeners.
2856
- * @param htmlString - The HTML string to apply
2857
- * @returns {Promise<string | null>} - Returns error message if failed, otherwise null
2858
- */
2859
- public async applyModifiedHTML(htmlString: string): Promise<string | null> {
2860
- if (!htmlString || (typeof htmlString === 'string' && htmlString.length === 0)) {
2861
- return 'No HTML content was provided. Please ensure a valid HTML string is passed.'
2862
- }
2863
-
2864
- const tempDiv = document.createElement('div')
2865
- tempDiv.innerHTML = htmlString.trim()
2866
-
2867
- const parsedElement = tempDiv.firstElementChild as HTMLElement | null
2868
-
2869
- if (!parsedElement) {
2870
- return 'Could not parse element from HTML string.'
2871
- }
2872
-
2873
- // Replace the actual DOM element
2874
- const oldElement = this.pageBuilderStateStore.getElement
2875
-
2876
- if (oldElement && oldElement.parentElement) {
2877
- oldElement.replaceWith(parsedElement)
2878
-
2879
- // Update the element in the store (now referencing the new one)
2880
- this.pageBuilderStateStore.setElement(parsedElement)
2881
- }
2882
-
2883
- await this.addListenersToEditableElements()
2884
- await nextTick()
2885
- return null
2886
- }
2887
-
2888
- /**
2889
- * Applies modified components by mounting them to the DOM and attaching listeners.
2890
- * @param htmlString - The HTML string to apply
2891
- * @returns {Promise<string | null>} - Returns error message if failed, otherwise null
2892
- */
2893
- public async applyModifiedComponents(htmlString: string): Promise<string | null> {
2894
- if (!htmlString || (typeof htmlString === 'string' && htmlString.length === 0)) {
2895
- return 'No HTML content was provided. Please ensure a valid HTML string is passed.'
2896
- }
2897
-
2898
- await this.mountComponentsToDOM(htmlString)
2899
-
2900
- await this.addListenersToEditableElements()
2901
- await nextTick()
2902
- return null
2903
- }
2904
-
2905
3012
  /**
2906
3013
  * Initializes the styles for the currently selected element.
2907
3014
  */
@@ -3,7 +3,7 @@ import FullWidthElement from '../Components/Layouts/FullWidthElement.vue'
3
3
  import PageBuilder from '../PageBuilder/PageBuilder.vue'
4
4
  import DemoMediaLibraryComponentTest from '../tests/TestComponents/DemoMediaLibraryComponentTest.vue'
5
5
  import DemoBuilderComponentsTest from '../tests/TestComponents/DemoBuilderComponentsTest.vue'
6
- import { onMounted, computed, watch } from 'vue'
6
+ import { computed, watch } from 'vue'
7
7
  import componentsArray from '../tests/componentsArray.test.json'
8
8
  import { getPageBuilder } from '../composables/builderInstance'
9
9
  import { useTranslations } from '../composables/useTranslations'