Skip to content

[6.x] Add a read-only mode to the element selector index - #20516

Draft
nfourtythree wants to merge 4 commits into
6.xfrom
feature/6x-fix-element-index-html-moved
Draft

nfourtythree wants to merge 4 commits into
6.xfrom
feature/6x-fix-element-index-html-moved

Conversation

@nfourtythree

Copy link
Copy Markdown
Contributor

Description

Plugins can't embed an element index for elements that aren't nested elements. The embedded index is tied to an owner, field and attribute, so something like a customer's orders has nowhere to go, and the selector modal always selects and never links.

element-selector-modals/body now accepts readOnly. In that mode titles and thumbnails link to each element's edit page, rows can't be selected, and the source sidebar is hidden. The request's criteria already applied in the modal context, so a plugin narrows the list with those.

ReadOnlyElementIndex is exported from @craftcms/cp and wraps this: it takes an elementType, criteria and an optional storageKey (so several indexes of one element type keep separate view settings), fetches its own first payload, and renders ElementPicker in read-only mode. ElementIndex gets a selectable prop to hide the checkboxes, and ContentIndexViewModel gets a linksToElements() hook in place of the hard-coded modal check.

Criteria come from the client, as they already do for the modal. They only narrow what the user can already see, so nothing is signed.

@github-actions

github-actions Bot commented Nov 9, 2026 •

Copy link
Copy Markdown

📚 Storybook previews

@craftcms/ui — open Storybook

No changed components detected in this Storybook.

resources/js — open Storybook

No changed components detected in this Storybook.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The read-only index nests its search form inside the surrounding editor form, so pressing Enter in search can unintentionally save the host element.

5 open issues
What changed in this PR

Adds a reusable read-only element index so plugins can embed criteria-filtered lists outside nested-element fields, using the existing selector infrastructure.

Changes:

  • Exports ReadOnlyElementIndex with criteria and optional storage keys for independent view settings.
  • Adds read-only server rendering and disables selection and the source sidebar in embedded indexes.
  • Adds coverage for links, criteria filtering, selection suppression, and storage keys.
File Description
tests/​Feature/​Http/​Controllers/​Elements/​ElementSelectorModalControllerTest.php Tests read-only links and criteria filtering.
src/​Http/​ViewModels/​ModalIndexViewModel.php Adds read-only title and thumbnail linking behavior.
src/​Http/​ViewModels/​ContentIndexViewModel.php Introduces an overridable element-linking hook.
src/​Http/​Controllers/​Elements/​ElementSelectorModalController.php Validates and forwards the read-only flag.
resources/​js/​modules/​elements/​index/​components/​ElementIndex.vue Adds a prop to disable row selection controls.
resources/​js/​modules/​element-selector-modal/​useModalElementIndex.ts Supports read-only behavior and custom storage keys.
resources/​js/​modules/​element-selector-modal/​useModalElementIndex.test.ts Tests selection suppression and settings storage.
resources/​js/​modules/​element-selector-modal/​ReadOnlyElementIndex.vue Fetches and renders the embedded read-only index.
resources/​js/​modules/​element-selector-modal/​ElementPicker.vue Adapts the picker layout and controls for embedding.
resources/​js/​cp-module.ts Exports the new component from @craftcms/cp.
CHANGELOG-WIP.md Documents the component and read-only endpoint option.

Review effort: Balanced

@brandonkelly
brandonkelly marked this pull request as draft November 9, 2026 20:15

@brandonkelly brandonkelly left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nfourtythree Converted this to a draft. Mark it as ready for review when Copilot is happy.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Active issues remove folder navigation links in read-only tables and leave the picker indefinitely pending if the second row-loading request fails.

No open issues 🎉

2 previously missed issues

In code that hasn't changed since last review

Medium severity Handle errors from ElementPicker's row request

resources/​js/​modules/​element-selector-modal/​ReadOnlyElementIndex.vue:75

The first request omits viewMode, so its successful response contains no rows; mounting ElementPicker starts a second request through index.restore(). If that row request fails, it bypasses this wrapper's error handler and leaves the index pending with disabled controls and placeholders indefinitely. Forward the detached index's onError callback through useModalElementIndex and ElementPicker to this wrapper's error/retry state.

Medium severity Preserve folder links in read-only mode

src/​Http/​ViewModels/​ModalIndexViewModel.php:158

With readOnly: true and showFolders: true, this early return also delegates asset folders to the parent, which renders plain chips because folders have no edit URL. This removes the table's folder links, so a normal click no longer opens folders and keyboard users lose the focusable link. Delegate only non-folder elements to the parent so the existing data-folder-link branch remains available.

5 resolved since last review

Review effort: Balanced

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Row-request failures and retries remove focused controls without restoring focus, disrupting keyboard navigation in the element picker.

1 open issue

Review effort: Balanced

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Initial fetch failures unconditionally move focus to “Try again,” interrupting users working elsewhere and potentially causing repeated focus theft when multiple embedded indexes fail.

1 open issue
1 resolved since last review

Review effort: Balanced

function showFailure(): void {
failed.value = true;
void focusWithin('craft-button');

This branch has not been deployed

No deployments
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.

3 participants