Thanks Aaron for this module, the picker is a real improvement over a long select list.
Problem/Motivation
Declare enum and x-canvas-icon on the same prop and the picker never appears. You get a plain select list, and no error.
The hook sets only fieldWidget. Canvas has already chosen list_string / options_select from the enum, so that shape stays and the widget is ignored.
This hits any theme with an enum-constrained icon prop, on Drupal core with Drupal Canvas, Drupal CMS included. Today the only option is to delete the prop's enum, which loses per-component icon curation for every site using that theme.
Still present in 1.0.3. Verified on Drupal core 11.4.5, PHP 8.4.20, Drupal Canvas 1.10.1, UI Icons 1.1.2, Canvas Icon Picker 1.0.2, against a Vartheme BS5 component's icon prop.
Steps to reproduce
- Enable Drupal Canvas, UI Icons and Canvas Icon Picker.
- Give a component a string prop declaring both an
enumof icon ids andx-canvas-icon. - Place the component on a Canvas page and open its props form.
- A select list renders, not the picker.
Proposed resolution
1. Force the string field type. The widget only supports string, so the hook must set the type, not just the widget:
$storable_prop_shape->fieldTypeProp = new FieldTypePropExpression('string', 'value');
$storable_prop_shape->fieldStorageSettings = NULL;2. Honour the enum. That alone shows the picker, but it stores pack:icon_id while the enum lists bare ids, so every pick fails with Does not have a value in the enumeration. When a prop declares an enum:
- pass its values as an
allowed_icon_idsinstance setting (43, 50 and 51 ids on the components tested); - send them to the index and sprite endpoints as an
idsquery parameter, filtering the grid server-side; no JavaScript change; - strip the
pack:prefix inmassageFormValues(), so the stored value matches the enum.IconRepository::resolve()already takes a bare id.
Tested: the index drops from 2078 icons to 5, the sprite returns 5 symbol elements, and both endpoints gain the url.query_args:ids cache context.
Remaining tasks
- ✅ File an issue
- ✅ Addition/Change/Update/Fix
- ❌ Testing to ensure no regression
- ❌ Automated unit/functional testing coverage
- ➖ Developer Documentation support
- ➖ User Guide Documentation support
- ➖ UX/UI designer responsibilities
- ➖ Accessibility and Readability
- ❌ Reviewed by a human
- ❌ Code review by maintainers
- ❌ Full testing and approval
- ❌ Credit contributors
- ❌ Review with the product owner
- ❌ Update Release Notes
- ❌ Release
User interface changes
- A prop declaring both
enumandx-canvas-icongets the picker instead of a select list, showing only the icons the enum lists.
API changes
- New optional
idsquery parameter on the icon index and sprite routes. - New
allowed_icon_idsfield instance setting.
Data model changes
field.field_settings.stringgains anallowed_icon_idssequence.
Release notes snippet
- The icon picker now works on a prop that also declares an
enum, and offers only the icons that enum lists.
AI-Generated: Yes
| Comment | File | Size | Author |
|---|---|---|---|
| #13 | cip-mr2-09-mercury-icon-card-unrestricted.png | 87.36 KB | rajab natshah |
| #13 | cip-mr2-08-mercury-button-enum6.png | 75.14 KB | rajab natshah |
| #12 | cip-mr2-07-byte-icon-card-unrestricted.png | 104.5 KB | rajab natshah |
| #12 | cip-mr2-06-byte-button-enum6.png | 91.51 KB | rajab natshah |
| #9 | cip-04-haven-icon-card-unrestricted.png | 114.69 KB | rajab natshah |
Issue fork canvas_icon_picker-3619510
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:
- 3619510-the-icon-picker
changes, plain diff MR !1
- 3619510-enum-aware-no-new-setting
changes, plain diff MR !2
Comments
Comment #2
rajab natshahComment #3
rajab natshahComment #5
rajab natshahComment #6
rajab natshahExample 1 of 4: what an enum-constrained icon prop looks like today. Reproduced on a stock Drupal CMS install, so this is not distribution-specific.
Environment: Drupal CMS with the Haven site template, Drupal core 11.4.5, PHP 8.4.20, Drupal Canvas 1.10.1, UI Icons 1.1.2, Canvas Icon Picker 1.0.3 unpatched. Icon pack is Haven's own
phosphor, 1512 icons.A component with a curated icon set, declaring both
enumandx-canvas-icon:Canvas derives
field_type: list_stringwithfield_widget: canvas_icon_picker. The widget only supportsstring, so it is dropped and a plain select renders. No error, no warning.What the editor shows: an
Iconselect with_none, info, warning, check-circle, x-circle, and zero picker elements in the DOM.The next three comments show the same site with the merge request applied.
AI-Generated: Yes
Comment #7
rajab natshahExample 2 of 4: a curated set that ships in Drupal CMS today. This one needs no invented component. The Haven site template's own Button already curates its icon prop:
Six icons, with labels, chosen by the theme's designers. Haven does not yet opt into this module, so for the test I added
x-canvas-icon: ['phosphor']to that prop and nothing else.Unpatched, that combination gives the select from the previous comment. With the merge request applied, the picker renders and the grid holds exactly those six:
The drawer reports
6 icons, and the element requests only those ids:data-canvas-icon-picker-index-url="/canvas-icon-picker/index?packs=phosphor&ids=arrow-right,arrow-left,caret-right,caret-left,download,user-plus"So the theme keeps its curated set and the editor still gets a visual picker.
AI-Generated: Yes
Comment #8
rajab natshahExample 3 of 4: a share component, where only social icons make sense. This is the case the Webshare module covers: buttons that share the current page. Webshare itself ships no Single Directory Components, so this is a small component modelled on it.
Nine icons out of the pack's 1512. An editor picking a share icon should not be offered 1512 options, and the component should not accept an arbitrary one.
The drawer reports
9 icons, and the grid tiles are exactly the enum values:link-simple, linkedin-logo, telegram-logo, instagram-logo, x-logo, envelope, whatsapp-logo, facebook-logo, reddit-logo.AI-Generated: Yes
Comment #9
rajab natshahExample 4 of 4: no enum, nothing changes. Haven's own Icon card has an icon prop with no
enum, so the whole pack stays available. This is the regression check: the patch must not narrow a prop that was never curated.The drawer reports
1512 iconsand the request carries noidsparameter:data-canvas-icon-picker-index-url="/canvas-icon-picker/index?packs=phosphor"Summary of the four examples
Same site, same pack,
canvas_icon_picker1.0.3 versus 1.0.3 plus the merge request:All six Haven components with an icon prop were opted in (Button, Badge, Card, Icon card, Feature card, Icon). Front page stayed HTTP 200 throughout, and the patch applies cleanly to the 1.0.3 tarball from ftp.drupal.org.
One note on adoption, since it surprised me: opting a theme in by adding
x-canvas-iconto its manifests is safe on a site with existing content. Canvas derives a new component version and existing instances keep resolving against the stored one.AI-Generated: Yes
Comment #11
rajab natshahA second merge request, as an alternative to the first. Same outcome, smaller surface. They are mutually exclusive, so pick whichever fits the module better.
stringand carries the enum forward in a newallowed_icon_idsfield instance setting, which needs aconfig/schemaaddition. 7 files.list_stringand reads the constraint where Canvas already keeps it. No new setting, no schema change, andcanvasStorablePropShapeAlter()is untouched. 5 files.The idea behind !2 is that the module does not need to be told about the enum, it can just notice it. A prop declaring
enumalready becomes a list field whose allowed values are those enum members, so the constraint exists on the field before the picker gets involved. Three small changes are enough:field_types: ['string', 'list_string'], so Canvas stops discarding it for an enum-constrained prop;allowed_values_function, which is Canvas'scanvas_load_allowed_values_for_component_prop;massageFormValues()strips thepack:prefix on a list field, so the stored value stays one of the allowed values.Verified on the same Drupal CMS and Haven site as the examples above, with Canvas Icon Picker 1.0.3. A prop with a curated
enumof four goes from a plain select to the picker reporting4 icons, requesting/canvas-icon-picker/index?packs=phosphor&ids=info,warning,check-circle,x-circle. A prop with noenumis unaffected, still1512 iconsand noidsparameter.One part of !2 I am not confident about: it calls
allowed_values_functiondirectly, becausegetSettableOptions()came back empty in the widget context. If there is a better-supported way to reach a list field's allowed values from a widget, that is the line to change.AI-Generated: Yes
Comment #12
rajab natshahMR !2 on Drupal CMS with the Byte site template. Fresh install, template picked in the installer, then MR !2 applied to Canvas Icon Picker 1.0.3.
Environment: Drupal core 11.4.5, PHP 8.4.20, Drupal Canvas 1.10.1, UI Icons 1.1.2. Icon pack is Byte's own
phosphor, 1512 icons. Byte does not opt into the picker yet, sox-canvas-icon: ['phosphor']was added to its four icon props and nothing else.Byte's Button ships a curated
enumof six, the same set Haven ships:With MR !2 the field stays
list_string, the widget is no longer discarded, and the grid holds exactly those six. The drawer reports6 icons, the request carriesids=arrow-right,arrow-left,caret-right,caret-left,download,user-plus, and no select renders.Byte's Icon card has no
enum, and is unaffected:1512 icons, noidsparameter.Config for the four opted-in components, after the patch:
Front page stayed HTTP 200 throughout.
AI-Generated: Yes
Comment #13
rajab natshahMR !2 on Drupal CMS with the Mercury Demo site template. A second fresh install, Mercury Demo picked in the installer, then MR !2 applied to Canvas Icon Picker 1.0.3.
Same environment as the Byte comment: Drupal core 11.4.5, PHP 8.4.20, Drupal Canvas 1.10.1, UI Icons 1.1.2, Mercury's own
phosphorpack of 1512 icons. Mercury does not opt into the picker yet, sox-canvas-icon: ['phosphor']was added to its four icon props and nothing else.Mercury's Button curates the same six icons Haven and Byte curate, which is worth noting on its own: this shape is standard across the Drupal CMS site templates, not a one-off.
Drawer reports
6 icons, request carriesids=arrow-right,arrow-left,caret-right,caret-left,download,user-plus, no select:Mercury's Icon card has no
enumand is unaffected:1512 icons, noidsparameter.Coverage so far for MR !2
The Varbase row is the one that differs: its enum of 43 includes
none, which is a valid prop value but not an icon, so the grid shows 42 tiles for 43 requested ids. Worth knowing if you want the count label to match the enum length exactly.One limitation of MR !2 to record: it keys off the field being
list_string. A component instance pinned to a stored version created while MR !1 was applied has field typestring, so it renders the picker unrestricted. That only affects a site that ran !1 first.AI-Generated: Yes
Comment #16
aaronchristian commentedThanks Rajab, good catch. Forcing the string field type was definitely the right call. I merged that into `1.0.x` with a follow-up commit (`493c1b0`).
I also used AI to help review the MR and test the different paths, and that surfaced another issue with enum props. The picker would show up correctly, but selecting an icon wouldn’t actually stick.
Canvas validates the value in the browser with Ajv before anything reaches the server. The picker was writing `pack:icon`, so if the enum only allowed bare IDs like `umbrella`, Canvas rejected it immediately. That meant
massageFormValues()never even ran. MR !2 had the same problem.What I landed instead is a bit simpler. The allowed enum values are passed into the JS, and the picker writes the value in the same format the enum already uses, either the bare icon ID or the full `pack:icon` value.
I dropped
massageFormValues()since it wasn’t doing anything in this flow, added kernel coverage, and added a short README section for enum usage.Tested it on a live Canvas page as well. Picking an icon now stores `icon: "umbrella"` and survives a reload.
No update hook needed for existing sites either. A cache rebuild will regenerate the Canvas component version, and existing stored values are already fine.
Closing both MRs as merged/superseded. Thanks again for the report and the patch.
Comment #17
aaronchristian commented