--- /dev/null
+<?php
+
+namespace Drupal\media_library\Plugin\views\field;
+
+use Drupal\Core\Ajax\AjaxResponse;
+use Drupal\Core\Ajax\CloseDialogCommand;
+use Drupal\Core\Ajax\InvokeCommand;
+use Drupal\Core\Form\FormBuilderInterface;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Url;
+use Drupal\views\Plugin\views\field\FieldPluginBase;
+use Drupal\views\Render\ViewsRenderPipelineMarkup;
+use Drupal\views\ResultRow;
+use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
+
+/**
+ * Defines a field that outputs a checkbox and form for selecting media.
+ *
+ * @ViewsField("media_library_select_form")
+ *
+ * @internal
+ */
+class MediaLibrarySelectForm extends FieldPluginBase {
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getValue(ResultRow $row, $field = NULL) {
+ return '<!--form-item-' . $this->options['id'] . '--' . $row->index . '-->';
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function render(ResultRow $values) {
+ return ViewsRenderPipelineMarkup::create($this->getValue($values));
+ }
+
+ /**
+ * Form constructor for the media library select form.
+ *
+ * @param array $form
+ * An associative array containing the structure of the form.
+ * @param \Drupal\Core\Form\FormStateInterface $form_state
+ * The current state of the form.
+ */
+ public function viewsForm(array &$form, FormStateInterface $form_state) {
+ // Only add the bulk form options and buttons if there are results.
+ if (empty($this->view->result)) {
+ return;
+ }
+
+ // Render checkboxes for all rows.
+ $form[$this->options['id']]['#tree'] = TRUE;
+ foreach ($this->view->result as $row_index => $row) {
+ $entity = $this->getEntity($row);
+ $form[$this->options['id']][$row_index] = [
+ '#type' => 'checkbox',
+ '#title' => $this->t('Select @label', [
+ '@label' => $entity->label(),
+ ]),
+ '#title_display' => 'invisible',
+ '#return_value' => $entity->id(),
+ ];
+ }
+
+ // @todo Remove in https://www.drupal.org/project/drupal/issues/2504115
+ // Currently the default URL for all AJAX form elements is the current URL,
+ // not the form action. This causes bugs when this form is rendered from an
+ // AJAX path like /views/ajax, which cannot process AJAX form submits.
+ $url = parse_url($form['#action'], PHP_URL_PATH);
+ $query = \Drupal::request()->query->all();
+ $query[FormBuilderInterface::AJAX_FORM_REQUEST] = TRUE;
+ $form['actions']['submit']['#ajax'] = [
+ 'url' => Url::fromUserInput($url),
+ 'options' => [
+ 'query' => $query,
+ ],
+ 'callback' => [static::class, 'updateWidget'],
+ ];
+
+ $form['actions']['submit']['#value'] = $this->t('Select media');
+ $form['actions']['submit']['#field_id'] = $this->options['id'];
+ }
+
+ /**
+ * Submit handler for the media library select form.
+ *
+ * @param array $form
+ * An associative array containing the structure of the form.
+ * @param \Drupal\Core\Form\FormStateInterface $form_state
+ * The current state of the form.
+ *
+ * @return \Drupal\Core\Ajax\AjaxResponse
+ * A command to send the selection to the current field widget.
+ */
+ public static function updateWidget(array &$form, FormStateInterface $form_state) {
+ $widget_id = \Drupal::request()->query->get('media_library_widget_id');
+ if (!$widget_id || !is_string($widget_id)) {
+ throw new BadRequestHttpException('The "media_library_widget_id" query parameter is required and must be a string.');
+ }
+ $field_id = $form_state->getTriggeringElement()['#field_id'];
+ $selected = array_values(array_filter($form_state->getValue($field_id, [])));
+ // Pass the selection to the field widget based on the current widget ID.
+ return (new AjaxResponse())
+ ->addCommand(new InvokeCommand("[data-media-library-widget-value=\"$widget_id\"]", 'val', [implode(',', $selected)]))
+ ->addCommand(new InvokeCommand("[data-media-library-widget-update=\"$widget_id\"]", 'trigger', ['mousedown']))
+ ->addCommand(new CloseDialogCommand());
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function viewsFormValidate(array &$form, FormStateInterface $form_state) {
+ $selected = array_filter($form_state->getValue($this->options['id']));
+ if (empty($selected)) {
+ $form_state->setErrorByName('', $this->t('No items selected.'));
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function clickSortable() {
+ return FALSE;
+ }
+
+}