├── .browserslistrc ├── .gitignore ├── README.md ├── babel.config.js ├── build └── rollup.config.js ├── dist ├── vue-otp-2.esm.js ├── vue-otp-2.min.js └── vue-otp-2.ssr.js ├── package.json └── src ├── entry.js ├── serve-dev.js ├── serve-dev.vue └── vue-otp-2.vue /.browserslistrc: -------------------------------------------------------------------------------- 1 | current node 2 | last 2 versions and > 2% 3 | ie > 10 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | package-lock.json 3 | .now 4 | now.json 5 | *DS_Store 6 | .vercel 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # VUE OTP INPUT 2 | 3 | > A OTP input component for Vue 4 | 5 | ![](https://i.imgur.com/aGuMJff.gif) 6 | 7 | DEMO: [https://vue-otp-2-hoaitx.vercel.app/](https://vue-otp-2-hoaitx.vercel.app/) 8 | 9 | ![NPM Version](https://img.shields.io/npm/v/vue-otp-2) 10 | 11 | ## Installation 12 | 13 | ``` sh 14 | npm i vue-otp-2 15 | ``` 16 | 17 | ## Usage example 18 | 19 | In main.js 20 | 21 | ``` javascript 22 | import Vue from 'vue' 23 | import VueOtp2 from 'vue-otp-2'; 24 | 25 | Vue.use(VueOtp2) 26 | ``` 27 | 28 | In App.vue 29 | 30 | ``` vue 31 | 39 | ``` 40 | 41 | ## Props 42 | 43 | |Name|Type|Default|Description| 44 | |---|---|---|---| 45 | |length|String|6|The number of input| 46 | |join-character|String||character to join inputs| 47 | |inputmode|String|numeric|numeric/text| 48 | |pattern|String|[0-9]*|[HTML attribute: pattern](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/pattern)| 49 | 50 | ## Events 51 | 52 | |Name|Description| 53 | |---|---| 54 | |onComplete|All input typed| 55 | |onChange|Input filled| 56 | 57 | ## Style 58 | 59 | ``` scss 60 | .vue-otp-2 { 61 | display: flex; 62 | justify-content: space-between; 63 | 64 | div { 65 | flex: 1; 66 | display: flex; 67 | align-items: center; 68 | justify-content: center; 69 | input { 70 | max-width: 30px; 71 | padding: 11.5px 8px; 72 | font-size: 20px; 73 | border-radius: 3px; 74 | border: 1px solid #cecece; 75 | text-align: center; 76 | } 77 | 78 | span { 79 | display: block; 80 | flex: 1; 81 | text-align: center; 82 | } 83 | } 84 | } 85 | ``` 86 | 87 | ## Release History 88 | 89 | ### 1.0.4: Fixed some bugs 90 | 91 | - Fix: length props does not work as expected 92 | 93 | ### 1.0.3: Fixed some bugs & improvement 94 | 95 | - Fix: Keypress not working on Samsung devices 96 | - Added inputmode & pattern html input 97 | - Improvement style 98 | - And more... 99 | 100 | ### 1.0.2: Fixed some bugs 101 | 102 | - Fix: Event emit not correct 103 | 104 | ### 1.0.1: RELEASE 105 | 106 | ## License 107 | 108 | MIT 109 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@babel/preset-env', 4 | ], 5 | }; 6 | -------------------------------------------------------------------------------- /build/rollup.config.js: -------------------------------------------------------------------------------- 1 | // rollup.config.js 2 | import fs from 'fs'; 3 | import path from 'path'; 4 | import vue from 'rollup-plugin-vue'; 5 | import alias from '@rollup/plugin-alias'; 6 | import commonjs from '@rollup/plugin-commonjs'; 7 | import replace from '@rollup/plugin-replace'; 8 | import babel from 'rollup-plugin-babel'; 9 | import { terser } from 'rollup-plugin-terser'; 10 | import minimist from 'minimist'; 11 | 12 | // Get browserslist config and remove ie from es build targets 13 | const esbrowserslist = fs.readFileSync('./.browserslistrc') 14 | .toString() 15 | .split('\n') 16 | .filter((entry) => entry && entry.substring(0, 2) !== 'ie'); 17 | 18 | const argv = minimist(process.argv.slice(2)); 19 | 20 | const projectRoot = path.resolve(__dirname, '..'); 21 | 22 | const baseConfig = { 23 | input: 'src/entry.js', 24 | plugins: { 25 | preVue: [ 26 | replace({ 27 | 'process.env.NODE_ENV': JSON.stringify('production'), 28 | }), 29 | alias({ 30 | resolve: ['.js', '.jsx', '.ts', '.tsx', '.vue'], 31 | entries: { 32 | '@': path.resolve(projectRoot, 'src'), 33 | }, 34 | }), 35 | ], 36 | vue: { 37 | css: true, 38 | template: { 39 | isProduction: true, 40 | }, 41 | }, 42 | babel: { 43 | exclude: 'node_modules/**', 44 | extensions: ['.js', '.jsx', '.ts', '.tsx', '.vue'], 45 | }, 46 | }, 47 | }; 48 | 49 | // ESM/UMD/IIFE shared settings: externals 50 | // Refer to https://rollupjs.org/guide/en/#warning-treating-module-as-external-dependency 51 | const external = [ 52 | // list external dependencies, exactly the way it is written in the import statement. 53 | // eg. 'jquery' 54 | 'vue', 55 | ]; 56 | 57 | // UMD/IIFE shared settings: output.globals 58 | // Refer to https://rollupjs.org/guide/en#output-globals for details 59 | const globals = { 60 | // Provide global variable names to replace your external imports 61 | // eg. jquery: '$' 62 | vue: 'Vue', 63 | }; 64 | 65 | // Customize configs for individual targets 66 | const buildFormats = []; 67 | if (!argv.format || argv.format === 'es') { 68 | const esConfig = { 69 | ...baseConfig, 70 | external, 71 | output: { 72 | file: 'dist/vue-otp-2.esm.js', 73 | format: 'esm', 74 | exports: 'named', 75 | }, 76 | plugins: [ 77 | ...baseConfig.plugins.preVue, 78 | vue(baseConfig.plugins.vue), 79 | babel({ 80 | ...baseConfig.plugins.babel, 81 | presets: [ 82 | [ 83 | '@babel/preset-env', 84 | { 85 | targets: esbrowserslist, 86 | }, 87 | ], 88 | ], 89 | }), 90 | commonjs(), 91 | ], 92 | }; 93 | buildFormats.push(esConfig); 94 | } 95 | 96 | if (!argv.format || argv.format === 'cjs') { 97 | const umdConfig = { 98 | ...baseConfig, 99 | external, 100 | output: { 101 | compact: true, 102 | file: 'dist/vue-otp-2.ssr.js', 103 | format: 'cjs', 104 | name: 'VueOtp2', 105 | exports: 'named', 106 | globals, 107 | }, 108 | plugins: [ 109 | ...baseConfig.plugins.preVue, 110 | vue({ 111 | ...baseConfig.plugins.vue, 112 | template: { 113 | ...baseConfig.plugins.vue.template, 114 | optimizeSSR: true, 115 | }, 116 | }), 117 | babel(baseConfig.plugins.babel), 118 | commonjs(), 119 | ], 120 | }; 121 | buildFormats.push(umdConfig); 122 | } 123 | 124 | if (!argv.format || argv.format === 'iife') { 125 | const unpkgConfig = { 126 | ...baseConfig, 127 | external, 128 | output: { 129 | compact: true, 130 | file: 'dist/vue-otp-2.min.js', 131 | format: 'iife', 132 | name: 'VueOtp2', 133 | exports: 'named', 134 | globals, 135 | }, 136 | plugins: [ 137 | ...baseConfig.plugins.preVue, 138 | vue(baseConfig.plugins.vue), 139 | babel(baseConfig.plugins.babel), 140 | commonjs(), 141 | terser({ 142 | output: { 143 | ecma: 5, 144 | }, 145 | }), 146 | ], 147 | }; 148 | buildFormats.push(unpkgConfig); 149 | } 150 | 151 | // Export config 152 | export default buildFormats; 153 | -------------------------------------------------------------------------------- /dist/vue-otp-2.esm.js: -------------------------------------------------------------------------------- 1 | const ON_INPUT_COMPLETE_EVENT = "onComplete"; 2 | const ON_INPUT_CHANGE_EVENT = "onChange"; 3 | var script = { 4 | name: "VueOtp2", 5 | props: ["length", "joinCharacter", "inputmode", "pattern"], 6 | data: function () { 7 | return { 8 | otpLength: +this.length || 6, 9 | inputMode: this.inputmode || "numeric", 10 | inputPattern: this.pattern || "[0-9]*", 11 | character: this.joinCharacter, 12 | otp: [], 13 | currentInputCursorIndex: 0, 14 | inputRefs: [], 15 | isDeleteKey: false // emulator delete key 16 | 17 | }; 18 | }, 19 | 20 | mounted() { 21 | this.initInputRefs(this.otpLength); 22 | this.initOtpLength(this.otpLength); 23 | }, 24 | 25 | computed: { 26 | currentOtpLength() { 27 | return this.otp.filter(item => item).length; 28 | } 29 | 30 | }, 31 | methods: { 32 | handleInput(e) { 33 | // fix samsung keyboard with keyCode on press not working normally 34 | const keyData = e.data; 35 | const keyCode = keyData ? keyData.charCodeAt(0) : 0; 36 | return this.isDeletePress(keyCode) ? this.onDelete() : this.onType(keyData); 37 | }, 38 | 39 | handleKeyup() { 40 | if (this.isDeleteKey) { 41 | this.prevInput(); 42 | } 43 | 44 | this.riseChangeIsDeleteKey(true); 45 | }, 46 | 47 | isDeletePress(keyCode) { 48 | return keyCode === 0; 49 | }, 50 | 51 | onType(keyData) { 52 | this.clearSoftAfterInput(this.currentInputCursorIndex); 53 | this.riseChangeOtp(this.currentInputCursorIndex, keyData); 54 | this.fillInputValue(this.currentInputCursorIndex, this.otp[this.currentInputCursorIndex]); // continue if input has value 55 | 56 | this.getInputValue(this.currentInputCursorIndex) && this.nextInput(); 57 | this.riseChangeIsDeleteKey(false); 58 | }, 59 | 60 | onDelete() { 61 | // this.otp[this.currentInputCursorIndex] 62 | // ? this.removeInputValue(this.inputRefs[this.currentInputCursorIndex]) 63 | // : this.prevInput(); 64 | if (this.otp[this.currentInputCursorIndex]) { 65 | this.removeInputValue(this.inputRefs[this.currentInputCursorIndex]); 66 | } else { 67 | this.prevInput(); 68 | this.riseChangeIsDeleteKey(true); 69 | } // if press delete, delete all after input 70 | 71 | 72 | this.clearSoftAfterInput(this.currentInputCursorIndex); 73 | }, 74 | 75 | riseChangeOtp(index, data) { 76 | this.otp.splice(index, 1, data); 77 | }, 78 | 79 | riseChangeIsDeleteKey(data) { 80 | this.isDeleteKey = !!data; 81 | }, 82 | 83 | handleFocus(e, i) { 84 | e.target.select(); 85 | const inputFilled = this.otp.filter(item => item).length; 86 | this.currentInputCursorIndex = i > inputFilled ? inputFilled : i; 87 | this.focusInput(this.inputRefs[this.currentInputCursorIndex]); 88 | }, 89 | 90 | initInputRefs(inputNums) { 91 | let i = 0; 92 | 93 | while (i < inputNums) { 94 | this.inputRefs = [...this.inputRefs, this.$refs[`input${i}`]]; 95 | i++; 96 | } 97 | }, 98 | 99 | initOtpLength(length) { 100 | let i = 0; 101 | 102 | while (i < length) { 103 | this.otp = [...this.otp, null]; 104 | i++; 105 | } 106 | }, 107 | 108 | changeInputCursor(idx) { 109 | this.currentInputCursorIndex = idx; 110 | }, 111 | 112 | nextInputCursor(currentIdx) { 113 | const index = currentIdx < this.otpLength - 1 ? currentIdx + 1 : this.otpLength - 1; 114 | this.changeInputCursor(index); 115 | }, 116 | 117 | prevInputCursor(currentIdx) { 118 | const index = currentIdx > 0 ? currentIdx - 1 : 0; 119 | this.changeInputCursor(index); 120 | }, 121 | 122 | nextInput() { 123 | this.nextInputCursor(this.currentInputCursorIndex); 124 | this.focusInput(this.inputRefs[this.currentInputCursorIndex]); 125 | }, 126 | 127 | prevInput() { 128 | this.prevInputCursor(this.currentInputCursorIndex); 129 | this.focusInput(this.inputRefs[this.currentInputCursorIndex]); 130 | }, 131 | 132 | focusInput(inputRefs) { 133 | inputRefs && inputRefs[0] && inputRefs[0].focus(); 134 | }, 135 | 136 | blurInput(inputRefs) { 137 | inputRefs && inputRefs[0] && inputRefs[0].blur(); 138 | }, 139 | 140 | removeInputValue(inputRefs) { 141 | inputRefs && inputRefs[0] && (() => inputRefs[0].value = null)(); 142 | }, 143 | 144 | getInputValue(idx) { 145 | return this.inputRefs[idx] && this.inputRefs[idx][0] && this.inputRefs[idx][0].value; 146 | }, 147 | 148 | fillInputValue(idx, value) { 149 | if (this.inputRefs[idx] && this.inputRefs[idx][0]) { 150 | this.inputRefs[idx][0].value = value; 151 | } 152 | }, 153 | 154 | validKeyCode(code) { 155 | const isLetter = code >= 65 && code <= 90; 156 | const isLowercaseLetter = code >= 97 && code <= 122; 157 | const isNumber = code >= 48 && code <= 57; 158 | const isDelete = code === 0; 159 | return isNumber || isLetter || isLowercaseLetter || isDelete; 160 | }, 161 | 162 | clearAfterInput(idx) { 163 | this.otp.splice(idx, this.otpLength - idx, ...Array(this.otpLength - idx).fill(null)); 164 | 165 | while (idx < this.otpLength) { 166 | this.removeInputValue(this.inputRefs[idx]); 167 | idx++; 168 | } 169 | }, 170 | 171 | clearSoftAfterInput(idx) { 172 | if (idx < 0) idx = 0; 173 | 174 | while (idx < this.otpLength) { 175 | this.otp[idx] = null; 176 | this.removeInputValue(this.inputRefs[idx]); 177 | idx++; 178 | } 179 | }, 180 | 181 | emitEvent(eventName, payload) { 182 | this.$emit(eventName, payload); 183 | }, 184 | 185 | emitEventChange(payload) { 186 | this.emitEvent(ON_INPUT_CHANGE_EVENT, payload); 187 | }, 188 | 189 | emitEventComplete(payload) { 190 | this.emitEvent(ON_INPUT_COMPLETE_EVENT, payload); 191 | } 192 | 193 | }, 194 | watch: { 195 | otp() { 196 | const otpLength = this.currentOtpLength; 197 | const idxCanGet = otpLength === this.otpLength ? this.currentInputCursorIndex : this.currentInputCursorIndex - 1; // onchange 198 | 199 | if (otpLength || otpLength === this.otpLength - 1) { 200 | const dataChange = { 201 | index: idxCanGet, 202 | value: this.otp[idxCanGet] 203 | }; 204 | this.emitEventChange(dataChange); 205 | } // on complete 206 | 207 | 208 | if (otpLength === this.otpLength) { 209 | this.emitEventComplete([...this.otp]); 210 | this.blurInput(this.inputRefs[this.otpLength - 1]); 211 | } 212 | } 213 | 214 | } 215 | }; 216 | 217 | function normalizeComponent(template, style, script, scopeId, isFunctionalTemplate, moduleIdentifier /* server only */, shadowMode, createInjector, createInjectorSSR, createInjectorShadow) { 218 | if (typeof shadowMode !== 'boolean') { 219 | createInjectorSSR = createInjector; 220 | createInjector = shadowMode; 221 | shadowMode = false; 222 | } 223 | // Vue.extend constructor export interop. 224 | const options = typeof script === 'function' ? script.options : script; 225 | // render functions 226 | if (template && template.render) { 227 | options.render = template.render; 228 | options.staticRenderFns = template.staticRenderFns; 229 | options._compiled = true; 230 | // functional template 231 | if (isFunctionalTemplate) { 232 | options.functional = true; 233 | } 234 | } 235 | // scopedId 236 | if (scopeId) { 237 | options._scopeId = scopeId; 238 | } 239 | let hook; 240 | if (moduleIdentifier) { 241 | // server build 242 | hook = function (context) { 243 | // 2.3 injection 244 | context = 245 | context || // cached call 246 | (this.$vnode && this.$vnode.ssrContext) || // stateful 247 | (this.parent && this.parent.$vnode && this.parent.$vnode.ssrContext); // functional 248 | // 2.2 with runInNewContext: true 249 | if (!context && typeof __VUE_SSR_CONTEXT__ !== 'undefined') { 250 | context = __VUE_SSR_CONTEXT__; 251 | } 252 | // inject component styles 253 | if (style) { 254 | style.call(this, createInjectorSSR(context)); 255 | } 256 | // register component module identifier for async chunk inference 257 | if (context && context._registeredComponents) { 258 | context._registeredComponents.add(moduleIdentifier); 259 | } 260 | }; 261 | // used by ssr in case component is cached and beforeCreate 262 | // never gets called 263 | options._ssrRegister = hook; 264 | } 265 | else if (style) { 266 | hook = shadowMode 267 | ? function (context) { 268 | style.call(this, createInjectorShadow(context, this.$root.$options.shadowRoot)); 269 | } 270 | : function (context) { 271 | style.call(this, createInjector(context)); 272 | }; 273 | } 274 | if (hook) { 275 | if (options.functional) { 276 | // register for functional component in vue file 277 | const originalRender = options.render; 278 | options.render = function renderWithStyleInjection(h, context) { 279 | hook.call(context); 280 | return originalRender(h, context); 281 | }; 282 | } 283 | else { 284 | // inject component registration as beforeCreate hook 285 | const existing = options.beforeCreate; 286 | options.beforeCreate = existing ? [].concat(existing, hook) : [hook]; 287 | } 288 | } 289 | return script; 290 | } 291 | 292 | const isOldIE = typeof navigator !== 'undefined' && 293 | /msie [6-9]\\b/.test(navigator.userAgent.toLowerCase()); 294 | function createInjector(context) { 295 | return (id, style) => addStyle(id, style); 296 | } 297 | let HEAD; 298 | const styles = {}; 299 | function addStyle(id, css) { 300 | const group = isOldIE ? css.media || 'default' : id; 301 | const style = styles[group] || (styles[group] = { ids: new Set(), styles: [] }); 302 | if (!style.ids.has(id)) { 303 | style.ids.add(id); 304 | let code = css.source; 305 | if (css.map) { 306 | // https://developer.chrome.com/devtools/docs/javascript-debugging 307 | // this makes source maps inside style tags work properly in Chrome 308 | code += '\n/*# sourceURL=' + css.map.sources[0] + ' */'; 309 | // http://stackoverflow.com/a/26603875 310 | code += 311 | '\n/*# sourceMappingURL=data:application/json;base64,' + 312 | btoa(unescape(encodeURIComponent(JSON.stringify(css.map)))) + 313 | ' */'; 314 | } 315 | if (!style.element) { 316 | style.element = document.createElement('style'); 317 | style.element.type = 'text/css'; 318 | if (css.media) 319 | style.element.setAttribute('media', css.media); 320 | if (HEAD === undefined) { 321 | HEAD = document.head || document.getElementsByTagName('head')[0]; 322 | } 323 | HEAD.appendChild(style.element); 324 | } 325 | if ('styleSheet' in style.element) { 326 | style.styles.push(code); 327 | style.element.styleSheet.cssText = style.styles 328 | .filter(Boolean) 329 | .join('\n'); 330 | } 331 | else { 332 | const index = style.ids.size - 1; 333 | const textNode = document.createTextNode(code); 334 | const nodes = style.element.childNodes; 335 | if (nodes[index]) 336 | style.element.removeChild(nodes[index]); 337 | if (nodes.length) 338 | style.element.insertBefore(textNode, nodes[index]); 339 | else 340 | style.element.appendChild(textNode); 341 | } 342 | } 343 | } 344 | 345 | /* script */ 346 | const __vue_script__ = script; 347 | /* template */ 348 | 349 | var __vue_render__ = function () { 350 | var _vm = this; 351 | 352 | var _h = _vm.$createElement; 353 | 354 | var _c = _vm._self._c || _h; 355 | 356 | return _c('div', { 357 | staticClass: "vue-otp-2" 358 | }, _vm._l(_vm.otpLength * 2 - 1, function (v, i) { 359 | return _c('div', { 360 | key: i / 2 361 | }, [i % 2 === 0 ? _c('input', { 362 | ref: 'input' + i / 2, 363 | refInFor: true, 364 | attrs: { 365 | "minlength": "1", 366 | "maxlength": "1", 367 | "type": "text", 368 | "inputmode": _vm.inputMode, 369 | "pattern": _vm.inputPattern 370 | }, 371 | on: { 372 | "input": _vm.handleInput, 373 | "keyup": _vm.handleKeyup, 374 | "focus": function ($event) { 375 | return _vm.handleFocus($event, i / 2); 376 | } 377 | } 378 | }) : _vm._e(), _vm._v(" "), i % 2 !== 0 && true ? _c('span', [_vm._v(_vm._s(_vm.character))]) : _vm._e()]); 379 | }), 0); 380 | }; 381 | 382 | var __vue_staticRenderFns__ = []; 383 | /* style */ 384 | 385 | const __vue_inject_styles__ = function (inject) { 386 | if (!inject) return; 387 | inject("data-v-10e5d926_0", { 388 | source: ".vue-otp-2[data-v-10e5d926]{display:flex;justify-content:space-between}.vue-otp-2 div[data-v-10e5d926]{flex:1;display:flex;align-items:center;justify-content:center}.vue-otp-2 div input[data-v-10e5d926]{max-width:30px;padding:11.5px 8px;font-size:20px;border-radius:3px;border:1px solid #cecece;text-align:center}.vue-otp-2 div span[data-v-10e5d926]{display:block;flex:1;text-align:center}", 389 | map: undefined, 390 | media: undefined 391 | }); 392 | }; 393 | /* scoped */ 394 | 395 | 396 | const __vue_scope_id__ = "data-v-10e5d926"; 397 | /* module identifier */ 398 | 399 | const __vue_module_identifier__ = undefined; 400 | /* functional template */ 401 | 402 | const __vue_is_functional_template__ = false; 403 | /* style inject SSR */ 404 | 405 | /* style inject shadow dom */ 406 | 407 | const __vue_component__ = /*#__PURE__*/normalizeComponent({ 408 | render: __vue_render__, 409 | staticRenderFns: __vue_staticRenderFns__ 410 | }, __vue_inject_styles__, __vue_script__, __vue_scope_id__, __vue_is_functional_template__, __vue_module_identifier__, false, createInjector, undefined, undefined); 411 | 412 | // Import vue component 413 | 414 | const install = function installVueOtp2(Vue) { 415 | if (install.installed) return; 416 | install.installed = true; 417 | Vue.component('VueOtp2', __vue_component__); 418 | }; // Create module definition for Vue.use() 419 | 420 | 421 | const plugin = { 422 | install 423 | }; // To auto-install when vue is found 424 | // eslint-disable-next-line no-redeclare 425 | 426 | /* global window, global */ 427 | 428 | let GlobalVue = null; 429 | 430 | if (typeof window !== 'undefined') { 431 | GlobalVue = window.Vue; 432 | } else if (typeof global !== 'undefined') { 433 | GlobalVue = global.Vue; 434 | } 435 | 436 | if (GlobalVue) { 437 | GlobalVue.use(plugin); 438 | } // Inject install function into component - allows component 439 | // to be registered via Vue.use() as well as Vue.component() 440 | 441 | 442 | __vue_component__.install = install; // Export component by default 443 | // also be used as directives, etc. - eg. import { RollupDemoDirective } from 'rollup-demo'; 444 | // export const RollupDemoDirective = component; 445 | 446 | export default __vue_component__; 447 | -------------------------------------------------------------------------------- /dist/vue-otp-2.min.js: -------------------------------------------------------------------------------- 1 | var VueOtp2=function(t){"use strict";function e(t){return function(t){if(Array.isArray(t))return n(t)}(t)||function(t){if("undefined"!=typeof Symbol&&null!=t[Symbol.iterator]||null!=t["@@iterator"])return Array.from(t)}(t)||function(t,e){if(!t)return;if("string"==typeof t)return n(t,e);var i=Object.prototype.toString.call(t).slice(8,-1);"Object"===i&&t.constructor&&(i=t.constructor.name);if("Map"===i||"Set"===i)return Array.from(t);if("Arguments"===i||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(i))return n(t,e)}(t)||function(){throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function n(t,e){(null==e||e>t.length)&&(e=t.length);for(var n=0,i=new Array(e);nn?n:e,this.focusInput(this.inputRefs[this.currentInputCursorIndex])},initInputRefs:function(t){for(var n=0;n0?t-1:0;this.changeInputCursor(e)},nextInput:function(){this.nextInputCursor(this.currentInputCursorIndex),this.focusInput(this.inputRefs[this.currentInputCursorIndex])},prevInput:function(){this.prevInputCursor(this.currentInputCursorIndex),this.focusInput(this.inputRefs[this.currentInputCursorIndex])},focusInput:function(t){t&&t[0]&&t[0].focus()},blurInput:function(t){t&&t[0]&&t[0].blur()},removeInputValue:function(t){t&&t[0]&&(t[0].value=null)},getInputValue:function(t){return this.inputRefs[t]&&this.inputRefs[t][0]&&this.inputRefs[t][0].value},fillInputValue:function(t,e){this.inputRefs[t]&&this.inputRefs[t][0]&&(this.inputRefs[t][0].value=e)},validKeyCode:function(t){return t>=48&&t<=57||t>=65&&t<=90||t>=97&&t<=122||0===t},clearAfterInput:function(t){var n;for((n=this.otp).splice.apply(n,[t,this.otpLength-t].concat(e(Array(this.otpLength-t).fill(null))));tfunction(t,e){const n=s?e.media||"default":t,i=p[n]||(p[n]={ids:new Set,styles:[]});if(!i.ids.has(t)){i.ids.add(t);let n=e.source;if(e.map&&(n+="\n/*# sourceURL="+e.map.sources[0]+" */",n+="\n/*# sourceMappingURL=data:application/json;base64,"+btoa(unescape(encodeURIComponent(JSON.stringify(e.map))))+" */"),i.element||(i.element=document.createElement("style"),i.element.type="text/css",e.media&&i.element.setAttribute("media",e.media),void 0===o&&(o=document.head||document.getElementsByTagName("head")[0]),o.appendChild(i.element)),"styleSheet"in i.element)i.styles.push(n),i.element.styleSheet.cssText=i.styles.filter(Boolean).join("\n");else{const t=i.ids.size-1,e=document.createTextNode(n),r=i.element.childNodes;r[t]&&i.element.removeChild(r[t]),r.length?i.element.insertBefore(e,r[t]):i.element.appendChild(e)}}}(t,e)}let o;const p={};var a=r({render:function(){var t=this,e=t.$createElement,n=t._self._c||e;return n("div",{staticClass:"vue-otp-2"},t._l(2*t.otpLength-1,(function(e,i){return n("div",{key:i/2},[i%2==0?n("input",{ref:"input"+i/2,refInFor:!0,attrs:{minlength:"1",maxlength:"1",type:"text",inputmode:t.inputMode,pattern:t.inputPattern},on:{input:t.handleInput,keyup:t.handleKeyup,focus:function(e){return t.handleFocus(e,i/2)}}}):t._e(),t._v(" "),i%2!=0?n("span",[t._v(t._s(t.character))]):t._e()])})),0)},staticRenderFns:[]},(function(t){t&&t("data-v-10e5d926_0",{source:".vue-otp-2[data-v-10e5d926]{display:flex;justify-content:space-between}.vue-otp-2 div[data-v-10e5d926]{flex:1;display:flex;align-items:center;justify-content:center}.vue-otp-2 div input[data-v-10e5d926]{max-width:30px;padding:11.5px 8px;font-size:20px;border-radius:3px;border:1px solid #cecece;text-align:center}.vue-otp-2 div span[data-v-10e5d926]{display:block;flex:1;text-align:center}",map:void 0,media:void 0})}),i,"data-v-10e5d926",!1,void 0,!1,u,void 0,void 0),h=function(t){h.installed||(h.installed=!0,t.component("VueOtp2",a))},c={install:h},l=null;return"undefined"!=typeof window?l=window.Vue:"undefined"!=typeof global&&(l=global.Vue),l&&l.use(c),a.install=h,t.default=a,t}({}); -------------------------------------------------------------------------------- /dist/vue-otp-2.ssr.js: -------------------------------------------------------------------------------- 1 | 'use strict';Object.defineProperty(exports,'__esModule',{value:true});function _toConsumableArray(arr) { 2 | return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread(); 3 | } 4 | 5 | function _arrayWithoutHoles(arr) { 6 | if (Array.isArray(arr)) return _arrayLikeToArray(arr); 7 | } 8 | 9 | function _iterableToArray(iter) { 10 | if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter); 11 | } 12 | 13 | function _unsupportedIterableToArray(o, minLen) { 14 | if (!o) return; 15 | if (typeof o === "string") return _arrayLikeToArray(o, minLen); 16 | var n = Object.prototype.toString.call(o).slice(8, -1); 17 | if (n === "Object" && o.constructor) n = o.constructor.name; 18 | if (n === "Map" || n === "Set") return Array.from(o); 19 | if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); 20 | } 21 | 22 | function _arrayLikeToArray(arr, len) { 23 | if (len == null || len > arr.length) len = arr.length; 24 | 25 | for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; 26 | 27 | return arr2; 28 | } 29 | 30 | function _nonIterableSpread() { 31 | throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); 32 | }var ON_INPUT_COMPLETE_EVENT = "onComplete"; 33 | var ON_INPUT_CHANGE_EVENT = "onChange"; 34 | var script = { 35 | name: "VueOtp2", 36 | props: ["length", "joinCharacter", "inputmode", "pattern"], 37 | data: function data() { 38 | return { 39 | otpLength: +this.length || 6, 40 | inputMode: this.inputmode || "numeric", 41 | inputPattern: this.pattern || "[0-9]*", 42 | character: this.joinCharacter, 43 | otp: [], 44 | currentInputCursorIndex: 0, 45 | inputRefs: [], 46 | isDeleteKey: false // emulator delete key 47 | 48 | }; 49 | }, 50 | mounted: function mounted() { 51 | this.initInputRefs(this.otpLength); 52 | this.initOtpLength(this.otpLength); 53 | }, 54 | computed: { 55 | currentOtpLength: function currentOtpLength() { 56 | return this.otp.filter(function (item) { 57 | return item; 58 | }).length; 59 | } 60 | }, 61 | methods: { 62 | handleInput: function handleInput(e) { 63 | // fix samsung keyboard with keyCode on press not working normally 64 | var keyData = e.data; 65 | var keyCode = keyData ? keyData.charCodeAt(0) : 0; 66 | return this.isDeletePress(keyCode) ? this.onDelete() : this.onType(keyData); 67 | }, 68 | handleKeyup: function handleKeyup() { 69 | if (this.isDeleteKey) { 70 | this.prevInput(); 71 | } 72 | 73 | this.riseChangeIsDeleteKey(true); 74 | }, 75 | isDeletePress: function isDeletePress(keyCode) { 76 | return keyCode === 0; 77 | }, 78 | onType: function onType(keyData) { 79 | this.clearSoftAfterInput(this.currentInputCursorIndex); 80 | this.riseChangeOtp(this.currentInputCursorIndex, keyData); 81 | this.fillInputValue(this.currentInputCursorIndex, this.otp[this.currentInputCursorIndex]); // continue if input has value 82 | 83 | this.getInputValue(this.currentInputCursorIndex) && this.nextInput(); 84 | this.riseChangeIsDeleteKey(false); 85 | }, 86 | onDelete: function onDelete() { 87 | // this.otp[this.currentInputCursorIndex] 88 | // ? this.removeInputValue(this.inputRefs[this.currentInputCursorIndex]) 89 | // : this.prevInput(); 90 | if (this.otp[this.currentInputCursorIndex]) { 91 | this.removeInputValue(this.inputRefs[this.currentInputCursorIndex]); 92 | } else { 93 | this.prevInput(); 94 | this.riseChangeIsDeleteKey(true); 95 | } // if press delete, delete all after input 96 | 97 | 98 | this.clearSoftAfterInput(this.currentInputCursorIndex); 99 | }, 100 | riseChangeOtp: function riseChangeOtp(index, data) { 101 | this.otp.splice(index, 1, data); 102 | }, 103 | riseChangeIsDeleteKey: function riseChangeIsDeleteKey(data) { 104 | this.isDeleteKey = !!data; 105 | }, 106 | handleFocus: function handleFocus(e, i) { 107 | e.target.select(); 108 | var inputFilled = this.otp.filter(function (item) { 109 | return item; 110 | }).length; 111 | this.currentInputCursorIndex = i > inputFilled ? inputFilled : i; 112 | this.focusInput(this.inputRefs[this.currentInputCursorIndex]); 113 | }, 114 | initInputRefs: function initInputRefs(inputNums) { 115 | var i = 0; 116 | 117 | while (i < inputNums) { 118 | this.inputRefs = [].concat(_toConsumableArray(this.inputRefs), [this.$refs["input".concat(i)]]); 119 | i++; 120 | } 121 | }, 122 | initOtpLength: function initOtpLength(length) { 123 | var i = 0; 124 | 125 | while (i < length) { 126 | this.otp = [].concat(_toConsumableArray(this.otp), [null]); 127 | i++; 128 | } 129 | }, 130 | changeInputCursor: function changeInputCursor(idx) { 131 | this.currentInputCursorIndex = idx; 132 | }, 133 | nextInputCursor: function nextInputCursor(currentIdx) { 134 | var index = currentIdx < this.otpLength - 1 ? currentIdx + 1 : this.otpLength - 1; 135 | this.changeInputCursor(index); 136 | }, 137 | prevInputCursor: function prevInputCursor(currentIdx) { 138 | var index = currentIdx > 0 ? currentIdx - 1 : 0; 139 | this.changeInputCursor(index); 140 | }, 141 | nextInput: function nextInput() { 142 | this.nextInputCursor(this.currentInputCursorIndex); 143 | this.focusInput(this.inputRefs[this.currentInputCursorIndex]); 144 | }, 145 | prevInput: function prevInput() { 146 | this.prevInputCursor(this.currentInputCursorIndex); 147 | this.focusInput(this.inputRefs[this.currentInputCursorIndex]); 148 | }, 149 | focusInput: function focusInput(inputRefs) { 150 | inputRefs && inputRefs[0] && inputRefs[0].focus(); 151 | }, 152 | blurInput: function blurInput(inputRefs) { 153 | inputRefs && inputRefs[0] && inputRefs[0].blur(); 154 | }, 155 | removeInputValue: function removeInputValue(inputRefs) { 156 | inputRefs && inputRefs[0] && function () { 157 | return inputRefs[0].value = null; 158 | }(); 159 | }, 160 | getInputValue: function getInputValue(idx) { 161 | return this.inputRefs[idx] && this.inputRefs[idx][0] && this.inputRefs[idx][0].value; 162 | }, 163 | fillInputValue: function fillInputValue(idx, value) { 164 | if (this.inputRefs[idx] && this.inputRefs[idx][0]) { 165 | this.inputRefs[idx][0].value = value; 166 | } 167 | }, 168 | validKeyCode: function validKeyCode(code) { 169 | var isLetter = code >= 65 && code <= 90; 170 | var isLowercaseLetter = code >= 97 && code <= 122; 171 | var isNumber = code >= 48 && code <= 57; 172 | var isDelete = code === 0; 173 | return isNumber || isLetter || isLowercaseLetter || isDelete; 174 | }, 175 | clearAfterInput: function clearAfterInput(idx) { 176 | var _this$otp; 177 | 178 | (_this$otp = this.otp).splice.apply(_this$otp, [idx, this.otpLength - idx].concat(_toConsumableArray(Array(this.otpLength - idx).fill(null)))); 179 | 180 | while (idx < this.otpLength) { 181 | this.removeInputValue(this.inputRefs[idx]); 182 | idx++; 183 | } 184 | }, 185 | clearSoftAfterInput: function clearSoftAfterInput(idx) { 186 | if (idx < 0) idx = 0; 187 | 188 | while (idx < this.otpLength) { 189 | this.otp[idx] = null; 190 | this.removeInputValue(this.inputRefs[idx]); 191 | idx++; 192 | } 193 | }, 194 | emitEvent: function emitEvent(eventName, payload) { 195 | this.$emit(eventName, payload); 196 | }, 197 | emitEventChange: function emitEventChange(payload) { 198 | this.emitEvent(ON_INPUT_CHANGE_EVENT, payload); 199 | }, 200 | emitEventComplete: function emitEventComplete(payload) { 201 | this.emitEvent(ON_INPUT_COMPLETE_EVENT, payload); 202 | } 203 | }, 204 | watch: { 205 | otp: function otp() { 206 | var otpLength = this.currentOtpLength; 207 | var idxCanGet = otpLength === this.otpLength ? this.currentInputCursorIndex : this.currentInputCursorIndex - 1; // onchange 208 | 209 | if (otpLength || otpLength === this.otpLength - 1) { 210 | var dataChange = { 211 | index: idxCanGet, 212 | value: this.otp[idxCanGet] 213 | }; 214 | this.emitEventChange(dataChange); 215 | } // on complete 216 | 217 | 218 | if (otpLength === this.otpLength) { 219 | this.emitEventComplete(_toConsumableArray(this.otp)); 220 | this.blurInput(this.inputRefs[this.otpLength - 1]); 221 | } 222 | } 223 | } 224 | };function normalizeComponent(template, style, script, scopeId, isFunctionalTemplate, moduleIdentifier /* server only */, shadowMode, createInjector, createInjectorSSR, createInjectorShadow) { 225 | if (typeof shadowMode !== 'boolean') { 226 | createInjectorSSR = createInjector; 227 | createInjector = shadowMode; 228 | shadowMode = false; 229 | } 230 | // Vue.extend constructor export interop. 231 | const options = typeof script === 'function' ? script.options : script; 232 | // render functions 233 | if (template && template.render) { 234 | options.render = template.render; 235 | options.staticRenderFns = template.staticRenderFns; 236 | options._compiled = true; 237 | // functional template 238 | if (isFunctionalTemplate) { 239 | options.functional = true; 240 | } 241 | } 242 | // scopedId 243 | if (scopeId) { 244 | options._scopeId = scopeId; 245 | } 246 | let hook; 247 | if (moduleIdentifier) { 248 | // server build 249 | hook = function (context) { 250 | // 2.3 injection 251 | context = 252 | context || // cached call 253 | (this.$vnode && this.$vnode.ssrContext) || // stateful 254 | (this.parent && this.parent.$vnode && this.parent.$vnode.ssrContext); // functional 255 | // 2.2 with runInNewContext: true 256 | if (!context && typeof __VUE_SSR_CONTEXT__ !== 'undefined') { 257 | context = __VUE_SSR_CONTEXT__; 258 | } 259 | // inject component styles 260 | if (style) { 261 | style.call(this, createInjectorSSR(context)); 262 | } 263 | // register component module identifier for async chunk inference 264 | if (context && context._registeredComponents) { 265 | context._registeredComponents.add(moduleIdentifier); 266 | } 267 | }; 268 | // used by ssr in case component is cached and beforeCreate 269 | // never gets called 270 | options._ssrRegister = hook; 271 | } 272 | else if (style) { 273 | hook = shadowMode 274 | ? function (context) { 275 | style.call(this, createInjectorShadow(context, this.$root.$options.shadowRoot)); 276 | } 277 | : function (context) { 278 | style.call(this, createInjector(context)); 279 | }; 280 | } 281 | if (hook) { 282 | if (options.functional) { 283 | // register for functional component in vue file 284 | const originalRender = options.render; 285 | options.render = function renderWithStyleInjection(h, context) { 286 | hook.call(context); 287 | return originalRender(h, context); 288 | }; 289 | } 290 | else { 291 | // inject component registration as beforeCreate hook 292 | const existing = options.beforeCreate; 293 | options.beforeCreate = existing ? [].concat(existing, hook) : [hook]; 294 | } 295 | } 296 | return script; 297 | }function createInjectorSSR(context) { 298 | if (!context && typeof __VUE_SSR_CONTEXT__ !== 'undefined') { 299 | context = __VUE_SSR_CONTEXT__; 300 | } 301 | if (!context) 302 | return () => { }; 303 | if (!('styles' in context)) { 304 | context._styles = context._styles || {}; 305 | Object.defineProperty(context, 'styles', { 306 | enumerable: true, 307 | get: () => context._renderStyles(context._styles) 308 | }); 309 | context._renderStyles = context._renderStyles || renderStyles; 310 | } 311 | return (id, style) => addStyle(id, style, context); 312 | } 313 | function addStyle(id, css, context) { 314 | const group = css.media || 'default' ; 315 | const style = context._styles[group] || (context._styles[group] = { ids: [], css: '' }); 316 | if (!style.ids.includes(id)) { 317 | style.media = css.media; 318 | style.ids.push(id); 319 | let code = css.source; 320 | style.css += code + '\n'; 321 | } 322 | } 323 | function renderStyles(styles) { 324 | let css = ''; 325 | for (const key in styles) { 326 | const style = styles[key]; 327 | css += 328 | ''; 335 | } 336 | return css; 337 | }/* script */ 338 | var __vue_script__ = script; 339 | /* template */ 340 | 341 | var __vue_render__ = function __vue_render__() { 342 | var _vm = this; 343 | 344 | var _h = _vm.$createElement; 345 | 346 | var _c = _vm._self._c || _h; 347 | 348 | return _c('div', { 349 | staticClass: "vue-otp-2" 350 | }, [_vm._ssrNode(_vm._ssrList(_vm.otpLength * 2 - 1, function (v, i) { 351 | return "
" + (i % 2 === 0 ? "" : "") + " " + (i % 2 !== 0 && true ? "" + _vm._ssrEscape(_vm._s(_vm.character)) + "" : "") + "
"; 352 | }))]); 353 | }; 354 | 355 | var __vue_staticRenderFns__ = []; 356 | /* style */ 357 | 358 | var __vue_inject_styles__ = function __vue_inject_styles__(inject) { 359 | if (!inject) return; 360 | inject("data-v-10e5d926_0", { 361 | source: ".vue-otp-2[data-v-10e5d926]{display:flex;justify-content:space-between}.vue-otp-2 div[data-v-10e5d926]{flex:1;display:flex;align-items:center;justify-content:center}.vue-otp-2 div input[data-v-10e5d926]{max-width:30px;padding:11.5px 8px;font-size:20px;border-radius:3px;border:1px solid #cecece;text-align:center}.vue-otp-2 div span[data-v-10e5d926]{display:block;flex:1;text-align:center}", 362 | map: undefined, 363 | media: undefined 364 | }); 365 | }; 366 | /* scoped */ 367 | 368 | 369 | var __vue_scope_id__ = "data-v-10e5d926"; 370 | /* module identifier */ 371 | 372 | var __vue_module_identifier__ = "data-v-10e5d926"; 373 | /* functional template */ 374 | 375 | var __vue_is_functional_template__ = false; 376 | /* style inject shadow dom */ 377 | 378 | var __vue_component__ = /*#__PURE__*/normalizeComponent({ 379 | render: __vue_render__, 380 | staticRenderFns: __vue_staticRenderFns__ 381 | }, __vue_inject_styles__, __vue_script__, __vue_scope_id__, __vue_is_functional_template__, __vue_module_identifier__, false, undefined, createInjectorSSR, undefined);// Import vue component 382 | 383 | var install = function installVueOtp2(Vue) { 384 | if (install.installed) return; 385 | install.installed = true; 386 | Vue.component('VueOtp2', __vue_component__); 387 | }; // Create module definition for Vue.use() 388 | 389 | 390 | var plugin = { 391 | install: install 392 | }; // To auto-install when vue is found 393 | // eslint-disable-next-line no-redeclare 394 | 395 | /* global window, global */ 396 | 397 | var GlobalVue = null; 398 | 399 | if (typeof window !== 'undefined') { 400 | GlobalVue = window.Vue; 401 | } else if (typeof global !== 'undefined') { 402 | GlobalVue = global.Vue; 403 | } 404 | 405 | if (GlobalVue) { 406 | GlobalVue.use(plugin); 407 | } // Inject install function into component - allows component 408 | // to be registered via Vue.use() as well as Vue.component() 409 | 410 | 411 | __vue_component__.install = install; // Export component by default 412 | // also be used as directives, etc. - eg. import { RollupDemoDirective } from 'rollup-demo'; 413 | // export const RollupDemoDirective = component; 414 | exports.default=__vue_component__; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-otp-2", 3 | "version": "1.0.4", 4 | "description": "", 5 | "keywords": [], 6 | "author": "hoaitx", 7 | "license": "MIT", 8 | "main": "dist/vue-otp-2.ssr.js", 9 | "browser": "dist/vue-otp-2.esm.js", 10 | "module": "dist/vue-otp-2.esm.js", 11 | "unpkg": "dist/vue-otp-2.min.js", 12 | "files": [ 13 | "dist/*", 14 | "src/**/*.vue", 15 | "!src/serve-dev.*" 16 | ], 17 | "scripts": { 18 | "serve": "vue-cli-service serve src/serve-dev.js", 19 | "build": "vue-cli-service build src/serve-dev.js", 20 | "build:lib": "cross-env NODE_ENV=production rollup --config build/rollup.config.js", 21 | "build:ssr": "cross-env NODE_ENV=production rollup --config build/rollup.config.js --format cjs", 22 | "build:es": "cross-env NODE_ENV=production rollup --config build/rollup.config.js --format es", 23 | "build:unpkg": "cross-env NODE_ENV=production rollup --config build/rollup.config.js --format iife" 24 | }, 25 | "dependencies": {}, 26 | "devDependencies": { 27 | "@babel/core": "^7.7.7", 28 | "@babel/preset-env": "^7.7.7", 29 | "@rollup/plugin-alias": "^2.2.0", 30 | "@rollup/plugin-commonjs": "^11.0.1", 31 | "@rollup/plugin-replace": "^2.2.1", 32 | "@vue/cli-plugin-babel": "^4.1.0", 33 | "@vue/cli-service": "^4.1.0", 34 | "cross-env": "^6.0.3", 35 | "minimist": "^1.2.0", 36 | "rollup": "^1.27.13", 37 | "rollup-plugin-babel": "^4.3.3", 38 | "rollup-plugin-terser": "^5.1.3", 39 | "rollup-plugin-vue": "^5.1.5", 40 | "sass-loader": "^8.0.2", 41 | "vue": "^2.6.10", 42 | "vue-template-compiler": "^2.6.10" 43 | }, 44 | "peerDependencies": { 45 | "vue": "^2.6.10" 46 | }, 47 | "engines": { 48 | "node": ">=8" 49 | }, 50 | "repository": { 51 | "type": "git", 52 | "url": "https://github.com/tonghoai/vue-otp-2.git" 53 | }, 54 | "homepage": "https://github.com/tonghoai/vue-otp-2" 55 | } 56 | -------------------------------------------------------------------------------- /src/entry.js: -------------------------------------------------------------------------------- 1 | // Import vue component 2 | import component from '@/vue-otp-2.vue'; 3 | 4 | // install function executed by Vue.use() 5 | const install = function installVueOtp2(Vue) { 6 | if (install.installed) return; 7 | install.installed = true; 8 | Vue.component('VueOtp2', component); 9 | }; 10 | 11 | // Create module definition for Vue.use() 12 | const plugin = { 13 | install, 14 | }; 15 | 16 | // To auto-install when vue is found 17 | // eslint-disable-next-line no-redeclare 18 | /* global window, global */ 19 | let GlobalVue = null; 20 | if (typeof window !== 'undefined') { 21 | GlobalVue = window.Vue; 22 | } else if (typeof global !== 'undefined') { 23 | GlobalVue = global.Vue; 24 | } 25 | if (GlobalVue) { 26 | GlobalVue.use(plugin); 27 | } 28 | 29 | // Inject install function into component - allows component 30 | // to be registered via Vue.use() as well as Vue.component() 31 | component.install = install; 32 | 33 | // Export component by default 34 | export default component; 35 | 36 | // It's possible to expose named exports when writing components that can 37 | // also be used as directives, etc. - eg. import { RollupDemoDirective } from 'rollup-demo'; 38 | // export const RollupDemoDirective = component; 39 | -------------------------------------------------------------------------------- /src/serve-dev.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import Dev from '@/serve-dev.vue'; 3 | 4 | Vue.config.productionTip = false; 5 | 6 | new Vue({ 7 | render: (h) => h(Dev), 8 | }).$mount('#app'); 9 | -------------------------------------------------------------------------------- /src/serve-dev.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 40 | 41 | 64 | -------------------------------------------------------------------------------- /src/vue-otp-2.vue: -------------------------------------------------------------------------------- 1 | 203 | 204 | 223 | 224 | 251 | --------------------------------------------------------------------------------