├── .babelrc
├── .eslintignore
├── .eslintrc.js
├── .gitignore
├── LICENSE
├── README.md
├── RELEASE.md
├── build
├── loader-util.js
├── path-util.js
├── template.index.html
├── uglify.config.base.js
├── webpack.config.base.js
├── webpack.config.demo.js
├── webpack.config.dev.js
├── webpack.config.prod.js
└── webpack.config.test.js
├── dist
├── demo.1320e4785106378b9e121f0ec76a34aa.min.css
├── demo.ecd3fe850e3573d9d50a.min.js
├── manifest.e0fcc538cee16be5b5d1.min.js
├── vendor.54f01e7571327264504c.min.js
├── vendor.bd1c25d7d835a9a7714785aa4249167b.min.css
├── vue-typer.js
└── vue-typer.min.js
├── index.html
├── package.json
├── src
├── demo
│ ├── Demo.vue
│ ├── assets
│ │ ├── demo.gif
│ │ └── images
│ │ │ ├── documentation-light.png
│ │ │ ├── download-light.png
│ │ │ └── github-mark-light.png
│ ├── components
│ │ ├── AppLayout.vue
│ │ ├── BadgeBar.vue
│ │ ├── CircleLink.vue
│ │ ├── CodeBlock.vue
│ │ ├── CopyrightFooter.vue
│ │ ├── FormCheck.vue
│ │ ├── FormInput.vue
│ │ ├── FormRadio.vue
│ │ ├── HeroHeader.vue
│ │ └── LinkBar.vue
│ ├── index.js
│ ├── lib
│ │ ├── bootstrap
│ │ │ ├── bootstrap-flex.min.css
│ │ │ ├── bootstrap-flex.min.css.map
│ │ │ └── index.js
│ │ └── prism
│ │ │ ├── index.js
│ │ │ ├── prism.css
│ │ │ └── prism.js
│ └── styles
│ │ ├── colors.scss
│ │ └── demo.scss
└── vue-typer
│ ├── components
│ ├── Caret.vue
│ ├── Char.vue
│ └── VueTyper.vue
│ ├── index.js
│ ├── styles
│ ├── caret-animations.scss
│ └── typer-colors.scss
│ └── utils
│ ├── random-int.js
│ ├── shallow-equals.js
│ └── shuffle.js
└── test
└── unit
├── .eslintrc
├── index.js
├── karma.conf.js
├── specs
└── VueTyper.spec.js
└── utils
├── mount.js
└── wait.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["es2015"],
3 | "plugins": ["transform-runtime"],
4 | "comments": false,
5 | "env": {
6 | "test": {
7 | "plugins": ["istanbul"]
8 | }
9 | }
10 | }
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | src/demo/lib
2 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | parser: 'babel-eslint',
4 | parserOptions: {
5 | sourceType: 'module'
6 | },
7 | // https://github.com/feross/standard/blob/master/RULES.md#javascript-standard-style
8 | extends: 'standard',
9 | plugins: [
10 | 'html' // required to lint *.vue files
11 | ],
12 | // add your custom rules here
13 | 'rules': {
14 | // allow no-spaces between function name and argument parethesis list
15 | 'space-before-function-paren': 0,
16 | // allow paren-less arrow functions
17 | 'arrow-parens': 0,
18 | // allow debugger during development
19 | 'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .vscode
3 | node_modules
4 | *.log
5 | dist/*.map
6 | test/**/coverage
7 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2016 Chris Nguyen
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Vue component that simulates a user typing, selecting, and erasing text.
5 |
6 | Play with vue-typer in this interactive demo.
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | - [Getting Started](#getting-started)
17 | - [Usage](#usage)
18 | - [Props](#props)
19 | - [text](#text)
20 | - [repeat](#repeat)
21 | - [shuffle](#shuffle)
22 | - [initialAction](#initialaction)
23 | - [preTypeDelay](#pretypedelay)
24 | - [typeDelay](#typedelay)
25 | - [preEraseDelay](#preerasedelay)
26 | - [eraseDelay](#erasedelay)
27 | - [eraseStyle](#erasestyle)
28 | - [eraseOnComplete](#eraseoncomplete)
29 | - [caretAnimation](#caretanimation)
30 | - [Events](#events)
31 | - [typed](#typed)
32 | - [typed-char](#typed-char)
33 | - [erased](#erased)
34 | - [completed](#completed)
35 | - [Styles](#styles)
36 | - [Contribution Guide](#contribution-guide)
37 | - [Changelog](#changelog)
38 | - [TODO](#todo)
39 | - [License](#license)
40 |
41 | ## Getting Started
42 |
43 | > VueTyper has a single dependency to [lodash.split](https://github.com/lodash/lodash/blob/master/split.js) to support emojis and other multi-codepoint Unicode characters. Apart from this, VueTyper does not have any direct dependencies to any other library or framework -- not even to Vue itself! All required Vue API calls are made through Vue's `this.$*` context methods. This means VueTyper can only execute within a Vue application context, but in exchange, it does not need to pull in Vue, which keeps VueTyper lightweight.
44 |
45 | ### Prerequisites
46 | - Vue v2.x
47 | - VueTyper has not been tested in Vue v1.x. ([See here for migration instructions from Vue 1.x to 2.x.](https://vuejs.org/v2/guide/migration.html))
48 |
49 | ### Installation
50 | #### npm
51 | Use this method if you wish to import/require VueTyper as a module.
52 | ```
53 | npm install --save vue-typer
54 | ```
55 |
56 | #### CDN
57 | Use this method if you wish to access VueTyper globally via `window.VueTyper`.
58 | ```html
59 |
60 | ```
61 |
62 | ## Usage
63 | After installing VueTyper, you may choose to register it either globally or locally. [What's the difference? See the Vue documentation here.](https://vuejs.org/v2/guide/components.html#Registration)
64 |
65 | #### Local Registration
66 | 1. Import the VueTyper component directly from your Vue component file:
67 | ```javascript
68 | // ES6
69 | import { VueTyper } from 'vue-typer'
70 | // CommonJS
71 | var VueTyper = require('vue-typer').VueTyper
72 | // Global
73 | var VueTyper = window.VueTyper.VueTyper
74 | ```
75 |
76 | 2. Register it as a local component in your Vue component options:
77 | ```javascript
78 | var MyComponent = {
79 | // ...
80 | components: {
81 | // ES6; property shorthand + Vue should automatically dasherize the key for us
82 | VueTyper
83 | // pre-ES6
84 | 'vue-typer': VueTyper
85 | }
86 | }
87 | ```
88 |
89 | 3. Use vue-typer in your Vue component's template:
90 |
91 | ```html
92 |
93 | ```
94 |
95 | #### Global Registration
96 | 1. Import the VueTyper plugin in your application entry point:
97 | ```javascript
98 | // ES6
99 | import VueTyperPlugin from 'vue-typer'
100 | // CommonJS
101 | var VueTyperPlugin = require('vue-typer').default
102 | // Global
103 | var VueTyperPlugin = window.VueTyper.default
104 | ```
105 |
106 | 2. Register the VueTyper plugin with Vue
107 | ```javascript
108 | Vue.use(VueTyperPlugin)
109 | ```
110 |
111 | 3. Now you can freely use vue-typer in any Vue component template:
112 | ```html
113 |
114 | ```
115 |
116 | ## Props
117 | It may be helpful to play around with these props in the [interactive demo](https://cngu.github.io/vue-typer/#playground).
118 |
119 | #### `text`
120 | - **type**: `String || Array`
121 | - **required**
122 | - **validator**: Non-empty
123 | - **Usage**:
124 |
125 | ```html
126 |
127 | ```
128 |
129 | Either a single string, or an ordered list of strings, for VueTyper to type. Strings will not be trimmed.
130 |
131 | - **Note**: Dynamically changing this value after VueTyper has mounted will cause VueTyper to reset itself and start typing from scratch.
132 | - **See also**: [`shuffle`](#shuffle)
133 |
134 | #### `repeat`
135 | - **type**: `Number`
136 | - **default**: `Infinity`
137 | - **validator**: Non-negative
138 | - **Usage**:
139 |
140 | ```html
141 |
142 | ```
143 |
144 | Number of _extra_ times to type `text` after the first time. Setting 0 will type `text` once; 1 will type twice; Infinity will type forever.
145 |
146 | - **Note**: Dynamically changing this value after VueTyper has mounted will cause VueTyper to reset itself and start typing from scratch.
147 |
148 | #### `shuffle`
149 | - **type**: `Boolean`
150 | - **default**: `false`
151 | - **Usage**:
152 |
153 | ```html
154 |
155 | ```
156 |
157 | Randomly shuffles `text` ([using the Fisher-Yates shuffle](https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle)) before typing it. If `repeat > 0`, `text` will always be shuffled before repeated typings. `text` is _not_ shuffled after every word is typed. This implies that:
158 | - all strings in `text` will be typed the same number of times, but just in a shuffled order
159 | - the frequencies of the order of strings typed will have equal distributions, e.g.:
160 | - given `text=['a','b']`, a,b will be printed 50% of the time, and b,a the other 50%
161 | - given `text=['a','b','c']`, there are 3!=6 possible permutations, and they will each be printed 100%/6=16.7% of the time
162 | - the only scenarios where the same word can be typed twice in a row are when:
163 | 1. `text` contains duplicate strings, or
164 | 2. `repeat > 0`, `text` is typed where the last word is W, and the next repeat typing shuffled `text` such that it starts with W.
165 |
166 | - **Note**: Dynamically changing this value after VueTyper has mounted will cause VueTyper to reset itself and start typing from scratch.
167 |
168 | #### `initialAction`
169 | - **type**: `String`
170 | - **default**: `"typing"`
171 | - **validator**: `"typing"` || `"erasing"`
172 | - **Usage**:
173 |
174 | ```html
175 |
176 | ```
177 |
178 | `typing` starts VueTyper off in the "typing" state; there will be empty space as VueTyper begins to type the first string in `text`.
179 |
180 | `erasing` starts VueTyper off in the "erasing" state; the first string in `text` will already be typed and visible as VueTyper begins to erase it.
181 |
182 | #### `preTypeDelay`
183 | - **type**: `Number`
184 | - **default**: `70`
185 | - **validator**: Non-negative
186 | - **Usage**:
187 |
188 | ```html
189 |
190 | ```
191 |
192 | Milliseconds to wait before typing the first character of every string in `text`.
193 |
194 | This is useful to have an idle period to show a blank space for a period of time before VueTyper types the first character.
195 |
196 | #### `typeDelay`
197 | - **type**: `Number`
198 | - **default**: `70`
199 | - **validator**: Non-negative
200 | - **Usage**:
201 |
202 | ```html
203 |
204 | ```
205 |
206 | Milliseconds to wait after typing a character, until the next character is typed.
207 |
208 | #### `preEraseDelay`
209 | - **type**: `Number`
210 | - **default**: `2000`
211 | - **validator**: Non-negative
212 | - **Usage**:
213 |
214 | ```html
215 |
216 | ```
217 |
218 | Milliseconds to wait after a string is fully typed, until the first erase action (i.e. backspace, highlight) is performed.
219 |
220 | This is useful to have an idle period that gives users time to read the typed string before it is erased.
221 |
222 | #### `eraseDelay`
223 | - **type**: `Number`
224 | - **default**: `250`
225 | - **validator**: Non-negative
226 | - **Usage**:
227 |
228 | ```html
229 |
230 | ```
231 |
232 | Milliseconds to wait after performing an erase action (i.e. backspace, highlight), until the next erase action can start.
233 |
234 | #### `eraseStyle`
235 | - **type**: `String`
236 | - **default**: `"select-all"`
237 | - **validator**: `"backspace"` || `"select-back"` || `"select-all"` || `"clear"`
238 | - **Usage**:
239 |
240 | ```html
241 |
242 | ```
243 |
244 | `backspace` erases one character at a time, simulating the backspace key.
245 |
246 | `select-back` highlights backward one character at a time, simulating Shift+LeftArrow, and erases everything once all characters are highlighted.
247 |
248 | `select-all` immediately highlights all characters at once, simulating Ctrl/Cmd+A, and erases all characters afterwards.
249 |
250 | `clear` immediately erases all characters at once; the typed string simply disappears.
251 |
252 | #### `eraseOnComplete`
253 | - **type**: `Boolean`
254 | - **default**: `false`
255 | - **Usage**:
256 |
257 | ```html
258 |
259 | ```
260 |
261 | By default, after VueTyper completes all its typing (i.e. it finishes typing all strings in `text`, `repeat+1` times), the last typed string will not be erased and stay visible. Enabling this flag will tell VueTyper to erase the final string as well.
262 |
263 | - **Note**: Has no effect if `repeat === Infinity`.
264 |
265 | #### `caretAnimation`
266 | - **type**: `String`
267 | - **default**: `"blink"`
268 | - **validator**: `"solid"` || `"blink"` || `"smooth"` || `"phase"` || `"expand"`
269 | - **Usage**:
270 |
271 | ```html
272 |
273 | ```
274 |
275 | Specifies a built-in caret animation to use, similar to Sublime and VS Code animations.
276 |
277 | - **Note**: Alternatively, custom animations can be applied via CSS.
278 |
279 | - **See also**: [Styles](#styles), [Example Custom Caret Animation](https://cngu.github.io/vue-typer#style-showcase)
280 |
281 | ## Events
282 | #### `typed`
283 | - **Event data**:
284 | - `String` typedString
285 | - **Usage**:
286 | ```html
287 |
288 | ```
289 | ```javascript
290 | {
291 | ...
292 | methods: {
293 | onTyped: function(typedString) {
294 | // handle typed string
295 | }
296 | }
297 | }
298 | ```
299 |
300 | Emitted everytime VueTyper finishes typing a string.
301 |
302 | #### `typed-char`
303 | - **Event data**:
304 | - `String` typedChar
305 | - `Number` typedCharIndex
306 | - **Usage**:
307 | ```html
308 |
309 | ```
310 | ```javascript
311 | {
312 | ...
313 | methods: {
314 | onTypedChar: function(typedChar, typedCharIndex) {
315 | // handle typed character at the given index
316 | // call #1: 'w', 0
317 | // call #2: 'a', 1
318 | // call #3: 't', 2
319 | // ...
320 | }
321 | }
322 | }
323 | ```
324 |
325 | Emitted everytime VueTyper finishes typing a single character.
326 |
327 | #### `erased`
328 | - **Event data**:
329 | - `String` erasedString
330 | - **Usage**:
331 | ```html
332 |
333 | ```
334 | ```javascript
335 | {
336 | ...
337 | methods: {
338 | onErased: function(erasedString) {
339 | // handle erased string
340 | }
341 | }
342 | }
343 | ```
344 |
345 | Emitted everytime VueTyper finishes erasing a string.
346 |
347 | #### `completed`
348 | - **Usage**:
349 | ```html
350 |
351 | ```
352 | ```javascript
353 | {
354 | ...
355 | methods: {
356 | onComplete: function() {
357 | // handle event when VueTyper has finished all typing/erasing
358 | }
359 | }
360 | }
361 | ```
362 |
363 | Emitted when VueTyper has finished typing all words in [`text`](#text), [`repeat`](#repeat)`+1` times.
364 |
365 | - **Note**: If [`eraseOnComplete`](#eraseoncomplete) is enabled, the final typed string must also be erased before this event is emitted.
366 |
367 | ## Styles
368 | To keep the separation of concern between component code and styles, VueTyper can be fully styled through CSS (as opposed to props).
369 |
370 | The following is a skeleton selector structure to override the style of each component of VueTyper.
371 |
372 | - **Usage**:
373 | ```css
374 | /* SCSS */
375 | .vue-typer {
376 | /* Styles for the vue-typer container
377 | e.g. font-family, font-size */
378 |
379 | .custom.char {
380 | /* Styles for each character
381 | e.g. color, background-color */
382 |
383 | &.typed {
384 | /* Styles specific to typed characters
385 | i.e. characters to the left of the caret */
386 | }
387 | &.selected {
388 | /* Styles specific to selected characters
389 | i.e. characters to the right of the caret while VueTyper's
390 | 'eraseStyle' is set to a selection-based style */
391 | }
392 | &.erased {
393 | /* Styles specific to erased characters
394 | i.e. characters to the right of the caret while VueTyper's
395 | 'eraseStyle' is set to a non-selection-based style */
396 | }
397 | }
398 |
399 | .custom.caret {
400 | /* Styles for the caret
401 | e.g. background-color, animation, display */
402 |
403 | &.pre-type {
404 | /* Styles for the caret when it is idle before typing
405 | i.e. before a string starts being typed, during 'preTypeDelay' */
406 | }
407 | &.pre-erase {
408 | /* Styles for the caret when it is idle before erasing
409 | i.e. before a string starts being erased, during 'preEraseDelay' */
410 | }
411 | &.idle {
412 | /* Styles for the caret when it is idle, but VueTyper has not yet completed typing
413 | i.e. when 'pre-type' or 'pre-erase' is set, but not 'complete' */
414 | }
415 | &.typing {
416 | /* Styles for the caret while VueTyper is typing
417 | i.e. when the caret is moving forwards */
418 | }
419 | &.selecting {
420 | /* Styles for the caret while VueTyper is selecting
421 | i.e. when the caret is moving backwards and 'eraseStyle' is
422 | set to a selection-based style */
423 | }
424 | &.erasing {
425 | /* Styles for the caret while VueTyper is erasing
426 | i.e. when the caret is moving backwards and 'eraseStyle' is
427 | set to a non-selection-based style */
428 | }
429 | &.complete {
430 | /* Styles for the idle caret when VueTyper has finished all typing/erasing */
431 | }
432 | }
433 | }
434 | ```
435 |
436 | - **Note**: Some of the default styles above make things hidden using `display: none;`. If you wish to make it visible again, use `display: inline-block;`. Do not use `block`.
437 | - **See also**: [CSS Examples](https://cngu.github.io/vue-typer#style-showcase)
438 |
439 | ## Contribution Guide
440 | 1. Make all changes on the `develop` branch.
441 | 2. Update the demo page to showcase new APIs or features.
442 | 3. Add unit tests.
443 | 4. Update this README if necessary.
444 | 5. Submit a PR!
445 |
446 | ## Changelog
447 | Changes for each release will be documented [here](https://github.com/cngu/vue-typer/releases).
448 |
449 | ## TODO
450 | - Update to latest webpack
451 | - Remove Bootstrap usage in demo app
452 | - Consider marking lodash.split as a peer dependency via webpack externals (webpack-node-externals may be overkill?)
453 | - Revisit community discussions around the best way to obtain deterministic hashes so we can remove HashedModuleIdsPlugin
454 | - Potential features (pull requests are welcome!):
455 | - start typing only when VueTyper is on-screen; potentially pause typing when off-screen
456 | - smarter typing algorithm: erase only up to the longest common starting substring
457 | - is it worth it to eliminate time-drifting from setInterval? If so, it could be a self-correcting interval (implemented as a series of timeouts)
458 | - See submitted [feature requests](https://github.com/cngu/vue-typer/issues?q=is%3Aissue+is%3Aopen+label%3A%22feature+request%22)
459 | - Vue documentation considers rendering-specific tests to still be 'unit' tests. Should we split this out into 'integration' tests?
460 |
461 | ## License
462 |
463 | [MIT](http://opensource.org/licenses/MIT)
464 |
465 | Copyright © 2016-Present, Chris Nguyen. All rights reserved.
466 |
--------------------------------------------------------------------------------
/RELEASE.md:
--------------------------------------------------------------------------------
1 | 0. Ensure that `dist/` and `index.html` are not committed on `develop`.
2 | ```bash
3 | npm run clean
4 | ```
5 |
6 | 1. Update version in package.json on `develop`.
7 |
8 | 2. Create RC commit on `develop` with new version number:
9 | ```bash
10 | git add package.json
11 | git commit -m "RC MAJOR.MINOR.PATCH[ - optional message]"
12 | git push
13 | ```
14 |
15 | 3. Switch to `master`:
16 | ```bash
17 | git checkout master
18 | ```
19 |
20 | 4. Merge `develop` into `master`:
21 | ```bash
22 | git merge --no-commit develop
23 | ```
24 |
25 | 5. Test out the demo page locally:
26 | ```bash
27 | npm run dev
28 | ```
29 |
30 | 6. Build new package and demo page, and ensure all tests pass:
31 | ```bash
32 | npm run build
33 | ```
34 |
35 | 7. Ensure that the following pieces of information are still correct in the auto-generated comment at the top of vue-typer.js and vue-typer.min.js:
36 | - Package name
37 | - Version number
38 | - Copyright date
39 | - License
40 |
41 | 8. Ensure that the paths in `index.html` correctly point to the built files in `dist/`
42 |
43 | 9. Commit and tag with the same message:
44 | ```bash
45 | git add dist index.html
46 | git commit -m "[release] vMAJOR.MINOR.PATCH"
47 | git tag -a vMAJOR.MINOR.PATCH -m "[release] vMAJOR.MINOR.PATCH"
48 | ```
49 |
50 | 10. Publish to npm:
51 | ```bash
52 | npm publish
53 | ```
54 |
55 | 11. Push to github:
56 | ```bash
57 | git push --follow-tags
58 | ```
59 |
60 | 12. Add release notes on Github.
61 |
--------------------------------------------------------------------------------
/build/loader-util.js:
--------------------------------------------------------------------------------
1 | var ExtractTextPlugin = require('extract-text-webpack-plugin')
2 | var pathUtil = require('./path-util.js')
3 |
4 | var vueTyperStyles = pathUtil.getPathFromRoot('src/vue-typer/styles')
5 | var demoStyles = pathUtil.getPathFromRoot('src/demo/styles')
6 |
7 | var cssLoader = 'css-loader?minimize'
8 | var sassLoader = `sass-loader?includePaths[]=${vueTyperStyles}&includePaths[]=${demoStyles}`
9 |
10 | module.exports = {
11 | getCssLoader: function() {
12 | if (process.env.NODE_ENV === 'production') {
13 | return `style-loader!${cssLoader}`
14 | }
15 | return ExtractTextPlugin.extract({
16 | use: cssLoader,
17 | fallback: 'style-loader'
18 | })
19 | },
20 | getScssLoader: function() {
21 | if (process.env.NODE_ENV === 'production') {
22 | return `style-loader!${cssLoader}!${sassLoader}`
23 | }
24 | return ExtractTextPlugin.extract({
25 | use: [cssLoader, `${sassLoader}`],
26 | fallback: 'style-loader'
27 | })
28 | },
29 | getVueCssLoader: function() {
30 | if (process.env.NODE_ENV === 'production') {
31 | return `vue-style-loader!${cssLoader}`
32 | }
33 | return ExtractTextPlugin.extract({
34 | use: cssLoader,
35 | fallback: 'vue-style-loader'
36 | })
37 | },
38 | getVueScssLoader: function() {
39 | if (process.env.NODE_ENV === 'production') {
40 | return `vue-style-loader!${cssLoader}!${sassLoader}`
41 | }
42 | return ExtractTextPlugin.extract({
43 | use: [cssLoader, `${sassLoader}`]
44 | })
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/build/path-util.js:
--------------------------------------------------------------------------------
1 | var path = require('path')
2 | var projectRoot = path.resolve(__dirname, '../')
3 |
4 | module.exports = {
5 | getRoot: function() {
6 | return projectRoot
7 | },
8 | getPathFromRoot: function(relativePath) {
9 | return path.join(projectRoot, relativePath)
10 | },
11 | getPathsFromRoot: function(relativePaths) {
12 | return relativePaths.map(this.getPathFromRoot)
13 | },
14 | getLibPath: function(project, libName) {
15 | return path.join(projectRoot, `src/${project}/lib/${libName}/index.js`)
16 | },
17 | getPublicImageAssetPath: function() {
18 | return process.env.NODE_ENV === 'development' ? '/' : 'dist/'
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/build/template.index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | VueTyper Demo
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/build/uglify.config.base.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | test: /\.min\.js$/,
3 | // Must set this explicitly in webpack 2: https://github.com/shama/webpack-stream/issues/81#issuecomment-243436780
4 | sourceMap: true
5 | }
6 |
--------------------------------------------------------------------------------
/build/webpack.config.base.js:
--------------------------------------------------------------------------------
1 | var webpack = require('webpack')
2 | var ExtractTextPlugin = require('extract-text-webpack-plugin')
3 | var autoprefixer = require('autoprefixer')
4 | var pathUtil = require('./path-util')
5 | var loaderUtil = require('./loader-util')
6 |
7 | const config = {
8 | output: {
9 | path: pathUtil.getPathFromRoot('dist')
10 | },
11 | resolve: {
12 | extensions: ['.js', '.vue']
13 | },
14 | module: {
15 | rules: [
16 | {
17 | enforce: 'pre',
18 | test: /\.(js|vue)$/,
19 | loader: 'eslint-loader',
20 | include: pathUtil.getPathsFromRoot(['src', 'test']),
21 | options: {
22 | formatter: require('eslint-friendly-formatter')
23 | }
24 | },
25 | {
26 | test: /\.vue$/,
27 | loader: 'vue-loader',
28 | include: pathUtil.getPathsFromRoot(['src', 'test']),
29 | options: {
30 | loaders: {
31 | css: loaderUtil.getVueCssLoader(),
32 | scss: loaderUtil.getVueScssLoader()
33 | },
34 | postcss: [
35 | autoprefixer({ browsers: ['last 2 versions'] })
36 | ]
37 | }
38 | },
39 | {
40 | test: /\.js$/,
41 | loader: 'babel-loader',
42 | include: pathUtil.getPathsFromRoot(['src', 'test'])
43 | },
44 | {
45 | test: /\.css$/,
46 | loader: loaderUtil.getCssLoader(),
47 | include: pathUtil.getPathsFromRoot(['src', 'test'])
48 | },
49 | {
50 | test: /\.scss$/,
51 | loader: loaderUtil.getScssLoader(),
52 | include: pathUtil.getPathsFromRoot(['src', 'test'])
53 | },
54 | {
55 | test: /\.png$/,
56 | include: pathUtil.getPathFromRoot('src/demo/assets/images'),
57 | loader: 'url-loader',
58 | options: {
59 | name: '[name].[hash].[ext]',
60 | limit: 10000,
61 | publicPath: pathUtil.getPublicImageAssetPath()
62 | }
63 | }
64 | ]
65 | },
66 | plugins: [
67 | // http://vuejs.github.io/vue-loader/en/workflow/production.html
68 | new webpack.DefinePlugin({
69 | 'process.env': {
70 | NODE_ENV: JSON.stringify(process.env.NODE_ENV)
71 | }
72 | }),
73 | new webpack.NoEmitOnErrorsPlugin()
74 | ]
75 | }
76 |
77 | // Only demo builds require ExtractTextPlugin to allow style caching separately from the bundle.
78 | // In the production build, we must bundle the CSS along with the code.
79 | if (process.env.NODE_ENV !== 'production') {
80 | config.plugins.push(new ExtractTextPlugin('[name].[contenthash].min.css'))
81 | }
82 |
83 | module.exports = config
84 |
--------------------------------------------------------------------------------
/build/webpack.config.demo.js:
--------------------------------------------------------------------------------
1 | var webpack = require('webpack')
2 | var merge = require('webpack-merge')
3 | var HtmlWebpackPlugin = require('html-webpack-plugin')
4 |
5 | var pathUtil = require('./path-util')
6 | var baseWebpackConfig = require('./webpack.config.base')
7 | var baseUglifyConfig = require('./uglify.config.base')
8 |
9 | module.exports = merge(baseWebpackConfig, {
10 | entry: {
11 | demo: pathUtil.getPathFromRoot('src/demo/index.js'),
12 | vendor: ['vue', pathUtil.getLibPath('demo', 'bootstrap'), pathUtil.getLibPath('demo', 'prism')]
13 | },
14 | output: {
15 | filename: '[name].[chunkhash].min.js'
16 | },
17 | devtool: '#source-map',
18 | plugins: [
19 | new webpack.optimize.CommonsChunkPlugin({
20 | name: ['vendor', 'manifest']
21 | }),
22 | // Replace module IDs with the hash of the module pathnames. This solves the issue
23 | // where module IDs change between builds when code-changes in one bundle and cause
24 | // other bundles to be re-emitted even if their code hasn't changed, e.g. importing
25 | // a new module in demo.js causes a new vendor.js bundle to be emitted even if no
26 | // vendor code has changed, and vice versa. Note that this slightly increases the
27 | // size of the bundle, as the hashes are longer than IDs.
28 | // https://github.com/webpack/webpack/issues/1315
29 | // An alternative is NamedModulesPlugin, but this leaks/exposes the pathnames.
30 | new webpack.HashedModuleIdsPlugin(),
31 | new webpack.optimize.UglifyJsPlugin(baseUglifyConfig),
32 | new webpack.optimize.OccurrenceOrderPlugin(),
33 | new HtmlWebpackPlugin({
34 | title: 'vue-typer demo',
35 | template: 'build/template.index.html',
36 | filename: '../index.html',
37 | inject: 'body',
38 | minify: {
39 | removeComments: true,
40 | collapseWhitespace: false,
41 | removeAttributeQuotes: false
42 | },
43 | // Necessary to consistently work with multiple chunks via CommonsChunkPlugin
44 | chunksSortMode: 'dependency'
45 | })
46 | ]
47 | })
--------------------------------------------------------------------------------
/build/webpack.config.dev.js:
--------------------------------------------------------------------------------
1 | var webpack = require('webpack')
2 | var merge = require('webpack-merge')
3 | var HtmlWebpackPlugin = require('html-webpack-plugin')
4 |
5 | var pathUtil = require('./path-util')
6 | var baseWebpackConfig = require('./webpack.config.base')
7 |
8 | module.exports = merge(baseWebpackConfig, {
9 | entry: {
10 | demo: pathUtil.getPathFromRoot('src/demo/index.js'),
11 | vendor: ['vue', pathUtil.getLibPath('demo', 'bootstrap'), pathUtil.getLibPath('demo', 'prism')]
12 | },
13 | output: {
14 | filename: '[name].js',
15 | publicPath: "/"
16 | },
17 | // Ideally #eval-source-map, but breakpoints don't work: https://github.com/webpack/webpack/issues/2145
18 | devtool: '#source-map',
19 | plugins: [
20 | new webpack.optimize.CommonsChunkPlugin({
21 | name: ['vendor', 'manifest']
22 | }),
23 | new HtmlWebpackPlugin({
24 | title: 'vue-typer demo dev server',
25 | template: 'build/template.index.html',
26 | inject: 'body'
27 | })
28 | ]
29 | })
--------------------------------------------------------------------------------
/build/webpack.config.prod.js:
--------------------------------------------------------------------------------
1 | var webpack = require('webpack')
2 | var merge = require('webpack-merge')
3 | var HtmlWebpackPlugin = require('html-webpack-plugin')
4 |
5 | var pathUtil = require('./path-util')
6 | var baseWebpackConfig = require('./webpack.config.base')
7 | var baseUglifyConfig = require('./uglify.config.base')
8 |
9 | var packageJson = require('../package.json')
10 | var nameAndVersion = packageJson.name + ' v' + packageJson.version
11 | var copyright = 'Copyright 2016-' + new Date().getFullYear() + ' ' + packageJson.author
12 | var license = 'Released under the ' + packageJson.license + ' license.'
13 | var bannerComment = nameAndVersion + '\n' + copyright + '\n' + license
14 |
15 | module.exports = merge(baseWebpackConfig, {
16 | entry: {
17 | 'vue-typer': pathUtil.getPathFromRoot('src/vue-typer/index.js'),
18 | 'vue-typer.min': pathUtil.getPathFromRoot('src/vue-typer/index.js')
19 | },
20 | output: {
21 | filename: '[name].js',
22 | library: 'VueTyper',
23 | libraryTarget: 'umd'
24 | },
25 | // devtool: '#source-map',
26 | plugins: [
27 | // http://vuejs.github.io/vue-loader/en/workflow/production.html
28 | new webpack.optimize.UglifyJsPlugin(baseUglifyConfig),
29 | new webpack.optimize.OccurrenceOrderPlugin(),
30 | new webpack.BannerPlugin(bannerComment)
31 | ]
32 | })
--------------------------------------------------------------------------------
/build/webpack.config.test.js:
--------------------------------------------------------------------------------
1 | var webpack = require('webpack')
2 | var merge = require('webpack-merge')
3 | var pathUtil = require('./path-util.js')
4 | var baseConfig = require('./webpack.config.base')
5 |
6 | var webpackConfig = merge(baseConfig, {
7 | resolve: {
8 | alias: {
9 | // We want to use the standalone build for tests so we can use the 'template' option
10 | 'vue$': 'vue/dist/vue.common.js'
11 | }
12 | },
13 | // use eval for karma-sourcemap-loader
14 | devtool: '#eval'
15 | })
16 |
17 | module.exports = webpackConfig
18 |
--------------------------------------------------------------------------------
/dist/demo.1320e4785106378b9e121f0ec76a34aa.min.css:
--------------------------------------------------------------------------------
1 | html{height:100%;-webkit-font-smoothing:antialiased}html body{height:100%;margin:0;background:#eee}.demo #output-panel .demo-typer-container[data-v-6d53ee08]{height:100%}.demo #text-panel .form-group[data-v-6d53ee08]{margin-bottom:0}.demo #text-panel .form-group textarea[data-v-6d53ee08]{width:100%}.demo .shrink-text[data-v-6d53ee08]{font-size:.9rem}@keyframes rocking{0%,to{transform:rotate(-10deg)}50%{transform:rotate(10deg)}}main .state-typer{font-family:Arial,Helvetica Neue,Helvetica,sans-serif}main .state-typer .custom.char.typed{color:#009688}main .state-typer .custom.char.selected{color:#e91e63}main .state-typer .custom.caret{animation:rocking 1s ease-in-out 0s infinite}main .state-typer .custom.caret.typing{background-color:#009688}main .state-typer .custom.caret.selecting{display:inline-block;background-color:#e91e63}main .code-typer{font-family:monospace}main .code-typer .custom.char{color:#d4d4bd;background-color:#1e1e1e}main .code-typer .custom.char.selected{background-color:#264f78}main .code-typer .custom.caret{width:10px;background-color:#3f51b5}main .ghost-typer{font-family:Copperplate,Copperplate Gothic Light,fantasy}main .ghost-typer .custom.char.typed{color:#607d8b}main .ghost-typer .custom.char.selected{color:#607d8b;background-color:transparent;text-decoration:line-through}main .ghost-typer .custom.caret{display:none}span.vue-typer[data-v-c41bac74]{cursor:default;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}span.vue-typer span.left[data-v-c41bac74],span.vue-typer span.right[data-v-c41bac74]{display:inline}@keyframes vue-typer-caret-blink{50%{opacity:0}to{opacity:1}}@keyframes vue-typer-caret-smooth{0%,20%{opacity:1}60%,to{opacity:0}}@keyframes vue-typer-caret-phase{0%,20%{opacity:1}90%,to{opacity:0}}@keyframes vue-typer-caret-expand{0%,20%{transform:scaleY(1)}80%,to{transform:scaleY(0)}}.vue-typer-caret-blink[data-v-a16e0f02]{animation:vue-typer-caret-blink 1s step-start 0s infinite}.vue-typer-caret-smooth[data-v-a16e0f02]{animation:vue-typer-caret-smooth .5s ease-in-out 0s infinite alternate}.vue-typer-caret-phase[data-v-a16e0f02]{animation:vue-typer-caret-phase .5s ease-in-out 0s infinite alternate}.vue-typer-caret-expand[data-v-a16e0f02]{animation:vue-typer-caret-expand .5s ease-in-out 0s infinite alternate}span.caret[data-v-a16e0f02]:empty:before{content:"\200B"}span[data-v-a16e0f02]{display:inline-block;width:1px}.idle[data-v-a16e0f02],.typing[data-v-a16e0f02]{background-color:#000}.selecting[data-v-a16e0f02]{display:none;background-color:#000}.erasing[data-v-a16e0f02]{background-color:#000}.complete[data-v-a16e0f02]{display:none;background-color:#000}.char[data-v-302772ec]{display:inline-block;white-space:pre-wrap}.newline[data-v-302772ec]{display:inline}.typed[data-v-302772ec]{color:#000;background-color:transparent}.selected[data-v-302772ec]{color:#000;background-color:#accef7}.erased[data-v-302772ec]{display:none}.app-layout main[data-v-322ce952]{margin-bottom:8rem}.app-layout main #playground[data-v-322ce952]{margin-bottom:4rem}.app-layout main h4[data-v-322ce952]{height:1.1em;margin-bottom:1em;color:#35495e}.app-layout main .card[data-v-322ce952]{padding:1em;margin-bottom:0}header[data-v-d376d9aa]{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;-ms-flex-align:center;align-items:center;color:#fff;background:#35495e;padding:2rem 0}header h1[data-v-d376d9aa]{margin-bottom:2rem}header .link-bar[data-v-d376d9aa]{margin-bottom:1rem}header h1 .title-typer{font-family:Source Sans Pro,Helvetica Neue,Arial,sans-serif;font-weight:300}header h1 .title-typer .custom.char{color:#fff}header h1 .title-typer .custom.caret{background-color:#fff}header h1 .title-typer .custom.caret.complete{display:inline-block}.link-bar img[data-v-9cbc8336]{width:50px;height:50px;padding:8px}a[data-v-4ed29124]{position:relative;display:inline-block;margin:15px 30px;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;border-radius:50%;transition:box-shadow .2s}a[data-v-4ed29124]:after{content:"";position:absolute;top:0;left:0;width:100%;height:100%;padding:0;border-radius:50%;box-shadow:0 0 0 2px #fff;transition:transform .2s,opacity .2s}a[data-v-4ed29124]:hover{box-shadow:0 0 0 4px #fff}a[data-v-4ed29124]:hover:after{transform:scale(.8);opacity:.5}.badge-bar a[data-v-6512f7f4]{margin:0 3px}footer[data-v-0582c86e]{padding:2rem;color:#fff;background:#41b883;text-align:center}footer a[data-v-0582c86e]{color:#fff;font-weight:700;text-decoration:none}.shift-up[data-v-8e4ab660]{margin-top:-2px}.col-form-label[data-v-06d6e692]{padding-top:.4rem}.col-lg-6[data-v-67442243]{padding-right:0}
2 | /*# sourceMappingURL=demo.1320e4785106378b9e121f0ec76a34aa.min.css.map*/
--------------------------------------------------------------------------------
/dist/demo.ecd3fe850e3573d9d50a.min.js:
--------------------------------------------------------------------------------
1 | webpackJsonp([0],{"+aK1":function(e,t){},"0T+P":function(e,t,n){n("i3yy");var r=n("VU/8")(n("mti1"),n("bC94"),"data-v-06d6e692",null);r.options.__file="/Users/cngu/dev/vue-typer/src/demo/components/FormInput.vue",r.esModule&&Object.keys(r.esModule).some(function(e){return"default"!==e&&"__esModule"!==e})&&console.error("named exports are not supported in *.vue files."),r.options.functional&&console.error("[vue-loader] FormInput.vue: functional components are not supported with templates, they should use render functions."),e.exports=r.exports},"0aME":function(e,t,n){n("j57+");var r=n("VU/8")(n("erKa"),n("OepV"),"data-v-42c2d6cc",null);r.options.__file="/Users/cngu/dev/vue-typer/src/demo/components/CodeBlock.vue",r.esModule&&Object.keys(r.esModule).some(function(e){return"default"!==e&&"__esModule"!==e})&&console.error("named exports are not supported in *.vue files."),r.options.functional&&console.error("[vue-loader] CodeBlock.vue: functional components are not supported with templates, they should use render functions."),e.exports=r.exports},"1JWU":function(e,t){e.exports="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGAAAABgAgMAAACf9p+rAAAADFBMVEUAAAD///////////84wDuoAAAABHRSTlMA/wCA4LPRVwAAAIhJREFUeAHtzbEJw0AMQFEveaTLIBlJQqVG0RKeIhAwvNpNMNxvX/GPG+3Wr7g64uombNgwi15AC2+ghA+Qwul8XAjtQigXQroQYlwI7UIoF0K6EGJcCO1CKBdCuhBiXAjtQigXQroQYlwI7UIoF0K6EGJYhNAsQigWISQLwR4Ka9kzgWzDX+AL65Wl6DEWiToAAAAASUVORK5CYII="},"1osy":function(e,t,n){n("Y9A6");var r=n("VU/8")(n("pd3l"),n("GqHW"),"data-v-c41bac74",null);r.options.__file="/Users/cngu/dev/vue-typer/src/vue-typer/components/VueTyper.vue",r.esModule&&Object.keys(r.esModule).some(function(e){return"default"!==e&&"__esModule"!==e})&&console.error("named exports are not supported in *.vue files."),r.options.functional&&console.error("[vue-loader] VueTyper.vue: functional components are not supported with templates, they should use render functions."),e.exports=r.exports},"2vxi":function(e,t,n){n("Gk9R");var r=n("VU/8")(n("Awc8"),n("zf0g"),"data-v-302772ec",null);r.options.__file="/Users/cngu/dev/vue-typer/src/vue-typer/components/Char.vue",r.esModule&&Object.keys(r.esModule).some(function(e){return"default"!==e&&"__esModule"!==e})&&console.error("named exports are not supported in *.vue files."),r.options.functional&&console.error("[vue-loader] Char.vue: functional components are not supported with templates, they should use render functions."),e.exports=r.exports},"30KW":function(e,t,n){var r=n("kM2E"),o=n("7KvD").isFinite;r(r.S,"Number",{isFinite:function(e){return"number"==typeof e&&o(e)}})},"3IRH":function(e,t){e.exports=function(e){return e.webpackPolyfill||(e.deprecate=function(){},e.paths=[],e.children||(e.children=[]),Object.defineProperty(e,"loaded",{enumerable:!0,get:function(){return e.l}}),Object.defineProperty(e,"id",{enumerable:!0,get:function(){return e.i}}),e.webpackPolyfill=1),e}},"4Vle":function(e,t){},"59Ki":function(e,t){e.exports="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGAAAABgCAQAAABIkb+zAAABQUlEQVR4Ae3VPUpDQRxF8ekS3IoIohvMFiRBg+hWtHEFFoG4C7v3/JhrOaWBMy+XgXtuO80PBv4lpZRSOiVd60FHzaJtVHrutGdrPaoKRAgcsNarQJTAAffiUQIAXKpKdgIA3InHCQDwrj5tEAEAPtWngggAMPUCIAIAzB0BnGAHcIIdwAl2ACfYAZxgB3CCHYAJBgAgAMBXfwAn+AGc4Adwgh/ACX4AJfgBkOAHQIIf8B8BA77PCGiEYQGNMCygEfwAXgAB8AL4CSCAAJYsgAAK3OKAXw4IIIAAAgjAFgfUAFABBBBAAKMfMnFAAAEEEEAAvgIIIIAAvAUwy9nEAR9yduCAvZxtOeBGvqquOKDoSa52Kj0AF3qToxetKKARnnXeqnZaqXBA2632OmrW0k06aNv+PgCMsQACQAsggAD+AOE0ggxCDzgSAAAAAElFTkSuQmCC"},"5C9N":function(e,t,n){e.exports={render:function(){var e=this,t=e.$createElement,n=e._self._c||t;return n("div",{staticClass:"app-layout"},[e._t("header"),n("main",{staticClass:"container"},[n("section",{staticClass:"row",attrs:{id:"playground"}},[n("h4",{staticClass:"col-xs-12 text-xs-center"},[e._v("VueTyper Playground")]),n("div",{staticClass:"card col-xs-12 col-lg-6",attrs:{id:"output-panel"}},[e._t("main-playground-output")],2),n("div",{staticClass:"card col-xs-12 col-lg-6",attrs:{id:"text-panel"}},[e._t("main-playground-text")],2),n("div",{staticClass:"card col-xs-12 col-lg-6",attrs:{id:"config-panel"}},[e._t("main-playground-config")],2),n("div",{staticClass:"card col-xs-12 col-lg-6",attrs:{id:"code-panel"}},[e._t("main-playground-code")],2)]),n("section",{staticClass:"row",attrs:{id:"style-showcase"}},[n("h4",{staticClass:"col-xs-12 text-xs-center"},[e._v("VueTyper is also stylable with CSS!")]),n("div",{staticClass:"col-xs"},[n("div",{staticClass:"row"},[n("div",{staticClass:"card col-xs-12 col-lg-4"},[e._t("style-showcase-panel-1")],2),n("div",{staticClass:"card col-xs-12 col-lg-4"},[e._t("style-showcase-panel-2")],2),n("div",{staticClass:"card col-xs-12 col-lg-4"},[e._t("style-showcase-panel-3")],2)])])])]),e._t("footer")],2)},staticRenderFns:[]},e.exports.render._withStripped=!0},"5fl1":function(e,t,n){n("DpAd");var r=n("VU/8")(n("GAYh"),n("tfFV"),"data-v-4ed29124",null);r.options.__file="/Users/cngu/dev/vue-typer/src/demo/components/CircleLink.vue",r.esModule&&Object.keys(r.esModule).some(function(e){return"default"!==e&&"__esModule"!==e})&&console.error("named exports are not supported in *.vue files."),r.options.functional&&console.error("[vue-loader] CircleLink.vue: functional components are not supported with templates, they should use render functions."),e.exports=r.exports},"5jpQ":function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default={props:{value:String,label:String,options:Array,model:String},computed:{localModel:function(){return this.model}}}},"6qCy":function(e,t,n){n("BAUM");var r=n("VU/8")(n("SJM8"),n("LIZD"),"data-v-a16e0f02",null);r.options.__file="/Users/cngu/dev/vue-typer/src/vue-typer/components/Caret.vue",r.esModule&&Object.keys(r.esModule).some(function(e){return"default"!==e&&"__esModule"!==e})&&console.error("named exports are not supported in *.vue files."),r.options.functional&&console.error("[vue-loader] Caret.vue: functional components are not supported with templates, they should use render functions."),e.exports=r.exports},"91BW":function(e,t,n){n("Zsqw"),n("RMxe");var r=n("VU/8")(n("NWVg"),n("XPSn"),"data-v-6d53ee08",null);r.options.__file="/Users/cngu/dev/vue-typer/src/demo/Demo.vue",r.esModule&&Object.keys(r.esModule).some(function(e){return"default"!==e&&"__esModule"!==e})&&console.error("named exports are not supported in *.vue files."),r.options.functional&&console.error("[vue-loader] Demo.vue: functional components are not supported with templates, they should use render functions."),e.exports=r.exports},"9dTX":function(e,t,n){e.exports={render:function(){var e=this,t=e.$createElement;e._self._c;return e._m(0)},staticRenderFns:[function(){var e=this,t=e.$createElement,n=e._self._c||t;return n("footer",[n("small",[e._v("Released under the "),n("a",{attrs:{href:"https://opensource.org/licenses/MIT",target:"_blank"}},[e._v("MIT License")]),n("br"),e._v("Copyright © 2016-2017 Chris Nguyen")])])}]},e.exports.render._withStripped=!0},AMV0:function(e,t,n){e.exports={default:n("k2Ib"),__esModule:!0}},Awc8:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default={props:{val:{type:String,default:""}},computed:{classes:function(){return{newline:0===this.val.indexOf("\n")}}}}},BAUM:function(e,t){},C47m:function(e,t,n){n("xT9Q");var r=n("VU/8")(null,n("9dTX"),"data-v-0582c86e",null);r.options.__file="/Users/cngu/dev/vue-typer/src/demo/components/CopyrightFooter.vue",r.esModule&&Object.keys(r.esModule).some(function(e){return"default"!==e&&"__esModule"!==e})&&console.error("named exports are not supported in *.vue files."),r.options.functional&&console.error("[vue-loader] CopyrightFooter.vue: functional components are not supported with templates, they should use render functions."),e.exports=r.exports},"CxC/":function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}n("oJ8v");var o=n("/5sW"),s=r(o),a=n("91BW"),i=r(a);new s.default({el:"#demo",render:function(e){return e(i.default)}})},DE6i:function(e,t,n){n("+aK1");var r=n("VU/8")(n("Z4s0"),n("RQdl"),"data-v-9cbc8336",null);r.options.__file="/Users/cngu/dev/vue-typer/src/demo/components/LinkBar.vue",r.esModule&&Object.keys(r.esModule).some(function(e){return"default"!==e&&"__esModule"!==e})&&console.error("named exports are not supported in *.vue files."),r.options.functional&&console.error("[vue-loader] LinkBar.vue: functional components are not supported with templates, they should use render functions."),e.exports=r.exports},DpAd:function(e,t){},E6Cn:function(e,t,n){n("zGu/"),n("iWFa");var r=n("VU/8")(n("L7bS"),n("NYV3"),"data-v-d376d9aa",null);r.options.__file="/Users/cngu/dev/vue-typer/src/demo/components/HeroHeader.vue",r.esModule&&Object.keys(r.esModule).some(function(e){return"default"!==e&&"__esModule"!==e})&&console.error("named exports are not supported in *.vue files."),r.options.functional&&console.error("[vue-loader] HeroHeader.vue: functional components are not supported with templates, they should use render functions."),e.exports=r.exports},E8JD:function(e,t,n){n("T/1P");var r=n("VU/8")(n("ZKVD"),n("K/7M"),"data-v-8e4ab660",null);r.options.__file="/Users/cngu/dev/vue-typer/src/demo/components/FormCheck.vue",r.esModule&&Object.keys(r.esModule).some(function(e){return"default"!==e&&"__esModule"!==e})&&console.error("named exports are not supported in *.vue files."),r.options.functional&&console.error("[vue-loader] FormCheck.vue: functional components are not supported with templates, they should use render functions."),e.exports=r.exports},Ejat:function(e,t,n){n("MytS");var r=n("VU/8")(null,n("R+mQ"),"data-v-6512f7f4",null);r.options.__file="/Users/cngu/dev/vue-typer/src/demo/components/BadgeBar.vue",r.esModule&&Object.keys(r.esModule).some(function(e){return"default"!==e&&"__esModule"!==e})&&console.error("named exports are not supported in *.vue files."),r.options.functional&&console.error("[vue-loader] BadgeBar.vue: functional components are not supported with templates, they should use render functions."),e.exports=r.exports},GAYh:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default={props:{href:String}}},Gk9R:function(e,t){},GqHW:function(e,t,n){e.exports={render:function(){var e=this,t=e.$createElement,n=e._self._c||t;return n("span",{staticClass:"vue-typer"},[n("span",{staticClass:"left"},e._l(e.numLeftChars,function(t){return n("char",{staticClass:"custom typed",attrs:{val:e.currentTextArray[t-1]}})})),n("caret",{class:e.caretClasses,attrs:{animation:e.caretAnimation}}),n("span",{staticClass:"right"},e._l(e.numRightChars,function(t){return n("char",{staticClass:"custom",class:e.rightCharClasses,attrs:{val:e.currentTextArray[e.numLeftChars+t-1]}})}))],1)},staticRenderFns:[]},e.exports.render._withStripped=!0},H73T:function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function o(e){return"number"==typeof e&&!(0,u.default)(e)&&(0,i.default)(e)}function s(e,t){return o(e)&&o(t)&&e<=t}Object.defineProperty(t,"__esModule",{value:!0});var a=n("AMV0"),i=r(a),l=n("MICi"),u=r(l);t.default=function(e,t){return s(e,t)?(e=Math.ceil(e),t=Math.floor(t),Math.floor(Math.random()*(t-e+1))+e):-1}},"K/7M":function(e,t,n){e.exports={render:function(){var e=this,t=e.$createElement,n=e._self._c||t;return n("div",{staticClass:"form-group row flex-items-xs-center"},[n("label",{staticClass:"col-xs-4 col-md-3 col-lg-6 flex-xs-middle shift-up",attrs:{for:e.label}},[e._v(e._s(e.label))]),n("div",{staticClass:"col-xs-4 col-md-3 col-lg-6"},[n("div",{staticClass:"form-check"},[n("label",{staticClass:"form-check-label"},[n("input",{staticClass:"form-check-input",attrs:{id:e.label,type:"checkbox"},domProps:{value:e.value},on:{change:function(t){e.$emit("input",t.target.checked)}}})])])])])},staticRenderFns:[]},e.exports.render._withStripped=!0},K4R9:function(e,t,n){n("NA/8"),e.exports=n("FeBl").Number.isNaN},L7bS:function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var o=n("bE5q"),s=n("DE6i"),a=r(s),i=n("Ejat"),l=r(i);t.default={components:{VueTyper:o.VueTyper,LinkBar:a.default,BadgeBar:l.default}}},LIZD:function(e,t,n){e.exports={render:function(){var e=this,t=e.$createElement;return(e._self._c||t)("span",{staticClass:"caret custom",class:e.animationClass})},staticRenderFns:[]},e.exports.render._withStripped=!0},LwYq:function(e,t,n){n("i9x0");var r=n("VU/8")(null,n("5C9N"),"data-v-322ce952",null);r.options.__file="/Users/cngu/dev/vue-typer/src/demo/components/AppLayout.vue",r.esModule&&Object.keys(r.esModule).some(function(e){return"default"!==e&&"__esModule"!==e})&&console.error("named exports are not supported in *.vue files."),r.options.functional&&console.error("[vue-loader] AppLayout.vue: functional components are not supported with templates, they should use render functions."),e.exports=r.exports},MICi:function(e,t,n){e.exports={default:n("K4R9"),__esModule:!0}},MytS:function(e,t){},"NA/8":function(e,t,n){var r=n("kM2E");r(r.S,"Number",{isNaN:function(e){return e!=e}})},NWVg:function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var o=n("MICi"),s=r(o),a=n("bE5q"),i=n("LwYq"),l=r(i),u=n("E6Cn"),c=r(u),p=n("C47m"),d=r(p),f=n("E8JD"),h=r(f),m=n("0T+P"),y=r(m),v=n("ue+v"),x=r(v),g=n("0aME"),A=r(g);t.default={components:{VueTyper:a.VueTyper,AppLayout:l.default,HeroHeader:c.default,CopyrightFooter:d.default,FormCheck:h.default,FormInput:y.default,FormRadio:x.default,CodeBlock:A.default},data:function(){return{textModel:["Arya Stark","Jon Snow","Daenerys Targaryen","Melisandre","Tyrion Lannister"].join("\n"),repeatModel:1/0,shuffle:!1,initialAction:"typing",typeDelay:70,preTypeDelay:70,eraseDelay:250,preEraseDelay:2e3,eraseStyle:"select-all",eraseOnComplete:!1,caretAnimation:"blink"}},computed:{text:function(){return this.textModel.split("\n")},repeat:function(){var e=parseInt(this.repeatModel);return(0,s.default)(e)?1/0:e},playgroundDemoCode:function(){return"\n \n "},stateDemoStyleCode:function(){return"\n @keyframes rocking {\n 0%,100% {transform: rotateZ(-10deg);},\n 50% {transform: rotateZ(10deg);}\n }\n\n .vue-typer {\n font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif;\n }\n .vue-typer .custom.char.typed {\n color: #009688;\n }\n .vue-typer .custom.char.selected {\n color: #E91E63;\n }\n\n .vue-typer .custom.caret {\n animation: rocking 1s ease-in-out 0s infinite;\n }\n .vue-typer .custom.caret.typing {\n background-color: #009688;\n }\n .vue-typer .custom.caret.selecting {\n display: inline-block;\n background-color: #E91E63;\n }\n "},codeDemoStyleCode:function(){return"\n .vue-typer {\n font-family: monospace;\n }\n\n .vue-typer .custom.char {\n color: #D4D4BD;\n background-color: #1E1E1E;\n }\n .vue-typer .custom.char.selected {\n background-color: #264F78;\n }\n\n .vue-typer .custom.caret {\n width: 10px;\n background-color: #3F51B5;\n }\n "},ghostDemoStyleCode:function(){return"\n .vue-typer {\n font-family: Copperplate, 'Copperplate Gothic Light', fantasy;\n }\n\n .vue-typer .custom.char.typed {\n color: #607D8B;\n }\n .vue-typer .custom.char.selected {\n color: #607D8B;\n background-color: transparent;\n text-decoration: line-through;\n }\n\n .vue-typer .custom.caret {\n display: none;\n }\n "}}}},NYV3:function(e,t,n){e.exports={render:function(){var e=this,t=e.$createElement,n=e._self._c||t;return n("header",{staticClass:"jumbotron jumbotron-fluid"},[n("h1",{staticClass:"display-4"},[n("vue-typer",{staticClass:"title-typer",attrs:{text:"VueTyper",repeat:0,"pre-type-delay":1e3,"type-delay":400,"caret-animation":"smooth"}})],1),n("link-bar",{staticClass:"link-bar"}),n("badge-bar")],1)},staticRenderFns:[]},e.exports.render._withStripped=!0},OepV:function(e,t,n){e.exports={render:function(){var e=this,t=e.$createElement,n=e._self._c||t;return n("pre",{staticClass:"code-block"},[n("code",{ref:"codeBlock",class:e.languageClass})])},staticRenderFns:[]},e.exports.render._withStripped=!0},"R+mQ":function(e,t,n){e.exports={render:function(){var e=this,t=e.$createElement;e._self._c;return e._m(0)},staticRenderFns:[function(){var e=this,t=e.$createElement,n=e._self._c||t;return n("span",{staticClass:"badge-bar"},[n("a",{attrs:{href:"https://www.npmjs.com/package/vue-typer",target:"_blank"}},[n("img",{attrs:{src:"https://img.shields.io/npm/dt/vue-typer.svg",alt:"Downloads"}})]),n("a",{attrs:{href:"https://www.npmjs.com/package/vue-typer",target:"_blank"}},[n("img",{attrs:{src:"https://img.shields.io/npm/v/vue-typer.svg",alt:"Version"}})]),n("a",{attrs:{href:"https://www.npmjs.com/package/vue-typer",target:"_blank"}},[n("img",{attrs:{src:"https://img.shields.io/npm/l/vue-typer.svg",alt:"License"}})])])}]},e.exports.render._withStripped=!0},RMxe:function(e,t){},RQdl:function(e,t,n){e.exports={render:function(){var e=this,t=e.$createElement,r=e._self._c||t;return r("div",{staticClass:"link-bar"},[r("circle-link",{attrs:{href:"https://github.com/cngu/vue-typer"}},[r("img",{attrs:{src:n("oGlG")}})]),r("circle-link",{attrs:{href:"https://github.com/cngu/vue-typer/blob/master/README.md#getting-started"}},[r("img",{attrs:{src:n("59Ki")}})]),r("circle-link",{attrs:{href:"https://api.github.com/repos/cngu/vue-typer/zipball"}},[r("img",{attrs:{src:n("1JWU")}})])],1)},staticRenderFns:[]},e.exports.render._withStripped=!0},SHlX:function(e,t,n){"use strict";function r(e,t,n){if(t!==n){var r=e[t];e[t]=e[n],e[n]=r}}Object.defineProperty(t,"__esModule",{value:!0});var o=n("H73T"),s=function(e){return e&&e.__esModule?e:{default:e}}(o);t.default=function(e){if(e instanceof Array)for(var t=e.length-1;t>0;t--){var n=(0,s.default)(0,t);r(e,t,n)}}},SJM8:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});t.default={props:{animation:{type:String,default:"blink",validator:function(e){return/^solid$|^blink$|^smooth$|^phase$|^expand$/.test(e)}}},computed:{animationClass:function(){return"vue-typer-caret-"+this.animation}}}},"T/1P":function(e,t){},"VU/8":function(e,t){e.exports=function(e,t,n,r){var o,s=e=e||{},a=typeof e.default;"object"!==a&&"function"!==a||(o=e,s=e.default);var i="function"==typeof s?s.options:s;if(t&&(i.render=t.render,i.staticRenderFns=t.staticRenderFns),n&&(i._scopeId=n),r){var l=i.computed||(i.computed={});Object.keys(r).forEach(function(e){var t=r[e];l[e]=function(){return t}})}return{esModule:o,exports:s,options:i}}},XPSn:function(e,t,n){e.exports={render:function(){var e=this,t=e.$createElement,n=e._self._c||t;return n("app-layout",{staticClass:"demo"},[n("hero-header",{slot:"header"}),n("template",{slot:"main-playground-output"},[n("h3",{staticClass:"demo-typer-container row flex-items-xs-center flex-items-xs-middle"},[n("vue-typer",{staticClass:"demo-typer",attrs:{text:e.text,repeat:e.repeat,shuffle:e.shuffle,"initial-action":e.initialAction,"pre-type-delay":e.preTypeDelay,"type-delay":e.typeDelay,"pre-erase-delay":e.preEraseDelay,"erase-delay":e.eraseDelay,"erase-style":e.eraseStyle,"erase-on-complete":e.eraseOnComplete,"caret-animation":e.caretAnimation}})],1)]),n("template",{slot:"main-playground-text"},[n("div",{staticClass:"form-group"},[n("label",{attrs:{for:"text"}},[e._v("List of words to type:")]),n("textarea",{directives:[{name:"model",rawName:"v-model",value:e.textModel,expression:"textModel"}],attrs:{id:"text",placeholder:"text",rows:3},domProps:{value:e.textModel},on:{input:function(t){t.target.composing||(e.textModel=t.target.value)}}})])]),n("template",{slot:"main-playground-config"},[n("div",{staticClass:"row"},[n("div",{staticClass:"col-xs-12 col-lg-6",attrs:{id:"general-config"}},[n("form-input",{attrs:{label:"repeat"},model:{value:e.repeatModel,callback:function(t){e.repeatModel=t},expression:"repeatModel"}}),n("form-check",{attrs:{label:"shuffle"},model:{value:e.shuffle,callback:function(t){e.shuffle=t},expression:"shuffle"}}),n("form-check",{staticClass:"shrink-text",attrs:{label:"eraseOnComplete"},model:{value:e.eraseOnComplete,callback:function(t){e.eraseOnComplete=t},expression:"eraseOnComplete"}}),n("form-radio",{attrs:{model:e.initialAction,label:"initialAction",options:["typing","erasing"]},model:{value:e.initialAction,callback:function(t){e.initialAction=t},expression:"initialAction"}})],1),n("div",{staticClass:"col-xs-12 col-lg-6",attrs:{id:"delay-config"}},[n("form-input",{attrs:{label:"preTypeDelay",type:"number"},model:{value:e.preTypeDelay,callback:function(t){e.preTypeDelay=e._n(t)},expression:"preTypeDelay"}}),n("form-input",{attrs:{label:"typeDelay",type:"number"},model:{value:e.typeDelay,callback:function(t){e.typeDelay=e._n(t)},expression:"typeDelay"}}),n("form-input",{attrs:{label:"preEraseDelay",type:"number"},model:{value:e.preEraseDelay,callback:function(t){e.preEraseDelay=e._n(t)},expression:"preEraseDelay"}}),n("form-input",{attrs:{label:"eraseDelay",type:"number"},model:{value:e.eraseDelay,callback:function(t){e.eraseDelay=e._n(t)},expression:"eraseDelay"}})],1),n("div",{staticClass:"col-xs-12 col-lg-6",attrs:{id:"erase-style-config"}},[n("form-radio",{attrs:{model:e.eraseStyle,label:"eraseStyle",options:["backspace","select-back","select-all","clear"]},model:{value:e.eraseStyle,callback:function(t){e.eraseStyle=t},expression:"eraseStyle"}})],1),n("div",{staticClass:"col-xs-12 col-lg-6",attrs:{id:"caret-config"}},[n("form-radio",{attrs:{model:e.caretAnimation,label:"caretAnimation",options:["solid","blink","smooth","phase","expand"]},model:{value:e.caretAnimation,callback:function(t){e.caretAnimation=t},expression:"caretAnimation"}})],1)])]),n("template",{slot:"main-playground-code"},[n("code-block",{attrs:{code:e.playgroundDemoCode,language:"html"}})],1),n("template",{slot:"style-showcase-panel-1"},[n("h4",{staticClass:"text-xs-center"},[n("vue-typer",{staticClass:"state-typer",attrs:{text:"Katniss Everdeen","pre-type-delay":1e3,"type-delay":160,"pre-erase-delay":2e3,"erase-delay":80,"erase-style":"select-back","caret-animation":"solid"}})],1),n("code-block",{attrs:{code:e.stateDemoStyleCode,language:"css"}})],1),n("template",{slot:"style-showcase-panel-2"},[n("h4",{staticClass:"text-xs-center"},[n("vue-typer",{staticClass:"code-typer",attrs:{text:"Katniss Everdeen","pre-type-delay":1e3,"type-delay":160,"pre-erase-delay":2e3,"erase-delay":1280,"erase-style":"select-all","caret-animation":"blink"}})],1),n("code-block",{attrs:{code:e.codeDemoStyleCode,language:"css"}})],1),n("template",{slot:"style-showcase-panel-3"},[n("h4",{staticClass:"card-title text-xs-center"},[n("vue-typer",{staticClass:"ghost-typer",attrs:{text:"Katniss Everdeen","pre-type-delay":1e3,"type-delay":160,"pre-erase-delay":2e3,"erase-delay":80,"erase-style":"select-back"}})],1),n("code-block",{attrs:{code:e.ghostDemoStyleCode,language:"css"}})],1),n("copyright-footer",{slot:"footer"})],2)},staticRenderFns:[]},e.exports.render._withStripped=!0},Y9A6:function(e,t){},"YI+A":function(e,t,n){(function(e,n){function r(e){return e.split("")}function o(e){return G.test(e)}function s(e){return o(e)?a(e):r(e)}function a(e){return e.match(F)||[]}function i(e){return v(e)&&W.call(e)==S}function l(e,t,n){var r=-1,o=e.length;t<0&&(t=-t>o?0:o+t),n=n>o?o:n,n<0&&(n+=o),o=t>n?0:n-t>>>0,t>>>=0;for(var s=Array(o);++r=r?e:l(e,t,n)}function p(e,t){return!!(t=null==t?M:t)&&("number"==typeof e||k.test(e))&&e>-1&&e%1==0&&e-1&&e%1==0&&e<=M}function v(e){var t=typeof e;return!!e&&("object"==t||"function"==t)}function x(e){return!!e&&"object"==typeof e}function g(e){return"symbol"==typeof e||x(e)&&W.call(e)==_}function A(e){return null==e?"":u(e)}function C(e,t,n){return n&&"number"!=typeof n&&d(e,t,n)&&(t=n=void 0),(n=void 0===n?E:n>>>0)?(e=A(e),e&&("string"==typeof t||null!=t&&!ee(t))&&!(t=u(t))&&o(e)?c(s(e),0,n):e.split(t,n)):[]}var b=1/0,M=9007199254740991,E=4294967295,T="[object Function]",I="[object GeneratorFunction]",S="[object RegExp]",_="[object Symbol]",k=/^(?:0|[1-9]\d*)$/,D="[\\u0300-\\u036f\\ufe20-\\ufe23\\u20d0-\\u20f0]",w="\\ud83c[\\udffb-\\udfff]",j="(?:\\ud83c[\\udde6-\\uddff]){2}",R="[\\ud800-\\udbff][\\udc00-\\udfff]",B="(?:[\\u0300-\\u036f\\ufe20-\\ufe23\\u20d0-\\u20f0]|\\ud83c[\\udffb-\\udfff])?",O="(?:\\u200d(?:"+["[^\\ud800-\\udfff]",j,R].join("|")+")[\\ufe0e\\ufe0f]?"+B+")*",N="[\\ufe0e\\ufe0f]?"+B+O,V="(?:"+["[^\\ud800-\\udfff]"+D+"?",D,j,R,"[\\ud800-\\udfff]"].join("|")+")",F=RegExp(w+"(?="+w+")|"+V+N,"g"),G=RegExp("[\\u200d\\ud800-\\udfff\\u0300-\\u036f\\ufe20-\\ufe23\\u20d0-\\u20f0\\ufe0e\\ufe0f]"),L="object"==typeof e&&e&&e.Object===Object&&e,Y="object"==typeof self&&self&&self.Object===Object&&self,U=L||Y||Function("return this")(),Q="object"==typeof t&&t&&!t.nodeType&&t,Z=Q&&"object"==typeof n&&n&&!n.nodeType&&n,H=Z&&Z.exports===Q,K=H&&L.process,J=function(){try{return K&&K.binding("util")}catch(e){}}(),P=J&&J.isRegExp,X=Object.prototype,W=X.toString,z=U.Symbol,q=z?z.prototype:void 0,$=q?q.toString:void 0,ee=P?function(e){return function(t){return e(t)}}(P):i;n.exports=C}).call(t,n("DuR2"),n("3IRH")(e))},Z4s0:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=n("5fl1"),o=function(e){return e&&e.__esModule?e:{default:e}}(r);t.default={components:{CircleLink:o.default}}},ZKVD:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default={props:{value:Boolean,label:String}}},Zsqw:function(e,t){},bC94:function(e,t,n){e.exports={render:function(){var e=this,t=e.$createElement,n=e._self._c||t;return n("div",{staticClass:"form-group row flex-items-xs-center"},[n("label",{staticClass:"col-form-label col-xs-4 col-md-3 col-lg-6",attrs:{for:e.label}},[e._v(e._s(e.label))]),n("div",{staticClass:"col-xs-4 col-md-3 col-lg-6"},[n("input",{staticClass:"form-control",attrs:{id:e.label,type:e.type},domProps:{value:e.value},on:{input:function(t){e.$emit("input",t.target.value)}}})])])},staticRenderFns:[]},e.exports.render._withStripped=!0},bE5q:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.VueTyper=void 0;var r=n("1osy"),o=function(e){return e&&e.__esModule?e:{default:e}}(r);t.VueTyper=o.default;t.default={install:function(e){e.component("vue-typer",o.default)}}},erKa:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=n("cmFa"),o=function(e){return e&&e.__esModule?e:{default:e}}(r),s={html:"markup",css:"css"};t.default={props:{code:String,language:String},computed:{languageClass:function(){return"language-"+s[this.language]}},mounted:function(){this.highlightCode()},methods:{highlightCode:function(){var e=this.$refs.codeBlock;e&&(e.innerHTML="",e.appendChild(document.createTextNode(this.code)),o.default.highlightElement(e))}},watch:{code:function(){this.highlightCode()}}}},i3yy:function(e,t){},i9x0:function(e,t){},iWFa:function(e,t){},"j57+":function(e,t){},k2Ib:function(e,t,n){n("30KW"),e.exports=n("FeBl").Number.isFinite},mti1:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default={props:{value:{},label:String,type:String}}},oGlG:function(e,t){e.exports="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHgAAAB4CAYAAAA5ZDbSAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyRpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNS1jMDIxIDc5LjE1NDkxMSwgMjAxMy8xMC8yOS0xMTo0NzoxNiAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6RERCMUIwQTM4NkNFMTFFM0FBNTJFRTMzNTJEMUJDNDYiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6RERCMUIwQTI4NkNFMTFFM0FBNTJFRTMzNTJEMUJDNDYiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoTWFjaW50b3NoKSI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOkU1MTc4QTMyOTlBMDExRTI5QTE1QkMxMDQ2QTg5MDREIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjJBNDE0QUJDOTlBMTExRTI5QTE1QkMxMDQ2QTg5MDREIi8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+8kSqyAAADD5JREFUeNrsXQ2QlVUZfllYUBe2YCuQFNel9Q9EcVEQSA3xB2pTSVcESjELnZomBW0ya5w0m1GyzKSmtEYDc6hGohRDrUGQZUko0EARCAXK+FEwXFz2yvY+fO/d+fbu/fm++533+7n3PDPPwC6Xc77zPvc7P+95z3t6dHR0kEXpoleJtGMwcwTzE8w6Zi1zELNG2JfZJ+P/tDEPMPcK32JuY25lbmauZ/476YbpkcA3+BjmucxxwlHMAUp1vc18ifmisJnZagU2jyHMKcxJzPOzvI1hAW/9MuYS5pPMN6zAxeNjzOnMq5mjY/qMLcyFzPnMXVZgb7iQOYt5ObMyIT1hO/MPzJ8xn7cCZ5/sTWXeKpOlJAOTs/uYTzBT5S4whJ3BvIM5tMRWKFuYd0v3nSpHgT/NnMs8pcSXoq8xZzOfKheBT2I+wLy0zHwOzzC/LoKHhooQ68KE6XYZo8pNXJI2rxMbVJbaG3wa83HmGWRBIvQ05oakv8E9mF9hrrHidsEZYpOvio0S+QbD//tL5lVWz7z4HXMmOX7xxAhcz1wkXbNFYWxkXsZ8PQld9HjmKiuuL5wqNhsfd4GbyHHVDbCa+cYAsV1TXAXGOPIbZm+rVdHoLTa8Pm4C3yQTqgqrkRFNHhGbxmKSNVPEtTCPLwa1bVCBm6RLsW+uDg4zryFnzzl0gcfLpMCOubo4RM4e+YowBa6Xab2dLYcDxIaNKWadXIzA8FCtlrWbRXiAM+Qc8unx8jt2wm/6KytuJDhVbN9DU2BsHFwZ8EH3keNof1n+XurYJ21Fm/cHLOtK0UCli4brcS0FD1n9DHWNbjhOJhHYL4U/9uiEC3qQnAC8Z2QSusP1b43MxQHLR+huA/OfJgXGBvXfKPiWHyYLOHHQnuPfq8mJ0UJUZdKC7/CWIqoSMVjv5rHjf5n9A9aF/eSz89jRdxd9G5nZz11S4KFgmHlSF4LcWxIg7Gp51hHy7O/m+Wy72CAoYJ9vmBqDT2Z+25AxXvDxWXRxOKLyOXLOC8UNW2VMHCPP6hXLDdV/h2gTuIv+M/NiQw/VIOO4X2DcnyNftFxzgDdkXHqVuZOcg2MgDpa9J2Njm6s8jPVV5BxOGyz8ODlRnsOYJ+QZA+9h3st8v0gbvGTInkuZlwQRGKGtfzL0MO1i0PYAZcDBAkf8cOZK6RGWy/hnOiIC6/3TyfHYnUfOQTd8gW6gYJGRlfKFMxV4lzlp9SxwL2nQSYYe5M08b4XftTh4OOQuOT2cmah3u6weTOB1WeGk/I7BMwyKC7xlqJyOCMRNC2uq3v8YfK560crXJKtSBnHT60MLB6bPGEOr3n4ExkGwoVaHxABaXe1H4DkKD3GU1aETGt66W70KPJF0vEgnWF07MUShzNNFu4IC36jUqIHMflbbIzYYqFT2TYUERtqEzypVjqXNWVbfIzbQOq7SKBrmFHgG6Z58m2j1VbVBZeaSKVPgJuXGNVp91W3QlEtgJBDTzmZzt9VX3Qaj3Utct8CXK1d8Fzkn6codsMF3leu4LJvAkxQrXBVCo5KEu8QmWpjcObOVzQakB0S0hUYGuQ9kjbbR6toF2JbELphGvlBsaSKkuTX9Bo8jvfSAD1lxs+JVsY0G+oimnV30WKWKsCH+PatlTtxDxQUNeMFYt8DjlCr5NcU0h2NMsEtspIFx7jF4L+kcQ8GUfbXVMS9wWkEjuBBzqhoIjDikHQoVbCW75egVW8QPYRrHoYvWij9+2urmGUuUyh0BgeuVCl9hdYvcVvUQuFapcDv2Rm+rWi2BERr7ptXNM2CrlJbAgxQKRljoB1Y3z4C4OxXKHQSBaxQK/p/VzDc0jtLWaAm83+rlGwe0BNaIk+pp9fINjU2HfhBYI0tOX6uXb2iEFffWym9VZfXyjWqNQrUEtrmzYmIz+KI1EkYfki7HXm3q/UXDtmGlRsEppW/jYKubZwwmnXDlVIXikuZEq5tn1CmVu7+C9HJV1VndIn8Z9kHg3UqFj7K6ecbZSuXuhsA7lQofa3WL3FY7NQU+k5xwXIvCPoMRmgJvVioc7soJVr+CmEB6rt3NEHiT4sNPsfoVxBWKZW+CowPpfLYrVYBtQ+w3t1odswJDGLIPaR2MPx5vMCIq9ypVgAefbnXMiemK4iJsdkfaF71GsRG3kL20Ixt6iW20cCRdYtrwKxUrwiGra62e3fB50r39vNkt8IvKjcEZnGqraSeqxSaaWOEWGD+0KVaGidb9VtdO/Ih0gh3TaMsUGFtVy5UbhVu8plltjyRJmalcx3LRtMvk548hNO5hcpJ8lytw4u/nIdTTmQLanU4Ymei2hVA5Ut4jwXhLmYmLk5ZLQ5qL1JKTIL3LG4xfhHHcpFoaenEZiYv8J8+GJO7qtLiUZX26IMRZJE7U3UmlHWKLtiFt0lMUXhrHx90/ZGZ8/yg5u0uVIRoBSzRc9rSuxMRFysJ5pJ97zA2cCYPreVeuNxib/4simHjAk/YT0snCGjYQnfELcjxJo0OuexFlpMzIdmfDBcy/+ii0WWZtKBjZArB5jS2wXkV+AzFM/JSSdfwUyUU/SU6m3qYIh50JmdrlupQDV9+M9FAgbg/5EHU/SYiu/mbmbCo+3hepl56QL8/fKX4huD1lyYekY1Mp+iBDDHFndvvm5RAYi3Gv2V9uZ34/y0IbnpTH5I0cGfDhcR3cC9Jb4Iq9Vyj8iy0xtuE6n1HSS0HcD8foCwff9nyvAqN7RaIur0lUHiDnqrU215pvgMyUEZKykFzp9QwB25xbZD39TTJ/Ewsmmj+WttRJTxVXwA7YuOge4w6Bc/DaDn/YyByZUcYVzGXMY+VP0ziQpU6TbGC+3xF/XJerDfkaV8Fc77OiVuYlrjKGMXczJzFrmNsNN2yWorhpfi3m4r4sWmV9/kJX28ED4zcdEu5HQlbzbHvMkynPNWxFTCrOIv1LsjCZQtLQuN56PpnypGEqFGmxhPzfXYgrY35PXe8OqBJXHcaIRw017D4K5wY0rBDujam4T1OBHFtebh/FRAt3GPrNRovdqfQFH8fIpAj37OG2TORKPjlAwxDMN5DCu02trziB4nT3Eya0w2SCRcW+wekZ2neKeIBG18y5VTxWt8nyppGCBdz/hcK9Ku+A1Bkn3FlIXK8CA/dTcXfe/sBVBxwXy6S7xloSV9duKLJxKyMwaJwy98G1O9fLB70KnBLnh9+35hTqfssI7uPFjseD5By6wpfgkI8yEai/NAKjxiWp+UHRImVSYOA1cT/6xeyMn58jJ7LjoHTdc8TN9y1ydpYyg+T3iGcM9xyMkS/NPyIw7LaYCHyzOKG8oYh14fwi1mrn5invROazzAeZR8nv+jOHMPu5PjeKOZd5fghr32ysjcGad4Hf5y6moVXMdT4frJnZM0d5dcw98rkG+d158rsNIjZ+t1Y+Mz8igT8SsbhwOvX1+9zFnDh4T5Y/fg6Oj5FZXzYgcfjx5ISRrnGNM0jQ+S+Xfxt3AV3KvD6irjEVYbe8R2zuOxuel3VwLmA35XnydxcuIjfmUTKBnaN3IppUTSx25RDkzBC27qb69CY9JNP7ygQKHMUzw7bTgiwLgx4KW8z8gk+RMatGQMFFCRO4KgJxYdtAIVQmTv0tkHHRj8jDZS2Lvdwbyd8xjmOp9JOdwpazyECUa5AxOBM46/pYgC8N3G6vyHpzn6yHEeuEdMfYuKgl54o8BBL0p/AjOmpl0hfWm2skhNlkCls8EJKqLfQ58UpjKHmPIOlTom/uQZnXLDZVoOmD2dha/BTp33Z2dAmKC5tdaFJcDYFJxtVzInInJhXrxWbNpgvWSq2AszHYVHjUalcQiF4dS67zREkQGIDH6zrmDfJ3i+72+ZJMqNTsE0ZylEfICchusZp2GcYQT/awdkVhZb9BNj1EdNxC4UZixHGWPEdssSmMCsNMb4TgtR+SE534ZBmKizafRk6AQ2iXhkWRvwqTiSmyJFhbBsLiXNVF0uZtYVceZYIyBLEhNusa8h8Ok4SUTBulbWjjc1E9RNQZ6OAnxQlC+KZx7HKVx//3dgTP6jXNVIu0Zbi07XCUBjbpizYFBAekz9lm81itoeiyySOytCGH+L8l51zzyjgZM44Cp4EN9qvI2cRAcAE2HnC4+ctaTgEPqCXn9P4F8maix1kg4r4TRyPGWWCLEhiDLZTxfwEGAIg2ItsKhKpcAAAAAElFTkSuQmCC"},oJ8v:function(e,t){},pd3l:function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var o=n("fZjL"),s=r(o),a=n("6qCy"),i=r(a),l=n("2vxi"),u=r(l),c=n("slMF"),p=r(c),d=n("SHlX"),f=r(d),h=n("YI+A"),m=r(h),y={IDLE:"idle",TYPING:"typing",ERASING:"erasing",COMPLETE:"complete"},v={BACKSPACE:"backspace",SELECT_BACK:"select-back",SELECT_ALL:"select-all",CLEAR:"clear"};t.default={name:"VueTyper",components:{Caret:i.default,Char:u.default},props:{text:{type:[String,Array],required:!0,validator:function(e){return"string"==typeof e?e.length>0:e.every(function(e){return"string"==typeof e&&e.length>0})}},repeat:{type:Number,default:1/0,validator:function(e){return e>=0}},shuffle:{type:Boolean,default:!1},initialAction:{type:String,default:y.TYPING,validator:function(e){return!!e.match("^"+y.TYPING+"|"+y.ERASING+"$")}},preTypeDelay:{type:Number,default:70,validator:function(e){return e>=0}},typeDelay:{type:Number,default:70,validator:function(e){return e>=0}},preEraseDelay:{type:Number,default:2e3,validator:function(e){return e>=0}},eraseDelay:{type:Number,default:250,validator:function(e){return e>=0}},eraseStyle:{type:String,default:v.SELECT_ALL,validator:function(e){return(0,s.default)(v).some(function(t){return v[t]===e})}},eraseOnComplete:{type:Boolean,default:!1},caretAnimation:String},data:function(){return{state:y.IDLE,nextState:null,spool:[],spoolIndex:-1,previousTextIndex:-1,currentTextIndex:-1,repeatCounter:0,actionTimeout:0,actionInterval:0}},computed:{caretClasses:function(){var e=this.state===y.IDLE;return{idle:e,"pre-type":e&&this.nextState===y.TYPING,"pre-erase":e&&this.nextState===y.ERASING,typing:this.state===y.TYPING,selecting:this.state===y.ERASING&&this.isSelectionBasedEraseStyle,erasing:this.state===y.ERASING&&!this.isSelectionBasedEraseStyle,complete:this.state===y.COMPLETE}},rightCharClasses:function(){return{selected:this.state===y.ERASING&&this.isSelectionBasedEraseStyle,erased:this.state!==y.ERASING||this.state===y.ERASING&&!this.isSelectionBasedEraseStyle}},isSelectionBasedEraseStyle:function(){return!!this.eraseStyle.match("^"+v.SELECT_BACK+"|"+v.SELECT_ALL+"$")},isEraseAllStyle:function(){return!!this.eraseStyle.match("^"+v.CLEAR+"|"+v.SELECT_ALL+"$")},isDoneTyping:function(){return this.currentTextIndex>=this.currentTextLength},isDoneErasing:function(){return this.isSelectionBasedEraseStyle?this.currentTextIndex<=0&&this.previousTextIndex<=0:this.currentTextIndex<=0},onLastWord:function(){return this.spoolIndex===this.spool.length-1},shouldRepeat:function(){return this.repeatCounter=0&&this.spoolIndex0}),this.spool=e}this.repeatCounter=0,this.resetSpool(),this.initialAction===y.TYPING?this.startTyping():this.initialAction===y.ERASING&&(this.moveCaretToEnd(),this.onTyped())},reset:function(){this.cancelCurrentAction(),this.init()},resetSpool:function(){this.spoolIndex=0,this.shuffle&&this.spool.length>1&&(0,f.default)(this.spool)},cancelCurrentAction:function(){this.actionInterval&&(clearInterval(this.actionInterval),this.actionInterval=0),this.actionTimeout&&(clearTimeout(this.actionTimeout),this.actionTimeout=0)},shiftCaret:function(e){this.previousTextIndex=this.currentTextIndex;var t=this.currentTextIndex+e;this.currentTextIndex=Math.min(Math.max(t,0),this.currentTextLength)},moveCaretToStart:function(){this.previousTextIndex=this.currentTextIndex,this.currentTextIndex=0},moveCaretToEnd:function(){this.previousTextIndex=this.currentTextIndex,this.currentTextIndex=this.currentTextLength},typeStep:function(){if(!this.isDoneTyping){this.shiftCaret(1);var e=this.previousTextIndex,t=this.currentTextArray[e];this.$emit("typed-char",t,e)}this.isDoneTyping&&(this.cancelCurrentAction(),this.$nextTick(this.onTyped))},eraseStep:function(){this.isDoneErasing||(this.isEraseAllStyle?this.moveCaretToStart():this.shiftCaret(-1)),this.isDoneErasing&&(this.cancelCurrentAction(),this.$nextTick(this.onErased))},startTyping:function(){var e=this;this.actionTimeout||this.actionInterval||(this.moveCaretToStart(),this.state=y.IDLE,this.nextState=y.TYPING,this.actionTimeout=setTimeout(function(){e.state=y.TYPING,e.typeStep(),e.isDoneTyping||(e.actionInterval=setInterval(e.typeStep,e.typeDelay))},this.preTypeDelay))},startErasing:function(){var e=this;this.actionTimeout||this.actionInterval||(this.moveCaretToEnd(),this.state=y.IDLE,this.nextState=y.ERASING,this.actionTimeout=setTimeout(function(){e.state=y.ERASING,e.eraseStep(),e.isDoneErasing||(e.actionInterval=setInterval(e.eraseStep,e.eraseDelay))},this.preEraseDelay))},onTyped:function(){this.$emit("typed",this.currentText),this.onLastWord?this.eraseOnComplete||this.shouldRepeat?this.startErasing():this.onComplete():this.startErasing()},onErased:function(){this.$emit("erased",this.currentText),this.onLastWord?this.shouldRepeat?(this.repeatCounter++,this.resetSpool(),this.startTyping()):this.onComplete():(this.spoolIndex++,this.startTyping())},onComplete:function(){this.state=y.COMPLETE,this.nextState=null,this.$emit("completed")}},watch:{text:function(e,t){e===t||(0,p.default)(e,t)||this.reset()},repeat:function(){this.reset()},shuffle:function(){this.reset()}}}},slMF:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=function(e,t){if(!Array.isArray(e)||!Array.isArray(t))return!1;if(e.length!==t.length)return!1;for(var n=0;nn.parts.length&&(r.parts.length=n.parts.length)}else{for(var u=[],i=0;i0?r:n)(t)}},function(t,e,n){var r=n(36),i=n(9);t.exports=function(t){return r(i(t))}},function(t,e,n){n(63);var r=n(7)(n(19),n(60),"data-v-c41bac74",null);t.exports=r.exports},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.VueTyper=void 0;var r=n(12),i=function(t){return t&&t.__esModule?t:{default:t}}(r);e.VueTyper=i.default;e.default={install:function(t){t.component("vue-typer",i.default)}}},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{default:t}}function i(t){return"number"==typeof t&&!(0,c.default)(t)&&(0,a.default)(t)}function o(t,e){return i(t)&&i(e)&&t<=e}Object.defineProperty(e,"__esModule",{value:!0});var u=n(20),a=r(u),s=n(21),c=r(s);e.default=function(t,e){return o(t,e)?(t=Math.ceil(t),e=Math.floor(e),Math.floor(Math.random()*(e-t+1))+t):-1}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.default=function(t,e){if(!Array.isArray(t)||!Array.isArray(e))return!1;if(t.length!==e.length)return!1;for(var n=0;n0;e--){var n=(0,o.default)(0,e);r(t,e,n)}}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});e.default={props:{animation:{type:String,default:"blink",validator:function(t){return/^solid$|^blink$|^smooth$|^phase$|^expand$/.test(t)}}},computed:{animationClass:function(){return"vue-typer-caret-"+this.animation}}}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.default={props:{val:{type:String,default:""}},computed:{classes:function(){return{newline:0===this.val.indexOf("\n")}}}}},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{default:t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(22),o=r(i),u=n(56),a=r(u),s=n(57),c=r(s),f=n(15),l=r(f),p=n(16),d=r(p),h=n(55),y=r(h),v={IDLE:"idle",TYPING:"typing",ERASING:"erasing",COMPLETE:"complete"},x={BACKSPACE:"backspace",SELECT_BACK:"select-back",SELECT_ALL:"select-all",CLEAR:"clear"};e.default={name:"VueTyper",components:{Caret:a.default,Char:c.default},props:{text:{type:[String,Array],required:!0,validator:function(t){return"string"==typeof t?t.length>0:t.every(function(t){return"string"==typeof t&&t.length>0})}},repeat:{type:Number,default:1/0,validator:function(t){return t>=0}},shuffle:{type:Boolean,default:!1},initialAction:{type:String,default:v.TYPING,validator:function(t){return!!t.match("^"+v.TYPING+"|"+v.ERASING+"$")}},preTypeDelay:{type:Number,default:70,validator:function(t){return t>=0}},typeDelay:{type:Number,default:70,validator:function(t){return t>=0}},preEraseDelay:{type:Number,default:2e3,validator:function(t){return t>=0}},eraseDelay:{type:Number,default:250,validator:function(t){return t>=0}},eraseStyle:{type:String,default:x.SELECT_ALL,validator:function(t){return(0,o.default)(x).some(function(e){return x[e]===t})}},eraseOnComplete:{type:Boolean,default:!1},caretAnimation:String},data:function(){return{state:v.IDLE,nextState:null,spool:[],spoolIndex:-1,previousTextIndex:-1,currentTextIndex:-1,repeatCounter:0,actionTimeout:0,actionInterval:0}},computed:{caretClasses:function(){var t=this.state===v.IDLE;return{idle:t,"pre-type":t&&this.nextState===v.TYPING,"pre-erase":t&&this.nextState===v.ERASING,typing:this.state===v.TYPING,selecting:this.state===v.ERASING&&this.isSelectionBasedEraseStyle,erasing:this.state===v.ERASING&&!this.isSelectionBasedEraseStyle,complete:this.state===v.COMPLETE}},rightCharClasses:function(){return{selected:this.state===v.ERASING&&this.isSelectionBasedEraseStyle,erased:this.state!==v.ERASING||this.state===v.ERASING&&!this.isSelectionBasedEraseStyle}},isSelectionBasedEraseStyle:function(){return!!this.eraseStyle.match("^"+x.SELECT_BACK+"|"+x.SELECT_ALL+"$")},isEraseAllStyle:function(){return!!this.eraseStyle.match("^"+x.CLEAR+"|"+x.SELECT_ALL+"$")},isDoneTyping:function(){return this.currentTextIndex>=this.currentTextLength},isDoneErasing:function(){return this.isSelectionBasedEraseStyle?this.currentTextIndex<=0&&this.previousTextIndex<=0:this.currentTextIndex<=0},onLastWord:function(){return this.spoolIndex===this.spool.length-1},shouldRepeat:function(){return this.repeatCounter=0&&this.spoolIndex0}),this.spool=t}this.repeatCounter=0,this.resetSpool(),this.initialAction===v.TYPING?this.startTyping():this.initialAction===v.ERASING&&(this.moveCaretToEnd(),this.onTyped())},reset:function(){this.cancelCurrentAction(),this.init()},resetSpool:function(){this.spoolIndex=0,this.shuffle&&this.spool.length>1&&(0,d.default)(this.spool)},cancelCurrentAction:function(){this.actionInterval&&(clearInterval(this.actionInterval),this.actionInterval=0),this.actionTimeout&&(clearTimeout(this.actionTimeout),this.actionTimeout=0)},shiftCaret:function(t){this.previousTextIndex=this.currentTextIndex;var e=this.currentTextIndex+t;this.currentTextIndex=Math.min(Math.max(e,0),this.currentTextLength)},moveCaretToStart:function(){this.previousTextIndex=this.currentTextIndex,this.currentTextIndex=0},moveCaretToEnd:function(){this.previousTextIndex=this.currentTextIndex,this.currentTextIndex=this.currentTextLength},typeStep:function(){if(!this.isDoneTyping){this.shiftCaret(1);var t=this.previousTextIndex,e=this.currentTextArray[t];this.$emit("typed-char",e,t)}this.isDoneTyping&&(this.cancelCurrentAction(),this.$nextTick(this.onTyped))},eraseStep:function(){this.isDoneErasing||(this.isEraseAllStyle?this.moveCaretToStart():this.shiftCaret(-1)),this.isDoneErasing&&(this.cancelCurrentAction(),this.$nextTick(this.onErased))},startTyping:function(){var t=this;this.actionTimeout||this.actionInterval||(this.moveCaretToStart(),this.state=v.IDLE,this.nextState=v.TYPING,this.actionTimeout=setTimeout(function(){t.state=v.TYPING,t.typeStep(),t.isDoneTyping||(t.actionInterval=setInterval(t.typeStep,t.typeDelay))},this.preTypeDelay))},startErasing:function(){var t=this;this.actionTimeout||this.actionInterval||(this.moveCaretToEnd(),this.state=v.IDLE,this.nextState=v.ERASING,this.actionTimeout=setTimeout(function(){t.state=v.ERASING,t.eraseStep(),t.isDoneErasing||(t.actionInterval=setInterval(t.eraseStep,t.eraseDelay))},this.preEraseDelay))},onTyped:function(){this.$emit("typed",this.currentText),this.onLastWord?this.eraseOnComplete||this.shouldRepeat?this.startErasing():this.onComplete():this.startErasing()},onErased:function(){this.$emit("erased",this.currentText),this.onLastWord?this.shouldRepeat?(this.repeatCounter++,this.resetSpool(),this.startTyping()):this.onComplete():(this.spoolIndex++,this.startTyping())},onComplete:function(){this.state=v.COMPLETE,this.nextState=null,this.$emit("completed")}},watch:{text:function(t,e){t===e||(0,l.default)(t,e)||this.reset()},repeat:function(){this.reset()},shuffle:function(){this.reset()}}}},function(t,e,n){t.exports={default:n(23),__esModule:!0}},function(t,e,n){t.exports={default:n(24),__esModule:!0}},function(t,e,n){t.exports={default:n(25),__esModule:!0}},function(t,e,n){n(49),t.exports=n(0).Number.isFinite},function(t,e,n){n(50),t.exports=n(0).Number.isNaN},function(t,e,n){n(51),t.exports=n(0).Object.keys},function(t,e){t.exports=function(t){if("function"!=typeof t)throw TypeError(t+" is not a function!");return t}},function(t,e,n){var r=n(5);t.exports=function(t){if(!r(t))throw TypeError(t+" is not an object!");return t}},function(t,e,n){var r=n(11),i=n(45),o=n(44);t.exports=function(t){return function(e,n,u){var a,s=r(e),c=i(s.length),f=o(u,c);if(t&&n!=n){for(;c>f;)if((a=s[f++])!=a)return!0}else for(;c>f;f++)if((t||f in s)&&s[f]===n)return t||f||0;return!t&&-1}}},function(t,e){var n={}.toString;t.exports=function(t){return n.call(t).slice(8,-1)}},function(t,e,n){var r=n(26);t.exports=function(t,e,n){if(r(t),void 0===e)return t;switch(n){case 1:return function(n){return t.call(e,n)};case 2:return function(n,r){return t.call(e,n,r)};case 3:return function(n,r,i){return t.call(e,n,r,i)}}return function(){return t.apply(e,arguments)}}},function(t,e,n){var r=n(5),i=n(1).document,o=r(i)&&r(i.createElement);t.exports=function(t){return o?i.createElement(t):{}}},function(t,e){t.exports="constructor,hasOwnProperty,isPrototypeOf,propertyIsEnumerable,toLocaleString,toString,valueOf".split(",")},function(t,e){var n={}.hasOwnProperty;t.exports=function(t,e){return n.call(t,e)}},function(t,e,n){var r=n(37),i=n(41);t.exports=n(2)?function(t,e,n){return r.f(t,e,i(1,n))}:function(t,e,n){return t[e]=n,t}},function(t,e,n){t.exports=!n(2)&&!n(4)(function(){return 7!=Object.defineProperty(n(31)("div"),"a",{get:function(){return 7}}).a})},function(t,e,n){var r=n(29);t.exports=Object("z").propertyIsEnumerable(0)?Object:function(t){return"String"==r(t)?t.split(""):Object(t)}},function(t,e,n){var r=n(27),i=n(35),o=n(47),u=Object.defineProperty;e.f=n(2)?Object.defineProperty:function(t,e,n){if(r(t),e=o(e,!0),r(n),i)try{return u(t,e,n)}catch(t){}if("get"in n||"set"in n)throw TypeError("Accessors not supported!");return"value"in n&&(t[e]=n.value),t}},function(t,e,n){var r=n(33),i=n(11),o=n(28)(!1),u=n(42)("IE_PROTO");t.exports=function(t,e){var n,a=i(t),s=0,c=[];for(n in a)n!=u&&r(a,n)&&c.push(n);for(;e.length>s;)r(a,n=e[s++])&&(~o(c,n)||c.push(n));return c}},function(t,e,n){var r=n(38),i=n(32);t.exports=Object.keys||function(t){return r(t,i)}},function(t,e,n){var r=n(3),i=n(0),o=n(4);t.exports=function(t,e){var n=(i.Object||{})[t]||Object[t],u={};u[t]=e(n),r(r.S+r.F*o(function(){n(1)}),"Object",u)}},function(t,e){t.exports=function(t,e){return{enumerable:!(1&t),configurable:!(2&t),writable:!(4&t),value:e}}},function(t,e,n){var r=n(43)("keys"),i=n(48);t.exports=function(t){return r[t]||(r[t]=i(t))}},function(t,e,n){var r=n(1),i=r["__core-js_shared__"]||(r["__core-js_shared__"]={});t.exports=function(t){return i[t]||(i[t]={})}},function(t,e,n){var r=n(10),i=Math.max,o=Math.min;t.exports=function(t,e){return t=r(t),t<0?i(t+e,0):o(t,e)}},function(t,e,n){var r=n(10),i=Math.min;t.exports=function(t){return t>0?i(r(t),9007199254740991):0}},function(t,e,n){var r=n(9);t.exports=function(t){return Object(r(t))}},function(t,e,n){var r=n(5);t.exports=function(t,e){if(!r(t))return t;var n,i;if(e&&"function"==typeof(n=t.toString)&&!r(i=n.call(t)))return i;if("function"==typeof(n=t.valueOf)&&!r(i=n.call(t)))return i;if(!e&&"function"==typeof(n=t.toString)&&!r(i=n.call(t)))return i;throw TypeError("Can't convert object to primitive value")}},function(t,e){var n=0,r=Math.random();t.exports=function(t){return"Symbol(".concat(void 0===t?"":t,")_",(++n+r).toString(36))}},function(t,e,n){var r=n(3),i=n(1).isFinite;r(r.S,"Number",{isFinite:function(t){return"number"==typeof t&&i(t)}})},function(t,e,n){var r=n(3);r(r.S,"Number",{isNaN:function(t){return t!=t}})},function(t,e,n){var r=n(46),i=n(39);n(40)("keys",function(){return function(t){return i(r(t))}})},function(t,e,n){e=t.exports=n(6)(),e.push([t.i,".char[data-v-302772ec]{display:inline-block;white-space:pre-wrap}.newline[data-v-302772ec]{display:inline}.typed[data-v-302772ec]{color:#000;background-color:transparent}.selected[data-v-302772ec]{color:#000;background-color:#accef7}.erased[data-v-302772ec]{display:none}",""])},function(t,e,n){e=t.exports=n(6)(),e.push([t.i,'@keyframes vue-typer-caret-blink{50%{opacity:0}to{opacity:1}}@keyframes vue-typer-caret-smooth{0%,20%{opacity:1}60%,to{opacity:0}}@keyframes vue-typer-caret-phase{0%,20%{opacity:1}90%,to{opacity:0}}@keyframes vue-typer-caret-expand{0%,20%{transform:scaleY(1)}80%,to{transform:scaleY(0)}}.vue-typer-caret-blink[data-v-a16e0f02]{animation:vue-typer-caret-blink 1s step-start 0s infinite}.vue-typer-caret-smooth[data-v-a16e0f02]{animation:vue-typer-caret-smooth .5s ease-in-out 0s infinite alternate}.vue-typer-caret-phase[data-v-a16e0f02]{animation:vue-typer-caret-phase .5s ease-in-out 0s infinite alternate}.vue-typer-caret-expand[data-v-a16e0f02]{animation:vue-typer-caret-expand .5s ease-in-out 0s infinite alternate}span.caret[data-v-a16e0f02]:empty:before{content:"\\200B"}span[data-v-a16e0f02]{display:inline-block;width:1px}.idle[data-v-a16e0f02],.typing[data-v-a16e0f02]{background-color:#000}.selecting[data-v-a16e0f02]{display:none;background-color:#000}.erasing[data-v-a16e0f02]{background-color:#000}.complete[data-v-a16e0f02]{display:none;background-color:#000}',""])},function(t,e,n){e=t.exports=n(6)(),e.push([t.i,"span.vue-typer[data-v-c41bac74]{cursor:default;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}span.vue-typer span.left[data-v-c41bac74],span.vue-typer span.right[data-v-c41bac74]{display:inline}",""])},function(t,e,n){(function(t,n){function r(t){return t.split("")}function i(t){return B.test(t)}function o(t){return i(t)?u(t):r(t)}function u(t){return t.match(G)||[]}function a(t){return x(t)&&H.call(t)==j}function s(t,e,n){var r=-1,i=t.length;e<0&&(e=-e>i?0:i+e),n=n>i?i:n,n<0&&(n+=i),i=e>n?0:n-e>>>0,e>>>=0;for(var o=Array(i);++r=r?t:s(t,e,n)}function l(t,e){return!!(e=null==e?E:e)&&("number"==typeof t||O.test(t))&&t>-1&&t%1==0&&t-1&&t%1==0&&t<=E}function x(t){var e=typeof t;return!!t&&("object"==e||"function"==e)}function m(t){return!!t&&"object"==typeof t}function g(t){return"symbol"==typeof t||m(t)&&H.call(t)==A}function b(t){return null==t?"":c(t)}function T(t,e,n){return n&&"number"!=typeof n&&p(t,e,n)&&(e=n=void 0),(n=void 0===n?C:n>>>0)?(t=b(t),t&&("string"==typeof e||null!=e&&!tt(e))&&!(e=c(e))&&i(t)?f(o(t),0,n):t.split(e,n)):[]}var S=1/0,E=9007199254740991,C=4294967295,_="[object Function]",I="[object GeneratorFunction]",j="[object RegExp]",A="[object Symbol]",O=/^(?:0|[1-9]\d*)$/,N="[\\u0300-\\u036f\\ufe20-\\ufe23\\u20d0-\\u20f0]",L="\\ud83c[\\udffb-\\udfff]",M="(?:\\ud83c[\\udde6-\\uddff]){2}",w="[\\ud800-\\udbff][\\udc00-\\udfff]",P="(?:[\\u0300-\\u036f\\ufe20-\\ufe23\\u20d0-\\u20f0]|\\ud83c[\\udffb-\\udfff])?",R="(?:\\u200d(?:"+["[^\\ud800-\\udfff]",M,w].join("|")+")[\\ufe0e\\ufe0f]?"+P+")*",k="[\\ufe0e\\ufe0f]?"+P+R,D="(?:"+["[^\\ud800-\\udfff]"+N+"?",N,M,w,"[\\ud800-\\udfff]"].join("|")+")",G=RegExp(L+"(?="+L+")|"+D+k,"g"),B=RegExp("[\\u200d\\ud800-\\udfff\\u0300-\\u036f\\ufe20-\\ufe23\\u20d0-\\u20f0\\ufe0e\\ufe0f]"),$="object"==typeof t&&t&&t.Object===Object&&t,F="object"==typeof self&&self&&self.Object===Object&&self,Y=$||F||Function("return this")(),U="object"==typeof e&&e&&!e.nodeType&&e,W=U&&"object"==typeof n&&n&&!n.nodeType&&n,V=W&&W.exports===U,K=V&&$.process,q=function(){try{return K&&K.binding("util")}catch(t){}}(),z=q&&q.isRegExp,J=Object.prototype,H=J.toString,Q=Y.Symbol,X=Q?Q.prototype:void 0,Z=X?X.toString:void 0,tt=z?function(t){return function(e){return t(e)}}(z):a;n.exports=T}).call(e,n(65),n(66)(t))},function(t,e,n){n(62);var r=n(7)(n(17),n(59),"data-v-a16e0f02",null);t.exports=r.exports},function(t,e,n){n(61);var r=n(7)(n(18),n(58),"data-v-302772ec",null);t.exports=r.exports},function(t,e){t.exports={render:function(){var t=this,e=t.$createElement;return(t._self._c||e)("span",{staticClass:"char",class:t.classes},[t._v(t._s(t.val))])},staticRenderFns:[]}},function(t,e){t.exports={render:function(){var t=this,e=t.$createElement;return(t._self._c||e)("span",{staticClass:"caret custom",class:t.animationClass})},staticRenderFns:[]}},function(t,e){t.exports={render:function(){var t=this,e=t.$createElement,n=t._self._c||e;return n("span",{staticClass:"vue-typer"},[n("span",{staticClass:"left"},t._l(t.numLeftChars,function(e){return n("char",{staticClass:"custom typed",attrs:{val:t.currentTextArray[e-1]}})})),n("caret",{class:t.caretClasses,attrs:{animation:t.caretAnimation}}),n("span",{staticClass:"right"},t._l(t.numRightChars,function(e){return n("char",{staticClass:"custom",class:t.rightCharClasses,attrs:{val:t.currentTextArray[t.numLeftChars+e-1]}})}))],1)},staticRenderFns:[]}},function(t,e,n){var r=n(52);"string"==typeof r&&(r=[[t.i,r,""]]),r.locals&&(t.exports=r.locals);n(8)("3bfdc45b",r,!0)},function(t,e,n){var r=n(53);"string"==typeof r&&(r=[[t.i,r,""]]),r.locals&&(t.exports=r.locals);n(8)("0dba035c",r,!0)},function(t,e,n){var r=n(54);"string"==typeof r&&(r=[[t.i,r,""]]),r.locals&&(t.exports=r.locals);n(8)("0f4cea8e",r,!0)},function(t,e){t.exports=function(t,e){for(var n=[],r={},i=0;i
2 |
3 |
4 |
5 |
6 |
7 | VueTyper Demo
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vue-typer",
3 | "version": "1.2.0",
4 | "description": "Vue component that simulates a user typing, selecting, and erasing text.",
5 | "main": "dist/vue-typer.min.js",
6 | "files": [
7 | "dist/vue-typer.js",
8 | "dist/vue-typer.min.js"
9 | ],
10 | "scripts": {
11 | "dev": "NODE_ENV='development' webpack-dev-server --config build/webpack.config.dev.js --hot --inline --open --compress",
12 | "dev:lan": "npm run dev -- --public $(ipconfig getifaddr en0):8080 --host 0.0.0.0",
13 | "demo": "webpack --config build/webpack.config.demo.js",
14 | "prod": "NODE_ENV='production' webpack --config build/webpack.config.prod.js",
15 | "build": "npm run lint && npm run test && npm run clean && npm run demo && npm run prod",
16 | "unit": "npm run unit:watch -- --single-run",
17 | "unit:watch": "NODE_ENV='test' karma start test/unit/karma.conf.js",
18 | "unit:debug": "npm run unit:watch -- --browsers Chrome",
19 | "test": "npm run unit",
20 | "lint": "NODE_ENV='production' eslint --format node_modules/eslint-friendly-formatter --ext .js,.vue src",
21 | "clean": "rimraf ./dist ./index.html"
22 | },
23 | "repository": {
24 | "type": "git",
25 | "url": "git+https://github.com/cngu/vue-typer.git"
26 | },
27 | "keywords": [
28 | "vue-typer",
29 | "vue",
30 | "vuejs",
31 | "vue.js",
32 | "component",
33 | "typer",
34 | "typewriter",
35 | "type",
36 | "auto"
37 | ],
38 | "author": "Chris Nguyen",
39 | "license": "MIT",
40 | "bugs": {
41 | "url": "https://github.com/cngu/vue-typer/issues"
42 | },
43 | "homepage": "https://github.com/cngu/vue-typer#readme",
44 | "dependencies": {
45 | "lodash.split": "^4.4.2"
46 | },
47 | "devDependencies": {
48 | "autoprefixer": "^6.5.4",
49 | "babel-core": "^6.21.0",
50 | "babel-eslint": "^7.1.1",
51 | "babel-loader": "^6.2.10",
52 | "babel-plugin-istanbul": "^4.1.1",
53 | "babel-plugin-transform-runtime": "^6.15.0",
54 | "babel-preset-es2015": "^6.18.0",
55 | "chai": "^3.5.0",
56 | "css-loader": "^0.26.1",
57 | "eslint": "^3.12.2",
58 | "eslint-config-standard": "^6.2.1",
59 | "eslint-friendly-formatter": "^2.0.6",
60 | "eslint-loader": "^1.6.1",
61 | "eslint-plugin-html": "^1.7.0",
62 | "eslint-plugin-promise": "^3.4.0",
63 | "eslint-plugin-standard": "^2.0.1",
64 | "extract-text-webpack-plugin": "^2.1.0",
65 | "file-loader": "^0.9.0",
66 | "html-webpack-plugin": "^2.24.1",
67 | "karma": "^1.3.0",
68 | "karma-chrome-launcher": "^2.0.0",
69 | "karma-coverage": "^1.1.1",
70 | "karma-mocha": "^1.3.0",
71 | "karma-phantomjs-launcher": "^1.0.2",
72 | "karma-sinon-chai": "^1.2.4",
73 | "karma-sourcemap-loader": "^0.3.7",
74 | "karma-spec-reporter": "0.0.26",
75 | "karma-webpack": "^2.0.3",
76 | "mocha": "^3.2.0",
77 | "node-sass": "^4.1.1",
78 | "phantomjs-prebuilt": "^2.1.14",
79 | "pug": "^2.0.0-beta6",
80 | "rimraf": "^2.5.4",
81 | "sass-loader": "^4.1.1",
82 | "sinon": "^2.1.0",
83 | "sinon-chai": "^2.8.0",
84 | "style-loader": "^0.13.1",
85 | "url-loader": "^0.5.7",
86 | "vue": "^2.1.8",
87 | "vue-loader": "^10.0.2",
88 | "vue-template-compiler": "^2.1.6",
89 | "webpack": "^2.4.1",
90 | "webpack-dev-server": "^2.4.4",
91 | "webpack-merge": "^1.1.2"
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/src/demo/Demo.vue:
--------------------------------------------------------------------------------
1 |
2 | app-layout.demo
3 | hero-header(slot='header')
4 |
5 | template(slot='main-playground-output')
6 | h3.demo-typer-container.row.flex-items-xs-center.flex-items-xs-middle
7 | vue-typer.demo-typer(
8 | :text='text',
9 | :repeat='repeat',
10 | :shuffle='shuffle',
11 | :initial-action='initialAction',
12 | :pre-type-delay='preTypeDelay',
13 | :type-delay='typeDelay',
14 | :pre-erase-delay='preEraseDelay',
15 | :erase-delay='eraseDelay',
16 | :erase-style='eraseStyle',
17 | :erase-on-complete='eraseOnComplete',
18 | :caret-animation='caretAnimation')
19 |
20 | template(slot='main-playground-text')
21 | .form-group
22 | label(for='text') List of words to type:
23 | textarea(id='text', v-model='textModel', placeholder='text', :rows='3')
24 |
25 | template(slot='main-playground-config')
26 | .row
27 | #general-config.col-xs-12.col-lg-6
28 | form-input(v-model='repeatModel', label='repeat')
29 | form-check(v-model='shuffle', label='shuffle')
30 | form-check.shrink-text(v-model='eraseOnComplete', label='eraseOnComplete')
31 | form-radio(v-model='initialAction', :model='initialAction', label='initialAction',
32 | :options='["typing", "erasing"]')
33 |
34 | #delay-config.col-xs-12.col-lg-6
35 | form-input(v-model.number='preTypeDelay', label='preTypeDelay', type='number')
36 | form-input(v-model.number='typeDelay', label='typeDelay', type='number')
37 | form-input(v-model.number='preEraseDelay', label='preEraseDelay', type='number')
38 | form-input(v-model.number='eraseDelay', label='eraseDelay', type='number')
39 |
40 | #erase-style-config.col-xs-12.col-lg-6
41 | form-radio(v-model='eraseStyle', :model='eraseStyle', label='eraseStyle',
42 | :options='["backspace", "select-back", "select-all", "clear"]')
43 |
44 | #caret-config.col-xs-12.col-lg-6
45 | form-radio(v-model='caretAnimation', :model='caretAnimation', label='caretAnimation',
46 | :options='["solid", "blink", "smooth", "phase", "expand"]')
47 |
48 | template(slot='main-playground-code')
49 | code-block(:code='playgroundDemoCode', language='html')
50 |
51 | template(slot='style-showcase-panel-1')
52 | h4.text-xs-center
53 | vue-typer.state-typer(
54 | text='Katniss Everdeen',
55 | :pre-type-delay='1000',
56 | :type-delay='160',
57 | :pre-erase-delay='2000',
58 | :erase-delay='80',
59 | erase-style='select-back',
60 | caret-animation='solid'
61 | )
62 | code-block(:code='stateDemoStyleCode', language='css')
63 |
64 | template(slot='style-showcase-panel-2')
65 | h4.text-xs-center
66 | vue-typer.code-typer(
67 | text='Katniss Everdeen',
68 | :pre-type-delay='1000',
69 | :type-delay='160',
70 | :pre-erase-delay='2000',
71 | :erase-delay='1280',
72 | erase-style='select-all',
73 | caret-animation='blink'
74 | )
75 | code-block(:code='codeDemoStyleCode', language='css')
76 |
77 | template(slot='style-showcase-panel-3')
78 | h4.card-title.text-xs-center
79 | vue-typer.ghost-typer(
80 | text='Katniss Everdeen',
81 | :pre-type-delay='1000',
82 | :type-delay='160',
83 | :pre-erase-delay='2000',
84 | :erase-delay='80',
85 | erase-style='select-back'
86 | )
87 | code-block(:code='ghostDemoStyleCode', language='css')
88 |
89 | copyright-footer(slot='footer')
90 |
91 |
92 |
217 |
218 |
241 |
242 |
309 |
--------------------------------------------------------------------------------
/src/demo/assets/demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cngu/vue-typer/db830cb8cd43327c86fdb3779b41b5cdc0e44417/src/demo/assets/demo.gif
--------------------------------------------------------------------------------
/src/demo/assets/images/documentation-light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cngu/vue-typer/db830cb8cd43327c86fdb3779b41b5cdc0e44417/src/demo/assets/images/documentation-light.png
--------------------------------------------------------------------------------
/src/demo/assets/images/download-light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cngu/vue-typer/db830cb8cd43327c86fdb3779b41b5cdc0e44417/src/demo/assets/images/download-light.png
--------------------------------------------------------------------------------
/src/demo/assets/images/github-mark-light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cngu/vue-typer/db830cb8cd43327c86fdb3779b41b5cdc0e44417/src/demo/assets/images/github-mark-light.png
--------------------------------------------------------------------------------
/src/demo/components/AppLayout.vue:
--------------------------------------------------------------------------------
1 |
2 | .app-layout
3 | slot(name='header')
4 |
5 | main.container
6 | section#playground.row
7 | h4.col-xs-12.text-xs-center VueTyper Playground
8 |
9 | #output-panel.card.col-xs-12.col-lg-6
10 | slot(name='main-playground-output')
11 |
12 | #text-panel.card.col-xs-12.col-lg-6
13 | slot(name='main-playground-text')
14 |
15 | #config-panel.card.col-xs-12.col-lg-6
16 | slot(name='main-playground-config')
17 |
18 | #code-panel.card.col-xs-12.col-lg-6
19 | slot(name='main-playground-code')
20 |
21 | section#style-showcase.row
22 | h4.col-xs-12.text-xs-center VueTyper is also stylable with CSS!
23 | .col-xs
24 | .row
25 | .card.col-xs-12.col-lg-4
26 | slot(name='style-showcase-panel-1')
27 | .card.col-xs-12.col-lg-4
28 | slot(name='style-showcase-panel-2')
29 | .card.col-xs-12.col-lg-4
30 | slot(name='style-showcase-panel-3')
31 |
32 | slot(name='footer')
33 |
34 |
35 |
--------------------------------------------------------------------------------
/src/demo/components/BadgeBar.vue:
--------------------------------------------------------------------------------
1 |
2 | span.badge-bar
3 | a(href='https://www.npmjs.com/package/vue-typer', target='_blank')
4 | img(src='https://img.shields.io/npm/dt/vue-typer.svg', alt='Downloads')
5 | a(href='https://www.npmjs.com/package/vue-typer', target='_blank')
6 | img(src='https://img.shields.io/npm/v/vue-typer.svg', alt='Version')
7 | a(href='https://www.npmjs.com/package/vue-typer', target='_blank')
8 | img(src='https://img.shields.io/npm/l/vue-typer.svg', alt='License')
9 |
10 |
11 |
18 |
--------------------------------------------------------------------------------
/src/demo/components/CircleLink.vue:
--------------------------------------------------------------------------------
1 |
2 | a(:href='href', target='_blank')
3 | slot
4 |
5 |
6 |
13 |
14 |
49 |
--------------------------------------------------------------------------------
/src/demo/components/CodeBlock.vue:
--------------------------------------------------------------------------------
1 |
2 | pre.code-block
3 | code(ref='codeBlock', :class='languageClass')
4 |
5 |
6 |
44 |
45 |
--------------------------------------------------------------------------------
/src/demo/components/CopyrightFooter.vue:
--------------------------------------------------------------------------------
1 |
2 | footer
3 | small
4 | | Released under the #[a(href='https://opensource.org/licenses/MIT', target='_blank') MIT License]
5 | br
6 | | Copyright © 2016-#{new Date().getFullYear()} Chris Nguyen
7 |
8 |
9 |
25 |
--------------------------------------------------------------------------------
/src/demo/components/FormCheck.vue:
--------------------------------------------------------------------------------
1 |
2 | .form-group.row.flex-items-xs-center
3 | label.col-xs-4.col-md-3.col-lg-6.flex-xs-middle.shift-up(:for='label') {{label}}
4 | .col-xs-4.col-md-3.col-lg-6
5 | .form-check
6 | label.form-check-label
7 | input.form-check-input(
8 | :id='label',
9 | type='checkbox',
10 | :value='value',
11 | @change='$emit("input", $event.target.checked)'
12 | )
13 |
14 |
15 |
23 |
24 |
29 |
--------------------------------------------------------------------------------
/src/demo/components/FormInput.vue:
--------------------------------------------------------------------------------
1 |
2 | .form-group.row.flex-items-xs-center
3 | label.col-form-label.col-xs-4.col-md-3.col-lg-6(:for='label') {{label}}
4 | .col-xs-4.col-md-3.col-lg-6
5 | input.form-control(
6 | :id='label',
7 | :type='type',
8 | :value='value',
9 | @input='$emit("input", $event.target.value)'
10 | )
11 |
12 |
13 |
22 |
23 |
28 |
--------------------------------------------------------------------------------
/src/demo/components/FormRadio.vue:
--------------------------------------------------------------------------------
1 |
2 | .form-group.row.flex-items-xs-center
3 | label.col-xs-4.col-md-3.col-lg-6 {{label}}
4 | .col-xs-4.col-md-3.col-lg-6
5 | .form-check(v-for='option of options')
6 | label.form-check-label
7 | input.form-check-input(
8 | type='radio',
9 | :value='option',
10 | @change='$emit("input", $event.target.value)',
11 | v-model='localModel'
12 | )
13 | | {{" " + option}}
14 |
15 |
16 |
31 |
32 |
37 |
--------------------------------------------------------------------------------
/src/demo/components/HeroHeader.vue:
--------------------------------------------------------------------------------
1 |
2 | header.jumbotron.jumbotron-fluid
3 | h1.display-4
4 | vue-typer.title-typer(text='VueTyper', :repeat='0', :pre-type-delay='1000', :type-delay='400', caret-animation='smooth')
5 | link-bar.link-bar
6 | badge-bar
7 |
8 |
9 |
20 |
21 |
43 |
44 |
--------------------------------------------------------------------------------
/src/demo/components/LinkBar.vue:
--------------------------------------------------------------------------------
1 |
2 | .link-bar
3 | circle-link(href='https://github.com/cngu/vue-typer')
4 | img(src='../assets/images/github-mark-light.png')
5 | circle-link(href='https://github.com/cngu/vue-typer/blob/master/README.md#getting-started')
6 | img(src='../assets/images/documentation-light.png')
7 | circle-link(href='https://api.github.com/repos/cngu/vue-typer/zipball')
8 | img(src='../assets/images/download-light.png')
9 |
10 |
11 |
20 |
21 |
--------------------------------------------------------------------------------
/src/demo/index.js:
--------------------------------------------------------------------------------
1 | import './styles/demo.scss'
2 |
3 | import Vue from 'vue'
4 | import Demo from './Demo'
5 |
6 | // eslint-disable-next-line no-new
7 | new Vue({
8 | el: '#demo',
9 | render: createElement => createElement(Demo)
10 | })
11 |
--------------------------------------------------------------------------------
/src/demo/lib/bootstrap/index.js:
--------------------------------------------------------------------------------
1 | import './bootstrap-flex.min.css'
2 |
--------------------------------------------------------------------------------
/src/demo/lib/prism/index.js:
--------------------------------------------------------------------------------
1 | import './prism.css'
2 | import Prism from './prism'
3 |
4 | export default Prism
5 |
--------------------------------------------------------------------------------
/src/demo/lib/prism/prism.css:
--------------------------------------------------------------------------------
1 | /* http://prismjs.com/download.html?themes=prism&languages=markup+css&plugins=previewer-base+previewer-color+normalize-whitespace */
2 | /**
3 | * prism.js default theme for JavaScript, CSS and HTML
4 | * Based on dabblet (http://dabblet.com)
5 | * @author Lea Verou
6 | */
7 |
8 | code[class*="language-"],
9 | pre[class*="language-"] {
10 | color: black;
11 | background: none;
12 | text-shadow: 0 1px white;
13 | font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
14 | text-align: left;
15 | white-space: pre;
16 | word-spacing: normal;
17 | word-break: normal;
18 | word-wrap: normal;
19 | line-height: 1.5;
20 |
21 | -moz-tab-size: 4;
22 | -o-tab-size: 4;
23 | tab-size: 4;
24 |
25 | -webkit-hyphens: none;
26 | -moz-hyphens: none;
27 | -ms-hyphens: none;
28 | hyphens: none;
29 | }
30 |
31 | pre[class*="language-"]::-moz-selection, pre[class*="language-"] ::-moz-selection,
32 | code[class*="language-"]::-moz-selection, code[class*="language-"] ::-moz-selection {
33 | text-shadow: none;
34 | background: #b3d4fc;
35 | }
36 |
37 | pre[class*="language-"]::selection, pre[class*="language-"] ::selection,
38 | code[class*="language-"]::selection, code[class*="language-"] ::selection {
39 | text-shadow: none;
40 | background: #b3d4fc;
41 | }
42 |
43 | @media print {
44 | code[class*="language-"],
45 | pre[class*="language-"] {
46 | text-shadow: none;
47 | }
48 | }
49 |
50 | /* Code blocks */
51 | pre[class*="language-"] {
52 | padding: 1em;
53 | margin: .5em 0;
54 | overflow: auto;
55 | }
56 |
57 | :not(pre) > code[class*="language-"],
58 | pre[class*="language-"] {
59 | background: #f5f2f0;
60 | }
61 |
62 | /* Inline code */
63 | :not(pre) > code[class*="language-"] {
64 | padding: .1em;
65 | border-radius: .3em;
66 | white-space: normal;
67 | }
68 |
69 | .token.comment,
70 | .token.prolog,
71 | .token.doctype,
72 | .token.cdata {
73 | color: slategray;
74 | }
75 |
76 | .token.punctuation {
77 | color: #999;
78 | }
79 |
80 | .namespace {
81 | opacity: .7;
82 | }
83 |
84 | .token.property,
85 | .token.tag,
86 | .token.boolean,
87 | .token.number,
88 | .token.constant,
89 | .token.symbol,
90 | .token.deleted {
91 | color: #905;
92 | }
93 |
94 | .token.selector,
95 | .token.attr-name,
96 | .token.string,
97 | .token.char,
98 | .token.builtin,
99 | .token.inserted {
100 | color: #690;
101 | }
102 |
103 | .token.operator,
104 | .token.entity,
105 | .token.url,
106 | .language-css .token.string,
107 | .style .token.string {
108 | color: #a67f59;
109 | background: hsla(0, 0%, 100%, .5);
110 | }
111 |
112 | .token.atrule,
113 | .token.attr-value,
114 | .token.keyword {
115 | color: #07a;
116 | }
117 |
118 | .token.function {
119 | color: #DD4A68;
120 | }
121 |
122 | .token.regex,
123 | .token.important,
124 | .token.variable {
125 | color: #e90;
126 | }
127 |
128 | .token.important,
129 | .token.bold {
130 | font-weight: bold;
131 | }
132 | .token.italic {
133 | font-style: italic;
134 | }
135 |
136 | .token.entity {
137 | cursor: help;
138 | }
139 |
140 | .prism-previewer,
141 | .prism-previewer:before,
142 | .prism-previewer:after {
143 | position: absolute;
144 | pointer-events: none;
145 | }
146 | .prism-previewer,
147 | .prism-previewer:after {
148 | left: 50%;
149 | }
150 | .prism-previewer {
151 | margin-top: -48px;
152 | width: 32px;
153 | height: 32px;
154 | margin-left: -16px;
155 |
156 | opacity: 0;
157 | -webkit-transition: opacity .25s;
158 | -o-transition: opacity .25s;
159 | transition: opacity .25s;
160 | }
161 | .prism-previewer.flipped {
162 | margin-top: 0;
163 | margin-bottom: -48px;
164 | }
165 | .prism-previewer:before,
166 | .prism-previewer:after {
167 | content: '';
168 | position: absolute;
169 | pointer-events: none;
170 | }
171 | .prism-previewer:before {
172 | top: -5px;
173 | right: -5px;
174 | left: -5px;
175 | bottom: -5px;
176 | border-radius: 10px;
177 | border: 5px solid #fff;
178 | box-shadow: 0 0 3px rgba(0, 0, 0, 0.5) inset, 0 0 10px rgba(0, 0, 0, 0.75);
179 | }
180 |
181 | .prism-previewer:after {
182 | top: 100%;
183 | width: 0;
184 | height: 0;
185 | margin: 5px 0 0 -7px;
186 | border: 7px solid transparent;
187 | border-color: rgba(255, 0, 0, 0);
188 | border-top-color: #fff;
189 | }
190 | .prism-previewer.flipped:after {
191 | top: auto;
192 | bottom: 100%;
193 | margin-top: 0;
194 | margin-bottom: 5px;
195 | border-top-color: rgba(255, 0, 0, 0);
196 | border-bottom-color: #fff;
197 | }
198 | .prism-previewer.active {
199 | opacity: 1;
200 | }
201 | .prism-previewer-color {
202 | background-image: linear-gradient(45deg, #bbb 25%, transparent 25%, transparent 75%, #bbb 75%, #bbb), linear-gradient(45deg, #bbb 25%, #eee 25%, #eee 75%, #bbb 75%, #bbb);
203 | background-size: 10px 10px;
204 | background-position: 0 0, 5px 5px;
205 | }
206 | .prism-previewer-color:before {
207 | background-color: inherit;
208 | background-clip: padding-box;
209 | }
210 |
211 |
--------------------------------------------------------------------------------
/src/demo/lib/prism/prism.js:
--------------------------------------------------------------------------------
1 | /* http://prismjs.com/download.html?themes=prism&languages=markup+css&plugins=previewer-base+previewer-color+normalize-whitespace */
2 | var _self="undefined"!=typeof window?window:"undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?self:{},Prism=function(){var e=/\blang(?:uage)?-(\w+)\b/i,t=0,n=_self.Prism={util:{encode:function(e){return e instanceof a?new a(e.type,n.util.encode(e.content),e.alias):"Array"===n.util.type(e)?e.map(n.util.encode):e.replace(/&/g,"&").replace(/e.length)break e;if(!(v instanceof a)){u.lastIndex=0;var b=u.exec(v),k=1;if(!b&&h&&m!=r.length-1){if(u.lastIndex=y,b=u.exec(e),!b)break;for(var w=b.index+(c?b[1].length:0),_=b.index+b[0].length,A=m,P=y,j=r.length;j>A&&_>P;++A)P+=r[A].length,w>=P&&(++m,y=P);if(r[m]instanceof a||r[A-1].greedy)continue;k=A-m,v=e.slice(y,P),b.index-=y}if(b){c&&(f=b[1].length);var w=b.index+f,b=b[0].slice(f),_=w+b.length,x=v.slice(0,w),O=v.slice(_),S=[m,k];x&&S.push(x);var N=new a(l,g?n.tokenize(b,g):b,d,b,h);S.push(N),O&&S.push(O),Array.prototype.splice.apply(r,S)}}}}}return r},hooks:{all:{},add:function(e,t){var a=n.hooks.all;a[e]=a[e]||[],a[e].push(t)},run:function(e,t){var a=n.hooks.all[e];if(a&&a.length)for(var r,i=0;r=a[i++];)r(t)}}},a=n.Token=function(e,t,n,a,r){this.type=e,this.content=t,this.alias=n,this.length=0|(a||"").length,this.greedy=!!r};if(a.stringify=function(e,t,r){if("string"==typeof e)return e;if("Array"===n.util.type(e))return e.map(function(n){return a.stringify(n,t,e)}).join("");var i={type:e.type,content:a.stringify(e.content,t,r),tag:"span",classes:["token",e.type],attributes:{},language:t,parent:r};if("comment"==i.type&&(i.attributes.spellcheck="true"),e.alias){var l="Array"===n.util.type(e.alias)?e.alias:[e.alias];Array.prototype.push.apply(i.classes,l)}n.hooks.run("wrap",i);var o=Object.keys(i.attributes).map(function(e){return e+'="'+(i.attributes[e]||"").replace(/"/g,""")+'"'}).join(" ");return"<"+i.tag+' class="'+i.classes.join(" ")+'"'+(o?" "+o:"")+">"+i.content+""+i.tag+">"},!_self.document)return _self.addEventListener?(_self.addEventListener("message",function(e){var t=JSON.parse(e.data),a=t.language,r=t.code,i=t.immediateClose;_self.postMessage(n.highlight(r,n.languages[a],a)),i&&_self.close()},!1),_self.Prism):_self.Prism;var r=document.currentScript||[].slice.call(document.getElementsByTagName("script")).pop();return r&&(n.filename=r.src,document.addEventListener&&!r.hasAttribute("data-manual")&&("loading"!==document.readyState?window.requestAnimationFrame?window.requestAnimationFrame(n.highlightAll):window.setTimeout(n.highlightAll,16):document.addEventListener("DOMContentLoaded",n.highlightAll))),_self.Prism}();"undefined"!=typeof module&&module.exports&&(module.exports=Prism),"undefined"!=typeof global&&(global.Prism=Prism);
3 | Prism.languages.markup={comment://,prolog:/<\?[\w\W]+?\?>/,doctype://i,cdata://i,tag:{pattern:/<\/?(?!\d)[^\s>\/=$<]+(?:\s+[^\s>\/=]+(?:=(?:("|')(?:\\\1|\\?(?!\1)[\w\W])*\1|[^\s'">=]+))?)*\s*\/?>/i,inside:{tag:{pattern:/^<\/?[^\s>\/]+/i,inside:{punctuation:/^<\/?/,namespace:/^[^\s>\/:]+:/}},"attr-value":{pattern:/=(?:('|")[\w\W]*?(\1)|[^\s>]+)/i,inside:{punctuation:/[=>"']/}},punctuation:/\/?>/,"attr-name":{pattern:/[^\s>\/]+/,inside:{namespace:/^[^\s>\/:]+:/}}}},entity:/?[\da-z]{1,8};/i},Prism.hooks.add("wrap",function(a){"entity"===a.type&&(a.attributes.title=a.content.replace(/&/,"&"))}),Prism.languages.xml=Prism.languages.markup,Prism.languages.html=Prism.languages.markup,Prism.languages.mathml=Prism.languages.markup,Prism.languages.svg=Prism.languages.markup;
4 | Prism.languages.css={comment:/\/\*[\w\W]*?\*\//,atrule:{pattern:/@[\w-]+?.*?(;|(?=\s*\{))/i,inside:{rule:/@[\w-]+/}},url:/url\((?:(["'])(\\(?:\r\n|[\w\W])|(?!\1)[^\\\r\n])*\1|.*?)\)/i,selector:/[^\{\}\s][^\{\};]*?(?=\s*\{)/,string:{pattern:/("|')(\\(?:\r\n|[\w\W])|(?!\1)[^\\\r\n])*\1/,greedy:!0},property:/(\b|\B)[\w-]+(?=\s*:)/i,important:/\B!important\b/i,"function":/[-a-z0-9]+(?=\()/i,punctuation:/[(){};:]/},Prism.languages.css.atrule.inside.rest=Prism.util.clone(Prism.languages.css),Prism.languages.markup&&(Prism.languages.insertBefore("markup","tag",{style:{pattern:/(
65 |
--------------------------------------------------------------------------------
/src/vue-typer/components/Char.vue:
--------------------------------------------------------------------------------
1 |
2 | span.char(:class="classes")
3 | | {{ val }}
4 |
5 |
6 |
23 |
24 |
53 |
--------------------------------------------------------------------------------
/src/vue-typer/components/VueTyper.vue:
--------------------------------------------------------------------------------
1 |
2 | //-
3 | Ideally we'd just have span.left and span.right contain all the chars to the left and
4 | right of the caret, but line-wrapping becomes tricky on some browsers (FF/IE/Edge).
5 | Until we can find a solution for this, we just create one span per character.
6 | span.vue-typer
7 | span.left
8 | char.custom.typed(v-for='l in numLeftChars',
9 | :val="currentTextArray[l-1]")
10 | caret(:class='caretClasses', :animation='caretAnimation')
11 | span.right
12 | char.custom(v-for='r in numRightChars',
13 | :val="currentTextArray[numLeftChars + r-1]",
14 | :class='rightCharClasses')
15 |
16 |
17 |
404 |
405 |
415 |
--------------------------------------------------------------------------------
/src/vue-typer/index.js:
--------------------------------------------------------------------------------
1 | import VueTyperComponent from './components/VueTyper'
2 |
3 | export const VueTyper = VueTyperComponent
4 |
5 | export default {
6 | install(Vue) {
7 | Vue.component('vue-typer', VueTyperComponent)
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/src/vue-typer/styles/caret-animations.scss:
--------------------------------------------------------------------------------
1 | @keyframes vue-typer-caret-blink {
2 | 50% {
3 | opacity: 0;
4 | }
5 | 100% {
6 | opacity: 1;
7 | }
8 | }
9 |
10 | @keyframes vue-typer-caret-smooth {
11 | 0%, 20% {
12 | opacity: 1;
13 | }
14 | 60%, 100% {
15 | opacity: 0;
16 | }
17 | }
18 |
19 | @keyframes vue-typer-caret-phase {
20 | 0%, 20% {
21 | opacity: 1;
22 | }
23 | 90%, 100% {
24 | opacity: 0;
25 | }
26 | }
27 |
28 | @keyframes vue-typer-caret-expand {
29 | 0%, 20% {
30 | transform: scaleY(1);
31 | }
32 | 80%, 100% {
33 | transform: scaleY(0);
34 | }
35 | }
36 |
37 | .vue-typer-caret-blink {
38 | animation: vue-typer-caret-blink 1s step-start 0s infinite;
39 | }
40 |
41 | .vue-typer-caret-smooth {
42 | animation: vue-typer-caret-smooth 0.5s ease-in-out 0s infinite alternate;
43 | }
44 |
45 | .vue-typer-caret-phase {
46 | animation: vue-typer-caret-phase 0.5s ease-in-out 0s infinite alternate;
47 | }
48 |
49 | .vue-typer-caret-expand {
50 | animation: vue-typer-caret-expand 0.5s ease-in-out 0s infinite alternate;
51 | }
52 |
--------------------------------------------------------------------------------
/src/vue-typer/styles/typer-colors.scss:
--------------------------------------------------------------------------------
1 | $char-typed-color: black;
2 | $char-selected-color: black;
3 |
4 | $char-typed-background-color: transparent;
5 | $char-selected-background-color: #ACCEF7;
6 |
7 | $caret-idle-color: black;
8 | $caret-typing-color: black;
9 | $caret-selecting-color: black;
10 | $caret-erasing-color: black;
11 | $caret-complete-color: black;
12 |
--------------------------------------------------------------------------------
/src/vue-typer/utils/random-int.js:
--------------------------------------------------------------------------------
1 | function validNumber(val) {
2 | return typeof val === 'number' && !Number.isNaN(val) && Number.isFinite(val)
3 | }
4 |
5 | function validRange(lower, upper) {
6 | return validNumber(lower) && validNumber(upper) && lower <= upper
7 | }
8 |
9 | /**
10 | * @param min - Minimum random int
11 | * @param max - Maximum random int
12 | * @returns a random int in the range [min, max], or -1 if either of the following conditions are met:
13 | * - min and/or max are not of type 'number', NaN, or Infinity
14 | * - min > max
15 | */
16 | export default (min, max) => {
17 | if (!validRange(min, max)) {
18 | return -1
19 | }
20 |
21 | // Since we're generating random integers, rounded the arguments to the closest int within the range
22 | min = Math.ceil(min)
23 | max = Math.floor(max)
24 |
25 | return Math.floor(Math.random() * (max - min + 1)) + min
26 | }
27 |
--------------------------------------------------------------------------------
/src/vue-typer/utils/shallow-equals.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Performs a shallow comparison between 2 arrays.
3 | * @param {Array} a1
4 | * @param {Array} a2
5 | * @returns true if the array contents are strictly equal (===); false otherwise
6 | */
7 | export default (a1, a2) => {
8 | if (!Array.isArray(a1) || !Array.isArray(a2)) {
9 | return false
10 | }
11 |
12 | if (a1.length !== a2.length) {
13 | return false
14 | }
15 |
16 | for (let i = 0; i < a1.length; i++) {
17 | if (a1[i] !== a2[i]) {
18 | return false
19 | }
20 | }
21 |
22 | return true
23 | }
24 |
--------------------------------------------------------------------------------
/src/vue-typer/utils/shuffle.js:
--------------------------------------------------------------------------------
1 | import randomInt from './random-int'
2 |
3 | function swap(a, i, j) {
4 | if (i === j) {
5 | return
6 | }
7 | const temp = a[i]
8 | a[i] = a[j]
9 | a[j] = temp
10 | }
11 |
12 | /**
13 | * Performs an in-place shuffle.
14 | * Implemented using the Fisher-Yates/Knuth shuffle algorithm.
15 | * @param list - Array of items to shuffle in-place.
16 | */
17 | export default (list) => {
18 | if (!(list instanceof Array)) {
19 | return
20 | }
21 |
22 | for (let i = list.length - 1; i > 0; i--) {
23 | let randomIndex = randomInt(0, i)
24 | swap(list, i, randomIndex)
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/test/unit/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "mocha": true
4 | },
5 | "globals": {
6 | "describe": true,
7 | "it": true,
8 | "expect": true,
9 | "sinon": true
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/test/unit/index.js:
--------------------------------------------------------------------------------
1 | // Polyfill fn.bind() for PhantomJS
2 | /* eslint-disable no-extend-native */
3 | Function.prototype.bind = require('function-bind')
4 |
5 | // require all test files (files that ends with .spec.js)
6 | const testsContext = require.context('./specs', true, /\.spec$/)
7 | testsContext.keys().forEach(testsContext)
8 |
9 | // require all src files except index.js for coverage.
10 | // you can also change this to match only the subset of files that
11 | // you want coverage for.
12 | const srcContext = require.context('../../src/vue-typer', true, /^\.\/(?!index(\.js)?$)/)
13 | srcContext.keys().forEach(srcContext)
14 |
--------------------------------------------------------------------------------
/test/unit/karma.conf.js:
--------------------------------------------------------------------------------
1 | // This is a karma config file. For more details see
2 | // http://karma-runner.github.io/0.13/config/configuration-file.html
3 | // we are also using it with karma-webpack
4 | // https://github.com/webpack/karma-webpack
5 |
6 | var webpackConfig = require('../../build/webpack.config.test')
7 |
8 | module.exports = function(config) {
9 | config.set({
10 | // to run in additional browsers:
11 | // 1. install corresponding karma launcher
12 | // http://karma-runner.github.io/0.13/config/browsers.html
13 | // 2. add it to the `browsers` array below.
14 | browsers: ['PhantomJS'],
15 | frameworks: ['mocha', 'sinon-chai'],
16 | reporters: ['spec', 'coverage'],
17 | files: ['./index.js'],
18 | preprocessors: {
19 | './index.js': ['webpack', 'sourcemap']
20 | },
21 | webpack: webpackConfig,
22 | webpackMiddleware: {
23 | noInfo: true
24 | },
25 | coverageReporter: {
26 | dir: './coverage',
27 | reporters: [
28 | { type: 'lcov', subdir: '.' },
29 | { type: 'text-summary' }
30 | ]
31 | }
32 | })
33 | }
34 |
--------------------------------------------------------------------------------
/test/unit/specs/VueTyper.spec.js:
--------------------------------------------------------------------------------
1 | import VueTyper from '../../../src/vue-typer/components/VueTyper'
2 | import _mount from '../utils/mount'
3 | import { waitUntilRendered, wait as _wait } from '../utils/wait'
4 |
5 | describe('VueTyper.vue', function() {
6 | let clock
7 |
8 | function mount(propsData) {
9 | return _mount(VueTyper, propsData)
10 | }
11 |
12 | function wait(ms, next) {
13 | _wait(clock, ms, next)
14 | }
15 |
16 | beforeEach(function() {
17 | clock = sinon.useFakeTimers()
18 | })
19 | afterEach(function() {
20 | clock.restore()
21 | })
22 |
23 | it('should have a name so it is identifiable in the Vue debugger', function() {
24 | expect(VueTyper.name).to.equal('VueTyper')
25 | })
26 |
27 | describe('Unicode support', function() {
28 | let vm
29 |
30 | function _mount(text) {
31 | vm = mount({ text })
32 | }
33 |
34 | function assertTextLength(length) {
35 | return expect(vm.currentTextLength).to.equal(length)
36 | }
37 |
38 | describe('Emojis', function() {
39 | const emojiTestData = {
40 | '1': ['💙', '⛳', '⛈'],
41 | '2': ['❤️', '💩'],
42 | '3': ['✍🏻', '🔥'],
43 | '4': ['👍🏻', '🤳🏻'],
44 | '5': ['💅🏻', '👨⚖️'],
45 | '7': ['👩🏻🎤', '👩🏻✈️'],
46 | '8': ['👩❤️👩', '👨👩👧'],
47 | '9': ['👩👩👦'],
48 | '11': ['👩❤️💋👩', '👨👩👧👦']
49 | }
50 |
51 | for (let emojiCodepoint in emojiTestData) {
52 | describe(`should properly count ${emojiCodepoint} codepoint emojis`, function() {
53 | let emojiList = emojiTestData[emojiCodepoint]
54 | for (let emoji of emojiList) {
55 | it(`${emoji} has length 1`, function() {
56 | _mount(emoji)
57 | assertTextLength(1)
58 | })
59 | }
60 | })
61 | }
62 | })
63 | })
64 |
65 | describe('Repeat and EraseOnComplete', function() {
66 | // eslint-disable-next-line one-var
67 | const preTypeDelay = 1, preEraseDelay = 1, typeDelay = 1, eraseDelay = 1
68 | let vm
69 | function createOptions(repeat, eraseOnComplete) {
70 | return { text: 'a', repeat, eraseOnComplete, preTypeDelay, typeDelay, preEraseDelay, eraseDelay }
71 | }
72 |
73 | it('should not repeat and should not erase final text', function(done) {
74 | vm = mount(createOptions(0, false))
75 | wait(preTypeDelay, () => {
76 | expect(vm.state).to.equal('complete')
77 | done()
78 | })
79 | })
80 |
81 | it('should not repeat and should erase final text', function(done) {
82 | vm = mount(createOptions(0, true))
83 | wait(preTypeDelay, () => {
84 | // assert that we're not done yet, we still have to erase the final text!
85 | expect(vm.state).not.to.equal('complete')
86 |
87 | // preEraseDelay = select-all, eraseDelay = actual erase
88 | wait(preEraseDelay + eraseDelay, () => {
89 | expect(vm.state).to.equal('complete')
90 | done()
91 | })
92 | })
93 | })
94 |
95 | it('should repeat as many times as specified and should not erase final text', function(done) {
96 | vm = mount(createOptions(1, false))
97 |
98 | // type first time
99 | wait(preTypeDelay, () => {
100 | // select-all and erase first time
101 | wait(preEraseDelay + eraseDelay, () => {
102 | // assert that we're not done yet, we still have to repeat one more time!
103 | expect(vm.state).not.to.equal('complete')
104 |
105 | // type second time
106 | wait(preTypeDelay, () => {
107 | // assert that we're not done yet, we still have to erase the final text!
108 | expect(vm.state).to.equal('complete')
109 | done()
110 | })
111 | })
112 | })
113 | })
114 |
115 | it('should repeat as many times as specified and erase final text', function(done) {
116 | vm = mount(createOptions(1, true))
117 |
118 | // type first time
119 | wait(preTypeDelay, () => {
120 | // select-all and erase first time
121 | wait(preEraseDelay + eraseDelay, () => {
122 | // assert that we're not done yet, we still have to repeat one more time!
123 | expect(vm.state).not.to.equal('complete')
124 |
125 | // type second time
126 | wait(preTypeDelay, () => {
127 | // assert that we're not done yet, we still have to erase the final text!
128 | expect(vm.state).not.to.equal('complete')
129 |
130 | // select-all and erase second time
131 | wait(preEraseDelay + eraseDelay, () => {
132 | expect(vm.state).to.equal('complete')
133 | done()
134 | })
135 | })
136 | })
137 | })
138 | })
139 | })
140 |
141 | describe('Caret', function() {
142 | it('should have the correct animation class', function() {
143 | let vm = mount({ text: 'abc', caretAnimation: 'solid' })
144 | let caret = vm.$el.querySelector('.caret')
145 | expect(caret.classList.contains('vue-typer-caret-solid')).to.be.true
146 |
147 | vm = mount({ text: 'abc', caretAnimation: 'blink' })
148 | caret = vm.$el.querySelector('.caret')
149 | expect(caret.classList.contains('vue-typer-caret-blink')).to.be.true
150 |
151 | vm = mount({ text: 'abc', caretAnimation: 'smooth' })
152 | caret = vm.$el.querySelector('.caret')
153 | expect(caret.classList.contains('vue-typer-caret-smooth')).to.be.true
154 |
155 | vm = mount({ text: 'abc', caretAnimation: 'phase' })
156 | caret = vm.$el.querySelector('.caret')
157 | expect(caret.classList.contains('vue-typer-caret-phase')).to.be.true
158 |
159 | vm = mount({ text: 'abc', caretAnimation: 'expand' })
160 | caret = vm.$el.querySelector('.caret')
161 | expect(caret.classList.contains('vue-typer-caret-expand')).to.be.true
162 | })
163 | it('should be positionable to the beginning', function() {
164 | const vm = mount({ text: 'abc' })
165 | vm.moveCaretToStart()
166 | expect(vm.currentTextIndex).to.equal(0)
167 | })
168 | it('should be positionable to the end', function() {
169 | const vm = mount({ text: 'abc' })
170 | vm.moveCaretToEnd()
171 | expect(vm.currentTextIndex).to.equal(3)
172 | })
173 | it('should be shiftable', function() {
174 | const vm = mount({ text: 'abc' })
175 | vm.moveCaretToStart()
176 | vm.shiftCaret(2)
177 | expect(vm.currentTextIndex).to.equal(2)
178 | })
179 | })
180 |
181 | describe('Events', function() {
182 | const text = 'abc'
183 | let vm
184 | beforeEach(function() {
185 | vm = mount({ text })
186 | })
187 |
188 | it('should emit \'typed-char\' event for each char in a typed word', function(done) {
189 | let numTyped = 0
190 | vm.$on('typed-char', (char, index) => {
191 | expect(text.charAt(numTyped)).to.equal(char)
192 | expect(numTyped).to.equal(index)
193 |
194 | numTyped++
195 | if (numTyped === text.length) {
196 | done()
197 | }
198 | })
199 |
200 | let numChars = text.length
201 | while (numChars--) {
202 | vm.typeStep()
203 | }
204 | })
205 |
206 | it('should emit \'typed\' event after a word is typed', function(done) {
207 | vm.$on('typed', (word) => {
208 | expect(word).to.equal(text)
209 | done()
210 | })
211 | vm.onTyped()
212 | })
213 | it('should emit \'erased\' event after a word is erased', function(done) {
214 | vm.$on('erased', (word) => {
215 | expect(word).to.equal(text)
216 | done()
217 | })
218 | vm.onErased()
219 | })
220 | it('should emit \'completed\' event after all words are typed/erased', function(done) {
221 | vm.$on('completed', (word) => {
222 | done()
223 | })
224 | vm.onComplete()
225 | })
226 | })
227 |
228 | describe('Typing and Erasing', function() {
229 | function expectText(vm, side, expectedText, expectedCharClass) {
230 | const container = vm.$el.querySelector('.' + side)
231 | expect(container.classList.contains(side)).to.be.true
232 | expect(container.childElementCount).to.equal(expectedText.length)
233 |
234 | let child
235 | for (let i = 0; i < expectedText.length; i++) {
236 | child = container.children[i]
237 | if (expectedCharClass) {
238 | expect(child.classList.contains(expectedCharClass))
239 | }
240 | expect(child.textContent).to.equal(expectedText.charAt(i))
241 | }
242 | }
243 | function expectLeftText(vm, expectedText) {
244 | expectText(vm, 'left', expectedText, 'typed')
245 | }
246 | function expectRightText(vm, expectedText, expectedCharClass) {
247 | expectText(vm, 'right', expectedText, expectedCharClass)
248 | }
249 |
250 | describe('Initial Action', function() {
251 | it('should initialize to typing state', function(done) {
252 | const vm = mount({
253 | text: 'abc',
254 | initialAction: 'typing'
255 | })
256 |
257 | waitUntilRendered(() => {
258 | expectLeftText(vm, '')
259 | expectRightText(vm, 'abc')
260 | done()
261 | })
262 | })
263 |
264 | it('should initialize to erasing state', function(done) {
265 | const vm = mount({
266 | text: 'abc',
267 | initialAction: 'erasing'
268 | })
269 |
270 | waitUntilRendered(() => {
271 | expectLeftText(vm, 'abc')
272 | expectRightText(vm, '')
273 | done()
274 | })
275 | })
276 | })
277 |
278 | describe('Typing Delay', function() {
279 | const preTypeDelay = 100
280 | const typeDelay = 50
281 | let vm
282 | beforeEach(function() {
283 | vm = mount({
284 | text: 'abc',
285 | typeDelay,
286 | preTypeDelay
287 | })
288 | })
289 |
290 | it('should wait \'preTypeDelay\' before typing the first character', function(done) {
291 | wait(preTypeDelay, () => {
292 | expectLeftText(vm, 'a')
293 | expectRightText(vm, 'bc')
294 | done()
295 | })
296 | })
297 |
298 | it('should wait \'typeDelay\' before typing the second character', function(done) {
299 | wait(preTypeDelay + typeDelay, () => {
300 | expectLeftText(vm, 'ab')
301 | expectRightText(vm, 'c')
302 | done()
303 | })
304 | })
305 | })
306 |
307 | describe('Erasing Delay', function() {
308 | const preEraseDelay = 100
309 | const eraseDelay = 50
310 | let vm
311 |
312 | function createBeforeEach(eraseStyle) {
313 | return function() {
314 | vm = mount({
315 | text: 'abc',
316 | initialAction: 'erasing',
317 | eraseStyle,
318 | eraseDelay,
319 | preEraseDelay
320 | })
321 | }
322 | }
323 |
324 | describe('backspace', function() {
325 | beforeEach(createBeforeEach('backspace'))
326 | it('should wait \'preEraseDelay\' before erasing the first character', function(done) {
327 | wait(preEraseDelay, () => {
328 | expectLeftText(vm, 'ab')
329 | expectRightText(vm, 'c', 'erased')
330 | done()
331 | })
332 | })
333 | it('should wait \'eraseDelay\' before erasing the second character', function(done) {
334 | wait(preEraseDelay + eraseDelay, () => {
335 | expectLeftText(vm, 'a')
336 | expectRightText(vm, 'bc', 'erased')
337 | done()
338 | })
339 | })
340 | })
341 |
342 | describe('select-back', function() {
343 | beforeEach(createBeforeEach('select-back'))
344 | it('should wait \'preEraseDelay\' before selecting the first character', function(done) {
345 | wait(preEraseDelay, () => {
346 | expectLeftText(vm, 'ab')
347 | expectRightText(vm, 'c', 'erased')
348 | done()
349 | })
350 | })
351 | it('should wait \'eraseDelay\' before selecting the second character', function(done) {
352 | wait(preEraseDelay + eraseDelay, () => {
353 | expectLeftText(vm, 'a')
354 | expectRightText(vm, 'bc', 'selected')
355 | done()
356 | })
357 | })
358 | })
359 |
360 | describe('select-all', function() {
361 | beforeEach(createBeforeEach('select-all'))
362 | it('should wait \'preEraseDelay\' before selecting all characters', function(done) {
363 | wait(preEraseDelay, () => {
364 | expectLeftText(vm, '')
365 | expectRightText(vm, 'abc', 'selected')
366 | done()
367 | })
368 | })
369 | it('should wait \'eraseDelay\' before erasing the entire selection', function(done) {
370 | wait(preEraseDelay + eraseDelay, () => {
371 | expectLeftText(vm, '')
372 | expectRightText(vm, 'abc', 'erased')
373 | done()
374 | })
375 | })
376 | })
377 |
378 | describe('clear', function() {
379 | beforeEach(createBeforeEach('clear'))
380 | it('should wait \'preEraseDelay\' before clearing all characters', function(done) {
381 | wait(preEraseDelay, () => {
382 | expectLeftText(vm, '')
383 | expectRightText(vm, 'abc', 'erased')
384 | done()
385 | })
386 | })
387 | })
388 | })
389 | })
390 | })
391 |
--------------------------------------------------------------------------------
/test/unit/utils/mount.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 |
3 | export default function mount(componentOptions, propsData) {
4 | const Ctor = Vue.extend(componentOptions)
5 | const vm = new Ctor({ propsData })
6 | return vm.$mount()
7 | }
8 |
--------------------------------------------------------------------------------
/test/unit/utils/wait.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 |
3 | export function waitUntilRendered(next) {
4 | Vue.nextTick(next)
5 | }
6 |
7 | export function wait(clock, ms, next) {
8 | clock.tick(ms)
9 | waitUntilRendered(next)
10 | }
11 |
--------------------------------------------------------------------------------