vue-wswg-editor 0.0.9 → 0.0.11

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 (56) hide show
  1. package/README.md +493 -20
  2. package/dist/style.css +1 -1
  3. package/dist/vue-wswg-editor.es.js +2431 -1895
  4. package/package.json +15 -8
  5. package/src/assets/styles/_mixins.scss +12 -17
  6. package/src/components/AddBlockItem/AddBlockItem.vue +5 -5
  7. package/src/components/BlockBrowser/BlockBrowser.vue +33 -3
  8. package/src/components/BlockEditorFieldNode/BlockEditorFieldNode.vue +79 -34
  9. package/src/components/BlockEditorFields/BlockEditorFields.vue +15 -6
  10. package/src/components/BlockImageFieldNode/BlockImageNode.vue +373 -0
  11. package/src/components/BlockMarginFieldNode/BlockMarginNode.vue +6 -4
  12. package/src/components/BlockRepeaterFieldNode/BlockRepeaterNode.vue +4 -2
  13. package/src/components/BrowserNavigation/BrowserNavigation.vue +2 -2
  14. package/src/components/EmptyState/EmptyState.vue +1 -1
  15. package/src/components/PageBlockList/PageBlockList.vue +1 -9
  16. package/src/components/PageBuilderSidebar/PageBuilderSidebar.vue +29 -10
  17. package/src/components/PageBuilderToolbar/PageBuilderToolbar.vue +4 -4
  18. package/src/components/PageRenderer/PageRenderer.vue +76 -24
  19. package/src/components/PageRenderer/blockModules.ts +32 -3
  20. package/src/components/PageRenderer/layoutModules.ts +32 -0
  21. package/src/components/PageSettings/PageSettings.vue +19 -11
  22. package/src/components/ResizeHandle/ResizeHandle.vue +10 -10
  23. package/src/components/WswgJsonEditor/WswgJsonEditor.vue +323 -117
  24. package/src/index.ts +2 -2
  25. package/src/style.css +10 -3
  26. package/src/types/Block.d.ts +1 -1
  27. package/src/util/fieldConfig.ts +29 -0
  28. package/src/util/registry.ts +30 -23
  29. package/src/util/validation.ts +178 -23
  30. package/src/vite-plugin.ts +27 -2
  31. package/types/vue-wswg-editor.d.ts +161 -0
  32. package/dist/types/components/AddBlockItem/AddBlockItem.vue.d.ts +0 -6
  33. package/dist/types/components/BlockBrowser/BlockBrowser.vue.d.ts +0 -2
  34. package/dist/types/components/BlockComponent/BlockComponent.vue.d.ts +0 -15
  35. package/dist/types/components/BlockEditorFieldNode/BlockEditorFieldNode.vue.d.ts +0 -15
  36. package/dist/types/components/BlockEditorFields/BlockEditorFields.vue.d.ts +0 -15
  37. package/dist/types/components/BlockMarginFieldNode/BlockMarginNode.vue.d.ts +0 -23
  38. package/dist/types/components/BlockRepeaterFieldNode/BlockRepeaterNode.vue.d.ts +0 -15
  39. package/dist/types/components/BrowserNavigation/BrowserNavigation.vue.d.ts +0 -5
  40. package/dist/types/components/EmptyState/EmptyState.vue.d.ts +0 -15
  41. package/dist/types/components/PageBlockList/PageBlockList.vue.d.ts +0 -19
  42. package/dist/types/components/PageBuilderSidebar/PageBuilderSidebar.vue.d.ts +0 -30
  43. package/dist/types/components/PageBuilderToolbar/PageBuilderToolbar.vue.d.ts +0 -28
  44. package/dist/types/components/PageRenderer/PageRenderer.vue.d.ts +0 -6
  45. package/dist/types/components/PageRenderer/blockModules.d.ts +0 -1
  46. package/dist/types/components/PageSettings/PageSettings.vue.d.ts +0 -15
  47. package/dist/types/components/ResizeHandle/ResizeHandle.vue.d.ts +0 -6
  48. package/dist/types/components/WswgJsonEditor/WswgJsonEditor.test.d.ts +0 -1
  49. package/dist/types/components/WswgJsonEditor/WswgJsonEditor.vue.d.ts +0 -40
  50. package/dist/types/index.d.ts +0 -7
  51. package/dist/types/util/fieldConfig.d.ts +0 -82
  52. package/dist/types/util/helpers.d.ts +0 -28
  53. package/dist/types/util/registry.d.ts +0 -20
  54. package/dist/types/util/validation.d.ts +0 -15
  55. package/dist/types/vite-plugin.d.ts +0 -9
  56. package/dist/vite-plugin.js +0 -53
package/package.json CHANGED
@@ -1,15 +1,16 @@
1
1
  {
2
2
  "name": "vue-wswg-editor",
3
- "version": "0.0.9",
3
+ "version": "0.0.11",
4
4
  "type": "module",
5
- "main": "src/index.ts",
6
- "module": "src/index.ts",
7
- "typings": "src/index.ts",
5
+ "main": "./dist/vue-wswg-editor.es.js",
6
+ "module": "./dist/vue-wswg-editor.es.js",
7
+ "typings": "./dist/types/index.d.ts",
8
+ "types": "./dist/types/index.d.ts",
8
9
  "exports": {
9
10
  ".": {
10
- "import": "./src/index.ts",
11
- "require": "./src/index.ts",
12
- "types": "./src/index.ts"
11
+ "import": "./dist/vue-wswg-editor.es.js",
12
+ "require": "./dist/vue-wswg-editor.es.js",
13
+ "types": "./dist/types/index.d.ts"
13
14
  },
14
15
  "./PageRenderer": {
15
16
  "import": "./src/components/PageRenderer/PageRenderer.vue",
@@ -24,12 +25,16 @@
24
25
  },
25
26
  "files": [
26
27
  "dist",
27
- "src"
28
+ "src",
29
+ "types"
28
30
  ],
29
31
  "scripts": {
30
32
  "dev": "concurrently \"vite build --watch\" \"vite build --config vite-plugin.config.ts --watch\"",
31
33
  "build": "vite build && vite build --config vite-plugin.config.ts && vue-tsc --declaration --emitDeclarationOnly",
32
34
  "preview": "vite preview",
35
+ "docs:dev": "vitepress dev docs",
36
+ "docs:build": "vitepress build docs",
37
+ "docs:preview": "vitepress preview docs",
33
38
  "test:unit": "vitest",
34
39
  "test:unit:ui": "vitest --ui",
35
40
  "test:unit:ci": "vitest run",
@@ -82,6 +87,8 @@
82
87
  "tailwindcss": "^3.4.10",
83
88
  "typescript": "~5.8.3",
84
89
  "vite": "^5.4.19",
90
+ "vitepress": "^1.6.4",
91
+ "vitepress-demo-plugin": "^1.5.1",
85
92
  "vitest": "^2.1.9",
86
93
  "vue-tsc": "^2.2.12",
87
94
  "yup": "^1.7.1"
@@ -3,64 +3,59 @@
3
3
  // Usage: @include block-margin-classes;
4
4
 
5
5
  @mixin block-margin-classes {
6
- // Margin size variables
7
- $sm-size: 2rem;
8
- $md-size: 4rem;
9
- $lg-size: 6rem;
10
-
11
6
  // Top margin classes
12
7
  &.margin-top-sm {
13
- padding-top: $sm-size;
8
+ padding-top: var(--block-margin-sm, 2rem);
14
9
 
15
10
  &::before {
16
11
  top: 0;
17
- height: $sm-size;
12
+ height: var(--block-margin-sm, 2rem);
18
13
  }
19
14
  }
20
15
 
21
16
  &.margin-top-md {
22
- padding-top: $md-size;
17
+ padding-top: var(--block-margin-md, 4rem);
23
18
 
24
19
  &::before {
25
20
  top: 0;
26
- height: $md-size;
21
+ height: var(--block-margin-md, 4rem);
27
22
  }
28
23
  }
29
24
 
30
25
  &.margin-top-lg {
31
- padding-top: $lg-size;
26
+ padding-top: var(--block-margin-lg, 6rem);
32
27
 
33
28
  &::before {
34
29
  top: 0;
35
- height: $lg-size;
30
+ height: var(--block-margin-lg, 6rem);
36
31
  }
37
32
  }
38
33
 
39
34
  // Bottom margin classes
40
35
  &.margin-bottom-sm {
41
- padding-bottom: $sm-size;
36
+ padding-bottom: var(--block-margin-sm, 2rem);
42
37
 
43
38
  &::after {
44
39
  bottom: 0;
45
- height: $sm-size;
40
+ height: var(--block-margin-sm, 2rem);
46
41
  }
47
42
  }
48
43
 
49
44
  &.margin-bottom-md {
50
- padding-bottom: $md-size;
45
+ padding-bottom: var(--block-margin-md, 4rem);
51
46
 
52
47
  &::after {
53
48
  bottom: 0;
54
- height: $md-size;
49
+ height: var(--block-margin-md, 4rem);
55
50
  }
56
51
  }
57
52
 
58
53
  &.margin-bottom-lg {
59
- padding-bottom: $lg-size;
54
+ padding-bottom: var(--block-margin-lg, 6rem);
60
55
 
61
56
  &::after {
62
57
  bottom: 0;
63
- height: $lg-size;
58
+ height: var(--block-margin-lg, 6rem);
64
59
  }
65
60
  }
66
61
 
@@ -2,7 +2,7 @@
2
2
  <div
3
3
  :data-block-type="block.type"
4
4
  draggable="true"
5
- class="cursor-pointer rounded-md border bg-zinc-50 p-3 text-sm text-zinc-900 hover:border-zinc-400 hover:text-zinc-900"
5
+ class="cursor-pointer rounded-md border border-gray-300 bg-zinc-50 p-2 text-sm text-zinc-900 hover:border-zinc-400 hover:text-zinc-900"
6
6
  @dragstart="(event) => handleDragStart(event, block)"
7
7
  >
8
8
  <!-- thumbnail image -->
@@ -14,15 +14,15 @@
14
14
  @error="thumbnailError = true"
15
15
  />
16
16
  </div>
17
- <!-- icon -->
18
- <div v-else-if="block.icon" class="mb-2 flex h-28 w-full items-center justify-center rounded-md bg-zinc-200">
19
- <span>Icon: {{ block.icon }}</span>
17
+ <!-- emoji -->
18
+ <div v-else-if="block.emoji" class="mb-2 flex h-28 w-full items-center justify-center rounded-md bg-zinc-200">
19
+ <span class="text-2xl">{{ block.emoji }}</span>
20
20
  </div>
21
21
  <!-- placeholder -->
22
22
  <div v-else class="mb-2 flex h-28 w-full items-center justify-center rounded-md bg-zinc-200">
23
23
  <CubeTransparentIcon class="size-6 text-zinc-400" />
24
24
  </div>
25
- <p class="font-bold">{{ block.label }}</p>
25
+ <p class="text-sm">{{ block.label }}</p>
26
26
  </div>
27
27
  </template>
28
28
 
@@ -1,7 +1,12 @@
1
1
  <template>
2
2
  <div class="block-browser">
3
- <div class="block-browser-header border-b bg-white px-5 py-3">
4
- <input v-model="blockSearch" type="text" placeholder="Search blocks" class="form-control" />
3
+ <div class="block-browser-header border-b border-gray-300 bg-white px-5 py-3">
4
+ <input
5
+ v-model="blockSearch"
6
+ type="text"
7
+ placeholder="Search blocks"
8
+ class="w-full rounded-md border border-gray-300 p-2"
9
+ />
5
10
  </div>
6
11
  <div v-if="!blockCount" class="p-5 text-center text-sm text-zinc-500">
7
12
  <p>Create your first block to get started.</p>
@@ -15,7 +20,7 @@
15
20
  </p>
16
21
  </div>
17
22
  <div v-else-if="!filteredBlocks.length" class="p-5 text-center text-sm text-zinc-500">No blocks found</div>
18
- <div v-else id="available-blocks-list" class="grid grid-cols-1 gap-3 p-5">
23
+ <div v-else id="available-blocks-list" class="available-blocks-grid">
19
24
  <AddBlockItem v-for="block in filteredBlocks" :key="block.type" :block="block" />
20
25
  </div>
21
26
  </div>
@@ -67,3 +72,28 @@ onMounted(() => {
67
72
  initSortable();
68
73
  });
69
74
  </script>
75
+
76
+ <style scoped>
77
+ .block-browser {
78
+ container-type: inline-size;
79
+ }
80
+
81
+ .available-blocks-grid {
82
+ display: grid;
83
+ grid-template-columns: 1fr;
84
+ gap: 0.75rem;
85
+ padding: 1.25rem;
86
+ }
87
+
88
+ @container (min-width: 360px) {
89
+ .available-blocks-grid {
90
+ grid-template-columns: repeat(2, minmax(0, 1fr));
91
+ }
92
+ }
93
+
94
+ @container (min-width: 560px) {
95
+ .available-blocks-grid {
96
+ grid-template-columns: repeat(3, minmax(0, 1fr));
97
+ }
98
+ }
99
+ </style>
@@ -16,7 +16,11 @@
16
16
  <span>Clear</span>
17
17
  </div>
18
18
  <!-- Description -->
19
- <div v-if="fieldConfig.description" :title="fieldConfig.description" class="cursor-default">
19
+ <div
20
+ v-if="fieldConfig.description && fieldConfig.type !== 'info'"
21
+ :title="fieldConfig.description"
22
+ class="cursor-default"
23
+ >
20
24
  <InformationCircleIcon class="size-4 text-zinc-500" />
21
25
  </div>
22
26
  </div>
@@ -58,7 +62,9 @@
58
62
 
59
63
  <!-- Range input -->
60
64
  <div v-else-if="fieldConfig.type === 'range'" class="flex items-center gap-2">
61
- <span class="rounded-full bg-zinc-100 px-2 py-1 text-sm font-bold text-zinc-600">{{ fieldValue }}</span>
65
+ <span class="rounded-full bg-zinc-100 px-2 py-1 text-sm font-bold text-zinc-600"
66
+ >{{ fieldValue }}{{ fieldConfig.valueSuffix || "" }}</span
67
+ >
62
68
  <input
63
69
  v-model="fieldValue"
64
70
  type="range"
@@ -75,14 +81,14 @@
75
81
  <label
76
82
  v-for="option in fieldConfig.options"
77
83
  :key="`${fieldName}_${option.value}`"
78
- class="flex cursor-pointer items-center gap-2 rounded-md border p-2"
84
+ class="flex cursor-pointer items-center gap-2 rounded-md border border-gray-300 p-2"
79
85
  >
80
86
  <input
81
87
  :id="`${fieldName}_${option.value}`"
82
88
  v-model="checkboxValues"
83
89
  :value="option.value"
84
90
  type="checkbox"
85
- class="form-control"
91
+ class="form-control appearance-none"
86
92
  :disabled="!editable"
87
93
  />
88
94
  <span class="text-sm">{{ option.label }}</span>
@@ -91,39 +97,37 @@
91
97
 
92
98
  <!-- Radio -->
93
99
  <div v-else-if="fieldConfig.type === 'radio'" class="form-control flex flex-col gap-2">
94
- <label
95
- v-for="option in fieldConfig.options"
96
- :key="`${fieldName}_${option.value}`"
97
- class="flex cursor-pointer items-center gap-2 rounded-md border p-2"
98
- >
99
- <input
100
- :id="`${fieldName}_${option.value}`"
101
- v-model="fieldValue"
102
- :value="option.value"
103
- type="radio"
104
- class="form-control"
105
- :disabled="!editable"
106
- />
107
- <span class="text-sm">{{ option.label }}</span>
108
- </label>
100
+ <div v-for="option in fieldConfig.options" :key="`${fieldName}_${option.value}`">
101
+ <label
102
+ :for="`${fieldName}_${option.value}`"
103
+ class="has-checked:border-blue-600 has-checked:ring-1 has-checked:ring-blue-600 flex cursor-pointer items-center justify-between gap-4 rounded border border-gray-300 bg-white p-3 text-sm font-medium shadow-sm transition-colors hover:bg-gray-50"
104
+ >
105
+ <p class="text-gray-700">{{ option.label }}</p>
106
+
107
+ <input
108
+ :id="`${fieldName}_${option.value}`"
109
+ v-model="fieldValue"
110
+ type="radio"
111
+ :name="fieldName"
112
+ class="sr-only"
113
+ :value="option.value"
114
+ :checked="fieldValue === option.value"
115
+ />
116
+ </label>
117
+ </div>
109
118
  </div>
110
119
 
111
120
  <!-- Boolean toggle -->
112
121
  <template v-else-if="fieldConfig.type === 'boolean'">
113
122
  <label
114
- tabindex="0"
115
- class="group inline-flex h-7 w-14 cursor-pointer items-center gap-2 rounded-full p-1 transition-all duration-200"
116
- :class="!!fieldValue ? 'bg-emerald-700 hover:bg-emerald-800' : 'bg-zinc-200 hover:bg-zinc-300'"
123
+ :for="fieldName"
124
+ class="has-checked:bg-emerald-700 w-13 relative block h-7 cursor-pointer rounded-full bg-gray-300 transition-colors [-webkit-tap-highlight-color:transparent] hover:bg-gray-400/75"
117
125
  >
118
- <input
119
- :id="fieldName"
120
- v-model="fieldValue"
121
- tabindex="-1"
122
- type="checkbox"
123
- class="ml-0 size-5 cursor-pointer rounded-full !border-none !bg-white !outline-none !ring-0 !ring-offset-0 transition-all duration-200 checked:ml-7"
124
- :disabled="!editable"
125
- />
126
- <span class="hidden text-sm">{{ fieldConfig.label }}</span>
126
+ <input :id="fieldName" v-model="fieldValue" type="checkbox" class="peer sr-only" :disabled="!editable" />
127
+
128
+ <span
129
+ class="absolute inset-y-0 start-0 m-1 size-5 rounded-full bg-white transition-[inset-inline-start] peer-checked:start-6"
130
+ ></span>
127
131
  </label>
128
132
  </template>
129
133
 
@@ -137,11 +141,36 @@
137
141
  />
138
142
  </div>
139
143
 
144
+ <!-- Object Input -->
145
+ <div v-else-if="fieldConfig.type === 'object' && fieldConfig.objectFields">
146
+ <div class="mt-3 border-t border-gray-300 pt-3">
147
+ <BlockEditorFields
148
+ v-model="objectFieldValue"
149
+ :fields="fieldConfig.objectFields"
150
+ :editable="editable"
151
+ :nested="true"
152
+ />
153
+ </div>
154
+ </div>
155
+
156
+ <!-- Image -->
157
+ <div v-else-if="fieldConfig.type === 'image'">
158
+ <BlockImageNode v-model="fieldValue" :fieldConfig="fieldConfig" :fieldName="fieldName" :editable="editable" />
159
+ </div>
160
+
140
161
  <!-- Margin -->
141
162
  <div v-else-if="fieldConfig.type === 'margin'">
142
163
  <BlockMarginNode v-model="fieldValue" :fieldConfig="fieldConfig" :fieldName="fieldName" :editable="editable" />
143
164
  </div>
144
165
 
166
+ <!-- Info -->
167
+ <template v-else-if="fieldConfig.type === 'info'">
168
+ <div class="font-base mt-1 rounded-md bg-zinc-100 p-2 text-sm text-zinc-600 md:p-3">
169
+ <InformationCircleIcon class="float-left mr-1 mt-0.5 inline-block size-4" />
170
+ {{ fieldConfig.description }}
171
+ </div>
172
+ </template>
173
+
145
174
  <!-- Default fallback -->
146
175
  <template v-else>
147
176
  <input v-model="textFieldValue" type="text" class="form-control" :disabled="!editable" />
@@ -159,7 +188,10 @@ import { ref, computed, watch } from "vue";
159
188
  import type { EditorFieldConfig } from "../../util/fieldConfig";
160
189
  import BlockRepeaterNode from "../BlockRepeaterFieldNode/BlockRepeaterNode.vue";
161
190
  import BlockMarginNode from "../BlockMarginFieldNode/BlockMarginNode.vue";
191
+ import BlockImageNode from "../BlockImageFieldNode/BlockImageNode.vue";
192
+ import BlockEditorFields from "../BlockEditorFields/BlockEditorFields.vue";
162
193
  import { InformationCircleIcon } from "@heroicons/vue/24/outline";
194
+ import { validateField as validateFieldUtil } from "../../util/validation";
163
195
  import * as yup from "yup";
164
196
 
165
197
  const fieldValue = defineModel<any>();
@@ -192,6 +224,20 @@ const checkboxValues = computed({
192
224
  },
193
225
  });
194
226
 
227
+ // Computed property for object field values - ensure it's always an object
228
+ const objectFieldValue = computed({
229
+ get: () => {
230
+ // Ensure fieldValue is always an object for object fields
231
+ if (typeof fieldValue.value !== "object" || fieldValue.value === null || Array.isArray(fieldValue.value)) {
232
+ return {};
233
+ }
234
+ return fieldValue.value;
235
+ },
236
+ set: (newValue: Record<string, any>) => {
237
+ fieldValue.value = newValue;
238
+ },
239
+ });
240
+
195
241
  // Computed property to handle object/array conversion for text inputs
196
242
  const textFieldValue = computed({
197
243
  get: () => {
@@ -247,12 +293,12 @@ const canClearFieldValue = computed(() => {
247
293
  * minItems // For repeater fields
248
294
  * maxItems // For repeater fields
249
295
  * required // For all fields
296
+ * validator // Custom validation function
250
297
  */
251
298
 
252
299
  async function validateField(): Promise<void> {
253
- if (!props.fieldConfig.validator) return;
254
300
  try {
255
- const result = await props.fieldConfig.validator(fieldValue.value);
301
+ const result = await validateFieldUtil(fieldValue.value, props.fieldConfig);
256
302
  // True = valid, false = invalid, string = error message
257
303
  if (result === true) {
258
304
  validationErrorMessage.value = null;
@@ -270,7 +316,6 @@ async function validateField(): Promise<void> {
270
316
  function clearFieldValue(): void {
271
317
  fieldValue.value = null;
272
318
  validationErrorMessage.value = null;
273
- // validateField();
274
319
  }
275
320
 
276
321
  // Watch the field value and validate it
@@ -1,7 +1,11 @@
1
1
  <template>
2
2
  <div class="section-editor-fields">
3
3
  <!-- Field group tabs-->
4
- <div v-if="editorFieldGroups.length" class="field-group-tabs flex gap-2 border-b px-5 pt-3">
4
+ <div
5
+ v-if="editorFieldGroups.length"
6
+ class="field-group-tabs flex gap-2 border-b border-gray-300"
7
+ :class="nested ? 'px-0 pt-0' : 'px-5 pt-3'"
8
+ >
5
9
  <button
6
10
  v-for="fieldGroupName in editorFieldGroups"
7
11
  :key="`fg_${fieldGroupName}`"
@@ -14,7 +18,11 @@
14
18
  </div>
15
19
 
16
20
  <!-- Fields -->
17
- <div v-if="blockData && Object.keys(editorFields).length > 0" class="flex flex-col gap-3 p-5">
21
+ <div
22
+ v-if="blockData && Object.keys(editorFields).length > 0"
23
+ class="flex flex-col gap-3"
24
+ :class="nested ? 'p-0' : 'p-5'"
25
+ >
18
26
  <div v-for="(fieldConfig, fieldName) in editorFields" :key="fieldName" class="prop-field">
19
27
  <BlockEditorFieldNode
20
28
  v-model="blockData[fieldName]"
@@ -26,8 +34,8 @@
26
34
  </div>
27
35
 
28
36
  <!-- No fields -->
29
- <div v-else class="p-5">
30
- <div class="rounded-lg bg-zinc-100 px-4 py-3 text-sm font-bold text-zinc-500">
37
+ <div v-else :class="nested ? 'p-0' : 'p-5'">
38
+ <div class="rounded-lg bg-zinc-100 px-4 py-3 text-sm font-medium text-zinc-600">
31
39
  <p>
32
40
  {{ isLayoutBlock ? "No settings available for this layout." : "No options available for this block." }}
33
41
  </p>
@@ -47,6 +55,7 @@ const { fields, editable } = defineProps<{
47
55
  fields?: Record<string, EditorFieldConfig>;
48
56
  editable: boolean;
49
57
  isLayoutBlock?: boolean;
58
+ nested?: boolean;
50
59
  }>();
51
60
 
52
61
  const editorFields = computed(() => {
@@ -79,13 +88,13 @@ const editorFieldGroups = computed(() => {
79
88
 
80
89
  watch(editorFieldGroups, () => {
81
90
  if (editorFieldGroups.value.length > 0) {
82
- activeFieldGroup.value = editorFieldGroups.value[0];
91
+ activeFieldGroup.value = editorFieldGroups.value[0] || "";
83
92
  }
84
93
  });
85
94
 
86
95
  onBeforeMount(() => {
87
96
  if (editorFieldGroups.value.length > 0) {
88
- activeFieldGroup.value = editorFieldGroups.value[0];
97
+ activeFieldGroup.value = editorFieldGroups.value[0] || "";
89
98
  }
90
99
  });
91
100
  </script>