From bd29672faf5c8aea2c8a2e6143bbd2e78038fd99 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 10 Aug 2026 00:15:48 +0000 Subject: [PATCH] fix(fields): resolve select + multiple to field:multiselect so its host label names the chip group `mapFieldTypeToFormType` keyed the widget id on the field `type` string alone, so an object-schema `{ type: 'select', multiple: true }` picklist became `field:select`. `SelectField` then delegated to `MultiSelectField` on `config.multiple`: the component that RENDERED was the chip picker, while everything keyed on the widget id still answered for the single-value combobox -- above all the label-association declaration (`ComponentMeta.labelling`, objectui#3961), which the form renderer resolves per widget id. The host emitted `label for` at the chip row's wrapper div, where a `for` is inert (`HTMLLabelElement.control` is null): a visible label naming nothing. Declaring `select` itself `labelling: 'group'` was not available -- a single-value select's trigger is a labelable `button[role=combobox]` whose `for` works, and a bare `select` is a builtin the renderer resolves without the registry. So the producer names the widget that renders: the id now carries the arity, and declaration and render can no longer diverge. - `mapFieldTypeToFormType(fieldType, config?)` gains an optional second argument; only `multiple` is read. Single-argument calls are unchanged. The override is table-driven and `select` is its only member: the spec's multi-capable set is larger (select / lookup / file / image, `radio` on the select branch, `user` storing like `lookup`), but LookupField, FileField and ImageField each render both arities themselves, so their id -- and their labelling declaration -- is already right either way. - The four object-form producers pass the pair: ObjectForm, DrawerForm, ModalForm, sectionFields. In sectionFields the id is computed once from the EFFECTIVE pair after view-level overrides merge, because `multiple` is itself a spec FormField key: a view restating only `multiple: true` moves the widget too, and `multiple: false` moves it back. - SelectField's delegation is KEPT, not retired. Measured, three entrances still reach it with `multiple` set, none of which consult the alias map: the inline grid editor (FieldEditWidget finds `select` in its own table first), ActionParamDialog (resolveFormWidgetType returns `select` from fieldWidgetMap first), and hand-written SDUI naming `field:select`. Read-only rendering is untouched (objectui#4005); so is the built-in `Select` branch (objectui#3976). Fixes #3986 Co-authored-by: Claude --- .../select-multiple-widget-mapping-3986.md | 16 + .../group-labelling-declaration.test.ts | 37 ++- .../src/field-type-alias.multiple.test.ts | 81 +++++ packages/fields/src/field-type-alias.ts | 60 +++- packages/fields/src/widgets/SelectField.tsx | 30 +- packages/plugin-form/src/DrawerForm.tsx | 3 +- packages/plugin-form/src/ModalForm.tsx | 3 +- packages/plugin-form/src/ObjectForm.tsx | 6 +- .../selectMultipleGroupLabel.test.tsx | 282 ++++++++++++++++++ packages/plugin-form/src/sectionFields.ts | 20 +- 10 files changed, 525 insertions(+), 13 deletions(-) create mode 100644 .changeset/select-multiple-widget-mapping-3986.md create mode 100644 packages/fields/src/field-type-alias.multiple.test.ts create mode 100644 packages/plugin-form/src/__tests__/selectMultipleGroupLabel.test.tsx diff --git a/.changeset/select-multiple-widget-mapping-3986.md b/.changeset/select-multiple-widget-mapping-3986.md new file mode 100644 index 0000000000..b3b5681dd2 --- /dev/null +++ b/.changeset/select-multiple-widget-mapping-3986.md @@ -0,0 +1,16 @@ +--- +'@object-ui/fields': patch +'@object-ui/plugin-form': patch +--- + +Resolve a `select` field declared `multiple: true` to the `field:multiselect` widget, so the object form's visible label actually names the chip picker it renders (objectui#3986). + +`mapFieldTypeToFormType` keyed the widget id on the field's `type` string alone, so an object-schema `{ type: 'select', multiple: true }` picklist — a spec-legal, entirely ordinary shape — became `field:select`. `SelectField` then delegated to `MultiSelectField` on `config.multiple`, so the component that RENDERED was the chip picker while everything keyed on the widget id still answered for the single-value combobox. Above all the label-association declaration (`ComponentMeta.labelling`, objectui#3961), which the form renderer resolves per widget id: the host emitted `