Problem/Motivation

A field group can render as a component, and so can a whole entity, but one cannot go inside the other. When the entity renders as a "Card" component, a group rendered as a "Card header" component is not offered as a mapping source. A group nested inside another group renders nothing. Every level of nesting has to be flattened into one template per view mode.

Field Group gives every formatter ID, Extra CSS classes and Allow HTML in label settings. The component formatter has no wrapper element and never prints the group label, so whatever the site builder enters there has no effect.

Field Group accepts cyclic group configurations. Once groups can feed each other, a cycle must not hang the request.

Proposed resolution

Composition

  • Offer top-level field groups as slot sources at the view-mode level, under the group's label and ordered by display weight. Do not offer the fields inside a group, or nested groups, which belong to their parent group's form.
  • Offer groups to slots only, never to props. A group's value is rendered markup.
  • Build field groups children first, so a group mapped into another component is complete when that component renders.
  • Declare that this module's hook_entity_view_alter() runs after Field Group's. Use OrderAfter on core versions that support hook ordering, and hook_module_implements_alter() marked #[LegacyModuleImplementsAlter] on earlier ones.

Cycles

  • Carry the chain of visited groups through every walk over the group graph, and refuse to enter a group already in it. This covers group children, mappings between groups, and the collection of unrendered children's cache metadata.
  • Refuse a mapping whose source is a group in a cycle before core's Element::isRenderArray() recurses into it. Log the cycle and render the rest of the page.
  • Read malformed mappings values defensively when looking for cyclic sources.

Forms

  • When a mapped field has moved into a field group, name the group in the notice and offer to map the group instead. Applying that repair takes precedence over removing the mapping.
  • Make the offer, and the sentence that explains it, only when a slot actually offers that group: a top-level group that is not hidden.
  • Remove the ID, Extra CSS classes and Allow HTML in label settings from the component formatter. Explain on the form that a component has no wrapper element, without storing the explanation in config.

Documentation

Add sections to docs/architecture.md on:

  • composing field groups;
  • why a group is a slot source and never a prop source;
  • the two ordering problems and their fixes;
  • how cycles are refused;
  • why a group has no wrapper element;
  • a recipe for a Card built from a Card header.

Update README.md to match.

Tests

  • Kernel coverage of group rendering, groups feeding props and slots, nesting, cycles and hook order.
  • Unit tests for the group build order and the cyclic source filter.
  • A Functional test rendering one page whose components come from a view mode, a field group, a field formatter and a referenced entity, checking that every component's library reaches the response.
  • Playwright specs for the group repair offer and the group formatter form.
  • A Playwright demo recording under tests/e2e/demo/ that walks through the module's main features, with presentable fixture components. It stays outside the test suite.

User interface changes

  • Top-level field groups appear as slot mapping options on the view-mode form.
  • A notice offers to map a group in place of a field that moved into it.
  • The component field group formatter no longer offers ID, Extra CSS classes or Allow HTML in label, and explains why.

API changes

None outside Util and the form classes.

Data model changes

A field group's name can appear as a source in mappings.dynamic.slots. The component formatter stops offering id, classes and label_as_html, and adds no key for the notice that replaces them.

Release notes snippet

Field groups rendered as components can now be placed in the slots of other components, including the view-mode component. Cyclic group configurations are refused and logged instead of hanging the request.

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

e0ipso created an issue. See original summary.

  • e0ipso committed 3679709f on 3628550-let-field-groups
    Issue #3628550: Let field groups be components inside other components...

  • e0ipso committed c35e0528 on 3628550-let-field-groups
    Issue #3628550: Let field groups be components inside other components...
pcambra’s picture

Status: Active » Needs review

  • e0ipso committed 4c003547 on 3628550-let-field-groups
    Issue #3628550: Let field groups be components inside other components...

  • e0ipso committed fd602019 on 3628550-let-field-groups
    Issue #3628550: Let field groups be components inside other components...

  • e0ipso committed 09bea0ca on 3628550-let-field-groups
    Issue #3628550: Let field groups be components inside other components...

  • e0ipso committed f2be29b8 on 3628550-let-field-groups
    Issue #3628550: Let field groups be components inside other components...

  • e0ipso committed 572db72a on 2.x
    Resolve #3628550 "Let field groups be components inside other components...