Coder Social home page Coder Social logo

github / auto-complete-element Goto Github PK

View Code? Open in Web Editor NEW
351.0 201.0 56.0 1.21 MB

Auto-complete input values from server search results.

Home Page: https://github.github.com/auto-complete-element/examples/

License: MIT License

JavaScript 65.22% TypeScript 32.27% Dockerfile 2.51%
web-components custom-elements

auto-complete-element's Introduction

<auto-complete> element

Auto-complete input values from server search results.

Installation

$ npm install --save @github/auto-complete-element

Usage

Script

Import as ES modules:

import '@github/auto-complete-element'

With a script tag:

<script type="module" src="./node_modules/@github/auto-complete-element/dist/bundle.js">

Markup

<auto-complete src="/users/search" for="users-popup">
  <input type="text" name="users">
  <!--
    Optional clear button:
    - id must match the id of the input or the name of the input plus "-clear"
    - recommended to be *before* UL elements to avoid conflicting with their blur logic

    Please see Note below on this button for more details
  -->
  <button id="users-clear">X</button>
  <ul id="users-popup"></ul>
  <!--
    Optional div for screen reader feedback. Note the ID matches the ul, but with -feedback appended.
    Recommended: Use a "Screen Reader Only" class to position the element off the visual boundary of the page.
  -->
  <div id="users-popup-feedback" class="sr-only"></div>
</auto-complete>

If you want to enable auto-select (pressing Enter in the input will select the first option), using the above example:

<auto-complete data-autoselect="true" src="/users/search" for="users-popup">
...
</auto-complete>

The server response should include the items that matched the search query.

<li role="option">Hubot</li>
<li role="option">Bender</li>
<li role="option">BB-8</li>
<li role="option" aria-disabled="true">R2-D2 (powered down)</li>

The data-autocomplete-value attribute can be used to define the value for an item whose display text needs to be different:

<li role="option" data-autocomplete-value="bb8">BB-8 (astromech)</li>

A Note on Clear button

While input type="search" comes with an x that clears the content of the field and refocuses it on many browsers, the implementation for this control is not keyboard accessible, and so we've opted to enable a customizable clear button so that your keyboard users will be able to interact with it.

As an example:

In Chrome, this 'x' isn't a button but a div with a pseudo="-webkit-search-cancel-button". It doesn't have a tab index or a way to navigate to it without a mouse. Additionally, this control is only visible on mouse hover.

Attributes

  • open is true when the auto-complete result list is visible
  • value is the selected value from the list or the empty string when cleared

Properties

  • fetchResult you can override the default method used to query for results by overriding this property: document.querySelector('auto-complete').fetchResult = async (el, url) => (await fetch(url)).text()

Events

Network request lifecycle events

Request lifecycle events are dispatched on the <auto-complete> element. These events do not bubble.

  • loadstart - The server fetch has started.
  • load - The network request completed successfully.
  • error - The network request failed.
  • loadend - The network request has completed.

Network events are useful for displaying progress states while the request is in-flight.

const completer = document.querySelector('auto-complete')
const container = completer.parentElement
completer.addEventListener('loadstart', () => container.classList.add('is-loading'))
completer.addEventListener('loadend', () => container.classList.remove('is-loading'))
completer.addEventListener('load', () => container.classList.add('is-success'))
completer.addEventListener('error', () => container.classList.add('is-error'))

Auto-complete events

auto-complete-change is dispatched after a value is selected. In event.detail you can find:

  • relatedTarget: The HTMLInputElement controlling the auto-complete result list.
completer.addEventListener('auto-complete-change', function(event) {
  console.log('Auto-completed value chosen or cleared', completer.value)
  console.log('Related input element', event.relatedTarget)
})

CSP Trusted Types

You can call setCSPTrustedTypesPolicy(policy: TrustedTypePolicy | Promise<TrustedTypePolicy> | null) from JavaScript to set a CSP trusted types policy, which can perform (synchronous) filtering or rejection of the fetch response before it is inserted into the page:

import AutoCompleteElement from 'auto-complete-element'
import DOMPurify from 'dompurify' // Using https://github.com/cure53/DOMPurify

// This policy removes all HTML markup except links.
const policy = trustedTypes.createPolicy('links-only', {
  createHTML: (htmlText: string) => {
    return DOMPurify.sanitize(htmlText, {
      ALLOWED_TAGS: ['a'],
      ALLOWED_ATTR: ['href'],
      RETURN_TRUSTED_TYPE: true
    })
  }
})
AutoCompleteElement.setCSPTrustedTypesPolicy(policy)

The policy has access to the fetch response object. Due to platform constraints, only synchronous information from the response (in addition to the HTML text body) can be used in the policy:

import AutoCompleteElement from 'auto-complete-element'

const policy = trustedTypes.createPolicy('require-server-header', {
  createHTML: (htmlText: string, response: Response) => {
    if (response.headers.get('X-Server-Sanitized') !== 'sanitized=true') {
      // Note: this will reject the contents, but the error may be caught before it shows in the JS console.
      throw new Error('Rejecting HTML that was not marked by the server as sanitized.')
    }
    return htmlText
  }
})
AutoCompleteElement.setCSPTrustedTypesPolicy(policy)

Note that:

  • Only a single policy can be set, shared by all AutoCompleteElement fetches.
  • You should call setCSPTrustedTypesPolicy() ahead of any other load of auto-complete element in your code.
    • If your policy itself requires asynchronous work to construct, you can also pass a Promise<TrustedTypePolicy>.
    • Pass null to remove the policy.
  • Not all browsers support the trusted types API in JavaScript. You may want to use the recommended tinyfill to construct a policy without causing issues in other browsers.

Browser support

Browsers without native custom element support require a polyfill.

  • Chrome
  • Firefox
  • Safari
  • Microsoft Edge

Development

npm install
npm test

To view changes locally, run npm run examples.

In examples/index.html, uncomment <!--<script type="module" src="./dist/bundle.js"></script>--> and comment out the script referencing the unpkg version. This allows you to use the src code in this repo. Otherwise, you will be pulling the latest published code, which will not reflect the local changes you are making.

Accessibility Testing

We have included some custom rules that assist in providing guardrails to confirm this component is being used accessibly.

If you are using the axe-core library in your project,

import axe from 'axe-core'
import autoCompleteRulesBuilder from '@github/auto-complete-element/validator'

const autoCompleteRules = autoCompleteRulesBuilder() // optionally, pass in your app's custom rules object, it will build and return the full object

axe.configure(autoCompleteRules)
axe.run(document)

Validate usage in your project

To confirm your usage is working as designed,

import {validate} from '@github/auto-complete-element/validator' 

validate(document)

Passes and failures may be determined by the length of the passes and violations arrays on the returned object:

{
  passes: [],
  violations: []
}

License

Distributed under the MIT license. See LICENSE for details.

auto-complete-element's People

Contributors

andrialexandrou avatar augonis avatar bnjamin avatar broccolinisoup avatar brunoprietog avatar camertron avatar danielguillan avatar dependabot[bot] avatar dgraham avatar dgreif avatar inkblotty avatar jfuchs avatar jonrohan avatar keithamus avatar koddsson avatar lindseywild avatar loisolire avatar manuelpuyol avatar maximernr avatar mislav avatar muan avatar siddharthkp avatar simurai avatar staabm avatar theinterned avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

auto-complete-element's Issues

Add client-side options support

Problem

If there are static options passed into the element, the options are deleted on request for filtered options.

Request

Client-side options should be supported and have filtering available. TBD: Do we allow both client-side (such as default options) and server-side options?

Implementation concerns

Accessibility

Ensure that any default / client-side options are announced when the input is focused. screen-reader-announcements.ts can assist with this.

When the options list isn't visible, an aria-describedby should be added to the input pointing to the feedback element. This should only be if the element has client-side options. Remove this aria-describedby when the user interacts with any key, and add it back in when the auto-complete-element is blurred. cc @github/accessibility and @jscholes for reference. "aria-live won't cut it" for this scenario

Developer Notes

There is some initial setup done in autocomplete.ts to detect client-side options, but all of this can be removed / reworked, as long as accessibility considerations are met.

Use abortcontroller to simplify connectedCallback

Per #53 (comment), we register many event handlers like addEventlistener('foo', this.bar = this.bar.bind(bar)), but rather than calling function bind we can use handleEvent and rather than calling removeEventListener a bunch, we can store an abort controller per-instance (e.g. with a private field) and abort during disconnectedCallback.

The pattern would look as follows:

class MyElement extends HTMElement {
  #ctl = new AbortController()

  connectedCallback() {
    this.input.addEventListener('foo', this, { signal: this.#ctl.signal })
    // many more events here...
  }

  handleEvent(event) {
    if (event.type === 'foo' && event.currentTarget === this.input) {
      // do things
    }
  }

  disconnectedCallback() {
    this.#ctl.abort()
  }

}

mouseup event not triggered if result option is dragged

If a result option is dragged outside of <ul id="users-popup"></ul> the this.mouseDown flag is set to true but it never turns back to false (unless you click any option from the list), so the popup can not be closed on input blur.

onResultsMouseDown() {
this.mouseDown = true
this.results.addEventListener('mouseup', () => (this.mouseDown = false), {once: true})
}

Test

  1. Type something
  2. Click and drag an option outside the results list
  3. Try to close the popup without selecting any option from the results list

Possible solution

Change mousedown/mouseup for mouseenter/mouseleave?

The combobox doesn't scroll when navigating

After PR #15 the combobox no longer scrolls when navigating with the arrow keys.

I've made a small example here https://auto-complete-scroll.glitch.me it is the example from this repo, but where I've added a height: 45px; overflow-y: auto; styling to the ul element.

The problem is that the select method in autocomplete.js is no longer called when navigating, so the scrollTo function is never called.

It can be fixed by either moving the scroll functionality to the combobox-nav project instead, or have the combo-box fire an combobox-navigation event and then listen for that and perform the necessary scrolling.

If one of the proposed methods are suitable, I can submit a PR.

[Localization] Support an optional object of Localized phrases for Screen Reader announcements

Context

Currently, all the screen reader announcements for this component are in English and have very generic wording such as "Results hidden". You may want to use more specific verbiage for your component use case, and/or support multiple languages.

Acceptance Criteria

  • Extend the autocomplete API to allow the developer to pass in localized phrases for every Screen Reader announcement
  • If one phrase isn't passed in, fallback to the default already defined (We've got a lot of feedback in the verbiage here.)
  • Create unit tests to ensure these phrases work correctly

Input cannot be found if javascript is run before element is present

I had some issues getting started with this module as the input could not be found when the element is connected.

Debugging I found it was due to

if (!(input instanceof HTMLInputElement) || !results) return

It was then I realised that the javascript needs to be run after the element is on the page. I was able to verify this by changing the example provided such that the javascript tag was in the head of the page.

Should there be a console log or should this case be handled?

I suspect that this might mean this element cannot be added to the page dynamically if the javascript is already present.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.