├── .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 | 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 | delete -------------------------------------------------------------------------------- /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 | 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 | 20 | 21 | 33 | -------------------------------------------------------------------------------- /examples/src/views/Vuex.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 31 | -------------------------------------------------------------------------------- /examples/src/views/BasicUsage.vue: -------------------------------------------------------------------------------- 1 | 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 | 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 | 13 | 14 | 32 | 33 | 38 | -------------------------------------------------------------------------------- /examples/src/views/SelectAll.vue: -------------------------------------------------------------------------------- 1 | 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 | 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 | 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 | 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 | 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 | 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 | --------------------------------------------------------------------------------