vue-wswg-editor 0.0.12 → 0.0.13

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 (60) hide show
  1. package/README.md +23 -8
  2. package/dist/style.css +1 -1
  3. package/dist/types/components/BlockEditorFields/BlockEditorFields.vue.d.ts +1 -0
  4. package/dist/types/components/EditorPageRenderer/EditorPageRenderer.vue.d.ts +21 -0
  5. package/dist/types/components/EmptyState/EmptyState.vue.d.ts +2 -8
  6. package/dist/types/components/IframePreview/IframePreview.vue.d.ts +26 -0
  7. package/dist/types/components/IframePreview/iframeContent.d.ts +9 -0
  8. package/dist/types/components/IframePreview/iframePreviewApp.d.ts +36 -0
  9. package/dist/types/components/IframePreview/messageHandler.d.ts +55 -0
  10. package/dist/types/components/IframePreview/types.d.ts +77 -0
  11. package/dist/types/components/PageBuilderSidebar/PageBuilderSidebar.vue.d.ts +2 -0
  12. package/dist/types/components/PageRenderer/PageRenderer.vue.d.ts +2 -0
  13. package/dist/types/components/PageSettings/PageSettings.vue.d.ts +2 -0
  14. package/dist/types/components/{WswgJsonEditor/WswgJsonEditor.vue.d.ts → WswgPageBuilder/WswgPageBuilder.vue.d.ts} +2 -0
  15. package/dist/types/index.d.ts +8 -2
  16. package/dist/types/util/registry.d.ts +2 -0
  17. package/dist/types/util/theme-registry.d.ts +42 -0
  18. package/dist/types/util/validation.d.ts +2 -2
  19. package/dist/vite-plugin.js +33 -29
  20. package/dist/vue-wswg-editor.es.js +2723 -1897
  21. package/package.json +1 -2
  22. package/src/assets/styles/_mixins.scss +15 -0
  23. package/src/components/AddBlockItem/AddBlockItem.vue +13 -4
  24. package/src/components/BlockBrowser/BlockBrowser.vue +5 -5
  25. package/src/components/BlockComponent/BlockComponent.vue +23 -50
  26. package/src/components/BlockEditorFieldNode/BlockEditorFieldNode.vue +12 -10
  27. package/src/components/BlockEditorFields/BlockEditorFields.vue +24 -4
  28. package/src/components/BlockRepeaterFieldNode/BlockRepeaterNode.vue +9 -4
  29. package/src/components/BrowserNavigation/BrowserNavigation.vue +1 -1
  30. package/src/components/EditorPageRenderer/EditorPageRenderer.vue +641 -0
  31. package/src/components/EmptyState/EmptyState.vue +3 -12
  32. package/src/components/IframePreview/IframePreview.vue +211 -0
  33. package/src/components/IframePreview/iframeContent.ts +210 -0
  34. package/src/components/IframePreview/iframePreviewApp.ts +221 -0
  35. package/src/components/IframePreview/messageHandler.ts +219 -0
  36. package/src/components/IframePreview/types.ts +126 -0
  37. package/src/components/PageBlockList/PageBlockList.vue +8 -6
  38. package/src/components/PageBuilderSidebar/PageBuilderSidebar.vue +5 -3
  39. package/src/components/PageRenderer/PageRenderer.vue +9 -33
  40. package/src/components/PageSettings/PageSettings.vue +10 -6
  41. package/src/components/ResizeHandle/ResizeHandle.vue +68 -10
  42. package/src/components/{WswgJsonEditor/WswgJsonEditor.test.ts → WswgPageBuilder/WswgPageBuilder.test.ts} +8 -8
  43. package/src/components/WswgPageBuilder/WswgPageBuilder.vue +375 -0
  44. package/src/index.ts +10 -2
  45. package/src/shims.d.ts +4 -0
  46. package/src/types/Theme.d.ts +15 -0
  47. package/src/util/registry.ts +2 -2
  48. package/src/util/theme-registry.ts +397 -0
  49. package/src/util/validation.ts +102 -11
  50. package/src/vite-plugin.ts +8 -4
  51. package/types/vue-wswg-editor.d.ts +4 -0
  52. package/dist/types/components/PageRenderer/blockModules.d.ts +0 -3
  53. package/dist/types/components/PageRenderer/layoutModules.d.ts +0 -3
  54. package/src/components/PageRenderer/blockModules-alternative.ts.example +0 -9
  55. package/src/components/PageRenderer/blockModules-manual.ts.example +0 -19
  56. package/src/components/PageRenderer/blockModules-runtime.ts.example +0 -23
  57. package/src/components/PageRenderer/blockModules.ts +0 -32
  58. package/src/components/PageRenderer/layoutModules.ts +0 -32
  59. package/src/components/WswgJsonEditor/WswgJsonEditor.vue +0 -595
  60. /package/dist/types/components/{WswgJsonEditor/WswgJsonEditor.test.d.ts → WswgPageBuilder/WswgPageBuilder.test.d.ts} +0 -0
@@ -1,32 +0,0 @@
1
- // Import from virtual module created by vue-wswg-editor vite plugin
2
- // Use lazy initialization to avoid circular dependency issues with createField
3
- // The import is deferred until first access, ensuring createField is available when blocks load
4
- import { type Component } from "vue";
5
- import { getModuleDefault } from "../../util/registry";
6
- import { markRaw } from "vue";
7
- let _blockModules: Record<string, Component> | null = null;
8
-
9
- export async function loadBlockModules(): Promise<Record<string, Component> | null> {
10
- if (!_blockModules) {
11
- _blockModules = {};
12
- // Lazy load virtual modules to prevent initialization order issues
13
- const { modules: discoveredModules } = await import("vue-wswg-editor:blocks");
14
- for (const [, module] of Object.entries(discoveredModules)) {
15
- let resolvedModule = module;
16
- // If module is a function (lazy-loaded), call it to get the actual module
17
- if (typeof module === "function") {
18
- resolvedModule = await module();
19
- }
20
- const component = getModuleDefault(resolvedModule);
21
- if (component && component.__name) {
22
- const blockType = component.type || component.__name;
23
- _blockModules[blockType] = markRaw(component);
24
- }
25
- }
26
- }
27
- return _blockModules;
28
- }
29
-
30
- export function getBlockModule(type: string): Component | undefined {
31
- return _blockModules?.[type];
32
- }
@@ -1,32 +0,0 @@
1
- // Import from virtual module created by vue-wswg-editor vite plugin
2
- // Use lazy initialization to avoid circular dependency issues with createField
3
- // The import is deferred until first access, ensuring createField is available when blocks load
4
- import { type Component } from "vue";
5
- import { getModuleDefault } from "../../util/registry";
6
- import { markRaw } from "vue";
7
- let _layoutModules: Record<string, Component> | null = null;
8
-
9
- export async function loadLayoutModules(): Promise<Record<string, Component> | null> {
10
- if (!_layoutModules) {
11
- _layoutModules = {};
12
- // Lazy load virtual modules to prevent initialization order issues
13
- const { modules: discoveredModules } = await import("vue-wswg-editor:layouts");
14
- for (const [, module] of Object.entries(discoveredModules)) {
15
- let resolvedModule = module;
16
- // If module is a function (lazy-loaded), call it to get the actual module
17
- if (typeof module === "function") {
18
- resolvedModule = await module();
19
- }
20
- const component = getModuleDefault(resolvedModule);
21
- if (component && component.__name) {
22
- const layoutType = component.type || component.__name;
23
- _layoutModules[layoutType] = markRaw(component);
24
- }
25
- }
26
- }
27
- return _layoutModules;
28
- }
29
-
30
- export function getLayoutModule(type: string): Component | undefined {
31
- return _layoutModules?.[type];
32
- }
@@ -1,595 +0,0 @@
1
- <template>
2
- <div class="wswg-json-editor">
3
- <slot v-if="loading" name="loading">
4
- <div class="wswg-json-editor-loading flex h-full flex-col items-center justify-center gap-4">
5
- <svg
6
- class="size-9 animate-[spin_2000ms_linear_infinite]"
7
- viewBox="0 0 24 24"
8
- fill="none"
9
- xmlns="http://www.w3.org/2000/svg"
10
- >
11
- <path
12
- d="M13 2a1 1 0 0 0-2 0v4.167a1 1 0 1 0 2 0V2ZM13 17.833a1 1 0 0 0-2 0V22a1 1 0 1 0 2 0v-4.167ZM16.834 12a1 1 0 0 1 1-1H22a1 1 0 0 1 0 2h-4.166a1 1 0 0 1-1-1ZM2 11a1 1 0 0 0 0 2h4.167a1 1 0 1 0 0-2H2ZM19.916 4.085a1 1 0 0 1 0 1.414l-2.917 2.917A1 1 0 1 1 15.585 7l2.917-2.916a1 1 0 0 1 1.414 0ZM8.415 16.999a1 1 0 0 0-1.414-1.414L4.084 18.5A1 1 0 1 0 5.5 19.916l2.916-2.917ZM15.585 15.585a1 1 0 0 1 1.414 0l2.917 2.916a1 1 0 1 1-1.414 1.415l-2.917-2.917a1 1 0 0 1 0-1.414ZM5.499 4.085a1 1 0 0 0-1.415 1.414l2.917 2.917A1 1 0 0 0 8.415 7L5.5 4.085Z"
13
- fill="#000000"
14
- />
15
- </svg>
16
- <span>Loading</span>
17
- </div>
18
- </slot>
19
- <!-- WYSIWYG editor -->
20
- <div v-else class="wswg-json-editor-body">
21
- <!-- Page preview -->
22
- <div class="wswg-json-editor-preview">
23
- <div
24
- class="mx-auto flex h-full flex-col transition-all duration-300"
25
- :class="{ 'w-full': editorViewport === 'desktop', 'w-96': editorViewport === 'mobile' }"
26
- >
27
- <BrowserNavigation v-if="showBrowserBar" class="browser-navigation-bar" :url="url" />
28
- <div
29
- v-if="pageLayout"
30
- id="page-viewport"
31
- class="relative overflow-hidden rounded-b-lg bg-white"
32
- :class="{ 'rounded-t-lg': !showBrowserBar }"
33
- >
34
- <component :is="pageLayout" v-bind="pageData?.[settingsKey]">
35
- <template #default>
36
- <!-- No blocks found -->
37
- <EmptyState
38
- v-if="!pageData?.[blocksKey]?.length"
39
- v-model:showAddBlockMenu="showAddBlockMenu"
40
- :editable="editable"
41
- @block-added="handleAddBlock"
42
- />
43
- <!-- Blocks found -->
44
- <div v-else id="page-blocks-wrapper" ref="pageBlocksWrapperRef">
45
- <div v-for="(block, blockIndex) in pageData[blocksKey]" :key="block.id">
46
- <BlockComponent
47
- :block="block"
48
- :blockIndex="blockIndex"
49
- :activeBlock="activeBlock"
50
- :editable="editable"
51
- :hoveredBlockId="hoveredBlockId"
52
- @hover-block="setHoveredBlockId"
53
- @click-block="handleBlockClick"
54
- />
55
- </div>
56
- </div>
57
- </template>
58
- </component>
59
- </div>
60
- <!-- No layout found -->
61
- <div v-else class="rounded-b-lg bg-white px-5 py-12 md:py-20">
62
- <div class="mx-auto max-w-md pb-7 text-center">
63
- <svg
64
- xmlns="http://www.w3.org/2000/svg"
65
- fill="none"
66
- viewBox="0 0 24 24"
67
- stroke-width="1.5"
68
- stroke="currentColor"
69
- class="mx-auto size-20 text-gray-400"
70
- >
71
- <path
72
- stroke-linecap="round"
73
- stroke-linejoin="round"
74
- d="M12 9v3.75m9-.75a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9 3.75h.008v.008H12v-.008Z"
75
- ></path>
76
- </svg>
77
-
78
- <h2 class="text-2xl font-bold text-gray-900">No layout found</h2>
79
-
80
- <p class="mt-4 text-pretty text-gray-700">
81
- Get started by creating your first layout. It only takes a few seconds.
82
- </p>
83
-
84
- <p class="mt-6 text-sm text-gray-700">
85
- <a href="#" class="underline hover:text-gray-900">Learn how</a> or
86
- <a href="#" class="underline hover:text-gray-900">view examples</a>
87
- </p>
88
- </div>
89
- </div>
90
- </div>
91
- </div>
92
-
93
- <!-- Resizable divider -->
94
- <ResizeHandle @sidebar-width="handleSidebarWidth" />
95
-
96
- <!-- Sidebar -->
97
- <div
98
- id="page-builder-sidebar"
99
- class="page-builder-sidebar-wrapper bg-white"
100
- :style="{ width: sidebarWidth + 'px' }"
101
- >
102
- <PageBuilderSidebar
103
- v-model="pageData"
104
- v-model:activeBlock="activeBlock"
105
- v-model:hoveredBlockId="hoveredBlockId"
106
- v-model:showPageSettings="showPageSettings"
107
- v-model:showAddBlockMenu="showAddBlockMenu"
108
- v-model:editorViewport="editorViewport"
109
- :hasPageSettings="hasPageSettings"
110
- :editable="editable"
111
- :blocksKey="blocksKey"
112
- :settingsKey="settingsKey"
113
- />
114
- </div>
115
- </div>
116
- </div>
117
- </template>
118
-
119
- <script setup lang="ts">
120
- import {
121
- ref,
122
- shallowRef,
123
- withDefaults,
124
- watch,
125
- type Component,
126
- onBeforeMount,
127
- onMounted,
128
- nextTick,
129
- computed,
130
- } from "vue";
131
- import ResizeHandle from "../ResizeHandle/ResizeHandle.vue";
132
- import PageBuilderSidebar from "../PageBuilderSidebar/PageBuilderSidebar.vue";
133
- import BrowserNavigation from "../BrowserNavigation/BrowserNavigation.vue";
134
- import BlockComponent from "../BlockComponent/BlockComponent.vue";
135
- import EmptyState from "../EmptyState/EmptyState.vue";
136
- import { getBlockComponent, getLayouts, initialiseRegistry } from "../../util/registry";
137
- import type { Block } from "../../types/Block";
138
- import Sortable from "sortablejs";
139
-
140
- const props = withDefaults(
141
- defineProps<{
142
- editable?: boolean;
143
- loading?: boolean;
144
- url?: string;
145
- showBrowserBar?: boolean;
146
- blocksKey?: string;
147
- settingsKey?: string;
148
- defaultBlockMargin?: "none" | "small" | "medium" | "large";
149
- }>(),
150
- {
151
- editable: false,
152
- loading: false,
153
- url: "",
154
- showBrowserBar: false,
155
- blocksKey: "blocks",
156
- settingsKey: "settings",
157
- defaultBlockMargin: "none",
158
- }
159
- );
160
-
161
- const editorViewport = ref<"desktop" | "mobile">("desktop");
162
- const showPageSettings = ref(false);
163
- const showAddBlockMenu = ref(false);
164
- const activeBlock = ref<any>(null);
165
- const isSorting = ref(false);
166
- const hoveredBlockId = ref<string | null>(null);
167
- const sidebarWidth = ref(380); // Default sidebar width (380px)
168
- const sortableInstance = ref<InstanceType<typeof Sortable> | null>(null);
169
- const pageBlocksWrapperRef = ref<HTMLElement | null>(null);
170
- const isInitializingSortable = ref(false); // Prevent concurrent initialization
171
-
172
- // Model value for the JSON page data
173
- const pageData = defineModel<Record<string, any>>();
174
-
175
- // Layout component - dynamically imported from page-builder directory
176
- // Using shallowRef to avoid making the component reactive (performance optimization)
177
- const pageLayout = shallowRef<Component | undefined>(undefined);
178
-
179
- // Apply the sidebar width from the resize handle
180
- function handleSidebarWidth(width: number) {
181
- sidebarWidth.value = width;
182
- }
183
-
184
- // Load layout component dynamically from @page-builder/layout/
185
- async function loadLayout(layoutName: string | undefined) {
186
- // Use "default" layout if no layout is provided
187
- const layout = layoutName || "default";
188
- try {
189
- const availableLayouts = getLayouts();
190
- const layoutModule = availableLayouts[layout];
191
- pageLayout.value = layoutModule;
192
- // Don't initialize Sortable here - let the watcher handle it
193
- // The watcher will react to pageLayout.value changing and check all conditions
194
- } catch (error) {
195
- // Layout doesn't exist, return undefined
196
- console.warn(`Layout "${layout}" not found in @page-builder/layout/`, error);
197
- pageLayout.value = undefined;
198
- }
199
- }
200
-
201
- // Check if the page has settings
202
- const hasPageSettings = computed(() => {
203
- // Show page settings if there are multiple layouts to choose from
204
- // Note: This computed might run before registry is initialized, so we check if layouts exist
205
- const layouts = getLayouts();
206
- if (Object.keys(layouts).length > 1) {
207
- return true;
208
- }
209
-
210
- // If the layout has settings
211
- if (pageData.value?.[props.settingsKey]) {
212
- if (Object.keys(pageData.value?.[props.settingsKey]).length > 0) {
213
- return true;
214
- }
215
- }
216
-
217
- return false;
218
- });
219
-
220
- function handleBlockClick(block: Block | null) {
221
- activeBlock.value = block;
222
- showPageSettings.value = false;
223
- hoveredBlockId.value = null;
224
- showAddBlockMenu.value = false;
225
- }
226
-
227
- async function handleAddBlock(blockType: string, insertIndex?: number) {
228
- if (!pageData.value) return;
229
-
230
- // Ensure blocks array exists
231
- if (!pageData.value[props.blocksKey]) {
232
- pageData.value[props.blocksKey] = [];
233
- }
234
-
235
- // Record if this is an add from the EmptyState
236
- const isAddFromEmptyState = insertIndex === undefined;
237
-
238
- // Create a new block object
239
- const newBlock = {
240
- id: `block-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
241
- type: blockType,
242
- margin: props.defaultBlockMargin
243
- ? { top: props.defaultBlockMargin, bottom: props.defaultBlockMargin }
244
- : undefined,
245
- };
246
- // Get the default prop values from the block component
247
- const blockComponent = getBlockComponent(blockType);
248
- if (blockComponent?.props) {
249
- // loop props and set their default value
250
- Object.entries(blockComponent.props).forEach(([key, value]: [string, any]) => {
251
- if (value.default) {
252
- if (typeof value.default === "function") {
253
- newBlock[key as keyof typeof newBlock] = value.default();
254
- } else {
255
- newBlock[key as keyof typeof newBlock] = value.default;
256
- }
257
- }
258
- });
259
- }
260
-
261
- // Add the new block at the specified index or at the end
262
- if (insertIndex !== undefined) {
263
- pageData.value[props.blocksKey].splice(insertIndex, 0, newBlock);
264
- } else {
265
- pageData.value[props.blocksKey].push(newBlock);
266
- // Set the new block as active only when adding to the end (from EmptyState)
267
- activeBlock.value = newBlock;
268
- showAddBlockMenu.value = false;
269
- }
270
-
271
- // finally, if this is an add from the EmptyState, we need to trigger a re-render and initialise the sortable
272
- if (isAddFromEmptyState) {
273
- await nextTick();
274
- await initSortable();
275
- }
276
-
277
- return newBlock;
278
- }
279
-
280
- function setHoveredBlockId(id: string | null) {
281
- if (isSorting.value) return;
282
- hoveredBlockId.value = id;
283
- }
284
-
285
- // Watch for changes in pageData layout and load the corresponding component
286
- watch(
287
- () => pageData.value?.[props.settingsKey]?.layout,
288
- (layoutName) => {
289
- loadLayout(layoutName);
290
- },
291
- { immediate: false }
292
- );
293
-
294
- // Watch for activeBlock changes and scroll to the corresponding block in the preview
295
- watch(
296
- () => activeBlock.value?.id,
297
- async (blockId) => {
298
- if (!blockId) return;
299
-
300
- // Wait for DOM to update
301
- await nextTick();
302
-
303
- // Find the block element by data-block-id
304
- const blockElement = document.querySelector(`[data-block-id="${blockId}"]`) as HTMLElement;
305
- if (!blockElement) return;
306
-
307
- // Scroll the block into view, centered in the scrollable container
308
- blockElement.scrollIntoView({
309
- behavior: "smooth",
310
- block: "center",
311
- inline: "nearest",
312
- });
313
- }
314
- );
315
-
316
- // Watch for the pageBlocksWrapperRef element to become available
317
- watch(
318
- pageBlocksWrapperRef,
319
- async (element) => {
320
- if (element) {
321
- // Element is now in the DOM, initialize Sortable
322
- await nextTick(); // One more tick to ensure it's fully rendered
323
- await initSortable();
324
- }
325
- },
326
- { immediate: true }
327
- );
328
-
329
- // Also watch for both pageLayout and blocks array changes to trigger re-initialization
330
- // But only if Sortable isn't already initialized or currently sorting
331
- watch(
332
- () => [pageLayout.value, pageData.value?.[props.blocksKey]?.length],
333
- async ([layout, blockCount], [oldLayout, oldBlockCount]) => {
334
- // Skip if already initializing or sorting
335
- if (isInitializingSortable.value || isSorting.value) {
336
- return;
337
- }
338
-
339
- // Only initialize Sortable if both layout exists and blocks exist
340
- // And only if the layout changed or blocks were added/removed (not just reordered)
341
- const layoutChanged = layout !== oldLayout;
342
- const blocksChanged = blockCount !== oldBlockCount;
343
- const shouldInitialize = layout && blockCount && blockCount > 0 && !props.loading && pageBlocksWrapperRef.value;
344
-
345
- // Only re-initialize if layout changed or blocks were added/removed
346
- // Don't re-initialize on reorder (same count, just different order)
347
- if (shouldInitialize && (layoutChanged || blocksChanged || !sortableInstance.value)) {
348
- // Wait for the dynamic component to fully mount and render
349
- await nextTick();
350
- await nextTick();
351
- await initSortable();
352
- }
353
- },
354
- { immediate: false }
355
- );
356
-
357
- async function initSortable() {
358
- // Don't re-initialize if already sorting or initializing (prevents conflicts)
359
- if (isSorting.value || isInitializingSortable.value) {
360
- return;
361
- }
362
-
363
- // Check prerequisites before attempting to initialize
364
- if (props.loading) {
365
- console.warn("Cannot initialize Sortable: component is still loading");
366
- return;
367
- }
368
-
369
- if (!pageLayout.value) {
370
- console.warn("Cannot initialize Sortable: pageLayout is not set");
371
- return;
372
- }
373
-
374
- const blockCount = pageData.value?.[props.blocksKey]?.length;
375
- if (!blockCount || blockCount === 0) {
376
- console.warn("Cannot initialize Sortable: no blocks exist");
377
- return;
378
- }
379
-
380
- // Use the template ref first, fallback to getElementById
381
- const sortableBlocksWrapper = pageBlocksWrapperRef.value || document.getElementById("page-blocks-wrapper");
382
-
383
- if (!sortableBlocksWrapper) {
384
- console.warn("page-blocks-wrapper element not found. Conditions:", {
385
- loading: props.loading,
386
- hasPageLayout: !!pageLayout.value,
387
- blockCount: pageData.value?.[props.blocksKey]?.length,
388
- hasRef: !!pageBlocksWrapperRef.value,
389
- pagePreviewViewportExists: !!document.getElementById("page-viewport"),
390
- });
391
- return;
392
- }
393
-
394
- // If Sortable is already initialized and working, don't re-initialize
395
- if (sortableInstance.value) {
396
- // Check if the instance is still valid by checking if the element is still attached
397
- const { el } = sortableInstance.value;
398
- if (el && el.isConnected && el === sortableBlocksWrapper) {
399
- // Sortable is already initialized and valid, no need to re-initialize
400
- return;
401
- }
402
- // Instance exists but element is disconnected or different, destroy it
403
- try {
404
- sortableInstance.value.destroy();
405
- } catch {
406
- // Ignore errors during destruction
407
- }
408
- sortableInstance.value = null;
409
- }
410
-
411
- isInitializingSortable.value = true;
412
- try {
413
- sortableInstance.value = new Sortable(sortableBlocksWrapper, {
414
- animation: 150,
415
- ghostClass: "sortable-ghost",
416
- chosenClass: "sortable-chosen",
417
- dragClass: "sortable-drag",
418
- group: "page-blocks",
419
- forceFallback: false, // Use native HTML5 drag if available
420
- fallbackOnBody: true, // Append fallback element to body
421
- swapThreshold: 0.7, // Threshold for swap
422
- onStart: () => {
423
- isSorting.value = true;
424
- },
425
- onAdd: (event: any) => {
426
- // This fires when an item is added from another list (drag from sidebar)
427
- const { item: draggedElement, newIndex } = event;
428
- const blockType = draggedElement.getAttribute("data-block-type");
429
-
430
- if (blockType) {
431
- // Use the consolidated handleAddBlock function
432
- handleAddBlock(blockType, newIndex);
433
-
434
- // Remove the cloned HTML element that SortableJS added
435
- draggedElement.remove();
436
-
437
- // Force Vue to re-render by triggering reactivity
438
- nextTick(() => {
439
- // The component will re-render with the new block data
440
- });
441
- }
442
- },
443
- onEnd: async (event: any) => {
444
- const { oldIndex, newIndex } = event;
445
-
446
- // Only handle reordering if this wasn't an add operation (oldIndex will be null for adds)
447
- if (
448
- oldIndex !== null &&
449
- oldIndex !== undefined &&
450
- newIndex !== null &&
451
- newIndex !== undefined &&
452
- oldIndex !== newIndex &&
453
- pageData.value?.[props.blocksKey]
454
- ) {
455
- // Wait for SortableJS to finish its DOM cleanup before we modify the array
456
- await nextTick();
457
-
458
- // Get the block data from the DOM element before Vue re-renders
459
- const movedBlock = pageData.value[props.blocksKey][oldIndex];
460
- if (movedBlock) {
461
- // Update the array - this will trigger Vue to re-render
462
- pageData.value[props.blocksKey].splice(oldIndex, 1);
463
- pageData.value[props.blocksKey].splice(newIndex, 0, movedBlock);
464
- }
465
- }
466
-
467
- // Reset sorting state after a small delay to ensure DOM is stable
468
- await nextTick();
469
- isSorting.value = false;
470
- },
471
- });
472
- } finally {
473
- isInitializingSortable.value = false;
474
- }
475
- }
476
-
477
- onBeforeMount(async () => {
478
- // Initialize the registry first
479
- await initialiseRegistry();
480
-
481
- if (!pageData.value) {
482
- pageData.value = {};
483
- }
484
-
485
- if (!pageData.value?.[props.settingsKey]) {
486
- if (pageData.value) {
487
- pageData.value[props.settingsKey] = {};
488
- }
489
- }
490
-
491
- if (!pageData.value?.[props.settingsKey]?.layout) {
492
- if (pageData.value && pageData.value[props.settingsKey]) {
493
- pageData.value[props.settingsKey].layout = "default";
494
- }
495
- }
496
-
497
- // Sanitise the layout data (must be a valid layout name string)
498
- if (pageData.value?.[props.settingsKey]?.layout) {
499
- const layouts = getLayouts();
500
- const settings = pageData.value?.[props.settingsKey];
501
- if (settings) {
502
- if (typeof settings.layout !== "string") {
503
- settings.layout = "default";
504
- } else if (!layouts[settings.layout]) {
505
- settings.layout = "default";
506
- }
507
- }
508
- }
509
-
510
- if (!pageData.value?.[props.blocksKey]) {
511
- if (pageData.value) {
512
- pageData.value[props.blocksKey] = [];
513
- }
514
- }
515
-
516
- loadLayout(pageData.value?.[props.settingsKey]?.layout);
517
- });
518
-
519
- // Initialize Sortable after component is mounted - watcher will handle the initialization
520
- // This is just a fallback in case the watcher didn't fire
521
- onMounted(async () => {
522
- await nextTick();
523
- await nextTick();
524
- // Trigger the watcher by checking conditions
525
- // The watcher will handle initialization if all conditions are met
526
- if (pageData.value?.[props.blocksKey]?.length && pageLayout.value && !props.loading) {
527
- await nextTick();
528
- await initSortable();
529
- }
530
- });
531
- </script>
532
-
533
- <style lang="scss">
534
- $editor-background-color: #6a6a6a;
535
-
536
- .wswg-json-editor {
537
- --editor-height: calc(100vh);
538
- --editor-bg-color: #6a6a6a;
539
-
540
- position: relative;
541
- width: 100%;
542
- max-width: 100vw;
543
- height: var(--editor-height);
544
- overflow-y: auto;
545
-
546
- &-loading {
547
- display: flex;
548
- align-items: center;
549
- justify-content: center;
550
- }
551
-
552
- &-body {
553
- display: flex;
554
- width: 100%;
555
- background-color: var(--editor-bg-color, $editor-background-color);
556
- }
557
-
558
- &-preview {
559
- position: relative;
560
- display: flex;
561
- flex: 1;
562
- flex-grow: 1;
563
- flex-shrink: 0;
564
- flex-direction: column;
565
- height: 100%;
566
- min-height: 0;
567
- padding: 1.5rem;
568
-
569
- .browser-navigation-bar {
570
- position: sticky;
571
- top: 1.5rem;
572
- z-index: 30;
573
-
574
- &::before {
575
- position: absolute;
576
- top: -1.5rem;
577
- left: 0;
578
- z-index: -1;
579
- width: 100%;
580
- height: 100%;
581
- content: "";
582
- background-color: var(--editor-bg-color, $editor-background-color);
583
- }
584
- }
585
- }
586
-
587
- .page-builder-sidebar-wrapper {
588
- position: sticky;
589
- top: 0;
590
- z-index: 12;
591
- height: var(--editor-height);
592
- overflow-y: auto;
593
- }
594
- }
595
- </style>