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.
- package/README.md +493 -20
- package/dist/style.css +1 -1
- package/dist/vue-wswg-editor.es.js +2431 -1895
- package/package.json +15 -8
- package/src/assets/styles/_mixins.scss +12 -17
- package/src/components/AddBlockItem/AddBlockItem.vue +5 -5
- package/src/components/BlockBrowser/BlockBrowser.vue +33 -3
- package/src/components/BlockEditorFieldNode/BlockEditorFieldNode.vue +79 -34
- package/src/components/BlockEditorFields/BlockEditorFields.vue +15 -6
- package/src/components/BlockImageFieldNode/BlockImageNode.vue +373 -0
- package/src/components/BlockMarginFieldNode/BlockMarginNode.vue +6 -4
- package/src/components/BlockRepeaterFieldNode/BlockRepeaterNode.vue +4 -2
- package/src/components/BrowserNavigation/BrowserNavigation.vue +2 -2
- package/src/components/EmptyState/EmptyState.vue +1 -1
- package/src/components/PageBlockList/PageBlockList.vue +1 -9
- package/src/components/PageBuilderSidebar/PageBuilderSidebar.vue +29 -10
- package/src/components/PageBuilderToolbar/PageBuilderToolbar.vue +4 -4
- package/src/components/PageRenderer/PageRenderer.vue +76 -24
- package/src/components/PageRenderer/blockModules.ts +32 -3
- package/src/components/PageRenderer/layoutModules.ts +32 -0
- package/src/components/PageSettings/PageSettings.vue +19 -11
- package/src/components/ResizeHandle/ResizeHandle.vue +10 -10
- package/src/components/WswgJsonEditor/WswgJsonEditor.vue +323 -117
- package/src/index.ts +2 -2
- package/src/style.css +10 -3
- package/src/types/Block.d.ts +1 -1
- package/src/util/fieldConfig.ts +29 -0
- package/src/util/registry.ts +30 -23
- package/src/util/validation.ts +178 -23
- package/src/vite-plugin.ts +27 -2
- package/types/vue-wswg-editor.d.ts +161 -0
- package/dist/types/components/AddBlockItem/AddBlockItem.vue.d.ts +0 -6
- package/dist/types/components/BlockBrowser/BlockBrowser.vue.d.ts +0 -2
- package/dist/types/components/BlockComponent/BlockComponent.vue.d.ts +0 -15
- package/dist/types/components/BlockEditorFieldNode/BlockEditorFieldNode.vue.d.ts +0 -15
- package/dist/types/components/BlockEditorFields/BlockEditorFields.vue.d.ts +0 -15
- package/dist/types/components/BlockMarginFieldNode/BlockMarginNode.vue.d.ts +0 -23
- package/dist/types/components/BlockRepeaterFieldNode/BlockRepeaterNode.vue.d.ts +0 -15
- package/dist/types/components/BrowserNavigation/BrowserNavigation.vue.d.ts +0 -5
- package/dist/types/components/EmptyState/EmptyState.vue.d.ts +0 -15
- package/dist/types/components/PageBlockList/PageBlockList.vue.d.ts +0 -19
- package/dist/types/components/PageBuilderSidebar/PageBuilderSidebar.vue.d.ts +0 -30
- package/dist/types/components/PageBuilderToolbar/PageBuilderToolbar.vue.d.ts +0 -28
- package/dist/types/components/PageRenderer/PageRenderer.vue.d.ts +0 -6
- package/dist/types/components/PageRenderer/blockModules.d.ts +0 -1
- package/dist/types/components/PageSettings/PageSettings.vue.d.ts +0 -15
- package/dist/types/components/ResizeHandle/ResizeHandle.vue.d.ts +0 -6
- package/dist/types/components/WswgJsonEditor/WswgJsonEditor.test.d.ts +0 -1
- package/dist/types/components/WswgJsonEditor/WswgJsonEditor.vue.d.ts +0 -40
- package/dist/types/index.d.ts +0 -7
- package/dist/types/util/fieldConfig.d.ts +0 -82
- package/dist/types/util/helpers.d.ts +0 -28
- package/dist/types/util/registry.d.ts +0 -20
- package/dist/types/util/validation.d.ts +0 -15
- package/dist/types/vite-plugin.d.ts +0 -9
- 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.
|
|
3
|
+
"version": "0.0.11",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"main": "
|
|
6
|
-
"module": "
|
|
7
|
-
"typings": "
|
|
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": "./
|
|
11
|
-
"require": "./
|
|
12
|
-
"types": "./
|
|
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:
|
|
8
|
+
padding-top: var(--block-margin-sm, 2rem);
|
|
14
9
|
|
|
15
10
|
&::before {
|
|
16
11
|
top: 0;
|
|
17
|
-
height:
|
|
12
|
+
height: var(--block-margin-sm, 2rem);
|
|
18
13
|
}
|
|
19
14
|
}
|
|
20
15
|
|
|
21
16
|
&.margin-top-md {
|
|
22
|
-
padding-top:
|
|
17
|
+
padding-top: var(--block-margin-md, 4rem);
|
|
23
18
|
|
|
24
19
|
&::before {
|
|
25
20
|
top: 0;
|
|
26
|
-
height:
|
|
21
|
+
height: var(--block-margin-md, 4rem);
|
|
27
22
|
}
|
|
28
23
|
}
|
|
29
24
|
|
|
30
25
|
&.margin-top-lg {
|
|
31
|
-
padding-top:
|
|
26
|
+
padding-top: var(--block-margin-lg, 6rem);
|
|
32
27
|
|
|
33
28
|
&::before {
|
|
34
29
|
top: 0;
|
|
35
|
-
height:
|
|
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:
|
|
36
|
+
padding-bottom: var(--block-margin-sm, 2rem);
|
|
42
37
|
|
|
43
38
|
&::after {
|
|
44
39
|
bottom: 0;
|
|
45
|
-
height:
|
|
40
|
+
height: var(--block-margin-sm, 2rem);
|
|
46
41
|
}
|
|
47
42
|
}
|
|
48
43
|
|
|
49
44
|
&.margin-bottom-md {
|
|
50
|
-
padding-bottom:
|
|
45
|
+
padding-bottom: var(--block-margin-md, 4rem);
|
|
51
46
|
|
|
52
47
|
&::after {
|
|
53
48
|
bottom: 0;
|
|
54
|
-
height:
|
|
49
|
+
height: var(--block-margin-md, 4rem);
|
|
55
50
|
}
|
|
56
51
|
}
|
|
57
52
|
|
|
58
53
|
&.margin-bottom-lg {
|
|
59
|
-
padding-bottom:
|
|
54
|
+
padding-bottom: var(--block-margin-lg, 6rem);
|
|
60
55
|
|
|
61
56
|
&::after {
|
|
62
57
|
bottom: 0;
|
|
63
|
-
height:
|
|
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-
|
|
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
|
-
<!--
|
|
18
|
-
<div v-else-if="block.
|
|
19
|
-
<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="
|
|
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
|
|
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="
|
|
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
|
|
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"
|
|
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
|
-
<
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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
|
-
|
|
115
|
-
class="
|
|
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
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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
|
|
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
|
|
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
|
|
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-
|
|
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>
|