#3516754 Require `minItems: 1` for required `type: array` props
Problem Summary
This issue addresses a semantic mismatch in how Canvas treats "required" for type: array (multi-cardinality) props. Canvas was incorrectly treating required: [prop] as equivalent to minItems: 1, which is wrong per JSON Schema. This caused three separate problems:
- UI over-enforced: The "cannot remove last item" behavior applied to ALL required array props, even those where
value: []is perfectly schema-valid. - API under-enforced: Programmatic saving accepted
value: []for required array props (correct per spec, but inconsistent with UI behavior). See MR !923 note_749762. minItems: 1was blocked entirely: Canvas rejected all SDC and code component props that includedminItems, making it impossible to explicitly opt into ≥1 enforcement.
Overview
Related: [#3529788] and [#3537154]
[#3529788] introduced graceful degradation for required string props (retaining empty string during editing) with a @todo Expand to support multiple-cardinality. [#3537154] fixed a regression and added a second such @todo. Both of those @todo items have since been addressed and removed from the codebase, making this issue the remaining piece of the multi-cardinality required-prop story.
Root Cause
The false assumption was encoded directly in a code comment in JsonSchemaType.php:
// marking an SDC prop as required has the same effect as `minItems: 1`
This incorrect belief led Canvas to:
- Block all
minItemsvalues in bothJsonSchemaType.phpandEntityFieldPropSourceMatcher.php(treating it as redundant withrequired). - Call
setRequired(TRUE)on the Drupal field for ALL required array props — causing the form to show the required asterisk and prevent removing the last item, even when the JSON Schema did not require it.
This assumption is false. Proven by tests in MR !923 note_749761: with required: ['data'] but no minItems, storing value: [] produces zero violations from validateComponentInput().
Key Technical Findings
required: [prop]in JSON Schema means "the key must be present in the object" — it does NOT mean the array must be non-empty.value: []is perfectly schema-valid.minItems: 1is the correct JSON Schema mechanism to require ≥1 array item.- Drupal Field API's
setRequired(TRUE)means "≥1 value" — which maps tominItems: 1semantics, not barerequired. - The UI enforcement chain: PHP
setRequired(TRUE)→ Drupal renders.form-requiredCSS class → JSisRemoveButtonEnabled()checks.form-required+rowCount === 1→ remove button disabled.
Behavioral Changes
Scenario
Before fix
After fix
SDC with required: [prop], no minItems, value: []
UI blocks removing last item (incorrect)
UI allows removing all items (correct per JSON Schema)
SDC with required: [prop] + minItems: 1, value: []
Canvas-ineligible (all minItems blocked)
Canvas-eligible; [] correctly fails validation
SDC with minItems: 2
Canvas-ineligible
Still ineligible (Drupal Field API cannot enforce minimum cardinality > 1)
Code component editor: toggle Required ON for array prop
No minItems serialized
minItems: 1 auto-serialized
Proposed resolution
1 — Allow minItems: 1 in prop definitions (PHP)
Two files previously blocked ALL minItems values:
src/JsonSchemaInterpreter/JsonSchemaType.php— now allowsminItemswhen value is exactly1; rejects all other values. Drupal Field API has no concept of minimum cardinality > 1.src/ShapeMatcher/EntityFieldPropSourceMatcher.php— same change.
Also removed the false code comment claiming required and minItems: 1 are equivalent.
2 — Code component editor auto-sets minItems: 1 (TypeScript)
Component authors using the Canvas code editor should not need to know about minItems: 1 — toggling "Required" on an array prop is enough.
ui/src/types/CodeComponent.ts— addedminItems?: numberto bothCodeComponentPropandCodeComponentPropSerialized.ui/src/features/code-editor/codeEditorSlice.ts—toggleRequirednow auto-setsminItems: 1when enabling required on an array prop, and clears it when disabling.ui/src/features/code-editor/utils/utils.ts—serializePropsoutputsminItemsanddeserializePropsreads it back.
3 — Fix UI enforcement (PHP)
src/Plugin/Canvas/ComponentSource/GeneratedFieldExplicitInputUxComponentSourceBase.php: setRequired(TRUE) is now only called for array props when BOTH required AND minItems: 1 are present. For array props with required but no minItems: 1, setRequired(FALSE) is used — no required asterisk, user CAN remove all items.
No JavaScript changes were needed. isRemoveButtonEnabled() already correctly read the .form-required CSS class; fixing the PHP source of that class was sufficient.
New test SDC: sparkline_min_1
tests/modules/canvas_test_sdc/components/sparkline_min_1/ — a Canvas-eligible sparkline with explicit minItems: 1. This forms a three-component test set:
sparkline—required: [data], nominItems;[]is schema-valid (Canvas-eligible)sparkline_min_1—required: [data]+minItems: 1;[]fails validation (Canvas-eligible)sparkline_min_2—minItems: 2; Canvas-ineligible
New/updated tests in SingleDirectoryComponentTest:
testValidateComponentInputRejectsEmptyRequiredMultiCardinalityPropWithMinItems1: verifiesvalue: []produces ≥1 violations, andvalue: [42]produces 0.testValidateComponentInputAllowsEmptyRequiredMultiCardinalityPropWithNoMinItems: clarified that 0 violations for[]on a bare-required prop IS correct post-fix behavior, not a bug.
Developer experience note
For SDC authors: required: [prop] alone intentionally does NOT enforce ≥1 items in Canvas. This respects JSON Schema semantics and keeps the SDC valid across non-Canvas contexts. To enforce ≥1 value, add minItems: 1 explicitly. minItems > 1 remains unsupported (Drupal Field API cannot enforce it).
For code component authors: Toggling "Required" in the Canvas editor for an array prop automatically handles minItems: 1 — no manual YAML editing needed.
User interface changes
No new UI visible to content authors. The behavior change — required array props without minItems: 1 no longer show the required asterisk or block removing the last item — is a bug fix aligning the UI with JSON Schema semantics, not a new UI feature.