vue-wswg-editor 0.0.11 → 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.
- package/README.md +23 -8
- package/dist/style.css +1 -1
- package/dist/types/components/AddBlockItem/AddBlockItem.vue.d.ts +6 -0
- package/dist/types/components/BlockBrowser/BlockBrowser.vue.d.ts +2 -0
- package/dist/types/components/BlockComponent/BlockComponent.vue.d.ts +15 -0
- package/dist/types/components/BlockEditorFieldNode/BlockEditorFieldNode.vue.d.ts +15 -0
- package/dist/types/components/BlockEditorFields/BlockEditorFields.vue.d.ts +17 -0
- package/dist/types/components/BlockImageFieldNode/BlockImageNode.vue.d.ts +19 -0
- package/dist/types/components/BlockMarginFieldNode/BlockMarginNode.vue.d.ts +23 -0
- package/dist/types/components/BlockRepeaterFieldNode/BlockRepeaterNode.vue.d.ts +15 -0
- package/dist/types/components/BrowserNavigation/BrowserNavigation.vue.d.ts +5 -0
- package/dist/types/components/EditorPageRenderer/EditorPageRenderer.vue.d.ts +21 -0
- package/dist/types/components/EmptyState/EmptyState.vue.d.ts +9 -0
- package/dist/types/components/IframePreview/IframePreview.vue.d.ts +26 -0
- package/dist/types/components/IframePreview/iframeContent.d.ts +9 -0
- package/dist/types/components/IframePreview/iframePreviewApp.d.ts +36 -0
- package/dist/types/components/IframePreview/messageHandler.d.ts +55 -0
- package/dist/types/components/IframePreview/types.d.ts +77 -0
- package/dist/types/components/PageBlockList/PageBlockList.vue.d.ts +19 -0
- package/dist/types/components/PageBuilderSidebar/PageBuilderSidebar.vue.d.ts +37 -0
- package/dist/types/components/PageBuilderToolbar/PageBuilderToolbar.vue.d.ts +28 -0
- package/dist/types/components/PageRenderer/PageRenderer.vue.d.ts +15 -0
- package/dist/types/components/PageSettings/PageSettings.vue.d.ts +19 -0
- package/dist/types/components/ResizeHandle/ResizeHandle.vue.d.ts +6 -0
- package/dist/types/components/WswgPageBuilder/WswgPageBuilder.test.d.ts +1 -0
- package/dist/types/components/WswgPageBuilder/WswgPageBuilder.vue.d.ts +38 -0
- package/dist/types/index.d.ts +13 -0
- package/dist/types/util/fieldConfig.d.ts +87 -0
- package/dist/types/util/helpers.d.ts +28 -0
- package/dist/types/util/registry.d.ts +27 -0
- package/dist/types/util/theme-registry.d.ts +42 -0
- package/dist/types/util/validation.d.ts +26 -0
- package/dist/types/vite-plugin.d.ts +9 -0
- package/dist/vite-plugin.js +80 -0
- package/dist/vue-wswg-editor.es.js +2854 -2006
- package/package.json +1 -2
- package/src/assets/styles/_mixins.scss +15 -0
- package/src/components/AddBlockItem/AddBlockItem.vue +13 -4
- package/src/components/BlockBrowser/BlockBrowser.vue +5 -5
- package/src/components/BlockComponent/BlockComponent.vue +23 -50
- package/src/components/BlockEditorFieldNode/BlockEditorFieldNode.vue +12 -10
- package/src/components/BlockEditorFields/BlockEditorFields.vue +24 -4
- package/src/components/BlockRepeaterFieldNode/BlockRepeaterNode.vue +9 -4
- package/src/components/BrowserNavigation/BrowserNavigation.vue +1 -1
- package/src/components/EditorPageRenderer/EditorPageRenderer.vue +641 -0
- package/src/components/EmptyState/EmptyState.vue +3 -12
- package/src/components/IframePreview/IframePreview.vue +211 -0
- package/src/components/IframePreview/iframeContent.ts +210 -0
- package/src/components/IframePreview/iframePreviewApp.ts +221 -0
- package/src/components/IframePreview/messageHandler.ts +219 -0
- package/src/components/IframePreview/types.ts +126 -0
- package/src/components/PageBlockList/PageBlockList.vue +8 -6
- package/src/components/PageBuilderSidebar/PageBuilderSidebar.vue +5 -3
- package/src/components/PageRenderer/PageRenderer.vue +9 -33
- package/src/components/PageSettings/PageSettings.vue +10 -6
- package/src/components/ResizeHandle/ResizeHandle.vue +68 -10
- package/src/components/{WswgJsonEditor/WswgJsonEditor.test.ts → WswgPageBuilder/WswgPageBuilder.test.ts} +8 -8
- package/src/components/WswgPageBuilder/WswgPageBuilder.vue +375 -0
- package/src/index.ts +10 -2
- package/src/shims.d.ts +4 -0
- package/src/types/Theme.d.ts +15 -0
- package/src/util/registry.ts +2 -2
- package/src/util/theme-registry.ts +397 -0
- package/src/util/validation.ts +104 -13
- package/src/vite-plugin.ts +8 -4
- package/types/vue-wswg-editor.d.ts +4 -0
- package/src/components/PageRenderer/blockModules-alternative.ts.example +0 -9
- package/src/components/PageRenderer/blockModules-manual.ts.example +0 -19
- package/src/components/PageRenderer/blockModules-runtime.ts.example +0 -23
- package/src/components/PageRenderer/blockModules.ts +0 -32
- package/src/components/PageRenderer/layoutModules.ts +0 -32
- package/src/components/WswgJsonEditor/WswgJsonEditor.vue +0 -595
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vue-wswg-editor",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.13",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/vue-wswg-editor.es.js",
|
|
6
6
|
"module": "./dist/vue-wswg-editor.es.js",
|
|
@@ -88,7 +88,6 @@
|
|
|
88
88
|
"typescript": "~5.8.3",
|
|
89
89
|
"vite": "^5.4.19",
|
|
90
90
|
"vitepress": "^1.6.4",
|
|
91
|
-
"vitepress-demo-plugin": "^1.5.1",
|
|
92
91
|
"vitest": "^2.1.9",
|
|
93
92
|
"vue-tsc": "^2.2.12",
|
|
94
93
|
"yup": "^1.7.1"
|
|
@@ -66,3 +66,18 @@
|
|
|
66
66
|
content: none;
|
|
67
67
|
}
|
|
68
68
|
}
|
|
69
|
+
|
|
70
|
+
// Apply Overlay Styles (Helper)
|
|
71
|
+
// Applies the backdrop styles directly - useful when state classes are on parent
|
|
72
|
+
// Uses CSS custom properties from .wswg-page-builder element
|
|
73
|
+
// Usage: @include backdrop-apply($backdrop-color, $border-color);
|
|
74
|
+
@mixin overlay-apply(
|
|
75
|
+
$backdrop-color: var(--block-backdrop-color, transparent),
|
|
76
|
+
$border-color: var(--block-border-color, #638ef1),
|
|
77
|
+
$border-width: var(--block-border-width, 4px),
|
|
78
|
+
$border-style: var(--block-border-style, solid)
|
|
79
|
+
) {
|
|
80
|
+
outline: $border-width $border-style $border-color;
|
|
81
|
+
outline-offset: calc(-1 * var(--block-border-width, 2px));
|
|
82
|
+
box-shadow: inset 0 0 0 9999px $backdrop-color;
|
|
83
|
+
}
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
<script setup lang="ts">
|
|
30
30
|
import { computed, defineProps, ref } from "vue";
|
|
31
31
|
import type { Block } from "../../types/Block";
|
|
32
|
-
import {
|
|
32
|
+
import { getBlockThumbnail } from "../../util/theme-registry";
|
|
33
33
|
import { CubeTransparentIcon } from "@heroicons/vue/24/outline";
|
|
34
34
|
|
|
35
35
|
const props = defineProps<{
|
|
@@ -38,13 +38,22 @@ const props = defineProps<{
|
|
|
38
38
|
|
|
39
39
|
const thumbnailError = ref<boolean>(false);
|
|
40
40
|
const thumbnailUrl = computed(() => {
|
|
41
|
-
return
|
|
41
|
+
return getBlockThumbnail(props.block.path);
|
|
42
42
|
});
|
|
43
43
|
|
|
44
44
|
function handleDragStart(event: DragEvent, block: Block) {
|
|
45
45
|
if (!event.dataTransfer) return;
|
|
46
|
-
// Store the block type in dataTransfer
|
|
47
|
-
|
|
46
|
+
// Store the block type in dataTransfer (use block.type as fallback if __name not available)
|
|
47
|
+
const blockType = block.__name || block.type;
|
|
48
|
+
event.dataTransfer.setData("block-type", blockType);
|
|
48
49
|
event.dataTransfer.effectAllowed = "move";
|
|
50
|
+
// Allow dragging to iframes
|
|
51
|
+
event.dataTransfer.setData("text/plain", blockType);
|
|
52
|
+
|
|
53
|
+
// Check if the dragged element has data-prevent-drop attribute
|
|
54
|
+
const draggedElement = event.target as HTMLElement;
|
|
55
|
+
if (draggedElement?.hasAttribute("data-prevent-drop")) {
|
|
56
|
+
event.dataTransfer.setData("prevent-drop", "true");
|
|
57
|
+
}
|
|
49
58
|
}
|
|
50
59
|
</script>
|
|
@@ -28,15 +28,15 @@
|
|
|
28
28
|
|
|
29
29
|
<script setup lang="ts">
|
|
30
30
|
import { computed, onMounted, ref } from "vue";
|
|
31
|
-
import {
|
|
31
|
+
import { themeBlocks } from "../../util/theme-registry";
|
|
32
32
|
import AddBlockItem from "../AddBlockItem/AddBlockItem.vue";
|
|
33
33
|
import type { Block } from "../../types/Block";
|
|
34
34
|
import Sortable from "sortablejs";
|
|
35
35
|
|
|
36
36
|
const blockSearch = ref("");
|
|
37
37
|
const filteredBlocks = computed(() => {
|
|
38
|
-
if (!
|
|
39
|
-
return Object.values(
|
|
38
|
+
if (!themeBlocks.value) return [];
|
|
39
|
+
return Object.values(themeBlocks.value).filter((block: Block) => {
|
|
40
40
|
// against block name and label
|
|
41
41
|
return (
|
|
42
42
|
block.type?.toLowerCase().includes(blockSearch.value.toLowerCase()) ||
|
|
@@ -46,8 +46,8 @@ const filteredBlocks = computed(() => {
|
|
|
46
46
|
});
|
|
47
47
|
|
|
48
48
|
const blockCount = computed(() => {
|
|
49
|
-
if (!
|
|
50
|
-
return Object.values(
|
|
49
|
+
if (!themeBlocks.value) return 0;
|
|
50
|
+
return Object.values(themeBlocks.value).length;
|
|
51
51
|
});
|
|
52
52
|
|
|
53
53
|
function initSortable() {
|
|
@@ -10,19 +10,19 @@
|
|
|
10
10
|
:data-block-id="block.id"
|
|
11
11
|
>
|
|
12
12
|
<div
|
|
13
|
-
v-if="
|
|
14
|
-
class="absolute -top-3 right-4 z-10 rounded-full bg-blue-500 px-2 py-1 text-xs text-white"
|
|
15
|
-
>
|
|
16
|
-
<p>Editing</p>
|
|
17
|
-
</div>
|
|
18
|
-
<div
|
|
19
|
-
v-if="pageBuilderBlocks[toCamelCase(block.type)]"
|
|
13
|
+
v-if="blockComponent"
|
|
20
14
|
class="block-component"
|
|
21
15
|
@mouseenter="emit('hoverBlock', block.id)"
|
|
22
16
|
@mouseleave="emit('hoverBlock', null)"
|
|
23
17
|
@click="emit('clickBlock', block)"
|
|
24
18
|
>
|
|
25
|
-
<
|
|
19
|
+
<div
|
|
20
|
+
v-if="activeBlock?.id === block.id"
|
|
21
|
+
class="editing-badge absolute right-2 top-2 z-40 rounded-full px-2 py-1 text-xs text-white"
|
|
22
|
+
>
|
|
23
|
+
<span>Editing</span>
|
|
24
|
+
</div>
|
|
25
|
+
<component :is="blockComponent" v-bind="block" ref="blockComponentRef" />
|
|
26
26
|
</div>
|
|
27
27
|
<div
|
|
28
28
|
v-else
|
|
@@ -41,9 +41,8 @@
|
|
|
41
41
|
</template>
|
|
42
42
|
|
|
43
43
|
<script setup lang="ts">
|
|
44
|
-
import { useTemplateRef } from "vue";
|
|
45
|
-
import {
|
|
46
|
-
import { pageBuilderBlocks } from "../../util/registry";
|
|
44
|
+
import { computed, useTemplateRef } from "vue";
|
|
45
|
+
import { getBlock } from "../../util/theme-registry";
|
|
47
46
|
import type { Block } from "../../types/Block";
|
|
48
47
|
import { toCamelCase } from "../../util/helpers";
|
|
49
48
|
import { onKeyStroke } from "@vueuse/core";
|
|
@@ -62,6 +61,10 @@ const props = defineProps<{
|
|
|
62
61
|
|
|
63
62
|
const blockComponentRef = useTemplateRef<HTMLElement | null>("blockComponentRef");
|
|
64
63
|
|
|
64
|
+
const blockComponent = computed<Block | undefined>(() => {
|
|
65
|
+
return getBlock(props.block.type) || undefined;
|
|
66
|
+
});
|
|
67
|
+
|
|
65
68
|
// Get the margin class for the block
|
|
66
69
|
// Margin is an object with top and bottom properties
|
|
67
70
|
// margin classses are formatted as `margin-<direction>-<size>`
|
|
@@ -78,16 +81,6 @@ function getMarginClass(block: Block): string {
|
|
|
78
81
|
return [getClass(top, "top"), getClass(bottom, "bottom")].join(" ");
|
|
79
82
|
}
|
|
80
83
|
|
|
81
|
-
// Click outside detection
|
|
82
|
-
onClickOutside(
|
|
83
|
-
blockComponentRef,
|
|
84
|
-
() => {
|
|
85
|
-
// Unset active block
|
|
86
|
-
emit("clickBlock", null);
|
|
87
|
-
},
|
|
88
|
-
{ ignore: ["#page-builder-sidebar", "#page-builder-resize-handle", "button"] }
|
|
89
|
-
);
|
|
90
|
-
|
|
91
84
|
// On escape key press
|
|
92
85
|
onKeyStroke("Escape", () => {
|
|
93
86
|
if (!props.activeBlock) return;
|
|
@@ -117,8 +110,8 @@ onKeyStroke("Escape", () => {
|
|
|
117
110
|
font-weight: 500;
|
|
118
111
|
color: #888017;
|
|
119
112
|
content: "Spacing";
|
|
120
|
-
background-color: #
|
|
121
|
-
border: 2px dashed #
|
|
113
|
+
background-color: var(--margin-color, #faf6d5e0);
|
|
114
|
+
border: 2px dashed var(--margin-border-color, #cbc59c);
|
|
122
115
|
border-radius: 7px;
|
|
123
116
|
opacity: 0;
|
|
124
117
|
transform: scaleY(0.9) scaleX(0.98);
|
|
@@ -128,32 +121,16 @@ onKeyStroke("Escape", () => {
|
|
|
128
121
|
.block-component {
|
|
129
122
|
position: relative;
|
|
130
123
|
cursor: pointer;
|
|
124
|
+
}
|
|
131
125
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
position: absolute;
|
|
135
|
-
top: 0;
|
|
136
|
-
left: 0;
|
|
137
|
-
z-index: 2;
|
|
138
|
-
width: 100%;
|
|
139
|
-
height: 100%;
|
|
140
|
-
pointer-events: none;
|
|
141
|
-
outline: 2px dashed #638ef1;
|
|
142
|
-
outline-offset: -2px;
|
|
143
|
-
content: "";
|
|
144
|
-
background-color: #9fd0f643;
|
|
145
|
-
opacity: 0;
|
|
146
|
-
}
|
|
126
|
+
.editing-badge {
|
|
127
|
+
background-color: var(--block-badge-color, #638ef1);
|
|
147
128
|
}
|
|
148
129
|
|
|
149
|
-
// Active state
|
|
130
|
+
// Active state - apply overlay and show margin spacing
|
|
150
131
|
&.active-block {
|
|
151
132
|
.block-component {
|
|
152
|
-
|
|
153
|
-
background-color: #9fd0f643;
|
|
154
|
-
border-color: #638ef1;
|
|
155
|
-
opacity: 1;
|
|
156
|
-
}
|
|
133
|
+
@include overlay-apply;
|
|
157
134
|
}
|
|
158
135
|
|
|
159
136
|
// Show the margin spacing overlay
|
|
@@ -163,14 +140,10 @@ onKeyStroke("Escape", () => {
|
|
|
163
140
|
}
|
|
164
141
|
}
|
|
165
142
|
|
|
166
|
-
// Hovered state
|
|
143
|
+
// Hovered state - apply overlay and show margin spacing
|
|
167
144
|
&.hovered-block {
|
|
168
145
|
.block-component {
|
|
169
|
-
|
|
170
|
-
background-color: #9fd0f643;
|
|
171
|
-
border-color: #638ef1;
|
|
172
|
-
opacity: 1;
|
|
173
|
-
}
|
|
146
|
+
@include overlay-apply;
|
|
174
147
|
}
|
|
175
148
|
|
|
176
149
|
// Show the margin spacing overlay
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<!-- Field -->
|
|
3
|
-
<div class="editor-field-node"
|
|
3
|
+
<div class="editor-field-node">
|
|
4
4
|
<div class="mb-1 flex items-center gap-1.5">
|
|
5
5
|
<!-- Label -->
|
|
6
6
|
<label class="mr-auto text-sm font-medium first-letter:uppercase">
|
|
@@ -121,7 +121,7 @@
|
|
|
121
121
|
<template v-else-if="fieldConfig.type === 'boolean'">
|
|
122
122
|
<label
|
|
123
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"
|
|
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 has-[:checked]:hover:bg-emerald-800"
|
|
125
125
|
>
|
|
126
126
|
<input :id="fieldName" v-model="fieldValue" type="checkbox" class="peer sr-only" :disabled="!editable" />
|
|
127
127
|
|
|
@@ -177,8 +177,11 @@
|
|
|
177
177
|
</template>
|
|
178
178
|
|
|
179
179
|
<!-- Validation error message -->
|
|
180
|
-
<div
|
|
181
|
-
|
|
180
|
+
<div
|
|
181
|
+
v-if="validationError && typeof validationError === 'string'"
|
|
182
|
+
class="rounded-sm bg-red-50 p-2 px-3 text-xs text-red-600"
|
|
183
|
+
>
|
|
184
|
+
{{ validationError }}
|
|
182
185
|
</div>
|
|
183
186
|
</div>
|
|
184
187
|
</template>
|
|
@@ -191,7 +194,7 @@ import BlockMarginNode from "../BlockMarginFieldNode/BlockMarginNode.vue";
|
|
|
191
194
|
import BlockImageNode from "../BlockImageFieldNode/BlockImageNode.vue";
|
|
192
195
|
import BlockEditorFields from "../BlockEditorFields/BlockEditorFields.vue";
|
|
193
196
|
import { InformationCircleIcon } from "@heroicons/vue/24/outline";
|
|
194
|
-
import { validateField as validateFieldUtil } from "../../util/validation";
|
|
197
|
+
import { validateField as validateFieldUtil, type ValidationResult } from "../../util/validation";
|
|
195
198
|
import * as yup from "yup";
|
|
196
199
|
|
|
197
200
|
const fieldValue = defineModel<any>();
|
|
@@ -202,7 +205,7 @@ const props = defineProps<{
|
|
|
202
205
|
editable: boolean;
|
|
203
206
|
}>();
|
|
204
207
|
|
|
205
|
-
const
|
|
208
|
+
const validationError = ref<string | null | ValidationResult>(null);
|
|
206
209
|
|
|
207
210
|
// Computed property for checkbox values (array of selected values)
|
|
208
211
|
const checkboxValues = computed({
|
|
@@ -301,21 +304,20 @@ async function validateField(): Promise<void> {
|
|
|
301
304
|
const result = await validateFieldUtil(fieldValue.value, props.fieldConfig);
|
|
302
305
|
// True = valid, false = invalid, string = error message
|
|
303
306
|
if (result === true) {
|
|
304
|
-
|
|
307
|
+
validationError.value = null;
|
|
305
308
|
} else {
|
|
306
309
|
// result is either false or a string error message
|
|
307
310
|
// TODO: i18n
|
|
308
|
-
|
|
311
|
+
validationError.value = result === false ? "Field is invalid" : result;
|
|
309
312
|
}
|
|
310
313
|
} catch (error) {
|
|
311
314
|
const validationErrorsList = error as yup.ValidationError;
|
|
312
|
-
|
|
315
|
+
validationError.value = validationErrorsList.errors[0] || "Field is invalid";
|
|
313
316
|
}
|
|
314
317
|
}
|
|
315
318
|
|
|
316
319
|
function clearFieldValue(): void {
|
|
317
320
|
fieldValue.value = null;
|
|
318
|
-
validationErrorMessage.value = null;
|
|
319
321
|
}
|
|
320
322
|
|
|
321
323
|
// Watch the field value and validate it
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
<!-- Field group tabs-->
|
|
4
4
|
<div
|
|
5
5
|
v-if="editorFieldGroups.length"
|
|
6
|
-
class="field-group-tabs flex gap-2 border-b border-gray-300"
|
|
6
|
+
class="field-group-tabs flex gap-2 overflow-x-auto border-b border-gray-300"
|
|
7
7
|
:class="nested ? 'px-0 pt-0' : 'px-5 pt-3'"
|
|
8
8
|
>
|
|
9
9
|
<button
|
|
@@ -51,11 +51,12 @@ import type { EditorFieldConfig } from "../../util/fieldConfig";
|
|
|
51
51
|
|
|
52
52
|
const blockData = defineModel<any>();
|
|
53
53
|
const activeFieldGroup = ref<string>("");
|
|
54
|
-
const { fields, editable } = defineProps<{
|
|
54
|
+
const { fields, editable, activeTab } = defineProps<{
|
|
55
55
|
fields?: Record<string, EditorFieldConfig>;
|
|
56
56
|
editable: boolean;
|
|
57
57
|
isLayoutBlock?: boolean;
|
|
58
58
|
nested?: boolean;
|
|
59
|
+
activeTab?: string;
|
|
59
60
|
}>();
|
|
60
61
|
|
|
61
62
|
const editorFields = computed(() => {
|
|
@@ -88,13 +89,32 @@ const editorFieldGroups = computed(() => {
|
|
|
88
89
|
|
|
89
90
|
watch(editorFieldGroups, () => {
|
|
90
91
|
if (editorFieldGroups.value.length > 0) {
|
|
91
|
-
|
|
92
|
+
// If activeTab prop is provided and exists in groups, use it; otherwise use first group
|
|
93
|
+
if (activeTab && editorFieldGroups.value.includes(activeTab)) {
|
|
94
|
+
activeFieldGroup.value = activeTab;
|
|
95
|
+
} else {
|
|
96
|
+
activeFieldGroup.value = editorFieldGroups.value[0] || "";
|
|
97
|
+
}
|
|
92
98
|
}
|
|
93
99
|
});
|
|
94
100
|
|
|
101
|
+
watch(
|
|
102
|
+
() => activeTab,
|
|
103
|
+
(newTab) => {
|
|
104
|
+
if (newTab && editorFieldGroups.value.includes(newTab)) {
|
|
105
|
+
activeFieldGroup.value = newTab;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
);
|
|
109
|
+
|
|
95
110
|
onBeforeMount(() => {
|
|
96
111
|
if (editorFieldGroups.value.length > 0) {
|
|
97
|
-
|
|
112
|
+
// If activeTab prop is provided and exists in groups, use it; otherwise use first group
|
|
113
|
+
if (activeTab && editorFieldGroups.value.includes(activeTab)) {
|
|
114
|
+
activeFieldGroup.value = activeTab;
|
|
115
|
+
} else {
|
|
116
|
+
activeFieldGroup.value = editorFieldGroups.value[0] || "";
|
|
117
|
+
}
|
|
98
118
|
}
|
|
99
119
|
});
|
|
100
120
|
</script>
|
|
@@ -127,10 +127,15 @@ const openRepeaterItems = ref<string[]>([]);
|
|
|
127
127
|
|
|
128
128
|
// Ensure fieldValue is always an array with id attribute
|
|
129
129
|
onBeforeMount(() => {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
}
|
|
130
|
+
// Ensure fieldValue is an array before calling map
|
|
131
|
+
if (!Array.isArray(fieldValue.value)) {
|
|
132
|
+
fieldValue.value = [];
|
|
133
|
+
}
|
|
134
|
+
fieldValue.value =
|
|
135
|
+
fieldValue.value?.map((item) => ({
|
|
136
|
+
id: item.id || crypto.randomUUID(),
|
|
137
|
+
...item,
|
|
138
|
+
})) ?? [];
|
|
134
139
|
});
|
|
135
140
|
|
|
136
141
|
function addItem() {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<!-- URL bar -->
|
|
3
3
|
<div class="browser-navigation-bar">
|
|
4
|
-
<div class="flex items-center justify-between
|
|
4
|
+
<div class="flex items-center justify-between bg-zinc-600 px-5 py-4">
|
|
5
5
|
<div class="flex w-full items-center gap-2 rounded-md bg-zinc-700 px-4 py-1.5 text-sm text-zinc-300">
|
|
6
6
|
<span class="block flex-1 truncate">{{ url }}</span>
|
|
7
7
|
|