[PP-1] Entity Reference Selection UI
>>> [!note] Migrated issue <!-- Drupal.org comment --> <!-- Migrated from issue #3585355. --> Reported by: [penyaskito](https://www.drupal.org/user/959536) >>> <h3 id="problem-motivation">Problem/Motivation</h3> <p> Editors configuring a JS code component need a way to declare which entity fields a prop depends on. The config storage was added in <span class="drupalorg-gitlab-issue-link drupalorg-gitlab-link-wrapper"><a href="https://git.drupalcode.org/project/canvas/-/work_items/3585298" class="drupalorg-gitlab-link">https://git.drupalcode.org/project/canvas/-/work_items/3585298</a></span> as<br> <code>dataDependencies.entityFields</code>, and the supporting HTTP APIs are added in <span class="drupalorg-gitlab-issue-link drupalorg-gitlab-link-wrapper"><a href="https://git.drupalcode.org/project/canvas/-/work_items/3585354" class="drupalorg-gitlab-link">https://git.drupalcode.org/project/canvas/-/work_items/3585354</a></span>. This issue wires both together with an editor-facing React UI.</p> <h3 id="proposed-resolution">Proposed resolution</h3> <p> Add a "Content Relationship"-style modal dialog to the code component editor. The editor picks an entity type + bundle, then selects fields via checkboxes; reference<br> fields are expandable and load their children lazily. Selections persist as an array of expression strings in <code>dataDependencies.entityFields[propName]</code>.</p> <p> <strong>Existing infrastructure reused (no new auto-save wiring):</strong></p> <ul> <li><code>codeEditorSlice</code> at <code>ui/src/features/code-editor/codeEditorSlice.ts</code> &mdash; already manages <code>state.codeComponent.dataDependencies</code>.</li> <li><code>setCodeComponentProperty([property, value])</code> action &mdash; sets any <code>codeComponent</code> property and flags <code>needsAutoSave</code>.</li> <li><code>useAutoSave</code> hook &mdash; already watches <code>dataDependencies</code> and dispatches the existing<br> <pre>PATCH<br>&nbsp; /canvas/api/v0/config/auto-save/js_component/{id}</pre><p> mutation.</p></li> <li>Props panel at <code>ui/src/features/code-editor/component-data/Props.tsx</code> &mdash; the entry point ("Bind to entity fields" action on a prop row).</li> </ul> <p> <strong>New components</strong> (in <code>ui/src/features/code-editor/component-data/entity-reference/</code>):</p> <ul> <li><code>EntityReferenceSelectionDialog</code> &mdash; top-level modal, receives <code>propName</code>, owns draft state.</li> <li><code>TypeSelector</code> &mdash; combobox for entity type + bundle (RTK Query against <code>/content-entity-types</code>).</li> <li><code>FieldTree</code> &mdash; recursive tree with checkboxes; expanding a reference field triggers a lazy fetch with <code>parent=[expression]</code>.</li> <li><code>FieldRow</code> &mdash; leaf row rendering (icon, label, reference-target pill, checkbox).</li> </ul> <p> <strong>New RTK Query service:</strong><br> <code>entityReferenceApi</code> slice in <code>ui/src/services/</code> with <code>getContentEntityTypes()</code> and <code>getFields(entityType, bundle, parent?)</code><br> endpoints, wired into <code>store.ts</code>. Cache tags scoped by entity type + bundle + parent so lazy-loaded subtrees cache independently. 403 responses (from a user's<br> permissions changing or a parent chain becoming inaccessible) surface as RTK Query errors &mdash; consuming components render an error state rather than crashing.</p> <p> <strong>Type extension:</strong><br> Extend <code>DataDependencies</code> at <code>ui/src/types/CodeComponent.ts</code> with <code>entityFields?: Record&lt;string, string[]&gt;</code>.</p> <p> <strong>Persistence flow (no direct PATCH from the UI):</strong></p> <ol> <li>Dialog opens with <code>propName</code> context; reads current selections from Redux.</li> <li>Local draft state tracks the working set separately from the store &mdash; only commits on Save.</li> <li>On Save, dispatches<br> <pre>setCodeComponentProperty(['dataDependencies', { ...current, entityFields: { ...current.entityFields, [propName]: [...selected] }<br>&nbsp; }])</pre><p>. Empty arrays/keys are pruned to respect backend <code>NotBlank</code> semantics.</p></li> <li>The existing <code>useAutoSave</code> hook observes the store change and auto-saves via the existing mutation.</li> <li>Cancel discards draft state without dispatching.</li> </ol> <h3 id="remaining-tasks">Remaining tasks</h3> <ul> <li>Land backend APIs from <span class="drupalorg-gitlab-issue-link drupalorg-gitlab-link-wrapper"><a href="https://git.drupalcode.org/project/canvas/-/work_items/3585354" class="drupalorg-gitlab-link">https://git.drupalcode.org/project/canvas/-/work_items/3585354</a></span>.</li> <li>Extend <code>DataDependencies</code> TypeScript type.</li> <li>Create <code>entityReferenceApi</code> RTK Query service and register it in <code>store.ts</code>.</li> <li>Build the four new components with error-state handling for 403s.</li> <li>Add "Bind to entity fields" action to <code>Props.tsx</code>.</li> <li>Vitest coverage for: Save dispatches correctly-shaped payload, Cancel does not dispatch, empty selections prune keys, dialog initialises from existing Redux<br> selection, reference-field expansion triggers <code>getFields</code> with the correct <code>parent</code>.</li> <li>Manual verification: open the JS component editor, configure <code>entityFields</code> for a prop, save, reload &mdash; selections persist.</li> </ul> <h3 id="user-interface-changes">User interface changes</h3> <p> New modal dialog accessible from the props panel in the code component editor. No changes to existing views.</p> <h3 id="api-changes">API changes</h3> <p> None. This issue consumes the endpoints added in <span class="drupalorg-gitlab-issue-link drupalorg-gitlab-link-wrapper"><a href="https://git.drupalcode.org/project/canvas/-/work_items/3585354" class="drupalorg-gitlab-link">https://git.drupalcode.org/project/canvas/-/work_items/3585354</a></span> and writes via the existing auto-save mutation.</p> <h3 id="data-model-changes">Data model changes</h3> <p> None server-side. Client-side <code>DataDependencies</code> TypeScript type gains an optional <code>entityFields</code> key.</p> > Related issue: [Issue #3585354](https://www.drupal.org/node/3585354) > Related issue: [Issue #3573831](https://www.drupal.org/node/3573831)
issue