diff --git a/app/assets/javascripts/govuk_publishing_components/components/accessible-autocomplete.js b/app/assets/javascripts/govuk_publishing_components/components/accessible-autocomplete.js new file mode 100644 index 0000000000..3ea2f0fe51 --- /dev/null +++ b/app/assets/javascripts/govuk_publishing_components/components/accessible-autocomplete.js @@ -0,0 +1,73 @@ +/* eslint-env jquery */ +/* global accessibleAutocomplete */ +// = require accessible-autocomplete/dist/accessible-autocomplete.min.js + +window.GOVUK = window.GOVUK || {} +window.GOVUK.Modules = window.GOVUK.Modules || {}; + +(function (Modules) { + function AccessibleAutocomplete ($module) { + this.$module = $module + this.selectElement = this.$module.querySelector('select') + } + + AccessibleAutocomplete.prototype.init = function () { + var configOptions = { + selectElement: this.selectElement, + autoselect: true, + confirmOnBlur: true, + preserveNullOptions: true, // https://github.com/alphagov/accessible-autocomplete#null-options + defaultValue: '', + govukModule: this // attach this instance of the module so we can access it in onConfirm + } + + configOptions.onConfirm = this.onConfirm + new accessibleAutocomplete.enhanceSelectElement(configOptions) // eslint-disable-line no-new, new-cap + this.autoCompleteInput = this.$module.querySelector('.autocomplete__input') + if (this.autoCompleteInput) { + this.autoCompleteInput.addEventListener('keyup', this.handleKeyup.bind(this)) + } + } + + // custom onConfirm function because will likely need future expansion e.g. tracking + AccessibleAutocomplete.prototype.onConfirm = function (value) { + // onConfirm fires on selecting an option + // also fires when blurring the input, which then provides `value` as undefined + // so we only update the hidden select if there's a value, and handle clearing it separately + if (typeof value !== 'undefined') { + // the accessible-autocomplete doesn't update the hidden select if an onConfirm function is supplied + // https://github.com/alphagov/accessible-autocomplete/issues/322 + var options = this.selectElement.querySelectorAll('option') + for (var i = 0; i < options.length; i++) { + var text = options[i].textContent + if (text === value) { + this.govukModule.setSelectOption(options[i]) + break + } + } + } + } + + // seems to be a bug in the accessible autocomplete where clearing the input + // doesn't update the select, so we have to do this + AccessibleAutocomplete.prototype.handleKeyup = function (e) { + var value = this.autoCompleteInput.value + + if (value.length === 0) { + this.clearSelect() + } + } + + AccessibleAutocomplete.prototype.setSelectOption = function (option) { + var value = option.value + this.selectElement.value = value + window.GOVUK.triggerEvent(this.selectElement, 'change') + } + + AccessibleAutocomplete.prototype.clearSelect = function () { + this.selectElement.value = '' + window.GOVUK.triggerEvent(this.selectElement, 'change') + } + + Modules.AccessibleAutocomplete = AccessibleAutocomplete +})(window.GOVUK.Modules) diff --git a/app/assets/stylesheets/govuk_publishing_components/_all_components.scss b/app/assets/stylesheets/govuk_publishing_components/_all_components.scss index e165a1c679..b34b3df180 100644 --- a/app/assets/stylesheets/govuk_publishing_components/_all_components.scss +++ b/app/assets/stylesheets/govuk_publishing_components/_all_components.scss @@ -11,6 +11,7 @@ $govuk-new-link-styles: true; @import "govuk/components/all"; // components +@import "components/accessible-autocomplete"; @import "components/accordion"; @import "components/action-link"; @import "components/attachment"; diff --git a/app/assets/stylesheets/govuk_publishing_components/components/_accessible-autocomplete.scss b/app/assets/stylesheets/govuk_publishing_components/components/_accessible-autocomplete.scss new file mode 100644 index 0000000000..b2164b8a54 --- /dev/null +++ b/app/assets/stylesheets/govuk_publishing_components/components/_accessible-autocomplete.scss @@ -0,0 +1,22 @@ +@import url(asset-path("accessible-autocomplete/dist/accessible-autocomplete.min.css")); // stylelint-disable function-url-quotes + +.gem-c-accessible-autocomplete { + .autocomplete__input { + z-index: 1; + } + + .autocomplete__option { + @include govuk-font(19); + min-height: 1.3em; // this ensures a blank item has a height + } + + .autocomplete__list .autocomplete__option { + padding: 5px 6px; + + &:before { + position: relative; + top: 3px; + padding-top: 2px; + } + } +} diff --git a/app/views/govuk_publishing_components/components/_accessible_autocomplete.html.erb b/app/views/govuk_publishing_components/components/_accessible_autocomplete.html.erb new file mode 100644 index 0000000000..04cfb3df15 --- /dev/null +++ b/app/views/govuk_publishing_components/components/_accessible_autocomplete.html.erb @@ -0,0 +1,27 @@ +<% + id ||= "autocomplete-#{SecureRandom.hex(4)}" + label ||= nil + data_attributes ||= nil + options ||= [] + selected_option ||= nil + + classes = %w(gem-c-accessible-autocomplete govuk-form-group) +%> +<% if label && options.any? %> + <%= tag.div class: classes, data: { module: "accessible-autocomplete" } do %> + <%= + render "govuk_publishing_components/components/label", { + html_for: id + }.merge(label.symbolize_keys) + %> + + <%= + select_tag( + id, + options_for_select(options, selected_option), + class: "govuk-select", + data: data_attributes + ) + %> + <% end %> +<% end %> diff --git a/app/views/govuk_publishing_components/components/docs/accessible_autocomplete.yml b/app/views/govuk_publishing_components/components/docs/accessible_autocomplete.yml new file mode 100644 index 0000000000..118aa5978a --- /dev/null +++ b/app/views/govuk_publishing_components/components/docs/accessible_autocomplete.yml @@ -0,0 +1,27 @@ +name: Accessible autocomplete (experimental) +description: An autocomplete component, built to be accessible. +body: | + This component uses the [Accessible Autocomplete](https://github.com/alphagov/accessible-autocomplete) code to progressively enhance a hidden select element (see [Autocomplete examples](https://alphagov.github.io/accessible-autocomplete/examples) here). It depends upon the [label component](https://github.com/component-guide/label). + + If Javascript is disabled, the component appears as a select box, so the user can still select an option. +accessibility_criteria: | + [Accessibility acceptance criteria](https://github.com/alphagov/accessible-autocomplete/blob/master/accessibility-criteria.md) +examples: + default: + data: + label: + text: 'Countries' + options: [['', ''], ['France', 'fr'], ['Germany', 'de'], ['Sweden', 'se'], ['Switzerland', 'ch'], ['United Kingdom', 'gb'], ['United States', 'us'], ['The Separate Customs Territory of Taiwan, Penghu, Kinmen, and Matsu (Chinese Taipei)', 'tw']] + with_unique_identifier: + data: + id: 'unique-autocomplete' + label: + text: 'Countries' + options: [['', ''], ['France', 'fr'], ['Germany', 'de'], ['Sweden', 'se'], ['Switzerland', 'ch'], ['United Kingdom', 'gb'], ['United States', 'us']] + with_selected_option_chosen: + data: + id: 'selected-option-chosen-autocomplete' + label: + text: 'Countries' + options: [['', ''], ['France', 'fr'], ['Germany', 'de'], ['Sweden', 'se'], ['Switzerland', 'ch'], ['United Kingdom', 'gb'], ['United States', 'us']] + selected_option: ['United Kingdom', 'gb'] diff --git a/config/initializers/assets.rb b/config/initializers/assets.rb index 1779fa0da9..3719b2058a 100644 --- a/config/initializers/assets.rb +++ b/config/initializers/assets.rb @@ -35,6 +35,7 @@ # GOV.UK Frontend assets Rails.application.config.assets.precompile += %w[ + accessible-autocomplete/dist/accessible-autocomplete.min.css govuk-logotype-crown.png favicon.ico govuk-opengraph-image.png diff --git a/package.json b/package.json index e84ff1144c..81c0994afb 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "extends": "stylelint-config-gds/scss" }, "dependencies": { + "accessible-autocomplete": "git://github.com/alphagov/accessible-autocomplete.git", "axe-core": "^3.5.4", "govuk-frontend": "^3.14.0", "jquery": "1.12.4", diff --git a/spec/components/accessible_autocomplete_spec.rb b/spec/components/accessible_autocomplete_spec.rb new file mode 100644 index 0000000000..7a8c0b3f54 --- /dev/null +++ b/spec/components/accessible_autocomplete_spec.rb @@ -0,0 +1,55 @@ +require "rails_helper" + +describe "AccessibleAutocomplete", type: :view do + def component_name + "accessible_autocomplete" + end + + it "renders select element" do + render_component( + id: "basic-autocomplete", + label: { text: "Countries" }, + options: [["United Kingdom", "gb"], ["United States", "us"]], + ) + + assert_select ".govuk-label", text: "Countries", for: "basic-autocomplete" + assert_select "select#basic-autocomplete" + assert_select "select[multiple]", false + assert_select "select#basic-autocomplete option[value=gb]" + assert_select "select#basic-autocomplete option[value=gb]", text: "United Kingdom" + assert_select "select#basic-autocomplete option[value=us]" + assert_select "select#basic-autocomplete option[value=us]", text: "United States" + end + + it "renders select element with selected value" do + render_component( + id: "basic-autocomplete", + label: { text: "Countries" }, + options: [["United Kingdom", "gb"], ["United States", "us"]], + selected_option: ["United States", "us"], + ) + + assert_select ".govuk-label", text: "Countries", for: "basic-autocomplete" + assert_select "select#basic-autocomplete" + assert_select "select#basic-autocomplete option[value=gb]" + assert_select "select#basic-autocomplete option[value=us][selected]" + end + + it "does not render when no data is specified" do + assert_empty render_component({}) + end + + it "does not render when no label is specified" do + assert_empty render_component( + id: "basic-autocomplete", + options: [["United Kingdom", "gb"], ["United States", "us"]], + ) + end + + it "does not render when no options are specified" do + assert_empty render_component( + id: "basic-autocomplete", + label: { text: "Countries" }, + ) + end +end diff --git a/spec/javascripts/components/accessible-autocomplete-spec.js b/spec/javascripts/components/accessible-autocomplete-spec.js new file mode 100644 index 0000000000..5bb8ee13ad --- /dev/null +++ b/spec/javascripts/components/accessible-autocomplete-spec.js @@ -0,0 +1,89 @@ +/* eslint-env jasmine, jquery */ +/* global GOVUK */ + +describe('An accessible autocomplete component', function () { + var fixture, select + + function loadAutocompleteComponent () { + fixture = document.createElement('div') + document.body.appendChild(fixture) + fixture.innerHTML = html + select = fixture.querySelector('select') + var autocomplete = new GOVUK.Modules.AccessibleAutocomplete(fixture.querySelector('.gem-c-accessible-autocomplete')) + autocomplete.init() + } + + var html = + '