main 87099b1f0ab1 cached
35 files
58.1 KB
16.9k tokens
6 symbols
1 requests
Download .txt
Repository: fengyuanchen/vue-number-input
Branch: main
Commit: 87099b1f0ab1
Files: 35
Total size: 58.1 KB

Directory structure:
gitextract_qqq1rvje/

├── .browserslistrc
├── .eslintignore
├── .eslintrc.js
├── .github/
│   └── workflows/
│       ├── ci.yml
│       └── deploy.yml
├── .gitignore
├── .husky/
│   ├── commit-msg
│   └── pre-commit
├── .stylelintignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── babel.config.js
├── commitlint.config.js
├── docs/
│   ├── app.vue
│   ├── components/
│   │   └── demo-block.vue
│   ├── index.html
│   └── index.ts
├── jest.config.js
├── lint-staged.config.js
├── package.json
├── postcss.config.js
├── rollup.config.js
├── src/
│   ├── README.md
│   ├── index.ts
│   ├── shims.d.ts
│   └── vue-number-input.vue
├── stylelint.config.js
├── tests/
│   ├── events.spec.ts
│   ├── methods.spec.ts
│   ├── others.spec.ts
│   └── props.spec.ts
├── tsconfig.eslint.json
├── tsconfig.json
└── webpack.config.js

================================================
FILE CONTENTS
================================================

================================================
FILE: .browserslistrc
================================================
defaults
not ie 11


================================================
FILE: .eslintignore
================================================
*.local*
coverage
dist
node_modules


================================================
FILE: .eslintrc.js
================================================
module.exports = {
  root: true,
  env: {
    browser: true,
    node: true,
  },
  extends: [
    'airbnb-typescript/base',
    'plugin:@typescript-eslint/recommended',
    'plugin:vue/vue3-recommended',
  ],
  parser: 'vue-eslint-parser',
  parserOptions: {
    parser: '@typescript-eslint/parser',
    project: 'tsconfig.eslint.json',
    sourceType: 'module',
    extraFileExtensions: ['.vue'],
  },
  plugins: [
    '@typescript-eslint',
    'import',
    'vue',
  ],
  rules: {
    '@typescript-eslint/no-explicit-any': 'off',
    '@typescript-eslint/no-var-requires': 'off',
    'no-restricted-properties': 'off',
  },
  overrides: [
    {
      files: ['tests/**/*.ts'],
      env: {
        jest: true,
      },
    },
  ],
};


================================================
FILE: .github/workflows/ci.yml
================================================
name: CI

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v2
      - uses: actions/setup-node@v2
        with:
          node-version: 16
      - run: npm install
      - run: npm run lint
      - run: npm run build
      - run: npm test
      - run: npm run test:coverage


================================================
FILE: .github/workflows/deploy.yml
================================================
name: Deploy

on:
  push:
    tags:
      - v2.*
  workflow_dispatch:

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v2
      - uses: actions/setup-node@v2
        with:
          node-version: 16
      - run: npm install
      - run: npm run build:docs
      - run: |
          cd docs/dist
          git init
          git config user.name "${{ github.actor }}"
          git config user.email "${{ github.actor }}@users.noreply.github.com"
          git add --all
          git commit --message "♥"
          git push --force https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git master:gh-pages


================================================
FILE: .gitignore
================================================
*.local*
*.log
*.map
.DS_Store
coverage
dist
node_modules


================================================
FILE: .husky/commit-msg
================================================
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

npx commitlint --edit $1


================================================
FILE: .husky/pre-commit
================================================
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

npx lint-staged


================================================
FILE: .stylelintignore
================================================
*.local*
coverage
dist
node_modules


================================================
FILE: CHANGELOG.md
================================================
## [2.0.1](https://github.com/fengyuanchen/vue-number-input/compare/v2.0.0...v2.0.1) (2022-02-13)


### Bug Fixes

* 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)



# [2.0.0](https://github.com/fengyuanchen/vue-number-input/compare/v2.0.0-rc.2...v2.0.0) (2022-02-07)



# [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)


### Bug Fixes

* correct the declaration file name ([7f96219](https://github.com/fengyuanchen/vue-number-input/commit/7f96219dc88afa516244db39715b087bcb04dbcf))



# [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)


### Bug Fixes

* add `oldValue` to `update:modelValue` event ([4fa92f0](https://github.com/fengyuanchen/vue-number-input/commit/4fa92f03047ebe5dbc45a5e531575d1df081f20c))
* 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)


### Features

* add auto-generated declaration file ([5ecd316](https://github.com/fengyuanchen/vue-number-input/commit/5ecd316699c3e60d0f7b6d591c0d3476e3f03cd2))



# [2.0.0-rc](https://github.com/fengyuanchen/vue-number-input/compare/v2.0.0-beta.1...v2.0.0-rc) (2021-06-12)



# [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)


### Bug Fixes

* round the new value in the last setp ([c43a69f](https://github.com/fengyuanchen/vue-number-input/commit/c43a69f5b67c5e02fbd1eccb007c947aa5482f8e))
* should emit a single value ([cb8bed9](https://github.com/fengyuanchen/vue-number-input/commit/cb8bed9b9470882588e8b281466cea088afc0009))


### Features

* 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)



# [2.0.0-beta](https://github.com/fengyuanchen/vue-number-input/compare/v2.0.0-alpha...v2.0.0-beta) (2021-01-16)



# [2.0.0-alpha](https://github.com/fengyuanchen/vue-number-input/compare/v1.2.1...v2.0.0-alpha) (2021-01-08)


* refactor!: upgrade to Vue 3 ([65be700](https://github.com/fengyuanchen/vue-number-input/commit/65be700df03c167abb347c546ba579035d59fc16))


### BREAKING CHANGES

* drop support for Vue 2.



## [1.2.1](https://github.com/fengyuanchen/vue-number-input/compare/v1.2.0...v1.2.1) (2020-01-18)


### Bug Fixes

* 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)



# [1.2.0](https://github.com/fengyuanchen/vue-number-input/compare/v1.1.2...v1.2.0) (2019-10-19)


### Features

* add new `attrs` prop ([e8b1498](https://github.com/fengyuanchen/vue-number-input/commit/e8b1498fa485253392afe505c854f6967d6e5990))



## [1.1.2](https://github.com/fengyuanchen/vue-number-input/compare/v1.1.1...v1.1.2) (2019-10-02)


### Bug Fixes

* 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)



## [1.1.1](https://github.com/fengyuanchen/vue-number-input/compare/v1.1.0...v1.1.1) (2019-03-09)


### Bug Fixes

* 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)
* 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)



# [1.1.0](https://github.com/fengyuanchen/vue-number-input/compare/v1.0.0...v1.1.0) (2019-01-26)


### Features

* register as a Vue plugin ([b23b09e](https://github.com/fengyuanchen/vue-number-input/commit/b23b09ef0e14601f0bb8b673bd2313b77d6c2e28))
* register the component automatically once loaded ([e86eab1](https://github.com/fengyuanchen/vue-number-input/commit/e86eab1db441100f71e667e5edb297c1030f6830))



# [1.0.0](https://github.com/fengyuanchen/vue-number-input/compare/v0.5.3...v1.0.0) (2018-12-20)


### Bug Fixes

* 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)


### Performance Improvements

* avoid to trigger change event when created ([b1cd06f](https://github.com/fengyuanchen/vue-number-input/commit/b1cd06f901f5c2567e94cb3353f326514ac30ed0))



## [0.5.3](https://github.com/fengyuanchen/vue-number-input/compare/v0.5.2...v0.5.3) (2018-12-01)


### Bug Fixes

* 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)
* 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)



## [0.5.2](https://github.com/fengyuanchen/vue-number-input/compare/v0.5.1...v0.5.2) (2018-08-05)


### Bug Fixes

* 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)



## [0.5.1](https://github.com/fengyuanchen/vue-number-input/compare/v0.5.0...v0.5.1) (2018-06-09)


### Bug Fixes

* 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)



# [0.5.0](https://github.com/fengyuanchen/vue-number-input/compare/v0.4.1...v0.5.0) (2018-06-06)


### Bug Fixes

* off auto complete ([d14af46](https://github.com/fengyuanchen/vue-number-input/commit/d14af4611b182615298e841db5806e00fac34615))


### Features

* add `placeholder` prop ([331caa6](https://github.com/fengyuanchen/vue-number-input/commit/331caa6a2a1828043a0df778ddd175cd63dce5c1))
* add `rounded` prop ([99dd179](https://github.com/fengyuanchen/vue-number-input/commit/99dd179d8e9b0438e867248e540d6f2a737a225d))



## [0.4.1](https://github.com/fengyuanchen/vue-number-input/compare/v0.4.0...v0.4.1) (2018-05-27)



# [0.4.0](https://github.com/fengyuanchen/vue-number-input/compare/v0.3.0...v0.4.0) (2018-05-27)


### Features

* add `inputtable` prop ([98131c9](https://github.com/fengyuanchen/vue-number-input/commit/98131c9d6890b7a0a4aa4f41b13891a766054f2e)), closes [#2](https://github.com/fengyuanchen/vue-number-input/issues/2)



# [0.3.0](https://github.com/fengyuanchen/vue-number-input/compare/v0.2.0...v0.3.0) (2018-03-25)



# [0.2.0](https://github.com/fengyuanchen/vue-number-input/compare/v0.1.0...v0.2.0) (2018-03-21)



# 0.1.0 (2018-03-13)





================================================
FILE: LICENSE
================================================
The MIT License (MIT)

Copyright 2018-present Chen Fengyuan

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.


================================================
FILE: README.md
================================================
# vue-number-input

[![Coverage Status](https://img.shields.io/codecov/c/github/fengyuanchen/vue-number-input.svg)](https://codecov.io/gh/fengyuanchen/vue-number-input) [![Downloads](https://img.shields.io/npm/dm/@chenfengyuan/vue-number-input.svg)](https://www.npmjs.com/package/@chenfengyuan/vue-number-input) [![Version](https://img.shields.io/npm/v/@chenfengyuan/vue-number-input.svg)](https://www.npmjs.com/package/@chenfengyuan/vue-number-input) [![Gzip Size](https://img.shields.io/bundlephobia/minzip/@chenfengyuan/vue-number-input.svg)](https://unpkg.com/@chenfengyuan/vue-number-input/dist/vue-number-input.js)

> Number input component for Vue 3. For Vue 2, check out the [`v1`](https://github.com/fengyuanchen/vue-number-input/tree/v1) branch.

- [Docs](src/README.md)
- [Demo](https://fengyuanchen.github.io/vue-number-input)

## Main npm package files

```text
dist/
├── vue-number-input.js         (UMD, default)
├── vue-number-input.min.js     (UMD, compressed)
├── vue-number-input.esm.js     (ECMAScript Module)
├── vue-number-input.esm.min.js (ECMAScript Module, compressed)
└── vue-number-input.d.ts       (TypeScript Declaration File)
```

## Getting started

### Installation

Using npm:

```shell
npm install vue@3 @chenfengyuan/vue-number-input@2
```

Using pnpm:

```shell
pnpm add vue@3 @chenfengyuan/vue-number-input@2
```

Using Yarn:

```shell
yarn add vue@3 @chenfengyuan/vue-number-input@2
```

Using CDN:

```html
<script src="https://unpkg.com/vue@3"></script><!-- Vue.js is required -->
<script src="https://unpkg.com/@chenfengyuan/vue-number-input@2"></script>
```

### Usage

```js
import { createApp } from 'vue';
import VueNumberInput from '@chenfengyuan/vue-number-input';

const app = createApp({});

app.component(VueNumberInput.name, VueNumberInput);
```

```html
<vue-number-input controls></vue-number-input>
```

## Browser support

Same as Vue 3.

## Versioning

Maintained under the [Semantic Versioning guidelines](https://semver.org/).

## License

[MIT](https://opensource.org/licenses/MIT) © [Chen Fengyuan](https://chenfengyuan.com/)


================================================
FILE: babel.config.js
================================================
module.exports = {
  presets: ['@babel/preset-env'],
};


================================================
FILE: commitlint.config.js
================================================
module.exports = {
  extends: [
    '@commitlint/config-conventional',
  ],
};


================================================
FILE: docs/app.vue
================================================
<template>
  <div class="app">
    <header class="navbar navbar-light navbar-expand-md">
      <nav class="container">
        <a
          class="navbar-brand"
          href="./"
        >
          {{ name }}
        </a>
        <button
          class="navbar-toggler"
          type="button"
          data-bs-toggle="collapse"
          data-bs-target="#navbar-collapse"
          aria-controls="navbar-collapse"
          aria-expanded="false"
          aria-label="Toggle navigation"
        >
          <span class="navbar-toggler-icon" />
        </button>
        <div
          id="navbar-collapse"
          class="collapse navbar-collapse justify-content-end"
          role="navigation"
        >
          <nav class="nav navbar-nav">
            <a
              class="nav-link"
              href="https://github.com/fengyuanchen/vue-number-input"
              title="View the GitHub project"
            >
              GitHub
            </a>
            <a
              class="nav-link"
              href="https://fengyuanchen.github.io/"
              title="Explore more projects"
            >
              Explore
            </a>
            <a
              class="nav-link"
              href="https://chenfengyuan.com/"
              title="About the author"
            >
              About
            </a>
          </nav>
        </div>
      </nav>
    </header>
    <main>
      <div class="bg-primary text-white py-5">
        <div class="container">
          <div class="row">
            <div class="col-md">
              <h1>
                {{ name }} <small class="h6">
                  v{{ version }}
                </small>
              </h1>
              <p class="lead">
                {{ description }}
              </p>
            </div>
            <div class="col-md">
              <div
                ref="carbonads"
                class="carbonads"
              />
            </div>
          </div>
        </div>
      </div>
      <div class="container">
        <div class="markdown-body">
          <Readme />
        </div>
      </div>
    </main>
    <footer>
      <div class="container">
        <p class="heart" />
        <nav class="nav flex-wrap justify-content-center mb-3">
          <a
            class="nav-link"
            href="https://github.com/fengyuanchen/vue-number-input"
          >
            GitHub
          </a>
          <a
            class="nav-link"
            href="https://github.com/fengyuanchen/vue-number-input/blob/main/CHANGELOG.md"
          >
            Changelog
          </a>
          <a
            class="nav-link"
            href="https://github.com/fengyuanchen/vue-number-input/blob/main/LICENSE"
          >
            License
          </a>
          <a
            class="nav-link"
            href="https://chenfengyuan.com/"
          >
            About
          </a>
        </nav>
      </div>
    </footer>
  </div>
</template>

<script lang="ts">
import { defineComponent } from 'vue';
import pkg from '../package.json';
import Readme from '../src/README.md';

// eslint-disable-next-line
pkg.name = pkg.name.split('/').pop() || '';

export default defineComponent({
  components: {
    Readme,
  },

  data() {
    return pkg;
  },

  mounted() {
    const script = document.createElement('script');

    script.async = true;
    script.id = '_carbonads_js';
    script.src = '//cdn.carbonads.com/carbon.js?serve=CKYI55Q7&placement=fengyuanchengithubio';
    (this.$refs.carbonads as Element).appendChild(script);
  },
});
</script>

<style lang="scss">
.markdown-body {
  table {
    display: table;
    min-width: 100%;
  }

  code {
    white-space: nowrap;
  }
}

.carbonads {
  border: 1px solid #ccc;
  border-radius: 0.25rem;
  font-size: 0.875rem;
  overflow: hidden;
  padding: 1rem;

  @media (min-width: 768px) {
    float: right;
    margin-bottom: -1rem;
    margin-top: -1rem;
    max-width: 360px;
  }
}

.carbon {
  &-wrap {
    overflow: hidden;
  }

  &-img {
    clear: left;
    display: block;
    float: left;
  }

  &-text,
  &-poweredby {
    display: block;
    margin-left: 140px;

    &,
    &:focus,
    &:hover {
      color: #fff;
      text-decoration: none;
    }
  }

  &-poweredby {
    color: #ddd;
  }
}

.heart {
  color: #ddd;
  display: block;
  height: 2rem;
  line-height: 2rem;
  margin-bottom: 0;
  margin-top: 1rem;
  position: relative;
  text-align: center;
  width: 100%;

  &:hover {
    color: #ff4136;
  }

  &::before {
    border-top: 1px solid #eee;
    content: '';
    display: block;
    height: 0;
    left: 0;
    position: absolute;
    right: 0;
    top: 50%;
  }

  &::after {
    background-color: #fff;
    content: '♥';
    padding-left: 0.5rem;
    padding-right: 0.5rem;
    position: relative;
    z-index: 1;
  }
}
</style>


================================================
FILE: docs/components/demo-block.vue
================================================
<template>
  <div class="demo-block">
    <slot />
  </div>
</template>

<script lang="ts">
import { defineComponent } from 'vue';

export default defineComponent({
  name: 'DemoBlock',
});
</script>

<style lang="scss">
.demo-block {
  border: 1px solid #eee;
  border-top-left-radius: 0.25rem;
  border-top-right-radius: 0.25rem;
  padding: 1rem;
  position: relative;

  & + pre {
    border: 1px solid #eee;
    border-radius: 0.25rem;
    border-top: 0;
    border-top-left-radius: 0;
    border-top-right-radius: 0;
    margin-bottom: 1rem;
  }
}
</style>


================================================
FILE: docs/index.html
================================================
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="x-ua-compatible" content="ie=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <title>vue-number-input</title>
    <meta name="description" content="Number input component for Vue.js.">
    <meta name="author" content="Chen Fengyuan">
    <link href="https://unpkg.com/bootstrap@5/dist/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous">
    <link rel="stylesheet" href="https://unpkg.com/github-markdown-css@4/github-markdown.css" crossorigin="anonymous">
    <link rel="stylesheet" href="https://unpkg.com/@highlightjs/cdn-assets@11/styles/github.min.css" crossorigin="anonymous">
  </head>
  <body>
    <div id="app"></div>
    <script src="https://unpkg.com/bootstrap@5/dist/js/bootstrap.bundle.min.js" crossorigin="anonymous"></script>
    <script src="https://unpkg.com/@highlightjs/cdn-assets@11/highlight.min.js" crossorigin="anonymous"></script>
    <script src="https://unpkg.com/vue@3/dist/vue.global.prod.js" crossorigin="anonymous"></script>
    <script src="https://fengyuanchen.github.io/shared/google-analytics.js" crossorigin="anonymous"></script>
    <script>hljs.highlightAll();</script>
  </body>
</html>


================================================
FILE: docs/index.ts
================================================
import { createApp } from 'vue';
import App from './app.vue';
import DemoBlock from './components/demo-block.vue';
import VueNumberInput from '../src';

const app = createApp(App);

app.component(VueNumberInput.name, VueNumberInput);
app.component(DemoBlock.name, DemoBlock);
app.mount('#app');


================================================
FILE: jest.config.js
================================================
module.exports = {
  preset: 'ts-jest',
  collectCoverage: true,
  coverageDirectory: 'coverage',
  coverageReporters: ['html', 'lcov', 'text'],
  moduleFileExtensions: ['js', 'ts', 'vue'],
  transform: {
    '^.+\\.js$': 'babel-jest',
    '^.+\\.vue$': '@vue/vue3-jest',
  },
  testEnvironment: 'jsdom',
  testMatch: ['**/tests/*.spec.ts'],
};


================================================
FILE: lint-staged.config.js
================================================
module.exports = {
  '*.{js,ts,vue}': 'eslint --fix',
  '*.{css,scss,vue}': 'stylelint --fix',
};


================================================
FILE: package.json
================================================
{
  "name": "@chenfengyuan/vue-number-input",
  "version": "2.0.1",
  "description": "Number input component for Vue 3.",
  "main": "dist/vue-number-input.js",
  "module": "dist/vue-number-input.esm.js",
  "types": "dist/vue-number-input.d.ts",
  "files": [
    "dist"
  ],
  "scripts": {
    "build": "rollup -c --environment BUILD:production",
    "build:docs": "webpack --env production",
    "build:types": "move-file dist/vue-number-input.vue.d.ts dist/vue-number-input.d.ts",
    "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 0",
    "clean": "del-cli dist",
    "lint": "npm run lint:js && npm run lint:css",
    "lint:css": "stylelint **/*.{css,scss,vue} --fix",
    "lint:js": "eslint . --ext .js,.ts,.vue --fix",
    "prepare": "husky install",
    "release": "npm run clean && npm run lint && npm run build && npm run build:types && npm run build:docs && npm test && npm run changelog",
    "serve": "webpack serve --hot --open",
    "start": "npm run serve",
    "test": "jest",
    "test:coverage": "cat coverage/lcov.info | codecov"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/fengyuanchen/vue-number-input.git"
  },
  "keywords": [
    "number",
    "input",
    "vue",
    "vue3",
    "vue-component",
    "front-end",
    "web"
  ],
  "author": "Chen Fengyuan (https://chenfengyuan.com/)",
  "license": "MIT",
  "bugs": "https://github.com/fengyuanchen/vue-number-input/issues",
  "homepage": "https://fengyuanchen.github.io/vue-number-input",
  "devDependencies": {
    "@babel/core": "^7.17.2",
    "@babel/preset-env": "^7.16.11",
    "@commitlint/cli": "^16.2.1",
    "@commitlint/config-conventional": "^16.2.1",
    "@types/jest": "^27.4.0",
    "@typescript-eslint/eslint-plugin": "^5.11.0",
    "@typescript-eslint/parser": "^5.11.0",
    "@vue/test-utils": "^2.0.0-rc.18",
    "@vue/vue3-jest": "^27.0.0-alpha.4",
    "babel-jest": "^27.5.1",
    "babel-loader": "^8.2.3",
    "change-case": "^4.1.2",
    "codecov": "^3.8.3",
    "conventional-changelog-cli": "^2.2.2",
    "create-banner": "^2.0.0",
    "css-loader": "^6.6.0",
    "del-cli": "^4.0.1",
    "eslint": "^8.9.0",
    "eslint-config-airbnb-typescript": "^16.1.0",
    "eslint-plugin-import": "^2.25.4",
    "eslint-plugin-vue": "^8.4.1",
    "html-webpack-plugin": "^5.5.0",
    "husky": "^7.0.4",
    "jest": "^27.5.1",
    "lint-staged": "^12.3.3",
    "markdown-to-vue-loader": "^3.1.3",
    "mini-css-extract-plugin": "^2.5.3",
    "move-file-cli": "^3.0.0",
    "postcss": "^8.4.6",
    "rollup": "^2.67.2",
    "rollup-plugin-postcss": "^4.0.2",
    "rollup-plugin-terser": "^7.0.2",
    "rollup-plugin-typescript2": "^0.31.2",
    "rollup-plugin-vue": "^6.0.0",
    "sass": "^1.49.7",
    "sass-loader": "^12.4.0",
    "style-loader": "^3.3.1",
    "stylelint": "^14.5.0",
    "stylelint-config-recommended-scss": "^5.0.2",
    "stylelint-config-recommended-vue": "^1.3.0",
    "stylelint-order": "^5.0.0",
    "ts-jest": "^27.1.3",
    "ts-loader": "^9.2.6",
    "tslib": "^2.3.1",
    "typescript": "^4.5.5",
    "vue": "^3.2.31",
    "vue-loader": "^17.0.0",
    "webpack": "^5.68.0",
    "webpack-cli": "^4.9.2",
    "webpack-dev-server": "^4.7.4"
  },
  "peerDependencies": {
    "vue": "^3.0.0"
  },
  "publishConfig": {
    "access": "public"
  }
}


================================================
FILE: postcss.config.js
================================================
module.exports = {
  plugins: {
    stylelint: {
      fix: true,
    },
  },
};


================================================
FILE: rollup.config.js
================================================
import createBanner from 'create-banner';
import postcss from 'rollup-plugin-postcss';
import typescript from 'rollup-plugin-typescript2';
import vue from 'rollup-plugin-vue';
import { pascalCase } from 'change-case';
import { terser } from 'rollup-plugin-terser';
import pkg from './package.json';

const name = pascalCase(pkg.name.replace(/^.+\//, ''));
const banner = createBanner({
  data: {
    year: '2018-present',
  },
  template: 'inline',
});

export default ['umd', 'esm'].map((format) => ({
  input: 'src/vue-number-input.vue',
  output: ['development', 'production'].map((mode) => {
    const output = {
      banner,
      format,
      name,
      file: pkg.main,
      globals: {
        vue: 'Vue',
      },
    };

    if (format === 'esm') {
      output.file = pkg.module;
    }

    if (mode === 'production') {
      output.compact = true;
      output.file = output.file.replace(/(\.js)$/, '.min$1');
      output.plugins = [
        terser(),
      ];
    }

    return output;
  }),
  external: Object.keys(pkg.peerDependencies),
  plugins: [
    typescript({
      tsconfigOverride: {
        compilerOptions: {
          declaration: format === 'esm',
        },
        exclude: [
          'src/index.ts',
          'docs',
          'tests',
        ],
      },
    }),
    vue({
      preprocessStyles: true,
    }),
    postcss({
      extensions: ['.css', '.scss'],
      minimize: true,
    }),
  ],
}));


================================================
FILE: src/README.md
================================================
# Number Input

> Number input with optional controls.

## Basic usage

```html
<template>
  <p>Value: {{ value }}</p>
  <vue-number-input v-model="value" :min="1" :max="10" inline controls></vue-number-input>
</template>

<script>
export default {
  data() {
    return {
      value: 1,
    };
  },
};
</script>
```

## Step

```html
<template>
  <vue-number-input :step="10" inline controls></vue-number-input>
</template>
```

## Inline

```html
<template>
  <p>
    <vue-number-input placeholder="Block input (default)" controls></vue-number-input>
  </p>
  <vue-number-input placeholder="Inline input" inline controls></vue-number-input>
</template>
```

## Center number

```html
<template>
  <vue-number-input :model-value="0" inline center controls></vue-number-input>
</template>
```

## Sizes

```html
<template>
  <vue-number-input placeholder="Small" size="small" inline controls></vue-number-input>
  <vue-number-input placeholder="Default" inline controls></vue-number-input>
  <vue-number-input placeholder="Large" size="large" inline controls></vue-number-input>
</template>

<style scoped>
.vue-number-input + .vue-number-input {
  margin-left: 1rem;
}
</style>
```

## Without controls

```html
<template>
  <vue-number-input placeholder="Small number input" size="small" inline></vue-number-input>
  <vue-number-input placeholder="Default number input" inline></vue-number-input>
  <vue-number-input placeholder="Large number input" size="large" inline></vue-number-input>
</template>

<style scoped>
.vue-number-input + .vue-number-input {
  margin-left: 1rem;
}
</style>
```

## Rounded

```html
<template>
  <vue-number-input :model-value="1.5" inline controls rounded></vue-number-input>
</template>
```

## Not inputtable

The input is not inputtable, but still allow to change the value by controls.

```html
<template>
  <vue-number-input :model-value="1" :min="1" :max="3" :inputtable="false" inline controls></vue-number-input>
</template>
```

## Readonly

```html
<template>
  <vue-number-input :model-value="1" inline controls readonly></vue-number-input>
</template>
```

## Disabled

```html
<template>
  <vue-number-input :model-value="0" inline controls disabled></vue-number-input>
</template>
```

## Customize attributes for the input element

```html
<template>
  <vue-number-input :model-value="0" :attrs="{ id: 'my-vue-number-input', tabindex: -1 }" inline controls></vue-number-input>
</template>
```

## Props

| Name | Type | Default | Options | Description |
| --- | --- | --- | --- | --- |
| attrs | `Object` | - | - | Specify attributes for the built-in input element. |
| center | `boolean` | `false` | - | Indicate if the number is center or not. |
| controls | `boolean` | `false` | - | Indicate if the controls is visible or not. |
| disabled | `boolean` | `false` | - | Indicate if the component is disabled or not. |
| inline | `boolean` | `false` | - | Indicate if the input is inline or not. |
| inputtable | `boolean` | `true` | - | Indicate if the input element is inputtable or not. |
| max | `number` | `Infinity` | - | The maximum value. |
| min | `number` | `-Infinity` | - | The minimum value. |
| name | `string` | - | - | The name of the input element. |
| placeholder | `string` | - | - | The placeholder of the input element. |
| readonly | `boolean` | `false` | - | Indicate if the component is read only or not. |
| rounded | `boolean` | `false` | - | Indicate if the number is rounded or not. |
| size | `string` | - | small, large | The size of the component. |
| step | `number` | `1` | - | The increment of each step. |
| modelValue | `number` | - | - | The binding value. |

## Events

| Name | Parameters | Description |
| --- | --- | --- |
| update:model-value | `(newValue, oldValue)` | Fire when the value is changed. |

> Native events that bubble up from child elements are also available.

```html
<template>
  <vue-number-input @update:model-value="onUpdate" @change="onChange" @input="onInput" inline controls></vue-number-input>
</template>

<script>
export default {
  methods: {
    onUpdate(newValue, oldValue) {
      console.log(newValue, oldValue);
    },
    onChange(event) {
      console.log(event);
    },
    onInput(event) {
      console.log(event);
    },
  },
};
</script>
```


================================================
FILE: src/index.ts
================================================
import VueNumberInput from './vue-number-input.vue';

export default VueNumberInput;


================================================
FILE: src/shims.d.ts
================================================
declare module '*.vue' {
  const content: any;

  export default content;
}

declare module '*.md' {
  const content: any;

  export default content;
}


================================================
FILE: src/vue-number-input.vue
================================================
<template>
  <div
    class="vue-number-input"
    :class="{
      'vue-number-input--inline': inline,
      'vue-number-input--center': center,
      'vue-number-input--controls': controls,
      [`vue-number-input--${size}`]: size,
    }"
  >
    <button
      v-if="controls"
      class="vue-number-input__button vue-number-input__button--minus"
      type="button"
      tabindex="-1"
      :disabled="disabled || readonly || !decreasable"
      @click.prevent="decrease"
    />
    <input
      ref="input"
      class="vue-number-input__input"
      v-bind="attrs"
      type="number"
      :name="name"
      :value="isNaN(value) ? '' : value"
      :min="min"
      :max="max"
      :step="step"
      :readonly="readonly || !inputtable"
      :disabled="disabled || (!decreasable && !increasable)"
      :placeholder="placeholder"
      autocomplete="off"
      @change="change"
      @paste="paste"
    >
    <button
      v-if="controls"
      class="vue-number-input__button vue-number-input__button--plus"
      type="button"
      tabindex="-1"
      :disabled="disabled || readonly || !increasable"
      @click.prevent="increase"
    />
  </div>
</template>

<script lang="ts">
import { defineComponent } from 'vue';

const isNaN = Number.isNaN || window.isNaN;
const REGEXP_NUMBER = /^-?(?:\d+|\d+\.\d+|\.\d+)(?:[eE][-+]?\d+)?$/;
const REGEXP_DECIMALS = /\.\d*(?:0|9){10}\d*$/;
const normalizeDecimalNumber = (value: number, times = 100000000000) => (
  REGEXP_DECIMALS.test(String(value)) ? (Math.round(value * times) / times) : value
);

export default defineComponent({
  name: 'VueNumberInput',

  props: {
    attrs: {
      type: Object,
      default: undefined,
    },

    center: Boolean,
    controls: Boolean,
    disabled: Boolean,

    inputtable: {
      type: Boolean,
      default: true,
    },

    inline: Boolean,

    max: {
      type: Number,
      default: Infinity,
    },

    min: {
      type: Number,
      default: -Infinity,
    },

    name: {
      type: String,
      default: undefined,
    },

    placeholder: {
      type: String,
      default: undefined,
    },

    readonly: Boolean,
    rounded: Boolean,

    size: {
      type: String,
      default: undefined,
    },

    step: {
      type: Number,
      default: 1,
    },

    modelValue: {
      type: Number,
      default: NaN,
    },
  },

  emits: [
    'update:modelValue',
  ],

  data() {
    return {
      value: NaN,
    };
  },

  computed: {
    /**
     * Indicate if the value is increasable.
     * @returns {boolean} Return `true` if it is decreasable, else `false`.
     */
    increasable(): boolean {
      return isNaN(this.value) || this.value < this.max;
    },

    /**
     * Indicate if the value is decreasable.
     * @returns {boolean} Return `true` if it is decreasable, else `false`.
     */
    decreasable(): boolean {
      return isNaN(this.value) || this.value > this.min;
    },
  },

  watch: {
    modelValue: {
      immediate: true,
      handler(newValue, oldValue) {
        if (
          // Avoid triggering change event when created
          !(isNaN(newValue) && typeof oldValue === 'undefined')

          // Avoid infinite loop
          && newValue !== this.value
        ) {
          this.setValue(newValue);
        }
      },
    },
  },

  methods: {
    isNaN,

    /**
     * Change event handler.
     * @param {string} value - The new value.
     */
    change(event: any) {
      this.setValue(event.target.value);
    },

    /**
     * Paste event handler.
     * @param {Event} event - Event object.
     */
    paste(event: ClipboardEvent) {
      const clipboardData = event.clipboardData || (window as any).clipboardData;

      if (clipboardData && !REGEXP_NUMBER.test(clipboardData.getData('text'))) {
        event.preventDefault();
      }
    },

    /**
     * Decrease the value.
     */
    decrease() {
      if (this.decreasable) {
        let { value } = this;

        if (isNaN(value)) {
          value = 0;
        }

        this.setValue(normalizeDecimalNumber(value - this.step));
      }
    },

    /**
     * Increase the value.
     */
    increase() {
      if (this.increasable) {
        let { value } = this;

        if (isNaN(value)) {
          value = 0;
        }

        this.setValue(normalizeDecimalNumber(value + this.step));
      }
    },

    /**
     * Set new value and dispatch change event.
     * @param {number} value - The new value to set.
     */
    setValue(value: number) {
      const oldValue = this.value;
      let newValue = typeof value !== 'number' ? parseFloat(value) : value;

      if (!isNaN(newValue)) {
        if (this.min <= this.max) {
          newValue = Math.min(this.max, Math.max(this.min, newValue));
        }

        if (this.rounded) {
          newValue = Math.round(newValue);
        }
      }

      this.value = newValue;

      if (newValue === oldValue) {
        // Force to override the number in the input box (#13).
        (this.$refs.input as HTMLInputElement).value = String(newValue);
      }

      this.$emit('update:modelValue', newValue, oldValue);
    },
  },
});
</script>

<style lang="scss" scoped>
.vue-number-input {
  display: block;
  font-size: 0;
  max-width: 100%;
  overflow: hidden;
  position: relative;

  &__button {
    background-color: #fff;
    border: 0;
    border-radius: 0.25rem;
    bottom: 1px;
    position: absolute;
    top: 1px;
    width: 2.5rem;
    z-index: 1;

    &:focus {
      outline: none;
    }

    &:hover {
      &::before,
      &::after {
        background-color: #0074d9;
      }
    }

    &:disabled {
      opacity: 0.65;

      &::before,
      &::after {
        background-color: #ddd;
      }
    }

    &::before,
    &::after {
      background-color: #111;
      content: "";
      left: 50%;
      position: absolute;
      top: 50%;
      transform: translate(-50%, -50%);
      transition: background-color 0.15s;
    }

    &::before {
      height: 1px;
      width: 50%;
    }

    &::after {
      height: 50%;
      width: 1px;
    }

    &--minus {
      border-bottom-right-radius: 0;
      border-right: 1px solid #ddd;
      border-top-right-radius: 0;
      left: 1px;

      &::after {
        visibility: hidden;
      }
    }

    &--plus {
      border-bottom-left-radius: 0;
      border-left: 1px solid #ddd;
      border-top-left-radius: 0;
      right: 1px;
    }
  }

  &__input {
    -moz-appearance: textfield;
    background-color: #fff;
    border: 1px solid #ddd;
    border-radius: 0.25rem;
    display: block;
    font-size: 1rem;
    line-height: 1.5;
    max-width: 100%;
    min-height: 1.5rem;
    min-width: 3rem;
    padding: 0.4375rem 0.875rem;
    transition: border-color 0.15s;
    width: 100%;

    &::-webkit-outer-spin-button,
    &::-webkit-inner-spin-button {
      -webkit-appearance: none;
    }

    &:focus {
      border-color: #0074d9;
      outline: none;
    }

    &:disabled,
    &[readonly] {
      background-color: #f8f8f8;
    }
  }

  &--inline {
    display: inline-block;

    & > input {
      display: inline-block;
      width: 12.5rem;
    }
  }

  &--center {
    & > input {
      text-align: center;
    }
  }

  &--controls {
    & > input {
      padding-left: 3.375rem;
      padding-right: 3.375rem;
    }
  }

  &--small {
    & > input {
      border-radius: 0.1875rem;
      font-size: 0.875rem;
      padding: 0.25rem 0.5rem;
    }

    &.vue-number-input--inline > input {
      width: 10rem;
    }

    &.vue-number-input--controls > button {
      width: 2rem;
    }

    &.vue-number-input--controls > input {
      padding-left: 2.5rem;
      padding-right: 2.5rem;
    }
  }

  &--large {
    & > input {
      border-radius: 0.3125rem;
      font-size: 1.25rem;
      padding: 0.5rem 1rem;
    }

    &.vue-number-input--inline > input {
      width: 15rem;
    }

    &.vue-number-input--controls > button {
      width: 3rem;
    }

    &.vue-number-input--controls > input {
      padding-left: 4rem;
      padding-right: 4rem;
    }
  }
}
</style>


================================================
FILE: stylelint.config.js
================================================
module.exports = {
  extends: 'stylelint-config-recommended-vue/scss',
  plugins: [
    'stylelint-order',
  ],
  rules: {
    'no-descending-specificity': null,
    'no-empty-source': null,
    'order/properties-alphabetical-order': true,
  },
};


================================================
FILE: tests/events.spec.ts
================================================
import { mount } from '@vue/test-utils';
import VueNumberInput from '../src';

describe('events', () => {
  describe('custom', () => {
    it('should trigger the custom `update:model-value` event', (done) => {
      const wrapper = mount({
        components: {
          VueNumberInput,
        },
        methods: {
          onModelValueChange(newValue: number) {
            expect(newValue).toBe(1);
            done();
          },
        },
        template: '<vue-number-input @update:model-value="onModelValueChange" />',
      });

      wrapper.get('.vue-number-input__input').setValue('1');
    });
  });

  describe('native', () => {
    it('should trigger the native `change` event', (done) => {
      const wrapper = mount({
        components: {
          VueNumberInput,
        },
        methods: {
          onChange(event: Event) {
            expect(event.type).toBe('change');
            expect((event.target as HTMLInputElement).value).toBe('1');
            done();
          },
        },
        template: '<vue-number-input @change="onChange" />',
      });

      wrapper.get('.vue-number-input__input').setValue('1');
    });

    it('should trigger the native `input` event', (done) => {
      const wrapper = mount({
        components: {
          VueNumberInput,
        },
        methods: {
          onInput(event: Event) {
            expect(event.type).toBe('input');
            expect((event.target as HTMLInputElement).value).toBe('1');
            done();
          },
        },
        template: '<vue-number-input @input="onInput" />',
      });

      wrapper.get('.vue-number-input__input').setValue('1');
    });
  });
});


================================================
FILE: tests/methods.spec.ts
================================================
import { mount } from '@vue/test-utils';
import VueNumberInput from '../src';

describe('methods', () => {
  describe('increase', () => {
    it('should increase the number', () => {
      const wrapper = mount(VueNumberInput);

      expect(wrapper.vm.value).toBeNaN();
      wrapper.vm.increase();
      expect(wrapper.vm.value).toBe(1);
    });

    it('should not increase the number when the current value is equal to the maximum value', () => {
      const wrapper = mount(VueNumberInput, {
        props: {
          max: 0,
          modelValue: 0,
        },
      });

      expect(wrapper.vm.value).toBe(0);
      wrapper.vm.increase();
      expect(wrapper.vm.value).toBe(0);
    });
  });

  describe('decrease', () => {
    it('should decrease the number', () => {
      const wrapper = mount(VueNumberInput);

      expect(wrapper.vm.value).toBeNaN();
      wrapper.vm.decrease();
      expect(wrapper.vm.value).toBe(-1);
    });

    it('should not decrease the number when the current value is equal to the maximum value', () => {
      const wrapper = mount(VueNumberInput, {
        props: {
          min: 0,
          modelValue: 0,
        },
      });

      expect(wrapper.vm.value).toBe(0);
      wrapper.vm.decrease();
      expect(wrapper.vm.value).toBe(0);
    });
  });

  describe('setValue', () => {
    it('should change the value', () => {
      const wrapper = mount(VueNumberInput);

      expect(wrapper.vm.value).toBeNaN();
      wrapper.vm.setValue(1);
      expect(wrapper.vm.value).toBe(1);
    });

    it('should transform the given value when it is less than the minimum value', () => {
      const wrapper = mount(VueNumberInput, {
        props: {
          min: 0,
        },
      });

      expect(wrapper.vm.value).toBeNaN();
      wrapper.vm.setValue(-1);
      expect(wrapper.vm.value).toBe(0);
    });

    it('should transform the given value when it is greater than the maximum value', () => {
      const wrapper = mount(VueNumberInput, {
        props: {
          max: 0,
        },
      });

      expect(wrapper.vm.value).toBeNaN();
      wrapper.vm.setValue(1);
      expect(wrapper.vm.value).toBe(0);
    });

    it('should not transform the given value when the maximum value is less than the minimum value', () => {
      const wrapper = mount(VueNumberInput, {
        props: {
          max: -10,
          min: 10,
        },
      });

      expect(wrapper.vm.value).toBeNaN();
      wrapper.vm.setValue(1);
      expect(wrapper.vm.value).toBe(1);
    });
  });
});


================================================
FILE: tests/others.spec.ts
================================================
import { mount } from '@vue/test-utils';
import VueNumberInput from '../src';

describe('others', () => {
  it('should fix the `0.30000000000000004` problem', (done) => {
    const wrapper = mount({
      components: {
        VueNumberInput,
      },
      data() {
        return {
          value: 0.1,
        };
      },
      template: '<vue-number-input v-model="value" :step="0.2" controls />',
    });

    expect(wrapper.vm.value).toBe(0.1);
    wrapper.get('.vue-number-input__button--plus').trigger('click').then(() => {
      expect(wrapper.vm.value).toBe(0.3);
      done();
    });
  });

  it('should update the model value when value changed', (done) => {
    const wrapper = mount({
      components: {
        VueNumberInput,
      },
      data() {
        return {
          value: 0,
        };
      },
      template: '<vue-number-input v-model="value" />',
    });

    expect(wrapper.vm.value).toBe(0);
    wrapper.get('.vue-number-input__input').setValue(1).then(() => {
      expect(wrapper.vm.value).toBe(1);
      done();
    });
  });

  it('should not update the value when paste nothing', (done) => {
    const wrapper = mount({
      components: {
        VueNumberInput,
      },
      data() {
        return {
          value: 0,
        };
      },
      template: '<vue-number-input v-model="value" />',
    });

    expect(wrapper.vm.value).toBe(0);
    wrapper.get('.vue-number-input__input').trigger('paste').then(() => {
      expect(wrapper.vm.value).toBe(0);
      done();
    });
  });
});


================================================
FILE: tests/props.spec.ts
================================================
import { mount } from '@vue/test-utils';
import VueNumberInput from '../src';

describe('props', () => {
  describe('attrs', () => {
    it('should be undefined by default', () => {
      const wrapper = mount(VueNumberInput);

      expect(wrapper.props('attrs')).toBeUndefined();
    });

    it('should apply the given attributes', () => {
      const wrapper = mount(VueNumberInput, {
        props: {
          attrs: {
            tabindex: 0,
          },
        },
      });

      expect(wrapper.props('attrs')).toEqual({
        tabindex: 0,
      });
      expect((wrapper.get('.vue-number-input__input').element as HTMLInputElement).tabIndex).toBe(0);
    });
  });

  describe('center', () => {
    it('should not be center by default', () => {
      const wrapper = mount(VueNumberInput);

      expect(wrapper.props('center')).toBe(false);
      expect(wrapper.classes()).not.toContain('vue-number-input--center');
    });

    it('should be center', () => {
      const wrapper = mount(VueNumberInput, {
        props: {
          center: true,
        },
      });

      expect(wrapper.props('center')).toBe(true);
      expect(wrapper.classes()).toContain('vue-number-input--center');
    });
  });

  describe('controls', () => {
    it('should not display the controls by default', () => {
      const wrapper = mount(VueNumberInput);

      expect(wrapper.props('controls')).toBe(false);
      expect(wrapper.classes()).not.toContain('vue-number-input--controls');
      expect(wrapper.find('.vue-number-input__button').exists()).toBe(false);
    });

    it('should display the controls', () => {
      const wrapper = mount(VueNumberInput, {
        props: {
          controls: true,
        },
      });

      expect(wrapper.props('controls')).toBe(true);
      expect(wrapper.classes()).toContain('vue-number-input--controls');
      expect(wrapper.findAll('.vue-number-input__button').length).toBe(2);
    });

    it('should increase the number when click the plus control', (done) => {
      const wrapper = mount(VueNumberInput, {
        props: {
          controls: true,
        },
      });

      expect(wrapper.vm.value).toBeNaN();
      wrapper.get('.vue-number-input__button--plus').trigger('click').then(() => {
        expect(wrapper.vm.value).toBe(1);
        expect((wrapper.get('.vue-number-input__input').element as HTMLInputElement).value).toBe('1');
        done();
      });
    });

    it('should decrease the number when click the minus control', (done) => {
      const wrapper = mount(VueNumberInput, {
        props: {
          controls: true,
        },
      });

      expect(wrapper.vm.value).toBeNaN();
      wrapper.get('.vue-number-input__button--minus').trigger('click').then(() => {
        expect(wrapper.vm.value).toBe(-1);
        expect((wrapper.get('.vue-number-input__input').element as HTMLInputElement).value).toBe('-1');
        done();
      });
    });
  });

  describe('disabled', () => {
    it('should not be disabled by default', () => {
      const wrapper = mount(VueNumberInput, {
        props: {
          controls: true,
        },
      });

      expect(wrapper.props('disabled')).toBe(false);
      expect((wrapper.get('.vue-number-input__input').element as HTMLInputElement).disabled).toBe(false);
      expect((wrapper.get('.vue-number-input__button--plus').element as HTMLButtonElement).disabled).toBe(false);
      expect((wrapper.get('.vue-number-input__button--minus').element as HTMLButtonElement).disabled).toBe(false);
    });

    it('should by disabled', () => {
      const wrapper = mount(VueNumberInput, {
        props: {
          controls: true,
          disabled: true,
        },
      });

      expect(wrapper.props('disabled')).toBe(true);
      expect((wrapper.get('.vue-number-input__input').element as HTMLInputElement).disabled).toBe(true);
      expect((wrapper.get('.vue-number-input__button--plus').element as HTMLButtonElement).disabled).toBe(true);
      expect((wrapper.get('.vue-number-input__button--minus').element as HTMLButtonElement).disabled).toBe(true);
    });
  });

  describe('inline', () => {
    it('should not be inline by default', () => {
      const wrapper = mount(VueNumberInput);

      expect(wrapper.props('inline')).toBe(false);
      expect(wrapper.classes()).not.toContain('vue-number-input--inline');
    });

    it('should be inline', () => {
      const wrapper = mount(VueNumberInput, {
        props: {
          inline: true,
        },
      });

      expect(wrapper.props('inline')).toBe(true);
      expect(wrapper.classes()).toContain('vue-number-input--inline');
    });
  });

  describe('inputtable', () => {
    it('should be inputtable by default', () => {
      const wrapper = mount(VueNumberInput, {
        props: {
          controls: true,
        },
      });

      expect(wrapper.props('inputtable')).toBe(true);
      expect((wrapper.get('.vue-number-input__input').element as HTMLInputElement).readOnly).toBe(false);
      expect((wrapper.get('.vue-number-input__button--plus').element as HTMLButtonElement).disabled).toBe(false);
      expect((wrapper.get('.vue-number-input__button--minus').element as HTMLButtonElement).disabled).toBe(false);
    });

    it('should not be inputtable', () => {
      const wrapper = mount(VueNumberInput, {
        props: {
          controls: true,
          inputtable: false,
        },
      });

      expect(wrapper.props('inputtable')).toBe(false);
      expect((wrapper.get('.vue-number-input__input').element as HTMLInputElement).readOnly).toBe(true);
      expect((wrapper.get('.vue-number-input__button--plus').element as HTMLButtonElement).disabled).toBe(false);
      expect((wrapper.get('.vue-number-input__button--minus').element as HTMLButtonElement).disabled).toBe(false);
    });
  });

  describe('max', () => {
    it('should be `Infinity` by default', () => {
      const wrapper = mount(VueNumberInput);

      expect(wrapper.props('max')).toBe(Infinity);
      expect((wrapper.get('.vue-number-input__input').element as HTMLInputElement).max).toBe('Infinity');
    });

    it('should be equal to the given value', () => {
      const wrapper = mount(VueNumberInput, {
        props: {
          max: 10,
        },
      });

      expect(wrapper.props('max')).toBe(10);
      expect((wrapper.get('.vue-number-input__input').element as HTMLInputElement).max).toBe('10');
    });

    it('should not be greater than the given maximum value', () => {
      const wrapper = mount(VueNumberInput, {
        props: {
          modelValue: 11,
          max: 10,
        },
      });

      expect((wrapper.get('.vue-number-input__input').element as HTMLInputElement).value).toBe('10');
    });

    it('should fix the out of range value', () => {
      const wrapper = mount(VueNumberInput, {
        props: {
          max: 10,
        },
      });

      wrapper.get('.vue-number-input__input').setValue('11').then(() => {
        expect((wrapper.get('.vue-number-input__input').element as HTMLInputElement).value).toBe('10');
      });
    });
  });

  describe('min', () => {
    it('should be `-Infinity` by default', () => {
      const wrapper = mount(VueNumberInput);

      expect(wrapper.props('min')).toBe(-Infinity);
      expect((wrapper.get('.vue-number-input__input').element as HTMLInputElement).min).toBe('-Infinity');
    });

    it('should be equal to the given value', () => {
      const wrapper = mount(VueNumberInput, {
        props: {
          min: -10,
        },
      });

      expect(wrapper.props('min')).toBe(-10);
      expect((wrapper.get('.vue-number-input__input').element as HTMLInputElement).min).toBe('-10');
    });

    it('should not be less than the given minimum value', () => {
      const wrapper = mount(VueNumberInput, {
        props: {
          modelValue: -11,
          min: -10,
        },
      });

      expect((wrapper.get('.vue-number-input__input').element as HTMLInputElement).value).toBe('-10');
    });

    it('should fix the out of range value', () => {
      const wrapper = mount(VueNumberInput, {
        props: {
          min: -10,
        },
      });

      wrapper.get('.vue-number-input__input').setValue('-11').then(() => {
        expect((wrapper.get('.vue-number-input__input').element as HTMLInputElement).value).toBe('-10');
      });
    });
  });

  describe('name', () => {
    it('should be undefined by default', () => {
      const wrapper = mount(VueNumberInput);

      expect(wrapper.props('name')).toBeUndefined();
      expect((wrapper.get('.vue-number-input__input').element as HTMLInputElement).name).toBe('');
    });

    it('should be equal to the given value', () => {
      const wrapper = mount(VueNumberInput, {
        props: {
          name: 'digit',
        },
      });

      expect(wrapper.props('name')).toBe('digit');
      expect((wrapper.get('.vue-number-input__input').element as HTMLInputElement).name).toBe('digit');
    });
  });

  describe('placeholder', () => {
    it('should be undefined by default', () => {
      const wrapper = mount(VueNumberInput);

      expect(wrapper.props('placeholder')).toBeUndefined();
      expect((wrapper.get('.vue-number-input__input').element as HTMLInputElement).placeholder).toBe('');
    });

    it('should be equal to the given value', () => {
      const wrapper = mount(VueNumberInput, {
        props: {
          placeholder: 'Number input',
        },
      });

      expect(wrapper.props('placeholder')).toBe('Number input');
      expect((wrapper.get('.vue-number-input__input').element as HTMLInputElement).placeholder).toBe('Number input');
    });
  });

  describe('readonly', () => {
    it('should not be read-only by default', () => {
      const wrapper = mount(VueNumberInput, {
        props: {
          controls: true,
        },
      });

      expect(wrapper.props('readonly')).toBe(false);
      expect((wrapper.get('.vue-number-input__input').element as HTMLInputElement).readOnly).toBe(false);
      expect((wrapper.get('.vue-number-input__button--plus').element as HTMLButtonElement).disabled).toBe(false);
      expect((wrapper.get('.vue-number-input__button--minus').element as HTMLButtonElement).disabled).toBe(false);
    });

    it('should be read-only', () => {
      const wrapper = mount(VueNumberInput, {
        props: {
          controls: true,
          readonly: true,
        },
      });

      expect(wrapper.props('readonly')).toBe(true);
      expect((wrapper.get('.vue-number-input__input').element as HTMLInputElement).readOnly).toBe(true);
      expect((wrapper.get('.vue-number-input__button--plus').element as HTMLButtonElement).disabled).toBe(true);
      expect((wrapper.get('.vue-number-input__button--minus').element as HTMLButtonElement).disabled).toBe(true);
    });
  });

  describe('rounded', () => {
    it('should not round the number by default', () => {
      const wrapper = mount(VueNumberInput, {
        props: {
          modelValue: 1.5,
        },
      });

      expect(wrapper.props('rounded')).toBe(false);
      expect((wrapper.get('.vue-number-input__input').element as HTMLInputElement).value).toBe('1.5');
    });

    it('should round the number', () => {
      const wrapper = mount(VueNumberInput, {
        props: {
          modelValue: 1.5,
          rounded: true,
        },
      });

      expect(wrapper.props('rounded')).toBe(true);
      expect((wrapper.get('.vue-number-input__input').element as HTMLInputElement).value).toBe('2');
    });
  });

  describe('size', () => {
    it('should be small size', () => {
      const wrapper = mount(VueNumberInput, {
        props: {
          size: 'small',
        },
      });

      expect(wrapper.props('size')).toBe('small');
      expect(wrapper.classes()).toContain('vue-number-input--small');
    });

    it('should be large size', () => {
      const wrapper = mount(VueNumberInput, {
        props: {
          size: 'large',
        },
      });

      expect(wrapper.props('size')).toBe('large');
      expect(wrapper.classes()).toContain('vue-number-input--large');
    });
  });

  describe('step', () => {
    it('should be `1` by default', (done) => {
      const wrapper = mount(VueNumberInput, {
        props: {
          controls: true,
        },
      });

      expect(wrapper.props('step')).toBe(1);
      wrapper.get('.vue-number-input__button--plus').trigger('click').then(() => {
        expect((wrapper.get('.vue-number-input__input').element as HTMLInputElement).step).toBe('1');
        done();
      });
    });

    it('should match the given value', (done) => {
      const wrapper = mount(VueNumberInput, {
        props: {
          controls: true,
          step: 2,
        },
      });

      expect(wrapper.props('step')).toBe(2);
      wrapper.get('.vue-number-input__button--plus').trigger('click').then(() => {
        expect((wrapper.get('.vue-number-input__input').element as HTMLInputElement).step).toBe('2');
        done();
      });
    });
  });

  describe('modelValue', () => {
    it('should be `NaN` by default', () => {
      const wrapper = mount(VueNumberInput);

      expect(wrapper.props('modelValue')).toBeNaN();
      expect((wrapper.get('.vue-number-input__input').element as HTMLInputElement).value).toBe('');
    });

    it('should be equal to the given value', () => {
      const wrapper = mount(VueNumberInput, {
        props: {
          modelValue: 10,
        },
      });

      expect(wrapper.props('modelValue')).toBe(10);
      expect((wrapper.get('.vue-number-input__input').element as HTMLInputElement).value).toBe('10');
    });
  });
});


================================================
FILE: tsconfig.eslint.json
================================================
{
  "extends": "./tsconfig",
  "include": [
    "*.js",
    ".*.js",
    "docs/**/*",
    "src/**/*",
    "tests/**/*"
  ]
}


================================================
FILE: tsconfig.json
================================================
{
  "compilerOptions": {
    "allowSyntheticDefaultImports": true,
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "strict": true,
    "target": "esnext"
  },
  "include": [
    "src/**/*",
    "docs/**/*",
    "tests/**/*"
  ]
}


================================================
FILE: webpack.config.js
================================================
const path = require('path');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const VueLoaderPlugin = require('vue-loader/dist/plugin').default;
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

module.exports = (env) => ({
  mode: env.production ? 'production' : 'development',
  entry: './docs',
  output: {
    path: path.resolve(__dirname, './docs/dist'),
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        use: 'babel-loader',
      },
      {
        test: /\.ts$/,
        loader: 'ts-loader',
        options: {
          appendTsSuffixTo: [/\.vue$/],
        },
      },
      {
        test: /\.vue$/,
        loader: 'vue-loader',
      },
      {
        test: /\.scss$/,
        use: [
          MiniCssExtractPlugin.loader,
          'css-loader',
          'sass-loader',
        ],
      },
      {
        test: /\.md$/,
        use: [
          'vue-loader',
          {
            loader: 'markdown-to-vue-loader',
            options: {
              componentWrapper: '<demo-block></demo-block>',
              tableClass: 'table',
              tableWrapper: '<div class="table-responsive"></div>',
            },
          },
        ],
      },
    ],
  },
  plugins: [
    new HtmlWebpackPlugin({
      filename: 'index.html',
      template: './docs/index.html',
    }),
    new MiniCssExtractPlugin(),
    new VueLoaderPlugin(),
    new webpack.DefinePlugin({
      __VUE_OPTIONS_API__: true,
      __VUE_PROD_DEVTOOLS__: false,
    }),
  ],
  externals: env.production ? {
    vue: 'Vue',
  } : {},
  resolve: {
    alias: {
      vue$: 'vue/dist/vue.esm-bundler',
    },
    extensions: ['.js', '.json', '.ts', '.d.ts', '.vue'],
  },
});
Download .txt
gitextract_qqq1rvje/

├── .browserslistrc
├── .eslintignore
├── .eslintrc.js
├── .github/
│   └── workflows/
│       ├── ci.yml
│       └── deploy.yml
├── .gitignore
├── .husky/
│   ├── commit-msg
│   └── pre-commit
├── .stylelintignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── babel.config.js
├── commitlint.config.js
├── docs/
│   ├── app.vue
│   ├── components/
│   │   └── demo-block.vue
│   ├── index.html
│   └── index.ts
├── jest.config.js
├── lint-staged.config.js
├── package.json
├── postcss.config.js
├── rollup.config.js
├── src/
│   ├── README.md
│   ├── index.ts
│   ├── shims.d.ts
│   └── vue-number-input.vue
├── stylelint.config.js
├── tests/
│   ├── events.spec.ts
│   ├── methods.spec.ts
│   ├── others.spec.ts
│   └── props.spec.ts
├── tsconfig.eslint.json
├── tsconfig.json
└── webpack.config.js
Download .txt
SYMBOL INDEX (6 symbols across 2 files)

FILE: tests/events.spec.ts
  method onModelValueChange (line 12) | onModelValueChange(newValue: number) {
  method onChange (line 31) | onChange(event: Event) {
  method onInput (line 49) | onInput(event: Event) {

FILE: tests/others.spec.ts
  method data (line 10) | data() {
  method data (line 30) | data() {
  method data (line 50) | data() {
Condensed preview — 35 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (64K chars).
[
  {
    "path": ".browserslistrc",
    "chars": 19,
    "preview": "defaults\nnot ie 11\n"
  },
  {
    "path": ".eslintignore",
    "chars": 36,
    "preview": "*.local*\ncoverage\ndist\nnode_modules\n"
  },
  {
    "path": ".eslintrc.js",
    "chars": 736,
    "preview": "module.exports = {\n  root: true,\n  env: {\n    browser: true,\n    node: true,\n  },\n  extends: [\n    'airbnb-typescript/ba"
  },
  {
    "path": ".github/workflows/ci.yml",
    "chars": 381,
    "preview": "name: CI\n\non:\n  push:\n    branches: [main]\n  pull_request:\n    branches: [main]\n\njobs:\n  build:\n    runs-on: ubuntu-late"
  },
  {
    "path": ".github/workflows/deploy.yml",
    "chars": 675,
    "preview": "name: Deploy\n\non:\n  push:\n    tags:\n      - v2.*\n  workflow_dispatch:\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n\n    st"
  },
  {
    "path": ".gitignore",
    "chars": 58,
    "preview": "*.local*\n*.log\n*.map\n.DS_Store\ncoverage\ndist\nnode_modules\n"
  },
  {
    "path": ".husky/commit-msg",
    "chars": 67,
    "preview": "#!/bin/sh\n. \"$(dirname \"$0\")/_/husky.sh\"\n\nnpx commitlint --edit $1\n"
  },
  {
    "path": ".husky/pre-commit",
    "chars": 58,
    "preview": "#!/bin/sh\n. \"$(dirname \"$0\")/_/husky.sh\"\n\nnpx lint-staged\n"
  },
  {
    "path": ".stylelintignore",
    "chars": 36,
    "preview": "*.local*\ncoverage\ndist\nnode_modules\n"
  },
  {
    "path": "CHANGELOG.md",
    "chars": 7504,
    "preview": "## [2.0.1](https://github.com/fengyuanchen/vue-number-input/compare/v2.0.0...v2.0.1) (2022-02-13)\n\n\n### Bug Fixes\n\n* rou"
  },
  {
    "path": "LICENSE",
    "chars": 1084,
    "preview": "The MIT License (MIT)\n\nCopyright 2018-present Chen Fengyuan\n\nPermission is hereby granted, free of charge, to any person"
  },
  {
    "path": "README.md",
    "chars": 2086,
    "preview": "# vue-number-input\n\n[![Coverage Status](https://img.shields.io/codecov/c/github/fengyuanchen/vue-number-input.svg)](http"
  },
  {
    "path": "babel.config.js",
    "chars": 56,
    "preview": "module.exports = {\n  presets: ['@babel/preset-env'],\n};\n"
  },
  {
    "path": "commitlint.config.js",
    "chars": 79,
    "preview": "module.exports = {\n  extends: [\n    '@commitlint/config-conventional',\n  ],\n};\n"
  },
  {
    "path": "docs/app.vue",
    "chars": 4836,
    "preview": "<template>\n  <div class=\"app\">\n    <header class=\"navbar navbar-light navbar-expand-md\">\n      <nav class=\"container\">\n "
  },
  {
    "path": "docs/components/demo-block.vue",
    "chars": 562,
    "preview": "<template>\n  <div class=\"demo-block\">\n    <slot />\n  </div>\n</template>\n\n<script lang=\"ts\">\nimport { defineComponent } f"
  },
  {
    "path": "docs/index.html",
    "chars": 1281,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\">\n    <meta http-equiv=\"x-ua-compatible\" content=\"ie="
  },
  {
    "path": "docs/index.ts",
    "chars": 295,
    "preview": "import { createApp } from 'vue';\nimport App from './app.vue';\nimport DemoBlock from './components/demo-block.vue';\nimpor"
  },
  {
    "path": "jest.config.js",
    "chars": 345,
    "preview": "module.exports = {\n  preset: 'ts-jest',\n  collectCoverage: true,\n  coverageDirectory: 'coverage',\n  coverageReporters: ["
  },
  {
    "path": "lint-staged.config.js",
    "chars": 98,
    "preview": "module.exports = {\n  '*.{js,ts,vue}': 'eslint --fix',\n  '*.{css,scss,vue}': 'stylelint --fix',\n};\n"
  },
  {
    "path": "package.json",
    "chars": 3307,
    "preview": "{\n  \"name\": \"@chenfengyuan/vue-number-input\",\n  \"version\": \"2.0.1\",\n  \"description\": \"Number input component for Vue 3.\""
  },
  {
    "path": "postcss.config.js",
    "chars": 81,
    "preview": "module.exports = {\n  plugins: {\n    stylelint: {\n      fix: true,\n    },\n  },\n};\n"
  },
  {
    "path": "rollup.config.js",
    "chars": 1439,
    "preview": "import createBanner from 'create-banner';\nimport postcss from 'rollup-plugin-postcss';\nimport typescript from 'rollup-pl"
  },
  {
    "path": "src/README.md",
    "chars": 4277,
    "preview": "# Number Input\n\n> Number input with optional controls.\n\n## Basic usage\n\n```html\n<template>\n  <p>Value: {{ value }}</p>\n "
  },
  {
    "path": "src/index.ts",
    "chars": 85,
    "preview": "import VueNumberInput from './vue-number-input.vue';\n\nexport default VueNumberInput;\n"
  },
  {
    "path": "src/shims.d.ts",
    "chars": 152,
    "preview": "declare module '*.vue' {\n  const content: any;\n\n  export default content;\n}\n\ndeclare module '*.md' {\n  const content: an"
  },
  {
    "path": "src/vue-number-input.vue",
    "chars": 8074,
    "preview": "<template>\n  <div\n    class=\"vue-number-input\"\n    :class=\"{\n      'vue-number-input--inline': inline,\n      'vue-number"
  },
  {
    "path": "stylelint.config.js",
    "chars": 248,
    "preview": "module.exports = {\n  extends: 'stylelint-config-recommended-vue/scss',\n  plugins: [\n    'stylelint-order',\n  ],\n  rules:"
  },
  {
    "path": "tests/events.spec.ts",
    "chars": 1674,
    "preview": "import { mount } from '@vue/test-utils';\nimport VueNumberInput from '../src';\n\ndescribe('events', () => {\n  describe('cu"
  },
  {
    "path": "tests/methods.spec.ts",
    "chars": 2535,
    "preview": "import { mount } from '@vue/test-utils';\nimport VueNumberInput from '../src';\n\ndescribe('methods', () => {\n  describe('i"
  },
  {
    "path": "tests/others.spec.ts",
    "chars": 1536,
    "preview": "import { mount } from '@vue/test-utils';\nimport VueNumberInput from '../src';\n\ndescribe('others', () => {\n  it('should f"
  },
  {
    "path": "tests/props.spec.ts",
    "chars": 13650,
    "preview": "import { mount } from '@vue/test-utils';\nimport VueNumberInput from '../src';\n\ndescribe('props', () => {\n  describe('att"
  },
  {
    "path": "tsconfig.eslint.json",
    "chars": 125,
    "preview": "{\n  \"extends\": \"./tsconfig\",\n  \"include\": [\n    \"*.js\",\n    \".*.js\",\n    \"docs/**/*\",\n    \"src/**/*\",\n    \"tests/**/*\"\n "
  },
  {
    "path": "tsconfig.json",
    "chars": 249,
    "preview": "{\n  \"compilerOptions\": {\n    \"allowSyntheticDefaultImports\": true,\n    \"moduleResolution\": \"node\",\n    \"resolveJsonModul"
  },
  {
    "path": "webpack.config.js",
    "chars": 1748,
    "preview": "const path = require('path');\nconst webpack = require('webpack');\nconst HtmlWebpackPlugin = require('html-webpack-plugin"
  }
]

About this extraction

This page contains the full source code of the fengyuanchen/vue-number-input GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 35 files (58.1 KB), approximately 16.9k tokens, and a symbol index with 6 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!