├── src
├── index.ts
├── components
│ └── web-complete
│ │ ├── web-complete.css
│ │ ├── web-complete.e2e.ts
│ │ ├── web-complete.tsx
│ │ └── readme.md
├── index.html
└── components.d.ts
├── .npmignore
├── .travis.yml
├── .editorconfig
├── .prettierrc.json
├── .gitignore
├── stencil.config.ts
├── tsconfig.json
├── LICENSE
├── package.json
├── docs
├── index.html
└── countries.json
└── README.md
/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from './components';
2 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | src/
3 | docs/
4 | .stencil/
5 | www/
6 | .gitignore
7 | tsconfig.json
8 | .editorconfig
9 | stencil.config.ts
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | dist: trusty
2 | sudo: required
3 |
4 | language: node_js
5 | node_js:
6 | - 12
7 |
8 | before_script:
9 | - npm i
10 | - npm run build
11 |
12 | script:
13 | - npm test
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # http://editorconfig.org
2 |
3 | root = true
4 |
5 | [*]
6 | charset = utf-8
7 | indent_style = space
8 | indent_size = 2
9 | end_of_line = lf
10 | insert_final_newline = true
11 | trim_trailing_whitespace = true
12 |
13 | [*.md]
14 | insert_final_newline = false
15 | trim_trailing_whitespace = false
16 |
--------------------------------------------------------------------------------
/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "arrowParens": "avoid",
3 | "bracketSpacing": true,
4 | "jsxBracketSameLine": false,
5 | "jsxSingleQuote": false,
6 | "quoteProps": "consistent",
7 | "printWidth": 180,
8 | "semi": true,
9 | "singleQuote": true,
10 | "tabWidth": 2,
11 | "trailingComma": "all",
12 | "useTabs": false
13 | }
14 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | dist/
2 | www/
3 | loader/
4 |
5 | *~
6 | *.sw[mnpcod]
7 | *.log
8 | *.lock
9 | *.tmp
10 | *.tmp.*
11 | log.txt
12 | *.sublime-project
13 | *.sublime-workspace
14 |
15 | .stencil/
16 | .idea/
17 | .vscode/
18 | .sass-cache/
19 | .versions/
20 | node_modules/
21 | $RECYCLE.BIN/
22 |
23 | .DS_Store
24 | Thumbs.db
25 | UserInterfaceState.xcuserstate
26 | .env
27 |
--------------------------------------------------------------------------------
/stencil.config.ts:
--------------------------------------------------------------------------------
1 | import { Config } from '@stencil/core';
2 |
3 | export const config: Config = {
4 | namespace: 'web-complete',
5 | buildEs5: 'prod',
6 | outputTargets: [
7 | {
8 | type: 'dist',
9 | esmLoaderPath: '../loader'
10 | },
11 | {
12 | type: 'dist-custom-elements-bundle',
13 | },
14 | {
15 | type: 'docs-readme'
16 | },
17 | {
18 | type: 'www',
19 | serviceWorker: null // disable service workers
20 | }
21 | ]
22 | };
23 |
--------------------------------------------------------------------------------
/src/components/web-complete/web-complete.css:
--------------------------------------------------------------------------------
1 | :host {
2 | --autocomplete-suggestion-focus-background: gray;
3 | }
4 |
5 | :host > div {
6 | position:relative;
7 | display:inline-block;
8 | }
9 | .suggestions {
10 | position:absolute;
11 | left:0;
12 | right:0;
13 | }
14 | .suggestion {
15 | display:block;
16 | width:100%;
17 | border:none;
18 | text-align:left;
19 | }
20 | .suggestion:hover, .suggestion.active {
21 | background:var(--autocomplete-suggestion-focus-background);
22 | }
23 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "allowSyntheticDefaultImports": true,
4 | "allowUnreachableCode": false,
5 | "declaration": false,
6 | "experimentalDecorators": true,
7 | "lib": [
8 | "dom",
9 | "es2017"
10 | ],
11 | "moduleResolution": "node",
12 | "module": "esnext",
13 | "target": "es2017",
14 | "noUnusedLocals": true,
15 | "noUnusedParameters": true,
16 | "jsx": "react",
17 | "jsxFactory": "h"
18 | },
19 | "include": [
20 | "src",
21 | "types/jsx.d.ts"
22 | ],
23 | "exclude": [
24 | "node_modules"
25 | ]
26 | }
27 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | BSD 2-Clause License
2 |
3 | Copyright (c) 2019, Stefan Huber
4 | All rights reserved.
5 |
6 | Redistribution and use in source and binary forms, with or without
7 | modification, are permitted provided that the following conditions are met:
8 |
9 | 1. Redistributions of source code must retain the above copyright notice, this
10 | list of conditions and the following disclaimer.
11 |
12 | 2. Redistributions in binary form must reproduce the above copyright notice,
13 | this list of conditions and the following disclaimer in the documentation
14 | and/or other materials provided with the distribution.
15 |
16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "web-complete",
3 | "version": "1.3.2",
4 | "description": "A lightweight, dependency-free, styleable autocomplete web component",
5 | "keywords": [
6 | "autocomplete",
7 | "web-component"
8 | ],
9 | "author": {
10 | "name": "Stefan Huber",
11 | "email": "mail@stefanhuber.at",
12 | "url": "https://www.stefanhuber.at"
13 | },
14 | "main": "dist/index.cjs.js",
15 | "module": "dist/custom-elements/index.js",
16 | "es2015": "dist/esm/index.mjs",
17 | "es2017": "dist/esm/index.mjs",
18 | "types": "dist/types/index.d.ts",
19 | "collection": "dist/collection/collection-manifest.json",
20 | "collection:main": "dist/collection/index.js",
21 | "unpkg": "dist/web-complete/web-complete.esm.js",
22 | "files": [
23 | "dist/",
24 | "loader/"
25 | ],
26 | "homepage": "https://github.com/stefanhuber/web-complete",
27 | "bugs": {
28 | "url": "https://github.com/stefanhuber/web-complete/issues"
29 | },
30 | "repository": {
31 | "type": "git",
32 | "url": "https://github.com/stefanhuber/web-complete.git"
33 | },
34 | "scripts": {
35 | "build": "stencil build --docs",
36 | "start": "stencil build --dev --watch --serve",
37 | "test": "stencil test --spec --e2e",
38 | "test.watch": "stencil test --spec --e2e --watchAll",
39 | "generate": "stencil generate"
40 | },
41 | "devDependencies": {
42 | "@stencil/core": "^2.3.0",
43 | "@types/jest": "^26.0.20",
44 | "@types/puppeteer": "^5.4.2",
45 | "jest": "^26.6.3",
46 | "jest-cli": "^26.6.3",
47 | "puppeteer": "^5.4.1"
48 | },
49 | "license": "BSD-2-Clause"
50 | }
51 |
--------------------------------------------------------------------------------
/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Web Complete
8 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
27 |
29 |
30 |
31 |
32 |
33 |
34 |
81 |
82 |
83 |
--------------------------------------------------------------------------------
/src/components/web-complete/web-complete.e2e.ts:
--------------------------------------------------------------------------------
1 | import { newE2EPage } from '@stencil/core/testing';
2 |
3 | const addDummySuggestionGenerator = (element: any) => {
4 | element.suggestionGenerator = (text) => {
5 | return ["abcdef", "xyz", "test abc", "test xyz"]
6 | .filter(suggestion => suggestion.indexOf(text) >= 0)
7 | .map((suggestion, index) => {
8 | return {
9 | text: suggestion,
10 | value: 1000 + index
11 | }
12 | });
13 | };
14 | };
15 |
16 | describe('web-complete', () => {
17 |
18 | it('should set text and value on inner input field', async () => {
19 | const page = await newE2EPage();
20 | await page.setContent(``);
21 |
22 | const complete = await page.find('web-complete');
23 |
24 | const value = await complete.callMethod('getValue');
25 | const text = await complete.callMethod('getText');
26 |
27 | expect(value).toEqual('123');
28 | expect(text).toEqual('some demo text');
29 | });
30 |
31 | it('should clear web-complete field', async () => {
32 | const page = await newE2EPage();
33 | await page.setContent(``);
34 |
35 | const complete = await page.find('web-complete');
36 | const unselected = await complete.spyOnEvent('unselected');
37 |
38 | await complete.callMethod('clear');
39 |
40 | const value = await complete.callMethod('getValue');
41 | const text = await complete.callMethod('getText');
42 |
43 | expect(value).toEqual('');
44 | expect(text).toEqual('');
45 |
46 | expect(unselected).toHaveReceivedEventDetail({
47 | text: 'some demo text',
48 | value: '123'
49 | });
50 | });
51 |
52 | it('should generate suggestions on typing', async () => {
53 | const page = await newE2EPage();
54 | await page.setContent(``);
55 |
56 | await page.$eval('web-complete', addDummySuggestionGenerator);
57 | await page.waitForChanges();
58 |
59 | const input = await page.find('web-complete > div > input');
60 | input.press('a');
61 | input.press('b');
62 | await page.waitForChanges();
63 |
64 | const suggestions = await page.findAll('web-complete > div > div > button');
65 | expect(suggestions.length).toEqual(2);
66 | expect(suggestions[0].textContent).toBe("abcdef");
67 | expect(suggestions[1].textContent).toBe("test abc");
68 | });
69 |
70 | it('should select suggestion on click', async () => {
71 | const page = await newE2EPage();
72 | await page.setContent(``);
73 |
74 | await page.$eval('web-complete', addDummySuggestionGenerator);
75 | await page.waitForChanges();
76 |
77 | const input = await page.find('web-complete > div > input');
78 | input.press('a');
79 | input.press('b');
80 | await page.waitForChanges();
81 |
82 | const suggestions = await page.findAll('web-complete > div > div > button');
83 | suggestions[1].click();
84 | await page.waitForChanges();
85 |
86 | const complete = await page.find('web-complete');
87 | const value = await complete.callMethod('getValue');
88 | const text = await complete.callMethod('getText');
89 | expect(value).toEqual("1001");
90 | expect(text).toEqual("test abc");
91 | });
92 |
93 | it('should keep text after blur when clearOnUnselectedClosing = false', async () => {
94 | const page = await newE2EPage();
95 | await page.setContent(``);
96 |
97 | await page.$eval('web-complete', addDummySuggestionGenerator);
98 | await page.waitForChanges();
99 |
100 | const input = await page.find('web-complete > div > input');
101 | input.press('v');
102 | input.press('w');
103 | await page.waitForChanges();
104 |
105 | const suggestions = await page.findAll('web-complete > div > div > button');
106 | expect(suggestions.length).toEqual(0);
107 |
108 | page.$eval('web-complete > div > input', element => element['blur']());
109 | await page.waitForTimeout(250);
110 |
111 | const complete = await page.find('web-complete');
112 | const value = await complete.callMethod('getValue');
113 | const text = await complete.callMethod('getText');
114 | expect(value).toEqual("");
115 | expect(text).toEqual("vw");
116 | });
117 |
118 | it('should remove text after blur when clearOnUnselectedClosing = true', async () => {
119 | const page = await newE2EPage();
120 | await page.setContent(``);
121 |
122 | await page.$eval('web-complete', addDummySuggestionGenerator);
123 | await page.waitForChanges();
124 |
125 | const input = await page.find('web-complete > div > input');
126 | input.press('v');
127 | input.press('w');
128 | await page.waitForChanges();
129 |
130 | const suggestions = await page.findAll('web-complete > div > div > button');
131 | expect(suggestions.length).toEqual(0);
132 |
133 | page.$eval('web-complete > div > input', element => element['blur']());
134 | await page.waitForTimeout(250);
135 |
136 | const complete = await page.find('web-complete');
137 | const value = await complete.callMethod('getValue');
138 | const text = await complete.callMethod('getText');
139 | expect(value).toEqual("");
140 | expect(text).toEqual("");
141 | });
142 |
143 | });
144 |
145 |
--------------------------------------------------------------------------------
/src/components.d.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | /* tslint:disable */
3 | /**
4 | * This is an autogenerated file created by the Stencil compiler.
5 | * It contains typing information for all components that exist in this project.
6 | */
7 | import { HTMLStencilElement, JSXBase } from "@stencil/core/internal";
8 | export namespace Components {
9 | interface WebComplete {
10 | /**
11 | * Clears the form field (suggestions and selection)
12 | */
13 | "clear": () => Promise;
14 | /**
15 | * If no value is selected, clear the input and emit unselected, if false, the value will not be cleared (usefull for suggesting values on a free text search)
16 | */
17 | "clearOnUnselectedClosing": boolean;
18 | /**
19 | * The class names, which should be set on the rendered html elements
20 | */
21 | "cssClasses": { wrapper: string; input: string; suggestions: string; suggestion: string; active: string; };
22 | /**
23 | * Enable/Disable the input field
24 | */
25 | "disabled": boolean;
26 | /**
27 | * Timing to suggest on empty (-1 to disable)
28 | */
29 | "emptySuggestionTime": number;
30 | /**
31 | * Returns the `text` of the selected item
32 | */
33 | "getText": () => Promise;
34 | /**
35 | * Returns the `value` of the selected item
36 | */
37 | "getValue": () => Promise;
38 | /**
39 | * id of the input field
40 | */
41 | "inputId": string;
42 | /**
43 | * A hint to the browser for which keyboard to display. Possible values: `"none"`, `"text"`, `"tel"`, `"url"`, `"email"`, `"numeric"`, `"decimal"`, and `"search"`.
44 | */
45 | "inputmode"?: 'none' | 'text' | 'tel' | 'url' | 'email' | 'numeric' | 'decimal' | 'search';
46 | /**
47 | * The maximally shown suggestions in the list
48 | */
49 | "maxSuggestions": number;
50 | /**
51 | * The minimum input size for generating suggestions
52 | */
53 | "minInput": number;
54 | /**
55 | * The placeholder for the input field
56 | */
57 | "placeholder": string;
58 | /**
59 | * Form validation: is the form input required
60 | */
61 | "required": boolean;
62 | /**
63 | * Async suggestion generator: `text` is the displayed for users in the form after selection (if no `suggesion` also as suggesion) `value` is the actual value of the form field optional `suggesion` if the autocomplete suggestion item should be formatted differently than `text`
64 | */
65 | "suggestionGenerator": (text: string) => Promise>;
66 | /**
67 | * The text is displayed by the form field for users
68 | */
69 | "text": string;
70 | /**
71 | * The actual value of the form field
72 | */
73 | "value": string;
74 | }
75 | }
76 | declare global {
77 | interface HTMLWebCompleteElement extends Components.WebComplete, HTMLStencilElement {
78 | }
79 | var HTMLWebCompleteElement: {
80 | prototype: HTMLWebCompleteElement;
81 | new (): HTMLWebCompleteElement;
82 | };
83 | interface HTMLElementTagNameMap {
84 | "web-complete": HTMLWebCompleteElement;
85 | }
86 | }
87 | declare namespace LocalJSX {
88 | interface WebComplete {
89 | /**
90 | * If no value is selected, clear the input and emit unselected, if false, the value will not be cleared (usefull for suggesting values on a free text search)
91 | */
92 | "clearOnUnselectedClosing"?: boolean;
93 | /**
94 | * The class names, which should be set on the rendered html elements
95 | */
96 | "cssClasses"?: { wrapper: string; input: string; suggestions: string; suggestion: string; active: string; };
97 | /**
98 | * Enable/Disable the input field
99 | */
100 | "disabled"?: boolean;
101 | /**
102 | * Timing to suggest on empty (-1 to disable)
103 | */
104 | "emptySuggestionTime"?: number;
105 | /**
106 | * id of the input field
107 | */
108 | "inputId"?: string;
109 | /**
110 | * A hint to the browser for which keyboard to display. Possible values: `"none"`, `"text"`, `"tel"`, `"url"`, `"email"`, `"numeric"`, `"decimal"`, and `"search"`.
111 | */
112 | "inputmode"?: 'none' | 'text' | 'tel' | 'url' | 'email' | 'numeric' | 'decimal' | 'search';
113 | /**
114 | * The maximally shown suggestions in the list
115 | */
116 | "maxSuggestions"?: number;
117 | /**
118 | * The minimum input size for generating suggestions
119 | */
120 | "minInput"?: number;
121 | /**
122 | * Emitted when an item from suggestions was selected
123 | */
124 | "onSelected"?: (event: CustomEvent) => void;
125 | /**
126 | * Emitted when item was cleared/unselected
127 | */
128 | "onUnselected"?: (event: CustomEvent) => void;
129 | /**
130 | * The placeholder for the input field
131 | */
132 | "placeholder"?: string;
133 | /**
134 | * Form validation: is the form input required
135 | */
136 | "required"?: boolean;
137 | /**
138 | * Async suggestion generator: `text` is the displayed for users in the form after selection (if no `suggesion` also as suggesion) `value` is the actual value of the form field optional `suggesion` if the autocomplete suggestion item should be formatted differently than `text`
139 | */
140 | "suggestionGenerator"?: (text: string) => Promise>;
141 | /**
142 | * The text is displayed by the form field for users
143 | */
144 | "text"?: string;
145 | /**
146 | * The actual value of the form field
147 | */
148 | "value"?: string;
149 | }
150 | interface IntrinsicElements {
151 | "web-complete": WebComplete;
152 | }
153 | }
154 | export { LocalJSX as JSX };
155 | declare module "@stencil/core" {
156 | export namespace JSX {
157 | interface IntrinsicElements {
158 | "web-complete": LocalJSX.WebComplete & JSXBase.HTMLAttributes;
159 | }
160 | }
161 | }
162 |
--------------------------------------------------------------------------------
/src/components/web-complete/web-complete.tsx:
--------------------------------------------------------------------------------
1 | import { Component, ComponentInterface, Prop, h, State, Event, EventEmitter, Method } from '@stencil/core';
2 |
3 | @Component({
4 | tag: 'web-complete',
5 | shadow: false
6 | })
7 | export class Autocomplete implements ComponentInterface {
8 |
9 | @State() activeIndex = -1; // focused suggestion
10 | @State() data: Array<{ text: string, value: string, suggestion?: string }> = [];
11 | @State() active: boolean = false; // has focus
12 |
13 | /**
14 | * The text is displayed by the form field for users
15 | */
16 | @Prop({ mutable: true }) text = "";
17 |
18 | /**
19 | * The actual value of the form field
20 | */
21 | @Prop({ mutable: true }) value = "";
22 |
23 | /**
24 | * The placeholder for the input field
25 | */
26 | @Prop() placeholder = "";
27 |
28 | /**
29 | * If no value is selected, clear the input and emit unselected, if false, the value will not be cleared (usefull for suggesting values on a free text search)
30 | */
31 | @Prop() clearOnUnselectedClosing: boolean = true;
32 |
33 | /**
34 | * Enable/Disable the input field
35 | */
36 | @Prop() disabled = false;
37 |
38 | /**
39 | * The minimum input size for generating suggestions
40 | */
41 | @Prop() minInput = 0;
42 |
43 | /**
44 | * The maximally shown suggestions in the list
45 | */
46 | @Prop() maxSuggestions = 5;
47 |
48 | /**
49 | * Timing to suggest on empty (-1 to disable)
50 | */
51 | @Prop() emptySuggestionTime = -1;
52 |
53 | /**
54 | * Form validation: is the form input required
55 | */
56 | @Prop() required = false;
57 |
58 | /**
59 | * A hint to the browser for which keyboard to display.
60 | * Possible values: `"none"`, `"text"`, `"tel"`, `"url"`,
61 | * `"email"`, `"numeric"`, `"decimal"`, and `"search"`.
62 | */
63 | @Prop() inputmode?: 'none' | 'text' | 'tel' | 'url' | 'email' | 'numeric' | 'decimal' | 'search';
64 |
65 | /**
66 | * id of the input field
67 | */
68 | @Prop() inputId = "";
69 |
70 | /**
71 | * Async suggestion generator:
72 | * `text` is the displayed for users in the form after selection (if no `suggesion` also as suggesion)
73 | * `value` is the actual value of the form field
74 | * optional `suggesion` if the autocomplete suggestion item should be formatted differently than `text`
75 | */
76 | @Prop() suggestionGenerator: (text: string) => Promise>;
77 |
78 | /**
79 | * The class names, which should be set on the rendered html elements
80 | */
81 | @Prop() cssClasses = {
82 | wrapper: "",
83 | input: "",
84 | suggestions: "suggestions",
85 | suggestion: "suggestion",
86 | active: "active"
87 | };
88 |
89 | /**
90 | * Emitted when an item from suggestions was selected
91 | */
92 | @Event() selected: EventEmitter;
93 |
94 | /**
95 | * Emitted when item was cleared/unselected
96 | */
97 | @Event() unselected: EventEmitter;
98 |
99 | /**
100 | * Returns the `value` of the selected item
101 | */
102 | @Method()
103 | async getValue(): Promise {
104 | return this.value;
105 | }
106 |
107 | /**
108 | * Returns the `text` of the selected item
109 | */
110 | @Method()
111 | async getText(): Promise {
112 | return this.text;
113 | }
114 |
115 | /**
116 | * Clears the form field (suggestions and selection)
117 | */
118 | @Method()
119 | async clear() {
120 | this.handleClose();
121 | }
122 |
123 | handleKeyDown(e: KeyboardEvent) {
124 | if (["ArrowDown", "ArrowUp", "Down", "Up"].indexOf(e.key) >= 0) { // some older browsers use Up/Down instead of ArrayUp/ArrowDown (https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values)
125 | e.preventDefault();
126 | this.handleActivation(e.key == "ArrowDown" || e.key == "Down")
127 | } else if (e.key == "Enter" || e.key == "Tab") {
128 | e.preventDefault();
129 | this.handleSelection(this.activeIndex);
130 | } else if (e.key == "Escape") {
131 | this.handleClose();
132 | }
133 | }
134 |
135 | handleKeyUp(key, text) {
136 | if (["ArrowDown", "ArrowUp", "Enter", "Tab", "Escape"].indexOf(key) < 0) { // IE doesn't have Array.includes (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/includes)
137 | this.clearSelection(true);
138 | this.prepareSuggestions(text);
139 | }
140 | this.active = true;
141 | this.text = text;
142 | }
143 |
144 | handleFocus(e: FocusEvent) {
145 | e.preventDefault();
146 | this.active = true;
147 | if (this.emptySuggestionTime >= 0) {
148 | this.prepareSuggestions(this.text).then(() => {
149 | this.handleActivation(false);
150 | })
151 | }
152 | }
153 |
154 | handleBlur(e: FocusEvent) {
155 | e.preventDefault();
156 |
157 | setTimeout(() => {
158 | if (this.active) {
159 | if (this.value) {
160 | this.clearData();
161 | } else {
162 | this.handleClose();
163 | }
164 | }
165 | }, 250);
166 | }
167 |
168 | handleClose() {
169 | this.clearSelection();
170 | this.clearData();
171 | }
172 |
173 | handleActivation(next = true) {
174 | if (this.data.length > 0) {
175 | if (next && (this.activeIndex + 1) < this.data.length) {
176 | this.activeIndex += 1;
177 | } else if (next) {
178 | this.activeIndex = 0;
179 | } else if (!next && (this.activeIndex) > 0) {
180 | this.activeIndex -= 1;
181 | } else if (!next) {
182 | this.activeIndex = this.data.length - 1;
183 | }
184 | }
185 | }
186 |
187 | handleSelection(index) {
188 | if (index >= 0 && index < this.data.length) {
189 | this.text = this.data[index].text;
190 | this.value = this.data[index].value;
191 | this.selected.emit(this.data[index]);
192 | this.clearData();
193 | } else if (!this.clearOnUnselectedClosing) {
194 | this.handleClose();
195 | }
196 | }
197 |
198 | clearData() {
199 | this.data = [];
200 | this.activeIndex = -1;
201 | this.active = false;
202 | }
203 |
204 | clearSelection(clearOnlyValue = false) {
205 | if (this.value != "") {
206 | this.unselected.emit({
207 | text: this.text,
208 | value: this.value
209 | });
210 | if (this.clearOnUnselectedClosing) {
211 | this.value = "";
212 | }
213 | }
214 | if (!clearOnlyValue && this.clearOnUnselectedClosing) {
215 | this.text = "";
216 | }
217 | }
218 |
219 | async prepareSuggestions(text) {
220 | if (this.suggestionGenerator && text.length >= this.minInput) {
221 | let suggestions = await this.suggestionGenerator(text);
222 | suggestions.splice(this.maxSuggestions);
223 | this.data = suggestions;
224 | } else {
225 | this.data = [];
226 | }
227 | }
228 |
229 | render() {
230 | return (
231 |
232 |
this.handleKeyDown(e)}
235 | onKeyUp={(e) => this.handleKeyUp(e.key, e.target['value'])}
236 | onBlur={(e) => { this.handleBlur(e) }}
237 | onFocus={(e) => { this.handleFocus(e) }}
238 | type="text"
239 | inputMode={this.inputmode}
240 | id={this.inputId}
241 | required={this.required}
242 | autocomplete="off"
243 | disabled={this.disabled}
244 | placeholder={this.placeholder}
245 | value={this.text}
246 | />
247 | { this.data && this.data.length > 0
248 | ?
{this.data.map((suggestion, index) => {
249 | return
253 | })}
254 | : ""
255 | }
256 |
257 | );
258 | }
259 | }
--------------------------------------------------------------------------------
/src/components/web-complete/readme.md:
--------------------------------------------------------------------------------
1 | # web-complete
2 |
3 |
4 |
5 |
6 |
7 |
8 | ## Properties
9 |
10 | | Property | Attribute | Description | Type | Default |
11 | | -------------------------- | ----------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
12 | | `clearOnUnselectedClosing` | `clear-on-unselected-closing` | If no value is selected, clear the input and emit unselected, if false, the value will not be cleared (usefull for suggesting values on a free text search) | `boolean` | `true` |
13 | | `cssClasses` | -- | The class names, which should be set on the rendered html elements | `{ wrapper: string; input: string; suggestions: string; suggestion: string; active: string; }` | `{ wrapper: "", input: "", suggestions: "suggestions", suggestion: "suggestion", active: "active" }` |
14 | | `disabled` | `disabled` | Enable/Disable the input field | `boolean` | `false` |
15 | | `inputId` | `input-id` | id of the input field | `string` | `""` |
16 | | `inputmode` | `inputmode` | A hint to the browser for which keyboard to display. Possible values: `"none"`, `"text"`, `"tel"`, `"url"`, `"email"`, `"numeric"`, `"decimal"`, and `"search"`. | `"decimal" \| "email" \| "none" \| "numeric" \| "search" \| "tel" \| "text" \| "url"` | `undefined` |
17 | | `maxSuggestions` | `max-suggestions` | The maximally shown suggestions in the list | `number` | `5` |
18 | | `minInput` | `min-input` | The minimum input size for generating suggestions | `number` | `0` |
19 | | `placeholder` | `placeholder` | The placeholder for the input field | `string` | `""` |
20 | | `required` | `required` | Form validation: is the form input required | `boolean` | `false` |
21 | | `suggestionGenerator` | -- | Async suggestion generator: `text` is the displayed for users in the form after selection (if no `suggesion` also as suggesion) `value` is the actual value of the form field optional `suggesion` if the autocomplete suggestion item should be formatted differently than `text` | `(text: string) => Promise<{ text: string; value: string; suggestion?: string; }[]>` | `undefined` |
22 | | `text` | `text` | The text is displayed by the form field for users | `string` | `""` |
23 | | `value` | `value` | The actual value of the form field | `string` | `""` |
24 | | `emptySuggestionTime` | `empty-suggestion-time` | Milliseconds before diplaying autocomplete, even if it's empty or nothing is type in the input. It allow to inspire users for example. Use -1 to disable it. | `number` | `-1` |
25 |
26 |
27 |
28 | ## Events
29 |
30 | | Event | Description | Type |
31 | | ------------ | -------------------------------------------------- | ------------------ |
32 | | `selected` | Emitted when an item from suggestions was selected | `CustomEvent` |
33 | | `unselected` | Emitted when item was cleared/unselected | `CustomEvent` |
34 |
35 |
36 | ## Methods
37 |
38 | ### `clear() => Promise`
39 |
40 | Clears the form field (suggestions and selection)
41 |
42 | #### Returns
43 |
44 | Type: `Promise`
45 |
46 |
47 |
48 | ### `getText() => Promise`
49 |
50 | Returns the `text` of the selected item
51 |
52 | #### Returns
53 |
54 | Type: `Promise`
55 |
56 |
57 |
58 | ### `getValue() => Promise`
59 |
60 | Returns the `value` of the selected item
61 |
62 | #### Returns
63 |
64 | Type: `Promise`
65 |
66 |
67 |
68 |
69 | ----------------------------------------------
70 |
71 | *Built with [StencilJS](https://stenciljs.com/)*
72 |
--------------------------------------------------------------------------------
/docs/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Web Complete Demo
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
web-complete
20 |
A lightweight, dependency-free, styleable autocomplete web component
21 |
22 |
You can find some usage examples below. Check out the source code on Github or install via npm.
23 |
24 |
Github
25 |
26 |
npm
27 |
28 |
29 |
30 |
31 |
32 | Basic example
33 |
41 |
43 |
77 |
78 |
79 |
80 | Add a clear button and custom suggestions
81 |
96 |
98 |
138 |
139 |
140 |
141 | Form validation
142 |
157 |
159 |
200 |
201 |
202 |
203 | Keep text also if no suggestion fits
204 |
216 |
218 |
244 |
245 |
246 |
247 |
248 |
249 |
--------------------------------------------------------------------------------
/docs/countries.json:
--------------------------------------------------------------------------------
1 | [
2 | {"name": "Afghanistan", "code": "AF"},
3 | {"name": "Åland Islands", "code": "AX"},
4 | {"name": "Albania", "code": "AL"},
5 | {"name": "Algeria", "code": "DZ"},
6 | {"name": "American Samoa", "code": "AS"},
7 | {"name": "AndorrA", "code": "AD"},
8 | {"name": "Angola", "code": "AO"},
9 | {"name": "Anguilla", "code": "AI"},
10 | {"name": "Antarctica", "code": "AQ"},
11 | {"name": "Antigua and Barbuda", "code": "AG"},
12 | {"name": "Argentina", "code": "AR"},
13 | {"name": "Armenia", "code": "AM"},
14 | {"name": "Aruba", "code": "AW"},
15 | {"name": "Australia", "code": "AU"},
16 | {"name": "Austria", "code": "AT"},
17 | {"name": "Azerbaijan", "code": "AZ"},
18 | {"name": "Bahamas", "code": "BS"},
19 | {"name": "Bahrain", "code": "BH"},
20 | {"name": "Bangladesh", "code": "BD"},
21 | {"name": "Barbados", "code": "BB"},
22 | {"name": "Belarus", "code": "BY"},
23 | {"name": "Belgium", "code": "BE"},
24 | {"name": "Belize", "code": "BZ"},
25 | {"name": "Benin", "code": "BJ"},
26 | {"name": "Bermuda", "code": "BM"},
27 | {"name": "Bhutan", "code": "BT"},
28 | {"name": "Bolivia", "code": "BO"},
29 | {"name": "Bosnia and Herzegovina", "code": "BA"},
30 | {"name": "Botswana", "code": "BW"},
31 | {"name": "Bouvet Island", "code": "BV"},
32 | {"name": "Brazil", "code": "BR"},
33 | {"name": "British Indian Ocean Territory", "code": "IO"},
34 | {"name": "Brunei Darussalam", "code": "BN"},
35 | {"name": "Bulgaria", "code": "BG"},
36 | {"name": "Burkina Faso", "code": "BF"},
37 | {"name": "Burundi", "code": "BI"},
38 | {"name": "Cambodia", "code": "KH"},
39 | {"name": "Cameroon", "code": "CM"},
40 | {"name": "Canada", "code": "CA"},
41 | {"name": "Cape Verde", "code": "CV"},
42 | {"name": "Cayman Islands", "code": "KY"},
43 | {"name": "Central African Republic", "code": "CF"},
44 | {"name": "Chad", "code": "TD"},
45 | {"name": "Chile", "code": "CL"},
46 | {"name": "China", "code": "CN"},
47 | {"name": "Christmas Island", "code": "CX"},
48 | {"name": "Cocos (Keeling) Islands", "code": "CC"},
49 | {"name": "Colombia", "code": "CO"},
50 | {"name": "Comoros", "code": "KM"},
51 | {"name": "Congo", "code": "CG"},
52 | {"name": "Congo, The Democratic Republic of the", "code": "CD"},
53 | {"name": "Cook Islands", "code": "CK"},
54 | {"name": "Costa Rica", "code": "CR"},
55 | {"name": "Cote D'Ivoire", "code": "CI"},
56 | {"name": "Croatia", "code": "HR"},
57 | {"name": "Cuba", "code": "CU"},
58 | {"name": "Cyprus", "code": "CY"},
59 | {"name": "Czech Republic", "code": "CZ"},
60 | {"name": "Denmark", "code": "DK"},
61 | {"name": "Djibouti", "code": "DJ"},
62 | {"name": "Dominica", "code": "DM"},
63 | {"name": "Dominican Republic", "code": "DO"},
64 | {"name": "Ecuador", "code": "EC"},
65 | {"name": "Egypt", "code": "EG"},
66 | {"name": "El Salvador", "code": "SV"},
67 | {"name": "Equatorial Guinea", "code": "GQ"},
68 | {"name": "Eritrea", "code": "ER"},
69 | {"name": "Estonia", "code": "EE"},
70 | {"name": "Ethiopia", "code": "ET"},
71 | {"name": "Falkland Islands (Malvinas)", "code": "FK"},
72 | {"name": "Faroe Islands", "code": "FO"},
73 | {"name": "Fiji", "code": "FJ"},
74 | {"name": "Finland", "code": "FI"},
75 | {"name": "France", "code": "FR"},
76 | {"name": "French Guiana", "code": "GF"},
77 | {"name": "French Polynesia", "code": "PF"},
78 | {"name": "French Southern Territories", "code": "TF"},
79 | {"name": "Gabon", "code": "GA"},
80 | {"name": "Gambia", "code": "GM"},
81 | {"name": "Georgia", "code": "GE"},
82 | {"name": "Germany", "code": "DE"},
83 | {"name": "Ghana", "code": "GH"},
84 | {"name": "Gibraltar", "code": "GI"},
85 | {"name": "Greece", "code": "GR"},
86 | {"name": "Greenland", "code": "GL"},
87 | {"name": "Grenada", "code": "GD"},
88 | {"name": "Guadeloupe", "code": "GP"},
89 | {"name": "Guam", "code": "GU"},
90 | {"name": "Guatemala", "code": "GT"},
91 | {"name": "Guernsey", "code": "GG"},
92 | {"name": "Guinea", "code": "GN"},
93 | {"name": "Guinea-Bissau", "code": "GW"},
94 | {"name": "Guyana", "code": "GY"},
95 | {"name": "Haiti", "code": "HT"},
96 | {"name": "Heard Island and Mcdonald Islands", "code": "HM"},
97 | {"name": "Holy See (Vatican City State)", "code": "VA"},
98 | {"name": "Honduras", "code": "HN"},
99 | {"name": "Hong Kong", "code": "HK"},
100 | {"name": "Hungary", "code": "HU"},
101 | {"name": "Iceland", "code": "IS"},
102 | {"name": "India", "code": "IN"},
103 | {"name": "Indonesia", "code": "ID"},
104 | {"name": "Iran, Islamic Republic Of", "code": "IR"},
105 | {"name": "Iraq", "code": "IQ"},
106 | {"name": "Ireland", "code": "IE"},
107 | {"name": "Isle of Man", "code": "IM"},
108 | {"name": "Israel", "code": "IL"},
109 | {"name": "Italy", "code": "IT"},
110 | {"name": "Jamaica", "code": "JM"},
111 | {"name": "Japan", "code": "JP"},
112 | {"name": "Jersey", "code": "JE"},
113 | {"name": "Jordan", "code": "JO"},
114 | {"name": "Kazakhstan", "code": "KZ"},
115 | {"name": "Kenya", "code": "KE"},
116 | {"name": "Kiribati", "code": "KI"},
117 | {"name": "Korea, Democratic People'S Republic of", "code": "KP"},
118 | {"name": "Korea, Republic of", "code": "KR"},
119 | {"name": "Kuwait", "code": "KW"},
120 | {"name": "Kyrgyzstan", "code": "KG"},
121 | {"name": "Lao People'S Democratic Republic", "code": "LA"},
122 | {"name": "Latvia", "code": "LV"},
123 | {"name": "Lebanon", "code": "LB"},
124 | {"name": "Lesotho", "code": "LS"},
125 | {"name": "Liberia", "code": "LR"},
126 | {"name": "Libyan Arab Jamahiriya", "code": "LY"},
127 | {"name": "Liechtenstein", "code": "LI"},
128 | {"name": "Lithuania", "code": "LT"},
129 | {"name": "Luxembourg", "code": "LU"},
130 | {"name": "Macao", "code": "MO"},
131 | {"name": "Macedonia, The Former Yugoslav Republic of", "code": "MK"},
132 | {"name": "Madagascar", "code": "MG"},
133 | {"name": "Malawi", "code": "MW"},
134 | {"name": "Malaysia", "code": "MY"},
135 | {"name": "Maldives", "code": "MV"},
136 | {"name": "Mali", "code": "ML"},
137 | {"name": "Malta", "code": "MT"},
138 | {"name": "Marshall Islands", "code": "MH"},
139 | {"name": "Martinique", "code": "MQ"},
140 | {"name": "Mauritania", "code": "MR"},
141 | {"name": "Mauritius", "code": "MU"},
142 | {"name": "Mayotte", "code": "YT"},
143 | {"name": "Mexico", "code": "MX"},
144 | {"name": "Micronesia, Federated States of", "code": "FM"},
145 | {"name": "Moldova, Republic of", "code": "MD"},
146 | {"name": "Monaco", "code": "MC"},
147 | {"name": "Mongolia", "code": "MN"},
148 | {"name": "Montserrat", "code": "MS"},
149 | {"name": "Morocco", "code": "MA"},
150 | {"name": "Mozambique", "code": "MZ"},
151 | {"name": "Myanmar", "code": "MM"},
152 | {"name": "Namibia", "code": "NA"},
153 | {"name": "Nauru", "code": "NR"},
154 | {"name": "Nepal", "code": "NP"},
155 | {"name": "Netherlands", "code": "NL"},
156 | {"name": "Netherlands Antilles", "code": "AN"},
157 | {"name": "New Caledonia", "code": "NC"},
158 | {"name": "New Zealand", "code": "NZ"},
159 | {"name": "Nicaragua", "code": "NI"},
160 | {"name": "Niger", "code": "NE"},
161 | {"name": "Nigeria", "code": "NG"},
162 | {"name": "Niue", "code": "NU"},
163 | {"name": "Norfolk Island", "code": "NF"},
164 | {"name": "Northern Mariana Islands", "code": "MP"},
165 | {"name": "Norway", "code": "NO"},
166 | {"name": "Oman", "code": "OM"},
167 | {"name": "Pakistan", "code": "PK"},
168 | {"name": "Palau", "code": "PW"},
169 | {"name": "Palestinian Territory, Occupied", "code": "PS"},
170 | {"name": "Panama", "code": "PA"},
171 | {"name": "Papua New Guinea", "code": "PG"},
172 | {"name": "Paraguay", "code": "PY"},
173 | {"name": "Peru", "code": "PE"},
174 | {"name": "Philippines", "code": "PH"},
175 | {"name": "Pitcairn", "code": "PN"},
176 | {"name": "Poland", "code": "PL"},
177 | {"name": "Portugal", "code": "PT"},
178 | {"name": "Puerto Rico", "code": "PR"},
179 | {"name": "Qatar", "code": "QA"},
180 | {"name": "Reunion", "code": "RE"},
181 | {"name": "Romania", "code": "RO"},
182 | {"name": "Russian Federation", "code": "RU"},
183 | {"name": "RWANDA", "code": "RW"},
184 | {"name": "Saint Helena", "code": "SH"},
185 | {"name": "Saint Kitts and Nevis", "code": "KN"},
186 | {"name": "Saint Lucia", "code": "LC"},
187 | {"name": "Saint Pierre and Miquelon", "code": "PM"},
188 | {"name": "Saint Vincent and the Grenadines", "code": "VC"},
189 | {"name": "Samoa", "code": "WS"},
190 | {"name": "San Marino", "code": "SM"},
191 | {"name": "Sao Tome and Principe", "code": "ST"},
192 | {"name": "Saudi Arabia", "code": "SA"},
193 | {"name": "Senegal", "code": "SN"},
194 | {"name": "Serbia and Montenegro", "code": "CS"},
195 | {"name": "Seychelles", "code": "SC"},
196 | {"name": "Sierra Leone", "code": "SL"},
197 | {"name": "Singapore", "code": "SG"},
198 | {"name": "Slovakia", "code": "SK"},
199 | {"name": "Slovenia", "code": "SI"},
200 | {"name": "Solomon Islands", "code": "SB"},
201 | {"name": "Somalia", "code": "SO"},
202 | {"name": "South Africa", "code": "ZA"},
203 | {"name": "South Georgia and the South Sandwich Islands", "code": "GS"},
204 | {"name": "Spain", "code": "ES"},
205 | {"name": "Sri Lanka", "code": "LK"},
206 | {"name": "Sudan", "code": "SD"},
207 | {"name": "Suriname", "code": "SR"},
208 | {"name": "Svalbard and Jan Mayen", "code": "SJ"},
209 | {"name": "Swaziland", "code": "SZ"},
210 | {"name": "Sweden", "code": "SE"},
211 | {"name": "Switzerland", "code": "CH"},
212 | {"name": "Syrian Arab Republic", "code": "SY"},
213 | {"name": "Taiwan, Province of China", "code": "TW"},
214 | {"name": "Tajikistan", "code": "TJ"},
215 | {"name": "Tanzania, United Republic of", "code": "TZ"},
216 | {"name": "Thailand", "code": "TH"},
217 | {"name": "Timor-Leste", "code": "TL"},
218 | {"name": "Togo", "code": "TG"},
219 | {"name": "Tokelau", "code": "TK"},
220 | {"name": "Tonga", "code": "TO"},
221 | {"name": "Trinidad and Tobago", "code": "TT"},
222 | {"name": "Tunisia", "code": "TN"},
223 | {"name": "Turkey", "code": "TR"},
224 | {"name": "Turkmenistan", "code": "TM"},
225 | {"name": "Turks and Caicos Islands", "code": "TC"},
226 | {"name": "Tuvalu", "code": "TV"},
227 | {"name": "Uganda", "code": "UG"},
228 | {"name": "Ukraine", "code": "UA"},
229 | {"name": "United Arab Emirates", "code": "AE"},
230 | {"name": "United Kingdom", "code": "GB"},
231 | {"name": "United States", "code": "US"},
232 | {"name": "United States Minor Outlying Islands", "code": "UM"},
233 | {"name": "Uruguay", "code": "UY"},
234 | {"name": "Uzbekistan", "code": "UZ"},
235 | {"name": "Vanuatu", "code": "VU"},
236 | {"name": "Venezuela", "code": "VE"},
237 | {"name": "Viet Nam", "code": "VN"},
238 | {"name": "Virgin Islands, British", "code": "VG"},
239 | {"name": "Virgin Islands, U.S.", "code": "VI"},
240 | {"name": "Wallis and Futuna", "code": "WF"},
241 | {"name": "Western Sahara", "code": "EH"},
242 | {"name": "Yemen", "code": "YE"},
243 | {"name": "Zambia", "code": "ZM"},
244 | {"name": "Zimbabwe", "code": "ZW"}
245 | ]
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |  [](https://www.webcomponents.org/element/web-complete) [](https://travis-ci.com/stefanhuber/web-complete)
2 |
3 | > `web-complete` is a lightweight, dependency-free, styleable autocomplete web component.
4 |
5 | # Installation
6 |
7 | ## Script tag
8 |
9 | ```html
10 |
11 |
12 | ```
13 |
14 | ## Node Module
15 |
16 | - Install via npm: `npm install web-complete --save`
17 | - Add script to html: ``
18 | - Or import as JS module: `import 'web-complete';`
19 |
20 | ## Framework integration
21 |
22 | For integration with different frameworks the [stencil docs](https://stenciljs.com/docs/overview) should be consulted.
23 |
24 | # Using this component
25 |
26 | Add the component to your html:
27 | ```html
28 |
29 | ```
30 |
31 | Add some javascript for additional configuration:
32 | ```javascript
33 | const webcomplete = document.querySelector('#my-web-complete');
34 |
35 | // change css classes for styling
36 | webcomplete.cssClasses = {
37 | "wrapper": "dropdown",
38 | "input": "form-control",
39 | "suggestions": "dropdown-menu show",
40 | "suggestion": "dropdown-item",
41 | "active": "active"
42 | };
43 |
44 | // add an async suggestion generator
45 | webcomplete.suggestionGenerator = function(text) {
46 | return new Promise(function(resolve, reject) {
47 | // generate suggestions with input text
48 | // e.g. by using http fetch
49 | });
50 | };
51 |
52 | // listen to selected/unselected events
53 | webcomplete.addEventListener('selected', function(e) {
54 | // suggestion selected (e.detail)
55 | });
56 | webcomplete.addEventListener('unselected', function(e) {
57 | // suggestion unselected (e.detail)
58 | });
59 | ```
60 |
61 | A full example with [Bootstrap 4 Dropdown](https://getbootstrap.com/docs/4.3/components/dropdowns/) theming can be found [here](https://github.com/stefanhuber/web-complete/blob/master/docs/index.html).
62 |
63 | # Component API
64 |
65 | ## Properties
66 |
67 | | Property | Attribute | Description | Type | Default |
68 | | -------------------------- | ----------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
69 | | `clearOnUnselectedClosing` | `clear-on-unselected-closing` | If no value is selected, clear the input and emit unselected, if false, the value will not be cleared (usefull for suggesting values on a free text search) | `boolean` | `true` |
70 | | `cssClasses` | -- | The class names, which should be set on the rendered html elements | `{ wrapper: string; input: string; suggestions: string; suggestion: string; active: string; }` | `{ wrapper: "", input: "", suggestions: "suggestions", suggestion: "suggestion", active: "active" }` |
71 | | `disabled` | `disabled` | Enable/Disable the input field | `boolean` | `false` |
72 | | `inputId` | `input-id` | id of the input field | `string` | `""` |
73 | | `inputmode` | `inputmode` | A hint to the browser for which keyboard to display. Possible values: `"none"`, `"text"`, `"tel"`, `"url"`, `"email"`, `"numeric"`, `"decimal"`, and `"search"`. | `"decimal" \| "email" \| "none" \| "numeric" \| "search" \| "tel" \| "text" \| "url"` | `undefined` |
74 | | `maxSuggestions` | `max-suggestions` | The maximally shown suggestions in the list | `number` | `5` |
75 | | `minInput` | `min-input` | The minimum input size for generating suggestions | `number` | `0` |
76 | | `placeholder` | `placeholder` | The placeholder for the input field | `string` | `""` |
77 | | `required` | `required` | Form validation: is the form input required | `boolean` | `false` |
78 | | `suggestionGenerator` | -- | Async suggestion generator: `text` is the displayed for users in the form after selection (if no `suggesion` also as suggesion) `value` is the actual value of the form field optional `suggesion` if the autocomplete suggestion item should be formatted differently than `text` | `(text: string) => Promise<{ text: string; value: string; suggestion?: string; }[]>` | `undefined` |
79 | | `text` | `text` | The text is displayed by the form field for users | `string` | `""` |
80 | | `value` | `value` | The actual value of the form field | `string` | `""` |
81 | | `emptySuggestionTime` | `empty-suggestion-time` | Milliseconds before diplaying autocomplete, even if it's empty or nothing is type in the input. It allow to inspire users for example. Use -1 to disable it. | `number` | `-1` |
82 |
83 |
84 | ## Events
85 |
86 | | Event | Description | Type |
87 | | ------------ | -------------------------------------------------- | ------------------ |
88 | | `selected` | Emitted when an item from suggestions was selected | `CustomEvent` |
89 | | `unselected` | Emitted when item was cleared/unselected | `CustomEvent` |
90 |
91 |
92 | ## Methods
93 |
94 | ### `clear() => Promise`
95 |
96 | Clears the form field (suggestions and selection)
97 |
98 | #### Returns
99 |
100 | Type: `Promise`
101 |
102 |
103 |
104 | ### `getText() => Promise`
105 |
106 | Returns the `text` of the selected item
107 |
108 | #### Returns
109 |
110 | Type: `Promise`
111 |
112 |
113 |
114 | ### `getValue() => Promise`
115 |
116 | Returns the `value` of the selected item
117 |
118 | #### Returns
119 |
120 | Type: `Promise`
121 |
122 |
123 | # Developer
124 |
125 | ```
126 | npm i install dependencies
127 | npm start start local development
128 | npm run build build component for production
129 | npm test run e2e tests
130 | ```
131 |
--------------------------------------------------------------------------------