diff --git a/frontend/src/components/AdminConfigValueEditor.vue b/frontend/src/components/AdminConfigValueEditor.vue
index 9184296..2062cbf 100644
--- a/frontend/src/components/AdminConfigValueEditor.vue
+++ b/frontend/src/components/AdminConfigValueEditor.vue
@@ -173,6 +173,20 @@ function blankCollectionValue(kind: ValueKind, siblings: unknown[]): unknown {
return template === undefined ? blankValue(kind) : blankLike(template)
}
+function isStructuredValue(
+ value: unknown,
+ structure?: AdminConfiguratorStructure,
+): boolean {
+ if (
+ structure?.kind === 'list' ||
+ structure?.kind === 'map' ||
+ structure?.kind === 'table' ||
+ structure?.kind === 'vector'
+ )
+ return true
+ return value !== null && typeof value === 'object'
+}
+
function blankFromStructure(structure: AdminConfiguratorStructure): unknown {
if (structure.kind === 'optionalString') return ''
if (structure.kind === 'value') return blankValue(structure.valueType)
@@ -431,6 +445,12 @@ function mapEntryStructure(
v-for="(row, index) in listValue"
:key="index"
class="config-structured-editor__row"
+ :class="{
+ 'has-structured-value': isStructuredValue(
+ row,
+ listStructure?.items[index],
+ ),
+ }"
>
{{ index + 1 }}
{{ index + 1 }}
@@ -541,6 +567,12 @@ function mapEntryStructure(
v-for="([key, value], index) in tableEntries"
:key="key"
class="config-structured-editor__property"
+ :class="{
+ 'has-structured-value': isStructuredValue(
+ value,
+ tableFieldStructure(key),
+ ),
+ }"
>
{{ index + 1 }}
{{ key }}
@@ -789,6 +821,49 @@ function mapEntryStructure(
grid-template-columns: 24px minmax(105px, 0.42fr) minmax(150px, 1fr) 27px;
}
+.config-structured-editor__row.has-structured-value,
+.config-structured-editor__property.has-structured-value {
+ align-items: start;
+}
+
+.config-structured-editor__row.has-structured-value > .config-structured-editor,
+.config-structured-editor__property.has-structured-value
+ > .config-structured-editor {
+ grid-column: 1 / -1;
+ grid-row: 2;
+ width: calc(100% + 12px);
+ margin-inline: -6px;
+}
+
+.config-structured-editor__row.has-structured-value
+ > .config-structured-editor__index,
+.config-structured-editor__property.has-structured-value
+ > .config-structured-editor__index {
+ grid-column: 1;
+ grid-row: 1;
+ align-self: center;
+}
+
+.config-structured-editor__property.has-structured-value > strong,
+.config-structured-editor__property.is-map.has-structured-value
+ > .config-structured-editor__map-key {
+ grid-column: 2 / 4;
+ grid-row: 1;
+ align-self: center;
+}
+
+.config-structured-editor__row.has-structured-value
+ > .config-structured-editor__remove {
+ grid-column: 3;
+ grid-row: 1;
+}
+
+.config-structured-editor__property.has-structured-value
+ > .config-structured-editor__remove {
+ grid-column: 4;
+ grid-row: 1;
+}
+
.config-structured-editor__map-key {
min-width: 0;
display: grid;
diff --git a/frontend/src/components/AdminPanel.contract.test.ts b/frontend/src/components/AdminPanel.contract.test.ts
index 892747a..e5ca958 100644
--- a/frontend/src/components/AdminPanel.contract.test.ts
+++ b/frontend/src/components/AdminPanel.contract.test.ts
@@ -287,6 +287,11 @@ describe('standalone admin panel contracts', () => {
expect(configuratorValueEditor).toContain('function isFixedTableField(')
expect(configuratorValueEditor).toContain('function blankCollectionValue(')
expect(configuratorValueEditor).toContain('function blankFromStructure(')
+ expect(source).toContain("'is-structured': field.type === 'json'")
+ expect(configuratorValueEditor).toContain('function isStructuredValue(')
+ expect(configuratorValueEditor).toContain(
+ '.config-structured-editor__property.has-structured-value',
+ )
expect(configuratorValueEditor).not.toContain('