vue-wswg-editor 0.0.8 → 0.0.10

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 (32) hide show
  1. package/dist/style.css +1 -1
  2. package/dist/types/components/PageBuilderSidebar/PageBuilderSidebar.vue.d.ts +9 -4
  3. package/dist/types/components/PageRenderer/PageRenderer.vue.d.ts +8 -1
  4. package/dist/types/components/PageRenderer/layoutModules.d.ts +1 -0
  5. package/dist/types/components/PageSettings/PageSettings.vue.d.ts +4 -2
  6. package/dist/types/components/WswgJsonEditor/WswgJsonEditor.vue.d.ts +5 -9
  7. package/dist/types/index.d.ts +1 -0
  8. package/dist/types/util/fieldConfig.d.ts +2 -1
  9. package/dist/vite-plugin.js +55 -22
  10. package/dist/vue-wswg-editor.es.js +1486 -1417
  11. package/package.json +1 -1
  12. package/src/components/AddBlockItem/AddBlockItem.vue +5 -5
  13. package/src/components/BlockBrowser/BlockBrowser.vue +33 -3
  14. package/src/components/BlockEditorFieldNode/BlockEditorFieldNode.vue +40 -30
  15. package/src/components/BlockEditorFields/BlockEditorFields.vue +4 -4
  16. package/src/components/BlockMarginFieldNode/BlockMarginNode.vue +6 -4
  17. package/src/components/BlockRepeaterFieldNode/BlockRepeaterNode.vue +4 -2
  18. package/src/components/BrowserNavigation/BrowserNavigation.vue +2 -2
  19. package/src/components/EmptyState/EmptyState.vue +1 -1
  20. package/src/components/PageBlockList/PageBlockList.vue +1 -9
  21. package/src/components/PageBuilderSidebar/PageBuilderSidebar.vue +32 -9
  22. package/src/components/PageBuilderToolbar/PageBuilderToolbar.vue +4 -4
  23. package/src/components/PageRenderer/PageRenderer.vue +58 -5
  24. package/src/components/PageRenderer/layoutModules.ts +3 -0
  25. package/src/components/PageSettings/PageSettings.vue +19 -11
  26. package/src/components/ResizeHandle/ResizeHandle.vue +10 -10
  27. package/src/components/WswgJsonEditor/WswgJsonEditor.vue +103 -65
  28. package/src/index.ts +1 -0
  29. package/src/types/Block.d.ts +1 -1
  30. package/src/util/fieldConfig.ts +7 -0
  31. package/src/util/helpers.ts +1 -1
  32. package/src/vite-plugin.ts +49 -0
@@ -55,25 +55,25 @@ function startResize(event: MouseEvent) {
55
55
  <style scoped lang="scss">
56
56
  /* Resize handle styles */
57
57
  .resize-handle {
58
- position: relative;
58
+ position: sticky;
59
59
  top: 0;
60
60
  right: 0;
61
- z-index: 2;
61
+ z-index: 14;
62
62
  width: 3px;
63
- height: 100%;
63
+ height: var(--editor-height);
64
64
  cursor: col-resize;
65
- background-color: var(--grey-20);
65
+ background-color: #dbdee0;
66
66
  transition: all 0.2s ease-in-out;
67
67
  }
68
68
 
69
69
  .resize-handle:hover {
70
70
  width: 3px;
71
- background-color: var(--yellow-40);
71
+ background-color: #fcd34d;
72
72
  }
73
73
 
74
74
  .resize-handle:active {
75
75
  width: 3px;
76
- background-color: var(--yellow-50);
76
+ background-color: #fbbf24;
77
77
  }
78
78
 
79
79
  /* Add a subtle indicator when resizing */
@@ -81,12 +81,12 @@ function startResize(event: MouseEvent) {
81
81
  position: absolute;
82
82
  top: 50%;
83
83
  left: -6px;
84
- z-index: 20;
84
+ z-index: 50;
85
85
  width: 14px;
86
86
  height: 40px;
87
87
  content: "";
88
- background-color: var(--yellow-40);
89
- border-radius: var(--border-radius-16);
88
+ background-color: #fcd34d;
89
+ border-radius: 8px;
90
90
  opacity: 0;
91
91
  transform: translateY(-50%);
92
92
  transition:
@@ -99,7 +99,7 @@ function startResize(event: MouseEvent) {
99
99
  }
100
100
 
101
101
  .resize-handle:active::before {
102
- background-color: var(--yellow-50);
102
+ background-color: #fbbf24;
103
103
  opacity: 1;
104
104
  }
105
105
  </style>
@@ -1,42 +1,41 @@
1
1
  <template>
2
2
  <div class="wswg-json-editor">
3
- <div class="wswg-json-editor-header">
4
- <!-- header slot for custom control elements -->
5
- <slot name="header">
6
- <!-- no default header content -->
7
- </slot>
8
- <!-- wswg toolbar-->
9
- <PageBuilderToolbar
10
- v-model:editorViewport="editorViewport"
11
- v-model:showPageSettings="showPageSettings"
12
- v-model:activeBlock="activeBlock"
13
- :hasPageSettings="hasPageSettings"
14
- >
15
- <slot name="toolbar">
16
- <!-- no default toolbar content -->
17
- </slot>
18
- </PageBuilderToolbar>
19
- </div>
20
3
  <slot v-if="loading" name="loading">
21
- <div class="wswg-json-editor-loading">
22
- <span>Loading...</span>
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>
23
17
  </div>
24
18
  </slot>
25
19
  <!-- WYSIWYG editor -->
26
- <div v-else class="wswg-json-editor-canvas">
20
+ <div v-else class="wswg-json-editor-body">
27
21
  <!-- Page preview -->
28
- <div class="wswg-json-editor-canvas-preview">
22
+ <div class="wswg-json-editor-preview">
29
23
  <div
30
- class="mx-auto h-full overflow-hidden rounded-lg bg-white transition-all duration-300"
24
+ class="mx-auto flex h-full flex-col transition-all duration-300"
31
25
  :class="{ 'w-full': editorViewport === 'desktop', 'w-96': editorViewport === 'mobile' }"
32
26
  >
33
- <BrowserNavigation v-if="showBrowserBar" :url="url" />
34
- <div v-if="pageLayout" id="page-preview-viewport" class="h-full overflow-y-auto">
27
+ <BrowserNavigation v-if="showBrowserBar" class="browser-navigation-bar" :url="url" />
28
+ <div
29
+ v-if="pageLayout"
30
+ id="page-preview-viewport"
31
+ class="overflow-hidden rounded-b-lg bg-white"
32
+ :class="{ 'rounded-t-lg': !showBrowserBar }"
33
+ >
35
34
  <component :is="pageLayout">
36
35
  <template #default>
37
36
  <!-- No blocks found -->
38
37
  <EmptyState
39
- v-if="!pageData[blocksKey]?.length"
38
+ v-if="!pageData?.[blocksKey]?.length"
40
39
  v-model:showAddBlockMenu="showAddBlockMenu"
41
40
  :editable="editable"
42
41
  @block-added="handleAddBlock"
@@ -66,24 +65,36 @@
66
65
  <ResizeHandle @sidebar-width="handleSidebarWidth" />
67
66
 
68
67
  <!-- Sidebar -->
69
- <PageBuilderSidebar
70
- v-model="pageData"
71
- v-model:activeBlock="activeBlock"
72
- v-model:hoveredBlockId="hoveredBlockId"
73
- v-model:showPageSettings="showPageSettings"
74
- v-model:showAddBlockMenu="showAddBlockMenu"
75
- :editable="editable"
76
- :blocksKey="blocksKey"
77
- :settingsKey="settingsKey"
78
- :style="{ width: sidebarWidth + 'px' }"
79
- />
68
+ <div class="page-builder-sidebar-wrapper bg-white" :style="{ width: sidebarWidth + 'px' }">
69
+ <PageBuilderSidebar
70
+ v-model="pageData"
71
+ v-model:activeBlock="activeBlock"
72
+ v-model:hoveredBlockId="hoveredBlockId"
73
+ v-model:showPageSettings="showPageSettings"
74
+ v-model:showAddBlockMenu="showAddBlockMenu"
75
+ v-model:editorViewport="editorViewport"
76
+ :hasPageSettings="hasPageSettings"
77
+ :editable="editable"
78
+ :blocksKey="blocksKey"
79
+ :settingsKey="settingsKey"
80
+ />
81
+ </div>
80
82
  </div>
81
83
  </div>
82
84
  </template>
83
85
 
84
86
  <script setup lang="ts">
85
- import { ref, shallowRef, withDefaults, watch, type Component, onBeforeMount, nextTick, computed } from "vue";
86
- import PageBuilderToolbar from "../PageBuilderToolbar/PageBuilderToolbar.vue";
87
+ import {
88
+ ref,
89
+ shallowRef,
90
+ withDefaults,
91
+ watch,
92
+ type Component,
93
+ onBeforeMount,
94
+ nextTick,
95
+ computed,
96
+ onMounted,
97
+ } from "vue";
87
98
  import ResizeHandle from "../ResizeHandle/ResizeHandle.vue";
88
99
  import PageBuilderSidebar from "../PageBuilderSidebar/PageBuilderSidebar.vue";
89
100
  import BrowserNavigation from "../BrowserNavigation/BrowserNavigation.vue";
@@ -123,7 +134,7 @@ const hoveredBlockId = ref<string | null>(null);
123
134
  const sidebarWidth = ref(380); // Default sidebar width (380px)
124
135
 
125
136
  // Model value for the JSON page data
126
- const pageData = defineModel<any>();
137
+ const pageData = defineModel<Record<string, any>>();
127
138
 
128
139
  // Layout component - dynamically imported from page-builder directory
129
140
  // Using shallowRef to avoid making the component reactive (performance optimization)
@@ -177,6 +188,8 @@ function handleBlockClick(block: Block | null) {
177
188
  }
178
189
 
179
190
  function handleAddBlock(blockType: string, insertIndex?: number) {
191
+ if (!pageData.value) return;
192
+
180
193
  // Ensure blocks array exists
181
194
  if (!pageData.value[props.blocksKey]) {
182
195
  pageData.value[props.blocksKey] = [];
@@ -240,7 +253,7 @@ watch(
240
253
  (layoutName) => {
241
254
  loadLayout(layoutName);
242
255
  },
243
- { immediate: true }
256
+ { immediate: false }
244
257
  );
245
258
 
246
259
  // Watch for activeBlock changes and scroll to the corresponding block in the preview
@@ -309,6 +322,10 @@ function initSortable() {
309
322
  }
310
323
 
311
324
  onBeforeMount(() => {
325
+ if (!pageData.value) {
326
+ pageData.value = {};
327
+ }
328
+
312
329
  if (!pageData.value?.[props.settingsKey]) {
313
330
  if (pageData.value) {
314
331
  pageData.value[props.settingsKey] = {};
@@ -340,22 +357,23 @@ onBeforeMount(() => {
340
357
  }
341
358
  }
342
359
  });
360
+
361
+ onMounted(() => {
362
+ loadLayout(pageData.value?.[props.settingsKey]?.layout);
363
+ });
343
364
  </script>
344
365
 
345
366
  <style lang="scss">
367
+ $editor-background-color: #6a6a6a;
368
+
346
369
  .wswg-json-editor {
347
- display: flex;
348
- flex-direction: column;
349
- width: 100%;
350
- max-width: 100%;
351
- height: 100vh;
370
+ --editor-height: calc(100vh);
352
371
 
353
- &-header {
354
- position: sticky;
355
- top: 0;
356
- z-index: 20;
357
- background-color: #fff;
358
- }
372
+ position: relative;
373
+ width: 100%;
374
+ max-width: 100vw;
375
+ height: var(--editor-height);
376
+ overflow-y: auto;
359
377
 
360
378
  &-loading {
361
379
  display: flex;
@@ -363,27 +381,47 @@ onBeforeMount(() => {
363
381
  justify-content: center;
364
382
  }
365
383
 
366
- &-canvas {
384
+ &-body {
385
+ display: flex;
386
+ width: 100%;
387
+ background-color: $editor-background-color;
388
+ }
389
+
390
+ &-preview {
391
+ position: relative;
367
392
  display: flex;
368
393
  flex: 1;
369
394
  flex-grow: 1;
370
395
  flex-shrink: 0;
396
+ flex-direction: column;
371
397
  height: 100%;
372
- overflow-y: auto;
373
- background-color: #6a6a6a;
374
-
375
- &-preview {
376
- flex: 1;
377
- flex-grow: 1;
378
- flex-shrink: 0;
379
- padding: 2rem;
398
+ min-height: 0;
399
+ padding: 1.5rem;
400
+
401
+ .browser-navigation-bar {
402
+ position: sticky;
403
+ top: 1.5rem;
404
+ z-index: 12;
405
+
406
+ &::before {
407
+ position: absolute;
408
+ top: -1.5rem;
409
+ left: 0;
410
+ z-index: -1;
411
+ width: 100%;
412
+ height: 100%;
413
+ content: "";
414
+ background-color: $editor-background-color;
415
+ }
380
416
  }
417
+ }
381
418
 
382
- &-sidebar {
383
- min-width: 300px;
384
- padding: 2rem;
385
- background: #fff;
386
- }
419
+ .page-builder-sidebar-wrapper {
420
+ position: sticky;
421
+ top: 0;
422
+ z-index: 12;
423
+ height: var(--editor-height);
424
+ overflow-y: auto;
387
425
  }
388
426
  }
389
427
  </style>
package/src/index.ts CHANGED
@@ -4,6 +4,7 @@ export { createField } from "./util/fieldConfig";
4
4
  export type { EditorFieldConfig, ValidatorFunction } from "./util/fieldConfig";
5
5
  export { getLayouts } from "./util/registry";
6
6
  export { validateField, validateAllFields } from "./util/validation";
7
+ export type { ValidationResult } from "./util/validation";
7
8
 
8
9
  // Export components (component exports don't cause side effects until used)
9
10
  export { default as WswgJsonEditor } from "./components/WswgJsonEditor/WswgJsonEditor.vue";
@@ -9,7 +9,7 @@ export type Block = Component & {
9
9
  type: string;
10
10
  // Defined in template
11
11
  label?: string;
12
- icon?: string;
12
+ emoji?: string; // A single emoji character
13
13
  // fields file
14
14
  fields?: Record<string, any>;
15
15
  // Auto generated from the component path
@@ -25,6 +25,7 @@ export type EditorFieldType =
25
25
  | "range" // ✅
26
26
  | "repeater" // ✅
27
27
  | "margin" // ✅
28
+ | "info" // ✅
28
29
  | "custom"; // 🔌 (image, json, video, richtext, etc)
29
30
 
30
31
  /**
@@ -170,4 +171,10 @@ export const createField = {
170
171
  label: "Margin",
171
172
  ...config,
172
173
  }),
174
+
175
+ // Display an info message
176
+ info: (config: Partial<EditorFieldConfig> = {}): EditorFieldConfig => ({
177
+ type: "info",
178
+ ...config,
179
+ }),
173
180
  };
@@ -148,7 +148,7 @@ export function toCamelCase(input: string): string {
148
148
  if (parts.length === 0) return "";
149
149
 
150
150
  return (
151
- parts[0].toLowerCase() +
151
+ parts?.[0]?.toLowerCase() +
152
152
  parts
153
153
  .slice(1)
154
154
  .map((p) => p.charAt(0).toUpperCase() + p.slice(1).toLowerCase())
@@ -21,6 +21,55 @@ export function vueWswgEditorPlugin(options: VueWswgEditorPluginOptions): Plugin
21
21
  name: "vue-wswg-editor-glob-plugin",
22
22
  enforce: "pre", // Run before other plugins to ensure import.meta.glob is processed correctly
23
23
 
24
+ config(config) {
25
+ // Exclude vue-wswg-editor from dependency optimization to prevent esbuild
26
+ // from trying to resolve virtual modules before plugins run
27
+ const optimizeDeps = config.optimizeDeps || {};
28
+ const existingExclude = optimizeDeps.exclude;
29
+ let exclude: string[];
30
+ if (Array.isArray(existingExclude)) {
31
+ exclude = [...existingExclude];
32
+ } else if (typeof existingExclude === "string") {
33
+ exclude = [existingExclude];
34
+ } else {
35
+ exclude = [];
36
+ }
37
+ // Exclude the package and virtual modules
38
+ const itemsToExclude = [
39
+ "vue-wswg-editor",
40
+ "vue-wswg-editor:layouts",
41
+ "vue-wswg-editor:blocks",
42
+ "vue-wswg-editor:fields",
43
+ "vue-wswg-editor:thumbnails",
44
+ ];
45
+ for (const item of itemsToExclude) {
46
+ if (!exclude.includes(item)) {
47
+ exclude.push(item);
48
+ }
49
+ }
50
+ return {
51
+ optimizeDeps: {
52
+ ...optimizeDeps,
53
+ exclude,
54
+ esbuildOptions: {
55
+ ...optimizeDeps.esbuildOptions,
56
+ plugins: [
57
+ ...(optimizeDeps.esbuildOptions?.plugins || []),
58
+ {
59
+ name: "vue-wswg-editor-virtual-modules",
60
+ setup(build) {
61
+ // Mark virtual modules as external during esbuild optimization
62
+ build.onResolve({ filter: /^vue-wswg-editor:/ }, () => {
63
+ return { external: true };
64
+ });
65
+ },
66
+ },
67
+ ],
68
+ },
69
+ },
70
+ };
71
+ },
72
+
24
73
  resolveId(id) {
25
74
  // Handle virtual module imports like "vue-wswg-editor:layouts"
26
75
  if (id.startsWith("vue-wswg-editor:")) {