Skip to content

feat(vue-vuetify): improve mixed and additional property editors - #2610

Open
kchobantonov wants to merge 17 commits into
eclipsesource:masterfrom
kchobantonov:feat/vue-vuetify-mixed-additional-properties
Open

kchobantonov wants to merge 17 commits into
eclipsesource:masterfrom
kchobantonov:feat/vue-vuetify-mixed-additional-properties

Conversation

@kchobantonov

Copy link
Copy Markdown
Contributor

Summary

This PR improves the Vue Vuetify handling of mixed-type and dynamic additional properties.

It adds a tree-based editor for object and array values while keeping primitive values editable through the existing mixed renderer. The implementation is scoped to the Vue Vuetify package.

Changes

  • Add a split-pane tree/detail view for mixed object and array values.
  • Support navigating, renaming, and deleting dynamic additional properties from the tree.
  • Use the same property-name validation for renaming through:
    • the Additional Properties component
    • the mixed-renderer tree
  • Validate renamed properties against:
    • existing property names
    • propertyNames
    • applicable patternProperties
  • Preserve the property value and its type when renaming.
  • Correctly update mixed renderers when an additional property’s value type changes.
  • Avoid mutating the provided JSON Schema while preparing renderer schemas.
  • Support property names containing brackets while rejecting dots, which are JSON Forms path separators.
  • Add translated labels, tooltips, accessibility labels, generated array-item labels, and validation errors.
  • Add splitpanes to the Vue Vuetify peer and development dependencies.

Validation

  • Added tests for dynamic property-name validation and translation handling.
  • Added coverage for additional-property value type changes.

@netlify

netlify Bot commented Aug 2, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for jsonforms-examples ready!

Name Link
🔨 Latest commit 358f371
🔍 Latest deploy log https://app.netlify.com/projects/jsonforms-examples/deploys/6ab2beafcdb7880008d5e7d3
😎 Deploy Preview https://deploy-preview-2610--jsonforms-examples.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@kchobantonov

Copy link
Copy Markdown
Contributor Author

@sdirix please review

@coveralls

coveralls commented Aug 3, 2026 •

Copy link
Copy Markdown

Coverage Status

coverage: 84.521%. remained the same — kchobantonov:feat/vue-vuetify-mixed-additional-properties into eclipsesource:master

@EclipseSourceAI EclipseSourceAI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note

Autonomous AI review.

This review was done by an AI agent and therefore may contain mistakes. Feel free to ignore any comment you disagree with. Noting why helps, since replies are read and taken into account in follow-up reviews.

Resolving all AI comments does not lead to an automatic approval. A maintainer still needs to review and sign off on the overall architecture and design.

To get an updated review after pushing changes, a maintainer may re-request a review from this account.

Running in Eclipse Enclave, submitted via review-guard-mcp

Reworks mixed-type and dynamic additionalProperties editing in @jsonforms/vue-vuetify: object/array mixed values now get a splitpanes tree + detail editor, nested mixed controls navigate into that tree instead of nesting renderers, and property renaming is added both in the tree and in the Additional Properties editor.

The extraction of src/util/dynamicProperties.ts is the strongest part: findPropertySchema and getPropertyNameSchema come straight out of AdditionalProperties.vue and are now shared by both rename paths, with unit tests. The non-mutating schema preparation in createMixedRenderInfos and replacing the JSON.stringify descriptor-rebuild check with haveAdditionalPropertyNamesChanged are both real fixes.

Where to focus:

  • getPathAncestorNodeIds in MixedRenderer.vue mixes up absolute and relative paths, so "reveal in tree" never expands ancestors when the mixed control's own path is non-empty (the normal additionalProperties case). Reproduced in the running example app on both the Additional Properties and Mixed Object examples.
  • Making splitpanes a required peer dependency plus re-implementing its base CSS in VSplitpanes.sass is a packaging decision that deserves an explicit call.
  • MixedRenderer.vue is now ~1600 lines; the pure tree-building layer would be better off in src/util where it can be tested.

pnpm test, pnpm lint, pnpm type-check and the full pnpm build all pass on the merged branch, and the CSS output includes the splitpanes rules.

Comment thread packages/vue-vuetify/src/complex/MixedRenderer.vue
Comment thread packages/vue-vuetify/src/complex/components/AdditionalProperties.vue Outdated
Comment thread packages/vue-vuetify/src/complex/MixedRenderer.vue
Comment thread packages/vue-vuetify/package.json
Comment on lines +19 to +22
parentPath: string,
propertyName: string,
): string => (parentPath ? `${parentPath}.${propertyName}` : propertyName);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Always dot-joining is right for core's current path handling, since resolveData and setDataAt split purely on . (setData.ts#L31), whereas core's compose drops the separator when the segment starts with [ (path.ts#L29). The downside is that a [foo] property created here won't resolve in material/vanilla/angular, which all still use composePaths. Fixing compose in core would make bracket support work everywhere instead of just this renderer set.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs separate PR fix since that could impact other code

Comment thread packages/vue-vuetify/src/complex/MixedRenderer.vue Outdated
Comment thread packages/vue-vuetify/src/complex/MixedRenderer.vue Outdated
Comment thread packages/vue-vuetify/src/complex/MixedRenderer.vue Outdated
Comment on lines +1129 to +1143
const treeNodes = computed(() =>
showTreeView.value
? buildTreeFromData(
input.control.value.data,
resolvedSchema.value ?? input.control.value.schema,
input.control.value.rootSchema,
input.control.value.path,
vuetifyControl.computedLabel.value,
input.control.value.enabled,
input.control.value.readonly,
showPrimitivesInTree.value,
mixedTranslations.itemLabel,
)
: [],
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

treeNodes depends on control.data, so every keystroke in the detail pane rebuilds the whole tree and each node runs cloneDeep + cleanSchema (twice for arrays, via prepareArraySchema). Fine for the example data, but it could get noticeable on larger objects; caching per node path or keying the rebuild on the data shape rather than the value would help.

kchobantonov and others added 14 commits August 16, 2026 16:57
- Enforce mutation permissions and protect required/static keys
- Preserve selected editors when tree rows are filtered out
- Retain allowed types, detail UI, and matching parent constraints
- Cancel stale renames and refresh dynamic property schemas
- Add regression tests and include tests in type checking
- Use draft-07 definitions in schema fixtures
Adjust selected paths when an earlier array item is deleted, preserving
the selected item and any nested field. Add regression tests for index
shifts, sibling boundaries, and selected-node or ancestor deletion.
Allow adding, renaming, and editing dotted keys in AdditionalProperties
through isolated forms. Preserve property schemas, local references,
validation, and read-only guards.

Add regression tests for JSON value types, literal-key updates, and
recursive schema references.
Keep empty Add inputs free of inline errors while preserving validation
and disabled-button guards. Align Svelte renderers with Vuetify, document
the behavior, and add regression tests.
Merge compatible scalar constraints from matching patternProperties and
preserve complex or conflicting schemas with allOf. Keep combinator
renderer selection intact and cache compiled patterns.

Add utility and component regression tests for overlapping patterns.
@kchobantonov

Copy link
Copy Markdown
Contributor Author

@sdirix @lucas-koehler ready for review - also side question there is vuetify4 already - are we planning to upgrade ?

@sdirix

sdirix commented Sep 25, 2026

Copy link
Copy Markdown
Member

We track it here now: #2629

At the moment we don't plan do this ourselves but we would accept a community contribution

export const mixedRendererDefaultTranslations: MixedRendererDefaultTranslation[] =
[
{
key: MixedRendererTranslationEnum.unsupportedPropertyName,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unsupportedPropertyName is not used anywhere, so it can be removed here and from MixedRendererTranslations.

Comment on lines +69 to +71
console.info(
`Unrelated edits: 10; tree nodes: 401; full tree traversals: ${traversals}`,
);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This triggers the no-console lint warning. The assertion below already covers it, so the log can go.

Comment on lines +366 to 372
const additionalPropertyItems = computed<AdditionalPropertyType[]>(() =>
additionalKeys.value.map((propName) =>
toAdditionalPropertyType(
propName,
control.value.data[propName],
control.value.schema,
control.value.rootSchema,
),

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This recomputes every row's schema and UI schema on any form change, as additionalKeys returns a new array whenever control changes. With two mixed rows under additionalProperties, 10 edits to an unrelated sibling field runs buildTreeFromData 20 times, so the new tree caching in MixedRenderer does not help here. We should only rebuild when the names or the schema change (haveAdditionalPropertyNamesChanged is now only used by its own tests).

element.propertyName.includes('.') || element.propertyName === ''
"
:data="control.data[element.propertyName]"
:schema="literalValueSchema(element.schema)"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

literalValueSchema() returns a new object on every render, so the nested form runs updateCore and core compiles the schema again with the shared ajv (see here). As Ajv caches by object reference, the cache grows by 20 entries over 10 unrelated edits with one dotted key, each embedding the whole root schema. We should memoize the literal schema per property schema, here and in MixedLiteralDetail, which recompiles on every tree rebuild.

Comment on lines +152 to +168
const findTreePropertySchema = (
parent: JsonSchema,
key: string,
root: JsonSchema,
): JsonSchema | undefined => {
const schemas: JsonSchema[] = [];
const declared = parent.properties?.[key];
if (declared) schemas.push(declared);
for (const [pattern, schema] of Object.entries(
parent.patternProperties ?? {},
)) {
if (new RegExp(pattern).test(key)) schemas.push(schema);
}
return schemas.length
? combineTreeSchemas(schemas, root)
: findPropertySchema(parent, key, root);
};

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This re-implements the pattern lookup from additionalPropertySchema with different merge rules, so the tree and the Additional Properties row can end up with different editors for the same key. We should have one lookup shared by both.

Comment on lines +179 to +183
// JSON Forms data paths use dots as segment separators. Brackets are
// treated literally by core and therefore are valid in property names.
if (!allowDots && propertyName.includes('.')) {
return { reason: 'invalid' };
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both callers pass allowDots: true now, so this check only runs in tests and the PR description ("rejecting dots") is outdated.

The dotted-key editing (isolated nested forms, literalPropertySchema, \0 segment encoding), allowEmptyPropertyNames and the pattern schema merging were all added on top and more than doubled the PR. I would move them into separate PRs so the tree editor itself can be reviewed and merged first.

Comment on lines +982 to +990
const parentPath = getParentPath(node.control.path);
const parentRelativePath = getRelativePath(parentPath);
const parentData =
parentRelativePath === null
? input.control.value.data
: mixedValueAt(
input.control.value.data,
parentRelativePath.split('.').map(decodeMixedSegment),
);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: readNodeValue(parentPath) already does this lookup, and the same block is repeated in validateRename, commitDelete and deleteNode. Same for decodeMixedSegment(node.control.path.split('.').slice(-1)[0] ?? ''), which appears four times. Also trimmed is no longer trimmed.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants