Skip to content

forms(qml): chrome slots -- a host replaces DynamicForm's labels, containers and buttons, not only its controls - #810

Merged
Yaraslaut merged 2 commits into
masterfrom
feature/forms-chrome-slots
Sep 25, 2026
Merged

Yaraslaut merged 2 commits into
masterfrom
feature/forms-chrome-slots

Conversation

@Yaraslaut

@Yaraslaut Yaraslaut commented Sep 24, 2026 •

Copy link
Copy Markdown
Member

Field slots replace a control, but the caption + *, help line, section heading / accordion toggle, TabBar, action-type heading, status line, Submit button, JSON preview and reply line were all hard-wired built-ins, so a host drawing from its own UI kit could not fully restyle a form.

SlotRegistry.byChrome(role, component) / resolveChrome(role); DynamicForm loads the chrome in place of the built-in (hidden, not drawn beside it). Members are assigned only if the chrome declares them; changing values are bindings; every chrome also gets form.

Role Replaces Members
fieldLabel caption + * field, text, required (live, incl. requiredWhen), invalid (live)
fieldHelp help line field, text
section / accordion titled group (accordion falls back to section) title, kind, section; contentItem required
tabset consecutive tab groups tabs, chrome-owned currentIndex; contentItem required
header / status / preview / result heading / status / JSON preview / reply text (+ ready, reason, explicitSubmit / ok)
submitButton explicit-mode Submit ready, submit() (form's ready guard still applies)
  • Container chrome: the form creates its field grid inside contentItem; the built-in Repeater is emptied, so each field exists once and field_<name> stays unique (prefill / resetFields() / lookups unaffected).
  • New public helpers: DynamicForm.statusText, fieldInvalid(name), chrome(role).
  • To remove a piece of chrome, register an empty Item. The Frame border/padding are set on the instance (background: null) — documented, no slot needed.
  • Nothing registered → identical rendering (317 existing QML cases untouched).

Spec: docs/spec/forms/forms.md — new "Chrome slots" subsection.

Tests (MSVC 14.51, Qt 6.11.1): new tst_DynamicFormChrome.qml (13 cases, incl. a fully chromed form with no visible built-in Label/Button/TabBar). Restoring the built-in Repeater under a chrome reddens the fields-created-once case. forms_qml_logic 330 passed.

Touches DynamicForm.qml alongside #808/#809: merged together on feature/forms-host-visuals-combined, the QML merges automatically and only CHANGELOG.md conflicts (each PR adds an entry at the head of "Added").

🤖 Generated with Claude Code

@codecov

codecov Bot commented Sep 24, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Yaraslaut added a commit that referenced this pull request Sep 25, 2026
….byChrome, and the registry reaches the forms they embed (fixes #813) (#819)

#810 made DynamicForm's chrome replaceable. The two composing views still
hard-coded theirs -- title, column headers, row cells, Open / row-action /
collection-action buttons, the "Are you sure?" and editor Dialogs, Back / Next
and the step counter -- and, more to the point, never gave the DynamicForms
they embed a slotRegistry at all: a host's field slots and form chrome stopped
at the view's edge, so even a fully registered kit drew built-in editors.

  - `slotRegistry` on both views, handed to modalForm / detailForm and to every
    wizard step.
  - CollectionView roles: collectionHeader (title, columns, actions, fire),
    collectionRow (row, columns, cells, rowKey, actions, canOpen, open, fire),
    confirmDialog (message, action, row, accept, reject -- loaded only while an
    action waits) and editorDialog (title, open, close, and a contentItem the
    editor form is reparented into).
  - WizardView roles: wizardHeader (title, stepIndex, stepCount, stepTitle)
    and wizardNav (canBack, canNext, lastStep, back, next). next() keeps the
    built-in gate: the step's action must have replied ok.
  - DateTimePicker gets no chrome: a host replaces the whole picker with a
    field slot, byKind("datetime") from #816.

Nothing registered, nothing changes: tst_collectionview.qml and
tst_wizardview.qml are untouched and green. tst_ViewChrome.qml (9 cases) pins
each role, the embedded forms receiving the registry, and a fully chromed
collection screen with the editor open showing no visible built-in Label or
Button. Dropping the registry hand-off and the row-chrome swap reddens 3 of
them. forms_qml_logic: 341 passed on MSVC 14.51 / Qt 6.11.1.

Stacked on #810 (byChrome / resolveChrome).

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Yaraslaut added a commit that referenced this pull request Sep 25, 2026
….byChrome, and the registry reaches the forms they embed (fixes #813) (#819)

hard-coded theirs -- title, column headers, row cells, Open / row-action /
collection-action buttons, the "Are you sure?" and editor Dialogs, Back / Next
and the step counter -- and, more to the point, never gave the DynamicForms
they embed a slotRegistry at all: a host's field slots and form chrome stopped
at the view's edge, so even a fully registered kit drew built-in editors.

  - `slotRegistry` on both views, handed to modalForm / detailForm and to every
    wizard step.
  - CollectionView roles: collectionHeader (title, columns, actions, fire),
    collectionRow (row, columns, cells, rowKey, actions, canOpen, open, fire),
    confirmDialog (message, action, row, accept, reject -- loaded only while an
    action waits) and editorDialog (title, open, close, and a contentItem the
    editor form is reparented into).
  - WizardView roles: wizardHeader (title, stepIndex, stepCount, stepTitle)
    and wizardNav (canBack, canNext, lastStep, back, next). next() keeps the
    built-in gate: the step's action must have replied ok.
  - DateTimePicker gets no chrome: a host replaces the whole picker with a
    field slot, byKind("datetime") from #816.

Nothing registered, nothing changes: tst_collectionview.qml and
tst_wizardview.qml are untouched and green. tst_ViewChrome.qml (9 cases) pins
each role, the embedded forms receiving the registry, and a fully chromed
collection screen with the editor open showing no visible built-in Label or
Button. Dropping the registry hand-off and the row-chrome swap reddens 3 of
them. forms_qml_logic: 341 passed on MSVC 14.51 / Qt 6.11.1.

Stacked on #810 (byChrome / resolveChrome).

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@Yaraslaut
Yaraslaut force-pushed the feature/forms-chrome-slots branch from e34375a to 4e5155f Compare September 25, 2026 07:00
Yaraslaut and others added 2 commits September 25, 2026 09:53
…tainers and buttons, not only its controls

SlotRegistry lets a host swap a field's *control*, but everything around the
controls stayed built-in: the bold caption and red `*`, the help line, the
section heading and accordion toggle, the TabBar, the action-type heading, the
status line, the Submit button, the monospace JSON preview and the reply line.
A host whose look comes from one UI kit therefore could not get a form that
looked like the rest of the app, whatever slots it registered.

`SlotRegistry.byChrome(role, component)` / `resolveChrome(role)` register one
Component per role -- fieldLabel, fieldHelp, section, accordion (falls back to
section), tabset, header, status, submitButton, preview, result -- and
DynamicForm loads it in place of the built-in, which is hidden rather than
drawn beside it. Each chrome item is handed only the members it declares
(`bindChrome`), as bindings where the value changes: a label gets `required`
(including requiredWhen) and `invalid` (typed text that does not encode, via
the new `fieldInvalid(name)`); the status line's text moved into
`DynamicForm.statusText` so the built-in and a chrome read one source.

Container chrome (section / accordion / tabset) declares a `contentItem`, and
the form creates its field grid inside it. The built-in grid's Repeater is
emptied under a chrome, so each field exists once and its `field_<name>`
objectName stays unique -- prefill, resetFields() and name-based lookups keep
working. A tabset chrome owns `currentIndex`; the form rebuilds the selected
tab's fields on a switch exactly as the built-in tab bar does. A chrome
submit button goes through `submit()`, so the `ready` guard still holds.

Nothing registered, nothing changes: the existing 317 QML cases are untouched
and green. The new tst_DynamicFormChrome.qml (13 cases) pins each role, and a
form with every role registered showing no visible built-in Label, Button or
TabBar. Restoring the section Repeater under a chrome reddens the
fields-created-once case. forms_qml_logic: 330 passed on MSVC 14.51 / Qt
6.11.1.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
….byChrome, and the registry reaches the forms they embed (fixes #813) (#819)

hard-coded theirs -- title, column headers, row cells, Open / row-action /
collection-action buttons, the "Are you sure?" and editor Dialogs, Back / Next
and the step counter -- and, more to the point, never gave the DynamicForms
they embed a slotRegistry at all: a host's field slots and form chrome stopped
at the view's edge, so even a fully registered kit drew built-in editors.

  - `slotRegistry` on both views, handed to modalForm / detailForm and to every
    wizard step.
  - CollectionView roles: collectionHeader (title, columns, actions, fire),
    collectionRow (row, columns, cells, rowKey, actions, canOpen, open, fire),
    confirmDialog (message, action, row, accept, reject -- loaded only while an
    action waits) and editorDialog (title, open, close, and a contentItem the
    editor form is reparented into).
  - WizardView roles: wizardHeader (title, stepIndex, stepCount, stepTitle)
    and wizardNav (canBack, canNext, lastStep, back, next). next() keeps the
    built-in gate: the step's action must have replied ok.
  - DateTimePicker gets no chrome: a host replaces the whole picker with a
    field slot, byKind("datetime") from #816.

Nothing registered, nothing changes: tst_collectionview.qml and
tst_wizardview.qml are untouched and green. tst_ViewChrome.qml (9 cases) pins
each role, the embedded forms receiving the registry, and a fully chromed
collection screen with the editor open showing no visible built-in Label or
Button. Dropping the registry hand-off and the row-chrome swap reddens 3 of
them. forms_qml_logic: 341 passed on MSVC 14.51 / Qt 6.11.1.

Stacked on #810 (byChrome / resolveChrome).

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@Yaraslaut
Yaraslaut force-pushed the feature/forms-chrome-slots branch from 4e5155f to 14e60d8 Compare September 25, 2026 07:54
@Yaraslaut
Yaraslaut merged commit 4ca807d into master Sep 25, 2026
1 of 16 checks passed
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.

1 participant