From 71cb5448caf5bad591c0ab1f18e44a71388f982d Mon Sep 17 00:00:00 2001
From: Brian Canini <canini.16@osu.edu>
Date: Wed, 4 Mar 2020 10:42:59 -0500
Subject: [PATCH] Updating drupal/views_autocomplete_filters (1.1.0 => 1.2.0)

---
 composer.json                                 |   4 +-
 composer.lock                                 |  16 +-
 vendor/composer/installed.json                |  16 +-
 ...iews_autocomplete_filters.views.schema.yml | 113 ++++++
 .../views-autocomplete-filters-dependent.js   | 351 ++++++++++++++----
 .../js/views-autocomplete-filters.js          |  21 ++
 .../ViewsAutocompleteFiltersController.php    |  70 ++--
 .../ViewsAutocompleteFiltersCombine.php       | 125 +------
 .../ViewsAutocompleteFiltersInterface.php     |  17 +
 ...wsAutocompleteFiltersSearchApiFulltext.php |  29 ++
 .../ViewsAutocompleteFiltersSearchApiText.php |  21 ++
 .../filter/ViewsAutocompleteFiltersString.php | 138 +------
 .../filter/ViewsAutocompleteFiltersTrait.php  | 162 ++++++++
 .../views_autocomplete_filters.info.yml       |   6 +-
 .../views_autocomplete_filters.libraries.yml  |   8 +
 .../views_autocomplete_filters.views.inc      |  12 +-
 16 files changed, 740 insertions(+), 369 deletions(-)
 create mode 100644 web/modules/views_autocomplete_filters/config/schema/views_autocomplete_filters.views.schema.yml
 create mode 100644 web/modules/views_autocomplete_filters/js/views-autocomplete-filters.js
 create mode 100644 web/modules/views_autocomplete_filters/src/Plugin/views/filter/ViewsAutocompleteFiltersInterface.php
 create mode 100644 web/modules/views_autocomplete_filters/src/Plugin/views/filter/ViewsAutocompleteFiltersSearchApiFulltext.php
 create mode 100644 web/modules/views_autocomplete_filters/src/Plugin/views/filter/ViewsAutocompleteFiltersSearchApiText.php
 create mode 100644 web/modules/views_autocomplete_filters/src/Plugin/views/filter/ViewsAutocompleteFiltersTrait.php

diff --git a/composer.json b/composer.json
index 8de26d8ad5..b25e8cfdc7 100644
--- a/composer.json
+++ b/composer.json
@@ -177,7 +177,7 @@
         "drupal/view_unpublished": "^1.0@alpha",
         "drupal/views_accordion": "1.1",
         "drupal/views_ajax_history": "^1.1",
-        "drupal/views_autocomplete_filters": "1.1",
+        "drupal/views_autocomplete_filters": "1.2",
         "drupal/views_bootstrap": "3.1",
         "drupal/views_bulk_operations": "^3.0",
         "drupal/views_fieldsets": "3.3",
@@ -316,4 +316,4 @@
             "php": "7.0.8"
         }
     }
-}
\ No newline at end of file
+}
diff --git a/composer.lock b/composer.lock
index e304ef53bb..7b0303c87c 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
         "This file is @generated automatically"
     ],
-    "content-hash": "9cebdfa2dca9a89bb18005ed1f9db499",
+    "content-hash": "1ef3b6e293db50b4ebd66d3e2d88a077",
     "packages": [
         {
             "name": "alchemy/zippy",
@@ -7915,17 +7915,17 @@
         },
         {
             "name": "drupal/views_autocomplete_filters",
-            "version": "1.1.0",
+            "version": "1.2.0",
             "source": {
                 "type": "git",
                 "url": "https://git.drupalcode.org/project/views_autocomplete_filters.git",
-                "reference": "8.x-1.1"
+                "reference": "8.x-1.2"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://ftp.drupal.org/files/projects/views_autocomplete_filters-8.x-1.1.zip",
-                "reference": "8.x-1.1",
-                "shasum": "08dda1e5bdc3e25e7946fc64642cf245c4f429cf"
+                "url": "https://ftp.drupal.org/files/projects/views_autocomplete_filters-8.x-1.2.zip",
+                "reference": "8.x-1.2",
+                "shasum": "bab27febe29aa69eff5c6b3ce3125e49685a6346"
             },
             "require": {
                 "drupal/core": "*"
@@ -7936,7 +7936,7 @@
                     "dev-1.x": "1.x-dev"
                 },
                 "drupal": {
-                    "version": "8.x-1.1",
+                    "version": "8.x-1.2",
                     "datestamp": "1500109743",
                     "security-coverage": {
                         "status": "covered",
@@ -7961,7 +7961,7 @@
             "description": "Use Autocomplete for string field filters.",
             "homepage": "https://www.drupal.org/project/views_autocomplete_filters",
             "support": {
-                "source": "http://cgit.drupalcode.org/views_autocomplete_filters"
+                "source": "https://git.drupalcode.org/project/views_autocomplete_filters"
             }
         },
         {
diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json
index 899a4506a1..a27afb929d 100644
--- a/vendor/composer/installed.json
+++ b/vendor/composer/installed.json
@@ -8166,18 +8166,18 @@
     },
     {
         "name": "drupal/views_autocomplete_filters",
-        "version": "1.1.0",
-        "version_normalized": "1.1.0.0",
+        "version": "1.2.0",
+        "version_normalized": "1.2.0.0",
         "source": {
             "type": "git",
             "url": "https://git.drupalcode.org/project/views_autocomplete_filters.git",
-            "reference": "8.x-1.1"
+            "reference": "8.x-1.2"
         },
         "dist": {
             "type": "zip",
-            "url": "https://ftp.drupal.org/files/projects/views_autocomplete_filters-8.x-1.1.zip",
-            "reference": "8.x-1.1",
-            "shasum": "08dda1e5bdc3e25e7946fc64642cf245c4f429cf"
+            "url": "https://ftp.drupal.org/files/projects/views_autocomplete_filters-8.x-1.2.zip",
+            "reference": "8.x-1.2",
+            "shasum": "bab27febe29aa69eff5c6b3ce3125e49685a6346"
         },
         "require": {
             "drupal/core": "*"
@@ -8188,7 +8188,7 @@
                 "dev-1.x": "1.x-dev"
             },
             "drupal": {
-                "version": "8.x-1.1",
+                "version": "8.x-1.2",
                 "datestamp": "1500109743",
                 "security-coverage": {
                     "status": "covered",
@@ -8214,7 +8214,7 @@
         "description": "Use Autocomplete for string field filters.",
         "homepage": "https://www.drupal.org/project/views_autocomplete_filters",
         "support": {
-            "source": "http://cgit.drupalcode.org/views_autocomplete_filters"
+            "source": "https://git.drupalcode.org/project/views_autocomplete_filters"
         }
     },
     {
diff --git a/web/modules/views_autocomplete_filters/config/schema/views_autocomplete_filters.views.schema.yml b/web/modules/views_autocomplete_filters/config/schema/views_autocomplete_filters.views.schema.yml
new file mode 100644
index 0000000000..2e5bb17d9a
--- /dev/null
+++ b/web/modules/views_autocomplete_filters/config/schema/views_autocomplete_filters.views.schema.yml
@@ -0,0 +1,113 @@
+views.filter.views_autocomplete_filters_combine:
+  type: views.filter.combine
+  label: 'Autocomplete for Combine fields filter'
+  mapping:
+    expose:
+      type: mapping
+      label: 'Exposed'
+      mapping:
+        autocomplete_filter:
+          type: integer
+          label: 'Use Autocomplete'
+        autocomplete_min_chars:
+          type: string
+          label: 'Minimum number of characters to start filter'
+        autocomplete_items:
+          type: string
+          label: 'Maximum number of items in Autocomplete'
+        autocomplete_raw_suggestion:
+          type: integer
+          label: 'Unformatted suggestion'
+        autocomplete_raw_dropdown:
+          type: integer
+          label: 'Unformatted dropdown'
+        autocomplete_dependent:
+          type: integer
+          label: 'Suggestions depend on other filter fields'
+
+views.filter.views_autocomplete_filters_search_api_fulltext:
+  type: views.filter.search_api_fulltext
+  label: 'Autocomplete for Search API fulltext search filter'
+  mapping:
+    expose:
+      type: mapping
+      label: 'Exposed'
+      mapping:
+        autocomplete_filter:
+          type: integer
+          label: 'Use Autocomplete'
+        autocomplete_min_chars:
+          type: string
+          label: 'Minimum number of characters to start filter'
+        autocomplete_items:
+          type: string
+          label: 'Maximum number of items in Autocomplete'
+        autocomplete_raw_suggestion:
+          type: integer
+          label: 'Unformatted suggestion'
+        autocomplete_raw_dropdown:
+          type: integer
+          label: 'Unformatted dropdown'
+        autocomplete_dependent:
+          type: integer
+          label: 'Suggestions depend on other filter fields'
+
+views.filter.views_autocomplete_filters_search_api_text:
+  type: views.filter.search_api_text
+  label: 'Autocomplete for Search API text field filter'
+  mapping:
+    expose:
+      type: mapping
+      label: 'Exposed'
+      mapping:
+        autocomplete_filter:
+          type: integer
+          label: 'Use Autocomplete'
+        autocomplete_min_chars:
+          type: string
+          label: 'Minimum number of characters to start filter'
+        autocomplete_items:
+          type: string
+          label: 'Maximum number of items in Autocomplete'
+        autocomplete_field:
+          type: string
+          label: 'Field with autocomplete results'
+        autocomplete_raw_suggestion:
+          type: integer
+          label: 'Unformatted suggestion'
+        autocomplete_raw_dropdown:
+          type: integer
+          label: 'Unformatted dropdown'
+        autocomplete_dependent:
+          type: integer
+          label: 'Suggestions depend on other filter fields'
+
+views.filter.views_autocomplete_filters_string:
+  type: views.filter.string
+  label: 'Autocomplete for basic textfield filter'
+  mapping:
+    expose:
+      type: mapping
+      label: 'Exposed'
+      mapping:
+        autocomplete_filter:
+          type: integer
+          label: 'Use Autocomplete'
+        autocomplete_min_chars:
+          type: string
+          label: 'Minimum number of characters to start filter'
+        autocomplete_items:
+          type: string
+          label: 'Maximum number of items in Autocomplete'
+        autocomplete_field:
+          type: string
+          label: 'Field with autocomplete results'
+        autocomplete_raw_suggestion:
+          type: integer
+          label: 'Unformatted suggestion'
+        autocomplete_raw_dropdown:
+          type: integer
+          label: 'Unformatted dropdown'
+        autocomplete_dependent:
+          type: integer
+          label: 'Suggestions depend on other filter fields'
diff --git a/web/modules/views_autocomplete_filters/js/views-autocomplete-filters-dependent.js b/web/modules/views_autocomplete_filters/js/views-autocomplete-filters-dependent.js
index c2a194bc55..9e81b71b4c 100644
--- a/web/modules/views_autocomplete_filters/js/views-autocomplete-filters-dependent.js
+++ b/web/modules/views_autocomplete_filters/js/views-autocomplete-filters-dependent.js
@@ -1,78 +1,297 @@
-(function ($) {
-
 /**
- * Overrides function from misc/autocomplete.js to send full form values instead
- * of just autocomplete value.
+ * @file
+ * Autocomplete based on jQuery UI.
  */
-Drupal.ACDB.prototype.search = function (searchString) {
-  var db = this;
-  this.searchString = searchString;
-
-  // See if this string needs to be searched for anyway.
-  searchString = searchString.replace(/^\s+|\s+$/, '');
-  if (searchString.length <= 0 ||
-    searchString.charAt(searchString.length - 1) == ',') {
-    return;
-  }
 
-  // See if this key has been searched for before.
-  if (this.cache[searchString]) {
-    return this.owner.found(this.cache[searchString]);
+(function ($, Drupal) {
+
+  'use strict';
+
+  var autocomplete;
+
+  /**
+   * Helper splitting terms from the autocomplete value.
+   *
+   * @function Drupal.autocomplete.splitValues
+   *
+   * @param {string} value
+   *   The value being entered by the user.
+   *
+   * @return {Array}
+   *   Array of values, split by comma.
+   */
+  function autocompleteSplitValues(value) {
+    // We will match the value against comma-separated terms.
+    var result = [];
+    var quote = false;
+    var current = '';
+    var valueLength = value.length;
+    var character;
+
+    for (var i = 0; i < valueLength; i++) {
+      character = value.charAt(i);
+      if (character === '"') {
+        current += character;
+        quote = !quote;
+      }
+      else if (character === ',' && !quote) {
+        result.push(current.trim());
+        current = '';
+      }
+      else {
+        current += character;
+      }
+    }
+    if (value.length > 0) {
+      result.push($.trim(current));
+    }
+
+    return result;
   }
 
-  // Fill data with form values if we're working with dependent autocomplete
-  var data = '';
-  if (this.owner.isDependent()) {
-    data = this.owner.serializeOuterForm();
+  /**
+   * Returns the last value of an multi-value textfield.
+   *
+   * @function Drupal.autocomplete.extractLastTerm
+   *
+   * @param {string} terms
+   *   The value of the field.
+   *
+   * @return {string}
+   *   The last value of the input field.
+   */
+  function extractLastTerm(terms) {
+    return autocomplete.splitValues(terms).pop();
   }
 
-  // Initiate delayed search.
-  if (this.timer) {
-    clearTimeout(this.timer);
+  /**
+   * The search handler is called before a search is performed.
+   *
+   * @function Drupal.autocomplete.options.search
+   *
+   * @param {object} event
+   *   The event triggered.
+   *
+   * @return {bool}
+   *   Whether to perform a search or not.
+   */
+  function searchHandler(event) {
+    var options = autocomplete.options;
+    var term = autocomplete.extractLastTerm(event.target.value);
+    // Abort search if the first character is in firstCharacterBlacklist.
+    if (term.length > 0 && options.firstCharacterBlacklist.indexOf(term[0]) !== -1) {
+      return false;
+    }
+    // Only search when the term is at least the minimum length.
+    return term.length >= options.minLength;
   }
-  this.timer = setTimeout(function () {
-    db.owner.setStatus('begin');
-
-    // Ajax GET request for autocompletion. We use Drupal.encodePath instead of
-    // encodeURIComponent to allow autocomplete search terms to contain slashes.
-    $.ajax({
-      type: 'GET',
-      url: db.uri + '/' + Drupal.encodePath(searchString),
-      data: data,
-      dataType: 'json',
-      success: function (matches) {
-        if (typeof matches.status == 'undefined' || matches.status != 0) {
-          db.cache[searchString] = matches;
-          // Verify if these are still the matches the user wants to see.
-          if (db.searchString == searchString) {
-            db.owner.found(matches);
-          }
-          db.owner.setStatus('found');
+
+  /**
+   * JQuery UI autocomplete source callback.
+   *
+   * @param {object} request
+   *   The request object.
+   * @param {function} response
+   *   The function to call with the response.
+   */
+  function sourceData(request, response) {
+    var elementId = this.element.attr('id');
+
+    if (!(elementId in autocomplete.cache)) {
+      autocomplete.cache[elementId] = {};
+    }
+
+    /**
+     * Filter through the suggestions removing all terms already tagged and
+     * display the available terms to the user.
+     *
+     * @param {object} suggestions
+     *   Suggestions returned by the server.
+     */
+    function showSuggestions(suggestions) {
+      var tagged = autocomplete.splitValues(request.term);
+      var il = tagged.length;
+      for (var i = 0; i < il; i++) {
+        var index = suggestions.toString().indexOf(tagged[i]);
+        if (index >= 0) {
+          suggestions.splice(index, 1);
         }
-      },
-      error: function (xmlhttp) {
-        alert(Drupal.ajaxError(xmlhttp, db.uri));
       }
-    });
-  }, this.delay);
-};
+      response(suggestions);
+    }
 
-/**
- * Function which checks if autocomplete depends on other filter fields.
- */
-Drupal.jsAC.prototype.isDependent = function() {
-  return $(this.input).hasClass('views-ac-dependent-filter');
-};
+    /**
+     * Transforms the data object into an array and update autocomplete results.
+     *
+     * @param {object} data
+     *   The data sent back from the server.
+     */
+    function sourceCallbackHandler(data) {
+      autocomplete.cache[elementId][term] = data;
 
-/**
- * Returns serialized input values from form except autocomplete input.
- */
-Drupal.jsAC.prototype.serializeOuterForm = function() {
-  return $(this.input)
-    .parents('form:first')
-    .find('select[name], textarea[name], input[name][type!=submit]')
-    .not(this.input)
-    .serialize();
-};
-
-})(jQuery);
\ No newline at end of file
+      // Send the new string array of terms to the jQuery UI list.
+      showSuggestions(data);
+    }
+
+    // Get the desired term and construct the autocomplete URL for it.
+    var term = autocomplete.extractLastTerm(request.term);
+
+    // Check if the term is already cached.
+    if (autocomplete.cache[elementId].hasOwnProperty(term)) {
+      showSuggestions(autocomplete.cache[elementId][term]);
+    }
+    else {
+      var data_string = [];
+      data_string.success = sourceCallbackHandler;
+      data_string.data = {};
+      data_string.data['q'] = term;
+
+      if (Drupal.isDependent(this.element)) {
+        var a = Drupal.serializeOuterForm(this.element);
+        $.each(a, function (key, value) {
+          data_string.data[value['name']] = value['value'];
+        });
+      }
+      var options = $.extend(data_string, autocomplete.ajax);
+      $.ajax(this.element.attr('data-autocomplete-path'), options);
+    }
+  }
+
+  /**
+   * Handles an autocompletefocus event.
+   *
+   * @return {bool}
+   *   Always returns false.
+   */
+  function focusHandler() {
+    return false;
+  }
+
+  /**
+   * Handles an autocompleteselect event.
+   *
+   * @param {jQuery.Event} event
+   *   The event triggered.
+   * @param {object} ui
+   *   The jQuery UI settings object.
+   *
+   * @return {bool}
+   *   Returns false to indicate the event status.
+   */
+  function selectHandler(event, ui) {
+    var terms = autocomplete.splitValues(event.target.value);
+    // Remove the current input.
+    terms.pop();
+    // Add the selected item.
+    terms.push(ui.item.value);
+    event.target.value = terms.join(', ');
+    // Return false to tell jQuery UI that we've filled in the value already.
+    return false;
+  }
+
+  /**
+   * Override jQuery UI _renderItem function to output HTML by default.
+   *
+   * @param {jQuery} ul
+   *   jQuery collection of the ul element.
+   * @param {object} item
+   *   The list item to append.
+   *
+   * @return {jQuery}
+   *   jQuery collection of the ul element.
+   */
+  function renderItem(ul, item) {
+    return $('<li>')
+      .append($('<a>').html(item.label))
+      .appendTo(ul);
+  }
+
+  /**
+   * Attaches the autocomplete behavior to all required fields.
+   *
+   * @type {Drupal~behavior}
+   *
+   * @prop {Drupal~behaviorAttach} attach
+   *   Attaches the autocomplete behaviors.
+   * @prop {Drupal~behaviorDetach} detach
+   *   Detaches the autocomplete behaviors.
+   */
+  Drupal.behaviors.autocomplete = {
+    attach: function (context) {
+      // Act on textfields with the "form-autocomplete" class.
+      var $autocomplete = $(context).find('input.form-autocomplete').once('autocomplete');
+      if ($autocomplete.length) {
+        // Allow options to be overriden per instance.
+        var blacklist = $autocomplete.attr('data-autocomplete-first-character-blacklist');
+        $.extend(autocomplete.options, {
+          firstCharacterBlacklist: (blacklist) ? blacklist : ''
+        });
+        // Use jQuery UI Autocomplete on the textfield.
+        $autocomplete.autocomplete(autocomplete.options)
+          .each(function () {
+            $(this).data('ui-autocomplete')._renderItem = autocomplete.options.renderItem;
+          });
+      }
+    },
+    detach: function (context, settings, trigger) {
+      if (trigger === 'unload') {
+        $(context).find('input.form-autocomplete')
+          .removeOnce('autocomplete')
+          .autocomplete('destroy');
+      }
+    }
+  };
+
+  /**
+   * Autocomplete object implementation.
+   *
+   * @namespace Drupal.autocomplete
+   */
+  autocomplete = {
+    cache: {},
+    // Exposes options to allow overriding by contrib.
+    splitValues: autocompleteSplitValues,
+    extractLastTerm: extractLastTerm,
+    // jQuery UI autocomplete options.
+
+    /**
+     * JQuery UI option object.
+     *
+     * @name Drupal.autocomplete.options
+     */
+    options: {
+      source: sourceData,
+      focus: focusHandler,
+      search: searchHandler,
+      select: selectHandler,
+      renderItem: renderItem,
+      minLength: 1,
+      // Custom options, used by Drupal.autocomplete.
+      firstCharacterBlacklist: ''
+    },
+    ajax: {
+      dataType: 'json'
+    }
+  };
+
+  Drupal.autocomplete = autocomplete;
+
+  /**
+   * Function which checks if autocomplete depends on other filter fields.
+   */
+  Drupal.isDependent = function (element) {
+    return $(element).hasClass('views-ac-dependent-filter');
+  };
+
+  /**
+   * Returns serialized input values from form except autocomplete input.
+   */
+  Drupal.serializeOuterForm = function (element) {
+    return $(element)
+      .parents('form:first')
+      .find('select[name], textarea[name], input[name][type!=submit]')
+      .not(this.input)
+      .serializeArray();
+  };
+
+})(jQuery, Drupal);
diff --git a/web/modules/views_autocomplete_filters/js/views-autocomplete-filters.js b/web/modules/views_autocomplete_filters/js/views-autocomplete-filters.js
new file mode 100644
index 0000000000..a1394fbdbe
--- /dev/null
+++ b/web/modules/views_autocomplete_filters/js/views-autocomplete-filters.js
@@ -0,0 +1,21 @@
+/**
+ * @file
+ * Expands the behaviour of the default autocompletion.
+ */
+
+(function ($, Drupal) {
+
+  // Override the "select" option of the jQueryUI autocomplete
+  // to make sure we do not use quotes for inputs with comma.
+  Drupal.autocomplete.options.select = function (event, ui) {
+    var terms = Drupal.autocomplete.splitValues(event.target.value);
+    // Remove the current input.
+    terms.pop();
+    // Add the selected item.
+    terms.push(ui.item.value);
+    event.target.value = terms.join(', ');
+    // Return false to tell jQuery UI that we've filled in the value already.
+    return false;
+  };
+
+})(jQuery, Drupal);
diff --git a/web/modules/views_autocomplete_filters/src/Controller/ViewsAutocompleteFiltersController.php b/web/modules/views_autocomplete_filters/src/Controller/ViewsAutocompleteFiltersController.php
index 9b0d2b74e6..5d7fc54da2 100644
--- a/web/modules/views_autocomplete_filters/src/Controller/ViewsAutocompleteFiltersController.php
+++ b/web/modules/views_autocomplete_filters/src/Controller/ViewsAutocompleteFiltersController.php
@@ -1,10 +1,5 @@
 <?php
 
-/**
- * @file
- * Contains \Drupal\views_autocomplete_filters\Controller\ViewsAutocompleteFiltersController.
- */
-
 namespace Drupal\views_autocomplete_filters\Controller;
 
 use Drupal\Component\Utility\Html;
@@ -12,6 +7,7 @@
 use Drupal\Core\Access\AccessResult;
 use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
 use Drupal\views\Views;
+use Psr\Log\LoggerInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpFoundation\JsonResponse;
@@ -22,11 +18,29 @@
  */
 class ViewsAutocompleteFiltersController implements ContainerInjectionInterface {
 
+  /**
+   * @var \Psr\Log\LoggerInterface
+   */
+  protected $logger;
+
+
+  /**
+   * ViewsAutocompleteFiltersController constructor.
+   *
+   * @param \Psr\Log\LoggerInterface $logger
+   */
+  public function __construct(LoggerInterface $logger) {
+    $this->logger = $logger;
+  }
+
+
   /**
    * {@inheritdoc}
    */
   public static function create(ContainerInterface $container) {
-    return new static();
+    return new static(
+      $container->get('logger.factory')->get('views_autocomplete_filters')
+    );
   }
 
   /**
@@ -75,7 +89,7 @@ public function access($view_name, $view_display) {
    *   containing a failure message.
    */
   public function autocomplete(Request $request, $view_name, $view_display, $filter_name, $view_args) {
-    $matches = [];
+    $matches = $field_names = [];
     $string = $request->query->get('q');
     // Get view and execute.
     $view = Views::getView($view_name);
@@ -103,8 +117,8 @@ public function autocomplete(Request $request, $view_name, $view_display, $filte
     if (empty($filters[$filter_name]['exposed']) || empty($filters[$filter_name]['expose']['autocomplete_filter'])) {
       throw new NotFoundHttpException();
     }
-    $filter = $filters[$filter_name];
-    $expose_options = $filter['expose'];
+    $current_filter = $filters[$filter_name];
+    $expose_options = $current_filter['expose'];
 
     // Do not filter if the string length is less that minimum characters setting.
     if (strlen(trim($string)) < $expose_options['autocomplete_min_chars']) {
@@ -112,20 +126,20 @@ public function autocomplete(Request $request, $view_name, $view_display, $filte
     }
 
     // Determine fields which will be used for output.
-    if (empty($expose_options['autocomplete_field']) && !empty($filter['name']) ) {
-      if ($view->getHandler($display_name, 'field', $filters[$filter_name]['id'])) {
+    if (empty($expose_options['autocomplete_field']) && !empty($current_filter['name']) ) {
+      if ($view->getHandler($view->current_display, 'field', $filters[$filter_name]['id'])) {
         $field_names = [[$filter_name]['id']];
-        // force raw data for no autocomplete field defined.
+        // Force raw data for no autocomplete field defined.
         $expose_options['autocomplete_raw_suggestion'] = 1;
         $expose_options['autocomplete_raw_dropdown'] = 1;
       }
       else {
         // Field is not set, report about it to watchdog and return empty array.
-        watchdog('views_autocomplete_filters', 'Field for autocomplete filter %label is not set in view %view, display %display', [
+        $this->logger->error('Field for autocomplete filter %label is not set in view %view, display %display', [
           '%label' => $expose_options['label'],
-          '%view' => $view->name,
-          '%display' => $display->id,
-        ], WATCHDOG_ERROR);
+          '%view' => $view->id(),
+          '%display' => $view->current_display,
+        ]);
         return new JsonResponse($matches);
       }
     }
@@ -134,20 +148,20 @@ public function autocomplete(Request $request, $view_name, $view_display, $filte
       $field_names =[$expose_options['autocomplete_field']];
     }
     // For combine fields autocomplete filter.
-    elseif (!empty($filter['fields'])) {
-      $field_names = array_keys($filter['fields']);
+    elseif (!empty($current_filter['fields'])) {
+      $field_names = array_keys($current_filter['fields']);
     }
 
     // Get fields options and check field exists in this display.
     foreach ($field_names as $field_name) {
-      $field_options = $view->getHandler($view_display, 'field', $field_name);
+      $field_options = $view->getHandler($view->current_display, 'field', $field_name);
       if (empty($field_options)) {
         // Field not exists, report about it to watchdog and return empty array.
-        watchdog('views_autocomplete_filters', 'Field for autocomplete filter %label not exists in view %view, display %display', [
+        $this->logger->error('Field for autocomplete filter %label not exists in view %view, display %display', [
           '%label' => $expose_options['label'],
-          '%view' => $view->name,
-          '%display' => $display->id,
-        ], WATCHDOG_ERROR);
+          '%view' => $view->id(),
+          '%display' => $view->current_display,
+        ]);
         return new JsonResponse($matches);
       }
     }
@@ -194,6 +208,7 @@ public function autocomplete(Request $request, $view_name, $view_display, $filte
     foreach ($view->result as $index => $row) {
       $view->row_index = $index;
       $rendered_field = $raw_field = '';
+      /** @var \Drupal\views\Plugin\views\style\StylePluginBase $style_plugin */
       $style_plugin = $display_handler->getPlugin('style');
   
       foreach ($field_names as $field_name) {
@@ -207,6 +222,13 @@ public function autocomplete(Request $request, $view_name, $view_display, $filte
           if (!is_array($raw_field)) {
             $raw_field = [['value' => $raw_field]];
           }
+          else {
+            $raw_field_items = $raw_field;
+            $raw_field = [];
+            foreach ($raw_field_items as $raw_field_item) {
+              $raw_field[] = ['value' => $raw_field_item];
+            }
+          }
         }
   
         if (empty($raw_field)) {
@@ -236,7 +258,7 @@ public function autocomplete(Request $request, $view_name, $view_display, $filte
     // provide a solution for such messages.
 
     if (!empty($matches)) {
-      $matches = array_unique($matches, SORT_REGULAR);
+      $matches = array_values(array_unique($matches, SORT_REGULAR));
     }
 
     return new JsonResponse($matches);
diff --git a/web/modules/views_autocomplete_filters/src/Plugin/views/filter/ViewsAutocompleteFiltersCombine.php b/web/modules/views_autocomplete_filters/src/Plugin/views/filter/ViewsAutocompleteFiltersCombine.php
index acca936002..2858405b56 100644
--- a/web/modules/views_autocomplete_filters/src/Plugin/views/filter/ViewsAutocompleteFiltersCombine.php
+++ b/web/modules/views_autocomplete_filters/src/Plugin/views/filter/ViewsAutocompleteFiltersCombine.php
@@ -1,13 +1,7 @@
 <?php
 
-/**
- * @file
- * Definition of Drupal\views_autocomplete_filters\Plugin\views\filter\ViewsAutocompleteFiltersCombine.
- */
-
 namespace Drupal\views_autocomplete_filters\Plugin\views\filter;
 
-use Drupal\Core\Form\FormStateInterface;
 use Drupal\views\Plugin\views\filter\Combine;
 
 /**
@@ -18,127 +12,18 @@
  *
  * @ViewsFilter("views_autocomplete_filters_combine")
  */
-class ViewsAutocompleteFiltersCombine extends Combine {
+class ViewsAutocompleteFiltersCombine extends Combine implements ViewsAutocompleteFiltersInterface {
 
   // Exposed filter options.
   var $alwaysMultiple = TRUE;
 
-  protected function defineOptions() {
-    $options = parent::defineOptions();
-
-    $options['expose']['contains']['required'] = ['default' => FALSE, 'bool' => TRUE];
-    $options['expose']['contains'] += [
-      'autocomplete_filter' => ['default' => 0],
-      'autocomplete_min_chars' => ['default' => 0],
-      'autocomplete_items' => ['default' => 10],
-      'autocomplete_raw_suggestion' => ['default' => TRUE],
-      'autocomplete_raw_dropdown' => ['default' => TRUE],
-      'autocomplete_dependent' => ['default' => FALSE],
-    ];
-
-    return $options;
-  }
+  use ViewsAutocompleteFiltersTrait;
 
   /**
-   * Build the options form.
+   * {@inheritdoc}
    */
-  public function buildOptionsForm(&$form, FormStateInterface $form_state) {
-    parent::buildOptionsForm($form, $form_state);
-
-    if ($this->canExpose() && !empty($form['expose'])) {
-      $field_options_all = $this->view->display_handler->getFieldLabels();
-      // Limit options to fields with the same name.
-      foreach ($this->view->display_handler->getHandlers('field') as $id => $handler) {
-        if ($handler->realField == $this->realField) {
-          $field_options[$id] = $field_options_all[$id];
-        }
-      }
-      if (empty($field_options)) {
-        $field_options[''] = $this->t('Add some fields to view');
-      }
-
-      // Build form elements for the right side of the exposed filter form
-      $states = [
-        'visible' => ['
-            :input[name="options[expose][autocomplete_filter]"]' => ['checked' => TRUE],
-        ],
-      ];
-      $form['expose'] += [
-        'autocomplete_filter' => [
-          '#type' => 'checkbox',
-          '#title' => $this->t('Use Autocomplete'),
-          '#default_value' => $this->options['expose']['autocomplete_filter'],
-          '#description' => $this->t('Use Autocomplete for this filter.'),
-        ],
-        'autocomplete_items' => [
-          '#type' => 'textfield',
-          '#title' => $this->t('Maximum number of items in Autocomplete'),
-          '#default_value' => $this->options['expose']['autocomplete_items'],
-          '#description' => $this->t('Enter 0 for no limit.'),
-          '#states' => $states,
-        ],
-        'autocomplete_min_chars' => [
-          '#type' => 'textfield',
-          '#title' => t('Minimum number of characters to start filter'),
-          '#default_value' => $this->options['expose']['autocomplete_min_chars'],
-          '#element_validate' => ['element_validate_integer'],
-          '#states' => $states,
-        ],
-        'autocomplete_dependent' => [
-          '#type' => 'checkbox',
-          '#title' => $this->t('Suggestions depend on other filter fields'),
-          '#default_value' => $this->options['expose']['autocomplete_dependent'],
-          '#description' => $this->t('Autocomplete suggestions will be filtered by other filter fields'),
-          '#states' => $states,
-        ],
-        'autocomplete_raw_dropdown' => [
-          '#type' => 'checkbox',
-          '#title' => $this->t('Unformatted dropdown'),
-          '#default_value' => $this->options['expose']['autocomplete_raw_dropdown'],
-          '#description' => $this->t('Use unformatted data from database for dropdown list instead of field formatter result. Value will be printed as plain text.'),
-          '#states' => $states,
-        ],
-        'autocomplete_raw_suggestion' => [
-          '#type' => 'checkbox',
-          '#title' => $this->t('Unformatted suggestion'),
-          '#default_value' => $this->options['expose']['autocomplete_raw_suggestion'],
-          '#description' => $this->t('The same as above, but for suggestion (text appearing inside textfield when item is selected).'),
-          '#states' => $states,
-        ],
-      ];
-    }
-  }
-
-  public function valueForm(&$form, FormStateInterface $form_state) {
-    parent::valueForm($form, $form_state);
-    $exposed = $form_state->get('exposed');
-    if (!$exposed || empty($this->options['expose']['autocomplete_filter'])) {
-      // It is not an exposed form or autocomplete is not enabled.
-      return;
-    }
-
-    if (empty($form['value']['#type']) || $form['value']['#type'] !== 'textfield') {
-      // Not a textfield.
-      return;
-    }
-
-    // Add autocomplete path to the exposed textfield.
-    $view_args = !empty($this->view->args) ? implode('||', $this->view->args) : 0;
-    $form['value']['#autocomplete_route_name'] = 'viewsfilters.autocomplete';
-    $form['value']['#autocomplete_route_parameters'] = [
-      'view_name' => $this->view->storage->get('id'),
-      'view_display' => $this->view->current_display,
-      'filter_name' => $this->options['id'],
-      'view_args' => $view_args,
-    ];
-
-    // Add JS script with core autocomplete overrides to the end of JS files
-    // list to be sure it is added after the "misc/autocomplete.js" file. Also
-    // mark the field with special class.
-    if (!empty($this->options['expose']['autocomplete_dependent'])) {
-      $form['#attached']['library'][] = 'views_autocomplete_filters/drupal.views-autocomplete-filters-dependent';
-      $form['value']['#attributes']['class'][] = 'views-ac-dependent-filter';
-    }
+  public function hasAutocompleteFieldSelector() {
+    return FALSE;
   }
 
 }
diff --git a/web/modules/views_autocomplete_filters/src/Plugin/views/filter/ViewsAutocompleteFiltersInterface.php b/web/modules/views_autocomplete_filters/src/Plugin/views/filter/ViewsAutocompleteFiltersInterface.php
new file mode 100644
index 0000000000..34b3f4d7b4
--- /dev/null
+++ b/web/modules/views_autocomplete_filters/src/Plugin/views/filter/ViewsAutocompleteFiltersInterface.php
@@ -0,0 +1,17 @@
+<?php
+
+namespace Drupal\views_autocomplete_filters\Plugin\views\filter;
+
+/**
+ * Define extra methods for Autocomplete filters.
+ */
+interface ViewsAutocompleteFiltersInterface {
+
+  /**
+   * Returns of the handler has 'autocomplete_field' selector.
+   *
+   * @return boolean
+   */
+  public function hasAutocompleteFieldSelector();
+
+}
diff --git a/web/modules/views_autocomplete_filters/src/Plugin/views/filter/ViewsAutocompleteFiltersSearchApiFulltext.php b/web/modules/views_autocomplete_filters/src/Plugin/views/filter/ViewsAutocompleteFiltersSearchApiFulltext.php
new file mode 100644
index 0000000000..c9dfd86fd1
--- /dev/null
+++ b/web/modules/views_autocomplete_filters/src/Plugin/views/filter/ViewsAutocompleteFiltersSearchApiFulltext.php
@@ -0,0 +1,29 @@
+<?php
+
+namespace Drupal\views_autocomplete_filters\Plugin\views\filter;
+
+use Drupal\search_api\Plugin\views\filter\SearchApiFulltext;
+
+/**
+ * Autocomplete for Search API fulltext search for the view to handle fulltext
+ * search filtering.
+ *
+ * @ingroup views_filter_handlers
+ *
+ * @ViewsFilter("views_autocomplete_filters_search_api_fulltext")
+ */
+class ViewsAutocompleteFiltersSearchApiFulltext extends SearchApiFulltext implements ViewsAutocompleteFiltersInterface {
+
+  // Exposed filter options.
+  var $alwaysMultiple = TRUE;
+
+  use ViewsAutocompleteFiltersTrait;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function hasAutocompleteFieldSelector() {
+    return FALSE;
+  }
+
+}
diff --git a/web/modules/views_autocomplete_filters/src/Plugin/views/filter/ViewsAutocompleteFiltersSearchApiText.php b/web/modules/views_autocomplete_filters/src/Plugin/views/filter/ViewsAutocompleteFiltersSearchApiText.php
new file mode 100644
index 0000000000..ac9484ecdc
--- /dev/null
+++ b/web/modules/views_autocomplete_filters/src/Plugin/views/filter/ViewsAutocompleteFiltersSearchApiText.php
@@ -0,0 +1,21 @@
+<?php
+
+namespace Drupal\views_autocomplete_filters\Plugin\views\filter;
+
+use Drupal\search_api\Plugin\views\filter\SearchApiText;
+
+/**
+ * Autocomplete for Search API fulltext fields to handle fulltext filtering.
+ *
+ * @ingroup views_filter_handlers
+ *
+ * @ViewsFilter("views_autocomplete_filters_search_api_text")
+ */
+class ViewsAutocompleteFiltersSearchApiText extends SearchApiText implements ViewsAutocompleteFiltersInterface {
+
+  // Exposed filter options.
+  var $alwaysMultiple = TRUE;
+
+  use ViewsAutocompleteFiltersTrait;
+
+}
diff --git a/web/modules/views_autocomplete_filters/src/Plugin/views/filter/ViewsAutocompleteFiltersString.php b/web/modules/views_autocomplete_filters/src/Plugin/views/filter/ViewsAutocompleteFiltersString.php
index 23181ea16b..96e3ba290f 100644
--- a/web/modules/views_autocomplete_filters/src/Plugin/views/filter/ViewsAutocompleteFiltersString.php
+++ b/web/modules/views_autocomplete_filters/src/Plugin/views/filter/ViewsAutocompleteFiltersString.php
@@ -1,13 +1,7 @@
 <?php
 
-/**
- * @file
- * Definition of Drupal\views_autocomplete_filters\Plugin\views\filter\ViewsAutocompleteFiltersString.
- */
-
 namespace Drupal\views_autocomplete_filters\Plugin\views\filter;
 
-use Drupal\Core\Form\FormStateInterface;
 use Drupal\views\Plugin\views\filter\StringFilter;
 
 /**
@@ -18,139 +12,11 @@
  *
  * @ViewsFilter("views_autocomplete_filters_string")
  */
-class ViewsAutocompleteFiltersString extends StringFilter {
+class ViewsAutocompleteFiltersString extends StringFilter implements ViewsAutocompleteFiltersInterface {
 
   // Exposed filter options.
   var $alwaysMultiple = TRUE;
 
-  protected function defineOptions() {
-    $options = parent::defineOptions();
-
-    $options['expose']['contains']['required'] = ['default' => FALSE, 'bool' => TRUE];
-    $options['expose']['contains'] += [
-      'autocomplete_filter' => ['default' => 0],
-      'autocomplete_min_chars' => ['default' => 0],
-      'autocomplete_items' => ['default' => 10],
-      'autocomplete_field' => ['default' => ''],
-      'autocomplete_raw_suggestion' => ['default' => TRUE],
-      'autocomplete_raw_dropdown' => ['default' => TRUE],
-      'autocomplete_dependent' => ['default' => FALSE],
-    ];
-
-    return $options;
-  }
-
-  /**
-   * Build the options form.
-   */
-  public function buildOptionsForm(&$form, FormStateInterface $form_state) {
-    parent::buildOptionsForm($form, $form_state);
-
-    if ($this->canExpose() && !empty($form['expose'])) {
-      $field_options_all = $this->view->display_handler->getFieldLabels();
-      // Limit options to fields with the same name.
-      foreach ($this->view->display_handler->getHandlers('field') as $id => $handler) {
-        if ($handler->realField == $this->realField) {
-          $field_options[$id] = $field_options_all[$id];
-        }
-      }
-      if (empty($field_options)) {
-        $field_options[''] = $this->t('Add some fields to view');
-      }
-      elseif (empty($this->options['expose']['autocomplete_field']) && !empty($field_options[$this->options['id']])) {
-        $this->options['expose']['autocomplete_field'] = $this->options['id'];
-      }
-
-      // Build form elements for the right side of the exposed filter form.
-      $states = [
-        'visible' => ['
-            :input[name="options[expose][autocomplete_filter]"]' => ['checked' => TRUE],
-        ],
-      ];
-      $form['expose'] += [
-        'autocomplete_filter' => [
-          '#type' => 'checkbox',
-          '#title' => $this->t('Use Autocomplete'),
-          '#default_value' => $this->options['expose']['autocomplete_filter'],
-          '#description' => $this->t('Use Autocomplete for this filter.'),
-        ],
-        'autocomplete_items' => [
-          '#type' => 'textfield',
-          '#title' => $this->t('Maximum number of items in Autocomplete'),
-          '#default_value' => $this->options['expose']['autocomplete_items'],
-          '#description' => $this->t('Enter 0 for no limit.'),
-          '#states' => $states,
-        ],
-        'autocomplete_min_chars' => [
-          '#type' => 'textfield',
-          '#title' => t('Minimum number of characters to start filter'),
-          '#default_value' => $this->options['expose']['autocomplete_min_chars'],
-          '#element_validate' => ['element_validate_integer'],
-          '#states' => $states,
-        ],
-        'autocomplete_dependent' => [
-          '#type' => 'checkbox',
-          '#title' => $this->t('Suggestions depend on other filter fields'),
-          '#default_value' => $this->options['expose']['autocomplete_dependent'],
-          '#description' => $this->t('Autocomplete suggestions will be filtered by other filter fields'),
-          '#states' => $states,
-        ],
-        'autocomplete_field' => [
-          '#type' => 'select',
-          '#title' => $this->t('Field with autocomplete results'),
-          '#default_value' => $this->options['expose']['autocomplete_field'],
-          '#options' => $field_options,
-          '#description' => $this->t('Selected field will be used for dropdown results of autocomplete. In most cases it should be the same field you use for filter.'),
-          '#states' => $states,
-        ],
-        'autocomplete_raw_dropdown' => [
-          '#type' => 'checkbox',
-          '#title' => $this->t('Unformatted dropdown'),
-          '#default_value' => $this->options['expose']['autocomplete_raw_dropdown'],
-          '#description' => $this->t('Use unformatted data from database for dropdown list instead of field formatter result. Value will be printed as plain text.'),
-          '#states' => $states,
-        ],
-        'autocomplete_raw_suggestion' => [
-          '#type' => 'checkbox',
-          '#title' => $this->t('Unformatted suggestion'),
-          '#default_value' => $this->options['expose']['autocomplete_raw_suggestion'],
-          '#description' => $this->t('The same as above, but for suggestion (text appearing inside textfield when item is selected).'),
-          '#states' => $states,
-        ],
-      ];
-    }
-  }
-
-  public function valueForm(&$form, FormStateInterface $form_state) {
-    parent::valueForm($form, $form_state);
-    $exposed = $form_state->get('exposed');
-    if (!$exposed || empty($this->options['expose']['autocomplete_filter'])) {
-      // It is not an exposed form or autocomplete is not enabled.
-      return;
-    }
-
-    if (empty($form['value']['#type']) || $form['value']['#type'] !== 'textfield') {
-      // Not a textfield.
-      return;
-    }
-
-    // Add autocomplete path to the exposed textfield.
-    $view_args = !empty($this->view->args) ? implode('||', $this->view->args) : 0;
-    $form['value']['#autocomplete_route_name'] = 'viewsfilters.autocomplete';
-    $form['value']['#autocomplete_route_parameters'] = [
-      'view_name' => $this->view->storage->get('id'),
-      'view_display' => $this->view->current_display,
-      'filter_name' => $this->options['id'],
-      'view_args' => $view_args,
-    ];
-
-    // Add JS script with core autocomplete overrides to the end of JS files
-    // list to be sure it is added after the "misc/autocomplete.js" file. Also
-    // mark the field with special class.
-    if (!empty($this->options['expose']['autocomplete_dependent'])) {
-      $form['#attached']['library'][] = 'views_autocomplete_filters/drupal.views-autocomplete-filters-dependent';
-      $form['value']['#attributes']['class'][] = 'views-ac-dependent-filter';
-    }
-  }
+  use ViewsAutocompleteFiltersTrait;
 
 }
diff --git a/web/modules/views_autocomplete_filters/src/Plugin/views/filter/ViewsAutocompleteFiltersTrait.php b/web/modules/views_autocomplete_filters/src/Plugin/views/filter/ViewsAutocompleteFiltersTrait.php
new file mode 100644
index 0000000000..f737d3e3e7
--- /dev/null
+++ b/web/modules/views_autocomplete_filters/src/Plugin/views/filter/ViewsAutocompleteFiltersTrait.php
@@ -0,0 +1,162 @@
+<?php
+
+namespace Drupal\views_autocomplete_filters\Plugin\views\filter;
+
+use Drupal\Core\Form\FormStateInterface;
+
+/**
+ * Provides common methods for all Views Autocomplete Filters.
+ */
+trait ViewsAutocompleteFiltersTrait {
+
+  public function defineOptions() {
+    $options = parent::defineOptions();
+
+    $options['expose']['contains']['required'] = ['default' => FALSE, 'bool' => TRUE];
+    $options['expose']['contains'] += [
+      'autocomplete_filter' => ['default' => 0],
+      'autocomplete_min_chars' => ['default' => 0],
+      'autocomplete_items' => ['default' => 10],
+      'autocomplete_field' => ['default' => ''],
+      'autocomplete_raw_suggestion' => ['default' => TRUE],
+      'autocomplete_raw_dropdown' => ['default' => TRUE],
+      'autocomplete_dependent' => ['default' => FALSE],
+    ];
+
+    if (!$this->hasAutocompleteFieldSelector()) {
+      unset($options['expose']['contains']['autocomplete_field']);
+    }
+
+    return $options;
+  }
+
+  /**
+   * Build the options form.
+   */
+  public function buildOptionsForm(&$form, FormStateInterface $form_state) {
+    parent::buildOptionsForm($form, $form_state);
+
+    if ($this->canExpose() && !empty($form['expose'])) {
+      $field_options_all = $this->view->display_handler->getFieldLabels();
+      // Limit options to fields with the same name.
+      /** @var \Drupal\views\Plugin\views\field\FieldHandlerInterface $handler */
+      foreach ($this->view->display_handler->getHandlers('field') as $id => $handler) {
+        if ($handler->field == $this->realField) {
+          $field_options[$id] = $field_options_all[$id];
+        }
+      }
+      if (empty($field_options)) {
+        $field_options[''] = $this->t('Add some fields to view');
+      }
+      elseif (empty($this->options['expose']['autocomplete_field']) && !empty($field_options[$this->options['id']])) {
+        $this->options['expose']['autocomplete_field'] = $this->options['id'];
+      }
+
+      // Build form elements for the right side of the exposed filter form.
+      $states = [
+        'visible' => ['
+            :input[name="options[expose][autocomplete_filter]"]' => ['checked' => TRUE],
+        ],
+      ];
+      $form['expose'] += [
+        'autocomplete_filter' => [
+          '#type' => 'checkbox',
+          '#title' => $this->t('Use Autocomplete'),
+          '#default_value' => $this->options['expose']['autocomplete_filter'],
+          '#description' => $this->t('Use Autocomplete for this filter.'),
+        ],
+        'autocomplete_items' => [
+          '#type' => 'textfield',
+          '#title' => $this->t('Maximum number of items in Autocomplete'),
+          '#default_value' => $this->options['expose']['autocomplete_items'],
+          '#description' => $this->t('Enter 0 for no limit.'),
+          '#states' => $states,
+        ],
+        'autocomplete_min_chars' => [
+          '#type' => 'textfield',
+          '#title' => t('Minimum number of characters to start filter'),
+          '#default_value' => $this->options['expose']['autocomplete_min_chars'],
+          '#element_validate' => ['element_validate_integer'],
+          '#states' => $states,
+        ],
+        'autocomplete_dependent' => [
+          '#type' => 'checkbox',
+          '#title' => $this->t('Suggestions depend on other filter fields'),
+          '#default_value' => $this->options['expose']['autocomplete_dependent'],
+          '#description' => $this->t('Autocomplete suggestions will be filtered by other filter fields'),
+          '#states' => $states,
+        ],
+        'autocomplete_field' => [
+          '#type' => 'select',
+          '#title' => $this->t('Field with autocomplete results'),
+          '#default_value' => $this->options['expose']['autocomplete_field'],
+          '#options' => $field_options,
+          '#description' => $this->t('Selected field will be used for dropdown results of autocomplete. In most cases it should be the same field you use for filter.'),
+          '#states' => $states,
+        ],
+        'autocomplete_raw_dropdown' => [
+          '#type' => 'checkbox',
+          '#title' => $this->t('Unformatted dropdown'),
+          '#default_value' => $this->options['expose']['autocomplete_raw_dropdown'],
+          '#description' => $this->t('Use unformatted data from database for dropdown list instead of field formatter result. Value will be printed as plain text.'),
+          '#states' => $states,
+        ],
+        'autocomplete_raw_suggestion' => [
+          '#type' => 'checkbox',
+          '#title' => $this->t('Unformatted suggestion'),
+          '#default_value' => $this->options['expose']['autocomplete_raw_suggestion'],
+          '#description' => $this->t('The same as above, but for suggestion (text appearing inside textfield when item is selected).'),
+          '#states' => $states,
+        ],
+      ];
+      if (!$this->hasAutocompleteFieldSelector()) {
+        unset($form['expose']['autocomplete_field']);
+      }
+    }
+  }
+
+  public function valueForm(&$form, FormStateInterface $form_state) {
+    parent::valueForm($form, $form_state);
+    $exposed = $form_state->get('exposed');
+    if (!$exposed || empty($this->options['expose']['autocomplete_filter'])) {
+      // It is not an exposed form or autocomplete is not enabled.
+      return;
+    }
+
+    if (empty($form['value']['#type']) || $form['value']['#type'] !== 'textfield') {
+      // Not a textfield.
+      return;
+    }
+
+    // Add autocomplete path to the exposed textfield.
+    $view_args = !empty($this->view->args) ? implode('||', $this->view->args) : 0;
+    $form['value']['#autocomplete_route_name'] = 'viewsfilters.autocomplete';
+    $form['value']['#autocomplete_route_parameters'] = [
+      'view_name' => $this->view->storage->get('id'),
+      'view_display' => $this->view->current_display,
+      'filter_name' => $this->options['id'],
+      'view_args' => $view_args,
+    ];
+
+    // Add JS script to expands the behaviour of the default autocompletion.
+    // Override the "select" option of the jQueryUI auto-complete for
+    // to make sure we do not use quotes for inputs with comma.
+    $form['#attached']['library'][] = 'views_autocomplete_filters/drupal.views-autocomplete-filters';
+
+    // Add JS script with core autocomplete overrides to the end of JS files
+    // list to be sure it is added after the "misc/autocomplete.js" file. Also
+    // mark the field with special class.
+    if (!empty($this->options['expose']['autocomplete_dependent'])) {
+      $form['#attached']['library'][] = 'views_autocomplete_filters/drupal.views-autocomplete-filters-dependent';
+      $form['value']['#attributes']['class'][] = 'views-ac-dependent-filter';
+    }
+  }
+
+  /**
+   * Autocomplete filters should have 'autocomplete_field' selector by default.
+   */
+  public function hasAutocompleteFieldSelector() {
+    return TRUE;
+  }
+
+}
diff --git a/web/modules/views_autocomplete_filters/views_autocomplete_filters.info.yml b/web/modules/views_autocomplete_filters/views_autocomplete_filters.info.yml
index be1baad746..0b096a291a 100644
--- a/web/modules/views_autocomplete_filters/views_autocomplete_filters.info.yml
+++ b/web/modules/views_autocomplete_filters/views_autocomplete_filters.info.yml
@@ -9,8 +9,8 @@ tags:
   - views
   - utility
 
-# Information added by Drupal.org packaging script on 2016-02-20
-version: '8.x-1.1'
+# Information added by Drupal.org packaging script on 2017-07-15
+version: '8.x-1.2'
 core: '8.x'
 project: 'views_autocomplete_filters'
-datestamp: 1455957542
+datestamp: 1500109745
diff --git a/web/modules/views_autocomplete_filters/views_autocomplete_filters.libraries.yml b/web/modules/views_autocomplete_filters/views_autocomplete_filters.libraries.yml
index f1297d26be..61a3d73d5d 100644
--- a/web/modules/views_autocomplete_filters/views_autocomplete_filters.libraries.yml
+++ b/web/modules/views_autocomplete_filters/views_autocomplete_filters.libraries.yml
@@ -1,4 +1,12 @@
 drupal.views-autocomplete-filters:
+  version: VERSION
+  js:
+    js/views-autocomplete-filters.js: {}
+  dependencies:
+    - core/jquery
+    - core/drupal
+    - core/drupal.form
+drupal.views-autocomplete-filters-dependent:
   version: VERSION
   js:
     js/views-autocomplete-filters-dependent.js: {}
diff --git a/web/modules/views_autocomplete_filters/views_autocomplete_filters.views.inc b/web/modules/views_autocomplete_filters/views_autocomplete_filters.views.inc
index d2fd1420ea..90534e4bc4 100644
--- a/web/modules/views_autocomplete_filters/views_autocomplete_filters.views.inc
+++ b/web/modules/views_autocomplete_filters/views_autocomplete_filters.views.inc
@@ -10,14 +10,22 @@
 function views_autocomplete_filters_views_data_alter(array &$data) {
   foreach ($data as $table_name => $table_data) {
     foreach ($table_data as $field_name => $field_data) {
-      // Extend String filter handler with autocomplete capabilities.
+      // Extend String handler with autocomplete capabilities.
       if (isset($field_data['filter']['id']) && $field_data['filter']['id'] == 'string') {
         $data[$table_name][$field_name]['filter']['id'] = 'views_autocomplete_filters_string';
       }
-      // Extend Combine fields filter handler with autocomplete capabilities.
+      // Extend Combine filter handler with autocomplete capabilities.
       if (isset($field_data['filter']['id']) && $field_data['filter']['id'] == 'combine') {
         $data[$table_name][$field_name]['filter']['id'] = 'views_autocomplete_filters_combine';
       }
+      // Extend SearchApiText filter handler with autocomplete capabilities.
+      if (isset($field_data['filter']['id']) && $field_data['filter']['id'] == 'search_api_text') {
+        $data[$table_name][$field_name]['filter']['id'] = 'views_autocomplete_filters_search_api_text';
+      }
+      // Extend SearchApiFulltext filter handler with autocomplete capabilities.
+      if (isset($field_data['filter']['id']) && $field_data['filter']['id'] == 'search_api_fulltext') {
+        $data[$table_name][$field_name]['filter']['id'] = 'views_autocomplete_filters_search_api_fulltext';
+      }
     }
   }
 }
-- 
GitLab