Skip to content

Commit

Permalink
Initial commit of new implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
niksy committed Jul 29, 2019
1 parent 0fa60ad commit 5b4653d
Show file tree
Hide file tree
Showing 21 changed files with 1,539 additions and 0 deletions.
41 changes: 41 additions & 0 deletions .babelrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{
"presets": [
"babel-preset-niksy/next",
[
"babel-preset-niksy",
{
"@babel/preset-env": {
"loose": true
}
}
]
],
"plugins": ["@babel/plugin-transform-object-assign"],
"env": {
"test": {
"presets": [
[
"babel-preset-niksy",
{
"@babel/preset-env": {
"loose": true,
"useBuiltIns": "usage",
"corejs": 2
}
}
]
],
"plugins": [
[
"@babel/plugin-transform-runtime",
{
"corejs": false,
"helpers": true,
"regenerator": false,
"useESModules": true
}
]
]
}
}
}
2 changes: 2 additions & 0 deletions .browserslistrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
last 2 versions
ie >= 11
16 changes: 16 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
root = true

[*]
indent_style = tab
indent_size = 4
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

[package.json]
indent_style = space
indent_size = 2

[*.md]
trim_trailing_whitespace = false
34 changes: 34 additions & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"root": true,
"extends": [
"niksy",
"niksy/next",
"niksy/browser",
"prettier"
],
"plugins": [
"prettier",
"html"
],
"settings": {
"html/html-extensions": [".svelte"],
"html/indent": "0"
},
"rules": {
"prettier/prettier": 1
},
"overrides": [
{
"files": [
"karma.conf.js"
],
"env": {
"node": true,
"es6": true
},
"rules": {
"no-console": 0
}
}
]
}
11 changes: 11 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
.DS_Store
node_modules/
package-lock.json
yarn.lock
npm-debug.log
local.log
coverage/
index.cjs.js
index.esm.js
index.cjs.js.map
index.esm.js.map
1 change: 1 addition & 0 deletions .npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package-lock=false
18 changes: 18 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"printWidth": 80,
"tabWidth": 4,
"useTabs": true,
"semi": true,
"singleQuote": true,
"quoteProps": "preserve",
"jsxSingleQuote": true,
"trailingComma": "none",
"bracketSpacing": true,
"jsxBracketSameLine": false,
"arrowParens": "always",
"requirePragma": false,
"insertPragma": false,
"proseWrap": "always",
"htmlWhitespaceSensitivity": "css",
"endOfLine": "lf"
}
3 changes: 3 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
language: node_js
node_js:
- '8'
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Changelog

## [Unreleased][]

### Added

- Initial implementation
19 changes: 19 additions & 0 deletions LICENSE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
Copyright (c) Ivan Nikolić <http://ivannikolic.com>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
186 changes: 186 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
# x-autosuggest

[![Build Status][ci-img]][ci]
[![BrowserStack Status][browserstack-img]][browserstack]

Autosuggest results based on input.

Features:

- Best accessibility practices baked in
- Flexible styling

**[Try it now!](#demo)**

## Install

```sh
npm install x-autosuggest --save
```

## Usage

Following example will decorate existing `input` element with autosuggest
functionality.

When user inputs query, autosuggest first checks if query has less than 2
characters. If it does, no results are returned, otherwise it fetches list of
countries and maps names to content and value which will be used for input value.

When user chooses option, closest `form` element submit event will be triggered.

```js
import autosuggest from 'x-autosuggest';

const element = document.querySelector('input[type="text"]');

const instance = autosuggest(element, {
onOptionSelect() {
element.closest('form').submit();
},
async onQueryInput(query) {
if (query.trim().length < 2) {
return [];
}
const response = await fetch(
`https://restcountries.eu/rest/v2/name/${query}`
);
const countries = await response.json();
return countries.map(({ name }) => {
return {
content: `<b>${name}</b>`,
value: name
};
});
}
});
```

## API

### autosuggest(element, options)

Returns: `Object`

Decorates `element` with autosuggest functionality.

#### element

Type: `HTMLInputElement`

Input element to decorate.

#### options

Type: `Object`

##### onQueryInput

Type: `Function`
Default: `async (query) => []`

Callback to run when query changes. You can perform actions such as testing
query, fetching data and mapping results to valid format here.

Return value should either be empty array (when you don’t want to display
results or don’t have results to display) or array of objects, where each object
contains two properties:

###### content

Type: `string`

Content for option. Can be regular string or HTML string as markup.

###### value

Type: `*`

Value used for `input` element value.

If it’s `null` option element will be considered as placeholder element and
won’t be used as option. Useful if you want to have dividers between your
options, or if you need to group option elements with headings.

##### decorateOption

Type: `Function`
Default: `(node) => {}`

Decorate autosuggest option. Callback receives one argument which is option
`Element` node. Useful if you want to add additional functionality to option
such as attach event listeners or add HTML classes.

If return value is function, it will be used as cleanup callback for when
autosuggest instance is removed or option is rerendered. You can perform actions
such as custom event handlers removal for option inside this callback.

##### decorateInputEvent

Type: `Function`
Default: `(listener) => {}`

Decorate `input` event of input element. Callback receives one argument which is
default listener for `input` event. useful if you want to add additional
functionality such as debounce or throttle of events.

##### onOptionSelect

Type: `Function`
Default: `(event, value) => {}`

Callback to run when option is selected. It receives two arguments: event object
of triggered event and value of triggered option.

This callback is useful for performing actions such as triggering form submit.

### instance.destroy()

Destroy instance.

## FAQ

### How do I cache results?

By default, results are not cached, but it can be achieved with techniques such
as memoization.

```js
import autosuggest from 'x-autosuggest';
import memoize from '@f/memoize';

// We cache fetch results based on URL
const cachedFetch = memoize((url) => {
return fetch(url);
});

// And then in autosuggest instance options…
const options = {
onQueryInput(query) {
return cachedFetch(`https://restcountries.eu/rest/v2/name/${query}`);
//
}
};
```

## Browser support

Tested in IE11+ and all modern browsers, assuming `Promise` is available.

## Test

For automated tests, run `npm run test:automated` (append `:watch` for watcher
support).

## License

MIT © [Ivan Nikolić](http://ivannikolic.com)

<!-- prettier-ignore-start -->

[ci]: https://travis-ci.com/niksy/x-autosuggest
[ci-img]: https://travis-ci.com/niksy/x-autosuggest.svg?branch=master
[browserstack]: https://www.browserstack.com/
[browserstack-img]: https://www.browserstack.com/automate/badge.svg?badge_key=<badge_key>

<!-- prettier-ignore-end -->
27 changes: 27 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import Component from './lib/index.svelte';

export default (element, options = {}) => {
const {
decorateOption = (node) => {},
decorateInputEvent = (listener) => listener,
onOptionSelect = (event, value) => {},
onQueryInput = (query) => Promise.resolve([])
} = options;

const instance = new Component({
target: element.parentElement,
elementToHandle: element,
data: {
decorateOption,
decorateInputEvent,
onOptionSelect,
onQueryInput
}
});
return {
destroy: () => {
instance.set({ isComponentActive: false });
instance.destroy();
}
};
};
Loading

0 comments on commit 5b4653d

Please sign in to comment.