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. UseOrderAfteron core versions that support hook ordering, andhook_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
mappingsvalues 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.
Issue fork sdc_display-3628550
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
Comment #4
pcambraComment #5
pcambra