├── .browserslistrc
├── .editorconfig
├── .eslintrc.js
├── .github
└── workflows
│ └── node.js.yml
├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── babel.config.js
├── deploy.sh
├── jest.config.js
├── package-lock.json
├── package.json
├── postbuild.js
├── public
├── favicon.ico
└── index.html
├── src
├── App.vue
├── assets
│ ├── logo.png
│ └── logo.svg
├── components
│ ├── VNumeric
│ │ ├── VCalculator.ts
│ │ ├── VNumeric.ts
│ │ └── VNumericInput.ts
│ └── index.ts
├── main.ts
├── plugins
│ └── vuetify.ts
├── shims-tsx.d.ts
├── shims-vue.d.ts
└── shims-vuetify.ts
├── testcdn
└── index.html
├── tests
└── unit
│ ├── VNumeric.spec.ts
│ └── __snapshots__
│ └── VNumeric.spec.ts.snap
├── tsconfig.json
├── vue.config.js
└── yarn.lock
/.browserslistrc:
--------------------------------------------------------------------------------
1 | > 1%
2 | last 2 versions
3 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | [*.{js,jsx,ts,tsx,vue}]
2 | indent_style = space
3 | indent_size = 2
4 | trim_trailing_whitespace = true
5 | insert_final_newline = true
6 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | env: {
4 | node: true
5 | },
6 | extends: [
7 | 'plugin:vue/essential',
8 | '@vue/standard',
9 | '@vue/typescript/recommended'
10 | ],
11 | parserOptions: {
12 | ecmaVersion: 2020
13 | },
14 | rules: {
15 | 'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
16 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off'
17 | },
18 | overrides: [
19 | {
20 | files: [
21 | '**/__tests__/*.{j,t}s?(x)',
22 | '**/tests/unit/**/*.spec.{j,t}s?(x)'
23 | ],
24 | env: {
25 | jest: true
26 | }
27 | }
28 | ]
29 | }
30 |
--------------------------------------------------------------------------------
/.github/workflows/node.js.yml:
--------------------------------------------------------------------------------
1 | # This workflow will do a clean install of node dependencies, cache/restore them, build the source code and run tests across different versions of node
2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
3 |
4 | name: Node.js CI
5 |
6 | on:
7 | push:
8 | branches: [ master ]
9 | pull_request:
10 | branches: [ master ]
11 |
12 | jobs:
13 | build:
14 |
15 | runs-on: ubuntu-latest
16 |
17 | strategy:
18 | matrix:
19 | node-version: [12.x, 14.x, 16.x]
20 | # See supported Node.js release schedule at https://nodejs.org/en/about/releases/
21 |
22 | steps:
23 | - uses: actions/checkout@v2
24 | - name: Use Node.js ${{ matrix.node-version }}
25 | uses: actions/setup-node@v2
26 | with:
27 | node-version: ${{ matrix.node-version }}
28 | cache: 'npm'
29 | - run: npm ci
30 | - run: npm run lib
31 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /dist
4 |
5 | # local env files
6 | .env.local
7 | .env.*.local
8 |
9 | # Log files
10 | npm-debug.log*
11 | yarn-debug.log*
12 | yarn-error.log*
13 |
14 | # Editor directories and files
15 | .idea
16 | .vscode
17 | *.suo
18 | *.ntvs*
19 | *.njsproj
20 | *.sln
21 | *.sw?
22 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "10"
4 | script:
5 | - npm run lint
6 | - npm run build
7 | - npm run test:unit
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Aleksandr Kolesnikov
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 | # vuetify-numeric
2 | Numeric input components for use with [vuetifyjs](https://vuetifyjs.com).
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | ## Features
17 | - Built-in calculator
18 | - Smart numeric input
19 | - Locale support number format
20 | - Adjustable text color
21 | - Groupping digits
22 | - Right number alignement
23 | - Show prefix (currency ...) near your number
24 | - No thirdpatry solutions is used
25 | - Vuetify [VTextField](https://vuetifyjs.com/en/components/text-fields) compatible
26 |
27 | ## Keyboard shortcuts
28 | | Key | Action |
29 | | ---- | -------- |
30 | | Enter | Activate calculator or calculate your expression and close the calculator. (Note) You can change calculator's activation key |
31 | | Delete | Reset calculator |
32 | | . or , | Swich your input between integer and fraction part of number |
33 | | - | Change your input number sign |
34 |
35 | ## Demo & Playground
36 | See [Live demo ](https://kolesnikovav.github.io/vuetify-numeric/). or Codesandbox example [codesandbox](https://codesandbox.io/s/condescending-mendel-5zpqn)
37 |
38 | ## The v-numeric component
39 | The component extends the Vuetify `v-text-field` component.
40 |
41 | ## How to use
42 |
43 | Install the package:
44 | ```
45 | yarn add vuetify-numeric
46 | ```
47 |
48 | Add the package to your app entry point:
49 | ```
50 | import VNumeric from 'vuetify-numeric/vuetify-numeric.umd.min'
51 | ```
52 |
53 | Or (in develop case)
54 | ```
55 | import VNumeric from 'vuetify-numeric/vuetify-numeric.umd'
56 | ```
57 | Than, register this plugin
58 | ```
59 | Vue.use(VNumeric)
60 | ```
61 | Once the plugin has been installed, you can now use the `v-numeric` component in your templates.
62 | Use `v-model` to bind to the value.
63 | ```
64 |
65 |
66 |
67 |
68 |
77 | ```
78 |
79 | ### Props:
80 |
81 | | Prop | description | type | default |
82 | | ---- | ---- | ------- | --- |
83 | | min | Sets minimum value | Number | - Number.MAX (infinity) |
84 | | max | Sets maximum value | Number | Number.MAX (infinity)|
85 | | length | Sets maximum number of digits | Number | 10 |
86 | | precision | Number of digits after decimal point | Number | 0 |
87 | | negativeTextColor | Text color when number is negative | String | red |
88 | | locale | Current locale | String | en-US |
89 | | useGrouping | use grouping digits | Boolean | true |
90 | | elevation | Sets the calculator elevation | Number | 10 |
91 | | fab | FAB-kind calculator's button | Boolean | false |
92 | | text | use transparent background in calculator | Boolean | false |
93 | | calcStyle | You can customize calculator's button style separately from input field. This is not mandatory.| object | undefined |
94 | | calcIcon | You can customize calculator's icon. If it's undefined, the calculator icon does not appear.| string | 'mdi-calculator' |
95 | | useCalculator | Turn on/off calculator usage.| boolean | true |
96 | | openKey | Key for open build-in calculator | String |'Enter'
97 | | calcNoTabindex | Set or not tabindex attribute in calc icon | Boolean | false |
98 |
99 | ### calcStyle object properties:
100 | This object uses for customizing calculator buttons, and consist of the same Vuetify v-btn properies.
101 | For details, see [official documentation](https://vuetifyjs.com/en/components/buttons/#api)
102 |
103 | calcStyle: {
104 | fab: false,
105 | outlined: false,
106 | rounded: false,
107 | text: false,
108 | tile: false,
109 | large: false,
110 | small: false
111 | }
112 |
113 | Anover props are derived from [v-text-field](https://vuetifyjs.com/en/components/text-fields) component
114 |
115 | ### Events:
116 |
117 | `@input`: Emitted when value is changed after user input.
118 | `@change`: Emitted formatted value as string when that is changed after user input.
119 |
120 | ### CDN example:
121 |
122 | You can use this library without installation, via cdn provider
123 | ```
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
157 |
158 |
159 |
160 | ```
161 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | '@vue/cli-plugin-babel/preset'
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/deploy.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | set -e
4 |
5 | npm run build
6 |
7 | cd dist
8 |
9 | git init
10 | git add -A
11 | git commit -m 'deploy'
12 |
13 | git push -f git@github.com:kolesnikovav/vuetify-numeric.git master:gh-pages
14 |
15 | cd -
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | preset: '@vue/cli-plugin-unit-jest/presets/typescript-and-babel',
3 | transformIgnorePatterns: ['/node_modules/(?!vuetify)']
4 | }
5 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vuetify-numeric",
3 | "version": "0.2.1",
4 | "private": false,
5 | "description": "Numeric input components for use with vuetifyjs",
6 | "author": {
7 | "name": "Aleksandr Kolesnikov",
8 | "email": "alex.v.kolesnikov72@gmail.com"
9 | },
10 | "scripts": {
11 | "serve": "vue-cli-service serve",
12 | "build": "vue-cli-service build",
13 | "test:unit": "vue-cli-service test:unit",
14 | "lint": "vue-cli-service lint",
15 | "lib": "vue-cli-service build --target lib 'src/components/index.ts'",
16 | "publish": "npm run lib && node postbuild.js"
17 | },
18 | "main": "vuetify-numeric.umd.min.js",
19 | "unpkg": "vuetify-numeric.umd.min.js",
20 | "dependencies": {
21 | "core-js": "^3.6.4",
22 | "vue": "^2.6.14",
23 | "vuetify": "^2.6.3"
24 | },
25 | "devDependencies": {
26 | "@types/jest": "^25.1.4",
27 | "@typescript-eslint/eslint-plugin": "^2.23.0",
28 | "@typescript-eslint/parser": "^2.23.0",
29 | "@vue/cli-plugin-babel": "~4.5.15",
30 | "@vue/cli-plugin-eslint": "~4.5.15",
31 | "@vue/cli-plugin-typescript": "~4.5.15",
32 | "@vue/cli-plugin-unit-jest": "~4.5.15",
33 | "@vue/cli-service": "~4.5.15",
34 | "@vue/eslint-config-standard": "^5.1.2",
35 | "@vue/eslint-config-typescript": "^5.0.2",
36 | "@vue/test-utils": "1.0.0-beta.32",
37 | "eslint": "^6.8.0",
38 | "eslint-plugin-import": "^2.20.1",
39 | "eslint-plugin-node": "^11.0.0",
40 | "eslint-plugin-promise": "^4.2.1",
41 | "eslint-plugin-standard": "^4.0.1",
42 | "eslint-plugin-vue": "^6.2.2",
43 | "node-sass": "^4.12.0",
44 | "sass": "^1.19.0",
45 | "sass-loader": "^8.0.2",
46 | "typescript": "~4.1.5",
47 | "vue-cli-plugin-vuetify": "~2.4.5",
48 | "vue-template-compiler": "^2.6.14",
49 | "vuetify-loader": "^1.7.3"
50 | },
51 | "homepage": "https://github.com/kolesnikovav/vuetify-numeric",
52 | "jsdelivr": "vuetify-numeric.umd.min.js",
53 | "keywords": [
54 | "vuetify",
55 | "vue",
56 | "calculator",
57 | "numeric input"
58 | ],
59 | "license": "MIT",
60 | "repository": {
61 | "type": "git",
62 | "url": "git+https://github.com/kolesnikovav/vuetify-numeric.git"
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/postbuild.js:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line @typescript-eslint/no-var-requires
2 | const fs = require('fs')
3 |
4 | const DIST_LIB_PATH = 'dist/'
5 | const README_PATH = 'README.md'
6 | const PACKJS_PATH = 'package.json'
7 |
8 | const PATH_TO = DIST_LIB_PATH + README_PATH
9 | const PATH_TO_JSON = 'dist/package.json'
10 |
11 | function copyFilesIntoDistFolder () {
12 | if (!fs.existsSync(README_PATH) || !fs.existsSync(PACKJS_PATH)) {
13 | throw new Error('README.md or package.json does not exist')
14 | } else {
15 | fs.copyFileSync(README_PATH, PATH_TO)
16 | fs.copyFileSync(PACKJS_PATH, PATH_TO_JSON)
17 | }
18 | }
19 |
20 | copyFilesIntoDistFolder()
21 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kolesnikovav/vuetify-numeric/bfa44944cafd33580770588b95616b01584bc439/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | <%= htmlWebpackPlugin.options.title %>
9 |
10 |
11 |
12 |
13 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | VUETIFY-NUMERIC demo page
6 |
7 |
8 | GitHub
11 |
12 |
13 |
14 | Vuetify-numeric demo page
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
32 |
33 |
34 |
39 |
40 |
41 |
46 |
47 |
48 |
53 |
54 |
55 |
56 |
57 |
58 | Input style
59 |
60 |
61 |
62 |
63 |
64 |
65 |
70 |
71 |
72 |
73 |
74 | Custom calculator's style
75 |
76 |
77 |
82 |
87 |
92 |
97 |
102 |
107 |
112 |
113 |
114 |
115 |
116 |
117 |
126 |
127 |
128 |
136 |
137 |
138 |
146 |
147 |
148 | v-numeric component example
149 |
150 |
151 |
174 |
175 |
176 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
250 |
251 |
259 |
--------------------------------------------------------------------------------
/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kolesnikovav/vuetify-numeric/bfa44944cafd33580770588b95616b01584bc439/src/assets/logo.png
--------------------------------------------------------------------------------
/src/assets/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/components/VNumeric/VCalculator.ts:
--------------------------------------------------------------------------------
1 | import Vue, { VNode } from 'vue'
2 | import { VTextFieldA, VBtnA, VRowA, VSheetA } from '../../shims-vuetify'
3 |
4 | type operationType = ((a: number|string, b: number| string) => number)|undefined
5 |
6 | export default Vue.extend({
7 | name: 'v-calculator',
8 | props: {
9 | isActive: {
10 | type: Boolean,
11 | default: false
12 | },
13 | elevation: {
14 | type: Number,
15 | default: 0
16 | },
17 | dark: {
18 | type: Boolean,
19 | default: false
20 | },
21 | fab: {
22 | type: Boolean,
23 | default: false
24 | },
25 | outlined: {
26 | type: Boolean,
27 | default: false
28 | },
29 | rounded: {
30 | type: Boolean,
31 | default: false
32 | },
33 | text: {
34 | type: Boolean,
35 | default: false
36 | },
37 | useGrouping: {
38 | type: Boolean,
39 | default: true
40 | },
41 | locale: {
42 | type: String,
43 | default: 'en-US'
44 | },
45 | precision: {
46 | type: Number,
47 | default: 0
48 | },
49 | initialValue: {
50 | type: Number,
51 | default: 0
52 | },
53 | negativeTextColor: {
54 | type: String,
55 | default: 'red'
56 | },
57 | calcStyle: {
58 | type: Object,
59 | default: undefined
60 | }
61 | },
62 | computed: {
63 | numberFormatter (): Intl.NumberFormat {
64 | return new Intl.NumberFormat(this.locale, {
65 | useGrouping: this.useGrouping
66 | })
67 | },
68 | resultNumber (): string {
69 | return this.numberFormatter.format(Number(this.value))
70 | },
71 | computedColor (): string| undefined {
72 | if (Number(this.$data.value) < 0 && this.negativeTextColor) {
73 | return this.negativeTextColor
74 | } else return undefined
75 | },
76 | computedOutlined (): boolean {
77 | if (!this.calcStyle) return this.outlined
78 | if (this.calcStyle.outlined === undefined) return this.outlined
79 | return this.calcStyle.outlined
80 | },
81 | computedRounded (): boolean {
82 | if (!this.calcStyle) return this.rounded
83 | if (this.calcStyle.rounded === undefined) return this.rounded
84 | return this.calcStyle.rounded
85 | },
86 | computedText (): boolean {
87 | if (!this.calcStyle) return this.text
88 | if (this.calcStyle.text === undefined) return this.text
89 | return this.calcStyle.text
90 | },
91 | computedTile (): boolean {
92 | if (!this.calcStyle) return false
93 | return (this.calcStyle.tile === undefined) ? false : this.calcStyle.tile
94 | },
95 | computedLarge (): boolean {
96 | if (!this.calcStyle) return false
97 | return (this.calcStyle.large === undefined) ? false : this.calcStyle.large
98 | },
99 | computedSmall (): boolean {
100 | if (!this.calcStyle) return false
101 | return (this.calcStyle.small === undefined) ? false : this.calcStyle.small
102 | },
103 | computedWidth (): string {
104 | if (!this.calcStyle) return '288px'
105 | return (this.calcStyle.width === undefined) ? '288px' : this.calcStyle.width
106 | },
107 | computedHeight (): string {
108 | if (!this.calcStyle) return '246px'
109 | return (this.calcStyle.height === undefined)
110 | ? this.calcStyle.small ? '212px' : '246px'
111 | : this.calcStyle.height
112 | },
113 | textOperand (): string {
114 | if (!this.operand) return ''
115 | return (this.operand === 0) ? '' : this.operand.toString()
116 | }
117 | },
118 | data: () => ({
119 | value: '0',
120 | operand: 0,
121 | operation: undefined as operationType
122 | }),
123 | watch: {
124 | initialValue: {
125 | immediate: true,
126 | deep: true,
127 | handler (newVal) {
128 | if (newVal) {
129 | this.value = newVal.toString()
130 | }
131 | }
132 | },
133 | computedColor (newVal) {
134 | const input = this.genResultInput()
135 | if (input) {
136 | input.style.color = newVal || null
137 | }
138 | }
139 | },
140 | methods: {
141 | reset (): void {
142 | this.value = '0'
143 | this.operation = undefined
144 | this.operand = 0
145 | },
146 | genResultInput (): HTMLInputElement| undefined {
147 | const inputs = (this.$refs.calcResult as Vue).$el.getElementsByTagName('input')
148 | if (inputs && inputs.length > 0) {
149 | return inputs[0]
150 | }
151 | },
152 | getOperation (simbol: string): operationType {
153 | if (simbol === '+') return (a: number|string, b: number|string) => { return Number(a) + Number(b) }
154 | else if (simbol === '-') return (a: number|string, b: number|string) => { return Number(a) - Number(b) }
155 | else if (simbol === '*') return (a: number|string, b: number|string) => { return Number(a) * Number(b) }
156 | else if (simbol === '÷' || simbol === '/') return (a: number|string, b: number|string) => { return Number(a) / Number(b) }
157 | else if (simbol === '%') return (a: number|string, b: number|string) => { return (Number(a) / 100) * Number(b) }
158 | },
159 | changeValue (newVal: KeyboardEvent| string) {
160 | if (!this.isActive) return
161 | let v: string
162 | if (newVal instanceof KeyboardEvent) {
163 | v = newVal.key
164 | } else {
165 | v = newVal
166 | }
167 | if (['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '00'].includes(v)) {
168 | if (this.value === '0') this.value = v
169 | else this.value += v
170 | } else if (v === 'Backspace' || v === '←') {
171 | if (this.value.length === 2 && this.value.startsWith('-')) {
172 | this.value = '0'
173 | } else {
174 | this.value = this.value.length <= 1 ? '0' : this.value.substring(0, this.value.length - 1)
175 | }
176 | } else if (v.toUpperCase() === 'C') {
177 | this.reset()
178 | } else if (v === ',' || v === '.') {
179 | if (this.value.indexOf('.') === -1) {
180 | this.value += '.'
181 | }
182 | } else if (v === '±') {
183 | if (this.value.toString().startsWith('-')) this.value = this.value.toString().substring(1, this.value.length)
184 | else this.value = '-' + this.value
185 | } else if (v === '1/x') {
186 | if (this.value !== '0') this.value = (1 / Number.parseFloat(this.value)).toString()
187 | } else if (['+', '-', '*', '÷', '/', '%'].includes(v)) {
188 | this.calculate()
189 | this.operation = this.getOperation(v)
190 | this.operand = Number(this.value)
191 | this.value = '0'
192 | } else if (['=', 'Enter', 'OK'].includes(v)) {
193 | this.calculate()
194 | this.operation = undefined
195 | this.operand = 0
196 | if (v === 'Enter' || v === 'OK') this.returnValue()
197 | } else if (v === 'CE') {
198 | this.value = '0'
199 | } else if (v === 'Escape') {
200 | this.reset()
201 | this.$emit('return-value', undefined)
202 | } else if (v === 'Delete') {
203 | this.value = '0'
204 | this.$emit('return-value', this.value)
205 | this.reset()
206 | }
207 | },
208 | returnValue (): void {
209 | this.$emit('return-value', this.value)
210 | this.reset()
211 | },
212 | calculate (): void {
213 | if (this.value && this.operand && this.operation) {
214 | const res = this.operation(this.operand, this.value)
215 | this.value = res.toString()
216 | }
217 | },
218 | genNumberButton (numberValue: string): VNode {
219 | return this.$createElement(VBtnA, {
220 | style: {
221 | 'padding-left': '0px',
222 | 'padding-right': '0px',
223 | 'max-width': '48px',
224 | 'min-width': '48px'
225 | },
226 | props: {
227 | fab: (this.calcStyle && this.calcStyle.fab) ? this.calcStyle.fab : this.fab,
228 | outlined: this.computedOutlined,
229 | rounded: this.computedRounded,
230 | text: this.computedText,
231 | tile: this.computedTile,
232 | large: this.computedLarge,
233 | small: this.computedSmall
234 | },
235 | domProps: {
236 | innerHTML: numberValue
237 | },
238 | on: {
239 | click: () => this.changeValue(numberValue)
240 | }
241 | })
242 | },
243 | genActionsButton (actValue: string): VNode {
244 | return this.$createElement(VBtnA, {
245 | style: {
246 | 'padding-left': '0px',
247 | 'padding-right': '0px',
248 | 'max-width': '48px',
249 | 'min-width': '48px'
250 | },
251 | props: {
252 | fab: (this.calcStyle && this.calcStyle.fab) ? this.calcStyle.fab : this.fab,
253 | outlined: this.computedOutlined,
254 | rounded: this.computedRounded,
255 | text: this.computedText,
256 | tile: this.computedTile,
257 | large: this.computedLarge,
258 | small: this.computedSmall
259 | },
260 | domProps: {
261 | innerHTML: actValue
262 | },
263 | on: {
264 | click: () => this.changeValue(actValue)
265 | }
266 | })
267 | },
268 | genRow (content: string[]): VNode|VNode[] {
269 | const rowContent: VNode[] = []
270 | // const actButtons = ['+', '±', 'C', '-', '%', 'CE', '*', '1/x', '←', '.', '÷', '=', 'OK']
271 | const actButtons = ['+', '\u00B1', 'C', '-', '%', 'CE', '*', '1/x', '\u2190', '.', '\u00F7', '=', 'OK']
272 | content.map(v => {
273 | if (actButtons.includes(v)) {
274 | rowContent.push(this.genActionsButton(v))
275 | } else {
276 | rowContent.push(this.genNumberButton(v))
277 | }
278 | })
279 | return this.$createElement(VRowA, {
280 | style: {
281 | 'margin-left': '0px',
282 | 'margin-right': '0px'
283 | }
284 | }, rowContent)
285 | },
286 | genResult (): VNode {
287 | return this.$createElement(VTextFieldA, {
288 | ref: 'calcResult',
289 | props: {
290 | outlined: true,
291 | reverse: true,
292 | readonly: true,
293 | value: this.resultNumber,
294 | autofocus: true,
295 | hint: this.textOperand,
296 | persistentHint: true
297 | },
298 | style: {
299 | padding: '12px',
300 | 'font-size': '24px'
301 | }
302 | })
303 | }
304 | },
305 | mounted () {
306 | document.addEventListener('keydown', this.changeValue)
307 | },
308 | beforeDestroy () {
309 | document.removeEventListener('keydown', this.changeValue)
310 | },
311 | render (): VNode {
312 | const layer1 = this.genRow(['7', '8', '9', '+', '\u00B1', 'C'])
313 | const layer2 = this.genRow(['4', '5', '6', '-', '%', 'CE'])
314 | const layer3 = this.genRow(['1', '2', '3', '*', '1/x', '\u2190'])
315 | const layer4 = this.genRow(['0', '00', '.', '\u00F7', '=', 'OK'])
316 | const content = []
317 | content.push(this.genResult())
318 | content.push(layer1, layer2, layer3, layer4)
319 | return this.$createElement(VSheetA, {
320 | attrs: {
321 | tabindex: 0
322 | },
323 | props: {
324 | width: this.computedWidth,
325 | height: this.computedHeight,
326 | elevation: this.elevation,
327 | dark: this.dark
328 | }
329 | }, content)
330 | }
331 |
332 | })
333 |
--------------------------------------------------------------------------------
/src/components/VNumeric/VNumeric.ts:
--------------------------------------------------------------------------------
1 | import Vue, { VNode } from 'vue'
2 | import { VMenuA, VTextFieldA } from '../../shims-vuetify'
3 | import VCalculator from './VCalculator'
4 | import VNumericInput from './VNumericInput'
5 |
6 | interface PosMenuType {
7 | bottom: number;
8 | right: number;
9 | }
10 |
11 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
12 | const VTextFieldProps = ((VTextFieldA as any).options as any).props
13 |
14 | export default Vue.extend({
15 | name: 'v-numeric',
16 | props: {
17 | calcNoTabindex: {
18 | type: Boolean,
19 | default: false
20 | },
21 | min: {
22 | type: Number,
23 | default: -Number.MAX_VALUE
24 | },
25 | max: {
26 | type: Number,
27 | default: Number.MAX_VALUE
28 | },
29 | length: {
30 | type: Number,
31 | default: 10
32 | },
33 | precision: {
34 | type: [Number, String],
35 | default: 0
36 | },
37 | negativeTextColor: {
38 | type: String,
39 | default: 'red'
40 | },
41 | openKey: {
42 | type: String,
43 | default: 'Enter'
44 | },
45 | textColor: {
46 | type: Function,
47 | default: undefined
48 | },
49 | locale: {
50 | type: String,
51 | default: 'en-US'
52 | },
53 | useGrouping: {
54 | type: Boolean,
55 | default: true
56 | },
57 | /* customizing calculator */
58 | elevation: {
59 | type: Number,
60 | default: 0
61 | },
62 | fab: {
63 | type: Boolean,
64 | default: false
65 | },
66 | rounded: {
67 | type: Boolean,
68 | default: false
69 | },
70 | text: {
71 | type: Boolean,
72 | default: false
73 | },
74 | calcIcon: {
75 | type: String,
76 | default: 'mdi-calculator'
77 | },
78 | useCalculator: {
79 | type: Boolean,
80 | default: true
81 | },
82 | calcStyle: {
83 | type: Object,
84 | default: undefined
85 | },
86 | ...VTextFieldProps
87 | },
88 | computed: {
89 | computedPrecision (): number {
90 | return Number(this.$props.precision)
91 | }
92 | },
93 | data: () => ({
94 | internalValue: 0,
95 | isMenuActive: false,
96 | xMenuPos: 0,
97 | yMenuPos: 0
98 | }),
99 | watch: {
100 | value: {
101 | deep: true,
102 | immediate: true,
103 | handler (newVal) {
104 | this.$data.internalValue = Number(newVal)
105 | }
106 | }
107 | },
108 | methods: {
109 | activateCalculator () {
110 | this.isMenuActive = true
111 | },
112 | closeCalculator (val: string|number|undefined) {
113 | this.isMenuActive = false
114 | this.changeValue(val)
115 | },
116 | changeValue (val: string|number|undefined) {
117 | let result: number
118 | if (val) {
119 | if (this.computedPrecision > 0) {
120 | const p = Math.pow(10, this.computedPrecision)
121 | result = Math.round(Number(val) * p) / p
122 | } else {
123 | result = Math.round(Number(val))
124 | }
125 | result = Math.max(Math.min(this.$props.max, result), this.$props.min)
126 | this.internalValue = result
127 | this.$emit('input', this.internalValue)
128 | } else if (val === 0) {
129 | this.internalValue = 0
130 | this.$emit('input', this.internalValue)
131 | }
132 | },
133 | genCalculator (): VNode|undefined {
134 | if (!this.$props.useCalculator) return undefined
135 | return this.$createElement(VCalculator, {
136 | props: {
137 | initialValue: this.internalValue,
138 | locale: this.$props.locale,
139 | useGrouping: this.$props.useGrouping,
140 | negativeTextColor: this.$props.negativeTextColor,
141 | precision: this.computedPrecision,
142 | elevation: this.$props.elevation,
143 | fab: this.$props.fab,
144 | outlined: this.$props.outlined,
145 | rounded: this.$props.rounded,
146 | text: this.$props.text,
147 | dark: this.$props.dark,
148 | dense: this.$props.dense,
149 | isActive: this.isMenuActive,
150 | calcStyle: this.$props.calcStyle
151 | },
152 | on: {
153 | 'return-value': (val: string|number|undefined) => this.closeCalculator(val)
154 | }
155 | })
156 | },
157 | setMenuPosition (rect: PosMenuType) {
158 | this.$data.yMenuPos = rect.bottom
159 | this.$data.xMenuPos = rect.right - 288
160 | },
161 | genInput (): VNode {
162 | const props = Object.assign({}, this.$props)
163 | props.value = this.internalValue
164 | props.precision = this.computedPrecision
165 | return this.$createElement(VNumericInput, {
166 | domProps: {
167 | value: this.internalValue
168 | },
169 | props,
170 | slot: 'activator',
171 | on: {
172 | 'activate-calculator': () => {
173 | this.activateCalculator()
174 | },
175 | 'change-value': (val: string|number|undefined) => this.changeValue(val),
176 | 'resize-numeric-input': (rect: PosMenuType) => this.setMenuPosition(rect),
177 | input: (val: string|number) => { this.internalValue = Number(val) },
178 | change: (val: string) => this.$emit('change', val)
179 | }
180 | })
181 | },
182 | computedWidth (): string {
183 | if (!this.$props.calcStyle) return '288px'
184 | return (this.$props.calcStyle.width === undefined) ? '288px' : this.$props.calcStyle.width
185 | },
186 | computedHeight (): string {
187 | if (!this.$props.calcStyle) return '246px'
188 | return (this.$props.calcStyle.height === undefined) ? '246px' : this.$props.calcStyle.height
189 | }
190 | },
191 | render (): VNode {
192 | // eslint-disable-next-line @typescript-eslint/no-this-alias
193 | const self = this
194 | return this.$createElement(VMenuA, {
195 | props: {
196 | absolute: true,
197 | positionX: this.xMenuPos,
198 | positionY: this.yMenuPos,
199 | closeOnContentClick: false,
200 | value: this.isMenuActive,
201 | dark: this.$props.dark,
202 | dense: this.$props.dense,
203 | width: this.computedWidth,
204 | maxWidth: this.computedWidth(),
205 | height: this.computedHeight,
206 | right: true
207 | },
208 | scopedSlots: {
209 | 'activator' () {
210 | return self.genInput()
211 | }
212 | },
213 | on: {
214 | 'update:return-value': (val: string|number|undefined) => this.closeCalculator(val)
215 | }
216 | }, [
217 | this.genCalculator()
218 | ])
219 | }
220 | })
221 |
--------------------------------------------------------------------------------
/src/components/VNumeric/VNumericInput.ts:
--------------------------------------------------------------------------------
1 | import Vue, { VNode } from 'vue'
2 | import { VIconA, VTextFieldA } from '../../shims-vuetify'
3 |
4 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
5 | const VTextFieldProps = ((VTextFieldA as any).options as any).props
6 |
7 | export default Vue.extend({
8 | name: 'v-numeric-input',
9 | props: {
10 | calcNoTabindex: {
11 | type: Boolean,
12 | default: false
13 | },
14 | min: {
15 | type: Number,
16 | default: -Number.MAX_VALUE
17 | },
18 | max: {
19 | type: Number,
20 | default: Number.MAX_VALUE
21 | },
22 | length: {
23 | type: Number,
24 | default: 10
25 | },
26 | openKey: {
27 | type: String,
28 | default: 'Enter'
29 | },
30 | precision: {
31 | type: Number,
32 | default: 0
33 | },
34 | negativeTextColor: {
35 | type: String,
36 | default: 'red'
37 | },
38 | textColor: {
39 | type: Function,
40 | default: undefined
41 | },
42 | locale: {
43 | type: String,
44 | default: 'en-US'
45 | },
46 | useGrouping: {
47 | type: Boolean,
48 | default: true
49 | },
50 | calcIcon: {
51 | type: String,
52 | default: 'mdi-calculator'
53 | },
54 | value: {
55 | type: [String, Number],
56 | default: 0
57 | },
58 | ...VTextFieldProps
59 | },
60 | data: () => ({
61 | internalValue: 0,
62 | fractDigitsEdited: false,
63 | fractPart: '0',
64 | isFocused: false,
65 | clrValue: false
66 | }),
67 | computed: {
68 | numberFormatter (): Intl.NumberFormat {
69 | return new Intl.NumberFormat(this.$props.locale, {
70 | useGrouping: this.$props.useGrouping,
71 | minimumFractionDigits: this.$props.precision
72 | })
73 | },
74 | computedValue (): string {
75 | if (this.internalValue) {
76 | return (
77 | (this.$props.prefix ? this.$props.prefix : '') +
78 | this.numberFormatter.format(this.internalValue)
79 | )
80 | }
81 | return (
82 | (this.$props.prefix ? this.$props.prefix : '') +
83 | this.numberFormatter.format(0)
84 | )
85 | },
86 | computedColor (): string | undefined {
87 | if (this.internalValue < 0 && this.$props.negativeTextColor) {
88 | return this.$props.negativeTextColor
89 | } else return this.$props.color
90 | }
91 | },
92 | watch: {
93 | value: {
94 | immediate: true,
95 | handler (newVal?: string | number) {
96 | if (!newVal) {
97 | this.internalValue = 0
98 | } else if (typeof newVal === 'string') {
99 | this.internalValue = Number.parseFloat(newVal)
100 | } else {
101 | this.internalValue = newVal
102 | }
103 | },
104 | deep: true
105 | },
106 | internalValue (val) {
107 | this.$emit('change-value', val)
108 | },
109 | computedColor (newVal) {
110 | const input = this.genTextInput()
111 | if (input) {
112 | input.style.color = newVal || null
113 | }
114 | }
115 | },
116 | methods: {
117 | genTextInput () {
118 | const inputs = this.$el.getElementsByTagName('input')
119 | if (inputs && inputs.length > 0) {
120 | return inputs[0]
121 | }
122 | },
123 | clearValue () {
124 | this.internalValue = 0
125 | this.fractPart = '0'
126 | this.fractDigitsEdited = false
127 | this.$nextTick(() => {
128 | if (this.$data.value) {
129 | this.internalValue = this.$data.value
130 | } else {
131 | this.internalValue = 0
132 | }
133 | this.$emit('change-value', this.internalValue)
134 | })
135 | },
136 | activateCalculator () {
137 | if (!this.$props.readonly) {
138 | this.$emit('activate-calculator', this.internalValue)
139 | }
140 | },
141 | keyProcess (keyEvent: KeyboardEvent) {
142 | if (!this.isFocused) return
143 | if (keyEvent.key === 'Tab') return
144 | if (this.$props.readonly) {
145 | keyEvent.preventDefault()
146 | keyEvent.stopPropagation()
147 | return
148 | }
149 | if (keyEvent.key !== 'ArrowLeft' && keyEvent.key !== 'ArrowRight') {
150 | keyEvent.preventDefault()
151 | }
152 | keyEvent.stopPropagation()
153 | if (keyEvent.key === this.$props.openKey) {
154 | this.updateDimensions()
155 | this.activateCalculator()
156 | return
157 | } else if (keyEvent.key === 'Delete') {
158 | this.clearValue()
159 | return
160 | }
161 | const numericButtons = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']
162 | let strVal = Math.trunc(this.internalValue).toString()
163 | if (numericButtons.includes(keyEvent.key)) {
164 | if (this.fractDigitsEdited) {
165 | this.fractPart += keyEvent.key.toString()
166 | this.fractPart = this.fractPart.substr(
167 | Math.max(0, this.fractPart.length - this.$props.precision),
168 | this.$props.precision
169 | )
170 | } else {
171 | if (this.clrValue) {
172 | this.fractPart = '00'
173 | strVal = '0'
174 | this.clrValue = false
175 | }
176 | if (strVal === '0' && keyEvent.key !== '0') {
177 | strVal = keyEvent.key
178 | } else if (strVal !== '0') {
179 | strVal += keyEvent.key
180 | }
181 | }
182 | } else if (keyEvent.key === '-') {
183 | if (strVal.startsWith('-')) strVal = strVal.replace('-', '')
184 | else strVal = '-' + strVal
185 | } else if (keyEvent.key === 'Backspace') {
186 | if (this.fractDigitsEdited) {
187 | this.fractPart =
188 | this.fractPart.length <= 1
189 | ? '0'
190 | : this.fractPart.substring(0, this.fractPart.length - 1)
191 | } else {
192 | if (strVal.length === 2 && strVal.startsWith('-')) {
193 | strVal = '0'
194 | } else {
195 | strVal =
196 | strVal.length <= 1 ? '0' : strVal.substring(0, strVal.length - 1)
197 | }
198 | }
199 | } else if ([',', '.'].includes(keyEvent.key)) {
200 | if (this.$props.precision > 0) {
201 | this.fractDigitsEdited = !this.fractDigitsEdited
202 | }
203 | }
204 | if (this.$props.precision > 0) {
205 | strVal = strVal + '.' + this.fractPart
206 | }
207 | let result = Number(strVal)
208 | if (this.$props.precision > 0) {
209 | const p = Math.pow(10, this.$props.precision)
210 | result = Math.round(Number(result) * p) / p
211 | }
212 | result = result = Math.max(
213 | Math.min(this.$props.max, result),
214 | this.$props.min
215 | )
216 | this.internalValue = result
217 | },
218 | updateDimensions () {
219 | const rect = this.$el.getBoundingClientRect()
220 | this.$emit('resize-numeric-input', {
221 | bottom: rect.bottom,
222 | right: rect.right
223 | })
224 | },
225 | setFocus (val: boolean) {
226 | this.isFocused = val
227 | }
228 | },
229 | mounted () {
230 | const input = this.genTextInput()
231 | if (input) {
232 | input.setAttribute('type', 'text')
233 | input.style.textAlign = 'right'
234 | }
235 | window.addEventListener('resize', this.updateDimensions)
236 | window.addEventListener('load', this.updateDimensions)
237 | },
238 | beforeDestroy () {
239 | window.removeEventListener('resize', this.updateDimensions)
240 | window.removeEventListener('load', this.updateDimensions)
241 | },
242 | render (createElement): VNode {
243 | const currentProps = Object.assign({}, this.$props)
244 | currentProps.value = this.computedValue
245 | if (currentProps.prefix) {
246 | currentProps.prefix = undefined
247 | }
248 | return createElement(VTextFieldA, {
249 | domProps: {
250 | value: this.internalValue
251 | },
252 | props: currentProps,
253 | on: {
254 | keydown: this.keyProcess,
255 | focus: () => {
256 | this.setFocus(true)
257 | this.fractDigitsEdited = false
258 | this.clrValue = true
259 | },
260 | blur: () => this.setFocus(false),
261 | 'click:clear': this.clearValue,
262 | input: (val: string) => {
263 | this.internalValue = Number(val)
264 | },
265 | change: (val: string) => this.$emit('change', val)
266 | }
267 | }, [...(this.$props.useCalculator
268 | ? [
269 | createElement(VIconA, {
270 | slot: 'append',
271 | attrs: {
272 | tabindex: this.$props.calcNoTabindex ? -1 : 0
273 | },
274 | on: {
275 | click: () => {
276 | this.updateDimensions()
277 | this.activateCalculator()
278 | }
279 | }
280 | }, this.$props.calcIcon)
281 | ]
282 | : []
283 | )])
284 | }
285 | })
286 |
--------------------------------------------------------------------------------
/src/components/index.ts:
--------------------------------------------------------------------------------
1 | import { VueConstructor, Component } from 'vue'
2 | import VNumeric from './VNumeric/VNumeric'
3 | import VNumericInput from './VNumeric/VNumericInput'
4 | import VCalculator from './VNumeric/VCalculator'
5 |
6 | export interface VuetifyNumericUseOptions {
7 | components?: Record;
8 | }
9 |
10 | const defaultComponents = {
11 | 'v-numeric': VNumeric,
12 | 'v-numeric-input': VNumericInput,
13 | 'v-calculator': VCalculator
14 | }
15 |
16 | function install (v: VueConstructor, args?: VuetifyNumericUseOptions): VueConstructor {
17 | const components = args ? args.components : defaultComponents
18 | for (const key in components) {
19 | const component = components[key]
20 | if (component) {
21 | v.component(key, component as typeof v)
22 | }
23 | }
24 | return v
25 | }
26 |
27 | export default install
28 |
29 | export {
30 | VNumeric,
31 | VNumericInput,
32 | VCalculator
33 | }
34 |
35 | if (typeof window !== 'undefined' && window.Vue) {
36 | window.Vue.use(install)
37 | }
38 |
--------------------------------------------------------------------------------
/src/main.ts:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import App from './App.vue'
3 | import vuetify from './plugins/vuetify'
4 |
5 | Vue.config.productionTip = false
6 |
7 | new Vue({
8 | vuetify,
9 | render: h => h(App)
10 | }).$mount('#app')
11 |
--------------------------------------------------------------------------------
/src/plugins/vuetify.ts:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Vuetify from 'vuetify/lib'
3 |
4 | Vue.use(Vuetify)
5 |
6 | export default new Vuetify({
7 | })
8 |
--------------------------------------------------------------------------------
/src/shims-tsx.d.ts:
--------------------------------------------------------------------------------
1 | import Vue, { VNode } from 'vue'
2 |
3 | declare global {
4 | namespace JSX {
5 | // tslint:disable no-empty-interface
6 | interface Element extends VNode {}
7 | // tslint:disable no-empty-interface
8 | interface ElementClass extends Vue {}
9 | interface IntrinsicElements {
10 | [elem: string]: any;
11 | }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/shims-vue.d.ts:
--------------------------------------------------------------------------------
1 | declare module '*.vue' {
2 | import Vue from 'vue'
3 | export default Vue
4 | }
5 |
--------------------------------------------------------------------------------
/src/shims-vuetify.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/no-explicit-any */
2 | import Vue, { VueConstructor } from 'vue'
3 |
4 | import {
5 | VTextField, VBtn, VRow, VSheet, VMenu, VIcon
6 | } from 'vuetify/lib'
7 |
8 | function VueComponent (component: any|undefined, name: string): VueConstructor {
9 | if (component) return component as VueConstructor
10 | return (Vue as any).options.components[name] as VueConstructor
11 | }
12 |
13 | let VTextFieldC
14 | let VBtnC
15 | let VRowC
16 | let VSheetC
17 | let VMenuC
18 | let VIconC
19 |
20 | try {
21 | VBtnC = VBtn
22 | VMenuC = VMenu
23 | VTextFieldC = VTextField
24 | VRowC = VRow
25 | VSheetC = VSheet
26 | VIconC = VIcon
27 | } catch (error) {
28 | VBtnC = undefined
29 | VMenuC = undefined
30 | VTextFieldC = undefined
31 | VRowC = undefined
32 | VSheetC = undefined
33 | VIconC = undefined
34 | }
35 |
36 | export const VBtnA = VueComponent(VBtnC, 'VBtn')
37 | export const VMenuA = VueComponent(VMenuC, 'VMenu')
38 | export const VTextFieldA = VueComponent(VTextFieldC, 'VTextField')
39 | export const VRowA = VueComponent(VRowC, 'VRow')
40 | export const VSheetA = VueComponent(VSheetC, 'VSheet')
41 | export const VIconA = VueComponent(VIconC, 'VIcon')
42 |
--------------------------------------------------------------------------------
/testcdn/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/tests/unit/VNumeric.spec.ts:
--------------------------------------------------------------------------------
1 | // Libraries
2 | import Vue from 'vue'
3 | import Vuetify from 'vuetify/lib'
4 | // Utilities
5 | import {
6 | mount
7 | } from '@vue/test-utils'
8 | // component to be tested
9 | import VNumeric from '@/components/VNumeric/VNumeric'
10 |
11 | Vue.use(Vuetify)
12 |
13 | const vuetify = new Vuetify({})
14 |
15 | const value = 1258
16 | const precision = 2
17 | const prefix = '$'
18 |
19 | describe('VNumeric.js', () => {
20 | it('renders', () => {
21 | const wrapper = mount(VNumeric, {
22 | vuetify,
23 | propsData: {
24 | value,
25 | useGrouping: true,
26 | precision,
27 | prefix
28 | }
29 | })
30 | // wrapper.nextTick()
31 | // const input = wrapper.element.getElementsByTagName('input')[0]
32 | // console.log(input)
33 | // // expect(input.innerText).toMatch('$1,258.00')
34 | expect(wrapper.html()).toMatchSnapshot()
35 | })
36 | })
37 |
--------------------------------------------------------------------------------
/tests/unit/__snapshots__/VNumeric.spec.ts.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`VNumeric.js renders 1`] = `
4 |
22 | `;
23 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "esnext",
4 | "module": "esnext",
5 | "strict": true,
6 | "jsx": "preserve",
7 | "importHelpers": true,
8 | "moduleResolution": "node",
9 | "esModuleInterop": true,
10 | "allowSyntheticDefaultImports": true,
11 | "sourceMap": true,
12 | "baseUrl": ".",
13 | "types": [
14 | "webpack-env",
15 | "vuetify",
16 | "jest"
17 | ],
18 | "paths": {
19 | "@/*": [
20 | "src/*"
21 | ]
22 | },
23 | "lib": [
24 | "esnext",
25 | "dom",
26 | "dom.iterable",
27 | "scripthost"
28 | ]
29 | },
30 | "include": [
31 | "src/**/*.ts",
32 | "src/**/*.tsx",
33 | "src/**/*.vue",
34 | "tests/**/*.ts",
35 | "tests/**/*.tsx"
36 | ],
37 | "exclude": [
38 | "node_modules"
39 | ]
40 | }
41 |
--------------------------------------------------------------------------------
/vue.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | transpileDependencies: [
3 | 'vuetify'
4 | ]
5 | }
6 |
7 | module.exports = {
8 | transpileDependencies: [
9 | 'vuetify'
10 | ],
11 | publicPath: process.env.NODE_ENV === 'production'
12 | ? '/vuetify-numeric/'
13 | : '/',
14 | configureWebpack: {
15 | ...(process.env.NODE_ENV === 'production'
16 | ? {
17 | externals: {
18 | 'vuetify/lib': 'vuetify/lib'
19 | }
20 | }
21 | : {})
22 | }
23 | }
24 |
--------------------------------------------------------------------------------