@myissue/vue-website-page-builder 3.2.91 → 3.2.93

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.
Files changed (42) hide show
  1. package/README.md +122 -84
  2. package/dist/vue-website-page-builder.css +1 -1
  3. package/dist/vue-website-page-builder.js +5221 -5286
  4. package/dist/vue-website-page-builder.umd.cjs +52 -52
  5. package/package.json +1 -1
  6. package/src/Components/Loaders/GlobalLoader.vue +11 -0
  7. package/src/Components/Modals/DynamicModalBuilder.vue +41 -245
  8. package/src/Components/Modals/ModalBuilder.vue +29 -4
  9. package/src/Components/PageBuilder/DefaultComponents/DefaultBuilderComponents.vue +3 -8
  10. package/src/Components/PageBuilder/EditorMenu/Editables/BackgroundColorEditor.vue +5 -4
  11. package/src/Components/PageBuilder/EditorMenu/Editables/BorderRadius.vue +12 -13
  12. package/src/Components/PageBuilder/EditorMenu/Editables/Borders.vue +8 -8
  13. package/src/Components/PageBuilder/EditorMenu/Editables/ClassEditor.vue +7 -6
  14. package/src/Components/PageBuilder/EditorMenu/Editables/ComponentTopMenu.vue +6 -10
  15. package/src/Components/PageBuilder/EditorMenu/Editables/DeleteElement.vue +4 -4
  16. package/src/Components/PageBuilder/EditorMenu/Editables/EditGetElement.vue +10 -11
  17. package/src/Components/PageBuilder/EditorMenu/Editables/ElementEditor.vue +4 -5
  18. package/src/Components/PageBuilder/EditorMenu/Editables/ImageEditor.vue +0 -9
  19. package/src/Components/PageBuilder/EditorMenu/Editables/LinkEditor.vue +5 -5
  20. package/src/Components/PageBuilder/EditorMenu/Editables/ManageBackgroundOpacity.vue +4 -4
  21. package/src/Components/PageBuilder/EditorMenu/Editables/ManageOpacity.vue +4 -4
  22. package/src/Components/PageBuilder/EditorMenu/Editables/Margin.vue +8 -8
  23. package/src/Components/PageBuilder/EditorMenu/Editables/Padding.vue +8 -8
  24. package/src/Components/PageBuilder/EditorMenu/Editables/TextColorEditor.vue +4 -4
  25. package/src/Components/PageBuilder/EditorMenu/Editables/Typography.vue +16 -16
  26. package/src/Components/PageBuilder/EditorMenu/RightSidebarEditor.vue +3 -7
  27. package/src/Components/PageBuilder/Settings/PageBuilderSettings.vue +55 -58
  28. package/src/Components/PageBuilder/ToolbarOption/ToolbarOption.vue +33 -40
  29. package/src/Components/TipTap/TipTap.vue +4 -9
  30. package/src/Components/TipTap/TipTapInput.vue +8 -8
  31. package/src/DemoComponents/DemoUnsplash.vue +11 -12
  32. package/src/DemoComponents/HomeSection.vue +9 -30
  33. package/src/PageBuilder/PageBuilder.vue +194 -96
  34. package/src/composables/{PageBuilderClass.ts → PageBuilderService.ts} +292 -112
  35. package/src/composables/builderInstance.ts +25 -0
  36. package/src/css/app.css +15 -0
  37. package/src/css/dev-global.css +7 -0
  38. package/src/index.ts +4 -2
  39. package/src/main.ts +3 -0
  40. package/src/stores/page-builder-state.ts +39 -10
  41. package/src/types/index.ts +100 -10
  42. package/src/helpers/passedPageBuilderConfig.ts +0 -71
@@ -14,8 +14,9 @@ import { computed, ref, nextTick } from 'vue'
14
14
  import type { ComputedRef } from 'vue'
15
15
  import { v4 as uuidv4 } from 'uuid'
16
16
  import { delay } from './delay'
17
+ import { isEmptyObject } from '../helpers/isEmptyObject'
17
18
 
18
- class PageBuilderClass {
19
+ export class PageBuilderService {
19
20
  // Class properties with types
20
21
  private nextTick: Promise<void>
21
22
  private containsPagebuilder: Element | null
@@ -23,7 +24,7 @@ class PageBuilderClass {
23
24
  private pageBuilderStateStore: ReturnType<typeof usePageBuilderStateStore>
24
25
  private getTextAreaVueModel: ComputedRef<string | null>
25
26
  private getLocalStorageItemName: ComputedRef<string | null>
26
- private getCurrentImage: ComputedRef<ImageObject>
27
+ private getApplyImageToSelection: ComputedRef<ImageObject>
27
28
  private getHyberlinkEnable: ComputedRef<boolean>
28
29
  private getComponents: ComputedRef<ComponentObject[] | null>
29
30
  private getComponent: ComputedRef<ComponentObject | null>
@@ -35,10 +36,10 @@ class PageBuilderClass {
35
36
  private NoneListernesTags: string[]
36
37
  private delay: (ms?: number) => Promise<void>
37
38
  private hasStartedEditing: boolean = false
39
+ private originalComponents: string | null = null
38
40
 
39
41
  constructor(pageBuilderStateStore: ReturnType<typeof usePageBuilderStateStore>) {
40
42
  this.nextTick = nextTick()
41
-
42
43
  this.hasStartedEditing = false
43
44
  this.containsPagebuilder = document.querySelector('#contains-pagebuilder')
44
45
  this.pageBuilderStateStore = pageBuilderStateStore
@@ -48,7 +49,9 @@ class PageBuilderClass {
48
49
  () => this.pageBuilderStateStore.getLocalStorageItemName,
49
50
  )
50
51
 
51
- this.getCurrentImage = computed(() => this.pageBuilderStateStore.getCurrentImage)
52
+ this.getApplyImageToSelection = computed(
53
+ () => this.pageBuilderStateStore.getApplyImageToSelection,
54
+ )
52
55
  this.getHyberlinkEnable = computed(() => this.pageBuilderStateStore.getHyberlinkEnable)
53
56
  this.getComponents = computed(() => this.pageBuilderStateStore.getComponents)
54
57
 
@@ -93,9 +96,138 @@ class PageBuilderClass {
93
96
  this.pageBuilderStateStore.setElement(null)
94
97
  await this.#removeHoveredAndSelected()
95
98
  }
96
- // Load existing content from HTML when in update mode
97
- applyPageBuilderConfig(data: PageBuilderConfig): void {
98
- this.pageBuilderStateStore.applyPageBuilderConfig(data)
99
+
100
+ #ensureUpdateOrCreateConfig(config: PageBuilderConfig): void {
101
+ // Case A: updateOrCreate is missing or an empty object
102
+ if (!config.updateOrCreate || (config.updateOrCreate && isEmptyObject(config.updateOrCreate))) {
103
+ const updatedConfig = {
104
+ ...config,
105
+ updateOrCreate: {
106
+ formType: 'create',
107
+ formName: 'post',
108
+ },
109
+ } as const
110
+
111
+ this.pageBuilderStateStore.setPageBuilderConfig(updatedConfig)
112
+ return
113
+ }
114
+
115
+ // Case B: formType is valid ('create' or 'update'), but formName is missing or an empty string
116
+ if (
117
+ (config.updateOrCreate &&
118
+ typeof config.updateOrCreate.formType === 'string' &&
119
+ (config.updateOrCreate.formType === 'create' ||
120
+ config.updateOrCreate.formType === 'update') &&
121
+ typeof config.updateOrCreate.formName !== 'string') ||
122
+ (typeof config.updateOrCreate.formName === 'string' &&
123
+ config.updateOrCreate.formName.length === 0)
124
+ ) {
125
+ const updatedConfig = {
126
+ ...config,
127
+ updateOrCreate: {
128
+ formType: config.updateOrCreate.formType,
129
+ formName: 'post',
130
+ },
131
+ } as const
132
+ this.pageBuilderStateStore.setPageBuilderConfig(updatedConfig)
133
+ }
134
+
135
+ // Case C: formType is missing or not a valid string like ('create' or 'update') but formName is valid string
136
+ if (
137
+ (config.updateOrCreate && typeof config.updateOrCreate.formType !== 'string') ||
138
+ (typeof config.updateOrCreate.formType === 'string' &&
139
+ config.updateOrCreate.formType !== 'create' &&
140
+ config.updateOrCreate.formType !== 'update' &&
141
+ typeof config.updateOrCreate.formName === 'string' &&
142
+ config.updateOrCreate.formName.length !== 0)
143
+ ) {
144
+ const updatedConfig = {
145
+ ...config,
146
+ updateOrCreate: {
147
+ formType: 'create',
148
+ formName: config.updateOrCreate.formName,
149
+ },
150
+ } as const
151
+
152
+ this.pageBuilderStateStore.setPageBuilderConfig(updatedConfig)
153
+ return
154
+ }
155
+
156
+ // Case D: formType exists but is not 'create' or 'update', and formName is missing or invalid
157
+ if (
158
+ config.updateOrCreate &&
159
+ typeof config.updateOrCreate.formType === 'string' &&
160
+ config.updateOrCreate.formType !== 'create' &&
161
+ config.updateOrCreate.formType !== 'update' &&
162
+ typeof config.formName !== 'string'
163
+ ) {
164
+ const updatedConfig = {
165
+ ...config,
166
+ updateOrCreate: {
167
+ formType: 'create',
168
+ formName: 'post',
169
+ },
170
+ } as const
171
+
172
+ this.pageBuilderStateStore.setPageBuilderConfig(updatedConfig)
173
+ }
174
+ }
175
+
176
+ #validateConfig(config: PageBuilderConfig): void {
177
+ const defaultConfigValues = {
178
+ updateOrCreate: {
179
+ formType: 'create',
180
+ formName: 'post',
181
+ },
182
+ } as const
183
+
184
+ // Set config for page builder if not set by user
185
+ if (!config || (config && Object.keys(config).length === 0 && config.constructor === Object)) {
186
+ this.pageBuilderStateStore.setPageBuilderConfig(defaultConfigValues)
187
+ }
188
+
189
+ if (config && Object.keys(config).length !== 0 && config.constructor === Object) {
190
+ this.#ensureUpdateOrCreateConfig(config)
191
+ }
192
+ }
193
+
194
+ /**
195
+ * Initializes the Page Builder with the provided configuration.
196
+ * Handles config validation, local storage, and sets up the builder state.
197
+ */
198
+ async startBuilder(config: PageBuilderConfig): Promise<void> {
199
+ // Show a global loading indicator while initializing
200
+ this.pageBuilderStateStore.setIsLoadingGlobal(true)
201
+
202
+ // Wait briefly to ensure UI updates and async processes settle
203
+ await this.delay(300)
204
+
205
+ // Store the provided config in the builder's state store
206
+ this.pageBuilderStateStore.setPageBuilderConfig(config)
207
+
208
+ // Validate and normalize the config (ensure required fields are present)
209
+ this.#validateConfig(config)
210
+
211
+ // Update the localStorage key name based on the config/resource
212
+ this.#updateLocalStorageItemName()
213
+
214
+ // If there is a local draft for this resource, mark it in the state
215
+ if (await this.#hasLocalDraftForUpdate()) {
216
+ this.pageBuilderStateStore.setHasLocalDraftForUpdate(true)
217
+ }
218
+
219
+ // Clean up any old localStorage items related to previous builder sessions
220
+ this.deleteOldPageBuilderLocalStorage()
221
+
222
+ // Clear any selected HTML elements in the builder UI
223
+ await this.clearHtmlSelection()
224
+
225
+ // Attach event listeners to all editable elements in the builder
226
+ await this.addListenersToEditableElements()
227
+
228
+ // Hide the global loading indicator and mark the builder as started
229
+ this.pageBuilderStateStore.setIsLoadingGlobal(false)
230
+ this.pageBuilderStateStore.setBuilderStarted(true)
99
231
  }
100
232
 
101
233
  #applyElementClassChanges(
@@ -265,7 +397,7 @@ class PageBuilderClass {
265
397
 
266
398
  handleAutoSave = async () => {
267
399
  this.startEditing()
268
- const passedConfig = this.pageBuilderStateStore.getConfigPageBuilder
400
+ const passedConfig = this.pageBuilderStateStore.getPageBuilderConfig
269
401
 
270
402
  // Check if config is set
271
403
  if (passedConfig && passedConfig.userSettings) {
@@ -280,7 +412,7 @@ class PageBuilderClass {
280
412
  try {
281
413
  this.pageBuilderStateStore.setIsSaving(true)
282
414
  await this.saveComponentsLocalStorage()
283
- await this.delay(300)
415
+ await this.delay(500)
284
416
  } catch (err) {
285
417
  console.error('Error trying auto save.', err)
286
418
  } finally {
@@ -303,7 +435,7 @@ class PageBuilderClass {
303
435
 
304
436
  handleManualSave = async () => {
305
437
  this.startEditing()
306
- const passedConfig = this.pageBuilderStateStore.getConfigPageBuilder
438
+ const passedConfig = this.pageBuilderStateStore.getPageBuilderConfig
307
439
 
308
440
  // Check if config is set
309
441
  if (passedConfig && passedConfig.userSettings) {
@@ -575,7 +707,6 @@ class PageBuilderClass {
575
707
  )
576
708
  }
577
709
 
578
- // border color, style & width / start
579
710
  handleBorderStyle(borderStyle?: string): void {
580
711
  this.#applyElementClassChanges(
581
712
  borderStyle,
@@ -611,7 +742,6 @@ class PageBuilderClass {
611
742
  this.#applyElementClassChanges(color, tailwindColors.textColorVariables, 'setTextColor')
612
743
  }
613
744
 
614
- // border radius / start
615
745
  handleBorderRadiusGlobal(borderRadiusGlobal?: string): void {
616
746
  this.#applyElementClassChanges(
617
747
  borderRadiusGlobal,
@@ -690,7 +820,7 @@ class PageBuilderClass {
690
820
  this.pageBuilderStateStore.setComponents([])
691
821
  }
692
822
 
693
- deleteSelectedComponent() {
823
+ async deleteSelectedComponent() {
694
824
  if (!this.getComponents.value || !this.getComponent.value) return
695
825
 
696
826
  // Find the index of the component to delete
@@ -706,6 +836,8 @@ class PageBuilderClass {
706
836
  // Remove the component from the array
707
837
  this.getComponents.value.splice(indexToDelete, 1)
708
838
  this.pageBuilderStateStore.setComponents(this.getComponents.value)
839
+ await nextTick()
840
+ await this.addListenersToEditableElements()
709
841
 
710
842
  this.pageBuilderStateStore.setComponent(null)
711
843
  this.pageBuilderStateStore.setElement(null)
@@ -879,16 +1011,16 @@ class PageBuilderClass {
879
1011
  .replace(/^-+|-+$/g, '') // Remove leading/trailing hyphens
880
1012
  }
881
1013
 
882
- updateLocalStorageItemName(): void {
1014
+ #updateLocalStorageItemName(): void {
883
1015
  const updateOrCreate =
884
- this.pageBuilderStateStore.getConfigPageBuilder &&
885
- this.pageBuilderStateStore.getConfigPageBuilder.updateOrCreate &&
886
- this.pageBuilderStateStore.getConfigPageBuilder.updateOrCreate.formType
1016
+ this.pageBuilderStateStore.getPageBuilderConfig &&
1017
+ this.pageBuilderStateStore.getPageBuilderConfig.updateOrCreate &&
1018
+ this.pageBuilderStateStore.getPageBuilderConfig.updateOrCreate.formType
887
1019
 
888
- const resourceData = this.pageBuilderStateStore.getConfigPageBuilder?.resourceData
1020
+ const resourceData = this.pageBuilderStateStore.getPageBuilderConfig?.resourceData
889
1021
 
890
1022
  const resourceFormName =
891
- this.pageBuilderStateStore.getConfigPageBuilder?.updateOrCreate?.formName
1023
+ this.pageBuilderStateStore.getPageBuilderConfig?.updateOrCreate?.formName
892
1024
 
893
1025
  // Logic for create resource
894
1026
  if (updateOrCreate === 'create') {
@@ -906,9 +1038,6 @@ class PageBuilderClass {
906
1038
  // Logic for create
907
1039
  // Logic for update and with resource form name
908
1040
  if (updateOrCreate === 'update') {
909
- //
910
- //
911
- //
912
1041
  if (typeof resourceFormName === 'string' && resourceFormName.length > 0) {
913
1042
  //
914
1043
  //
@@ -1053,14 +1182,16 @@ class PageBuilderClass {
1053
1182
  })
1054
1183
  })
1055
1184
 
1056
- if (this.getLocalStorageItemName.value) {
1057
- localStorage.setItem(
1058
- this.getLocalStorageItemName.value,
1059
- JSON.stringify({
1060
- pageBuilderContentSavedAt: new Date().toISOString(),
1061
- components: componentsToSave,
1062
- }),
1063
- )
1185
+ // Save to localStorage with pageBuilderContentSavedAt using the correct key
1186
+ const dataToSave = {
1187
+ components: componentsToSave,
1188
+ pageBuilderContentSavedAt: new Date().toISOString(),
1189
+ }
1190
+
1191
+ const keyForSavingFromDomToLocal = this.getLocalStorageItemName.value
1192
+
1193
+ if (keyForSavingFromDomToLocal && typeof keyForSavingFromDomToLocal === 'string') {
1194
+ localStorage.setItem(keyForSavingFromDomToLocal, JSON.stringify(dataToSave))
1064
1195
  }
1065
1196
 
1066
1197
  // No DOM mutation here!
@@ -1085,10 +1216,10 @@ class PageBuilderClass {
1085
1216
  //
1086
1217
  deleteOldPageBuilderLocalStorage(): void {
1087
1218
  if (
1088
- this.pageBuilderStateStore.getConfigPageBuilder &&
1089
- this.pageBuilderStateStore.getConfigPageBuilder.updateOrCreate &&
1090
- typeof this.pageBuilderStateStore.getConfigPageBuilder.updateOrCreate.formType === 'string' &&
1091
- this.pageBuilderStateStore.getConfigPageBuilder.updateOrCreate.formType === 'update'
1219
+ this.pageBuilderStateStore.getPageBuilderConfig &&
1220
+ this.pageBuilderStateStore.getPageBuilderConfig.updateOrCreate &&
1221
+ typeof this.pageBuilderStateStore.getPageBuilderConfig.updateOrCreate.formType === 'string' &&
1222
+ this.pageBuilderStateStore.getPageBuilderConfig.updateOrCreate.formType === 'update'
1092
1223
  ) {
1093
1224
  let oldCountLocalStorages = 0
1094
1225
  const deletedItemsLog: { Number: number; Key: string; SavedAt: string }[] = []
@@ -1137,14 +1268,14 @@ class PageBuilderClass {
1137
1268
  }
1138
1269
  }
1139
1270
 
1140
- async hasLocalDraftForUpdate(): Promise<boolean> {
1271
+ async #hasLocalDraftForUpdate(): Promise<boolean> {
1141
1272
  if (this.hasStartedEditing) return false
1142
1273
 
1143
1274
  if (
1144
- this.pageBuilderStateStore.getConfigPageBuilder &&
1145
- this.pageBuilderStateStore.getConfigPageBuilder.updateOrCreate &&
1146
- typeof this.pageBuilderStateStore.getConfigPageBuilder.updateOrCreate.formType === 'string' &&
1147
- this.pageBuilderStateStore.getConfigPageBuilder.updateOrCreate.formType === 'update'
1275
+ this.pageBuilderStateStore.getPageBuilderConfig &&
1276
+ this.pageBuilderStateStore.getPageBuilderConfig.updateOrCreate &&
1277
+ typeof this.pageBuilderStateStore.getPageBuilderConfig.updateOrCreate.formType === 'string' &&
1278
+ this.pageBuilderStateStore.getPageBuilderConfig.updateOrCreate.formType === 'update'
1148
1279
  ) {
1149
1280
  const key = this.getLocalStorageItemName.value
1150
1281
  if (typeof key === 'string') {
@@ -1152,10 +1283,13 @@ class PageBuilderClass {
1152
1283
  if (draft) {
1153
1284
  try {
1154
1285
  await this.delay(500)
1155
- const draftParsed = JSON.parse(draft)
1156
- const dbComponents = this.getComponents.value
1157
- return JSON.stringify(draftParsed.components) !== JSON.stringify(dbComponents)
1158
- } catch (e) {
1286
+
1287
+ return true
1288
+ // const dbComponents = this.getComponents.value
1289
+ // const draftParsed = JSON.parse(draft)
1290
+ // return JSON.stringify(draftParsed.components) !== JSON.stringify(dbComponents)
1291
+ } catch (err) {
1292
+ console.error('Unable to mount components to DOM.', err)
1159
1293
  return false
1160
1294
  }
1161
1295
  }
@@ -1169,34 +1303,51 @@ class PageBuilderClass {
1169
1303
  this.hasStartedEditing = true
1170
1304
  }
1171
1305
 
1306
+ //
1172
1307
  async resumeEditingForUpdate() {
1173
1308
  if (
1174
- this.pageBuilderStateStore.getConfigPageBuilder &&
1175
- this.pageBuilderStateStore.getConfigPageBuilder.updateOrCreate &&
1176
- typeof this.pageBuilderStateStore.getConfigPageBuilder.updateOrCreate.formType === 'string' &&
1177
- this.pageBuilderStateStore.getConfigPageBuilder.updateOrCreate.formType === 'update'
1309
+ this.pageBuilderStateStore.getPageBuilderConfig &&
1310
+ this.pageBuilderStateStore.getPageBuilderConfig.updateOrCreate &&
1311
+ typeof this.pageBuilderStateStore.getPageBuilderConfig.updateOrCreate.formType === 'string' &&
1312
+ this.pageBuilderStateStore.getPageBuilderConfig.updateOrCreate.formType === 'update'
1178
1313
  ) {
1179
1314
  const key = this.getLocalStorageItemName.value
1315
+
1180
1316
  if (typeof key === 'string') {
1181
- const savedCurrentDesign = localStorage.getItem(key)
1182
- if (savedCurrentDesign) {
1183
- try {
1184
- const parsed = JSON.parse(savedCurrentDesign)
1185
- if (parsed && Array.isArray(parsed.components)) {
1186
- this.pageBuilderStateStore.setComponents(parsed.components)
1187
- localStorage.removeItem(key)
1188
- await nextTick()
1189
- await this.addListenersToEditableElements()
1190
- await this.handleAutoSave()
1191
- }
1192
- } catch (e) {
1193
- console.error('Failed to parse local draft:', e)
1194
- }
1317
+ const updateDraftFromLocalStorage = localStorage.getItem(key)
1318
+
1319
+ if (typeof updateDraftFromLocalStorage === 'string') {
1320
+ this.pageBuilderStateStore.setIsResumeEditing(true)
1321
+ await delay(500)
1322
+ this.mountComponentsToDOM(updateDraftFromLocalStorage)
1323
+ this.pageBuilderStateStore.setIsResumeEditing(false)
1195
1324
  }
1196
1325
  }
1197
1326
  }
1198
1327
  }
1199
1328
 
1329
+ async restoreOriginalContent() {
1330
+ if (
1331
+ this.pageBuilderStateStore.getPageBuilderConfig &&
1332
+ this.pageBuilderStateStore.getPageBuilderConfig.updateOrCreate &&
1333
+ typeof this.pageBuilderStateStore.getPageBuilderConfig.updateOrCreate.formType === 'string' &&
1334
+ this.pageBuilderStateStore.getPageBuilderConfig.updateOrCreate.formType === 'update'
1335
+ ) {
1336
+ this.pageBuilderStateStore.setIsRestoring(true)
1337
+ await this.delay(300)
1338
+
1339
+ // Restore the original content if available
1340
+ if (this.originalComponents) {
1341
+ this.mountComponentsToDOM(this.originalComponents)
1342
+ }
1343
+
1344
+ await nextTick()
1345
+ await this.addListenersToEditableElements()
1346
+
1347
+ this.pageBuilderStateStore.setIsRestoring(false)
1348
+ }
1349
+ }
1350
+
1200
1351
  getStorageItemNameForResource(): string | null {
1201
1352
  return this.getLocalStorageItemName.value
1202
1353
  }
@@ -1217,39 +1368,56 @@ class PageBuilderClass {
1217
1368
 
1218
1369
  return false
1219
1370
  }
1220
- //
1221
- //
1222
- async updateBasePrimaryImage(): Promise<void> {
1371
+
1372
+ /**
1373
+ * Sets the image selected from the media library as the "pending" image
1374
+ * to be applied to the currently selected element in the builder.
1375
+ * This does not update the DOM immediately—call `applyPendingImageToSelectedElement` to commit.
1376
+ * @param image - The image object to be staged for application
1377
+ */
1378
+ stageImageForSelectedElement(image: ImageObject) {
1379
+ this.pageBuilderStateStore.setApplyImageToSelection(image)
1380
+ }
1381
+
1382
+ /**
1383
+ * Applies the staged image (set by `stageImageForSelectedElement`) to the currently selected element.
1384
+ * This updates the builder state and triggers an auto-save.
1385
+ * If no element is selected or no image is staged, nothing happens.
1386
+ */
1387
+ async applyPendingImageToSelectedElement(): Promise<void> {
1223
1388
  if (!this.getElement.value) return
1224
1389
 
1225
- // If no data provided, apply current image if available (new simplified usage)
1226
- if (this.getCurrentImage.value && this.getCurrentImage.value.src) {
1390
+ // Only apply if an image is staged
1391
+ if (this.getApplyImageToSelection.value && this.getApplyImageToSelection.value.src) {
1227
1392
  await this.nextTick
1228
- this.pageBuilderStateStore.setBasePrimaryImage(`${this.getCurrentImage.value.src}`)
1393
+ this.pageBuilderStateStore.setBasePrimaryImage(`${this.getApplyImageToSelection.value.src}`)
1394
+
1229
1395
  await this.handleAutoSave()
1230
1396
  }
1231
1397
  }
1232
1398
 
1233
- setBasePrimaryImageFromCurrent() {
1399
+ /**
1400
+ * Inspects the currently selected element and, if it contains exactly one <img> and no <div>,
1401
+ * sets that image's src as the base primary image in the builder state.
1402
+ * If the element does not meet these criteria, clears the base primary image.
1403
+ */
1404
+ setBasePrimaryImageFromSelectedElement() {
1234
1405
  if (!this.getElement.value) return
1235
1406
 
1236
1407
  const currentImageContainer = document.createElement('div')
1237
-
1238
1408
  currentImageContainer.innerHTML = this.getElement.value.outerHTML
1239
1409
 
1240
1410
  // Get all img and div within the current image container
1241
1411
  const imgElements = currentImageContainer.getElementsByTagName('img')
1242
1412
  const divElements = currentImageContainer.getElementsByTagName('div')
1243
1413
 
1244
- // Check if there is exactly one img and no div
1414
+ // If exactly one img and no div, set as base primary image
1245
1415
  if (imgElements.length === 1 && divElements.length === 0) {
1246
- // Return the source of the only img
1247
-
1248
1416
  this.pageBuilderStateStore.setBasePrimaryImage(imgElements[0].src)
1249
-
1250
1417
  return
1251
1418
  }
1252
1419
 
1420
+ // Otherwise, clear the base primary image
1253
1421
  this.pageBuilderStateStore.setBasePrimaryImage(null)
1254
1422
  }
1255
1423
 
@@ -1476,9 +1644,9 @@ class PageBuilderClass {
1476
1644
  * @param data - JSON string (e.g., '[{"html_code":"...","id":"123","title":"..."}]')
1477
1645
  * OR HTML string (e.g., '<section data-componentid="123">...</section>')
1478
1646
  */
1479
- setComponentsFromData(data: string): void {
1647
+ #setComponentsFromData(htmlString: string): void {
1480
1648
  // Auto-detect if input is JSON or HTML
1481
- const trimmedData = data.trim()
1649
+ const trimmedData = htmlString.trim()
1482
1650
 
1483
1651
  if (trimmedData.startsWith('[') || trimmedData.startsWith('{')) {
1484
1652
  // Looks like JSON - parse as JSON
@@ -1492,7 +1660,7 @@ class PageBuilderClass {
1492
1660
  }
1493
1661
 
1494
1662
  // Private method to parse JSON components and save pageBuilderContentSavedAt to localStorage
1495
- #parseJSONComponents(jsonData: string): void {
1663
+ async #parseJSONComponents(jsonData: string): Promise<void> {
1496
1664
  try {
1497
1665
  const parsedData = JSON.parse(jsonData)
1498
1666
  let componentsArray: ComponentObject[] = []
@@ -1544,18 +1712,8 @@ class PageBuilderClass {
1544
1712
 
1545
1713
  this.pageBuilderStateStore.setComponents(savedCurrentDesign)
1546
1714
 
1547
- // Save to localStorage with pageBuilderContentSavedAt using the correct key
1548
- const dataToSave = {
1549
- pageBuilderContentSavedAt: parsedData.pageBuilderContentSavedAt || new Date().toISOString(),
1550
- components: savedCurrentDesign,
1551
- }
1552
-
1553
- if (
1554
- this.getLocalStorageItemName.value &&
1555
- typeof this.getLocalStorageItemName.value === 'string'
1556
- ) {
1557
- localStorage.setItem(this.getLocalStorageItemName.value, JSON.stringify(dataToSave))
1558
- }
1715
+ await this.clearHtmlSelection()
1716
+ await this.addListenersToEditableElements()
1559
1717
  } catch (error) {
1560
1718
  console.error('Error parsing JSON components:', error)
1561
1719
  this.pageBuilderStateStore.setComponents([])
@@ -1602,37 +1760,60 @@ class PageBuilderClass {
1602
1760
  }
1603
1761
  }
1604
1762
 
1605
- // Load existing content from HTML when in update mode
1606
- mountComponentsToDOM(data?: string, injectCustomHTMLSections?: boolean): void {
1607
- this.pageBuilderStateStore.setComponents([])
1608
-
1609
- if (!this.pageBuilderStateStore.getConfigPageBuilder) return
1610
-
1611
- if (injectCustomHTMLSections && data !== undefined) {
1612
- this.setComponentsFromData(data)
1763
+ /**
1764
+ * Mount Components to DOM
1765
+ * @param passedData - HTML/JSON string to inject (optional)
1766
+ * @param preferLocalStorage - if true, always try localStorage first
1767
+ */
1768
+ mountComponentsToDOM(passedData: string): void {
1769
+ // Save the original content only once, in update mode, and only if passedData is provided
1770
+ // Form type Update
1771
+ if (
1772
+ this.pageBuilderStateStore.getPageBuilderConfig &&
1773
+ this.pageBuilderStateStore.getPageBuilderConfig.updateOrCreate &&
1774
+ typeof this.pageBuilderStateStore.getPageBuilderConfig.updateOrCreate.formType === 'string' &&
1775
+ this.pageBuilderStateStore.getPageBuilderConfig.updateOrCreate.formType === 'update' &&
1776
+ passedData &&
1777
+ !this.originalComponents
1778
+ ) {
1779
+ this.originalComponents = passedData
1613
1780
  }
1614
1781
 
1615
- const storedData = this.loadStoredComponentsFromStorage()
1782
+ this.pageBuilderStateStore.setComponents([])
1783
+
1784
+ if (!this.pageBuilderStateStore.getPageBuilderConfig) return
1616
1785
 
1786
+ // Form type Update
1617
1787
  if (
1618
- this.pageBuilderStateStore.getConfigPageBuilder &&
1619
- this.pageBuilderStateStore.getConfigPageBuilder.updateOrCreate &&
1620
- typeof this.pageBuilderStateStore.getConfigPageBuilder.updateOrCreate.formType === 'string' &&
1621
- this.pageBuilderStateStore.getConfigPageBuilder.updateOrCreate.formType === 'create'
1788
+ this.pageBuilderStateStore.getPageBuilderConfig &&
1789
+ this.pageBuilderStateStore.getPageBuilderConfig.updateOrCreate &&
1790
+ typeof this.pageBuilderStateStore.getPageBuilderConfig.updateOrCreate.formType === 'string' &&
1791
+ this.pageBuilderStateStore.getPageBuilderConfig.updateOrCreate.formType === 'update'
1622
1792
  ) {
1623
- if (storedData) {
1624
- this.setComponentsFromData(storedData)
1793
+ if (passedData) {
1794
+ this.#setComponentsFromData(passedData)
1795
+ return
1625
1796
  }
1626
1797
  }
1627
1798
 
1799
+ // Form type Create
1800
+ const localStorageData = this.loadStoredComponentsFromStorage()
1801
+
1628
1802
  if (
1629
- this.pageBuilderStateStore.getConfigPageBuilder &&
1630
- this.pageBuilderStateStore.getConfigPageBuilder.updateOrCreate &&
1631
- typeof this.pageBuilderStateStore.getConfigPageBuilder.updateOrCreate.formType === 'string' &&
1632
- this.pageBuilderStateStore.getConfigPageBuilder.updateOrCreate.formType === 'update'
1803
+ this.pageBuilderStateStore.getPageBuilderConfig &&
1804
+ this.pageBuilderStateStore.getPageBuilderConfig.updateOrCreate &&
1805
+ typeof this.pageBuilderStateStore.getPageBuilderConfig.updateOrCreate.formType === 'string' &&
1806
+ this.pageBuilderStateStore.getPageBuilderConfig.updateOrCreate.formType === 'create'
1633
1807
  ) {
1634
- if (data) {
1635
- this.setComponentsFromData(data)
1808
+ if (localStorageData) {
1809
+ this.#setComponentsFromData(localStorageData)
1810
+ return
1811
+ }
1812
+
1813
+ // If no localStorage, but passedData exists (for demo), use it
1814
+ if (passedData) {
1815
+ this.#setComponentsFromData(passedData)
1816
+ return
1636
1817
  }
1637
1818
  }
1638
1819
  }
@@ -1646,6 +1827,7 @@ class PageBuilderClass {
1646
1827
  }
1647
1828
 
1648
1829
  async initializeElementStyles(): Promise<void> {
1830
+ if (!this.pageBuilderStateStore.getPageBuilderConfig) return
1649
1831
  await new Promise((resolve) => requestAnimationFrame(resolve))
1650
1832
 
1651
1833
  // handle custom URL
@@ -1655,7 +1837,7 @@ class PageBuilderClass {
1655
1837
  // handle BG opacity
1656
1838
  this.handleBackgroundOpacity(undefined)
1657
1839
  // displayed image
1658
- this.setBasePrimaryImageFromCurrent()
1840
+ this.setBasePrimaryImageFromSelectedElement()
1659
1841
  // border style
1660
1842
  this.handleBorderStyle(undefined)
1661
1843
  // border width
@@ -1699,5 +1881,3 @@ class PageBuilderClass {
1699
1881
  await this.#syncCurrentClasses()
1700
1882
  }
1701
1883
  }
1702
-
1703
- export default PageBuilderClass
@@ -0,0 +1,25 @@
1
+ // builderInstance.ts
2
+ import { PageBuilderService } from '../composables/PageBuilderService'
3
+ import { sharedPageBuilderStore } from '../stores/shared-store'
4
+
5
+ // Singleton instance
6
+ let instance: PageBuilderService | null = null
7
+
8
+ // Used to create and store the single instance
9
+ export function initPageBuilder(): PageBuilderService {
10
+ if (!instance) {
11
+ const pageBuilderStateStore = sharedPageBuilderStore
12
+ instance = new PageBuilderService(pageBuilderStateStore)
13
+ }
14
+ return instance
15
+ }
16
+
17
+ // Used to access the existing instance anywhere else
18
+ export function getPageBuilder(): PageBuilderService {
19
+ if (!instance) {
20
+ throw new Error(
21
+ 'PageBuilderService has not been created. Please call createPageBuilder() first in your App.vue or setup file.',
22
+ )
23
+ }
24
+ return instance
25
+ }
package/src/css/app.css CHANGED
@@ -357,3 +357,18 @@
357
357
  @apply pbx-aspect-square pbx-border pbx-border-gray-200 pbx-cursor-pointer pbx-min-h-[1.5rem] pbx-rounded-sm;
358
358
  }
359
359
  }
360
+
361
+ /* p {
362
+ @apply pbx-font-normal lg:pbx-text-base pbx-text-base;
363
+ } */
364
+
365
+ a {
366
+ @apply pbx-text-myPrimaryLinkColor;
367
+ }
368
+
369
+ h2 {
370
+ @apply pbx-text-3xl pbx-mt-4 pbx-mb-3 pbx-font-medium;
371
+ }
372
+ h3 {
373
+ @apply pbx-text-2xl pbx-mt-4 pbx-mb-3 pbx-font-medium;
374
+ }