├── .nvmrc
├── .husky
├── .gitignore
├── pre-commit
├── commit-msg
└── pre-push
├── FUNDING.yml
├── babel.config.json
├── .DS_Store
├── examples
├── .gitignore
├── public
│ └── favicon.ico
├── src
│ ├── assets
│ │ └── logo.png
│ ├── shims-vue.d.ts
│ ├── store.ts
│ ├── main.ts
│ ├── components
│ │ └── api-reference-link.vue
│ ├── views
│ │ ├── LimitingSelections.vue
│ │ ├── Vuex.vue
│ │ ├── BasicUsage.vue
│ │ ├── CustomEmptyModelValue.vue
│ │ ├── CustomTag.vue
│ │ ├── SelectAll.vue
│ │ ├── CustomDropdown.vue
│ │ ├── Vuelidate.vue
│ │ ├── Creatable.vue
│ │ └── Group.vue
│ ├── App.vue
│ └── route.ts
├── sandbox.config.json
├── vite.config.ts
├── index.html
├── tsconfig.json
└── package.json
├── test
├── .DS_Store
└── unit
│ ├── multiple.spec.ts
│ ├── disabled.spec.ts
│ ├── close-on-select.spec.ts
│ └── visible-option.spec.ts
├── commitlint.config.js
├── .prettierignore
├── .versionrc.js
├── .gitignore
├── src
├── shims-vue.d.ts
├── images
│ └── delete.svg
├── crud.js
├── components
│ └── tags.vue
└── normalize.ts
├── cypress.config.ts
├── renovate.json
├── cypress
├── fixtures
│ └── example.json
├── e2e
│ ├── slots
│ │ ├── tag
│ │ │ ├── index.cy.js
│ │ │ ├── remove.cy.js
│ │ │ ├── index.html
│ │ │ └── remove.html
│ │ ├── dropdown-item
│ │ │ ├── index.cy.js
│ │ │ └── index.html
│ │ ├── toggle
│ │ │ ├── index.cy.js
│ │ │ └── index.html
│ │ ├── loading
│ │ │ ├── index.cy.js
│ │ │ └── index.html
│ │ └── label
│ │ │ ├── index.cy.js
│ │ │ ├── index.html
│ │ │ └── multiple.html
│ ├── a11y
│ │ ├── role
│ │ │ ├── listbox
│ │ │ │ ├── loading.cy.js
│ │ │ │ ├── disabled.cy.js
│ │ │ │ ├── mutiple.cy.js
│ │ │ │ ├── disabled.html
│ │ │ │ ├── loading.html
│ │ │ │ └── multiple.html
│ │ │ └── option
│ │ │ │ ├── index.html
│ │ │ │ └── index.cy.js
│ │ └── kdb-interaction
│ │ │ ├── single.html
│ │ │ └── multiple.html
│ ├── props
│ │ ├── taggable
│ │ │ ├── group
│ │ │ │ ├── index.cy.js
│ │ │ │ └── index.html
│ │ │ ├── index.cy.js
│ │ │ ├── without.html
│ │ │ └── with.html
│ │ ├── searchable
│ │ │ ├── prevent-submit.cy.js
│ │ │ ├── without.html
│ │ │ ├── default.html
│ │ │ ├── index.cy.js
│ │ │ ├── prevent-submit.html
│ │ │ └── with.html
│ │ ├── loading
│ │ │ ├── index.cy.js
│ │ │ ├── without.html
│ │ │ └── with.html
│ │ ├── placeholder
│ │ │ ├── index.cy.js
│ │ │ ├── without.html
│ │ │ └── with.html
│ │ ├── search-placeholder
│ │ │ ├── index.cy.js
│ │ │ ├── without.html
│ │ │ └── with.html
│ │ ├── clear-on-select
│ │ │ ├── index.cy.js
│ │ │ └── index.html
│ │ ├── visible-options
│ │ │ ├── index.cy.js
│ │ │ ├── without.html
│ │ │ └── with.html
│ │ ├── clear-on-close
│ │ │ ├── index.cy.js
│ │ │ └── index.html
│ │ ├── collapse-tags
│ │ │ ├── index.cy.js
│ │ │ ├── without.html
│ │ │ └── with.html
│ │ ├── empty-model-value
│ │ │ ├── index.cy.js
│ │ │ ├── default.html
│ │ │ └── empty-string.html
│ │ ├── disabled
│ │ │ ├── group
│ │ │ │ ├── index.cy.js
│ │ │ │ ├── disabled-by-group.html
│ │ │ │ └── disabled-by-values.html
│ │ │ ├── single
│ │ │ │ ├── without.html
│ │ │ │ └── with.html
│ │ │ ├── multiple
│ │ │ │ ├── without.html
│ │ │ │ └── with.html
│ │ │ └── taggable
│ │ │ │ ├── without.html
│ │ │ │ └── with.html
│ │ ├── value-by
│ │ │ ├── index.cy.js
│ │ │ ├── without.html
│ │ │ ├── with-path.html
│ │ │ └── with-function.html
│ │ ├── label-by
│ │ │ ├── index.cy.js
│ │ │ ├── without.html
│ │ │ ├── with-path.html
│ │ │ └── with-function.html
│ │ ├── max
│ │ │ ├── index.cy.js
│ │ │ ├── without.html
│ │ │ └── with.html
│ │ ├── multiple
│ │ │ ├── index.cy.js
│ │ │ ├── without.html
│ │ │ └── with.html
│ │ ├── min
│ │ │ ├── single-without-min.html
│ │ │ ├── without.html
│ │ │ ├── single-with-min.html
│ │ │ ├── with.html
│ │ │ └── index.cy.js
│ │ ├── close-on-select
│ │ │ ├── single
│ │ │ │ ├── without.html
│ │ │ │ └── with.html
│ │ │ └── multiple
│ │ │ │ ├── without.html
│ │ │ │ └── with.html
│ │ ├── hide-selected
│ │ │ ├── without.html
│ │ │ ├── with.html
│ │ │ └── index.cy.js
│ │ ├── options
│ │ │ ├── raw-options.html
│ │ │ ├── group
│ │ │ │ ├── index.html
│ │ │ │ └── index.cy.js
│ │ │ ├── disabled-options.html
│ │ │ ├── change-options.html
│ │ │ └── change-options-with-object.html
│ │ └── model
│ │ │ ├── multiple
│ │ │ ├── change-model.html
│ │ │ └── change-model-with-object.html
│ │ │ └── single
│ │ │ └── change-model.html
│ ├── events
│ │ ├── selected
│ │ │ ├── index.cy.js
│ │ │ └── index.html
│ │ ├── search-input
│ │ │ ├── index.cy.js
│ │ │ └── index.html
│ │ ├── focus
│ │ │ └── index.html
│ │ ├── removed
│ │ │ ├── single.html
│ │ │ ├── index.html
│ │ │ └── index.cy.js
│ │ ├── blur
│ │ │ └── index.html
│ │ ├── search-focus
│ │ │ └── index.html
│ │ ├── search-change
│ │ │ ├── index.html
│ │ │ └── index.cy.js
│ │ ├── search-blur
│ │ │ └── index.html
│ │ └── sequence-of-events
│ │ │ └── single.html
│ └── behavior
│ │ ├── search
│ │ ├── index.cy.js
│ │ └── index.html
│ │ ├── tab
│ │ ├── index.cy.js
│ │ ├── without-taggable.html
│ │ └── with-taggable.html
│ │ ├── pointer
│ │ ├── without-searchable.html
│ │ ├── highlight-selected.html
│ │ ├── with-searchable.html
│ │ ├── highlight-selected-with-multiple.html
│ │ ├── removable.html
│ │ └── with-disabled.html
│ │ └── open-and-close
│ │ ├── without-searchable-and-taggable.html
│ │ ├── with-searchable.html
│ │ ├── with-taggable.html
│ │ └── with-searchable-and-taggable.html
└── support
│ ├── e2e.js
│ └── commands.js
├── prettier.config.js
├── docs
├── package.json
├── examples.md
└── .vitepress
│ └── config.js
├── README.md
├── jest.config.js
├── CONTRIBUTING.md
├── .github
├── ISSUE_TEMPLATE
│ ├── feature_request.md
│ └── bug_report.md
└── workflows
│ ├── release.yml
│ ├── docs.yml
│ └── examples.yml
├── tsconfig.json
├── LICENSE
└── script
└── build.js
/.nvmrc:
--------------------------------------------------------------------------------
1 | 22
2 |
--------------------------------------------------------------------------------
/.husky/.gitignore:
--------------------------------------------------------------------------------
1 | _
2 |
--------------------------------------------------------------------------------
/FUNDING.yml:
--------------------------------------------------------------------------------
1 | buy_me_a_coffee: iendeavor
2 |
--------------------------------------------------------------------------------
/babel.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["@babel/preset-env"]
3 | }
4 |
--------------------------------------------------------------------------------
/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/soc221b/vue-next-select/HEAD/.DS_Store
--------------------------------------------------------------------------------
/examples/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .DS_Store
3 | dist
4 | dist-ssr
5 | *.local
6 |
--------------------------------------------------------------------------------
/test/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/soc221b/vue-next-select/HEAD/test/.DS_Store
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | . "$(dirname "$0")/_/husky.sh"
3 |
4 | pnpm run lint
5 |
--------------------------------------------------------------------------------
/commitlint.config.js:
--------------------------------------------------------------------------------
1 | module.exports = { extends: ['@commitlint/config-conventional'] }
2 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | dist
2 | node_modules
3 | coverage
4 | CHANGELOG.md
5 | pnpm-lock.yaml
6 | LICENSE
7 |
--------------------------------------------------------------------------------
/.husky/commit-msg:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | . "$(dirname "$0")/_/husky.sh"
3 |
4 | pnpm exec commitlint --edit $1
5 |
--------------------------------------------------------------------------------
/.versionrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | scripts: {
3 | postbump: 'pnpm run build',
4 | },
5 | }
6 |
--------------------------------------------------------------------------------
/examples/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/soc221b/vue-next-select/HEAD/examples/public/favicon.ico
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | cypress/screenshots
2 | cypress/videos
3 | node_modules
4 | coverage
5 | .vscode
6 | .DS_Store
7 | dist
8 |
--------------------------------------------------------------------------------
/examples/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/soc221b/vue-next-select/HEAD/examples/src/assets/logo.png
--------------------------------------------------------------------------------
/.husky/pre-push:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | . "$(dirname "$0")/_/husky.sh"
3 |
4 | git diff --exit-code
5 | git diff --staged --exit-code
6 | pnpm run lint
7 | pnpm run build
8 | pnpm run test
9 |
--------------------------------------------------------------------------------
/src/shims-vue.d.ts:
--------------------------------------------------------------------------------
1 | declare module '*.vue' {
2 | import { DefineComponent } from 'vue'
3 | const component: DefineComponent<{}, {}, any>
4 | export default component
5 | }
6 |
--------------------------------------------------------------------------------
/examples/sandbox.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "infiniteLoopProtection": true,
3 | "hardReloadOnChange": false,
4 | "view": "browser",
5 | "container": {
6 | "node": "12"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/cypress.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'cypress'
2 |
3 | export default defineConfig({
4 | video: false,
5 | e2e: {
6 | excludeSpecPattern: ['**/*.html'],
7 | },
8 | })
9 |
--------------------------------------------------------------------------------
/examples/src/shims-vue.d.ts:
--------------------------------------------------------------------------------
1 | declare module '*.vue' {
2 | import { DefineComponent } from 'vue'
3 | const component: DefineComponent<{}, {}, any>
4 | export default component
5 | }
6 |
--------------------------------------------------------------------------------
/renovate.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json",
3 | "automerge": true,
4 | "automergeStrategy": "rebase",
5 | "extends": ["config:base"]
6 | }
7 |
--------------------------------------------------------------------------------
/cypress/fixtures/example.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Using fixtures to represent data",
3 | "email": "hello@cypress.io",
4 | "body": "Fixtures are a great way to mock data for responses to routes"
5 | }
6 |
--------------------------------------------------------------------------------
/examples/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import vue from '@vitejs/plugin-vue'
3 |
4 | // https://vitejs.dev/config/
5 | export default defineConfig({
6 | plugins: [vue()],
7 | })
8 |
--------------------------------------------------------------------------------
/examples/src/store.ts:
--------------------------------------------------------------------------------
1 | import { createStore } from 'vuex'
2 |
3 | export default createStore({
4 | state: {
5 | techStack: null,
6 | },
7 | mutations: {
8 | SET_TECH_STACK(state, value) {
9 | state.techStack = value
10 | },
11 | },
12 | })
13 |
--------------------------------------------------------------------------------
/prettier.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | printWidth: 120,
3 | tabWidth: 2,
4 | useTabs: false,
5 | semi: false,
6 | singleQuote: true,
7 | quoteProps: 'as-needed',
8 | trailingComma: 'all',
9 | bracketSpacing: true,
10 | arrowParens: 'avoid',
11 | proseWrap: 'always',
12 | endOfLine: 'lf',
13 | }
14 |
--------------------------------------------------------------------------------
/cypress/e2e/slots/tag/index.cy.js:
--------------------------------------------------------------------------------
1 | ///
2 | import path from 'path'
3 |
4 | context('tag', () => {
5 | it('should expose slot', () => {
6 | cy.visit(path.join(__dirname, 'index.html'))
7 | cy.get('.vue-select').click()
8 |
9 | cy.get('.vue-tag').should('have.text', 'I')
10 | })
11 | })
12 |
--------------------------------------------------------------------------------
/cypress/e2e/slots/dropdown-item/index.cy.js:
--------------------------------------------------------------------------------
1 | ///
2 | import path from 'path'
3 |
4 | context('dropdown-item', () => {
5 | it('should expose slot', () => {
6 | cy.visit(path.join(__dirname, 'index.html'))
7 | cy.get('.vue-select').click()
8 |
9 | cy.get('.vue-dropdown-item').should('have.text', 'I')
10 | })
11 | })
12 |
--------------------------------------------------------------------------------
/docs/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "docs",
3 | "version": "1.0.0",
4 | "main": "index.js",
5 | "license": "MIT",
6 | "private": true,
7 | "scripts": {
8 | "docs:dev": "vitepress dev",
9 | "docs:build": "vitepress build",
10 | "docs:serve": "vitepress serve"
11 | },
12 | "devDependencies": {
13 | "vitepress": "^1.0.0"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/examples/src/main.ts:
--------------------------------------------------------------------------------
1 | import { createApp } from 'vue'
2 | // @ts-ignore-next
3 | import App from './App.vue'
4 | import route from './route'
5 | import store from './store'
6 | import 'vue-next-select/dist/index.css'
7 | import ApiReferenceLink from './components/api-reference-link.vue'
8 |
9 | createApp(App).component('api', ApiReferenceLink).use(route).use(store).mount('#app')
10 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # vue-next-select
2 |
3 | ## Documentation
4 |
5 | https://iendeavor.github.io/vue-next-select/
6 |
7 | ## Contributing
8 |
9 | Please make sure to read the [contributing guide](./CONTRIBUTING.md) before making a pull request.
10 |
11 | ```bash
12 | # build package
13 | pnpm run build
14 |
15 | # run linter
16 | pnpm run lint
17 |
18 | # run tests
19 | pnpm run test
20 | ```
21 |
--------------------------------------------------------------------------------
/examples/src/components/api-reference-link.vue:
--------------------------------------------------------------------------------
1 |
2 | {{ prop }}
3 |
4 |
5 |
17 |
--------------------------------------------------------------------------------
/examples/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Vue-next-select
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/cypress/e2e/a11y/role/listbox/loading.cy.js:
--------------------------------------------------------------------------------
1 | ///
2 | import path from 'path'
3 |
4 | context('listbox', () => {
5 | // https://www.w3.org/TR/wai-aria-1.1/#listbox
6 |
7 | it('should set aria-busy to true when loading options', () => {
8 | cy.visit(path.join(__dirname, 'loading.html'))
9 |
10 | cy.get('[id="vs1-listbox"]').should('have.attr', 'aria-busy', 'true')
11 | })
12 | })
13 |
--------------------------------------------------------------------------------
/cypress/e2e/slots/tag/remove.cy.js:
--------------------------------------------------------------------------------
1 | ///
2 | import path from 'path'
3 |
4 | context('tag', () => {
5 | it('should expose remove function', () => {
6 | cy.visit(path.join(__dirname, 'remove.html'))
7 | cy.get('.vue-tag.selected').should('have.length', 1)
8 |
9 | cy.get('.vue-tag.selected').click()
10 |
11 | cy.get('.vue-tag.selected').should('have.length', 0)
12 | })
13 | })
14 |
--------------------------------------------------------------------------------
/cypress/e2e/a11y/role/listbox/disabled.cy.js:
--------------------------------------------------------------------------------
1 | ///
2 | import path from 'path'
3 |
4 | context('listbox', () => {
5 | // https://www.w3.org/TR/wai-aria-1.1/#listbox
6 |
7 | it('should set aria-busy to true when disabled options', () => {
8 | cy.visit(path.join(__dirname, 'disabled.html'))
9 |
10 | cy.get('[id="vs1-listbox"]').should('have.attr', 'aria-disabled', 'true')
11 | })
12 | })
13 |
--------------------------------------------------------------------------------
/cypress/e2e/props/taggable/group/index.cy.js:
--------------------------------------------------------------------------------
1 | ///
2 | import path from 'path'
3 |
4 | context('group', () => {
5 | it('should not show group option', () => {
6 | cy.visit(path.join(__dirname, 'index.html'))
7 | cy.get('.vue-select').click()
8 |
9 | cy.get('.vue-dropdown-item.group').trigger('click')
10 |
11 | cy.get('.vue-tag.selected').should('have.length', 3)
12 | })
13 | })
14 |
--------------------------------------------------------------------------------
/examples/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "esnext",
4 | "module": "esnext",
5 | "moduleResolution": "node",
6 | "strict": true,
7 | "jsx": "preserve",
8 | "sourceMap": true,
9 | "resolveJsonModule": true,
10 | "esModuleInterop": true,
11 | "lib": ["esnext", "dom"],
12 | "types": ["vite/client"]
13 | },
14 | "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"]
15 | }
16 |
--------------------------------------------------------------------------------
/cypress/e2e/a11y/role/listbox/mutiple.cy.js:
--------------------------------------------------------------------------------
1 | ///
2 | import path from 'path'
3 |
4 | context('listbox', () => {
5 | // https://www.w3.org/TR/wai-aria-1.1/#listbox
6 |
7 | it('should set aria-multiselectable to true when multiple options can be selected', () => {
8 | cy.visit(path.join(__dirname, 'multiple.html'))
9 |
10 | cy.get('[id="vs1-listbox"]').should('have.attr', 'aria-multiselectable', 'true')
11 | })
12 | })
13 |
--------------------------------------------------------------------------------
/cypress/e2e/slots/toggle/index.cy.js:
--------------------------------------------------------------------------------
1 | ///
2 | import path from 'path'
3 |
4 | context('toggle', () => {
5 | it('should expose slot', () => {
6 | cy.visit(path.join(__dirname, 'index.html'))
7 |
8 | cy.get('#custom-toggle').should('exist')
9 | cy.get('#custom-toggle').should('have.text', 'Closing')
10 |
11 | cy.get('#custom-toggle').click()
12 |
13 | cy.get('#custom-toggle').should('have.text', 'Opening')
14 | })
15 | })
16 |
--------------------------------------------------------------------------------
/cypress/e2e/props/searchable/prevent-submit.cy.js:
--------------------------------------------------------------------------------
1 | ///
2 | import path from 'path'
3 |
4 | context('prevent-submit', () => {
5 | it('should prevent submit when press enter key', () => {
6 | cy.visit(path.join(__dirname, 'prevent-submit.html'))
7 | cy.get('.vue-select').click()
8 |
9 | cy.get('input').type('{enter}')
10 |
11 | cy.get('.icon.arrow-downward').click()
12 | cy.get('button').should('contain.text', 'Submit!')
13 | })
14 | })
15 |
--------------------------------------------------------------------------------
/cypress/e2e/slots/loading/index.cy.js:
--------------------------------------------------------------------------------
1 | ///
2 | import path from 'path'
3 |
4 | context('loading', () => {
5 | it('should expose slot', () => {
6 | cy.visit(path.join(__dirname, 'index.html'))
7 |
8 | cy.get('#custom-loading').should('not.exist')
9 |
10 | cy.get('input').type('i')
11 | cy.get('#custom-loading').should('exist')
12 |
13 | cy.wait(100).then(() => {
14 | cy.get('#custom-loading').should('not.exist')
15 | })
16 | })
17 | })
18 |
--------------------------------------------------------------------------------
/src/images/delete.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | // For a detailed explanation regarding each configuration property, visit:
2 | // https://jestjs.io/docs/en/configuration.html
3 |
4 | module.exports = {
5 | collectCoverage: false,
6 | collectCoverageFrom: ['dist/vue-next-select.es.js'],
7 | coverageDirectory: 'coverage',
8 | coverageReporters: ['json', 'text', 'lcov', 'clover'],
9 | errorOnDeprecated: true,
10 | verbose: true,
11 | preset: 'ts-jest',
12 | testEnvironment: 'jsdom',
13 | transform: {
14 | '^.+\\.jsx?$': 'babel-jest',
15 | },
16 | }
17 |
--------------------------------------------------------------------------------
/cypress/e2e/props/loading/index.cy.js:
--------------------------------------------------------------------------------
1 | ///
2 | import path from 'path'
3 |
4 | context('loading', () => {
5 | it('should not be loading by default', () => {
6 | cy.visit(path.join(__dirname, 'without.html'))
7 | cy.get('.vue-select').click()
8 |
9 | cy.get('.loading').should('not.exist')
10 | })
11 |
12 | it('should not be removed', () => {
13 | cy.visit(path.join(__dirname, 'with.html'))
14 | cy.get('.vue-select').click()
15 |
16 | cy.get('.loading').should('be.visible')
17 | })
18 | })
19 |
--------------------------------------------------------------------------------
/docs/examples.md:
--------------------------------------------------------------------------------
1 | # Examples
2 |
3 |
9 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | ## Reporting Issues
4 |
5 | A great way to contribute to the project is to send a detailed report when you encounter an issue.
6 |
7 | Please make sure to include a reproduction repository or CodeSandBox so that bugs can be reproduced without great
8 | efforts. The better a bug can be reproduced, the faster we can start fixing it!
9 |
10 | ## Pull Requests
11 |
12 | We'd love to see your pull requests, even if it's just to fix a typo!
13 |
14 | However, any significant improvement should be associated to an existing feature request or bug report.
15 |
--------------------------------------------------------------------------------
/cypress/e2e/props/placeholder/index.cy.js:
--------------------------------------------------------------------------------
1 | ///
2 | import path from 'path'
3 |
4 | context('placeholder', () => {
5 | it('should have default placeholder', () => {
6 | cy.visit(path.join(__dirname, 'without.html'))
7 | cy.get('.vue-select').click()
8 |
9 | cy.get('.vue-select').should('contain.html', 'Select option')
10 | })
11 |
12 | it('can customize placeholder', () => {
13 | cy.visit(path.join(__dirname, 'with.html'))
14 | cy.get('.vue-select').click()
15 |
16 | cy.get('.vue-select').should('contain.html', 'Pick option')
17 | })
18 | })
19 |
--------------------------------------------------------------------------------
/cypress/e2e/props/search-placeholder/index.cy.js:
--------------------------------------------------------------------------------
1 | ///
2 | import path from 'path'
3 |
4 | context('search-placeholder', () => {
5 | it('should have default placeholder', () => {
6 | cy.visit(path.join(__dirname, 'without.html'))
7 | cy.get('.vue-select').click()
8 |
9 | cy.get('.vue-select').should('contain.html', 'Type to search')
10 | })
11 |
12 | it('can customize placeholder', () => {
13 | cy.visit(path.join(__dirname, 'with.html'))
14 | cy.get('.vue-select').click()
15 |
16 | cy.get('.vue-select').should('contain.html', 'Search something')
17 | })
18 | })
19 |
--------------------------------------------------------------------------------
/cypress/e2e/props/clear-on-select/index.cy.js:
--------------------------------------------------------------------------------
1 | ///
2 | import path from 'path'
3 |
4 | context('clear on select', () => {
5 | it('should open dropdown', () => {
6 | cy.visit(path.join(__dirname, 'index.html'))
7 | cy.get('.vue-select').click()
8 |
9 | cy.get('input').type('i')
10 | cy.get('input').should('have.value', 'i')
11 | cy.get('.vue-dropdown').children().should('have.length', 1)
12 |
13 | cy.get('.vue-dropdown-item').first().click()
14 | cy.get('input').should('have.value', '')
15 | cy.get('.vue-dropdown').children().should('have.length', 3)
16 | })
17 | })
18 |
--------------------------------------------------------------------------------
/cypress/e2e/props/visible-options/index.cy.js:
--------------------------------------------------------------------------------
1 | ///
2 | import path from 'path'
3 |
4 | context('visible-options', () => {
5 | it('should show all options by default', () => {
6 | cy.visit(path.join(__dirname, 'without.html'))
7 | cy.get('.vue-select').click()
8 |
9 | cy.get('.vue-dropdown-item').children().should('have.length', 3)
10 | })
11 |
12 | it('should only show visible options', () => {
13 | cy.visit(path.join(__dirname, 'with.html'))
14 | cy.get('.vue-select').click()
15 |
16 | cy.get('.vue-dropdown-item').children().should('have.length', 2)
17 | })
18 | })
19 |
--------------------------------------------------------------------------------
/cypress/e2e/props/clear-on-close/index.cy.js:
--------------------------------------------------------------------------------
1 | ///
2 | import path from 'path'
3 |
4 | context('clear on close', () => {
5 | it('should open dropdown', () => {
6 | cy.visit(path.join(__dirname, 'index.html'))
7 | cy.get('.vue-select').click()
8 |
9 | cy.get('input').type('i')
10 | cy.get('input').should('have.value', 'i')
11 | cy.get('.vue-dropdown').children().should('have.length', 1)
12 | cy.get('body').click()
13 |
14 | cy.get('.vue-select').click()
15 | cy.get('input').should('have.value', '')
16 | cy.get('.vue-dropdown').children().should('have.length', 3)
17 | })
18 | })
19 |
--------------------------------------------------------------------------------
/examples/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vue-next-select-examples",
3 | "version": "0.0.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "vite",
7 | "build": "vite build",
8 | "serve": "vite preview"
9 | },
10 | "dependencies": {
11 | "@vuelidate/core": "^2.0.3",
12 | "@vuelidate/validators": "^2.0.4",
13 | "vue": "^3.4.21",
14 | "vue-next-select": "2.10.5",
15 | "vue-router": "^4.3.0",
16 | "vuex": "^4.1.0"
17 | },
18 | "devDependencies": {
19 | "@vitejs/plugin-vue": "^5.0.4",
20 | "@vue/compiler-sfc": "^3.4.21",
21 | "typescript": "^5.4.3",
22 | "vite": "^5.2.6"
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/examples/src/views/LimitingSelections.vue:
--------------------------------------------------------------------------------
1 |
2 | modelValue: {{ JSON.stringify(artists) }}
3 |
4 |
5 |
6 |
7 |
25 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 | ---
8 |
9 | **Is your feature request related to a problem? Please describe.** A clear and concise description of what the problem
10 | is. Ex. I'm always frustrated when [...]
11 |
12 | **Describe the solution you'd like** A clear and concise description of what you want to happen.
13 |
14 | **Describe alternatives you've considered** A clear and concise description of any alternative solutions or features
15 | you've considered.
16 |
17 | **Additional context** Add any other context or screenshots about the feature request here.
18 |
--------------------------------------------------------------------------------
/cypress/e2e/props/collapse-tags/index.cy.js:
--------------------------------------------------------------------------------
1 | ///
2 | import path from 'path'
3 |
4 | context('collapse-tags', () => {
5 | it('should not collapse tags by default', () => {
6 | cy.visit(path.join(__dirname, 'without.html'))
7 | cy.get('.vue-select').click()
8 |
9 | cy.get('.vue-dropdown').children().click({ multiple: true })
10 | cy.get('.vue-tags').should('not.have.class', 'collapsed')
11 | })
12 |
13 | it('should collapse tags', () => {
14 | cy.visit(path.join(__dirname, 'with.html'))
15 | cy.get('.vue-select').click()
16 |
17 | cy.get('.vue-dropdown').children().click({ multiple: true })
18 | cy.get('.vue-tags').should('have.class', 'collapsed')
19 | })
20 | })
21 |
--------------------------------------------------------------------------------
/docs/.vitepress/config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | lang: 'en-US',
3 | base: '/vue-next-select/',
4 | title: 'Vue next select',
5 | description: 'The selecting solution for Vue 3',
6 | markdown: {
7 | code: {
8 | lineNumbers: true,
9 | },
10 | },
11 | themeConfig: {
12 | repo: 'iendeavor/vue-next-select',
13 | docsDir: 'docs',
14 |
15 | lastUpdated: 'Last Updated',
16 |
17 | nav: [
18 | { text: 'Examples', link: '/examples', activeMatch: '/#examples' },
19 | { text: 'API Reference', link: '/api-reference', activeMatch: '/api-reference' },
20 | { text: 'Change Logs', link: 'https://github.com/iendeavor/vue-next-select/blob/main/CHANGELOG.md' },
21 | ],
22 | },
23 | }
24 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": ".",
4 | "outDir": "dist",
5 | "sourceMap": false,
6 | "declaration": true,
7 | "noEmit": true,
8 | "target": "es2016",
9 | "useDefineForClassFields": false,
10 | "module": "esnext",
11 | "moduleResolution": "node",
12 | "allowJs": true,
13 | "strict": true,
14 | "noUnusedLocals": true,
15 | "experimentalDecorators": true,
16 | "noImplicitAny": false,
17 | "resolveJsonModule": true,
18 | "esModuleInterop": true,
19 | "removeComments": false,
20 | "jsx": "preserve",
21 | "lib": ["esnext", "dom"],
22 | "types": ["jest", "node"],
23 | "rootDir": "."
24 | },
25 | "include": ["src", "test", "test-dts"]
26 | }
27 |
--------------------------------------------------------------------------------
/cypress/e2e/props/empty-model-value/index.cy.js:
--------------------------------------------------------------------------------
1 | ///
2 | import path from 'path'
3 |
4 | context('empty-model-value', () => {
5 | it('should use null as default empty-model-value', () => {
6 | cy.visit(path.join(__dirname, 'default.html'))
7 | cy.get('.vue-select').click()
8 |
9 | cy.get('.vue-dropdown-item.selected').click()
10 | cy.get('#modelValue').should('have.text', 'null')
11 | })
12 |
13 | it('should use empty string as empty-model-value', () => {
14 | cy.visit(path.join(__dirname, 'empty-string.html'))
15 | cy.get('.vue-select').click()
16 |
17 | cy.get('.vue-dropdown-item.selected').click()
18 | cy.get('#modelValue').should('have.text', '')
19 | })
20 | })
21 |
--------------------------------------------------------------------------------
/examples/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | -
5 |
6 | {{
7 | route.path
8 | .slice(1)
9 | .split('-')
10 | .map(part => part.charAt(0).toUpperCase() + part.slice(1))
11 | .join(' ')
12 | }}
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
33 |
--------------------------------------------------------------------------------
/examples/src/views/Vuex.vue:
--------------------------------------------------------------------------------
1 |
2 | state: {{ JSON.stringify(state.techStack) }}
3 |
4 | commit('SET_TECH_STACK', value)"
7 | :options="options"
8 | />
9 |
10 |
11 |
31 |
--------------------------------------------------------------------------------
/examples/src/views/BasicUsage.vue:
--------------------------------------------------------------------------------
1 |
2 | modelValue: {{ JSON.stringify(techStack) }}
3 |
4 |
5 |
6 |
7 |
29 |
--------------------------------------------------------------------------------
/cypress/e2e/props/disabled/group/index.cy.js:
--------------------------------------------------------------------------------
1 | ///
2 | import path from 'path'
3 |
4 | context('disabled group', () => {
5 | it('all of values should be disabled if group is disabled', () => {
6 | cy.visit(path.join(__dirname, 'disabled-by-group.html'))
7 | cy.get('.vue-select').click()
8 |
9 | cy.get('.vue-dropdown-item.disabled').should('have.length', 3)
10 | cy.get('.vue-dropdown-item').last().should('not.have.class', 'disabled')
11 | })
12 |
13 | it("should be disabled if all of group's value are disabled", () => {
14 | cy.visit(path.join(__dirname, 'disabled-by-values.html'))
15 | cy.get('.vue-select').click()
16 |
17 | cy.get('.vue-dropdown-item.group.disabled').should('have.length', 1)
18 | })
19 | })
20 |
--------------------------------------------------------------------------------
/cypress/support/e2e.js:
--------------------------------------------------------------------------------
1 | // ***********************************************************
2 | // This example support/index.js is processed and
3 | // loaded automatically before your test files.
4 | //
5 | // This is a great place to put global configuration and
6 | // behavior that modifies Cypress.
7 | //
8 | // You can change the location of this file or turn off
9 | // automatically serving support files with the
10 | // 'supportFile' configuration option.
11 | //
12 | // You can read more here:
13 | // https://on.cypress.io/configuration
14 | // ***********************************************************
15 |
16 | // Import commands.js using ES2015 syntax:
17 | import './commands'
18 | import 'cypress-plugin-tab'
19 |
20 | // Alternatively you can use CommonJS syntax:
21 | // require('./commands')
22 |
--------------------------------------------------------------------------------
/examples/src/views/CustomEmptyModelValue.vue:
--------------------------------------------------------------------------------
1 |
2 | modelValue: {{ JSON.stringify(techStack) }}
3 |
4 |
5 |
6 |
7 |
29 |
--------------------------------------------------------------------------------
/cypress/e2e/props/value-by/index.cy.js:
--------------------------------------------------------------------------------
1 | ///
2 | import path from 'path'
3 |
4 | context('value-by', () => {
5 | it('should not match any value', () => {
6 | cy.visit(path.join(__dirname, 'without.html'))
7 | cy.get('.vue-select').click()
8 |
9 | cy.get('.vue-dropdown-item.selected').should('have.length', 0)
10 | })
11 |
12 | it('should works with path', () => {
13 | cy.visit(path.join(__dirname, 'with-path.html'))
14 | cy.get('.vue-select').click()
15 |
16 | cy.get('.vue-dropdown-item.selected').should('have.length', 1)
17 | })
18 |
19 | it('should works with function', () => {
20 | cy.visit(path.join(__dirname, 'with-function.html'))
21 | cy.get('.vue-select').click()
22 |
23 | cy.get('.vue-dropdown-item.selected').should('have.length', 1)
24 | })
25 | })
26 |
--------------------------------------------------------------------------------
/cypress/e2e/events/selected/index.cy.js:
--------------------------------------------------------------------------------
1 | ///
2 | import path from 'path'
3 |
4 | let shouldReject = false
5 | const setReject = () => {
6 | shouldReject = true
7 | }
8 | const setResolve = () => {
9 | shouldReject = false
10 | }
11 | const finish = () => {
12 | if (shouldReject) throw Error()
13 | }
14 |
15 | context('selected event', () => {
16 | it('should fire event', () => {
17 | setReject()
18 | cy.visit(path.join(__dirname, 'index.html')).then(window => {
19 | cy.get('.vue-select').click()
20 | cy.then(() => {
21 | window.removeEventListener('selected-custom-event', setResolve)
22 | window.addEventListener('selected-custom-event', setResolve)
23 | })
24 | cy.get('.vue-dropdown').children().first().next().click()
25 | cy.then(finish)
26 | })
27 | })
28 | })
29 |
--------------------------------------------------------------------------------
/cypress/e2e/props/taggable/index.cy.js:
--------------------------------------------------------------------------------
1 | ///
2 | import path from 'path'
3 |
4 | context('taggable', () => {
5 | it('should not have tag by default', () => {
6 | cy.visit(path.join(__dirname, 'without.html'))
7 | cy.get('.vue-select').click()
8 |
9 | cy.get('.vue-tag.selected').should('have.length', 0)
10 | })
11 |
12 | it('should show selected options as tags', () => {
13 | cy.visit(path.join(__dirname, 'with.html'))
14 | cy.get('.vue-select').click()
15 |
16 | cy.get('.vue-tag.selected').should('have.length', 1)
17 |
18 | cy.get('.vue-dropdown').children().first().next().click()
19 | cy.get('.vue-tag.selected').should('have.length', 2)
20 |
21 | cy.get('.vue-dropdown').children().first().next().next().click()
22 | cy.get('.vue-tag.selected').should('have.length', 3)
23 | })
24 | })
25 |
--------------------------------------------------------------------------------
/cypress/e2e/events/search-input/index.cy.js:
--------------------------------------------------------------------------------
1 | ///
2 | import path from 'path'
3 |
4 | let shouldReject = false
5 | const setReject = () => {
6 | shouldReject = true
7 | }
8 | const setResolve = () => {
9 | shouldReject = false
10 | }
11 | const finish = () => {
12 | if (shouldReject) throw Error()
13 | }
14 |
15 | context('search:input event', () => {
16 | it('should fire event after type something', () => {
17 | setReject()
18 | cy.visit(path.join(__dirname, 'index.html')).then(window => {
19 | cy.get('.vue-select').click()
20 | cy.then(() => {
21 | window.removeEventListener('search:input-custom-event', setResolve)
22 | window.addEventListener('search:input-custom-event', setResolve)
23 | })
24 | cy.get('.vue-input').type('i')
25 | cy.then(finish)
26 | })
27 | })
28 | })
29 |
--------------------------------------------------------------------------------
/test/unit/multiple.spec.ts:
--------------------------------------------------------------------------------
1 | import { reactive } from 'vue'
2 | import { mount } from '@vue/test-utils'
3 | // @ts-ignore
4 | import VueSelect from '../../'
5 | import { clickAllDropdownItemElements } from '../dom-utils'
6 |
7 | it('it should select when init with one option', async () => {
8 | const state = reactive({
9 | model: [0],
10 | options: [0, 1, 2],
11 | })
12 | const app = {
13 | setup() {
14 | return {
15 | state,
16 | }
17 | },
18 | components: {
19 | VueSelect,
20 | },
21 | template: `
22 |
27 | `,
28 | }
29 | const wrapper = mount(app)
30 |
31 | await wrapper.trigger('focus')
32 | await clickAllDropdownItemElements(wrapper)
33 | expect(state.model).toStrictEqual([1, 2])
34 | })
35 |
--------------------------------------------------------------------------------
/test/unit/disabled.spec.ts:
--------------------------------------------------------------------------------
1 | import { reactive } from 'vue'
2 | import { mount } from '@vue/test-utils'
3 | // @ts-ignore
4 | import VueSelect from '../../'
5 | import { clickFirstDeleteIconElement } from '../dom-utils'
6 |
7 | it('should disable tag', async () => {
8 | const state = reactive({
9 | model: [0],
10 | options: [0, 1, 2],
11 | })
12 | const app = {
13 | setup() {
14 | return {
15 | state,
16 | }
17 | },
18 | components: {
19 | VueSelect,
20 | },
21 | template: `
22 |
29 | `,
30 | }
31 | const wrapper = mount(app)
32 | await wrapper.trigger('focus')
33 |
34 | await clickFirstDeleteIconElement(wrapper)
35 | expect(state.model).toStrictEqual([0])
36 | })
37 |
--------------------------------------------------------------------------------
/examples/src/route.ts:
--------------------------------------------------------------------------------
1 | import { createRouter, createWebHistory, RouteRecordRaw, RouterOptions } from 'vue-router'
2 |
3 | const viewNames = [
4 | 'basic-usage',
5 | 'creatable',
6 | 'select-all',
7 | 'limiting-selections',
8 | 'group',
9 | 'remote-searching',
10 | 'custom-dropdown',
11 | 'custom-tag',
12 | 'custom-empty-model-value',
13 | 'vuex',
14 | 'vuelidate',
15 | 'playground',
16 | ]
17 |
18 | export const routes: RouterOptions['routes'] = viewNames.map(viewName => ({
19 | path: `/${viewName}`,
20 | component: () =>
21 | import(
22 | `./views/${viewName
23 | .split('-')
24 | .map(part => part.charAt(0).toUpperCase() + part.slice(1))
25 | .join('')}.vue`
26 | ),
27 | }))
28 | routes.unshift({
29 | path: '/',
30 | redirect: '/basic-usage',
31 | })
32 |
33 | export default createRouter({
34 | history: createWebHistory('vue-next-select'),
35 | routes,
36 | })
37 |
--------------------------------------------------------------------------------
/test/unit/close-on-select.spec.ts:
--------------------------------------------------------------------------------
1 | import { reactive } from 'vue'
2 | import { mount } from '@vue/test-utils'
3 | // @ts-ignore
4 | import VueSelect from '../../'
5 | import { getAllDropdownItemElements, clickFirstDropdownItemElement } from '../dom-utils'
6 |
7 | it('should not close by default', async () => {
8 | const state = reactive({
9 | model: [],
10 | options: [0, 1, 2],
11 | })
12 | const app = {
13 | setup() {
14 | return {
15 | state,
16 | }
17 | },
18 | components: {
19 | VueSelect,
20 | },
21 | template: `
22 |
27 | `,
28 | }
29 | const wrapper = mount(app)
30 | await wrapper.trigger('focus')
31 |
32 | await clickFirstDropdownItemElement(wrapper)
33 | expect(getAllDropdownItemElements(wrapper).length).toBe(3)
34 | })
35 |
--------------------------------------------------------------------------------
/cypress/e2e/slots/label/index.cy.js:
--------------------------------------------------------------------------------
1 | ///
2 | import path from 'path'
3 |
4 | context('label', () => {
5 | it('should show the placeholder then the selected', () => {
6 | cy.visit(path.join(__dirname, 'index.html'))
7 | cy.get('.vue-input').should('have.text', 'Select option')
8 | cy.get('.vue-select').click()
9 | cy.get('.vue-dropdown').children().first().click()
10 | cy.get('.vue-input').should('have.text', 'I')
11 | })
12 |
13 | it('should show the selected options count', () => {
14 | cy.visit(path.join(__dirname, 'multiple.html'))
15 | cy.get('.vue-input').should('have.text', 'Select option')
16 | cy.get('.vue-select').click()
17 | cy.get('.vue-dropdown').children().first().click()
18 | cy.get('.vue-input').should('have.text', '1')
19 | cy.get('.vue-dropdown').children().first().next().click()
20 | cy.get('.vue-input').should('have.text', '2')
21 | })
22 | })
23 |
--------------------------------------------------------------------------------
/cypress/support/commands.js:
--------------------------------------------------------------------------------
1 | // ***********************************************
2 | // This example commands.js shows you how to
3 | // create various custom commands and overwrite
4 | // existing commands.
5 | //
6 | // For more comprehensive examples of custom
7 | // commands please read more here:
8 | // https://on.cypress.io/custom-commands
9 | // ***********************************************
10 | //
11 | //
12 | // -- This is a parent command --
13 | // Cypress.Commands.add("login", (email, password) => { ... })
14 | //
15 | //
16 | // -- This is a child command --
17 | // Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })
18 | //
19 | //
20 | // -- This is a dual command --
21 | // Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
22 | //
23 | //
24 | // -- This will overwrite an existing command --
25 | // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
26 |
--------------------------------------------------------------------------------
/test/unit/visible-option.spec.ts:
--------------------------------------------------------------------------------
1 | import { reactive } from 'vue'
2 | import { mount } from '@vue/test-utils'
3 | // @ts-ignore
4 | import VueSelect from '../../'
5 | import { clickAllDropdownItemElements } from '../dom-utils'
6 |
7 | it('should works', async () => {
8 | const state = reactive({
9 | model: [],
10 | options: [0, 1, 2],
11 | visibleOptions: [0, 1],
12 | })
13 |
14 | const app = {
15 | setup() {
16 | return {
17 | state,
18 | }
19 | },
20 | components: {
21 | VueSelect,
22 | },
23 | template: `
24 |
30 | `,
31 | }
32 | const wrapper = mount(app)
33 |
34 | await wrapper.trigger('focus')
35 | await clickAllDropdownItemElements(wrapper)
36 | expect(state.model).toStrictEqual([0, 1])
37 | })
38 |
--------------------------------------------------------------------------------
/cypress/e2e/props/label-by/index.cy.js:
--------------------------------------------------------------------------------
1 | ///
2 | import path from 'path'
3 |
4 | context('label-by', () => {
5 | it('should use whole object as label', () => {
6 | cy.visit(path.join(__dirname, 'without.html'))
7 | cy.get('.vue-select').click()
8 |
9 | cy.get('.vue-dropdown-item.selected').should(
10 | 'have.text',
11 | `
12 | {
13 | "deep": {
14 | "label": "I",
15 | "value": "i"
16 | }
17 | }
18 | `.trim(),
19 | )
20 | })
21 |
22 | it('should works with path', () => {
23 | cy.visit(path.join(__dirname, 'with-path.html'))
24 | cy.get('.vue-select').click()
25 |
26 | cy.get('.vue-dropdown-item.selected').should('have.text', 'I')
27 | })
28 |
29 | it('should works with function', () => {
30 | cy.visit(path.join(__dirname, 'with-function.html'))
31 | cy.get('.vue-select').click()
32 |
33 | cy.get('.vue-dropdown-item.selected').should('have.text', 'I')
34 | })
35 | })
36 |
--------------------------------------------------------------------------------
/cypress/e2e/behavior/search/index.cy.js:
--------------------------------------------------------------------------------
1 | ///
2 | import path from 'path'
3 |
4 | context('search', () => {
5 | it('should keep searching text after select option', () => {
6 | cy.visit(path.join(__dirname, 'index.html'))
7 | cy.get('.vue-select').click()
8 |
9 | cy.focused().type('e')
10 | cy.focused().should('have.value', 'e')
11 |
12 | cy.get('.vue-dropdown').children().click({ multiple: true })
13 | cy.focused().should('have.value', 'e')
14 | })
15 |
16 | it('should keep searching text after removing selected option', () => {
17 | cy.visit(path.join(__dirname, 'index.html'))
18 | cy.get('.vue-select').click()
19 |
20 | cy.focused().type('e')
21 | cy.focused().should('have.value', 'e')
22 |
23 | cy.get('.vue-dropdown').children().click({ multiple: true })
24 | cy.get('.vue-tag.selected').children().filter('.icon.delete').click({ multiple: true })
25 | cy.focused().should('have.value', 'e')
26 | })
27 | })
28 |
--------------------------------------------------------------------------------
/cypress/e2e/props/max/index.cy.js:
--------------------------------------------------------------------------------
1 | ///
2 | import path from 'path'
3 |
4 | context('max', () => {
5 | it('can be removed by default', () => {
6 | cy.visit(path.join(__dirname, 'without.html'))
7 | cy.get('.vue-select').click()
8 |
9 | cy.get('.vue-dropdown').children().first().next().click()
10 | cy.get('.vue-dropdown-item.selected').should('have.length', 2)
11 |
12 | cy.get('.vue-dropdown').children().first().next().next().click()
13 | cy.get('.vue-dropdown-item.selected').should('have.length', 3)
14 | })
15 |
16 | it('should not be removed', () => {
17 | cy.visit(path.join(__dirname, 'with.html'))
18 | cy.get('.vue-select').click()
19 |
20 | cy.get('.vue-dropdown').children().first().next().click()
21 | cy.get('.vue-dropdown-item.selected').should('have.length', 2)
22 |
23 | cy.get('.vue-dropdown').children().first().next().next().click()
24 | cy.get('.vue-dropdown-item.selected').should('have.length', 2)
25 | })
26 | })
27 |
--------------------------------------------------------------------------------
/examples/src/views/CustomTag.vue:
--------------------------------------------------------------------------------
1 |
2 | modelValue: {{ JSON.stringify(colors) }}
3 |
4 |
5 |
6 |
7 | {{ option }}
8 | x
9 |
10 |
11 |
12 |
13 |
14 |
32 |
33 |
38 |
--------------------------------------------------------------------------------
/examples/src/views/SelectAll.vue:
--------------------------------------------------------------------------------
1 |
2 | modelValue: {{ JSON.stringify(option) }}
3 |
4 |
5 |
6 |
7 |
31 |
--------------------------------------------------------------------------------
/cypress/e2e/props/multiple/index.cy.js:
--------------------------------------------------------------------------------
1 | ///
2 | import path from 'path'
3 |
4 | context('multiple', () => {
5 | it('can only select single option by default', () => {
6 | cy.visit(path.join(__dirname, 'without.html'))
7 | cy.get('.vue-select').click()
8 |
9 | cy.get('.vue-dropdown').children().first().next().click()
10 | cy.get('.vue-dropdown-item.selected').should('have.length', 1)
11 |
12 | cy.get('.vue-dropdown').children().first().next().next().click()
13 | cy.get('.vue-dropdown-item.selected').should('have.length', 1)
14 | })
15 |
16 | it('can select multiple option', () => {
17 | cy.visit(path.join(__dirname, 'with.html'))
18 | cy.get('.vue-select').click()
19 |
20 | cy.get('.vue-dropdown').children().first().next().click()
21 | cy.get('.vue-dropdown-item.selected').should('have.length', 2)
22 |
23 | cy.get('.vue-dropdown').children().first().next().next().click()
24 | cy.get('.vue-dropdown-item.selected').should('have.length', 3)
25 | })
26 | })
27 |
--------------------------------------------------------------------------------
/src/crud.js:
--------------------------------------------------------------------------------
1 | export const isSameOption = (option1, option2, { valueBy }) => {
2 | return valueBy(option1) === valueBy(option2)
3 | }
4 |
5 | export const hasOption = (options, option, { valueBy }) => {
6 | return options.some(_option => isSameOption(_option, option, { valueBy }))
7 | }
8 |
9 | export const getOptionByValue = (options, value, { valueBy }) => {
10 | return options.find(option => valueBy(option) === value)
11 | }
12 |
13 | export const addOption = (selectedOptions, option, { max, valueBy }) => {
14 | if (hasOption(selectedOptions, option, { valueBy })) return selectedOptions
15 | if (selectedOptions.length >= max) return selectedOptions
16 |
17 | return selectedOptions.concat(option)
18 | }
19 |
20 | export const removeOption = (selectedOptions, option, { min, valueBy }) => {
21 | if (hasOption(selectedOptions, option, { valueBy }) === false) return selectedOptions
22 | if (selectedOptions.length <= min) return selectedOptions
23 |
24 | return selectedOptions.filter(_option => isSameOption(_option, option, { valueBy }) === false)
25 | }
26 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Ernest
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: bug
6 | assignees: ''
7 | ---
8 |
9 | **Describe the bug** A clear and concise description of what the bug is.
10 |
11 | **To Reproduce** A reproduce link:
12 | https://codesandbox.io/s/github/iendeavor/vue-next-select/tree/main/examples?file=/src/views/Playground.vue
13 |
14 | Steps to reproduce the behavior:
15 |
16 | 1. Go to '...'
17 | 2. Click on '....'
18 | 3. Scroll down to '....'
19 | 4. See error
20 |
21 | **Expected behavior** A clear and concise description of what you expected to happen.
22 |
23 | **Screenshots** If applicable, add screenshots to help explain your problem.
24 |
25 | **Desktop (please complete the following information):**
26 |
27 | - OS: [e.g. iOS]
28 | - Browser [e.g. chrome, safari]
29 | - Version [e.g. 22]
30 |
31 | **Smartphone (please complete the following information):**
32 |
33 | - Device: [e.g. iPhone6]
34 | - OS: [e.g. iOS8.1]
35 | - Browser [e.g. stock browser, safari]
36 | - Version [e.g. 22]
37 |
38 | **Additional context** Add any other context about the problem here.
39 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Release
2 |
3 | on:
4 | schedule:
5 | - cron: '0 6 * * 3'
6 |
7 | jobs:
8 | release:
9 | runs-on: ubuntu-latest
10 |
11 | steps:
12 | - uses: actions/checkout@v4.2.2
13 |
14 | - name: Use Node.js
15 | uses: actions/setup-node@v4.1.0
16 | with:
17 | node-version-file: .nvmrc
18 |
19 | - name: Use PNPM
20 | uses: pnpm/action-setup@v4.0.0
21 | with:
22 | version: 7
23 |
24 | - name: Install
25 | run: pnpm install
26 |
27 | - name: Lint
28 | run: pnpm run lint
29 |
30 | - name: Build
31 | run: pnpm run build
32 |
33 | - name: Test
34 | run: pnpm run test
35 |
36 | - name: Release
37 | run: |
38 | git config --local user.email "github-actions[bot]@users.noreply.github.com"
39 | git config --local user.name "github-actions[bot]"
40 | pnpm run release
41 |
42 | - name: Create Pull Request
43 | uses: peter-evans/create-pull-request@v7.0.6
44 | with:
45 | delete-branch: true
46 | reviewers: 'iendeavor'
47 |
--------------------------------------------------------------------------------
/.github/workflows/docs.yml:
--------------------------------------------------------------------------------
1 | name: Docs
2 |
3 | on:
4 | push:
5 | branches: [main]
6 |
7 | jobs:
8 | docs:
9 | runs-on: ubuntu-latest
10 |
11 | steps:
12 | - uses: actions/checkout@v4.2.2
13 |
14 | - name: Use Node.js
15 | uses: actions/setup-node@v4.1.0
16 | with:
17 | node-version-file: .nvmrc
18 |
19 | - name: Use PNPM
20 | uses: pnpm/action-setup@v4.0.0
21 | with:
22 | version: 7
23 |
24 | - name: Setup user
25 | run: |
26 | git config --global user.email actions@users.noreply.github.com
27 | git config --global user.name "Action"
28 |
29 | - name: Build docs
30 | run: |
31 | cd docs
32 | pnpm install
33 | pnpm run docs:build
34 | cd .vitepress/dist
35 | git init
36 | git add -A
37 | git commit -m 'deploy'
38 |
39 | - name: Deploy docs
40 | uses: ad-m/github-push-action@v0.8.0
41 | with:
42 | github_token: ${{ secrets.GITHUB_TOKEN }}
43 | branch: 'docs'
44 | force: true
45 | directory: './docs/.vitepress/dist'
46 |
--------------------------------------------------------------------------------
/script/build.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs')
2 | const rimraf = require('rimraf')
3 | const esbuild = require('esbuild')
4 | const packageJson = require('../package.json')
5 |
6 | const toCamelCase = name =>
7 | name
8 | .split('-')
9 | .map(str => str.charAt(0).toUpperCase() + str.slice(1))
10 | .join('')
11 |
12 | rimraf.sync('dist/*')
13 |
14 | const commonOptions = {
15 | bundle: true,
16 | write: true,
17 | entryPoints: ['src/index.js'],
18 | outfile: `dist/${packageJson.name}`,
19 | globalName: toCamelCase(packageJson.name),
20 | }
21 | const platforms = ['browser', 'node']
22 | const formats = ['iife', 'cjs', 'esm']
23 | for (const format of formats) {
24 | for (const platform of platforms) {
25 | const option = {
26 | ...commonOptions,
27 | format,
28 | platform,
29 | }
30 |
31 | option.outfile = commonOptions.outfile + '.' + format + '.' + platform + '.js'
32 | esbuild.buildSync(option)
33 |
34 | option.outfile = commonOptions.outfile + '.' + format + '.' + platform + '.min.js'
35 | option.minify = true
36 | esbuild.buildSync(option)
37 | }
38 | }
39 |
40 | fs.createReadStream('src/index.css').pipe(fs.createWriteStream('dist/index.css'))
41 |
--------------------------------------------------------------------------------
/cypress/e2e/behavior/tab/index.cy.js:
--------------------------------------------------------------------------------
1 | ///
2 | import path from 'path'
3 |
4 | context('tab', () => {
5 | it('should tab', () => {
6 | cy.visit(path.join(__dirname, 'with-taggable.html'))
7 |
8 | cy.get('.vue-select').click()
9 | cy.get('input').should('be.focused')
10 |
11 | cy.focused().tab({ shift: true })
12 | cy.get('#previous-button').should('be.focused')
13 |
14 | cy.focused().tab()
15 | cy.get('input').should('be.focused')
16 |
17 | cy.focused().tab()
18 | cy.get('#next-button').should('be.focused')
19 |
20 | cy.focused().tab({ shift: true })
21 | cy.get('input').should('be.focused')
22 | })
23 |
24 | it('should tab', () => {
25 | cy.visit(path.join(__dirname, 'without-taggable.html'))
26 |
27 | cy.get('.vue-select').click()
28 | cy.get('input').should('be.focused')
29 |
30 | cy.focused().tab({ shift: true })
31 | cy.get('#previous-button').should('be.focused')
32 |
33 | cy.focused().tab()
34 | cy.get('input').should('be.focused')
35 |
36 | cy.focused().tab()
37 | cy.get('#next-button').should('be.focused')
38 |
39 | cy.focused().tab({ shift: true })
40 | cy.get('input').should('be.focused')
41 | })
42 | })
43 |
--------------------------------------------------------------------------------
/examples/src/views/CustomDropdown.vue:
--------------------------------------------------------------------------------
1 |
2 | modelValue: {{ JSON.stringify(country) }}
3 |
4 |
5 |
6 |
7 | {{ selected.flag }} {{ selected.label }}
8 |
9 | Select option
10 |
11 |
12 | {{ option.flag }} {{ option.label }}
13 |
14 |
15 |
16 |
17 |
48 |
--------------------------------------------------------------------------------
/.github/workflows/examples.yml:
--------------------------------------------------------------------------------
1 | name: Examples
2 |
3 | on:
4 | push:
5 | branches: [main]
6 |
7 | jobs:
8 | bump:
9 | runs-on: ubuntu-latest
10 |
11 | steps:
12 | - uses: actions/checkout@v4.2.2
13 | with:
14 | ref: main
15 |
16 | - name: Use Node.js
17 | uses: actions/setup-node@v4.1.0
18 | with:
19 | node-version-file: .nvmrc
20 |
21 | - name: Use PNPM
22 | uses: pnpm/action-setup@v4.0.0
23 | with:
24 | version: 7
25 |
26 | - name: Setup user
27 | run: |
28 | git config --global user.email actions@users.noreply.github.com
29 | git config --global user.name "Action"
30 |
31 | - name: Bump version
32 | run: |
33 | git init
34 | cd examples
35 | eval $(echo pnpm add vue-next-select@$(cat ../package.json | head -3 | sed 1,2d | sed 's/^.*\: "//' | sed 's/",$//'))
36 | git add .
37 | git commit -m 'chore(bot): bump vue-next-select' || exit 0
38 | cd ..
39 |
40 | - name: Push
41 | uses: ad-m/github-push-action@v0.8.0
42 | with:
43 | github_token: ${{ secrets.GITHUB_TOKEN }}
44 | branch: 'main'
45 | directory: '.'
46 |
--------------------------------------------------------------------------------
/cypress/e2e/a11y/kdb-interaction/single.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/cypress/e2e/a11y/role/listbox/disabled.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/cypress/e2e/a11y/role/listbox/loading.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/cypress/e2e/a11y/kdb-interaction/multiple.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/cypress/e2e/a11y/role/listbox/multiple.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/examples/src/views/Vuelidate.vue:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
49 |
50 |
55 |
--------------------------------------------------------------------------------
/cypress/e2e/props/empty-model-value/default.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/cypress/e2e/props/loading/without.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/cypress/e2e/props/multiple/without.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/cypress/e2e/props/min/single-without-min.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/cypress/e2e/props/placeholder/without.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/cypress/e2e/props/searchable/without.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/cypress/e2e/props/disabled/single/without.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/cypress/e2e/behavior/pointer/without-searchable.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/cypress/e2e/props/close-on-select/single/without.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/cypress/e2e/props/loading/with.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/cypress/e2e/props/max/without.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/cypress/e2e/props/min/without.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/cypress/e2e/a11y/role/option/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/cypress/e2e/props/multiple/with.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/cypress/e2e/props/taggable/without.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/cypress/e2e/props/disabled/single/with.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/cypress/e2e/props/min/single-with-min.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/cypress/e2e/behavior/open-and-close/without-searchable-and-taggable.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/cypress/e2e/props/disabled/multiple/without.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/cypress/e2e/props/label-by/without.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/cypress/e2e/props/visible-options/without.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/cypress/e2e/behavior/pointer/highlight-selected.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/cypress/e2e/behavior/pointer/with-searchable.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/cypress/e2e/props/empty-model-value/empty-string.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/cypress/e2e/props/search-placeholder/without.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/cypress/e2e/behavior/open-and-close/with-searchable.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/cypress/e2e/props/close-on-select/multiple/without.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/cypress/e2e/props/close-on-select/single/with.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/cypress/e2e/props/max/with.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/cypress/e2e/props/min/with.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/cypress/e2e/props/placeholder/with.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/cypress/e2e/props/taggable/with.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/cypress/e2e/props/disabled/multiple/with.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/cypress/e2e/props/hide-selected/without.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/cypress/e2e/props/disabled/taggable/without.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/cypress/e2e/behavior/open-and-close/with-taggable.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/cypress/e2e/props/close-on-select/multiple/with.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/cypress/e2e/slots/dropdown-item/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/cypress/e2e/props/label-by/with-path.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/src/components/tags.vue:
--------------------------------------------------------------------------------
1 |
2 |
18 |
19 |
20 |
55 |
--------------------------------------------------------------------------------
/cypress/e2e/behavior/pointer/highlight-selected-with-multiple.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/cypress/e2e/props/disabled/taggable/with.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/cypress/e2e/props/hide-selected/with.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/cypress/e2e/props/collapse-tags/without.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/cypress/e2e/props/search-placeholder/with.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/cypress/e2e/slots/tag/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/cypress/e2e/behavior/open-and-close/with-searchable-and-taggable.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/cypress/e2e/props/collapse-tags/with.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/cypress/e2e/slots/label/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/cypress/e2e/slots/tag/remove.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/cypress/e2e/slots/toggle/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/cypress/e2e/props/value-by/without.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/cypress/e2e/slots/label/multiple.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/cypress/e2e/behavior/tab/without-taggable.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/cypress/e2e/events/focus/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/cypress/e2e/props/label-by/with-function.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/cypress/e2e/props/options/raw-options.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/cypress/e2e/props/visible-options/with.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/cypress/e2e/behavior/tab/with-taggable.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/cypress/e2e/events/removed/single.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/cypress/e2e/events/selected/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/cypress/e2e/props/value-by/with-path.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/cypress/e2e/props/hide-selected/index.cy.js:
--------------------------------------------------------------------------------
1 | ///
2 | import path from 'path'
3 |
4 | context('hide-selected', () => {
5 | it('should not hide selected on init', () => {
6 | cy.visit(path.join(__dirname, 'without.html'))
7 | cy.get('.vue-select').click()
8 |
9 | cy.get('.vue-dropdown').children().should('have.length', 3)
10 | })
11 |
12 | it('should not hide selected after selecting option', () => {
13 | cy.visit(path.join(__dirname, 'without.html'))
14 | cy.get('.vue-select').click()
15 |
16 | cy.get('.vue-dropdown').children().first().next().click()
17 | cy.get('.vue-dropdown').children().should('have.length', 3)
18 | })
19 |
20 | it('should hide selected on init', () => {
21 | cy.visit(path.join(__dirname, 'with.html'))
22 | cy.get('.vue-select').click()
23 |
24 | cy.get('.vue-dropdown').children().should('have.length', 2)
25 | })
26 |
27 | it('should hide selected after selecting option', () => {
28 | cy.visit(path.join(__dirname, 'with.html'))
29 | cy.get('.vue-select').click()
30 |
31 | cy.get('.vue-dropdown').children().first().next().click()
32 | cy.get('.vue-dropdown').children().should('have.length', 1)
33 | })
34 |
35 | it('should not hide after removing option', () => {
36 | cy.visit(path.join(__dirname, 'with.html'))
37 | cy.get('.vue-select').click()
38 |
39 | cy.get('.vue-tags').children().first().click()
40 | cy.get('.vue-dropdown').children().should('have.length', 3)
41 | })
42 | })
43 |
--------------------------------------------------------------------------------
/cypress/e2e/props/searchable/default.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/cypress/e2e/events/blur/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/cypress/e2e/events/removed/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/cypress/e2e/events/search-input/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/cypress/e2e/behavior/pointer/removable.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/cypress/e2e/events/search-focus/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/cypress/e2e/props/value-by/with-function.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/cypress/e2e/props/options/group/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/cypress/e2e/props/model/multiple/change-model.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/cypress/e2e/events/search-change/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/cypress/e2e/props/disabled/group/disabled-by-group.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/cypress/e2e/props/taggable/group/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/cypress/e2e/props/clear-on-close/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/cypress/e2e/props/clear-on-select/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/cypress/e2e/props/searchable/index.cy.js:
--------------------------------------------------------------------------------
1 | ///
2 | import path from 'path'
3 |
4 | context('searchable', () => {
5 | it('should not be searchable by default', () => {
6 | cy.visit(path.join(__dirname, 'without.html'))
7 | cy.get('.vue-select').click()
8 |
9 | cy.get('.vue-select').should('not.contain.html', 'Type to search')
10 | })
11 |
12 | it('should open dropdown', () => {
13 | cy.visit(path.join(__dirname, 'without.html'))
14 | cy.get('.vue-select').click()
15 |
16 | cy.get('.vue-dropdown').should('have.length', 1)
17 | })
18 |
19 | it('should open dropdown', () => {
20 | cy.visit(path.join(__dirname, 'with.html'))
21 | cy.get('.vue-select').click()
22 |
23 | cy.get('.vue-dropdown').should('have.length', 1)
24 | })
25 |
26 | it('filter typing value', () => {
27 | cy.visit(path.join(__dirname, 'with.html'))
28 | cy.get('.vue-select').click()
29 |
30 | cy.get('.vue-input').type('i')
31 | cy.get('.vue-dropdown-item').should('have.length', 1)
32 | })
33 |
34 | it('filter typing value by label by default', () => {
35 | cy.visit(path.join(__dirname, 'default.html'))
36 | cy.get('.vue-select').click()
37 |
38 | cy.get('.vue-input').type('Rails')
39 | cy.get('.vue-dropdown-item').should('have.length', 1)
40 | })
41 |
42 | it('filter typing value (ignore case) by label by default', () => {
43 | cy.visit(path.join(__dirname, 'default.html'))
44 | cy.get('.vue-select').click()
45 |
46 | cy.get('.vue-input').type('RAILS')
47 | cy.get('.vue-dropdown-item').should('have.length', 1)
48 | })
49 | })
50 |
--------------------------------------------------------------------------------
/cypress/e2e/events/search-blur/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/cypress/e2e/props/searchable/prevent-submit.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/cypress/e2e/props/disabled/group/disabled-by-values.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/src/normalize.ts:
--------------------------------------------------------------------------------
1 | import { reactive, computed, toRef, watchEffect } from 'vue'
2 |
3 | const createComputedForGetterFunction = maybePathFunc =>
4 | computed(() => {
5 | return typeof maybePathFunc.value === 'function'
6 | ? maybePathFunc.value
7 | : typeof maybePathFunc.value === 'string'
8 | ? option => maybePathFunc.value.split('.').reduce((value, key) => value[key], option)
9 | : option => option
10 | })
11 |
12 | export default props => {
13 | const normalized = reactive(
14 | {} as {
15 | labelBy: any
16 | valueBy: any
17 | disabledBy: any
18 | groupBy: any
19 | min: any
20 | max: any
21 | options: any
22 | },
23 | )
24 |
25 | const labelBy = createComputedForGetterFunction(toRef(props, 'labelBy'))
26 | watchEffect(() => (normalized.labelBy = labelBy.value))
27 | const valueBy = createComputedForGetterFunction(toRef(props, 'valueBy'))
28 | watchEffect(() => (normalized.valueBy = valueBy.value))
29 | const disabledBy = createComputedForGetterFunction(toRef(props, 'disabledBy'))
30 | watchEffect(() => (normalized.disabledBy = disabledBy.value))
31 | const groupBy = createComputedForGetterFunction(toRef(props, 'groupBy'))
32 | watchEffect(() => (normalized.groupBy = groupBy.value))
33 |
34 | const min = computed(() => (props.multiple ? props.min : Math.min(1, props.min)))
35 | watchEffect(() => (normalized.min = min.value))
36 | const max = computed(() => (props.multiple ? props.max : 1))
37 | watchEffect(() => (normalized.max = max.value))
38 |
39 | watchEffect(() => (normalized.options = props.options))
40 |
41 | return normalized
42 | }
43 |
--------------------------------------------------------------------------------
/cypress/e2e/props/options/disabled-options.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
55 |
56 |
57 |
--------------------------------------------------------------------------------
/cypress/e2e/props/options/group/index.cy.js:
--------------------------------------------------------------------------------
1 | ///
2 | import path from 'path'
3 |
4 | context('group-options', () => {
5 | it('should select values of group option when click an group option', () => {
6 | cy.visit(path.join(__dirname, 'index.html'))
7 | cy.get('.vue-select').click()
8 |
9 | cy.get('.vue-dropdown-item.group').trigger('click')
10 |
11 | cy.get('.vue-dropdown-item.selected').should('have.length', 4)
12 | })
13 |
14 | it('should deselect values of group option when all of its values are selected', () => {
15 | cy.visit(path.join(__dirname, 'index.html'))
16 | cy.get('.vue-select').click()
17 | cy.get('.vue-dropdown-item.group').trigger('click')
18 |
19 | cy.get('.vue-dropdown-item.group').trigger('click')
20 |
21 | cy.get('.vue-dropdown-item.selected').should('have.length', 0)
22 | })
23 |
24 | it('group should be select if and only if all of its values are selected', () => {
25 | cy.visit(path.join(__dirname, 'index.html'))
26 | cy.get('.vue-select').click()
27 |
28 | cy.get('.vue-dropdown-item:not(.group)').first().trigger('click').should('have.class', 'selected')
29 | cy.get('.vue-dropdown-item.group').should('not.have.class', 'selected')
30 |
31 | cy.get('.vue-dropdown-item:not(.group)').first().next().trigger('click').should('have.class', 'selected')
32 | cy.get('.vue-dropdown-item.group').should('not.have.class', 'selected')
33 |
34 | cy.get('.vue-dropdown-item:not(.group)').first().next().next().trigger('click').should('have.class', 'selected')
35 | cy.get('.vue-dropdown-item.group').should('have.class', 'selected')
36 | })
37 | })
38 |
--------------------------------------------------------------------------------
/examples/src/views/Creatable.vue:
--------------------------------------------------------------------------------
1 |
2 | modelValue: {{ JSON.stringify(favoriteLanguages) }}
3 |
4 |
14 |
15 |
16 |
57 |
--------------------------------------------------------------------------------
/cypress/e2e/props/model/single/change-model.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/cypress/e2e/behavior/pointer/with-disabled.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
56 |
57 |
58 |
--------------------------------------------------------------------------------
/cypress/e2e/a11y/role/option/index.cy.js:
--------------------------------------------------------------------------------
1 | ///
2 | import path from 'path'
3 |
4 | context('option', () => {
5 | // https://www.w3.org/TR/wai-aria-1.1/#option
6 |
7 | it('should set aria-selected to true when one option is selected', () => {
8 | cy.visit(path.join(__dirname, 'index.html'))
9 | cy.get('[aria-owns="vs1-listbox"]').click()
10 |
11 | cy.get('[id="vs1-option-0"]').click()
12 |
13 | cy.get('[id="vs1-option-0"]').should('have.attr', 'aria-selected', 'true')
14 | })
15 |
16 | it('should set aria-selected to false when one option is not selected', () => {
17 | cy.visit(path.join(__dirname, 'index.html'))
18 | cy.get('[aria-owns="vs1-listbox"]').click()
19 |
20 | cy.get('[id="vs1-option-0"]').should('have.attr', 'aria-selected', 'false')
21 | })
22 |
23 | it('should set aria-selected to true when one option is selected even that option is disabled', () => {
24 | cy.visit(path.join(__dirname, 'index.html'))
25 | cy.get('[aria-owns="vs1-listbox"]').click()
26 |
27 | cy.get('[id="vs1-option-3"]').should('have.attr', 'aria-selected', 'true')
28 | })
29 |
30 | it('should not aria-selected when one option is not selected and is disabled', () => {
31 | cy.visit(path.join(__dirname, 'index.html'))
32 | cy.get('[aria-owns="vs1-listbox"]').click()
33 |
34 | cy.get('[id="vs1-option-0"]').click()
35 |
36 | cy.get('[id="vs1-option-3"]').should('not.have.attr', 'aria-selected')
37 | })
38 |
39 | it('should set aria-disabled when one option is disabled', () => {
40 | cy.visit(path.join(__dirname, 'index.html'))
41 | cy.get('[aria-owns="vs1-listbox"]').click()
42 |
43 | cy.get('[id="vs1-option-3"]').should('have.attr', 'aria-disabled', 'true')
44 | })
45 | })
46 |
--------------------------------------------------------------------------------
/cypress/e2e/props/min/index.cy.js:
--------------------------------------------------------------------------------
1 | ///
2 | import path from 'path'
3 |
4 | context('min (single)', () => {
5 | it('can be removed by default', () => {
6 | cy.visit(path.join(__dirname, 'single-without-min.html'))
7 | cy.get('.vue-select').click()
8 |
9 | cy.get('.vue-dropdown').children().first().click()
10 | cy.get('.vue-dropdown-item.selected').should('have.length', 0)
11 | })
12 |
13 | it('should not be removed', () => {
14 | cy.visit(path.join(__dirname, 'single-with-min.html'))
15 | cy.get('.vue-select').click()
16 |
17 | cy.get('.vue-dropdown').children().first().click()
18 | cy.get('.vue-dropdown-item.selected').should('have.length', 1)
19 | })
20 | })
21 |
22 | context('min (multiple)', () => {
23 | it('can be removed by default', () => {
24 | cy.visit(path.join(__dirname, 'without.html'))
25 | cy.get('.vue-select').click()
26 |
27 | cy.get('.vue-dropdown').children().first().click()
28 | cy.get('.vue-dropdown-item.selected').should('have.length', 0)
29 | })
30 |
31 | it('should not be removed', () => {
32 | cy.visit(path.join(__dirname, 'with.html'))
33 | cy.get('.vue-select').click()
34 |
35 | cy.get('.vue-dropdown').children().first().click()
36 | cy.get('.vue-dropdown-item.selected').should('have.length', 1)
37 |
38 | cy.get('.vue-dropdown').children().first().next().click()
39 | cy.get('.vue-dropdown').children().first().next().click()
40 | cy.get('.vue-dropdown-item.selected').should('have.length', 2)
41 |
42 | cy.get('.vue-dropdown').children().first().next().next().click()
43 | cy.get('.vue-dropdown').children().first().next().next().click()
44 | cy.get('.vue-dropdown-item.selected').should('have.length', 2)
45 | })
46 | })
47 |
--------------------------------------------------------------------------------
/cypress/e2e/props/options/change-options.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/cypress/e2e/slots/loading/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
55 |
56 |
57 |
--------------------------------------------------------------------------------
/cypress/e2e/props/searchable/with.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/cypress/e2e/props/options/change-options-with-object.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/cypress/e2e/behavior/search/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/cypress/e2e/events/removed/index.cy.js:
--------------------------------------------------------------------------------
1 | ///
2 | import path from 'path'
3 |
4 | let shouldReject = false
5 | const setReject = () => {
6 | shouldReject = true
7 | }
8 | const setResolve = () => {
9 | shouldReject = false
10 | }
11 | const finish = () => {
12 | if (shouldReject) throw Error()
13 | }
14 |
15 | context('removed event', () => {
16 | it('should fire event after removing option', () => {
17 | setReject()
18 | cy.visit(path.join(__dirname, 'index.html')).then(window => {
19 | cy.get('.vue-select').click()
20 | cy.then(() => {
21 | window.removeEventListener('removed-custom-event', setResolve)
22 | window.addEventListener('removed-custom-event', setResolve)
23 | })
24 | cy.get('.vue-dropdown').children().first().click()
25 | cy.then(finish)
26 | })
27 | })
28 |
29 | it('should fire event after removing option by clicking tag', () => {
30 | setReject()
31 | cy.visit(path.join(__dirname, 'index.html')).then(window => {
32 | cy.get('.vue-select').click()
33 | cy.then(() => {
34 | window.removeEventListener('removed-custom-event', setResolve)
35 | window.addEventListener('removed-custom-event', setResolve)
36 | })
37 | cy.get('.vue-tags').children().first().click()
38 | cy.then(finish)
39 | })
40 | })
41 |
42 | it('should not fire event when there is no selected option', () => {
43 | setResolve()
44 | cy.visit(path.join(__dirname, 'single.html')).then(window => {
45 | cy.get('.vue-select').click()
46 | cy.then(() => {
47 | window.removeEventListener('removed-custom-event', setReject)
48 | window.addEventListener('removed-custom-event', setReject)
49 | })
50 | cy.get('.vue-dropdown').children().first().next().click()
51 | cy.then(finish)
52 | })
53 | })
54 | })
55 |
--------------------------------------------------------------------------------
/cypress/e2e/events/search-change/index.cy.js:
--------------------------------------------------------------------------------
1 | ///
2 | import path from 'path'
3 |
4 | let shouldReject = false
5 | const setReject = () => {
6 | shouldReject = true
7 | }
8 | const setResolve = () => {
9 | shouldReject = false
10 | }
11 | const finish = () => {
12 | if (shouldReject) throw Error()
13 | }
14 |
15 | context('search:change event', () => {
16 | it('should not fire event after type something', () => {
17 | setResolve()
18 | cy.visit(path.join(__dirname, 'index.html')).then(window => {
19 | cy.get('.vue-select').click()
20 | cy.then(() => {
21 | window.removeEventListener('search:change-custom-event', setReject)
22 | window.addEventListener('search:change-custom-event', setReject)
23 | })
24 | cy.get('.vue-input').type('i')
25 | cy.then(finish)
26 | })
27 | })
28 |
29 | it('should not fire event after direct blur', () => {
30 | setResolve()
31 | cy.visit(path.join(__dirname, 'index.html')).then(window => {
32 | cy.get('.vue-select').click()
33 | cy.then(() => {
34 | window.removeEventListener('search:change-custom-event', setReject)
35 | window.addEventListener('search:change-custom-event', setReject)
36 | })
37 | cy.get('#previous-button').click()
38 | cy.then(finish)
39 | })
40 | })
41 |
42 | it('should fire event after type something and blur', () => {
43 | setReject()
44 | cy.visit(path.join(__dirname, 'index.html')).then(window => {
45 | cy.get('.vue-select').click()
46 | cy.then(() => {
47 | window.removeEventListener('search:change-custom-event', setResolve)
48 | window.addEventListener('search:change-custom-event', setResolve)
49 | })
50 | cy.get('.vue-input').type('i')
51 | cy.get('#previous-button').click()
52 | cy.then(finish)
53 | })
54 | })
55 | })
56 |
--------------------------------------------------------------------------------
/examples/src/views/Group.vue:
--------------------------------------------------------------------------------
1 |
2 | modelValue: {{ JSON.stringify(used) }}
3 |
4 |
5 |
6 |
7 | {{ option.label }}
8 |
9 |
10 | {{ option.label }}
11 |
12 |
13 |
14 |
15 |
16 |
60 |
--------------------------------------------------------------------------------
/cypress/e2e/events/sequence-of-events/single.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
63 |
64 |
65 |
--------------------------------------------------------------------------------
/cypress/e2e/props/model/multiple/change-model-with-object.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
65 |
66 |
67 |
--------------------------------------------------------------------------------