vue-wswg-editor 0.0.12 → 0.0.14

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 +2783 -1905
  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 +230 -0
  34. package/src/components/IframePreview/iframePreviewApp.ts +308 -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 +18 -38
  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
@@ -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
@@ -1,7 +1,7 @@
1
1
  <template>
2
2
  <div id="page-viewport" class="page-renderer-wrapper relative">
3
3
  <template v-if="isReady">
4
- <component :is="layoutComponent" v-if="withLayout && layoutComponent" v-bind="settings">
4
+ <component :is="layoutComponent" v-if="withLayout && layoutComponent" v-bind="settings" :blocks="blocks">
5
5
  <template #default>
6
6
  <div id="page-blocks-wrapper">
7
7
  <div
@@ -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,45 +40,21 @@ 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
- return getLayout(props.layout);
57
+ return getLayout(props.layout || props.settings?.layout);
85
58
  });
86
59
 
87
60
  // Get the margin class for the block
@@ -102,16 +75,23 @@ 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>
110
86
 
111
- <style scoped lang="scss">
87
+ <style lang="scss">
112
88
  @use "../../assets/styles/mixins" as *;
113
89
 
114
- .block-wrapper {
115
- @include block-margin-classes;
90
+ // Styles are scoped to the component using the root class selector instead of Vue's scoped attribute
91
+ // This ensures styles work correctly when the component is consumed from a library
92
+ .page-renderer-wrapper {
93
+ .block-wrapper {
94
+ @include block-margin-classes;
95
+ }
116
96
  }
117
97
  </style>
@@ -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;
@@ -1,18 +1,18 @@
1
1
  import { describe, it, expect } from "vitest";
2
2
  import { mount } from "@vue/test-utils";
3
- import WswgJsonEditor from "./WswgJsonEditor.vue";
3
+ import WswgPageBuilder from "./WswgPageBuilder.vue";
4
4
 
5
- describe("WswgJsonEditor", () => {
5
+ describe("WswgPageBuilder", () => {
6
6
  it("renders the placeholder component", () => {
7
- const wrapper = mount(WswgJsonEditor);
7
+ const wrapper = mount(WswgPageBuilder);
8
8
 
9
- expect(wrapper.text()).toContain("WswgJsonEditor");
9
+ expect(wrapper.text()).toContain("WswgPageBuilder");
10
10
  expect(wrapper.text()).toContain("This is a placeholder component");
11
11
  });
12
12
 
13
13
  it("renders header slot content when provided", () => {
14
14
  const headerContent = "Custom Header Content";
15
- const wrapper = mount(WswgJsonEditor, {
15
+ const wrapper = mount(WswgPageBuilder, {
16
16
  slots: {
17
17
  header: headerContent,
18
18
  },
@@ -22,7 +22,7 @@ describe("WswgJsonEditor", () => {
22
22
  });
23
23
 
24
24
  it("renders header slot with HTML content", () => {
25
- const wrapper = mount(WswgJsonEditor, {
25
+ const wrapper = mount(WswgPageBuilder, {
26
26
  slots: {
27
27
  header: "<div class='custom-header'>Header Title</div>",
28
28
  },
@@ -33,10 +33,10 @@ describe("WswgJsonEditor", () => {
33
33
  });
34
34
 
35
35
  it("does not render header slot when not provided", () => {
36
- const wrapper = mount(WswgJsonEditor);
36
+ const wrapper = mount(WswgPageBuilder);
37
37
 
38
38
  // The component should still render without the header slot
39
- expect(wrapper.text()).toContain("WswgJsonEditor");
39
+ expect(wrapper.text()).toContain("WswgPageBuilder");
40
40
  // Header slot should be empty/not present
41
41
  expect(wrapper.html()).not.toContain("custom-header");
42
42
  });