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
@@ -0,0 +1,219 @@
1
+ import type {
2
+ IframeMessage,
3
+ ParentMessage,
4
+ UpdatePageDataMessage,
5
+ UpdateHTMLMessage,
6
+ SetActiveBlockMessage,
7
+ SetHoveredBlockMessage,
8
+ SetSettingsOpenMessage,
9
+ SetViewportMessage,
10
+ ScrollToBlockMessage,
11
+ } from "./types";
12
+ import type { Block } from "../../types/Block";
13
+
14
+ /**
15
+ * Serialize data for postMessage (handles Vue reactive proxies)
16
+ */
17
+ function serializeForPostMessage(data: any): any {
18
+ try {
19
+ return JSON.parse(JSON.stringify(data));
20
+ } catch (error) {
21
+ console.warn("Failed to serialize data for postMessage:", error);
22
+ return undefined;
23
+ }
24
+ }
25
+
26
+ /**
27
+ * Send a message to the iframe
28
+ */
29
+ export function sendToIframe(iframe: HTMLIFrameElement | null, message: IframeMessage): void {
30
+ if (!iframe || !iframe.contentWindow) {
31
+ console.warn("Cannot send message: iframe not ready");
32
+ return;
33
+ }
34
+
35
+ // Serialize message to handle Vue reactive proxies
36
+ const serializedMessage = serializeForPostMessage(message) as IframeMessage;
37
+ if (!serializedMessage) {
38
+ console.error("Failed to serialize message for postMessage");
39
+ return;
40
+ }
41
+
42
+ // Security: Only send to same origin
43
+ try {
44
+ iframe.contentWindow.postMessage(serializedMessage, "*");
45
+ } catch (error) {
46
+ console.error("Error sending message to iframe:", error);
47
+ }
48
+ }
49
+
50
+ /**
51
+ * Send page data update to iframe
52
+ */
53
+ export function sendPageDataUpdate(
54
+ iframe: HTMLIFrameElement | null,
55
+ pageData: Record<string, any>,
56
+ blocksKey: string,
57
+ settingsKey: string,
58
+ theme: string
59
+ ): void {
60
+ // Serialize pageData to avoid Vue reactive proxy issues
61
+ const serializedPageData = serializeForPostMessage(pageData);
62
+ const message: UpdatePageDataMessage = {
63
+ type: "UPDATE_PAGE_DATA",
64
+ pageData: serializedPageData,
65
+ blocksKey,
66
+ settingsKey,
67
+ theme,
68
+ };
69
+ sendToIframe(iframe, message);
70
+ }
71
+
72
+ /**
73
+ * Send rendered HTML to iframe
74
+ */
75
+ export function sendHTMLUpdate(
76
+ iframe: HTMLIFrameElement | null,
77
+ html: string,
78
+ pageData?: Record<string, any>,
79
+ blocksKey?: string,
80
+ settingsKey?: string
81
+ ): void {
82
+ // Serialize pageData to avoid Vue reactive proxy issues
83
+ const serializedPageData = pageData ? serializeForPostMessage(pageData) : undefined;
84
+ const message: UpdateHTMLMessage = {
85
+ type: "UPDATE_HTML",
86
+ html,
87
+ pageData: serializedPageData,
88
+ blocksKey,
89
+ settingsKey,
90
+ };
91
+ sendToIframe(iframe, message);
92
+ }
93
+
94
+ /**
95
+ * Send active block update to iframe
96
+ */
97
+ export function sendActiveBlock(iframe: HTMLIFrameElement | null, block: Block | null): void {
98
+ const message: SetActiveBlockMessage = {
99
+ type: "SET_ACTIVE_BLOCK",
100
+ block,
101
+ };
102
+ sendToIframe(iframe, message);
103
+ }
104
+
105
+ /**
106
+ * Send hovered block update to iframe
107
+ */
108
+ export function sendHoveredBlock(iframe: HTMLIFrameElement | null, blockId: string | null): void {
109
+ const message: SetHoveredBlockMessage = {
110
+ type: "SET_HOVERED_BLOCK",
111
+ blockId,
112
+ };
113
+ sendToIframe(iframe, message);
114
+ }
115
+
116
+ /**
117
+ * Send settings open state update to iframe
118
+ */
119
+ export function sendSettingsOpen(iframe: HTMLIFrameElement | null, settingsOpen: boolean): void {
120
+ const message: SetSettingsOpenMessage = {
121
+ type: "SET_SETTINGS_OPEN",
122
+ settingsOpen,
123
+ };
124
+ sendToIframe(iframe, message);
125
+ }
126
+
127
+ /**
128
+ * Send viewport update to iframe
129
+ */
130
+ export function sendViewport(iframe: HTMLIFrameElement | null, viewport: "desktop" | "mobile"): void {
131
+ const message: SetViewportMessage = {
132
+ type: "SET_VIEWPORT",
133
+ viewport,
134
+ };
135
+ sendToIframe(iframe, message);
136
+ }
137
+
138
+ /**
139
+ * Send scroll to block command to iframe
140
+ */
141
+ export function sendScrollToBlock(iframe: HTMLIFrameElement | null, blockId: string): void {
142
+ const message: ScrollToBlockMessage = {
143
+ type: "SCROLL_TO_BLOCK",
144
+ blockId,
145
+ };
146
+ sendToIframe(iframe, message);
147
+ }
148
+
149
+ /**
150
+ * Validate incoming message from iframe
151
+ */
152
+ export function isValidParentMessage(message: any): message is ParentMessage {
153
+ if (!message || typeof message !== "object" || !message.type) {
154
+ return false;
155
+ }
156
+
157
+ const validTypes: string[] = [
158
+ "BLOCK_CLICK",
159
+ "BLOCK_HOVER",
160
+ "BLOCK_REORDER",
161
+ "BLOCK_ADD",
162
+ "IFRAME_READY",
163
+ "BLOCK_ELEMENT_POSITION",
164
+ "CLICK_PARTIAL",
165
+ ];
166
+
167
+ return validTypes.includes(message.type);
168
+ }
169
+
170
+ /**
171
+ * Handle incoming message from iframe
172
+ */
173
+ export function handleIframeMessage(
174
+ event: MessageEvent,
175
+ callbacks: {
176
+ onBlockClick?: (blockId: string, block: any) => void;
177
+ onBlockHover?: (blockId: string | null) => void;
178
+ onBlockReorder?: (oldIndex: number, newIndex: number) => void;
179
+ onBlockAdd?: (blockType: string, index: number) => void;
180
+ onPartialClick?: (partialValue: string) => void;
181
+ onIframeReady?: () => void;
182
+ onBlockPosition?: (
183
+ blockId: string,
184
+ position: { top: number; left: number; width: number; height: number }
185
+ ) => void;
186
+ }
187
+ ): void {
188
+ // Security: Validate origin (in production, check against expected origin)
189
+ // For now, we'll accept messages from any origin since we're using blob URLs
190
+ if (!isValidParentMessage(event.data)) {
191
+ return;
192
+ }
193
+
194
+ const message = event.data as ParentMessage;
195
+
196
+ switch (message.type) {
197
+ case "BLOCK_CLICK":
198
+ callbacks.onBlockClick?.(message.blockId, message.block);
199
+ break;
200
+ case "BLOCK_HOVER":
201
+ callbacks.onBlockHover?.(message.blockId);
202
+ break;
203
+ case "BLOCK_REORDER":
204
+ callbacks.onBlockReorder?.(message.oldIndex, message.newIndex);
205
+ break;
206
+ case "BLOCK_ADD":
207
+ callbacks.onBlockAdd?.(message.blockType, message.index);
208
+ break;
209
+ case "CLICK_PARTIAL":
210
+ callbacks.onPartialClick?.(message.partial);
211
+ break;
212
+ case "IFRAME_READY":
213
+ callbacks.onIframeReady?.();
214
+ break;
215
+ case "BLOCK_ELEMENT_POSITION":
216
+ callbacks.onBlockPosition?.(message.blockId, message.position);
217
+ break;
218
+ }
219
+ }
@@ -0,0 +1,126 @@
1
+ // Message types for parent ↔ iframe communication
2
+
3
+ import type { Block } from "../../types/Block";
4
+
5
+ export type MessageType =
6
+ | "UPDATE_PAGE_DATA"
7
+ | "UPDATE_HTML"
8
+ | "SET_ACTIVE_BLOCK"
9
+ | "SET_HOVERED_BLOCK"
10
+ | "SET_SETTINGS_OPEN"
11
+ | "SET_VIEWPORT"
12
+ | "CLICK_PARTIAL"
13
+ | "SCROLL_TO_BLOCK"
14
+ | "BLOCK_CLICK"
15
+ | "BLOCK_HOVER"
16
+ | "BLOCK_REORDER"
17
+ | "BLOCK_ADD"
18
+ | "IFRAME_READY"
19
+ | "BLOCK_ELEMENT_POSITION";
20
+
21
+ export interface BaseMessage {
22
+ type: MessageType;
23
+ }
24
+
25
+ export interface UpdatePageDataMessage extends BaseMessage {
26
+ type: "UPDATE_PAGE_DATA";
27
+ pageData: Record<string, any>;
28
+ blocksKey: string;
29
+ settingsKey: string;
30
+ theme: string;
31
+ }
32
+
33
+ export interface UpdateHTMLMessage extends BaseMessage {
34
+ type: "UPDATE_HTML";
35
+ html: string;
36
+ pageData?: Record<string, any>;
37
+ blocksKey?: string;
38
+ settingsKey?: string;
39
+ }
40
+
41
+ export interface SetActiveBlockMessage extends BaseMessage {
42
+ type: "SET_ACTIVE_BLOCK";
43
+ block: Block | null;
44
+ }
45
+
46
+ export interface SetHoveredBlockMessage extends BaseMessage {
47
+ type: "SET_HOVERED_BLOCK";
48
+ blockId: string | null;
49
+ }
50
+
51
+ export interface SetSettingsOpenMessage extends BaseMessage {
52
+ type: "SET_SETTINGS_OPEN";
53
+ settingsOpen: boolean;
54
+ }
55
+
56
+ export interface SetViewportMessage extends BaseMessage {
57
+ type: "SET_VIEWPORT";
58
+ viewport: "desktop" | "mobile";
59
+ }
60
+
61
+ export interface PartialClickMessage extends BaseMessage {
62
+ type: "CLICK_PARTIAL";
63
+ partial: string;
64
+ }
65
+
66
+ export interface ScrollToBlockMessage extends BaseMessage {
67
+ type: "SCROLL_TO_BLOCK";
68
+ blockId: string;
69
+ }
70
+
71
+ export interface BlockClickMessage extends BaseMessage {
72
+ type: "BLOCK_CLICK";
73
+ blockId: string;
74
+ block: any;
75
+ }
76
+
77
+ export interface BlockHoverMessage extends BaseMessage {
78
+ type: "BLOCK_HOVER";
79
+ blockId: string | null;
80
+ }
81
+
82
+ export interface BlockReorderMessage extends BaseMessage {
83
+ type: "BLOCK_REORDER";
84
+ oldIndex: number;
85
+ newIndex: number;
86
+ }
87
+
88
+ export interface BlockAddMessage extends BaseMessage {
89
+ type: "BLOCK_ADD";
90
+ blockType: string;
91
+ index: number;
92
+ }
93
+
94
+ export interface IframeReadyMessage extends BaseMessage {
95
+ type: "IFRAME_READY";
96
+ }
97
+
98
+ export interface BlockElementPositionMessage extends BaseMessage {
99
+ type: "BLOCK_ELEMENT_POSITION";
100
+ blockId: string;
101
+ position: {
102
+ top: number;
103
+ left: number;
104
+ width: number;
105
+ height: number;
106
+ };
107
+ }
108
+
109
+ export type IframeMessage =
110
+ | UpdatePageDataMessage
111
+ | UpdateHTMLMessage
112
+ | SetActiveBlockMessage
113
+ | SetHoveredBlockMessage
114
+ | SetSettingsOpenMessage
115
+ | SetViewportMessage
116
+ | ScrollToBlockMessage
117
+ | PartialClickMessage;
118
+
119
+ export type ParentMessage =
120
+ | BlockClickMessage
121
+ | BlockHoverMessage
122
+ | BlockReorderMessage
123
+ | BlockAddMessage
124
+ | IframeReadyMessage
125
+ | BlockElementPositionMessage
126
+ | PartialClickMessage;
@@ -4,6 +4,7 @@
4
4
  <div
5
5
  v-for="block in pageBlocks"
6
6
  :key="block.id"
7
+ data-prevent-drop="true"
7
8
  :class="{ 'bg-blue-100 text-blue-600': hoveredBlockId === block.id }"
8
9
  class="block-item -mx-2.5 flex cursor-pointer items-center gap-1 rounded-md p-2.5 text-sm text-neutral-900"
9
10
  @mouseenter="setHoveredBlockId(block.id)"
@@ -24,9 +25,9 @@
24
25
 
25
26
  <script setup lang="ts">
26
27
  import { computed, defineEmits, onMounted, ref } from "vue";
27
- import { pageBuilderBlocks } from "../../util/registry";
28
+ import { getBlock } from "../../util/theme-registry";
28
29
  import type { Block } from "../../types/Block";
29
- import { toCamelCase, toNiceName } from "../../util/helpers";
30
+ import { toNiceName } from "../../util/helpers";
30
31
  import Sortable from "sortablejs";
31
32
 
32
33
  const emit = defineEmits<{
@@ -45,9 +46,12 @@ const pageBlocks = computed(() => {
45
46
  if (!pageData.value?.[props.blocksKey]) return [];
46
47
  // loop through pageData[blocksKey] and get the block data from registry or return a default block data
47
48
  return pageData.value[props.blocksKey].map((block: any) => {
48
- if (pageBuilderBlocks.value[toCamelCase(block.type)]) {
49
+ // Find the corresponding block in the registry
50
+ const registryBlock = getBlock(block.type);
51
+
52
+ if (registryBlock) {
49
53
  return {
50
- ...pageBuilderBlocks.value[toCamelCase(block.type)],
54
+ ...registryBlock,
51
55
  ...block,
52
56
  };
53
57
  }
@@ -56,8 +60,6 @@ const pageBlocks = computed(() => {
56
60
  id: block.id,
57
61
  name: block.type,
58
62
  label: toNiceName(block.type),
59
- icon: "question-mark",
60
- thumbnail: "https://via.placeholder.com/150",
61
63
  };
62
64
  });
63
65
  });
@@ -14,6 +14,7 @@
14
14
  v-model="pageData"
15
15
  :editable="editable"
16
16
  :settingsKey="settingsKey"
17
+ :activeTab="activeSettingsTab"
17
18
  @close="showPageSettings = false"
18
19
  />
19
20
  <!-- Active section-->
@@ -90,9 +91,8 @@
90
91
 
91
92
  <script setup lang="ts">
92
93
  import { computed } from "vue";
93
- import { pageBuilderBlocks } from "../../util/registry";
94
+ import { getBlock } from "../../util/theme-registry";
94
95
  import type { Block } from "../../types/Block";
95
- import { toCamelCase } from "../../util/helpers";
96
96
  import BlockBrowser from "../BlockBrowser/BlockBrowser.vue";
97
97
  import BlockEditorFields from "../BlockEditorFields/BlockEditorFields.vue";
98
98
  import PageBlockList from "../PageBlockList/PageBlockList.vue";
@@ -114,12 +114,14 @@ const props = withDefaults(
114
114
  blocksKey?: string;
115
115
  settingsKey?: string;
116
116
  hasPageSettings?: boolean;
117
+ activeSettingsTab?: string;
117
118
  }>(),
118
119
  {
119
120
  editable: true,
120
121
  blocksKey: "blocks",
121
122
  settingsKey: "settings",
122
123
  hasPageSettings: false,
124
+ activeSettingsTab: undefined,
123
125
  }
124
126
  );
125
127
 
@@ -136,7 +138,7 @@ const computedActiveBlock = computed(() => {
136
138
  }
137
139
 
138
140
  // Find the corresponding block in the registry
139
- const registryBlock = pageBuilderBlocks.value[toCamelCase(blockType)];
141
+ const registryBlock = getBlock(blockType);
140
142
 
141
143
  if (!registryBlock) {
142
144
  // If no registry block found, return activeBlock as-is
@@ -24,18 +24,15 @@
24
24
  >
25
25
  <component :is="getBlock(block.type)" v-bind="block" :key="`block-${block.id}`" />
26
26
  </div>
27
- <pre>{{ blocks }}</pre>
28
27
  </div>
29
28
  </template>
30
29
  </div>
31
30
  </template>
32
31
 
33
32
  <script setup lang="ts">
34
- import { type Component, computed, withDefaults, onBeforeMount, ref } from "vue";
35
- import { generateNameVariations } from "../../util/helpers";
36
- import { getBlockModule, loadBlockModules } from "./blockModules";
37
- import { loadLayoutModules, getLayoutModule } from "./layoutModules";
33
+ import { computed, withDefaults, onBeforeMount, ref } from "vue";
38
34
  import type { Block } from "../../types/Block";
35
+ import { initialiseRegistry, getBlock, getLayout } from "../../util/theme-registry";
39
36
 
40
37
  const props = withDefaults(
41
38
  defineProps<{
@@ -43,42 +40,18 @@ const props = withDefaults(
43
40
  layout?: string;
44
41
  settings?: Record<string, any>;
45
42
  withLayout?: boolean;
43
+ theme?: string;
46
44
  }>(),
47
45
  {
48
46
  layout: "default",
49
47
  settings: () => ({}),
50
48
  withLayout: false,
49
+ theme: "default",
51
50
  }
52
51
  );
53
52
 
54
53
  const isReady = ref(false);
55
54
 
56
- function getBlock(blockType: string): Component | undefined {
57
- // Generate name variations and try to find a match
58
- const nameVariations = generateNameVariations(blockType);
59
-
60
- // Try each variation to find the block component
61
- for (const variation of nameVariations) {
62
- const module = getBlockModule(variation);
63
- if (module) return module;
64
- }
65
-
66
- return undefined;
67
- }
68
-
69
- function getLayout(layoutName: string): Component | undefined {
70
- // Generate name variations and try to find a match in layoutModules keys (file paths)
71
- const nameVariations = generateNameVariations(layoutName);
72
-
73
- // Try each variation to find the block component
74
- for (const variation of nameVariations) {
75
- const module = getLayoutModule(variation);
76
- if (module) return module;
77
- }
78
-
79
- return undefined;
80
- }
81
-
82
55
  // Get the layout component based on the layout prop
83
56
  const layoutComponent = computed(() => {
84
57
  return getLayout(props.layout);
@@ -102,8 +75,11 @@ function getMarginClass(block: Block): string {
102
75
 
103
76
  onBeforeMount(async () => {
104
77
  isReady.value = false;
105
- await loadBlockModules();
106
- await loadLayoutModules();
78
+
79
+ // Initialise registries for editor preview
80
+ // Exclude the editing registry to load only the theme and blocks
81
+ await initialiseRegistry(props.theme, false);
82
+
107
83
  isReady.value = true;
108
84
  });
109
85
  </script>
@@ -11,7 +11,7 @@
11
11
  <h4 class="mt-1 text-lg font-bold">Page settings</h4>
12
12
  </div>
13
13
  </div>
14
- <div class="border-b border-gray-300 p-5">
14
+ <div v-if="availableLayouts.length > 1" class="border-b border-gray-300 p-5">
15
15
  <!-- Page layout -->
16
16
  <div class="editor-field-node">
17
17
  <!-- Label -->
@@ -37,6 +37,7 @@
37
37
  :fields="pageSettingsFields"
38
38
  :editable="true"
39
39
  :isLayoutBlock="true"
40
+ :activeTab="activeTab"
40
41
  />
41
42
  </div>
42
43
  </div>
@@ -44,7 +45,7 @@
44
45
 
45
46
  <script setup lang="ts">
46
47
  import { computed, onBeforeMount, onMounted, ref } from "vue";
47
- import { getLayoutFields, getLayouts } from "../../util/registry";
48
+ import { themeLayouts } from "../../util/theme-registry";
48
49
  import BlockEditorFields from "../BlockEditorFields/BlockEditorFields.vue";
49
50
 
50
51
  const emit = defineEmits<{
@@ -57,23 +58,26 @@ const pageSettingsFields = ref<any>({});
57
58
  const props = withDefaults(
58
59
  defineProps<{
59
60
  settingsKey?: string;
61
+ activeTab?: string;
60
62
  }>(),
61
63
  {
62
64
  settingsKey: "settings",
65
+ activeTab: undefined,
63
66
  }
64
67
  );
65
68
  const availableLayouts = computed(() => {
66
- return getLayouts();
69
+ return Object.values(themeLayouts.value) || [];
67
70
  });
68
71
 
69
72
  function getLayoutSettings() {
70
- if (!pageData.value[props.settingsKey].layout) return;
71
- pageSettingsFields.value = getLayoutFields(pageData.value[props.settingsKey].layout) || null;
73
+ const activeLayout = pageData.value[props.settingsKey].layout;
74
+ if (!activeLayout) return;
75
+ pageSettingsFields.value = themeLayouts.value[activeLayout]?.fields || null;
72
76
  }
73
77
 
74
78
  onBeforeMount(() => {
75
79
  if (!pageData.value[props.settingsKey]) {
76
- pageData.value.settings = null;
80
+ pageData.value[props.settingsKey] = {};
77
81
  }
78
82
  });
79
83
 
@@ -1,15 +1,23 @@
1
1
  <template>
2
- <div
3
- id="page-builder-resize-handle"
4
- ref="resizeHandle"
5
- class="resize-handle shrink-0 cursor-col-resize transition-colors duration-200"
6
- @mousedown="startResize"
7
- ></div>
2
+ <div class="resize-handle-wrapper group">
3
+ <div
4
+ id="page-builder-resize-handle"
5
+ ref="resizeHandle"
6
+ class="resize-handle shrink-0 cursor-col-resize transition-colors duration-200"
7
+ @mousedown="startResize"
8
+ ></div>
9
+ <span
10
+ class="viewport-size absolute right-1 top-1 opacity-0 transition-opacity duration-200 group-hover:opacity-100"
11
+ >
12
+ <div class="rounded-sm bg-yellow-400 px-1.5 py-1 font-mono text-xs font-medium text-gray-900">
13
+ {{ computedViewportSize }}
14
+ </div>
15
+ </span>
16
+ </div>
8
17
  </template>
9
18
 
10
19
  <script setup lang="ts">
11
- import { ref } from "vue";
12
- import { defineEmits } from "vue";
20
+ import { ref, computed, onMounted, onBeforeUnmount } from "vue";
13
21
 
14
22
  const emit = defineEmits<{
15
23
  (e: "sidebarWidth", width: number): void;
@@ -18,6 +26,9 @@ const emit = defineEmits<{
18
26
  const resizeHandle = ref<HTMLElement | null>(null);
19
27
  const sidebarWidth = ref(380); // Default sidebar width (380px)
20
28
  const isResizing = ref(false);
29
+ const viewportWidth = ref<number>(0);
30
+ let resizeObserver: ResizeObserver | null = null;
31
+ let updateViewportWidthFn: (() => void) | null = null;
21
32
 
22
33
  // Resize functionality
23
34
  function startResize(event: MouseEvent) {
@@ -50,15 +61,62 @@ function startResize(event: MouseEvent) {
50
61
  document.body.style.cursor = "col-resize";
51
62
  document.body.style.userSelect = "none";
52
63
  }
64
+
65
+ // Calculate viewport width based on #page-viewport element
66
+ const computedViewportSize = computed(() => {
67
+ return viewportWidth.value > 0 ? `${viewportWidth.value}px` : "0px";
68
+ });
69
+
70
+ // Setup ResizeObserver to watch #page-viewport element
71
+ onMounted(() => {
72
+ updateViewportWidthFn = () => {
73
+ const viewportElement = document.getElementById("page-preview-container");
74
+ if (viewportElement) {
75
+ viewportWidth.value = viewportElement.offsetWidth;
76
+ }
77
+ };
78
+
79
+ // Initial measurement
80
+ updateViewportWidthFn();
81
+
82
+ // Use ResizeObserver to watch for size changes
83
+ const viewportElement = document.getElementById("page-preview-container");
84
+ if (viewportElement) {
85
+ resizeObserver = new ResizeObserver(() => {
86
+ if (updateViewportWidthFn) {
87
+ updateViewportWidthFn();
88
+ }
89
+ });
90
+ resizeObserver.observe(viewportElement);
91
+ }
92
+
93
+ // Also listen for window resize events as a fallback
94
+ window.addEventListener("resize", updateViewportWidthFn);
95
+ });
96
+
97
+ onBeforeUnmount(() => {
98
+ if (resizeObserver) {
99
+ resizeObserver.disconnect();
100
+ resizeObserver = null;
101
+ }
102
+ if (updateViewportWidthFn) {
103
+ window.removeEventListener("resize", updateViewportWidthFn);
104
+ updateViewportWidthFn = null;
105
+ }
106
+ });
53
107
  </script>
54
108
 
55
109
  <style scoped lang="scss">
56
110
  /* Resize handle styles */
57
- .resize-handle {
111
+ .resize-handle-wrapper {
58
112
  position: sticky;
59
113
  top: 0;
60
114
  right: 0;
61
- z-index: 14;
115
+ z-index: 30;
116
+ height: var(--editor-height);
117
+ }
118
+
119
+ .resize-handle {
62
120
  width: 3px;
63
121
  height: var(--editor-height);
64
122
  cursor: col-resize;