@shwfed/nuxt 0.8.0 → 0.8.2
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/dist/module.json +1 -1
- package/dist/runtime/components/fields.d.vue.ts +6 -6
- package/dist/runtime/components/fields.vue +6 -6
- package/dist/runtime/components/fields.vue.d.ts +6 -6
- package/dist/runtime/components/table.d.vue.ts +4 -4
- package/dist/runtime/components/table.vue +1 -1
- package/dist/runtime/components/table.vue.d.ts +4 -4
- package/dist/runtime/components/ui/fields/Fields.d.vue.ts +6 -203
- package/dist/runtime/components/ui/fields/Fields.vue +31 -85
- package/dist/runtime/components/ui/fields/Fields.vue.d.ts +6 -203
- package/dist/runtime/components/ui/fields/schema.d.ts +210 -0
- package/dist/runtime/components/ui/fields/schema.js +66 -0
- package/dist/runtime/components/ui/fields-configurator/FieldsConfiguratorDialog.d.vue.ts +3 -3
- package/dist/runtime/components/ui/fields-configurator/FieldsConfiguratorDialog.vue +1439 -39
- package/dist/runtime/components/ui/fields-configurator/FieldsConfiguratorDialog.vue.d.ts +3 -3
- package/dist/runtime/components/ui/table/Table.d.vue.ts +3 -3
- package/dist/runtime/components/ui/table/Table.vue +15 -4
- package/dist/runtime/components/ui/table/Table.vue.d.ts +3 -3
- package/dist/runtime/components/ui/table/schema.d.ts +16 -4
- package/dist/runtime/components/ui/table/schema.js +7 -3
- package/package.json +1 -1
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import { useSortable } from "@vueuse/integrations/useSortable";
|
|
3
3
|
import { Icon } from "@iconify/vue";
|
|
4
|
-
import { computed, nextTick, ref, watch } from "vue";
|
|
4
|
+
import { computed, nextTick, ref, toRaw, watch } from "vue";
|
|
5
5
|
import { useI18n } from "vue-i18n";
|
|
6
|
+
import {
|
|
7
|
+
CalendarFieldC,
|
|
8
|
+
NumberFieldC,
|
|
9
|
+
SelectFieldC,
|
|
10
|
+
StringFieldC
|
|
11
|
+
} from "../fields/schema";
|
|
6
12
|
import { cn } from "../../../utils/cn";
|
|
7
|
-
import { getLocalizedText } from "../../../utils/coders";
|
|
8
13
|
import { Button } from "../button";
|
|
9
14
|
import {
|
|
10
15
|
Dialog,
|
|
@@ -14,7 +19,18 @@ import {
|
|
|
14
19
|
DialogHeader,
|
|
15
20
|
DialogTitle
|
|
16
21
|
} from "../dialog";
|
|
22
|
+
import {
|
|
23
|
+
DropdownMenu,
|
|
24
|
+
DropdownMenuContent,
|
|
25
|
+
DropdownMenuItem,
|
|
26
|
+
DropdownMenuTrigger
|
|
27
|
+
} from "../dropdown-menu";
|
|
28
|
+
import { IconPicker } from "../icon-picker";
|
|
29
|
+
import { Input } from "../input";
|
|
17
30
|
import Locale from "../locale/Locale.vue";
|
|
31
|
+
import { NativeSelect, NativeSelectOption } from "../native-select";
|
|
32
|
+
import { Switch } from "../switch";
|
|
33
|
+
import { Textarea } from "../textarea";
|
|
18
34
|
const props = defineProps({
|
|
19
35
|
fields: { type: Array, required: true }
|
|
20
36
|
});
|
|
@@ -22,41 +38,214 @@ const emit = defineEmits(["confirm"]);
|
|
|
22
38
|
const open = defineModel("open", { type: Boolean, ...{
|
|
23
39
|
default: false
|
|
24
40
|
} });
|
|
25
|
-
const { t
|
|
41
|
+
const { t } = useI18n();
|
|
26
42
|
const selectedItemId = ref("general");
|
|
27
43
|
const draftFields = ref([]);
|
|
28
44
|
const sortableListRef = ref(null);
|
|
29
45
|
const sortableItemIds = ref([]);
|
|
46
|
+
const validationErrors = ref({});
|
|
47
|
+
const fieldTypeOptions = computed(() => [
|
|
48
|
+
{ type: "string", label: t("field-type-string") },
|
|
49
|
+
{ type: "number", label: t("field-type-number") },
|
|
50
|
+
{ type: "select", label: t("field-type-select") },
|
|
51
|
+
{ type: "calendar", label: t("field-type-calendar") }
|
|
52
|
+
]);
|
|
30
53
|
const generalItem = computed(() => ({
|
|
31
54
|
id: "general",
|
|
32
55
|
label: t("general")
|
|
33
56
|
}));
|
|
34
|
-
|
|
35
|
-
|
|
57
|
+
const selectedField = computed(() => draftFields.value.find((field) => field.draftId === selectedItemId.value));
|
|
58
|
+
const selectedFieldValidationRules = computed(() => selectedField.value?.field.validation ?? []);
|
|
59
|
+
function createDraftId() {
|
|
60
|
+
return crypto.randomUUID();
|
|
61
|
+
}
|
|
62
|
+
function createDefaultLocaleValue() {
|
|
63
|
+
return [{ locale: "zh", message: "" }];
|
|
64
|
+
}
|
|
65
|
+
function createDraftField(field) {
|
|
66
|
+
return {
|
|
67
|
+
draftId: createDraftId(),
|
|
68
|
+
field: JSON.parse(JSON.stringify(toRaw(field)))
|
|
69
|
+
};
|
|
36
70
|
}
|
|
37
|
-
const fieldItems = computed(() => draftFields.value.map((field) => ({
|
|
38
|
-
itemId: field.path,
|
|
39
|
-
label: getFieldLabel(field),
|
|
40
|
-
path: field.path
|
|
41
|
-
})));
|
|
42
|
-
const selectedField = computed(() => draftFields.value.find((field) => field.path === selectedItemId.value));
|
|
43
|
-
const selectedItemLabel = computed(() => selectedField.value ? getFieldLabel(selectedField.value) : generalItem.value.label);
|
|
44
71
|
function cloneFields(fields) {
|
|
45
|
-
return fields.
|
|
72
|
+
return fields.map(createDraftField);
|
|
73
|
+
}
|
|
74
|
+
function getFieldTypeLabel(type) {
|
|
75
|
+
return t(`field-type-${type}`);
|
|
76
|
+
}
|
|
77
|
+
function getUnnamedFieldLabel(field) {
|
|
78
|
+
return t("unnamed-field", {
|
|
79
|
+
type: getFieldTypeLabel(field.type)
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
function getFieldChineseTitle(field) {
|
|
83
|
+
const zhTitle = field.title.find((item) => item.locale === "zh");
|
|
84
|
+
if (!zhTitle) {
|
|
85
|
+
return void 0;
|
|
86
|
+
}
|
|
87
|
+
const message = zhTitle.message.trim();
|
|
88
|
+
return message.length > 0 ? message : void 0;
|
|
89
|
+
}
|
|
90
|
+
function getFieldListLabel(field) {
|
|
91
|
+
return getFieldChineseTitle(field) ?? getUnnamedFieldLabel(field);
|
|
92
|
+
}
|
|
93
|
+
const fieldItems = computed(() => draftFields.value.map((item) => ({
|
|
94
|
+
itemId: item.draftId,
|
|
95
|
+
label: getFieldListLabel(item.field),
|
|
96
|
+
path: item.field.path,
|
|
97
|
+
type: item.field.type
|
|
98
|
+
})));
|
|
99
|
+
const selectedItemLabel = computed(() => selectedField.value ? getFieldListLabel(selectedField.value.field) : generalItem.value.label);
|
|
100
|
+
const sortable = useSortable(sortableListRef, sortableItemIds);
|
|
101
|
+
function getFieldErrorKey(draftId, fieldKey) {
|
|
102
|
+
return `${draftId}:${fieldKey}`;
|
|
103
|
+
}
|
|
104
|
+
function getValidationRuleErrorKey(draftId, index, control) {
|
|
105
|
+
return `${draftId}:validation:${index}:${control}`;
|
|
106
|
+
}
|
|
107
|
+
function clearError(key) {
|
|
108
|
+
Reflect.deleteProperty(validationErrors.value, key);
|
|
109
|
+
}
|
|
110
|
+
function clearFieldError(draftId, fieldKey) {
|
|
111
|
+
clearError(getFieldErrorKey(draftId, fieldKey));
|
|
112
|
+
}
|
|
113
|
+
function clearValidationRuleError(draftId, index, control) {
|
|
114
|
+
clearError(getValidationRuleErrorKey(draftId, index, control));
|
|
115
|
+
}
|
|
116
|
+
function clearFieldErrors(draftId) {
|
|
117
|
+
for (const key of Object.keys(validationErrors.value)) {
|
|
118
|
+
if (key.startsWith(`${draftId}:`)) {
|
|
119
|
+
Reflect.deleteProperty(validationErrors.value, key);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
function clearValidationRuleErrors(draftId) {
|
|
124
|
+
for (const key of Object.keys(validationErrors.value)) {
|
|
125
|
+
if (key.startsWith(`${draftId}:validation:`)) {
|
|
126
|
+
Reflect.deleteProperty(validationErrors.value, key);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
function setError(errors, key, message) {
|
|
131
|
+
if (errors[key] === void 0) {
|
|
132
|
+
errors[key] = message;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
function normalizeOptionalString(value) {
|
|
136
|
+
const normalizedValue = value.trim();
|
|
137
|
+
if (normalizedValue.length === 0) {
|
|
138
|
+
return void 0;
|
|
139
|
+
}
|
|
140
|
+
return normalizedValue;
|
|
141
|
+
}
|
|
142
|
+
function normalizeValidationRules(validation) {
|
|
143
|
+
if (!validation || validation.length === 0) {
|
|
144
|
+
return void 0;
|
|
145
|
+
}
|
|
146
|
+
return validation.map((rule) => ({
|
|
147
|
+
expression: rule.expression.trim(),
|
|
148
|
+
message: rule.message
|
|
149
|
+
}));
|
|
150
|
+
}
|
|
151
|
+
function normalizeField(field) {
|
|
152
|
+
switch (field.type) {
|
|
153
|
+
case "string":
|
|
154
|
+
return {
|
|
155
|
+
...field,
|
|
156
|
+
path: field.path.trim(),
|
|
157
|
+
icon: normalizeOptionalString(field.icon ?? ""),
|
|
158
|
+
style: normalizeOptionalString(field.style ?? ""),
|
|
159
|
+
maxLength: normalizeOptionalString(field.maxLength ?? ""),
|
|
160
|
+
hidden: normalizeOptionalString(field.hidden ?? ""),
|
|
161
|
+
disabled: normalizeOptionalString(field.disabled ?? ""),
|
|
162
|
+
discardEmptyString: field.discardEmptyString ? true : void 0,
|
|
163
|
+
validation: normalizeValidationRules(field.validation)
|
|
164
|
+
};
|
|
165
|
+
case "number":
|
|
166
|
+
return {
|
|
167
|
+
...field,
|
|
168
|
+
path: field.path.trim(),
|
|
169
|
+
icon: normalizeOptionalString(field.icon ?? ""),
|
|
170
|
+
style: normalizeOptionalString(field.style ?? ""),
|
|
171
|
+
min: normalizeOptionalString(field.min ?? ""),
|
|
172
|
+
max: normalizeOptionalString(field.max ?? ""),
|
|
173
|
+
step: normalizeOptionalString(field.step ?? ""),
|
|
174
|
+
hidden: normalizeOptionalString(field.hidden ?? ""),
|
|
175
|
+
disabled: normalizeOptionalString(field.disabled ?? ""),
|
|
176
|
+
validation: normalizeValidationRules(field.validation)
|
|
177
|
+
};
|
|
178
|
+
case "select":
|
|
179
|
+
return {
|
|
180
|
+
...field,
|
|
181
|
+
path: field.path.trim(),
|
|
182
|
+
icon: normalizeOptionalString(field.icon ?? ""),
|
|
183
|
+
style: normalizeOptionalString(field.style ?? ""),
|
|
184
|
+
options: field.options.trim(),
|
|
185
|
+
label: field.label.trim(),
|
|
186
|
+
value: field.value.trim(),
|
|
187
|
+
key: field.key.trim(),
|
|
188
|
+
hidden: normalizeOptionalString(field.hidden ?? ""),
|
|
189
|
+
disabled: normalizeOptionalString(field.disabled ?? ""),
|
|
190
|
+
validation: normalizeValidationRules(field.validation)
|
|
191
|
+
};
|
|
192
|
+
case "calendar":
|
|
193
|
+
return {
|
|
194
|
+
...field,
|
|
195
|
+
path: field.path.trim(),
|
|
196
|
+
icon: normalizeOptionalString(field.icon ?? ""),
|
|
197
|
+
style: normalizeOptionalString(field.style ?? ""),
|
|
198
|
+
display: normalizeOptionalString(field.display ?? ""),
|
|
199
|
+
value: field.value.trim(),
|
|
200
|
+
disableDate: normalizeOptionalString(field.disableDate ?? ""),
|
|
201
|
+
hidden: normalizeOptionalString(field.hidden ?? ""),
|
|
202
|
+
disabled: normalizeOptionalString(field.disabled ?? ""),
|
|
203
|
+
validation: normalizeValidationRules(field.validation)
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
function createField(type) {
|
|
208
|
+
const title = createDefaultLocaleValue();
|
|
209
|
+
switch (type) {
|
|
210
|
+
case "string":
|
|
211
|
+
return {
|
|
212
|
+
type,
|
|
213
|
+
path: "",
|
|
214
|
+
title
|
|
215
|
+
};
|
|
216
|
+
case "number":
|
|
217
|
+
return {
|
|
218
|
+
type,
|
|
219
|
+
path: "",
|
|
220
|
+
title
|
|
221
|
+
};
|
|
222
|
+
case "select":
|
|
223
|
+
return {
|
|
224
|
+
type,
|
|
225
|
+
path: "",
|
|
226
|
+
title,
|
|
227
|
+
options: "[]",
|
|
228
|
+
label: '""',
|
|
229
|
+
value: "option",
|
|
230
|
+
key: '""'
|
|
231
|
+
};
|
|
232
|
+
case "calendar":
|
|
233
|
+
return {
|
|
234
|
+
type,
|
|
235
|
+
path: "",
|
|
236
|
+
title,
|
|
237
|
+
mode: "date",
|
|
238
|
+
value: "yyyy-MM-dd"
|
|
239
|
+
};
|
|
240
|
+
}
|
|
46
241
|
}
|
|
47
242
|
function resetDraftFields() {
|
|
48
243
|
draftFields.value = cloneFields(props.fields);
|
|
49
244
|
selectedItemId.value = "general";
|
|
245
|
+
validationErrors.value = {};
|
|
50
246
|
}
|
|
51
|
-
watch(() => props.fields, (fields) => {
|
|
52
|
-
if (!open.value) {
|
|
53
|
-
draftFields.value = cloneFields(fields);
|
|
54
|
-
selectedItemId.value = "general";
|
|
55
|
-
}
|
|
56
|
-
}, { immediate: true });
|
|
57
|
-
const sortable = useSortable(sortableListRef, sortableItemIds);
|
|
58
247
|
function syncSortableItemIds() {
|
|
59
|
-
sortableItemIds.value = draftFields.value.map((field) => field.
|
|
248
|
+
sortableItemIds.value = draftFields.value.map((field) => field.draftId);
|
|
60
249
|
}
|
|
61
250
|
function moveField(fields, oldIndex, newIndex) {
|
|
62
251
|
if (oldIndex < 0 || newIndex < 0 || oldIndex >= fields.length || newIndex >= fields.length) {
|
|
@@ -93,6 +282,13 @@ async function refreshSortable() {
|
|
|
93
282
|
sortable.start();
|
|
94
283
|
configureSortable();
|
|
95
284
|
}
|
|
285
|
+
watch(() => props.fields, (fields) => {
|
|
286
|
+
if (!open.value) {
|
|
287
|
+
draftFields.value = cloneFields(fields);
|
|
288
|
+
selectedItemId.value = "general";
|
|
289
|
+
validationErrors.value = {};
|
|
290
|
+
}
|
|
291
|
+
}, { immediate: true });
|
|
96
292
|
watch(draftFields, () => {
|
|
97
293
|
syncSortableItemIds();
|
|
98
294
|
}, { immediate: true });
|
|
@@ -103,6 +299,7 @@ watch(open, async (value) => {
|
|
|
103
299
|
return;
|
|
104
300
|
}
|
|
105
301
|
sortable.stop();
|
|
302
|
+
validationErrors.value = {};
|
|
106
303
|
}, { immediate: true });
|
|
107
304
|
watch(fieldItems, async (items) => {
|
|
108
305
|
if (selectedItemId.value === "general") {
|
|
@@ -132,27 +329,493 @@ function selectGeneral() {
|
|
|
132
329
|
function selectItem(itemId) {
|
|
133
330
|
selectedItemId.value = itemId;
|
|
134
331
|
}
|
|
332
|
+
function updateDraftField(draftId, updater) {
|
|
333
|
+
draftFields.value = draftFields.value.map((item) => item.draftId === draftId ? {
|
|
334
|
+
draftId: item.draftId,
|
|
335
|
+
field: updater(item.field)
|
|
336
|
+
} : item);
|
|
337
|
+
}
|
|
338
|
+
function addField(type) {
|
|
339
|
+
const draftField = createDraftField(createField(type));
|
|
340
|
+
draftFields.value = [...draftFields.value, draftField];
|
|
341
|
+
selectedItemId.value = draftField.draftId;
|
|
342
|
+
clearFieldErrors(draftField.draftId);
|
|
343
|
+
}
|
|
135
344
|
function deleteField(itemId) {
|
|
136
|
-
const deleteIndex = draftFields.value.findIndex((field) => field.
|
|
345
|
+
const deleteIndex = draftFields.value.findIndex((field) => field.draftId === itemId);
|
|
137
346
|
if (deleteIndex < 0) {
|
|
138
347
|
return;
|
|
139
348
|
}
|
|
140
|
-
const nextFields = draftFields.value.filter((field) => field.
|
|
349
|
+
const nextFields = draftFields.value.filter((field) => field.draftId !== itemId);
|
|
141
350
|
draftFields.value = nextFields;
|
|
351
|
+
clearFieldErrors(itemId);
|
|
142
352
|
if (selectedItemId.value !== itemId) {
|
|
143
353
|
return;
|
|
144
354
|
}
|
|
145
355
|
const nextField = nextFields[deleteIndex] ?? nextFields[deleteIndex - 1];
|
|
146
|
-
selectedItemId.value = nextField?.
|
|
356
|
+
selectedItemId.value = nextField?.draftId ?? "general";
|
|
147
357
|
}
|
|
148
358
|
function updateSelectedFieldTitle(value) {
|
|
149
|
-
|
|
359
|
+
const selected = selectedField.value;
|
|
360
|
+
if (!selected) {
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
clearFieldError(selected.draftId, "title");
|
|
364
|
+
updateDraftField(selected.draftId, (field) => ({
|
|
365
|
+
...field,
|
|
366
|
+
title: value
|
|
367
|
+
}));
|
|
368
|
+
}
|
|
369
|
+
function updateSelectedFieldPath(value) {
|
|
370
|
+
const selected = selectedField.value;
|
|
371
|
+
if (!selected) {
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
clearFieldError(selected.draftId, "path");
|
|
375
|
+
updateDraftField(selected.draftId, (field) => ({
|
|
376
|
+
...field,
|
|
377
|
+
path: String(value).trim()
|
|
378
|
+
}));
|
|
379
|
+
}
|
|
380
|
+
function updateSelectedFieldIcon(value) {
|
|
381
|
+
const selected = selectedField.value;
|
|
382
|
+
if (!selected) {
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
clearFieldError(selected.draftId, "icon");
|
|
386
|
+
updateDraftField(selected.draftId, (field) => ({
|
|
387
|
+
...field,
|
|
388
|
+
icon: normalizeOptionalString(String(value ?? ""))
|
|
389
|
+
}));
|
|
390
|
+
}
|
|
391
|
+
function updateSelectedFieldStyle(value) {
|
|
392
|
+
const selected = selectedField.value;
|
|
393
|
+
if (!selected) {
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
clearFieldError(selected.draftId, "style");
|
|
397
|
+
updateDraftField(selected.draftId, (field) => ({
|
|
398
|
+
...field,
|
|
399
|
+
style: normalizeOptionalString(String(value))
|
|
400
|
+
}));
|
|
401
|
+
}
|
|
402
|
+
function updateSelectedFieldHidden(value) {
|
|
403
|
+
const selected = selectedField.value;
|
|
404
|
+
if (!selected) {
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
clearFieldError(selected.draftId, "hidden");
|
|
408
|
+
updateDraftField(selected.draftId, (field) => ({
|
|
409
|
+
...field,
|
|
410
|
+
hidden: normalizeOptionalString(String(value))
|
|
411
|
+
}));
|
|
412
|
+
}
|
|
413
|
+
function updateSelectedFieldDisabled(value) {
|
|
414
|
+
const selected = selectedField.value;
|
|
415
|
+
if (!selected) {
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
clearFieldError(selected.draftId, "disabled");
|
|
419
|
+
updateDraftField(selected.draftId, (field) => ({
|
|
420
|
+
...field,
|
|
421
|
+
disabled: normalizeOptionalString(String(value))
|
|
422
|
+
}));
|
|
423
|
+
}
|
|
424
|
+
function updateSelectedStringDiscardEmpty(value) {
|
|
425
|
+
const selected = selectedField.value;
|
|
426
|
+
if (!selected || selected.field.type !== "string") {
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
updateDraftField(selected.draftId, (field) => {
|
|
430
|
+
if (field.type !== "string") {
|
|
431
|
+
return field;
|
|
432
|
+
}
|
|
433
|
+
return {
|
|
434
|
+
...field,
|
|
435
|
+
discardEmptyString: value ? true : void 0
|
|
436
|
+
};
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
function updateSelectedStringMaxLength(value) {
|
|
440
|
+
const selected = selectedField.value;
|
|
441
|
+
if (!selected || selected.field.type !== "string") {
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
clearFieldError(selected.draftId, "maxLength");
|
|
445
|
+
updateDraftField(selected.draftId, (field) => {
|
|
446
|
+
if (field.type !== "string") {
|
|
447
|
+
return field;
|
|
448
|
+
}
|
|
449
|
+
return {
|
|
450
|
+
...field,
|
|
451
|
+
maxLength: normalizeOptionalString(String(value))
|
|
452
|
+
};
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
function updateSelectedNumberMin(value) {
|
|
456
|
+
const selected = selectedField.value;
|
|
457
|
+
if (!selected || selected.field.type !== "number") {
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
clearFieldError(selected.draftId, "min");
|
|
461
|
+
updateDraftField(selected.draftId, (field) => {
|
|
462
|
+
if (field.type !== "number") {
|
|
463
|
+
return field;
|
|
464
|
+
}
|
|
465
|
+
return {
|
|
466
|
+
...field,
|
|
467
|
+
min: normalizeOptionalString(String(value))
|
|
468
|
+
};
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
function updateSelectedNumberMax(value) {
|
|
472
|
+
const selected = selectedField.value;
|
|
473
|
+
if (!selected || selected.field.type !== "number") {
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
476
|
+
clearFieldError(selected.draftId, "max");
|
|
477
|
+
updateDraftField(selected.draftId, (field) => {
|
|
478
|
+
if (field.type !== "number") {
|
|
479
|
+
return field;
|
|
480
|
+
}
|
|
481
|
+
return {
|
|
482
|
+
...field,
|
|
483
|
+
max: normalizeOptionalString(String(value))
|
|
484
|
+
};
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
function updateSelectedNumberStep(value) {
|
|
488
|
+
const selected = selectedField.value;
|
|
489
|
+
if (!selected || selected.field.type !== "number") {
|
|
490
|
+
return;
|
|
491
|
+
}
|
|
492
|
+
clearFieldError(selected.draftId, "step");
|
|
493
|
+
updateDraftField(selected.draftId, (field) => {
|
|
494
|
+
if (field.type !== "number") {
|
|
495
|
+
return field;
|
|
496
|
+
}
|
|
497
|
+
return {
|
|
498
|
+
...field,
|
|
499
|
+
step: normalizeOptionalString(String(value))
|
|
500
|
+
};
|
|
501
|
+
});
|
|
502
|
+
}
|
|
503
|
+
function updateSelectedSelectOptions(value) {
|
|
504
|
+
const selected = selectedField.value;
|
|
505
|
+
if (!selected || selected.field.type !== "select") {
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
clearFieldError(selected.draftId, "options");
|
|
509
|
+
updateDraftField(selected.draftId, (field) => {
|
|
510
|
+
if (field.type !== "select") {
|
|
511
|
+
return field;
|
|
512
|
+
}
|
|
513
|
+
return {
|
|
514
|
+
...field,
|
|
515
|
+
options: String(value).trim()
|
|
516
|
+
};
|
|
517
|
+
});
|
|
518
|
+
}
|
|
519
|
+
function updateSelectedSelectLabel(value) {
|
|
520
|
+
const selected = selectedField.value;
|
|
521
|
+
if (!selected || selected.field.type !== "select") {
|
|
522
|
+
return;
|
|
523
|
+
}
|
|
524
|
+
clearFieldError(selected.draftId, "label");
|
|
525
|
+
updateDraftField(selected.draftId, (field) => {
|
|
526
|
+
if (field.type !== "select") {
|
|
527
|
+
return field;
|
|
528
|
+
}
|
|
529
|
+
return {
|
|
530
|
+
...field,
|
|
531
|
+
label: String(value).trim()
|
|
532
|
+
};
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
function updateSelectedSelectValue(value) {
|
|
536
|
+
const selected = selectedField.value;
|
|
537
|
+
if (!selected || selected.field.type !== "select") {
|
|
538
|
+
return;
|
|
539
|
+
}
|
|
540
|
+
clearFieldError(selected.draftId, "value");
|
|
541
|
+
updateDraftField(selected.draftId, (field) => {
|
|
542
|
+
if (field.type !== "select") {
|
|
543
|
+
return field;
|
|
544
|
+
}
|
|
545
|
+
return {
|
|
546
|
+
...field,
|
|
547
|
+
value: String(value).trim()
|
|
548
|
+
};
|
|
549
|
+
});
|
|
550
|
+
}
|
|
551
|
+
function updateSelectedSelectKey(value) {
|
|
552
|
+
const selected = selectedField.value;
|
|
553
|
+
if (!selected || selected.field.type !== "select") {
|
|
554
|
+
return;
|
|
555
|
+
}
|
|
556
|
+
clearFieldError(selected.draftId, "key");
|
|
557
|
+
updateDraftField(selected.draftId, (field) => {
|
|
558
|
+
if (field.type !== "select") {
|
|
559
|
+
return field;
|
|
560
|
+
}
|
|
561
|
+
return {
|
|
562
|
+
...field,
|
|
563
|
+
key: String(value).trim()
|
|
564
|
+
};
|
|
565
|
+
});
|
|
566
|
+
}
|
|
567
|
+
function updateSelectedCalendarMode(value) {
|
|
568
|
+
const selected = selectedField.value;
|
|
569
|
+
if (!selected || selected.field.type !== "calendar") {
|
|
570
|
+
return;
|
|
571
|
+
}
|
|
572
|
+
const normalizedValue = String(value);
|
|
573
|
+
if (normalizedValue !== "year" && normalizedValue !== "month" && normalizedValue !== "date") {
|
|
574
|
+
return;
|
|
575
|
+
}
|
|
576
|
+
clearFieldError(selected.draftId, "mode");
|
|
577
|
+
updateDraftField(selected.draftId, (field) => {
|
|
578
|
+
if (field.type !== "calendar") {
|
|
579
|
+
return field;
|
|
580
|
+
}
|
|
581
|
+
return {
|
|
582
|
+
...field,
|
|
583
|
+
mode: normalizedValue
|
|
584
|
+
};
|
|
585
|
+
});
|
|
586
|
+
}
|
|
587
|
+
function updateSelectedCalendarDisplay(value) {
|
|
588
|
+
const selected = selectedField.value;
|
|
589
|
+
if (!selected || selected.field.type !== "calendar") {
|
|
590
|
+
return;
|
|
591
|
+
}
|
|
592
|
+
clearFieldError(selected.draftId, "display");
|
|
593
|
+
updateDraftField(selected.draftId, (field) => {
|
|
594
|
+
if (field.type !== "calendar") {
|
|
595
|
+
return field;
|
|
596
|
+
}
|
|
597
|
+
return {
|
|
598
|
+
...field,
|
|
599
|
+
display: normalizeOptionalString(String(value))
|
|
600
|
+
};
|
|
601
|
+
});
|
|
602
|
+
}
|
|
603
|
+
function updateSelectedCalendarValue(value) {
|
|
604
|
+
const selected = selectedField.value;
|
|
605
|
+
if (!selected || selected.field.type !== "calendar") {
|
|
606
|
+
return;
|
|
607
|
+
}
|
|
608
|
+
clearFieldError(selected.draftId, "value");
|
|
609
|
+
updateDraftField(selected.draftId, (field) => {
|
|
610
|
+
if (field.type !== "calendar") {
|
|
611
|
+
return field;
|
|
612
|
+
}
|
|
613
|
+
return {
|
|
614
|
+
...field,
|
|
615
|
+
value: String(value).trim()
|
|
616
|
+
};
|
|
617
|
+
});
|
|
618
|
+
}
|
|
619
|
+
function updateSelectedCalendarDisableDate(value) {
|
|
620
|
+
const selected = selectedField.value;
|
|
621
|
+
if (!selected || selected.field.type !== "calendar") {
|
|
622
|
+
return;
|
|
623
|
+
}
|
|
624
|
+
clearFieldError(selected.draftId, "disableDate");
|
|
625
|
+
updateDraftField(selected.draftId, (field) => {
|
|
626
|
+
if (field.type !== "calendar") {
|
|
627
|
+
return field;
|
|
628
|
+
}
|
|
629
|
+
return {
|
|
630
|
+
...field,
|
|
631
|
+
disableDate: normalizeOptionalString(String(value))
|
|
632
|
+
};
|
|
633
|
+
});
|
|
634
|
+
}
|
|
635
|
+
function addValidationRule() {
|
|
636
|
+
const selected = selectedField.value;
|
|
637
|
+
if (!selected) {
|
|
638
|
+
return;
|
|
639
|
+
}
|
|
640
|
+
updateDraftField(selected.draftId, (field) => ({
|
|
641
|
+
...field,
|
|
642
|
+
validation: [
|
|
643
|
+
...field.validation ?? [],
|
|
644
|
+
{
|
|
645
|
+
expression: "",
|
|
646
|
+
message: ""
|
|
647
|
+
}
|
|
648
|
+
]
|
|
649
|
+
}));
|
|
650
|
+
}
|
|
651
|
+
function updateValidationRule(index, updater) {
|
|
652
|
+
const selected = selectedField.value;
|
|
653
|
+
if (!selected) {
|
|
654
|
+
return;
|
|
655
|
+
}
|
|
656
|
+
const validation = selected.field.validation ?? [];
|
|
657
|
+
const currentRule = validation[index];
|
|
658
|
+
if (!currentRule) {
|
|
659
|
+
return;
|
|
660
|
+
}
|
|
661
|
+
const nextValidation = validation.map((rule, ruleIndex) => ruleIndex === index ? updater(rule) : rule);
|
|
662
|
+
updateDraftField(selected.draftId, (field) => ({
|
|
663
|
+
...field,
|
|
664
|
+
validation: nextValidation
|
|
665
|
+
}));
|
|
666
|
+
}
|
|
667
|
+
function updateSelectedValidationExpression(index, value) {
|
|
668
|
+
const selected = selectedField.value;
|
|
669
|
+
if (!selected) {
|
|
670
|
+
return;
|
|
671
|
+
}
|
|
672
|
+
clearValidationRuleError(selected.draftId, index, "expression");
|
|
673
|
+
updateValidationRule(index, (rule) => ({
|
|
674
|
+
...rule,
|
|
675
|
+
expression: String(value).trim()
|
|
676
|
+
}));
|
|
677
|
+
}
|
|
678
|
+
function updateSelectedValidationMessage(index, value) {
|
|
679
|
+
const selected = selectedField.value;
|
|
680
|
+
if (!selected) {
|
|
681
|
+
return;
|
|
682
|
+
}
|
|
683
|
+
clearValidationRuleError(selected.draftId, index, "message");
|
|
684
|
+
updateValidationRule(index, (rule) => ({
|
|
685
|
+
...rule,
|
|
686
|
+
message: String(value)
|
|
687
|
+
}));
|
|
688
|
+
}
|
|
689
|
+
function moveValidationRule(index, offset) {
|
|
690
|
+
const selected = selectedField.value;
|
|
691
|
+
if (!selected) {
|
|
692
|
+
return;
|
|
693
|
+
}
|
|
694
|
+
const validation = selected.field.validation ?? [];
|
|
695
|
+
const nextIndex = index + offset;
|
|
696
|
+
if (!validation[index] || !validation[nextIndex]) {
|
|
697
|
+
return;
|
|
698
|
+
}
|
|
699
|
+
const nextValidation = validation.slice();
|
|
700
|
+
const currentRule = nextValidation[index];
|
|
701
|
+
const targetRule = nextValidation[nextIndex];
|
|
702
|
+
if (!currentRule || !targetRule) {
|
|
703
|
+
return;
|
|
704
|
+
}
|
|
705
|
+
nextValidation[index] = targetRule;
|
|
706
|
+
nextValidation[nextIndex] = currentRule;
|
|
707
|
+
updateDraftField(selected.draftId, (field) => ({
|
|
708
|
+
...field,
|
|
709
|
+
validation: nextValidation
|
|
710
|
+
}));
|
|
711
|
+
clearValidationRuleErrors(selected.draftId);
|
|
712
|
+
}
|
|
713
|
+
function deleteValidationRule(index) {
|
|
714
|
+
const selected = selectedField.value;
|
|
715
|
+
if (!selected) {
|
|
150
716
|
return;
|
|
151
717
|
}
|
|
152
|
-
|
|
718
|
+
const validation = selected.field.validation ?? [];
|
|
719
|
+
if (!validation[index]) {
|
|
720
|
+
return;
|
|
721
|
+
}
|
|
722
|
+
clearValidationRuleError(selected.draftId, index, "expression");
|
|
723
|
+
clearValidationRuleError(selected.draftId, index, "message");
|
|
724
|
+
updateDraftField(selected.draftId, (field) => {
|
|
725
|
+
const nextValidation = (field.validation ?? []).filter((_, ruleIndex) => ruleIndex !== index);
|
|
726
|
+
return {
|
|
727
|
+
...field,
|
|
728
|
+
validation: nextValidation.length > 0 ? nextValidation : void 0
|
|
729
|
+
};
|
|
730
|
+
});
|
|
731
|
+
clearValidationRuleErrors(selected.draftId);
|
|
732
|
+
}
|
|
733
|
+
function getSchemaIssues(field) {
|
|
734
|
+
switch (field.type) {
|
|
735
|
+
case "string": {
|
|
736
|
+
const result = StringFieldC.safeParse(field);
|
|
737
|
+
return result.success ? [] : result.error.issues;
|
|
738
|
+
}
|
|
739
|
+
case "number": {
|
|
740
|
+
const result = NumberFieldC.safeParse(field);
|
|
741
|
+
return result.success ? [] : result.error.issues;
|
|
742
|
+
}
|
|
743
|
+
case "select": {
|
|
744
|
+
const result = SelectFieldC.safeParse(field);
|
|
745
|
+
return result.success ? [] : result.error.issues;
|
|
746
|
+
}
|
|
747
|
+
case "calendar": {
|
|
748
|
+
const result = CalendarFieldC.safeParse(field);
|
|
749
|
+
return result.success ? [] : result.error.issues;
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
function normalizeIssuePath(path) {
|
|
754
|
+
const normalizedPath = [];
|
|
755
|
+
for (const segment of path) {
|
|
756
|
+
if (typeof segment === "string" || typeof segment === "number") {
|
|
757
|
+
normalizedPath.push(segment);
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
return normalizedPath;
|
|
761
|
+
}
|
|
762
|
+
function getIssueErrorKey(draftId, path) {
|
|
763
|
+
const [head, second, third] = path;
|
|
764
|
+
if (head === "validation" && typeof second === "number" && (third === "expression" || third === "message")) {
|
|
765
|
+
return getValidationRuleErrorKey(draftId, second, third);
|
|
766
|
+
}
|
|
767
|
+
if (typeof head !== "string") {
|
|
768
|
+
return void 0;
|
|
769
|
+
}
|
|
770
|
+
return getFieldErrorKey(draftId, head);
|
|
771
|
+
}
|
|
772
|
+
function validateDraftFields() {
|
|
773
|
+
const errors = {};
|
|
774
|
+
const normalizedFields = draftFields.value.map((item) => ({
|
|
775
|
+
draftId: item.draftId,
|
|
776
|
+
field: normalizeField(item.field)
|
|
777
|
+
}));
|
|
778
|
+
const pathOwners = {};
|
|
779
|
+
let firstInvalidItemId;
|
|
780
|
+
for (const item of normalizedFields) {
|
|
781
|
+
const existingOwner = pathOwners[item.field.path];
|
|
782
|
+
if (item.field.path.length === 0) {
|
|
783
|
+
setError(errors, getFieldErrorKey(item.draftId, "path"), t("field-path-required"));
|
|
784
|
+
firstInvalidItemId = firstInvalidItemId ?? item.draftId;
|
|
785
|
+
} else if (existingOwner !== void 0) {
|
|
786
|
+
setError(errors, getFieldErrorKey(item.draftId, "path"), t("field-path-duplicate"));
|
|
787
|
+
setError(errors, getFieldErrorKey(existingOwner, "path"), t("field-path-duplicate"));
|
|
788
|
+
firstInvalidItemId = firstInvalidItemId ?? item.draftId;
|
|
789
|
+
} else {
|
|
790
|
+
pathOwners[item.field.path] = item.draftId;
|
|
791
|
+
}
|
|
792
|
+
if (item.field.type === "calendar" && item.field.value.length === 0) {
|
|
793
|
+
setError(errors, getFieldErrorKey(item.draftId, "value"), t("calendar-value-required"));
|
|
794
|
+
firstInvalidItemId = firstInvalidItemId ?? item.draftId;
|
|
795
|
+
}
|
|
796
|
+
for (const issue of getSchemaIssues(item.field)) {
|
|
797
|
+
const issueKey = getIssueErrorKey(item.draftId, normalizeIssuePath(issue.path));
|
|
798
|
+
if (!issueKey) {
|
|
799
|
+
continue;
|
|
800
|
+
}
|
|
801
|
+
setError(errors, issueKey, issue.message);
|
|
802
|
+
firstInvalidItemId = firstInvalidItemId ?? item.draftId;
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
validationErrors.value = errors;
|
|
806
|
+
if (firstInvalidItemId) {
|
|
807
|
+
selectedItemId.value = firstInvalidItemId;
|
|
808
|
+
return void 0;
|
|
809
|
+
}
|
|
810
|
+
return normalizedFields;
|
|
153
811
|
}
|
|
154
812
|
function confirmChanges() {
|
|
155
|
-
|
|
813
|
+
const normalizedFields = validateDraftFields();
|
|
814
|
+
if (!normalizedFields) {
|
|
815
|
+
return;
|
|
816
|
+
}
|
|
817
|
+
draftFields.value = normalizedFields.map((item) => createDraftField(item.field));
|
|
818
|
+
emit("confirm", normalizedFields.map((item) => item.field));
|
|
156
819
|
open.value = false;
|
|
157
820
|
}
|
|
158
821
|
</script>
|
|
@@ -209,6 +872,7 @@ function confirmChanges() {
|
|
|
209
872
|
:key="item.itemId"
|
|
210
873
|
data-slot="fields-configurator-field-item"
|
|
211
874
|
:data-item-id="item.itemId"
|
|
875
|
+
:data-field-path="item.path"
|
|
212
876
|
:data-selected="selectedItemId === item.itemId ? 'true' : 'false'"
|
|
213
877
|
:class="cn(
|
|
214
878
|
'flex w-full items-center gap-2 rounded-md border p-1 transition-colors',
|
|
@@ -260,6 +924,37 @@ function confirmChanges() {
|
|
|
260
924
|
</p>
|
|
261
925
|
</div>
|
|
262
926
|
</div>
|
|
927
|
+
|
|
928
|
+
<div
|
|
929
|
+
data-slot="fields-configurator-add-container"
|
|
930
|
+
class="mt-4"
|
|
931
|
+
>
|
|
932
|
+
<DropdownMenu>
|
|
933
|
+
<DropdownMenuTrigger as-child>
|
|
934
|
+
<Button
|
|
935
|
+
type="button"
|
|
936
|
+
data-slot="fields-configurator-add"
|
|
937
|
+
size="sm"
|
|
938
|
+
variant="default"
|
|
939
|
+
class="w-full justify-center"
|
|
940
|
+
>
|
|
941
|
+
<Icon icon="fluent:add-20-regular" />
|
|
942
|
+
{{ t("add-field") }}
|
|
943
|
+
</Button>
|
|
944
|
+
</DropdownMenuTrigger>
|
|
945
|
+
|
|
946
|
+
<DropdownMenuContent align="start">
|
|
947
|
+
<DropdownMenuItem
|
|
948
|
+
v-for="option in fieldTypeOptions"
|
|
949
|
+
:key="option.type"
|
|
950
|
+
:data-slot="`fields-configurator-add-item-${option.type}`"
|
|
951
|
+
@select="addField(option.type)"
|
|
952
|
+
>
|
|
953
|
+
{{ option.label }}
|
|
954
|
+
</DropdownMenuItem>
|
|
955
|
+
</DropdownMenuContent>
|
|
956
|
+
</DropdownMenu>
|
|
957
|
+
</div>
|
|
263
958
|
</section>
|
|
264
959
|
|
|
265
960
|
<section class="flex min-h-0 flex-col overflow-y-auto px-6 py-6">
|
|
@@ -269,18 +964,19 @@ function confirmChanges() {
|
|
|
269
964
|
>
|
|
270
965
|
{{ selectedItemLabel }}
|
|
271
966
|
</h3>
|
|
967
|
+
|
|
272
968
|
<p
|
|
273
969
|
v-if="selectedItemId === 'general'"
|
|
274
970
|
data-slot="fields-configurator-detail-description"
|
|
275
971
|
class="mt-2 text-sm text-zinc-500"
|
|
276
972
|
>
|
|
277
|
-
{{ t("general-
|
|
973
|
+
{{ t("general-description") }}
|
|
278
974
|
</p>
|
|
279
975
|
|
|
280
976
|
<div
|
|
281
977
|
v-if="selectedItemId === 'general'"
|
|
282
978
|
data-slot="fields-configurator-general-placeholder"
|
|
283
|
-
class="mt-6
|
|
979
|
+
class="mt-6 rounded-lg border border-dashed border-zinc-200 bg-zinc-50/60 px-6 py-8 text-sm text-zinc-500"
|
|
284
980
|
>
|
|
285
981
|
{{ t("general-empty") }}
|
|
286
982
|
</div>
|
|
@@ -290,6 +986,47 @@ function confirmChanges() {
|
|
|
290
986
|
data-slot="fields-configurator-field-main"
|
|
291
987
|
class="mt-6 flex flex-col gap-6"
|
|
292
988
|
>
|
|
989
|
+
<section class="grid gap-4 md:grid-cols-2">
|
|
990
|
+
<label
|
|
991
|
+
data-slot="fields-configurator-field-type-section"
|
|
992
|
+
class="flex flex-col gap-2"
|
|
993
|
+
>
|
|
994
|
+
<span class="text-xs font-medium text-zinc-500">
|
|
995
|
+
{{ t("field-type") }}
|
|
996
|
+
</span>
|
|
997
|
+
<div
|
|
998
|
+
data-slot="fields-configurator-field-type"
|
|
999
|
+
class="flex h-9 items-center rounded-md border border-zinc-200 bg-zinc-50 px-3 text-sm text-zinc-600"
|
|
1000
|
+
>
|
|
1001
|
+
{{ getFieldTypeLabel(selectedField.field.type) }}
|
|
1002
|
+
</div>
|
|
1003
|
+
</label>
|
|
1004
|
+
|
|
1005
|
+
<label
|
|
1006
|
+
data-slot="fields-configurator-field-path-section"
|
|
1007
|
+
class="flex flex-col gap-2"
|
|
1008
|
+
>
|
|
1009
|
+
<span class="text-xs font-medium text-zinc-500">
|
|
1010
|
+
{{ t("field-path") }}
|
|
1011
|
+
</span>
|
|
1012
|
+
<Input
|
|
1013
|
+
data-slot="fields-configurator-field-path-input"
|
|
1014
|
+
:model-value="selectedField.field.path"
|
|
1015
|
+
:aria-invalid="validationErrors[getFieldErrorKey(selectedField.draftId, 'path')] ? 'true' : void 0"
|
|
1016
|
+
:placeholder="t('field-path-placeholder')"
|
|
1017
|
+
class="font-mono text-sm"
|
|
1018
|
+
@update:model-value="updateSelectedFieldPath"
|
|
1019
|
+
/>
|
|
1020
|
+
<p
|
|
1021
|
+
v-if="validationErrors[getFieldErrorKey(selectedField.draftId, 'path')]"
|
|
1022
|
+
data-slot="fields-configurator-field-path-error"
|
|
1023
|
+
class="text-xs text-red-500"
|
|
1024
|
+
>
|
|
1025
|
+
{{ validationErrors[getFieldErrorKey(selectedField.draftId, "path")] }}
|
|
1026
|
+
</p>
|
|
1027
|
+
</label>
|
|
1028
|
+
</section>
|
|
1029
|
+
|
|
293
1030
|
<section
|
|
294
1031
|
data-slot="fields-configurator-field-label-section"
|
|
295
1032
|
class="flex flex-col gap-2"
|
|
@@ -300,9 +1037,498 @@ function confirmChanges() {
|
|
|
300
1037
|
|
|
301
1038
|
<Locale
|
|
302
1039
|
data-slot="fields-configurator-field-title-locale"
|
|
303
|
-
:model-value="selectedField.title"
|
|
1040
|
+
:model-value="selectedField.field.title"
|
|
304
1041
|
@update:model-value="updateSelectedFieldTitle"
|
|
305
1042
|
/>
|
|
1043
|
+
|
|
1044
|
+
<p
|
|
1045
|
+
v-if="validationErrors[getFieldErrorKey(selectedField.draftId, 'title')]"
|
|
1046
|
+
data-slot="fields-configurator-field-title-error"
|
|
1047
|
+
class="text-xs text-red-500"
|
|
1048
|
+
>
|
|
1049
|
+
{{ validationErrors[getFieldErrorKey(selectedField.draftId, "title")] }}
|
|
1050
|
+
</p>
|
|
1051
|
+
</section>
|
|
1052
|
+
|
|
1053
|
+
<section
|
|
1054
|
+
data-slot="fields-configurator-field-general-options"
|
|
1055
|
+
class="grid gap-4 md:grid-cols-2"
|
|
1056
|
+
>
|
|
1057
|
+
<label class="flex flex-col gap-2">
|
|
1058
|
+
<span class="text-xs font-medium text-zinc-500">
|
|
1059
|
+
{{ t("field-icon") }}
|
|
1060
|
+
</span>
|
|
1061
|
+
<IconPicker
|
|
1062
|
+
data-slot="fields-configurator-field-icon-picker"
|
|
1063
|
+
:model-value="selectedField.field.icon ?? ''"
|
|
1064
|
+
:invalid="validationErrors[getFieldErrorKey(selectedField.draftId, 'icon')] !== void 0"
|
|
1065
|
+
:placeholder="t('field-icon-placeholder')"
|
|
1066
|
+
@update:model-value="updateSelectedFieldIcon"
|
|
1067
|
+
/>
|
|
1068
|
+
<p
|
|
1069
|
+
v-if="validationErrors[getFieldErrorKey(selectedField.draftId, 'icon')]"
|
|
1070
|
+
class="text-xs text-red-500"
|
|
1071
|
+
>
|
|
1072
|
+
{{ validationErrors[getFieldErrorKey(selectedField.draftId, "icon")] }}
|
|
1073
|
+
</p>
|
|
1074
|
+
</label>
|
|
1075
|
+
|
|
1076
|
+
<label class="flex flex-col gap-2">
|
|
1077
|
+
<span class="text-xs font-medium text-zinc-500">
|
|
1078
|
+
{{ t("field-style") }}
|
|
1079
|
+
</span>
|
|
1080
|
+
<Textarea
|
|
1081
|
+
data-slot="fields-configurator-field-style-input"
|
|
1082
|
+
:model-value="selectedField.field.style ?? ''"
|
|
1083
|
+
:aria-invalid="validationErrors[getFieldErrorKey(selectedField.draftId, 'style')] ? 'true' : void 0"
|
|
1084
|
+
:placeholder="t('field-style-placeholder')"
|
|
1085
|
+
class="min-h-20 font-mono text-sm"
|
|
1086
|
+
@update:model-value="updateSelectedFieldStyle"
|
|
1087
|
+
/>
|
|
1088
|
+
<p
|
|
1089
|
+
v-if="validationErrors[getFieldErrorKey(selectedField.draftId, 'style')]"
|
|
1090
|
+
class="text-xs text-red-500"
|
|
1091
|
+
>
|
|
1092
|
+
{{ validationErrors[getFieldErrorKey(selectedField.draftId, "style")] }}
|
|
1093
|
+
</p>
|
|
1094
|
+
</label>
|
|
1095
|
+
|
|
1096
|
+
<label class="flex flex-col gap-2">
|
|
1097
|
+
<span class="text-xs font-medium text-zinc-500">
|
|
1098
|
+
{{ t("field-hidden") }}
|
|
1099
|
+
</span>
|
|
1100
|
+
<Textarea
|
|
1101
|
+
data-slot="fields-configurator-field-hidden-input"
|
|
1102
|
+
:model-value="selectedField.field.hidden ?? ''"
|
|
1103
|
+
:aria-invalid="validationErrors[getFieldErrorKey(selectedField.draftId, 'hidden')] ? 'true' : void 0"
|
|
1104
|
+
:placeholder="t('field-hidden-placeholder')"
|
|
1105
|
+
class="min-h-20 font-mono text-sm"
|
|
1106
|
+
@update:model-value="updateSelectedFieldHidden"
|
|
1107
|
+
/>
|
|
1108
|
+
<p
|
|
1109
|
+
v-if="validationErrors[getFieldErrorKey(selectedField.draftId, 'hidden')]"
|
|
1110
|
+
class="text-xs text-red-500"
|
|
1111
|
+
>
|
|
1112
|
+
{{ validationErrors[getFieldErrorKey(selectedField.draftId, "hidden")] }}
|
|
1113
|
+
</p>
|
|
1114
|
+
</label>
|
|
1115
|
+
|
|
1116
|
+
<label class="flex flex-col gap-2">
|
|
1117
|
+
<span class="text-xs font-medium text-zinc-500">
|
|
1118
|
+
{{ t("field-disabled") }}
|
|
1119
|
+
</span>
|
|
1120
|
+
<Textarea
|
|
1121
|
+
data-slot="fields-configurator-field-disabled-input"
|
|
1122
|
+
:model-value="selectedField.field.disabled ?? ''"
|
|
1123
|
+
:aria-invalid="validationErrors[getFieldErrorKey(selectedField.draftId, 'disabled')] ? 'true' : void 0"
|
|
1124
|
+
:placeholder="t('field-disabled-placeholder')"
|
|
1125
|
+
class="min-h-20 font-mono text-sm"
|
|
1126
|
+
@update:model-value="updateSelectedFieldDisabled"
|
|
1127
|
+
/>
|
|
1128
|
+
<p
|
|
1129
|
+
v-if="validationErrors[getFieldErrorKey(selectedField.draftId, 'disabled')]"
|
|
1130
|
+
class="text-xs text-red-500"
|
|
1131
|
+
>
|
|
1132
|
+
{{ validationErrors[getFieldErrorKey(selectedField.draftId, "disabled")] }}
|
|
1133
|
+
</p>
|
|
1134
|
+
</label>
|
|
1135
|
+
</section>
|
|
1136
|
+
|
|
1137
|
+
<section
|
|
1138
|
+
v-if="selectedField.field.type === 'string'"
|
|
1139
|
+
data-slot="fields-configurator-string-options"
|
|
1140
|
+
class="grid gap-4 md:grid-cols-2"
|
|
1141
|
+
>
|
|
1142
|
+
<label class="flex items-center justify-between gap-3 rounded-md border border-zinc-200 px-3 py-2">
|
|
1143
|
+
<div class="flex flex-col gap-1">
|
|
1144
|
+
<span class="text-sm font-medium text-zinc-800">{{ t("field-discard-empty-string") }}</span>
|
|
1145
|
+
<span class="text-xs text-zinc-500">{{ t("field-discard-empty-string-description") }}</span>
|
|
1146
|
+
</div>
|
|
1147
|
+
<Switch
|
|
1148
|
+
data-slot="fields-configurator-field-discard-empty-switch"
|
|
1149
|
+
:model-value="selectedField.field.discardEmptyString ?? false"
|
|
1150
|
+
@update:model-value="updateSelectedStringDiscardEmpty"
|
|
1151
|
+
/>
|
|
1152
|
+
</label>
|
|
1153
|
+
|
|
1154
|
+
<label class="flex flex-col gap-2">
|
|
1155
|
+
<span class="text-xs font-medium text-zinc-500">
|
|
1156
|
+
{{ t("field-max-length") }}
|
|
1157
|
+
</span>
|
|
1158
|
+
<Textarea
|
|
1159
|
+
data-slot="fields-configurator-field-max-length-input"
|
|
1160
|
+
:model-value="selectedField.field.maxLength ?? ''"
|
|
1161
|
+
:aria-invalid="validationErrors[getFieldErrorKey(selectedField.draftId, 'maxLength')] ? 'true' : void 0"
|
|
1162
|
+
:placeholder="t('field-max-length-placeholder')"
|
|
1163
|
+
class="min-h-20 font-mono text-sm"
|
|
1164
|
+
@update:model-value="updateSelectedStringMaxLength"
|
|
1165
|
+
/>
|
|
1166
|
+
<p
|
|
1167
|
+
v-if="validationErrors[getFieldErrorKey(selectedField.draftId, 'maxLength')]"
|
|
1168
|
+
class="text-xs text-red-500"
|
|
1169
|
+
>
|
|
1170
|
+
{{ validationErrors[getFieldErrorKey(selectedField.draftId, "maxLength")] }}
|
|
1171
|
+
</p>
|
|
1172
|
+
</label>
|
|
1173
|
+
</section>
|
|
1174
|
+
|
|
1175
|
+
<section
|
|
1176
|
+
v-if="selectedField.field.type === 'number'"
|
|
1177
|
+
data-slot="fields-configurator-number-options"
|
|
1178
|
+
class="grid gap-4 md:grid-cols-3"
|
|
1179
|
+
>
|
|
1180
|
+
<label class="flex flex-col gap-2">
|
|
1181
|
+
<span class="text-xs font-medium text-zinc-500">
|
|
1182
|
+
{{ t("field-min") }}
|
|
1183
|
+
</span>
|
|
1184
|
+
<Textarea
|
|
1185
|
+
data-slot="fields-configurator-field-min-input"
|
|
1186
|
+
:model-value="selectedField.field.min ?? ''"
|
|
1187
|
+
:aria-invalid="validationErrors[getFieldErrorKey(selectedField.draftId, 'min')] ? 'true' : void 0"
|
|
1188
|
+
:placeholder="t('field-min-placeholder')"
|
|
1189
|
+
class="min-h-20 font-mono text-sm"
|
|
1190
|
+
@update:model-value="updateSelectedNumberMin"
|
|
1191
|
+
/>
|
|
1192
|
+
<p
|
|
1193
|
+
v-if="validationErrors[getFieldErrorKey(selectedField.draftId, 'min')]"
|
|
1194
|
+
class="text-xs text-red-500"
|
|
1195
|
+
>
|
|
1196
|
+
{{ validationErrors[getFieldErrorKey(selectedField.draftId, "min")] }}
|
|
1197
|
+
</p>
|
|
1198
|
+
</label>
|
|
1199
|
+
|
|
1200
|
+
<label class="flex flex-col gap-2">
|
|
1201
|
+
<span class="text-xs font-medium text-zinc-500">
|
|
1202
|
+
{{ t("field-max") }}
|
|
1203
|
+
</span>
|
|
1204
|
+
<Textarea
|
|
1205
|
+
data-slot="fields-configurator-field-max-input"
|
|
1206
|
+
:model-value="selectedField.field.max ?? ''"
|
|
1207
|
+
:aria-invalid="validationErrors[getFieldErrorKey(selectedField.draftId, 'max')] ? 'true' : void 0"
|
|
1208
|
+
:placeholder="t('field-max-placeholder')"
|
|
1209
|
+
class="min-h-20 font-mono text-sm"
|
|
1210
|
+
@update:model-value="updateSelectedNumberMax"
|
|
1211
|
+
/>
|
|
1212
|
+
<p
|
|
1213
|
+
v-if="validationErrors[getFieldErrorKey(selectedField.draftId, 'max')]"
|
|
1214
|
+
class="text-xs text-red-500"
|
|
1215
|
+
>
|
|
1216
|
+
{{ validationErrors[getFieldErrorKey(selectedField.draftId, "max")] }}
|
|
1217
|
+
</p>
|
|
1218
|
+
</label>
|
|
1219
|
+
|
|
1220
|
+
<label class="flex flex-col gap-2">
|
|
1221
|
+
<span class="text-xs font-medium text-zinc-500">
|
|
1222
|
+
{{ t("field-step") }}
|
|
1223
|
+
</span>
|
|
1224
|
+
<Textarea
|
|
1225
|
+
data-slot="fields-configurator-field-step-input"
|
|
1226
|
+
:model-value="selectedField.field.step ?? ''"
|
|
1227
|
+
:aria-invalid="validationErrors[getFieldErrorKey(selectedField.draftId, 'step')] ? 'true' : void 0"
|
|
1228
|
+
:placeholder="t('field-step-placeholder')"
|
|
1229
|
+
class="min-h-20 font-mono text-sm"
|
|
1230
|
+
@update:model-value="updateSelectedNumberStep"
|
|
1231
|
+
/>
|
|
1232
|
+
<p
|
|
1233
|
+
v-if="validationErrors[getFieldErrorKey(selectedField.draftId, 'step')]"
|
|
1234
|
+
class="text-xs text-red-500"
|
|
1235
|
+
>
|
|
1236
|
+
{{ validationErrors[getFieldErrorKey(selectedField.draftId, "step")] }}
|
|
1237
|
+
</p>
|
|
1238
|
+
</label>
|
|
1239
|
+
</section>
|
|
1240
|
+
|
|
1241
|
+
<section
|
|
1242
|
+
v-if="selectedField.field.type === 'select'"
|
|
1243
|
+
data-slot="fields-configurator-select-options"
|
|
1244
|
+
class="grid gap-4 md:grid-cols-2"
|
|
1245
|
+
>
|
|
1246
|
+
<label class="flex flex-col gap-2">
|
|
1247
|
+
<span class="text-xs font-medium text-zinc-500">
|
|
1248
|
+
{{ t("field-options") }}
|
|
1249
|
+
</span>
|
|
1250
|
+
<Textarea
|
|
1251
|
+
data-slot="fields-configurator-field-options-input"
|
|
1252
|
+
:model-value="selectedField.field.options"
|
|
1253
|
+
:aria-invalid="validationErrors[getFieldErrorKey(selectedField.draftId, 'options')] ? 'true' : void 0"
|
|
1254
|
+
:placeholder="t('field-options-placeholder')"
|
|
1255
|
+
class="min-h-20 font-mono text-sm"
|
|
1256
|
+
@update:model-value="updateSelectedSelectOptions"
|
|
1257
|
+
/>
|
|
1258
|
+
<p
|
|
1259
|
+
v-if="validationErrors[getFieldErrorKey(selectedField.draftId, 'options')]"
|
|
1260
|
+
class="text-xs text-red-500"
|
|
1261
|
+
>
|
|
1262
|
+
{{ validationErrors[getFieldErrorKey(selectedField.draftId, "options")] }}
|
|
1263
|
+
</p>
|
|
1264
|
+
</label>
|
|
1265
|
+
|
|
1266
|
+
<label class="flex flex-col gap-2">
|
|
1267
|
+
<span class="text-xs font-medium text-zinc-500">
|
|
1268
|
+
{{ t("field-option-label") }}
|
|
1269
|
+
</span>
|
|
1270
|
+
<Textarea
|
|
1271
|
+
data-slot="fields-configurator-field-option-label-input"
|
|
1272
|
+
:model-value="selectedField.field.label"
|
|
1273
|
+
:aria-invalid="validationErrors[getFieldErrorKey(selectedField.draftId, 'label')] ? 'true' : void 0"
|
|
1274
|
+
:placeholder="t('field-option-label-placeholder')"
|
|
1275
|
+
class="min-h-20 font-mono text-sm"
|
|
1276
|
+
@update:model-value="updateSelectedSelectLabel"
|
|
1277
|
+
/>
|
|
1278
|
+
<p
|
|
1279
|
+
v-if="validationErrors[getFieldErrorKey(selectedField.draftId, 'label')]"
|
|
1280
|
+
class="text-xs text-red-500"
|
|
1281
|
+
>
|
|
1282
|
+
{{ validationErrors[getFieldErrorKey(selectedField.draftId, "label")] }}
|
|
1283
|
+
</p>
|
|
1284
|
+
</label>
|
|
1285
|
+
|
|
1286
|
+
<label class="flex flex-col gap-2">
|
|
1287
|
+
<span class="text-xs font-medium text-zinc-500">
|
|
1288
|
+
{{ t("field-option-value") }}
|
|
1289
|
+
</span>
|
|
1290
|
+
<Textarea
|
|
1291
|
+
data-slot="fields-configurator-field-option-value-input"
|
|
1292
|
+
:model-value="selectedField.field.value"
|
|
1293
|
+
:aria-invalid="validationErrors[getFieldErrorKey(selectedField.draftId, 'value')] ? 'true' : void 0"
|
|
1294
|
+
:placeholder="t('field-option-value-placeholder')"
|
|
1295
|
+
class="min-h-20 font-mono text-sm"
|
|
1296
|
+
@update:model-value="updateSelectedSelectValue"
|
|
1297
|
+
/>
|
|
1298
|
+
<p
|
|
1299
|
+
v-if="validationErrors[getFieldErrorKey(selectedField.draftId, 'value')]"
|
|
1300
|
+
class="text-xs text-red-500"
|
|
1301
|
+
>
|
|
1302
|
+
{{ validationErrors[getFieldErrorKey(selectedField.draftId, "value")] }}
|
|
1303
|
+
</p>
|
|
1304
|
+
</label>
|
|
1305
|
+
|
|
1306
|
+
<label class="flex flex-col gap-2">
|
|
1307
|
+
<span class="text-xs font-medium text-zinc-500">
|
|
1308
|
+
{{ t("field-option-key") }}
|
|
1309
|
+
</span>
|
|
1310
|
+
<Textarea
|
|
1311
|
+
data-slot="fields-configurator-field-option-key-input"
|
|
1312
|
+
:model-value="selectedField.field.key"
|
|
1313
|
+
:aria-invalid="validationErrors[getFieldErrorKey(selectedField.draftId, 'key')] ? 'true' : void 0"
|
|
1314
|
+
:placeholder="t('field-option-key-placeholder')"
|
|
1315
|
+
class="min-h-20 font-mono text-sm"
|
|
1316
|
+
@update:model-value="updateSelectedSelectKey"
|
|
1317
|
+
/>
|
|
1318
|
+
<p
|
|
1319
|
+
v-if="validationErrors[getFieldErrorKey(selectedField.draftId, 'key')]"
|
|
1320
|
+
class="text-xs text-red-500"
|
|
1321
|
+
>
|
|
1322
|
+
{{ validationErrors[getFieldErrorKey(selectedField.draftId, "key")] }}
|
|
1323
|
+
</p>
|
|
1324
|
+
</label>
|
|
1325
|
+
</section>
|
|
1326
|
+
|
|
1327
|
+
<section
|
|
1328
|
+
v-if="selectedField.field.type === 'calendar'"
|
|
1329
|
+
data-slot="fields-configurator-calendar-options"
|
|
1330
|
+
class="grid gap-4 md:grid-cols-2"
|
|
1331
|
+
>
|
|
1332
|
+
<label class="flex flex-col gap-2">
|
|
1333
|
+
<span class="text-xs font-medium text-zinc-500">
|
|
1334
|
+
{{ t("field-calendar-mode") }}
|
|
1335
|
+
</span>
|
|
1336
|
+
<NativeSelect
|
|
1337
|
+
data-slot="fields-configurator-field-calendar-mode-select"
|
|
1338
|
+
:model-value="selectedField.field.mode"
|
|
1339
|
+
@update:model-value="updateSelectedCalendarMode"
|
|
1340
|
+
>
|
|
1341
|
+
<NativeSelectOption value="year">
|
|
1342
|
+
{{ t("field-calendar-mode-year") }}
|
|
1343
|
+
</NativeSelectOption>
|
|
1344
|
+
<NativeSelectOption value="month">
|
|
1345
|
+
{{ t("field-calendar-mode-month") }}
|
|
1346
|
+
</NativeSelectOption>
|
|
1347
|
+
<NativeSelectOption value="date">
|
|
1348
|
+
{{ t("field-calendar-mode-date") }}
|
|
1349
|
+
</NativeSelectOption>
|
|
1350
|
+
</NativeSelect>
|
|
1351
|
+
</label>
|
|
1352
|
+
|
|
1353
|
+
<label class="flex flex-col gap-2">
|
|
1354
|
+
<span class="text-xs font-medium text-zinc-500">
|
|
1355
|
+
{{ t("field-calendar-display") }}
|
|
1356
|
+
</span>
|
|
1357
|
+
<Input
|
|
1358
|
+
data-slot="fields-configurator-field-calendar-display-input"
|
|
1359
|
+
:model-value="selectedField.field.display ?? ''"
|
|
1360
|
+
:placeholder="t('field-calendar-display-placeholder')"
|
|
1361
|
+
@update:model-value="updateSelectedCalendarDisplay"
|
|
1362
|
+
/>
|
|
1363
|
+
</label>
|
|
1364
|
+
|
|
1365
|
+
<label class="flex flex-col gap-2">
|
|
1366
|
+
<span class="text-xs font-medium text-zinc-500">
|
|
1367
|
+
{{ t("field-calendar-value") }}
|
|
1368
|
+
</span>
|
|
1369
|
+
<Input
|
|
1370
|
+
data-slot="fields-configurator-field-calendar-value-input"
|
|
1371
|
+
:model-value="selectedField.field.value"
|
|
1372
|
+
:aria-invalid="validationErrors[getFieldErrorKey(selectedField.draftId, 'value')] ? 'true' : void 0"
|
|
1373
|
+
:placeholder="t('field-calendar-value-placeholder')"
|
|
1374
|
+
@update:model-value="updateSelectedCalendarValue"
|
|
1375
|
+
/>
|
|
1376
|
+
<p
|
|
1377
|
+
v-if="validationErrors[getFieldErrorKey(selectedField.draftId, 'value')]"
|
|
1378
|
+
class="text-xs text-red-500"
|
|
1379
|
+
>
|
|
1380
|
+
{{ validationErrors[getFieldErrorKey(selectedField.draftId, "value")] }}
|
|
1381
|
+
</p>
|
|
1382
|
+
</label>
|
|
1383
|
+
|
|
1384
|
+
<label class="flex flex-col gap-2">
|
|
1385
|
+
<span class="text-xs font-medium text-zinc-500">
|
|
1386
|
+
{{ t("field-disable-date") }}
|
|
1387
|
+
</span>
|
|
1388
|
+
<Textarea
|
|
1389
|
+
data-slot="fields-configurator-field-disable-date-input"
|
|
1390
|
+
:model-value="selectedField.field.disableDate ?? ''"
|
|
1391
|
+
:aria-invalid="validationErrors[getFieldErrorKey(selectedField.draftId, 'disableDate')] ? 'true' : void 0"
|
|
1392
|
+
:placeholder="t('field-disable-date-placeholder')"
|
|
1393
|
+
class="min-h-20 font-mono text-sm"
|
|
1394
|
+
@update:model-value="updateSelectedCalendarDisableDate"
|
|
1395
|
+
/>
|
|
1396
|
+
<p
|
|
1397
|
+
v-if="validationErrors[getFieldErrorKey(selectedField.draftId, 'disableDate')]"
|
|
1398
|
+
class="text-xs text-red-500"
|
|
1399
|
+
>
|
|
1400
|
+
{{ validationErrors[getFieldErrorKey(selectedField.draftId, "disableDate")] }}
|
|
1401
|
+
</p>
|
|
1402
|
+
</label>
|
|
1403
|
+
</section>
|
|
1404
|
+
|
|
1405
|
+
<section
|
|
1406
|
+
data-slot="fields-configurator-validation"
|
|
1407
|
+
class="flex flex-col gap-4"
|
|
1408
|
+
>
|
|
1409
|
+
<div class="flex items-center justify-between gap-3">
|
|
1410
|
+
<div>
|
|
1411
|
+
<p class="text-xs font-medium text-zinc-500">
|
|
1412
|
+
{{ t("field-validation") }}
|
|
1413
|
+
</p>
|
|
1414
|
+
<p class="text-xs text-zinc-400">
|
|
1415
|
+
{{ t("field-validation-description") }}
|
|
1416
|
+
</p>
|
|
1417
|
+
</div>
|
|
1418
|
+
|
|
1419
|
+
<Button
|
|
1420
|
+
type="button"
|
|
1421
|
+
data-slot="fields-configurator-validation-add"
|
|
1422
|
+
size="sm"
|
|
1423
|
+
variant="default"
|
|
1424
|
+
@click="addValidationRule"
|
|
1425
|
+
>
|
|
1426
|
+
<Icon icon="fluent:add-20-regular" />
|
|
1427
|
+
{{ t("add-validation-rule") }}
|
|
1428
|
+
</Button>
|
|
1429
|
+
</div>
|
|
1430
|
+
|
|
1431
|
+
<div
|
|
1432
|
+
v-if="selectedFieldValidationRules.length > 0"
|
|
1433
|
+
class="flex flex-col gap-4"
|
|
1434
|
+
>
|
|
1435
|
+
<div
|
|
1436
|
+
v-for="(rule, index) in selectedFieldValidationRules"
|
|
1437
|
+
:key="`${selectedField.draftId}:${index}`"
|
|
1438
|
+
data-slot="fields-configurator-validation-rule"
|
|
1439
|
+
class="rounded-lg border border-zinc-200 p-4"
|
|
1440
|
+
>
|
|
1441
|
+
<div class="mb-3 flex items-center justify-between gap-3">
|
|
1442
|
+
<p class="text-sm font-medium text-zinc-700">
|
|
1443
|
+
{{ t("validation-rule-title", { index: index + 1 }) }}
|
|
1444
|
+
</p>
|
|
1445
|
+
|
|
1446
|
+
<div class="flex items-center gap-1">
|
|
1447
|
+
<Button
|
|
1448
|
+
type="button"
|
|
1449
|
+
size="xs"
|
|
1450
|
+
variant="ghost"
|
|
1451
|
+
data-slot="fields-configurator-validation-move-up"
|
|
1452
|
+
:disabled="index === 0"
|
|
1453
|
+
@click="moveValidationRule(index, -1)"
|
|
1454
|
+
>
|
|
1455
|
+
<Icon icon="fluent:arrow-up-20-regular" />
|
|
1456
|
+
</Button>
|
|
1457
|
+
|
|
1458
|
+
<Button
|
|
1459
|
+
type="button"
|
|
1460
|
+
size="xs"
|
|
1461
|
+
variant="ghost"
|
|
1462
|
+
data-slot="fields-configurator-validation-move-down"
|
|
1463
|
+
:disabled="index === selectedFieldValidationRules.length - 1"
|
|
1464
|
+
@click="moveValidationRule(index, 1)"
|
|
1465
|
+
>
|
|
1466
|
+
<Icon icon="fluent:arrow-down-20-regular" />
|
|
1467
|
+
</Button>
|
|
1468
|
+
|
|
1469
|
+
<Button
|
|
1470
|
+
type="button"
|
|
1471
|
+
size="xs"
|
|
1472
|
+
variant="ghost"
|
|
1473
|
+
data-slot="fields-configurator-validation-delete"
|
|
1474
|
+
@click="deleteValidationRule(index)"
|
|
1475
|
+
>
|
|
1476
|
+
<Icon icon="fluent:delete-20-regular" />
|
|
1477
|
+
</Button>
|
|
1478
|
+
</div>
|
|
1479
|
+
</div>
|
|
1480
|
+
|
|
1481
|
+
<div class="grid gap-4">
|
|
1482
|
+
<label class="flex flex-col gap-2">
|
|
1483
|
+
<span class="text-xs font-medium text-zinc-500">
|
|
1484
|
+
{{ t("validation-expression") }}
|
|
1485
|
+
</span>
|
|
1486
|
+
<Textarea
|
|
1487
|
+
data-slot="fields-configurator-validation-expression-input"
|
|
1488
|
+
:model-value="rule.expression"
|
|
1489
|
+
:aria-invalid="validationErrors[getValidationRuleErrorKey(selectedField.draftId, index, 'expression')] ? 'true' : void 0"
|
|
1490
|
+
:placeholder="t('validation-expression-placeholder')"
|
|
1491
|
+
class="min-h-20 font-mono text-sm"
|
|
1492
|
+
@update:model-value="updateSelectedValidationExpression(index, $event)"
|
|
1493
|
+
/>
|
|
1494
|
+
<p
|
|
1495
|
+
v-if="validationErrors[getValidationRuleErrorKey(selectedField.draftId, index, 'expression')]"
|
|
1496
|
+
class="text-xs text-red-500"
|
|
1497
|
+
>
|
|
1498
|
+
{{ validationErrors[getValidationRuleErrorKey(selectedField.draftId, index, "expression")] }}
|
|
1499
|
+
</p>
|
|
1500
|
+
</label>
|
|
1501
|
+
|
|
1502
|
+
<label class="flex flex-col gap-2">
|
|
1503
|
+
<span class="text-xs font-medium text-zinc-500">
|
|
1504
|
+
{{ t("validation-message") }}
|
|
1505
|
+
</span>
|
|
1506
|
+
<Textarea
|
|
1507
|
+
data-slot="fields-configurator-validation-message-input"
|
|
1508
|
+
:model-value="rule.message"
|
|
1509
|
+
:aria-invalid="validationErrors[getValidationRuleErrorKey(selectedField.draftId, index, 'message')] ? 'true' : void 0"
|
|
1510
|
+
:placeholder="t('validation-message-placeholder')"
|
|
1511
|
+
class="min-h-20 text-sm"
|
|
1512
|
+
@update:model-value="updateSelectedValidationMessage(index, $event)"
|
|
1513
|
+
/>
|
|
1514
|
+
<p
|
|
1515
|
+
v-if="validationErrors[getValidationRuleErrorKey(selectedField.draftId, index, 'message')]"
|
|
1516
|
+
class="text-xs text-red-500"
|
|
1517
|
+
>
|
|
1518
|
+
{{ validationErrors[getValidationRuleErrorKey(selectedField.draftId, index, "message")] }}
|
|
1519
|
+
</p>
|
|
1520
|
+
</label>
|
|
1521
|
+
</div>
|
|
1522
|
+
</div>
|
|
1523
|
+
</div>
|
|
1524
|
+
|
|
1525
|
+
<p
|
|
1526
|
+
v-else
|
|
1527
|
+
data-slot="fields-configurator-validation-empty"
|
|
1528
|
+
class="rounded-lg border border-dashed border-zinc-200 px-4 py-6 text-sm text-zinc-400"
|
|
1529
|
+
>
|
|
1530
|
+
{{ t("no-validation-rules") }}
|
|
1531
|
+
</p>
|
|
306
1532
|
</section>
|
|
307
1533
|
</div>
|
|
308
1534
|
</section>
|
|
@@ -337,10 +1563,68 @@ function confirmChanges() {
|
|
|
337
1563
|
"zh": {
|
|
338
1564
|
"configure-fields": "配置字段",
|
|
339
1565
|
"configure-fields-description": "在这里浏览通用项和字段配置项。",
|
|
1566
|
+
"field-list": "字段列表",
|
|
340
1567
|
"general": "通用",
|
|
341
|
-
"general-
|
|
342
|
-
"general-empty": "
|
|
343
|
-
"field
|
|
1568
|
+
"general-description": "当前没有可保存的字段集合通用配置。",
|
|
1569
|
+
"general-empty": "字段集合的通用配置目前由宿主组件决定,这里只配置字段自身。",
|
|
1570
|
+
"add-field": "新增字段",
|
|
1571
|
+
"field-type": "字段类型",
|
|
1572
|
+
"field-type-string": "文本",
|
|
1573
|
+
"field-type-number": "数字",
|
|
1574
|
+
"field-type-select": "选择",
|
|
1575
|
+
"field-type-calendar": "日期",
|
|
1576
|
+
"field-path": "字段路径",
|
|
1577
|
+
"field-path-placeholder": "例如 profile.name",
|
|
1578
|
+
"field-path-required": "字段路径不能为空",
|
|
1579
|
+
"field-path-duplicate": "字段路径不能重复",
|
|
1580
|
+
"field-label": "字段标题",
|
|
1581
|
+
"field-icon": "图标",
|
|
1582
|
+
"field-icon-placeholder": "例如 fluent:person-20-regular",
|
|
1583
|
+
"field-style": "样式表达式",
|
|
1584
|
+
"field-style-placeholder": "例如 width: 100%",
|
|
1585
|
+
"field-hidden": "隐藏条件",
|
|
1586
|
+
"field-hidden-placeholder": "返回 true 时隐藏字段",
|
|
1587
|
+
"field-disabled": "禁用条件",
|
|
1588
|
+
"field-disabled-placeholder": "返回 true 时禁用字段",
|
|
1589
|
+
"field-discard-empty-string": "保留空字符串",
|
|
1590
|
+
"field-discard-empty-string-description": "开启后,清空输入时仍保存空字符串。",
|
|
1591
|
+
"field-max-length": "最大长度",
|
|
1592
|
+
"field-max-length-placeholder": "例如 100",
|
|
1593
|
+
"field-min": "最小值",
|
|
1594
|
+
"field-min-placeholder": "例如 0.0",
|
|
1595
|
+
"field-max": "最大值",
|
|
1596
|
+
"field-max-placeholder": "例如 100.0",
|
|
1597
|
+
"field-step": "步长",
|
|
1598
|
+
"field-step-placeholder": "例如 0.5",
|
|
1599
|
+
"field-options": "候选项表达式",
|
|
1600
|
+
"field-options-placeholder": "例如 users",
|
|
1601
|
+
"field-option-label": "选项标题表达式",
|
|
1602
|
+
"field-option-label-placeholder": "例如 option.name",
|
|
1603
|
+
"field-option-value": "选项值表达式",
|
|
1604
|
+
"field-option-value-placeholder": "例如 option.id",
|
|
1605
|
+
"field-option-key": "选项 key 表达式",
|
|
1606
|
+
"field-option-key-placeholder": "例如 string(option.id)",
|
|
1607
|
+
"field-calendar-mode": "日期精度",
|
|
1608
|
+
"field-calendar-mode-year": "年",
|
|
1609
|
+
"field-calendar-mode-month": "年月",
|
|
1610
|
+
"field-calendar-mode-date": "年月日",
|
|
1611
|
+
"field-calendar-display": "展示格式",
|
|
1612
|
+
"field-calendar-display-placeholder": "例如 yyyy年MM月dd日",
|
|
1613
|
+
"field-calendar-value": "存储格式",
|
|
1614
|
+
"field-calendar-value-placeholder": "例如 yyyy-MM-dd",
|
|
1615
|
+
"calendar-value-required": "日期存储格式不能为空",
|
|
1616
|
+
"field-disable-date": "禁用日期条件",
|
|
1617
|
+
"field-disable-date-placeholder": "返回 true 时禁用该日期",
|
|
1618
|
+
"field-validation": "校验规则",
|
|
1619
|
+
"field-validation-description": "字段失焦时按顺序执行,命中第一个失败规则后停止。",
|
|
1620
|
+
"add-validation-rule": "新增规则",
|
|
1621
|
+
"validation-rule-title": "规则 {index}",
|
|
1622
|
+
"validation-expression": "校验表达式",
|
|
1623
|
+
"validation-expression-placeholder": "返回 false 时展示下面的消息",
|
|
1624
|
+
"validation-message": "失败消息",
|
|
1625
|
+
"validation-message-placeholder": "支持 Markdown 与双花括号表达式",
|
|
1626
|
+
"no-validation-rules": "暂未配置校验规则。",
|
|
1627
|
+
"unnamed-field": "未命名{type}字段",
|
|
344
1628
|
"no-fields": "还没有字段。",
|
|
345
1629
|
"drag-field": "拖拽调整字段顺序:{field}",
|
|
346
1630
|
"delete-field": "删除字段:{field}",
|
|
@@ -350,10 +1634,68 @@ function confirmChanges() {
|
|
|
350
1634
|
"ja": {
|
|
351
1635
|
"configure-fields": "フィールドを設定",
|
|
352
1636
|
"configure-fields-description": "共通項目とフィールド設定をここで確認できます。",
|
|
1637
|
+
"field-list": "フィールド一覧",
|
|
353
1638
|
"general": "共通",
|
|
354
|
-
"general-
|
|
355
|
-
"general-empty": "
|
|
356
|
-
"field
|
|
1639
|
+
"general-description": "現在、保存できるフィールド群の共通設定はありません。",
|
|
1640
|
+
"general-empty": "フィールド群の共通設定はホスト側で管理され、この画面では各フィールドのみ編集できます。",
|
|
1641
|
+
"add-field": "フィールドを追加",
|
|
1642
|
+
"field-type": "フィールド種別",
|
|
1643
|
+
"field-type-string": "テキスト",
|
|
1644
|
+
"field-type-number": "数値",
|
|
1645
|
+
"field-type-select": "選択",
|
|
1646
|
+
"field-type-calendar": "日付",
|
|
1647
|
+
"field-path": "フィールドパス",
|
|
1648
|
+
"field-path-placeholder": "例: profile.name",
|
|
1649
|
+
"field-path-required": "フィールドパスは必須です",
|
|
1650
|
+
"field-path-duplicate": "フィールドパスは重複できません",
|
|
1651
|
+
"field-label": "フィールドラベル",
|
|
1652
|
+
"field-icon": "アイコン",
|
|
1653
|
+
"field-icon-placeholder": "例: fluent:person-20-regular",
|
|
1654
|
+
"field-style": "スタイル式",
|
|
1655
|
+
"field-style-placeholder": "例: width: 100%",
|
|
1656
|
+
"field-hidden": "非表示条件",
|
|
1657
|
+
"field-hidden-placeholder": "true を返すと非表示になります",
|
|
1658
|
+
"field-disabled": "無効化条件",
|
|
1659
|
+
"field-disabled-placeholder": "true を返すと無効になります",
|
|
1660
|
+
"field-discard-empty-string": "空文字列を保持",
|
|
1661
|
+
"field-discard-empty-string-description": "有効にすると、入力を空にしても空文字列を保存します。",
|
|
1662
|
+
"field-max-length": "最大長",
|
|
1663
|
+
"field-max-length-placeholder": "例: 100",
|
|
1664
|
+
"field-min": "最小値",
|
|
1665
|
+
"field-min-placeholder": "例: 0.0",
|
|
1666
|
+
"field-max": "最大値",
|
|
1667
|
+
"field-max-placeholder": "例: 100.0",
|
|
1668
|
+
"field-step": "ステップ",
|
|
1669
|
+
"field-step-placeholder": "例: 0.5",
|
|
1670
|
+
"field-options": "候補式",
|
|
1671
|
+
"field-options-placeholder": "例: users",
|
|
1672
|
+
"field-option-label": "候補ラベル式",
|
|
1673
|
+
"field-option-label-placeholder": "例: option.name",
|
|
1674
|
+
"field-option-value": "候補値式",
|
|
1675
|
+
"field-option-value-placeholder": "例: option.id",
|
|
1676
|
+
"field-option-key": "候補 key 式",
|
|
1677
|
+
"field-option-key-placeholder": "例: string(option.id)",
|
|
1678
|
+
"field-calendar-mode": "日付粒度",
|
|
1679
|
+
"field-calendar-mode-year": "年",
|
|
1680
|
+
"field-calendar-mode-month": "年月",
|
|
1681
|
+
"field-calendar-mode-date": "年月日",
|
|
1682
|
+
"field-calendar-display": "表示形式",
|
|
1683
|
+
"field-calendar-display-placeholder": "例: yyyy年MM月dd日",
|
|
1684
|
+
"field-calendar-value": "保存形式",
|
|
1685
|
+
"field-calendar-value-placeholder": "例: yyyy-MM-dd",
|
|
1686
|
+
"calendar-value-required": "日付の保存形式は必須です",
|
|
1687
|
+
"field-disable-date": "無効日条件",
|
|
1688
|
+
"field-disable-date-placeholder": "true を返す日付を無効化します",
|
|
1689
|
+
"field-validation": "検証ルール",
|
|
1690
|
+
"field-validation-description": "フォーカスを外したときに順番に評価し、最初の失敗で停止します。",
|
|
1691
|
+
"add-validation-rule": "ルールを追加",
|
|
1692
|
+
"validation-rule-title": "ルール {index}",
|
|
1693
|
+
"validation-expression": "検証式",
|
|
1694
|
+
"validation-expression-placeholder": "false を返すと下のメッセージを表示します",
|
|
1695
|
+
"validation-message": "失敗メッセージ",
|
|
1696
|
+
"validation-message-placeholder": "Markdown と二重波括弧式を利用できます",
|
|
1697
|
+
"no-validation-rules": "検証ルールはまだありません。",
|
|
1698
|
+
"unnamed-field": "未命名の{type}フィールド",
|
|
357
1699
|
"no-fields": "フィールドがありません。",
|
|
358
1700
|
"drag-field": "{field} の順序をドラッグで変更",
|
|
359
1701
|
"delete-field": "{field} を削除",
|
|
@@ -363,10 +1705,68 @@ function confirmChanges() {
|
|
|
363
1705
|
"en": {
|
|
364
1706
|
"configure-fields": "Configure Fields",
|
|
365
1707
|
"configure-fields-description": "Browse the shared and field-level settings here.",
|
|
1708
|
+
"field-list": "Field list",
|
|
366
1709
|
"general": "General",
|
|
367
|
-
"general-
|
|
368
|
-
"general-empty": "
|
|
369
|
-
"field
|
|
1710
|
+
"general-description": "There is no shared field-group configuration to save here.",
|
|
1711
|
+
"general-empty": "Field-group level settings are owned by the host. This dialog only edits fields.",
|
|
1712
|
+
"add-field": "Add field",
|
|
1713
|
+
"field-type": "Field type",
|
|
1714
|
+
"field-type-string": "Text",
|
|
1715
|
+
"field-type-number": "Number",
|
|
1716
|
+
"field-type-select": "Select",
|
|
1717
|
+
"field-type-calendar": "Date",
|
|
1718
|
+
"field-path": "Field path",
|
|
1719
|
+
"field-path-placeholder": "For example profile.name",
|
|
1720
|
+
"field-path-required": "Field path is required",
|
|
1721
|
+
"field-path-duplicate": "Field path must be unique",
|
|
1722
|
+
"field-label": "Field label",
|
|
1723
|
+
"field-icon": "Icon",
|
|
1724
|
+
"field-icon-placeholder": "For example fluent:person-20-regular",
|
|
1725
|
+
"field-style": "Style expression",
|
|
1726
|
+
"field-style-placeholder": "For example width: 100%",
|
|
1727
|
+
"field-hidden": "Hidden expression",
|
|
1728
|
+
"field-hidden-placeholder": "Return true to hide the field",
|
|
1729
|
+
"field-disabled": "Disabled expression",
|
|
1730
|
+
"field-disabled-placeholder": "Return true to disable the field",
|
|
1731
|
+
"field-discard-empty-string": "Keep empty string",
|
|
1732
|
+
"field-discard-empty-string-description": "When enabled, clearing the input still stores an empty string.",
|
|
1733
|
+
"field-max-length": "Max length",
|
|
1734
|
+
"field-max-length-placeholder": "For example 100",
|
|
1735
|
+
"field-min": "Min",
|
|
1736
|
+
"field-min-placeholder": "For example 0.0",
|
|
1737
|
+
"field-max": "Max",
|
|
1738
|
+
"field-max-placeholder": "For example 100.0",
|
|
1739
|
+
"field-step": "Step",
|
|
1740
|
+
"field-step-placeholder": "For example 0.5",
|
|
1741
|
+
"field-options": "Options expression",
|
|
1742
|
+
"field-options-placeholder": "For example users",
|
|
1743
|
+
"field-option-label": "Option label expression",
|
|
1744
|
+
"field-option-label-placeholder": "For example option.name",
|
|
1745
|
+
"field-option-value": "Option value expression",
|
|
1746
|
+
"field-option-value-placeholder": "For example option.id",
|
|
1747
|
+
"field-option-key": "Option key expression",
|
|
1748
|
+
"field-option-key-placeholder": "For example string(option.id)",
|
|
1749
|
+
"field-calendar-mode": "Date precision",
|
|
1750
|
+
"field-calendar-mode-year": "Year",
|
|
1751
|
+
"field-calendar-mode-month": "Month",
|
|
1752
|
+
"field-calendar-mode-date": "Date",
|
|
1753
|
+
"field-calendar-display": "Display format",
|
|
1754
|
+
"field-calendar-display-placeholder": "For example yyyy-MM-dd",
|
|
1755
|
+
"field-calendar-value": "Storage format",
|
|
1756
|
+
"field-calendar-value-placeholder": "For example yyyy-MM-dd",
|
|
1757
|
+
"calendar-value-required": "Storage format is required",
|
|
1758
|
+
"field-disable-date": "Disabled date expression",
|
|
1759
|
+
"field-disable-date-placeholder": "Return true to disable the date",
|
|
1760
|
+
"field-validation": "Validation rules",
|
|
1761
|
+
"field-validation-description": "Rules run on blur in order and stop at the first failure.",
|
|
1762
|
+
"add-validation-rule": "Add rule",
|
|
1763
|
+
"validation-rule-title": "Rule {index}",
|
|
1764
|
+
"validation-expression": "Validation expression",
|
|
1765
|
+
"validation-expression-placeholder": "Return false to show the message below",
|
|
1766
|
+
"validation-message": "Failure message",
|
|
1767
|
+
"validation-message-placeholder": "Supports Markdown and double-curly expressions",
|
|
1768
|
+
"no-validation-rules": "No validation rules yet.",
|
|
1769
|
+
"unnamed-field": "Untitled {type} field",
|
|
370
1770
|
"no-fields": "No fields yet.",
|
|
371
1771
|
"drag-field": "Drag to reorder field {field}",
|
|
372
1772
|
"delete-field": "Delete field {field}",
|