├── .browserslistrc ├── vue3-simple-typeahead.gif ├── vue3-simple-typeahead.webm ├── dev ├── serve.js └── serve.vue ├── .gitignore ├── babel.config.js ├── src ├── entry.js ├── entry.esm.js └── vue3-simple-typeahead.vue ├── LICENSE ├── dist ├── vue3-simple-typeahead.css ├── vue3-simple-typeahead.min.js ├── vue3-simple-typeahead.esm.js └── vue3-simple-typeahead.ssr.js ├── package.json └── README.md /.browserslistrc: -------------------------------------------------------------------------------- 1 | current node 2 | last 2 versions and > 2% 3 | ie > 10 4 | -------------------------------------------------------------------------------- /vue3-simple-typeahead.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frikinside/vue3-simple-typeahead/HEAD/vue3-simple-typeahead.gif -------------------------------------------------------------------------------- /vue3-simple-typeahead.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frikinside/vue3-simple-typeahead/HEAD/vue3-simple-typeahead.webm -------------------------------------------------------------------------------- /dev/serve.js: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue'; 2 | import Dev from './serve.vue'; 3 | 4 | const app = createApp(Dev); 5 | app.mount('#app'); 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | 4 | # local env files 5 | .env.local 6 | .env.*.local 7 | 8 | # Log files 9 | npm-debug.log* 10 | yarn-debug.log* 11 | yarn-error.log* 12 | 13 | # Editor directories and files 14 | .idea 15 | .vscode 16 | *.suo 17 | *.ntvs* 18 | *.njsproj 19 | *.sln 20 | *.sw* 21 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | const devPresets = ['@vue/babel-preset-app']; 2 | const buildPresets = [ 3 | [ 4 | '@babel/preset-env', 5 | // Config for @babel/preset-env 6 | { 7 | // Example: Always transpile optional chaining/nullish coalescing 8 | // include: [ 9 | // /(optional-chaining|nullish-coalescing)/ 10 | // ], 11 | }, 12 | ], 13 | ]; 14 | module.exports = { 15 | presets: (process.env.NODE_ENV === 'development' ? devPresets : buildPresets), 16 | }; 17 | -------------------------------------------------------------------------------- /src/entry.js: -------------------------------------------------------------------------------- 1 | // iife/cjs usage extends esm default export - so import it all 2 | import component, * as namedExports from '@/entry.esm'; 3 | 4 | // Attach named exports directly to component. IIFE/CJS will 5 | // only expose one global var, with named exports exposed as properties of 6 | // that global var (eg. plugin.namedExport) 7 | Object.entries(namedExports).forEach(([exportName, exported]) => { 8 | if (exportName !== 'default') component[exportName] = exported; 9 | }); 10 | 11 | export default component; 12 | -------------------------------------------------------------------------------- /src/entry.esm.js: -------------------------------------------------------------------------------- 1 | 2 | // Import vue component 3 | import component from '@/vue3-simple-typeahead.vue'; 4 | 5 | // Default export is installable instance of component. 6 | // IIFE injects install function into component, allowing component 7 | // to be registered via Vue.use() as well as Vue.component(), 8 | export default /*#__PURE__*/(() => { 9 | // Get component instance 10 | const installable = component; 11 | 12 | // Attach install function executed by Vue.use() 13 | installable.install = (app) => { 14 | app.component('Vue3SimpleTypeahead', installable); 15 | }; 16 | return installable; 17 | })(); 18 | 19 | // It's possible to expose named exports when writing components that can 20 | // also be used as directives, etc. - eg. import { RollupDemoDirective } from 'rollup-demo'; 21 | // export const RollupDemoDirective = directive; 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2021 frikinside 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | -------------------------------------------------------------------------------- /dist/vue3-simple-typeahead.css: -------------------------------------------------------------------------------- 1 | 2 | .simple-typeahead[data-v-f81ca714] { 3 | position: relative; 4 | width: 100%; 5 | } 6 | .simple-typeahead > input[data-v-f81ca714] { 7 | margin-bottom: 0; 8 | } 9 | .simple-typeahead .simple-typeahead-list[data-v-f81ca714] { 10 | position: absolute; 11 | width: 100%; 12 | border: none; 13 | max-height: 400px; 14 | overflow-y: auto; 15 | border-bottom: 0.1rem solid #d1d1d1; 16 | z-index: 9; 17 | } 18 | .simple-typeahead .simple-typeahead-list .simple-typeahead-list-header[data-v-f81ca714] { 19 | background-color: #fafafa; 20 | padding: 0.6rem 1rem; 21 | border-bottom: 0.1rem solid #d1d1d1; 22 | border-left: 0.1rem solid #d1d1d1; 23 | border-right: 0.1rem solid #d1d1d1; 24 | } 25 | .simple-typeahead .simple-typeahead-list .simple-typeahead-list-footer[data-v-f81ca714] { 26 | background-color: #fafafa; 27 | padding: 0.6rem 1rem; 28 | border-left: 0.1rem solid #d1d1d1; 29 | border-right: 0.1rem solid #d1d1d1; 30 | } 31 | .simple-typeahead .simple-typeahead-list .simple-typeahead-list-item[data-v-f81ca714] { 32 | cursor: pointer; 33 | background-color: #fafafa; 34 | padding: 0.6rem 1rem; 35 | border-bottom: 0.1rem solid #d1d1d1; 36 | border-left: 0.1rem solid #d1d1d1; 37 | border-right: 0.1rem solid #d1d1d1; 38 | } 39 | .simple-typeahead .simple-typeahead-list .simple-typeahead-list-item[data-v-f81ca714]:last-child { 40 | border-bottom: none; 41 | } 42 | .simple-typeahead .simple-typeahead-list .simple-typeahead-list-item.simple-typeahead-list-item-active[data-v-f81ca714] { 43 | background-color: #e1e1e1; 44 | } 45 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue3-simple-typeahead", 3 | "version": "1.0.11", 4 | "description": "A simple and lightweight Vue3 typeahead component that show a suggested list of elements while the user types in.", 5 | "author": "frikinside (http://frikinside.net/)", 6 | "license": "MIT", 7 | "homepage": "https://github.com/frikinside/vue3-simple-typeahead#readme", 8 | "repository": { 9 | "type": "git", 10 | "url": "https://github.com/frikinside/vue3-simple-typeahead.git" 11 | }, 12 | "bugs": { 13 | "url": "https://github.com/frikinside/vue3-simple-typeahead/issues", 14 | "email": "friki.inside@gmail.com" 15 | }, 16 | "keywords": [ 17 | "autocomplete", 18 | "autosuggest", 19 | "typeahead", 20 | "select", 21 | "search", 22 | "searchbox", 23 | "enhanced input", 24 | "component", 25 | "vue-component", 26 | "vue3-simple-typeahead", 27 | "vue3-typeahead", 28 | "vue-typeahead", 29 | "vue-autocomplete", 30 | "v-typeahead", 31 | "v-autocomplete", 32 | "vue", 33 | "vue3", 34 | "vue3js", 35 | "vuejs3" 36 | ], 37 | "main": "dist/vue3-simple-typeahead.ssr.js", 38 | "browser": "dist/vue3-simple-typeahead.esm.js", 39 | "module": "dist/vue3-simple-typeahead.esm.js", 40 | "unpkg": "dist/vue3-simple-typeahead.min.js", 41 | "style": "dist/vue3-simple-typeahead.css", 42 | "files": [ 43 | "dist/*", 44 | "src/**/*.vue", 45 | "LICENSE", 46 | "README.md" 47 | ], 48 | "sideEffects": [ 49 | "*.css" 50 | ], 51 | "scripts": { 52 | "serve": "vue-cli-service serve dev/serve.js", 53 | "prebuild": "rimraf ./dist", 54 | "build": "cross-env NODE_ENV=production rollup --config build/rollup.config.js", 55 | "build:ssr": "cross-env NODE_ENV=production rollup --config build/rollup.config.js --format cjs", 56 | "build:es": "cross-env NODE_ENV=production rollup --config build/rollup.config.js --format es", 57 | "build:unpkg": "cross-env NODE_ENV=production rollup --config build/rollup.config.js --format iife" 58 | }, 59 | "dependencies": {}, 60 | "devDependencies": { 61 | "@babel/core": "^7.14.6", 62 | "@babel/preset-env": "^7.14.7", 63 | "@rollup/plugin-alias": "^3.1.2", 64 | "@rollup/plugin-babel": "^5.3.0", 65 | "@rollup/plugin-commonjs": "^14.0.0", 66 | "@rollup/plugin-node-resolve": "^9.0.0", 67 | "@rollup/plugin-replace": "^2.4.2", 68 | "@vue/cli-plugin-babel": "^4.5.13", 69 | "@vue/cli-service": "^4.5.13", 70 | "@vue/compiler-sfc": "^3.0.11", 71 | "cross-env": "^7.0.3", 72 | "minimist": "^1.2.5", 73 | "postcss": "^8.2.10", 74 | "rimraf": "^3.0.2", 75 | "rollup": "^2.52.8", 76 | "rollup-plugin-postcss": "^4.0.0", 77 | "rollup-plugin-terser": "^7.0.2", 78 | "rollup-plugin-vue": "^6.0.0", 79 | "vue": "^3.0.5" 80 | }, 81 | "peerDependencies": { 82 | "vue": "^3.0.5" 83 | }, 84 | "engines": { 85 | "node": ">=12" 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /dist/vue3-simple-typeahead.min.js: -------------------------------------------------------------------------------- 1 | var Vue3SimpleTypeahead=function(e){"use strict";function t(e,t){return function(e){if(Array.isArray(e))return e}(e)||function(e,t){var n=null==e?null:"undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(null==n)return;var i,r,o=[],s=!0,l=!1;try{for(n=n.call(e);!(s=(i=n.next()).done)&&(o.push(i.value),!t||o.length!==t);s=!0);}catch(e){l=!0,r=e}finally{try{s||null==n.return||n.return()}finally{if(l)throw r}}return o}(e,t)||function(e,t){if(!e)return;if("string"==typeof e)return n(e,t);var i=Object.prototype.toString.call(e).slice(8,-1);"Object"===i&&e.constructor&&(i=e.constructor.name);if("Map"===i||"Set"===i)return Array.from(e);if("Arguments"===i||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(i))return n(e,t)}(e,t)||function(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function n(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,i=new Array(t);n=0}},minItemLength:{type:Number,default:0,validator:function(e){return e>=0}},selectOnTab:{type:Boolean,default:!0}},mounted:function(){void 0!==this.defaultItem&&null!==this.defaultItem&&this.selectItem(this.defaultItem)},data:function(){return{inputId:this.id||"simple_typeahead_".concat((1e3*Math.random()).toFixed()),input:"",isInputFocused:!1,currentSelectionIndex:0}},methods:{onInput:function(){this.isListVisible&&this.currentSelectionIndex>=this.filteredItems.length&&(this.currentSelectionIndex=(this.filteredItems.length||1)-1),this.$emit("onInput",{input:this.input,items:this.filteredItems})},onFocus:function(){this.isInputFocused=!0,this.$emit("onFocus",{input:this.input,items:this.filteredItems})},onBlur:function(){this.isInputFocused=!1,this.$emit("onBlur",{input:this.input,items:this.filteredItems})},onArrowDown:function(e){this.isListVisible&&this.currentSelectionIndex0&&this.currentSelectionIndex--,this.scrollSelectionIntoView()},scrollSelectionIntoView:function(){var e=this;setTimeout((function(){var t=document.querySelector("#".concat(e.wrapperId," .simple-typeahead-list")),n=document.querySelector("#".concat(e.wrapperId," .simple-typeahead-list-item.simple-typeahead-list-item-active"));if(!(n.offsetTop>=t.scrollTop&&n.offsetTop+n.offsetHeightt.scrollTop?i=n.offsetTop+n.offsetHeight-t.offsetHeight:n.offsetTop$1")},clearInput:function(){this.input=""},getInput:function(){return this.$refs.inputRef},focusInput:function(){this.$refs.inputRef.focus(),this.onFocus()},blurInput:function(){this.$refs.inputRef.blur(),this.onBlur()}},computed:{wrapperId:function(){return"".concat(this.inputId,"_wrapper")},filteredItems:function(){var e=this,t=new RegExp(this.escapeRegExp(this.input),"i");return this.items.filter((function(n){return e.itemProjection(n).match(t)}))},isListVisible:function(){return this.isInputFocused&&this.input.length>=this.minInputLength&&this.filteredItems.length>this.minItemLength},currentSelection:function(){return this.isListVisible&&this.currentSelectionIndex 2 |
3 | 20 |
21 |
22 |
31 | 34 | 35 |
36 | 37 |
38 |
39 | 40 | 41 | 203 | 204 | 251 | -------------------------------------------------------------------------------- /dist/vue3-simple-typeahead.esm.js: -------------------------------------------------------------------------------- 1 | import { defineComponent, pushScopeId, popScopeId, openBlock, createElementBlock, withDirectives, createElementVNode, mergeProps, withKeys, withModifiers, vModelText, renderSlot, createCommentVNode, Fragment, renderList, normalizeClass } from 'vue'; 2 | 3 | var script = /*#__PURE__*/defineComponent({ 4 | name: 'Vue3SimpleTypeahead', 5 | emits: ['onInput', 'onFocus', 'onBlur', 'selectItem'], 6 | inheritAttrs: false, 7 | props: { 8 | id: { 9 | type: String 10 | }, 11 | placeholder: { 12 | type: String, 13 | default: '' 14 | }, 15 | items: { 16 | type: Array, 17 | required: true 18 | }, 19 | defaultItem: { 20 | default: null 21 | }, 22 | itemProjection: { 23 | type: Function, 24 | 25 | default(item) { 26 | return item; 27 | } 28 | 29 | }, 30 | minInputLength: { 31 | type: Number, 32 | default: 2, 33 | validator: prop => { 34 | return prop >= 0; 35 | } 36 | }, 37 | minItemLength: { 38 | type: Number, 39 | default: 0, 40 | validator: prop => { 41 | return prop >= 0; 42 | } 43 | }, 44 | selectOnTab: { 45 | type: Boolean, 46 | default: true 47 | } 48 | }, 49 | 50 | mounted() { 51 | if (this.defaultItem !== undefined && this.defaultItem !== null) { 52 | this.selectItem(this.defaultItem); 53 | } 54 | }, 55 | 56 | data() { 57 | return { 58 | inputId: this.id || `simple_typeahead_${(Math.random() * 1000).toFixed()}`, 59 | input: '', 60 | isInputFocused: false, 61 | currentSelectionIndex: 0 62 | }; 63 | }, 64 | 65 | methods: { 66 | onInput() { 67 | if (this.isListVisible && this.currentSelectionIndex >= this.filteredItems.length) { 68 | this.currentSelectionIndex = (this.filteredItems.length || 1) - 1; 69 | } 70 | 71 | this.$emit('onInput', { 72 | input: this.input, 73 | items: this.filteredItems 74 | }); 75 | }, 76 | 77 | onFocus() { 78 | this.isInputFocused = true; 79 | this.$emit('onFocus', { 80 | input: this.input, 81 | items: this.filteredItems 82 | }); 83 | }, 84 | 85 | onBlur() { 86 | this.isInputFocused = false; 87 | this.$emit('onBlur', { 88 | input: this.input, 89 | items: this.filteredItems 90 | }); 91 | }, 92 | 93 | onArrowDown($event) { 94 | if (this.isListVisible && this.currentSelectionIndex < this.filteredItems.length - 1) { 95 | this.currentSelectionIndex++; 96 | } 97 | 98 | this.scrollSelectionIntoView(); 99 | }, 100 | 101 | onArrowUp($event) { 102 | if (this.isListVisible && this.currentSelectionIndex > 0) { 103 | this.currentSelectionIndex--; 104 | } 105 | 106 | this.scrollSelectionIntoView(); 107 | }, 108 | 109 | scrollSelectionIntoView() { 110 | setTimeout(() => { 111 | const list_node = document.querySelector(`#${this.wrapperId} .simple-typeahead-list`); 112 | const active_node = document.querySelector(`#${this.wrapperId} .simple-typeahead-list-item.simple-typeahead-list-item-active`); 113 | 114 | if (!(active_node.offsetTop >= list_node.scrollTop && active_node.offsetTop + active_node.offsetHeight < list_node.scrollTop + list_node.offsetHeight)) { 115 | let scroll_to = 0; 116 | 117 | if (active_node.offsetTop > list_node.scrollTop) { 118 | scroll_to = active_node.offsetTop + active_node.offsetHeight - list_node.offsetHeight; 119 | } else if (active_node.offsetTop < list_node.scrollTop) { 120 | scroll_to = active_node.offsetTop; 121 | } 122 | 123 | list_node.scrollTo(0, scroll_to); 124 | } 125 | }); 126 | }, 127 | 128 | selectCurrentSelection() { 129 | if (this.currentSelection) { 130 | this.selectItem(this.currentSelection); 131 | } 132 | }, 133 | 134 | selectCurrentSelectionTab() { 135 | if (this.selectOnTab) { 136 | this.selectCurrentSelection(); 137 | } else { 138 | this.$refs.inputRef.blur(); 139 | } 140 | }, 141 | 142 | selectItem(item) { 143 | this.input = this.itemProjection(item); 144 | this.currentSelectionIndex = 0; 145 | this.$refs.inputRef.blur(); 146 | this.$emit('selectItem', item); 147 | }, 148 | 149 | escapeRegExp(string) { 150 | return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); 151 | }, 152 | 153 | boldMatchText(text) { 154 | const regexp = new RegExp(`(${this.escapeRegExp(this.input)})`, 'ig'); 155 | return text.replace(regexp, '$1'); 156 | }, 157 | 158 | clearInput() { 159 | this.input = ''; 160 | }, 161 | 162 | getInput() { 163 | return this.$refs.inputRef; 164 | }, 165 | 166 | focusInput() { 167 | this.$refs.inputRef.focus(); 168 | this.onFocus(); 169 | }, 170 | 171 | blurInput() { 172 | this.$refs.inputRef.blur(); 173 | this.onBlur(); 174 | } 175 | 176 | }, 177 | computed: { 178 | wrapperId() { 179 | return `${this.inputId}_wrapper`; 180 | }, 181 | 182 | filteredItems() { 183 | const regexp = new RegExp(this.escapeRegExp(this.input), 'i'); 184 | return this.items.filter(item => this.itemProjection(item).match(regexp)); 185 | }, 186 | 187 | isListVisible() { 188 | return this.isInputFocused && this.input.length >= this.minInputLength && this.filteredItems.length > this.minItemLength; 189 | }, 190 | 191 | currentSelection() { 192 | return this.isListVisible && this.currentSelectionIndex < this.filteredItems.length ? this.filteredItems[this.currentSelectionIndex] : undefined; 193 | } 194 | 195 | } 196 | }); 197 | 198 | pushScopeId("data-v-f81ca714"); 199 | 200 | const _hoisted_1 = ["id"]; 201 | const _hoisted_2 = ["id", "placeholder"]; 202 | const _hoisted_3 = { 203 | key: 0, 204 | class: "simple-typeahead-list" 205 | }; 206 | const _hoisted_4 = { 207 | key: 0, 208 | class: "simple-typeahead-list-header" 209 | }; 210 | const _hoisted_5 = ["onClick", "onMouseenter"]; 211 | const _hoisted_6 = ["data-text"]; 212 | const _hoisted_7 = ["data-text", "innerHTML"]; 213 | const _hoisted_8 = { 214 | key: 1, 215 | class: "simple-typeahead-list-footer" 216 | }; 217 | 218 | popScopeId(); 219 | 220 | function render(_ctx, _cache, $props, $setup, $data, $options) { 221 | return openBlock(), createElementBlock("div", { 222 | id: _ctx.wrapperId, 223 | class: "simple-typeahead" 224 | }, [withDirectives(createElementVNode("input", mergeProps({ 225 | ref: "inputRef", 226 | id: _ctx.inputId, 227 | class: "simple-typeahead-input", 228 | type: "text", 229 | placeholder: _ctx.placeholder, 230 | "onUpdate:modelValue": _cache[0] || (_cache[0] = $event => _ctx.input = $event), 231 | onInput: _cache[1] || (_cache[1] = (...args) => _ctx.onInput && _ctx.onInput(...args)), 232 | onFocus: _cache[2] || (_cache[2] = (...args) => _ctx.onFocus && _ctx.onFocus(...args)), 233 | onBlur: _cache[3] || (_cache[3] = (...args) => _ctx.onBlur && _ctx.onBlur(...args)), 234 | onKeydown: [_cache[4] || (_cache[4] = withKeys(withModifiers((...args) => _ctx.onArrowDown && _ctx.onArrowDown(...args), ["prevent"]), ["down"])), _cache[5] || (_cache[5] = withKeys(withModifiers((...args) => _ctx.onArrowUp && _ctx.onArrowUp(...args), ["prevent"]), ["up"])), _cache[6] || (_cache[6] = withKeys(withModifiers((...args) => _ctx.selectCurrentSelection && _ctx.selectCurrentSelection(...args), ["prevent"]), ["enter"])), _cache[7] || (_cache[7] = withKeys(withModifiers((...args) => _ctx.selectCurrentSelectionTab && _ctx.selectCurrentSelectionTab(...args), ["prevent"]), ["tab"]))], 235 | autocomplete: "off" 236 | }, _ctx.$attrs), null, 16, _hoisted_2), [[vModelText, _ctx.input]]), _ctx.isListVisible ? (openBlock(), createElementBlock("div", _hoisted_3, [_ctx.$slots['list-header'] ? (openBlock(), createElementBlock("div", _hoisted_4, [renderSlot(_ctx.$slots, "list-header")])) : createCommentVNode("", true), (openBlock(true), createElementBlock(Fragment, null, renderList(_ctx.filteredItems, (item, index) => { 237 | return openBlock(), createElementBlock("div", { 238 | class: normalizeClass(["simple-typeahead-list-item", { 239 | 'simple-typeahead-list-item-active': _ctx.currentSelectionIndex == index 240 | }]), 241 | key: index, 242 | onMousedown: _cache[8] || (_cache[8] = withModifiers(() => {}, ["prevent"])), 243 | onClick: $event => _ctx.selectItem(item), 244 | onMouseenter: $event => _ctx.currentSelectionIndex = index 245 | }, [_ctx.$slots['list-item-text'] ? (openBlock(), createElementBlock("span", { 246 | key: 0, 247 | class: "simple-typeahead-list-item-text", 248 | "data-text": _ctx.itemProjection(item) 249 | }, [renderSlot(_ctx.$slots, "list-item-text", { 250 | item: item, 251 | itemProjection: _ctx.itemProjection, 252 | boldMatchText: _ctx.boldMatchText 253 | })], 8, _hoisted_6)) : (openBlock(), createElementBlock("span", { 254 | key: 1, 255 | class: "simple-typeahead-list-item-text", 256 | "data-text": _ctx.itemProjection(item), 257 | innerHTML: _ctx.boldMatchText(_ctx.itemProjection(item)) 258 | }, null, 8, _hoisted_7))], 42, _hoisted_5); 259 | }), 128)), _ctx.$slots['list-footer'] ? (openBlock(), createElementBlock("div", _hoisted_8, [renderSlot(_ctx.$slots, "list-footer")])) : createCommentVNode("", true)])) : createCommentVNode("", true)], 8, _hoisted_1); 260 | } 261 | 262 | script.render = render; 263 | script.__scopeId = "data-v-f81ca714"; 264 | 265 | // Import vue component 266 | // IIFE injects install function into component, allowing component 267 | // to be registered via Vue.use() as well as Vue.component(), 268 | 269 | var entry_esm = /*#__PURE__*/(() => { 270 | // Get component instance 271 | const installable = script; // Attach install function executed by Vue.use() 272 | 273 | installable.install = app => { 274 | app.component('Vue3SimpleTypeahead', installable); 275 | }; 276 | 277 | return installable; 278 | })(); // It's possible to expose named exports when writing components that can 279 | // also be used as directives, etc. - eg. import { RollupDemoDirective } from 'rollup-demo'; 280 | // export const RollupDemoDirective = directive; 281 | 282 | export { entry_esm as default }; 283 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vue3-simple-typeahead 2 | 3 | [![npm](https://img.shields.io/npm/v/vue3-simple-typeahead.svg)](https://www.npmjs.com/package/vue3-simple-typeahead) 4 | [![vue3](https://img.shields.io/badge/vue-3.x-brightgreen.svg)](https://v3.vuejs.org/) 5 | [![License](https://img.shields.io/npm/l/vue3-simple-typeahead)](https://en.wikipedia.org/wiki/MIT_License) 6 | [![npm](https://img.shields.io/npm/dt/vue3-simple-typeahead.svg)](https://www.npmjs.com/package/vue3-simple-typeahead) 7 | [![npm bundle size](https://img.shields.io/bundlephobia/min/vue3-simple-typeahead?color=brightgreen)](https://www.npmjs.com/package/vue3-simple-typeahead) 8 | 9 | A Vue3 component for a simple typeahead component. 10 | It will show a list of suggested items based on the user input. 11 | 12 | The component includes it's own input and when the user types on it the suggested options appear. 13 | 14 | ![Demo](vue3-simple-typeahead.gif) 15 | 16 | ## Demo 17 | 18 | [Go to demo page](https://vue3-simple-typeahead-demo.netlify.app/) 19 | 20 | [vue3-simple-typeahead-demo source code](https://github.com/frikinside/vue3-simple-typeahead-demo) 21 | 22 | ## Installation 23 | 24 | ### [NPM](https://www.npmjs.com/package/vue3-simple-typeahead) 25 | 26 | ```js 27 | npm install vue3-simple-typeahead 28 | ``` 29 | 30 | ### Browser 31 | 32 | You can also use the browser bundle in a script tag. 33 | 34 | ```html 35 | 36 | ``` 37 | 38 | ## Add installed component to your app 39 | 40 | Import the vue3-simple-typeahead component and register it globally in your Vue app. Import the CSS as well if you wish to use the default styling. 41 | 42 | ```js 43 | import { createApp } from 'vue'; 44 | import App from './App.vue'; 45 | import SimpleTypeahead from 'vue3-simple-typeahead'; 46 | import 'vue3-simple-typeahead/dist/vue3-simple-typeahead.css'; //Optional default CSS 47 | 48 | let app = createApp(App); 49 | app.use(SimpleTypeahead); 50 | app.mount('#app'); 51 | ``` 52 | 53 | You can also import vue3-simple-typeahead locally in your component if you prefer. 54 | 55 | ```js 56 | import SimpleTypeahead from 'vue3-simple-typeahead'; 57 | import 'vue3-simple-typeahead/dist/vue3-simple-typeahead.css'; //Optional default CSS 58 | 59 | export default { 60 | name: 'my-vue-component', 61 | components: { 62 | SimpleTypeahead, 63 | }, 64 | }; 65 | ``` 66 | 67 | ## Usage 68 | 69 | Use the component on your own app components 70 | 71 | ```html 72 | 83 | 84 | ``` 85 | 86 | With custom slots template 87 | 88 | ```html 89 | 100 | 103 | 104 | 107 | 108 | ``` 109 | 110 | ### User interaction 111 | 112 | When the user types on the typeahead input and the minimum input length is meeted a suggestion list appears below the input with the items that match the user input. 113 | You can continue to type further to filter the selection, but you could use keyboard or mouse input to make your selection.abnf 114 | 115 | When the suggestion list show up, you can continue to type to filter the selection or you use the `Arrow Up` or `Arrow Down` keys to navigate the list of suggestions. When you have selected the desired element press Enter or TAB to select the current element. 116 | 117 | | Control | Effect | 118 | | :--------------- | :----------------------------------------------------------------- | 119 | | | Navigate up on the suggestion list, selecting the previous element | 120 | | | Navigate down on the suggestion list, selecting the next element | 121 | | Enter | Choose the current element selection | 122 | | TAB | Choose the current element selection (if `selectOnTab` is `true`) | 123 | 124 | You can use the mouse instead, simply hover you cursor over the desire element and click on it. 125 | 126 | ![User controls](vue3-simple-typeahead.gif) 127 | 128 | ### Fallthrough attributes 129 | 130 | All attributes added to the component not provided by props fallthrough the input control. 131 | For example if you added the `disabled` attribute: 132 | 133 | ```html 134 | 146 | 147 | ``` 148 | 149 | It would fallthrough to the input control of the component: 150 | 151 | ```html 152 | 153 | 154 | 155 | ``` 156 | 157 | ### Props 158 | 159 | | Prop | Type | Default | Description | 160 | | :---------------------------------- | :--------------- | :------------------------- | :------------------------------------------------------------------------------------------------------- | 161 | | [`id`](#id) | String | Random id generation | The id for the input control. Can be useful to link with a `label for=""` | 162 | | [`placeholder`](#placeholder) | String | `''` | Placeholder text for the input | 163 | | [`items`](#items) | Array (Required) | | List of objects or strings with the elements for suggestions | 164 | | [`defaultItem`](#defaultItem) | Any | | Default item to be selected | 165 | | [`minInputLength`](#minInputLength) | Number | 2 | Minimum input length for the suggestion length to appear, the prop value has to be >= 0 | 166 | | [`minItemLength`](#minItemLength) | Number | 0 | Minimum number of items that need to be visible for suggestions to appear, the prop value has to be >= 0 | 167 | | [`itemProjection`](#itemProjection) | Function: String | `(item) => {return item;}` | Projection function to map the items to a string value for search and display | 168 | | [`selectOnTab`](#selectOnTab) | Boolean | `true` | Enable/Disable item selection on TAB | 169 | 170 | _Remember you can always use lower-kebap-case for camelCase props like `min-input-length`_ 171 | 172 | ### Events 173 | 174 | | Event | Signature | Description | 175 | | :-------------------------- | :--------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------- | 176 | | [`selectItem`](#selectItem) | `function (item: String): void` | Emitted when the user selects an item from the suggestion list | 177 | | [`onInput`](#onInput) | `function (event: Object { input: String, items: Array }): void` | Emitted when the user types anything | 178 | | [`onFocus`](#onFocus) | `function (event: Object { input: String, items: Array }): void` | Emitted when the input control get the focus | 179 | | [`onBlur`](#onBlur) | `function (event: Object { input: String, items: Array }): void` | Emitted when the input control lost the focus [When the user select an item, the focus is lost too] | 180 | 181 | ### Slots 182 | 183 | | Slot | Parent | Props | Description | 184 | | :----------------------------------- | :-------------------------------------- | :---------------------------------------- | :-------------------------------------------------------------- | 185 | | [`#list-header`](#list-header) | `div.simple-typeahead-list-header` | | Slot to be show at top of the suggestion list | 186 | | [`#list-item-text`](#list-item-text) | `span.simple-typeahead-list-item-text'` | `item`, `itemProjection`, `boldMatchText` | Slot to customize the text of every item in the suggestion list | 187 | | [`#list-footer`](#list-footer) | `div.simple-typeahead-list-footer` | | Slot to be show at bottom of the suggestion list | 188 | 189 | #### Slot `#list-item-text` props 190 | 191 | | Prop | Type | Description | 192 | | :---------------------------------- | :--------------- | :------------------------------------------------------------------------------------------------------------ | 193 | | [`item`](#item) | String or Object | The item of the items array | 194 | | [`itemProjection`](#itemProjection) | function | Use the item projection function provided as prop to the `vue3-simple-typeahead` element | 195 | | [`boldMatchText`](#boldMatchText) | function | A function that receives a string and add strong tags to the parts of the text matched by the search criteria | 196 | 197 | ### Methods 198 | 199 | | Method | Signature | Description | 200 | | :-------------------------- | :------------------------------ | :--------------------------------------------------------------- | 201 | | [`clearInput`](#clearInput) | `function (): void` | Clean the input with an empty string `''` | 202 | | [`focusInput`](#focusInput) | `function (): void` | Trigger focus on the input and called `onFocus` event handler | 203 | | [`blurInput`](#blurInput) | `function (): void` | Trigger blur on the input and called `onBlur` event handler | 204 | | [`getInput`](#getInput) | `function (): HTMLInputElement` | Return the `HTMLInputElement` corresponding to the input control | 205 | 206 | _This methods are accesible via [refs](https://vuejs.org/guide/essentials/template-refs.html)_ 207 | 208 | ```html 209 | 210 | ``` 211 | 212 | ```javascript 213 | { 214 | this.$refs.inputRef; 215 | } 216 | ``` 217 | 218 | ### Styling 219 | 220 | Overwrite styles when using the default css included or add custom styles basing your rules on this structure. 221 | 222 | ```stylus 223 | div#{:id}_wrapper.simple-typeahead 224 | input#{:id}.simple-typeahead-input 225 | div.simple-typeahead-list 226 | .simple-typeahead-list-header 227 | .simple-typeahead-list-item &.simple-typeahead-list-item-active 228 | .simple-typeahead-list-item-text 229 | .simple-typeahead-list-footer 230 | ``` 231 | -------------------------------------------------------------------------------- /dist/vue3-simple-typeahead.ssr.js: -------------------------------------------------------------------------------- 1 | 'use strict';var vue=require('vue');function _slicedToArray(arr, i) { 2 | return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); 3 | } 4 | 5 | function _arrayWithHoles(arr) { 6 | if (Array.isArray(arr)) return arr; 7 | } 8 | 9 | function _iterableToArrayLimit(arr, i) { 10 | var _i = arr == null ? null : typeof Symbol !== "undefined" && arr[Symbol.iterator] || arr["@@iterator"]; 11 | 12 | if (_i == null) return; 13 | var _arr = []; 14 | var _n = true; 15 | var _d = false; 16 | 17 | var _s, _e; 18 | 19 | try { 20 | for (_i = _i.call(arr); !(_n = (_s = _i.next()).done); _n = true) { 21 | _arr.push(_s.value); 22 | 23 | if (i && _arr.length === i) break; 24 | } 25 | } catch (err) { 26 | _d = true; 27 | _e = err; 28 | } finally { 29 | try { 30 | if (!_n && _i["return"] != null) _i["return"](); 31 | } finally { 32 | if (_d) throw _e; 33 | } 34 | } 35 | 36 | return _arr; 37 | } 38 | 39 | function _unsupportedIterableToArray(o, minLen) { 40 | if (!o) return; 41 | if (typeof o === "string") return _arrayLikeToArray(o, minLen); 42 | var n = Object.prototype.toString.call(o).slice(8, -1); 43 | if (n === "Object" && o.constructor) n = o.constructor.name; 44 | if (n === "Map" || n === "Set") return Array.from(o); 45 | if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); 46 | } 47 | 48 | function _arrayLikeToArray(arr, len) { 49 | if (len == null || len > arr.length) len = arr.length; 50 | 51 | for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; 52 | 53 | return arr2; 54 | } 55 | 56 | function _nonIterableRest() { 57 | throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); 58 | }var script = /*#__PURE__*/vue.defineComponent({ 59 | name: 'Vue3SimpleTypeahead', 60 | emits: ['onInput', 'onFocus', 'onBlur', 'selectItem'], 61 | inheritAttrs: false, 62 | props: { 63 | id: { 64 | type: String 65 | }, 66 | placeholder: { 67 | type: String, 68 | default: '' 69 | }, 70 | items: { 71 | type: Array, 72 | required: true 73 | }, 74 | defaultItem: { 75 | default: null 76 | }, 77 | itemProjection: { 78 | type: Function, 79 | default: function _default(item) { 80 | return item; 81 | } 82 | }, 83 | minInputLength: { 84 | type: Number, 85 | default: 2, 86 | validator: function validator(prop) { 87 | return prop >= 0; 88 | } 89 | }, 90 | minItemLength: { 91 | type: Number, 92 | default: 0, 93 | validator: function validator(prop) { 94 | return prop >= 0; 95 | } 96 | }, 97 | selectOnTab: { 98 | type: Boolean, 99 | default: true 100 | } 101 | }, 102 | mounted: function mounted() { 103 | if (this.defaultItem !== undefined && this.defaultItem !== null) { 104 | this.selectItem(this.defaultItem); 105 | } 106 | }, 107 | data: function data() { 108 | return { 109 | inputId: this.id || "simple_typeahead_".concat((Math.random() * 1000).toFixed()), 110 | input: '', 111 | isInputFocused: false, 112 | currentSelectionIndex: 0 113 | }; 114 | }, 115 | methods: { 116 | onInput: function onInput() { 117 | if (this.isListVisible && this.currentSelectionIndex >= this.filteredItems.length) { 118 | this.currentSelectionIndex = (this.filteredItems.length || 1) - 1; 119 | } 120 | 121 | this.$emit('onInput', { 122 | input: this.input, 123 | items: this.filteredItems 124 | }); 125 | }, 126 | onFocus: function onFocus() { 127 | this.isInputFocused = true; 128 | this.$emit('onFocus', { 129 | input: this.input, 130 | items: this.filteredItems 131 | }); 132 | }, 133 | onBlur: function onBlur() { 134 | this.isInputFocused = false; 135 | this.$emit('onBlur', { 136 | input: this.input, 137 | items: this.filteredItems 138 | }); 139 | }, 140 | onArrowDown: function onArrowDown($event) { 141 | if (this.isListVisible && this.currentSelectionIndex < this.filteredItems.length - 1) { 142 | this.currentSelectionIndex++; 143 | } 144 | 145 | this.scrollSelectionIntoView(); 146 | }, 147 | onArrowUp: function onArrowUp($event) { 148 | if (this.isListVisible && this.currentSelectionIndex > 0) { 149 | this.currentSelectionIndex--; 150 | } 151 | 152 | this.scrollSelectionIntoView(); 153 | }, 154 | scrollSelectionIntoView: function scrollSelectionIntoView() { 155 | var _this = this; 156 | 157 | setTimeout(function () { 158 | var list_node = document.querySelector("#".concat(_this.wrapperId, " .simple-typeahead-list")); 159 | var active_node = document.querySelector("#".concat(_this.wrapperId, " .simple-typeahead-list-item.simple-typeahead-list-item-active")); 160 | 161 | if (!(active_node.offsetTop >= list_node.scrollTop && active_node.offsetTop + active_node.offsetHeight < list_node.scrollTop + list_node.offsetHeight)) { 162 | var scroll_to = 0; 163 | 164 | if (active_node.offsetTop > list_node.scrollTop) { 165 | scroll_to = active_node.offsetTop + active_node.offsetHeight - list_node.offsetHeight; 166 | } else if (active_node.offsetTop < list_node.scrollTop) { 167 | scroll_to = active_node.offsetTop; 168 | } 169 | 170 | list_node.scrollTo(0, scroll_to); 171 | } 172 | }); 173 | }, 174 | selectCurrentSelection: function selectCurrentSelection() { 175 | if (this.currentSelection) { 176 | this.selectItem(this.currentSelection); 177 | } 178 | }, 179 | selectCurrentSelectionTab: function selectCurrentSelectionTab() { 180 | if (this.selectOnTab) { 181 | this.selectCurrentSelection(); 182 | } else { 183 | this.$refs.inputRef.blur(); 184 | } 185 | }, 186 | selectItem: function selectItem(item) { 187 | this.input = this.itemProjection(item); 188 | this.currentSelectionIndex = 0; 189 | this.$refs.inputRef.blur(); 190 | this.$emit('selectItem', item); 191 | }, 192 | escapeRegExp: function escapeRegExp(string) { 193 | return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); 194 | }, 195 | boldMatchText: function boldMatchText(text) { 196 | var regexp = new RegExp("(".concat(this.escapeRegExp(this.input), ")"), 'ig'); 197 | return text.replace(regexp, '$1'); 198 | }, 199 | clearInput: function clearInput() { 200 | this.input = ''; 201 | }, 202 | getInput: function getInput() { 203 | return this.$refs.inputRef; 204 | }, 205 | focusInput: function focusInput() { 206 | this.$refs.inputRef.focus(); 207 | this.onFocus(); 208 | }, 209 | blurInput: function blurInput() { 210 | this.$refs.inputRef.blur(); 211 | this.onBlur(); 212 | } 213 | }, 214 | computed: { 215 | wrapperId: function wrapperId() { 216 | return "".concat(this.inputId, "_wrapper"); 217 | }, 218 | filteredItems: function filteredItems() { 219 | var _this2 = this; 220 | 221 | var regexp = new RegExp(this.escapeRegExp(this.input), 'i'); 222 | return this.items.filter(function (item) { 223 | return _this2.itemProjection(item).match(regexp); 224 | }); 225 | }, 226 | isListVisible: function isListVisible() { 227 | return this.isInputFocused && this.input.length >= this.minInputLength && this.filteredItems.length > this.minItemLength; 228 | }, 229 | currentSelection: function currentSelection() { 230 | return this.isListVisible && this.currentSelectionIndex < this.filteredItems.length ? this.filteredItems[this.currentSelectionIndex] : undefined; 231 | } 232 | } 233 | });vue.pushScopeId("data-v-f81ca714"); 234 | 235 | var _hoisted_1 = ["id"]; 236 | var _hoisted_2 = ["id", "placeholder"]; 237 | var _hoisted_3 = { 238 | key: 0, 239 | class: "simple-typeahead-list" 240 | }; 241 | var _hoisted_4 = { 242 | key: 0, 243 | class: "simple-typeahead-list-header" 244 | }; 245 | var _hoisted_5 = ["onClick", "onMouseenter"]; 246 | var _hoisted_6 = ["data-text"]; 247 | var _hoisted_7 = ["data-text", "innerHTML"]; 248 | var _hoisted_8 = { 249 | key: 1, 250 | class: "simple-typeahead-list-footer" 251 | }; 252 | 253 | vue.popScopeId(); 254 | 255 | function render(_ctx, _cache, $props, $setup, $data, $options) { 256 | return vue.openBlock(), vue.createElementBlock("div", { 257 | id: _ctx.wrapperId, 258 | class: "simple-typeahead" 259 | }, [vue.withDirectives(vue.createElementVNode("input", vue.mergeProps({ 260 | ref: "inputRef", 261 | id: _ctx.inputId, 262 | class: "simple-typeahead-input", 263 | type: "text", 264 | placeholder: _ctx.placeholder, 265 | "onUpdate:modelValue": _cache[0] || (_cache[0] = function ($event) { 266 | return _ctx.input = $event; 267 | }), 268 | onInput: _cache[1] || (_cache[1] = function () { 269 | return _ctx.onInput && _ctx.onInput.apply(_ctx, arguments); 270 | }), 271 | onFocus: _cache[2] || (_cache[2] = function () { 272 | return _ctx.onFocus && _ctx.onFocus.apply(_ctx, arguments); 273 | }), 274 | onBlur: _cache[3] || (_cache[3] = function () { 275 | return _ctx.onBlur && _ctx.onBlur.apply(_ctx, arguments); 276 | }), 277 | onKeydown: [_cache[4] || (_cache[4] = vue.withKeys(vue.withModifiers(function () { 278 | return _ctx.onArrowDown && _ctx.onArrowDown.apply(_ctx, arguments); 279 | }, ["prevent"]), ["down"])), _cache[5] || (_cache[5] = vue.withKeys(vue.withModifiers(function () { 280 | return _ctx.onArrowUp && _ctx.onArrowUp.apply(_ctx, arguments); 281 | }, ["prevent"]), ["up"])), _cache[6] || (_cache[6] = vue.withKeys(vue.withModifiers(function () { 282 | return _ctx.selectCurrentSelection && _ctx.selectCurrentSelection.apply(_ctx, arguments); 283 | }, ["prevent"]), ["enter"])), _cache[7] || (_cache[7] = vue.withKeys(vue.withModifiers(function () { 284 | return _ctx.selectCurrentSelectionTab && _ctx.selectCurrentSelectionTab.apply(_ctx, arguments); 285 | }, ["prevent"]), ["tab"]))], 286 | autocomplete: "off" 287 | }, _ctx.$attrs), null, 16, _hoisted_2), [[vue.vModelText, _ctx.input]]), _ctx.isListVisible ? (vue.openBlock(), vue.createElementBlock("div", _hoisted_3, [_ctx.$slots['list-header'] ? (vue.openBlock(), vue.createElementBlock("div", _hoisted_4, [vue.renderSlot(_ctx.$slots, "list-header")])) : vue.createCommentVNode("", true), (vue.openBlock(true), vue.createElementBlock(vue.Fragment, null, vue.renderList(_ctx.filteredItems, function (item, index) { 288 | return vue.openBlock(), vue.createElementBlock("div", { 289 | class: vue.normalizeClass(["simple-typeahead-list-item", { 290 | 'simple-typeahead-list-item-active': _ctx.currentSelectionIndex == index 291 | }]), 292 | key: index, 293 | onMousedown: _cache[8] || (_cache[8] = vue.withModifiers(function () {}, ["prevent"])), 294 | onClick: function onClick($event) { 295 | return _ctx.selectItem(item); 296 | }, 297 | onMouseenter: function onMouseenter($event) { 298 | return _ctx.currentSelectionIndex = index; 299 | } 300 | }, [_ctx.$slots['list-item-text'] ? (vue.openBlock(), vue.createElementBlock("span", { 301 | key: 0, 302 | class: "simple-typeahead-list-item-text", 303 | "data-text": _ctx.itemProjection(item) 304 | }, [vue.renderSlot(_ctx.$slots, "list-item-text", { 305 | item: item, 306 | itemProjection: _ctx.itemProjection, 307 | boldMatchText: _ctx.boldMatchText 308 | })], 8, _hoisted_6)) : (vue.openBlock(), vue.createElementBlock("span", { 309 | key: 1, 310 | class: "simple-typeahead-list-item-text", 311 | "data-text": _ctx.itemProjection(item), 312 | innerHTML: _ctx.boldMatchText(_ctx.itemProjection(item)) 313 | }, null, 8, _hoisted_7))], 42, _hoisted_5); 314 | }), 128)), _ctx.$slots['list-footer'] ? (vue.openBlock(), vue.createElementBlock("div", _hoisted_8, [vue.renderSlot(_ctx.$slots, "list-footer")])) : vue.createCommentVNode("", true)])) : vue.createCommentVNode("", true)], 8, _hoisted_1); 315 | }script.render = render; 316 | script.__scopeId = "data-v-f81ca714";// Import vue component 317 | // IIFE injects install function into component, allowing component 318 | // to be registered via Vue.use() as well as Vue.component(), 319 | 320 | var component = /*#__PURE__*/(function () { 321 | // Get component instance 322 | var installable = script; // Attach install function executed by Vue.use() 323 | 324 | installable.install = function (app) { 325 | app.component('Vue3SimpleTypeahead', installable); 326 | }; 327 | 328 | return installable; 329 | })(); // It's possible to expose named exports when writing components that can 330 | // also be used as directives, etc. - eg. import { RollupDemoDirective } from 'rollup-demo'; 331 | // export const RollupDemoDirective = directive; 332 | var namedExports=/*#__PURE__*/Object.freeze({__proto__:null,'default': component});// only expose one global var, with named exports exposed as properties of 333 | // that global var (eg. plugin.namedExport) 334 | 335 | Object.entries(namedExports).forEach(function (_ref) { 336 | var _ref2 = _slicedToArray(_ref, 2), 337 | exportName = _ref2[0], 338 | exported = _ref2[1]; 339 | 340 | if (exportName !== 'default') component[exportName] = exported; 341 | });module.exports=component; -------------------------------------------------------------------------------- /dev/serve.vue: -------------------------------------------------------------------------------- 1 | 54 | 55 | 1596 | 1597 | 1608 | --------------------------------------------------------------------------------