├── .browserslistrc
├── .eslintignore
├── .stylelintignore
├── babel.config.js
├── .husky
├── pre-commit
└── commit-msg
├── .gitignore
├── src
├── index.ts
├── shims.d.ts
├── README.md
└── vue-number-input.vue
├── commitlint.config.js
├── postcss.config.js
├── lint-staged.config.js
├── tsconfig.eslint.json
├── stylelint.config.js
├── tsconfig.json
├── docs
├── index.ts
├── components
│ └── demo-block.vue
├── index.html
└── app.vue
├── jest.config.js
├── .github
└── workflows
│ ├── ci.yml
│ └── deploy.yml
├── .eslintrc.js
├── LICENSE
├── rollup.config.js
├── tests
├── others.spec.ts
├── events.spec.ts
├── methods.spec.ts
└── props.spec.ts
├── webpack.config.js
├── README.md
├── package.json
└── CHANGELOG.md
/.browserslistrc:
--------------------------------------------------------------------------------
1 | defaults
2 | not ie 11
3 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | *.local*
2 | coverage
3 | dist
4 | node_modules
5 |
--------------------------------------------------------------------------------
/.stylelintignore:
--------------------------------------------------------------------------------
1 | *.local*
2 | coverage
3 | dist
4 | node_modules
5 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: ['@babel/preset-env'],
3 | };
4 |
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | . "$(dirname "$0")/_/husky.sh"
3 |
4 | npx lint-staged
5 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.local*
2 | *.log
3 | *.map
4 | .DS_Store
5 | coverage
6 | dist
7 | node_modules
8 |
--------------------------------------------------------------------------------
/.husky/commit-msg:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | . "$(dirname "$0")/_/husky.sh"
3 |
4 | npx commitlint --edit $1
5 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | import VueNumberInput from './vue-number-input.vue';
2 |
3 | export default VueNumberInput;
4 |
--------------------------------------------------------------------------------
/commitlint.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: [
3 | '@commitlint/config-conventional',
4 | ],
5 | };
6 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | stylelint: {
4 | fix: true,
5 | },
6 | },
7 | };
8 |
--------------------------------------------------------------------------------
/lint-staged.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | '*.{js,ts,vue}': 'eslint --fix',
3 | '*.{css,scss,vue}': 'stylelint --fix',
4 | };
5 |
--------------------------------------------------------------------------------
/tsconfig.eslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig",
3 | "include": [
4 | "*.js",
5 | ".*.js",
6 | "docs/**/*",
7 | "src/**/*",
8 | "tests/**/*"
9 | ]
10 | }
11 |
--------------------------------------------------------------------------------
/src/shims.d.ts:
--------------------------------------------------------------------------------
1 | declare module '*.vue' {
2 | const content: any;
3 |
4 | export default content;
5 | }
6 |
7 | declare module '*.md' {
8 | const content: any;
9 |
10 | export default content;
11 | }
12 |
--------------------------------------------------------------------------------
/stylelint.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: 'stylelint-config-recommended-vue/scss',
3 | plugins: [
4 | 'stylelint-order',
5 | ],
6 | rules: {
7 | 'no-descending-specificity': null,
8 | 'no-empty-source': null,
9 | 'order/properties-alphabetical-order': true,
10 | },
11 | };
12 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "allowSyntheticDefaultImports": true,
4 | "moduleResolution": "node",
5 | "resolveJsonModule": true,
6 | "strict": true,
7 | "target": "esnext"
8 | },
9 | "include": [
10 | "src/**/*",
11 | "docs/**/*",
12 | "tests/**/*"
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/docs/index.ts:
--------------------------------------------------------------------------------
1 | import { createApp } from 'vue';
2 | import App from './app.vue';
3 | import DemoBlock from './components/demo-block.vue';
4 | import VueNumberInput from '../src';
5 |
6 | const app = createApp(App);
7 |
8 | app.component(VueNumberInput.name, VueNumberInput);
9 | app.component(DemoBlock.name, DemoBlock);
10 | app.mount('#app');
11 |
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | preset: 'ts-jest',
3 | collectCoverage: true,
4 | coverageDirectory: 'coverage',
5 | coverageReporters: ['html', 'lcov', 'text'],
6 | moduleFileExtensions: ['js', 'ts', 'vue'],
7 | transform: {
8 | '^.+\\.js$': 'babel-jest',
9 | '^.+\\.vue$': '@vue/vue3-jest',
10 | },
11 | testEnvironment: 'jsdom',
12 | testMatch: ['**/tests/*.spec.ts'],
13 | };
14 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | push:
5 | branches: [main]
6 | pull_request:
7 | branches: [main]
8 |
9 | jobs:
10 | build:
11 | runs-on: ubuntu-latest
12 |
13 | steps:
14 | - uses: actions/checkout@v2
15 | - uses: actions/setup-node@v2
16 | with:
17 | node-version: 16
18 | - run: npm install
19 | - run: npm run lint
20 | - run: npm run build
21 | - run: npm test
22 | - run: npm run test:coverage
23 |
--------------------------------------------------------------------------------
/docs/components/demo-block.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
14 |
15 |
33 |
--------------------------------------------------------------------------------
/.github/workflows/deploy.yml:
--------------------------------------------------------------------------------
1 | name: Deploy
2 |
3 | on:
4 | push:
5 | tags:
6 | - v2.*
7 | workflow_dispatch:
8 |
9 | jobs:
10 | build:
11 | runs-on: ubuntu-latest
12 |
13 | steps:
14 | - uses: actions/checkout@v2
15 | - uses: actions/setup-node@v2
16 | with:
17 | node-version: 16
18 | - run: npm install
19 | - run: npm run build:docs
20 | - run: |
21 | cd docs/dist
22 | git init
23 | git config user.name "${{ github.actor }}"
24 | git config user.email "${{ github.actor }}@users.noreply.github.com"
25 | git add --all
26 | git commit --message "♥"
27 | git push --force https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git master:gh-pages
28 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | env: {
4 | browser: true,
5 | node: true,
6 | },
7 | extends: [
8 | 'airbnb-typescript/base',
9 | 'plugin:@typescript-eslint/recommended',
10 | 'plugin:vue/vue3-recommended',
11 | ],
12 | parser: 'vue-eslint-parser',
13 | parserOptions: {
14 | parser: '@typescript-eslint/parser',
15 | project: 'tsconfig.eslint.json',
16 | sourceType: 'module',
17 | extraFileExtensions: ['.vue'],
18 | },
19 | plugins: [
20 | '@typescript-eslint',
21 | 'import',
22 | 'vue',
23 | ],
24 | rules: {
25 | '@typescript-eslint/no-explicit-any': 'off',
26 | '@typescript-eslint/no-var-requires': 'off',
27 | 'no-restricted-properties': 'off',
28 | },
29 | overrides: [
30 | {
31 | files: ['tests/**/*.ts'],
32 | env: {
33 | jest: true,
34 | },
35 | },
36 | ],
37 | };
38 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright 2018-present Chen Fengyuan
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
13 | all 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
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/docs/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | vue-number-input
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import createBanner from 'create-banner';
2 | import postcss from 'rollup-plugin-postcss';
3 | import typescript from 'rollup-plugin-typescript2';
4 | import vue from 'rollup-plugin-vue';
5 | import { pascalCase } from 'change-case';
6 | import { terser } from 'rollup-plugin-terser';
7 | import pkg from './package.json';
8 |
9 | const name = pascalCase(pkg.name.replace(/^.+\//, ''));
10 | const banner = createBanner({
11 | data: {
12 | year: '2018-present',
13 | },
14 | template: 'inline',
15 | });
16 |
17 | export default ['umd', 'esm'].map((format) => ({
18 | input: 'src/vue-number-input.vue',
19 | output: ['development', 'production'].map((mode) => {
20 | const output = {
21 | banner,
22 | format,
23 | name,
24 | file: pkg.main,
25 | globals: {
26 | vue: 'Vue',
27 | },
28 | };
29 |
30 | if (format === 'esm') {
31 | output.file = pkg.module;
32 | }
33 |
34 | if (mode === 'production') {
35 | output.compact = true;
36 | output.file = output.file.replace(/(\.js)$/, '.min$1');
37 | output.plugins = [
38 | terser(),
39 | ];
40 | }
41 |
42 | return output;
43 | }),
44 | external: Object.keys(pkg.peerDependencies),
45 | plugins: [
46 | typescript({
47 | tsconfigOverride: {
48 | compilerOptions: {
49 | declaration: format === 'esm',
50 | },
51 | exclude: [
52 | 'src/index.ts',
53 | 'docs',
54 | 'tests',
55 | ],
56 | },
57 | }),
58 | vue({
59 | preprocessStyles: true,
60 | }),
61 | postcss({
62 | extensions: ['.css', '.scss'],
63 | minimize: true,
64 | }),
65 | ],
66 | }));
67 |
--------------------------------------------------------------------------------
/tests/others.spec.ts:
--------------------------------------------------------------------------------
1 | import { mount } from '@vue/test-utils';
2 | import VueNumberInput from '../src';
3 |
4 | describe('others', () => {
5 | it('should fix the `0.30000000000000004` problem', (done) => {
6 | const wrapper = mount({
7 | components: {
8 | VueNumberInput,
9 | },
10 | data() {
11 | return {
12 | value: 0.1,
13 | };
14 | },
15 | template: '',
16 | });
17 |
18 | expect(wrapper.vm.value).toBe(0.1);
19 | wrapper.get('.vue-number-input__button--plus').trigger('click').then(() => {
20 | expect(wrapper.vm.value).toBe(0.3);
21 | done();
22 | });
23 | });
24 |
25 | it('should update the model value when value changed', (done) => {
26 | const wrapper = mount({
27 | components: {
28 | VueNumberInput,
29 | },
30 | data() {
31 | return {
32 | value: 0,
33 | };
34 | },
35 | template: '',
36 | });
37 |
38 | expect(wrapper.vm.value).toBe(0);
39 | wrapper.get('.vue-number-input__input').setValue(1).then(() => {
40 | expect(wrapper.vm.value).toBe(1);
41 | done();
42 | });
43 | });
44 |
45 | it('should not update the value when paste nothing', (done) => {
46 | const wrapper = mount({
47 | components: {
48 | VueNumberInput,
49 | },
50 | data() {
51 | return {
52 | value: 0,
53 | };
54 | },
55 | template: '',
56 | });
57 |
58 | expect(wrapper.vm.value).toBe(0);
59 | wrapper.get('.vue-number-input__input').trigger('paste').then(() => {
60 | expect(wrapper.vm.value).toBe(0);
61 | done();
62 | });
63 | });
64 | });
65 |
--------------------------------------------------------------------------------
/tests/events.spec.ts:
--------------------------------------------------------------------------------
1 | import { mount } from '@vue/test-utils';
2 | import VueNumberInput from '../src';
3 |
4 | describe('events', () => {
5 | describe('custom', () => {
6 | it('should trigger the custom `update:model-value` event', (done) => {
7 | const wrapper = mount({
8 | components: {
9 | VueNumberInput,
10 | },
11 | methods: {
12 | onModelValueChange(newValue: number) {
13 | expect(newValue).toBe(1);
14 | done();
15 | },
16 | },
17 | template: '',
18 | });
19 |
20 | wrapper.get('.vue-number-input__input').setValue('1');
21 | });
22 | });
23 |
24 | describe('native', () => {
25 | it('should trigger the native `change` event', (done) => {
26 | const wrapper = mount({
27 | components: {
28 | VueNumberInput,
29 | },
30 | methods: {
31 | onChange(event: Event) {
32 | expect(event.type).toBe('change');
33 | expect((event.target as HTMLInputElement).value).toBe('1');
34 | done();
35 | },
36 | },
37 | template: '',
38 | });
39 |
40 | wrapper.get('.vue-number-input__input').setValue('1');
41 | });
42 |
43 | it('should trigger the native `input` event', (done) => {
44 | const wrapper = mount({
45 | components: {
46 | VueNumberInput,
47 | },
48 | methods: {
49 | onInput(event: Event) {
50 | expect(event.type).toBe('input');
51 | expect((event.target as HTMLInputElement).value).toBe('1');
52 | done();
53 | },
54 | },
55 | template: '',
56 | });
57 |
58 | wrapper.get('.vue-number-input__input').setValue('1');
59 | });
60 | });
61 | });
62 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const webpack = require('webpack');
3 | const HtmlWebpackPlugin = require('html-webpack-plugin');
4 | const VueLoaderPlugin = require('vue-loader/dist/plugin').default;
5 | const MiniCssExtractPlugin = require('mini-css-extract-plugin');
6 |
7 | module.exports = (env) => ({
8 | mode: env.production ? 'production' : 'development',
9 | entry: './docs',
10 | output: {
11 | path: path.resolve(__dirname, './docs/dist'),
12 | },
13 | module: {
14 | rules: [
15 | {
16 | test: /\.js$/,
17 | use: 'babel-loader',
18 | },
19 | {
20 | test: /\.ts$/,
21 | loader: 'ts-loader',
22 | options: {
23 | appendTsSuffixTo: [/\.vue$/],
24 | },
25 | },
26 | {
27 | test: /\.vue$/,
28 | loader: 'vue-loader',
29 | },
30 | {
31 | test: /\.scss$/,
32 | use: [
33 | MiniCssExtractPlugin.loader,
34 | 'css-loader',
35 | 'sass-loader',
36 | ],
37 | },
38 | {
39 | test: /\.md$/,
40 | use: [
41 | 'vue-loader',
42 | {
43 | loader: 'markdown-to-vue-loader',
44 | options: {
45 | componentWrapper: '',
46 | tableClass: 'table',
47 | tableWrapper: '',
48 | },
49 | },
50 | ],
51 | },
52 | ],
53 | },
54 | plugins: [
55 | new HtmlWebpackPlugin({
56 | filename: 'index.html',
57 | template: './docs/index.html',
58 | }),
59 | new MiniCssExtractPlugin(),
60 | new VueLoaderPlugin(),
61 | new webpack.DefinePlugin({
62 | __VUE_OPTIONS_API__: true,
63 | __VUE_PROD_DEVTOOLS__: false,
64 | }),
65 | ],
66 | externals: env.production ? {
67 | vue: 'Vue',
68 | } : {},
69 | resolve: {
70 | alias: {
71 | vue$: 'vue/dist/vue.esm-bundler',
72 | },
73 | extensions: ['.js', '.json', '.ts', '.d.ts', '.vue'],
74 | },
75 | });
76 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # vue-number-input
2 |
3 | [](https://codecov.io/gh/fengyuanchen/vue-number-input) [](https://www.npmjs.com/package/@chenfengyuan/vue-number-input) [](https://www.npmjs.com/package/@chenfengyuan/vue-number-input) [](https://unpkg.com/@chenfengyuan/vue-number-input/dist/vue-number-input.js)
4 |
5 | > Number input component for Vue 3. For Vue 2, check out the [`v1`](https://github.com/fengyuanchen/vue-number-input/tree/v1) branch.
6 |
7 | - [Docs](src/README.md)
8 | - [Demo](https://fengyuanchen.github.io/vue-number-input)
9 |
10 | ## Main npm package files
11 |
12 | ```text
13 | dist/
14 | ├── vue-number-input.js (UMD, default)
15 | ├── vue-number-input.min.js (UMD, compressed)
16 | ├── vue-number-input.esm.js (ECMAScript Module)
17 | ├── vue-number-input.esm.min.js (ECMAScript Module, compressed)
18 | └── vue-number-input.d.ts (TypeScript Declaration File)
19 | ```
20 |
21 | ## Getting started
22 |
23 | ### Installation
24 |
25 | Using npm:
26 |
27 | ```shell
28 | npm install vue@3 @chenfengyuan/vue-number-input@2
29 | ```
30 |
31 | Using pnpm:
32 |
33 | ```shell
34 | pnpm add vue@3 @chenfengyuan/vue-number-input@2
35 | ```
36 |
37 | Using Yarn:
38 |
39 | ```shell
40 | yarn add vue@3 @chenfengyuan/vue-number-input@2
41 | ```
42 |
43 | Using CDN:
44 |
45 | ```html
46 |
47 |
48 | ```
49 |
50 | ### Usage
51 |
52 | ```js
53 | import { createApp } from 'vue';
54 | import VueNumberInput from '@chenfengyuan/vue-number-input';
55 |
56 | const app = createApp({});
57 |
58 | app.component(VueNumberInput.name, VueNumberInput);
59 | ```
60 |
61 | ```html
62 |
63 | ```
64 |
65 | ## Browser support
66 |
67 | Same as Vue 3.
68 |
69 | ## Versioning
70 |
71 | Maintained under the [Semantic Versioning guidelines](https://semver.org/).
72 |
73 | ## License
74 |
75 | [MIT](https://opensource.org/licenses/MIT) © [Chen Fengyuan](https://chenfengyuan.com/)
76 |
--------------------------------------------------------------------------------
/tests/methods.spec.ts:
--------------------------------------------------------------------------------
1 | import { mount } from '@vue/test-utils';
2 | import VueNumberInput from '../src';
3 |
4 | describe('methods', () => {
5 | describe('increase', () => {
6 | it('should increase the number', () => {
7 | const wrapper = mount(VueNumberInput);
8 |
9 | expect(wrapper.vm.value).toBeNaN();
10 | wrapper.vm.increase();
11 | expect(wrapper.vm.value).toBe(1);
12 | });
13 |
14 | it('should not increase the number when the current value is equal to the maximum value', () => {
15 | const wrapper = mount(VueNumberInput, {
16 | props: {
17 | max: 0,
18 | modelValue: 0,
19 | },
20 | });
21 |
22 | expect(wrapper.vm.value).toBe(0);
23 | wrapper.vm.increase();
24 | expect(wrapper.vm.value).toBe(0);
25 | });
26 | });
27 |
28 | describe('decrease', () => {
29 | it('should decrease the number', () => {
30 | const wrapper = mount(VueNumberInput);
31 |
32 | expect(wrapper.vm.value).toBeNaN();
33 | wrapper.vm.decrease();
34 | expect(wrapper.vm.value).toBe(-1);
35 | });
36 |
37 | it('should not decrease the number when the current value is equal to the maximum value', () => {
38 | const wrapper = mount(VueNumberInput, {
39 | props: {
40 | min: 0,
41 | modelValue: 0,
42 | },
43 | });
44 |
45 | expect(wrapper.vm.value).toBe(0);
46 | wrapper.vm.decrease();
47 | expect(wrapper.vm.value).toBe(0);
48 | });
49 | });
50 |
51 | describe('setValue', () => {
52 | it('should change the value', () => {
53 | const wrapper = mount(VueNumberInput);
54 |
55 | expect(wrapper.vm.value).toBeNaN();
56 | wrapper.vm.setValue(1);
57 | expect(wrapper.vm.value).toBe(1);
58 | });
59 |
60 | it('should transform the given value when it is less than the minimum value', () => {
61 | const wrapper = mount(VueNumberInput, {
62 | props: {
63 | min: 0,
64 | },
65 | });
66 |
67 | expect(wrapper.vm.value).toBeNaN();
68 | wrapper.vm.setValue(-1);
69 | expect(wrapper.vm.value).toBe(0);
70 | });
71 |
72 | it('should transform the given value when it is greater than the maximum value', () => {
73 | const wrapper = mount(VueNumberInput, {
74 | props: {
75 | max: 0,
76 | },
77 | });
78 |
79 | expect(wrapper.vm.value).toBeNaN();
80 | wrapper.vm.setValue(1);
81 | expect(wrapper.vm.value).toBe(0);
82 | });
83 |
84 | it('should not transform the given value when the maximum value is less than the minimum value', () => {
85 | const wrapper = mount(VueNumberInput, {
86 | props: {
87 | max: -10,
88 | min: 10,
89 | },
90 | });
91 |
92 | expect(wrapper.vm.value).toBeNaN();
93 | wrapper.vm.setValue(1);
94 | expect(wrapper.vm.value).toBe(1);
95 | });
96 | });
97 | });
98 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@chenfengyuan/vue-number-input",
3 | "version": "2.0.1",
4 | "description": "Number input component for Vue 3.",
5 | "main": "dist/vue-number-input.js",
6 | "module": "dist/vue-number-input.esm.js",
7 | "types": "dist/vue-number-input.d.ts",
8 | "files": [
9 | "dist"
10 | ],
11 | "scripts": {
12 | "build": "rollup -c --environment BUILD:production",
13 | "build:docs": "webpack --env production",
14 | "build:types": "move-file dist/vue-number-input.vue.d.ts dist/vue-number-input.d.ts",
15 | "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 0",
16 | "clean": "del-cli dist",
17 | "lint": "npm run lint:js && npm run lint:css",
18 | "lint:css": "stylelint **/*.{css,scss,vue} --fix",
19 | "lint:js": "eslint . --ext .js,.ts,.vue --fix",
20 | "prepare": "husky install",
21 | "release": "npm run clean && npm run lint && npm run build && npm run build:types && npm run build:docs && npm test && npm run changelog",
22 | "serve": "webpack serve --hot --open",
23 | "start": "npm run serve",
24 | "test": "jest",
25 | "test:coverage": "cat coverage/lcov.info | codecov"
26 | },
27 | "repository": {
28 | "type": "git",
29 | "url": "https://github.com/fengyuanchen/vue-number-input.git"
30 | },
31 | "keywords": [
32 | "number",
33 | "input",
34 | "vue",
35 | "vue3",
36 | "vue-component",
37 | "front-end",
38 | "web"
39 | ],
40 | "author": "Chen Fengyuan (https://chenfengyuan.com/)",
41 | "license": "MIT",
42 | "bugs": "https://github.com/fengyuanchen/vue-number-input/issues",
43 | "homepage": "https://fengyuanchen.github.io/vue-number-input",
44 | "devDependencies": {
45 | "@babel/core": "^7.17.2",
46 | "@babel/preset-env": "^7.16.11",
47 | "@commitlint/cli": "^16.2.1",
48 | "@commitlint/config-conventional": "^16.2.1",
49 | "@types/jest": "^27.4.0",
50 | "@typescript-eslint/eslint-plugin": "^5.11.0",
51 | "@typescript-eslint/parser": "^5.11.0",
52 | "@vue/test-utils": "^2.0.0-rc.18",
53 | "@vue/vue3-jest": "^27.0.0-alpha.4",
54 | "babel-jest": "^27.5.1",
55 | "babel-loader": "^8.2.3",
56 | "change-case": "^4.1.2",
57 | "codecov": "^3.8.3",
58 | "conventional-changelog-cli": "^2.2.2",
59 | "create-banner": "^2.0.0",
60 | "css-loader": "^6.6.0",
61 | "del-cli": "^4.0.1",
62 | "eslint": "^8.9.0",
63 | "eslint-config-airbnb-typescript": "^16.1.0",
64 | "eslint-plugin-import": "^2.25.4",
65 | "eslint-plugin-vue": "^8.4.1",
66 | "html-webpack-plugin": "^5.5.0",
67 | "husky": "^7.0.4",
68 | "jest": "^27.5.1",
69 | "lint-staged": "^12.3.3",
70 | "markdown-to-vue-loader": "^3.1.3",
71 | "mini-css-extract-plugin": "^2.5.3",
72 | "move-file-cli": "^3.0.0",
73 | "postcss": "^8.4.6",
74 | "rollup": "^2.67.2",
75 | "rollup-plugin-postcss": "^4.0.2",
76 | "rollup-plugin-terser": "^7.0.2",
77 | "rollup-plugin-typescript2": "^0.31.2",
78 | "rollup-plugin-vue": "^6.0.0",
79 | "sass": "^1.49.7",
80 | "sass-loader": "^12.4.0",
81 | "style-loader": "^3.3.1",
82 | "stylelint": "^14.5.0",
83 | "stylelint-config-recommended-scss": "^5.0.2",
84 | "stylelint-config-recommended-vue": "^1.3.0",
85 | "stylelint-order": "^5.0.0",
86 | "ts-jest": "^27.1.3",
87 | "ts-loader": "^9.2.6",
88 | "tslib": "^2.3.1",
89 | "typescript": "^4.5.5",
90 | "vue": "^3.2.31",
91 | "vue-loader": "^17.0.0",
92 | "webpack": "^5.68.0",
93 | "webpack-cli": "^4.9.2",
94 | "webpack-dev-server": "^4.7.4"
95 | },
96 | "peerDependencies": {
97 | "vue": "^3.0.0"
98 | },
99 | "publishConfig": {
100 | "access": "public"
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/src/README.md:
--------------------------------------------------------------------------------
1 | # Number Input
2 |
3 | > Number input with optional controls.
4 |
5 | ## Basic usage
6 |
7 | ```html
8 |
9 | Value: {{ value }}
10 |
11 |
12 |
13 |
22 | ```
23 |
24 | ## Step
25 |
26 | ```html
27 |
28 |
29 |
30 | ```
31 |
32 | ## Inline
33 |
34 | ```html
35 |
36 |
37 |
38 |
39 |
40 |
41 | ```
42 |
43 | ## Center number
44 |
45 | ```html
46 |
47 |
48 |
49 | ```
50 |
51 | ## Sizes
52 |
53 | ```html
54 |
55 |
56 |
57 |
58 |
59 |
60 |
65 | ```
66 |
67 | ## Without controls
68 |
69 | ```html
70 |
71 |
72 |
73 |
74 |
75 |
76 |
81 | ```
82 |
83 | ## Rounded
84 |
85 | ```html
86 |
87 |
88 |
89 | ```
90 |
91 | ## Not inputtable
92 |
93 | The input is not inputtable, but still allow to change the value by controls.
94 |
95 | ```html
96 |
97 |
98 |
99 | ```
100 |
101 | ## Readonly
102 |
103 | ```html
104 |
105 |
106 |
107 | ```
108 |
109 | ## Disabled
110 |
111 | ```html
112 |
113 |
114 |
115 | ```
116 |
117 | ## Customize attributes for the input element
118 |
119 | ```html
120 |
121 |
122 |
123 | ```
124 |
125 | ## Props
126 |
127 | | Name | Type | Default | Options | Description |
128 | | --- | --- | --- | --- | --- |
129 | | attrs | `Object` | - | - | Specify attributes for the built-in input element. |
130 | | center | `boolean` | `false` | - | Indicate if the number is center or not. |
131 | | controls | `boolean` | `false` | - | Indicate if the controls is visible or not. |
132 | | disabled | `boolean` | `false` | - | Indicate if the component is disabled or not. |
133 | | inline | `boolean` | `false` | - | Indicate if the input is inline or not. |
134 | | inputtable | `boolean` | `true` | - | Indicate if the input element is inputtable or not. |
135 | | max | `number` | `Infinity` | - | The maximum value. |
136 | | min | `number` | `-Infinity` | - | The minimum value. |
137 | | name | `string` | - | - | The name of the input element. |
138 | | placeholder | `string` | - | - | The placeholder of the input element. |
139 | | readonly | `boolean` | `false` | - | Indicate if the component is read only or not. |
140 | | rounded | `boolean` | `false` | - | Indicate if the number is rounded or not. |
141 | | size | `string` | - | small, large | The size of the component. |
142 | | step | `number` | `1` | - | The increment of each step. |
143 | | modelValue | `number` | - | - | The binding value. |
144 |
145 | ## Events
146 |
147 | | Name | Parameters | Description |
148 | | --- | --- | --- |
149 | | update:model-value | `(newValue, oldValue)` | Fire when the value is changed. |
150 |
151 | > Native events that bubble up from child elements are also available.
152 |
153 | ```html
154 |
155 |
156 |
157 |
158 |
173 | ```
174 |
--------------------------------------------------------------------------------
/docs/app.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
53 |
54 |
55 |
56 |
57 |
58 |
59 | {{ name }}
60 | v{{ version }}
61 |
62 |
63 |
64 | {{ description }}
65 |
66 |
67 |
73 |
74 |
75 |
76 |
81 |
82 |
113 |
114 |
115 |
116 |
143 |
144 |
236 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## [2.0.1](https://github.com/fengyuanchen/vue-number-input/compare/v2.0.0...v2.0.1) (2022-02-13)
2 |
3 |
4 | ### Bug Fixes
5 |
6 | * round the normalized new value ([30aaa6a](https://github.com/fengyuanchen/vue-number-input/commit/30aaa6a4a74b99e177477fb21f8ad178d13bb8af)), closes [#32](https://github.com/fengyuanchen/vue-number-input/issues/32)
7 |
8 |
9 |
10 | # [2.0.0](https://github.com/fengyuanchen/vue-number-input/compare/v2.0.0-rc.2...v2.0.0) (2022-02-07)
11 |
12 |
13 |
14 | # [2.0.0-rc.2](https://github.com/fengyuanchen/vue-number-input/compare/v2.0.0-rc.1...v2.0.0-rc.2) (2022-01-22)
15 |
16 |
17 | ### Bug Fixes
18 |
19 | * correct the declaration file name ([7f96219](https://github.com/fengyuanchen/vue-number-input/commit/7f96219dc88afa516244db39715b087bcb04dbcf))
20 |
21 |
22 |
23 | # [2.0.0-rc.1](https://github.com/fengyuanchen/vue-number-input/compare/v2.0.0-rc...v2.0.0-rc.1) (2022-01-08)
24 |
25 |
26 | ### Bug Fixes
27 |
28 | * add `oldValue` to `update:modelValue` event ([4fa92f0](https://github.com/fengyuanchen/vue-number-input/commit/4fa92f03047ebe5dbc45a5e531575d1df081f20c))
29 | * avoid specifying `NaN` value to number input ([101abce](https://github.com/fengyuanchen/vue-number-input/commit/101abce5375f938fd06301877a84cb25c88df71c)), closes [#31](https://github.com/fengyuanchen/vue-number-input/issues/31)
30 |
31 |
32 | ### Features
33 |
34 | * add auto-generated declaration file ([5ecd316](https://github.com/fengyuanchen/vue-number-input/commit/5ecd316699c3e60d0f7b6d591c0d3476e3f03cd2))
35 |
36 |
37 |
38 | # [2.0.0-rc](https://github.com/fengyuanchen/vue-number-input/compare/v2.0.0-beta.1...v2.0.0-rc) (2021-06-12)
39 |
40 |
41 |
42 | # [2.0.0-beta.1](https://github.com/fengyuanchen/vue-number-input/compare/v2.0.0-beta...v2.0.0-beta.1) (2021-04-10)
43 |
44 |
45 | ### Bug Fixes
46 |
47 | * round the new value in the last setp ([c43a69f](https://github.com/fengyuanchen/vue-number-input/commit/c43a69f5b67c5e02fbd1eccb007c947aa5482f8e))
48 | * should emit a single value ([cb8bed9](https://github.com/fengyuanchen/vue-number-input/commit/cb8bed9b9470882588e8b281466cea088afc0009))
49 |
50 |
51 | ### Features
52 |
53 | * support to clear the input value through the keyboard ([fd9faf2](https://github.com/fengyuanchen/vue-number-input/commit/fd9faf23737fb568426fbbe0d879c076cb6cf419)), closes [#28](https://github.com/fengyuanchen/vue-number-input/issues/28)
54 |
55 |
56 |
57 | # [2.0.0-beta](https://github.com/fengyuanchen/vue-number-input/compare/v2.0.0-alpha...v2.0.0-beta) (2021-01-16)
58 |
59 |
60 |
61 | # [2.0.0-alpha](https://github.com/fengyuanchen/vue-number-input/compare/v1.2.1...v2.0.0-alpha) (2021-01-08)
62 |
63 |
64 | * refactor!: upgrade to Vue 3 ([65be700](https://github.com/fengyuanchen/vue-number-input/commit/65be700df03c167abb347c546ba579035d59fc16))
65 |
66 |
67 | ### BREAKING CHANGES
68 |
69 | * drop support for Vue 2.
70 |
71 |
72 |
73 | ## [1.2.1](https://github.com/fengyuanchen/vue-number-input/compare/v1.2.0...v1.2.1) (2020-01-18)
74 |
75 |
76 | ### Bug Fixes
77 |
78 | * add `tabindex` attribute to control buttons ([05869bc](https://github.com/fengyuanchen/vue-number-input/commit/05869bcb058d06608085141b0260962b6f028262)), closes [#20](https://github.com/fengyuanchen/vue-number-input/issues/20)
79 |
80 |
81 |
82 | # [1.2.0](https://github.com/fengyuanchen/vue-number-input/compare/v1.1.2...v1.2.0) (2019-10-19)
83 |
84 |
85 | ### Features
86 |
87 | * add new `attrs` prop ([e8b1498](https://github.com/fengyuanchen/vue-number-input/commit/e8b1498fa485253392afe505c854f6967d6e5990))
88 |
89 |
90 |
91 | ## [1.1.2](https://github.com/fengyuanchen/vue-number-input/compare/v1.1.1...v1.1.2) (2019-10-02)
92 |
93 |
94 | ### Bug Fixes
95 |
96 | * hide the spin box in number input box in Firefox ([580ec5c](https://github.com/fengyuanchen/vue-number-input/commit/580ec5c960c6b9bd4984631a1932321443673ba5)), closes [#17](https://github.com/fengyuanchen/vue-number-input/issues/17)
97 |
98 |
99 |
100 | ## [1.1.1](https://github.com/fengyuanchen/vue-number-input/compare/v1.1.0...v1.1.1) (2019-03-09)
101 |
102 |
103 | ### Bug Fixes
104 |
105 | * add missing `-webkit-` prefix to `appearance` property ([f71d0ae](https://github.com/fengyuanchen/vue-number-input/commit/f71d0aeb2e6211e2b37dd66cc348fb035247c207)), closes [#12](https://github.com/fengyuanchen/vue-number-input/issues/12)
106 | * force to override the number in the input box ([7d816d4](https://github.com/fengyuanchen/vue-number-input/commit/7d816d4ec7652160aae2b330e9502ce2e256fc9a)), closes [#13](https://github.com/fengyuanchen/vue-number-input/issues/13)
107 |
108 |
109 |
110 | # [1.1.0](https://github.com/fengyuanchen/vue-number-input/compare/v1.0.0...v1.1.0) (2019-01-26)
111 |
112 |
113 | ### Features
114 |
115 | * register as a Vue plugin ([b23b09e](https://github.com/fengyuanchen/vue-number-input/commit/b23b09ef0e14601f0bb8b673bd2313b77d6c2e28))
116 | * register the component automatically once loaded ([e86eab1](https://github.com/fengyuanchen/vue-number-input/commit/e86eab1db441100f71e667e5edb297c1030f6830))
117 |
118 |
119 |
120 | # [1.0.0](https://github.com/fengyuanchen/vue-number-input/compare/v0.5.3...v1.0.0) (2018-12-20)
121 |
122 |
123 | ### Bug Fixes
124 |
125 | * avoid the size to less than 1px ([03d804d](https://github.com/fengyuanchen/vue-number-input/commit/03d804d418d1b21c971fe0e7df8a50cec2413bd9)), closes [#10](https://github.com/fengyuanchen/vue-number-input/issues/10)
126 |
127 |
128 | ### Performance Improvements
129 |
130 | * avoid to trigger change event when created ([b1cd06f](https://github.com/fengyuanchen/vue-number-input/commit/b1cd06f901f5c2567e94cb3353f326514ac30ed0))
131 |
132 |
133 |
134 | ## [0.5.3](https://github.com/fengyuanchen/vue-number-input/compare/v0.5.2...v0.5.3) (2018-12-01)
135 |
136 |
137 | ### Bug Fixes
138 |
139 | * sync value only when it is different ([7100b1f](https://github.com/fengyuanchen/vue-number-input/commit/7100b1f01923a9dd4e5927e181a6f1fb15ae8711)), closes [#8](https://github.com/fengyuanchen/vue-number-input/issues/8)
140 | * update input value automatically ([b2dae7f](https://github.com/fengyuanchen/vue-number-input/commit/b2dae7f5358914d9e863b2a0604eaa2201ed7b1d)), closes [#9](https://github.com/fengyuanchen/vue-number-input/issues/9)
141 |
142 |
143 |
144 | ## [0.5.2](https://github.com/fengyuanchen/vue-number-input/compare/v0.5.1...v0.5.2) (2018-08-05)
145 |
146 |
147 | ### Bug Fixes
148 |
149 | * the 0.30000000000000004 problem ([8f5a3e0](https://github.com/fengyuanchen/vue-number-input/commit/8f5a3e0f63ed46417fa80d597aa7c246fde65ba4)), closes [#6](https://github.com/fengyuanchen/vue-number-input/issues/6)
150 |
151 |
152 |
153 | ## [0.5.1](https://github.com/fengyuanchen/vue-number-input/compare/v0.5.0...v0.5.1) (2018-06-09)
154 |
155 |
156 | ### Bug Fixes
157 |
158 | * change the input value only when it is mounted ([cada801](https://github.com/fengyuanchen/vue-number-input/commit/cada8012e9428e633188eab93b19cff6e35f8f17)), closes [#4](https://github.com/fengyuanchen/vue-number-input/issues/4)
159 |
160 |
161 |
162 | # [0.5.0](https://github.com/fengyuanchen/vue-number-input/compare/v0.4.1...v0.5.0) (2018-06-06)
163 |
164 |
165 | ### Bug Fixes
166 |
167 | * off auto complete ([d14af46](https://github.com/fengyuanchen/vue-number-input/commit/d14af4611b182615298e841db5806e00fac34615))
168 |
169 |
170 | ### Features
171 |
172 | * add `placeholder` prop ([331caa6](https://github.com/fengyuanchen/vue-number-input/commit/331caa6a2a1828043a0df778ddd175cd63dce5c1))
173 | * add `rounded` prop ([99dd179](https://github.com/fengyuanchen/vue-number-input/commit/99dd179d8e9b0438e867248e540d6f2a737a225d))
174 |
175 |
176 |
177 | ## [0.4.1](https://github.com/fengyuanchen/vue-number-input/compare/v0.4.0...v0.4.1) (2018-05-27)
178 |
179 |
180 |
181 | # [0.4.0](https://github.com/fengyuanchen/vue-number-input/compare/v0.3.0...v0.4.0) (2018-05-27)
182 |
183 |
184 | ### Features
185 |
186 | * add `inputtable` prop ([98131c9](https://github.com/fengyuanchen/vue-number-input/commit/98131c9d6890b7a0a4aa4f41b13891a766054f2e)), closes [#2](https://github.com/fengyuanchen/vue-number-input/issues/2)
187 |
188 |
189 |
190 | # [0.3.0](https://github.com/fengyuanchen/vue-number-input/compare/v0.2.0...v0.3.0) (2018-03-25)
191 |
192 |
193 |
194 | # [0.2.0](https://github.com/fengyuanchen/vue-number-input/compare/v0.1.0...v0.2.0) (2018-03-21)
195 |
196 |
197 |
198 | # 0.1.0 (2018-03-13)
199 |
200 |
201 |
202 |
--------------------------------------------------------------------------------
/src/vue-number-input.vue:
--------------------------------------------------------------------------------
1 |
2 |
11 |
19 |
36 |
44 |
45 |
46 |
47 |
244 |
245 |
419 |
--------------------------------------------------------------------------------
/tests/props.spec.ts:
--------------------------------------------------------------------------------
1 | import { mount } from '@vue/test-utils';
2 | import VueNumberInput from '../src';
3 |
4 | describe('props', () => {
5 | describe('attrs', () => {
6 | it('should be undefined by default', () => {
7 | const wrapper = mount(VueNumberInput);
8 |
9 | expect(wrapper.props('attrs')).toBeUndefined();
10 | });
11 |
12 | it('should apply the given attributes', () => {
13 | const wrapper = mount(VueNumberInput, {
14 | props: {
15 | attrs: {
16 | tabindex: 0,
17 | },
18 | },
19 | });
20 |
21 | expect(wrapper.props('attrs')).toEqual({
22 | tabindex: 0,
23 | });
24 | expect((wrapper.get('.vue-number-input__input').element as HTMLInputElement).tabIndex).toBe(0);
25 | });
26 | });
27 |
28 | describe('center', () => {
29 | it('should not be center by default', () => {
30 | const wrapper = mount(VueNumberInput);
31 |
32 | expect(wrapper.props('center')).toBe(false);
33 | expect(wrapper.classes()).not.toContain('vue-number-input--center');
34 | });
35 |
36 | it('should be center', () => {
37 | const wrapper = mount(VueNumberInput, {
38 | props: {
39 | center: true,
40 | },
41 | });
42 |
43 | expect(wrapper.props('center')).toBe(true);
44 | expect(wrapper.classes()).toContain('vue-number-input--center');
45 | });
46 | });
47 |
48 | describe('controls', () => {
49 | it('should not display the controls by default', () => {
50 | const wrapper = mount(VueNumberInput);
51 |
52 | expect(wrapper.props('controls')).toBe(false);
53 | expect(wrapper.classes()).not.toContain('vue-number-input--controls');
54 | expect(wrapper.find('.vue-number-input__button').exists()).toBe(false);
55 | });
56 |
57 | it('should display the controls', () => {
58 | const wrapper = mount(VueNumberInput, {
59 | props: {
60 | controls: true,
61 | },
62 | });
63 |
64 | expect(wrapper.props('controls')).toBe(true);
65 | expect(wrapper.classes()).toContain('vue-number-input--controls');
66 | expect(wrapper.findAll('.vue-number-input__button').length).toBe(2);
67 | });
68 |
69 | it('should increase the number when click the plus control', (done) => {
70 | const wrapper = mount(VueNumberInput, {
71 | props: {
72 | controls: true,
73 | },
74 | });
75 |
76 | expect(wrapper.vm.value).toBeNaN();
77 | wrapper.get('.vue-number-input__button--plus').trigger('click').then(() => {
78 | expect(wrapper.vm.value).toBe(1);
79 | expect((wrapper.get('.vue-number-input__input').element as HTMLInputElement).value).toBe('1');
80 | done();
81 | });
82 | });
83 |
84 | it('should decrease the number when click the minus control', (done) => {
85 | const wrapper = mount(VueNumberInput, {
86 | props: {
87 | controls: true,
88 | },
89 | });
90 |
91 | expect(wrapper.vm.value).toBeNaN();
92 | wrapper.get('.vue-number-input__button--minus').trigger('click').then(() => {
93 | expect(wrapper.vm.value).toBe(-1);
94 | expect((wrapper.get('.vue-number-input__input').element as HTMLInputElement).value).toBe('-1');
95 | done();
96 | });
97 | });
98 | });
99 |
100 | describe('disabled', () => {
101 | it('should not be disabled by default', () => {
102 | const wrapper = mount(VueNumberInput, {
103 | props: {
104 | controls: true,
105 | },
106 | });
107 |
108 | expect(wrapper.props('disabled')).toBe(false);
109 | expect((wrapper.get('.vue-number-input__input').element as HTMLInputElement).disabled).toBe(false);
110 | expect((wrapper.get('.vue-number-input__button--plus').element as HTMLButtonElement).disabled).toBe(false);
111 | expect((wrapper.get('.vue-number-input__button--minus').element as HTMLButtonElement).disabled).toBe(false);
112 | });
113 |
114 | it('should by disabled', () => {
115 | const wrapper = mount(VueNumberInput, {
116 | props: {
117 | controls: true,
118 | disabled: true,
119 | },
120 | });
121 |
122 | expect(wrapper.props('disabled')).toBe(true);
123 | expect((wrapper.get('.vue-number-input__input').element as HTMLInputElement).disabled).toBe(true);
124 | expect((wrapper.get('.vue-number-input__button--plus').element as HTMLButtonElement).disabled).toBe(true);
125 | expect((wrapper.get('.vue-number-input__button--minus').element as HTMLButtonElement).disabled).toBe(true);
126 | });
127 | });
128 |
129 | describe('inline', () => {
130 | it('should not be inline by default', () => {
131 | const wrapper = mount(VueNumberInput);
132 |
133 | expect(wrapper.props('inline')).toBe(false);
134 | expect(wrapper.classes()).not.toContain('vue-number-input--inline');
135 | });
136 |
137 | it('should be inline', () => {
138 | const wrapper = mount(VueNumberInput, {
139 | props: {
140 | inline: true,
141 | },
142 | });
143 |
144 | expect(wrapper.props('inline')).toBe(true);
145 | expect(wrapper.classes()).toContain('vue-number-input--inline');
146 | });
147 | });
148 |
149 | describe('inputtable', () => {
150 | it('should be inputtable by default', () => {
151 | const wrapper = mount(VueNumberInput, {
152 | props: {
153 | controls: true,
154 | },
155 | });
156 |
157 | expect(wrapper.props('inputtable')).toBe(true);
158 | expect((wrapper.get('.vue-number-input__input').element as HTMLInputElement).readOnly).toBe(false);
159 | expect((wrapper.get('.vue-number-input__button--plus').element as HTMLButtonElement).disabled).toBe(false);
160 | expect((wrapper.get('.vue-number-input__button--minus').element as HTMLButtonElement).disabled).toBe(false);
161 | });
162 |
163 | it('should not be inputtable', () => {
164 | const wrapper = mount(VueNumberInput, {
165 | props: {
166 | controls: true,
167 | inputtable: false,
168 | },
169 | });
170 |
171 | expect(wrapper.props('inputtable')).toBe(false);
172 | expect((wrapper.get('.vue-number-input__input').element as HTMLInputElement).readOnly).toBe(true);
173 | expect((wrapper.get('.vue-number-input__button--plus').element as HTMLButtonElement).disabled).toBe(false);
174 | expect((wrapper.get('.vue-number-input__button--minus').element as HTMLButtonElement).disabled).toBe(false);
175 | });
176 | });
177 |
178 | describe('max', () => {
179 | it('should be `Infinity` by default', () => {
180 | const wrapper = mount(VueNumberInput);
181 |
182 | expect(wrapper.props('max')).toBe(Infinity);
183 | expect((wrapper.get('.vue-number-input__input').element as HTMLInputElement).max).toBe('Infinity');
184 | });
185 |
186 | it('should be equal to the given value', () => {
187 | const wrapper = mount(VueNumberInput, {
188 | props: {
189 | max: 10,
190 | },
191 | });
192 |
193 | expect(wrapper.props('max')).toBe(10);
194 | expect((wrapper.get('.vue-number-input__input').element as HTMLInputElement).max).toBe('10');
195 | });
196 |
197 | it('should not be greater than the given maximum value', () => {
198 | const wrapper = mount(VueNumberInput, {
199 | props: {
200 | modelValue: 11,
201 | max: 10,
202 | },
203 | });
204 |
205 | expect((wrapper.get('.vue-number-input__input').element as HTMLInputElement).value).toBe('10');
206 | });
207 |
208 | it('should fix the out of range value', () => {
209 | const wrapper = mount(VueNumberInput, {
210 | props: {
211 | max: 10,
212 | },
213 | });
214 |
215 | wrapper.get('.vue-number-input__input').setValue('11').then(() => {
216 | expect((wrapper.get('.vue-number-input__input').element as HTMLInputElement).value).toBe('10');
217 | });
218 | });
219 | });
220 |
221 | describe('min', () => {
222 | it('should be `-Infinity` by default', () => {
223 | const wrapper = mount(VueNumberInput);
224 |
225 | expect(wrapper.props('min')).toBe(-Infinity);
226 | expect((wrapper.get('.vue-number-input__input').element as HTMLInputElement).min).toBe('-Infinity');
227 | });
228 |
229 | it('should be equal to the given value', () => {
230 | const wrapper = mount(VueNumberInput, {
231 | props: {
232 | min: -10,
233 | },
234 | });
235 |
236 | expect(wrapper.props('min')).toBe(-10);
237 | expect((wrapper.get('.vue-number-input__input').element as HTMLInputElement).min).toBe('-10');
238 | });
239 |
240 | it('should not be less than the given minimum value', () => {
241 | const wrapper = mount(VueNumberInput, {
242 | props: {
243 | modelValue: -11,
244 | min: -10,
245 | },
246 | });
247 |
248 | expect((wrapper.get('.vue-number-input__input').element as HTMLInputElement).value).toBe('-10');
249 | });
250 |
251 | it('should fix the out of range value', () => {
252 | const wrapper = mount(VueNumberInput, {
253 | props: {
254 | min: -10,
255 | },
256 | });
257 |
258 | wrapper.get('.vue-number-input__input').setValue('-11').then(() => {
259 | expect((wrapper.get('.vue-number-input__input').element as HTMLInputElement).value).toBe('-10');
260 | });
261 | });
262 | });
263 |
264 | describe('name', () => {
265 | it('should be undefined by default', () => {
266 | const wrapper = mount(VueNumberInput);
267 |
268 | expect(wrapper.props('name')).toBeUndefined();
269 | expect((wrapper.get('.vue-number-input__input').element as HTMLInputElement).name).toBe('');
270 | });
271 |
272 | it('should be equal to the given value', () => {
273 | const wrapper = mount(VueNumberInput, {
274 | props: {
275 | name: 'digit',
276 | },
277 | });
278 |
279 | expect(wrapper.props('name')).toBe('digit');
280 | expect((wrapper.get('.vue-number-input__input').element as HTMLInputElement).name).toBe('digit');
281 | });
282 | });
283 |
284 | describe('placeholder', () => {
285 | it('should be undefined by default', () => {
286 | const wrapper = mount(VueNumberInput);
287 |
288 | expect(wrapper.props('placeholder')).toBeUndefined();
289 | expect((wrapper.get('.vue-number-input__input').element as HTMLInputElement).placeholder).toBe('');
290 | });
291 |
292 | it('should be equal to the given value', () => {
293 | const wrapper = mount(VueNumberInput, {
294 | props: {
295 | placeholder: 'Number input',
296 | },
297 | });
298 |
299 | expect(wrapper.props('placeholder')).toBe('Number input');
300 | expect((wrapper.get('.vue-number-input__input').element as HTMLInputElement).placeholder).toBe('Number input');
301 | });
302 | });
303 |
304 | describe('readonly', () => {
305 | it('should not be read-only by default', () => {
306 | const wrapper = mount(VueNumberInput, {
307 | props: {
308 | controls: true,
309 | },
310 | });
311 |
312 | expect(wrapper.props('readonly')).toBe(false);
313 | expect((wrapper.get('.vue-number-input__input').element as HTMLInputElement).readOnly).toBe(false);
314 | expect((wrapper.get('.vue-number-input__button--plus').element as HTMLButtonElement).disabled).toBe(false);
315 | expect((wrapper.get('.vue-number-input__button--minus').element as HTMLButtonElement).disabled).toBe(false);
316 | });
317 |
318 | it('should be read-only', () => {
319 | const wrapper = mount(VueNumberInput, {
320 | props: {
321 | controls: true,
322 | readonly: true,
323 | },
324 | });
325 |
326 | expect(wrapper.props('readonly')).toBe(true);
327 | expect((wrapper.get('.vue-number-input__input').element as HTMLInputElement).readOnly).toBe(true);
328 | expect((wrapper.get('.vue-number-input__button--plus').element as HTMLButtonElement).disabled).toBe(true);
329 | expect((wrapper.get('.vue-number-input__button--minus').element as HTMLButtonElement).disabled).toBe(true);
330 | });
331 | });
332 |
333 | describe('rounded', () => {
334 | it('should not round the number by default', () => {
335 | const wrapper = mount(VueNumberInput, {
336 | props: {
337 | modelValue: 1.5,
338 | },
339 | });
340 |
341 | expect(wrapper.props('rounded')).toBe(false);
342 | expect((wrapper.get('.vue-number-input__input').element as HTMLInputElement).value).toBe('1.5');
343 | });
344 |
345 | it('should round the number', () => {
346 | const wrapper = mount(VueNumberInput, {
347 | props: {
348 | modelValue: 1.5,
349 | rounded: true,
350 | },
351 | });
352 |
353 | expect(wrapper.props('rounded')).toBe(true);
354 | expect((wrapper.get('.vue-number-input__input').element as HTMLInputElement).value).toBe('2');
355 | });
356 | });
357 |
358 | describe('size', () => {
359 | it('should be small size', () => {
360 | const wrapper = mount(VueNumberInput, {
361 | props: {
362 | size: 'small',
363 | },
364 | });
365 |
366 | expect(wrapper.props('size')).toBe('small');
367 | expect(wrapper.classes()).toContain('vue-number-input--small');
368 | });
369 |
370 | it('should be large size', () => {
371 | const wrapper = mount(VueNumberInput, {
372 | props: {
373 | size: 'large',
374 | },
375 | });
376 |
377 | expect(wrapper.props('size')).toBe('large');
378 | expect(wrapper.classes()).toContain('vue-number-input--large');
379 | });
380 | });
381 |
382 | describe('step', () => {
383 | it('should be `1` by default', (done) => {
384 | const wrapper = mount(VueNumberInput, {
385 | props: {
386 | controls: true,
387 | },
388 | });
389 |
390 | expect(wrapper.props('step')).toBe(1);
391 | wrapper.get('.vue-number-input__button--plus').trigger('click').then(() => {
392 | expect((wrapper.get('.vue-number-input__input').element as HTMLInputElement).step).toBe('1');
393 | done();
394 | });
395 | });
396 |
397 | it('should match the given value', (done) => {
398 | const wrapper = mount(VueNumberInput, {
399 | props: {
400 | controls: true,
401 | step: 2,
402 | },
403 | });
404 |
405 | expect(wrapper.props('step')).toBe(2);
406 | wrapper.get('.vue-number-input__button--plus').trigger('click').then(() => {
407 | expect((wrapper.get('.vue-number-input__input').element as HTMLInputElement).step).toBe('2');
408 | done();
409 | });
410 | });
411 | });
412 |
413 | describe('modelValue', () => {
414 | it('should be `NaN` by default', () => {
415 | const wrapper = mount(VueNumberInput);
416 |
417 | expect(wrapper.props('modelValue')).toBeNaN();
418 | expect((wrapper.get('.vue-number-input__input').element as HTMLInputElement).value).toBe('');
419 | });
420 |
421 | it('should be equal to the given value', () => {
422 | const wrapper = mount(VueNumberInput, {
423 | props: {
424 | modelValue: 10,
425 | },
426 | });
427 |
428 | expect(wrapper.props('modelValue')).toBe(10);
429 | expect((wrapper.get('.vue-number-input__input').element as HTMLInputElement).value).toBe('10');
430 | });
431 | });
432 | });
433 |
--------------------------------------------------------------------------------