@shwfed/nuxt 0.11.47 → 0.11.49

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.
@@ -7,6 +7,7 @@ import { computed, nextTick, ref, toRaw, watch } from "vue";
7
7
  import { useI18n } from "vue-i18n";
8
8
  import {
9
9
  CalendarFieldC,
10
+ ContainerFieldC,
10
11
  EmptyFieldC,
11
12
  FieldGroupC,
12
13
  FieldGroupStyleC,
@@ -46,6 +47,8 @@ import { NativeSelect, NativeSelectOption } from "../native-select";
46
47
  import { Switch } from "../switch";
47
48
  import { Textarea } from "../textarea";
48
49
  const ROOT_FIELD_GROUP_VALUE = "__root__";
50
+ const GROUP_OWNER_PREFIX = "group:";
51
+ const CONTAINER_OWNER_PREFIX = "container:";
49
52
  const props = defineProps({
50
53
  config: { type: null, required: true }
51
54
  });
@@ -78,6 +81,7 @@ const fieldTypeOptions = computed(() => [
78
81
  { type: "radio", label: t("field-type-radio") },
79
82
  { type: "calendar", label: t("field-type-calendar") },
80
83
  { type: "upload", label: t("field-type-upload") },
84
+ { type: "container", label: t("field-type-container") },
81
85
  { type: "empty", label: t("field-type-empty") },
82
86
  { type: "slot", label: t("field-type-slot") }
83
87
  ]);
@@ -86,28 +90,83 @@ const generalItem = computed(() => ({
86
90
  label: t("general")
87
91
  }));
88
92
  const normalizedSearch = computed(() => search.value.trim().toLocaleLowerCase());
93
+ function cloneValue(value) {
94
+ return JSON.parse(JSON.stringify(toRaw(value)));
95
+ }
96
+ function createContainerDraftField(field) {
97
+ const { fields, ...rest } = cloneValue(field);
98
+ return {
99
+ draftId: createDraftId(),
100
+ field: {
101
+ ...rest,
102
+ fields: []
103
+ },
104
+ children: cloneFields(fields)
105
+ };
106
+ }
107
+ function findDraftField(fields, draftId) {
108
+ for (const field of fields) {
109
+ if (field.draftId === draftId) {
110
+ return field;
111
+ }
112
+ const child = findDraftField(field.children, draftId);
113
+ if (child) {
114
+ return child;
115
+ }
116
+ }
117
+ return void 0;
118
+ }
119
+ function findFieldOwnerInFields(fields, draftId, owner) {
120
+ for (const field of fields) {
121
+ if (field.draftId === draftId) {
122
+ return owner;
123
+ }
124
+ if (field.field.type !== "container") {
125
+ continue;
126
+ }
127
+ const childOwner = findFieldOwnerInFields(field.children, draftId, {
128
+ kind: "container",
129
+ draftId: field.draftId
130
+ });
131
+ if (childOwner) {
132
+ return childOwner;
133
+ }
134
+ }
135
+ return void 0;
136
+ }
137
+ function findFieldOwnerByDraftId(draftId) {
138
+ const rootOwner = findFieldOwnerInFields(draftFields.value, draftId, {
139
+ kind: "root"
140
+ });
141
+ if (rootOwner) {
142
+ return rootOwner;
143
+ }
144
+ for (const group of draftGroups.value) {
145
+ const groupOwner = findFieldOwnerInFields(group.fields, draftId, {
146
+ kind: "group",
147
+ draftId: group.draftId
148
+ });
149
+ if (groupOwner) {
150
+ return groupOwner;
151
+ }
152
+ }
153
+ return void 0;
154
+ }
89
155
  const selectedGroup = computed(() => draftGroups.value.find((group) => group.draftId === selectedItemId.value));
90
156
  const selectedField = computed(() => {
91
- const rootField = draftFields.value.find((item) => item.draftId === selectedItemId.value);
157
+ const rootField = findDraftField(draftFields.value, selectedItemId.value);
92
158
  if (rootField) {
93
159
  return rootField;
94
160
  }
95
161
  for (const group of draftGroups.value) {
96
- const field = group.fields.find((item) => item.draftId === selectedItemId.value);
162
+ const field = findDraftField(group.fields, selectedItemId.value);
97
163
  if (field) {
98
164
  return field;
99
165
  }
100
166
  }
101
167
  return void 0;
102
168
  });
103
- const selectedFieldGroup = computed(() => {
104
- for (const group of draftGroups.value) {
105
- if (group.fields.some((item) => item.draftId === selectedItemId.value)) {
106
- return group;
107
- }
108
- }
109
- return void 0;
110
- });
169
+ const selectedFieldOwner = computed(() => findFieldOwnerByDraftId(selectedItemId.value));
111
170
  const usesContentsOrientation = computed(() => draftOrientation.value === "contents");
112
171
  const selectedFieldValidationRules = computed(() => {
113
172
  const field = selectedField.value?.field;
@@ -129,9 +188,13 @@ function createDefaultLocaleValue() {
129
188
  return [{ locale: "zh", message: "" }];
130
189
  }
131
190
  function createDraftField(field) {
191
+ if (field.type === "container") {
192
+ return createContainerDraftField(field);
193
+ }
132
194
  return {
133
195
  draftId: createDraftId(),
134
- field: JSON.parse(JSON.stringify(toRaw(field)))
196
+ field: cloneValue(field),
197
+ children: []
135
198
  };
136
199
  }
137
200
  function cloneFields(fields) {
@@ -140,11 +203,11 @@ function cloneFields(fields) {
140
203
  function createDraftGroup(group) {
141
204
  return {
142
205
  draftId: createDraftId(),
143
- group: JSON.parse(JSON.stringify(toRaw({
206
+ group: cloneValue({
144
207
  id: group.id,
145
208
  style: group.style,
146
209
  fields: []
147
- }))),
210
+ }),
148
211
  fields: cloneFields(group.fields)
149
212
  };
150
213
  }
@@ -164,7 +227,7 @@ function getFieldTypeLabel(type) {
164
227
  return t(`field-type-${type}`);
165
228
  }
166
229
  function isPassiveField(field) {
167
- return field.type === "slot" || field.type === "empty";
230
+ return field.type === "empty";
168
231
  }
169
232
  function isMarkdownField(field) {
170
233
  return field.type === "markdown";
@@ -176,33 +239,42 @@ function isMarkdownContentField(field) {
176
239
  return isMarkdownField(field) || isMarkdownBodyField(field);
177
240
  }
178
241
  function hasFieldLabel(field) {
179
- return !isPassiveField(field) && !isMarkdownBodyField(field);
242
+ return field.type !== "empty" && !isMarkdownBodyField(field);
180
243
  }
181
244
  function supportsFieldCellStyles(field) {
182
- return !isPassiveField(field) && !isMarkdownBodyField(field);
245
+ return field.type !== "empty" && !isMarkdownBodyField(field);
183
246
  }
184
247
  function isPathlessField(field) {
185
- return isPassiveField(field) || isMarkdownContentField(field);
248
+ return field.type === "slot" || field.type === "empty" || isMarkdownContentField(field) || field.type === "container";
186
249
  }
187
250
  function isInteractiveField(field) {
188
251
  return !isPathlessField(field);
189
252
  }
190
- function getSlotFieldLabel(_) {
191
- return getFieldTypeLabel("slot");
253
+ function getSlotFieldLabel(field) {
254
+ return getFieldChineseTitle(field) ?? getFieldTypeLabel("slot");
192
255
  }
193
256
  function getEmptyFieldLabel(_) {
194
257
  return getFieldTypeLabel("empty");
195
258
  }
259
+ function getContainerFieldLabel(field) {
260
+ return getFieldChineseTitle(field) ?? getUnnamedFieldLabel(field);
261
+ }
196
262
  function getUnnamedFieldLabel(field) {
197
263
  return t("unnamed-field", {
198
264
  type: getFieldTypeLabel(field.type)
199
265
  });
200
266
  }
267
+ function getFieldTitleValue(field) {
268
+ if (!hasFieldLabel(field)) {
269
+ return createDefaultLocaleValue();
270
+ }
271
+ return field.title ?? createDefaultLocaleValue();
272
+ }
201
273
  function getFieldChineseTitle(field) {
202
274
  if (!hasFieldLabel(field)) {
203
275
  return void 0;
204
276
  }
205
- const zhTitle = field.title.find((item) => item.locale === "zh");
277
+ const zhTitle = field.title?.find((item) => item.locale === "zh");
206
278
  if (!zhTitle) {
207
279
  return void 0;
208
280
  }
@@ -213,6 +285,9 @@ function getFieldListLabel(field) {
213
285
  if (field.type === "slot") {
214
286
  return getSlotFieldLabel(field);
215
287
  }
288
+ if (field.type === "container") {
289
+ return getContainerFieldLabel(field);
290
+ }
216
291
  if (field.type === "empty") {
217
292
  return getEmptyFieldLabel(field);
218
293
  }
@@ -223,32 +298,49 @@ function getGroupListLabel(index) {
223
298
  index: index + 1
224
299
  });
225
300
  }
226
- const rootFieldItems = computed(() => draftFields.value.map((item) => ({
227
- itemId: item.draftId,
228
- fieldId: item.field.id,
229
- label: getFieldListLabel(item.field),
230
- path: isPathlessField(item.field) ? void 0 : item.field.path,
231
- searchMeta: isPathlessField(item.field) ? item.field.id : [item.field.path, item.field.id].filter(Boolean).join(" "),
232
- type: item.field.type
233
- })));
301
+ function buildFieldSearchMeta(field) {
302
+ return isPathlessField(field) ? [field.id, getFieldChineseTitle(field)].filter(Boolean).join(" ") : [field.path, field.id, getFieldChineseTitle(field)].filter(Boolean).join(" ");
303
+ }
304
+ function flattenFieldItems(fields, depth, groupItemId) {
305
+ return fields.flatMap((item) => {
306
+ const currentItem = {
307
+ itemId: item.draftId,
308
+ groupItemId,
309
+ fieldId: item.field.id,
310
+ label: getFieldListLabel(item.field),
311
+ path: isPathlessField(item.field) ? void 0 : item.field.path,
312
+ searchMeta: buildFieldSearchMeta(item.field),
313
+ type: item.field.type,
314
+ depth
315
+ };
316
+ if (item.field.type !== "container") {
317
+ return [currentItem];
318
+ }
319
+ return [
320
+ currentItem,
321
+ ...flattenFieldItems(item.children, depth + 1, groupItemId)
322
+ ];
323
+ });
324
+ }
325
+ const rootFieldItems = computed(() => flattenFieldItems(draftFields.value, 0));
234
326
  const groupItems = computed(() => draftGroups.value.map((group, index) => {
235
- const fields = group.fields.map((item) => ({
236
- itemId: item.draftId,
237
- groupItemId: group.draftId,
238
- fieldId: item.field.id,
239
- label: getFieldListLabel(item.field),
240
- path: isPathlessField(item.field) ? void 0 : item.field.path,
241
- searchMeta: isPathlessField(item.field) ? item.field.id : [item.field.path, item.field.id].filter(Boolean).join(" "),
242
- type: item.field.type
243
- }));
244
327
  return {
245
328
  itemId: group.draftId,
246
329
  groupId: group.group.id,
247
330
  label: getGroupListLabel(index),
248
331
  searchMeta: group.group.id,
249
- fields
332
+ fields: flattenFieldItems(group.fields, 0, group.draftId)
250
333
  };
251
334
  }));
335
+ function getFieldOwnerValue(owner) {
336
+ if (owner.kind === "root") {
337
+ return ROOT_FIELD_GROUP_VALUE;
338
+ }
339
+ if (owner.kind === "group") {
340
+ return `${GROUP_OWNER_PREFIX}${owner.draftId}`;
341
+ }
342
+ return `${CONTAINER_OWNER_PREFIX}${owner.draftId}`;
343
+ }
252
344
  const filteredGroupItems = computed(() => {
253
345
  if (!normalizedSearch.value) {
254
346
  return groupItems.value;
@@ -279,6 +371,52 @@ const filteredRootFieldItems = computed(() => {
279
371
  });
280
372
  });
281
373
  const selectedItemLabel = computed(() => selectedField.value ? getFieldListLabel(selectedField.value.field) : selectedGroup.value ? getGroupListLabel(Math.max(draftGroups.value.findIndex((group) => group.draftId === selectedGroup.value?.draftId), 0)) : generalItem.value.label);
374
+ function collectDraftFieldIds(fields) {
375
+ return fields.flatMap((field) => [field.draftId, ...collectDraftFieldIds(field.children)]);
376
+ }
377
+ function buildFieldOwnerOptions(fields, depth, options, excludedDraftIds) {
378
+ for (const field of fields) {
379
+ if (field.field.type !== "container") {
380
+ continue;
381
+ }
382
+ if (!excludedDraftIds.has(field.draftId)) {
383
+ options.push({
384
+ value: getFieldOwnerValue({
385
+ kind: "container",
386
+ draftId: field.draftId
387
+ }),
388
+ label: `${" ".repeat(depth)}${getFieldListLabel(field.field)}`
389
+ });
390
+ }
391
+ buildFieldOwnerOptions(field.children, depth + 1, options, excludedDraftIds);
392
+ }
393
+ }
394
+ const selectedFieldOwnerOptions = computed(() => {
395
+ const options = [{
396
+ value: ROOT_FIELD_GROUP_VALUE,
397
+ label: t("field-group-none")
398
+ }];
399
+ const excludedDraftIds = new Set(
400
+ selectedField.value ? collectDraftFieldIds([selectedField.value]) : []
401
+ );
402
+ for (const group of groupItems.value) {
403
+ options.push({
404
+ value: `${GROUP_OWNER_PREFIX}${group.itemId}`,
405
+ label: group.label
406
+ });
407
+ }
408
+ buildFieldOwnerOptions(draftFields.value, 1, options, excludedDraftIds);
409
+ for (const group of draftGroups.value) {
410
+ buildFieldOwnerOptions(group.fields, 1, options, excludedDraftIds);
411
+ }
412
+ return options;
413
+ });
414
+ const selectedContainerChildItems = computed(() => {
415
+ if (selectedField.value?.field.type !== "container") {
416
+ return [];
417
+ }
418
+ return flattenFieldItems(selectedField.value.children, 1);
419
+ });
282
420
  function getFieldErrorKey(draftId, fieldKey) {
283
421
  return `${draftId}:${fieldKey}`;
284
422
  }
@@ -447,8 +585,27 @@ function normalizeField(field) {
447
585
  case "slot":
448
586
  return {
449
587
  ...field,
588
+ title: field.title,
589
+ hideLabel: field.hideLabel ? true : void 0,
590
+ required: field.required ? true : void 0,
591
+ hidden: normalizeOptionalString(field.hidden ?? ""),
592
+ labelStyle: normalizeOptionalString(field.labelStyle ?? ""),
593
+ contentStyle: normalizeOptionalString(field.contentStyle ?? ""),
450
594
  style: normalizeOptionalString(field.style ?? "")
451
595
  };
596
+ case "container":
597
+ return {
598
+ ...field,
599
+ hideLabel: field.hideLabel ? true : void 0,
600
+ required: field.required ? true : void 0,
601
+ hidden: normalizeOptionalString(field.hidden ?? ""),
602
+ labelStyle: normalizeOptionalString(field.labelStyle ?? ""),
603
+ contentStyle: normalizeOptionalString(field.contentStyle ?? ""),
604
+ bodyOrientation: field.bodyOrientation ? normalizeOrientation(field.bodyOrientation) : void 0,
605
+ bodyBordered: field.bodyBordered ? true : void 0,
606
+ bodyStyle: normalizeOptionalString(field.bodyStyle ?? ""),
607
+ fields: []
608
+ };
452
609
  case "empty":
453
610
  return {
454
611
  ...field,
@@ -522,6 +679,14 @@ function createField(type) {
522
679
  path: "",
523
680
  title
524
681
  };
682
+ case "container":
683
+ return {
684
+ id,
685
+ type,
686
+ hideLabel: void 0,
687
+ title,
688
+ fields: []
689
+ };
525
690
  case "empty":
526
691
  return {
527
692
  id,
@@ -569,6 +734,120 @@ function moveField(fields, oldIndex, newIndex) {
569
734
  nextFields.splice(newIndex, 0, movedField);
570
735
  return nextFields;
571
736
  }
737
+ function mapDraftFields(fields, updater) {
738
+ return fields.map((item) => {
739
+ const nextItem = updater(item);
740
+ if (nextItem.field.type !== "container") {
741
+ return nextItem;
742
+ }
743
+ return {
744
+ ...nextItem,
745
+ children: mapDraftFields(nextItem.children, updater)
746
+ };
747
+ });
748
+ }
749
+ function updateContainerFields(fields, containerDraftId, updater) {
750
+ return fields.map((item) => {
751
+ if (item.field.type !== "container") {
752
+ return item;
753
+ }
754
+ if (item.draftId === containerDraftId) {
755
+ return {
756
+ ...item,
757
+ children: updater(item.children)
758
+ };
759
+ }
760
+ return {
761
+ ...item,
762
+ children: updateContainerFields(item.children, containerDraftId, updater)
763
+ };
764
+ });
765
+ }
766
+ function findContainerChildren(fields, containerDraftId) {
767
+ for (const item of fields) {
768
+ if (item.field.type !== "container") {
769
+ continue;
770
+ }
771
+ if (item.draftId === containerDraftId) {
772
+ return item.children;
773
+ }
774
+ const children = findContainerChildren(item.children, containerDraftId);
775
+ if (children) {
776
+ return children;
777
+ }
778
+ }
779
+ return void 0;
780
+ }
781
+ function updateOwnerFields(owner, updater) {
782
+ if (owner.kind === "root") {
783
+ draftFields.value = updater(draftFields.value);
784
+ return;
785
+ }
786
+ if (owner.kind === "group") {
787
+ draftGroups.value = draftGroups.value.map((group) => group.draftId === owner.draftId ? {
788
+ ...group,
789
+ fields: updater(group.fields)
790
+ } : group);
791
+ return;
792
+ }
793
+ draftFields.value = updateContainerFields(draftFields.value, owner.draftId, updater);
794
+ draftGroups.value = draftGroups.value.map((group) => ({
795
+ ...group,
796
+ fields: updateContainerFields(group.fields, owner.draftId, updater)
797
+ }));
798
+ }
799
+ function getOwnerFields(owner) {
800
+ if (owner.kind === "root") {
801
+ return draftFields.value;
802
+ }
803
+ if (owner.kind === "group") {
804
+ return draftGroups.value.find((group) => group.draftId === owner.draftId)?.fields ?? [];
805
+ }
806
+ return findContainerChildren(draftFields.value, owner.draftId) ?? draftGroups.value.flatMap((group) => findContainerChildren(group.fields, owner.draftId) ?? []);
807
+ }
808
+ function removeFieldFromOwner(owner, fieldDraftId) {
809
+ let movedField;
810
+ updateOwnerFields(owner, (fields) => fields.filter((field) => {
811
+ if (field.draftId !== fieldDraftId) {
812
+ return true;
813
+ }
814
+ movedField = field;
815
+ return false;
816
+ }));
817
+ return movedField;
818
+ }
819
+ function insertFieldIntoOwner(owner, field, newIndex) {
820
+ updateOwnerFields(owner, (fields) => {
821
+ const nextFields = fields.slice();
822
+ const insertIndex = Math.min(Math.max(newIndex, 0), nextFields.length);
823
+ nextFields.splice(insertIndex, 0, field);
824
+ return nextFields;
825
+ });
826
+ }
827
+ function getOwnerItemCount(owner) {
828
+ return getOwnerFields(owner).length;
829
+ }
830
+ function getFieldOwnerFromValue(value) {
831
+ const ownerValue = String(value);
832
+ if (ownerValue === ROOT_FIELD_GROUP_VALUE) {
833
+ return {
834
+ kind: "root"
835
+ };
836
+ }
837
+ if (ownerValue.startsWith(GROUP_OWNER_PREFIX)) {
838
+ return {
839
+ kind: "group",
840
+ draftId: ownerValue.slice(GROUP_OWNER_PREFIX.length)
841
+ };
842
+ }
843
+ if (ownerValue.startsWith(CONTAINER_OWNER_PREFIX)) {
844
+ return {
845
+ kind: "container",
846
+ draftId: ownerValue.slice(CONTAINER_OWNER_PREFIX.length)
847
+ };
848
+ }
849
+ return void 0;
850
+ }
572
851
  function setSortableGroupListRef(groupDraftId, element) {
573
852
  sortableGroupListRefs.value[groupDraftId] = element instanceof HTMLElement ? element : null;
574
853
  }
@@ -604,84 +883,41 @@ function reorderRootFields(oldIndex, newIndex) {
604
883
  draftFields.value = moveField(draftFields.value, oldIndex, newIndex);
605
884
  }
606
885
  function moveFieldBetweenGroups(sourceGroupDraftId, targetGroupDraftId, fieldDraftId, newIndex) {
607
- let movedField;
608
- draftGroups.value = draftGroups.value.map((group) => {
609
- if (group.draftId === sourceGroupDraftId) {
610
- const nextFields = group.fields.filter((field) => {
611
- if (field.draftId !== fieldDraftId) {
612
- return true;
613
- }
614
- movedField = field;
615
- return false;
616
- });
617
- return {
618
- ...group,
619
- fields: nextFields
620
- };
621
- }
622
- return group;
623
- });
886
+ const movedField = removeFieldFromOwner({
887
+ kind: "group",
888
+ draftId: sourceGroupDraftId
889
+ }, fieldDraftId);
624
890
  if (!movedField) {
625
891
  return;
626
892
  }
627
- const nextMovedField = movedField;
628
- draftGroups.value = draftGroups.value.map((group) => {
629
- if (group.draftId !== targetGroupDraftId) {
630
- return group;
631
- }
632
- const nextFields = group.fields.slice();
633
- const insertIndex = Math.min(Math.max(newIndex, 0), nextFields.length);
634
- nextFields.splice(insertIndex, 0, nextMovedField);
635
- return {
636
- ...group,
637
- fields: nextFields
638
- };
639
- });
893
+ insertFieldIntoOwner({
894
+ kind: "group",
895
+ draftId: targetGroupDraftId
896
+ }, movedField, newIndex);
640
897
  }
641
898
  function moveFieldToRoot(sourceGroupDraftId, fieldDraftId, newIndex) {
642
- let movedField;
643
- draftGroups.value = draftGroups.value.map((group) => {
644
- if (group.draftId !== sourceGroupDraftId) {
645
- return group;
646
- }
647
- const nextFields2 = group.fields.filter((field) => {
648
- if (field.draftId !== fieldDraftId) {
649
- return true;
650
- }
651
- movedField = field;
652
- return false;
653
- });
654
- return {
655
- ...group,
656
- fields: nextFields2
657
- };
658
- });
899
+ const movedField = removeFieldFromOwner({
900
+ kind: "group",
901
+ draftId: sourceGroupDraftId
902
+ }, fieldDraftId);
659
903
  if (!movedField) {
660
904
  return;
661
905
  }
662
- const nextFields = draftFields.value.slice();
663
- const insertIndex = Math.min(Math.max(newIndex, 0), nextFields.length);
664
- nextFields.splice(insertIndex, 0, movedField);
665
- draftFields.value = nextFields;
906
+ insertFieldIntoOwner({
907
+ kind: "root"
908
+ }, movedField, newIndex);
666
909
  }
667
910
  function moveRootFieldToGroup(targetGroupDraftId, fieldDraftId, newIndex) {
668
- const movedField = draftFields.value.find((field) => field.draftId === fieldDraftId);
911
+ const movedField = removeFieldFromOwner({
912
+ kind: "root"
913
+ }, fieldDraftId);
669
914
  if (!movedField) {
670
915
  return;
671
916
  }
672
- draftFields.value = draftFields.value.filter((field) => field.draftId !== fieldDraftId);
673
- draftGroups.value = draftGroups.value.map((group) => {
674
- if (group.draftId !== targetGroupDraftId) {
675
- return group;
676
- }
677
- const nextFields = group.fields.slice();
678
- const insertIndex = Math.min(Math.max(newIndex, 0), nextFields.length);
679
- nextFields.splice(insertIndex, 0, movedField);
680
- return {
681
- ...group,
682
- fields: nextFields
683
- };
684
- });
917
+ insertFieldIntoOwner({
918
+ kind: "group",
919
+ draftId: targetGroupDraftId
920
+ }, movedField, newIndex);
685
921
  }
686
922
  async function refreshSortable() {
687
923
  for (const sortable of sortables.value) {
@@ -833,14 +1069,14 @@ function updateDraftStyle(value) {
833
1069
  draftStyle.value = normalizeOptionalString(String(value));
834
1070
  }
835
1071
  function updateDraftField(draftId, updater) {
836
- draftFields.value = draftFields.value.map((item) => item.draftId === draftId ? {
837
- draftId: item.draftId,
1072
+ draftFields.value = mapDraftFields(draftFields.value, (item) => item.draftId === draftId ? {
1073
+ ...item,
838
1074
  field: updater(item.field)
839
1075
  } : item);
840
1076
  draftGroups.value = draftGroups.value.map((group) => ({
841
1077
  ...group,
842
- fields: group.fields.map((item) => item.draftId === draftId ? {
843
- draftId: item.draftId,
1078
+ fields: mapDraftFields(group.fields, (item) => item.draftId === draftId ? {
1079
+ ...item,
844
1080
  field: updater(item.field)
845
1081
  } : item)
846
1082
  }));
@@ -861,38 +1097,39 @@ function updateSelectedGroupStyle(value) {
861
1097
  }
862
1098
  function updateSelectedFieldGroup(value) {
863
1099
  const selected = selectedField.value;
864
- const sourceGroup = selectedFieldGroup.value;
865
- if (!selected) {
1100
+ const sourceOwner = selectedFieldOwner.value;
1101
+ const targetOwner = getFieldOwnerFromValue(value);
1102
+ if (!selected || !sourceOwner || !targetOwner) {
866
1103
  return;
867
1104
  }
868
- const targetGroupDraftId = String(value);
869
- if (!targetGroupDraftId) {
1105
+ if (getFieldOwnerValue(sourceOwner) === getFieldOwnerValue(targetOwner)) {
870
1106
  return;
871
1107
  }
872
- if (targetGroupDraftId === ROOT_FIELD_GROUP_VALUE) {
873
- if (!sourceGroup) {
874
- return;
875
- }
876
- moveFieldToRoot(sourceGroup.draftId, selected.draftId, draftFields.value.length);
877
- return;
878
- }
879
- if (sourceGroup && targetGroupDraftId === sourceGroup.draftId) {
880
- return;
881
- }
882
- if (!sourceGroup) {
883
- moveRootFieldToGroup(targetGroupDraftId, selected.draftId, Number.MAX_SAFE_INTEGER);
1108
+ const movedField = removeFieldFromOwner(sourceOwner, selected.draftId);
1109
+ if (!movedField) {
884
1110
  return;
885
1111
  }
886
- moveFieldBetweenGroups(sourceGroup.draftId, targetGroupDraftId, selected.draftId, Number.MAX_SAFE_INTEGER);
1112
+ insertFieldIntoOwner(targetOwner, movedField, getOwnerItemCount(targetOwner));
887
1113
  }
888
- function getInsertionGroupDraftId() {
1114
+ function getInsertionOwner() {
1115
+ if (selectedField.value?.field.type === "container") {
1116
+ return {
1117
+ kind: "container",
1118
+ draftId: selectedField.value.draftId
1119
+ };
1120
+ }
889
1121
  if (selectedGroup.value) {
890
- return selectedGroup.value.draftId;
1122
+ return {
1123
+ kind: "group",
1124
+ draftId: selectedGroup.value.draftId
1125
+ };
891
1126
  }
892
- if (selectedFieldGroup.value) {
893
- return selectedFieldGroup.value.draftId;
1127
+ if (selectedFieldOwner.value) {
1128
+ return selectedFieldOwner.value;
894
1129
  }
895
- return void 0;
1130
+ return {
1131
+ kind: "root"
1132
+ };
896
1133
  }
897
1134
  function addGroup() {
898
1135
  const draftGroup = createDraftGroup(createGroup());
@@ -902,36 +1139,10 @@ function addGroup() {
902
1139
  }
903
1140
  function addField(type) {
904
1141
  const draftField = createDraftField(createField(type));
905
- const targetGroupDraftId = getInsertionGroupDraftId();
906
- if (targetGroupDraftId) {
907
- draftGroups.value = draftGroups.value.map((group) => {
908
- if (group.draftId !== targetGroupDraftId) {
909
- return group;
910
- }
911
- if (selectedFieldGroup.value?.draftId === targetGroupDraftId && selectedField.value) {
912
- const currentIndex = group.fields.findIndex((field) => field.draftId === selectedField.value?.draftId);
913
- if (currentIndex >= 0) {
914
- const nextFields = group.fields.slice();
915
- nextFields.splice(currentIndex + 1, 0, draftField);
916
- return {
917
- ...group,
918
- fields: nextFields
919
- };
920
- }
921
- }
922
- return {
923
- ...group,
924
- fields: [...group.fields, draftField]
925
- };
926
- });
927
- } else if (selectedField.value && !selectedFieldGroup.value) {
928
- const currentIndex = draftFields.value.findIndex((field) => field.draftId === selectedField.value?.draftId);
929
- const nextFields = draftFields.value.slice();
930
- nextFields.splice(currentIndex >= 0 ? currentIndex + 1 : nextFields.length, 0, draftField);
931
- draftFields.value = nextFields;
932
- } else {
933
- draftFields.value = [...draftFields.value, draftField];
934
- }
1142
+ const targetOwner = getInsertionOwner();
1143
+ const ownerFields = getOwnerFields(targetOwner);
1144
+ const currentIndex = selectedField.value && selectedFieldOwner.value && getFieldOwnerValue(selectedFieldOwner.value) === getFieldOwnerValue(targetOwner) && selectedField.value.field.type !== "container" ? ownerFields.findIndex((field) => field.draftId === selectedField.value?.draftId) : -1;
1145
+ insertFieldIntoOwner(targetOwner, draftField, currentIndex >= 0 ? currentIndex + 1 : ownerFields.length);
935
1146
  selectedItemId.value = draftField.draftId;
936
1147
  clearFieldErrors(draftField.draftId);
937
1148
  }
@@ -946,7 +1157,7 @@ function deleteGroup(itemId) {
946
1157
  if (deletedGroup) {
947
1158
  clearFieldErrors(deletedGroup.draftId);
948
1159
  for (const field of deletedGroup.fields) {
949
- clearFieldErrors(field.draftId);
1160
+ clearNestedFieldErrors(field);
950
1161
  }
951
1162
  }
952
1163
  if (selectedItemId.value !== itemId) {
@@ -956,30 +1167,33 @@ function deleteGroup(itemId) {
956
1167
  selectedItemId.value = nextGroup?.draftId ?? "general";
957
1168
  }
958
1169
  function deleteField(itemId) {
959
- let nextSelectedItemId;
960
- const rootDeleteIndex = draftFields.value.findIndex((field) => field.draftId === itemId);
961
- if (rootDeleteIndex >= 0) {
962
- const nextFields = draftFields.value.filter((field) => field.draftId !== itemId);
963
- const nextField = nextFields[rootDeleteIndex] ?? nextFields[rootDeleteIndex - 1];
964
- draftFields.value = nextFields;
965
- nextSelectedItemId = nextField?.draftId ?? "general";
966
- }
967
- draftGroups.value = draftGroups.value.map((group) => {
968
- const deleteIndex = group.fields.findIndex((field) => field.draftId === itemId);
969
- if (deleteIndex < 0) {
970
- return group;
971
- }
972
- const nextFields = group.fields.filter((field) => field.draftId !== itemId);
973
- const nextField = nextFields[deleteIndex] ?? nextFields[deleteIndex - 1];
974
- nextSelectedItemId = nextField?.draftId ?? group.draftId;
975
- return {
976
- ...group,
977
- fields: nextFields
978
- };
979
- });
1170
+ const owner = selectedItemId.value === itemId ? selectedFieldOwner.value : findFieldOwnerByDraftId(itemId);
1171
+ if (!owner) {
1172
+ return;
1173
+ }
1174
+ const currentFields = getOwnerFields(owner);
1175
+ const deleteIndex = currentFields.findIndex((field) => field.draftId === itemId);
1176
+ if (deleteIndex < 0) {
1177
+ return;
1178
+ }
1179
+ removeFieldFromOwner(owner, itemId);
1180
+ const nextFields = getOwnerFields(owner);
1181
+ const nextField = nextFields[deleteIndex] ?? nextFields[deleteIndex - 1];
980
1182
  clearFieldErrors(itemId);
981
1183
  if (selectedItemId.value === itemId) {
982
- selectedItemId.value = nextSelectedItemId ?? "general";
1184
+ if (nextField) {
1185
+ selectedItemId.value = nextField.draftId;
1186
+ return;
1187
+ }
1188
+ if (owner.kind === "group") {
1189
+ selectedItemId.value = owner.draftId;
1190
+ return;
1191
+ }
1192
+ if (owner.kind === "container") {
1193
+ selectedItemId.value = owner.draftId;
1194
+ return;
1195
+ }
1196
+ selectedItemId.value = "general";
983
1197
  }
984
1198
  }
985
1199
  function handleAddItem(type) {
@@ -992,6 +1206,9 @@ function handleAddItem(type) {
992
1206
  function isFieldLabelConfigurable(field) {
993
1207
  return hasFieldLabel(field);
994
1208
  }
1209
+ function hasDirectFieldStyle(field) {
1210
+ return field.type !== "container";
1211
+ }
995
1212
  function getFieldHideLabelValue(field) {
996
1213
  if (!isFieldLabelConfigurable(field)) {
997
1214
  return false;
@@ -1079,14 +1296,51 @@ function updateSelectedFieldPath(value) {
1079
1296
  }
1080
1297
  function updateSelectedFieldStyle(value) {
1081
1298
  const selected = selectedField.value;
1082
- if (!selected) {
1299
+ if (!selected || !hasDirectFieldStyle(selected.field)) {
1083
1300
  return;
1084
1301
  }
1085
1302
  clearFieldError(selected.draftId, "style");
1086
- updateDraftField(selected.draftId, (field) => ({
1303
+ updateDraftField(selected.draftId, (field) => {
1304
+ if (!hasDirectFieldStyle(field)) {
1305
+ return field;
1306
+ }
1307
+ return {
1308
+ ...field,
1309
+ style: normalizeOptionalString(String(value))
1310
+ };
1311
+ });
1312
+ }
1313
+ function updateSelectedContainerBodyOrientation(value) {
1314
+ const selected = selectedField.value;
1315
+ if (!selected || selected.field.type !== "container") {
1316
+ return;
1317
+ }
1318
+ const normalizedValue = normalizeOrientation(value);
1319
+ updateDraftField(selected.draftId, (field) => field.type === "container" ? {
1087
1320
  ...field,
1088
- style: normalizeOptionalString(String(value))
1089
- }));
1321
+ bodyOrientation: normalizedValue === "horizontal" ? void 0 : normalizedValue
1322
+ } : field);
1323
+ }
1324
+ function updateSelectedContainerBodyBordered(value) {
1325
+ const selected = selectedField.value;
1326
+ if (!selected || selected.field.type !== "container") {
1327
+ return;
1328
+ }
1329
+ updateDraftField(selected.draftId, (field) => field.type === "container" ? {
1330
+ ...field,
1331
+ bodyBordered: value ? true : void 0
1332
+ } : field);
1333
+ }
1334
+ function updateSelectedContainerBodyStyle(value) {
1335
+ const selected = selectedField.value;
1336
+ if (!selected || selected.field.type !== "container") {
1337
+ return;
1338
+ }
1339
+ clearFieldError(selected.draftId, "bodyStyle");
1340
+ updateDraftField(selected.draftId, (field) => field.type === "container" ? {
1341
+ ...field,
1342
+ bodyStyle: normalizeOptionalString(String(value))
1343
+ } : field);
1090
1344
  }
1091
1345
  function updateSelectedFieldLabelStyle(value) {
1092
1346
  const selected = selectedField.value;
@@ -1145,17 +1399,17 @@ function updateSelectedFieldInitialValue(value) {
1145
1399
  }
1146
1400
  function updateSelectedFieldRequired(value) {
1147
1401
  const selected = selectedField.value;
1148
- if (!selected || !isInteractiveField(selected.field)) {
1402
+ if (!selected || selected.field.type !== "slot" && selected.field.type !== "container" && !isInteractiveField(selected.field)) {
1149
1403
  return;
1150
1404
  }
1151
1405
  updateDraftField(selected.draftId, (field) => {
1152
- if (!isInteractiveField(field)) {
1153
- return field;
1406
+ if (field.type === "slot" || field.type === "container" || isInteractiveField(field)) {
1407
+ return {
1408
+ ...field,
1409
+ required: value ? true : void 0
1410
+ };
1154
1411
  }
1155
- return {
1156
- ...field,
1157
- required: value ? true : void 0
1158
- };
1412
+ return field;
1159
1413
  });
1160
1414
  }
1161
1415
  function updateSelectedStringDiscardEmpty(value) {
@@ -1627,6 +1881,10 @@ function getSchemaIssues(field) {
1627
1881
  const result = SlotFieldC.safeParse(field);
1628
1882
  return result.success ? [] : result.error.issues;
1629
1883
  }
1884
+ case "container": {
1885
+ const result = ContainerFieldC.safeParse(field);
1886
+ return result.success ? [] : result.error.issues;
1887
+ }
1630
1888
  case "empty": {
1631
1889
  const result = EmptyFieldC.safeParse(field);
1632
1890
  return result.success ? [] : result.error.issues;
@@ -1662,9 +1920,32 @@ function normalizeGroup(group) {
1662
1920
  return {
1663
1921
  ...group,
1664
1922
  style: normalizeOptionalString(group.style ?? ""),
1665
- fields: group.fields.map((field) => normalizeField(field))
1923
+ fields: group.fields
1666
1924
  };
1667
1925
  }
1926
+ function normalizeDraftField(item) {
1927
+ const normalizedField = normalizeField(item.field);
1928
+ return {
1929
+ ...item,
1930
+ field: normalizedField,
1931
+ children: item.children.map((child) => normalizeDraftField(child))
1932
+ };
1933
+ }
1934
+ function buildFieldFromDraft(item) {
1935
+ if (item.field.type !== "container") {
1936
+ return item.field;
1937
+ }
1938
+ return {
1939
+ ...item.field,
1940
+ fields: item.children.map((child) => buildFieldFromDraft(child))
1941
+ };
1942
+ }
1943
+ function clearNestedFieldErrors(item) {
1944
+ clearFieldErrors(item.draftId);
1945
+ for (const child of item.children) {
1946
+ clearNestedFieldErrors(child);
1947
+ }
1948
+ }
1668
1949
  function validateDraftFields(fields, errors, idOwners, pathOwners) {
1669
1950
  let firstInvalidItemId;
1670
1951
  for (const item of fields) {
@@ -1702,25 +1983,21 @@ function validateDraftFields(fields, errors, idOwners, pathOwners) {
1702
1983
  setError(errors, issueKey, getSchemaIssueMessage(issue, issuePath));
1703
1984
  firstInvalidItemId = firstInvalidItemId ?? item.draftId;
1704
1985
  }
1986
+ const childInvalidItemId = validateDraftFields(item.children, errors, idOwners, pathOwners);
1987
+ firstInvalidItemId = firstInvalidItemId ?? childInvalidItemId;
1705
1988
  }
1706
1989
  return firstInvalidItemId;
1707
1990
  }
1708
1991
  function validateDraftState() {
1709
1992
  const errors = {};
1710
- const normalizedFields = draftFields.value.map((item) => ({
1711
- draftId: item.draftId,
1712
- field: normalizeField(item.field)
1713
- }));
1993
+ const normalizedFields = draftFields.value.map((item) => normalizeDraftField(item));
1714
1994
  const normalizedGroups = draftGroups.value.map((group) => ({
1715
1995
  draftId: group.draftId,
1716
1996
  group: normalizeGroup({
1717
1997
  ...group.group,
1718
- fields: group.fields.map((item) => item.field)
1998
+ fields: group.fields.map((item) => buildFieldFromDraft(normalizeDraftField(item)))
1719
1999
  }),
1720
- fields: group.fields.map((item) => ({
1721
- draftId: item.draftId,
1722
- field: normalizeField(item.field)
1723
- }))
2000
+ fields: group.fields.map((item) => normalizeDraftField(item))
1724
2001
  }));
1725
2002
  const groupIdOwners = {};
1726
2003
  const idOwners = {};
@@ -1782,10 +2059,10 @@ function buildDraftConfig() {
1782
2059
  return void 0;
1783
2060
  }
1784
2061
  const nextBody = {
1785
- fields: validatedDraftState.normalizedFields.map((item) => item.field),
2062
+ fields: validatedDraftState.normalizedFields.map((item) => buildFieldFromDraft(item)),
1786
2063
  groups: validatedDraftState.normalizedGroups.map((group) => ({
1787
2064
  ...group.group,
1788
- fields: group.fields.map((item) => item.field)
2065
+ fields: group.fields.map((item) => buildFieldFromDraft(item))
1789
2066
  }))
1790
2067
  };
1791
2068
  if (draftOrientation.value !== "horizontal") {
@@ -1870,10 +2147,12 @@ function buildDslGuideMarkdown() {
1870
2147
  "- \u4EE3\u7801\u91CC\u5E94\u5148\u751F\u6210\u5E76\u4F7F\u7528 UUID\uFF0C\u518D\u628A\u540C\u4E00\u4E2A `id` \u7C98\u8D34\u56DE\u5B57\u6BB5\u914D\u7F6E\u3002",
1871
2148
  "- \u6240\u6709\u5B57\u6BB5\u90FD\u5FC5\u987B\u5305\u542B\u552F\u4E00\u4E14\u5408\u6CD5\u7684 UUID `id`\u3002",
1872
2149
  "- \u53EA\u6709\u53EF\u7ED1\u5B9A\u503C\u7684\u5B57\u6BB5\u53EF\u4EE5\u914D\u7F6E `path`\uFF0C\u5E76\u53C2\u4E0E\u8868\u5355\u503C\u8BFB\u5199\u3002",
1873
- "- `slot` \u548C `empty` \u5B57\u6BB5\u90FD\u4E0D\u4F1A\u7ED1\u5B9A\u6A21\u578B\u503C\uFF0C\u53EA\u5141\u8BB8 `id`\u3001`type` \u548C\u53EF\u9009\u7684 `style`\u3002",
2150
+ "- `slot` \u5B57\u6BB5\u4E0D\u4F1A\u7ED1\u5B9A\u6A21\u578B\u503C\uFF1B\u53EF\u9009 `title`\u3001`hideLabel`\u3001`required`\u3001`hidden`\u3001`labelStyle`\u3001`contentStyle` \u4E0E `style`\u3002",
2151
+ "- `container` \u5B57\u6BB5\u4E0D\u4F1A\u7ED1\u5B9A\u6A21\u578B\u503C\uFF1B\u4F7F\u7528 `fields` \u627F\u8F7D\u9012\u5F52\u5D4C\u5957\u5B57\u6BB5\uFF0C\u53EF\u9009 `bodyOrientation`\u3001`bodyBordered` \u4E0E `bodyStyle` \u63A7\u5236\u5185\u90E8\u5E03\u5C40\u3002",
2152
+ "- `empty` \u5B57\u6BB5\u4E0D\u4F1A\u7ED1\u5B9A\u6A21\u578B\u503C\uFF0C\u53EA\u5141\u8BB8 `id`\u3001`type` \u548C\u53EF\u9009\u7684 `style`\u3002",
1874
2153
  "- `markdown` \u5B57\u6BB5\u4F7F\u7528 `title` \u4E0E `locale` \u5C55\u793A\u5E26\u6807\u7B7E\u7684 Markdown \u5185\u5BB9\uFF0C\u4E0D\u7ED1\u5B9A `path`\u3002",
1875
2154
  "- `markdown-body` \u5B57\u6BB5\u4F7F\u7528 `locale` \u5C55\u793A\u65E0\u6807\u7B7E\u7684\u7EAF Markdown \u5185\u5BB9\uFF0C\u53EF\u9009 `inline` \u63A7\u5236\u5185\u8054\u6E32\u67D3\uFF0C\u4E0D\u7ED1\u5B9A `path`\u3002",
1876
- "- \u9664 `slot`\u3001`empty` \u4E0E `markdown-body` \u5916\uFF0C\u5176\u5B83\u5E26\u6807\u7B7E\u5B57\u6BB5\u90FD\u53EF\u4EE5\u8BBE\u7F6E `hideLabel` \u9690\u85CF\u6807\u7B7E\u3002",
2155
+ "- \u9664 `empty` \u4E0E `markdown-body` \u5916\uFF0C\u5176\u5B83\u5E26\u6807\u7B7E\u5B57\u6BB5\u90FD\u53EF\u4EE5\u8BBE\u7F6E `hideLabel` \u9690\u85CF\u6807\u7B7E\u3002",
1877
2156
  "- \u666E\u901A\u5B57\u6BB5\u53EF\u989D\u5916\u4F7F\u7528 `labelStyle` \u4E0E `contentStyle`\uFF0C\u7528\u4E8E contents \u5E03\u5C40\u6A21\u5F0F\u4E0B\u5206\u522B\u63A7\u5236\u6807\u7B7E\u4E0E\u5185\u5BB9\u5355\u5143\u683C\u3002"
1878
2157
  ].join("\n");
1879
2158
  }
@@ -1885,10 +2164,12 @@ function buildMarkdownNotes() {
1885
2164
  "- \u6240\u6709\u5B57\u6BB5\u7EC4 `id` \u90FD\u5FC5\u987B\u552F\u4E00\u4E14\u7B26\u5408 UUID \u683C\u5F0F\u3002",
1886
2165
  "- \u6240\u6709\u5B57\u6BB5 `id` \u90FD\u5FC5\u987B\u552F\u4E00\u4E14\u7B26\u5408 UUID \u683C\u5F0F\u3002",
1887
2166
  "- \u5B57\u6BB5\u7EC4\u6CA1\u6709\u6807\u9898\uFF1B\u5982\u679C\u9700\u8981\u5206\u6BB5\u6807\u9898\u6216\u8BF4\u660E\uFF0C\u4F18\u5148\u63D2\u5165 `markdown-body` \u5B57\u6BB5\uFF1B\u9700\u8981\u6807\u7B7E\u884C\u65F6\u518D\u4F7F\u7528 `markdown` \u5B57\u6BB5\u3002",
1888
- "- `slot` \u4E0E `empty` \u5B57\u6BB5\u53EA\u80FD\u4F7F\u7528 `id`\u3001`type` \u548C\u53EF\u9009\u7684 `style`\u3002",
2167
+ "- `slot` \u5B57\u6BB5\u4E0D\u7ED1\u5B9A `path`\uFF0C\u53EF\u9009 `title`\u3001`hideLabel`\u3001`required`\u3001`hidden`\u3001`labelStyle`\u3001`contentStyle` \u4E0E `style`\u3002",
2168
+ "- `container` \u5B57\u6BB5\u4E0D\u7ED1\u5B9A `path`\uFF0C\u901A\u8FC7 `fields` \u9012\u5F52\u5D4C\u5957\u5B50\u5B57\u6BB5\uFF0C\u4E0D\u652F\u6301\u5D4C\u5957 `groups`\u3002",
2169
+ "- `empty` \u5B57\u6BB5\u53EA\u80FD\u4F7F\u7528 `id`\u3001`type` \u548C\u53EF\u9009\u7684 `style`\u3002",
1889
2170
  "- `markdown` \u5B57\u6BB5\u4E0D\u4F7F\u7528 `path`\uFF0C\u5185\u5BB9\u6765\u81EA `title` \u4E0E `locale` \u672C\u5730\u5316 Markdown\u3002",
1890
2171
  "- `markdown-body` \u5B57\u6BB5\u4E0D\u4F7F\u7528 `path`\uFF0C\u5185\u5BB9\u6765\u81EA `locale` \u672C\u5730\u5316 Markdown\uFF0C\u53EF\u9009 `inline` \u63A7\u5236\u6E32\u67D3\u65B9\u5F0F\u3002",
1891
- "- \u9664 `slot`\u3001`empty` \u4E0E `markdown-body` \u5916\uFF0C\u5176\u5B83\u5E26\u6807\u7B7E\u5B57\u6BB5\u90FD\u53EF\u4EE5\u8BBE\u7F6E `hideLabel`\u3002",
2172
+ "- \u9664 `empty` \u4E0E `markdown-body` \u5916\uFF0C\u5176\u5B83\u5E26\u6807\u7B7E\u5B57\u6BB5\u90FD\u53EF\u4EE5\u8BBE\u7F6E `hideLabel`\u3002",
1892
2173
  "- \u666E\u901A\u5B57\u6BB5\u5728 contents \u5E03\u5C40\u6A21\u5F0F\u4E0B\u53EF\u989D\u5916\u4F7F\u7528 `labelStyle` \u4E0E `contentStyle`\u3002",
1893
2174
  "- \u53EA\u6709\u5E26 `path` \u7684\u5B57\u6BB5\u624D\u8981\u6C42 `path` \u552F\u4E00\u4E14\u4E0D\u80FD\u4E3A\u7A7A\u3002",
1894
2175
  "- \u8868\u8FBE\u5F0F\u5B57\u6BB5\u5FC5\u987B\u4E25\u683C\u9075\u5B88 schema \u7EA6\u675F\uFF1B\u5982\u679C schema \u4E0D\u652F\u6301\uFF0C\u5C31\u76F4\u63A5\u8BF4\u660E\u9650\u5236\u3002"
@@ -2047,10 +2328,10 @@ function confirmChanges() {
2047
2328
  if (!result) {
2048
2329
  return;
2049
2330
  }
2050
- draftFields.value = result.normalizedFields.map((item) => createDraftField(item.field));
2331
+ draftFields.value = result.normalizedFields.map((item) => createDraftField(buildFieldFromDraft(item)));
2051
2332
  draftGroups.value = result.normalizedGroups.map((group) => createDraftGroup({
2052
2333
  ...group.group,
2053
- fields: group.fields.map((item) => item.field)
2334
+ fields: group.fields.map((item) => buildFieldFromDraft(item))
2054
2335
  }));
2055
2336
  emit("confirm", result.config);
2056
2337
  open.value = false;
@@ -2160,6 +2441,7 @@ function confirmChanges() {
2160
2441
  :data-field-id="item.fieldId"
2161
2442
  :data-field-path="item.path"
2162
2443
  :data-selected="selectedItemId === item.itemId ? 'true' : 'false'"
2444
+ :style="{ paddingLeft: `${item.depth * 12 + 4}px` }"
2163
2445
  :class="cn(
2164
2446
  'flex min-w-0 items-center gap-2 rounded-md border p-1 transition-colors',
2165
2447
  selectedItemId === item.itemId ? 'border-(--primary)/25 bg-[color-mix(in_srgb,var(--primary)_10%,white)]' : 'border-transparent hover:border-zinc-200 hover:bg-zinc-50'
@@ -2272,8 +2554,9 @@ function confirmChanges() {
2272
2554
  :data-field-id="item.fieldId"
2273
2555
  :data-field-path="item.path"
2274
2556
  :data-selected="selectedItemId === item.itemId ? 'true' : 'false'"
2557
+ :style="{ paddingLeft: `${item.depth * 12 + 16}px` }"
2275
2558
  :class="cn(
2276
- 'flex min-w-0 items-center gap-2 rounded-md border p-1 pl-4 transition-colors',
2559
+ 'flex min-w-0 items-center gap-2 rounded-md border p-1 transition-colors',
2277
2560
  selectedItemId === item.itemId ? 'border-(--primary)/25 bg-[color-mix(in_srgb,var(--primary)_10%,white)]' : 'border-transparent hover:border-zinc-200 hover:bg-zinc-50'
2278
2561
  )"
2279
2562
  >
@@ -2539,18 +2822,15 @@ function confirmChanges() {
2539
2822
  </span>
2540
2823
  <NativeSelect
2541
2824
  data-slot="fields-configurator-field-group-select"
2542
- :model-value="selectedFieldGroup?.draftId ?? ROOT_FIELD_GROUP_VALUE"
2825
+ :model-value="selectedFieldOwner ? getFieldOwnerValue(selectedFieldOwner) : ROOT_FIELD_GROUP_VALUE"
2543
2826
  @update:model-value="updateSelectedFieldGroup"
2544
2827
  >
2545
- <NativeSelectOption :value="ROOT_FIELD_GROUP_VALUE">
2546
- {{ t("field-group-none") }}
2547
- </NativeSelectOption>
2548
2828
  <NativeSelectOption
2549
- v-for="group in groupItems"
2550
- :key="group.itemId"
2551
- :value="group.itemId"
2829
+ v-for="option in selectedFieldOwnerOptions"
2830
+ :key="option.value"
2831
+ :value="option.value"
2552
2832
  >
2553
- {{ group.label }}
2833
+ {{ option.label }}
2554
2834
  </NativeSelectOption>
2555
2835
  </NativeSelect>
2556
2836
  </label>
@@ -2567,7 +2847,7 @@ function confirmChanges() {
2567
2847
 
2568
2848
  <Locale
2569
2849
  data-slot="fields-configurator-field-title-locale"
2570
- :model-value="selectedField.field.title"
2850
+ :model-value="getFieldTitleValue(selectedField.field)"
2571
2851
  @update:model-value="updateSelectedFieldTitle"
2572
2852
  />
2573
2853
 
@@ -2657,7 +2937,7 @@ function confirmChanges() {
2657
2937
  class="grid gap-4 md:grid-cols-2"
2658
2938
  >
2659
2939
  <label
2660
- v-if="isInteractiveField(selectedField.field)"
2940
+ v-if="isInteractiveField(selectedField.field) || selectedField.field.type === 'slot' || selectedField.field.type === 'container'"
2661
2941
  class="flex items-center justify-between gap-3 rounded-md border border-zinc-200 px-3 py-2 md:col-span-2"
2662
2942
  >
2663
2943
  <div class="flex flex-col gap-1">
@@ -2687,7 +2967,7 @@ function confirmChanges() {
2687
2967
  </label>
2688
2968
 
2689
2969
  <label
2690
- v-if="!usesContentsOrientation || isMarkdownBodyField(selectedField.field)"
2970
+ v-if="hasDirectFieldStyle(selectedField.field) && (!usesContentsOrientation || isMarkdownBodyField(selectedField.field) || selectedField.field.type === 'slot')"
2691
2971
  class="flex flex-col gap-2"
2692
2972
  >
2693
2973
  <span class="text-xs font-medium text-zinc-500">
@@ -2695,7 +2975,7 @@ function confirmChanges() {
2695
2975
  </span>
2696
2976
  <Textarea
2697
2977
  data-slot="fields-configurator-field-style-input"
2698
- :model-value="selectedField.field.style ?? ''"
2978
+ :model-value="hasDirectFieldStyle(selectedField.field) ? selectedField.field.style ?? '' : ''"
2699
2979
  :aria-invalid="validationErrors[getFieldErrorKey(selectedField.draftId, 'style')] ? 'true' : void 0"
2700
2980
  :placeholder="t('field-style-placeholder')"
2701
2981
  class="min-h-20 font-mono text-sm"
@@ -2822,6 +3102,99 @@ function confirmChanges() {
2822
3102
  </label>
2823
3103
  </section>
2824
3104
 
3105
+ <section
3106
+ v-if="selectedField.field.type === 'container'"
3107
+ data-slot="fields-configurator-container-options"
3108
+ class="grid gap-4 md:grid-cols-2"
3109
+ >
3110
+ <label class="flex flex-col gap-2">
3111
+ <span class="text-xs font-medium text-zinc-500">
3112
+ {{ t("container-body-orientation") }}
3113
+ </span>
3114
+ <NativeSelect
3115
+ data-slot="fields-configurator-container-body-orientation-select"
3116
+ :model-value="selectedField.field.bodyOrientation ?? 'horizontal'"
3117
+ @update:model-value="updateSelectedContainerBodyOrientation"
3118
+ >
3119
+ <NativeSelectOption value="horizontal">
3120
+ {{ t("fields-orientation-horizontal") }}
3121
+ </NativeSelectOption>
3122
+ <NativeSelectOption value="vertical">
3123
+ {{ t("fields-orientation-vertical") }}
3124
+ </NativeSelectOption>
3125
+ <NativeSelectOption value="floating">
3126
+ {{ t("fields-orientation-floating") }}
3127
+ </NativeSelectOption>
3128
+ <NativeSelectOption value="contents">
3129
+ {{ t("fields-orientation-contents") }}
3130
+ </NativeSelectOption>
3131
+ </NativeSelect>
3132
+ </label>
3133
+
3134
+ <label class="flex items-center justify-between gap-3 rounded-md border border-zinc-200 px-3 py-2">
3135
+ <div class="flex flex-col gap-1">
3136
+ <span class="text-sm font-medium text-zinc-800">{{ t("container-body-bordered") }}</span>
3137
+ <span class="text-xs text-zinc-500">{{ t("container-body-bordered-description") }}</span>
3138
+ </div>
3139
+ <Switch
3140
+ data-slot="fields-configurator-container-body-bordered-switch"
3141
+ :model-value="selectedField.field.bodyBordered ?? false"
3142
+ @update:model-value="updateSelectedContainerBodyBordered"
3143
+ />
3144
+ </label>
3145
+
3146
+ <label class="flex flex-col gap-2 md:col-span-2">
3147
+ <span class="text-xs font-medium text-zinc-500">
3148
+ {{ t("container-body-style") }}
3149
+ </span>
3150
+ <Textarea
3151
+ data-slot="fields-configurator-container-body-style-input"
3152
+ :model-value="selectedField.field.bodyStyle ?? ''"
3153
+ :aria-invalid="validationErrors[getFieldErrorKey(selectedField.draftId, 'bodyStyle')] ? 'true' : void 0"
3154
+ :placeholder="t('group-style-placeholder')"
3155
+ class="min-h-20 font-mono text-sm"
3156
+ @update:model-value="updateSelectedContainerBodyStyle"
3157
+ />
3158
+ <p
3159
+ v-if="validationErrors[getFieldErrorKey(selectedField.draftId, 'bodyStyle')]"
3160
+ class="text-xs text-red-500"
3161
+ >
3162
+ {{ validationErrors[getFieldErrorKey(selectedField.draftId, "bodyStyle")] }}
3163
+ </p>
3164
+ </label>
3165
+
3166
+ <div
3167
+ data-slot="fields-configurator-container-children"
3168
+ class="flex flex-col gap-2 md:col-span-2"
3169
+ >
3170
+ <p class="text-xs font-medium text-zinc-500">
3171
+ {{ t("field-children") }}
3172
+ </p>
3173
+ <div
3174
+ v-if="selectedContainerChildItems.length > 0"
3175
+ class="flex flex-col gap-1"
3176
+ >
3177
+ <button
3178
+ v-for="item in selectedContainerChildItems"
3179
+ :key="item.itemId"
3180
+ type="button"
3181
+ data-slot="fields-configurator-container-child"
3182
+ class="rounded-md border border-zinc-200 px-3 py-2 text-left text-sm text-zinc-700 transition-colors hover:border-zinc-300 hover:bg-zinc-50"
3183
+ :style="{ paddingLeft: `${item.depth * 12 + 12}px` }"
3184
+ @click="selectItem(item.itemId)"
3185
+ >
3186
+ {{ item.label }}
3187
+ </button>
3188
+ </div>
3189
+ <p
3190
+ v-else
3191
+ class="text-xs text-zinc-500"
3192
+ >
3193
+ {{ t("field-children-empty") }}
3194
+ </p>
3195
+ </div>
3196
+ </section>
3197
+
2825
3198
  <section
2826
3199
  v-if="selectedField.field.type === 'string' || selectedField.field.type === 'textarea'"
2827
3200
  data-slot="fields-configurator-string-options"
@@ -3421,6 +3794,7 @@ function confirmChanges() {
3421
3794
  "field-type-radio": "单选按钮组",
3422
3795
  "field-type-calendar": "日期",
3423
3796
  "field-type-upload": "上传",
3797
+ "field-type-container": "容器",
3424
3798
  "field-type-empty": "空白",
3425
3799
  "field-type-slot": "插槽",
3426
3800
  "field-id": "字段 ID",
@@ -3438,6 +3812,8 @@ function confirmChanges() {
3438
3812
  "copy-field-id-failed": "复制字段 ID 失败,请检查剪贴板权限。",
3439
3813
  "field-group": "所属分组",
3440
3814
  "field-group-none": "不分组",
3815
+ "field-children": "子字段",
3816
+ "field-children-empty": "这个容器还没有子字段。",
3441
3817
  "field-label": "字段标题",
3442
3818
  "group-style": "分组样式表达式",
3443
3819
  "group-style-placeholder": "例如返回一个 style map,例如 display: grid",
@@ -3455,6 +3831,10 @@ function confirmChanges() {
3455
3831
  "field-style": "样式表达式",
3456
3832
  "field-label-style": "标签样式表达式",
3457
3833
  "field-content-style": "内容样式表达式",
3834
+ "container-body-orientation": "容器内部布局",
3835
+ "container-body-bordered": "容器内部表格式边框",
3836
+ "container-body-bordered-description": "仅在 contents 布局下生效,用于容器内部字段网格。",
3837
+ "container-body-style": "容器内部样式表达式",
3458
3838
  "field-style-placeholder": "例如 width: 100%",
3459
3839
  "field-hidden": "隐藏条件",
3460
3840
  "field-hidden-placeholder": "返回 true 时隐藏字段",
@@ -3559,6 +3939,7 @@ function confirmChanges() {
3559
3939
  "field-type-radio": "ラジオグループ",
3560
3940
  "field-type-calendar": "日付",
3561
3941
  "field-type-upload": "アップロード",
3942
+ "field-type-container": "コンテナ",
3562
3943
  "field-type-empty": "空白",
3563
3944
  "field-type-slot": "スロット",
3564
3945
  "field-id": "フィールド ID",
@@ -3576,6 +3957,8 @@ function confirmChanges() {
3576
3957
  "copy-field-id-failed": "フィールド ID のコピーに失敗しました。クリップボード権限を確認してください。",
3577
3958
  "field-group": "所属グループ",
3578
3959
  "field-group-none": "グループなし",
3960
+ "field-children": "子フィールド",
3961
+ "field-children-empty": "このコンテナにはまだ子フィールドがありません。",
3579
3962
  "field-label": "フィールドラベル",
3580
3963
  "group-style": "グループスタイル式",
3581
3964
  "group-style-placeholder": "例: style map を返す式。例: display: grid",
@@ -3593,6 +3976,10 @@ function confirmChanges() {
3593
3976
  "field-style": "スタイル式",
3594
3977
  "field-label-style": "ラベルスタイル式",
3595
3978
  "field-content-style": "コンテンツスタイル式",
3979
+ "container-body-orientation": "内部レイアウト",
3980
+ "container-body-bordered": "内部テーブル枠線",
3981
+ "container-body-bordered-description": "contents レイアウト時のみ有効で、内部フィールドグリッドに適用します。",
3982
+ "container-body-style": "内部スタイル式",
3596
3983
  "field-style-placeholder": "例: width: 100%",
3597
3984
  "field-hidden": "非表示条件",
3598
3985
  "field-hidden-placeholder": "true を返すと非表示になります",
@@ -3697,6 +4084,7 @@ function confirmChanges() {
3697
4084
  "field-type-radio": "Radio Group",
3698
4085
  "field-type-calendar": "Date",
3699
4086
  "field-type-upload": "Upload",
4087
+ "field-type-container": "Container",
3700
4088
  "field-type-empty": "Empty",
3701
4089
  "field-type-slot": "Slot",
3702
4090
  "field-id": "Field ID",
@@ -3714,6 +4102,8 @@ function confirmChanges() {
3714
4102
  "copy-field-id-failed": "Failed to copy the field ID. Check clipboard permissions.",
3715
4103
  "field-group": "Belong group",
3716
4104
  "field-group-none": "No group",
4105
+ "field-children": "Child fields",
4106
+ "field-children-empty": "This container does not have child fields yet.",
3717
4107
  "field-label": "Field label",
3718
4108
  "group-style": "Group style expression",
3719
4109
  "group-style-placeholder": "Return a style map, for example display: grid",
@@ -3731,6 +4121,10 @@ function confirmChanges() {
3731
4121
  "field-style": "Style expression",
3732
4122
  "field-label-style": "Label style expression",
3733
4123
  "field-content-style": "Content style expression",
4124
+ "container-body-orientation": "Nested layout",
4125
+ "container-body-bordered": "Nested table borders",
4126
+ "container-body-bordered-description": "Only applies in contents layout for the nested field grid.",
4127
+ "container-body-style": "Nested style expression",
3734
4128
  "field-style-placeholder": "For example width: 100%",
3735
4129
  "field-hidden": "Hidden expression",
3736
4130
  "field-hidden-placeholder": "Return true to hide the field",